@linzumi/cli 0.0.22-beta → 0.0.24-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 +31 -16
  2. package/dist/index.js +138 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,8 +32,20 @@ starts that hot-reload app on your computer, creates the shared support
32
32
  channel, starts a Linzumi Codex session in a work thread, and opens the
33
33
  browser editor pointed at the demo app.
34
34
 
35
+ Terms:
36
+
37
+ - **Bootstrapper Codex**: the outer Codex started by the pasted command. It
38
+ sets up Linzumi and local processes, but does not edit the demo app.
39
+ - **Linzumi Commander**: the long-running local bridge started with
40
+ `linzumi commander`; it owns the secure tunnel, trusted folder, browser
41
+ editor, and inner Codex launch.
42
+ - **Linzumi Codex session**: the inner agent running inside the Linzumi thread;
43
+ it edits `/tmp/hello_linzumi` and posts progress.
44
+ - **Human**: the workspace owner who opens the one-time login link and watches
45
+ the work in `#general` and `#linzumi-support`.
46
+
35
47
  The snippet uses automatic command approval and full local process access
36
- because the bootstrapper must start a real runner and browser editor on this
48
+ because the bootstrapper must start a real Linzumi Commander and browser editor on this
37
49
  computer. It is intentionally not the dangerous sandbox-bypass mode.
38
50
 
39
51
  Today, your AI coding agent has two homes, both bad.
@@ -104,7 +116,7 @@ stays available for compatibility. The bootstrap agent will confirm your
104
116
  email and workspace choice up front, ask for the emailed code after signup
105
117
  sends it, say hello to `@sean` in the shared support channel,
106
118
  generate `/tmp/hello_linzumi`, start its hot-reload Node server, start the
107
- runner for that folder, start a Linzumi Codex session in a work thread, and
119
+ Commander for that folder, start a Linzumi Codex session in a work thread, and
108
120
  open the browser editor. The Linzumi Codex session then adds confetti to the
109
121
  demo page while you watch. Workspace names are plain display names from 2 to
110
122
  100 characters; Linzumi generates the URL-safe workspace slug.
@@ -117,25 +129,28 @@ Codex command.
117
129
  npx -y @linzumi/cli@latest signup --email alice@example.com --workspace-name "Alice's Linzumi" --agent-name BuildBot
118
130
  npx -y @linzumi/cli@latest claim --pending <pending_id> --code <XXXX-XXXX>
119
131
  npx -y @linzumi/cli@latest channel post <support_channel_id> "Hello @sean, starting this launch run."
120
- npx -y @linzumi/cli@latest hello
132
+ npx -y @linzumi/cli@latest thread new "Hello Linzumi confetti" --message "Preparing the Hello Linzumi demo. Next I will generate /tmp/hello_linzumi, start its hot-reload server bound to 0.0.0.0 on port 8787, and ask Linzumi Codex to add confetti when the page loads."
133
+ commander_id="hello-linzumi-commander-${thread_id%%-*}"
134
+ npx -y @linzumi/cli@latest init-hello-linzumi-demo-app --parent-dir /tmp --name hello_linzumi --host 0.0.0.0 --port 8787 --reset --json
121
135
  (cd /tmp/hello_linzumi && npm run dev > /tmp/hello_linzumi/dev.log 2>&1 &)
122
- npx -y @linzumi/cli@latest thread new "Hello Linzumi confetti" --message "Please add confetti when the page loads."
123
- npx -y @linzumi/cli@latest agent runner /tmp/hello_linzumi \
124
- --runner-id hello-linzumi-agent \
136
+ npx -y @linzumi/cli@latest commander /tmp/hello_linzumi \
137
+ --runner-id "$commander_id" \
125
138
  --allowed-cwd /tmp/hello_linzumi \
126
139
  --forward-port 8787 \
127
140
  --sandbox danger-full-access \
128
141
  --approval-policy never
129
142
  ```
130
143
 
131
- The agent-owned runner reads `~/.linzumi/agent-token.json`, uses the
144
+ The agent-owned Commander reads `~/.linzumi/agent-token.json`, uses the
132
145
  workspace/channel scope from the approval flow, trusts only the selected
133
146
  folder, advertises the explicit preview port, and listens only to the
134
- approving human unless `--listen-user` is explicitly passed. The bootstrap
135
- agent waits for `Runner connected:` before starting Codex in the Linzumi
147
+ approving human unless `--listen-user` is explicitly passed. Use a unique
148
+ Commander id per launch thread; Linzumi stores trusted-folder config per
149
+ Commander id, so reusing an old fixed id can pick up stale allowed-cwd config.
150
+ The bootstrap agent waits for `Runner connected:` before starting Codex in the Linzumi
136
151
  thread.
137
152
 
138
- By default, the runner downloads the Linzumi-approved `code-server`
153
+ By default, the Commander downloads the Linzumi-approved `code-server`
139
154
  runtime for your platform and verifies its checksum before enabling the
140
155
  browser editor. Linux editor launches are wrapped with `bubblewrap`
141
156
  (`bwrap`) for filesystem confinement.
@@ -146,20 +161,20 @@ workspace. The claim also provisions `#linzumi-support`, a shared channel
146
161
  connected to Linzumi's workspace so our team can see setup issues from our
147
162
  side; the bootstrap agent posts a hello there with the printed
148
163
  `support_channel_id`.
149
- Keep demo work in task threads; use the support channel when signup, runner,
164
+ Keep demo work in task threads; use the support channel when signup, Commander,
150
165
  preview, or browser-editor setup gets stuck.
151
166
 
152
- Once the runner is online, the bootstrap agent can ask Linzumi to start
167
+ Once the Commander is online, the bootstrap agent can ask Linzumi to start
153
168
  Codex and open the browser editor for the same thread and folder:
154
169
 
155
170
  ```bash
156
171
  npx -y @linzumi/cli@latest codex start <thread_id> \
157
- --runner hello-linzumi-agent \
172
+ --runner "$commander_id" \
158
173
  --cwd /tmp/hello_linzumi \
159
174
  --work-description "Work only in /tmp/hello_linzumi. Add tasteful confetti when the Hello Linzumi page loads, keep the hot-reload app working on port 8787, and post the exact files changed."
160
175
 
161
176
  npx -y @linzumi/cli@latest editor open <thread_id> \
162
- --runner hello-linzumi-agent \
177
+ --runner "$commander_id" \
163
178
  --cwd /tmp/hello_linzumi
164
179
  ```
165
180
 
@@ -261,7 +276,7 @@ intentionally. Every action is auditable from the thread.
261
276
  ## Pinning a version
262
277
 
263
278
  ```bash
264
- npm install -g @linzumi/cli@0.0.22-beta
279
+ npm install -g @linzumi/cli@0.0.24-beta
265
280
  linzumi --version
266
281
  ```
267
282
 
@@ -284,7 +299,7 @@ linzumi connect \
284
299
  ### All the flags
285
300
 
286
301
  ```text
287
- --agent-token-file <path> Agent token cache for `linzumi agent runner`
302
+ --agent-token-file <path> Agent token cache for `linzumi commander`
288
303
  --oauth-callback-host <ip> Sign-in callback host your browser can reach
289
304
  --codex-bin <path> Codex executable, default `codex`
290
305
  --model <name> Model requested for Codex sessions and labelled in Linzumi
package/dist/index.js CHANGED
@@ -7773,48 +7773,92 @@ Launch target:
7773
7773
  }
7774
7774
 
7775
7775
  // src/helloLinzumiProject.ts
7776
- import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
7776
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
7777
7777
  import { dirname as dirname7, join as join7, resolve as resolve5 } from "node:path";
7778
7778
  import { fileURLToPath } from "node:url";
7779
7779
  var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
7780
+ var defaultHelloLinzumiProjectName = "hello_linzumi";
7781
+ var defaultHelloLinzumiParentDir = "/tmp";
7782
+ var defaultHelloLinzumiPort = 8787;
7783
+ var defaultHelloLinzumiHost = "0.0.0.0";
7780
7784
  var markerFile = ".linzumi-demo-project";
7781
7785
  var moduleDir = dirname7(fileURLToPath(import.meta.url));
7782
7786
  var linzumiLogoSvg = readFileSync6(join7(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
7783
- function createHelloLinzumiProject(rootPath = defaultHelloLinzumiProjectDir) {
7784
- const root = resolve5(rootPath);
7785
- assertWritableDemoRoot(root);
7787
+ function createHelloLinzumiProject(input = {}) {
7788
+ const options = typeof input === "string" ? { rootPath: input } : input;
7789
+ const root = resolveHelloProjectRoot(options);
7790
+ const port = options.port ?? defaultHelloLinzumiPort;
7791
+ const host = normalizeHost(options.host);
7792
+ assertTcpPort(port);
7793
+ assertWritableDemoRoot(root, options.reset === true);
7786
7794
  mkdirSync7(join7(root, "src"), { recursive: true });
7787
- for (const file of demoFiles()) {
7795
+ for (const file of demoFiles({ root, port, host })) {
7788
7796
  writeFileSync6(join7(root, file.path), file.content, "utf8");
7789
7797
  }
7790
7798
  return {
7791
7799
  root,
7792
- port: 8787,
7800
+ port,
7801
+ host,
7793
7802
  startCommand: "npm run dev",
7794
- previewUrl: "http://127.0.0.1:8787"
7803
+ previewUrl: `http://${host}:${port}`
7795
7804
  };
7796
7805
  }
7797
- function assertWritableDemoRoot(root) {
7806
+ function resolveHelloProjectRoot(options) {
7807
+ if (options.rootPath !== undefined && (options.parentDir !== undefined || options.name !== undefined)) {
7808
+ throw new Error("linzumi init-hello-linzumi-demo-app accepts either --dir or --parent-dir/--name, not both");
7809
+ }
7810
+ if (options.rootPath !== undefined) {
7811
+ return resolve5(options.rootPath);
7812
+ }
7813
+ const name = normalizeProjectName(options.name ?? defaultHelloLinzumiProjectName);
7814
+ return resolve5(options.parentDir ?? defaultHelloLinzumiParentDir, name);
7815
+ }
7816
+ function normalizeProjectName(value) {
7817
+ const name = value.trim();
7818
+ if (name === "" || name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
7819
+ throw new Error("--name must be a single directory name");
7820
+ }
7821
+ return name;
7822
+ }
7823
+ function normalizeHost(value) {
7824
+ const host = value?.trim() ?? defaultHelloLinzumiHost;
7825
+ if (host === "") {
7826
+ throw new Error("--host must not be empty");
7827
+ }
7828
+ return host;
7829
+ }
7830
+ function assertTcpPort(port) {
7831
+ if (Number.isInteger(port) && port > 0 && port <= 65535) {
7832
+ return;
7833
+ }
7834
+ throw new Error("--port must be a TCP port from 1 to 65535");
7835
+ }
7836
+ function assertWritableDemoRoot(root, reset) {
7798
7837
  if (!existsSync7(root)) {
7799
7838
  return;
7800
7839
  }
7801
7840
  const markerPath = join7(root, markerFile);
7802
- if (existsSync7(markerPath) && readFileSync6(markerPath, "utf8").trim() === "hello-linzumi") {
7841
+ const isDemoRoot = existsSync7(markerPath) && readFileSync6(markerPath, "utf8").trim() === "hello-linzumi";
7842
+ if (isDemoRoot && reset) {
7843
+ rmSync2(root, { recursive: true, force: true });
7803
7844
  return;
7804
7845
  }
7805
- throw new Error(`${root} already exists and is not a Linzumi demo project; move it before rerunning linzumi hello`);
7846
+ if (isDemoRoot) {
7847
+ return;
7848
+ }
7849
+ throw new Error(`${root} already exists and is not a Linzumi demo project; move it before rerunning linzumi init-hello-linzumi-demo-app`);
7806
7850
  }
7807
- function demoFiles() {
7851
+ function demoFiles(options) {
7808
7852
  return [
7809
7853
  { path: markerFile, content: `hello-linzumi
7810
7854
  ` },
7811
7855
  { path: "package.json", content: packageJson },
7812
- { path: "server.mjs", content: serverSource },
7856
+ { path: "server.mjs", content: serverSource(options) },
7813
7857
  { path: "index.html", content: htmlSource },
7814
- { path: "src/main.js", content: mainSource },
7858
+ { path: "src/main.js", content: mainSource(options) },
7815
7859
  { path: "src/styles.css", content: cssSource },
7816
7860
  { path: "src/linzumi-logo.svg", content: linzumiLogoSvg },
7817
- { path: "README.md", content: readmeSource }
7861
+ { path: "README.md", content: readmeSource(options) }
7818
7862
  ];
7819
7863
  }
7820
7864
  var packageJson = `${JSON.stringify({
@@ -7828,12 +7872,13 @@ var packageJson = `${JSON.stringify({
7828
7872
  }
7829
7873
  }, null, 2)}
7830
7874
  `;
7831
- var serverSource = `import { createReadStream, existsSync, statSync, watch } from "node:fs";
7875
+ var serverSource = ({ host, port }) => `import { createReadStream, existsSync, statSync, watch } from "node:fs";
7832
7876
  import { extname, join, normalize } from "node:path";
7833
7877
  import { createServer } from "node:http";
7834
7878
 
7835
7879
  const root = process.cwd();
7836
- const port = Number(process.env.PORT ?? "8787");
7880
+ const port = Number(process.env.PORT ?? "${port}");
7881
+ const host = process.env.HOST ?? ${JSON.stringify(host)};
7837
7882
  const clients = new Set();
7838
7883
 
7839
7884
  const contentTypes = new Map([
@@ -7844,7 +7889,7 @@ const contentTypes = new Map([
7844
7889
  ]);
7845
7890
 
7846
7891
  const server = createServer((request, response) => {
7847
- const url = new URL(request.url ?? "/", "http://127.0.0.1");
7892
+ const url = new URL(request.url ?? "/", "http://0.0.0.0");
7848
7893
 
7849
7894
  if (url.pathname === "/events") {
7850
7895
  response.writeHead(200, {
@@ -7891,10 +7936,10 @@ for (const watchedPath of [join(root, "index.html"), join(root, "src")]) {
7891
7936
  });
7892
7937
  }
7893
7938
 
7894
- server.listen(port, "127.0.0.1", () => {
7939
+ server.listen(port, host, () => {
7895
7940
  const address = server.address();
7896
7941
  const actualPort = typeof address === "object" && address !== null ? address.port : port;
7897
- process.stdout.write(\`Hello Linzumi listening on http://127.0.0.1:\${actualPort}\\n\`);
7942
+ process.stdout.write(\`Hello Linzumi listening on http://\${host}:\${actualPort}\\n\`);
7898
7943
  });
7899
7944
  `;
7900
7945
  var htmlSource = `<!doctype html>
@@ -8180,7 +8225,9 @@ h1 {
8180
8225
  }
8181
8226
  }
8182
8227
  `;
8183
- var mainSource = `const prompts = [
8228
+ var mainSource = ({ root, host, port }) => `const projectLabel = ${JSON.stringify(`${root} · ${host}:${port}`)};
8229
+
8230
+ const prompts = [
8184
8231
  "Add a soft confetti burst when I click the heading.",
8185
8232
  "Make this a kanban board with three columns for my groceries.",
8186
8233
  "Add a guestbook so my team can sign in.",
@@ -8236,7 +8283,7 @@ document.querySelector("#app").innerHTML = \`
8236
8283
  </div>
8237
8284
 
8238
8285
  <div class="footer-note">
8239
- <code>/tmp/hello_linzumi · localhost:8787</code>
8286
+ <code>\${escape(projectLabel)}</code>
8240
8287
  <a href="https://linzumi.com" target="_blank" rel="noreferrer">linzumi.com →</a>
8241
8288
  </div>
8242
8289
  </div>
@@ -8261,10 +8308,10 @@ events.addEventListener("reload", () => {
8261
8308
  window.location.reload();
8262
8309
  });
8263
8310
  `;
8264
- var readmeSource = `# Linzumi launch demo
8311
+ var readmeSource = ({ host, port }) => `# Linzumi launch demo
8265
8312
 
8266
8313
  A tiny Node-only HTTP server with browser live reload, generated by
8267
- \`linzumi hello\`.
8314
+ \`linzumi init-hello-linzumi-demo-app\`.
8268
8315
 
8269
8316
  Run it:
8270
8317
 
@@ -8272,7 +8319,8 @@ Run it:
8272
8319
  npm run dev
8273
8320
  \`\`\`
8274
8321
 
8275
- Then open http://127.0.0.1:8787. The page reloads when \`index.html\` or files
8322
+ The app binds to \`${host}\` by default so Linzumi can reach it through the
8323
+ secure tunnel. Then open http://${host}:${port}. The page reloads when \`index.html\` or files
8276
8324
  under \`src/\` change.
8277
8325
 
8278
8326
  The bootstrap agent in your terminal has already opened the browser VS Code
@@ -8318,6 +8366,12 @@ var flagDefinitions = new Map([
8318
8366
  ]);
8319
8367
  var helloFlagDefinitions = new Map([
8320
8368
  ["dir", { kind: "value" }],
8369
+ ["parent-dir", { kind: "value" }],
8370
+ ["name", { kind: "value" }],
8371
+ ["port", { kind: "value" }],
8372
+ ["host", { kind: "value" }],
8373
+ ["reset", { kind: "boolean" }],
8374
+ ["json", { kind: "boolean" }],
8321
8375
  ["help", { kind: "boolean" }]
8322
8376
  ]);
8323
8377
  if (isMainModule()) {
@@ -8343,7 +8397,7 @@ async function main(args) {
8343
8397
  process.stdout.write(connectGuideText());
8344
8398
  return;
8345
8399
  case "version":
8346
- process.stdout.write(`linzumi 0.0.22-beta
8400
+ process.stdout.write(`linzumi 0.0.24-beta
8347
8401
  `);
8348
8402
  return;
8349
8403
  case "auth":
@@ -8393,10 +8447,12 @@ function parseCommand(args) {
8393
8447
  case "paths":
8394
8448
  return { command: "paths", args: rest };
8395
8449
  case "hello":
8450
+ case "init-hello-linzumi-demo-app":
8396
8451
  return { command: "hello", args: rest };
8397
8452
  case "agent":
8398
8453
  return rest[0] === "runner" ? { command: "agentRunner", args: rest.slice(1) } : { command: "agent", args: rest };
8399
8454
  case "agent-runner":
8455
+ case "commander":
8400
8456
  return { command: "agentRunner", args: rest };
8401
8457
  case "signup":
8402
8458
  case "claim":
@@ -8422,12 +8478,32 @@ function runHelloCommand(args) {
8422
8478
  process.stdout.write(helloHelpText());
8423
8479
  return;
8424
8480
  }
8425
- const project = createHelloLinzumiProject(stringValue3(values, "dir") ?? defaultHelloLinzumiProjectDir);
8481
+ const project = createHelloLinzumiProject({
8482
+ rootPath: stringValue3(values, "dir"),
8483
+ parentDir: stringValue3(values, "parent-dir") === undefined ? undefined : resolveUserPath(required(values, "parent-dir")),
8484
+ name: stringValue3(values, "name"),
8485
+ port: tcpPortValue(values, "port"),
8486
+ host: stringValue3(values, "host"),
8487
+ reset: values.get("reset") === true
8488
+ });
8489
+ if (values.get("json") === true) {
8490
+ process.stdout.write(`${JSON.stringify({
8491
+ project_dir: project.root,
8492
+ dev_command: project.startCommand,
8493
+ preview_url: project.previewUrl,
8494
+ port: project.port,
8495
+ host: project.host
8496
+ })}
8497
+ `);
8498
+ return;
8499
+ }
8426
8500
  process.stdout.write(`project_dir: ${project.root}
8427
8501
  `);
8428
8502
  process.stdout.write(`dev_command: ${project.startCommand}
8429
8503
  `);
8430
8504
  process.stdout.write(`preview_url: ${project.previewUrl}
8505
+ `);
8506
+ process.stdout.write(`bind_host: ${project.host}
8431
8507
  `);
8432
8508
  }
8433
8509
  function runPathsCommand(args) {
@@ -8611,7 +8687,7 @@ async function parseAgentRunnerArgs(args, deps = {
8611
8687
  }
8612
8688
  rejectAgentRunnerTargetingFlags(values);
8613
8689
  if (cwdArg !== undefined && values.has("cwd")) {
8614
- throw new Error("linzumi agent runner accepts either <folder> or --cwd, not both");
8690
+ throw new Error("linzumi commander accepts either <folder> or --cwd, not both");
8615
8691
  }
8616
8692
  const tokenFilePath = stringValue3(values, "agent-token-file") ?? defaultAgentTokenFilePath();
8617
8693
  const tokenFile = readStoredAgentTokenFile(tokenFilePath, deps.readTextFile);
@@ -8677,14 +8753,14 @@ function readAgentTokenTextFile(path) {
8677
8753
  function rejectAgentRunnerTargetingFlags(values) {
8678
8754
  const unsupportedFlags = ["workspace", "channel", "token", "auth-file", "oauth-callback-host"].filter((flag) => values.has(flag));
8679
8755
  if (unsupportedFlags.length > 0) {
8680
- throw new Error(`linzumi agent runner uses the claimed agent token scope; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")}.`);
8756
+ throw new Error(`linzumi commander uses the claimed agent token scope; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")}.`);
8681
8757
  }
8682
8758
  }
8683
8759
  function requiredStoredAgentChannel(channelId) {
8684
8760
  if (channelId !== undefined) {
8685
8761
  return channelId;
8686
8762
  }
8687
- throw new Error("agent token file is missing channelId; rerun linzumi claim before starting an agent runner");
8763
+ throw new Error("agent token file is missing channelId; rerun linzumi claim before starting a Commander");
8688
8764
  }
8689
8765
  function requiredStoredOwnerUsername(ownerUsername) {
8690
8766
  if (ownerUsername !== undefined) {
@@ -8742,7 +8818,7 @@ async function parseRunnerArgs(args, deps = {
8742
8818
  process.exit(0);
8743
8819
  }
8744
8820
  if (values.get("version") === true) {
8745
- process.stdout.write(`linzumi 0.0.22-beta
8821
+ process.stdout.write(`linzumi 0.0.24-beta
8746
8822
  `);
8747
8823
  process.exit(0);
8748
8824
  }
@@ -8941,6 +9017,17 @@ function positiveIntegerValue(values, key) {
8941
9017
  }
8942
9018
  throw new Error(`--${key} must be a positive integer`);
8943
9019
  }
9020
+ function tcpPortValue(values, key) {
9021
+ const value = stringValue3(values, key);
9022
+ if (value === undefined) {
9023
+ return;
9024
+ }
9025
+ const parsed = Number(value);
9026
+ if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
9027
+ return parsed;
9028
+ }
9029
+ throw new Error(`--${key} must be a TCP port from 1 to 65535`);
9030
+ }
8944
9031
  function helpText() {
8945
9032
  return `Linzumi local Codex runner
8946
9033
 
@@ -8952,8 +9039,8 @@ Usage:
8952
9039
  linzumi post <thread_id> <message>
8953
9040
  linzumi inbox <thread_id> --since-last
8954
9041
  linzumi done <thread_id> --message <message>
8955
- linzumi hello
8956
- linzumi agent runner <folder> [options]
9042
+ linzumi init-hello-linzumi-demo-app
9043
+ linzumi commander <folder> [options]
8957
9044
  linzumi start <folder> [options]
8958
9045
  linzumi paths list|add|remove [path]
8959
9046
  linzumi connect --kandan-url <ws-url> --workspace <slug> --channel <slug> [options]
@@ -8994,8 +9081,8 @@ Examples:
8994
9081
  linzumi thread new "Hello world" --message "Starting now. ETA 3m."
8995
9082
  linzumi post thr_abc123 "PR is open"
8996
9083
  linzumi done thr_abc123 --message "Done: https://github.com/example/repo/pull/1"
8997
- linzumi hello
8998
- linzumi agent runner ~/code/my-app --runner-id launch-agent-runner
9084
+ linzumi init-hello-linzumi-demo-app
9085
+ linzumi commander ~/code/my-app --runner-id launch-commander
8999
9086
  linzumi start ~/
9000
9087
  linzumi start ~/code/my-app
9001
9088
  linzumi connect --workspace <your-workspace> --channel <your-channel> --launch-tui
@@ -9019,19 +9106,27 @@ function helloHelpText() {
9019
9106
  return `Linzumi Hello demo project
9020
9107
 
9021
9108
  Usage:
9022
- linzumi hello [--dir <path>]
9109
+ linzumi init-hello-linzumi-demo-app [--dir <path>]
9110
+ linzumi init-hello-linzumi-demo-app [--parent-dir <path> --name <dirname>]
9023
9111
 
9024
9112
  Creates a tiny Node-only "Hello Linzumi" app with browser live reload.
9025
9113
 
9026
9114
  Default:
9027
9115
  ${defaultHelloLinzumiProjectDir}
9116
+ host ${defaultHelloLinzumiHost}, port ${defaultHelloLinzumiPort}
9028
9117
 
9029
9118
  After creation:
9030
9119
  cd ${defaultHelloLinzumiProjectDir}
9031
9120
  npm run dev
9032
9121
 
9033
9122
  Options:
9034
- --dir <path> Override the project directory for tests or local experiments.
9123
+ --dir <path> Exact project directory.
9124
+ --parent-dir <path> Parent directory, default ${defaultHelloLinzumiParentDir}.
9125
+ --name <dirname> Project directory name with --parent-dir, default ${defaultHelloLinzumiProjectName}.
9126
+ --host <host> Bind host baked into the demo app, default ${defaultHelloLinzumiHost}.
9127
+ --port <port> Port baked into the demo app, default ${defaultHelloLinzumiPort}.
9128
+ --reset Recreate an existing marked Linzumi demo directory.
9129
+ --json Print machine-readable project details.
9035
9130
  `;
9036
9131
  }
9037
9132
  function pathsHelpText() {
@@ -9079,13 +9174,14 @@ Examples:
9079
9174
  `;
9080
9175
  }
9081
9176
  function agentRunnerHelpText() {
9082
- return `Linzumi agent-owned local runner
9177
+ return `Linzumi Commander
9083
9178
 
9084
9179
  Usage:
9180
+ linzumi commander <folder> [options]
9085
9181
  linzumi agent runner <folder> [options]
9086
9182
 
9087
9183
  What it does:
9088
- Starts this computer as the claimed agent's scoped local runner. The command
9184
+ Starts this computer as the claimed agent's scoped Linzumi Commander. The command
9089
9185
  reads ~/.linzumi/agent-token.json, uses its workspace/channel scope, trusts
9090
9186
  only the selected folder by default, and listens only to the owning human
9091
9187
  recorded during claim unless --listen-user is passed.
@@ -9093,7 +9189,7 @@ What it does:
9093
9189
  Options:
9094
9190
  --agent-token-file <path> Agent token cache, default ~/.linzumi/agent-token.json
9095
9191
  --kandan-url <ws-url> Kandan websocket base URL. Defaults deterministically from the stored apiUrl.
9096
- --runner-id <id> Stable local runner id
9192
+ --runner-id <id> Stable Commander id
9097
9193
  --codex-bin <path> Codex executable, default codex
9098
9194
  --code-server-bin <path> Custom development code-server executable. By default Kandan installs the approved editor runtime.
9099
9195
  --listen-user <user> Human whose replies Codex may accept, default owner from claim
@@ -9103,11 +9199,11 @@ Options:
9103
9199
  --approval-policy <value> Approval-policy metadata shown in Kandan
9104
9200
  --forward-port <ports> Comma-separated local TCP ports Kandan may expose as previews
9105
9201
  --allowed-cwd <paths> Override the selected folder with comma-separated trusted roots
9106
- --fast Mark this runner as low-latency/fast in Kandan
9202
+ --fast Mark this Commander as low-latency/fast in Linzumi
9107
9203
 
9108
9204
  Examples:
9109
- linzumi agent runner "$PWD" --runner-id hello-world-agent
9110
- linzumi agent runner ~/code/my-app --kandan-url ws://127.0.0.1:4162 --runner-id local-qa-agent
9205
+ linzumi commander "$PWD" --runner-id hello-world-commander
9206
+ linzumi commander ~/code/my-app --kandan-url ws://127.0.0.1:4162 --runner-id local-qa-commander
9111
9207
  `;
9112
9208
  }
9113
9209
  function connectGuideText() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.22-beta",
3
+ "version": "0.0.24-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": {