cue-ai 0.9.2 → 0.9.3
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.
- package/CHANGELOG.md +4 -3
- package/README.md +148 -170
- package/bin/cue-learnings +30 -4
- package/bin/cue-review-progress +0 -0
- package/bin/cue-review-watch +0 -0
- package/dist/cue.js +4328 -3108
- package/package.json +1 -1
- package/plugins/cue/commands/cue-switch.md +1 -1
- package/plugins/cue/commands/cue.md +1 -1
- package/profiles/backend/profile.yaml +4 -0
- package/profiles/browser/profile.yaml +4 -0
- package/profiles/career/profile.yaml +2 -13
- package/profiles/commerce/profile.yaml +0 -2
- package/profiles/coolify/profile.yaml +0 -1
- package/profiles/core/profile.yaml +78 -11
- package/profiles/dash-merge-test/profile.yaml +6 -1
- package/profiles/designer/profile.yaml +9 -1
- package/profiles/dropshipping/profile.yaml +69 -0
- package/profiles/frontend/profile.yaml +4 -0
- package/profiles/google-ads/profile.yaml +34 -0
- package/profiles/google-analytics/profile.yaml +34 -0
- package/profiles/google-drive/profile.yaml +34 -0
- package/profiles/gstack/profile.yaml +117 -29
- package/profiles/marketing/profile.yaml +0 -1
- package/profiles/media/README.md +70 -0
- package/profiles/media/profile.yaml +104 -0
- package/profiles/nano-banana/profile.yaml +52 -0
- package/profiles/ops/profile.yaml +1 -2
- package/profiles/secops/profile.yaml +3 -0
- package/profiles/skill-writer/profile.yaml +15 -0
- package/profiles/video/profile.yaml +3 -0
- package/profiles/web-frontend-base/profile.yaml +6 -0
- package/profiles/webshop/profile.yaml +0 -1
- package/profiles/webshop-google/profile.yaml +1 -0
- package/profiles/x-growth-bot/profile.yaml +2 -0
- package/resources/icons/generate-icons.py +2 -128
- package/resources/mcps/configs/claude.sanitized.json +88 -20
- package/resources/mcps/configs/claude_runtime.sanitized.json +40 -1
- package/resources/mcps/configs/codex.sanitized.json +29 -0
- package/resources/skills/skills/career/job-hunter/LICENSE +21 -0
- package/resources/skills/skills/career/job-hunter/README.md +323 -0
- package/resources/skills/skills/career/job-hunter/SKILL.md +91 -0
- package/resources/skills/skills/career/job-hunter/agents/README.md +96 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-assessment-prep.md +195 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-ats-scan.md +155 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-bias-audit.md +224 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-cover-letter.md +69 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-decode-jd.md +117 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-fit-score.md +183 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-audit.md +74 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-scrape.md +255 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-portfolio-brief.md +123 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-reality-check.md +164 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-reference-prep.md +150 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-rejection-analysis.md +172 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-resume.md +70 -0
- package/resources/skills/skills/career/job-hunter/agents/apply-skills-gap-filler.md +109 -0
- package/resources/skills/skills/career/job-hunter/agents/career-internal.md +94 -0
- package/resources/skills/skills/career/job-hunter/agents/career-linkedin-content.md +173 -0
- package/resources/skills/skills/career/job-hunter/agents/career-linkedin-scanner.md +262 -0
- package/resources/skills/skills/career/job-hunter/agents/career-network-message.md +108 -0
- package/resources/skills/skills/career/job-hunter/agents/career-promote.md +102 -0
- package/resources/skills/skills/career/job-hunter/agents/career-review.md +71 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-debrief.md +117 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-mock.md +171 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-panel-decoder.md +152 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-prep.md +184 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-question-bank.md +133 -0
- package/resources/skills/skills/career/job-hunter/agents/interview-research.md +148 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-compare.md +117 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-counteroffer.md +144 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-deadline-manager.md +148 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-negotiate.md +126 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-schedule.md +99 -0
- package/resources/skills/skills/career/job-hunter/agents/offer-thankyou.md +80 -0
- package/resources/skills/skills/career/job-hunter/agents/search-company-research.md +146 -0
- package/resources/skills/skills/career/job-hunter/agents/search-follow-up.md +129 -0
- package/resources/skills/skills/career/job-hunter/agents/search-ghost-job-detector.md +152 -0
- package/resources/skills/skills/career/job-hunter/agents/search-inbox-scan.md +193 -0
- package/resources/skills/skills/career/job-hunter/agents/search-interview-scorecard.md +164 -0
- package/resources/skills/skills/career/job-hunter/agents/search-jobs.md +149 -0
- package/resources/skills/skills/career/job-hunter/agents/search-momentum-check.md +194 -0
- package/resources/skills/skills/career/job-hunter/agents/search-outreach.md +85 -0
- package/resources/skills/skills/career/job-hunter/agents/search-referral-finder.md +124 -0
- package/resources/skills/skills/career/job-hunter/agents/search-salary.md +96 -0
- package/resources/skills/skills/career/job-hunter/agents/search-send-email.md +109 -0
- package/resources/skills/skills/career/job-hunter/agents/search-tracker-update.md +127 -0
- package/resources/skills/skills/career/job-hunter/inputs/README.md +26 -0
- package/resources/skills/skills/career/job-hunter/inputs/apply-linkedin-url.txt +8 -0
- package/resources/skills/skills/career/job-hunter/inputs/interview-context.md +24 -0
- package/resources/skills/skills/career/job-hunter/inputs/job-description.md +20 -0
- package/resources/skills/skills/career/job-hunter/inputs/job-search-criteria.md +36 -0
- package/resources/skills/skills/career/job-hunter/inputs/my-linkedin.md +24 -0
- package/resources/skills/skills/career/job-hunter/inputs/my-resume.md +28 -0
- package/resources/skills/skills/career/job-hunter/inputs/search-outreach-target.md +24 -0
- package/resources/skills/skills/career/job-hunter/rules/README.md +37 -0
- package/resources/skills/skills/career/job-hunter/rules/writing-rules.md +81 -0
- package/resources/skills/skills/design/banana/SKILL.md +375 -0
- package/resources/skills/skills/design/banana/references/cost-tracking.md +47 -0
- package/resources/skills/skills/design/banana/references/gemini-models.md +236 -0
- package/resources/skills/skills/design/banana/references/mcp-tools.md +145 -0
- package/resources/skills/skills/design/banana/references/post-processing.md +192 -0
- package/resources/skills/skills/design/banana/references/presets.md +69 -0
- package/resources/skills/skills/design/banana/references/prompt-engineering.md +481 -0
- package/resources/skills/skills/design/banana/scripts/batch.py +97 -0
- package/resources/skills/skills/design/banana/scripts/cost_tracker.py +191 -0
- package/resources/skills/skills/design/banana/scripts/edit.py +159 -0
- package/resources/skills/skills/design/banana/scripts/generate.py +168 -0
- package/resources/skills/skills/design/banana/scripts/presets.py +154 -0
- package/resources/skills/skills/design/banana/scripts/setup_mcp.py +151 -0
- package/resources/skills/skills/design/banana/scripts/validate_setup.py +133 -0
- package/resources/skills/skills/gstack/ship/SKILL.md +13 -0
- package/resources/skills/skills/media/3d-logo-animation/SKILL.md +59 -0
- package/resources/skills/skills/media/action-figure-generator/SKILL.md +48 -0
- package/resources/skills/skills/media/ad-creative/SKILL.md +79 -0
- package/resources/skills/skills/media/ai-clipping/SKILL.md +194 -0
- package/resources/skills/skills/media/ai-clipping/scripts/run-ai-clipping.sh +200 -0
- package/resources/skills/skills/media/ai-fight-scene/SKILL.md +132 -0
- package/resources/skills/skills/media/amazon-product-listing/SKILL.md +68 -0
- package/resources/skills/skills/media/animal-video-generator/SKILL.md +59 -0
- package/resources/skills/skills/media/award-ceremony-video/SKILL.md +87 -0
- package/resources/skills/skills/media/blog-header/SKILL.md +61 -0
- package/resources/skills/skills/media/brand-kit/SKILL.md +72 -0
- package/resources/skills/skills/media/brochures/SKILL.md +65 -0
- package/resources/skills/skills/media/cartoon-dance-animation/SKILL.md +62 -0
- package/resources/skills/skills/media/character-story-video/SKILL.md +84 -0
- package/resources/skills/skills/media/chibi-collage-effect/SKILL.md +63 -0
- package/resources/skills/skills/media/cinema-director/SKILL.md +93 -0
- package/resources/skills/skills/media/cinema-director/scripts/generate-film.sh +78 -0
- package/resources/skills/skills/media/color-analysis-board/SKILL.md +71 -0
- package/resources/skills/skills/media/core-edit/SKILL.md +48 -0
- package/resources/skills/skills/media/core-edit/edit-image.sh +54 -0
- package/resources/skills/skills/media/core-edit/enhance-image.sh +191 -0
- package/resources/skills/skills/media/core-edit/lipsync.sh +144 -0
- package/resources/skills/skills/media/core-edit/video-effects.sh +193 -0
- package/resources/skills/skills/media/core-media/SKILL.md +49 -0
- package/resources/skills/skills/media/core-media/create-music.sh +169 -0
- package/resources/skills/skills/media/core-media/generate-image.sh +161 -0
- package/resources/skills/skills/media/core-media/generate-video.sh +137 -0
- package/resources/skills/skills/media/core-media/image-to-video.sh +228 -0
- package/resources/skills/skills/media/core-media/schema_data.json +18708 -0
- package/resources/skills/skills/media/core-media/upload.sh +41 -0
- package/resources/skills/skills/media/core-platform/SKILL.md +41 -0
- package/resources/skills/skills/media/core-platform/check-result.sh +37 -0
- package/resources/skills/skills/media/core-platform/setup.sh +31 -0
- package/resources/skills/skills/media/couple-grid-creator/SKILL.md +47 -0
- package/resources/skills/skills/media/design-guide/SKILL.md +73 -0
- package/resources/skills/skills/media/drone-style-video/SKILL.md +61 -0
- package/resources/skills/skills/media/fashion-try-on/SKILL.md +61 -0
- package/resources/skills/skills/media/floor-plan-rendering/SKILL.md +56 -0
- package/resources/skills/skills/media/freeze-effect-video/SKILL.md +100 -0
- package/resources/skills/skills/media/giant-product-showcase/SKILL.md +61 -0
- package/resources/skills/skills/media/instagram-post/SKILL.md +58 -0
- package/resources/skills/skills/media/interior-design/SKILL.md +61 -0
- package/resources/skills/skills/media/interior-design-visualizer/SKILL.md +57 -0
- package/resources/skills/skills/media/jewelry-product-video/SKILL.md +61 -0
- package/resources/skills/skills/media/kdenlive/SKILL.md +106 -0
- package/resources/skills/skills/media/kdenlive/scripts/assemble.sh +57 -0
- package/resources/skills/skills/media/kdenlive/scripts/common.sh +30 -0
- package/resources/skills/skills/media/kdenlive/scripts/inspect.sh +19 -0
- package/resources/skills/skills/media/kdenlive/scripts/reframe.sh +22 -0
- package/resources/skills/skills/media/kdenlive/scripts/render.sh +16 -0
- package/resources/skills/skills/media/kdenlive/scripts/title-card.sh +25 -0
- package/resources/skills/skills/media/keyboard-art-maker/SKILL.md +44 -0
- package/resources/skills/skills/media/logo-branding/SKILL.md +70 -0
- package/resources/skills/skills/media/logo-creator/SKILL.md +80 -0
- package/resources/skills/skills/media/logo-creator/scripts/create-logo.sh +38 -0
- package/resources/skills/skills/media/logo-generator/SKILL.md +56 -0
- package/resources/skills/skills/media/multi-angle-reshoot/SKILL.md +70 -0
- package/resources/skills/skills/media/multi-angle-shots/SKILL.md +73 -0
- package/resources/skills/skills/media/music-video/SKILL.md +61 -0
- package/resources/skills/skills/media/nano-banana/SKILL.md +80 -0
- package/resources/skills/skills/media/nano-banana/scripts/generate-nano-art.sh +54 -0
- package/resources/skills/skills/media/one-shot-video/SKILL.md +56 -0
- package/resources/skills/skills/media/photo-pack-generator/SKILL.md +205 -0
- package/resources/skills/skills/media/photo-pack-generator/scripts/generate-pack.sh +241 -0
- package/resources/skills/skills/media/product-ad-cinematic/SKILL.md +78 -0
- package/resources/skills/skills/media/product-campaign/SKILL.md +76 -0
- package/resources/skills/skills/media/product-showcase-video/SKILL.md +60 -0
- package/resources/skills/skills/media/product-video-ad-maker/SKILL.md +59 -0
- package/resources/skills/skills/media/rednote-cover/SKILL.md +57 -0
- package/resources/skills/skills/media/seedance-2/SKILL.md +632 -0
- package/resources/skills/skills/media/seedance-2/scripts/generate-seedance.sh +701 -0
- package/resources/skills/skills/media/selfie-with-celebrities/SKILL.md +64 -0
- package/resources/skills/skills/media/social-media-video/SKILL.md +277 -0
- package/resources/skills/skills/media/social-media-video/scripts/run-social-video.sh +316 -0
- package/resources/skills/skills/media/social-pack/SKILL.md +58 -0
- package/resources/skills/skills/media/storyboard/SKILL.md +57 -0
- package/resources/skills/skills/media/storyboard-to-cooking-video/SKILL.md +143 -0
- package/resources/skills/skills/media/talking-baby-video/SKILL.md +57 -0
- package/resources/skills/skills/media/ugc-ads-workflow/SKILL.md +70 -0
- package/resources/skills/skills/media/ugc-lifestyle-try-on/SKILL.md +65 -0
- package/resources/skills/skills/media/ugc-video-factory/SKILL.md +134 -0
- package/resources/skills/skills/media/ui-design/SKILL.md +81 -0
- package/resources/skills/skills/media/ui-design/scripts/generate-mockup.sh +49 -0
- package/resources/skills/skills/media/url-to-design/SKILL.md +61 -0
- package/resources/skills/skills/media/workflow/SKILL.md +197 -0
- package/resources/skills/skills/media/workflow/scripts/discover-workflow.sh +18 -0
- package/resources/skills/skills/media/workflow/scripts/generate-workflow.sh +33 -0
- package/resources/skills/skills/media/workflow/scripts/interactive-run.sh +16 -0
- package/resources/skills/skills/media/workflow/scripts/list-workflows.sh +20 -0
- package/resources/skills/skills/media/workflow/scripts/run-workflow.sh +34 -0
- package/resources/skills/skills/media/youtube-shorts/SKILL.md +173 -0
- package/resources/skills/skills/media/youtube-shorts/scripts/run-youtube-shorts.sh +141 -0
- package/resources/skills/skills/media/youtube-thumbnail/SKILL.md +66 -0
- package/resources/skills/skills/meta/cue-developer/references/architecture.md +2 -2
- package/resources/skills/skills/meta/cue-usage/SKILL.md +1 -1
- package/resources/skills/skills/meta/profile-fit-monitor/SKILL.md +2 -2
- package/resources/skills/skills/meta/profile-optimizer/SKILL.md +1 -1
- package/resources/skills/skills/meta/profile-suggest/SKILL.md +7 -7
- package/resources/skills/skills/meta/profile-summon/SKILL.md +159 -0
- package/resources/skills/skills/meta/profile-summon/evals/evals.json +53 -0
- package/resources/skills/skills/meta/save-profile/SKILL.md +1 -1
- package/resources/skills/skills/meta/skill-reviewer/SKILL.md +3 -0
- package/resources/skills/skills/meta/skill-reviewer/references/tdd-for-skills.md +55 -0
- package/resources/skills/skills/research/find-skills/SKILL.md +1 -1
- package/resources/skills/skills/review/code-review-deep/SKILL.md +20 -0
- package/resources/skills/skills/security/trivy-scan/SKILL.md +139 -0
- package/resources/skills/skills/security/trivy-scan/scripts/ensure-trivy.sh +21 -0
- package/resources/skills/skills/tools/ccusage/SKILL.md +142 -0
- package/src/commands/_index.ts +8 -0
- package/src/commands/ai.ts +2 -2
- package/src/commands/auto-detect.test.ts +74 -0
- package/src/commands/auto-detect.ts +9 -7
- package/src/commands/cli.test.ts +20 -4
- package/src/commands/cli.ts +36 -20
- package/src/commands/create-profile.ts +2 -2
- package/src/commands/debug.ts +2 -2
- package/src/commands/discover.ts +14 -4
- package/src/commands/export-docker.ts +1 -1
- package/src/commands/features-batch1.test.ts +1 -1
- package/src/commands/gates.ts +1 -1
- package/src/commands/import-profile.ts +1 -1
- package/src/commands/init.ts +15 -11
- package/src/commands/install.test.ts +192 -0
- package/src/commands/install.ts +610 -0
- package/src/commands/launch-handoff.e2e.test.ts +33 -1
- package/src/commands/launch.e2e.test.ts +15 -10
- package/src/commands/launch.ts +73 -116
- package/src/commands/materialize.ts +2 -2
- package/src/commands/prune.ts +1 -1
- package/src/commands/security-audit.ts +1 -1
- package/src/commands/shell.ts +7 -7
- package/src/commands/skill-report.ts +1 -1
- package/src/commands/skills.ts +3 -3
- package/src/commands/snapshot.ts +2 -2
- package/src/commands/summon.test.ts +116 -0
- package/src/commands/summon.ts +338 -0
- package/src/commands/trigger-gaps.ts +1 -1
- package/src/commands/use.ts +47 -3
- package/src/commands/watch-live.ts +5 -5
- package/src/commands/watch.ts +8 -8
- package/src/index.ts +2 -0
- package/src/lib/active-sessions.test.ts +3 -3
- package/src/lib/active-sessions.ts +4 -4
- package/src/lib/auto-detect.test.ts +172 -8
- package/src/lib/auto-detect.ts +191 -136
- package/src/lib/codex-persona-parity.test.ts +58 -0
- package/src/lib/companion-detect.test.ts +43 -1
- package/src/lib/companion-detect.ts +35 -0
- package/src/lib/credentials-sync.test.ts +121 -1
- package/src/lib/credentials-sync.ts +95 -1
- package/src/lib/cwd-resolver.test.ts +8 -8
- package/src/lib/cwd-resolver.ts +2 -2
- package/src/lib/dashboard-merge.test.ts +9 -4
- package/src/lib/dashboard-server.ts +1 -1
- package/src/lib/picker.test.ts +1 -1
- package/src/lib/picker.ts +5 -5
- package/src/lib/profile-merge.test.ts +8 -0
- package/src/lib/profile-names.test.ts +3 -3
- package/src/lib/runtime-install.ts +166 -0
- package/src/lib/runtime-materializer.test.ts +137 -0
- package/src/lib/runtime-materializer.ts +105 -2
- package/src/lib/skill-router.test.ts +38 -0
- package/src/lib/skill-router.ts +65 -4
- package/profiles/eu-tender-research/README.md +0 -48
- package/profiles/eu-tender-research/logo.png +0 -0
- 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.
|
package/src/commands/_index.ts
CHANGED
|
@@ -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"),
|
package/src/commands/ai.ts
CHANGED
|
@@ -129,7 +129,7 @@ Options:
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (apply) {
|
|
132
|
-
writeFileSync(join(process.cwd(), ".cue
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
process.stdout.write(`
|
|
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
|
|
47
|
-
process.stdout.write(`✅ Pinned "${best.profile}" to .cue
|
|
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
|
}
|
package/src/commands/cli.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 () => {
|
package/src/commands/cli.ts
CHANGED
|
@@ -40,7 +40,12 @@ function which(cmd: string): boolean {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function readRecipes(): Record<string, Recipe> {
|
|
43
|
-
try {
|
|
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
|
|
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",
|
|
65
|
-
if (recipe.dnf && which("dnf")) tries.push(["dnf",
|
|
66
|
-
if (recipe.pacman && which("pacman")) tries.push(["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",
|
|
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",
|
|
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",
|
|
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",
|
|
97
|
+
tries.push(["pipx", ["pipx", "install", ...words(recipe.pipx)]]);
|
|
87
98
|
} else if (recipe.pip) {
|
|
88
|
-
if (which("pipx")) tries.push(["pipx",
|
|
89
|
-
else if (which("pip3")) tries.push(["pip",
|
|
90
|
-
else tries.push(["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",
|
|
93
|
-
if (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 ??
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|