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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cue summon [profile]` — bind a profile into the LIVE session, no cold restart.
|
|
3
|
+
*
|
|
4
|
+
* When you open a directory with no `.cue.profile`, the right profile's skills
|
|
5
|
+
* and MCPs normally need a pin + a full `claude` restart (CLAUDE_CONFIG_DIR, the
|
|
6
|
+
* Skill() list, MCP servers, and /slash commands are frozen at boot). `summon`
|
|
7
|
+
* is the two-tier bridge:
|
|
8
|
+
*
|
|
9
|
+
* Tier A (now, zero restart): resolve a profile, list its skills as
|
|
10
|
+
* readable SKILL.md paths + a persona, so the running agent can soft-load
|
|
11
|
+
* them inline (the `meta/profile-summon` skill drives this — same mechanism
|
|
12
|
+
* as `meta/smart-loader`, just whole-profile).
|
|
13
|
+
* Tier B (durable + full fidelity): write the `.cue.profile` pin so the next
|
|
14
|
+
* launch is correct, and hand back the warm re-exec (`claude --continue`)
|
|
15
|
+
* that resumes THIS conversation under the fully-materialized profile —
|
|
16
|
+
* the only honest way to get the MCP servers + /slash commands the soft
|
|
17
|
+
* load can't fake.
|
|
18
|
+
*
|
|
19
|
+
* This command never spawns an agent and never fakes MCP tools. Its only side
|
|
20
|
+
* effect is writing the pin (skippable with `--no-pin`).
|
|
21
|
+
*
|
|
22
|
+
* Flags:
|
|
23
|
+
* [profile] force this profile (else auto-detect from cwd)
|
|
24
|
+
* --json machine-readable output (consumed by meta/profile-summon)
|
|
25
|
+
* --no-pin don't write .cue.profile
|
|
26
|
+
* --pick list detected candidates and exit (no pin, no apply)
|
|
27
|
+
* --active <name> override active-session profile detection (for mcp_status)
|
|
28
|
+
* --dry-run compute everything, write nothing
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
32
|
+
import { basename, join } from "node:path";
|
|
33
|
+
|
|
34
|
+
import { loadProfile, listProfiles } from "../lib/profile-loader";
|
|
35
|
+
import { resolveLocalSkill } from "../lib/resolver-local";
|
|
36
|
+
import { detectProfileV2 } from "../lib/auto-detect";
|
|
37
|
+
import { getSkillDependencies } from "../lib/skill-dependencies";
|
|
38
|
+
|
|
39
|
+
/** Minimum auto-detect confidence to summon without an explicit arg. Mirrors
|
|
40
|
+
* SUGGESTED_MIN_CONFIDENCE in launch.ts so the picker and summon agree. */
|
|
41
|
+
export const SUMMON_MIN_CONFIDENCE = 0.5;
|
|
42
|
+
|
|
43
|
+
/** The warm re-exec that resumes this conversation under the full profile. */
|
|
44
|
+
export const REEXEC_CMD = "claude --continue";
|
|
45
|
+
|
|
46
|
+
export interface SummonOptions {
|
|
47
|
+
cwd: string;
|
|
48
|
+
/** Explicit target profile; when null, auto-detect from cwd. */
|
|
49
|
+
profile?: string | null;
|
|
50
|
+
/** Override active-session profile detection (for mcp_status). */
|
|
51
|
+
active?: string | null;
|
|
52
|
+
noPin?: boolean;
|
|
53
|
+
dryRun?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SummonSkill {
|
|
57
|
+
id: string;
|
|
58
|
+
/** Absolute SKILL.md path to Read, or "" when it can't be resolved on disk. */
|
|
59
|
+
path: string;
|
|
60
|
+
/** "ok" (soft-loadable now) or "missing:<mcp1,mcp2>" (needs the harness). */
|
|
61
|
+
mcp_status: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SummonResult {
|
|
65
|
+
profile: string;
|
|
66
|
+
/** Profile persona prose to apply inline ("" when none declared). */
|
|
67
|
+
persona: string;
|
|
68
|
+
/** true when the profile was auto-detected (no explicit arg). */
|
|
69
|
+
detected: boolean;
|
|
70
|
+
confidence?: number;
|
|
71
|
+
reasons?: string[];
|
|
72
|
+
/** Active running-session profile selector, or null when undetectable. */
|
|
73
|
+
active_profile: string | null;
|
|
74
|
+
pin_written: boolean;
|
|
75
|
+
pin_path: string;
|
|
76
|
+
/** Existing `.cue.profile` content before this summon, or null when none.
|
|
77
|
+
* Lets the consumer flag a re-pin over a *different* profile instead of a
|
|
78
|
+
* silent clobber, and skip a redundant write when it already matches. */
|
|
79
|
+
pin_previous: string | null;
|
|
80
|
+
/** Local skills — soft-loadable inline (read each `path`). */
|
|
81
|
+
skills: SummonSkill[];
|
|
82
|
+
/** npx skills — loaded at launch only, can't be soft-loaded as prose. */
|
|
83
|
+
npx_skills: string[];
|
|
84
|
+
/** Profile MCPs and whether they're already loaded in the active session. */
|
|
85
|
+
mcps: { id: string; loaded: boolean }[];
|
|
86
|
+
/** /slash commands the profile adds (need the harness). */
|
|
87
|
+
commands: string[];
|
|
88
|
+
plugins: string[];
|
|
89
|
+
reexec_cmd: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Detect the profile of the *currently running* session (not the cwd's pin).
|
|
94
|
+
* Mirrors `resolve_active_profile` in smart-lookup.sh: env first, then the
|
|
95
|
+
* CLAUDE_CONFIG_DIR runtime path. Returns a selector like "core+skill-writer".
|
|
96
|
+
*/
|
|
97
|
+
export function detectActiveProfile(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
98
|
+
const fromEnv = env.CUE_ACTIVE_PROFILE || env.CUE_PROFILE;
|
|
99
|
+
if (fromEnv) return fromEnv;
|
|
100
|
+
const ccd = env.CLAUDE_CONFIG_DIR;
|
|
101
|
+
if (ccd) {
|
|
102
|
+
const m = ccd.match(/\/cue\/runtime\/(.+?)\/claude\/?$/);
|
|
103
|
+
if (m) return m[1]!;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Union of MCP ids (lowercased) loaded by an active profile selector. */
|
|
109
|
+
async function loadActiveMcpIds(selector: string | null): Promise<Set<string>> {
|
|
110
|
+
const ids = new Set<string>();
|
|
111
|
+
if (!selector) return ids;
|
|
112
|
+
for (const part of selector.split("+").map((s) => s.trim()).filter(Boolean)) {
|
|
113
|
+
try {
|
|
114
|
+
const p = await loadProfile(part);
|
|
115
|
+
for (const m of p.mcps) ids.add(m.id.toLowerCase());
|
|
116
|
+
} catch {
|
|
117
|
+
// Unknown/stale part — skip; conservative (its MCPs count as not loaded).
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return ids;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** "ok" when every MCP a skill needs is loaded in the active session, else the
|
|
124
|
+
* missing list. Skills with no MCP deps are always "ok" (pure-prose soft-load). */
|
|
125
|
+
function skillMcpStatus(skillId: string, activeMcps: Set<string>): string {
|
|
126
|
+
const deps = getSkillDependencies(skillId);
|
|
127
|
+
if (deps.length === 0) return "ok";
|
|
128
|
+
const missing = [
|
|
129
|
+
...new Set(deps.map((d) => d.mcpId).filter((m) => !activeMcps.has(m.toLowerCase()))),
|
|
130
|
+
];
|
|
131
|
+
return missing.length === 0 ? "ok" : `missing:${missing.join(",")}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve which profile to summon: explicit arg (must exist) or the top
|
|
136
|
+
* auto-detection above the confidence floor. Throws a user-facing Error when
|
|
137
|
+
* nothing resolves.
|
|
138
|
+
*/
|
|
139
|
+
async function resolveTarget(
|
|
140
|
+
explicit: string | null | undefined,
|
|
141
|
+
cwd: string,
|
|
142
|
+
): Promise<{ profile: string; detected: boolean; confidence?: number; reasons?: string[] }> {
|
|
143
|
+
const known = new Set(await listProfiles());
|
|
144
|
+
if (explicit) {
|
|
145
|
+
if (!known.has(explicit)) {
|
|
146
|
+
throw new Error(`unknown profile "${explicit}" — run \`cue list\` to see profiles`);
|
|
147
|
+
}
|
|
148
|
+
return { profile: explicit, detected: false };
|
|
149
|
+
}
|
|
150
|
+
const dets = detectProfileV2(cwd).filter((d) => known.has(d.profile));
|
|
151
|
+
const top = dets[0];
|
|
152
|
+
if (!top || top.confidence < SUMMON_MIN_CONFIDENCE) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"no profile confidently matches this directory — pass one explicitly: `cue summon <profile>`",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return { profile: top.profile, detected: true, confidence: top.confidence, reasons: top.reasons };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Pure core: resolve, enumerate, (optionally) pin. No printing, no agent spawn.
|
|
162
|
+
* Exported for tests and for the `meta/profile-summon` skill via `--json`.
|
|
163
|
+
*/
|
|
164
|
+
export async function summon(opts: SummonOptions): Promise<SummonResult> {
|
|
165
|
+
const target = await resolveTarget(opts.profile, opts.cwd);
|
|
166
|
+
const profile = await loadProfile(target.profile);
|
|
167
|
+
|
|
168
|
+
// `undefined` (omitted) → auto-detect the running session; explicit `null` →
|
|
169
|
+
// treat as no active session (every MCP-gated skill counts as not loaded).
|
|
170
|
+
const active = opts.active === undefined ? detectActiveProfile() : opts.active;
|
|
171
|
+
const activeMcps = await loadActiveMcpIds(active);
|
|
172
|
+
|
|
173
|
+
const skills: SummonSkill[] = [];
|
|
174
|
+
for (const s of profile.skills.local) {
|
|
175
|
+
let path = "";
|
|
176
|
+
try {
|
|
177
|
+
path = join(await resolveLocalSkill(s.id), "SKILL.md");
|
|
178
|
+
} catch {
|
|
179
|
+
// Skill id doesn't resolve on disk — still report it; the consumer can
|
|
180
|
+
// fall through to smart-loader's filesystem scan.
|
|
181
|
+
}
|
|
182
|
+
skills.push({ id: s.id, path, mcp_status: skillMcpStatus(s.id, activeMcps) });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const pinPath = join(opts.cwd, ".cue.profile");
|
|
186
|
+
const pinPrevious = existsSync(pinPath) ? readFileSync(pinPath, "utf8").trim() || null : null;
|
|
187
|
+
let pinWritten = false;
|
|
188
|
+
// Skip the write when it's already pinned to this profile (no-op, respects
|
|
189
|
+
// the skill's pinned-noop contract); otherwise write — a re-pin over a
|
|
190
|
+
// DIFFERENT profile is surfaced via `pin_previous`, never silently clobbered.
|
|
191
|
+
if (!opts.noPin && !opts.dryRun && pinPrevious !== profile.name) {
|
|
192
|
+
writeFileSync(pinPath, `${profile.name}\n`);
|
|
193
|
+
pinWritten = true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
profile: profile.name,
|
|
198
|
+
persona: profile.persona ?? "",
|
|
199
|
+
detected: target.detected,
|
|
200
|
+
confidence: target.confidence,
|
|
201
|
+
reasons: target.reasons,
|
|
202
|
+
active_profile: active,
|
|
203
|
+
pin_written: pinWritten,
|
|
204
|
+
pin_path: pinPath,
|
|
205
|
+
pin_previous: pinPrevious,
|
|
206
|
+
skills,
|
|
207
|
+
npx_skills: profile.skills.npx.flatMap((n) => n.skills),
|
|
208
|
+
mcps: profile.mcps.map((m) => ({ id: m.id, loaded: activeMcps.has(m.id.toLowerCase()) })),
|
|
209
|
+
commands: profile.commands.map((c) => `/${basename(c, ".md")}`),
|
|
210
|
+
plugins: profile.plugins.map((p) => p.id),
|
|
211
|
+
reexec_cmd: REEXEC_CMD,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// CLI wrapper
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
const HELP = `cue summon — bind a profile into the LIVE session, no cold restart
|
|
220
|
+
|
|
221
|
+
Usage: cue summon [profile] [flags]
|
|
222
|
+
|
|
223
|
+
Resolves a profile (explicit arg, else auto-detected from this directory),
|
|
224
|
+
lists its skills as readable SKILL.md paths so the running agent can soft-load
|
|
225
|
+
them inline, pins .cue.profile, and prints the warm re-exec (\`${REEXEC_CMD}\`)
|
|
226
|
+
that resumes this conversation under the full profile (MCPs + /slash commands).
|
|
227
|
+
|
|
228
|
+
Flags:
|
|
229
|
+
--json machine-readable output (for meta/profile-summon)
|
|
230
|
+
--no-pin don't write .cue.profile
|
|
231
|
+
--pick list detected candidates and exit (no pin)
|
|
232
|
+
--active <name> override active-session profile (affects mcp_status)
|
|
233
|
+
--dry-run compute everything, write nothing
|
|
234
|
+
-h, --help show this help
|
|
235
|
+
|
|
236
|
+
Examples:
|
|
237
|
+
cue summon vercel # summon a known profile here
|
|
238
|
+
cue summon # auto-detect from cwd
|
|
239
|
+
cue summon --json | jq # drive from the meta/profile-summon skill
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
function printHuman(r: SummonResult): void {
|
|
243
|
+
const out: string[] = [];
|
|
244
|
+
const det = r.detected ? ` (auto-detected, ${Math.round((r.confidence ?? 0) * 100)}% match)` : "";
|
|
245
|
+
out.push(`🔮 summon ${r.profile}${det}`);
|
|
246
|
+
if (r.detected && r.reasons?.length) out.push(` why: ${r.reasons.slice(0, 3).join(", ")}`);
|
|
247
|
+
out.push("");
|
|
248
|
+
|
|
249
|
+
const loadable = r.skills.filter((s) => s.mcp_status === "ok");
|
|
250
|
+
const gated = r.skills.filter((s) => s.mcp_status !== "ok");
|
|
251
|
+
out.push(`✅ soft-load now (no restart): persona${r.persona ? "" : " (none)"} + ${loadable.length} skill${loadable.length === 1 ? "" : "s"}`);
|
|
252
|
+
for (const s of loadable.slice(0, 8)) out.push(` • ${s.id}`);
|
|
253
|
+
if (loadable.length > 8) out.push(` …and ${loadable.length - 8} more`);
|
|
254
|
+
|
|
255
|
+
const harnessBits: string[] = [];
|
|
256
|
+
if (gated.length) harnessBits.push(`${gated.length} MCP-gated skill${gated.length === 1 ? "" : "s"}`);
|
|
257
|
+
if (r.npx_skills.length) harnessBits.push(`${r.npx_skills.length} npx skill${r.npx_skills.length === 1 ? "" : "s"}`);
|
|
258
|
+
const unloadedMcps = r.mcps.filter((m) => !m.loaded).map((m) => m.id);
|
|
259
|
+
if (unloadedMcps.length) harnessBits.push(`MCP: ${unloadedMcps.join(", ")}`);
|
|
260
|
+
if (r.plugins.length) harnessBits.push(`plugins: ${r.plugins.join(", ")}`);
|
|
261
|
+
if (r.commands.length) harnessBits.push(`commands: ${r.commands.slice(0, 6).join(" ")}`);
|
|
262
|
+
if (harnessBits.length) {
|
|
263
|
+
out.push("");
|
|
264
|
+
out.push(`🔒 needs the harness (won't soft-load): ${harnessBits.join(" · ")}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
out.push("");
|
|
268
|
+
if (r.pin_written && r.pin_previous && r.pin_previous !== r.profile) {
|
|
269
|
+
out.push(`📌 repinned .cue.profile: ${r.pin_previous} → ${r.profile} (replaced a different pin)`);
|
|
270
|
+
} else if (r.pin_written) {
|
|
271
|
+
out.push(`📌 pinned .cue.profile → ${r.profile}`);
|
|
272
|
+
} else if (r.pin_previous === r.profile) {
|
|
273
|
+
out.push(`📌 already pinned → ${r.profile}`);
|
|
274
|
+
} else {
|
|
275
|
+
out.push(`📌 pin skipped`);
|
|
276
|
+
}
|
|
277
|
+
out.push(`↻ full fidelity (MCPs + /slash): run \`${r.reexec_cmd}\` resumes this conversation`);
|
|
278
|
+
process.stdout.write(out.join("\n") + "\n");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function run(args: string[]): Promise<number> {
|
|
282
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
283
|
+
process.stdout.write(HELP);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let profile: string | null = null;
|
|
288
|
+
// undefined → auto-detect the running session; only set when --active passed.
|
|
289
|
+
let active: string | undefined;
|
|
290
|
+
let json = false;
|
|
291
|
+
let noPin = false;
|
|
292
|
+
let pick = false;
|
|
293
|
+
let dryRun = false;
|
|
294
|
+
for (let i = 0; i < args.length; i++) {
|
|
295
|
+
const a = args[i]!;
|
|
296
|
+
if (a === "--json") json = true;
|
|
297
|
+
else if (a === "--no-pin") noPin = true;
|
|
298
|
+
else if (a === "--pick") pick = true;
|
|
299
|
+
else if (a === "--dry-run") dryRun = true;
|
|
300
|
+
else if (a === "--active") active = args[++i] ?? undefined;
|
|
301
|
+
else if (!a.startsWith("-") && profile === null) profile = a;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const cwd = process.cwd();
|
|
305
|
+
|
|
306
|
+
// --pick: just surface the candidates, don't act.
|
|
307
|
+
if (pick) {
|
|
308
|
+
const known = new Set(await listProfiles());
|
|
309
|
+
const dets = detectProfileV2(cwd).filter((d) => known.has(d.profile));
|
|
310
|
+
if (json) {
|
|
311
|
+
process.stdout.write(JSON.stringify({ candidates: dets }, null, 2) + "\n");
|
|
312
|
+
} else if (dets.length === 0) {
|
|
313
|
+
process.stdout.write("No profile confidently matches this directory.\n");
|
|
314
|
+
} else {
|
|
315
|
+
process.stdout.write("Detected profiles for this directory:\n");
|
|
316
|
+
for (const d of dets.slice(0, 5)) {
|
|
317
|
+
process.stdout.write(` ${Math.round(d.confidence * 100)}% ${d.profile} — ${d.reasons.slice(0, 2).join(", ")}\n`);
|
|
318
|
+
}
|
|
319
|
+
process.stdout.write(`\nSummon one: cue summon <profile>\n`);
|
|
320
|
+
}
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let result: SummonResult;
|
|
325
|
+
try {
|
|
326
|
+
result = await summon({ cwd, profile, active, noPin, dryRun });
|
|
327
|
+
} catch (err) {
|
|
328
|
+
process.stderr.write(`cue summon: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (json) {
|
|
333
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
334
|
+
} else {
|
|
335
|
+
printHuman(result);
|
|
336
|
+
}
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
@@ -73,7 +73,7 @@ function helpText(): string {
|
|
|
73
73
|
|
|
74
74
|
function resolveActiveProfile(explicit: string | null): string | null {
|
|
75
75
|
if (explicit) return explicit;
|
|
76
|
-
const pin = join(process.cwd(), ".cue
|
|
76
|
+
const pin = join(process.cwd(), ".cue.profile");
|
|
77
77
|
if (existsSync(pin)) {
|
|
78
78
|
try {
|
|
79
79
|
const txt = readFileSync(pin, "utf8").trim().split("\n")[0]?.trim();
|
package/src/commands/use.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `cue use <profile>` — pin a profile to the current directory.
|
|
3
3
|
*
|
|
4
|
-
* Writes `.cue
|
|
4
|
+
* Writes `.cue.profile` in CWD (or $HOME with --global).
|
|
5
5
|
*
|
|
6
6
|
* Composite selectors are accepted: `cue use postizz+trendradar` validates
|
|
7
7
|
* each part separately and pins the full `a+b` string verbatim.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* prompt with `--no-prompt` (or in non-TTY environments — auto-skipped).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { writeFileSync } from "node:fs";
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { createInterface } from "node:readline/promises";
|
|
@@ -19,9 +19,44 @@ import { stdin, stdout } from "node:process";
|
|
|
19
19
|
|
|
20
20
|
import { isCompositeSelector, listProfiles, loadProfile, parseProfileSelector } from "../lib/profile-loader";
|
|
21
21
|
|
|
22
|
+
/** Ensure `entry` appears in the .gitignore at `dir` (idempotent). */
|
|
23
|
+
function ensureGitignoreEntry(dir: string, entry: string): void {
|
|
24
|
+
const path = join(dir, ".gitignore");
|
|
25
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
26
|
+
const lines = existing.split("\n").map(l => l.trim());
|
|
27
|
+
if (lines.includes(entry)) return;
|
|
28
|
+
const suffix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
29
|
+
writeFileSync(path, existing + suffix + entry + "\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Write profiles/<name>.json for each profile part into dir (best-effort). */
|
|
33
|
+
async function writeProfilesDir(parts: string[], dir: string): Promise<void> {
|
|
34
|
+
try {
|
|
35
|
+
mkdirSync(join(dir, "profiles"), { recursive: true });
|
|
36
|
+
await Promise.all(parts.map(async (name) => {
|
|
37
|
+
try {
|
|
38
|
+
const p = await loadProfile(name);
|
|
39
|
+
const snapshot = {
|
|
40
|
+
name: p.name,
|
|
41
|
+
description: p.description,
|
|
42
|
+
...(p.icon ? { icon: p.icon } : {}),
|
|
43
|
+
...(p.inheritanceChain.length > 1 ? { inherits: p.inheritanceChain.slice(0, -1) } : {}),
|
|
44
|
+
...(p.recommends.length > 0 ? { recommends: p.recommends } : {}),
|
|
45
|
+
};
|
|
46
|
+
writeFileSync(join(dir, "profiles", `${name}.json`), JSON.stringify(snapshot, null, 2) + "\n");
|
|
47
|
+
} catch {
|
|
48
|
+
// best-effort per profile — missing profile metadata is non-fatal
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
} catch {
|
|
52
|
+
// never fail the pin because of profiles/ write errors
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
export async function run(args: string[]): Promise<number> {
|
|
23
57
|
const global = args.includes("--global") || args.includes("-g");
|
|
24
58
|
const noPrompt = args.includes("--no-prompt");
|
|
59
|
+
const noProfilesDir = args.includes("--no-profiles-dir");
|
|
25
60
|
const selector = args.find(a => !a.startsWith("-"));
|
|
26
61
|
|
|
27
62
|
if (!selector) {
|
|
@@ -49,7 +84,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
49
84
|
}
|
|
50
85
|
|
|
51
86
|
const writePin = (value: string) => {
|
|
52
|
-
const target = global ? join(homedir(), ".cue
|
|
87
|
+
const target = global ? join(homedir(), ".cue.profile") : join(process.cwd(), ".cue.profile");
|
|
53
88
|
writeFileSync(target, value + "\n");
|
|
54
89
|
};
|
|
55
90
|
|
|
@@ -57,6 +92,15 @@ export async function run(args: string[]): Promise<number> {
|
|
|
57
92
|
const scope = global ? "globally" : `in ${process.cwd()}`;
|
|
58
93
|
process.stdout.write(`✅ Now using "${selector}" ${scope}\n`);
|
|
59
94
|
|
|
95
|
+
// Write profiles/<name>.json manifest + gitignore both cue artifacts (project-local only).
|
|
96
|
+
if (!global && !noProfilesDir) {
|
|
97
|
+
await writeProfilesDir(parts, process.cwd());
|
|
98
|
+
const created = parts.map(p => `profiles/${p}.json`).join(", ");
|
|
99
|
+
process.stdout.write(`📁 Profile manifest written: ${created}\n`);
|
|
100
|
+
ensureGitignoreEntry(process.cwd(), ".cue.profile");
|
|
101
|
+
ensureGitignoreEntry(process.cwd(), "profiles/");
|
|
102
|
+
}
|
|
103
|
+
|
|
60
104
|
// Recommendation surfacing — only on plain (non-composite) selections.
|
|
61
105
|
if (!isCompositeSelector(selector)) {
|
|
62
106
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `cue watch-live [--profile <name>]` — file watcher for auto-rematerialization.
|
|
3
3
|
*
|
|
4
|
-
* Monitors profile.yaml, referenced SKILL.md files, and .cue
|
|
4
|
+
* Monitors profile.yaml, referenced SKILL.md files, and .cue.profile in cwd.
|
|
5
5
|
* On change: re-runs materialization with 500ms debounce.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -25,7 +25,7 @@ Usage: cue watch-live [--profile <name>]
|
|
|
25
25
|
Monitors:
|
|
26
26
|
• Active profile's profile.yaml
|
|
27
27
|
• All SKILL.md files referenced by the profile
|
|
28
|
-
• .cue
|
|
28
|
+
• .cue.profile in cwd
|
|
29
29
|
|
|
30
30
|
On any change, re-runs materialization (500ms debounce).
|
|
31
31
|
Press Ctrl+C to stop.
|
|
@@ -43,7 +43,7 @@ Press Ctrl+C to stop.
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (!profileName) {
|
|
46
|
-
process.stderr.write("No active profile. Use --profile <name> or set .cue
|
|
46
|
+
process.stderr.write("No active profile. Use --profile <name> or set .cue.profile.\n");
|
|
47
47
|
return 1;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -68,8 +68,8 @@ Press Ctrl+C to stop.
|
|
|
68
68
|
if (existsSync(skillMd)) watchPaths.push(skillMd);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// 3. .cue
|
|
72
|
-
const cueProfile = join(process.cwd(), ".cue
|
|
71
|
+
// 3. .cue.profile in cwd
|
|
72
|
+
const cueProfile = join(process.cwd(), ".cue.profile");
|
|
73
73
|
if (existsSync(cueProfile)) watchPaths.push(cueProfile);
|
|
74
74
|
|
|
75
75
|
if (watchPaths.length === 0) {
|
package/src/commands/watch.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* eval "$(cue watch bash)" # add to ~/.bashrc
|
|
6
6
|
* eval "$(cue watch zsh)" # add to ~/.zshrc
|
|
7
7
|
*
|
|
8
|
-
* The hook runs after every `cd` and checks if the .cue
|
|
8
|
+
* The hook runs after every `cd` and checks if the .cue.profile changed.
|
|
9
9
|
* If it did, it shows a notification with the new profile.
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -18,10 +18,10 @@ __cue_watch() {
|
|
|
18
18
|
local profile_file=""
|
|
19
19
|
local dir="$PWD"
|
|
20
20
|
|
|
21
|
-
# Walk up to find .cue
|
|
21
|
+
# Walk up to find .cue.profile
|
|
22
22
|
while [[ "$dir" != "/" && "$dir" != "$HOME" ]]; do
|
|
23
|
-
if [[ -f "$dir/.cue
|
|
24
|
-
profile_file="$dir/.cue
|
|
23
|
+
if [[ -f "$dir/.cue.profile" ]]; then
|
|
24
|
+
profile_file="$dir/.cue.profile"
|
|
25
25
|
break
|
|
26
26
|
fi
|
|
27
27
|
dir="$(dirname "$dir")"
|
|
@@ -71,10 +71,10 @@ function zshHook(): string {
|
|
|
71
71
|
' local profile_file=""',
|
|
72
72
|
' local dir="$PWD"',
|
|
73
73
|
"",
|
|
74
|
-
" # Walk up to find .cue
|
|
74
|
+
" # Walk up to find .cue.profile",
|
|
75
75
|
' while [[ "$dir" != "/" && "$dir" != "$HOME" ]]; do',
|
|
76
|
-
' if [[ -f "$dir/.cue
|
|
77
|
-
' profile_file="$dir/.cue
|
|
76
|
+
' if [[ -f "$dir/.cue.profile" ]]; then',
|
|
77
|
+
' profile_file="$dir/.cue.profile"',
|
|
78
78
|
" break",
|
|
79
79
|
" fi",
|
|
80
80
|
' dir="${dir:h}"',
|
|
@@ -125,7 +125,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
125
125
|
"Usage:\n" +
|
|
126
126
|
' eval "$(cue watch bash)" # add to ~/.bashrc\n' +
|
|
127
127
|
' eval "$(cue watch zsh)" # add to ~/.zshrc\n\n' +
|
|
128
|
-
"When you cd into a directory with a .cue
|
|
128
|
+
"When you cd into a directory with a .cue.profile, it shows:\n" +
|
|
129
129
|
" ⚡ cue: profile switched frontend → backend\n"
|
|
130
130
|
);
|
|
131
131
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -69,7 +69,9 @@ function printHelp(): void {
|
|
|
69
69
|
["mem", "Inspect/manage per-profile claude-mem stores"],
|
|
70
70
|
],
|
|
71
71
|
"Launch & Shell": [
|
|
72
|
+
["install", "Prepare profile runtimes and optionally install required CLIs"],
|
|
72
73
|
["launch", "Resolve + materialize + exec claude/codex"],
|
|
74
|
+
["summon", "Bind a profile into the live session (soft-load + pin), no restart"],
|
|
73
75
|
["shell", "Install/uninstall shims (~/.local/bin)"],
|
|
74
76
|
["update", "Self-update: git pull + bun install"],
|
|
75
77
|
["upgrade", "Pull new skills from the registry"],
|
|
@@ -94,10 +94,10 @@ describe("profileFromConfigDir", () => {
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
describe("profileFromCwdPin", () => {
|
|
97
|
-
test("returns the first line of .cue
|
|
97
|
+
test("returns the first line of .cue.profile when present", () => {
|
|
98
98
|
const dir = mkdtempSync(join(tmpdir(), "cue-pin-test-"));
|
|
99
99
|
try {
|
|
100
|
-
writeFileSync(join(dir, ".cue
|
|
100
|
+
writeFileSync(join(dir, ".cue.profile"), "skill-writer+ecc\n# comment line\n");
|
|
101
101
|
expect(profileFromCwdPin(dir)).toBe("skill-writer+ecc");
|
|
102
102
|
} finally {
|
|
103
103
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -108,7 +108,7 @@ describe("profileFromCwdPin", () => {
|
|
|
108
108
|
const dir = mkdtempSync(join(tmpdir(), "cue-pin-test-"));
|
|
109
109
|
try {
|
|
110
110
|
expect(profileFromCwdPin(dir)).toBeNull();
|
|
111
|
-
writeFileSync(join(dir, ".cue
|
|
111
|
+
writeFileSync(join(dir, ".cue.profile"), "");
|
|
112
112
|
expect(profileFromCwdPin(dir)).toBeNull();
|
|
113
113
|
} finally {
|
|
114
114
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* b. CLAUDE_CONFIG_DIR matches `<runtimeRoot>/<profile>/claude`
|
|
11
11
|
* — works for plain `claude` wrappers like claude-account2 that
|
|
12
12
|
* bypass `cue launch` but still point at a cue runtime
|
|
13
|
-
* c. `.cue
|
|
13
|
+
* c. `.cue.profile` file in the process's cwd
|
|
14
14
|
* d. "(unpinned)" — agent is running but isn't using a cue profile
|
|
15
15
|
*
|
|
16
16
|
* Linux-only. macOS exposes env via `ps eww` but with different escaping;
|
|
@@ -115,13 +115,13 @@ export function profileFromConfigDir(configDir: string | undefined): string | nu
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Read `.cue
|
|
118
|
+
* Read `.cue.profile` from a cwd (process-cwd fallback). Trimmed first line.
|
|
119
119
|
* Exported for tests.
|
|
120
120
|
*/
|
|
121
121
|
export function profileFromCwdPin(cwd: string | null): string | null {
|
|
122
122
|
if (!cwd) return null;
|
|
123
123
|
try {
|
|
124
|
-
const raw = readFileSync(join(cwd, ".cue
|
|
124
|
+
const raw = readFileSync(join(cwd, ".cue.profile"), "utf8");
|
|
125
125
|
const first = raw.split("\n")[0]?.trim();
|
|
126
126
|
return first && first.length > 0 ? first : null;
|
|
127
127
|
} catch {
|
|
@@ -192,7 +192,7 @@ export function listActiveSessions(): ActiveSession[] {
|
|
|
192
192
|
|
|
193
193
|
// Profile resolution. CUE_PROFILE wins; otherwise sniff CLAUDE_CONFIG_DIR
|
|
194
194
|
// (covers wrappers like `claude-account2` that bypass `cue launch` but
|
|
195
|
-
// still point at a cue runtime); otherwise read .cue
|
|
195
|
+
// still point at a cue runtime); otherwise read .cue.profile from cwd.
|
|
196
196
|
const env = readEnviron(pid);
|
|
197
197
|
let profile: string | null = null;
|
|
198
198
|
let profileSource: ActiveSession["profileSource"] = "unpinned";
|