scar-cli 0.22.0__tar.gz → 0.23.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scar_cli-0.23.0/.release-please-manifest.json +3 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0017-codex-pretool-context-can-replay-apply-patch.landmine.md +0 -1
- scar_cli-0.22.0/.scars/candidates/one-directional-health-check-leaves-flattering-direction-unwatched.md → scar_cli-0.23.0/.scars/0020-one-directional-health-check-leaves-flattering-direction-unwatched.landmine.md +6 -2
- scar_cli-0.22.0/.scars/candidates/posttool-fix-in-hooks-does-not-reach-codex-or-cascade.md → scar_cli-0.23.0/.scars/0021-posttool-fix-in-hooks-does-not-reach-codex-or-cascade.landmine.md +5 -3
- scar_cli-0.22.0/.scars/candidates/codex-hook-delivery-needs-user-file-and-trust.md → scar_cli-0.23.0/.scars/0022-codex-hook-delivery-needs-user-file-and-trust.landmine.md +4 -3
- scar_cli-0.23.0/.scars/candidates/one-plugin-hook-manifest-two-hosts-two-root-variables.md +30 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/CHANGELOG.md +11 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/PKG-INFO +6 -12
- {scar_cli-0.22.0 → scar_cli-0.23.0}/README.md +5 -11
- {scar_cli-0.22.0 → scar_cli-0.23.0}/SPEC.md +4 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/plugin/plugin.json +1 -1
- {scar_cli-0.22.0 → scar_cli-0.23.0}/pyproject.toml +1 -1
- {scar_cli-0.22.0 → scar_cli-0.23.0}/release-please-config.json +1 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/cli.py +188 -37
- scar_cli-0.23.0/src/scar/hosts.py +210 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/installer.py +31 -0
- scar_cli-0.23.0/tests/test_hosts.py +359 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_installer.py +285 -22
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_plugin.py +11 -23
- {scar_cli-0.22.0 → scar_cli-0.23.0}/uv.lock +1 -1
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docs/agents.md +6 -6
- scar_cli-0.22.0/.release-please-manifest.json +0 -3
- scar_cli-0.22.0/plugin/hooks/hooks.json +0 -43
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.claude-plugin/marketplace.json +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/workflows/ci.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/workflows/docs.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/workflows/pr-validation.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.github/workflows/release.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.gitignore +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.pre-commit-config.yaml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.pre-commit-hooks.yaml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0001-git-grep-ere-pitfalls.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0002-agent-direct-hook-install.deadend.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0003-installer-binds-to-active-venv-scar.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0004-promote-roundtrip-drops-expires-evidence.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0005-history-rewrite-orphans-commit-evidence.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0006-yaml-pattern-anchor-over-escaping.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0007-release-please-config-change-skips-open-pr.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0008-rich-output-nontty-must-bypass-rich.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0009-anchor-survival-harness-measures-symbol-not-path-anchors.deadend.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0010-git-returncode-128-vs-empty-tracked-set.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0011-lint-compile-check-lets-redos-anchors-hang-hotpath.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0012-firing-log-readers-need-blanket-guards.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0013-docusaurus-future-v4-requires-faster-dep.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0014-inject-flag-dest-collides-with-subcommand-dispatch.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0015-zero-checkruns-means-approval-gate-not-workflow-bug.deadend.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0016-hook-ownership-must-be-kind-scoped.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0018-cli-subparsers-register-through-_add-wrapper.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/0019-token-in-scar-body-cannot-prove-hook-delivery.deadend.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/README.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/candidates/cascade-block-exit-collides-with-argparse.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/.scars/template.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/AGENTS.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/CONTRIBUTING.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/IDEA.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/LICENSE +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/ROADMAP.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/SCAR-FORMAT.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/STRESS-TEST.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/action.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/codecov.yml +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/PROTOCOL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/RESULTS.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/long_replay.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/replay.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/replay_shipped.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/anchor-survival/test_replay_shipped.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/auto-authorship/FINDINGS.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/auto-authorship/PROTOCOL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/.gitignore +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/PROTOCOL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/RESULTS.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/.scars/0001-vendor-retry-window.fence.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/.scars/0002-evicting-session-store.deadend.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/.scars/0003-export-column-order.landmine.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/README.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/payments/retry.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/reports/export.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/fixture/services/sessions.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/fence-honor/grade.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/harvest/PROTOCOL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/experiments/harvest/harvest.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/hook/scar-hooks.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/plugin/hooks/run.sh +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/plugin/skills/scar-authoring/SKILL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/plugin/skills/scar-authoring/assets/template.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/__init__.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/agent.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/cascade.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/codex.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/draftcheck.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/evidence.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/gc.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/harvest.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/hooks.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/lint.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/match.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/mcp.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/model.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/orphan.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/output.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/reanchor.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/renames.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/render.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/review.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/skills/scar-authoring/SKILL.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/skills/scar-authoring/assets/template.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/store.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/src/scar/symbols.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/conftest.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_action_yml.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_anchor_kind.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_block_capability.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_cascade.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_cli.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_codex.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_context_size.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_docs.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_draftcheck.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_evidence.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_gc.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_git_hook.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_harvest.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_hooks.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_json_contract.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_lifecycle.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_lint.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_match.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_mcp.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_model.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_orphan.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_output.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_precommit_hooks.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_reanchor.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_renames.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_render.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_review.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_skill.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_store.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_symbols.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/tests/test_verdict_observability.py +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/.gitignore +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docs/concepts.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docs/index.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docs/methodology.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docs/quickstart.md +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/docusaurus.config.ts +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/package-lock.json +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/package.json +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/scripts/sync-changelog.mjs +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/sidebars.ts +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/src/css/custom.css +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/static/img/favicon.svg +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/static/llms.txt +0 -0
- {scar_cli-0.22.0 → scar_cli-0.23.0}/website/tsconfig.json +0 -0
|
@@ -8,7 +8,6 @@ created: 2026-08-28
|
|
|
8
8
|
authors: ["codex", "Kibukx"]
|
|
9
9
|
anchors:
|
|
10
10
|
- path: src/scar/codex.py
|
|
11
|
-
- path: plugin/hooks/hooks.json
|
|
12
11
|
evidence:
|
|
13
12
|
- issue: 243
|
|
14
13
|
- note: live codex-cli 0.147.0 A/B emitted two tool_use_ids for one identical apply_patch after the first PreToolUse context injection
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
id:
|
|
2
|
+
id: 20
|
|
3
3
|
type: landmine
|
|
4
4
|
title: A health check that watches one direction of pipeline failure leaves the flattering direction unwatched
|
|
5
5
|
severity: high
|
|
6
6
|
confidence: 0.9
|
|
7
7
|
created: 2026-08-30
|
|
8
8
|
authors: ["claude-code"]
|
|
9
|
+
promoted_by: Kibukx
|
|
10
|
+
promoted_by_source: git-config
|
|
9
11
|
anchors:
|
|
10
12
|
- path: src/scar/cli.py
|
|
11
13
|
- path: src/scar/hooks.py
|
|
14
|
+
- path: src/scar/codex.py
|
|
15
|
+
- path: src/scar/cascade.py
|
|
12
16
|
- pattern: "instrument_disconnected|posttool_silent|verdict_observed"
|
|
13
17
|
evidence:
|
|
14
18
|
- issue: 277
|
|
15
19
|
- issue: 237
|
|
16
20
|
expires:
|
|
17
21
|
condition: "the pipeline stops having two independently-installable halves"
|
|
18
|
-
status:
|
|
22
|
+
status: active
|
|
19
23
|
---
|
|
20
24
|
|
|
21
25
|
The precheck and posttool hooks install separately and fail separately. #237
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
id:
|
|
2
|
+
id: 21
|
|
3
3
|
type: landmine
|
|
4
4
|
title: A posttool change in hooks.py does not reach codex.py or cascade.py, which carry their own copy of the same logic
|
|
5
5
|
severity: high
|
|
6
6
|
confidence: 0.8
|
|
7
7
|
created: 2026-09-02
|
|
8
8
|
authors: ["claude-code"]
|
|
9
|
+
promoted_by: Kibukx
|
|
10
|
+
promoted_by_source: git-config
|
|
9
11
|
anchors:
|
|
10
12
|
- path: src/scar/hooks.py
|
|
11
13
|
- path: src/scar/codex.py
|
|
12
14
|
- path: src/scar/cascade.py
|
|
13
|
-
violation: "if not violations:"
|
|
14
15
|
evidence:
|
|
15
16
|
- issue: 277
|
|
16
17
|
- issue: 293
|
|
@@ -18,7 +19,8 @@ evidence:
|
|
|
18
19
|
expires:
|
|
19
20
|
condition: "the three posttool paths share one implementation, or the adapters delegate to hooks.posttool"
|
|
20
21
|
review_after: 2026-12-01
|
|
21
|
-
|
|
22
|
+
violation: "if not violations:"
|
|
23
|
+
status: active
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
The posttool verdict logic exists three times: `hooks.posttool` for Claude
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
id:
|
|
2
|
+
id: 22
|
|
3
3
|
type: landmine
|
|
4
4
|
title: Codex plugin hooks are not materialized, and untrusted Codex hooks are skipped silently
|
|
5
5
|
severity: high
|
|
6
6
|
confidence: 0.9
|
|
7
7
|
created: 2026-08-28
|
|
8
8
|
authors: ["claude-code"]
|
|
9
|
+
promoted_by: Kibukx
|
|
10
|
+
promoted_by_source: git-config
|
|
9
11
|
anchors:
|
|
10
|
-
- path: plugin/hooks/hooks.json
|
|
11
12
|
- path: src/scar/codex.py
|
|
12
13
|
- pattern: "hooks/hooks\.json|CODEX_HOME|codex-(pretool|posttool|session-notice)"
|
|
13
14
|
evidence:
|
|
@@ -17,7 +18,7 @@ evidence:
|
|
|
17
18
|
expires:
|
|
18
19
|
condition: "Codex materializes a plugin's hooks/hooks.json into the installed cache AND surfaces an error for untrusted hooks"
|
|
19
20
|
review_after: 2027-02-28
|
|
20
|
-
status:
|
|
21
|
+
status: active
|
|
21
22
|
---
|
|
22
23
|
|
|
23
24
|
Shipping a Codex hook through a plugin's `hooks/hooks.json` does not deliver it,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0
|
|
3
|
+
type: landmine
|
|
4
|
+
title: A plugin hook manifest is read by every host that installs the plugin, and hosts expand different root variables
|
|
5
|
+
severity: high
|
|
6
|
+
confidence: 0.9
|
|
7
|
+
created: 2026-09-03
|
|
8
|
+
authors: ["claude-code"]
|
|
9
|
+
anchors:
|
|
10
|
+
- path: plugin/
|
|
11
|
+
- path: src/scar/installer.py
|
|
12
|
+
- pattern: "PLUGIN_ROOT"
|
|
13
|
+
evidence:
|
|
14
|
+
- issue: 303
|
|
15
|
+
- issue: 244
|
|
16
|
+
expires:
|
|
17
|
+
condition: "the plugin format gains per-host scoping for hook files"
|
|
18
|
+
review_after: 2026-12-01
|
|
19
|
+
status: candidate
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
Claude Code merges plugin.json hooks with hooks/hooks.json and defines only
|
|
23
|
+
CLAUDE_PLUGIN_ROOT. Codex reads the same file and defines both PLUGIN_ROOT
|
|
24
|
+
and CLAUDE_PLUGIN_ROOT. A hooks/hooks.json written for Codex with
|
|
25
|
+
${PLUGIN_ROOT} therefore ran on every Claude session start and Bash call as
|
|
26
|
+
"/hooks/run.sh": No such file or directory, on every machine with the plugin
|
|
27
|
+
(#244 introduced it, #303 removed it). There is no way to mark a plugin
|
|
28
|
+
hook file as one host's only. Ship one manifest per plugin, on
|
|
29
|
+
${CLAUDE_PLUGIN_ROOT}, and deliver host-specific hooks through
|
|
30
|
+
`scar hook install --runtime <host>`, whose files live outside the plugin.
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.23.0](https://github.com/Daily-Nerd/Scar/compare/v0.22.0...v0.23.0) (2026-09-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **install:** with no --runtime, no terminal, and more than one detected host, scar hook install and scar skill install write nothing and print the commands to run instead of installing Claude Code by default.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **install:** detect installed hosts and the channel that already serves each ([#304](https://github.com/Daily-Nerd/Scar/issues/304)) ([180cf6f](https://github.com/Daily-Nerd/Scar/commit/180cf6f53ab671e6fb9ce6636fe718aabead1c7c))
|
|
13
|
+
|
|
3
14
|
## [0.22.0](https://github.com/Daily-Nerd/Scar/compare/v0.21.0...v0.22.0) (2026-09-03)
|
|
4
15
|
|
|
5
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: scar-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: SCAR — version control for negative knowledge (deadends, fences, landmines)
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -75,20 +75,14 @@ cp .scars/template.md .scars/candidates/redis-sessions.md
|
|
|
75
75
|
$EDITOR .scars/candidates/redis-sessions.md
|
|
76
76
|
scar lint # validate format
|
|
77
77
|
scar promote redis-sessions.md # human review gate: candidate -> active
|
|
78
|
-
scar hook install #
|
|
79
|
-
scar skill install #
|
|
80
|
-
scar hook
|
|
78
|
+
scar hook install # detects installed hosts, asks which to wire (--all, --runtime X)
|
|
79
|
+
scar skill install # same detection for the scar-authoring skill
|
|
80
|
+
scar hook status # every host with the channel that serves it (settings, plugin, none)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Claude Code users: the marketplace **plugin** ships the hooks and the
|
|
84
|
-
scar-authoring skill together; the two commands above are the manual fallback.
|
|
85
|
-
Re-run `scar skill install` after upgrading — the installed skill is a static copy.
|
|
83
|
+
Claude Code users: the marketplace **plugin** ships the hooks and the scar-authoring skill together. `scar hook install` and `scar skill install` see the plugin and skip Claude unless you pass `--runtime claude --force`. Re-run `scar skill install` after upgrading a manual install; the installed skill is a static copy.
|
|
86
84
|
|
|
87
|
-
Codex users:
|
|
88
|
-
and trust its current hook definitions. The native plugin hooks push matching
|
|
89
|
-
scars before `Bash` and `apply_patch` calls and report post-patch `violation:`
|
|
90
|
-
tripwires without blocking or rewriting the action. Plugin updates that change
|
|
91
|
-
the hook definitions require trust review again.
|
|
85
|
+
Codex users: run `scar hook install --runtime codex`, then open `/hooks` in Codex to review and trust the entries. The hooks push matching scars before `Bash` and `apply_patch` calls and report post-patch `violation:` tripwires without blocking. The plugin no longer carries Codex hook definitions; the installer is the one path.
|
|
92
86
|
|
|
93
87
|
Full walkthrough, lifecycle commands, and agent wiring (Claude Code plugin,
|
|
94
88
|
Codex native hooks, Windsurf/Cascade hooks, MCP server, `scar draft-check`):
|
|
@@ -60,20 +60,14 @@ cp .scars/template.md .scars/candidates/redis-sessions.md
|
|
|
60
60
|
$EDITOR .scars/candidates/redis-sessions.md
|
|
61
61
|
scar lint # validate format
|
|
62
62
|
scar promote redis-sessions.md # human review gate: candidate -> active
|
|
63
|
-
scar hook install #
|
|
64
|
-
scar skill install #
|
|
65
|
-
scar hook
|
|
63
|
+
scar hook install # detects installed hosts, asks which to wire (--all, --runtime X)
|
|
64
|
+
scar skill install # same detection for the scar-authoring skill
|
|
65
|
+
scar hook status # every host with the channel that serves it (settings, plugin, none)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Claude Code users: the marketplace **plugin** ships the hooks and the
|
|
69
|
-
scar-authoring skill together; the two commands above are the manual fallback.
|
|
70
|
-
Re-run `scar skill install` after upgrading — the installed skill is a static copy.
|
|
68
|
+
Claude Code users: the marketplace **plugin** ships the hooks and the scar-authoring skill together. `scar hook install` and `scar skill install` see the plugin and skip Claude unless you pass `--runtime claude --force`. Re-run `scar skill install` after upgrading a manual install; the installed skill is a static copy.
|
|
71
69
|
|
|
72
|
-
Codex users:
|
|
73
|
-
and trust its current hook definitions. The native plugin hooks push matching
|
|
74
|
-
scars before `Bash` and `apply_patch` calls and report post-patch `violation:`
|
|
75
|
-
tripwires without blocking or rewriting the action. Plugin updates that change
|
|
76
|
-
the hook definitions require trust review again.
|
|
70
|
+
Codex users: run `scar hook install --runtime codex`, then open `/hooks` in Codex to review and trust the entries. The hooks push matching scars before `Bash` and `apply_patch` calls and report post-patch `violation:` tripwires without blocking. The plugin no longer carries Codex hook definitions; the installer is the one path.
|
|
77
71
|
|
|
78
72
|
Full walkthrough, lifecycle commands, and agent wiring (Claude Code plugin,
|
|
79
73
|
Codex native hooks, Windsurf/Cascade hooks, MCP server, `scar draft-check`):
|
|
@@ -464,3 +464,7 @@ just as well as edit rows do.
|
|
|
464
464
|
command output, and asserts the nullable semantics of §9.2. A guarantee no
|
|
465
465
|
test enforces is an untestable assertion, and untestable assertions are the
|
|
466
466
|
ones that reach `main` wrong.
|
|
467
|
+
|
|
468
|
+
### 9.10 Install detection
|
|
469
|
+
|
|
470
|
+
`scar hook install` and `scar skill install` without `--runtime` detect hosts (claude, codex, windsurf, cursor, opencode) by config directory or binary on PATH, and resolve for each the channel that already serves it: `settings` (Scar's own entries in the host's hook file), `plugin` (the scar marketplace plugin installed and not disabled), or `none`. windsurf is repo-scoped: its hooks file is written into the working repo, so the binary on PATH counts only when that repo is inside a git repository. On a terminal, one question per present, wirable, unserved host, default no. Without a terminal, exactly one such host is installed; zero or several means the commands are printed and nothing is written. Zero detected hosts says so, rather than naming the plugin. `--all` wires every such host. `--runtime claude` on a Claude Code already served by the plugin refuses unless `--force`. `--all` and `--force` apply to `install` only, and `--force` applies to `--runtime claude` only. A missing or unreadable plugin registry never counts as `plugin`.
|
|
@@ -9,11 +9,13 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
|
+
import os
|
|
12
13
|
import sys
|
|
13
14
|
import time
|
|
14
15
|
from datetime import datetime
|
|
15
16
|
from importlib.metadata import PackageNotFoundError, version as _dist_version
|
|
16
17
|
from pathlib import Path
|
|
18
|
+
from typing import Callable
|
|
17
19
|
|
|
18
20
|
from rich_argparse import RichHelpFormatter
|
|
19
21
|
|
|
@@ -2666,46 +2668,166 @@ def _cmd_cascade_hook(args) -> int:
|
|
|
2666
2668
|
return cascade_hook()
|
|
2667
2669
|
|
|
2668
2670
|
|
|
2671
|
+
def _detect(kind: str):
|
|
2672
|
+
"""Which hosts exist here, and which channel already serves each one.
|
|
2673
|
+
Pure read: hosts.py never writes and never prompts (#303)."""
|
|
2674
|
+
from . import hosts, installer
|
|
2675
|
+
home = installer.CLAUDE_DIR.parent
|
|
2676
|
+
repo = Path.cwd()
|
|
2677
|
+
found = hosts.detect_hosts(home, repo, kind=kind,
|
|
2678
|
+
path_env=os.environ.get("PATH", ""),
|
|
2679
|
+
codex_dir=installer.codex_home())
|
|
2680
|
+
resolved = hosts.resolve_channels(found, claude_dir=installer.CLAUDE_DIR,
|
|
2681
|
+
repo=repo, kind=kind)
|
|
2682
|
+
return resolved, repo
|
|
2683
|
+
|
|
2684
|
+
|
|
2685
|
+
def _run_hook_installers(names: list[str], repo: Path, dry: bool) -> int:
|
|
2686
|
+
"""Worst exit code wins: one host failing must not be reported as success."""
|
|
2687
|
+
from .installer import cascade_install, codex_install, install
|
|
2688
|
+
rc = 0
|
|
2689
|
+
for name in names:
|
|
2690
|
+
print(f"== {name}")
|
|
2691
|
+
if name == "claude":
|
|
2692
|
+
rc = max(rc, install(dry=dry))
|
|
2693
|
+
elif name == "codex":
|
|
2694
|
+
rc = max(rc, codex_install(dry=dry))
|
|
2695
|
+
elif name == "windsurf":
|
|
2696
|
+
rc = max(rc, cascade_install(repo, dry=dry))
|
|
2697
|
+
return rc
|
|
2698
|
+
|
|
2699
|
+
|
|
2700
|
+
def _lifecycle_flag_guard(args) -> int | None:
|
|
2701
|
+
"""--all/--runtime exclusivity is argparse's job. The rest fails here, and
|
|
2702
|
+
by returning: every command in this CLI returns exit codes and never
|
|
2703
|
+
raises to the shell. Shared verbatim by `hook` and `skill` (#303).
|
|
2704
|
+
|
|
2705
|
+
Both --all and --force describe how to install, so on uninstall or status
|
|
2706
|
+
they can only be silently ignored. --force overrides the plugin check,
|
|
2707
|
+
and the plugin channel is Claude Code's alone, so on any other runtime it
|
|
2708
|
+
would claim to override something that does not exist."""
|
|
2709
|
+
force = getattr(args, "force", False)
|
|
2710
|
+
for flag, given in (("--all", getattr(args, "all", False)), ("--force", force)):
|
|
2711
|
+
if given and args.kind != "install":
|
|
2712
|
+
print(f"{flag} applies to install only")
|
|
2713
|
+
return 2
|
|
2714
|
+
if force and not args.runtime:
|
|
2715
|
+
print("--force needs --runtime: it overrides the plugin check for one "
|
|
2716
|
+
"named runtime, and detection never needs overriding.")
|
|
2717
|
+
return 2
|
|
2718
|
+
if force and args.runtime != "claude":
|
|
2719
|
+
print("--force applies to --runtime claude only, the plugin channel "
|
|
2720
|
+
"is Claude Code only")
|
|
2721
|
+
return 2
|
|
2722
|
+
return None
|
|
2723
|
+
|
|
2724
|
+
|
|
2725
|
+
def _lifecycle_no_runtime(kind: str, args,
|
|
2726
|
+
install_many: Callable[[list[str], Path, bool], int],
|
|
2727
|
+
status_fn: Callable[[], int]) -> int:
|
|
2728
|
+
"""No --runtime on install/status: look at the machine first, print what
|
|
2729
|
+
is there, then either decide-and-install or fall through to the existing
|
|
2730
|
+
per-runtime status. Plain print, not Rich, because the non-tty branch of
|
|
2731
|
+
a read command must stay unwrapped (scar 0008). `kind` doubles as the
|
|
2732
|
+
`hosts.decide` command name, matching the subcommand it is called under
|
|
2733
|
+
(`hook`/`skill`)."""
|
|
2734
|
+
from . import hosts
|
|
2735
|
+
found, repo = _detect(kind)
|
|
2736
|
+
print(hosts.render_table(found))
|
|
2737
|
+
if args.kind == "install":
|
|
2738
|
+
decision = hosts.decide(found, interactive=hosts.is_interactive(),
|
|
2739
|
+
all_flag=getattr(args, "all", False),
|
|
2740
|
+
ask=hosts.ask_yes_no, command=kind)
|
|
2741
|
+
for line in decision.lines:
|
|
2742
|
+
print(line)
|
|
2743
|
+
return install_many(decision.install, repo, args.dry_run)
|
|
2744
|
+
print()
|
|
2745
|
+
return status_fn()
|
|
2746
|
+
|
|
2747
|
+
|
|
2748
|
+
def _plugin_refusal(subject: str, target: str) -> None:
|
|
2749
|
+
"""The one-line refusal printed when `--runtime claude install` would
|
|
2750
|
+
write over a channel the scar plugin already serves. `subject` reads
|
|
2751
|
+
into "claude: {subject} provided by the scar plugin...", so it carries
|
|
2752
|
+
its own verb agreement ("hooks are", "the scar-authoring skill is")."""
|
|
2753
|
+
print(f"claude: {subject} provided by the scar plugin (scar@scar); "
|
|
2754
|
+
f"nothing written. Pass --force to install into {target} as well.")
|
|
2755
|
+
|
|
2756
|
+
|
|
2669
2757
|
def _cmd_hook_lifecycle(args) -> int:
|
|
2670
|
-
|
|
2758
|
+
from . import hosts
|
|
2759
|
+
from .installer import (
|
|
2760
|
+
CLAUDE_DIR,
|
|
2761
|
+
cascade_install,
|
|
2762
|
+
cascade_status,
|
|
2763
|
+
cascade_uninstall,
|
|
2764
|
+
codex_install,
|
|
2765
|
+
codex_status,
|
|
2766
|
+
codex_uninstall,
|
|
2767
|
+
git_hook_install,
|
|
2768
|
+
git_hook_status,
|
|
2769
|
+
git_hook_uninstall,
|
|
2770
|
+
install,
|
|
2771
|
+
status,
|
|
2772
|
+
uninstall,
|
|
2773
|
+
)
|
|
2774
|
+
dry = args.dry_run
|
|
2775
|
+
if (rc := _lifecycle_flag_guard(args)) is not None:
|
|
2776
|
+
return rc
|
|
2777
|
+
if getattr(args, "git", False):
|
|
2778
|
+
repo = Path.cwd()
|
|
2779
|
+
return {"install": lambda: git_hook_install(repo, dry=dry),
|
|
2780
|
+
"uninstall": lambda: git_hook_uninstall(repo, dry=dry),
|
|
2781
|
+
"status": lambda: git_hook_status(repo)}[args.kind]()
|
|
2782
|
+
runtime = args.runtime
|
|
2783
|
+
if runtime is None and args.kind in ("install", "status"):
|
|
2784
|
+
return _lifecycle_no_runtime("hook", args, _run_hook_installers, status)
|
|
2785
|
+
# uninstall with no --runtime keeps targeting Claude: removal is out of
|
|
2786
|
+
# the detection spec's scope, and widening it silently would rip hooks
|
|
2787
|
+
# out of hosts the user never named.
|
|
2788
|
+
runtime = runtime or "claude"
|
|
2789
|
+
if (runtime == "claude" and args.kind == "install"
|
|
2790
|
+
and not getattr(args, "force", False)
|
|
2791
|
+
and hosts.claude_plugin_enabled(CLAUDE_DIR)):
|
|
2792
|
+
_plugin_refusal("hooks are", "~/.claude/settings.json")
|
|
2793
|
+
return 0
|
|
2671
2794
|
if runtime == "codex":
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
if args.kind == "uninstall":
|
|
2676
|
-
return codex_uninstall(dry=args.dry_run)
|
|
2677
|
-
return codex_status()
|
|
2795
|
+
return {"install": lambda: codex_install(dry=dry),
|
|
2796
|
+
"uninstall": lambda: codex_uninstall(dry=dry),
|
|
2797
|
+
"status": codex_status}[args.kind]()
|
|
2678
2798
|
if runtime == "windsurf":
|
|
2679
|
-
from .installer import cascade_install, cascade_status, cascade_uninstall
|
|
2680
|
-
repo = Path.cwd()
|
|
2681
|
-
if args.kind == "install":
|
|
2682
|
-
return cascade_install(repo, dry=args.dry_run)
|
|
2683
|
-
if args.kind == "uninstall":
|
|
2684
|
-
return cascade_uninstall(repo, dry=args.dry_run)
|
|
2685
|
-
return cascade_status(repo)
|
|
2686
|
-
if getattr(args, "git", False):
|
|
2687
|
-
from .installer import git_hook_install, git_hook_status, git_hook_uninstall
|
|
2688
2799
|
repo = Path.cwd()
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
if args.kind == "install":
|
|
2696
|
-
return install(dry=args.dry_run)
|
|
2697
|
-
if args.kind == "uninstall":
|
|
2698
|
-
return uninstall(dry=args.dry_run)
|
|
2699
|
-
return status()
|
|
2800
|
+
return {"install": lambda: cascade_install(repo, dry=dry),
|
|
2801
|
+
"uninstall": lambda: cascade_uninstall(repo, dry=dry),
|
|
2802
|
+
"status": lambda: cascade_status(repo)}[args.kind]()
|
|
2803
|
+
return {"install": lambda: install(dry=dry),
|
|
2804
|
+
"uninstall": lambda: uninstall(dry=dry),
|
|
2805
|
+
"status": status}[args.kind]()
|
|
2700
2806
|
|
|
2701
2807
|
|
|
2702
2808
|
def _cmd_skill_lifecycle(args) -> int:
|
|
2703
|
-
from .
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if args
|
|
2707
|
-
return
|
|
2708
|
-
|
|
2809
|
+
from . import hosts
|
|
2810
|
+
from .installer import CLAUDE_DIR, skill_install, skill_status, skill_uninstall
|
|
2811
|
+
dry = args.dry_run
|
|
2812
|
+
if (rc := _lifecycle_flag_guard(args)) is not None:
|
|
2813
|
+
return rc
|
|
2814
|
+
runtime = args.runtime
|
|
2815
|
+
if runtime is None and args.kind in ("install", "status"):
|
|
2816
|
+
def install_many(names: list[str], repo: Path, dry: bool) -> int:
|
|
2817
|
+
del repo # skill has exactly one wirable host: claude
|
|
2818
|
+
return skill_install(dry=dry) if "claude" in names else 0
|
|
2819
|
+
return _lifecycle_no_runtime("skill", args, install_many, skill_status)
|
|
2820
|
+
# uninstall with no --runtime keeps targeting Claude, the only wirable
|
|
2821
|
+
# host today: same rule as `hook`.
|
|
2822
|
+
runtime = runtime or "claude"
|
|
2823
|
+
if (runtime == "claude" and args.kind == "install"
|
|
2824
|
+
and not getattr(args, "force", False)
|
|
2825
|
+
and hosts.claude_plugin_enabled(CLAUDE_DIR)):
|
|
2826
|
+
_plugin_refusal("the scar-authoring skill is", "~/.claude/skills")
|
|
2827
|
+
return 0
|
|
2828
|
+
return {"install": lambda: skill_install(dry=dry),
|
|
2829
|
+
"uninstall": lambda: skill_uninstall(dry=dry),
|
|
2830
|
+
"status": skill_status}[args.kind]()
|
|
2709
2831
|
|
|
2710
2832
|
|
|
2711
2833
|
def _scar_version() -> str:
|
|
@@ -2866,22 +2988,34 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2866
2988
|
p.add_argument("--dry-run", action="store_true",
|
|
2867
2989
|
help="show lifecycle changes without writing settings")
|
|
2868
2990
|
# One invocation, one target: --git writes .git/hooks/post-commit,
|
|
2869
|
-
# --runtime windsurf writes .windsurf/hooks.json,
|
|
2870
|
-
#
|
|
2991
|
+
# --runtime windsurf writes .windsurf/hooks.json, --all writes to every
|
|
2992
|
+
# unserved host detection finds, and no flag at all means detect and ask.
|
|
2993
|
+
# Passing two can only mean one is ignored.
|
|
2871
2994
|
target = p.add_mutually_exclusive_group()
|
|
2872
2995
|
target.add_argument("--git", action="store_true",
|
|
2873
2996
|
help="with install/uninstall/status: target this repo's "
|
|
2874
2997
|
".git/hooks/post-commit trigger for `scar draft-check` "
|
|
2875
2998
|
"(#117) instead of Claude Code's settings.json")
|
|
2876
2999
|
target.add_argument("--runtime", choices=["claude", "codex", "windsurf"],
|
|
2877
|
-
default=
|
|
3000
|
+
default=None,
|
|
2878
3001
|
help="with install/uninstall/status: which runtime to wire. "
|
|
3002
|
+
"Omitted: detect installed hosts and ask (install), or "
|
|
3003
|
+
"show every host's channel (status); uninstall still "
|
|
3004
|
+
"targets claude. "
|
|
2879
3005
|
"codex writes ~/.codex/hooks.json — shared with other "
|
|
2880
3006
|
"tools, so merged, and inactive until you trust the "
|
|
2881
3007
|
"entries in Codex's `/hooks` (#246); "
|
|
2882
3008
|
"windsurf writes this repo's committed "
|
|
2883
3009
|
".windsurf/hooks.json (Cascade block-once, #197); "
|
|
2884
|
-
"claude
|
|
3010
|
+
"claude writes ~/.claude/settings.json")
|
|
3011
|
+
target.add_argument("--all", action="store_true",
|
|
3012
|
+
help="with install: wire every detected, unserved host "
|
|
3013
|
+
"without asking. A target like the other two, so "
|
|
3014
|
+
"argparse rejects it next to --runtime or --git "
|
|
3015
|
+
"rather than letting one be silently ignored")
|
|
3016
|
+
p.add_argument("--force", action="store_true",
|
|
3017
|
+
help="with --runtime claude: install into the settings file even "
|
|
3018
|
+
"though the scar plugin already serves that host")
|
|
2885
3019
|
|
|
2886
3020
|
_add(sub, "cascade-hook", _cmd_cascade_hook,
|
|
2887
3021
|
help="run the Windsurf/Cascade hook adapter (stdin JSON; installed by "
|
|
@@ -2891,6 +3025,23 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2891
3025
|
p.add_argument("kind", choices=["install", "uninstall", "status"])
|
|
2892
3026
|
p.add_argument("--dry-run", action="store_true",
|
|
2893
3027
|
help="show changes without writing to ~/.claude/skills")
|
|
3028
|
+
# One invocation, one target: --runtime claude writes ~/.claude/skills,
|
|
3029
|
+
# --all writes to every unserved host detection finds (only claude is
|
|
3030
|
+
# wirable today), and no flag at all means detect and ask.
|
|
3031
|
+
target = p.add_mutually_exclusive_group()
|
|
3032
|
+
target.add_argument("--runtime", choices=["claude"], default=None,
|
|
3033
|
+
help="with install/uninstall/status: which host to wire. "
|
|
3034
|
+
"Omitted: detect installed hosts and ask (install), "
|
|
3035
|
+
"or show every host's channel (status); uninstall "
|
|
3036
|
+
"still targets claude")
|
|
3037
|
+
target.add_argument("--all", action="store_true",
|
|
3038
|
+
help="with install: wire every detected, unserved host "
|
|
3039
|
+
"without asking. A target like --runtime, so "
|
|
3040
|
+
"argparse rejects it next to --runtime rather than "
|
|
3041
|
+
"letting one be silently ignored")
|
|
3042
|
+
p.add_argument("--force", action="store_true",
|
|
3043
|
+
help="with --runtime claude: install even though the scar plugin "
|
|
3044
|
+
"already ships the skill")
|
|
2894
3045
|
|
|
2895
3046
|
_add(sub, "mcp", _cmd_mcp, help="run the SCAR MCP stdio server")
|
|
2896
3047
|
|