@wrongstack/cli 0.318.0 → 0.319.1

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 (56) hide show
  1. package/data/providers.json +1 -22
  2. package/dist/{acp-CEQINXWB.js → acp-5ANIAOZC.js} +4 -4
  3. package/dist/arg-parser.d.ts +4 -0
  4. package/dist/{auth-3OSF6BMU.js → auth-ND7J7DTD.js} +10 -10
  5. package/dist/{chunk-QC5I4VWZ.js → chunk-635WW3II.js} +8 -14
  6. package/dist/{chunk-7CECQDNH.js → chunk-D3K4NYQL.js} +1 -1
  7. package/dist/{chunk-QYIIOMLE.js → chunk-E4I2TF5S.js} +2 -2
  8. package/dist/{chunk-4JRIWVJ4.js → chunk-ERX4BLKP.js} +1 -1
  9. package/dist/{chunk-37SPPLUD.js → chunk-F5GA2QW2.js} +15 -2
  10. package/dist/chunk-GGGUZSAU.js +235 -0
  11. package/dist/{chunk-GKHUHAIK.js → chunk-HF5VQWIY.js} +5 -5
  12. package/dist/{chunk-YX6KUIDF.js → chunk-IMT6PNV5.js} +861 -29
  13. package/dist/{chunk-G7337TTK.js → chunk-IS2PPBQV.js} +1 -1
  14. package/dist/{chunk-G3IMKQRH.js → chunk-JHPXJRRI.js} +1 -1
  15. package/dist/chunk-KYDJJLJI.js +52 -0
  16. package/dist/chunk-LIWVIEZM.js +46 -0
  17. package/dist/chunk-P2OKBDCH.js +1166 -0
  18. package/dist/chunk-PCBJSXYV.js +65 -0
  19. package/dist/{chunk-7JBKXVMK.js → chunk-TALOKMIC.js} +14 -18
  20. package/dist/{chunk-KG4X5F7Z.js → chunk-VFCJOWJB.js} +10 -12
  21. package/dist/{chunk-SYIBTG3X.js → chunk-VOTTENR5.js} +3 -5
  22. package/dist/chunk-WHOIR577.js +1 -0
  23. package/dist/{chunk-2ZNBRZ4W.js → chunk-WQZW3RDP.js} +2 -2
  24. package/dist/{chunk-BXFYBQKR.js → chunk-XPX432BZ.js} +8 -1
  25. package/dist/cli-context-K73EIND5.js +3369 -0
  26. package/dist/{cli-main-ILRTNPDP.js → cli-main-QXSKKANW.js} +84 -93
  27. package/dist/{diag-doctor-LFCCBPMD.js → diag-doctor-QZVKUAE5.js} +4 -5
  28. package/dist/{execution-BN4QIFH6.js → execution-7ZFY4FJY.js} +499 -473
  29. package/dist/{hq-3GWBFLON.js → hq-4QQJO2GN.js} +2 -2
  30. package/dist/hq-server/routes.d.ts +1 -1
  31. package/dist/{hq-server-OARPUSQJ.js → hq-server-CVAYICIS.js} +2 -2
  32. package/dist/index.js +20 -4440
  33. package/dist/{modeldiag-4NYBRSFP.js → modeldiag-ZL4ROTQ5.js} +2 -2
  34. package/dist/per-subcommand-help-6VCIKDUW.js +23 -0
  35. package/dist/{plugin-usage-AP7SJCDA.js → plugin-usage-7WCZW4NP.js} +2 -2
  36. package/dist/{plugins-HQ5FU2AM.js → plugins-7N3TF2QF.js} +3 -3
  37. package/dist/pre-launch/first-run.d.ts +51 -0
  38. package/dist/pre-launch.d.ts +1 -0
  39. package/dist/{project-manifest-YFTJ3D5N.js → project-manifest-RV5FXGQI.js} +2 -2
  40. package/dist/{project-picker-R5TYAE3Q.js → project-picker-CCQR3W4I.js} +3 -3
  41. package/dist/{providers-models-663NDPRQ.js → providers-models-OKKQZIWU.js} +2 -2
  42. package/dist/{proxy-wiring-EQB5X5YH.js → proxy-wiring-QCI7EWQC.js} +3 -3
  43. package/dist/{sdd-runtime-FLKCSTYV.js → sdd-runtime-R6FAK2G2.js} +2 -2
  44. package/dist/services/sdd-runtime.d.ts +1 -1
  45. package/dist/{sessions-config-P2LK2L32.js → sessions-config-KAMEBVOX.js} +4 -4
  46. package/dist/short-circuit-flags-7YUCERBB.js +11 -0
  47. package/dist/slash-commands/sdd.d.ts +1 -1
  48. package/dist/subcommands-KE6MEQQP.js +10 -0
  49. package/dist/{webui-server-OJLLM6PE.js → webui-server-F7MSRHOF.js} +8 -6
  50. package/dist/wiring/wrongtrace-gate.d.ts +2 -2
  51. package/dist/wiring/wrongtrace-hooks.d.ts +1 -1
  52. package/dist/{wrongtrace-gate-counters-P5BCBXQN.js → wrongtrace-gate-counters-GSVY372D.js} +2 -2
  53. package/dist/{wrongtrace-telemetry-FNLANZ5I.js → wrongtrace-telemetry-KBBM5NLQ.js} +2 -2
  54. package/package.json +28 -28
  55. package/dist/chunk-ALE5KHCA.js +0 -1
  56. package/dist/chunk-L6ZNCTRJ.js +0 -1067
@@ -1,12 +1,17 @@
1
1
  import {
2
- LOCAL_LLM_PRESETS,
3
- runLiveProviderPicker
4
- } from "./chunk-L6ZNCTRJ.js";
2
+ appendHistory,
3
+ backupCurrent
4
+ } from "./chunk-B4JLSXZB.js";
5
+ import {
6
+ hasApiKey,
7
+ isKeylessLocalProvider,
8
+ visibleModelIds
9
+ } from "./chunk-VUVOMXWP.js";
5
10
  import {
6
11
  openBrowser,
7
12
  runCodexOAuthLogin,
8
13
  startLoopbackServer
9
- } from "./chunk-QYIIOMLE.js";
14
+ } from "./chunk-E4I2TF5S.js";
10
15
  import {
11
16
  activeLabel,
12
17
  loadConfigProviders,
@@ -15,7 +20,7 @@ import {
15
20
  normalizeKeys,
16
21
  nowIso,
17
22
  writeKeysBack
18
- } from "./chunk-BXFYBQKR.js";
23
+ } from "./chunk-XPX432BZ.js";
19
24
 
20
25
  // src/auth-menu/local.ts
21
26
  import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core/security";
@@ -98,6 +103,10 @@ async function loadProviders(deps) {
98
103
  });
99
104
  }
100
105
 
106
+ // src/auth-menu/local-presets.ts
107
+ import { projectLocalProviderPresets } from "@wrongstack/providers/definitions";
108
+ var LOCAL_LLM_PRESETS = projectLocalProviderPresets();
109
+
101
110
  // src/auth-menu/shared.ts
102
111
  import { color } from "@wrongstack/core/utils";
103
112
  function renderProviderLine(renderer, id, cfg, idx) {
@@ -1074,9 +1083,824 @@ async function runOAuthLoginMenu(deps) {
1074
1083
  await runOAuthLoginChoice(deps, pick);
1075
1084
  }
1076
1085
 
1086
+ // src/picker.ts
1087
+ import os from "node:os";
1088
+ import {
1089
+ color as color8,
1090
+ expectDefined as expectDefined2,
1091
+ setOutputLineGuard as setOutputLineGuard2,
1092
+ setRawMode as setRawMode2,
1093
+ stripAnsi as stripAnsi2,
1094
+ toErrorMessage,
1095
+ writeOut as writeOut2
1096
+ } from "@wrongstack/core/utils";
1097
+
1098
+ // src/picker-key-state.ts
1099
+ function applyPickerKey(state, chunk, matchCount) {
1100
+ if (chunk === "\x1B[A") return { ...state, selected: Math.max(0, state.selected - 1) };
1101
+ if (chunk === "\x1B[B")
1102
+ return { ...state, selected: Math.min(Math.max(0, matchCount - 1), state.selected + 1) };
1103
+ if (chunk === "\x1B") return { ...state, query: "", selected: 0, status: "typing" };
1104
+ if (chunk.charCodeAt(0) === 27) return state;
1105
+ let query = state.query;
1106
+ let selected = state.selected;
1107
+ const status = "typing";
1108
+ for (const ch of chunk) {
1109
+ if (ch === "\r" || ch === "\n") {
1110
+ return { ...state, status: matchCount > 0 ? "submitted" : "typing" };
1111
+ }
1112
+ if (ch === "") return { ...state, status: "cancelled" };
1113
+ if (ch === "") {
1114
+ query = "";
1115
+ selected = 0;
1116
+ continue;
1117
+ }
1118
+ if (ch === "\x7F" || ch === "\b") {
1119
+ query = query.slice(0, -1);
1120
+ selected = 0;
1121
+ continue;
1122
+ }
1123
+ if (ch >= " " && ch !== "\x7F") {
1124
+ query += ch;
1125
+ selected = 0;
1126
+ }
1127
+ }
1128
+ return { query, selected, status };
1129
+ }
1130
+
1131
+ // src/picker-model-picker.ts
1132
+ import {
1133
+ color as color7,
1134
+ expectDefined,
1135
+ setOutputLineGuard,
1136
+ setRawMode,
1137
+ writeOut
1138
+ } from "@wrongstack/core/utils";
1139
+
1140
+ // src/picker-ui.ts
1141
+ import { color as color6, stripAnsi } from "@wrongstack/core/utils";
1142
+ var theme = {
1143
+ accent: color6.amber,
1144
+ chrome: color6.gray,
1145
+ cursor: (s) => color6.amber(color6.bold(s)),
1146
+ ctx: color6.cyan,
1147
+ cost: color6.green,
1148
+ caps: color6.magenta
1149
+ };
1150
+ var BOX = {
1151
+ tl: "\u256D",
1152
+ tr: "\u256E",
1153
+ bl: "\u2570",
1154
+ br: "\u256F",
1155
+ h: "\u2500",
1156
+ v: "\u2502"
1157
+ };
1158
+ var PICKER_MIN_WIDTH = 74;
1159
+ var PICKER_MAX_WIDTH = 100;
1160
+ function pickerWidth() {
1161
+ const cols = process.stdout.columns;
1162
+ if (typeof cols !== "number" || !Number.isFinite(cols) || cols <= 0) {
1163
+ return PICKER_MIN_WIDTH;
1164
+ }
1165
+ return Math.max(PICKER_MIN_WIDTH, Math.min(PICKER_MAX_WIDTH, cols - 2));
1166
+ }
1167
+ function visibleWidth(s) {
1168
+ return stripAnsi(s).length;
1169
+ }
1170
+ function padVisible(s, width) {
1171
+ const w = visibleWidth(s);
1172
+ if (w === width) return s;
1173
+ if (w < width) return s + " ".repeat(width - w);
1174
+ const plain = stripAnsi(s);
1175
+ return `${plain.slice(0, Math.max(0, width - 1))}\u2026`;
1176
+ }
1177
+ function boxTop(title) {
1178
+ const width = pickerWidth();
1179
+ const label = ` ${title} `;
1180
+ const pad = Math.max(0, width - visibleWidth(label) - 1);
1181
+ return theme.chrome(
1182
+ `${BOX.tl}${BOX.h}${theme.accent(color6.bold(label))}${theme.chrome(BOX.h.repeat(pad))}${BOX.tr}`
1183
+ );
1184
+ }
1185
+ function boxBottom() {
1186
+ return theme.chrome(`${BOX.bl}${BOX.h.repeat(pickerWidth())}${BOX.br}`);
1187
+ }
1188
+ function boxRow(content) {
1189
+ return `${theme.chrome(BOX.v)} ${padVisible(content, pickerWidth() - 2)} ${theme.chrome(BOX.v)}`;
1190
+ }
1191
+ function boxDivider() {
1192
+ return theme.chrome(`${BOX.v}${BOX.h.repeat(pickerWidth())}${BOX.v}`);
1193
+ }
1194
+ function keyHints(pairs) {
1195
+ return pairs.map(([k, label]) => `${theme.accent(k)} ${color6.dim(label)}`).join(color6.dim(" \xB7 "));
1196
+ }
1197
+ var CODEX_PROVIDER_ID = "openai-codex";
1198
+ var CODEX_PICKER_HEADER = "Select Model and Effort";
1199
+ var CODEX_LEGACY_NOTE = "Access legacy models by running `wstack -m <model_name>` or in your `config.json`.";
1200
+ function codexPickerPreamble(provider) {
1201
+ if (provider.id !== CODEX_PROVIDER_ID) return "";
1202
+ return ` ${color6.bold(CODEX_PICKER_HEADER)}
1203
+ ${color6.dim(` ${CODEX_LEGACY_NOTE}`)}
1204
+
1205
+ `;
1206
+ }
1207
+
1208
+ // src/picker-model-picker.ts
1209
+ var LIVE_PICKER_MAX_VISIBLE = 15;
1210
+ function filterModels(query, models) {
1211
+ const q = query.trim().toLowerCase();
1212
+ if (!q) return [...models];
1213
+ return models.filter((m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q));
1214
+ }
1215
+ function renderLiveModelList(query, filtered, selectedIdx, header) {
1216
+ const ordered = [...filtered].sort(
1217
+ (a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "")
1218
+ );
1219
+ const visible = ordered.slice(0, LIVE_PICKER_MAX_VISIBLE);
1220
+ const lines = [];
1221
+ lines.push(boxTop("Select a model"));
1222
+ lines.push(boxRow(color7.dim(header)));
1223
+ const caret = query.length > 0 ? color7.dim("\u258F") : theme.cursor("\u258F");
1224
+ lines.push(boxRow(`${theme.accent("\u276F")} ${color7.dim("Select model:")} ${query}${caret}`));
1225
+ lines.push(boxDivider());
1226
+ let flat = 0;
1227
+ for (const m of visible) {
1228
+ const selected = flat === selectedIdx;
1229
+ const marker = selected ? theme.cursor("\u25B6") : " ";
1230
+ const ctxRaw = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
1231
+ const ctx = theme.ctx(ctxRaw.padStart(6));
1232
+ const costRaw = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
1233
+ const cost = costRaw ? theme.cost(costRaw) : "";
1234
+ const capTags = [];
1235
+ if (m.tool_call) capTags.push("tools");
1236
+ if (m.reasoning) capTags.push("reason");
1237
+ if (m.modalities?.input?.includes("image")) capTags.push("vision");
1238
+ const caps = capTags.map((c) => theme.caps(c)).join(color7.dim(" "));
1239
+ const id = selected ? theme.accent(color7.bold(m.id)) : m.id;
1240
+ lines.push(boxRow(`${marker} ${padVisible(id, 34)} ${ctx} ${padVisible(cost, 12)} ${caps}`));
1241
+ flat++;
1242
+ }
1243
+ if (ordered.length > LIVE_PICKER_MAX_VISIBLE) {
1244
+ lines.push(
1245
+ boxRow(color7.dim(` \u2026 ${ordered.length - LIVE_PICKER_MAX_VISIBLE} more \u2014 type to filter`))
1246
+ );
1247
+ }
1248
+ lines.push(boxDivider());
1249
+ lines.push(
1250
+ boxRow(
1251
+ keyHints([
1252
+ ["\u2191\u2193", "move"],
1253
+ ["Enter", "select"],
1254
+ ["Esc", "clear"],
1255
+ ["Ctrl+C", "quit"]
1256
+ ])
1257
+ )
1258
+ );
1259
+ lines.push(boxBottom());
1260
+ return lines.join("\n");
1261
+ }
1262
+ async function runLiveModelPicker(provider, defaultModel) {
1263
+ const stdin = process.stdin;
1264
+ const out = process.stdout;
1265
+ if (!stdin.isTTY || !out.isTTY) return void 0;
1266
+ const header = `${provider.name} (${provider.id}) models:`;
1267
+ const byNewest = (a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "");
1268
+ const ranked = [...provider.models].sort(byNewest);
1269
+ const defaultIdx = defaultModel !== void 0 ? ranked.findIndex((m) => m.id === defaultModel) : -1;
1270
+ setOutputLineGuard(null);
1271
+ let state = {
1272
+ query: "",
1273
+ selected: defaultIdx >= 0 ? defaultIdx : 0,
1274
+ status: "typing"
1275
+ };
1276
+ const order = (filtered) => [...filtered].sort(byNewest);
1277
+ let ordered = order(filterModels(state.query, provider.models));
1278
+ const visibleCount = () => Math.min(ordered.length, LIVE_PICKER_MAX_VISIBLE);
1279
+ const clamp = () => {
1280
+ if (state.selected >= visibleCount()) state.selected = Math.max(0, visibleCount() - 1);
1281
+ };
1282
+ clamp();
1283
+ const preamble = codexPickerPreamble(provider);
1284
+ if (preamble) writeOut(preamble);
1285
+ let frame = renderLiveModelList(state.query, ordered, state.selected, header);
1286
+ writeOut(frame);
1287
+ return new Promise((resolve) => {
1288
+ const wasRaw = stdin.isRaw;
1289
+ const wasPaused = stdin.isPaused();
1290
+ setRawMode(stdin, true);
1291
+ stdin.resume();
1292
+ stdin.setEncoding("utf8");
1293
+ const cleanup = () => {
1294
+ stdin.off("data", onData);
1295
+ setRawMode(stdin, wasRaw);
1296
+ if (wasPaused) stdin.pause();
1297
+ };
1298
+ const repaint = () => {
1299
+ const ups = (frame.match(/\n/g) ?? []).length;
1300
+ writeOut(`\x1B[${ups}A\r\x1B[J`);
1301
+ ordered = order(filterModels(state.query, provider.models));
1302
+ clamp();
1303
+ frame = renderLiveModelList(state.query, ordered, state.selected, header);
1304
+ writeOut(frame);
1305
+ };
1306
+ const onData = (chunk) => {
1307
+ ordered = order(filterModels(state.query, provider.models));
1308
+ state = applyPickerKey(state, chunk, visibleCount());
1309
+ ordered = order(filterModels(state.query, provider.models));
1310
+ clamp();
1311
+ if (state.status === "cancelled") {
1312
+ cleanup();
1313
+ writeOut("\n");
1314
+ resolve(void 0);
1315
+ return;
1316
+ }
1317
+ if (state.status === "submitted") {
1318
+ if (ordered.length === 0) {
1319
+ state = { ...state, status: "typing" };
1320
+ repaint();
1321
+ return;
1322
+ }
1323
+ const pick = ordered[state.selected] ?? expectDefined(ordered[0]);
1324
+ cleanup();
1325
+ writeOut("\n");
1326
+ resolve(pick);
1327
+ return;
1328
+ }
1329
+ repaint();
1330
+ };
1331
+ stdin.on("data", onData);
1332
+ });
1333
+ }
1334
+
1335
+ // src/picker.ts
1336
+ function appendLocalPresetProviders(merged) {
1337
+ const existing = new Set(merged.map((p) => p.id));
1338
+ for (const preset of LOCAL_LLM_PRESETS) {
1339
+ if (existing.has(preset.id)) continue;
1340
+ merged.push({
1341
+ id: preset.id,
1342
+ name: preset.label,
1343
+ family: "openai-compatible",
1344
+ apiBase: preset.defaultBaseUrl,
1345
+ // No env vars → with a loopback apiBase this reads as a keyless
1346
+ // local gateway, so the picker offers it without a key.
1347
+ envVars: [],
1348
+ // No catalog models yet; OmniRoute/LM Studio/etc. auto-discover at
1349
+ // boot or the user picks `m` to set a list. An empty list still
1350
+ // lets the provider be chosen — pickModel surfaces the "no models
1351
+ // listed" hint and the user can type an id directly.
1352
+ models: []
1353
+ });
1354
+ }
1355
+ return merged;
1356
+ }
1357
+ function filterProviders(query, providers) {
1358
+ const q = query.trim().toLowerCase();
1359
+ if (!q) return [...providers];
1360
+ return providers.filter(
1361
+ (p) => p.id.toLowerCase().includes(q) || p.name.toLowerCase().includes(q)
1362
+ );
1363
+ }
1364
+ var PROVIDER_FAMILY_PREFERRED_ORDER = [
1365
+ "anthropic",
1366
+ "anthropic-oauth",
1367
+ "openai",
1368
+ "openai-codex",
1369
+ "github-copilot",
1370
+ "google",
1371
+ "openai-compatible"
1372
+ ];
1373
+ function orderProvidersForDisplay(filtered) {
1374
+ const families = /* @__PURE__ */ new Map();
1375
+ for (const p of filtered) {
1376
+ const arr = families.get(p.family) ?? [];
1377
+ arr.push(p);
1378
+ families.set(p.family, arr);
1379
+ }
1380
+ const order = [
1381
+ ...PROVIDER_FAMILY_PREFERRED_ORDER.filter((f) => families.has(f)),
1382
+ ...[...families.keys()].filter((f) => !PROVIDER_FAMILY_PREFERRED_ORDER.includes(f))
1383
+ ];
1384
+ const flat = [];
1385
+ for (const fam of order) {
1386
+ const arr = (families.get(fam) ?? []).slice().sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
1387
+ flat.push(...arr);
1388
+ }
1389
+ return flat;
1390
+ }
1391
+ function renderLiveProviderList(query, filtered, selectedIdx, savedSet) {
1392
+ const ordered = orderProvidersForDisplay(filtered);
1393
+ const scrollOffset = Math.max(0, selectedIdx - LIVE_PICKER_MAX_VISIBLE + 1);
1394
+ const visible = ordered.slice(scrollOffset, scrollOffset + LIVE_PICKER_MAX_VISIBLE);
1395
+ const lines = [];
1396
+ lines.push(boxTop("Select a provider"));
1397
+ const caret = query.length > 0 ? color8.dim("\u258F") : theme.cursor("\u258F");
1398
+ lines.push(boxRow(`${theme.accent("\u276F")} ${color8.dim("Select provider:")} ${query}${caret}`));
1399
+ lines.push(boxDivider());
1400
+ if (scrollOffset > 0) {
1401
+ lines.push(boxRow(color8.dim(` ${scrollOffset} more above \u2191`)));
1402
+ }
1403
+ let flat = 0;
1404
+ let lastFamily = "";
1405
+ for (const p of visible) {
1406
+ if (p.family !== lastFamily) {
1407
+ lines.push(boxRow(`${theme.accent("\xB7")} ${color8.dim(p.family.toUpperCase())}`));
1408
+ lastFamily = p.family;
1409
+ }
1410
+ const selected = flat === selectedIdx - scrollOffset;
1411
+ const sel = selected ? theme.cursor("\u25B6") : " ";
1412
+ let marker;
1413
+ if (savedSet) {
1414
+ const mark = savedSet.has(p.id) ? color8.cyan("\u25C9") : color8.dim("\u25CB");
1415
+ marker = `${sel} ${mark}`;
1416
+ } else {
1417
+ marker = `${sel}`;
1418
+ }
1419
+ const id = selected ? theme.accent(color8.bold(p.id)) : p.id;
1420
+ const name = selected ? p.name : color8.dim(p.name);
1421
+ lines.push(boxRow(`${marker} ${padVisible(id, 24)} ${name}`));
1422
+ flat++;
1423
+ }
1424
+ if (scrollOffset + LIVE_PICKER_MAX_VISIBLE < ordered.length) {
1425
+ lines.push(
1426
+ boxRow(
1427
+ color8.dim(` ${ordered.length - scrollOffset - LIVE_PICKER_MAX_VISIBLE} more below \u2193`)
1428
+ )
1429
+ );
1430
+ }
1431
+ lines.push(boxDivider());
1432
+ lines.push(
1433
+ boxRow(
1434
+ keyHints([
1435
+ ["\u2191\u2193", "scroll"],
1436
+ ["Enter", "select"],
1437
+ ["Esc", "clear"],
1438
+ ["Ctrl+C", "quit"]
1439
+ ])
1440
+ )
1441
+ );
1442
+ lines.push(boxBottom());
1443
+ return lines.join("\n");
1444
+ }
1445
+ async function saveToGlobalConfig(configPath, provider, model, homeFn = () => process.env.HOME ?? os.homedir()) {
1446
+ try {
1447
+ const { atomicWrite: atomicWrite2 } = await import("@wrongstack/core/utils");
1448
+ const fs2 = await import("node:fs/promises");
1449
+ let existing = {};
1450
+ try {
1451
+ const raw = await fs2.readFile(configPath, "utf8");
1452
+ existing = JSON.parse(raw);
1453
+ } catch {
1454
+ }
1455
+ const oldCfg = { ...existing };
1456
+ existing.provider = provider;
1457
+ existing.model = model;
1458
+ try {
1459
+ await backupCurrent(homeFn, configPath);
1460
+ } catch (err) {
1461
+ console.warn(
1462
+ JSON.stringify({
1463
+ level: "warn",
1464
+ event: "picker.backup_failed",
1465
+ message: toErrorMessage(err),
1466
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1467
+ })
1468
+ );
1469
+ }
1470
+ await atomicWrite2(configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1471
+ try {
1472
+ await appendHistory(
1473
+ oldCfg,
1474
+ existing,
1475
+ `Provider/model changed: ${oldCfg.provider ?? "(none)"} \u2192 ${provider}, ${oldCfg.model ?? "(none)"} \u2192 ${model}`,
1476
+ homeFn,
1477
+ configPath
1478
+ );
1479
+ } catch {
1480
+ }
1481
+ return true;
1482
+ } catch (err) {
1483
+ console.warn(
1484
+ JSON.stringify({
1485
+ level: "warn",
1486
+ event: "picker.save_failed",
1487
+ message: toErrorMessage(err),
1488
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1489
+ })
1490
+ );
1491
+ return false;
1492
+ }
1493
+ }
1494
+ var QUERY_MARKER = "Select provider:";
1495
+ function cursorToQuery(frame, query) {
1496
+ const rows = frame.split("\n");
1497
+ const lastIdx = rows.length - 1;
1498
+ const queryIdx = rows.findIndex((r) => r.includes(QUERY_MARKER));
1499
+ if (queryIdx < 0) return;
1500
+ const up = lastIdx - queryIdx;
1501
+ const queryLine = rows[queryIdx] ?? "";
1502
+ const plain = stripAnsi2(queryLine);
1503
+ const markerAt = plain.indexOf(QUERY_MARKER);
1504
+ const col = markerAt + QUERY_MARKER.length + 1 + query.length + 1;
1505
+ if (up > 0) writeOut2(`\x1B[${up}A`);
1506
+ writeOut2(`\x1B[${col}G`);
1507
+ }
1508
+ async function runLiveProviderPicker(displayList, savedSet) {
1509
+ const stdin = process.stdin;
1510
+ const out = process.stdout;
1511
+ if (!stdin.isTTY || !out.isTTY) return void 0;
1512
+ setOutputLineGuard2(null);
1513
+ let state = { query: "", selected: 0, status: "typing" };
1514
+ let ordered = orderProvidersForDisplay(filterProviders(state.query, displayList));
1515
+ const visibleCount = () => ordered.length;
1516
+ const clamp = () => {
1517
+ if (state.selected >= visibleCount()) state.selected = Math.max(0, visibleCount() - 1);
1518
+ };
1519
+ clamp();
1520
+ let frame = renderLiveProviderList(state.query, ordered, state.selected, savedSet);
1521
+ writeOut2(frame);
1522
+ writeOut2("\x1B7");
1523
+ cursorToQuery(frame, state.query);
1524
+ return new Promise((resolve) => {
1525
+ const wasRaw = stdin.isRaw;
1526
+ const wasPaused = stdin.isPaused();
1527
+ setRawMode2(stdin, true);
1528
+ stdin.resume();
1529
+ stdin.setEncoding("utf8");
1530
+ const cleanup = () => {
1531
+ stdin.off("data", onData);
1532
+ setRawMode2(stdin, wasRaw);
1533
+ if (wasPaused) stdin.pause();
1534
+ };
1535
+ const repaint = () => {
1536
+ writeOut2("\x1B8");
1537
+ const ups = (frame.match(/\n/g) ?? []).length;
1538
+ writeOut2(`\x1B[${ups}A\r\x1B[J`);
1539
+ ordered = orderProvidersForDisplay(filterProviders(state.query, displayList));
1540
+ clamp();
1541
+ frame = renderLiveProviderList(state.query, ordered, state.selected, savedSet);
1542
+ writeOut2(frame);
1543
+ writeOut2("\x1B7");
1544
+ cursorToQuery(frame, state.query);
1545
+ };
1546
+ const onData = (chunk) => {
1547
+ ordered = orderProvidersForDisplay(filterProviders(state.query, displayList));
1548
+ state = applyPickerKey(state, chunk, visibleCount());
1549
+ ordered = orderProvidersForDisplay(filterProviders(state.query, displayList));
1550
+ clamp();
1551
+ if (state.status === "cancelled") {
1552
+ cleanup();
1553
+ writeOut2("\n");
1554
+ resolve(void 0);
1555
+ return;
1556
+ }
1557
+ if (state.status === "submitted") {
1558
+ if (ordered.length === 0) {
1559
+ state = { ...state, status: "typing" };
1560
+ repaint();
1561
+ return;
1562
+ }
1563
+ const pick = ordered[state.selected] ?? ordered[0];
1564
+ cleanup();
1565
+ writeOut2("\n");
1566
+ resolve(pick);
1567
+ return;
1568
+ }
1569
+ repaint();
1570
+ };
1571
+ stdin.on("data", onData);
1572
+ });
1573
+ }
1574
+ async function runPicker(deps) {
1575
+ const { modelsRegistry, renderer, reader, config, defaultProvider, defaultModel } = deps;
1576
+ renderer.write(
1577
+ `
1578
+ ${theme.accent(color8.bold("\u258C WrongStack"))} ${color8.dim("Provider & Model Selection")}
1579
+ `
1580
+ );
1581
+ renderer.write(color8.dim(`${theme.chrome("\xB7")} Loading provider catalog\u2026
1582
+ `));
1583
+ let providers;
1584
+ try {
1585
+ providers = await modelsRegistry.listProviders();
1586
+ } catch {
1587
+ renderer.writeError(
1588
+ "Failed to load provider catalog. Pass --provider and --model to skip the picker."
1589
+ );
1590
+ return void 0;
1591
+ }
1592
+ const supported = providers.filter((p) => p.family !== "unsupported");
1593
+ const catalogById = new Map(supported.map((p) => [p.id, p]));
1594
+ const overlay = config?.providers ?? {};
1595
+ const seen = /* @__PURE__ */ new Set();
1596
+ const merged = [];
1597
+ for (const p of supported) {
1598
+ const cfg = overlay[p.id];
1599
+ seen.add(p.id);
1600
+ if (cfg) {
1601
+ merged.push({
1602
+ ...p,
1603
+ family: cfg.family ?? p.family,
1604
+ apiBase: cfg.baseUrl ?? p.apiBase,
1605
+ envVars: cfg.envVars && cfg.envVars.length > 0 ? cfg.envVars : p.envVars,
1606
+ // When the user has saved an explicit model list, it wins — they
1607
+ // know which models their endpoint actually serves (e.g. LM
1608
+ // Studio, vLLM, or a proxy with custom model ids). Otherwise the
1609
+ // catalog list keeps providing suggestions.
1610
+ models: visibleModelIds(
1611
+ p.id,
1612
+ config ?? { providers: {} },
1613
+ p.models.map((m) => m.id),
1614
+ cfg
1615
+ ).map((m) => p.models.find((pm) => pm.id === m) ?? { id: m, name: m })
1616
+ });
1617
+ } else {
1618
+ merged.push(p);
1619
+ }
1620
+ }
1621
+ for (const [id, cfg] of Object.entries(overlay)) {
1622
+ if (seen.has(id)) continue;
1623
+ if (!cfg?.family || cfg.family === "unsupported") continue;
1624
+ const catalogType = cfg.type && cfg.type !== id ? cfg.type : void 0;
1625
+ const inherited = catalogType ? catalogById.get(catalogType) : void 0;
1626
+ merged.push({
1627
+ id,
1628
+ name: inherited ? `${inherited.name} ${color8.dim("(alias)")}` : id,
1629
+ family: cfg.family,
1630
+ apiBase: cfg.baseUrl ?? inherited?.apiBase,
1631
+ envVars: cfg.envVars ?? inherited?.envVars ?? [],
1632
+ models: visibleModelIds(
1633
+ id,
1634
+ config ?? { providers: {} },
1635
+ (inherited?.models ?? []).map((m) => m.id),
1636
+ cfg
1637
+ ).map((m) => inherited?.models.find((pm) => pm.id === m) ?? { id: m, name: m }),
1638
+ npm: inherited?.npm
1639
+ });
1640
+ }
1641
+ const keyed = merged.filter((p) => hasApiKey(p, config) || isKeylessLocalProvider(p));
1642
+ let displayList = keyed;
1643
+ let showingFallback = false;
1644
+ if (keyed.length === 0) {
1645
+ displayList = merged;
1646
+ showingFallback = merged.length > 0;
1647
+ }
1648
+ appendLocalPresetProviders(displayList);
1649
+ if (displayList.length === 0) {
1650
+ renderer.writeError("No supported providers found in catalog.");
1651
+ return void 0;
1652
+ }
1653
+ if (process.stdin.isTTY) {
1654
+ const chosen2 = await runLiveProviderPicker(displayList);
1655
+ if (!chosen2) {
1656
+ renderer.write(color8.dim("Cancelled.\n"));
1657
+ return void 0;
1658
+ }
1659
+ return pickModel(chosen2, modelsRegistry, renderer, reader, defaultModel);
1660
+ }
1661
+ const families = /* @__PURE__ */ new Map();
1662
+ for (const p of displayList) {
1663
+ const list = families.get(p.family) ?? [];
1664
+ list.push(p);
1665
+ families.set(p.family, list);
1666
+ }
1667
+ const ordered = [];
1668
+ const preferredOrder = [
1669
+ "anthropic",
1670
+ "anthropic-oauth",
1671
+ "openai",
1672
+ "openai-codex",
1673
+ "github-copilot",
1674
+ "google",
1675
+ "openai-compatible"
1676
+ ];
1677
+ const familyOrder = [
1678
+ ...preferredOrder.filter((f) => families.has(f)),
1679
+ ...[...families.keys()].filter((f) => !preferredOrder.includes(f))
1680
+ ];
1681
+ let idx = 1;
1682
+ let defaultIdx;
1683
+ renderer.write("\n");
1684
+ renderer.write(`${boxTop("Select a provider")}
1685
+ `);
1686
+ for (const fam of familyOrder) {
1687
+ const list = [...families.get(fam) ?? []].sort(
1688
+ (a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase())
1689
+ );
1690
+ if (!list || list.length === 0) continue;
1691
+ renderer.write(`${boxRow(`${theme.accent("\xB7")} ${color8.dim(fam.toUpperCase())}`)}
1692
+ `);
1693
+ for (const p of list) {
1694
+ const envFound = p.envVars.some((v) => !!process.env[v]);
1695
+ const entry = config?.providers?.[p.id];
1696
+ const configKey = typeof entry?.apiKey === "string" && entry.apiKey.length > 0 || Array.isArray(entry?.apiKeys) && entry?.apiKeys?.some((k) => k?.apiKey);
1697
+ const marker = envFound ? color8.green("\u25CF") : configKey ? color8.cyan("\u25C9") : color8.dim("\u25CB");
1698
+ const isDefault = p.id === defaultProvider;
1699
+ if (isDefault) defaultIdx = idx;
1700
+ const idLabel = isDefault ? theme.accent(color8.bold(p.id)) : p.id;
1701
+ const suffix = isDefault ? color8.dim(" (default)") : "";
1702
+ renderer.write(
1703
+ `${boxRow(`${color8.dim(`${idx}.`.padStart(4))} ${marker} ${padVisible(idLabel, 22)} ${color8.dim(p.name)}${suffix}`)}
1704
+ `
1705
+ );
1706
+ ordered.push({ provider: p, index: idx });
1707
+ idx++;
1708
+ }
1709
+ }
1710
+ renderer.write(`${boxDivider()}
1711
+ `);
1712
+ if (showingFallback) {
1713
+ renderer.write(
1714
+ `${boxRow(`${color8.yellow("\u26A0 No API keys detected.")} ${color8.dim("Run `wstack auth <provider>` after picking.")}`)}
1715
+ `
1716
+ );
1717
+ } else {
1718
+ renderer.write(`${boxRow(color8.dim("\u25CF env key \u25C9 stored in config \u25CB no key"))}
1719
+ `);
1720
+ }
1721
+ renderer.write(`${boxBottom()}
1722
+ `);
1723
+ const defaultHint = defaultIdx !== void 0 && defaultProvider ? ` ${color8.dim(`[Enter = ${defaultProvider}]`)}` : "";
1724
+ const providerAnswer = (await reader.readLine(
1725
+ `
1726
+ ${color8.amber("?")} Select provider (1-${ordered.length})${defaultHint} ${color8.dim("[q to quit]")}: `
1727
+ )).trim();
1728
+ if (providerAnswer.toLowerCase() === "q") {
1729
+ renderer.write(color8.dim("Cancelled.\n"));
1730
+ return void 0;
1731
+ }
1732
+ if (!providerAnswer) {
1733
+ if (defaultIdx !== void 0) {
1734
+ const def = ordered[defaultIdx - 1];
1735
+ if (def) return pickModel(def.provider, modelsRegistry, renderer, reader, defaultModel);
1736
+ }
1737
+ renderer.write(color8.dim("Cancelled.\n"));
1738
+ return void 0;
1739
+ }
1740
+ const providerIdx = Number.parseInt(providerAnswer, 10);
1741
+ if (Number.isNaN(providerIdx) || providerIdx < 1 || providerIdx > ordered.length) {
1742
+ const byId = ordered.find((o) => o.provider.id.toLowerCase() === providerAnswer.toLowerCase());
1743
+ if (!byId) {
1744
+ renderer.writeError(`Invalid selection: "${providerAnswer}"`);
1745
+ return void 0;
1746
+ }
1747
+ return pickModel(byId.provider, modelsRegistry, renderer, reader, defaultModel);
1748
+ }
1749
+ const chosen = ordered[providerIdx - 1];
1750
+ if (!chosen) return void 0;
1751
+ const modelHint = chosen.provider.id === defaultProvider ? defaultModel : void 0;
1752
+ return pickModel(chosen.provider, modelsRegistry, renderer, reader, modelHint);
1753
+ }
1754
+ async function pickModel(provider, registry, renderer, reader, defaultModel) {
1755
+ renderer.write(`
1756
+ ${boxTop("Select a model")}
1757
+ `);
1758
+ renderer.write(
1759
+ `${boxRow(`${color8.bold(provider.name)} ${color8.dim(`(${provider.id})`)} models:`)}
1760
+ `
1761
+ );
1762
+ renderer.write(`${boxBottom()}
1763
+ `);
1764
+ const codexBlock = codexPickerPreamble(provider);
1765
+ if (codexBlock) renderer.write(codexBlock);
1766
+ const models = [...provider.models].sort(
1767
+ (a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "")
1768
+ );
1769
+ if (models.length === 0) {
1770
+ renderer.write(
1771
+ color8.dim(
1772
+ " No models listed yet for this provider \u2014 type a model id to use it (local servers auto-discover models at launch).\n"
1773
+ )
1774
+ );
1775
+ const typed = (await reader.readLine(`
1776
+ ${color8.amber("?")} Model id ${color8.dim("(or Enter to cancel)")}: `)).trim();
1777
+ if (!typed) {
1778
+ renderer.write(color8.dim("Cancelled.\n"));
1779
+ return void 0;
1780
+ }
1781
+ renderer.write(`
1782
+ ${color8.green("\u2713")} ${color8.bold(provider.id)} / ${color8.bold(typed)}
1783
+
1784
+ `);
1785
+ return { provider: provider.id, model: typed };
1786
+ }
1787
+ if (process.stdin.isTTY) {
1788
+ const chosen = await runLiveModelPicker(provider, defaultModel);
1789
+ if (!chosen) {
1790
+ renderer.write(color8.dim("Cancelled.\n"));
1791
+ return void 0;
1792
+ }
1793
+ renderer.write(
1794
+ `
1795
+ ${color8.green("\u2713")} ${color8.bold(provider.id)} / ${color8.bold(chosen.id)}
1796
+
1797
+ `
1798
+ );
1799
+ return { provider: provider.id, model: chosen.id };
1800
+ }
1801
+ const defaultIdxInModels = defaultModel !== void 0 ? models.findIndex((m) => m.id === defaultModel) : -1;
1802
+ const pageSize = 30;
1803
+ let offset = 0;
1804
+ while (offset < models.length) {
1805
+ const page = models.slice(offset, offset + pageSize);
1806
+ const pageFrom = offset + 1;
1807
+ const pageTo = Math.min(offset + page.length, models.length);
1808
+ renderer.write(`${boxTop(`Models ${pageFrom}\u2013${pageTo} of ${models.length}`)}
1809
+ `);
1810
+ for (let i = 0; i < page.length; i++) {
1811
+ const m = expectDefined2(page[i]);
1812
+ const num = offset + i + 1;
1813
+ const ctxRaw = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
1814
+ const ctx = theme.ctx(ctxRaw.padStart(6));
1815
+ const cost = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
1816
+ const costCol = cost ? theme.cost(cost) : "";
1817
+ const capTags = [];
1818
+ if (m.tool_call) capTags.push("tools");
1819
+ if (m.reasoning) capTags.push("reason");
1820
+ if (m.modalities?.input?.includes("image")) capTags.push("vision");
1821
+ const capStr = capTags.map((c) => theme.caps(c)).join(color8.dim(" "));
1822
+ const isDefault = m.id === defaultModel;
1823
+ const idLabel = isDefault ? theme.accent(color8.bold(m.id)) : m.id;
1824
+ const suffix = isDefault ? color8.dim(" (default)") : "";
1825
+ const num5 = color8.dim(`${num}.`.padStart(5));
1826
+ renderer.write(
1827
+ `${boxRow(`${num5} ${padVisible(idLabel, 34)} ${ctx} ${padVisible(costCol, 11)} ${capStr}${suffix}`)}
1828
+ `
1829
+ );
1830
+ }
1831
+ renderer.write(`${boxBottom()}
1832
+ `);
1833
+ offset += pageSize;
1834
+ if (offset < models.length) {
1835
+ const more = (await reader.readLine(
1836
+ `
1837
+ ${color8.amber("?")} Showing ${Math.min(offset, models.length)}/${models.length} \u2014 Enter number, ${color8.dim("Enter")} for more, or ${color8.dim("q")} to quit: `
1838
+ )).trim();
1839
+ if (more.toLowerCase() === "q") {
1840
+ renderer.write(color8.dim("Cancelled.\n"));
1841
+ return void 0;
1842
+ }
1843
+ if (!more) continue;
1844
+ return resolveModelSelection(more, models, provider, registry, renderer, reader);
1845
+ }
1846
+ }
1847
+ const defaultHint = defaultIdxInModels >= 0 && defaultModel ? ` ${color8.dim(`[Enter = ${defaultModel}]`)}` : "";
1848
+ const answer = (await reader.readLine(
1849
+ `
1850
+ ${color8.amber("?")} Select model (1-${models.length})${defaultHint} ${color8.dim("[q to quit]")}: `
1851
+ )).trim();
1852
+ if (answer.toLowerCase() === "q") {
1853
+ renderer.write(color8.dim("Cancelled.\n"));
1854
+ return void 0;
1855
+ }
1856
+ if (!answer) {
1857
+ if (defaultIdxInModels >= 0 && defaultModel) {
1858
+ renderer.write(
1859
+ `
1860
+ ${color8.green("\u2713")} ${color8.bold(provider.id)} / ${color8.bold(defaultModel)}
1861
+
1862
+ `
1863
+ );
1864
+ return { provider: provider.id, model: defaultModel };
1865
+ }
1866
+ renderer.write(color8.dim("Cancelled.\n"));
1867
+ return void 0;
1868
+ }
1869
+ return resolveModelSelection(answer, models, provider, registry, renderer, reader);
1870
+ }
1871
+ async function resolveModelSelection(answer, models, provider, _registry, renderer, _reader) {
1872
+ const idx = Number.parseInt(answer, 10);
1873
+ let modelId;
1874
+ if (!Number.isNaN(idx) && idx >= 1 && idx <= models.length) {
1875
+ modelId = models[idx - 1]?.id;
1876
+ } else {
1877
+ const lower = answer.toLowerCase();
1878
+ const match = models.find((m) => m.id.toLowerCase() === lower);
1879
+ if (match) {
1880
+ modelId = match.id;
1881
+ } else {
1882
+ const partial = models.filter((m) => m.id.toLowerCase().includes(lower));
1883
+ if (partial.length === 1) {
1884
+ modelId = partial[0]?.id;
1885
+ } else if (partial.length > 1) {
1886
+ renderer.writeError(`"${answer}" matches multiple models. Be more specific.`);
1887
+ return void 0;
1888
+ }
1889
+ }
1890
+ }
1891
+ if (!modelId) {
1892
+ modelId = answer;
1893
+ }
1894
+ renderer.write(`
1895
+ ${color8.green("\u2713")} ${color8.bold(provider.id)} / ${color8.bold(modelId)}
1896
+
1897
+ `);
1898
+ return { provider: provider.id, model: modelId };
1899
+ }
1900
+
1077
1901
  // src/auth-menu/add-provider.ts
1078
1902
  import * as fs from "node:fs/promises";
1079
- import { atomicWrite, color as color6 } from "@wrongstack/core/utils";
1903
+ import { atomicWrite, color as color9 } from "@wrongstack/core/utils";
1080
1904
  import { catalogProviderIdFor, PROVIDER_DEFINITIONS } from "@wrongstack/providers";
1081
1905
 
1082
1906
  // src/provider-id.ts
@@ -1115,12 +1939,12 @@ async function addFromCatalog(deps) {
1115
1939
  catalog = [...catalog, ...ownDefinitionsAsCatalog(new Set(catalog.map((p) => p.id)))];
1116
1940
  const saved = new Set(Object.keys(await loadProviders(deps)));
1117
1941
  deps.renderer.write(
1118
- color6.dim(
1942
+ color9.dim(
1119
1943
  ` Catalog: ${catalog.length} providers. Type to filter, \u2191/\u2193 navigate, Enter to select.
1120
1944
  `
1121
1945
  )
1122
1946
  );
1123
- deps.renderer.write(` ${color6.dim("\u25C9 already saved \u25CB no key yet")}
1947
+ deps.renderer.write(` ${color9.dim("\u25C9 already saved \u25CB no key yet")}
1124
1948
 
1125
1949
  `);
1126
1950
  renderOAuthLoginOptions(deps);
@@ -1129,11 +1953,11 @@ async function addFromCatalog(deps) {
1129
1953
  if (!chosen) {
1130
1954
  deps.renderer.write(
1131
1955
  `
1132
- ${color6.dim("OAuth login options:")} ${color6.dim("chatgpt")}, ${color6.dim("claude")}, or ${color6.dim("copilot")}?
1956
+ ${color9.dim("OAuth login options:")} ${color9.dim("chatgpt")}, ${color9.dim("claude")}, or ${color9.dim("copilot")}?
1133
1957
  `
1134
1958
  );
1135
1959
  const answer = (await deps.reader.readLine(
1136
- ` ${color6.amber("?")} OAuth or q to quit ${color6.dim("[chatgpt/claude/copilot]")}: `
1960
+ ` ${color9.amber("?")} OAuth or q to quit ${color9.dim("[chatgpt/claude/copilot]")}: `
1137
1961
  )).trim();
1138
1962
  if (answer && await runOAuthLoginChoice(deps, answer, { allowNumeric: false })) {
1139
1963
  return true;
@@ -1144,12 +1968,12 @@ async function addFromCatalog(deps) {
1144
1968
  }
1145
1969
  async function addKeyForCatalogProvider(deps, chosen) {
1146
1970
  deps.renderer.write(
1147
- color6.dim(`
1971
+ color9.dim(`
1148
1972
  Defaults from models.dev \u2014 press Enter to keep, or type overrides.
1149
1973
  `)
1150
1974
  );
1151
1975
  const famRaw = (await deps.reader.readLine(
1152
- ` ${color6.amber("?")} Family ${color6.dim(`[${chosen.family}]`)} ${color6.dim("(q to quit)")}: `
1976
+ ` ${color9.amber("?")} Family ${color9.dim(`[${chosen.family}]`)} ${color9.dim("(q to quit)")}: `
1153
1977
  )).trim();
1154
1978
  if (famRaw === "q") return false;
1155
1979
  let family = chosen.family;
@@ -1164,7 +1988,7 @@ async function addKeyForCatalogProvider(deps, chosen) {
1164
1988
  family = validated;
1165
1989
  }
1166
1990
  const baseRaw = (await deps.reader.readLine(
1167
- ` ${color6.amber("?")} Base URL ${color6.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color6.dim("(q to quit)")}: `
1991
+ ` ${color9.amber("?")} Base URL ${color9.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color9.dim("(q to quit)")}: `
1168
1992
  )).trim();
1169
1993
  if (baseRaw === "q") return false;
1170
1994
  const baseUrl = baseRaw || chosen.apiBase;
@@ -1180,7 +2004,7 @@ async function addKeyForCatalogProvider(deps, chosen) {
1180
2004
  suggestedAlias = candidate;
1181
2005
  }
1182
2006
  const aliasRaw = (await deps.reader.readLine(
1183
- ` ${color6.amber("?")} Save as alias ${color6.dim(`[${suggestedAlias}]`)} ${color6.dim("(used with --provider <alias>)")}: `
2007
+ ` ${color9.amber("?")} Save as alias ${color9.dim(`[${suggestedAlias}]`)} ${color9.dim("(used with --provider <alias>)")}: `
1184
2008
  )).trim();
1185
2009
  const alias = aliasRaw || suggestedAlias;
1186
2010
  const existing = providersNow[alias];
@@ -1207,25 +2031,25 @@ async function addKeyForCatalogProvider(deps, chosen) {
1207
2031
  async function addCustomProvider(deps) {
1208
2032
  deps.renderer.write(
1209
2033
  `
1210
- ${color6.bold("Custom provider")} ${color6.dim("\u2014 for local models or proxies not in the catalog.")}
2034
+ ${color9.bold("Custom provider")} ${color9.dim("\u2014 for local models or proxies not in the catalog.")}
1211
2035
  `
1212
2036
  );
1213
2037
  const providersNow = await loadProviders(deps);
1214
2038
  const idRaw = (await deps.reader.readLine(
1215
- ` ${color6.amber("?")} Provider id ${color6.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
2039
+ ` ${color9.amber("?")} Provider id ${color9.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
1216
2040
  )).trim();
1217
2041
  if (idRaw === "q") return false;
1218
2042
  let type = idRaw;
1219
2043
  if (!type) {
1220
2044
  type = nextCustomProviderId(Object.keys(providersNow));
1221
- deps.renderer.write(` ${color6.dim("Using generated id:")} ${color6.bold(type)}
2045
+ deps.renderer.write(` ${color9.dim("Using generated id:")} ${color9.bold(type)}
1222
2046
  `);
1223
2047
  } else if (providersNow[type]) {
1224
2048
  deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
1225
2049
  return false;
1226
2050
  }
1227
2051
  const familyRaw = (await deps.reader.readLine(
1228
- ` ${color6.amber("?")} Wire family ${color6.dim("(anthropic | openai | openai-compatible | google)")} ${color6.dim("(q to quit)")}: `
2052
+ ` ${color9.amber("?")} Wire family ${color9.dim("(anthropic | openai | openai-compatible | google)")} ${color9.dim("(q to quit)")}: `
1229
2053
  )).trim();
1230
2054
  if (familyRaw === "q") return false;
1231
2055
  const family = validateFamily(familyRaw);
@@ -1234,14 +2058,14 @@ ${color6.bold("Custom provider")} ${color6.dim("\u2014 for local models or proxi
1234
2058
  return false;
1235
2059
  }
1236
2060
  const baseUrl = (await deps.reader.readLine(
1237
- ` ${color6.amber("?")} Base URL ${color6.dim("(e.g. http://localhost:11434/v1, optional)")}: `
2061
+ ` ${color9.amber("?")} Base URL ${color9.dim("(e.g. http://localhost:11434/v1, optional)")}: `
1238
2062
  )).trim();
1239
2063
  const modelsRaw = (await deps.reader.readLine(
1240
- ` ${color6.amber("?")} Model ids ${color6.dim("(comma-separated, optional)")}: `
2064
+ ` ${color9.amber("?")} Model ids ${color9.dim("(comma-separated, optional)")}: `
1241
2065
  )).trim();
1242
2066
  const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1243
2067
  const envVarsRaw = (await deps.reader.readLine(
1244
- ` ${color6.amber("?")} Env var names ${color6.dim("(comma-separated, optional)")}: `
2068
+ ` ${color9.amber("?")} Env var names ${color9.dim("(comma-separated, optional)")}: `
1245
2069
  )).trim();
1246
2070
  const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1247
2071
  return addKeyForProvider(type, deps, {
@@ -1253,17 +2077,17 @@ ${color6.bold("Custom provider")} ${color6.dim("\u2014 for local models or proxi
1253
2077
  });
1254
2078
  }
1255
2079
  async function addManualEntry(deps) {
1256
- const pid = (await deps.reader.readLine(` ${color6.amber("?")} Provider id ${color6.dim("[q to quit]")}: `)).trim();
2080
+ const pid = (await deps.reader.readLine(` ${color9.amber("?")} Provider id ${color9.dim("[q to quit]")}: `)).trim();
1257
2081
  if (!pid || pid === "q") return false;
1258
2082
  const famRaw = (await deps.reader.readLine(
1259
- ` ${color6.amber("?")} Family ${color6.dim("(anthropic/openai/openai-compatible/google)")}: `
2083
+ ` ${color9.amber("?")} Family ${color9.dim("(anthropic/openai/openai-compatible/google)")}: `
1260
2084
  )).trim();
1261
2085
  const family = validateFamily(famRaw);
1262
2086
  if (!family) {
1263
2087
  deps.renderer.writeError(`Invalid family: "${famRaw}"`);
1264
2088
  return false;
1265
2089
  }
1266
- const baseUrl = (await deps.reader.readLine(` ${color6.amber("?")} Base URL ${color6.dim("(optional)")}: `)).trim();
2090
+ const baseUrl = (await deps.reader.readLine(` ${color9.amber("?")} Base URL ${color9.dim("(optional)")}: `)).trim();
1267
2091
  return addKeyForProvider(pid, deps, {
1268
2092
  type: pid,
1269
2093
  family,
@@ -1306,10 +2130,10 @@ async function addKeyForProvider(providerId, deps, template) {
1306
2130
  deps.profileConfigPath
1307
2131
  );
1308
2132
  deps.renderer.write(
1309
- ` ${color6.green("\u2713")} Saved ${color6.bold(providerId)}/${color6.bold(label)}.
2133
+ ` ${color9.green("\u2713")} Saved ${color9.bold(providerId)}/${color9.bold(label)}.
1310
2134
  `
1311
2135
  );
1312
- deps.renderer.write(color6.dim(` Launch: wstack --provider ${providerId} "<task>"
2136
+ deps.renderer.write(color9.dim(` Launch: wstack --provider ${providerId} "<task>"
1313
2137
  `));
1314
2138
  try {
1315
2139
  await adoptAsDefaultIfUnset(deps, providerId, template);
@@ -1337,14 +2161,14 @@ async function adoptAsDefaultIfUnset(deps, providerId, template) {
1337
2161
  cfg["model"] = modelId;
1338
2162
  await atomicWrite(targetPath, JSON.stringify(cfg, null, 2), { mode: 384 });
1339
2163
  deps.renderer.write(
1340
- ` ${color6.green("\u2713")} Set ${color6.bold(providerId)}/${color6.bold(modelId)} as the default.
2164
+ ` ${color9.green("\u2713")} Set ${color9.bold(providerId)}/${color9.bold(modelId)} as the default.
1341
2165
  `
1342
2166
  );
1343
2167
  }
1344
2168
  async function promptForLabel(deps, usedLabels) {
1345
2169
  const defaultLabel = suggestLabel(usedLabels);
1346
2170
  const labelRaw = (await deps.reader.readLine(
1347
- ` ${color6.amber("?")} Label for this key ${color6.dim(`[${defaultLabel}]`)}: `
2171
+ ` ${color9.amber("?")} Label for this key ${color9.dim(`[${defaultLabel}]`)}: `
1348
2172
  )).trim();
1349
2173
  const label = labelRaw || defaultLabel;
1350
2174
  if (usedLabels.has(label)) {
@@ -1364,14 +2188,22 @@ export {
1364
2188
  confirm,
1365
2189
  validateFamily,
1366
2190
  runCopilotOAuthLogin,
2191
+ LOCAL_LLM_PRESETS,
1367
2192
  runAuthLocal,
1368
2193
  resolveOAuthKind,
1369
2194
  runOAuthLoginKind,
1370
2195
  runOAuthLoginMenu,
2196
+ theme,
2197
+ boxTop,
2198
+ boxBottom,
2199
+ boxRow,
2200
+ boxDivider,
2201
+ saveToGlobalConfig,
2202
+ runPicker,
1371
2203
  nextCustomProviderId,
1372
2204
  addFromCatalog,
1373
2205
  addKeyForCatalogProvider,
1374
2206
  addCustomProvider,
1375
2207
  addKeyForProvider
1376
2208
  };
1377
- //# sourceMappingURL=chunk-YX6KUIDF.js.map
2209
+ //# sourceMappingURL=chunk-IMT6PNV5.js.map