cue-ai 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +4 -3
  2. package/README.md +148 -170
  3. package/bin/cue-learnings +30 -4
  4. package/bin/cue-review-progress +0 -0
  5. package/bin/cue-review-watch +0 -0
  6. package/dist/cue.js +4328 -3108
  7. package/package.json +1 -1
  8. package/plugins/cue/commands/cue-switch.md +1 -1
  9. package/plugins/cue/commands/cue.md +1 -1
  10. package/profiles/backend/profile.yaml +4 -0
  11. package/profiles/browser/profile.yaml +4 -0
  12. package/profiles/career/profile.yaml +2 -13
  13. package/profiles/commerce/profile.yaml +0 -2
  14. package/profiles/coolify/profile.yaml +0 -1
  15. package/profiles/core/profile.yaml +78 -11
  16. package/profiles/dash-merge-test/profile.yaml +6 -1
  17. package/profiles/designer/profile.yaml +9 -1
  18. package/profiles/dropshipping/profile.yaml +69 -0
  19. package/profiles/frontend/profile.yaml +4 -0
  20. package/profiles/google-ads/profile.yaml +34 -0
  21. package/profiles/google-analytics/profile.yaml +34 -0
  22. package/profiles/google-drive/profile.yaml +34 -0
  23. package/profiles/gstack/profile.yaml +117 -29
  24. package/profiles/marketing/profile.yaml +0 -1
  25. package/profiles/media/README.md +70 -0
  26. package/profiles/media/profile.yaml +104 -0
  27. package/profiles/nano-banana/profile.yaml +52 -0
  28. package/profiles/ops/profile.yaml +1 -2
  29. package/profiles/secops/profile.yaml +3 -0
  30. package/profiles/skill-writer/profile.yaml +15 -0
  31. package/profiles/video/profile.yaml +3 -0
  32. package/profiles/web-frontend-base/profile.yaml +6 -0
  33. package/profiles/webshop/profile.yaml +0 -1
  34. package/profiles/webshop-google/profile.yaml +1 -0
  35. package/profiles/x-growth-bot/profile.yaml +2 -0
  36. package/resources/icons/generate-icons.py +2 -128
  37. package/resources/mcps/configs/claude.sanitized.json +88 -20
  38. package/resources/mcps/configs/claude_runtime.sanitized.json +40 -1
  39. package/resources/mcps/configs/codex.sanitized.json +29 -0
  40. package/resources/skills/skills/career/job-hunter/LICENSE +21 -0
  41. package/resources/skills/skills/career/job-hunter/README.md +323 -0
  42. package/resources/skills/skills/career/job-hunter/SKILL.md +91 -0
  43. package/resources/skills/skills/career/job-hunter/agents/README.md +96 -0
  44. package/resources/skills/skills/career/job-hunter/agents/apply-assessment-prep.md +195 -0
  45. package/resources/skills/skills/career/job-hunter/agents/apply-ats-scan.md +155 -0
  46. package/resources/skills/skills/career/job-hunter/agents/apply-bias-audit.md +224 -0
  47. package/resources/skills/skills/career/job-hunter/agents/apply-cover-letter.md +69 -0
  48. package/resources/skills/skills/career/job-hunter/agents/apply-decode-jd.md +117 -0
  49. package/resources/skills/skills/career/job-hunter/agents/apply-fit-score.md +183 -0
  50. package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-audit.md +74 -0
  51. package/resources/skills/skills/career/job-hunter/agents/apply-linkedin-scrape.md +255 -0
  52. package/resources/skills/skills/career/job-hunter/agents/apply-portfolio-brief.md +123 -0
  53. package/resources/skills/skills/career/job-hunter/agents/apply-reality-check.md +164 -0
  54. package/resources/skills/skills/career/job-hunter/agents/apply-reference-prep.md +150 -0
  55. package/resources/skills/skills/career/job-hunter/agents/apply-rejection-analysis.md +172 -0
  56. package/resources/skills/skills/career/job-hunter/agents/apply-resume.md +70 -0
  57. package/resources/skills/skills/career/job-hunter/agents/apply-skills-gap-filler.md +109 -0
  58. package/resources/skills/skills/career/job-hunter/agents/career-internal.md +94 -0
  59. package/resources/skills/skills/career/job-hunter/agents/career-linkedin-content.md +173 -0
  60. package/resources/skills/skills/career/job-hunter/agents/career-linkedin-scanner.md +262 -0
  61. package/resources/skills/skills/career/job-hunter/agents/career-network-message.md +108 -0
  62. package/resources/skills/skills/career/job-hunter/agents/career-promote.md +102 -0
  63. package/resources/skills/skills/career/job-hunter/agents/career-review.md +71 -0
  64. package/resources/skills/skills/career/job-hunter/agents/interview-debrief.md +117 -0
  65. package/resources/skills/skills/career/job-hunter/agents/interview-mock.md +171 -0
  66. package/resources/skills/skills/career/job-hunter/agents/interview-panel-decoder.md +152 -0
  67. package/resources/skills/skills/career/job-hunter/agents/interview-prep.md +184 -0
  68. package/resources/skills/skills/career/job-hunter/agents/interview-question-bank.md +133 -0
  69. package/resources/skills/skills/career/job-hunter/agents/interview-research.md +148 -0
  70. package/resources/skills/skills/career/job-hunter/agents/offer-compare.md +117 -0
  71. package/resources/skills/skills/career/job-hunter/agents/offer-counteroffer.md +144 -0
  72. package/resources/skills/skills/career/job-hunter/agents/offer-deadline-manager.md +148 -0
  73. package/resources/skills/skills/career/job-hunter/agents/offer-negotiate.md +126 -0
  74. package/resources/skills/skills/career/job-hunter/agents/offer-schedule.md +99 -0
  75. package/resources/skills/skills/career/job-hunter/agents/offer-thankyou.md +80 -0
  76. package/resources/skills/skills/career/job-hunter/agents/search-company-research.md +146 -0
  77. package/resources/skills/skills/career/job-hunter/agents/search-follow-up.md +129 -0
  78. package/resources/skills/skills/career/job-hunter/agents/search-ghost-job-detector.md +152 -0
  79. package/resources/skills/skills/career/job-hunter/agents/search-inbox-scan.md +193 -0
  80. package/resources/skills/skills/career/job-hunter/agents/search-interview-scorecard.md +164 -0
  81. package/resources/skills/skills/career/job-hunter/agents/search-jobs.md +149 -0
  82. package/resources/skills/skills/career/job-hunter/agents/search-momentum-check.md +194 -0
  83. package/resources/skills/skills/career/job-hunter/agents/search-outreach.md +85 -0
  84. package/resources/skills/skills/career/job-hunter/agents/search-referral-finder.md +124 -0
  85. package/resources/skills/skills/career/job-hunter/agents/search-salary.md +96 -0
  86. package/resources/skills/skills/career/job-hunter/agents/search-send-email.md +109 -0
  87. package/resources/skills/skills/career/job-hunter/agents/search-tracker-update.md +127 -0
  88. package/resources/skills/skills/career/job-hunter/inputs/README.md +26 -0
  89. package/resources/skills/skills/career/job-hunter/inputs/apply-linkedin-url.txt +8 -0
  90. package/resources/skills/skills/career/job-hunter/inputs/interview-context.md +24 -0
  91. package/resources/skills/skills/career/job-hunter/inputs/job-description.md +20 -0
  92. package/resources/skills/skills/career/job-hunter/inputs/job-search-criteria.md +36 -0
  93. package/resources/skills/skills/career/job-hunter/inputs/my-linkedin.md +24 -0
  94. package/resources/skills/skills/career/job-hunter/inputs/my-resume.md +28 -0
  95. package/resources/skills/skills/career/job-hunter/inputs/search-outreach-target.md +24 -0
  96. package/resources/skills/skills/career/job-hunter/rules/README.md +37 -0
  97. package/resources/skills/skills/career/job-hunter/rules/writing-rules.md +81 -0
  98. package/resources/skills/skills/design/banana/SKILL.md +375 -0
  99. package/resources/skills/skills/design/banana/references/cost-tracking.md +47 -0
  100. package/resources/skills/skills/design/banana/references/gemini-models.md +236 -0
  101. package/resources/skills/skills/design/banana/references/mcp-tools.md +145 -0
  102. package/resources/skills/skills/design/banana/references/post-processing.md +192 -0
  103. package/resources/skills/skills/design/banana/references/presets.md +69 -0
  104. package/resources/skills/skills/design/banana/references/prompt-engineering.md +481 -0
  105. package/resources/skills/skills/design/banana/scripts/batch.py +97 -0
  106. package/resources/skills/skills/design/banana/scripts/cost_tracker.py +191 -0
  107. package/resources/skills/skills/design/banana/scripts/edit.py +159 -0
  108. package/resources/skills/skills/design/banana/scripts/generate.py +168 -0
  109. package/resources/skills/skills/design/banana/scripts/presets.py +154 -0
  110. package/resources/skills/skills/design/banana/scripts/setup_mcp.py +151 -0
  111. package/resources/skills/skills/design/banana/scripts/validate_setup.py +133 -0
  112. package/resources/skills/skills/gstack/ship/SKILL.md +13 -0
  113. package/resources/skills/skills/media/3d-logo-animation/SKILL.md +59 -0
  114. package/resources/skills/skills/media/action-figure-generator/SKILL.md +48 -0
  115. package/resources/skills/skills/media/ad-creative/SKILL.md +79 -0
  116. package/resources/skills/skills/media/ai-clipping/SKILL.md +194 -0
  117. package/resources/skills/skills/media/ai-clipping/scripts/run-ai-clipping.sh +200 -0
  118. package/resources/skills/skills/media/ai-fight-scene/SKILL.md +132 -0
  119. package/resources/skills/skills/media/amazon-product-listing/SKILL.md +68 -0
  120. package/resources/skills/skills/media/animal-video-generator/SKILL.md +59 -0
  121. package/resources/skills/skills/media/award-ceremony-video/SKILL.md +87 -0
  122. package/resources/skills/skills/media/blog-header/SKILL.md +61 -0
  123. package/resources/skills/skills/media/brand-kit/SKILL.md +72 -0
  124. package/resources/skills/skills/media/brochures/SKILL.md +65 -0
  125. package/resources/skills/skills/media/cartoon-dance-animation/SKILL.md +62 -0
  126. package/resources/skills/skills/media/character-story-video/SKILL.md +84 -0
  127. package/resources/skills/skills/media/chibi-collage-effect/SKILL.md +63 -0
  128. package/resources/skills/skills/media/cinema-director/SKILL.md +93 -0
  129. package/resources/skills/skills/media/cinema-director/scripts/generate-film.sh +78 -0
  130. package/resources/skills/skills/media/color-analysis-board/SKILL.md +71 -0
  131. package/resources/skills/skills/media/core-edit/SKILL.md +48 -0
  132. package/resources/skills/skills/media/core-edit/edit-image.sh +54 -0
  133. package/resources/skills/skills/media/core-edit/enhance-image.sh +191 -0
  134. package/resources/skills/skills/media/core-edit/lipsync.sh +144 -0
  135. package/resources/skills/skills/media/core-edit/video-effects.sh +193 -0
  136. package/resources/skills/skills/media/core-media/SKILL.md +49 -0
  137. package/resources/skills/skills/media/core-media/create-music.sh +169 -0
  138. package/resources/skills/skills/media/core-media/generate-image.sh +161 -0
  139. package/resources/skills/skills/media/core-media/generate-video.sh +137 -0
  140. package/resources/skills/skills/media/core-media/image-to-video.sh +228 -0
  141. package/resources/skills/skills/media/core-media/schema_data.json +18708 -0
  142. package/resources/skills/skills/media/core-media/upload.sh +41 -0
  143. package/resources/skills/skills/media/core-platform/SKILL.md +41 -0
  144. package/resources/skills/skills/media/core-platform/check-result.sh +37 -0
  145. package/resources/skills/skills/media/core-platform/setup.sh +31 -0
  146. package/resources/skills/skills/media/couple-grid-creator/SKILL.md +47 -0
  147. package/resources/skills/skills/media/design-guide/SKILL.md +73 -0
  148. package/resources/skills/skills/media/drone-style-video/SKILL.md +61 -0
  149. package/resources/skills/skills/media/fashion-try-on/SKILL.md +61 -0
  150. package/resources/skills/skills/media/floor-plan-rendering/SKILL.md +56 -0
  151. package/resources/skills/skills/media/freeze-effect-video/SKILL.md +100 -0
  152. package/resources/skills/skills/media/giant-product-showcase/SKILL.md +61 -0
  153. package/resources/skills/skills/media/instagram-post/SKILL.md +58 -0
  154. package/resources/skills/skills/media/interior-design/SKILL.md +61 -0
  155. package/resources/skills/skills/media/interior-design-visualizer/SKILL.md +57 -0
  156. package/resources/skills/skills/media/jewelry-product-video/SKILL.md +61 -0
  157. package/resources/skills/skills/media/kdenlive/SKILL.md +106 -0
  158. package/resources/skills/skills/media/kdenlive/scripts/assemble.sh +57 -0
  159. package/resources/skills/skills/media/kdenlive/scripts/common.sh +30 -0
  160. package/resources/skills/skills/media/kdenlive/scripts/inspect.sh +19 -0
  161. package/resources/skills/skills/media/kdenlive/scripts/reframe.sh +22 -0
  162. package/resources/skills/skills/media/kdenlive/scripts/render.sh +16 -0
  163. package/resources/skills/skills/media/kdenlive/scripts/title-card.sh +25 -0
  164. package/resources/skills/skills/media/keyboard-art-maker/SKILL.md +44 -0
  165. package/resources/skills/skills/media/logo-branding/SKILL.md +70 -0
  166. package/resources/skills/skills/media/logo-creator/SKILL.md +80 -0
  167. package/resources/skills/skills/media/logo-creator/scripts/create-logo.sh +38 -0
  168. package/resources/skills/skills/media/logo-generator/SKILL.md +56 -0
  169. package/resources/skills/skills/media/multi-angle-reshoot/SKILL.md +70 -0
  170. package/resources/skills/skills/media/multi-angle-shots/SKILL.md +73 -0
  171. package/resources/skills/skills/media/music-video/SKILL.md +61 -0
  172. package/resources/skills/skills/media/nano-banana/SKILL.md +80 -0
  173. package/resources/skills/skills/media/nano-banana/scripts/generate-nano-art.sh +54 -0
  174. package/resources/skills/skills/media/one-shot-video/SKILL.md +56 -0
  175. package/resources/skills/skills/media/photo-pack-generator/SKILL.md +205 -0
  176. package/resources/skills/skills/media/photo-pack-generator/scripts/generate-pack.sh +241 -0
  177. package/resources/skills/skills/media/product-ad-cinematic/SKILL.md +78 -0
  178. package/resources/skills/skills/media/product-campaign/SKILL.md +76 -0
  179. package/resources/skills/skills/media/product-showcase-video/SKILL.md +60 -0
  180. package/resources/skills/skills/media/product-video-ad-maker/SKILL.md +59 -0
  181. package/resources/skills/skills/media/rednote-cover/SKILL.md +57 -0
  182. package/resources/skills/skills/media/seedance-2/SKILL.md +632 -0
  183. package/resources/skills/skills/media/seedance-2/scripts/generate-seedance.sh +701 -0
  184. package/resources/skills/skills/media/selfie-with-celebrities/SKILL.md +64 -0
  185. package/resources/skills/skills/media/social-media-video/SKILL.md +277 -0
  186. package/resources/skills/skills/media/social-media-video/scripts/run-social-video.sh +316 -0
  187. package/resources/skills/skills/media/social-pack/SKILL.md +58 -0
  188. package/resources/skills/skills/media/storyboard/SKILL.md +57 -0
  189. package/resources/skills/skills/media/storyboard-to-cooking-video/SKILL.md +143 -0
  190. package/resources/skills/skills/media/talking-baby-video/SKILL.md +57 -0
  191. package/resources/skills/skills/media/ugc-ads-workflow/SKILL.md +70 -0
  192. package/resources/skills/skills/media/ugc-lifestyle-try-on/SKILL.md +65 -0
  193. package/resources/skills/skills/media/ugc-video-factory/SKILL.md +134 -0
  194. package/resources/skills/skills/media/ui-design/SKILL.md +81 -0
  195. package/resources/skills/skills/media/ui-design/scripts/generate-mockup.sh +49 -0
  196. package/resources/skills/skills/media/url-to-design/SKILL.md +61 -0
  197. package/resources/skills/skills/media/workflow/SKILL.md +197 -0
  198. package/resources/skills/skills/media/workflow/scripts/discover-workflow.sh +18 -0
  199. package/resources/skills/skills/media/workflow/scripts/generate-workflow.sh +33 -0
  200. package/resources/skills/skills/media/workflow/scripts/interactive-run.sh +16 -0
  201. package/resources/skills/skills/media/workflow/scripts/list-workflows.sh +20 -0
  202. package/resources/skills/skills/media/workflow/scripts/run-workflow.sh +34 -0
  203. package/resources/skills/skills/media/youtube-shorts/SKILL.md +173 -0
  204. package/resources/skills/skills/media/youtube-shorts/scripts/run-youtube-shorts.sh +141 -0
  205. package/resources/skills/skills/media/youtube-thumbnail/SKILL.md +66 -0
  206. package/resources/skills/skills/meta/cue-developer/references/architecture.md +2 -2
  207. package/resources/skills/skills/meta/cue-usage/SKILL.md +1 -1
  208. package/resources/skills/skills/meta/profile-fit-monitor/SKILL.md +2 -2
  209. package/resources/skills/skills/meta/profile-optimizer/SKILL.md +1 -1
  210. package/resources/skills/skills/meta/profile-suggest/SKILL.md +7 -7
  211. package/resources/skills/skills/meta/profile-summon/SKILL.md +159 -0
  212. package/resources/skills/skills/meta/profile-summon/evals/evals.json +53 -0
  213. package/resources/skills/skills/meta/save-profile/SKILL.md +1 -1
  214. package/resources/skills/skills/meta/skill-reviewer/SKILL.md +3 -0
  215. package/resources/skills/skills/meta/skill-reviewer/references/tdd-for-skills.md +55 -0
  216. package/resources/skills/skills/research/find-skills/SKILL.md +1 -1
  217. package/resources/skills/skills/review/code-review-deep/SKILL.md +20 -0
  218. package/resources/skills/skills/security/trivy-scan/SKILL.md +139 -0
  219. package/resources/skills/skills/security/trivy-scan/scripts/ensure-trivy.sh +21 -0
  220. package/resources/skills/skills/tools/ccusage/SKILL.md +142 -0
  221. package/src/commands/_index.ts +8 -0
  222. package/src/commands/ai.ts +2 -2
  223. package/src/commands/auto-detect.test.ts +74 -0
  224. package/src/commands/auto-detect.ts +9 -7
  225. package/src/commands/cli.test.ts +20 -4
  226. package/src/commands/cli.ts +36 -20
  227. package/src/commands/create-profile.ts +2 -2
  228. package/src/commands/debug.ts +2 -2
  229. package/src/commands/discover.ts +14 -4
  230. package/src/commands/export-docker.ts +1 -1
  231. package/src/commands/features-batch1.test.ts +1 -1
  232. package/src/commands/gates.ts +1 -1
  233. package/src/commands/import-profile.ts +1 -1
  234. package/src/commands/init.ts +15 -11
  235. package/src/commands/install.test.ts +192 -0
  236. package/src/commands/install.ts +610 -0
  237. package/src/commands/launch-handoff.e2e.test.ts +33 -1
  238. package/src/commands/launch.e2e.test.ts +15 -10
  239. package/src/commands/launch.ts +73 -116
  240. package/src/commands/materialize.ts +2 -2
  241. package/src/commands/prune.ts +1 -1
  242. package/src/commands/security-audit.ts +1 -1
  243. package/src/commands/shell.ts +7 -7
  244. package/src/commands/skill-report.ts +1 -1
  245. package/src/commands/skills.ts +3 -3
  246. package/src/commands/snapshot.ts +2 -2
  247. package/src/commands/summon.test.ts +116 -0
  248. package/src/commands/summon.ts +338 -0
  249. package/src/commands/trigger-gaps.ts +1 -1
  250. package/src/commands/use.ts +47 -3
  251. package/src/commands/watch-live.ts +5 -5
  252. package/src/commands/watch.ts +8 -8
  253. package/src/index.ts +2 -0
  254. package/src/lib/active-sessions.test.ts +3 -3
  255. package/src/lib/active-sessions.ts +4 -4
  256. package/src/lib/auto-detect.test.ts +172 -8
  257. package/src/lib/auto-detect.ts +191 -136
  258. package/src/lib/codex-persona-parity.test.ts +58 -0
  259. package/src/lib/companion-detect.test.ts +43 -1
  260. package/src/lib/companion-detect.ts +35 -0
  261. package/src/lib/credentials-sync.test.ts +121 -1
  262. package/src/lib/credentials-sync.ts +95 -1
  263. package/src/lib/cwd-resolver.test.ts +8 -8
  264. package/src/lib/cwd-resolver.ts +2 -2
  265. package/src/lib/dashboard-merge.test.ts +9 -4
  266. package/src/lib/dashboard-server.ts +1 -1
  267. package/src/lib/picker.test.ts +1 -1
  268. package/src/lib/picker.ts +5 -5
  269. package/src/lib/profile-merge.test.ts +8 -0
  270. package/src/lib/profile-names.test.ts +3 -3
  271. package/src/lib/runtime-install.ts +166 -0
  272. package/src/lib/runtime-materializer.test.ts +137 -0
  273. package/src/lib/runtime-materializer.ts +105 -2
  274. package/src/lib/skill-router.test.ts +38 -0
  275. package/src/lib/skill-router.ts +65 -4
  276. package/profiles/eu-tender-research/README.md +0 -48
  277. package/profiles/eu-tender-research/logo.png +0 -0
  278. package/profiles/eu-tender-research/profile.yaml +0 -108
@@ -53,6 +53,29 @@ describe("materializeRuntime", () => {
53
53
  expect(hash).toMatch(/^[a-f0-9]{64}$/);
54
54
  });
55
55
 
56
+ test("surfaces allowlisted profile.env (CLAUDE_CODE_SUBAGENT_MODEL) into settings.env", async () => {
57
+ const out = await materializeRuntime({
58
+ profile: {
59
+ ...sampleProfile,
60
+ env: {
61
+ CLAUDE_CODE_SUBAGENT_MODEL: "claude-sonnet-4-6",
62
+ // secret reference — must NOT leak into settings.json
63
+ AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}",
64
+ },
65
+ },
66
+ agent: "claude-code",
67
+ runtimeRoot: join(root, "runtime"),
68
+ skillSourceLookup: async (id) => `/fake/skills/${id}`,
69
+ mcpRegistry: { "claude-mem": { command: "claude-mem", args: [] } },
70
+ userClaudeMd: "# user CLAUDE.md\n",
71
+ });
72
+
73
+ const settings = JSON.parse(await readFile(join(out.runtimeDir, "settings.json"), "utf8"));
74
+ expect(settings.env).toEqual({ CLAUDE_CODE_SUBAGENT_MODEL: "claude-sonnet-4-6" });
75
+ // unresolved "${...}" placeholder and non-allowlisted keys stay out
76
+ expect(settings.env.AWS_SECRET_ACCESS_KEY).toBeUndefined();
77
+ });
78
+
56
79
  test("second call with same profile is a no-op (rebuilt=false)", async () => {
57
80
  const args = {
58
81
  profile: sampleProfile,
@@ -396,6 +419,120 @@ describe("materializeRuntime", () => {
396
419
  expect(creds.claudeAiOauth.refreshToken).toBe("live");
397
420
  });
398
421
 
422
+ test("credentialsSource: cache hit re-seeds a FILE .claude.json on account switch", async () => {
423
+ // Regression: runtime dirs are keyed by profile, so two authmux accounts
424
+ // share one runtime. Claude's atomic rewrite turns the .claude.json symlink
425
+ // into a local FILE owned by the last-logged-in account; the overlay's
426
+ // "cue override — don't touch" rule then left the OLD account's identity
427
+ // paired with the NEW account's tokens, forcing a re-login every time the
428
+ // accounts alternated on a profile.
429
+ const credSrcA = join(root, "accA");
430
+ const credSrcB = join(root, "accB");
431
+ await mkdir(credSrcA, { recursive: true });
432
+ await mkdir(credSrcB, { recursive: true });
433
+ await writeFile(join(credSrcA, ".credentials.json"), '{"claudeAiOauth":{"refreshToken":"A"}}');
434
+ await writeFile(join(credSrcB, ".credentials.json"), '{"claudeAiOauth":{"refreshToken":"B"}}');
435
+ await writeFile(join(credSrcA, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" } }));
436
+ await writeFile(join(credSrcB, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-B" } }));
437
+
438
+ const args = {
439
+ profile: { ...sampleProfile, name: "acct-switch" },
440
+ agent: "claude-code" as const,
441
+ runtimeRoot: join(root, "runtime"),
442
+ skillSourceLookup: async (id: string) => `/fake/source/${id}`,
443
+ mcpRegistry: { "claude-mem": { command: "claude-mem" } },
444
+ userClaudeMd: "",
445
+ };
446
+
447
+ const first = await materializeRuntime({ ...args, credentialsSource: credSrcA });
448
+ expect(first.rebuilt).toBe(true);
449
+ // Simulate Claude Code's atomic rewrite: the .claude.json symlink becomes
450
+ // a local regular file carrying account A's identity + session state.
451
+ await rm(join(first.runtimeDir, ".claude.json"), { force: true });
452
+ await writeFile(
453
+ join(first.runtimeDir, ".claude.json"),
454
+ JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" }, projects: { "/w": {} } }),
455
+ );
456
+
457
+ // Account B launches the same profile → cache hit → identity must follow.
458
+ const second = await materializeRuntime({ ...args, credentialsSource: credSrcB });
459
+ expect(second.rebuilt).toBe(false);
460
+ const cj = JSON.parse(await readFile(join(second.runtimeDir, ".claude.json"), "utf8"));
461
+ expect(cj.oauthAccount.accountUuid).toBe("uuid-B");
462
+ const creds = JSON.parse(await readFile(join(second.runtimeDir, ".credentials.json"), "utf8"));
463
+ expect(creds.claudeAiOauth.refreshToken).toBe("B");
464
+ });
465
+
466
+ test("credentialsSource: cache hit keeps a FILE .claude.json when the account matches", async () => {
467
+ // Same-account relaunch must NOT clobber per-profile session state
468
+ // (projects list etc.) that Claude wrote into the runtime's local file.
469
+ const credSrc = join(root, "accSame");
470
+ await mkdir(credSrc, { recursive: true });
471
+ await writeFile(join(credSrc, ".credentials.json"), '{"claudeAiOauth":{"refreshToken":"A"}}');
472
+ await writeFile(join(credSrc, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" } }));
473
+
474
+ const args = {
475
+ profile: { ...sampleProfile, name: "acct-same" },
476
+ agent: "claude-code" as const,
477
+ runtimeRoot: join(root, "runtime"),
478
+ skillSourceLookup: async (id: string) => `/fake/source/${id}`,
479
+ mcpRegistry: { "claude-mem": { command: "claude-mem" } },
480
+ userClaudeMd: "",
481
+ };
482
+
483
+ const first = await materializeRuntime({ ...args, credentialsSource: credSrc });
484
+ await rm(join(first.runtimeDir, ".claude.json"), { force: true });
485
+ const localState = JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" }, projects: { "/w": { history: [1] } } });
486
+ await writeFile(join(first.runtimeDir, ".claude.json"), localState);
487
+
488
+ const second = await materializeRuntime({ ...args, credentialsSource: credSrc });
489
+ expect(second.rebuilt).toBe(false);
490
+ // Identity + per-profile session state preserved (syncMcpsIntoClaudeJson
491
+ // legitimately rewrites the file to merge mcpServers, so compare fields,
492
+ // not bytes).
493
+ const cj = JSON.parse(await readFile(join(second.runtimeDir, ".claude.json"), "utf8"));
494
+ expect(cj.oauthAccount.accountUuid).toBe("uuid-A");
495
+ expect(cj.projects).toEqual({ "/w": { history: [1] } });
496
+ });
497
+
498
+ test("credentialsSource: rebuild does not resurrect another account's identity or tokens", async () => {
499
+ // The preserve step's expiresAt comparison is meaningless across accounts:
500
+ // the old runtime's token may expire later yet belong to the OTHER account.
501
+ // On a cross-account rebuild the source state must win wholesale.
502
+ const credSrcA = join(root, "rebA");
503
+ const credSrcB = join(root, "rebB");
504
+ await mkdir(credSrcA, { recursive: true });
505
+ await mkdir(credSrcB, { recursive: true });
506
+ const LATER = 9_000_000_000_000;
507
+ await writeFile(join(credSrcA, ".credentials.json"), JSON.stringify({ claudeAiOauth: { expiresAt: LATER, refreshToken: "A" } }));
508
+ await writeFile(join(credSrcB, ".credentials.json"), JSON.stringify({ claudeAiOauth: { expiresAt: 1_000, refreshToken: "B" } }));
509
+ await writeFile(join(credSrcA, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" } }));
510
+ await writeFile(join(credSrcB, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-B" } }));
511
+
512
+ const base = {
513
+ agent: "claude-code" as const,
514
+ runtimeRoot: join(root, "runtime"),
515
+ skillSourceLookup: async (id: string) => `/fake/source/${id}`,
516
+ mcpRegistry: { "claude-mem": { command: "claude-mem" } },
517
+ userClaudeMd: "",
518
+ };
519
+ const p1: ResolvedProfile = { ...sampleProfile, name: "acct-rebuild" };
520
+ const p2: ResolvedProfile = { ...sampleProfile, name: "acct-rebuild", skills: { local: [{ id: "design/ui-ux-pro-max" }, { id: "design/extra" }], npx: [] } };
521
+
522
+ const first = await materializeRuntime({ ...base, profile: p1, credentialsSource: credSrcA });
523
+ // Claude's rewrite pins account A's identity into the runtime as a FILE.
524
+ await rm(join(first.runtimeDir, ".claude.json"), { force: true });
525
+ await writeFile(join(first.runtimeDir, ".claude.json"), JSON.stringify({ oauthAccount: { accountUuid: "uuid-A" } }));
526
+
527
+ // Account B relaunches with a changed profile → rebuild + preserve step.
528
+ const second = await materializeRuntime({ ...base, profile: p2, credentialsSource: credSrcB });
529
+ expect(second.rebuilt).toBe(true);
530
+ const creds = JSON.parse(await readFile(join(second.runtimeDir, ".credentials.json"), "utf8"));
531
+ expect(creds.claudeAiOauth.refreshToken).toBe("B");
532
+ const cj = JSON.parse(await readFile(join(second.runtimeDir, ".claude.json"), "utf8"));
533
+ expect(cj.oauthAccount.accountUuid).toBe("uuid-B");
534
+ });
535
+
399
536
  test("CLAUDE.md stamp uses real ISO timestamp, not literal $(date)", async () => {
400
537
  const out = await materializeRuntime({
401
538
  profile: sampleProfile,
@@ -531,11 +531,18 @@ export async function materializeRuntime(input: MaterializeInput): Promise<Mater
531
531
  process.env.CUE_TRIGGER_PHRASES === "1" ||
532
532
  process.env.CUE_TRIGGER_PHRASES === "true"
533
533
  );
534
+ // Cap the capability table so heavy profiles (60+ skills) don't blow past
535
+ // Claude Code's 40KB CLAUDE.md perf threshold. Overflow skills stay listed
536
+ // under "Available Skills" and loadable on demand. Override with
537
+ // CUE_MAX_CAPABILITY_ROWS (0 disables the cap).
538
+ const maxCapEnv = Number(process.env.CUE_MAX_CAPABILITY_ROWS);
539
+ const maxCapabilityRows = Number.isFinite(maxCapEnv) && maxCapEnv >= 0 ? maxCapEnv : 50;
534
540
  const routerBlock = renderRouter(routerParsed, {
535
541
  overrides: routerOverrides,
536
542
  zombies: zombieIds,
537
543
  lean,
538
544
  omitTriggerPhrases,
545
+ maxCapabilityRows,
539
546
  });
540
547
  if (routerBlock) stamp += routerBlock;
541
548
 
@@ -716,7 +723,21 @@ export async function materializeRuntime(input: MaterializeInput): Promise<Mater
716
723
  // rm ~/.config/cue/runtime/<profile>/claude/.credentials.json
717
724
  // rm ~/.config/cue/runtime/<profile>/claude/.claude.json
718
725
  // Next launch will copy current source state.
719
- const preserveFiles = [".claude.json", ".credentials.json", "backups"];
726
+ // Account-identity guard: runtime dirs are keyed by PROFILE, so two authmux
727
+ // accounts (claude-account1 / claude-account2 with different
728
+ // CLAUDE_CONFIG_DIRs) share the same runtime. When the OLD runtime belongs
729
+ // to a different account than the current credentialsSource, resurrecting
730
+ // its .claude.json/.credentials.json would pair the old account's identity
731
+ // with the new account's tokens (or vice versa) — and the expiresAt
732
+ // comparison below is meaningless across accounts. Skip preservation
733
+ // entirely and let the overlay's source state win.
734
+ let sameAccount = true;
735
+ if (input.credentialsSource) {
736
+ const srcUuid = await accountUuidAt(join(input.credentialsSource, ".claude.json"));
737
+ const oldUuid = await accountUuidAt(join(runtimeDir, ".claude.json"));
738
+ if (srcUuid && oldUuid && srcUuid !== oldUuid) sameAccount = false;
739
+ }
740
+ const preserveFiles = sameAccount ? [".claude.json", ".credentials.json", "backups"] : [];
720
741
  for (const name of preserveFiles) {
721
742
  const oldPath = join(runtimeDir, name);
722
743
  const newPath = join(tmpDir, name);
@@ -770,6 +791,24 @@ async function credentialsExpiresAt(path: string): Promise<number> {
770
791
  }
771
792
  }
772
793
 
794
+ /**
795
+ * Read `oauthAccount.accountUuid` from a `.claude.json` at `path`. Returns
796
+ * undefined when the file is missing, unparseable, or carries no account —
797
+ * callers treat "unknown" as "don't make account-based decisions".
798
+ *
799
+ * Sibling of `readAccountUuid` in credentials-sync.ts (dir-based); keep the
800
+ * schema (`oauthAccount.accountUuid`) in sync if it ever changes.
801
+ */
802
+ async function accountUuidAt(path: string): Promise<string | undefined> {
803
+ try {
804
+ const raw = await readFile(path, "utf8");
805
+ const parsed = JSON.parse(raw) as { oauthAccount?: { accountUuid?: string } };
806
+ return parsed?.oauthAccount?.accountUuid;
807
+ } catch {
808
+ return undefined;
809
+ }
810
+ }
811
+
773
812
  function collectProfileMcps(
774
813
  profile: ResolvedProfile,
775
814
  agent: AgentKind,
@@ -900,7 +939,36 @@ async function overlaySourceState(targetDir: string, sourceDir: string): Promise
900
939
  // a shared one that gets clobbered when 2 profiles run concurrently.
901
940
  const isCopyFile = name === ".credentials.json" || isLegacyClaudeJson;
902
941
 
903
- if (existingType === "other" && !isCopyFile) continue; // cue override — don't touch
942
+ if (existingType === "other" && !isCopyFile) {
943
+ // Account-identity guard: .claude.json starts life as a symlink into the
944
+ // source dir, but Claude Code's atomic rewrite (tmp → rename) replaces it
945
+ // with a local FILE owned by whichever account last logged in here. Since
946
+ // runtime dirs are keyed by profile (not account), a different authmux
947
+ // account launching the same profile used to find its fresh tokens paired
948
+ // with the OLD account's identity — booting into the login flow every time
949
+ // the two accounts alternated on a profile. When the uuids differ, re-seed
950
+ // identity from the source so it follows CLAUDE_CONFIG_DIR.
951
+ //
952
+ // Trade-off: the swap replaces the whole file, so the OLD account's
953
+ // per-profile session state (projects list etc.) in this runtime is
954
+ // discarded — acceptable, since it belongs to a different account.
955
+ if (name === ".claude.json") {
956
+ const srcUuid = await accountUuidAt(sourcePath);
957
+ const dstUuid = await accountUuidAt(targetPath);
958
+ if (srcUuid && dstUuid && srcUuid !== dstUuid) {
959
+ try {
960
+ // Copy to a sibling tmp + atomic rename — never leaves a window
961
+ // where .claude.json is missing/partial while a concurrent claude
962
+ // process might read or atomically rewrite it.
963
+ const { copyFile } = await import("node:fs/promises");
964
+ const tmp = `${targetPath}.cue-swap.${process.pid}`;
965
+ await copyFile(sourcePath, tmp);
966
+ await rename(tmp, targetPath);
967
+ } catch { /* non-fatal — keep existing file */ }
968
+ }
969
+ }
970
+ continue; // cue override — don't touch
971
+ }
904
972
 
905
973
  if (existingType === "symlink" || (existingType === "other" && isCopyFile)) {
906
974
  // Replace if it points elsewhere (e.g. previous account on cache hit).
@@ -1057,6 +1125,41 @@ async function buildClaudeSettings(
1057
1125
  if (Object.keys(mergedHooks).length > 0) {
1058
1126
  settings.hooks = mergedHooks;
1059
1127
  }
1128
+
1129
+ // Surface an allowlisted subset of profile.env into settings.json `env` so
1130
+ // Claude Code's cost/runtime knobs actually reach the session. profile.env is
1131
+ // otherwise consumed only for MCP-placeholder substitution (mcp-materializer)
1132
+ // and never reaches the agent process. We allowlist deliberately: profile.env
1133
+ // also holds secret references like "${AWS_SECRET_ACCESS_KEY}" that must NOT
1134
+ // be written into settings.json. Gated to claude-code (these keys are
1135
+ // Claude-Code-specific; codex uses its own config). Set in `core` so it fans
1136
+ // out to every inheriting profile — e.g. CLAUDE_CODE_SUBAGENT_MODEL pins
1137
+ // subagents to Sonnet, ~50-60% cheaper than Opus on file-read/grep/review.
1138
+ if (agent === "claude-code") {
1139
+ // Allowlist of Claude-Code cost/runtime knobs that may flow from profile.env
1140
+ // into settings.json. To surface a new one, append its key here.
1141
+ const CLAUDE_RUNTIME_ENV_KEYS = [
1142
+ "CLAUDE_CODE_SUBAGENT_MODEL", // run Task/Agent subagents on a cheaper model
1143
+ ];
1144
+ // Preserve any account-level env from credentialsSource (spread in via
1145
+ // baseSettings above); profile-declared keys overlay it (profile is more
1146
+ // specific). Skip unset values and unresolved placeholders — the `${`
1147
+ // check is deliberately conservative: any "${...}"-shaped value is treated
1148
+ // as an unresolved secret reference and dropped, never written out.
1149
+ const runtimeEnv: Record<string, string> = {
1150
+ ...((settings.env as Record<string, string> | undefined) ?? {}),
1151
+ };
1152
+ for (const key of CLAUDE_RUNTIME_ENV_KEYS) {
1153
+ const val = profile.env?.[key];
1154
+ if (typeof val === "string" && val.length > 0 && !val.includes("${")) {
1155
+ runtimeEnv[key] = val;
1156
+ }
1157
+ }
1158
+ if (Object.keys(runtimeEnv).length > 0) {
1159
+ settings.env = runtimeEnv;
1160
+ }
1161
+ }
1162
+
1060
1163
  return JSON.stringify(settings, null, 2);
1061
1164
  }
1062
1165
 
@@ -297,3 +297,41 @@ describe("renderRouter zombie compaction", () => {
297
297
  expect(md).toContain("Rarely-used skills (3)");
298
298
  });
299
299
  });
300
+
301
+ describe("renderRouter capability cap", () => {
302
+ function manySkills(n: number): ParsedSkill[] {
303
+ return Array.from({ length: n }, (_, i) =>
304
+ parseSkillFromContent(
305
+ `cat/skill-${i}`,
306
+ `---\ndescription: Does useful thing number ${i} for the pipeline workflow.\n---`,
307
+ ),
308
+ );
309
+ }
310
+
311
+ test("no cap → every capability row renders", () => {
312
+ const md = renderRouter(manySkills(60));
313
+ const rows = md.split("\n").filter((l) => l.startsWith("| Does useful thing"));
314
+ expect(rows.length).toBe(60);
315
+ });
316
+
317
+ test("cap truncates capability rows and emits an on-demand note", () => {
318
+ const md = renderRouter(manySkills(60), { maxCapabilityRows: 50 });
319
+ const rows = md.split("\n").filter((l) => l.startsWith("| Does useful thing"));
320
+ expect(rows.length).toBe(50);
321
+ expect(md).toMatch(/\+10 more skills/);
322
+ expect(md).toContain("loadable via the Skill tool on demand");
323
+ });
324
+
325
+ test("cap is a no-op when row count is at or below the cap", () => {
326
+ const md = renderRouter(manySkills(50), { maxCapabilityRows: 50 });
327
+ expect(md).not.toMatch(/more skill.* loadable via the Skill tool/);
328
+ });
329
+
330
+ test("manual persona_routing rows survive the cap", () => {
331
+ const md = renderRouter(manySkills(60), {
332
+ maxCapabilityRows: 50,
333
+ overrides: [{ capability: "ALWAYS-KEEP this manual row", skill: "x/manual" }],
334
+ });
335
+ expect(md).toContain("ALWAYS-KEEP this manual row");
336
+ });
337
+ });
@@ -324,6 +324,16 @@ export interface RouterRenderOptions {
324
324
  * push the file past Claude Code's 40KB perf-warning threshold.
325
325
  */
326
326
  omitTriggerPhrases?: boolean;
327
+ /**
328
+ * Cap the number of rows in the capability table. On heavy profiles (60+
329
+ * skills) the auto-built capability table is the single largest block in
330
+ * the materialized CLAUDE.md. Beyond the cap, the lowest-signal skills
331
+ * (weak metadata first) drop their capability row and collapse into a
332
+ * single "+N more loadable on demand" note — they stay listed under
333
+ * "Available Skills" and remain invokable via the Skill tool. Manual
334
+ * `persona_routing:` rows are always kept. 0 / undefined → no cap.
335
+ */
336
+ maxCapabilityRows?: number;
327
337
  }
328
338
 
329
339
  /**
@@ -370,15 +380,23 @@ export function renderRouter(
370
380
  // Capability rows: any skill with a capability blurb OR explicit
371
381
  // when_to_invoke entries. We surface up to 3 task-shapes per skill;
372
382
  // skills with only a capability blurb render a single "any X work" row.
373
- const capabilityRows: { task: string; skill: string; manual?: boolean; note?: string }[] = [];
383
+ const capabilityRows: {
384
+ task: string;
385
+ skill: string;
386
+ manual?: boolean;
387
+ note?: string;
388
+ quality?: ParseQuality;
389
+ }[] = [];
374
390
  for (const s of activeSkills) {
375
391
  if (s.whenToInvoke.length > 0) {
376
- for (const task of s.whenToInvoke.slice(0, 3)) {
377
- capabilityRows.push({ task, skill: s.name });
392
+ // Two task-shapes per skill is enough to prime routing; a third row
393
+ // rarely adds signal and inflates the table on heavy profiles.
394
+ for (const task of s.whenToInvoke.slice(0, 2)) {
395
+ capabilityRows.push({ task, skill: s.name, quality: s.quality });
378
396
  }
379
397
  } else if (s.capability) {
380
398
  const summary = truncate(s.capability, 70);
381
- capabilityRows.push({ task: summary, skill: s.name });
399
+ capabilityRows.push({ task: summary, skill: s.name, quality: s.quality });
382
400
  }
383
401
  }
384
402
 
@@ -411,6 +429,44 @@ export function renderRouter(
411
429
  }
412
430
  }
413
431
 
432
+ // Capability-table cap. On heavy profiles this table is the largest block
433
+ // in the materialized CLAUDE.md. Keep manual (persona_routing) rows plus the
434
+ // highest-signal skills; collapse the overflow into a single on-demand note.
435
+ // The dropped skills stay listed under "Available Skills" and invokable via
436
+ // the Skill tool — only their capability hint is omitted.
437
+ let capOverflowSkills = 0;
438
+ const cap = options.maxCapabilityRows ?? 0;
439
+ if (cap > 0 && capabilityRows.length > cap) {
440
+ const qualityRank: Record<ParseQuality, number> = { good: 0, partial: 1, none: 2 };
441
+ const ordered = capabilityRows
442
+ .map((row, idx) => ({ row, idx }))
443
+ .sort((a, b) => {
444
+ // Manual rows first (always kept), then by metadata quality, then
445
+ // stable on original order so a skill's rows stay together.
446
+ const am = a.row.manual ? 0 : 1;
447
+ const bm = b.row.manual ? 0 : 1;
448
+ if (am !== bm) return am - bm;
449
+ const aq = qualityRank[a.row.quality ?? "none"];
450
+ const bq = qualityRank[b.row.quality ?? "none"];
451
+ if (aq !== bq) return aq - bq;
452
+ return a.idx - b.idx;
453
+ });
454
+ const keptSet = new Set(ordered.slice(0, cap).map((e) => e.idx));
455
+ const keptSkills = new Set<string>();
456
+ const droppedSkills = new Set<string>();
457
+ capabilityRows.forEach((row, idx) => {
458
+ if (keptSet.has(idx)) keptSkills.add(row.skill);
459
+ });
460
+ capabilityRows.forEach((row, idx) => {
461
+ if (!keptSet.has(idx) && !keptSkills.has(row.skill)) droppedSkills.add(row.skill);
462
+ });
463
+ capOverflowSkills = droppedSkills.size;
464
+ // Re-filter in original order so kept rows read naturally.
465
+ const filtered = capabilityRows.filter((_, idx) => keptSet.has(idx));
466
+ capabilityRows.length = 0;
467
+ capabilityRows.push(...filtered);
468
+ }
469
+
414
470
  // Tail: skills that yielded nothing or are missing on disk. Zombies that
415
471
  // already match the "no metadata" criterion are deduped here so they don't
416
472
  // appear in both the "Other skills" tail AND the "Rarely-used" tail.
@@ -448,6 +504,11 @@ export function renderRouter(
448
504
  "These wrap the underlying tools with prompt-enhancement, house " +
449
505
  "style, or correct CLI invocations. Freestyling around them produces " +
450
506
  "worse output.\n\n";
507
+ if (capOverflowSkills > 0) {
508
+ out +=
509
+ `_+${capOverflowSkills} more skill${capOverflowSkills === 1 ? "" : "s"} in this profile — ` +
510
+ "listed under \"Available Skills\" and loadable via the Skill tool on demand._\n\n";
511
+ }
451
512
  }
452
513
 
453
514
  if (triggerRows.length > 0 && !options.omitTriggerPhrases) {
@@ -1,48 +0,0 @@
1
- # 🏛️ eu-tender-research
2
-
3
- EU funding research for **agentic AI companies in Slovakia or Hungary**. Two jobs in one
4
- profile: find public-procurement **tenders** to bid on, and map the **grants, cheap loans,
5
- and venture capital** a company can apply for. Built so the second run is faster, cheaper,
6
- and more accurate than the first.
7
-
8
- ## Two channels, two skills
9
-
10
- | You want to... | Skill | Source |
11
- |---|---|---|
12
- | Find EU tenders to bid on | `eu-funding/ted-tender-search` | Official TED API (free, no key) |
13
- | Find grants / loans / VC for a company | `eu-funding/hu-grant-finder` | palyazat.gov.hu, NKFIH, MFB, Hiventures, EIC |
14
-
15
- Both skills carry the **verified recipes** so the agent doesn't rediscover them:
16
- the TED API endpoint + Expert-Search query language, the Hungarian funding ladder,
17
- the canonical sources, and the eligibility gotchas that disqualify fastest.
18
-
19
- ## The funding channels (the #1 thing users confuse)
20
-
21
- - **Tender / közbeszerzés** — TED procurement: you *sell* services to the state.
22
- - **Grant / támogatás** — GINOP/DIMOP: non-repayable project money.
23
- - **Loan / hitel** — MFB / GINOP 1.4.x: cheap (often 0%) but you repay.
24
- - **VC / kockázati tőke** — Hiventures (GINOP 2.5.1), EIC: equity investment.
25
-
26
- ## Gotchas baked into the persona
27
-
28
- - **Closed-year wall** — most KKV grants/loans need ≥1 closed business year. An egyéni
29
- vállalkozó's closed tax year counts; don't found a fresh Kft and reset the clock.
30
- - **Region split** — DIMOP/GINOP: Budapest (`/C`) vs every other county (`/B`, favoured).
31
- - **Timing** — frames close on *forráskimerülés*, often before the deadline; re-check live.
32
- - **VC is not free** — equity + TRL6 product; GINOP 2.5.1 excludes "only software" firms.
33
- - **Deep-tech ≠ SaaS** — an LLM-wrapper won't win EIC; Hungary's route is EIC Pre-Accelerator.
34
-
35
- ## MCPs
36
-
37
- `ted-eu` (free single-notice fetch), `lightpanda` (page render), `gbrain` (track
38
- shortlisted opportunities across sessions). No paid scraper, no API token. Apify is
39
- re-addable if ever wanted: `cue mcps add apify-ted-eu` (needs `APIFY_API_TOKEN`).
40
-
41
- ## Use it
42
-
43
- ```bash
44
- cue use eu-tender-research
45
- ```
46
-
47
- Then ask things like *"find open IT tenders from Hungarian buyers"* or *"what grants can a
48
- new Hungarian AI company apply for?"* — the matching skill activates and runs the verified flow.
@@ -1,108 +0,0 @@
1
- name: eu-tender-research
2
- icon: "🏛️"
3
- iconImage: "logo.png"
4
- description: "EU funding research for agentic AI companies in Slovakia or Hungary: TED tenders + grants/loans/VC (GINOP, DIMOP, Hiventures, EIC). Find, filter, assess eligibility, draft briefs."
5
- agents: [claude-code]
6
- inherits: core
7
- skills:
8
- local:
9
- - eu-funding/ted-tender-search
10
- - eu-funding/hu-grant-finder
11
- - eu-funding/grant-outreach
12
- - research/find-skills
13
- - research/defuddle
14
- - research/trendradar
15
- - gstack/scrape
16
- - gstack/document-generate
17
- - gstack/make-pdf
18
- - content/article-writer
19
- mcps:
20
- - lightpanda
21
- - gbrain
22
- - trendradar
23
- env:
24
- EU_TENDER_HQ_COUNTRIES: "SK,HU"
25
- TED_API_BASE: "https://api.ted.europa.eu/v3"
26
- persona: |
27
- You run EU funding research for agentic AI companies headquartered in
28
- Slovakia (SK) or Hungary (HU). Two jobs: (1) surface TED procurement
29
- tenders these firms can bid on, and (2) map the grants, cheap loans, and
30
- venture capital they can apply for. Rank by fit and eligibility, then turn
31
- winners into a clean brief.
32
-
33
- ## Funding channels (DON'T conflate them, this is the #1 user confusion)
34
-
35
- | Channel | What it is | Lead skill |
36
- |---|---|---|
37
- | Tender / közbeszerzés | TED procurement, you *sell* to the state | `eu-funding/ted-tender-search` |
38
- | Grant / támogatás | GINOP/DIMOP, non-repayable | `eu-funding/hu-grant-finder` |
39
- | Cheap loan / hitel | MFB / GINOP 1.4.x, 0% but repay | `eu-funding/hu-grant-finder` |
40
- | Venture capital | Hiventures (GINOP 2.5.1), EIC, equity | `eu-funding/hu-grant-finder` |
41
-
42
- The two `eu-funding/*` skills carry the verified recipes (TED API query
43
- language, the Hungarian funding ladder, the sources, the eligibility
44
- gotchas). Read the matching skill first, don't rediscover the endpoint or
45
- the gotchas from scratch.
46
-
47
- ## Gotchas that gate everything (check before detailing amounts)
48
-
49
- - **Closed-year wall** — most KKV grants/loans need >=1 closed business
50
- year + 1 employee; a brand-new company can't apply day one. An egyeni
51
- vallalkozo's closed tax year counts, so don't found a fresh Kft and
52
- reset that clock.
53
- - **Region split** — DIMOP/GINOP is Budapest (`/C`) vs every other county
54
- (`/B`, "less developed"); wealthy counties are in the favoured `/B`.
55
- - **Timing** — frames close on forraskimerules, often before the deadline;
56
- re-check the live active list at palyazat.gov.hu the same day.
57
- - **VC is not free** — Hiventures/EIC take equity, need a TRL6 working
58
- product, and GINOP 2.5.1 excludes "only software" firms.
59
- - **Deep-tech vs SaaS** — an LLM-wrapper is SaaS, not deep tech; EIC wants
60
- breakthrough. Hungary is a "widening" country, so EIC Pre-Accelerator is
61
- the realistic stepping stone.
62
-
63
- ## Data sources (route by need)
64
-
65
- | Need | Tool |
66
- |-------------------------------------------------------------------|-------------------------------|
67
- | Search TED notices (CPV, country, deadline, value) — free, no key | Official TED API, `${TED_API_BASE}/notices/search` (POST) |
68
- | Fetch one notice in full by publication number | Same TED API: `query: "publication-number IN (123456-2025)"` |
69
- | Render a TED portal page that the API can't reach | `lightpanda` MCP, gstack/scrape |
70
- | Strip a notice page to clean text | research/defuddle |
71
- | Track shortlisted tenders + buyer contacts across sessions | `gbrain` MCP |
72
- | Spot which procurement themes are heating up | research/trendradar |
73
- | Draft a bid summary / capability statement | content/article-writer |
74
- | Export the brief as a shareable doc/PDF | gstack/document-generate, gstack/make-pdf |
75
-
76
- ## Defaults
77
-
78
- - **Fit before volume.** A relevant notice an SK/HU agentic-AI firm can win
79
- beats ten generic IT tenders. Filter hard on CPV codes for software, AI,
80
- data, and consulting services (72xxxxxx, 48xxxxxx, 73xxxxxx), buyer
81
- country, deadline still open, and eligibility (no incumbent lock-in).
82
- - **Source every notice.** Each tender in a brief carries its TED
83
- publication number (e.g. `123456-2025`), buyer, country, value, deadline,
84
- and the live notice URL. No number, not in the brief.
85
- - **Rank, don't dump.** When you produce a list of tenders (3+), run
86
- `/roi-estimator` so each row carries a fit/value tag the user can sort by.
87
- - **Both bid angles.** A notice can fit either as the prime bidder or as a
88
- subcontractor to a local integrator — flag which when it's not obvious.
89
- - **Verify before claiming done.** Confirm a notice is still open and the
90
- publication number resolves before putting it in front of the user.
91
-
92
- ## Free stack, no credentials
93
-
94
- Everything here runs without an API key or paid scraper:
95
-
96
- - **Search** is the official EU TED API at `${TED_API_BASE}/notices/search`
97
- (POST JSON `{query, fields, limit, scope:"ACTIVE"}`). The Expert Search
98
- query language filters on `classification-cpv`, `place-of-performance`,
99
- `deadline-date`, `notice-type`, etc. No key needed.
100
- Example: `classification-cpv IN (72000000) AND place-of-performance IN (SVK HUN)`.
101
- - **Single-notice detail** comes from the same API:
102
- `query: "publication-number IN (123456-2025)"`, `scope: "ALL"`. No MCP needed.
103
- - **Page scraping** uses `lightpanda` + `defuddle` locally — no cloud
104
- scraper (Apify, Firecrawl, etc.) required.
105
- - **Why no TED MCP:** the `ted-eu` gateway exposes 22 tools for 2 useful
106
- ones, and its search is an NL-router black box. The API above is precise,
107
- verified, and zero-bloat. Re-add it only if you want NL queries:
108
- `cue mcps add ted-eu` (or `apify-ted-eu`, needs `APIFY_API_TOKEN`).