cue-ai 0.9.0 → 0.9.2
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 +40 -0
- package/README.md +82 -33
- package/bin/cue-review-progress +107 -0
- package/bin/cue-review-watch +98 -0
- package/dist/cue.js +7352 -3744
- package/package.json +16 -5
- package/profiles/_types.ts +9 -0
- package/profiles/backend/profile.yaml +2 -0
- package/profiles/blog-writer/profile.yaml +10 -0
- package/profiles/browser/profile.yaml +9 -2
- package/profiles/builder/profile.yaml +3 -6
- package/profiles/career/profile.yaml +13 -2
- package/profiles/claude-api/profile.yaml +1 -1
- package/profiles/commerce/profile.yaml +27 -3
- package/profiles/core/logo.png +0 -0
- package/profiles/core/profile.yaml +62 -2
- package/profiles/dash-merge-test/profile.yaml +109 -0
- package/profiles/designer/profile.yaml +2 -0
- package/profiles/designer-medusa-next/profile.yaml +4 -1
- package/profiles/designer-medusa-vite/profile.yaml +4 -1
- package/profiles/docs-writer/profile.yaml +3 -1
- package/profiles/eu-tender-research/README.md +48 -0
- package/profiles/eu-tender-research/logo.png +0 -0
- package/profiles/eu-tender-research/profile.yaml +108 -0
- package/profiles/finance/logo.png +0 -0
- package/profiles/finance/profile.yaml +46 -0
- package/profiles/frontend/profile.yaml +5 -9
- package/profiles/growth/profile.yaml +2 -3
- package/profiles/gstack/profile.yaml +15 -0
- package/profiles/higgsfield/profile.yaml +3 -0
- package/profiles/hyperframes/logo.png +0 -0
- package/profiles/hyperframes/profile.yaml +59 -0
- package/profiles/improver/profile.yaml +88 -0
- package/profiles/marketing/profile.yaml +0 -3
- package/profiles/medusa-dev/profile.yaml +2 -0
- package/profiles/medusa-next/profile.yaml +2 -3
- package/profiles/medusa-vite/profile.yaml +2 -3
- package/profiles/n8n/logo.png +0 -0
- package/profiles/n8n/profile.yaml +50 -0
- package/profiles/nextjs/profile.yaml +2 -3
- package/profiles/ops/profile.yaml +2 -0
- package/profiles/postizz/profile.yaml +13 -3
- package/profiles/python/profile.yaml +3 -0
- package/profiles/research/profile.yaml +3 -1
- package/profiles/schema.json +10 -0
- package/profiles/secops/profile.yaml +2 -0
- package/profiles/seo/profile.yaml +56 -0
- package/profiles/skill-writer/profile.yaml +8 -0
- package/profiles/ssh/profile.yaml +32 -0
- package/profiles/strapi/logo.png +0 -0
- package/profiles/strapi/profile.yaml +45 -0
- package/profiles/stripe/logo.png +0 -0
- package/profiles/stripe/profile.yaml +1 -0
- package/profiles/supabase/logo.png +0 -0
- package/profiles/supabase/profile.yaml +85 -0
- package/profiles/vercel/logo.png +0 -0
- package/profiles/vercel/profile.yaml +25 -1
- package/profiles/vite/profile.yaml +4 -3
- package/profiles/web-frontend-base/profile.yaml +5 -4
- package/profiles/webshop/profile.yaml +23 -5
- package/profiles/x-growth-bot/profile.yaml +44 -0
- package/resources/icons/generate-icons.py +128 -2
- package/resources/mcps/configs/claude.sanitized.json +42 -0
- package/resources/mcps/configs/codex.sanitized.json +7 -0
- package/resources/skills/skills/career/resume-version-manager/SKILL.md +351 -0
- package/resources/skills/skills/career/salary-negotiation-prep/SKILL.md +378 -0
- package/resources/skills/skills/content/pdf/SKILL.md +2 -0
- package/resources/skills/skills/content/postiz-cards/SKILL.md +48 -0
- package/resources/skills/skills/content/postiz-cards/scripts/analytics.sh +38 -0
- package/resources/skills/skills/content/postiz-cards/scripts/card.sh +42 -0
- package/resources/skills/skills/content/postiz-cards/scripts/lint.py +38 -0
- package/resources/skills/skills/design/headless-gif-demo/SKILL.md +1 -1
- package/resources/skills/skills/design/readme-svg-design/SKILL.md +1 -1
- package/resources/skills/skills/eu-funding/grant-outreach/SKILL.md +70 -0
- package/resources/skills/skills/eu-funding/hu-grant-finder/SKILL.md +114 -0
- package/resources/skills/skills/eu-funding/hu-grant-finder/evals.md +26 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/SKILL.md +80 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/evals.md +26 -0
- package/resources/skills/skills/eu-funding/ted-tender-search/scripts/ted-search.sh +46 -0
- package/resources/skills/skills/event-design/wedding-invitations/SKILL.md +1 -1
- package/resources/skills/skills/github/gx-agents/SKILL.md +96 -0
- package/resources/skills/skills/gstack/design-shotgun/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ab-test-analyzer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ab-test-setup-and-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/account-structure-review/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-copy-variant-generator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-extension-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/ad-spend-allocator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/anomaly-detection/SKILL.md +1 -1
- package/resources/skills/skills/marketing/attribution-model-comparison/SKILL.md +1 -1
- package/resources/skills/skills/marketing/audience-overlap-analysis/SKILL.md +7 -1
- package/resources/skills/skills/marketing/bid-strategy-recommendations/SKILL.md +7 -1
- package/resources/skills/skills/marketing/budget-scenario-planner/SKILL.md +6 -1
- package/resources/skills/skills/marketing/campaign-naming-convention-builder/SKILL.md +7 -1
- package/resources/skills/skills/marketing/channel-mix-optimizer/SKILL.md +7 -1
- package/resources/skills/skills/marketing/client-report-narratives/SKILL.md +6 -1
- package/resources/skills/skills/marketing/competitor-creative-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/competitor-teardown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/content-repurposer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/conversion-path-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/cpa-diagnostics/SKILL.md +1 -1
- package/resources/skills/skills/marketing/creative-fatigue-detection/SKILL.md +1 -1
- package/resources/skills/skills/marketing/day-hour-performance-breakdown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/device-performance-split/SKILL.md +1 -1
- package/resources/skills/skills/marketing/e2e-seo-assistant/SKILL.md +1 -1
- package/resources/skills/skills/marketing/email-sequence-writer/SKILL.md +1 -1
- package/resources/skills/skills/marketing/frequency-cap-recommendations/SKILL.md +1 -1
- package/resources/skills/skills/marketing/geo-performance-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/google-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/icp-research-assistant/SKILL.md +1 -1
- package/resources/skills/skills/marketing/keyword-cannibalization-check/SKILL.md +1 -1
- package/resources/skills/skills/marketing/landing-page-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/landing-page-audit-quick/SKILL.md +1 -1
- package/resources/skills/skills/marketing/linkedin-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/meta-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/pacing-monitor/SKILL.md +1 -1
- package/resources/skills/skills/marketing/performance-benchmarking/SKILL.md +1 -1
- package/resources/skills/skills/marketing/programmatic-seo-builder/SKILL.md +1 -1
- package/resources/skills/skills/marketing/quality-score-breakdown/SKILL.md +1 -1
- package/resources/skills/skills/marketing/reddit-ads-audit/SKILL.md +1 -1
- package/resources/skills/skills/marketing/retargeting-window-analysis/SKILL.md +1 -1
- package/resources/skills/skills/marketing/roas-forecasting/SKILL.md +1 -1
- package/resources/skills/skills/marketing/search-term-mining/SKILL.md +1 -1
- package/resources/skills/skills/marketing/utm-tracking-generator/SKILL.md +1 -1
- package/resources/skills/skills/marketing/wasted-spend-finder/SKILL.md +1 -1
- package/resources/skills/skills/marketing/weekly-account-summary/SKILL.md +1 -1
- package/resources/skills/skills/meta/awesome-list-submit/SKILL.md +4 -4
- package/resources/skills/skills/meta/cue-dashboard/SKILL.md +109 -0
- package/resources/skills/skills/meta/cue-developer/SKILL.md +161 -0
- package/resources/skills/skills/meta/cue-developer/evals/evals.json +57 -0
- package/resources/skills/skills/meta/cue-developer/references/architecture.md +65 -0
- package/resources/skills/skills/meta/cue-developer/references/build_and_test.md +72 -0
- package/resources/skills/skills/meta/cue-developer/references/contributing.md +75 -0
- package/resources/skills/skills/meta/cue-developer/references/conventions.md +57 -0
- package/resources/skills/skills/meta/cue-developer/references/first_time_setup.md +51 -0
- package/resources/skills/skills/meta/cue-developer/references/skill_and_mcp_authoring.md +84 -0
- package/resources/skills/skills/meta/cue-developer/references/troubleshooting.md +42 -0
- package/resources/skills/skills/meta/delegation-check/SKILL.md +148 -0
- package/resources/skills/skills/meta/delegation-check/specs/scan-algorithm.md +125 -0
- package/resources/skills/skills/meta/delegation-check/specs/separation-rules.md +190 -0
- package/resources/skills/skills/meta/focus/SKILL.md +62 -0
- package/resources/skills/skills/meta/help/SKILL.md +1 -1
- package/resources/skills/skills/meta/integrity-tags/SKILL.md +2 -0
- package/resources/skills/skills/meta/next-steps/SKILL.md +124 -0
- package/resources/skills/skills/meta/next-steps/evals/eval-set.json +92 -0
- package/resources/skills/skills/meta/profile-from-docs/SKILL.md +141 -0
- package/resources/skills/skills/meta/ralph-loop/SKILL.md +83 -0
- package/resources/skills/skills/meta/ralph-loop/scripts/loop.sh +73 -0
- package/resources/skills/skills/meta/skill-simplify/SKILL.md +136 -0
- package/resources/skills/skills/meta/skill-simplify/phases/01-analysis.md +173 -0
- package/resources/skills/skills/meta/skill-simplify/phases/02-optimize.md +104 -0
- package/resources/skills/skills/meta/skill-simplify/phases/03-check.md +145 -0
- package/resources/skills/skills/meta/smart-loader/scripts/smart-lookup.sh +13 -4
- package/resources/skills/skills/meta/verify-council/SKILL.md +182 -0
- package/resources/skills/skills/meta/verify-council/references/lane-prompts.md +103 -0
- package/resources/skills/skills/meta/verify-council/references/workflow.js +217 -0
- package/resources/skills/skills/nvidia/aiq-research/SKILL.md +1 -1
- package/resources/skills/skills/nvidia/cuopt-developer/SKILL.md +16 -1
- package/resources/skills/skills/nvidia/cuopt-developer/resources/contributing.md +2 -2
- package/resources/skills/skills/nvidia/cuopt-developer/resources/numerical_debugging.md +128 -0
- package/resources/skills/skills/nvidia/cuopt-developer/resources/python_bindings.md +2 -9
- package/resources/skills/skills/nvidia/cuopt-developer/resources/vrp_skills.md +166 -0
- package/resources/skills/skills/nvidia/cuopt-install/SKILL.md +2 -10
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/SKILL.md +3 -23
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/resources/examples.md +40 -20
- package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-python/SKILL.md +5 -1
- package/resources/skills/skills/nvidia/skill-evolution/SKILL.md +4 -5
- package/resources/skills/skills/research/trendradar/SKILL.md +1 -1
- package/resources/skills/skills/ssh/ssh-config/SKILL.md +94 -0
- package/resources/skills/skills/ssh/ssh-copy/SKILL.md +92 -0
- package/resources/skills/skills/ssh/ssh-harden/SKILL.md +108 -0
- package/resources/skills/skills/ssh/ssh-keys/SKILL.md +82 -0
- package/resources/skills/skills/ssh/ssh-paste-image/LICENSE +28 -0
- package/resources/skills/skills/ssh/ssh-paste-image/SKILL.md +149 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/build.sh +29 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/go.mod +3 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/main.go +79 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/ccimgd.service +12 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/com.ccimgd.plist +20 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/go.mod +3 -0
- package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/main.go +98 -0
- package/resources/skills/skills/ssh/ssh-tunnel/SKILL.md +96 -0
- package/resources/skills/skills/strapi/building-with-strapi/SKILL.md +112 -0
- package/resources/skills/skills/strapi/strapi-cli/SKILL.md +93 -0
- package/resources/skills/skills/strapi/strapi-content-api/SKILL.md +115 -0
- package/resources/skills/skills/strapi/strapi-deploy/SKILL.md +89 -0
- package/resources/skills/skills/strapi/strapi-mcp-setup/SKILL.md +101 -0
- package/resources/skills/skills/strapi/strapi-plugins/SKILL.md +97 -0
- package/resources/skills/skills/tools/context7/SKILL.md +101 -0
- package/resources/skills/skills/tools/opensrc/SKILL.md +1 -1
- package/resources/skills/skills/tools/portless/SKILL.md +186 -0
- package/resources/skills/skills/xbot/operate/SKILL.md +229 -0
- package/src/commands/_index.ts +8 -0
- package/src/commands/ai-score.e2e.test.ts +11 -4
- package/src/commands/ai.ts +3 -4
- package/src/commands/auto-detect.ts +1 -1
- package/src/commands/cli.test.ts +1 -2
- package/src/commands/cli.ts +1 -1
- package/src/commands/cloud.ts +1 -1
- package/src/commands/current.ts +1 -4
- package/src/commands/dash.test.ts +110 -0
- package/src/commands/dash.ts +194 -0
- package/src/commands/dashboard.ts +26 -0
- package/src/commands/diff.ts +1 -1
- package/src/commands/discover.test.ts +1 -1
- package/src/commands/discover.ts +90 -40
- package/src/commands/doctor.test.ts +58 -0
- package/src/commands/doctor.ts +79 -3
- package/src/commands/eval-behavior.ts +1 -1
- package/src/commands/eval.ts +2 -2
- package/src/commands/evolve.ts +4 -3
- package/src/commands/failures.test.ts +1 -1
- package/src/commands/features-batch1.test.ts +6 -1
- package/src/commands/icon.ts +1 -5
- package/src/commands/import-profile.ts +1 -1
- package/src/commands/init.ts +50 -7
- package/src/commands/install-sh.e2e.test.ts +65 -0
- package/src/commands/launch-handoff.e2e.test.ts +88 -0
- package/src/commands/launch.e2e.test.ts +8 -1
- package/src/commands/launch.test.ts +29 -0
- package/src/commands/launch.ts +185 -131
- package/src/commands/lock.ts +0 -1
- package/src/commands/marketplace.ts +0 -4
- package/src/commands/materialize.ts +1 -1
- package/src/commands/mem.ts +341 -0
- package/src/commands/optimizer.ts +0 -3
- package/src/commands/playground.ts +1 -2
- package/src/commands/profile-draft-skill.ts +1 -1
- package/src/commands/replay-whatif.ts +1 -6
- package/src/commands/score.ts +2 -2
- package/src/commands/security.test.ts +88 -0
- package/src/commands/security.ts +74 -28
- package/src/commands/shell.test.ts +65 -4
- package/src/commands/shell.ts +67 -7
- package/src/commands/skills-test.ts +0 -1
- package/src/commands/skills.ts +28 -2
- package/src/commands/sources.ts +1 -2
- package/src/commands/status.ts +2 -6
- package/src/commands/submit-profile.ts +1 -1
- package/src/commands/suggest.ts +35 -10
- package/src/commands/trigger-gaps.test.ts +50 -0
- package/src/commands/trigger-gaps.ts +63 -29
- package/src/commands/update.ts +1 -1
- package/src/commands/validate.ts +16 -4
- package/src/commands/watch-live.ts +1 -1
- package/src/commands/workspace.ts +1 -1
- package/src/index.ts +26 -10
- package/src/lib/active-sessions.ts +1 -1
- package/src/lib/agent-adapters.test.ts +100 -0
- package/src/lib/agent-adapters.ts +2 -2
- package/src/lib/analytics.test.ts +88 -0
- package/src/lib/analytics.ts +82 -1
- package/src/lib/auto-detect.test.ts +10 -4
- package/src/lib/auto-detect.ts +19 -23
- package/src/lib/brand-icons.ts +0 -1
- package/src/lib/cache.ts +2 -3
- package/src/lib/claude-mem-env.test.ts +148 -0
- package/src/lib/claude-mem-env.ts +172 -0
- package/src/lib/combo-history.test.ts +53 -0
- package/src/lib/combo-history.ts +83 -0
- package/src/lib/companion-detect.test.ts +108 -0
- package/src/lib/companion-detect.ts +140 -0
- package/src/lib/companion-fetch.ts +4 -6
- package/src/lib/conditional-skills.test.ts +1 -1
- package/src/lib/config-paths.test.ts +53 -0
- package/src/lib/config-paths.ts +33 -0
- package/src/lib/dashboard-server.test.ts +351 -0
- package/src/lib/dashboard-server.ts +1476 -27
- package/src/lib/debug-log.test.ts +66 -0
- package/src/lib/debug-log.ts +45 -0
- package/src/lib/mcp-catalog.test.ts +102 -0
- package/src/lib/mcp-catalog.ts +193 -0
- package/src/lib/pair-suggestions.test.ts +111 -0
- package/src/lib/pair-suggestions.ts +98 -5
- package/src/lib/permissions.test.ts +76 -0
- package/src/lib/permissions.ts +125 -0
- package/src/lib/picker.test.ts +1106 -1
- package/src/lib/picker.ts +1230 -142
- package/src/lib/plugin-discovery.ts +126 -0
- package/src/lib/pr-poster.ts +1 -1
- package/src/lib/pr-throttle.ts +2 -6
- package/src/lib/profile-linter.test.ts +67 -1
- package/src/lib/profile-linter.ts +59 -14
- package/src/lib/profile-loader.test.ts +21 -0
- package/src/lib/profile-loader.ts +22 -3
- package/src/lib/profile-metrics.ts +2 -6
- package/src/lib/profile-names.test.ts +58 -0
- package/src/lib/repos.test.ts +57 -0
- package/src/lib/repos.ts +167 -0
- package/src/lib/resolver-npx.ts +10 -1
- package/src/lib/runtime-materializer.test.ts +200 -3
- package/src/lib/runtime-materializer.ts +129 -20
- package/src/lib/shared-profiles.ts +2 -3
- package/src/lib/skill-clis.test.ts +113 -0
- package/src/lib/skill-clis.ts +232 -0
- package/src/lib/skill-dependencies.ts +9 -1
- package/src/lib/skill-deps.ts +1 -1
- package/src/lib/skill-linter.ts +1 -1
- package/src/lib/skill-quality.ts +0 -1
- package/src/lib/skill-sandbox.test.ts +1 -1
- package/src/lib/skills-lock.test.ts +1 -1
- package/src/lib/telemetry-consent.ts +3 -5
- package/src/lib/telemetry-report.test.ts +2 -2
- package/src/lib/token-budget.ts +111 -0
- package/src/lib/trigger-gaps.test.ts +70 -0
- package/src/lib/trigger-gaps.ts +48 -6
- package/src/lib/tui/data.ts +1 -5
- package/src/lib/workflow-store.ts +150 -0
- package/src/lib/workspace-secrets.ts +0 -4
- package/src/lib/workspaces.ts +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D9 activation check (checkActivation). Injectable opts let us drive it
|
|
3
|
+
* against a throwaway HOME/PATH without touching the real machine.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
6
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import { checkActivation } from "./doctor";
|
|
11
|
+
|
|
12
|
+
let home: string;
|
|
13
|
+
let binDir: string;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
home = mkdtempSync(join(tmpdir(), "cue-doctor-"));
|
|
16
|
+
binDir = join(home, ".local", "bin");
|
|
17
|
+
mkdirSync(binDir, { recursive: true });
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => rmSync(home, { recursive: true, force: true }));
|
|
20
|
+
|
|
21
|
+
function writeShim() {
|
|
22
|
+
writeFileSync(join(binDir, "claude"), '#!/usr/bin/env bash\nexec cue launch claude "$@"\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("checkActivation (D9)", () => {
|
|
26
|
+
test("no shim → D9 warning (gating)", () => {
|
|
27
|
+
const issues = checkActivation({ homeDir: home, realBin: "/usr/bin/claude", pathDirs: [binDir, "/usr/bin"] });
|
|
28
|
+
expect(issues).toHaveLength(1);
|
|
29
|
+
expect(issues[0]!.code).toBe("D9");
|
|
30
|
+
expect(issues[0]!.severity).toBe("warning");
|
|
31
|
+
expect(issues[0]!.message).toContain("shim missing");
|
|
32
|
+
expect(issues[0]!.fix).toBe("cue shell install");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("shim + real bin + ~/.local/bin first on PATH → healthy", () => {
|
|
36
|
+
writeShim();
|
|
37
|
+
const issues = checkActivation({ homeDir: home, realBin: "/usr/bin/claude", pathDirs: [binDir, "/usr/bin"] });
|
|
38
|
+
expect(issues).toHaveLength(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("shim + real bin shadowing the shim on PATH → D9 error", () => {
|
|
42
|
+
writeShim();
|
|
43
|
+
const issues = checkActivation({ homeDir: home, realBin: "/usr/bin/claude", pathDirs: ["/usr/bin", binDir] });
|
|
44
|
+
expect(issues).toHaveLength(1);
|
|
45
|
+
expect(issues[0]!.code).toBe("D9");
|
|
46
|
+
expect(issues[0]!.severity).toBe("error");
|
|
47
|
+
expect(issues[0]!.message).toContain("shadowed");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("shim but no real claude binary → D9 warning", () => {
|
|
51
|
+
writeShim();
|
|
52
|
+
const issues = checkActivation({ homeDir: home, realBin: null, pathDirs: [binDir] });
|
|
53
|
+
expect(issues).toHaveLength(1);
|
|
54
|
+
expect(issues[0]!.code).toBe("D9");
|
|
55
|
+
expect(issues[0]!.severity).toBe("warning");
|
|
56
|
+
expect(issues[0]!.message).toContain("not found");
|
|
57
|
+
});
|
|
58
|
+
});
|
package/src/commands/doctor.ts
CHANGED
|
@@ -16,16 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { readFileSync, existsSync, lstatSync, readlinkSync, readdirSync } from "node:fs";
|
|
19
|
-
import { readFile, writeFile,
|
|
19
|
+
import { readFile, writeFile, rm } from "node:fs/promises";
|
|
20
20
|
import { resolve, dirname, join } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
23
22
|
import { spawnSync } from "node:child_process";
|
|
24
23
|
|
|
25
24
|
import { listProfiles, loadProfile } from "../lib/profile-loader";
|
|
26
25
|
import { listAllSkillIds } from "../lib/resolver-local";
|
|
27
|
-
import { findIncompleteSkills, fetchCompanionFiles,
|
|
26
|
+
import { findIncompleteSkills, fetchCompanionFiles, readSourceFile } from "../lib/companion-fetch";
|
|
28
27
|
import { detectMissingDependencies } from "../lib/skill-dependencies";
|
|
28
|
+
import { shimInstalled, runInstall } from "./shell";
|
|
29
|
+
import { findRealClaudeBin } from "../lib/claude-binary";
|
|
30
|
+
import { homedir } from "node:os";
|
|
29
31
|
|
|
30
32
|
const REPO_ROOT = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
31
33
|
const PROFILES_DIR = process.env.CUE_PROFILES_DIR ?? join(REPO_ROOT, "profiles");
|
|
@@ -202,6 +204,67 @@ async function checkProfile(profileName: string, allSkillIds: Set<string>, allMc
|
|
|
202
204
|
return issues;
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
/**
|
|
208
|
+
* D9 — Activation health (environment-scoped, runs once, not per profile).
|
|
209
|
+
* Verifies the claude shim is installed, the real binary resolves, and
|
|
210
|
+
* ~/.local/bin precedes it on PATH. Injectable for testing.
|
|
211
|
+
*/
|
|
212
|
+
export function checkActivation(
|
|
213
|
+
opts: { homeDir?: string; pathDirs?: string[]; realBin?: string | null } = {},
|
|
214
|
+
): Issue[] {
|
|
215
|
+
const issues: Issue[] = [];
|
|
216
|
+
const PROF = "(activation)";
|
|
217
|
+
|
|
218
|
+
// 1. The claude shim must be installed (and be a cue shim). Gating check —
|
|
219
|
+
// the rest is moot without it.
|
|
220
|
+
if (!shimInstalled(opts.homeDir)) {
|
|
221
|
+
// Warning, not error: a user may legitimately not have run `cue shell
|
|
222
|
+
// install` yet (or use `cue launch` directly). Surfacing it shouldn't flip
|
|
223
|
+
// `cue doctor`'s exit code, which should track actual profile breakage.
|
|
224
|
+
issues.push({
|
|
225
|
+
code: "D9",
|
|
226
|
+
severity: "warning",
|
|
227
|
+
profile: PROF,
|
|
228
|
+
message: "~/.local/bin/claude shim missing or not a cue shim — `claude` won't load profiles (run `cue shell install`)",
|
|
229
|
+
fix: "cue shell install",
|
|
230
|
+
});
|
|
231
|
+
return issues;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 2. The real claude binary must resolve.
|
|
235
|
+
const realBin = opts.realBin !== undefined ? opts.realBin : findRealClaudeBin();
|
|
236
|
+
if (!realBin) {
|
|
237
|
+
issues.push({
|
|
238
|
+
code: "D9",
|
|
239
|
+
severity: "warning",
|
|
240
|
+
profile: PROF,
|
|
241
|
+
message: "Real claude binary not found on PATH (only the cue shim resolves)",
|
|
242
|
+
fix: "Install Claude Code, or set CUE_REAL_CLAUDE",
|
|
243
|
+
});
|
|
244
|
+
return issues;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 3. ~/.local/bin must precede the real binary's dir on PATH (else the real
|
|
248
|
+
// binary shadows the shim). Only meaningful when both dirs are on PATH.
|
|
249
|
+
const home = opts.homeDir ?? homedir();
|
|
250
|
+
const shimDir = join(home, ".local", "bin");
|
|
251
|
+
const pathDirs = opts.pathDirs ?? (process.env.PATH ?? "").split(":");
|
|
252
|
+
const shimIdx = pathDirs.indexOf(shimDir);
|
|
253
|
+
const realDir = resolve(realBin, "..");
|
|
254
|
+
const realIdx = pathDirs.indexOf(realDir);
|
|
255
|
+
if (shimIdx >= 0 && realIdx >= 0 && shimIdx > realIdx) {
|
|
256
|
+
issues.push({
|
|
257
|
+
code: "D9",
|
|
258
|
+
severity: "error",
|
|
259
|
+
profile: PROF,
|
|
260
|
+
message: `Real binary dir ${realDir} precedes ${shimDir} on PATH — the shim is shadowed`,
|
|
261
|
+
fix: "Reorder PATH so ~/.local/bin comes before the real claude/codex",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return issues;
|
|
266
|
+
}
|
|
267
|
+
|
|
205
268
|
async function applyFix(issue: Issue): Promise<boolean> {
|
|
206
269
|
const yamlPath = join(PROFILES_DIR, issue.profile, "profile.yaml");
|
|
207
270
|
|
|
@@ -287,6 +350,14 @@ async function applyFix(issue: Issue): Promise<boolean> {
|
|
|
287
350
|
});
|
|
288
351
|
return fetched.length > 0;
|
|
289
352
|
}
|
|
353
|
+
case "D9": {
|
|
354
|
+
// Install/repair the shim. runInstall returns 1 (no throw) when PATH
|
|
355
|
+
// ordering is wrong — that case can't be auto-fixed (user must reorder
|
|
356
|
+
// their shell PATH), so success is keyed on rc === 0.
|
|
357
|
+
const realBin = findRealClaudeBin();
|
|
358
|
+
const rc = await runInstall({ realClaude: realBin ?? undefined });
|
|
359
|
+
return rc === 0;
|
|
360
|
+
}
|
|
290
361
|
default:
|
|
291
362
|
return false;
|
|
292
363
|
}
|
|
@@ -311,6 +382,7 @@ Checks:
|
|
|
311
382
|
D6 Broken symlink in runtime
|
|
312
383
|
D7 Incomplete skill (companions declared but missing)
|
|
313
384
|
D8 Quality gate declared but the .sh under resources/quality-gates/ is missing
|
|
385
|
+
D9 Activation: claude shim installed, real claude resolves, ~/.local/bin precedes it
|
|
314
386
|
|
|
315
387
|
Flags:
|
|
316
388
|
--fix Auto-repair issues
|
|
@@ -363,6 +435,10 @@ Examples:
|
|
|
363
435
|
} catch { /* skip */ }
|
|
364
436
|
}
|
|
365
437
|
|
|
438
|
+
// D9: activation health (shim + real binary + PATH order). Environment-
|
|
439
|
+
// scoped, so run once regardless of --profile.
|
|
440
|
+
issues.push(...checkActivation());
|
|
441
|
+
|
|
366
442
|
// D3 only when checking all profiles, and only if no profile globs everything.
|
|
367
443
|
if (!targetProfile && !hasGlobAll) {
|
|
368
444
|
for (const id of allSkillIds) {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Output: per-scenario pass/fail + a single summary score per profile.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { readFileSync,
|
|
16
|
+
import { readFileSync, } from "node:fs";
|
|
17
17
|
import { join, resolve, dirname, isAbsolute } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { homedir } from "node:os";
|
package/src/commands/eval.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* cost-per-message and the score now use perMessage so they reflect reality.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { resolve, join, dirname,
|
|
19
|
-
import { readFileSync,
|
|
18
|
+
import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
19
|
+
import { readFileSync, } from "node:fs";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
|
package/src/commands/evolve.ts
CHANGED
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
} from "node:fs";
|
|
20
20
|
import { join, resolve, dirname } from "node:path";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
|
+
import { configDir } from "../lib/config-paths";
|
|
22
23
|
|
|
23
|
-
import { loadProfile,
|
|
24
|
+
import { loadProfile, } from "../lib/profile-loader";
|
|
24
25
|
import { resolveProfileForCwd } from "../lib/cwd-resolver";
|
|
25
26
|
|
|
26
27
|
const CONFIG_DIR = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "cue");
|
|
@@ -249,11 +250,11 @@ export async function run(args: string[]): Promise<number> {
|
|
|
249
250
|
if (args.includes("--history")) { showHistory(); return 0; }
|
|
250
251
|
|
|
251
252
|
// Resolve profile
|
|
252
|
-
const
|
|
253
|
+
const cfgDir = configDir();
|
|
253
254
|
const explicit = args.find(a => !a.startsWith("-"));
|
|
254
255
|
let profileName = explicit;
|
|
255
256
|
if (!profileName) {
|
|
256
|
-
const resolved = await resolveProfileForCwd({ cwd: process.cwd(), homeDir: homedir(), configDir });
|
|
257
|
+
const resolved = await resolveProfileForCwd({ cwd: process.cwd(), homeDir: homedir(), configDir: cfgDir });
|
|
257
258
|
profileName = "profile" in resolved ? resolved.profile : "core";
|
|
258
259
|
}
|
|
259
260
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
7
7
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
8
|
-
import { tmpdir,
|
|
8
|
+
import { tmpdir, } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
11
|
import { run as failuresRun } from "./failures";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
6
|
-
import { mkdirSync,
|
|
6
|
+
import { mkdirSync, rmSync, readFileSync, existsSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
|
|
@@ -16,9 +16,14 @@ let tmp: string;
|
|
|
16
16
|
beforeEach(() => {
|
|
17
17
|
tmp = `${tmpdir()}/cue-features-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
18
18
|
mkdirSync(tmp, { recursive: true });
|
|
19
|
+
// Point `cue suggest`'s session scan at the empty tmp dir so the suggest
|
|
20
|
+
// tests stay hermetic + fast — otherwise they scan the real ~/.claude
|
|
21
|
+
// history (minutes on a heavy machine), which hung the whole suite.
|
|
22
|
+
process.env.CUE_SUGGEST_SESSIONS_DIR = tmp;
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
afterEach(() => {
|
|
26
|
+
delete process.env.CUE_SUGGEST_SESSIONS_DIR;
|
|
22
27
|
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
|
|
23
28
|
});
|
|
24
29
|
|
package/src/commands/icon.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as p from "@clack/prompts";
|
|
|
10
10
|
import { resolveProfileForCwd } from "../lib/cwd-resolver";
|
|
11
11
|
import { loadProfile } from "../lib/profile-loader";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
|
+
import { configDir } from "../lib/config-paths";
|
|
13
14
|
|
|
14
15
|
const REPO_ROOT = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
15
16
|
|
|
@@ -19,11 +20,6 @@ const ICONS = [
|
|
|
19
20
|
"🦈", "🐊", "🦅", "🐎", "🦁", "🐘",
|
|
20
21
|
];
|
|
21
22
|
|
|
22
|
-
function configDir(): string {
|
|
23
|
-
return process.env.XDG_CONFIG_HOME
|
|
24
|
-
? join(process.env.XDG_CONFIG_HOME, "cue")
|
|
25
|
-
: join(homedir(), ".config", "cue");
|
|
26
|
-
}
|
|
27
23
|
|
|
28
24
|
export async function run(args: string[]): Promise<number> {
|
|
29
25
|
const profileName = args[0] ?? await resolveCurrentProfile();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
7
|
-
import { resolve, dirname, join,
|
|
7
|
+
import { resolve, dirname, join, } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
10
|
import { loadProfile } from "../lib/profile-loader";
|
package/src/commands/init.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { detectProfile } from "../lib/auto-detect";
|
|
|
18
18
|
import { scanProject } from "../lib/project-scanner";
|
|
19
19
|
import { listProfiles } from "../lib/profile-loader";
|
|
20
20
|
import { getCachedGemsForProfile, autoInstallClis } from "./discover";
|
|
21
|
+
import { shimInstalled, runInstall } from "./shell";
|
|
22
|
+
import { gateFreshSkill } from "./security";
|
|
21
23
|
import {
|
|
22
24
|
configDir,
|
|
23
25
|
enable as enableTelemetry,
|
|
@@ -67,11 +69,6 @@ export async function runGlobalOnboarding(): Promise<boolean> {
|
|
|
67
69
|
hint: "recommended — minimal base plus skill management",
|
|
68
70
|
},
|
|
69
71
|
{ value: "core", label: "core only", hint: "smallest — just the base" },
|
|
70
|
-
{
|
|
71
|
-
value: "core+skill-writer+ecc",
|
|
72
|
-
label: "core + skill-writer + ecc",
|
|
73
|
-
hint: "+ workspace conventions (CLAUDE.md / AGENTS.md)",
|
|
74
|
-
},
|
|
75
72
|
{ value: "__custom", label: "Custom…", hint: "type a +-separated composite" },
|
|
76
73
|
{ value: "__skip", label: "Skip for now", hint: "falls back to plain `core`" },
|
|
77
74
|
],
|
|
@@ -82,7 +79,7 @@ export async function runGlobalOnboarding(): Promise<boolean> {
|
|
|
82
79
|
let defaultComposite: string | null = null;
|
|
83
80
|
if (defaultPick === "__custom") {
|
|
84
81
|
const custom = await p.text({
|
|
85
|
-
message: "Composite (e.g., core+skill-writer+
|
|
82
|
+
message: "Composite (e.g., core+skill-writer+backend):",
|
|
86
83
|
placeholder: "core+skill-writer",
|
|
87
84
|
validate: (v) => {
|
|
88
85
|
const parts = (v ?? "").split("+").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -160,14 +157,58 @@ async function offerDiscoverGems(profile: string): Promise<void> {
|
|
|
160
157
|
const install = await p.confirm({ message: "Install these gems?" });
|
|
161
158
|
if (p.isCancel(install) || !install) return;
|
|
162
159
|
|
|
160
|
+
let flagged = 0;
|
|
163
161
|
for (const g of gems) {
|
|
164
162
|
p.log.step(`Installing ${g.full_name}...`);
|
|
165
163
|
spawnSync("npx", ["skills", "add", g.full_name, "-a", "claude-code", "-y"], {
|
|
166
164
|
encoding: "utf8", timeout: 60000, stdio: ["ignore", "pipe", "pipe"],
|
|
167
165
|
});
|
|
166
|
+
// Security gate: flag a just-fetched skill with critical findings and skip
|
|
167
|
+
// its CLI auto-install (the gem is installed to ~/.claude/skills, but the
|
|
168
|
+
// wizard does not auto-register it to a profile).
|
|
169
|
+
const gate = gateFreshSkill(g.name);
|
|
170
|
+
if (!gate.ok) {
|
|
171
|
+
flagged++;
|
|
172
|
+
p.log.error(`${g.full_name}: ${gate.critical.length} critical security finding(s) — review before use.`);
|
|
173
|
+
for (const c of gate.critical) p.log.message(` [${c.code}] ${c.message}`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!gate.scanned) {
|
|
177
|
+
p.log.warn(`${g.full_name}: no SKILL.md found to scan — review manually.`);
|
|
178
|
+
}
|
|
168
179
|
autoInstallClis(g.name);
|
|
169
180
|
}
|
|
170
|
-
p.log.success(`Installed ${gems.length} gem(s).`);
|
|
181
|
+
p.log.success(`Installed ${gems.length} gem(s) to ~/.claude/skills${flagged > 0 ? ` (${flagged} flagged by the security scan — review before use)` : ""}.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Offer to install the shell shims if they're missing. Without the
|
|
186
|
+
* `~/.local/bin/claude` shim, typing `claude` runs vanilla Claude Code and
|
|
187
|
+
* the pinned profile is never loaded — the #1 "I followed the docs and
|
|
188
|
+
* nothing happened" failure. Detect it here and offer the one-time fix.
|
|
189
|
+
*/
|
|
190
|
+
async function ensureShim(): Promise<void> {
|
|
191
|
+
if (shimInstalled()) return;
|
|
192
|
+
p.log.warn(
|
|
193
|
+
"The `claude`/`codex` shim isn't installed yet — without it, launching `claude` runs vanilla Claude Code and won't load this profile.",
|
|
194
|
+
);
|
|
195
|
+
const install = await p.confirm({
|
|
196
|
+
message: "Install the shell shim now? (writes ~/.local/bin/claude)",
|
|
197
|
+
});
|
|
198
|
+
if (p.isCancel(install) || !install) {
|
|
199
|
+
p.log.message("Skipped — run `cue shell install` later to activate profile loading.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const code = await runInstall();
|
|
204
|
+
if (code === 0) {
|
|
205
|
+
p.log.success("Shim installed to ~/.local/bin. Make sure it's earlier on your PATH than the real claude/codex.");
|
|
206
|
+
} else {
|
|
207
|
+
p.log.warn("Shim install reported an issue — run `cue shell install` manually for details.");
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
p.log.warn("Couldn't install the shim automatically — run `cue shell install` manually.");
|
|
211
|
+
}
|
|
171
212
|
}
|
|
172
213
|
|
|
173
214
|
export async function run(args: string[]): Promise<number> {
|
|
@@ -264,6 +305,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
264
305
|
|
|
265
306
|
writeFileSync(join(cwd, ".cue-profile"), (name as string) + "\n");
|
|
266
307
|
await offerDiscoverGems(name as string);
|
|
308
|
+
await ensureShim();
|
|
267
309
|
p.outro(`✅ Created profile "${name}" and pinned to this directory.`);
|
|
268
310
|
return 0;
|
|
269
311
|
}
|
|
@@ -271,6 +313,7 @@ export async function run(args: string[]): Promise<number> {
|
|
|
271
313
|
// Pin the chosen profile
|
|
272
314
|
writeFileSync(join(cwd, ".cue-profile"), (choice as string) + "\n");
|
|
273
315
|
await offerDiscoverGems(choice as string);
|
|
316
|
+
await ensureShim();
|
|
274
317
|
p.outro(`✅ Pinned "${choice}" to this directory. Next \`claude\` launch will use it.`);
|
|
275
318
|
return 0;
|
|
276
319
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke test for install.sh — proves the shell install path that every new
|
|
3
|
+
* user depends on: it symlinks `cue` onto PATH, writes a working `claude`
|
|
4
|
+
* shim, and `cue --version` runs through the shim dir.
|
|
5
|
+
*
|
|
6
|
+
* Hermetic: installs into a throwaway SHIM_DIR with a stub `authmux` on PATH so
|
|
7
|
+
* install.sh's Step 5 never runs `npm install -g authmux` (no network).
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { mkdtempSync, writeFileSync, chmodSync, existsSync, readFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
const REPO_ROOT = join(import.meta.dir, "..", "..");
|
|
16
|
+
// CI-only: install.sh does `cd CUE_DIR && bun install`, which would mutate the
|
|
17
|
+
// developer's real node_modules and may hit the network on a cold cache. CI
|
|
18
|
+
// runs on a fresh checkout where that's fine and is the canonical place to
|
|
19
|
+
// prove the shell install path; locally we skip to keep the working tree clean.
|
|
20
|
+
const CAN_RUN =
|
|
21
|
+
!!process.env.CI &&
|
|
22
|
+
process.platform !== "win32" &&
|
|
23
|
+
spawnSync("bash", ["--version"], { encoding: "utf8" }).status === 0;
|
|
24
|
+
|
|
25
|
+
describe.skipIf(!CAN_RUN)("install.sh smoke", () => {
|
|
26
|
+
let shimDir: string;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
shimDir = mkdtempSync(join(tmpdir(), "cue-installsh-"));
|
|
29
|
+
// Stub authmux so `command -v authmux` short-circuits Step 5 (no npm -g).
|
|
30
|
+
const stub = join(shimDir, "authmux");
|
|
31
|
+
writeFileSync(stub, "#!/usr/bin/env bash\necho 0.0.0\n");
|
|
32
|
+
chmodSync(stub, 0o755);
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => rmSync(shimDir, { recursive: true, force: true }));
|
|
35
|
+
|
|
36
|
+
test("symlinks cue, writes a working claude shim, and cue --version runs through it", () => {
|
|
37
|
+
const env = {
|
|
38
|
+
...process.env,
|
|
39
|
+
SHIM_DIR: shimDir,
|
|
40
|
+
CUE_DIR: REPO_ROOT,
|
|
41
|
+
PATH: `${shimDir}:${process.env.PATH ?? ""}`,
|
|
42
|
+
};
|
|
43
|
+
const res = spawnSync("bash", [join(REPO_ROOT, "install.sh"), "--yes"], {
|
|
44
|
+
encoding: "utf8",
|
|
45
|
+
timeout: 120000,
|
|
46
|
+
env,
|
|
47
|
+
});
|
|
48
|
+
expect(res.status).toBe(0);
|
|
49
|
+
|
|
50
|
+
// cue is exposed on PATH (symlink to bin/cue).
|
|
51
|
+
expect(existsSync(join(shimDir, "cue"))).toBe(true);
|
|
52
|
+
|
|
53
|
+
// claude shim routes through cue.
|
|
54
|
+
const claudeShim = join(shimDir, "claude");
|
|
55
|
+
expect(existsSync(claudeShim)).toBe(true);
|
|
56
|
+
expect(readFileSync(claudeShim, "utf8")).toContain("exec cue launch claude");
|
|
57
|
+
|
|
58
|
+
// `cue --version` works through the installed symlink and matches package.json.
|
|
59
|
+
const pkgVersion = JSON.parse(readFileSync(join(REPO_ROOT, "package.json"), "utf8")).version;
|
|
60
|
+
const ver = spawnSync(join(shimDir, "cue"), ["--version"], { encoding: "utf8", env, timeout: 20000 });
|
|
61
|
+
expect(ver.status).toBe(0);
|
|
62
|
+
expect(ver.stdout.trim()).toBe(pkgVersion);
|
|
63
|
+
expect(ver.stdout.trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2e coverage for the launch EXEC HANDOFF — the lines that decide whether
|
|
3
|
+
* `claude`/`codex` actually starts with the right env. Uses `--dry-run` (which
|
|
4
|
+
* builds childEnv + the exec plan and prints it as JSON without exec'ing) and a
|
|
5
|
+
* direct recursion-guard probe.
|
|
6
|
+
*
|
|
7
|
+
* Kept in its own file (not launch.e2e.test.ts) to stay additive while that
|
|
8
|
+
* file is being edited concurrently.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
11
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
const CUE_BIN = join(import.meta.dir, "../index.ts");
|
|
17
|
+
const BUN_SPAWNABLE = spawnSync("bun", ["--version"], { encoding: "utf8" }).status === 0;
|
|
18
|
+
|
|
19
|
+
function cue(args: string[], env: Record<string, string> = {}): { status: number; stdout: string; stderr: string } {
|
|
20
|
+
const cleanEnv = { ...process.env, ...env };
|
|
21
|
+
delete cleanEnv.CUE_LAUNCHING;
|
|
22
|
+
delete cleanEnv.CLAUDE_CONFIG_DIR;
|
|
23
|
+
const res = spawnSync("bun", ["run", CUE_BIN, ...args], { encoding: "utf8", timeout: 20000, env: cleanEnv });
|
|
24
|
+
return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function plan(stdout: string): any {
|
|
28
|
+
return JSON.parse(stdout.match(/\{[\s\S]*\}/)![0]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue launch --dry-run exec handoff", () => {
|
|
32
|
+
let xdg: string;
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
xdg = await mkdtemp(join(tmpdir(), "cue-handoff-"));
|
|
35
|
+
});
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
await rm(xdg, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("claude → CLAUDE_CONFIG_DIR points at the materialized runtime", () => {
|
|
41
|
+
const r = cue(["launch", "claude", "--cue-profile", "core", "--dry-run"], { XDG_CONFIG_HOME: xdg });
|
|
42
|
+
expect(r.status).toBe(0);
|
|
43
|
+
const p = plan(r.stdout);
|
|
44
|
+
const expected = join(xdg, "cue", "runtime", "core", "claude");
|
|
45
|
+
expect(p.agent).toBe("claude-code");
|
|
46
|
+
expect(p.env.CLAUDE_CONFIG_DIR).toBe(expected);
|
|
47
|
+
expect(p.runtimeDir).toBe(expected);
|
|
48
|
+
expect(p.command).toEqual(["claude"]);
|
|
49
|
+
expect(p.env.CODEX_HOME).toBeUndefined();
|
|
50
|
+
// NOTE: CUE_LAUNCHING is intentionally absent from the dry-run JSON (only
|
|
51
|
+
// env[envKey] is serialized); it's covered by the recursion-guard test.
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("codex → CODEX_HOME points at the codex runtime", () => {
|
|
55
|
+
const r = cue(["launch", "codex", "--cue-profile", "core", "--dry-run"], { XDG_CONFIG_HOME: xdg });
|
|
56
|
+
expect(r.status).toBe(0);
|
|
57
|
+
const p = plan(r.stdout);
|
|
58
|
+
const expected = join(xdg, "cue", "runtime", "core", "codex");
|
|
59
|
+
expect(p.agent).toBe("codex");
|
|
60
|
+
expect(p.env.CODEX_HOME).toBe(expected);
|
|
61
|
+
expect(p.command).toEqual(["codex"]);
|
|
62
|
+
expect(p.env.CLAUDE_CONFIG_DIR).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("passthrough args flow into the exec command", () => {
|
|
66
|
+
const r = cue(["launch", "claude", "--cue-profile", "core", "--dry-run", "--resume", "foo"], { XDG_CONFIG_HOME: xdg });
|
|
67
|
+
expect(r.status).toBe(0);
|
|
68
|
+
const p = plan(r.stdout);
|
|
69
|
+
expect(p.command).toEqual(["claude", "--resume", "foo"]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue launch recursion guard", () => {
|
|
74
|
+
test("CUE_LAUNCHING=1 aborts with exit 2 (shim recursion)", () => {
|
|
75
|
+
// Must NOT use the cue() helper — it strips CUE_LAUNCHING. Spawn directly
|
|
76
|
+
// with CUE_LAUNCHING=1 set (and CLAUDE_CONFIG_DIR cleared to avoid the
|
|
77
|
+
// unrelated account-alias → picker path).
|
|
78
|
+
const env = { ...process.env, CUE_LAUNCHING: "1" };
|
|
79
|
+
delete env.CLAUDE_CONFIG_DIR;
|
|
80
|
+
const res = spawnSync("bun", ["run", CUE_BIN, "launch", "claude", "--cue-profile", "core", "--dry-run"], {
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
timeout: 15000,
|
|
83
|
+
env,
|
|
84
|
+
});
|
|
85
|
+
expect(res.status).toBe(2);
|
|
86
|
+
expect(res.stderr).toContain("shim recursion detected");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -11,6 +11,13 @@ import { spawnSync } from "node:child_process";
|
|
|
11
11
|
|
|
12
12
|
const CUE_BIN = join(import.meta.dir, "../index.ts");
|
|
13
13
|
|
|
14
|
+
// These e2e tests shell out to `bun run`. In some sandboxes (and odd PATH
|
|
15
|
+
// setups) a spawned child can't find `bun`, which would hard-fail the suite
|
|
16
|
+
// with "Executable not found in $PATH: bun" — unrelated to what's under test.
|
|
17
|
+
// Skip the whole describe when a child `bun` can't be spawned. CI installs bun
|
|
18
|
+
// via setup-bun, so this only skips in constrained local/sandbox runs.
|
|
19
|
+
const BUN_SPAWNABLE = spawnSync("bun", ["--version"], { encoding: "utf8" }).status === 0;
|
|
20
|
+
|
|
14
21
|
function cue(args: string[], opts: { cwd?: string; env?: Record<string, string> } = {}): { status: number; stdout: string; stderr: string } {
|
|
15
22
|
// Strip env vars set when the test runner itself is running inside a cue
|
|
16
23
|
// session — they propagate to the child cue invocation and break it in
|
|
@@ -29,7 +36,7 @@ function cue(args: string[], opts: { cwd?: string; env?: Record<string, string>
|
|
|
29
36
|
return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
describe("cue launch e2e", () => {
|
|
39
|
+
describe.skipIf(!BUN_SPAWNABLE)("cue launch e2e", () => {
|
|
33
40
|
let tmpDir: string;
|
|
34
41
|
|
|
35
42
|
beforeEach(async () => {
|
|
@@ -219,6 +219,22 @@ describe("buildPickerSections", () => {
|
|
|
219
219
|
expect(composite?.label).toBe("backend + designer");
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
+
test("recent composite reuses each part's icon label, not bare names", () => {
|
|
223
|
+
// Combined recent rows should carry every part's icon (emoji or kitty image
|
|
224
|
+
// placeholder), pulled from each part's own option label.
|
|
225
|
+
const all = [
|
|
226
|
+
{ value: "improver", label: "📈 improver", hint: "" },
|
|
227
|
+
{ value: "secops", label: "🔒 secops", hint: "" },
|
|
228
|
+
{ value: "builder", label: "🐻 builder", hint: "" },
|
|
229
|
+
];
|
|
230
|
+
const recent = [
|
|
231
|
+
{ name: "improver+secops+builder", sessions: 4, lastUsed: "2026-05-26T09:00:00Z" },
|
|
232
|
+
];
|
|
233
|
+
const out = buildPickerSections(opt("__default"), all, recent, 3, now);
|
|
234
|
+
const composite = out.find((o) => o.value === "improver+secops+builder");
|
|
235
|
+
expect(composite?.label).toBe("📈 improver + 🔒 secops + 🐻 builder");
|
|
236
|
+
});
|
|
237
|
+
|
|
222
238
|
test("featured: composites synthesized, single profiles reuse their option and leave All", () => {
|
|
223
239
|
const all = [opt("backend"), opt("designer"), opt("webshop")];
|
|
224
240
|
const out = buildPickerSections(
|
|
@@ -510,6 +526,19 @@ describe("formatProfileSummary", () => {
|
|
|
510
526
|
expect(out[0]).toBe("skills 6 ← 🧬 writer:2 + 🐢 core:2");
|
|
511
527
|
});
|
|
512
528
|
|
|
529
|
+
test("caps a fat composite breakdown at 6 parts with a '+N more' suffix", () => {
|
|
530
|
+
const names = ["a", "b", "c", "d", "e", "f", "g"]; // 7 parts → cap kicks in
|
|
531
|
+
const main = makeProfile({
|
|
532
|
+
name: names.join("+"),
|
|
533
|
+
skills: { local: names.map((n) => ({ id: `${n}/1` })), npx: [] },
|
|
534
|
+
});
|
|
535
|
+
const parts = names.map((n) =>
|
|
536
|
+
makeProfile({ name: n, skills: { local: [{ id: `${n}/1` }], npx: [] } }),
|
|
537
|
+
);
|
|
538
|
+
const out = formatProfileSummary(main, parts);
|
|
539
|
+
expect(out[0]).toBe("skills 7 ← a:1 + b:1 + c:1 + d:1 + e:1 + f:1 +1 more");
|
|
540
|
+
});
|
|
541
|
+
|
|
513
542
|
test("adds category line below skills when localCount >= 5", () => {
|
|
514
543
|
const profile = makeProfile({
|
|
515
544
|
skills: {
|