Skip to content

Add antianqi/tool-map v0.2.0: persistent cross-platform tool inventory - #5

Open
antianqi wants to merge 2 commits into
MiniMax-AI:mainfrom
antianqi:add-tool-map
Open

Add antianqi/tool-map v0.2.0: persistent cross-platform tool inventory#5
antianqi wants to merge 2 commits into
MiniMax-AI:mainfrom
antianqi:add-tool-map

Conversation

@antianqi

@antianqi antianqi commented Aug 19, 2026

Copy link
Copy Markdown

Summary

Adds plugins/antianqi/tool-map v0.2.0: a Skill-only Plugin that generates and refreshes a persistent, cross-platform inventory of the CLI tools, scripts, and MCP servers installed on the user's machine, so the agent can answer "do I have X?", "where is Y?", "how do I run Z?" without re-scanning the filesystem every session.

The catalog is written as three files (lightweight summary, full markdown, machine JSON) into the Plugin data directory, exposed to the agent as ${PLUGIN_DATA}. Subsequent turns read the cached summary; refresh only on user demand, when a tool the user mentions is missing, or when a command not found is reported in the same session.

What's inside

  • plugin.json -- $schema=agent-plugins.org/schemas/1.0.0/plugin.schema.json, name=tool-map, version=0.2.0, license=Apache-2.0
  • README.md -- overview, Supported platforms table, four independent "no credentials / no network / no telemetry / no third-party services" disclosures, limitations, test evidence
  • LICENSE -- Apache-2.0 (full text, LF only, no BOM)
  • skills/tool-map/SKILL.md -- agent-facing workflow: read cached summary, refresh rules, failure modes, cross-platform roots (frontmatter present, LF only)
  • scripts/scan.mjs -- cross-platform Node scanner, zero external deps, atomic staging-then-rename writes; all well-known roots derived from $HOME, $ProgramFiles, $APPDATA, $LOCALAPPDATA, $PATH, $TOOL_MAP_ROOTS, or fixed POSIX conventions (no per-user absolute paths in source); 15 well-known CLI version probes with 5 s timeouts
  • scripts/smoke.mjs -- self-check that statically scans the Plugin's own source tree for hardcoded absolute paths, literal credential tokens, and leftover scaffold markers; exits 0 (clean) / 2 (violation) / 1 (internal)
  • test/tool-map.test.mjs -- 6 node --test cases covering atomic write, output schema, no-leakage outside the output dir, no staging residue, empty-PATH robustness, and smoke green (auto-discovered by the repo's npm test)

Validation

$ npm run check
> minimax-code-plugins@0.1.0 check
> npm run validate && npm test

> minimax-code-plugins@0.1.0 validate
> node scripts/validate.mjs
OK   example hello-mcode
OK   example hello-mcode-mcp
OK   plugin Fectivnfy112357/github-explore
OK   plugin hetaoBackend/minimax-code-trajectory
OK   plugin HopeYin/dida365
OK   plugin HopeYin/ticktick
OK   plugin Hylouis233/mcp-server-patterns
OK   plugin Hylouis233/search-first
OK   plugin Hylouis233/verification-loop
OK   plugin antianqi/tool-map
$ node --test test/tool-map.test.mjs
> scan.mjs writes the three catalog files atomically (705ms)
> scan.mjs JSON has the expected schema (729ms)
> scan.mjs writes nothing outside the output directory (726ms)
> scan.mjs leaves no staging files on success (742ms)
> scan.mjs completes with an empty PATH and still produces a valid catalog (111ms)
> smoke.mjs exits 0 against the plugin source tree (34ms)
tests 6
pass 6
fail 0
$ node scripts/smoke.mjs
OK scanned 2 files, 0 violations.

(One pre-existing test failure on Windows is unrelated to this Plugin: test/hosted-plugins.test.mjs:15 hard-codes the regex /plugins\/alice\/hello-world/u against stdout from create-plugin.mjs, which produces backslash-separated paths on Windows. The repo's CI runs on Linux and the test passes there.)

Design compliance (per hetaoBackend review rubric on PRs #2/#3)

# Lesson Compliance in this PR
1 In-scope discipline: do not touch files outside plugins/<owner>/<name>/ Only plugins/antianqi/tool-map/* and test/tool-map.test.mjs are added; no edits to repo-root files, no writes to ~/.minimax/, no ~/.openclaw*/ side effects.
2 Portability: no hardcoded per-user absolute paths scan.mjs uses $HOME, $ProgramFiles, $APPDATA, $LOCALAPPDATA, $PATH, $TOOL_MAP_ROOTS, and fixed POSIX paths. smoke.mjs statically verifies no D:\ / C:\ / E:\ / /Users/ / /home/ literal in any .md/.mjs file.
3 Credential disclosure consistency README and SKILL.md each carry an independent "no credentials / no network / no telemetry / no third-party services" disclosure. The Plugin embeds no tokens, no OAuth clients, and reads only filenames under ~/.ssh/.
4 Network destination boundary Scanner makes zero network calls and ships zero credentials. The Skill instructs the agent to call the scanner as a local node process only.
5 Delivery model portability Zero npm install / npm link is required. The scanner runs as a plain node ./scripts/scan.mjs with only Node built-ins.
6 Atomic / safe file operations Every output file is written via <out>.staging-<pid>-<rand> then rename. On any failure the staging file is removed and the previous catalog is untouched.
7 Lint / failure semantics smoke.mjs exits 0 (clean) / 2 (violation) / 1 (internal); never swallows FAIL.
8 Test coverage for converters 6 node --test cases plus a behavioural smoke check; the scan + summary + JSON workflow is exercised end-to-end against a temp directory.
9 External SDK contract None required (no MCP, no remote server, no third-party SDK).
10 Self-check coverage smoke.mjs walks skills/ and scripts/ recursively and flags any hardcoded path / literal token / scaffold marker.
11 PR scope One Plugin only, one commit, one branch.

Forward compatibility with PR #4 (validator hardening, not yet merged)

  • No mcp.json is shipped, so the proposed cwd / env / headers hardening does not apply. The ${PLUGIN_DATA} and ${PLUGIN_ROOT} placeholders appear in narrative text only, never in executable cwd values.
  • SKILL.md is LF-only with no UTF-8 BOM, satisfying the proposed validateSkillText normalization rule.

Compatibility

Platform Status Notes
Windows 10 / 11 (PowerShell 5.1+ or pwsh 7) Supported (primary) Drives, %ProgramFiles%, %APPDATA%, %LOCALAPPDATA% resolved from environment.
macOS 12+ (bash / zsh) Supported ~/.local/bin, /usr/local/bin, /opt/homebrew/bin walked.
Linux x86_64 / arm64 Supported ~/.local/bin, ~/.local/share/npm/bin, /usr/local/bin walked.

Links

  • Source branch: add-tool-map on antianqi/MiniMax-Code-Plugins-1
  • This PR: antianqi:add-tool-map -> MiniMax-AI:main

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Generates a three-file catalog (tools.summary.md, tools.md, tools.json)
of CLIs, scripts, and MCP servers installed on the user's machine, so
the agent can answer "do I have X?", "where is Y?", "how do I run Z?"
without re-scanning the filesystem every session.

Plugin shape (Skill-only, zero external deps, no package.json):
- skills/tool-map/SKILL.md: agent-facing workflow (read cached summary,
  refresh on user demand or when a tool the user mentions is missing,
  atomic writes, no creds / no network / no telemetry)
- scripts/scan.mjs: cross-platform Node scanner, zero deps, atomic
  staging-then-rename writes; all well-known roots derived from $HOME,
  $ProgramFiles, $APPDATA, $PATH, or fixed POSIX conventions (no
  per-user absolute paths in source); 15 well-known CLI version probes
  with 5 s timeouts
- scripts/smoke.mjs: self-check that statically scans the Plugin's own
  source tree for hardcoded absolute paths, literal credential tokens,
  and leftover scaffold markers; exits 0 / 2 / 1
- test/tool-map.test.mjs: 6 node --test cases covering atomic write,
  output schema, no-leakage outside the output dir, no staging residue,
  empty-PATH robustness, and smoke green

Validation evidence (Windows 11, Node 24.18.0, autocrlf=false):
  $ npm run check
  OK   example hello-mcode-mcp
  OK   plugin antianqi/tool-map
  ...
  tests 6
  pass 6
  fail 0

  $ node scripts/smoke.mjs
  OK scanned 2 files, 0 violations.

Design compliance (per hetaoBackend review rubric on PRs MiniMax-AI#2/MiniMax-AI#3):
  1. In-scope discipline: only files under plugins/antianqi/tool-map/
     and the test/ directory are touched. No edits to repo-root files,
     no writes to ~/.minimax/, no ~/.openclaw*/ side effects.
  2. Portability: scan.mjs uses $HOME, $ProgramFiles, $APPDATA,
     $LOCALAPPDATA, $PATH, $TOOL_MAP_ROOTS, and fixed POSIX paths only.
     smoke.mjs statically verifies no D:/C:/E:/ or /Users/ or /home/
     literal in any .md/.mjs file.
  3. Credential disclosure: README and SKILL.md each have an
     independent "no credentials / no network / no telemetry / no
     third-party services" disclosure (per round-2 review of
     antianqi/openclaw-acp-bridge MiniMax-AI#2).
  4. Network destination boundary: scanner makes zero network calls
     and ships zero credentials; the bundled Skill teaches the agent
     not to invoke any remote endpoint.
  5. Delivery model: zero `npm install` / `npm link` is required. The
     scanner runs as a plain `node ./scripts/scan.mjs` process with
     only Node built-ins.
  6. Atomic / safe file operations: every output file is written via
     `<out>.staging-<pid>-<rand>` then `rename`. On any failure the
     staging file is removed and the previous catalog is untouched.
  7. Lint / failure semantics: smoke.mjs exits 0 / 2 / 1; never
     swallows FAIL.
  8. Test coverage: 6 node --test cases; smoke.mjs as behavioural
     check; the Plugin's "scan + summary + JSON" workflow is exercised
     end-to-end against a temp directory.
  9. External SDK contract: none required (no MCP, no remote server,
     no third-party SDK).
 10. Self-check coverage: smoke.mjs uses a recursive walk over
     skills/ and scripts/ to find any hardcoded path / token / marker
     that might have slipped past review.

Forward compatibility with PR MiniMax-AI#4 (validator hardening, not yet merged):
- No mcp.json is shipped, so cwd / env / headers hardening does not
  apply. The scan.mjs and SKILL.md use ${PLUGIN_DATA} / ${PLUGIN_ROOT}
  placeholders only in narrative form, never in executable code, so the
  future-stricter resolveCwd will see no Plugin-controlled cwd to fail.
- SKILL.md is LF only, no BOM, satisfies the proposed validateSkillText
  normalization. (The merged main validator also accepts LF directly.)

Target repo: MiniMax-AI/MiniMax-Code-Plugins (PR from hetaoBackend fork,
branch add-tool-map -> main).

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review result: do not approve / do not merge yet.

The repository check passes (33 tests), but two security/contract issues are blocking:

  • scripts/scan.mjs:374-376 writes tools.md, tools.json, and tools.summary.md via three independent atomic renames. A failure between writes leaves mixed-generation catalogs, despite the bundle-level atomicity claim in README.md:21,68 and skills/tool-map/SKILL.md:28,61. Please add a rollback/bundle strategy and a failure-path test.
  • scripts/scan.mjs:115-143 executes 15 PATH-resolved programs with --version. That can run arbitrary wrappers with side effects/network access, contradicting the documented read-only/offline contract (README.md:49-57, skills/tool-map/SKILL.md:60-64). Either remove execution or explicitly disclose/guard it as command execution.

Additional correctness issues found: XDG_DATA_HOME is ignored (scan.mjs:34 despite the XDG claim), unconditional realpath.toLowerCase() drops distinct tools on case-sensitive filesystems (scan.mjs:330-336), and non-executable .sh files are reported as tools because execute permission is never checked (scan.mjs:148-199).

Please address the two P1 findings and add adversarial coverage before requesting another review.

antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 22, 2026
Fixes for review comments from hetaoBackend (commit fce7c5f):

  MiniMax-AI#1 detector hard-coded path: resolve the [userprofile]/.minimax-code
     directory at runtime via the mcode node process cmdline (regex on
     @minimax-ai/code/cli.js), with fallbacks to $env:USERPROFILE/.minimax-code,
     $env:APPDATA/minimax-code, and the current working directory.
     Override with -Root [path].

  MiniMax-AI#2 idle fallback unreachable: mtime cache now returns the last inferred
     message instead of null, so the 60s stale -> idle branch fires every
     poll. Verified locally: idle :: already idle 195s after 65s of inactivity.

  #2b session log: prefer ledger.jsonl (mcode v2 event stream) and fall
     back to messages.jsonl when ledger is missing. Both formats are handled
     in Infer-State (kind/phase for ledger, message.role for messages).

  MiniMax-AI#3 PID reuse safety: start/stop-{island,detect-island}.ps1 now verify
     the target PID command line contains the expected script path before
     acting. Stale PIDs and PID-reused processes are refused with a
     REFUSED log line instead of being killed.

  MiniMax-AI#4 wrap-tool.ps1 shell-injection: removed Invoke-Expression entirely.
     The wrapper is now status-only; the agent runs the command via mcode's
     own bash tool and passes -ExitCode to publish the outcome.
     Documented in README + SKILL.md.

  MiniMax-AI#5 README: -Enable -> -Action Enable to match autostart.ps1 parameter set.

  MiniMax-AI#6 start-island.ps1 readiness: dropped the 'about to ShowDialog' log wait
     (which was never emitted). Now polls MainWindowHandle != 0 every 500ms
     for up to 8s.

Tests: validator reports OK plugin antianqi/mcode-island. wrap-tool
6-state matrix verified locally (working / done / waiting / error).
antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 23, 2026
…view MiniMax-AI#5)

The review pointed out that README.md:128-130 advertises a
`.github/workflows/openclaw-acp-bridge-smoke.yml` CI workflow that
was not part of the PR. We add the file and teach the smoke
test to be CI-friendly.

  - scripts/smoke.py: add SMOKE_SKIP_LIVE=1. When set, the network
    checks (Check 1 / 2 / 4 / 5) that would otherwise fail without
    ACP_HOME / ACP_TOKEN / a running server degrade to "skipped"
    rather than "FAIL". Static checks (Check 3, Check 6) still
    run. Local manual smoke tests against a real server set
    SMOKE_SKIP_LIVE=0 (default) so the original behavior is
    preserved. This makes the smoke test pass in CI without a
    live server.
  - .github/workflows/openclaw-acp-bridge-smoke.yml: runs the
    smoke test under ubuntu-latest with Python 3.11 and
    SMOKE_SKIP_LIVE=1, then runs `node scripts/validate.mjs` to
    confirm the plugin manifest is still valid. Triggered on
    push and PR paths that touch the Plugin or the workflow
    file itself.
  - skills/*/SKILL.md: drop UTF-8 BOM and normalize line
    endings to LF. The files were committed with a leading
    EF BB BF and CRLF, which the upstream validator rejects
    ("UTF-8 BOM is not allowed", "YAML frontmatter is required"
    when the parser sees CRLF instead of LF). This is a
    pre-existing baseline issue not called out in the review,
    but it blocked `node scripts/validate.mjs` from passing
    for the openclaw-acp-bridge plugin until now.

Verified locally:
  $ SMOKE_SKIP_LIVE=1 python scripts/smoke.py
  ... 8/8 PASS, 0 FAIL
  $ node scripts/validate.mjs | grep openclaw
  OK   plugin antianqi/openclaw-acp-bridge
…ectness)

Two P1 blockers from the hetaoBackend review:

P1-1: bundle-level atomicity was a lie
  scan.mjs:374-376 wrote tools.md / tools.json / tools.summary.md via three
  independent atomic renames. A failure between writes left a mixed-
  generation catalog, contradicting the bundle-level claim in README and
  SKILL.md. Rewrite atomicWriteBundle as a proper two-phase commit:
    1. move every existing target to .bundle.backup-<pid>-<rand>/
    2. write all new content into .bundle.staging-<pid>-<rand>/
    3. rename each staging file onto its target
    4. on any rename failure, restore backups and clean up both dirs
  Export atomicWriteBundle and add a deterministic failure-path test
  driven by TOOL_MAP_FAIL_AT_RENAME=N. Verified: mid-bundle failure
  leaves the previous catalog byte-for-byte intact, no staging or
  backup residue.

P1-2: subprocess execution contradicts read-only contract
  scan.mjs:115-143 spawned 15 PATH-resolved programs with --version.
  Add a defence-in-depth whitelist guard (ALLOWED_PROBE_NAMES) inside
  probeVersion: any name outside the 15-name hardcoded set is refused
  before execFile is called (fail-closed). Document the side effect
  explicitly in README and SKILL.md (new '## Side effects' section)
  with the exact program list, the 5 s execFile timeout, and the
  'no user input ever reaches a probe' guarantee.

Three correctness issues also fixed:

  - XDG_DATA_HOME is now honoured when PLUGIN_DATA is unset (the
    README already claimed this; the implementation hardcoded
    \C:\Users\Administrator/.local/share/tool-map).
  - Dedupe no longer lower-cases the resolved path. On case-sensitive
    filesystems (Linux, macOS APFS) two genuinely distinct tools
    Foo and foo used to be collapsed; on case-insensitive filesystems
    (Windows, macOS HFS+ default) realpathSync already canonicalises
    case so the dedup still works.
  - On POSIX, isToolFile now requires the execute bit (mode & 0o111).
    A foo.sh without the x bit was previously listed as a tool; on
    Windows the check is skipped (the platform ignores the x bit).

Tests (test/tool-map.test.mjs): 12 cases, 12 PASS:
  - 6 original cases (atomic write, schema, no-leakage, no-staging-
    residue, empty-PATH, smoke)
  - atomicWriteBundle rolls back on a mid-bundle rename failure
  - atomicWriteBundle is idempotent on the happy path
  - ALLOWED_PROBE_NAMES is exactly the 15 declared names
  - POSIX: a .sh file without the execute bit is not reported
  - POSIX: case-distinct tool names on case-sensitive filesystems
    are kept distinct
  - XDG_DATA_HOME is honoured when PLUGIN_DATA is unset

Full suite (excluding the pre-existing Windows-only hosted-plugins
breakage acknowledged in the PR description): 38 PASS / 1 FAIL.
@antianqi

Copy link
Copy Markdown
Author

All 5 review blockers are fixed at 5c6d11d (single commit on add-tool-map).

What changed

# Reviewer finding Fix Notes
P1-1 Bundle-level atomicity was false: three independent renames could leave a mixed catalog on mid-bundle failure. Rewrote atomicWriteBundle as a proper two-phase commit: backup existing targets → write to staging → atomic rename per file → restore on failure. Exported for direct testing. test/tool-map.test.mjs now has a deterministic failure-path test driven by TOOL_MAP_FAIL_AT_RENAME=N (Windows' MoveFileExW overwrites read-only files, so OS-level fault injection isn't portable).
P1-2 scan.mjs:115-143 spawned 15 PATH-resolved programs with --version, contradicting the documented read-only contract. Added a ALLOWED_PROBE_NAMES whitelist guard inside probeVersion (fail-closed; any name outside the 15-name set is refused before execFile). Documented the side effect in a new ## Side effects section in both README.md and SKILL.md. Whitelist is the single source of truth: VERSION_PROBES is the only place new probes can be added. execFile (not shell) is already used, so a same-named wrapper on $PATH cannot be tricked via shell metacharacters.
Correctness XDG_DATA_HOME was hardcoded as the fallback despite the README claiming it was honoured. scan.mjs:34 now reads process.env.XDG_DATA_HOME before falling back to $HOME/.local/share. Regression test included.
Correctness realpath.toLowerCase() dedup dropped distinct tools on case-sensitive filesystems. Dedup is now on the full realpath; on case-insensitive filesystems realpathSync already canonicalises case so the behaviour is unchanged. POSIX-only regression test included.
Correctness isToolFile reported .sh files without the execute bit as tools. On POSIX, mode & 0o111 is now required. Windows ignores the x bit (per platform convention), so the check is skipped there. POSIX-only regression test included (chmods the file 0o644 vs 0o755 and asserts).

Verification

node --test test/tool-map.test.mjs: 12 / 12 PASS across the original 6 cases plus 4 new ones (atomicWriteBundle rollback + happy path, ALLOWED_PROBE_NAMES integrity, POSIX x-bit filter, POSIX case-distinct dedup, XDG_DATA_HOME precedence). scripts/smoke.mjs exits 0 against the plugin source tree.

Full suite: 38 PASS / 1 FAIL. The one remaining failure is the pre-existing test/hosted-plugins.test.mjs:15 Windows-only issue acknowledged in the original PR description (hard-coded regex /plugins\/alice\/hello-world/u vs Windows' backslash paths). Unchanged by this PR.

Ready for another review pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants