cue-ai 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -3
- package/README.md +154 -394
- 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
|
@@ -38,18 +38,23 @@ function cue(args: string[], opts: { cwd?: string; env?: Record<string, string>
|
|
|
38
38
|
|
|
39
39
|
describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
40
40
|
let tmpDir: string;
|
|
41
|
+
let oldXdgConfigHome: string | undefined;
|
|
41
42
|
|
|
42
43
|
beforeEach(async () => {
|
|
43
44
|
tmpDir = await mkdtemp(join(tmpdir(), "cue-e2e-launch-"));
|
|
45
|
+
oldXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
46
|
+
process.env.XDG_CONFIG_HOME = join(tmpDir, "xdg");
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
afterEach(async () => {
|
|
50
|
+
if (oldXdgConfigHome === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
51
|
+
else process.env.XDG_CONFIG_HOME = oldXdgConfigHome;
|
|
47
52
|
await rm(tmpDir, { recursive: true, force: true });
|
|
48
53
|
});
|
|
49
54
|
|
|
50
|
-
test("launch --rematerialize with .cue
|
|
51
|
-
// Create a .cue
|
|
52
|
-
await writeFile(join(tmpDir, ".cue
|
|
55
|
+
test("launch --rematerialize with .cue.profile resolves and builds runtime", async () => {
|
|
56
|
+
// Create a .cue.profile pointing to a real profile
|
|
57
|
+
await writeFile(join(tmpDir, ".cue.profile"), "caveman-quick\n");
|
|
53
58
|
|
|
54
59
|
const res = cue(["launch", "claude", "--rematerialize"], { cwd: tmpDir });
|
|
55
60
|
|
|
@@ -67,7 +72,7 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
|
67
72
|
});
|
|
68
73
|
|
|
69
74
|
test("launch --rematerialize second call is cache hit (rebuilt=false)", async () => {
|
|
70
|
-
await writeFile(join(tmpDir, ".cue
|
|
75
|
+
await writeFile(join(tmpDir, ".cue.profile"), "core\n");
|
|
71
76
|
|
|
72
77
|
const first = cue(["launch", "claude", "--rematerialize"], { cwd: tmpDir });
|
|
73
78
|
expect(first.status).toBe(0);
|
|
@@ -83,12 +88,12 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
|
83
88
|
expect(secondJson.profile).toBe("core");
|
|
84
89
|
});
|
|
85
90
|
|
|
86
|
-
test("launch resolves profile from .cue
|
|
87
|
-
// Create a subdirectory and put .cue
|
|
91
|
+
test("launch resolves profile from .cue.profile in parent directory", async () => {
|
|
92
|
+
// Create a subdirectory and put .cue.profile in parent
|
|
88
93
|
const { mkdir } = await import("node:fs/promises");
|
|
89
94
|
const subDir = join(tmpDir, "src", "lib");
|
|
90
95
|
await mkdir(subDir, { recursive: true });
|
|
91
|
-
await writeFile(join(tmpDir, ".cue
|
|
96
|
+
await writeFile(join(tmpDir, ".cue.profile"), "rust\n");
|
|
92
97
|
|
|
93
98
|
const res = cue(["launch", "claude", "--rematerialize"], { cwd: subDir });
|
|
94
99
|
expect(res.status).toBe(0);
|
|
@@ -97,7 +102,7 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
|
97
102
|
});
|
|
98
103
|
|
|
99
104
|
test("launch produces CLAUDE.md with profile stamp in runtime dir", async () => {
|
|
100
|
-
await writeFile(join(tmpDir, ".cue
|
|
105
|
+
await writeFile(join(tmpDir, ".cue.profile"), "backend\n");
|
|
101
106
|
|
|
102
107
|
const res = cue(["launch", "claude", "--rematerialize"], { cwd: tmpDir });
|
|
103
108
|
expect(res.status).toBe(0);
|
|
@@ -109,7 +114,7 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
|
109
114
|
});
|
|
110
115
|
|
|
111
116
|
test("launch produces settings.json with MCPs and plugins", async () => {
|
|
112
|
-
await writeFile(join(tmpDir, ".cue
|
|
117
|
+
await writeFile(join(tmpDir, ".cue.profile"), "backend\n");
|
|
113
118
|
|
|
114
119
|
const res = cue(["launch", "claude", "--rematerialize"], { cwd: tmpDir });
|
|
115
120
|
expect(res.status).toBe(0);
|
|
@@ -121,7 +126,7 @@ describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
|
121
126
|
});
|
|
122
127
|
|
|
123
128
|
test("launch creates skills/ symlinks in runtime dir", async () => {
|
|
124
|
-
await writeFile(join(tmpDir, ".cue
|
|
129
|
+
await writeFile(join(tmpDir, ".cue.profile"), "backend\n");
|
|
125
130
|
|
|
126
131
|
const res = cue(["launch", "claude", "--rematerialize"], { cwd: tmpDir });
|
|
127
132
|
expect(res.status).toBe(0);
|
package/src/commands/launch.ts
CHANGED
|
@@ -29,12 +29,13 @@ import {
|
|
|
29
29
|
import { loadProfile, listProfiles, listFeaturedProfiles, parseProfileSelector } from "../lib/profile-loader";
|
|
30
30
|
import { resolveProfileForCwd } from "../lib/cwd-resolver";
|
|
31
31
|
import { DIVIDER_PREFIX, runPicker, type PickerOption, type ProfileTally } from "../lib/picker";
|
|
32
|
-
import { materializeRuntime
|
|
33
|
-
import { resolveLocalSkill
|
|
32
|
+
import { materializeRuntime } from "../lib/runtime-materializer";
|
|
33
|
+
import { resolveLocalSkill } from "../lib/resolver-local";
|
|
34
|
+
import { expandSkillWildcards, loadMcpRegistry, resolveClaudeCredentialsSource as resolveSharedClaudeCredentialsSource } from "../lib/runtime-install";
|
|
34
35
|
import { detectKittyTerminal, kittyPlaceholderLabel, transmitKittyImage } from "../lib/kitty-image";
|
|
35
36
|
import { computeStats } from "../lib/analytics";
|
|
36
37
|
import { detectProfileV2, type DetectionResultV2 } from "../lib/auto-detect";
|
|
37
|
-
import { detectCompanions, type CompanionSignal } from "../lib/companion-detect";
|
|
38
|
+
import { detectCompanions, serviceCompanions, type CompanionSignal } from "../lib/companion-detect";
|
|
38
39
|
import type { ResolvedProfile } from "../../profiles/_types";
|
|
39
40
|
import type { ProfileAffinity, UniversalSuggestion } from "../lib/pair-suggestions";
|
|
40
41
|
import { hasWorkspaces, getActiveWorkspace, computeOverrides, resolveWorkspaceForCwd } from "../lib/workspaces";
|
|
@@ -144,6 +145,18 @@ function execAgent(bin: string, args: string[], env: NodeJS.ProcessEnv): Promise
|
|
|
144
145
|
});
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
function isAgentHelpPassthrough(parsed: ParsedArgs): boolean {
|
|
149
|
+
return (
|
|
150
|
+
!parsed.override &&
|
|
151
|
+
!parsed.forcePick &&
|
|
152
|
+
!parsed.dryRun &&
|
|
153
|
+
!parsed.rematerialize &&
|
|
154
|
+
parsed.subset === null &&
|
|
155
|
+
parsed.passthrough.length === 1 &&
|
|
156
|
+
(parsed.passthrough[0] === "--help" || parsed.passthrough[0] === "-h")
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
export interface TmuxAnnounceExtras {
|
|
148
161
|
/** Token-overhead summary: dot = 🟢/🟡/🟠/🔴, size = "8K". Both optional. */
|
|
149
162
|
overhead?: { dot: string; size: string };
|
|
@@ -281,16 +294,7 @@ function announceTmuxProfile(
|
|
|
281
294
|
* Used by both the launch hot path and the picker `details` callback so the
|
|
282
295
|
* shown summary matches what materializeRuntime will actually link.
|
|
283
296
|
*/
|
|
284
|
-
|
|
285
|
-
if (!profile.skills.local.some((s) => s.id === "*/*")) return;
|
|
286
|
-
const allIds = await listAllSkillIds();
|
|
287
|
-
const wildcard = profile.skills.local.find((s) => s.id === "*/*")!;
|
|
288
|
-
const existing = new Set(profile.skills.local.filter((s) => s.id !== "*/*").map((s) => s.id));
|
|
289
|
-
profile.skills.local = [
|
|
290
|
-
...profile.skills.local.filter((s) => s.id !== "*/*"),
|
|
291
|
-
...allIds.filter((id) => !existing.has(id)).map((id) => ({ ...wildcard, id })),
|
|
292
|
-
];
|
|
293
|
-
}
|
|
297
|
+
const expandWildcards = expandSkillWildcards;
|
|
294
298
|
|
|
295
299
|
/**
|
|
296
300
|
* Compact human-readable summary of what a profile would load. Each returned
|
|
@@ -1061,52 +1065,6 @@ async function listProfileOptions(pinnedProfile?: string): Promise<PickerOption[
|
|
|
1061
1065
|
return buildPickerSections(defaultOpt, sorted, recent, 3, Date.now(), suggested, featured);
|
|
1062
1066
|
}
|
|
1063
1067
|
|
|
1064
|
-
async function loadMcpRegistry(agent: "claude-code" | "codex"): Promise<Record<string, McpServerConfig>> {
|
|
1065
|
-
const root = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(
|
|
1066
|
-
new URL(import.meta.url).pathname,
|
|
1067
|
-
"..",
|
|
1068
|
-
"..",
|
|
1069
|
-
"..",
|
|
1070
|
-
);
|
|
1071
|
-
// Files to merge, in priority order. The master `claude.sanitized.json` wins
|
|
1072
|
-
// on key collisions; `claude_runtime.sanitized.json` is the live snapshot
|
|
1073
|
-
// captured from the user's actual `~/.claude.json` (covers servers
|
|
1074
|
-
// registered at runtime but not yet promoted to the master registry).
|
|
1075
|
-
// Without this merge, profiles like `marketing` that reference
|
|
1076
|
-
// `reddit`/`google-ads-mcp`/`meta-ads`/`Higgsfield` (runtime-only entries)
|
|
1077
|
-
// would silently drop those MCPs at materialize time.
|
|
1078
|
-
const files = agent === "claude-code"
|
|
1079
|
-
? ["claude_runtime.sanitized.json", "claude.sanitized.json"]
|
|
1080
|
-
: ["codex.sanitized.json"];
|
|
1081
|
-
|
|
1082
|
-
const merged: Record<string, McpServerConfig> = {};
|
|
1083
|
-
for (const file of files) {
|
|
1084
|
-
const path = join(root, "resources", "mcps", "configs", file);
|
|
1085
|
-
try {
|
|
1086
|
-
const text = await readFile(path, "utf8");
|
|
1087
|
-
const raw = JSON.parse(text) as { servers?: Record<string, McpServerConfig> };
|
|
1088
|
-
for (const [k, v] of Object.entries(raw.servers ?? {})) {
|
|
1089
|
-
// First file wins (claude_runtime first, then claude master).
|
|
1090
|
-
// We want master to win, so only set if not already present.
|
|
1091
|
-
if (!(k in merged)) merged[k] = v;
|
|
1092
|
-
}
|
|
1093
|
-
} catch { /* file missing — skip */ }
|
|
1094
|
-
}
|
|
1095
|
-
// Second pass: let the master registry override the runtime snapshot
|
|
1096
|
-
// (master is the curated source of truth; runtime is just a fallback).
|
|
1097
|
-
const masterPath = join(root, "resources", "mcps", "configs",
|
|
1098
|
-
agent === "claude-code" ? "claude.sanitized.json" : "codex.sanitized.json");
|
|
1099
|
-
try {
|
|
1100
|
-
const text = await readFile(masterPath, "utf8");
|
|
1101
|
-
const raw = JSON.parse(text) as { servers?: Record<string, McpServerConfig> };
|
|
1102
|
-
for (const [k, v] of Object.entries(raw.servers ?? {})) {
|
|
1103
|
-
merged[k] = v;
|
|
1104
|
-
}
|
|
1105
|
-
} catch (err) { debug("launch:master-config", err); /* keep runtime fallbacks */ }
|
|
1106
|
-
|
|
1107
|
-
return merged;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
1068
|
async function readSharedClaudeMd(profile?: { name: string; inheritanceChain?: string[] }): Promise<string> {
|
|
1111
1069
|
const root = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(
|
|
1112
1070
|
new URL(import.meta.url).pathname, "..", "..", "..",
|
|
@@ -1147,15 +1105,17 @@ async function readSharedClaudeMd(profile?: { name: string; inheritanceChain?: s
|
|
|
1147
1105
|
async function buildUserClaudeMd(profile: ResolvedProfile, agent: "claude-code" | "codex"): Promise<string> {
|
|
1148
1106
|
let content = await readSharedClaudeMd(profile) + await readUserClaudeMd(agent);
|
|
1149
1107
|
|
|
1150
|
-
// First-time profile suggestion: if no .cue
|
|
1151
|
-
const cueProfilePath = join(process.cwd(), ".cue
|
|
1108
|
+
// First-time profile suggestion: if no .cue.profile in cwd, inject marker
|
|
1109
|
+
const cueProfilePath = join(process.cwd(), ".cue.profile");
|
|
1152
1110
|
if (!existsSync(cueProfilePath)) {
|
|
1153
1111
|
content += "\n<!-- cue:first-time-suggest -->\n" +
|
|
1154
1112
|
"## ⚡ First-Time Setup\n\n" +
|
|
1155
|
-
"No `.cue
|
|
1156
|
-
"
|
|
1157
|
-
"
|
|
1158
|
-
"
|
|
1113
|
+
"No `.cue.profile` is pinned to this directory. Before answering the user's first message, " +
|
|
1114
|
+
"summon the right profile into THIS session — no restart. Invoke the `meta/profile-summon` " +
|
|
1115
|
+
"skill, or run `cue summon` (auto-detects from the repo). It soft-loads the profile's persona " +
|
|
1116
|
+
"and skill playbooks inline, pins `.cue.profile`, and prints `claude --continue` for the MCP / " +
|
|
1117
|
+
"/slash-command tail (which needs a warm re-exec). Propose the detected profile in 3-4 lines, " +
|
|
1118
|
+
"apply on the user's OK, then proceed with their request.\n\n" +
|
|
1159
1119
|
"Available profiles:\n```\n" +
|
|
1160
1120
|
(await getProfileListForStamp()) +
|
|
1161
1121
|
"```\n\n";
|
|
@@ -1238,56 +1198,31 @@ async function findRealBinary(name: string): Promise<string | null> {
|
|
|
1238
1198
|
* accountUuid and copies the freshest one back to source.
|
|
1239
1199
|
*/
|
|
1240
1200
|
async function resolveClaudeCredentialsSource(): Promise<string> {
|
|
1241
|
-
|
|
1242
|
-
// Heal source from freshest sibling runtime (if any). Silent best-effort.
|
|
1243
|
-
try {
|
|
1244
|
-
const { syncFreshestToSource } = await import("../lib/credentials-sync");
|
|
1245
|
-
const runtimeRoot = join(configDir(), "runtime");
|
|
1246
|
-
const result = await syncFreshestToSource(picked, runtimeRoot);
|
|
1247
|
-
if (result.synced) {
|
|
1248
|
-
// Tiny breadcrumb so users can see when the heal kicked in. Stays on
|
|
1249
|
-
// stderr so it doesn't pollute pipelines or `claude --print` output.
|
|
1250
|
-
process.stderr.write(
|
|
1251
|
-
`▸ cue: refreshed source credentials from a sibling runtime (rotated refresh-token healed)\n`,
|
|
1252
|
-
);
|
|
1253
|
-
}
|
|
1254
|
-
} catch (err) { debug("launch:runtime-heal", err); /* best-effort — never blocks launch */ }
|
|
1255
|
-
return picked;
|
|
1201
|
+
return resolveSharedClaudeCredentialsSource({ healFromRuntime: true });
|
|
1256
1202
|
}
|
|
1257
1203
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1204
|
+
/**
|
|
1205
|
+
* Write the runtime's login-fresh `.credentials.json` back to the account
|
|
1206
|
+
* dir that owns it (matched by accountUuid). Runs (a) before materialization
|
|
1207
|
+
* — so the account-identity guard can't destroy the only live copy of the
|
|
1208
|
+
* OTHER account's rotated token when two authmux accounts alternate on one
|
|
1209
|
+
* profile — and (b) after the agent exits, so a `/login` done inside the
|
|
1210
|
+
* session lands in the account's CLAUDE_CONFIG_DIR immediately instead of
|
|
1211
|
+
* waiting for that account's next launch. Best-effort: never blocks launch.
|
|
1212
|
+
*/
|
|
1213
|
+
async function rescueRuntimeCredsToOwner(profileName: string): Promise<void> {
|
|
1264
1214
|
try {
|
|
1265
|
-
const {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1272
|
-
const profiles = parsed?.data?.profiles ?? [];
|
|
1273
|
-
const withMtime = profiles
|
|
1274
|
-
.map((p) => {
|
|
1275
|
-
const credsPath = join(p.configDir, ".credentials.json");
|
|
1276
|
-
let mtime = 0;
|
|
1277
|
-
try { mtime = statSync(credsPath).mtimeMs; } catch { /* missing */ }
|
|
1278
|
-
return { ...p, mtime };
|
|
1279
|
-
})
|
|
1280
|
-
.filter((p) => p.mtime > 0)
|
|
1281
|
-
.sort((a, b) => b.mtime - a.mtime);
|
|
1282
|
-
const pick = withMtime[0];
|
|
1283
|
-
if (pick) {
|
|
1284
|
-
process.stderr.write(`▸ cue: inheriting auth from authmux profile "${pick.name}"\n`);
|
|
1285
|
-
return pick.configDir;
|
|
1286
|
-
}
|
|
1215
|
+
const { listKnownAccountDirs, rescueRuntimeCredentials } = await import("../lib/credentials-sync");
|
|
1216
|
+
// basename() pins the path inside the runtime tree — this helper WRITES
|
|
1217
|
+
// token files, so a profile name with a path separator must not escape.
|
|
1218
|
+
const runtimeClaudeDir = join(configDir(), "runtime", basename(profileName), "claude");
|
|
1219
|
+
const result = await rescueRuntimeCredentials(runtimeClaudeDir, await listKnownAccountDirs(homedir()));
|
|
1220
|
+
if (result.rescued) {
|
|
1221
|
+
process.stderr.write(`▸ cue: wrote login-fresh credentials back to ${result.to}\n`);
|
|
1287
1222
|
}
|
|
1288
|
-
} catch {
|
|
1289
|
-
|
|
1290
|
-
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
debug("launch:cred-rescue", err);
|
|
1225
|
+
}
|
|
1291
1226
|
}
|
|
1292
1227
|
|
|
1293
1228
|
// ---------------------------------------------------------------------------
|
|
@@ -1308,6 +1243,16 @@ export async function run(args: string[]): Promise<number> {
|
|
|
1308
1243
|
process.stderr.write("cue launch: missing agent (use 'claude' or 'codex')\n");
|
|
1309
1244
|
return 1;
|
|
1310
1245
|
}
|
|
1246
|
+
if (isAgentHelpPassthrough(parsed)) {
|
|
1247
|
+
const realBin = await findRealBinary(parsed.agent);
|
|
1248
|
+
if (!realBin) {
|
|
1249
|
+
process.stderr.write(
|
|
1250
|
+
`cue launch: couldn't find the real '${parsed.agent}' binary on PATH=${process.env.PATH}\n`,
|
|
1251
|
+
);
|
|
1252
|
+
return 127;
|
|
1253
|
+
}
|
|
1254
|
+
return execAgent(realBin, parsed.passthrough, process.env);
|
|
1255
|
+
}
|
|
1311
1256
|
const agentKind = parsed.agent === "claude" ? "claude-code" : "codex";
|
|
1312
1257
|
|
|
1313
1258
|
// Resolve profile.
|
|
@@ -1405,16 +1350,19 @@ export async function run(args: string[]): Promise<number> {
|
|
|
1405
1350
|
// with what the directory actually looks like (e.g. picking medusa-next
|
|
1406
1351
|
// in a vite.config.ts project).
|
|
1407
1352
|
let detected: ReadonlyArray<{ name: string; reasons: string[]; confidence: number }> = [];
|
|
1353
|
+
let rawDetections: DetectionResultV2[] = [];
|
|
1408
1354
|
try {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
.map((d) => ({ name: d.profile, reasons: d.reasons, confidence: d.confidence }));
|
|
1355
|
+
rawDetections = detectProfileV2(cwd).filter((d) => knownProfileNames.has(d.profile));
|
|
1356
|
+
detected = rawDetections.map((d) => ({ name: d.profile, reasons: d.reasons, confidence: d.confidence }));
|
|
1412
1357
|
} catch (err) { debug("launch:autodetect", err); }
|
|
1413
1358
|
// Content-aware combine companions: scan the cwd for asset/draft/brand
|
|
1414
|
-
// signals and feed matching profiles into the combine multiselect
|
|
1359
|
+
// signals and feed matching profiles into the combine multiselect — plus
|
|
1360
|
+
// dep-detected service profiles (stripe, @aws-sdk/*, …), which join as
|
|
1361
|
+
// pre-checked rows (see serviceCompanions).
|
|
1415
1362
|
let companions: CompanionSignal[] = [];
|
|
1416
1363
|
try {
|
|
1417
1364
|
companions = detectCompanions({ cwd, knownProfiles: knownProfileNames, brands: listPostizzBrands() });
|
|
1365
|
+
companions = companions.concat(serviceCompanions(rawDetections, knownProfileNames));
|
|
1418
1366
|
} catch (err) { debug("launch:companions", err); }
|
|
1419
1367
|
// Cross-profile combine suggestions offered under every primary: the curated
|
|
1420
1368
|
// `_featured.yaml` set (improver, secops, builder, …) plus the profiles the
|
|
@@ -1635,6 +1583,11 @@ export async function run(args: string[]): Promise<number> {
|
|
|
1635
1583
|
}
|
|
1636
1584
|
}
|
|
1637
1585
|
|
|
1586
|
+
// Rescue-before-wipe: if this runtime's credentials belong to a different
|
|
1587
|
+
// account than credentialsSource, the materializer's identity guard is
|
|
1588
|
+
// about to discard them — return them to their owning account dir first.
|
|
1589
|
+
if (agentKind === "claude-code") await rescueRuntimeCredsToOwner(profileName);
|
|
1590
|
+
|
|
1638
1591
|
const runtime = await materializeRuntime({
|
|
1639
1592
|
profile: await applyWorkspaceOverrides(profile),
|
|
1640
1593
|
agent: agentKind,
|
|
@@ -1918,5 +1871,9 @@ export async function run(args: string[]): Promise<number> {
|
|
|
1918
1871
|
health: healthBadge,
|
|
1919
1872
|
});
|
|
1920
1873
|
|
|
1921
|
-
|
|
1874
|
+
const exitCode = await execAgent(realBin, parsed.passthrough, childEnv);
|
|
1875
|
+
// Persist any /login done inside the session to its account dir now —
|
|
1876
|
+
// don't leave the only live rotated token stranded in the shared runtime.
|
|
1877
|
+
if (agentKind === "claude-code") await rescueRuntimeCredsToOwner(profileName);
|
|
1878
|
+
return exitCode;
|
|
1922
1879
|
}
|
|
@@ -50,7 +50,7 @@ Agents: ${AGENT_IDS.join(", ")}
|
|
|
50
50
|
|
|
51
51
|
Flags:
|
|
52
52
|
--dir <path> Target directory (default: cwd or agent's config dir)
|
|
53
|
-
--profile <name> Use specific profile (default: resolved from .cue
|
|
53
|
+
--profile <name> Use specific profile (default: resolved from .cue.profile)
|
|
54
54
|
--all Materialize for all agents listed in the profile
|
|
55
55
|
--dry-run Show what would be written without writing
|
|
56
56
|
|
|
@@ -89,7 +89,7 @@ Examples:
|
|
|
89
89
|
if (!name) throw new Error("no active profile");
|
|
90
90
|
profile = await loadProfile(name);
|
|
91
91
|
} catch {
|
|
92
|
-
process.stderr.write("No active profile. Pin one with `echo <name> > .cue
|
|
92
|
+
process.stderr.write("No active profile. Pin one with `echo <name> > .cue.profile`\n");
|
|
93
93
|
return 1;
|
|
94
94
|
}
|
|
95
95
|
|
package/src/commands/prune.ts
CHANGED
|
@@ -77,7 +77,7 @@ function helpText(): string {
|
|
|
77
77
|
|
|
78
78
|
function resolveActiveProfile(explicit: string | null): string | null {
|
|
79
79
|
if (explicit) return explicit;
|
|
80
|
-
const pin = join(process.cwd(), ".cue
|
|
80
|
+
const pin = join(process.cwd(), ".cue.profile");
|
|
81
81
|
if (existsSync(pin)) {
|
|
82
82
|
try {
|
|
83
83
|
const txt = readFileSync(pin, "utf8").trim().split("\n")[0]?.trim();
|
|
@@ -126,7 +126,7 @@ export async function runSecurityAudit(args: string[]): Promise<number> {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
if (!profileName) {
|
|
129
|
-
process.stderr.write("No active profile. Specify a profile name or set .cue
|
|
129
|
+
process.stderr.write("No active profile. Specify a profile name or set .cue.profile.\n");
|
|
130
130
|
return 1;
|
|
131
131
|
}
|
|
132
132
|
|
package/src/commands/shell.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `cue shell install` — install shims
|
|
4
4
|
*
|
|
5
5
|
* Usage: eval "$(cue shell hook)"
|
|
6
|
-
* Adds a cd wrapper that checks .cue
|
|
6
|
+
* Adds a cd wrapper that checks .cue.profile on directory change.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, statSync, accessSync, constants } from "node:fs";
|
|
@@ -33,8 +33,8 @@ __cue_check_profile() {
|
|
|
33
33
|
local dir="$PWD"
|
|
34
34
|
local profile=""
|
|
35
35
|
while [ "$dir" != "/" ] && [ "$dir" != "$HOME" ]; do
|
|
36
|
-
if [ -f "$dir/.cue
|
|
37
|
-
profile="$(cat "$dir/.cue
|
|
36
|
+
if [ -f "$dir/.cue.profile" ]; then
|
|
37
|
+
profile="$(cat "$dir/.cue.profile" 2>/dev/null | tr -d '\\n')"
|
|
38
38
|
break
|
|
39
39
|
fi
|
|
40
40
|
dir="$(dirname "$dir")"
|
|
@@ -57,8 +57,8 @@ __cue_check_profile() {
|
|
|
57
57
|
local dir="$PWD"
|
|
58
58
|
local profile=""
|
|
59
59
|
while [[ "$dir" != "/" && "$dir" != "$HOME" ]]; do
|
|
60
|
-
if [[ -f "$dir/.cue
|
|
61
|
-
profile="$(cat "$dir/.cue
|
|
60
|
+
if [[ -f "$dir/.cue.profile" ]]; then
|
|
61
|
+
profile="$(cat "$dir/.cue.profile" | tr -d '\\n')"
|
|
62
62
|
break
|
|
63
63
|
fi
|
|
64
64
|
dir="$(dirname "$dir")"
|
|
@@ -82,8 +82,8 @@ function __cue_check_profile --on-variable PWD
|
|
|
82
82
|
set -l dir $PWD
|
|
83
83
|
set -l profile ""
|
|
84
84
|
while test "$dir" != "/" -a "$dir" != "$HOME"
|
|
85
|
-
if test -f "$dir/.cue
|
|
86
|
-
set profile (cat "$dir/.cue
|
|
85
|
+
if test -f "$dir/.cue.profile"
|
|
86
|
+
set profile (cat "$dir/.cue.profile" | string trim)
|
|
87
87
|
break
|
|
88
88
|
end
|
|
89
89
|
set dir (dirname "$dir")
|
|
@@ -74,7 +74,7 @@ function helpText(): string {
|
|
|
74
74
|
|
|
75
75
|
function resolveActiveProfile(explicit: string | null): string | null {
|
|
76
76
|
if (explicit) return explicit;
|
|
77
|
-
const pin = join(process.cwd(), ".cue
|
|
77
|
+
const pin = join(process.cwd(), ".cue.profile");
|
|
78
78
|
if (existsSync(pin)) {
|
|
79
79
|
try {
|
|
80
80
|
const txt = readFileSync(pin, "utf8").trim().split("\n")[0]?.trim();
|
package/src/commands/skills.ts
CHANGED
|
@@ -219,7 +219,7 @@ async function cmdSearch(query: string, json: boolean): Promise<number> {
|
|
|
219
219
|
async function cmdAddToProfile(id: string, preview = false): Promise<number> {
|
|
220
220
|
const profileName = await getActiveProfileName();
|
|
221
221
|
if (!profileName) {
|
|
222
|
-
process.stderr.write("No active profile. Pin one with `echo <name> > .cue
|
|
222
|
+
process.stderr.write("No active profile. Pin one with `echo <name> > .cue.profile`\n");
|
|
223
223
|
return 1;
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -808,8 +808,8 @@ async function cmdNpxAdd(args: string[]): Promise<number> {
|
|
|
808
808
|
// #2: Auto-pin option
|
|
809
809
|
const pin = await p.confirm({ message: `Pin "${name}" to current directory?`, initialValue: true });
|
|
810
810
|
if (!p.isCancel(pin) && pin) {
|
|
811
|
-
await writeFile(join(process.cwd(), ".cue
|
|
812
|
-
p.log.success(`Pinned → .cue
|
|
811
|
+
await writeFile(join(process.cwd(), ".cue.profile"), `${name}\n`);
|
|
812
|
+
p.log.success(`Pinned → .cue.profile`);
|
|
813
813
|
}
|
|
814
814
|
|
|
815
815
|
// #10: Post-create launch prompt
|
package/src/commands/snapshot.ts
CHANGED
|
@@ -25,7 +25,7 @@ async function cmdSnapshot(args: string[]): Promise<number> {
|
|
|
25
25
|
|
|
26
26
|
const profileName = await resolveActiveProfile();
|
|
27
27
|
if (!profileName) {
|
|
28
|
-
process.stderr.write("No active profile. Pin one with `echo <name> > .cue
|
|
28
|
+
process.stderr.write("No active profile. Pin one with `echo <name> > .cue.profile`\n");
|
|
29
29
|
return 1;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -101,6 +101,6 @@ async function cmdRestore(args: string[]): Promise<number> {
|
|
|
101
101
|
|
|
102
102
|
writeFileSync(join(profileDir, "profile.yaml"), yaml.stringify(profileYaml));
|
|
103
103
|
process.stdout.write(`✅ Restored profile "${snapshot.profile.name}" from snapshot\n`);
|
|
104
|
-
process.stdout.write(` Pin with: echo ${snapshot.profile.name} > .cue
|
|
104
|
+
process.stdout.write(` Pin with: echo ${snapshot.profile.name} > .cue.profile\n`);
|
|
105
105
|
return 0;
|
|
106
106
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { summon, detectActiveProfile, REEXEC_CMD } from "./summon";
|
|
7
|
+
|
|
8
|
+
let dir: string;
|
|
9
|
+
beforeEach(async () => { dir = await mkdtemp(join(tmpdir(), "cue-summon-")); });
|
|
10
|
+
afterEach(async () => { await rm(dir, { recursive: true, force: true }); });
|
|
11
|
+
|
|
12
|
+
describe("detectActiveProfile", () => {
|
|
13
|
+
test("prefers CUE_ACTIVE_PROFILE, then CUE_PROFILE", () => {
|
|
14
|
+
// Arrange / Act / Assert
|
|
15
|
+
expect(detectActiveProfile({ CUE_ACTIVE_PROFILE: "a", CUE_PROFILE: "b" } as NodeJS.ProcessEnv)).toBe("a");
|
|
16
|
+
expect(detectActiveProfile({ CUE_PROFILE: "b" } as NodeJS.ProcessEnv)).toBe("b");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("falls back to the CLAUDE_CONFIG_DIR runtime path", () => {
|
|
20
|
+
const env = { CLAUDE_CONFIG_DIR: "/home/u/.config/cue/runtime/core+skill-writer/claude" } as NodeJS.ProcessEnv;
|
|
21
|
+
expect(detectActiveProfile(env)).toBe("core+skill-writer");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("returns null when nothing identifies the session", () => {
|
|
25
|
+
expect(detectActiveProfile({} as NodeJS.ProcessEnv)).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("summon", () => {
|
|
30
|
+
test("explicit profile arg overrides an auto-detect signal", async () => {
|
|
31
|
+
// Arrange: a dir that WOULD auto-detect vercel...
|
|
32
|
+
await writeFile(join(dir, "vercel.json"), "{}");
|
|
33
|
+
// Act: ...but an explicit profile is passed.
|
|
34
|
+
const r = await summon({ cwd: dir, profile: "core", active: null, noPin: true });
|
|
35
|
+
// Assert
|
|
36
|
+
expect(r.profile).toBe("core");
|
|
37
|
+
expect(r.detected).toBe(false);
|
|
38
|
+
expect(r.reexec_cmd).toBe(REEXEC_CMD);
|
|
39
|
+
expect(r.skills.length).toBeGreaterThan(0);
|
|
40
|
+
expect(r.skills.every((s) => s.id.length > 0 && typeof s.mcp_status === "string")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("auto-detects vercel from vercel.json + @vercel deps", async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
await writeFile(join(dir, "vercel.json"), "{}");
|
|
46
|
+
await writeFile(join(dir, "next.config.js"), "");
|
|
47
|
+
await writeFile(join(dir, "package.json"), JSON.stringify({ dependencies: { next: "15", vercel: "39" } }));
|
|
48
|
+
// Act
|
|
49
|
+
const r = await summon({ cwd: dir, active: null, noPin: true });
|
|
50
|
+
// Assert
|
|
51
|
+
expect(r.profile).toBe("vercel");
|
|
52
|
+
expect(r.detected).toBe(true);
|
|
53
|
+
expect(r.confidence ?? 0).toBeGreaterThanOrEqual(0.9);
|
|
54
|
+
expect(r.persona.length).toBeGreaterThan(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("throws when no profile resolves from an empty dir", async () => {
|
|
58
|
+
await expect(summon({ cwd: dir, active: null, noPin: true })).rejects.toThrow();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("throws on an unknown explicit profile", async () => {
|
|
62
|
+
await expect(summon({ cwd: dir, profile: "does-not-exist-xyz", active: null })).rejects.toThrow(/unknown profile/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("writes the .cue.profile pin by default, skips it with noPin", async () => {
|
|
66
|
+
// Arrange / Act
|
|
67
|
+
const r1 = await summon({ cwd: dir, profile: "vercel", active: null });
|
|
68
|
+
// Assert
|
|
69
|
+
expect(r1.pin_written).toBe(true);
|
|
70
|
+
expect(r1.pin_path).toBe(join(dir, ".cue.profile"));
|
|
71
|
+
expect((await readFile(join(dir, ".cue.profile"), "utf8")).trim()).toBe("vercel");
|
|
72
|
+
|
|
73
|
+
const r2 = await summon({ cwd: dir, profile: "vercel", active: null, noPin: true });
|
|
74
|
+
expect(r2.pin_written).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("dry-run computes a result without writing the pin", async () => {
|
|
78
|
+
const r = await summon({ cwd: dir, profile: "vercel", active: null, dryRun: true });
|
|
79
|
+
expect(r.pin_written).toBe(false);
|
|
80
|
+
expect(r.pin_previous).toBeNull();
|
|
81
|
+
await expect(stat(join(dir, ".cue.profile"))).rejects.toThrow();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("re-pinning the same profile is a no-op, not a clobber", async () => {
|
|
85
|
+
// Arrange: already pinned to vercel
|
|
86
|
+
await writeFile(join(dir, ".cue.profile"), "vercel\n");
|
|
87
|
+
// Act
|
|
88
|
+
const r = await summon({ cwd: dir, profile: "vercel", active: null });
|
|
89
|
+
// Assert: no rewrite, but the prior pin is surfaced
|
|
90
|
+
expect(r.pin_written).toBe(false);
|
|
91
|
+
expect(r.pin_previous).toBe("vercel");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("re-pinning a different profile surfaces the replaced pin", async () => {
|
|
95
|
+
// Arrange: pinned to a different profile
|
|
96
|
+
await writeFile(join(dir, ".cue.profile"), "core\n");
|
|
97
|
+
// Act
|
|
98
|
+
const r = await summon({ cwd: dir, profile: "vercel", active: null });
|
|
99
|
+
// Assert: written, and the previous pin is reported (not silently clobbered)
|
|
100
|
+
expect(r.pin_written).toBe(true);
|
|
101
|
+
expect(r.pin_previous).toBe("core");
|
|
102
|
+
expect((await readFile(join(dir, ".cue.profile"), "utf8")).trim()).toBe("vercel");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("mcp_status reflects the active session's loaded MCPs", async () => {
|
|
106
|
+
// browser/lightpanda needs the `lightpanda` MCP; core loads it.
|
|
107
|
+
const lp = (skills: { id: string; mcp_status: string }[]) =>
|
|
108
|
+
skills.find((s) => s.id === "browser/lightpanda");
|
|
109
|
+
|
|
110
|
+
const noActive = await summon({ cwd: dir, profile: "vercel", active: null, noPin: true });
|
|
111
|
+
const withCore = await summon({ cwd: dir, profile: "vercel", active: "core", noPin: true });
|
|
112
|
+
|
|
113
|
+
expect(lp(noActive.skills)?.mcp_status).toBe("missing:lightpanda");
|
|
114
|
+
expect(lp(withCore.skills)?.mcp_status).toBe("ok");
|
|
115
|
+
});
|
|
116
|
+
});
|