@mutmutco/cli 2.28.1 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.cjs +35 -12
- package/dist/main.cjs +7844 -6274
- package/dist/saga.cjs +167 -85
- package/package.json +2 -2
package/dist/saga.cjs
CHANGED
|
@@ -3411,18 +3411,28 @@ var import_node_path2 = require("node:path");
|
|
|
3411
3411
|
var CLIENT_VERSION_HEADER = "x-client-version";
|
|
3412
3412
|
|
|
3413
3413
|
// src/client-version.ts
|
|
3414
|
-
function
|
|
3414
|
+
function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
3415
|
+
return [
|
|
3416
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
|
|
3417
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".cursor-plugin", "plugin.json"),
|
|
3418
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".codex-plugin", "plugin.json"),
|
|
3419
|
+
(0, import_node_path2.join)(distDir, "..", "package.json")
|
|
3420
|
+
];
|
|
3421
|
+
}
|
|
3422
|
+
function readVersionFromManifest(path2) {
|
|
3415
3423
|
try {
|
|
3416
|
-
const
|
|
3417
|
-
return
|
|
3424
|
+
const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
|
|
3425
|
+
return typeof version === "string" && version.trim() ? version.trim() : null;
|
|
3418
3426
|
} catch {
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
function resolveClientVersion() {
|
|
3431
|
+
for (const manifest of resolveClientVersionManifestCandidates()) {
|
|
3432
|
+
const version = readVersionFromManifest(manifest);
|
|
3433
|
+
if (version) return version;
|
|
3425
3434
|
}
|
|
3435
|
+
return "0.0.0";
|
|
3426
3436
|
}
|
|
3427
3437
|
function clientVersionHeaders() {
|
|
3428
3438
|
return { [CLIENT_VERSION_HEADER]: resolveClientVersion() };
|
|
@@ -3430,6 +3440,26 @@ function clientVersionHeaders() {
|
|
|
3430
3440
|
|
|
3431
3441
|
// src/saga-commands.ts
|
|
3432
3442
|
var import_node_crypto3 = require("node:crypto");
|
|
3443
|
+
var import_promises2 = require("node:fs/promises");
|
|
3444
|
+
|
|
3445
|
+
// src/issue-body.ts
|
|
3446
|
+
async function resolveTextArg(input, deps, labels) {
|
|
3447
|
+
const hasValue = input.value !== void 0;
|
|
3448
|
+
const hasFile = input.file !== void 0;
|
|
3449
|
+
if (hasValue && hasFile) {
|
|
3450
|
+
throw new Error(`pass only one of ${labels.value} or ${labels.file}`);
|
|
3451
|
+
}
|
|
3452
|
+
if (!hasValue && !hasFile) {
|
|
3453
|
+
throw new Error(`pass ${labels.value} or ${labels.file}`);
|
|
3454
|
+
}
|
|
3455
|
+
if (hasValue) return input.value ?? "";
|
|
3456
|
+
const source = input.file ?? "";
|
|
3457
|
+
const text = source === "-" ? await deps.readStdin() : await deps.readFile(source, "utf8");
|
|
3458
|
+
if (text.trim().length === 0) {
|
|
3459
|
+
throw new Error(`${labels.file} produced an empty ${labels.noun}`);
|
|
3460
|
+
}
|
|
3461
|
+
return text;
|
|
3462
|
+
}
|
|
3433
3463
|
|
|
3434
3464
|
// src/saga-capture.ts
|
|
3435
3465
|
function parseHookInput(stdin) {
|
|
@@ -3566,6 +3596,7 @@ async function runHeadEngine(prompt, timeoutMs = HEAD_ENGINE_TIMEOUT_MS) {
|
|
|
3566
3596
|
|
|
3567
3597
|
// src/saga-health.ts
|
|
3568
3598
|
var MEMORY_STALE_DAYS = 14;
|
|
3599
|
+
var SESSION_START_LIVENESS = { attempts: 1, timeoutMs: 3e3 };
|
|
3569
3600
|
function buildHealth(i) {
|
|
3570
3601
|
const problems = [];
|
|
3571
3602
|
if (!i.sagaApiUrl) problems.push("Hub API URL not configured");
|
|
@@ -3591,6 +3622,7 @@ function buildHealth(i) {
|
|
|
3591
3622
|
authorized: i.authorized,
|
|
3592
3623
|
sagaApiUrl: i.sagaApiUrl,
|
|
3593
3624
|
pendingNotes: i.pendingNotes ?? 0,
|
|
3625
|
+
honchoPending: i.honchoPending ?? 0,
|
|
3594
3626
|
key: i.key,
|
|
3595
3627
|
source: i.source,
|
|
3596
3628
|
problems,
|
|
@@ -3607,73 +3639,14 @@ function healthBanner(report) {
|
|
|
3607
3639
|
if (report.warnings.length) return `saga health: NOTE - ${report.warnings.join("; ")}`;
|
|
3608
3640
|
return null;
|
|
3609
3641
|
}
|
|
3610
|
-
function
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
|
|
3619
|
-
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
3620
|
-
let lastErr;
|
|
3621
|
-
for (let i = 0; i < attempts; i++) {
|
|
3622
|
-
const isLast = i === attempts - 1;
|
|
3623
|
-
const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
|
|
3624
|
-
try {
|
|
3625
|
-
const res = await fetchImpl(url, attemptInit);
|
|
3626
|
-
if (!isLast && retryOn(res)) {
|
|
3627
|
-
await sleep(baseDelayMs * 2 ** i);
|
|
3628
|
-
continue;
|
|
3629
|
-
}
|
|
3630
|
-
return res;
|
|
3631
|
-
} catch (e) {
|
|
3632
|
-
lastErr = e;
|
|
3633
|
-
if (isLast) throw e;
|
|
3634
|
-
await sleep(baseDelayMs * 2 ** i);
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
throw lastErr;
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
// src/saga-note.ts
|
|
3641
|
-
var AGENT_SURFACE_TOKENS = ["claude", "codex", "cursor", "gemini"];
|
|
3642
|
-
var ROUTE_LEVEL_403 = "saga API route-level 403 from HubSessionAuthorizer/session policy";
|
|
3643
|
-
function agentSurface(env = process.env) {
|
|
3644
|
-
const surface = env.MMI_AGENT_SURFACE?.trim() || (env.CODEX_THREAD_ID?.trim() && !env.CLAUDE_SESSION_ID?.trim() ? "codex" : "claude");
|
|
3645
|
-
if (AGENT_SURFACE_TOKENS.includes(surface)) return surface;
|
|
3646
|
-
throw new Error(`MMI_AGENT_SURFACE must be one of: ${AGENT_SURFACE_TOKENS.join(", ")}`);
|
|
3647
|
-
}
|
|
3648
|
-
function buildNoteCapture(summary, o, id, evidence) {
|
|
3649
|
-
const queueOp = o.queueAdd ? { op: "add", text: o.queueAdd } : o.queueDone != null ? { op: "done", index: Number(o.queueDone) } : void 0;
|
|
3650
|
-
const state = o.diagnostic ? "diagnostic" : o.verified ? "verified" : "asserted";
|
|
3651
|
-
const source = o.diagnostic ? "probe" : "note";
|
|
3652
|
-
const ev = {};
|
|
3653
|
-
if (evidence.sha) ev.sha = evidence.sha;
|
|
3654
|
-
if (evidence.branch) ev.branch = evidence.branch;
|
|
3655
|
-
if (evidence.pr) ev.pr = evidence.pr;
|
|
3656
|
-
if (evidence.file) ev.file = evidence.file;
|
|
3657
|
-
const anchor = o.anchor ? { intent: o.anchor, setAt: (/* @__PURE__ */ new Date()).toISOString() } : void 0;
|
|
3658
|
-
return {
|
|
3659
|
-
event: "note",
|
|
3660
|
-
id,
|
|
3661
|
-
summary,
|
|
3662
|
-
next: o.next,
|
|
3663
|
-
decision: o.decision,
|
|
3664
|
-
queueOp,
|
|
3665
|
-
state,
|
|
3666
|
-
source,
|
|
3667
|
-
evidence: Object.keys(ev).length ? ev : void 0,
|
|
3668
|
-
surface: agentSurface(),
|
|
3669
|
-
supersedes: o.supersedes,
|
|
3670
|
-
anchor,
|
|
3671
|
-
anchorForce: o.anchorForce || void 0
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
3674
|
-
function formatCaptureFailure(status, message) {
|
|
3675
|
-
if (status === 403 && message === "Forbidden") return `saga: ${ROUTE_LEVEL_403} (HTTP 403)`;
|
|
3676
|
-
return `saga: HTTP ${status}`;
|
|
3642
|
+
function memorySyncBanner(report) {
|
|
3643
|
+
const saga = report.pendingNotes;
|
|
3644
|
+
const honcho = report.honchoPending;
|
|
3645
|
+
if (saga <= 0 && honcho <= 0) return null;
|
|
3646
|
+
const parts = [];
|
|
3647
|
+
if (saga > 0) parts.push(`${saga} saga`);
|
|
3648
|
+
if (honcho > 0) parts.push(`${honcho} honcho`);
|
|
3649
|
+
return `MEMORY SYNC \u2014 ${parts.join(" + ")} write(s) queued locally \u2014 run \`mmi-cli saga flush\` / \`honcho flush\` (this device only).`;
|
|
3677
3650
|
}
|
|
3678
3651
|
|
|
3679
3652
|
// src/saga-pending.ts
|
|
@@ -3852,6 +3825,81 @@ async function flushPending(post, dir = ".mmi") {
|
|
|
3852
3825
|
return { flushed, dropped, remaining: readPending(dir).length };
|
|
3853
3826
|
}
|
|
3854
3827
|
|
|
3828
|
+
// src/honcho-pending.ts
|
|
3829
|
+
var HONCHO_QUEUE_DIR = ".mmi/honcho";
|
|
3830
|
+
function readHonchoPending(dir = HONCHO_QUEUE_DIR) {
|
|
3831
|
+
return readPending(dir);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/fetch-retry.ts
|
|
3835
|
+
async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
3836
|
+
const attempts = opts.attempts ?? 3;
|
|
3837
|
+
const baseDelayMs = opts.baseDelayMs ?? 250;
|
|
3838
|
+
const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
|
|
3839
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
3840
|
+
let lastErr;
|
|
3841
|
+
for (let i = 0; i < attempts; i++) {
|
|
3842
|
+
const isLast = i === attempts - 1;
|
|
3843
|
+
const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
|
|
3844
|
+
try {
|
|
3845
|
+
const res = await fetchImpl(url, attemptInit);
|
|
3846
|
+
if (!isLast && retryOn(res)) {
|
|
3847
|
+
await sleep(baseDelayMs * 2 ** i);
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
return res;
|
|
3851
|
+
} catch (e) {
|
|
3852
|
+
lastErr = e;
|
|
3853
|
+
if (isLast) throw e;
|
|
3854
|
+
await sleep(baseDelayMs * 2 ** i);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
throw lastErr;
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
// src/saga-note.ts
|
|
3861
|
+
var AGENT_SURFACE_TOKENS = ["claude", "codex", "cursor", "gemini"];
|
|
3862
|
+
var ROUTE_LEVEL_403 = "saga API route-level 403 from HubSessionAuthorizer/session policy";
|
|
3863
|
+
function agentSurface(env = process.env) {
|
|
3864
|
+
const surface = env.MMI_AGENT_SURFACE?.trim() || (env.CODEX_THREAD_ID?.trim() && !env.CLAUDE_SESSION_ID?.trim() ? "codex" : "claude");
|
|
3865
|
+
if (AGENT_SURFACE_TOKENS.includes(surface)) return surface;
|
|
3866
|
+
throw new Error(`MMI_AGENT_SURFACE must be one of: ${AGENT_SURFACE_TOKENS.join(", ")}`);
|
|
3867
|
+
}
|
|
3868
|
+
function buildNoteCapture(summary, o, id, evidence) {
|
|
3869
|
+
const queueOp = o.queueAdd ? { op: "add", text: o.queueAdd } : o.queueDone != null ? { op: "done", index: Number(o.queueDone) } : void 0;
|
|
3870
|
+
const state = o.diagnostic ? "diagnostic" : o.verified ? "verified" : "asserted";
|
|
3871
|
+
const source = o.diagnostic ? "probe" : "note";
|
|
3872
|
+
const ev = {};
|
|
3873
|
+
if (evidence.sha) ev.sha = evidence.sha;
|
|
3874
|
+
if (evidence.branch) ev.branch = evidence.branch;
|
|
3875
|
+
if (evidence.pr) ev.pr = evidence.pr;
|
|
3876
|
+
if (evidence.file) ev.file = evidence.file;
|
|
3877
|
+
const anchor = o.anchor ? {
|
|
3878
|
+
intent: o.anchor,
|
|
3879
|
+
...o.anchorSlug ? { slug: o.anchorSlug } : {},
|
|
3880
|
+
setAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3881
|
+
} : void 0;
|
|
3882
|
+
return {
|
|
3883
|
+
event: "note",
|
|
3884
|
+
id,
|
|
3885
|
+
summary,
|
|
3886
|
+
next: o.next,
|
|
3887
|
+
decision: o.decision,
|
|
3888
|
+
queueOp,
|
|
3889
|
+
state,
|
|
3890
|
+
source,
|
|
3891
|
+
evidence: Object.keys(ev).length ? ev : void 0,
|
|
3892
|
+
surface: agentSurface(),
|
|
3893
|
+
supersedes: o.supersedes,
|
|
3894
|
+
anchor,
|
|
3895
|
+
anchorForce: o.anchorForce || void 0
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
function formatCaptureFailure(status, message) {
|
|
3899
|
+
if (status === 403 && message === "Forbidden") return `saga: ${ROUTE_LEVEL_403} (HTTP 403)`;
|
|
3900
|
+
return `saga: HTTP ${status}`;
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3855
3903
|
// src/hub-auth.ts
|
|
3856
3904
|
var import_node_crypto = require("node:crypto");
|
|
3857
3905
|
var import_node_fs5 = require("node:fs");
|
|
@@ -4147,6 +4195,13 @@ async function runNote(summary, o) {
|
|
|
4147
4195
|
const capture = buildNoteCapture(summary, o, (0, import_node_crypto3.randomUUID)(), { sha: sha || void 0, branch: key.branch });
|
|
4148
4196
|
await postCapture(capture);
|
|
4149
4197
|
}
|
|
4198
|
+
function resolveSummary(summary, o) {
|
|
4199
|
+
return resolveTextArg({ value: summary, file: o.messageFile }, { readFile: import_promises2.readFile, readStdin }, {
|
|
4200
|
+
value: "a summary argument",
|
|
4201
|
+
file: "--message-file",
|
|
4202
|
+
noun: "message"
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4150
4205
|
async function runSagaFlush(o, io = consoleIo) {
|
|
4151
4206
|
if (o.run) {
|
|
4152
4207
|
try {
|
|
@@ -4181,7 +4236,6 @@ async function runSagaShow(opts, io = consoleIo) {
|
|
|
4181
4236
|
const qs = opts.latestAnywhere ? "scope=anywhere" : new URLSearchParams({ project: key.project, branch: key.branch }).toString();
|
|
4182
4237
|
const res = await fetchWithRetry(fetch, `${cfg.sagaApiUrl}/saga/head?${qs}`, { headers: await hubHeaders() }, { attempts: 2, timeoutMs: 3e3 });
|
|
4183
4238
|
if (res.ok) {
|
|
4184
|
-
io.log(resumeCue());
|
|
4185
4239
|
return io.log(await res.text());
|
|
4186
4240
|
}
|
|
4187
4241
|
if (!opts.quiet) io.log(`saga show failed: HTTP ${res.status}`);
|
|
@@ -4192,9 +4246,9 @@ async function runSagaShow(opts, io = consoleIo) {
|
|
|
4192
4246
|
}
|
|
4193
4247
|
}
|
|
4194
4248
|
}
|
|
4195
|
-
async function probeBackend(url) {
|
|
4249
|
+
async function probeBackend(url, opts = { attempts: 3, timeoutMs: 4e3 }) {
|
|
4196
4250
|
try {
|
|
4197
|
-
const res = await fetchWithRetry(fetch, `${url}/saga/head`, { headers: await hubHeaders() },
|
|
4251
|
+
const res = await fetchWithRetry(fetch, `${url}/saga/head`, { headers: await hubHeaders() }, opts);
|
|
4198
4252
|
let message = "";
|
|
4199
4253
|
try {
|
|
4200
4254
|
const body = await res.clone().json();
|
|
@@ -4233,12 +4287,13 @@ async function runSagaHealth(o, io = consoleIo) {
|
|
|
4233
4287
|
const session = resolveSessionId();
|
|
4234
4288
|
const key = await sagaKey(cfg, session);
|
|
4235
4289
|
const source = session.source;
|
|
4290
|
+
const livenessOpts = o.banner ? SESSION_START_LIVENESS : { attempts: 3, timeoutMs: 4e3 };
|
|
4236
4291
|
const [identity, liveness] = await Promise.all([
|
|
4237
4292
|
hubAuthSession({ baseUrl: cfg.sagaApiUrl ?? defaultHubUrl(), githubToken }).then((s) => s?.login),
|
|
4238
|
-
cfg.sagaApiUrl ? probeBackend(cfg.sagaApiUrl) : Promise.resolve({ reachable: false })
|
|
4293
|
+
cfg.sagaApiUrl ? probeBackend(cfg.sagaApiUrl, livenessOpts) : Promise.resolve({ reachable: false })
|
|
4239
4294
|
]);
|
|
4240
|
-
const authorized = cfg.sagaApiUrl && liveness.reachable ? await probeSagaAccess(cfg.sagaApiUrl, key) : void 0;
|
|
4241
|
-
const memoryAgeDays = cfg.sagaApiUrl && liveness.reachable ? await fetchMemoryAge(cfg.sagaApiUrl, key.project) : void 0;
|
|
4295
|
+
const authorized = o.banner ? void 0 : cfg.sagaApiUrl && liveness.reachable ? await probeSagaAccess(cfg.sagaApiUrl, key) : void 0;
|
|
4296
|
+
const memoryAgeDays = o.banner ? void 0 : cfg.sagaApiUrl && liveness.reachable ? await fetchMemoryAge(cfg.sagaApiUrl, key.project) : void 0;
|
|
4242
4297
|
const report = buildHealth({
|
|
4243
4298
|
key,
|
|
4244
4299
|
source,
|
|
@@ -4249,12 +4304,15 @@ async function runSagaHealth(o, io = consoleIo) {
|
|
|
4249
4304
|
authorized,
|
|
4250
4305
|
sagaApiUrl: cfg.sagaApiUrl,
|
|
4251
4306
|
pendingNotes: readPending().length,
|
|
4307
|
+
honchoPending: readHonchoPending().length,
|
|
4252
4308
|
memoryAgeDays
|
|
4253
4309
|
});
|
|
4254
4310
|
if (o.json) return io.log(JSON.stringify(report));
|
|
4255
4311
|
if (o.banner) {
|
|
4256
4312
|
const banner = healthBanner(report);
|
|
4257
4313
|
if (banner) io.log(banner);
|
|
4314
|
+
const sync = memorySyncBanner(report);
|
|
4315
|
+
if (sync) io.log(sync);
|
|
4258
4316
|
return;
|
|
4259
4317
|
}
|
|
4260
4318
|
if (o.quiet) return;
|
|
@@ -4265,8 +4323,24 @@ async function runSagaHealth(o, io = consoleIo) {
|
|
|
4265
4323
|
}
|
|
4266
4324
|
function registerSagaCommands(program2) {
|
|
4267
4325
|
const saga = program2.command("saga").description("per-session continuity");
|
|
4268
|
-
saga.command("note
|
|
4269
|
-
|
|
4326
|
+
saga.command("note [summary]").description("record a one-line structured note into your saga (the per-turn capture)").option("--next <text>", 'set "where I left off" (NEXT)').option("--decision <text>", "append a verbatim decision").option("--queue-add <text>", "add a worklist item").option("--queue-done <n>", "mark worklist item N done").option("--verified", "mark this claim as checked against source (state: verified, else asserted)").option("--diagnostic", "isolate a probe write (state: diagnostic, source: probe) \u2014 never resume/LAST 5").option("--supersedes <key>", "retire prior decisions matching an evidence key (pr:N | file:path)").option("--anchor <intent>", "set the sprint North-Star (write-protected; needs --anchor-force to change)").option("--anchor-slug <slug>", "bind the anchor to a North Star plan slug (SSOT at plans/.../<slug>.md)").option("--anchor-force", "overwrite an existing anchor").option("--message-file <path|->", "read the summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").action(async (summary, o) => {
|
|
4327
|
+
let text;
|
|
4328
|
+
try {
|
|
4329
|
+
text = await resolveSummary(summary, o);
|
|
4330
|
+
} catch (e) {
|
|
4331
|
+
return fail(`saga note: ${e.message}`);
|
|
4332
|
+
}
|
|
4333
|
+
await runNote(text, o);
|
|
4334
|
+
});
|
|
4335
|
+
saga.command("probe [summary]").description("record a diagnostic probe note (alias for `saga note --diagnostic`)").option("--next <text>", 'set "where I left off" (NEXT)').option("--decision <text>", "append a verbatim decision").option("--queue-add <text>", "add a worklist item").option("--queue-done <n>", "mark worklist item N done").option("--message-file <path|->", "read the summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").action(async (summary, o) => {
|
|
4336
|
+
let text;
|
|
4337
|
+
try {
|
|
4338
|
+
text = await resolveSummary(summary, o);
|
|
4339
|
+
} catch (e) {
|
|
4340
|
+
return fail(`saga probe: ${e.message}`);
|
|
4341
|
+
}
|
|
4342
|
+
await runNote(text, { ...o, diagnostic: true });
|
|
4343
|
+
});
|
|
4270
4344
|
saga.command("flush").option("--json", "machine-readable {flushed, dropped, remaining}").option("--run", "detached worker: drain the queue silently (spawned by note/capture)").description("roll the local pending-note queue forward (re-POST queued saga writes); reports what landed").action((o) => runSagaFlush(o));
|
|
4271
4345
|
saga.command("show").option("--quiet", "no-op silently when unconfigured/unreachable (SessionStart hook)").option("--latest-anywhere", "resume the newest saga across all repos (default: current repo)").description("print your resume block \u2014 current repo HEAD + project memory (where you left off)").action((opts) => runSagaShow(opts));
|
|
4272
4346
|
saga.command("capture").option("--quiet", "capture silently (for the Stop hook)").description("per-turn deterministic capture (Stop hook): turn boundary + current sha + gated HEAD-update").action(async (opts) => {
|
|
@@ -4380,8 +4454,16 @@ function socketPath(env = process.env, platform = process.platform, user) {
|
|
|
4380
4454
|
return platform === "win32" ? `\\\\.\\pipe\\mmi-cli-${hash}` : (0, import_node_path6.join)(daemonDir(env), `mmi-cli-${hash}.sock`);
|
|
4381
4455
|
}
|
|
4382
4456
|
var HOT_VERBS = /* @__PURE__ */ new Set(["note", "probe", "capture", "session", "head-update"]);
|
|
4457
|
+
function argvReadsStdin(args) {
|
|
4458
|
+
for (let i = 0; i < args.length; i++) {
|
|
4459
|
+
const a = args[i];
|
|
4460
|
+
if (a.endsWith("-file=-")) return true;
|
|
4461
|
+
if (a.endsWith("-file") && args[i + 1] === "-") return true;
|
|
4462
|
+
}
|
|
4463
|
+
return false;
|
|
4464
|
+
}
|
|
4383
4465
|
function daemonEligible(args) {
|
|
4384
|
-
return args[0] === "saga" && HOT_VERBS.has(args[1] ?? "") && !args.includes("--run") && !args.includes("--help") && !args.includes("-h");
|
|
4466
|
+
return args[0] === "saga" && HOT_VERBS.has(args[1] ?? "") && !args.includes("--run") && !args.includes("--help") && !args.includes("-h") && !argvReadsStdin(args);
|
|
4385
4467
|
}
|
|
4386
4468
|
function buildStamp(version, bundleMtimeMs) {
|
|
4387
4469
|
return `${version}#${Math.trunc(bundleMtimeMs)}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^22.0.0",
|
|
42
|
-
"esbuild": "^0.28.
|
|
42
|
+
"esbuild": "^0.28.1",
|
|
43
43
|
"typescript": "^5.7.0",
|
|
44
44
|
"vitest": "^4.1.0"
|
|
45
45
|
}
|