@linzumi/cli 0.0.17-beta → 0.0.19-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.
package/README.md CHANGED
@@ -22,12 +22,21 @@
22
22
  Copy this into your terminal from the repo you want changed:
23
23
 
24
24
  ```bash
25
- codex -- "Get a working hello-world PR from https://linzumi.com/agents.md"
25
+ codex -c sandbox_workspace_write.network_access=true --ask-for-approval never --sandbox workspace-write --add-dir ~/.linzumi --add-dir ~/.kandan --add-dir ~/.npm --add-dir ~/.codex -- 'Follow https://linzumi.com/agents.md to get me up and running'
26
26
  ```
27
27
 
28
- That's the launch path: Codex opens the agent instructions, signs up,
29
- asks you for the emailed code, connects Linzumi to this repo, opens the
30
- browser editor, and posts the PR or review branch.
28
+ That's the launch path: the first Codex is only the bootstrapper. It
29
+ opens the agent instructions, collects the email up front, asks you for
30
+ the emailed code when it arrives, connects Linzumi to this repo, creates
31
+ the shared support channel, starts a Linzumi Codex session in a work
32
+ thread, and opens the browser editor. The Linzumi Codex session does the
33
+ branch, test, push, and PR work.
34
+
35
+ The snippet uses Codex's normal workspace sandbox with network access
36
+ enabled, automatic command approval, and explicit writable access to
37
+ Linzumi's persistent local auth/config, editor runtime cache, npm cache,
38
+ and Codex's own local session state. It is intentionally not the dangerous
39
+ sandbox-bypass mode.
31
40
 
32
41
  Today, your AI coding agent has two homes, both bad.
33
42
 
@@ -58,10 +67,14 @@ In three minutes from now you'll have it.
58
67
  - **A private support channel with the Linzumi team**, in case
59
68
  anything goes sideways.
60
69
 
61
- Two commands. No cloud VM. Your code, your terminal, and your dev
70
+ One pasted command. No cloud VM. Your code, your terminal, and your dev
62
71
  server stay on your laptop the whole time.
63
72
 
64
- ## Install + start
73
+ ## Manual Runner Setup
74
+
75
+ The agent-first command above is the launch path. Use this manual path
76
+ when you want to connect your laptop yourself before starting work from
77
+ the browser.
65
78
 
66
79
  You'll need **Node.js 20+**, **Bun 1.2+**, the **Codex CLI**, and a
67
80
  Chromium-based browser (Chrome, Edge, Arc, or Brave). Safari mostly
@@ -80,51 +93,63 @@ Here's what the next 30 seconds look like:
80
93
  4. Your laptop appears as a runner in your workspace, and
81
94
  `~/code/my-app` is added to your trusted-paths list automatically.
82
95
  5. Type something in chat — *"Explain this project and tell me how
83
- to run it"* — and Codex picks it up on your machine.
96
+ to run it"* — and Linzumi Codex picks it up on your machine.
84
97
 
85
- That's it. The rest of this README is detail.
98
+ That's it for manual setup. The rest of this README is detail.
86
99
 
87
100
  ## Agent-first launch path
88
101
 
89
102
  That one prompt is the launch path. The canonical agent instructions are
90
103
  hosted at `https://linzumi.com/agents.md`; `https://linzumi.com/skills.md`
91
- stays available for compatibility. The agent will ask for your email,
92
- ask for the emailed code, say hello to `@sean` in the shared support
93
- channel, start the runner in the current folder, open Codex and the
94
- browser editor, then post the PR or reviewable branch.
104
+ stays available for compatibility. The bootstrap agent will collect your
105
+ email up front, ask for the emailed code after signup sends it, say
106
+ hello to `@sean` in the shared support channel, start the runner in the
107
+ current folder, start a Linzumi Codex session in a work thread, and open
108
+ the browser editor. The Linzumi Codex session then posts the PR or
109
+ reviewable branch.
95
110
 
96
- Under the hood, the npm package exposes these commands:
111
+ Under the hood, the npm package exposes these commands for the bootstrap
112
+ agent to run. They are not extra human setup steps after the one pasted
113
+ Codex command.
97
114
 
98
115
  ```bash
99
116
  npx -y @linzumi/cli@latest signup --email alice@example.com --agent-name BuildBot
100
117
  npx -y @linzumi/cli@latest claim --pending <pending_id> --code <XXXX-XXXX>
118
+ npx -y @linzumi/cli@latest channel post <support_channel_id> "Hello @sean, starting this launch run."
101
119
  npx -y @linzumi/cli@latest thread new "Hello world PR" --message "Starting now."
102
- npx -y @linzumi/cli@latest agent runner "$PWD" --runner-id hello-world-agent
120
+ npx -y @linzumi/cli@latest agent runner "$PWD" \
121
+ --runner-id hello-world-agent \
122
+ --allowed-cwd "$PWD" \
123
+ --sandbox workspace-write \
124
+ --approval-policy never
103
125
  ```
104
126
 
105
127
  The agent-owned runner reads `~/.linzumi/agent-token.json`, uses the
106
128
  workspace/channel scope from the approval flow, trusts only the selected
107
- folder by default, and listens only to the approving human unless
108
- `--listen-user` is explicitly passed.
129
+ folder, and listens only to the approving human unless `--listen-user` is
130
+ explicitly passed. The bootstrap agent waits for `Runner connected:` before
131
+ starting Codex in the Linzumi thread.
109
132
 
110
- By default, the runner downloads the Kandan-approved `code-server`
133
+ By default, the runner downloads the Linzumi-approved `code-server`
111
134
  runtime for your platform and verifies its checksum before enabling the
112
135
  browser editor. Linux editor launches are wrapped with `bubblewrap`
113
136
  (`bwrap`) for filesystem confinement.
114
137
 
115
- `linzumi claim` also prints `support_channel_url`. That channel is the
116
- shared onboarding room for the approving human, the claimed agent identity,
117
- and Linzumi support. Keep repository work in task threads; use the support
118
- channel when signup, runner, mobile, or browser-editor setup gets stuck.
138
+ `linzumi claim` also prints `login_url`. That one-time link logs the human
139
+ into `#general` in their own email-named workspace. The claim also provisions
140
+ `#linzumi-support`, a shared channel with the Linzumi workspace; the bootstrap
141
+ agent posts a hello there with the printed `support_channel_id`.
142
+ Keep repository work in task threads; use the support channel when signup,
143
+ runner, or browser-editor setup gets stuck.
119
144
 
120
- Once the runner is online, the agent can ask Kandan to start Codex and
121
- open the browser editor for the same thread and folder:
145
+ Once the runner is online, the bootstrap agent can ask Linzumi to start
146
+ Codex and open the browser editor for the same thread and folder:
122
147
 
123
148
  ```bash
124
149
  npx -y @linzumi/cli@latest codex start <thread_id> \
125
150
  --runner hello-world-agent \
126
151
  --cwd "$PWD" \
127
- --work-description "Make the smallest hello-world change, run the relevant test, open a PR or clearly labeled local-review branch, and post the link."
152
+ --work-description "You are the Linzumi Codex agent running inside this Linzumi thread on the user's local runner. Create a small hello-world branch in the current repo, make the smallest reviewable change, run the nearest relevant local test or smoke check, push the branch or open a PR, and post the PR URL or exact review branch back in this Linzumi thread. Keep the human updated in the thread. If you are blocked, post the exact blocker instead of pretending the run succeeded."
128
153
 
129
154
  npx -y @linzumi/cli@latest editor open <thread_id> \
130
155
  --runner hello-world-agent \
@@ -155,12 +180,12 @@ You now have:
155
180
  ## Three things to try first
156
181
 
157
182
  1. **Onboard yourself to a new repo.** *"Explain this project and tell
158
- me how to run it."* Codex maps the codebase before you've opened
159
- a single file. Days saved on day one.
160
- 2. **Make a real change without context-switching.** Codex edits files
161
- on your disk. Watch the diff land in chat, or jump into the
162
- browser editor (it's VS Code in your browser, pointed at your
163
- folder) and shape it yourself.
183
+ me how to run it."* Linzumi Codex maps the codebase before you've
184
+ opened a single file. Days saved on day one.
185
+ 2. **Make a real change without context-switching.** The Codex session
186
+ running in Linzumi edits files on your disk. Watch the diff land in
187
+ chat, or jump into the browser editor (it's VS Code in your browser,
188
+ pointed at your folder) and shape it yourself.
164
189
  3. **Show a teammate something on your screen.** Open the browser
165
190
  editor or share your local dev server through a normal HTTPS
166
191
  link. No exposing localhost, no copy-pasting IPs, no "screenshare
@@ -181,9 +206,9 @@ alone in a terminal.
181
206
  work on, hit start. Linzumi attaches a fresh Codex to that runner
182
207
  with the folder and settings you picked.
183
208
 
184
- - **One Codex per thread.** Once a Codex picks up a thread, it owns
185
- the thread — no second Codex can step in and trample the work. The
186
- lock holds for the life of the thread.
209
+ - **One Codex per thread.** Once a Linzumi Codex session picks up a
210
+ thread, it owns the thread — no second Codex can step in and trample
211
+ the work. The lock holds for the life of the thread.
187
212
 
188
213
  - **Browse what your team's been up to.** Open the runners dropdown
189
214
  to see, across every device, the most recently active threads with
@@ -208,9 +233,10 @@ linzumi paths remove ~/code/my-app
208
233
  on first use. `linzumi connect` uses the list as-is — or pass
209
234
  `--allowed-cwd <paths>` for a one-off comma-separated override.
210
235
 
211
- There's no credential escalation: an agent attached to a trusted
212
- folder can only read and write inside that folder, with the same
213
- privileges as your shell. Every action is auditable from the thread.
236
+ There's no credential escalation: Linzumi only asks the runner to start
237
+ Codex or the editor inside trusted folders. Those processes still run
238
+ with your shell's operating-system privileges, so choose trusted folders
239
+ intentionally. Every action is auditable from the thread.
214
240
 
215
241
  ## When something looks wrong
216
242
 
@@ -234,7 +260,7 @@ privileges as your shell. Every action is auditable from the thread.
234
260
  ## Pinning a version
235
261
 
236
262
  ```bash
237
- npm install -g @linzumi/cli@0.0.17-beta
263
+ npm install -g @linzumi/cli@0.0.19-beta
238
264
  linzumi --version
239
265
  ```
240
266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.17-beta",
3
+ "version": "0.0.19-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": {
@@ -35,6 +35,13 @@ type AgentCommand =
35
35
  readonly message: string;
36
36
  readonly tokenFile: string;
37
37
  }
38
+ | {
39
+ readonly kind: "channelPost";
40
+ readonly apiUrl: string;
41
+ readonly channelId: string;
42
+ readonly body: string;
43
+ readonly tokenFile: string;
44
+ }
38
45
  | {
39
46
  readonly kind: "post";
40
47
  readonly apiUrl: string;
@@ -87,7 +94,9 @@ export type AgentTokenFile = {
87
94
  readonly workspaceId: string;
88
95
  readonly channelId: string | undefined;
89
96
  readonly ownerUsername: string | undefined;
90
- readonly dmUrl: string;
97
+ readonly channelUrl: string;
98
+ readonly loginUrl: string;
99
+ readonly supportChannelId: string;
91
100
  readonly supportChannelUrl: string | undefined;
92
101
  readonly savedAt: string;
93
102
  readonly cursors: Record<string, string>;
@@ -124,6 +133,9 @@ export async function runAgentCliCommand(
124
133
  case "threadNew":
125
134
  await runThreadNew(command, deps);
126
135
  return;
136
+ case "channelPost":
137
+ await runChannelPost(command, deps);
138
+ return;
127
139
  case "post":
128
140
  await runPost(command, deps);
129
141
  return;
@@ -196,6 +208,36 @@ function parseAgentCommand(args: readonly string[]): AgentCommand {
196
208
  tokenFile: agentTokenFile(parsed.flags),
197
209
  };
198
210
  }
211
+ case "channel": {
212
+ const [subcommand, ...subcommandArgs] = rest;
213
+
214
+ if (subcommand !== "post") {
215
+ throw new Error("linzumi channel supports: post");
216
+ }
217
+
218
+ const parsed = parseAgentArgs(subcommandArgs);
219
+ const [channelId, body, ...extra] = parsed.positionals;
220
+
221
+ if (channelId === undefined || channelId.trim() === "") {
222
+ throw new Error("missing channel id");
223
+ }
224
+
225
+ if (body === undefined || body.trim() === "") {
226
+ throw new Error("missing message body");
227
+ }
228
+
229
+ if (extra.length > 0) {
230
+ throw new Error("linzumi channel post accepts channel id and one message body");
231
+ }
232
+
233
+ return {
234
+ kind: "channelPost",
235
+ apiUrl: agentApiUrl(parsed.flags),
236
+ channelId,
237
+ body,
238
+ tokenFile: agentTokenFile(parsed.flags),
239
+ };
240
+ }
199
241
  case "post": {
200
242
  const parsed = parseAgentArgs(rest);
201
243
  const [threadId, body, ...extra] = parsed.positionals;
@@ -426,7 +468,9 @@ async function runClaim(
426
468
  workspaceId: requiredString(response, "workspace_id"),
427
469
  channelId: stringValue(response.channel_id),
428
470
  ownerUsername: stringValue(response.owner_username),
429
- dmUrl: requiredString(response, "dm_url"),
471
+ channelUrl: requiredString(response, "channel_url"),
472
+ loginUrl: requiredString(response, "login_url"),
473
+ supportChannelId: requiredString(response, "support_channel_id"),
430
474
  supportChannelUrl: requiredString(response, "support_channel_url"),
431
475
  savedAt: new Date().toISOString(),
432
476
  cursors: {},
@@ -434,6 +478,9 @@ async function runClaim(
434
478
 
435
479
  writeTokenFile(command.tokenFile, tokenFile, deps.writeTextFile);
436
480
  deps.stdout.write(`Logged in as agent ${tokenFile.agentId}\n`);
481
+ deps.stdout.write(`login_url: ${tokenFile.loginUrl}\n`);
482
+ deps.stdout.write(`channel_url: ${tokenFile.channelUrl}\n`);
483
+ deps.stdout.write(`support_channel_id: ${tokenFile.supportChannelId}\n`);
437
484
  deps.stdout.write(`support_channel_url: ${tokenFile.supportChannelUrl}\n`);
438
485
  }
439
486
 
@@ -457,6 +504,22 @@ async function runThreadNew(
457
504
  deps.stdout.write(`thread_url: ${requiredString(response, "thread_url")}\n`);
458
505
  }
459
506
 
507
+ async function runChannelPost(
508
+ command: Extract<AgentCommand, { readonly kind: "channelPost" }>,
509
+ deps: AgentCliDeps,
510
+ ): Promise<void> {
511
+ const tokenFile = readTokenFile(command.tokenFile, deps.readTextFile);
512
+ const response = await postJson(
513
+ command.apiUrl,
514
+ `/agent/channels/${encodeURIComponent(command.channelId)}/messages`,
515
+ { body: command.body },
516
+ tokenFile.agentToken,
517
+ deps.fetchImpl,
518
+ );
519
+
520
+ deps.stdout.write(`message_id: ${requiredString(response, "message_id")}\n`);
521
+ }
522
+
460
523
  async function runPost(
461
524
  command: Extract<AgentCommand, { readonly kind: "post" }>,
462
525
  deps: AgentCliDeps,
@@ -761,7 +824,9 @@ export function readStoredAgentTokenFile(
761
824
  workspaceId: requiredString(parsed, "workspaceId"),
762
825
  channelId: stringValue(parsed.channelId),
763
826
  ownerUsername: stringValue(parsed.ownerUsername),
764
- dmUrl: requiredString(parsed, "dmUrl"),
827
+ channelUrl: requiredString(parsed, "channelUrl"),
828
+ loginUrl: requiredString(parsed, "loginUrl"),
829
+ supportChannelId: requiredString(parsed, "supportChannelId"),
765
830
  supportChannelUrl: stringValue(parsed.supportChannelUrl),
766
831
  savedAt: requiredString(parsed, "savedAt"),
767
832
  cursors,
@@ -790,6 +855,7 @@ Usage:
790
855
  linzumi signup --email <email> --agent-name <name> [--api-url <url>]
791
856
  linzumi claim --pending <pending_id> --code <XXXX-XXXX> [--api-url <url>]
792
857
  linzumi thread new <title> --message <message> [--api-url <url>]
858
+ linzumi channel post <channel_id> <message> [--api-url <url>]
793
859
  linzumi post <thread_id> <message> [--kind progress|question]
794
860
  linzumi inbox <thread_id> --since-last
795
861
  linzumi done <thread_id> --message <message>
package/src/index.ts CHANGED
@@ -121,7 +121,7 @@ async function main(args: readonly string[]): Promise<void> {
121
121
  process.stdout.write(connectGuideText());
122
122
  return;
123
123
  case "version":
124
- process.stdout.write("linzumi 0.0.17-beta\n");
124
+ process.stdout.write("linzumi 0.0.19-beta\n");
125
125
  return;
126
126
  case "auth":
127
127
  await runAuthCommand(parsed.args);
@@ -614,7 +614,7 @@ export async function parseRunnerArgs(
614
614
  }
615
615
 
616
616
  if (values.get("version") === true) {
617
- process.stdout.write("linzumi 0.0.17-beta\n");
617
+ process.stdout.write("linzumi 0.0.19-beta\n");
618
618
  process.exit(0);
619
619
  }
620
620