@linzumi/cli 0.0.2-beta → 0.0.4-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 +29 -38
- package/package.json +1 -1
- package/src/channelSessionSupport.ts +3 -3
- package/src/index.ts +45 -22
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# @linzumi/cli
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
___ ___
|
|
5
|
+
.-' '-. .-' '-.
|
|
6
|
+
.' ' '.
|
|
7
|
+
/ _ _ \
|
|
8
|
+
| .' '. .' '. |
|
|
9
|
+
| / \ .-. / \ |
|
|
10
|
+
\ \ (o o) / /
|
|
11
|
+
'._'._ \_/ _.'_.'
|
|
12
|
+
| | |
|
|
13
|
+
| /|\ |
|
|
14
|
+
|___/ | \___|
|
|
15
|
+
\_|_/
|
|
16
|
+
/ \
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Linzumi's CLI package. It installs the `linzumi` executable.
|
|
@@ -27,7 +27,7 @@ runner. It wraps the same local runner that was previously started with
|
|
|
27
27
|
Install the exact beta version:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npm install -g @linzumi/cli@0.0.
|
|
30
|
+
npm install -g @linzumi/cli@0.0.4-beta
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Or install the current beta tag:
|
|
@@ -47,50 +47,43 @@ linzumi --version
|
|
|
47
47
|
Expected CLI output:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
linzumi 0.0.
|
|
50
|
+
linzumi 0.0.4-beta
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Prod Quick Start
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
For the Linzumi workspace in prod, this is the first command to try:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
|
|
58
|
+
npm install -g @linzumi/cli@0.0.4-beta
|
|
59
|
+
|
|
60
|
+
linzumi connect \
|
|
59
61
|
--kandan-url wss://serve.kandanai.com \
|
|
60
62
|
--workspace linzumi \
|
|
61
63
|
--channel seans-playground \
|
|
62
|
-
--kandan-thread-id 6d454179-8c6c-45c6-a447-06f01cb6fa71 \
|
|
63
|
-
--listen-user sean \
|
|
64
|
-
--cwd /Users/seans/code/linzumi-main \
|
|
65
64
|
--codex-bin codex \
|
|
66
|
-
--
|
|
67
|
-
--reasoning-effort low \
|
|
68
|
-
--fast \
|
|
69
|
-
--launch-tui \
|
|
70
|
-
--oauth-callback-host 100.71.192.98
|
|
65
|
+
--launch-tui
|
|
71
66
|
```
|
|
72
67
|
|
|
73
|
-
is
|
|
68
|
+
The runner handles OAuth itself. If the cached Kandan token is missing or
|
|
69
|
+
rejected, it opens the OAuth flow and saves the refreshed auth cache.
|
|
70
|
+
By default it listens for replies from the authenticated Kandan user.
|
|
71
|
+
|
|
72
|
+
For a more explicit launch that pins the Codex model, reasoning effort, and
|
|
73
|
+
fast service tier:
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
linzumi connect \
|
|
77
77
|
--kandan-url wss://serve.kandanai.com \
|
|
78
78
|
--workspace linzumi \
|
|
79
79
|
--channel seans-playground \
|
|
80
|
-
--kandan-thread-id 6d454179-8c6c-45c6-a447-06f01cb6fa71 \
|
|
81
|
-
--listen-user sean \
|
|
82
|
-
--cwd /Users/seans/code/linzumi-main \
|
|
83
80
|
--codex-bin codex \
|
|
84
81
|
--model gpt-5.5 \
|
|
85
82
|
--reasoning-effort low \
|
|
86
83
|
--fast \
|
|
87
|
-
--launch-tui
|
|
88
|
-
--oauth-callback-host 100.71.192.98
|
|
84
|
+
--launch-tui
|
|
89
85
|
```
|
|
90
86
|
|
|
91
|
-
The runner handles OAuth itself. If the cached Kandan token is missing or
|
|
92
|
-
rejected, it opens the OAuth flow and saves the refreshed auth cache.
|
|
93
|
-
|
|
94
87
|
## Basic Use
|
|
95
88
|
|
|
96
89
|
Running `linzumi` without arguments prints the local runner guide:
|
|
@@ -106,8 +99,6 @@ linzumi connect \
|
|
|
106
99
|
--kandan-url wss://serve.kandanai.com \
|
|
107
100
|
--workspace linzumi \
|
|
108
101
|
--channel seans-playground \
|
|
109
|
-
--listen-user sean \
|
|
110
|
-
--cwd /Users/seans/code/linzumi-main \
|
|
111
102
|
--codex-bin codex \
|
|
112
103
|
--launch-tui
|
|
113
104
|
```
|
|
@@ -137,7 +128,7 @@ linzumi auth [auth options]
|
|
|
137
128
|
--workspace <slug> Workspace slug, for example linzumi
|
|
138
129
|
--channel <slug|w/c> Channel slug, or workspace/channel
|
|
139
130
|
--kandan-thread-id <uuid> Resume an existing Kandan thread
|
|
140
|
-
--listen-user <user|all> User whose replies are accepted,
|
|
131
|
+
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
141
132
|
--cwd <path> Working directory for Codex
|
|
142
133
|
--codex-bin <path> Codex executable, default codex
|
|
143
134
|
--model <name> Codex model
|
package/package.json
CHANGED
|
@@ -139,18 +139,18 @@ export function senderAllowed(
|
|
|
139
139
|
event: Pick<KandanChatEvent, "actorSlug" | "actorUserId">,
|
|
140
140
|
runnerIdentity: RunnerIdentity,
|
|
141
141
|
): boolean {
|
|
142
|
-
const normalized = listenUser.trim();
|
|
142
|
+
const normalized = listenUser.trim().toLocaleLowerCase();
|
|
143
143
|
|
|
144
144
|
if (normalized === "all") {
|
|
145
145
|
return true;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
if (event.actorSlug !== undefined && event.actorSlug === normalized) {
|
|
148
|
+
if (event.actorSlug !== undefined && event.actorSlug.toLocaleLowerCase() === normalized) {
|
|
149
149
|
return true;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
return (
|
|
153
|
-
runnerIdentity.actorUsername === normalized &&
|
|
153
|
+
runnerIdentity.actorUsername?.toLocaleLowerCase() === normalized &&
|
|
154
154
|
runnerIdentity.actorUserId !== undefined &&
|
|
155
155
|
event.actorUserId === runnerIdentity.actorUserId
|
|
156
156
|
);
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { randomUUID } from "node:crypto";
|
|
|
18
18
|
import { runLocalCodexRunner, type RunnerOptions } from "./runner";
|
|
19
19
|
import { writeCachedLocalRunnerToken } from "./authCache";
|
|
20
20
|
import { resolveLocalRunnerToken } from "./authResolution";
|
|
21
|
+
import { identityFromAccessToken } from "./channelSessionSupport";
|
|
21
22
|
import { acquireLocalRunnerTokenDetails } from "./oauth";
|
|
22
23
|
|
|
23
24
|
type FlagDefinition = {
|
|
@@ -48,11 +49,13 @@ const flagDefinitions = new Map<string, FlagDefinition>([
|
|
|
48
49
|
["help", { kind: "boolean" }],
|
|
49
50
|
]);
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (import.meta.main) {
|
|
53
|
+
try {
|
|
54
|
+
await main(process.argv.slice(2));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
async function main(args: readonly string[]): Promise<void> {
|
|
@@ -63,7 +66,7 @@ async function main(args: readonly string[]): Promise<void> {
|
|
|
63
66
|
process.stdout.write(connectGuideText());
|
|
64
67
|
return;
|
|
65
68
|
case "version":
|
|
66
|
-
process.stdout.write("linzumi 0.0.
|
|
69
|
+
process.stdout.write("linzumi 0.0.4-beta\n");
|
|
67
70
|
return;
|
|
68
71
|
case "auth":
|
|
69
72
|
await runAuthCommand(parsed.args);
|
|
@@ -132,7 +135,7 @@ async function runAuthCommand(args: readonly string[]): Promise<void> {
|
|
|
132
135
|
process.stdout.write(`Saved Kandan local runner auth for ${cached.kandanBaseUrl}\n`);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
async function parseRunnerArgs(args: readonly string[]): Promise<RunnerOptions> {
|
|
138
|
+
export async function parseRunnerArgs(args: readonly string[]): Promise<RunnerOptions> {
|
|
136
139
|
const values = strictFlagValues(args);
|
|
137
140
|
|
|
138
141
|
if (values.get("help") === true) {
|
|
@@ -141,24 +144,25 @@ async function parseRunnerArgs(args: readonly string[]): Promise<RunnerOptions>
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
if (values.get("version") === true) {
|
|
144
|
-
process.stdout.write("linzumi 0.0.
|
|
147
|
+
process.stdout.write("linzumi 0.0.4-beta\n");
|
|
145
148
|
process.exit(0);
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
const
|
|
151
|
+
const channelTarget = parseChannelSessionTarget(values);
|
|
149
152
|
const kandanUrl = required(values, "kandan-url");
|
|
150
153
|
const explicitToken = stringValue(values, "token");
|
|
151
154
|
const token = await resolveLocalRunnerToken({
|
|
152
155
|
kandanUrl,
|
|
153
156
|
explicitToken,
|
|
154
|
-
workspaceSlug:
|
|
155
|
-
channelSlug:
|
|
157
|
+
workspaceSlug: channelTarget?.workspaceSlug,
|
|
158
|
+
channelSlug: channelTarget?.channelSlug,
|
|
156
159
|
authFilePath: stringValue(values, "auth-file"),
|
|
157
160
|
callbackHost: stringValue(values, "oauth-callback-host"),
|
|
158
161
|
reportRejectedCachedToken: () => {
|
|
159
162
|
process.stderr.write("Cached Kandan local runner auth was rejected; starting OAuth.\n");
|
|
160
163
|
},
|
|
161
164
|
});
|
|
165
|
+
const channelSession = parseChannelSession(values, token, channelTarget);
|
|
162
166
|
|
|
163
167
|
return {
|
|
164
168
|
kandanUrl,
|
|
@@ -210,15 +214,16 @@ function strictFlagValues(args: readonly string[]): Map<string, string | true> {
|
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
function parseChannelSession(
|
|
213
|
-
values: Map<string, string | true
|
|
217
|
+
values: Map<string, string | true>,
|
|
218
|
+
token: string,
|
|
219
|
+
target: { readonly workspaceSlug: string; readonly channelSlug: string } | undefined,
|
|
214
220
|
): RunnerOptions["channelSession"] {
|
|
215
|
-
const target = parseOptionalChannelTarget(values);
|
|
216
|
-
|
|
217
221
|
if (target === undefined) {
|
|
218
222
|
return undefined;
|
|
219
223
|
}
|
|
220
224
|
|
|
221
|
-
const listenUser =
|
|
225
|
+
const listenUser =
|
|
226
|
+
stringValue(values, "listen-user") ?? defaultListenUserFromToken(token);
|
|
222
227
|
|
|
223
228
|
return {
|
|
224
229
|
workspaceSlug: target.workspaceSlug,
|
|
@@ -232,6 +237,22 @@ function parseChannelSession(
|
|
|
232
237
|
};
|
|
233
238
|
}
|
|
234
239
|
|
|
240
|
+
function parseChannelSessionTarget(
|
|
241
|
+
values: Map<string, string | true>
|
|
242
|
+
): { readonly workspaceSlug: string; readonly channelSlug: string } | undefined {
|
|
243
|
+
return parseOptionalChannelTarget(values);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function defaultListenUserFromToken(token: string): string {
|
|
247
|
+
const username = identityFromAccessToken(token).actorUsername;
|
|
248
|
+
|
|
249
|
+
if (username !== undefined) {
|
|
250
|
+
return username;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
throw new Error("missing --listen-user and authenticated user is unavailable");
|
|
254
|
+
}
|
|
255
|
+
|
|
235
256
|
function parseOptionalChannelTarget(
|
|
236
257
|
values: Map<string, string | true>
|
|
237
258
|
): { readonly workspaceSlug: string; readonly channelSlug: string } | undefined {
|
|
@@ -297,7 +318,7 @@ function helpText(): string {
|
|
|
297
318
|
|
|
298
319
|
Usage:
|
|
299
320
|
linzumi
|
|
300
|
-
linzumi connect --kandan-url <ws-url> --workspace <slug> --channel <slug>
|
|
321
|
+
linzumi connect --kandan-url <ws-url> --workspace <slug> --channel <slug> [options]
|
|
301
322
|
linzumi auth --kandan-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
302
323
|
|
|
303
324
|
Required:
|
|
@@ -310,7 +331,7 @@ Channel binding:
|
|
|
310
331
|
--workspace <slug> Workspace slug
|
|
311
332
|
--channel <slug|w/c> Channel slug, or workspace/channel
|
|
312
333
|
--kandan-thread-id <uuid> Resume an existing Kandan thread instead of announcing a new root
|
|
313
|
-
--listen-user <user|all> User whose replies are accepted,
|
|
334
|
+
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
314
335
|
|
|
315
336
|
Codex:
|
|
316
337
|
--cwd <path> Working directory for Codex, default current directory
|
|
@@ -326,15 +347,17 @@ Codex:
|
|
|
326
347
|
|
|
327
348
|
Examples:
|
|
328
349
|
Good:
|
|
350
|
+
linzumi connect --kandan-url wss://serve.kandanai.com --workspace linzumi --channel seans-playground --codex-bin codex --launch-tui
|
|
351
|
+
linzumi connect --kandan-url wss://serve.kandanai.com --workspace linzumi --channel seans-playground --codex-bin codex --model gpt-5.5 --reasoning-effort low --fast --launch-tui
|
|
329
352
|
linzumi auth --kandan-url ws://127.0.0.1:4160 --workspace default --channel seans-playground
|
|
330
353
|
linzumi auth --kandan-url ws://100.71.192.98:4160 --oauth-callback-host 100.71.192.98 --workspace default --channel seans-playground
|
|
331
|
-
linzumi connect --kandan-url ws://127.0.0.1:4160 --token "$TOKEN" --workspace default --channel seans-playground --
|
|
332
|
-
linzumi connect --kandan-url ws://127.0.0.1:4160 --workspace default --channel seans-playground
|
|
354
|
+
linzumi connect --kandan-url ws://127.0.0.1:4160 --token "$TOKEN" --workspace default --channel seans-playground --cwd /tmp/kandan-runner-a
|
|
355
|
+
linzumi connect --kandan-url ws://127.0.0.1:4160 --workspace default --channel seans-playground
|
|
333
356
|
linzumi connect --kandan-url ws://127.0.0.1:4160 --token "$TOKEN" --channel default/seans-playground --listen-user all --launch-tui
|
|
334
357
|
|
|
335
358
|
Bad:
|
|
336
|
-
linzumi connect --kandan-url ws://127.0.0.1:4160 --workspace default --channel seans-playground
|
|
337
|
-
Missing --listen-user.
|
|
359
|
+
linzumi connect --kandan-url ws://127.0.0.1:4160 --token not-a-jwt --workspace default --channel seans-playground
|
|
360
|
+
Missing --listen-user and authenticated user is unavailable.
|
|
338
361
|
linzumi connect --kandan-url ws://127.0.0.1:4160 --token "$TOKEN" --listen-users sean
|
|
339
362
|
Invalid flag: use --listen-user.
|
|
340
363
|
`;
|
|
@@ -346,7 +369,7 @@ function connectGuideText(): string {
|
|
|
346
369
|
Connect this computer to Kandan as a local Codex runner.
|
|
347
370
|
|
|
348
371
|
Use:
|
|
349
|
-
linzumi connect --kandan-url <ws-url> --workspace <slug> --channel <slug>
|
|
372
|
+
linzumi connect --kandan-url <ws-url> --workspace <slug> --channel <slug> [options]
|
|
350
373
|
|
|
351
374
|
For help:
|
|
352
375
|
linzumi connect --help
|