@tryarcanist/cli 0.1.12 → 0.1.13

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.
Files changed (2) hide show
  1. package/dist/index.js +658 -137
  2. 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/api.ts
13
- async function apiRequest(config, path, init) {
14
- const res = await fetch(`${normalizeBaseUrl(config.apiUrl)}${path}`, {
15
- ...init,
16
- headers: {
17
- "Content-Type": "application/json",
18
- Authorization: `Bearer ${config.token}`,
19
- ...init?.headers
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
- if (res.status === 401) {
23
- throw new Error("Token is invalid or expired. Run `arcanist login` to re-authenticate.");
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 Error(`API error ${res.status}: ${body}`);
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
- function loadConfig() {
147
+ var DEFAULT_API_URL = "https://app.tryarcanist.com";
148
+ function loadFileConfig() {
47
149
  try {
48
- const config = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
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
- console.error("Error: Not logged in. Run `arcanist login` first.");
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, prompt, options) {
92
- const config = requireConfig();
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
- console.error(`Error: ${repoError}`);
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
- console.error(`Session created (${sessionId}) but prompt failed: ${err instanceof Error ? err.message : String(err)}`);
119
- console.error(`Retry with: arcanist message ${sessionId} "${prompt}"`);
120
- process.exit(1);
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: ${uiUrl}/sessions/${sessionId}`);
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
- const chunks = [];
133
- for await (const chunk of process.stdin) {
134
- chunks.push(chunk);
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
- console.error("Error: No token provided.");
142
- process.exit(1);
346
+ throw new CliError("user", "No token provided.");
143
347
  }
144
348
  if (!token.startsWith("arc_")) {
145
- console.error("Error: Invalid token format. Token must start with 'arc_'.");
146
- process.exit(1);
349
+ throw new CliError("user", "Invalid token format. Token must start with 'arc_'.");
147
350
  }
148
- if (options.apiUrl) {
149
- const urlError = validateApiUrl(options.apiUrl);
351
+ if (runtime.apiUrl) {
352
+ const urlError = validateApiUrl(runtime.apiUrl);
150
353
  if (urlError) {
151
- console.error(`Error: ${urlError}`);
152
- process.exit(1);
354
+ throw new CliError("user", urlError);
153
355
  }
154
356
  }
155
- const apiUrl = normalizeBaseUrl(options.apiUrl ?? loadConfig()?.apiUrl ?? "https://app.tryarcanist.com");
357
+ const apiUrl = normalizeBaseUrl(resolveLoginApiUrl(runtime.apiUrl));
156
358
  saveConfig({ apiUrl, token });
157
- console.log(`Logged in. API: ${apiUrl}`);
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
- return new Promise((resolve) => {
173
- const rl = createInterface({ input: process.stdin, output: process.stdout });
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
- process.exit(1);
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,32 +408,21 @@ function promptHidden(prompt) {
197
408
  }
198
409
 
199
410
  // src/commands/message.ts
200
- async function messageCommand(sessionId, prompt) {
201
- const config = requireConfig();
202
- try {
203
- await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
204
- method: "POST",
205
- body: JSON.stringify({ prompt })
206
- });
207
- console.log(`Message sent to session ${sessionId}.`);
208
- } catch (err) {
209
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
210
- process.exit(1);
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
@@ -794,22 +994,6 @@ function renderWatchEvent(event, state) {
794
994
  }
795
995
  }
796
996
 
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
997
  // src/constants/watch.ts
814
998
  var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
815
999
  var WATCH_REPLAY_PAGE_SIZE = 200;
@@ -821,11 +1005,17 @@ function sleep(ms) {
821
1005
  }
822
1006
  function parsePollInterval(raw) {
823
1007
  if (!raw) return DEFAULT_WATCH_POLL_INTERVAL_MS;
824
- const parsed = Number.parseInt(raw, 10);
825
- if (!Number.isFinite(parsed) || parsed < 0) {
826
- throw new Error("Polling interval must be a non-negative integer.");
1008
+ if (!/^\d+$/.test(raw)) {
1009
+ throw new CliError("user", "Polling interval must be a non-negative integer.");
827
1010
  }
828
- return parsed;
1011
+ return Number(raw);
1012
+ }
1013
+ function parseNonNegativeInteger(raw, name, defaultValue) {
1014
+ if (!raw) return defaultValue;
1015
+ if (!/^\d+$/.test(raw)) {
1016
+ throw new CliError("user", `${name} must be a non-negative integer.`);
1017
+ }
1018
+ return Number(raw);
829
1019
  }
830
1020
  function formatStatusLine(status) {
831
1021
  const details = [];
@@ -837,33 +1027,42 @@ async function fetchPromptLabels(config, sessionId) {
837
1027
  const data = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
838
1028
  return buildPromptLabelMap(data.prompts);
839
1029
  }
840
- async function watchCommand(sessionId, options) {
841
- const config = requireConfig();
1030
+ async function watchCommand(sessionId, options, command) {
1031
+ const runtime = getRuntimeOptions(command, options);
1032
+ const config = requireConfig(runtime);
842
1033
  const pollIntervalMs = parsePollInterval(options.pollInterval);
1034
+ const initialAfterSequence = parseNonNegativeInteger(options.afterSequence, "--after-sequence", 0);
1035
+ const pageSize = parseNonNegativeInteger(options.limit, "--limit", WATCH_REPLAY_PAGE_SIZE);
1036
+ if (pageSize === 0) {
1037
+ throw new CliError("user", "--limit must be greater than 0.");
1038
+ }
1039
+ const json = isJson(command, options);
843
1040
  let promptLabels = /* @__PURE__ */ new Map();
844
- try {
845
- promptLabels = await fetchPromptLabels(config, sessionId);
846
- } catch (err) {
847
- console.error(`Warning: failed to fetch prompt labels for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
1041
+ if (!json) {
1042
+ try {
1043
+ promptLabels = await fetchPromptLabels(config, sessionId);
1044
+ } catch (err) {
1045
+ console.error(`Warning: failed to fetch prompt labels for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
1046
+ }
848
1047
  }
849
1048
  const renderState = {
850
1049
  promptLabels,
851
1050
  toolCalls: /* @__PURE__ */ new Map()
852
1051
  };
853
- let afterSequence = 0;
1052
+ let afterSequence = initialAfterSequence;
854
1053
  let lastStatusLine = null;
855
1054
  let textOpen = false;
856
- console.log(`Watching session ${sessionId}...`);
1055
+ if (!json) console.log(`Watching session ${sessionId}...`);
857
1056
  try {
858
1057
  while (true) {
859
1058
  const query = new URLSearchParams({
860
1059
  afterSequence: String(afterSequence),
861
- limit: String(WATCH_REPLAY_PAGE_SIZE)
1060
+ limit: String(pageSize)
862
1061
  });
863
1062
  const payload = await apiFetchText(config, `/api/sessions/${sessionId}/events?${query}`);
864
1063
  const parsed = parseSsePayload(payload);
865
- const receivedFullPage = parsed.events.length >= WATCH_REPLAY_PAGE_SIZE;
866
- if (parsed.status) {
1064
+ const receivedFullPage = parsed.events.length >= pageSize;
1065
+ if (!json && parsed.status) {
867
1066
  const nextStatusLine = formatStatusLine(parsed.status);
868
1067
  if (nextStatusLine !== lastStatusLine) {
869
1068
  if (textOpen) {
@@ -876,6 +1075,11 @@ async function watchCommand(sessionId, options) {
876
1075
  }
877
1076
  for (const event of parsed.events) {
878
1077
  if (typeof event.id === "number" && event.id > afterSequence) afterSequence = event.id;
1078
+ if (json) {
1079
+ process.stdout.write(`${JSON.stringify({ sequence: event.id, type: event.type, data: event.data })}
1080
+ `);
1081
+ continue;
1082
+ }
879
1083
  const rendered = renderWatchEvent(event, renderState);
880
1084
  if (!rendered) continue;
881
1085
  if (rendered.kind === "text") {
@@ -899,30 +1103,347 @@ async function watchCommand(sessionId, options) {
899
1103
  await sleep(pollIntervalMs);
900
1104
  }
901
1105
  }
902
- } catch (err) {
1106
+ } finally {
903
1107
  if (textOpen) process.stdout.write("\n");
904
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
905
- process.exit(1);
1108
+ }
1109
+ }
1110
+
1111
+ // src/commands/sessions.ts
1112
+ async function listSessionsCommand(options, command) {
1113
+ const runtime = getRuntimeOptions(command, options);
1114
+ const config = requireConfig(runtime);
1115
+ const query = new URLSearchParams();
1116
+ if (options.status) query.set("status", options.status);
1117
+ if (options.scope) query.set("scope", options.scope);
1118
+ if (options.limit) query.set("limit", options.limit);
1119
+ if (options.cursor) query.set("cursor", options.cursor);
1120
+ const payload = await apiFetch(
1121
+ config,
1122
+ `/api/sessions${query.size ? `?${query.toString()}` : ""}`
1123
+ );
1124
+ if (isJson(command, options)) {
1125
+ writeJson(payload);
1126
+ return;
1127
+ }
1128
+ if (payload.sessions.length === 0) {
1129
+ console.log("No sessions found.");
1130
+ return;
1131
+ }
1132
+ for (const session of payload.sessions) {
1133
+ const id = String(session.id ?? session.sessionId ?? "");
1134
+ const status = String(session.status ?? "");
1135
+ const title = typeof session.title === "string" ? ` ${session.title}` : "";
1136
+ console.log(`${id} ${status}${title}`);
1137
+ }
1138
+ if (payload.nextCursor) console.log(`Next cursor: ${payload.nextCursor}`);
1139
+ }
1140
+ async function getSessionCommand(sessionId, options, command) {
1141
+ const runtime = getRuntimeOptions(command, options);
1142
+ const config = requireConfig(runtime);
1143
+ const payload = await apiFetch(config, `/api/sessions/${sessionId}`);
1144
+ if (isJson(command, options)) {
1145
+ writeJson(payload);
1146
+ return;
1147
+ }
1148
+ const session = payload.session && typeof payload.session === "object" ? payload.session : payload;
1149
+ console.log(`Session: ${String(session.sessionId ?? session.id ?? sessionId)}`);
1150
+ console.log(`Status: ${String(session.status ?? "unknown")}`);
1151
+ if (session.repoUrl) console.log(`Repo: ${String(session.repoUrl)}`);
1152
+ if (session.title) console.log(`Title: ${String(session.title)}`);
1153
+ }
1154
+ async function sessionEventsCommand(sessionId, options, command) {
1155
+ if (options.follow) {
1156
+ if (options.beforeSequence) {
1157
+ throw new CliError("user", "--before-sequence cannot be used with --follow.");
1158
+ }
1159
+ if (options.promptId) {
1160
+ throw new CliError("user", "--prompt-id cannot be used with --follow because the follow endpoint does not support prompt filtering.");
1161
+ }
1162
+ await watchCommand(sessionId, { ...options, pollInterval: options.pollInterval, afterSequence: options.afterSequence, limit: options.limit }, command);
1163
+ return;
1164
+ }
1165
+ const runtime = getRuntimeOptions(command, options);
1166
+ const config = requireConfig(runtime);
1167
+ const query = new URLSearchParams();
1168
+ if (options.afterSequence) query.set("after_sequence", options.afterSequence);
1169
+ if (options.beforeSequence) query.set("before_sequence", options.beforeSequence);
1170
+ if (options.promptId) query.set("prompt_id", options.promptId);
1171
+ if (options.limit) query.set("limit", options.limit);
1172
+ const payload = await apiFetch(config, `/api/sessions/${sessionId}/events/history${query.size ? `?${query.toString()}` : ""}`);
1173
+ if (isJson(command, options)) {
1174
+ writeJson(payload);
1175
+ return;
1176
+ }
1177
+ const promptLabels = await fetchPromptLabels(config, sessionId).catch(() => /* @__PURE__ */ new Map());
1178
+ const state = { promptLabels, toolCalls: /* @__PURE__ */ new Map() };
1179
+ let textOpen = false;
1180
+ for (const event of payload.events) {
1181
+ const rendered = renderWatchEvent(event, state);
1182
+ if (rendered?.kind === "line") {
1183
+ if (textOpen) {
1184
+ process.stdout.write("\n");
1185
+ textOpen = false;
1186
+ }
1187
+ console.log(rendered.line);
1188
+ }
1189
+ if (rendered?.kind === "text" && rendered.text) {
1190
+ process.stdout.write(rendered.text);
1191
+ textOpen = true;
1192
+ }
906
1193
  }
907
1194
  if (textOpen) process.stdout.write("\n");
908
1195
  }
1196
+ async function usageCommand(sessionId, options, command) {
1197
+ const runtime = getRuntimeOptions(command, options);
1198
+ const config = requireConfig(runtime);
1199
+ const payload = await apiFetch(config, `/api/sessions/${sessionId}/usage`);
1200
+ if (isJson(command, options)) {
1201
+ writeJson(payload);
1202
+ return;
1203
+ }
1204
+ const usage = payload.usage && typeof payload.usage === "object" ? payload.usage : payload;
1205
+ console.log(`Input tokens: ${String(usage.inputTokens ?? 0)}`);
1206
+ console.log(`Output tokens: ${String(usage.outputTokens ?? 0)}`);
1207
+ console.log(`Total tokens: ${String(usage.totalTokens ?? 0)}`);
1208
+ if (usage.totalCostUsd !== void 0) console.log(`Cost: ${String(usage.totalCostUsd)}`);
1209
+ }
1210
+
1211
+ // src/commands/stop.ts
1212
+ async function stopCommand(sessionId, options = {}, command) {
1213
+ const runtime = getRuntimeOptions(command, options);
1214
+ const config = requireConfig(runtime);
1215
+ const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
1216
+ method: "POST"
1217
+ });
1218
+ const status = response.status ?? "stopping";
1219
+ if (isJson(command, options)) {
1220
+ writeJson({ sessionId, status });
1221
+ return;
1222
+ }
1223
+ console.log(status === "already_stopped" ? `Session ${sessionId} is already stopped.` : `Stop requested for session ${sessionId}.`);
1224
+ }
1225
+
1226
+ // src/commands/tokens.ts
1227
+ async function listTokensCommand(options, command) {
1228
+ const runtime = getRuntimeOptions(command, options);
1229
+ const config = requireConfig(runtime);
1230
+ const query = new URLSearchParams();
1231
+ if (options.limit) query.set("limit", options.limit);
1232
+ if (options.cursor) query.set("cursor", options.cursor);
1233
+ const payload = await apiFetch(config, `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`);
1234
+ if (isJson(command, options)) {
1235
+ writeJson(payload);
1236
+ return;
1237
+ }
1238
+ if (payload.data.length === 0) {
1239
+ console.log("No CLI tokens found.");
1240
+ return;
1241
+ }
1242
+ for (const token of payload.data) {
1243
+ console.log(`${String(token.id)} ${String(token.scope)} ${String(token.tokenPrefix)} ${String(token.revokedAt ? "revoked" : "active")}`);
1244
+ }
1245
+ if (payload.nextCursor) console.log(`Next cursor: ${payload.nextCursor}`);
1246
+ }
1247
+ async function createTokenCommand(options, command) {
1248
+ const runtime = getRuntimeOptions(command, options);
1249
+ const config = requireConfig(runtime);
1250
+ const expiresInDays = options.expiresInDays === void 0 ? void 0 : Number(options.expiresInDays);
1251
+ if (expiresInDays !== void 0 && (!Number.isInteger(expiresInDays) || expiresInDays <= 0)) {
1252
+ throw new CliError("user", "expiresInDays must be a positive integer.");
1253
+ }
1254
+ const payload = await apiFetch(config, "/api/cli-tokens", {
1255
+ method: "POST",
1256
+ body: JSON.stringify({
1257
+ scope: options.scope ?? "read",
1258
+ ...expiresInDays !== void 0 ? { expiresInDays } : {}
1259
+ })
1260
+ });
1261
+ if (isJson(command, options)) {
1262
+ writeJson(payload);
1263
+ return;
1264
+ }
1265
+ console.log(`Token: ${String(payload.token)}`);
1266
+ console.log(`ID: ${String(payload.id)}`);
1267
+ console.log(`Scope: ${String(payload.scope)}`);
1268
+ }
1269
+ async function revokeTokenCommand(tokenId, options, command) {
1270
+ if (isJson(command, options) && options.yes !== true) {
1271
+ throw new CliError("user", "`tokens revoke --json` requires --yes.");
1272
+ }
1273
+ if (options.yes !== true) {
1274
+ await confirmOrThrow(`Revoke CLI token ${tokenId}?`);
1275
+ }
1276
+ const runtime = getRuntimeOptions(command, options);
1277
+ const config = requireConfig(runtime);
1278
+ const payload = await apiFetch(config, `/api/cli-tokens/${tokenId}/revoke`, { method: "POST" });
1279
+ if (isJson(command, options)) {
1280
+ writeJson(payload);
1281
+ return;
1282
+ }
1283
+ console.log(`Revoked token ${tokenId}.`);
1284
+ }
1285
+
1286
+ // src/commands/transcript.ts
1287
+ async function transcriptCommand(sessionId, options, command) {
1288
+ const runtime = getRuntimeOptions(command, options);
1289
+ const config = requireConfig(runtime);
1290
+ const exportData = await apiFetch(config, `/api/sessions/${sessionId}/export`);
1291
+ if (isJson(command, options)) {
1292
+ writeJson(exportData);
1293
+ return;
1294
+ }
1295
+ console.log(renderSessionTranscript(exportData));
1296
+ }
909
1297
 
910
1298
  // src/index.ts
911
- var require2 = createRequire(import.meta.url);
912
- var { version } = require2("../package.json");
913
- var program = new Command().name("arcanist").description("Arcanist CLI").version(version);
914
- 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(loginCommand);
915
- program.command("create").description("Create a session and send a prompt").argument("<repo-url>", "Repository URL").argument("<prompt>", "Prompt to send").option("--model <model>", "Model to use").action(createCommand);
916
- program.command("message").description("Send a message to an existing session").argument("<session-id>", "Session ID").argument("<prompt>", "Message to send").action(messageCommand);
917
- program.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").action(stopCommand);
918
- program.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").option("--json", "Output raw session export JSON").action(transcriptCommand);
919
- 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(watchCommand);
1299
+ var require3 = createRequire2(import.meta.url);
1300
+ var { version: version2 } = require3("../package.json");
1301
+ 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", `
1302
+ Examples:
1303
+ arcanist auth login --token-stdin
1304
+ arcanist sessions create https://github.com/org/repo "fix bug" --json | jq -r .sessionId
1305
+ printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
1306
+ arcanist sessions events <session-id> --follow --json
1307
+
1308
+ Exit codes:
1309
+ 0 ok, 1 user/input, 2 auth, 3 not found, 4 conflict, 10 server/network, 130 interrupted
1310
+ `);
1311
+ program.configureOutput({
1312
+ writeErr: (str) => process.stderr.write(str)
1313
+ });
1314
+ program.hook("preAction", (_thisCommand, actionCommand) => {
1315
+ applyColorEnvironment(getRuntimeOptions(actionCommand));
1316
+ });
1317
+ function addCreateOptions(cmd) {
1318
+ 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", `
1319
+ Examples:
1320
+ arcanist sessions create https://github.com/org/repo "fix bug"
1321
+ printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
1322
+ arcanist sessions create https://github.com/org/repo - --json | jq -r .sessionId | xargs -I{} arcanist sessions events {} --follow --json
1323
+ `);
1324
+ }
1325
+ function addSendOptions(cmd) {
1326
+ 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", `
1327
+ Examples:
1328
+ arcanist sessions send <session-id> "also update tests"
1329
+ printf "also update tests" | arcanist sessions send <session-id> --prompt-stdin --json
1330
+ `);
1331
+ }
1332
+ var auth = program.command("auth").description("Authentication commands");
1333
+ 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", `
1334
+ Examples:
1335
+ arcanist auth login
1336
+ printf "arc_..." | arcanist auth login --token-stdin
1337
+ ARCANIST_TOKEN=arc_... arcanist auth whoami --json
1338
+ `).action((options, command) => loginCommand(options, command));
1339
+ auth.command("whoami").description("Print the authenticated user and token scope").addHelpText("after", `
1340
+ Examples:
1341
+ arcanist auth whoami
1342
+ ARCANIST_TOKEN=arc_... arcanist auth whoami --json
1343
+ `).action((options, command) => whoamiCommand(options, command));
1344
+ var sessions = program.command("sessions").description("Session commands");
1345
+ addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action((repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command));
1346
+ addSendOptions(sessions.command("send").description("Send a message to an existing session")).action((sessionId, prompt, options, command) => messageCommand(sessionId, prompt, options, command));
1347
+ sessions.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").addHelpText("after", `
1348
+ Examples:
1349
+ arcanist sessions stop <session-id>
1350
+ arcanist sessions stop <session-id> --json
1351
+ `).action((sessionId, options, command) => stopCommand(sessionId, options, command));
1352
+ sessions.command("get").description("Get session details").argument("<session-id>", "Session ID").addHelpText("after", `
1353
+ Examples:
1354
+ arcanist sessions get <session-id>
1355
+ arcanist sessions get <session-id> --json
1356
+ `).action((sessionId, options, command) => getSessionCommand(sessionId, options, command));
1357
+ 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", `
1358
+ Examples:
1359
+ arcanist sessions list
1360
+ arcanist sessions list --status idle --json
1361
+ `).action((options, command) => listSessionsCommand(options, command));
1362
+ 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", `
1363
+ Examples:
1364
+ arcanist sessions events <session-id> --json
1365
+ arcanist sessions events <session-id> --follow --json
1366
+ `).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
1367
+ sessions.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").addHelpText("after", `
1368
+ Examples:
1369
+ arcanist sessions transcript <session-id>
1370
+ arcanist sessions transcript <session-id> --json
1371
+ `).action((sessionId, options, command) => transcriptCommand(sessionId, options, command));
1372
+ 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", `
1373
+ Examples:
1374
+ arcanist sessions watch <session-id>
1375
+ arcanist sessions watch <session-id> --json
1376
+ `).action((sessionId, options, command) => watchCommand(sessionId, options, command));
1377
+ sessions.command("usage").description("Get token usage for a session").argument("<session-id>", "Session ID").addHelpText("after", `
1378
+ Examples:
1379
+ arcanist sessions usage <session-id>
1380
+ arcanist sessions usage <session-id> --json
1381
+ `).action((sessionId, options, command) => usageCommand(sessionId, options, command));
1382
+ var tokens = program.command("tokens").description("CLI token commands");
1383
+ 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));
1384
+ 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", `
1385
+ Examples:
1386
+ arcanist tokens create --scope read
1387
+ arcanist tokens create --scope read --json
1388
+ `).action((options, command) => createTokenCommand(options, command));
1389
+ tokens.command("revoke").description("Revoke a CLI token").argument("<id>", "Token ID").option("--yes", "Confirm revocation without prompting").addHelpText("after", `
1390
+ Examples:
1391
+ arcanist tokens revoke 42
1392
+ arcanist tokens revoke 42 --yes --json
1393
+ `).action((id, options, command) => revokeTokenCommand(id, options, command));
1394
+ 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) => {
1395
+ printDeprecatedAlias("login", "auth login", command);
1396
+ return loginCommand(options, command);
1397
+ });
1398
+ addCreateOptions(program.command("create").description("Create a session and send a prompt")).action((repoUrl, prompt, options, command) => {
1399
+ printDeprecatedAlias("create", "sessions create", command);
1400
+ return createCommand(repoUrl, prompt, options, command);
1401
+ });
1402
+ addSendOptions(program.command("message").description("Send a message to an existing session")).action((sessionId, prompt, options, command) => {
1403
+ printDeprecatedAlias("message", "sessions send", command);
1404
+ return messageCommand(sessionId, prompt, options, command);
1405
+ });
1406
+ program.command("stop").description("Stop the active run for a session").argument("<session-id>", "Session ID").action((sessionId, options, command) => {
1407
+ printDeprecatedAlias("stop", "sessions stop", command);
1408
+ return stopCommand(sessionId, options, command);
1409
+ });
1410
+ program.command("transcript").description("Render a session transcript").argument("<session-id>", "Session ID").action((sessionId, options, command) => {
1411
+ printDeprecatedAlias("transcript", "sessions transcript", command);
1412
+ return transcriptCommand(sessionId, options, command);
1413
+ });
1414
+ 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) => {
1415
+ printDeprecatedAlias("watch", "sessions events --follow", command);
1416
+ return watchCommand(sessionId, options, command);
1417
+ });
920
1418
  async function main() {
921
1419
  try {
922
1420
  await program.parseAsync(process.argv);
923
1421
  } catch (err) {
924
- console.error(`Error: ${err instanceof Error ? err.message : "An unexpected error occurred."}`);
925
- process.exit(1);
1422
+ if (isCommanderHelpOrVersion(err)) {
1423
+ process.exit(0);
1424
+ }
1425
+ if (err instanceof CliError && err.exitCode === 130) restoreTerminal();
1426
+ const cliError = toCliError(err);
1427
+ const options = program.opts();
1428
+ if (options.json) {
1429
+ process.stderr.write(`${formatJsonError(cliError)}
1430
+ `);
1431
+ } else {
1432
+ process.stderr.write(`Error: ${cliError.message}
1433
+ `);
1434
+ if (cliError.hint) process.stderr.write(`Hint: ${cliError.hint}
1435
+ `);
1436
+ }
1437
+ process.exit(cliError.exitCode);
926
1438
  }
927
1439
  }
1440
+ function isCommanderHelpOrVersion(err) {
1441
+ if (!err || typeof err !== "object") return false;
1442
+ const code = err.code;
1443
+ return code === "commander.helpDisplayed" || code === "commander.version";
1444
+ }
1445
+ function restoreTerminal() {
1446
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
1447
+ process.stdout.write("\x1B[?25h");
1448
+ }
928
1449
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {