cue-ai 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +4 -3
  2. package/README.md +154 -394
  3. package/bin/cue-learnings +30 -4
  4. package/bin/cue-review-progress +0 -0
  5. package/bin/cue-review-watch +0 -0
  6. package/dist/cue.js +4328 -3108
  7. package/package.json +1 -1
  8. package/plugins/cue/commands/cue-switch.md +1 -1
  9. package/plugins/cue/commands/cue.md +1 -1
  10. package/profiles/backend/profile.yaml +4 -0
  11. package/profiles/browser/profile.yaml +4 -0
  12. package/profiles/career/profile.yaml +2 -13
  13. package/profiles/commerce/profile.yaml +0 -2
  14. package/profiles/coolify/profile.yaml +0 -1
  15. package/profiles/core/profile.yaml +78 -11
  16. package/profiles/dash-merge-test/profile.yaml +6 -1
  17. package/profiles/designer/profile.yaml +9 -1
  18. package/profiles/dropshipping/profile.yaml +69 -0
  19. package/profiles/frontend/profile.yaml +4 -0
  20. package/profiles/google-ads/profile.yaml +34 -0
  21. package/profiles/google-analytics/profile.yaml +34 -0
  22. package/profiles/google-drive/profile.yaml +34 -0
  23. package/profiles/gstack/profile.yaml +117 -29
  24. package/profiles/marketing/profile.yaml +0 -1
  25. package/profiles/media/README.md +70 -0
  26. package/profiles/media/profile.yaml +104 -0
  27. package/profiles/nano-banana/profile.yaml +52 -0
  28. package/profiles/ops/profile.yaml +1 -2
  29. package/profiles/secops/profile.yaml +3 -0
  30. package/profiles/skill-writer/profile.yaml +15 -0
  31. package/profiles/video/profile.yaml +3 -0
  32. package/profiles/web-frontend-base/profile.yaml +6 -0
  33. package/profiles/webshop/profile.yaml +0 -1
  34. package/profiles/webshop-google/profile.yaml +1 -0
  35. package/profiles/x-growth-bot/profile.yaml +2 -0
  36. package/resources/icons/generate-icons.py +2 -128
  37. package/resources/mcps/configs/claude.sanitized.json +88 -20
  38. package/resources/mcps/configs/claude_runtime.sanitized.json +40 -1
  39. package/resources/mcps/configs/codex.sanitized.json +29 -0
  40. package/resources/skills/skills/career/job-hunter/LICENSE +21 -0
  41. package/resources/skills/skills/career/job-hunter/README.md +323 -0
  42. package/resources/skills/skills/career/job-hunter/SKILL.md +91 -0
  43. package/resources/skills/skills/career/job-hunter/agents/README.md +96 -0
  44. package/resources/skills/skills/career/job-hunter/agents/apply-assessment-prep.md +195 -0
  45. package/resources/skills/skills/career/job-hunter/agents/apply-ats-scan.md +155 -0
  46. package/resources/skills/skills/career/job-hunter/agents/apply-bias-audit.md +224 -0
  47. package/resources/skills/skills/career/job-hunter/agents/apply-cover-letter.md +69 -0
  48. package/resources/skills/skills/career/job-hunter/agents/apply-decode-jd.md +117 -0
  49. package/resources/skills/skills/career/job-hunter/agents/apply-fit-score.md +183 -0
  50. package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-audit.md +74 -0
  51. package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-scrape.md +255 -0
  52. package/resources/skills/skills/career/job-hunter/agents/apply-portfolio-brief.md +123 -0
  53. package/resources/skills/skills/career/job-hunter/agents/apply-reality-check.md +164 -0
  54. package/resources/skills/skills/career/job-hunter/agents/apply-reference-prep.md +150 -0
  55. package/resources/skills/skills/career/job-hunter/agents/apply-rejection-analysis.md +172 -0
  56. package/resources/skills/skills/career/job-hunter/agents/apply-resume.md +70 -0
  57. package/resources/skills/skills/career/job-hunter/agents/apply-skills-gap-filler.md +109 -0
  58. package/resources/skills/skills/career/job-hunter/agents/career-internal.md +94 -0
  59. package/resources/skills/skills/career/job-hunter/agents/career-linkedin-content.md +173 -0
  60. package/resources/skills/skills/career/job-hunter/agents/career-linkedin-scanner.md +262 -0
  61. package/resources/skills/skills/career/job-hunter/agents/career-network-message.md +108 -0
  62. package/resources/skills/skills/career/job-hunter/agents/career-promote.md +102 -0
  63. package/resources/skills/skills/career/job-hunter/agents/career-review.md +71 -0
  64. package/resources/skills/skills/career/job-hunter/agents/interview-debrief.md +117 -0
  65. package/resources/skills/skills/career/job-hunter/agents/interview-mock.md +171 -0
  66. package/resources/skills/skills/career/job-hunter/agents/interview-panel-decoder.md +152 -0
  67. package/resources/skills/skills/career/job-hunter/agents/interview-prep.md +184 -0
  68. package/resources/skills/skills/career/job-hunter/agents/interview-question-bank.md +133 -0
  69. package/resources/skills/skills/career/job-hunter/agents/interview-research.md +148 -0
  70. package/resources/skills/skills/career/job-hunter/agents/offer-compare.md +117 -0
  71. package/resources/skills/skills/career/job-hunter/agents/offer-counteroffer.md +144 -0
  72. package/resources/skills/skills/career/job-hunter/agents/offer-deadline-manager.md +148 -0
  73. package/resources/skills/skills/career/job-hunter/agents/offer-negotiate.md +126 -0
  74. package/resources/skills/skills/career/job-hunter/agents/offer-schedule.md +99 -0
  75. package/resources/skills/skills/career/job-hunter/agents/offer-thankyou.md +80 -0
  76. package/resources/skills/skills/career/job-hunter/agents/search-company-research.md +146 -0
  77. package/resources/skills/skills/career/job-hunter/agents/search-follow-up.md +129 -0
  78. package/resources/skills/skills/career/job-hunter/agents/search-ghost-job-detector.md +152 -0
  79. package/resources/skills/skills/career/job-hunter/agents/search-inbox-scan.md +193 -0
  80. package/resources/skills/skills/career/job-hunter/agents/search-interview-scorecard.md +164 -0
  81. package/resources/skills/skills/career/job-hunter/agents/search-jobs.md +149 -0
  82. package/resources/skills/skills/career/job-hunter/agents/search-momentum-check.md +194 -0
  83. package/resources/skills/skills/career/job-hunter/agents/search-outreach.md +85 -0
  84. package/resources/skills/skills/career/job-hunter/agents/search-referral-finder.md +124 -0
  85. package/resources/skills/skills/career/job-hunter/agents/search-salary.md +96 -0
  86. package/resources/skills/skills/career/job-hunter/agents/search-send-email.md +109 -0
  87. package/resources/skills/skills/career/job-hunter/agents/search-tracker-update.md +127 -0
  88. package/resources/skills/skills/career/job-hunter/inputs/README.md +26 -0
  89. package/resources/skills/skills/career/job-hunter/inputs/apply-linkedin-url.txt +8 -0
  90. package/resources/skills/skills/career/job-hunter/inputs/interview-context.md +24 -0
  91. package/resources/skills/skills/career/job-hunter/inputs/job-description.md +20 -0
  92. package/resources/skills/skills/career/job-hunter/inputs/job-search-criteria.md +36 -0
  93. package/resources/skills/skills/career/job-hunter/inputs/my-linkedin.md +24 -0
  94. package/resources/skills/skills/career/job-hunter/inputs/my-resume.md +28 -0
  95. package/resources/skills/skills/career/job-hunter/inputs/search-outreach-target.md +24 -0
  96. package/resources/skills/skills/career/job-hunter/rules/README.md +37 -0
  97. package/resources/skills/skills/career/job-hunter/rules/writing-rules.md +81 -0
  98. package/resources/skills/skills/design/banana/SKILL.md +375 -0
  99. package/resources/skills/skills/design/banana/references/cost-tracking.md +47 -0
  100. package/resources/skills/skills/design/banana/references/gemini-models.md +236 -0
  101. package/resources/skills/skills/design/banana/references/mcp-tools.md +145 -0
  102. package/resources/skills/skills/design/banana/references/post-processing.md +192 -0
  103. package/resources/skills/skills/design/banana/references/presets.md +69 -0
  104. package/resources/skills/skills/design/banana/references/prompt-engineering.md +481 -0
  105. package/resources/skills/skills/design/banana/scripts/batch.py +97 -0
  106. package/resources/skills/skills/design/banana/scripts/cost_tracker.py +191 -0
  107. package/resources/skills/skills/design/banana/scripts/edit.py +159 -0
  108. package/resources/skills/skills/design/banana/scripts/generate.py +168 -0
  109. package/resources/skills/skills/design/banana/scripts/presets.py +154 -0
  110. package/resources/skills/skills/design/banana/scripts/setup_mcp.py +151 -0
  111. package/resources/skills/skills/design/banana/scripts/validate_setup.py +133 -0
  112. package/resources/skills/skills/gstack/ship/SKILL.md +13 -0
  113. package/resources/skills/skills/media/3d-logo-animation/SKILL.md +59 -0
  114. package/resources/skills/skills/media/action-figure-generator/SKILL.md +48 -0
  115. package/resources/skills/skills/media/ad-creative/SKILL.md +79 -0
  116. package/resources/skills/skills/media/ai-clipping/SKILL.md +194 -0
  117. package/resources/skills/skills/media/ai-clipping/scripts/run-ai-clipping.sh +200 -0
  118. package/resources/skills/skills/media/ai-fight-scene/SKILL.md +132 -0
  119. package/resources/skills/skills/media/amazon-product-listing/SKILL.md +68 -0
  120. package/resources/skills/skills/media/animal-video-generator/SKILL.md +59 -0
  121. package/resources/skills/skills/media/award-ceremony-video/SKILL.md +87 -0
  122. package/resources/skills/skills/media/blog-header/SKILL.md +61 -0
  123. package/resources/skills/skills/media/brand-kit/SKILL.md +72 -0
  124. package/resources/skills/skills/media/brochures/SKILL.md +65 -0
  125. package/resources/skills/skills/media/cartoon-dance-animation/SKILL.md +62 -0
  126. package/resources/skills/skills/media/character-story-video/SKILL.md +84 -0
  127. package/resources/skills/skills/media/chibi-collage-effect/SKILL.md +63 -0
  128. package/resources/skills/skills/media/cinema-director/SKILL.md +93 -0
  129. package/resources/skills/skills/media/cinema-director/scripts/generate-film.sh +78 -0
  130. package/resources/skills/skills/media/color-analysis-board/SKILL.md +71 -0
  131. package/resources/skills/skills/media/core-edit/SKILL.md +48 -0
  132. package/resources/skills/skills/media/core-edit/edit-image.sh +54 -0
  133. package/resources/skills/skills/media/core-edit/enhance-image.sh +191 -0
  134. package/resources/skills/skills/media/core-edit/lipsync.sh +144 -0
  135. package/resources/skills/skills/media/core-edit/video-effects.sh +193 -0
  136. package/resources/skills/skills/media/core-media/SKILL.md +49 -0
  137. package/resources/skills/skills/media/core-media/create-music.sh +169 -0
  138. package/resources/skills/skills/media/core-media/generate-image.sh +161 -0
  139. package/resources/skills/skills/media/core-media/generate-video.sh +137 -0
  140. package/resources/skills/skills/media/core-media/image-to-video.sh +228 -0
  141. package/resources/skills/skills/media/core-media/schema_data.json +18708 -0
  142. package/resources/skills/skills/media/core-media/upload.sh +41 -0
  143. package/resources/skills/skills/media/core-platform/SKILL.md +41 -0
  144. package/resources/skills/skills/media/core-platform/check-result.sh +37 -0
  145. package/resources/skills/skills/media/core-platform/setup.sh +31 -0
  146. package/resources/skills/skills/media/couple-grid-creator/SKILL.md +47 -0
  147. package/resources/skills/skills/media/design-guide/SKILL.md +73 -0
  148. package/resources/skills/skills/media/drone-style-video/SKILL.md +61 -0
  149. package/resources/skills/skills/media/fashion-try-on/SKILL.md +61 -0
  150. package/resources/skills/skills/media/floor-plan-rendering/SKILL.md +56 -0
  151. package/resources/skills/skills/media/freeze-effect-video/SKILL.md +100 -0
  152. package/resources/skills/skills/media/giant-product-showcase/SKILL.md +61 -0
  153. package/resources/skills/skills/media/instagram-post/SKILL.md +58 -0
  154. package/resources/skills/skills/media/interior-design/SKILL.md +61 -0
  155. package/resources/skills/skills/media/interior-design-visualizer/SKILL.md +57 -0
  156. package/resources/skills/skills/media/jewelry-product-video/SKILL.md +61 -0
  157. package/resources/skills/skills/media/kdenlive/SKILL.md +106 -0
  158. package/resources/skills/skills/media/kdenlive/scripts/assemble.sh +57 -0
  159. package/resources/skills/skills/media/kdenlive/scripts/common.sh +30 -0
  160. package/resources/skills/skills/media/kdenlive/scripts/inspect.sh +19 -0
  161. package/resources/skills/skills/media/kdenlive/scripts/reframe.sh +22 -0
  162. package/resources/skills/skills/media/kdenlive/scripts/render.sh +16 -0
  163. package/resources/skills/skills/media/kdenlive/scripts/title-card.sh +25 -0
  164. package/resources/skills/skills/media/keyboard-art-maker/SKILL.md +44 -0
  165. package/resources/skills/skills/media/logo-branding/SKILL.md +70 -0
  166. package/resources/skills/skills/media/logo-creator/SKILL.md +80 -0
  167. package/resources/skills/skills/media/logo-creator/scripts/create-logo.sh +38 -0
  168. package/resources/skills/skills/media/logo-generator/SKILL.md +56 -0
  169. package/resources/skills/skills/media/multi-angle-reshoot/SKILL.md +70 -0
  170. package/resources/skills/skills/media/multi-angle-shots/SKILL.md +73 -0
  171. package/resources/skills/skills/media/music-video/SKILL.md +61 -0
  172. package/resources/skills/skills/media/nano-banana/SKILL.md +80 -0
  173. package/resources/skills/skills/media/nano-banana/scripts/generate-nano-art.sh +54 -0
  174. package/resources/skills/skills/media/one-shot-video/SKILL.md +56 -0
  175. package/resources/skills/skills/media/photo-pack-generator/SKILL.md +205 -0
  176. package/resources/skills/skills/media/photo-pack-generator/scripts/generate-pack.sh +241 -0
  177. package/resources/skills/skills/media/product-ad-cinematic/SKILL.md +78 -0
  178. package/resources/skills/skills/media/product-campaign/SKILL.md +76 -0
  179. package/resources/skills/skills/media/product-showcase-video/SKILL.md +60 -0
  180. package/resources/skills/skills/media/product-video-ad-maker/SKILL.md +59 -0
  181. package/resources/skills/skills/media/rednote-cover/SKILL.md +57 -0
  182. package/resources/skills/skills/media/seedance-2/SKILL.md +632 -0
  183. package/resources/skills/skills/media/seedance-2/scripts/generate-seedance.sh +701 -0
  184. package/resources/skills/skills/media/selfie-with-celebrities/SKILL.md +64 -0
  185. package/resources/skills/skills/media/social-media-video/SKILL.md +277 -0
  186. package/resources/skills/skills/media/social-media-video/scripts/run-social-video.sh +316 -0
  187. package/resources/skills/skills/media/social-pack/SKILL.md +58 -0
  188. package/resources/skills/skills/media/storyboard/SKILL.md +57 -0
  189. package/resources/skills/skills/media/storyboard-to-cooking-video/SKILL.md +143 -0
  190. package/resources/skills/skills/media/talking-baby-video/SKILL.md +57 -0
  191. package/resources/skills/skills/media/ugc-ads-workflow/SKILL.md +70 -0
  192. package/resources/skills/skills/media/ugc-lifestyle-try-on/SKILL.md +65 -0
  193. package/resources/skills/skills/media/ugc-video-factory/SKILL.md +134 -0
  194. package/resources/skills/skills/media/ui-design/SKILL.md +81 -0
  195. package/resources/skills/skills/media/ui-design/scripts/generate-mockup.sh +49 -0
  196. package/resources/skills/skills/media/url-to-design/SKILL.md +61 -0
  197. package/resources/skills/skills/media/workflow/SKILL.md +197 -0
  198. package/resources/skills/skills/media/workflow/scripts/discover-workflow.sh +18 -0
  199. package/resources/skills/skills/media/workflow/scripts/generate-workflow.sh +33 -0
  200. package/resources/skills/skills/media/workflow/scripts/interactive-run.sh +16 -0
  201. package/resources/skills/skills/media/workflow/scripts/list-workflows.sh +20 -0
  202. package/resources/skills/skills/media/workflow/scripts/run-workflow.sh +34 -0
  203. package/resources/skills/skills/media/youtube-shorts/SKILL.md +173 -0
  204. package/resources/skills/skills/media/youtube-shorts/scripts/run-youtube-shorts.sh +141 -0
  205. package/resources/skills/skills/media/youtube-thumbnail/SKILL.md +66 -0
  206. package/resources/skills/skills/meta/cue-developer/references/architecture.md +2 -2
  207. package/resources/skills/skills/meta/cue-usage/SKILL.md +1 -1
  208. package/resources/skills/skills/meta/profile-fit-monitor/SKILL.md +2 -2
  209. package/resources/skills/skills/meta/profile-optimizer/SKILL.md +1 -1
  210. package/resources/skills/skills/meta/profile-suggest/SKILL.md +7 -7
  211. package/resources/skills/skills/meta/profile-summon/SKILL.md +159 -0
  212. package/resources/skills/skills/meta/profile-summon/evals/evals.json +53 -0
  213. package/resources/skills/skills/meta/save-profile/SKILL.md +1 -1
  214. package/resources/skills/skills/meta/skill-reviewer/SKILL.md +3 -0
  215. package/resources/skills/skills/meta/skill-reviewer/references/tdd-for-skills.md +55 -0
  216. package/resources/skills/skills/research/find-skills/SKILL.md +1 -1
  217. package/resources/skills/skills/review/code-review-deep/SKILL.md +20 -0
  218. package/resources/skills/skills/security/trivy-scan/SKILL.md +139 -0
  219. package/resources/skills/skills/security/trivy-scan/scripts/ensure-trivy.sh +21 -0
  220. package/resources/skills/skills/tools/ccusage/SKILL.md +142 -0
  221. package/src/commands/_index.ts +8 -0
  222. package/src/commands/ai.ts +2 -2
  223. package/src/commands/auto-detect.test.ts +74 -0
  224. package/src/commands/auto-detect.ts +9 -7
  225. package/src/commands/cli.test.ts +20 -4
  226. package/src/commands/cli.ts +36 -20
  227. package/src/commands/create-profile.ts +2 -2
  228. package/src/commands/debug.ts +2 -2
  229. package/src/commands/discover.ts +14 -4
  230. package/src/commands/export-docker.ts +1 -1
  231. package/src/commands/features-batch1.test.ts +1 -1
  232. package/src/commands/gates.ts +1 -1
  233. package/src/commands/import-profile.ts +1 -1
  234. package/src/commands/init.ts +15 -11
  235. package/src/commands/install.test.ts +192 -0
  236. package/src/commands/install.ts +610 -0
  237. package/src/commands/launch-handoff.e2e.test.ts +33 -1
  238. package/src/commands/launch.e2e.test.ts +15 -10
  239. package/src/commands/launch.ts +73 -116
  240. package/src/commands/materialize.ts +2 -2
  241. package/src/commands/prune.ts +1 -1
  242. package/src/commands/security-audit.ts +1 -1
  243. package/src/commands/shell.ts +7 -7
  244. package/src/commands/skill-report.ts +1 -1
  245. package/src/commands/skills.ts +3 -3
  246. package/src/commands/snapshot.ts +2 -2
  247. package/src/commands/summon.test.ts +116 -0
  248. package/src/commands/summon.ts +338 -0
  249. package/src/commands/trigger-gaps.ts +1 -1
  250. package/src/commands/use.ts +47 -3
  251. package/src/commands/watch-live.ts +5 -5
  252. package/src/commands/watch.ts +8 -8
  253. package/src/index.ts +2 -0
  254. package/src/lib/active-sessions.test.ts +3 -3
  255. package/src/lib/active-sessions.ts +4 -4
  256. package/src/lib/auto-detect.test.ts +172 -8
  257. package/src/lib/auto-detect.ts +191 -136
  258. package/src/lib/codex-persona-parity.test.ts +58 -0
  259. package/src/lib/companion-detect.test.ts +43 -1
  260. package/src/lib/companion-detect.ts +35 -0
  261. package/src/lib/credentials-sync.test.ts +121 -1
  262. package/src/lib/credentials-sync.ts +95 -1
  263. package/src/lib/cwd-resolver.test.ts +8 -8
  264. package/src/lib/cwd-resolver.ts +2 -2
  265. package/src/lib/dashboard-merge.test.ts +9 -4
  266. package/src/lib/dashboard-server.ts +1 -1
  267. package/src/lib/picker.test.ts +1 -1
  268. package/src/lib/picker.ts +5 -5
  269. package/src/lib/profile-merge.test.ts +8 -0
  270. package/src/lib/profile-names.test.ts +3 -3
  271. package/src/lib/runtime-install.ts +166 -0
  272. package/src/lib/runtime-materializer.test.ts +137 -0
  273. package/src/lib/runtime-materializer.ts +105 -2
  274. package/src/lib/skill-router.test.ts +38 -0
  275. package/src/lib/skill-router.ts +65 -4
  276. package/profiles/eu-tender-research/README.md +0 -48
  277. package/profiles/eu-tender-research/logo.png +0 -0
  278. package/profiles/eu-tender-research/profile.yaml +0 -108
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: trivy-scan
3
+ description: |
4
+ Scan the repo for dependency CVEs, leaked secrets, and IaC/Dockerfile
5
+ misconfigs with Trivy, then hard-block HIGH/CRITICAL before a merge.
6
+ Use when the user says "trivy", "vuln scan", "scan dependencies",
7
+ "supply chain scan", "scan for CVEs", or "security scan before merge".
8
+ allowed-tools: Bash(Bash:*), Read, Grep, Glob, AskUserQuestion
9
+ category: security
10
+ tags: [security, supply-chain, vulnerability, gate, trivy]
11
+ triggers:
12
+ - trivy
13
+ - vuln scan
14
+ - scan dependencies
15
+ - supply chain scan
16
+ - scan before merge
17
+ ---
18
+
19
+ # /trivy-scan: supply-chain vulnerability gate
20
+
21
+ Trivy (Aqua Security) scans a repo for three classes of problem that
22
+ code review misses: known CVEs in your dependency lockfiles, secrets
23
+ committed to the tree, and misconfigured IaC/Dockerfiles. This skill is
24
+ the **single source of truth** for the pre-merge security gate. The
25
+ reviewer (`/code-review-deep`) and the ship gates (`/ship`, `/autoship`)
26
+ all invoke it. Run it standalone any time with "trivy" or "vuln scan".
27
+
28
+ ## Iron contract
29
+
30
+ 1. **A HIGH or CRITICAL finding blocks the merge.** No exceptions
31
+ without a logged waiver (see Rules). The gate is the safety, not a
32
+ prompt. Don't merge past a real finding because it's inconvenient.
33
+ 2. **Every reported finding cites the package + CVE + fixed version.**
34
+ "Trivy found stuff" is not a report. Quote the row.
35
+ 3. **Never auto-bump a dependency to silence a finding.** Surface the
36
+ fixed version; the user decides whether to upgrade now or waive.
37
+
38
+ ## Prerequisites
39
+
40
+ `trivy` CLI. Check and install if missing (canonical extracted version:
41
+ this skill's `scripts/ensure-trivy.sh`, use it in CI):
42
+
43
+ ```bash
44
+ command -v trivy >/dev/null || brew install trivy 2>/dev/null || sudo pacman -S --noconfirm trivy 2>/dev/null || curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.71.0/contrib/install.sh | sh -s -- -b "$HOME/.local/bin"
45
+ trivy --version
46
+ ```
47
+
48
+ Not in default `apt`/`dnf`, those need the Aqua repo. The script's
49
+ installer fallback is cross-distro. Recipe lives in
50
+ `resources/cli-recipes.json` under `trivy`.
51
+
52
+ ## The gate, one command
53
+
54
+ ```bash
55
+ trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 --no-progress --skip-dirs "**/node_modules" --skip-dirs "**/_cache" --skip-dirs "**/dist" --skip-dirs "**/.next" --skip-dirs "**/vendor" .
56
+ ```
57
+
58
+ - **Exit 0** → no HIGH/CRITICAL findings → gate **PASS**, merge may proceed.
59
+ - **Exit 1** → at least one HIGH/CRITICAL finding → gate **BLOCK**. Report
60
+ and stop. Do not merge.
61
+ - Any other exit (Trivy error, e.g. DB download failure) → treat as
62
+ **BLOCK** and report the error. A scan that didn't run is not a pass.
63
+
64
+ ## Steps
65
+
66
+ 1. **Ensure Trivy is installed** (Prerequisites). If it cannot be
67
+ installed (offline, no network approval), report that the gate could
68
+ not run and **block**. Do not silently skip.
69
+
70
+ 2. **Pick scope.** Default is the whole working tree (`trivy fs … .`),
71
+ which catches issues in unchanged files too, the right default for a
72
+ merge gate. For a fast diff-scoped pass on a huge repo, scan only the
73
+ changed paths:
74
+
75
+ ```bash
76
+ CHANGED=$(git diff --name-only origin/main...HEAD | tr '\n' ' ')
77
+ [ -n "$CHANGED" ] && trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 --no-progress $CHANGED
78
+ ```
79
+
80
+ When in doubt, scan the whole tree. A transitive CVE in an untouched
81
+ lockfile still ships in the merge.
82
+
83
+ 3. **Run the gate command** and capture the exit code (`echo $?`).
84
+
85
+ 4. **Report findings** in this format, one row per finding (see Example).
86
+
87
+ 5. **On BLOCK**, list each finding's remediation (bump to fixed version,
88
+ remove the secret and rotate it, or fix the misconfig) and stop. Hand
89
+ the merge decision back to the user with the remediation, not a vibe.
90
+
91
+ ## Example
92
+
93
+ Gate run that blocks a merge:
94
+
95
+ ```
96
+ $ trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 .
97
+ $ echo $?
98
+ 1
99
+ ```
100
+
101
+ Reported as:
102
+
103
+ ```
104
+ [CRITICAL] pkg:lodash@4.17.4 → CVE-2021-23337 (command injection)
105
+ fixed in 4.17.21 · path: package-lock.json
106
+ [HIGH] secret: AWS access key · path: config/deploy.env:12 (ROTATE)
107
+ [HIGH] misconfig: Dockerfile runs as root (DS002) · Dockerfile:1
108
+ ```
109
+
110
+ Verdict: **BLOCK**. Bump lodash to 4.17.21, remove + rotate the AWS key,
111
+ add a non-root `USER` to the Dockerfile, then re-run the gate.
112
+
113
+ ## Waivers
114
+
115
+ A finding can be waived only with a justification, never blanket-ignored.
116
+ Add the vulnerability ID to `.trivyignore` with a dated comment:
117
+
118
+ ```
119
+ # CVE-2024-1234 transitive via build-only dep, not reachable at runtime.
120
+ # Waived 2026-06-05 by <user>; revisit when upstream patches.
121
+ CVE-2024-1234
122
+ ```
123
+
124
+ A `.trivyignore` entry with no comment is a smell, flag it. Secret
125
+ findings are **never** waivable: a leaked key is always a stop, and it
126
+ must be rotated, not ignored.
127
+
128
+ ## Rules
129
+
130
+ - HIGH/CRITICAL → merge **blocked**. The exit code is the gate; honor it.
131
+ - A scan that errored or didn't run is a **block**, never a silent pass.
132
+ - Secret findings always block and require rotation, regardless of any
133
+ severity flag or `.trivyignore` entry.
134
+ - Never auto-upgrade a dependency to clear a finding. Surface the fix.
135
+ - Waivers live in `.trivyignore` with a dated reason. No bare entries.
136
+ - In CI/offline, pre-warm the vuln DB (`trivy fs --download-db-only`) and
137
+ pass `--skip-db-update` so the gate doesn't fail on a network hiccup.
138
+ - Don't widen `--severity` to MEDIUM/LOW in the merge gate, that's noise
139
+ that trains people to ignore the gate. Run those on demand only.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Ensure the Trivy CLI is installed; install via the first method that works.
3
+ # Used by the security/trivy-scan skill as the pre-merge gate's prerequisite.
4
+ set -euo pipefail
5
+
6
+ if command -v trivy >/dev/null 2>&1; then
7
+ trivy --version
8
+ exit 0
9
+ fi
10
+
11
+ echo "trivy not found, installing..." >&2
12
+ brew install trivy 2>/dev/null \
13
+ || sudo pacman -S --noconfirm trivy 2>/dev/null \
14
+ || curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/v0.71.0/contrib/install.sh \
15
+ | sh -s -- -b "$HOME/.local/bin"
16
+
17
+ command -v trivy >/dev/null 2>&1 || {
18
+ echo "FATAL: trivy install failed. Install manually: https://trivy.dev/latest/getting-started/installation/" >&2
19
+ exit 1
20
+ }
21
+ trivy --version
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: ccusage
3
+ description: Analyze coding-agent CLI token usage and cost from local logs — Claude Code, Codex, Gemini, Copilot, OpenCode, Amp, Droid and more — as daily, weekly, monthly, or session reports. Use when the user says "ccusage", "how much have I spent", "my token usage", "usage report", "cost breakdown", "how many tokens", or "which model do I use most", or wants to audit AI coding spend. Reads existing logs; no setup.
4
+ allowed-tools: Bash(bunx:*), Bash(npx:*), Bash(ccusage:*)
5
+ category: tools
6
+ tags: [tools, usage, cost, tokens, observability, claude-code, codex]
7
+ metadata:
8
+ version: 1.0.0
9
+ homepage: https://ccusage.com
10
+ ---
11
+
12
+ # Coding-agent usage & cost with ccusage
13
+
14
+ `ccusage` reads the local logs your coding-agent CLIs already write and turns them
15
+ into token-usage and cost reports. It covers Claude Code, Codex, Gemini CLI, GitHub
16
+ Copilot CLI, OpenCode, Amp, Droid, Codebuff, Goose, OpenClaw, Kilo, Kimi, Qwen,
17
+ Hermes, and pi-agent. Nothing to instrument: it parses logs already on disk and
18
+ prices them from a cached LiteLLM snapshot.
19
+
20
+ ## When to activate
21
+
22
+ - The user asks how much they have spent on Claude Code, Codex, Gemini, or Copilot.
23
+ - The user wants a token-usage or cost report by day, week, month, or session.
24
+ - The user asks which model they use most, or for a per-model cost breakdown.
25
+ - The user says "ccusage", "usage report", "cost breakdown", or "audit my AI spend".
26
+ - The user wants to track usage inside Claude's 5-hour billing windows.
27
+
28
+ ## Examples
29
+
30
+ Realistic requests and the command each maps to:
31
+
32
+ - "How much have I spent on Claude Code this month?" → `bunx ccusage claude monthly`
33
+ - "Show my token usage by day across every agent." → `bunx ccusage daily`
34
+ - "Which model am I burning the most on?" → `bunx ccusage daily --breakdown`
35
+ - "What did Codex cost me last week?" → `bunx ccusage codex weekly`
36
+ - "Give me a usage report I can paste into Slack." → `bunx ccusage --compact`
37
+ - "Total my spend between two dates as JSON." → `bunx ccusage daily --since 2026-05-01 --until 2026-05-31 --json | jq '[.daily[].totalCost] | add'`
38
+
39
+ ## Prerequisites
40
+
41
+ No install needed. Run it on demand with `bunx` (preferred) or `npx`:
42
+
43
+ ```bash
44
+ bunx ccusage --version
45
+ ```
46
+
47
+ `bunx` caches the binary after the first run, so repeat calls are fast. If `bun`
48
+ is missing, `npx ccusage@latest` works the same way. To pin a global copy:
49
+ `npm install -g ccusage`, then call `ccusage` directly.
50
+
51
+ ## Core pattern
52
+
53
+ Show every detected agent's usage by day, then drill in:
54
+
55
+ ```bash
56
+ bunx ccusage # all detected sources, by day (default)
57
+ bunx ccusage session # group by conversation session
58
+ bunx ccusage --json # same data as structured JSON
59
+ ```
60
+
61
+ Expected output is a colored table with date or session rows and columns for input,
62
+ output, cache-create, and cache-read tokens plus cost in USD. `--json` emits the
63
+ same numbers for scripting.
64
+
65
+ ## Reports
66
+
67
+ ```bash
68
+ bunx ccusage daily # aggregated by date (default)
69
+ bunx ccusage weekly # aggregated by week
70
+ bunx ccusage monthly # aggregated by month
71
+ bunx ccusage session # grouped by conversation session
72
+ bunx ccusage blocks # Claude Code 5-hour billing windows, with active-block monitoring
73
+ ```
74
+
75
+ ## One agent at a time
76
+
77
+ Prefix the report with a source name to focus on a single CLI:
78
+
79
+ ```bash
80
+ bunx ccusage claude daily # Claude Code only
81
+ bunx ccusage codex daily # Codex
82
+ bunx ccusage gemini daily # Gemini CLI
83
+ bunx ccusage copilot daily # GitHub Copilot CLI
84
+ bunx ccusage opencode weekly # OpenCode
85
+ bunx ccusage amp session # Amp
86
+ bunx ccusage droid daily # Droid
87
+ ```
88
+
89
+ Other sources: `codebuff`, `goose`, `openclaw`, `kilo`, `kimi`, `qwen`, `hermes`,
90
+ `pi`. Use `bunx ccusage daily --all` for the explicit unified report.
91
+
92
+ ## Filters and options
93
+
94
+ ```bash
95
+ bunx ccusage daily --since 2026-04-25 --until 2026-05-16 # date range
96
+ bunx ccusage daily --breakdown # per-model cost breakdown
97
+ bunx ccusage claude daily --instances # group Claude Code by project
98
+ bunx ccusage claude daily --project myproject # filter to one project
99
+ bunx ccusage daily --no-cost # hide cost columns
100
+ bunx ccusage daily --timezone UTC # timezone for date grouping
101
+ bunx ccusage daily --offline # use cached pricing, no network
102
+ bunx ccusage --compact # narrow table for screenshots
103
+ ```
104
+
105
+ ## Programmatic use
106
+
107
+ Pipe `--json` into `jq` to answer questions in scripts. Top-level keys are the
108
+ report name (`daily`, `weekly`, `monthly`, `sessions`, `blocks`) plus `totals`;
109
+ each row carries `inputTokens`, `outputTokens`, `totalCost`, and `modelBreakdowns`:
110
+
111
+ ```bash
112
+ # Sum cost across every daily row in the report
113
+ bunx ccusage daily --json | jq '[.daily[].totalCost] | add'
114
+
115
+ # Per-model breakdown for Claude Code
116
+ bunx ccusage claude daily --json --breakdown | jq '.daily[].modelBreakdowns'
117
+ ```
118
+
119
+ Confirm field names with `bunx ccusage daily --json | jq 'keys'` before relying on
120
+ them; the schema can shift between major versions.
121
+
122
+ ## What this skill does NOT do
123
+
124
+ - It does not bill, charge, or change any account. It only reads local logs.
125
+ - It does not replace `/cost-report`, which queries a separate cost-tracker SQLite
126
+ database. ccusage needs no tracker and reads raw transcripts instead.
127
+ - It does not upload your logs. Pricing is fetched (or cached with `--offline`);
128
+ usage data stays on the machine.
129
+ - It cannot report an agent whose logs are absent on this machine.
130
+
131
+ ## Rules
132
+
133
+ - Prefer `bunx ccusage` over a global install so reports run the latest binary;
134
+ fall back to `npx ccusage@latest` when `bun` is missing.
135
+ - Use `--offline` on flaky or air-gapped networks. It prices from the cached
136
+ LiteLLM snapshot instead of fetching, so the command still completes.
137
+ - Reach for `--json` whenever the answer feeds a calculation or another tool, and
138
+ parse with `jq` rather than eyeballing the table.
139
+ - Pass an explicit source (`ccusage claude ...`) when the user asks about one
140
+ agent, and `--all` when they want every agent in one report.
141
+ - Add `--since`/`--until` for "this week" or "last month" questions instead of
142
+ summing rows by hand.
@@ -57,10 +57,18 @@ export const COMMANDS = {
57
57
  summary: "Resolve+materialize a profile then exec claude/codex (hot path)",
58
58
  load: () => import("./launch"),
59
59
  },
60
+ install: {
61
+ summary: "Prepare profile runtimes and optionally install required CLIs",
62
+ load: () => import("./install"),
63
+ },
60
64
  materialize: {
61
65
  summary: "Write skills + MCPs for any agent (cursor, cline, gemini, copilot, etc.)",
62
66
  load: () => import("./materialize"),
63
67
  },
68
+ summon: {
69
+ summary: "Bind a profile into the LIVE session (soft-load + pin), no cold restart",
70
+ load: () => import("./summon"),
71
+ },
64
72
  quick: {
65
73
  summary: "One-shot bare launch — no profile, no skills, fastest cold start",
66
74
  load: () => import("./quick"),
@@ -129,7 +129,7 @@ Options:
129
129
  }
130
130
 
131
131
  if (apply) {
132
- writeFileSync(join(process.cwd(), ".cue-profile"), best.name + "\n");
132
+ writeFileSync(join(process.cwd(), ".cue.profile"), best.name + "\n");
133
133
  process.stdout.write(` ${green("✓")} Pinned ${bold(best.name)} to this directory.\n\n`);
134
134
  } else {
135
135
  process.stdout.write(` Use it: ${bold(`cue use ${best.name}`)}\n`);
@@ -164,7 +164,7 @@ Options:
164
164
  const profileDir = join(PROFILES_DIR, profileName);
165
165
  mkdirSync(profileDir, { recursive: true });
166
166
  writeFileSync(join(profileDir, "profile.yaml"), output);
167
- writeFileSync(join(process.cwd(), ".cue-profile"), profileName + "\n");
167
+ writeFileSync(join(process.cwd(), ".cue.profile"), profileName + "\n");
168
168
  process.stdout.write(`\n ${green("✓")} Created ${bold(profileName)} (inherits from ${inheritsFrom})\n`);
169
169
  process.stdout.write(` ${green("✓")} Pinned to this directory.\n`);
170
170
  process.stdout.write(` Edit: profiles/${profileName}/profile.yaml\n\n`);
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { run } from "./auto-detect";
7
+
8
+ let tmp: string;
9
+ let origCwd: string;
10
+
11
+ beforeEach(() => {
12
+ tmp = mkdtempSync(join(tmpdir(), "cue-autodetect-cli-"));
13
+ origCwd = process.cwd();
14
+ process.chdir(tmp);
15
+ });
16
+ afterEach(() => {
17
+ process.chdir(origCwd);
18
+ try { rmSync(tmp, { recursive: true, force: true }); } catch {}
19
+ });
20
+
21
+ async function capture(args: string[]): Promise<{ stdout: string; code: number }> {
22
+ const origOut = process.stdout.write.bind(process.stdout);
23
+ let stdout = "";
24
+ (process.stdout as any).write = (c: string | Uint8Array) => { stdout += String(c); return true; };
25
+ try {
26
+ const code = await run(args);
27
+ return { stdout, code };
28
+ } finally {
29
+ (process.stdout as any).write = origOut;
30
+ }
31
+ }
32
+
33
+ describe("cue auto-detect (CLI)", () => {
34
+ test("package.json with stripe dep surfaces the stripe profile", async () => {
35
+ writeFileSync(join(tmp, "package.json"), JSON.stringify({
36
+ dependencies: { stripe: "14.0.0" },
37
+ }));
38
+ const { stdout, code } = await capture([]);
39
+ expect(code).toBe(0);
40
+ expect(stdout).toContain("stripe");
41
+ });
42
+
43
+ test("--json emits v2 suggestions with reasons", async () => {
44
+ writeFileSync(join(tmp, "package.json"), JSON.stringify({
45
+ dependencies: { next: "14.0.0", "@aws-sdk/client-s3": "3.0.0" },
46
+ }));
47
+ const { stdout, code } = await capture(["--json"]);
48
+ expect(code).toBe(0);
49
+ const parsed = JSON.parse(stdout);
50
+ const profiles = parsed.suggestions.map((s: { profile: string }) => s.profile);
51
+ expect(profiles).toContain("nextjs");
52
+ expect(profiles).toContain("aws");
53
+ for (const s of parsed.suggestions) {
54
+ expect(Array.isArray(s.reasons)).toBe(true);
55
+ expect(s.confidence).toBeGreaterThan(0);
56
+ expect(s.confidence).toBeLessThanOrEqual(1);
57
+ }
58
+ });
59
+
60
+ test("--apply pins the top v2 match to .cue.profile", async () => {
61
+ writeFileSync(join(tmp, "package.json"), JSON.stringify({
62
+ dependencies: { next: "14.0.0" },
63
+ }));
64
+ const { code } = await capture(["--apply"]);
65
+ expect(code).toBe(0);
66
+ expect(readFileSync(join(tmp, ".cue.profile"), "utf8").trim()).toBe("nextjs");
67
+ });
68
+
69
+ test("empty dir reports no matches", async () => {
70
+ const { stdout, code } = await capture([]);
71
+ expect(code).toBe(0);
72
+ expect(stdout).toContain("No profile matches");
73
+ });
74
+ });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
- import { detectProfile, } from "../lib/auto-detect";
7
+ import { detectProfileV2 } from "../lib/auto-detect";
8
8
  import { scanProject } from "../lib/project-scanner";
9
9
 
10
10
  export async function run(args: string[]): Promise<number> {
@@ -12,7 +12,10 @@ export async function run(args: string[]): Promise<number> {
12
12
  const apply = args.includes("--apply");
13
13
  const cwd = process.cwd();
14
14
 
15
- const results = detectProfile(cwd);
15
+ // Same detector the launch picker's "Suggested for this cwd" uses, so the
16
+ // CLI and the picker can never disagree about what this directory looks
17
+ // like (the old v1 file-signal detector was blind to package.json deps).
18
+ const results = detectProfileV2(cwd);
16
19
  const project = scanProject(cwd);
17
20
 
18
21
  if (json) {
@@ -36,15 +39,14 @@ export async function run(args: string[]): Promise<number> {
36
39
  process.stdout.write("Suggested profiles:\n\n");
37
40
  for (let i = 0; i < Math.min(results.length, 5); i++) {
38
41
  const r = results[i]!;
39
- const signals = r.signals.join(", ");
40
- process.stdout.write(` ${i + 1}. ${r.profile} (${r.confidence}% match)\n`);
41
- process.stdout.write(` signals: ${signals}\n\n`);
42
+ process.stdout.write(` ${i + 1}. ${r.profile} (${Math.round(r.confidence * 100)}% match)\n`);
43
+ process.stdout.write(` signals: ${r.reasons.join(", ")}\n\n`);
42
44
  }
43
45
 
44
46
  if (apply && results.length > 0) {
45
47
  const best = results[0]!;
46
- writeFileSync(join(cwd, ".cue-profile"), best.profile + "\n");
47
- process.stdout.write(`✅ Pinned "${best.profile}" to .cue-profile\n`);
48
+ writeFileSync(join(cwd, ".cue.profile"), best.profile + "\n");
49
+ process.stdout.write(`✅ Pinned "${best.profile}" to .cue.profile\n`);
48
50
  } else if (!apply && results.length > 0) {
49
51
  process.stdout.write(`Run with --apply to pin the top match, or use \`cue init\` for interactive selection.\n`);
50
52
  }
@@ -46,17 +46,17 @@ describe("cue cli list", () => {
46
46
  expect(installable.length).toBeGreaterThan(5);
47
47
  });
48
48
 
49
- test("no positional + no .cue-profile → usage error", async () => {
49
+ test("no positional + no .cue.profile → usage error", async () => {
50
50
  const orig = process.stderr.write.bind(process.stderr);
51
51
  let err = "";
52
52
  (process.stderr as any).write = (c: string | Uint8Array) => { err += String(c); return true; };
53
53
  try {
54
- // run from /tmp so no .cue-profile lookup succeeds
54
+ // run from /tmp so no .cue.profile lookup succeeds
55
55
  const cwd = process.cwd();
56
56
  process.chdir("/tmp");
57
57
  try {
58
58
  const exit = await cliRun(["list"]);
59
- // either succeeds via parent .cue-profile resolution, or returns 1 with usage
59
+ // either succeeds via parent .cue.profile resolution, or returns 1 with usage
60
60
  if (exit !== 0) expect(err).toContain("Usage");
61
61
  } finally {
62
62
  process.chdir(cwd);
@@ -95,11 +95,27 @@ describe("cue cli install", () => {
95
95
 
96
96
  test("install <known-tool> dry-run produces apt or pip plan", async () => {
97
97
  const { stdout } = await capture(() => cliRun(["install", "nmap", "--json"]));
98
- const out = JSON.parse(stdout) as { plans: Array<{ cli: string; mode: string; command?: string }> };
98
+ const out = JSON.parse(stdout) as { plans: Array<{ cli: string; mode: string; command?: string; argv?: string[] }> };
99
99
  expect(out.plans).toHaveLength(1);
100
100
  expect(out.plans[0]!.cli).toBe("nmap");
101
101
  // On Linux with apt available, mode should be apt; otherwise some other available manager.
102
102
  expect(["apt", "brew", "dnf", "pacman", "winget", "manual"]).toContain(out.plans[0]!.mode);
103
+ if (out.plans[0]!.command) expect(out.plans[0]!.argv?.length).toBeGreaterThan(0);
104
+ });
105
+
106
+ test("script recipes are manual unless explicitly allowed", async () => {
107
+ const blocked = JSON.parse((await capture(() => cliRun(["install", "metasploit", "--json"]))).stdout) as {
108
+ plans: Array<{ mode: string; command?: string; argv?: string[]; hint?: string }>;
109
+ };
110
+ expect(blocked.plans[0]!.mode).toBe("manual");
111
+ expect(blocked.plans[0]!.command).toBeUndefined();
112
+ expect(blocked.plans[0]!.hint).toContain("--allow-scripts");
113
+
114
+ const allowed = JSON.parse((await capture(() => cliRun(["install", "metasploit", "--allow-scripts", "--json"]))).stdout) as {
115
+ plans: Array<{ mode: string; command?: string; argv?: string[] }>;
116
+ };
117
+ expect(allowed.plans[0]!.mode).toBe("script");
118
+ expect(allowed.plans[0]!.argv?.[0]).toBe("bash");
103
119
  });
104
120
 
105
121
  test("install <unknown-tool> dry-run reports no recipe", async () => {
@@ -40,7 +40,12 @@ function which(cmd: string): boolean {
40
40
  }
41
41
 
42
42
  function readRecipes(): Record<string, Recipe> {
43
- try { return JSON.parse(readFileSync(RECIPES_PATH, "utf8")); }
43
+ try {
44
+ const raw = JSON.parse(readFileSync(RECIPES_PATH, "utf8")) as Record<string, unknown>;
45
+ return Object.fromEntries(
46
+ Object.entries(raw).filter(([, v]) => v && typeof v === "object" && !Array.isArray(v)),
47
+ ) as Record<string, Recipe>;
48
+ }
44
49
  catch { return {}; }
45
50
  }
46
51
 
@@ -52,28 +57,34 @@ interface InstallPlan {
52
57
  cli: string;
53
58
  mode: "apt" | "brew" | "dnf" | "pacman" | "snap" | "winget" | "pip" | "pipx" | "npm" | "script" | "manual" | "unknown";
54
59
  command?: string;
60
+ argv?: string[];
55
61
  hint?: string;
56
62
  needs?: string;
57
63
  }
58
64
 
59
- function planInstall(cli: string, recipe: Recipe | undefined): InstallPlan {
65
+ function shellDisplay(argv: string[]): string {
66
+ return argv.map((part) => /^[a-zA-Z0-9_./:@%+=,-]+$/.test(part) ? part : JSON.stringify(part)).join(" ");
67
+ }
68
+
69
+ function planInstall(cli: string, recipe: Recipe | undefined, opts: { allowScripts?: boolean } = {}): InstallPlan {
60
70
  if (!recipe) return { cli, mode: "unknown", hint: `no recipe for "${cli}" in resources/cli-recipes.json` };
61
71
  const os = platform();
62
- const tries: Array<[InstallPlan["mode"], string]> = [];
72
+ const tries: Array<[InstallPlan["mode"], string[]]> = [];
73
+ const words = (s: string) => s.split(/\s+/).filter(Boolean);
63
74
  if (os === "linux") {
64
- if (recipe.apt && which("apt")) tries.push(["apt", `sudo apt install -y ${recipe.apt}`]);
65
- if (recipe.dnf && which("dnf")) tries.push(["dnf", `sudo dnf install -y ${recipe.dnf}`]);
66
- if (recipe.pacman && which("pacman")) tries.push(["pacman", `sudo pacman -S --noconfirm ${recipe.pacman}`]);
75
+ if (recipe.apt && which("apt")) tries.push(["apt", ["sudo", "apt", "install", "-y", ...words(recipe.apt)]]);
76
+ if (recipe.dnf && which("dnf")) tries.push(["dnf", ["sudo", "dnf", "install", "-y", ...words(recipe.dnf)]]);
77
+ if (recipe.pacman && which("pacman")) tries.push(["pacman", ["sudo", "pacman", "-S", "--noconfirm", ...words(recipe.pacman)]]);
67
78
  // snap as a fallback for tools that aren't in distro repos (helm, terraform, etc.)
68
79
  if (recipe.snap && which("snap")) {
69
80
  const classic = recipe.snap.includes("--classic") ? "" : " --classic";
70
81
  const pkg = recipe.snap.replace(/--classic\s*/, "").trim();
71
- tries.push(["snap", `sudo snap install ${pkg}${classic}`]);
82
+ tries.push(["snap", ["sudo", "snap", "install", pkg, ...words(classic)]]);
72
83
  }
73
84
  } else if (os === "darwin") {
74
- if (recipe.brew && which("brew")) tries.push(["brew", `brew install ${recipe.brew}`]);
85
+ if (recipe.brew && which("brew")) tries.push(["brew", ["brew", "install", ...words(recipe.brew)]]);
75
86
  } else if (os === "win32") {
76
- if (recipe.winget && which("winget")) tries.push(["winget", `winget install --id ${recipe.winget} -e`]);
87
+ if (recipe.winget && which("winget")) tries.push(["winget", ["winget", "install", "--id", recipe.winget, "-e"]]);
77
88
  }
78
89
  // Cross-platform language pkg managers as fallback.
79
90
  // For Python packages: prefer pipx (isolated, ships its own pip), then pip3
@@ -83,21 +94,23 @@ function planInstall(cli: string, recipe: Recipe | undefined): InstallPlan {
83
94
  // available — most pip recipes here are CLI tools, which is exactly what
84
95
  // pipx is designed for.
85
96
  if (recipe.pipx && which("pipx")) {
86
- tries.push(["pipx", `pipx install ${recipe.pipx}`]);
97
+ tries.push(["pipx", ["pipx", "install", ...words(recipe.pipx)]]);
87
98
  } else if (recipe.pip) {
88
- if (which("pipx")) tries.push(["pipx", `pipx install ${recipe.pip}`]);
89
- else if (which("pip3")) tries.push(["pip", `pip3 install --user ${recipe.pip}`]);
90
- else tries.push(["pip", `python3 -m pip install --user ${recipe.pip}`]);
99
+ if (which("pipx")) tries.push(["pipx", ["pipx", "install", ...words(recipe.pip)]]);
100
+ else if (which("pip3")) tries.push(["pip", ["pip3", "install", "--user", ...words(recipe.pip)]]);
101
+ else tries.push(["pip", ["python3", "-m", "pip", "install", "--user", ...words(recipe.pip)]]);
91
102
  }
92
- if (recipe.npm && which("npm")) tries.push(["npm", `npm install -g ${recipe.npm}`]);
93
- if (recipe.script) tries.push(["script", recipe.script]);
103
+ if (recipe.npm && which("npm")) tries.push(["npm", ["npm", "install", "-g", ...words(recipe.npm)]]);
104
+ if (recipe.script && opts.allowScripts) tries.push(["script", ["bash", "-c", recipe.script]]);
94
105
 
95
106
  if (tries.length === 0) {
96
- const manual = recipe.manual ?? `no installer for this OS/recipe`;
107
+ const manual = recipe.manual ?? (recipe.script
108
+ ? `script installer requires --allow-scripts: ${recipe.script}`
109
+ : `no installer for this OS/recipe`);
97
110
  return { cli, mode: "manual", hint: manual, needs: recipe.needs };
98
111
  }
99
- const [mode, command] = tries[0]!;
100
- return { cli, mode, command, needs: recipe.needs };
112
+ const [mode, argv] = tries[0]!;
113
+ return { cli, mode, argv, command: shellDisplay(argv), needs: recipe.needs };
101
114
  }
102
115
 
103
116
  async function resolveProfileArg(args: string[]): Promise<string | undefined> {
@@ -210,6 +223,7 @@ async function listCmd(args: string[]): Promise<number> {
210
223
  async function installCmd(args: string[]): Promise<number> {
211
224
  const all = args.includes("--all");
212
225
  const yes = args.includes("--yes");
226
+ const allowScripts = args.includes("--allow-scripts");
213
227
  const dryRun = !yes;
214
228
  const asJson = args.includes("--json");
215
229
  const positional = args.filter((a) => !a.startsWith("-"));
@@ -237,7 +251,7 @@ async function installCmd(args: string[]): Promise<number> {
237
251
  return 0;
238
252
  }
239
253
 
240
- const plans = targets.map((cli) => planInstall(cli, recipes[cli]));
254
+ const plans = targets.map((cli) => planInstall(cli, recipes[cli], { allowScripts }));
241
255
  const installable = plans.filter((p) => p.command);
242
256
  const manual = plans.filter((p) => !p.command);
243
257
 
@@ -271,7 +285,8 @@ async function installCmd(args: string[]): Promise<number> {
271
285
  let failed = 0;
272
286
  for (const p of installable) {
273
287
  process.stdout.write(`\n ${bold(`→ Installing ${p.cli}`)}\n ${dim("$ " + p.command)}\n`);
274
- const res = spawnSync("bash", ["-c", p.command!], { stdio: "inherit" });
288
+ const argv = p.argv!;
289
+ const res = spawnSync(argv[0]!, argv.slice(1), { stdio: "inherit" });
275
290
  if (res.status !== 0) {
276
291
  process.stdout.write(` ${red(`✗ ${p.cli} install failed (exit ${res.status})`)}\n`);
277
292
  failed++;
@@ -298,6 +313,7 @@ export async function run(args: string[]): Promise<number> {
298
313
  process.stderr.write(" cue cli list [profile]\n");
299
314
  process.stderr.write(" cue cli install <tool>\n");
300
315
  process.stderr.write(" cue cli install --all [profile] [--yes] [--json]\n");
316
+ process.stderr.write(" cue cli install --all [profile] [--yes] [--allow-scripts]\n");
301
317
  return sub ? 1 : 0;
302
318
  }
303
319
  }
@@ -118,11 +118,11 @@ export async function run(args: string[]): Promise<number> {
118
118
  await writeFile(yamlPath, renderYaml(parsed));
119
119
 
120
120
  if (parsed.pin) {
121
- await writeFile(join(process.cwd(), ".cue-profile"), `${parsed.name}\n`);
121
+ await writeFile(join(process.cwd(), ".cue.profile"), `${parsed.name}\n`);
122
122
  }
123
123
 
124
124
  process.stdout.write(`✓ created ${yamlPath}\n`);
125
- if (parsed.pin) process.stdout.write(`✓ pinned to ${process.cwd()}/.cue-profile\n`);
125
+ if (parsed.pin) process.stdout.write(`✓ pinned to ${process.cwd()}/.cue.profile\n`);
126
126
  process.stdout.write(`launch with: claude\n`);
127
127
  return 0;
128
128
  }