@quireco/cli 0.0.2 → 0.0.3

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,31 +1,95 @@
1
- import { n as __require, r as __toESM, t as __commonJSMin } from "./chunk-e9Ob2GDo.mjs";
1
+ import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-Vs_PY4HZ.mjs";
2
+ import { createRequire } from "node:module";
2
3
  import { defineCommand, parseArgs, renderUsage, runCommand } from "citty";
3
- import { closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, watch, writeFileSync } from "node:fs";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
6
+ import { AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, createAgentSession, defineTool, getAgentDir } from "@earendil-works/pi-coding-agent";
7
+ import { exec, execFile, execFileSync, spawn } from "node:child_process";
8
+ import os, { homedir, networkInterfaces, tmpdir } from "node:os";
4
9
  import { basename, delimiter, dirname, extname, join, relative, resolve, sep } from "node:path";
10
+ import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
11
+ import { convertMessages } from "@earendil-works/pi-ai/openai-completions";
12
+ import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
13
+ import { closeSync, constants as constants$1, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, watch, writeFileSync } from "node:fs";
5
14
  import { Type } from "@sinclair/typebox";
6
15
  import { Value } from "@sinclair/typebox/value";
7
- import { exec, execFile, execFileSync, spawn } from "node:child_process";
8
- import { access, constants as constants$1, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
9
16
  import { promisify } from "node:util";
10
- import { homedir, networkInterfaces, tmpdir } from "node:os";
11
17
  import { createHash, randomBytes } from "node:crypto";
12
18
  import * as net from "net";
13
19
  import { promisify as promisify$1 } from "util";
14
- import os from "os";
20
+ import process$1 from "node:process";
21
+ import tty from "node:tty";
15
22
  import { createServer } from "node:http";
16
23
  import { pathToFileURL } from "node:url";
17
24
  import path, { basename as basename$1 } from "path";
18
25
  import * as crypto$1 from "crypto";
19
26
  import crypto, { createHash as createHash$1 } from "crypto";
20
27
  import fs, { promises } from "fs";
28
+ import os$1 from "os";
21
29
  import { Readable } from "stream";
22
30
  import * as zlib$1 from "zlib";
23
31
  import { execFile as execFile$1 } from "child_process";
24
32
  import { EventEmitter } from "events";
25
33
  import { pipeline } from "stream/promises";
26
- import { AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, createAgentSession, defineTool, getAgentDir } from "@mariozechner/pi-coding-agent";
27
- import { calculateCost, createAssistantMessageEventStream } from "@mariozechner/pi-ai";
28
- import { convertMessages } from "@mariozechner/pi-ai/openai-completions";
34
+ //#region src/auth/open-browser.ts
35
+ async function openBrowser(url) {
36
+ const command = browserCommand(url);
37
+ if (command === null) return false;
38
+ return new Promise((resolve) => {
39
+ const child = spawn(command.command, command.args, {
40
+ detached: true,
41
+ stdio: "ignore",
42
+ shell: false
43
+ });
44
+ child.once("error", () => resolve(false));
45
+ child.once("spawn", () => {
46
+ child.unref();
47
+ resolve(true);
48
+ });
49
+ });
50
+ }
51
+ function browserCommand(url) {
52
+ if (process.platform === "darwin") return {
53
+ command: "open",
54
+ args: [url]
55
+ };
56
+ if (process.platform === "win32") return {
57
+ command: "cmd",
58
+ args: [
59
+ "/c",
60
+ "start",
61
+ "",
62
+ url
63
+ ]
64
+ };
65
+ return {
66
+ command: "xdg-open",
67
+ args: [url]
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region src/exit-codes.ts
72
+ const ExitCode = {
73
+ Success: 0,
74
+ InternalError: 1,
75
+ InvalidInput: 2,
76
+ AuthFailure: 10,
77
+ NetworkFailure: 11
78
+ };
79
+ var CliError$1 = class extends Error {
80
+ constructor(message, exitCode = ExitCode.InternalError) {
81
+ super(message);
82
+ this.exitCode = exitCode;
83
+ this.name = "CliError";
84
+ }
85
+ };
86
+ function readExitCode(error) {
87
+ return error instanceof CliError$1 ? error.exitCode : ExitCode.InternalError;
88
+ }
89
+ function toOneLineError(error) {
90
+ return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim() || "unknown error";
91
+ }
92
+ //#endregion
29
93
  //#region src/output.ts
30
94
  function writeLine(output, message = "") {
31
95
  output?.write(`${message}\n`);
@@ -34,6 +98,885 @@ function writeJson(output, value) {
34
98
  writeLine(output, JSON.stringify(value));
35
99
  }
36
100
  //#endregion
101
+ //#region src/auth/constants.ts
102
+ const QUIRE_API_TOKEN_ENV = "QUIRE_API_TOKEN";
103
+ const QUIRE_API_TOKEN_TYPE = "QuireApiKey";
104
+ //#endregion
105
+ //#region src/auth/api.ts
106
+ const CLIENT_ID = "quire-cli";
107
+ const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
108
+ var HttpError = class extends CliError$1 {
109
+ constructor(message, status, body, code) {
110
+ super(message, status === 401 || status === 403 ? ExitCode.AuthFailure : ExitCode.NetworkFailure);
111
+ this.status = status;
112
+ this.body = body;
113
+ this.code = code;
114
+ this.name = "HttpError";
115
+ }
116
+ };
117
+ function readApiBaseUrl(value = process.env.QUIRE_API_URL ?? "https://quire.sh") {
118
+ return normalizeApiBaseUrl(value);
119
+ }
120
+ function normalizeApiBaseUrl(value) {
121
+ try {
122
+ const url = new URL(value);
123
+ url.pathname = url.pathname.replace(/\/+$/, "");
124
+ url.search = "";
125
+ url.hash = "";
126
+ return url.toString().replace(/\/$/, "");
127
+ } catch {
128
+ throw new CliError$1(`Invalid Quire API URL: ${value}`, ExitCode.InvalidInput);
129
+ }
130
+ }
131
+ async function requestDeviceCode(options) {
132
+ return parseDeviceCodeResponse((await requestJson$1({
133
+ url: authUrl(options.apiBaseUrl, "/device/code"),
134
+ fetchFn: options.fetchFn,
135
+ init: {
136
+ method: "POST",
137
+ headers: jsonHeaders(),
138
+ body: JSON.stringify({
139
+ client_id: CLIENT_ID,
140
+ ...options.scope === void 0 ? {} : { scope: options.scope }
141
+ })
142
+ }
143
+ })).body);
144
+ }
145
+ async function requestDeviceToken(options) {
146
+ return parseDeviceTokenResponse((await requestJson$1({
147
+ url: authUrl(options.apiBaseUrl, "/device/token"),
148
+ fetchFn: options.fetchFn,
149
+ init: {
150
+ method: "POST",
151
+ headers: jsonHeaders(),
152
+ body: JSON.stringify({
153
+ grant_type: DEVICE_GRANT_TYPE,
154
+ device_code: options.deviceCode,
155
+ client_id: CLIENT_ID
156
+ })
157
+ }
158
+ })).body);
159
+ }
160
+ async function fetchCurrentSession(options) {
161
+ const response = await requestJson$1({
162
+ url: authUrl(options.apiBaseUrl, "/get-session"),
163
+ fetchFn: options.fetchFn,
164
+ init: {
165
+ method: "GET",
166
+ headers: {
167
+ Accept: "application/json",
168
+ ...authHeaders(options)
169
+ }
170
+ }
171
+ });
172
+ const refreshedAccessToken = response.response.headers.get("set-auth-token") ?? void 0;
173
+ return {
174
+ data: parseCurrentSession(response.body),
175
+ ...refreshedAccessToken === void 0 ? {} : { refreshedAccessToken }
176
+ };
177
+ }
178
+ async function fetchModelSourceBrokerStatus(options) {
179
+ return parseModelSourceBrokerStatus((await requestJson$1({
180
+ url: apiUrl(options.apiBaseUrl, "/api/model-sources"),
181
+ fetchFn: options.fetchFn,
182
+ init: {
183
+ method: "GET",
184
+ headers: {
185
+ Accept: "application/json",
186
+ ...authHeaders(options),
187
+ "User-Agent": "quire-cli"
188
+ }
189
+ }
190
+ })).body);
191
+ }
192
+ function authUrl(apiBaseUrl, path) {
193
+ return apiUrl(apiBaseUrl, `/api/auth${path}`);
194
+ }
195
+ function apiUrl(apiBaseUrl, path) {
196
+ return new URL(path, apiBaseUrl).toString();
197
+ }
198
+ function jsonHeaders() {
199
+ return {
200
+ Accept: "application/json",
201
+ "Content-Type": "application/json",
202
+ "User-Agent": "quire-cli"
203
+ };
204
+ }
205
+ function authHeaders(credentials) {
206
+ if (credentials.tokenType === "QuireApiKey") return { "X-Quire-API-Key": credentials.accessToken };
207
+ return { Authorization: `Bearer ${credentials.accessToken}` };
208
+ }
209
+ async function requestJson$1(options) {
210
+ let response;
211
+ try {
212
+ response = await (options.fetchFn ?? fetch)(options.url, options.init);
213
+ } catch (error) {
214
+ throw new CliError$1(`Could not reach Quire API: ${error instanceof Error ? error.message : String(error)}`, ExitCode.NetworkFailure);
215
+ }
216
+ const body = await readResponseBody(response);
217
+ if (!response.ok) {
218
+ const { code, description } = readErrorBody(body);
219
+ throw new HttpError(description ?? `Quire API request failed with HTTP ${response.status}`, response.status, body, code);
220
+ }
221
+ return {
222
+ body,
223
+ response
224
+ };
225
+ }
226
+ async function readResponseBody(response) {
227
+ const text = await response.text();
228
+ if (!text) return null;
229
+ try {
230
+ return JSON.parse(text);
231
+ } catch {
232
+ return text;
233
+ }
234
+ }
235
+ function readErrorBody(body) {
236
+ if (!isRecord$21(body)) return {};
237
+ return {
238
+ code: typeof body.error === "string" ? body.error : void 0,
239
+ description: typeof body.error_description === "string" ? body.error_description : typeof body.message === "string" ? body.message : void 0
240
+ };
241
+ }
242
+ function parseDeviceCodeResponse(value) {
243
+ if (!isRecord$21(value)) throw new CliError$1("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
244
+ const response = {
245
+ device_code: value.device_code,
246
+ user_code: value.user_code,
247
+ verification_uri: value.verification_uri,
248
+ verification_uri_complete: value.verification_uri_complete,
249
+ expires_in: value.expires_in,
250
+ interval: value.interval
251
+ };
252
+ 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$1("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
253
+ return response;
254
+ }
255
+ function parseDeviceTokenResponse(value) {
256
+ if (!isRecord$21(value) || typeof value.access_token !== "string") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
257
+ if (value.expires_in !== void 0 && typeof value.expires_in !== "number") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
258
+ if (value.scope !== void 0 && typeof value.scope !== "string") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
259
+ return {
260
+ access_token: value.access_token,
261
+ token_type: typeof value.token_type === "string" ? value.token_type : "Bearer",
262
+ ...value.expires_in === void 0 ? {} : { expires_in: value.expires_in },
263
+ ...value.scope === void 0 ? {} : { scope: value.scope }
264
+ };
265
+ }
266
+ function parseCurrentSession(value) {
267
+ if (value === null) return null;
268
+ if (!isRecord$21(value) || !isRecord$21(value.session) || !isRecord$21(value.user)) throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
269
+ if (typeof value.session.id !== "string" || typeof value.user.id !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
270
+ if (value.user.email !== void 0 && value.user.email !== null && typeof value.user.email !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
271
+ if (value.user.name !== void 0 && value.user.name !== null && typeof value.user.name !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
272
+ return {
273
+ session: {
274
+ id: value.session.id,
275
+ ...typeof value.session.expiresAt === "string" ? { expiresAt: value.session.expiresAt } : {}
276
+ },
277
+ user: {
278
+ id: value.user.id,
279
+ ...value.user.email === void 0 ? {} : { email: value.user.email },
280
+ ...value.user.name === void 0 ? {} : { name: value.user.name }
281
+ }
282
+ };
283
+ }
284
+ function parseModelSourceBrokerStatus(value) {
285
+ if (!isRecord$21(value) || !Array.isArray(value.selectedOrder)) throw new CliError$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
286
+ const resolvedAvailability = value.resolvedAvailability;
287
+ if (!isRecord$21(resolvedAvailability)) throw new CliError$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
288
+ const status = resolvedAvailability.status;
289
+ const reason = resolvedAvailability.reason;
290
+ const selectedProviderMode = resolvedAvailability.mode;
291
+ const requiredNextAction = resolvedAvailability.nextAction;
292
+ 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$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
293
+ const actor = readActor(value);
294
+ return {
295
+ ...actor === void 0 ? {} : { actor },
296
+ available: status === "available",
297
+ status,
298
+ selectedProviderMode,
299
+ selectedOrder: value.selectedOrder,
300
+ reason,
301
+ requiredNextAction,
302
+ requiredBalance: readWalletRequiredBalance(value),
303
+ remainingBalance: readWalletRemainingBalance(value),
304
+ recentUsage: readRecentUsage(value)
305
+ };
306
+ }
307
+ function readActor(value) {
308
+ const actor = value.actor;
309
+ if (!isRecord$21(actor)) return;
310
+ const actorType = actor.actorType;
311
+ if (actorType !== "user_session" && actorType !== "api_key") return;
312
+ return {
313
+ actorType,
314
+ actorId: typeof actor.actorId === "string" ? actor.actorId : null,
315
+ environmentId: typeof actor.environmentId === "string" ? actor.environmentId : null,
316
+ environmentName: typeof actor.environmentName === "string" ? actor.environmentName : null,
317
+ triggerSource: typeof actor.triggerSource === "string" ? actor.triggerSource : null
318
+ };
319
+ }
320
+ function isRecord$21(value) {
321
+ return typeof value === "object" && value !== null && !Array.isArray(value);
322
+ }
323
+ function readWalletRequiredBalance(value) {
324
+ const source = readFirstSource(value);
325
+ const requiredBalance = (isRecord$21(source?.wallet) ? source.wallet : null)?.requiredBalance;
326
+ return typeof requiredBalance === "number" && Number.isFinite(requiredBalance) ? requiredBalance : null;
327
+ }
328
+ function readWalletRemainingBalance(value) {
329
+ const source = readFirstSource(value);
330
+ const wallet = isRecord$21(source?.wallet) ? source.wallet : null;
331
+ const remaining = (isRecord$21(wallet?.balance) ? wallet.balance : null)?.remaining;
332
+ return typeof remaining === "number" && Number.isFinite(remaining) ? remaining : null;
333
+ }
334
+ function readFirstSource(value) {
335
+ if (!Array.isArray(value.sources)) return null;
336
+ const [source] = value.sources;
337
+ return isRecord$21(source) ? source : null;
338
+ }
339
+ function readRecentUsage(value) {
340
+ if (!Array.isArray(value.recentUsage)) return [];
341
+ return value.recentUsage.flatMap((event) => {
342
+ if (!isRecord$21(event)) return [];
343
+ if (typeof event.modelId !== "string" || typeof event.status !== "string" || typeof event.totalTokens !== "number" || typeof event.createdAt !== "string") return [];
344
+ return [{
345
+ modelId: event.modelId,
346
+ status: event.status,
347
+ totalTokens: event.totalTokens,
348
+ runId: typeof event.runId === "string" ? event.runId : null,
349
+ createdAt: event.createdAt
350
+ }];
351
+ });
352
+ }
353
+ //#endregion
354
+ //#region src/auth/store.ts
355
+ function defaultAuthPath(env = process.env) {
356
+ if (env.QUIRE_AUTH_FILE) return resolve(env.QUIRE_AUTH_FILE);
357
+ return join(resolve(env.QUIRE_HOME ?? join(homedir(), ".quire")), "auth.json");
358
+ }
359
+ function createAuthStore(path = defaultAuthPath()) {
360
+ async function get() {
361
+ try {
362
+ const contents = await readFile(path, "utf8");
363
+ return parseStoredCredentials(JSON.parse(contents));
364
+ } catch {
365
+ return null;
366
+ }
367
+ }
368
+ async function set(credentials) {
369
+ await mkdir(dirname(path), {
370
+ recursive: true,
371
+ mode: 448
372
+ });
373
+ await writeFile(path, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 384 });
374
+ }
375
+ async function clear() {
376
+ try {
377
+ await unlink(path);
378
+ } catch {}
379
+ }
380
+ return {
381
+ path,
382
+ get,
383
+ set,
384
+ clear
385
+ };
386
+ }
387
+ const authStore = createAuthStore();
388
+ async function resolveAuthCredentials(options = {}) {
389
+ const env = options.env ?? process.env;
390
+ const apiToken = env[QUIRE_API_TOKEN_ENV]?.trim();
391
+ if (apiToken) return createStoredCredentials({
392
+ apiBaseUrl: readApiBaseUrl(env.QUIRE_API_URL),
393
+ accessToken: apiToken,
394
+ tokenType: QUIRE_API_TOKEN_TYPE,
395
+ user: {
396
+ id: "api-token",
397
+ name: "API token"
398
+ }
399
+ });
400
+ return (options.store ?? authStore).get();
401
+ }
402
+ function createStoredCredentials(input) {
403
+ const now = input.now ?? /* @__PURE__ */ new Date();
404
+ const timestamp = now.toISOString();
405
+ const expiresAt = input.expiresAt ?? expiresAtFromSeconds(input.expiresIn, now);
406
+ return {
407
+ version: 1,
408
+ apiBaseUrl: input.apiBaseUrl,
409
+ accessToken: input.accessToken,
410
+ tokenType: input.tokenType ?? "Bearer",
411
+ ...expiresAt === void 0 ? {} : { expiresAt },
412
+ ...input.user === void 0 ? {} : { user: input.user },
413
+ createdAt: timestamp,
414
+ updatedAt: timestamp
415
+ };
416
+ }
417
+ function updateStoredCredentials(credentials, input) {
418
+ return {
419
+ ...credentials,
420
+ accessToken: input.accessToken ?? credentials.accessToken,
421
+ tokenType: input.tokenType ?? credentials.tokenType,
422
+ ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt },
423
+ ...input.user === void 0 ? {} : { user: input.user },
424
+ updatedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
425
+ };
426
+ }
427
+ function expiresAtFromSeconds(expiresIn, now) {
428
+ return typeof expiresIn === "number" ? new Date(now.getTime() + expiresIn * 1e3).toISOString() : void 0;
429
+ }
430
+ function parseStoredCredentials(value) {
431
+ if (!isRecord$20(value)) return null;
432
+ 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;
433
+ if (value.expiresAt !== void 0 && typeof value.expiresAt !== "string") return null;
434
+ const user = parseStoredUser(value.user);
435
+ if (value.user !== void 0 && user === null) return null;
436
+ return {
437
+ version: 1,
438
+ apiBaseUrl: value.apiBaseUrl,
439
+ accessToken: value.accessToken,
440
+ tokenType: value.tokenType,
441
+ ...value.expiresAt === void 0 ? {} : { expiresAt: value.expiresAt },
442
+ ...user === void 0 || user === null ? {} : { user },
443
+ createdAt: value.createdAt,
444
+ updatedAt: value.updatedAt
445
+ };
446
+ }
447
+ function parseStoredUser(value) {
448
+ if (value === void 0) return;
449
+ if (!isRecord$20(value) || typeof value.id !== "string") return null;
450
+ if (value.email !== void 0 && value.email !== null && typeof value.email !== "string") return null;
451
+ if (value.name !== void 0 && value.name !== null && typeof value.name !== "string") return null;
452
+ return {
453
+ id: value.id,
454
+ ...value.email === void 0 ? {} : { email: value.email },
455
+ ...value.name === void 0 ? {} : { name: value.name }
456
+ };
457
+ }
458
+ function isRecord$20(value) {
459
+ return typeof value === "object" && value !== null && !Array.isArray(value);
460
+ }
461
+ //#endregion
462
+ //#region src/pipeline/quire-model-provider.ts
463
+ const QUIRE_MODEL_PROVIDER = "quire";
464
+ const QUIRE_MODEL_ID = "gpt-5.1";
465
+ const PI_CODEX_MODEL_PROVIDER = "openai-codex";
466
+ const PI_CODEX_MODEL_ID = "gpt-5.5";
467
+ const QUIRE_MODEL_API = "quire-chat-completions";
468
+ const QUIRE_RUNTIME_AUTH_ENV = "QUIRE_RUNTIME_AUTH_TOKEN";
469
+ const QUIRE_MODEL_AUTH_PATH_ENV = "QUIRE_MODEL_AUTH_PATH";
470
+ const openAiCompletionCompat = {
471
+ supportsStore: false,
472
+ supportsDeveloperRole: false,
473
+ supportsReasoningEffort: true,
474
+ supportsUsageInStreaming: false,
475
+ maxTokensField: "max_completion_tokens",
476
+ requiresToolResultName: false,
477
+ requiresAssistantAfterToolResult: false,
478
+ requiresThinkingAsText: false,
479
+ requiresReasoningContentOnAssistantMessages: false,
480
+ thinkingFormat: "openai",
481
+ zaiToolStream: false,
482
+ supportsStrictMode: true,
483
+ sendSessionAffinityHeaders: false,
484
+ supportsLongCacheRetention: true
485
+ };
486
+ async function createQuireModelSessionDependencies({ runId, store = authStore, fetchFn }) {
487
+ const credentials = await readValidatedQuireCredentials({
488
+ store,
489
+ fetchFn
490
+ });
491
+ const authStorage = AuthStorage.inMemory();
492
+ authStorage.setRuntimeApiKey(QUIRE_MODEL_PROVIDER, credentials.accessToken);
493
+ const modelRegistry = ModelRegistry.inMemory(authStorage);
494
+ modelRegistry.registerProvider(QUIRE_MODEL_PROVIDER, {
495
+ name: "Quire Credits",
496
+ baseUrl: credentials.apiBaseUrl,
497
+ apiKey: QUIRE_RUNTIME_AUTH_ENV,
498
+ api: QUIRE_MODEL_API,
499
+ models: [quireModelDefinition()],
500
+ streamSimple: createQuireCreditsStream({
501
+ apiBaseUrl: credentials.apiBaseUrl,
502
+ tokenType: credentials.tokenType,
503
+ runId,
504
+ fetchFn
505
+ })
506
+ });
507
+ const model = modelRegistry.find(QUIRE_MODEL_PROVIDER, QUIRE_MODEL_ID);
508
+ if (!model) throw new Error(`Quire model not found: ${QUIRE_MODEL_PROVIDER}/${QUIRE_MODEL_ID}`);
509
+ return {
510
+ authStorage,
511
+ modelRegistry,
512
+ model,
513
+ source: "quire_credits"
514
+ };
515
+ }
516
+ async function createInvestigationModelSessionDependencies({ runId, store = authStore, fetchFn, localAuthStorage }) {
517
+ const localCodex = await createLocalCodexSessionDependencies(localAuthStorage);
518
+ if (localCodex) return localCodex;
519
+ return createQuireModelSessionDependencies({
520
+ runId,
521
+ store,
522
+ fetchFn
523
+ });
524
+ }
525
+ async function createLocalCodexSessionDependencies(localAuthStorage) {
526
+ const authStorages = localAuthStorage === void 0 ? [AuthStorage.create(readQuireModelAuthPath()), AuthStorage.create()] : [localAuthStorage];
527
+ for (const authStorage of authStorages) {
528
+ const modelRegistry = ModelRegistry.create(authStorage);
529
+ const model = modelRegistry.find(PI_CODEX_MODEL_PROVIDER, PI_CODEX_MODEL_ID);
530
+ if (!model || !modelRegistry.hasConfiguredAuth(model)) continue;
531
+ const auth = await modelRegistry.getApiKeyAndHeaders(model);
532
+ if (!auth.ok || !auth.apiKey && !auth.headers) continue;
533
+ return {
534
+ authStorage,
535
+ modelRegistry,
536
+ model,
537
+ source: "local_openai_codex"
538
+ };
539
+ }
540
+ return null;
541
+ }
542
+ function readQuireModelAuthPath() {
543
+ const envPath = process.env[QUIRE_MODEL_AUTH_PATH_ENV]?.trim();
544
+ return envPath && envPath.length > 0 ? envPath : join(homedir(), ".quire", "model-auth.json");
545
+ }
546
+ function quireModelDefinition() {
547
+ return {
548
+ id: QUIRE_MODEL_ID,
549
+ name: "Quire Credits GPT-5.1",
550
+ api: QUIRE_MODEL_API,
551
+ reasoning: true,
552
+ thinkingLevelMap: {
553
+ off: null,
554
+ xhigh: "high"
555
+ },
556
+ input: ["text"],
557
+ cost: {
558
+ input: 1.25,
559
+ output: 10,
560
+ cacheRead: .125,
561
+ cacheWrite: 0
562
+ },
563
+ contextWindow: 128e3,
564
+ maxTokens: 32e3
565
+ };
566
+ }
567
+ function createQuireCreditsStream(options) {
568
+ return (model, context, streamOptions) => {
569
+ const stream = createAssistantMessageEventStream();
570
+ (async () => {
571
+ const output = createEmptyAssistantMessage(model);
572
+ try {
573
+ const accessToken = streamOptions?.apiKey;
574
+ if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
575
+ stream.push({
576
+ type: "start",
577
+ partial: output
578
+ });
579
+ applyBrokerResponseToStream(stream, output, model, await requestBrokerChatCompletion({
580
+ apiBaseUrl: options.apiBaseUrl,
581
+ accessToken,
582
+ tokenType: options.tokenType,
583
+ runId: options.runId,
584
+ model,
585
+ context,
586
+ streamOptions,
587
+ fetchFn: options.fetchFn
588
+ }));
589
+ } catch (error) {
590
+ output.stopReason = streamOptions?.signal?.aborted ? "aborted" : "error";
591
+ output.errorMessage = error instanceof Error ? error.message : String(error);
592
+ stream.push({
593
+ type: "error",
594
+ reason: output.stopReason,
595
+ error: output
596
+ });
597
+ stream.end();
598
+ }
599
+ })();
600
+ return stream;
601
+ };
602
+ }
603
+ async function readValidatedQuireCredentials(options) {
604
+ const credentials = await resolveAuthCredentials({ store: options.store });
605
+ if (!credentials) throw new CliError$1("Not logged in. Run `quire login` or set QUIRE_API_TOKEN before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
606
+ if (credentials.tokenType === "QuireApiKey") return credentials;
607
+ const sessionLookup = await fetchCurrentSession({
608
+ apiBaseUrl: credentials.apiBaseUrl,
609
+ accessToken: credentials.accessToken,
610
+ tokenType: credentials.tokenType,
611
+ fetchFn: options.fetchFn
612
+ });
613
+ if (!sessionLookup.data) {
614
+ await options.store.clear();
615
+ throw new CliError$1("Stored Quire credentials are no longer valid. Run `quire login`.", ExitCode.AuthFailure);
616
+ }
617
+ if (!sessionLookup.refreshedAccessToken) return credentials;
618
+ const refreshedCredentials = updateStoredCredentials(credentials, {
619
+ accessToken: sessionLookup.refreshedAccessToken,
620
+ expiresAt: sessionLookup.data.session.expiresAt,
621
+ user: sessionLookup.data.user
622
+ });
623
+ await options.store.set(refreshedCredentials);
624
+ return refreshedCredentials;
625
+ }
626
+ async function requestBrokerChatCompletion(input) {
627
+ const response = await (input.fetchFn ?? fetch)(new URL("/api/model-broker/chat-completions", input.apiBaseUrl).toString(), {
628
+ method: "POST",
629
+ headers: {
630
+ Accept: "application/json",
631
+ ...authHeaders(input),
632
+ "Content-Type": "application/json",
633
+ "User-Agent": "quire-cli"
634
+ },
635
+ signal: input.streamOptions?.signal,
636
+ body: JSON.stringify({
637
+ model: input.model.id,
638
+ messages: toBrokerMessages(input.model, input.context),
639
+ runId: input.runId,
640
+ stream: false,
641
+ agentContext: {
642
+ messages: [],
643
+ tools: toBrokerTools(input.context.tools)
644
+ },
645
+ agentOptions: {
646
+ reasoning: normalizeReasoning(input.streamOptions?.reasoning),
647
+ sessionId: input.streamOptions?.sessionId ?? null
648
+ }
649
+ })
650
+ });
651
+ const body = await readJsonBody(response);
652
+ if (!response.ok || body?.status !== "succeeded") throw brokerResponseError(response, body);
653
+ const rawResponse = body.data?.response;
654
+ if (!rawResponse || typeof rawResponse !== "object" || Array.isArray(rawResponse)) throw new Error("Quire model broker returned an invalid model response.");
655
+ return rawResponse;
656
+ }
657
+ function toBrokerMessages(model, context) {
658
+ return convertMessages(model, context, openAiCompletionCompat).map((message) => {
659
+ const record = message;
660
+ const role = normalizeRole(record.role);
661
+ const toolCalls = parseBrokerToolCalls(record.tool_calls);
662
+ return {
663
+ role,
664
+ content: normalizeMessageContent(record.content),
665
+ ...typeof record.name === "string" ? { name: record.name } : {},
666
+ ...typeof record.tool_call_id === "string" ? { tool_call_id: record.tool_call_id } : {},
667
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
668
+ };
669
+ });
670
+ }
671
+ function toBrokerTools(tools) {
672
+ return (tools ?? []).map((tool) => ({
673
+ type: "function",
674
+ function: {
675
+ name: tool.name,
676
+ description: tool.description,
677
+ parameters: tool.parameters,
678
+ strict: false
679
+ }
680
+ }));
681
+ }
682
+ function applyBrokerResponseToStream(stream, output, model, response) {
683
+ output.responseId = typeof response.id === "string" ? response.id : void 0;
684
+ output.responseModel = typeof response.model === "string" ? response.model : void 0;
685
+ output.usage = readUsage(response.usage, model);
686
+ const choice = response.choices?.[0];
687
+ const message = choice?.message;
688
+ const content = typeof message?.content === "string" ? message.content : "";
689
+ if (content.length > 0) {
690
+ const textBlock = {
691
+ type: "text",
692
+ text: content
693
+ };
694
+ output.content.push(textBlock);
695
+ const contentIndex = output.content.length - 1;
696
+ stream.push({
697
+ type: "text_start",
698
+ contentIndex,
699
+ partial: output
700
+ });
701
+ stream.push({
702
+ type: "text_delta",
703
+ contentIndex,
704
+ delta: content,
705
+ partial: output
706
+ });
707
+ stream.push({
708
+ type: "text_end",
709
+ contentIndex,
710
+ content,
711
+ partial: output
712
+ });
713
+ }
714
+ const toolCalls = parseBrokerToolCalls(message?.tool_calls);
715
+ for (const toolCall of toolCalls) {
716
+ const block = {
717
+ type: "toolCall",
718
+ id: toolCall.id,
719
+ name: toolCall.function.name,
720
+ arguments: parseToolCallArguments(toolCall.function.arguments)
721
+ };
722
+ output.content.push(block);
723
+ const contentIndex = output.content.length - 1;
724
+ stream.push({
725
+ type: "toolcall_start",
726
+ contentIndex,
727
+ partial: output
728
+ });
729
+ stream.push({
730
+ type: "toolcall_delta",
731
+ contentIndex,
732
+ delta: toolCall.function.arguments,
733
+ partial: output
734
+ });
735
+ stream.push({
736
+ type: "toolcall_end",
737
+ contentIndex,
738
+ toolCall: block,
739
+ partial: output
740
+ });
741
+ }
742
+ output.stopReason = mapStopReason(choice?.finish_reason, toolCalls.length > 0);
743
+ stream.push({
744
+ type: "done",
745
+ reason: output.stopReason,
746
+ message: output
747
+ });
748
+ stream.end();
749
+ }
750
+ function createEmptyAssistantMessage(model) {
751
+ return {
752
+ role: "assistant",
753
+ content: [],
754
+ api: model.api,
755
+ provider: model.provider,
756
+ model: model.id,
757
+ usage: emptyUsage(),
758
+ stopReason: "stop",
759
+ timestamp: Date.now()
760
+ };
761
+ }
762
+ function emptyUsage() {
763
+ return {
764
+ input: 0,
765
+ output: 0,
766
+ cacheRead: 0,
767
+ cacheWrite: 0,
768
+ totalTokens: 0,
769
+ cost: {
770
+ input: 0,
771
+ output: 0,
772
+ cacheRead: 0,
773
+ cacheWrite: 0,
774
+ total: 0
775
+ }
776
+ };
777
+ }
778
+ function readUsage(rawUsage, model) {
779
+ const input = readNumber(rawUsage?.prompt_tokens ?? rawUsage?.promptTokens) ?? 0;
780
+ const output = readNumber(rawUsage?.completion_tokens ?? rawUsage?.completionTokens) ?? 0;
781
+ const usage = {
782
+ input,
783
+ output,
784
+ cacheRead: 0,
785
+ cacheWrite: 0,
786
+ totalTokens: readNumber(rawUsage?.total_tokens ?? rawUsage?.totalTokens) ?? input + output,
787
+ cost: {
788
+ input: 0,
789
+ output: 0,
790
+ cacheRead: 0,
791
+ cacheWrite: 0,
792
+ total: 0
793
+ }
794
+ };
795
+ calculateCost(model, usage);
796
+ return usage;
797
+ }
798
+ function mapStopReason(finishReason, hasToolCalls) {
799
+ if (finishReason === "length") return "length";
800
+ if (hasToolCalls || finishReason === "tool_calls" || finishReason === "function_call") return "toolUse";
801
+ return "stop";
802
+ }
803
+ function normalizeRole(value) {
804
+ if (value === "developer") return "system";
805
+ if (value === "system" || value === "user" || value === "assistant" || value === "tool") return value;
806
+ throw new Error(`Unsupported broker message role: ${String(value)}`);
807
+ }
808
+ function normalizeMessageContent(value) {
809
+ if (typeof value === "string") return value;
810
+ if (value === null || value === void 0) return "";
811
+ if (Array.isArray(value)) {
812
+ const text = value.map((part) => {
813
+ if (!part || typeof part !== "object" || Array.isArray(part)) return "";
814
+ const record = part;
815
+ return record.type === "text" && typeof record.text === "string" ? record.text : "[non-text content omitted]";
816
+ }).filter((part) => part.length > 0).join("\n");
817
+ return text.length > 0 ? text : "[non-text content omitted]";
818
+ }
819
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
820
+ return "[non-text content omitted]";
821
+ }
822
+ function parseBrokerToolCalls(value) {
823
+ if (!Array.isArray(value)) return [];
824
+ return value.flatMap((toolCall, index) => {
825
+ if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) return [];
826
+ const record = toolCall;
827
+ const fn = record.function;
828
+ if (!fn || typeof fn !== "object" || Array.isArray(fn)) return [];
829
+ const functionRecord = fn;
830
+ const name = typeof functionRecord.name === "string" ? functionRecord.name : "";
831
+ if (!name) return [];
832
+ return [{
833
+ id: typeof record.id === "string" ? record.id : `call_${index}`,
834
+ type: "function",
835
+ function: {
836
+ name,
837
+ arguments: typeof functionRecord.arguments === "string" ? functionRecord.arguments : "{}"
838
+ }
839
+ }];
840
+ });
841
+ }
842
+ function parseToolCallArguments(value) {
843
+ try {
844
+ const parsed = JSON.parse(value);
845
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
846
+ } catch {
847
+ return {};
848
+ }
849
+ }
850
+ async function readJsonBody(response) {
851
+ const text = await response.text();
852
+ if (!text) return null;
853
+ try {
854
+ return JSON.parse(text);
855
+ } catch {
856
+ return text;
857
+ }
858
+ }
859
+ function brokerResponseError(response, body) {
860
+ const code = body?.error?.code ?? `http_${response.status}`;
861
+ const message = body?.error?.message ?? `Quire model broker request failed with HTTP ${response.status}`;
862
+ const action = body?.error?.nextAction;
863
+ const billingDetail = formatBrokerBillingDetail(body);
864
+ return new Error([
865
+ `Quire model broker request failed (${code}): ${message}`,
866
+ billingDetail,
867
+ action === void 0 ? void 0 : `Next action: ${action}.`
868
+ ].filter((part) => part !== void 0 && part.length > 0).join(" "));
869
+ }
870
+ function normalizeReasoning(reasoning) {
871
+ return reasoning === "minimal" || reasoning === "low" || reasoning === "medium" || reasoning === "high" || reasoning === "xhigh" ? reasoning : void 0;
872
+ }
873
+ function readNumber(value) {
874
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
875
+ }
876
+ function formatBrokerBillingDetail(body) {
877
+ const requiredBalance = readNumber(body?.metadata?.wallet?.requiredBalance) ?? readNumber(body?.metadata?.billing?.wallet?.requiredBalance) ?? readNumber(body?.metadata?.billing?.wallet?.estimatedTotalTokens);
878
+ const remainingBalance = readNumber(body?.metadata?.wallet?.balance?.remaining);
879
+ if (requiredBalance === null && remainingBalance === null) return;
880
+ return `Wallet: ${[requiredBalance === null ? void 0 : `required=${requiredBalance}`, remainingBalance === null ? void 0 : `remaining=${remainingBalance}`].filter((part) => part !== void 0).join(", ")}.`;
881
+ }
882
+ //#endregion
883
+ //#region src/commands/connect.ts
884
+ const connectCommand = defineCommand({
885
+ meta: {
886
+ name: "connect",
887
+ description: "Connect local provider accounts used by Quire investigations."
888
+ },
889
+ subCommands: { chatgpt: defineCommand({
890
+ meta: {
891
+ name: "chatgpt",
892
+ description: "Connect ChatGPT/Codex subscription auth for local investigations."
893
+ },
894
+ args: {
895
+ "no-open": {
896
+ type: "boolean",
897
+ description: "Print the authorization URL without trying to open a browser."
898
+ },
899
+ json: {
900
+ type: "boolean",
901
+ description: "Print machine-readable connection state to stdout."
902
+ }
903
+ },
904
+ async run({ args }) {
905
+ await runConnectChatgpt({
906
+ noOpen: args["no-open"] === true,
907
+ json: args.json === true
908
+ });
909
+ }
910
+ }) }
911
+ });
912
+ async function runConnectChatgpt(options = {}) {
913
+ const stdout = options.io?.stdout ?? process.stdout;
914
+ const stderr = options.io?.stderr ?? process.stderr;
915
+ const modelAuthPath = options.modelAuthPath ?? readQuireModelAuthPath();
916
+ const modelAuthStorage = options.modelAuthStorage ?? AuthStorage.create(modelAuthPath);
917
+ const loginChatgpt = options.loginChatgpt ?? loginOpenAICodex;
918
+ if (options.json !== true) {
919
+ writeLine(stdout, "Connect ChatGPT for local Quire investigations");
920
+ writeLine(stdout, "Quire will store Pi-compatible OpenAI Codex auth locally and use it before Quire Credits.");
921
+ }
922
+ const credentials = await loginChatgpt({
923
+ originator: "quire",
924
+ onAuth: (info) => {
925
+ const authOutput = options.json === true ? stderr : stdout;
926
+ writeLine(authOutput, `Authorization URL: ${info.url}`);
927
+ if (info.instructions) writeLine(authOutput, info.instructions);
928
+ if (options.noOpen === true) return;
929
+ (async () => {
930
+ let opened = false;
931
+ try {
932
+ opened = await (options.opener ?? openBrowser)(info.url);
933
+ } catch {
934
+ opened = false;
935
+ }
936
+ writeLine(stderr, opened ? "Opened ChatGPT authorization in your browser." : "Could not open a browser automatically; open the authorization URL manually.");
937
+ })();
938
+ },
939
+ onPrompt: async (prompt) => {
940
+ const message = prompt.placeholder ? `${prompt.message} (${prompt.placeholder})` : prompt.message;
941
+ return (options.promptForLine ?? promptForLine)(message);
942
+ },
943
+ onProgress: (message) => {
944
+ writeLine(stderr, message);
945
+ }
946
+ });
947
+ modelAuthStorage.set(PI_CODEX_MODEL_PROVIDER, oauthCredential(credentials));
948
+ if (options.json === true) {
949
+ writeJson(stdout, {
950
+ connected: true,
951
+ provider: PI_CODEX_MODEL_PROVIDER,
952
+ authPath: modelAuthPath,
953
+ source: "quire_model_auth"
954
+ });
955
+ return;
956
+ }
957
+ writeLine(stdout, "ChatGPT connected for local Quire investigations.");
958
+ writeLine(stderr, `Credentials saved to ${modelAuthPath}.`);
959
+ }
960
+ function oauthCredential(credentials) {
961
+ return {
962
+ type: "oauth",
963
+ ...credentials
964
+ };
965
+ }
966
+ async function promptForLine(message) {
967
+ const rl = createInterface({
968
+ input: process.stdin,
969
+ output: process.stdout
970
+ });
971
+ try {
972
+ return await rl.question(`${message} `);
973
+ } catch (error) {
974
+ throw new CliError$1(`Could not read authorization code: ${error instanceof Error ? error.message : String(error)}`, ExitCode.InvalidInput);
975
+ } finally {
976
+ rl.close();
977
+ }
978
+ }
979
+ //#endregion
37
980
  //#region src/doctor/render.ts
38
981
  const SECTION_TITLES = {
39
982
  identity: "Identity",
@@ -152,352 +1095,6 @@ function normalizeFieldPath(path) {
152
1095
  return path;
153
1096
  }
154
1097
  //#endregion
155
- //#region src/exit-codes.ts
156
- const ExitCode = {
157
- Success: 0,
158
- InternalError: 1,
159
- InvalidInput: 2,
160
- AuthFailure: 10,
161
- NetworkFailure: 11
162
- };
163
- var CliError$1 = class extends Error {
164
- constructor(message, exitCode = ExitCode.InternalError) {
165
- super(message);
166
- this.exitCode = exitCode;
167
- this.name = "CliError";
168
- }
169
- };
170
- function readExitCode(error) {
171
- return error instanceof CliError$1 ? error.exitCode : ExitCode.InternalError;
172
- }
173
- function toOneLineError(error) {
174
- return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim() || "unknown error";
175
- }
176
- //#endregion
177
- //#region src/auth/api.ts
178
- const CLIENT_ID = "quire-cli";
179
- const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
180
- var HttpError = class extends CliError$1 {
181
- constructor(message, status, body, code) {
182
- super(message, status === 401 || status === 403 ? ExitCode.AuthFailure : ExitCode.NetworkFailure);
183
- this.status = status;
184
- this.body = body;
185
- this.code = code;
186
- this.name = "HttpError";
187
- }
188
- };
189
- function readApiBaseUrl(value = process.env.QUIRE_API_URL ?? "https://quire.sh") {
190
- return normalizeApiBaseUrl(value);
191
- }
192
- function normalizeApiBaseUrl(value) {
193
- try {
194
- const url = new URL(value);
195
- url.pathname = url.pathname.replace(/\/+$/, "");
196
- url.search = "";
197
- url.hash = "";
198
- return url.toString().replace(/\/$/, "");
199
- } catch {
200
- throw new CliError$1(`Invalid Quire API URL: ${value}`, ExitCode.InvalidInput);
201
- }
202
- }
203
- async function requestDeviceCode(options) {
204
- return parseDeviceCodeResponse((await requestJson$1({
205
- url: authUrl(options.apiBaseUrl, "/device/code"),
206
- fetchFn: options.fetchFn,
207
- init: {
208
- method: "POST",
209
- headers: jsonHeaders(),
210
- body: JSON.stringify({
211
- client_id: CLIENT_ID,
212
- ...options.scope === void 0 ? {} : { scope: options.scope }
213
- })
214
- }
215
- })).body);
216
- }
217
- async function requestDeviceToken(options) {
218
- return parseDeviceTokenResponse((await requestJson$1({
219
- url: authUrl(options.apiBaseUrl, "/device/token"),
220
- fetchFn: options.fetchFn,
221
- init: {
222
- method: "POST",
223
- headers: jsonHeaders(),
224
- body: JSON.stringify({
225
- grant_type: DEVICE_GRANT_TYPE,
226
- device_code: options.deviceCode,
227
- client_id: CLIENT_ID
228
- })
229
- }
230
- })).body);
231
- }
232
- async function fetchCurrentSession(options) {
233
- const response = await requestJson$1({
234
- url: authUrl(options.apiBaseUrl, "/get-session"),
235
- fetchFn: options.fetchFn,
236
- init: {
237
- method: "GET",
238
- headers: {
239
- Accept: "application/json",
240
- Authorization: `Bearer ${options.accessToken}`
241
- }
242
- }
243
- });
244
- const refreshedAccessToken = response.response.headers.get("set-auth-token") ?? void 0;
245
- return {
246
- data: parseCurrentSession(response.body),
247
- ...refreshedAccessToken === void 0 ? {} : { refreshedAccessToken }
248
- };
249
- }
250
- async function fetchModelSourceBrokerStatus(options) {
251
- return parseModelSourceBrokerStatus((await requestJson$1({
252
- url: apiUrl(options.apiBaseUrl, "/api/model-sources"),
253
- fetchFn: options.fetchFn,
254
- init: {
255
- method: "GET",
256
- headers: {
257
- Accept: "application/json",
258
- Authorization: `Bearer ${options.accessToken}`,
259
- "User-Agent": "quire-cli"
260
- }
261
- }
262
- })).body);
263
- }
264
- function authUrl(apiBaseUrl, path) {
265
- return apiUrl(apiBaseUrl, `/api/auth${path}`);
266
- }
267
- function apiUrl(apiBaseUrl, path) {
268
- return new URL(path, apiBaseUrl).toString();
269
- }
270
- function jsonHeaders() {
271
- return {
272
- Accept: "application/json",
273
- "Content-Type": "application/json",
274
- "User-Agent": "quire-cli"
275
- };
276
- }
277
- async function requestJson$1(options) {
278
- let response;
279
- try {
280
- response = await (options.fetchFn ?? fetch)(options.url, options.init);
281
- } catch (error) {
282
- throw new CliError$1(`Could not reach Quire API: ${error instanceof Error ? error.message : String(error)}`, ExitCode.NetworkFailure);
283
- }
284
- const body = await readResponseBody(response);
285
- if (!response.ok) {
286
- const { code, description } = readErrorBody(body);
287
- throw new HttpError(description ?? `Quire API request failed with HTTP ${response.status}`, response.status, body, code);
288
- }
289
- return {
290
- body,
291
- response
292
- };
293
- }
294
- async function readResponseBody(response) {
295
- const text = await response.text();
296
- if (!text) return null;
297
- try {
298
- return JSON.parse(text);
299
- } catch {
300
- return text;
301
- }
302
- }
303
- function readErrorBody(body) {
304
- if (!isRecord$21(body)) return {};
305
- return {
306
- code: typeof body.error === "string" ? body.error : void 0,
307
- description: typeof body.error_description === "string" ? body.error_description : typeof body.message === "string" ? body.message : void 0
308
- };
309
- }
310
- function parseDeviceCodeResponse(value) {
311
- if (!isRecord$21(value)) throw new CliError$1("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
312
- const response = {
313
- device_code: value.device_code,
314
- user_code: value.user_code,
315
- verification_uri: value.verification_uri,
316
- verification_uri_complete: value.verification_uri_complete,
317
- expires_in: value.expires_in,
318
- interval: value.interval
319
- };
320
- 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$1("Quire API returned an invalid device code response", ExitCode.NetworkFailure);
321
- return response;
322
- }
323
- function parseDeviceTokenResponse(value) {
324
- if (!isRecord$21(value) || typeof value.access_token !== "string") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
325
- if (value.expires_in !== void 0 && typeof value.expires_in !== "number") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
326
- if (value.scope !== void 0 && typeof value.scope !== "string") throw new CliError$1("Quire API returned an invalid device token response", ExitCode.NetworkFailure);
327
- return {
328
- access_token: value.access_token,
329
- token_type: typeof value.token_type === "string" ? value.token_type : "Bearer",
330
- ...value.expires_in === void 0 ? {} : { expires_in: value.expires_in },
331
- ...value.scope === void 0 ? {} : { scope: value.scope }
332
- };
333
- }
334
- function parseCurrentSession(value) {
335
- if (value === null) return null;
336
- if (!isRecord$21(value) || !isRecord$21(value.session) || !isRecord$21(value.user)) throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
337
- if (typeof value.session.id !== "string" || typeof value.user.id !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
338
- if (value.user.email !== void 0 && value.user.email !== null && typeof value.user.email !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
339
- if (value.user.name !== void 0 && value.user.name !== null && typeof value.user.name !== "string") throw new CliError$1("Quire API returned an invalid session response", ExitCode.NetworkFailure);
340
- return {
341
- session: {
342
- id: value.session.id,
343
- ...typeof value.session.expiresAt === "string" ? { expiresAt: value.session.expiresAt } : {}
344
- },
345
- user: {
346
- id: value.user.id,
347
- ...value.user.email === void 0 ? {} : { email: value.user.email },
348
- ...value.user.name === void 0 ? {} : { name: value.user.name }
349
- }
350
- };
351
- }
352
- function parseModelSourceBrokerStatus(value) {
353
- if (!isRecord$21(value) || !Array.isArray(value.selectedOrder)) throw new CliError$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
354
- const resolvedAvailability = value.resolvedAvailability;
355
- if (!isRecord$21(resolvedAvailability)) throw new CliError$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
356
- const status = resolvedAvailability.status;
357
- const reason = resolvedAvailability.reason;
358
- const selectedProviderMode = resolvedAvailability.mode;
359
- const requiredNextAction = resolvedAvailability.nextAction;
360
- 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$1("Quire API returned an invalid model-source status response", ExitCode.NetworkFailure);
361
- return {
362
- available: status === "available",
363
- status,
364
- selectedProviderMode,
365
- selectedOrder: value.selectedOrder,
366
- reason,
367
- requiredNextAction,
368
- requiredBalance: readWalletRequiredBalance(value),
369
- remainingBalance: readWalletRemainingBalance(value),
370
- recentUsage: readRecentUsage(value)
371
- };
372
- }
373
- function isRecord$21(value) {
374
- return typeof value === "object" && value !== null && !Array.isArray(value);
375
- }
376
- function readWalletRequiredBalance(value) {
377
- const source = readFirstSource(value);
378
- const requiredBalance = (isRecord$21(source?.wallet) ? source.wallet : null)?.requiredBalance;
379
- return typeof requiredBalance === "number" && Number.isFinite(requiredBalance) ? requiredBalance : null;
380
- }
381
- function readWalletRemainingBalance(value) {
382
- const source = readFirstSource(value);
383
- const wallet = isRecord$21(source?.wallet) ? source.wallet : null;
384
- const remaining = (isRecord$21(wallet?.balance) ? wallet.balance : null)?.remaining;
385
- return typeof remaining === "number" && Number.isFinite(remaining) ? remaining : null;
386
- }
387
- function readFirstSource(value) {
388
- if (!Array.isArray(value.sources)) return null;
389
- const [source] = value.sources;
390
- return isRecord$21(source) ? source : null;
391
- }
392
- function readRecentUsage(value) {
393
- if (!Array.isArray(value.recentUsage)) return [];
394
- return value.recentUsage.flatMap((event) => {
395
- if (!isRecord$21(event)) return [];
396
- if (typeof event.modelId !== "string" || typeof event.status !== "string" || typeof event.totalTokens !== "number" || typeof event.createdAt !== "string") return [];
397
- return [{
398
- modelId: event.modelId,
399
- status: event.status,
400
- totalTokens: event.totalTokens,
401
- runId: typeof event.runId === "string" ? event.runId : null,
402
- createdAt: event.createdAt
403
- }];
404
- });
405
- }
406
- //#endregion
407
- //#region src/auth/store.ts
408
- function defaultAuthPath(env = process.env) {
409
- if (env.QUIRE_AUTH_FILE) return resolve(env.QUIRE_AUTH_FILE);
410
- return join(resolve(env.QUIRE_HOME ?? join(homedir(), ".quire")), "auth.json");
411
- }
412
- function createAuthStore(path = defaultAuthPath()) {
413
- async function get() {
414
- try {
415
- const contents = await readFile(path, "utf8");
416
- return parseStoredCredentials(JSON.parse(contents));
417
- } catch {
418
- return null;
419
- }
420
- }
421
- async function set(credentials) {
422
- await mkdir(dirname(path), {
423
- recursive: true,
424
- mode: 448
425
- });
426
- await writeFile(path, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 384 });
427
- }
428
- async function clear() {
429
- try {
430
- await unlink(path);
431
- } catch {}
432
- }
433
- return {
434
- path,
435
- get,
436
- set,
437
- clear
438
- };
439
- }
440
- const authStore = createAuthStore();
441
- function createStoredCredentials(input) {
442
- const now = input.now ?? /* @__PURE__ */ new Date();
443
- const timestamp = now.toISOString();
444
- const expiresAt = input.expiresAt ?? expiresAtFromSeconds(input.expiresIn, now);
445
- return {
446
- version: 1,
447
- apiBaseUrl: input.apiBaseUrl,
448
- accessToken: input.accessToken,
449
- tokenType: input.tokenType ?? "Bearer",
450
- ...expiresAt === void 0 ? {} : { expiresAt },
451
- ...input.user === void 0 ? {} : { user: input.user },
452
- createdAt: timestamp,
453
- updatedAt: timestamp
454
- };
455
- }
456
- function updateStoredCredentials(credentials, input) {
457
- return {
458
- ...credentials,
459
- accessToken: input.accessToken ?? credentials.accessToken,
460
- tokenType: input.tokenType ?? credentials.tokenType,
461
- ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt },
462
- ...input.user === void 0 ? {} : { user: input.user },
463
- updatedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
464
- };
465
- }
466
- function expiresAtFromSeconds(expiresIn, now) {
467
- return typeof expiresIn === "number" ? new Date(now.getTime() + expiresIn * 1e3).toISOString() : void 0;
468
- }
469
- function parseStoredCredentials(value) {
470
- if (!isRecord$20(value)) return null;
471
- 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;
472
- if (value.expiresAt !== void 0 && typeof value.expiresAt !== "string") return null;
473
- const user = parseStoredUser(value.user);
474
- if (value.user !== void 0 && user === null) return null;
475
- return {
476
- version: 1,
477
- apiBaseUrl: value.apiBaseUrl,
478
- accessToken: value.accessToken,
479
- tokenType: value.tokenType,
480
- ...value.expiresAt === void 0 ? {} : { expiresAt: value.expiresAt },
481
- ...user === void 0 || user === null ? {} : { user },
482
- createdAt: value.createdAt,
483
- updatedAt: value.updatedAt
484
- };
485
- }
486
- function parseStoredUser(value) {
487
- if (value === void 0) return;
488
- if (!isRecord$20(value) || typeof value.id !== "string") return null;
489
- if (value.email !== void 0 && value.email !== null && typeof value.email !== "string") return null;
490
- if (value.name !== void 0 && value.name !== null && typeof value.name !== "string") return null;
491
- return {
492
- id: value.id,
493
- ...value.email === void 0 ? {} : { email: value.email },
494
- ...value.name === void 0 ? {} : { name: value.name }
495
- };
496
- }
497
- function isRecord$20(value) {
498
- return typeof value === "object" && value !== null && !Array.isArray(value);
499
- }
500
- //#endregion
501
1098
  //#region src/run-dir.ts
502
1099
  function createRunDir(repoPath, options = {}) {
503
1100
  const workspaceKey = workspaceKeyForRepoPath(resolve(repoPath));
@@ -628,7 +1225,7 @@ function sanitizeWorkspaceName(value) {
628
1225
  }
629
1226
  function appendLineAtomically(filePath, line) {
630
1227
  mkdirSync(dirname(filePath), { recursive: true });
631
- const file = openSync(filePath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
1228
+ const file = openSync(filePath, constants$1.O_APPEND | constants$1.O_CREAT | constants$1.O_WRONLY, 384);
632
1229
  try {
633
1230
  writeFileSync(file, line, "utf8");
634
1231
  fsyncSync(file);
@@ -858,7 +1455,7 @@ async function defaultExecFn(file, args, options) {
858
1455
  };
859
1456
  }
860
1457
  async function checkAuthPresent(context) {
861
- const credentials = await context.authStore.get();
1458
+ const credentials = await resolveAuthCredentials({ store: context.authStore });
862
1459
  if (credentials === null) return { result: {
863
1460
  id: "auth.present",
864
1461
  section: "identity",
@@ -876,15 +1473,38 @@ async function checkAuthPresent(context) {
876
1473
  auth: {
877
1474
  apiBaseUrl: credentials.apiBaseUrl,
878
1475
  accessToken: credentials.accessToken,
1476
+ tokenType: credentials.tokenType,
879
1477
  ...credentials.user === void 0 ? {} : { user: credentials.user }
880
1478
  }
881
1479
  };
882
1480
  }
883
1481
  async function checkAuthValid(context, auth) {
1482
+ if (auth.tokenType === "QuireApiKey") try {
1483
+ return { result: {
1484
+ id: "auth.valid",
1485
+ section: "identity",
1486
+ severity: "pass",
1487
+ message: `API token valid for ${(await fetchModelSourceBrokerStatus({
1488
+ apiBaseUrl: auth.apiBaseUrl,
1489
+ accessToken: auth.accessToken,
1490
+ tokenType: auth.tokenType,
1491
+ fetchFn: context.fetchFn
1492
+ })).actor?.environmentName ?? "remote agent environment"}.`
1493
+ } };
1494
+ } catch (error) {
1495
+ return { result: {
1496
+ id: "auth.valid",
1497
+ section: "identity",
1498
+ severity: "fail",
1499
+ message: `Could not validate Quire API token: ${toMessage(error)}`,
1500
+ fix: { command: "Set QUIRE_API_TOKEN to an active token from Quire settings" }
1501
+ } };
1502
+ }
884
1503
  try {
885
1504
  const lookup = await fetchCurrentSession({
886
1505
  apiBaseUrl: auth.apiBaseUrl,
887
1506
  accessToken: auth.accessToken,
1507
+ tokenType: auth.tokenType,
888
1508
  fetchFn: context.fetchFn
889
1509
  });
890
1510
  if (lookup.data === null) return { result: {
@@ -919,6 +1539,7 @@ async function checkAuthBroker(context, auth) {
919
1539
  return brokerToCheckResult(await fetchModelSourceBrokerStatus({
920
1540
  apiBaseUrl: auth.apiBaseUrl,
921
1541
  accessToken: auth.accessToken,
1542
+ tokenType: auth.tokenType,
922
1543
  fetchFn: context.fetchFn
923
1544
  }));
924
1545
  } catch (error) {
@@ -1174,7 +1795,7 @@ async function checkRunsRoot(context) {
1174
1795
  recursive: true,
1175
1796
  mode: 448
1176
1797
  });
1177
- await access(context.runsRoot, constants$1.W_OK);
1798
+ await access(context.runsRoot, constants.W_OK);
1178
1799
  return {
1179
1800
  id: "runs.rootWritable",
1180
1801
  section: "runs",
@@ -7690,81 +8311,104 @@ var require_browser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7690
8311
  };
7691
8312
  }));
7692
8313
  //#endregion
7693
- //#region ../../node_modules/.pnpm/has-flag@4.0.0/node_modules/has-flag/index.js
7694
- var require_has_flag = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7695
- module.exports = (flag, argv = process.argv) => {
7696
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
7697
- const position = argv.indexOf(prefix + flag);
7698
- const terminatorPosition = argv.indexOf("--");
7699
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
7700
- };
7701
- }));
7702
- //#endregion
7703
- //#region ../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js
7704
- var require_supports_color = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7705
- const os$1 = __require("os");
7706
- const tty$1 = __require("tty");
7707
- const hasFlag = require_has_flag();
7708
- const { env } = process;
7709
- let forceColor;
7710
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) forceColor = 0;
7711
- else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) forceColor = 1;
7712
- if ("FORCE_COLOR" in env) if (env.FORCE_COLOR === "true") forceColor = 1;
7713
- else if (env.FORCE_COLOR === "false") forceColor = 0;
7714
- else forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
7715
- function translateLevel(level) {
7716
- if (level === 0) return false;
7717
- return {
7718
- level,
7719
- hasBasic: true,
7720
- has256: level >= 2,
7721
- has16m: level >= 3
7722
- };
7723
- }
7724
- function supportsColor(haveStream, streamIsTTY) {
7725
- if (forceColor === 0) return 0;
8314
+ //#region ../../node_modules/.pnpm/supports-color@10.2.2/node_modules/supports-color/index.js
8315
+ var supports_color_exports = /* @__PURE__ */ __exportAll({
8316
+ createSupportsColor: () => createSupportsColor,
8317
+ default: () => supportsColor
8318
+ });
8319
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
8320
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
8321
+ const position = argv.indexOf(prefix + flag);
8322
+ const terminatorPosition = argv.indexOf("--");
8323
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
8324
+ }
8325
+ function envForceColor() {
8326
+ if (!("FORCE_COLOR" in env)) return;
8327
+ if (env.FORCE_COLOR === "true") return 1;
8328
+ if (env.FORCE_COLOR === "false") return 0;
8329
+ if (env.FORCE_COLOR.length === 0) return 1;
8330
+ const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
8331
+ if (![
8332
+ 0,
8333
+ 1,
8334
+ 2,
8335
+ 3
8336
+ ].includes(level)) return;
8337
+ return level;
8338
+ }
8339
+ function translateLevel(level) {
8340
+ if (level === 0) return false;
8341
+ return {
8342
+ level,
8343
+ hasBasic: true,
8344
+ has256: level >= 2,
8345
+ has16m: level >= 3
8346
+ };
8347
+ }
8348
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
8349
+ const noFlagForceColor = envForceColor();
8350
+ if (noFlagForceColor !== void 0) flagForceColor = noFlagForceColor;
8351
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
8352
+ if (forceColor === 0) return 0;
8353
+ if (sniffFlags) {
7726
8354
  if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) return 3;
7727
8355
  if (hasFlag("color=256")) return 2;
7728
- if (haveStream && !streamIsTTY && forceColor === void 0) return 0;
7729
- const min = forceColor || 0;
7730
- if (env.TERM === "dumb") return min;
7731
- if (process.platform === "win32") {
7732
- const osRelease = os$1.release().split(".");
7733
- if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
7734
- return 1;
7735
- }
7736
- if ("CI" in env) {
7737
- if ([
7738
- "TRAVIS",
7739
- "CIRCLECI",
7740
- "APPVEYOR",
7741
- "GITLAB_CI",
7742
- "GITHUB_ACTIONS",
7743
- "BUILDKITE"
7744
- ].some((sign) => sign in env) || env.CI_NAME === "codeship") return 1;
7745
- return min;
7746
- }
7747
- if ("TEAMCITY_VERSION" in env) return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
7748
- if (env.COLORTERM === "truecolor") return 3;
7749
- if ("TERM_PROGRAM" in env) {
7750
- const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
7751
- switch (env.TERM_PROGRAM) {
7752
- case "iTerm.app": return version >= 3 ? 3 : 2;
7753
- case "Apple_Terminal": return 2;
7754
- }
7755
- }
7756
- if (/-256(color)?$/i.test(env.TERM)) return 2;
7757
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) return 1;
7758
- if ("COLORTERM" in env) return 1;
7759
- return min;
7760
8356
  }
7761
- function getSupportLevel(stream) {
7762
- return translateLevel(supportsColor(stream, stream && stream.isTTY));
8357
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) return 1;
8358
+ if (haveStream && !streamIsTTY && forceColor === void 0) return 0;
8359
+ const min = forceColor || 0;
8360
+ if (env.TERM === "dumb") return min;
8361
+ if (process$1.platform === "win32") {
8362
+ const osRelease = os.release().split(".");
8363
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
8364
+ return 1;
8365
+ }
8366
+ if ("CI" in env) {
8367
+ if ([
8368
+ "GITHUB_ACTIONS",
8369
+ "GITEA_ACTIONS",
8370
+ "CIRCLECI"
8371
+ ].some((key) => key in env)) return 3;
8372
+ if ([
8373
+ "TRAVIS",
8374
+ "APPVEYOR",
8375
+ "GITLAB_CI",
8376
+ "BUILDKITE",
8377
+ "DRONE"
8378
+ ].some((sign) => sign in env) || env.CI_NAME === "codeship") return 1;
8379
+ return min;
7763
8380
  }
7764
- module.exports = {
7765
- supportsColor: getSupportLevel,
7766
- stdout: translateLevel(supportsColor(true, tty$1.isatty(1))),
7767
- stderr: translateLevel(supportsColor(true, tty$1.isatty(2)))
8381
+ if ("TEAMCITY_VERSION" in env) return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
8382
+ if (env.COLORTERM === "truecolor") return 3;
8383
+ if (env.TERM === "xterm-kitty") return 3;
8384
+ if (env.TERM === "xterm-ghostty") return 3;
8385
+ if (env.TERM === "wezterm") return 3;
8386
+ if ("TERM_PROGRAM" in env) {
8387
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
8388
+ switch (env.TERM_PROGRAM) {
8389
+ case "iTerm.app": return version >= 3 ? 3 : 2;
8390
+ case "Apple_Terminal": return 2;
8391
+ }
8392
+ }
8393
+ if (/-256(color)?$/i.test(env.TERM)) return 2;
8394
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) return 1;
8395
+ if ("COLORTERM" in env) return 1;
8396
+ return min;
8397
+ }
8398
+ function createSupportsColor(stream, options = {}) {
8399
+ return translateLevel(_supportsColor(stream, {
8400
+ streamIsTTY: stream && stream.isTTY,
8401
+ ...options
8402
+ }));
8403
+ }
8404
+ var env, flagForceColor, supportsColor;
8405
+ var init_supports_color = __esmMin((() => {
8406
+ ({env} = process$1);
8407
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) flagForceColor = 0;
8408
+ else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) flagForceColor = 1;
8409
+ supportsColor = {
8410
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
8411
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
7768
8412
  };
7769
8413
  }));
7770
8414
  //#endregion
@@ -7773,7 +8417,7 @@ var require_node$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7773
8417
  /**
7774
8418
  * Module dependencies.
7775
8419
  */
7776
- const tty = __require("tty");
8420
+ const tty$1 = __require("tty");
7777
8421
  const util$2 = __require("util");
7778
8422
  /**
7779
8423
  * This is the Node.js implementation of `debug()`.
@@ -7797,7 +8441,7 @@ var require_node$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7797
8441
  1
7798
8442
  ];
7799
8443
  try {
7800
- const supportsColor = require_supports_color();
8444
+ const supportsColor = (init_supports_color(), __toCommonJS(supports_color_exports));
7801
8445
  if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) exports.colors = [
7802
8446
  20,
7803
8447
  21,
@@ -7900,7 +8544,7 @@ var require_node$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7900
8544
  * Is stdout a TTY? Colored output is enabled when `true`.
7901
8545
  */
7902
8546
  function useColors() {
7903
- return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(process.stderr.fd);
8547
+ return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty$1.isatty(process.stderr.fd);
7904
8548
  }
7905
8549
  /**
7906
8550
  * Adds ANSI color escape codes if enabled.
@@ -31411,7 +32055,7 @@ function isENOENT(err) {
31411
32055
  return e?.code === "ENOENT" || e?.cause?.code === "ENOENT";
31412
32056
  }
31413
32057
  function concurrencyLimit() {
31414
- const cpu = os.cpus()?.length ?? 1;
32058
+ const cpu = os$1.cpus()?.length ?? 1;
31415
32059
  return Math.min(4, Math.max(1, cpu - 1));
31416
32060
  }
31417
32061
  async function mapLimit(items, limit, fn) {
@@ -31563,7 +32207,7 @@ async function collectAdditionalFiles(additionalFiles) {
31563
32207
  let xdelta3WasmReady = null;
31564
32208
  async function loadXdelta3Wasm() {
31565
32209
  if (!xdelta3WasmReady) xdelta3WasmReady = (async () => {
31566
- const mod = await import("./dist-CW-Om7Oq.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
32210
+ const mod = await import("./dist-CF5hqugO.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
31567
32211
  await mod.init();
31568
32212
  return mod;
31569
32213
  })().catch((err) => {
@@ -31670,7 +32314,7 @@ async function syncFolderOnce(localFolderPath, opts, reason, attempt = 0) {
31670
32314
  const basisPath = cacheGet(opts.basisCacheDir, f.path);
31671
32315
  if (fs.existsSync(basisPath)) {
31672
32316
  const basisSha = await sha256FileHex(basisPath);
31673
- const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "limulator-xdelta3-"));
32317
+ const tmpDir = await fs.promises.mkdtemp(path.join(os$1.tmpdir(), "limulator-xdelta3-"));
31674
32318
  const patchPath = path.join(tmpDir, "patch.xdelta3");
31675
32319
  const encodeStart = nowMs();
31676
32320
  const patchSize = await encodeXdelta3Patch(basisPath, f.absPath, patchPath, maxPatchBytes);
@@ -32144,7 +32788,7 @@ var XcodeInstances = class extends XcodeInstances$1 {
32144
32788
  async sync(localCodePath, opts) {
32145
32789
  const resolvedPath = path.resolve(localCodePath);
32146
32790
  const cacheKey = `limsync-cache-${path.basename(resolvedPath)}-${crypto.createHash("sha1").update(resolvedPath).digest("hex").slice(0, 8)}`;
32147
- const basisCacheDir = opts?.basisCacheDir ?? path.join(os.tmpdir(), cacheKey);
32791
+ const basisCacheDir = opts?.basisCacheDir ?? path.join(os$1.tmpdir(), cacheKey);
32148
32792
  const additionalFiles = opts?.additionalFiles?.map((file) => ({
32149
32793
  localPath: file.localPath,
32150
32794
  remotePath: file.remotePath.startsWith("~/") ? `${sandboxInfo.homeDir}/${file.remotePath.slice(2)}` : file.remotePath
@@ -38275,7 +38919,7 @@ async function createInstanceClient(options) {
38275
38919
  if (!cachedDeviceInfo) throw new Error("Device info not available yet; wait for client connection to be established.");
38276
38920
  const resolvedPath = path.resolve(localAppBundlePath);
38277
38921
  const cacheKey = `limsync-cache-${path.basename(resolvedPath)}-${crypto.createHash("sha1").update(resolvedPath).digest("hex").slice(0, 8)}`;
38278
- const basisCacheDir = opts?.basisCacheDir ?? path.join(os.tmpdir(), cacheKey);
38922
+ const basisCacheDir = opts?.basisCacheDir ?? path.join(os$1.tmpdir(), cacheKey);
38279
38923
  const syncLog = (level, msg) => {
38280
38924
  switch (level) {
38281
38925
  case "debug":
@@ -39603,7 +40247,7 @@ async function findExecutable$1(name) {
39603
40247
  for (const pathEntry of paths) {
39604
40248
  const candidate = join(pathEntry, name);
39605
40249
  try {
39606
- await access(candidate, constants.X_OK);
40250
+ await access(candidate, constants$1.X_OK);
39607
40251
  return candidate;
39608
40252
  } catch {
39609
40253
  continue;
@@ -40386,7 +41030,7 @@ async function findExecutable(name) {
40386
41030
  for (const pathEntry of paths) {
40387
41031
  const candidate = join(pathEntry, name);
40388
41032
  try {
40389
- await access(candidate, constants.X_OK);
41033
+ await access(candidate, constants$1.X_OK);
40390
41034
  return candidate;
40391
41035
  } catch {
40392
41036
  continue;
@@ -42775,11 +43419,12 @@ function quoteBatchArg(value) {
42775
43419
  }
42776
43420
  //#endregion
42777
43421
  //#region src/tools/browser.ts
43422
+ const requireFromHere = createRequire(import.meta.url);
42778
43423
  const BrowserToolInputSchema = Type.Object({
42779
43424
  argv: Type.Array(Type.String(), { minItems: 1 }),
42780
43425
  cwd: Type.Optional(Type.String({ minLength: 1 }))
42781
43426
  }, { additionalProperties: false });
42782
- function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false }) {
43427
+ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, agentBrowserExecutableResolver = resolveAgentBrowserExecutable }) {
42783
43428
  return {
42784
43429
  name: "agent-browser",
42785
43430
  label: "agent-browser",
@@ -42791,6 +43436,7 @@ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false })
42791
43436
  sessionId,
42792
43437
  defaultCwd,
42793
43438
  headed,
43439
+ agentBrowserExecutableResolver,
42794
43440
  input,
42795
43441
  signal
42796
43442
  });
@@ -42801,6 +43447,7 @@ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false })
42801
43447
  sessionId,
42802
43448
  defaultCwd,
42803
43449
  headed,
43450
+ agentBrowserExecutableResolver,
42804
43451
  input: parseBrowserToolInput(params),
42805
43452
  signal
42806
43453
  });
@@ -42814,11 +43461,18 @@ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false })
42814
43461
  }
42815
43462
  };
42816
43463
  }
43464
+ function resolveAgentBrowserExecutable() {
43465
+ const packageJsonPath = requireFromHere.resolve("agent-browser/package.json");
43466
+ return {
43467
+ command: process.execPath,
43468
+ argsPrefix: [join(dirname(packageJsonPath), "bin", "agent-browser.js")]
43469
+ };
43470
+ }
42817
43471
  function parseBrowserToolInput(input) {
42818
43472
  if (!Value.Check(BrowserToolInputSchema, input)) throw new Error("Invalid agent-browser tool input");
42819
43473
  return input;
42820
43474
  }
42821
- async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, input, signal }) {
43475
+ async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, agentBrowserExecutableResolver, input, signal }) {
42822
43476
  const [executable, ...args] = input.argv;
42823
43477
  if (executable !== "agent-browser") throw new Error(`Refusing to spawn non-agent-browser executable: ${executable}`);
42824
43478
  const cwd = input.cwd ?? defaultCwd ?? process.cwd();
@@ -42844,12 +43498,26 @@ async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, input,
42844
43498
  sessionId,
42845
43499
  cwd,
42846
43500
  headed,
43501
+ agentBrowserExecutableResolver,
42847
43502
  signal
42848
43503
  });
42849
43504
  }
42850
- function spawnAgentBrowser({ args, sessionId, cwd, signal }) {
43505
+ function spawnAgentBrowser({ args, sessionId, cwd, agentBrowserExecutableResolver, signal }) {
42851
43506
  return new Promise((resolve) => {
42852
- const child = spawn("agent-browser", [
43507
+ let executable;
43508
+ try {
43509
+ executable = agentBrowserExecutableResolver();
43510
+ } catch (error) {
43511
+ resolve({
43512
+ ok: false,
43513
+ exitCode: null,
43514
+ stdout: "",
43515
+ stderr: `Unable to resolve bundled agent-browser: ${formatErrorMessage(error)}`
43516
+ });
43517
+ return;
43518
+ }
43519
+ const child = spawn(executable.command, [
43520
+ ...executable.argsPrefix,
42853
43521
  ...args,
42854
43522
  "--session",
42855
43523
  sessionId
@@ -42900,7 +43568,10 @@ function spawnAgentBrowser({ args, sessionId, cwd, signal }) {
42900
43568
  });
42901
43569
  });
42902
43570
  }
42903
- async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, signal }) {
43571
+ function formatErrorMessage(error) {
43572
+ return error instanceof Error ? error.message : String(error);
43573
+ }
43574
+ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, agentBrowserExecutableResolver, signal }) {
42904
43575
  const stdout = [];
42905
43576
  const stderr = [];
42906
43577
  for (const command of commands) {
@@ -42916,6 +43587,7 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
42916
43587
  args,
42917
43588
  sessionId,
42918
43589
  cwd,
43590
+ agentBrowserExecutableResolver,
42919
43591
  signal
42920
43592
  });
42921
43593
  stdout.push(result.stdout);
@@ -44378,423 +45050,6 @@ function truncateForLine(text) {
44378
45050
  function truncateForFile(text) {
44379
45051
  return text.length <= 4e3 ? text : `${text.slice(0, 3997)}...`;
44380
45052
  }
44381
- //#endregion
44382
- //#region src/pipeline/quire-model-provider.ts
44383
- const QUIRE_MODEL_PROVIDER = "quire";
44384
- const QUIRE_MODEL_ID = "gpt-5.1";
44385
- const PI_CODEX_MODEL_PROVIDER = "openai-codex";
44386
- const PI_CODEX_MODEL_ID = "gpt-5.5";
44387
- const QUIRE_MODEL_API = "quire-chat-completions";
44388
- const QUIRE_RUNTIME_AUTH_ENV = "QUIRE_RUNTIME_AUTH_TOKEN";
44389
- const QUIRE_MODEL_AUTH_PATH_ENV = "QUIRE_MODEL_AUTH_PATH";
44390
- const openAiCompletionCompat = {
44391
- supportsStore: false,
44392
- supportsDeveloperRole: false,
44393
- supportsReasoningEffort: true,
44394
- supportsUsageInStreaming: false,
44395
- maxTokensField: "max_completion_tokens",
44396
- requiresToolResultName: false,
44397
- requiresAssistantAfterToolResult: false,
44398
- requiresThinkingAsText: false,
44399
- requiresReasoningContentOnAssistantMessages: false,
44400
- thinkingFormat: "openai",
44401
- zaiToolStream: false,
44402
- supportsStrictMode: true,
44403
- sendSessionAffinityHeaders: false,
44404
- supportsLongCacheRetention: true
44405
- };
44406
- async function createQuireModelSessionDependencies({ runId, store = authStore, fetchFn }) {
44407
- const credentials = await readValidatedQuireCredentials({
44408
- store,
44409
- fetchFn
44410
- });
44411
- const authStorage = AuthStorage.inMemory();
44412
- authStorage.setRuntimeApiKey(QUIRE_MODEL_PROVIDER, credentials.accessToken);
44413
- const modelRegistry = ModelRegistry.inMemory(authStorage);
44414
- modelRegistry.registerProvider(QUIRE_MODEL_PROVIDER, {
44415
- name: "Quire Credits",
44416
- baseUrl: credentials.apiBaseUrl,
44417
- apiKey: QUIRE_RUNTIME_AUTH_ENV,
44418
- api: QUIRE_MODEL_API,
44419
- models: [quireModelDefinition()],
44420
- streamSimple: createQuireCreditsStream({
44421
- apiBaseUrl: credentials.apiBaseUrl,
44422
- runId,
44423
- fetchFn
44424
- })
44425
- });
44426
- const model = modelRegistry.find(QUIRE_MODEL_PROVIDER, QUIRE_MODEL_ID);
44427
- if (!model) throw new Error(`Quire model not found: ${QUIRE_MODEL_PROVIDER}/${QUIRE_MODEL_ID}`);
44428
- return {
44429
- authStorage,
44430
- modelRegistry,
44431
- model,
44432
- source: "quire_credits"
44433
- };
44434
- }
44435
- async function createInvestigationModelSessionDependencies({ runId, store = authStore, fetchFn, localAuthStorage }) {
44436
- const localCodex = await createLocalCodexSessionDependencies(localAuthStorage);
44437
- if (localCodex) return localCodex;
44438
- return createQuireModelSessionDependencies({
44439
- runId,
44440
- store,
44441
- fetchFn
44442
- });
44443
- }
44444
- async function createLocalCodexSessionDependencies(localAuthStorage) {
44445
- const authStorages = localAuthStorage === void 0 ? [AuthStorage.create(readQuireModelAuthPath()), AuthStorage.create()] : [localAuthStorage];
44446
- for (const authStorage of authStorages) {
44447
- const modelRegistry = ModelRegistry.create(authStorage);
44448
- const model = modelRegistry.find(PI_CODEX_MODEL_PROVIDER, PI_CODEX_MODEL_ID);
44449
- if (!model || !modelRegistry.hasConfiguredAuth(model)) continue;
44450
- const auth = await modelRegistry.getApiKeyAndHeaders(model);
44451
- if (!auth.ok || !auth.apiKey && !auth.headers) continue;
44452
- return {
44453
- authStorage,
44454
- modelRegistry,
44455
- model,
44456
- source: "local_openai_codex"
44457
- };
44458
- }
44459
- return null;
44460
- }
44461
- function readQuireModelAuthPath() {
44462
- const envPath = process.env[QUIRE_MODEL_AUTH_PATH_ENV]?.trim();
44463
- return envPath && envPath.length > 0 ? envPath : join(homedir(), ".quire", "model-auth.json");
44464
- }
44465
- function quireModelDefinition() {
44466
- return {
44467
- id: QUIRE_MODEL_ID,
44468
- name: "Quire Credits GPT-5.1",
44469
- api: QUIRE_MODEL_API,
44470
- reasoning: true,
44471
- thinkingLevelMap: {
44472
- off: null,
44473
- xhigh: "high"
44474
- },
44475
- input: ["text"],
44476
- cost: {
44477
- input: 1.25,
44478
- output: 10,
44479
- cacheRead: .125,
44480
- cacheWrite: 0
44481
- },
44482
- contextWindow: 128e3,
44483
- maxTokens: 32e3
44484
- };
44485
- }
44486
- function createQuireCreditsStream(options) {
44487
- return (model, context, streamOptions) => {
44488
- const stream = createAssistantMessageEventStream();
44489
- (async () => {
44490
- const output = createEmptyAssistantMessage(model);
44491
- try {
44492
- const accessToken = streamOptions?.apiKey;
44493
- if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
44494
- stream.push({
44495
- type: "start",
44496
- partial: output
44497
- });
44498
- applyBrokerResponseToStream(stream, output, model, await requestBrokerChatCompletion({
44499
- apiBaseUrl: options.apiBaseUrl,
44500
- accessToken,
44501
- runId: options.runId,
44502
- model,
44503
- context,
44504
- streamOptions,
44505
- fetchFn: options.fetchFn
44506
- }));
44507
- } catch (error) {
44508
- output.stopReason = streamOptions?.signal?.aborted ? "aborted" : "error";
44509
- output.errorMessage = error instanceof Error ? error.message : String(error);
44510
- stream.push({
44511
- type: "error",
44512
- reason: output.stopReason,
44513
- error: output
44514
- });
44515
- stream.end();
44516
- }
44517
- })();
44518
- return stream;
44519
- };
44520
- }
44521
- async function readValidatedQuireCredentials(options) {
44522
- const credentials = await options.store.get();
44523
- if (!credentials) throw new CliError$1("Not logged in. Run `quire login` before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
44524
- const sessionLookup = await fetchCurrentSession({
44525
- apiBaseUrl: credentials.apiBaseUrl,
44526
- accessToken: credentials.accessToken,
44527
- fetchFn: options.fetchFn
44528
- });
44529
- if (!sessionLookup.data) {
44530
- await options.store.clear();
44531
- throw new CliError$1("Stored Quire credentials are no longer valid. Run `quire login`.", ExitCode.AuthFailure);
44532
- }
44533
- if (!sessionLookup.refreshedAccessToken) return credentials;
44534
- const refreshedCredentials = updateStoredCredentials(credentials, {
44535
- accessToken: sessionLookup.refreshedAccessToken,
44536
- expiresAt: sessionLookup.data.session.expiresAt,
44537
- user: sessionLookup.data.user
44538
- });
44539
- await options.store.set(refreshedCredentials);
44540
- return refreshedCredentials;
44541
- }
44542
- async function requestBrokerChatCompletion(input) {
44543
- const response = await (input.fetchFn ?? fetch)(new URL("/api/model-broker/chat-completions", input.apiBaseUrl).toString(), {
44544
- method: "POST",
44545
- headers: {
44546
- Accept: "application/json",
44547
- Authorization: `Bearer ${input.accessToken}`,
44548
- "Content-Type": "application/json",
44549
- "User-Agent": "quire-cli"
44550
- },
44551
- signal: input.streamOptions?.signal,
44552
- body: JSON.stringify({
44553
- model: input.model.id,
44554
- messages: toBrokerMessages(input.model, input.context),
44555
- runId: input.runId,
44556
- stream: false,
44557
- agentContext: {
44558
- messages: [],
44559
- tools: toBrokerTools(input.context.tools)
44560
- },
44561
- agentOptions: {
44562
- reasoning: normalizeReasoning(input.streamOptions?.reasoning),
44563
- sessionId: input.streamOptions?.sessionId ?? null
44564
- }
44565
- })
44566
- });
44567
- const body = await readJsonBody(response);
44568
- if (!response.ok || body?.status !== "succeeded") throw brokerResponseError(response, body);
44569
- const rawResponse = body.data?.response;
44570
- if (!rawResponse || typeof rawResponse !== "object" || Array.isArray(rawResponse)) throw new Error("Quire model broker returned an invalid model response.");
44571
- return rawResponse;
44572
- }
44573
- function toBrokerMessages(model, context) {
44574
- return convertMessages(model, context, openAiCompletionCompat).map((message) => {
44575
- const record = message;
44576
- const role = normalizeRole(record.role);
44577
- const toolCalls = parseBrokerToolCalls(record.tool_calls);
44578
- return {
44579
- role,
44580
- content: normalizeMessageContent(record.content),
44581
- ...typeof record.name === "string" ? { name: record.name } : {},
44582
- ...typeof record.tool_call_id === "string" ? { tool_call_id: record.tool_call_id } : {},
44583
- ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
44584
- };
44585
- });
44586
- }
44587
- function toBrokerTools(tools) {
44588
- return (tools ?? []).map((tool) => ({
44589
- type: "function",
44590
- function: {
44591
- name: tool.name,
44592
- description: tool.description,
44593
- parameters: tool.parameters,
44594
- strict: false
44595
- }
44596
- }));
44597
- }
44598
- function applyBrokerResponseToStream(stream, output, model, response) {
44599
- output.responseId = typeof response.id === "string" ? response.id : void 0;
44600
- output.responseModel = typeof response.model === "string" ? response.model : void 0;
44601
- output.usage = readUsage(response.usage, model);
44602
- const choice = response.choices?.[0];
44603
- const message = choice?.message;
44604
- const content = typeof message?.content === "string" ? message.content : "";
44605
- if (content.length > 0) {
44606
- const textBlock = {
44607
- type: "text",
44608
- text: content
44609
- };
44610
- output.content.push(textBlock);
44611
- const contentIndex = output.content.length - 1;
44612
- stream.push({
44613
- type: "text_start",
44614
- contentIndex,
44615
- partial: output
44616
- });
44617
- stream.push({
44618
- type: "text_delta",
44619
- contentIndex,
44620
- delta: content,
44621
- partial: output
44622
- });
44623
- stream.push({
44624
- type: "text_end",
44625
- contentIndex,
44626
- content,
44627
- partial: output
44628
- });
44629
- }
44630
- const toolCalls = parseBrokerToolCalls(message?.tool_calls);
44631
- for (const toolCall of toolCalls) {
44632
- const block = {
44633
- type: "toolCall",
44634
- id: toolCall.id,
44635
- name: toolCall.function.name,
44636
- arguments: parseToolCallArguments(toolCall.function.arguments)
44637
- };
44638
- output.content.push(block);
44639
- const contentIndex = output.content.length - 1;
44640
- stream.push({
44641
- type: "toolcall_start",
44642
- contentIndex,
44643
- partial: output
44644
- });
44645
- stream.push({
44646
- type: "toolcall_delta",
44647
- contentIndex,
44648
- delta: toolCall.function.arguments,
44649
- partial: output
44650
- });
44651
- stream.push({
44652
- type: "toolcall_end",
44653
- contentIndex,
44654
- toolCall: block,
44655
- partial: output
44656
- });
44657
- }
44658
- output.stopReason = mapStopReason(choice?.finish_reason, toolCalls.length > 0);
44659
- stream.push({
44660
- type: "done",
44661
- reason: output.stopReason,
44662
- message: output
44663
- });
44664
- stream.end();
44665
- }
44666
- function createEmptyAssistantMessage(model) {
44667
- return {
44668
- role: "assistant",
44669
- content: [],
44670
- api: model.api,
44671
- provider: model.provider,
44672
- model: model.id,
44673
- usage: emptyUsage(),
44674
- stopReason: "stop",
44675
- timestamp: Date.now()
44676
- };
44677
- }
44678
- function emptyUsage() {
44679
- return {
44680
- input: 0,
44681
- output: 0,
44682
- cacheRead: 0,
44683
- cacheWrite: 0,
44684
- totalTokens: 0,
44685
- cost: {
44686
- input: 0,
44687
- output: 0,
44688
- cacheRead: 0,
44689
- cacheWrite: 0,
44690
- total: 0
44691
- }
44692
- };
44693
- }
44694
- function readUsage(rawUsage, model) {
44695
- const input = readNumber(rawUsage?.prompt_tokens ?? rawUsage?.promptTokens) ?? 0;
44696
- const output = readNumber(rawUsage?.completion_tokens ?? rawUsage?.completionTokens) ?? 0;
44697
- const usage = {
44698
- input,
44699
- output,
44700
- cacheRead: 0,
44701
- cacheWrite: 0,
44702
- totalTokens: readNumber(rawUsage?.total_tokens ?? rawUsage?.totalTokens) ?? input + output,
44703
- cost: {
44704
- input: 0,
44705
- output: 0,
44706
- cacheRead: 0,
44707
- cacheWrite: 0,
44708
- total: 0
44709
- }
44710
- };
44711
- calculateCost(model, usage);
44712
- return usage;
44713
- }
44714
- function mapStopReason(finishReason, hasToolCalls) {
44715
- if (finishReason === "length") return "length";
44716
- if (hasToolCalls || finishReason === "tool_calls" || finishReason === "function_call") return "toolUse";
44717
- return "stop";
44718
- }
44719
- function normalizeRole(value) {
44720
- if (value === "developer") return "system";
44721
- if (value === "system" || value === "user" || value === "assistant" || value === "tool") return value;
44722
- throw new Error(`Unsupported broker message role: ${String(value)}`);
44723
- }
44724
- function normalizeMessageContent(value) {
44725
- if (typeof value === "string") return value;
44726
- if (value === null || value === void 0) return "";
44727
- if (Array.isArray(value)) {
44728
- const text = value.map((part) => {
44729
- if (!part || typeof part !== "object" || Array.isArray(part)) return "";
44730
- const record = part;
44731
- return record.type === "text" && typeof record.text === "string" ? record.text : "[non-text content omitted]";
44732
- }).filter((part) => part.length > 0).join("\n");
44733
- return text.length > 0 ? text : "[non-text content omitted]";
44734
- }
44735
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
44736
- return "[non-text content omitted]";
44737
- }
44738
- function parseBrokerToolCalls(value) {
44739
- if (!Array.isArray(value)) return [];
44740
- return value.flatMap((toolCall, index) => {
44741
- if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) return [];
44742
- const record = toolCall;
44743
- const fn = record.function;
44744
- if (!fn || typeof fn !== "object" || Array.isArray(fn)) return [];
44745
- const functionRecord = fn;
44746
- const name = typeof functionRecord.name === "string" ? functionRecord.name : "";
44747
- if (!name) return [];
44748
- return [{
44749
- id: typeof record.id === "string" ? record.id : `call_${index}`,
44750
- type: "function",
44751
- function: {
44752
- name,
44753
- arguments: typeof functionRecord.arguments === "string" ? functionRecord.arguments : "{}"
44754
- }
44755
- }];
44756
- });
44757
- }
44758
- function parseToolCallArguments(value) {
44759
- try {
44760
- const parsed = JSON.parse(value);
44761
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
44762
- } catch {
44763
- return {};
44764
- }
44765
- }
44766
- async function readJsonBody(response) {
44767
- const text = await response.text();
44768
- if (!text) return null;
44769
- try {
44770
- return JSON.parse(text);
44771
- } catch {
44772
- return text;
44773
- }
44774
- }
44775
- function brokerResponseError(response, body) {
44776
- const code = body?.error?.code ?? `http_${response.status}`;
44777
- const message = body?.error?.message ?? `Quire model broker request failed with HTTP ${response.status}`;
44778
- const action = body?.error?.nextAction;
44779
- const billingDetail = formatBrokerBillingDetail(body);
44780
- return new Error([
44781
- `Quire model broker request failed (${code}): ${message}`,
44782
- billingDetail,
44783
- action === void 0 ? void 0 : `Next action: ${action}.`
44784
- ].filter((part) => part !== void 0 && part.length > 0).join(" "));
44785
- }
44786
- function normalizeReasoning(reasoning) {
44787
- return reasoning === "minimal" || reasoning === "low" || reasoning === "medium" || reasoning === "high" || reasoning === "xhigh" ? reasoning : void 0;
44788
- }
44789
- function readNumber(value) {
44790
- return typeof value === "number" && Number.isFinite(value) ? value : null;
44791
- }
44792
- function formatBrokerBillingDetail(body) {
44793
- const requiredBalance = readNumber(body?.metadata?.wallet?.requiredBalance) ?? readNumber(body?.metadata?.billing?.wallet?.requiredBalance) ?? readNumber(body?.metadata?.billing?.wallet?.estimatedTotalTokens);
44794
- const remainingBalance = readNumber(body?.metadata?.wallet?.balance?.remaining);
44795
- if (requiredBalance === null && remainingBalance === null) return;
44796
- return `Wallet: ${[requiredBalance === null ? void 0 : `required=${requiredBalance}`, remainingBalance === null ? void 0 : `remaining=${remainingBalance}`].filter((part) => part !== void 0).join(", ")}.`;
44797
- }
44798
45053
  const EXPLORE_THINKING_LEVEL = "xhigh";
44799
45054
  const EXPLORE_CODE_TOOL_NAMES = [
44800
45055
  "read",
@@ -45431,6 +45686,7 @@ function syncConfigFromCredentials(credentials, options = {}) {
45431
45686
  return {
45432
45687
  apiBaseUrl: credentials.apiBaseUrl,
45433
45688
  accessToken: credentials.accessToken,
45689
+ tokenType: credentials.tokenType,
45434
45690
  artifactMode: options.artifactMode ?? "evidence"
45435
45691
  };
45436
45692
  }
@@ -45574,7 +45830,7 @@ var InvestigationSyncSession = class {
45574
45830
  headers: {
45575
45831
  Accept: "application/json",
45576
45832
  "Content-Type": "application/json",
45577
- Authorization: `Bearer ${this.config.accessToken}`,
45833
+ ...authHeaders(this.config),
45578
45834
  "User-Agent": "quire-cli",
45579
45835
  ...Object.fromEntries(new Headers(init.headers).entries())
45580
45836
  }
@@ -45612,6 +45868,7 @@ async function syncExistingRun(options) {
45612
45868
  await new InvestigationSyncSession(options.runDir, {
45613
45869
  apiBaseUrl: options.credentials.apiBaseUrl,
45614
45870
  accessToken: options.credentials.accessToken,
45871
+ tokenType: options.credentials.tokenType,
45615
45872
  artifactMode: options.artifactMode ?? "evidence",
45616
45873
  fetchFn: options.fetchFn
45617
45874
  }).syncFromDisk();
@@ -46172,7 +46429,7 @@ async function runInvestigationWorker(requestPath) {
46172
46429
  pid: process.pid
46173
46430
  });
46174
46431
  try {
46175
- const credentials = await authStore.get();
46432
+ const credentials = await resolveAuthCredentials({ store: authStore });
46176
46433
  const result = await runLocalInvestigation({
46177
46434
  cwd: request.cwd,
46178
46435
  query: request.query,
@@ -46503,7 +46760,7 @@ async function listRuns(options = {}) {
46503
46760
  }
46504
46761
  async function syncRun(run, options = {}) {
46505
46762
  const stdout = options.io?.stdout ?? process.stdout;
46506
- const credentials = await (options.store ?? authStore).get();
46763
+ const credentials = await resolveAuthCredentials({ store: options.store ?? authStore });
46507
46764
  if (credentials === null) throw new CliError$1("Run `quire login` before syncing runs.", ExitCode.AuthFailure);
46508
46765
  const status = readFreshRunStatus(resolveRunPath(run, options));
46509
46766
  await syncExistingRun({
@@ -46629,7 +46886,7 @@ async function runInvestigate(options) {
46629
46886
  const stdout = options.io?.stdout ?? process.stdout;
46630
46887
  const stderr = options.io?.stderr ?? process.stderr;
46631
46888
  const stdinText = options.stdin === true ? await readAll(options.input ?? process.stdin) : void 0;
46632
- const credentials = await (options.store ?? authStore).get();
46889
+ const credentials = await resolveAuthCredentials({ store: options.store ?? authStore });
46633
46890
  if (options.foreground === true) {
46634
46891
  const result = await (options.runner ?? runLocalInvestigation)({
46635
46892
  query: options.query,
@@ -46754,43 +47011,6 @@ function defaultSleep(ms) {
46754
47011
  return new Promise((resolve) => setTimeout(resolve, ms));
46755
47012
  }
46756
47013
  //#endregion
46757
- //#region src/auth/open-browser.ts
46758
- async function openBrowser(url) {
46759
- const command = browserCommand(url);
46760
- if (command === null) return false;
46761
- return new Promise((resolve) => {
46762
- const child = spawn(command.command, command.args, {
46763
- detached: true,
46764
- stdio: "ignore",
46765
- shell: false
46766
- });
46767
- child.once("error", () => resolve(false));
46768
- child.once("spawn", () => {
46769
- child.unref();
46770
- resolve(true);
46771
- });
46772
- });
46773
- }
46774
- function browserCommand(url) {
46775
- if (process.platform === "darwin") return {
46776
- command: "open",
46777
- args: [url]
46778
- };
46779
- if (process.platform === "win32") return {
46780
- command: "cmd",
46781
- args: [
46782
- "/c",
46783
- "start",
46784
- "",
46785
- url
46786
- ]
46787
- };
46788
- return {
46789
- command: "xdg-open",
46790
- args: [url]
46791
- };
46792
- }
46793
- //#endregion
46794
47014
  //#region src/commands/login.ts
46795
47015
  const loginCommand = defineCommand({
46796
47016
  meta: {
@@ -46843,6 +47063,7 @@ async function runLogin(options) {
46843
47063
  const sessionLookup = await (options.getSession?.(token.access_token) ?? fetchCurrentSession({
46844
47064
  apiBaseUrl: options.apiBaseUrl,
46845
47065
  accessToken: token.access_token,
47066
+ tokenType: token.token_type,
46846
47067
  fetchFn: options.fetchFn
46847
47068
  }));
46848
47069
  const session = sessionLookup.data;
@@ -46946,14 +47167,38 @@ const whoamiCommand = defineCommand({
46946
47167
  async function runWhoami(options) {
46947
47168
  const store = options.store ?? authStore;
46948
47169
  const stdout = options.io?.stdout ?? process.stdout;
46949
- const credentials = await store.get();
47170
+ const credentials = await resolveAuthCredentials({ store });
46950
47171
  if (credentials === null) {
46951
47172
  if (options.json === true) writeJson(stdout, { authenticated: false });
46952
47173
  throw new CliError$1("Not logged in. Run `quire login`.", ExitCode.AuthFailure);
46953
47174
  }
47175
+ if (credentials.tokenType === "QuireApiKey") {
47176
+ const modelSourceBroker = await readModelSourceBrokerStatus({
47177
+ accessToken: credentials.accessToken,
47178
+ tokenType: credentials.tokenType,
47179
+ apiBaseUrl: credentials.apiBaseUrl,
47180
+ fetchFn: options.fetchFn,
47181
+ getModelSourceBrokerStatus: options.getModelSourceBrokerStatus
47182
+ });
47183
+ const actor = modelSourceBroker?.actor ?? null;
47184
+ if (options.json === true) {
47185
+ writeJson(stdout, {
47186
+ authenticated: true,
47187
+ authMethod: "api_token",
47188
+ apiBaseUrl: credentials.apiBaseUrl,
47189
+ actor,
47190
+ modelSourceBroker
47191
+ });
47192
+ return;
47193
+ }
47194
+ writeLine(stdout, `Authenticated with API token for ${formatActor(actor)}.`);
47195
+ writeLine(stdout, formatModelSourceBrokerStatus(modelSourceBroker));
47196
+ return;
47197
+ }
46954
47198
  const sessionLookup = await (options.getSession?.(credentials.accessToken) ?? fetchCurrentSession({
46955
47199
  apiBaseUrl: credentials.apiBaseUrl,
46956
47200
  accessToken: credentials.accessToken,
47201
+ tokenType: credentials.tokenType,
46957
47202
  fetchFn: options.fetchFn
46958
47203
  }));
46959
47204
  const session = sessionLookup.data;
@@ -46969,6 +47214,7 @@ async function runWhoami(options) {
46969
47214
  await store.set(refreshedCredentials);
46970
47215
  const modelSourceBroker = await readModelSourceBrokerStatus({
46971
47216
  accessToken: refreshedCredentials.accessToken,
47217
+ tokenType: refreshedCredentials.tokenType,
46972
47218
  apiBaseUrl: refreshedCredentials.apiBaseUrl,
46973
47219
  fetchFn: options.fetchFn,
46974
47220
  getModelSourceBrokerStatus: options.getModelSourceBrokerStatus
@@ -46991,12 +47237,16 @@ async function readModelSourceBrokerStatus(options) {
46991
47237
  return await (options.getModelSourceBrokerStatus?.(options.accessToken, options.apiBaseUrl) ?? fetchModelSourceBrokerStatus({
46992
47238
  apiBaseUrl: options.apiBaseUrl,
46993
47239
  accessToken: options.accessToken,
47240
+ tokenType: options.tokenType,
46994
47241
  fetchFn: options.fetchFn
46995
47242
  }));
46996
47243
  } catch {
46997
47244
  return null;
46998
47245
  }
46999
47246
  }
47247
+ function formatActor(actor) {
47248
+ return actor?.environmentName ?? actor?.triggerSource ?? "remote agent environment";
47249
+ }
47000
47250
  function formatUser(user) {
47001
47251
  if (user.name && user.email && user.name !== user.email) return `${user.name} <${user.email}>`;
47002
47252
  return user.email ?? user.name ?? user.id;
@@ -47011,13 +47261,14 @@ function formatModelSourceBrokerStatus(status) {
47011
47261
  }
47012
47262
  //#endregion
47013
47263
  //#region package.json
47014
- var version$1 = "0.0.2";
47264
+ var version$1 = "0.0.3";
47015
47265
  //#endregion
47016
47266
  //#region src/cli.ts
47017
47267
  const subCommands = {
47018
47268
  login: loginCommand,
47019
47269
  logout: logoutCommand,
47020
47270
  whoami: whoamiCommand,
47271
+ connect: connectCommand,
47021
47272
  doctor: doctorCommand,
47022
47273
  env: envCommand,
47023
47274
  investigate: investigateCommand,
@@ -47061,12 +47312,28 @@ function isHelpRequest(rawArgs) {
47061
47312
  return rawArgs.some((arg) => arg === "--help" || arg === "-h");
47062
47313
  }
47063
47314
  async function renderHelp(rawArgs) {
47064
- const commandName = rawArgs.find(isSubCommandName);
47065
- if (commandName !== void 0) return renderUsage(subCommands[commandName], cli);
47066
- return renderUsage(cli);
47315
+ const { command, parent } = resolveHelpCommand(rawArgs);
47316
+ return renderUsage(command, parent);
47317
+ }
47318
+ function resolveHelpCommand(rawArgs) {
47319
+ let command = cli;
47320
+ let parent;
47321
+ for (const arg of rawArgs) {
47322
+ if (arg.startsWith("-")) continue;
47323
+ const next = readSubCommand(command, arg);
47324
+ if (next === void 0) break;
47325
+ parent = command;
47326
+ command = next;
47327
+ }
47328
+ return parent === void 0 ? { command } : {
47329
+ command,
47330
+ parent
47331
+ };
47067
47332
  }
47068
- function isSubCommandName(value) {
47069
- return Object.hasOwn(subCommands, value);
47333
+ function readSubCommand(command, name) {
47334
+ const subCommandMap = command.subCommands;
47335
+ if (subCommandMap === void 0 || !Object.hasOwn(subCommandMap, name)) return;
47336
+ return subCommandMap[name];
47070
47337
  }
47071
47338
  async function runWorkerCommand(rawArgs) {
47072
47339
  const requestFlagIndex = rawArgs.indexOf("--request");