@quireco/cli 0.0.8 → 0.0.9

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.
@@ -1,2519 +0,0 @@
1
- import { createMain, defineCommand, runCommand, showUsage } from "citty";
2
- import { isCancel, log, note, spinner, text } from "@clack/prompts";
3
- import { loginGitHubCopilot, loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
4
- import { AuthStorage } from "@earendil-works/pi-coding-agent";
5
- import { execFile, spawn } from "node:child_process";
6
- import pc from "picocolors";
7
- import { homedir } from "node:os";
8
- import { basename, dirname, join, relative, resolve } from "node:path";
9
- import "@earendil-works/pi-ai";
10
- import "@earendil-works/pi-ai/openai-completions";
11
- import { access, constants, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
12
- import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
13
- import { Type } from "@sinclair/typebox";
14
- import { Value } from "@sinclair/typebox/value";
15
- import { promisify } from "node:util";
16
- import { createHash, randomBytes } from "node:crypto";
17
- import { cwd } from "process";
18
- //#region src/auth/open-browser.ts
19
- async function openBrowser(url) {
20
- const command = browserCommand(url);
21
- if (command === null) return false;
22
- return new Promise((resolve) => {
23
- const child = spawn(command.command, command.args, {
24
- detached: true,
25
- stdio: "ignore",
26
- shell: false
27
- });
28
- child.once("error", () => resolve(false));
29
- child.once("spawn", () => {
30
- child.unref();
31
- resolve(true);
32
- });
33
- });
34
- }
35
- function browserCommand(url) {
36
- if (process.platform === "darwin") return {
37
- command: "open",
38
- args: [url]
39
- };
40
- if (process.platform === "win32") return {
41
- command: "cmd",
42
- args: [
43
- "/c",
44
- "start",
45
- "",
46
- url
47
- ]
48
- };
49
- return {
50
- command: "xdg-open",
51
- args: [url]
52
- };
53
- }
54
- //#endregion
55
- //#region src/exit-codes.ts
56
- const ExitCode = {
57
- Success: 0,
58
- InternalError: 1,
59
- InvalidInput: 2,
60
- AuthFailure: 10,
61
- NetworkFailure: 11
62
- };
63
- var CliError = class extends Error {
64
- constructor(message, exitCode = ExitCode.InternalError) {
65
- super(message);
66
- this.exitCode = exitCode;
67
- this.name = "CliError";
68
- }
69
- };
70
- function readExitCode(error) {
71
- return error instanceof CliError ? error.exitCode : ExitCode.InternalError;
72
- }
73
- function toOneLineError(error) {
74
- return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim() || "unknown error";
75
- }
76
- //#endregion
77
- //#region src/output.ts
78
- function writeLine(output, message = "") {
79
- output?.write(`${message}\n`);
80
- }
81
- function writeJson(output, value) {
82
- writeLine(output, JSON.stringify(value));
83
- }
84
- const color = {
85
- heading(value) {
86
- return pc.bold(value);
87
- },
88
- label(value) {
89
- return pc.gray(value);
90
- },
91
- success(value) {
92
- return pc.green(value);
93
- },
94
- warn(value) {
95
- return pc.yellow(value);
96
- },
97
- error(value) {
98
- return pc.red(value);
99
- },
100
- info(value) {
101
- return pc.cyan(value);
102
- },
103
- path(value) {
104
- return pc.dim(value);
105
- },
106
- command(value) {
107
- return pc.cyan(value);
108
- },
109
- value(value) {
110
- return pc.bold(value);
111
- }
112
- };
113
- //#endregion
114
- //#region src/auth/constants.ts
115
- const QUIRE_API_TOKEN_ENV = "QUIRE_API_TOKEN";
116
- const QUIRE_API_TOKEN_TYPE = "QuireApiKey";
117
- //#endregion
118
- //#region src/utils/runtime.ts
119
- function isRecord(value) {
120
- return typeof value === "object" && value !== null && !Array.isArray(value);
121
- }
122
- function readString(value) {
123
- return typeof value === "string" && value.length > 0 ? value : void 0;
124
- }
125
- //#endregion
126
- //#region src/auth/api.ts
127
- const CLIENT_ID = "quire-cli";
128
- const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
129
- var HttpError = class extends CliError {
130
- constructor(message, status, body, code) {
131
- super(message, status === 401 || status === 403 ? ExitCode.AuthFailure : ExitCode.NetworkFailure);
132
- this.status = status;
133
- this.body = body;
134
- this.code = code;
135
- this.name = "HttpError";
136
- }
137
- };
138
- function readApiBaseUrl(value = process.env.QUIRE_API_URL ?? "http://localhost:3000") {
139
- return normalizeApiBaseUrl(value);
140
- }
141
- function normalizeApiBaseUrl(value) {
142
- try {
143
- const url = new URL(value);
144
- url.pathname = url.pathname.replace(/\/+$/, "");
145
- url.search = "";
146
- url.hash = "";
147
- return url.toString().replace(/\/$/, "");
148
- } catch {
149
- throw new CliError(`Invalid Quire API URL: ${value}`, ExitCode.InvalidInput);
150
- }
151
- }
152
- async function requestDeviceCode(options) {
153
- return parseDeviceCodeResponse((await requestJson({
154
- url: authUrl(options.apiBaseUrl, "/device/code"),
155
- fetchFn: options.fetchFn,
156
- init: {
157
- method: "POST",
158
- headers: jsonHeaders(),
159
- body: JSON.stringify({
160
- client_id: CLIENT_ID,
161
- ...options.scope === void 0 ? {} : { scope: options.scope }
162
- })
163
- }
164
- })).body);
165
- }
166
- async function requestDeviceToken(options) {
167
- return parseDeviceTokenResponse((await requestJson({
168
- url: authUrl(options.apiBaseUrl, "/device/token"),
169
- fetchFn: options.fetchFn,
170
- init: {
171
- method: "POST",
172
- headers: jsonHeaders(),
173
- body: JSON.stringify({
174
- grant_type: DEVICE_GRANT_TYPE,
175
- device_code: options.deviceCode,
176
- client_id: CLIENT_ID
177
- })
178
- }
179
- })).body);
180
- }
181
- async function fetchCurrentSession(options) {
182
- const response = await requestJson({
183
- url: authUrl(options.apiBaseUrl, "/get-session"),
184
- fetchFn: options.fetchFn,
185
- init: {
186
- method: "GET",
187
- headers: {
188
- Accept: "application/json",
189
- ...authHeaders(options)
190
- }
191
- }
192
- });
193
- const refreshedAccessToken = response.response.headers.get("set-auth-token") ?? void 0;
194
- return {
195
- data: parseCurrentSession(response.body),
196
- ...refreshedAccessToken === void 0 ? {} : { refreshedAccessToken }
197
- };
198
- }
199
- async function fetchModelSourceBrokerStatus(options) {
200
- return parseModelSourceBrokerStatus((await requestJson({
201
- url: apiUrl(options.apiBaseUrl, "/api/model-sources"),
202
- fetchFn: options.fetchFn,
203
- init: {
204
- method: "GET",
205
- headers: {
206
- Accept: "application/json",
207
- ...authHeaders(options),
208
- "User-Agent": "quire-cli"
209
- }
210
- }
211
- })).body);
212
- }
213
- function authUrl(apiBaseUrl, path) {
214
- return apiUrl(apiBaseUrl, `/api/auth${path}`);
215
- }
216
- function apiUrl(apiBaseUrl, path) {
217
- return new URL(path, apiBaseUrl).toString();
218
- }
219
- function jsonHeaders() {
220
- return {
221
- Accept: "application/json",
222
- "Content-Type": "application/json",
223
- "User-Agent": "quire-cli"
224
- };
225
- }
226
- function authHeaders(credentials) {
227
- if (credentials.tokenType === "QuireApiKey") return { "X-Quire-API-Key": credentials.accessToken };
228
- return { Authorization: `Bearer ${credentials.accessToken}` };
229
- }
230
- async function requestJson(options) {
231
- let response;
232
- try {
233
- response = await (options.fetchFn ?? fetch)(options.url, options.init);
234
- } catch (error) {
235
- throw new CliError(`Could not reach Quire API: ${error instanceof Error ? error.message : String(error)}`, ExitCode.NetworkFailure);
236
- }
237
- const body = await readResponseBody(response);
238
- if (!response.ok) {
239
- const { code, description } = readErrorBody(body);
240
- throw new HttpError(description ?? `Quire API request failed with HTTP ${response.status}`, response.status, body, code);
241
- }
242
- return {
243
- body,
244
- response
245
- };
246
- }
247
- async function readResponseBody(response) {
248
- const text = await response.text();
249
- if (!text) return null;
250
- try {
251
- return JSON.parse(text);
252
- } catch {
253
- return text;
254
- }
255
- }
256
- function readErrorBody(body) {
257
- if (!isRecord(body)) return {};
258
- return {
259
- code: typeof body.error === "string" ? body.error : void 0,
260
- description: typeof body.error_description === "string" ? body.error_description : typeof body.message === "string" ? body.message : void 0
261
- };
262
- }
263
- function parseDeviceCodeResponse(value) {
264
- if (!isRecord(value)) throw new CliError("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
265
- const response = {
266
- device_code: value.device_code,
267
- user_code: value.user_code,
268
- verification_uri: value.verification_uri,
269
- verification_uri_complete: value.verification_uri_complete,
270
- expires_in: value.expires_in,
271
- interval: value.interval
272
- };
273
- if (typeof response.device_code !== "string" || typeof response.user_code !== "string" || typeof response.verification_uri !== "string" || typeof response.verification_uri_complete !== "string" || typeof response.expires_in !== "number" || typeof response.interval !== "number") throw new CliError("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
274
- return response;
275
- }
276
- function parseDeviceTokenResponse(value) {
277
- if (!isRecord(value) || typeof value.access_token !== "string") throw new CliError("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
278
- if (value.expires_in !== void 0 && typeof value.expires_in !== "number") throw new CliError("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
279
- if (value.scope !== void 0 && typeof value.scope !== "string") throw new CliError("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
280
- return {
281
- access_token: value.access_token,
282
- token_type: typeof value.token_type === "string" ? value.token_type : "Bearer",
283
- ...value.expires_in === void 0 ? {} : { expires_in: value.expires_in },
284
- ...value.scope === void 0 ? {} : { scope: value.scope }
285
- };
286
- }
287
- function parseCurrentSession(value) {
288
- if (value === null) return null;
289
- if (!isRecord(value) || !isRecord(value.session) || !isRecord(value.user)) throw new CliError("Quire API returned an invalid session response", ExitCode.NetworkFailure);
290
- if (typeof value.session.id !== "string" || typeof value.user.id !== "string") throw new CliError("Quire API returned an invalid session response", ExitCode.NetworkFailure);
291
- if (value.user.email !== void 0 && value.user.email !== null && typeof value.user.email !== "string") throw new CliError("Quire API returned an invalid session response", ExitCode.NetworkFailure);
292
- if (value.user.name !== void 0 && value.user.name !== null && typeof value.user.name !== "string") throw new CliError("Quire API returned an invalid session response", ExitCode.NetworkFailure);
293
- return {
294
- session: {
295
- id: value.session.id,
296
- ...typeof value.session.expiresAt === "string" ? { expiresAt: value.session.expiresAt } : {}
297
- },
298
- user: {
299
- id: value.user.id,
300
- ...value.user.email === void 0 ? {} : { email: value.user.email },
301
- ...value.user.name === void 0 ? {} : { name: value.user.name }
302
- }
303
- };
304
- }
305
- function parseModelSourceBrokerStatus(value) {
306
- if (!isRecord(value) || !Array.isArray(value.selectedOrder)) throw new CliError("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
307
- const resolvedAvailability = value.resolvedAvailability;
308
- if (!isRecord(resolvedAvailability)) throw new CliError("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
309
- const status = resolvedAvailability.status;
310
- const reason = resolvedAvailability.reason;
311
- const selectedProviderMode = resolvedAvailability.mode;
312
- const requiredNextAction = resolvedAvailability.nextAction;
313
- if (status !== "available" && status !== "unavailable" || typeof reason !== "string" || selectedProviderMode !== null && typeof selectedProviderMode !== "string" || requiredNextAction !== null && typeof requiredNextAction !== "string" || !value.selectedOrder.every((mode) => typeof mode === "string")) throw new CliError("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
314
- const actor = readActor(value);
315
- return {
316
- ...actor === void 0 ? {} : { actor },
317
- available: status === "available",
318
- status,
319
- selectedProviderMode,
320
- selectedOrder: value.selectedOrder,
321
- reason,
322
- requiredNextAction,
323
- requiredBalance: readWalletRequiredBalance(value),
324
- remainingBalance: readWalletRemainingBalance(value),
325
- recentUsage: readRecentUsage(value)
326
- };
327
- }
328
- function readActor(value) {
329
- const actor = value.actor;
330
- if (!isRecord(actor)) return;
331
- const actorType = actor.actorType;
332
- if (actorType !== "user_session" && actorType !== "api_key") return;
333
- return {
334
- actorType,
335
- actorId: typeof actor.actorId === "string" ? actor.actorId : null,
336
- environmentId: typeof actor.environmentId === "string" ? actor.environmentId : null,
337
- environmentName: typeof actor.environmentName === "string" ? actor.environmentName : null,
338
- triggerSource: typeof actor.triggerSource === "string" ? actor.triggerSource : null
339
- };
340
- }
341
- function readWalletRequiredBalance(value) {
342
- const source = readFirstSource(value);
343
- const requiredBalance = (isRecord(source?.wallet) ? source.wallet : null)?.requiredBalance;
344
- return typeof requiredBalance === "number" && Number.isFinite(requiredBalance) ? requiredBalance : null;
345
- }
346
- function readWalletRemainingBalance(value) {
347
- const source = readFirstSource(value);
348
- const wallet = isRecord(source?.wallet) ? source.wallet : null;
349
- const remaining = (isRecord(wallet?.balance) ? wallet.balance : null)?.remaining;
350
- return typeof remaining === "number" && Number.isFinite(remaining) ? remaining : null;
351
- }
352
- function readFirstSource(value) {
353
- if (!Array.isArray(value.sources)) return null;
354
- const [source] = value.sources;
355
- return isRecord(source) ? source : null;
356
- }
357
- function readRecentUsage(value) {
358
- if (!Array.isArray(value.recentUsage)) return [];
359
- return value.recentUsage.flatMap((event) => {
360
- if (!isRecord(event)) return [];
361
- if (typeof event.modelId !== "string" || typeof event.status !== "string" || typeof event.totalTokens !== "number" || typeof event.createdAt !== "string") return [];
362
- return [{
363
- modelId: event.modelId,
364
- status: event.status,
365
- totalTokens: event.totalTokens,
366
- runId: typeof event.runId === "string" ? event.runId : null,
367
- createdAt: event.createdAt
368
- }];
369
- });
370
- }
371
- //#endregion
372
- //#region src/auth/store.ts
373
- function defaultAuthPath(env = process.env) {
374
- if (env.QUIRE_AUTH_FILE) return resolve(env.QUIRE_AUTH_FILE);
375
- return join(resolve(env.QUIRE_HOME ?? join(homedir(), ".quire")), "auth.json");
376
- }
377
- function createAuthStore(path = defaultAuthPath()) {
378
- async function get() {
379
- try {
380
- const contents = await readFile(path, "utf8");
381
- return parseStoredCredentials(JSON.parse(contents));
382
- } catch {
383
- return null;
384
- }
385
- }
386
- async function set(credentials) {
387
- await mkdir(dirname(path), {
388
- recursive: true,
389
- mode: 448
390
- });
391
- await writeFile(path, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 384 });
392
- }
393
- async function clear() {
394
- try {
395
- await unlink(path);
396
- } catch {}
397
- }
398
- return {
399
- path,
400
- get,
401
- set,
402
- clear
403
- };
404
- }
405
- const authStore = createAuthStore();
406
- async function resolveAuthCredentials(options = {}) {
407
- const env = options.env ?? process.env;
408
- const apiToken = env[QUIRE_API_TOKEN_ENV]?.trim();
409
- if (apiToken) return createStoredCredentials({
410
- apiBaseUrl: readApiBaseUrl(env.QUIRE_API_URL),
411
- accessToken: apiToken,
412
- tokenType: QUIRE_API_TOKEN_TYPE,
413
- user: {
414
- id: "api-token",
415
- name: "API token"
416
- }
417
- });
418
- return (options.store ?? authStore).get();
419
- }
420
- function createStoredCredentials(input) {
421
- const now = input.now ?? /* @__PURE__ */ new Date();
422
- const timestamp = now.toISOString();
423
- const expiresAt = input.expiresAt ?? expiresAtFromSeconds(input.expiresIn, now);
424
- return {
425
- version: 1,
426
- apiBaseUrl: input.apiBaseUrl,
427
- accessToken: input.accessToken,
428
- tokenType: input.tokenType ?? "Bearer",
429
- ...expiresAt === void 0 ? {} : { expiresAt },
430
- ...input.user === void 0 ? {} : { user: input.user },
431
- createdAt: timestamp,
432
- updatedAt: timestamp
433
- };
434
- }
435
- function updateStoredCredentials(credentials, input) {
436
- return {
437
- ...credentials,
438
- accessToken: input.accessToken ?? credentials.accessToken,
439
- tokenType: input.tokenType ?? credentials.tokenType,
440
- ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt },
441
- ...input.user === void 0 ? {} : { user: input.user },
442
- updatedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
443
- };
444
- }
445
- function expiresAtFromSeconds(expiresIn, now) {
446
- return typeof expiresIn === "number" ? new Date(now.getTime() + expiresIn * 1e3).toISOString() : void 0;
447
- }
448
- function parseStoredCredentials(value) {
449
- if (!isRecord(value)) return null;
450
- if (value.version !== 1 || typeof value.apiBaseUrl !== "string" || typeof value.accessToken !== "string" || typeof value.tokenType !== "string" || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") return null;
451
- if (value.expiresAt !== void 0 && typeof value.expiresAt !== "string") return null;
452
- const user = parseStoredUser(value.user);
453
- if (value.user !== void 0 && user === null) return null;
454
- return {
455
- version: 1,
456
- apiBaseUrl: value.apiBaseUrl,
457
- accessToken: value.accessToken,
458
- tokenType: value.tokenType,
459
- ...value.expiresAt === void 0 ? {} : { expiresAt: value.expiresAt },
460
- ...user === void 0 || user === null ? {} : { user },
461
- createdAt: value.createdAt,
462
- updatedAt: value.updatedAt
463
- };
464
- }
465
- function parseStoredUser(value) {
466
- if (value === void 0) return;
467
- if (!isRecord(value) || typeof value.id !== "string") return null;
468
- if (value.email !== void 0 && value.email !== null && typeof value.email !== "string") return null;
469
- if (value.name !== void 0 && value.name !== null && typeof value.name !== "string") return null;
470
- return {
471
- id: value.id,
472
- ...value.email === void 0 ? {} : { email: value.email },
473
- ...value.name === void 0 ? {} : { name: value.name }
474
- };
475
- }
476
- //#endregion
477
- //#region src/pipeline/quire-model-provider.ts
478
- const PI_CODEX_MODEL_PROVIDER = "openai-codex";
479
- const PI_GITHUB_COPILOT_MODEL_PROVIDER = "github-copilot";
480
- const PI_GITHUB_COPILOT_MODEL_ID = "gpt-5.5";
481
- const QUIRE_MODEL_AUTH_PATH_ENV = "QUIRE_MODEL_AUTH_PATH";
482
- function readQuireModelAuthPath() {
483
- const envPath = process.env[QUIRE_MODEL_AUTH_PATH_ENV]?.trim();
484
- return envPath && envPath.length > 0 ? envPath : join(homedir(), ".quire", "model-auth.json");
485
- }
486
- //#endregion
487
- //#region src/commands/connect.ts
488
- const CHATGPT_MANUAL_CODE_PROMPT = "Paste the localhost redirect URL from your browser";
489
- const CHATGPT_MANUAL_CODE_PLACEHOLDER = "http://localhost:1455/auth/callback?code=...";
490
- const connectCommand = defineCommand({
491
- meta: {
492
- name: "connect",
493
- description: "Connect local provider accounts used by Quire investigations."
494
- },
495
- subCommands: {
496
- chatgpt: defineCommand({
497
- meta: {
498
- name: "chatgpt",
499
- description: "Connect ChatGPT/Codex subscription auth for local investigations."
500
- },
501
- args: {
502
- "no-open": {
503
- type: "boolean",
504
- description: "Print the authorization URL without trying to open a browser."
505
- },
506
- json: {
507
- type: "boolean",
508
- description: "Print machine-readable connection state to stdout."
509
- }
510
- },
511
- async run({ args }) {
512
- await runConnectChatgpt({
513
- noOpen: args["no-open"] === true,
514
- json: args.json === true
515
- });
516
- }
517
- }),
518
- copilot: defineCommand({
519
- meta: {
520
- name: "copilot",
521
- alias: "github",
522
- description: "Connect GitHub Copilot subscription auth for local investigations."
523
- },
524
- args: {
525
- "github-enterprise": {
526
- type: "string",
527
- description: "GitHub Enterprise URL/domain for Copilot login. Omit or pass an empty value for github.com."
528
- },
529
- "no-open": {
530
- type: "boolean",
531
- description: "Print the device authorization URL without trying to open a browser."
532
- },
533
- json: {
534
- type: "boolean",
535
- description: "Print machine-readable connection state to stdout."
536
- }
537
- },
538
- async run({ args }) {
539
- await runConnectCopilot({
540
- githubEnterprise: args["github-enterprise"],
541
- noOpen: args["no-open"] === true,
542
- json: args.json === true
543
- });
544
- }
545
- })
546
- }
547
- });
548
- async function runConnectChatgpt(options = {}) {
549
- const stdout = options.io?.stdout ?? process.stdout;
550
- const stderr = options.io?.stderr ?? process.stderr;
551
- const modelAuthPath = options.modelAuthPath ?? readQuireModelAuthPath();
552
- const modelAuthStorage = options.modelAuthStorage ?? AuthStorage.create(modelAuthPath);
553
- const loginChatgpt = options.loginChatgpt ?? loginOpenAICodex;
554
- const promptOutput = options.json === true ? stderr : stdout;
555
- const promptForInput = options.promptForLine ?? ((message, promptOptions) => promptForLine(message, promptOutput, promptOptions));
556
- let launchAttempt;
557
- if (options.json !== true) {
558
- log.step("Connect ChatGPT for local Quire investigations", {
559
- output: stdout,
560
- spacing: 0
561
- });
562
- log.message("Quire will use local Codex subscription auth before Quire Credits.", {
563
- output: stdout,
564
- spacing: 0,
565
- withGuide: true
566
- });
567
- }
568
- const credentials = await loginChatgpt({
569
- originator: "quire",
570
- onAuth: (info) => {
571
- const authOutput = options.json === true ? stderr : stdout;
572
- if (options.json === true) {
573
- writeLine(authOutput, `${color.label("Authorization URL")}: ${info.url}`);
574
- if (info.instructions) writeLine(authOutput, info.instructions);
575
- } else writeChatgptAuthorizationNote(authOutput, info.url, info.instructions);
576
- if (options.noOpen === true) return;
577
- launchAttempt = (async () => {
578
- let opened = false;
579
- try {
580
- opened = await (options.opener ?? openBrowser)(info.url);
581
- } catch {
582
- opened = false;
583
- }
584
- if (opened) log.success("Opened ChatGPT authorization in your browser.", {
585
- output: stderr,
586
- spacing: 0
587
- });
588
- else log.warn("Could not open a browser automatically. Open the authorization URL manually.", {
589
- output: stderr,
590
- spacing: 0
591
- });
592
- })();
593
- },
594
- onPrompt: async (prompt) => {
595
- return promptForInput(prompt.placeholder ? `${prompt.message} (${prompt.placeholder})` : prompt.message);
596
- },
597
- onManualCodeInput: async () => {
598
- await launchAttempt;
599
- if (options.json !== true) log.message([
600
- "Complete ChatGPT authorization in the browser.",
601
- "When it redirects to localhost, the page may fail to load in a VM.",
602
- "Copy the full address-bar URL and paste it below."
603
- ], {
604
- output: promptOutput,
605
- spacing: 0,
606
- withGuide: true
607
- });
608
- return promptForInput(CHATGPT_MANUAL_CODE_PROMPT, { placeholder: CHATGPT_MANUAL_CODE_PLACEHOLDER });
609
- },
610
- onProgress: (message) => {
611
- writeLine(stderr, message);
612
- }
613
- });
614
- modelAuthStorage.set(PI_CODEX_MODEL_PROVIDER, oauthCredential(credentials));
615
- if (options.json === true) {
616
- writeJson(stdout, {
617
- connected: true,
618
- provider: PI_CODEX_MODEL_PROVIDER,
619
- authPath: modelAuthPath,
620
- source: "quire_model_auth"
621
- });
622
- return;
623
- }
624
- log.success("ChatGPT connected for local Quire investigations.", { output: stdout });
625
- }
626
- function writeChatgptAuthorizationNote(output, url, instructions) {
627
- const lines = [
628
- "If you're in a VM or remote shell, browser launch may not work.",
629
- "Open the sign-in URL below in your local browser.",
630
- "After signing in, ChatGPT will redirect to localhost.",
631
- "Copy that final localhost URL back into this terminal."
632
- ];
633
- if (instructions) lines.push("", instructions);
634
- note(lines.join("\n"), "ChatGPT authorization", { output });
635
- writeLine(output);
636
- writeLine(output, `${color.label("Open")}: ${terminalLink("ChatGPT authorization", url, output)}`);
637
- writeLine(output, color.label("Sign-in URL to paste into your browser:"));
638
- writeLine(output, url);
639
- writeLine(output);
640
- }
641
- function terminalLink(label, url, output) {
642
- if (!isInteractiveOutput(output)) return label;
643
- return `\u001B]8;;${url}\u001B\\${label}\u001B]8;;\u001B\\`;
644
- }
645
- function isInteractiveOutput(output) {
646
- return Boolean(output && "isTTY" in output && output.isTTY === true && process.env.TERM !== "dumb" && process.env.CI !== "true");
647
- }
648
- async function runConnectCopilot(options = {}) {
649
- const stdout = options.io?.stdout ?? process.stdout;
650
- const stderr = options.io?.stderr ?? process.stderr;
651
- const modelAuthPath = options.modelAuthPath ?? readQuireModelAuthPath();
652
- const modelAuthStorage = options.modelAuthStorage ?? AuthStorage.create(modelAuthPath);
653
- const loginCopilot = options.loginCopilot ?? loginGitHubCopilot;
654
- const promptOutput = options.json === true ? stderr : stdout;
655
- const promptForInput = options.promptForLine ?? ((message) => promptForLine(message, promptOutput));
656
- if (options.json !== true) {
657
- log.step("Connect GitHub Copilot for local Quire investigations", {
658
- output: stdout,
659
- spacing: 0
660
- });
661
- log.message(`Quire will use local GitHub Copilot auth with ${PI_GITHUB_COPILOT_MODEL_ID} before Quire Credits.`, {
662
- output: stdout,
663
- spacing: 0,
664
- withGuide: true
665
- });
666
- }
667
- let usedEnterpriseFlag = false;
668
- const credentials = await loginCopilot({
669
- onAuth: (url, instructions) => {
670
- const authOutput = options.json === true ? stderr : stdout;
671
- writeLine(authOutput, `${color.label("Authorization URL")}: ${url}`);
672
- if (instructions) writeLine(authOutput, instructions);
673
- if (options.noOpen === true) return;
674
- (async () => {
675
- let opened = false;
676
- try {
677
- opened = await (options.opener ?? openBrowser)(url);
678
- } catch {
679
- opened = false;
680
- }
681
- if (opened) log.success("Opened GitHub Copilot authorization in your browser.", {
682
- output: stderr,
683
- spacing: 0
684
- });
685
- else log.warn("Could not open a browser automatically. Open the authorization URL manually.", {
686
- output: stderr,
687
- spacing: 0
688
- });
689
- })();
690
- },
691
- onPrompt: async (prompt) => {
692
- if (options.githubEnterprise !== void 0 && !usedEnterpriseFlag && isGithubEnterprisePrompt(prompt)) {
693
- usedEnterpriseFlag = true;
694
- return options.githubEnterprise;
695
- }
696
- return promptForInput(prompt.placeholder ? `${prompt.message} (${prompt.placeholder})` : prompt.message);
697
- },
698
- onProgress: (message) => {
699
- writeLine(stderr, message);
700
- }
701
- });
702
- modelAuthStorage.set(PI_GITHUB_COPILOT_MODEL_PROVIDER, oauthCredential(credentials));
703
- if (options.json === true) {
704
- writeJson(stdout, {
705
- connected: true,
706
- provider: PI_GITHUB_COPILOT_MODEL_PROVIDER,
707
- model: PI_GITHUB_COPILOT_MODEL_ID,
708
- authPath: modelAuthPath,
709
- source: "quire_model_auth"
710
- });
711
- return;
712
- }
713
- log.success("GitHub Copilot connected for local Quire investigations.", { output: stdout });
714
- }
715
- function oauthCredential(credentials) {
716
- return {
717
- type: "oauth",
718
- ...credentials
719
- };
720
- }
721
- function isGithubEnterprisePrompt(prompt) {
722
- return /github enterprise/i.test(prompt.message);
723
- }
724
- async function promptForLine(message, output = process.stdout, options = {}) {
725
- const value = await text({
726
- message,
727
- placeholder: options.placeholder,
728
- input: process.stdin,
729
- output
730
- });
731
- if (isCancel(value)) throw new CliError("Authorization input was cancelled.", ExitCode.InvalidInput);
732
- return value;
733
- }
734
- //#endregion
735
- //#region src/commands/continue.ts
736
- const continueCommand = defineCommand({
737
- meta: {
738
- name: "continue",
739
- alias: "resume",
740
- description: "Continue a previous Quire investigation with more context."
741
- },
742
- args: {
743
- run: {
744
- type: "positional",
745
- description: "Run id or run directory to continue.",
746
- required: true
747
- },
748
- prompt: {
749
- type: "positional",
750
- description: "Additional context or instruction for the continued run.",
751
- required: false
752
- },
753
- stdin: {
754
- type: "boolean",
755
- description: "Reserved for the rebuilt continuation command."
756
- },
757
- json: {
758
- type: "boolean",
759
- description: "Reserved for the rebuilt continuation command."
760
- },
761
- watch: {
762
- type: "boolean",
763
- description: "Reserved for the rebuilt continuation command."
764
- },
765
- detach: {
766
- type: "boolean",
767
- description: "Reserved for the rebuilt continuation command."
768
- },
769
- headed: {
770
- type: "boolean",
771
- description: "Reserved for the rebuilt continuation command."
772
- }
773
- },
774
- async run({ args }) {
775
- await runContinue({
776
- run: readRequiredString$1(args.run, "run"),
777
- prompt: readOptionalString$2(args.prompt),
778
- stdin: args.stdin === true,
779
- json: args.json === true,
780
- watch: args.watch === true,
781
- detach: args.detach === true,
782
- headed: args.headed === true
783
- });
784
- }
785
- });
786
- async function runContinue(_options) {
787
- throw new CliError("Investigation continuation is intentionally a clean-slate learning stub on this branch. Rebuild it after the run protocol and investigate command.", ExitCode.InternalError);
788
- }
789
- function readRequiredString$1(value, name) {
790
- if (typeof value === "string" && value.length > 0) return value;
791
- throw new CliError(`Missing required ${name}.`, ExitCode.InvalidInput);
792
- }
793
- function readOptionalString$2(value) {
794
- return typeof value === "string" && value.length > 0 ? value : void 0;
795
- }
796
- //#endregion
797
- //#region src/doctor/render.ts
798
- const SECTION_TITLES = {
799
- identity: "Identity",
800
- workspace: "Workspace",
801
- runs: "Runs"
802
- };
803
- const SECTION_ORDER = [
804
- "identity",
805
- "workspace",
806
- "runs"
807
- ];
808
- const GLYPHS = {
809
- pass: color.success("✓"),
810
- warn: color.warn("!"),
811
- fail: color.error("✗")
812
- };
813
- function renderDoctorReport(output, report, strict = false) {
814
- writeLine(output, color.heading("Quire doctor"));
815
- writeLine(output);
816
- for (const section of SECTION_ORDER) {
817
- const checks = report.checks.filter((check) => check.section === section);
818
- if (checks.length === 0) continue;
819
- writeLine(output, color.heading(SECTION_TITLES[section]));
820
- for (const check of checks) {
821
- writeLine(output, ` ${GLYPHS[check.severity]} ${check.message}`);
822
- if (check.fix !== void 0) writeLine(output, ` ${color.label("→")} ${color.command(check.fix.command)}`);
823
- }
824
- writeLine(output);
825
- }
826
- writeLine(output, formatSummary(report, strict));
827
- }
828
- function formatSummary(report, strict) {
829
- const { warn, fail } = report.summary;
830
- if (fail === 0 && warn === 0) return "All checks passed.";
831
- const parts = [];
832
- if (fail > 0) parts.push(`${fail} ${pluralize(fail, "failure", "failures")}`);
833
- if (warn > 0) parts.push(`${warn} ${pluralize(warn, "warning", "warnings")}`);
834
- const sentence = `${parts.join(", ")}.`;
835
- if (fail === 0 && warn > 0 && !strict) return `${sentence} Run with --strict to fail on warnings.`;
836
- return sentence;
837
- }
838
- function pluralize(count, singular, plural) {
839
- return count === 1 ? singular : plural;
840
- }
841
- //#endregion
842
- //#region src/schema/environment.ts
843
- const VerificationCommandSchema = Type.Object({
844
- command: Type.String({ minLength: 1 }),
845
- purpose: Type.Optional(Type.String({ minLength: 1 }))
846
- }, { additionalProperties: false });
847
- const WebTargetSchema = Type.Object({
848
- kind: Type.Literal("web"),
849
- url: Type.String({ minLength: 1 }),
850
- description: Type.Optional(Type.String({ minLength: 1 })),
851
- entrypoint: Type.Optional(Type.String({ minLength: 1 })),
852
- readOnly: Type.Optional(Type.Boolean()),
853
- codePaths: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
854
- verify: Type.Optional(Type.Array(VerificationCommandSchema))
855
- }, { additionalProperties: false });
856
- const EnvironmentFileSchema = Type.Object({
857
- version: Type.Literal(2),
858
- defaultTarget: Type.Optional(Type.String({ minLength: 1 })),
859
- targets: Type.Record(Type.String({ minLength: 1 }), WebTargetSchema)
860
- }, { additionalProperties: false });
861
- function environmentFilePath(repoPath) {
862
- return join(repoPath, ".quire", "environment.json");
863
- }
864
- function sanitizeTargetName(value) {
865
- const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
866
- if (sanitized.length === 0 || sanitized === "." || sanitized === "..") throw new EnvironmentError("schema_violation", "Invalid environment name", {
867
- fieldPath: "/target",
868
- filePath: ""
869
- });
870
- return sanitized;
871
- }
872
- var EnvironmentError = class extends Error {
873
- code;
874
- fieldPath;
875
- filePath;
876
- constructor(code, message, options) {
877
- super(`${message} at ${options.fieldPath}`, { cause: options.cause });
878
- this.name = "EnvironmentError";
879
- this.code = code;
880
- this.fieldPath = options.fieldPath;
881
- this.filePath = options.filePath;
882
- }
883
- };
884
- function loadEnvironment(repoPath, targetName) {
885
- const filePath = environmentFilePath(repoPath);
886
- if (!existsSync(filePath)) throw new EnvironmentError("missing_file", "Missing .quire/environment.json", {
887
- fieldPath: "/",
888
- filePath
889
- });
890
- let parsed;
891
- try {
892
- parsed = JSON.parse(readFileSync(filePath, "utf8"));
893
- } catch (error) {
894
- throw new EnvironmentError("malformed_json", "Malformed environment JSON", {
895
- fieldPath: "/",
896
- filePath,
897
- cause: error
898
- });
899
- }
900
- if (!Value.Check(EnvironmentFileSchema, parsed)) {
901
- const firstError = Value.Errors(EnvironmentFileSchema, parsed).First();
902
- const fieldPath = normalizeFieldPath(firstError?.path);
903
- throw new EnvironmentError("schema_violation", firstError?.message ?? "Environment schema validation failed", {
904
- fieldPath,
905
- filePath
906
- });
907
- }
908
- return resolveEnvironmentTarget(parsed, targetName, filePath);
909
- }
910
- function isMobileEnvironment(env) {
911
- return env.app.kind === "mobile";
912
- }
913
- function isWebEnvironment(env) {
914
- return env.app.kind === "web";
915
- }
916
- function environmentBaseUrl(env) {
917
- return isWebEnvironment(env) ? env.app.url : void 0;
918
- }
919
- function resolveEnvironmentTarget(env, requestedTarget, filePath) {
920
- const targetName = requestedTarget ?? env.defaultTarget ?? Object.keys(env.targets)[0];
921
- if (targetName === void 0) throw new EnvironmentError("schema_violation", "Environment must define at least one target", {
922
- fieldPath: "/targets",
923
- filePath
924
- });
925
- const sanitizedTargetName = sanitizeTargetName(targetName);
926
- const target = env.targets[sanitizedTargetName] ?? env.targets[targetName];
927
- if (target === void 0) throw new EnvironmentError("schema_violation", `Unknown target ${targetName}`, {
928
- fieldPath: `/targets/${sanitizedTargetName}`,
929
- filePath
930
- });
931
- return {
932
- version: 2,
933
- targetName: sanitizedTargetName,
934
- app: {
935
- ...target,
936
- baseUrl: target.url
937
- }
938
- };
939
- }
940
- function normalizeFieldPath(path) {
941
- if (path === void 0 || path === "") return "/";
942
- return path;
943
- }
944
- //#endregion
945
- //#region src/run-dir.ts
946
- function createRunDir(repoPath, options = {}) {
947
- const workspaceKey = workspaceKeyForRepoPath(resolve(repoPath));
948
- const runsRoot = resolve(options.runsRoot ?? defaultRunsRoot());
949
- const runId = options.runId ?? formatRunId(options.now ?? /* @__PURE__ */ new Date());
950
- const root = join(runsRoot, workspaceKey, runId);
951
- const paths = runDirPaths(root);
952
- mkdirSync(paths.evidence, {
953
- recursive: true,
954
- mode: 448
955
- });
956
- return {
957
- runId,
958
- workspaceKey,
959
- runsRoot,
960
- root,
961
- paths
962
- };
963
- }
964
- function runStatusPath(runPath) {
965
- return join(resolve(runPath), "status.json");
966
- }
967
- function runProgressPath(runPath) {
968
- return join(resolve(runPath), "progress.jsonl");
969
- }
970
- function runAgentSessionPath(runPath) {
971
- return join(resolve(runPath), "agent-session.jsonl");
972
- }
973
- function runHandoffPath(runPath) {
974
- return join(resolve(runPath), "handoff.md");
975
- }
976
- function runDirPaths(root) {
977
- const evidence = join(root, "evidence");
978
- return {
979
- status: runStatusPath(root),
980
- progress: runProgressPath(root),
981
- agentSession: runAgentSessionPath(root),
982
- handoff: runHandoffPath(root),
983
- evidence,
984
- screenshots: join(evidence, "screenshots"),
985
- video: join(evidence, "video"),
986
- trace: join(evidence, "trace"),
987
- console: join(evidence, "console"),
988
- network: join(evidence, "network"),
989
- harnessSessions: join(root, ".harness-sessions")
990
- };
991
- }
992
- function defaultRunsRoot(env = process.env) {
993
- if (env.QUIRE_RUNS_DIR !== void 0 && env.QUIRE_RUNS_DIR.length > 0) return resolve(env.QUIRE_RUNS_DIR);
994
- return join(resolve(env.QUIRE_HOME ?? join(homedir(), ".quire")), "runs");
995
- }
996
- function workspaceKeyForRepoPath(repoPath) {
997
- const resolvedRepoPath = resolve(repoPath);
998
- return `${sanitizeWorkspaceName(basename(resolvedRepoPath) ?? "workspace")}-${createHash("sha256").update(resolvedRepoPath, "utf8").digest("hex").slice(0, 10)}`;
999
- }
1000
- function formatRunId(date) {
1001
- return [
1002
- date.getFullYear().toString().padStart(4, "0"),
1003
- (date.getMonth() + 1).toString().padStart(2, "0"),
1004
- date.getDate().toString().padStart(2, "0"),
1005
- "-",
1006
- date.getHours().toString().padStart(2, "0"),
1007
- date.getMinutes().toString().padStart(2, "0"),
1008
- date.getSeconds().toString().padStart(2, "0"),
1009
- "-",
1010
- date.getMilliseconds().toString().padStart(3, "0"),
1011
- "-",
1012
- randomBytes(3).toString("hex")
1013
- ].join("");
1014
- }
1015
- function sanitizeWorkspaceName(value) {
1016
- return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") ?? "workspace";
1017
- }
1018
- //#endregion
1019
- //#region src/run-status.ts
1020
- function updateRunStatus(runPath, patch) {
1021
- const existing = readRunStatus(runPath);
1022
- if (existing === void 0) throw new CliError(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
1023
- const timestamp = (patch.now ?? /* @__PURE__ */ new Date()).toISOString();
1024
- const nextStatus = patch.status ?? existing.status;
1025
- const status = {
1026
- ...existing,
1027
- ...definedPatch(patch),
1028
- status: nextStatus,
1029
- updatedAt: timestamp,
1030
- ...nextStatus === "running" && existing.startedAt === void 0 ? { startedAt: timestamp } : {},
1031
- ...isTerminalRunStatus(nextStatus) && existing.completedAt === void 0 ? { completedAt: timestamp } : {}
1032
- };
1033
- writeStatus(runStatusPath(runPath), status);
1034
- return status;
1035
- }
1036
- function readRunStatus(runPath) {
1037
- const filePath = runStatusPath(runPath);
1038
- if (!existsSync(filePath)) return;
1039
- try {
1040
- return parseRunStatus(JSON.parse(readFileSync(filePath, "utf8")));
1041
- } catch {
1042
- return;
1043
- }
1044
- }
1045
- function readFreshRunStatus(runPath) {
1046
- const status = readRequiredRunStatus(runPath);
1047
- if (status.status === "queued" && !isPidAlive(status.pid)) {
1048
- if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1049
- return updateRunStatus(runPath, {
1050
- status: "stale",
1051
- error: "Worker process exited before marking the run as running."
1052
- });
1053
- }
1054
- if ((status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid)) {
1055
- if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1056
- return updateRunStatus(runPath, {
1057
- status: status.status === "canceling" ? "canceled" : "stale",
1058
- error: status.status === "canceling" ? void 0 : "Worker process is no longer running."
1059
- });
1060
- }
1061
- return status;
1062
- }
1063
- function readRequiredRunStatus(runPath) {
1064
- const status = readRunStatus(runPath);
1065
- if (status === void 0) throw new CliError(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
1066
- return status;
1067
- }
1068
- function listRunStatuses(options = {}) {
1069
- const runsRoot = resolve(options.runsRoot ?? defaultRunsRoot());
1070
- if (!existsSync(runsRoot)) return [];
1071
- return readdirSync(runsRoot, { withFileTypes: true }).filter((workspaceEntry) => workspaceEntry.isDirectory()).flatMap((workspaceEntry) => {
1072
- const workspacePath = join(runsRoot, workspaceEntry.name);
1073
- return readdirSync(workspacePath, { withFileTypes: true }).filter((runEntry) => runEntry.isDirectory()).map((runEntry) => readRunStatus(join(workspacePath, runEntry.name))).filter((status) => status !== void 0);
1074
- }).map((status) => readFreshRunStatus(status.runPath)).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
1075
- }
1076
- function isTerminalRunStatus(status) {
1077
- return status === "completed" || status === "failed" || status === "canceled" || status === "stale";
1078
- }
1079
- function isPidAlive(pid) {
1080
- if (pid === void 0 || !Number.isInteger(pid) || pid <= 0) return false;
1081
- try {
1082
- process.kill(pid, 0);
1083
- return true;
1084
- } catch (error) {
1085
- return isRecord(error) && error.code === "EPERM";
1086
- }
1087
- }
1088
- function writeStatus(filePath, status) {
1089
- mkdirSync(dirname(filePath), {
1090
- recursive: true,
1091
- mode: 448
1092
- });
1093
- const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.status.tmp`);
1094
- writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
1095
- fsyncFile(tempPath);
1096
- renameSync(tempPath, filePath);
1097
- fsyncDirectory(dirname(filePath));
1098
- }
1099
- function fsyncFile(filePath) {
1100
- const file = openSync(filePath, "r");
1101
- try {
1102
- fsyncSync(file);
1103
- } finally {
1104
- closeSync(file);
1105
- }
1106
- }
1107
- function fsyncDirectory(directoryPath) {
1108
- try {
1109
- const directory = openSync(directoryPath, "r");
1110
- try {
1111
- fsyncSync(directory);
1112
- } finally {
1113
- closeSync(directory);
1114
- }
1115
- } catch {}
1116
- }
1117
- function definedPatch(patch) {
1118
- const { now: _now, ...rest } = patch;
1119
- return Object.fromEntries(Object.entries(rest).filter((entry) => {
1120
- const [, value] = entry;
1121
- return value !== void 0;
1122
- }));
1123
- }
1124
- function parseRunStatus(value) {
1125
- if (!isRecord(value)) return;
1126
- if (value.version !== 1 || typeof value.runId !== "string" || typeof value.workspaceKey !== "string" || typeof value.runPath !== "string" || typeof value.repoPath !== "string" || !isRunStatusName(value.status) || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") return;
1127
- return {
1128
- version: 1,
1129
- runId: value.runId,
1130
- workspaceKey: value.workspaceKey,
1131
- runPath: value.runPath,
1132
- files: parseRunStatusFiles(value.files),
1133
- repoPath: value.repoPath,
1134
- status: value.status,
1135
- createdAt: value.createdAt,
1136
- updatedAt: value.updatedAt,
1137
- ...typeof value.pid === "number" ? { pid: value.pid } : {},
1138
- ...typeof value.startedAt === "string" ? { startedAt: value.startedAt } : {},
1139
- ...typeof value.completedAt === "string" ? { completedAt: value.completedAt } : {},
1140
- ...value.modelUsage === void 0 ? {} : { modelUsage: value.modelUsage },
1141
- ...value.agent === void 0 ? {} : { agent: value.agent },
1142
- ...value.sync === void 0 ? {} : { sync: value.sync },
1143
- ...typeof value.error === "string" ? { error: value.error } : {}
1144
- };
1145
- }
1146
- function parseRunStatusFiles(value) {
1147
- if (isRecord(value) && typeof value.status === "string" && typeof value.progress === "string" && typeof value.agentSession === "string" && typeof value.handoff === "string" && typeof value.evidence === "string") return {
1148
- status: value.status,
1149
- progress: value.progress,
1150
- agentSession: value.agentSession,
1151
- handoff: value.handoff,
1152
- evidence: value.evidence
1153
- };
1154
- return {
1155
- status: "status.json",
1156
- progress: "progress.jsonl",
1157
- agentSession: "agent-session.jsonl",
1158
- handoff: "handoff.md",
1159
- evidence: "evidence"
1160
- };
1161
- }
1162
- function isRunStatusName(value) {
1163
- return value === "queued" || value === "running" || value === "canceling" || value === "completed" || value === "failed" || value === "canceled" || value === "stale";
1164
- }
1165
- //#endregion
1166
- //#region src/doctor/checks.ts
1167
- const execFileAsync = promisify(execFile);
1168
- function createDoctorContext(overrides = {}) {
1169
- return {
1170
- cwd: resolve(overrides.cwd ?? process.cwd()),
1171
- runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
1172
- authStore: overrides.authStore ?? authStore,
1173
- fetchFn: overrides.fetchFn ?? fetch,
1174
- execFn: overrides.execFn ?? defaultExecFn,
1175
- probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
1176
- };
1177
- }
1178
- async function defaultExecFn(file, args, options) {
1179
- const result = await execFileAsync(file, [...args], {
1180
- timeout: options?.timeout ?? 5e3,
1181
- encoding: "utf8"
1182
- });
1183
- return {
1184
- stdout: result.stdout ?? "",
1185
- stderr: result.stderr ?? ""
1186
- };
1187
- }
1188
- async function checkAuthPresent(context) {
1189
- const credentials = await resolveAuthCredentials({ store: context.authStore });
1190
- if (credentials === null) return { result: {
1191
- id: "auth.present",
1192
- section: "identity",
1193
- severity: "fail",
1194
- message: "No Quire credentials on this machine.",
1195
- fix: { command: "quire login" }
1196
- } };
1197
- return {
1198
- result: {
1199
- id: "auth.present",
1200
- section: "identity",
1201
- severity: "pass",
1202
- message: "Credentials present."
1203
- },
1204
- auth: {
1205
- apiBaseUrl: credentials.apiBaseUrl,
1206
- accessToken: credentials.accessToken,
1207
- tokenType: credentials.tokenType,
1208
- ...credentials.user === void 0 ? {} : { user: credentials.user }
1209
- }
1210
- };
1211
- }
1212
- async function checkAuthValid(context, auth) {
1213
- if (auth.tokenType === "QuireApiKey") try {
1214
- return { result: {
1215
- id: "auth.valid",
1216
- section: "identity",
1217
- severity: "pass",
1218
- message: `API token valid for ${(await fetchModelSourceBrokerStatus({
1219
- apiBaseUrl: auth.apiBaseUrl,
1220
- accessToken: auth.accessToken,
1221
- tokenType: auth.tokenType,
1222
- fetchFn: context.fetchFn
1223
- })).actor?.environmentName ?? "remote agent environment"}.`
1224
- } };
1225
- } catch (error) {
1226
- return { result: {
1227
- id: "auth.valid",
1228
- section: "identity",
1229
- severity: "fail",
1230
- message: `Could not validate Quire API token: ${toMessage(error)}`,
1231
- fix: { command: "Set QUIRE_API_TOKEN to an active token from Quire settings" }
1232
- } };
1233
- }
1234
- try {
1235
- const lookup = await fetchCurrentSession({
1236
- apiBaseUrl: auth.apiBaseUrl,
1237
- accessToken: auth.accessToken,
1238
- tokenType: auth.tokenType,
1239
- fetchFn: context.fetchFn
1240
- });
1241
- if (lookup.data === null) return { result: {
1242
- id: "auth.valid",
1243
- section: "identity",
1244
- severity: "fail",
1245
- message: "Stored credentials were rejected by Quire.",
1246
- fix: { command: "quire login" }
1247
- } };
1248
- const user = lookup.data.user;
1249
- return {
1250
- result: {
1251
- id: "auth.valid",
1252
- section: "identity",
1253
- severity: "pass",
1254
- message: `Logged in as ${formatUser$2(user)}.`
1255
- },
1256
- user
1257
- };
1258
- } catch (error) {
1259
- return { result: {
1260
- id: "auth.valid",
1261
- section: "identity",
1262
- severity: "fail",
1263
- message: `Could not validate Quire session: ${toMessage(error)}`,
1264
- fix: { command: "quire login" }
1265
- } };
1266
- }
1267
- }
1268
- async function checkAuthBroker(context, auth) {
1269
- try {
1270
- return brokerToCheckResult(await fetchModelSourceBrokerStatus({
1271
- apiBaseUrl: auth.apiBaseUrl,
1272
- accessToken: auth.accessToken,
1273
- tokenType: auth.tokenType,
1274
- fetchFn: context.fetchFn
1275
- }));
1276
- } catch (error) {
1277
- return {
1278
- id: "auth.broker",
1279
- section: "identity",
1280
- severity: "warn",
1281
- message: `Could not read model-source status: ${toMessage(error)}`
1282
- };
1283
- }
1284
- }
1285
- function brokerToCheckResult(status) {
1286
- if (status.requiredNextAction !== null) return {
1287
- id: "auth.broker",
1288
- section: "identity",
1289
- severity: "fail",
1290
- message: `Model broker requires action: ${status.requiredNextAction}.`,
1291
- fix: { command: "quire whoami --json" }
1292
- };
1293
- if (!status.available) return {
1294
- id: "auth.broker",
1295
- section: "identity",
1296
- severity: "warn",
1297
- message: `Model broker unavailable (${status.reason}).`
1298
- };
1299
- return {
1300
- id: "auth.broker",
1301
- section: "identity",
1302
- severity: "pass",
1303
- message: `Model broker available (${status.selectedProviderMode ?? "default"}).`
1304
- };
1305
- }
1306
- function checkEnvironment(context) {
1307
- try {
1308
- const environment = loadEnvironment(context.cwd);
1309
- return {
1310
- presence: {
1311
- id: "env.present",
1312
- section: "workspace",
1313
- severity: "pass",
1314
- message: ".quire/environment.json found."
1315
- },
1316
- validity: {
1317
- id: "env.valid",
1318
- section: "workspace",
1319
- severity: "pass",
1320
- message: `Target: ${describeTarget(environment)}.`
1321
- },
1322
- env: { environment }
1323
- };
1324
- } catch (error) {
1325
- if (error instanceof EnvironmentError && error.code === "missing_file") return { presence: {
1326
- id: "env.present",
1327
- section: "workspace",
1328
- severity: "fail",
1329
- message: "No .quire/environment.json in this workspace.",
1330
- fix: { command: "quire setup" }
1331
- } };
1332
- return {
1333
- presence: {
1334
- id: "env.present",
1335
- section: "workspace",
1336
- severity: "pass",
1337
- message: ".quire/environment.json found."
1338
- },
1339
- validity: {
1340
- id: "env.valid",
1341
- section: "workspace",
1342
- severity: "fail",
1343
- message: error instanceof EnvironmentError ? `Invalid environment: ${error.message}` : `Could not read environment: ${toMessage(error)}`,
1344
- fix: { command: "quire setup" }
1345
- }
1346
- };
1347
- }
1348
- }
1349
- function describeTarget(environment) {
1350
- if (isMobileEnvironment(environment)) return `mobile/${environment.app.platform ?? "unknown"} → ${environment.app.appId ?? environment.app.appPath ?? "unspecified"}`;
1351
- return `web → ${environmentBaseUrl(environment) ?? "unspecified"}`;
1352
- }
1353
- async function checkWebReachable(context, baseUrl) {
1354
- const started = Date.now();
1355
- try {
1356
- const response = await fetchWithTimeout(context.fetchFn, baseUrl, { method: "HEAD" }, context.probeTimeoutMs);
1357
- const elapsed = Date.now() - started;
1358
- if (!response.ok) return {
1359
- id: "target.web.reachable",
1360
- section: "workspace",
1361
- severity: "warn",
1362
- message: `Target responded with HTTP ${response.status} (${elapsed}ms).`
1363
- };
1364
- return {
1365
- id: "target.web.reachable",
1366
- section: "workspace",
1367
- severity: "pass",
1368
- message: `Target reachable (${response.status}, ${elapsed}ms).`
1369
- };
1370
- } catch (error) {
1371
- return {
1372
- id: "target.web.reachable",
1373
- section: "workspace",
1374
- severity: "fail",
1375
- message: `Target unreachable: ${toMessage(error)}`
1376
- };
1377
- }
1378
- }
1379
- async function checkMobileDevice(context, app) {
1380
- const provider = app.provider ?? "local";
1381
- if (provider === "local") {
1382
- if (app.platform === "ios") return [await checkIosSimulator(context, app)];
1383
- if (app.platform === "android") return [await checkAndroidDevice(context, app)];
1384
- return [{
1385
- id: "target.mobile.device",
1386
- section: "workspace",
1387
- severity: "warn",
1388
- message: "Mobile platform not specified."
1389
- }];
1390
- }
1391
- if (provider === "appium") return [await checkAppiumServer(context, app)];
1392
- return [{
1393
- id: "target.mobile.device",
1394
- section: "workspace",
1395
- severity: "pass",
1396
- message: `Provider ${provider} configured; remote device checks deferred.`
1397
- }];
1398
- }
1399
- async function checkIosSimulator(context, app) {
1400
- try {
1401
- const { stdout } = await context.execFn("xcrun", [
1402
- "simctl",
1403
- "list",
1404
- "devices",
1405
- "booted",
1406
- "-j"
1407
- ], { timeout: context.probeTimeoutMs });
1408
- const parsed = parseSimctlBooted(stdout);
1409
- if (parsed.length === 0) return {
1410
- id: "target.mobile.device",
1411
- section: "workspace",
1412
- severity: "fail",
1413
- message: "No booted iOS simulator found.",
1414
- fix: { command: "xcrun simctl boot \"iPhone 15\"" }
1415
- };
1416
- if (app.device !== void 0 && !parsed.some((entry) => matchesIosDevice(entry, app.device))) return {
1417
- id: "target.mobile.device",
1418
- section: "workspace",
1419
- severity: "warn",
1420
- message: `Configured device ${app.device} is not currently booted.`,
1421
- fix: { command: `xcrun simctl boot "${app.device}"` }
1422
- };
1423
- return {
1424
- id: "target.mobile.device",
1425
- section: "workspace",
1426
- severity: "pass",
1427
- message: `iOS simulator booted: ${parsed.map((entry) => entry.name).join(", ")}.`
1428
- };
1429
- } catch (error) {
1430
- return {
1431
- id: "target.mobile.device",
1432
- section: "workspace",
1433
- severity: "fail",
1434
- message: `xcrun simctl failed: ${toMessage(error)}`
1435
- };
1436
- }
1437
- }
1438
- async function checkAndroidDevice(context, app) {
1439
- try {
1440
- const { stdout } = await context.execFn("adb", ["devices"], { timeout: context.probeTimeoutMs });
1441
- const devices = parseAdbDevices(stdout);
1442
- if (devices.length === 0) return {
1443
- id: "target.mobile.device",
1444
- section: "workspace",
1445
- severity: "fail",
1446
- message: "No connected Android devices.",
1447
- fix: { command: "adb devices" }
1448
- };
1449
- if (app.device !== void 0 && !devices.includes(app.device)) return {
1450
- id: "target.mobile.device",
1451
- section: "workspace",
1452
- severity: "warn",
1453
- message: `Configured device ${app.device} is not in adb devices.`
1454
- };
1455
- return {
1456
- id: "target.mobile.device",
1457
- section: "workspace",
1458
- severity: "pass",
1459
- message: `Android device(s) available: ${devices.join(", ")}.`
1460
- };
1461
- } catch (error) {
1462
- return {
1463
- id: "target.mobile.device",
1464
- section: "workspace",
1465
- severity: "fail",
1466
- message: `adb failed: ${toMessage(error)}`
1467
- };
1468
- }
1469
- }
1470
- async function checkAppiumServer(context, app) {
1471
- if (app.appiumUrl === void 0) return {
1472
- id: "target.mobile.appium",
1473
- section: "workspace",
1474
- severity: "fail",
1475
- message: "Appium provider configured without appiumUrl."
1476
- };
1477
- try {
1478
- const response = await fetchWithTimeout(context.fetchFn, `${stripTrailingSlash(app.appiumUrl)}/status`, { method: "GET" }, context.probeTimeoutMs);
1479
- if (!response.ok) return {
1480
- id: "target.mobile.appium",
1481
- section: "workspace",
1482
- severity: "fail",
1483
- message: `Appium /status returned HTTP ${response.status}.`
1484
- };
1485
- return {
1486
- id: "target.mobile.appium",
1487
- section: "workspace",
1488
- severity: "pass",
1489
- message: `Appium server reachable at ${app.appiumUrl}.`
1490
- };
1491
- } catch (error) {
1492
- return {
1493
- id: "target.mobile.appium",
1494
- section: "workspace",
1495
- severity: "fail",
1496
- message: `Appium server unreachable: ${toMessage(error)}`
1497
- };
1498
- }
1499
- }
1500
- function checkMobileAppPath(app) {
1501
- if (app.appPath === void 0) return {
1502
- id: "target.mobile.appPath",
1503
- section: "workspace",
1504
- severity: "warn",
1505
- message: "appPath not set; agent-mobile will not auto-install."
1506
- };
1507
- if (!existsSync(app.appPath)) return {
1508
- id: "target.mobile.appPath",
1509
- section: "workspace",
1510
- severity: "fail",
1511
- message: `appPath does not exist: ${app.appPath}.`
1512
- };
1513
- return {
1514
- id: "target.mobile.appPath",
1515
- section: "workspace",
1516
- severity: "pass",
1517
- message: `appPath exists: ${app.appPath}.`
1518
- };
1519
- }
1520
- async function checkRunsRoot(context) {
1521
- try {
1522
- await mkdir(context.runsRoot, {
1523
- recursive: true,
1524
- mode: 448
1525
- });
1526
- await access(context.runsRoot, constants.W_OK);
1527
- return {
1528
- id: "runs.rootWritable",
1529
- section: "runs",
1530
- severity: "pass",
1531
- message: `Runs root writable (${context.runsRoot}).`
1532
- };
1533
- } catch (error) {
1534
- return {
1535
- id: "runs.rootWritable",
1536
- section: "runs",
1537
- severity: "fail",
1538
- message: `Runs root not writable: ${toMessage(error)}`
1539
- };
1540
- }
1541
- }
1542
- function checkStuckRuns(context) {
1543
- try {
1544
- const workspaceKey = workspaceKeyForRepoPath(context.cwd);
1545
- const stuck = listRunStatuses({
1546
- cwd: context.cwd,
1547
- runsRoot: context.runsRoot
1548
- }).filter((status) => status.workspaceKey === workspaceKey && (status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid));
1549
- if (stuck.length === 0) return {
1550
- id: "runs.stuck",
1551
- section: "runs",
1552
- severity: "pass",
1553
- message: "No stuck runs."
1554
- };
1555
- const first = stuck[0];
1556
- if (first === void 0) return {
1557
- id: "runs.stuck",
1558
- section: "runs",
1559
- severity: "pass",
1560
- message: "No stuck runs."
1561
- };
1562
- return {
1563
- id: "runs.stuck",
1564
- section: "runs",
1565
- severity: "warn",
1566
- message: `${stuck.length} stuck run(s); first: ${first.runId}.`,
1567
- fix: { command: `quire runs cancel ${first.runId}` }
1568
- };
1569
- } catch (error) {
1570
- return {
1571
- id: "runs.stuck",
1572
- section: "runs",
1573
- severity: "warn",
1574
- message: `Could not inspect run statuses: ${toMessage(error)}`
1575
- };
1576
- }
1577
- }
1578
- async function fetchWithTimeout(fetchFn, url, init, timeoutMs) {
1579
- return fetchFn(url, {
1580
- ...init,
1581
- signal: AbortSignal.timeout(timeoutMs)
1582
- });
1583
- }
1584
- function stripTrailingSlash(value) {
1585
- return value.endsWith("/") ? value.slice(0, -1) : value;
1586
- }
1587
- function formatUser$2(user) {
1588
- if (user.email !== void 0 && user.email !== null && user.email.length > 0) return user.email;
1589
- if (user.name !== void 0 && user.name !== null && user.name.length > 0) return user.name;
1590
- return user.id;
1591
- }
1592
- function toMessage(error) {
1593
- return error instanceof Error ? error.message : String(error);
1594
- }
1595
- function parseSimctlBooted(stdout) {
1596
- try {
1597
- const devices = JSON.parse(stdout).devices ?? {};
1598
- const entries = [];
1599
- for (const list of Object.values(devices)) {
1600
- if (!Array.isArray(list)) continue;
1601
- for (const device of list) if (typeof device === "object" && device !== null && device.state === "Booted" && typeof device.name === "string" && typeof device.udid === "string") entries.push({
1602
- name: device.name,
1603
- udid: device.udid
1604
- });
1605
- }
1606
- return entries;
1607
- } catch {
1608
- return [];
1609
- }
1610
- }
1611
- function matchesIosDevice(entry, device) {
1612
- if (device === void 0) return true;
1613
- return entry.udid === device || entry.name === device;
1614
- }
1615
- function parseAdbDevices(stdout) {
1616
- return stdout.split("\n").slice(1).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("*")).map((line) => line.split(/\s+/)[0]).filter((id) => id !== void 0 && id.length > 0 && id !== "List");
1617
- }
1618
- //#endregion
1619
- //#region src/doctor/runner.ts
1620
- async function runDoctor(overrides = {}) {
1621
- const context = createDoctorContext(overrides);
1622
- const [identityResults, workspaceResults, runsResults] = await Promise.all([
1623
- runIdentitySection(context),
1624
- runWorkspaceSection(context),
1625
- runRunsSection(context)
1626
- ]);
1627
- return buildReport([
1628
- ...identityResults,
1629
- ...workspaceResults,
1630
- ...runsResults
1631
- ]);
1632
- }
1633
- async function runIdentitySection(context) {
1634
- const results = [];
1635
- const presence = await checkAuthPresent(context);
1636
- results.push(presence.result);
1637
- if (presence.auth === void 0) return results;
1638
- const validity = await checkAuthValid(context, presence.auth);
1639
- results.push(validity.result);
1640
- if (validity.result.severity === "fail") return results;
1641
- results.push(await checkAuthBroker(context, presence.auth));
1642
- return results;
1643
- }
1644
- async function runWorkspaceSection(context) {
1645
- const env = checkEnvironment(context);
1646
- const results = [env.presence];
1647
- if (env.validity !== void 0) results.push(env.validity);
1648
- if (env.env === void 0) return results;
1649
- if (isWebEnvironment(env.env.environment)) {
1650
- results.push(await checkWebReachable(context, env.env.environment.app.baseUrl));
1651
- return results;
1652
- }
1653
- if (isMobileEnvironment(env.env.environment)) {
1654
- const app = env.env.environment.app;
1655
- results.push(...await checkMobileDevice(context, app));
1656
- results.push(checkMobileAppPath(app));
1657
- }
1658
- return results;
1659
- }
1660
- async function runRunsSection(context) {
1661
- return [await checkRunsRoot(context), checkStuckRuns(context)];
1662
- }
1663
- function buildReport(results) {
1664
- const summary = {
1665
- pass: 0,
1666
- warn: 0,
1667
- fail: 0
1668
- };
1669
- for (const result of results) summary[result.severity] += 1;
1670
- return {
1671
- ok: summary.fail === 0,
1672
- summary,
1673
- checks: results
1674
- };
1675
- }
1676
- function isFailingReport(report, options = {}) {
1677
- if (report.summary.fail > 0) return true;
1678
- return options.strict === true && report.summary.warn > 0;
1679
- }
1680
- //#endregion
1681
- //#region src/commands/doctor.ts
1682
- const doctorCommand = defineCommand({
1683
- meta: {
1684
- name: "doctor",
1685
- description: "Diagnose Quire setup: auth, workspace target, and run state."
1686
- },
1687
- args: {
1688
- json: {
1689
- type: "boolean",
1690
- description: "Emit the doctor report as JSON."
1691
- },
1692
- strict: {
1693
- type: "boolean",
1694
- description: "Exit non-zero on warnings as well as failures."
1695
- }
1696
- },
1697
- async run({ args }) {
1698
- await runDoctorCommand({
1699
- json: args.json === true,
1700
- strict: args.strict === true
1701
- });
1702
- }
1703
- });
1704
- async function runDoctorCommand(options = {}) {
1705
- const stdout = options.io?.stdout ?? process.stdout;
1706
- const report = await withDoctorSpinner(startDoctorSpinner(options, stdout), () => runDoctor(options.context ?? {}));
1707
- if (options.json === true) writeJson(stdout, report);
1708
- else renderDoctorReport(stdout, report, options.strict === true);
1709
- if (isFailingReport(report, { strict: options.strict === true })) process.exitCode = 1;
1710
- return report;
1711
- }
1712
- function startDoctorSpinner(options, output) {
1713
- if (options.json === true) return;
1714
- const doctorSpinner = spinner({ output: output ?? process.stdout });
1715
- doctorSpinner.start("Checking Quire setup");
1716
- return doctorSpinner;
1717
- }
1718
- async function withDoctorSpinner(doctorSpinner, callback) {
1719
- try {
1720
- return await callback();
1721
- } finally {
1722
- doctorSpinner?.clear();
1723
- }
1724
- }
1725
- //#endregion
1726
- //#region src/utils/command-args.ts
1727
- function readOptionalString$1(value) {
1728
- return readString(value);
1729
- }
1730
- //#endregion
1731
- //#region src/commands/env.ts
1732
- const envCommand = defineCommand({
1733
- meta: {
1734
- name: "env",
1735
- description: "Show the workspace target configuration Quire uses for investigations."
1736
- },
1737
- args: {
1738
- json: {
1739
- type: "boolean",
1740
- description: "Print the environment as JSON."
1741
- },
1742
- target: {
1743
- type: "string",
1744
- description: "Show a named target from .quire/environment.json.",
1745
- valueHint: "name"
1746
- }
1747
- },
1748
- async run({ args }) {
1749
- await runEnvPrint({
1750
- json: args.json === true,
1751
- target: readOptionalString$1(args.target)
1752
- });
1753
- },
1754
- subCommands: { show: defineCommand({
1755
- meta: {
1756
- name: "show",
1757
- description: "Show the current workspace target configuration."
1758
- },
1759
- args: {
1760
- json: {
1761
- type: "boolean",
1762
- description: "Print the environment as JSON."
1763
- },
1764
- target: {
1765
- type: "string",
1766
- description: "Show a named target from .quire/environment.json.",
1767
- valueHint: "name"
1768
- }
1769
- },
1770
- async run({ args }) {
1771
- await runEnvPrint({
1772
- json: args.json === true,
1773
- target: readOptionalString$1(args.target)
1774
- });
1775
- }
1776
- }) }
1777
- });
1778
- async function runEnvPrint(options = {}) {
1779
- const cwd = resolve(options.cwd ?? process.cwd());
1780
- const stdout = options.io?.stdout ?? process.stdout;
1781
- const env = readEnvIfPresent(cwd, normalizeTargetName(options.target, cwd));
1782
- if (env === null) {
1783
- if (options.json === true) writeJson(stdout, null);
1784
- else {
1785
- log.warn(`No ${formatEnvironmentPath(cwd)} in this workspace.`, {
1786
- output: stdout,
1787
- spacing: 0
1788
- });
1789
- log.message(`Create one with ${color.command("quire setup")}.`, {
1790
- output: stdout,
1791
- spacing: 0,
1792
- withGuide: true
1793
- });
1794
- }
1795
- return null;
1796
- }
1797
- if (options.json === true) {
1798
- writeJson(stdout, env);
1799
- return env;
1800
- }
1801
- log.success("Quire environment", {
1802
- output: stdout,
1803
- spacing: 0
1804
- });
1805
- writeLine(stdout, `${color.label("Path")}: ${color.path(formatEnvironmentPath(cwd))}`);
1806
- for (const line of describeEnvironment(env)) writeLine(stdout, line);
1807
- return env;
1808
- }
1809
- function readEnvIfPresent(cwd, target) {
1810
- try {
1811
- return loadEnvironment(cwd, target);
1812
- } catch (error) {
1813
- if (error instanceof EnvironmentError && error.code === "missing_file") return null;
1814
- if (error instanceof EnvironmentError) throw environmentCliError(error, target, cwd);
1815
- throw error;
1816
- }
1817
- }
1818
- function formatEnvironmentPath(cwd) {
1819
- const path = environmentFilePath(cwd);
1820
- const rel = relative(cwd, path);
1821
- return rel.startsWith("..") || rel.startsWith("/") ? path : rel;
1822
- }
1823
- function normalizeTargetName(target, cwd) {
1824
- if (target === void 0) return;
1825
- try {
1826
- return sanitizeTargetName(target);
1827
- } catch (error) {
1828
- if (error instanceof EnvironmentError) throw environmentCliError(error, target, cwd);
1829
- throw error;
1830
- }
1831
- }
1832
- function environmentCliError(error, target, cwd) {
1833
- if (error.code === "schema_violation" && error.filePath.length === 0) return new CliError(`Invalid target name: ${target ?? ""}`, ExitCode.InvalidInput);
1834
- const relPath = error.filePath.length === 0 ? "environment configuration" : formatEnvironmentPath(cwd);
1835
- return new CliError(`${error.message} in ${relPath}`, ExitCode.InvalidInput);
1836
- }
1837
- function describeEnvironment(env) {
1838
- const baseUrl = isWebEnvironment(env) ? env.app.url : "(unset)";
1839
- const lines = [
1840
- `${color.label("Target")}: ${color.value(env.targetName)}`,
1841
- `${color.label("Kind")}: ${color.value(env.app.kind)}`,
1842
- `${color.label("URL")}: ${baseUrl}`
1843
- ];
1844
- if (!isWebEnvironment(env)) return lines;
1845
- if (env.app.description !== void 0) lines.push(`${color.label("Description")}: ${env.app.description}`);
1846
- if (env.app.entrypoint !== void 0) lines.push(`${color.label("Entrypoint")}: ${env.app.entrypoint}`);
1847
- if (env.app.readOnly !== void 0) lines.push(`${color.label("Read only")}: ${String(env.app.readOnly)}`);
1848
- return lines;
1849
- }
1850
- //#endregion
1851
- //#region src/commands/investigate.ts
1852
- const investigationCommandSchema = {
1853
- version: 1,
1854
- command: "quire investigate",
1855
- description: "Investigation command learning stub. Rebuild the implementation from the guide.",
1856
- status: "not_implemented",
1857
- arguments: [
1858
- {
1859
- name: "prompt",
1860
- type: "string",
1861
- positional: true,
1862
- requiredUnless: "--stdin",
1863
- description: "Natural-language investigation query."
1864
- },
1865
- {
1866
- name: "--stdin",
1867
- type: "boolean",
1868
- description: "Read additional plain-text investigation context from stdin."
1869
- },
1870
- {
1871
- name: "--json",
1872
- type: "boolean",
1873
- description: "Reserved for the rebuilt command's machine-readable start handle."
1874
- },
1875
- {
1876
- name: "--watch",
1877
- type: "boolean",
1878
- description: "Reserved for the rebuilt command's attached run watcher."
1879
- },
1880
- {
1881
- name: "--detach",
1882
- type: "boolean",
1883
- description: "Reserved for the rebuilt command's detached run mode."
1884
- },
1885
- {
1886
- name: "--url",
1887
- type: "string",
1888
- description: "Reserved for the rebuilt command's web target override."
1889
- },
1890
- {
1891
- name: "--target",
1892
- type: "string",
1893
- description: "Reserved for the rebuilt command's named target selection."
1894
- },
1895
- {
1896
- name: "--schema",
1897
- type: "boolean",
1898
- description: "Print this machine-readable invocation schema and exit."
1899
- }
1900
- ]
1901
- };
1902
- const investigateCommand = defineCommand({
1903
- meta: {
1904
- name: "investigate",
1905
- alias: "ask",
1906
- description: "Investigate a question against the current workspace."
1907
- },
1908
- args: {
1909
- prompt: {
1910
- type: "positional",
1911
- description: "Natural-language investigation query.",
1912
- required: false
1913
- },
1914
- url: {
1915
- type: "string",
1916
- description: "Reserved for the rebuilt web target override.",
1917
- valueHint: "url"
1918
- },
1919
- target: {
1920
- type: "string",
1921
- description: "Reserved for the rebuilt named target selection.",
1922
- valueHint: "name"
1923
- },
1924
- stdin: {
1925
- type: "boolean",
1926
- description: "Read additional plain-text investigation context from stdin."
1927
- },
1928
- json: {
1929
- type: "boolean",
1930
- description: "Reserved for the rebuilt JSON start handle."
1931
- },
1932
- watch: {
1933
- type: "boolean",
1934
- description: "Reserved for the rebuilt run watcher."
1935
- },
1936
- detach: {
1937
- type: "boolean",
1938
- description: "Reserved for the rebuilt detached run mode."
1939
- },
1940
- headed: {
1941
- type: "boolean",
1942
- description: "Reserved for the rebuilt browser tooling."
1943
- },
1944
- schema: {
1945
- type: "boolean",
1946
- description: "Emit the machine-readable investigation invocation schema and exit."
1947
- }
1948
- },
1949
- async run({ args }) {
1950
- if (args.schema === true) {
1951
- writeInvestigationCommandSchema();
1952
- return;
1953
- }
1954
- await runInvestigate({
1955
- query: readOptionalString(args.prompt),
1956
- target: readOptionalString(args.target),
1957
- url: readOptionalString(args.url),
1958
- stdin: args.stdin === true,
1959
- json: args.json === true,
1960
- watch: args.watch === true,
1961
- detach: args.detach === true,
1962
- headed: args.headed === true
1963
- });
1964
- }
1965
- });
1966
- function writeInvestigationCommandSchema(options = {}) {
1967
- writeJson(options.io?.stdout ?? process.stdout, investigationCommandSchema);
1968
- }
1969
- async function runInvestigate(_options) {
1970
- createRunDir(cwd());
1971
- }
1972
- function readOptionalString(value) {
1973
- return typeof value === "string" && value.length > 0 ? value : void 0;
1974
- }
1975
- //#endregion
1976
- //#region src/auth/device-flow.ts
1977
- var DeviceAuthError = class extends CliError {
1978
- constructor(message, code) {
1979
- super(message, ExitCode.AuthFailure);
1980
- this.code = code;
1981
- this.name = "DeviceAuthError";
1982
- }
1983
- };
1984
- async function pollForToken(options) {
1985
- const sleep = options.sleep ?? defaultSleep;
1986
- const now = options.now ?? Date.now;
1987
- const expiresAt = now() + options.expiresIn * 1e3;
1988
- let interval = Math.max(options.interval, 1);
1989
- while (now() < expiresAt) try {
1990
- return await (options.requestToken?.() ?? requestDeviceToken({
1991
- apiBaseUrl: options.apiBaseUrl,
1992
- deviceCode: options.deviceCode,
1993
- fetchFn: options.fetchFn
1994
- }));
1995
- } catch (error) {
1996
- const code = readDevicePollErrorCode(error);
1997
- if (code === void 0) throw error;
1998
- if (code === "authorization_pending") {
1999
- options.onStatus?.(code, interval);
2000
- await sleep(interval * 1e3);
2001
- continue;
2002
- }
2003
- if (code === "slow_down") {
2004
- interval += 5;
2005
- options.onStatus?.(code, interval);
2006
- await sleep(interval * 1e3);
2007
- continue;
2008
- }
2009
- throw new DeviceAuthError(deviceErrorMessage(code), code);
2010
- }
2011
- throw new DeviceAuthError(deviceErrorMessage("expired_token"), "expired_token");
2012
- }
2013
- function readDevicePollErrorCode(error) {
2014
- if (error instanceof DeviceAuthError) return error.code;
2015
- if (error instanceof HttpError && isDevicePollErrorCode(error.code)) return error.code;
2016
- }
2017
- function isDevicePollErrorCode(value) {
2018
- return value === "authorization_pending" || value === "slow_down" || value === "access_denied" || value === "expired_token" || value === "invalid_grant";
2019
- }
2020
- function deviceErrorMessage(code) {
2021
- switch (code) {
2022
- case "access_denied": return "Device login was denied in the browser.";
2023
- case "expired_token": return "Device login code expired. Run `quire login` again.";
2024
- case "invalid_grant": return "Device login code is invalid. Run `quire login` again.";
2025
- case "authorization_pending": return "Device login is still waiting for browser approval.";
2026
- case "slow_down": return "Device login polling was too frequent.";
2027
- }
2028
- }
2029
- function defaultSleep(ms) {
2030
- return new Promise((resolve) => setTimeout(resolve, ms));
2031
- }
2032
- //#endregion
2033
- //#region src/commands/login.ts
2034
- const loginCommand = defineCommand({
2035
- meta: {
2036
- name: "login",
2037
- description: "Authenticate this machine with Quire using browser device authorization."
2038
- },
2039
- args: {
2040
- "api-url": {
2041
- type: "string",
2042
- description: "Quire web app base URL. Defaults to QUIRE_API_URL or https://quire.sh.",
2043
- valueHint: "url"
2044
- },
2045
- "no-open": {
2046
- type: "boolean",
2047
- description: "Print the device URL without trying to open a browser."
2048
- }
2049
- },
2050
- async run({ args }) {
2051
- await runLogin({
2052
- apiBaseUrl: readApiBaseUrl(args["api-url"]),
2053
- noOpen: args["no-open"] === true
2054
- });
2055
- }
2056
- });
2057
- async function runLogin(options) {
2058
- const store = options.store ?? authStore;
2059
- const stdout = options.io?.stdout ?? process.stdout;
2060
- const stderr = options.io?.stderr ?? process.stderr;
2061
- const device = await (options.requestCode?.() ?? requestDeviceCode({
2062
- apiBaseUrl: options.apiBaseUrl,
2063
- fetchFn: options.fetchFn
2064
- }));
2065
- log.step("Authorize Quire CLI", {
2066
- output: stdout,
2067
- spacing: 0
2068
- });
2069
- log.message([`${color.label("Open")}: ${device.verification_uri}`, `${color.label("Code")}: ${color.value(device.user_code)}`], {
2070
- output: stdout,
2071
- spacing: 0,
2072
- withGuide: true
2073
- });
2074
- if (options.noOpen !== true) if (await (options.opener ?? openBrowser)(device.verification_uri_complete)) log.success("Opened browser for Quire authorization.", {
2075
- output: stderr,
2076
- spacing: 0
2077
- });
2078
- else log.warn("Could not open a browser automatically. Open the URL above.", {
2079
- output: stderr,
2080
- spacing: 0
2081
- });
2082
- log.info("Waiting for browser approval...", {
2083
- output: stderr,
2084
- spacing: 0
2085
- });
2086
- const token = await (options.pollToken?.() ?? pollForToken({
2087
- apiBaseUrl: options.apiBaseUrl,
2088
- deviceCode: device.device_code,
2089
- interval: device.interval,
2090
- expiresIn: device.expires_in,
2091
- fetchFn: options.fetchFn,
2092
- sleep: options.sleep,
2093
- onStatus(status, nextInterval) {
2094
- if (status === "slow_down") log.warn(`Server asked Quire to slow down; polling every ${nextInterval}s.`, {
2095
- output: stderr,
2096
- spacing: 0
2097
- });
2098
- }
2099
- }));
2100
- const sessionLookup = await (options.getSession?.(token.access_token) ?? fetchCurrentSession({
2101
- apiBaseUrl: options.apiBaseUrl,
2102
- accessToken: token.access_token,
2103
- tokenType: token.token_type,
2104
- fetchFn: options.fetchFn
2105
- }));
2106
- const session = sessionLookup.data;
2107
- if (session === null) throw new CliError("Login succeeded but Quire could not read the current account.", ExitCode.AuthFailure);
2108
- const credentials = createStoredCredentials({
2109
- apiBaseUrl: options.apiBaseUrl,
2110
- accessToken: sessionLookup.refreshedAccessToken ?? token.access_token,
2111
- tokenType: token.token_type,
2112
- expiresAt: session.session.expiresAt,
2113
- expiresIn: token.expires_in,
2114
- user: session.user
2115
- });
2116
- await store.set(credentials);
2117
- log.success(`Logged in as ${color.value(formatUser$1(session.user))}.`, { output: stdout });
2118
- }
2119
- function formatUser$1(user) {
2120
- if (user.name && user.email && user.name !== user.email) return `${user.name} <${user.email}>`;
2121
- return user.email ?? user.name ?? user.id;
2122
- }
2123
- //#endregion
2124
- //#region src/commands/logout.ts
2125
- const logoutCommand = defineCommand({
2126
- meta: {
2127
- name: "logout",
2128
- description: "Remove stored Quire CLI credentials from this machine."
2129
- },
2130
- async run() {
2131
- await runLogout({});
2132
- }
2133
- });
2134
- async function runLogout(options) {
2135
- const store = options.store ?? authStore;
2136
- const stdout = options.io?.stdout ?? process.stdout;
2137
- const existing = await store.get();
2138
- await store.clear();
2139
- if (existing === null) log.info("Already logged out.", {
2140
- output: stdout,
2141
- spacing: 0
2142
- });
2143
- else log.success("Logged out of Quire.", {
2144
- output: stdout,
2145
- spacing: 0
2146
- });
2147
- }
2148
- //#endregion
2149
- //#region src/commands/runs.ts
2150
- const runsCommand = defineCommand({
2151
- meta: {
2152
- name: "runs",
2153
- description: "Inspect, watch, and stop durable Quire investigation runs."
2154
- },
2155
- subCommands: {
2156
- status: defineCommand({
2157
- meta: {
2158
- name: "status",
2159
- description: "Reserved for the rebuilt investigation run status command."
2160
- },
2161
- args: {
2162
- run: {
2163
- type: "positional",
2164
- description: "Run id or run directory.",
2165
- required: true
2166
- },
2167
- json: {
2168
- type: "boolean",
2169
- description: "Emit status JSON."
2170
- }
2171
- },
2172
- async run({ args }) {
2173
- await runStatus(readRequiredString(args.run, "run"), { json: args.json === true });
2174
- }
2175
- }),
2176
- watch: defineCommand({
2177
- meta: {
2178
- name: "watch",
2179
- description: "Reserved for the rebuilt investigation run watcher."
2180
- },
2181
- args: { run: {
2182
- type: "positional",
2183
- description: "Run id or run directory.",
2184
- required: true
2185
- } },
2186
- async run({ args }) {
2187
- await watchRun(readRequiredString(args.run, "run"));
2188
- }
2189
- }),
2190
- cancel: defineCommand({
2191
- meta: {
2192
- name: "cancel",
2193
- description: "Reserved for the rebuilt investigation run cancel command."
2194
- },
2195
- args: {
2196
- run: {
2197
- type: "positional",
2198
- description: "Run id or run directory.",
2199
- required: true
2200
- },
2201
- json: {
2202
- type: "boolean",
2203
- description: "Emit status JSON after the cancel request."
2204
- }
2205
- },
2206
- async run({ args }) {
2207
- await cancelRun(readRequiredString(args.run, "run"), { json: args.json === true });
2208
- }
2209
- }),
2210
- list: defineCommand({
2211
- meta: {
2212
- name: "list",
2213
- description: "Reserved for the rebuilt investigation run list command."
2214
- },
2215
- args: { json: {
2216
- type: "boolean",
2217
- description: "Emit run status JSON."
2218
- } },
2219
- async run({ args }) {
2220
- await listRuns({ json: args.json === true });
2221
- }
2222
- })
2223
- }
2224
- });
2225
- async function runStatus(run, options = {}) {
2226
- const status = {
2227
- runId: run,
2228
- status: "not_implemented"
2229
- };
2230
- if (options.json === true) {
2231
- writeJson(options.io?.stdout ?? process.stdout, status);
2232
- return status;
2233
- }
2234
- throw notImplemented("runs status");
2235
- }
2236
- async function watchRun(run) {
2237
- throw notImplemented(`runs watch ${run}`);
2238
- }
2239
- async function cancelRun(run, options = {}) {
2240
- const status = {
2241
- runId: run,
2242
- status: "not_implemented"
2243
- };
2244
- if (options.json === true) {
2245
- writeJson(options.io?.stdout ?? process.stdout, status);
2246
- return status;
2247
- }
2248
- throw notImplemented("runs cancel");
2249
- }
2250
- async function listRuns(options = {}) {
2251
- if (options.json === true) {
2252
- writeJson(options.io?.stdout ?? process.stdout, []);
2253
- return [];
2254
- }
2255
- throw notImplemented("runs list");
2256
- }
2257
- function notImplemented(command) {
2258
- return new CliError(`${command} is intentionally a clean-slate learning stub on this branch. Rebuild it from the implementation guide.`, ExitCode.InternalError);
2259
- }
2260
- function readRequiredString(value, name) {
2261
- if (typeof value === "string" && value.length > 0) return value;
2262
- throw new CliError(`Missing required ${name}.`, ExitCode.InvalidInput);
2263
- }
2264
- //#endregion
2265
- //#region src/commands/setup.ts
2266
- const SETUP_URL = "https://quire.sh/agent-setup";
2267
- const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
2268
-
2269
- Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
2270
-
2271
- Inspect the project, create the smallest useful .quire/environment.json launch brief, update AGENTS.md with concise run and verification instructions for future agents, then run quire doctor if available and report anything still blocked.`;
2272
- const setupCommand = defineCommand({
2273
- meta: {
2274
- name: "setup",
2275
- description: "Prepare this workspace for Quire investigations."
2276
- },
2277
- async run() {
2278
- await runSetup();
2279
- }
2280
- });
2281
- async function runSetup(options = {}) {
2282
- const stdout = options.io?.stdout ?? process.stdout;
2283
- log.step("Set up Quire for this workspace", {
2284
- output: stdout,
2285
- spacing: 0
2286
- });
2287
- log.message([`Open ${color.info(SETUP_URL)} to load the Quire setup skill, or copy this prompt into your coding agent like Claude Code, Codex, Pi, or Amp:`], {
2288
- output: stdout,
2289
- spacing: 0,
2290
- withGuide: true
2291
- });
2292
- writeLine(stdout);
2293
- writeLine(stdout, color.label("--- copy prompt ---"));
2294
- writeLine(stdout, AGENT_SETUP_PROMPT);
2295
- writeLine(stdout, color.label("--- end prompt ---"));
2296
- }
2297
- //#endregion
2298
- //#region src/commands/whoami.ts
2299
- const whoamiCommand = defineCommand({
2300
- meta: {
2301
- name: "whoami",
2302
- alias: "me",
2303
- description: "Print the Quire account for the stored CLI credentials."
2304
- },
2305
- args: { json: {
2306
- type: "boolean",
2307
- description: "Print machine-readable account state to stdout."
2308
- } },
2309
- async run({ args }) {
2310
- await runWhoami({ json: args.json === true });
2311
- }
2312
- });
2313
- async function runWhoami(options) {
2314
- const store = options.store ?? authStore;
2315
- const stdout = options.io?.stdout ?? process.stdout;
2316
- const credentials = await resolveAuthCredentials({ store });
2317
- if (credentials === null) {
2318
- if (options.json === true) writeJson(stdout, { authenticated: false });
2319
- throw new CliError("Not logged in. Run `quire login`.", ExitCode.AuthFailure);
2320
- }
2321
- if (credentials.tokenType === "QuireApiKey") {
2322
- const modelSourceBroker = await withProfileSpinner(startProfileSpinner(options, stdout), () => readModelSourceBrokerStatus({
2323
- accessToken: credentials.accessToken,
2324
- tokenType: credentials.tokenType,
2325
- apiBaseUrl: credentials.apiBaseUrl,
2326
- fetchFn: options.fetchFn,
2327
- getModelSourceBrokerStatus: options.getModelSourceBrokerStatus
2328
- }));
2329
- const actor = modelSourceBroker?.actor ?? null;
2330
- if (options.json === true) {
2331
- writeJson(stdout, {
2332
- authenticated: true,
2333
- authMethod: "api_token",
2334
- apiBaseUrl: credentials.apiBaseUrl,
2335
- actor,
2336
- modelSourceBroker
2337
- });
2338
- return;
2339
- }
2340
- writeWhoamiSummary(stdout, {
2341
- title: `Authenticated with API token for ${formatActor(actor)}.`,
2342
- modelSourceBroker
2343
- });
2344
- return;
2345
- }
2346
- const profile = await withProfileSpinner(startProfileSpinner(options, stdout), async () => {
2347
- const sessionLookup = await (options.getSession?.(credentials.accessToken) ?? fetchCurrentSession({
2348
- apiBaseUrl: credentials.apiBaseUrl,
2349
- accessToken: credentials.accessToken,
2350
- tokenType: credentials.tokenType,
2351
- fetchFn: options.fetchFn
2352
- }));
2353
- const session = sessionLookup.data;
2354
- if (session === null) {
2355
- await store.clear();
2356
- throw new CliError("Stored Quire credentials are no longer valid. Run `quire login`.", ExitCode.AuthFailure);
2357
- }
2358
- const refreshedCredentials = updateStoredCredentials(credentials, {
2359
- accessToken: sessionLookup.refreshedAccessToken,
2360
- expiresAt: session.session.expiresAt,
2361
- user: session.user
2362
- });
2363
- await store.set(refreshedCredentials);
2364
- return {
2365
- refreshedCredentials,
2366
- session,
2367
- modelSourceBroker: await readModelSourceBrokerStatus({
2368
- accessToken: refreshedCredentials.accessToken,
2369
- tokenType: refreshedCredentials.tokenType,
2370
- apiBaseUrl: refreshedCredentials.apiBaseUrl,
2371
- fetchFn: options.fetchFn,
2372
- getModelSourceBrokerStatus: options.getModelSourceBrokerStatus
2373
- })
2374
- };
2375
- });
2376
- if (options.json === true) {
2377
- writeJson(stdout, {
2378
- authenticated: true,
2379
- apiBaseUrl: profile.refreshedCredentials.apiBaseUrl,
2380
- user: profile.session.user,
2381
- session: profile.session.session,
2382
- modelSourceBroker: profile.modelSourceBroker
2383
- });
2384
- return;
2385
- }
2386
- writeWhoamiSummary(stdout, {
2387
- title: `Logged in as ${color.value(formatUser(profile.session.user))}.`,
2388
- modelSourceBroker: profile.modelSourceBroker
2389
- });
2390
- }
2391
- function startProfileSpinner(options, output) {
2392
- if (options.json === true) return;
2393
- const profileSpinner = spinner({ output: output ?? process.stdout });
2394
- profileSpinner.start("Loading profile");
2395
- return profileSpinner;
2396
- }
2397
- async function withProfileSpinner(profileSpinner, callback) {
2398
- try {
2399
- return await callback();
2400
- } finally {
2401
- profileSpinner?.clear();
2402
- }
2403
- }
2404
- async function readModelSourceBrokerStatus(options) {
2405
- try {
2406
- return await (options.getModelSourceBrokerStatus?.(options.accessToken, options.apiBaseUrl) ?? fetchModelSourceBrokerStatus({
2407
- apiBaseUrl: options.apiBaseUrl,
2408
- accessToken: options.accessToken,
2409
- tokenType: options.tokenType,
2410
- fetchFn: options.fetchFn
2411
- }));
2412
- } catch {
2413
- return null;
2414
- }
2415
- }
2416
- function formatActor(actor) {
2417
- return actor?.environmentName ?? actor?.triggerSource ?? "remote agent environment";
2418
- }
2419
- function formatUser(user) {
2420
- if (user.name && user.email && user.name !== user.email) return `${user.name} <${user.email}>`;
2421
- return user.email ?? user.name ?? user.id;
2422
- }
2423
- function writeWhoamiSummary(output, profile) {
2424
- log.success(profile.title, {
2425
- output: output ?? process.stdout,
2426
- spacing: 0
2427
- });
2428
- writeWalletBalance(output, profile.modelSourceBroker);
2429
- }
2430
- function writeWalletBalance(output, status) {
2431
- if (status?.remainingBalance === null || status?.remainingBalance === void 0) return;
2432
- log.message(`${color.label("Quire Wallet")}: ${color.value(formatWalletBalance(status.remainingBalance))}`, {
2433
- output: output ?? process.stdout,
2434
- spacing: 0,
2435
- withGuide: true
2436
- });
2437
- }
2438
- function formatWalletBalance(value) {
2439
- return new Intl.NumberFormat("en-US", {
2440
- style: "currency",
2441
- currency: "USD",
2442
- maximumFractionDigits: 2
2443
- }).format(value);
2444
- }
2445
- //#endregion
2446
- //#region package.json
2447
- var version$1 = "0.0.6";
2448
- //#endregion
2449
- //#region src/cli.ts
2450
- const subCommands = {
2451
- login: loginCommand,
2452
- logout: logoutCommand,
2453
- whoami: whoamiCommand,
2454
- connect: connectCommand,
2455
- continue: continueCommand,
2456
- doctor: doctorCommand,
2457
- env: envCommand,
2458
- investigate: investigateCommand,
2459
- runs: runsCommand,
2460
- setup: setupCommand
2461
- };
2462
- const subCommandAliases = new Set([
2463
- "ask",
2464
- "me",
2465
- "resume"
2466
- ]);
2467
- const cli = defineCommand({
2468
- meta: {
2469
- name: "quire",
2470
- version: version$1,
2471
- description: "Triage and investigation specialist for software teams and coding agents."
2472
- },
2473
- subCommands,
2474
- async run({ cmd, rawArgs }) {
2475
- if (rawArgs.length === 0) await showUsage(cmd);
2476
- }
2477
- });
2478
- const runMain = createMain(cli);
2479
- async function main(rawArgs = process.argv.slice(2)) {
2480
- const commandArgs = normalizeRawArgs(rawArgs);
2481
- if (shouldUseCittyBuiltinHandling(commandArgs)) {
2482
- await runMain({ rawArgs: commandArgs });
2483
- return;
2484
- }
2485
- try {
2486
- await runCommand(cli, { rawArgs: commandArgs });
2487
- } catch (error) {
2488
- writeCliError(error, commandArgs);
2489
- }
2490
- }
2491
- function shouldUseCittyBuiltinHandling(rawArgs) {
2492
- return rawArgs.length === 0 || rawArgs.some((arg) => arg === "--help" || arg === "-h") || isVersionRequest(rawArgs);
2493
- }
2494
- function isVersionRequest(rawArgs) {
2495
- return rawArgs.length === 1 && (rawArgs[0] === "--version" || rawArgs[0] === "-v");
2496
- }
2497
- function normalizeRawArgs(rawArgs) {
2498
- const first = rawArgs[0];
2499
- if (first === void 0 || first.startsWith("-") || isSubCommandName(first)) return rawArgs;
2500
- return ["investigate", ...rawArgs];
2501
- }
2502
- function isSubCommandName(name) {
2503
- return Object.hasOwn(subCommands, name) || subCommandAliases.has(name);
2504
- }
2505
- function writeCliError(error, rawArgs) {
2506
- const message = toOneLineError(error);
2507
- const exitCode = readExitCode(error);
2508
- if (wantsJsonError(rawArgs)) console.error(JSON.stringify({ error: {
2509
- message,
2510
- exitCode
2511
- } }));
2512
- else console.error(`${color.error("Error")}: ${message}`);
2513
- process.exitCode = exitCode;
2514
- }
2515
- function wantsJsonError(rawArgs) {
2516
- return rawArgs.includes("--json");
2517
- }
2518
- //#endregion
2519
- export { ExitCode as i, main as n, CliError as r, cli as t };