@moda-ai/cli 1.55.96 → 1.55.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/{cli-f0z97a7t.js → cli-4t3zznhh.js} +20 -5
- package/dist/cli.js +95 -22
- package/dist/{index-w2hs49tb.js → index-wdttmhsg.js} +1 -1
- package/dist/{skills-1pv2mp9v.js → skills-9c1xvp2s.js} +1 -1
- package/dist/{sync-4zeyb408.js → sync-skzdw9fb.js} +1 -1
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
package/README.md
CHANGED
|
@@ -489,3 +489,26 @@ in order; changed old literals cannot qualify through unchanged sibling text.
|
|
|
489
489
|
Keep call inputs and redirected output outside the repository: they can contain customer content.
|
|
490
490
|
Python, unresolved dynamic dispatch and new files without old context remain unmodelled. This
|
|
491
491
|
local result does not establish deployment attribution or production precision/recall.
|
|
492
|
+
|
|
493
|
+
### Updating agent installations
|
|
494
|
+
|
|
495
|
+
The CLI checks npm at most daily. When a newer version is cached, `--json`
|
|
496
|
+
commands also print an update notice on stderr without requiring a terminal,
|
|
497
|
+
including on validation and authentication failures. JSON stdout is unchanged.
|
|
498
|
+
With a writable home, notices are limited to once an hour for each running/latest version pair;
|
|
499
|
+
`--quiet`, `--no-update-check`, `MODA_CLI_UPDATE_CHECK=0`, `DO_NOT_TRACK=1`,
|
|
500
|
+
and CI suppress them. Read-only homes still receive cached guidance without a
|
|
501
|
+
persisted cooldown. Agent envelopes continue to carry `meta.update`.
|
|
502
|
+
|
|
503
|
+
After updating, run `moda skills install moda-cli` from the project directory.
|
|
504
|
+
It refreshes the installed guide and the init-managed Moda block in `AGENTS.md`,
|
|
505
|
+
replacing legacy `moda fixes` instructions. Text outside that block stays intact;
|
|
506
|
+
locally edited skill files are kept. Malformed or duplicated block markers and
|
|
507
|
+
symlinked `AGENTS.md` files are left unchanged with a warning and recovery steps.
|
|
508
|
+
Repeated installation is idempotent.
|
|
509
|
+
|
|
510
|
+
Retired `fix`, `fixes`, `prompts ab`, and `prompts propose` calls fail locally
|
|
511
|
+
with migration guidance. Read problem evidence with `moda problems get <id>
|
|
512
|
+
--evidence`, make changes in your project, and use `moda prompts status`,
|
|
513
|
+
`moda prompts diff`, and `moda prompts sync --dry-run` to review prompt versions.
|
|
514
|
+
The retired names stay out of help and the manifest.
|
|
@@ -41,7 +41,7 @@ import { execFileSync } from "node:child_process";
|
|
|
41
41
|
import { createHash } from "node:crypto";
|
|
42
42
|
import {
|
|
43
43
|
existsSync as existsSync2,
|
|
44
|
-
lstatSync,
|
|
44
|
+
lstatSync as lstatSync2,
|
|
45
45
|
mkdirSync as mkdirSync2,
|
|
46
46
|
readdirSync,
|
|
47
47
|
readFileSync as readFileSync2,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
import { basename, dirname as dirname2, join as join2, relative } from "node:path";
|
|
51
51
|
|
|
52
52
|
// src/agents-md.ts
|
|
53
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
54
54
|
import { dirname, join } from "node:path";
|
|
55
55
|
var AGENTS_MD_FILENAME = "AGENTS.md";
|
|
56
56
|
var FIXES_BLOCK_BEGIN = "<!-- moda:fixes:begin -->";
|
|
@@ -119,7 +119,7 @@ function upsertManagedBlock(existing, block) {
|
|
|
119
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
120
|
return {
|
|
121
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 push
|
|
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
123
|
};
|
|
124
124
|
}
|
|
125
125
|
function writeFileIfChanged(filePath, content) {
|
|
@@ -141,6 +141,15 @@ function writeFileIfChanged(filePath, content) {
|
|
|
141
141
|
function upsertFixesAgentsBlock(cwd) {
|
|
142
142
|
const filePath = join(cwd, AGENTS_MD_FILENAME);
|
|
143
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
|
+
}
|
|
144
153
|
if (existsSync(filePath)) {
|
|
145
154
|
try {
|
|
146
155
|
existing = readFileSync(filePath, "utf8");
|
|
@@ -485,7 +494,7 @@ function walkSkillFiles(dir, depth) {
|
|
|
485
494
|
const fullPath = join2(dir, entry);
|
|
486
495
|
let stat;
|
|
487
496
|
try {
|
|
488
|
-
stat =
|
|
497
|
+
stat = lstatSync2(fullPath);
|
|
489
498
|
} catch {
|
|
490
499
|
continue;
|
|
491
500
|
}
|
|
@@ -899,9 +908,15 @@ async function installModaCliSkill(context) {
|
|
|
899
908
|
if (result.status === "skipped") {
|
|
900
909
|
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.");
|
|
901
910
|
}
|
|
911
|
+
const agentsMd = result.status === "failed" ? undefined : upsertFixesAgentsBlock(context.cwd);
|
|
912
|
+
const blockWarning = agentsMdProblem(agentsMd);
|
|
913
|
+
if (blockWarning) {
|
|
914
|
+
result.warnings.push(blockWarning);
|
|
915
|
+
result.status = "warning";
|
|
916
|
+
}
|
|
902
917
|
const summary = summarizeInstall(result);
|
|
903
918
|
if (context.outputMode !== "human") {
|
|
904
|
-
context.output.writeData({ skill: MODA_CLI_SKILL_ID, status: result.status, agents: result.agents, counts: result.counts, summary, outcomes: result.outcomes, notes: result.notes }, { ...result.status === "failed" ? { status: "error" } : {}, warnings: result.warnings });
|
|
919
|
+
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 });
|
|
905
920
|
} else {
|
|
906
921
|
console.log(`moda-cli skill for ${result.agents.join(", ")}: ${summary}`);
|
|
907
922
|
for (const note of result.notes)
|
package/dist/cli.js
CHANGED
|
@@ -142,7 +142,7 @@ import {
|
|
|
142
142
|
} from "./cli-0v6na3yp.js";
|
|
143
143
|
|
|
144
144
|
// src/cli.ts
|
|
145
|
-
import { randomUUID as
|
|
145
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
146
146
|
import { z } from "zod";
|
|
147
147
|
|
|
148
148
|
// src/trace-findings.ts
|
|
@@ -314,6 +314,17 @@ function batchNotRunWarning(results) {
|
|
|
314
314
|
return [`Families not run (detector off or trace not processed yet), so no detections there does not mean clean: ${notRun.join("; ")}.`];
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
// src/retired-commands.ts
|
|
318
|
+
function retiredCommandError(command, positionals) {
|
|
319
|
+
const fix = command === "fix" || command === "fixes";
|
|
320
|
+
const prompt = command === "prompts" && ["ab", "propose"].includes(positionals[0]);
|
|
321
|
+
if (!fix && !prompt)
|
|
322
|
+
return;
|
|
323
|
+
const name = fix ? command : `prompts ${positionals[0]}`;
|
|
324
|
+
const commands = fix ? ["moda problems", "moda problems get <problem_id> --evidence", "moda skills install moda-cli"] : ["moda prompts status", "moda prompts diff", "moda prompts sync --dry-run", "moda skills install moda-cli"];
|
|
325
|
+
return new CliInputError(`The 'moda ${name}' command was retired. Nothing was run. ` + (fix ? "Moda supplies production evidence; engineers make changes in their own code, prompts, tools or skills." : "The old automatic prompt proposal and A/B workflow was retired. Review and edit prompt changes in your project."), `Use ${commands.map((value) => `\`${value}\``).join(", ")}. ` + "Refresh the installed guide and the Moda-managed AGENTS.md block with `moda skills install moda-cli`. " + "Review any user-authored instructions that still reference the retired workflow.", "retired_command", { field: "<command>", suggestedCommands: commands });
|
|
326
|
+
}
|
|
327
|
+
|
|
317
328
|
// src/telemetry.ts
|
|
318
329
|
import { spawn as spawn2 } from "node:child_process";
|
|
319
330
|
|
|
@@ -444,7 +455,7 @@ function appendLines(path, text) {
|
|
|
444
455
|
function sleepSync(ms) {
|
|
445
456
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
446
457
|
}
|
|
447
|
-
function createLockFile(path, staleMs, now) {
|
|
458
|
+
function createLockFile(path, staleMs, now, onError) {
|
|
448
459
|
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
449
460
|
try {
|
|
450
461
|
const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | O_NOFOLLOW, 384);
|
|
@@ -455,13 +466,18 @@ function createLockFile(path, staleMs, now) {
|
|
|
455
466
|
}
|
|
456
467
|
return true;
|
|
457
468
|
} catch (error) {
|
|
458
|
-
if (error.code !== "EEXIST")
|
|
469
|
+
if (error.code !== "EEXIST") {
|
|
470
|
+
onError?.(error);
|
|
459
471
|
return false;
|
|
472
|
+
}
|
|
460
473
|
try {
|
|
461
474
|
if (now - statSync(path).mtimeMs < staleMs)
|
|
462
475
|
return false;
|
|
463
476
|
unlinkSync(path);
|
|
464
|
-
} catch {
|
|
477
|
+
} catch (error2) {
|
|
478
|
+
if (error2.code !== "ENOENT")
|
|
479
|
+
onError?.(error2);
|
|
480
|
+
}
|
|
465
481
|
}
|
|
466
482
|
}
|
|
467
483
|
return false;
|
|
@@ -542,10 +558,10 @@ function flushDue(entries, group, now) {
|
|
|
542
558
|
}
|
|
543
559
|
return false;
|
|
544
560
|
}
|
|
545
|
-
function acquireFlushLock(dir = telemetrySpoolDir(), now = Date.now()) {
|
|
561
|
+
function acquireFlushLock(dir = telemetrySpoolDir(), now = Date.now(), onError) {
|
|
546
562
|
ensureDir(dir);
|
|
547
563
|
const path = join(dir, LOCK_FILE);
|
|
548
|
-
return createLockFile(path, FLUSH_LOCK_STALE_MS, now) ? path : undefined;
|
|
564
|
+
return createLockFile(path, FLUSH_LOCK_STALE_MS, now, onError) ? path : undefined;
|
|
549
565
|
}
|
|
550
566
|
function releaseFlushLock(path) {
|
|
551
567
|
if (!path)
|
|
@@ -976,7 +992,8 @@ async function sendCliUsageTelemetry(input) {
|
|
|
976
992
|
}
|
|
977
993
|
|
|
978
994
|
// src/update-check.ts
|
|
979
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
995
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync } from "node:fs";
|
|
996
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
980
997
|
import { join as join2 } from "node:path";
|
|
981
998
|
var UPDATE_CHECK_FLAG = "no-update-check";
|
|
982
999
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -1091,12 +1108,59 @@ function getUpdateNotice(options = {}) {
|
|
|
1091
1108
|
].join(`
|
|
1092
1109
|
`);
|
|
1093
1110
|
}
|
|
1094
|
-
function printUpdateNotice(options = {}) {
|
|
1095
|
-
if (!process.stderr.isTTY)
|
|
1111
|
+
function printUpdateNotice(options = {}, mode = {}, now) {
|
|
1112
|
+
if (mode.quiet || !process.stderr.isTTY && mode.outputMode !== "json")
|
|
1096
1113
|
return;
|
|
1097
|
-
|
|
1098
|
-
|
|
1114
|
+
let lock;
|
|
1115
|
+
try {
|
|
1116
|
+
const notice = getUpdateNotice(options);
|
|
1117
|
+
if (!notice)
|
|
1118
|
+
return;
|
|
1119
|
+
if (mode.outputMode === "json") {
|
|
1120
|
+
let lockFailed = false;
|
|
1121
|
+
try {
|
|
1122
|
+
lock = acquireFlushLock(join2(getModaDir(options), "update-notice-lock"), now ?? Date.now(), () => {
|
|
1123
|
+
lockFailed = true;
|
|
1124
|
+
});
|
|
1125
|
+
} catch {
|
|
1126
|
+
lockFailed = true;
|
|
1127
|
+
}
|
|
1128
|
+
if (!lock) {
|
|
1129
|
+
if (lockFailed)
|
|
1130
|
+
console.error(notice);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const noticeNow = now ?? Date.now();
|
|
1134
|
+
const path = join2(getModaDir(options), "update-notice.json");
|
|
1135
|
+
const version = `${VERSION}:${getAvailableUpdate(options)}`;
|
|
1136
|
+
const notices = {};
|
|
1137
|
+
try {
|
|
1138
|
+
const previous = JSON.parse(readFileSync2(path, "utf8"));
|
|
1139
|
+
if (previous?.notices && typeof previous.notices === "object") {
|
|
1140
|
+
for (const [pair, timestamp] of Object.entries(previous.notices)) {
|
|
1141
|
+
if (typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp <= noticeNow && noticeNow - timestamp < 60 * 60 * 1000)
|
|
1142
|
+
notices[pair] = timestamp;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (notices[version] !== undefined)
|
|
1146
|
+
return;
|
|
1147
|
+
} catch {}
|
|
1148
|
+
const temporary = `${path}.${randomUUID3()}.tmp`;
|
|
1149
|
+
try {
|
|
1150
|
+
mkdirSync2(getModaDir(options), { recursive: true });
|
|
1151
|
+
writeFileSync(temporary, JSON.stringify({ notices: { ...notices, [version]: noticeNow } }) + `
|
|
1152
|
+
`, { flag: "wx", mode: 384 });
|
|
1153
|
+
renameSync2(temporary, path);
|
|
1154
|
+
} catch {} finally {
|
|
1155
|
+
try {
|
|
1156
|
+
rmSync2(temporary, { force: true });
|
|
1157
|
+
} catch {}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1099
1160
|
console.error(notice);
|
|
1161
|
+
} catch {} finally {
|
|
1162
|
+
releaseFlushLock(lock);
|
|
1163
|
+
}
|
|
1100
1164
|
}
|
|
1101
1165
|
// src/command-tree.ts
|
|
1102
1166
|
var CANONICAL_LEAVES = [
|
|
@@ -3005,13 +3069,13 @@ async function runPromptsCommand(...args) {
|
|
|
3005
3069
|
return (await import("./prompts-x3vdeax6.js")).runPromptsCommand(...args);
|
|
3006
3070
|
}
|
|
3007
3071
|
async function runSkillsCommand(...args) {
|
|
3008
|
-
return (await import("./skills-
|
|
3072
|
+
return (await import("./skills-9c1xvp2s.js")).runSkillsCommand(...args);
|
|
3009
3073
|
}
|
|
3010
3074
|
async function runHarnessCommand(...args) {
|
|
3011
3075
|
return (await import("./harness-jka07f99.js")).runHarnessCommand(...args);
|
|
3012
3076
|
}
|
|
3013
3077
|
async function runSyncCommand(...args) {
|
|
3014
|
-
return (await import("./sync-
|
|
3078
|
+
return (await import("./sync-skzdw9fb.js")).runSyncCommand(...args);
|
|
3015
3079
|
}
|
|
3016
3080
|
async function runRegistryCommand(...args) {
|
|
3017
3081
|
return (await import("./registry-4mhr7xsd.js")).runRegistryCommand(...args);
|
|
@@ -6368,7 +6432,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
6368
6432
|
}
|
|
6369
6433
|
const data = await callDataAPI(reviewPath, {
|
|
6370
6434
|
method: "POST",
|
|
6371
|
-
body: JSON.stringify({ ...reviewBody, request_nonce:
|
|
6435
|
+
body: JSON.stringify({ ...reviewBody, request_nonce: randomUUID4() }),
|
|
6372
6436
|
headers: { "Content-Type": "application/json" }
|
|
6373
6437
|
});
|
|
6374
6438
|
context.output.writeData(data);
|
|
@@ -6461,7 +6525,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
6461
6525
|
});
|
|
6462
6526
|
break;
|
|
6463
6527
|
}
|
|
6464
|
-
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce:
|
|
6528
|
+
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce: randomUUID4() } : request.body;
|
|
6465
6529
|
const data = await callDataAPI(request.path, {
|
|
6466
6530
|
method: request.method,
|
|
6467
6531
|
...sentBody ? { body: JSON.stringify(sentBody), headers: { "Content-Type": "application/json" } } : {}
|
|
@@ -6796,7 +6860,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
6796
6860
|
resetApiRequestCountBeforeRun: true,
|
|
6797
6861
|
telemetry: "result-and-error",
|
|
6798
6862
|
handler: async (context) => {
|
|
6799
|
-
const { runInit } = await import("./index-
|
|
6863
|
+
const { runInit } = await import("./index-wdttmhsg.js");
|
|
6800
6864
|
if (context.outputMode === "agent-stream") {
|
|
6801
6865
|
context.output.writeEvent({
|
|
6802
6866
|
event: "started",
|
|
@@ -7945,6 +8009,12 @@ async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
|
|
|
7945
8009
|
try {
|
|
7946
8010
|
return await dispatchParsedCommandInner(parsed, startedAt);
|
|
7947
8011
|
} finally {
|
|
8012
|
+
if (!isUpdateCheckDisabled(parsed.flags) && parsed.command && !["--version", "-V", "--help", "-h"].includes(parsed.command) && !optOutFlagSet(parsed.flags, "help") && !optOutFlagSet(parsed.flags, "version") && !optOutFlagSet(parsed.flags, "json-schemas")) {
|
|
8013
|
+
printUpdateNotice({}, {
|
|
8014
|
+
outputMode: resolveOutputMode(parsed.flags, { isTTY: process.stdout.isTTY, env: process.env }),
|
|
8015
|
+
quiet: optOutFlagSet(parsed.flags, "quiet")
|
|
8016
|
+
});
|
|
8017
|
+
}
|
|
7948
8018
|
if (previousProfileEnv === undefined)
|
|
7949
8019
|
delete process.env.MODA_PROFILE;
|
|
7950
8020
|
else
|
|
@@ -8100,6 +8170,15 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
8100
8170
|
console.log(`moda-cli ${VERSION}`);
|
|
8101
8171
|
return 0;
|
|
8102
8172
|
}
|
|
8173
|
+
const retired = retiredCommandError(command, typedParsed.positionals);
|
|
8174
|
+
if (retired) {
|
|
8175
|
+
const mode = resolveOutputMode(typedFlags, { isTTY: process.stdout.isTTY, env: process.env });
|
|
8176
|
+
if (mode === "agent" || mode === "agent-stream" || mode === "json")
|
|
8177
|
+
printAgentCliError(command, retired, mode);
|
|
8178
|
+
else
|
|
8179
|
+
printCliError(retired);
|
|
8180
|
+
return 1;
|
|
8181
|
+
}
|
|
8103
8182
|
if (command === "--help" || command === "-h" || flags.help === "true") {
|
|
8104
8183
|
if (command && printTreeHelp(typedParsed))
|
|
8105
8184
|
return 0;
|
|
@@ -8330,9 +8409,6 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
8330
8409
|
recorded: context.getTelemetryMeta()
|
|
8331
8410
|
});
|
|
8332
8411
|
}
|
|
8333
|
-
if (!updateCheckDisabled) {
|
|
8334
|
-
printUpdateNotice();
|
|
8335
|
-
}
|
|
8336
8412
|
printFeedbackTip(definition.name, context);
|
|
8337
8413
|
return finishRun(context, { kind: "completed", exitCode: result.exitCode });
|
|
8338
8414
|
} catch (thrown) {
|
|
@@ -8354,9 +8430,6 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
8354
8430
|
} else {
|
|
8355
8431
|
printCliError(error);
|
|
8356
8432
|
}
|
|
8357
|
-
if (!updateCheckDisabled) {
|
|
8358
|
-
printUpdateNotice();
|
|
8359
|
-
}
|
|
8360
8433
|
context.output.close();
|
|
8361
8434
|
return exitCodeForError(error);
|
|
8362
8435
|
}
|
package/package.json
CHANGED