agents-anywhere 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,2550 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/version.ts
30
+ var version = "0.5.0";
31
+
32
+ // src/commands/init.ts
33
+ var fs4 = __toESM(require("fs"));
34
+ var import_node_path5 = __toESM(require("path"));
35
+ var import_node_os2 = __toESM(require("os"));
36
+ var import_node_child_process2 = require("child_process");
37
+ var import_select = __toESM(require("@inquirer/select"));
38
+ var import_confirm = __toESM(require("@inquirer/confirm"));
39
+ var import_simple_git = require("simple-git");
40
+
41
+ // src/core/detector.ts
42
+ var import_node_fs2 = require("fs");
43
+ var import_node_child_process = require("child_process");
44
+
45
+ // src/core/schema-loader.ts
46
+ var import_node_fs = require("fs");
47
+ var import_node_path = __toESM(require("path"));
48
+
49
+ // src/schemas/agent-definition-schema-data.ts
50
+ var agentDefinitionSchema = {
51
+ $schema: "http://json-schema.org/draft-07/schema#",
52
+ title: "AgentDefinition",
53
+ description: "Declarative agent definition for agents-anywhere. Each agent is defined by a JSON file matching this schema.",
54
+ type: "object",
55
+ required: [
56
+ "id",
57
+ "name",
58
+ "configDir",
59
+ "detect",
60
+ "portable",
61
+ "ignore",
62
+ "credentials",
63
+ "instructions",
64
+ "mcp"
65
+ ],
66
+ additionalProperties: false,
67
+ properties: {
68
+ id: {
69
+ type: "string",
70
+ description: "Unique identifier, used as directory name in the config repo"
71
+ },
72
+ name: {
73
+ type: "string",
74
+ description: "Human-readable display name"
75
+ },
76
+ configDir: {
77
+ $ref: "#/definitions/PlatformPaths",
78
+ description: "Per-platform config directory paths"
79
+ },
80
+ detect: {
81
+ $ref: "#/definitions/DetectRule",
82
+ description: "How to detect if the agent is installed"
83
+ },
84
+ portable: {
85
+ type: "array",
86
+ items: { type: "string" },
87
+ description: "Glob patterns for files to symlink"
88
+ },
89
+ ignore: {
90
+ type: "array",
91
+ items: { type: "string" },
92
+ description: "Glob patterns for files to never sync"
93
+ },
94
+ credentials: {
95
+ type: "array",
96
+ items: { type: "string" },
97
+ description: "Paths to credential files to warn about if found in repo"
98
+ },
99
+ instructions: {
100
+ $ref: "#/definitions/InstructionsConfig",
101
+ description: "Agent instructions file configuration"
102
+ },
103
+ mcp: {
104
+ $ref: "#/definitions/MCPConfig",
105
+ description: "MCP configuration for the agent"
106
+ }
107
+ },
108
+ definitions: {
109
+ PlatformPaths: {
110
+ type: "object",
111
+ required: ["darwin", "linux", "win32"],
112
+ additionalProperties: false,
113
+ properties: {
114
+ darwin: { type: "string" },
115
+ linux: { type: "string" },
116
+ win32: { type: "string" }
117
+ }
118
+ },
119
+ DetectRule: {
120
+ type: "object",
121
+ required: ["type"],
122
+ additionalProperties: false,
123
+ properties: {
124
+ type: {
125
+ type: "string",
126
+ enum: ["directory-exists", "command-exists"]
127
+ },
128
+ path: { type: "string" },
129
+ command: { type: "string" }
130
+ }
131
+ },
132
+ InstructionsConfig: {
133
+ type: "object",
134
+ required: ["filename", "globalPath", "globalSupport"],
135
+ additionalProperties: false,
136
+ properties: {
137
+ filename: { type: "string" },
138
+ globalPath: { type: "string" },
139
+ globalSupport: {
140
+ type: "boolean",
141
+ description: "Whether this agent supports global (user-level) instructions"
142
+ }
143
+ }
144
+ },
145
+ TransportConfig: {
146
+ type: "object",
147
+ additionalProperties: false,
148
+ properties: {
149
+ typeField: { type: "string" },
150
+ typeValue: { type: "string" },
151
+ urlKey: { type: "string" }
152
+ }
153
+ },
154
+ MCPConfig: {
155
+ type: "object",
156
+ required: [
157
+ "configPath",
158
+ "scope",
159
+ "rootKey",
160
+ "writeMode",
161
+ "envSyntax",
162
+ "transports",
163
+ "commandType",
164
+ "envKey"
165
+ ],
166
+ additionalProperties: false,
167
+ properties: {
168
+ configPath: {
169
+ type: "string",
170
+ description: "Path relative to configDir"
171
+ },
172
+ scope: {
173
+ type: "string",
174
+ enum: ["project-and-user", "user", "project"]
175
+ },
176
+ rootKey: {
177
+ type: "string",
178
+ description: "Top-level key wrapping server entries"
179
+ },
180
+ format: {
181
+ type: "string",
182
+ enum: ["json", "toml"]
183
+ },
184
+ writeMode: {
185
+ type: "string",
186
+ enum: ["standalone", "merge"]
187
+ },
188
+ envSyntax: {
189
+ type: "string",
190
+ description: "Template for env var substitution \u2014 VAR is replaced with env var name"
191
+ },
192
+ defaultSyntax: {
193
+ type: "string",
194
+ description: "Fallback env syntax with default value support"
195
+ },
196
+ transports: {
197
+ type: "object",
198
+ properties: {
199
+ stdio: { $ref: "#/definitions/TransportConfig" },
200
+ http: { $ref: "#/definitions/TransportConfig" }
201
+ },
202
+ additionalProperties: false
203
+ },
204
+ commandType: {
205
+ type: "string",
206
+ enum: ["string", "array"]
207
+ },
208
+ envKey: {
209
+ type: "string",
210
+ description: "Key name for environment variables object"
211
+ },
212
+ serverSection: {
213
+ type: "string",
214
+ description: "TOML section key for reading existing servers"
215
+ },
216
+ envVarStyle: {
217
+ type: "string",
218
+ enum: ["inline", "named"]
219
+ }
220
+ }
221
+ }
222
+ }
223
+ };
224
+
225
+ // src/utils/output.ts
226
+ var RESET = "\x1B[0m";
227
+ var BOLD = "\x1B[1m";
228
+ var DIM = "\x1B[2m";
229
+ var RED = "\x1B[31m";
230
+ var GREEN = "\x1B[32m";
231
+ var YELLOW = "\x1B[33m";
232
+ var BLUE = "\x1B[34m";
233
+ var CYAN = "\x1B[36m";
234
+ var _verbose = false;
235
+ function setVerbose(v) {
236
+ _verbose = v;
237
+ }
238
+ function isVerbose() {
239
+ return _verbose || process.env.AGENTS_ANYWHERE_VERBOSE !== void 0;
240
+ }
241
+ function useColor() {
242
+ if (process.env.NO_COLOR !== void 0) return false;
243
+ if (process.env.FORCE_COLOR !== void 0) return true;
244
+ return process.stdout.isTTY === true;
245
+ }
246
+ function wrap(code, msg) {
247
+ return useColor() ? `${code}${msg}${RESET}` : msg;
248
+ }
249
+ function info(msg) {
250
+ const prefix = useColor() ? `${BLUE}\u2139${RESET}` : "\u2139";
251
+ console.log(`${prefix} ${msg}`);
252
+ }
253
+ function success(msg) {
254
+ const prefix = useColor() ? `${GREEN}\u2713${RESET}` : "\u2713";
255
+ console.log(`${prefix} ${msg}`);
256
+ }
257
+ function warn(msg) {
258
+ const prefix = useColor() ? `${YELLOW}\u26A0${RESET}` : "\u26A0";
259
+ console.log(`${prefix} ${msg}`);
260
+ }
261
+ function error(msg) {
262
+ const prefix = useColor() ? `${RED}\u2717${RESET}` : "\u2717";
263
+ console.error(`${prefix} ${msg}`);
264
+ }
265
+ function debug(msg) {
266
+ if (!isVerbose()) return;
267
+ const prefix = useColor() ? `${DIM}[debug]${RESET}` : "[debug]";
268
+ console.error(`${prefix} ${msg}`);
269
+ }
270
+ function heading(msg) {
271
+ console.log(`
272
+ ${wrap(BOLD, msg)}`);
273
+ }
274
+ function dim(msg) {
275
+ return wrap(DIM, msg);
276
+ }
277
+ function bold(msg) {
278
+ return wrap(BOLD, msg);
279
+ }
280
+ function green(msg) {
281
+ return wrap(GREEN, msg);
282
+ }
283
+ function yellow(msg) {
284
+ return wrap(YELLOW, msg);
285
+ }
286
+ function red(msg) {
287
+ return wrap(RED, msg);
288
+ }
289
+ function cyan(msg) {
290
+ return wrap(CYAN, msg);
291
+ }
292
+ function table(rows, indent = 2) {
293
+ if (rows.length === 0) return;
294
+ const maxKey = Math.max(...rows.map(([k]) => k.length));
295
+ const pad = " ".repeat(indent);
296
+ for (const [key, value] of rows) {
297
+ console.log(`${pad}${key.padEnd(maxKey)} ${value}`);
298
+ }
299
+ }
300
+ function statusBadge(status) {
301
+ switch (status) {
302
+ case "linked":
303
+ return green("linked");
304
+ case "unlinked":
305
+ return dim("unlinked");
306
+ case "diverged":
307
+ return yellow("diverged");
308
+ case "missing":
309
+ return dim("missing");
310
+ case "installed":
311
+ return green("installed");
312
+ case "not installed":
313
+ return dim("not installed");
314
+ default:
315
+ return status;
316
+ }
317
+ }
318
+
319
+ // src/core/schema-loader.ts
320
+ function getAgentsDir() {
321
+ const candidates = [
322
+ import_node_path.default.resolve(__dirname, "../../agents"),
323
+ import_node_path.default.resolve(__dirname, "../agents"),
324
+ import_node_path.default.resolve(__dirname, "agents")
325
+ ];
326
+ for (const candidate of candidates) {
327
+ try {
328
+ (0, import_node_fs.readdirSync)(candidate);
329
+ return candidate;
330
+ } catch (err) {
331
+ debug(`Agents dir candidate ${candidate} not accessible: ${err.message}`);
332
+ }
333
+ }
334
+ throw new Error(
335
+ "Could not find agents/ directory. Ensure agent definition JSON files are bundled."
336
+ );
337
+ }
338
+ function resolveRef(ref, root) {
339
+ const parts = ref.replace(/^#\//, "").split("/");
340
+ let current = root;
341
+ for (const part of parts) {
342
+ if (current && typeof current === "object" && part in current) {
343
+ current = current[part];
344
+ } else {
345
+ throw new Error(`Cannot resolve $ref: ${ref}`);
346
+ }
347
+ }
348
+ return current;
349
+ }
350
+ function validateNode(value, schema, root, currentPath) {
351
+ const errors = [];
352
+ let resolved = schema;
353
+ if (schema.$ref) {
354
+ resolved = { ...resolveRef(schema.$ref, root) };
355
+ }
356
+ if (resolved.type) {
357
+ const actualType = getJsonType(value);
358
+ if (actualType !== resolved.type) {
359
+ errors.push({
360
+ path: currentPath,
361
+ message: `Expected type "${resolved.type}", got "${actualType}"`
362
+ });
363
+ return errors;
364
+ }
365
+ }
366
+ if (resolved.enum && typeof value === "string") {
367
+ if (!resolved.enum.includes(value)) {
368
+ errors.push({
369
+ path: currentPath,
370
+ message: `Invalid value "${value}". Must be one of: ${resolved.enum.join(", ")}`
371
+ });
372
+ }
373
+ }
374
+ if (resolved.type === "object" && typeof value === "object" && value !== null) {
375
+ const obj = value;
376
+ if (resolved.required) {
377
+ for (const field of resolved.required) {
378
+ if (obj[field] === void 0 || obj[field] === null) {
379
+ errors.push({
380
+ path: currentPath ? `${currentPath}.${field}` : field,
381
+ message: `Missing required field "${field}"`
382
+ });
383
+ }
384
+ }
385
+ }
386
+ if (resolved.properties) {
387
+ for (const [key, propSchema] of Object.entries(resolved.properties)) {
388
+ if (obj[key] !== void 0 && obj[key] !== null) {
389
+ const propPath = currentPath ? `${currentPath}.${key}` : key;
390
+ errors.push(...validateNode(obj[key], propSchema, root, propPath));
391
+ }
392
+ }
393
+ }
394
+ if (resolved.additionalProperties === false && resolved.properties) {
395
+ const allowedKeys = new Set(Object.keys(resolved.properties));
396
+ for (const key of Object.keys(obj)) {
397
+ if (!allowedKeys.has(key)) {
398
+ errors.push({
399
+ path: currentPath ? `${currentPath}.${key}` : key,
400
+ message: `Unknown property "${key}" is not allowed`
401
+ });
402
+ }
403
+ }
404
+ }
405
+ }
406
+ if (resolved.type === "array" && Array.isArray(value)) {
407
+ if (resolved.items) {
408
+ for (let i = 0; i < value.length; i++) {
409
+ errors.push(
410
+ ...validateNode(value[i], resolved.items, root, `${currentPath}[${i}]`)
411
+ );
412
+ }
413
+ }
414
+ }
415
+ return errors;
416
+ }
417
+ function getJsonType(value) {
418
+ if (value === null) return "null";
419
+ if (Array.isArray(value)) return "array";
420
+ return typeof value;
421
+ }
422
+ function validateAgainstSchema(data) {
423
+ const schema = agentDefinitionSchema;
424
+ const errors = validateNode(data, schema, schema, "");
425
+ return { valid: errors.length === 0, errors };
426
+ }
427
+ function loadAgentDefinition(filePath) {
428
+ const raw = (0, import_node_fs.readFileSync)(filePath, "utf-8");
429
+ const parsed = JSON.parse(raw);
430
+ const result = validateAgainstSchema(parsed);
431
+ if (!result.valid) {
432
+ const messages = result.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
433
+ throw new Error(
434
+ `Agent definition ${filePath} failed validation:
435
+ ${messages}`
436
+ );
437
+ }
438
+ return parsed;
439
+ }
440
+ var _cache = null;
441
+ function loadAllAgentDefinitions() {
442
+ if (_cache) return _cache;
443
+ const agentsDir = getAgentsDir();
444
+ const files = (0, import_node_fs.readdirSync)(agentsDir).filter((f) => f.endsWith(".json"));
445
+ _cache = files.map((file) => loadAgentDefinition(import_node_path.default.join(agentsDir, file)));
446
+ return _cache;
447
+ }
448
+ function loadAgentById(id) {
449
+ const all = loadAllAgentDefinitions();
450
+ return all.find((agent) => agent.id === id);
451
+ }
452
+
453
+ // src/utils/paths.ts
454
+ var import_node_path2 = __toESM(require("path"));
455
+ var import_node_os = __toESM(require("os"));
456
+ function expandPath(p) {
457
+ if (p.startsWith("~")) {
458
+ return import_node_path2.default.join(import_node_os.default.homedir(), p.slice(1));
459
+ }
460
+ if (p.startsWith("%APPDATA%")) {
461
+ if (process.env.APPDATA) {
462
+ return import_node_path2.default.join(process.env.APPDATA, p.slice("%APPDATA%".length));
463
+ }
464
+ if (process.platform === "win32") {
465
+ throw new Error("APPDATA environment variable is not set");
466
+ }
467
+ }
468
+ return p;
469
+ }
470
+ function getPlatformPath(paths) {
471
+ const platform = process.platform;
472
+ if (platform === "darwin" || platform === "linux" || platform === "win32") {
473
+ return paths[platform];
474
+ }
475
+ warn(`Unsupported platform "${platform}", falling back to Linux paths`);
476
+ return paths.linux;
477
+ }
478
+
479
+ // src/core/detector.ts
480
+ function detectAgents() {
481
+ const definitions = loadAllAgentDefinitions();
482
+ return definitions.map((def) => detectSingleAgent(def));
483
+ }
484
+ function detectSingleAgent(definition) {
485
+ const configDir = expandPath(getPlatformPath(definition.configDir));
486
+ const installed = checkDetectRule(definition);
487
+ return { definition, configDir, installed };
488
+ }
489
+ function checkDetectRule(definition) {
490
+ const { detect } = definition;
491
+ switch (detect.type) {
492
+ case "directory-exists":
493
+ return (0, import_node_fs2.existsSync)(expandPath(detect.path));
494
+ case "command-exists":
495
+ return commandExists(detect.command);
496
+ default:
497
+ return false;
498
+ }
499
+ }
500
+ function commandExists(command) {
501
+ try {
502
+ const cmd = process.platform === "win32" ? "where" : "which";
503
+ (0, import_node_child_process.execFileSync)(cmd, [command], { stdio: "ignore" });
504
+ return true;
505
+ } catch {
506
+ debug(`command-exists: "${command}" not found in PATH`);
507
+ return false;
508
+ }
509
+ }
510
+
511
+ // src/core/linker.ts
512
+ var import_node_fs3 = require("fs");
513
+ var import_node_path3 = __toESM(require("path"));
514
+ function resolvePaths(agentDef, repoDir) {
515
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
516
+ const agentRepoDir = import_node_path3.default.join(repoDir, agentDef.id);
517
+ return { configDir, agentRepoDir };
518
+ }
519
+ function getPortableItems(agentDef) {
520
+ const items = /* @__PURE__ */ new Set();
521
+ for (const pattern of agentDef.portable) {
522
+ const topLevel = pattern.split("/")[0];
523
+ items.add(topLevel);
524
+ }
525
+ return [...items];
526
+ }
527
+ function backupTimestamp() {
528
+ return Date.now().toString();
529
+ }
530
+ function linkAgent(agentDef, repoDir, dryRun = false) {
531
+ const { configDir, agentRepoDir } = resolvePaths(agentDef, repoDir);
532
+ const items = getPortableItems(agentDef);
533
+ const results = [];
534
+ if (!dryRun) {
535
+ (0, import_node_fs3.mkdirSync)(configDir, { recursive: true });
536
+ }
537
+ for (const item of items) {
538
+ const agentPath = import_node_path3.default.join(configDir, item);
539
+ const repoPath = import_node_path3.default.join(agentRepoDir, item);
540
+ if (!(0, import_node_fs3.existsSync)(repoPath)) {
541
+ continue;
542
+ }
543
+ if ((0, import_node_fs3.existsSync)(agentPath) || lstatExists(agentPath)) {
544
+ if (isSymlinkTo(agentPath, repoPath)) {
545
+ results.push({ item, action: "skipped", agentPath });
546
+ continue;
547
+ }
548
+ if (!dryRun) {
549
+ const backupPath = `${agentPath}.backup.${backupTimestamp()}`;
550
+ (0, import_node_fs3.renameSync)(agentPath, backupPath);
551
+ (0, import_node_fs3.symlinkSync)(repoPath, agentPath);
552
+ }
553
+ results.push({ item, action: "backed-up-and-linked", agentPath });
554
+ continue;
555
+ }
556
+ if (!dryRun) {
557
+ (0, import_node_fs3.symlinkSync)(repoPath, agentPath);
558
+ }
559
+ results.push({ item, action: "linked", agentPath });
560
+ }
561
+ return results;
562
+ }
563
+ function unlinkAgent(agentDef, repoDir, dryRun = false) {
564
+ const { configDir, agentRepoDir } = resolvePaths(agentDef, repoDir);
565
+ const items = getPortableItems(agentDef);
566
+ const results = [];
567
+ for (const item of items) {
568
+ const agentPath = import_node_path3.default.join(configDir, item);
569
+ const repoPath = import_node_path3.default.join(agentRepoDir, item);
570
+ if (!lstatExists(agentPath) || !isSymlinkTo(agentPath, repoPath)) {
571
+ results.push({ item, action: "skipped", agentPath });
572
+ continue;
573
+ }
574
+ if (!dryRun) {
575
+ (0, import_node_fs3.unlinkSync)(agentPath);
576
+ }
577
+ const backup = findMostRecentBackup(configDir, item);
578
+ if (backup) {
579
+ if (!dryRun) {
580
+ (0, import_node_fs3.renameSync)(import_node_path3.default.join(configDir, backup), agentPath);
581
+ }
582
+ results.push({ item, action: "restored", agentPath });
583
+ } else {
584
+ results.push({ item, action: "unlinked", agentPath });
585
+ }
586
+ }
587
+ return results;
588
+ }
589
+ function getStatus(agentDef, repoDir) {
590
+ const { configDir, agentRepoDir } = resolvePaths(agentDef, repoDir);
591
+ const items = getPortableItems(agentDef);
592
+ const statuses = [];
593
+ for (const item of items) {
594
+ const agentPath = import_node_path3.default.join(configDir, item);
595
+ const repoPath = import_node_path3.default.join(agentRepoDir, item);
596
+ let status;
597
+ if (!(0, import_node_fs3.existsSync)(repoPath) && !lstatExists(agentPath)) {
598
+ status = "missing";
599
+ } else if (isSymlinkTo(agentPath, repoPath)) {
600
+ status = "linked";
601
+ } else if (lstatExists(agentPath)) {
602
+ status = "diverged";
603
+ } else {
604
+ status = "unlinked";
605
+ }
606
+ statuses.push({ item, status, agentPath, repoPath });
607
+ }
608
+ return statuses;
609
+ }
610
+ function lstatExists(p) {
611
+ try {
612
+ (0, import_node_fs3.lstatSync)(p);
613
+ return true;
614
+ } catch (err) {
615
+ debug(`lstat failed for ${p}: ${err.message}`);
616
+ return false;
617
+ }
618
+ }
619
+ function isSymlinkTo(linkPath, expectedTarget) {
620
+ try {
621
+ const stat = (0, import_node_fs3.lstatSync)(linkPath);
622
+ if (!stat.isSymbolicLink()) return false;
623
+ const target = (0, import_node_fs3.readlinkSync)(linkPath);
624
+ return import_node_path3.default.resolve(import_node_path3.default.dirname(linkPath), target) === import_node_path3.default.resolve(expectedTarget);
625
+ } catch (err) {
626
+ debug(`isSymlinkTo check failed for ${linkPath}: ${err.message}`);
627
+ return false;
628
+ }
629
+ }
630
+ function findMostRecentBackup(dir, item) {
631
+ try {
632
+ const entries = (0, import_node_fs3.readdirSync)(dir);
633
+ const prefix = `${item}.backup.`;
634
+ const backups = entries.filter((e) => e.startsWith(prefix)).sort().reverse();
635
+ return backups[0];
636
+ } catch (err) {
637
+ debug(`findMostRecentBackup readdir failed for ${dir}/${item}: ${err.message}`);
638
+ return void 0;
639
+ }
640
+ }
641
+
642
+ // src/mcp/importer.ts
643
+ var fs = __toESM(require("fs"));
644
+ var import_node_path4 = __toESM(require("path"));
645
+ var TOML = __toESM(require("smol-toml"));
646
+ function importFromAgent(agentDef) {
647
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
648
+ const configPath = import_node_path4.default.join(configDir, agentDef.mcp.configPath);
649
+ if (!fs.existsSync(configPath)) return null;
650
+ const nativeServers = readNativeConfig(configPath, agentDef.mcp);
651
+ if (!nativeServers) return null;
652
+ const servers = {};
653
+ for (const [name, serverData] of Object.entries(nativeServers)) {
654
+ const normalized = reverseTransformServer(
655
+ serverData,
656
+ agentDef.mcp
657
+ );
658
+ if (normalized) {
659
+ servers[name] = normalized;
660
+ }
661
+ }
662
+ if (Object.keys(servers).length === 0) return null;
663
+ return {
664
+ agentId: agentDef.id,
665
+ agentName: agentDef.name,
666
+ servers,
667
+ sourcePath: configPath
668
+ };
669
+ }
670
+ function importAndMergeAll(agents) {
671
+ const allImports = [];
672
+ for (const agent of agents) {
673
+ const result = importFromAgent(agent);
674
+ if (result) {
675
+ allImports.push(result);
676
+ }
677
+ }
678
+ const merged = {};
679
+ const sources = {};
680
+ const conflictAgents = {};
681
+ for (const imp of allImports) {
682
+ for (const [name, server] of Object.entries(imp.servers)) {
683
+ if (merged[name]) {
684
+ if (!conflictAgents[name]) {
685
+ conflictAgents[name] = [sources[name]];
686
+ }
687
+ conflictAgents[name].push(imp.agentId);
688
+ const existingScore = serverScore(merged[name]);
689
+ const newScore = serverScore(server);
690
+ if (newScore > existingScore) {
691
+ merged[name] = server;
692
+ sources[name] = imp.agentId;
693
+ }
694
+ } else {
695
+ merged[name] = server;
696
+ sources[name] = imp.agentId;
697
+ }
698
+ }
699
+ }
700
+ const conflicts = [];
701
+ for (const [serverName, agents2] of Object.entries(conflictAgents)) {
702
+ conflicts.push({ serverName, agents: agents2, kept: sources[serverName] });
703
+ }
704
+ return { config: { servers: merged }, sources, conflicts };
705
+ }
706
+ function readNativeConfig(filePath, mcp2) {
707
+ try {
708
+ const raw = fs.readFileSync(filePath, "utf-8");
709
+ let parsed;
710
+ if (mcp2.format === "toml") {
711
+ parsed = TOML.parse(raw);
712
+ } else {
713
+ parsed = JSON.parse(raw);
714
+ }
715
+ const servers = parsed[mcp2.rootKey];
716
+ if (!servers || typeof servers !== "object") return null;
717
+ return servers;
718
+ } catch (err) {
719
+ debug(`Failed to parse native config ${filePath}: ${err.message}`);
720
+ return null;
721
+ }
722
+ }
723
+ function reverseTransformServer(serverData, mcp2) {
724
+ const transport = detectTransport(serverData, mcp2);
725
+ if (!transport) return null;
726
+ const server = { transport };
727
+ if (transport === "stdio") {
728
+ reverseStdioFields(server, serverData, mcp2);
729
+ } else {
730
+ reverseHttpFields(server, serverData, mcp2);
731
+ }
732
+ if (mcp2.envVarStyle === "named") {
733
+ const envVars = serverData[mcp2.envKey];
734
+ if (Array.isArray(envVars)) {
735
+ server.env = reverseNamedEnvVars(envVars);
736
+ }
737
+ const bearerVar = serverData.bearer_token_env_var;
738
+ if (typeof bearerVar === "string") {
739
+ server.headers = {
740
+ Authorization: { $env: bearerVar, prefix: "Bearer " }
741
+ };
742
+ }
743
+ } else {
744
+ const envObj = serverData[mcp2.envKey];
745
+ if (envObj && typeof envObj === "object" && !Array.isArray(envObj)) {
746
+ const reversed = reverseInlineEnvVars(
747
+ envObj,
748
+ mcp2.envSyntax
749
+ );
750
+ if (Object.keys(reversed).length > 0) {
751
+ server.env = reversed;
752
+ }
753
+ }
754
+ const headers = serverData.headers;
755
+ if (headers && typeof headers === "object" && !Array.isArray(headers)) {
756
+ const reversedHeaders = reverseInlineHeaders(
757
+ headers,
758
+ mcp2.envSyntax
759
+ );
760
+ if (Object.keys(reversedHeaders).length > 0) {
761
+ server.headers = reversedHeaders;
762
+ }
763
+ }
764
+ }
765
+ return server;
766
+ }
767
+ function detectTransport(serverData, mcp2) {
768
+ for (const [transportType, config] of Object.entries(mcp2.transports)) {
769
+ if (!config?.typeField) continue;
770
+ const value = serverData[config.typeField];
771
+ if (value === config.typeValue) {
772
+ return transportType;
773
+ }
774
+ }
775
+ if (serverData.command) return "stdio";
776
+ const httpConfig = mcp2.transports.http;
777
+ const urlKey = httpConfig?.urlKey ?? "url";
778
+ if (serverData[urlKey] || serverData.url) return "http";
779
+ return null;
780
+ }
781
+ function reverseStdioFields(server, serverData, mcp2) {
782
+ if (mcp2.commandType === "array" && Array.isArray(serverData.command)) {
783
+ const arr = serverData.command;
784
+ server.command = arr[0];
785
+ if (arr.length > 1) {
786
+ server.args = arr.slice(1);
787
+ }
788
+ } else {
789
+ if (typeof serverData.command === "string") {
790
+ server.command = serverData.command;
791
+ }
792
+ if (Array.isArray(serverData.args) && serverData.args.length > 0) {
793
+ server.args = serverData.args;
794
+ }
795
+ }
796
+ }
797
+ function reverseHttpFields(server, serverData, mcp2) {
798
+ const httpConfig = mcp2.transports.http;
799
+ const urlKey = httpConfig?.urlKey ?? "url";
800
+ const url = serverData[urlKey] ?? serverData.url;
801
+ if (typeof url === "string") {
802
+ server.url = url;
803
+ }
804
+ }
805
+ function extractEnvVarName(value, envSyntax) {
806
+ const parts = envSyntax.split("VAR");
807
+ if (parts.length !== 2) return null;
808
+ const [prefix, suffix] = parts;
809
+ if (value.startsWith(prefix) && value.endsWith(suffix)) {
810
+ const varName = value.slice(
811
+ prefix.length,
812
+ suffix.length > 0 ? value.length - suffix.length : void 0
813
+ );
814
+ if (varName.length > 0) return varName;
815
+ }
816
+ return null;
817
+ }
818
+ function reverseEnvRef(value, envSyntax) {
819
+ const direct = extractEnvVarName(value, envSyntax);
820
+ if (direct) return { $env: direct };
821
+ const parts = envSyntax.split("VAR");
822
+ if (parts.length !== 2) return null;
823
+ const [syntaxPrefix, syntaxSuffix] = parts;
824
+ const idx = value.indexOf(syntaxPrefix);
825
+ if (idx === -1) return null;
826
+ const beforeSyntax = value.slice(0, idx);
827
+ const afterPrefix = value.slice(idx + syntaxPrefix.length);
828
+ let varName;
829
+ if (syntaxSuffix.length > 0) {
830
+ const suffixIdx = afterPrefix.indexOf(syntaxSuffix);
831
+ if (suffixIdx === -1) return null;
832
+ varName = afterPrefix.slice(0, suffixIdx);
833
+ } else {
834
+ varName = afterPrefix;
835
+ }
836
+ if (varName.length === 0) return null;
837
+ const ref = { $env: varName };
838
+ if (beforeSyntax) {
839
+ ref.prefix = beforeSyntax;
840
+ }
841
+ return ref;
842
+ }
843
+ function reverseNamedEnvVars(envVars) {
844
+ const result = {};
845
+ for (const name of envVars) {
846
+ result[name] = { $env: name };
847
+ }
848
+ return result;
849
+ }
850
+ function reverseInlineEnvVars(env, envSyntax) {
851
+ const result = {};
852
+ for (const [key, value] of Object.entries(env)) {
853
+ if (typeof value !== "string") continue;
854
+ const ref = reverseEnvRef(value, envSyntax);
855
+ if (ref) {
856
+ result[key] = ref;
857
+ }
858
+ }
859
+ return result;
860
+ }
861
+ function reverseInlineHeaders(headers, envSyntax) {
862
+ const result = {};
863
+ for (const [key, value] of Object.entries(headers)) {
864
+ if (typeof value !== "string") continue;
865
+ const ref = reverseEnvRef(value, envSyntax);
866
+ if (ref) {
867
+ result[key] = ref;
868
+ }
869
+ }
870
+ return result;
871
+ }
872
+ function serverScore(server) {
873
+ let score = 0;
874
+ if (server.command) score++;
875
+ if (server.url) score++;
876
+ if (server.args) score += server.args.length;
877
+ if (server.env) score += Object.keys(server.env).length;
878
+ if (server.headers) score += Object.keys(server.headers).length;
879
+ return score;
880
+ }
881
+
882
+ // src/mcp/parser.ts
883
+ var fs2 = __toESM(require("fs"));
884
+ function parseMCPConfig(filePath) {
885
+ const raw = fs2.readFileSync(filePath, "utf-8");
886
+ return parseMCPConfigFromString(raw);
887
+ }
888
+ function parseMCPConfigFromString(content) {
889
+ const parsed = JSON.parse(content);
890
+ if (!parsed.servers || typeof parsed.servers !== "object") {
891
+ throw new Error('mcp.json must have a "servers" object at the top level');
892
+ }
893
+ for (const [name, server] of Object.entries(parsed.servers)) {
894
+ validateServer(name, server);
895
+ }
896
+ return parsed;
897
+ }
898
+ function validateServer(name, server) {
899
+ if (!server.transport) {
900
+ throw new Error(`Server "${name}" must specify a "transport" (stdio or http)`);
901
+ }
902
+ if (server.transport !== "stdio" && server.transport !== "http") {
903
+ throw new Error(`Server "${name}" has invalid transport "${server.transport}" (must be stdio or http)`);
904
+ }
905
+ if (server.transport === "stdio" && !server.command) {
906
+ throw new Error(`Server "${name}" with stdio transport must specify a "command"`);
907
+ }
908
+ if (server.transport === "http" && !server.url) {
909
+ throw new Error(`Server "${name}" with http transport must specify a "url"`);
910
+ }
911
+ if (server.env) {
912
+ for (const [key, ref] of Object.entries(server.env)) {
913
+ if (!ref.$env || typeof ref.$env !== "string") {
914
+ throw new Error(`Server "${name}" env var "${key}" must have a "$env" string`);
915
+ }
916
+ }
917
+ }
918
+ if (server.headers) {
919
+ for (const [key, ref] of Object.entries(server.headers)) {
920
+ if (!ref.$env || typeof ref.$env !== "string") {
921
+ throw new Error(`Server "${name}" header "${key}" must have a "$env" string`);
922
+ }
923
+ }
924
+ }
925
+ }
926
+
927
+ // src/mcp/transformer.ts
928
+ function transformForAgent(config, agentDef) {
929
+ const mcp2 = agentDef.mcp;
930
+ const servers = {};
931
+ for (const [name, server] of Object.entries(config.servers)) {
932
+ servers[name] = transformServer(server, mcp2, name, agentDef.name);
933
+ }
934
+ return {
935
+ rootKey: mcp2.rootKey,
936
+ servers,
937
+ format: mcp2.format ?? "json"
938
+ };
939
+ }
940
+ function transformServer(server, mcp2, serverName, agentName) {
941
+ if (!mcp2.transports[server.transport]) {
942
+ warn(
943
+ `Agent "${agentName}" does not define transport "${server.transport}" \u2014 using defaults for server "${serverName}"`
944
+ );
945
+ }
946
+ if (mcp2.envVarStyle === "named") {
947
+ return transformServerNamed(server, mcp2);
948
+ }
949
+ return transformServerInline(server, mcp2);
950
+ }
951
+ function transformServerInline(server, mcp2) {
952
+ const result = {};
953
+ const transportDef = mcp2.transports[server.transport];
954
+ if (transportDef?.typeField && transportDef.typeValue) {
955
+ result[transportDef.typeField] = transportDef.typeValue;
956
+ }
957
+ if (server.transport === "stdio") {
958
+ if (mcp2.commandType === "array") {
959
+ result.command = [server.command, ...server.args ?? []];
960
+ } else {
961
+ result.command = server.command;
962
+ if (server.args?.length) {
963
+ result.args = server.args;
964
+ }
965
+ }
966
+ } else if (server.transport === "http") {
967
+ const urlKey = transportDef?.urlKey ?? "url";
968
+ result[urlKey] = server.url;
969
+ }
970
+ if (server.env && Object.keys(server.env).length > 0) {
971
+ const env = {};
972
+ for (const [key, ref] of Object.entries(server.env)) {
973
+ env[key] = resolveEnvRef(ref, mcp2.envSyntax);
974
+ }
975
+ result[mcp2.envKey] = env;
976
+ }
977
+ if (server.headers && Object.keys(server.headers).length > 0) {
978
+ const headers = {};
979
+ for (const [key, ref] of Object.entries(server.headers)) {
980
+ headers[key] = resolveEnvRef(ref, mcp2.envSyntax);
981
+ }
982
+ result.headers = headers;
983
+ }
984
+ return result;
985
+ }
986
+ function transformServerNamed(server, mcp2) {
987
+ const result = {};
988
+ const transportDef = mcp2.transports[server.transport];
989
+ if (transportDef?.typeField && transportDef.typeValue) {
990
+ result[transportDef.typeField] = transportDef.typeValue;
991
+ }
992
+ if (server.transport === "stdio") {
993
+ result.command = server.command;
994
+ if (server.args?.length) {
995
+ result.args = server.args;
996
+ }
997
+ } else if (server.transport === "http") {
998
+ const urlKey = transportDef?.urlKey ?? "url";
999
+ result[urlKey] = server.url;
1000
+ }
1001
+ if (server.env && Object.keys(server.env).length > 0) {
1002
+ result[mcp2.envKey] = Object.values(server.env).map((ref) => ref.$env);
1003
+ }
1004
+ if (server.headers) {
1005
+ for (const [, ref] of Object.entries(server.headers)) {
1006
+ if (ref.prefix?.toLowerCase().startsWith("bearer")) {
1007
+ result.bearer_token_env_var = ref.$env;
1008
+ }
1009
+ }
1010
+ }
1011
+ return result;
1012
+ }
1013
+ function resolveEnvRef(ref, syntax) {
1014
+ if (!syntax.includes("VAR")) {
1015
+ throw new Error(`envSyntax "${syntax}" does not contain placeholder "VAR"`);
1016
+ }
1017
+ const resolved = syntax.replace("VAR", ref.$env);
1018
+ if (ref.prefix) {
1019
+ return ref.prefix + resolved;
1020
+ }
1021
+ return resolved;
1022
+ }
1023
+
1024
+ // src/mcp/writer.ts
1025
+ var fs3 = __toESM(require("fs"));
1026
+ var path5 = __toESM(require("path"));
1027
+ var TOML2 = __toESM(require("smol-toml"));
1028
+ function ensureDir(filePath) {
1029
+ const dir = path5.dirname(filePath);
1030
+ if (!fs3.existsSync(dir)) {
1031
+ fs3.mkdirSync(dir, { recursive: true });
1032
+ }
1033
+ }
1034
+ function writeJSON(filePath, rootKey, servers) {
1035
+ ensureDir(filePath);
1036
+ const content = { [rootKey]: servers };
1037
+ fs3.writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n", "utf-8");
1038
+ }
1039
+ function mergeJSON(filePath, key, servers) {
1040
+ ensureDir(filePath);
1041
+ let existing = {};
1042
+ if (fs3.existsSync(filePath)) {
1043
+ const raw = fs3.readFileSync(filePath, "utf-8");
1044
+ try {
1045
+ existing = JSON.parse(raw);
1046
+ } catch {
1047
+ throw new Error(`Invalid JSON in ${filePath} \u2014 fix or delete the file before syncing`);
1048
+ }
1049
+ }
1050
+ existing[key] = servers;
1051
+ fs3.writeFileSync(
1052
+ filePath,
1053
+ JSON.stringify(existing, null, 2) + "\n",
1054
+ "utf-8"
1055
+ );
1056
+ }
1057
+ function writeTOML(filePath, rootKey, servers) {
1058
+ ensureDir(filePath);
1059
+ let existing = {};
1060
+ if (fs3.existsSync(filePath)) {
1061
+ const raw = fs3.readFileSync(filePath, "utf-8");
1062
+ existing = TOML2.parse(raw);
1063
+ }
1064
+ existing[rootKey] = servers;
1065
+ fs3.writeFileSync(filePath, TOML2.stringify(existing) + "\n", "utf-8");
1066
+ }
1067
+
1068
+ // src/commands/init.ts
1069
+ var DEFAULT_REPO_DIR = import_node_path5.default.join(import_node_os2.default.homedir(), "agents-anywhere-config");
1070
+ var POST_MERGE_HOOK = `#!/bin/sh
1071
+ # agents-anywhere post-merge hook \u2014 re-link configs and regenerate MCP on pull
1072
+ agents-anywhere link
1073
+ agents-anywhere mcp sync
1074
+ `;
1075
+ var GITIGNORE = `# agents-anywhere generated
1076
+ *.backup.*
1077
+
1078
+ # OS files
1079
+ .DS_Store
1080
+ Thumbs.db
1081
+ `;
1082
+ async function initCommand(repoDir, options) {
1083
+ const targetDir = repoDir ?? DEFAULT_REPO_DIR;
1084
+ if (options?.from) {
1085
+ await initFromRemote(options.from, targetDir);
1086
+ return;
1087
+ }
1088
+ heading("Detecting installed AI coding agents...");
1089
+ const agents = detectAgents();
1090
+ const installed = agents.filter((a) => a.installed);
1091
+ if (installed.length === 0) {
1092
+ warn("No AI coding agents detected.");
1093
+ info(
1094
+ "Supported agents: " + agents.map((a) => a.definition.name).join(", ")
1095
+ );
1096
+ return;
1097
+ }
1098
+ for (const agent of agents) {
1099
+ if (agent.installed) {
1100
+ success(`${agent.definition.name} ${dim(agent.configDir)}`);
1101
+ } else {
1102
+ info(`${agent.definition.name} ${dim("not installed")}`);
1103
+ }
1104
+ }
1105
+ if (fs4.existsSync(import_node_path5.default.join(targetDir, "agents-anywhere.json"))) {
1106
+ warn(`Config repo already exists at ${targetDir}`);
1107
+ info("Run `agents-anywhere link` to connect your agents.");
1108
+ return;
1109
+ }
1110
+ const primary = await promptPrimaryAgent(installed);
1111
+ heading("Creating config repo...");
1112
+ fs4.mkdirSync(targetDir, { recursive: true });
1113
+ const git = (0, import_simple_git.simpleGit)(targetDir);
1114
+ if (!await git.checkIsRepo()) {
1115
+ await git.init();
1116
+ success("git init");
1117
+ }
1118
+ copyPortableFiles(installed, targetDir);
1119
+ syncInstructions(primary, installed, targetDir);
1120
+ importMCPServers(installed, targetDir);
1121
+ const manifest = buildManifest(installed, targetDir, primary.definition.id);
1122
+ fs4.writeFileSync(
1123
+ import_node_path5.default.join(targetDir, "agents-anywhere.json"),
1124
+ JSON.stringify(manifest, null, 2) + "\n",
1125
+ "utf-8"
1126
+ );
1127
+ success("agents-anywhere.json");
1128
+ fs4.writeFileSync(import_node_path5.default.join(targetDir, ".gitignore"), GITIGNORE, "utf-8");
1129
+ success(".gitignore");
1130
+ const hooksDir = import_node_path5.default.join(targetDir, ".git", "hooks");
1131
+ fs4.mkdirSync(hooksDir, { recursive: true });
1132
+ fs4.writeFileSync(import_node_path5.default.join(hooksDir, "post-merge"), POST_MERGE_HOOK, {
1133
+ mode: 493
1134
+ });
1135
+ success("post-merge hook");
1136
+ heading("Linking agent configs...");
1137
+ for (const agent of installed) {
1138
+ const results = linkAgent(agent.definition, targetDir);
1139
+ const linked = results.filter(
1140
+ (r) => r.action === "linked" || r.action === "backed-up-and-linked"
1141
+ );
1142
+ if (linked.length > 0) {
1143
+ success(`${agent.definition.name} \u2014 ${linked.length} item(s) linked`);
1144
+ }
1145
+ }
1146
+ syncMCPToAllAgents(targetDir, installed);
1147
+ await git.add(".");
1148
+ await git.commit("Initial agents-anywhere setup");
1149
+ success("Initial commit created");
1150
+ console.log(`
1151
+ ${dim("Setup complete!")} Config repo at ${dim(targetDir)}`);
1152
+ await promptGitHubRepo(targetDir);
1153
+ }
1154
+ async function promptPrimaryAgent(installed) {
1155
+ const candidates = installed.filter(
1156
+ (a) => a.definition.instructions.globalSupport
1157
+ );
1158
+ if (candidates.length === 0) {
1159
+ warn(
1160
+ "No installed agents support global instructions \u2014 instruction syncing will be skipped."
1161
+ );
1162
+ return installed[0];
1163
+ }
1164
+ if (candidates.length === 1) {
1165
+ info(
1166
+ `Using ${candidates[0].definition.name} as primary agent.`
1167
+ );
1168
+ return candidates[0];
1169
+ }
1170
+ if (!process.stdin.isTTY) {
1171
+ info(
1172
+ `Using ${candidates[0].definition.name} as primary agent (non-interactive).`
1173
+ );
1174
+ return candidates[0];
1175
+ }
1176
+ const chosen = await (0, import_select.default)({
1177
+ message: "Which agent is your primary? (its instructions become the source of truth)",
1178
+ choices: candidates.map((c) => ({
1179
+ name: c.definition.name,
1180
+ value: c.definition.id
1181
+ }))
1182
+ });
1183
+ return candidates.find((c) => c.definition.id === chosen);
1184
+ }
1185
+ function copyPortableFiles(installed, repoDir) {
1186
+ heading("Copying portable files...");
1187
+ for (const agent of installed) {
1188
+ const def = agent.definition;
1189
+ const configDir = agent.configDir;
1190
+ const agentRepoDir = import_node_path5.default.join(repoDir, def.id);
1191
+ fs4.mkdirSync(agentRepoDir, { recursive: true });
1192
+ const items = getPortableItems(def);
1193
+ const ignoreRoots = new Set(def.ignore.map((p) => p.split("/")[0]));
1194
+ const credBasenames = new Set(
1195
+ def.credentials.map((c) => import_node_path5.default.basename(expandPath(c)))
1196
+ );
1197
+ let copied = 0;
1198
+ for (const item of items) {
1199
+ if (ignoreRoots.has(item) || credBasenames.has(item)) continue;
1200
+ const sourcePath = import_node_path5.default.join(configDir, item);
1201
+ const destPath = import_node_path5.default.join(agentRepoDir, item);
1202
+ if (!fs4.existsSync(sourcePath)) continue;
1203
+ if (lstatExists(sourcePath) && fs4.lstatSync(sourcePath).isSymbolicLink()) {
1204
+ const realPath = fs4.realpathSync(sourcePath);
1205
+ if (!fs4.existsSync(realPath)) continue;
1206
+ const stat2 = fs4.statSync(realPath);
1207
+ if (stat2.isDirectory()) {
1208
+ fs4.cpSync(realPath, destPath, { recursive: true });
1209
+ } else {
1210
+ fs4.mkdirSync(import_node_path5.default.dirname(destPath), { recursive: true });
1211
+ fs4.copyFileSync(realPath, destPath);
1212
+ }
1213
+ copied++;
1214
+ continue;
1215
+ }
1216
+ const stat = fs4.statSync(sourcePath);
1217
+ if (stat.isDirectory()) {
1218
+ fs4.cpSync(sourcePath, destPath, { recursive: true, dereference: true });
1219
+ } else {
1220
+ fs4.mkdirSync(import_node_path5.default.dirname(destPath), { recursive: true });
1221
+ fs4.copyFileSync(sourcePath, destPath);
1222
+ }
1223
+ copied++;
1224
+ }
1225
+ if (copied > 0) {
1226
+ success(`${def.name} \u2014 copied ${copied} item(s)`);
1227
+ } else {
1228
+ info(`${def.name} \u2014 no portable files found`);
1229
+ }
1230
+ }
1231
+ }
1232
+ function syncInstructions(primary, installed, repoDir) {
1233
+ heading("Syncing instructions...");
1234
+ const primaryDef = primary.definition;
1235
+ const isFileBased = primaryDef.instructions.filename.includes(".");
1236
+ let primaryInstructionsPath;
1237
+ if (isFileBased) {
1238
+ primaryInstructionsPath = import_node_path5.default.join(
1239
+ repoDir,
1240
+ primaryDef.id,
1241
+ primaryDef.instructions.filename
1242
+ );
1243
+ } else {
1244
+ primaryInstructionsPath = import_node_path5.default.join(
1245
+ repoDir,
1246
+ primaryDef.id,
1247
+ primaryDef.instructions.filename,
1248
+ "instructions.md"
1249
+ );
1250
+ }
1251
+ if (!fs4.existsSync(primaryInstructionsPath)) {
1252
+ fs4.mkdirSync(import_node_path5.default.dirname(primaryInstructionsPath), { recursive: true });
1253
+ fs4.writeFileSync(
1254
+ primaryInstructionsPath,
1255
+ "# Agent Instructions\n\nAdd your instructions here.\n",
1256
+ "utf-8"
1257
+ );
1258
+ info(`Created empty instructions file for ${primaryDef.name}`);
1259
+ }
1260
+ success(`${primaryDef.name} \u2014 instructions file (source of truth)`);
1261
+ for (const agent of installed) {
1262
+ if (agent.definition.id === primaryDef.id) continue;
1263
+ const def = agent.definition;
1264
+ const agentRepoDir = import_node_path5.default.join(repoDir, def.id);
1265
+ if (!def.instructions.globalSupport) {
1266
+ warn(`${def.name} \u2014 project-level rules only, instructions not synced`);
1267
+ continue;
1268
+ }
1269
+ const isTargetFileBased = def.instructions.filename.includes(".");
1270
+ let symlinkPath;
1271
+ if (isTargetFileBased) {
1272
+ symlinkPath = import_node_path5.default.join(agentRepoDir, def.instructions.filename);
1273
+ } else {
1274
+ const targetDir = import_node_path5.default.join(agentRepoDir, def.instructions.filename);
1275
+ fs4.mkdirSync(targetDir, { recursive: true });
1276
+ symlinkPath = import_node_path5.default.join(targetDir, "instructions.md");
1277
+ }
1278
+ if (lstatExists(symlinkPath)) {
1279
+ const stat = fs4.lstatSync(symlinkPath);
1280
+ if (!stat.isSymbolicLink()) {
1281
+ const backupPath = `${symlinkPath}.backup.${Date.now()}`;
1282
+ fs4.renameSync(symlinkPath, backupPath);
1283
+ } else {
1284
+ fs4.unlinkSync(symlinkPath);
1285
+ }
1286
+ }
1287
+ fs4.mkdirSync(import_node_path5.default.dirname(symlinkPath), { recursive: true });
1288
+ const symlinkTarget = import_node_path5.default.relative(
1289
+ import_node_path5.default.dirname(symlinkPath),
1290
+ primaryInstructionsPath
1291
+ );
1292
+ fs4.symlinkSync(symlinkTarget, symlinkPath);
1293
+ success(`${def.name} \u2014 ${def.instructions.filename} \u2192 ${symlinkTarget}`);
1294
+ }
1295
+ }
1296
+ function importMCPServers(installed, repoDir) {
1297
+ heading("Importing MCP servers...");
1298
+ const agentDefs = installed.map((a) => a.definition);
1299
+ const result = importAndMergeAll(agentDefs);
1300
+ const serverCount = Object.keys(result.config.servers).length;
1301
+ if (serverCount === 0) {
1302
+ info("No MCP servers found in any agent configs.");
1303
+ fs4.writeFileSync(
1304
+ import_node_path5.default.join(repoDir, "mcp.json"),
1305
+ JSON.stringify({ servers: {} }, null, 2) + "\n",
1306
+ "utf-8"
1307
+ );
1308
+ return;
1309
+ }
1310
+ for (const [name, agentId] of Object.entries(result.sources)) {
1311
+ success(`${name} \u2014 imported from ${agentId}`);
1312
+ }
1313
+ for (const conflict of result.conflicts) {
1314
+ warn(
1315
+ `${conflict.serverName} \u2014 found in ${conflict.agents.join(" and ")}, kept version from ${conflict.kept}`
1316
+ );
1317
+ }
1318
+ fs4.writeFileSync(
1319
+ import_node_path5.default.join(repoDir, "mcp.json"),
1320
+ JSON.stringify(result.config, null, 2) + "\n",
1321
+ "utf-8"
1322
+ );
1323
+ success(`Imported ${serverCount} MCP server(s) to mcp.json`);
1324
+ }
1325
+ function syncMCPToAllAgents(repoDir, installed) {
1326
+ const mcpPath = import_node_path5.default.join(repoDir, "mcp.json");
1327
+ if (!fs4.existsSync(mcpPath)) return;
1328
+ let config;
1329
+ try {
1330
+ config = parseMCPConfig(mcpPath);
1331
+ } catch (err) {
1332
+ debug(`Failed to parse MCP config at ${mcpPath}: ${err.message}`);
1333
+ return;
1334
+ }
1335
+ if (Object.keys(config.servers).length === 0) return;
1336
+ heading("Syncing MCP config to agents...");
1337
+ for (const agent of installed) {
1338
+ const agentDef = agent.definition;
1339
+ const result = transformForAgent(config, agentDef);
1340
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
1341
+ const targetPath = import_node_path5.default.join(configDir, agentDef.mcp.configPath);
1342
+ if (result.format === "toml") {
1343
+ writeTOML(targetPath, result.rootKey, result.servers);
1344
+ } else if (agentDef.mcp.writeMode === "merge") {
1345
+ mergeJSON(targetPath, result.rootKey, result.servers);
1346
+ } else {
1347
+ writeJSON(targetPath, result.rootKey, result.servers);
1348
+ }
1349
+ success(`${agentDef.name} \u2014 wrote ${dim(targetPath)}`);
1350
+ }
1351
+ }
1352
+ async function promptGitHubRepo(repoDir) {
1353
+ if (!process.stdin.isTTY) return;
1354
+ try {
1355
+ (0, import_node_child_process2.execSync)("gh --version", { stdio: "ignore" });
1356
+ } catch {
1357
+ info(
1358
+ "Install GitHub CLI (gh) to create a private repo: https://cli.github.com"
1359
+ );
1360
+ return;
1361
+ }
1362
+ const shouldCreate = await (0, import_confirm.default)({
1363
+ message: "Create a private GitHub repo and push?",
1364
+ default: false
1365
+ });
1366
+ if (!shouldCreate) return;
1367
+ const repoName = import_node_path5.default.basename(repoDir);
1368
+ try {
1369
+ (0, import_node_child_process2.execSync)(
1370
+ `gh repo create ${repoName} --private --source="${repoDir}" --push`,
1371
+ { stdio: "inherit", cwd: repoDir }
1372
+ );
1373
+ success(`Created and pushed to private GitHub repo: ${repoName}`);
1374
+ } catch (err) {
1375
+ warn(`Failed to create GitHub repo: ${err.message}`);
1376
+ info("You can create it manually later with: gh repo create");
1377
+ }
1378
+ }
1379
+ async function initFromRemote(url, targetDir) {
1380
+ if (fs4.existsSync(import_node_path5.default.join(targetDir, "agents-anywhere.json"))) {
1381
+ warn(`Config repo already exists at ${targetDir}`);
1382
+ info("Run `agents-anywhere link` to connect your agents.");
1383
+ return;
1384
+ }
1385
+ heading("Cloning config repo...");
1386
+ const git = (0, import_simple_git.simpleGit)();
1387
+ try {
1388
+ await git.clone(url, targetDir);
1389
+ } catch (err) {
1390
+ error(`Failed to clone ${url}: ${err.message}`);
1391
+ return;
1392
+ }
1393
+ if (!fs4.existsSync(import_node_path5.default.join(targetDir, "agents-anywhere.json"))) {
1394
+ fs4.rmSync(targetDir, { recursive: true, force: true });
1395
+ error(
1396
+ `Not an agents-anywhere config repo: ${url} (no agents-anywhere.json found)`
1397
+ );
1398
+ return;
1399
+ }
1400
+ success(`Cloned config repo to ${targetDir}`);
1401
+ info(
1402
+ "Run `agents-anywhere link && agents-anywhere mcp sync` to connect your agents."
1403
+ );
1404
+ }
1405
+ function buildManifest(installedAgents, repoDir, primaryAgentId) {
1406
+ return {
1407
+ version: "0.1.0",
1408
+ repoDir,
1409
+ primaryAgent: primaryAgentId,
1410
+ agents: Object.fromEntries(
1411
+ installedAgents.map((a) => [
1412
+ a.definition.id,
1413
+ { enabled: true, name: a.definition.name }
1414
+ ])
1415
+ )
1416
+ };
1417
+ }
1418
+
1419
+ // src/utils/manifest.ts
1420
+ var fs5 = __toESM(require("fs"));
1421
+ var import_node_path6 = __toESM(require("path"));
1422
+ var import_node_os3 = __toESM(require("os"));
1423
+ var DEFAULT_REPO_DIR2 = import_node_path6.default.join(import_node_os3.default.homedir(), "agents-anywhere-config");
1424
+ function validateManifest(data, filePath) {
1425
+ if (typeof data !== "object" || data === null) {
1426
+ error(`Invalid manifest in ${filePath}: expected an object`);
1427
+ return null;
1428
+ }
1429
+ const obj = data;
1430
+ if (typeof obj.version !== "string") {
1431
+ error(`Invalid manifest in ${filePath}: "version" must be a string`);
1432
+ return null;
1433
+ }
1434
+ if (typeof obj.agents !== "object" || obj.agents === null || Array.isArray(obj.agents)) {
1435
+ error(`Invalid manifest in ${filePath}: "agents" must be an object`);
1436
+ return null;
1437
+ }
1438
+ for (const [key, value] of Object.entries(
1439
+ obj.agents
1440
+ )) {
1441
+ if (typeof value !== "object" || value === null) {
1442
+ error(
1443
+ `Invalid manifest in ${filePath}: agent "${key}" must be an object`
1444
+ );
1445
+ return null;
1446
+ }
1447
+ const agent = value;
1448
+ if (typeof agent.enabled !== "boolean") {
1449
+ error(
1450
+ `Invalid manifest in ${filePath}: agent "${key}.enabled" must be a boolean`
1451
+ );
1452
+ return null;
1453
+ }
1454
+ if (typeof agent.name !== "string") {
1455
+ error(
1456
+ `Invalid manifest in ${filePath}: agent "${key}.name" must be a string`
1457
+ );
1458
+ return null;
1459
+ }
1460
+ }
1461
+ return data;
1462
+ }
1463
+ function loadManifest() {
1464
+ const candidates = [
1465
+ import_node_path6.default.join(process.cwd(), "agents-anywhere.json"),
1466
+ import_node_path6.default.join(DEFAULT_REPO_DIR2, "agents-anywhere.json")
1467
+ ];
1468
+ for (const candidate of candidates) {
1469
+ if (fs5.existsSync(candidate)) {
1470
+ const raw = fs5.readFileSync(candidate, "utf-8");
1471
+ let parsed;
1472
+ try {
1473
+ parsed = JSON.parse(raw);
1474
+ } catch {
1475
+ error(`Invalid JSON in ${candidate}`);
1476
+ return null;
1477
+ }
1478
+ const manifest = validateManifest(parsed, candidate);
1479
+ if (!manifest) return null;
1480
+ manifest.repoDir = import_node_path6.default.dirname(candidate);
1481
+ return manifest;
1482
+ }
1483
+ }
1484
+ error("No agents-anywhere.json found. Run `agents-anywhere init` first.");
1485
+ info(`Looked in: ${candidates.join(", ")}`);
1486
+ return null;
1487
+ }
1488
+ function saveManifest(manifest) {
1489
+ const filePath = import_node_path6.default.join(manifest.repoDir, "agents-anywhere.json");
1490
+ fs5.writeFileSync(filePath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
1491
+ }
1492
+
1493
+ // src/commands/link.ts
1494
+ async function linkCommand(agentId, options = {}) {
1495
+ const manifest = loadManifest();
1496
+ if (!manifest) return;
1497
+ const repoDir = manifest.repoDir;
1498
+ const dryRun = options.dryRun ?? false;
1499
+ const prefix = dryRun ? "[dry-run] " : "";
1500
+ if (agentId) {
1501
+ const agentDef = loadAgentById(agentId);
1502
+ if (!agentDef) {
1503
+ error(`Unknown agent: ${agentId}`);
1504
+ const all = loadAllAgentDefinitions();
1505
+ info("Known agents: " + all.map((a) => a.id).join(", "));
1506
+ return;
1507
+ }
1508
+ if (!manifest.agents[agentId]?.enabled) {
1509
+ warn(`Agent "${agentId}" is not enabled in agents-anywhere.json`);
1510
+ return;
1511
+ }
1512
+ linkSingleAgent(agentDef, repoDir, dryRun, prefix);
1513
+ } else {
1514
+ heading(`${prefix}Linking agent configs...`);
1515
+ const enabledIds = Object.entries(manifest.agents).filter(([, v]) => v.enabled).map(([id]) => id);
1516
+ if (enabledIds.length === 0) {
1517
+ warn("No agents enabled in agents-anywhere.json");
1518
+ return;
1519
+ }
1520
+ for (const id of enabledIds) {
1521
+ const agentDef = loadAgentById(id);
1522
+ if (!agentDef) {
1523
+ warn(`Agent "${id}" in manifest but no definition found \u2014 skipping`);
1524
+ continue;
1525
+ }
1526
+ linkSingleAgent(agentDef, repoDir, dryRun, prefix);
1527
+ }
1528
+ }
1529
+ }
1530
+ function linkSingleAgent(agentDef, repoDir, dryRun, prefix) {
1531
+ const results = linkAgent(agentDef, repoDir, dryRun);
1532
+ if (results.length === 0) {
1533
+ info(`${prefix}${agentDef.name} \u2014 no portable files found in repo`);
1534
+ return;
1535
+ }
1536
+ const linked = results.filter((r) => r.action === "linked" || r.action === "backed-up-and-linked");
1537
+ const skipped = results.filter((r) => r.action === "skipped");
1538
+ const items = results.map((r) => r.item).join(", ");
1539
+ if (linked.length > 0) {
1540
+ success(`${prefix}${agentDef.name} \u2014 ${items} linked`);
1541
+ } else if (results.length > 0 && skipped.length === results.length) {
1542
+ info(`${prefix}${agentDef.name} \u2014 already linked ${dim("(skipped)")}`);
1543
+ }
1544
+ for (const r of results) {
1545
+ if (r.action === "backed-up-and-linked") {
1546
+ info(`${prefix} backed up existing ${r.item}`);
1547
+ }
1548
+ }
1549
+ }
1550
+
1551
+ // src/commands/unlink.ts
1552
+ async function unlinkCommand(agentId, options = {}) {
1553
+ const manifest = loadManifest();
1554
+ if (!manifest) return;
1555
+ const repoDir = manifest.repoDir;
1556
+ const dryRun = options.dryRun ?? false;
1557
+ const prefix = dryRun ? "[dry-run] " : "";
1558
+ if (agentId) {
1559
+ const agentDef = loadAgentById(agentId);
1560
+ if (!agentDef) {
1561
+ error(`Unknown agent: ${agentId}`);
1562
+ const all = loadAllAgentDefinitions();
1563
+ info("Known agents: " + all.map((a) => a.id).join(", "));
1564
+ return;
1565
+ }
1566
+ unlinkSingleAgent(agentDef, repoDir, dryRun, prefix);
1567
+ } else {
1568
+ heading(`${prefix}Unlinking agent configs...`);
1569
+ const agentIds = Object.keys(manifest.agents);
1570
+ if (agentIds.length === 0) {
1571
+ warn("No agents in agents-anywhere.json");
1572
+ return;
1573
+ }
1574
+ for (const id of agentIds) {
1575
+ const agentDef = loadAgentById(id);
1576
+ if (!agentDef) {
1577
+ warn(`Agent "${id}" in manifest but no definition found \u2014 skipping`);
1578
+ continue;
1579
+ }
1580
+ unlinkSingleAgent(agentDef, repoDir, dryRun, prefix);
1581
+ }
1582
+ }
1583
+ }
1584
+ function unlinkSingleAgent(agentDef, repoDir, dryRun, prefix) {
1585
+ const results = unlinkAgent(agentDef, repoDir, dryRun);
1586
+ const unlinked = results.filter((r) => r.action === "unlinked" || r.action === "restored");
1587
+ const skipped = results.filter((r) => r.action === "skipped");
1588
+ if (unlinked.length > 0) {
1589
+ const items = unlinked.map((r) => r.item).join(", ");
1590
+ success(`${prefix}${agentDef.name} \u2014 ${items} unlinked`);
1591
+ } else if (skipped.length === results.length) {
1592
+ info(`${prefix}${agentDef.name} \u2014 nothing to unlink`);
1593
+ }
1594
+ for (const r of results) {
1595
+ if (r.action === "restored") {
1596
+ info(`${prefix} restored backup for ${r.item}`);
1597
+ }
1598
+ }
1599
+ }
1600
+
1601
+ // src/commands/status.ts
1602
+ async function statusCommand() {
1603
+ const manifest = loadManifest();
1604
+ if (!manifest) return;
1605
+ const repoDir = manifest.repoDir;
1606
+ const agentIds = Object.keys(manifest.agents);
1607
+ if (agentIds.length === 0) {
1608
+ warn("No agents in agents-anywhere.json");
1609
+ return;
1610
+ }
1611
+ heading("Agent link status");
1612
+ for (const id of agentIds) {
1613
+ const agentDef = loadAgentById(id);
1614
+ if (!agentDef) {
1615
+ warn(`Agent "${id}" \u2014 definition not found`);
1616
+ continue;
1617
+ }
1618
+ const statuses = getStatus(agentDef, repoDir);
1619
+ console.log(`
1620
+ ${bold(agentDef.name)}`);
1621
+ const rows = statuses.map((s) => [
1622
+ s.item,
1623
+ statusBadge(s.status)
1624
+ ]);
1625
+ table(rows, 4);
1626
+ }
1627
+ console.log();
1628
+ }
1629
+
1630
+ // src/commands/agents.ts
1631
+ async function agentsCommand() {
1632
+ const manifest = loadManifest();
1633
+ const repoDir = manifest?.repoDir;
1634
+ const agents = detectAgents();
1635
+ heading("Known AI coding agents");
1636
+ console.log();
1637
+ for (const agent of agents) {
1638
+ const id = agent.definition.id;
1639
+ const name = agent.definition.name;
1640
+ const installStatus = agent.installed ? statusBadge("installed") : statusBadge("not installed");
1641
+ let linkInfo = "";
1642
+ if (agent.installed && repoDir && manifest?.agents[id]?.enabled) {
1643
+ const statuses = getStatus(agent.definition, repoDir);
1644
+ const linkedCount = statuses.filter((s) => s.status === "linked").length;
1645
+ const total = statuses.length;
1646
+ if (linkedCount === total && total > 0) {
1647
+ linkInfo = ` ${statusBadge("linked")}`;
1648
+ } else if (linkedCount > 0) {
1649
+ linkInfo = ` ${dim(`${linkedCount}/${total} linked`)}`;
1650
+ }
1651
+ }
1652
+ console.log(` ${bold(name.padEnd(16))} ${installStatus}${linkInfo}`);
1653
+ console.log(` ${dim(agent.configDir)}`);
1654
+ console.log();
1655
+ }
1656
+ }
1657
+
1658
+ // src/commands/mcp-sync.ts
1659
+ var import_node_path7 = __toESM(require("path"));
1660
+ async function mcpSyncCommand(options = {}) {
1661
+ const manifest = loadManifest();
1662
+ if (!manifest) return;
1663
+ const dryRun = options.dryRun ?? false;
1664
+ const prefix = dryRun ? "[dry-run] " : "";
1665
+ const mcpPath = import_node_path7.default.join(manifest.repoDir, "mcp.json");
1666
+ let config;
1667
+ try {
1668
+ config = parseMCPConfig(mcpPath);
1669
+ } catch (err) {
1670
+ error(`Failed to parse mcp.json: ${err.message}`);
1671
+ return;
1672
+ }
1673
+ const serverCount = Object.keys(config.servers).length;
1674
+ if (serverCount === 0) {
1675
+ warn("No servers defined in mcp.json. Use `agents-anywhere mcp add` to add one.");
1676
+ return;
1677
+ }
1678
+ heading(`${prefix}Syncing MCP config to agents...`);
1679
+ const enabledIds = Object.entries(manifest.agents).filter(([, v]) => v.enabled).map(([id]) => id);
1680
+ if (enabledIds.length === 0) {
1681
+ warn("No agents enabled in agents-anywhere.json");
1682
+ return;
1683
+ }
1684
+ let synced = 0;
1685
+ for (const id of enabledIds) {
1686
+ const agentDef = loadAgentById(id);
1687
+ if (!agentDef) {
1688
+ warn(`Agent "${id}" in manifest but no definition found \u2014 skipping`);
1689
+ continue;
1690
+ }
1691
+ const result = transformForAgent(config, agentDef);
1692
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
1693
+ const targetPath = import_node_path7.default.join(configDir, agentDef.mcp.configPath);
1694
+ if (!dryRun) {
1695
+ if (result.format === "toml") {
1696
+ writeTOML(targetPath, result.rootKey, result.servers);
1697
+ } else if (agentDef.mcp.writeMode === "merge") {
1698
+ mergeJSON(targetPath, result.rootKey, result.servers);
1699
+ } else {
1700
+ writeJSON(targetPath, result.rootKey, result.servers);
1701
+ }
1702
+ }
1703
+ success(`${prefix}${agentDef.name} \u2014 ${dryRun ? "would write" : "wrote"} ${dim(targetPath)}`);
1704
+ synced++;
1705
+ }
1706
+ console.log(`
1707
+ ${prefix}Synced ${serverCount} server(s) to ${synced} agent(s).`);
1708
+ }
1709
+
1710
+ // src/commands/mcp-add.ts
1711
+ var fs6 = __toESM(require("fs"));
1712
+ var readline = __toESM(require("readline/promises"));
1713
+ var import_node_path8 = __toESM(require("path"));
1714
+ function buildServerFromFlags(flags) {
1715
+ if (!flags.transport) return null;
1716
+ if (flags.transport !== "stdio" && flags.transport !== "http") {
1717
+ error(`Transport must be "stdio" or "http", got "${flags.transport}"`);
1718
+ return null;
1719
+ }
1720
+ if (flags.transport === "stdio") {
1721
+ if (!flags.command) return null;
1722
+ const server2 = {
1723
+ transport: "stdio",
1724
+ command: flags.command
1725
+ };
1726
+ if (flags.args) {
1727
+ server2.args = flags.args.split(",").map((a) => a.trim());
1728
+ }
1729
+ if (flags.env && flags.env.length > 0) {
1730
+ server2.env = parseEnvPairs(flags.env);
1731
+ }
1732
+ return server2;
1733
+ }
1734
+ if (!flags.url) return null;
1735
+ const server = {
1736
+ transport: "http",
1737
+ url: flags.url
1738
+ };
1739
+ if (flags.env && flags.env.length > 0) {
1740
+ server.env = parseEnvPairs(flags.env);
1741
+ }
1742
+ return server;
1743
+ }
1744
+ function parseEnvPairs(pairs) {
1745
+ const env = {};
1746
+ for (const pair of pairs) {
1747
+ const eqIdx = pair.indexOf("=");
1748
+ if (eqIdx === -1) {
1749
+ warn(`Skipping invalid env pair "${pair}" \u2014 expected KEY=VAR format`);
1750
+ continue;
1751
+ }
1752
+ const key = pair.slice(0, eqIdx);
1753
+ const varName = pair.slice(eqIdx + 1);
1754
+ if (!key || !varName) {
1755
+ warn(`Skipping invalid env pair "${pair}" \u2014 empty key or value`);
1756
+ continue;
1757
+ }
1758
+ env[key] = { $env: varName };
1759
+ }
1760
+ return env;
1761
+ }
1762
+ async function ask(rl, question) {
1763
+ const answer = await rl.question(question);
1764
+ return answer.trim();
1765
+ }
1766
+ async function mcpAddCommand(name, flags = {}) {
1767
+ const manifest = loadManifest();
1768
+ if (!manifest) return;
1769
+ const mcpPath = import_node_path8.default.join(manifest.repoDir, "mcp.json");
1770
+ let config;
1771
+ try {
1772
+ config = parseMCPConfig(mcpPath);
1773
+ } catch {
1774
+ config = { servers: {} };
1775
+ }
1776
+ if (config.servers[name]) {
1777
+ warn(`Server "${name}" already exists in mcp.json. It will be overwritten.`);
1778
+ }
1779
+ if (flags.transport) {
1780
+ const serverFromFlags = buildServerFromFlags(flags);
1781
+ if (serverFromFlags) {
1782
+ config.servers[name] = serverFromFlags;
1783
+ fs6.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1784
+ success(`Added server "${name}" to mcp.json`);
1785
+ info("Run `agents-anywhere mcp sync` to generate per-agent configs.");
1786
+ }
1787
+ return;
1788
+ }
1789
+ const rl = readline.createInterface({
1790
+ input: process.stdin,
1791
+ output: process.stdout
1792
+ });
1793
+ try {
1794
+ const transport = await ask(rl, "Transport (stdio/http): ");
1795
+ if (transport !== "stdio" && transport !== "http") {
1796
+ error('Transport must be "stdio" or "http"');
1797
+ return;
1798
+ }
1799
+ const server = { transport };
1800
+ if (transport === "stdio") {
1801
+ const command = await ask(rl, "Command (e.g., npx): ");
1802
+ if (!command) {
1803
+ error("Command is required for stdio transport");
1804
+ return;
1805
+ }
1806
+ server.command = command;
1807
+ const argsStr = await ask(rl, "Args (comma-separated, or empty): ");
1808
+ if (argsStr) {
1809
+ server.args = argsStr.split(",").map((a) => a.trim());
1810
+ }
1811
+ } else {
1812
+ const url = await ask(rl, "URL: ");
1813
+ if (!url) {
1814
+ error("URL is required for http transport");
1815
+ return;
1816
+ }
1817
+ server.url = url;
1818
+ }
1819
+ const envVars = {};
1820
+ info("Add environment variables (empty name to finish):");
1821
+ while (true) {
1822
+ const envName = await ask(rl, " Env var name (or empty to finish): ");
1823
+ if (!envName) break;
1824
+ const envVar = await ask(rl, ` Shell variable for ${envName}: `);
1825
+ if (!envVar) {
1826
+ warn(`Skipping env var "${envName}" \u2014 no value provided`);
1827
+ continue;
1828
+ }
1829
+ envVars[envName] = { $env: envVar };
1830
+ }
1831
+ if (Object.keys(envVars).length > 0) {
1832
+ server.env = envVars;
1833
+ }
1834
+ if (transport === "http") {
1835
+ const addAuth = await ask(rl, "Add authorization header? (y/n): ");
1836
+ if (addAuth.toLowerCase() === "y") {
1837
+ const tokenVar = await ask(rl, " Token env variable name: ");
1838
+ if (tokenVar) {
1839
+ server.headers = {
1840
+ Authorization: { $env: tokenVar, prefix: "Bearer " }
1841
+ };
1842
+ }
1843
+ }
1844
+ }
1845
+ config.servers[name] = server;
1846
+ fs6.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1847
+ success(`Added server "${name}" to mcp.json`);
1848
+ info("Run `agents-anywhere mcp sync` to generate per-agent configs.");
1849
+ } finally {
1850
+ rl.close();
1851
+ }
1852
+ }
1853
+
1854
+ // src/commands/mcp-list.ts
1855
+ var import_node_path9 = __toESM(require("path"));
1856
+ async function mcpListCommand() {
1857
+ const manifest = loadManifest();
1858
+ if (!manifest) return;
1859
+ const mcpPath = import_node_path9.default.join(manifest.repoDir, "mcp.json");
1860
+ let config;
1861
+ try {
1862
+ config = parseMCPConfig(mcpPath);
1863
+ } catch (err) {
1864
+ error(`Failed to parse mcp.json: ${err.message}`);
1865
+ return;
1866
+ }
1867
+ const servers = Object.entries(config.servers);
1868
+ if (servers.length === 0) {
1869
+ warn("No MCP servers configured. Use `agents-anywhere mcp add` to add one.");
1870
+ return;
1871
+ }
1872
+ heading(`MCP Servers (${servers.length})`);
1873
+ const rows = [];
1874
+ for (const [name, server] of servers) {
1875
+ const target = server.transport === "stdio" ? `${server.command}${server.args?.length ? " " + server.args.join(" ") : ""}` : server.url ?? "";
1876
+ const envVars = server.env ? Object.values(server.env).map((ref) => ref.$env).join(", ") : "";
1877
+ const headerVars = server.headers ? Object.values(server.headers).map((ref) => ref.$env).join(", ") : "";
1878
+ rows.push([bold(name), `${server.transport} ${dim(target)}`]);
1879
+ if (envVars) {
1880
+ rows.push(["", `env: ${envVars}`]);
1881
+ }
1882
+ if (headerVars) {
1883
+ rows.push(["", `headers: ${headerVars}`]);
1884
+ }
1885
+ }
1886
+ table(rows);
1887
+ }
1888
+
1889
+ // src/commands/mcp-diff.ts
1890
+ var fs7 = __toESM(require("fs"));
1891
+ var import_node_path10 = __toESM(require("path"));
1892
+ var TOML3 = __toESM(require("smol-toml"));
1893
+ function readExistingServers(agentDef, targetPath) {
1894
+ if (!fs7.existsSync(targetPath)) {
1895
+ return null;
1896
+ }
1897
+ const raw = fs7.readFileSync(targetPath, "utf-8");
1898
+ const format = agentDef.mcp.format ?? "json";
1899
+ if (format === "toml") {
1900
+ const parsed2 = TOML3.parse(raw);
1901
+ const section2 = parsed2[agentDef.mcp.serverSection ?? agentDef.mcp.rootKey];
1902
+ if (!section2 || typeof section2 !== "object") return {};
1903
+ return section2;
1904
+ }
1905
+ const parsed = JSON.parse(raw);
1906
+ const rootKey = agentDef.mcp.rootKey;
1907
+ const section = parsed[rootKey];
1908
+ if (!section || typeof section !== "object") return {};
1909
+ return section;
1910
+ }
1911
+ function diffServers(existing, incoming) {
1912
+ const diff = { added: [], removed: [], changed: [], unchanged: [] };
1913
+ if (existing === null) {
1914
+ diff.added = Object.keys(incoming);
1915
+ return diff;
1916
+ }
1917
+ const existingNames = new Set(Object.keys(existing));
1918
+ const incomingNames = new Set(Object.keys(incoming));
1919
+ for (const name of incomingNames) {
1920
+ if (!existingNames.has(name)) {
1921
+ diff.added.push(name);
1922
+ } else {
1923
+ const existingStr = JSON.stringify(existing[name], null, 2);
1924
+ const incomingStr = JSON.stringify(incoming[name], null, 2);
1925
+ if (existingStr === incomingStr) {
1926
+ diff.unchanged.push(name);
1927
+ } else {
1928
+ diff.changed.push(name);
1929
+ }
1930
+ }
1931
+ }
1932
+ for (const name of existingNames) {
1933
+ if (!incomingNames.has(name)) {
1934
+ diff.removed.push(name);
1935
+ }
1936
+ }
1937
+ return diff;
1938
+ }
1939
+ async function mcpDiffCommand() {
1940
+ const manifest = loadManifest();
1941
+ if (!manifest) return;
1942
+ const mcpPath = import_node_path10.default.join(manifest.repoDir, "mcp.json");
1943
+ let config;
1944
+ try {
1945
+ config = parseMCPConfig(mcpPath);
1946
+ } catch (err) {
1947
+ error(`Failed to parse mcp.json: ${err.message}`);
1948
+ return;
1949
+ }
1950
+ const serverCount = Object.keys(config.servers).length;
1951
+ if (serverCount === 0) {
1952
+ warn("No servers defined in mcp.json. Use `agents-anywhere mcp add` to add one.");
1953
+ return;
1954
+ }
1955
+ const enabledIds = Object.entries(manifest.agents).filter(([, v]) => v.enabled).map(([id]) => id);
1956
+ if (enabledIds.length === 0) {
1957
+ warn("No agents enabled in agents-anywhere.json");
1958
+ return;
1959
+ }
1960
+ heading("MCP diff \u2014 previewing sync changes...");
1961
+ let anyChanges = false;
1962
+ for (const id of enabledIds) {
1963
+ const agentDef = loadAgentById(id);
1964
+ if (!agentDef) {
1965
+ warn(`Agent "${id}" in manifest but no definition found \u2014 skipping`);
1966
+ continue;
1967
+ }
1968
+ const result = transformForAgent(config, agentDef);
1969
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
1970
+ const targetPath = import_node_path10.default.join(configDir, agentDef.mcp.configPath);
1971
+ const existing = readExistingServers(agentDef, targetPath);
1972
+ const diff = diffServers(existing, result.servers);
1973
+ const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
1974
+ if (!hasChanges && existing !== null) {
1975
+ success(`${agentDef.name} \u2014 ${dim("up to date")}`);
1976
+ continue;
1977
+ }
1978
+ anyChanges = true;
1979
+ if (existing === null) {
1980
+ console.log(`
1981
+ ${bold(agentDef.name)} ${dim(`(${targetPath})`)}`);
1982
+ console.log(` ${dim("File does not exist \u2014 will be created")}`);
1983
+ } else {
1984
+ console.log(`
1985
+ ${bold(agentDef.name)} ${dim(`(${targetPath})`)}`);
1986
+ }
1987
+ for (const name of diff.added) {
1988
+ console.log(` ${green("+ " + name)}`);
1989
+ }
1990
+ for (const name of diff.removed) {
1991
+ console.log(` ${red("- " + name)}`);
1992
+ }
1993
+ for (const name of diff.changed) {
1994
+ console.log(` ${yellow("~ " + name)}`);
1995
+ }
1996
+ }
1997
+ console.log();
1998
+ if (!anyChanges) {
1999
+ success("All agents up to date");
2000
+ } else {
2001
+ console.log(`Run ${bold("agents-anywhere mcp sync")} to apply these changes.`);
2002
+ }
2003
+ }
2004
+
2005
+ // src/commands/doctor.ts
2006
+ var fs8 = __toESM(require("fs"));
2007
+ var import_node_path11 = __toESM(require("path"));
2008
+ function checkBrokenSymlinks(agents, repoDir) {
2009
+ const issues = [];
2010
+ for (const { id, def } of agents) {
2011
+ const configDir = expandPath(getPlatformPath(def.configDir));
2012
+ const items = getPortableItems(def);
2013
+ for (const item of items) {
2014
+ const agentPath = import_node_path11.default.join(configDir, item);
2015
+ try {
2016
+ const stat = fs8.lstatSync(agentPath);
2017
+ if (stat.isSymbolicLink()) {
2018
+ const target = fs8.readlinkSync(agentPath);
2019
+ const resolved = import_node_path11.default.resolve(import_node_path11.default.dirname(agentPath), target);
2020
+ if (!fs8.existsSync(resolved)) {
2021
+ issues.push({
2022
+ agent: def.name,
2023
+ message: `${item} \u2192 ${resolved} (target missing)`,
2024
+ fix: `Run \`agents-anywhere unlink ${id} && agents-anywhere link ${id}\``
2025
+ });
2026
+ }
2027
+ }
2028
+ } catch {
2029
+ }
2030
+ }
2031
+ }
2032
+ return issues;
2033
+ }
2034
+ function checkCredentialsInRepo(agents, repoDir) {
2035
+ const issues = [];
2036
+ for (const { def } of agents) {
2037
+ for (const credPath of def.credentials) {
2038
+ const credFileName = import_node_path11.default.basename(expandPath(credPath));
2039
+ const inRoot = import_node_path11.default.join(repoDir, credFileName);
2040
+ if (fs8.existsSync(inRoot)) {
2041
+ issues.push({
2042
+ agent: def.name,
2043
+ message: `${credFileName} found at repo root`,
2044
+ fix: `Remove ${inRoot} and add to .gitignore`
2045
+ });
2046
+ }
2047
+ const inAgentDir = import_node_path11.default.join(repoDir, def.id, credFileName);
2048
+ if (fs8.existsSync(inAgentDir)) {
2049
+ issues.push({
2050
+ agent: def.name,
2051
+ message: `${credFileName} found in ${def.id}/ directory`,
2052
+ fix: `Remove ${inAgentDir} and add to .gitignore`
2053
+ });
2054
+ }
2055
+ }
2056
+ }
2057
+ return issues;
2058
+ }
2059
+ function checkStaleConfigs(agents, repoDir) {
2060
+ const issues = [];
2061
+ for (const { id, def } of agents) {
2062
+ const configDir = expandPath(getPlatformPath(def.configDir));
2063
+ const agentRepoDir = import_node_path11.default.join(repoDir, id);
2064
+ const items = getPortableItems(def);
2065
+ for (const item of items) {
2066
+ const agentPath = import_node_path11.default.join(configDir, item);
2067
+ const repoPath = import_node_path11.default.join(agentRepoDir, item);
2068
+ try {
2069
+ const stat = fs8.lstatSync(agentPath);
2070
+ if (stat.isSymbolicLink() && !fs8.existsSync(repoPath)) {
2071
+ issues.push({
2072
+ agent: def.name,
2073
+ message: `${item} is linked but missing from repo`,
2074
+ fix: `Run \`agents-anywhere unlink ${id}\` to clean up, then re-add the file`
2075
+ });
2076
+ }
2077
+ } catch {
2078
+ }
2079
+ }
2080
+ }
2081
+ return issues;
2082
+ }
2083
+ function checkMCPFreshness(agents, repoDir) {
2084
+ const issues = [];
2085
+ const mcpJsonPath = import_node_path11.default.join(repoDir, "mcp.json");
2086
+ if (!fs8.existsSync(mcpJsonPath)) {
2087
+ return issues;
2088
+ }
2089
+ const mcpMtime = fs8.statSync(mcpJsonPath).mtimeMs;
2090
+ for (const { def } of agents) {
2091
+ const configDir = expandPath(getPlatformPath(def.configDir));
2092
+ const generatedPath = import_node_path11.default.join(configDir, def.mcp.configPath);
2093
+ if (fs8.existsSync(generatedPath)) {
2094
+ const genMtime = fs8.statSync(generatedPath).mtimeMs;
2095
+ if (genMtime < mcpMtime) {
2096
+ issues.push({
2097
+ agent: def.name,
2098
+ message: `${def.mcp.configPath} is older than mcp.json`,
2099
+ fix: "Run `agents-anywhere mcp sync` to regenerate"
2100
+ });
2101
+ }
2102
+ }
2103
+ }
2104
+ return issues;
2105
+ }
2106
+ async function doctorCommand() {
2107
+ const manifest = loadManifest();
2108
+ if (!manifest) return;
2109
+ const repoDir = manifest.repoDir;
2110
+ const enabledIds = Object.entries(manifest.agents).filter(([, v]) => v.enabled).map(([id]) => id);
2111
+ if (enabledIds.length === 0) {
2112
+ warn("No agents enabled in agents-anywhere.json");
2113
+ return;
2114
+ }
2115
+ heading("agents-anywhere doctor");
2116
+ console.log();
2117
+ const agents = enabledIds.map((id) => ({ id, def: loadAgentById(id) })).filter(
2118
+ (a) => a.def !== void 0
2119
+ );
2120
+ const checks = [
2121
+ { name: "Broken symlinks", issues: checkBrokenSymlinks(agents, repoDir) },
2122
+ {
2123
+ name: "Credentials in repo",
2124
+ issues: checkCredentialsInRepo(agents, repoDir)
2125
+ },
2126
+ { name: "Stale configs", issues: checkStaleConfigs(agents, repoDir) },
2127
+ {
2128
+ name: "MCP config freshness",
2129
+ issues: checkMCPFreshness(agents, repoDir)
2130
+ }
2131
+ ];
2132
+ let totalIssues = 0;
2133
+ for (const check of checks) {
2134
+ if (check.issues.length === 0) {
2135
+ success(check.name);
2136
+ } else {
2137
+ error(check.name);
2138
+ for (const issue of check.issues) {
2139
+ console.log(` ${issue.agent}: ${issue.message}`);
2140
+ console.log(` ${dim(`Fix: ${issue.fix}`)}`);
2141
+ }
2142
+ totalIssues += check.issues.length;
2143
+ }
2144
+ }
2145
+ console.log();
2146
+ if (totalIssues === 0) {
2147
+ success("All checks passed \u2014 config is healthy");
2148
+ } else {
2149
+ warn(`${totalIssues} issue(s) found`);
2150
+ }
2151
+ }
2152
+
2153
+ // src/commands/validate.ts
2154
+ var import_node_fs4 = require("fs");
2155
+ var import_node_path12 = __toESM(require("path"));
2156
+ async function validateCommand() {
2157
+ heading("Validating agent definitions");
2158
+ const agentsDir = getAgentsDir();
2159
+ const files = (0, import_node_fs4.readdirSync)(agentsDir).filter((f) => f.endsWith(".json"));
2160
+ let totalErrors = 0;
2161
+ for (const file of files) {
2162
+ const filePath = import_node_path12.default.join(agentsDir, file);
2163
+ try {
2164
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
2165
+ const parsed = JSON.parse(raw);
2166
+ const result = validateAgainstSchema(parsed);
2167
+ if (result.valid) {
2168
+ success(`${file} \u2014 valid`);
2169
+ } else {
2170
+ error(`${file} \u2014 ${result.errors.length} error(s)`);
2171
+ for (const err of result.errors) {
2172
+ console.log(` ${err.path}: ${err.message}`);
2173
+ }
2174
+ totalErrors += result.errors.length;
2175
+ }
2176
+ } catch (err) {
2177
+ error(`${file} \u2014 failed to parse: ${err.message}`);
2178
+ totalErrors++;
2179
+ }
2180
+ }
2181
+ console.log();
2182
+ if (totalErrors === 0) {
2183
+ info(`All ${files.length} agent definitions are valid.`);
2184
+ } else {
2185
+ info(`${totalErrors} error(s) found across agent definitions.`);
2186
+ process.exitCode = 1;
2187
+ }
2188
+ }
2189
+
2190
+ // src/commands/export.ts
2191
+ var fs9 = __toESM(require("fs"));
2192
+ var import_node_path13 = __toESM(require("path"));
2193
+ var import_node_os4 = __toESM(require("os"));
2194
+ var TOML4 = __toESM(require("smol-toml"));
2195
+ async function exportCommand() {
2196
+ const manifest = loadManifest();
2197
+ if (!manifest) return;
2198
+ const mcpPath = import_node_path13.default.join(manifest.repoDir, "mcp.json");
2199
+ let config;
2200
+ try {
2201
+ config = parseMCPConfig(mcpPath);
2202
+ } catch (err) {
2203
+ error(`Failed to parse mcp.json: ${err.message}`);
2204
+ return;
2205
+ }
2206
+ const mcpRaw = fs9.readFileSync(mcpPath, "utf-8");
2207
+ const enabledIds = Object.entries(manifest.agents).filter(([, v]) => v.enabled).map(([id]) => id);
2208
+ if (enabledIds.length === 0) {
2209
+ warn("No agents enabled in agents-anywhere.json");
2210
+ return;
2211
+ }
2212
+ const agents = [];
2213
+ for (const id of enabledIds) {
2214
+ const agentDef = loadAgentById(id);
2215
+ if (!agentDef) {
2216
+ warn(`Agent "${id}" in manifest but no definition found \u2014 skipping`);
2217
+ continue;
2218
+ }
2219
+ const result = transformForAgent(config, agentDef);
2220
+ const configDir = expandPath(getPlatformPath(agentDef.configDir));
2221
+ const mcpTargetPath = import_node_path13.default.join(configDir, agentDef.mcp.configPath);
2222
+ let serialized;
2223
+ if (result.format === "toml") {
2224
+ serialized = TOML4.stringify({ [result.rootKey]: result.servers }) + "\n";
2225
+ } else {
2226
+ serialized = JSON.stringify({ [result.rootKey]: result.servers }, null, 2) + "\n";
2227
+ }
2228
+ agents.push({
2229
+ agentDef,
2230
+ configDir,
2231
+ mcpTargetPath,
2232
+ mcpContent: serialized,
2233
+ portableItems: getPortableItems(agentDef)
2234
+ });
2235
+ }
2236
+ const script = generateExportScript(manifest.repoDir, mcpRaw, agents);
2237
+ process.stdout.write(script);
2238
+ }
2239
+ function makePortablePath(p) {
2240
+ const normalized = p.replace(/\\/g, "/");
2241
+ const home = import_node_os4.default.homedir().replace(/\\/g, "/");
2242
+ if (normalized.startsWith(home + "/") || normalized === home) {
2243
+ return "$HOME" + normalized.slice(home.length);
2244
+ }
2245
+ return normalized;
2246
+ }
2247
+ function safeDelimiter(base, content) {
2248
+ let delimiter = base;
2249
+ while (content.includes(`
2250
+ ${delimiter}
2251
+ `) || content.startsWith(`${delimiter}
2252
+ `) || content.endsWith(`
2253
+ ${delimiter}`)) {
2254
+ delimiter = `${base}_${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
2255
+ }
2256
+ return delimiter;
2257
+ }
2258
+ function generateExportScript(repoDir, mcpRaw, agents) {
2259
+ const lines = [];
2260
+ lines.push("#!/bin/bash");
2261
+ lines.push("# Generated by agents-anywhere export");
2262
+ lines.push(
2263
+ "# Sets up agent configs without requiring agents-anywhere to be installed."
2264
+ );
2265
+ lines.push("# Usage: bash install.sh");
2266
+ lines.push("");
2267
+ lines.push("set -euo pipefail");
2268
+ lines.push("");
2269
+ const portableRepoDir = makePortablePath(repoDir);
2270
+ lines.push(`REPO_DIR="${portableRepoDir}"`);
2271
+ lines.push("");
2272
+ lines.push("# Create config repo directory structure");
2273
+ lines.push('mkdir -p "$REPO_DIR"');
2274
+ for (const agent of agents) {
2275
+ lines.push(`mkdir -p "$REPO_DIR/${agent.agentDef.id}"`);
2276
+ }
2277
+ lines.push("");
2278
+ const mcpDelimiter = safeDelimiter("AGENTSYNC_MCP_EOF", mcpRaw);
2279
+ lines.push("# Write normalized mcp.json");
2280
+ lines.push(`cat > "$REPO_DIR/mcp.json" << '${mcpDelimiter}'`);
2281
+ lines.push(mcpRaw.trimEnd());
2282
+ lines.push(mcpDelimiter);
2283
+ lines.push("");
2284
+ for (const agent of agents) {
2285
+ const eofBase = `AGENTSYNC_EOF_${agent.agentDef.id.toUpperCase().replace(/-/g, "_")}`;
2286
+ const eof = safeDelimiter(eofBase, agent.mcpContent);
2287
+ const portableConfigDir = makePortablePath(agent.configDir);
2288
+ const portableMcpTarget = makePortablePath(agent.mcpTargetPath);
2289
+ lines.push(`# --- ${agent.agentDef.name} ---`);
2290
+ lines.push(`AGENT_CONFIG_DIR="${portableConfigDir}"`);
2291
+ lines.push('mkdir -p "$AGENT_CONFIG_DIR"');
2292
+ lines.push("");
2293
+ lines.push(`cat > "${portableMcpTarget}" << '${eof}'`);
2294
+ lines.push(agent.mcpContent.trimEnd());
2295
+ lines.push(eof);
2296
+ lines.push("");
2297
+ if (agent.portableItems.length > 0) {
2298
+ lines.push(`# Symlinks for ${agent.agentDef.name}`);
2299
+ for (const item of agent.portableItems) {
2300
+ lines.push(
2301
+ `if [ -e "$REPO_DIR/${agent.agentDef.id}/${item}" ]; then`
2302
+ );
2303
+ lines.push(` DEST="$AGENT_CONFIG_DIR/${item}"`);
2304
+ lines.push(` SRC="$REPO_DIR/${agent.agentDef.id}/${item}"`);
2305
+ lines.push(' if [ -L "$DEST" ]; then');
2306
+ lines.push(' rm "$DEST"');
2307
+ lines.push(' elif [ -e "$DEST" ]; then');
2308
+ lines.push(' mv "$DEST" "$DEST.backup.$(date +%s)"');
2309
+ lines.push(" fi");
2310
+ lines.push(' ln -s "$SRC" "$DEST"');
2311
+ lines.push("fi");
2312
+ }
2313
+ lines.push("");
2314
+ }
2315
+ lines.push(`echo "Done: ${agent.agentDef.name}"`);
2316
+ lines.push("");
2317
+ }
2318
+ lines.push(`echo "${agents.length} agent(s) configured from $REPO_DIR"`);
2319
+ return lines.join("\n") + "\n";
2320
+ }
2321
+
2322
+ // src/commands/enable.ts
2323
+ async function enableCommand(agentId) {
2324
+ const manifest = loadManifest();
2325
+ if (!manifest) return;
2326
+ const agentDef = await loadAgentById(agentId);
2327
+ if (!agentDef) {
2328
+ error(`Unknown agent: ${agentId}`);
2329
+ const all = await loadAllAgentDefinitions();
2330
+ info("Known agents: " + all.map((a) => a.id).join(", "));
2331
+ return;
2332
+ }
2333
+ const entry = manifest.agents[agentId];
2334
+ if (!entry) {
2335
+ manifest.agents[agentId] = { enabled: true, name: agentDef.name };
2336
+ saveManifest(manifest);
2337
+ success(`Enabled ${agentDef.name} (added to manifest)`);
2338
+ return;
2339
+ }
2340
+ if (entry.enabled) {
2341
+ info(`${agentDef.name} is already enabled`);
2342
+ return;
2343
+ }
2344
+ entry.enabled = true;
2345
+ saveManifest(manifest);
2346
+ success(`Enabled ${agentDef.name}`);
2347
+ }
2348
+
2349
+ // src/commands/disable.ts
2350
+ async function disableCommand(agentId) {
2351
+ const manifest = loadManifest();
2352
+ if (!manifest) return;
2353
+ const agentDef = await loadAgentById(agentId);
2354
+ if (!agentDef) {
2355
+ error(`Unknown agent: ${agentId}`);
2356
+ const all = await loadAllAgentDefinitions();
2357
+ info("Known agents: " + all.map((a) => a.id).join(", "));
2358
+ return;
2359
+ }
2360
+ const entry = manifest.agents[agentId];
2361
+ if (!entry) {
2362
+ error(`Agent "${agentId}" is not in the manifest. Run \`agents-anywhere init\` first.`);
2363
+ return;
2364
+ }
2365
+ if (!entry.enabled) {
2366
+ info(`${agentDef.name} is already disabled`);
2367
+ return;
2368
+ }
2369
+ entry.enabled = false;
2370
+ saveManifest(manifest);
2371
+ success(`Disabled ${agentDef.name}`);
2372
+ }
2373
+
2374
+ // src/commands/mcp-remove.ts
2375
+ var fs10 = __toESM(require("fs"));
2376
+ var import_node_path14 = __toESM(require("path"));
2377
+ async function mcpRemoveCommand(name) {
2378
+ const manifest = loadManifest();
2379
+ if (!manifest) return;
2380
+ const mcpPath = import_node_path14.default.join(manifest.repoDir, "mcp.json");
2381
+ let config;
2382
+ try {
2383
+ config = parseMCPConfig(mcpPath);
2384
+ } catch (err) {
2385
+ error(`Failed to parse mcp.json: ${err.message}`);
2386
+ return;
2387
+ }
2388
+ if (!config.servers[name]) {
2389
+ warn(`Server "${name}" not found in mcp.json`);
2390
+ return;
2391
+ }
2392
+ delete config.servers[name];
2393
+ fs10.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2394
+ success(`Removed server "${name}" from mcp.json`);
2395
+ info("Run `agents-anywhere mcp sync` to update per-agent configs.");
2396
+ }
2397
+
2398
+ // src/commands/push.ts
2399
+ var import_simple_git2 = require("simple-git");
2400
+ async function pushCommand(opts = {}) {
2401
+ const manifest = loadManifest();
2402
+ if (!manifest) return;
2403
+ const git = (0, import_simple_git2.simpleGit)(manifest.repoDir);
2404
+ const status = await git.status();
2405
+ if (status.isClean()) {
2406
+ info("No changes to push.");
2407
+ return;
2408
+ }
2409
+ heading("Changes to commit:");
2410
+ const categories = [
2411
+ [status.created, "new file", green],
2412
+ [status.not_added, "new file", green],
2413
+ [status.modified, "modified", yellow],
2414
+ [status.deleted, "deleted", red],
2415
+ [status.renamed.map((r) => `${r.from} \u2192 ${r.to}`), "renamed", cyan]
2416
+ ];
2417
+ for (const [files, label, colorFn] of categories) {
2418
+ for (const file of files) {
2419
+ console.log(` ${colorFn(label)}: ${file}`);
2420
+ }
2421
+ }
2422
+ console.log();
2423
+ if (opts.dryRun) {
2424
+ info("Dry run \u2014 no changes made.");
2425
+ return;
2426
+ }
2427
+ try {
2428
+ await git.add("-A");
2429
+ const commitMsg = opts.message ?? "Update agent configs";
2430
+ const summary = await git.commit(commitMsg);
2431
+ success(
2432
+ `Committed: ${summary.summary.changes} file(s) changed`
2433
+ );
2434
+ } catch (err) {
2435
+ error(`Commit failed: ${err.message}`);
2436
+ return;
2437
+ }
2438
+ try {
2439
+ const remotes = await git.getRemotes();
2440
+ if (remotes.length === 0) {
2441
+ warn("No remote configured. Add one with: git remote add origin <url>");
2442
+ return;
2443
+ }
2444
+ await git.push();
2445
+ success("Pushed to remote.");
2446
+ } catch (err) {
2447
+ error(`Push failed: ${err.message}`);
2448
+ }
2449
+ }
2450
+
2451
+ // src/commands/pull.ts
2452
+ var import_simple_git3 = require("simple-git");
2453
+ async function pullCommand() {
2454
+ const manifest = loadManifest();
2455
+ if (!manifest) return;
2456
+ const git = (0, import_simple_git3.simpleGit)(manifest.repoDir);
2457
+ heading("Pulling config changes...");
2458
+ try {
2459
+ const remotes = await git.getRemotes();
2460
+ if (remotes.length === 0) {
2461
+ error(
2462
+ "No remote configured. Add one with: git remote add origin <url>"
2463
+ );
2464
+ return;
2465
+ }
2466
+ const result = await git.pull();
2467
+ if (result.summary.changes === 0) {
2468
+ info("Already up to date.");
2469
+ } else {
2470
+ success(
2471
+ `Pulled ${result.summary.changes} change(s). Post-merge hook will re-link configs.`
2472
+ );
2473
+ }
2474
+ } catch (err) {
2475
+ error(`Pull failed: ${err.message}`);
2476
+ }
2477
+ }
2478
+
2479
+ // src/cli.ts
2480
+ var program = new import_commander.Command();
2481
+ program.name("agents-anywhere").description(
2482
+ "Your AI agent configs, skills, and instructions \u2014 on every device."
2483
+ ).version(version).option("--verbose", "Show debug output for troubleshooting");
2484
+ program.hook("preAction", () => {
2485
+ if (program.opts().verbose) {
2486
+ setVerbose(true);
2487
+ }
2488
+ });
2489
+ program.command("init").description(
2490
+ "Detect installed agents, create config repo, and scaffold structure"
2491
+ ).argument("[dir]", "Config repo directory (default: ~/agents-anywhere-config)").option("--from <url>", "Clone an existing agents-anywhere config repo from a git URL").action(async (dir, opts) => {
2492
+ await initCommand(dir, { from: opts?.from });
2493
+ });
2494
+ program.command("link [agent]").description("Link agent configs from central repo to agent config dirs").option("--dry-run", "Show what would be linked without making changes").action(async (agent, opts) => {
2495
+ await linkCommand(agent, { dryRun: opts.dryRun });
2496
+ });
2497
+ program.command("unlink [agent]").description("Unlink agent configs and restore backups").option("--dry-run", "Show what would be unlinked without making changes").action(async (agent, opts) => {
2498
+ await unlinkCommand(agent, { dryRun: opts.dryRun });
2499
+ });
2500
+ program.command("status").description("Show link status for all agents and their config files").action(async () => {
2501
+ await statusCommand();
2502
+ });
2503
+ program.command("agents").description("List all known agents with install and link status").action(async () => {
2504
+ await agentsCommand();
2505
+ });
2506
+ program.command("enable <agent>").description("Enable an agent in the manifest").action(async (agent) => {
2507
+ await enableCommand(agent);
2508
+ });
2509
+ program.command("disable <agent>").description("Disable an agent in the manifest").action(async (agent) => {
2510
+ await disableCommand(agent);
2511
+ });
2512
+ program.command("doctor").description("Diagnose config health: broken symlinks, credentials, stale configs").action(async () => {
2513
+ await doctorCommand();
2514
+ });
2515
+ program.command("validate").description("Validate all bundled agent definition JSON files against the schema").action(async () => {
2516
+ await validateCommand();
2517
+ });
2518
+ program.command("export").description(
2519
+ "Generate a standalone install script (pure bash, no agents-anywhere needed)"
2520
+ ).action(async () => {
2521
+ await exportCommand();
2522
+ });
2523
+ program.command("push").description("Stage, commit, and push config changes to remote").option("--dry-run", "Show what would be committed without making changes").option("-m, --message <msg>", "Custom commit message").action(async (opts) => {
2524
+ await pushCommand(opts);
2525
+ });
2526
+ program.command("pull").description("Pull config changes from remote (post-merge hook re-links)").action(async () => {
2527
+ await pullCommand();
2528
+ });
2529
+ var mcp = program.command("mcp").description("MCP server configuration management");
2530
+ mcp.command("sync").description(
2531
+ "Generate per-agent MCP configs from the normalized mcp.json"
2532
+ ).option("--dry-run", "Show what would be written without making changes").action(async (opts) => {
2533
+ await mcpSyncCommand({ dryRun: opts.dryRun });
2534
+ });
2535
+ mcp.command("add <name>").description("Add an MCP server to the normalized mcp.json").option("--transport <type>", "Transport type: stdio or http").option("--command <cmd>", "Command to run (stdio transport)").option("--url <url>", "Server URL (http transport)").option("--args <csv>", "Comma-separated arguments (stdio transport)").option("--env <pair...>", "Environment variables as KEY=VAR pairs").action(async (name, opts) => {
2536
+ await mcpAddCommand(name, opts);
2537
+ });
2538
+ mcp.command("remove <name>").description("Remove an MCP server from mcp.json").action(async (name) => {
2539
+ await mcpRemoveCommand(name);
2540
+ });
2541
+ mcp.command("diff").description("Preview what `mcp sync` would change for each agent").action(async () => {
2542
+ await mcpDiffCommand();
2543
+ });
2544
+ mcp.command("list").description("List all configured MCP servers").action(async () => {
2545
+ await mcpListCommand();
2546
+ });
2547
+ program.parseAsync().catch((err) => {
2548
+ console.error(err.message);
2549
+ process.exit(1);
2550
+ });