@linzumi/cli 0.0.44-beta → 0.0.46-beta

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +402 -125
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ Install the CLI or run it with `npx`:
63
63
 
64
64
  ```bash
65
65
  npm install -g @linzumi/cli@latest
66
- npx -y @linzumi/cli@0.0.44-beta --version
66
+ npx -y @linzumi/cli@0.0.46-beta --version
67
67
  linzumi --version
68
68
  ```
69
69
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import { randomUUID as randomUUID4 } from "node:crypto";
3
- import { existsSync as existsSync11, readFileSync as readFileSync10, realpathSync as realpathSync6 } from "node:fs";
3
+ import { existsSync as existsSync11, readFileSync as readFileSync11, realpathSync as realpathSync6 } from "node:fs";
4
4
  import { homedir as homedir9 } from "node:os";
5
5
  import { resolve as resolve9 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -1488,7 +1488,6 @@ var defaultIntervalMs = 2000;
1488
1488
  var defaultDebounceMs = 750;
1489
1489
  function startPortForwardWatcher(options) {
1490
1490
  const rootPid = options.rootPid ?? process.pid;
1491
- const rootCwd = normalizeCwd(options.rootCwd);
1492
1491
  const intervalMs = options.intervalMs ?? defaultIntervalMs;
1493
1492
  const debounceMs = options.debounceMs ?? defaultDebounceMs;
1494
1493
  const lostDebounceMs = options.lostDebounceMs ?? intervalMs * 2 + debounceMs;
@@ -1508,8 +1507,8 @@ function startPortForwardWatcher(options) {
1508
1507
  Promise.resolve().then(async () => {
1509
1508
  const descendants = descendantPidSet(scanProcesses(), rootPid);
1510
1509
  const sockets = scanListenSockets();
1511
- const candidatePids = rootCwd === undefined ? sockets.filter((socket) => descendants.has(socket.pid)).map((socket) => socket.pid) : sockets.map((socket) => socket.pid);
1512
- const candidates = detectedForwardCandidates(sockets, descendants, scanProcessCwds(candidatePids), { rootCwd });
1510
+ const candidatePids = sockets.filter((socket) => descendants.has(socket.pid)).map((socket) => socket.pid);
1511
+ const candidates = detectedForwardCandidates(sockets, descendants, scanProcessCwds(candidatePids));
1513
1512
  const scanTimeMs = nowMs();
1514
1513
  const stable = stableForwardCandidates(candidateStabilityByPort, candidates, scanTimeMs, debounceMs);
1515
1514
  const changes = debouncedForwardCandidateChanges(emittedByPort, missingByPort, stable.stableCandidates, scanTimeMs, lostDebounceMs);
@@ -1613,17 +1612,8 @@ function descendantPidSet(rows, rootPid) {
1613
1612
  }
1614
1613
  return descendants;
1615
1614
  }
1616
- function detectedForwardCandidates(sockets, descendantPids, processCwds = new Map, options = {}) {
1617
- const rootCwd = normalizeCwd(options.rootCwd);
1618
- return sockets.filter((socket) => {
1619
- if (descendantPids.has(socket.pid)) {
1620
- return true;
1621
- }
1622
- if (rootCwd === undefined) {
1623
- return false;
1624
- }
1625
- return cwdMatchesRoot(processCwds.get(socket.pid), rootCwd);
1626
- }).filter((socket) => socket.port > 0 && socket.port < 65536).sort((left, right) => left.port - right.port).map((socket) => {
1615
+ function detectedForwardCandidates(sockets, descendantPids, processCwds = new Map) {
1616
+ return sockets.filter((socket) => descendantPids.has(socket.pid)).filter((socket) => socket.port > 0 && socket.port < 65536).sort((left, right) => left.port - right.port).map((socket) => {
1627
1617
  const cwd = processCwds.get(socket.pid);
1628
1618
  return {
1629
1619
  port: socket.port,
@@ -1633,20 +1623,6 @@ function detectedForwardCandidates(sockets, descendantPids, processCwds = new Ma
1633
1623
  };
1634
1624
  });
1635
1625
  }
1636
- function normalizeCwd(cwd) {
1637
- if (cwd === undefined) {
1638
- return;
1639
- }
1640
- const normalized = cwd.trim().replace(/\/+$/, "");
1641
- return normalized === "" ? undefined : normalized;
1642
- }
1643
- function cwdMatchesRoot(candidateCwd, rootCwd) {
1644
- const normalizedCandidate = normalizeCwd(candidateCwd);
1645
- if (normalizedCandidate === undefined) {
1646
- return false;
1647
- }
1648
- return normalizedCandidate === rootCwd || normalizedCandidate.startsWith(`${rootCwd}/`);
1649
- }
1650
1626
  function parseProcessRows(output) {
1651
1627
  return output.split(`
1652
1628
  `).map((line) => line.trim()).filter((line) => line !== "").map((line) => {
@@ -2716,7 +2692,6 @@ function startPortForwardWatchIfEnabled(args, state, payloadContext) {
2716
2692
  state.approvedForwardPorts.add(port);
2717
2693
  }
2718
2694
  state.portForwardWatcher = start({
2719
- rootCwd: watchOptions.rootCwd ?? args.options.cwd,
2720
2695
  ...watchOptions,
2721
2696
  onCandidate: (candidate) => publishPortForwardPrompt(args, state, payloadContext, candidate),
2722
2697
  onCandidateLost: (candidate) => handleLostPortForwardCandidate(args, state, payloadContext, candidate),
@@ -6016,6 +5991,7 @@ function pathLooksAbsolute(pathValue) {
6016
5991
 
6017
5992
  // src/localForwarding.ts
6018
5993
  import { gzipSync } from "node:zlib";
5994
+ import NodeWebSocket from "ws";
6019
5995
  var maxForwardBodyBytes = 64 * 1024 * 1024;
6020
5996
  var gzipForwardThresholdBytes = 32 * 1024;
6021
5997
  async function handleForwardHttpRequest(control, allowedPorts) {
@@ -6059,15 +6035,15 @@ function isForwardHttpRequestControl(control) {
6059
6035
  function isForwardWebSocketControl(control) {
6060
6036
  return control.type === "forward_websocket_open" || control.type === "forward_websocket_send" || control.type === "forward_websocket_close";
6061
6037
  }
6062
- function createForwardWebSocketManager(kandan, topic, allowedPorts) {
6038
+ function createForwardWebSocketManager(kandan, topic, allowedPorts, socketFactory = defaultForwardWebSocketFactory) {
6063
6039
  const sockets = new Map;
6064
6040
  const pushEvent = (payload) => kandan.push(topic, "forward:websocket_event", payload).catch(() => {
6065
6041
  return;
6066
6042
  });
6067
6043
  const closeSocket = (socketId) => {
6068
- const socket = sockets.get(socketId);
6044
+ const stream = sockets.get(socketId);
6069
6045
  sockets.delete(socketId);
6070
- socket?.close();
6046
+ stream?.socket.close();
6071
6047
  };
6072
6048
  return {
6073
6049
  handle: (control) => {
@@ -6081,12 +6057,12 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
6081
6057
  });
6082
6058
  return;
6083
6059
  }
6084
- openLocalWebSocket(control, sockets, pushEvent, "ws");
6060
+ openLocalWebSocket(control, sockets, pushEvent, socketFactory, "ws");
6085
6061
  return;
6086
6062
  }
6087
6063
  case "forward_websocket_send": {
6088
- const socket = sockets.get(control.socketId);
6089
- if (socket === undefined || socket.readyState !== WebSocket.OPEN) {
6064
+ const stream = sockets.get(control.socketId);
6065
+ if (stream === undefined || stream.socket.readyState !== WebSocket.OPEN) {
6090
6066
  pushEvent({
6091
6067
  socketId: control.socketId,
6092
6068
  type: "error",
@@ -6097,12 +6073,12 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
6097
6073
  const body = Buffer.from(control.bodyBase64, "base64");
6098
6074
  switch (control.opcode) {
6099
6075
  case "text":
6100
- socket.send(body.toString());
6076
+ stream.socket.send(body.toString());
6101
6077
  return;
6102
6078
  case "binary":
6103
6079
  case "ping":
6104
6080
  case "pong":
6105
- socket.send(body);
6081
+ stream.socket.send(body);
6106
6082
  return;
6107
6083
  }
6108
6084
  }
@@ -6118,18 +6094,27 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
6118
6094
  }
6119
6095
  };
6120
6096
  }
6121
- function openLocalWebSocket(control, sockets, pushEvent, scheme) {
6097
+ function openLocalWebSocket(control, sockets, pushEvent, socketFactory, scheme) {
6122
6098
  let opened = false;
6123
- const url = localForwardUrl(scheme === "ws" ? "http" : "https", control.port, control.path, control.queryString).replace(/^http/, scheme);
6099
+ const url = localForwardWebSocketUrl(scheme, control.port, control.path, control.queryString);
6124
6100
  const protocols = webSocketProtocols(control.headers);
6125
- const websocket = protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
6101
+ const headers = webSocketHeaders(control.headers);
6102
+ const websocket = socketFactory(url, protocols, headers);
6126
6103
  websocket.binaryType = "arraybuffer";
6127
- sockets.set(control.socketId, websocket);
6104
+ const previousStream = sockets.get(control.socketId);
6105
+ previousStream?.socket.close();
6106
+ sockets.set(control.socketId, { socket: websocket });
6128
6107
  websocket.addEventListener("open", () => {
6108
+ if (!currentWebSocket(sockets, control.socketId, websocket)) {
6109
+ return;
6110
+ }
6129
6111
  opened = true;
6130
6112
  pushEvent({ socketId: control.socketId, type: "open" });
6131
6113
  });
6132
6114
  websocket.addEventListener("message", (event) => {
6115
+ if (!currentWebSocket(sockets, control.socketId, websocket)) {
6116
+ return;
6117
+ }
6133
6118
  const body = typeof event.data === "string" ? Buffer.from(event.data) : Buffer.from(event.data);
6134
6119
  pushEvent({
6135
6120
  socketId: control.socketId,
@@ -6139,6 +6124,9 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
6139
6124
  });
6140
6125
  });
6141
6126
  websocket.addEventListener("close", (event) => {
6127
+ if (!currentWebSocket(sockets, control.socketId, websocket)) {
6128
+ return;
6129
+ }
6142
6130
  sockets.delete(control.socketId);
6143
6131
  pushEvent({
6144
6132
  socketId: control.socketId,
@@ -6148,18 +6136,32 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
6148
6136
  });
6149
6137
  });
6150
6138
  websocket.addEventListener("error", () => {
6139
+ if (!currentWebSocket(sockets, control.socketId, websocket)) {
6140
+ return;
6141
+ }
6151
6142
  sockets.delete(control.socketId);
6152
6143
  if (!opened && scheme === "ws") {
6153
- openLocalWebSocket(control, sockets, pushEvent, "wss");
6144
+ openLocalWebSocket(control, sockets, pushEvent, socketFactory, "wss");
6154
6145
  return;
6155
6146
  }
6156
6147
  pushEvent({
6157
6148
  socketId: control.socketId,
6158
6149
  type: "error",
6159
- error: "websocket_error"
6150
+ error: "websocket_error",
6151
+ attemptedScheme: scheme
6160
6152
  });
6161
6153
  });
6162
6154
  }
6155
+ function defaultForwardWebSocketFactory(url, protocols, headers) {
6156
+ if (headers === undefined) {
6157
+ return protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
6158
+ }
6159
+ const options = { headers };
6160
+ return protocols === undefined ? new NodeWebSocket(url, options) : new NodeWebSocket(url, protocols, options);
6161
+ }
6162
+ function currentWebSocket(sockets, socketId, socket) {
6163
+ return sockets.get(socketId)?.socket === socket;
6164
+ }
6163
6165
  function webSocketProtocols(headers) {
6164
6166
  if (!Array.isArray(headers)) {
6165
6167
  return;
@@ -6172,6 +6174,21 @@ function webSocketProtocols(headers) {
6172
6174
  });
6173
6175
  return protocols.length === 0 ? undefined : protocols;
6174
6176
  }
6177
+ function webSocketHeaders(headers) {
6178
+ if (!Array.isArray(headers)) {
6179
+ return;
6180
+ }
6181
+ const forwarded = headers.reduce((acc, header) => {
6182
+ if (isOctWebSocketHeader(header)) {
6183
+ acc[header.name] = header.value;
6184
+ }
6185
+ return acc;
6186
+ }, {});
6187
+ return Object.keys(forwarded).length === 0 ? undefined : forwarded;
6188
+ }
6189
+ function isOctWebSocketHeader(value) {
6190
+ return isHeader(value) && value.name.toLowerCase().startsWith("x-oct-");
6191
+ }
6175
6192
  function localForwardUrl(scheme, port, path, queryString) {
6176
6193
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
6177
6194
  const url = new URL(`${scheme}://127.0.0.1:${port}${normalizedPath}`);
@@ -6180,6 +6197,12 @@ function localForwardUrl(scheme, port, path, queryString) {
6180
6197
  }
6181
6198
  return url.toString();
6182
6199
  }
6200
+ function localForwardWebSocketUrl(scheme, port, path, queryString) {
6201
+ const httpScheme = scheme === "ws" ? "http" : "https";
6202
+ const url = new URL(localForwardUrl(httpScheme, port, path, queryString));
6203
+ url.protocol = `${scheme}:`;
6204
+ return url.toString();
6205
+ }
6183
6206
  async function fetchWithHttpsFallback(port, path, queryString, request) {
6184
6207
  try {
6185
6208
  return await fetch(localForwardUrl("http", port, path, queryString), request);
@@ -6292,8 +6315,10 @@ var blockedForwardHeaderNames = new Set([
6292
6315
  "connection",
6293
6316
  "content-encoding",
6294
6317
  "content-length",
6318
+ "cookie",
6295
6319
  "host",
6296
6320
  "keep-alive",
6321
+ "authorization",
6297
6322
  "proxy-authenticate",
6298
6323
  "proxy-authorization",
6299
6324
  "te",
@@ -6305,10 +6330,12 @@ var blockedForwardHeaderNames = new Set([
6305
6330
  // src/localEditor.ts
6306
6331
  import { spawn as spawn2 } from "node:child_process";
6307
6332
  import {
6333
+ copyFileSync,
6308
6334
  cpSync,
6309
6335
  existsSync as existsSync2,
6310
6336
  mkdirSync as mkdirSync3,
6311
6337
  mkdtempSync,
6338
+ readFileSync as readFileSync2,
6312
6339
  realpathSync as realpathSync3,
6313
6340
  writeFileSync as writeFileSync2
6314
6341
  } from "node:fs";
@@ -6510,6 +6537,7 @@ function prepareCodeServerProfile(collaboration, editorRuntime) {
6510
6537
  mkdirSync3(collaborationServerDir, { recursive: true });
6511
6538
  mkdirSync3(tempDir, { recursive: true });
6512
6539
  if (editorRuntime !== undefined) {
6540
+ ensureCodeServerBrowserExtensionAssets(editorRuntime);
6513
6541
  installDirectory(editorRuntime.assets.documentStateExtensionDir, join4(extensionsDir, "kandan.document-state-telemetry"));
6514
6542
  }
6515
6543
  writeFileSync2(join4(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
@@ -6518,6 +6546,68 @@ function prepareCodeServerProfile(collaboration, editorRuntime) {
6518
6546
  return { ok: false, reason: "code_server_spawn_failed" };
6519
6547
  }
6520
6548
  }
6549
+ function ensureCodeServerBrowserExtensionAssets(runtime) {
6550
+ const vscodeRoot = codeServerVscodeRoot(runtime);
6551
+ const repairs = [
6552
+ {
6553
+ source: join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js"),
6554
+ target: join4(vscodeRoot, "extensions", "git-base", "dist", "browser", "extension.js"),
6555
+ required: true
6556
+ },
6557
+ {
6558
+ source: join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js.map"),
6559
+ target: join4(vscodeRoot, "extensions", "git-base", "dist", "browser", "extension.js.map"),
6560
+ required: false
6561
+ },
6562
+ {
6563
+ source: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js"),
6564
+ target: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "browser", "mergeConflictMain.js"),
6565
+ required: true
6566
+ },
6567
+ {
6568
+ source: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js.map"),
6569
+ target: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "browser", "mergeConflictMain.js.map"),
6570
+ required: false
6571
+ }
6572
+ ];
6573
+ repairs.forEach(({ source, target, required }) => {
6574
+ switch (true) {
6575
+ case existsSync2(target):
6576
+ return;
6577
+ case (!required && !existsSync2(source)):
6578
+ return;
6579
+ default:
6580
+ mkdirSync3(dirname2(target), { recursive: true });
6581
+ copyFileSync(source, target);
6582
+ return;
6583
+ }
6584
+ });
6585
+ }
6586
+ function codeServerVscodeRoot(runtime) {
6587
+ const roots = uniquePaths([
6588
+ join4(runtime.root, "lib", "vscode"),
6589
+ join4(dirname2(dirname2(runtime.codeServerBin)), "lib", "vscode"),
6590
+ ...wrappedCodeServerBin(runtime.codeServerBin).map((codeServerBin) => join4(dirname2(dirname2(codeServerBin)), "lib", "vscode"))
6591
+ ]);
6592
+ const rootWithRequiredAssets = roots.find((root) => codeServerBrowserAssetSources(root).every((source) => existsSync2(source)));
6593
+ return rootWithRequiredAssets ?? roots[0];
6594
+ }
6595
+ function wrappedCodeServerBin(codeServerBin) {
6596
+ try {
6597
+ const script = readFileSync2(codeServerBin, "utf8");
6598
+ const match = script.match(/exec\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))\s+"\$@"/u);
6599
+ const target = match?.[1] ?? match?.[2] ?? match?.[3];
6600
+ return target === undefined || target.trim() === "" ? [] : [target];
6601
+ } catch (_error) {
6602
+ return [];
6603
+ }
6604
+ }
6605
+ function codeServerBrowserAssetSources(vscodeRoot) {
6606
+ return [
6607
+ join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js"),
6608
+ join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js")
6609
+ ];
6610
+ }
6521
6611
  function prepareCodeServerLaunch(options) {
6522
6612
  const platform = options.platform ?? process.platform;
6523
6613
  if (platform === "linux") {
@@ -6650,12 +6740,16 @@ function prepareLocalEditorCollaboration(collaboration, runnerId, serverPort, br
6650
6740
  }
6651
6741
  const targetPath = `/local-codex-runners/${encodeURIComponent(runnerId)}/forwards/${serverPort}/preview-target`;
6652
6742
  const serverUrl = new URL(targetPath, `${browserBaseUrl}/`).toString();
6653
- const bootstrapServerUrl = collaboration.bootstrapToken === undefined || collaboration.bootstrapToken === "" ? serverUrl : new URL(`${targetPath}/_kandan-collaboration/${encodeURIComponent(collaboration.bootstrapToken)}`, `${browserBaseUrl}/`).toString();
6743
+ const collaborationStatePath = `/api/v2/editor/sessions/${collaboration.editorSessionId}/collaboration-state`;
6744
+ const collaborationBootstrapPath = collaboration.bootstrapToken === undefined || collaboration.bootstrapToken === "" ? undefined : `${targetPath}/_kandan-collaboration/${encodeURIComponent(collaboration.bootstrapToken)}`;
6745
+ const collaborationStateUrl = collaborationBootstrapPath === undefined ? collaborationStatePath : `${collaborationBootstrapPath}${collaborationStatePath}`;
6746
+ const bootstrapServerUrl = collaborationBootstrapPath === undefined ? serverUrl : new URL(collaborationBootstrapPath, `${browserBaseUrl}/`).toString();
6654
6747
  return {
6655
6748
  ...collaboration,
6656
6749
  serverPort,
6657
6750
  serverUrl,
6658
- bootstrapServerUrl
6751
+ bootstrapServerUrl,
6752
+ collaborationStateUrl
6659
6753
  };
6660
6754
  }
6661
6755
  function codeServerSettings(collaboration) {
@@ -6668,6 +6762,7 @@ function codeServerSettings(collaboration) {
6668
6762
  "oct.joinAcceptMode": "auto",
6669
6763
  ...collaboration === undefined ? {} : {
6670
6764
  "oct.kandanEditorSessionId": collaboration.editorSessionId,
6765
+ "oct.kandanCollaborationStateUrl": collaboration.collaborationStateUrl,
6671
6766
  "oct.serverUrl": collaboration.bootstrapServerUrl
6672
6767
  },
6673
6768
  "security.workspace.trust.enabled": false,
@@ -6746,6 +6841,7 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime,
6746
6841
  serverPort: collaboration.serverPort,
6747
6842
  serverUrl: collaboration.serverUrl,
6748
6843
  bootstrapServerUrl: collaboration.bootstrapServerUrl,
6844
+ collaborationStateUrl: collaboration.collaborationStateUrl,
6749
6845
  process: child,
6750
6846
  exited
6751
6847
  }
@@ -6769,7 +6865,7 @@ function installDirectory(sourceDir, destinationDir) {
6769
6865
  mkdirSync3(dirname2(destinationDir), { recursive: true });
6770
6866
  cpSync(sourceDir, destinationDir, { recursive: true });
6771
6867
  }
6772
- function codeServerEnv(env, cwd, _userDataDir, collaboration) {
6868
+ function codeServerEnv(env, cwd, userDataDir, collaboration) {
6773
6869
  const { PORT: _port, ...hostEnv } = env;
6774
6870
  const base = {
6775
6871
  ...hostEnv,
@@ -6783,7 +6879,8 @@ function codeServerEnv(env, cwd, _userDataDir, collaboration) {
6783
6879
  KANDAN_EDITOR_COLLABORATION_DEPLOYMENT_SHAPE: "local_runner_sidecar",
6784
6880
  KANDAN_EDITOR_COLLABORATION_ENTRY_MODE: "kandan_auto_host_or_join",
6785
6881
  KANDAN_EDITOR_COLLABORATION_ROOM_ID: collaboration.roomId,
6786
- KANDAN_EDITOR_COLLABORATION_SERVER_URL: collaboration.bootstrapServerUrl
6882
+ KANDAN_EDITOR_COLLABORATION_SERVER_URL: collaboration.bootstrapServerUrl,
6883
+ KANDAN_EDITOR_COLLABORATION_AUTO_HOST_CLAIM_PATH: join4(userDataDir, "kandan-oct-auto-host.lock")
6787
6884
  };
6788
6885
  }
6789
6886
  function collaborationEvent(collaboration) {
@@ -6986,7 +7083,7 @@ import {
6986
7083
  existsSync as existsSync3,
6987
7084
  mkdirSync as mkdirSync4,
6988
7085
  mkdtempSync as mkdtempSync2,
6989
- readFileSync as readFileSync2,
7086
+ readFileSync as readFileSync3,
6990
7087
  renameSync,
6991
7088
  rmSync,
6992
7089
  writeFileSync as writeFileSync3
@@ -7474,7 +7571,7 @@ function installedRuntime(cacheRoot, manifest) {
7474
7571
  return { ok: false };
7475
7572
  }
7476
7573
  try {
7477
- const installed = JSON.parse(readFileSync2(manifestPath, "utf8"));
7574
+ const installed = JSON.parse(readFileSync3(manifestPath, "utf8"));
7478
7575
  if (isJsonObject(installed) && installed.version === manifest.version && installed.platform === manifest.platform && (installed.archiveSha256 === undefined || installed.archiveSha256 === manifest.archiveSha256)) {
7479
7576
  return {
7480
7577
  ok: true,
@@ -7705,7 +7802,7 @@ function manifestAssetChecksums(assets) {
7705
7802
  return checksums;
7706
7803
  }
7707
7804
  function fileSha256Sync(path) {
7708
- return createHash2("sha256").update(readFileSync2(path)).digest("hex");
7805
+ return createHash2("sha256").update(readFileSync3(path)).digest("hex");
7709
7806
  }
7710
7807
  function defaultEditorRuntimeCacheRoot() {
7711
7808
  return join5(homedir4(), ".linzumi", "editor-runtimes");
@@ -7884,6 +7981,7 @@ function firstNonBlank(...values) {
7884
7981
  }
7885
7982
 
7886
7983
  // src/phoenix.ts
7984
+ var defaultPushTimeoutMs = 30000;
7887
7985
  function phoenixWebsocketUrl(baseUrl, token) {
7888
7986
  const parsed = new URL(baseUrl);
7889
7987
  switch (parsed.protocol) {
@@ -7899,7 +7997,7 @@ function phoenixWebsocketUrl(baseUrl, token) {
7899
7997
  parsed.searchParams.set("vsn", "2.0.0");
7900
7998
  return parsed.toString();
7901
7999
  }
7902
- async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new WebSocket(url)) {
8000
+ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new WebSocket(url), options = {}) {
7903
8001
  const pending = new Map;
7904
8002
  const joins = new Map;
7905
8003
  const controlCallbacks = new Set;
@@ -7917,9 +8015,13 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
7917
8015
  rejectReady: undefined,
7918
8016
  reconnectTimer: undefined
7919
8017
  };
8018
+ const pushTimeoutMs = options.pushTimeoutMs ?? defaultPushTimeoutMs;
7920
8019
  const rejectPending = (message) => {
7921
8020
  const error = new Error(message);
7922
- pending.forEach((pendingPush) => pendingPush.reject(error));
8021
+ pending.forEach((pendingPush) => {
8022
+ clearTimeout(pendingPush.timer);
8023
+ pendingPush.reject(error);
8024
+ });
7923
8025
  pending.clear();
7924
8026
  };
7925
8027
  const resetReady = () => {
@@ -7935,6 +8037,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
7935
8037
  const pendingPush = pending.get(ref);
7936
8038
  if (pendingPush !== undefined) {
7937
8039
  pending.delete(ref);
8040
+ clearTimeout(pendingPush.timer);
7938
8041
  if (name === "phx_error") {
7939
8042
  pendingPush.reject(new Error("phoenix push failed"));
7940
8043
  } else if (isNonOkPushReply(payload) && pendingPush.event !== "phx_join") {
@@ -7960,7 +8063,12 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
7960
8063
  state.nextRef += 1;
7961
8064
  const frame = [null, ref, topic, event, payload];
7962
8065
  return new Promise((resolve5, reject) => {
7963
- pending.set(ref, { event, resolve: resolve5, reject });
8066
+ const timer = setTimeout(() => {
8067
+ if (pending.delete(ref)) {
8068
+ reject(new Error(`phoenix push timed out after ${pushTimeoutMs}ms: ${event}`));
8069
+ }
8070
+ }, pushTimeoutMs);
8071
+ pending.set(ref, { event, timer, resolve: resolve5, reject });
7964
8072
  websocket.send(JSON.stringify(frame));
7965
8073
  });
7966
8074
  };
@@ -8035,10 +8143,10 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
8035
8143
  return pushOnOpenSocket(topic, event, payload);
8036
8144
  };
8037
8145
  return {
8038
- join: async (topic, payload, options) => {
8146
+ join: async (topic, payload, options2) => {
8039
8147
  const reply = await push(topic, "phx_join", payload);
8040
8148
  if (isJoinReply(reply)) {
8041
- joins.set(topic, { payload: options?.rejoinPayload ?? (() => payload) });
8149
+ joins.set(topic, { payload: options2?.rejoinPayload ?? (() => payload) });
8042
8150
  return reply.response;
8043
8151
  }
8044
8152
  throw new Error(`phoenix join failed: ${joinErrorMessage(reply)}`);
@@ -8110,7 +8218,7 @@ import {
8110
8218
  existsSync as existsSync5,
8111
8219
  mkdirSync as mkdirSync6,
8112
8220
  openSync as openSync2,
8113
- readFileSync as readFileSync4,
8221
+ readFileSync as readFileSync5,
8114
8222
  unlinkSync as unlinkSync2,
8115
8223
  writeSync
8116
8224
  } from "node:fs";
@@ -8122,27 +8230,38 @@ import {
8122
8230
  existsSync as existsSync4,
8123
8231
  linkSync,
8124
8232
  mkdirSync as mkdirSync5,
8125
- readFileSync as readFileSync3,
8233
+ readFileSync as readFileSync4,
8126
8234
  realpathSync as realpathSync4,
8127
8235
  unlinkSync,
8128
8236
  writeFileSync as writeFileSync4
8129
8237
  } from "node:fs";
8130
8238
  import { homedir as homedir5 } from "node:os";
8131
8239
  import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
8240
+
8241
+ // src/defaultUrls.ts
8242
+ var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
8243
+ var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
8244
+
8245
+ // src/localConfig.ts
8246
+ var prodConfigScope = "prod";
8132
8247
  function localConfigPath(env = process.env) {
8133
8248
  const override = env.LINZUMI_CONFIG_FILE;
8134
8249
  return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
8135
8250
  }
8251
+ function localConfigScopeKey(linzumiUrl) {
8252
+ const normalizedUrl = kandanHttpBaseUrl(linzumiUrl);
8253
+ const normalizedProdUrl = kandanHttpBaseUrl(defaultLinzumiWebSocketUrl);
8254
+ return normalizedUrl === normalizedProdUrl ? prodConfigScope : normalizedUrl;
8255
+ }
8256
+ function localConfigScopeFileStem(linzumiUrl) {
8257
+ const scopeKey = localConfigScopeKey(linzumiUrl);
8258
+ return scopeKey === prodConfigScope ? prodConfigScope : Buffer.from(scopeKey).toString("base64url");
8259
+ }
8136
8260
  function readLocalConfig(path = localConfigPath()) {
8137
- if (!existsSync4(path)) {
8138
- return { version: 1, allowedCwds: [] };
8139
- }
8140
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
8141
- if (!isConfigPayload(parsed)) {
8142
- throw new Error(`invalid Linzumi config: ${path}`);
8143
- }
8144
- const allowedCwds = uniqueStrings(parsed.allowedCwds);
8145
- return parsed.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: parsed.machineId, allowedCwds };
8261
+ return readLocalConfigSection(path);
8262
+ }
8263
+ function readLocalConfigForLinzumiUrl(linzumiUrl, path = localConfigPath()) {
8264
+ return readLocalConfigSection(path, linzumiUrl);
8146
8265
  }
8147
8266
  function ensureLocalMachineId(path = localConfigPath(), createMachineId = randomUUID2) {
8148
8267
  const config = readLocalConfig(path);
@@ -8158,13 +8277,36 @@ function ensureLocalMachineId(path = localConfigPath(), createMachineId = random
8158
8277
  writeLocalConfig({ ...latestConfig, machineId }, path);
8159
8278
  return machineId;
8160
8279
  }
8161
- function localMachineIdSeedPath(configPath = localConfigPath()) {
8280
+ function ensureLocalMachineIdForLinzumiUrl(linzumiUrl, path = localConfigPath(), createMachineId = randomUUID2) {
8281
+ if (localConfigScopeKey(linzumiUrl) === prodConfigScope) {
8282
+ return ensureLocalMachineId(path, createMachineId);
8283
+ }
8284
+ const config = readLocalConfigForLinzumiUrl(linzumiUrl, path);
8285
+ if (config.machineId !== undefined) {
8286
+ return config.machineId;
8287
+ }
8288
+ const machineId = ensureLocalMachineIdSeed(path, createMachineId, linzumiUrl);
8289
+ const latestConfig = readLocalConfigForLinzumiUrl(linzumiUrl, path);
8290
+ const latestMachineId = latestConfig.machineId;
8291
+ if (latestMachineId !== undefined) {
8292
+ return latestMachineId;
8293
+ }
8294
+ writeLocalConfigSection({ ...latestConfig, machineId }, path, linzumiUrl);
8295
+ return machineId;
8296
+ }
8297
+ function localMachineIdSeedPath(configPath = localConfigPath(), linzumiUrl) {
8298
+ if (linzumiUrl !== undefined && localConfigScopeKey(linzumiUrl) !== prodConfigScope) {
8299
+ return join6(dirname4(configPath), `${basename4(configPath)}.${localConfigScopeFileStem(linzumiUrl)}.machine-id`);
8300
+ }
8162
8301
  return join6(dirname4(configPath), `${basename4(configPath)}.machine-id`);
8163
8302
  }
8164
- function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
8303
+ function readConfiguredAllowedCwdDetailsForLinzumiUrl(linzumiUrl, path = localConfigPath()) {
8304
+ return readConfiguredAllowedCwdDetailsFromConfig(readLocalConfigForLinzumiUrl(linzumiUrl, path));
8305
+ }
8306
+ function readConfiguredAllowedCwdDetailsFromConfig(config) {
8165
8307
  const allowedCwds = [];
8166
8308
  const missingAllowedCwds = [];
8167
- for (const cwd of readLocalConfig(path).allowedCwds) {
8309
+ for (const cwd of config.allowedCwds) {
8168
8310
  const absolutePath = resolve5(expandUserPath(cwd));
8169
8311
  try {
8170
8312
  const realPath = realpathSync4(absolutePath);
@@ -8183,36 +8325,91 @@ function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
8183
8325
  };
8184
8326
  }
8185
8327
  function addAllowedCwd(pathValue, path = localConfigPath()) {
8328
+ return addAllowedCwdToConfig(pathValue, path);
8329
+ }
8330
+ function addAllowedCwdForLinzumiUrl(pathValue, linzumiUrl, path = localConfigPath()) {
8331
+ return addAllowedCwdToConfig(pathValue, path, linzumiUrl);
8332
+ }
8333
+ function addAllowedCwdToConfig(pathValue, path, linzumiUrl) {
8186
8334
  const normalizedPath = realpathSync4(resolve5(expandUserPath(pathValue)));
8187
- const config = readLocalConfig(path);
8335
+ const config = readLocalConfigSection(path, linzumiUrl);
8188
8336
  const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
8189
- writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
8337
+ writeLocalConfigSection({ ...config, version: 1, allowedCwds }, path, linzumiUrl);
8190
8338
  return allowedCwds;
8191
8339
  }
8192
8340
  function removeAllowedCwd(pathValue, path = localConfigPath()) {
8341
+ return removeAllowedCwdFromConfig(pathValue, path);
8342
+ }
8343
+ function removeAllowedCwdForLinzumiUrl(pathValue, linzumiUrl, path = localConfigPath()) {
8344
+ return removeAllowedCwdFromConfig(pathValue, path, linzumiUrl);
8345
+ }
8346
+ function removeAllowedCwdFromConfig(pathValue, path, linzumiUrl) {
8193
8347
  const requestedPath = resolve5(expandUserPath(pathValue));
8194
8348
  const normalizedRequest = realpathOrResolved(requestedPath);
8195
- const config = readLocalConfig(path);
8349
+ const config = readLocalConfigSection(path, linzumiUrl);
8196
8350
  const allowedCwds = config.allowedCwds.filter((cwd) => {
8197
8351
  const normalizedExisting = realpathOrResolved(cwd);
8198
8352
  return cwd !== pathValue && normalizedExisting !== normalizedRequest;
8199
8353
  });
8200
- writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
8354
+ writeLocalConfigSection({ ...config, version: 1, allowedCwds }, path, linzumiUrl);
8201
8355
  return allowedCwds;
8202
8356
  }
8203
8357
  function writeLocalConfig(config, path = localConfigPath()) {
8358
+ writeLocalConfigSection(config, path);
8359
+ }
8360
+ function readLocalConfigFile(path) {
8361
+ if (!existsSync4(path)) {
8362
+ return { version: 1, allowedCwds: [] };
8363
+ }
8364
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
8365
+ if (!isConfigPayload(parsed)) {
8366
+ throw new Error(`invalid Linzumi config: ${path}`);
8367
+ }
8368
+ return parsed;
8369
+ }
8370
+ function readLocalConfigSection(path, linzumiUrl) {
8371
+ const parsed = readLocalConfigFile(path);
8372
+ const scopeKey = linzumiUrl === undefined ? prodConfigScope : localConfigScopeKey(linzumiUrl);
8373
+ const section = scopeKey === prodConfigScope ? parsed : parsed[scopeKey] ?? emptySection();
8374
+ if (!isConfigSection(section)) {
8375
+ throw new Error(`invalid Linzumi config section ${scopeKey}: ${path}`);
8376
+ }
8377
+ const allowedCwds = uniqueStrings(section.allowedCwds);
8378
+ return section.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: section.machineId, allowedCwds };
8379
+ }
8380
+ function writeLocalConfigSection(config, path, linzumiUrl) {
8381
+ const scopeKey = linzumiUrl === undefined ? prodConfigScope : localConfigScopeKey(linzumiUrl);
8382
+ const nextSection = normalizedConfigSection(config);
8383
+ const next = scopeKey === prodConfigScope ? { ...readLocalConfigFile(path), ...nextSection, version: 1 } : {
8384
+ ...readLocalConfigFile(path),
8385
+ version: 1,
8386
+ [scopeKey]: nextSection
8387
+ };
8204
8388
  mkdirSync5(dirname4(path), { recursive: true });
8205
- writeFileSync4(path, `${JSON.stringify(config, null, 2)}
8389
+ writeFileSync4(path, `${JSON.stringify(next, null, 2)}
8206
8390
  `, "utf8");
8207
8391
  }
8208
8392
  function isConfigPayload(value) {
8209
- return typeof value === "object" && value !== null && value.version === 1 && Array.isArray(value.allowedCwds) && machineIdValid(value.machineId) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
8393
+ return typeof value === "object" && value !== null && value.version === 1 && isConfigSection(value);
8394
+ }
8395
+ function isConfigSection(value) {
8396
+ return typeof value === "object" && value !== null && Array.isArray(value.allowedCwds) && machineIdValid(value.machineId) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
8397
+ }
8398
+ function normalizedConfigSection(config) {
8399
+ if (!isConfigPayload(config)) {
8400
+ throw new Error("invalid Linzumi config");
8401
+ }
8402
+ const allowedCwds = uniqueStrings(config.allowedCwds);
8403
+ return config.machineId === undefined ? { allowedCwds } : { machineId: config.machineId, allowedCwds };
8404
+ }
8405
+ function emptySection() {
8406
+ return { allowedCwds: [] };
8210
8407
  }
8211
8408
  function machineIdValid(value) {
8212
8409
  return value === undefined || typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
8213
8410
  }
8214
- function ensureLocalMachineIdSeed(configPath, createMachineId) {
8215
- const seedPath = localMachineIdSeedPath(configPath);
8411
+ function ensureLocalMachineIdSeed(configPath, createMachineId, linzumiUrl) {
8412
+ const seedPath = localMachineIdSeedPath(configPath, linzumiUrl);
8216
8413
  if (existsSync4(seedPath)) {
8217
8414
  return readMachineIdSeed(seedPath);
8218
8415
  }
@@ -8237,7 +8434,7 @@ function ensureLocalMachineIdSeed(configPath, createMachineId) {
8237
8434
  }
8238
8435
  }
8239
8436
  function readMachineIdSeed(seedPath) {
8240
- const machineId = readFileSync3(seedPath, "utf8").trim();
8437
+ const machineId = readFileSync4(seedPath, "utf8").trim();
8241
8438
  if (!machineIdValid(machineId)) {
8242
8439
  throw new Error(`invalid Linzumi machine id seed: ${seedPath}`);
8243
8440
  }
@@ -8263,15 +8460,16 @@ function realpathOrResolved(pathValue) {
8263
8460
  }
8264
8461
 
8265
8462
  // src/version.ts
8266
- var linzumiCliVersion = "0.0.44-beta";
8463
+ var linzumiCliVersion = "0.0.46-beta";
8267
8464
  var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
8268
8465
 
8269
8466
  // src/runnerLock.ts
8270
- function runnerLockPath(machineId, configPath = localConfigPath()) {
8271
- return join7(dirname5(configPath), "runners", `${encodeURIComponent(machineId)}.lock`);
8467
+ function runnerLockPath(machineId, configPath = localConfigPath(), linzumiUrl) {
8468
+ const lockName = linzumiUrl === undefined ? encodeURIComponent(machineId) : localConfigScopeFileStem(linzumiUrl);
8469
+ return join7(dirname5(configPath), "runners", `${lockName}.lock`);
8272
8470
  }
8273
8471
  function acquireRunnerLock(options) {
8274
- const path = runnerLockPath(options.machineId, options.configPath);
8472
+ const path = runnerLockPath(options.machineId, options.configPath, options.linzumiUrl);
8275
8473
  const isPidAlive = options.isPidAlive ?? processIsAlive;
8276
8474
  const record = {
8277
8475
  version: 1,
@@ -8280,9 +8478,11 @@ function acquireRunnerLock(options) {
8280
8478
  pid: options.pid ?? process.pid,
8281
8479
  cwd: options.cwd,
8282
8480
  workspace: options.workspace,
8481
+ ...options.linzumiUrl === undefined ? {} : { linzumiUrl: kandanHttpBaseUrl(options.linzumiUrl) },
8283
8482
  startedAt: (options.now ?? (() => new Date))().toISOString(),
8284
8483
  cliVersion: options.cliVersion ?? linzumiCliVersion
8285
8484
  };
8485
+ rejectLiveLegacyProductionRunnerLock(options.machineId, options.configPath, options.linzumiUrl, isPidAlive);
8286
8486
  writeLockOrHandleExisting(path, record, isPidAlive, options.beforeReadExistingLock, options.beforeReplaceStaleLock);
8287
8487
  return {
8288
8488
  path,
@@ -8290,6 +8490,28 @@ function acquireRunnerLock(options) {
8290
8490
  release: () => releaseRunnerLock(path, record)
8291
8491
  };
8292
8492
  }
8493
+ function rejectLiveLegacyProductionRunnerLock(machineId, configPath, linzumiUrl, isPidAlive) {
8494
+ if (linzumiUrl === undefined || localConfigScopeKey(linzumiUrl) !== localConfigScopeKey(defaultLinzumiHttpUrl)) {
8495
+ return;
8496
+ }
8497
+ const legacyPath = runnerLockPath(machineId, configPath);
8498
+ const existing = readRunnerLockIfPresent(legacyPath);
8499
+ if (existing === undefined) {
8500
+ return;
8501
+ }
8502
+ if (isPidAlive(existing.pid)) {
8503
+ throw new Error(activeRunnerLockMessage(legacyPath, existing));
8504
+ }
8505
+ withStaleReplacementLock(legacyPath, isPidAlive, () => {
8506
+ const latest = existsSync5(legacyPath) ? readRunnerLock(legacyPath) : undefined;
8507
+ if (latest !== undefined && isPidAlive(latest.pid)) {
8508
+ throw new Error(activeRunnerLockMessage(legacyPath, latest));
8509
+ }
8510
+ if (latest !== undefined) {
8511
+ unlinkSync2(legacyPath);
8512
+ }
8513
+ });
8514
+ }
8293
8515
  function writeLockOrHandleExisting(path, record, isPidAlive, beforeReadExistingLock, beforeReplaceStaleLock) {
8294
8516
  if (tryCreateLock(path, record)) {
8295
8517
  return;
@@ -8375,7 +8597,7 @@ function withStaleReplacementLock(path, isPidAlive, callback) {
8375
8597
  function readReplacementLockPidIfPresent(path) {
8376
8598
  let value;
8377
8599
  try {
8378
- value = readFileSync4(path, "utf8").trim();
8600
+ value = readFileSync5(path, "utf8").trim();
8379
8601
  } catch (error) {
8380
8602
  if (isNodeErrorCode2(error, "ENOENT")) {
8381
8603
  return;
@@ -8415,14 +8637,17 @@ function readRunnerLockIfPresent(path) {
8415
8637
  }
8416
8638
  }
8417
8639
  function readRunnerLock(path) {
8418
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
8640
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
8419
8641
  if (!isRunnerLockRecord(parsed)) {
8420
8642
  throw new Error(`invalid Linzumi runner lock: ${path}`);
8421
8643
  }
8422
8644
  return parsed;
8423
8645
  }
8424
8646
  function isRunnerLockRecord(value) {
8425
- return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
8647
+ return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && linzumiUrlValid(value.linzumiUrl) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
8648
+ }
8649
+ function linzumiUrlValid(value) {
8650
+ return value === undefined || typeof value === "string" && value.trim() !== "";
8426
8651
  }
8427
8652
  function workspaceValid(value) {
8428
8653
  return value === null || typeof value === "string" && value.trim() !== "";
@@ -8437,8 +8662,10 @@ function processIsAlive(pid) {
8437
8662
  }
8438
8663
  function activeRunnerLockMessage(path, record) {
8439
8664
  const workspace = record.workspace === null ? "workspace: unknown" : `workspace: ${record.workspace}`;
8665
+ const linzumiUrl = record.linzumiUrl === undefined ? undefined : `linzumi url: ${displayLinzumiUrl(record.linzumiUrl)}`;
8440
8666
  return [
8441
- "another Linzumi runner is already running for this machine",
8667
+ record.linzumiUrl === undefined ? "another Linzumi runner is already running for this machine" : "another Linzumi runner is already running for this Linzumi URL",
8668
+ ...linzumiUrl === undefined ? [] : [linzumiUrl],
8442
8669
  `runner id: ${record.runnerId}`,
8443
8670
  `pid: ${record.pid}`,
8444
8671
  `cwd: ${record.cwd}`,
@@ -8450,6 +8677,12 @@ function activeRunnerLockMessage(path, record) {
8450
8677
  ].join(`
8451
8678
  `);
8452
8679
  }
8680
+ function displayLinzumiUrl(linzumiUrl) {
8681
+ if (linzumiUrl === localConfigScopeKey(defaultLinzumiHttpUrl)) {
8682
+ return defaultLinzumiHttpUrl;
8683
+ }
8684
+ return localConfigScopeKey(linzumiUrl) === localConfigScopeKey(defaultLinzumiHttpUrl) ? defaultLinzumiHttpUrl : linzumiUrl;
8685
+ }
8453
8686
  function isNodeErrorCode2(error, code) {
8454
8687
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
8455
8688
  }
@@ -8611,6 +8844,7 @@ async function runLocalCodexRunner(options) {
8611
8844
  runnerId: options.runnerId,
8612
8845
  cwd: options.cwd,
8613
8846
  workspace: runnerWorkspaceSlug(options) ?? null,
8847
+ linzumiUrl: options.kandanUrl,
8614
8848
  configPath: options.runnerLockConfigPath
8615
8849
  });
8616
8850
  cleanup.actions.push(() => runnerLock.release());
@@ -8717,11 +8951,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
8717
8951
  cleanup.actions.push(() => kandan.close());
8718
8952
  const topic = `local_runner:${options.runnerId}`;
8719
8953
  const clientId = options.machineId ?? options.runnerId;
8954
+ const runnerHost = hostname2();
8720
8955
  const joinPayload = () => ({
8721
8956
  clientName: "kandan-local-codex-runner",
8722
8957
  clientId,
8723
8958
  runnerPid: process.pid,
8724
8959
  version: linzumiCliVersion,
8960
+ hostname: runnerHost,
8961
+ cwd: options.cwd,
8725
8962
  workspace: runnerWorkspaceSlug(options) ?? null,
8726
8963
  channel: options.channelSession?.channelSlug ?? null,
8727
8964
  capabilities: capabilitiesPayload()
@@ -8990,7 +9227,6 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
8990
9227
  const seq = { value: 0 };
8991
9228
  const codexThreads = options.channelSession === undefined ? await discoverCodexThreads(codex, options.cwd) : [];
8992
9229
  const discoveredCodexThreads = { value: codexThreads };
8993
- const runnerHost = hostname2();
8994
9230
  const runtimeDefaults = runnerRuntimeDefaults(options);
8995
9231
  const instancePayload = {
8996
9232
  instanceId,
@@ -10005,7 +10241,7 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
10005
10241
  }
10006
10242
 
10007
10243
  // src/authCache.ts
10008
- import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
10244
+ import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
10009
10245
  import { homedir as homedir6 } from "node:os";
10010
10246
  import { dirname as dirname6, join as join9 } from "node:path";
10011
10247
  function defaultAuthFilePath() {
@@ -10016,7 +10252,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
10016
10252
  if (!existsSync6(authFilePath)) {
10017
10253
  return;
10018
10254
  }
10019
- const authFile = parseAuthFile(readFileSync5(authFilePath, "utf8"));
10255
+ const authFile = parseAuthFile(readFileSync6(authFilePath, "utf8"));
10020
10256
  const kandanBaseUrl = kandanHttpBaseUrl(kandanUrl);
10021
10257
  const entry = authFile.local_codex_runner?.[kandanBaseUrl];
10022
10258
  if (entry === undefined || entry.access_token.trim() === "") {
@@ -10034,7 +10270,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
10034
10270
  }
10035
10271
  function writeCachedLocalRunnerToken(args) {
10036
10272
  const authFilePath = args.authFilePath ?? defaultAuthFilePath();
10037
- const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync5(authFilePath, "utf8")) : { version: 1 };
10273
+ const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync6(authFilePath, "utf8")) : { version: 1 };
10038
10274
  const kandanBaseUrl = kandanHttpBaseUrl(args.kandanUrl);
10039
10275
  const issuedAt = new Date;
10040
10276
  const expiresAt = args.expiresInSeconds === undefined ? undefined : new Date(issuedAt.getTime() + args.expiresInSeconds * 1000).toISOString();
@@ -10143,12 +10379,8 @@ async function acquireAndCacheToken(args) {
10143
10379
  return token.accessToken;
10144
10380
  }
10145
10381
 
10146
- // src/defaultUrls.ts
10147
- var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
10148
- var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
10149
-
10150
10382
  // src/kandanTls.ts
10151
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
10383
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
10152
10384
  import { Agent } from "undici";
10153
10385
  import { WebSocket as WsWebSocket } from "ws";
10154
10386
  function kandanTlsTrustFromEnv() {
@@ -10162,7 +10394,7 @@ function kandanTlsTrustFromCaFile(caFile) {
10162
10394
  if (!existsSync7(trimmed)) {
10163
10395
  throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
10164
10396
  }
10165
- const ca = readFileSync6(trimmed, "utf8");
10397
+ const ca = readFileSync7(trimmed, "utf8");
10166
10398
  return {
10167
10399
  caFile: trimmed,
10168
10400
  ca,
@@ -10191,7 +10423,7 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
10191
10423
  }
10192
10424
 
10193
10425
  // src/agentBootstrap.ts
10194
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
10426
+ import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
10195
10427
  import { dirname as dirname7, join as join10 } from "node:path";
10196
10428
  import { homedir as homedir7 } from "node:os";
10197
10429
  async function runAgentCliCommand(args, deps = {
@@ -10801,7 +11033,7 @@ function authorizationHeaders(token) {
10801
11033
  return { authorization: `Bearer ${token}` };
10802
11034
  }
10803
11035
  function readOptionalTextFile(path) {
10804
- return existsSync8(path) ? readFileSync7(path, "utf8") : undefined;
11036
+ return existsSync8(path) ? readFileSync8(path, "utf8") : undefined;
10805
11037
  }
10806
11038
  function writeTextFile(path, content) {
10807
11039
  mkdirSync8(dirname7(path), { recursive: true });
@@ -10881,7 +11113,7 @@ Launch target:
10881
11113
  }
10882
11114
 
10883
11115
  // src/helloLinzumiProject.ts
10884
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
11116
+ import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
10885
11117
  import { dirname as dirname8, join as join11, resolve as resolve7 } from "node:path";
10886
11118
  import { fileURLToPath } from "node:url";
10887
11119
  var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
@@ -10891,7 +11123,7 @@ var defaultHelloLinzumiPort = 8787;
10891
11123
  var defaultHelloLinzumiHost = "0.0.0.0";
10892
11124
  var markerFile = ".linzumi-demo-project";
10893
11125
  var moduleDir = dirname8(fileURLToPath(import.meta.url));
10894
- var linzumiLogoSvg = readFileSync8(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
11126
+ var linzumiLogoSvg = readFileSync9(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
10895
11127
  function createHelloLinzumiProject(input = {}) {
10896
11128
  const options = typeof input === "string" ? { rootPath: input } : input;
10897
11129
  const root = resolveHelloProjectRoot(options);
@@ -10946,7 +11178,7 @@ function assertWritableDemoRoot(root, reset) {
10946
11178
  return;
10947
11179
  }
10948
11180
  const markerPath = join11(root, markerFile);
10949
- const isDemoRoot = existsSync9(markerPath) && readFileSync8(markerPath, "utf8").trim() === "hello-linzumi";
11181
+ const isDemoRoot = existsSync9(markerPath) && readFileSync9(markerPath, "utf8").trim() === "hello-linzumi";
10950
11182
  if (isDemoRoot && reset) {
10951
11183
  rmSync2(root, { recursive: true, force: true });
10952
11184
  return;
@@ -11449,7 +11681,7 @@ import {
11449
11681
  closeSync as closeSync2,
11450
11682
  mkdirSync as mkdirSync10,
11451
11683
  openSync as openSync3,
11452
- readFileSync as readFileSync9,
11684
+ readFileSync as readFileSync10,
11453
11685
  watch,
11454
11686
  writeFileSync as writeFileSync8
11455
11687
  } from "node:fs";
@@ -11534,12 +11766,12 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
11534
11766
  if (!existsSync10(statusFile)) {
11535
11767
  return { status: "missing", runnerId, statusFile };
11536
11768
  }
11537
- const record = parseRecord(readFileSync9(statusFile, "utf8"));
11769
+ const record = parseRecord(readFileSync10(statusFile, "utf8"));
11538
11770
  return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
11539
11771
  }
11540
11772
  async function waitForCommanderDaemon(options) {
11541
11773
  const now = options.now ?? (() => Date.now());
11542
- const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync9(path, "utf8") : undefined);
11774
+ const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync10(path, "utf8") : undefined);
11543
11775
  const statusImpl = options.statusImpl ?? commanderDaemonStatus;
11544
11776
  const deadline = now() + options.timeoutMs;
11545
11777
  while (now() <= deadline) {
@@ -11794,7 +12026,7 @@ async function main(args) {
11794
12026
  }
11795
12027
  case "start": {
11796
12028
  const options = await parseStartRunnerArgs(parsed.args);
11797
- addAllowedCwd(options.cwd);
12029
+ addAllowedCwdForLinzumiUrl(options.cwd, options.kandanUrl);
11798
12030
  await runLocalCodexRunner(withLocalMachineId(options));
11799
12031
  return;
11800
12032
  }
@@ -11897,19 +12129,16 @@ function runHelloCommand(args) {
11897
12129
  `);
11898
12130
  }
11899
12131
  function runPathsCommand(args) {
11900
- const [subcommand, pathValue, ...rest] = args;
11901
- if (subcommand === undefined || subcommand === "help" || subcommand === "--help") {
12132
+ const { subcommand, pathValue, linzumiUrl, help } = parsePathsCommandArgs(args);
12133
+ if (help || subcommand === undefined || subcommand === "help") {
11902
12134
  process.stdout.write(pathsHelpText());
11903
12135
  return;
11904
12136
  }
11905
- if (rest.length > 0) {
11906
- throw new Error("linzumi paths accepts one path argument");
11907
- }
11908
12137
  switch (subcommand) {
11909
12138
  case "list": {
11910
- const config = readLocalConfig();
12139
+ const config = linzumiUrl === undefined ? readLocalConfig() : readLocalConfigForLinzumiUrl(linzumiUrl);
11911
12140
  if (config.allowedCwds.length === 0) {
11912
- process.stdout.write(`No trusted paths configured in ${localConfigPath()}
12141
+ process.stdout.write(`No trusted paths configured in ${localConfigPath()}${pathsScopeSuffix(linzumiUrl)}
11913
12142
  `);
11914
12143
  return;
11915
12144
  }
@@ -11923,7 +12152,11 @@ function runPathsCommand(args) {
11923
12152
  throw new Error("missing path for linzumi paths add");
11924
12153
  }
11925
12154
  const trustedPath = realpathSync6(resolve9(expandUserPath(pathValue)));
11926
- addAllowedCwd(pathValue);
12155
+ if (linzumiUrl === undefined) {
12156
+ addAllowedCwd(pathValue);
12157
+ } else {
12158
+ addAllowedCwdForLinzumiUrl(pathValue, linzumiUrl);
12159
+ }
11927
12160
  process.stdout.write(`Trusted ${trustedPath}
11928
12161
  `);
11929
12162
  return;
@@ -11932,7 +12165,11 @@ function runPathsCommand(args) {
11932
12165
  if (pathValue === undefined || pathValue.trim() === "") {
11933
12166
  throw new Error("missing path for linzumi paths remove");
11934
12167
  }
11935
- removeAllowedCwd(pathValue);
12168
+ if (linzumiUrl === undefined) {
12169
+ removeAllowedCwd(pathValue);
12170
+ } else {
12171
+ removeAllowedCwdForLinzumiUrl(pathValue, linzumiUrl);
12172
+ }
11936
12173
  process.stdout.write(`Removed trusted path ${pathValue}
11937
12174
  `);
11938
12175
  return;
@@ -11941,6 +12178,46 @@ function runPathsCommand(args) {
11941
12178
  throw new Error(`invalid paths command: ${subcommand}`);
11942
12179
  }
11943
12180
  }
12181
+ function parsePathsCommandArgs(args) {
12182
+ const positional = [];
12183
+ let linzumiUrl;
12184
+ let help = false;
12185
+ for (let index = 0;index < args.length; index += 1) {
12186
+ const arg = args[index];
12187
+ switch (arg) {
12188
+ case "--help":
12189
+ help = true;
12190
+ break;
12191
+ case "--linzumi-url":
12192
+ case "--kandan-url": {
12193
+ const value = args[index + 1];
12194
+ if (value === undefined || value.startsWith("--")) {
12195
+ throw new Error(`missing value for ${arg}`);
12196
+ }
12197
+ linzumiUrl = value;
12198
+ index += 1;
12199
+ break;
12200
+ }
12201
+ default:
12202
+ if (arg !== undefined && arg.startsWith("--")) {
12203
+ throw new Error(`invalid flag: ${arg}`);
12204
+ }
12205
+ positional.push(arg ?? "");
12206
+ }
12207
+ }
12208
+ if (positional.length > 2) {
12209
+ throw new Error("linzumi paths accepts one path argument");
12210
+ }
12211
+ return {
12212
+ subcommand: positional[0],
12213
+ pathValue: positional[1],
12214
+ linzumiUrl,
12215
+ help
12216
+ };
12217
+ }
12218
+ function pathsScopeSuffix(linzumiUrl) {
12219
+ return linzumiUrl === undefined ? "" : ` for ${linzumiUrl}`;
12220
+ }
11944
12221
  async function runCommanderDaemonCommand(args) {
11945
12222
  const [subcommand, ...rest] = args;
11946
12223
  switch (subcommand) {
@@ -12172,7 +12449,7 @@ async function parseAgentRunnerArgs(args, deps = {
12172
12449
  const kandanUrl = stringValue3(values, "linzumi-url") ?? agentApiUrlToKandanUrl(tokenFile.apiUrl);
12173
12450
  const requestedCwdValue = cwdArg ?? stringValue3(values, "cwd");
12174
12451
  const requestedCwd = resolveUserPath(requestedCwdValue ?? process.cwd());
12175
- const configuredAllowedCwds2 = requestedCwdValue === undefined && !values.has("allowed-cwd") ? readConfiguredAllowedCwdDetails() : { allowedCwds: [], missingAllowedCwds: [] };
12452
+ const configuredAllowedCwds2 = requestedCwdValue === undefined && !values.has("allowed-cwd") ? readConfiguredAllowedCwdDetailsForLinzumiUrl(kandanUrl) : { allowedCwds: [], missingAllowedCwds: [] };
12176
12453
  const allowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : requestedCwdValue === undefined ? configuredAllowedCwds2.allowedCwds.length > 0 ? [...configuredAllowedCwds2.allowedCwds] : assertConfiguredAllowedCwds([requestedCwd]) : assertConfiguredAllowedCwds([requestedCwd]);
12177
12454
  const cwd = allowedCwds[0] ?? requestedCwd;
12178
12455
  const codexBin = stringValue3(values, "codex-bin") ?? "codex";
@@ -12219,7 +12496,7 @@ async function parseAgentRunnerArgs(args, deps = {
12219
12496
  };
12220
12497
  }
12221
12498
  function readAgentTokenTextFile(path) {
12222
- return existsSync11(path) ? readFileSync10(path, "utf8") : undefined;
12499
+ return existsSync11(path) ? readFileSync11(path, "utf8") : undefined;
12223
12500
  }
12224
12501
  function rejectAgentRunnerTargetingFlags(values) {
12225
12502
  const unsupportedFlags = [
@@ -12303,7 +12580,7 @@ async function parseRunnerArgs(args, deps = {
12303
12580
  const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
12304
12581
  const cwd = stringValue3(values, "cwd") ?? process.cwd();
12305
12582
  const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
12306
- const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetails();
12583
+ const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetailsForLinzumiUrl(kandanUrl);
12307
12584
  const configuredAllowedCwds2 = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [...localConfiguredAllowedCwds.allowedCwds];
12308
12585
  const codexBin = stringValue3(values, "codex-bin") ?? "codex";
12309
12586
  const customCodeServerBin = stringValue3(values, "code-server-bin");
@@ -12516,7 +12793,7 @@ function parseChannelPath(channel) {
12516
12793
  function withLocalMachineId(options) {
12517
12794
  return {
12518
12795
  ...options,
12519
- machineId: ensureLocalMachineId()
12796
+ machineId: localConfigScopeKey(options.kandanUrl) === localConfigScopeKey(defaultLinzumiWebSocketUrl) ? ensureLocalMachineId() : ensureLocalMachineIdForLinzumiUrl(options.kandanUrl)
12520
12797
  };
12521
12798
  }
12522
12799
  function required(values, key) {
@@ -12698,13 +12975,13 @@ function pathsHelpText() {
12698
12975
  return `Linzumi trusted paths
12699
12976
 
12700
12977
  Usage:
12701
- linzumi paths list
12702
- linzumi paths add <path>
12703
- linzumi paths remove <path>
12978
+ linzumi paths [--linzumi-url <ws-url>] list
12979
+ linzumi paths [--linzumi-url <ws-url>] add <path>
12980
+ linzumi paths [--linzumi-url <ws-url>] remove <path>
12704
12981
 
12705
- Trusted paths are stored in ~/.linzumi/config.json. linzumi connect always
12706
- trusts its selected cwd for that runner process and also uses configured paths
12707
- unless --allowed-cwd is passed with explicit extra roots.
12982
+ Trusted paths are stored in ~/.linzumi/config.json. Production/default paths
12983
+ use the root config fields; explicit --linzumi-url paths use a URL-scoped config
12984
+ section so local, staging, and production runners do not share trust state.
12708
12985
  `;
12709
12986
  }
12710
12987
  function startHelpText() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.44-beta",
3
+ "version": "0.0.46-beta",
4
4
  "description": "Linzumi CLI — point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {