stub-auth 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/handler.js +1632 -2
  2. package/dist/index.js +1839 -32
  3. package/package.json +26 -7
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // core-auth/dist/opencode.js
2
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
3
- import { join as join4, dirname, resolve } from "path";
2
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
3
+ import { join as join7, dirname, resolve } from "path";
4
4
  import { homedir as homedir2 } from "os";
5
5
 
6
6
  // core-auth/dist/env.js
@@ -10,27 +10,139 @@ import { homedir } from "os";
10
10
  function getConfigDir() {
11
11
  return process.env.HUB_CONFIG_DIR || (existsSync(join(homedir(), ".claude")) ? join(homedir(), ".claude") : join(homedir(), ".config", "opencode"));
12
12
  }
13
+ function configFolder() {
14
+ return join(getConfigDir(), "config");
15
+ }
13
16
 
14
17
  // core-auth/dist/log.js
15
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
16
- import { join as join3 } from "path";
18
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync } from "fs";
19
+ import { join as join4 } from "path";
17
20
 
18
21
  // core-auth/dist/config.js
22
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
23
+ import { join as join3 } from "path";
24
+
25
+ // core-auth/dist/models-cache.js
19
26
  import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
20
27
  import { join as join2 } from "path";
28
+ var MODELS_FILE = "core-auth-models.json";
29
+ function cachePath() {
30
+ return join2(configFolder(), MODELS_FILE);
31
+ }
32
+ function readAll() {
33
+ try {
34
+ if (existsSync2(cachePath()))
35
+ return JSON.parse(readFileSync(cachePath(), "utf8")) || {};
36
+ } catch {
37
+ }
38
+ return {};
39
+ }
40
+ function readModelCache(providerId) {
41
+ const entry = readAll()[providerId];
42
+ return entry && entry.models ? entry : null;
43
+ }
44
+ function writeModelCache(providerId, entry) {
45
+ try {
46
+ const all = readAll();
47
+ all[providerId] = { ...entry, fetchedAt: entry.fetchedAt || 0 };
48
+ const dir = configFolder();
49
+ if (!existsSync2(dir))
50
+ mkdirSync(dir, { recursive: true });
51
+ writeFileSync(cachePath(), JSON.stringify(all, null, 2), "utf8");
52
+ } catch (e) {
53
+ log("model cache write failed: " + (e && e.message));
54
+ }
55
+ }
56
+ async function resolveProviderModels(def, ctx, nowMs) {
57
+ const providerId = def.id;
58
+ let catalog = null;
59
+ if (typeof def.fetchModels === "function" && ctx && ctx.hasAccounts) {
60
+ try {
61
+ const result = await def.fetchModels(ctx);
62
+ if (result && result.models && Object.keys(result.models).length > 0) {
63
+ catalog = { models: result.models, ranking: result.ranking || Object.keys(result.models), defaultModelId: result.defaultModelId };
64
+ }
65
+ } catch (e) {
66
+ log("fetchModels failed for " + providerId + ": " + e);
67
+ }
68
+ }
69
+ if (!catalog && def.models && Object.keys(def.models).length > 0) {
70
+ catalog = { models: def.models, ranking: Object.keys(def.models) };
71
+ }
72
+ if (!catalog) {
73
+ const cached = readModelCache(providerId);
74
+ return cached ? cached.models : {};
75
+ }
76
+ const prev = readModelCache(providerId) || {};
77
+ writeModelCache(providerId, {
78
+ models: catalog.models,
79
+ ranking: catalog.ranking,
80
+ defaultModelId: catalog.defaultModelId,
81
+ sorts: prev.sorts || [],
82
+ sortOrders: prev.sortOrders || {},
83
+ fetchedAt: nowMs || 0
84
+ });
85
+ return catalog.models;
86
+ }
87
+
88
+ // core-auth/dist/config.js
21
89
  function paths() {
22
90
  const dir = getConfigDir();
23
- return { preferred: join2(dir, "config", "core-auth.json"), fallback: join2(dir, "core-auth.json") };
91
+ return { preferred: join3(dir, "config", "core-auth.json"), fallback: join3(dir, "core-auth.json") };
24
92
  }
25
93
  function readConfig() {
26
94
  const { preferred, fallback } = paths();
27
- const p = existsSync2(preferred) ? preferred : existsSync2(fallback) ? fallback : null;
95
+ const p = existsSync3(preferred) ? preferred : existsSync3(fallback) ? fallback : null;
28
96
  try {
29
- return p ? JSON.parse(readFileSync(p, "utf8")) : {};
97
+ return p ? JSON.parse(readFileSync2(p, "utf8")) : {};
30
98
  } catch {
31
99
  return {};
32
100
  }
33
101
  }
102
+ function writeConfig(cfg) {
103
+ const { preferred } = paths();
104
+ try {
105
+ if (!existsSync3(configFolder()))
106
+ mkdirSync2(configFolder(), { recursive: true });
107
+ writeFileSync2(preferred, JSON.stringify(cfg, null, 2), "utf8");
108
+ } catch {
109
+ }
110
+ }
111
+ function getAutoSources(providerId) {
112
+ const cache = readModelCache(providerId);
113
+ const extra = (cache && Array.isArray(cache.sorts) ? cache.sorts : []).filter((s) => s && s.id);
114
+ return [{ id: "manual", label: "Manual" }, ...extra];
115
+ }
116
+ function getAutoConfig(providerId) {
117
+ const stored = (readConfig().auto || {})[providerId] || {};
118
+ const cache = readModelCache(providerId);
119
+ const catalogOrder = cache && cache.ranking || [];
120
+ const sortOrders = cache && cache.sortOrders || {};
121
+ const reconcile = (ids) => {
122
+ const out = (Array.isArray(ids) ? ids : []).filter((id) => catalogOrder.includes(id));
123
+ for (const id of catalogOrder)
124
+ if (!out.includes(id))
125
+ out.push(id);
126
+ return out;
127
+ };
128
+ const sources = getAutoSources(providerId);
129
+ const validIds = sources.map((s) => s.id);
130
+ const source = stored.source && validIds.includes(stored.source) ? stored.source : "manual";
131
+ const order = source === "manual" ? reconcile(stored.order && stored.order.length ? stored.order : catalogOrder) : reconcile(sortOrders[source] || catalogOrder);
132
+ const excluded = (Array.isArray(stored.excluded) ? stored.excluded : []).filter((id) => catalogOrder.includes(id));
133
+ return { order, excluded, source, sources };
134
+ }
135
+ function setAutoConfig(providerId, auto) {
136
+ const cfg = readConfig();
137
+ cfg.auto = cfg.auto || {};
138
+ const prev = cfg.auto[providerId] || {};
139
+ cfg.auto[providerId] = {
140
+ order: auto.order !== void 0 ? auto.order : prev.order || [],
141
+ excluded: auto.excluded !== void 0 ? auto.excluded : prev.excluded || [],
142
+ source: auto.source !== void 0 ? auto.source : prev.source || "manual"
143
+ };
144
+ writeConfig(cfg);
145
+ }
34
146
 
35
147
  // core-auth/dist/log.js
36
148
  var START_TIME = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").split(".")[0];
@@ -39,24 +151,1258 @@ function log(message) {
39
151
  return;
40
152
  try {
41
153
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
42
- const dir = join3(getConfigDir(), "logs", dateStr);
43
- if (!existsSync3(dir))
44
- mkdirSync2(dir, { recursive: true });
45
- appendFileSync(join3(dir, "core-auth-" + START_TIME + ".log"), "[" + (/* @__PURE__ */ new Date()).toISOString() + "] " + message + "\n");
154
+ const dir = join4(getConfigDir(), "logs", dateStr);
155
+ if (!existsSync4(dir))
156
+ mkdirSync3(dir, { recursive: true });
157
+ appendFileSync(join4(dir, "core-auth-" + START_TIME + ".log"), "[" + (/* @__PURE__ */ new Date()).toISOString() + "] " + message + "\n");
158
+ } catch {
159
+ }
160
+ }
161
+
162
+ // core-auth/dist/accounts.js
163
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, renameSync, openSync, closeSync, unlinkSync, statSync } from "fs";
164
+ import { join as join5 } from "path";
165
+ import { randomBytes } from "crypto";
166
+ var DEFAULT_FILE = "core-auth-accounts.json";
167
+ var LOCK_STALE_MS = 15 * 1e3;
168
+ var LOCK_WAIT_MS = 5 * 1e3;
169
+ var LOCK_POLL_MS = 25;
170
+ function storeFile(opts) {
171
+ return join5(opts && opts.dir || configFolder(), opts && opts.file || DEFAULT_FILE);
172
+ }
173
+ function ensureDir(opts) {
174
+ const dir = opts && opts.dir || configFolder();
175
+ if (!existsSync5(dir))
176
+ mkdirSync4(dir, { recursive: true });
177
+ }
178
+ function sleepSync(ms) {
179
+ try {
180
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
181
+ } catch {
182
+ }
183
+ }
184
+ function withLock(opts, fn) {
185
+ ensureDir(opts);
186
+ const lockPath = storeFile(opts) + ".lock";
187
+ const deadline = Date.now() + LOCK_WAIT_MS;
188
+ let handle = null;
189
+ while (handle === null) {
190
+ try {
191
+ handle = openSync(lockPath, "wx");
192
+ } catch (error) {
193
+ if (!error || error.code !== "EEXIST")
194
+ break;
195
+ try {
196
+ if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
197
+ unlinkSync(lockPath);
198
+ continue;
199
+ }
200
+ } catch {
201
+ }
202
+ if (Date.now() > deadline)
203
+ break;
204
+ sleepSync(LOCK_POLL_MS);
205
+ }
206
+ }
207
+ try {
208
+ return fn();
209
+ } finally {
210
+ if (handle !== null) {
211
+ try {
212
+ closeSync(handle);
213
+ } catch {
214
+ }
215
+ try {
216
+ unlinkSync(lockPath);
217
+ } catch {
218
+ }
219
+ }
220
+ }
221
+ }
222
+ function readStore(opts) {
223
+ try {
224
+ const file = storeFile(opts);
225
+ if (existsSync5(file))
226
+ return JSON.parse(readFileSync3(file, "utf8")) || {};
227
+ } catch {
228
+ }
229
+ return { version: 1, providers: {} };
230
+ }
231
+ function writeStore(store, opts) {
232
+ ensureDir(opts);
233
+ const file = storeFile(opts);
234
+ const tmp = file + "." + randomBytes(6).toString("hex") + ".tmp";
235
+ writeFileSync3(tmp, JSON.stringify(store, null, 2), { encoding: "utf8", mode: 384 });
236
+ renameSync(tmp, file);
237
+ }
238
+ function emptyPool() {
239
+ return { accounts: [], activeIndex: 0, activeIndexByLane: {} };
240
+ }
241
+ function poolFrom(store, provider) {
242
+ const p = store.providers && store.providers[provider];
243
+ if (!p || !Array.isArray(p.accounts))
244
+ return emptyPool();
245
+ return { accounts: p.accounts, activeIndex: p.activeIndex || 0, activeIndexByLane: p.activeIndexByLane || {} };
246
+ }
247
+ function loadAccounts(provider, opts) {
248
+ return poolFrom(readStore(opts), provider);
249
+ }
250
+ function saveAccounts(provider, pool, opts) {
251
+ withLock(opts, () => {
252
+ const store = readStore(opts);
253
+ store.version = 1;
254
+ store.providers = store.providers || {};
255
+ store.providers[provider] = {
256
+ accounts: pool.accounts || [],
257
+ activeIndex: pool.activeIndex || 0,
258
+ activeIndexByLane: pool.activeIndexByLane || {}
259
+ };
260
+ writeStore(store, opts);
261
+ });
262
+ }
263
+ function updateAccounts(provider, mutator, opts) {
264
+ const pool = withLock(opts, () => {
265
+ const store = readStore(opts);
266
+ store.version = 1;
267
+ store.providers = store.providers || {};
268
+ const current = poolFrom(store, provider);
269
+ mutator(current);
270
+ store.providers[provider] = {
271
+ accounts: current.accounts || [],
272
+ activeIndex: current.activeIndex || 0,
273
+ activeIndexByLane: current.activeIndexByLane || {}
274
+ };
275
+ writeStore(store, opts);
276
+ return current;
277
+ });
278
+ return pool;
279
+ }
280
+ function listAccounts(provider, opts) {
281
+ return loadAccounts(provider, opts).accounts;
282
+ }
283
+ function addAccount(provider, account, opts) {
284
+ updateAccounts(provider, (pool) => {
285
+ const i = pool.accounts.findIndex((a) => account.id && a.id === account.id || account.refresh && a.refresh === account.refresh);
286
+ if (i >= 0)
287
+ pool.accounts[i] = { ...pool.accounts[i], ...account };
288
+ else
289
+ pool.accounts.push(account);
290
+ }, opts);
291
+ }
292
+ function removeAccount(provider, id, opts) {
293
+ updateAccounts(provider, (pool) => {
294
+ pool.accounts = pool.accounts.filter((a) => a.id !== id);
295
+ }, opts);
296
+ }
297
+
298
+ // core-auth/dist/ui/ansi.js
299
+ var ANSI = {
300
+ hide: "\x1B[?25l",
301
+ show: "\x1B[?25h",
302
+ up: (n = 1) => `\x1B[${n}A`,
303
+ clearLine: "\x1B[2K",
304
+ clearScreen: "\x1B[2J",
305
+ moveTo: (row, col) => `\x1B[${row};${col}H`,
306
+ cyan: "\x1B[36m",
307
+ green: "\x1B[32m",
308
+ red: "\x1B[31m",
309
+ yellow: "\x1B[33m",
310
+ dim: "\x1B[2m",
311
+ bold: "\x1B[1m",
312
+ reset: "\x1B[0m"
313
+ };
314
+ function parseKey(data) {
315
+ const s = data.toString();
316
+ if (s === "\x1B[A" || s === "\x1BOA")
317
+ return "up";
318
+ if (s === "\x1B[B" || s === "\x1BOB")
319
+ return "down";
320
+ if (s === "\x1B[5~")
321
+ return "pageup";
322
+ if (s === "\x1B[6~")
323
+ return "pagedown";
324
+ if (s === "\x1B[H" || s === "\x1B[1~" || s === "\x1BOH")
325
+ return "home";
326
+ if (s === "\x1B[F" || s === "\x1B[4~" || s === "\x1BOF")
327
+ return "end";
328
+ if (s === "\r" || s === "\n")
329
+ return "enter";
330
+ if (s === "" || s === "\x1B")
331
+ return "escape";
332
+ return null;
333
+ }
334
+ function isTTY() {
335
+ return Boolean(process.stdin.isTTY);
336
+ }
337
+ function stripAnsi(s) {
338
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
339
+ }
340
+ function truncateAnsi(s, max) {
341
+ if (stripAnsi(s).length <= max)
342
+ return s;
343
+ let out = "", visible = 0, i = 0;
344
+ while (i < s.length && visible < max) {
345
+ if (s[i] === "\x1B") {
346
+ const end = s.indexOf("m", i);
347
+ if (end !== -1) {
348
+ out += s.slice(i, end + 1);
349
+ i = end + 1;
350
+ continue;
351
+ }
352
+ }
353
+ out += s[i];
354
+ visible++;
355
+ i++;
356
+ }
357
+ return out + ANSI.reset;
358
+ }
359
+
360
+ // core-auth/dist/ui/menu-render.js
361
+ import { createInterface as createInterface2 } from "node:readline/promises";
362
+
363
+ // core-auth/dist/ui/select.js
364
+ function colorCode(color) {
365
+ if (color === "red")
366
+ return ANSI.red;
367
+ if (color === "green")
368
+ return ANSI.green;
369
+ if (color === "yellow")
370
+ return ANSI.yellow;
371
+ if (color === "cyan")
372
+ return ANSI.cyan;
373
+ return "";
374
+ }
375
+ async function select(items, options) {
376
+ if (!isTTY())
377
+ throw new Error("Interactive select requires a TTY terminal");
378
+ const isSelectable = (i) => i && !i.disabled && !i.separator && i.kind !== "heading";
379
+ const enabled = items.filter(isSelectable);
380
+ if (enabled.length === 0)
381
+ throw new Error("All items disabled");
382
+ if (enabled.length === 1)
383
+ return enabled[0].value;
384
+ const { stdin: stdin2, stdout: stdout2 } = process;
385
+ let cursor = items.findIndex(isSelectable);
386
+ if (cursor === -1)
387
+ cursor = 0;
388
+ let renderedLines = 0;
389
+ const render = () => {
390
+ const columns = stdout2.columns ?? 80;
391
+ const rows = stdout2.rows ?? 24;
392
+ const shouldClear = options.clearScreen === true;
393
+ if (shouldClear)
394
+ stdout2.write(ANSI.clearScreen + ANSI.moveTo(1, 1));
395
+ else if (renderedLines > 0)
396
+ stdout2.write(ANSI.up(renderedLines));
397
+ let written = 0;
398
+ const writeLine = (line) => {
399
+ stdout2.write(`${ANSI.clearLine}${line}
400
+ `);
401
+ written += 1;
402
+ };
403
+ const subtitleLines = options.subtitle ? 3 : 0;
404
+ const fixed = 1 + subtitleLines + 2;
405
+ const maxVisible = Math.max(1, Math.min(items.length, rows - fixed - 1));
406
+ let start = 0, end = items.length;
407
+ if (items.length > maxVisible) {
408
+ start = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), items.length - maxVisible));
409
+ end = start + maxVisible;
410
+ }
411
+ writeLine(`${ANSI.dim}\u250C ${ANSI.reset}${truncateAnsi(options.message, Math.max(1, columns - 4))}`);
412
+ if (options.subtitle) {
413
+ writeLine(`${ANSI.dim}\u2502${ANSI.reset}`);
414
+ writeLine(`${ANSI.cyan}\u25C6${ANSI.reset} ${truncateAnsi(options.subtitle, Math.max(1, columns - 4))}`);
415
+ writeLine("");
416
+ }
417
+ for (let i = start; i < end; i++) {
418
+ const item = items[i];
419
+ if (!item)
420
+ continue;
421
+ if (item.separator) {
422
+ writeLine(`${ANSI.dim}\u2502${ANSI.reset}`);
423
+ continue;
424
+ }
425
+ if (item.kind === "heading") {
426
+ writeLine(`${ANSI.cyan}\u2502${ANSI.reset} ${truncateAnsi(`${ANSI.bold}${item.label}${ANSI.reset}`, Math.max(1, columns - 6))}`);
427
+ continue;
428
+ }
429
+ const selected = i === cursor;
430
+ const cc = colorCode(item.color);
431
+ let text = selected ? cc ? `${cc}${item.label}${ANSI.reset}` : item.label : `${ANSI.dim}${cc}${item.label}${ANSI.reset}`;
432
+ if (item.hint)
433
+ text += ` ${ANSI.dim}${item.hint}${ANSI.reset}`;
434
+ text = truncateAnsi(text, Math.max(1, columns - 8));
435
+ const marker = selected ? `${ANSI.green}\u25CF${ANSI.reset}` : `${ANSI.dim}\u25CB${ANSI.reset}`;
436
+ writeLine(`${ANSI.cyan}\u2502${ANSI.reset} ${marker} ${text}`);
437
+ }
438
+ const windowHint = items.length > end - start ? ` (${start + 1}-${end}/${items.length})` : "";
439
+ writeLine(`${ANSI.cyan}\u2502${ANSI.reset} ${ANSI.dim}${options.help ?? `\u2191\u2193 move \xB7 PgUp/PgDn fast \xB7 Enter select \xB7 Esc back${windowHint}`}${ANSI.reset}`);
440
+ writeLine(`${ANSI.cyan}\u2514${ANSI.reset}`);
441
+ renderedLines = written;
442
+ };
443
+ return new Promise((resolve2) => {
444
+ const wasRaw = stdin2.isRaw ?? false;
445
+ const cleanup = () => {
446
+ try {
447
+ stdin2.removeListener("data", onKey);
448
+ stdin2.setRawMode(wasRaw);
449
+ stdin2.pause();
450
+ stdout2.write(ANSI.show);
451
+ } catch {
452
+ }
453
+ process.removeListener("SIGINT", onSignal);
454
+ };
455
+ const onSignal = () => {
456
+ cleanup();
457
+ resolve2(null);
458
+ };
459
+ const nextSelectable = (from, dir) => {
460
+ let next = from;
461
+ do {
462
+ next = (next + dir + items.length) % items.length;
463
+ } while (!isSelectable(items[next]) && next !== from);
464
+ return next;
465
+ };
466
+ const nearestSelectable = (idx) => {
467
+ const clamped = Math.max(0, Math.min(items.length - 1, idx));
468
+ for (let d = 0; d < items.length; d++) {
469
+ if (clamped + d < items.length && isSelectable(items[clamped + d]))
470
+ return clamped + d;
471
+ if (clamped - d >= 0 && isSelectable(items[clamped - d]))
472
+ return clamped - d;
473
+ }
474
+ return cursor;
475
+ };
476
+ const page = () => Math.max(1, (stdout2.rows || 24) - 8);
477
+ const onKey = (data) => {
478
+ const action = parseKey(data);
479
+ if (action === "up") {
480
+ cursor = nextSelectable(cursor, -1);
481
+ render();
482
+ } else if (action === "down") {
483
+ cursor = nextSelectable(cursor, 1);
484
+ render();
485
+ } else if (action === "pageup") {
486
+ cursor = nearestSelectable(cursor - page());
487
+ render();
488
+ } else if (action === "pagedown") {
489
+ cursor = nearestSelectable(cursor + page());
490
+ render();
491
+ } else if (action === "home") {
492
+ cursor = nearestSelectable(0);
493
+ render();
494
+ } else if (action === "end") {
495
+ cursor = nearestSelectable(items.length - 1);
496
+ render();
497
+ } else if (action === "enter") {
498
+ cleanup();
499
+ resolve2(items[cursor]?.value ?? null);
500
+ } else if (action === "escape") {
501
+ cleanup();
502
+ resolve2(null);
503
+ }
504
+ };
505
+ process.once("SIGINT", onSignal);
506
+ try {
507
+ stdin2.setRawMode(true);
508
+ } catch {
509
+ resolve2(null);
510
+ return;
511
+ }
512
+ stdin2.resume();
513
+ stdout2.write(ANSI.hide);
514
+ render();
515
+ stdin2.on("data", onKey);
516
+ });
517
+ }
518
+
519
+ // core-auth/dist/ui/prompt.js
520
+ import { createInterface } from "node:readline/promises";
521
+ import { stdin, stdout } from "node:process";
522
+ async function prompt(message) {
523
+ const rl = createInterface({ input: stdin, output: stdout });
524
+ try {
525
+ const answer = await rl.question(message + " ");
526
+ const trimmed = (answer || "").trim();
527
+ return trimmed || null;
528
+ } finally {
529
+ rl.close();
530
+ }
531
+ }
532
+
533
+ // core-auth/dist/ui/menu-render.js
534
+ async function runMenu(rootBuilder) {
535
+ if (!isTTY())
536
+ return;
537
+ const stack = [rootBuilder];
538
+ const apply = (a) => {
539
+ if (!a)
540
+ return;
541
+ if (a.push)
542
+ stack.push(a.push);
543
+ else if (a.pop)
544
+ stack.pop();
545
+ else if (a.close)
546
+ stack.length = 0;
547
+ };
548
+ while (stack.length) {
549
+ const menu = stack[stack.length - 1]();
550
+ const items = menu.items.map((it, i) => ({
551
+ label: it.label,
552
+ hint: it.hint,
553
+ color: it.color,
554
+ kind: it.kind,
555
+ separator: it.separator,
556
+ value: i
557
+ }));
558
+ const choice = await select(items, { message: menu.title, subtitle: menu.subtitle, clearScreen: true });
559
+ if (choice === null || choice === void 0) {
560
+ stack.pop();
561
+ continue;
562
+ }
563
+ const item = menu.items[choice];
564
+ if (!item || typeof item.run !== "function")
565
+ continue;
566
+ let action;
567
+ try {
568
+ action = await item.run();
569
+ } catch (e) {
570
+ process.stderr.write(String(e) + "\n");
571
+ continue;
572
+ }
573
+ if (action && action.input) {
574
+ const inp = action.input;
575
+ if (inp.message)
576
+ process.stdout.write("\n" + inp.message + "\n");
577
+ let result;
578
+ if (inp.background) {
579
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
580
+ const pasteP = rl.question((inp.title || "Input") + ": ").then((t) => ({ paste: (t || "").trim() })).catch(() => ({ paste: null }));
581
+ const bgP = inp.background.then((account) => ({ bg: account }));
582
+ result = await Promise.race([pasteP, bgP]);
583
+ try {
584
+ rl.close();
585
+ } catch {
586
+ }
587
+ } else {
588
+ result = { paste: await prompt((inp.title || "Input") + ":") };
589
+ }
590
+ if (inp.onClose) {
591
+ try {
592
+ inp.onClose();
593
+ } catch {
594
+ }
595
+ }
596
+ try {
597
+ if (result.bg)
598
+ apply(result.bg);
599
+ else if (result.paste != null && String(result.paste).trim() !== "") {
600
+ if (inp.pendingLabel)
601
+ process.stdout.write("\n" + inp.pendingLabel + "\n");
602
+ apply(await inp.complete(String(result.paste).trim()));
603
+ }
604
+ } catch (e) {
605
+ process.stderr.write(String(e) + "\n");
606
+ }
607
+ continue;
608
+ }
609
+ apply(action);
610
+ }
611
+ }
612
+
613
+ // core-auth/dist/ui/confirm.js
614
+ async function confirm(message, defaultYes = false) {
615
+ const items = defaultYes ? [{ label: "Yes", value: true }, { label: "No", value: false }] : [{ label: "No", value: false }, { label: "Yes", value: true }];
616
+ const result = await select(items, { message });
617
+ return result ?? false;
618
+ }
619
+
620
+ // core-auth/dist/proxy/store.js
621
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, renameSync as renameSync2 } from "fs";
622
+ import { join as join6 } from "path";
623
+ import { randomBytes as randomBytes2 } from "crypto";
624
+ var FILE = "core-auth-proxies.json";
625
+ function storeFile2() {
626
+ return join6(configFolder(), FILE);
627
+ }
628
+ function empty() {
629
+ return { version: 1, mode: "disabled", providers: {}, proxies: [], assignments: {}, manualSelection: {} };
630
+ }
631
+ function loadProxyStore() {
632
+ try {
633
+ const f = storeFile2();
634
+ if (existsSync6(f))
635
+ return { ...empty(), ...JSON.parse(readFileSync4(f, "utf8")) || {} };
636
+ } catch {
637
+ }
638
+ return empty();
639
+ }
640
+ function saveProxyStore(store) {
641
+ try {
642
+ if (!existsSync6(configFolder()))
643
+ mkdirSync5(configFolder(), { recursive: true });
644
+ const file = storeFile2();
645
+ const tmp = file + "." + randomBytes2(6).toString("hex") + ".tmp";
646
+ writeFileSync4(tmp, JSON.stringify(store, null, 2), { encoding: "utf8", mode: 384 });
647
+ renameSync2(tmp, file);
648
+ } catch {
649
+ }
650
+ }
651
+ function updateProxyStore(mutator) {
652
+ const store = loadProxyStore();
653
+ mutator(store);
654
+ saveProxyStore(store);
655
+ return store;
656
+ }
657
+
658
+ // core-auth/dist/proxy/providers.js
659
+ async function getText(url, init) {
660
+ const aborter = new AbortController();
661
+ const timer = setTimeout(() => aborter.abort(), 1e4);
662
+ try {
663
+ const r = await fetch(url, { ...init || {}, signal: aborter.signal });
664
+ return r.ok ? await r.text() : "";
665
+ } catch {
666
+ return "";
667
+ } finally {
668
+ clearTimeout(timer);
669
+ }
670
+ }
671
+ function linesToProxies(text, provider) {
672
+ return text.split(/\s+/).map((l) => l.trim()).filter(Boolean).map((hostPort) => ({ url: hostPort.startsWith("http") ? hostPort : "http://" + hostPort, provider }));
673
+ }
674
+ async function proxyscrape() {
675
+ const text = await getText("https://api.proxyscrape.com/v2/?request=displayproxies&protocol=http&timeout=5000&country=all&ssl=all&anonymity=all");
676
+ return linesToProxies(text, "proxyscrape");
677
+ }
678
+ async function proxifly() {
679
+ const text = await getText("https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/protocols/http/data.txt");
680
+ return linesToProxies(text, "proxifly");
681
+ }
682
+ async function pubproxy() {
683
+ const text = await getText("http://pubproxy.com/api/proxy?limit=20&type=http&format=txt");
684
+ return linesToProxies(text, "pubproxy");
685
+ }
686
+ async function geonix() {
687
+ const text = await getText("https://cdn.jsdelivr.net/gh/TheSpeedX/PROXY-List@master/http.txt");
688
+ return linesToProxies(text, "geonix");
689
+ }
690
+ async function iplocate() {
691
+ const text = await getText("https://cdn.jsdelivr.net/gh/monosans/proxy-list@main/proxies/http.txt");
692
+ return linesToProxies(text, "iplocate");
693
+ }
694
+ async function keyed(provider, config) {
695
+ if (!config || !config.key)
696
+ return [];
697
+ return [];
698
+ }
699
+ var FREE = { proxyscrape, proxifly, pubproxy, geonix, iplocate };
700
+ var KEYED = ["webshare", "brightdata", "oxylabs", "litport"];
701
+ async function fetchEnabledProxies(providersConfig) {
702
+ const config = providersConfig || {};
703
+ const out = [];
704
+ for (const [name, fn] of Object.entries(FREE)) {
705
+ if (config[name] && config[name].enabled) {
706
+ try {
707
+ out.push(...await fn());
708
+ } catch {
709
+ }
710
+ }
711
+ }
712
+ for (const name of KEYED) {
713
+ if (config[name] && config[name].enabled) {
714
+ try {
715
+ out.push(...await keyed(name, config[name]));
716
+ } catch {
717
+ }
718
+ }
719
+ }
720
+ return out;
721
+ }
722
+ var PROXY_PROVIDERS = ["manual", ...Object.keys(FREE), ...KEYED];
723
+
724
+ // core-auth/dist/proxy/manager.js
725
+ var MAX_ACCOUNTS_PER_PROXY = 3;
726
+ function countAssignments(store, url) {
727
+ return Object.values(store.assignments || {}).filter((u) => u === url).length;
728
+ }
729
+ function scoreOf(store, proxy) {
730
+ const s = proxy.stats || {};
731
+ const checks = s.checks || 0;
732
+ const failRate = checks ? (s.failures || 0) / checks : 0.5;
733
+ const inUse = countAssignments(store, proxy.url);
734
+ return (s.avgLatencyMs || 2e3) / 1e3 + failRate * 10 + (s.ipRateLimitHits || 0) * 20 + inUse * 5 - (proxy.provider === "manual" ? 10 : 0);
735
+ }
736
+ var ProxyManager = class {
737
+ load() {
738
+ return loadProxyStore();
739
+ }
740
+ getMode() {
741
+ return this.load().mode || "disabled";
742
+ }
743
+ setMode(mode) {
744
+ updateProxyStore((s) => {
745
+ s.mode = mode;
746
+ });
747
+ }
748
+ enableProvider(name, on, key) {
749
+ updateProxyStore((s) => {
750
+ s.providers[name] = { ...s.providers[name] || {}, enabled: !!on, ...key !== void 0 ? { key } : {} };
751
+ });
752
+ }
753
+ providersConfig() {
754
+ return this.load().providers || {};
755
+ }
756
+ // proxies sorted best-first (lowest score)
757
+ list() {
758
+ const store = this.load();
759
+ return [...store.proxies].map((p) => ({ ...p, score: scoreOf(store, p), inUse: countAssignments(store, p.url) })).sort((a, b) => a.score - b.score);
760
+ }
761
+ // global view (account-only proxies are excluded; they show in their account's menu)
762
+ byProvider() {
763
+ const groups = {};
764
+ for (const p of this.list())
765
+ if (!p.owner)
766
+ (groups[p.provider] = groups[p.provider] || []).push(p);
767
+ return groups;
768
+ }
769
+ // proxies visible to one account: all global + the account's own
770
+ accountProxies(accountId) {
771
+ return this.list().filter((p) => !p.owner || p.owner === accountId);
772
+ }
773
+ get(url) {
774
+ return this.list().find((p) => p.url === url) || null;
775
+ }
776
+ // owner set -> account-only (visible + auto-used for that account only); else global
777
+ addManual(url, owner) {
778
+ const clean = url.startsWith("http") ? url : "http://" + url;
779
+ updateProxyStore((s) => {
780
+ if (!s.proxies.find((p) => p.url === clean))
781
+ s.proxies.push({ url: clean, provider: "manual", owner: owner || void 0, addedAt: Date.now(), stats: { checks: 0, failures: 0, avgLatencyMs: 0, ipRateLimitHits: 0, lastOkAt: 0 } });
782
+ });
783
+ return clean;
784
+ }
785
+ remove(url) {
786
+ updateProxyStore((s) => {
787
+ s.proxies = s.proxies.filter((p) => p.url !== url);
788
+ for (const [acc, u] of Object.entries(s.assignments))
789
+ if (u === url)
790
+ delete s.assignments[acc];
791
+ for (const acc of Object.keys(s.manualSelection))
792
+ s.manualSelection[acc] = (s.manualSelection[acc] || []).filter((u) => u !== url);
793
+ });
794
+ }
795
+ // manual-mode per-account selection (urls from the pool)
796
+ getAccountSelection(accountId) {
797
+ return this.load().manualSelection[accountId] || [];
798
+ }
799
+ setAccountSelection(accountId, urls) {
800
+ updateProxyStore((s) => {
801
+ s.manualSelection[accountId] = urls;
802
+ });
803
+ }
804
+ // pick a proxy url for an account per mode; sticks until freed (rate-limit) or cap-bound
805
+ selectForAccount(accountId) {
806
+ const store = this.load();
807
+ if (store.mode === "disabled")
808
+ return null;
809
+ if (store.mode === "manual") {
810
+ const owned = store.proxies.filter((p) => p.owner === accountId).map((p) => p.url);
811
+ const pool = [.../* @__PURE__ */ new Set([...store.manualSelection[accountId] || [], ...owned])].filter((u) => store.proxies.find((p) => p.url === u));
812
+ if (!pool.length)
813
+ return null;
814
+ const current2 = store.assignments[accountId];
815
+ if (current2 && pool.includes(current2))
816
+ return current2;
817
+ const chosen2 = pool[0];
818
+ updateProxyStore((s) => {
819
+ s.assignments[accountId] = chosen2;
820
+ });
821
+ return chosen2;
822
+ }
823
+ const current = store.assignments[accountId];
824
+ if (current && store.proxies.find((p) => p.url === current))
825
+ return current;
826
+ const candidates = store.proxies.filter((p) => (!p.owner || p.owner === accountId) && countAssignments(store, p.url) < MAX_ACCOUNTS_PER_PROXY).sort((a, b) => scoreOf(store, a) - scoreOf(store, b));
827
+ if (!candidates.length)
828
+ return null;
829
+ const chosen = candidates[0].url;
830
+ updateProxyStore((s) => {
831
+ s.assignments[accountId] = chosen;
832
+ });
833
+ return chosen;
834
+ }
835
+ // pick a proxy for login traffic before an account exists (no id to bind yet);
836
+ // returns the best globally-available proxy, or null when none/disabled
837
+ pickForLogin() {
838
+ const store = this.load();
839
+ if (store.mode === "disabled")
840
+ return null;
841
+ const candidates = store.proxies.filter((p) => !p.owner && countAssignments(store, p.url) < MAX_ACCOUNTS_PER_PROXY).sort((a, b) => scoreOf(store, a) - scoreOf(store, b));
842
+ return candidates.length ? candidates[0].url : null;
843
+ }
844
+ // stick the login proxy to the account so refresh + requests reuse it; in manual
845
+ // mode also record it in the account's selection so selectForAccount keeps it
846
+ bindAccountProxy(accountId, url) {
847
+ if (!url)
848
+ return;
849
+ updateProxyStore((s) => {
850
+ if (s.mode === "manual") {
851
+ const selection = s.manualSelection[accountId] || [];
852
+ if (!selection.includes(url))
853
+ selection.push(url);
854
+ s.manualSelection[accountId] = selection;
855
+ }
856
+ s.assignments[accountId] = url;
857
+ });
858
+ }
859
+ reportRateLimit(url) {
860
+ updateProxyStore((s) => {
861
+ const p = s.proxies.find((x) => x.url === url);
862
+ if (p) {
863
+ p.stats = p.stats || {};
864
+ p.stats.ipRateLimitHits = (p.stats.ipRateLimitHits || 0) + 1;
865
+ p.stats.lastRateLimitAt = Date.now();
866
+ }
867
+ for (const [acc, u] of Object.entries(s.assignments))
868
+ if (u === url)
869
+ delete s.assignments[acc];
870
+ });
871
+ }
872
+ reportResult(url, ok, latencyMs) {
873
+ updateProxyStore((s) => {
874
+ const p = s.proxies.find((x) => x.url === url);
875
+ if (!p)
876
+ return;
877
+ const st = p.stats = p.stats || { checks: 0, failures: 0, avgLatencyMs: 0, ipRateLimitHits: 0 };
878
+ st.checks = (st.checks || 0) + 1;
879
+ if (!ok)
880
+ st.failures = (st.failures || 0) + 1;
881
+ else {
882
+ st.lastOkAt = Date.now();
883
+ if (typeof latencyMs === "number")
884
+ st.avgLatencyMs = st.avgLatencyMs ? Math.round(st.avgLatencyMs * 0.7 + latencyMs * 0.3) : latencyMs;
885
+ }
886
+ });
887
+ }
888
+ async refresh() {
889
+ const fetched = await fetchEnabledProxies(this.providersConfig());
890
+ updateProxyStore((s) => {
891
+ const have = new Set(s.proxies.map((p) => p.url));
892
+ for (const f of fetched)
893
+ if (!have.has(f.url)) {
894
+ s.proxies.push({ url: f.url, provider: f.provider, addedAt: Date.now(), stats: { checks: 0, failures: 0, avgLatencyMs: 0, ipRateLimitHits: 0, lastOkAt: 0 } });
895
+ have.add(f.url);
896
+ }
897
+ });
898
+ return fetched.length;
899
+ }
900
+ };
901
+ var proxyManager = new ProxyManager();
902
+
903
+ // core-auth/dist/ui/proxy-menu.js
904
+ function fmtScore(n) {
905
+ return (Math.round(n * 10) / 10).toString();
906
+ }
907
+ async function selectAccountProxies(accountId) {
908
+ if (!isTTY())
909
+ return;
910
+ while (true) {
911
+ const selected = new Set(proxyManager.getAccountSelection(accountId));
912
+ const all = proxyManager.accountProxies(accountId);
913
+ const items = [
914
+ { label: "Done", value: { t: "done" } },
915
+ { label: "Add proxy", value: { t: "add" }, color: "cyan" }
916
+ ];
917
+ for (const p of all) {
918
+ const owned = p.owner === accountId;
919
+ const on = owned || selected.has(p.url);
920
+ items.push({ label: (on ? "[x] " : "[ ] ") + p.url + (owned ? " (this account)" : ""), hint: p.provider + " \xB7 score " + fmtScore(p.score), value: { t: "toggle", url: p.url, owned } });
921
+ }
922
+ const result = await select(items, { message: "Proxies for " + accountId, subtitle: "manual mode \xB7 [x] = used by this account", clearScreen: true });
923
+ if (!result || result.t === "done")
924
+ return;
925
+ if (result.t === "add") {
926
+ const url = await prompt("Proxy URL (host:port or http://...):");
927
+ if (!url)
928
+ continue;
929
+ const scope = await select([{ label: "Global (all accounts)", value: "global" }, { label: "This account only", value: "account" }], { message: "Proxy visibility", clearScreen: true });
930
+ if (scope === "account")
931
+ proxyManager.addManual(url, accountId);
932
+ else if (scope === "global") {
933
+ const clean = proxyManager.addManual(url);
934
+ proxyManager.setAccountSelection(accountId, [...selected, clean]);
935
+ }
936
+ } else if (result.t === "toggle") {
937
+ if (result.owned) {
938
+ if (await confirm("Remove this account-only proxy?"))
939
+ proxyManager.remove(result.url);
940
+ } else {
941
+ if (selected.has(result.url))
942
+ selected.delete(result.url);
943
+ else
944
+ selected.add(result.url);
945
+ proxyManager.setAccountSelection(accountId, [...selected]);
946
+ }
947
+ }
948
+ }
949
+ }
950
+
951
+ // core-auth/dist/browser.js
952
+ import { spawn } from "node:child_process";
953
+ function openBrowser(url) {
954
+ if (!url)
955
+ return;
956
+ try {
957
+ const platform = process.platform;
958
+ const command = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
959
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
960
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
961
+ child.on("error", () => {
962
+ });
963
+ child.unref();
46
964
  } catch {
47
965
  }
48
966
  }
49
967
 
968
+ // core-auth/dist/ui/url-auth.js
969
+ async function buildLoginInput(def) {
970
+ const flow = await def.loginFlow({ configDir: getConfigDir(), log });
971
+ openBrowser(flow.url);
972
+ return {
973
+ input: {
974
+ title: "Sign in to " + def.label,
975
+ message: (flow.instructions || "Approve in your browser, then paste the authorization code here.") + (flow.url ? "\n\n" + flow.url : ""),
976
+ // shown while complete() runs — the token exchange + project discovery can
977
+ // take ~10-15s (proxied), so the field reports progress instead of vanishing
978
+ pendingLabel: "Adding account\u2026 (exchanging the code, this can take a few seconds)",
979
+ // paste fallback: trade the pasted code/redirect URL for an account
980
+ complete: async (text) => {
981
+ await flow.complete(text);
982
+ return { refresh: true };
983
+ },
984
+ // primary path: the loopback listener auto-completes the input when it fires
985
+ background: flow.loopback ? flow.loopback.then((account) => account ? { refresh: true } : null).catch(() => null) : null,
986
+ // release the listener when the input is dismissed / superseded
987
+ onClose: typeof flow.cancel === "function" ? flow.cancel : void 0
988
+ }
989
+ };
990
+ }
991
+
992
+ // core-auth/dist/ui/settings-menu.js
993
+ function formatValue(field, value) {
994
+ if (field.type === "bool")
995
+ return value ? "on" : "off";
996
+ if (value === void 0 || value === null || value === "")
997
+ return "default";
998
+ return String(value);
999
+ }
1000
+ function fieldItem(def, field) {
1001
+ const settings = def.settings;
1002
+ const value = settings.get(field.key);
1003
+ const label = field.label + " [" + formatValue(field, value) + "]";
1004
+ if (field.type === "bool") {
1005
+ return { label, hint: field.hint || "", run: () => {
1006
+ settings.set(field.key, !value);
1007
+ return { refresh: true };
1008
+ } };
1009
+ }
1010
+ if (field.type === "enum") {
1011
+ const options = field.options || [];
1012
+ return { label, hint: field.hint || "", run: () => {
1013
+ const i = options.indexOf(value);
1014
+ settings.set(field.key, options[(i + 1) % options.length]);
1015
+ return { refresh: true };
1016
+ } };
1017
+ }
1018
+ const range = field.type === "number" && (field.min != null || field.max != null) ? " (range " + (field.min != null ? field.min : "") + "\u2013" + (field.max != null ? field.max : "") + ")" : "";
1019
+ return {
1020
+ label,
1021
+ hint: field.hint || "",
1022
+ run: () => ({ input: {
1023
+ title: field.label,
1024
+ message: (field.hint ? field.hint + "\n\n" : "") + "Current: " + formatValue(field, value) + range + "\n\nEnter a new value (blank resets to default):",
1025
+ complete: (text) => {
1026
+ const t = (text || "").trim();
1027
+ if (t === "") {
1028
+ settings.set(field.key, void 0);
1029
+ return { refresh: true };
1030
+ }
1031
+ if (field.type === "number") {
1032
+ const n = Number(t);
1033
+ if (!isFinite(n))
1034
+ return { refresh: true };
1035
+ if (field.min != null && n < field.min)
1036
+ return { refresh: true };
1037
+ if (field.max != null && n > field.max)
1038
+ return { refresh: true };
1039
+ settings.set(field.key, n);
1040
+ } else {
1041
+ settings.set(field.key, t);
1042
+ }
1043
+ return { refresh: true };
1044
+ }
1045
+ } })
1046
+ };
1047
+ }
1048
+ function buildSettingsMenu(def, groupIndex) {
1049
+ const groups = def.settings && def.settings.groups || [];
1050
+ if (groupIndex === void 0 && groups.length > 1) {
1051
+ const items2 = [{ label: "Back", run: () => ({ pop: true }) }];
1052
+ groups.forEach((g, i) => items2.push({ label: g.title, hint: (g.fields || []).length + " options", run: () => ({ push: () => buildSettingsMenu(def, i) }) }));
1053
+ return { title: def.label + " \u2014 Settings", subtitle: "Pick a section \xB7 changes apply on restart \xB7 Esc to go back", items: items2 };
1054
+ }
1055
+ const group = groups[groupIndex || 0] || { title: "Settings", fields: [] };
1056
+ const items = [{ label: "Back", run: () => ({ pop: true }) }];
1057
+ for (const field of group.fields)
1058
+ items.push(fieldItem(def, field));
1059
+ return { title: def.label + " \u2014 " + group.title, subtitle: "Enter to change \xB7 blank input resets to default \xB7 applies on restart", items };
1060
+ }
1061
+
1062
+ // core-auth/dist/ui/menu-model.js
1063
+ function buildProxyDetail(url) {
1064
+ return { title: url, items: [
1065
+ { label: "Back", run: () => ({ pop: true }) },
1066
+ { label: "Remove this proxy", color: "red", suspend: true, run: async () => {
1067
+ if (await confirm("Remove " + url + "?")) {
1068
+ proxyManager.remove(url);
1069
+ return { pop: true };
1070
+ }
1071
+ return { refresh: true };
1072
+ } }
1073
+ ] };
1074
+ }
1075
+ function buildProxyMenu() {
1076
+ const mode = proxyManager.getMode();
1077
+ const grouped = proxyManager.byProvider() || {};
1078
+ const items = [
1079
+ { label: "Back", run: () => ({ pop: true }) },
1080
+ { label: "Mode: " + mode, color: "cyan", run: () => {
1081
+ const order = ["automatic", "manual", "disabled"];
1082
+ const i = order.indexOf(mode);
1083
+ proxyManager.setMode(order[(i + 1) % order.length]);
1084
+ return { refresh: true };
1085
+ } },
1086
+ { label: "Add proxy", color: "green", run: () => ({ input: { title: "Proxy URL", message: "Enter a proxy (host:port or http://...)", complete: (url) => {
1087
+ proxyManager.addManual(url);
1088
+ return { refresh: true };
1089
+ } } }) },
1090
+ { label: "Refresh from providers", color: "cyan", suspend: true, run: async () => {
1091
+ try {
1092
+ await proxyManager.refresh();
1093
+ } catch {
1094
+ }
1095
+ return { refresh: true };
1096
+ } }
1097
+ ];
1098
+ for (const provider of Object.keys(grouped)) {
1099
+ const list = grouped[provider] || [];
1100
+ if (!list.length)
1101
+ continue;
1102
+ items.push({ label: "", separator: true });
1103
+ items.push({ label: provider + " (" + list.length + ")", kind: "heading" });
1104
+ for (const p of list)
1105
+ items.push({ label: p.url, hint: "score " + (typeof p.score === "number" ? p.score.toFixed(2) : "?") + " \xB7 in-use " + (p.inUse || 0), run: /* @__PURE__ */ ((u) => () => ({ push: () => buildProxyDetail(u) }))(p.url) });
1106
+ }
1107
+ return { title: "Proxies", subtitle: "mode: " + mode + " \xB7 Esc to go back", items };
1108
+ }
1109
+ var STATUS = {
1110
+ active: "[active]",
1111
+ "rate-limited": "[rate-limited]",
1112
+ "cooling-down": "[cooling]",
1113
+ "verification-required": "[needs verification]",
1114
+ disabled: "[disabled]"
1115
+ };
1116
+ function modelName(providerId, id) {
1117
+ const cache = readModelCache(providerId);
1118
+ const m = cache && cache.models && cache.models[id];
1119
+ return m && m.name || id;
1120
+ }
1121
+ function buildAutoModelEdit(def, id) {
1122
+ const providerId = def.id;
1123
+ const { order, excluded, source } = getAutoConfig(providerId);
1124
+ const included = !excluded.includes(id);
1125
+ const pos = order.indexOf(id);
1126
+ const items = [
1127
+ { label: "Back", run: () => ({ pop: true }) },
1128
+ {
1129
+ label: included ? "Exclude" : "Include",
1130
+ color: included ? "yellow" : "green",
1131
+ run: () => {
1132
+ setAutoConfig(providerId, { excluded: included ? [...excluded, id] : excluded.filter((x) => x !== id) });
1133
+ return { pop: true };
1134
+ }
1135
+ }
1136
+ ];
1137
+ if (source === "manual") {
1138
+ items.push({ label: "Move up", run: () => {
1139
+ if (pos > 0) {
1140
+ const n = order.slice();
1141
+ [n[pos - 1], n[pos]] = [n[pos], n[pos - 1]];
1142
+ setAutoConfig(providerId, { order: n });
1143
+ }
1144
+ return { pop: true };
1145
+ } });
1146
+ items.push({ label: "Move down", run: () => {
1147
+ if (pos >= 0 && pos < order.length - 1) {
1148
+ const n = order.slice();
1149
+ [n[pos + 1], n[pos]] = [n[pos], n[pos + 1]];
1150
+ setAutoConfig(providerId, { order: n });
1151
+ }
1152
+ return { pop: true };
1153
+ } });
1154
+ }
1155
+ return { title: modelName(providerId, id), items };
1156
+ }
1157
+ function buildAutoMenu(def) {
1158
+ const providerId = def.id;
1159
+ const { order, excluded, source, sources } = getAutoConfig(providerId);
1160
+ const current = sources.find((s) => s.id === source) || sources[0] || { id: "manual", label: "Manual" };
1161
+ const items = [];
1162
+ if (sources.length > 1) {
1163
+ items.push({
1164
+ label: "Sort: " + current.label,
1165
+ color: "cyan",
1166
+ run: () => {
1167
+ const i = sources.findIndex((s) => s.id === source);
1168
+ setAutoConfig(providerId, { source: sources[(i + 1) % sources.length].id });
1169
+ return { refresh: true };
1170
+ }
1171
+ });
1172
+ }
1173
+ if (source === "manual")
1174
+ items.push({ label: "Reset to default order", color: "yellow", run: () => {
1175
+ setAutoConfig(providerId, { order: [] });
1176
+ return { refresh: true };
1177
+ } });
1178
+ items.push({ label: "", separator: true });
1179
+ items.push({ label: "Models (top = preferred)", kind: "heading" });
1180
+ order.forEach((id, i) => {
1181
+ const inc = !excluded.includes(id);
1182
+ items.push({ label: (inc ? "[x] " : "[ ] ") + (i + 1) + ". " + modelName(providerId, id), hint: inc ? "" : "excluded", run: () => ({ push: () => buildAutoModelEdit(def, id) }) });
1183
+ });
1184
+ const sub = source === "manual" ? "Tries these top-to-bottom, skipping rate-limited ones. Enter a model to reorder/include." : "Order is automatic (" + current.label + "). Enter a model to include/exclude.";
1185
+ return { title: def.label + " \u2014 Auto model ranking", subtitle: sub, items };
1186
+ }
1187
+ function buildAccountDetail(def, view) {
1188
+ const controller = def.accounts;
1189
+ const proxies = !!def.proxies;
1190
+ const label = view.email || view.id;
1191
+ const extra = typeof controller.accountActions === "function" ? controller.accountActions(view) : [];
1192
+ const items = [
1193
+ { label: "Back", run: () => ({ pop: true }) },
1194
+ { label: view.enabled === false ? "Enable" : "Disable", color: view.enabled === false ? "green" : "yellow", run: () => {
1195
+ controller.enable(view.id, view.enabled === false);
1196
+ return { pop: true };
1197
+ } }
1198
+ ];
1199
+ if (proxies)
1200
+ items.push({ label: "Select proxies", color: "cyan", suspend: true, run: async () => {
1201
+ await selectAccountProxies(view.id);
1202
+ return { pop: true };
1203
+ } });
1204
+ extra.forEach((a) => items.push({ label: a.label, color: a.color || "cyan", suspend: true, run: async () => {
1205
+ try {
1206
+ await a.run();
1207
+ } catch {
1208
+ }
1209
+ return { pop: true };
1210
+ } }));
1211
+ items.push({ label: "Remove", color: "red", suspend: true, run: async () => {
1212
+ if (await confirm(`Remove ${label}?`)) {
1213
+ controller.remove(view.id);
1214
+ return { pop: true };
1215
+ }
1216
+ return { refresh: true };
1217
+ } });
1218
+ return { title: label + (STATUS[view.status] ? " " + STATUS[view.status] : ""), items };
1219
+ }
1220
+ function buildAccountMenu(def) {
1221
+ const controller = def.accounts;
1222
+ const proxies = !!def.proxies;
1223
+ const views = controller.list();
1224
+ const extraActions = (typeof controller.actions === "function" ? controller.actions() : []).slice();
1225
+ if (readModelCache(def.id))
1226
+ extraActions.push({ label: "Configure Auto models", color: "cyan", auto: true });
1227
+ const addAccount2 = typeof def.loginFlow === "function" ? { label: "Add account", color: "cyan", run: () => buildLoginInput(def) } : { label: "Add account", color: "cyan", suspend: true, run: async () => {
1228
+ try {
1229
+ await controller.login();
1230
+ } catch (e) {
1231
+ process.stderr.write(String(e) + "\n");
1232
+ }
1233
+ return { refresh: true };
1234
+ } };
1235
+ const items = [{ label: "Actions", kind: "heading" }, addAccount2];
1236
+ if (typeof controller.refreshQuota === "function")
1237
+ items.push({ label: "Refresh quotas", color: "cyan", suspend: true, run: async () => {
1238
+ try {
1239
+ await controller.refreshQuota();
1240
+ } catch {
1241
+ }
1242
+ return { refresh: true };
1243
+ } });
1244
+ if (proxies)
1245
+ items.push({ label: "Manage proxies", color: "cyan", run: () => ({ push: () => buildProxyMenu() }) });
1246
+ if (def.settings && (def.settings.groups || []).length)
1247
+ items.push({ label: "Settings", color: "cyan", run: () => ({ push: () => buildSettingsMenu(def) }) });
1248
+ extraActions.forEach((a) => {
1249
+ if (a.auto)
1250
+ items.push({ label: a.label, color: a.color || "cyan", run: () => ({ push: () => buildAutoMenu(def) }) });
1251
+ else
1252
+ items.push({ label: a.label, color: a.color || "cyan", suspend: true, run: async () => {
1253
+ try {
1254
+ await a.run();
1255
+ } catch (e) {
1256
+ process.stderr.write(String(e) + "\n");
1257
+ }
1258
+ return { refresh: true };
1259
+ } });
1260
+ });
1261
+ items.push({ label: "", separator: true });
1262
+ items.push({ label: `Accounts (${views.length})`, kind: "heading" });
1263
+ for (const view of views) {
1264
+ items.push({ label: `${view.email || view.id}${STATUS[view.status] ? " " + STATUS[view.status] : ""}`, hint: view.detail || "", run: () => ({ push: () => buildAccountDetail(def, view) }) });
1265
+ }
1266
+ if (views.length > 0) {
1267
+ items.push({ label: "", separator: true });
1268
+ items.push({ label: "Delete all accounts", color: "red", suspend: true, run: async () => {
1269
+ if (await confirm("Delete ALL accounts? This cannot be undone.")) {
1270
+ for (const v of controller.list())
1271
+ controller.remove(v.id);
1272
+ }
1273
+ return { refresh: true };
1274
+ } });
1275
+ }
1276
+ return { title: def.label + " accounts", subtitle: "Esc to exit \xB7 Enter an action or account", items, providerLabel: def.label };
1277
+ }
1278
+
1279
+ // core-auth/dist/menu.js
1280
+ async function runProviderMenu(def) {
1281
+ if (!def || !def.accounts || !isTTY())
1282
+ return;
1283
+ await runMenu(() => buildAccountMenu(def));
1284
+ }
1285
+
1286
+ // core-auth/dist/leaderboard.js
1287
+ function apiKey() {
1288
+ const fromEnv = (process.env.ARTIFICIAL_ANALYSIS_API_KEY || "").trim();
1289
+ if (fromEnv)
1290
+ return fromEnv;
1291
+ const cfg = readConfig().leaderboard || {};
1292
+ return String(cfg.apiKey || "").trim();
1293
+ }
1294
+ function normalize(name) {
1295
+ return String(name || "").toLowerCase().replace(/-(minimal|low|medium|high|thinking|agent|extra-low|preview|customtools)\b/g, "").replace(/[^a-z0-9]/g, "");
1296
+ }
1297
+ function extractScores(payload) {
1298
+ const rows = Array.isArray(payload) ? payload : payload && (payload.data || payload.models || payload.results);
1299
+ if (!Array.isArray(rows))
1300
+ return [];
1301
+ const out = [];
1302
+ for (const r of rows) {
1303
+ if (!r)
1304
+ continue;
1305
+ const name = r.name || r.model_name || r.slug || r.id || r.model;
1306
+ const score = r.intelligenceIndex ?? r.intelligence_index ?? r.intelligence ?? (r.evaluations && (r.evaluations.artificial_analysis_intelligence_index ?? r.evaluations.intelligence_index)) ?? r.quality ?? r.elo ?? r.score;
1307
+ if (name && typeof score === "number")
1308
+ out.push({ name: String(name), score });
1309
+ }
1310
+ return out;
1311
+ }
1312
+ var QUALITY_HINTS = [
1313
+ [/opus/i, 100],
1314
+ [/gemini-3\.1-pro|gemini-3-pro|pro-agent/i, 92],
1315
+ [/sonnet/i, 85],
1316
+ [/gpt|oss/i, 75],
1317
+ [/gemini-3\.5-flash|gemini-3-flash/i, 58],
1318
+ [/flash-lite|flash-extra-low/i, 45],
1319
+ [/flash/i, 55]
1320
+ ];
1321
+ function heuristicScore(id) {
1322
+ for (const [re, score] of QUALITY_HINTS)
1323
+ if (re.test(id))
1324
+ return score;
1325
+ return 50;
1326
+ }
1327
+ function heuristicOrder(candidateIds) {
1328
+ return candidateIds.map((id, i) => ({ id, i, score: heuristicScore(id) })).sort((a, b) => b.score - a.score || a.i - b.i).map((s) => s.id);
1329
+ }
1330
+ async function computeLeaderboardOrder(candidateIds) {
1331
+ const key = apiKey();
1332
+ if (!key)
1333
+ return heuristicOrder(candidateIds);
1334
+ let scores = [];
1335
+ try {
1336
+ const response = await fetch("https://artificialanalysis.ai/api/v2/data/llms/models", {
1337
+ headers: { "x-api-key": key, Accept: "application/json" }
1338
+ });
1339
+ if (!response.ok) {
1340
+ log("leaderboard fetch " + response.status);
1341
+ return heuristicOrder(candidateIds);
1342
+ }
1343
+ scores = extractScores(await response.json());
1344
+ } catch (error) {
1345
+ log("leaderboard fetch failed: " + error);
1346
+ return heuristicOrder(candidateIds);
1347
+ }
1348
+ if (!scores.length)
1349
+ return heuristicOrder(candidateIds);
1350
+ const normScores = scores.map((s) => ({ norm: normalize(s.name), score: s.score }));
1351
+ const scoreFor = (id) => {
1352
+ const n = normalize(id);
1353
+ let best = -1;
1354
+ for (const s of normScores) {
1355
+ if (s.norm === n || s.norm.includes(n) || n.includes(s.norm))
1356
+ best = Math.max(best, s.score);
1357
+ }
1358
+ return best;
1359
+ };
1360
+ const scored = candidateIds.map((id, i) => {
1361
+ const aa = scoreFor(id);
1362
+ return { id, i, score: aa >= 0 ? aa : heuristicScore(id) };
1363
+ });
1364
+ return scored.sort((a, b) => b.score - a.score || a.i - b.i).map((s) => s.id);
1365
+ }
1366
+
1367
+ // core-auth/dist/sorts.js
1368
+ var BUILTIN_LABEL = { recommended: "Recommended", leaderboard: "Leaderboard (quality)" };
1369
+ async function computeSorts(def, ranking) {
1370
+ const ids = Array.isArray(ranking) ? ranking : [];
1371
+ const sorts = [];
1372
+ const sortOrders = {};
1373
+ if (ids.length) {
1374
+ sorts.push({ id: "recommended", label: BUILTIN_LABEL.recommended });
1375
+ sortOrders.recommended = ids.slice();
1376
+ }
1377
+ for (const entry of def && def.sorts || []) {
1378
+ try {
1379
+ if (entry === "leaderboard" || entry && entry.id === "leaderboard") {
1380
+ if (!ids.length)
1381
+ continue;
1382
+ sorts.push({ id: "leaderboard", label: BUILTIN_LABEL.leaderboard });
1383
+ sortOrders.leaderboard = await computeLeaderboardOrder(ids);
1384
+ } else if (entry && typeof entry === "object" && entry.id && typeof entry.compute === "function") {
1385
+ sorts.push({ id: entry.id, label: entry.label || entry.id });
1386
+ const order = await entry.compute(ids);
1387
+ sortOrders[entry.id] = Array.isArray(order) && order.length ? order : ids.slice();
1388
+ }
1389
+ } catch (e) {
1390
+ log("sort '" + (entry && entry.id || entry) + "' failed: " + e);
1391
+ }
1392
+ }
1393
+ return { sorts, sortOrders };
1394
+ }
1395
+
50
1396
  // core-auth/dist/opencode.js
51
1397
  function opencodeConfigPath() {
52
1398
  const override = (process.env.OPENCODE_CONFIG || "").trim();
53
1399
  if (override)
54
1400
  return resolve(override);
55
- const base = process.env.XDG_CONFIG_HOME || join4(homedir2(), ".config");
56
- const dir = join4(base, "opencode");
57
- const jsonc = join4(dir, "opencode.jsonc");
58
- const json = join4(dir, "opencode.json");
59
- return existsSync4(jsonc) ? jsonc : json;
1401
+ const base = process.env.XDG_CONFIG_HOME || join7(homedir2(), ".config");
1402
+ const dir = join7(base, "opencode");
1403
+ const jsonc = join7(dir, "opencode.jsonc");
1404
+ const json = join7(dir, "opencode.json");
1405
+ return existsSync7(jsonc) ? jsonc : json;
60
1406
  }
61
1407
  function stripJsonc(text) {
62
1408
  return text.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, group) => group ? "" : match).replace(/,(\s*[}\]])/g, "$1");
@@ -65,47 +1411,118 @@ function mergeModels(opencodeProvider, models, npm) {
65
1411
  const path = opencodeConfigPath();
66
1412
  let config = {};
67
1413
  try {
68
- if (existsSync4(path))
69
- config = JSON.parse(stripJsonc(readFileSync2(path, "utf8")));
1414
+ if (existsSync7(path))
1415
+ config = JSON.parse(stripJsonc(readFileSync5(path, "utf8")));
70
1416
  } catch {
71
1417
  }
72
1418
  if (!config.$schema)
73
1419
  config.$schema = "https://opencode.ai/config.json";
74
1420
  config.provider = config.provider || {};
75
1421
  config.provider[opencodeProvider] = config.provider[opencodeProvider] || {};
76
- if (npm)
1422
+ if (npm) {
77
1423
  config.provider[opencodeProvider].npm = npm;
78
- const existing = config.provider[opencodeProvider].models || {};
79
- config.provider[opencodeProvider].models = { ...existing, ...models };
1424
+ const existingOptions = config.provider[opencodeProvider].options || {};
1425
+ config.provider[opencodeProvider].options = {
1426
+ ...existingOptions,
1427
+ apiKey: existingOptions.apiKey || opencodeProvider
1428
+ };
1429
+ }
1430
+ config.provider[opencodeProvider].models = { ...models };
80
1431
  try {
81
- if (!existsSync4(dirname(path)))
82
- mkdirSync3(dirname(path), { recursive: true });
83
- writeFileSync2(path, JSON.stringify(config, null, 2), "utf8");
1432
+ if (!existsSync7(dirname(path)))
1433
+ mkdirSync6(dirname(path), { recursive: true });
1434
+ writeFileSync5(path, JSON.stringify(config, null, 2), "utf8");
84
1435
  } catch (e) {
85
1436
  log("opencode model merge failed: " + (e && e.message));
86
1437
  }
87
1438
  }
1439
+ async function refreshAndMerge(def) {
1440
+ const opencodeProvider = def.opencodeProvider || "anthropic";
1441
+ try {
1442
+ const hasAccounts = listAccounts(def.id).length > 0;
1443
+ const models = await resolveProviderModels(def, { configDir: getConfigDir(), log, hasAccounts }, Date.now());
1444
+ mergeModels(opencodeProvider, models, def.opencodeNpm);
1445
+ const cache = readModelCache(def.id);
1446
+ if (cache) {
1447
+ const { sorts, sortOrders } = await computeSorts(def, cache.ranking || []);
1448
+ writeModelCache(def.id, { ...cache, sorts, sortOrders });
1449
+ }
1450
+ } catch (e) {
1451
+ log("model refresh/merge failed: " + e);
1452
+ }
1453
+ }
1454
+ function authMethods(def) {
1455
+ if (typeof def.loginFlow !== "function") {
1456
+ return [{ label: def.label + " (via core-auth)", type: "api" }];
1457
+ }
1458
+ return [{
1459
+ type: "oauth",
1460
+ label: def.label,
1461
+ authorize: async function() {
1462
+ if (def.accounts && isTTY()) {
1463
+ try {
1464
+ await runProviderMenu(def);
1465
+ } catch (e) {
1466
+ log("account menu failed: " + e);
1467
+ }
1468
+ await refreshAndMerge(def);
1469
+ return { url: "", instructions: def.label + " accounts updated.", method: "auto", callback: async () => ({ type: "success", refresh: "core-auth", access: "", expires: 0 }) };
1470
+ }
1471
+ const flow = await def.loginFlow({ configDir: getConfigDir(), log });
1472
+ return {
1473
+ url: flow.url,
1474
+ instructions: flow.instructions || "Sign in to " + def.label + ", then paste the authorization code (or the full redirect URL) here.",
1475
+ method: "code",
1476
+ callback: async function(code) {
1477
+ try {
1478
+ const account = await flow.complete(code);
1479
+ if (!account || !account.refresh)
1480
+ return { type: "failed" };
1481
+ await refreshAndMerge(def);
1482
+ return { type: "success", refresh: account.refresh, access: account.access || "", expires: account.expires || 0 };
1483
+ } catch (error) {
1484
+ log("oauth login failed: " + error);
1485
+ return { type: "failed" };
1486
+ }
1487
+ }
1488
+ };
1489
+ }
1490
+ }];
1491
+ }
88
1492
  function createOpencodePlugin(def) {
89
1493
  const opencodeProvider = def.opencodeProvider || "anthropic";
90
- return async function() {
1494
+ return async function(input) {
1495
+ await refreshAndMerge(def);
91
1496
  try {
92
- mergeModels(opencodeProvider, def.models || {}, def.opencodeNpm);
93
- } catch {
1497
+ const client = input && input.client;
1498
+ if (client && client.auth && listAccounts(def.id).length > 0) {
1499
+ await client.auth.set({ path: { id: opencodeProvider }, body: { type: "oauth", refresh: "", access: "", expires: 0 } });
1500
+ }
1501
+ } catch (e) {
1502
+ log("auto-route seed failed: " + e);
94
1503
  }
95
- return {
1504
+ const hooks = {
96
1505
  auth: {
97
1506
  provider: opencodeProvider,
98
- methods: [{ label: def.label + " (via core-auth)", type: "api" }],
1507
+ methods: authMethods(def),
99
1508
  loader: async function() {
100
1509
  return {
101
1510
  apiKey: def.id,
102
- fetch: function(input, init) {
103
- return def.handle(new Request(input, init), { configDir: getConfigDir(), log });
1511
+ fetch: function(req, init) {
1512
+ return def.handle(new Request(req, init), { configDir: getConfigDir(), log });
104
1513
  }
105
1514
  };
106
1515
  }
107
1516
  }
108
1517
  };
1518
+ if (typeof def.opencodeHooks === "function") {
1519
+ try {
1520
+ Object.assign(hooks, await def.opencodeHooks(input) || {});
1521
+ } catch (e) {
1522
+ log("opencodeHooks failed: " + e);
1523
+ }
1524
+ }
1525
+ return hooks;
109
1526
  };
110
1527
  }
111
1528
 
@@ -118,7 +1535,391 @@ function defineProvider(def) {
118
1535
  };
119
1536
  }
120
1537
 
1538
+ // core-auth/dist/oauth.js
1539
+ var ACCESS_TOKEN_EXPIRY_BUFFER_MS = 60 * 1e3;
1540
+ function accessTokenExpired(auth) {
1541
+ if (!auth || !auth.access || typeof auth.expires !== "number")
1542
+ return true;
1543
+ return auth.expires <= Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS;
1544
+ }
1545
+ function calculateTokenExpiry(requestTimeMs, expiresInSeconds) {
1546
+ const seconds = typeof expiresInSeconds === "number" ? expiresInSeconds : 3600;
1547
+ if (isNaN(seconds) || seconds <= 0)
1548
+ return requestTimeMs;
1549
+ return requestTimeMs + seconds * 1e3;
1550
+ }
1551
+ var TokenRefreshError = class extends Error {
1552
+ constructor(options) {
1553
+ super(options.message);
1554
+ this.name = "TokenRefreshError";
1555
+ this.code = options.code;
1556
+ this.description = options.description;
1557
+ this.status = options.status;
1558
+ this.statusText = options.statusText;
1559
+ this.revoked = options.code === "invalid_grant";
1560
+ }
1561
+ };
1562
+ function parseOAuthError(text) {
1563
+ if (!text)
1564
+ return {};
1565
+ try {
1566
+ const payload = JSON.parse(text);
1567
+ if (!payload || typeof payload !== "object")
1568
+ return { description: text };
1569
+ let code;
1570
+ if (typeof payload.error === "string")
1571
+ code = payload.error;
1572
+ else if (payload.error && typeof payload.error === "object") {
1573
+ code = payload.error.status || payload.error.code;
1574
+ if (!payload.error_description && payload.error.message)
1575
+ return { code, description: payload.error.message };
1576
+ }
1577
+ if (payload.error_description)
1578
+ return { code, description: payload.error_description };
1579
+ if (payload.error && typeof payload.error === "object" && payload.error.message)
1580
+ return { code, description: payload.error.message };
1581
+ return { code };
1582
+ } catch {
1583
+ return { description: text };
1584
+ }
1585
+ }
1586
+ async function refreshAccessToken(refreshToken, opts) {
1587
+ if (!refreshToken)
1588
+ return void 0;
1589
+ const startTime = Date.now();
1590
+ const params = {
1591
+ grant_type: "refresh_token",
1592
+ refresh_token: refreshToken,
1593
+ client_id: opts.clientId
1594
+ };
1595
+ if (opts.clientSecret)
1596
+ params.client_secret = opts.clientSecret;
1597
+ Object.assign(params, opts.extraParams || {});
1598
+ const init = {
1599
+ method: "POST",
1600
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1601
+ body: new URLSearchParams(params)
1602
+ };
1603
+ if (opts.proxy)
1604
+ init.proxy = opts.proxy;
1605
+ let response;
1606
+ try {
1607
+ response = await fetch(opts.tokenUrl, init);
1608
+ } catch (err) {
1609
+ const message = String(err && err.message || err);
1610
+ if (init.proxy && /unable to connect|failed to connect|could not connect|fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EHOSTUNREACH|EAI_AGAIN|socket|proxy|tunnel|network/i.test(message)) {
1611
+ delete init.proxy;
1612
+ response = await fetch(opts.tokenUrl, init);
1613
+ } else {
1614
+ throw err;
1615
+ }
1616
+ }
1617
+ if (!response.ok) {
1618
+ let errorText;
1619
+ try {
1620
+ errorText = await response.text();
1621
+ } catch {
1622
+ errorText = void 0;
1623
+ }
1624
+ const { code, description } = parseOAuthError(errorText);
1625
+ const details = [code, description || errorText].filter(Boolean).join(": ");
1626
+ const base = "OAuth token refresh failed (" + response.status + " " + response.statusText + ")";
1627
+ throw new TokenRefreshError({
1628
+ message: details ? base + " - " + details : base,
1629
+ code,
1630
+ description: description || errorText,
1631
+ status: response.status,
1632
+ statusText: response.statusText
1633
+ });
1634
+ }
1635
+ const payload = await response.json();
1636
+ return {
1637
+ access: payload.access_token,
1638
+ expires: calculateTokenExpiry(startTime, payload.expires_in),
1639
+ refresh: payload.refresh_token || refreshToken
1640
+ };
1641
+ }
1642
+
1643
+ // core-auth/dist/ratelimit.js
1644
+ function isEnabled(account) {
1645
+ return account.enabled !== false;
1646
+ }
1647
+ function isCoolingDown(account, now) {
1648
+ return typeof account.coolingDownUntil === "number" && account.coolingDownUntil > now;
1649
+ }
1650
+ function isLaneRateLimited(account, lane, now) {
1651
+ if (!lane || !account.rateLimitResetTimes)
1652
+ return false;
1653
+ const until = account.rateLimitResetTimes[lane];
1654
+ return typeof until === "number" && until > now;
1655
+ }
1656
+ function isAvailable(account, lane, now) {
1657
+ if (!isEnabled(account))
1658
+ return false;
1659
+ if (isCoolingDown(account, now))
1660
+ return false;
1661
+ if (lane && isLaneRateLimited(account, lane, now))
1662
+ return false;
1663
+ return true;
1664
+ }
1665
+ function availableAt(account, lane, now) {
1666
+ if (!isEnabled(account))
1667
+ return Infinity;
1668
+ let t = 0;
1669
+ if (typeof account.coolingDownUntil === "number")
1670
+ t = Math.max(t, account.coolingDownUntil);
1671
+ if (lane && account.rateLimitResetTimes && typeof account.rateLimitResetTimes[lane] === "number") {
1672
+ t = Math.max(t, account.rateLimitResetTimes[lane]);
1673
+ }
1674
+ return Math.max(t, now);
1675
+ }
1676
+ function calculateBackoffMs(attempt, opts) {
1677
+ const base = opts && opts.baseMs || 1e3;
1678
+ const max = opts && opts.maxMs || 5 * 60 * 1e3;
1679
+ const raw = Math.min(max, base * Math.pow(2, Math.max(0, attempt)));
1680
+ if (opts && opts.jitter === false)
1681
+ return raw;
1682
+ return Math.floor(raw / 2 + Math.random() * (raw / 2));
1683
+ }
1684
+
1685
+ // core-auth/dist/selection.js
1686
+ function laneCursor(pool, lane) {
1687
+ if (lane && pool.activeIndexByLane && typeof pool.activeIndexByLane[lane] === "number")
1688
+ return pool.activeIndexByLane[lane];
1689
+ return pool.activeIndex || 0;
1690
+ }
1691
+ function setLaneCursor(pool, lane, index) {
1692
+ if (lane) {
1693
+ pool.activeIndexByLane = pool.activeIndexByLane || {};
1694
+ pool.activeIndexByLane[lane] = index;
1695
+ } else {
1696
+ pool.activeIndex = index;
1697
+ }
1698
+ }
1699
+ function firstAvailableFrom(pool, start, lane, now, available) {
1700
+ const n = pool.accounts.length;
1701
+ for (let step = 0; step < n; step++) {
1702
+ const i = (start + step) % n;
1703
+ if (available(pool.accounts[i], lane, now))
1704
+ return i;
1705
+ }
1706
+ return -1;
1707
+ }
1708
+ function soonestFree(pool, lane, now) {
1709
+ let best = -1, bestAt = Infinity;
1710
+ for (let i = 0; i < pool.accounts.length; i++) {
1711
+ const at = availableAt(pool.accounts[i], lane, now);
1712
+ if (at < bestAt) {
1713
+ bestAt = at;
1714
+ best = i;
1715
+ }
1716
+ }
1717
+ return best;
1718
+ }
1719
+ function selectIndex(pool, lane, now, strategy, available) {
1720
+ const n = pool.accounts.length;
1721
+ if (n === 0)
1722
+ return -1;
1723
+ const isFree = available || isAvailable;
1724
+ const cursor = laneCursor(pool, lane);
1725
+ const strat = strategy || "hybrid";
1726
+ if (strat === "round-robin") {
1727
+ const i2 = firstAvailableFrom(pool, (cursor + 1) % n, lane, now, isFree);
1728
+ if (i2 >= 0)
1729
+ setLaneCursor(pool, lane, i2);
1730
+ return i2;
1731
+ }
1732
+ if (cursor >= 0 && cursor < n && isFree(pool.accounts[cursor], lane, now))
1733
+ return cursor;
1734
+ const i = firstAvailableFrom(pool, cursor, lane, now, isFree);
1735
+ if (i >= 0) {
1736
+ setLaneCursor(pool, lane, i);
1737
+ return i;
1738
+ }
1739
+ if (strat === "hybrid") {
1740
+ const best = soonestFree(pool, lane, now);
1741
+ if (best >= 0)
1742
+ setLaneCursor(pool, lane, best);
1743
+ return best;
1744
+ }
1745
+ return -1;
1746
+ }
1747
+
1748
+ // core-auth/dist/manager.js
1749
+ function oauthWithProxy(oauth, id) {
1750
+ const proxy = proxyManager.selectForAccount(id);
1751
+ return proxy ? { ...oauth, proxy } : oauth;
1752
+ }
1753
+ var AccountManager = class {
1754
+ constructor(providerId, opts) {
1755
+ this.providerId = providerId;
1756
+ const options = opts || {};
1757
+ this.strategy = options.selection || "hybrid";
1758
+ this.oauth = options.oauth || null;
1759
+ this.backoff = options.backoff || {};
1760
+ this.store = options.store || null;
1761
+ this.extraAvailable = typeof options.isAvailable === "function" ? options.isAvailable : null;
1762
+ this.available = (account, lane, now) => isAvailable(account, lane, now) && (!this.extraAvailable || this.extraAvailable(account, lane, now));
1763
+ }
1764
+ load() {
1765
+ return loadAccounts(this.providerId, this.store);
1766
+ }
1767
+ save(pool) {
1768
+ saveAccounts(this.providerId, pool, this.store);
1769
+ }
1770
+ list() {
1771
+ return this.load().accounts;
1772
+ }
1773
+ // selection + lastUsed claim run under the store lock; the network token refresh runs outside it so a slow refresh never blocks other writers.
1774
+ async acquire(lane) {
1775
+ const now = Date.now();
1776
+ let claimedId = null;
1777
+ updateAccounts(this.providerId, (pool) => {
1778
+ const index = selectIndex(pool, lane, now, this.strategy, this.available);
1779
+ if (index < 0)
1780
+ return;
1781
+ const account2 = pool.accounts[index];
1782
+ if (!this.available(account2, lane, now))
1783
+ return;
1784
+ account2.lastUsed = now;
1785
+ claimedId = account2.id;
1786
+ }, this.store);
1787
+ if (!claimedId)
1788
+ return null;
1789
+ const access = await this.ensureAccess(claimedId);
1790
+ const account = this.load().accounts.find((candidate) => candidate.id === claimedId);
1791
+ return { account, access };
1792
+ }
1793
+ // a revoked refresh token disables the account so selection skips it.
1794
+ async ensureAccess(id) {
1795
+ const account = this.load().accounts.find((candidate) => candidate.id === id);
1796
+ if (!account)
1797
+ return void 0;
1798
+ if (!accessTokenExpired(account))
1799
+ return account.access;
1800
+ if (!this.oauth || !account.refresh)
1801
+ return account.access;
1802
+ try {
1803
+ const refreshed = await refreshAccessToken(account.refresh, oauthWithProxy(this.oauth, id));
1804
+ this.mutate(id, (a) => {
1805
+ a.access = refreshed.access;
1806
+ a.expires = refreshed.expires;
1807
+ if (refreshed.refresh)
1808
+ a.refresh = refreshed.refresh;
1809
+ });
1810
+ return refreshed.access;
1811
+ } catch (error) {
1812
+ if (error instanceof TokenRefreshError && error.revoked) {
1813
+ this.mutate(id, (a) => {
1814
+ a.enabled = false;
1815
+ a.cooldownReason = "refresh token revoked";
1816
+ });
1817
+ }
1818
+ throw error;
1819
+ }
1820
+ }
1821
+ reportRateLimit(id, lane, resetMs) {
1822
+ this.mutate(id, (account) => {
1823
+ account.rateLimitResetTimes = account.rateLimitResetTimes || {};
1824
+ account.rateLimitResetTimes[lane] = resetMs;
1825
+ });
1826
+ }
1827
+ reportError(id, attempt, reason) {
1828
+ const ms = calculateBackoffMs(attempt || 0, this.backoff);
1829
+ this.mutate(id, (account) => {
1830
+ account.coolingDownUntil = Date.now() + ms;
1831
+ account.cooldownReason = reason || "transient error";
1832
+ });
1833
+ }
1834
+ reportSuccess(id) {
1835
+ this.mutate(id, (account) => {
1836
+ account.coolingDownUntil = 0;
1837
+ account.cooldownReason = null;
1838
+ account.lastUsed = Date.now();
1839
+ });
1840
+ }
1841
+ nextAvailableAt(lane) {
1842
+ const now = Date.now();
1843
+ let best = Infinity;
1844
+ for (const account of this.load().accounts)
1845
+ best = Math.min(best, availableAt(account, lane, now));
1846
+ return best === Infinity ? null : best;
1847
+ }
1848
+ mutate(id, fn) {
1849
+ updateAccounts(this.providerId, (pool) => {
1850
+ const account = pool.accounts.find((candidate) => candidate.id === id);
1851
+ if (account)
1852
+ fn(account);
1853
+ }, this.store);
1854
+ }
1855
+ remove(id) {
1856
+ removeAccount(this.providerId, id, this.store);
1857
+ }
1858
+ // force a token refresh regardless of expiry (manual "refresh token" action)
1859
+ async refresh(id) {
1860
+ const account = this.load().accounts.find((candidate) => candidate.id === id);
1861
+ if (!account || !this.oauth || !account.refresh)
1862
+ return false;
1863
+ const refreshed = await refreshAccessToken(account.refresh, oauthWithProxy(this.oauth, id));
1864
+ this.mutate(id, (a) => {
1865
+ a.access = refreshed.access;
1866
+ a.expires = refreshed.expires;
1867
+ if (refreshed.refresh)
1868
+ a.refresh = refreshed.refresh;
1869
+ });
1870
+ return true;
1871
+ }
1872
+ };
1873
+
1874
+ // core-auth/dist/controller.js
1875
+ function defaultStatus(account, now) {
1876
+ if (account.enabled === false)
1877
+ return "disabled";
1878
+ if (isCoolingDown(account, now))
1879
+ return "cooling-down";
1880
+ const lanes = account.rateLimitResetTimes || {};
1881
+ if (Object.values(lanes).some((reset) => typeof reset === "number" && reset > now))
1882
+ return "rate-limited";
1883
+ return "active";
1884
+ }
1885
+ function accountControllerFromManager(manager, opts) {
1886
+ const options = opts || {};
1887
+ return {
1888
+ list() {
1889
+ const now = Date.now();
1890
+ return manager.list().map((account) => ({
1891
+ id: account.id,
1892
+ email: account.email,
1893
+ enabled: account.enabled !== false,
1894
+ lastUsed: account.lastUsed,
1895
+ status: options.status ? options.status(account, now) : defaultStatus(account, now),
1896
+ detail: options.detail ? options.detail(account, now) : void 0,
1897
+ quota: options.quota ? options.quota(account) : void 0
1898
+ }));
1899
+ },
1900
+ enable(id, on) {
1901
+ manager.mutate(id, (account) => {
1902
+ account.enabled = !!on;
1903
+ });
1904
+ },
1905
+ remove(id) {
1906
+ manager.remove(id);
1907
+ },
1908
+ login: options.login || (async () => null),
1909
+ refreshQuota: options.refreshQuota,
1910
+ actions: options.actions,
1911
+ accountActions: options.accountActions
1912
+ };
1913
+ }
1914
+
121
1915
  // src/driver.ts
1916
+ var accountManager = new AccountManager("stub", {});
1917
+ function stubAddAccount() {
1918
+ const n = accountManager.list().length + 1;
1919
+ const account = { id: "stub-" + n + "@example.com", email: "stub-" + n + "@example.com", refresh: "stub-refresh-" + n, addedAt: Date.now(), lastUsed: 0, enabled: true };
1920
+ addAccount("stub", account);
1921
+ return account;
1922
+ }
122
1923
  var STUB_TEXT = "Hello from stub-auth \u2014 the core-auth pipeline works end to end.";
123
1924
  function stubText(model) {
124
1925
  return STUB_TEXT + " (served by " + model + ")";
@@ -164,7 +1965,13 @@ var driver = {
164
1965
  return new Response(streamBody(model), { status: 200, headers: { "content-type": "text/event-stream" } });
165
1966
  }
166
1967
  return new Response(JSON.stringify(jsonBody(model)), { status: 200, headers: { "content-type": "application/json" } });
167
- }
1968
+ },
1969
+ loginFlow: async () => ({ url: "https://example.com/stub-login", instructions: "Stub login (no real OAuth) \u2014 completes immediately.", complete: async () => stubAddAccount() }),
1970
+ accounts: accountControllerFromManager(accountManager, { login: async () => {
1971
+ const a = stubAddAccount();
1972
+ return { id: a.id, email: a.email, status: "active", enabled: true };
1973
+ } }),
1974
+ proxies: true
168
1975
  };
169
1976
 
170
1977
  // src/index.ts