@linzumi/cli 0.0.55-beta → 0.0.56-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 +185 -327
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -62,7 +62,7 @@ Install the CLI or run it with `npx`:
62
62
  ```bash
63
63
  npm install -g @linzumi/cli@latest
64
64
  npx -y @linzumi/cli@latest signup
65
- npx -y @linzumi/cli@0.0.55-beta --version
65
+ npx -y @linzumi/cli@0.0.56-beta --version
66
66
  linzumi --version
67
67
  ```
68
68
 
package/dist/index.js CHANGED
@@ -18203,8 +18203,8 @@ function pathLooksAbsolute(pathValue) {
18203
18203
  }
18204
18204
 
18205
18205
  // src/localForwarding.ts
18206
+ import { connect as connectTcp } from "node:net";
18206
18207
  import { gzipSync } from "node:zlib";
18207
- import NodeWebSocket from "ws";
18208
18208
  var maxForwardBodyBytes = 64 * 1024 * 1024;
18209
18209
  var gzipForwardThresholdBytes = 32 * 1024;
18210
18210
  async function handleForwardHttpRequest(control, allowedPorts) {
@@ -18261,21 +18261,21 @@ async function handleForwardHttpRequest(control, allowedPorts) {
18261
18261
  function isForwardHttpRequestControl(control) {
18262
18262
  return control.type === "forward_http_request";
18263
18263
  }
18264
- function isForwardWebSocketControl(control) {
18265
- return control.type === "forward_websocket_open" || control.type === "forward_websocket_send" || control.type === "forward_websocket_close";
18264
+ function isForwardTcpControl(control) {
18265
+ return control.type === "forward_tcp_open" || control.type === "forward_tcp_send" || control.type === "forward_tcp_close";
18266
18266
  }
18267
- function createForwardWebSocketManager(kandan, topic, allowedPorts, socketFactory = defaultForwardWebSocketFactory) {
18267
+ function createForwardTcpManager(kandan, topic, allowedPorts, socketFactory = defaultForwardTcpFactory) {
18268
18268
  const sockets = /* @__PURE__ */ new Map();
18269
- const pushEvent = (payload) => kandan.push(topic, "forward:websocket_event", payload).catch(() => void 0);
18269
+ const pushEvent = (payload) => kandan.push(topic, "forward:tcp_event", payload).catch(() => void 0);
18270
18270
  const closeSocket = (socketId) => {
18271
18271
  const stream = sockets.get(socketId);
18272
18272
  sockets.delete(socketId);
18273
- stream?.socket.close();
18273
+ stream?.socket.end();
18274
18274
  };
18275
18275
  return {
18276
18276
  handle: (control) => {
18277
18277
  switch (control.type) {
18278
- case "forward_websocket_open": {
18278
+ case "forward_tcp_open": {
18279
18279
  if (!allowedPorts().includes(control.port)) {
18280
18280
  void pushEvent({
18281
18281
  socketId: control.socketId,
@@ -18284,32 +18284,70 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts, socketFactor
18284
18284
  });
18285
18285
  return;
18286
18286
  }
18287
- openLocalWebSocket(control, sockets, pushEvent, socketFactory, "ws");
18287
+ const previous = sockets.get(control.socketId);
18288
+ previous?.socket.destroy();
18289
+ const socket = socketFactory(control.port);
18290
+ sockets.set(control.socketId, { socket });
18291
+ socket.on("connect", () => {
18292
+ if (!currentTcpSocket(sockets, control.socketId, socket)) {
18293
+ return;
18294
+ }
18295
+ void pushEvent({ socketId: control.socketId, type: "open" });
18296
+ });
18297
+ socket.on("data", (data) => {
18298
+ if (!currentTcpSocket(sockets, control.socketId, socket) || !(data instanceof Uint8Array)) {
18299
+ return;
18300
+ }
18301
+ void pushEvent({
18302
+ socketId: control.socketId,
18303
+ type: "data",
18304
+ bodyBase64: Buffer.from(data).toString("base64")
18305
+ });
18306
+ });
18307
+ socket.on("close", () => {
18308
+ if (!currentTcpSocket(sockets, control.socketId, socket)) {
18309
+ return;
18310
+ }
18311
+ sockets.delete(control.socketId);
18312
+ void pushEvent({ socketId: control.socketId, type: "close" });
18313
+ });
18314
+ socket.on("error", () => {
18315
+ if (!currentTcpSocket(sockets, control.socketId, socket)) {
18316
+ return;
18317
+ }
18318
+ sockets.delete(control.socketId);
18319
+ void pushEvent({
18320
+ socketId: control.socketId,
18321
+ type: "error",
18322
+ error: "forward_target_unavailable"
18323
+ });
18324
+ socket.destroy();
18325
+ });
18288
18326
  return;
18289
18327
  }
18290
- case "forward_websocket_send": {
18328
+ case "forward_tcp_send": {
18291
18329
  const stream = sockets.get(control.socketId);
18292
- if (stream === void 0 || stream.socket.readyState !== WebSocket.OPEN) {
18330
+ const body = decodeBase64Body(control.bodyBase64);
18331
+ if (body === void 0) {
18293
18332
  void pushEvent({
18294
18333
  socketId: control.socketId,
18295
18334
  type: "error",
18296
- error: "websocket_not_open"
18335
+ error: "invalid_forward_request"
18297
18336
  });
18298
18337
  return;
18299
18338
  }
18300
- const body = Buffer.from(control.bodyBase64, "base64");
18301
- switch (control.opcode) {
18302
- case "text":
18303
- stream.socket.send(body.toString());
18304
- return;
18305
- case "binary":
18306
- case "ping":
18307
- case "pong":
18308
- stream.socket.send(body);
18309
- return;
18339
+ if (stream === void 0 || stream.socket.destroyed === true || stream.socket.writable === false) {
18340
+ void pushEvent({
18341
+ socketId: control.socketId,
18342
+ type: "error",
18343
+ error: "tcp_socket_not_open"
18344
+ });
18345
+ return;
18310
18346
  }
18347
+ stream.socket.write(body);
18348
+ return;
18311
18349
  }
18312
- case "forward_websocket_close":
18350
+ case "forward_tcp_close":
18313
18351
  closeSocket(control.socketId);
18314
18352
  return;
18315
18353
  }
@@ -18321,105 +18359,11 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts, socketFactor
18321
18359
  }
18322
18360
  };
18323
18361
  }
18324
- function openLocalWebSocket(control, sockets, pushEvent, socketFactory, scheme) {
18325
- let opened = false;
18326
- const url = localForwardWebSocketUrl(
18327
- scheme,
18328
- control.port,
18329
- control.path,
18330
- control.queryString
18331
- );
18332
- const protocols = webSocketProtocols(control.headers);
18333
- const headers = webSocketHeaders(control.headers);
18334
- const websocket = socketFactory(url, protocols, headers);
18335
- websocket.binaryType = "arraybuffer";
18336
- const previousStream = sockets.get(control.socketId);
18337
- previousStream?.socket.close();
18338
- sockets.set(control.socketId, { socket: websocket });
18339
- websocket.addEventListener("open", () => {
18340
- if (!currentWebSocket(sockets, control.socketId, websocket)) {
18341
- return;
18342
- }
18343
- opened = true;
18344
- void pushEvent({ socketId: control.socketId, type: "open" });
18345
- });
18346
- websocket.addEventListener("message", (event) => {
18347
- if (!currentWebSocket(sockets, control.socketId, websocket)) {
18348
- return;
18349
- }
18350
- const body = typeof event.data === "string" ? Buffer.from(event.data) : Buffer.from(event.data);
18351
- void pushEvent({
18352
- socketId: control.socketId,
18353
- type: "message",
18354
- opcode: typeof event.data === "string" ? "text" : "binary",
18355
- bodyBase64: body.toString("base64")
18356
- });
18357
- });
18358
- websocket.addEventListener("close", (event) => {
18359
- if (!currentWebSocket(sockets, control.socketId, websocket)) {
18360
- return;
18361
- }
18362
- sockets.delete(control.socketId);
18363
- void pushEvent({
18364
- socketId: control.socketId,
18365
- type: "close",
18366
- code: event.code,
18367
- reason: event.reason
18368
- });
18369
- });
18370
- websocket.addEventListener("error", () => {
18371
- if (!currentWebSocket(sockets, control.socketId, websocket)) {
18372
- return;
18373
- }
18374
- sockets.delete(control.socketId);
18375
- if (!opened && scheme === "ws") {
18376
- openLocalWebSocket(control, sockets, pushEvent, socketFactory, "wss");
18377
- return;
18378
- }
18379
- void pushEvent({
18380
- socketId: control.socketId,
18381
- type: "error",
18382
- error: "websocket_error",
18383
- attemptedScheme: scheme
18384
- });
18385
- });
18386
- }
18387
- function defaultForwardWebSocketFactory(url, protocols, headers) {
18388
- if (headers === void 0) {
18389
- return protocols === void 0 ? new WebSocket(url) : new WebSocket(url, protocols);
18390
- }
18391
- const options = { headers };
18392
- return protocols === void 0 ? new NodeWebSocket(url, options) : new NodeWebSocket(url, protocols, options);
18393
- }
18394
- function currentWebSocket(sockets, socketId, socket) {
18362
+ function currentTcpSocket(sockets, socketId, socket) {
18395
18363
  return sockets.get(socketId)?.socket === socket;
18396
18364
  }
18397
- function webSocketProtocols(headers) {
18398
- if (!Array.isArray(headers)) {
18399
- return void 0;
18400
- }
18401
- const protocols = headers.flatMap((header) => {
18402
- if (!isWireHeader(header) || header.name.toLowerCase() !== "sec-websocket-protocol") {
18403
- return [];
18404
- }
18405
- return header.value.split(",").map((protocol) => protocol.trim()).filter((protocol) => protocol !== "");
18406
- });
18407
- return protocols.length === 0 ? void 0 : protocols;
18408
- }
18409
- function webSocketHeaders(headers) {
18410
- if (!Array.isArray(headers)) {
18411
- return void 0;
18412
- }
18413
- const forwarded = headers.reduce((acc, header) => {
18414
- if (isOctWebSocketHeader(header)) {
18415
- acc[header.name] = header.value;
18416
- }
18417
- return acc;
18418
- }, {});
18419
- return Object.keys(forwarded).length === 0 ? void 0 : forwarded;
18420
- }
18421
- function isOctWebSocketHeader(value) {
18422
- return isHeader(value) && value.name.toLowerCase().startsWith("x-oct-");
18365
+ function defaultForwardTcpFactory(port) {
18366
+ return connectTcp({ host: "127.0.0.1", port });
18423
18367
  }
18424
18368
  function localForwardUrl(scheme, port, path2, queryString) {
18425
18369
  const normalizedPath = path2.startsWith("/") ? path2 : `/${path2}`;
@@ -18429,12 +18373,6 @@ function localForwardUrl(scheme, port, path2, queryString) {
18429
18373
  }
18430
18374
  return url.toString();
18431
18375
  }
18432
- function localForwardWebSocketUrl(scheme, port, path2, queryString) {
18433
- const httpScheme = scheme === "ws" ? "http" : "https";
18434
- const url = new URL(localForwardUrl(httpScheme, port, path2, queryString));
18435
- url.protocol = `${scheme}:`;
18436
- return url.toString();
18437
- }
18438
18376
  async function fetchWithHttpsFallback(port, path2, queryString, request) {
18439
18377
  try {
18440
18378
  return await fetch(
@@ -21159,7 +21097,7 @@ function realpathOrResolved(pathValue) {
21159
21097
  }
21160
21098
 
21161
21099
  // src/version.ts
21162
- var linzumiCliVersion = "0.0.55-beta";
21100
+ var linzumiCliVersion = "0.0.56-beta";
21163
21101
  var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
21164
21102
 
21165
21103
  // src/runnerLock.ts
@@ -21665,6 +21603,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
21665
21603
  allowedCwds.value
21666
21604
  ),
21667
21605
  portForwarding: liveForwardPorts.size > 0,
21606
+ tcpForwarding: true,
21668
21607
  allowedPorts: Array.from(liveForwardPorts).sort(
21669
21608
  (left, right) => left - right
21670
21609
  ),
@@ -22013,64 +21952,6 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
22013
21952
  cleanup.actions.push(() => codex.close());
22014
21953
  const seq = { value: 0 };
22015
21954
  const discoveredCodexThreads = { value: [] };
22016
- const lastReportedDiscoveryFailure = { value: void 0 };
22017
- const reportClientError = async (args) => {
22018
- await kandan.push(topic, "client_error_report", {
22019
- id: `client-error-${randomUUID3()}`,
22020
- severity: args.severity ?? "error",
22021
- code: args.code,
22022
- operation: args.operation,
22023
- message: args.message,
22024
- occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
22025
- instanceId,
22026
- clientId,
22027
- runnerId: options.runnerId,
22028
- hostname: runnerHost,
22029
- cwd: options.cwd,
22030
- codexUrl,
22031
- workspace: runnerWorkspaceSlug(options) ?? null,
22032
- channel: options.channelSession?.channelSlug ?? null,
22033
- cliVersion: linzumiCliVersion,
22034
- details: args.details ?? {}
22035
- }).catch((error) => {
22036
- log("kandan.client_error_report_push_failed", {
22037
- code: args.code,
22038
- operation: args.operation,
22039
- message: error instanceof Error ? error.message : String(error)
22040
- });
22041
- });
22042
- };
22043
- const loadDiscoveredCodexThreads = async (phase) => {
22044
- try {
22045
- const threads = await discoverCodexThreads(codex, options.cwd);
22046
- lastReportedDiscoveryFailure.value = void 0;
22047
- return threads;
22048
- } catch (error) {
22049
- const message = error instanceof Error ? error.message : String(error);
22050
- const signature = `codex_thread_discovery_failed:${message}`;
22051
- log("codex.thread_discovery_failed", {
22052
- phase,
22053
- message
22054
- });
22055
- if (lastReportedDiscoveryFailure.value !== signature) {
22056
- lastReportedDiscoveryFailure.value = signature;
22057
- await reportClientError({
22058
- code: "codex_thread_discovery_failed",
22059
- operation: "codex_thread_discovery",
22060
- message,
22061
- details: {
22062
- phase,
22063
- codexUrl,
22064
- cwd: options.cwd
22065
- }
22066
- });
22067
- }
22068
- return discoveredCodexThreads.value;
22069
- }
22070
- };
22071
- if (options.channelSession === void 0) {
22072
- discoveredCodexThreads.value = await loadDiscoveredCodexThreads("initial");
22073
- }
22074
21955
  const runtimeDefaults = runnerRuntimeDefaults(options);
22075
21956
  const instancePayload = {
22076
21957
  instanceId,
@@ -22229,24 +22110,18 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
22229
22110
  message: error instanceof Error ? error.message : String(error)
22230
22111
  });
22231
22112
  });
22232
- const refreshDiscoveredCodexThreads = async (phase) => {
22233
- discoveredCodexThreads.value = await loadDiscoveredCodexThreads(phase);
22234
- await pushHeartbeat();
22235
- };
22236
22113
  const heartbeatInterval = setInterval(() => {
22237
- void (channelSession === void 0 ? refreshDiscoveredCodexThreads("heartbeat") : pushHeartbeat());
22114
+ void pushHeartbeat();
22238
22115
  }, 15e3);
22239
22116
  cleanup.actions.push(() => clearInterval(heartbeatInterval));
22240
- kandan.onReconnect(
22241
- () => channelSession === void 0 ? refreshDiscoveredCodexThreads("reconnect").then(() => void 0) : pushHeartbeat().then(() => void 0)
22242
- );
22117
+ kandan.onReconnect(() => pushHeartbeat().then(() => void 0));
22243
22118
  void pushHeartbeat();
22244
- const forwardWebSockets = createForwardWebSocketManager(
22119
+ const forwardTcp = createForwardTcpManager(
22245
22120
  kandan,
22246
22121
  topic,
22247
22122
  () => Array.from(liveForwardPorts)
22248
22123
  );
22249
- cleanup.actions.push(() => forwardWebSockets.close());
22124
+ cleanup.actions.push(() => forwardTcp.close());
22250
22125
  const channelCodexThreadId = channelSession?.currentCodexThreadId();
22251
22126
  if (options.launchTui && channelCodexThreadId !== void 0) {
22252
22127
  await prepareCodexThreadForTuiResume(codex, channelCodexThreadId);
@@ -22282,9 +22157,6 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
22282
22157
  });
22283
22158
  });
22284
22159
  }
22285
- if (channelSession === void 0 && notification.method === "thread/started") {
22286
- void refreshDiscoveredCodexThreads("thread_started");
22287
- }
22288
22160
  log("codex.notification", {
22289
22161
  method: notification.method,
22290
22162
  metadata
@@ -22343,8 +22215,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
22343
22215
  });
22344
22216
  return;
22345
22217
  }
22346
- if (isForwardWebSocketControl(control)) {
22347
- forwardWebSockets.handle(control);
22218
+ if (isForwardTcpControl(control)) {
22219
+ forwardTcp.handle(control);
22348
22220
  return;
22349
22221
  }
22350
22222
  if (isStartLocalEditorControl(control)) {
@@ -22548,99 +22420,6 @@ async function closeCleanupStack(cleanup) {
22548
22420
  })();
22549
22421
  return cleanup.closePromise;
22550
22422
  }
22551
- async function discoverCodexThreads(codex, _cwd) {
22552
- const response = await codex.request("thread/list", {
22553
- limit: CODEX_THREAD_DISCOVERY_LIMIT,
22554
- sortKey: "updated_at",
22555
- sortDirection: "desc",
22556
- archived: false,
22557
- useStateDbOnly: true
22558
- });
22559
- if ("error" in response) {
22560
- throw new Error(`thread/list failed: ${response.error.message}`);
22561
- }
22562
- const result = objectValue(response.result);
22563
- const data = arrayValue(result?.data)?.filter(isJsonObject) ?? [];
22564
- const rowsById = /* @__PURE__ */ new Map();
22565
- for (const thread of data.slice(0, CODEX_THREAD_DISCOVERY_LIMIT)) {
22566
- const row = codexThreadHistoryRow(thread, false);
22567
- const id = stringValue(objectValue(row)?.id);
22568
- if (id !== void 0 && id !== "" && !rowsById.has(id)) {
22569
- rowsById.set(id, row);
22570
- }
22571
- }
22572
- return Array.from(rowsById.values()).sort(compareCodexThreadHistoryRows);
22573
- }
22574
- var CODEX_THREAD_DISCOVERY_LIMIT = 50;
22575
- var CODEX_THREAD_TITLE_FIELD_LIMIT = 500;
22576
- var CODEX_THREAD_TEXT_FIELD_LIMIT = 2e3;
22577
- var CODEX_THREAD_PATH_FIELD_LIMIT = 1e3;
22578
- function codexThreadHistoryRow(thread, archived) {
22579
- const gitInfo = objectValue(thread.gitInfo);
22580
- const preview = codexThreadText(thread.preview, CODEX_THREAD_TEXT_FIELD_LIMIT).replace(/\s+/g, " ").trim();
22581
- const name = codexThreadText(thread.name, CODEX_THREAD_TITLE_FIELD_LIMIT);
22582
- const directTitle = codexThreadText(
22583
- thread.title,
22584
- CODEX_THREAD_TITLE_FIELD_LIMIT
22585
- );
22586
- const title = directTitle === "" ? name === "" ? preview : name : directTitle;
22587
- const description = codexThreadText(thread.description, CODEX_THREAD_TEXT_FIELD_LIMIT) || codexThreadText(thread.summary, CODEX_THREAD_TEXT_FIELD_LIMIT) || preview;
22588
- return {
22589
- id: codexThreadText(thread.id, CODEX_THREAD_PATH_FIELD_LIMIT),
22590
- title: title ?? "",
22591
- description: description ?? "",
22592
- preview,
22593
- cwd: codexThreadText(thread.cwd, CODEX_THREAD_PATH_FIELD_LIMIT),
22594
- source: codexThreadText(thread.source, CODEX_THREAD_TITLE_FIELD_LIMIT),
22595
- createdAt: codexTimestamp(thread.createdAt),
22596
- updatedAt: codexTimestamp(thread.updatedAt),
22597
- archived,
22598
- modelProvider: codexThreadText(
22599
- thread.modelProvider,
22600
- CODEX_THREAD_TITLE_FIELD_LIMIT
22601
- ),
22602
- cliVersion: codexThreadText(
22603
- thread.cliVersion,
22604
- CODEX_THREAD_TITLE_FIELD_LIMIT
22605
- ),
22606
- gitBranch: codexThreadText(
22607
- gitInfo?.branch,
22608
- CODEX_THREAD_TITLE_FIELD_LIMIT
22609
- ),
22610
- gitOriginUrl: codexThreadText(
22611
- gitInfo?.originUrl,
22612
- CODEX_THREAD_PATH_FIELD_LIMIT
22613
- ),
22614
- path: codexThreadText(thread.path, CODEX_THREAD_PATH_FIELD_LIMIT)
22615
- };
22616
- }
22617
- function codexThreadText(value, maxLength) {
22618
- const text2 = stringValue(value) ?? "";
22619
- if (text2.length <= maxLength) {
22620
- return text2;
22621
- }
22622
- return `${text2.slice(0, Math.max(0, maxLength - 3))}...`;
22623
- }
22624
- function codexTimestamp(value) {
22625
- if (typeof value === "number" && Number.isFinite(value)) {
22626
- return new Date(value * 1e3).toISOString();
22627
- }
22628
- return stringValue(value) ?? "";
22629
- }
22630
- function compareCodexThreadHistoryRows(left, right) {
22631
- const leftRow = objectValue(left);
22632
- const rightRow = objectValue(right);
22633
- const leftTime = Date.parse(
22634
- stringValue(leftRow?.createdAt) ?? stringValue(leftRow?.updatedAt) ?? ""
22635
- );
22636
- const rightTime = Date.parse(
22637
- stringValue(rightRow?.createdAt) ?? stringValue(rightRow?.updatedAt) ?? ""
22638
- );
22639
- return safeComparableTime(leftTime) - safeComparableTime(rightTime);
22640
- }
22641
- function safeComparableTime(value) {
22642
- return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
22643
- }
22644
22423
  function extractStartedThreadId(response) {
22645
22424
  if ("error" in response) {
22646
22425
  throw new Error(`thread/start failed: ${response.error.message}`);
@@ -23023,9 +22802,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
23023
22802
  case "update_session_settings":
23024
22803
  case "set_port_forward_enabled":
23025
22804
  case "forward_http_request":
23026
- case "forward_websocket_open":
23027
- case "forward_websocket_send":
23028
- case "forward_websocket_close":
22805
+ case "forward_tcp_open":
22806
+ case "forward_tcp_send":
22807
+ case "forward_tcp_close":
23029
22808
  case "start_local_editor":
23030
22809
  case "update_runner_config":
23031
22810
  return { instanceId, controlType: control.type, skipped: true };
@@ -27278,6 +27057,16 @@ import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
27278
27057
  import { emitKeypressEvents } from "node:readline";
27279
27058
 
27280
27059
  // src/signupServerClient.ts
27060
+ var SignupServerError = class extends Error {
27061
+ status;
27062
+ code;
27063
+ constructor(args) {
27064
+ super(args.message);
27065
+ this.name = "SignupServerError";
27066
+ this.status = args.status;
27067
+ this.code = args.code;
27068
+ }
27069
+ };
27281
27070
  function createSignupServerClient(args) {
27282
27071
  const serviceUrl = args.serviceUrl ?? defaultLinzumiHttpUrl;
27283
27072
  const fetchImpl = args.fetchImpl ?? fetch;
@@ -27368,9 +27157,7 @@ async function signupJsonRequest(args) {
27368
27157
  });
27369
27158
  const responseText = await response.text();
27370
27159
  if (!response.ok) {
27371
- throw new Error(
27372
- `Linzumi signup request failed with HTTP ${response.status}${errorResponseDetail(responseText)}`
27373
- );
27160
+ throw signupResponseError(response.status, responseText);
27374
27161
  }
27375
27162
  const body = parseJsonResponse(responseText, "Linzumi signup response");
27376
27163
  return args.render(body);
@@ -27389,6 +27176,38 @@ function errorResponseDetail(text2) {
27389
27176
  }
27390
27177
  return `: ${trimmed.slice(0, 240)}`;
27391
27178
  }
27179
+ function signupResponseError(status, text2) {
27180
+ const code = signupErrorCodeFromResponseText(text2);
27181
+ if (code !== void 0) {
27182
+ return new SignupServerError({
27183
+ status,
27184
+ code,
27185
+ message: code
27186
+ });
27187
+ }
27188
+ return new Error(
27189
+ `Linzumi signup request failed with HTTP ${status}${errorResponseDetail(text2)}`
27190
+ );
27191
+ }
27192
+ function signupErrorCodeFromResponseText(text2) {
27193
+ const trimmed = text2.trim();
27194
+ if (trimmed === "") {
27195
+ return void 0;
27196
+ }
27197
+ try {
27198
+ const value = JSON.parse(trimmed);
27199
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
27200
+ return void 0;
27201
+ }
27202
+ const error = value.error;
27203
+ if (typeof error === "string" && error.trim() !== "") {
27204
+ return error;
27205
+ }
27206
+ return void 0;
27207
+ } catch (_error) {
27208
+ return void 0;
27209
+ }
27210
+ }
27392
27211
  function renderEmailCodeStart(value) {
27393
27212
  const body = objectRecord(value, "signup email-code start response");
27394
27213
  return {
@@ -27576,6 +27395,7 @@ function booleanValue(record, key) {
27576
27395
  }
27577
27396
 
27578
27397
  // src/signupFlow.ts
27398
+ var maxSignupVerificationCodeAttempts = 3;
27579
27399
  var blue = (value) => `\x1B[38;5;75m${value}\x1B[0m`;
27580
27400
  var green = (value) => `\x1B[38;5;114m${value}\x1B[0m`;
27581
27401
  var red = (value) => `\x1B[38;5;203m${value}\x1B[0m`;
@@ -28108,22 +27928,19 @@ async function runSignupFlow(deps = {}) {
28108
27928
  writeDebugLaunchPayload(output, state, debugLaunchPayload);
28109
27929
  return;
28110
27930
  }
28111
- writeVerificationPrompt(output, codeRequest.request.email);
28112
- const code = await prompts.input({
28113
- message: "Enter your Linzumi verification code",
28114
- defaultValue: signupServerClient === void 0 ? "482931" : ""
28115
- });
28116
- const verifyResult = signupServerClient === void 0 ? void 0 : await verifyEmailCodeForSignup(signupServerClient, {
27931
+ const verificationResult = await promptForVerifiedEmailCode({
27932
+ prompts,
27933
+ output,
27934
+ signupServerClient,
28117
27935
  pendingId: codeRequest.request.pendingId,
28118
- code
27936
+ email: codeRequest.request.email
28119
27937
  });
28120
- if (verifyResult?.type === "failed") {
27938
+ if (verificationResult.type === "failed") {
28121
27939
  state = updateSignupState(state, {
28122
27940
  currentStep: "launch",
28123
- verificationCode: code,
28124
- launchFailure: verifyResult.message
27941
+ verificationCode: verificationResult.code,
27942
+ launchFailure: verificationResult.message
28125
27943
  });
28126
- writeCodeEntry(output, code);
28127
27944
  writeSignupScreen(output, state);
28128
27945
  writePreflightSummary(output, state);
28129
27946
  writeSelectedProjectsSummary(output, state);
@@ -28132,14 +27949,13 @@ async function runSignupFlow(deps = {}) {
28132
27949
  writeDebugLaunchPayload(output, state, debugLaunchPayload);
28133
27950
  return;
28134
27951
  }
28135
- verifiedAuth = verifyResult?.auth;
27952
+ verifiedAuth = verificationResult.verifiedAuth;
28136
27953
  const storedSignupAuth = verifiedAuth === void 0 ? writeSignupAuth({ email }) : signupAuthFromVerification(verifiedAuth, serviceUrl);
28137
27954
  state = updateSignupState(state, {
28138
27955
  currentStep: "launch",
28139
27956
  storedSignupAuth,
28140
- verificationCode: code
27957
+ verificationCode: verificationResult.code
28141
27958
  });
28142
- writeCodeEntry(output, code);
28143
27959
  }
28144
27960
  if (signupServerClient !== void 0 && verifiedAuth !== void 0) {
28145
27961
  const defaultWorkspaceName = verifiedAuth.privateWorkspaceDefault.name || defaultWorkspaceNameForEmail(
@@ -28335,7 +28151,8 @@ async function verifyEmailCodeForSignup(signupServerClient, args) {
28335
28151
  message: signupServerFailureMessage(
28336
28152
  "We couldn't verify your Linzumi email code",
28337
28153
  error
28338
- )
28154
+ ),
28155
+ code: signupServerFailureCode(error)
28339
28156
  };
28340
28157
  }
28341
28158
  }
@@ -28343,6 +28160,43 @@ function signupServerFailureMessage(prefix, error) {
28343
28160
  const message = error instanceof Error ? error.message : String(error);
28344
28161
  return `${prefix}: ${message}`;
28345
28162
  }
28163
+ function signupServerFailureCode(error) {
28164
+ if (error instanceof SignupServerError) {
28165
+ return error.code;
28166
+ }
28167
+ if (error instanceof Error && error.message === "invalid_signup_code") {
28168
+ return "invalid_signup_code";
28169
+ }
28170
+ return void 0;
28171
+ }
28172
+ async function promptForVerifiedEmailCode(args) {
28173
+ writeVerificationPrompt(args.output, args.email);
28174
+ return verifyPromptAttempt(args, 1);
28175
+ }
28176
+ async function verifyPromptAttempt(args, attempt) {
28177
+ const code = await args.prompts.input({
28178
+ message: "Enter your Linzumi verification code",
28179
+ defaultValue: args.signupServerClient === void 0 ? "482931" : ""
28180
+ });
28181
+ const verifyResult = args.signupServerClient === void 0 ? void 0 : await verifyEmailCodeForSignup(args.signupServerClient, {
28182
+ pendingId: args.pendingId,
28183
+ code
28184
+ });
28185
+ writeCodeEntry(args.output, code);
28186
+ if (verifyResult?.type === "failed") {
28187
+ if (verifyResult.code === "invalid_signup_code" && attempt < maxSignupVerificationCodeAttempts) {
28188
+ writeBoundedLine(
28189
+ args.output,
28190
+ muted(
28191
+ " That code was not valid. Enter the newest 6-digit code from your email."
28192
+ )
28193
+ );
28194
+ return verifyPromptAttempt(args, attempt + 1);
28195
+ }
28196
+ return { type: "failed", message: verifyResult.message, code };
28197
+ }
28198
+ return verifyResult === void 0 ? { type: "completed", code } : { type: "completed", code, verifiedAuth: verifyResult.auth };
28199
+ }
28346
28200
  async function refreshExistingSignupAuth(prompts, output, auth, writeSignupAuth, signupServerClient, serviceUrl) {
28347
28201
  output.write(
28348
28202
  `${green("\u2713")} You're already logged in!
@@ -28361,25 +28215,29 @@ ${muted(
28361
28215
  if (codeRequest.type === "failed") {
28362
28216
  return codeRequest;
28363
28217
  }
28364
- writeVerificationPrompt(output, codeRequest.request.email);
28365
- const code = await prompts.input({
28366
- message: "Enter your Linzumi verification code",
28367
- defaultValue: signupServerClient === void 0 ? "482931" : ""
28368
- });
28369
- const verifyResult = signupServerClient === void 0 ? void 0 : await verifyEmailCodeForSignup(signupServerClient, {
28218
+ const verificationResult = await promptForVerifiedEmailCode({
28219
+ prompts,
28220
+ output,
28221
+ signupServerClient,
28370
28222
  pendingId: codeRequest.request.pendingId,
28371
- code
28223
+ email: codeRequest.request.email
28372
28224
  });
28373
- if (verifyResult?.type === "failed") {
28374
- writeCodeEntry(output, code);
28225
+ if (verificationResult.type === "failed") {
28375
28226
  output.write("\n");
28376
- return verifyResult;
28227
+ return {
28228
+ type: "failed",
28229
+ message: verificationResult.message
28230
+ };
28377
28231
  }
28378
- const verifiedAuth = verifyResult?.auth;
28232
+ const verifiedAuth = verificationResult.verifiedAuth;
28379
28233
  const refreshedAuth = verifiedAuth === void 0 ? writeSignupAuth({ email: auth.email }) : signupAuthFromVerification(verifiedAuth, serviceUrl);
28380
- writeCodeEntry(output, code);
28381
28234
  output.write("\n");
28382
- return { type: "completed", auth: refreshedAuth, code, verifiedAuth };
28235
+ return {
28236
+ type: "completed",
28237
+ auth: refreshedAuth,
28238
+ code: verificationResult.code,
28239
+ verifiedAuth
28240
+ };
28383
28241
  }
28384
28242
  async function validateExistingSignupAuth(signupServerClient, auth, retryPolicy) {
28385
28243
  if (signupServerClient === void 0 || auth.accessToken === void 0) {
@@ -30251,7 +30109,7 @@ function runProcessCapture(args) {
30251
30109
  child.on("error", () => {
30252
30110
  finish("");
30253
30111
  });
30254
- child.on("exit", (code) => {
30112
+ child.on("close", (code) => {
30255
30113
  finish(code === 0 ? stdout : "");
30256
30114
  });
30257
30115
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.55-beta",
3
+ "version": "0.0.56-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": {