@linzumi/cli 0.0.17-beta → 0.0.18-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 +63 -37
- package/package.json +1 -1
- package/src/agentBootstrap.ts +69 -3
- package/src/index.ts +2 -2
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 -- "
|
|
25
|
+
codex -c sandbox_workspace_write.network_access=true --ask-for-approval never --sandbox workspace-write --add-dir "$HOME/.linzumi" --add-dir "$HOME/.kandan" --add-dir "$HOME/.npm" --add-dir "$HOME/.codex" -- "Follow https://linzumi.com/agents.md to get me up and running!"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
That's the launch path: Codex
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
92
|
-
ask for the emailed code
|
|
93
|
-
|
|
94
|
-
|
|
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"
|
|
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
|
|
108
|
-
|
|
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
|
|
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 `
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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 "
|
|
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
|
|
159
|
-
a single file. Days saved on day one.
|
|
160
|
-
2. **Make a real change without context-switching.** Codex
|
|
161
|
-
on your disk. Watch the diff land in
|
|
162
|
-
browser editor (it's VS Code in your browser,
|
|
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
|
|
185
|
-
the thread — no second Codex can step in and trample
|
|
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:
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
263
|
+
npm install -g @linzumi/cli@0.0.18-beta
|
|
238
264
|
linzumi --version
|
|
239
265
|
```
|
|
240
266
|
|
package/package.json
CHANGED
package/src/agentBootstrap.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
124
|
+
process.stdout.write("linzumi 0.0.18-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.
|
|
617
|
+
process.stdout.write("linzumi 0.0.18-beta\n");
|
|
618
618
|
process.exit(0);
|
|
619
619
|
}
|
|
620
620
|
|