diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 24dc374..f979d3d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -42,7 +42,7 @@ "name": "screenote", "source": "./plugins/screenote", "description": "Capture pages, publish OAuth-authenticated snapshots with the Screenote CLI, and retrieve visual feedback from Claude Code or Codex.", - "version": "3.0.2", + "version": "3.1.0", "author": { "name": "ivankuznetsov", "url": "https://github.com/ivankuznetsov" diff --git a/.github/workflows/agent-platforms.yml b/.github/workflows/agent-platforms.yml index 7583b87..e96cc4a 100644 --- a/.github/workflows/agent-platforms.yml +++ b/.github/workflows/agent-platforms.yml @@ -120,7 +120,7 @@ jobs: "$launcher" --project "$SCREENOTE_PROJECT" screenshot create \ --title "Agent plugin release probe" --page "ci-release-probe" \ --file "$integration_dir/capture.png" >"$integration_dir/capture.json" - "$launcher" --project "$SCREENOTE_PROJECT" page list --limit 100 --offset 0 >"$integration_dir/pages.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" "$launcher" --project "$SCREENOTE_PROJECT" annotation list \ diff --git a/docs/agent-compatibility.md b/docs/agent-compatibility.md index 22bdc22..91eb720 100644 --- a/docs/agent-compatibility.md +++ b/docs/agent-compatibility.md @@ -36,7 +36,7 @@ plugin directory so a copied package does not depend on the repository root. | Agent SEO | `2.0.1` | Stable | `skills/seo/SKILL.md` | agents, context, data sources, hooks, scripts | | Agent Writing | `0.5.2` | Stable | `skills/writing/SKILL.md` | agents, voice/style context | | LLM Wiki | `0.3.5` | Stable | five files under `skills/` | assets, consent-gated maintenance templates | -| Screenote | `3.0.2` | Stable | `skills/{screenote,snapshot,feedback}/SKILL.md` | CLI launcher, references, evals | +| Screenote | `3.1.0` | Stable | `skills/{screenote,snapshot,feedback}/SKILL.md` | CLI launcher, references, evals | ## Plugin invocations diff --git a/docs/screenote-cli-migration.md b/docs/screenote-cli-migration.md index 939d1c2..507195c 100644 --- a/docs/screenote-cli-migration.md +++ b/docs/screenote-cli-migration.md @@ -53,6 +53,18 @@ An ambiguous or inaccessible project stops. Interactive agents may show accessible choices after a `missing_project` response; noninteractive runs do not read stdin, prompt, guess, or launch a browser. +Capture and snapshot workflows may create a project when the input explicitly +requests an exact new name: + +```bash +plugins/screenote/scripts/screenote-cli.sh project create --name rabata.io +``` + +The command deliberately rejects global `--project`. It uses user-scoped OAuth +authorization, returns the created project object, and never runs merely +because project resolution failed. Exact accessible matches are reused instead +of duplicated. + ## JSON errors | Exit | Error | Behavior | diff --git a/plugin-surfaces.json b/plugin-surfaces.json index ae8255b..4e3ae05 100644 --- a/plugin-surfaces.json +++ b/plugin-surfaces.json @@ -24,6 +24,7 @@ "minimum_release": null, "approved_commands": [ ["project", "list"], + ["project", "create"], ["page", "list"], ["screenshot", "list"], ["screenshot", "create"], @@ -208,7 +209,7 @@ { "name": "screenote", "path": "plugins/screenote", - "version": "3.0.2", + "version": "3.1.0", "stability": "stable", "metadata": { "display_name": "Screenote", diff --git a/plugin-surfaces.lock.json b/plugin-surfaces.lock.json index 788f416..0c829d5 100644 --- a/plugin-surfaces.lock.json +++ b/plugin-surfaces.lock.json @@ -8,6 +8,10 @@ "project", "list" ], + [ + "project", + "create" + ], [ "page", "list" @@ -34,7 +38,7 @@ ] ], "artifact": "plugins/screenote/scripts/screenote-approved-commands.sh", - "sha256": "18163fe9062ccb7250ec845b101b8f2084c50eb5fe5ea44a6bae980f9d22c54a" + "sha256": "e1ca68f4a7d55bba5f6325b68ef770aef52b33210347b101f31a26f5ac29cdfc" }, "plugins": { "agent-reviewer": { @@ -845,27 +849,27 @@ } }, "screenote": { - "version": "3.0.2", + "version": "3.1.0", "canonical": { "skills/screenote/SKILL.md": { - "sha256": "28ae0e482177daa54f3131213db1eaffce8eafc4d71008b116ebf6119df9258e", - "semantic_sha256": "ac6c122e1cc07b6b471fe25960eb388d12347dbdea8f4c95231f6bded3ba94fa", + "sha256": "118b0da106447ed69eba5343f5f730b5bcff1a087691af07eeb47b4f31bc9291", + "semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1", "sections": { - "1:screenote — one-page visual review": "f2a3832ce0f91c507992d65ac8a5d9dc5362a8b42ddca0df021f1b82f46e29bc", + "1:screenote — one-page visual review": "37a69d0026e5d9f01b90e4a325920bf1ce411d42142a41f9c9ce9f36e58deca9", "2:parse the request": "38485a573382dad4b7297c049ec4ee43e74be316bef5c3dba11b5abdb4ab3cf2", "2:resolve a safe target": "329d16bfb9bfa3862ad531e0392ca12ead8ac4d58e175bbcf47abfb516d5be24", - "2:establish the cli and project": "6c66c3fe176811ab90a4106ba5080be0f4e81244fbf1ac904dbda9069ef2d190", + "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" } }, "skills/snapshot/SKILL.md": { - "sha256": "9880fb48fbfb11e1b3b75ce3d7e274c095cf1ecb8bf8fef80c1d6ac360046797", - "semantic_sha256": "d7314f2c8a0807e1c2f4f650e899d6050c8fd887d531ef43d5c9026f667a1cf5", + "sha256": "7fd2470860cdb8a1de036c1498dd051a94655ddbb2c668afee1100fa4c62c1ce", + "semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7", "sections": { - "1:snapshot — multi-page visual review": "cc19b6e91862f2c785543aa5bc9223102b72305451981d73b487ea4970af27c1", - "2:preflight": "089c8c90cacb45fe3bb0320e7cc284fe7d3dbb18d3a1b91774b658abea47d279", + "1:snapshot — multi-page visual review": "cf0f569c8f218283f98815967f07a895c63f579c1359b4be5fb5543b15f0d11b", + "2:preflight": "4a0dd546789198a62058792ec0866f08ac3e9c65d35fc3d856b44a42479c5629", "2:discover and confirm routes": "219ca5b16548cebf0204c299f51946b257eff32eee3c2c8f1bf7b1a55faf75bb", "2:capture and publish": "81d60f088f7aeb3e4b7f7e96c50f1e4236ce5fbdea444c3beb243e2a102b06e1" } @@ -884,7 +888,7 @@ "resources": { "references": { "exists": true, - "sha256": "54c1794d0bf08f154d341987922890cede96e33f431141fb6f3765698d483647", + "sha256": "83c9cefcc74ff9ad3f6940fd34e6f73e9cc1b1fd10eda17d282ce8a870992cba", "files": [ "references/cli.md", "references/workflows.json" @@ -892,7 +896,7 @@ }, "evals": { "exists": true, - "sha256": "0ffe1e6d07882cbc11b5976ccefd6a5b87b66ba5574d71673922dc220fddb2aa", + "sha256": "dae11cf4ea1ce3dda00a8c08d742c89bb00afc2ef0c9dadc009fcc0e46718cff", "files": [ "evals/README.md", "evals/lint-skills-test.sh", @@ -903,7 +907,7 @@ }, "scripts/screenote-cli.sh": { "exists": true, - "sha256": "dd76457972f7426917763c635a2ed832ed4c9ed3ec8828b6aa41a3a93146fcd7" + "sha256": "9f54e4717158e2e9ec1485ed7c610951a671fbfaa688c201d82af127e6f29d41" }, "scripts/screenote_flow.py": { "exists": true, @@ -914,7 +918,7 @@ "pi/skills/screenote/SKILL.md": { "sha256": "6acc53b130d691fe0fe3288e2079904d52bbb76065b6620524cdcbad7f03be3f", "canonical": "skills/screenote/SKILL.md", - "canonical_semantic_sha256": "ac6c122e1cc07b6b471fe25960eb388d12347dbdea8f4c95231f6bded3ba94fa", + "canonical_semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1", "overlays": [ "frontmatter", "invocation", @@ -924,7 +928,7 @@ "openclaw/skills/screenote/SKILL.md": { "sha256": "a8e0378d09299bc70297800080e348ad928ea914327b3a761ec5804a6a12f018", "canonical": "skills/screenote/SKILL.md", - "canonical_semantic_sha256": "ac6c122e1cc07b6b471fe25960eb388d12347dbdea8f4c95231f6bded3ba94fa", + "canonical_semantic_sha256": "5a3cfe3c9e550d70327832513a4caa656c949da4640f83ff6170ff7c514927d1", "overlays": [ "frontmatter", "invocation", @@ -934,7 +938,7 @@ "pi/skills/snapshot/SKILL.md": { "sha256": "a7291d9e71b30e440a94b9b2d9d6e2cb001c9ff656fb2bc4e93c659325b4159d", "canonical": "skills/snapshot/SKILL.md", - "canonical_semantic_sha256": "d7314f2c8a0807e1c2f4f650e899d6050c8fd887d531ef43d5c9026f667a1cf5", + "canonical_semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7", "overlays": [ "frontmatter", "invocation", @@ -944,7 +948,7 @@ "openclaw/skills/snapshot/SKILL.md": { "sha256": "d0bbf6f51de399d7b63fb0cd23ab1f1f097160d4dbe9dca7a0fa11c9e205b763", "canonical": "skills/snapshot/SKILL.md", - "canonical_semantic_sha256": "d7314f2c8a0807e1c2f4f650e899d6050c8fd887d531ef43d5c9026f667a1cf5", + "canonical_semantic_sha256": "08f65cf3ccfb978237d5b7c7ff1aebc65a56ab6aac665e91279ee3fcc17628a7", "overlays": [ "frontmatter", "invocation", diff --git a/plugins/screenote/.claude-plugin/marketplace.json b/plugins/screenote/.claude-plugin/marketplace.json index 8078a74..3a8b71f 100644 --- a/plugins/screenote/.claude-plugin/marketplace.json +++ b/plugins/screenote/.claude-plugin/marketplace.json @@ -13,7 +13,7 @@ "name": "screenote", "source": "./", "description": "Capture pages, publish OAuth-authenticated snapshots with the Screenote CLI, and retrieve visual feedback from Claude Code or Codex.", - "version": "3.0.2", + "version": "3.1.0", "author": { "name": "ivankuznetsov" }, diff --git a/plugins/screenote/.claude-plugin/plugin.json b/plugins/screenote/.claude-plugin/plugin.json index bc4ea88..4f4ae56 100644 --- a/plugins/screenote/.claude-plugin/plugin.json +++ b/plugins/screenote/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "screenote", - "version": "3.0.2", + "version": "3.1.0", "description": "Capture full-page browser snapshots and manage Screenote feedback through the OAuth CLI from Claude Code or Codex.", "author": { "name": "ivankuznetsov" diff --git a/plugins/screenote/.codex-plugin/plugin.json b/plugins/screenote/.codex-plugin/plugin.json index 3c8a357..8bc9391 100644 --- a/plugins/screenote/.codex-plugin/plugin.json +++ b/plugins/screenote/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "screenote", - "version": "3.0.2", + "version": "3.1.0", "description": "Capture full-page browser snapshots and manage Screenote feedback through the OAuth CLI from Claude Code or Codex.", "author": { "name": "ivankuznetsov", diff --git a/plugins/screenote/CHANGELOG.md b/plugins/screenote/CHANGELOG.md index f55f5ea..4ca4cdb 100644 --- a/plugins/screenote/CHANGELOG.md +++ b/plugins/screenote/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to the Screenote plugin are documented here. +## [3.1.0] - 2026-07-31 + +### Added + +- Create an exactly named Screenote project during capture or snapshot setup + when the user explicitly requests that mutation. + +### Fixed + +- Stop passing unsupported pagination flags to `page list` in the protected + integration workflow. + ## [3.0.2] - 2026-07-31 ### Fixed diff --git a/plugins/screenote/evals/lint-skills.sh b/plugins/screenote/evals/lint-skills.sh index 3f5ae30..398d3b3 100755 --- a/plugins/screenote/evals/lint-skills.sh +++ b/plugins/screenote/evals/lint-skills.sh @@ -37,6 +37,7 @@ done for tuple in \ 'project list' \ + 'project create' \ 'page list' \ 'screenshot list' \ 'screenshot create' \ @@ -79,7 +80,6 @@ for forbidden in \ 'screenote_browser_use_mcp' \ 'create_multi_viewport_screenshot' \ 'annotation resolve' \ - 'project create' \ 'snapshot --manifest' \ '--token'; do if grep -R -n -i -F -- "$forbidden" "${active_files[@]}" >/dev/null 2>&1; then diff --git a/plugins/screenote/openclaw.plugin.json b/plugins/screenote/openclaw.plugin.json index afdeee7..a1686bc 100644 --- a/plugins/screenote/openclaw.plugin.json +++ b/plugins/screenote/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "screenote", "name": "Screenote", "description": "Capture pages, publish screenshots, and retrieve visual feedback through the Screenote OAuth CLI.", - "version": "3.0.2", + "version": "3.1.0", "skills": [ "./openclaw/skills" ], diff --git a/plugins/screenote/package.json b/plugins/screenote/package.json index d3cb6ac..458719f 100644 --- a/plugins/screenote/package.json +++ b/plugins/screenote/package.json @@ -1,6 +1,6 @@ { "name": "screenote", - "version": "3.0.2", + "version": "3.1.0", "type": "module", "description": "Capture pages, publish screenshots, and retrieve visual feedback through the Screenote OAuth CLI.", "homepage": "https://screenote.ai", diff --git a/plugins/screenote/references/cli.md b/plugins/screenote/references/cli.md index 78040e3..697fdd4 100644 --- a/plugins/screenote/references/cli.md +++ b/plugins/screenote/references/cli.md @@ -48,6 +48,7 @@ accepts only these command tuples: | Tuple | Purpose | | --- | --- | | `project list` | Validate authentication and accessible projects. | +| `project create` | Create one explicitly named project before capture or snapshot publication. | | `page list` | List captured pages in the selected project. | | `screenshot list` | List versions for a selected page. | | `screenshot create` | Upload one user-approved local capture file. | @@ -70,6 +71,24 @@ Do not maintain a plugin-owned project cache. Run `project list` before a project-scoped flow and validate that the resolved project is accessible. An ambiguous name, inaccessible id, or empty list is an error; never guess. +For `screenote` and `snapshot`, an interactive request that explicitly asks to +create an exact project name may run: + +```text +screenote-cli.sh project create --name EXACT_NAME +``` + +Do not pass global `--project` to this command. Require exit zero plus a +`project` object containing an id and the exact requested name, then select the +returned id for the remaining project-scoped calls. If the exact name is +already accessible, select it and do not create a duplicate. A named but +missing destination without explicit create intent requires confirmation; +`missing_project`, an empty project list, an inferred repository name, and an +ambiguous name never authorize creation. Noninteractive creation requires an +exact name and an explicit create directive in its input. Project creation +requires user-scoped OAuth authorization; on exit 3, stop without trying a +project-scoped token or another auth mechanism. + Ordinary CLI commands are noninteractive. In an interactive agent session, after a `missing_project` error, show the accessible projects and ask the user which explicit `--project` to use. In a noninteractive run, never read stdin, @@ -86,7 +105,8 @@ credential-shaped value before quoting surrounding prose. `screenote --base-url https://screenote.ai login` for the hosted service; noninteractively require `SCREENOTE_TOKEN`. Do not run login automatically. - Exit 2 with `missing_project`: stop and explain the three project sources - above. Only an interactive agent may present accessible choices. + above. Only an interactive agent may present accessible choices; the error + alone never authorizes `project create`. - Exit 3: stop and report invalid/expired authentication or authorization. Do not retry with another auth mechanism. - Every other nonzero exit, including not-found and rate-limit results: stop diff --git a/plugins/screenote/references/workflows.json b/plugins/screenote/references/workflows.json index 9374b1f..1d01f19 100644 --- a/plugins/screenote/references/workflows.json +++ b/plugins/screenote/references/workflows.json @@ -14,6 +14,11 @@ "required_flags": [], "paginated": false }, + "project create": { + "object": "project", + "required_flags": ["--name"], + "paginated": false + }, "page list": { "collection": "pages", "required_flags": [], @@ -46,10 +51,12 @@ "screenote": { "skill": "skills/screenote/SKILL.md", "input_modes": ["browser_capture", "existing_image"], + "conditional_commands": ["project create"], "ordered_commands": ["project list", "screenshot create"] }, "snapshot": { "skill": "skills/snapshot/SKILL.md", + "conditional_commands": ["project create"], "ordered_commands": ["project list", "screenshot create"] }, "feedback": { diff --git a/plugins/screenote/scripts/screenote-approved-commands.sh b/plugins/screenote/scripts/screenote-approved-commands.sh index f2758bc..eacf9cc 100644 --- a/plugins/screenote/scripts/screenote-approved-commands.sh +++ b/plugins/screenote/scripts/screenote-approved-commands.sh @@ -5,6 +5,8 @@ declare -ar SCREENOTE_APPROVED_COMMANDS=( project list + project + create page list screenshot diff --git a/plugins/screenote/scripts/screenote-cli.sh b/plugins/screenote/scripts/screenote-cli.sh index eb26fb0..c407a7e 100755 --- a/plugins/screenote/scripts/screenote-cli.sh +++ b/plugins/screenote/scripts/screenote-cli.sh @@ -81,6 +81,7 @@ if [[ ${1-} == --check-contract ]]; then } required_flags=() case "$noun $verb" in + 'project create') required_flags=(--name) ;; 'screenshot list') required_flags=(--page --status --limit --offset) ;; 'screenshot create') required_flags=(--title --page --file) ;; 'annotation list') required_flags=(--screenshot --status --viewport --limit --offset) ;; @@ -99,6 +100,7 @@ if [[ ${1-} == --check-contract ]]; then fi screenote_argv=() +project_was_supplied=false while (($# > 0)); do case "$1" in --project) @@ -107,6 +109,7 @@ while (($# > 0)); do exit 64 fi screenote_argv+=("$1" "$2") + project_was_supplied=true shift 2 ;; *) @@ -129,6 +132,14 @@ if ! screenote_command_is_approved "$noun" "$verb"; then exit 64 fi +if [[ $noun == project && $verb == create ]]; then + if [[ $project_was_supplied == true ]] || (($# != 2)) || + [[ $1 != --name || -z ${2//[[:space:]]/} || $2 == -* ]]; then + json_error '{"error":{"code":"invalid_arguments","message":"Project creation requires exactly: project create --name NAME, without a global --project."}}' + exit 64 + fi +fi + for argument in "$@"; do case "$argument" in --project|--project=*) diff --git a/plugins/screenote/skills/screenote/SKILL.md b/plugins/screenote/skills/screenote/SKILL.md index f35efb6..de5d296 100644 --- a/plugins/screenote/skills/screenote/SKILL.md +++ b/plugins/screenote/skills/screenote/SKILL.md @@ -12,7 +12,8 @@ Load [the shipped workflow contract](../../references/workflows.json) and use its `screenote` command sequence and response keys as the authority for the deterministic CLI portion. This skill remains authoritative for browser capture and user intent. -Canonical CLI order: `project list`, then one `screenshot create` per capture. +Canonical CLI order: `project list`, optional explicit `project create`, then +one `screenshot create` per capture. Use the bundled `../../scripts/screenote-cli.sh`; do not invoke unapproved CLI commands or another transport. @@ -59,6 +60,16 @@ Detect `screenote` on `PATH`; never install it. Run the launcher's non-secret `SCREENOTE_PROJECT`, then CLI config. Validate accessibility and never guess an ambiguous project. +If the user explicitly asks to create an exact missing project, invoke +`project create --name ` without global `--project`, validate the +returned `project.id` and exact `project.name`, and use that id for uploads. If +the exact project already exists, select it without creating a duplicate. If a +request merely names a missing upload destination, ask before creating it. +Never create from `missing_project` alone, an inferred repository name, an +empty list, or an ambiguous name. Noninteractive input must contain an exact +name and explicit create directive. Exit 3 during creation stops the flow; +project-scoped credentials cannot be substituted for user-scoped OAuth. + Handle JSON failures exactly: exit 2 `missing_token` suggests `screenote --base-url https://screenote.ai login` only as separate interactive guidance or `SCREENOTE_TOKEN` noninteractively; exit 2 diff --git a/plugins/screenote/skills/snapshot/SKILL.md b/plugins/screenote/skills/snapshot/SKILL.md index 5356089..02e4309 100644 --- a/plugins/screenote/skills/snapshot/SKILL.md +++ b/plugins/screenote/skills/snapshot/SKILL.md @@ -12,7 +12,8 @@ Load [the shipped workflow contract](../../references/workflows.json) and use its `snapshot` command sequence and response keys as the authority for the deterministic CLI portion. This skill remains authoritative for route discovery, browser capture, and user confirmation. -Canonical CLI order: `project list`, then repeated `screenshot create` calls. +Canonical CLI order: `project list`, optional explicit `project create`, then +repeated `screenshot create` calls. The public grammar remains: ```text @@ -35,6 +36,15 @@ Detect the external CLI without installing it, run the launcher's non-secret `SCREENOTE_PROJECT` over CLI config. Noninteractive execution never prompts or opens a browser. +If the user explicitly asks to create an exact missing project, run `project +create --name ` without global `--project`, validate the returned +`project.id` and exact `project.name`, and select that id. Reuse an accessible +exact-name match rather than creating a duplicate. A merely missing +destination requires interactive confirmation; never create from +`missing_project`, an empty list, an inferred name, or ambiguity alone. +Noninteractive creation requires an exact name and an explicit create +directive. Stop on exit 3 because creation requires user-scoped OAuth. + Create one private invocation directory with `mktemp -d`, mode `0700`, and a mode `0600` PNG per route/viewport. Never reuse an existing destination. diff --git a/tests/fixtures/screenote-cli/screenote b/tests/fixtures/screenote-cli/screenote index 030e6c4..6629007 100755 --- a/tests/fixtures/screenote-cli/screenote +++ b/tests/fixtures/screenote-cli/screenote @@ -32,6 +32,7 @@ def main() -> int: if "--help" in arguments: flags = { "project list": [], + "project create": ["--name"], "page list": [], "screenshot list": ["--page", "--status", "--limit", "--offset"], "screenshot create": ["--title", "--page", "--file"], diff --git a/tests/test_screenote_cli_contract.py b/tests/test_screenote_cli_contract.py index 32d2e29..1cb8a36 100644 --- a/tests/test_screenote_cli_contract.py +++ b/tests/test_screenote_cli_contract.py @@ -62,7 +62,7 @@ def _run(self, arguments): " print('Usage: screenote --base-url URL --project PROJECT --config PATH')\n" "elif '--help' in args:\n" " flags = {\n" - " 'project list': [], 'page list': [],\n" + " 'project list': [], 'project create': ['--name'], 'page list': [],\n" " 'screenshot list': ['--page', '--status', '--limit', '--offset'],\n" " 'screenshot create': ['--title', '--page', '--file'],\n" " 'annotation list': ['--screenshot', '--status', '--viewport', '--limit', '--offset'],\n" @@ -114,6 +114,8 @@ def _run_flow(self, scenario, workflow="screenote", **options): def test_launcher_allows_only_approved_tuples_and_preserves_argv(self): hostile = "Fixed user's $(layout); still data" for noun, verb in APPROVED: + if (noun, verb) == ("project", "create"): + continue with self.subTest(command=f"{noun} {verb}"): result, argv = self._run(["--project", "project-7", noun, verb, "--body", hostile]) self.assertEqual(0, result.returncode, result.stderr) @@ -123,13 +125,30 @@ def test_launcher_allows_only_approved_tuples_and_preserves_argv(self): ) self.assertEqual('{"ok":true}\n', result.stdout) - for command in (("snapshot", "create"), ("project", "create"), ("annotation", "resolve"), ("login", "--device")): + for command in (("snapshot", "create"), ("annotation", "resolve"), ("login", "--device")): with self.subTest(rejected=" ".join(command)): result, argv = self._run(list(command)) self.assertNotEqual(0, result.returncode) self.assertEqual([], argv) self.assertIn("command_not_allowed", result.stderr) + def test_launcher_allows_explicit_project_creation_only_with_an_exact_name(self): + result, argv = self._run(["project", "create", "--name", "rabata.io"]) + self.assertEqual(0, result.returncode, result.stderr) + self.assertEqual(["project", "create", "--name", "rabata.io"], argv) + + for arguments in ( + ["project", "create"], + ["project", "create", "--name"], + ["project", "create", "--name", ""], + ["--project", "project-7", "project", "create", "--name", "rabata.io"], + ): + with self.subTest(arguments=arguments): + rejected, rejected_argv = self._run(arguments) + self.assertEqual(64, rejected.returncode) + self.assertEqual([], rejected_argv) + self.assertIn("invalid_arguments", rejected.stderr) + def test_launcher_rejects_every_runtime_endpoint_or_config_override(self): attacks = ( ["--base-url", "https://attacker.example", "project", "list"], @@ -225,12 +244,14 @@ def test_shipped_workflow_contract_is_the_canonical_cli_authority(self): self.assertIn("../../references/workflows.json", body) for command in specification["ordered_commands"]: self.assertIn(command, body, f"{skill_path}: canonical workflow lost {command}") + for command in specification.get("conditional_commands", []): + self.assertIn(command, body, f"{skill_path}: conditional workflow lost {command}") def test_screenote_manifests_and_repository_have_no_mcp_transport(self): self.assertFalse((PLUGIN_ROOT / ".mcp.json").exists()) for manifest in (PLUGIN_ROOT / ".claude-plugin/plugin.json", PLUGIN_ROOT / ".codex-plugin/plugin.json"): self.assertNotIn("mcpServers", json.loads(manifest.read_text())) - forbidden = ("mcpServers", "screenote_browser_use_mcp", "Browser Use MCP", "annotation resolve", "project create") + forbidden = ("mcpServers", "screenote_browser_use_mcp", "Browser Use MCP", "annotation resolve") scanned = [ *PLUGIN_ROOT.glob("skills/*/SKILL.md"), PLUGIN_ROOT / "references/cli.md", diff --git a/tests/test_screenote_redaction.py b/tests/test_screenote_redaction.py index 881a9a5..bbddcce 100644 --- a/tests/test_screenote_redaction.py +++ b/tests/test_screenote_redaction.py @@ -93,7 +93,6 @@ def test_active_packages_exclude_retired_transport_and_credential_arguments(self "/mcp/messages", "create_multi_viewport_screenshot", "annotation resolve", - "project create", "snapshot --manifest", "--token ", )