Skip to content

ROI follow-ups + bigger bets: portable shim, validate hang, security gate, doctor D9, SKILL.md rebuild, savings docs, launch tests - #7

Merged
NagyVikt merged 11 commits into
cue/week1-roi-fixesfrom
cue/roi-followups
Jun 2, 2026
Merged

ROI follow-ups + bigger bets: portable shim, validate hang, security gate, doctor D9, SKILL.md rebuild, savings docs, launch tests#7
NagyVikt merged 11 commits into
cue/week1-roi-fixesfrom
cue/roi-followups

Conversation

@NagyVikt

@NagyVikt NagyVikt commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

ROI follow-ups + bigger bets (stacked on #6)

Base: cue/week1-roi-fixes (PR #6). This stacks on the week1 fixes — review/merge that first, or merge both together. The 3 discovered follow-ups + the 📈 bigger bets from the audit.

What's here

Commit Item What
0b7a70d follow-up Portable shimcue shell install baked an absolute ~/Documents/cue/bin/cue path that doesn't exist for npm-global users; now prefers bare cue on PATH, else a real abspath.
ecca999+8b988da+795e2d8 bigger bet Savings numbers reconciled to real cue cost --compare output (~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.
a9cd46f follow-up cue validate --all hang → offline by default (was ~2000 serial network npx spawns). >60s → ~4.5s. --online opts in; npxFetch gets a 45s timeout. Real errors still reported.
e1322b8+e66fcb2 bigger bet Security-scanner gatescanSkill is now enforced on freshly-fetched remote skills with the (attacker-controllable) category suppressions OFF, scans fenced content (no code-block bypass), reports scanned:false rather than failing open, and gates cue discover install, cue init, and cue skills add. Blocks SEC1-3 unless --allow-unsafe.
fdde76e+81b816d bigger bet cue doctor D9 activation check (shim installed / real binary resolves / PATH order). Shim-missing is a warning (doesn't break exit codes); --fix installs the shim.
3430f8d bigger bet Tests for the launch exec handoff (--dry-run env assembly + recursion guard) + an install.sh CI smoke step.
f128ebf bigger bet Materializer rebuilds on SKILL.md change, not just profile.yaml — "edit a skill → relaunch → see the change" now works.

Verification

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 add path, plus a stale docs lede) — all fixed in the harden(...) commits and proven by tests (a fenced grep api_key ~/.aws/credentials + curl POST evil is now caught). Re-verify: zero open blockers/majors. Locally: typecheck clean, 671 unit tests pass, validate --all 4.5s, cue security output unchanged.

⚠️ Merge conflicts expected (fleet is editing these)

  • runtime-materializer.tsisRuntimeStale is edited here and concurrently elsewhere. Conflict likely in that function.
  • ci.yml — overlaps the week1 + concurrent CI edits.
  • Launch tests were deliberately put in a new file (launch-handoff.e2e.test.ts) to avoid conflicting with concurrent edits to launch.e2e.test.ts.

Out of scope (flagged, not silently bundled)

  • cue marketplace install-skill and cue upgrade --apply also fetch+register remote skills and are not yet gated — follow-up.
  • eval.ts */*-glob bug (eval understates savings vs full) — left separate to avoid churning eval test fixtures; docs now steer to cue cost --compare instead.
  • D9 is claude-only (no findRealCodexBin exists yet).

🤖 Generated with Claude Code

NagyVikt and others added 11 commits June 1, 2026 22:36
`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
NagyVikt merged commit e8248ed into cue/week1-roi-fixes Jun 2, 2026
2 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant