Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions .github/workflows/agent-platforms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
run: >-
go install
github.com/ivankuznetsov/screenote-cli/cmd/screenote@c28ac8b3b1b720ef60275e5f59db3a96f8cfa98b
- name: Exercise noninteractive project, capture, and feedback commands
- name: Exercise noninteractive project, snapshot, capture, and feedback commands
shell: bash
run: |
set -euo pipefail
Expand All @@ -107,20 +107,33 @@ jobs:
integration_dir=$(mktemp -d "${RUNNER_TEMP}/screenote-live-XXXXXX")
chmod 700 "$integration_dir"
trap 'rm -rf "$integration_dir"' EXIT
python3 - "$integration_dir/capture.png" <<'PY'
python3 - "$integration_dir" <<'PY'
import base64
from pathlib import Path
import sys

Path(sys.argv[1]).write_bytes(base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wl2nWQAAAAASUVORK5CYII="))
Path(sys.argv[1]).chmod(0o600)
root = Path(sys.argv[1])
content = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wl2nWQAAAAASUVORK5CYII=")
for viewport in ("desktop", "tablet", "mobile"):
path = root / f"capture-{viewport}.png"
path.write_bytes(content)
path.chmod(0o600)
PY
launcher=plugins/screenote/scripts/screenote-cli.sh
"$launcher" --check-contract >"$integration_dir/contract.json"
"$launcher" --project "$SCREENOTE_PROJECT" project list >"$integration_dir/projects.json"
python3 plugins/screenote/scripts/screenote_flow.py prepare-snapshot-manifest \
--directory "$integration_dir" --git-commit "$GITHUB_SHA" \
--taken-at "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--entry "ci-release-probe" "Agent plugin release probe" desktop "capture-desktop.png" \
--entry "ci-release-probe" "Agent plugin release probe" tablet "capture-tablet.png" \
--entry "ci-release-probe" "Agent plugin release probe" mobile "capture-mobile.png" \
>"$integration_dir/manifest-result.json"
"$launcher" --project "$SCREENOTE_PROJECT" snapshot \
--manifest "$integration_dir/snapshot.json" --wait 2m >"$integration_dir/snapshot.jsonl"
"$launcher" --project "$SCREENOTE_PROJECT" screenshot create \
--title "Agent plugin release probe" --page "ci-release-probe" \
--file "$integration_dir/capture.png" >"$integration_dir/capture.json"
--file "$integration_dir/capture-desktop.png" >"$integration_dir/capture.json"
"$launcher" --project "$SCREENOTE_PROJECT" page list >"$integration_dir/pages.json"
"$launcher" --project "$SCREENOTE_PROJECT" screenshot list \
--page "$SCREENOTE_TEST_PAGE_ID" --limit 100 --offset 0 >"$integration_dir/screenshots.json"
Expand All @@ -133,11 +146,15 @@ jobs:
--annotation "$SCREENOTE_TEST_ANNOTATION_ID" \
--body "Automated release compatibility probe completed." >"$integration_dir/comment.json"
python3 - "$integration_dir" <<'PY'
import json
import os
from pathlib import Path
import sys

root = Path(sys.argv[1])
snapshot_events = [json.loads(line) for line in (root / "snapshot.jsonl").read_text().splitlines() if line]
if not snapshot_events or snapshot_events[-1].get("event") != "snapshot_ready" or not snapshot_events[-1].get("review_url"):
raise SystemExit("Screenote snapshot integration did not return snapshot_ready.review_url")
sentinel = os.environ["SCREENOTE_TOKEN"].encode()
contaminated = []
for path in root.rglob("*"):
Expand Down
19 changes: 11 additions & 8 deletions docs/screenote-cli-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,26 @@ of duplicated.
| `3` | Invalid/expired authentication or authorization | Stop without trying another auth mechanism |
| Any other nonzero | JSON error code from the CLI | Stop immediately and preserve the machine-readable diagnostic |

Success requires exit zero and one complete valid JSON value. Collection keys,
pagination metadata, and identifiers must match the shipped pinned workflow
contract; the plugin stops rather than inventing missing IDs.
Success requires exit zero and one complete valid JSON value for ordinary
commands. Snapshot publication emits JSON Lines and additionally requires a
final `snapshot_ready` event with `review_url`. Collection keys, pagination
metadata, and identifiers must match the shipped pinned workflow contract; the
plugin stops rather than inventing missing IDs.

## Capture and recovery

`screenote` captures an explicit HTTP(S) page. `snapshot` discovers and
confirms same-origin HTTP(S) routes, then performs repeated per-route captures.
Both use serial native browser automation and one approved `screenshot create`
call per private PNG; the plugin does not invoke a bulk snapshot command.
Both use serial native browser automation, build one complete manifest, and
invoke `snapshot --manifest` once so viewport variants share one logical
version.

Each run creates a unique mode-`0700` directory and mode-`0600` capture files.
User-supplied local upload paths, symlinks, existing destinations, path escapes,
and non-HTTP(S) navigation are rejected. A successful upload deletes its
temporary capture unless retention was requested. A failed capture/upload
retains the unchanged private file and reports its exact recovery path; retries
use a new name.
temporary captures and manifest unless retention was requested. A failed
capture/upload retains the unchanged private directory and reports its exact
recovery path; an unchanged manifest retry resumes the same Snapshot.

## Feedback resolution

Expand Down
3 changes: 2 additions & 1 deletion plugin-surfaces.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
["screenshot", "create"],
["annotation", "list"],
["annotation", "get"],
["comment", "add"]
["comment", "add"],
["snapshot", "--manifest"]
]
},
"plugins": [
Expand Down
51 changes: 28 additions & 23 deletions plugin-surfaces.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@
[
"comment",
"add"
],
[
"snapshot",
"--manifest"
]
],
"artifact": "plugins/screenote/scripts/screenote-approved-commands.sh",
"sha256": "e1ca68f4a7d55bba5f6325b68ef770aef52b33210347b101f31a26f5ac29cdfc"
"sha256": "aaf6706eb1830f4349f3ab62c173c00feea5714bfc2678dce02480205b0b1b92"
},
"plugins": {
"agent-reviewer": {
Expand Down Expand Up @@ -852,26 +856,27 @@
"version": "3.1.0",
"canonical": {
"skills/screenote/SKILL.md": {
"sha256": "118b0da106447ed69eba5343f5f730b5bcff1a087691af07eeb47b4f31bc9291",
"semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1",
"sha256": "2944db045534e4d6f45f9637d26c0b117fb96e3595c584cdf4155a635c875782",
"semantic_sha256": "08f78714c649329f08575cb905597b2e4687bfeb55617ea7c7a22f08295c2209",
"sections": {
"1:screenote — one-page visual review": "37a69d0026e5d9f01b90e4a325920bf1ce411d42142a41f9c9ce9f36e58deca9",
"2:parse the request": "38485a573382dad4b7297c049ec4ee43e74be316bef5c3dba11b5abdb4ab3cf2",
"1:screenote — one-page visual review": "6860d0df6138cb5f476bd78c6be5a9986d11aa8022f56510b21c9876e3b66acb",
"2:parse the request": "07af83c395625a98b29917f770bfd2628eb303cec19c667f92ba8bdc6de46ce0",
"2:resolve a safe target": "329d16bfb9bfa3862ad531e0392ca12ead8ac4d58e175bbcf47abfb516d5be24",
"2:establish the cli and project": "3e721f75c2d0227fb1522d224de9d573d31ad57480f33cfbece0e6b8d5cf0e2b",
"2:existing-image upload mode": "4aeda494603c2484d04bc02a6676c0c188ee851726d1b889ebd9f25c210aa12f",
"2:browser capture and upload mode": "b95ba86e59c30d6f0efde6f3cc460c7cdabd21509012fe065836c21ea904ee02",
"2:report and clean up": "38b91358340d6cc84e27ae602c0489b12b7e6c32062fa2ac2cde81cbb91dd910"
"2:existing-image upload mode": "f1b596ac83c23e33fc9fe64e1be0e81aa6dcf8229a1678cd638c01416c37f8d2",
"2:browser capture and upload mode": "d9887b3a12436b01bb61c4c14f78e1438502b29851eec0479d44cf55e5102696",
"2:build and publish one logical version": "f829329359547291e730570a7c9513752ea128b46fb035efd66d0bd790d046d6",
"2:report and clean up": "6e2a6f1cb431fd682f1faad3d478baafd331075dc9c6e81e1b39fb5841f3f31c"
}
},
"skills/snapshot/SKILL.md": {
"sha256": "7fd2470860cdb8a1de036c1498dd051a94655ddbb2c668afee1100fa4c62c1ce",
"semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7",
"sha256": "a07233be927a686d5d77b6b5276b44ee65c6b878b8513dcaa2f7ec78dea82e8b",
"semantic_sha256": "8932abdb3a46b21fd5f7a3244fe40b33623b7c069d08f05cf8afe0a421383c4b",
"sections": {
"1:snapshot — multi-page visual review": "cf0f569c8f218283f98815967f07a895c63f579c1359b4be5fb5543b15f0d11b",
"1:snapshot — multi-page visual review": "6be3486148325dcc124469e4eff8988f00b33f0e8778d2d4fe7fa13e40cc1026",
"2:preflight": "4a0dd546789198a62058792ec0866f08ac3e9c65d35fc3d856b44a42479c5629",
"2:discover and confirm routes": "219ca5b16548cebf0204c299f51946b257eff32eee3c2c8f1bf7b1a55faf75bb",
"2:capture and publish": "81d60f088f7aeb3e4b7f7e96c50f1e4236ce5fbdea444c3beb243e2a102b06e1"
"2:discover and confirm routes": "5f514b67e742e1beac6d215cb8802e081f26eae46fd07c9698785f188bbfc257",
"2:capture and publish": "61216542ce17a118b8b6feaf46849fa2b8c20d1235270c7dc5fdb921cb4b81df"
}
},
"skills/feedback/SKILL.md": {
Expand All @@ -888,15 +893,15 @@
"resources": {
"references": {
"exists": true,
"sha256": "83c9cefcc74ff9ad3f6940fd34e6f73e9cc1b1fd10eda17d282ce8a870992cba",
"sha256": "b00b4738337cf5b880b9dbbc63c9440422d35d4627ae1d68d85cdf6d699504bc",
"files": [
"references/cli.md",
"references/workflows.json"
]
},
"evals": {
"exists": true,
"sha256": "dae11cf4ea1ce3dda00a8c08d742c89bb00afc2ef0c9dadc009fcc0e46718cff",
"sha256": "232970953ba825529bc265d8bceab433d22e97112048054060a8e02aa2fe0822",
"files": [
"evals/README.md",
"evals/lint-skills-test.sh",
Expand All @@ -907,18 +912,18 @@
},
"scripts/screenote-cli.sh": {
"exists": true,
"sha256": "9f54e4717158e2e9ec1485ed7c610951a671fbfaa688c201d82af127e6f29d41"
"sha256": "7df9b61ee853e1f7003f06ca58deddbb20825352e9b580a31926821a1eba2edb"
},
"scripts/screenote_flow.py": {
"exists": true,
"sha256": "4070946a4706d2c0b60900501750b204167e0c6df57bed3eb4f6257eb6714d85"
"sha256": "f25e5b84001e90ae723c1b71db2b5fc4713fba529bb638f136c7f6705b022321"
}
},
"adapters": {
"pi/skills/screenote/SKILL.md": {
"sha256": "6acc53b130d691fe0fe3288e2079904d52bbb76065b6620524cdcbad7f03be3f",
"canonical": "skills/screenote/SKILL.md",
"canonical_semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1",
"canonical_semantic_sha256": "08f78714c649329f08575cb905597b2e4687bfeb55617ea7c7a22f08295c2209",
"overlays": [
"frontmatter",
"invocation",
Expand All @@ -928,27 +933,27 @@
"openclaw/skills/screenote/SKILL.md": {
"sha256": "a8e0378d09299bc70297800080e348ad928ea914327b3a761ec5804a6a12f018",
"canonical": "skills/screenote/SKILL.md",
"canonical_semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1",
"canonical_semantic_sha256": "08f78714c649329f08575cb905597b2e4687bfeb55617ea7c7a22f08295c2209",
"overlays": [
"frontmatter",
"invocation",
"install-path"
]
},
"pi/skills/snapshot/SKILL.md": {
"sha256": "a7291d9e71b30e440a94b9b2d9d6e2cb001c9ff656fb2bc4e93c659325b4159d",
"sha256": "c935637fe16765d450c68e1bcf60123d2f2af8e1cc362d39838286b2a52ce931",
"canonical": "skills/snapshot/SKILL.md",
"canonical_semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7",
"canonical_semantic_sha256": "8932abdb3a46b21fd5f7a3244fe40b33623b7c069d08f05cf8afe0a421383c4b",
"overlays": [
"frontmatter",
"invocation",
"install-path"
]
},
"openclaw/skills/snapshot/SKILL.md": {
"sha256": "d0bbf6f51de399d7b63fb0cd23ab1f1f097160d4dbe9dca7a0fa11c9e205b763",
"sha256": "adebbedd30e55d544a8600fd6b89b4a3aeb70a5549a9fb65f5e6f7c186aed94e",
"canonical": "skills/snapshot/SKILL.md",
"canonical_semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7",
"canonical_semantic_sha256": "8932abdb3a46b21fd5f7a3244fe40b33623b7c069d08f05cf8afe0a421383c4b",
"overlays": [
"frontmatter",
"invocation",
Expand Down
13 changes: 13 additions & 0 deletions plugins/screenote/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to the Screenote plugin are documented here.

## [Unreleased]

### Fixed

- Publish all selected viewport captures through one resumable snapshot
manifest so desktop, tablet, and mobile appear as variants of one version
instead of separate desktop-labeled versions.
- Pass the snapshot processing wait explicitly, retain the complete manifest
directory on timeouts or malformed terminal events, and exercise the
manifest-backed path in protected integration tests.
- Accept explicit commit provenance for existing-image and capture publication
outside a Git worktree.

## [3.1.0] - 2026-07-31

### Added
Expand Down
22 changes: 15 additions & 7 deletions plugins/screenote/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,22 @@ Publish an existing image without starting browser automation:
/screenote desktop ./tmp/login.png
```

Multiple explicitly named files may be published serially:
Multiple explicitly named files may be published together:

```text
/screenote ./tmp/login-desktop.png ./tmp/login-mobile.png
```

The helper validates file type, extension, image structure, dimensions, size,
and every source-path component for symlinks, then uploads a new private copy.
It never passes the original path or basename in CLI file or metadata arguments,
and never deletes the source file.
and every source-path component for symlinks, then publishes new private copies
through one manifest. Files identified as viewport variants share one logical
version and appear behind Screenote's desktop/tablet/mobile switcher. The
workflow never passes the original path or basename in CLI file or metadata
arguments, and never deletes the source file.

Snapshot manifests require immutable commit provenance. The workflows use the
current Git commit by default and accept an explicit `git_commit=<7-40 hex>`
value for uploads invoked outside a worktree.

Discover, confirm, and capture an application route set:

Expand All @@ -109,9 +115,10 @@ does not perform the final resolution mutation.
validation and copying into the plugin-owned private directory.
- Native browser automation captures serially to a unique mode-`0700`
directory with mode-`0600` files.
- `scripts/screenote-cli.sh` accepts only project/page/screenshot/annotation
reads, screenshot creation, and comment creation; endpoint/config overrides
are forbidden and arguments remain separate argv elements.
- `scripts/screenote-cli.sh` accepts only approved project/page/screenshot/
annotation reads, snapshot publication, screenshot compatibility upload, and
comment creation; endpoint/config overrides are forbidden and arguments
remain separate argv elements.
- Credentials stay in the CLI's environment or config channels, never command
arguments, generated files, or diagnostics.
- Exit 2 reports missing authentication/project setup, exit 3 reports rejected
Expand All @@ -127,6 +134,7 @@ error mapping, project precedence, capture boundary, cleanup rules, and the

- A compatible `screenote` executable on `PATH`
- A Screenote account and an accessible project
- A Git worktree commit or an explicit `git_commit` value for manifest provenance
- A supported agent host with native browser automation only for fresh capture
workflows; existing-image publication does not need a browser runtime

Expand Down
7 changes: 5 additions & 2 deletions plugins/screenote/evals/lint-skills.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ for tuple in \
'screenshot create' \
'annotation list' \
'annotation get' \
'comment add'; do
'comment add' \
'snapshot --manifest'; do
read -r noun verb <<<"$tuple"
bash -c 'source scripts/screenote-approved-commands.sh; screenote_command_is_approved "$1" "$2"' _ "$noun" "$verb" ||
fail "generated launcher allowlist is missing: $tuple"
Expand All @@ -70,6 +71,9 @@ require_text skills/screenote/SKILL.md 'source path or basename'
require_text references/cli.md 'does not start browser automation'
require_text references/cli.md 'private copy'
require_text scripts/screenote_flow.py 'prepare_existing_image'
require_text scripts/screenote_flow.py 'create_snapshot_manifest'
require_text skills/screenote/SKILL.md 'prepare-snapshot-manifest'
require_text skills/snapshot/SKILL.md 'prepare-snapshot-manifest'

[[ ! -e .mcp.json ]] || fail ".mcp.json must not exist"

Expand All @@ -80,7 +84,6 @@ for forbidden in \
'screenote_browser_use_mcp' \
'create_multi_viewport_screenshot' \
'annotation resolve' \
'snapshot --manifest' \
'--token'; do
if grep -R -n -i -F -- "$forbidden" "${active_files[@]}" >/dev/null 2>&1; then
fail "active plugin surface contains forbidden text: $forbidden"
Expand Down
2 changes: 1 addition & 1 deletion plugins/screenote/openclaw/skills/snapshot/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: snapshot
description: "Discover approved HTTP(S) application routes and publish serial per-route captures through allowlisted screenshot create calls."
description: "Discover approved HTTP(S) application routes and publish one manifest-backed multi-viewport Screenote snapshot."
metadata:
generated-from: skills/snapshot/SKILL.md
generated-for: openclaw
Expand Down
2 changes: 1 addition & 1 deletion plugins/screenote/pi/skills/snapshot/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: snapshot
description: "Discover approved HTTP(S) application routes and publish serial per-route captures through allowlisted screenshot create calls."
description: "Discover approved HTTP(S) application routes and publish one manifest-backed multi-viewport Screenote snapshot."
metadata:
generated-from: skills/snapshot/SKILL.md
generated-for: pi
Expand Down
Loading
Loading