@moda-ai/cli 1.55.20 → 1.55.22

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.
@@ -23,7 +23,7 @@ import {
23
23
  pruneManagedSkillPaths,
24
24
  readSkillsManifest,
25
25
  writeSkillsManifest
26
- } from "./cli-sf0g0fjg.js";
26
+ } from "./cli-8dtnwp9r.js";
27
27
  import {
28
28
  __require
29
29
  } from "./cli-0v6na3yp.js";
@@ -80,27 +80,39 @@ function renderFixesBlock() {
80
80
  ].join(`
81
81
  `);
82
82
  }
83
+ function countOccurrences(text, needle) {
84
+ return text.split(needle).length - 1;
85
+ }
83
86
  function upsertManagedBlock(existing, block) {
84
87
  if (existing === null) {
85
- return `${block}
86
- `;
87
- }
88
- const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
89
- const endIdx = existing.indexOf(FIXES_BLOCK_END);
90
- if (beginIdx !== -1 && endIdx !== -1 && endIdx >= beginIdx) {
91
- const before = existing.slice(0, beginIdx);
92
- const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
93
- return `${before}${block}${after}`;
88
+ return { ok: true, content: `${block}
89
+ ` };
94
90
  }
95
- const separator = existing === "" || existing.endsWith(`
91
+ const begins = countOccurrences(existing, FIXES_BLOCK_BEGIN);
92
+ const ends = countOccurrences(existing, FIXES_BLOCK_END);
93
+ if (begins === 0 && ends === 0) {
94
+ const separator = existing === "" || existing.endsWith(`
96
95
 
97
96
  `) ? "" : existing.endsWith(`
98
97
  `) ? `
99
98
  ` : `
100
99
 
101
100
  `;
102
- return `${existing}${separator}${block}
103
- `;
101
+ return { ok: true, content: `${existing}${separator}${block}
102
+ ` };
103
+ }
104
+ const beginIdx = existing.indexOf(FIXES_BLOCK_BEGIN);
105
+ const endIdx = existing.indexOf(FIXES_BLOCK_END);
106
+ if (begins === 1 && ends === 1 && endIdx > beginIdx) {
107
+ const before = existing.slice(0, beginIdx);
108
+ const after = existing.slice(endIdx + FIXES_BLOCK_END.length);
109
+ return { ok: true, content: `${before}${block}${after}` };
110
+ }
111
+ 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`;
112
+ return {
113
+ ok: false,
114
+ 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 sync`."
115
+ };
104
116
  }
105
117
  function writeFileIfChanged(filePath, content) {
106
118
  try {
@@ -108,14 +120,14 @@ function writeFileIfChanged(filePath, content) {
108
120
  if (existsSync(filePath)) {
109
121
  const current = readFileSync(filePath, "utf8");
110
122
  if (current === content)
111
- return "unchanged";
123
+ return { status: "unchanged" };
112
124
  writeFileSync(filePath, content);
113
- return "updated";
125
+ return { status: "updated" };
114
126
  }
115
127
  writeFileSync(filePath, content);
116
- return "wrote";
117
- } catch {
118
- return "failed";
128
+ return { status: "wrote" };
129
+ } catch (err) {
130
+ return { status: "failed", error: err instanceof Error ? err.message : String(err) };
119
131
  }
120
132
  }
121
133
  function upsertFixesAgentsBlock(cwd) {
@@ -124,12 +136,28 @@ function upsertFixesAgentsBlock(cwd) {
124
136
  if (existsSync(filePath)) {
125
137
  try {
126
138
  existing = readFileSync(filePath, "utf8");
127
- } catch {
128
- return { path: AGENTS_MD_FILENAME, status: "failed" };
139
+ } catch (err) {
140
+ return {
141
+ path: AGENTS_MD_FILENAME,
142
+ status: "failed",
143
+ reason: `Could not read ${AGENTS_MD_FILENAME}: ${err instanceof Error ? err.message : String(err)}`
144
+ };
129
145
  }
130
146
  }
131
147
  const next = upsertManagedBlock(existing, renderFixesBlock());
132
- return { path: AGENTS_MD_FILENAME, status: writeFileIfChanged(filePath, next) };
148
+ if (!next.ok)
149
+ return { path: AGENTS_MD_FILENAME, status: "skipped", reason: next.reason };
150
+ const written = writeFileIfChanged(filePath, next.content);
151
+ return {
152
+ path: AGENTS_MD_FILENAME,
153
+ status: written.status,
154
+ ...written.error ? { reason: `Could not write ${AGENTS_MD_FILENAME}: ${written.error}` } : {}
155
+ };
156
+ }
157
+ function agentsMdProblem(agentsMd) {
158
+ if (!agentsMd || agentsMd.status !== "skipped" && agentsMd.status !== "failed")
159
+ return;
160
+ return agentsMd.reason ?? `The Moda block in ${agentsMd.path} could not be updated.`;
133
161
  }
134
162
 
135
163
  // src/skills.ts
@@ -717,11 +745,12 @@ async function runSkillsCommand(input) {
717
745
  if (sub === "sync") {
718
746
  assertFlags("skills sync", flags, ["dry-run"]);
719
747
  const result = await runSkillSync(flags, { profile: context.profile, env: context.env });
748
+ const agentsMdWarning = agentsMdProblem(result.agentsMd);
720
749
  context.output.writeData({
721
750
  synced: result.synced,
722
751
  dryRun: result.dryRun,
723
752
  ...result.agentsMd ? { agentsMd: result.agentsMd } : {}
724
- });
753
+ }, agentsMdWarning ? { status: "degraded", warnings: [agentsMdWarning] } : {});
725
754
  return 0;
726
755
  }
727
756
  if (sub === "install") {
@@ -866,7 +895,7 @@ function sha256(input) {
866
895
  return createHash("sha256").update(input).digest("hex");
867
896
  }
868
897
  async function runSkillsInstall(context, ids) {
869
- const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-r9a2vs07.js");
898
+ const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-dq20spzk.js");
870
899
  const index = readBundledIndex();
871
900
  if (context.flags.list === "true" || ids.length === 0) {
872
901
  if (!index) {
@@ -947,4 +976,4 @@ async function runSkillRegistryCommand(sub, positionals, flags, context) {
947
976
  return 0;
948
977
  }
949
978
 
950
- export { pullSkills, runSkillSync, runSkillsCommand, parseScopeInt };
979
+ export { agentsMdProblem, pullSkills, runSkillSync, runSkillsCommand, parseScopeInt };
@@ -7,8 +7,8 @@ function packageAssetPath(...parts) {
7
7
 
8
8
  // src/init/rules.ts
9
9
  import { createHash } from "node:crypto";
10
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
11
- import { dirname as dirname2, join as join2 } from "node:path";
10
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
11
+ import { dirname as dirname2, join as join2, sep } from "node:path";
12
12
  import { confirm } from "@inquirer/prompts";
13
13
  var SKILLS_SOURCE_DIR = packageAssetPath("skills");
14
14
  var SKILLS = [
@@ -93,18 +93,68 @@ function skillVersion(skillContent) {
93
93
  const frontmatter = FRONTMATTER_RE.exec(skillContent)?.[1] ?? "";
94
94
  return /^version:\s*(\S+)\s*$/m.exec(frontmatter)?.[1] ?? "unknown";
95
95
  }
96
+ function normalizeEol(text) {
97
+ return text.replace(/\r\n/g, `
98
+ `);
99
+ }
100
+ function matchesRecord(existing, recorded) {
101
+ const hash = recorded?.sha256;
102
+ return typeof hash === "string" && (hash === sha256(existing) || hash === sha256(normalizeEol(existing)));
103
+ }
96
104
  function looksLikeShippedSkill(existing, skill) {
97
- return existing.startsWith(`---
105
+ const text = normalizeEol(existing);
106
+ return text.startsWith(`---
98
107
  name: ${skill.id}
99
- `) || /^# Moda CLI$/m.test(existing);
108
+ `) || /^# Moda CLI$/m.test(text);
109
+ }
110
+ function lstatOrUndefined(path) {
111
+ try {
112
+ return lstatSync(path);
113
+ } catch {
114
+ return;
115
+ }
116
+ }
117
+ function firstSymlinkedComponent(relativePath) {
118
+ let current = "";
119
+ for (const part of relativePath.split(/[\\/]+/).filter(Boolean)) {
120
+ current = current ? join2(current, part) : part;
121
+ const stat = lstatOrUndefined(join2(process.cwd(), current));
122
+ if (!stat)
123
+ return;
124
+ if (stat.isSymbolicLink())
125
+ return current;
126
+ }
127
+ return;
100
128
  }
101
129
  function unusedBackupPath(relativePath) {
102
130
  let candidate = `${relativePath}.bak`;
103
- for (let n = 1;existsSync(join2(process.cwd(), candidate)); n++) {
131
+ for (let n = 1;lstatOrUndefined(join2(process.cwd(), candidate)); n++) {
104
132
  candidate = `${relativePath}.bak.${n}`;
105
133
  }
106
134
  return candidate;
107
135
  }
136
+ function removeStaleReferences(referencesDir, source, record) {
137
+ const removed = [];
138
+ const kept = [];
139
+ const prefix = `${referencesDir}${sep}`;
140
+ for (const key of Object.keys(record)) {
141
+ if (!key.startsWith(prefix))
142
+ continue;
143
+ const name = key.slice(prefix.length);
144
+ if (source.references.has(name) || !/^[^\\/]+\.md$/.test(name))
145
+ continue;
146
+ const path = join2(process.cwd(), key);
147
+ const stat = lstatOrUndefined(path);
148
+ if (stat?.isFile() && matchesRecord(readFileSync(path, "utf8"), record[key])) {
149
+ unlinkSync(path);
150
+ removed.push(key);
151
+ } else if (stat) {
152
+ kept.push(key);
153
+ }
154
+ delete record[key];
155
+ }
156
+ return { removed, kept };
157
+ }
108
158
  function installSkillForAgent(skill, adapter, source, record) {
109
159
  const { dir, filename } = adapter.targetFor(skill.id);
110
160
  const dirPath = join2(process.cwd(), dir);
@@ -112,6 +162,10 @@ function installSkillForAgent(skill, adapter, source, record) {
112
162
  const relativePath = join2(dir, filename);
113
163
  const referencesDir = adapter.referencesDirFor(skill.id);
114
164
  const content = adapter.transform(source.content, referencesDir);
165
+ const symlinkPath = firstSymlinkedComponent(relativePath) ?? firstSymlinkedComponent(referencesDir);
166
+ if (symlinkPath) {
167
+ return { agent: adapter.name, relativePath, status: "symlink", symlinkPath };
168
+ }
115
169
  try {
116
170
  mkdirSync(dirPath, { recursive: true });
117
171
  let status = "wrote";
@@ -119,9 +173,9 @@ function installSkillForAgent(skill, adapter, source, record) {
119
173
  if (existsSync(filePath)) {
120
174
  const existing = readFileSync(filePath, "utf8");
121
175
  const recorded = record[relativePath];
122
- if (existing === content) {
176
+ if (normalizeEol(existing) === content) {
123
177
  status = "current";
124
- } else if (recorded ? recorded.sha256 === sha256(existing) : looksLikeShippedSkill(existing, skill)) {
178
+ } else if (recorded ? matchesRecord(existing, recorded) : looksLikeShippedSkill(existing, skill)) {
125
179
  if (!recorded) {
126
180
  backupPath = unusedBackupPath(relativePath);
127
181
  writeFileSync(join2(process.cwd(), backupPath), existing);
@@ -138,12 +192,18 @@ function installSkillForAgent(skill, adapter, source, record) {
138
192
  const referencesPath = join2(process.cwd(), referencesDir);
139
193
  mkdirSync(referencesPath, { recursive: true });
140
194
  const keptReferences = [];
195
+ const symlinkedReferences = [];
141
196
  for (const [name, text] of source.references) {
142
197
  const referencePath = join2(referencesPath, name);
143
198
  const referenceKey = join2(referencesDir, name);
144
- const existing = existsSync(referencePath) ? readFileSync(referencePath, "utf8") : undefined;
145
- if (existing !== text) {
146
- if (existing !== undefined && record[referenceKey]?.sha256 !== sha256(existing)) {
199
+ const stat = lstatOrUndefined(referencePath);
200
+ if (stat?.isSymbolicLink()) {
201
+ symlinkedReferences.push(referenceKey);
202
+ continue;
203
+ }
204
+ const existing = stat ? readFileSync(referencePath, "utf8") : undefined;
205
+ if (existing === undefined || normalizeEol(existing) !== text) {
206
+ if (existing !== undefined && !matchesRecord(existing, record[referenceKey])) {
147
207
  keptReferences.push(referenceKey);
148
208
  continue;
149
209
  }
@@ -151,12 +211,16 @@ function installSkillForAgent(skill, adapter, source, record) {
151
211
  }
152
212
  record[referenceKey] = { version, sha256: sha256(text) };
153
213
  }
214
+ const stale = removeStaleReferences(referencesDir, source, record);
154
215
  return {
155
216
  agent: adapter.name,
156
217
  relativePath,
157
218
  status,
158
219
  ...backupPath ? { backupPath } : {},
159
- ...keptReferences.length > 0 ? { keptReferences } : {}
220
+ ...keptReferences.length > 0 ? { keptReferences } : {},
221
+ ...symlinkedReferences.length > 0 ? { symlinkedReferences } : {},
222
+ ...stale.removed.length > 0 ? { removedReferences: stale.removed } : {},
223
+ ...stale.kept.length > 0 ? { keptStaleReferences: stale.kept } : {}
160
224
  };
161
225
  } catch (err) {
162
226
  return {
@@ -167,30 +231,59 @@ function installSkillForAgent(skill, adapter, source, record) {
167
231
  };
168
232
  }
169
233
  }
234
+ function emptyResult(status, agents) {
235
+ return {
236
+ status,
237
+ agents,
238
+ outcomes: [],
239
+ counts: { written: 0, updated: 0, current: 0, kept: 0, skipped: 0, failed: 0 },
240
+ warnings: [],
241
+ notes: []
242
+ };
243
+ }
244
+ function summarizeInstall(result) {
245
+ const { written, updated, current, kept, skipped, failed } = result.counts;
246
+ return [
247
+ written && `${written} written`,
248
+ updated && `${updated} updated`,
249
+ current && `${current} already current`,
250
+ kept && `${kept} kept (edited locally)`,
251
+ skipped && `${skipped} skipped (symlink)`,
252
+ failed && `${failed} failed`
253
+ ].filter(Boolean).join(", ");
254
+ }
170
255
  async function installRules(detectedAgents, options = {}) {
171
256
  const adapters = detectedAgents.map((a) => ADAPTERS[a]).filter((a) => a !== undefined);
172
257
  if (adapters.length === 0) {
173
258
  if (!options.silent)
174
259
  console.log(" No supported coding agents detected");
175
- return;
260
+ return emptyResult("skipped", []);
176
261
  }
177
- const agentList = adapters.map((a) => a.name).join(", ");
262
+ const agentNames = adapters.map((a) => a.name);
178
263
  const skillList = SKILLS.map((s) => `
179
264
  • ${s.label}`).join("");
180
265
  const shouldInstall = options.yes ? true : await confirm({
181
- message: `Install Moda skills for ${agentList}?${skillList}
266
+ message: `Install Moda skills for ${agentNames.join(", ")}?${skillList}
182
267
  Continue?`,
183
268
  default: true
184
269
  });
185
270
  if (!shouldInstall) {
186
271
  if (!options.silent)
187
272
  console.log(" Skipped");
188
- return;
273
+ return emptyResult("skipped", agentNames);
189
274
  }
190
- let wrote = 0;
191
- let skipped = 0;
192
- let failed = 0;
193
- let updated = 0;
275
+ const result = emptyResult("ok", agentNames);
276
+ const { counts, warnings, notes } = result;
277
+ const warn = (message) => {
278
+ warnings.push(message);
279
+ if (!options.silent)
280
+ console.error(` ${message}`);
281
+ };
282
+ const note = (message) => {
283
+ notes.push(message);
284
+ if (!options.silent)
285
+ console.log(` ${message}`);
286
+ };
194
287
  const record = readInstallRecord();
195
288
  const recordBefore = JSON.stringify(record);
196
289
  for (const skill of SKILLS) {
@@ -198,45 +291,54 @@ async function installRules(detectedAgents, options = {}) {
198
291
  try {
199
292
  source = loadSkillSource(skill.id);
200
293
  } catch (err) {
201
- const message = err instanceof Error ? err.message : String(err);
202
- console.error(` Warning: ${message}`);
203
- failed += adapters.length;
294
+ warn(`Warning: ${err instanceof Error ? err.message : String(err)}`);
295
+ counts.failed += adapters.length;
204
296
  continue;
205
297
  }
206
298
  for (const adapter of adapters) {
207
299
  const outcome = installSkillForAgent(skill, adapter, source, record);
208
- if (!options.silent) {
209
- for (const kept of outcome.keptReferences ?? []) {
210
- console.log(` ${outcome.agent}: ${kept} was edited locally — skipping (delete it to reinstall)`);
211
- }
300
+ result.outcomes.push(outcome);
301
+ const agent = outcome.agent;
302
+ for (const kept of outcome.keptReferences ?? []) {
303
+ note(`${agent}: ${kept} was edited locally — skipping (delete it to reinstall)`);
304
+ }
305
+ for (const linked of outcome.symlinkedReferences ?? []) {
306
+ warn(`${agent}: ${linked} is a symlink — skipping so the file it points to is not overwritten`);
307
+ }
308
+ for (const removed of outcome.removedReferences ?? []) {
309
+ note(`${agent}: removed ${removed} (no longer part of the skill)`);
310
+ }
311
+ for (const kept of outcome.keptStaleReferences ?? []) {
312
+ note(`${agent}: ${kept} is no longer part of the skill but was edited locally — leaving it in place`);
212
313
  }
213
314
  switch (outcome.status) {
214
315
  case "wrote":
215
316
  if (!options.silent)
216
- console.log(` ${outcome.agent}: wrote ${outcome.relativePath}`);
217
- wrote++;
317
+ console.log(` ${agent}: wrote ${outcome.relativePath}`);
318
+ counts.written++;
218
319
  break;
219
320
  case "updated":
220
- if (!options.silent) {
221
- const backup = outcome.backupPath ? ` (previous copy saved to ${outcome.backupPath})` : "";
222
- console.log(` ${outcome.agent}: updated ${outcome.relativePath} to the current skill${backup}`);
321
+ if (outcome.backupPath) {
322
+ warn(`${agent}: replaced ${outcome.relativePath} with the current skill (previous copy saved to ${outcome.backupPath})`);
323
+ } else if (!options.silent) {
324
+ console.log(` ${agent}: updated ${outcome.relativePath} to the current skill`);
223
325
  }
224
- updated++;
326
+ counts.updated++;
225
327
  break;
226
328
  case "current":
227
- skipped++;
329
+ counts.current++;
228
330
  break;
229
331
  case "exists":
230
- if (!options.silent) {
231
- console.log(` ${outcome.agent}: ${outcome.relativePath} was edited locally — skipping (delete it to reinstall)`);
232
- }
233
- skipped++;
332
+ note(`${agent}: ${outcome.relativePath} was edited locally — skipping (delete it to reinstall)`);
333
+ counts.kept++;
334
+ break;
335
+ case "symlink":
336
+ warn(`${agent}: skipped ${outcome.relativePath} because ${outcome.symlinkPath} is a symlink ` + "(install never writes through a link, which could overwrite files outside this project)");
337
+ counts.skipped++;
234
338
  break;
235
339
  case "failed":
236
- if (!options.silent) {
237
- console.error(` ${outcome.agent}: failed to write ${outcome.relativePath} — ${outcome.error}`);
238
- }
239
- failed++;
340
+ warn(`${agent}: failed to write ${outcome.relativePath} — ${outcome.error}`);
341
+ counts.failed++;
240
342
  break;
241
343
  }
242
344
  }
@@ -245,21 +347,14 @@ async function installRules(detectedAgents, options = {}) {
245
347
  try {
246
348
  writeInstallRecord(record);
247
349
  } catch (err) {
248
- if (!options.silent) {
249
- console.error(` Could not save ${INSTALL_RECORD_PATH} — ${err instanceof Error ? err.message : String(err)}. ` + "A later moda init may overwrite local edits to these skill files.");
250
- }
350
+ warn(`Could not save ${INSTALL_RECORD_PATH} — ${err instanceof Error ? err.message : String(err)}. ` + "A later moda init may overwrite local edits to these skill files.");
251
351
  }
252
352
  }
253
- const summary = [
254
- wrote && `${wrote} written`,
255
- updated && `${updated} updated`,
256
- skipped && `${skipped} already present`,
257
- failed && `${failed} failed`
258
- ].filter(Boolean).join(", ");
259
- if (summary) {
260
- if (!options.silent)
261
- console.log(` Done — ${summary}`);
262
- }
353
+ result.status = counts.failed > 0 ? "failed" : warnings.length > 0 ? "warning" : "ok";
354
+ const summary = summarizeInstall(result);
355
+ if (summary && !options.silent)
356
+ console.log(` Done — ${summary}`);
357
+ return result;
263
358
  }
264
359
 
265
360
  // src/init/mcp.ts
@@ -293,7 +388,7 @@ import {
293
388
  readdirSync as readdirSync2,
294
389
  readFileSync as readFileSync2,
295
390
  rmdirSync,
296
- unlinkSync,
391
+ unlinkSync as unlinkSync2,
297
392
  writeFileSync as writeFileSync2
298
393
  } from "node:fs";
299
394
  import { dirname as dirname3, isAbsolute, join as join4, normalize, relative, resolve } from "node:path";
@@ -376,7 +471,7 @@ function pruneManagedSkillPaths(cwd, managedPaths, log) {
376
471
  if (!absolutePath || !existsSync3(absolutePath))
377
472
  continue;
378
473
  try {
379
- unlinkSync(absolutePath);
474
+ unlinkSync2(absolutePath);
380
475
  pruned++;
381
476
  log(` pruned ${safePath}`);
382
477
  pruneEmptyClaudeSkillDir(cwd, safePath);
@@ -505,4 +600,4 @@ function yamlQuote(value) {
505
600
  return JSON.stringify(value);
506
601
  }
507
602
 
508
- export { packageAssetPath, claudeSkillToCursorRule, installRules, detectAgents, SKILL_SOURCE_HARNESS, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, writeSkillsManifest, upsertSkillManifestRecord, pruneManagedSkillPaths };
603
+ export { packageAssetPath, claudeSkillToCursorRule, summarizeInstall, installRules, detectAgents, SKILL_SOURCE_HARNESS, SKILL_SOURCE_INTEGRATION_BUNDLED, readSkillsManifest, writeSkillsManifest, upsertSkillManifestRecord, pruneManagedSkillPaths };
@@ -5,7 +5,7 @@ import {
5
5
  packageAssetPath,
6
6
  readSkillsManifest,
7
7
  upsertSkillManifestRecord
8
- } from "./cli-sf0g0fjg.js";
8
+ } from "./cli-8dtnwp9r.js";
9
9
 
10
10
  // src/skills-bundled.ts
11
11
  import { createHash } from "node:crypto";
package/dist/cli.js CHANGED
@@ -1853,7 +1853,7 @@ async function runPromptsCommand(...args) {
1853
1853
  return (await import("./prompts-1tkhrxkq.js")).runPromptsCommand(...args);
1854
1854
  }
1855
1855
  async function runSkillsCommand(...args) {
1856
- return (await import("./skills-br82cpt7.js")).runSkillsCommand(...args);
1856
+ return (await import("./skills-gejdvjak.js")).runSkillsCommand(...args);
1857
1857
  }
1858
1858
  async function runFixCommand(...args) {
1859
1859
  return (await import("./fix-9fmy6p2x.js")).runFixCommand(...args);
@@ -1865,7 +1865,7 @@ async function runHarnessCommand(...args) {
1865
1865
  return (await import("./harness-xvg6ws6x.js")).runHarnessCommand(...args);
1866
1866
  }
1867
1867
  async function runSyncCommand(...args) {
1868
- return (await import("./sync-25vn78je.js")).runSyncCommand(...args);
1868
+ return (await import("./sync-57ercmh9.js")).runSyncCommand(...args);
1869
1869
  }
1870
1870
  async function runRegistryCommand(...args) {
1871
1871
  return (await import("./registry-atqyb5fa.js")).runRegistryCommand(...args);
@@ -4473,7 +4473,9 @@ function initAgentWriteOptions(result) {
4473
4473
  warnings: [
4474
4474
  ...manualActions.filter((action) => action.reason).map((action) => `${action.title}: ${action.reason}`),
4475
4475
  ...result.promptSync?.status === "failed" ? [`Prompt sync failed: ${result.promptSync.error}. Retry with 'moda prompts sync'.`] : [],
4476
- ...result.skillSync?.status === "failed" ? [`Skill sync failed: ${result.skillSync.error}. Retry with 'moda skills sync'.`] : []
4476
+ ...result.skillSync?.status === "failed" ? [`Skill sync failed: ${result.skillSync.error}. Retry with 'moda skills sync'.`] : [],
4477
+ ...result.skillSync?.warnings ?? [],
4478
+ ...(result.skillInstall?.warnings ?? []).map((warning) => `moda-cli skill install: ${warning}`)
4477
4479
  ]
4478
4480
  };
4479
4481
  }
@@ -4533,7 +4535,7 @@ var commandRegistry = createCommandRegistry([
4533
4535
  resetApiRequestCountBeforeRun: true,
4534
4536
  telemetry: "result-and-error",
4535
4537
  handler: async (context) => {
4536
- const { runInit } = await import("./index-ewqbpnwz.js");
4538
+ const { runInit } = await import("./index-1wbjz3jc.js");
4537
4539
  if (context.outputMode === "agent-stream") {
4538
4540
  context.output.writeEvent({
4539
4541
  event: "started",
@@ -4553,7 +4555,10 @@ var commandRegistry = createCommandRegistry([
4553
4555
  }
4554
4556
  });
4555
4557
  if (context.outputMode !== "human") {
4556
- context.output.writeData(result, initAgentWriteOptions(result));
4558
+ context.output.writeData(result, {
4559
+ ...initAgentWriteOptions(result),
4560
+ ...result.skillInstall?.status === "failed" ? { status: "degraded" } : {}
4561
+ });
4557
4562
  }
4558
4563
  return { exitCode: 0, apiKey: result.apiKey };
4559
4564
  }
@@ -42,10 +42,11 @@ import {
42
42
  bundledSkillsDir,
43
43
  installIntegrationSkill,
44
44
  readBundledIndex
45
- } from "./cli-38p87ykc.js";
45
+ } from "./cli-frhm2wz7.js";
46
46
  import {
47
+ agentsMdProblem,
47
48
  runSkillSync
48
- } from "./cli-4j8nffsm.js";
49
+ } from "./cli-0ef72tqx.js";
49
50
  import {
50
51
  createCommandContext
51
52
  } from "./cli-569hj0bj.js";
@@ -62,8 +63,9 @@ import {
62
63
  } from "./cli-5ke3xe6y.js";
63
64
  import {
64
65
  detectAgents,
65
- installRules
66
- } from "./cli-sf0g0fjg.js";
66
+ installRules,
67
+ summarizeInstall
68
+ } from "./cli-8dtnwp9r.js";
67
69
  import"./cli-0v6na3yp.js";
68
70
 
69
71
  // src/init/index.ts
@@ -2022,13 +2024,33 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2022
2024
  }
2023
2025
  emit("apply", "Applying approved setup changes.");
2024
2026
  const laneBoard = startBoard();
2027
+ const setupWarnings = [];
2028
+ const addSetupWarning = (message) => {
2029
+ setupWarnings.push(message);
2030
+ emit("apply", message, "warning");
2031
+ };
2025
2032
  const agents = detectAgents();
2026
2033
  laneBoard?.startLane("agent-skills", "Agent skills");
2027
- if (agents.length > 0) {
2028
- await installRules(agents, { yes: true, silent: true });
2029
- laneBoard?.complete("agent-skills", `moda-cli skill installed for ${agents.join(", ")}`);
2034
+ const skillInstallResult = agents.length > 0 ? await installRules(agents, { yes: true, silent: true }) : undefined;
2035
+ const skillInstall = {
2036
+ status: skillInstallResult?.status ?? "skipped",
2037
+ agents: skillInstallResult?.agents ?? [],
2038
+ summary: skillInstallResult ? summarizeInstall(skillInstallResult) : "",
2039
+ warnings: skillInstallResult?.warnings ?? []
2040
+ };
2041
+ skillInstall.warnings.forEach(addSetupWarning);
2042
+ if (skillInstall.agents.length === 0) {
2043
+ laneBoard?.complete("agent-skills", agents.length > 0 ? "no supported coding agents detected" : "no coding agents detected");
2044
+ } else if (skillInstall.status === "ok") {
2045
+ laneBoard?.complete("agent-skills", `moda-cli skill installed for ${skillInstall.agents.join(", ")}`);
2030
2046
  } else {
2031
- laneBoard?.complete("agent-skills", "no coding agents detected");
2047
+ const counts = skillInstallResult?.counts;
2048
+ const nothingInstalled = counts !== undefined && counts.written + counts.updated + counts.current === 0;
2049
+ const note = `moda-cli skill: ${skillInstall.summary}; ${skillInstall.warnings.length} warning(s), details below`;
2050
+ if (skillInstall.status === "failed" && nothingInstalled)
2051
+ laneBoard?.fail("agent-skills", note);
2052
+ else
2053
+ laneBoard?.warn("agent-skills", note);
2032
2054
  }
2033
2055
  await initPrompts({ silent: true });
2034
2056
  let promptSyncOutcome;
@@ -2046,8 +2068,19 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2046
2068
  laneBoard?.startLane("skills", "Skills");
2047
2069
  try {
2048
2070
  const skillSync = await runSkillSync({}, { profile });
2049
- skillSyncOutcome = { status: "ok", synced: skillSync.synced.map((skill) => skill.key) };
2050
- laneBoard?.complete("skills", skillSync.synced.length > 0 ? `${skillSync.synced.length} skill(s) synced` : "no local skills to sync yet");
2071
+ const agentsMdWarning = agentsMdProblem(skillSync.agentsMd);
2072
+ skillSyncOutcome = {
2073
+ status: "ok",
2074
+ synced: skillSync.synced.map((skill) => skill.key),
2075
+ ...agentsMdWarning ? { warnings: [agentsMdWarning] } : {}
2076
+ };
2077
+ const syncedNote = skillSync.synced.length > 0 ? `${skillSync.synced.length} skill(s) synced` : "no local skills to sync yet";
2078
+ if (agentsMdWarning) {
2079
+ addSetupWarning(agentsMdWarning);
2080
+ laneBoard?.warn("skills", `${syncedNote}; AGENTS.md block not updated, details below`);
2081
+ } else {
2082
+ laneBoard?.complete("skills", syncedNote);
2083
+ }
2051
2084
  } catch (err) {
2052
2085
  const message = err instanceof Error ? err.message : String(err);
2053
2086
  skillSyncOutcome = { status: "failed", synced: [], error: message };
@@ -2160,11 +2193,13 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2160
2193
  }
2161
2194
  }
2162
2195
  board?.stop();
2196
+ for (const message of setupWarnings)
2197
+ warn(renderInitNote(message, "warning"));
2163
2198
  if (!harnessDeferred)
2164
2199
  markApplied(setupPlan, "harness");
2165
2200
  phases.push(harnessPhase);
2166
- emit("apply", "Approved changes applied.", "completed");
2167
- phases.push({ id: "apply", title: "Set up", status: "completed" });
2201
+ emit("apply", "Approved changes applied.", setupWarnings.length > 0 ? "warning" : "completed");
2202
+ phases.push(setupWarnings.length > 0 ? { id: "apply", title: "Set up", status: "warning", details: `${setupWarnings.length} setup warning(s); see warnings.` } : { id: "apply", title: "Set up", status: "completed" });
2168
2203
  const offlineValidation = isTruthyFlag(flagValue(flags, "offline"));
2169
2204
  log(renderInitStep(5, 5, "Verify", offlineValidation ? "Running local doctor checks so the setup leaves a clear state." : "Running Moda doctor online as a tenant health check (not proof of SDK integration)."));
2170
2205
  emit("validate", `Running Moda doctor diagnostics (${offlineValidation ? "offline" : "online"}).`);
@@ -2205,6 +2240,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2205
2240
  setupPlan,
2206
2241
  promptSync: promptSyncOutcome,
2207
2242
  skillSync: skillSyncOutcome,
2243
+ skillInstall,
2208
2244
  ...sdkIntegration ? { sdk_integration: sdkIntegration } : {}
2209
2245
  };
2210
2246
  }
@@ -3,8 +3,8 @@ import {
3
3
  installIntegrationSkill,
4
4
  readBundledIndex,
5
5
  readBundledSkillMd
6
- } from "./cli-38p87ykc.js";
7
- import"./cli-sf0g0fjg.js";
6
+ } from "./cli-frhm2wz7.js";
7
+ import"./cli-8dtnwp9r.js";
8
8
  import"./cli-0v6na3yp.js";
9
9
  export {
10
10
  readBundledSkillMd,
@@ -3,13 +3,13 @@ import {
3
3
  pullSkills,
4
4
  runSkillSync,
5
5
  runSkillsCommand
6
- } from "./cli-4j8nffsm.js";
6
+ } from "./cli-0ef72tqx.js";
7
7
  import"./cli-569hj0bj.js";
8
8
  import"./cli-cehskjde.js";
9
9
  import"./cli-rqk6f6n6.js";
10
10
  import"./cli-7ea02wca.js";
11
11
  import"./cli-5ke3xe6y.js";
12
- import"./cli-sf0g0fjg.js";
12
+ import"./cli-8dtnwp9r.js";
13
13
  import"./cli-0v6na3yp.js";
14
14
  export {
15
15
  runSkillsCommand,
@@ -8,7 +8,7 @@ import {
8
8
  import"./cli-97fnztpy.js";
9
9
  import {
10
10
  runSkillsCommand
11
- } from "./cli-4j8nffsm.js";
11
+ } from "./cli-0ef72tqx.js";
12
12
  import"./cli-569hj0bj.js";
13
13
  import"./cli-cehskjde.js";
14
14
  import"./cli-rqk6f6n6.js";
@@ -16,7 +16,7 @@ import"./cli-7ea02wca.js";
16
16
  import {
17
17
  CliInputError
18
18
  } from "./cli-5ke3xe6y.js";
19
- import"./cli-sf0g0fjg.js";
19
+ import"./cli-8dtnwp9r.js";
20
20
  import"./cli-0v6na3yp.js";
21
21
 
22
22
  // src/sync.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.55.20",
3
+ "version": "1.55.22",
4
4
  "description": "CLI for Moda: production monitoring and evals for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-10-15T10:28:15.672Z",
4
- "cli_version": "1.55.20",
3
+ "bundled_at": "2026-10-15T12:08:24.887Z",
4
+ "cli_version": "1.55.22",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",