agents-united 1.0.0 → 1.2.0

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 (250) hide show
  1. package/LICENSE +21 -201
  2. package/README.md +627 -2
  3. package/dist/cli.d.ts +4 -1
  4. package/dist/cli.js +3826 -260
  5. package/dist/cli.js.map +1 -1
  6. package/package.json +9 -3
  7. package/registry/agents/orchestrator-business.md +151 -11
  8. package/registry/agents/orchestrator-design.md +127 -12
  9. package/registry/agents/orchestrator-engineering.md +129 -13
  10. package/registry/agents/orchestrator-marketing.md +134 -11
  11. package/registry/agents/orchestrator-research.md +159 -11
  12. package/registry/agents/orchestrator-security.md +150 -12
  13. package/registry/agents/orchestrator-system-architecture.md +131 -12
  14. package/registry/agents/subagent-accessibility-lead.md +40 -0
  15. package/registry/agents/subagent-ai-model-architect.md +76 -0
  16. package/registry/agents/subagent-android-architect.md +40 -0
  17. package/registry/agents/subagent-backend-architect.md +342 -9
  18. package/registry/agents/subagent-business-panel-experts.md +114 -8
  19. package/registry/agents/subagent-code-reviewer.md +181 -8
  20. package/registry/agents/subagent-cross-platform-specialist.md +40 -0
  21. package/registry/agents/subagent-data-engineer.md +40 -0
  22. package/registry/agents/subagent-deep-research.md +65 -8
  23. package/registry/agents/subagent-design-ops-lead.md +109 -8
  24. package/registry/agents/subagent-design-researcher.md +121 -8
  25. package/registry/agents/subagent-design-systems-architect.md +120 -8
  26. package/registry/agents/subagent-designer-toolkit-expert.md +335 -8
  27. package/registry/agents/subagent-devops-engineer.md +403 -0
  28. package/registry/agents/subagent-distributed-systems-architect.md +40 -0
  29. package/registry/agents/subagent-e2e-tester.md +40 -0
  30. package/registry/agents/subagent-frontend-architect.md +337 -8
  31. package/registry/agents/subagent-interaction-designer.md +288 -8
  32. package/registry/agents/subagent-ios-architect.md +40 -0
  33. package/registry/agents/subagent-lifecycle-email-specialist.md +40 -0
  34. package/registry/agents/subagent-marketing-campaign-specialist.md +66 -8
  35. package/registry/agents/subagent-marketing-content-strategist.md +171 -8
  36. package/registry/agents/subagent-marketing-conversion-specialist.md +66 -8
  37. package/registry/agents/subagent-marketing-creative-designer.md +85 -0
  38. package/registry/agents/subagent-marketing-growth-strategist.md +125 -8
  39. package/registry/agents/subagent-ml-platform-engineer.md +74 -0
  40. package/registry/agents/subagent-paid-acquisition-specialist.md +40 -0
  41. package/registry/agents/subagent-plg-strategist.md +40 -0
  42. package/registry/agents/subagent-prototype-tester.md +114 -8
  43. package/registry/agents/subagent-qa-automation-lead.md +40 -0
  44. package/registry/agents/subagent-repo-index.md +169 -8
  45. package/registry/agents/subagent-security-engineer.md +117 -8
  46. package/registry/agents/subagent-seo-specialist.md +40 -0
  47. package/registry/agents/subagent-socratic-mentor.md +91 -8
  48. package/registry/agents/subagent-sysops-sre-lead.md +62 -0
  49. package/registry/agents/subagent-system-architect.md +106 -8
  50. package/registry/agents/subagent-ui-designer.md +135 -8
  51. package/registry/agents/subagent-ux-strategist.md +75 -8
  52. package/registry/bundles.json +818 -154
  53. package/registry/rules/AGENTS.md +41 -0
  54. package/registry/rules/CLAUDE.md +12 -0
  55. package/registry/rules/CURSOR.md +12 -0
  56. package/registry/rules/GEMINI.md +40 -5
  57. package/registry/rules/clean-code-and-architecture.md +25 -0
  58. package/registry/rules/domain-modeling-and-adr.md +27 -0
  59. package/registry/rules/git-guardrails.md +19 -0
  60. package/registry/rules/multi-agent-coordination.md +33 -0
  61. package/registry/rules/quality-aesthetics-accessibility.md +29 -0
  62. package/registry/rules/skill-attribution.md +27 -0
  63. package/registry/rules/test-driven-development.md +41 -0
  64. package/registry/skills/ab-test-setup/SKILL.md +140 -6
  65. package/registry/skills/accessibility-audit/SKILL.md +147 -0
  66. package/registry/skills/ad-attribution-modeling/SKILL.md +202 -0
  67. package/registry/skills/ad-creative-design/SKILL.md +24 -0
  68. package/registry/skills/ai-prototype-refactoring/SKILL.md +25 -0
  69. package/registry/skills/architecture-design/SKILL.md +133 -7
  70. package/registry/skills/azure-infrastructure-bicep/SKILL.md +24 -0
  71. package/registry/skills/backend-api-design/SKILL.md +148 -0
  72. package/registry/skills/chaos-engineering/SKILL.md +38 -0
  73. package/registry/skills/churn-prevention-playbook/SKILL.md +177 -0
  74. package/registry/skills/ci-cd-pipeline-automation/SKILL.md +37 -0
  75. package/registry/skills/clickable-prototype-spec/SKILL.md +147 -0
  76. package/registry/skills/code-refactoring/SKILL.md +132 -0
  77. package/registry/skills/component-library-management/SKILL.md +147 -0
  78. package/registry/skills/component-playground-setup/SKILL.md +147 -0
  79. package/registry/skills/content-calendar-strategy/SKILL.md +147 -0
  80. package/registry/skills/conversion-funnel-optimization/SKILL.md +147 -0
  81. package/registry/skills/copywriting-frameworks/SKILL.md +147 -0
  82. package/registry/skills/database-design/SKILL.md +132 -0
  83. package/registry/skills/dependency-management/SKILL.md +133 -7
  84. package/registry/skills/design-handoff-spec/SKILL.md +147 -0
  85. package/registry/skills/design-ops-workflow/SKILL.md +147 -0
  86. package/registry/skills/design-system-governance/SKILL.md +147 -0
  87. package/registry/skills/design-system-tokens/SKILL.md +147 -0
  88. package/registry/skills/design-tokens-management/SKILL.md +147 -0
  89. package/registry/skills/design-version-control/SKILL.md +147 -0
  90. package/registry/skills/diagnosing-bugs/SKILL.md +43 -0
  91. package/registry/skills/docker-deployment/SKILL.md +141 -0
  92. package/registry/skills/domain-modeling/SKILL.md +50 -0
  93. package/registry/skills/email-drip-sequences/SKILL.md +177 -0
  94. package/registry/skills/email-marketing-automation/SKILL.md +147 -0
  95. package/registry/skills/finishing-a-development-branch/SKILL.md +123 -7
  96. package/registry/skills/frontend-component-design/SKILL.md +150 -0
  97. package/registry/skills/git-guardrails/SKILL.md +38 -0
  98. package/registry/skills/google-ads-optimization/SKILL.md +24 -0
  99. package/registry/skills/graphql-schema-design/SKILL.md +143 -0
  100. package/registry/skills/grill-me/SKILL.md +51 -0
  101. package/registry/skills/grill-with-docs/SKILL.md +78 -0
  102. package/registry/skills/growth-experiment-design/SKILL.md +147 -0
  103. package/registry/skills/handoff/SKILL.md +41 -0
  104. package/registry/skills/hf-model-evaluation/SKILL.md +80 -0
  105. package/registry/skills/interaction-pattern-library/SKILL.md +147 -0
  106. package/registry/skills/interactive-prototype-builder/SKILL.md +147 -0
  107. package/registry/skills/local-llm-inference/SKILL.md +74 -0
  108. package/registry/skills/maestro-mobile-testing/SKILL.md +37 -0
  109. package/registry/skills/marketing-creative-design/SKILL.md +182 -0
  110. package/registry/skills/meta-ad-creative-testing/SKILL.md +23 -0
  111. package/registry/skills/micro-interaction-design/SKILL.md +147 -0
  112. package/registry/skills/microservices-architecture/SKILL.md +138 -0
  113. package/registry/skills/mobile-android-design/SKILL.md +37 -0
  114. package/registry/skills/mobile-first-design/SKILL.md +147 -0
  115. package/registry/skills/mobile-ios-design/SKILL.md +37 -0
  116. package/registry/skills/mobile-platform-offline-validate/SKILL.md +37 -0
  117. package/registry/skills/modal-serverless-python/SKILL.md +78 -0
  118. package/registry/skills/onboarding-cro/SKILL.md +175 -7
  119. package/registry/skills/paid-acquisition-ppc/SKILL.md +167 -0
  120. package/registry/skills/performance-optimization/SKILL.md +132 -7
  121. package/registry/skills/playwright-best-practices/SKILL.md +36 -0
  122. package/registry/skills/product-launch-playbook/SKILL.md +147 -0
  123. package/registry/skills/programmatic-seo/SKILL.md +203 -7
  124. package/registry/skills/rag-vector-pipeline/SKILL.md +80 -0
  125. package/registry/skills/react-best-practices/SKILL.md +37 -0
  126. package/registry/skills/receiving-code-review/SKILL.md +121 -7
  127. package/registry/skills/replicate-model-inference/SKILL.md +73 -0
  128. package/registry/skills/requesting-code-review/SKILL.md +128 -7
  129. package/registry/skills/responsive-design-audit/SKILL.md +147 -0
  130. package/registry/skills/runpod-gpu-orchestration/SKILL.md +81 -0
  131. package/registry/skills/schema-markup-strategy/SKILL.md +190 -0
  132. package/registry/skills/security-audit/SKILL.md +136 -0
  133. package/registry/skills/seo-audit/SKILL.md +140 -6
  134. package/registry/skills/signup-flow-cro/SKILL.md +140 -6
  135. package/registry/skills/social-media-campaign/SKILL.md +147 -0
  136. package/registry/skills/state-driven-ui-animation/SKILL.md +147 -0
  137. package/registry/skills/subagent-driven-development/SKILL.md +126 -7
  138. package/registry/skills/supabase-backend-architecture/SKILL.md +25 -0
  139. package/registry/skills/systematic-debugging/SKILL.md +129 -7
  140. package/registry/skills/technical-documentation/SKILL.md +133 -0
  141. package/registry/skills/technical-seo-audit/SKILL.md +24 -0
  142. package/registry/skills/telemetry-monitoring/SKILL.md +37 -0
  143. package/registry/skills/test-driven-development/SKILL.md +127 -7
  144. package/registry/skills/to-spec/SKILL.md +43 -0
  145. package/registry/skills/to-tickets/SKILL.md +45 -0
  146. package/registry/skills/turso-distributed-sqlite/SKILL.md +23 -0
  147. package/registry/skills/ui-component-spec/SKILL.md +147 -0
  148. package/registry/skills/usability-testing-protocol/SKILL.md +147 -0
  149. package/registry/skills/user-flow-mapping/SKILL.md +147 -0
  150. package/registry/skills/user-journey-mapping/SKILL.md +147 -0
  151. package/registry/skills/vector-database-design/SKILL.md +80 -0
  152. package/registry/skills/vercel-deploy-best-practices/SKILL.md +24 -0
  153. package/registry/skills/viral-referral-loops/SKILL.md +205 -0
  154. package/registry/workflows/workflow-analyze.md +53 -8
  155. package/registry/workflows/workflow-api-contract-design.md +48 -0
  156. package/registry/workflows/workflow-app-store-release.md +48 -0
  157. package/registry/workflows/workflow-brainstorm.md +53 -8
  158. package/registry/workflows/workflow-build.md +53 -8
  159. package/registry/workflows/workflow-business-panel.md +53 -8
  160. package/registry/workflows/workflow-cleanup.md +53 -8
  161. package/registry/workflows/workflow-deploy-staging.md +49 -0
  162. package/registry/workflows/workflow-design-code.md +53 -8
  163. package/registry/workflows/workflow-design-ops--handoff.md +53 -8
  164. package/registry/workflows/workflow-design-ops--plan-sprint.md +53 -8
  165. package/registry/workflows/workflow-design-ops--setup-workflow.md +53 -8
  166. package/registry/workflows/workflow-design-orchestrate.md +53 -8
  167. package/registry/workflows/workflow-design-systems--audit-system.md +53 -8
  168. package/registry/workflows/workflow-design-systems--create-component.md +53 -8
  169. package/registry/workflows/workflow-design-systems--tokenize.md +53 -8
  170. package/registry/workflows/workflow-diagnose.md +45 -0
  171. package/registry/workflows/workflow-e2e-testing.md +49 -0
  172. package/registry/workflows/workflow-email-drip-sequence.md +65 -0
  173. package/registry/workflows/workflow-estimate.md +53 -8
  174. package/registry/workflows/workflow-explain.md +53 -8
  175. package/registry/workflows/workflow-frontend-audit.md +49 -0
  176. package/registry/workflows/workflow-git.md +53 -8
  177. package/registry/workflows/workflow-grill.md +51 -0
  178. package/registry/workflows/workflow-implement.md +54 -8
  179. package/registry/workflows/workflow-incident-triage.md +49 -0
  180. package/registry/workflows/workflow-interaction-design--design-interaction.md +53 -8
  181. package/registry/workflows/workflow-interaction-design--error-flow.md +53 -8
  182. package/registry/workflows/workflow-interaction-design--map-states.md +53 -8
  183. package/registry/workflows/workflow-marketing-audit.md +53 -8
  184. package/registry/workflows/workflow-marketing-campaign-builder.md +53 -8
  185. package/registry/workflows/workflow-marketing-content-pipeline.md +53 -8
  186. package/registry/workflows/workflow-marketing-growth-experiment.md +53 -8
  187. package/registry/workflows/workflow-marketing-launch.md +53 -8
  188. package/registry/workflows/workflow-marketing-panel.md +53 -8
  189. package/registry/workflows/workflow-ml-eval.md +51 -0
  190. package/registry/workflows/workflow-mobile-build.md +49 -0
  191. package/registry/workflows/workflow-onboarding-funnel-cro.md +65 -0
  192. package/registry/workflows/workflow-paid-acquisition-campaign.md +65 -0
  193. package/registry/workflows/workflow-paid-campaign-launch.md +47 -0
  194. package/registry/workflows/workflow-plan.md +53 -8
  195. package/registry/workflows/workflow-prototyping-testing--evaluate.md +53 -8
  196. package/registry/workflows/workflow-prototyping-testing--experiment.md +53 -8
  197. package/registry/workflows/workflow-prototyping-testing--prototype-plan.md +53 -8
  198. package/registry/workflows/workflow-prototyping-testing--test-plan.md +53 -8
  199. package/registry/workflows/workflow-rag-pipeline-deploy.md +53 -0
  200. package/registry/workflows/workflow-recommend.md +53 -8
  201. package/registry/workflows/workflow-research.md +53 -8
  202. package/registry/workflows/workflow-review.md +53 -8
  203. package/registry/workflows/workflow-seo-audit-pipeline.md +47 -0
  204. package/registry/workflows/workflow-seo-content-pipeline.md +65 -0
  205. package/registry/workflows/workflow-serverless-gpu-deploy.md +52 -0
  206. package/registry/workflows/workflow-spec-panel.md +53 -8
  207. package/registry/workflows/workflow-spec.md +44 -0
  208. package/registry/workflows/workflow-test.md +53 -8
  209. package/registry/workflows/workflow-troubleshoot.md +53 -8
  210. package/registry/workflows/workflow-ui-design--color-palette.md +53 -8
  211. package/registry/workflows/workflow-ui-design--design-screen.md +53 -8
  212. package/registry/workflows/workflow-ui-design--responsive-audit.md +53 -8
  213. package/registry/workflows/workflow-ui-design--type-system.md +53 -8
  214. package/registry/workflows/workflow-ux-strategy--benchmark.md +53 -8
  215. package/registry/workflows/workflow-ux-strategy--frame-problem.md +53 -8
  216. package/registry/workflows/workflow-ux-strategy--strategize.md +53 -8
  217. package/registry/skills/analytics-tracking/SKILL.md +0 -13
  218. package/registry/skills/brainstorming/SKILL.md +0 -13
  219. package/registry/skills/browser-agent/SKILL.md +0 -13
  220. package/registry/skills/campaign-strategy/SKILL.md +0 -13
  221. package/registry/skills/competitor-alternatives/SKILL.md +0 -13
  222. package/registry/skills/confidence-check/SKILL.md +0 -13
  223. package/registry/skills/copy-editing/SKILL.md +0 -13
  224. package/registry/skills/copywriting/SKILL.md +0 -13
  225. package/registry/skills/deep-research/SKILL.md +0 -13
  226. package/registry/skills/design-ops/SKILL.md +0 -13
  227. package/registry/skills/design-research/SKILL.md +0 -13
  228. package/registry/skills/design-systems/SKILL.md +0 -13
  229. package/registry/skills/designer-toolkit/SKILL.md +0 -13
  230. package/registry/skills/email-sequence/SKILL.md +0 -13
  231. package/registry/skills/executing-plans/SKILL.md +0 -13
  232. package/registry/skills/form-cro/SKILL.md +0 -13
  233. package/registry/skills/free-tool-strategy/SKILL.md +0 -13
  234. package/registry/skills/interaction-design/SKILL.md +0 -13
  235. package/registry/skills/launch-strategy/SKILL.md +0 -13
  236. package/registry/skills/marketing-ideas/SKILL.md +0 -13
  237. package/registry/skills/marketing-psychology/SKILL.md +0 -13
  238. package/registry/skills/page-cro/SKILL.md +0 -13
  239. package/registry/skills/paid-ads/SKILL.md +0 -13
  240. package/registry/skills/paywall-upgrade-cro/SKILL.md +0 -13
  241. package/registry/skills/popup-cro/SKILL.md +0 -13
  242. package/registry/skills/pricing-strategy/SKILL.md +0 -13
  243. package/registry/skills/prototyping-testing/SKILL.md +0 -13
  244. package/registry/skills/referral-program/SKILL.md +0 -13
  245. package/registry/skills/schema-markup/SKILL.md +0 -13
  246. package/registry/skills/security-review/SKILL.md +0 -13
  247. package/registry/skills/social-content/SKILL.md +0 -13
  248. package/registry/skills/ui-design/SKILL.md +0 -13
  249. package/registry/skills/ux-strategy/SKILL.md +0 -13
  250. package/registry/skills/writing-plans/SKILL.md +0 -13
package/dist/cli.js CHANGED
@@ -3,7 +3,9 @@
3
3
  // src/cli.ts
4
4
  import { cac } from "cac";
5
5
  import pc from "picocolors";
6
- import { intro, outro, spinner, note } from "@clack/prompts";
6
+ import { intro, outro, spinner, note, select, multiselect, confirm, text } from "@clack/prompts";
7
+ import fs11 from "fs-extra";
8
+ import path13 from "path";
7
9
 
8
10
  // src/core/registry.ts
9
11
  import path from "path";
@@ -43,22 +45,63 @@ var RegistryResolver = class {
43
45
  }
44
46
  async getBundle(bundleName) {
45
47
  const manifest = await this.loadBundles();
46
- return manifest.bundles[bundleName] || null;
48
+ if (manifest.bundles[bundleName]) {
49
+ return manifest.bundles[bundleName];
50
+ }
51
+ const found = Object.values(manifest.bundles).find(
52
+ (b) => b.aliases?.includes(bundleName)
53
+ );
54
+ return found || null;
47
55
  }
48
56
  async listBundles() {
49
57
  const manifest = await this.loadBundles();
50
58
  return Object.values(manifest.bundles);
51
59
  }
52
- async resolve(identifier) {
60
+ async getBundlesByDomain(domain) {
53
61
  const manifest = await this.loadBundles();
54
- const bundle = manifest.bundles[identifier];
62
+ const d = domain.toLowerCase();
63
+ return Object.values(manifest.bundles).filter((b) => b.domain?.toLowerCase() === d);
64
+ }
65
+ async resolve(identifier) {
66
+ if (identifier.startsWith("domain:")) {
67
+ const domainName = identifier.replace(/^domain:/, "").toLowerCase();
68
+ const domainBundles = await this.getBundlesByDomain(domainName);
69
+ if (domainBundles.length > 0) {
70
+ const agents = /* @__PURE__ */ new Set();
71
+ const skills = /* @__PURE__ */ new Set();
72
+ const workflows = /* @__PURE__ */ new Set();
73
+ for (const b of domainBundles) {
74
+ const resolved = await this.resolve(b.name);
75
+ resolved.agents.forEach((a) => agents.add(a));
76
+ resolved.skills.forEach((s) => skills.add(s));
77
+ resolved.workflows.forEach((w) => workflows.add(w));
78
+ }
79
+ return {
80
+ targetBundle: `domain:${domainName}`,
81
+ agents: Array.from(agents),
82
+ skills: Array.from(skills),
83
+ workflows: Array.from(workflows),
84
+ rules: ["GEMINI.md"]
85
+ };
86
+ }
87
+ }
88
+ const bundle = await this.getBundle(identifier);
55
89
  if (bundle) {
56
90
  const agents = /* @__PURE__ */ new Set();
57
- if (bundle.orchestrator) agents.add(bundle.orchestrator);
58
- if (bundle.agents) bundle.agents.forEach((a) => agents.add(a));
59
91
  const skills = new Set(bundle.skills || []);
60
92
  const workflows = new Set(bundle.workflows || []);
61
93
  const rules = ["GEMINI.md"];
94
+ if (bundle.parentBundle) {
95
+ const parent = await this.getBundle(bundle.parentBundle);
96
+ if (parent) {
97
+ if (parent.orchestrator) agents.add(parent.orchestrator);
98
+ if (parent.agents) parent.agents.forEach((a) => agents.add(a));
99
+ if (parent.skills) parent.skills.forEach((s) => skills.add(s));
100
+ if (parent.workflows) parent.workflows.forEach((w) => workflows.add(w));
101
+ }
102
+ }
103
+ if (bundle.orchestrator) agents.add(bundle.orchestrator);
104
+ if (bundle.agents) bundle.agents.forEach((a) => agents.add(a));
62
105
  return {
63
106
  targetBundle: bundle.name,
64
107
  agents: Array.from(agents),
@@ -97,76 +140,626 @@ var RegistryResolver = class {
97
140
  }
98
141
  throw new Error(`Item or bundle "${identifier}" not found in registry.`);
99
142
  }
100
- async find(query) {
143
+ async find(query = "", options) {
101
144
  const manifest = await this.loadBundles();
102
- const q = query.toLowerCase();
103
- const matchedBundles = Object.values(manifest.bundles).filter(
104
- (b) => b.name.toLowerCase().includes(q) || b.description.toLowerCase().includes(q)
105
- );
145
+ const q = query.trim().toLowerCase();
146
+ let matchedBundles = [];
147
+ if (!options?.type || options.type === "bundle") {
148
+ matchedBundles = Object.values(manifest.bundles).filter((b) => {
149
+ if (options?.domain && b.domain?.toLowerCase() !== options.domain.toLowerCase()) {
150
+ return false;
151
+ }
152
+ if (!q) return true;
153
+ return b.name.toLowerCase().includes(q) || b.description.toLowerCase().includes(q) || b.category?.toLowerCase().includes(q) || b.domain?.toLowerCase().includes(q) || b.aliases?.some((a) => a.toLowerCase().includes(q));
154
+ });
155
+ }
106
156
  const agentsDir = path.join(this.registryDir, "agents");
107
157
  let matchedAgents = [];
108
- if (await fs.pathExists(agentsDir)) {
109
- const files = await fs.readdir(agentsDir);
110
- matchedAgents = files.filter((f) => f.toLowerCase().includes(q));
158
+ if (!options?.type || options.type === "agent") {
159
+ if (await fs.pathExists(agentsDir)) {
160
+ const files = await fs.readdir(agentsDir);
161
+ matchedAgents = files.filter((f) => {
162
+ if (!q) return true;
163
+ return f.toLowerCase().includes(q);
164
+ });
165
+ }
111
166
  }
112
167
  const skillsDir = path.join(this.registryDir, "skills");
113
168
  let matchedSkills = [];
114
- if (await fs.pathExists(skillsDir)) {
115
- const dirs = await fs.readdir(skillsDir);
116
- matchedSkills = dirs.filter((d) => d.toLowerCase().includes(q));
169
+ if (!options?.type || options.type === "skill") {
170
+ if (await fs.pathExists(skillsDir)) {
171
+ const dirs = await fs.readdir(skillsDir);
172
+ matchedSkills = dirs.filter((d) => {
173
+ if (!q) return true;
174
+ return d.toLowerCase().includes(q);
175
+ });
176
+ }
177
+ }
178
+ const workflowsDir = path.join(this.registryDir, "workflows");
179
+ let matchedWorkflows = [];
180
+ if (!options?.type || options.type === "workflow") {
181
+ if (await fs.pathExists(workflowsDir)) {
182
+ const files = await fs.readdir(workflowsDir);
183
+ matchedWorkflows = files.filter((f) => f.endsWith(".md")).filter((f) => {
184
+ if (!q) return true;
185
+ return f.toLowerCase().includes(q);
186
+ });
187
+ }
117
188
  }
118
189
  return {
119
190
  bundles: matchedBundles,
120
191
  agents: matchedAgents,
121
- skills: matchedSkills
192
+ skills: matchedSkills,
193
+ workflows: matchedWorkflows
122
194
  };
123
195
  }
124
196
  };
125
197
 
126
198
  // src/core/installer.ts
127
- import path3 from "path";
199
+ import path5 from "path";
128
200
  import crypto from "crypto";
129
- import fs2 from "fs-extra";
201
+ import fs3 from "fs-extra";
130
202
 
131
203
  // src/core/adapter.ts
132
- import path2 from "path";
204
+ import path3 from "path";
133
205
  import os from "os";
134
- var TargetAdapter = class {
135
- static resolveTargetDir(scope = "workspace", overrideDir) {
206
+
207
+ // src/core/hosts.ts
208
+ import path2 from "path";
209
+ var HOST_REGISTRY = {
210
+ agents: {
211
+ id: "agents",
212
+ label: "Google Antigravity & Master Library (\u{1F4C1} .agents/)",
213
+ projectDir: ".agents",
214
+ globalDirSegments: [".agents"],
215
+ agentsSubdir: "agents",
216
+ detectionMarkers: [".agents"],
217
+ profile: "antigravity",
218
+ projectionCapable: false
219
+ },
220
+ gemini: {
221
+ id: "gemini",
222
+ label: "Antigravity legacy (\u{1F4C1} .gemini/)",
223
+ projectDir: ".gemini",
224
+ globalDirSegments: [".gemini", "config"],
225
+ agentsSubdir: "agents",
226
+ detectionMarkers: [".gemini"],
227
+ profile: "antigravity",
228
+ projectionCapable: false
229
+ },
230
+ claude: {
231
+ id: "claude",
232
+ label: "Anthropic Claude Code (\u{1F4C1} .claude/)",
233
+ projectDir: ".claude",
234
+ globalDirSegments: [".claude"],
235
+ agentsSubdir: "agents",
236
+ detectionMarkers: [".claude"],
237
+ profile: "claude-code",
238
+ projectionCapable: true
239
+ },
240
+ cursor: {
241
+ id: "cursor",
242
+ label: "Cursor IDE (\u{1F4C1} .cursor/)",
243
+ projectDir: ".cursor",
244
+ globalDirSegments: [".cursor"],
245
+ agentsSubdir: "agents",
246
+ detectionMarkers: [".cursor"],
247
+ profile: "cursor",
248
+ projectionCapable: true
249
+ },
250
+ cline: {
251
+ id: "cline",
252
+ label: "Cline (\u{1F4C1} .cline/)",
253
+ projectDir: ".cline",
254
+ globalDirSegments: [".cline"],
255
+ agentsSubdir: "agents",
256
+ detectionMarkers: [".cline", ".clinerules"],
257
+ profile: "cline",
258
+ projectionCapable: true
259
+ },
260
+ opencode: {
261
+ id: "opencode",
262
+ label: "OpenCode (\u{1F4C1} .opencode/)",
263
+ projectDir: ".opencode",
264
+ globalDirSegments: [".config", "opencode"],
265
+ agentsSubdir: "agent",
266
+ detectionMarkers: [".opencode", "opencode.json"],
267
+ profile: "opencode",
268
+ projectionCapable: true
269
+ },
270
+ codex: {
271
+ id: "codex",
272
+ label: "OpenAI Codex, Copilot, Aider & Zed (\u{1F4C4} ./AGENTS.md)",
273
+ projectDir: ".",
274
+ globalDirSegments: [".codex"],
275
+ agentsSubdir: void 0,
276
+ detectionMarkers: ["AGENTS.md", ".codex"],
277
+ profile: "agentsmd",
278
+ projectionCapable: true
279
+ }
280
+ };
281
+ var KNOWN_HOST_IDS = Object.keys(HOST_REGISTRY);
282
+ function isKnownHost(id) {
283
+ return Object.prototype.hasOwnProperty.call(HOST_REGISTRY, id);
284
+ }
285
+ function resolveHostProjectDir(host, cwd) {
286
+ return path2.resolve(cwd, HOST_REGISTRY[host].projectDir);
287
+ }
288
+ function resolveHostGlobalDir(host, home) {
289
+ return path2.join(home, ...HOST_REGISTRY[host].globalDirSegments);
290
+ }
291
+ function planInstallTargets(selected) {
292
+ const normalized = Array.from(
293
+ new Set(selected.map((s) => typeof s === "string" ? s.trim().toLowerCase() : "").filter(Boolean))
294
+ );
295
+ const known = normalized.filter(isKnownHost);
296
+ const fanout = known.filter((h) => HOST_REGISTRY[h].projectionCapable);
297
+ const hosts = known.filter((h) => !HOST_REGISTRY[h].projectionCapable);
298
+ let addedCanonicalStore = false;
299
+ if (fanout.length > 0 && !hosts.includes("agents")) {
300
+ hosts.unshift("agents");
301
+ addedCanonicalStore = true;
302
+ }
303
+ if (hosts.length === 0 && fanout.length === 0) {
304
+ hosts.push("agents");
305
+ }
306
+ return { hosts, fanout, addedCanonicalStore };
307
+ }
308
+
309
+ // src/core/adapter.ts
310
+ var AgentHostAdapter = class {
311
+ static resolveHostDir(scope = "project", host = "agents", overrideDir) {
136
312
  if (overrideDir) {
137
- return path2.resolve(overrideDir);
313
+ return path3.resolve(overrideDir);
138
314
  }
315
+ if (!isKnownHost(host)) {
316
+ host = "agents";
317
+ }
318
+ const home = os.homedir();
319
+ const cwd = process.cwd();
139
320
  if (scope === "global") {
140
- const home = os.homedir();
141
- return path2.join(home, ".gemini", "config");
321
+ return resolveHostGlobalDir(host, home);
142
322
  }
143
- return path2.resolve(process.cwd(), ".agents");
323
+ return resolveHostProjectDir(host, cwd);
144
324
  }
145
325
  static getSubPaths(targetDir) {
146
326
  return {
147
- agentsDir: path2.join(targetDir, "agents"),
148
- skillsDir: path2.join(targetDir, "skills"),
149
- workflowsDir: path2.join(targetDir, "workflows"),
150
- rulesDir: path2.join(targetDir, "rules"),
151
- lockfile: path2.join(targetDir, "agents-united.json")
327
+ agentsDir: path3.join(targetDir, "agents"),
328
+ skillsDir: path3.join(targetDir, "skills"),
329
+ workflowsDir: path3.join(targetDir, "workflows"),
330
+ rulesDir: path3.join(targetDir, "rules"),
331
+ lockfile: path3.join(targetDir, "agents-united.json")
332
+ };
333
+ }
334
+ };
335
+
336
+ // src/core/projector.ts
337
+ import YAML from "yaml";
338
+
339
+ // src/core/cline-projector.ts
340
+ import yaml from "yaml";
341
+ import path4 from "path";
342
+ import fs2 from "fs-extra";
343
+ var ClineProjector = class {
344
+ /**
345
+ * Render a Cline role definition from canonical agent markdown content.
346
+ */
347
+ static renderRole(canonicalContent, canonicalRelPath) {
348
+ const normCanonical = canonicalRelPath.replace(/\\/g, "/");
349
+ const marker = `<!-- managed-by: agents-united | profile: cline | canonical: ${normCanonical} | do not edit -->`;
350
+ const preamble = `## Cline runtime note
351
+
352
+ Use the equivalent capabilities available in this Cline session. Canonical tool names describe
353
+ intent and may differ from Cline's runtime tool names. For delegation, prefer Agent Teams when
354
+ available, then session subagents; otherwise complete the role in the main session.`;
355
+ const match = canonicalContent.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
356
+ if (!match) {
357
+ return `${marker}
358
+
359
+ ${preamble}
360
+
361
+ ${canonicalContent.trim()}`;
362
+ }
363
+ const rawYaml = match[1];
364
+ const rawBody = match[2];
365
+ let parsed = {};
366
+ try {
367
+ parsed = yaml.parse(rawYaml) || {};
368
+ } catch {
369
+ parsed = {};
370
+ }
371
+ const cleanFrontmatter = {};
372
+ if (parsed.name) cleanFrontmatter.name = parsed.name;
373
+ if (parsed.description) cleanFrontmatter.description = parsed.description;
374
+ if (parsed.providerId) cleanFrontmatter.providerId = parsed.providerId;
375
+ if (parsed.modelId) cleanFrontmatter.modelId = parsed.modelId;
376
+ if (parsed.cwd) cleanFrontmatter.cwd = parsed.cwd;
377
+ if (parsed.maxIterations) cleanFrontmatter.maxIterations = parsed.maxIterations;
378
+ const frontmatterStr = yaml.stringify(cleanFrontmatter).trim();
379
+ const bodyStr = rawBody.trim();
380
+ return `---
381
+ ${frontmatterStr}
382
+ ---
383
+ ${marker}
384
+
385
+ ${preamble}
386
+
387
+ ${bodyStr}
388
+ `;
389
+ }
390
+ /**
391
+ * Render a Cline SKILL.md preserving frontmatter and placing managed marker after delimiter.
392
+ */
393
+ static renderSkillMd(canonicalContent, canonicalRelPath) {
394
+ const normCanonical = canonicalRelPath.replace(/\\/g, "/");
395
+ const marker = `<!-- managed-by: agents-united | profile: cline | canonical: ${normCanonical} | do not edit -->`;
396
+ const match = canonicalContent.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
397
+ if (!match) {
398
+ return `${marker}
399
+
400
+ ${canonicalContent.trim()}`;
401
+ }
402
+ const rawYaml = match[1];
403
+ const rawBody = match[2];
404
+ let parsed = {};
405
+ try {
406
+ parsed = yaml.parse(rawYaml) || {};
407
+ } catch {
408
+ parsed = {};
409
+ }
410
+ const frontmatterStr = yaml.stringify(parsed).trim();
411
+ const bodyStr = rawBody.trim();
412
+ return `---
413
+ ${frontmatterStr}
414
+ ---
415
+ ${marker}
416
+
417
+ ${bodyStr}
418
+ `;
419
+ }
420
+ /**
421
+ * Render Team Manifest YAML matching ClineTeamManifest schema.
422
+ */
423
+ static renderTeamManifest(bundle, scope) {
424
+ const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
425
+ const coordinatorName = coordinatorFile.replace(/\.md$/, "");
426
+ const coordinatorCanonical = `agents/${coordinatorFile}`;
427
+ const roles = (bundle.agents || []).map((agentFile) => ({
428
+ name: agentFile.replace(/\.md$/, ""),
429
+ canonicalPath: `agents/${agentFile}`
430
+ }));
431
+ const sortedSkills = [...bundle.skills || []].sort();
432
+ const sortedWorkflows = [...bundle.workflows || []].sort();
433
+ const recommendedAddons = bundle.recommendedAddons || [];
434
+ const manifest = {
435
+ schemaVersion: 1,
436
+ bundle: bundle.name,
437
+ scope,
438
+ coordinator: {
439
+ name: coordinatorName,
440
+ canonicalPath: coordinatorCanonical
441
+ },
442
+ roles,
443
+ skills: sortedSkills,
444
+ workflows: sortedWorkflows,
445
+ recommendedAddons,
446
+ activation: {
447
+ preferred: "named-team",
448
+ fallbacks: ["adaptive-session", "single-orchestrator"]
449
+ }
152
450
  };
451
+ return yaml.stringify(manifest);
452
+ }
453
+ /**
454
+ * Render the coordinator rule file (.cline/rules/agents-united-<bundle>.md).
455
+ */
456
+ static renderCoordinatorRule(bundle, scope) {
457
+ const marker = `<!-- managed-by: agents-united | profile: cline | bundle: ${bundle.name} | do not edit -->`;
458
+ const manifestRelPath = scope === "global" ? `~/.cline/agents-united/teams/${bundle.name}.yaml` : `.cline/agents-united/teams/${bundle.name}.yaml`;
459
+ const coordinatorFile = bundle.orchestrator || `${bundle.name}.md`;
460
+ const coordinatorName = coordinatorFile.replace(/\.md$/, "");
461
+ const coordinatorCanonical = `agents/${coordinatorFile}`;
462
+ const specialistLines = (bundle.agents || []).map((agentFile) => {
463
+ const name = agentFile.replace(/\.md$/, "");
464
+ return `- **${name}**: \`.agents/agents/${agentFile}\``;
465
+ });
466
+ const addons = bundle.recommendedAddons || [];
467
+ const addonSection = addons.length > 0 ? `
468
+ ### Recommended Addon Policy
469
+ When user tasks require capabilities from: ${addons.join(", ")}, explain the capability and request user confirmation to install via \`agents add <addon> -t cline -y\` before running the installation.` : "";
470
+ return `# Agents United \u2014 ${bundle.name} Coordinator Rule
471
+ ${marker}
472
+
473
+ > Installed bundle: **${bundle.name}** (${scope} scope)
474
+ > Team Manifest: \`${manifestRelPath}\`
475
+ > Coordinator role: \`${coordinatorName}\` (\`.agents/${coordinatorCanonical}\`)
476
+
477
+ ## Activation Protocol
478
+ 1. At session start, read the Team Manifest (\`${manifestRelPath}\`) and coordinator role prompt (\`.agents/${coordinatorCanonical}\`).
479
+ 2. Delegate specialist tasks using **Agent Teams** (\`team_spawn_teammate\`, \`team_delegate_task\`) when available, assigning non-overlapping scopes.
480
+ 3. For lightweight read-only research, use session subagents.
481
+ 4. Only specialist roles declared in the Team Manifest are active in this workspace.
482
+ ${specialistLines.length > 0 ? `
483
+ ### Installed Specialist Roles
484
+ ${specialistLines.join("\n")}` : ""}
485
+ ${addonSection}
486
+ `;
487
+ }
488
+ /**
489
+ * Plan all compound artifacts for a bundle installation into Cline.
490
+ */
491
+ static async planCompoundProjection(bundle, scope, resolved, registryDir) {
492
+ const artifacts = [];
493
+ const baseDir = scope === "global" ? ".cline" : ".cline";
494
+ for (const agentFile of resolved.agents) {
495
+ const canonicalRel = `agents/${agentFile}`;
496
+ const srcPath = path4.join(registryDir, "agents", agentFile);
497
+ if (await fs2.pathExists(srcPath)) {
498
+ const content = await fs2.readFile(srcPath, "utf8");
499
+ const rendered = this.renderRole(content, canonicalRel);
500
+ artifacts.push({
501
+ kind: "role",
502
+ canonical: canonicalRel,
503
+ relPath: `${baseDir}/agents/${agentFile}`.replace(/\\/g, "/"),
504
+ content: rendered,
505
+ managedMarker: true
506
+ });
507
+ }
508
+ }
509
+ for (const skillName of resolved.skills) {
510
+ const skillSrcDir = path4.join(registryDir, "skills", skillName);
511
+ if (await fs2.pathExists(skillSrcDir)) {
512
+ const entries = await fs2.readdir(skillSrcDir, { recursive: true, withFileTypes: true });
513
+ for (const entry of entries) {
514
+ if (entry.isFile()) {
515
+ const entryRel = path4.relative(skillSrcDir, path4.join(entry.parentPath || skillSrcDir, entry.name)).replace(/\\/g, "/");
516
+ const canonicalRel = `skills/${skillName}/${entryRel}`;
517
+ const targetRel = `${baseDir}/skills/${skillName}/${entryRel}`.replace(/\\/g, "/");
518
+ const fullSrcPath = path4.join(skillSrcDir, entryRel);
519
+ if (entry.name === "SKILL.md") {
520
+ const content = await fs2.readFile(fullSrcPath, "utf8");
521
+ const rendered = this.renderSkillMd(content, canonicalRel);
522
+ artifacts.push({
523
+ kind: "skill",
524
+ canonical: canonicalRel,
525
+ relPath: targetRel,
526
+ content: rendered,
527
+ managedMarker: true
528
+ });
529
+ } else {
530
+ artifacts.push({
531
+ kind: "skill",
532
+ canonical: canonicalRel,
533
+ relPath: targetRel,
534
+ sourceFilePath: fullSrcPath,
535
+ managedMarker: false
536
+ });
537
+ }
538
+ }
539
+ }
540
+ }
541
+ }
542
+ const ruleContent = this.renderCoordinatorRule(bundle, scope);
543
+ artifacts.push({
544
+ kind: "rule",
545
+ relPath: `${baseDir}/rules/agents-united-${bundle.name}.md`.replace(/\\/g, "/"),
546
+ content: ruleContent,
547
+ managedMarker: true
548
+ });
549
+ const manifestContent = this.renderTeamManifest(bundle, scope);
550
+ artifacts.push({
551
+ kind: "team-manifest",
552
+ relPath: `${baseDir}/agents-united/teams/${bundle.name}.yaml`.replace(/\\/g, "/"),
553
+ content: manifestContent,
554
+ managedMarker: false
555
+ });
556
+ return artifacts;
557
+ }
558
+ };
559
+
560
+ // src/core/projector.ts
561
+ var ProjectionError = class extends Error {
562
+ fileName;
563
+ cause;
564
+ constructor(message, fileName, cause) {
565
+ super(message, { cause });
566
+ this.name = "ProjectionError";
567
+ this.fileName = fileName;
568
+ this.cause = cause;
569
+ }
570
+ };
571
+ var TOOL_NAME_MAP = {
572
+ view_file: "Read",
573
+ replace_file_content: "Edit",
574
+ write_to_file: "Write",
575
+ run_command: "Bash",
576
+ grep_search: "Grep",
577
+ list_dir: "Glob",
578
+ read_file: "Read",
579
+ search_file_content: "Grep",
580
+ apply_diff: "Edit",
581
+ web_search: "WebSearch"
582
+ };
583
+ var ANTIGRAVITY_ONLY_KEYS = [
584
+ "version",
585
+ "type",
586
+ "permissionMode",
587
+ "commandExecutionPolicy",
588
+ "mainAgent",
589
+ "subagent",
590
+ "hooks"
591
+ ];
592
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]+?)\r?\n---/;
593
+ var HostProjector = class _HostProjector {
594
+ /**
595
+ * Splits a canonical file into its YAML frontmatter metadata and markdown body.
596
+ * The returned body starts right after the closing `---` delimiter so callers can
597
+ * insert a managed marker as the very first body line.
598
+ */
599
+ static parseFrontmatter(content, fileName = "<memory>") {
600
+ const match = content.match(FRONTMATTER_REGEX);
601
+ if (!match) {
602
+ throw new ProjectionError(
603
+ `No YAML frontmatter found in ${fileName}. Expected a leading "---" block.`,
604
+ fileName
605
+ );
606
+ }
607
+ let parsed;
608
+ try {
609
+ parsed = YAML.parse(match[1]);
610
+ } catch (cause) {
611
+ const err = cause instanceof Error ? cause : new Error(typeof cause === "string" ? cause : "");
612
+ throw new ProjectionError(
613
+ `Invalid YAML frontmatter in ${fileName}: ${err.message}`,
614
+ fileName,
615
+ err
616
+ );
617
+ }
618
+ const meta = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
619
+ const body = content.slice(match[0].length).replace(/^\r?\n/, "");
620
+ return { meta, body };
621
+ }
622
+ /**
623
+ * Projects a canonical `.agents/` file into the given {@link ProjectionProfile}'s
624
+ * native dialect, stamping it with the managed marker and preserving the body
625
+ * verbatim (line endings normalized to `\n`).
626
+ */
627
+ static projectAgent(md, profile, canonicalRelPath) {
628
+ const { meta, body } = _HostProjector.parseFrontmatter(md, canonicalRelPath);
629
+ const warnings = [];
630
+ const normalizedBody = body.replace(/\r\n/g, "\n");
631
+ const marker = `<!-- managed-by: agents-united | profile: ${profile} | canonical: ${canonicalRelPath} | do not edit -->`;
632
+ if (profile === "agentsmd") {
633
+ const name = typeof meta.name === "string" ? meta.name : "(unnamed)";
634
+ const description = typeof meta.description === "string" ? meta.description.replace(/[\r\n]+/g, " ").replace(/\|/g, "\\|") : "";
635
+ const link = canonicalRelPath.replace(/\\/g, "/");
636
+ const content2 = [
637
+ `## ${name}`,
638
+ "",
639
+ `| ${name} | ${description} |`,
640
+ "| --- | --- |",
641
+ "",
642
+ `[View source](${link})`,
643
+ "",
644
+ marker,
645
+ ""
646
+ ].join("\n");
647
+ return { content: content2, warnings };
648
+ }
649
+ if (profile === "cline") {
650
+ const content2 = ClineProjector.renderRole(md, canonicalRelPath);
651
+ return { content: content2, warnings };
652
+ }
653
+ const out = {};
654
+ out.name = meta.name;
655
+ if (typeof meta.description === "string") {
656
+ out.description = meta.description;
657
+ }
658
+ if (meta.model !== void 0 && meta.model !== "inherit") {
659
+ out.model = meta.model;
660
+ }
661
+ for (const key of ANTIGRAVITY_ONLY_KEYS) {
662
+ delete out[key];
663
+ }
664
+ if (Array.isArray(meta.tools)) {
665
+ const translated = [];
666
+ for (const raw of meta.tools) {
667
+ if (typeof raw !== "string") {
668
+ continue;
669
+ }
670
+ const mapped = TOOL_NAME_MAP[raw];
671
+ if (mapped !== void 0) {
672
+ translated.push(mapped);
673
+ } else {
674
+ warnings.push(
675
+ `Dropping unknown tool "${raw}" for profile ${profile}: no known name mapping (never kept, never invented).`
676
+ );
677
+ }
678
+ }
679
+ out.tools = translated;
680
+ }
681
+ let yamlStr;
682
+ try {
683
+ yamlStr = YAML.stringify(out).replace(/\r\n/g, "\n").trimEnd();
684
+ } catch (cause) {
685
+ const err = cause instanceof Error ? cause : new Error(String(cause));
686
+ throw new ProjectionError(
687
+ `Failed to serialize frontmatter for ${canonicalRelPath}: ${err.message}`,
688
+ canonicalRelPath,
689
+ err
690
+ );
691
+ }
692
+ const content = `---
693
+ ${yamlStr}
694
+ ---
695
+ ${marker}
696
+ ${normalizedBody}`;
697
+ return { content, warnings };
698
+ }
699
+ /**
700
+ * Returns true when `content` is one of our managed projections. Agent projections
701
+ * carry the marker as the first body line after the frontmatter; the AGENTS.md
702
+ * bridge has no frontmatter, so the whole document is checked. Used to distinguish
703
+ * "our own deterministic projection" (safe to regenerate) from foreign user files
704
+ * (never overwritten without --force).
705
+ */
706
+ static hasManagedMarker(content) {
707
+ const match = content.match(FRONTMATTER_REGEX);
708
+ if (match) {
709
+ const rest = content.slice(match[0].length).replace(/^\r?\n/, "");
710
+ const firstLine = rest.split(/\r?\n/)[0] || "";
711
+ return firstLine.includes("managed-by: agents-united");
712
+ }
713
+ return content.includes("managed-by: agents-united");
714
+ }
715
+ /**
716
+ * Builds the root AGENTS.md bridge index, grouping canonical assets by type and
717
+ * linking each to its canonical `.agents/` path. Deterministic for identical input.
718
+ */
719
+ static buildAgentsMdIndex(assets) {
720
+ const sections = ["agent", "skill", "workflow"];
721
+ const lines = [];
722
+ lines.push("# agents-united \u2014 Managed Agents Index");
723
+ lines.push("");
724
+ lines.push("<!-- managed-by: agents-united | profile: agentsmd | do not edit -->");
725
+ lines.push("");
726
+ lines.push(
727
+ "This index links the canonical `.agents/` store for runtimes without a subagent loader. Files under `.claude/`, `.cursor/`, `.cline/`, and `.opencode/` are machine-managed projections (see marker lines)."
728
+ );
729
+ lines.push("");
730
+ for (const type of sections) {
731
+ const heading = type === "agent" ? "## Agents" : type === "skill" ? "## Skills" : "## Workflows";
732
+ lines.push(heading);
733
+ lines.push("");
734
+ const items = assets.filter((a) => a.type === type).map((a) => ({ ...a, relPath: a.relPath.replace(/\\/g, "/") }));
735
+ if (items.length === 0) {
736
+ lines.push("_none_");
737
+ lines.push("");
738
+ continue;
739
+ }
740
+ for (const item of items) {
741
+ lines.push(`- [${item.name}](${item.relPath})`);
742
+ }
743
+ lines.push("");
744
+ }
745
+ return lines.join("\n");
153
746
  }
154
747
  };
155
748
 
156
749
  // src/core/installer.ts
157
- var InstallEngine = class {
750
+ var InstallEngine = class _InstallEngine {
158
751
  registry;
159
752
  constructor(registry2) {
160
753
  this.registry = registry2 || new RegistryResolver();
161
754
  }
162
755
  async calculateHash(filePath) {
163
- const buffer = await fs2.readFile(filePath);
756
+ const buffer = await fs3.readFile(filePath);
164
757
  return "sha256:" + crypto.createHash("sha256").update(buffer).digest("hex");
165
758
  }
166
759
  async readLockfile(lockfilePath) {
167
- if (await fs2.pathExists(lockfilePath)) {
760
+ if (await fs3.pathExists(lockfilePath)) {
168
761
  try {
169
- return await fs2.readJson(lockfilePath);
762
+ return await fs3.readJson(lockfilePath);
170
763
  } catch {
171
764
  }
172
765
  }
@@ -182,272 +775,2330 @@ var InstallEngine = class {
182
775
  files: {}
183
776
  };
184
777
  }
778
+ parseHosts(options) {
779
+ if (options.hosts && options.hosts.length > 0) {
780
+ return options.hosts;
781
+ }
782
+ if (options.target) {
783
+ const raw = Array.isArray(options.target) ? options.target.join(",") : options.target;
784
+ const parsed = raw.split(",").map((s) => s.trim().toLowerCase());
785
+ const valid = [];
786
+ for (const h of parsed) {
787
+ if (isKnownHost(h)) {
788
+ valid.push(h);
789
+ }
790
+ }
791
+ if (valid.length > 0) return valid;
792
+ }
793
+ return ["agents"];
794
+ }
795
+ parseMethod(options) {
796
+ if (options.copy) return "copy";
797
+ if (options.symlink || options.method === "symlink") return "symlink";
798
+ return options.method || "symlink";
799
+ }
800
+ parseScope(options) {
801
+ if (options.global) return "global";
802
+ return options.scope || "project";
803
+ }
804
+ async deployFile(src, dest, method, force) {
805
+ await fs3.ensureDir(path5.dirname(dest));
806
+ if (await fs3.pathExists(dest) || await fs3.pathExists(dest).catch(() => false)) {
807
+ if (force) {
808
+ await fs3.remove(dest);
809
+ }
810
+ }
811
+ if (method === "symlink") {
812
+ try {
813
+ const stat = await fs3.stat(src);
814
+ const type = stat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
815
+ await fs3.ensureSymlink(src, dest, type);
816
+ return "symlink";
817
+ } catch {
818
+ await fs3.copy(src, dest, { overwrite: true });
819
+ return "copy";
820
+ }
821
+ }
822
+ await fs3.copy(src, dest, { overwrite: true });
823
+ return "copy";
824
+ }
825
+ /**
826
+ * Writes a projection copy (never a symlink). Projections are always translated
827
+ * copies of canonical assets, so their bytes differ from the source.
828
+ */
829
+ async deployProjection(dest, content) {
830
+ await fs3.ensureDir(path5.dirname(dest));
831
+ await fs3.writeFile(dest, content, "utf8");
832
+ }
833
+ /**
834
+ * Computes the canonical `.agents/` target dir and the workspace root that contains
835
+ * it. Projections are recorded relative to this root (e.g. '.claude/agents/x.md').
836
+ */
837
+ static projectionRoot(scope, targetDir) {
838
+ const agentsTarget = AgentHostAdapter.resolveHostDir(scope, "agents", targetDir);
839
+ return { agentsTarget, root: path5.resolve(path5.dirname(agentsTarget)) };
840
+ }
841
+ toPosix(p) {
842
+ return p.split(path5.sep).join("/");
843
+ }
844
+ canonicalRelAgent(agentFile) {
845
+ return `.agents/agents/${agentFile}`;
846
+ }
847
+ lockRel(subdir, file) {
848
+ return path5.join(subdir, file);
849
+ }
850
+ destForProjection(host, root, file) {
851
+ const def = HOST_REGISTRY[host];
852
+ const base = resolveHostProjectDir(host, root);
853
+ if (def.agentsSubdir) return path5.join(base, def.agentsSubdir, file);
854
+ return base;
855
+ }
856
+ recordProjectedTo(lockfile, relKey, projPath) {
857
+ const normProj = this.toPosix(projPath);
858
+ const posixKey = relKey.replace(/\\/g, "/");
859
+ const sysKey = relKey.replace(/\//g, path5.sep);
860
+ const targetKey = lockfile.files[posixKey] ? posixKey : lockfile.files[sysKey] ? sysKey : relKey;
861
+ if (!lockfile.files[targetKey]) {
862
+ lockfile.files[targetKey] = { hash: "", installedAt: (/* @__PURE__ */ new Date()).toISOString() };
863
+ }
864
+ const asset = lockfile.files[targetKey];
865
+ asset.projectedTo = asset.projectedTo || [];
866
+ if (!asset.projectedTo.includes(normProj)) {
867
+ asset.projectedTo.push(normProj);
868
+ }
869
+ }
870
+ async removeEmptyProjectionDirs(workspaceRoot, projPath) {
871
+ let dir = path5.dirname(path5.join(workspaceRoot, projPath));
872
+ while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {
873
+ try {
874
+ const entries = await fs3.readdir(dir);
875
+ if (entries.length > 0) break;
876
+ await fs3.rmdir(dir);
877
+ } catch {
878
+ break;
879
+ }
880
+ dir = path5.dirname(dir);
881
+ }
882
+ }
883
+ /**
884
+ * Dry-run: compute the projection plan (paths + warnings) without writing anything.
885
+ */
886
+ async buildProjections(fanoutHosts, resolved, registryDir, scope, targetDir) {
887
+ const infos = [];
888
+ if (fanoutHosts.length === 0) return infos;
889
+ const { root } = _InstallEngine.projectionRoot(scope, targetDir);
890
+ for (const host of fanoutHosts) {
891
+ if (host === "codex") {
892
+ infos.push({ host, path: "AGENTS.md", kind: "bridge", warnings: [] });
893
+ continue;
894
+ }
895
+ if (host === "cline") {
896
+ const bundleDef = resolved.targetBundle ? await this.registry.getBundle(resolved.targetBundle) : void 0;
897
+ if (bundleDef) {
898
+ const artifacts = await ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
899
+ for (const artifact of artifacts) {
900
+ infos.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
901
+ }
902
+ continue;
903
+ }
904
+ }
905
+ const base = resolveHostProjectDir(host, root);
906
+ const subdir = HOST_REGISTRY[host].agentsSubdir ?? "agents";
907
+ for (const agentFile of resolved.agents) {
908
+ const content = await fs3.readFile(path5.join(registryDir, "agents", agentFile), "utf8");
909
+ const res = HostProjector.projectAgent(content, HOST_REGISTRY[host].profile, this.canonicalRelAgent(agentFile));
910
+ const dest = path5.join(base, subdir, agentFile);
911
+ infos.push({ host, path: this.toPosix(path5.relative(root, dest)), kind: "role", warnings: res.warnings });
912
+ }
913
+ }
914
+ return infos;
915
+ }
916
+ /**
917
+ * Real fan-out: project canonical `.agents/` agents into each non-bridge host, build
918
+ * the root AGENTS.md bridge for `codex`, and record every projection in the lockfile
919
+ * of the canonical `.agents/` store that `targetDir` points at.
920
+ */
921
+ async applyFanout(fanoutHosts, resolved, registryDir, scope, targetDir, lockfile, options, projections) {
922
+ if (fanoutHosts.length === 0) return;
923
+ const { root } = _InstallEngine.projectionRoot(scope, options.targetDir);
924
+ const now = (/* @__PURE__ */ new Date()).toISOString();
925
+ for (const host of fanoutHosts) {
926
+ if (host === "codex") {
927
+ const assets = [];
928
+ resolved.agents.forEach((f) => assets.push({ name: f.replace(/\.md$/, ""), type: "agent", relPath: `.agents/agents/${f}` }));
929
+ resolved.workflows.forEach((f) => assets.push({ name: f.replace(/\.md$/, ""), type: "workflow", relPath: `.agents/workflows/${f}` }));
930
+ resolved.skills.forEach((s) => assets.push({ name: s, type: "skill", relPath: `.agents/skills/${s}/SKILL.md` }));
931
+ const bridge = HostProjector.buildAgentsMdIndex(assets);
932
+ const bridgeDest = path5.join(root, "AGENTS.md");
933
+ if (await fs3.pathExists(bridgeDest) && !options.force) {
934
+ const existing = await fs3.readFile(bridgeDest, "utf8");
935
+ if (!HostProjector.hasManagedMarker(existing)) {
936
+ throw new Error(
937
+ "File AGENTS.md already exists at the workspace root and is not managed by agents-united. Use --force to overwrite."
938
+ );
939
+ }
940
+ }
941
+ await this.deployProjection(bridgeDest, bridge);
942
+ projections.push({ host, path: "AGENTS.md", kind: "bridge", warnings: [] });
943
+ resolved.agents.forEach((f) => this.recordProjectedTo(lockfile, this.lockRel("agents", f), "AGENTS.md"));
944
+ resolved.workflows.forEach((f) => this.recordProjectedTo(lockfile, this.lockRel("workflows", f), "AGENTS.md"));
945
+ resolved.skills.forEach((s) => this.recordProjectedTo(lockfile, this.lockRel("skills", path5.join(s, "SKILL.md")), "AGENTS.md"));
946
+ continue;
947
+ }
948
+ if (host === "cline") {
949
+ const bundleDef = resolved.targetBundle ? await this.registry.getBundle(resolved.targetBundle) : void 0;
950
+ if (bundleDef) {
951
+ const artifacts = await ClineProjector.planCompoundProjection(bundleDef, scope, resolved, registryDir);
952
+ for (const artifact of artifacts) {
953
+ const dest = path5.join(root, artifact.relPath);
954
+ if (await fs3.pathExists(dest) && !options.force) {
955
+ const existing = await fs3.readFile(dest, "utf8").catch(() => null);
956
+ if (existing !== null) {
957
+ if (artifact.managedMarker && !HostProjector.hasManagedMarker(existing)) {
958
+ throw new Error(
959
+ `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
960
+ );
961
+ } else if (!artifact.managedMarker) {
962
+ const registeredProj = lockfile.projections?.[artifact.relPath];
963
+ if (!registeredProj) {
964
+ throw new Error(
965
+ `Projection target ${artifact.relPath} already exists and is not managed by agents-united. Use --force to overwrite.`
966
+ );
967
+ }
968
+ }
969
+ }
970
+ }
971
+ if (artifact.content !== void 0) {
972
+ await this.deployProjection(dest, artifact.content);
973
+ } else if (artifact.sourceFilePath) {
974
+ await fs3.ensureDir(path5.dirname(dest));
975
+ await fs3.copy(artifact.sourceFilePath, dest, { overwrite: true });
976
+ }
977
+ const deployedHash = await this.calculateHash(dest);
978
+ lockfile.projections = lockfile.projections || {};
979
+ const existingProj = lockfile.projections[artifact.relPath];
980
+ const bundleName = bundleDef.name;
981
+ const owners = existingProj?.owners ? Array.from(/* @__PURE__ */ new Set([...existingProj.owners, bundleName])) : [bundleName];
982
+ lockfile.projections[artifact.relPath] = {
983
+ host: "cline",
984
+ kind: artifact.kind,
985
+ canonical: artifact.canonical,
986
+ owners,
987
+ hash: deployedHash,
988
+ installedAt: now,
989
+ managedMarker: artifact.managedMarker
990
+ };
991
+ projections.push({ host, path: artifact.relPath, kind: artifact.kind, warnings: [] });
992
+ if (artifact.canonical) {
993
+ this.recordProjectedTo(lockfile, artifact.canonical, artifact.relPath);
994
+ }
995
+ }
996
+ continue;
997
+ }
998
+ }
999
+ const base = resolveHostProjectDir(host, root);
1000
+ const subdir = HOST_REGISTRY[host].agentsSubdir ?? "agents";
1001
+ for (const agentFile of resolved.agents) {
1002
+ const content = await fs3.readFile(path5.join(registryDir, "agents", agentFile), "utf8");
1003
+ const canonicalRel = this.canonicalRelAgent(agentFile);
1004
+ const res = HostProjector.projectAgent(content, HOST_REGISTRY[host].profile, canonicalRel);
1005
+ const dest = path5.join(base, subdir, agentFile);
1006
+ if (await fs3.pathExists(dest) && !options.force) {
1007
+ const existing = await fs3.readFile(dest, "utf8");
1008
+ if (!HostProjector.hasManagedMarker(existing)) {
1009
+ throw new Error(
1010
+ `Projection target ${this.toPosix(path5.relative(root, dest))} already exists and is not managed by agents-united. Use --force to overwrite.`
1011
+ );
1012
+ }
1013
+ }
1014
+ await this.deployProjection(dest, res.content);
1015
+ const projPath = this.toPosix(path5.relative(root, dest));
1016
+ projections.push({ host, path: projPath, kind: "role", warnings: res.warnings });
1017
+ this.recordProjectedTo(lockfile, this.lockRel("agents", agentFile), projPath);
1018
+ }
1019
+ }
1020
+ }
185
1021
  async install(identifier, options = {}) {
186
- const scope = options.global ? "global" : options.scope || "workspace";
187
- const targetDir = TargetAdapter.resolveTargetDir(scope, options.targetDir);
188
- const subPaths = TargetAdapter.getSubPaths(targetDir);
1022
+ const scope = this.parseScope(options);
1023
+ const method = this.parseMethod(options);
1024
+ const hosts = this.parseHosts(options);
189
1025
  const resolved = await this.registry.resolve(identifier);
190
1026
  const registryDir = this.registry.getRegistryDir();
1027
+ const targetDirs = hosts.map((h) => AgentHostAdapter.resolveHostDir(scope, h, options.targetDir));
1028
+ const hasCanonicalAgents = hosts.includes("agents");
1029
+ const fanoutHosts = (options.fanout || []).map((h) => typeof h === "string" ? h.trim().toLowerCase() : h).filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
191
1030
  if (options.dryRun) {
192
- return { installed: resolved, targetDir, dryRun: true };
1031
+ const projections2 = hasCanonicalAgents ? await this.buildProjections(fanoutHosts, resolved, registryDir, scope, options.targetDir) : [];
1032
+ return { installed: resolved, targetDirs, dryRun: true, method, projections: projections2 };
193
1033
  }
194
- await fs2.ensureDir(subPaths.agentsDir);
195
- await fs2.ensureDir(subPaths.skillsDir);
196
- await fs2.ensureDir(subPaths.workflowsDir);
197
- await fs2.ensureDir(subPaths.rulesDir);
198
- const lockfile = await this.readLockfile(subPaths.lockfile);
199
1034
  const now = (/* @__PURE__ */ new Date()).toISOString();
200
- for (const agentFile of resolved.agents) {
201
- const src = path3.join(registryDir, "agents", agentFile);
202
- const dest = path3.join(subPaths.agentsDir, agentFile);
203
- if (await fs2.pathExists(dest) && !options.force) {
204
- const existingHash = await this.calculateHash(dest);
205
- const relPath2 = path3.relative(targetDir, dest);
206
- if (lockfile.files[relPath2] && lockfile.files[relPath2].hash !== existingHash) {
207
- throw new Error(`File ${relPath2} has user modifications. Use --force to overwrite.`);
208
- }
209
- }
210
- await fs2.copy(src, dest, { overwrite: true });
211
- const hash = await this.calculateHash(dest);
212
- const relPath = path3.relative(targetDir, dest);
213
- lockfile.files[relPath] = {
214
- hash,
215
- bundle: resolved.targetBundle,
216
- installedAt: now
217
- };
218
- if (!lockfile.installed.agents.includes(agentFile)) {
219
- lockfile.installed.agents.push(agentFile);
1035
+ const projections = [];
1036
+ const agentsTarget = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
1037
+ for (const targetDir of targetDirs) {
1038
+ const subPaths = AgentHostAdapter.getSubPaths(targetDir);
1039
+ await fs3.ensureDir(subPaths.agentsDir);
1040
+ await fs3.ensureDir(subPaths.skillsDir);
1041
+ await fs3.ensureDir(subPaths.workflowsDir);
1042
+ await fs3.ensureDir(subPaths.rulesDir);
1043
+ const lockfile = await this.readLockfile(subPaths.lockfile);
1044
+ lockfile.scope = scope;
1045
+ lockfile.method = method;
1046
+ lockfile.hosts = hosts;
1047
+ const effectiveFanout = (options.fanout !== void 0 ? fanoutHosts : lockfile.fanout ?? []).filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
1048
+ if (options.fanout !== void 0) {
1049
+ lockfile.fanout = effectiveFanout;
220
1050
  }
221
- }
222
- for (const skillName of resolved.skills) {
223
- const src = path3.join(registryDir, "skills", skillName);
224
- const dest = path3.join(subPaths.skillsDir, skillName);
225
- if (await fs2.pathExists(dest) && !options.force) {
226
- }
227
- await fs2.copy(src, dest, { overwrite: true });
228
- const skillFile = path3.join(dest, "SKILL.md");
229
- if (await fs2.pathExists(skillFile)) {
230
- const hash = await this.calculateHash(skillFile);
231
- const relPath = path3.relative(targetDir, skillFile);
1051
+ for (const agentFile of resolved.agents) {
1052
+ const src = path5.join(registryDir, "agents", agentFile);
1053
+ const dest = path5.join(subPaths.agentsDir, agentFile);
1054
+ if (await fs3.pathExists(dest) && !options.force && method !== "symlink") {
1055
+ const existingHash = await this.calculateHash(dest).catch(() => null);
1056
+ const relPath2 = path5.relative(targetDir, dest);
1057
+ if (existingHash && lockfile.files[relPath2] && lockfile.files[relPath2].hash !== existingHash) {
1058
+ throw new Error(`File ${relPath2} has user modifications. Use --force to overwrite.`);
1059
+ }
1060
+ }
1061
+ const actualMethod = await this.deployFile(src, dest, method, options.force);
1062
+ const hash = await this.calculateHash(src);
1063
+ const relPath = path5.relative(targetDir, dest);
1064
+ const previousProjections = lockfile.files[relPath]?.projectedTo;
232
1065
  lockfile.files[relPath] = {
233
1066
  hash,
234
1067
  bundle: resolved.targetBundle,
235
- installedAt: now
1068
+ method: actualMethod,
1069
+ installedAt: now,
1070
+ ...previousProjections ? { projectedTo: previousProjections } : {}
236
1071
  };
1072
+ if (!lockfile.installed.agents.includes(agentFile)) {
1073
+ lockfile.installed.agents.push(agentFile);
1074
+ }
237
1075
  }
238
- if (!lockfile.installed.skills.includes(skillName)) {
239
- lockfile.installed.skills.push(skillName);
240
- }
241
- }
242
- for (const workflowFile of resolved.workflows) {
243
- const src = path3.join(registryDir, "workflows", workflowFile);
244
- const dest = path3.join(subPaths.workflowsDir, workflowFile);
245
- await fs2.copy(src, dest, { overwrite: true });
246
- const hash = await this.calculateHash(dest);
247
- const relPath = path3.relative(targetDir, dest);
248
- lockfile.files[relPath] = {
249
- hash,
250
- bundle: resolved.targetBundle,
251
- installedAt: now
252
- };
253
- if (!lockfile.installed.workflows.includes(workflowFile)) {
254
- lockfile.installed.workflows.push(workflowFile);
1076
+ for (const skillName of resolved.skills) {
1077
+ const src = path5.join(registryDir, "skills", skillName);
1078
+ const dest = path5.join(subPaths.skillsDir, skillName);
1079
+ const actualMethod = await this.deployFile(src, dest, method, options.force);
1080
+ const skillFile = path5.join(src, "SKILL.md");
1081
+ if (await fs3.pathExists(skillFile)) {
1082
+ const hash = await this.calculateHash(skillFile);
1083
+ const relPath = path5.relative(targetDir, path5.join(subPaths.skillsDir, skillName, "SKILL.md"));
1084
+ const previousProjections = lockfile.files[relPath]?.projectedTo;
1085
+ lockfile.files[relPath] = {
1086
+ hash,
1087
+ bundle: resolved.targetBundle,
1088
+ method: actualMethod,
1089
+ installedAt: now,
1090
+ ...previousProjections ? { projectedTo: previousProjections } : {}
1091
+ };
1092
+ }
1093
+ if (!lockfile.installed.skills.includes(skillName)) {
1094
+ lockfile.installed.skills.push(skillName);
1095
+ }
255
1096
  }
256
- }
257
- for (const ruleFile of resolved.rules) {
258
- const src = path3.join(registryDir, "rules", ruleFile);
259
- const dest = path3.join(subPaths.rulesDir, ruleFile);
260
- if (await fs2.pathExists(src)) {
261
- await fs2.copy(src, dest, { overwrite: true });
262
- const hash = await this.calculateHash(dest);
263
- const relPath = path3.relative(targetDir, dest);
1097
+ for (const workflowFile of resolved.workflows) {
1098
+ const src = path5.join(registryDir, "workflows", workflowFile);
1099
+ const dest = path5.join(subPaths.workflowsDir, workflowFile);
1100
+ const actualMethod = await this.deployFile(src, dest, method, options.force);
1101
+ const hash = await this.calculateHash(src);
1102
+ const relPath = path5.relative(targetDir, dest);
1103
+ const previousProjections = lockfile.files[relPath]?.projectedTo;
264
1104
  lockfile.files[relPath] = {
265
1105
  hash,
266
1106
  bundle: resolved.targetBundle,
267
- installedAt: now
1107
+ method: actualMethod,
1108
+ installedAt: now,
1109
+ ...previousProjections ? { projectedTo: previousProjections } : {}
268
1110
  };
1111
+ if (!lockfile.installed.workflows.includes(workflowFile)) {
1112
+ lockfile.installed.workflows.push(workflowFile);
1113
+ }
269
1114
  }
1115
+ for (const ruleFile of resolved.rules) {
1116
+ const src = path5.join(registryDir, "rules", ruleFile);
1117
+ const dest = path5.join(subPaths.rulesDir, ruleFile);
1118
+ if (await fs3.pathExists(src)) {
1119
+ const actualMethod = await this.deployFile(src, dest, method, options.force);
1120
+ const hash = await this.calculateHash(src);
1121
+ const relPath = path5.relative(targetDir, dest);
1122
+ lockfile.files[relPath] = {
1123
+ hash,
1124
+ bundle: resolved.targetBundle,
1125
+ method: actualMethod,
1126
+ installedAt: now
1127
+ };
1128
+ }
1129
+ }
1130
+ if (resolved.targetBundle) {
1131
+ if (!lockfile.installed.bundles.includes(resolved.targetBundle)) {
1132
+ lockfile.installed.bundles.push(resolved.targetBundle);
1133
+ }
1134
+ lockfile.bundleVersions = lockfile.bundleVersions || {};
1135
+ const bundleDef = await this.registry.getBundle(resolved.targetBundle);
1136
+ lockfile.bundleVersions[resolved.targetBundle] = bundleDef?.version || "1.0.0";
1137
+ if (options.mode) {
1138
+ lockfile.bundleModes = lockfile.bundleModes || {};
1139
+ lockfile.bundleModes[resolved.targetBundle] = options.mode;
1140
+ }
1141
+ }
1142
+ if (hasCanonicalAgents && effectiveFanout.length > 0 && path5.resolve(targetDir) === path5.resolve(agentsTarget)) {
1143
+ await this.applyFanout(effectiveFanout, resolved, registryDir, scope, targetDir, lockfile, options, projections);
1144
+ }
1145
+ await fs3.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
270
1146
  }
271
- if (resolved.targetBundle && !lockfile.installed.bundles.includes(resolved.targetBundle)) {
272
- lockfile.installed.bundles.push(resolved.targetBundle);
273
- }
274
- await fs2.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
275
- return { installed: resolved, targetDir, dryRun: false };
1147
+ return { installed: resolved, targetDirs, dryRun: false, method, projections };
276
1148
  }
277
1149
  };
278
1150
 
279
1151
  // src/core/uninstaller.ts
280
- import path4 from "path";
1152
+ import path6 from "path";
281
1153
  import crypto2 from "crypto";
282
- import fs3 from "fs-extra";
1154
+ import fs4 from "fs-extra";
283
1155
  var UninstallEngine = class {
284
1156
  registry;
285
1157
  constructor(registry2) {
286
1158
  this.registry = registry2 || new RegistryResolver();
287
1159
  }
288
1160
  async calculateHash(filePath) {
289
- const buffer = await fs3.readFile(filePath);
1161
+ const buffer = await fs4.readFile(filePath);
290
1162
  return "sha256:" + crypto2.createHash("sha256").update(buffer).digest("hex");
291
1163
  }
292
- async uninstall(identifier, options = {}) {
293
- const scope = options.global ? "global" : options.scope || "workspace";
294
- const targetDir = TargetAdapter.resolveTargetDir(scope, options.targetDir);
295
- const subPaths = TargetAdapter.getSubPaths(targetDir);
296
- if (!await fs3.pathExists(subPaths.lockfile)) {
297
- throw new Error(`No agents-united.json lockfile found in ${targetDir}. Nothing to uninstall.`);
1164
+ /**
1165
+ * Determines whether a projected file still carries our managed marker. Agent
1166
+ * projections have YAML frontmatter with the marker as the first body line; the
1167
+ * AGENTS.md bridge (agentsmd profile) is a plain markdown index with no
1168
+ * frontmatter, so the marker is checked against the whole content.
1169
+ */
1170
+ async isManagedProjection(absPath, isAgentsMd) {
1171
+ const content = await fs4.readFile(absPath, "utf8");
1172
+ if (isAgentsMd) {
1173
+ return content.includes("managed-by: agents-united");
298
1174
  }
299
- const lockfile = await fs3.readJson(subPaths.lockfile);
300
- const resolved = await this.registry.resolve(identifier).catch(() => null);
301
- const removedFiles = [];
302
- if (resolved && resolved.targetBundle) {
303
- const bundleName = resolved.targetBundle;
304
- if (!lockfile.installed.bundles.includes(bundleName)) {
305
- throw new Error(`Bundle "${bundleName}" is not currently installed.`);
306
- }
307
- if (options.dryRun) {
308
- return { removed: resolved.agents.concat(resolved.skills, resolved.workflows), targetDir, dryRun: true };
309
- }
310
- for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
311
- if (assetMeta.bundle === bundleName) {
312
- const fullPath = path4.join(targetDir, relPath);
313
- if (await fs3.pathExists(fullPath)) {
314
- if (!options.force) {
315
- const currentHash = await this.calculateHash(fullPath).catch(() => null);
316
- if (currentHash && currentHash !== assetMeta.hash) {
317
- throw new Error(`File ${relPath} has user modifications. Use --force to remove.`);
318
- }
319
- }
320
- await fs3.remove(fullPath);
321
- removedFiles.push(relPath);
322
- }
323
- delete lockfile.files[relPath];
324
- }
325
- }
326
- lockfile.installed.bundles = lockfile.installed.bundles.filter((b) => b !== bundleName);
327
- if (resolved.agents) {
328
- lockfile.installed.agents = lockfile.installed.agents.filter((a) => !resolved.agents.includes(a));
1175
+ return HostProjector.hasManagedMarker(content);
1176
+ }
1177
+ /**
1178
+ * Removes empty projection dirs left behind after deleting a projected file
1179
+ * (e.g. `.claude/agents/` and then `.claude/`). Only ever removes empty dirs.
1180
+ */
1181
+ async removeEmptyProjectionDirs(workspaceRoot, projPath) {
1182
+ let dir = path6.dirname(path6.join(workspaceRoot, projPath));
1183
+ while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {
1184
+ try {
1185
+ const entries = await fs4.readdir(dir);
1186
+ if (entries.length > 0) break;
1187
+ await fs4.rmdir(dir);
1188
+ } catch {
1189
+ break;
329
1190
  }
330
- if (resolved.skills) {
331
- lockfile.installed.skills = lockfile.installed.skills.filter((s) => !resolved.skills.includes(s));
1191
+ dir = path6.dirname(dir);
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Removes every recorded projection for a canonical asset, guarding against
1196
+ * clobbering user-modified files. Mirrors the hash-conflict error pattern.
1197
+ */
1198
+ async removeProjections(workspaceRoot, projectedTo, force) {
1199
+ for (const projPath of projectedTo) {
1200
+ const absProjection = path6.join(workspaceRoot, projPath);
1201
+ if (!await fs4.pathExists(absProjection)) {
1202
+ continue;
332
1203
  }
333
- if (resolved.workflows) {
334
- lockfile.installed.workflows = lockfile.installed.workflows.filter((w) => !resolved.workflows.includes(w));
1204
+ const isAgentsMd = projPath === "AGENTS.md";
1205
+ const managed = await this.isManagedProjection(absProjection, isAgentsMd);
1206
+ if (!managed && !force) {
1207
+ throw new Error(
1208
+ `Projection ${projPath} has user modifications. Use --force to remove.`
1209
+ );
335
1210
  }
336
- await fs3.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
337
- return { removed: removedFiles, targetDir, dryRun: false };
1211
+ await this.safeRemoveProjection(absProjection);
1212
+ await this.removeEmptyProjectionDirs(workspaceRoot, projPath);
338
1213
  }
339
- const itemTargetRelPaths = Object.keys(lockfile.files).filter((p) => p.includes(identifier));
340
- if (itemTargetRelPaths.length === 0) {
341
- throw new Error(`No installed assets found matching "${identifier}".`);
1214
+ }
1215
+ async safeRemoveProjection(absPath) {
1216
+ try {
1217
+ const lstat = await fs4.lstat(absPath);
1218
+ if (lstat.isSymbolicLink()) {
1219
+ await fs4.unlink(absPath);
1220
+ return;
1221
+ }
1222
+ } catch {
342
1223
  }
343
- if (options.dryRun) {
344
- return { removed: itemTargetRelPaths, targetDir, dryRun: true };
1224
+ await fs4.remove(absPath);
1225
+ }
1226
+ parseHosts(options) {
1227
+ if (options.hosts && options.hosts.length > 0) return options.hosts;
1228
+ if (options.target) {
1229
+ const raw = Array.isArray(options.target) ? options.target.join(",") : options.target;
1230
+ const parsed = raw.split(",").map((s) => s.trim().toLowerCase());
1231
+ const valid = parsed.filter(isKnownHost);
1232
+ if (valid.length > 0) return valid;
345
1233
  }
346
- for (const relPath of itemTargetRelPaths) {
347
- const fullPath = path4.join(targetDir, relPath);
348
- if (await fs3.pathExists(fullPath)) {
349
- if (!options.force) {
350
- const currentHash = await this.calculateHash(fullPath).catch(() => null);
351
- const assetMeta = lockfile.files[relPath];
352
- if (currentHash && assetMeta && currentHash !== assetMeta.hash) {
353
- throw new Error(`File ${relPath} has user modifications. Use --force to remove.`);
1234
+ return ["agents"];
1235
+ }
1236
+ parseScope(options) {
1237
+ if (options.global) return "global";
1238
+ return options.scope || "project";
1239
+ }
1240
+ async safeRemove(targetDir, relPath) {
1241
+ const fullPath = path6.join(targetDir, relPath);
1242
+ if (relPath.startsWith("skills" + path6.sep) || relPath.startsWith("skills/")) {
1243
+ const parts = relPath.split(/[/\\]/);
1244
+ if (parts.length >= 2) {
1245
+ const skillDir = path6.join(targetDir, parts[0], parts[1]);
1246
+ try {
1247
+ const lstat = await fs4.lstat(skillDir);
1248
+ if (lstat.isSymbolicLink()) {
1249
+ await fs4.unlink(skillDir);
1250
+ return;
354
1251
  }
1252
+ } catch {
355
1253
  }
356
- await fs3.remove(fullPath);
357
- removedFiles.push(relPath);
358
1254
  }
359
- delete lockfile.files[relPath];
360
1255
  }
361
- await fs3.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
362
- return { removed: removedFiles, targetDir, dryRun: false };
363
- }
364
- };
365
-
366
- // src/core/doctor.ts
367
- import path5 from "path";
368
- import fs4 from "fs-extra";
369
- import parseYaml from "yaml";
370
- var DoctorEngine = class {
371
- static async runDoctor(targetDir) {
372
- const root = TargetAdapter.resolveTargetDir("workspace", targetDir);
373
- const subPaths = TargetAdapter.getSubPaths(root);
374
- const issues = [];
375
- const warnings = [];
376
- let agentsCount = 0;
377
- let skillsCount = 0;
378
- let workflowsCount = 0;
379
- if (!await fs4.pathExists(subPaths.lockfile)) {
380
- warnings.push(`No lockfile found at ${subPaths.lockfile}. Workspace might not be initialized.`);
381
- } else {
382
- try {
383
- const manifest = await fs4.readJson(subPaths.lockfile);
384
- agentsCount = manifest.installed.agents.length;
385
- skillsCount = manifest.installed.skills.length;
386
- workflowsCount = manifest.installed.workflows.length;
387
- } catch (err) {
388
- issues.push(`Corrupt lockfile at ${subPaths.lockfile}: ${err.message}`);
1256
+ try {
1257
+ const lstat = await fs4.lstat(fullPath);
1258
+ if (lstat.isSymbolicLink()) {
1259
+ await fs4.unlink(fullPath);
1260
+ return;
389
1261
  }
1262
+ } catch {
390
1263
  }
391
- if (await fs4.pathExists(subPaths.agentsDir)) {
392
- const agentFiles = await fs4.readdir(subPaths.agentsDir);
393
- for (const file of agentFiles) {
394
- if (file.endsWith(".md")) {
395
- const content = await fs4.readFile(path5.join(subPaths.agentsDir, file), "utf8");
396
- const frontmatterMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
397
- if (!frontmatterMatch) {
398
- warnings.push(`Agent ${file} is missing YAML frontmatter.`);
1264
+ await fs4.remove(fullPath);
1265
+ }
1266
+ async uninstall(identifier, options = {}) {
1267
+ const scope = this.parseScope(options);
1268
+ const hosts = this.parseHosts(options);
1269
+ const targetDirs = hosts.map((h) => AgentHostAdapter.resolveHostDir(scope, h, options.targetDir));
1270
+ const resolved = await this.registry.resolve(identifier).catch(() => null);
1271
+ const totalRemoved = [];
1272
+ for (const targetDir of targetDirs) {
1273
+ const subPaths = AgentHostAdapter.getSubPaths(targetDir);
1274
+ if (!await fs4.pathExists(subPaths.lockfile)) {
1275
+ continue;
1276
+ }
1277
+ const lockfile = await fs4.readJson(subPaths.lockfile);
1278
+ const removedFiles = [];
1279
+ if (resolved && resolved.targetBundle) {
1280
+ const bundleName = resolved.targetBundle;
1281
+ if (lockfile.installed.bundles.includes(bundleName)) {
1282
+ if (!options.dryRun) {
1283
+ const workspaceRoot = path6.resolve(path6.dirname(targetDir));
1284
+ if (lockfile.projections) {
1285
+ for (const [projRelPath, proj] of Object.entries(lockfile.projections)) {
1286
+ if (proj.owners.includes(bundleName)) {
1287
+ proj.owners = proj.owners.filter((o) => o !== bundleName);
1288
+ if (proj.owners.length === 0) {
1289
+ const absProjection = path6.join(workspaceRoot, projRelPath);
1290
+ if (await fs4.pathExists(absProjection)) {
1291
+ if (proj.managedMarker) {
1292
+ const managed = await this.isManagedProjection(absProjection, projRelPath === "AGENTS.md");
1293
+ if (!managed && !options.force) {
1294
+ throw new Error(`Projection ${projRelPath} has user modifications. Use --force to remove.`);
1295
+ }
1296
+ } else {
1297
+ const currentHash = await this.calculateHash(absProjection).catch(() => null);
1298
+ if (currentHash && currentHash !== proj.hash && !options.force) {
1299
+ throw new Error(`Projection ${projRelPath} has user modifications. Use --force to remove.`);
1300
+ }
1301
+ }
1302
+ await this.safeRemoveProjection(absProjection);
1303
+ await this.removeEmptyProjectionDirs(workspaceRoot, projRelPath);
1304
+ }
1305
+ delete lockfile.projections[projRelPath];
1306
+ }
1307
+ }
1308
+ }
1309
+ }
1310
+ for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
1311
+ if (assetMeta.bundle === bundleName) {
1312
+ if (assetMeta.projectedTo && assetMeta.projectedTo.length > 0) {
1313
+ await this.removeProjections(workspaceRoot, assetMeta.projectedTo, options.force);
1314
+ }
1315
+ const fullPath = path6.join(targetDir, relPath);
1316
+ if (await fs4.pathExists(fullPath) || await fs4.pathExists(fullPath).catch(() => false)) {
1317
+ if (!options.force && assetMeta.method !== "symlink") {
1318
+ const currentHash = await this.calculateHash(fullPath).catch(() => null);
1319
+ if (currentHash && currentHash !== assetMeta.hash) {
1320
+ throw new Error(`File ${relPath} has user modifications. Use --force to remove.`);
1321
+ }
1322
+ }
1323
+ await this.safeRemove(targetDir, relPath);
1324
+ removedFiles.push(relPath);
1325
+ }
1326
+ delete lockfile.files[relPath];
1327
+ }
1328
+ }
1329
+ lockfile.installed.bundles = lockfile.installed.bundles.filter((b) => b !== bundleName);
1330
+ if (lockfile.bundleVersions) {
1331
+ delete lockfile.bundleVersions[bundleName];
1332
+ }
1333
+ if (resolved.agents) {
1334
+ lockfile.installed.agents = lockfile.installed.agents.filter((a) => !resolved.agents.includes(a));
1335
+ }
1336
+ if (resolved.skills) {
1337
+ lockfile.installed.skills = lockfile.installed.skills.filter((s) => !resolved.skills.includes(s));
1338
+ }
1339
+ if (resolved.workflows) {
1340
+ lockfile.installed.workflows = lockfile.installed.workflows.filter((w) => !resolved.workflows.includes(w));
1341
+ }
1342
+ await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
399
1343
  } else {
400
- try {
401
- const meta = parseYaml.parse(frontmatterMatch[1]);
402
- if (!meta.name) issues.push(`Agent ${file} missing 'name' in frontmatter.`);
403
- if (!meta.description) warnings.push(`Agent ${file} missing 'description'.`);
404
- if (!meta.model) warnings.push(`Agent ${file} missing 'model' definition.`);
405
- } catch (err) {
406
- issues.push(`Invalid YAML in agent ${file}: ${err.message}`);
1344
+ removedFiles.push(...resolved.agents, ...resolved.skills, ...resolved.workflows);
1345
+ }
1346
+ }
1347
+ } else {
1348
+ const itemTargetRelPaths = Object.keys(lockfile.files).filter((p) => p.includes(identifier));
1349
+ if (!options.dryRun) {
1350
+ for (const relPath of itemTargetRelPaths) {
1351
+ const fullPath = path6.join(targetDir, relPath);
1352
+ if (await fs4.pathExists(fullPath) || await fs4.pathExists(fullPath).catch(() => false)) {
1353
+ if (!options.force && lockfile.files[relPath]?.method !== "symlink") {
1354
+ const currentHash = await this.calculateHash(fullPath).catch(() => null);
1355
+ const assetMeta = lockfile.files[relPath];
1356
+ if (currentHash && assetMeta && currentHash !== assetMeta.hash) {
1357
+ throw new Error(`File ${relPath} has user modifications. Use --force to remove.`);
1358
+ }
1359
+ }
1360
+ await this.safeRemove(targetDir, relPath);
1361
+ removedFiles.push(relPath);
407
1362
  }
1363
+ delete lockfile.files[relPath];
408
1364
  }
1365
+ await fs4.writeJson(subPaths.lockfile, lockfile, { spaces: 2 });
1366
+ } else {
1367
+ removedFiles.push(...itemTargetRelPaths);
409
1368
  }
410
1369
  }
1370
+ totalRemoved.push(...removedFiles);
411
1371
  }
412
- return {
413
- valid: issues.length === 0,
414
- issues,
415
- warnings,
416
- agentsCount,
417
- skillsCount,
418
- workflowsCount
419
- };
1372
+ if (totalRemoved.length === 0 && !options.dryRun) {
1373
+ throw new Error(`No installed assets found matching "${identifier}".`);
1374
+ }
1375
+ return { removed: totalRemoved, targetDirs, dryRun: options.dryRun || false };
420
1376
  }
421
1377
  };
422
1378
 
423
- // src/cli.ts
424
- var cli = cac("agents-united");
425
- var registry = new RegistryResolver();
1379
+ // src/core/inventory.ts
1380
+ import path7 from "path";
1381
+ import os2 from "os";
1382
+ import fs5 from "fs-extra";
1383
+ var InventoryScanner = class _InventoryScanner {
1384
+ registry;
1385
+ constructor(registry2) {
1386
+ this.registry = registry2 || new RegistryResolver();
1387
+ }
1388
+ static formatDisplayLocation(scope, host, targetDir) {
1389
+ const home = os2.homedir();
1390
+ const cwd = process.cwd();
1391
+ let prettyPath = targetDir;
1392
+ if (targetDir.startsWith(cwd)) {
1393
+ const rel = path7.relative(cwd, targetDir);
1394
+ prettyPath = rel ? `./${rel.replace(/\\/g, "/")}` : ".";
1395
+ } else if (targetDir.startsWith(home)) {
1396
+ const rel = path7.relative(home, targetDir);
1397
+ prettyPath = `~/${rel.replace(/\\/g, "/")}`;
1398
+ }
1399
+ return `[${scope}: ${prettyPath}]`;
1400
+ }
1401
+ parseHosts(options) {
1402
+ if (options.hosts && options.hosts.length > 0) return options.hosts;
1403
+ if (options.target) {
1404
+ const raw = Array.isArray(options.target) ? options.target.join(",") : options.target;
1405
+ const parsed = raw.split(",").map((s) => s.trim().toLowerCase());
1406
+ const valid = parsed.filter((h) => KNOWN_HOST_IDS.includes(h));
1407
+ if (valid.length > 0) return valid;
1408
+ }
1409
+ return [...KNOWN_HOST_IDS];
1410
+ }
1411
+ async scan(options = {}) {
1412
+ const cwd = options.cwd || process.cwd();
1413
+ const hosts = this.parseHosts(options);
1414
+ const scopes = [];
1415
+ if (options.global || options.scope === "global") {
1416
+ scopes.push("global");
1417
+ } else if (options.scope === "project") {
1418
+ scopes.push("project");
1419
+ } else {
1420
+ scopes.push("project", "global");
1421
+ }
1422
+ const candidateDirs = [];
1423
+ if (options.targetDir) {
1424
+ candidateDirs.push({
1425
+ dir: path7.resolve(options.targetDir),
1426
+ scope: options.scope || "project",
1427
+ host: hosts[0] || "agents"
1428
+ });
1429
+ } else {
1430
+ for (const scope of scopes) {
1431
+ for (const host of hosts) {
1432
+ const dir = AgentHostAdapter.resolveHostDir(scope, host);
1433
+ candidateDirs.push({ dir, scope, host });
1434
+ }
1435
+ }
1436
+ }
1437
+ const allRecords = [];
1438
+ const bundleRecords = [];
1439
+ const standaloneRecords = [];
1440
+ const scannedTargetDirs = /* @__PURE__ */ new Set();
1441
+ for (const item of candidateDirs) {
1442
+ const lockfilePath = path7.join(item.dir, "agents-united.json");
1443
+ if (!await fs5.pathExists(lockfilePath)) {
1444
+ continue;
1445
+ }
1446
+ scannedTargetDirs.add(item.dir);
1447
+ let lockfile;
1448
+ try {
1449
+ lockfile = await fs5.readJson(lockfilePath);
1450
+ } catch {
1451
+ continue;
1452
+ }
1453
+ const displayLocation = _InventoryScanner.formatDisplayLocation(item.scope, item.host, item.dir);
1454
+ const bundleOwnedSkills = /* @__PURE__ */ new Set();
1455
+ const bundleOwnedAgents = /* @__PURE__ */ new Set();
1456
+ const bundleOwnedWorkflows = /* @__PURE__ */ new Set();
1457
+ const installedBundleNames = lockfile.installed?.bundles || [];
1458
+ for (const bundleName of installedBundleNames) {
1459
+ const bundleDef = await this.registry.getBundle(bundleName).catch(() => null);
1460
+ const resolvedBundle = await this.registry.resolve(bundleName).catch(() => null);
1461
+ const installedVersion = lockfile.bundleVersions?.[bundleName] || "1.0.0";
1462
+ const upstreamVersion = bundleDef?.version || "1.0.0";
1463
+ let driftStatus = "up-to-date";
1464
+ if (installedVersion !== upstreamVersion) {
1465
+ driftStatus = "outdated";
1466
+ }
1467
+ let fileCount = 0;
1468
+ if (lockfile.files) {
1469
+ fileCount = Object.values(lockfile.files).filter((f) => f.bundle === bundleName).length;
1470
+ }
1471
+ if (resolvedBundle) {
1472
+ resolvedBundle.skills?.forEach((s) => bundleOwnedSkills.add(s));
1473
+ resolvedBundle.agents?.forEach((a) => {
1474
+ bundleOwnedAgents.add(a);
1475
+ bundleOwnedAgents.add(a.replace(/\.md$/, ""));
1476
+ });
1477
+ resolvedBundle.workflows?.forEach((w) => {
1478
+ bundleOwnedWorkflows.add(w);
1479
+ bundleOwnedWorkflows.add(w.replace(/\.md$/, ""));
1480
+ });
1481
+ }
1482
+ if (bundleDef) {
1483
+ if (bundleDef.orchestrator) {
1484
+ bundleOwnedAgents.add(bundleDef.orchestrator);
1485
+ bundleOwnedAgents.add(bundleDef.orchestrator.replace(/\.md$/, ""));
1486
+ }
1487
+ bundleDef.skills?.forEach((s) => bundleOwnedSkills.add(s));
1488
+ bundleDef.agents?.forEach((a) => {
1489
+ bundleOwnedAgents.add(a);
1490
+ bundleOwnedAgents.add(a.replace(/\.md$/, ""));
1491
+ });
1492
+ bundleDef.workflows?.forEach((w) => {
1493
+ bundleOwnedWorkflows.add(w);
1494
+ bundleOwnedWorkflows.add(w.replace(/\.md$/, ""));
1495
+ });
1496
+ }
1497
+ const record = {
1498
+ id: `${bundleName}@${item.scope}:${item.host}`,
1499
+ name: bundleName,
1500
+ type: "bundle",
1501
+ scope: item.scope,
1502
+ host: item.host,
1503
+ targetDir: item.dir,
1504
+ displayLocation,
1505
+ installedVersion,
1506
+ upstreamVersion,
1507
+ driftStatus,
1508
+ method: lockfile.method || "symlink",
1509
+ fileCount,
1510
+ title: bundleDef?.name || bundleName,
1511
+ description: bundleDef?.description
1512
+ };
1513
+ allRecords.push(record);
1514
+ bundleRecords.push(record);
1515
+ }
1516
+ const installedSkills = lockfile.installed?.skills || [];
1517
+ for (const skillName of installedSkills) {
1518
+ if (bundleOwnedSkills.has(skillName)) continue;
1519
+ const record = {
1520
+ id: `skill:${skillName}@${item.scope}:${item.host}`,
1521
+ name: skillName,
1522
+ type: "skill",
1523
+ scope: item.scope,
1524
+ host: item.host,
1525
+ targetDir: item.dir,
1526
+ displayLocation,
1527
+ installedVersion: "1.0.0",
1528
+ upstreamVersion: "1.0.0",
1529
+ driftStatus: "up-to-date",
1530
+ method: lockfile.method || "symlink",
1531
+ fileCount: 1,
1532
+ title: skillName,
1533
+ description: `Standalone skill (${skillName})`
1534
+ };
1535
+ allRecords.push(record);
1536
+ standaloneRecords.push(record);
1537
+ }
1538
+ const installedAgents = lockfile.installed?.agents || [];
1539
+ for (const agentFile of installedAgents) {
1540
+ if (bundleOwnedAgents.has(agentFile)) continue;
1541
+ const agentName = agentFile.replace(/\.md$/, "");
1542
+ const record = {
1543
+ id: `agent:${agentName}@${item.scope}:${item.host}`,
1544
+ name: agentName,
1545
+ type: "agent",
1546
+ scope: item.scope,
1547
+ host: item.host,
1548
+ targetDir: item.dir,
1549
+ displayLocation,
1550
+ installedVersion: "1.0.0",
1551
+ upstreamVersion: "1.0.0",
1552
+ driftStatus: "up-to-date",
1553
+ method: lockfile.method || "symlink",
1554
+ fileCount: 1,
1555
+ title: agentName,
1556
+ description: `Standalone agent (${agentFile})`
1557
+ };
1558
+ allRecords.push(record);
1559
+ standaloneRecords.push(record);
1560
+ }
1561
+ }
1562
+ return {
1563
+ records: allRecords,
1564
+ bundles: bundleRecords,
1565
+ standaloneItems: standaloneRecords,
1566
+ targetDirs: Array.from(scannedTargetDirs)
1567
+ };
1568
+ }
1569
+ };
1570
+
1571
+ // src/core/updater.ts
1572
+ import path8 from "path";
1573
+ import crypto3 from "crypto";
1574
+ import fs6 from "fs-extra";
1575
+ var UpdateEngine = class {
1576
+ registry;
1577
+ scanner;
1578
+ installer;
1579
+ constructor(registry2, scanner2, installer2) {
1580
+ this.registry = registry2 || new RegistryResolver();
1581
+ this.scanner = scanner2 || new InventoryScanner(this.registry);
1582
+ this.installer = installer2 || new InstallEngine(this.registry);
1583
+ }
1584
+ async calculateHash(filePath) {
1585
+ const buffer = await fs6.readFile(filePath);
1586
+ return "sha256:" + crypto3.createHash("sha256").update(buffer).digest("hex");
1587
+ }
1588
+ /**
1589
+ * Determines whether a projected file still carries our managed marker (plan 007
1590
+ * M5). Agent projections place the marker as the first body line after frontmatter;
1591
+ * the AGENTS.md bridge has no frontmatter so it is checked over the whole content.
1592
+ */
1593
+ async isManagedProjection(absPath, isAgentsMd) {
1594
+ const content = await fs6.readFile(absPath, "utf8");
1595
+ if (isAgentsMd) {
1596
+ return content.includes("managed-by: agents-united");
1597
+ }
1598
+ return HostProjector.hasManagedMarker(content);
1599
+ }
1600
+ /**
1601
+ * Returns the first user-modified projection path belonging to a record, or '' when
1602
+ * every recorded projection still carries the managed marker.
1603
+ */
1604
+ async staleProjection(record, lockfile) {
1605
+ let workspaceRoot = path8.dirname(record.targetDir);
1606
+ for (const assetMeta of Object.values(lockfile.files)) {
1607
+ if (assetMeta.bundle !== record.name) continue;
1608
+ if (!assetMeta.projectedTo || assetMeta.projectedTo.length === 0) continue;
1609
+ for (const projPath of assetMeta.projectedTo) {
1610
+ const absProjection = path8.join(workspaceRoot, projPath);
1611
+ if (await fs6.pathExists(absProjection)) {
1612
+ const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md");
1613
+ if (!managed) {
1614
+ return projPath;
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ return "";
1620
+ }
1621
+ async checkUpdates(options = {}) {
1622
+ const inventory = await this.scanner.scan(options);
1623
+ const items = [];
1624
+ for (const record of inventory.records) {
1625
+ const hasUpdate = record.driftStatus === "outdated" || record.installedVersion !== record.upstreamVersion;
1626
+ items.push({
1627
+ record,
1628
+ hasUpdate,
1629
+ installedVersion: record.installedVersion,
1630
+ upstreamVersion: record.upstreamVersion,
1631
+ reason: hasUpdate ? `Upstream version (${record.upstreamVersion}) is newer than installed version (${record.installedVersion})` : void 0
1632
+ });
1633
+ }
1634
+ const outdatedCount = items.filter((i) => i.hasUpdate).length;
1635
+ const upToDateCount = items.filter((i) => !i.hasUpdate).length;
1636
+ return {
1637
+ items,
1638
+ outdatedCount,
1639
+ upToDateCount,
1640
+ totalCount: items.length
1641
+ };
1642
+ }
1643
+ async update(targets, options = {}) {
1644
+ const inventory = await this.scanner.scan({
1645
+ scope: options.scope,
1646
+ global: options.global,
1647
+ hosts: options.hosts,
1648
+ target: options.target,
1649
+ targetDir: options.targetDir,
1650
+ cwd: options.cwd
1651
+ });
1652
+ const isAll = targets === "__all__" || targets === "*" || Array.isArray(targets) && targets.includes("__all__");
1653
+ const targetList = Array.isArray(targets) ? targets : [targets];
1654
+ const recordsToUpdate = inventory.records.filter((r) => {
1655
+ if (isAll) return true;
1656
+ return targetList.includes(r.name) || targetList.includes(r.id);
1657
+ });
1658
+ if (recordsToUpdate.length === 0) {
1659
+ return {
1660
+ updated: [],
1661
+ skipped: [],
1662
+ targetDirs: inventory.targetDirs,
1663
+ dryRun: options.dryRun || false
1664
+ };
1665
+ }
1666
+ if (options.dryRun) {
1667
+ return {
1668
+ updated: recordsToUpdate,
1669
+ skipped: [],
1670
+ targetDirs: inventory.targetDirs,
1671
+ dryRun: true
1672
+ };
1673
+ }
1674
+ const updated = [];
1675
+ const skipped = [];
1676
+ for (const record of recordsToUpdate) {
1677
+ const lockfilePath = path8.join(record.targetDir, "agents-united.json");
1678
+ if (!await fs6.pathExists(lockfilePath)) {
1679
+ continue;
1680
+ }
1681
+ const lockfile = await fs6.readJson(lockfilePath);
1682
+ if (record.method === "copy" && !options.force && lockfile.files) {
1683
+ let hasConflict = false;
1684
+ let conflictRelPath = "";
1685
+ for (const [relPath, assetMeta] of Object.entries(lockfile.files)) {
1686
+ if (assetMeta.bundle === record.name || relPath.includes(record.name)) {
1687
+ const fullPath = path8.join(record.targetDir, relPath);
1688
+ if (await fs6.pathExists(fullPath)) {
1689
+ const currentHash = await this.calculateHash(fullPath).catch(() => null);
1690
+ if (currentHash && currentHash !== assetMeta.hash) {
1691
+ hasConflict = true;
1692
+ conflictRelPath = relPath;
1693
+ break;
1694
+ }
1695
+ }
1696
+ }
1697
+ }
1698
+ if (hasConflict) {
1699
+ skipped.push({
1700
+ record,
1701
+ reason: `User modifications detected in ${conflictRelPath}. Use --force to overwrite.`
1702
+ });
1703
+ continue;
1704
+ }
1705
+ }
1706
+ if (!options.force && lockfile.files) {
1707
+ const stale = await this.staleProjection(record, lockfile);
1708
+ if (stale) {
1709
+ skipped.push({
1710
+ record,
1711
+ reason: `User modifications detected in projection ${stale}. Use --force to overwrite.`
1712
+ });
1713
+ continue;
1714
+ }
1715
+ }
1716
+ await this.installer.install(record.name, {
1717
+ scope: record.scope,
1718
+ method: record.method,
1719
+ hosts: [record.host],
1720
+ targetDir: record.targetDir,
1721
+ fanout: options.fanout,
1722
+ force: true
1723
+ // force overwrite since we passed collision check above
1724
+ });
1725
+ if (await fs6.pathExists(lockfilePath)) {
1726
+ const updatedLockfile = await fs6.readJson(lockfilePath);
1727
+ updatedLockfile.bundleVersions = updatedLockfile.bundleVersions || {};
1728
+ updatedLockfile.bundleVersions[record.name] = record.upstreamVersion;
1729
+ await fs6.writeJson(lockfilePath, updatedLockfile, { spaces: 2 });
1730
+ }
1731
+ record.installedVersion = record.upstreamVersion;
1732
+ record.driftStatus = "up-to-date";
1733
+ updated.push(record);
1734
+ }
1735
+ return {
1736
+ updated,
1737
+ skipped,
1738
+ targetDirs: inventory.targetDirs,
1739
+ dryRun: false
1740
+ };
1741
+ }
1742
+ };
1743
+
1744
+ // src/core/doctor.ts
1745
+ import path10 from "path";
1746
+ import fs8 from "fs-extra";
1747
+ import YAML2 from "yaml";
1748
+
1749
+ // src/core/cline-capabilities.ts
1750
+ import path9 from "path";
1751
+ import fs7 from "fs-extra";
1752
+ import { execFile } from "child_process";
1753
+ var defaultProcessRunner = (executable, args, options) => {
1754
+ return new Promise((resolve) => {
1755
+ const timeout = options?.timeoutMs ?? 5e3;
1756
+ try {
1757
+ execFile(
1758
+ executable,
1759
+ args,
1760
+ {
1761
+ cwd: options?.cwd,
1762
+ env: options?.env ? { ...process.env, ...options.env } : process.env,
1763
+ timeout,
1764
+ shell: false
1765
+ },
1766
+ (error, stdout, stderr) => {
1767
+ if (error) {
1768
+ const exitCode = typeof error.code === "number" ? error.code : 1;
1769
+ resolve({
1770
+ exitCode,
1771
+ stdout: (stdout || "").toString(),
1772
+ stderr: (stderr || error.message || "").toString()
1773
+ });
1774
+ } else {
1775
+ resolve({
1776
+ exitCode: 0,
1777
+ stdout: (stdout || "").toString(),
1778
+ stderr: (stderr || "").toString()
1779
+ });
1780
+ }
1781
+ }
1782
+ );
1783
+ } catch (err) {
1784
+ const msg = err instanceof Error ? err.message : String(err);
1785
+ resolve({
1786
+ exitCode: 1,
1787
+ stdout: "",
1788
+ stderr: msg
1789
+ });
1790
+ }
1791
+ });
1792
+ };
1793
+ var ClineCapabilityProbe = class {
1794
+ runner;
1795
+ customResolver;
1796
+ constructor(runner, options) {
1797
+ this.runner = runner || defaultProcessRunner;
1798
+ this.customResolver = options?.resolveExecutable;
1799
+ }
1800
+ /**
1801
+ * Safe executable resolution across POSIX and Windows layouts.
1802
+ */
1803
+ resolveExecutable() {
1804
+ if (this.customResolver) {
1805
+ return this.customResolver();
1806
+ }
1807
+ const envBin = process.env.CLINE_BIN_PATH;
1808
+ if (envBin && path9.isAbsolute(envBin) && fs7.existsSync(envBin)) {
1809
+ return {
1810
+ executable: envBin,
1811
+ prefixArgs: [],
1812
+ source: "env-binary"
1813
+ };
1814
+ }
1815
+ const pathEnv = process.env.PATH || "";
1816
+ const pathSeparator = process.platform === "win32" ? ";" : ":";
1817
+ const dirs = pathEnv.split(pathSeparator).filter(Boolean);
1818
+ if (process.platform === "win32") {
1819
+ const exeNames = ["cline.cmd", "cline.bat", "cline.exe", "cline"];
1820
+ for (const dir of dirs) {
1821
+ for (const exeName of exeNames) {
1822
+ const fullPath = path9.join(dir, exeName);
1823
+ if (fs7.existsSync(fullPath)) {
1824
+ const adjacentWrapper1 = path9.join(dir, "node_modules", "cline", "bin", "cline");
1825
+ const adjacentWrapper2 = path9.join(dir, "node_modules", "@cline", "cline", "bin", "cline");
1826
+ if (fs7.existsSync(adjacentWrapper1)) {
1827
+ return {
1828
+ executable: process.execPath,
1829
+ prefixArgs: [adjacentWrapper1],
1830
+ source: "node-wrapper"
1831
+ };
1832
+ }
1833
+ if (fs7.existsSync(adjacentWrapper2)) {
1834
+ return {
1835
+ executable: process.execPath,
1836
+ prefixArgs: [adjacentWrapper2],
1837
+ source: "node-wrapper"
1838
+ };
1839
+ }
1840
+ return {
1841
+ executable: fullPath,
1842
+ prefixArgs: [],
1843
+ source: "path-executable"
1844
+ };
1845
+ }
1846
+ }
1847
+ }
1848
+ } else {
1849
+ for (const dir of dirs) {
1850
+ const fullPath = path9.join(dir, "cline");
1851
+ if (fs7.existsSync(fullPath)) {
1852
+ return {
1853
+ executable: fullPath,
1854
+ prefixArgs: [],
1855
+ source: "path-executable"
1856
+ };
1857
+ }
1858
+ }
1859
+ }
1860
+ return null;
1861
+ }
1862
+ /**
1863
+ * Run read-only capability probe against local Cline.
1864
+ */
1865
+ async probe() {
1866
+ const cmd = this.resolveExecutable();
1867
+ const diagnostics = [];
1868
+ if (!cmd) {
1869
+ diagnostics.push("Cline executable not found on PATH or via CLINE_BIN_PATH.");
1870
+ return {
1871
+ installed: false,
1872
+ namedTeams: false,
1873
+ rolePresetConsumer: "unknown",
1874
+ diagnostics
1875
+ };
1876
+ }
1877
+ let version;
1878
+ try {
1879
+ const verRes = await this.runner(cmd.executable, [...cmd.prefixArgs, "version"], {
1880
+ timeoutMs: 5e3
1881
+ });
1882
+ if (verRes.exitCode === 0) {
1883
+ version = verRes.stdout.trim().split("\n")[0]?.trim();
1884
+ } else {
1885
+ diagnostics.push(`Cline version check failed (exit code ${verRes.exitCode}): ${verRes.stderr.trim()}`);
1886
+ return {
1887
+ installed: false,
1888
+ command: cmd,
1889
+ namedTeams: false,
1890
+ rolePresetConsumer: "unknown",
1891
+ diagnostics
1892
+ };
1893
+ }
1894
+ } catch (err) {
1895
+ const msg = err instanceof Error ? err.message : String(err);
1896
+ diagnostics.push(`Error executing Cline version check: ${msg}`);
1897
+ return {
1898
+ installed: false,
1899
+ command: cmd,
1900
+ namedTeams: false,
1901
+ rolePresetConsumer: "unknown",
1902
+ diagnostics
1903
+ };
1904
+ }
1905
+ let namedTeams = false;
1906
+ try {
1907
+ const probeRes = await this.runner(
1908
+ cmd.executable,
1909
+ [...cmd.prefixArgs, "--team-name", "agents-united-capability-probe", "version"],
1910
+ { timeoutMs: 5e3 }
1911
+ );
1912
+ if (probeRes.exitCode === 0) {
1913
+ namedTeams = true;
1914
+ } else {
1915
+ diagnostics.push(`Named teams probe returned non-zero (exit code ${probeRes.exitCode}). Falling back to adaptive session.`);
1916
+ }
1917
+ } catch (err) {
1918
+ const msg = err instanceof Error ? err.message : String(err);
1919
+ diagnostics.push(`Named teams probe failed: ${msg}`);
1920
+ }
1921
+ return {
1922
+ installed: true,
1923
+ version,
1924
+ command: cmd,
1925
+ namedTeams,
1926
+ rolePresetConsumer: "unknown",
1927
+ diagnostics
1928
+ };
1929
+ }
1930
+ };
1931
+
1932
+ // src/core/doctor.ts
1933
+ var DoctorEngine = class {
1934
+ static async isManagedProjection(absPath, isAgentsMd) {
1935
+ const content = await fs8.readFile(absPath, "utf8");
1936
+ if (isAgentsMd) {
1937
+ return content.includes("managed-by: agents-united");
1938
+ }
1939
+ return HostProjector.hasManagedMarker(content);
1940
+ }
1941
+ static async runDoctor(targetDir, host) {
1942
+ const root = AgentHostAdapter.resolveHostDir("project", "agents", targetDir);
1943
+ const subPaths = AgentHostAdapter.getSubPaths(root);
1944
+ const issues = [];
1945
+ const warnings = [];
1946
+ let agentsCount = 0;
1947
+ let skillsCount = 0;
1948
+ let workflowsCount = 0;
1949
+ let manifest;
1950
+ let clineCapability;
1951
+ let isInitialized = false;
1952
+ const lockfileExists = await fs8.pathExists(subPaths.lockfile);
1953
+ const agentsDirExists = await fs8.pathExists(subPaths.agentsDir);
1954
+ if (lockfileExists) {
1955
+ try {
1956
+ const parsed = await fs8.readJson(subPaths.lockfile);
1957
+ manifest = parsed;
1958
+ agentsCount = parsed.installed?.agents?.length || 0;
1959
+ skillsCount = parsed.installed?.skills?.length || 0;
1960
+ workflowsCount = parsed.installed?.workflows?.length || 0;
1961
+ isInitialized = true;
1962
+ } catch (err) {
1963
+ issues.push(`Corrupt lockfile at ${subPaths.lockfile}: ${err.message}`);
1964
+ }
1965
+ } else if (agentsDirExists) {
1966
+ warnings.push(`No lockfile found at ${subPaths.lockfile}, but agent files were detected in ${subPaths.agentsDir}. Run 'agents update' to sync.`);
1967
+ }
1968
+ if (await fs8.pathExists(subPaths.agentsDir)) {
1969
+ const agentFiles = await fs8.readdir(subPaths.agentsDir);
1970
+ for (const file of agentFiles) {
1971
+ if (file.endsWith(".md")) {
1972
+ const content = await fs8.readFile(path10.join(subPaths.agentsDir, file), "utf8");
1973
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
1974
+ if (!frontmatterMatch) {
1975
+ warnings.push(`Agent ${file} is missing YAML frontmatter.`);
1976
+ } else {
1977
+ try {
1978
+ const meta = YAML2.parse(frontmatterMatch[1]);
1979
+ if (!meta.name) issues.push(`Agent ${file} missing 'name' in frontmatter.`);
1980
+ if (!meta.description) warnings.push(`Agent ${file} missing 'description'.`);
1981
+ if (!meta.model) warnings.push(`Agent ${file} missing 'model' definition.`);
1982
+ } catch (err) {
1983
+ issues.push(`Invalid YAML in agent ${file}: ${err.message}`);
1984
+ }
1985
+ }
1986
+ }
1987
+ }
1988
+ }
1989
+ if (manifest) {
1990
+ const workspaceRoot = path10.resolve(path10.dirname(root));
1991
+ for (const [relPath, assetMeta] of Object.entries(manifest.files)) {
1992
+ const projectedTo = assetMeta.projectedTo;
1993
+ if (!projectedTo || projectedTo.length === 0) continue;
1994
+ for (const projPath of projectedTo) {
1995
+ const absProjection = path10.join(workspaceRoot, projPath);
1996
+ if (!await fs8.pathExists(absProjection)) {
1997
+ warnings.push(
1998
+ `Missing projection ${projPath} for canonical ${relPath}. Re-run: agents add ... --fanout`
1999
+ );
2000
+ continue;
2001
+ }
2002
+ const managed = await this.isManagedProjection(absProjection, projPath === "AGENTS.md");
2003
+ if (!managed) {
2004
+ warnings.push(`user-modified projection ${projPath}`);
2005
+ }
2006
+ }
2007
+ }
2008
+ if (manifest.projections) {
2009
+ for (const [projRelPath, proj] of Object.entries(manifest.projections)) {
2010
+ const absPath = path10.join(workspaceRoot, projRelPath);
2011
+ if (!await fs8.pathExists(absPath)) {
2012
+ warnings.push(`Missing compound projection ${projRelPath} (owners: ${proj.owners.join(", ")}).`);
2013
+ continue;
2014
+ }
2015
+ if (proj.managedMarker) {
2016
+ const managed = await this.isManagedProjection(absPath, projRelPath === "AGENTS.md");
2017
+ if (!managed) {
2018
+ warnings.push(`user-modified projection ${projRelPath}`);
2019
+ }
2020
+ }
2021
+ }
2022
+ }
2023
+ }
2024
+ if (host === "cline") {
2025
+ const probe = new ClineCapabilityProbe();
2026
+ clineCapability = await probe.probe();
2027
+ if (!clineCapability.installed) {
2028
+ warnings.push("Cline executable was not detected on PATH or via CLINE_BIN_PATH.");
2029
+ }
2030
+ }
2031
+ return {
2032
+ valid: issues.length === 0,
2033
+ targetDir: root,
2034
+ isInitialized,
2035
+ issues,
2036
+ warnings,
2037
+ agentsCount,
2038
+ skillsCount,
2039
+ workflowsCount,
2040
+ clineCapability
2041
+ };
2042
+ }
2043
+ };
2044
+
2045
+ // src/core/cline-launcher.ts
2046
+ import path11 from "path";
2047
+ import os3 from "os";
2048
+ import crypto4 from "crypto";
2049
+ import fs9 from "fs-extra";
2050
+ var ClineLauncher = class _ClineLauncher {
2051
+ probe;
2052
+ runner;
2053
+ constructor(probe, runner) {
2054
+ this.probe = probe || new ClineCapabilityProbe();
2055
+ this.runner = runner || defaultProcessRunner;
2056
+ }
2057
+ /**
2058
+ * Generate stable team name: au-<bundle>-<first-8-sha256-of-workspace>
2059
+ */
2060
+ static generateTeamName(bundleName, workspace) {
2061
+ const sanitizedBundle = bundleName.replace(/[^A-Za-z0-9_-]/g, "-");
2062
+ const hash = crypto4.createHash("sha256").update(path11.resolve(workspace)).digest("hex").slice(0, 8);
2063
+ const prefix = `au-${sanitizedBundle}`;
2064
+ const maxPrefixLen = 64 - 9;
2065
+ const truncatedPrefix = prefix.slice(0, maxPrefixLen);
2066
+ return `${truncatedPrefix}-${hash}`;
2067
+ }
2068
+ /**
2069
+ * Validate custom team names against safe identifier requirements.
2070
+ */
2071
+ static validateTeamName(name) {
2072
+ return /^[A-Za-z0-9_-]{1,64}$/.test(name);
2073
+ }
2074
+ /**
2075
+ * Resolve an installed bundle for Cline activation.
2076
+ */
2077
+ async resolveInstallation(bundleName, options = {}) {
2078
+ const cwd = options.cwd || process.cwd();
2079
+ const isGlobal = !!options.global;
2080
+ let targetDir;
2081
+ let scope;
2082
+ let workspace;
2083
+ if (isGlobal) {
2084
+ targetDir = AgentHostAdapter.resolveHostDir("global", "agents");
2085
+ scope = "global";
2086
+ workspace = os3.homedir();
2087
+ } else {
2088
+ workspace = cwd;
2089
+ targetDir = path11.join(cwd, ".agents");
2090
+ scope = "project";
2091
+ }
2092
+ const lockfilePath = path11.join(targetDir, "agents-united.json");
2093
+ if (!await fs9.pathExists(lockfilePath)) {
2094
+ if (!isGlobal) {
2095
+ const globalTarget = AgentHostAdapter.resolveHostDir("global", "agents");
2096
+ const globalLockfile = path11.join(globalTarget, "agents-united.json");
2097
+ if (await fs9.pathExists(globalLockfile)) {
2098
+ const gLock = await fs9.readJson(globalLockfile);
2099
+ if (gLock.installed?.bundles?.includes(bundleName)) {
2100
+ return this.resolveInstallation(bundleName, { ...options, global: true });
2101
+ }
2102
+ }
2103
+ }
2104
+ throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
2105
+ }
2106
+ const lockfile = await fs9.readJson(lockfilePath);
2107
+ if (!lockfile.installed?.bundles?.includes(bundleName)) {
2108
+ throw new Error(`Bundle '${bundleName}' is not installed in ${scope} scope.`);
2109
+ }
2110
+ const hasClineFanout = (lockfile.fanout || []).includes("cline");
2111
+ const manifestRel = `.cline/agents-united/teams/${bundleName}.yaml`;
2112
+ const manifestPath = path11.join(workspace, manifestRel);
2113
+ if (!hasClineFanout || !await fs9.pathExists(manifestPath)) {
2114
+ throw new Error(
2115
+ `Bundle '${bundleName}' is not projected to Cline. Run 'agents update ${bundleName} --fanout cline' first.`
2116
+ );
2117
+ }
2118
+ return {
2119
+ scope,
2120
+ workspace,
2121
+ lockfile,
2122
+ manifestPath
2123
+ };
2124
+ }
2125
+ /**
2126
+ * Plan activation strategy, argv, and bootstrap prompt without launching process.
2127
+ */
2128
+ planActivation(options) {
2129
+ const {
2130
+ bundleName,
2131
+ workspace,
2132
+ scope,
2133
+ report,
2134
+ prompt,
2135
+ teamName: customTeamName,
2136
+ allowAddons,
2137
+ headless
2138
+ } = options;
2139
+ const command = report.command || {
2140
+ executable: "cline",
2141
+ prefixArgs: [],
2142
+ source: "path-executable"
2143
+ };
2144
+ let teamName = _ClineLauncher.generateTeamName(bundleName, workspace);
2145
+ if (customTeamName) {
2146
+ if (!_ClineLauncher.validateTeamName(customTeamName)) {
2147
+ throw new Error(
2148
+ `Invalid team name "${customTeamName}". Must match [A-Za-z0-9_-] and be at most 64 characters.`
2149
+ );
2150
+ }
2151
+ teamName = customTeamName;
2152
+ }
2153
+ const strategy = report.namedTeams ? "named-team" : "adaptive-session";
2154
+ const manifestRel = scope === "global" ? `~/.cline/agents-united/teams/${bundleName}.yaml` : `.cline/agents-united/teams/${bundleName}.yaml`;
2155
+ const coordinatorCanonical = `.agents/agents/orchestrator-engineering.md`;
2156
+ const addonPolicyText = allowAddons ? "Addon auto-installation is pre-authorized for this session." : `Before installing any recommended addon, explain the requirement to the user and request explicit confirmation to run: agents add <addon> -t cline ${scope === "global" ? "-g " : ""}-y.`;
2157
+ const taskText = prompt && prompt.trim().length > 0 ? `User task: ${prompt.trim()}` : "Please introduce your coordinator role to the user and ask for their first task.";
2158
+ const bootstrapPrompt = [
2159
+ `You are coordinating the "${bundleName}" team in Agents United.`,
2160
+ `Read the Team Manifest at "${manifestRel}" and your coordinator role definition at "${coordinatorCanonical}" before acting.`,
2161
+ `Use specialist roles only when necessary.`,
2162
+ addonPolicyText,
2163
+ taskText
2164
+ ].join("\n\n");
2165
+ const argv = [...command.prefixArgs];
2166
+ if (strategy === "named-team") {
2167
+ argv.push("--team-name", teamName);
2168
+ }
2169
+ argv.push("--cwd", workspace);
2170
+ if (!headless) {
2171
+ argv.push("-i");
2172
+ }
2173
+ argv.push(bootstrapPrompt);
2174
+ return {
2175
+ bundleName,
2176
+ scope,
2177
+ workspace,
2178
+ teamName,
2179
+ strategy,
2180
+ executable: command.executable,
2181
+ argv,
2182
+ bootstrapPrompt
2183
+ };
2184
+ }
2185
+ /**
2186
+ * Launch Cline session using argument array.
2187
+ */
2188
+ async launch(plan) {
2189
+ const { spawn } = await import("child_process");
2190
+ const child = spawn(plan.executable, plan.argv, {
2191
+ cwd: plan.workspace,
2192
+ stdio: "inherit",
2193
+ shell: false
2194
+ });
2195
+ await new Promise((resolve, reject) => {
2196
+ child.on("error", reject);
2197
+ child.on("exit", (code) => {
2198
+ if (code === 0 || code === null) {
2199
+ resolve();
2200
+ } else {
2201
+ resolve();
2202
+ }
2203
+ });
2204
+ });
2205
+ }
2206
+ };
2207
+
2208
+ // src/core/prerequisites.ts
2209
+ import path12 from "path";
2210
+ import os4 from "os";
2211
+ import fs10 from "fs-extra";
2212
+ var PrerequisiteChecker = class _PrerequisiteChecker {
2213
+ /**
2214
+ * Discovers whether an MCP server is configured across supported host runtimes
2215
+ * (Antigravity/Gemini, Cursor, Cline, Claude Code, etc.)
2216
+ */
2217
+ static async isMcpConfigured(mcpName, cwd = process.cwd()) {
2218
+ const normName = mcpName.trim().toLowerCase();
2219
+ const homeDir = os4.homedir();
2220
+ const localCursor = [
2221
+ path12.join(cwd, ".cursor", "mcp.json"),
2222
+ path12.join(cwd, "cursor.mcp.json")
2223
+ ];
2224
+ for (const cand of localCursor) {
2225
+ if (await fs10.pathExists(cand)) {
2226
+ try {
2227
+ const config = await fs10.readJson(cand);
2228
+ const servers = config?.mcpServers || config?.servers || config;
2229
+ if (servers && (servers[normName] || servers[mcpName])) {
2230
+ return { configured: true, host: "cursor", location: cand };
2231
+ }
2232
+ } catch {
2233
+ }
2234
+ }
2235
+ }
2236
+ const localCline = [
2237
+ path12.join(cwd, "cline_mcp_settings.json"),
2238
+ path12.join(cwd, ".cline", "mcp.json")
2239
+ ];
2240
+ for (const cand of localCline) {
2241
+ if (await fs10.pathExists(cand)) {
2242
+ try {
2243
+ const config = await fs10.readJson(cand);
2244
+ const servers = config?.mcpServers || config?.servers || config;
2245
+ if (servers && (servers[normName] || servers[mcpName])) {
2246
+ return { configured: true, host: "cline", location: cand };
2247
+ }
2248
+ } catch {
2249
+ }
2250
+ }
2251
+ }
2252
+ const localClaude = [
2253
+ path12.join(cwd, "claude.json"),
2254
+ path12.join(cwd, ".claude", "mcp.json")
2255
+ ];
2256
+ for (const cand of localClaude) {
2257
+ if (await fs10.pathExists(cand)) {
2258
+ try {
2259
+ const config = await fs10.readJson(cand);
2260
+ const servers = config?.mcpServers || config?.servers || config;
2261
+ if (servers && (servers[normName] || servers[mcpName])) {
2262
+ return { configured: true, host: "claude", location: cand };
2263
+ }
2264
+ } catch {
2265
+ }
2266
+ }
2267
+ }
2268
+ const localGemini = [
2269
+ path12.join(cwd, ".gemini", "antigravity", "mcp", normName),
2270
+ path12.join(cwd, ".gemini", "antigravity", "mcp")
2271
+ ];
2272
+ for (const cand of localGemini) {
2273
+ if (await fs10.pathExists(cand)) {
2274
+ if (cand.endsWith(normName)) {
2275
+ return { configured: true, host: "gemini", location: cand };
2276
+ }
2277
+ const entries = await fs10.readdir(cand).catch(() => []);
2278
+ if (entries.some((e) => e.toLowerCase() === normName || e.toLowerCase().startsWith(`${normName}.`))) {
2279
+ return { configured: true, host: "gemini", location: cand };
2280
+ }
2281
+ }
2282
+ }
2283
+ const globalGemini = [
2284
+ path12.join(homeDir, ".gemini", "antigravity", "mcp", normName),
2285
+ path12.join(homeDir, ".gemini", "antigravity", "mcp")
2286
+ ];
2287
+ for (const cand of globalGemini) {
2288
+ if (await fs10.pathExists(cand)) {
2289
+ if (cand.endsWith(normName)) {
2290
+ return { configured: true, host: "gemini", location: cand };
2291
+ }
2292
+ const entries = await fs10.readdir(cand).catch(() => []);
2293
+ if (entries.some((e) => e.toLowerCase() === normName || e.toLowerCase().startsWith(`${normName}.`))) {
2294
+ return { configured: true, host: "gemini", location: cand };
2295
+ }
2296
+ }
2297
+ }
2298
+ const globalCursor = [path12.join(homeDir, ".cursor", "mcp.json")];
2299
+ for (const cand of globalCursor) {
2300
+ if (await fs10.pathExists(cand)) {
2301
+ try {
2302
+ const config = await fs10.readJson(cand);
2303
+ const servers = config?.mcpServers || config?.servers || config;
2304
+ if (servers && (servers[normName] || servers[mcpName])) {
2305
+ return { configured: true, host: "cursor", location: cand };
2306
+ }
2307
+ } catch {
2308
+ }
2309
+ }
2310
+ }
2311
+ const globalCline = [path12.join(homeDir, "cline_mcp_settings.json")];
2312
+ for (const cand of globalCline) {
2313
+ if (await fs10.pathExists(cand)) {
2314
+ try {
2315
+ const config = await fs10.readJson(cand);
2316
+ const servers = config?.mcpServers || config?.servers || config;
2317
+ if (servers && (servers[normName] || servers[mcpName])) {
2318
+ return { configured: true, host: "cline", location: cand };
2319
+ }
2320
+ } catch {
2321
+ }
2322
+ }
2323
+ }
2324
+ const globalClaude = [
2325
+ path12.join(homeDir, "claude.json"),
2326
+ path12.join(homeDir, ".claude", "mcp.json")
2327
+ ];
2328
+ for (const cand of globalClaude) {
2329
+ if (await fs10.pathExists(cand)) {
2330
+ try {
2331
+ const config = await fs10.readJson(cand);
2332
+ const servers = config?.mcpServers || config?.servers || config;
2333
+ if (servers && (servers[normName] || servers[mcpName])) {
2334
+ return { configured: true, host: "claude", location: cand };
2335
+ }
2336
+ } catch {
2337
+ }
2338
+ }
2339
+ }
2340
+ return { configured: false };
2341
+ }
2342
+ /**
2343
+ * Checks if an npm package is installed in node_modules or declared in package.json
2344
+ */
2345
+ static async isPackageInstalled(pkgName, cwd = process.cwd()) {
2346
+ const modulePath = path12.join(cwd, "node_modules", ...pkgName.split("/"));
2347
+ if (await fs10.pathExists(modulePath)) {
2348
+ return { installed: true, location: modulePath };
2349
+ }
2350
+ const pkgJsonPath = path12.join(cwd, "package.json");
2351
+ if (await fs10.pathExists(pkgJsonPath)) {
2352
+ try {
2353
+ const pkg = await fs10.readJson(pkgJsonPath);
2354
+ const deps = {
2355
+ ...pkg.dependencies,
2356
+ ...pkg.devDependencies,
2357
+ ...pkg.peerDependencies
2358
+ };
2359
+ if (deps[pkgName]) {
2360
+ return { installed: true, location: pkgJsonPath };
2361
+ }
2362
+ } catch {
2363
+ }
2364
+ }
2365
+ return { installed: false };
2366
+ }
2367
+ /**
2368
+ * Checks if an environment variable is defined in process.env or local .env files
2369
+ */
2370
+ static async isEnvVarSet(varName, cwd = process.cwd(), customEnv) {
2371
+ const envSource = customEnv || process.env;
2372
+ if (envSource[varName] && envSource[varName]?.trim() !== "") {
2373
+ return { set: true, source: "process.env" };
2374
+ }
2375
+ const envFiles = [
2376
+ path12.join(cwd, ".env.local"),
2377
+ path12.join(cwd, ".env")
2378
+ ];
2379
+ for (const envFile of envFiles) {
2380
+ if (await fs10.pathExists(envFile)) {
2381
+ try {
2382
+ const content = await fs10.readFile(envFile, "utf8");
2383
+ const lines = content.split("\n");
2384
+ for (const line of lines) {
2385
+ const trimmed = line.trim();
2386
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
2387
+ const [k, ...v] = trimmed.split("=");
2388
+ if (k.trim() === varName && v.join("=").trim() !== "") {
2389
+ return { set: true, source: path12.basename(envFile) };
2390
+ }
2391
+ }
2392
+ } catch {
2393
+ }
2394
+ }
2395
+ }
2396
+ return { set: false };
2397
+ }
2398
+ /**
2399
+ * Evaluates all prerequisites declared on a bundle
2400
+ */
2401
+ async evaluate(bundle, options = {}) {
2402
+ const cwd = options.cwd || process.cwd();
2403
+ const env = options.env || process.env;
2404
+ const tier = bundle.tier || "domain";
2405
+ const prereqs = bundle.prerequisites || {};
2406
+ const items = [];
2407
+ if (prereqs.requiredMcps && prereqs.requiredMcps.length > 0) {
2408
+ for (const mcp of prereqs.requiredMcps) {
2409
+ const check = await _PrerequisiteChecker.isMcpConfigured(mcp.name, cwd);
2410
+ items.push({
2411
+ type: "mcp",
2412
+ name: mcp.name,
2413
+ purpose: mcp.purpose,
2414
+ satisfied: check.configured,
2415
+ status: check.configured ? "ok" : "missing",
2416
+ details: check.configured ? `Configured in ${check.host || "host"}` : "Not found in host MCP configuration",
2417
+ optionalForBrainstorming: mcp.optionalForBrainstorming ?? true
2418
+ });
2419
+ }
2420
+ }
2421
+ if (prereqs.requiredPackages && prereqs.requiredPackages.length > 0) {
2422
+ for (const pkg of prereqs.requiredPackages) {
2423
+ const check = await _PrerequisiteChecker.isPackageInstalled(pkg, cwd);
2424
+ items.push({
2425
+ type: "package",
2426
+ name: pkg,
2427
+ satisfied: check.installed,
2428
+ status: check.installed ? "ok" : "missing",
2429
+ details: check.installed ? "Installed in workspace" : "Not found in node_modules or package.json",
2430
+ optionalForBrainstorming: false
2431
+ });
2432
+ }
2433
+ }
2434
+ if (prereqs.requiredEnvVars && prereqs.requiredEnvVars.length > 0) {
2435
+ for (const envVar of prereqs.requiredEnvVars) {
2436
+ const check = await _PrerequisiteChecker.isEnvVarSet(envVar, cwd, env);
2437
+ items.push({
2438
+ type: "env",
2439
+ name: envVar,
2440
+ satisfied: check.set,
2441
+ status: check.set ? "ok" : "missing",
2442
+ details: check.set ? `Defined in ${check.source}` : "Environment variable not set",
2443
+ optionalForBrainstorming: true
2444
+ });
2445
+ }
2446
+ }
2447
+ const hasPrerequisites = items.length > 0;
2448
+ const allSatisfied = items.length === 0 || items.every((i) => i.satisfied);
2449
+ const operationalPossible = allSatisfied;
2450
+ return {
2451
+ bundleName: bundle.name,
2452
+ tier,
2453
+ hasPrerequisites,
2454
+ allSatisfied,
2455
+ operationalPossible,
2456
+ items,
2457
+ modes: bundle.modes
2458
+ };
2459
+ }
2460
+ };
2461
+
2462
+ // src/cli.ts
2463
+ var cli = cac("agents-united");
2464
+ var registry = new RegistryResolver();
426
2465
  var installer = new InstallEngine(registry);
427
2466
  var uninstaller = new UninstallEngine(registry);
428
- cli.command("add <identifier>", "Add a bundle, agent, skill, or workflow to workspace or global configuration").option("-g, --global", "Install globally to ~/.gemini/config/").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force overwrite user modified files").option("--dry-run", "Simulate installation without writing files").action(async (identifier, options) => {
429
- intro(pc.cyan("Agents United - Add Package"));
2467
+ var scanner = new InventoryScanner(registry);
2468
+ var updater = new UpdateEngine(registry, scanner, installer);
2469
+ function detectWorkspaceHosts(cwd = process.cwd()) {
2470
+ const detected = [];
2471
+ for (const host of Object.values(HOST_REGISTRY)) {
2472
+ const found = host.detectionMarkers.some((marker) => fs11.pathExistsSync(path13.join(cwd, marker)));
2473
+ if (found) detected.push(host.id);
2474
+ }
2475
+ return detected;
2476
+ }
2477
+ function renderProjections(projections) {
2478
+ const byHost = /* @__PURE__ */ new Map();
2479
+ for (const p of projections) {
2480
+ const list = byHost.get(p.host) ?? [];
2481
+ list.push(p);
2482
+ byHost.set(p.host, list);
2483
+ }
2484
+ const lines = [];
2485
+ for (const [host, items] of byHost) {
2486
+ lines.push(`${HOST_REGISTRY[host]?.label ?? host} \u2014 ${items.length} file${items.length > 1 ? "s" : ""}`);
2487
+ for (const p of items) {
2488
+ lines.push(` \u2192 ${p.path}`);
2489
+ for (const w of p.warnings) lines.push(pc.yellow(` \u26A0 ${w}`));
2490
+ }
2491
+ }
2492
+ return lines.join("\n");
2493
+ }
2494
+ var BUNDLE_DISPLAY_NAMES = {
2495
+ "software-engineering": {
2496
+ title: "Software Engineering Team",
2497
+ summary: "Autonomous dev lead, backend/frontend architects, TDD & git guardrails"
2498
+ },
2499
+ "mobile-development": {
2500
+ title: "Mobile Development Team",
2501
+ summary: "iOS (SwiftUI), Android (Compose) & Cross-Platform (React Native/Flutter)"
2502
+ },
2503
+ "frontend-engineering": {
2504
+ title: "Frontend Architecture Team",
2505
+ summary: "Next.js/React App Router, Server Components & Web Vitals"
2506
+ },
2507
+ "backend-distributed-systems": {
2508
+ title: "Backend & Distributed Systems Team",
2509
+ summary: "Microservices, Kafka/RabbitMQ event streams & DB migrations"
2510
+ },
2511
+ "qa-automation": {
2512
+ title: "QA & Test Automation Team",
2513
+ summary: "Playwright E2E automation, test matrix planning & chaos engineering"
2514
+ },
2515
+ "devops-engineering": {
2516
+ title: "DevOps & Delivery Pipeline Team",
2517
+ summary: "CI/CD automation, Docker/K8s, Infrastructure as Code & release engineering"
2518
+ },
2519
+ "ai-ml-engineering": {
2520
+ title: "AI & Machine Learning Engineering Team",
2521
+ summary: "Serverless GPU deployment, LLM fine-tuning, RAG vector pipelines & AI safety guardrails"
2522
+ },
2523
+ "sysops-sre": {
2524
+ title: "SysOps & Site Reliability Team",
2525
+ summary: "99.999% uptime, Prometheus telemetry, incident triage & disaster recovery"
2526
+ },
2527
+ "system-architecture": {
2528
+ title: "System Architecture Team",
2529
+ summary: "High-level distributed systems, API schemas, and ADR planning"
2530
+ },
2531
+ "product-design": {
2532
+ title: "Product Design Team",
2533
+ summary: "UI/UX designers, design systems architect, and prototyping"
2534
+ },
2535
+ "growth-marketing": {
2536
+ title: "Growth & Marketing Team",
2537
+ summary: "Growth strategists, creative visual designer, content pipeline & conversion optimization"
2538
+ },
2539
+ "seo-content-marketing": {
2540
+ title: "SEO & Content Marketing Team",
2541
+ summary: "Programmatic SEO, technical SEO audits, content pipeline automation & schema markup"
2542
+ },
2543
+ "performance-paid-acquisition": {
2544
+ title: "Performance & Paid Acquisition Team",
2545
+ summary: "Multi-channel PPC (Google/Meta/LinkedIn), ROAS/CAC attribution & ad copy testing"
2546
+ },
2547
+ "product-led-growth": {
2548
+ title: "Product-Led Growth Team",
2549
+ summary: "Onboarding CRO, signup funnel optimization, viral referral loops & paywalls"
2550
+ },
2551
+ "lifecycle-email-marketing": {
2552
+ title: "Lifecycle & Email Marketing Team",
2553
+ summary: "Automated email drip sequences, churn prevention playbooks & newsletter workflows"
2554
+ },
2555
+ "security-operations": {
2556
+ title: "Security Operations Team",
2557
+ summary: "AppSec engineer, threat modeling, and vulnerability audits"
2558
+ },
2559
+ "deep-research": {
2560
+ title: "Deep Research Team",
2561
+ summary: "Technical research lead, literature review, and Socratic mentor"
2562
+ },
2563
+ "business-strategy": {
2564
+ title: "Business Strategy Team",
2565
+ summary: "Market analysts, monetization experts, and executive spec panels"
2566
+ },
2567
+ "digital-agency": {
2568
+ title: "Digital Agency (TBA soon)",
2569
+ summary: "Full-service digital product agency with web dev, mobile, design, SEO, and growth marketing"
2570
+ },
2571
+ "universal-skills": {
2572
+ title: "Universal Meta-Skills",
2573
+ summary: "Domain-agnostic Socratic grilling, spec generation, handoff, and domain modeling"
2574
+ },
2575
+ "design-systems-ops": {
2576
+ title: "Design Systems & Ops Team",
2577
+ summary: "Token governance, component libraries, design handoff & version control workflows"
2578
+ },
2579
+ "design-research-testing": {
2580
+ title: "Design Research & Testing Team",
2581
+ summary: "Usability testing, user journey mapping, interactive prototypes & AI prototype refactoring"
2582
+ },
2583
+ "full": {
2584
+ title: "All-in-One Autonomous Department",
2585
+ summary: "Complete suite with all 7 team leads, 38 agents, and all skills & workflows"
2586
+ }
2587
+ };
2588
+ cli.command("add [identifier]", "Add a bundle, agent, skill, or workflow to project or global configuration").option("-g, --global", "Install globally into home directory (~/.agents/)").option("-s, --symlink", "Create symbolic links to central registry cache (default / recommended)").option("--copy", "Create independent standalone copies of asset files").option("-t, --target <hosts>", "Which assistants to set up (agents = main library; claude, cursor, cline, opencode, codex get translated copies)", { default: "agents" }).option("--fanout <hosts>", "Also make translated copies for these assistants: claude, cursor, cline, opencode, codex").option("--mode <mode>", "Execution mode for organization bundles (operational | brainstorming)", { default: "operational" }).option("--allow-missing-prereqs", "Proceed with installation even if some prerequisites are missing").option("--allow-under-construction", "Allow installation of bundles marked as under construction").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force overwrite user modified files").option("--start", "Start the installed team in Cline immediately after setup").option("--dry-run", "Simulate installation without writing files").action(async (targetIdentifier, options = {}) => {
2589
+ intro(pc.cyan("Agents United \u2014 AI Agent Ecosystem"));
2590
+ let identifier = targetIdentifier;
2591
+ let scope = options.global ? "global" : "project";
2592
+ let method = options.copy ? "copy" : "symlink";
2593
+ let hosts = options.target ? (Array.isArray(options.target) ? options.target : options.target.split(",")).map(
2594
+ (h) => h.trim().toLowerCase()
2595
+ ) : ["agents"];
2596
+ const unknownHosts = hosts.filter((h) => !isKnownHost(h));
2597
+ if (unknownHosts.length > 0) {
2598
+ note(`Ignoring unknown host id(s): ${unknownHosts.join(", ")}`, "Invalid target");
2599
+ hosts = hosts.filter(isKnownHost);
2600
+ if (hosts.length === 0) hosts = ["agents"];
2601
+ }
2602
+ const flagPlan = planInstallTargets(hosts);
2603
+ hosts = flagPlan.hosts;
2604
+ if (flagPlan.addedCanonicalStore) {
2605
+ note(
2606
+ "Added .agents/ \u2014 the main library your assistants share.\nEach assistant you picked gets its own translated copy. Edit only .agents/.",
2607
+ "Main library"
2608
+ );
2609
+ }
2610
+ let fanout = flagPlan.fanout;
2611
+ if (options.fanout) {
2612
+ const rawFanout = Array.isArray(options.fanout) ? options.fanout : String(options.fanout).split(",");
2613
+ const parsedFanout = rawFanout.map((h) => h.trim().toLowerCase());
2614
+ const invalidFanout = parsedFanout.filter((h) => !isKnownHost(h) || !HOST_REGISTRY[h].projectionCapable);
2615
+ if (invalidFanout.length > 0) {
2616
+ note(
2617
+ `Ignoring invalid fanout host id(s): ${invalidFanout.join(", ")}. Valid ids: ${KNOWN_HOST_IDS.filter((h) => HOST_REGISTRY[h].projectionCapable).join(", ")}`,
2618
+ "Invalid fanout"
2619
+ );
2620
+ }
2621
+ fanout = Array.from(/* @__PURE__ */ new Set([...fanout, ...parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].projectionCapable)]));
2622
+ }
2623
+ const isInteractive = process.stdout.isTTY && !options.yes;
2624
+ if (isInteractive && !options.global && !options.copy && !options.symlink && options.target === "agents") {
2625
+ const detectedHosts = detectWorkspaceHosts();
2626
+ if (detectedHosts.length > 0) {
2627
+ note(
2628
+ detectedHosts.map((h) => ` \u2714 Detected .${HOST_REGISTRY[h].projectDir === "." ? "" : "/"}${HOST_REGISTRY[h].projectDir === "." ? "AGENTS.md" : HOST_REGISTRY[h].projectDir}`).join("\n"),
2629
+ "Workspace Environment Discovery"
2630
+ );
2631
+ }
2632
+ const hostSelection = await multiselect({
2633
+ message: "1. Which AI tools should we configure agent teams & personas for?",
2634
+ options: [
2635
+ {
2636
+ value: "agents",
2637
+ label: HOST_REGISTRY.agents.label,
2638
+ hint: detectedHosts.includes("agents") ? "found in this project" : "the master library where all skills & agents live \u2014 always included"
2639
+ },
2640
+ {
2641
+ value: "claude",
2642
+ label: HOST_REGISTRY.claude.label,
2643
+ hint: detectedHosts.includes("claude") ? "found in this project" : "orchestrator & subagent personas for Claude Code"
2644
+ },
2645
+ {
2646
+ value: "cursor",
2647
+ label: HOST_REGISTRY.cursor.label,
2648
+ hint: detectedHosts.includes("cursor") ? "found in this project" : "orchestrator & subagent personas for Cursor IDE"
2649
+ },
2650
+ {
2651
+ value: "cline",
2652
+ label: HOST_REGISTRY.cline.label,
2653
+ hint: detectedHosts.includes("cline") ? "found in this project" : "roles, skills, coordinator rules & team manifests for Cline"
2654
+ },
2655
+ {
2656
+ value: "opencode",
2657
+ label: HOST_REGISTRY.opencode.label,
2658
+ hint: detectedHosts.includes("opencode") ? "found in this project" : "orchestrator & subagent personas for OpenCode"
2659
+ },
2660
+ {
2661
+ value: "codex",
2662
+ label: HOST_REGISTRY.codex.label,
2663
+ hint: detectedHosts.includes("codex") ? "found in this project" : "root index linking orchestrators, subagents & skills for Codex, Copilot, Aider & Zed"
2664
+ },
2665
+ {
2666
+ value: "gemini",
2667
+ label: HOST_REGISTRY.gemini.label,
2668
+ hint: detectedHosts.includes("gemini") ? "found in this project" : "older Antigravity 1.0 / Gemini folder"
2669
+ }
2670
+ ],
2671
+ initialValues: detectedHosts.length > 0 ? detectedHosts : ["agents"],
2672
+ required: true
2673
+ });
2674
+ if (Array.isArray(hostSelection) && hostSelection.length > 0) {
2675
+ const wizardPlan = planInstallTargets(hostSelection);
2676
+ hosts = wizardPlan.hosts;
2677
+ fanout = Array.from(/* @__PURE__ */ new Set([...fanout, ...wizardPlan.fanout]));
2678
+ if (wizardPlan.addedCanonicalStore) {
2679
+ note(
2680
+ "Added .agents/ \u2014 the master repository library your tools share.\nEach tool you selected gets its own orchestrators and subagents. Edit only .agents/.",
2681
+ "Master library"
2682
+ );
2683
+ }
2684
+ }
2685
+ const scopeSelection = await select({
2686
+ message: "2. Select Installation Scope:",
2687
+ options: [
2688
+ {
2689
+ value: "project",
2690
+ label: "Project Scope (Recommended)",
2691
+ hint: "Workspace directory; tracked in Git & shared with team via lockfile"
2692
+ },
2693
+ {
2694
+ value: "global",
2695
+ label: "Global Scope (-g / --global)",
2696
+ hint: "User home directory (~/.agents/); available across all workspaces on machine"
2697
+ }
2698
+ ]
2699
+ });
2700
+ if (typeof scopeSelection === "string") {
2701
+ scope = scopeSelection;
2702
+ }
2703
+ const methodSelection = await select({
2704
+ message: "3. Select Installation Method:",
2705
+ options: [
2706
+ {
2707
+ value: "symlink",
2708
+ label: "Symlink Mode (Recommended)",
2709
+ hint: "Single source of truth; package updates reflect instantly"
2710
+ },
2711
+ {
2712
+ value: "copy",
2713
+ label: "Copy Mode",
2714
+ hint: "Independent standalone files; supports offline isolated modifications"
2715
+ }
2716
+ ]
2717
+ });
2718
+ if (typeof methodSelection === "string") {
2719
+ method = methodSelection;
2720
+ }
2721
+ }
2722
+ if (!identifier) {
2723
+ const bundles = await registry.listBundles();
2724
+ const domainMeta = {
2725
+ universal: { label: "Universal Autonomous Department", icon: "\u{1F310} " },
2726
+ engineering: { label: "Software Engineering & Delivery", icon: "\u{1F6E0}\uFE0F " },
2727
+ architecture: { label: "System Architecture & SRE", icon: "\u{1F3DB}\uFE0F " },
2728
+ design: { label: "Product Design & UI/UX", icon: "\u{1F3A8} " },
2729
+ marketing: { label: "Growth & Marketing Operations", icon: "\u{1F4C8} " },
2730
+ security: { label: "Security Operations", icon: "\u{1F512} " },
2731
+ research: { label: "Deep Technical Research", icon: "\u{1F52C} " },
2732
+ business: { label: "Business Strategy & Economics", icon: "\u{1F4BC} " },
2733
+ organization: { label: "Organization Bundles (Experimental / Cross-Functional)", icon: "\u{1F3E2} " }
2734
+ };
2735
+ let selectedBundle;
2736
+ while (!selectedBundle) {
2737
+ const domainOptions = Object.entries(domainMeta).map(([domainKey, meta]) => {
2738
+ const count = bundles.filter((b) => b.domain === domainKey || domainKey === "organization" && b.tier === "organization").length;
2739
+ return {
2740
+ value: domainKey,
2741
+ label: `${meta.icon} ${meta.label}`,
2742
+ hint: domainKey === "universal" ? "meta-skills baseline + full suite" : domainKey === "organization" ? "cross-functional teams with prerequisites" : `${count} specialized team${count > 1 ? "s" : ""}`
2743
+ };
2744
+ });
2745
+ domainOptions.push({
2746
+ value: "__search__",
2747
+ label: "\u{1F50D} Search by name / keyword...",
2748
+ hint: "custom keyword search"
2749
+ });
2750
+ const selectedDomain = await select({
2751
+ message: "4. Select Department Domain / Category:",
2752
+ options: domainOptions
2753
+ });
2754
+ if (typeof selectedDomain !== "string") {
2755
+ outro(pc.yellow("Installation cancelled."));
2756
+ return;
2757
+ }
2758
+ if (selectedDomain === "__search__") {
2759
+ const searchQuery = await text({
2760
+ message: "Enter keyword to search:",
2761
+ placeholder: "e.g. mobile, playwright, react, backend, agency"
2762
+ });
2763
+ if (typeof searchQuery !== "string" || !searchQuery.trim()) {
2764
+ continue;
2765
+ }
2766
+ const searchResults = await registry.find(searchQuery.trim());
2767
+ const matchOptions = [
2768
+ ...searchResults.bundles.map((b) => ({
2769
+ value: b.name,
2770
+ label: `[Bundle] ${BUNDLE_DISPLAY_NAMES[b.name]?.title || b.name} (${b.name})`,
2771
+ hint: b.description
2772
+ })),
2773
+ ...searchResults.agents.map((a) => ({
2774
+ value: a.replace(/\.md$/, ""),
2775
+ label: `[Agent] ${a}`,
2776
+ hint: "autonomous agent"
2777
+ })),
2778
+ ...searchResults.skills.map((s2) => ({
2779
+ value: s2,
2780
+ label: `[Skill] ${s2}`,
2781
+ hint: "specialized skill"
2782
+ })),
2783
+ ...searchResults.workflows.map((w) => ({
2784
+ value: w.replace(/\.md$/, ""),
2785
+ label: `[Workflow] ${w}`,
2786
+ hint: "guided workflow"
2787
+ })),
2788
+ {
2789
+ value: "__back__",
2790
+ label: "\u{1F519} Back to Department Selection",
2791
+ hint: ""
2792
+ }
2793
+ ];
2794
+ if (matchOptions.length === 1) {
2795
+ note(`No items found matching "${searchQuery}".`, "Search Results");
2796
+ continue;
2797
+ }
2798
+ const chosenMatch = await select({
2799
+ message: `Search Results for "${searchQuery}":`,
2800
+ options: matchOptions
2801
+ });
2802
+ if (typeof chosenMatch === "string" && chosenMatch !== "__back__") {
2803
+ selectedBundle = chosenMatch;
2804
+ break;
2805
+ }
2806
+ continue;
2807
+ }
2808
+ const domainBundles = bundles.filter((b) => b.domain === selectedDomain || selectedDomain === "organization" && b.tier === "organization");
2809
+ const subTeamOptions = [];
2810
+ if (domainBundles.length > 1 && selectedDomain !== "organization" && selectedDomain !== "universal") {
2811
+ subTeamOptions.push({
2812
+ value: `__all_domain__:${selectedDomain}`,
2813
+ label: `\u{1F31F} Install Entire ${domainMeta[selectedDomain]?.label || selectedDomain} (${domainBundles.length} Bundles)`,
2814
+ hint: "installs all sub-teams under this department"
2815
+ });
2816
+ }
2817
+ const sortedBundles = [...domainBundles].sort((a, b) => {
2818
+ if (a.name === "universal-skills") return -1;
2819
+ if (b.name === "universal-skills") return 1;
2820
+ if (!a.parentBundle && b.parentBundle) return -1;
2821
+ if (a.parentBundle && !b.parentBundle) return 1;
2822
+ return a.name.localeCompare(b.name);
2823
+ });
2824
+ sortedBundles.forEach((b, idx) => {
2825
+ const isLast = idx === sortedBundles.length - 1;
2826
+ const branch = sortedBundles.length > 1 ? isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : "";
2827
+ const meta = BUNDLE_DISPLAY_NAMES[b.name];
2828
+ const isEssentials = !b.parentBundle && b.name !== "full" && b.tier !== "organization" && b.name !== "universal-skills";
2829
+ const title = meta ? meta.title : b.name;
2830
+ let statusBadge = "";
2831
+ if (b.name === "universal-skills") statusBadge = pc.green(" \u2B50 [Recommended Baseline]");
2832
+ else if (b.status === "under-construction") statusBadge = pc.yellow(" \u{1F6A7} [Under Construction]");
2833
+ else if (b.status === "needs-audit") statusBadge = pc.magenta(" \u26A0\uFE0F [Needs Audit]");
2834
+ else if (b.status === "experimental") statusBadge = pc.cyan(" [Experimental]");
2835
+ else if (b.status === "deprecated") statusBadge = pc.red(" [Deprecated]");
2836
+ const labelText = isEssentials ? `Essentials: ${title}${statusBadge}` : `${title}${statusBadge}`;
2837
+ const summary = meta ? meta.summary : b.description;
2838
+ subTeamOptions.push({
2839
+ value: b.name,
2840
+ label: `${branch}\u{1F4E6} ${labelText} (${b.name})`,
2841
+ hint: summary
2842
+ });
2843
+ });
2844
+ subTeamOptions.push({
2845
+ value: "__back__",
2846
+ label: "\u{1F519} Back to Department Selection",
2847
+ hint: ""
2848
+ });
2849
+ const chosenSubTeam = await select({
2850
+ message: `Select Package / Team Bundle to install in ${domainMeta[selectedDomain]?.label || selectedDomain}:`,
2851
+ options: subTeamOptions
2852
+ });
2853
+ if (typeof chosenSubTeam !== "string") {
2854
+ outro(pc.yellow("Installation cancelled."));
2855
+ return;
2856
+ }
2857
+ if (chosenSubTeam === "__back__") {
2858
+ continue;
2859
+ }
2860
+ if (chosenSubTeam.startsWith("__all_domain__:")) {
2861
+ const dName = chosenSubTeam.replace("__all_domain__:", "");
2862
+ const dTitle = domainMeta[dName]?.label || dName;
2863
+ const proceed = await confirm({
2864
+ message: `\u26A0\uFE0F You are about to install ALL ${domainBundles.length} sub-teams under "${dTitle}". Proceed?`,
2865
+ initialValue: true
2866
+ });
2867
+ if (!proceed || typeof proceed !== "boolean") {
2868
+ continue;
2869
+ }
2870
+ selectedBundle = `domain:${dName}`;
2871
+ break;
2872
+ }
2873
+ selectedBundle = chosenSubTeam;
2874
+ break;
2875
+ }
2876
+ identifier = selectedBundle;
2877
+ }
2878
+ if (!identifier) {
2879
+ outro(pc.yellow("No package specified."));
2880
+ return;
2881
+ }
2882
+ const targetBundleDef = await registry.getBundle(identifier);
2883
+ if (targetBundleDef && targetBundleDef.status === "under-construction") {
2884
+ if (isInteractive) {
2885
+ note(
2886
+ `The bundle "${targetBundleDef.name}" is currently under active construction (status: under-construction).
2887
+ Its orchestrators, workflows, and specialized skills are being authored and are not ready for production use.
2888
+
2889
+ Planned Capabilities:
2890
+ \u2022 Lead Orchestrator with Firecrawl & GitHub MCP tool calling
2891
+ \u2022 Full-stack web, design & SEO delivery workflows
2892
+ \u2022 Dual Execution Modes (Operational / Brainstorming)`,
2893
+ "\u{1F6A7} Under Construction Gate"
2894
+ );
2895
+ const underConstructionChoice = await select({
2896
+ message: pc.yellow(`"${targetBundleDef.name}" is currently under construction. How would you like to proceed?`),
2897
+ options: [
2898
+ {
2899
+ value: "abort",
2900
+ label: "\u{1F6D1} Abort installation (Recommended)",
2901
+ hint: "return without modifying workspace files"
2902
+ },
2903
+ {
2904
+ value: "allow",
2905
+ label: "\u26A0\uFE0F Install in-development draft anyway (--allow-under-construction)",
2906
+ hint: "proceed with in-progress placeholder assets"
2907
+ }
2908
+ ]
2909
+ });
2910
+ if (underConstructionChoice === "abort" || typeof underConstructionChoice !== "string") {
2911
+ outro(pc.yellow(`Installation cancelled. "${targetBundleDef.name}" is under construction.`));
2912
+ return;
2913
+ }
2914
+ } else {
2915
+ if (!options.allowUnderConstruction && !options.force) {
2916
+ outro(
2917
+ pc.red(
2918
+ `Error: Cannot install "${targetBundleDef.name}" \u2014 this bundle is currently under construction.
2919
+ To bypass this gate and install draft assets anyway, pass: --allow-under-construction or --force`
2920
+ )
2921
+ );
2922
+ process.exit(1);
2923
+ }
2924
+ }
2925
+ }
2926
+ let executionMode = options.mode || "operational";
2927
+ if (targetBundleDef && (targetBundleDef.prerequisites || targetBundleDef.tier === "organization")) {
2928
+ const checker = new PrerequisiteChecker();
2929
+ const prereqEval = await checker.evaluate(targetBundleDef);
2930
+ if (prereqEval.hasPrerequisites) {
2931
+ const checkLines = [];
2932
+ for (const item of prereqEval.items) {
2933
+ const typeLabel = item.type === "mcp" ? "MCP" : item.type === "env" ? "Env" : "Pkg";
2934
+ const icon = item.satisfied ? pc.green("\u2713") : pc.red("\u2717");
2935
+ const details = pc.dim(`(${item.details || ""})`);
2936
+ checkLines.push(` ${icon} [${typeLabel}] ${pc.bold(item.name)}: ${item.satisfied ? pc.green("Detected") : pc.red("Missing")} ${details}`);
2937
+ }
2938
+ note(
2939
+ checkLines.join("\n"),
2940
+ `Prerequisite Evaluation: ${targetBundleDef.name} (${targetBundleDef.tier === "organization" ? "Organization Bundle" : "Prerequisites Required"})`
2941
+ );
2942
+ if (!prereqEval.allSatisfied) {
2943
+ if (isInteractive) {
2944
+ const gateChoice = await select({
2945
+ message: pc.yellow(`Prerequisites not fully satisfied for "${targetBundleDef.name}". How would you like to proceed?`),
2946
+ options: [
2947
+ {
2948
+ value: "abort",
2949
+ label: "\u{1F6D1} Abort and configure missing prerequisites (Recommended)",
2950
+ hint: "exit and configure MCP servers, packages, or environment variables"
2951
+ },
2952
+ {
2953
+ value: "brainstorming",
2954
+ label: "\u{1F4A1} Install in Brainstorming Mode (Advisory & Spec generation only)",
2955
+ hint: "runs in idea/planning mode without requiring live MCP tool connections"
2956
+ },
2957
+ {
2958
+ value: "force",
2959
+ label: "\u26A0\uFE0F Force install in Operational Mode anyway",
2960
+ hint: "proceed despite missing prerequisites (may fail during runtime calls)"
2961
+ }
2962
+ ]
2963
+ });
2964
+ if (gateChoice === "abort" || typeof gateChoice !== "string") {
2965
+ outro(pc.yellow("Installation aborted. Please configure required MCPs and environment variables, then retry."));
2966
+ return;
2967
+ }
2968
+ if (gateChoice === "brainstorming") {
2969
+ executionMode = "brainstorming";
2970
+ note(pc.cyan('Switched execution mode to "brainstorming" (idea/spec fallback mode).'), "Mode Selected");
2971
+ } else if (gateChoice === "force") {
2972
+ executionMode = "operational";
2973
+ note(pc.yellow('Proceeding in "operational" mode with missing prerequisites.'), "Warning");
2974
+ }
2975
+ } else {
2976
+ if (options.mode === "brainstorming") {
2977
+ executionMode = "brainstorming";
2978
+ } else if (options.force || options.allowMissingPrereqs) {
2979
+ executionMode = "operational";
2980
+ } else {
2981
+ const missingList = prereqEval.items.filter((i) => !i.satisfied).map((i) => `${i.type}:${i.name}`).join(", ");
2982
+ outro(
2983
+ pc.red(
2984
+ `Error: Prerequisites not satisfied for "${targetBundleDef.name}".
2985
+ Missing items: ${missingList}
2986
+
2987
+ To install in fallback mode: agents add ${identifier} --mode brainstorming
2988
+ To bypass prerequisite gate: agents add ${identifier} --allow-missing-prereqs`
2989
+ )
2990
+ );
2991
+ process.exit(1);
2992
+ }
2993
+ }
2994
+ }
2995
+ }
2996
+ }
430
2997
  const s = spinner();
431
2998
  s.start(`Resolving "${identifier}"...`);
432
2999
  try {
433
3000
  const result = await installer.install(identifier, {
434
- global: options.global,
3001
+ scope,
3002
+ method,
3003
+ hosts,
3004
+ fanout,
3005
+ mode: executionMode,
3006
+ allowMissingPrereqs: options.allowMissingPrereqs,
435
3007
  yes: options.yes,
436
3008
  force: options.force,
437
3009
  dryRun: options.dryRun
438
3010
  });
439
3011
  s.stop(`Resolved assets for "${identifier}"`);
440
3012
  if (options.dryRun) {
441
- outro(pc.yellow(`[DRY RUN] Would install ${result.installed.agents.length} agents, ${result.installed.skills.length} skills to ${result.targetDir}`));
3013
+ outro(pc.yellow(`[DRY RUN] Would install ${result.installed.agents.length} agents, ${result.installed.skills.length} skills to ${result.targetDirs.join(", ")}`));
3014
+ if (result.projections.length > 0) {
3015
+ note(renderProjections(result.projections), "Projection Plan (dry run)");
3016
+ }
3017
+ if (options.start && result.projections.some((p) => p.host === "cline")) {
3018
+ const bundleName = result.installed.targetBundle || identifier;
3019
+ note(`Would start Cline team for bundle "${bundleName}".`, "Start in Cline (dry run)");
3020
+ }
442
3021
  return;
443
3022
  }
444
3023
  note(
445
3024
  `Bundle: ${result.installed.targetBundle || "Single Item"}
3025
+ Scope: ${scope}
3026
+ Method: ${result.method}
3027
+ Targets: ${hosts.join(", ")}
446
3028
  Agents: ${result.installed.agents.join(", ") || "None"}
447
3029
  Skills: ${result.installed.skills.join(", ") || "None"}
448
- Target Directory: ${result.targetDir}`,
3030
+ Target Directories: ${result.targetDirs.join("\n ")}` + (result.projections.length > 0 ? `
3031
+
3032
+ Projections:
3033
+ ${renderProjections(result.projections)}` : ""),
449
3034
  "Installation Success"
450
3035
  );
3036
+ const hasClineProjection = result.projections.some((p) => p.host === "cline");
3037
+ if (options.start && !hasClineProjection) {
3038
+ throw new Error("--start requires Cline projection. Add -t cline or --fanout cline.");
3039
+ }
3040
+ if (hasClineProjection) {
3041
+ const bundleName = result.installed.targetBundle || identifier;
3042
+ if (options.start) {
3043
+ if (options.dryRun) {
3044
+ note(`Would start Cline team for bundle "${bundleName}".`, "Start in Cline (dry run)");
3045
+ } else {
3046
+ note(`Launching Cline team for "${bundleName}"...`, "Starting Cline");
3047
+ const launcher = new ClineLauncher();
3048
+ const probe = new ClineCapabilityProbe();
3049
+ const probeReport = await probe.probe();
3050
+ if (probeReport.installed) {
3051
+ const plan = launcher.planActivation({
3052
+ bundleName,
3053
+ workspace: process.cwd(),
3054
+ scope,
3055
+ report: probeReport
3056
+ });
3057
+ await launcher.launch(plan);
3058
+ } else {
3059
+ note(pc.yellow("Cline executable not detected. Install Cline CLI to run: agents start " + bundleName), "Cline Not Found");
3060
+ }
3061
+ }
3062
+ } else if (isInteractive && !options.dryRun) {
3063
+ const startChoice = await select({
3064
+ message: `Start the ${bundleName} team in Cline now?`,
3065
+ options: [
3066
+ { value: "start", label: "\u{1F680} Start in Cline" },
3067
+ { value: "copy", label: `\u{1F4CB} Copy command (agents start ${bundleName})` },
3068
+ { value: "later", label: "\u23F3 Later" }
3069
+ ]
3070
+ });
3071
+ if (startChoice === "start") {
3072
+ const launcher = new ClineLauncher();
3073
+ const probe = new ClineCapabilityProbe();
3074
+ const probeReport = await probe.probe();
3075
+ if (probeReport.installed) {
3076
+ const plan = launcher.planActivation({
3077
+ bundleName,
3078
+ workspace: process.cwd(),
3079
+ scope,
3080
+ report: probeReport
3081
+ });
3082
+ await launcher.launch(plan);
3083
+ } else {
3084
+ note(pc.yellow("Cline executable not detected. Install Cline CLI to run: agents start " + bundleName), "Cline Not Found");
3085
+ }
3086
+ } else if (startChoice === "copy") {
3087
+ note(`Run 'agents start ${bundleName}' to launch the team in Cline anytime.`, "Start Command");
3088
+ }
3089
+ } else if (!options.dryRun) {
3090
+ note(pc.cyan(`Tip: Run 'agents start ${bundleName}' to launch the team in Cline.`), "Start in Cline");
3091
+ }
3092
+ }
3093
+ if (!options.dryRun && hosts.includes("agents") && result.projections.length === 0) {
3094
+ note(
3095
+ pc.yellow(
3096
+ `Tip: only Antigravity reads the main library (.agents/) directly.
3097
+ To use this bundle in Cline, Claude Code & others: agents update ${identifier} --fanout cline,claude`
3098
+ ),
3099
+ "One library, every assistant"
3100
+ );
3101
+ }
451
3102
  outro(pc.green(`\u2714 Installed "${identifier}" successfully!`));
452
3103
  } catch (err) {
453
3104
  s.stop(pc.red("Failed resolution"));
@@ -455,20 +3106,67 @@ Target Directory: ${result.targetDir}`,
455
3106
  process.exit(1);
456
3107
  }
457
3108
  });
458
- cli.command("remove <identifier>", "Remove a bundle, agent, skill, or workflow").alias("uninstall").option("-g, --global", "Uninstall from global ~/.gemini/config/").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force removal of modified files").option("--dry-run", "Simulate removal without unlinking files").action(async (identifier, options) => {
459
- intro(pc.cyan("Agents United - Remove Package"));
3109
+ cli.command("remove [identifier]", "Remove an installed bundle, agent, skill, or workflow").alias("uninstall").option("-g, --global", "Uninstall from global home directory").option("-t, --target <hosts>", "Target agent host runtimes", { default: "agents" }).option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force removal of modified files").option("--dry-run", "Simulate removal without unlinking files").action(async (targetIdentifier, options = {}) => {
3110
+ intro(pc.cyan("Agents United \u2014 Remove Package"));
3111
+ let identifier = targetIdentifier;
3112
+ let selectedRecord;
3113
+ const isInteractive = process.stdout.isTTY && !options.yes;
3114
+ if (!identifier) {
3115
+ const inventory = await scanner.scan({
3116
+ global: options.global,
3117
+ target: options.target
3118
+ });
3119
+ if (inventory.records.length === 0) {
3120
+ outro(pc.yellow("No installed packages found in project or global configuration."));
3121
+ return;
3122
+ }
3123
+ if (!isInteractive) {
3124
+ outro(pc.red("Package identifier required in non-interactive mode. Usage: agents remove <identifier>"));
3125
+ return;
3126
+ }
3127
+ const itemOptions = inventory.records.map((record) => {
3128
+ const meta = BUNDLE_DISPLAY_NAMES[record.name];
3129
+ const title = meta?.title || record.title || record.name;
3130
+ const icon = record.type === "bundle" ? "\u{1F4E6}" : record.type === "skill" ? "\u26A1" : "\u{1F916}";
3131
+ const typeLabel = record.type !== "bundle" ? `[${record.type}] ` : "";
3132
+ const badge = pc.cyan(record.displayLocation);
3133
+ const versionTag = pc.dim(`(v${record.installedVersion})`);
3134
+ return {
3135
+ value: record.id,
3136
+ label: `${icon} ${typeLabel}${title} ${badge} ${versionTag}`,
3137
+ hint: `${record.fileCount} file${record.fileCount > 1 ? "s" : ""} \u2014 ${record.description || record.name}`
3138
+ };
3139
+ });
3140
+ const selectedId = await select({
3141
+ message: "Select an installed package to remove:",
3142
+ options: itemOptions
3143
+ });
3144
+ if (typeof selectedId !== "string") {
3145
+ outro(pc.yellow("Removal cancelled."));
3146
+ return;
3147
+ }
3148
+ selectedRecord = inventory.records.find((r) => r.id === selectedId);
3149
+ if (selectedRecord) {
3150
+ identifier = selectedRecord.name;
3151
+ } else {
3152
+ identifier = selectedId;
3153
+ }
3154
+ }
460
3155
  const s = spinner();
461
3156
  s.start(`Removing "${identifier}"...`);
462
3157
  try {
463
3158
  const result = await uninstaller.uninstall(identifier, {
3159
+ scope: selectedRecord?.scope || (options.global ? "global" : "project"),
464
3160
  global: options.global,
3161
+ target: selectedRecord ? [selectedRecord.host] : options.target,
3162
+ targetDir: selectedRecord?.targetDir,
465
3163
  yes: options.yes,
466
3164
  force: options.force,
467
3165
  dryRun: options.dryRun
468
3166
  });
469
3167
  s.stop(`Uninstall processed`);
470
3168
  if (options.dryRun) {
471
- outro(pc.yellow(`[DRY RUN] Would remove ${result.removed.length} assets from ${result.targetDir}`));
3169
+ outro(pc.yellow(`[DRY RUN] Would remove ${result.removed.length} assets from ${result.targetDirs.join(", ")}`));
472
3170
  return;
473
3171
  }
474
3172
  outro(pc.green(`\u2714 Successfully removed ${result.removed.length} files matching "${identifier}"`));
@@ -478,35 +3176,811 @@ cli.command("remove <identifier>", "Remove a bundle, agent, skill, or workflow")
478
3176
  process.exit(1);
479
3177
  }
480
3178
  });
481
- cli.command("list", "List available and installed bundles").alias("ls").action(async () => {
482
- intro(pc.cyan("Agents United - Available Bundles"));
483
- const bundles = await registry.listBundles();
3179
+ cli.command("update [identifier]", "Update installed packages to upstream registry versions").alias("upgrade").option("-g, --global", "Update global packages in home directory").option("-t, --target <hosts>", "Target agent host runtimes").option("--fanout <hosts>", "Also project the canonical .agents/ store into these runtimes during update (claude, cursor, cline, opencode, codex)").option("-a, --all", "Update all installed packages without prompting").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force overwrite user modified files").option("--start", "Start the updated team in Cline immediately after update").option("--dry-run", "Simulate update without writing files").action(async (targetIdentifier, options = {}) => {
3180
+ intro(pc.cyan("Agents United \u2014 Package Update Engine"));
3181
+ let updateFanout;
3182
+ if (options.fanout !== void 0) {
3183
+ const rawFanout = Array.isArray(options.fanout) ? options.fanout : String(options.fanout).split(",");
3184
+ const parsedFanout = rawFanout.map((h) => h.trim().toLowerCase()).filter(Boolean);
3185
+ const invalidFanout = parsedFanout.filter((h) => !isKnownHost(h) || !HOST_REGISTRY[h].projectionCapable);
3186
+ if (invalidFanout.length > 0) {
3187
+ note(
3188
+ pc.yellow(
3189
+ `Ignoring invalid fanout host id(s): ${invalidFanout.join(", ")}. Valid ids: ${KNOWN_HOST_IDS.filter((h) => HOST_REGISTRY[h].projectionCapable).join(", ")}`
3190
+ ),
3191
+ "Invalid fanout"
3192
+ );
3193
+ }
3194
+ updateFanout = parsedFanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h].projectionCapable);
3195
+ }
3196
+ const s = spinner();
3197
+ s.start("Checking installed package inventory & upstream drift...");
3198
+ const report = await updater.checkUpdates({
3199
+ global: options.global,
3200
+ target: options.target
3201
+ });
3202
+ s.stop("Inventory discovery complete");
3203
+ if (report.totalCount === 0) {
3204
+ outro(pc.yellow("No installed packages found in project or global configuration."));
3205
+ return;
3206
+ }
3207
+ const isInteractive = process.stdout.isTTY && !options.yes && !options.all;
3208
+ let targetsToUpdate = [];
3209
+ if (targetIdentifier) {
3210
+ targetsToUpdate = [targetIdentifier];
3211
+ } else if (options.all || options.yes || !isInteractive) {
3212
+ targetsToUpdate = "__all__";
3213
+ } else {
3214
+ const statusMessage = `Total Installed Packages: ${report.totalCount}
3215
+ Updates Available: ${report.outdatedCount > 0 ? pc.yellow(pc.bold(`${report.outdatedCount} package${report.outdatedCount > 1 ? "s" : ""} can be updated`)) : pc.green("0 (All up to date)")}`;
3216
+ note(statusMessage, "Inventory Status");
3217
+ const menuOptions = [];
3218
+ if (report.outdatedCount > 0) {
3219
+ menuOptions.push({
3220
+ value: "__all_outdated__",
3221
+ label: `\u26A1 Update All Outdated Packages (${report.outdatedCount} available)`,
3222
+ hint: "batch upgrade all packages with newer upstream versions"
3223
+ });
3224
+ }
3225
+ menuOptions.push({
3226
+ value: "__selective__",
3227
+ label: "\u{1F4E6} Selectively Pick Packages to Update",
3228
+ hint: "choose specific bundles or items from an interactive list"
3229
+ });
3230
+ menuOptions.push({
3231
+ value: "__resync_all__",
3232
+ label: `\u{1F504} Re-sync / Repair All Packages (${report.totalCount})`,
3233
+ hint: "re-link or re-copy all packages to guarantee full integrity"
3234
+ });
3235
+ const action = await select({
3236
+ message: "Select update action:",
3237
+ options: menuOptions
3238
+ });
3239
+ if (typeof action !== "string") {
3240
+ outro(pc.yellow("Update cancelled."));
3241
+ return;
3242
+ }
3243
+ if (action === "__all_outdated__") {
3244
+ targetsToUpdate = report.items.filter((i) => i.hasUpdate).map((i) => i.record.name);
3245
+ } else if (action === "__resync_all__") {
3246
+ targetsToUpdate = "__all__";
3247
+ } else if (action === "__selective__") {
3248
+ const itemOptions = report.items.map((item) => {
3249
+ const meta = BUNDLE_DISPLAY_NAMES[item.record.name];
3250
+ const title = meta?.title || item.record.title || item.record.name;
3251
+ const badge = pc.cyan(item.record.displayLocation);
3252
+ const versionDiff = item.hasUpdate ? pc.yellow(`(v${item.installedVersion} \u2192 v${item.upstreamVersion}) [UPDATE]`) : pc.dim(`(v${item.installedVersion}) [UP TO DATE]`);
3253
+ return {
3254
+ value: item.record.name,
3255
+ label: `\u{1F4E6} ${title} ${badge} ${versionDiff}`,
3256
+ hint: item.record.description || item.record.name
3257
+ };
3258
+ });
3259
+ const initialValues = report.items.filter((i) => i.hasUpdate).map((i) => i.record.name);
3260
+ const selection = await multiselect({
3261
+ message: "Select packages to update:",
3262
+ options: itemOptions,
3263
+ initialValues: initialValues.length > 0 ? initialValues : void 0,
3264
+ required: true
3265
+ });
3266
+ if (!Array.isArray(selection) || selection.length === 0) {
3267
+ outro(pc.yellow("No packages selected for update."));
3268
+ return;
3269
+ }
3270
+ targetsToUpdate = selection;
3271
+ }
3272
+ }
3273
+ const updateSpinner = spinner();
3274
+ updateSpinner.start("Updating packages...");
3275
+ try {
3276
+ const result = await updater.update(targetsToUpdate, {
3277
+ global: options.global,
3278
+ target: options.target,
3279
+ force: options.force,
3280
+ dryRun: options.dryRun,
3281
+ fanout: updateFanout
3282
+ });
3283
+ updateSpinner.stop("Update completed");
3284
+ if (options.dryRun) {
3285
+ outro(pc.yellow(`[DRY RUN] Would update ${result.updated.length} packages in ${result.targetDirs.join(", ")}`));
3286
+ return;
3287
+ }
3288
+ if (result.updated.length > 0) {
3289
+ const unprojected = [];
3290
+ for (const rec of result.updated) {
3291
+ try {
3292
+ const lf = await fs11.readJson(path13.join(rec.targetDir, "agents-united.json"));
3293
+ if (!lf.fanout || lf.fanout.length === 0) unprojected.push(rec.name);
3294
+ } catch {
3295
+ }
3296
+ }
3297
+ if (unprojected.length > 0) {
3298
+ note(
3299
+ pc.yellow(
3300
+ `Tip: not synced to other assistants yet (only Antigravity reads .agents/).
3301
+ To add Cline & friends: agents update ${unprojected[0]} --fanout cline,claude`
3302
+ ),
3303
+ "One library, every assistant"
3304
+ );
3305
+ }
3306
+ const updatedList = result.updated.map((u) => ` \u2714 ${pc.bold(u.name)} ${pc.cyan(u.displayLocation)} ${pc.green(`(v${u.installedVersion})`)}`).join("\n");
3307
+ note(updatedList, "Updated Packages");
3308
+ }
3309
+ if (result.skipped.length > 0) {
3310
+ const skippedList = result.skipped.map((s2) => ` \u26A0 ${pc.yellow(s2.record.name)}: ${s2.reason}`).join("\n");
3311
+ note(skippedList, "Skipped Packages (User Modifications)");
3312
+ }
3313
+ if (result.updated.length === 0 && result.skipped.length === 0) {
3314
+ outro(pc.yellow("All packages are already up to date."));
3315
+ } else {
3316
+ outro(pc.green(`\u2714 Successfully processed update for ${result.updated.length} package${result.updated.length > 1 ? "s" : ""}!`));
3317
+ }
3318
+ } catch (err) {
3319
+ updateSpinner.stop(pc.red("Update failed"));
3320
+ outro(pc.red(`Error: ${err.message}`));
3321
+ process.exit(1);
3322
+ }
3323
+ });
3324
+ function renderBundleDetailTree(bundle) {
3325
+ if (bundle.name === "full") {
3326
+ const lines2 = [];
3327
+ lines2.push(`\u{1F4E6} ${pc.bold(pc.green("full"))} ${pc.cyan("(Universal Autonomous Department)")} ${pc.green("\u2B50 [Recommended]")}`);
3328
+ lines2.push(`\u2502 ${pc.white("Complete suite with all 7 lead orchestrators, 38 subagents, 90 skills, and 63 workflows.")}`);
3329
+ lines2.push(`\u2502`);
3330
+ lines2.push(`\u251C\u2500\u2500 \u{1F916} Lead Orchestrators (7 department domains):`);
3331
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F6E0}\uFE0F ${pc.blue("orchestrator-engineering")} ${pc.dim("(Software Engineering & Delivery)")}`);
3332
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F3DB}\uFE0F ${pc.blue("orchestrator-system-architecture")} ${pc.dim("(System Architecture & SRE)")}`);
3333
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F3A8} ${pc.blue("orchestrator-design")} ${pc.dim("(Product Design & UI/UX)")}`);
3334
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F4C8} ${pc.blue("orchestrator-marketing")} ${pc.dim("(Growth & Marketing Operations)")}`);
3335
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F512} ${pc.blue("orchestrator-security")} ${pc.dim("(Security Operations)")}`);
3336
+ lines2.push(`\u2502 \u251C\u2500\u2500 \u{1F52C} ${pc.blue("orchestrator-research")} ${pc.dim("(Deep Technical Research)")}`);
3337
+ lines2.push(`\u2502 \u2514\u2500\u2500 \u{1F4BC} ${pc.blue("orchestrator-business")} ${pc.dim("(Business Strategy & Economics)")}`);
3338
+ lines2.push(`\u251C\u2500\u2500 \u{1F916} Specialized Sub-Agents: ${pc.blue("38 worker agents across all 8 departments")}`);
3339
+ lines2.push(`\u251C\u2500\u2500 \u26A1 Skills: ${pc.yellow("90 modular skills & runbooks")}`);
3340
+ lines2.push(`\u251C\u2500\u2500 \u{1F504} Workflows: ${pc.magenta("63 guided multi-step workflows")}`);
3341
+ lines2.push(`\u251C\u2500\u2500 \u{1F50C} Prerequisites: ${pc.dim("None (Self-contained universal suite)")}`);
3342
+ lines2.push(`\u2514\u2500\u2500 \u{1F4A1} Execution Modes: ${pc.green("Operational")}`);
3343
+ return lines2.join("\n");
3344
+ }
3345
+ const meta = BUNDLE_DISPLAY_NAMES[bundle.name];
3346
+ const isEssentials = !bundle.parentBundle && bundle.name !== "full" && bundle.name !== "universal-skills" && bundle.tier !== "organization";
3347
+ const titleSuffix = isEssentials ? pc.cyan(" (Essentials Base)") : "";
3348
+ const parentTag = bundle.parentBundle ? pc.gray(` [inherits: ${bundle.parentBundle}]`) : "";
3349
+ const aliasesTag = bundle.aliases && bundle.aliases.length > 0 ? pc.gray(` [alias: ${bundle.aliases.join(", ")}]`) : "";
3350
+ let statusBadge = "";
3351
+ if (bundle.name === "universal-skills") statusBadge = pc.green(" \u2B50 [Recommended]");
3352
+ else if (bundle.status === "under-construction") statusBadge = pc.yellow(" \u{1F6A7} [Under Construction]");
3353
+ else if (bundle.status === "needs-audit") statusBadge = pc.magenta(" \u26A0\uFE0F [Needs Audit]");
3354
+ else if (bundle.status === "experimental") statusBadge = pc.cyan(" [Experimental]");
3355
+ else if (bundle.status === "deprecated") statusBadge = pc.red(" [Deprecated]");
3356
+ else statusBadge = pc.green(" [Stable]");
3357
+ const lines = [];
3358
+ lines.push(`\u{1F4E6} ${pc.bold(pc.green(bundle.name))}${titleSuffix}${statusBadge}${parentTag}${aliasesTag}`);
3359
+ lines.push(`\u2502 ${pc.white(bundle.description || meta?.summary || "")}`);
3360
+ lines.push(`\u2502`);
3361
+ if (bundle.orchestrator) {
3362
+ const orchName = bundle.orchestrator.replace(/\.md$/, "");
3363
+ lines.push(`\u251C\u2500\u2500 \u{1F916} Lead: ${pc.blue(pc.bold(orchName))}`);
3364
+ } else {
3365
+ lines.push(`\u251C\u2500\u2500 \u{1F916} Lead: ${pc.dim("None (Self-directed meta-skills)")}`);
3366
+ }
3367
+ if (bundle.agents && bundle.agents.length > 0) {
3368
+ const subNames = bundle.agents.map((a) => a.replace(/^subagent-/, "").replace(/\.md$/, ""));
3369
+ lines.push(`\u251C\u2500\u2500 \u{1F916} Sub-agents (${bundle.agents.length}):`);
3370
+ if (subNames.length <= 5) {
3371
+ subNames.forEach((name, sIdx) => {
3372
+ const isLastSub = sIdx === subNames.length - 1;
3373
+ const subBranch = isLastSub ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
3374
+ lines.push(`\u2502 ${subBranch} \u{1F916} ${pc.blue(name)}`);
3375
+ });
3376
+ } else {
3377
+ subNames.slice(0, 4).forEach((name) => {
3378
+ lines.push(`\u2502 \u251C\u2500\u2500 \u{1F916} ${pc.blue(name)}`);
3379
+ });
3380
+ lines.push(`\u2502 \u2514\u2500\u2500 \u{1F916} ${pc.dim(`(+${subNames.length - 4} more: ${subNames.slice(4).join(", ")})`)}`);
3381
+ }
3382
+ } else {
3383
+ lines.push(`\u251C\u2500\u2500 \u{1F916} Sub-agents: ${pc.dim("None")}`);
3384
+ }
3385
+ if (bundle.skills && bundle.skills.length > 0) {
3386
+ lines.push(`\u251C\u2500\u2500 \u26A1 Skills (${bundle.skills.length}):`);
3387
+ if (bundle.skills.length <= 5) {
3388
+ lines.push(`\u2502 \u2514\u2500\u2500 ${pc.yellow(bundle.skills.join(", "))}`);
3389
+ } else {
3390
+ lines.push(`\u2502 \u251C\u2500\u2500 ${pc.yellow(bundle.skills.slice(0, 5).join(", "))}`);
3391
+ lines.push(`\u2502 \u2514\u2500\u2500 ${pc.dim(`(+${bundle.skills.length - 5} more: ${bundle.skills.slice(5).join(", ")})`)}`);
3392
+ }
3393
+ } else {
3394
+ lines.push(`\u251C\u2500\u2500 \u26A1 Skills: ${pc.dim("Inherited from base bundle")}`);
3395
+ }
3396
+ if (bundle.workflows && bundle.workflows.length > 0) {
3397
+ const wfNames = bundle.workflows.map((w) => w.replace(/^workflow-/, "").replace(/\.md$/, ""));
3398
+ lines.push(`\u251C\u2500\u2500 \u{1F504} Workflows (${bundle.workflows.length}):`);
3399
+ if (wfNames.length <= 4) {
3400
+ lines.push(`\u2502 \u2514\u2500\u2500 ${pc.magenta(wfNames.join(", "))}`);
3401
+ } else {
3402
+ lines.push(`\u2502 \u251C\u2500\u2500 ${pc.magenta(wfNames.slice(0, 4).join(", "))}`);
3403
+ lines.push(`\u2502 \u2514\u2500\u2500 ${pc.dim(`(+${wfNames.length - 4} more: ${wfNames.slice(4).join(", ")})`)}`);
3404
+ }
3405
+ } else {
3406
+ lines.push(`\u251C\u2500\u2500 \u{1F504} Workflows: ${pc.dim("Inherited from base bundle")}`);
3407
+ }
3408
+ if (bundle.prerequisites) {
3409
+ const prereqParts = [];
3410
+ if (bundle.prerequisites.requiredMcps && bundle.prerequisites.requiredMcps.length > 0) {
3411
+ prereqParts.push(`MCPs: ${bundle.prerequisites.requiredMcps.map((m) => m.name).join(", ")}`);
3412
+ }
3413
+ if (bundle.prerequisites.requiredPackages && bundle.prerequisites.requiredPackages.length > 0) {
3414
+ prereqParts.push(`Packages: ${bundle.prerequisites.requiredPackages.join(", ")}`);
3415
+ }
3416
+ if (bundle.prerequisites.requiredEnvVars && bundle.prerequisites.requiredEnvVars.length > 0) {
3417
+ prereqParts.push(`Env: ${bundle.prerequisites.requiredEnvVars.join(", ")}`);
3418
+ }
3419
+ if (prereqParts.length > 0) {
3420
+ lines.push(`\u251C\u2500\u2500 \u{1F50C} Prerequisites: ${pc.yellow(prereqParts.join(" | "))}`);
3421
+ }
3422
+ } else {
3423
+ lines.push(`\u251C\u2500\u2500 \u{1F50C} Prerequisites: ${pc.dim("None (Self-contained domain bundle)")}`);
3424
+ }
3425
+ if (bundle.modes) {
3426
+ lines.push(`\u2514\u2500\u2500 \u{1F4A1} Execution Modes: ${pc.green("Operational")} (live MCPs) / ${pc.cyan("Brainstorming")} (advisory fallback)`);
3427
+ } else {
3428
+ lines.push(`\u2514\u2500\u2500 \u{1F4A1} Execution Modes: ${pc.green("Operational")}`);
3429
+ }
3430
+ return lines.join("\n");
3431
+ }
3432
+ function renderFullCatalogTree(bundles) {
3433
+ intro(pc.cyan("Agents United \u2014 Registry Catalog Tree"));
3434
+ const domainTitles = {
3435
+ engineering: "\u{1F6E0}\uFE0F Software Engineering & Delivery",
3436
+ architecture: "\u{1F3DB}\uFE0F System Architecture & SRE",
3437
+ design: "\u{1F3A8} Product Design & UI/UX",
3438
+ marketing: "\u{1F4C8} Growth & Marketing Operations",
3439
+ security: "\u{1F512} Security Operations",
3440
+ research: "\u{1F52C} Deep Technical Research",
3441
+ business: "\u{1F4BC} Business Strategy & Economics",
3442
+ universal: "\u{1F310} Universal Autonomous Department"
3443
+ };
3444
+ const domainOrder = [
3445
+ "universal",
3446
+ "engineering",
3447
+ "architecture",
3448
+ "design",
3449
+ "marketing",
3450
+ "security",
3451
+ "research",
3452
+ "business"
3453
+ ];
3454
+ const grouped = {};
3455
+ const orgBundles = [];
484
3456
  for (const b of bundles) {
485
- console.log(` ${pc.bold(pc.green(b.name))}`);
486
- console.log(` ${pc.dim(b.description)}`);
487
- console.log(` ${pc.gray(`Orchestrator: ${b.orchestrator} | Skills: ${b.skills?.length || 0} | Workflows: ${b.workflows?.length || 0}`)}
3457
+ if (b.tier === "organization" || b.domain === "organization") {
3458
+ orgBundles.push(b);
3459
+ continue;
3460
+ }
3461
+ const d = b.domain || "other";
3462
+ if (!grouped[d]) grouped[d] = [];
3463
+ grouped[d].push(b);
3464
+ }
3465
+ for (const domainKey of domainOrder) {
3466
+ const items = grouped[domainKey];
3467
+ if (!items || items.length === 0) continue;
3468
+ const header = domainTitles[domainKey] || `\u{1F4C1} ${domainKey.toUpperCase()}`;
3469
+ console.log(`
3470
+ ${pc.bold(pc.magenta(header))} ${pc.dim(`(${items.length} bundle${items.length > 1 ? "s" : ""})`)}`);
3471
+ items.forEach((b, bIdx) => {
3472
+ const isLastBundle = bIdx === items.length - 1;
3473
+ const bBranch = isLastBundle ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
3474
+ const subIndent = isLastBundle ? " " : "\u2502 ";
3475
+ const meta = BUNDLE_DISPLAY_NAMES[b.name];
3476
+ const isEssentials = !b.parentBundle && b.name !== "full" && b.name !== "universal-skills";
3477
+ const titleSuffix = isEssentials ? pc.cyan(" (Essentials)") : "";
3478
+ const parentTag = b.parentBundle ? pc.gray(` [inherits: ${b.parentBundle}]`) : "";
3479
+ const aliasesTag = b.aliases && b.aliases.length > 0 ? pc.gray(` [alias: ${b.aliases.join(", ")}]`) : "";
3480
+ let statusBadge = "";
3481
+ if (b.name === "universal-skills") statusBadge = pc.green(" \u2B50 [Recommended]");
3482
+ else if (b.status === "under-construction") statusBadge = pc.yellow(" \u{1F6A7} [Under Construction]");
3483
+ else if (b.status === "needs-audit") statusBadge = pc.magenta(" \u26A0\uFE0F [Needs Audit]");
3484
+ else if (b.status === "experimental") statusBadge = pc.cyan(" [Experimental]");
3485
+ else if (b.status === "deprecated") statusBadge = pc.red(" [Deprecated]");
3486
+ console.log(`${bBranch} \u{1F4E6} ${pc.bold(pc.green(b.name))}${titleSuffix}${statusBadge}${parentTag}${aliasesTag}`);
3487
+ console.log(`${subIndent}\u2502 ${pc.white(b.description)}`);
3488
+ if (b.orchestrator) {
3489
+ const orchName = b.orchestrator.replace(/\.md$/, "");
3490
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F916} Lead: ${pc.blue(orchName)}`);
3491
+ }
3492
+ if (b.agents && b.agents.length > 0) {
3493
+ const subNames = b.agents.map((a) => a.replace(/^subagent-/, "").replace(/\.md$/, ""));
3494
+ const displaySubs = subNames.length > 3 ? `${subNames.slice(0, 3).join(", ")} (+${subNames.length - 3} more)` : subNames.join(", ");
3495
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F916} Sub-agents: ${pc.blue(displaySubs)}`);
3496
+ }
3497
+ if (b.skills && b.skills.length > 0) {
3498
+ const displaySkills = b.skills.length > 3 ? `${b.skills.slice(0, 3).join(", ")} (+${b.skills.length - 3} more)` : b.skills.join(", ");
3499
+ console.log(`${subIndent}\u251C\u2500\u2500 \u26A1 Skills: ${pc.yellow(displaySkills)}`);
3500
+ }
3501
+ if (b.workflows && b.workflows.length > 0) {
3502
+ const wfNames = b.workflows.map((w) => w.replace(/^workflow-/, "").replace(/\.md$/, ""));
3503
+ const displayWfs = wfNames.length > 3 ? `${wfNames.slice(0, 3).join(", ")} (+${wfNames.length - 3} more)` : wfNames.join(", ");
3504
+ console.log(`${subIndent}\u2514\u2500\u2500 \u{1F504} Workflows: ${pc.magenta(displayWfs)}`);
3505
+ } else {
3506
+ console.log(`${subIndent}\u2514\u2500\u2500 \u{1F504} Workflows: ${pc.dim("Inherited from parent")}`);
3507
+ }
3508
+ if (!isLastBundle) {
3509
+ console.log(`${subIndent}`);
3510
+ }
3511
+ });
3512
+ }
3513
+ if (orgBundles.length > 0) {
3514
+ console.log(`
3515
+ ${pc.bold(pc.cyan("\u{1F3E2} Organization Bundles (Experimental / Cross-Functional)"))} ${pc.dim(`(${orgBundles.length} bundle${orgBundles.length > 1 ? "s" : ""})`)}`);
3516
+ console.log(pc.dim(" Cross-domain teams modeled after real organizations. Require runtime prerequisites (MCPs/packages)."));
3517
+ orgBundles.forEach((b, bIdx) => {
3518
+ const isLastBundle = bIdx === orgBundles.length - 1;
3519
+ const bBranch = isLastBundle ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
3520
+ const subIndent = isLastBundle ? " " : "\u2502 ";
3521
+ let statusBadge = "";
3522
+ if (b.status === "under-construction") statusBadge = pc.yellow(" \u{1F6A7} [Under Construction (TBA)]");
3523
+ else if (b.status === "needs-audit") statusBadge = pc.magenta(" \u26A0\uFE0F [Needs Audit]");
3524
+ else if (b.status === "experimental") statusBadge = pc.cyan(" [Experimental]");
3525
+ else if (b.status === "deprecated") statusBadge = pc.red(" [Deprecated]");
3526
+ else statusBadge = pc.green(" [Stable]");
3527
+ const prereqBadge = pc.dim(" [Prerequisites Required]");
3528
+ console.log(`${bBranch} \u{1F3E2} ${pc.bold(pc.cyan(b.name))}${statusBadge}${prereqBadge}`);
3529
+ console.log(`${subIndent}\u2502 ${pc.white(b.description)}`);
3530
+ if (b.orchestrator) {
3531
+ const orchName = b.orchestrator.replace(/\.md$/, "");
3532
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F916} Lead: ${pc.blue(orchName)}`);
3533
+ }
3534
+ if (b.agents && b.agents.length > 0) {
3535
+ const subNames = b.agents.map((a) => a.replace(/^subagent-/, "").replace(/\.md$/, ""));
3536
+ const displaySubs = subNames.length > 3 ? `${subNames.slice(0, 3).join(", ")} (+${subNames.length - 3} more)` : subNames.join(", ");
3537
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F916} Sub-agents: ${pc.blue(displaySubs)}`);
3538
+ }
3539
+ if (b.skills && b.skills.length > 0) {
3540
+ const displaySkills = b.skills.length > 3 ? `${b.skills.slice(0, 3).join(", ")} (+${b.skills.length - 3} more)` : b.skills.join(", ");
3541
+ console.log(`${subIndent}\u251C\u2500\u2500 \u26A1 Skills: ${pc.yellow(displaySkills)}`);
3542
+ }
3543
+ if (b.workflows && b.workflows.length > 0) {
3544
+ const wfNames = b.workflows.map((w) => w.replace(/^workflow-/, "").replace(/\.md$/, ""));
3545
+ const displayWfs = wfNames.length > 3 ? `${wfNames.slice(0, 3).join(", ")} (+${wfNames.length - 3} more)` : wfNames.join(", ");
3546
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F504} Workflows: ${pc.magenta(displayWfs)}`);
3547
+ }
3548
+ if (b.prerequisites) {
3549
+ const prereqParts = [];
3550
+ if (b.prerequisites.requiredMcps && b.prerequisites.requiredMcps.length > 0) {
3551
+ prereqParts.push(`MCPs: ${b.prerequisites.requiredMcps.map((m) => m.name).join(", ")}`);
3552
+ }
3553
+ if (b.prerequisites.requiredPackages && b.prerequisites.requiredPackages.length > 0) {
3554
+ prereqParts.push(`Packages: ${b.prerequisites.requiredPackages.join(", ")}`);
3555
+ }
3556
+ if (b.prerequisites.requiredEnvVars && b.prerequisites.requiredEnvVars.length > 0) {
3557
+ prereqParts.push(`Env: ${b.prerequisites.requiredEnvVars.join(", ")}`);
3558
+ }
3559
+ if (prereqParts.length > 0) {
3560
+ console.log(`${subIndent}\u251C\u2500\u2500 \u{1F50C} Prerequisites: ${pc.yellow(prereqParts.join(" | "))}`);
3561
+ }
3562
+ }
3563
+ if (b.modes) {
3564
+ console.log(`${subIndent}\u2514\u2500\u2500 \u{1F4A1} Execution Modes: ${pc.green("Operational")} (live MCPs) / ${pc.cyan("Brainstorming")} (advisory fallback)`);
3565
+ } else {
3566
+ console.log(`${subIndent}\u2514\u2500\u2500 \u{1F4A1} Execution Modes: ${pc.green("Operational")}`);
3567
+ }
3568
+ if (!isLastBundle) {
3569
+ console.log(`${subIndent}`);
3570
+ }
3571
+ });
3572
+ }
3573
+ outro(pc.cyan('\nRun "agents add <bundle>" or select interactively to install.'));
3574
+ }
3575
+ async function handleBundleDetailView(bundle) {
3576
+ const treeOutput = renderBundleDetailTree(bundle);
3577
+ console.log(`
3578
+ ${pc.bold(pc.cyan("\u250C\u2500\u2500 \u{1F4CB} Bundle Detail: " + pc.green(bundle.name) + " " + "\u2500".repeat(Math.max(2, 40 - bundle.name.length))))}`);
3579
+ console.log(treeOutput);
3580
+ console.log(`${pc.bold(pc.cyan("\u2514" + "\u2500".repeat(60)))}
488
3581
  `);
3582
+ const actionOptions = [
3583
+ {
3584
+ value: "install",
3585
+ label: `\u{1F680} Install "${bundle.name}" bundle`,
3586
+ hint: `run installer for ${bundle.name}`
3587
+ },
3588
+ {
3589
+ value: "back_bundle",
3590
+ label: `\u{1F519} Back to ${bundle.domain || "Department"} bundle list`,
3591
+ hint: "explore other bundles in this category"
3592
+ },
3593
+ {
3594
+ value: "back_dept",
3595
+ label: `\u{1F3DB}\uFE0F Back to Department selection`,
3596
+ hint: "explore other departments"
3597
+ },
3598
+ {
3599
+ value: "exit",
3600
+ label: `\u{1F6AA} Exit explorer`,
3601
+ hint: "return to shell"
3602
+ }
3603
+ ];
3604
+ const action = await select({
3605
+ message: `Action for ${pc.bold(pc.green(bundle.name))}:`,
3606
+ options: actionOptions
3607
+ });
3608
+ if (action === "install") {
3609
+ if (bundle.status === "under-construction") {
3610
+ note(
3611
+ `The bundle "${bundle.name}" is currently under active construction (status: under-construction).
3612
+ Its orchestrators, workflows, and specialized skills are being authored and are not ready for production use.
3613
+
3614
+ Planned Capabilities:
3615
+ \u2022 Lead Orchestrator with Firecrawl & GitHub MCP tool calling
3616
+ \u2022 Full-stack web, design & SEO delivery workflows
3617
+ \u2022 Dual Execution Modes (Operational / Brainstorming)`,
3618
+ "\u{1F6A7} Under Construction Gate"
3619
+ );
3620
+ const underConstructionChoice = await select({
3621
+ message: pc.yellow(`"${bundle.name}" is currently under construction. How would you like to proceed?`),
3622
+ options: [
3623
+ {
3624
+ value: "abort",
3625
+ label: "\u{1F6D1} Abort installation (Recommended)",
3626
+ hint: "return without modifying workspace files"
3627
+ },
3628
+ {
3629
+ value: "allow",
3630
+ label: "\u26A0\uFE0F Install in-development draft anyway (--allow-under-construction)",
3631
+ hint: "proceed with in-progress placeholder assets"
3632
+ }
3633
+ ]
3634
+ });
3635
+ if (underConstructionChoice === "abort" || typeof underConstructionChoice !== "string") {
3636
+ outro(pc.yellow(`Installation cancelled. "${bundle.name}" is under construction.`));
3637
+ return "__back_bundle__";
3638
+ }
3639
+ }
3640
+ let executionMode = "operational";
3641
+ if (bundle.prerequisites || bundle.tier === "organization") {
3642
+ const checker = new PrerequisiteChecker();
3643
+ const prereqEval = await checker.evaluate(bundle);
3644
+ if (prereqEval.hasPrerequisites) {
3645
+ const checkLines = [];
3646
+ for (const item of prereqEval.items) {
3647
+ const typeLabel = item.type === "mcp" ? "MCP" : item.type === "env" ? "Env" : "Pkg";
3648
+ const icon = item.satisfied ? pc.green("\u2713") : pc.red("\u2717");
3649
+ const details = pc.dim(`(${item.details || ""})`);
3650
+ checkLines.push(` ${icon} [${typeLabel}] ${pc.bold(item.name)}: ${item.satisfied ? pc.green("Detected") : pc.red("Missing")} ${details}`);
3651
+ }
3652
+ note(
3653
+ checkLines.join("\n"),
3654
+ `Prerequisite Evaluation: ${bundle.name} (${bundle.tier === "organization" ? "Organization Bundle" : "Prerequisites Required"})`
3655
+ );
3656
+ if (!prereqEval.allSatisfied) {
3657
+ const gateChoice = await select({
3658
+ message: pc.yellow(`Prerequisites not fully satisfied for "${bundle.name}". How would you like to proceed?`),
3659
+ options: [
3660
+ {
3661
+ value: "abort",
3662
+ label: "\u{1F6D1} Abort and configure missing prerequisites (Recommended)",
3663
+ hint: "exit and configure MCP servers, packages, or environment variables"
3664
+ },
3665
+ {
3666
+ value: "brainstorming",
3667
+ label: "\u{1F4A1} Install in Brainstorming Mode (Advisory & Spec generation only)",
3668
+ hint: "runs in idea/planning mode without requiring live MCP tool connections"
3669
+ },
3670
+ {
3671
+ value: "force",
3672
+ label: "\u26A0\uFE0F Force install in Operational Mode anyway",
3673
+ hint: "proceed despite missing prerequisites (may fail during runtime calls)"
3674
+ }
3675
+ ]
3676
+ });
3677
+ if (gateChoice === "abort" || typeof gateChoice !== "string") {
3678
+ outro(pc.yellow("Installation aborted. Please configure required MCPs and environment variables, then retry."));
3679
+ return "__back_bundle__";
3680
+ }
3681
+ if (gateChoice === "brainstorming") {
3682
+ executionMode = "brainstorming";
3683
+ note(pc.cyan('Switched execution mode to "brainstorming" (idea/spec fallback mode).'), "Mode Selected");
3684
+ } else if (gateChoice === "force") {
3685
+ executionMode = "operational";
3686
+ note(pc.yellow('Proceeding in "operational" mode with missing prerequisites.'), "Warning");
3687
+ }
3688
+ }
3689
+ }
3690
+ }
3691
+ const detectedHosts = detectWorkspaceHosts();
3692
+ let installHosts = ["agents"];
3693
+ let installFanout = [];
3694
+ if (detectedHosts.length > 0) {
3695
+ const projectionHosts = detectedHosts.filter((h) => HOST_REGISTRY[h].projectionCapable);
3696
+ installFanout = projectionHosts;
3697
+ installHosts = detectedHosts.filter((h) => !HOST_REGISTRY[h].projectionCapable);
3698
+ if (!installHosts.includes("agents")) installHosts.unshift("agents");
3699
+ }
3700
+ const installSpinner = spinner();
3701
+ installSpinner.start(`Installing "${bundle.name}"...`);
3702
+ try {
3703
+ const result = await installer.install(bundle.name, {
3704
+ scope: "project",
3705
+ hosts: installHosts,
3706
+ fanout: installFanout,
3707
+ mode: executionMode,
3708
+ method: "symlink"
3709
+ });
3710
+ installSpinner.stop(pc.green(`\u2714 Successfully installed ${bundle.name}!`));
3711
+ note(
3712
+ `Bundle: ${result.installed.targetBundle || bundle.name}
3713
+ Scope: project
3714
+ Method: ${result.method}
3715
+ Targets: ${installHosts.join(", ")}
3716
+ Agents: ${result.installed.agents.join(", ") || "None"}
3717
+ Skills: ${result.installed.skills.join(", ") || "None"}
3718
+ Target Directories: ${result.targetDirs.join("\n ")}` + (result.projections.length > 0 ? `
3719
+
3720
+ Projections:
3721
+ ${renderProjections(result.projections)}` : ""),
3722
+ "Installation Success"
3723
+ );
3724
+ outro(pc.green(`\u2728 Installation of "${bundle.name}" complete!`));
3725
+ } catch (err) {
3726
+ installSpinner.stop(pc.red("Installation failed"));
3727
+ outro(pc.red(`Error: ${err.message}`));
3728
+ }
3729
+ return "__exit__";
3730
+ } else if (action === "back_dept") {
3731
+ return "__back_dept__";
3732
+ } else if (action === "exit" || typeof action !== "string") {
3733
+ return "__exit__";
3734
+ }
3735
+ return "__back_bundle__";
3736
+ }
3737
+ cli.command("list", "List available bundles grouped by department domain").alias("ls").option("--json", "Output bundles in JSON format").option("--tree", "Output full static catalog tree").option("--all", "Output full static catalog tree").action(async (options = {}) => {
3738
+ const bundles = await registry.listBundles();
3739
+ if (options.json) {
3740
+ console.log(JSON.stringify(bundles, null, 2));
3741
+ return;
3742
+ }
3743
+ const isInteractive = process.stdout.isTTY && !options.tree && !options.all;
3744
+ if (!isInteractive) {
3745
+ renderFullCatalogTree(bundles);
3746
+ return;
3747
+ }
3748
+ intro(pc.cyan("\u{1F310} Agents United \u2014 Interactive Catalog Explorer"));
3749
+ const domainMeta = {
3750
+ universal: { label: "Universal Autonomous Department", icon: "\u{1F310} " },
3751
+ engineering: { label: "Software Engineering & Delivery", icon: "\u{1F6E0}\uFE0F " },
3752
+ architecture: { label: "System Architecture & SRE", icon: "\u{1F3DB}\uFE0F " },
3753
+ design: { label: "Product Design & UI/UX", icon: "\u{1F3A8} " },
3754
+ marketing: { label: "Growth & Marketing Operations", icon: "\u{1F4C8} " },
3755
+ security: { label: "Security Operations", icon: "\u{1F512} " },
3756
+ research: { label: "Deep Technical Research", icon: "\u{1F52C} " },
3757
+ business: { label: "Business Strategy & Economics", icon: "\u{1F4BC} " },
3758
+ organization: { label: "Organization Bundles (Experimental / Cross-Functional)", icon: "\u{1F3E2} " }
3759
+ };
3760
+ let activeDomain;
3761
+ while (true) {
3762
+ if (!activeDomain) {
3763
+ const domainOptions = Object.entries(domainMeta).map(([domainKey, meta]) => {
3764
+ const count = bundles.filter((b) => b.domain === domainKey || domainKey === "organization" && b.tier === "organization").length;
3765
+ return {
3766
+ value: domainKey,
3767
+ label: `${meta.icon} ${meta.label}`,
3768
+ hint: domainKey === "universal" ? "meta-skills baseline + full suite" : domainKey === "organization" ? "cross-functional teams with prerequisites" : `${count} specialized bundle${count > 1 ? "s" : ""}`
3769
+ };
3770
+ });
3771
+ domainOptions.push({
3772
+ value: "__search__",
3773
+ label: "\u{1F50D} Search by name / keyword...",
3774
+ hint: "find bundles, agents, skills or workflows"
3775
+ });
3776
+ domainOptions.push({
3777
+ value: "__full_tree__",
3778
+ label: "\u{1F333} View Full Static Catalog Tree",
3779
+ hint: "expand all 18 bundles and 8 departments at once"
3780
+ });
3781
+ const selectedDomain = await select({
3782
+ message: "Select Department Domain to explore:",
3783
+ options: domainOptions
3784
+ });
3785
+ if (typeof selectedDomain !== "string") {
3786
+ outro(pc.cyan("Catalog explorer closed."));
3787
+ return;
3788
+ }
3789
+ if (selectedDomain === "__full_tree__") {
3790
+ renderFullCatalogTree(bundles);
3791
+ continue;
3792
+ }
3793
+ if (selectedDomain === "__search__") {
3794
+ const query = await text({
3795
+ message: "Enter keyword to search:",
3796
+ placeholder: "e.g. mobile, playwright, react, backend, seo"
3797
+ });
3798
+ if (typeof query !== "string" || !query.trim()) {
3799
+ continue;
3800
+ }
3801
+ const results = await registry.find(query.trim());
3802
+ if (results.bundles.length === 0 && results.agents.length === 0 && results.skills.length === 0 && results.workflows.length === 0) {
3803
+ note(`No items found matching "${query.trim()}".`, "Search Results");
3804
+ continue;
3805
+ }
3806
+ const searchOptions = [
3807
+ ...results.bundles.map((b) => ({
3808
+ value: `bundle:${b.name}`,
3809
+ label: `\u{1F4E6} [Bundle] ${BUNDLE_DISPLAY_NAMES[b.name]?.title || b.name} (${b.name})`,
3810
+ hint: b.description
3811
+ })),
3812
+ {
3813
+ value: "__back__",
3814
+ label: "\u{1F519} Back to Department Selection",
3815
+ hint: ""
3816
+ }
3817
+ ];
3818
+ if (searchOptions.length === 1) {
3819
+ note(`Found related agents/skills for "${query}", but no top-level bundles. Try browsing by department.`, "Search Results");
3820
+ continue;
3821
+ }
3822
+ const chosen = await select({
3823
+ message: `Search Results for "${query.trim()}":`,
3824
+ options: searchOptions
3825
+ });
3826
+ if (typeof chosen === "string" && chosen.startsWith("bundle:")) {
3827
+ const bundleName = chosen.replace(/^bundle:/, "");
3828
+ const b = await registry.getBundle(bundleName);
3829
+ if (b) {
3830
+ const action2 = await handleBundleDetailView(b);
3831
+ if (action2 === "__exit__") {
3832
+ outro(pc.cyan("Catalog explorer closed."));
3833
+ return;
3834
+ }
3835
+ }
3836
+ }
3837
+ continue;
3838
+ }
3839
+ activeDomain = selectedDomain;
3840
+ }
3841
+ const domainBundles = bundles.filter((b) => b.domain === activeDomain || activeDomain === "organization" && b.tier === "organization");
3842
+ const sortedBundles = [...domainBundles].sort((a, b) => {
3843
+ if (a.name === "universal-skills") return -1;
3844
+ if (b.name === "universal-skills") return 1;
3845
+ if (!a.parentBundle && b.parentBundle) return -1;
3846
+ if (a.parentBundle && !b.parentBundle) return 1;
3847
+ return a.name.localeCompare(b.name);
3848
+ });
3849
+ const bundleOptions = sortedBundles.map((b, idx) => {
3850
+ const isLast = idx === sortedBundles.length - 1;
3851
+ const branch = sortedBundles.length > 1 ? isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : "";
3852
+ const meta = BUNDLE_DISPLAY_NAMES[b.name];
3853
+ const isEssentials = !b.parentBundle && b.name !== "full" && b.name !== "universal-skills" && b.tier !== "organization";
3854
+ const titleSuffix = isEssentials ? pc.cyan(" (Essentials Base)") : "";
3855
+ const parentTag = b.parentBundle ? pc.gray(` [inherits: ${b.parentBundle}]`) : "";
3856
+ let badge = "";
3857
+ if (b.name === "universal-skills") badge = pc.green(" \u2B50 [Recommended]");
3858
+ else if (b.status === "under-construction") badge = pc.yellow(" \u{1F6A7} [Under Construction]");
3859
+ else if (b.status === "needs-audit") badge = pc.magenta(" \u26A0\uFE0F [Needs Audit]");
3860
+ else if (b.status === "experimental") badge = pc.cyan(" [Experimental]");
3861
+ else if (b.status === "deprecated") badge = pc.red(" [Deprecated]");
3862
+ return {
3863
+ value: b.name,
3864
+ label: `${branch}\u{1F4E6} ${meta?.title || b.name}${titleSuffix}${badge}${parentTag}`,
3865
+ hint: b.description || meta?.summary || b.name
3866
+ };
3867
+ });
3868
+ bundleOptions.push({
3869
+ value: "__back_domain__",
3870
+ label: "\u{1F519} Back to Department Selection",
3871
+ hint: "choose another department domain"
3872
+ });
3873
+ const selectedBundleName = await select({
3874
+ message: `${domainMeta[activeDomain]?.label || activeDomain} \u2014 Select bundle to open:`,
3875
+ options: bundleOptions
3876
+ });
3877
+ if (typeof selectedBundleName !== "string") {
3878
+ outro(pc.cyan("Catalog explorer closed."));
3879
+ return;
3880
+ }
3881
+ if (selectedBundleName === "__back_domain__") {
3882
+ activeDomain = void 0;
3883
+ continue;
3884
+ }
3885
+ const bundle = await registry.getBundle(selectedBundleName);
3886
+ if (!bundle) continue;
3887
+ const action = await handleBundleDetailView(bundle);
3888
+ if (action === "__exit__") {
3889
+ outro(pc.cyan("Catalog explorer closed."));
3890
+ return;
3891
+ }
3892
+ if (action === "__back_dept__") {
3893
+ activeDomain = void 0;
3894
+ }
489
3895
  }
490
- outro(pc.cyan('Run "npx agents-united add <bundle>" to install a bundle.'));
491
3896
  });
492
- cli.command("find <query>", "Search for agents, skills, or bundles in the registry").alias("search").action(async (query) => {
493
- intro(pc.cyan(`Agents United - Search: "${query}"`));
494
- const results = await registry.find(query);
495
- console.log(pc.bold("\nMatching Bundles:"));
496
- results.bundles.forEach((b) => console.log(` - ${pc.green(b.name)}: ${pc.dim(b.description)}`));
497
- console.log(pc.bold("\nMatching Agents:"));
498
- results.agents.forEach((a) => console.log(` - ${pc.blue(a)}`));
499
- console.log(pc.bold("\nMatching Skills:"));
500
- results.skills.forEach((s) => console.log(` - ${pc.yellow(s)}`));
501
- outro(pc.cyan('\nUse "npx agents-united add <name>" to install any match.'));
3897
+ cli.command("find [query]", "Search for agents, skills, workflows, or bundles in the registry").alias("search").option("-c, --category <domain>", "Filter search by domain (e.g. engineering, architecture, design)").option("-t, --type <type>", "Filter by item type (bundle, agent, skill, workflow)").option("--json", "Output results in JSON format").option("-i, --interactive", "Interactive selection to install matching item").action(async (query, options = {}) => {
3898
+ const q = query || "";
3899
+ const results = await registry.find(q, {
3900
+ domain: options.category || options.domain,
3901
+ type: options.type
3902
+ });
3903
+ if (options.json) {
3904
+ console.log(JSON.stringify(results, null, 2));
3905
+ return;
3906
+ }
3907
+ const totalCount = results.bundles.length + results.agents.length + results.skills.length + results.workflows.length;
3908
+ intro(pc.cyan(`Agents United - Search Results: "${q || "*"}" (${totalCount} found)`));
3909
+ if (results.bundles.length > 0) {
3910
+ console.log(pc.bold(`
3911
+ \u{1F4E6} Bundles (${results.bundles.length}):`));
3912
+ results.bundles.forEach((b) => {
3913
+ const meta = BUNDLE_DISPLAY_NAMES[b.name];
3914
+ console.log(` - ${pc.green(pc.bold(b.name))}: ${pc.white(meta?.title || b.name)} \u2014 ${pc.dim(b.description)}`);
3915
+ });
3916
+ }
3917
+ if (results.agents.length > 0) {
3918
+ console.log(pc.bold(`
3919
+ \u{1F916} Agents (${results.agents.length}):`));
3920
+ results.agents.forEach((a) => {
3921
+ const stem = a.replace(/\.md$/, "");
3922
+ console.log(` - ${pc.blue(stem)} ${pc.gray(`(${a})`)}`);
3923
+ });
3924
+ }
3925
+ if (results.skills.length > 0) {
3926
+ console.log(pc.bold(`
3927
+ \u26A1 Skills (${results.skills.length}):`));
3928
+ results.skills.forEach((s) => console.log(` - ${pc.yellow(s)}`));
3929
+ }
3930
+ if (results.workflows.length > 0) {
3931
+ console.log(pc.bold(`
3932
+ \u{1F504} Workflows (${results.workflows.length}):`));
3933
+ results.workflows.forEach((w) => {
3934
+ const stem = w.replace(/\.md$/, "");
3935
+ console.log(` - ${pc.magenta(stem)} ${pc.gray(`(${w})`)}`);
3936
+ });
3937
+ }
3938
+ if (totalCount === 0) {
3939
+ console.log(pc.yellow(`
3940
+ No matching items found for "${q}". Try searching without filters or using "agents list".`));
3941
+ }
3942
+ if (options.interactive && totalCount > 0) {
3943
+ const items = [
3944
+ ...results.bundles.map((b) => ({ value: b.name, label: `[Bundle] ${b.name} \u2014 ${b.description}` })),
3945
+ ...results.agents.map((a) => ({ value: a.replace(/\.md$/, ""), label: `[Agent] ${a}` })),
3946
+ ...results.skills.map((s) => ({ value: s, label: `[Skill] ${s}` })),
3947
+ ...results.workflows.map((w) => ({ value: w.replace(/\.md$/, ""), label: `[Workflow] ${w}` }))
3948
+ ];
3949
+ const selected = await select({
3950
+ message: "Select an item to install:",
3951
+ options: items
3952
+ });
3953
+ if (typeof selected === "string") {
3954
+ const s = spinner();
3955
+ s.start(`Installing "${selected}"...`);
3956
+ try {
3957
+ const res = await installer.install(selected, { scope: "project" });
3958
+ s.stop(`Installed ${selected}`);
3959
+ outro(pc.green(`\u2714 Successfully installed "${selected}"!`));
3960
+ } catch (err) {
3961
+ s.stop(pc.red("Installation failed"));
3962
+ outro(pc.red(`Error: ${err.message}`));
3963
+ }
3964
+ return;
3965
+ }
3966
+ }
3967
+ outro(pc.cyan('\nUse "agents add <name>" to install any match.'));
502
3968
  });
503
- cli.command("init", "Initialize workspace .agents folder and install recommended bundle").option("-b, --bundle <bundle>", "Default bundle to install", { default: "software-engineering" }).action(async (options) => {
3969
+ cli.command("init", "Initialize project workspace directory and install recommended bundle").option("-b, --bundle <bundle>", "Default bundle to install", { default: "software-engineering" }).option("-s, --symlink", "Use symlinks (default / recommended)").option("--copy", "Use standalone copies").option("-t, --target <hosts>", "Target agent hosts", { default: "agents" }).action(async (options) => {
504
3970
  intro(pc.cyan("Agents United - Initialize Workspace"));
505
3971
  const s = spinner();
506
- s.start(`Initializing .agents directory with bundle "${options.bundle}"...`);
3972
+ s.start(`Initializing workspace with bundle "${options.bundle}"...`);
507
3973
  try {
508
- const result = await installer.install(options.bundle, { scope: "workspace" });
509
- s.stop(`Initialized ${result.targetDir}`);
3974
+ const initTargets = (Array.isArray(options.target) ? options.target : String(options.target || "agents").split(",")).map((h) => h.trim().toLowerCase());
3975
+ const initPlan = planInstallTargets(initTargets);
3976
+ const result = await installer.install(options.bundle, {
3977
+ scope: "project",
3978
+ symlink: options.symlink,
3979
+ copy: options.copy,
3980
+ hosts: initPlan.hosts,
3981
+ fanout: initPlan.fanout
3982
+ });
3983
+ s.stop(`Initialized ${result.targetDirs.join(", ")}`);
510
3984
  outro(pc.green(`\u2714 Initialized workspace with "${options.bundle}" bundle!`));
511
3985
  } catch (err) {
512
3986
  s.stop(pc.red("Initialization failed"));
@@ -514,20 +3988,109 @@ cli.command("init", "Initialize workspace .agents folder and install recommended
514
3988
  process.exit(1);
515
3989
  }
516
3990
  });
517
- cli.command("doctor", "Verify health of installed agents, frontmatter schemas, and hooks").action(async () => {
518
- intro(pc.cyan("Agents United - Health Doctor"));
519
- const report = await DoctorEngine.runDoctor();
520
- console.log(` Installed Agents: ${report.agentsCount}`);
521
- console.log(` Installed Skills: ${report.skillsCount}`);
522
- console.log(` Installed Workflows: ${report.workflowsCount}
3991
+ cli.command("start <bundle> [prompt]", "Start an installed bundle team in its host runtime (e.g. Cline)").option("--host <host>", "Host runtime (default: auto-detect from lockfile fanout)").option("-g, --global", "Select global installation").option("--team <name>", "Override generated team name").option("--allow-addons", "Pre-authorize recommended addon installations for this session").option("--headless", "Run non-interactively without interactive TUI").option("--dry-run", "Print activation resolution and argv summary without launching").action(async (bundle, prompt, options = {}) => {
3992
+ intro(pc.cyan("Agents United \u2014 Runtime Activation"));
3993
+ const launcher = new ClineLauncher();
3994
+ const probe = new ClineCapabilityProbe();
3995
+ try {
3996
+ const resolution = await launcher.resolveInstallation(bundle, {
3997
+ global: options.global,
3998
+ cwd: process.cwd()
3999
+ });
4000
+ const probeReport = await probe.probe();
4001
+ if (!probeReport.installed && !options.dryRun) {
4002
+ outro(pc.red(`Cline executable was not found on PATH or via CLINE_BIN_PATH. Please install Cline CLI or ensure it is accessible.`));
4003
+ process.exit(1);
4004
+ }
4005
+ const plan = launcher.planActivation({
4006
+ bundleName: bundle,
4007
+ workspace: resolution.workspace,
4008
+ scope: resolution.scope,
4009
+ report: probeReport,
4010
+ prompt,
4011
+ teamName: options.team,
4012
+ allowAddons: options.allowAddons,
4013
+ headless: options.headless
4014
+ });
4015
+ if (options.dryRun) {
4016
+ note(
4017
+ `Bundle: ${plan.bundleName}
4018
+ Scope: ${plan.scope}
4019
+ Workspace: ${plan.workspace}
4020
+ Team Name: ${plan.teamName}
4021
+ Strategy: ${plan.strategy}
4022
+ Executable: ${plan.executable}
4023
+ Argv: ${plan.argv.map((a) => a.includes(" ") || a.includes("\n") ? `"${a.replace(/\n/g, "\\n")}"` : a).join(" ")}`,
4024
+ "Activation Plan (dry run)"
4025
+ );
4026
+ outro(pc.yellow("Dry run complete. No processes launched."));
4027
+ return;
4028
+ }
4029
+ note(
4030
+ `Team: ${pc.bold(plan.teamName)}
4031
+ Strategy: ${pc.cyan(plan.strategy)}
4032
+ Workspace: ${plan.workspace}`,
4033
+ "Starting Cline Team"
4034
+ );
4035
+ await launcher.launch(plan);
4036
+ outro(pc.green(`\u2714 Cline session finished.`));
4037
+ } catch (err) {
4038
+ outro(pc.red(`Error: ${err.message}`));
4039
+ process.exit(1);
4040
+ }
4041
+ });
4042
+ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, and hooks").option("--host <host>", "Audit specific host runtime (e.g. cline)").action(async (options = {}) => {
4043
+ intro(pc.cyan("\u{1FA7A} Agents United \u2014 Health Doctor"));
4044
+ const report = await DoctorEngine.runDoctor(void 0, options.host);
4045
+ if (!report.isInitialized || report.agentsCount === 0 && report.skillsCount === 0 && report.workflowsCount === 0 && report.issues.length === 0) {
4046
+ console.log(` ${pc.bold("\u{1F4E6} Status:")} ${pc.yellow("No agents or bundles installed in this workspace.")}`);
4047
+ console.log(` ${pc.bold("\u{1F4CD} Location:")} ${pc.dim(report.targetDir)}
4048
+ `);
4049
+ console.log(pc.bold(pc.cyan("\u{1F4A1} Get Started:")));
4050
+ console.log(` \u{1F449} ${pc.bold("agents add")} Launch the interactive installation wizard`);
4051
+ console.log(` \u{1F449} ${pc.bold("agents add software-engineering")} Install the engineering essentials team`);
4052
+ console.log(` \u{1F449} ${pc.bold("agents list")} Browse all 18 bundles and 8 departments
4053
+ `);
4054
+ if (report.clineCapability) {
4055
+ console.log(pc.bold(pc.cyan("Cline Runtime & Compound Projection Audit:")));
4056
+ console.log(` Installed: ${report.clineCapability.installed ? pc.green("\u2714 Detected") : pc.yellow("\u2716 Not Found")}`);
4057
+ if (report.clineCapability.version) {
4058
+ console.log(` Version: ${report.clineCapability.version}`);
4059
+ }
4060
+ console.log(` Named Teams: ${report.clineCapability.namedTeams ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported (Adaptive fallback)")}
523
4061
  `);
4062
+ }
4063
+ if (report.warnings.length > 0) {
4064
+ console.log(pc.yellow(pc.bold("\u26A0\uFE0F Warnings:")));
4065
+ report.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
4066
+ console.log();
4067
+ }
4068
+ outro(pc.blue("\u2728 Workspace is ready for initialization."));
4069
+ return;
4070
+ }
4071
+ console.log(` \u{1F916} Installed Agents: ${pc.bold(report.agentsCount.toString())}`);
4072
+ console.log(` \u26A1 Installed Skills: ${pc.bold(report.skillsCount.toString())}`);
4073
+ console.log(` \u{1F504} Installed Workflows: ${pc.bold(report.workflowsCount.toString())}
4074
+ `);
4075
+ if (report.clineCapability) {
4076
+ console.log(pc.bold(pc.cyan("Cline Runtime & Compound Projection Audit:")));
4077
+ console.log(` Installed: ${report.clineCapability.installed ? pc.green("\u2714 Detected") : pc.yellow("\u2716 Not Found")}`);
4078
+ if (report.clineCapability.version) {
4079
+ console.log(` Version: ${report.clineCapability.version}`);
4080
+ }
4081
+ console.log(` Named Teams: ${report.clineCapability.namedTeams ? pc.green("\u2714 Supported") : pc.yellow("\u2716 Unsupported (Adaptive fallback)")}`);
4082
+ console.log(` Role Definitions: ${report.agentsCount} prepared (activation via "agents start")
4083
+ `);
4084
+ }
524
4085
  if (report.issues.length > 0) {
525
- console.log(pc.red(pc.bold("Issues Found:")));
4086
+ console.log(pc.red(pc.bold("\u274C Issues Found:")));
526
4087
  report.issues.forEach((i) => console.log(` \u2716 ${i}`));
4088
+ console.log();
527
4089
  }
528
4090
  if (report.warnings.length > 0) {
529
- console.log(pc.yellow(pc.bold("Warnings:")));
4091
+ console.log(pc.yellow(pc.bold("\u26A0\uFE0F Warnings:")));
530
4092
  report.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
4093
+ console.log();
531
4094
  }
532
4095
  if (report.valid && report.issues.length === 0) {
533
4096
  outro(pc.green("\u2714 All installed agents and frontmatter schemas are healthy!"));
@@ -539,4 +4102,7 @@ cli.command("doctor", "Verify health of installed agents, frontmatter schemas, a
539
4102
  cli.help();
540
4103
  cli.version("1.0.0");
541
4104
  cli.parse();
4105
+ export {
4106
+ detectWorkspaceHosts
4107
+ };
542
4108
  //# sourceMappingURL=cli.js.map