@moda-ai/cli 1.55.19 → 1.55.20

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.
@@ -5,7 +5,7 @@ import {
5
5
  packageAssetPath,
6
6
  readSkillsManifest,
7
7
  upsertSkillManifestRecord
8
- } from "./cli-ssfq84k5.js";
8
+ } from "./cli-sf0g0fjg.js";
9
9
 
10
10
  // src/skills-bundled.ts
11
11
  import { createHash } from "node:crypto";
@@ -23,7 +23,7 @@ import {
23
23
  pruneManagedSkillPaths,
24
24
  readSkillsManifest,
25
25
  writeSkillsManifest
26
- } from "./cli-ssfq84k5.js";
26
+ } from "./cli-sf0g0fjg.js";
27
27
  import {
28
28
  __require
29
29
  } from "./cli-0v6na3yp.js";
@@ -53,7 +53,11 @@ function renderFixesBlock() {
53
53
  "## Fixing production issues with Moda (managed block — edits inside are overwritten by moda-cli)",
54
54
  "",
55
55
  "Moda supplies the production evidence; you propose and make the fix yourself.",
56
+ "The `moda` CLI needs `MODA_API_KEY` (or `npx -y -p @moda-ai/cli moda ...` when",
57
+ "not installed); `moda <command> --help` lists its flags.",
56
58
  "",
59
+ '- Find where something happened in production: `moda search "<words>"`, then',
60
+ " `moda context <conversation_id> --msg-index=<message_index>` to read the turn.",
57
61
  "- Find what to fix: `moda investigate` ranks tool failures, user frustrations,",
58
62
  " and top Problems with evidence; `moda problems` ranks cross-signal Problems",
59
63
  " by root cause; `moda failures` lists tool failures only.",
@@ -862,7 +866,7 @@ function sha256(input) {
862
866
  return createHash("sha256").update(input).digest("hex");
863
867
  }
864
868
  async function runSkillsInstall(context, ids) {
865
- const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-mdmhzfsr.js");
869
+ const { installIntegrationSkill, readBundledIndex, readBundledSkillMd } = await import("./skills-bundled-r9a2vs07.js");
866
870
  const index = readBundledIndex();
867
871
  if (context.flags.list === "true" || ids.length === 0) {
868
872
  if (!index) {
@@ -6,8 +6,9 @@ function packageAssetPath(...parts) {
6
6
  }
7
7
 
8
8
  // src/init/rules.ts
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
- import { join as join2 } from "node:path";
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";
11
12
  import { confirm } from "@inquirer/prompts";
12
13
  var SKILLS_SOURCE_DIR = packageAssetPath("skills");
13
14
  var SKILLS = [
@@ -23,6 +24,7 @@ var ADAPTERS = {
23
24
  dir: join2(".claude", "skills", skillId),
24
25
  filename: "SKILL.md"
25
26
  }),
27
+ referencesDirFor: (skillId) => join2(".claude", "skills", skillId, "references"),
26
28
  transform: (content) => content
27
29
  },
28
30
  Cursor: {
@@ -31,7 +33,8 @@ var ADAPTERS = {
31
33
  dir: join2(".cursor", "rules"),
32
34
  filename: `${skillId}.mdc`
33
35
  }),
34
- transform: claudeSkillToCursorRule
36
+ referencesDirFor: (skillId) => join2(".cursor", "rules", `${skillId}-references`),
37
+ transform: (content, referencesDir) => claudeSkillToCursorRule(content).replace(/`references\//g, `\`${referencesDir}/`)
35
38
  }
36
39
  };
37
40
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
@@ -53,24 +56,108 @@ function claudeSkillToCursorRule(skillContent) {
53
56
  ${body.replace(/^\s+/, "")}`;
54
57
  }
55
58
  function loadSkillSource(skillId) {
56
- const path = join2(SKILLS_SOURCE_DIR, skillId, "SKILL.md");
59
+ const dir = join2(SKILLS_SOURCE_DIR, skillId);
60
+ const path = join2(dir, "SKILL.md");
57
61
  if (!existsSync(path)) {
58
62
  throw new Error(`Skill source missing: ${path}`);
59
63
  }
60
- return readFileSync(path, "utf8");
64
+ const references = new Map;
65
+ const referencesDir = join2(dir, "references");
66
+ if (existsSync(referencesDir)) {
67
+ for (const name of readdirSync(referencesDir).sort()) {
68
+ if (name.endsWith(".md"))
69
+ references.set(name, readFileSync(join2(referencesDir, name), "utf8"));
70
+ }
71
+ }
72
+ return { content: readFileSync(path, "utf8"), references };
73
+ }
74
+ var INSTALL_RECORD_PATH = join2(".moda", "agent-skills.json");
75
+ function sha256(text) {
76
+ return createHash("sha256").update(text).digest("hex");
77
+ }
78
+ function readInstallRecord() {
79
+ try {
80
+ const parsed = JSON.parse(readFileSync(join2(process.cwd(), INSTALL_RECORD_PATH), "utf8"));
81
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
82
+ } catch {
83
+ return {};
84
+ }
85
+ }
86
+ function writeInstallRecord(record) {
87
+ const path = join2(process.cwd(), INSTALL_RECORD_PATH);
88
+ mkdirSync(dirname2(path), { recursive: true });
89
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}
90
+ `);
61
91
  }
62
- function installSkillForAgent(skill, adapter, content) {
92
+ function skillVersion(skillContent) {
93
+ const frontmatter = FRONTMATTER_RE.exec(skillContent)?.[1] ?? "";
94
+ return /^version:\s*(\S+)\s*$/m.exec(frontmatter)?.[1] ?? "unknown";
95
+ }
96
+ function looksLikeShippedSkill(existing, skill) {
97
+ return existing.startsWith(`---
98
+ name: ${skill.id}
99
+ `) || /^# Moda CLI$/m.test(existing);
100
+ }
101
+ function unusedBackupPath(relativePath) {
102
+ let candidate = `${relativePath}.bak`;
103
+ for (let n = 1;existsSync(join2(process.cwd(), candidate)); n++) {
104
+ candidate = `${relativePath}.bak.${n}`;
105
+ }
106
+ return candidate;
107
+ }
108
+ function installSkillForAgent(skill, adapter, source, record) {
63
109
  const { dir, filename } = adapter.targetFor(skill.id);
64
110
  const dirPath = join2(process.cwd(), dir);
65
111
  const filePath = join2(dirPath, filename);
66
112
  const relativePath = join2(dir, filename);
113
+ const referencesDir = adapter.referencesDirFor(skill.id);
114
+ const content = adapter.transform(source.content, referencesDir);
67
115
  try {
68
116
  mkdirSync(dirPath, { recursive: true });
117
+ let status = "wrote";
118
+ let backupPath;
69
119
  if (existsSync(filePath)) {
70
- return { agent: adapter.name, relativePath, status: "exists" };
120
+ const existing = readFileSync(filePath, "utf8");
121
+ const recorded = record[relativePath];
122
+ if (existing === content) {
123
+ status = "current";
124
+ } else if (recorded ? recorded.sha256 === sha256(existing) : looksLikeShippedSkill(existing, skill)) {
125
+ if (!recorded) {
126
+ backupPath = unusedBackupPath(relativePath);
127
+ writeFileSync(join2(process.cwd(), backupPath), existing);
128
+ }
129
+ status = "updated";
130
+ } else {
131
+ return { agent: adapter.name, relativePath, status: "exists" };
132
+ }
133
+ }
134
+ if (status !== "current")
135
+ writeFileSync(filePath, content);
136
+ const version = skillVersion(source.content);
137
+ record[relativePath] = { version, sha256: sha256(content) };
138
+ const referencesPath = join2(process.cwd(), referencesDir);
139
+ mkdirSync(referencesPath, { recursive: true });
140
+ const keptReferences = [];
141
+ for (const [name, text] of source.references) {
142
+ const referencePath = join2(referencesPath, name);
143
+ 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)) {
147
+ keptReferences.push(referenceKey);
148
+ continue;
149
+ }
150
+ writeFileSync(referencePath, text);
151
+ }
152
+ record[referenceKey] = { version, sha256: sha256(text) };
71
153
  }
72
- writeFileSync(filePath, adapter.transform(content));
73
- return { agent: adapter.name, relativePath, status: "wrote" };
154
+ return {
155
+ agent: adapter.name,
156
+ relativePath,
157
+ status,
158
+ ...backupPath ? { backupPath } : {},
159
+ ...keptReferences.length > 0 ? { keptReferences } : {}
160
+ };
74
161
  } catch (err) {
75
162
  return {
76
163
  agent: adapter.name,
@@ -103,10 +190,13 @@ async function installRules(detectedAgents, options = {}) {
103
190
  let wrote = 0;
104
191
  let skipped = 0;
105
192
  let failed = 0;
193
+ let updated = 0;
194
+ const record = readInstallRecord();
195
+ const recordBefore = JSON.stringify(record);
106
196
  for (const skill of SKILLS) {
107
- let content;
197
+ let source;
108
198
  try {
109
- content = loadSkillSource(skill.id);
199
+ source = loadSkillSource(skill.id);
110
200
  } catch (err) {
111
201
  const message = err instanceof Error ? err.message : String(err);
112
202
  console.error(` Warning: ${message}`);
@@ -114,16 +204,31 @@ async function installRules(detectedAgents, options = {}) {
114
204
  continue;
115
205
  }
116
206
  for (const adapter of adapters) {
117
- const outcome = installSkillForAgent(skill, adapter, content);
207
+ 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
+ }
212
+ }
118
213
  switch (outcome.status) {
119
214
  case "wrote":
120
215
  if (!options.silent)
121
216
  console.log(` ${outcome.agent}: wrote ${outcome.relativePath}`);
122
217
  wrote++;
123
218
  break;
219
+ 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}`);
223
+ }
224
+ updated++;
225
+ break;
226
+ case "current":
227
+ skipped++;
228
+ break;
124
229
  case "exists":
125
230
  if (!options.silent) {
126
- console.log(` ${outcome.agent}: ${outcome.relativePath} already exists — skipping`);
231
+ console.log(` ${outcome.agent}: ${outcome.relativePath} was edited locally — skipping (delete it to reinstall)`);
127
232
  }
128
233
  skipped++;
129
234
  break;
@@ -136,8 +241,18 @@ async function installRules(detectedAgents, options = {}) {
136
241
  }
137
242
  }
138
243
  }
244
+ if (JSON.stringify(record) !== recordBefore) {
245
+ try {
246
+ writeInstallRecord(record);
247
+ } 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
+ }
251
+ }
252
+ }
139
253
  const summary = [
140
254
  wrote && `${wrote} written`,
255
+ updated && `${updated} updated`,
141
256
  skipped && `${skipped} already present`,
142
257
  failed && `${failed} failed`
143
258
  ].filter(Boolean).join(", ");
@@ -175,13 +290,13 @@ function detectAgents() {
175
290
  import {
176
291
  existsSync as existsSync3,
177
292
  mkdirSync as mkdirSync2,
178
- readdirSync,
293
+ readdirSync as readdirSync2,
179
294
  readFileSync as readFileSync2,
180
295
  rmdirSync,
181
296
  unlinkSync,
182
297
  writeFileSync as writeFileSync2
183
298
  } from "node:fs";
184
- import { dirname as dirname2, isAbsolute, join as join4, normalize, relative, resolve } from "node:path";
299
+ import { dirname as dirname3, isAbsolute, join as join4, normalize, relative, resolve } from "node:path";
185
300
  var SKILLS_MANIFEST_PATH = join4(".moda", "skills.yml");
186
301
  var SKILL_SOURCE_HARNESS = "moda";
187
302
  var SKILL_SOURCE_INTEGRATION_BUNDLED = "integration-bundled";
@@ -241,7 +356,7 @@ function writeSkillsManifest(cwd, header, records) {
241
356
  ]),
242
357
  ""
243
358
  ];
244
- mkdirSync2(dirname2(manifestPath), { recursive: true });
359
+ mkdirSync2(dirname3(manifestPath), { recursive: true });
245
360
  writeFileSync2(manifestPath, lines.join(`
246
361
  `));
247
362
  }
@@ -272,9 +387,9 @@ function pruneManagedSkillPaths(cwd, managedPaths, log) {
272
387
  function pruneEmptyClaudeSkillDir(cwd, relativePath) {
273
388
  if (!relativePath.startsWith(".claude/skills/") || !relativePath.endsWith("/SKILL.md"))
274
389
  return;
275
- const dir = dirname2(join4(cwd, relativePath));
390
+ const dir = dirname3(join4(cwd, relativePath));
276
391
  try {
277
- if (existsSync3(dir) && readdirSync(dir).length === 0) {
392
+ if (existsSync3(dir) && readdirSync2(dir).length === 0) {
278
393
  rmdirSync(dir);
279
394
  }
280
395
  } catch {}
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-d35nnm66.js")).runSkillsCommand(...args);
1856
+ return (await import("./skills-br82cpt7.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-n2psdffz.js")).runSyncCommand(...args);
1868
+ return (await import("./sync-25vn78je.js")).runSyncCommand(...args);
1869
1869
  }
1870
1870
  async function runRegistryCommand(...args) {
1871
1871
  return (await import("./registry-atqyb5fa.js")).runRegistryCommand(...args);
@@ -3245,8 +3245,8 @@ async function readFullTranscript(conversationId, opts = {}) {
3245
3245
  if (tied > 0 || synthetic > 0) {
3246
3246
  out.push(`Message order may not be chronological: ${tied} of ${total} messages share a timestamp with another message (ordered by id, not time)${synthetic > 0 ? ` and ${synthetic} carry a synthetic pre-2022 timestamp` : ""}.`);
3247
3247
  }
3248
- if (returned.some(isTruncated)) {
3249
- out.push(`Message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters are truncated by the context endpoint. For full content (prompts, tool arguments/results) use \`moda audit <conversation_id> --include-bodies\` (or \`--include-raw\` for the verbatim events).`);
3248
+ if (legacy && returned.some(isTruncated)) {
3249
+ out.push(`This Moda server is older and truncates message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters. For full content (prompts, tool arguments/results) use \`moda audit <conversation_id> --include-bodies\` (or \`--include-raw\` for the verbatim events).`);
3250
3250
  }
3251
3251
  return {
3252
3252
  data: stripLoneSurrogates({
@@ -4533,7 +4533,7 @@ var commandRegistry = createCommandRegistry([
4533
4533
  resetApiRequestCountBeforeRun: true,
4534
4534
  telemetry: "result-and-error",
4535
4535
  handler: async (context) => {
4536
- const { runInit } = await import("./index-6qqmjqb2.js");
4536
+ const { runInit } = await import("./index-ewqbpnwz.js");
4537
4537
  if (context.outputMode === "agent-stream") {
4538
4538
  context.output.writeEvent({
4539
4539
  event: "started",
@@ -42,10 +42,10 @@ import {
42
42
  bundledSkillsDir,
43
43
  installIntegrationSkill,
44
44
  readBundledIndex
45
- } from "./cli-c5c6f0yw.js";
45
+ } from "./cli-38p87ykc.js";
46
46
  import {
47
47
  runSkillSync
48
- } from "./cli-5km7j04z.js";
48
+ } from "./cli-4j8nffsm.js";
49
49
  import {
50
50
  createCommandContext
51
51
  } from "./cli-569hj0bj.js";
@@ -63,7 +63,7 @@ import {
63
63
  import {
64
64
  detectAgents,
65
65
  installRules
66
- } from "./cli-ssfq84k5.js";
66
+ } from "./cli-sf0g0fjg.js";
67
67
  import"./cli-0v6na3yp.js";
68
68
 
69
69
  // src/init/index.ts
@@ -3,13 +3,13 @@ import {
3
3
  pullSkills,
4
4
  runSkillSync,
5
5
  runSkillsCommand
6
- } from "./cli-5km7j04z.js";
6
+ } from "./cli-4j8nffsm.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-ssfq84k5.js";
12
+ import"./cli-sf0g0fjg.js";
13
13
  import"./cli-0v6na3yp.js";
14
14
  export {
15
15
  runSkillsCommand,
@@ -3,8 +3,8 @@ import {
3
3
  installIntegrationSkill,
4
4
  readBundledIndex,
5
5
  readBundledSkillMd
6
- } from "./cli-c5c6f0yw.js";
7
- import"./cli-ssfq84k5.js";
6
+ } from "./cli-38p87ykc.js";
7
+ import"./cli-sf0g0fjg.js";
8
8
  import"./cli-0v6na3yp.js";
9
9
  export {
10
10
  readBundledSkillMd,
@@ -8,7 +8,7 @@ import {
8
8
  import"./cli-97fnztpy.js";
9
9
  import {
10
10
  runSkillsCommand
11
- } from "./cli-5km7j04z.js";
11
+ } from "./cli-4j8nffsm.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-ssfq84k5.js";
19
+ import"./cli-sf0g0fjg.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.19",
3
+ "version": "1.55.20",
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:14:25.379Z",
4
- "cli_version": "1.55.19",
3
+ "bundled_at": "2026-10-15T10:28:15.672Z",
4
+ "cli_version": "1.55.20",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",