@linzumi/cli 0.0.32-beta → 0.0.34-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 +30 -292
  2. package/dist/index.js +647 -151
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import { randomUUID as randomUUID3 } from "node:crypto";
3
3
  import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync5 } from "node:fs";
4
- import { homedir as homedir8 } from "node:os";
4
+ import { homedir as homedir9 } from "node:os";
5
5
  import { resolve as resolve8 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7
7
 
@@ -9,7 +9,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
9
9
  import { spawn as spawn6 } from "node:child_process";
10
10
  import { randomUUID as randomUUID2 } from "node:crypto";
11
11
  import { hostname as hostname2 } from "node:os";
12
- import { join as join5 } from "node:path";
12
+ import { join as join6 } from "node:path";
13
13
 
14
14
  // src/channelSessionSupport.ts
15
15
  import { spawnSync } from "node:child_process";
@@ -1872,7 +1872,11 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
1872
1872
  }
1873
1873
  state.approvedForwardPorts.add(request.port);
1874
1874
  state.approvedForwardTargets.set(request.port, approvedTargetFromRequest(request));
1875
- const capabilities = args.options.onForwardPortApproved?.(request.port);
1875
+ const capabilities = args.options.onForwardPortApproved?.(request.port, {
1876
+ kandanThreadId: state.kandanThreadId ?? null,
1877
+ codexThreadId: state.codexThreadId ?? null,
1878
+ channelSlug: args.options.channelSession.channelSlug ?? null
1879
+ });
1876
1880
  await publishForwardPortResolvedEvent(args, request, capabilities);
1877
1881
  await publishMessageStateForPortForwardResult(args, state, request, "processed");
1878
1882
  await publishPortForwardReadyMessage(args, state, payloadContext, request);
@@ -3959,6 +3963,179 @@ async function pushOptional(kandan, topic, event, payload, log) {
3959
3963
  // src/codexAppServer.ts
3960
3964
  import { spawn } from "node:child_process";
3961
3965
  import { createServer } from "node:net";
3966
+
3967
+ // src/runnerLogger.ts
3968
+ import { appendFileSync, openSync } from "node:fs";
3969
+ import { createWriteStream } from "node:fs";
3970
+ import { homedir } from "node:os";
3971
+ import { dirname, join as join2 } from "node:path";
3972
+ import { mkdirSync } from "node:fs";
3973
+ var sensitiveMarker = "<SENSITIVE_DATA>";
3974
+ var sensitiveQueryParams = new Set([
3975
+ "access_token",
3976
+ "authorization",
3977
+ "code",
3978
+ "cookie",
3979
+ "kandan_preview_ticket",
3980
+ "refresh_token",
3981
+ "token"
3982
+ ]);
3983
+ var sensitiveArgFlags = new Set([
3984
+ "--access-token",
3985
+ "--api-key",
3986
+ "--authorization",
3987
+ "--cookie",
3988
+ "--oauth-code",
3989
+ "--password",
3990
+ "--refresh-token",
3991
+ "--secret",
3992
+ "--token"
3993
+ ]);
3994
+ function createRunnerLogger(logFile, consoleReporter) {
3995
+ mkdirSync(dirname(logFile), { recursive: true });
3996
+ const fd = openSync(logFile, "a");
3997
+ const stream = createWriteStream("", { fd, flags: "a", autoClose: true });
3998
+ const logger = (event, payload) => {
3999
+ const redacted = redactForCliLog(payload);
4000
+ stream.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...redacted })}
4001
+ `, "utf8");
4002
+ consoleReporter?.(event, redacted);
4003
+ };
4004
+ Object.defineProperty(logger, "close", {
4005
+ value: () => closeStream(stream)
4006
+ });
4007
+ return logger;
4008
+ }
4009
+ function writeCliAuditEvent(event, payload, options = {}) {
4010
+ const logFile = options.logFile ?? defaultCliAuditLogFile();
4011
+ try {
4012
+ mkdirSync(dirname(logFile), { recursive: true });
4013
+ appendFileSync(logFile, `${JSON.stringify({
4014
+ ts: new Date().toISOString(),
4015
+ event,
4016
+ ...options.sessionId === undefined ? {} : { sessionId: options.sessionId },
4017
+ ...redactForCliLog(payload)
4018
+ })}
4019
+ `, "utf8");
4020
+ } catch (_error) {
4021
+ return;
4022
+ }
4023
+ }
4024
+ function defaultCliAuditLogFile() {
4025
+ const override = process.env.LINZUMI_CLI_AUDIT_LOG?.trim();
4026
+ return override === undefined || override === "" ? join2(homedir(), ".linzumi", "logs", "command-events.jsonl") : override;
4027
+ }
4028
+ function redactForCliLog(value) {
4029
+ return redactValue(value, undefined);
4030
+ }
4031
+ function redactValue(value, key) {
4032
+ if (sensitiveKey(key)) {
4033
+ return sensitiveMarker;
4034
+ }
4035
+ if (typeof value === "string") {
4036
+ return redactString(value);
4037
+ }
4038
+ if (Array.isArray(value)) {
4039
+ return key === "args" ? redactArgs(value) : value.map((item) => redactValue(item, undefined));
4040
+ }
4041
+ if (value !== null && typeof value === "object") {
4042
+ return redactObject(value);
4043
+ }
4044
+ return value;
4045
+ }
4046
+ function redactObject(value) {
4047
+ const headerName = typeof value.name === "string" ? value.name.toLowerCase() : undefined;
4048
+ const shouldRedactHeaderValue = headerName === "authorization" || headerName === "cookie" || headerName === "set-cookie";
4049
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
4050
+ key,
4051
+ shouldRedactHeaderValue && key === "value" ? sensitiveMarker : redactValue(entry, key)
4052
+ ]));
4053
+ }
4054
+ function redactArgs(args) {
4055
+ let redactNext = false;
4056
+ return args.map((arg) => {
4057
+ if (typeof arg !== "string") {
4058
+ redactNext = false;
4059
+ return redactValue(arg, undefined);
4060
+ }
4061
+ if (redactNext) {
4062
+ redactNext = false;
4063
+ return sensitiveMarker;
4064
+ }
4065
+ const [flag, value] = splitArgAssignment(arg);
4066
+ if (sensitiveArgFlags.has(flag)) {
4067
+ if (value === undefined) {
4068
+ redactNext = true;
4069
+ return arg;
4070
+ }
4071
+ return `${flag}=${sensitiveMarker}`;
4072
+ }
4073
+ return redactString(arg);
4074
+ });
4075
+ }
4076
+ function splitArgAssignment(value) {
4077
+ const index = value.indexOf("=");
4078
+ return index === -1 ? [value, undefined] : [value.slice(0, index), value.slice(index + 1)];
4079
+ }
4080
+ function sensitiveKey(key) {
4081
+ if (key === undefined) {
4082
+ return false;
4083
+ }
4084
+ return /^(authorization|cookie|set-cookie|password)$/i.test(key) || /(^|[_-])(access[_-]?token|api[_-]?key|auth[_-]?token|oauth[_-]?code|refresh[_-]?token|secret|token)$/i.test(key);
4085
+ }
4086
+ function redactString(value) {
4087
+ const withRedactedQuery = redactUrlQuery(value);
4088
+ return withRedactedQuery.replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, sensitiveMarker);
4089
+ }
4090
+ function redactUrlQuery(value) {
4091
+ let parsed;
4092
+ try {
4093
+ parsed = new URL(value);
4094
+ } catch (_error) {
4095
+ return redactRelativeUrlQuery(value);
4096
+ }
4097
+ for (const name of [...parsed.searchParams.keys()]) {
4098
+ if (sensitiveQueryParams.has(name.toLowerCase())) {
4099
+ parsed.searchParams.set(name, sensitiveMarker);
4100
+ }
4101
+ }
4102
+ return parsed.toString();
4103
+ }
4104
+ function redactRelativeUrlQuery(value) {
4105
+ const queryStart = value.indexOf("?");
4106
+ const hashStart = value.indexOf("#");
4107
+ if (queryStart === -1 || hashStart !== -1 && hashStart < queryStart) {
4108
+ return value;
4109
+ }
4110
+ const path = value.slice(0, queryStart);
4111
+ const query = hashStart === -1 ? value.slice(queryStart + 1) : value.slice(queryStart + 1, hashStart);
4112
+ const hash = hashStart === -1 ? "" : value.slice(hashStart);
4113
+ const searchParams = new URLSearchParams(query);
4114
+ let redacted = false;
4115
+ for (const name of [...searchParams.keys()]) {
4116
+ if (sensitiveQueryParams.has(name.toLowerCase())) {
4117
+ searchParams.set(name, sensitiveMarker);
4118
+ redacted = true;
4119
+ }
4120
+ }
4121
+ switch (redacted) {
4122
+ case true:
4123
+ return `${path}?${searchParams.toString()}${hash}`;
4124
+ case false:
4125
+ return value;
4126
+ }
4127
+ }
4128
+ function closeStream(stream) {
4129
+ if (stream.closed || stream.destroyed) {
4130
+ return Promise.resolve();
4131
+ }
4132
+ return new Promise((resolve2, reject) => {
4133
+ stream.once("error", reject);
4134
+ stream.end(resolve2);
4135
+ });
4136
+ }
4137
+
4138
+ // src/codexAppServer.ts
3962
4139
  async function chooseLoopbackPort() {
3963
4140
  return new Promise((resolve2, reject) => {
3964
4141
  const server = createServer();
@@ -3984,12 +4161,35 @@ async function chooseLoopbackPort() {
3984
4161
  async function startCodexAppServer(codexBin, cwd, options = {}) {
3985
4162
  const port = await chooseLoopbackPort();
3986
4163
  const url = `ws://127.0.0.1:${port}`;
3987
- const child = spawn(codexBin, codexAppServerArgs(url, options), {
4164
+ const args = codexAppServerArgs(url, options);
4165
+ writeCliAuditEvent("process.spawn", {
4166
+ command: codexBin,
4167
+ args,
4168
+ cwd,
4169
+ purpose: "codex.app_server"
4170
+ });
4171
+ const child = spawn(codexBin, args, {
3988
4172
  cwd,
3989
4173
  env: process.env,
3990
4174
  stdio: ["ignore", "inherit", "inherit"]
3991
4175
  });
3992
- child.once("exit", (code) => {
4176
+ writeCliAuditEvent("process.spawned", {
4177
+ command: codexBin,
4178
+ args,
4179
+ cwd,
4180
+ pid: child.pid,
4181
+ purpose: "codex.app_server"
4182
+ });
4183
+ child.once("exit", (code, signal) => {
4184
+ writeCliAuditEvent("process.exit", {
4185
+ command: codexBin,
4186
+ args,
4187
+ cwd,
4188
+ pid: child.pid,
4189
+ code,
4190
+ signal,
4191
+ purpose: "codex.app_server"
4192
+ });
3993
4193
  if (code !== 0) {
3994
4194
  process.stderr.write(`codex app-server exited with code ${code ?? "signal"}
3995
4195
  `);
@@ -4218,21 +4418,21 @@ function readyzUrlForWebsocket(websocketUrl) {
4218
4418
  // src/codexProjectTrust.ts
4219
4419
  import {
4220
4420
  existsSync,
4221
- mkdirSync,
4421
+ mkdirSync as mkdirSync2,
4222
4422
  readFileSync,
4223
4423
  realpathSync,
4224
4424
  writeFileSync
4225
4425
  } from "node:fs";
4226
- import { homedir } from "node:os";
4227
- import { join as join2, resolve as resolve2 } from "node:path";
4426
+ import { homedir as homedir2 } from "node:os";
4427
+ import { join as join3, resolve as resolve2 } from "node:path";
4228
4428
  function ensureCodexProjectTrusted(projectPath, options = {}) {
4229
4429
  const trustedPath = realpathSync(resolve2(projectPath));
4230
- const configHome = options.configHome ?? process.env.CODEX_HOME ?? join2(homedir(), ".codex");
4231
- const configPath = join2(configHome, "config.toml");
4430
+ const configHome = options.configHome ?? process.env.CODEX_HOME ?? join3(homedir2(), ".codex");
4431
+ const configPath = join3(configHome, "config.toml");
4232
4432
  const currentConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
4233
4433
  const nextConfig = codexConfigWithTrustedProject(currentConfig, trustedPath);
4234
4434
  if (nextConfig !== currentConfig) {
4235
- mkdirSync(configHome, { recursive: true });
4435
+ mkdirSync2(configHome, { recursive: true });
4236
4436
  writeFileSync(configPath, nextConfig);
4237
4437
  }
4238
4438
  return trustedPath;
@@ -4279,7 +4479,7 @@ function trimTrailingNewlines(value) {
4279
4479
 
4280
4480
  // src/localCapabilities.ts
4281
4481
  import { realpathSync as realpathSync2 } from "node:fs";
4282
- import { homedir as homedir2 } from "node:os";
4482
+ import { homedir as homedir3 } from "node:os";
4283
4483
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
4284
4484
  function parseAllowedCwdList(value) {
4285
4485
  if (value === undefined) {
@@ -4319,10 +4519,10 @@ function assertConfiguredAllowedCwds(paths) {
4319
4519
  }
4320
4520
  function expandUserPath(pathValue) {
4321
4521
  if (pathValue === "~") {
4322
- return homedir2();
4522
+ return homedir3();
4323
4523
  }
4324
4524
  if (pathValue.startsWith("~/")) {
4325
- return resolve3(homedir2(), pathValue.slice(2));
4525
+ return resolve3(homedir3(), pathValue.slice(2));
4326
4526
  }
4327
4527
  return pathValue;
4328
4528
  }
@@ -4368,6 +4568,7 @@ async function handleForwardHttpRequest(control, allowedPorts) {
4368
4568
  const request = {
4369
4569
  method: control.method,
4370
4570
  headers: requestHeaders(control.headers),
4571
+ redirect: "manual",
4371
4572
  ...bodyDecision.body === undefined ? {} : { body: bodyDecision.body }
4372
4573
  };
4373
4574
  const response = await fetchWithHttpsFallback(control.port, control.path, control.queryString, request);
@@ -4458,6 +4659,7 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
4458
4659
  const url = localForwardUrl(scheme === "ws" ? "http" : "https", control.port, control.path, control.queryString).replace(/^http/, scheme);
4459
4660
  const protocols = webSocketProtocols(control.headers);
4460
4661
  const websocket = protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
4662
+ websocket.binaryType = "arraybuffer";
4461
4663
  sockets.set(control.socketId, websocket);
4462
4664
  websocket.addEventListener("open", () => {
4463
4665
  opened = true;
@@ -4641,13 +4843,13 @@ import { spawn as spawn2 } from "node:child_process";
4641
4843
  import {
4642
4844
  cpSync,
4643
4845
  existsSync as existsSync2,
4644
- mkdirSync as mkdirSync2,
4846
+ mkdirSync as mkdirSync3,
4645
4847
  mkdtempSync,
4646
4848
  realpathSync as realpathSync3,
4647
4849
  writeFileSync as writeFileSync2
4648
4850
  } from "node:fs";
4649
4851
  import { tmpdir } from "node:os";
4650
- import { basename as basename3, delimiter, dirname, join as join3 } from "node:path";
4852
+ import { basename as basename3, delimiter, dirname as dirname2, join as join4 } from "node:path";
4651
4853
  function isStartLocalEditorControl(control) {
4652
4854
  return control.type === "start_local_editor";
4653
4855
  }
@@ -4727,7 +4929,7 @@ async function startLocalEditor(control, options) {
4727
4929
  reason: launchResult.reason
4728
4930
  };
4729
4931
  }
4730
- const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime);
4932
+ const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime, options.runnerId);
4731
4933
  if (!collaborationResult.ok) {
4732
4934
  return {
4733
4935
  ok: false,
@@ -4735,11 +4937,35 @@ async function startLocalEditor(control, options) {
4735
4937
  reason: "code_server_spawn_failed"
4736
4938
  };
4737
4939
  }
4940
+ writeCliAuditEvent("process.spawn", {
4941
+ command: launchResult.command,
4942
+ args: launchResult.args,
4943
+ cwd: cwdDecision.cwd,
4944
+ purpose: "local_editor.code_server"
4945
+ }, { sessionId: options.runnerId });
4738
4946
  const child = spawn2(launchResult.command, [...launchResult.args], {
4739
4947
  cwd: cwdDecision.cwd,
4740
- env: codeServerEnv(process.env, profileResult.userDataDir, collaborationResult.collaboration),
4948
+ env: codeServerEnv(process.env, cwdDecision.cwd, profileResult.userDataDir, collaborationResult.collaboration),
4741
4949
  stdio: ["ignore", "inherit", "inherit"]
4742
4950
  });
4951
+ writeCliAuditEvent("process.spawned", {
4952
+ command: launchResult.command,
4953
+ args: launchResult.args,
4954
+ cwd: cwdDecision.cwd,
4955
+ pid: child.pid,
4956
+ purpose: "local_editor.code_server"
4957
+ }, { sessionId: options.runnerId });
4958
+ child.once("exit", (code, signal) => {
4959
+ writeCliAuditEvent("process.exit", {
4960
+ command: launchResult.command,
4961
+ args: launchResult.args,
4962
+ cwd: cwdDecision.cwd,
4963
+ pid: child.pid,
4964
+ code,
4965
+ signal,
4966
+ purpose: "local_editor.code_server"
4967
+ }, { sessionId: options.runnerId });
4968
+ });
4743
4969
  const exited = waitForCodeServerExit(child);
4744
4970
  const spawnResult = await waitForCodeServerSpawn(child);
4745
4971
  if (spawnResult === "failed") {
@@ -4810,17 +5036,17 @@ function codeServerArgs(port, cwd, userDataDir, extensionsDir) {
4810
5036
  }
4811
5037
  function prepareCodeServerProfile(collaboration, editorRuntime) {
4812
5038
  try {
4813
- const userDataDir = mkdtempSync(join3(tmpdir(), "kandan-local-editor-"));
4814
- const extensionsDir = join3(userDataDir, "extensions");
4815
- const collaborationServerDir = join3(userDataDir, "collaboration-server");
4816
- const userSettingsDir = join3(userDataDir, "User");
4817
- mkdirSync2(userSettingsDir, { recursive: true });
4818
- mkdirSync2(extensionsDir, { recursive: true });
4819
- mkdirSync2(collaborationServerDir, { recursive: true });
5039
+ const userDataDir = mkdtempSync(join4(tmpdir(), "kandan-local-editor-"));
5040
+ const extensionsDir = join4(userDataDir, "extensions");
5041
+ const collaborationServerDir = join4(userDataDir, "collaboration-server");
5042
+ const userSettingsDir = join4(userDataDir, "User");
5043
+ mkdirSync3(userSettingsDir, { recursive: true });
5044
+ mkdirSync3(extensionsDir, { recursive: true });
5045
+ mkdirSync3(collaborationServerDir, { recursive: true });
4820
5046
  if (editorRuntime !== undefined) {
4821
- installDirectory(editorRuntime.assets.documentStateExtensionDir, join3(extensionsDir, "kandan.document-state-telemetry"));
5047
+ installDirectory(editorRuntime.assets.documentStateExtensionDir, join4(extensionsDir, "kandan.document-state-telemetry"));
4822
5048
  }
4823
- writeFileSync2(join3(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
5049
+ writeFileSync2(join4(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
4824
5050
  return { ok: true, userDataDir, extensionsDir, collaborationServerDir };
4825
5051
  } catch (_error) {
4826
5052
  return { ok: false, reason: "code_server_spawn_failed" };
@@ -4886,13 +5112,16 @@ function prepareLinuxCodeServerLaunch(options) {
4886
5112
  "/tmp",
4887
5113
  "--setenv",
4888
5114
  "HOME",
4889
- options.userDataDir,
5115
+ options.cwd,
5116
+ "--setenv",
5117
+ "PWD",
5118
+ options.cwd,
4890
5119
  "--setenv",
4891
5120
  "XDG_DATA_HOME",
4892
- join3(options.userDataDir, "data"),
5121
+ join4(options.userDataDir, "data"),
4893
5122
  "--setenv",
4894
5123
  "XDG_CONFIG_HOME",
4895
- join3(options.userDataDir, "config"),
5124
+ join4(options.userDataDir, "config"),
4896
5125
  ...readOnlyRoots.flatMap((path) => ["--ro-bind-try", path, path]),
4897
5126
  "--bind",
4898
5127
  options.cwd,
@@ -4965,12 +5194,12 @@ function resolveCodeServerExecutable(command, envPath) {
4965
5194
  if (directory.trim() === "") {
4966
5195
  continue;
4967
5196
  }
4968
- const candidate = join3(directory, command);
5197
+ const candidate = join4(directory, command);
4969
5198
  if (!existsSync2(candidate)) {
4970
5199
  continue;
4971
5200
  }
4972
5201
  const realpath = realpathSync3(candidate);
4973
- return { ok: true, command: realpath, directory: dirname(realpath) };
5202
+ return { ok: true, command: realpath, directory: dirname2(realpath) };
4974
5203
  }
4975
5204
  return { ok: false };
4976
5205
  }
@@ -4979,10 +5208,10 @@ function hasPathSeparator(path) {
4979
5208
  }
4980
5209
  function safeRealpathDir(path) {
4981
5210
  try {
4982
- const directory = dirname(realpathSync3(path));
5211
+ const directory = dirname2(realpathSync3(path));
4983
5212
  return directory === "/" ? undefined : directory;
4984
5213
  } catch (_error) {
4985
- const directory = dirname(path);
5214
+ const directory = dirname2(path);
4986
5215
  return directory === "." || directory === "/" ? undefined : directory;
4987
5216
  }
4988
5217
  }
@@ -5032,7 +5261,7 @@ function codeServerSettings(collaboration) {
5032
5261
  "workbench.welcomePage.walkthroughs.openOnInstall": false
5033
5262
  };
5034
5263
  }
5035
- async function startCollaborationSidecar(collaboration, profile, editorRuntime) {
5264
+ async function startCollaborationSidecar(collaboration, profile, editorRuntime, runnerId) {
5036
5265
  if (collaboration === undefined) {
5037
5266
  return { ok: true, ready: Promise.resolve("ready") };
5038
5267
  }
@@ -5041,13 +5270,22 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
5041
5270
  installLocalTarball(editorRuntime.assets.collaborationExtensionTarball, profile.extensionsDir),
5042
5271
  installLocalTarball(editorRuntime.assets.collaborationServerTarball, profile.collaborationServerDir)
5043
5272
  ]);
5044
- const child = spawn2(nodeRuntimeExecutable(), [
5045
- join3(profile.collaborationServerDir, "open-collaboration-server", "bundle", "app.js"),
5273
+ const command = nodeRuntimeExecutable();
5274
+ const args = [
5275
+ join4(profile.collaborationServerDir, "open-collaboration-server", "bundle", "app.js"),
5046
5276
  "--hostname",
5047
5277
  "127.0.0.1",
5048
5278
  "--port",
5049
5279
  String(collaboration.serverPort)
5050
- ], {
5280
+ ];
5281
+ writeCliAuditEvent("process.spawn", {
5282
+ command,
5283
+ args,
5284
+ purpose: "local_editor.collaboration_sidecar",
5285
+ editorSessionId: collaboration.editorSessionId,
5286
+ runtimeSessionId: collaboration.runtimeSessionId
5287
+ }, { sessionId: runnerId });
5288
+ const child = spawn2(command, args, {
5051
5289
  env: {
5052
5290
  ...process.env,
5053
5291
  OCT_ACTIVATE_SIMPLE_LOGIN: "true",
@@ -5055,6 +5293,26 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
5055
5293
  },
5056
5294
  stdio: ["ignore", "inherit", "inherit"]
5057
5295
  });
5296
+ writeCliAuditEvent("process.spawned", {
5297
+ command,
5298
+ args,
5299
+ pid: child.pid,
5300
+ purpose: "local_editor.collaboration_sidecar",
5301
+ editorSessionId: collaboration.editorSessionId,
5302
+ runtimeSessionId: collaboration.runtimeSessionId
5303
+ }, { sessionId: runnerId });
5304
+ child.once("exit", (code, signal) => {
5305
+ writeCliAuditEvent("process.exit", {
5306
+ command,
5307
+ args,
5308
+ pid: child.pid,
5309
+ code,
5310
+ signal,
5311
+ purpose: "local_editor.collaboration_sidecar",
5312
+ editorSessionId: collaboration.editorSessionId,
5313
+ runtimeSessionId: collaboration.runtimeSessionId
5314
+ }, { sessionId: runnerId });
5315
+ });
5058
5316
  const exited = waitForCodeServerExit(child);
5059
5317
  const spawnResult = await waitForCodeServerSpawn(child);
5060
5318
  if (spawnResult === "failed") {
@@ -5088,21 +5346,22 @@ function nodeRuntimeExecutable(env = process.env, execPath = process.execPath) {
5088
5346
  return basename3(execPath).toLowerCase().includes("bun") ? "node" : execPath;
5089
5347
  }
5090
5348
  async function installLocalTarball(archivePath, destinationDir) {
5091
- mkdirSync2(destinationDir, { recursive: true });
5349
+ mkdirSync3(destinationDir, { recursive: true });
5092
5350
  await runProcess("tar", ["-xzf", archivePath, "-C", destinationDir]);
5093
5351
  }
5094
5352
  function installDirectory(sourceDir, destinationDir) {
5095
- mkdirSync2(dirname(destinationDir), { recursive: true });
5353
+ mkdirSync3(dirname2(destinationDir), { recursive: true });
5096
5354
  cpSync(sourceDir, destinationDir, { recursive: true });
5097
5355
  }
5098
- function codeServerEnv(env, userDataDir, collaboration) {
5356
+ function codeServerEnv(env, cwd, userDataDir, collaboration) {
5099
5357
  const { PORT: _port, ...hostEnv } = env;
5100
5358
  const base = {
5101
5359
  ...hostEnv,
5102
- HOME: userDataDir,
5103
- XDG_CACHE_HOME: join3(userDataDir, "xdg-cache"),
5104
- XDG_CONFIG_HOME: join3(userDataDir, "xdg-config"),
5105
- XDG_DATA_HOME: join3(userDataDir, "xdg-data")
5360
+ HOME: cwd,
5361
+ PWD: cwd,
5362
+ XDG_CACHE_HOME: join4(userDataDir, "xdg-cache"),
5363
+ XDG_CONFIG_HOME: join4(userDataDir, "xdg-config"),
5364
+ XDG_DATA_HOME: join4(userDataDir, "xdg-data")
5106
5365
  };
5107
5366
  if (collaboration === undefined) {
5108
5367
  return base;
@@ -5136,11 +5395,30 @@ function sameCollaboration(running, requested) {
5136
5395
  }
5137
5396
  function runProcess(command, args) {
5138
5397
  return new Promise((resolve4, reject) => {
5398
+ writeCliAuditEvent("process.spawn", {
5399
+ command,
5400
+ args,
5401
+ purpose: "local_editor.install_process"
5402
+ });
5139
5403
  const child = spawn2(command, [...args], {
5140
5404
  stdio: ["ignore", "ignore", "inherit"]
5141
5405
  });
5406
+ writeCliAuditEvent("process.spawned", {
5407
+ command,
5408
+ args,
5409
+ pid: child.pid,
5410
+ purpose: "local_editor.install_process"
5411
+ });
5142
5412
  child.once("error", reject);
5143
- child.once("exit", (code) => {
5413
+ child.once("exit", (code, signal) => {
5414
+ writeCliAuditEvent("process.exit", {
5415
+ command,
5416
+ args,
5417
+ pid: child.pid,
5418
+ code,
5419
+ signal,
5420
+ purpose: "local_editor.install_process"
5421
+ });
5144
5422
  if (code === 0) {
5145
5423
  resolve4();
5146
5424
  } else {
@@ -5292,17 +5570,17 @@ import { spawn as spawn4 } from "node:child_process";
5292
5570
  import { createHash as createHash2 } from "node:crypto";
5293
5571
  import {
5294
5572
  createReadStream,
5295
- createWriteStream,
5573
+ createWriteStream as createWriteStream2,
5296
5574
  existsSync as existsSync3,
5297
- mkdirSync as mkdirSync3,
5575
+ mkdirSync as mkdirSync4,
5298
5576
  mkdtempSync as mkdtempSync2,
5299
5577
  readFileSync as readFileSync2,
5300
5578
  renameSync,
5301
5579
  rmSync,
5302
5580
  writeFileSync as writeFileSync3
5303
5581
  } from "node:fs";
5304
- import { homedir as homedir3 } from "node:os";
5305
- import { dirname as dirname2, join as join4, resolve as resolve4 } from "node:path";
5582
+ import { homedir as homedir4 } from "node:os";
5583
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
5306
5584
  import { Readable } from "node:stream";
5307
5585
  import { pipeline } from "node:stream/promises";
5308
5586
 
@@ -5489,8 +5767,8 @@ function startCallbackServer(args) {
5489
5767
  if (error !== null && error.trim() !== "") {
5490
5768
  rejectCallback?.(new Error(`local runner OAuth failed: ${error}`));
5491
5769
  writeOauthResult(response, {
5492
- title: "Linzumi CLI was not authorized",
5493
- body: "You denied the request. You can close this tab and rerun linzumi start when you are ready.",
5770
+ title: "Not authorized",
5771
+ body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
5494
5772
  status: 403
5495
5773
  });
5496
5774
  return;
@@ -5498,22 +5776,22 @@ function startCallbackServer(args) {
5498
5776
  if (code === null || state === null || code.trim() === "" || state.trim() === "") {
5499
5777
  writeOauthResult(response, {
5500
5778
  title: "Authorization callback was incomplete",
5501
- body: "Kandan did not send the local runner authorization code. Return to your terminal and try again.",
5779
+ body: "We didn't receive the authorization code. Return to your terminal and try again.",
5502
5780
  status: 400
5503
5781
  });
5504
5782
  return;
5505
5783
  }
5506
5784
  resolveCallback?.({ code, state });
5507
5785
  writeOauthResult(response, {
5508
- title: "Linzumi CLI is connected",
5509
- body: "You can close this tab and return to the terminal. Kandan will finish starting the local runner.",
5786
+ title: "Connected",
5787
+ body: "You can close this tab. Your terminal will finish the setup.",
5510
5788
  status: 200
5511
5789
  });
5512
5790
  } catch (error) {
5513
5791
  rejectCallback?.(error);
5514
5792
  writeOauthResult(response, {
5515
5793
  title: "Authorization callback failed",
5516
- body: "Return to your terminal and rerun linzumi start.",
5794
+ body: "Return to your terminal and rerun the bootstrap command.",
5517
5795
  status: 500
5518
5796
  });
5519
5797
  }
@@ -5577,7 +5855,18 @@ function openBrowser(url) {
5577
5855
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
5578
5856
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
5579
5857
  return new Promise((resolve4) => {
5858
+ writeCliAuditEvent("process.spawn", {
5859
+ command,
5860
+ args,
5861
+ purpose: "oauth.open_browser"
5862
+ });
5580
5863
  const child = spawn3(command, args, { stdio: "ignore", detached: true });
5864
+ writeCliAuditEvent("process.spawned", {
5865
+ command,
5866
+ args,
5867
+ pid: child.pid,
5868
+ purpose: "oauth.open_browser"
5869
+ });
5581
5870
  child.on("error", () => resolve4());
5582
5871
  child.on("spawn", () => {
5583
5872
  child.unref();
@@ -5764,8 +6053,8 @@ function normalizeRuntimeAssets(value) {
5764
6053
  }
5765
6054
  function installedRuntime(cacheRoot, manifest) {
5766
6055
  const runtimeRoot = runtimeInstallRoot(cacheRoot, manifest);
5767
- const manifestPath = join4(runtimeRoot, manifest.manifestPath);
5768
- const codeServerBin = join4(runtimeRoot, manifest.codeServerBinPath);
6056
+ const manifestPath = join5(runtimeRoot, manifest.manifestPath);
6057
+ const codeServerBin = join5(runtimeRoot, manifest.codeServerBinPath);
5769
6058
  const assets = verifiedRuntimeAssetPaths(runtimeRoot, manifest);
5770
6059
  if (!existsSync3(manifestPath) || !existsSync3(codeServerBin) || assets === undefined) {
5771
6060
  return { ok: false };
@@ -5789,10 +6078,10 @@ function installedRuntime(cacheRoot, manifest) {
5789
6078
  return { ok: false };
5790
6079
  }
5791
6080
  async function installRuntime(args) {
5792
- mkdirSync3(args.cacheRoot, { recursive: true });
5793
- const tempRoot = mkdtempSync2(join4(args.cacheRoot, ".install-"));
5794
- const archivePath = join4(tempRoot, "runtime.tar.gz");
5795
- const extractRoot = join4(tempRoot, "runtime");
6081
+ mkdirSync4(args.cacheRoot, { recursive: true });
6082
+ const tempRoot = mkdtempSync2(join5(args.cacheRoot, ".install-"));
6083
+ const archivePath = join5(tempRoot, "runtime.tar.gz");
6084
+ const extractRoot = join5(tempRoot, "runtime");
5796
6085
  try {
5797
6086
  const downloaded = await downloadArchive({
5798
6087
  kandanUrl: args.kandanUrl,
@@ -5804,7 +6093,7 @@ async function installRuntime(args) {
5804
6093
  if (!downloaded.ok) {
5805
6094
  return downloaded;
5806
6095
  }
5807
- mkdirSync3(extractRoot, { recursive: true });
6096
+ mkdirSync4(extractRoot, { recursive: true });
5808
6097
  if (!await args.extractArchive(archivePath, extractRoot)) {
5809
6098
  return { ok: false, reason: "archive_extract_failed" };
5810
6099
  }
@@ -5817,13 +6106,13 @@ async function installRuntime(args) {
5817
6106
  if (!assetsInstalled) {
5818
6107
  return { ok: false, reason: "install_failed" };
5819
6108
  }
5820
- const manifestPath = join4(extractRoot, args.manifest.manifestPath);
5821
- const codeServerBin = join4(extractRoot, args.manifest.codeServerBinPath);
6109
+ const manifestPath = join5(extractRoot, args.manifest.manifestPath);
6110
+ const codeServerBin = join5(extractRoot, args.manifest.codeServerBinPath);
5822
6111
  const assets = verifiedRuntimeAssetPaths(extractRoot, args.manifest);
5823
6112
  if (!existsSync3(codeServerBin) || assets === undefined) {
5824
6113
  return { ok: false, reason: "invalid_archive" };
5825
6114
  }
5826
- mkdirSync3(dirname2(manifestPath), { recursive: true });
6115
+ mkdirSync4(dirname3(manifestPath), { recursive: true });
5827
6116
  writeFileSync3(manifestPath, JSON.stringify({
5828
6117
  version: args.manifest.version,
5829
6118
  platform: args.manifest.platform,
@@ -5835,18 +6124,18 @@ async function installRuntime(args) {
5835
6124
  }, null, 2));
5836
6125
  const targetRoot = runtimeInstallRoot(args.cacheRoot, args.manifest);
5837
6126
  rmSync(targetRoot, { recursive: true, force: true });
5838
- mkdirSync3(dirname2(targetRoot), { recursive: true });
6127
+ mkdirSync4(dirname3(targetRoot), { recursive: true });
5839
6128
  renameSync(extractRoot, targetRoot);
5840
6129
  return {
5841
6130
  ok: true,
5842
6131
  runtime: {
5843
6132
  mode: "server_managed",
5844
6133
  root: targetRoot,
5845
- codeServerBin: join4(targetRoot, args.manifest.codeServerBinPath),
6134
+ codeServerBin: join5(targetRoot, args.manifest.codeServerBinPath),
5846
6135
  assets: {
5847
- collaborationExtensionTarball: join4(targetRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz"),
5848
- collaborationServerTarball: join4(targetRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz"),
5849
- documentStateExtensionDir: join4(targetRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry")
6136
+ collaborationExtensionTarball: join5(targetRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz"),
6137
+ collaborationServerTarball: join5(targetRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz"),
6138
+ documentStateExtensionDir: join5(targetRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry")
5850
6139
  }
5851
6140
  }
5852
6141
  };
@@ -5858,7 +6147,7 @@ async function installRuntime(args) {
5858
6147
  }
5859
6148
  async function materializeRuntimeAssets(args) {
5860
6149
  for (const asset of args.manifest.assets) {
5861
- const targetPath = join4(args.runtimeRoot, asset.path);
6150
+ const targetPath = join5(args.runtimeRoot, asset.path);
5862
6151
  try {
5863
6152
  const bytes = await runtimeAssetBytes({
5864
6153
  kandanUrl: args.kandanUrl,
@@ -5868,7 +6157,7 @@ async function materializeRuntimeAssets(args) {
5868
6157
  if (bytes === undefined) {
5869
6158
  continue;
5870
6159
  }
5871
- mkdirSync3(dirname2(targetPath), { recursive: true });
6160
+ mkdirSync4(dirname3(targetPath), { recursive: true });
5872
6161
  writeFileSync3(targetPath, bytes);
5873
6162
  } catch (_error) {
5874
6163
  return false;
@@ -5899,7 +6188,7 @@ async function downloadArchive(args) {
5899
6188
  if (response.status !== 200 || response.body === null) {
5900
6189
  return { ok: false, reason: "download_failed" };
5901
6190
  }
5902
- await pipeline(Readable.fromWeb(response.body), createWriteStream(args.archivePath));
6191
+ await pipeline(Readable.fromWeb(response.body), createWriteStream2(args.archivePath));
5903
6192
  const sha256 = await fileSha256(args.archivePath);
5904
6193
  if (sha256 !== args.manifest.archiveSha256) {
5905
6194
  return { ok: false, reason: "checksum_mismatch" };
@@ -5911,11 +6200,34 @@ function sameOrigin(left, right) {
5911
6200
  }
5912
6201
  function extractTarGz(archivePath, destination) {
5913
6202
  return new Promise((resolveExtract) => {
5914
- const child = spawn4("tar", ["-xzf", archivePath, "-C", destination], {
6203
+ const command = "tar";
6204
+ const args = ["-xzf", archivePath, "-C", destination];
6205
+ writeCliAuditEvent("process.spawn", {
6206
+ command,
6207
+ args,
6208
+ purpose: "editor_runtime.extract"
6209
+ });
6210
+ const child = spawn4(command, args, {
5915
6211
  stdio: ["ignore", "ignore", "ignore"]
5916
6212
  });
6213
+ writeCliAuditEvent("process.spawned", {
6214
+ command,
6215
+ args,
6216
+ pid: child.pid,
6217
+ purpose: "editor_runtime.extract"
6218
+ });
5917
6219
  child.on("error", () => resolveExtract(false));
5918
- child.on("exit", (code) => resolveExtract(code === 0));
6220
+ child.on("exit", (code, signal) => {
6221
+ writeCliAuditEvent("process.exit", {
6222
+ command,
6223
+ args,
6224
+ pid: child.pid,
6225
+ code,
6226
+ signal,
6227
+ purpose: "editor_runtime.extract"
6228
+ });
6229
+ resolveExtract(code === 0);
6230
+ });
5919
6231
  });
5920
6232
  }
5921
6233
  function fileSha256(path) {
@@ -5931,14 +6243,14 @@ function runtimeInstallRoot(cacheRoot, manifest) {
5931
6243
  return resolve4(cacheRoot, manifest.platform, manifest.archiveSha256);
5932
6244
  }
5933
6245
  function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
5934
- const collaborationExtensionTarball = join4(runtimeRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz");
5935
- const collaborationServerTarball = join4(runtimeRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz");
5936
- const documentStateExtensionDir = join4(runtimeRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry");
6246
+ const collaborationExtensionTarball = join5(runtimeRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz");
6247
+ const collaborationServerTarball = join5(runtimeRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz");
6248
+ const documentStateExtensionDir = join5(runtimeRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry");
5937
6249
  const codeServerRoot = codeServerRuntimeRoot(manifest.codeServerBinPath);
5938
6250
  const requiredPaths = [
5939
6251
  manifest.codeServerBinPath,
5940
- join4(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda.js"),
5941
- join4(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda_bg.wasm"),
6252
+ join5(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda.js"),
6253
+ join5(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda_bg.wasm"),
5942
6254
  "kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz",
5943
6255
  "kandan/editor_servers/open-collaboration-server.tar.gz",
5944
6256
  "kandan/editor_extensions/kandan.document-state-telemetry/package.json",
@@ -5950,7 +6262,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
5950
6262
  if (expectedSha256 === undefined && relativePath !== manifest.codeServerBinPath) {
5951
6263
  return;
5952
6264
  }
5953
- const absolutePath = join4(runtimeRoot, relativePath);
6265
+ const absolutePath = join5(runtimeRoot, relativePath);
5954
6266
  if (!existsSync3(absolutePath)) {
5955
6267
  return;
5956
6268
  }
@@ -5969,7 +6281,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
5969
6281
  }
5970
6282
  function codeServerRuntimeRoot(codeServerBinPath) {
5971
6283
  const normalized = codeServerBinPath.replaceAll("\\", "/");
5972
- return normalized === "bin/code-server" ? "." : normalized.endsWith("/bin/code-server") ? normalized.slice(0, -"/bin/code-server".length) || "." : dirname2(normalized);
6284
+ return normalized === "bin/code-server" ? "." : normalized.endsWith("/bin/code-server") ? normalized.slice(0, -"/bin/code-server".length) || "." : dirname3(normalized);
5973
6285
  }
5974
6286
  function manifestAssetChecksums(assets) {
5975
6287
  const checksums = new Map;
@@ -5982,7 +6294,7 @@ function fileSha256Sync(path) {
5982
6294
  return createHash2("sha256").update(readFileSync2(path)).digest("hex");
5983
6295
  }
5984
6296
  function defaultEditorRuntimeCacheRoot() {
5985
- return join4(homedir3(), ".linzumi", "editor-runtimes");
6297
+ return join5(homedir4(), ".linzumi", "editor-runtimes");
5986
6298
  }
5987
6299
  function nonEmptyString(value) {
5988
6300
  return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
@@ -5995,10 +6307,24 @@ function sha256String(value) {
5995
6307
  // src/dependencyStatus.ts
5996
6308
  function probeTool(command, cwd) {
5997
6309
  return new Promise((resolve5) => {
5998
- const child = spawn5(command, ["--version"], {
6310
+ const args = ["--version"];
6311
+ writeCliAuditEvent("process.spawn", {
6312
+ command,
6313
+ args,
6314
+ cwd,
6315
+ purpose: "dependency_probe"
6316
+ });
6317
+ const child = spawn5(command, args, {
5999
6318
  cwd,
6000
6319
  stdio: ["ignore", "pipe", "pipe"]
6001
6320
  });
6321
+ writeCliAuditEvent("process.spawned", {
6322
+ command,
6323
+ args,
6324
+ cwd,
6325
+ pid: child.pid,
6326
+ purpose: "dependency_probe"
6327
+ });
6002
6328
  let stdout = "";
6003
6329
  let stderr = "";
6004
6330
  let resolved = false;
@@ -6012,6 +6338,15 @@ function probeTool(command, cwd) {
6012
6338
  };
6013
6339
  const timeout = setTimeout(() => {
6014
6340
  child.kill("SIGKILL");
6341
+ writeCliAuditEvent("process.exit", {
6342
+ command,
6343
+ args,
6344
+ cwd,
6345
+ pid: child.pid,
6346
+ code: null,
6347
+ signal: "SIGKILL",
6348
+ purpose: "dependency_probe"
6349
+ });
6015
6350
  finish({ command, available: false });
6016
6351
  }, 1000);
6017
6352
  child.stdout?.on("data", (chunk) => {
@@ -6021,9 +6356,28 @@ function probeTool(command, cwd) {
6021
6356
  stderr += chunk.toString();
6022
6357
  });
6023
6358
  child.on("error", () => {
6359
+ writeCliAuditEvent("process.exit", {
6360
+ command,
6361
+ args,
6362
+ cwd,
6363
+ pid: child.pid,
6364
+ code: null,
6365
+ signal: null,
6366
+ error: "spawn_failed",
6367
+ purpose: "dependency_probe"
6368
+ });
6024
6369
  finish({ command, available: false });
6025
6370
  });
6026
- child.on("exit", (code) => {
6371
+ child.on("exit", (code, signal) => {
6372
+ writeCliAuditEvent("process.exit", {
6373
+ command,
6374
+ args,
6375
+ cwd,
6376
+ pid: child.pid,
6377
+ code,
6378
+ signal,
6379
+ purpose: "dependency_probe"
6380
+ });
6027
6381
  if (code !== 0) {
6028
6382
  finish({ command, available: false });
6029
6383
  return;
@@ -6072,7 +6426,7 @@ function codeServerDependencyStatus(args) {
6072
6426
  }
6073
6427
  function assertStartDependencies(status) {
6074
6428
  if (!status.bun.available) {
6075
- throw new Error("Node.js is not available. Install Node.js 20+, then rerun linzumi start.");
6429
+ throw new Error("Node.js is not available. Install Node.js 20+, then rerun the bootstrap command.");
6076
6430
  }
6077
6431
  if (!status.codex.available) {
6078
6432
  throw new Error(`Codex is not available at ${status.codex.command}. Install Codex or pass --codex-bin <path>.`);
@@ -6336,35 +6690,6 @@ function waitForOpen2(websocket) {
6336
6690
  });
6337
6691
  }
6338
6692
 
6339
- // src/runnerLogger.ts
6340
- import { openSync } from "node:fs";
6341
- import { createWriteStream as createWriteStream2 } from "node:fs";
6342
- import { dirname as dirname3 } from "node:path";
6343
- import { mkdirSync as mkdirSync4 } from "node:fs";
6344
- function createRunnerLogger(logFile, consoleReporter) {
6345
- mkdirSync4(dirname3(logFile), { recursive: true });
6346
- const fd = openSync(logFile, "a");
6347
- const stream = createWriteStream2("", { fd, flags: "a", autoClose: true });
6348
- const logger = (event, payload) => {
6349
- stream.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...payload })}
6350
- `, "utf8");
6351
- consoleReporter?.(event, payload);
6352
- };
6353
- Object.defineProperty(logger, "close", {
6354
- value: () => closeStream(stream)
6355
- });
6356
- return logger;
6357
- }
6358
- function closeStream(stream) {
6359
- if (stream.closed || stream.destroyed) {
6360
- return Promise.resolve();
6361
- }
6362
- return new Promise((resolve5, reject) => {
6363
- stream.once("error", reject);
6364
- stream.end(resolve5);
6365
- });
6366
- }
6367
-
6368
6693
  // src/runnerConsoleReporter.ts
6369
6694
  function reportRunnerConsoleEvent(event, payload) {
6370
6695
  const line = formatRunnerConsoleEvent(event, payload);
@@ -6486,6 +6811,26 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6486
6811
  const allowedForwardPorts = options.allowedForwardPorts ?? [];
6487
6812
  const liveForwardPorts = new Set(allowedForwardPorts);
6488
6813
  const managedForwardPorts = new Set;
6814
+ const forwardPortAttributions = new Map;
6815
+ const setForwardPortAttribution = (port, attribution) => {
6816
+ forwardPortAttributions.set(port, {
6817
+ kandanThreadId: attribution.kandanThreadId ?? null,
6818
+ codexThreadId: attribution.codexThreadId ?? null,
6819
+ channelSlug: attribution.channelSlug ?? null
6820
+ });
6821
+ };
6822
+ const clearForwardPortAttribution = (port) => {
6823
+ forwardPortAttributions.delete(port);
6824
+ };
6825
+ const buildForwardPortAttributionPayload = () => Array.from(liveForwardPorts).sort((left, right) => left - right).map((port) => {
6826
+ const attribution = forwardPortAttributions.get(port);
6827
+ return {
6828
+ port,
6829
+ kandanThreadId: attribution?.kandanThreadId ?? null,
6830
+ codexThreadId: attribution?.codexThreadId ?? null,
6831
+ channelSlug: attribution?.channelSlug ?? null
6832
+ };
6833
+ });
6489
6834
  const allowedCwds = { value: [...options.allowedCwds] };
6490
6835
  const localEditorState = {
6491
6836
  value: { status: "disabled" }
@@ -6505,6 +6850,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6505
6850
  allowedCwdSuggestions: allowedCwdSuggestions(options.cwd, allowedCwds.value),
6506
6851
  portForwarding: liveForwardPorts.size > 0,
6507
6852
  allowedPorts: Array.from(liveForwardPorts).sort((left, right) => left - right),
6853
+ forwardedPortAttributions: buildForwardPortAttributionPayload(),
6508
6854
  toolStatus: options.dependencyStatus === undefined ? null : dependencyStatusPayload(options.dependencyStatus),
6509
6855
  editorRuntime: options.dependencyStatus?.editorRuntime === undefined ? null : dependencyStatusPayload(options.dependencyStatus).editorRuntime,
6510
6856
  ...localEditorCapabilities(options.editorRuntime, allowedCwds.value, localEditorState.value)
@@ -6612,12 +6958,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6612
6958
  enablePortForwardWatch: true,
6613
6959
  initialForwardPorts: allowedForwardPorts,
6614
6960
  suppressedForwardPorts: () => Array.from(managedForwardPorts),
6615
- onForwardPortApproved: (port) => {
6961
+ onForwardPortApproved: (port, attribution) => {
6616
6962
  liveForwardPorts.add(port);
6963
+ setForwardPortAttribution(port, attribution);
6617
6964
  return capabilitiesPayload();
6618
6965
  },
6619
6966
  onForwardPortRevoked: (port) => {
6620
6967
  liveForwardPorts.delete(port);
6968
+ clearForwardPortAttribution(port);
6621
6969
  return capabilitiesPayload();
6622
6970
  },
6623
6971
  channelSession: options.channelSession
@@ -6659,12 +7007,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6659
7007
  enablePortForwardWatch: true,
6660
7008
  initialForwardPorts: allowedForwardPorts,
6661
7009
  suppressedForwardPorts: () => Array.from(managedForwardPorts),
6662
- onForwardPortApproved: (port) => {
7010
+ onForwardPortApproved: (port, attribution) => {
6663
7011
  liveForwardPorts.add(port);
7012
+ setForwardPortAttribution(port, attribution);
6664
7013
  return capabilitiesPayload();
6665
7014
  },
6666
7015
  onForwardPortRevoked: (port) => {
6667
7016
  liveForwardPorts.delete(port);
7017
+ clearForwardPortAttribution(port);
6668
7018
  return capabilitiesPayload();
6669
7019
  },
6670
7020
  channelSession: {
@@ -6760,7 +7110,17 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6760
7110
  return;
6761
7111
  }
6762
7112
  if (isForwardHttpRequestControl(control)) {
6763
- handleForwardHttpRequest(control, Array.from(liveForwardPorts)).then((response) => kandan.push(topic, "forward:http_response", response)).catch((error) => kandan.push(topic, "forward:http_response", {
7113
+ handleForwardHttpRequest(control, Array.from(liveForwardPorts)).then((response) => {
7114
+ log("kandan.forward_response", {
7115
+ requestId: control.requestId,
7116
+ port: control.port,
7117
+ path: control.path,
7118
+ status: response.status,
7119
+ ok: response.ok,
7120
+ location: forwardedHeaderValue(response.headers, "location")
7121
+ });
7122
+ return kandan.push(topic, "forward:http_response", response);
7123
+ }).catch((error) => kandan.push(topic, "forward:http_response", {
6764
7124
  requestId: control.requestId,
6765
7125
  ok: false,
6766
7126
  error: error instanceof Error ? error.message : String(error)
@@ -6927,7 +7287,7 @@ function normalizedWorkDescription(value) {
6927
7287
  return normalized === undefined || normalized === "" ? undefined : normalized;
6928
7288
  }
6929
7289
  function makeRunnerLogger(options) {
6930
- return createRunnerLogger(options.logFile ?? join5(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
7290
+ return createRunnerLogger(options.logFile ?? join6(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
6931
7291
  }
6932
7292
  function installCleanupHandlers(close) {
6933
7293
  const closeAndExit = () => {
@@ -6952,11 +7312,44 @@ function installCleanupHandlers(close) {
6952
7312
  };
6953
7313
  }
6954
7314
  function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
6955
- return spawn6(codexBin, codexTuiArgs(codexUrl, codexThreadId, session, fast), {
7315
+ const args = codexTuiArgs(codexUrl, codexThreadId, session, fast);
7316
+ writeCliAuditEvent("process.spawn", {
7317
+ command: codexBin,
7318
+ args,
7319
+ cwd,
7320
+ purpose: "codex.tui"
7321
+ });
7322
+ const child = spawn6(codexBin, args, {
6956
7323
  cwd,
6957
7324
  env: process.env,
6958
7325
  stdio: "inherit"
6959
7326
  });
7327
+ writeCliAuditEvent("process.spawned", {
7328
+ command: codexBin,
7329
+ args,
7330
+ cwd,
7331
+ pid: child.pid,
7332
+ purpose: "codex.tui"
7333
+ });
7334
+ child.once("exit", (code, signal) => {
7335
+ writeCliAuditEvent("process.exit", {
7336
+ command: codexBin,
7337
+ args,
7338
+ cwd,
7339
+ pid: child.pid,
7340
+ code,
7341
+ signal,
7342
+ purpose: "codex.tui"
7343
+ });
7344
+ });
7345
+ return child;
7346
+ }
7347
+ function forwardedHeaderValue(headers, name) {
7348
+ if (!Array.isArray(headers)) {
7349
+ return;
7350
+ }
7351
+ const header = headers.find((value) => isJsonObject(value) && typeof value.name === "string" && value.name.toLowerCase() === name.toLowerCase() && typeof value.value === "string");
7352
+ return isJsonObject(header) && typeof header.value === "string" ? header.value : undefined;
6960
7353
  }
6961
7354
  function codexTuiArgs(codexUrl, codexThreadId, session, fast) {
6962
7355
  const overrides = codexTuiConfigArgs(session, fast);
@@ -7205,11 +7598,11 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
7205
7598
 
7206
7599
  // src/authCache.ts
7207
7600
  import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
7208
- import { homedir as homedir4 } from "node:os";
7209
- import { dirname as dirname4, join as join6 } from "node:path";
7601
+ import { homedir as homedir5 } from "node:os";
7602
+ import { dirname as dirname4, join as join7 } from "node:path";
7210
7603
  function defaultAuthFilePath() {
7211
- const base = process.env.KANDAN_HOME ?? join6(homedir4(), ".kandan");
7212
- return join6(base, "auth.json");
7604
+ const base = process.env.KANDAN_HOME ?? join7(homedir5(), ".kandan");
7605
+ return join7(base, "auth.json");
7213
7606
  }
7214
7607
  function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
7215
7608
  if (!existsSync4(authFilePath)) {
@@ -7344,11 +7737,11 @@ async function acquireAndCacheToken(args) {
7344
7737
 
7345
7738
  // src/localConfig.ts
7346
7739
  import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync4, realpathSync as realpathSync4, writeFileSync as writeFileSync5 } from "node:fs";
7347
- import { homedir as homedir5 } from "node:os";
7740
+ import { homedir as homedir6 } from "node:os";
7348
7741
  import { dirname as dirname5, resolve as resolve5 } from "node:path";
7349
7742
  function localConfigPath(env = process.env) {
7350
7743
  const override = env.LINZUMI_CONFIG_FILE;
7351
- return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
7744
+ return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir6(), ".linzumi", "config.json");
7352
7745
  }
7353
7746
  function readLocalConfig(path = localConfigPath()) {
7354
7747
  if (!existsSync5(path)) {
@@ -7409,6 +7802,10 @@ function realpathOrResolved(pathValue) {
7409
7802
  }
7410
7803
  }
7411
7804
 
7805
+ // src/defaultUrls.ts
7806
+ var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
7807
+ var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
7808
+
7412
7809
  // src/kandanTls.ts
7413
7810
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
7414
7811
  import { Agent } from "undici";
@@ -7454,9 +7851,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
7454
7851
 
7455
7852
  // src/agentBootstrap.ts
7456
7853
  import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
7457
- import { dirname as dirname6, join as join7 } from "node:path";
7458
- import { homedir as homedir6 } from "node:os";
7459
- var defaultApiUrl = "https://serve.linzumi.com";
7854
+ import { dirname as dirname6, join as join8 } from "node:path";
7855
+ import { homedir as homedir7 } from "node:os";
7460
7856
  async function runAgentCliCommand(args, deps = {
7461
7857
  fetchImpl: fetch,
7462
7858
  stdout: process.stdout,
@@ -7474,6 +7870,12 @@ async function runAgentCliCommand(args, deps = {
7474
7870
  case "claim":
7475
7871
  await runClaim(command, deps);
7476
7872
  return;
7873
+ case "loginLinkIssue":
7874
+ await runLoginLinkIssue(command, deps);
7875
+ return;
7876
+ case "loginLinkClaim":
7877
+ await runLoginLinkClaim(command, deps);
7878
+ return;
7477
7879
  case "threadNew":
7478
7880
  await runThreadNew(command, deps);
7479
7881
  return;
@@ -7528,6 +7930,28 @@ function parseAgentCommand(args) {
7528
7930
  tokenFile: agentTokenFile(parsed.flags)
7529
7931
  };
7530
7932
  }
7933
+ case "login-link": {
7934
+ const [subcommand, ...subcommandArgs] = rest;
7935
+ const parsed = parseAgentArgs(subcommandArgs);
7936
+ switch (subcommand) {
7937
+ case "issue":
7938
+ return {
7939
+ kind: "loginLinkIssue",
7940
+ apiUrl: agentApiUrl(parsed.flags),
7941
+ email: requiredFlag(parsed.flags, "email"),
7942
+ workspaceId: requiredFlag(parsed.flags, "workspace")
7943
+ };
7944
+ case "claim":
7945
+ return {
7946
+ kind: "loginLinkClaim",
7947
+ apiUrl: agentApiUrl(parsed.flags),
7948
+ pendingId: requiredFlag(parsed.flags, "pending"),
7949
+ code: requiredFlag(parsed.flags, "code")
7950
+ };
7951
+ default:
7952
+ throw new Error("linzumi login-link supports: issue, claim");
7953
+ }
7954
+ }
7531
7955
  case "thread": {
7532
7956
  const [subcommand, ...subcommandArgs] = rest;
7533
7957
  if (subcommand !== "new") {
@@ -7790,6 +8214,7 @@ async function runClaim(command, deps) {
7790
8214
  `);
7791
8215
  deps.stdout.write(`login_url: ${tokenFile.loginUrl}
7792
8216
  `);
8217
+ writeHumanLoginUrlWarning(deps);
7793
8218
  deps.stdout.write(`channel_url: ${tokenFile.channelUrl}
7794
8219
  `);
7795
8220
  deps.stdout.write(`support_channel_id: ${tokenFile.supportChannelId}
@@ -7797,6 +8222,37 @@ async function runClaim(command, deps) {
7797
8222
  deps.stdout.write(`support_channel_url: ${tokenFile.supportChannelUrl}
7798
8223
  `);
7799
8224
  }
8225
+ async function runLoginLinkIssue(command, deps) {
8226
+ const response = await postJson(command.apiUrl, "/agent/login-link/issue", {
8227
+ email: command.email,
8228
+ workspace_id: command.workspaceId
8229
+ }, undefined, deps.fetchImpl);
8230
+ const pendingId = requiredString(response, "pending_id");
8231
+ const expiresInSeconds = numberValue2(response.expires_in_seconds) ?? 600;
8232
+ const codeFormatHint = stringValue(response.code_format_hint) ?? "XXXX-XXXX";
8233
+ deps.stdout.write(`pending_id: ${pendingId}
8234
+ `);
8235
+ deps.stdout.write(`Code emailed to ${command.email} (format ${codeFormatHint}, expires in ${Math.ceil(expiresInSeconds / 60)}m)
8236
+ `);
8237
+ }
8238
+ async function runLoginLinkClaim(command, deps) {
8239
+ const response = await postJson(command.apiUrl, "/agent/login-link/claim", {
8240
+ pending_id: command.pendingId,
8241
+ code: command.code
8242
+ }, undefined, deps.fetchImpl);
8243
+ deps.stdout.write(`workspace_id: ${requiredString(response, "workspace_id")}
8244
+ `);
8245
+ const workspaceName = stringValue(response.workspace_name);
8246
+ if (workspaceName !== undefined) {
8247
+ deps.stdout.write(`workspace_name: ${workspaceName}
8248
+ `);
8249
+ }
8250
+ deps.stdout.write(`login_url: ${requiredString(response, "login_url")}
8251
+ `);
8252
+ writeHumanLoginUrlWarning(deps);
8253
+ deps.stdout.write(`channel_url: ${requiredString(response, "channel_url")}
8254
+ `);
8255
+ }
7800
8256
  async function runThreadNew(command, deps) {
7801
8257
  const tokenFile = readTokenFile(command.tokenFile, deps.readTextFile);
7802
8258
  const response = await postJson(command.apiUrl, "/agent/threads", {
@@ -7987,13 +8443,13 @@ function numberValue2(value) {
7987
8443
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
7988
8444
  }
7989
8445
  function agentApiUrl(flags) {
7990
- return flags.get("api-url") ?? defaultApiUrl;
8446
+ return flags.get("api-url") ?? defaultLinzumiHttpUrl;
7991
8447
  }
7992
8448
  function agentTokenFile(flags) {
7993
8449
  return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
7994
8450
  }
7995
8451
  function defaultAgentTokenFilePath() {
7996
- return join7(homedir6(), ".linzumi", "agent-token.json");
8452
+ return join8(homedir7(), ".linzumi", "agent-token.json");
7997
8453
  }
7998
8454
  function normalizedApiUrl(apiUrl) {
7999
8455
  return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
@@ -8044,12 +8500,20 @@ function writeTokenFile(path, tokenFile, writeTextFile2) {
8044
8500
  writeTextFile2(path, `${JSON.stringify(tokenFile, null, 2)}
8045
8501
  `);
8046
8502
  }
8503
+ function writeHumanLoginUrlWarning(deps) {
8504
+ deps.stdout.write(`This is a one-time human login link. Do not open it from automation.
8505
+ `);
8506
+ deps.stdout.write(`Give it directly to the human who will use the workspace.
8507
+ `);
8508
+ }
8047
8509
  function agentHelpText() {
8048
8510
  return `Linzumi agent bootstrap
8049
8511
 
8050
8512
  Usage:
8051
8513
  linzumi signup --email <email> --agent-name <name> [--workspace-name <name>] [--api-url <url>]
8052
8514
  linzumi claim --pending <pending_id> --code <XXXX-XXXX> [--api-url <url>]
8515
+ linzumi login-link issue --email <email> --workspace <workspace_id> [--api-url <url>]
8516
+ linzumi login-link claim --pending <pending_id> --code <XXXX-XXXX> [--api-url <url>]
8053
8517
  linzumi thread new <title> --message <message> [--api-url <url>]
8054
8518
  linzumi channel post <channel_id> <message> [--api-url <url>]
8055
8519
  linzumi post <thread_id> <message> [--kind progress|question]
@@ -8060,10 +8524,14 @@ Usage:
8060
8524
  linzumi editor open <thread_id> --runner <runner_id> --cwd <path>
8061
8525
 
8062
8526
  Options:
8063
- --api-url <url> Agent API origin, default ${defaultApiUrl}
8527
+ --api-url <url> Agent API origin, default ${defaultLinzumiHttpUrl}
8064
8528
  --agent-token-file <path> Token cache path, default ~/.linzumi/agent-token.json
8065
8529
  --workspace-name <name> Human-readable workspace name requested during signup
8066
8530
 
8531
+ Human login:
8532
+ Human browser login recovery; prints one-time links for the human only.
8533
+ Do not open login_url from Codex, CLI automation, or an unconfirmed browser profile.
8534
+
8067
8535
  Launch target:
8068
8536
  zero-to-hello-world-pr+editor in under 3 minutes.
8069
8537
  `;
@@ -8071,7 +8539,7 @@ Launch target:
8071
8539
 
8072
8540
  // src/helloLinzumiProject.ts
8073
8541
  import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
8074
- import { dirname as dirname7, join as join8, resolve as resolve6 } from "node:path";
8542
+ import { dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
8075
8543
  import { fileURLToPath } from "node:url";
8076
8544
  var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
8077
8545
  var defaultHelloLinzumiProjectName = "hello_linzumi";
@@ -8080,7 +8548,7 @@ var defaultHelloLinzumiPort = 8787;
8080
8548
  var defaultHelloLinzumiHost = "0.0.0.0";
8081
8549
  var markerFile = ".linzumi-demo-project";
8082
8550
  var moduleDir = dirname7(fileURLToPath(import.meta.url));
8083
- var linzumiLogoSvg = readFileSync7(join8(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
8551
+ var linzumiLogoSvg = readFileSync7(join9(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
8084
8552
  function createHelloLinzumiProject(input = {}) {
8085
8553
  const options = typeof input === "string" ? { rootPath: input } : input;
8086
8554
  const root = resolveHelloProjectRoot(options);
@@ -8088,9 +8556,9 @@ function createHelloLinzumiProject(input = {}) {
8088
8556
  const host = normalizeHost(options.host);
8089
8557
  assertTcpPort(port);
8090
8558
  assertWritableDemoRoot(root, options.reset === true);
8091
- mkdirSync8(join8(root, "src"), { recursive: true });
8559
+ mkdirSync8(join9(root, "src"), { recursive: true });
8092
8560
  for (const file of demoFiles({ root, port, host })) {
8093
- writeFileSync7(join8(root, file.path), file.content, "utf8");
8561
+ writeFileSync7(join9(root, file.path), file.content, "utf8");
8094
8562
  }
8095
8563
  return {
8096
8564
  root,
@@ -8134,7 +8602,7 @@ function assertWritableDemoRoot(root, reset) {
8134
8602
  if (!existsSync8(root)) {
8135
8603
  return;
8136
8604
  }
8137
- const markerPath = join8(root, markerFile);
8605
+ const markerPath = join9(root, markerFile);
8138
8606
  const isDemoRoot = existsSync8(markerPath) && readFileSync7(markerPath, "utf8").trim() === "hello-linzumi";
8139
8607
  if (isDemoRoot && reset) {
8140
8608
  rmSync2(root, { recursive: true, force: true });
@@ -8642,19 +9110,19 @@ import {
8642
9110
  watch,
8643
9111
  writeFileSync as writeFileSync8
8644
9112
  } from "node:fs";
8645
- import { homedir as homedir7 } from "node:os";
8646
- import { dirname as dirname8, join as join9, resolve as resolve7 } from "node:path";
9113
+ import { homedir as homedir8 } from "node:os";
9114
+ import { dirname as dirname8, join as join10, resolve as resolve7 } from "node:path";
8647
9115
  import { execFileSync, spawn as spawn7 } from "node:child_process";
8648
9116
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8649
9117
  var connectedMarker = "Runner connected:";
8650
9118
  function commanderStatusDir() {
8651
- return join9(homedir7(), ".linzumi", "commanders");
9119
+ return join10(homedir8(), ".linzumi", "commanders");
8652
9120
  }
8653
9121
  function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
8654
- return join9(statusDir, `${safeRunnerId(runnerId)}.json`);
9122
+ return join10(statusDir, `${safeRunnerId(runnerId)}.json`);
8655
9123
  }
8656
9124
  function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
8657
- return join9(statusDir, `${safeRunnerId(runnerId)}.log`);
9125
+ return join10(statusDir, `${safeRunnerId(runnerId)}.log`);
8658
9126
  }
8659
9127
  function startCommanderDaemon(options) {
8660
9128
  const statusDir = options.statusDir ?? commanderStatusDir();
@@ -8676,11 +9144,24 @@ function startCommanderDaemon(options) {
8676
9144
  mkdirSync9(dirname8(logFile), { recursive: true });
8677
9145
  const out = openSync2(logFile, "a");
8678
9146
  const err = openSync2(logFile, "a");
9147
+ writeCliAuditEvent("process.spawn", {
9148
+ command: nodeBin,
9149
+ args: command,
9150
+ cwd: options.cwd,
9151
+ purpose: "commander_daemon.start"
9152
+ }, { sessionId: options.runnerId });
8679
9153
  const child = (options.spawnImpl ?? spawn7)(nodeBin, command, {
8680
9154
  detached: true,
8681
9155
  stdio: ["ignore", out, err],
8682
9156
  env: process.env
8683
9157
  });
9158
+ writeCliAuditEvent("process.spawned", {
9159
+ command: nodeBin,
9160
+ args: command,
9161
+ cwd: options.cwd,
9162
+ pid: child.pid,
9163
+ purpose: "commander_daemon.start"
9164
+ }, { sessionId: options.runnerId });
8684
9165
  closeSync(out);
8685
9166
  closeSync(err);
8686
9167
  child.unref();
@@ -8804,10 +9285,22 @@ function processMatchesRecord(record, processIdentityReader) {
8804
9285
  }
8805
9286
  function readProcessIdentity(pid) {
8806
9287
  try {
8807
- const output = execFileSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], {
9288
+ const command = "ps";
9289
+ const args = ["-p", String(pid), "-o", "lstart=", "-o", "command="];
9290
+ writeCliAuditEvent("process.exec_file", {
9291
+ command,
9292
+ args,
9293
+ purpose: "commander_daemon.process_identity"
9294
+ });
9295
+ const output = execFileSync(command, args, {
8808
9296
  encoding: "utf8",
8809
9297
  stdio: ["ignore", "pipe", "ignore"]
8810
9298
  }).trim();
9299
+ writeCliAuditEvent("process.exec_file_completed", {
9300
+ command,
9301
+ args,
9302
+ purpose: "commander_daemon.process_identity"
9303
+ });
8811
9304
  if (output === "") {
8812
9305
  return;
8813
9306
  }
@@ -8922,7 +9415,7 @@ async function main(args) {
8922
9415
  process.stdout.write(connectGuideText());
8923
9416
  return;
8924
9417
  case "version":
8925
- process.stdout.write(`linzumi 0.0.32-beta
9418
+ process.stdout.write(`linzumi 0.0.34-beta
8926
9419
  `);
8927
9420
  return;
8928
9421
  case "auth":
@@ -8993,6 +9486,7 @@ function parseCommand(args) {
8993
9486
  return { command: "commanderDaemon", args: ["stop", ...rest] };
8994
9487
  case "signup":
8995
9488
  case "claim":
9489
+ case "login-link":
8996
9490
  case "thread":
8997
9491
  case "channel":
8998
9492
  case "post":
@@ -9198,7 +9692,7 @@ async function parseStartRunnerArgs(args, deps = {
9198
9692
  process.exit(0);
9199
9693
  }
9200
9694
  rejectStartTargetingFlags(values);
9201
- const kandanUrl = stringValue3(values, "kandan-url") ?? "wss://serve.linzumi.com";
9695
+ const kandanUrl = stringValue3(values, "kandan-url") ?? defaultLinzumiWebSocketUrl;
9202
9696
  const requestedCwd = resolveUserPath(cwdArg ?? process.cwd());
9203
9697
  const cwd = assertConfiguredAllowedCwds([requestedCwd])[0] ?? requestedCwd;
9204
9698
  const explicitAllowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [];
@@ -9435,7 +9929,7 @@ async function parseRunnerArgs(args, deps = {
9435
9929
  process.exit(0);
9436
9930
  }
9437
9931
  if (values.get("version") === true) {
9438
- process.stdout.write(`linzumi 0.0.32-beta
9932
+ process.stdout.write(`linzumi 0.0.34-beta
9439
9933
  `);
9440
9934
  process.exit(0);
9441
9935
  }
@@ -9580,10 +10074,10 @@ function rejectStartTargetingFlags(values) {
9580
10074
  }
9581
10075
  function resolveUserPath(pathValue) {
9582
10076
  if (pathValue === "~") {
9583
- return homedir8();
10077
+ return homedir9();
9584
10078
  }
9585
10079
  if (pathValue.startsWith("~/")) {
9586
- return resolve8(homedir8(), pathValue.slice(2));
10080
+ return resolve8(homedir9(), pathValue.slice(2));
9587
10081
  }
9588
10082
  return resolve8(pathValue);
9589
10083
  }
@@ -9678,6 +10172,8 @@ Usage:
9678
10172
  linzumi
9679
10173
  linzumi signup --email <email> --agent-name <name> [--workspace-name <name>]
9680
10174
  linzumi claim --pending <pending_id> --code <XXXX-XXXX>
10175
+ linzumi login-link issue --email <email> --workspace <workspace_id>
10176
+ linzumi login-link claim --pending <pending_id> --code <XXXX-XXXX>
9681
10177
  linzumi thread new <title> --message <message>
9682
10178
  linzumi post <thread_id> <message>
9683
10179
  linzumi inbox <thread_id> --since-last
@@ -9693,7 +10189,7 @@ Usage:
9693
10189
  linzumi auth --kandan-url <ws-url> [--workspace <slug> --channel <slug>]
9694
10190
 
9695
10191
  Required:
9696
- --kandan-url <ws-url> Linzumi backend URL, default wss://serve.linzumi.com
10192
+ --kandan-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
9697
10193
  --token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
9698
10194
  --auth-file <path> Auth cache path, default ~/.linzumi/auth.json
9699
10195
  --oauth-callback-host <ip> Callback host reachable by your browser
@@ -9830,7 +10326,7 @@ What it does:
9830
10326
  previews, and the browser VS Code editor.
9831
10327
 
9832
10328
  Options:
9833
- --kandan-url <ws-url> Linzumi backend URL, default wss://serve.linzumi.com
10329
+ --kandan-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
9834
10330
  --token <jwt> Optional scoped local-runner token override
9835
10331
  --auth-file <path> Auth cache path, default ~/.linzumi/auth.json
9836
10332
  --oauth-callback-host <ip> Callback host reachable by your browser