25 Commits
Author SHA1 Message Date
Julius BrusseeandClaude Fable 5 ed1fbb7f4c fix(install): rename bin/ to cli/, harden Windows quoting, clean uninstall
Marketplace fix (#712, #705): Claude Desktop rejects plugins containing a
top-level bin/ directory, and .claude-plugin/marketplace.json packages the
repo root, so the installer directory is now cli/. Every reference updated
(package.json bin entry + files, shims, docs, tests, caveman-init require
path). Supersedes PR #726.

Security (PR #717 verified): quoteWinArg only quoted on whitespace/quotes,
leaving cmd.exe metacharacters (& | ^ < > % parens) unescaped on the
shell:true Windows spawn path. Attacker-influenced arguments (--with-init
cwd, --with-mcp-shrink value) could chain commands. Trigger regex now
covers the metacharacter set; quoting logic split into a platform-
independent, unit-tested helper.

Also:
- uninstall removes .caveman-active.prev, .caveman-mode-log.jsonl,
  .caveman-statusline-suffix, .caveman-nudge-shown; keeps
  .caveman-history.jsonl with a printed note; dry-run now says
  'would remove' instead of lying (#635, supersedes PRs #693 #636)
- Array.isArray guard in rewriteLegacyManagedHookCommands — malformed
  hook event no longer crashes the installer mid-run (supersedes PR #646)
- gemini extensions install --consent: the security prompt hung every
  piped/non-interactive install forever (#676, part of PR #664)
- OpenClaw skill stamps the real PINNED_REF version instead of hardcoded
  1.0.0; new --no-always flag for load-on-demand installs (supersedes
  PR #720)
- shims scope NPM_CONFIG_ALLOW_GIT=all to the npx call — npm >=12
  defaults allow-git to none and EALLOWGITs github: installs (#698)
- .codex/config.toml ships hooks + codex_hooks keys so auto-activation
  works on both sides of the codex-cli rename (#617)
- caveman-help card shows the Windows config path (%APPDATA%) (#723)
- caveman-parse.js added to HOOK_FILES, opencode payload (.cjs), and the
  regenerated checksums.sha256; manifest now matches shipped hook
  contents — release must bump PINNED_REF to a tag containing these files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
2026-07-21 02:01:27 +02:00
Julius BrusseeandClaude Fable 5 19f7b5a0c0 fix(#565): install.ps1 works when piped to iex
irm .../install.ps1 | iex crashed with "Cannot bind argument to
parameter 'Path' because it is null": under iex there is no script
file, so $MyInvocation.MyCommand.Path is $null and Split-Path threw.
The top-level param() block also can't receive arguments through a
pipe.

- wrap all logic in Install-Caveman, invoked at the bottom with the
  automatic $args (populated for file runs, empty under iex)
- replace $MyInvocation.MyCommand.Path with $PSCommandPath, guarded —
  pipe installs skip the local-clone branch and go straight to npx
- static regression tests in tests/installer/ps1-pipe.test.mjs (CI has
  no pwsh, so pin the pipe-safety contract textually)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
2026-07-02 15:13:19 +02:00
Julius BrusseeandClaude Fable 5 5f079ab7d8 fix(#571): ship commands/*.md so Claude Code discovers /caveman slash commands
Claude Code only scans commands/*.md (YAML frontmatter) for plugin slash
commands — the commands/*.toml files are the Gemini extension format and
are ignored, so /caveman, /caveman-commit, /caveman-review,
/caveman-stats and /caveman-init all returned Unknown command after a
plugin install.

- add commands/{caveman,caveman-commit,caveman-review,caveman-stats,
  caveman-init}.md mirroring the toml prompts ({{args}} -> $ARGUMENTS)
- caveman-stats.md body still matches the mode-tracker intercept regex
  so the hook injects real numbers, model computes nothing
- caveman-init.md uses the standalone-fallback body from #603
- keep every .toml — Gemini CLI extensions only read TOML
- pin the two-format contract + hook-regex + no-{{args}}-leak in
  tests/installer/slash-commands.test.mjs

The namespaced-command side of #599 (/caveman:caveman-commit etc.) is
already handled by the mode-tracker (both bare and namespaced forms,
one-shot restore) — this closes the discovery half.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
2026-07-02 15:11:52 +02:00
Julius Brussee cdb0d0c613 Merge PR #604: fix(install): treat spawn ENOENT as failure, not success (fixes #592)
# Conflicts:
#	tests/installer/e2e.freshinstall.test.mjs
2026-07-02 14:54:36 +02:00
Julius Brussee 9663344b79 Merge PR #607: fix(settings): string-aware trailing-comma removal in JSONC reader (fixes #595) 2026-07-02 14:52:58 +02:00
Julius Brussee d900443b84 Merge PR #605: fix(uninstall): match managed hook basenames, not substring (fixes #593) 2026-07-02 14:52:58 +02:00
Julius Brussee 5b875b8e2e Merge PR #609: fix(openclaw): stray SOUL.md marker no longer chains into data loss (fixes #596)
# Conflicts:
#	tests/installer/e2e.freshinstall.test.mjs
2026-07-02 14:52:49 +02:00
Julius Brussee 79a14ee79f Merge PR #610: fix(opencode): --force migrates legacy AGENTS.md, never wipes it (fixes #594) 2026-07-02 14:51:43 +02:00
AmirF194 516f917619 fix(opencode): --force migrates legacy AGENTS.md, never wipes it
--force on an AGENTS.md containing the legacy un-fenced sentinel
replaced the entire file with the fenced block — destroying all
user-authored content around the legacy block. The installer's own
hint told users with mixed files to run exactly that.

Migrate instead: back up once to AGENTS.md.bak, remove the legacy
block (exact match of the current rule body when possible, otherwise
cut from the sentinel's paragraph start — the legacy path appended the
block, so user content precedes it), and write the fenced block after
the preserved user content.

Fixes #594
2026-07-01 23:52:36 -06:00
AmirF194 ae02d2a560 fix(openclaw): stray SOUL.md marker no longer chains into data loss
A truncated/stray marker (interrupted write, partial user edit) chained
into deleting user content: appendBootstrapToSoul saw 'no complete
block' and appended a second one; stripBootstrapFromSoul then cut from
the FIRST begin to the FIRST end — spanning everything between the
stray marker and the appended block. Reported reproduction ended with
the whole SOUL.md deleted.

Replace the single-span cut with a scan that pairs each begin with the
nearest end before the next begin; unpaired markers are removed as just
the marker text, never as a span. Append now detects damaged markers
(orphans, duplicates), strips them safely, and writes one clean block.

Fixes #596
2026-07-01 23:50:28 -06:00
AmirF194 704a460ef8 fix(settings): string-aware trailing-comma removal in JSONC reader
The trailing-comma sweep ran a global regex over the whole
comment-stripped output, including string contents — a JSONC
settings.json with a value containing ',}' or ',]' (shell brace
expansion, inline JSON in hook args) was silently corrupted on read
and persisted corrupted on the next write.

Replace the regex with a scan that tracks string state (same approach
as the comment stripper above it) and only drops commas outside
strings.

Fixes #595
2026-07-01 23:44:57 -06:00
AmirF194 70ea40dced fix(uninstall): match managed hook basenames, not substring
removeCavemanHooks stripped any settings.json hook whose command
contained the substring 'caveman' anywhere — a user-authored hook like
'node ~/Projects/caveman-notes/my-hook.js' was silently deleted by
--uninstall.

Match tokens against MANAGED_HOOK_BASENAMES by exact basename instead
(win32.basename so Windows-written configs match anywhere), the same
pattern pruneOrphanedManagedHooks already uses. Hoist the tokenizer to
module scope and reuse it in the prune pass. Add caveman-statusline.ps1
to the managed set so the Windows statusline wiring is covered by
removal and orphan-pruning too.

Fixes #593
2026-07-01 23:34:41 -06:00
AmirF194 959b943ad4 fix(install): treat spawn ENOENT as failure, not success
spawnSync reports a missing binary as { status: null, error }, and the
(r.status || 0) === 0 checks coerced that null to success. On a machine
without the claude CLI, --only claude printed 'installed: claude',
skipped the standalone-hook fallback (which works offline), and left
nothing installed. Same pattern at 8 sites: claude, gemini, npx-skills
providers, mcp-shrink, runInit (both paths), uninstall, skills-auto.

Route every spawn result through spawnOk() (!r.error && r.status === 0)
and warn when the claude CLI itself could not be spawned. Regression
test runs the installer with an empty PATH and asserts failure is
reported and standalone hooks get wired.

Fixes #592
2026-07-01 23:31:35 -06:00
Julius BrusseeandClaude Opus 4.8 e8139f86e3 fix(#524): make Hermes install/uninstall symmetric + verify against real Hermes
Adversarial review against a live Hermes Agent install (v0.11.0) found the
forward install was correct (skills land in the real ~/.hermes/skills/
productivity/<skill>/ layout, all 7 load as 'enabled' via 'hermes skills
list' — verified empirically, no version: field required), but uninstall had
NO Hermes handling: --uninstall silently orphaned all 7 skill folders forever.

- add Hermes block to uninstall() honoring HERMES_HOME (mirrors opencode/openclaw)
- tests/installer/hermes.test.mjs: install lands 7 skills, uninstall removes
  them (regression guard for the asymmetry), dry-run uninstall is a no-op
- INSTALL.md: add Hermes Agent row to the per-agent install table (CLAUDE.md
  mandates the install table stay complete)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:25:44 +02:00
Julius Brussee c1ac0664c9 Merge PR #505: register /caveman-stats slash command (#470) 2026-06-14 22:01:18 +02:00
Julius BrusseeandClaude Opus 4.8 ddf212173a fix(opencode): plugin now loads in compiled Bun runtime
Smoke-tested against real opencode 1.4.0 (the missing verification
flagged in 22f75e3) — the plugin never loaded: opencode runs plugins
inside a compiled Bun binary where require() of on-disk files is
rejected ('require() async module is unsupported') and await import()
of a CJS file returns an empty namespace. caveman-config.cjs is now
evaluated as CommonJS by hand (readFileSync + Function wrapper with a
createRequire shim — built-ins still resolve fine).

Three more real-runtime gaps found and fixed in the same pass:
- session-init flag write now also happens at plugin factory time; in
  one-shot 'opencode run' the first session.created publishes before
  plugin event dispatch is wired, so the event handler alone missed it
- the TUI expands '/caveman <level>' into the command template before
  chat.message fires; the parser now recovers the level from the
  expanded 'Activate caveman mode: <level>' text
- the non-interactive run path wraps messages in literal quotes
  ('"/caveman ultra"'); the parser unwraps symmetric quotes

Verified end-to-end against opencode 1.4.0: plugin loads clean,
session-init writes the flag, /caveman ultra flips it, 'stop caveman'
deletes it, and the reinforcement line shows up in the outgoing LLM
system prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:57:15 +02:00
Ousama Ben YounesandClaude Opus 4.7 b5e8cdaf1f fix(commands): add caveman-stats.toml so Claude Code registers /caveman-stats
Without commands/caveman-stats.toml on disk Claude Code rejects
/caveman-stats as 'Unknown command' before the UserPromptSubmit
hook in src/hooks/caveman-mode-tracker.js can intercept it. README
and INSTALL.md both advertise the command; this brings the slash
command registry in line with the docs.

Fixes #470

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-10 10:04:13 +00:00
Julius BrusseeandClaude Opus 4.8 22f75e3de6 fix(opencode): real lifecycle hooks + ship caveman-compress command
Folds in #419 (replace the non-existent session.created/tui.prompt.append hooks with real opencode hooks: event dispatcher for session.created, chat.message for mode parsing, experimental.chat.system.transform for reinforcement; fixes #418/#421), #398 (ship the missing caveman-compress.md command + un-ignore it; fixes #426/#451/#464), and #376 plugin-side (drop %APPDATA% branch). Smoke test rewritten for the new hook shapes. NOTE: not smoke-tested against a real opencode runtime here — verify before release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:05:24 +02:00
Julius BrusseeandClaude Opus 4.8 f0dd780305 fix(install): Windows/Copilot/skills fixes, hook-wiring + MCP-shrink, security hardening
Installer fixes: #414 (rename PS1 $Args->$InstallerArgs), #437 (detect Copilot via extension dirs, fixes #336), #395 (--skill '*' instead of --all so -a <agent> is honored, fixes #389), #472 (prune orphaned managed hooks from settings.json, fixes #471), #393 (don't double-wire hooks when the plugin manifest already does, fixes #392), #380 (MCP-shrink off by default, requires an upstream, fixes #474), #376 install-side (opencode uses ~/.config/opencode, drop %APPDATA%), #443 (strip tools: from cavecrew agent copies for opencode, #386), #434 (existsSync guard on command copy), #396 (doc: discover profile slugs via --list).

Security hardening: #261 (pin remote fetch to release tag PINNED_REF=v1.8.2, not moving main) and #262 (SHA-256-verify downloaded hook files against src/hooks/checksums.sha256 before they execute; abort on mismatch). #260 (inspect-before-run note). NOTE: enforcement activates fully once a release tag shipping checksums.sha256 is published and PINNED_REF is bumped; v1.8.2 predates the manifest so downloads there warn-and-proceed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:05:24 +02:00
Julius BrusseeandClaude Opus 4.7 754795ada4 fix(install): unbreak curl|bash one-liner (regression in v1.8.0)
Two bugs at the curl|bash entry point made the headline install command
fail immediately:

1. install.sh used `${BASH_SOURCE[0]}` under `set -u`. That variable is
   unset when bash is invoked from stdin (curl | bash), tripping the
   nounset trap before we ever reached the npx fallback.
2. install.sh + install.ps1 passed `--` between npx and the package args.
   On modern npm, npx forwards the literal `--` to bin/install.js, which
   parseArgs rejected as an unknown flag.

Fix:
- install.sh: default BASH_SOURCE[0] to empty so the curl-pipe path falls
  through cleanly under set -u.
- install.sh + install.ps1: drop the `--` separator. npm 7+ npx already
  forwards trailing args correctly.
- bin/install.js parseArgs: accept a bare `--` as a no-op (POSIX
  end-of-options marker) so future shim drift can't re-break this.
- New regression test asserts `--` is accepted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:04:44 +02:00
Julius Brussee e843518438 Improve installer idempotency and opencode fencing
Refactor and harden the unified Node installer and related docs. Major changes:

- bin/install.js: validate --only ids, expand ~ for --config-dir, make Claude/install flows async (await hooks/init), add many CLI flags (--no-mcp-shrink, --with-hooks/--no-hooks, --skip-skills, --config-dir docs), preserve original backups once, avoid overwriting plugin/command files unless --force, and add opencode AGENTS.md fenced begin/end markers so installs/uninstalls can append/strip cleanly. Also add opencode idempotency probes for claude/gemini uninstall paths and better handling of opencode plugin payload.
- bin/lib/settings.js: safer removeCavemanHooks that validates shapes before mutating hooks.
- runInit/installHooks/downloadTo calls made async and awaited; runInit returns promise now.
- Docs and README/INSTALL/CONTRIBUTING/CLAUDE.md/src/hooks/README.md: update user-facing text to match new flags/behaviour, standardize use of $CLAUDE_CONFIG_DIR, clarify --with-init semantics, and note opencode/openclaw handling. Change command name usages from `/caveman:compress` to `/caveman-compress`.
- opencode: write fenced caveman block to AGENTS.md and handle legacy unfenced blocks; tests updated to expect fence markers.

These changes improve idempotency, safer upgrades/uninstalls, clearer UX around per-repo vs per-user init, and make the opencode rule block removable without destroying user content.
2026-05-10 15:50:33 +02:00
Julius BrusseeandClaude Opus 4.7 7b2bed2d0b feat(openclaw): add OpenClaw as a first-class agent target
OpenClaw is a self-hosted gateway that orchestrates multiple agents and
loads workspace skills on-demand. To make caveman always-on through it we
write a spec-correct skill folder plus a marker-fenced bootstrap block in
SOUL.md (which OpenClaw auto-injects every turn). Both writes are
idempotent and reachable from `bin/install.js --only openclaw` and
`caveman-init.js --only openclaw`. Side-effect: `--only <id>` now bypasses
the detect-match guard so explicit opt-in works for any provider whose
preconditions can't be probed (e.g. custom OPENCLAW_WORKSPACE paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:18:00 +02:00
Julius BrusseeandClaude Opus 4.7 8b8068d8ca feat(opencode): native plugin replaces npx-skills fallback
Replaces opencode's Tier-4 npx-skills entry with a native in-repo plugin
that mirrors the Claude Code hook architecture (session.created +
tui.prompt.append) using opencode's lifecycle hook system. Reaches Tier-1
parity minus the statusline (opencode TUI exposes no plugin-writable
badge). Skill files drop in unchanged — opencode reads SKILL.md natively.

Plugin reuses src/hooks/caveman-config.js for the symlink-safe flag-write
helpers via createRequire (renamed .cjs post-install to coexist with the
plugin dir's "type":"module"). AGENTS.md provides a Tier-3 always-on
fallback if the plugin runtime breaks.

5 new tests cover fresh install, idempotency, JSONC tolerance of
pre-existing opencode.json, uninstall, and a plugin smoke test that fires
synthetic tui.prompt.append events. All 38 installer tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:08:17 +02:00
Julius Brussee 4f2314ae43 feat: land bin/install.js + JSONC settings helper + installer tests
Brings the long-stashed Node installer onto main. install.sh and install.ps1
shrink to thin shims (~50 lines each) that delegate to bin/install.js, fixing
the cross-platform drift that caused #249-class quoting bugs.

- bin/install.js (850 lines) — unified PROVIDERS-driven installer
- bin/lib/settings.js (221 lines) — JSONC parser + hook validator
  (validateHookFields prevents single bad hook from poisoning settings.json)
- tests/installer/{unit.argv,unit.settings,e2e.dryrun}.test.mjs — npm test
  now actually runs four real tests (was silently passing 0)
- .agents/skills/cavecrew, .junie/, .kiro/, .roo/ — per-agent skill mirrors
- skills-lock.json — vercel-labs/skills slug pinning
- install.{sh,ps1}.legacy escape hatch dropped (git history is the fallback)
- Minor cavecrew agent description refinements

Closes the gap between docs (already merged) describing bin/install.js and
the actual implementation.
2026-05-10 14:11:17 +02:00
Julius BrusseeandClaude Opus 4.7 a2dca8194d test(installer): add fresh-install e2e test against temp CLAUDE_CONFIG_DIR
Real install harness — writes hooks, merges settings.json, asserts on-disk
state. Catches regressions a dry-run can't see (missing hooks, malformed
settings, broken statusline). Five cases:

  - Fresh install populates hooks dir + wires SessionStart/UserPromptSubmit
  - Idempotent install does not duplicate hook entries
  - Uninstall strips caveman hooks, preserves user-authored ones
  - Install tolerates JSONC settings.json (#249 regression guard)
  - Lib-level addCommandHook idempotency (always runs, no claude CLI)

Tests requiring `claude` on PATH skip cleanly with a clear reason. Uninstall
test strips claude/gemini from PATH so the user's real plugin/extension
state is never touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:09:55 +02:00