@moda-ai/cli 1.55.108 → 1.55.110

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 (46) hide show
  1. package/README.md +3 -3
  2. package/dist/{agent-vvefg4bm.js → agent-5kfdbx36.js} +8 -8
  3. package/dist/{change-impact-5h3k9kg6.js → change-impact-v2vbcphy.js} +5 -5
  4. package/dist/cli-2fv0q9tx.js +141 -0
  5. package/dist/{cli-xxpaq3n8.js → cli-3r578f36.js} +6 -6
  6. package/dist/{cli-13zdcv4w.js → cli-3thzh1mq.js} +1 -1
  7. package/dist/{cli-998n4kz9.js → cli-6df723jn.js} +1 -1
  8. package/dist/{cli-5z2f1w5m.js → cli-6zmc6asf.js} +6 -6
  9. package/dist/{cli-ap7wfbnk.js → cli-7gnw6smk.js} +2 -2
  10. package/dist/{cli-r2dxdhg8.js → cli-84jkd2x7.js} +13 -13
  11. package/dist/{cli-mc3z77bv.js → cli-8qtxp95w.js} +1 -1
  12. package/dist/cli-ahqr7wsp.js +429 -0
  13. package/dist/{cli-qwnkv4jy.js → cli-bgbtrmtr.js} +1 -1
  14. package/dist/{cli-n9ng8mym.js → cli-cp8gv24j.js} +1 -1
  15. package/dist/{cli-eq09n00v.js → cli-ew4pkn5b.js} +6 -6
  16. package/dist/{cli-ekartfps.js → cli-hv4bztsm.js} +1 -1
  17. package/dist/{cli-62e1vy8f.js → cli-m6w66x9d.js} +3 -3
  18. package/dist/{cli-2bhhk2nn.js → cli-mh94f7gp.js} +2 -2
  19. package/dist/{cli-1nd4339b.js → cli-tca7sztv.js} +2 -2
  20. package/dist/cli.js +29 -108
  21. package/dist/{doctor-1hhktzsz.js → doctor-t0b99wnq.js} +7 -7
  22. package/dist/{harness-github-actions-1hq2ed0v.js → harness-github-actions-e4zrcrym.js} +5 -5
  23. package/dist/{harness-7s0w2wc9.js → harness-phdj0eqr.js} +5 -5
  24. package/dist/{index-9adksf01.js → index-65grk0mp.js} +5 -5
  25. package/dist/{index-n1jx9hta.js → index-86wb6vbh.js} +6 -6
  26. package/dist/{index-2g0ft2hm.js → index-zhg2zc3x.js} +13 -13
  27. package/dist/{production-intelligence-8w7an97c.js → production-intelligence-nphep7xw.js} +5 -3
  28. package/dist/{profile-commands-zgq8dja0.js → profile-commands-tz3jykvh.js} +4 -4
  29. package/dist/{prompt-attribution-c1a5pysf.js → prompt-attribution-8ak38a3q.js} +5 -5
  30. package/dist/prompt-source-mcp.js +5 -5
  31. package/dist/{prompts-fdqrbawx.js → prompts-a8zcvc1c.js} +5 -5
  32. package/dist/{provision-d0sxcka1.js → provision-2dzzb0be.js} +5 -5
  33. package/dist/{registry-vxrxw1tj.js → registry-p07epn79.js} +22 -18
  34. package/dist/skills-5atq9sgh.js +17 -0
  35. package/dist/{skills-bundled-7jes6hqx.js → skills-bundled-tcxpw88n.js} +2 -2
  36. package/dist/{sync-gxgpfztm.js → sync-e76vmfty.js} +10 -12
  37. package/package.json +1 -1
  38. package/skills/integration/index.json +2 -2
  39. package/skills/moda-cli/SKILL.md +1 -1
  40. package/skills/moda-cli/references/commands.md +2 -2
  41. package/skills/moda-cli/references/dashboard-parity.md +1 -1
  42. package/skills/moda-cli/references/output-protocol.md +4 -4
  43. package/skills/moda-cli/references/registry.md +1 -3
  44. package/dist/cli-h7hfpmwa.js +0 -1019
  45. package/dist/cli-qm5b1vck.js +0 -220
  46. package/dist/skills-rjbnv8nn.js +0 -23
@@ -1,1019 +0,0 @@
1
- import {
2
- SKILL_SOURCE_HARNESS,
3
- SKILL_SOURCE_INTEGRATION_BUNDLED,
4
- pruneManagedSkillPaths,
5
- readSkillsManifest,
6
- writeSkillsManifest
7
- } from "./cli-qm5b1vck.js";
8
- import {
9
- detectAgents
10
- } from "./cli-rjav0vj1.js";
11
- import {
12
- claudeSkillToCursorRule
13
- } from "./cli-hcwdz9sd.js";
14
- import {
15
- createCommandContext
16
- } from "./cli-13zdcv4w.js";
17
- import {
18
- assertFlags,
19
- assertPositionals,
20
- writeDryRun
21
- } from "./cli-2bhhk2nn.js";
22
- import {
23
- callControlAPI
24
- } from "./cli-n9ng8mym.js";
25
- import {
26
- recordApiRequest,
27
- resolveApiKey,
28
- resolveIngestUrl,
29
- resolveTenantId,
30
- shellArg
31
- } from "./cli-1nd4339b.js";
32
- import {
33
- CliInputError
34
- } from "./cli-k4tqwah4.js";
35
- import {
36
- __require
37
- } from "./cli-0v6na3yp.js";
38
-
39
- // src/skills.ts
40
- import { execFileSync } from "node:child_process";
41
- import { createHash } from "node:crypto";
42
- import {
43
- existsSync as existsSync2,
44
- lstatSync as lstatSync2,
45
- mkdirSync as mkdirSync2,
46
- readdirSync,
47
- readFileSync as readFileSync2,
48
- writeFileSync as writeFileSync2
49
- } from "node:fs";
50
- import { basename, dirname as dirname2, join as join2, relative } from "node:path";
51
-
52
- // src/agents-md.ts
53
- import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
54
- import { dirname, join } from "node:path";
55
- var AGENTS_MD_FILENAME = "AGENTS.md";
56
- var FIXES_BLOCK_BEGIN = "<!-- moda:fixes:begin -->";
57
- var FIXES_BLOCK_END = "<!-- moda:fixes:end -->";
58
- function renderFixesBlock() {
59
- return [
60
- FIXES_BLOCK_BEGIN,
61
- "## Fixing production issues with Moda (managed block — edits inside are overwritten by moda-cli)",
62
- "",
63
- "Moda supplies the production evidence; you propose and make the fix yourself.",
64
- "The `moda` CLI needs `MODA_API_KEY` (or `npx -y -p @moda-ai/cli moda ...` when",
65
- "not installed); `moda <command> --help` lists its flags.",
66
- "",
67
- '- Find where something happened in production: `moda traces search "<words>"`, then',
68
- " `moda traces get <conversation_id> --msg-index=<message_index>` to read the turn.",
69
- "- Find what to fix: `moda investigate` ranks tool failures, user frustrations,",
70
- " and top Problems with evidence; `moda problems` ranks cross-signal Problems",
71
- " by root cause; `moda failures` lists tool failures only.",
72
- "- Read the evidence before editing: `moda problems get <problem_id>` (dossier),",
73
- " `moda problems get <problem_id> --evidence` (attribution rationales and anchors),",
74
- " `moda problems get <problem_id> --traces` (affected traces),",
75
- " `moda detections tools <tool_name>` (failing calls in context), and",
76
- " `moda traces get <conversation_id>` (the messages around a turn; `--all` for",
77
- " the whole trace).",
78
- "- Propose the change in the code, prompt, tool definition, or skill that",
79
- " causes the issue, and cite the Moda problem or trace ids it is based on",
80
- " in the PR description.",
81
- "- Prompts live in `.moda/prompts.yml` (version 2: `prompt_paths` globs plus",
82
- " `sources:` mappings for prompts defined in code); after `moda prompts sync`,",
83
- " pass `prompt: lock.prompts['<key>']` to createThinkTelemetry/createAI7Telemetry.",
84
- "- After the fix ships, record it with",
85
- " `moda problems feedback <problem_id> --action=mark_fixed`; if the issue",
86
- " is detected again, the Problem comes back.",
87
- FIXES_BLOCK_END
88
- ].join(`
89
- `);
90
- }
91
- function countOccurrences(text, needle) {
92
- return text.split(needle).length - 1;
93
- }
94
- function upsertManagedBlock(existing, block) {
95
- if (existing === null) {
96
- return { ok: true, content: `${block}
97
- ` };
98
- }
99
- const begins = countOccurrences(existing, FIXES_BLOCK_BEGIN);
100
- const ends = countOccurrences(existing, FIXES_BLOCK_END);
101
- if (begins === 0 && ends === 0) {
102
- const separator = existing === "" || existing.endsWith(`
103
-
104
- `) ? "" : existing.endsWith(`
105
- `) ? `
106
- ` : `
107
-
108
- `;
109
- return { ok: true, content: `${existing}${separator}${block}
110
- ` };
111
- }
112
- const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
113
- const endIdx = existing.indexOf(FIXES_BLOCK_END);
114
- if (begins === 1 && ends === 1 && endIdx > beginIdx) {
115
- const before = existing.slice(0, beginIdx);
116
- const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
117
- return { ok: true, content: `${before}${block}${after}` };
118
- }
119
- const problem = begins === 1 && ends === 1 ? `its ${FIXES_BLOCK_END} marker comes before its ${FIXES_BLOCK_BEGIN} marker` : `it has ${begins} ${FIXES_BLOCK_BEGIN} and ${ends} ${FIXES_BLOCK_END} marker(s) instead of one of each`;
120
- return {
121
- ok: false,
122
- reason: `The Moda block in ${AGENTS_MD_FILENAME} was not updated because ${problem}. ` + "The file was left unchanged so no text is lost. Keep exactly one begin marker followed by one end marker " + "(or delete the stray markers), then rerun `moda skills install moda-cli` (or `moda skills push`)."
123
- };
124
- }
125
- function writeFileIfChanged(filePath, content) {
126
- try {
127
- mkdirSync(dirname(filePath), { recursive: true });
128
- if (existsSync(filePath)) {
129
- const current = readFileSync(filePath, "utf8");
130
- if (current === content)
131
- return { status: "unchanged" };
132
- writeFileSync(filePath, content);
133
- return { status: "updated" };
134
- }
135
- writeFileSync(filePath, content);
136
- return { status: "wrote" };
137
- } catch (err) {
138
- return { status: "failed", error: err instanceof Error ? err.message : String(err) };
139
- }
140
- }
141
- function upsertFixesAgentsBlock(cwd) {
142
- const filePath = join(cwd, AGENTS_MD_FILENAME);
143
- let existing = null;
144
- try {
145
- if (lstatSync(filePath).isSymbolicLink()) {
146
- return { path: AGENTS_MD_FILENAME, status: "skipped", reason: "AGENTS.md is a symlink and was left unchanged. Update the linked file manually, or replace the link with a project file and rerun `moda skills install moda-cli`." };
147
- }
148
- } catch (err) {
149
- if (err.code !== "ENOENT") {
150
- return { path: AGENTS_MD_FILENAME, status: "failed", reason: `Could not inspect ${AGENTS_MD_FILENAME}: ${err instanceof Error ? err.message : String(err)}` };
151
- }
152
- }
153
- if (existsSync(filePath)) {
154
- try {
155
- existing = readFileSync(filePath, "utf8");
156
- } catch (err) {
157
- return {
158
- path: AGENTS_MD_FILENAME,
159
- status: "failed",
160
- reason: `Could not read ${AGENTS_MD_FILENAME}: ${err instanceof Error ? err.message : String(err)}`
161
- };
162
- }
163
- }
164
- const next = upsertManagedBlock(existing, renderFixesBlock());
165
- if (!next.ok)
166
- return { path: AGENTS_MD_FILENAME, status: "skipped", reason: next.reason };
167
- const written = writeFileIfChanged(filePath, next.content);
168
- return {
169
- path: AGENTS_MD_FILENAME,
170
- status: written.status,
171
- ...written.error ? { reason: `Could not write ${AGENTS_MD_FILENAME}: ${written.error}` } : {}
172
- };
173
- }
174
- function agentsMdProblem(agentsMd) {
175
- if (!agentsMd || agentsMd.status !== "skipped" && agentsMd.status !== "failed")
176
- return;
177
- return agentsMd.reason ?? `The Moda block in ${agentsMd.path} could not be updated.`;
178
- }
179
-
180
- // src/skills.ts
181
- var SKILLS_REQUEST_TIMEOUT_MS = 15000;
182
- var VALID_STATUSES = new Set(["approved", "proposed", "all"]);
183
- var SKILL_PROPOSALS_MANIFEST_PATH = join2(".moda", "skill-proposals.yml");
184
- async function fetchSkills(status) {
185
- const apiKey = resolveApiKey();
186
- if (!apiKey) {
187
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
188
- }
189
- const ingestUrl = resolveIngestUrl().replace(/\/$/, "");
190
- const url = `${ingestUrl}/v1/skills?status=${encodeURIComponent(status)}`;
191
- const controller = new AbortController;
192
- const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
193
- let response;
194
- try {
195
- recordApiRequest();
196
- response = await fetch(url, {
197
- headers: { Authorization: `Bearer ${apiKey}` },
198
- signal: controller.signal
199
- });
200
- } catch (err) {
201
- const message = err instanceof Error ? err.message : String(err);
202
- throw new Error(`Network error: failed to reach ${ingestUrl} (${message})`);
203
- } finally {
204
- clearTimeout(timeout);
205
- }
206
- if (!response.ok) {
207
- const body = await response.text().catch(() => "");
208
- throw new Error(`Failed to fetch skills (HTTP ${response.status}): ${body.slice(0, 200)}`);
209
- }
210
- const data = await response.json();
211
- if (!data.success) {
212
- throw new Error(data.message || "Failed to fetch skills");
213
- }
214
- const skills = Array.isArray(data.skills) ? data.skills : [];
215
- const release = data.release && typeof data.release === "object" ? data.release : null;
216
- return {
217
- skills,
218
- tenantId: data.tenant_id || resolveTenantId() || "",
219
- release,
220
- releaseKey: releaseKeyFor(release, skills, status),
221
- status
222
- };
223
- }
224
- async function skillRunsRequest(url) {
225
- const apiKey = resolveApiKey();
226
- if (!apiKey) {
227
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
228
- }
229
- const controller = new AbortController;
230
- const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
231
- let response;
232
- try {
233
- recordApiRequest();
234
- response = await fetch(url, {
235
- headers: { Authorization: `Bearer ${apiKey}` },
236
- signal: controller.signal
237
- });
238
- } catch (err) {
239
- const message = err instanceof Error ? err.message : String(err);
240
- throw new Error(`Network error: failed to reach ${url} (${message})`);
241
- } finally {
242
- clearTimeout(timeout);
243
- }
244
- const text = await response.text().catch(() => "");
245
- if (!response.ok && response.status !== 404) {
246
- throw new Error(`Failed to fetch run status (HTTP ${response.status}): ${text.slice(0, 200)}`);
247
- }
248
- let body = {};
249
- try {
250
- body = text ? JSON.parse(text) : {};
251
- } catch {
252
- body = {};
253
- }
254
- return { status: response.status, body };
255
- }
256
- async function fetchSkillRunStatus(runId) {
257
- const ingestUrl = resolveIngestUrl().replace(/\/$/, "");
258
- let resolvedRunId = runId;
259
- if (!resolvedRunId) {
260
- const list = await skillRunsRequest(`${ingestUrl}/v1/skills/runs?limit=1`);
261
- const listBody = list.body;
262
- const latest = Array.isArray(listBody.runs) ? listBody.runs[0] : undefined;
263
- if (!latest)
264
- return { run: null, events: [] };
265
- resolvedRunId = latest.generation_run_id;
266
- if (!resolvedRunId)
267
- return { run: latest, events: [] };
268
- }
269
- const detail = await skillRunsRequest(`${ingestUrl}/v1/skills/runs/${encodeURIComponent(resolvedRunId)}`);
270
- if (detail.status === 404) {
271
- if (runId) {
272
- throw new CliInputError(`No skill generation run found with id '${runId}'`);
273
- }
274
- return { run: null, events: [] };
275
- }
276
- const detailBody = detail.body;
277
- return {
278
- run: detailBody.run ?? null,
279
- events: Array.isArray(detailBody.events) ? detailBody.events : []
280
- };
281
- }
282
- var STATUS_HUMAN_EVENT_LIMIT = 10;
283
- function formatRunStatusHuman(run, events) {
284
- const counters = run.counters ?? {};
285
- const lines = [
286
- run.generation_run_id && `run: ${run.generation_run_id}`,
287
- `status: ${run.status || "unknown"}`,
288
- run.current_stage && `stage: ${run.current_stage}`,
289
- run.outcome && `outcome: ${run.outcome}`,
290
- `processed: ${Number(counters.sessions_processed ?? 0)}`,
291
- `failed: ${Number(counters.sessions_failed ?? 0)}`,
292
- Number(counters.distilled_skills_generated ?? 0) > 0 && `distilled: ${Number(counters.distilled_skills_generated ?? 0)}`,
293
- Number(counters.skills_indexed ?? 0) > 0 && `indexed: ${Number(counters.skills_indexed ?? 0)}`,
294
- (run.generated_skill_ids?.length ?? 0) > 0 && `skills: ${(run.generated_skill_ids ?? []).join(", ")}`,
295
- run.error && `error: ${run.error}`,
296
- run.started_at && `started: ${run.started_at}`,
297
- run.updated_at && `updated: ${run.updated_at}`
298
- ].filter(Boolean);
299
- if (events.length > 0) {
300
- lines.push("recent events (newest first):");
301
- for (const event of events.slice(0, STATUS_HUMAN_EVENT_LIMIT)) {
302
- lines.push(` ${[event.created_at, event.level, event.stage, event.message].filter(Boolean).join(" ")}`);
303
- }
304
- }
305
- return lines.join(`
306
- `);
307
- }
308
- function writeIfChanged(filePath, content) {
309
- try {
310
- mkdirSync2(dirname2(filePath), { recursive: true });
311
- if (existsSync2(filePath)) {
312
- const current = readFileSync2(filePath, "utf8");
313
- if (current === content)
314
- return "unchanged";
315
- writeFileSync2(filePath, content);
316
- return "updated";
317
- }
318
- writeFileSync2(filePath, content);
319
- return "wrote";
320
- } catch {
321
- return "failed";
322
- }
323
- }
324
- async function pullSkills(options = {}) {
325
- const status = options.status ?? "approved";
326
- const cwd = options.cwd ?? process.cwd();
327
- const log = (msg) => {
328
- if (!options.silent)
329
- console.log(msg);
330
- };
331
- const fetched = await fetchSkills(status);
332
- const { skills } = fetched;
333
- const summary = {
334
- total: skills.length,
335
- pruned: 0,
336
- wrote: 0,
337
- updated: 0,
338
- unchanged: 0,
339
- failed: 0
340
- };
341
- if (skills.length === 0) {
342
- log(" No skills to pull yet.");
343
- return summary;
344
- }
345
- const previousManifest = readSkillsManifest(cwd);
346
- const preservedIntegrationRecords = (previousManifest?.records ?? []).filter((record) => record.source === SKILL_SOURCE_INTEGRATION_BUNDLED);
347
- if (previousManifest && previousManifest.managedPaths.length > 0 && previousManifest.releaseKey !== fetched.releaseKey) {
348
- const integrationPaths = new Set(preservedIntegrationRecords.flatMap((record) => record.paths));
349
- const harnessPaths = previousManifest.managedPaths.filter((path) => !integrationPaths.has(path));
350
- if (harnessPaths.length > 0) {
351
- summary.pruned = pruneManagedSkillPaths(cwd, harnessPaths, log);
352
- }
353
- }
354
- const agents = detectAgents();
355
- const writeCursor = agents.includes("Cursor") || existsSync2(join2(cwd, ".cursor"));
356
- const records = [];
357
- for (const skill of skills) {
358
- if (!skill.skill_id || !skill.skill_md) {
359
- summary.failed++;
360
- continue;
361
- }
362
- const safeId = basename(skill.skill_id);
363
- if (!safeId || !/^[A-Za-z0-9_-]+$/.test(safeId)) {
364
- summary.failed++;
365
- log(` skipped invalid skill_id: ${JSON.stringify(skill.skill_id).slice(0, 80)}`);
366
- continue;
367
- }
368
- const claudePath = join2(cwd, ".claude", "skills", safeId, "SKILL.md");
369
- const claudeStatus = writeIfChanged(claudePath, skill.skill_md);
370
- const claudeRelativePath = `.claude/skills/${safeId}/SKILL.md`;
371
- recordOutcome(summary, claudeStatus, log, claudeRelativePath);
372
- const paths = [];
373
- if (claudeStatus !== "failed")
374
- paths.push(claudeRelativePath);
375
- if (writeCursor) {
376
- const cursorPath = join2(cwd, ".cursor", "rules", `${safeId}.mdc`);
377
- const cursorStatus = writeIfChanged(cursorPath, claudeSkillToCursorRule(skill.skill_md));
378
- const cursorRelativePath = `.cursor/rules/${safeId}.mdc`;
379
- recordOutcome(summary, cursorStatus, log, cursorRelativePath);
380
- if (cursorStatus !== "failed")
381
- paths.push(cursorRelativePath);
382
- }
383
- if (paths.length > 0) {
384
- records.push({
385
- skillId: safeId,
386
- contentHash: sha256(skill.skill_md),
387
- source: SKILL_SOURCE_HARNESS,
388
- paths
389
- });
390
- }
391
- }
392
- writeSkillsManifest(cwd, manifestHeaderFor(fetched), [...records, ...preservedIntegrationRecords]);
393
- return summary;
394
- }
395
- function recordOutcome(summary, status, log, relativePath) {
396
- switch (status) {
397
- case "wrote":
398
- summary.wrote++;
399
- log(` wrote ${relativePath}`);
400
- break;
401
- case "updated":
402
- summary.updated++;
403
- log(` updated ${relativePath}`);
404
- break;
405
- case "unchanged":
406
- summary.unchanged++;
407
- break;
408
- case "failed":
409
- summary.failed++;
410
- log(` failed ${relativePath}`);
411
- break;
412
- }
413
- }
414
- var SKILLS_DIR = join2(".claude", "skills");
415
- var SKILL_FILE = "SKILL.md";
416
- var MAX_SKILL_WALK_DEPTH = 4;
417
- async function runSkillSync(flags = {}, profileOptions = {}, options = {}) {
418
- const cwd = options.cwd ?? process.cwd();
419
- const dryRun = flags["dry-run"] === "true";
420
- const agentsMd = dryRun ? undefined : upsertFixesAgentsBlock(cwd);
421
- const skills = discoverLocalSkills(cwd);
422
- if (skills.length === 0) {
423
- return { synced: [], dryRun, ...agentsMd ? { agentsMd } : {} };
424
- }
425
- const sourceCommit = getGitCommit(cwd);
426
- const response = await callControlAPI("/skills/sync", {
427
- method: "POST",
428
- body: JSON.stringify({
429
- skills: skills.map((skill) => ({
430
- key: skill.key,
431
- name: skill.name,
432
- description: skill.description,
433
- skillMd: skill.skillMd,
434
- sourcePath: skill.sourcePath,
435
- contentHash: skill.contentHash,
436
- sourceCommit
437
- })),
438
- sourceCommit,
439
- dryRun
440
- })
441
- }, profileOptions);
442
- return { synced: response.synced ?? [], dryRun, ...agentsMd ? { agentsMd } : {} };
443
- }
444
- function discoverLocalSkills(cwd) {
445
- const skillsRoot = join2(cwd, SKILLS_DIR);
446
- if (!existsSync2(skillsRoot))
447
- return [];
448
- const managed = new Set(managedSkillPaths(cwd));
449
- const skills = [];
450
- for (const absPath of walkSkillFiles(skillsRoot, 0)) {
451
- const sourcePath = relative(cwd, absPath).replace(/\\/g, "/");
452
- if (managed.has(sourcePath))
453
- continue;
454
- const key = basename(dirname2(absPath));
455
- if (key === "moda-cli" || !/^[A-Za-z0-9_-]+$/.test(key))
456
- continue;
457
- let content;
458
- try {
459
- content = readFileSync2(absPath, "utf8");
460
- } catch {
461
- continue;
462
- }
463
- if (!content.trim())
464
- continue;
465
- const { name, description } = parseSkillFrontmatter(content, key);
466
- skills.push({
467
- key,
468
- name,
469
- description,
470
- skillMd: content,
471
- sourcePath,
472
- contentHash: sha256(content)
473
- });
474
- }
475
- return skills.sort((a, b) => a.key.localeCompare(b.key));
476
- }
477
- function managedSkillPaths(cwd) {
478
- const manifest = readSkillsManifest(cwd);
479
- if (!manifest)
480
- return [];
481
- return manifest.managedPaths.filter((path) => path.startsWith(".claude/skills/") && path.endsWith("/SKILL.md"));
482
- }
483
- function walkSkillFiles(dir, depth) {
484
- if (depth > MAX_SKILL_WALK_DEPTH)
485
- return [];
486
- let entries;
487
- try {
488
- entries = readdirSync(dir);
489
- } catch {
490
- return [];
491
- }
492
- const files = [];
493
- for (const entry of entries) {
494
- const fullPath = join2(dir, entry);
495
- let stat;
496
- try {
497
- stat = lstatSync2(fullPath);
498
- } catch {
499
- continue;
500
- }
501
- if (stat.isSymbolicLink())
502
- continue;
503
- if (stat.isDirectory()) {
504
- files.push(...walkSkillFiles(fullPath, depth + 1));
505
- } else if (stat.isFile() && entry === SKILL_FILE) {
506
- files.push(fullPath);
507
- }
508
- }
509
- return files;
510
- }
511
- function parseSkillFrontmatter(raw, fallbackKey) {
512
- let name = fallbackKey;
513
- let description;
514
- if (raw.startsWith("---")) {
515
- const end = raw.indexOf(`
516
- ---`, 3);
517
- if (end !== -1) {
518
- const frontmatter = raw.slice(3, end);
519
- const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
520
- const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
521
- if (nameMatch)
522
- name = stripQuotes(nameMatch[1].trim()) || fallbackKey;
523
- if (descMatch)
524
- description = stripQuotes(descMatch[1].trim()) || undefined;
525
- }
526
- }
527
- return { name, description };
528
- }
529
- function stripQuotes(value) {
530
- return value.replace(/^["']|["']$/g, "");
531
- }
532
- function getGitCommit(cwd) {
533
- try {
534
- return execFileSync("git", ["rev-parse", "HEAD"], {
535
- cwd,
536
- encoding: "utf8",
537
- stdio: ["ignore", "pipe", "ignore"]
538
- }).trim();
539
- } catch {
540
- return;
541
- }
542
- }
543
- async function fetchSkillProposals(flags, profileOptions = {}) {
544
- const params = new URLSearchParams;
545
- if (flags["generation-run-id"])
546
- params.set("generationRunId", flags["generation-run-id"]);
547
- const status = flags.status ?? "ready_for_pr";
548
- if (status !== "all")
549
- params.set("status", status);
550
- if (flags["tenant-id"])
551
- params.set("tenantId", flags["tenant-id"]);
552
- const suffix = params.toString() ? `?${params.toString()}` : "";
553
- const response = await callControlAPI(`/skills/proposals${suffix}`, { method: "GET" }, profileOptions);
554
- return Array.isArray(response.proposals) ? response.proposals : [];
555
- }
556
- async function applySkillProposal(proposalId, flags, profileOptions = {}, options = {}) {
557
- const cwd = options.cwd ?? process.cwd();
558
- const params = new URLSearchParams;
559
- if (flags["tenant-id"])
560
- params.set("tenantId", flags["tenant-id"]);
561
- const suffix = params.toString() ? `?${params.toString()}` : "";
562
- const proposal = await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}${suffix}`, { method: "GET" }, profileOptions);
563
- if (!proposal.skillMd?.trim()) {
564
- throw new CliInputError(`Proposal ${proposalId} does not include SKILL.md content`);
565
- }
566
- const safeId = safeSkillId(proposal.skillId);
567
- const relativePath = `.claude/skills/${safeId}/SKILL.md`;
568
- const writeStatus = writeIfChanged(join2(cwd, relativePath), proposal.skillMd);
569
- if (writeStatus === "failed") {
570
- throw new Error(`Failed to write ${relativePath}`);
571
- }
572
- writeSkillProposalManifest(cwd, proposal, relativePath);
573
- let acknowledged = true;
574
- let ackError;
575
- try {
576
- await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}/mark-applied${suffix}`, {
577
- method: "POST",
578
- body: JSON.stringify({ appliedBy: "moda-cli" })
579
- }, profileOptions);
580
- } catch (err) {
581
- acknowledged = false;
582
- ackError = err instanceof Error ? err.message : String(err);
583
- }
584
- return {
585
- proposalId,
586
- skillId: proposal.skillId,
587
- path: relativePath,
588
- status: writeStatus,
589
- acknowledged,
590
- ackError
591
- };
592
- }
593
- function safeSkillId(skillId) {
594
- const safeId = basename(skillId || "");
595
- if (!safeId || !/^[A-Za-z0-9_-]+$/.test(safeId)) {
596
- throw new CliInputError(`Invalid skill_id in proposal: ${JSON.stringify(skillId).slice(0, 80)}`);
597
- }
598
- return safeId;
599
- }
600
- function writeSkillProposalManifest(cwd, proposal, relativePath) {
601
- const manifestPath = join2(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
602
- mkdirSync2(dirname2(manifestPath), { recursive: true });
603
- const existing = existsSync2(manifestPath) ? readFileSync2(manifestPath, "utf8") : `applied_proposals:
604
- `;
605
- const entry = [
606
- ` - proposal_id: "${escapeYamlString(proposal.proposalId)}"`,
607
- ` skill_id: "${escapeYamlString(proposal.skillId)}"`,
608
- ` skill_family_id: "${escapeYamlString(proposal.skillFamilyId)}"`,
609
- ` skill_version: ${Number(proposal.skillVersion || 0)}`,
610
- ` generation_run_id: "${escapeYamlString(proposal.generationRunId)}"`,
611
- ` clustering_run_id: "${escapeYamlString(proposal.clusteringRunId)}"`,
612
- ` topic_id: "${escapeYamlString(proposal.topicId)}"`,
613
- ` replay_set_id: "${escapeYamlString(proposal.replaySetId)}"`,
614
- ` delta_pass_rate: ${Number(proposal.deltaPassRate || 0)}`,
615
- ` path: "${escapeYamlString(relativePath)}"`,
616
- ` applied_at: "${new Date().toISOString()}"`
617
- ].join(`
618
- `);
619
- const prefix = existing.endsWith(`
620
- `) ? existing : `${existing}
621
- `;
622
- writeFileSync2(manifestPath, `${prefix}${entry}
623
- `);
624
- }
625
- function escapeYamlString(value) {
626
- return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
627
- }
628
- function formatProposalRate(value) {
629
- return `${(Number(value || 0) * 100).toFixed(1)}%`;
630
- }
631
- var SKILLS_SUBCOMMAND_FLAGS = {
632
- gen: [
633
- "source",
634
- "wait",
635
- "clustering-poll-seconds",
636
- "clustering-run-id",
637
- "clustering-timeout-seconds",
638
- "end-at",
639
- "force-reprocess",
640
- "harness-url",
641
- "lookback-hours",
642
- "max-cluster-pct",
643
- "max-sessions",
644
- "max-traces",
645
- "no-clustering",
646
- "reprocess-segments",
647
- "start-at",
648
- "tenant-id",
649
- "wait-for-completion"
650
- ],
651
- status: ["run-id"],
652
- pull: ["status"],
653
- proposals: ["status", "generation-run-id", "tenant-id"],
654
- proposal: ["proposal-id", "tenant-id"],
655
- install: ["list"]
656
- };
657
- async function runSkillsCommand(input) {
658
- const context = "output" in input ? input : createCommandContext({
659
- command: input.command,
660
- positional: input.positional,
661
- positionals: input.positionals,
662
- flags: input.flags
663
- }, {
664
- outputMode: input.flags.agent === "true" ? "agent" : input.flags.json === "true" ? "json" : "human"
665
- });
666
- const sub = context.positional;
667
- const positionals = context.positionals ?? (sub ? [sub] : []);
668
- const flags = context.flags;
669
- if (sub && SKILLS_SUBCOMMAND_FLAGS[sub])
670
- assertFlags(`skills ${sub}`, flags, SKILLS_SUBCOMMAND_FLAGS[sub]);
671
- if (sub === "gen") {
672
- const result = await generateSkills(flags);
673
- if (context.outputMode !== "human") {
674
- context.output.writeData(result, {
675
- status: Number(result.sessions_failed ?? 0) > 0 ? "error" : "success"
676
- });
677
- } else if (result.accepted) {
678
- console.log([
679
- "accepted: true",
680
- result.generation_run_id && `generation_run_id: ${result.generation_run_id}`,
681
- result.message && `message: ${result.message}`,
682
- result.generation_run_id && `follow progress: moda skills status ${shellArg(result.generation_run_id)}`
683
- ].filter(Boolean).join(`
684
- `));
685
- } else {
686
- console.log([
687
- `processed: ${result.sessions_processed ?? 0}`,
688
- `failed: ${result.sessions_failed ?? 0}`,
689
- `pending: ${result.sessions_pending ?? 0}`,
690
- `pending_found: ${result.pending_sessions_found ?? 0}`,
691
- `distilled: ${Number(result.cluster_result?.distilled_skills_generated ?? 0)}`,
692
- `indexed: ${Number(result.cluster_result?.skills_indexed ?? 0)}`,
693
- result.distillation_skipped && `distillation skipped: ${result.distillation_skip_reason || "unknown"}`
694
- ].filter(Boolean).join(`
695
- `));
696
- }
697
- return Number(result.sessions_failed ?? 0) > 0 ? 1 : 0;
698
- }
699
- if (sub === "status") {
700
- const runId = positionals[1] || flags["run-id"] || undefined;
701
- const { run, events } = await fetchSkillRunStatus(runId);
702
- if (context.outputMode !== "human") {
703
- context.output.writeData({ run, events }, { status: run?.status === "failed" ? "error" : "success" });
704
- } else if (!run) {
705
- console.log("No skill generation runs found yet. Start one with `moda skills gen`.");
706
- } else {
707
- console.log(formatRunStatusHuman(run, events));
708
- }
709
- return run?.status === "failed" ? 1 : 0;
710
- }
711
- if (sub === "proposals" && positionals[1] === "list") {
712
- const proposals = await fetchSkillProposals(flags, { profile: context.profile });
713
- if (flags.json === "true") {
714
- console.log(JSON.stringify({ proposals }));
715
- } else if (proposals.length === 0) {
716
- console.log("No skill proposals found.");
717
- } else {
718
- for (const proposal of proposals) {
719
- console.log([
720
- proposal.proposalId,
721
- proposal.proposalStatus,
722
- `${proposal.skillId}@v${proposal.skillVersion}`,
723
- `delta=${formatProposalRate(proposal.deltaPassRate)}`,
724
- `baseline=${formatProposalRate(proposal.baselinePassRate)}`,
725
- `candidate=${formatProposalRate(proposal.candidatePassRate)}`,
726
- proposal.name
727
- ].filter(Boolean).join("\t"));
728
- }
729
- }
730
- return 0;
731
- }
732
- if (sub === "proposal" && positionals[1] === "apply") {
733
- const proposalId = positionals[2] || flags["proposal-id"];
734
- if (!proposalId) {
735
- throw new CliInputError("A proposal id is required", "Usage: moda skills proposal apply <proposal-id>");
736
- }
737
- const result = await applySkillProposal(proposalId, flags, { profile: context.profile });
738
- if (flags.json === "true") {
739
- console.log(JSON.stringify(result));
740
- } else {
741
- console.log([
742
- `${result.status} ${result.path}`,
743
- result.acknowledged ? "acknowledged: true" : "acknowledged: false"
744
- ].join(`
745
- `));
746
- }
747
- if (!result.acknowledged) {
748
- console.error(`Warning: wrote ${result.path} but failed to acknowledge proposal ${proposalId} on the server` + (result.ackError ? ` (${result.ackError})` : "") + ". The proposal is still listed as ready — re-run `moda skills proposal apply` to acknowledge it.");
749
- return 1;
750
- }
751
- return 0;
752
- }
753
- if (sub === "list" || sub === "show" || sub === "policy") {
754
- return runSkillRegistryCommand(sub, positionals, flags, context);
755
- }
756
- if (sub === "sync") {
757
- assertFlags("skills sync", flags, ["dry-run"]);
758
- const result = await runSkillSync(flags, { profile: context.profile, env: context.env });
759
- const agentsMdWarning = agentsMdProblem(result.agentsMd);
760
- context.output.writeData({
761
- synced: result.synced,
762
- dryRun: result.dryRun,
763
- ...result.agentsMd ? { agentsMd: result.agentsMd } : {}
764
- }, agentsMdWarning ? { status: "degraded", warnings: [agentsMdWarning] } : {});
765
- return 0;
766
- }
767
- if (sub === "install") {
768
- return runSkillsInstall(context, positionals.slice(1));
769
- }
770
- if (sub !== "pull") {
771
- throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : "A skills subcommand is required", "Usage: moda skills gen [--source=all|sdk] [--max-sessions=N (default 200)] [--lookback-hours=N (default 720)] [--max-traces=N] [--start-at=ISO] [--end-at=ISO] [--reprocess-segments] [--force-reprocess] [--wait] | moda skills status [run-id] | moda skills proposals list [--status=ready_for_pr] | moda skills proposal apply <proposal-id> | moda skills pull [--status=approved|proposed|all] | moda skills push [--dry-run] | moda skills install [--list] <id...>");
772
- }
773
- const status = (flags.status ?? "approved").toLowerCase();
774
- if (!VALID_STATUSES.has(status)) {
775
- throw new CliInputError(`Invalid --status '${status}'`, `Must be one of: ${[...VALID_STATUSES].join(", ")}`);
776
- }
777
- const summary = await pullSkills({ status, silent: context.outputMode !== "human" });
778
- const parts = [
779
- summary.pruned && `${summary.pruned} pruned`,
780
- summary.wrote && `${summary.wrote} written`,
781
- summary.updated && `${summary.updated} updated`,
782
- summary.unchanged && `${summary.unchanged} unchanged`,
783
- summary.failed && `${summary.failed} failed`
784
- ].filter(Boolean);
785
- if (context.outputMode === "human" && summary.total > 0) {
786
- console.log(` Done — ${parts.join(", ") || "no changes"}`);
787
- }
788
- if (context.outputMode !== "human") {
789
- context.output.writeData({ status, summary });
790
- }
791
- return summary.failed > 0 ? 1 : 0;
792
- }
793
- async function generateSkills(flags) {
794
- const apiKey = resolveApiKey();
795
- if (!apiKey) {
796
- throw new CliInputError("No Moda API key found. Run `moda init` first (or set MODA_API_KEY).");
797
- }
798
- const tenantId = flags["tenant-id"] || resolveTenantId();
799
- const directHarnessUrl = flags["harness-url"] || process.env.MODA_SKILL_HARNESS_URL;
800
- if (directHarnessUrl && !tenantId) {
801
- throw new CliInputError("No Moda tenant id found. Run `moda init` first (or set MODA_TENANT_ID / --tenant-id).");
802
- }
803
- const endpoint = directHarnessUrl ? `${directHarnessUrl.replace(/\/+$/, "")}/skills/generate` : `${resolveIngestUrl().replace(/\/+$/, "")}/v1/skills/generate`;
804
- const waitForCompletion = flags.wait === "true" || flags["wait-for-completion"] === "true";
805
- const forceReprocess = flags["force-reprocess"] === "true";
806
- const source = normalizeSkillSource(flags.source);
807
- const body = compactObject({
808
- tenant_id: tenantId || undefined,
809
- max_sessions: parseScopeInt(flags["max-sessions"], "max-sessions"),
810
- lookback_hours: parseScopeInt(flags["lookback-hours"], "lookback-hours"),
811
- start_at: flags["start-at"] || undefined,
812
- end_at: flags["end-at"] || undefined,
813
- clustering_run_id: flags["clustering-run-id"] || undefined,
814
- run_clustering: flags["no-clustering"] !== "true",
815
- source,
816
- clustering_timeout_seconds: parsePositiveInt(flags["clustering-timeout-seconds"], 7200),
817
- clustering_poll_seconds: parsePositiveInt(flags["clustering-poll-seconds"], 15),
818
- max_cluster_pct: parseOptionalFloat(flags["max-cluster-pct"]),
819
- reprocess_segments: flags["reprocess-segments"] === "true" || forceReprocess,
820
- max_traces: parseScopeInt(flags["max-traces"], "max-traces"),
821
- force_reprocess: forceReprocess,
822
- wait_for_completion: waitForCompletion
823
- });
824
- const headers = {
825
- Authorization: `Bearer ${apiKey}`,
826
- "Content-Type": "application/json"
827
- };
828
- if (tenantId)
829
- headers["X-Moda-Tenant-Id"] = tenantId;
830
- recordApiRequest();
831
- const response = await fetch(endpoint, {
832
- method: "POST",
833
- headers,
834
- body: JSON.stringify(body)
835
- });
836
- if (!response.ok) {
837
- const text = await response.text().catch(() => "");
838
- throw new Error(`Failed to generate skills (HTTP ${response.status}): ${text.slice(0, 200)}`);
839
- }
840
- return await response.json();
841
- }
842
- function parseScopeInt(value, flag, max) {
843
- if (value === undefined)
844
- return;
845
- const parsed = /^\d+$/.test(value.trim()) ? Number.parseInt(value, 10) : Number.NaN;
846
- if (!Number.isFinite(parsed) || parsed < 1) {
847
- throw new CliInputError(`--${flag}=${value} is not a positive integer, so nothing was sent.`, `0 would mean unlimited server-side; omit --${flag} for the server default, or pass a number of 1 or more.`);
848
- }
849
- if (max !== undefined && parsed > max) {
850
- throw new CliInputError(`--${flag}=${value} is above the maximum of ${max}, so nothing was sent.`);
851
- }
852
- return parsed;
853
- }
854
- function parsePositiveInt(value, fallback) {
855
- if (!value)
856
- return fallback;
857
- const parsed = Number.parseInt(value, 10);
858
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
859
- }
860
- function parseOptionalFloat(value) {
861
- if (!value)
862
- return;
863
- const parsed = Number.parseFloat(value);
864
- return Number.isFinite(parsed) ? parsed : undefined;
865
- }
866
- function compactObject(input) {
867
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
868
- }
869
- function normalizeSkillSource(value) {
870
- const normalized = (value || "all").toLowerCase();
871
- if (["all", "sdk"].includes(normalized))
872
- return normalized;
873
- throw new CliInputError(`Invalid --source '${value}'. Use --source=all or --source=sdk.`);
874
- }
875
- function releaseKeyFor(release, skills, status) {
876
- if (release?.generation_run_id)
877
- return `${status}:run:${release.generation_run_id}`;
878
- if (Number(release?.generation_version || 0) > 0) {
879
- return `${status}:version:${Number(release?.generation_version)}`;
880
- }
881
- return `${status}:legacy:${sha256(JSON.stringify(skills.map((skill) => ({
882
- skill_id: skill.skill_id,
883
- hash: sha256(skill.skill_md || "")
884
- })).sort((a, b) => a.skill_id.localeCompare(b.skill_id))))}`;
885
- }
886
- function manifestHeaderFor(fetched) {
887
- const release = fetched.release;
888
- return {
889
- tenantId: fetched.tenantId,
890
- releaseKey: fetched.releaseKey,
891
- statusFilter: fetched.status,
892
- generationRunId: release?.generation_run_id || "",
893
- generationVersion: Number(release?.generation_version || 0),
894
- releasedAt: release?.released_at || ""
895
- };
896
- }
897
- function sha256(input) {
898
- return createHash("sha256").update(input).digest("hex");
899
- }
900
- var MODA_CLI_SKILL_ID = "moda-cli";
901
- async function installModaCliSkill(context) {
902
- const { detectAgents: detectAgents2 } = await import("./mcp-1qre9f0n.js");
903
- const { installRules, summarizeInstall } = await import("./rules-qddx5t0j.js");
904
- const result = await installRules(detectAgents2(), { yes: true, silent: true });
905
- if (result.status === "skipped") {
906
- throw new CliInputError("No supported coding agent was detected here, so the moda-cli skill was not installed.", "Run this from a project with a .claude or .cursor directory, or with Claude Code on PATH. Only Claude Code and Cursor are supported.");
907
- }
908
- const agentsMd = result.status === "failed" ? undefined : upsertFixesAgentsBlock(context.cwd);
909
- const blockWarning = agentsMdProblem(agentsMd);
910
- if (blockWarning) {
911
- result.warnings.push(blockWarning);
912
- result.status = "warning";
913
- }
914
- const summary = summarizeInstall(result);
915
- if (context.outputMode !== "human") {
916
- context.output.writeData({ skill: MODA_CLI_SKILL_ID, status: result.status, agents: result.agents, counts: result.counts, summary, outcomes: result.outcomes, notes: result.notes, agentsMd }, { ...result.status === "failed" ? { status: "error" } : {}, warnings: result.warnings });
917
- } else {
918
- console.log(`moda-cli skill for ${result.agents.join(", ")}: ${summary}`);
919
- for (const note of result.notes)
920
- console.log(` ${note}`);
921
- for (const warning of result.warnings)
922
- console.error(` ${warning}`);
923
- }
924
- return result.status === "failed" ? 1 : 0;
925
- }
926
- async function runSkillsInstall(context, ids) {
927
- const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-7jes6hqx.js");
928
- const index = readBundledIndex();
929
- if (context.flags.list === "true" || ids.length === 0) {
930
- const { bundledSkillVersion } = await import("./rules-qddx5t0j.js");
931
- if (!index) {
932
- throw new CliInputError("This CLI build has no bundled integration guides", "Install a published @moda-ai/cli, or run `bun run bundle:skills` in apps/moda-cli.");
933
- }
934
- const skills = [
935
- { id: MODA_CLI_SKILL_ID, version: bundledSkillVersion(MODA_CLI_SKILL_ID), language: "any", summary: "This CLI's own guide for coding agents; installs or refreshes it for each detected agent (copies edited locally are kept)." },
936
- ...index.skills.map((skill) => ({
937
- id: skill.id,
938
- version: skill.version,
939
- language: skill.targets.language,
940
- summary: skill.summary
941
- }))
942
- ];
943
- if (context.outputMode !== "human") {
944
- context.output.writeData({ skills });
945
- } else {
946
- for (const skill of skills)
947
- console.log(`${skill.id} ${skill.version} ${skill.summary}`);
948
- if (ids.length === 0 && context.flags.list !== "true") {
949
- console.error("Install one or more with: moda skills install <id...>");
950
- }
951
- }
952
- return 0;
953
- }
954
- if (ids.includes(MODA_CLI_SKILL_ID)) {
955
- const others = ids.filter((id) => id !== MODA_CLI_SKILL_ID);
956
- if (others.length > 0) {
957
- throw new CliInputError(`Install ${MODA_CLI_SKILL_ID} on its own, so nothing was written.`, `Run \`moda skills install ${MODA_CLI_SKILL_ID}\`, then \`moda skills install ${others.join(" ")}\`.`);
958
- }
959
- return installModaCliSkill(context);
960
- }
961
- const unknown = ids.filter((id) => !/^[a-z0-9-]+$/.test(id) || readBundledSkillMd(id) === null);
962
- if (unknown.length > 0) {
963
- throw new CliInputError(`Not bundled with this CLI: ${unknown.map((id) => JSON.stringify(id).slice(0, 80)).join(", ")}`, "List the bundled guides with: moda skills install --list");
964
- }
965
- const results = [];
966
- for (const id of ids) {
967
- try {
968
- results.push(installIntegrationSkill(id));
969
- } catch (error) {
970
- const done = results.map((result) => result.skillId);
971
- throw new CliInputError(error instanceof Error ? error.message : String(error), done.length > 0 ? `Already installed before the failure: ${done.join(", ")}. Fix the error and rerun; installed guides are left as they are.` : "List the bundled guides with: moda skills install --list");
972
- }
973
- }
974
- if (context.outputMode !== "human") {
975
- context.output.writeData({ installed: results });
976
- } else {
977
- for (const result of results) {
978
- console.log(`${result.status} ${result.skillId}`);
979
- for (const path of result.paths)
980
- console.log(` ${path}`);
981
- }
982
- }
983
- return 0;
984
- }
985
- var RESERVED_SKILL_PATHS = new Set(["inbox", "library", "observed", "runs", "sync", "surface", "families", "proposals"]);
986
- async function runSkillRegistryCommand(sub, positionals, flags, context) {
987
- const profileOptions = { profile: context.profile, env: context.env };
988
- assertFlags(`skills ${sub}`, flags, sub === "policy" ? ["live", "local", "dry-run"] : []);
989
- assertPositionals(`skills ${sub}`, positionals, sub === "list" ? 1 : 2, sub === "list" ? "moda skills list" : `moda skills ${sub} <key>${sub === "policy" ? " --live|--local" : ""}`);
990
- if (sub === "list") {
991
- context.output.writeData(await callControlAPI("/skills", {}, profileOptions));
992
- return 0;
993
- }
994
- const idOrKey = positionals[1];
995
- if (!idOrKey) {
996
- throw new CliInputError(`A skill key or id is required for skills ${sub}.`, sub === "show" ? "Usage: moda skills show <key>" : "Usage: moda skills policy <key> --live|--local");
997
- }
998
- if (RESERVED_SKILL_PATHS.has(idOrKey)) {
999
- throw new CliInputError(`'${idOrKey}' is a reserved path, so this skill must be addressed by its id.`, "Find the id (skill_…) with `moda skills list`, then pass that instead of the key.");
1000
- }
1001
- if (sub === "show") {
1002
- context.output.writeData(await callControlAPI(`/skills/${encodeURIComponent(idOrKey)}`, {}, profileOptions));
1003
- return 0;
1004
- }
1005
- const live = flags.live === "true";
1006
- const local = flags.local === "true";
1007
- if (live === local) {
1008
- throw new CliInputError("Pass exactly one of --live or --local.", "Usage: moda skills policy <key> --live|--local");
1009
- }
1010
- const surfacePolicy = live ? "include" : "local_only";
1011
- if (context.dryRun) {
1012
- writeDryRun(context, { plane: "control", method: "PATCH", path: `/skills/${encodeURIComponent(idOrKey)}/surface-policy`, body: { surfacePolicy } });
1013
- return 0;
1014
- }
1015
- context.output.writeData(await callControlAPI(`/skills/${encodeURIComponent(idOrKey)}/surface-policy`, { method: "PATCH", body: JSON.stringify({ surfacePolicy }) }, profileOptions, { retries: 0 }));
1016
- return 0;
1017
- }
1018
-
1019
- export { agentsMdProblem, pullSkills, runSkillSync, runSkillsCommand, parseScopeInt };