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,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cue install` — prepare cue profiles, import external skill repos, and audit
|
|
3
|
+
* materialized runtimes.
|
|
4
|
+
*
|
|
5
|
+
* Dry-run is the default. Pass --yes to write runtimes/profile files or execute
|
|
6
|
+
* installers. External repo setup scripts need --run-setup in addition to --yes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { existsSync, lstatSync, readdirSync, statSync } from "node:fs";
|
|
11
|
+
import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
12
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
13
|
+
|
|
14
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
15
|
+
|
|
16
|
+
import type { AgentKind } from "../../profiles/_types";
|
|
17
|
+
import { configDir } from "../lib/config-paths";
|
|
18
|
+
import { resolveActiveProfile } from "../lib/cwd-resolver";
|
|
19
|
+
import { getAdapter, AGENT_IDS } from "../lib/agent-adapters";
|
|
20
|
+
import { loadProfile, listProfiles } from "../lib/profile-loader";
|
|
21
|
+
import {
|
|
22
|
+
expandSkillWildcards,
|
|
23
|
+
isRuntimeAgent,
|
|
24
|
+
loadMcpRegistry,
|
|
25
|
+
prepareRuntime,
|
|
26
|
+
resolveClaudeCredentialsSource,
|
|
27
|
+
runtimeDirFor,
|
|
28
|
+
type RuntimeAgent,
|
|
29
|
+
RUNTIME_AGENTS,
|
|
30
|
+
} from "../lib/runtime-install";
|
|
31
|
+
import { resolveLocalSkill } from "../lib/resolver-local";
|
|
32
|
+
import { run as cliRun } from "./cli";
|
|
33
|
+
|
|
34
|
+
type AnyAgent = AgentKind;
|
|
35
|
+
|
|
36
|
+
interface ParsedArgs {
|
|
37
|
+
profiles: string[];
|
|
38
|
+
allProfiles: boolean;
|
|
39
|
+
agents: AnyAgent[];
|
|
40
|
+
yes: boolean;
|
|
41
|
+
dryRun: boolean;
|
|
42
|
+
json: boolean;
|
|
43
|
+
force: boolean;
|
|
44
|
+
withClis: boolean;
|
|
45
|
+
dir?: string;
|
|
46
|
+
preset?: "skills-only" | "runtimes-and-clis" | "full";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface InstallAction {
|
|
50
|
+
profile: string;
|
|
51
|
+
agent: AnyAgent;
|
|
52
|
+
targetDir: string;
|
|
53
|
+
status: "planned" | "rebuilt" | "cached" | "written" | "failed";
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface InstallReport {
|
|
58
|
+
profile: string;
|
|
59
|
+
agent: RuntimeAgent;
|
|
60
|
+
missingMcps: string[];
|
|
61
|
+
oversizedSkills: Array<{ id: string; bytes: number }>;
|
|
62
|
+
brokenRuntimeSkills: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface RepoArgs {
|
|
66
|
+
repo: string;
|
|
67
|
+
profile?: string;
|
|
68
|
+
category: string;
|
|
69
|
+
yes: boolean;
|
|
70
|
+
dryRun: boolean;
|
|
71
|
+
json: boolean;
|
|
72
|
+
runSetup: boolean;
|
|
73
|
+
force: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const REPO_ROOT = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(import.meta.dirname, "..", "..");
|
|
77
|
+
const PROFILES_DIR = process.env.CUE_PROFILES_DIR ?? process.env.SOUL_PROFILES_DIR ?? join(REPO_ROOT, "profiles");
|
|
78
|
+
const SKILLS_ROOT = join(REPO_ROOT, "resources", "skills", "skills");
|
|
79
|
+
const BYTE_CEILING = 160_000;
|
|
80
|
+
|
|
81
|
+
function bold(s: string): string { return `\x1b[1m${s}\x1b[0m`; }
|
|
82
|
+
function green(s: string): string { return `\x1b[32m${s}\x1b[0m`; }
|
|
83
|
+
function yellow(s: string): string { return `\x1b[33m${s}\x1b[0m`; }
|
|
84
|
+
function dim(s: string): string { return `\x1b[2m${s}\x1b[0m`; }
|
|
85
|
+
|
|
86
|
+
function usage(): void {
|
|
87
|
+
process.stdout.write(`cue install — prepare cue profiles for local agents
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
cue install [profile] [--agents claude-code,codex] [--with-clis] [--yes]
|
|
91
|
+
cue install --all-profiles [--agents claude-code,codex] [--with-clis] [--yes]
|
|
92
|
+
cue install repo <github-url|owner/repo> --profile <name> [--yes] [--run-setup]
|
|
93
|
+
cue install doctor [profile|--all-profiles] [--json]
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
--all-profiles Prepare or audit every installed profile
|
|
97
|
+
--profile <name> Prepare a specific profile
|
|
98
|
+
--agents <list> Agents: claude-code,codex,cursor,cline,windsurf,gemini,copilot,roo,amp,aider,all
|
|
99
|
+
--dir <path> Target dir for project/global adapters outside cue runtime
|
|
100
|
+
--preset <name> skills-only | runtimes-and-clis | full
|
|
101
|
+
--with-clis Also run cue cli install --all for each profile
|
|
102
|
+
--yes Execute writes/installers. Default is dry-run
|
|
103
|
+
--dry-run Show the plan without writing
|
|
104
|
+
--force Rebuild runtimes by dropping their .cue-hash first
|
|
105
|
+
--json Machine-readable output
|
|
106
|
+
|
|
107
|
+
Repo flags:
|
|
108
|
+
--category <name> Category for vendored skills, default external
|
|
109
|
+
--run-setup Run ./setup in the cloned repo. Requires --yes
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
cue install core
|
|
113
|
+
cue install gstack --yes
|
|
114
|
+
cue install backend --agents cursor --dir . --yes
|
|
115
|
+
cue install --all-profiles --preset runtimes-and-clis --yes
|
|
116
|
+
cue install repo https://github.com/garrytan/gstack.git --profile maker --yes --run-setup
|
|
117
|
+
cue install doctor --all-profiles
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseAgents(raw: string | undefined): AnyAgent[] | null {
|
|
122
|
+
if (!raw) return [...RUNTIME_AGENTS];
|
|
123
|
+
const values = raw === "all" ? AGENT_IDS : raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
124
|
+
const out: AnyAgent[] = [];
|
|
125
|
+
for (const value of values) {
|
|
126
|
+
const normalized = value === "claude" ? "claude-code" : value;
|
|
127
|
+
if (!AGENT_IDS.includes(normalized)) return null;
|
|
128
|
+
if (!out.includes(normalized as AnyAgent)) out.push(normalized as AnyAgent);
|
|
129
|
+
}
|
|
130
|
+
return out.length > 0 ? out : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parse(args: string[]): ParsedArgs | null {
|
|
134
|
+
const profiles: string[] = [];
|
|
135
|
+
let allProfiles = false;
|
|
136
|
+
let agents: AnyAgent[] = [...RUNTIME_AGENTS];
|
|
137
|
+
let yes = false;
|
|
138
|
+
let dryRun = false;
|
|
139
|
+
let json = false;
|
|
140
|
+
let force = false;
|
|
141
|
+
let withClis = false;
|
|
142
|
+
let dir: string | undefined;
|
|
143
|
+
let preset: ParsedArgs["preset"];
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < args.length; i++) {
|
|
146
|
+
const a = args[i]!;
|
|
147
|
+
if (a === "-h" || a === "--help") return null;
|
|
148
|
+
if (a === "--all-profiles") allProfiles = true;
|
|
149
|
+
else if (a === "--profile") profiles.push(args[++i] ?? "");
|
|
150
|
+
else if (a === "--agents") {
|
|
151
|
+
const parsed = parseAgents(args[++i]);
|
|
152
|
+
if (!parsed) throw new Error(`invalid --agents value; use ${AGENT_IDS.join(",")},all`);
|
|
153
|
+
agents = parsed;
|
|
154
|
+
} else if (a === "--dir") dir = args[++i];
|
|
155
|
+
else if (a === "--preset") {
|
|
156
|
+
const value = args[++i];
|
|
157
|
+
if (value !== "skills-only" && value !== "runtimes-and-clis" && value !== "full") {
|
|
158
|
+
throw new Error("invalid --preset; use skills-only, runtimes-and-clis, or full");
|
|
159
|
+
}
|
|
160
|
+
preset = value;
|
|
161
|
+
} else if (a === "--yes") yes = true;
|
|
162
|
+
else if (a === "--dry-run") dryRun = true;
|
|
163
|
+
else if (a === "--json") json = true;
|
|
164
|
+
else if (a === "--force") force = true;
|
|
165
|
+
else if (a === "--with-clis" || a === "--clis") withClis = true;
|
|
166
|
+
else if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
167
|
+
else profiles.push(a);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (preset === "runtimes-and-clis" || preset === "full") withClis = true;
|
|
171
|
+
if (preset === "skills-only") withClis = false;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
profiles: profiles.filter(Boolean),
|
|
175
|
+
allProfiles,
|
|
176
|
+
agents,
|
|
177
|
+
yes,
|
|
178
|
+
dryRun: dryRun || !yes,
|
|
179
|
+
json,
|
|
180
|
+
force,
|
|
181
|
+
withClis,
|
|
182
|
+
dir,
|
|
183
|
+
preset,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function resolveProfiles(parsed: ParsedArgs): Promise<string[]> {
|
|
188
|
+
if (parsed.allProfiles) return listProfiles();
|
|
189
|
+
if (parsed.profiles.length > 0) return [...new Set(parsed.profiles)];
|
|
190
|
+
const active = await resolveActiveProfile();
|
|
191
|
+
if (!active) throw new Error("no active profile. Pass a profile name or use --all-profiles.");
|
|
192
|
+
return [active];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function targetDirFor(profile: string, agent: AnyAgent, dir?: string): string {
|
|
196
|
+
if (isRuntimeAgent(agent)) return runtimeDirFor(profile, agent);
|
|
197
|
+
const adapter = getAdapter(agent);
|
|
198
|
+
return resolve(dir ?? adapter?.configDir() ?? process.cwd());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function loadSkillContent(id: string): Promise<{ id: string; content: string } | null> {
|
|
202
|
+
try {
|
|
203
|
+
const dir = await resolveLocalSkill(id);
|
|
204
|
+
return { id, content: await readFile(join(dir, "SKILL.md"), "utf8") };
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function materializeExternalProfile(profileName: string, agent: Exclude<AnyAgent, RuntimeAgent>, dir?: string): Promise<InstallAction> {
|
|
211
|
+
const adapter = getAdapter(agent);
|
|
212
|
+
const targetDir = targetDirFor(profileName, agent, dir);
|
|
213
|
+
if (!adapter) return { profile: profileName, agent, targetDir, status: "failed", error: `unknown agent ${agent}` };
|
|
214
|
+
try {
|
|
215
|
+
const profile = await loadProfile(profileName);
|
|
216
|
+
await expandSkillWildcards(profile);
|
|
217
|
+
const skills = (await Promise.all(profile.skills.local.map((s) => loadSkillContent(s.id))))
|
|
218
|
+
.filter(Boolean) as { id: string; content: string }[];
|
|
219
|
+
const registry = isRuntimeAgent(agent) ? {} : await loadMcpRegistry("claude-code");
|
|
220
|
+
const mcps: Record<string, unknown> = {};
|
|
221
|
+
for (const m of profile.mcps) {
|
|
222
|
+
if (registry[m.id]) mcps[m.id] = registry[m.id];
|
|
223
|
+
}
|
|
224
|
+
adapter.writeSkills(skills, targetDir);
|
|
225
|
+
adapter.writeMcps(mcps, targetDir);
|
|
226
|
+
return { profile: profileName, agent, targetDir, status: "written" };
|
|
227
|
+
} catch (err) {
|
|
228
|
+
return {
|
|
229
|
+
profile: profileName,
|
|
230
|
+
agent,
|
|
231
|
+
targetDir,
|
|
232
|
+
status: "failed",
|
|
233
|
+
error: err instanceof Error ? err.message : String(err),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function materializeProfile(profileName: string, agent: AnyAgent, force: boolean, dir?: string): Promise<InstallAction> {
|
|
239
|
+
const targetDir = targetDirFor(profileName, agent, dir);
|
|
240
|
+
if (!isRuntimeAgent(agent)) return materializeExternalProfile(profileName, agent, dir);
|
|
241
|
+
try {
|
|
242
|
+
if (force) await rm(join(targetDir, ".cue-hash"), { force: true });
|
|
243
|
+
const profile = await loadProfile(profileName);
|
|
244
|
+
await expandSkillWildcards(profile);
|
|
245
|
+
const result = await prepareRuntime({
|
|
246
|
+
profile,
|
|
247
|
+
agent,
|
|
248
|
+
credentialsSource: agent === "claude-code"
|
|
249
|
+
? await resolveClaudeCredentialsSource({ healFromRuntime: false })
|
|
250
|
+
: undefined,
|
|
251
|
+
});
|
|
252
|
+
return { profile: profileName, agent, targetDir: result.runtimeDir, status: result.rebuilt ? "rebuilt" : "cached" };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return {
|
|
255
|
+
profile: profileName,
|
|
256
|
+
agent,
|
|
257
|
+
targetDir,
|
|
258
|
+
status: "failed",
|
|
259
|
+
error: err instanceof Error ? err.message : String(err),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function plannedActions(profiles: string[], agents: AnyAgent[], dir?: string): InstallAction[] {
|
|
265
|
+
return profiles.flatMap((profile) =>
|
|
266
|
+
agents.map((agent) => ({ profile, agent, targetDir: targetDirFor(profile, agent, dir), status: "planned" as const })),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function captureStdout(fn: () => Promise<number>): Promise<{ stdout: string; code: number }> {
|
|
271
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
272
|
+
let stdout = "";
|
|
273
|
+
(process.stdout as any).write = (chunk: string | Uint8Array) => {
|
|
274
|
+
stdout += String(chunk);
|
|
275
|
+
return true;
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
const code = await fn();
|
|
279
|
+
return { stdout, code };
|
|
280
|
+
} finally {
|
|
281
|
+
(process.stdout as any).write = orig;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function runCliStep(profile: string, dryRun: boolean, json: boolean): Promise<{ profile: string; code: number; stdout?: string }> {
|
|
286
|
+
const args = ["install", "--all", profile];
|
|
287
|
+
if (!dryRun) args.push("--yes");
|
|
288
|
+
if (json || dryRun) args.push("--json");
|
|
289
|
+
if (json || dryRun) {
|
|
290
|
+
const result = await captureStdout(() => cliRun(args));
|
|
291
|
+
return { profile, code: result.code, stdout: result.stdout };
|
|
292
|
+
}
|
|
293
|
+
return { profile, code: await cliRun(args) };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function parseJsonOrText(stdout: string | undefined): unknown {
|
|
297
|
+
if (!stdout) return undefined;
|
|
298
|
+
try { return JSON.parse(stdout); } catch { return { text: stdout }; }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function buildReport(profileName: string, agent: RuntimeAgent): Promise<InstallReport> {
|
|
302
|
+
const profile = await loadProfile(profileName);
|
|
303
|
+
await expandSkillWildcards(profile);
|
|
304
|
+
const registry = await loadMcpRegistry(agent);
|
|
305
|
+
const missingMcps = profile.mcps
|
|
306
|
+
.filter((m) => !m.agents || m.agents.includes(agent))
|
|
307
|
+
.map((m) => m.id)
|
|
308
|
+
.filter((id) => !registry[id]);
|
|
309
|
+
|
|
310
|
+
const oversizedSkills: InstallReport["oversizedSkills"] = [];
|
|
311
|
+
for (const skill of profile.skills.local) {
|
|
312
|
+
if (skill.agents && !skill.agents.includes(agent)) continue;
|
|
313
|
+
try {
|
|
314
|
+
const dir = await resolveLocalSkill(skill.id);
|
|
315
|
+
const bytes = statSync(join(dir, "SKILL.md")).size;
|
|
316
|
+
if (bytes > BYTE_CEILING) oversizedSkills.push({ id: skill.id, bytes });
|
|
317
|
+
} catch {
|
|
318
|
+
// validate/debug surface missing skills.
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const brokenRuntimeSkills: string[] = [];
|
|
323
|
+
const skillsDir = join(runtimeDirFor(profileName, agent), "skills");
|
|
324
|
+
try {
|
|
325
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
326
|
+
const path = join(skillsDir, entry);
|
|
327
|
+
try {
|
|
328
|
+
if (lstatSync(path).isSymbolicLink() && !existsSync(join(path, "SKILL.md"))) {
|
|
329
|
+
brokenRuntimeSkills.push(entry);
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
brokenRuntimeSkills.push(entry);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
// Runtime not materialized yet.
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return { profile: profileName, agent, missingMcps, oversizedSkills, brokenRuntimeSkills };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function doctorCmd(args: string[]): Promise<number> {
|
|
343
|
+
const parsed = parse(args);
|
|
344
|
+
if (!parsed) { usage(); return 0; }
|
|
345
|
+
let profiles: string[];
|
|
346
|
+
try { profiles = await resolveProfiles(parsed); }
|
|
347
|
+
catch (err) { process.stderr.write(`cue install doctor: ${(err as Error).message}\n`); return 1; }
|
|
348
|
+
const agents = parsed.agents.filter(isRuntimeAgent);
|
|
349
|
+
const reports = (await Promise.all(profiles.flatMap((profile) => agents.map((agent) => buildReport(profile, agent)))));
|
|
350
|
+
const failed = reports.some((r) => r.missingMcps.length || r.oversizedSkills.length || r.brokenRuntimeSkills.length);
|
|
351
|
+
if (parsed.json) {
|
|
352
|
+
process.stdout.write(JSON.stringify({ reports }, null, 2) + "\n");
|
|
353
|
+
return failed ? 1 : 0;
|
|
354
|
+
}
|
|
355
|
+
process.stdout.write(`\n ${bold("cue install doctor")}\n\n`);
|
|
356
|
+
for (const r of reports) {
|
|
357
|
+
const issues = r.missingMcps.length + r.oversizedSkills.length + r.brokenRuntimeSkills.length;
|
|
358
|
+
process.stdout.write(` ${issues ? yellow("warn") : green("ok")} ${r.profile} ${r.agent}\n`);
|
|
359
|
+
if (r.missingMcps.length) process.stdout.write(` missing MCPs: ${r.missingMcps.join(", ")}\n`);
|
|
360
|
+
if (r.oversizedSkills.length) process.stdout.write(` oversized skills: ${r.oversizedSkills.map((s) => `${s.id} (${s.bytes} bytes)`).join(", ")}\n`);
|
|
361
|
+
if (r.brokenRuntimeSkills.length) process.stdout.write(` broken runtime skills: ${r.brokenRuntimeSkills.join(", ")}\n`);
|
|
362
|
+
}
|
|
363
|
+
process.stdout.write("\n");
|
|
364
|
+
return failed ? 1 : 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function normalizeRepo(input: string): { owner: string; repo: string; cloneUrl: string; slug: string } {
|
|
368
|
+
const cleaned = input.trim().replace(/\.git$/, "");
|
|
369
|
+
const match = cleaned.match(/github\.com[:/](?<owner>[^/]+)\/(?<repo>[^/#?]+)/) ?? cleaned.match(/^(?<owner>[^/\s]+)\/(?<repo>[^/\s]+)$/);
|
|
370
|
+
if (!match?.groups) throw new Error("repo must be a GitHub URL or owner/repo");
|
|
371
|
+
const owner = match.groups.owner!;
|
|
372
|
+
const repo = match.groups.repo!;
|
|
373
|
+
const slug = `${owner}-${repo}`.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
374
|
+
return { owner, repo, slug, cloneUrl: `https://github.com/${owner}/${repo}.git` };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function parseRepoArgs(args: string[]): RepoArgs | null {
|
|
378
|
+
if (args.includes("-h") || args.includes("--help")) return null;
|
|
379
|
+
let repo: string | undefined;
|
|
380
|
+
let profile: string | undefined;
|
|
381
|
+
let category = "external";
|
|
382
|
+
let yes = false;
|
|
383
|
+
let dryRun = false;
|
|
384
|
+
let json = false;
|
|
385
|
+
let runSetup = false;
|
|
386
|
+
let force = false;
|
|
387
|
+
for (let i = 0; i < args.length; i++) {
|
|
388
|
+
const a = args[i]!;
|
|
389
|
+
if (a === repo) continue;
|
|
390
|
+
if (a === "--profile") profile = args[++i];
|
|
391
|
+
else if (a === "--category") category = args[++i] ?? "external";
|
|
392
|
+
else if (a === "--yes") yes = true;
|
|
393
|
+
else if (a === "--dry-run") dryRun = true;
|
|
394
|
+
else if (a === "--json") json = true;
|
|
395
|
+
else if (a === "--run-setup") runSetup = true;
|
|
396
|
+
else if (a === "--force") force = true;
|
|
397
|
+
else if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
398
|
+
else if (!repo) repo = a;
|
|
399
|
+
else throw new Error(`unexpected argument: ${a}`);
|
|
400
|
+
}
|
|
401
|
+
if (!repo) throw new Error("missing repo. Usage: cue install repo <github-url|owner/repo>");
|
|
402
|
+
return { repo, profile, category, yes, dryRun: dryRun || !yes, json, runSetup, force };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function findSkillDirs(root: string): string[] {
|
|
406
|
+
const out: string[] = [];
|
|
407
|
+
const ignore = new Set([".git", "node_modules", "dist", "coverage"]);
|
|
408
|
+
function walk(dir: string, depth: number): void {
|
|
409
|
+
if (depth > 5) return;
|
|
410
|
+
let entries: string[];
|
|
411
|
+
try { entries = readdirSync(dir); } catch { return; }
|
|
412
|
+
if (entries.includes("SKILL.md")) {
|
|
413
|
+
out.push(dir);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
if (ignore.has(entry)) continue;
|
|
418
|
+
const path = join(dir, entry);
|
|
419
|
+
try {
|
|
420
|
+
if (statSync(path).isDirectory()) walk(path, depth + 1);
|
|
421
|
+
} catch {
|
|
422
|
+
// skip
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
walk(root, 0);
|
|
427
|
+
return out.sort();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function profileYamlPath(profile: string): string {
|
|
431
|
+
return join(PROFILES_DIR, profile, "profile.yaml");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function addSkillsToProfile(profile: string, skillIds: string[]): Promise<void> {
|
|
435
|
+
const path = profileYamlPath(profile);
|
|
436
|
+
const raw = await readFile(path, "utf8");
|
|
437
|
+
const doc = (parseYaml(raw) ?? {}) as Record<string, any>;
|
|
438
|
+
doc.skills ??= {};
|
|
439
|
+
doc.skills.local ??= [];
|
|
440
|
+
const existing = new Set((doc.skills.local as unknown[]).map((x) => typeof x === "string" ? x : (x as any)?.id).filter(Boolean));
|
|
441
|
+
for (const id of skillIds) {
|
|
442
|
+
if (!existing.has(id)) doc.skills.local.push(id);
|
|
443
|
+
}
|
|
444
|
+
await writeFile(path, stringifyYaml(doc));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function repoCmd(args: string[]): Promise<number> {
|
|
448
|
+
let parsed: RepoArgs;
|
|
449
|
+
try {
|
|
450
|
+
const p = parseRepoArgs(args);
|
|
451
|
+
if (!p) { usage(); return 0; }
|
|
452
|
+
parsed = p;
|
|
453
|
+
} catch (err) {
|
|
454
|
+
process.stderr.write(`cue install repo: ${(err as Error).message}\n`);
|
|
455
|
+
return 1;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const repo = normalizeRepo(parsed.repo);
|
|
459
|
+
const cacheDir = join(configDir(), "repo-cache", repo.slug);
|
|
460
|
+
const sourceDesc = `${repo.owner}/${repo.repo}`;
|
|
461
|
+
const skillIds: string[] = [];
|
|
462
|
+
|
|
463
|
+
if (!parsed.dryRun) {
|
|
464
|
+
await mkdir(dirname(cacheDir), { recursive: true });
|
|
465
|
+
if (!existsSync(cacheDir)) {
|
|
466
|
+
const clone = spawnSync("git", ["clone", "--single-branch", "--depth", "1", repo.cloneUrl, cacheDir], { stdio: "inherit" });
|
|
467
|
+
if (clone.status !== 0) return clone.status ?? 1;
|
|
468
|
+
} else if (parsed.force) {
|
|
469
|
+
const pull = spawnSync("git", ["-C", cacheDir, "pull", "--ff-only"], { stdio: "inherit" });
|
|
470
|
+
if (pull.status !== 0) return pull.status ?? 1;
|
|
471
|
+
}
|
|
472
|
+
if (parsed.runSetup) {
|
|
473
|
+
const setup = join(cacheDir, "setup");
|
|
474
|
+
if (!existsSync(setup)) {
|
|
475
|
+
process.stderr.write(`cue install repo: no setup script at ${setup}\n`);
|
|
476
|
+
return 1;
|
|
477
|
+
}
|
|
478
|
+
const res = spawnSync("./setup", { cwd: cacheDir, stdio: "inherit" });
|
|
479
|
+
if (res.status !== 0) return res.status ?? 1;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const scannedRoot = existsSync(cacheDir) ? cacheDir : process.cwd();
|
|
484
|
+
const skillDirs = parsed.dryRun && !existsSync(cacheDir) ? [] : findSkillDirs(scannedRoot);
|
|
485
|
+
if (!parsed.dryRun) {
|
|
486
|
+
await mkdir(join(SKILLS_ROOT, parsed.category), { recursive: true });
|
|
487
|
+
for (const dir of skillDirs) {
|
|
488
|
+
const slug = basename(dir).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
489
|
+
if (!slug) continue;
|
|
490
|
+
const dest = join(SKILLS_ROOT, parsed.category, slug);
|
|
491
|
+
if (!existsSync(dest)) {
|
|
492
|
+
const rel = relative(dirname(dest), dir);
|
|
493
|
+
await symlink(rel, dest);
|
|
494
|
+
}
|
|
495
|
+
await writeFile(join(dest, ".source"), `${sourceDesc}::${relative(cacheDir, dir)}\n`);
|
|
496
|
+
skillIds.push(`${parsed.category}/${slug}`);
|
|
497
|
+
}
|
|
498
|
+
if (parsed.profile && skillIds.length > 0) await addSkillsToProfile(parsed.profile, skillIds);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const out = {
|
|
502
|
+
dryRun: parsed.dryRun,
|
|
503
|
+
repo: sourceDesc,
|
|
504
|
+
cacheDir,
|
|
505
|
+
category: parsed.category,
|
|
506
|
+
setup: parsed.runSetup ? "will-run" : "skipped",
|
|
507
|
+
discoveredSkills: skillDirs.map((d) => relative(scannedRoot, d)),
|
|
508
|
+
registeredSkills: skillIds,
|
|
509
|
+
profile: parsed.profile,
|
|
510
|
+
};
|
|
511
|
+
if (parsed.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
512
|
+
else {
|
|
513
|
+
process.stdout.write(`\n ${bold("cue install repo")} ${parsed.dryRun ? dim("(dry-run; pass --yes to execute)") : ""}\n`);
|
|
514
|
+
process.stdout.write(` repo: ${sourceDesc}\n`);
|
|
515
|
+
process.stdout.write(` cache: ${cacheDir}\n`);
|
|
516
|
+
process.stdout.write(` category: ${parsed.category}\n`);
|
|
517
|
+
process.stdout.write(` setup: ${parsed.runSetup ? "enabled" : "skipped"}\n`);
|
|
518
|
+
process.stdout.write(` skills: ${parsed.dryRun && !existsSync(cacheDir) ? "unknown until clone" : String(skillDirs.length)}\n`);
|
|
519
|
+
if (parsed.profile) process.stdout.write(` profile: ${parsed.profile}\n`);
|
|
520
|
+
process.stdout.write("\n");
|
|
521
|
+
}
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function runMain(args: string[]): Promise<number> {
|
|
526
|
+
let parsed: ParsedArgs;
|
|
527
|
+
try {
|
|
528
|
+
const p = parse(args);
|
|
529
|
+
if (!p) { usage(); return 0; }
|
|
530
|
+
parsed = p;
|
|
531
|
+
} catch (err) {
|
|
532
|
+
process.stderr.write(`cue install: ${(err as Error).message}\n`);
|
|
533
|
+
return 1;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let profiles: string[];
|
|
537
|
+
try { profiles = await resolveProfiles(parsed); }
|
|
538
|
+
catch (err) { process.stderr.write(`cue install: ${(err as Error).message}\n`); return 1; }
|
|
539
|
+
|
|
540
|
+
const projectAgents = parsed.agents.filter((a) => !isRuntimeAgent(a));
|
|
541
|
+
if (!parsed.dryRun && profiles.length > 1 && projectAgents.length > 0) {
|
|
542
|
+
process.stderr.write("cue install: refusing to write project/global agent files for multiple profiles in one target dir. Use one profile at a time.\n");
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const actions = parsed.dryRun
|
|
547
|
+
? plannedActions(profiles, parsed.agents, parsed.dir)
|
|
548
|
+
: await Promise.all(profiles.flatMap((profile) =>
|
|
549
|
+
parsed.agents.map((agent) => materializeProfile(profile, agent, parsed.force, parsed.dir)),
|
|
550
|
+
));
|
|
551
|
+
|
|
552
|
+
const cliResults = parsed.withClis
|
|
553
|
+
? await Promise.all(profiles.map((profile) => runCliStep(profile, parsed.dryRun, parsed.json)))
|
|
554
|
+
: [];
|
|
555
|
+
const reports = await Promise.all(profiles.flatMap((profile) =>
|
|
556
|
+
parsed.agents.filter(isRuntimeAgent).map((agent) => buildReport(profile, agent)),
|
|
557
|
+
));
|
|
558
|
+
|
|
559
|
+
if (parsed.json) {
|
|
560
|
+
process.stdout.write(JSON.stringify({
|
|
561
|
+
dryRun: parsed.dryRun,
|
|
562
|
+
profiles,
|
|
563
|
+
agents: parsed.agents,
|
|
564
|
+
actions,
|
|
565
|
+
cliResults: cliResults.map((r) => ({ profile: r.profile, code: r.code, plan: parseJsonOrText(r.stdout) })),
|
|
566
|
+
reports,
|
|
567
|
+
}, null, 2) + "\n");
|
|
568
|
+
return actions.some((a) => a.status === "failed") || cliResults.some((r) => r.code !== 0) ? 1 : 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
process.stdout.write(`\n ${bold("cue install")} ${parsed.dryRun ? dim("(dry-run; pass --yes to execute)") : ""}\n`);
|
|
572
|
+
process.stdout.write(` profiles: ${profiles.join(", ")}\n`);
|
|
573
|
+
process.stdout.write(` agents: ${parsed.agents.join(", ")}\n\n`);
|
|
574
|
+
for (const action of actions) {
|
|
575
|
+
const status = action.status === "planned" ? yellow("plan")
|
|
576
|
+
: action.status === "failed" ? yellow("failed")
|
|
577
|
+
: green(action.status);
|
|
578
|
+
process.stdout.write(` ${status.padEnd(16)} ${action.profile.padEnd(20)} ${action.agent.padEnd(11)} ${dim(action.targetDir)}\n`);
|
|
579
|
+
if (action.error) process.stdout.write(` ${yellow(action.error)}\n`);
|
|
580
|
+
}
|
|
581
|
+
if (parsed.withClis) {
|
|
582
|
+
process.stdout.write(`\n ${bold("CLI installers")}\n`);
|
|
583
|
+
for (const result of cliResults) {
|
|
584
|
+
process.stdout.write(` ${result.code === 0 ? green("ok") : yellow("failed")} ${result.profile}\n`);
|
|
585
|
+
if (result.stdout && parsed.dryRun) {
|
|
586
|
+
const plan = parseJsonOrText(result.stdout) as { plans?: unknown[]; text?: string };
|
|
587
|
+
process.stdout.write(` ${plan.plans?.length ?? 0} missing CLI plan(s)\n`);
|
|
588
|
+
} else if (result.stdout) process.stdout.write(result.stdout);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const warnings = reports.reduce((n, r) => n + r.missingMcps.length + r.oversizedSkills.length + r.brokenRuntimeSkills.length, 0);
|
|
592
|
+
process.stdout.write(`\n ${bold("Report")} ${warnings ? yellow(`${warnings} warning(s)`) : green("clean")}\n`);
|
|
593
|
+
if (parsed.dryRun) {
|
|
594
|
+
process.stdout.write(`\n Execute: ${bold("cue install " + (parsed.allProfiles ? "--all-profiles " : profiles.join(" ") + " ") + "--yes")}\n\n`);
|
|
595
|
+
} else {
|
|
596
|
+
process.stdout.write(`\n ${green("install complete")}\n\n`);
|
|
597
|
+
}
|
|
598
|
+
return actions.some((a) => a.status === "failed") || cliResults.some((r) => r.code !== 0) ? 1 : 0;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export async function run(args: string[]): Promise<number> {
|
|
602
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
603
|
+
usage();
|
|
604
|
+
return 0;
|
|
605
|
+
}
|
|
606
|
+
const sub = args[0];
|
|
607
|
+
if (sub === "repo") return repoCmd(args.slice(1));
|
|
608
|
+
if (sub === "doctor") return doctorCmd(args.slice(1));
|
|
609
|
+
return runMain(args);
|
|
610
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* file is being edited concurrently.
|
|
9
9
|
*/
|
|
10
10
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
11
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
11
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
@@ -70,6 +70,38 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch --dry-run exec handoff", () => {
|
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue launch help passthrough", () => {
|
|
74
|
+
let tmp: string;
|
|
75
|
+
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
tmp = await mkdtemp(join(tmpdir(), "cue-help-"));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(async () => {
|
|
81
|
+
await rm(tmp, { recursive: true, force: true });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("agent --help bypasses runtime materialization", async () => {
|
|
85
|
+
const binDir = join(tmp, "bin");
|
|
86
|
+
await mkdir(binDir);
|
|
87
|
+
const fakeClaude = join(binDir, "claude");
|
|
88
|
+
await writeFile(fakeClaude, "#!/usr/bin/env sh\necho fake-claude-help \"$@\"\nexit 42\n");
|
|
89
|
+
await chmod(fakeClaude, 0o755);
|
|
90
|
+
|
|
91
|
+
const blockedXdg = join(tmp, "xdg-file");
|
|
92
|
+
await writeFile(blockedXdg, "not a directory\n");
|
|
93
|
+
const r = cue(["launch", "claude", "--help"], {
|
|
94
|
+
PATH: `${binDir}:${process.env.PATH ?? ""}`,
|
|
95
|
+
XDG_CONFIG_HOME: blockedXdg,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(r.status).toBe(42);
|
|
99
|
+
expect(r.stdout).toContain("fake-claude-help --help");
|
|
100
|
+
expect(r.stderr).not.toContain("materialize");
|
|
101
|
+
expect(r.stderr).not.toContain("ENOTDIR");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
73
105
|
describe.skipIf(!BUN_SPAWNABLE)("cue launch recursion guard", () => {
|
|
74
106
|
test("CUE_LAUNCHING=1 aborts with exit 2 (shim recursion)", () => {
|
|
75
107
|
// Must NOT use the cue() helper — it strips CUE_LAUNCHING. Spawn directly
|