asdm-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +784 -0
  2. package/dist/index.mjs +3013 -0
  3. package/package.json +58 -0
  4. package/registry/agents/.gitkeep +0 -0
  5. package/registry/agents/architect.asdm.md +71 -0
  6. package/registry/agents/code-reviewer.asdm.md +75 -0
  7. package/registry/agents/data-analyst.asdm.md +69 -0
  8. package/registry/agents/documentation-writer.asdm.md +70 -0
  9. package/registry/agents/mobile-engineer.asdm.md +68 -0
  10. package/registry/agents/security-auditor.asdm.md +74 -0
  11. package/registry/agents/test-engineer.asdm.md +70 -0
  12. package/registry/commands/.gitkeep +0 -0
  13. package/registry/commands/analyze-schema.asdm.md +73 -0
  14. package/registry/commands/audit-deps.asdm.md +76 -0
  15. package/registry/commands/check-file.asdm.md +58 -0
  16. package/registry/commands/generate-types.asdm.md +82 -0
  17. package/registry/commands/scaffold-component.asdm.md +89 -0
  18. package/registry/commands/summarize.asdm.md +61 -0
  19. package/registry/latest.json +253 -0
  20. package/registry/policy.yaml +14 -0
  21. package/registry/profiles/base/.gitkeep +0 -0
  22. package/registry/profiles/base/profile.yaml +19 -0
  23. package/registry/profiles/data-analytics/.gitkeep +0 -0
  24. package/registry/profiles/data-analytics/profile.yaml +24 -0
  25. package/registry/profiles/fullstack-engineer/.gitkeep +0 -0
  26. package/registry/profiles/fullstack-engineer/profile.yaml +38 -0
  27. package/registry/profiles/mobile/ios/.gitkeep +0 -0
  28. package/registry/profiles/mobile/profile.yaml +24 -0
  29. package/registry/profiles/security/profile.yaml +24 -0
  30. package/registry/skills/api-design/.gitkeep +0 -0
  31. package/registry/skills/api-design/SKILL.asdm.md +101 -0
  32. package/registry/skills/code-review/SKILL.asdm.md +83 -0
  33. package/registry/skills/data-pipeline/SKILL.asdm.md +95 -0
  34. package/registry/skills/frontend-design/SKILL.asdm.md +73 -0
  35. package/registry/skills/mobile-patterns/SKILL.asdm.md +102 -0
  36. package/registry/skills/pandas/.gitkeep +0 -0
  37. package/registry/skills/plan-protocol/SKILL.asdm.md +66 -0
  38. package/registry/skills/react-best-practices/.gitkeep +0 -0
  39. package/registry/skills/react-native/.gitkeep +0 -0
  40. package/registry/skills/sql/.gitkeep +0 -0
  41. package/registry/skills/swift-ui/.gitkeep +0 -0
  42. package/registry/skills/threat-modeling/SKILL.asdm.md +87 -0
  43. package/registry/v0.1.0.json +253 -0
  44. package/registry/v1.0.0.json +153 -0
  45. package/schemas/.gitkeep +0 -0
  46. package/schemas/agent.schema.json +82 -0
  47. package/schemas/command.schema.json +58 -0
  48. package/schemas/config.schema.json +29 -0
  49. package/schemas/lock.schema.json +65 -0
  50. package/schemas/manifest.schema.json +98 -0
  51. package/schemas/overlay.schema.json +72 -0
  52. package/schemas/profile.schema.json +64 -0
  53. package/schemas/skill.schema.json +64 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3013 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/utils/fs.ts
13
+ var fs_exports = {};
14
+ __export(fs_exports, {
15
+ copyFile: () => copyFile,
16
+ ensureDir: () => ensureDir,
17
+ exists: () => exists,
18
+ getAsdmCacheDir: () => getAsdmCacheDir,
19
+ getAsdmConfigDir: () => getAsdmConfigDir,
20
+ listFiles: () => listFiles,
21
+ normalizePath: () => normalizePath,
22
+ readFile: () => readFile,
23
+ readJson: () => readJson,
24
+ removeFile: () => removeFile,
25
+ writeFile: () => writeFile,
26
+ writeJson: () => writeJson
27
+ });
28
+ import { promises as fs } from "fs";
29
+ import path from "path";
30
+ import os from "os";
31
+ function normalizePath(p) {
32
+ return p.replace(/\\/g, "/");
33
+ }
34
+ async function ensureDir(dirPath) {
35
+ await fs.mkdir(dirPath, { recursive: true });
36
+ }
37
+ async function writeFile(filePath, content) {
38
+ await ensureDir(path.dirname(filePath));
39
+ await fs.writeFile(filePath, content, "utf-8");
40
+ }
41
+ async function readFile(filePath) {
42
+ try {
43
+ return await fs.readFile(filePath, "utf-8");
44
+ } catch (err) {
45
+ if (err.code === "ENOENT") return null;
46
+ throw err;
47
+ }
48
+ }
49
+ async function exists(filePath) {
50
+ try {
51
+ await fs.access(filePath);
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ async function removeFile(filePath) {
58
+ try {
59
+ await fs.unlink(filePath);
60
+ } catch (err) {
61
+ if (err.code !== "ENOENT") throw err;
62
+ }
63
+ }
64
+ async function listFiles(dirPath) {
65
+ const results = [];
66
+ async function walk(current) {
67
+ const entries = await fs.readdir(current, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ const fullPath = path.join(current, entry.name);
70
+ if (entry.isDirectory()) {
71
+ await walk(fullPath);
72
+ } else {
73
+ results.push(fullPath);
74
+ }
75
+ }
76
+ }
77
+ await walk(dirPath);
78
+ return results;
79
+ }
80
+ function getAsdmConfigDir() {
81
+ return path.join(os.homedir(), ".config", "asdm");
82
+ }
83
+ function getAsdmCacheDir() {
84
+ const xdgCache = process.env["XDG_CACHE_HOME"];
85
+ if (xdgCache) return path.join(xdgCache, "asdm");
86
+ return path.join(os.homedir(), ".cache", "asdm");
87
+ }
88
+ async function readJson(filePath) {
89
+ const content = await readFile(filePath);
90
+ if (content === null) return null;
91
+ return JSON.parse(content);
92
+ }
93
+ async function writeJson(filePath, data) {
94
+ await writeFile(filePath, JSON.stringify(data, null, 2));
95
+ }
96
+ async function copyFile(src, dest) {
97
+ await ensureDir(path.dirname(dest));
98
+ await fs.copyFile(src, dest);
99
+ }
100
+ var init_fs = __esm({
101
+ "src/utils/fs.ts"() {
102
+ "use strict";
103
+ }
104
+ });
105
+
106
+ // src/core/hash.ts
107
+ import { createHash } from "crypto";
108
+ import { promises as fs2 } from "fs";
109
+ import os2 from "os";
110
+ function hashString(content) {
111
+ return createHash("sha256").update(content, "utf-8").digest("hex");
112
+ }
113
+ function hashBuffer(buf) {
114
+ return createHash("sha256").update(buf).digest("hex");
115
+ }
116
+ async function hashFile(filePath) {
117
+ const content = await fs2.readFile(filePath);
118
+ return hashBuffer(content);
119
+ }
120
+ function machineId() {
121
+ const hostname = os2.hostname();
122
+ const username = os2.userInfo().username;
123
+ return hashString(`${hostname}:${username}`).slice(0, 12);
124
+ }
125
+ var init_hash = __esm({
126
+ "src/core/hash.ts"() {
127
+ "use strict";
128
+ }
129
+ });
130
+
131
+ // src/adapters/base.ts
132
+ function createEmittedFile(relativePath, content, adapter, sourcePath) {
133
+ return {
134
+ relativePath,
135
+ content,
136
+ sha256: hashString(content),
137
+ adapter,
138
+ sourcePath
139
+ };
140
+ }
141
+ function managedFileHeader(provider) {
142
+ return [
143
+ `# \u26A0\uFE0F ASDM MANAGED FILE \u2014 DO NOT EDIT MANUALLY`,
144
+ `# This file is generated by asdm. Changes will be overwritten on next sync.`,
145
+ `# To modify: edit the source in your asdm-registry and run \`asdm sync\`.`,
146
+ `# Provider: ${provider}`,
147
+ `#`
148
+ ].join("\n");
149
+ }
150
+ function managedJsonHeader(provider) {
151
+ return [
152
+ `// \u26A0\uFE0F ASDM MANAGED FILE \u2014 DO NOT EDIT MANUALLY`,
153
+ `// This file is generated by asdm. Changes will be overwritten on next sync.`,
154
+ `// To modify: edit the source in your asdm-registry and run \`asdm sync\`.`,
155
+ `// Provider: ${provider}`,
156
+ `//`
157
+ ].join("\n");
158
+ }
159
+ var init_base = __esm({
160
+ "src/adapters/base.ts"() {
161
+ "use strict";
162
+ init_hash();
163
+ }
164
+ });
165
+
166
+ // src/adapters/opencode.ts
167
+ var opencode_exports = {};
168
+ __export(opencode_exports, {
169
+ OpenCodeAdapter: () => OpenCodeAdapter,
170
+ createOpenCodeAdapter: () => createOpenCodeAdapter
171
+ });
172
+ import path8 from "path";
173
+ async function detectOcx() {
174
+ const os4 = await import("os");
175
+ const home = os4.homedir();
176
+ const ocxPaths = [
177
+ path8.join(home, ".config", "ocx"),
178
+ path8.join(home, ".ocx")
179
+ ];
180
+ for (const p of ocxPaths) {
181
+ if (await exists(p)) return true;
182
+ }
183
+ return false;
184
+ }
185
+ function formatAgentContent(parsed) {
186
+ const lines = [
187
+ managedFileHeader(ADAPTER_NAME),
188
+ ""
189
+ ];
190
+ lines.push(parsed.body);
191
+ return lines.join("\n");
192
+ }
193
+ function formatSkillContent(parsed) {
194
+ return [
195
+ managedFileHeader(ADAPTER_NAME),
196
+ "",
197
+ parsed.body
198
+ ].join("\n");
199
+ }
200
+ function formatCommandContent(parsed) {
201
+ return [
202
+ managedFileHeader(ADAPTER_NAME),
203
+ "",
204
+ parsed.body
205
+ ].join("\n");
206
+ }
207
+ function generateOpencodeConfig(profile, hasOcx) {
208
+ const providerConfig = profile.provider_config["opencode"] ?? {};
209
+ const config = {};
210
+ if (providerConfig.theme) {
211
+ config["theme"] = providerConfig.theme;
212
+ }
213
+ if (providerConfig.mcp_servers && providerConfig.mcp_servers.length > 0) {
214
+ config["mcp"] = {
215
+ servers: providerConfig.mcp_servers.reduce((acc, srv) => {
216
+ acc[srv.name] = {
217
+ command: srv.command,
218
+ args: srv.args ?? []
219
+ };
220
+ return acc;
221
+ }, {})
222
+ };
223
+ }
224
+ if (hasOcx) {
225
+ config["$schema"] = "https://opencode.ai/config.json";
226
+ }
227
+ const json = JSON.stringify(config, null, 2);
228
+ return `${managedJsonHeader(ADAPTER_NAME)}
229
+ ${json}
230
+ `;
231
+ }
232
+ function generateAgentsMd(profile) {
233
+ const lines = [
234
+ managedFileHeader(ADAPTER_NAME),
235
+ "",
236
+ "# AI Agent Instructions",
237
+ "",
238
+ "This project uses ASDM-managed AI agents and skills.",
239
+ ""
240
+ ];
241
+ if (profile.agents.length > 0) {
242
+ lines.push("## Available Agents", "");
243
+ for (const agent of profile.agents) {
244
+ lines.push(`- **${agent}**: See \`.opencode/agents/${agent}.md\``);
245
+ }
246
+ lines.push("");
247
+ }
248
+ if (profile.skills.length > 0) {
249
+ lines.push("<available_skills>");
250
+ for (const skill of profile.skills) {
251
+ lines.push(` <skill location=".opencode/skills/${skill}/SKILL.md" />`);
252
+ }
253
+ lines.push("</available_skills>");
254
+ lines.push("");
255
+ }
256
+ if (profile.commands.length > 0) {
257
+ lines.push("## Slash Commands", "");
258
+ for (const cmd of profile.commands) {
259
+ lines.push(`- \`/${cmd}\`: See \`.opencode/commands/${cmd}.md\``);
260
+ }
261
+ lines.push("");
262
+ }
263
+ return lines.join("\n");
264
+ }
265
+ function createOpenCodeAdapter() {
266
+ return new OpenCodeAdapter();
267
+ }
268
+ var ADAPTER_NAME, OpenCodeAdapter;
269
+ var init_opencode = __esm({
270
+ "src/adapters/opencode.ts"() {
271
+ "use strict";
272
+ init_fs();
273
+ init_base();
274
+ ADAPTER_NAME = "opencode";
275
+ OpenCodeAdapter = class {
276
+ name = ADAPTER_NAME;
277
+ ocxDetected = null;
278
+ async isOcxAvailable() {
279
+ if (this.ocxDetected === null) {
280
+ this.ocxDetected = await detectOcx();
281
+ }
282
+ return this.ocxDetected;
283
+ }
284
+ emitAgent(parsed, _targetDir) {
285
+ const relativePath = `.opencode/agents/${parsed.name}.md`;
286
+ const content = formatAgentContent(parsed);
287
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME, parsed.sourcePath)];
288
+ }
289
+ emitSkill(parsed, _targetDir) {
290
+ const relativePath = `.opencode/skills/${parsed.name}/SKILL.md`;
291
+ const content = formatSkillContent(parsed);
292
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME, parsed.sourcePath)];
293
+ }
294
+ emitCommand(parsed, _targetDir) {
295
+ const relativePath = `.opencode/commands/${parsed.name}.md`;
296
+ const content = formatCommandContent(parsed);
297
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME, parsed.sourcePath)];
298
+ }
299
+ emitRootInstructions(profile, _targetDir) {
300
+ const content = generateAgentsMd(profile);
301
+ return [createEmittedFile("AGENTS.md", content, ADAPTER_NAME, "root")];
302
+ }
303
+ async emitConfigAsync(profile, _targetDir) {
304
+ const hasOcx = await this.isOcxAvailable();
305
+ const content = generateOpencodeConfig(profile, hasOcx);
306
+ const relativePath = ".opencode/opencode.jsonc";
307
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME, "config")];
308
+ }
309
+ // Synchronous version (OCX detection skipped — used when sync is not needed)
310
+ emitConfig(profile, _targetDir) {
311
+ const content = generateOpencodeConfig(profile, false);
312
+ const relativePath = ".opencode/opencode.jsonc";
313
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME, "config")];
314
+ }
315
+ };
316
+ }
317
+ });
318
+
319
+ // src/adapters/claude-code.ts
320
+ var claude_code_exports = {};
321
+ __export(claude_code_exports, {
322
+ ClaudeCodeAdapter: () => ClaudeCodeAdapter,
323
+ createClaudeCodeAdapter: () => createClaudeCodeAdapter
324
+ });
325
+ import path9 from "path";
326
+ function formatAgentContent2(parsed) {
327
+ return [managedFileHeader(ADAPTER_NAME2), "", parsed.body].join("\n");
328
+ }
329
+ function formatSkillContent2(parsed) {
330
+ return [managedFileHeader(ADAPTER_NAME2), "", parsed.body].join("\n");
331
+ }
332
+ function formatCommandContent2(parsed) {
333
+ return [managedFileHeader(ADAPTER_NAME2), "", parsed.body].join("\n");
334
+ }
335
+ function generateClaudeMd(profile) {
336
+ const lines = [
337
+ managedFileHeader(ADAPTER_NAME2),
338
+ "",
339
+ "# AI Assistant Configuration",
340
+ ""
341
+ ];
342
+ if (profile.agents.length > 0) {
343
+ lines.push("## Active Agents", "");
344
+ for (const agent of profile.agents) {
345
+ lines.push(`- **${agent}**: See \`.claude/agents/${agent}.md\``);
346
+ }
347
+ lines.push("");
348
+ }
349
+ if (profile.skills.length > 0) {
350
+ lines.push("## Available Skills", "");
351
+ lines.push("The following skills are available:", "");
352
+ lines.push("<available_skills>");
353
+ for (const skill of profile.skills) {
354
+ lines.push(` <skill>`);
355
+ lines.push(` <name>${skill}</name>`);
356
+ lines.push(` <location>.claude/skills/${skill}/SKILL.md</location>`);
357
+ lines.push(` </skill>`);
358
+ }
359
+ lines.push("</available_skills>");
360
+ lines.push("");
361
+ }
362
+ if (profile.commands.length > 0) {
363
+ lines.push("## Slash Commands", "");
364
+ for (const cmd of profile.commands) {
365
+ lines.push(`- \`/${cmd}\`: See \`.claude/commands/${cmd}.md\``);
366
+ }
367
+ lines.push("");
368
+ }
369
+ return lines.join("\n");
370
+ }
371
+ function createClaudeCodeAdapter() {
372
+ return new ClaudeCodeAdapter();
373
+ }
374
+ var ADAPTER_NAME2, ClaudeCodeAdapter;
375
+ var init_claude_code = __esm({
376
+ "src/adapters/claude-code.ts"() {
377
+ "use strict";
378
+ init_fs();
379
+ init_base();
380
+ ADAPTER_NAME2 = "claude-code";
381
+ ClaudeCodeAdapter = class {
382
+ name = ADAPTER_NAME2;
383
+ emitAgent(parsed, _targetDir) {
384
+ const relativePath = `.claude/agents/${parsed.name}.md`;
385
+ const content = formatAgentContent2(parsed);
386
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME2, parsed.sourcePath)];
387
+ }
388
+ emitSkill(parsed, _targetDir) {
389
+ const relativePath = `.claude/skills/${parsed.name}/SKILL.md`;
390
+ const content = formatSkillContent2(parsed);
391
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME2, parsed.sourcePath)];
392
+ }
393
+ emitCommand(parsed, _targetDir) {
394
+ const relativePath = `.claude/commands/${parsed.name}.md`;
395
+ const content = formatCommandContent2(parsed);
396
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME2, parsed.sourcePath)];
397
+ }
398
+ emitRootInstructions(profile, _targetDir) {
399
+ const content = generateClaudeMd(profile);
400
+ return [createEmittedFile(".claude/CLAUDE.md", content, ADAPTER_NAME2, "root")];
401
+ }
402
+ emitConfig(_profile, _targetDir) {
403
+ return [];
404
+ }
405
+ /**
406
+ * Remove all ASDM-managed files from .claude/ in the given project root.
407
+ * Returns the list of absolute paths that were removed.
408
+ */
409
+ async clean(projectRoot) {
410
+ const claudeDir = path9.join(projectRoot, ".claude");
411
+ if (!await exists(claudeDir)) return [];
412
+ const allFiles = await listFiles(claudeDir);
413
+ const removedPaths = [];
414
+ for (const filePath of allFiles) {
415
+ const content = await readFile(filePath);
416
+ if (content && content.includes("ASDM MANAGED FILE")) {
417
+ await removeFile(filePath);
418
+ removedPaths.push(filePath);
419
+ }
420
+ }
421
+ return removedPaths;
422
+ }
423
+ };
424
+ }
425
+ });
426
+
427
+ // src/adapters/copilot.ts
428
+ var copilot_exports = {};
429
+ __export(copilot_exports, {
430
+ CopilotAdapter: () => CopilotAdapter,
431
+ createCopilotAdapter: () => createCopilotAdapter
432
+ });
433
+ import path10 from "path";
434
+ function formatAgentContent3(parsed) {
435
+ const frontmatter = [
436
+ "---",
437
+ `name: ${parsed.name}`,
438
+ `description: ${parsed.description}`,
439
+ "---",
440
+ ""
441
+ ].join("\n");
442
+ return [managedFileHeader(ADAPTER_NAME3), "", frontmatter, parsed.body].join("\n");
443
+ }
444
+ function formatSkillContent3(parsed) {
445
+ return [managedFileHeader(ADAPTER_NAME3), "", parsed.body].join("\n");
446
+ }
447
+ function generateCopilotInstructions(profile) {
448
+ const lines = [
449
+ managedFileHeader(ADAPTER_NAME3),
450
+ "",
451
+ "# GitHub Copilot Instructions",
452
+ ""
453
+ ];
454
+ if (profile.agents.length > 0) {
455
+ lines.push("## Active Agents", "");
456
+ for (const agent of profile.agents) {
457
+ lines.push(`- **${agent}**: See \`.github/agents/${agent}.agent.md\``);
458
+ }
459
+ lines.push("");
460
+ }
461
+ if (profile.commands.length > 0) {
462
+ lines.push("## Commands Available", "");
463
+ for (const cmd of profile.commands) {
464
+ lines.push(`- **${cmd}**`);
465
+ }
466
+ lines.push("");
467
+ }
468
+ if (profile.skills.length > 0) {
469
+ lines.push("## Skills", "");
470
+ lines.push("The following skills are configured for this workspace:");
471
+ for (const skill of profile.skills) {
472
+ lines.push(`- **${skill}**: See \`.github/skills/${skill}/SKILL.md\``);
473
+ }
474
+ lines.push("");
475
+ }
476
+ return lines.join("\n");
477
+ }
478
+ function createCopilotAdapter() {
479
+ return new CopilotAdapter();
480
+ }
481
+ var ADAPTER_NAME3, CopilotAdapter;
482
+ var init_copilot = __esm({
483
+ "src/adapters/copilot.ts"() {
484
+ "use strict";
485
+ init_fs();
486
+ init_base();
487
+ ADAPTER_NAME3 = "copilot";
488
+ CopilotAdapter = class {
489
+ name = ADAPTER_NAME3;
490
+ emitAgent(parsed, _targetDir) {
491
+ const relativePath = `.github/agents/${parsed.name}.agent.md`;
492
+ const content = formatAgentContent3(parsed);
493
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME3, parsed.sourcePath)];
494
+ }
495
+ emitSkill(parsed, _targetDir) {
496
+ const relativePath = `.github/skills/${parsed.name}/SKILL.md`;
497
+ const content = formatSkillContent3(parsed);
498
+ return [createEmittedFile(relativePath, content, ADAPTER_NAME3, parsed.sourcePath)];
499
+ }
500
+ emitCommand(_parsed, _targetDir) {
501
+ return [];
502
+ }
503
+ emitRootInstructions(profile, _targetDir) {
504
+ const content = generateCopilotInstructions(profile);
505
+ return [
506
+ createEmittedFile(".github/copilot-instructions.md", content, ADAPTER_NAME3, "root")
507
+ ];
508
+ }
509
+ emitConfig(_profile, _targetDir) {
510
+ return [];
511
+ }
512
+ /**
513
+ * Remove all ASDM-managed files from .github/ in the given project root.
514
+ * Returns the list of absolute paths that were removed.
515
+ */
516
+ async clean(projectRoot) {
517
+ const githubDir = path10.join(projectRoot, ".github");
518
+ if (!await exists(githubDir)) return [];
519
+ const allFiles = await listFiles(githubDir);
520
+ const removedPaths = [];
521
+ for (const filePath of allFiles) {
522
+ const content = await readFile(filePath);
523
+ if (content && content.includes("ASDM MANAGED FILE")) {
524
+ await removeFile(filePath);
525
+ removedPaths.push(filePath);
526
+ }
527
+ }
528
+ return removedPaths;
529
+ }
530
+ };
531
+ }
532
+ });
533
+
534
+ // src/cli/index.ts
535
+ import { defineCommand as defineCommand16, runMain } from "citty";
536
+
537
+ // src/cli/commands/init.ts
538
+ init_fs();
539
+ import { defineCommand } from "citty";
540
+ import path5 from "path";
541
+
542
+ // src/utils/gitignore.ts
543
+ init_fs();
544
+ import path2 from "path";
545
+ var ASDM_MARKER_START = "# ASDM MANAGED \u2014 start";
546
+ var ASDM_MARKER_END = "# ASDM MANAGED \u2014 end";
547
+ var PROVIDER_ENTRIES = {
548
+ opencode: [
549
+ ".opencode/agents/",
550
+ ".opencode/skills/",
551
+ ".opencode/commands/"
552
+ ],
553
+ "claude-code": [
554
+ ".claude/agents/",
555
+ ".claude/skills/",
556
+ ".claude/commands/",
557
+ ".claude/CLAUDE.md"
558
+ ],
559
+ copilot: [
560
+ ".github/agents/",
561
+ ".github/skills/",
562
+ ".github/copilot-instructions.md"
563
+ ]
564
+ };
565
+ function getGitignoreEntries(emit) {
566
+ const lines = [];
567
+ for (const provider of emit) {
568
+ const entries = PROVIDER_ENTRIES[provider];
569
+ if (entries) lines.push(...entries);
570
+ }
571
+ return lines;
572
+ }
573
+ function buildBlock(entries) {
574
+ return [ASDM_MARKER_START, ...entries, ASDM_MARKER_END].join("\n");
575
+ }
576
+ function extractExistingBlock(content) {
577
+ const startIdx = content.indexOf(ASDM_MARKER_START);
578
+ const endIdx = content.indexOf(ASDM_MARKER_END);
579
+ if (startIdx === -1 || endIdx === -1) return "";
580
+ return content.slice(startIdx, endIdx + ASDM_MARKER_END.length);
581
+ }
582
+ function replaceBlock(content, newBlock) {
583
+ const startIdx = content.indexOf(ASDM_MARKER_START);
584
+ const endIdx = content.indexOf(ASDM_MARKER_END);
585
+ if (startIdx === -1 || endIdx === -1) return content;
586
+ return content.slice(0, startIdx) + newBlock + content.slice(endIdx + ASDM_MARKER_END.length);
587
+ }
588
+ async function updateGitignore(projectRoot, emit) {
589
+ const gitignorePath = path2.join(projectRoot, ".gitignore");
590
+ const entries = getGitignoreEntries(emit);
591
+ if (entries.length === 0) return false;
592
+ const existing = await readFile(gitignorePath) ?? "";
593
+ const newBlock = buildBlock(entries);
594
+ if (existing.includes(ASDM_MARKER_START)) {
595
+ const currentBlock = extractExistingBlock(existing);
596
+ if (currentBlock === newBlock) return false;
597
+ const newContent2 = replaceBlock(existing, newBlock);
598
+ await writeFile(gitignorePath, newContent2);
599
+ return true;
600
+ }
601
+ const newContent = existing ? existing.trimEnd() + "\n\n" + newBlock + "\n" : newBlock + "\n";
602
+ await writeFile(gitignorePath, newContent);
603
+ return true;
604
+ }
605
+ async function removeGitignoreBlock(projectRoot) {
606
+ const gitignorePath = path2.join(projectRoot, ".gitignore");
607
+ const existing = await readFile(gitignorePath);
608
+ if (existing === null || !existing.includes(ASDM_MARKER_START)) return false;
609
+ const startIdx = existing.indexOf(ASDM_MARKER_START);
610
+ const endIdx = existing.indexOf(ASDM_MARKER_END);
611
+ if (endIdx === -1) return false;
612
+ const before = existing.slice(0, startIdx).trimEnd();
613
+ const after = existing.slice(endIdx + ASDM_MARKER_END.length).trimStart();
614
+ const newContent = before + (after ? "\n" + after : "") + "\n";
615
+ await writeFile(gitignorePath, newContent);
616
+ return true;
617
+ }
618
+
619
+ // src/core/config.ts
620
+ init_fs();
621
+ import path3 from "path";
622
+
623
+ // src/utils/errors.ts
624
+ var AsdmError = class extends Error {
625
+ constructor(message, code, suggestion) {
626
+ super(message);
627
+ this.code = code;
628
+ this.suggestion = suggestion;
629
+ this.name = "AsdmError";
630
+ }
631
+ };
632
+ var ConfigError = class extends AsdmError {
633
+ constructor(message, suggestion) {
634
+ super(message, "CONFIG_ERROR", suggestion);
635
+ this.name = "ConfigError";
636
+ }
637
+ };
638
+ var RegistryError = class extends AsdmError {
639
+ constructor(message, suggestion) {
640
+ super(message, "REGISTRY_ERROR", suggestion);
641
+ this.name = "RegistryError";
642
+ }
643
+ };
644
+ var IntegrityError = class extends AsdmError {
645
+ constructor(message, suggestion) {
646
+ super(message, "INTEGRITY_ERROR", suggestion);
647
+ this.name = "IntegrityError";
648
+ }
649
+ };
650
+ var ParseError = class extends AsdmError {
651
+ constructor(message, suggestion) {
652
+ super(message, "PARSE_ERROR", suggestion);
653
+ this.name = "ParseError";
654
+ }
655
+ };
656
+ var PolicyError = class extends AsdmError {
657
+ constructor(message, suggestion) {
658
+ super(message, "POLICY_ERROR", suggestion);
659
+ this.name = "PolicyError";
660
+ }
661
+ };
662
+ var NetworkError = class extends AsdmError {
663
+ constructor(message, suggestion) {
664
+ super(message, "NETWORK_ERROR", suggestion);
665
+ this.name = "NetworkError";
666
+ }
667
+ };
668
+
669
+ // src/core/config.ts
670
+ var PROJECT_CONFIG_FILE = ".asdm.json";
671
+ var USER_CONFIG_FILE = ".asdm.local.json";
672
+ function parseRegistryUrl(url) {
673
+ const match = url.match(/^github:\/\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)$/);
674
+ if (!match) {
675
+ throw new ConfigError(
676
+ `Invalid registry URL: "${url}"`,
677
+ "Registry URL must be in format: github://{org}/{repo}"
678
+ );
679
+ }
680
+ return { org: match[1], repo: match[2] };
681
+ }
682
+ async function readProjectConfig(cwd) {
683
+ const filePath = path3.join(cwd, PROJECT_CONFIG_FILE);
684
+ const config = await readJson(filePath);
685
+ if (!config) {
686
+ throw new ConfigError(
687
+ `No ${PROJECT_CONFIG_FILE} found in ${cwd}`,
688
+ "Run `asdm init --profile <name>` to initialize"
689
+ );
690
+ }
691
+ if (!config.registry) {
692
+ throw new ConfigError(`Missing required field 'registry' in ${PROJECT_CONFIG_FILE}`);
693
+ }
694
+ if (!config.profile) {
695
+ throw new ConfigError(`Missing required field 'profile' in ${PROJECT_CONFIG_FILE}`);
696
+ }
697
+ parseRegistryUrl(config.registry);
698
+ return config;
699
+ }
700
+ async function readUserConfig(cwd) {
701
+ const filePath = path3.join(cwd, USER_CONFIG_FILE);
702
+ return readJson(filePath);
703
+ }
704
+ async function writeUserConfig(cwd, config) {
705
+ const filePath = path3.join(cwd, USER_CONFIG_FILE);
706
+ await writeJson(filePath, config);
707
+ }
708
+ function resolveConfig(project, user, policy) {
709
+ const profile = user?.profile ?? project.profile;
710
+ const providers = project.providers ?? ["opencode"];
711
+ if (!policy.allowed_profiles.includes(profile)) {
712
+ throw new PolicyError(
713
+ `Profile "${profile}" is not allowed by corporate policy`,
714
+ `Allowed profiles: ${policy.allowed_profiles.join(", ")}
715
+ Run \`asdm profiles\` to see available profiles`
716
+ );
717
+ }
718
+ const disallowedProviders = providers.filter((p) => !policy.allowed_providers.includes(p));
719
+ if (disallowedProviders.length > 0) {
720
+ throw new PolicyError(
721
+ `Provider(s) not allowed by corporate policy: ${disallowedProviders.join(", ")}`,
722
+ `Allowed providers: ${policy.allowed_providers.join(", ")}`
723
+ );
724
+ }
725
+ return {
726
+ registry: project.registry,
727
+ profile,
728
+ providers,
729
+ policy
730
+ };
731
+ }
732
+ async function createProjectConfig(cwd, registry, profile, providers = ["opencode"]) {
733
+ const filePath = path3.join(cwd, PROJECT_CONFIG_FILE);
734
+ const config = {
735
+ $schema: "https://asdm.dev/schemas/config.schema.json",
736
+ registry,
737
+ profile,
738
+ providers
739
+ };
740
+ await writeJson(filePath, config);
741
+ }
742
+
743
+ // src/core/telemetry.ts
744
+ init_hash();
745
+ import path4 from "path";
746
+ import { promises as fs3 } from "fs";
747
+ var TELEMETRY_FILE = ".asdm-telemetry.jsonl";
748
+ var TelemetryWriter = class {
749
+ logPath;
750
+ constructor(projectRoot) {
751
+ this.logPath = path4.join(projectRoot, TELEMETRY_FILE);
752
+ }
753
+ /** Append one telemetry event as a JSONL line */
754
+ async write(event) {
755
+ const fullEvent = {
756
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
757
+ machineId: machineId(),
758
+ version: "0.1.0",
759
+ ...event
760
+ };
761
+ const line = JSON.stringify(fullEvent) + "\n";
762
+ await fs3.appendFile(this.logPath, line, "utf-8");
763
+ }
764
+ /** Read and parse all events, silently skipping malformed lines */
765
+ async readAll() {
766
+ let content;
767
+ try {
768
+ content = await fs3.readFile(this.logPath, "utf-8");
769
+ } catch (err) {
770
+ if (err.code === "ENOENT") return [];
771
+ throw err;
772
+ }
773
+ return content.split("\n").filter((line) => line.trim().length > 0).flatMap((line) => {
774
+ try {
775
+ return [JSON.parse(line)];
776
+ } catch {
777
+ return [];
778
+ }
779
+ });
780
+ }
781
+ /**
782
+ * Rotate the log file, keeping only the last N entries.
783
+ * Prevents unbounded growth — default cap is 1000 events.
784
+ */
785
+ async rotate(maxEntries = 1e3) {
786
+ const entries = await this.readAll();
787
+ if (entries.length <= maxEntries) return;
788
+ const trimmed = entries.slice(entries.length - maxEntries);
789
+ const content = trimmed.map((e) => JSON.stringify(e)).join("\n") + "\n";
790
+ await fs3.writeFile(this.logPath, content, "utf-8");
791
+ }
792
+ /** Delete the telemetry log file */
793
+ async clear() {
794
+ try {
795
+ await fs3.unlink(this.logPath);
796
+ } catch (err) {
797
+ if (err.code !== "ENOENT") throw err;
798
+ }
799
+ }
800
+ };
801
+
802
+ // src/utils/logger.ts
803
+ var RESET = "\x1B[0m";
804
+ var BOLD = "\x1B[1m";
805
+ var DIM = "\x1B[2m";
806
+ var FG_RED = "\x1B[31m";
807
+ var FG_GREEN = "\x1B[32m";
808
+ var FG_YELLOW = "\x1B[33m";
809
+ var FG_BLUE = "\x1B[34m";
810
+ var FG_CYAN = "\x1B[36m";
811
+ var FG_WHITE = "\x1B[37m";
812
+ var FG_GRAY = "\x1B[90m";
813
+ var ICONS = {
814
+ success: "\u2713",
815
+ error: "\u2717",
816
+ warn: "\u26A0",
817
+ info: "\u2139",
818
+ pending: "\u25CB",
819
+ arrow: "\u2192",
820
+ bullet: "\u2022",
821
+ badge_asdm: "asdm"
822
+ };
823
+ var _quiet = false;
824
+ var _verbose = false;
825
+ var logger = {
826
+ setQuiet(q) {
827
+ _quiet = q;
828
+ },
829
+ setVerbose(v) {
830
+ _verbose = v;
831
+ },
832
+ /** Brand prefix for main operations */
833
+ asdm(msg) {
834
+ if (_quiet) return;
835
+ console.log(`${BOLD}${FG_CYAN}${ICONS.badge_asdm}${RESET} ${msg}`);
836
+ },
837
+ success(msg) {
838
+ if (_quiet) return;
839
+ console.log(`${FG_GREEN}${ICONS.success}${RESET} ${msg}`);
840
+ },
841
+ error(msg, suggestion) {
842
+ console.error(`${FG_RED}${ICONS.error}${RESET} ${msg}`);
843
+ if (suggestion) {
844
+ console.error(` ${FG_GRAY}${ICONS.arrow} ${suggestion}${RESET}`);
845
+ }
846
+ },
847
+ warn(msg) {
848
+ if (_quiet) return;
849
+ console.warn(`${FG_YELLOW}${ICONS.warn}${RESET} ${msg}`);
850
+ },
851
+ info(msg) {
852
+ if (_quiet) return;
853
+ console.log(`${FG_BLUE}${ICONS.info}${RESET} ${msg}`);
854
+ },
855
+ dim(msg) {
856
+ if (_quiet) return;
857
+ console.log(`${DIM} ${msg}${RESET}`);
858
+ },
859
+ verbose(msg) {
860
+ if (!_verbose) return;
861
+ console.log(`${FG_GRAY} [verbose] ${msg}${RESET}`);
862
+ },
863
+ bullet(msg, indent = 0) {
864
+ if (_quiet) return;
865
+ const pad = " ".repeat(indent);
866
+ console.log(`${pad}${FG_GRAY}${ICONS.bullet}${RESET} ${msg}`);
867
+ },
868
+ divider() {
869
+ if (_quiet) return;
870
+ console.log(`${DIM}${"\u2500".repeat(50)}${RESET}`);
871
+ },
872
+ /** Print a table of key-value pairs */
873
+ table(rows, keyWidth = 20) {
874
+ if (_quiet) return;
875
+ for (const [key, value] of rows) {
876
+ const padded = key.padEnd(keyWidth);
877
+ console.log(` ${FG_GRAY}${padded}${RESET} ${value}`);
878
+ }
879
+ },
880
+ /** Status line: ✓ ok / ✗ fail / ⚠ warn */
881
+ status(label, status, detail) {
882
+ if (_quiet) return;
883
+ const icons = {
884
+ ok: `${FG_GREEN}${ICONS.success}${RESET}`,
885
+ fail: `${FG_RED}${ICONS.error}${RESET}`,
886
+ warn: `${FG_YELLOW}${ICONS.warn}${RESET}`,
887
+ skip: `${FG_GRAY}-${RESET}`
888
+ };
889
+ const icon = icons[status] ?? icons["skip"];
890
+ const labelPad = label.padEnd(40);
891
+ const detailStr = detail ? ` ${FG_GRAY}${detail}${RESET}` : "";
892
+ console.log(` ${icon} ${labelPad}${detailStr}`);
893
+ },
894
+ /** Highlight text in cyan bold */
895
+ highlight(text) {
896
+ return `${BOLD}${FG_CYAN}${text}${RESET}`;
897
+ },
898
+ bold(text) {
899
+ return `${BOLD}${text}${RESET}`;
900
+ },
901
+ white(text) {
902
+ return `${FG_WHITE}${text}${RESET}`;
903
+ }
904
+ };
905
+
906
+ // src/cli/commands/init.ts
907
+ var DEFAULT_REGISTRY = "github://your-org/asdm-registry";
908
+ var init_default = defineCommand({
909
+ meta: {
910
+ name: "init",
911
+ description: "Initialize .asdm.json in the current project"
912
+ },
913
+ args: {
914
+ profile: {
915
+ type: "positional",
916
+ description: "Profile to initialize with (default: base)",
917
+ required: false,
918
+ default: "base"
919
+ },
920
+ registry: {
921
+ type: "string",
922
+ description: "Registry URL in github://org/repo format",
923
+ default: DEFAULT_REGISTRY
924
+ },
925
+ force: {
926
+ type: "boolean",
927
+ description: "Overwrite existing .asdm.json",
928
+ default: false
929
+ },
930
+ gitignore: {
931
+ type: "boolean",
932
+ description: "Add ASDM output dirs to .gitignore after init",
933
+ default: false
934
+ }
935
+ },
936
+ async run(ctx) {
937
+ const cwd = process.cwd();
938
+ const configPath = path5.join(cwd, ".asdm.json");
939
+ const alreadyExists = await exists(configPath);
940
+ if (alreadyExists && !ctx.args.force) {
941
+ logger.warn(".asdm.json already exists. Use --force to overwrite.");
942
+ process.exit(0);
943
+ }
944
+ const profile = ctx.args.profile || "base";
945
+ const registry = ctx.args.registry || DEFAULT_REGISTRY;
946
+ const providers = ["opencode"];
947
+ try {
948
+ await createProjectConfig(cwd, registry, profile, providers);
949
+ logger.success(`Initialized .asdm.json with profile "${profile}"`);
950
+ logger.info(`Registry: ${registry}`);
951
+ logger.info("Next step: run `asdm sync` to install agents, skills, and commands");
952
+ const telemetry = new TelemetryWriter(cwd);
953
+ telemetry.write({ event: "init.completed", profile, registry }).catch(() => {
954
+ });
955
+ if (ctx.args.gitignore) {
956
+ const updated = await updateGitignore(cwd, providers);
957
+ if (updated) {
958
+ logger.success("Updated .gitignore with ASDM managed entries");
959
+ } else {
960
+ logger.info(".gitignore already up to date");
961
+ }
962
+ } else if (process.stdout.isTTY) {
963
+ logger.info("\u{1F4A1} Tip: Run 'asdm gitignore' to add ASDM output dirs to .gitignore");
964
+ }
965
+ } catch (err) {
966
+ const message = err instanceof Error ? err.message : String(err);
967
+ logger.error(message);
968
+ process.exit(1);
969
+ }
970
+ }
971
+ });
972
+
973
+ // src/cli/commands/sync.ts
974
+ import { defineCommand as defineCommand2 } from "citty";
975
+
976
+ // src/core/syncer.ts
977
+ import path11 from "path";
978
+
979
+ // src/core/registry-client.ts
980
+ var GITHUB_API_BASE = "https://api.github.com";
981
+ var GITHUB_RAW_BASE = "https://github.com";
982
+ function getGithubToken() {
983
+ return process.env["ASDM_GITHUB_TOKEN"] ?? process.env["GITHUB_TOKEN"];
984
+ }
985
+ function buildHeaders(token) {
986
+ const headers = {
987
+ "Accept": "application/vnd.github+json",
988
+ "X-GitHub-Api-Version": "2022-11-28",
989
+ "User-Agent": "asdm-cli/0.1.0"
990
+ };
991
+ const tok = token ?? getGithubToken();
992
+ if (tok) {
993
+ headers["Authorization"] = `Bearer ${tok}`;
994
+ }
995
+ return headers;
996
+ }
997
+ async function fetchWithRetry(url, options, maxRetries = 3) {
998
+ let lastError;
999
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1000
+ if (attempt > 0) {
1001
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt - 1)));
1002
+ }
1003
+ try {
1004
+ const response = await fetch(url, options);
1005
+ if (response.status === 403 || response.status === 429) {
1006
+ const retryAfter = response.headers.get("Retry-After");
1007
+ const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 6e4;
1008
+ if (attempt < maxRetries - 1) {
1009
+ await new Promise((resolve) => setTimeout(resolve, Math.min(waitMs, 1e4)));
1010
+ continue;
1011
+ }
1012
+ }
1013
+ return response;
1014
+ } catch (err) {
1015
+ lastError = err instanceof Error ? err : new Error(String(err));
1016
+ if (attempt === maxRetries - 1) throw new NetworkError(
1017
+ `Network request failed after ${maxRetries} attempts: ${lastError.message}`,
1018
+ "Check your internet connection and GitHub token"
1019
+ );
1020
+ }
1021
+ }
1022
+ throw new NetworkError(
1023
+ `Network request failed: ${lastError?.message ?? "Unknown error"}`,
1024
+ "Check your internet connection and GitHub token"
1025
+ );
1026
+ }
1027
+ var RegistryClient = class {
1028
+ org;
1029
+ repo;
1030
+ options;
1031
+ constructor(registryUrl, options = {}) {
1032
+ const { org, repo } = parseRegistryUrl(registryUrl);
1033
+ this.org = org;
1034
+ this.repo = repo;
1035
+ this.options = {
1036
+ token: options.token ?? getGithubToken() ?? "",
1037
+ timeout: options.timeout ?? 3e4,
1038
+ maxRetries: options.maxRetries ?? 3
1039
+ };
1040
+ }
1041
+ get headers() {
1042
+ return buildHeaders(this.options.token || void 0);
1043
+ }
1044
+ /**
1045
+ * Fetch the latest release manifest from GitHub Releases.
1046
+ * GET /repos/{org}/{repo}/releases/latest → downloads manifest.json asset
1047
+ */
1048
+ async getLatestManifest() {
1049
+ const url = `${GITHUB_API_BASE}/repos/${this.org}/${this.repo}/releases/latest`;
1050
+ const response = await fetchWithRetry(
1051
+ url,
1052
+ { headers: this.headers },
1053
+ this.options.maxRetries
1054
+ );
1055
+ if (response.status === 404) {
1056
+ throw new RegistryError(
1057
+ `Registry not found: github://${this.org}/${this.repo}`,
1058
+ "Verify the registry URL in .asdm.json and ensure the repo exists with published releases"
1059
+ );
1060
+ }
1061
+ if (!response.ok) {
1062
+ throw new RegistryError(
1063
+ `Failed to fetch latest release: HTTP ${response.status}`,
1064
+ response.status === 401 ? "Set GITHUB_TOKEN or ASDM_GITHUB_TOKEN environment variable" : "Check registry URL and permissions"
1065
+ );
1066
+ }
1067
+ const release = await response.json();
1068
+ const manifestAsset = release.assets.find((a) => a.name === "manifest.json");
1069
+ if (!manifestAsset) {
1070
+ throw new RegistryError(
1071
+ `manifest.json not found in latest release of github://${this.org}/${this.repo}`,
1072
+ "Run the CI workflow to publish a release with manifest.json"
1073
+ );
1074
+ }
1075
+ const manifestResponse = await fetchWithRetry(
1076
+ manifestAsset.browser_download_url,
1077
+ { headers: this.headers },
1078
+ this.options.maxRetries
1079
+ );
1080
+ if (!manifestResponse.ok) {
1081
+ throw new RegistryError(`Failed to download manifest.json: HTTP ${manifestResponse.status}`);
1082
+ }
1083
+ return manifestResponse.json();
1084
+ }
1085
+ /**
1086
+ * Download a specific asset file from a GitHub Release.
1087
+ *
1088
+ * @param assetPath - Registry-relative path (e.g., "agents/code-reviewer.asdm.md")
1089
+ * @param version - Release tag version (e.g., "1.0.0")
1090
+ */
1091
+ async downloadAsset(assetPath, version) {
1092
+ const url = `${GITHUB_RAW_BASE}/${this.org}/${this.repo}/releases/download/v${version}/${assetPath}`;
1093
+ const response = await fetchWithRetry(
1094
+ url,
1095
+ { headers: this.headers },
1096
+ this.options.maxRetries
1097
+ );
1098
+ if (response.status === 404) {
1099
+ throw new RegistryError(
1100
+ `Asset not found: ${assetPath} at version ${version}`,
1101
+ "Run `asdm sync --force` to re-download all assets"
1102
+ );
1103
+ }
1104
+ if (!response.ok) {
1105
+ throw new RegistryError(
1106
+ `Failed to download asset ${assetPath}: HTTP ${response.status}`
1107
+ );
1108
+ }
1109
+ return response.text();
1110
+ }
1111
+ /**
1112
+ * Check if the registry is accessible (for `asdm doctor`).
1113
+ */
1114
+ async ping() {
1115
+ try {
1116
+ const url = `${GITHUB_API_BASE}/repos/${this.org}/${this.repo}`;
1117
+ const response = await fetch(url, { headers: this.headers });
1118
+ return response.status === 200;
1119
+ } catch {
1120
+ return false;
1121
+ }
1122
+ }
1123
+ };
1124
+
1125
+ // src/core/profile-resolver.ts
1126
+ init_fs();
1127
+ import path6 from "path";
1128
+ import { parse as parseYaml } from "yaml";
1129
+ function mergeProviderConfig(base, override) {
1130
+ const result = { ...base };
1131
+ for (const [provider, config] of Object.entries(override)) {
1132
+ if (result[provider]) {
1133
+ const merged = { ...result[provider] };
1134
+ for (const [key, value] of Object.entries(config)) {
1135
+ if (Array.isArray(value) && Array.isArray(merged[key])) {
1136
+ const combined = [...merged[key], ...value];
1137
+ merged[key] = [...new Set(combined)];
1138
+ } else {
1139
+ merged[key] = value;
1140
+ }
1141
+ }
1142
+ result[provider] = merged;
1143
+ } else {
1144
+ result[provider] = { ...config };
1145
+ }
1146
+ }
1147
+ return result;
1148
+ }
1149
+ function mergeStringArrays(base, override) {
1150
+ const result = [...base];
1151
+ for (const item of override) {
1152
+ if (item.startsWith("-")) {
1153
+ const toRemove = item.slice(1);
1154
+ const idx = result.indexOf(toRemove);
1155
+ if (idx !== -1) result.splice(idx, 1);
1156
+ } else if (!result.includes(item)) {
1157
+ result.push(item);
1158
+ }
1159
+ }
1160
+ return result;
1161
+ }
1162
+ function resolveProfileFromManifest(manifestProfiles, profileName, visited = /* @__PURE__ */ new Set()) {
1163
+ if (visited.has(profileName)) {
1164
+ throw new ConfigError(
1165
+ `Circular profile inheritance in manifest: ${[...visited].join(" \u2192 ")} \u2192 ${profileName}`
1166
+ );
1167
+ }
1168
+ visited.add(profileName);
1169
+ const profile = manifestProfiles[profileName];
1170
+ if (!profile) {
1171
+ throw new ConfigError(`Profile "${profileName}" not found in manifest`);
1172
+ }
1173
+ let resolved = {
1174
+ name: profileName,
1175
+ agents: [],
1176
+ skills: [],
1177
+ commands: [],
1178
+ providers: [],
1179
+ provider_config: {},
1180
+ resolvedFrom: []
1181
+ };
1182
+ for (const parentName of profile.extends ?? []) {
1183
+ const parent = resolveProfileFromManifest(manifestProfiles, parentName, new Set(visited));
1184
+ resolved = {
1185
+ ...resolved,
1186
+ agents: mergeStringArrays(parent.agents, resolved.agents),
1187
+ skills: mergeStringArrays(parent.skills, resolved.skills),
1188
+ commands: mergeStringArrays(parent.commands, resolved.commands),
1189
+ providers: [.../* @__PURE__ */ new Set([...parent.providers, ...resolved.providers])],
1190
+ provider_config: mergeProviderConfig(parent.provider_config, resolved.provider_config),
1191
+ resolvedFrom: [...parent.resolvedFrom]
1192
+ };
1193
+ }
1194
+ resolved = {
1195
+ ...resolved,
1196
+ agents: mergeStringArrays(resolved.agents, profile.agents ?? []),
1197
+ skills: mergeStringArrays(resolved.skills, profile.skills ?? []),
1198
+ commands: mergeStringArrays(resolved.commands, profile.commands ?? []),
1199
+ providers: [.../* @__PURE__ */ new Set([...resolved.providers, ...profile.providers ?? []])],
1200
+ resolvedFrom: [...resolved.resolvedFrom, profileName]
1201
+ };
1202
+ return resolved;
1203
+ }
1204
+
1205
+ // src/core/manifest.ts
1206
+ init_fs();
1207
+ function diffManifest(manifest, localShas, assetPaths) {
1208
+ const added = [];
1209
+ const updated = [];
1210
+ const removed = [];
1211
+ const unchanged = [];
1212
+ for (const assetPath of assetPaths) {
1213
+ const manifestAsset = manifest.assets[assetPath];
1214
+ if (!manifestAsset) continue;
1215
+ const localSha = localShas[assetPath];
1216
+ if (!localSha) {
1217
+ added.push(assetPath);
1218
+ } else if (localSha !== manifestAsset.sha256) {
1219
+ updated.push(assetPath);
1220
+ } else {
1221
+ unchanged.push(assetPath);
1222
+ }
1223
+ }
1224
+ for (const assetPath of Object.keys(localShas)) {
1225
+ if (!assetPaths.includes(assetPath) && !manifest.assets[assetPath]) {
1226
+ removed.push(assetPath);
1227
+ }
1228
+ }
1229
+ return { added, updated, removed, unchanged };
1230
+ }
1231
+ function getProfileAssetPaths(manifest, agentNames, skillNames, commandNames) {
1232
+ const paths = [];
1233
+ for (const name of agentNames) {
1234
+ const path18 = `agents/${name}.asdm.md`;
1235
+ if (manifest.assets[path18]) paths.push(path18);
1236
+ }
1237
+ for (const name of skillNames) {
1238
+ const path18 = `skills/${name}/SKILL.asdm.md`;
1239
+ if (manifest.assets[path18]) paths.push(path18);
1240
+ }
1241
+ for (const name of commandNames) {
1242
+ const path18 = `commands/${name}.asdm.md`;
1243
+ if (manifest.assets[path18]) paths.push(path18);
1244
+ }
1245
+ return paths;
1246
+ }
1247
+ async function loadCachedManifest(cacheDir) {
1248
+ return readJson(`${cacheDir}/manifest.json`);
1249
+ }
1250
+
1251
+ // src/core/lockfile.ts
1252
+ init_fs();
1253
+ import path7 from "path";
1254
+ var LOCKFILE_NAME = ".asdm-lock.json";
1255
+ async function readLockfile(cwd) {
1256
+ const filePath = path7.join(cwd, LOCKFILE_NAME);
1257
+ return readJson(filePath);
1258
+ }
1259
+ async function writeLockfile(cwd, lockfile) {
1260
+ const filePath = path7.join(cwd, LOCKFILE_NAME);
1261
+ await writeJson(filePath, {
1262
+ $schema: "https://asdm.dev/schemas/lock.schema.json",
1263
+ ...lockfile
1264
+ });
1265
+ }
1266
+ function createLockEntry(sha256, source, adapter, version, managed = true) {
1267
+ return { sha256, source, adapter, version, managed };
1268
+ }
1269
+ function buildLockfile(params) {
1270
+ return {
1271
+ $schema: "https://asdm.dev/schemas/lock.schema.json",
1272
+ synced_at: (/* @__PURE__ */ new Date()).toISOString(),
1273
+ cli_version: params.cliVersion,
1274
+ manifest_version: params.manifestVersion,
1275
+ registry: params.registry,
1276
+ profile: params.profile,
1277
+ resolved_profiles: params.resolvedProfiles,
1278
+ files: params.files
1279
+ };
1280
+ }
1281
+
1282
+ // src/core/syncer.ts
1283
+ init_hash();
1284
+ init_fs();
1285
+
1286
+ // src/core/parser.ts
1287
+ init_hash();
1288
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1289
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
1290
+ function parseAsset(content, sourcePath, provider = "opencode") {
1291
+ const match = content.match(FRONTMATTER_REGEX);
1292
+ if (!match) {
1293
+ throw new ParseError(
1294
+ `Failed to parse frontmatter in ${sourcePath}`,
1295
+ "Ensure the file has valid YAML frontmatter between --- delimiters"
1296
+ );
1297
+ }
1298
+ const [, frontmatterRaw, body] = match;
1299
+ let frontmatter;
1300
+ try {
1301
+ frontmatter = parseYaml2(frontmatterRaw);
1302
+ } catch (err) {
1303
+ throw new ParseError(
1304
+ `Invalid YAML frontmatter in ${sourcePath}: ${err.message}`,
1305
+ "Validate the frontmatter with a YAML linter"
1306
+ );
1307
+ }
1308
+ const name = frontmatter["name"];
1309
+ const type = frontmatter["type"];
1310
+ const version = frontmatter["version"];
1311
+ const description = frontmatter["description"];
1312
+ if (typeof name !== "string") {
1313
+ throw new ParseError(`Missing required field 'name' in ${sourcePath}`);
1314
+ }
1315
+ if (type !== "agent" && type !== "skill" && type !== "command") {
1316
+ throw new ParseError(
1317
+ `Invalid type '${String(type)}' in ${sourcePath}`,
1318
+ "Type must be one of: 'agent', 'skill', 'command'"
1319
+ );
1320
+ }
1321
+ if (typeof version !== "string") {
1322
+ throw new ParseError(`Missing required field 'version' in ${sourcePath}`);
1323
+ }
1324
+ if (typeof description !== "string") {
1325
+ throw new ParseError(`Missing required field 'description' in ${sourcePath}`);
1326
+ }
1327
+ const providers = frontmatter["providers"] ?? {};
1328
+ const providerConfig = providers[provider] ?? {};
1329
+ const sha256 = hashString(content);
1330
+ return {
1331
+ name,
1332
+ type,
1333
+ version,
1334
+ description,
1335
+ frontmatter,
1336
+ providerConfig,
1337
+ body: body.trim(),
1338
+ sourcePath,
1339
+ sha256
1340
+ };
1341
+ }
1342
+
1343
+ // src/core/syncer.ts
1344
+ async function loadAdapters(providers) {
1345
+ const adapters = [];
1346
+ for (const provider of providers) {
1347
+ switch (provider) {
1348
+ case "opencode": {
1349
+ const { createOpenCodeAdapter: createOpenCodeAdapter2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
1350
+ adapters.push(createOpenCodeAdapter2());
1351
+ break;
1352
+ }
1353
+ case "claude-code": {
1354
+ const { createClaudeCodeAdapter: createClaudeCodeAdapter2 } = await Promise.resolve().then(() => (init_claude_code(), claude_code_exports));
1355
+ adapters.push(createClaudeCodeAdapter2());
1356
+ break;
1357
+ }
1358
+ case "copilot": {
1359
+ const { createCopilotAdapter: createCopilotAdapter2 } = await Promise.resolve().then(() => (init_copilot(), copilot_exports));
1360
+ adapters.push(createCopilotAdapter2());
1361
+ break;
1362
+ }
1363
+ }
1364
+ }
1365
+ return adapters;
1366
+ }
1367
+ async function getCliVersion() {
1368
+ return "0.1.0";
1369
+ }
1370
+ async function sync(options) {
1371
+ const startTime = Date.now();
1372
+ const { cwd } = options;
1373
+ options.telemetry?.write({ event: "sync.started" }).catch(() => {
1374
+ });
1375
+ try {
1376
+ const projectConfig = await readProjectConfig(cwd);
1377
+ const userConfig = await readUserConfig(cwd);
1378
+ const client = new RegistryClient(projectConfig.registry);
1379
+ const manifest = await client.getLatestManifest();
1380
+ const resolvedConfig = resolveConfig(projectConfig, userConfig, manifest.policy);
1381
+ const activeProviders = options.provider ? resolvedConfig.providers.filter((p) => p === options.provider) : resolvedConfig.providers;
1382
+ const resolvedProfile = resolveProfileFromManifest(manifest.profiles, resolvedConfig.profile);
1383
+ const assetPaths = getProfileAssetPaths(
1384
+ manifest,
1385
+ resolvedProfile.agents,
1386
+ resolvedProfile.skills,
1387
+ resolvedProfile.commands
1388
+ );
1389
+ const existingLockfile = options.force ? null : await readLockfile(cwd);
1390
+ const localSourceShas = {};
1391
+ if (existingLockfile) {
1392
+ for (const [, entry] of Object.entries(existingLockfile.files)) {
1393
+ if (entry.managed && entry.source) {
1394
+ localSourceShas[entry.source] = entry.sha256;
1395
+ }
1396
+ }
1397
+ }
1398
+ const diff = diffManifest(manifest, localSourceShas, assetPaths);
1399
+ const toDownload = options.force ? assetPaths : [...diff.added, ...diff.updated];
1400
+ const cacheDir = path11.join(getAsdmCacheDir(), manifest.version);
1401
+ await ensureDir(cacheDir);
1402
+ const downloadedAssets = /* @__PURE__ */ new Map();
1403
+ for (const assetPath of toDownload) {
1404
+ const assetMeta = manifest.assets[assetPath];
1405
+ if (!assetMeta) continue;
1406
+ const content = await client.downloadAsset(assetPath, manifest.version);
1407
+ const downloadedSha = hashString(content);
1408
+ if (downloadedSha !== assetMeta.sha256) {
1409
+ throw new IntegrityError(
1410
+ `SHA-256 mismatch for ${assetPath}: expected ${assetMeta.sha256}, got ${downloadedSha}`,
1411
+ "The asset may have been tampered with or the manifest is stale. Run `asdm sync --force`."
1412
+ );
1413
+ }
1414
+ const cachedPath = path11.join(cacheDir, assetPath);
1415
+ if (!options.dryRun) {
1416
+ await writeFile(cachedPath, content);
1417
+ }
1418
+ downloadedAssets.set(assetPath, content);
1419
+ }
1420
+ for (const assetPath of diff.unchanged) {
1421
+ const cachedPath = path11.join(cacheDir, assetPath);
1422
+ try {
1423
+ const { readFile: readFile2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
1424
+ const cached = await readFile2(cachedPath);
1425
+ if (cached) downloadedAssets.set(assetPath, cached);
1426
+ } catch {
1427
+ }
1428
+ }
1429
+ if (options.dryRun || options.noEmit) {
1430
+ const stats2 = {
1431
+ filesAdded: diff.added.length,
1432
+ filesUpdated: diff.updated.length,
1433
+ filesUnchanged: diff.unchanged.length,
1434
+ filesRemoved: diff.removed.length,
1435
+ duration: Date.now() - startTime,
1436
+ manifestVersion: manifest.version,
1437
+ profile: resolvedConfig.profile,
1438
+ providers: activeProviders
1439
+ };
1440
+ options.telemetry?.write({
1441
+ event: "sync.completed",
1442
+ profile: resolvedConfig.profile,
1443
+ registry: resolvedConfig.registry,
1444
+ providers: activeProviders,
1445
+ assetCount: 0,
1446
+ durationMs: stats2.duration
1447
+ }).catch(() => {
1448
+ });
1449
+ return { stats: stats2, emittedFiles: [], dryRun: !!options.dryRun };
1450
+ }
1451
+ const adapters = await loadAdapters(activeProviders);
1452
+ const allEmittedFiles = [];
1453
+ for (const adapter of adapters) {
1454
+ for (const agentName of resolvedProfile.agents) {
1455
+ const assetPath = `agents/${agentName}.asdm.md`;
1456
+ const content = downloadedAssets.get(assetPath);
1457
+ if (!content) continue;
1458
+ const parsed = parseAsset(content, assetPath, adapter.name);
1459
+ const emitted = adapter.emitAgent(parsed, cwd);
1460
+ allEmittedFiles.push(...emitted);
1461
+ }
1462
+ for (const skillName of resolvedProfile.skills) {
1463
+ const assetPath = `skills/${skillName}/SKILL.asdm.md`;
1464
+ const content = downloadedAssets.get(assetPath);
1465
+ if (!content) continue;
1466
+ const parsed = parseAsset(content, assetPath, adapter.name);
1467
+ const emitted = adapter.emitSkill(parsed, cwd);
1468
+ allEmittedFiles.push(...emitted);
1469
+ }
1470
+ for (const commandName of resolvedProfile.commands) {
1471
+ const assetPath = `commands/${commandName}.asdm.md`;
1472
+ const content = downloadedAssets.get(assetPath);
1473
+ if (!content) continue;
1474
+ const parsed = parseAsset(content, assetPath, adapter.name);
1475
+ const emitted = adapter.emitCommand(parsed, cwd);
1476
+ allEmittedFiles.push(...emitted);
1477
+ }
1478
+ const rootFiles = adapter.emitRootInstructions(resolvedProfile, cwd);
1479
+ allEmittedFiles.push(...rootFiles);
1480
+ const configFiles = adapter.emitConfig(resolvedProfile, cwd);
1481
+ allEmittedFiles.push(...configFiles);
1482
+ }
1483
+ for (const emittedFile of allEmittedFiles) {
1484
+ const absolutePath = path11.join(cwd, emittedFile.relativePath);
1485
+ await writeFile(absolutePath, emittedFile.content);
1486
+ }
1487
+ const lockfileFiles = {};
1488
+ for (const emittedFile of allEmittedFiles) {
1489
+ lockfileFiles[emittedFile.relativePath] = createLockEntry(
1490
+ emittedFile.sha256,
1491
+ emittedFile.sourcePath,
1492
+ emittedFile.adapter,
1493
+ manifest.assets[emittedFile.sourcePath]?.version ?? "0.0.0"
1494
+ );
1495
+ }
1496
+ const cliVersion = await getCliVersion();
1497
+ const lockfile = buildLockfile({
1498
+ cliVersion,
1499
+ manifestVersion: manifest.version,
1500
+ registry: resolvedConfig.registry,
1501
+ profile: resolvedConfig.profile,
1502
+ resolvedProfiles: resolvedProfile.resolvedFrom,
1503
+ files: lockfileFiles
1504
+ });
1505
+ await writeLockfile(cwd, lockfile);
1506
+ const stats = {
1507
+ filesAdded: diff.added.length,
1508
+ filesUpdated: diff.updated.length,
1509
+ filesUnchanged: diff.unchanged.length,
1510
+ filesRemoved: diff.removed.length,
1511
+ duration: Date.now() - startTime,
1512
+ manifestVersion: manifest.version,
1513
+ profile: resolvedConfig.profile,
1514
+ providers: activeProviders
1515
+ };
1516
+ options.telemetry?.write({
1517
+ event: "sync.completed",
1518
+ profile: resolvedConfig.profile,
1519
+ registry: resolvedConfig.registry,
1520
+ providers: activeProviders,
1521
+ assetCount: allEmittedFiles.length,
1522
+ durationMs: stats.duration
1523
+ }).catch(() => {
1524
+ });
1525
+ return { stats, emittedFiles: allEmittedFiles, dryRun: false };
1526
+ } catch (err) {
1527
+ options.telemetry?.write({
1528
+ event: "sync.failed",
1529
+ error: err instanceof Error ? err.message : String(err)
1530
+ }).catch(() => {
1531
+ });
1532
+ throw err;
1533
+ }
1534
+ }
1535
+
1536
+ // src/cli/commands/sync.ts
1537
+ var sync_default = defineCommand2({
1538
+ meta: {
1539
+ name: "sync",
1540
+ description: "Synchronize assets from the registry"
1541
+ },
1542
+ args: {
1543
+ "dry-run": {
1544
+ type: "boolean",
1545
+ description: "Show what would be done without writing files",
1546
+ default: false
1547
+ },
1548
+ force: {
1549
+ type: "boolean",
1550
+ description: "Re-download all assets even if SHA matches",
1551
+ default: false
1552
+ },
1553
+ verbose: {
1554
+ type: "boolean",
1555
+ description: "Print verbose output",
1556
+ default: false
1557
+ },
1558
+ provider: {
1559
+ type: "string",
1560
+ description: "Sync only for a specific provider"
1561
+ }
1562
+ },
1563
+ async run(ctx) {
1564
+ const cwd = process.cwd();
1565
+ const dryRun = ctx.args["dry-run"];
1566
+ const verbose = ctx.args.verbose;
1567
+ if (verbose) logger.setVerbose(true);
1568
+ if (dryRun) {
1569
+ logger.info("Dry run \u2014 no files will be written");
1570
+ }
1571
+ logger.asdm("Starting sync\u2026");
1572
+ const telemetry = new TelemetryWriter(cwd);
1573
+ try {
1574
+ const result = await sync({
1575
+ cwd,
1576
+ force: ctx.args.force,
1577
+ dryRun,
1578
+ verbose,
1579
+ provider: ctx.args.provider,
1580
+ telemetry
1581
+ });
1582
+ const { stats } = result;
1583
+ if (dryRun) {
1584
+ logger.info("Sync plan (dry run):");
1585
+ logger.bullet(`${stats.filesAdded} file(s) to add`);
1586
+ logger.bullet(`${stats.filesUpdated} file(s) to update`);
1587
+ logger.bullet(`${stats.filesUnchanged} file(s) unchanged`);
1588
+ logger.bullet(`${stats.filesRemoved} file(s) to remove`);
1589
+ return;
1590
+ }
1591
+ logger.divider();
1592
+ logger.success("Sync complete");
1593
+ logger.table([
1594
+ ["Profile", stats.profile],
1595
+ ["Manifest version", stats.manifestVersion],
1596
+ ["Providers", stats.providers.join(", ")],
1597
+ ["Files added", String(stats.filesAdded)],
1598
+ ["Files updated", String(stats.filesUpdated)],
1599
+ ["Files unchanged", String(stats.filesUnchanged)],
1600
+ ["Duration", `${stats.duration}ms`]
1601
+ ]);
1602
+ } catch (err) {
1603
+ const message = err instanceof Error ? err.message : String(err);
1604
+ const suggestion = err.suggestion ?? void 0;
1605
+ logger.error(message, suggestion);
1606
+ process.exit(1);
1607
+ }
1608
+ }
1609
+ });
1610
+
1611
+ // src/cli/commands/verify.ts
1612
+ import { defineCommand as defineCommand3 } from "citty";
1613
+
1614
+ // src/core/verifier.ts
1615
+ import path12 from "path";
1616
+ init_hash();
1617
+ init_fs();
1618
+ var VERIFY_EXIT_CODES = {
1619
+ OK: 0,
1620
+ MODIFIED: 1,
1621
+ NO_LOCK: 2,
1622
+ OUTDATED: 3
1623
+ };
1624
+ async function verify(cwd, latestManifestVersion, onlyManaged = true, telemetry) {
1625
+ const lockfile = await readLockfile(cwd);
1626
+ if (!lockfile) {
1627
+ telemetry?.write({ event: "verify.failed" }).catch(() => {
1628
+ });
1629
+ return {
1630
+ exitCode: VERIFY_EXIT_CODES.NO_LOCK,
1631
+ violations: [],
1632
+ checkedFiles: 0
1633
+ };
1634
+ }
1635
+ const violations = [];
1636
+ let checkedFiles = 0;
1637
+ const filesToCheck = onlyManaged ? Object.entries(lockfile.files).filter(([, entry]) => entry.managed) : Object.entries(lockfile.files);
1638
+ for (const [relativePath, entry] of filesToCheck) {
1639
+ const absolutePath = path12.join(cwd, relativePath);
1640
+ checkedFiles++;
1641
+ const fileExists = await exists(absolutePath);
1642
+ if (!fileExists) {
1643
+ violations.push({
1644
+ filePath: relativePath,
1645
+ type: "missing",
1646
+ expected: entry.sha256
1647
+ });
1648
+ continue;
1649
+ }
1650
+ const actualHash = await hashFile(absolutePath);
1651
+ if (actualHash !== entry.sha256) {
1652
+ violations.push({
1653
+ filePath: relativePath,
1654
+ type: "modified",
1655
+ expected: entry.sha256,
1656
+ actual: actualHash
1657
+ });
1658
+ }
1659
+ }
1660
+ let exitCode = VERIFY_EXIT_CODES.OK;
1661
+ if (violations.length > 0) {
1662
+ exitCode = VERIFY_EXIT_CODES.MODIFIED;
1663
+ } else if (latestManifestVersion && lockfile.manifest_version !== latestManifestVersion) {
1664
+ exitCode = VERIFY_EXIT_CODES.OUTDATED;
1665
+ }
1666
+ if (violations.length > 0) {
1667
+ telemetry?.write({ event: "verify.modified", violations: violations.length }).catch(() => {
1668
+ });
1669
+ } else {
1670
+ telemetry?.write({ event: "verify.passed", violations: 0 }).catch(() => {
1671
+ });
1672
+ }
1673
+ return {
1674
+ exitCode,
1675
+ violations,
1676
+ checkedFiles,
1677
+ currentManifestVersion: lockfile.manifest_version,
1678
+ latestManifestVersion
1679
+ };
1680
+ }
1681
+
1682
+ // src/cli/commands/verify.ts
1683
+ var verify_default = defineCommand3({
1684
+ meta: {
1685
+ name: "verify",
1686
+ description: "Verify integrity of managed files against the lockfile"
1687
+ },
1688
+ args: {
1689
+ strict: {
1690
+ type: "boolean",
1691
+ description: "Exit 1 on any violation (used by git hooks)",
1692
+ default: false
1693
+ },
1694
+ json: {
1695
+ type: "boolean",
1696
+ description: "Output results as JSON",
1697
+ default: false
1698
+ },
1699
+ quiet: {
1700
+ type: "boolean",
1701
+ description: "Suppress non-error output",
1702
+ default: false
1703
+ }
1704
+ },
1705
+ async run(ctx) {
1706
+ const cwd = process.cwd();
1707
+ const useJson = ctx.args.json;
1708
+ const quiet = ctx.args.quiet;
1709
+ if (quiet) logger.setQuiet(true);
1710
+ const telemetry = new TelemetryWriter(cwd);
1711
+ if (ctx.args.strict) {
1712
+ const result = await verify(cwd, void 0, true, telemetry);
1713
+ if (useJson) {
1714
+ console.log(JSON.stringify({
1715
+ status: result.exitCode === VERIFY_EXIT_CODES.OK ? "ok" : "error",
1716
+ violations: result.violations
1717
+ }));
1718
+ } else if (result.exitCode === VERIFY_EXIT_CODES.OK) {
1719
+ logger.success("All managed files are intact");
1720
+ } else {
1721
+ for (const v of result.violations) {
1722
+ const icon = v.type === "missing" ? "\u274C" : "\u26A0\uFE0F";
1723
+ logger.error(`${icon} ${v.filePath} (${v.type})`);
1724
+ }
1725
+ }
1726
+ process.exit(result.exitCode);
1727
+ }
1728
+ try {
1729
+ const result = await verify(cwd, void 0, true, telemetry);
1730
+ if (useJson) {
1731
+ console.log(JSON.stringify(result, null, 2));
1732
+ process.exit(result.exitCode);
1733
+ }
1734
+ if (result.exitCode === VERIFY_EXIT_CODES.NO_LOCK) {
1735
+ logger.warn("No lockfile found (.asdm-lock.json)");
1736
+ logger.info("Run `asdm sync` to initialize");
1737
+ process.exit(VERIFY_EXIT_CODES.NO_LOCK);
1738
+ }
1739
+ logger.asdm(`Verified ${result.checkedFiles} managed file(s)`);
1740
+ logger.divider();
1741
+ if (result.violations.length === 0) {
1742
+ logger.success("All managed files are intact");
1743
+ } else {
1744
+ for (const violation of result.violations) {
1745
+ if (violation.type === "missing") {
1746
+ logger.status(violation.filePath, "fail", "\u274C MISSING");
1747
+ } else if (violation.type === "modified") {
1748
+ logger.status(violation.filePath, "warn", "\u26A0\uFE0F MODIFIED");
1749
+ } else {
1750
+ logger.status(violation.filePath, "warn", `\u26A0\uFE0F ${violation.type.toUpperCase()}`);
1751
+ }
1752
+ }
1753
+ logger.divider();
1754
+ logger.warn(`${result.violations.length} violation(s) detected`);
1755
+ logger.info("Run `asdm sync --force` to restore managed files");
1756
+ }
1757
+ if (result.exitCode === VERIFY_EXIT_CODES.OUTDATED) {
1758
+ logger.warn(`Manifest is outdated (local: ${result.currentManifestVersion}, latest: ${result.latestManifestVersion})`);
1759
+ logger.info("Run `asdm sync` to update");
1760
+ }
1761
+ process.exit(result.exitCode);
1762
+ } catch (err) {
1763
+ const message = err instanceof Error ? err.message : String(err);
1764
+ logger.error(message);
1765
+ process.exit(1);
1766
+ }
1767
+ }
1768
+ });
1769
+
1770
+ // src/cli/commands/status.ts
1771
+ import { defineCommand as defineCommand4 } from "citty";
1772
+ var status_default = defineCommand4({
1773
+ meta: {
1774
+ name: "status",
1775
+ description: "Show current config and sync status"
1776
+ },
1777
+ async run(_ctx) {
1778
+ const cwd = process.cwd();
1779
+ let projectConfig;
1780
+ try {
1781
+ projectConfig = await readProjectConfig(cwd);
1782
+ } catch (err) {
1783
+ if (err instanceof ConfigError) {
1784
+ logger.warn("Not initialized. Run `asdm init` first.");
1785
+ process.exit(1);
1786
+ }
1787
+ throw err;
1788
+ }
1789
+ const userConfig = await readUserConfig(cwd);
1790
+ const lockfile = await readLockfile(cwd);
1791
+ const activeProfile = userConfig?.profile ?? projectConfig.profile;
1792
+ const providers = projectConfig.providers ?? ["opencode"];
1793
+ logger.asdm("ASDM Status");
1794
+ logger.divider();
1795
+ logger.table([
1796
+ ["Registry", projectConfig.registry],
1797
+ ["Profile (project)", projectConfig.profile],
1798
+ ["Profile (active)", activeProfile],
1799
+ ["Providers", providers.join(", ")]
1800
+ ]);
1801
+ logger.divider();
1802
+ if (!lockfile) {
1803
+ logger.warn("Not synced \u2014 no lockfile found");
1804
+ logger.info("Run `asdm sync` to synchronize assets");
1805
+ return;
1806
+ }
1807
+ const syncedAt = new Date(lockfile.synced_at).toLocaleString();
1808
+ const managedFiles = Object.entries(lockfile.files).filter(([, e]) => e.managed);
1809
+ const agents = [...new Set(managedFiles.filter(([, e]) => e.source.startsWith("agents/")).map(([, e]) => e.source.replace("agents/", "").replace(".asdm.md", "")))];
1810
+ const skills = [...new Set(managedFiles.filter(([, e]) => e.source.startsWith("skills/")).map(([, e]) => e.source.replace("skills/", "").replace("/SKILL.asdm.md", "")))];
1811
+ const commands = [...new Set(managedFiles.filter(([, e]) => e.source.startsWith("commands/")).map(([, e]) => e.source.replace("commands/", "").replace(".asdm.md", "")))];
1812
+ logger.table([
1813
+ ["Last synced", syncedAt],
1814
+ ["Manifest version", lockfile.manifest_version],
1815
+ ["CLI version", lockfile.cli_version],
1816
+ ["Managed files", String(managedFiles.length)],
1817
+ ["Agents", agents.length > 0 ? agents.join(", ") : "(none)"],
1818
+ ["Skills", skills.length > 0 ? skills.join(", ") : "(none)"],
1819
+ ["Commands", commands.length > 0 ? commands.join(", ") : "(none)"]
1820
+ ]);
1821
+ logger.divider();
1822
+ logger.success("Up to date \u2014 run `asdm sync` to check for updates");
1823
+ }
1824
+ });
1825
+
1826
+ // src/cli/commands/use.ts
1827
+ import { defineCommand as defineCommand5 } from "citty";
1828
+ var use_default = defineCommand5({
1829
+ meta: {
1830
+ name: "use",
1831
+ description: "Switch the active profile (stored in .asdm.local.json)"
1832
+ },
1833
+ args: {
1834
+ profile: {
1835
+ type: "positional",
1836
+ description: "Profile name to activate",
1837
+ required: true
1838
+ }
1839
+ },
1840
+ async run(ctx) {
1841
+ const cwd = process.cwd();
1842
+ const profile = ctx.args.profile;
1843
+ if (!profile) {
1844
+ logger.error("Profile name is required", "Usage: asdm use <profile>");
1845
+ process.exit(1);
1846
+ }
1847
+ try {
1848
+ await readProjectConfig(cwd);
1849
+ } catch (err) {
1850
+ if (err instanceof ConfigError) {
1851
+ logger.error("No .asdm.json found", "Run `asdm init` first");
1852
+ process.exit(1);
1853
+ }
1854
+ throw err;
1855
+ }
1856
+ const cachedManifest = await loadCachedManifest(cwd);
1857
+ if (cachedManifest) {
1858
+ if (!cachedManifest.profiles[profile]) {
1859
+ const available = Object.keys(cachedManifest.profiles).join(", ");
1860
+ logger.error(
1861
+ `Profile "${profile}" not found in manifest`,
1862
+ `Available profiles: ${available}`
1863
+ );
1864
+ process.exit(1);
1865
+ }
1866
+ } else {
1867
+ logger.warn("No cached manifest found \u2014 skipping profile validation");
1868
+ }
1869
+ try {
1870
+ await writeUserConfig(cwd, { profile });
1871
+ logger.success(`Switched to profile "${profile}"`);
1872
+ logger.asdm("Syncing with new profile\u2026");
1873
+ const result = await sync({ cwd });
1874
+ const changed = result.stats.filesAdded + result.stats.filesUpdated;
1875
+ logger.success(`Sync complete \u2014 ${changed} file(s) updated`);
1876
+ } catch (err) {
1877
+ const message = err instanceof Error ? err.message : String(err);
1878
+ const suggestion = err.suggestion ?? void 0;
1879
+ logger.error(message, suggestion);
1880
+ process.exit(1);
1881
+ }
1882
+ }
1883
+ });
1884
+
1885
+ // src/cli/commands/profiles.ts
1886
+ import { defineCommand as defineCommand6 } from "citty";
1887
+ var profiles_default = defineCommand6({
1888
+ meta: {
1889
+ name: "profiles",
1890
+ description: "List available profiles from the registry"
1891
+ },
1892
+ args: {
1893
+ json: {
1894
+ type: "boolean",
1895
+ description: "Output as JSON",
1896
+ default: false
1897
+ }
1898
+ },
1899
+ async run(ctx) {
1900
+ const cwd = process.cwd();
1901
+ let projectConfig;
1902
+ try {
1903
+ projectConfig = await readProjectConfig(cwd);
1904
+ } catch (err) {
1905
+ if (err instanceof ConfigError) {
1906
+ logger.error("No .asdm.json found", "Run `asdm init` first");
1907
+ process.exit(1);
1908
+ }
1909
+ throw err;
1910
+ }
1911
+ try {
1912
+ const client = new RegistryClient(projectConfig.registry);
1913
+ const manifest = await client.getLatestManifest();
1914
+ if (ctx.args.json) {
1915
+ console.log(JSON.stringify(manifest.profiles, null, 2));
1916
+ return;
1917
+ }
1918
+ logger.asdm(`Available profiles (manifest v${manifest.version})`);
1919
+ logger.divider();
1920
+ const profileEntries = Object.entries(manifest.profiles);
1921
+ if (profileEntries.length === 0) {
1922
+ logger.info("No profiles found in manifest");
1923
+ return;
1924
+ }
1925
+ for (const [name, profile] of profileEntries) {
1926
+ const agents = profile.agents?.length ?? 0;
1927
+ const skills = profile.skills?.length ?? 0;
1928
+ const cmds = profile.commands?.length ?? 0;
1929
+ const extendsFrom = profile.extends?.join(", ") ?? "\u2014";
1930
+ logger.bullet(logger.bold(name));
1931
+ logger.table([
1932
+ [" Extends", extendsFrom],
1933
+ [" Agents", String(agents)],
1934
+ [" Skills", String(skills)],
1935
+ [" Commands", String(cmds)]
1936
+ ], 12);
1937
+ }
1938
+ logger.divider();
1939
+ logger.info("Use `asdm use <profile>` to switch profiles");
1940
+ } catch (err) {
1941
+ const message = err instanceof Error ? err.message : String(err);
1942
+ const suggestion = err.suggestion ?? void 0;
1943
+ logger.error(message, suggestion);
1944
+ process.exit(1);
1945
+ }
1946
+ }
1947
+ });
1948
+
1949
+ // src/cli/commands/agents.ts
1950
+ import { defineCommand as defineCommand7 } from "citty";
1951
+ var agents_default = defineCommand7({
1952
+ meta: {
1953
+ name: "agents",
1954
+ description: "List installed or available agents"
1955
+ },
1956
+ args: {
1957
+ available: {
1958
+ type: "boolean",
1959
+ description: "List agents available in the registry (requires network)",
1960
+ default: false
1961
+ },
1962
+ json: {
1963
+ type: "boolean",
1964
+ description: "Output as JSON",
1965
+ default: false
1966
+ }
1967
+ },
1968
+ async run(ctx) {
1969
+ const cwd = process.cwd();
1970
+ if (ctx.args.available) {
1971
+ await listAvailableAgents(cwd, ctx.args.json);
1972
+ return;
1973
+ }
1974
+ await listInstalledAgents(cwd, ctx.args.json);
1975
+ }
1976
+ });
1977
+ async function listInstalledAgents(cwd, asJson) {
1978
+ const lockfile = await readLockfile(cwd);
1979
+ if (!lockfile) {
1980
+ logger.warn("No lockfile found \u2014 run `asdm sync` first");
1981
+ process.exit(1);
1982
+ }
1983
+ const agentSources = [...new Set(
1984
+ Object.values(lockfile.files).filter((e) => e.managed && e.source.startsWith("agents/")).map((e) => e.source.replace("agents/", "").replace(".asdm.md", ""))
1985
+ )];
1986
+ if (asJson) {
1987
+ console.log(JSON.stringify(agentSources, null, 2));
1988
+ return;
1989
+ }
1990
+ logger.asdm(`Installed agents (profile: ${lockfile.profile})`);
1991
+ logger.divider();
1992
+ if (agentSources.length === 0) {
1993
+ logger.info("No agents installed");
1994
+ return;
1995
+ }
1996
+ for (const agent of agentSources) {
1997
+ logger.bullet(agent);
1998
+ }
1999
+ }
2000
+ async function listAvailableAgents(cwd, asJson) {
2001
+ let projectConfig;
2002
+ try {
2003
+ projectConfig = await readProjectConfig(cwd);
2004
+ } catch (err) {
2005
+ if (err instanceof ConfigError) {
2006
+ logger.error("No .asdm.json found", "Run `asdm init` first");
2007
+ process.exit(1);
2008
+ }
2009
+ throw err;
2010
+ }
2011
+ try {
2012
+ const client = new RegistryClient(projectConfig.registry);
2013
+ const manifest = await client.getLatestManifest();
2014
+ const resolved = resolveProfileFromManifest(manifest.profiles, projectConfig.profile);
2015
+ if (asJson) {
2016
+ console.log(JSON.stringify(resolved.agents, null, 2));
2017
+ return;
2018
+ }
2019
+ logger.asdm(`Available agents for profile "${projectConfig.profile}"`);
2020
+ logger.divider();
2021
+ if (resolved.agents.length === 0) {
2022
+ logger.info("No agents defined for this profile");
2023
+ return;
2024
+ }
2025
+ for (const agent of resolved.agents) {
2026
+ const assetKey = `agents/${agent}.asdm.md`;
2027
+ const meta = manifest.assets[assetKey];
2028
+ const version = meta?.version ?? "?";
2029
+ logger.bullet(`${agent} (v${version})`);
2030
+ }
2031
+ } catch (err) {
2032
+ const message = err instanceof Error ? err.message : String(err);
2033
+ const suggestion = err.suggestion ?? void 0;
2034
+ logger.error(message, suggestion);
2035
+ process.exit(1);
2036
+ }
2037
+ }
2038
+
2039
+ // src/cli/commands/skills.ts
2040
+ import { defineCommand as defineCommand8 } from "citty";
2041
+ var skills_default = defineCommand8({
2042
+ meta: {
2043
+ name: "skills",
2044
+ description: "List installed or available skills"
2045
+ },
2046
+ args: {
2047
+ available: {
2048
+ type: "boolean",
2049
+ description: "List skills available in the registry (requires network)",
2050
+ default: false
2051
+ },
2052
+ json: {
2053
+ type: "boolean",
2054
+ description: "Output as JSON",
2055
+ default: false
2056
+ }
2057
+ },
2058
+ async run(ctx) {
2059
+ const cwd = process.cwd();
2060
+ if (ctx.args.available) {
2061
+ await listAvailableSkills(cwd, ctx.args.json);
2062
+ return;
2063
+ }
2064
+ await listInstalledSkills(cwd, ctx.args.json);
2065
+ }
2066
+ });
2067
+ async function listInstalledSkills(cwd, asJson) {
2068
+ const lockfile = await readLockfile(cwd);
2069
+ if (!lockfile) {
2070
+ logger.warn("No lockfile found \u2014 run `asdm sync` first");
2071
+ process.exit(1);
2072
+ }
2073
+ const skillSources = [...new Set(
2074
+ Object.values(lockfile.files).filter((e) => e.managed && e.source.startsWith("skills/")).map((e) => {
2075
+ const parts = e.source.replace("skills/", "").split("/");
2076
+ return parts[0] ?? e.source;
2077
+ })
2078
+ )];
2079
+ if (asJson) {
2080
+ console.log(JSON.stringify(skillSources, null, 2));
2081
+ return;
2082
+ }
2083
+ logger.asdm(`Installed skills (profile: ${lockfile.profile})`);
2084
+ logger.divider();
2085
+ if (skillSources.length === 0) {
2086
+ logger.info("No skills installed");
2087
+ return;
2088
+ }
2089
+ for (const skill of skillSources) {
2090
+ logger.bullet(skill);
2091
+ }
2092
+ }
2093
+ async function listAvailableSkills(cwd, asJson) {
2094
+ let projectConfig;
2095
+ try {
2096
+ projectConfig = await readProjectConfig(cwd);
2097
+ } catch (err) {
2098
+ if (err instanceof ConfigError) {
2099
+ logger.error("No .asdm.json found", "Run `asdm init` first");
2100
+ process.exit(1);
2101
+ }
2102
+ throw err;
2103
+ }
2104
+ try {
2105
+ const client = new RegistryClient(projectConfig.registry);
2106
+ const manifest = await client.getLatestManifest();
2107
+ const resolved = resolveProfileFromManifest(manifest.profiles, projectConfig.profile);
2108
+ if (asJson) {
2109
+ console.log(JSON.stringify(resolved.skills, null, 2));
2110
+ return;
2111
+ }
2112
+ logger.asdm(`Available skills for profile "${projectConfig.profile}"`);
2113
+ logger.divider();
2114
+ if (resolved.skills.length === 0) {
2115
+ logger.info("No skills defined for this profile");
2116
+ return;
2117
+ }
2118
+ for (const skill of resolved.skills) {
2119
+ const assetKey = `skills/${skill}/SKILL.asdm.md`;
2120
+ const meta = manifest.assets[assetKey];
2121
+ const version = meta?.version ?? "?";
2122
+ logger.bullet(`${skill} (v${version})`);
2123
+ }
2124
+ } catch (err) {
2125
+ const message = err instanceof Error ? err.message : String(err);
2126
+ const suggestion = err.suggestion ?? void 0;
2127
+ logger.error(message, suggestion);
2128
+ process.exit(1);
2129
+ }
2130
+ }
2131
+
2132
+ // src/cli/commands/commands.ts
2133
+ import { defineCommand as defineCommand9 } from "citty";
2134
+ var commands_default = defineCommand9({
2135
+ meta: {
2136
+ name: "commands",
2137
+ description: "List installed or available slash commands"
2138
+ },
2139
+ args: {
2140
+ available: {
2141
+ type: "boolean",
2142
+ description: "List commands available in the registry (requires network)",
2143
+ default: false
2144
+ },
2145
+ json: {
2146
+ type: "boolean",
2147
+ description: "Output as JSON",
2148
+ default: false
2149
+ }
2150
+ },
2151
+ async run(ctx) {
2152
+ const cwd = process.cwd();
2153
+ if (ctx.args.available) {
2154
+ await listAvailableCommands(cwd, ctx.args.json);
2155
+ return;
2156
+ }
2157
+ await listInstalledCommands(cwd, ctx.args.json);
2158
+ }
2159
+ });
2160
+ async function listInstalledCommands(cwd, asJson) {
2161
+ const lockfile = await readLockfile(cwd);
2162
+ if (!lockfile) {
2163
+ logger.warn("No lockfile found \u2014 run `asdm sync` first");
2164
+ process.exit(1);
2165
+ }
2166
+ const commandSources = [...new Set(
2167
+ Object.values(lockfile.files).filter((e) => e.managed && e.source.startsWith("commands/")).map((e) => e.source.replace("commands/", "").replace(".asdm.md", ""))
2168
+ )];
2169
+ if (asJson) {
2170
+ console.log(JSON.stringify(commandSources, null, 2));
2171
+ return;
2172
+ }
2173
+ logger.asdm(`Installed commands (profile: ${lockfile.profile})`);
2174
+ logger.divider();
2175
+ if (commandSources.length === 0) {
2176
+ logger.info("No commands installed");
2177
+ return;
2178
+ }
2179
+ for (const cmd of commandSources) {
2180
+ logger.bullet(`/${cmd}`);
2181
+ }
2182
+ }
2183
+ async function listAvailableCommands(cwd, asJson) {
2184
+ let projectConfig;
2185
+ try {
2186
+ projectConfig = await readProjectConfig(cwd);
2187
+ } catch (err) {
2188
+ if (err instanceof ConfigError) {
2189
+ logger.error("No .asdm.json found", "Run `asdm init` first");
2190
+ process.exit(1);
2191
+ }
2192
+ throw err;
2193
+ }
2194
+ try {
2195
+ const client = new RegistryClient(projectConfig.registry);
2196
+ const manifest = await client.getLatestManifest();
2197
+ const resolved = resolveProfileFromManifest(manifest.profiles, projectConfig.profile);
2198
+ if (asJson) {
2199
+ console.log(JSON.stringify(resolved.commands, null, 2));
2200
+ return;
2201
+ }
2202
+ logger.asdm(`Available commands for profile "${projectConfig.profile}"`);
2203
+ logger.divider();
2204
+ if (resolved.commands.length === 0) {
2205
+ logger.info("No commands defined for this profile");
2206
+ return;
2207
+ }
2208
+ for (const cmd of resolved.commands) {
2209
+ const assetKey = `commands/${cmd}.asdm.md`;
2210
+ const meta = manifest.assets[assetKey];
2211
+ const version = meta?.version ?? "?";
2212
+ logger.bullet(`/${cmd} (v${version})`);
2213
+ }
2214
+ } catch (err) {
2215
+ const message = err instanceof Error ? err.message : String(err);
2216
+ const suggestion = err.suggestion ?? void 0;
2217
+ logger.error(message, suggestion);
2218
+ process.exit(1);
2219
+ }
2220
+ }
2221
+
2222
+ // src/cli/commands/version.ts
2223
+ import { defineCommand as defineCommand10 } from "citty";
2224
+ import os3 from "os";
2225
+ var version_default = defineCommand10({
2226
+ meta: {
2227
+ name: "version",
2228
+ description: "Print CLI version and environment info"
2229
+ },
2230
+ run(_ctx) {
2231
+ console.log(`asdm v${"0.1.0"}`);
2232
+ console.log(`node ${process.version}`);
2233
+ console.log(`os ${os3.type()} ${os3.release()} (${process.platform})`);
2234
+ }
2235
+ });
2236
+
2237
+ // src/cli/commands/doctor.ts
2238
+ import { defineCommand as defineCommand11 } from "citty";
2239
+ import path14 from "path";
2240
+
2241
+ // src/core/overlay.ts
2242
+ init_fs();
2243
+ import path13 from "path";
2244
+ import { promises as fs4 } from "fs";
2245
+ var OVERLAY_SEPARATOR = [
2246
+ "",
2247
+ "<!-- managed content above -->",
2248
+ "",
2249
+ "---",
2250
+ "<!-- LOCAL OVERLAY \u2014 this section is not managed by ASDM -->",
2251
+ ""
2252
+ ].join("\n");
2253
+ async function readOverlays(projectRoot) {
2254
+ const overlaysDir = path13.join(projectRoot, "overlays");
2255
+ const dirExists = await exists(overlaysDir);
2256
+ if (!dirExists) return /* @__PURE__ */ new Map();
2257
+ const entries = await fs4.readdir(overlaysDir, { withFileTypes: true });
2258
+ const overlayMap = /* @__PURE__ */ new Map();
2259
+ for (const entry of entries) {
2260
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2261
+ const agentId = entry.name.slice(0, -".md".length);
2262
+ const overlayPath = path13.join(overlaysDir, entry.name);
2263
+ const content = await readFile(overlayPath);
2264
+ if (content === null) continue;
2265
+ overlayMap.set(agentId, { agentId, content, path: overlayPath });
2266
+ }
2267
+ return overlayMap;
2268
+ }
2269
+
2270
+ // src/cli/commands/doctor.ts
2271
+ init_fs();
2272
+ function extractAgentNamesFromLockfile(lockfile) {
2273
+ const names = /* @__PURE__ */ new Set();
2274
+ for (const entry of Object.values(lockfile.files)) {
2275
+ if (entry.source.startsWith("agents/") && entry.source.endsWith(".asdm.md")) {
2276
+ const name = entry.source.slice("agents/".length, -".asdm.md".length);
2277
+ names.add(name);
2278
+ }
2279
+ }
2280
+ return names;
2281
+ }
2282
+ var doctor_default = defineCommand11({
2283
+ meta: {
2284
+ name: "doctor",
2285
+ description: "Run diagnostics on the ASDM setup"
2286
+ },
2287
+ async run(_ctx) {
2288
+ const cwd = process.cwd();
2289
+ const checks = [];
2290
+ let anyFailed = false;
2291
+ let projectConfig = null;
2292
+ let registryClient = null;
2293
+ const configPath = path14.join(cwd, ".asdm.json");
2294
+ const hasConfig = await exists(configPath);
2295
+ checks.push({
2296
+ label: ".asdm.json present",
2297
+ status: hasConfig ? "ok" : "fail",
2298
+ detail: hasConfig ? void 0 : "Run `asdm init` to create it"
2299
+ });
2300
+ if (!hasConfig) anyFailed = true;
2301
+ if (hasConfig) {
2302
+ try {
2303
+ projectConfig = await readProjectConfig(cwd);
2304
+ registryClient = new RegistryClient(projectConfig.registry);
2305
+ const reachable = await registryClient.ping();
2306
+ checks.push({
2307
+ label: `Registry reachable (${projectConfig.registry})`,
2308
+ status: reachable ? "ok" : "fail",
2309
+ detail: reachable ? void 0 : "Check GITHUB_TOKEN and network connectivity"
2310
+ });
2311
+ if (!reachable) anyFailed = true;
2312
+ } catch {
2313
+ checks.push({
2314
+ label: "Registry reachable",
2315
+ status: "fail",
2316
+ detail: "Could not read registry config"
2317
+ });
2318
+ anyFailed = true;
2319
+ }
2320
+ } else {
2321
+ checks.push({
2322
+ label: "Registry reachable",
2323
+ status: "skip",
2324
+ detail: "Skipped \u2014 no .asdm.json"
2325
+ });
2326
+ }
2327
+ const lockfile = await readLockfile(cwd);
2328
+ checks.push({
2329
+ label: ".asdm-lock.json present",
2330
+ status: lockfile ? "ok" : "warn",
2331
+ detail: lockfile ? void 0 : "Run `asdm sync` to generate lockfile"
2332
+ });
2333
+ if (!lockfile) anyFailed = true;
2334
+ if (lockfile) {
2335
+ const verifyResult = await verify(cwd);
2336
+ if (verifyResult.exitCode === VERIFY_EXIT_CODES.OK) {
2337
+ checks.push({
2338
+ label: `Managed files intact (${verifyResult.checkedFiles} files)`,
2339
+ status: "ok"
2340
+ });
2341
+ } else if (verifyResult.exitCode === VERIFY_EXIT_CODES.MODIFIED) {
2342
+ const count = verifyResult.violations.length;
2343
+ checks.push({
2344
+ label: "Managed files intact",
2345
+ status: "fail",
2346
+ detail: `${count} violation(s) \u2014 run \`asdm verify\` for details`
2347
+ });
2348
+ anyFailed = true;
2349
+ } else {
2350
+ checks.push({
2351
+ label: "Managed files intact",
2352
+ status: "warn",
2353
+ detail: `Exit code ${verifyResult.exitCode}`
2354
+ });
2355
+ }
2356
+ const managedEntries = Object.entries(lockfile.files).filter(([, e]) => e.managed);
2357
+ const missingFiles = [];
2358
+ const resolvedCwd = path14.resolve(cwd);
2359
+ for (const [filePath] of managedEntries) {
2360
+ const absPath = path14.resolve(cwd, filePath);
2361
+ if (!absPath.startsWith(resolvedCwd + path14.sep) && absPath !== resolvedCwd) {
2362
+ continue;
2363
+ }
2364
+ const fileExists = await exists(absPath);
2365
+ if (!fileExists) missingFiles.push(filePath);
2366
+ }
2367
+ checks.push({
2368
+ label: "All managed files on disk",
2369
+ status: missingFiles.length === 0 ? "ok" : "fail",
2370
+ detail: missingFiles.length > 0 ? `${missingFiles.length} missing \u2014 run \`asdm sync --force\`` : void 0
2371
+ });
2372
+ if (missingFiles.length > 0) anyFailed = true;
2373
+ } else {
2374
+ checks.push({
2375
+ label: "Managed files intact",
2376
+ status: "skip",
2377
+ detail: "Skipped \u2014 no lockfile"
2378
+ });
2379
+ checks.push({
2380
+ label: "All managed files on disk",
2381
+ status: "skip",
2382
+ detail: "Skipped \u2014 no lockfile"
2383
+ });
2384
+ }
2385
+ if (lockfile) {
2386
+ const overlays = await readOverlays(cwd);
2387
+ if (overlays.size === 0) {
2388
+ checks.push({
2389
+ label: "Overlay files valid",
2390
+ status: "ok",
2391
+ detail: "No overlays found"
2392
+ });
2393
+ } else {
2394
+ const knownAgents = extractAgentNamesFromLockfile(lockfile);
2395
+ const unknownOverlays = [...overlays.keys()].filter((id) => !knownAgents.has(id));
2396
+ if (unknownOverlays.length === 0) {
2397
+ checks.push({
2398
+ label: `Overlay files valid (${overlays.size} overlay(s))`,
2399
+ status: "ok"
2400
+ });
2401
+ } else {
2402
+ checks.push({
2403
+ label: "Overlay files valid",
2404
+ status: "warn",
2405
+ detail: `Unknown agent(s): ${unknownOverlays.join(", ")} \u2014 not in lockfile`
2406
+ });
2407
+ }
2408
+ }
2409
+ } else {
2410
+ checks.push({
2411
+ label: "Overlay files valid",
2412
+ status: "skip",
2413
+ detail: "Skipped \u2014 no lockfile"
2414
+ });
2415
+ }
2416
+ const gitignoreContent = await readFile(path14.join(cwd, ".gitignore"));
2417
+ const hasAsdmBlock = gitignoreContent?.includes(ASDM_MARKER_START) ?? false;
2418
+ checks.push({
2419
+ label: ".gitignore has ASDM block",
2420
+ status: hasAsdmBlock ? "ok" : "skip",
2421
+ detail: hasAsdmBlock ? void 0 : "Run 'asdm gitignore' to add managed output dirs"
2422
+ });
2423
+ if (lockfile && registryClient) {
2424
+ try {
2425
+ const latestManifest = await registryClient.getLatestManifest();
2426
+ const isUpToDate = lockfile.manifest_version === latestManifest.version;
2427
+ checks.push({
2428
+ label: "Registry version",
2429
+ status: isUpToDate ? "ok" : "warn",
2430
+ detail: isUpToDate ? `Up to date (v${lockfile.manifest_version})` : `Behind \u2014 local v${lockfile.manifest_version}, registry v${latestManifest.version}`
2431
+ });
2432
+ } catch {
2433
+ checks.push({
2434
+ label: "Registry version",
2435
+ status: "skip",
2436
+ detail: "Could not reach registry to check version"
2437
+ });
2438
+ }
2439
+ } else if (!registryClient) {
2440
+ checks.push({
2441
+ label: "Registry version",
2442
+ status: "skip",
2443
+ detail: "Skipped \u2014 no registry config"
2444
+ });
2445
+ } else {
2446
+ checks.push({
2447
+ label: "Registry version",
2448
+ status: "skip",
2449
+ detail: "Skipped \u2014 no lockfile"
2450
+ });
2451
+ }
2452
+ logger.asdm("ASDM Doctor");
2453
+ logger.divider();
2454
+ for (const check of checks) {
2455
+ logger.status(check.label, check.status, check.detail);
2456
+ }
2457
+ logger.divider();
2458
+ if (anyFailed) {
2459
+ logger.error("Some checks failed \u2014 review the output above");
2460
+ process.exit(1);
2461
+ } else {
2462
+ logger.success("All checks passed");
2463
+ }
2464
+ }
2465
+ });
2466
+
2467
+ // src/cli/commands/clean.ts
2468
+ import { defineCommand as defineCommand12 } from "citty";
2469
+ import path15 from "path";
2470
+ import { promises as fs5 } from "fs";
2471
+ import readline from "readline";
2472
+ init_fs();
2473
+ var LOCKFILE_NAME2 = ".asdm-lock.json";
2474
+ async function getFileSizeBytes(filePath) {
2475
+ try {
2476
+ const stat = await fs5.stat(filePath);
2477
+ return stat.size;
2478
+ } catch {
2479
+ return 0;
2480
+ }
2481
+ }
2482
+ function formatBytes(bytes) {
2483
+ if (bytes < 1024) return `${bytes} B`;
2484
+ const kb = bytes / 1024;
2485
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
2486
+ return `${(kb / 1024).toFixed(2)} MB`;
2487
+ }
2488
+ async function confirmPrompt(question) {
2489
+ const rl = readline.createInterface({
2490
+ input: process.stdin,
2491
+ output: process.stdout
2492
+ });
2493
+ return new Promise((resolve) => {
2494
+ rl.question(question, (answer) => {
2495
+ rl.close();
2496
+ resolve(answer.toLowerCase() === "y");
2497
+ });
2498
+ });
2499
+ }
2500
+ var clean_default = defineCommand12({
2501
+ meta: {
2502
+ name: "clean",
2503
+ description: "Remove ASDM-managed files and the lockfile"
2504
+ },
2505
+ args: {
2506
+ "dry-run": {
2507
+ type: "boolean",
2508
+ description: "Preview what would be removed without deleting",
2509
+ default: false
2510
+ },
2511
+ target: {
2512
+ type: "string",
2513
+ description: "Only clean files for a specific provider (opencode | claude-code | copilot)",
2514
+ alias: "t"
2515
+ }
2516
+ },
2517
+ async run(ctx) {
2518
+ const cwd = process.cwd();
2519
+ const dryRun = ctx.args["dry-run"];
2520
+ const target = ctx.args.target;
2521
+ if (dryRun) {
2522
+ logger.info("Dry run \u2014 no files will be removed");
2523
+ }
2524
+ const lockfile = await readLockfile(cwd);
2525
+ if (!lockfile) {
2526
+ logger.warn("No lockfile found \u2014 nothing to clean");
2527
+ return;
2528
+ }
2529
+ const managedEntries = Object.entries(lockfile.files).filter(([, entry]) => {
2530
+ if (!entry.managed) return false;
2531
+ if (target) return entry.adapter === target;
2532
+ return true;
2533
+ });
2534
+ if (managedEntries.length === 0) {
2535
+ if (target) {
2536
+ logger.warn(`No managed files found for provider "${target}"`);
2537
+ } else {
2538
+ logger.warn("No managed files found \u2014 nothing to clean");
2539
+ }
2540
+ return;
2541
+ }
2542
+ const managedPaths = managedEntries.map(([filePath]) => filePath);
2543
+ const resolvedCwd = path15.resolve(cwd);
2544
+ const safePaths = managedPaths.filter((relativePath) => {
2545
+ const absPath = path15.resolve(cwd, relativePath);
2546
+ return absPath.startsWith(resolvedCwd + path15.sep) || absPath === resolvedCwd;
2547
+ });
2548
+ const skippedSuspicious = managedPaths.length - safePaths.length;
2549
+ if (skippedSuspicious > 0) {
2550
+ logger.warn(`Skipping ${skippedSuspicious} path(s) outside project root`);
2551
+ }
2552
+ if (!dryRun && process.stdout.isTTY && process.stdin.isTTY) {
2553
+ const suffix = target ? ` for provider "${target}"` : "";
2554
+ const confirmed = await confirmPrompt(
2555
+ `About to delete ${safePaths.length} file(s)${suffix}. Continue? [y/N] `
2556
+ );
2557
+ if (!confirmed) {
2558
+ logger.info("Aborted \u2014 no files were removed");
2559
+ return;
2560
+ }
2561
+ }
2562
+ logger.asdm(`Cleaning ${safePaths.length} managed file(s)\u2026`);
2563
+ logger.divider();
2564
+ let removed = 0;
2565
+ let skippedMissing = 0;
2566
+ let totalBytesFreed = 0;
2567
+ for (const relativePath of safePaths) {
2568
+ const absolutePath = path15.resolve(cwd, relativePath);
2569
+ const fileExists = await exists(absolutePath);
2570
+ if (!fileExists) {
2571
+ logger.dim(` skip ${relativePath} (not found)`);
2572
+ skippedMissing++;
2573
+ continue;
2574
+ }
2575
+ if (dryRun) {
2576
+ logger.bullet(`would remove: ${relativePath}`);
2577
+ removed++;
2578
+ continue;
2579
+ }
2580
+ const fileSize = await getFileSizeBytes(absolutePath);
2581
+ await removeFile(absolutePath);
2582
+ totalBytesFreed += fileSize;
2583
+ logger.bullet(`removed: ${relativePath}`);
2584
+ removed++;
2585
+ }
2586
+ const lockfilePath = path15.join(cwd, LOCKFILE_NAME2);
2587
+ const lockfileOnDisk = await exists(lockfilePath);
2588
+ if (target) {
2589
+ if (!dryRun && lockfileOnDisk) {
2590
+ const updatedFiles = Object.fromEntries(
2591
+ Object.entries(lockfile.files).filter(([, entry]) => entry.adapter !== target)
2592
+ );
2593
+ await writeLockfile(cwd, { ...lockfile, files: updatedFiles });
2594
+ logger.bullet(`updated: ${LOCKFILE_NAME2} (removed ${target} entries)`);
2595
+ } else if (dryRun) {
2596
+ logger.bullet(`would update: ${LOCKFILE_NAME2} (remove ${target} entries)`);
2597
+ }
2598
+ } else {
2599
+ if (lockfileOnDisk) {
2600
+ if (dryRun) {
2601
+ logger.bullet(`would remove: ${LOCKFILE_NAME2}`);
2602
+ } else {
2603
+ const lockfileSize = await getFileSizeBytes(lockfilePath);
2604
+ await removeFile(lockfilePath);
2605
+ totalBytesFreed += lockfileSize;
2606
+ logger.bullet(`removed: ${LOCKFILE_NAME2}`);
2607
+ }
2608
+ }
2609
+ }
2610
+ logger.divider();
2611
+ if (dryRun) {
2612
+ const suffix = target ? ` for provider "${target}"` : "";
2613
+ logger.info(`${removed} file(s) would be removed${suffix}, ${skippedMissing} not found`);
2614
+ logger.info("Run without --dry-run to actually remove them");
2615
+ } else {
2616
+ const suffix = target ? ` (${target})` : "";
2617
+ logger.success(`Cleaned ${removed} managed file(s)${suffix} \u2014 ${formatBytes(totalBytesFreed)} freed`);
2618
+ if (skippedMissing > 0) logger.dim(` ${skippedMissing} file(s) were already missing`);
2619
+ logger.info("Run `asdm sync` to reinstall");
2620
+ }
2621
+ }
2622
+ });
2623
+
2624
+ // src/cli/commands/hooks.ts
2625
+ init_fs();
2626
+ import { defineCommand as defineCommand13 } from "citty";
2627
+ import path16 from "path";
2628
+ import { promises as fs6 } from "fs";
2629
+
2630
+ // src/utils/post-merge-hook.ts
2631
+ function generatePostMergeHook() {
2632
+ return `#!/usr/bin/env sh
2633
+ # ASDM MANAGED \u2014 post-merge hook
2634
+ if [ -f ".asdm.yaml" ] || [ -f ".asdm.json" ]; then
2635
+ echo "\u{1F504} ASDM: syncing after merge..."
2636
+ npx asdm sync
2637
+ fi
2638
+ `;
2639
+ }
2640
+
2641
+ // src/cli/commands/hooks.ts
2642
+ var HOOK_DEFINITIONS = {
2643
+ "pre-commit": {
2644
+ relativePath: ".git/hooks/pre-commit",
2645
+ content: `#!/usr/bin/env sh
2646
+ # ASDM \u2014 managed pre-commit hook
2647
+ # Verifies integrity of managed files before allowing commits.
2648
+ npx asdm verify --strict --quiet
2649
+ `,
2650
+ marker: "ASDM \u2014 managed pre-commit hook",
2651
+ description: "runs `asdm verify --strict --quiet` before every commit"
2652
+ },
2653
+ "post-merge": {
2654
+ relativePath: ".git/hooks/post-merge",
2655
+ content: generatePostMergeHook(),
2656
+ marker: "ASDM MANAGED \u2014 post-merge hook",
2657
+ description: "runs `asdm sync` after git pull/merge"
2658
+ }
2659
+ };
2660
+ function resolveHookTypes(hookFlag) {
2661
+ if (hookFlag === "pre-commit" || hookFlag === "post-merge") {
2662
+ return [hookFlag];
2663
+ }
2664
+ return ["pre-commit", "post-merge"];
2665
+ }
2666
+ async function installHook(cwd, hookType) {
2667
+ const def = HOOK_DEFINITIONS[hookType];
2668
+ const hookPath = path16.join(cwd, def.relativePath);
2669
+ const gitDir = path16.join(cwd, ".git");
2670
+ const hasGit = await exists(gitDir);
2671
+ if (!hasGit) {
2672
+ logger.error("No .git directory found", "Run `git init` first");
2673
+ process.exit(1);
2674
+ }
2675
+ const hookExists = await exists(hookPath);
2676
+ if (hookExists) {
2677
+ const existing = await fs6.readFile(hookPath, "utf-8");
2678
+ if (existing.includes(def.marker)) {
2679
+ logger.info(`ASDM ${hookType} hook is already installed`);
2680
+ return;
2681
+ }
2682
+ logger.warn(`A ${hookType} hook already exists and is not managed by ASDM.`);
2683
+ logger.warn(`Manual action required: add the ASDM logic to ${def.relativePath}`);
2684
+ process.exit(1);
2685
+ }
2686
+ await writeFile(hookPath, def.content);
2687
+ try {
2688
+ await fs6.chmod(hookPath, 493);
2689
+ } catch {
2690
+ }
2691
+ logger.success(`Installed ${hookType} hook at ${def.relativePath}`);
2692
+ logger.info(`The hook ${def.description}`);
2693
+ }
2694
+ async function uninstallHook(cwd, hookType) {
2695
+ const def = HOOK_DEFINITIONS[hookType];
2696
+ const hookPath = path16.join(cwd, def.relativePath);
2697
+ const hookExists = await exists(hookPath);
2698
+ if (!hookExists) {
2699
+ logger.info(`No ${hookType} hook found \u2014 nothing to remove`);
2700
+ return;
2701
+ }
2702
+ const content = await fs6.readFile(hookPath, "utf-8");
2703
+ if (!content.includes(def.marker)) {
2704
+ logger.warn(`The ${hookType} hook was not installed by ASDM \u2014 not removing it`);
2705
+ logger.info(`If you want to remove it manually: rm ${def.relativePath}`);
2706
+ process.exit(1);
2707
+ }
2708
+ await removeFile(hookPath);
2709
+ logger.success(`Removed ASDM ${hookType} hook from ${def.relativePath}`);
2710
+ }
2711
+ var installCommand = defineCommand13({
2712
+ meta: {
2713
+ name: "install",
2714
+ description: "Install ASDM git hooks (pre-commit and/or post-merge)"
2715
+ },
2716
+ args: {
2717
+ hook: {
2718
+ type: "string",
2719
+ description: "Which hook to install: pre-commit | post-merge | all",
2720
+ default: "all"
2721
+ }
2722
+ },
2723
+ async run(ctx) {
2724
+ const cwd = process.cwd();
2725
+ const hookTypes = resolveHookTypes(ctx.args.hook);
2726
+ for (const hookType of hookTypes) {
2727
+ await installHook(cwd, hookType);
2728
+ }
2729
+ }
2730
+ });
2731
+ var uninstallCommand = defineCommand13({
2732
+ meta: {
2733
+ name: "uninstall",
2734
+ description: "Remove ASDM git hooks"
2735
+ },
2736
+ args: {
2737
+ hook: {
2738
+ type: "string",
2739
+ description: "Which hook to remove: pre-commit | post-merge | all",
2740
+ default: "all"
2741
+ }
2742
+ },
2743
+ async run(ctx) {
2744
+ const cwd = process.cwd();
2745
+ const hookTypes = resolveHookTypes(ctx.args.hook);
2746
+ for (const hookType of hookTypes) {
2747
+ await uninstallHook(cwd, hookType);
2748
+ }
2749
+ }
2750
+ });
2751
+ var hooks_default = defineCommand13({
2752
+ meta: {
2753
+ name: "hooks",
2754
+ description: "Manage git hooks for ASDM integrity verification and auto-sync"
2755
+ },
2756
+ subCommands: {
2757
+ install: installCommand,
2758
+ uninstall: uninstallCommand
2759
+ }
2760
+ });
2761
+
2762
+ // src/cli/commands/gitignore.ts
2763
+ import { defineCommand as defineCommand14 } from "citty";
2764
+ var gitignore_default = defineCommand14({
2765
+ meta: {
2766
+ name: "gitignore",
2767
+ description: "Add or remove ASDM managed entries in .gitignore"
2768
+ },
2769
+ args: {
2770
+ "dry-run": {
2771
+ type: "boolean",
2772
+ description: "Print what would be added without modifying the file",
2773
+ default: false
2774
+ },
2775
+ remove: {
2776
+ type: "boolean",
2777
+ description: "Remove the ASDM block from .gitignore",
2778
+ default: false
2779
+ }
2780
+ },
2781
+ async run(ctx) {
2782
+ const cwd = process.cwd();
2783
+ let providers;
2784
+ try {
2785
+ const config = await readProjectConfig(cwd);
2786
+ providers = config.providers ?? ["opencode"];
2787
+ } catch {
2788
+ logger.error("No .asdm.json found", "Run `asdm init` first");
2789
+ process.exit(1);
2790
+ return;
2791
+ }
2792
+ if (ctx.args.remove) {
2793
+ const removed = await removeGitignoreBlock(cwd);
2794
+ if (removed) {
2795
+ logger.success("Removed ASDM block from .gitignore");
2796
+ } else {
2797
+ logger.info("No ASDM block found in .gitignore \u2014 nothing to remove");
2798
+ }
2799
+ return;
2800
+ }
2801
+ const entries = getGitignoreEntries(providers);
2802
+ if (ctx.args["dry-run"]) {
2803
+ logger.asdm("Would add to .gitignore:");
2804
+ logger.dim(ASDM_MARKER_START);
2805
+ for (const entry of entries) {
2806
+ logger.bullet(entry);
2807
+ }
2808
+ logger.dim(ASDM_MARKER_END);
2809
+ return;
2810
+ }
2811
+ const updated = await updateGitignore(cwd, providers);
2812
+ if (updated) {
2813
+ logger.success(`Updated .gitignore with ${entries.length} ASDM entr${entries.length === 1 ? "y" : "ies"}`);
2814
+ } else {
2815
+ logger.info(".gitignore is already up to date");
2816
+ }
2817
+ }
2818
+ });
2819
+
2820
+ // src/cli/commands/telemetry.ts
2821
+ import { defineCommand as defineCommand15 } from "citty";
2822
+ var showCommand = defineCommand15({
2823
+ meta: {
2824
+ name: "show",
2825
+ description: "Show recent telemetry events"
2826
+ },
2827
+ args: {
2828
+ limit: {
2829
+ type: "string",
2830
+ description: "Number of events to show",
2831
+ default: "20"
2832
+ },
2833
+ json: {
2834
+ type: "boolean",
2835
+ description: "Output as JSON",
2836
+ default: false
2837
+ }
2838
+ },
2839
+ async run(ctx) {
2840
+ const cwd = process.cwd();
2841
+ const writer = new TelemetryWriter(cwd);
2842
+ const events = await writer.readAll();
2843
+ const limit = Math.max(1, parseInt(ctx.args.limit, 10) || 20);
2844
+ const shown = events.slice(-limit);
2845
+ if (ctx.args.json) {
2846
+ console.log(JSON.stringify(shown, null, 2));
2847
+ return;
2848
+ }
2849
+ if (shown.length === 0) {
2850
+ logger.info("No telemetry events found in .asdm-telemetry.jsonl");
2851
+ return;
2852
+ }
2853
+ logger.asdm(`Last ${shown.length} telemetry event(s)`);
2854
+ logger.divider();
2855
+ for (const event of shown) {
2856
+ const ts = new Date(event.timestamp).toLocaleString();
2857
+ const parts = [];
2858
+ if (event.profile) parts.push(`profile=${event.profile}`);
2859
+ if (event.durationMs != null) parts.push(`${event.durationMs}ms`);
2860
+ if (event.assetCount != null) parts.push(`${event.assetCount} assets`);
2861
+ if (event.violations != null) parts.push(`${event.violations} violation(s)`);
2862
+ if (event.error) parts.push(`error="${event.error}"`);
2863
+ const detail = parts.length > 0 ? ` ${parts.join(" ")}` : "";
2864
+ logger.bullet(`${event.event.padEnd(20)} ${ts}${detail}`);
2865
+ }
2866
+ }
2867
+ });
2868
+ var clearCommand = defineCommand15({
2869
+ meta: {
2870
+ name: "clear",
2871
+ description: "Clear the local telemetry log"
2872
+ },
2873
+ args: {
2874
+ force: {
2875
+ type: "boolean",
2876
+ description: "Skip confirmation prompt",
2877
+ default: false
2878
+ }
2879
+ },
2880
+ async run(ctx) {
2881
+ const cwd = process.cwd();
2882
+ if (!ctx.args.force) {
2883
+ logger.warn("This will permanently delete all local telemetry data.");
2884
+ logger.info("Use --force to confirm: asdm telemetry clear --force");
2885
+ process.exit(1);
2886
+ }
2887
+ const writer = new TelemetryWriter(cwd);
2888
+ await writer.clear();
2889
+ logger.success("Telemetry log cleared (.asdm-telemetry.jsonl removed)");
2890
+ }
2891
+ });
2892
+ var telemetry_default = defineCommand15({
2893
+ meta: {
2894
+ name: "telemetry",
2895
+ description: "Manage local telemetry data"
2896
+ },
2897
+ subCommands: {
2898
+ show: showCommand,
2899
+ clear: clearCommand
2900
+ }
2901
+ });
2902
+
2903
+ // src/core/version-check.ts
2904
+ init_fs();
2905
+ import path17 from "path";
2906
+ var CACHE_FILENAME = "version-check-cache.json";
2907
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2908
+ var FETCH_TIMEOUT_MS = 3e3;
2909
+ function isNewerVersion(current, latest) {
2910
+ const parse = (v) => {
2911
+ const parts = v.replace(/^v/, "").split(".");
2912
+ return [
2913
+ parseInt(parts[0] ?? "0", 10),
2914
+ parseInt(parts[1] ?? "0", 10),
2915
+ parseInt(parts[2] ?? "0", 10)
2916
+ ];
2917
+ };
2918
+ const [cMajor, cMinor, cPatch] = parse(current);
2919
+ const [lMajor, lMinor, lPatch] = parse(latest);
2920
+ if (lMajor !== cMajor) return lMajor > cMajor;
2921
+ if (lMinor !== cMinor) return lMinor > cMinor;
2922
+ return lPatch > cPatch;
2923
+ }
2924
+ async function checkForUpdate(currentVersion) {
2925
+ const cacheDir = getAsdmConfigDir();
2926
+ const cachePath = path17.join(cacheDir, CACHE_FILENAME);
2927
+ const cache = await readJson(cachePath);
2928
+ if (cache) {
2929
+ const ageMs = Date.now() - new Date(cache.checkedAt).getTime();
2930
+ if (ageMs < CHECK_INTERVAL_MS) {
2931
+ return isNewerVersion(currentVersion, cache.latestVersion) ? cache.latestVersion : null;
2932
+ }
2933
+ }
2934
+ try {
2935
+ const controller = new AbortController();
2936
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2937
+ const response = await fetch("https://registry.npmjs.org/@asdm/cli/latest", {
2938
+ signal: controller.signal,
2939
+ headers: { Accept: "application/json" }
2940
+ });
2941
+ clearTimeout(timer);
2942
+ if (!response.ok) return null;
2943
+ const data = await response.json();
2944
+ const latestVersion = data.version;
2945
+ if (!latestVersion) return null;
2946
+ await ensureDir(cacheDir);
2947
+ await writeJson(cachePath, {
2948
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
2949
+ latestVersion
2950
+ });
2951
+ return isNewerVersion(currentVersion, latestVersion) ? latestVersion : null;
2952
+ } catch {
2953
+ return null;
2954
+ }
2955
+ }
2956
+
2957
+ // src/cli/index.ts
2958
+ var rootCommand = defineCommand16({
2959
+ meta: {
2960
+ name: "asdm",
2961
+ version: "0.1.0",
2962
+ description: "Agentic Software Delivery Model \u2014 Write Once, Emit Many"
2963
+ },
2964
+ subCommands: {
2965
+ init: init_default,
2966
+ sync: sync_default,
2967
+ verify: verify_default,
2968
+ status: status_default,
2969
+ use: use_default,
2970
+ profiles: profiles_default,
2971
+ agents: agents_default,
2972
+ skills: skills_default,
2973
+ commands: commands_default,
2974
+ version: version_default,
2975
+ doctor: doctor_default,
2976
+ clean: clean_default,
2977
+ hooks: hooks_default,
2978
+ gitignore: gitignore_default,
2979
+ telemetry: telemetry_default
2980
+ }
2981
+ });
2982
+ function printUpdateBox(currentVersion, latestVersion) {
2983
+ const YELLOW = "\x1B[33m";
2984
+ const BOLD2 = "\x1B[1m";
2985
+ const RESET2 = "\x1B[0m";
2986
+ const updateLine = ` Update available: ${currentVersion} \u2192 ${latestVersion}`;
2987
+ const cmdLine = ` Run: npm install -g @asdm/cli`;
2988
+ const MIN_WIDTH = 45;
2989
+ const innerWidth = Math.max(
2990
+ Math.max(updateLine.length, cmdLine.length) + 2,
2991
+ MIN_WIDTH
2992
+ );
2993
+ const top = `\u256D${"\u2500".repeat(innerWidth)}\u256E`;
2994
+ const r1 = `\u2502${updateLine.padEnd(innerWidth)}\u2502`;
2995
+ const r2 = `\u2502${cmdLine.padEnd(innerWidth)}\u2502`;
2996
+ const bot = `\u2570${"\u2500".repeat(innerWidth)}\u256F`;
2997
+ console.log(`
2998
+ ${YELLOW}${BOLD2}${top}
2999
+ ${r1}
3000
+ ${r2}
3001
+ ${bot}${RESET2}`);
3002
+ }
3003
+ async function main() {
3004
+ await runMain(rootCommand);
3005
+ try {
3006
+ const latestVersion = await checkForUpdate("0.1.0");
3007
+ if (latestVersion) {
3008
+ printUpdateBox("0.1.0", latestVersion);
3009
+ }
3010
+ } catch {
3011
+ }
3012
+ }
3013
+ main();