mulmoterminal 1.7.1 → 1.7.2
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/bin/mulmoterminal.js +22 -34
- package/bin/npx-cache-hint.d.ts +2 -0
- package/bin/npx-cache-hint.js +69 -0
- package/bin/wait-ready.d.ts +26 -0
- package/bin/wait-ready.js +61 -0
- package/common/modelPresets.ts +11 -6
- package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-0ZB3Q7Mv.js → abnfDiagram-VRR7QNED-RjiYivmv-CCLjG9E9.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-Dc62_ljl.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-BGEQ26kW.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-BcngES-G.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-CfG6VjIt.js → blockDiagram-677ZJIJ3-BPuAJQRW-K8X4YdbW.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-5Y3QAEOO.js → c4Diagram-LMCZKHZV-C0LAqQso-DPvBKT_o.js} +1 -1
- package/dist/assets/channel-7wUqSdoX-NCtPilPL.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-LKWk21XQ.js → chunk-32BRIVSS-BRrYpgtb-Sgw4YiwO.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-DQ0c_tel.js → chunk-52WLFC77-BJ-ss3Xr-DXTsqQmw.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-D1ScG-A9.js → chunk-C7G6YPKG-BZEucKEL-CzDWUuQD.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-CM3AIHWh.js → chunk-EX3LRPZG-DLS6FBN1-BZ8245YL.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-C7tdhIeD.js → chunk-FWX5IMBZ-DHLSFw1H-rWT4wsYF.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Cz_lCkPW.js → chunk-HOUHSVGY-Bhlt8hXJ-DHHJ39yY.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-BiaXcLCM.js → chunk-ICXQ74PX-DwgHBX_g-CVO4XUCO.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-DynKnij8.js → chunk-MOJQB5TN-CMZRaeqt-CfjRj6ge.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BHp0IyoO.js → chunk-OGEWGWER-8Qy4a8b5-Cs1ErBxS.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-CgXHo6E0.js → chunk-PUDLZKDR-DcrWQRYh-DDlhQIXq.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-BEB0ANC3.js → chunk-Q4XR5HBZ-ZXVGkG8Z-DtPCWcx0.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-Ct5XDuM1.js → chunk-V7JOEXUC-DmGdheTX-FwuThvxD.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-BkjFKKFo.js → chunk-VAUOI2AC-CS9QJ4yz-rBHFym4n.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Cvguj9hE.js → chunk-VR4S4FIN-C6a91eNY-6Y9hr75K.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-Dm5-lJPj.js → chunk-WYO6CB5R-BlzOfotS-Cg8JP_k-.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-hzHMTH8r.js → chunk-ZGVPDNZ5-BYwxNFTK-BvTVX4I4.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-D1ky24Ds.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-D1ky24Ds.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-ORm6k8ze.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--9Vfx1RL5.js → cynefinDiagram-TSTJHNR4-DHA9iPo--Coj5CuZV.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-DVMiDNUa.js → dagre-VKFMJZFB--oJKqXBZ-iQ8NhR-E.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-Dw8uXi5J.js → diagram-FQU43EPY-CcJCB9bG-CZBlndUC.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BiirKYwU.js → diagram-G47NLZAW-C_o-WGG1-CezpEy0i.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CxJ-W_AV.js → diagram-NH7WQ7WH-CXJCYvY--DmwB4xPU.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-C66U-WpA.js → diagram-OA4YK3LP-BMzeJ87A-KO3buG9g.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-D_93NKqo-C5wgOP3m.js → diagram-WEI45ONY-D_93NKqo-epWpVF6L.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-XxRZOagG.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-CluKhaw-.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-C3SFUgS_.js → erDiagram-Q63AITRT-CiABnA0s-DuCAxim-.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a--puF0Mfa.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-Cr4R3boa.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-CZAkzl2_.js → ganttDiagram-NO4QXBWP-DQZvdFo1-9pklplap.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-Catx3rAM.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CXNXoPSF.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-CWEyMeWe.js} +1 -1
- package/dist/assets/index-CxXEEdyv.css +1 -0
- package/dist/assets/{index-C-I-KZAf.js → index-Dh4imzyE.js} +184 -184
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-C6Q2N8lR.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-GVMnr0TB.js → infoDiagram-FWYZ7A6U-7UnoB5AP-MGxGOrtx.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-BmKgPmF9.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-Bx_aHmxj.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-BOoOb2Up.js → journeyDiagram-5HDEW3XC-c5xIah9o-DoZ1k7c2.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-CV1DfLpD.js → kanban-definition-HUTT4EX6-Cnt6loYD-D_jZUsQX.js} +1 -1
- package/dist/assets/{lib-BfLKLNAW.js → lib-BuOUsCeD.js} +1 -1
- package/dist/assets/{line-D7ziSjKi-qjBgm7Wg.js → line-D7ziSjKi-BHXiLBOo.js} +1 -1
- package/dist/assets/{marp-BPQ2jYa4.js → marp-gvb94kjy.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DxEa8E3F-bFNCoAMV.js → mermaid-parser.core-DxEa8E3F-kLn0LOBf.js} +2 -2
- package/dist/assets/{mermaid.core-V0OYwIz3-bDvzm251.js → mermaid.core-V0OYwIz3-C-sR2rUO.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-BRp2Mb--.js → mindmap-definition-LN4V7U3C-CexN3O6L-C2mzLUhV.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-C5aIMsHg.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW--kWTOoR2.js → pegDiagram-2B236MQR-BiM4G0cW-ilTx6ElC.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-DLGaOWQD.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-BOFV4ngY.js → pieDiagram-ENE6RG2P-B_fBBS-2-WTDOFCHf.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-jyaO71dB.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-2XE8FR0c.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-XEiHFJv1.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CHJoHZk-.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-93zoonP0.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-Op85BMOm.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CX7O2hYb.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-D8DkHqgs.js → railroadDiagram-RFXS5EU6--lNIlG64-BUzUIavz.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-d5Jr6_2i.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-rbwe3PPS.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-VGEBaos9.js → sankeyDiagram-HTMAVEWB-DlutaXDv-DygoObNU.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-hpEall7s.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-B0ncAj6o.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-CDm7zgbz.js → stateDiagram-2N3HPSRC-jf_dXUEU-RKHr1CUK.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CvRqyCfT.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-Bn3Yx5s4.js → swimlanes-5IMT3BWC-CYjtALQH-xIOTAMQY.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-JHgNIgBl.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CtWavdRU.js → timeline-definition-FHXFAJF6-DYJ4oUm8-CGt2mnFM.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-8FYcTJ8G.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DLUudxda.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-D-tozz5H.js → vennDiagram-L72KCM5P-CdKAHoek-DbQdfDPm.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-BnWGyxAv.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-Bd3huzRu.js → wardleyDiagram-EHGQE667-BF6c4_CW-CE26h6BQ.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-PiOfLnci.js → xychartDiagram-FW5EYKEG-CGiKngj7-DqMbN7gq.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +3 -3
- package/server/backends/byte-range.ts +18 -8
- package/server/backends/collections.ts +7 -1
- package/server/backends/files.ts +14 -9
- package/server/backends/html.ts +2 -2
- package/server/backends/image-gen.ts +13 -2
- package/server/backends/mutateStatus.ts +9 -0
- package/server/backends/remoteHost/attachment-path.ts +4 -1
- package/server/backends/remoteHost/attachmentStore.spec.ts +14 -1
- package/server/backends/remoteHost/attachmentStore.ts +3 -1
- package/server/backends/remoteHost/getFeed.spec.ts +1 -1
- package/server/backends/remoteHost/handlers.spec.ts +69 -4
- package/server/backends/remoteHost/handlers.ts +18 -5
- package/server/backends/remoteHost/ingestAttachments.spec.ts +11 -7
- package/server/backends/remoteHost/ingestAttachments.ts +29 -19
- package/server/backends/remoteHost/skills.ts +4 -2
- package/server/backends/remoteHost/stagedStorageIds.ts +5 -2
- package/server/backends/streamFile.ts +26 -0
- package/server/backends/worklog.ts +3 -3
- package/server/config/config-schema.ts +6 -1
- package/server/config/header-context.ts +10 -3
- package/server/git/pr-for-branch.ts +10 -5
- package/server/git/spawn-collect.ts +18 -9
- package/server/git/worktree-diff.ts +16 -3
- package/server/git/worktree-pr.ts +17 -1
- package/server/git/worktrees.ts +21 -5
- package/server/infra/plugins-registry.ts +6 -2
- package/server/infra/tmux-routes.ts +17 -1
- package/server/routes/app-routes.ts +2 -1
- package/server/routes/plugin-narration.ts +4 -2
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-9X3_cRqd.js +0 -1
- package/dist/assets/channel-7wUqSdoX-vfZkO_uL.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-CeGPKz7I.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-CeGPKz7I.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-0kc9OM8m.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-CjeZJqe8.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-B6tkM4yH.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-BnYaIjat.js +0 -1
- package/dist/assets/index-Cue6y3NP.css +0 -1
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-DYTQnnkg.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-TB9IxewR.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-DfFUYw37.js +0 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CfuJM-7B.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-d15bc0qE.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CyjJaPJJ.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CWODrAAC.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-uM99LygW.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-BJkHeYpz.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-Dq42c4u7.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DVQpaIF5.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DpXrJ_Vl.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DS8lznjT.js +0 -1
package/bin/mulmoterminal.js
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import { execSync, spawn } from "node:child_process";
|
|
9
9
|
import { existsSync, statSync } from "node:fs";
|
|
10
|
-
import { get as httpGet } from "node:http";
|
|
11
10
|
import { createRequire } from "node:module";
|
|
12
11
|
import { createServer } from "node:net";
|
|
13
12
|
import { dirname, join, resolve } from "node:path";
|
|
14
13
|
import { createInterface } from "node:readline";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
15
|
import { computeUpdateNotice, isUpdateCheckDisabled } from "./update-check.js";
|
|
16
|
+
import { detectNpxCacheDir, npxCacheHintLines } from "./npx-cache-hint.js";
|
|
17
|
+
import { waitUntilReady } from "./wait-ready.js";
|
|
17
18
|
import {
|
|
18
19
|
chooseCwd,
|
|
19
20
|
parsePortArg,
|
|
@@ -30,10 +31,12 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
30
31
|
const PKG_DIR = join(__dirname, "..");
|
|
31
32
|
const SERVER_ENTRY = join(PKG_DIR, "server", "index.ts");
|
|
32
33
|
const DEFAULT_PORT = 34567;
|
|
33
|
-
const READY_TIMEOUT_MS = 15_000;
|
|
34
34
|
// Server exit code meaning "port taken at bind time" — keep in sync with
|
|
35
35
|
// server/index.ts (PORT_IN_USE_EXIT_CODE).
|
|
36
36
|
const PORT_IN_USE_EXIT_CODE = 75;
|
|
37
|
+
// Only the end of stderr matters for the crash diagnosis; a long-lived server can log
|
|
38
|
+
// arbitrarily much before dying, so the tail is bounded.
|
|
39
|
+
const STDERR_TAIL_MAX_BYTES = 64 * 1024;
|
|
37
40
|
|
|
38
41
|
// Single source of truth: read the version from the shipped package.json so
|
|
39
42
|
// `--version` never drifts from the published version.
|
|
@@ -170,36 +173,6 @@ function isPortFree(port) {
|
|
|
170
173
|
});
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
// Poll the server until it answers, then call onReady; give up after the timeout
|
|
174
|
-
// so the launcher never hangs on a crash loop. Returns a cancel function — a
|
|
175
|
-
// raced/abandoned attempt stops polling so it can't fire a stale banner.
|
|
176
|
-
function waitUntilReady(port, onReady) {
|
|
177
|
-
const startedAt = Date.now();
|
|
178
|
-
let timer = null;
|
|
179
|
-
let cancelled = false;
|
|
180
|
-
const attempt = () => {
|
|
181
|
-
if (cancelled) return;
|
|
182
|
-
const req = httpGet({ host: "127.0.0.1", port, path: "/", timeout: 1000 }, (res) => {
|
|
183
|
-
res.resume();
|
|
184
|
-
if (!cancelled) onReady();
|
|
185
|
-
});
|
|
186
|
-
req.on("error", retry);
|
|
187
|
-
req.on("timeout", () => {
|
|
188
|
-
req.destroy();
|
|
189
|
-
retry();
|
|
190
|
-
});
|
|
191
|
-
};
|
|
192
|
-
const retry = () => {
|
|
193
|
-
if (cancelled || Date.now() - startedAt > READY_TIMEOUT_MS) return;
|
|
194
|
-
timer = setTimeout(attempt, 300);
|
|
195
|
-
};
|
|
196
|
-
attempt();
|
|
197
|
-
return () => {
|
|
198
|
-
cancelled = true;
|
|
199
|
-
if (timer) clearTimeout(timer);
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
176
|
function printReadyBanner(url) {
|
|
204
177
|
const bar = "\x1b[32m" + "─".repeat(48) + "\x1b[0m";
|
|
205
178
|
console.log(`\n${bar}`);
|
|
@@ -272,10 +245,18 @@ async function choosePort(requested, explicit) {
|
|
|
272
245
|
function runServer(port, noOpen, cwd, onChild) {
|
|
273
246
|
return new Promise((resolveExit) => {
|
|
274
247
|
log(`Starting MulmoTerminal on port ${port}...`);
|
|
248
|
+
// stderr is piped (and passed through) so a fatal boot error can be inspected once the
|
|
249
|
+
// child closes — a half-unpacked npx cache entry crashes here with ERR_MODULE_NOT_FOUND,
|
|
250
|
+
// and without reading stderr the launcher cannot tell that from a real bug.
|
|
275
251
|
const server = spawn(process.execPath, ["--import", "tsx", SERVER_ENTRY], {
|
|
276
252
|
cwd: PKG_DIR,
|
|
277
253
|
env: { ...process.env, NODE_ENV: "production", PORT: String(port), CLAUDE_CWD: cwd },
|
|
278
|
-
stdio: "inherit",
|
|
254
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
255
|
+
});
|
|
256
|
+
let stderrTail = "";
|
|
257
|
+
server.stderr.on("data", (chunk) => {
|
|
258
|
+
process.stderr.write(chunk);
|
|
259
|
+
stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_BYTES);
|
|
279
260
|
});
|
|
280
261
|
onChild(server);
|
|
281
262
|
|
|
@@ -292,7 +273,10 @@ function runServer(port, noOpen, cwd, onChild) {
|
|
|
292
273
|
}
|
|
293
274
|
});
|
|
294
275
|
|
|
295
|
-
|
|
276
|
+
// `close`, not `exit`: it fires only once the piped stderr has fully drained, so the
|
|
277
|
+
// whole crash output — including a trailing `_npx/<hash>` line that can arrive after
|
|
278
|
+
// `exit` — is in `stderrTail` before we inspect it.
|
|
279
|
+
server.on("close", (code) => {
|
|
296
280
|
cancelReady();
|
|
297
281
|
// Exit code 75 means this child failed to bind (EADDRINUSE) and never
|
|
298
282
|
// served — always retriable, regardless of what a probe to the port saw
|
|
@@ -301,6 +285,10 @@ function runServer(port, noOpen, cwd, onChild) {
|
|
|
301
285
|
resolveExit();
|
|
302
286
|
return;
|
|
303
287
|
}
|
|
288
|
+
if (code !== 0) {
|
|
289
|
+
const cacheDir = detectNpxCacheDir(stderrTail);
|
|
290
|
+
if (cacheDir) npxCacheHintLines(cacheDir, process.platform).forEach((line) => error(line));
|
|
291
|
+
}
|
|
304
292
|
process.exit(code ?? 1);
|
|
305
293
|
});
|
|
306
294
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Turning a confusing crash into a one-line fix. When an `npx mulmoterminal` install is
|
|
2
|
+
// aborted mid-unpack (npm's npx lock race — `ECOMPROMISED: Lock compromised`), the next run
|
|
3
|
+
// reuses the half-unpacked `~/.npm/_npx/<hash>/node_modules` and dies at boot with
|
|
4
|
+
// ERR_MODULE_NOT_FOUND for some transitive dependency. That reads as a broken package, but
|
|
5
|
+
// the published tarball is fine — the local npx cache entry is corrupted, and removing it
|
|
6
|
+
// is the whole fix. Nothing here reads the filesystem; the caller feeds it stderr text.
|
|
7
|
+
|
|
8
|
+
const MISSING_MODULE = /ERR_MODULE_NOT_FOUND|Cannot find (?:package|module)/;
|
|
9
|
+
|
|
10
|
+
// The cache entry itself: `<sep>_npx<sep><hash>` followed by another separator. Anchored on
|
|
11
|
+
// the literal `_npx` rather than on a leading `[^\s'"]*` wildcard — that prefix can also match
|
|
12
|
+
// separators, which makes the match ambiguous and the regex backtrack super-linearly on a long
|
|
13
|
+
// stack trace. The directory part before it is recovered by scanning, not by matching.
|
|
14
|
+
const NPX_ENTRY = /[/\\]_npx[/\\][0-9a-f]+(?=[/\\])/;
|
|
15
|
+
|
|
16
|
+
// What ends the path token a stack trace embeds: whitespace, or the quotes Node wraps
|
|
17
|
+
// specifiers in.
|
|
18
|
+
const PATH_BOUNDARIES = [" ", "\t", "\n", "\r", "'", '"'];
|
|
19
|
+
|
|
20
|
+
// Where the path token containing the entry begins: just past the last boundary before it.
|
|
21
|
+
const pathStartIndex = (textBefore) => Math.max(-1, ...PATH_BOUNDARIES.map((ch) => textBefore.lastIndexOf(ch))) + 1;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The corrupted npx cache entry a failed boot points at, or null.
|
|
25
|
+
* Fires only when BOTH signals are present — a module-resolution error and a path inside
|
|
26
|
+
* an `_npx/<hash>` cache entry — so an ordinary crash, or a resolution error in a normal
|
|
27
|
+
* install, never triggers the hint.
|
|
28
|
+
*/
|
|
29
|
+
export function detectNpxCacheDir(stderrText) {
|
|
30
|
+
if (typeof stderrText !== "string" || !MISSING_MODULE.test(stderrText)) return null;
|
|
31
|
+
const match = NPX_ENTRY.exec(stderrText);
|
|
32
|
+
if (!match) return null;
|
|
33
|
+
return stderrText.slice(pathStartIndex(stderrText.slice(0, match.index)), match.index + match[0].length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Shell-quote the cache dir so a path containing quote characters (a home dir with an
|
|
37
|
+
// apostrophe on POSIX is the realistic case) produces a command that is still valid — and
|
|
38
|
+
// safe — to copy-paste, rather than one that breaks or runs something unintended.
|
|
39
|
+
|
|
40
|
+
// POSIX single-quoting: wrap in '…' and turn every embedded ' into '\'' (close, escaped
|
|
41
|
+
// quote, reopen). Nothing inside single quotes is special, so this is injection-proof.
|
|
42
|
+
const posixQuote = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
43
|
+
|
|
44
|
+
// PowerShell single-quoting: '…' with each ' doubled. Single quotes suppress $-interpolation
|
|
45
|
+
// (a double-quoted path containing $ would otherwise expand a variable), so this is safe.
|
|
46
|
+
const powershellQuote = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
47
|
+
|
|
48
|
+
// cmd has no way to escape a " inside "…", but " is an illegal character in a Windows path,
|
|
49
|
+
// so double-quoting a real cache dir is safe; it protects the spaces that do occur.
|
|
50
|
+
const cmdQuote = (s) => `"${s}"`;
|
|
51
|
+
|
|
52
|
+
// The delete command differs per platform, and a wrong one is worse than none: `rm -rf` is not
|
|
53
|
+
// a command on Windows, and cmd's `rmdir /s /q` is a syntax error in PowerShell (where `rmdir`
|
|
54
|
+
// aliases Remove-Item), so Windows gets both of its shells spelled out.
|
|
55
|
+
const removalCommands = (cacheDir, platform) =>
|
|
56
|
+
platform === "win32"
|
|
57
|
+
? [` cmd: rmdir /s /q ${cmdQuote(cacheDir)}`, ` PowerShell: Remove-Item -Recurse -Force ${powershellQuote(cacheDir)}`]
|
|
58
|
+
: [` rm -rf ${posixQuote(cacheDir)}`];
|
|
59
|
+
|
|
60
|
+
/** The recovery instructions shown under the crash, one string per line. */
|
|
61
|
+
export function npxCacheHintLines(cacheDir, platform) {
|
|
62
|
+
return [
|
|
63
|
+
"This looks like a corrupted npx cache, not a MulmoTerminal bug.",
|
|
64
|
+
'An interrupted `npx` install (npm\'s "Lock compromised" race) can leave this entry half-unpacked:',
|
|
65
|
+
` ${cacheDir}`,
|
|
66
|
+
"Remove it and run the same command again:",
|
|
67
|
+
...removalCommands(cacheDir, platform),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Only the surface probeOnce actually touches — narrower than http.ClientRequest /
|
|
2
|
+
// IncomingMessage on purpose, so a test fake and the real node:http both satisfy it.
|
|
3
|
+
export interface ProbeRequest {
|
|
4
|
+
on(event: "error" | "timeout", listener: (...args: unknown[]) => void): unknown;
|
|
5
|
+
destroy(): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ProbeResponse {
|
|
9
|
+
resume(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ProbeGet = (opts: { host: string; port: number; path: string; timeout: number }, cb: (res: ProbeResponse) => void) => ProbeRequest;
|
|
13
|
+
|
|
14
|
+
export declare function probeOnce(get: ProbeGet, port: number, timeoutMs?: number): Promise<"ready" | "retry">;
|
|
15
|
+
|
|
16
|
+
export declare function waitUntilReady(
|
|
17
|
+
port: number,
|
|
18
|
+
onReady: () => void,
|
|
19
|
+
deps?: {
|
|
20
|
+
get?: ProbeGet;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
intervalMs?: number;
|
|
24
|
+
readyTimeoutMs?: number;
|
|
25
|
+
},
|
|
26
|
+
): () => void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Polling the launched server until it answers `/`, then firing onReady exactly once.
|
|
2
|
+
// Split out of mulmoterminal.js so the retry state machine is testable without a socket.
|
|
3
|
+
|
|
4
|
+
import { get as httpGet } from "node:http";
|
|
5
|
+
|
|
6
|
+
const PROBE_TIMEOUT_MS = 1000;
|
|
7
|
+
const RETRY_INTERVAL_MS = 300;
|
|
8
|
+
const DEFAULT_READY_TIMEOUT_MS = 15_000;
|
|
9
|
+
|
|
10
|
+
// One probe of the server: resolves "ready" if it answers, else "retry". It NEVER
|
|
11
|
+
// rejects, and yields exactly ONE outcome per call — the crucial guarantee. Destroying a
|
|
12
|
+
// timed-out request itself emits an 'error', so a single timeout would otherwise trigger
|
|
13
|
+
// both the timeout handler AND the error handler; each schedules a retry, the poll loop
|
|
14
|
+
// forks in two, and every fork fires onReady when the server finally answers — printing
|
|
15
|
+
// the banner and opening the browser several times. The `settled` latch collapses each
|
|
16
|
+
// request to one outcome.
|
|
17
|
+
export function probeOnce(get, port, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let settled = false;
|
|
20
|
+
const done = (outcome) => {
|
|
21
|
+
if (settled) return;
|
|
22
|
+
settled = true;
|
|
23
|
+
resolve(outcome);
|
|
24
|
+
};
|
|
25
|
+
const req = get({ host: "127.0.0.1", port, path: "/", timeout: timeoutMs }, (res) => {
|
|
26
|
+
res.resume();
|
|
27
|
+
done("ready");
|
|
28
|
+
});
|
|
29
|
+
req.on("error", () => done("retry"));
|
|
30
|
+
req.on("timeout", () => {
|
|
31
|
+
req.destroy();
|
|
32
|
+
done("retry");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Poll until the server answers, then call onReady once; give up after readyTimeoutMs so
|
|
38
|
+
// the launcher never hangs on a crash loop. Returns a cancel function — a raced/abandoned
|
|
39
|
+
// attempt stops polling so it can't fire a stale banner. `deps` is injectable for tests.
|
|
40
|
+
export function waitUntilReady(port, onReady, deps = {}) {
|
|
41
|
+
const { get = httpGet, now = Date.now, timeoutMs = PROBE_TIMEOUT_MS, intervalMs = RETRY_INTERVAL_MS, readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS } = deps;
|
|
42
|
+
const startedAt = now();
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
let timer = null;
|
|
45
|
+
const loop = async () => {
|
|
46
|
+
if (cancelled) return;
|
|
47
|
+
const outcome = await probeOnce(get, port, timeoutMs);
|
|
48
|
+
if (cancelled) return;
|
|
49
|
+
if (outcome === "ready") {
|
|
50
|
+
onReady();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (now() - startedAt > readyTimeoutMs) return;
|
|
54
|
+
timer = setTimeout(loop, intervalMs);
|
|
55
|
+
};
|
|
56
|
+
loop();
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
if (timer) clearTimeout(timer);
|
|
60
|
+
};
|
|
61
|
+
}
|
package/common/modelPresets.ts
CHANGED
|
@@ -197,7 +197,7 @@ export const MODEL_PRESETS: readonly ModelPreset[] = [
|
|
|
197
197
|
provider: "openrouter",
|
|
198
198
|
id: "nvidia/nemotron-3-ultra-550b-a55b",
|
|
199
199
|
label: "Nemotron 3 Ultra 550B",
|
|
200
|
-
contextLength: 512_288
|
|
200
|
+
contextLength: 524_288, // 512 KiB = 512 * 1024; was 512_288 (a typo)
|
|
201
201
|
pricePerMTok: { input: 0.6, output: 3.6 },
|
|
202
202
|
trials: measured(3, 3, 13),
|
|
203
203
|
},
|
|
@@ -300,14 +300,19 @@ export const MODEL_PRESETS: readonly ModelPreset[] = [
|
|
|
300
300
|
|
|
301
301
|
// Presets for one provider, plus whatever the user added to that provider's `models`.
|
|
302
302
|
// A user entry with the same id as a preset keeps the preset's measured numbers rather
|
|
303
|
-
// than appearing twice
|
|
303
|
+
// than appearing twice — and two user entries for the same id (even differing only in
|
|
304
|
+
// case) collapse to one, so a dropdown never shows the same model twice.
|
|
304
305
|
export function presetsForProvider(providerId: string, userModels: readonly string[] = []): ModelPreset[] {
|
|
305
306
|
const presets = MODEL_PRESETS.filter((preset) => preset.provider === providerId);
|
|
306
307
|
// Match `presetFor` (modelOption.ts), which compares ids case-insensitively: a user
|
|
307
308
|
// entry that only differs from a preset in case is the same model, not a new one.
|
|
308
|
-
const
|
|
309
|
-
const added: ModelPreset[] =
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
const seen = new Set(presets.map((preset) => preset.id.toLowerCase()));
|
|
310
|
+
const added: ModelPreset[] = [];
|
|
311
|
+
for (const id of userModels) {
|
|
312
|
+
const key = id.toLowerCase();
|
|
313
|
+
if (seen.has(key)) continue; // already a preset OR an earlier user entry
|
|
314
|
+
seen.add(key);
|
|
315
|
+
added.push({ provider: providerId, id, label: id, contextLength: 0, pricePerMTok: { input: 0, output: 0 }, trials: { status: "unmeasured" } });
|
|
316
|
+
}
|
|
312
317
|
return [...presets, ...added];
|
|
313
318
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-
|
|
1
|
+
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-kLn0LOBf.js";import"./chunk-WYO6CB5R-BlzOfotS-Cg8JP_k-.js";import"./chunk-VAUOI2AC-CS9QJ4yz-rBHFym4n.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CfjRj6ge.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{f as e}from"./mermaid-parser.core-DxEa8E3F-kLn0LOBf.js";export{e as createArchitectureServices};
|