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.
Files changed (310) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +82 -33
  3. package/bin/cue-review-progress +107 -0
  4. package/bin/cue-review-watch +98 -0
  5. package/dist/cue.js +7352 -3744
  6. package/package.json +16 -5
  7. package/profiles/_types.ts +9 -0
  8. package/profiles/backend/profile.yaml +2 -0
  9. package/profiles/blog-writer/profile.yaml +10 -0
  10. package/profiles/browser/profile.yaml +9 -2
  11. package/profiles/builder/profile.yaml +3 -6
  12. package/profiles/career/profile.yaml +13 -2
  13. package/profiles/claude-api/profile.yaml +1 -1
  14. package/profiles/commerce/profile.yaml +27 -3
  15. package/profiles/core/logo.png +0 -0
  16. package/profiles/core/profile.yaml +62 -2
  17. package/profiles/dash-merge-test/profile.yaml +109 -0
  18. package/profiles/designer/profile.yaml +2 -0
  19. package/profiles/designer-medusa-next/profile.yaml +4 -1
  20. package/profiles/designer-medusa-vite/profile.yaml +4 -1
  21. package/profiles/docs-writer/profile.yaml +3 -1
  22. package/profiles/eu-tender-research/README.md +48 -0
  23. package/profiles/eu-tender-research/logo.png +0 -0
  24. package/profiles/eu-tender-research/profile.yaml +108 -0
  25. package/profiles/finance/logo.png +0 -0
  26. package/profiles/finance/profile.yaml +46 -0
  27. package/profiles/frontend/profile.yaml +5 -9
  28. package/profiles/growth/profile.yaml +2 -3
  29. package/profiles/gstack/profile.yaml +15 -0
  30. package/profiles/higgsfield/profile.yaml +3 -0
  31. package/profiles/hyperframes/logo.png +0 -0
  32. package/profiles/hyperframes/profile.yaml +59 -0
  33. package/profiles/improver/profile.yaml +88 -0
  34. package/profiles/marketing/profile.yaml +0 -3
  35. package/profiles/medusa-dev/profile.yaml +2 -0
  36. package/profiles/medusa-next/profile.yaml +2 -3
  37. package/profiles/medusa-vite/profile.yaml +2 -3
  38. package/profiles/n8n/logo.png +0 -0
  39. package/profiles/n8n/profile.yaml +50 -0
  40. package/profiles/nextjs/profile.yaml +2 -3
  41. package/profiles/ops/profile.yaml +2 -0
  42. package/profiles/postizz/profile.yaml +13 -3
  43. package/profiles/python/profile.yaml +3 -0
  44. package/profiles/research/profile.yaml +3 -1
  45. package/profiles/schema.json +10 -0
  46. package/profiles/secops/profile.yaml +2 -0
  47. package/profiles/seo/profile.yaml +56 -0
  48. package/profiles/skill-writer/profile.yaml +8 -0
  49. package/profiles/ssh/profile.yaml +32 -0
  50. package/profiles/strapi/logo.png +0 -0
  51. package/profiles/strapi/profile.yaml +45 -0
  52. package/profiles/stripe/logo.png +0 -0
  53. package/profiles/stripe/profile.yaml +1 -0
  54. package/profiles/supabase/logo.png +0 -0
  55. package/profiles/supabase/profile.yaml +85 -0
  56. package/profiles/vercel/logo.png +0 -0
  57. package/profiles/vercel/profile.yaml +25 -1
  58. package/profiles/vite/profile.yaml +4 -3
  59. package/profiles/web-frontend-base/profile.yaml +5 -4
  60. package/profiles/webshop/profile.yaml +23 -5
  61. package/profiles/x-growth-bot/profile.yaml +44 -0
  62. package/resources/icons/generate-icons.py +128 -2
  63. package/resources/mcps/configs/claude.sanitized.json +42 -0
  64. package/resources/mcps/configs/codex.sanitized.json +7 -0
  65. package/resources/skills/skills/career/resume-version-manager/SKILL.md +351 -0
  66. package/resources/skills/skills/career/salary-negotiation-prep/SKILL.md +378 -0
  67. package/resources/skills/skills/content/pdf/SKILL.md +2 -0
  68. package/resources/skills/skills/content/postiz-cards/SKILL.md +48 -0
  69. package/resources/skills/skills/content/postiz-cards/scripts/analytics.sh +38 -0
  70. package/resources/skills/skills/content/postiz-cards/scripts/card.sh +42 -0
  71. package/resources/skills/skills/content/postiz-cards/scripts/lint.py +38 -0
  72. package/resources/skills/skills/design/headless-gif-demo/SKILL.md +1 -1
  73. package/resources/skills/skills/design/readme-svg-design/SKILL.md +1 -1
  74. package/resources/skills/skills/eu-funding/grant-outreach/SKILL.md +70 -0
  75. package/resources/skills/skills/eu-funding/hu-grant-finder/SKILL.md +114 -0
  76. package/resources/skills/skills/eu-funding/hu-grant-finder/evals.md +26 -0
  77. package/resources/skills/skills/eu-funding/ted-tender-search/SKILL.md +80 -0
  78. package/resources/skills/skills/eu-funding/ted-tender-search/evals.md +26 -0
  79. package/resources/skills/skills/eu-funding/ted-tender-search/scripts/ted-search.sh +46 -0
  80. package/resources/skills/skills/event-design/wedding-invitations/SKILL.md +1 -1
  81. package/resources/skills/skills/github/gx-agents/SKILL.md +96 -0
  82. package/resources/skills/skills/gstack/design-shotgun/SKILL.md +1 -1
  83. package/resources/skills/skills/marketing/ab-test-analyzer/SKILL.md +1 -1
  84. package/resources/skills/skills/marketing/ab-test-setup-and-analysis/SKILL.md +1 -1
  85. package/resources/skills/skills/marketing/account-structure-review/SKILL.md +1 -1
  86. package/resources/skills/skills/marketing/ad-copy-variant-generator/SKILL.md +1 -1
  87. package/resources/skills/skills/marketing/ad-extension-audit/SKILL.md +1 -1
  88. package/resources/skills/skills/marketing/ad-spend-allocator/SKILL.md +1 -1
  89. package/resources/skills/skills/marketing/anomaly-detection/SKILL.md +1 -1
  90. package/resources/skills/skills/marketing/attribution-model-comparison/SKILL.md +1 -1
  91. package/resources/skills/skills/marketing/audience-overlap-analysis/SKILL.md +7 -1
  92. package/resources/skills/skills/marketing/bid-strategy-recommendations/SKILL.md +7 -1
  93. package/resources/skills/skills/marketing/budget-scenario-planner/SKILL.md +6 -1
  94. package/resources/skills/skills/marketing/campaign-naming-convention-builder/SKILL.md +7 -1
  95. package/resources/skills/skills/marketing/channel-mix-optimizer/SKILL.md +7 -1
  96. package/resources/skills/skills/marketing/client-report-narratives/SKILL.md +6 -1
  97. package/resources/skills/skills/marketing/competitor-creative-analysis/SKILL.md +1 -1
  98. package/resources/skills/skills/marketing/competitor-teardown/SKILL.md +1 -1
  99. package/resources/skills/skills/marketing/content-repurposer/SKILL.md +1 -1
  100. package/resources/skills/skills/marketing/conversion-path-analysis/SKILL.md +1 -1
  101. package/resources/skills/skills/marketing/cpa-diagnostics/SKILL.md +1 -1
  102. package/resources/skills/skills/marketing/creative-fatigue-detection/SKILL.md +1 -1
  103. package/resources/skills/skills/marketing/day-hour-performance-breakdown/SKILL.md +1 -1
  104. package/resources/skills/skills/marketing/device-performance-split/SKILL.md +1 -1
  105. package/resources/skills/skills/marketing/e2e-seo-assistant/SKILL.md +1 -1
  106. package/resources/skills/skills/marketing/email-sequence-writer/SKILL.md +1 -1
  107. package/resources/skills/skills/marketing/frequency-cap-recommendations/SKILL.md +1 -1
  108. package/resources/skills/skills/marketing/geo-performance-analysis/SKILL.md +1 -1
  109. package/resources/skills/skills/marketing/google-ads-audit/SKILL.md +1 -1
  110. package/resources/skills/skills/marketing/icp-research-assistant/SKILL.md +1 -1
  111. package/resources/skills/skills/marketing/keyword-cannibalization-check/SKILL.md +1 -1
  112. package/resources/skills/skills/marketing/landing-page-audit/SKILL.md +1 -1
  113. package/resources/skills/skills/marketing/landing-page-audit-quick/SKILL.md +1 -1
  114. package/resources/skills/skills/marketing/linkedin-ads-audit/SKILL.md +1 -1
  115. package/resources/skills/skills/marketing/meta-ads-audit/SKILL.md +1 -1
  116. package/resources/skills/skills/marketing/pacing-monitor/SKILL.md +1 -1
  117. package/resources/skills/skills/marketing/performance-benchmarking/SKILL.md +1 -1
  118. package/resources/skills/skills/marketing/programmatic-seo-builder/SKILL.md +1 -1
  119. package/resources/skills/skills/marketing/quality-score-breakdown/SKILL.md +1 -1
  120. package/resources/skills/skills/marketing/reddit-ads-audit/SKILL.md +1 -1
  121. package/resources/skills/skills/marketing/retargeting-window-analysis/SKILL.md +1 -1
  122. package/resources/skills/skills/marketing/roas-forecasting/SKILL.md +1 -1
  123. package/resources/skills/skills/marketing/search-term-mining/SKILL.md +1 -1
  124. package/resources/skills/skills/marketing/utm-tracking-generator/SKILL.md +1 -1
  125. package/resources/skills/skills/marketing/wasted-spend-finder/SKILL.md +1 -1
  126. package/resources/skills/skills/marketing/weekly-account-summary/SKILL.md +1 -1
  127. package/resources/skills/skills/meta/awesome-list-submit/SKILL.md +4 -4
  128. package/resources/skills/skills/meta/cue-dashboard/SKILL.md +109 -0
  129. package/resources/skills/skills/meta/cue-developer/SKILL.md +161 -0
  130. package/resources/skills/skills/meta/cue-developer/evals/evals.json +57 -0
  131. package/resources/skills/skills/meta/cue-developer/references/architecture.md +65 -0
  132. package/resources/skills/skills/meta/cue-developer/references/build_and_test.md +72 -0
  133. package/resources/skills/skills/meta/cue-developer/references/contributing.md +75 -0
  134. package/resources/skills/skills/meta/cue-developer/references/conventions.md +57 -0
  135. package/resources/skills/skills/meta/cue-developer/references/first_time_setup.md +51 -0
  136. package/resources/skills/skills/meta/cue-developer/references/skill_and_mcp_authoring.md +84 -0
  137. package/resources/skills/skills/meta/cue-developer/references/troubleshooting.md +42 -0
  138. package/resources/skills/skills/meta/delegation-check/SKILL.md +148 -0
  139. package/resources/skills/skills/meta/delegation-check/specs/scan-algorithm.md +125 -0
  140. package/resources/skills/skills/meta/delegation-check/specs/separation-rules.md +190 -0
  141. package/resources/skills/skills/meta/focus/SKILL.md +62 -0
  142. package/resources/skills/skills/meta/help/SKILL.md +1 -1
  143. package/resources/skills/skills/meta/integrity-tags/SKILL.md +2 -0
  144. package/resources/skills/skills/meta/next-steps/SKILL.md +124 -0
  145. package/resources/skills/skills/meta/next-steps/evals/eval-set.json +92 -0
  146. package/resources/skills/skills/meta/profile-from-docs/SKILL.md +141 -0
  147. package/resources/skills/skills/meta/ralph-loop/SKILL.md +83 -0
  148. package/resources/skills/skills/meta/ralph-loop/scripts/loop.sh +73 -0
  149. package/resources/skills/skills/meta/skill-simplify/SKILL.md +136 -0
  150. package/resources/skills/skills/meta/skill-simplify/phases/01-analysis.md +173 -0
  151. package/resources/skills/skills/meta/skill-simplify/phases/02-optimize.md +104 -0
  152. package/resources/skills/skills/meta/skill-simplify/phases/03-check.md +145 -0
  153. package/resources/skills/skills/meta/smart-loader/scripts/smart-lookup.sh +13 -4
  154. package/resources/skills/skills/meta/verify-council/SKILL.md +182 -0
  155. package/resources/skills/skills/meta/verify-council/references/lane-prompts.md +103 -0
  156. package/resources/skills/skills/meta/verify-council/references/workflow.js +217 -0
  157. package/resources/skills/skills/nvidia/aiq-research/SKILL.md +1 -1
  158. package/resources/skills/skills/nvidia/cuopt-developer/SKILL.md +16 -1
  159. package/resources/skills/skills/nvidia/cuopt-developer/resources/contributing.md +2 -2
  160. package/resources/skills/skills/nvidia/cuopt-developer/resources/numerical_debugging.md +128 -0
  161. package/resources/skills/skills/nvidia/cuopt-developer/resources/python_bindings.md +2 -9
  162. package/resources/skills/skills/nvidia/cuopt-developer/resources/vrp_skills.md +166 -0
  163. package/resources/skills/skills/nvidia/cuopt-install/SKILL.md +2 -10
  164. package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/SKILL.md +3 -23
  165. package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-c/resources/examples.md +40 -20
  166. package/resources/skills/skills/nvidia/cuopt-numerical-optimization-api-python/SKILL.md +5 -1
  167. package/resources/skills/skills/nvidia/skill-evolution/SKILL.md +4 -5
  168. package/resources/skills/skills/research/trendradar/SKILL.md +1 -1
  169. package/resources/skills/skills/ssh/ssh-config/SKILL.md +94 -0
  170. package/resources/skills/skills/ssh/ssh-copy/SKILL.md +92 -0
  171. package/resources/skills/skills/ssh/ssh-harden/SKILL.md +108 -0
  172. package/resources/skills/skills/ssh/ssh-keys/SKILL.md +82 -0
  173. package/resources/skills/skills/ssh/ssh-paste-image/LICENSE +28 -0
  174. package/resources/skills/skills/ssh/ssh-paste-image/SKILL.md +149 -0
  175. package/resources/skills/skills/ssh/ssh-paste-image/scripts/build.sh +29 -0
  176. package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/go.mod +3 -0
  177. package/resources/skills/skills/ssh/ssh-paste-image/scripts/client/main.go +79 -0
  178. package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/ccimgd.service +12 -0
  179. package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/com.ccimgd.plist +20 -0
  180. package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/go.mod +3 -0
  181. package/resources/skills/skills/ssh/ssh-paste-image/scripts/daemon/main.go +98 -0
  182. package/resources/skills/skills/ssh/ssh-tunnel/SKILL.md +96 -0
  183. package/resources/skills/skills/strapi/building-with-strapi/SKILL.md +112 -0
  184. package/resources/skills/skills/strapi/strapi-cli/SKILL.md +93 -0
  185. package/resources/skills/skills/strapi/strapi-content-api/SKILL.md +115 -0
  186. package/resources/skills/skills/strapi/strapi-deploy/SKILL.md +89 -0
  187. package/resources/skills/skills/strapi/strapi-mcp-setup/SKILL.md +101 -0
  188. package/resources/skills/skills/strapi/strapi-plugins/SKILL.md +97 -0
  189. package/resources/skills/skills/tools/context7/SKILL.md +101 -0
  190. package/resources/skills/skills/tools/opensrc/SKILL.md +1 -1
  191. package/resources/skills/skills/tools/portless/SKILL.md +186 -0
  192. package/resources/skills/skills/xbot/operate/SKILL.md +229 -0
  193. package/src/commands/_index.ts +8 -0
  194. package/src/commands/ai-score.e2e.test.ts +11 -4
  195. package/src/commands/ai.ts +3 -4
  196. package/src/commands/auto-detect.ts +1 -1
  197. package/src/commands/cli.test.ts +1 -2
  198. package/src/commands/cli.ts +1 -1
  199. package/src/commands/cloud.ts +1 -1
  200. package/src/commands/current.ts +1 -4
  201. package/src/commands/dash.test.ts +110 -0
  202. package/src/commands/dash.ts +194 -0
  203. package/src/commands/dashboard.ts +26 -0
  204. package/src/commands/diff.ts +1 -1
  205. package/src/commands/discover.test.ts +1 -1
  206. package/src/commands/discover.ts +90 -40
  207. package/src/commands/doctor.test.ts +58 -0
  208. package/src/commands/doctor.ts +79 -3
  209. package/src/commands/eval-behavior.ts +1 -1
  210. package/src/commands/eval.ts +2 -2
  211. package/src/commands/evolve.ts +4 -3
  212. package/src/commands/failures.test.ts +1 -1
  213. package/src/commands/features-batch1.test.ts +6 -1
  214. package/src/commands/icon.ts +1 -5
  215. package/src/commands/import-profile.ts +1 -1
  216. package/src/commands/init.ts +50 -7
  217. package/src/commands/install-sh.e2e.test.ts +65 -0
  218. package/src/commands/launch-handoff.e2e.test.ts +88 -0
  219. package/src/commands/launch.e2e.test.ts +8 -1
  220. package/src/commands/launch.test.ts +29 -0
  221. package/src/commands/launch.ts +185 -131
  222. package/src/commands/lock.ts +0 -1
  223. package/src/commands/marketplace.ts +0 -4
  224. package/src/commands/materialize.ts +1 -1
  225. package/src/commands/mem.ts +341 -0
  226. package/src/commands/optimizer.ts +0 -3
  227. package/src/commands/playground.ts +1 -2
  228. package/src/commands/profile-draft-skill.ts +1 -1
  229. package/src/commands/replay-whatif.ts +1 -6
  230. package/src/commands/score.ts +2 -2
  231. package/src/commands/security.test.ts +88 -0
  232. package/src/commands/security.ts +74 -28
  233. package/src/commands/shell.test.ts +65 -4
  234. package/src/commands/shell.ts +67 -7
  235. package/src/commands/skills-test.ts +0 -1
  236. package/src/commands/skills.ts +28 -2
  237. package/src/commands/sources.ts +1 -2
  238. package/src/commands/status.ts +2 -6
  239. package/src/commands/submit-profile.ts +1 -1
  240. package/src/commands/suggest.ts +35 -10
  241. package/src/commands/trigger-gaps.test.ts +50 -0
  242. package/src/commands/trigger-gaps.ts +63 -29
  243. package/src/commands/update.ts +1 -1
  244. package/src/commands/validate.ts +16 -4
  245. package/src/commands/watch-live.ts +1 -1
  246. package/src/commands/workspace.ts +1 -1
  247. package/src/index.ts +26 -10
  248. package/src/lib/active-sessions.ts +1 -1
  249. package/src/lib/agent-adapters.test.ts +100 -0
  250. package/src/lib/agent-adapters.ts +2 -2
  251. package/src/lib/analytics.test.ts +88 -0
  252. package/src/lib/analytics.ts +82 -1
  253. package/src/lib/auto-detect.test.ts +10 -4
  254. package/src/lib/auto-detect.ts +19 -23
  255. package/src/lib/brand-icons.ts +0 -1
  256. package/src/lib/cache.ts +2 -3
  257. package/src/lib/claude-mem-env.test.ts +148 -0
  258. package/src/lib/claude-mem-env.ts +172 -0
  259. package/src/lib/combo-history.test.ts +53 -0
  260. package/src/lib/combo-history.ts +83 -0
  261. package/src/lib/companion-detect.test.ts +108 -0
  262. package/src/lib/companion-detect.ts +140 -0
  263. package/src/lib/companion-fetch.ts +4 -6
  264. package/src/lib/conditional-skills.test.ts +1 -1
  265. package/src/lib/config-paths.test.ts +53 -0
  266. package/src/lib/config-paths.ts +33 -0
  267. package/src/lib/dashboard-server.test.ts +351 -0
  268. package/src/lib/dashboard-server.ts +1476 -27
  269. package/src/lib/debug-log.test.ts +66 -0
  270. package/src/lib/debug-log.ts +45 -0
  271. package/src/lib/mcp-catalog.test.ts +102 -0
  272. package/src/lib/mcp-catalog.ts +193 -0
  273. package/src/lib/pair-suggestions.test.ts +111 -0
  274. package/src/lib/pair-suggestions.ts +98 -5
  275. package/src/lib/permissions.test.ts +76 -0
  276. package/src/lib/permissions.ts +125 -0
  277. package/src/lib/picker.test.ts +1106 -1
  278. package/src/lib/picker.ts +1230 -142
  279. package/src/lib/plugin-discovery.ts +126 -0
  280. package/src/lib/pr-poster.ts +1 -1
  281. package/src/lib/pr-throttle.ts +2 -6
  282. package/src/lib/profile-linter.test.ts +67 -1
  283. package/src/lib/profile-linter.ts +59 -14
  284. package/src/lib/profile-loader.test.ts +21 -0
  285. package/src/lib/profile-loader.ts +22 -3
  286. package/src/lib/profile-metrics.ts +2 -6
  287. package/src/lib/profile-names.test.ts +58 -0
  288. package/src/lib/repos.test.ts +57 -0
  289. package/src/lib/repos.ts +167 -0
  290. package/src/lib/resolver-npx.ts +10 -1
  291. package/src/lib/runtime-materializer.test.ts +200 -3
  292. package/src/lib/runtime-materializer.ts +129 -20
  293. package/src/lib/shared-profiles.ts +2 -3
  294. package/src/lib/skill-clis.test.ts +113 -0
  295. package/src/lib/skill-clis.ts +232 -0
  296. package/src/lib/skill-dependencies.ts +9 -1
  297. package/src/lib/skill-deps.ts +1 -1
  298. package/src/lib/skill-linter.ts +1 -1
  299. package/src/lib/skill-quality.ts +0 -1
  300. package/src/lib/skill-sandbox.test.ts +1 -1
  301. package/src/lib/skills-lock.test.ts +1 -1
  302. package/src/lib/telemetry-consent.ts +3 -5
  303. package/src/lib/telemetry-report.test.ts +2 -2
  304. package/src/lib/token-budget.ts +111 -0
  305. package/src/lib/trigger-gaps.test.ts +70 -0
  306. package/src/lib/trigger-gaps.ts +48 -6
  307. package/src/lib/tui/data.ts +1 -5
  308. package/src/lib/workflow-store.ts +150 -0
  309. package/src/lib/workspace-secrets.ts +0 -4
  310. package/src/lib/workspaces.ts +1 -1
package/src/index.ts CHANGED
@@ -66,6 +66,7 @@ function printHelp(): void {
66
66
  ["stats", "Profile usage analytics"],
67
67
  ["scan", "Tree of installed skills/plugins by domain"],
68
68
  ["why", "Trace why a skill/MCP is loaded"],
69
+ ["mem", "Inspect/manage per-profile claude-mem stores"],
69
70
  ],
70
71
  "Launch & Shell": [
71
72
  ["launch", "Resolve + materialize + exec claude/codex"],
@@ -154,10 +155,10 @@ function similarity(a: string, b: string): number {
154
155
  async function checkForUpdate(currentVersion: string): Promise<void> {
155
156
  const { existsSync, readFileSync: rf, writeFileSync: wf, mkdirSync } = await import("node:fs");
156
157
  const { join } = await import("node:path");
157
- const { homedir } = await import("node:os");
158
+ const { configDir } = await import("./lib/config-paths");
158
159
 
159
- const configDir = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "cue");
160
- const checkFile = join(configDir, ".last-update-check");
160
+ const cfgDir = configDir();
161
+ const checkFile = join(cfgDir, ".last-update-check");
161
162
 
162
163
  // Only check once per 24 hours
163
164
  if (existsSync(checkFile)) {
@@ -173,7 +174,7 @@ async function checkForUpdate(currentVersion: string): Promise<void> {
173
174
  if (!latest) return;
174
175
 
175
176
  // Save check timestamp
176
- mkdirSync(configDir, { recursive: true });
177
+ mkdirSync(cfgDir, { recursive: true });
177
178
  wf(checkFile, String(Date.now()));
178
179
 
179
180
  // Compare versions
@@ -191,11 +192,12 @@ async function checkForUpdate(currentVersion: string): Promise<void> {
191
192
  const readline = await import("node:readline");
192
193
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
193
194
  const answer = await new Promise<string>((resolve) => {
194
- rl.question(" Install now? [Y/n] ", (a) => { rl.close(); resolve(a); });
195
- // Auto-accept after 5 seconds
196
- setTimeout(() => { rl.close(); resolve("y"); }, 5000);
195
+ rl.question(" Install now? [y/N] ", (a) => { rl.close(); resolve(a); });
196
+ // Auto-decline after 5 seconds — never install a new global without an
197
+ // affirmative y/yes. Walking away from the terminal must be a no-op.
198
+ setTimeout(() => { rl.close(); resolve("n"); }, 5000);
197
199
  });
198
- if (!answer || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
200
+ if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
199
201
  process.stderr.write(" 📦 Updating...\n");
200
202
  const { spawnSync } = await import("node:child_process");
201
203
  const result = spawnSync("npm", ["install", "-g", "cue-ai"], { encoding: "utf8", timeout: 60000, stdio: ["ignore", "pipe", "pipe"] });
@@ -213,8 +215,22 @@ async function checkForUpdate(currentVersion: string): Promise<void> {
213
215
  async function main(argv: string[]): Promise<number> {
214
216
  const args = argv.slice(2);
215
217
 
216
- // Non-blocking update check (runs in background, shows prompt if outdated)
217
- checkForUpdate(readVersion()).catch(() => {});
218
+ // Update check never during a live agent launch. The `claude`/`codex`
219
+ // shim is `exec cue launch ...`, so running it here would open a readline on
220
+ // the agent's stdin (stealing keystrokes) and could fire a blocking
221
+ // `npm install -g` mid-session. Skip for any command that spawns an agent
222
+ // (`launch`, plus `quick`/`playground`), for trivial or non-interactive
223
+ // invocations, when launching (CUE_LAUNCHING), or in CI.
224
+ const AGENT_LAUNCH_COMMANDS = new Set(["launch", "quick", "playground"]);
225
+ const TRIVIAL_ARGS = new Set(["--version", "-v", "version", "--help", "-h", "help"]);
226
+ const skipUpdateCheck =
227
+ AGENT_LAUNCH_COMMANDS.has(args[0] ?? "") ||
228
+ TRIVIAL_ARGS.has(args[0] ?? "") ||
229
+ process.env.CUE_LAUNCHING === "1" ||
230
+ !!process.env.CI ||
231
+ !process.stdin.isTTY ||
232
+ !process.stdout.isTTY;
233
+ if (!skipUpdateCheck) checkForUpdate(readVersion()).catch(() => {});
218
234
 
219
235
  if (args.length === 0) {
220
236
  // Show status dashboard by default (like `git status`)
@@ -22,7 +22,7 @@
22
22
  * expected; we skip them and keep going.
23
23
  */
24
24
 
25
- import { existsSync, readdirSync, readFileSync, readlinkSync, statSync } from "node:fs";
25
+ import { existsSync, readdirSync, readFileSync, readlinkSync, } from "node:fs";
26
26
  import { homedir } from "node:os";
27
27
  import { join } from "node:path";
28
28
 
@@ -0,0 +1,100 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { tmpdir, homedir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { ADAPTERS, AGENT_IDS, getAdapter, claudeCode, codex } from "./agent-adapters";
7
+
8
+ describe("agent-adapters registry contract", () => {
9
+ test("every adapter conforms to the AgentAdapter shape", () => {
10
+ for (const [key, a] of Object.entries(ADAPTERS)) {
11
+ expect(typeof a.id, key).toBe("string");
12
+ expect(a.id.length, key).toBeGreaterThan(0);
13
+ expect(typeof a.name, key).toBe("string");
14
+ expect(typeof a.configDir, key).toBe("function");
15
+ expect(typeof a.writeSkills, key).toBe("function");
16
+ expect(typeof a.writeMcps, key).toBe("function");
17
+ expect(typeof a.detectBinary, key).toBe("function");
18
+ }
19
+ });
20
+
21
+ test("each adapter's id matches its registry key (no copy-paste drift)", () => {
22
+ for (const [key, a] of Object.entries(ADAPTERS)) {
23
+ expect(a.id).toBe(key);
24
+ }
25
+ });
26
+
27
+ test("AGENT_IDS mirrors the registry keys", () => {
28
+ expect([...AGENT_IDS].sort()).toEqual(Object.keys(ADAPTERS).sort());
29
+ });
30
+
31
+ test("configDir() returns a non-empty absolute path for every adapter", () => {
32
+ for (const [key, a] of Object.entries(ADAPTERS)) {
33
+ const dir = a.configDir();
34
+ expect(dir.length, key).toBeGreaterThan(0);
35
+ expect(dir.startsWith("/"), key).toBe(true);
36
+ }
37
+ });
38
+ });
39
+
40
+ describe("getAdapter", () => {
41
+ test("resolves known agent ids to the right adapter", () => {
42
+ expect(getAdapter("claude-code")).toBe(claudeCode);
43
+ expect(getAdapter("codex")).toBe(codex);
44
+ });
45
+
46
+ test("returns null for an unknown id", () => {
47
+ expect(getAdapter("does-not-exist")).toBeNull();
48
+ expect(getAdapter("")).toBeNull();
49
+ });
50
+ });
51
+
52
+ describe("configDir env overrides", () => {
53
+ const saved = { claude: process.env.CLAUDE_CONFIG_DIR, codex: process.env.CODEX_HOME };
54
+ afterEach(() => {
55
+ if (saved.claude === undefined) delete process.env.CLAUDE_CONFIG_DIR;
56
+ else process.env.CLAUDE_CONFIG_DIR = saved.claude;
57
+ if (saved.codex === undefined) delete process.env.CODEX_HOME;
58
+ else process.env.CODEX_HOME = saved.codex;
59
+ });
60
+
61
+ test("claude-code honors CLAUDE_CONFIG_DIR, else ~/.claude", () => {
62
+ process.env.CLAUDE_CONFIG_DIR = "/tmp/custom-claude";
63
+ expect(claudeCode.configDir()).toBe("/tmp/custom-claude");
64
+ delete process.env.CLAUDE_CONFIG_DIR;
65
+ expect(claudeCode.configDir()).toBe(join(homedir(), ".claude"));
66
+ });
67
+
68
+ test("codex honors CODEX_HOME, else ~/.codex", () => {
69
+ process.env.CODEX_HOME = "/tmp/custom-codex";
70
+ expect(codex.configDir()).toBe("/tmp/custom-codex");
71
+ delete process.env.CODEX_HOME;
72
+ expect(codex.configDir()).toBe(join(homedir(), ".codex"));
73
+ });
74
+ });
75
+
76
+ describe("writeMcps behavior", () => {
77
+ let dir: string;
78
+ beforeEach(() => {
79
+ dir = mkdtempSync(join(tmpdir(), "cue-adapters-"));
80
+ });
81
+ afterEach(() => {
82
+ rmSync(dir, { recursive: true, force: true });
83
+ });
84
+
85
+ test("claude-code writes mcpServers into settings.json and preserves existing keys", () => {
86
+ // Pre-existing settings with an unrelated key that must survive the merge.
87
+ writeFileSync(join(dir, "settings.json"), JSON.stringify({ theme: "dark" }));
88
+ claudeCode.writeMcps({ ctx7: { command: "npx", args: ["ctx7"] } }, dir);
89
+ const out = JSON.parse(readFileSync(join(dir, "settings.json"), "utf8"));
90
+ expect(out.theme).toBe("dark");
91
+ expect(out.mcpServers.ctx7).toEqual({ command: "npx", args: ["ctx7"] });
92
+ });
93
+
94
+ test("codex writes a config.toml with an [mcp_servers.<id>] section", () => {
95
+ codex.writeMcps({ ctx7: { command: "npx" } }, dir);
96
+ const toml = readFileSync(join(dir, "config.toml"), "utf8");
97
+ expect(toml).toContain("[mcp_servers.ctx7]");
98
+ expect(toml).toContain('command = "npx"');
99
+ });
100
+ });
@@ -5,8 +5,8 @@
5
5
  * that a specific agent expects.
6
6
  */
7
7
 
8
- import { writeFileSync, mkdirSync, existsSync, readFileSync, symlinkSync } from "node:fs";
9
- import { join, resolve } from "node:path";
8
+ import { writeFileSync, mkdirSync, existsSync, readFileSync, } from "node:fs";
9
+ import { join, } from "node:path";
10
10
  import { homedir } from "node:os";
11
11
  import { spawnSync } from "node:child_process";
12
12
 
@@ -0,0 +1,88 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { computeDailyActivity } from "./analytics";
7
+
8
+ // A fixed "now" so the day-bucket math is deterministic regardless of when the
9
+ // suite runs. 10:00 UTC means the boundary cases below straddle a UTC midnight.
10
+ const NOW = Date.parse("2026-06-03T10:00:00.000Z");
11
+
12
+ let prevXdg: string | undefined;
13
+ let scratch: string;
14
+
15
+ function writeAnalytics(lines: object[]): void {
16
+ writeFileSync(join(scratch, "cue", "analytics.jsonl"), lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
17
+ }
18
+ function writeSessionLog(lines: object[]): void {
19
+ writeFileSync(join(scratch, "cue", "session-log.jsonl"), lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
20
+ }
21
+ function bucket(rows: { date: string; sessions: number }[], date: string): number {
22
+ return rows.find((r) => r.date === date)?.sessions ?? -1;
23
+ }
24
+
25
+ beforeEach(() => {
26
+ prevXdg = process.env.XDG_CONFIG_HOME;
27
+ scratch = mkdtempSync(join(tmpdir(), "cue-analytics-test-"));
28
+ mkdirSync(join(scratch, "cue"), { recursive: true });
29
+ process.env.XDG_CONFIG_HOME = scratch;
30
+ });
31
+
32
+ afterEach(() => {
33
+ if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
34
+ else process.env.XDG_CONFIG_HOME = prevXdg;
35
+ try { rmSync(scratch, { recursive: true, force: true }); } catch { /* ignore */ }
36
+ });
37
+
38
+ describe("computeDailyActivity", () => {
39
+ test("returns exactly `days` contiguous UTC buckets ending today", () => {
40
+ writeAnalytics([]);
41
+ writeSessionLog([]);
42
+ const out = computeDailyActivity(30, NOW);
43
+ expect(out).toHaveLength(30);
44
+ expect(out[0]!.date).toBe("2026-05-05"); // now - 29 days
45
+ expect(out[29]!.date).toBe("2026-06-03"); // today
46
+ // every bucket present, zero-filled, strictly ascending
47
+ for (let i = 1; i < out.length; i++) {
48
+ expect(out[i]!.date > out[i - 1]!.date).toBe(true);
49
+ }
50
+ });
51
+
52
+ test("counts a session on its own day", () => {
53
+ writeAnalytics([]);
54
+ writeSessionLog([
55
+ { ts: "2026-05-20T12:00:00.000Z", cwd: "/x", profile: "core", session_id: "S2" },
56
+ ]);
57
+ expect(bucket(computeDailyActivity(30, NOW), "2026-05-20")).toBe(1);
58
+ });
59
+
60
+ // Regression for the off-by-one: reading a full `days` span back pulled in the
61
+ // day BEFORE the oldest bucket. A session whose `start` lands there poisoned the
62
+ // dedup set, so its Stop-hook log on the oldest shown day was dropped. With the
63
+ // fix `since` is the oldest bucket's midnight, so the start is simply out of
64
+ // window and the log counts. This bucket is 2 after the fix, 1 before it.
65
+ test("does not drop a session that starts pre-window but logs on the oldest shown day", () => {
66
+ writeAnalytics([
67
+ // out of window after the fix (prior day, 23:00) — must NOT poison dedup
68
+ { ts: "2026-05-04T23:00:00.000Z", event: "start", profile: "core", cwd: "/x", session_id: "S1" },
69
+ // in window, early on the oldest shown day — proves full-day capture
70
+ { ts: "2026-05-05T00:30:00.000Z", event: "start", profile: "core", cwd: "/x", session_id: "S3" },
71
+ ]);
72
+ writeSessionLog([
73
+ { ts: "2026-05-05T01:00:00.000Z", cwd: "/x", profile: "core", session_id: "S1" },
74
+ ]);
75
+ const out = computeDailyActivity(30, NOW);
76
+ expect(bucket(out, "2026-05-05")).toBe(2); // S1 (via log) + S3 (via start)
77
+ });
78
+
79
+ test("excludes events before the window entirely", () => {
80
+ writeAnalytics([]);
81
+ writeSessionLog([
82
+ { ts: "2026-05-04T09:00:00.000Z", cwd: "/x", profile: "core", session_id: "OLD" },
83
+ ]);
84
+ const out = computeDailyActivity(30, NOW);
85
+ const total = out.reduce((a, r) => a + r.sessions, 0);
86
+ expect(total).toBe(0);
87
+ });
88
+ });
@@ -69,10 +69,15 @@ function readSessionLog(since?: Date): SessionLogEntry[] {
69
69
  * - `skill_invoked` (structured `Skill` tool_use): skill, session_id, tool_use_id
70
70
  * - `skill_miss` (trigger matched but skill wasn't fired): session_id,
71
71
  * prompt_redacted (first 80 chars, secret-masked), matched_skills
72
+ * - `skill_gap` (self-learner — profile-self-improve.sh Stop hook): where the
73
+ * active profile's skills fell short. `source:"hook"` carries cheap friction
74
+ * `signals`; `source:"critic"` carries a critic-agent verdict (`skill`,
75
+ * `gap_type`, `suggestion`, `confidence`). Consumed by `cue profile
76
+ * self-improve`; inert to existing readers.
72
77
  */
73
78
  export interface SessionEvent {
74
79
  ts: string;
75
- event: "start" | "end" | "skill_hit" | "skill_invoked" | "skill_miss";
80
+ event: "start" | "end" | "skill_hit" | "skill_invoked" | "skill_miss" | "skill_gap";
76
81
  profile?: string;
77
82
  agent?: "claude-code" | "codex";
78
83
  cwd?: string;
@@ -82,6 +87,13 @@ export interface SessionEvent {
82
87
  tool_use_id?: string;
83
88
  prompt_redacted?: string;
84
89
  matched_skills?: string[];
90
+ // skill_gap variant
91
+ source?: "hook" | "critic";
92
+ signals?: string[];
93
+ gap_type?: "missing-skill" | "weak-description" | "weak-body" | "profile-composition";
94
+ suggestion?: string;
95
+ confidence?: number;
96
+ first_prompt?: string;
85
97
  }
86
98
 
87
99
  /**
@@ -238,6 +250,75 @@ export function computeStats(optsOrSince: Date | ComputeStatsOptions = {}): Prof
238
250
  .sort((a, b) => b.sessions - a.sessions);
239
251
  }
240
252
 
253
+ export interface DailyActivity {
254
+ /** Calendar day, `YYYY-MM-DD` (UTC). */
255
+ date: string;
256
+ sessions: number;
257
+ }
258
+
259
+ /**
260
+ * Sessions per calendar day over the window, gap-filled so a sparkline/area
261
+ * chart is continuous (days with no activity render as 0, not a missing point).
262
+ * Dedupes a session that appears in both the `start` events and the Stop-hook
263
+ * session log by `session_id`, mirroring computeStats' intent.
264
+ */
265
+ export function computeDailyActivity(sinceDays: number, now: number = Date.now()): DailyActivity[] {
266
+ const days = Math.max(1, Math.floor(sinceDays));
267
+ // Read from the START of the oldest day we actually render a bucket for. Reading
268
+ // a full `days` span back (now - days*day) reaches into the day BEFORE the oldest
269
+ // bucket: those events have no bucket, and worse, their session ids poison the
270
+ // dedup `seen` set, so a session that starts late on that prior day but whose
271
+ // Stop-hook log lands on the oldest shown day is dropped from the chart entirely.
272
+ // Anchoring `since` to the oldest bucket's UTC midnight keeps read and display
273
+ // windows identical.
274
+ const oldestDay = new Date(now - (days - 1) * 86_400_000).toISOString().slice(0, 10);
275
+ const since = new Date(`${oldestDay}T00:00:00.000Z`);
276
+ const counts = new Map<string, number>();
277
+ const seen = new Set<string>();
278
+ const bump = (ts: string, id: string): void => {
279
+ if (seen.has(id)) return;
280
+ seen.add(id);
281
+ const day = ts.slice(0, 10);
282
+ counts.set(day, (counts.get(day) ?? 0) + 1);
283
+ };
284
+ for (const e of readEvents(since)) {
285
+ if (e.event !== "start" || !e.profile) continue;
286
+ bump(e.ts, e.session_id || `start|${e.ts}|${e.cwd ?? ""}`);
287
+ }
288
+ for (const e of readSessionLog(since)) {
289
+ bump(e.ts, e.session_id || `log|${e.ts}|${e.cwd}`);
290
+ }
291
+ const out: DailyActivity[] = [];
292
+ for (let i = days - 1; i >= 0; i--) {
293
+ const key = new Date(now - i * 86_400_000).toISOString().slice(0, 10);
294
+ out.push({ date: key, sessions: counts.get(key) ?? 0 });
295
+ }
296
+ return out;
297
+ }
298
+
299
+ export interface DurationSummary {
300
+ /** Average length of *ended* sessions, in seconds. */
301
+ avgS: number;
302
+ /** Total tracked session time, in seconds. */
303
+ totalS: number;
304
+ /** How many sessions had a recorded end (the basis for avgS). */
305
+ ended: number;
306
+ }
307
+
308
+ /**
309
+ * Aggregate session-duration stats from `end` events (the only ones carrying
310
+ * `duration_s`). Reported separately from session *counts* because many
311
+ * sessions start but never log a clean end — averaging over starts would
312
+ * understate real session length.
313
+ */
314
+ export function sessionDurationSummary(since?: Date): DurationSummary {
315
+ const ends = readEvents(since).filter(
316
+ (e) => e.event === "end" && typeof e.duration_s === "number",
317
+ );
318
+ const totalS = ends.reduce((a, e) => a + (e.duration_s ?? 0), 0);
319
+ return { avgS: ends.length ? Math.round(totalS / ends.length) : 0, totalS, ended: ends.length };
320
+ }
321
+
241
322
  export interface SkillUsageStats {
242
323
  skill: string;
243
324
  hits: number;
@@ -24,14 +24,20 @@ describe("detectProfileV2", () => {
24
24
  expect(rust!.reasons).toContain("Cargo.toml");
25
25
  });
26
26
 
27
- test("Cargo.toml + src/main.rs → rust-cli at 0.7", () => {
27
+ test("Cargo.toml + src/main.rs → rust, corroborated above the lone-signal base", () => {
28
28
  writeFileSync(join(tmp, "Cargo.toml"), "[package]");
29
29
  mkdirSync(join(tmp, "src"));
30
30
  writeFileSync(join(tmp, "src/main.rs"), "fn main() {}");
31
31
  const results = detectProfileV2(tmp);
32
- const cli = results.find(r => r.profile === "rust-cli");
33
- expect(cli).toBeDefined();
34
- expect(cli!.confidence).toBe(0.7);
32
+ const rust = results.find(r => r.profile === "rust");
33
+ expect(rust).toBeDefined();
34
+ expect(rust!.reasons).toContain("src/main.rs");
35
+ // Two corroborating signals (Cargo.toml + src/main.rs) lift confidence
36
+ // above the 0.9 lone-signal base, capped at 0.97.
37
+ expect(rust!.confidence).toBeGreaterThan(0.9);
38
+ expect(rust!.confidence).toBeLessThanOrEqual(0.97);
39
+ // `rust-cli` was a phantom profile (no profiles/rust-cli on disk) — gone now.
40
+ expect(results.find(r => r.profile === "rust-cli")).toBeUndefined();
35
41
  });
36
42
 
37
43
  test("package.json with next → nextjs 0.9", () => {
@@ -12,7 +12,7 @@ interface Signal {
12
12
  profile: string;
13
13
  }
14
14
 
15
- const SIGNALS: Signal[] = [
15
+ export const SIGNALS: Signal[] = [
16
16
  // Frontend / Next.js
17
17
  { file: "next.config.js", weight: 5, profile: "nextjs" },
18
18
  { file: "next.config.ts", weight: 5, profile: "nextjs" },
@@ -41,15 +41,15 @@ const SIGNALS: Signal[] = [
41
41
  { file: ".github/workflows/", weight: 1, profile: "backend" },
42
42
 
43
43
  // Python API
44
- { file: "pyproject.toml", weight: 4, profile: "python-api" },
45
- { file: "setup.py", weight: 3, profile: "python-api" },
46
- { file: "requirements.txt", weight: 3, profile: "python-api" },
47
- { file: "app/main.py", weight: 5, profile: "python-api" },
48
- { file: "main.py", weight: 3, profile: "python-api" },
49
- { file: "manage.py", weight: 5, profile: "python-api" },
50
- { file: "uvicorn.ini", weight: 4, profile: "python-api" },
51
- { file: "alembic.ini", weight: 4, profile: "python-api" },
52
- { file: ".python-version", weight: 2, profile: "python-api" },
44
+ { file: "pyproject.toml", weight: 4, profile: "python" },
45
+ { file: "setup.py", weight: 3, profile: "python" },
46
+ { file: "requirements.txt", weight: 3, profile: "python" },
47
+ { file: "app/main.py", weight: 5, profile: "python" },
48
+ { file: "main.py", weight: 3, profile: "python" },
49
+ { file: "manage.py", weight: 5, profile: "python" },
50
+ { file: "uvicorn.ini", weight: 4, profile: "python" },
51
+ { file: "alembic.ini", weight: 4, profile: "python" },
52
+ { file: ".python-version", weight: 2, profile: "python" },
53
53
 
54
54
  // Rust
55
55
  { file: "Cargo.toml", weight: 5, profile: "rust" },
@@ -58,10 +58,6 @@ const SIGNALS: Signal[] = [
58
58
  { file: "src/lib.rs", weight: 3, profile: "rust" },
59
59
  { file: ".cargo/config.toml", weight: 2, profile: "rust" },
60
60
 
61
- // Rust CLI sub-profile
62
- { file: "src/main.rs", weight: 3, profile: "rust-cli" },
63
- { file: "Cargo.toml", weight: 3, profile: "rust-cli" },
64
-
65
61
  // Go API
66
62
  { file: "go.mod", weight: 5, profile: "go-api" },
67
63
  { file: "go.sum", weight: 3, profile: "go-api" },
@@ -97,9 +93,9 @@ const SIGNALS: Signal[] = [
97
93
  // Three.js
98
94
  { file: "three.js", weight: 4, profile: "threejs" },
99
95
 
100
- // ECC
101
- { file: "CLAUDE.md", weight: 2, profile: "ecc" },
102
- { file: ".claude/", weight: 2, profile: "ecc" },
96
+ // Generic Claude-managed repo → baseline profile
97
+ { file: "CLAUDE.md", weight: 2, profile: "core" },
98
+ { file: ".claude/", weight: 2, profile: "core" },
103
99
 
104
100
  // Full (meta)
105
101
  { file: "profiles/", weight: 2, profile: "full" },
@@ -183,7 +179,7 @@ export function detectProfileV2(cwd: string): DetectionResultV2[] {
183
179
  // ── Rust ──
184
180
  if (ex(cwd, "Cargo.toml")) {
185
181
  add("rust", 0.9, "Cargo.toml");
186
- if (ex(cwd, "src/main.rs")) add("rust-cli", 0.7, "src/main.rs");
182
+ if (ex(cwd, "src/main.rs")) add("rust", 0.7, "src/main.rs");
187
183
  if (ex(cwd, "src/lib.rs")) add("rust", 0.6, "src/lib.rs");
188
184
  }
189
185
 
@@ -193,10 +189,10 @@ export function detectProfileV2(cwd: string): DetectionResultV2[] {
193
189
  if (exAny(cwd, ["cmd", "internal"])) add("go-api", 0.4, "cmd/ or internal/");
194
190
 
195
191
  // ── Python ──
196
- if (ex(cwd, "pyproject.toml")) add("python-api", 0.7, "pyproject.toml");
197
- if (ex(cwd, "requirements.txt")) add("python-api", 0.7, "requirements.txt");
198
- if (ex(cwd, "manage.py")) add("python-api", 0.8, "manage.py");
199
- if (exAny(cwd, ["alembic.ini", "app/main.py"])) add("python-api", 0.6, "alembic.ini or app/main.py");
192
+ if (ex(cwd, "pyproject.toml")) add("python", 0.7, "pyproject.toml");
193
+ if (ex(cwd, "requirements.txt")) add("python", 0.7, "requirements.txt");
194
+ if (ex(cwd, "manage.py")) add("python", 0.8, "manage.py");
195
+ if (exAny(cwd, ["alembic.ini", "app/main.py"])) add("python", 0.6, "alembic.ini or app/main.py");
200
196
 
201
197
  // ── Backend (containers / CI / DB) ──
202
198
  if (exAny(cwd, ["docker-compose.yml", "docker-compose.yaml", "Dockerfile"])) {
@@ -223,7 +219,7 @@ export function detectProfileV2(cwd: string): DetectionResultV2[] {
223
219
 
224
220
  // ── Fleet / meta ──
225
221
  if (exAny(cwd, [".colony", ".omx", "scripts/codex-fleet"])) add("fleet-control", 0.6, "fleet markers");
226
- if (exAny(cwd, ["CLAUDE.md", ".claude"])) add("ecc", 0.4, "CLAUDE.md or .claude/");
222
+ if (exAny(cwd, ["CLAUDE.md", ".claude"])) add("core", 0.4, "CLAUDE.md or .claude/");
227
223
  if (ex(cwd, "profiles")) add("full", 0.3, "profiles/ dir");
228
224
 
229
225
  // ── package.json deps ──
@@ -14,7 +14,6 @@ import { fileURLToPath } from "node:url";
14
14
 
15
15
  const REPO_ROOT = process.env.CUE_REPO_ROOT ?? process.env.SOUL_REPO_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
16
16
  const SKILLS_ROOT = join(REPO_ROOT, "resources", "skills", "skills");
17
- const PROFILES_DIR = join(REPO_ROOT, "profiles");
18
17
  const ICONS_DIR = join(REPO_ROOT, "resources", "icons");
19
18
 
20
19
  // Known brand → asset file mappings (skill slug or MCP id → relative icon path)
package/src/lib/cache.ts CHANGED
@@ -17,8 +17,8 @@
17
17
  */
18
18
 
19
19
  import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, utimesSync } from "node:fs";
20
- import { homedir } from "node:os";
21
20
  import { dirname, join, resolve } from "node:path";
21
+ import { cacheDir } from "./config-paths";
22
22
 
23
23
  export interface CacheLayout {
24
24
  /**
@@ -41,8 +41,7 @@ const NPX_SUBDIR = "npx";
41
41
  function npxRoot(layout: CacheLayout): string {
42
42
  if (layout.cacheRoot) return resolve(layout.cacheRoot, NPX_SUBDIR);
43
43
  if (layout.repoRoot) return resolve(layout.repoRoot, "profiles", "_cache", NPX_SUBDIR);
44
- const xdg = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
45
- return join(xdg, "cue", NPX_SUBDIR);
44
+ return join(cacheDir(), NPX_SUBDIR);
46
45
  }
47
46
 
48
47
  /** Maximum number of cache entries before LRU eviction kicks in. */