@linzumi/cli 0.0.1-beta → 0.0.2-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/src/cli.js DELETED
@@ -1,240 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
-
3
- const version = "0.0.1-beta";
4
-
5
- const requiredEnv = {
6
- linzBin: "LINZUMI_LOCAL_CODEX_RUNNER_LINZ_BIN",
7
- vmId: "LINZUMI_LOCAL_CODEX_RUNNER_VM_ID",
8
- remoteCodexBin: "LINZUMI_LOCAL_CODEX_RUNNER_REMOTE_CODEX_BIN",
9
- remoteEnv: "LINZUMI_LOCAL_CODEX_RUNNER_REMOTE_ENV",
10
- };
11
-
12
- const usage = `linzumi connect [OPTIONS]
13
-
14
- Connects this machine to Kandan as the local Codex runner.
15
-
16
- This command runs on your local computer. Use the command shown by Kandan's
17
- local runner connection flow to start the local Codex runner here.
18
-
19
- Options:
20
- --linz-bin <path> LINZ CLI or bridge wrapper to execute
21
- --vm-id <id> LINZ runner VM id
22
- --remote-codex-bin <path> Remote Codex binary path
23
- --remote-env <path> Remote env-file path
24
- --remote-kandan-linz <path> Remote kandan_linz path
25
- --help Show this help
26
- --version Show CLI version
27
-
28
- Environment defaults:
29
- ${requiredEnv.linzBin}
30
- ${requiredEnv.vmId}
31
- ${requiredEnv.remoteCodexBin}
32
- ${requiredEnv.remoteEnv}
33
- LINZUMI_LOCAL_CODEX_RUNNER_REMOTE_KANDAN_LINZ
34
-
35
- Internal runner protocol:
36
- linzumi connect [OPTIONS] app-server --listen <url>
37
- `;
38
-
39
- const defaultRemoteKandanLinz = "/home/linz/.kandan/runner/bin/kandan_linz";
40
-
41
- const connectGuide = `linzumi connect
42
-
43
- This command connects this machine to Kandan as a local Codex runner.
44
-
45
- Open Kandan, start the local runner connection flow, and follow the command it
46
- shows you. Run that command on this computer.
47
-
48
- For help:
49
- linzumi connect --help
50
- `;
51
-
52
- const isBlank = (value) => typeof value !== "string" || value.trim() === "";
53
-
54
- const readOptionValue = (tokens, index, name) => {
55
- const value = tokens[index + 1];
56
- if (isBlank(value)) {
57
- return { ok: false, error: `${name} requires a value` };
58
- }
59
-
60
- return { ok: true, value, nextIndex: index + 2 };
61
- };
62
-
63
- const shellQuote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
64
-
65
- export const buildRemoteCommand = ({ remoteKandanLinz, remoteEnv, remoteCodexBin, listenUrl }) => {
66
- const rendered = [
67
- "exec",
68
- shellQuote(remoteKandanLinz),
69
- "app-server",
70
- "--env-file",
71
- shellQuote(remoteEnv),
72
- shellQuote(remoteCodexBin),
73
- shellQuote(listenUrl),
74
- ];
75
-
76
- return rendered.join(" ");
77
- };
78
-
79
- const normalizeRemotePath = (path) => {
80
- if (path.startsWith("/")) {
81
- return path;
82
- }
83
-
84
- return `/home/linz/${path}`;
85
- };
86
-
87
- export const parseLocalCodexRunnerArgs = ({ argv, env }) => {
88
- const options = {
89
- linzBin: env[requiredEnv.linzBin],
90
- vmId: env[requiredEnv.vmId],
91
- remoteCodexBin: env[requiredEnv.remoteCodexBin],
92
- remoteEnv: env[requiredEnv.remoteEnv],
93
- remoteKandanLinz:
94
- env.LINZUMI_LOCAL_CODEX_RUNNER_REMOTE_KANDAN_LINZ ?? defaultRemoteKandanLinz,
95
- };
96
- const command = [];
97
-
98
- let index = 0;
99
- while (index < argv.length) {
100
- const token = argv[index];
101
-
102
- switch (token) {
103
- case "--help":
104
- case "-h":
105
- return { ok: true, help: true };
106
- case "--version":
107
- return { ok: true, version: true };
108
- case "--linz-bin": {
109
- const parsed = readOptionValue(argv, index, "--linz-bin");
110
- if (!parsed.ok) return parsed;
111
- options.linzBin = parsed.value;
112
- index = parsed.nextIndex;
113
- break;
114
- }
115
- case "--vm-id": {
116
- const parsed = readOptionValue(argv, index, "--vm-id");
117
- if (!parsed.ok) return parsed;
118
- options.vmId = parsed.value;
119
- index = parsed.nextIndex;
120
- break;
121
- }
122
- case "--remote-codex-bin": {
123
- const parsed = readOptionValue(argv, index, "--remote-codex-bin");
124
- if (!parsed.ok) return parsed;
125
- options.remoteCodexBin = parsed.value;
126
- index = parsed.nextIndex;
127
- break;
128
- }
129
- case "--remote-env": {
130
- const parsed = readOptionValue(argv, index, "--remote-env");
131
- if (!parsed.ok) return parsed;
132
- options.remoteEnv = parsed.value;
133
- index = parsed.nextIndex;
134
- break;
135
- }
136
- case "--remote-kandan-linz": {
137
- const parsed = readOptionValue(argv, index, "--remote-kandan-linz");
138
- if (!parsed.ok) return parsed;
139
- options.remoteKandanLinz = parsed.value;
140
- index = parsed.nextIndex;
141
- break;
142
- }
143
- case "--":
144
- command.push(...argv.slice(index + 1));
145
- index = argv.length;
146
- break;
147
- default:
148
- command.push(...argv.slice(index));
149
- index = argv.length;
150
- break;
151
- }
152
- }
153
-
154
- const [subcommand, listenFlag, listenUrl, ...extra] = command;
155
- if (subcommand !== "app-server" || listenFlag !== "--listen" || isBlank(listenUrl) || extra.length > 0) {
156
- return {
157
- ok: false,
158
- error: "connect must be launched by Kandan with the internal runner protocol",
159
- };
160
- }
161
-
162
- const missing = Object.entries(options)
163
- .filter(([, value]) => isBlank(value))
164
- .map(([name]) => name);
165
-
166
- if (missing.length > 0) {
167
- return { ok: false, error: `missing connect option(s): ${missing.join(", ")}` };
168
- }
169
-
170
- return {
171
- ok: true,
172
- options: {
173
- ...options,
174
- remoteCodexBin: normalizeRemotePath(options.remoteCodexBin),
175
- remoteEnv: normalizeRemotePath(options.remoteEnv),
176
- remoteKandanLinz: normalizeRemotePath(options.remoteKandanLinz),
177
- listenUrl,
178
- },
179
- };
180
- };
181
-
182
- export const runLocalCodexRunner = ({ argv, env, cwd, spawn = spawnSync }) => {
183
- if (argv.length === 0) {
184
- return { exitCode: 0, stdout: connectGuide, stderr: "" };
185
- }
186
-
187
- const parsed = parseLocalCodexRunnerArgs({ argv, env });
188
-
189
- if (!parsed.ok) {
190
- return { exitCode: 64, stdout: "", stderr: `${parsed.error}\n` };
191
- }
192
-
193
- if (parsed.help) {
194
- return { exitCode: 0, stdout: usage, stderr: "" };
195
- }
196
-
197
- if (parsed.version) {
198
- return { exitCode: 0, stdout: `linzumi ${version}\n`, stderr: "" };
199
- }
200
-
201
- const { linzBin, vmId, remoteKandanLinz, remoteEnv, remoteCodexBin, listenUrl } = parsed.options;
202
- const remoteCommand = buildRemoteCommand({ remoteKandanLinz, remoteEnv, remoteCodexBin, listenUrl });
203
- const result = spawn(linzBin, ["exec", vmId, "--", "sh", "-lc", remoteCommand], {
204
- cwd,
205
- encoding: "utf8",
206
- stdio: ["ignore", "inherit", "inherit"],
207
- });
208
-
209
- if (result.error) {
210
- return { exitCode: 127, stdout: "", stderr: `${result.error.message}\n` };
211
- }
212
-
213
- return { exitCode: result.status ?? 1, stdout: "", stderr: "" };
214
- };
215
-
216
- export const runCliToResult = ({ argv, env, cwd, spawn = spawnSync }) => {
217
- const [command, ...rest] = argv;
218
-
219
- switch (command) {
220
- case undefined:
221
- return runLocalCodexRunner({ argv: [], env, cwd, spawn });
222
- case "connect":
223
- case "local-codex-runner":
224
- return runLocalCodexRunner({ argv: rest, env, cwd, spawn });
225
- case "--help":
226
- case "-h":
227
- return { exitCode: 0, stdout: usage, stderr: "" };
228
- case "--version":
229
- return { exitCode: 0, stdout: `linzumi ${version}\n`, stderr: "" };
230
- default:
231
- return { exitCode: 64, stdout: "", stderr: `unknown linzumi command: ${command}\n${usage}` };
232
- }
233
- };
234
-
235
- export const runCli = ({ argv, env, cwd, stdout, stderr, spawn = spawnSync }) => {
236
- const result = runCliToResult({ argv, env, cwd, spawn });
237
- if (result.stdout !== "") stdout.write(result.stdout);
238
- if (result.stderr !== "") stderr.write(result.stderr);
239
- return { exitCode: result.exitCode };
240
- };