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