feat(scripts): ship the repository as the cli-tools Claude Code plugin - #158
Conversation
The cli-tools skill lived in netresearch/cli-tools-skill, a fork carrying
its own copy of these scripts and catalog, and the two drifted: the last
three PRs ported fixes the fork had and this repository lacked. The skill
now ships from here and calls these scripts through
${CLAUDE_SKILL_DIR}/../../scripts/, so there is one copy.
- .claude-plugin/plugin.json and the portable Agent Plugins plugin.json.
No version: Claude Code then versions the plugin by commit, so every
merge reaches users; a pinned version would freeze them until someone
bumps it, and this repository has no release flow that would.
- skills/cli-tools/SKILL.md and references/, ported from the fork and
adapted. The binary map is rebuilt from the catalog (the fork mapped
`docker` to `compose`; here docker has its own entry, and difft, awf,
gws, wslview and ble.sh were missing). Project tools that are not
cataloged (phpstan, mypy, ...) are now listed as project dependencies
instead of machine requirements.
- scripts/check_environment.sh and scripts/detect_project_type.sh, ported
with fixes found by running them:
* detect json printed `"project_types": [""]` for a directory with no
type -- the JSON is now built by jq;
* `k8s` was tested with -f, so a k8s/ directory never matched;
* required tools were checked by catalog name, so rust (rustc),
compose (docker) and ansible-core (ansible) read as missing -- the
binary now comes from the catalog's binary_name;
* duplicate detection counted one file reached through two PATH
entries, or /bin -> /usr/bin, and virtualenvs, as several
installations -- it now uses detect_all_installations;
* version probes ran without </dev/null through `| head -1`, and
`go --version` does not exist.
The ansible heuristic that matched any *.yml containing "hosts:" is gone.
- LICENSE becomes LICENSE-MIT, plus LICENSE-CC-BY-SA-4.0 for the skill
content: the fork's split licensing, kept.
- evals/evals.json, the fork's 25 evals, unchanged.
The PostToolUse hook from the fork is not included: it read keys the hook
input does not have and printed to stdout, which Claude Code does not pass
to the model, and PostToolUse does not fire for a failed command at all.
A working version follows separately.
tests/test_claude_plugin.py ties the prose to the files: manifests, every
script and reference SKILL.md names, the binary table against the catalog,
and the project-type table against detect_project_type.sh. With the fork's
two scripts swapped in, 5 of its 6 script tests fail.
Assisted-by: claude-code:claude-opus-5-5
Agent-Session: https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m
Agent-Host: 32116e
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
|
Warning Review limit reachedNext included review available in 51 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (17)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Review before mergeCodeRabbit refused this head as rate limited and Copilot is out of quota, so the review of Checked against the tools, not the description.
Limits, stated. 21 checks pass, 0 fail; 0 unresolved threads; full suite 1014 passed / 1 skipped locally. Assisted by claude-code:claude-opus-5-5 — Session |
|
Self-review: 4224413 The bot review this pull request demands is unsatisfiable (Copilot quota wall or repeated bot failures on this head). The diff on this head was reviewed by the PR author; this comment is the on-the-record attestation the merge gate reads back. It stops matching on the next push. |
Merging this adds the `cli-tools` plugin's hook. When a Bash call fails with `command not found`, Claude gets the catalog entry that provides the binary and the exact install command in its context (`rg` → catalog entry `ripgrep` → `…/scripts/install_tool.sh ripgrep install`). **Why a new hook rather than the fork's.** `netresearch/cli-tools-skill` shipped one, and against the [hooks reference](https://code.claude.com/docs/en/hooks) it could never have fired: - It was registered for `PostToolUse` only, which fires after a *successful* call. A missing command exits 127 and fires `PostToolUseFailure`. - It read `output`/`stdout`/`stderr` at the top level of the input. The failure text actually arrives in `error` (`PostToolUseFailure`) or in `tool_response.stderr` (`PostToolUse`). - It printed to stdout, which these events do not pass to the model. Text reaches the model only through `hookSpecificOutput.additionalContext`. Given either documented payload, the fork's script prints nothing; that was measured, not inferred. **How this one works.** `hooks/hooks.json` registers `scripts/detect_missing_tool.py` for both events on `Bash`: - `PostToolUseFailure` reads `error`. - `PostToolUse` reads `tool_response.stderr`. This covers a missing command inside a pipeline or list whose exit status is still 0. The script matches only the lines shells print: bash, zsh (including `(eval):1:`) and dash forms, never the phrase anywhere in the output. It looks the binary up by file name and by `binary_name` in `catalog/*.json`. It uses only the standard library and fails open on any error, so it cannot break the call it observes. **Tests.** `tests/test_detect_missing_tool.py` covers: - each message form, plus the real stderr of `bash`, `sh` and (where installed) `zsh`; - both payload shapes, including that `PostToolUse` reads stderr only; - the JSON output and the `hooks.json` registration. With the fork's script in place, the output test fails: it emits nothing. Full suite 1019 passed / 2 skipped. Independent of #158, which adds the plugin manifest and skill; the hook is loaded once both are on `main` and the plugin is installed from this repository. _Assisted by claude-code:claude-opus-5-5 — [Session](https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m)_ https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m
Merging this makes `/plugin install cli-tools@netresearch-claude-code-marketplace` install from [`netresearch/coding_agent_cli_toolset`](https://github.com/netresearch/coding_agent_cli_toolset) instead of `netresearch/cli-tools-skill`. The plugin keeps its name. **Why.** The `cli-tools` skill always wrapped the toolset's scripts and catalog; `cli-tools-skill` carried a copy of them that drifted. The toolset now ships the plugin itself ([#158](netresearch/coding_agent_cli_toolset#158)), after the fixes only the fork had were ported ([#154](netresearch/coding_agent_cli_toolset#154), [#155](netresearch/coding_agent_cli_toolset#155), [#156](netresearch/coding_agent_cli_toolset#156)). The fork is archived once this is merged. **Changes.** `.claude-plugin/marketplace.json` source repo, the README catalog row, and the German description; the catalog count reads 100+ (the toolset has 107 entries) instead of 74+. No `version` is set here or in the plugin's manifest, so Claude Code versions the plugin by commit. `./scripts/validate.sh` passes (40 plugins, no warning for this entry). _Assisted by claude-code:claude-opus-5-5 — [Session](https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m)_ https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m



Merging this makes the repository installable as the
cli-toolsClaude Code plugin: its skill resolvescommand not found, installs and updates catalog tools and audits a project's environment by calling this repository's own scripts. It replaces the separatenetresearch/cli-tools-skill, which carried a drifting copy of these scripts; that fork is archived and the marketplace entry repointed afterwards.What is added.
.claude-plugin/plugin.jsonplus the portable Agent Pluginsplugin.jsonat the root, kept in sync (sync-plugin-manifest.sh --checkpasses). Noversion: Claude Code then versions the plugin by commit SHA, so every merge tomainreaches users. A pinned version would hold them back until someone bumps it, and this repository has no release flow that would (docs).skills/cli-tools/SKILL.mdand five references, ported from the fork and adapted. Scripts are called as${CLAUDE_SKILL_DIR}/../../scripts/…, so no copy exists. The binary map is rebuilt from the 107-entry catalog: the fork mappeddockertocompose, butdockerhas its own entry here, anddifft,awf,gws,wslviewandble.shwere missing. Tools that are not cataloged, such as phpstan and mypy, are now listed as project dependencies rather than machine requirements. The fork'sallowed-toolsgrant forapt,brew,npmandpipis dropped, so installs go through the normal permission prompt.scripts/check_environment.shandscripts/detect_project_type.sh, ported with fixes that came out of running them here:detect … jsonprinted"project_types": [""]for an empty directory. The JSON is now built by jq.k8swas tested with-f, so ak8s/directory never matched.rust(rustc),compose(docker) andansible-core(ansible) read as missing. The binary now comes frombinary_name./bin→/usr/bin, and virtualenvs as several installations. It now usesdetect_all_installations.</dev/nullthrough| head -1, andgo --versiondoes not exist.*.ymlcontaininghosts:; it is gone.LICENSEbecomesLICENSE-MIT, andLICENSE-CC-BY-SA-4.0covers the skill content (about 430 of the added lines).evals/evals.json: the fork's 25 evals, unchanged.Not included: the fork's hook. Measured against the hooks reference, it could never have fired. It read
output/stdoutat the top level, where the input hastool_response, and it printed to stdout, which Claude Code does not pass to the model; that takesadditionalContext. Most of all,PostToolUsedoes not fire for a failed command, which is exactly thecommand not foundcase;PostToolUseFailuredoes. A working version follows in its own PR.Tests.
tests/test_claude_plugin.pyties the prose to the files: both manifests, every script and reference that SKILL.md names, the binary table against the catalog in both directions, and the project-type table againstdetect_project_type.sh. With the fork's two scripts swapped in and the tests kept, 5 of the 6 script tests fail. Full suite 1014 passed / 1 skipped; both shell test scripts andtest_smoke.shpass.validate-skill.sh .reports no errors for the skill itself. The remaining repo-level errors are a pre-existingsmart_column.pymode, which is ruff-specific and not applicable to this flake8 repo, andcomposer.json. Whether a composer or npm channel should continue is settled together with the fork's archival, against its actual consumers.Assisted by claude-code:claude-opus-5-5 — Session
https://claude.ai/code/session_01NxSeVq1hDnGBCqKLGcjQ6m