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