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