@tryarcanist/cli 0.1.12 → 0.1.14
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/dist/index.js +787 -154
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,30 +1,131 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createRequire } from "module";
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
|
+
// src/api.ts
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
|
|
7
10
|
// ../../shared/utils/url.ts
|
|
8
11
|
function normalizeBaseUrl(url) {
|
|
9
12
|
return url.replace(/\/+$/, "");
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
// src/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// src/errors.ts
|
|
16
|
+
var CliError = class extends Error {
|
|
17
|
+
code;
|
|
18
|
+
exitCode;
|
|
19
|
+
hint;
|
|
20
|
+
requestId;
|
|
21
|
+
constructor(code, message, options = {}) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "CliError";
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.exitCode = options.exitCode ?? exitCodeForErrorCode(code);
|
|
26
|
+
this.hint = options.hint;
|
|
27
|
+
this.requestId = options.requestId;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ApiError = class extends CliError {
|
|
31
|
+
status;
|
|
32
|
+
body;
|
|
33
|
+
constructor(status, body, requestId) {
|
|
34
|
+
const code = codeForHttpStatus(status);
|
|
35
|
+
super(code, messageForApiError(status, body), {
|
|
36
|
+
exitCode: exitCodeForHttpStatus(status),
|
|
37
|
+
hint: hintForHttpStatus(status),
|
|
38
|
+
requestId
|
|
39
|
+
});
|
|
40
|
+
this.name = "ApiError";
|
|
41
|
+
this.status = status;
|
|
42
|
+
this.body = body;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function exitCodeForErrorCode(code) {
|
|
46
|
+
switch (code) {
|
|
47
|
+
case "auth":
|
|
48
|
+
return 2;
|
|
49
|
+
case "not_found":
|
|
50
|
+
return 3;
|
|
51
|
+
case "conflict":
|
|
52
|
+
return 4;
|
|
53
|
+
case "server":
|
|
54
|
+
return 10;
|
|
55
|
+
case "user":
|
|
56
|
+
default:
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function codeForHttpStatus(status) {
|
|
61
|
+
if (status === 401 || status === 403) return "auth";
|
|
62
|
+
if (status === 404) return "not_found";
|
|
63
|
+
if (status === 409) return "conflict";
|
|
64
|
+
if (status >= 500) return "server";
|
|
65
|
+
return "user";
|
|
66
|
+
}
|
|
67
|
+
function exitCodeForHttpStatus(status) {
|
|
68
|
+
return exitCodeForErrorCode(codeForHttpStatus(status));
|
|
69
|
+
}
|
|
70
|
+
function messageForApiError(status, body) {
|
|
71
|
+
const parsed = parseApiErrorBody(body);
|
|
72
|
+
if (parsed) return parsed;
|
|
73
|
+
return body ? `API error ${status}: ${body}` : `API error ${status}`;
|
|
74
|
+
}
|
|
75
|
+
function parseApiErrorBody(body) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(body);
|
|
78
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
79
|
+
const error = parsed.error;
|
|
80
|
+
return typeof error === "string" && error.length > 0 ? error : null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function hintForHttpStatus(status) {
|
|
86
|
+
if (status === 401 || status === 403) return "Run `arcanist login` or set `ARCANIST_TOKEN`.";
|
|
87
|
+
if (status === 404) return "List sessions with `arcanist sessions list`.";
|
|
88
|
+
if (status === 409) return "Check the current resource state and retry the command when it is ready.";
|
|
89
|
+
if (status >= 500) return "Retry later or check the control-plane logs.";
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
function toCliError(err) {
|
|
93
|
+
if (err instanceof CliError) return err;
|
|
94
|
+
if (err instanceof Error) return new CliError("user", err.message);
|
|
95
|
+
return new CliError("user", String(err));
|
|
96
|
+
}
|
|
97
|
+
function formatJsonError(err) {
|
|
98
|
+
return JSON.stringify({
|
|
99
|
+
error: {
|
|
100
|
+
code: err.code,
|
|
101
|
+
message: err.message,
|
|
102
|
+
...err.hint ? { hint: err.hint } : {},
|
|
103
|
+
...err.requestId ? { requestId: err.requestId } : {}
|
|
20
104
|
}
|
|
21
105
|
});
|
|
22
|
-
|
|
23
|
-
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/api.ts
|
|
109
|
+
var require2 = createRequire(import.meta.url);
|
|
110
|
+
var { version } = require2("../package.json");
|
|
111
|
+
var CLI_USER_AGENT = `arcanist-cli/${version}`;
|
|
112
|
+
async function apiRequest(config, path, init) {
|
|
113
|
+
let res;
|
|
114
|
+
try {
|
|
115
|
+
const headers = new Headers(init?.headers);
|
|
116
|
+
headers.set("Content-Type", "application/json");
|
|
117
|
+
headers.set("User-Agent", CLI_USER_AGENT);
|
|
118
|
+
headers.set("Authorization", `Bearer ${config.token}`);
|
|
119
|
+
res = await fetch(`${normalizeBaseUrl(config.apiUrl)}${path}`, {
|
|
120
|
+
...init,
|
|
121
|
+
headers
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw new CliError("server", `Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
24
125
|
}
|
|
25
126
|
if (!res.ok) {
|
|
26
127
|
const body = await res.text().catch(() => "");
|
|
27
|
-
throw new
|
|
128
|
+
throw new ApiError(res.status, body, res.headers.get("x-request-id") ?? void 0);
|
|
28
129
|
}
|
|
29
130
|
return res;
|
|
30
131
|
}
|
|
@@ -43,26 +144,42 @@ import { homedir } from "os";
|
|
|
43
144
|
import { join } from "path";
|
|
44
145
|
var CONFIG_DIR = join(homedir(), ".arcanist");
|
|
45
146
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
46
|
-
|
|
147
|
+
var DEFAULT_API_URL = "https://app.tryarcanist.com";
|
|
148
|
+
function loadFileConfig() {
|
|
47
149
|
try {
|
|
48
|
-
|
|
49
|
-
return { ...config, apiUrl: normalizeBaseUrl(config.apiUrl) };
|
|
150
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
50
151
|
} catch {
|
|
51
152
|
return null;
|
|
52
153
|
}
|
|
53
154
|
}
|
|
155
|
+
function loadConfig(overrides = {}) {
|
|
156
|
+
const fileConfig = loadFileConfig();
|
|
157
|
+
const apiUrl = overrides.apiUrl ?? process.env.ARCANIST_API_URL ?? fileConfig?.apiUrl;
|
|
158
|
+
const token = overrides.token ?? process.env.ARCANIST_TOKEN ?? fileConfig?.token;
|
|
159
|
+
if (!apiUrl || !token) return null;
|
|
160
|
+
const urlError = validateApiUrl(apiUrl);
|
|
161
|
+
if (urlError) throw new CliError("user", urlError);
|
|
162
|
+
return { apiUrl: normalizeBaseUrl(apiUrl), token };
|
|
163
|
+
}
|
|
54
164
|
function saveConfig(config) {
|
|
165
|
+
const urlError = validateApiUrl(config.apiUrl);
|
|
166
|
+
if (urlError) throw new CliError("user", urlError);
|
|
55
167
|
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
56
168
|
writeFileSync(CONFIG_FILE, JSON.stringify({ ...config, apiUrl: normalizeBaseUrl(config.apiUrl) }, null, 2) + "\n", { mode: 384 });
|
|
57
169
|
}
|
|
58
|
-
function requireConfig() {
|
|
59
|
-
const config = loadConfig();
|
|
170
|
+
function requireConfig(overrides = {}) {
|
|
171
|
+
const config = loadConfig(overrides);
|
|
60
172
|
if (!config) {
|
|
61
|
-
|
|
62
|
-
process.exit(1);
|
|
173
|
+
throw new CliError("auth", "Not logged in.", { hint: "Run `arcanist login` or set `ARCANIST_TOKEN`." });
|
|
63
174
|
}
|
|
64
175
|
return config;
|
|
65
176
|
}
|
|
177
|
+
function resolveLoginApiUrl(apiUrl) {
|
|
178
|
+
const raw = apiUrl ?? process.env.ARCANIST_API_URL ?? loadFileConfig()?.apiUrl ?? DEFAULT_API_URL;
|
|
179
|
+
const urlError = validateApiUrl(raw);
|
|
180
|
+
if (urlError) throw new CliError("user", urlError);
|
|
181
|
+
return normalizeBaseUrl(raw);
|
|
182
|
+
}
|
|
66
183
|
function validateApiUrl(url) {
|
|
67
184
|
let parsed;
|
|
68
185
|
try {
|
|
@@ -78,6 +195,83 @@ function validateApiUrl(url) {
|
|
|
78
195
|
return null;
|
|
79
196
|
}
|
|
80
197
|
|
|
198
|
+
// src/runtime.ts
|
|
199
|
+
import { randomUUID } from "crypto";
|
|
200
|
+
import { createInterface } from "readline/promises";
|
|
201
|
+
function getRuntimeOptions(command, options = {}) {
|
|
202
|
+
const globals = command?.optsWithGlobals?.();
|
|
203
|
+
const merged = { ...globals, ...options };
|
|
204
|
+
return { ...merged, noColor: merged.noColor === true || merged.color === false };
|
|
205
|
+
}
|
|
206
|
+
function isJson(command, options = {}) {
|
|
207
|
+
return getRuntimeOptions(command, options).json === true;
|
|
208
|
+
}
|
|
209
|
+
function isQuiet(command, options = {}) {
|
|
210
|
+
return getRuntimeOptions(command, options).quiet === true;
|
|
211
|
+
}
|
|
212
|
+
function writeJson(value) {
|
|
213
|
+
process.stdout.write(`${JSON.stringify(value)}
|
|
214
|
+
`);
|
|
215
|
+
}
|
|
216
|
+
async function readStdin() {
|
|
217
|
+
const chunks = [];
|
|
218
|
+
for await (const chunk of process.stdin) {
|
|
219
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
220
|
+
}
|
|
221
|
+
return Buffer.concat(chunks).toString();
|
|
222
|
+
}
|
|
223
|
+
async function readStdinTrimmed() {
|
|
224
|
+
return (await readStdin()).trim();
|
|
225
|
+
}
|
|
226
|
+
async function resolvePromptInput(promptArg, options) {
|
|
227
|
+
const shouldReadStdin = options.promptStdin === true || promptArg === "-";
|
|
228
|
+
const prompt = shouldReadStdin ? await readStdin() : promptArg;
|
|
229
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
230
|
+
throw new CliError("user", "Missing prompt. Pass a prompt argument, use '-' to read stdin, or pass --prompt-stdin.");
|
|
231
|
+
}
|
|
232
|
+
return prompt;
|
|
233
|
+
}
|
|
234
|
+
async function confirmOrThrow(message) {
|
|
235
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
236
|
+
throw new CliError("user", "Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
237
|
+
}
|
|
238
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
239
|
+
try {
|
|
240
|
+
const answer = (await rl.question(`${message} [y/N] `)).trim().toLowerCase();
|
|
241
|
+
if (answer !== "y" && answer !== "yes") {
|
|
242
|
+
throw new CliError("user", "Aborted.");
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
rl.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function printDeprecatedAlias(alias, replacement, command, options = {}) {
|
|
249
|
+
if (isQuiet(command, options)) return;
|
|
250
|
+
process.stderr.write(`Warning: \`arcanist ${alias}\` is deprecated; use \`arcanist ${replacement}\`.
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
function applyColorEnvironment(options) {
|
|
254
|
+
if (options.noColor === true || !process.stdout.isTTY) {
|
|
255
|
+
process.env.NO_COLOR = "1";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function randomIdempotencyKey() {
|
|
259
|
+
return randomUUID();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/commands/auth.ts
|
|
263
|
+
async function whoamiCommand(options, command) {
|
|
264
|
+
const runtime = getRuntimeOptions(command, options);
|
|
265
|
+
const config = requireConfig(runtime);
|
|
266
|
+
const payload = await apiFetch(config, "/api/auth/whoami");
|
|
267
|
+
if (isJson(command, options)) {
|
|
268
|
+
writeJson(payload);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
console.log(`User: ${String(payload.email ?? payload.userId ?? "unknown")}`);
|
|
272
|
+
if (payload.tokenScope) console.log(`Token scope: ${String(payload.tokenScope)}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
81
275
|
// src/commands/create.ts
|
|
82
276
|
var REPO_URL_PATTERNS = [
|
|
83
277
|
/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/,
|
|
@@ -88,89 +282,104 @@ function validateRepoUrl(url) {
|
|
|
88
282
|
if (REPO_URL_PATTERNS.some((pattern) => pattern.test(url))) return null;
|
|
89
283
|
return `Invalid repo URL: "${url}". Expected a GitHub URL (https://github.com/owner/repo) or owner/repo shorthand.`;
|
|
90
284
|
}
|
|
91
|
-
async function createCommand(repoUrl,
|
|
92
|
-
const
|
|
285
|
+
async function createCommand(repoUrl, promptArg, options, command) {
|
|
286
|
+
const runtime = getRuntimeOptions(command, options);
|
|
287
|
+
const config = requireConfig(runtime);
|
|
288
|
+
const prompt = await resolvePromptInput(promptArg, options);
|
|
93
289
|
const repoError = validateRepoUrl(repoUrl);
|
|
94
290
|
if (repoError) {
|
|
95
|
-
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
let sessionId;
|
|
99
|
-
try {
|
|
100
|
-
const sessionData = await apiFetch(config, "/api/sessions", {
|
|
101
|
-
method: "POST",
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
context: { repoUrl },
|
|
104
|
-
...options.model ? { model: options.model } : {}
|
|
105
|
-
})
|
|
106
|
-
});
|
|
107
|
-
sessionId = sessionData.sessionId;
|
|
108
|
-
} catch (err) {
|
|
109
|
-
console.error(`Error creating session: ${err instanceof Error ? err.message : String(err)}`);
|
|
110
|
-
process.exit(1);
|
|
291
|
+
throw new CliError("user", repoError);
|
|
111
292
|
}
|
|
293
|
+
const idempotencyKey = options.idempotencyKey ?? randomIdempotencyKey();
|
|
294
|
+
const sessionIdempotencyKey = `${idempotencyKey}:session`;
|
|
295
|
+
const promptIdempotencyKey = `${idempotencyKey}:prompt`;
|
|
296
|
+
const sessionData = await apiFetch(config, "/api/sessions", {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { "Idempotency-Key": sessionIdempotencyKey },
|
|
299
|
+
body: JSON.stringify({
|
|
300
|
+
context: { repoUrl },
|
|
301
|
+
...options.model ? { model: options.model } : {}
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
const sessionId = sessionData.sessionId;
|
|
112
305
|
try {
|
|
113
|
-
await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
|
|
306
|
+
const promptData = await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
|
|
114
307
|
method: "POST",
|
|
308
|
+
headers: { "Idempotency-Key": promptIdempotencyKey },
|
|
115
309
|
body: JSON.stringify({ prompt })
|
|
116
310
|
});
|
|
311
|
+
const promptId = promptData.prompt?.promptId ?? promptData.prompt?.id;
|
|
312
|
+
if (isJson(command, options)) {
|
|
313
|
+
writeJson({
|
|
314
|
+
sessionId,
|
|
315
|
+
...sessionData.sessionUrl ? { sessionUrl: sessionData.sessionUrl } : {},
|
|
316
|
+
repoUrl,
|
|
317
|
+
...options.model ? { model: options.model } : {},
|
|
318
|
+
...promptId ? { promptId } : {}
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
117
322
|
} catch (err) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
323
|
+
throw new CliError(err instanceof CliError ? err.code : "server", `Session created (${sessionId}) but prompt failed: ${err instanceof Error ? err.message : String(err)}`, {
|
|
324
|
+
exitCode: err instanceof CliError ? err.exitCode : void 0,
|
|
325
|
+
hint: `Retry with: arcanist sessions send ${sessionId} --prompt-stdin`,
|
|
326
|
+
requestId: err instanceof CliError ? err.requestId : void 0
|
|
327
|
+
});
|
|
121
328
|
}
|
|
122
|
-
const uiUrl = config.apiUrl.replace(/:\d+$/, ":5173");
|
|
123
329
|
console.log(`Session: ${sessionId}`);
|
|
124
|
-
console.log(`URL: ${
|
|
330
|
+
if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
|
|
125
331
|
}
|
|
126
332
|
|
|
127
333
|
// src/commands/login.ts
|
|
128
|
-
import { createInterface } from "readline";
|
|
129
|
-
async function loginCommand(options) {
|
|
334
|
+
import { createInterface as createInterface2 } from "readline";
|
|
335
|
+
async function loginCommand(options, command) {
|
|
336
|
+
const runtime = getRuntimeOptions(command, options);
|
|
130
337
|
let token;
|
|
131
338
|
if (options.tokenStdin) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
token = Buffer.concat(chunks).toString().trim();
|
|
339
|
+
token = await readStdinTrimmed();
|
|
340
|
+
} else if (runtime.token) {
|
|
341
|
+
token = runtime.token;
|
|
137
342
|
} else {
|
|
138
343
|
token = await promptHidden("Enter your CLI token: ");
|
|
139
344
|
}
|
|
140
345
|
if (!token) {
|
|
141
|
-
|
|
142
|
-
process.exit(1);
|
|
346
|
+
throw new CliError("user", "No token provided.");
|
|
143
347
|
}
|
|
144
348
|
if (!token.startsWith("arc_")) {
|
|
145
|
-
|
|
146
|
-
process.exit(1);
|
|
349
|
+
throw new CliError("user", "Invalid token format. Token must start with 'arc_'.");
|
|
147
350
|
}
|
|
148
|
-
if (
|
|
149
|
-
const urlError = validateApiUrl(
|
|
351
|
+
if (runtime.apiUrl) {
|
|
352
|
+
const urlError = validateApiUrl(runtime.apiUrl);
|
|
150
353
|
if (urlError) {
|
|
151
|
-
|
|
152
|
-
process.exit(1);
|
|
354
|
+
throw new CliError("user", urlError);
|
|
153
355
|
}
|
|
154
356
|
}
|
|
155
|
-
const apiUrl = normalizeBaseUrl(
|
|
357
|
+
const apiUrl = normalizeBaseUrl(resolveLoginApiUrl(runtime.apiUrl));
|
|
156
358
|
saveConfig({ apiUrl, token });
|
|
157
|
-
|
|
359
|
+
if (runtime.json) {
|
|
360
|
+
writeJson({ ok: true, apiUrl });
|
|
361
|
+
} else if (!runtime.quiet) {
|
|
362
|
+
console.log(`Logged in. API: ${apiUrl}`);
|
|
363
|
+
}
|
|
158
364
|
try {
|
|
159
365
|
const res = await fetch(`${apiUrl}/api/cli-tokens`, {
|
|
160
|
-
headers: { Authorization: `Bearer ${token}
|
|
366
|
+
headers: { Authorization: `Bearer ${token}`, "User-Agent": CLI_USER_AGENT }
|
|
161
367
|
});
|
|
162
|
-
if (res.ok) {
|
|
368
|
+
if (res.ok && !runtime.json && !runtime.quiet) {
|
|
163
369
|
console.log("Token verified.");
|
|
164
|
-
} else if (res.status === 401) {
|
|
370
|
+
} else if (res.status === 401 && !runtime.json && !runtime.quiet) {
|
|
165
371
|
console.warn("Warning: Token could not be verified (401). It may be invalid or expired.");
|
|
166
372
|
}
|
|
167
373
|
} catch {
|
|
168
|
-
console.warn("Warning: Could not reach API to verify token.");
|
|
374
|
+
if (!runtime.json && !runtime.quiet) console.warn("Warning: Could not reach API to verify token.");
|
|
169
375
|
}
|
|
170
376
|
}
|
|
171
377
|
function promptHidden(prompt) {
|
|
172
|
-
|
|
173
|
-
|
|
378
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
379
|
+
return Promise.reject(new CliError("user", "No interactive terminal available. Re-run with --token-stdin or set ARCANIST_TOKEN."));
|
|
380
|
+
}
|
|
381
|
+
return new Promise((resolve, reject) => {
|
|
382
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
174
383
|
process.stdout.write(prompt);
|
|
175
384
|
const stdin = process.stdin;
|
|
176
385
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
@@ -185,7 +394,9 @@ function promptHidden(prompt) {
|
|
|
185
394
|
resolve(input);
|
|
186
395
|
} else if (c === "") {
|
|
187
396
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
188
|
-
|
|
397
|
+
stdin.removeListener("data", onData);
|
|
398
|
+
rl.close();
|
|
399
|
+
reject(new CliError("user", "Interrupted.", { exitCode: 130 }));
|
|
189
400
|
} else if (c === "\x7F" || c === "\b") {
|
|
190
401
|
input = input.slice(0, -1);
|
|
191
402
|
} else {
|
|
@@ -197,36 +408,26 @@ function promptHidden(prompt) {
|
|
|
197
408
|
}
|
|
198
409
|
|
|
199
410
|
// src/commands/message.ts
|
|
200
|
-
async function messageCommand(sessionId,
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// src/commands/stop.ts
|
|
215
|
-
async function stopCommand(sessionId) {
|
|
216
|
-
const config = requireConfig();
|
|
217
|
-
try {
|
|
218
|
-
await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
|
|
219
|
-
method: "POST"
|
|
220
|
-
});
|
|
221
|
-
console.log(`Stop requested for session ${sessionId}.`);
|
|
222
|
-
} catch (err) {
|
|
223
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
224
|
-
process.exit(1);
|
|
411
|
+
async function messageCommand(sessionId, promptArg, options = {}, command) {
|
|
412
|
+
const runtime = getRuntimeOptions(command, options);
|
|
413
|
+
const config = requireConfig(runtime);
|
|
414
|
+
const prompt = await resolvePromptInput(promptArg, options);
|
|
415
|
+
const response = await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
|
|
416
|
+
method: "POST",
|
|
417
|
+
headers: { "Idempotency-Key": options.idempotencyKey ?? randomIdempotencyKey() },
|
|
418
|
+
body: JSON.stringify({ prompt })
|
|
419
|
+
});
|
|
420
|
+
const promptId = response.prompt?.promptId ?? response.prompt?.id;
|
|
421
|
+
if (isJson(command, options)) {
|
|
422
|
+
writeJson({ sessionId, ...promptId ? { promptId } : {} });
|
|
423
|
+
return;
|
|
225
424
|
}
|
|
425
|
+
console.log(`Message sent to session ${sessionId}.`);
|
|
226
426
|
}
|
|
227
427
|
|
|
228
428
|
// ../../shared/transcript/projector.ts
|
|
229
429
|
var DUPLICATE_TEXT_DELTA_MIN_CHARS = 24;
|
|
430
|
+
var SUBAGENT_EVENT_TYPES = /* @__PURE__ */ new Set(["subagent_start", "subagent_complete", "subagent_tool_call", "subagent_text"]);
|
|
230
431
|
var RAW_OPENCODE_NOISE = /* @__PURE__ */ new Set([
|
|
231
432
|
"session.updated",
|
|
232
433
|
"session.diff",
|
|
@@ -240,6 +441,12 @@ function shouldAppendTextDelta(existingText, incomingText) {
|
|
|
240
441
|
if (incomingText.length < DUPLICATE_TEXT_DELTA_MIN_CHARS) return true;
|
|
241
442
|
return !existingText.endsWith(incomingText);
|
|
242
443
|
}
|
|
444
|
+
function streamableKey(type, streamId) {
|
|
445
|
+
return `${type}:${streamId}`;
|
|
446
|
+
}
|
|
447
|
+
function resolveSegmentId(streamId, segmentOrdinal) {
|
|
448
|
+
return segmentOrdinal === 0 ? streamId : `${streamId}#${segmentOrdinal}`;
|
|
449
|
+
}
|
|
243
450
|
function isRecord(value) {
|
|
244
451
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
245
452
|
}
|
|
@@ -274,6 +481,8 @@ function normalizeToolStatus(value) {
|
|
|
274
481
|
function flattenSessionEvents(raw) {
|
|
275
482
|
const merged = [];
|
|
276
483
|
const streamableIndexById = /* @__PURE__ */ new Map();
|
|
484
|
+
const segmentOrdinalByStreamId = /* @__PURE__ */ new Map();
|
|
485
|
+
const concatByStreamId = /* @__PURE__ */ new Map();
|
|
277
486
|
const toolCallIndexById = /* @__PURE__ */ new Map();
|
|
278
487
|
const questionIndexById = /* @__PURE__ */ new Map();
|
|
279
488
|
for (const event of raw) {
|
|
@@ -326,7 +535,11 @@ function flattenSessionEvents(raw) {
|
|
|
326
535
|
message: typeof data?.message === "string" ? data.message : "Retrying...",
|
|
327
536
|
...typeof data?.nextRetryAt === "string" ? { nextRetryAt: data.nextRetryAt } : {},
|
|
328
537
|
...typeof data?.provider === "string" ? { provider: data.provider } : {},
|
|
329
|
-
...typeof data?.errorCode === "string" ? { errorCode: data.errorCode } : {}
|
|
538
|
+
...typeof data?.errorCode === "string" ? { errorCode: data.errorCode } : {},
|
|
539
|
+
...typeof data?.scope === "string" ? { scope: data.scope } : {},
|
|
540
|
+
...typeof data?.maxAttempts === "number" ? { maxAttempts: data.maxAttempts } : {},
|
|
541
|
+
...typeof data?.reason === "string" ? { reason: data.reason } : {},
|
|
542
|
+
...typeof data?.retryAfterMs === "number" ? { retryAfterMs: data.retryAfterMs } : {}
|
|
330
543
|
});
|
|
331
544
|
continue;
|
|
332
545
|
}
|
|
@@ -365,21 +578,30 @@ function flattenSessionEvents(raw) {
|
|
|
365
578
|
continue;
|
|
366
579
|
}
|
|
367
580
|
if (event.type === "reasoning") {
|
|
368
|
-
const
|
|
581
|
+
const streamId = resolveEventId(data, "reasoning", merged.length);
|
|
582
|
+
const key = streamableKey("reasoning", streamId);
|
|
369
583
|
const text = resolveTextValue(data);
|
|
370
584
|
const promptId = resolvePromptId(data);
|
|
371
|
-
const
|
|
372
|
-
|
|
585
|
+
const existingText = concatByStreamId.get(key) ?? "";
|
|
586
|
+
const existingIdx = streamableIndexById.get(key);
|
|
587
|
+
if (existingIdx !== void 0 && existingIdx === merged.length - 1) {
|
|
373
588
|
const existing = merged[existingIdx];
|
|
374
|
-
if (existing.type === "reasoning" && shouldAppendTextDelta(
|
|
589
|
+
if (existing.type === "reasoning" && shouldAppendTextDelta(existingText, text)) {
|
|
375
590
|
existing.text += text;
|
|
591
|
+
concatByStreamId.set(key, existingText + text);
|
|
376
592
|
if (!existing.promptId && promptId) existing.promptId = promptId;
|
|
377
593
|
}
|
|
378
594
|
} else {
|
|
379
|
-
|
|
595
|
+
if (existingText && !shouldAppendTextDelta(existingText, text)) continue;
|
|
596
|
+
const previousOrdinal = segmentOrdinalByStreamId.get(key);
|
|
597
|
+
const segmentOrdinal = previousOrdinal === void 0 ? 0 : previousOrdinal + 1;
|
|
598
|
+
segmentOrdinalByStreamId.set(key, segmentOrdinal);
|
|
599
|
+
streamableIndexById.set(key, merged.length);
|
|
600
|
+
concatByStreamId.set(key, existingText + text);
|
|
380
601
|
merged.push({
|
|
381
602
|
type: "reasoning",
|
|
382
|
-
id,
|
|
603
|
+
id: resolveSegmentId(streamId, segmentOrdinal),
|
|
604
|
+
...segmentOrdinal > 0 ? { streamId } : {},
|
|
383
605
|
text,
|
|
384
606
|
...promptId ? { promptId } : {}
|
|
385
607
|
});
|
|
@@ -429,21 +651,30 @@ function flattenSessionEvents(raw) {
|
|
|
429
651
|
continue;
|
|
430
652
|
}
|
|
431
653
|
if (event.type === "text") {
|
|
432
|
-
const
|
|
654
|
+
const streamId = resolveEventId(data, "text", merged.length);
|
|
655
|
+
const key = streamableKey("text", streamId);
|
|
433
656
|
const text = resolveTextValue(data);
|
|
434
657
|
const promptId = resolvePromptId(data);
|
|
435
|
-
const
|
|
436
|
-
|
|
658
|
+
const existingText = concatByStreamId.get(key) ?? "";
|
|
659
|
+
const existingIdx = streamableIndexById.get(key);
|
|
660
|
+
if (existingIdx !== void 0 && existingIdx === merged.length - 1) {
|
|
437
661
|
const existing = merged[existingIdx];
|
|
438
|
-
if (existing.type === "text" && shouldAppendTextDelta(
|
|
662
|
+
if (existing.type === "text" && shouldAppendTextDelta(existingText, text)) {
|
|
439
663
|
existing.text += text;
|
|
664
|
+
concatByStreamId.set(key, existingText + text);
|
|
440
665
|
if (!existing.promptId && promptId) existing.promptId = promptId;
|
|
441
666
|
}
|
|
442
667
|
} else {
|
|
443
|
-
|
|
668
|
+
if (existingText && !shouldAppendTextDelta(existingText, text)) continue;
|
|
669
|
+
const previousOrdinal = segmentOrdinalByStreamId.get(key);
|
|
670
|
+
const segmentOrdinal = previousOrdinal === void 0 ? 0 : previousOrdinal + 1;
|
|
671
|
+
segmentOrdinalByStreamId.set(key, segmentOrdinal);
|
|
672
|
+
streamableIndexById.set(key, merged.length);
|
|
673
|
+
concatByStreamId.set(key, existingText + text);
|
|
444
674
|
merged.push({
|
|
445
675
|
type: "text",
|
|
446
|
-
id,
|
|
676
|
+
id: resolveSegmentId(streamId, segmentOrdinal),
|
|
677
|
+
...segmentOrdinal > 0 ? { streamId } : {},
|
|
447
678
|
text,
|
|
448
679
|
...promptId ? { promptId } : {}
|
|
449
680
|
});
|
|
@@ -537,6 +768,65 @@ function getEmbeddedTerminalHistory(raw) {
|
|
|
537
768
|
function resolveAuthoritativePromptEvents(raw) {
|
|
538
769
|
return getEmbeddedTerminalHistory(raw) ?? raw;
|
|
539
770
|
}
|
|
771
|
+
function extractSubagentInfo(raw) {
|
|
772
|
+
const empty = {
|
|
773
|
+
names: /* @__PURE__ */ new Map(),
|
|
774
|
+
activity: /* @__PURE__ */ new Map(),
|
|
775
|
+
toolToChildSessions: /* @__PURE__ */ new Map(),
|
|
776
|
+
subagentUsage: /* @__PURE__ */ new Map()
|
|
777
|
+
};
|
|
778
|
+
if (!raw.some((event) => SUBAGENT_EVENT_TYPES.has(event.type))) return empty;
|
|
779
|
+
const { names, activity, toolToChildSessions, subagentUsage } = empty;
|
|
780
|
+
for (const event of raw) {
|
|
781
|
+
const data = event.data;
|
|
782
|
+
if (!data) continue;
|
|
783
|
+
const childSessionId = typeof data.childSessionId === "string" ? data.childSessionId : void 0;
|
|
784
|
+
const parentToolId = typeof data.parentToolId === "string" ? data.parentToolId : void 0;
|
|
785
|
+
const key = childSessionId || parentToolId;
|
|
786
|
+
if (!key) continue;
|
|
787
|
+
if (event.type === "subagent_start" || event.type === "subagent_complete") {
|
|
788
|
+
const name = typeof data.name === "string" ? data.name : "";
|
|
789
|
+
if (name) names.set(key, name);
|
|
790
|
+
if (parentToolId && childSessionId) {
|
|
791
|
+
const existing = toolToChildSessions.get(parentToolId) ?? [];
|
|
792
|
+
if (!existing.includes(childSessionId)) existing.push(childSessionId);
|
|
793
|
+
toolToChildSessions.set(parentToolId, existing);
|
|
794
|
+
}
|
|
795
|
+
if (event.type === "subagent_complete" && isRecord(data.tokenUsage)) {
|
|
796
|
+
subagentUsage.set(key, {
|
|
797
|
+
input: Number(data.tokenUsage.input ?? 0),
|
|
798
|
+
output: Number(data.tokenUsage.output ?? 0),
|
|
799
|
+
cacheRead: Number(data.tokenUsage.cacheRead ?? 0),
|
|
800
|
+
cacheWrite: Number(data.tokenUsage.cacheWrite ?? 0)
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (event.type === "subagent_tool_call") {
|
|
806
|
+
const items = activity.get(key) ?? [];
|
|
807
|
+
items.push({
|
|
808
|
+
type: "tool",
|
|
809
|
+
tool: typeof data.tool === "string" ? data.tool : "unknown",
|
|
810
|
+
summary: typeof data.summary === "string" ? data.summary : ""
|
|
811
|
+
});
|
|
812
|
+
activity.set(key, items);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (event.type === "subagent_text") {
|
|
816
|
+
const delta = typeof data.delta === "string" ? data.delta : typeof data.text === "string" ? data.text : "";
|
|
817
|
+
if (!delta) continue;
|
|
818
|
+
const items = activity.get(key) ?? [];
|
|
819
|
+
const last = items[items.length - 1];
|
|
820
|
+
if (last?.type === "text") {
|
|
821
|
+
items[items.length - 1] = { ...last, text: last.text + delta };
|
|
822
|
+
} else {
|
|
823
|
+
items.push({ type: "text", text: delta });
|
|
824
|
+
}
|
|
825
|
+
activity.set(key, items);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return { names, activity, toolToChildSessions, subagentUsage };
|
|
829
|
+
}
|
|
540
830
|
|
|
541
831
|
// src/utils/session-output.ts
|
|
542
832
|
function formatDate(value) {
|
|
@@ -544,13 +834,33 @@ function formatDate(value) {
|
|
|
544
834
|
if (Number.isNaN(date.getTime())) return value;
|
|
545
835
|
return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
546
836
|
}
|
|
547
|
-
function
|
|
837
|
+
function renderSubagentActivityItem(item) {
|
|
838
|
+
if (item.type === "text") return ` - ${item.text}
|
|
839
|
+
`;
|
|
840
|
+
return ` - **Tool:** ${item.tool}${item.summary ? ` - ${item.summary}` : ""}
|
|
841
|
+
`;
|
|
842
|
+
}
|
|
843
|
+
function renderSubagentActivityForTool(toolId, subagents) {
|
|
844
|
+
const childSessionIds = subagents.toolToChildSessions.get(toolId) ?? [];
|
|
845
|
+
if (childSessionIds.length === 0) return "";
|
|
846
|
+
const lines = [];
|
|
847
|
+
for (const childSessionId of childSessionIds) {
|
|
848
|
+
const name = subagents.names.get(childSessionId);
|
|
849
|
+
lines.push(` - **Subagent:** ${name ?? childSessionId}`);
|
|
850
|
+
for (const item of subagents.activity.get(childSessionId) ?? []) {
|
|
851
|
+
lines.push(renderSubagentActivityItem(item).trimEnd());
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return `${lines.join("\n")}
|
|
855
|
+
`;
|
|
856
|
+
}
|
|
857
|
+
function renderTranscriptEvent(event, subagents) {
|
|
548
858
|
switch (event.type) {
|
|
549
859
|
case "text":
|
|
550
860
|
return event.text;
|
|
551
861
|
case "tool_call":
|
|
552
862
|
return `**Tool call:** ${event.tool}${event.summary ? ` - ${event.summary}` : ""}${event.toolStatus ? ` (${event.toolStatus})` : ""}
|
|
553
|
-
`;
|
|
863
|
+
${subagents ? renderSubagentActivityForTool(event.id, subagents) : ""}`;
|
|
554
864
|
case "reasoning":
|
|
555
865
|
return `<details><summary>Reasoning</summary>
|
|
556
866
|
|
|
@@ -613,7 +923,9 @@ function renderSessionTranscript(exportData) {
|
|
|
613
923
|
for (let i = 0; i < exportData.prompts.length; i++) {
|
|
614
924
|
const prompt = exportData.prompts[i];
|
|
615
925
|
const rawEvents = eventBuckets.get(prompt.id) ?? [];
|
|
616
|
-
const
|
|
926
|
+
const authoritativeEvents = resolveAuthoritativePromptEvents(rawEvents);
|
|
927
|
+
const events = flattenSessionEvents(authoritativeEvents);
|
|
928
|
+
const subagents = extractSubagentInfo(authoritativeEvents);
|
|
617
929
|
lines.push("---\n");
|
|
618
930
|
lines.push(`## Turn ${i + 1}
|
|
619
931
|
`);
|
|
@@ -627,7 +939,7 @@ function renderSessionTranscript(exportData) {
|
|
|
627
939
|
lines.push("**Assistant:**\n");
|
|
628
940
|
let pendingText = "";
|
|
629
941
|
for (const event of events) {
|
|
630
|
-
const rendered = renderTranscriptEvent(event);
|
|
942
|
+
const rendered = renderTranscriptEvent(event, subagents);
|
|
631
943
|
if (!rendered) continue;
|
|
632
944
|
if (event.type === "text") {
|
|
633
945
|
pendingText += rendered;
|
|
@@ -794,22 +1106,6 @@ function renderWatchEvent(event, state) {
|
|
|
794
1106
|
}
|
|
795
1107
|
}
|
|
796
1108
|
|
|
797
|
-
// src/commands/transcript.ts
|
|
798
|
-
async function transcriptCommand(sessionId, options) {
|
|
799
|
-
const config = requireConfig();
|
|
800
|
-
try {
|
|
801
|
-
const exportData = await apiFetch(config, `/api/sessions/${sessionId}/export`);
|
|
802
|
-
if (options.json) {
|
|
803
|
-
console.log(JSON.stringify(exportData, null, 2));
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
console.log(renderSessionTranscript(exportData));
|
|
807
|
-
} catch (err) {
|
|
808
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
809
|
-
process.exit(1);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
1109
|
// src/constants/watch.ts
|
|
814
1110
|
var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
|
|
815
1111
|
var WATCH_REPLAY_PAGE_SIZE = 200;
|
|
@@ -821,11 +1117,17 @@ function sleep(ms) {
|
|
|
821
1117
|
}
|
|
822
1118
|
function parsePollInterval(raw) {
|
|
823
1119
|
if (!raw) return DEFAULT_WATCH_POLL_INTERVAL_MS;
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
throw new Error("Polling interval must be a non-negative integer.");
|
|
1120
|
+
if (!/^\d+$/.test(raw)) {
|
|
1121
|
+
throw new CliError("user", "Polling interval must be a non-negative integer.");
|
|
827
1122
|
}
|
|
828
|
-
return
|
|
1123
|
+
return Number(raw);
|
|
1124
|
+
}
|
|
1125
|
+
function parseNonNegativeInteger(raw, name, defaultValue) {
|
|
1126
|
+
if (!raw) return defaultValue;
|
|
1127
|
+
if (!/^\d+$/.test(raw)) {
|
|
1128
|
+
throw new CliError("user", `${name} must be a non-negative integer.`);
|
|
1129
|
+
}
|
|
1130
|
+
return Number(raw);
|
|
829
1131
|
}
|
|
830
1132
|
function formatStatusLine(status) {
|
|
831
1133
|
const details = [];
|
|
@@ -837,33 +1139,42 @@ async function fetchPromptLabels(config, sessionId) {
|
|
|
837
1139
|
const data = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
|
|
838
1140
|
return buildPromptLabelMap(data.prompts);
|
|
839
1141
|
}
|
|
840
|
-
async function watchCommand(sessionId, options) {
|
|
841
|
-
const
|
|
1142
|
+
async function watchCommand(sessionId, options, command) {
|
|
1143
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1144
|
+
const config = requireConfig(runtime);
|
|
842
1145
|
const pollIntervalMs = parsePollInterval(options.pollInterval);
|
|
1146
|
+
const initialAfterSequence = parseNonNegativeInteger(options.afterSequence, "--after-sequence", 0);
|
|
1147
|
+
const pageSize = parseNonNegativeInteger(options.limit, "--limit", WATCH_REPLAY_PAGE_SIZE);
|
|
1148
|
+
if (pageSize === 0) {
|
|
1149
|
+
throw new CliError("user", "--limit must be greater than 0.");
|
|
1150
|
+
}
|
|
1151
|
+
const json = isJson(command, options);
|
|
843
1152
|
let promptLabels = /* @__PURE__ */ new Map();
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1153
|
+
if (!json) {
|
|
1154
|
+
try {
|
|
1155
|
+
promptLabels = await fetchPromptLabels(config, sessionId);
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
console.error(`Warning: failed to fetch prompt labels for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1158
|
+
}
|
|
848
1159
|
}
|
|
849
1160
|
const renderState = {
|
|
850
1161
|
promptLabels,
|
|
851
1162
|
toolCalls: /* @__PURE__ */ new Map()
|
|
852
1163
|
};
|
|
853
|
-
let afterSequence =
|
|
1164
|
+
let afterSequence = initialAfterSequence;
|
|
854
1165
|
let lastStatusLine = null;
|
|
855
1166
|
let textOpen = false;
|
|
856
|
-
console.log(`Watching session ${sessionId}...`);
|
|
1167
|
+
if (!json) console.log(`Watching session ${sessionId}...`);
|
|
857
1168
|
try {
|
|
858
1169
|
while (true) {
|
|
859
1170
|
const query = new URLSearchParams({
|
|
860
1171
|
afterSequence: String(afterSequence),
|
|
861
|
-
limit: String(
|
|
1172
|
+
limit: String(pageSize)
|
|
862
1173
|
});
|
|
863
1174
|
const payload = await apiFetchText(config, `/api/sessions/${sessionId}/events?${query}`);
|
|
864
1175
|
const parsed = parseSsePayload(payload);
|
|
865
|
-
const receivedFullPage = parsed.events.length >=
|
|
866
|
-
if (parsed.status) {
|
|
1176
|
+
const receivedFullPage = parsed.events.length >= pageSize;
|
|
1177
|
+
if (!json && parsed.status) {
|
|
867
1178
|
const nextStatusLine = formatStatusLine(parsed.status);
|
|
868
1179
|
if (nextStatusLine !== lastStatusLine) {
|
|
869
1180
|
if (textOpen) {
|
|
@@ -876,6 +1187,11 @@ async function watchCommand(sessionId, options) {
|
|
|
876
1187
|
}
|
|
877
1188
|
for (const event of parsed.events) {
|
|
878
1189
|
if (typeof event.id === "number" && event.id > afterSequence) afterSequence = event.id;
|
|
1190
|
+
if (json) {
|
|
1191
|
+
process.stdout.write(`${JSON.stringify({ sequence: event.id, type: event.type, data: event.data })}
|
|
1192
|
+
`);
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
879
1195
|
const rendered = renderWatchEvent(event, renderState);
|
|
880
1196
|
if (!rendered) continue;
|
|
881
1197
|
if (rendered.kind === "text") {
|
|
@@ -899,30 +1215,347 @@ async function watchCommand(sessionId, options) {
|
|
|
899
1215
|
await sleep(pollIntervalMs);
|
|
900
1216
|
}
|
|
901
1217
|
}
|
|
902
|
-
}
|
|
1218
|
+
} finally {
|
|
903
1219
|
if (textOpen) process.stdout.write("\n");
|
|
904
|
-
|
|
905
|
-
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/commands/sessions.ts
|
|
1224
|
+
async function listSessionsCommand(options, command) {
|
|
1225
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1226
|
+
const config = requireConfig(runtime);
|
|
1227
|
+
const query = new URLSearchParams();
|
|
1228
|
+
if (options.status) query.set("status", options.status);
|
|
1229
|
+
if (options.scope) query.set("scope", options.scope);
|
|
1230
|
+
if (options.limit) query.set("limit", options.limit);
|
|
1231
|
+
if (options.cursor) query.set("cursor", options.cursor);
|
|
1232
|
+
const payload = await apiFetch(
|
|
1233
|
+
config,
|
|
1234
|
+
`/api/sessions${query.size ? `?${query.toString()}` : ""}`
|
|
1235
|
+
);
|
|
1236
|
+
if (isJson(command, options)) {
|
|
1237
|
+
writeJson(payload);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (payload.sessions.length === 0) {
|
|
1241
|
+
console.log("No sessions found.");
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
for (const session of payload.sessions) {
|
|
1245
|
+
const id = String(session.id ?? session.sessionId ?? "");
|
|
1246
|
+
const status = String(session.status ?? "");
|
|
1247
|
+
const title = typeof session.title === "string" ? ` ${session.title}` : "";
|
|
1248
|
+
console.log(`${id} ${status}${title}`);
|
|
1249
|
+
}
|
|
1250
|
+
if (payload.nextCursor) console.log(`Next cursor: ${payload.nextCursor}`);
|
|
1251
|
+
}
|
|
1252
|
+
async function getSessionCommand(sessionId, options, command) {
|
|
1253
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1254
|
+
const config = requireConfig(runtime);
|
|
1255
|
+
const payload = await apiFetch(config, `/api/sessions/${sessionId}`);
|
|
1256
|
+
if (isJson(command, options)) {
|
|
1257
|
+
writeJson(payload);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
const session = payload.session && typeof payload.session === "object" ? payload.session : payload;
|
|
1261
|
+
console.log(`Session: ${String(session.sessionId ?? session.id ?? sessionId)}`);
|
|
1262
|
+
console.log(`Status: ${String(session.status ?? "unknown")}`);
|
|
1263
|
+
if (session.repoUrl) console.log(`Repo: ${String(session.repoUrl)}`);
|
|
1264
|
+
if (session.title) console.log(`Title: ${String(session.title)}`);
|
|
1265
|
+
}
|
|
1266
|
+
async function sessionEventsCommand(sessionId, options, command) {
|
|
1267
|
+
if (options.follow) {
|
|
1268
|
+
if (options.beforeSequence) {
|
|
1269
|
+
throw new CliError("user", "--before-sequence cannot be used with --follow.");
|
|
1270
|
+
}
|
|
1271
|
+
if (options.promptId) {
|
|
1272
|
+
throw new CliError("user", "--prompt-id cannot be used with --follow because the follow endpoint does not support prompt filtering.");
|
|
1273
|
+
}
|
|
1274
|
+
await watchCommand(sessionId, { ...options, pollInterval: options.pollInterval, afterSequence: options.afterSequence, limit: options.limit }, command);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1278
|
+
const config = requireConfig(runtime);
|
|
1279
|
+
const query = new URLSearchParams();
|
|
1280
|
+
if (options.afterSequence) query.set("after_sequence", options.afterSequence);
|
|
1281
|
+
if (options.beforeSequence) query.set("before_sequence", options.beforeSequence);
|
|
1282
|
+
if (options.promptId) query.set("prompt_id", options.promptId);
|
|
1283
|
+
if (options.limit) query.set("limit", options.limit);
|
|
1284
|
+
const payload = await apiFetch(config, `/api/sessions/${sessionId}/events/history${query.size ? `?${query.toString()}` : ""}`);
|
|
1285
|
+
if (isJson(command, options)) {
|
|
1286
|
+
writeJson(payload);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const promptLabels = await fetchPromptLabels(config, sessionId).catch(() => /* @__PURE__ */ new Map());
|
|
1290
|
+
const state = { promptLabels, toolCalls: /* @__PURE__ */ new Map() };
|
|
1291
|
+
let textOpen = false;
|
|
1292
|
+
for (const event of payload.events) {
|
|
1293
|
+
const rendered = renderWatchEvent(event, state);
|
|
1294
|
+
if (rendered?.kind === "line") {
|
|
1295
|
+
if (textOpen) {
|
|
1296
|
+
process.stdout.write("\n");
|
|
1297
|
+
textOpen = false;
|
|
1298
|
+
}
|
|
1299
|
+
console.log(rendered.line);
|
|
1300
|
+
}
|
|
1301
|
+
if (rendered?.kind === "text" && rendered.text) {
|
|
1302
|
+
process.stdout.write(rendered.text);
|
|
1303
|
+
textOpen = true;
|
|
1304
|
+
}
|
|
906
1305
|
}
|
|
907
1306
|
if (textOpen) process.stdout.write("\n");
|
|
908
1307
|
}
|
|
1308
|
+
async function usageCommand(sessionId, options, command) {
|
|
1309
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1310
|
+
const config = requireConfig(runtime);
|
|
1311
|
+
const payload = await apiFetch(config, `/api/sessions/${sessionId}/usage`);
|
|
1312
|
+
if (isJson(command, options)) {
|
|
1313
|
+
writeJson(payload);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const usage = payload.usage && typeof payload.usage === "object" ? payload.usage : payload;
|
|
1317
|
+
console.log(`Input tokens: ${String(usage.inputTokens ?? 0)}`);
|
|
1318
|
+
console.log(`Output tokens: ${String(usage.outputTokens ?? 0)}`);
|
|
1319
|
+
console.log(`Total tokens: ${String(usage.totalTokens ?? 0)}`);
|
|
1320
|
+
if (usage.totalCostUsd !== void 0) console.log(`Cost: ${String(usage.totalCostUsd)}`);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/commands/stop.ts
|
|
1324
|
+
async function stopCommand(sessionId, options = {}, command) {
|
|
1325
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1326
|
+
const config = requireConfig(runtime);
|
|
1327
|
+
const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
|
|
1328
|
+
method: "POST"
|
|
1329
|
+
});
|
|
1330
|
+
const status = response.status ?? "stopping";
|
|
1331
|
+
if (isJson(command, options)) {
|
|
1332
|
+
writeJson({ sessionId, status });
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
console.log(status === "already_stopped" ? `Session ${sessionId} is already stopped.` : `Stop requested for session ${sessionId}.`);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/commands/tokens.ts
|
|
1339
|
+
async function listTokensCommand(options, command) {
|
|
1340
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1341
|
+
const config = requireConfig(runtime);
|
|
1342
|
+
const query = new URLSearchParams();
|
|
1343
|
+
if (options.limit) query.set("limit", options.limit);
|
|
1344
|
+
if (options.cursor) query.set("cursor", options.cursor);
|
|
1345
|
+
const payload = await apiFetch(config, `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`);
|
|
1346
|
+
if (isJson(command, options)) {
|
|
1347
|
+
writeJson(payload);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
if (payload.data.length === 0) {
|
|
1351
|
+
console.log("No CLI tokens found.");
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
for (const token of payload.data) {
|
|
1355
|
+
console.log(`${String(token.id)} ${String(token.scope)} ${String(token.tokenPrefix)} ${String(token.revokedAt ? "revoked" : "active")}`);
|
|
1356
|
+
}
|
|
1357
|
+
if (payload.nextCursor) console.log(`Next cursor: ${payload.nextCursor}`);
|
|
1358
|
+
}
|
|
1359
|
+
async function createTokenCommand(options, command) {
|
|
1360
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1361
|
+
const config = requireConfig(runtime);
|
|
1362
|
+
const expiresInDays = options.expiresInDays === void 0 ? void 0 : Number(options.expiresInDays);
|
|
1363
|
+
if (expiresInDays !== void 0 && (!Number.isInteger(expiresInDays) || expiresInDays <= 0)) {
|
|
1364
|
+
throw new CliError("user", "expiresInDays must be a positive integer.");
|
|
1365
|
+
}
|
|
1366
|
+
const payload = await apiFetch(config, "/api/cli-tokens", {
|
|
1367
|
+
method: "POST",
|
|
1368
|
+
body: JSON.stringify({
|
|
1369
|
+
scope: options.scope ?? "read",
|
|
1370
|
+
...expiresInDays !== void 0 ? { expiresInDays } : {}
|
|
1371
|
+
})
|
|
1372
|
+
});
|
|
1373
|
+
if (isJson(command, options)) {
|
|
1374
|
+
writeJson(payload);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
console.log(`Token: ${String(payload.token)}`);
|
|
1378
|
+
console.log(`ID: ${String(payload.id)}`);
|
|
1379
|
+
console.log(`Scope: ${String(payload.scope)}`);
|
|
1380
|
+
}
|
|
1381
|
+
async function revokeTokenCommand(tokenId, options, command) {
|
|
1382
|
+
if (isJson(command, options) && options.yes !== true) {
|
|
1383
|
+
throw new CliError("user", "`tokens revoke --json` requires --yes.");
|
|
1384
|
+
}
|
|
1385
|
+
if (options.yes !== true) {
|
|
1386
|
+
await confirmOrThrow(`Revoke CLI token ${tokenId}?`);
|
|
1387
|
+
}
|
|
1388
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1389
|
+
const config = requireConfig(runtime);
|
|
1390
|
+
const payload = await apiFetch(config, `/api/cli-tokens/${tokenId}/revoke`, { method: "POST" });
|
|
1391
|
+
if (isJson(command, options)) {
|
|
1392
|
+
writeJson(payload);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
console.log(`Revoked token ${tokenId}.`);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/commands/transcript.ts
|
|
1399
|
+
async function transcriptCommand(sessionId, options, command) {
|
|
1400
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1401
|
+
const config = requireConfig(runtime);
|
|
1402
|
+
const exportData = await apiFetch(config, `/api/sessions/${sessionId}/export`);
|
|
1403
|
+
if (isJson(command, options)) {
|
|
1404
|
+
writeJson(exportData);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
console.log(renderSessionTranscript(exportData));
|
|
1408
|
+
}
|
|
909
1409
|
|
|
910
1410
|
// src/index.ts
|
|
911
|
-
var
|
|
912
|
-
var { version } =
|
|
913
|
-
var program = new Command().name("arcanist").description("Arcanist CLI").version(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1411
|
+
var require3 = createRequire2(import.meta.url);
|
|
1412
|
+
var { version: version2 } = require3("../package.json");
|
|
1413
|
+
var program = new Command().name("arcanist").description("Arcanist CLI").version(version2).option("--json", "Output machine-readable JSON").option("--quiet", "Suppress non-essential stderr output").option("--api-url <url>", "Override API URL. Prefer ARCANIST_API_URL for persistent use").option("--token <token>", "Override API token. Prefer ARCANIST_TOKEN because flags can be visible in shell history and process lists").option("--no-color", "Disable color output").exitOverride().addHelpText("after", `
|
|
1414
|
+
Examples:
|
|
1415
|
+
arcanist auth login --token-stdin
|
|
1416
|
+
arcanist sessions create https://github.com/org/repo "fix bug" --json | jq -r .sessionId
|
|
1417
|
+
printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
|
|
1418
|
+
arcanist sessions events <session-id> --follow --json
|
|
1419
|
+
|
|
1420
|
+
Exit codes:
|
|
1421
|
+
0 ok, 1 user/input, 2 auth, 3 not found, 4 conflict, 10 server/network, 130 interrupted
|
|
1422
|
+
`);
|
|
1423
|
+
program.configureOutput({
|
|
1424
|
+
writeErr: (str) => process.stderr.write(str)
|
|
1425
|
+
});
|
|
1426
|
+
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
1427
|
+
applyColorEnvironment(getRuntimeOptions(actionCommand));
|
|
1428
|
+
});
|
|
1429
|
+
function addCreateOptions(cmd) {
|
|
1430
|
+
return cmd.argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--model <model>", "Model to use").option("--prompt-stdin", "Read prompt from stdin").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText("after", `
|
|
1431
|
+
Examples:
|
|
1432
|
+
arcanist sessions create https://github.com/org/repo "fix bug"
|
|
1433
|
+
printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
|
|
1434
|
+
arcanist sessions create https://github.com/org/repo - --json | jq -r .sessionId | xargs -I{} arcanist sessions events {} --follow --json
|
|
1435
|
+
`);
|
|
1436
|
+
}
|
|
1437
|
+
function addSendOptions(cmd) {
|
|
1438
|
+
return cmd.argument("<session-id>", "Session ID").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--prompt-stdin", "Read prompt from stdin").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText("after", `
|
|
1439
|
+
Examples:
|
|
1440
|
+
arcanist sessions send <session-id> "also update tests"
|
|
1441
|
+
printf "also update tests" | arcanist sessions send <session-id> --prompt-stdin --json
|
|
1442
|
+
`);
|
|
1443
|
+
}
|
|
1444
|
+
var auth = program.command("auth").description("Authentication commands");
|
|
1445
|
+
auth.command("login").description("Authenticate with a personal access token").option("--token-stdin", "Read token from stdin instead of interactive prompt").option("--api-url <url>", "Set custom API URL").addHelpText("after", `
|
|
1446
|
+
Examples:
|
|
1447
|
+
arcanist auth login
|
|
1448
|
+
printf "arc_..." | arcanist auth login --token-stdin
|
|
1449
|
+
ARCANIST_TOKEN=arc_... arcanist auth whoami --json
|
|
1450
|
+
`).action((options, command) => loginCommand(options, command));
|
|
1451
|
+
auth.command("whoami").description("Print the authenticated user and token scope").addHelpText("after", `
|
|
1452
|
+
Examples:
|
|
1453
|
+
arcanist auth whoami
|
|
1454
|
+
ARCANIST_TOKEN=arc_... arcanist auth whoami --json
|
|
1455
|
+
`).action((options, command) => whoamiCommand(options, command));
|
|
1456
|
+
var sessions = program.command("sessions").description("Session commands");
|
|
1457
|
+
addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action((repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command));
|
|
1458
|
+
addSendOptions(sessions.command("send").description("Send a message to an existing session")).action((sessionId, prompt, options, command) => messageCommand(sessionId, prompt, options, command));
|
|
1459
|
+
sessions.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").addHelpText("after", `
|
|
1460
|
+
Examples:
|
|
1461
|
+
arcanist sessions stop <session-id>
|
|
1462
|
+
arcanist sessions stop <session-id> --json
|
|
1463
|
+
`).action((sessionId, options, command) => stopCommand(sessionId, options, command));
|
|
1464
|
+
sessions.command("get").description("Get session details").argument("<session-id>", "Session ID").addHelpText("after", `
|
|
1465
|
+
Examples:
|
|
1466
|
+
arcanist sessions get <session-id>
|
|
1467
|
+
arcanist sessions get <session-id> --json
|
|
1468
|
+
`).action((sessionId, options, command) => getSessionCommand(sessionId, options, command));
|
|
1469
|
+
sessions.command("list").description("List sessions").option("--status <status>", "Filter by session status").option("--scope <scope>", "Session scope: mine or business").option("--limit <n>", "Maximum sessions to return").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
1470
|
+
Examples:
|
|
1471
|
+
arcanist sessions list
|
|
1472
|
+
arcanist sessions list --status idle --json
|
|
1473
|
+
`).action((options, command) => listSessionsCommand(options, command));
|
|
1474
|
+
sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--before-sequence <n>", "Return events before this sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until the session is idle").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText("after", `
|
|
1475
|
+
Examples:
|
|
1476
|
+
arcanist sessions events <session-id> --json
|
|
1477
|
+
arcanist sessions events <session-id> --follow --json
|
|
1478
|
+
`).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
|
|
1479
|
+
sessions.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").addHelpText("after", `
|
|
1480
|
+
Examples:
|
|
1481
|
+
arcanist sessions transcript <session-id>
|
|
1482
|
+
arcanist sessions transcript <session-id> --json
|
|
1483
|
+
`).action((sessionId, options, command) => transcriptCommand(sessionId, options, command));
|
|
1484
|
+
sessions.command("watch").description("Watch session activity until it becomes idle").argument("<session-id>", "Session ID").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText("after", `
|
|
1485
|
+
Examples:
|
|
1486
|
+
arcanist sessions watch <session-id>
|
|
1487
|
+
arcanist sessions watch <session-id> --json
|
|
1488
|
+
`).action((sessionId, options, command) => watchCommand(sessionId, options, command));
|
|
1489
|
+
sessions.command("usage").description("Get token usage for a session").argument("<session-id>", "Session ID").addHelpText("after", `
|
|
1490
|
+
Examples:
|
|
1491
|
+
arcanist sessions usage <session-id>
|
|
1492
|
+
arcanist sessions usage <session-id> --json
|
|
1493
|
+
`).action((sessionId, options, command) => usageCommand(sessionId, options, command));
|
|
1494
|
+
var tokens = program.command("tokens").description("CLI token commands");
|
|
1495
|
+
tokens.command("list").description("List CLI tokens").option("--limit <n>", "Maximum tokens to return").option("--cursor <cursor>", "Pagination cursor").action((options, command) => listTokensCommand(options, command));
|
|
1496
|
+
tokens.command("create").description("Create a CLI token and print it once").option("--scope <scope>", "Token scope: read or write", "read").option("--expires-in-days <days>", "Token expiry in days").addHelpText("after", `
|
|
1497
|
+
Examples:
|
|
1498
|
+
arcanist tokens create --scope read
|
|
1499
|
+
arcanist tokens create --scope read --json
|
|
1500
|
+
`).action((options, command) => createTokenCommand(options, command));
|
|
1501
|
+
tokens.command("revoke").description("Revoke a CLI token").argument("<id>", "Token ID").option("--yes", "Confirm revocation without prompting").addHelpText("after", `
|
|
1502
|
+
Examples:
|
|
1503
|
+
arcanist tokens revoke 42
|
|
1504
|
+
arcanist tokens revoke 42 --yes --json
|
|
1505
|
+
`).action((id, options, command) => revokeTokenCommand(id, options, command));
|
|
1506
|
+
program.command("login").description("Authenticate with a personal access token").option("--token-stdin", "Read token from stdin instead of interactive prompt").option("--api-url <url>", "Set custom API URL").action((options, command) => {
|
|
1507
|
+
printDeprecatedAlias("login", "auth login", command);
|
|
1508
|
+
return loginCommand(options, command);
|
|
1509
|
+
});
|
|
1510
|
+
addCreateOptions(program.command("create").description("Create a session and send a prompt")).action((repoUrl, prompt, options, command) => {
|
|
1511
|
+
printDeprecatedAlias("create", "sessions create", command);
|
|
1512
|
+
return createCommand(repoUrl, prompt, options, command);
|
|
1513
|
+
});
|
|
1514
|
+
addSendOptions(program.command("message").description("Send a message to an existing session")).action((sessionId, prompt, options, command) => {
|
|
1515
|
+
printDeprecatedAlias("message", "sessions send", command);
|
|
1516
|
+
return messageCommand(sessionId, prompt, options, command);
|
|
1517
|
+
});
|
|
1518
|
+
program.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").action((sessionId, options, command) => {
|
|
1519
|
+
printDeprecatedAlias("stop", "sessions stop", command);
|
|
1520
|
+
return stopCommand(sessionId, options, command);
|
|
1521
|
+
});
|
|
1522
|
+
program.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").action((sessionId, options, command) => {
|
|
1523
|
+
printDeprecatedAlias("transcript", "sessions transcript", command);
|
|
1524
|
+
return transcriptCommand(sessionId, options, command);
|
|
1525
|
+
});
|
|
1526
|
+
program.command("watch").description("Watch session activity until it becomes idle").argument("<session-id>", "Session ID").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).action((sessionId, options, command) => {
|
|
1527
|
+
printDeprecatedAlias("watch", "sessions events --follow", command);
|
|
1528
|
+
return watchCommand(sessionId, options, command);
|
|
1529
|
+
});
|
|
920
1530
|
async function main() {
|
|
921
1531
|
try {
|
|
922
1532
|
await program.parseAsync(process.argv);
|
|
923
1533
|
} catch (err) {
|
|
924
|
-
|
|
925
|
-
|
|
1534
|
+
if (isCommanderHelpOrVersion(err)) {
|
|
1535
|
+
process.exit(0);
|
|
1536
|
+
}
|
|
1537
|
+
if (err instanceof CliError && err.exitCode === 130) restoreTerminal();
|
|
1538
|
+
const cliError = toCliError(err);
|
|
1539
|
+
const options = program.opts();
|
|
1540
|
+
if (options.json) {
|
|
1541
|
+
process.stderr.write(`${formatJsonError(cliError)}
|
|
1542
|
+
`);
|
|
1543
|
+
} else {
|
|
1544
|
+
process.stderr.write(`Error: ${cliError.message}
|
|
1545
|
+
`);
|
|
1546
|
+
if (cliError.hint) process.stderr.write(`Hint: ${cliError.hint}
|
|
1547
|
+
`);
|
|
1548
|
+
}
|
|
1549
|
+
process.exit(cliError.exitCode);
|
|
926
1550
|
}
|
|
927
1551
|
}
|
|
1552
|
+
function isCommanderHelpOrVersion(err) {
|
|
1553
|
+
if (!err || typeof err !== "object") return false;
|
|
1554
|
+
const code = err.code;
|
|
1555
|
+
return code === "commander.helpDisplayed" || code === "commander.version";
|
|
1556
|
+
}
|
|
1557
|
+
function restoreTerminal() {
|
|
1558
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1559
|
+
process.stdout.write("\x1B[?25h");
|
|
1560
|
+
}
|
|
928
1561
|
main();
|