@wrongstack/cli 0.299.0 → 0.301.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 (82) hide show
  1. package/dist/{acp-S4LZQB66.js → acp-DATHHPNT.js} +36 -17
  2. package/dist/acp-server-agent.d.ts +1 -1
  3. package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
  4. package/dist/{auth-G5YKKSRH.js → auth-JASC4K3F.js} +151 -38
  5. package/dist/auth-menu/loopback-server.d.ts +41 -55
  6. package/dist/auth-menu/openai-codex-oauth.d.ts +36 -123
  7. package/dist/auto-discover-providers-DRW4OZHG.js +8 -0
  8. package/dist/boot/dispatch-webui.d.ts +3 -0
  9. package/dist/boot/launch-menu.d.ts +2 -4
  10. package/dist/boot/tool-registry.d.ts +3 -0
  11. package/dist/chimera-reviewer-policy.d.ts +10 -3
  12. package/dist/chimera-work-registry.d.ts +19 -0
  13. package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
  14. package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
  15. package/dist/{chunk-2SLSUDYS.js → chunk-2KDOFTTM.js} +312 -211
  16. package/dist/{chunk-TGDHN4LM.js → chunk-4GVH7V3S.js} +4 -24
  17. package/dist/chunk-CSAPOCBP.js +24 -0
  18. package/dist/{chunk-66T43Q4Y.js → chunk-ETOEGL3V.js} +6 -9
  19. package/dist/{chunk-PEKUKUCH.js → chunk-GUHQQG63.js} +32 -14
  20. package/dist/chunk-KE7E7DPX.js +38 -0
  21. package/dist/{chunk-ZNZJ34RF.js → chunk-MHF2IJDE.js} +21 -4
  22. package/dist/{chunk-4HH3YGG4.js → chunk-MRLYDHQ7.js} +8 -5
  23. package/dist/{chunk-L5CE52XW.js → chunk-O7XNGCRT.js} +317 -309
  24. package/dist/chunk-PYTFS4MC.js +238 -0
  25. package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
  26. package/dist/{chunk-4U3DMA2Y.js → chunk-ZECLGUTF.js} +28 -21
  27. package/dist/{cli-main-3MQDFSYK.js → cli-main-XWC57MGE.js} +849 -242
  28. package/dist/{execution-ANOYMWPO.js → execution-LHMSMDHB.js} +174 -55
  29. package/dist/execution-chimera-cascade.d.ts +3 -4
  30. package/dist/execution-chimera-review.d.ts +2 -3
  31. package/dist/execution-cleanup.d.ts +3 -2
  32. package/dist/execution.d.ts +1 -1
  33. package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
  34. package/dist/fleet/budget-source.d.ts +18 -0
  35. package/dist/fleet/host-context.d.ts +1 -1
  36. package/dist/fleet/host-helpers.d.ts +14 -0
  37. package/dist/fleet/host-status.d.ts +31 -0
  38. package/dist/fleet/host-types.d.ts +8 -0
  39. package/dist/fleet/host.d.ts +5 -0
  40. package/dist/fleet/status-broadcast.d.ts +1 -1
  41. package/dist/{hq-BDO56CX6.js → hq-LN5CJHCM.js} +22 -3
  42. package/dist/hq-server/auth-state.d.ts +29 -1
  43. package/dist/hq-server/auth.d.ts +16 -0
  44. package/dist/hq-server/client-address.d.ts +56 -0
  45. package/dist/hq-server/login-attempt-store.d.ts +30 -10
  46. package/dist/hq-server/routes/auth-handlers.d.ts +66 -26
  47. package/dist/hq-server/routes.d.ts +23 -1
  48. package/dist/hq-server/types.d.ts +10 -0
  49. package/dist/hq-server/ws.d.ts +17 -6
  50. package/dist/{hq-server-RJLJJZU7.js → hq-server-S7HZG3PA.js} +3 -2
  51. package/dist/hq-server.d.ts +19 -0
  52. package/dist/index.js +69 -104
  53. package/dist/live-settings-input.d.ts +5 -0
  54. package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
  55. package/dist/{modeldiag-HENYJ4N7.js → modeldiag-3DOK4BWW.js} +3 -3
  56. package/dist/{plugin-usage-VTL7ZTVC.js → plugin-usage-YEVLOR33.js} +7 -3
  57. package/dist/{plugins-ETY6W3DC.js → plugins-K3BAUP4D.js} +3 -3
  58. package/dist/profile-config-path.d.ts +0 -2
  59. package/dist/provider-helpers.d.ts +0 -5
  60. package/dist/{providers-models-GDELBHZK.js → providers-models-WEZ4EGLL.js} +38 -12
  61. package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
  62. package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
  63. package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
  64. package/dist/slash-commands/command-context.d.ts +19 -0
  65. package/dist/subcommands/flags.d.ts +23 -0
  66. package/dist/terminal-format.d.ts +15 -0
  67. package/dist/webui-server/setup-events.d.ts +17 -0
  68. package/dist/webui-server/terminal-log-view.d.ts +29 -12
  69. package/dist/{webui-server-PCLSXX6L.js → webui-server-DD35QFNO.js} +113 -50
  70. package/dist/webui-server-options.d.ts +16 -0
  71. package/dist/wiring/brain-and-orchestration.d.ts +4 -1
  72. package/dist/wiring/director-setup.d.ts +4 -0
  73. package/dist/wiring/fallback-gate.d.ts +31 -0
  74. package/dist/wiring/fleet-command-handlers.d.ts +1 -1
  75. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
  76. package/dist/wiring/management-tools.d.ts +9 -1
  77. package/dist/wiring/pipeline.d.ts +5 -5
  78. package/dist/wiring/plugins.d.ts +3 -10
  79. package/dist/wiring/provider-utility-tools.d.ts +27 -1
  80. package/dist/wiring/tools.d.ts +4 -5
  81. package/package.json +23 -23
  82. package/dist/chunk-GO3TJICK.js +0 -489
@@ -3,8 +3,8 @@ import {
3
3
  loadCachedAcpRegistry,
4
4
  refreshAcpRegistry,
5
5
  setupProvider
6
- } from "./chunk-4HH3YGG4.js";
7
- import "./chunk-GO3TJICK.js";
6
+ } from "./chunk-MRLYDHQ7.js";
7
+ import "./chunk-PYTFS4MC.js";
8
8
  import "./chunk-PJHET45A.js";
9
9
  import "./chunk-7OCVIDC7.js";
10
10
 
@@ -32,13 +32,14 @@ import { WebSocketServer } from "ws";
32
32
 
33
33
  // src/acp-server-agent.ts
34
34
  import { Agent, Context, createDefaultPipelines } from "@wrongstack/core/agent";
35
- import { AutoApprovePermissionPolicy, ToolCapabilities } from "@wrongstack/core/security";
36
- import { DefaultLogger } from "@wrongstack/core/infrastructure";
35
+ import { ToolExecutor } from "@wrongstack/core/execution";
36
+ import { DefaultLogger, DefaultTokenCounter } from "@wrongstack/core/infrastructure";
37
37
  import { EventBus, TOKENS } from "@wrongstack/core/kernel";
38
- import { ToolValidationError } from "@wrongstack/core/types";
39
38
  import { ToolRegistry } from "@wrongstack/core/registry";
40
- import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
41
- import { ToolExecutor } from "@wrongstack/core/execution";
39
+ import { AutoApprovePermissionPolicy, ToolCapabilities } from "@wrongstack/core/security";
40
+ import {
41
+ ToolValidationError
42
+ } from "@wrongstack/core/types";
42
43
  import { createDefaultContainer } from "@wrongstack/runtime";
43
44
  var AcpServerConfigError = class extends Error {
44
45
  constructor(message) {
@@ -123,7 +124,8 @@ function makeAcpRead(base, api) {
123
124
  mutating: false,
124
125
  execute: async (input) => {
125
126
  const i = input ?? {};
126
- if (!i.path) throw new ToolValidationError({ message: "read: path is required", field: "path" });
127
+ if (!i.path)
128
+ throw new ToolValidationError({ message: "read: path is required", field: "path" });
127
129
  const content = await api.readTextFile({
128
130
  path: i.path,
129
131
  ...typeof i.offset === "number" ? { line: i.offset } : {},
@@ -142,7 +144,8 @@ function makeAcpWrite(base, api) {
142
144
  ...base,
143
145
  execute: async (input) => {
144
146
  const i = input ?? {};
145
- if (!i.path) throw new ToolValidationError({ message: "write: path is required", field: "path" });
147
+ if (!i.path)
148
+ throw new ToolValidationError({ message: "write: path is required", field: "path" });
146
149
  await api.writeTextFile({ path: i.path, content: i.content ?? "" });
147
150
  return { path: i.path, ok: true };
148
151
  }
@@ -153,8 +156,13 @@ function makeAcpEdit(base, api) {
153
156
  ...base,
154
157
  execute: async (input) => {
155
158
  const i = input ?? {};
156
- if (!i.path) throw new ToolValidationError({ message: "edit: path is required", field: "path" });
157
- if (!i.old_string) throw new ToolValidationError({ message: "edit: old_string is required", field: "old_string" });
159
+ if (!i.path)
160
+ throw new ToolValidationError({ message: "edit: path is required", field: "path" });
161
+ if (!i.old_string)
162
+ throw new ToolValidationError({
163
+ message: "edit: old_string is required",
164
+ field: "old_string"
165
+ });
158
166
  const before = await api.readTextFile({ path: i.path });
159
167
  const occurrences = before.split(i.old_string).length - 1;
160
168
  if (occurrences === 0) {
@@ -182,7 +190,8 @@ function makeAcpBash(base, api) {
182
190
  ...base,
183
191
  execute: async (input) => {
184
192
  const i = input ?? {};
185
- if (!i.command) throw new ToolValidationError({ message: "bash: command is required", field: "command" });
193
+ if (!i.command)
194
+ throw new ToolValidationError({ message: "bash: command is required", field: "command" });
186
195
  const { output, exitCode } = await api.runTerminal({
187
196
  command: "sh",
188
197
  args: ["-c", i.command],
@@ -219,8 +228,7 @@ function buildAcpServerAgentFactory(deps, options = {}) {
219
228
  const { provider, providerRegistry } = await bootProvider();
220
229
  const events = new EventBus();
221
230
  const source = deps.toolRegistry ?? new ToolRegistry();
222
- const tools = new ToolRegistry();
223
- for (const t of source.list()) tools.register(t);
231
+ const tools = source.clone();
224
232
  if (api) {
225
233
  wireClientBackedTools(tools, api);
226
234
  }
@@ -242,7 +250,7 @@ function buildAcpServerAgentFactory(deps, options = {}) {
242
250
  projectRoot: cwd,
243
251
  allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? false,
244
252
  model: config.model,
245
- tools: [...tools.list()],
253
+ tools: [...tools.listForProvider()],
246
254
  agentId: "acp-server",
247
255
  agentName: "wrongstack-acp"
248
256
  });
@@ -481,7 +489,12 @@ async function runACPWebSocketServer(deps, port) {
481
489
  return;
482
490
  }
483
491
  transport.receive(msg);
484
- void handler.handleMessage(msg);
492
+ void handler.handleMessage(msg).catch((err) => {
493
+ deps.renderer.writeWarning(
494
+ `ACP: failed to handle message: ${err instanceof Error ? err.message : String(err)}
495
+ `
496
+ );
497
+ });
485
498
  });
486
499
  const teardown = () => {
487
500
  handler.close();
@@ -499,6 +512,12 @@ async function runACPWebSocketServer(deps, port) {
499
512
  await new Promise((resolve) => {
500
513
  const shutdown = () => {
501
514
  deps.renderer.writeWarning("\nShutting down ACP WebSocket server...");
515
+ for (const client of wss.clients) {
516
+ try {
517
+ client.terminate();
518
+ } catch {
519
+ }
520
+ }
502
521
  wss.close();
503
522
  resolve();
504
523
  };
@@ -889,4 +908,4 @@ ${renderAcpBenchText(result)}
889
908
  export {
890
909
  acpCmd
891
910
  };
892
- //# sourceMappingURL=acp-S4LZQB66.js.map
911
+ //# sourceMappingURL=acp-DATHHPNT.js.map
@@ -13,9 +13,9 @@
13
13
  * belong in the interactive host. The ACP server is a headless single-turn
14
14
  * surface; a future PR can layer richer session behaviour if needed.
15
15
  */
16
+ import type { RunTurnApi } from '@wrongstack/acp/agent';
16
17
  import { Agent } from '@wrongstack/core/agent';
17
18
  import { type Logger, type PermissionDecision, type PermissionPolicy, type Tool } from '@wrongstack/core/types';
18
- import type { RunTurnApi } from '@wrongstack/acp/agent';
19
19
  import type { SubcommandDeps } from './subcommands/index.js';
20
20
  /**
21
21
  * Error thrown when the server cannot start because no model provider is
@@ -1,9 +1,13 @@
1
+ import {
2
+ restoreFlags
3
+ } from "./chunk-CSAPOCBP.js";
1
4
  import "./chunk-7OCVIDC7.js";
2
5
 
3
6
  // src/subcommands/handlers/audit.ts
4
7
  import { color, resolveWstackPaths } from "@wrongstack/core/utils";
5
8
  import { ToolAuditLog } from "@wrongstack/core/storage";
6
9
  var auditCmd = async (args, deps) => {
10
+ args = restoreFlags(args, deps, ["list", "l"]);
7
11
  const wpaths = resolveWstackPaths({
8
12
  projectRoot: deps.projectRoot,
9
13
  userHome: deps.userHome
@@ -99,4 +103,4 @@ async function listAudits(log, dir, deps) {
99
103
  export {
100
104
  auditCmd
101
105
  };
102
- //# sourceMappingURL=audit-2ZSFGNIF.js.map
106
+ //# sourceMappingURL=audit-QRCLEHHW.js.map
@@ -13,12 +13,12 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-L5CE52XW.js";
16
+ } from "./chunk-O7XNGCRT.js";
17
17
  import {
18
18
  parseAuthFlags
19
- } from "./chunk-ZNZJ34RF.js";
20
- import "./chunk-66T43Q4Y.js";
21
- import "./chunk-GO3TJICK.js";
19
+ } from "./chunk-MHF2IJDE.js";
20
+ import "./chunk-ETOEGL3V.js";
21
+ import "./chunk-PYTFS4MC.js";
22
22
  import {
23
23
  loadConfigProviders,
24
24
  maskedKey,
@@ -27,14 +27,17 @@ import {
27
27
  nowIso,
28
28
  writeKeysBack
29
29
  } from "./chunk-PJHET45A.js";
30
- import "./chunk-YWUPHRGB.js";
30
+ import {
31
+ restoreFlags
32
+ } from "./chunk-CSAPOCBP.js";
33
+ import "./chunk-263G3FMB.js";
31
34
  import {
32
35
  activeProfileConfigPath
33
- } from "./chunk-DPJZQCVP.js";
36
+ } from "./chunk-YMXXOOFN.js";
34
37
  import "./chunk-7OCVIDC7.js";
35
38
 
36
39
  // src/subcommands/handlers/auth.ts
37
- import { color as color3 } from "@wrongstack/core/utils";
40
+ import { color as color4 } from "@wrongstack/core/utils";
38
41
 
39
42
  // src/auth-menu/direct.ts
40
43
  async function runAuthDirect(deps, opts) {
@@ -311,8 +314,94 @@ ${color2.amber("?")} Pick: `)).trim().toLowerCase();
311
314
  }
312
315
  }
313
316
 
317
+ // src/subcommands/handlers/auth-local-help.ts
318
+ import { color as color3 } from "@wrongstack/core/utils";
319
+ var LOCAL_AUTH_FLAGS = [
320
+ {
321
+ flag: "--name <ollama|vllm|lmstudio>",
322
+ description: "Pick a preset (skip the interactive picker). Defaults to the first run; set this for scripting."
323
+ },
324
+ {
325
+ flag: "--base-url <url>",
326
+ description: "Override the preset default (e.g. `http://gpu-host:8000/v1` for a remote vLLM). Empty falls back to the preset."
327
+ },
328
+ {
329
+ flag: "--no-key / --skip-key",
330
+ description: "Skip the API-key prompt even for presets that accept one. Use for non-TTY scripted setups."
331
+ },
332
+ {
333
+ flag: "--no-probe / --skip-probe",
334
+ description: "Skip the health probe. Use when the local server is not running yet but the config must be persisted."
335
+ },
336
+ {
337
+ flag: "--probe-only",
338
+ description: "Run the probe and report, do not save. Use to re-check a previously-saved provider."
339
+ },
340
+ {
341
+ flag: "--model <spec> / -m <spec>",
342
+ description: "Pre-populate the saved `models` allowlist. `--model first` / `<N>` consume the probe; `<csv>` is a literal list; bare `--model` clears the allowlist."
343
+ },
344
+ {
345
+ flag: "--audit [target]",
346
+ description: "Emit JSONL audit events for the save lifecycle. Bare / `stdout` \u2192 stdout; `stderr` \u2192 stderr; `<path>` \u2192 file. Default is silent."
347
+ }
348
+ ];
349
+ var LOCAL_FLAG_COLUMN_WIDTH = 38;
350
+ var LOCAL_AUTH_HELP_USAGE = "wstack auth local [--name <id>] [--base-url <url>] [--no-key] [--no-probe|--probe-only] [--model <spec>] [--audit [target]]";
351
+ function wantsLocalHelp(args) {
352
+ return args.includes("--help") || args.includes("-h");
353
+ }
354
+ function renderAuthLocalHelpToString() {
355
+ const lines = [
356
+ color3.bold("wstack auth local \u2014 quick-add Ollama / vLLM / LM Studio"),
357
+ color3.dim(" Pre-fills the base URL, runs a health probe (`GET /v1/models`),"),
358
+ color3.dim(" and persists the allowlist so you can `wstack --provider <id>`"),
359
+ color3.dim(" right away. Use `--no-probe` to skip when the local server is not"),
360
+ color3.dim(" running yet; use `--audit <file>` to capture the save lifecycle."),
361
+ "",
362
+ color3.bold("Usage"),
363
+ ` ${LOCAL_AUTH_HELP_USAGE}`,
364
+ "",
365
+ color3.bold("Flags")
366
+ ];
367
+ for (const { flag, description } of LOCAL_AUTH_FLAGS) {
368
+ const padded = flag.padEnd(LOCAL_FLAG_COLUMN_WIDTH, " ");
369
+ lines.push(` ${color3.cyan(padded)}${description}`);
370
+ }
371
+ lines.push("");
372
+ lines.push(color3.dim(" See also: `wstack --help` for the top-level command list."));
373
+ lines.push(
374
+ color3.dim(
375
+ " Examples: `wstack auth local --name ollama --no-probe --model 'llama3.1:8b'`"
376
+ )
377
+ );
378
+ lines.push(
379
+ color3.dim(
380
+ " `wstack auth local --name ollama --audit /var/log/wstack-auth.jsonl`"
381
+ )
382
+ );
383
+ return lines.join("\n") + "\n";
384
+ }
385
+
314
386
  // src/subcommands/handlers/auth.ts
315
387
  var authCmd = async (args, deps) => {
388
+ args = restoreFlags(args, deps, [
389
+ "label",
390
+ "family",
391
+ "base-url",
392
+ "env",
393
+ // `auth local` flags — see the `local` branch below.
394
+ "name",
395
+ "no-key",
396
+ "skip-key",
397
+ "no-probe",
398
+ "skip-probe",
399
+ "probe-only",
400
+ "model",
401
+ "m",
402
+ "help",
403
+ "h"
404
+ ]);
316
405
  const flags = parseAuthFlags(args);
317
406
  const profileConfigPath = activeProfileConfigPath(deps.paths, deps.config);
318
407
  const menuDeps = {
@@ -329,6 +418,30 @@ var authCmd = async (args, deps) => {
329
418
  if (first === "list" || first === "ls") {
330
419
  return runAuthList(menuDeps);
331
420
  }
421
+ if (first === "local") {
422
+ if (wantsLocalHelp(args)) {
423
+ deps.renderer.write(`${renderAuthLocalHelpToString()}
424
+ `);
425
+ return 0;
426
+ }
427
+ const value = (name) => {
428
+ const i = args.indexOf(`--${name}`);
429
+ return i >= 0 ? args[i + 1] : void 0;
430
+ };
431
+ const has = (...names) => names.some((n) => args.includes(`--${n}`));
432
+ const modelIdx = args.indexOf("--model") >= 0 ? args.indexOf("--model") : args.indexOf("-m");
433
+ return runAuthLocal(menuDeps, {
434
+ name: flags.positional[1] ?? value("name"),
435
+ baseUrl: flags.baseUrl ?? value("base-url"),
436
+ label: flags.label,
437
+ skipKey: has("no-key", "skip-key"),
438
+ noProbe: has("no-probe", "skip-probe"),
439
+ probeOnly: has("probe-only"),
440
+ // Bare `--model` (no value, or followed by another flag) clears the
441
+ // allowlist, which is why an empty string is meaningful here.
442
+ ...modelIdx >= 0 ? { models: args[modelIdx + 1]?.startsWith("-") ? "" : args[modelIdx + 1] ?? "" } : {}
443
+ });
444
+ }
332
445
  if (first === "status") {
333
446
  const pid = flags.positional[1];
334
447
  if (!pid) {
@@ -352,7 +465,7 @@ var authCmd = async (args, deps) => {
352
465
  if (kind) return runOAuthLoginKind(menuDeps, kind);
353
466
  deps.renderer.writeError("OAuth login is only supported for ChatGPT, Claude, and GitHub Copilot.");
354
467
  deps.renderer.write(
355
- color3.dim(" Sign in with ChatGPT: ") + color3.bold("wstack auth login chatgpt") + "\n" + color3.dim(" Sign in with Claude: ") + color3.bold("wstack auth login claude") + "\n" + color3.dim(" Sign in with Copilot: ") + color3.bold("wstack auth login copilot") + "\n" + color3.dim(" For an API key instead: ") + color3.bold(`wstack auth ${pid}`) + "\n"
468
+ color4.dim(" Sign in with ChatGPT: ") + color4.bold("wstack auth login chatgpt") + "\n" + color4.dim(" Sign in with Claude: ") + color4.bold("wstack auth login claude") + "\n" + color4.dim(" Sign in with Copilot: ") + color4.bold("wstack auth login copilot") + "\n" + color4.dim(" For an API key instead: ") + color4.bold(`wstack auth ${pid}`) + "\n"
356
469
  );
357
470
  return 1;
358
471
  }
@@ -375,14 +488,14 @@ async function runAuthList(deps) {
375
488
  const ids = Object.keys(providers).sort();
376
489
  if (ids.length === 0) {
377
490
  deps.renderer.write(
378
- `${color3.dim("No providers configured.")}
379
- ${color3.dim("Run")} ${color3.bold("wstack auth")} ${color3.dim("to add one.")}
491
+ `${color4.dim("No providers configured.")}
492
+ ${color4.dim("Run")} ${color4.bold("wstack auth")} ${color4.dim("to add one.")}
380
493
  `
381
494
  );
382
495
  return 0;
383
496
  }
384
497
  deps.renderer.write(`
385
- ${color3.bold("Saved providers")} ${color3.dim(`(${ids.length})`)}
498
+ ${color4.bold("Saved providers")} ${color4.dim(`(${ids.length})`)}
386
499
 
387
500
  `);
388
501
  for (const id of ids) {
@@ -390,34 +503,34 @@ ${color3.bold("Saved providers")} ${color3.dim(`(${ids.length})`)}
390
503
  if (!cfg) continue;
391
504
  const keys = normalizeKeys(cfg);
392
505
  const active = cfg.activeKey ?? keys[0]?.label;
393
- const famTag = cfg.family ? `${cfg.family}` : color3.amber("no-family");
394
- const aliasHint = cfg.type && cfg.type !== id ? color3.dim(` (\u2192 ${cfg.type})`) : "";
395
- const modelHint = cfg.models && cfg.models.length > 0 ? color3.dim(` [${cfg.models.length} models]`) : "";
396
- deps.renderer.write(` ${color3.bold(id)}${aliasHint}
506
+ const famTag = cfg.family ? `${cfg.family}` : color4.amber("no-family");
507
+ const aliasHint = cfg.type && cfg.type !== id ? color4.dim(` (\u2192 ${cfg.type})`) : "";
508
+ const modelHint = cfg.models && cfg.models.length > 0 ? color4.dim(` [${cfg.models.length} models]`) : "";
509
+ deps.renderer.write(` ${color4.bold(id)}${aliasHint}
397
510
  `);
398
511
  deps.renderer.write(
399
- ` family: ${famTag} baseUrl: ${cfg.baseUrl ?? color3.dim("unset")}${modelHint}
512
+ ` family: ${famTag} baseUrl: ${cfg.baseUrl ?? color4.dim("unset")}${modelHint}
400
513
  `
401
514
  );
402
515
  if (keys.length === 0) {
403
- deps.renderer.write(` ${color3.amber("no keys")}
516
+ deps.renderer.write(` ${color4.amber("no keys")}
404
517
  `);
405
518
  } else {
406
519
  deps.renderer.write(
407
- ` ${color3.dim(`${keys.length} key${keys.length === 1 ? "" : "s"}:`)}
520
+ ` ${color4.dim(`${keys.length} key${keys.length === 1 ? "" : "s"}:`)}
408
521
  `
409
522
  );
410
523
  for (const k of keys) {
411
- const marker = k.label === active ? color3.green("\u25CF") : color3.dim("\u25CB");
524
+ const marker = k.label === active ? color4.green("\u25CF") : color4.dim("\u25CB");
412
525
  deps.renderer.write(
413
- ` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color3.dim(k.createdAt)}
526
+ ` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color4.dim(k.createdAt)}
414
527
  `
415
528
  );
416
529
  }
417
530
  }
418
531
  deps.renderer.write("\n");
419
532
  }
420
- deps.renderer.write(color3.dim(`Manage: wstack auth Add key: wstack auth <provider>
533
+ deps.renderer.write(color4.dim(`Manage: wstack auth Add key: wstack auth <provider>
421
534
  `));
422
535
  return 0;
423
536
  }
@@ -433,7 +546,7 @@ async function runAuthStatus(deps, providerId) {
433
546
  if (!cfg) {
434
547
  deps.renderer.writeError(`Provider "${providerId}" not found in config.`);
435
548
  deps.renderer.write(
436
- color3.dim(`Run ${color3.bold("wstack auth list")} to see saved providers.
549
+ color4.dim(`Run ${color4.bold("wstack auth list")} to see saved providers.
437
550
  `)
438
551
  );
439
552
  return 1;
@@ -442,32 +555,32 @@ async function runAuthStatus(deps, providerId) {
442
555
  const active = cfg.activeKey ?? keys[0]?.label;
443
556
  const lines = [
444
557
  `
445
- ${color3.bold(providerId)} ${cfg.family ? color3.dim(`[${cfg.family}]`) : color3.amber("[no family]")}`,
558
+ ${color4.bold(providerId)} ${cfg.family ? color4.dim(`[${cfg.family}]`) : color4.amber("[no family]")}`,
446
559
  "",
447
- ` type: ${color3.cyan(cfg.type ?? providerId)}`,
448
- ` family: ${cfg.family ? color3.cyan(cfg.family) : color3.dim("unset")}`,
449
- ` baseUrl: ${cfg.baseUrl ? color3.cyan(cfg.baseUrl) : color3.dim("unset")}`
560
+ ` type: ${color4.cyan(cfg.type ?? providerId)}`,
561
+ ` family: ${cfg.family ? color4.cyan(cfg.family) : color4.dim("unset")}`,
562
+ ` baseUrl: ${cfg.baseUrl ? color4.cyan(cfg.baseUrl) : color4.dim("unset")}`
450
563
  ];
451
564
  if (cfg.models?.length) {
452
- lines.push(` models: ${color3.cyan(cfg.models.join(", "))}`);
565
+ lines.push(` models: ${color4.cyan(cfg.models.join(", "))}`);
453
566
  }
454
567
  if (cfg.envVars?.length) {
455
- lines.push(` envVars: ${color3.cyan(cfg.envVars.join(", "))}`);
568
+ lines.push(` envVars: ${color4.cyan(cfg.envVars.join(", "))}`);
456
569
  }
457
570
  lines.push("");
458
571
  if (keys.length === 0) {
459
- lines.push(color3.amber(" (no keys saved)"));
572
+ lines.push(color4.amber(" (no keys saved)"));
460
573
  } else {
461
- lines.push(` ${color3.dim("Keys:")}`);
574
+ lines.push(` ${color4.dim("Keys:")}`);
462
575
  for (const k of keys) {
463
- const marker = k.label === active ? color3.green("\u25CF") : color3.dim("\u25CB");
576
+ const marker = k.label === active ? color4.green("\u25CF") : color4.dim("\u25CB");
464
577
  lines.push(
465
- ` ${marker} ${color3.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color3.dim(k.createdAt)}`
578
+ ` ${marker} ${color4.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color4.dim(k.createdAt)}`
466
579
  );
467
580
  }
468
581
  }
469
582
  lines.push("");
470
- lines.push(color3.dim(`Manage: wstack auth \u2192 pick ${providerId}`));
583
+ lines.push(color4.dim(`Manage: wstack auth \u2192 pick ${providerId}`));
471
584
  deps.renderer.write(lines.join("\n") + "\n");
472
585
  return 0;
473
586
  }
@@ -478,19 +591,19 @@ async function runAuthRemove(deps, providerId) {
478
591
  return 1;
479
592
  }
480
593
  deps.renderer.write(
481
- `${color3.amber("!")} This will remove "${providerId}" and all its saved keys.
594
+ `${color4.amber("!")} This will remove "${providerId}" and all its saved keys.
482
595
  `
483
596
  );
484
- const answer = (await deps.reader.readLine(` ${color3.amber("?")} Confirm removal? ${color3.dim("[y/N]")} `)).trim().toLowerCase();
597
+ const answer = (await deps.reader.readLine(` ${color4.amber("?")} Confirm removal? ${color4.dim("[y/N]")} `)).trim().toLowerCase();
485
598
  if (answer !== "y" && answer !== "yes") {
486
- deps.renderer.write(color3.dim("Cancelled.\n"));
599
+ deps.renderer.write(color4.dim("Cancelled.\n"));
487
600
  return 0;
488
601
  }
489
602
  try {
490
603
  await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
491
604
  delete all[providerId];
492
605
  });
493
- deps.renderer.write(` ${color3.green("\u2713")} Removed ${color3.bold(providerId)}.
606
+ deps.renderer.write(` ${color4.green("\u2713")} Removed ${color4.bold(providerId)}.
494
607
  `);
495
608
  return 0;
496
609
  } catch (err) {
@@ -501,4 +614,4 @@ async function runAuthRemove(deps, providerId) {
501
614
  export {
502
615
  authCmd
503
616
  };
504
- //# sourceMappingURL=auth-G5YKKSRH.js.map
617
+ //# sourceMappingURL=auth-JASC4K3F.js.map
@@ -1,40 +1,29 @@
1
1
  /**
2
- * Shared loopback OAuth server HTTP callback listener for OAuth flows.
2
+ * Terminal-side adapter for the OAuth loopback callback server.
3
3
  *
4
- * Both the Codex ("Sign in with ChatGPT") and Anthropic ("Sign in with Claude")
5
- * OAuth flows start a local HTTP server on a loopback port to receive the
6
- * authorization code redirect from the provider's browser flow. Before this
7
- * module existed, each flow duplicated:
4
+ * The server itself HTTP listener, port fallback, `state` validation, the
5
+ * static callback page and its CSP/anti-framing headers, abort handling lives
6
+ * in `@wrongstack/providers/oauth`. This file used to carry a second, complete
7
+ * implementation of all of it.
8
8
  *
9
- * - `escapeHtml` / `callbackHtml` render success/error HTML pages
10
- * - `openBrowser` best-effort platform browser opener
11
- * - `startLoopbackServer` HTTP server with port fallback, abort handling,
12
- * state validation, and callback URL parsing
9
+ * The copies were not identical, and the difference mattered: this one
10
+ * registered its abort listener with `{ once: true }` and never removed it, so
11
+ * every completed login leaked a listener on the caller's long-lived
12
+ * AbortSignal (the TUI hands the same signal to each attempt). The providers
13
+ * copy had the `detachAbort` cleanup. Neither file had any way to notice the
14
+ * other had drifted.
13
15
  *
14
- * Usage:
15
- * ```ts
16
- * import { startLoopbackServer, callbackHtml, openBrowser } from './loopback-server.js';
16
+ * What legitimately stays here is {@link openBrowser}: spawning the platform
17
+ * browser is terminal-only behaviour, and the headless WebUI flows deliberately
18
+ * hand the authorize URL to the client instead of opening anything.
17
19
  *
18
- * const server = await startLoopbackServer(state, [1455, 1457], signal);
19
- * const code = await server.waitForCode();
20
- * ```
20
+ * The positional signature below is preserved so the three CLI OAuth flows and
21
+ * their tests keep working unchanged.
21
22
  *
22
23
  * @module auth-menu/loopback-server
23
24
  */
24
- /**
25
- * Render a simple dark-themed HTML response for the browser callback page.
26
- * Shared by all loopback-based OAuth flows so the success/error page styling
27
- * is consistent and the HTML generation isn't duplicated.
28
- */
29
- export declare function callbackHtml(ok: boolean, message: string): string;
30
- /**
31
- * Open a URL in the user's default browser. Best-effort — never throws.
32
- *
33
- * - Windows: `cmd /c start "" <url>`
34
- * - macOS: `open <url>`
35
- * - Linux: `xdg-open <url>`
36
- */
37
- export declare function openBrowser(url: string): void;
25
+ import { type LoopbackServer } from '@wrongstack/providers/oauth';
26
+ export { callbackHtml, type LoopbackServer } from '@wrongstack/providers/oauth';
38
27
  /** Result of waiting for the OAuth callback. */
39
28
  export interface LoopbackCallback {
40
29
  /** The authorization code from the provider. */
@@ -42,35 +31,32 @@ export interface LoopbackCallback {
42
31
  /** The OAuth state parameter from the callback (validated against expected). */
43
32
  state: string;
44
33
  }
45
- export interface LoopbackServer {
46
- /**
47
- * Wait for the browser to redirect back with the authorization code.
48
- * Resolves with the code and state, or `null` if cancelled/failed.
49
- */
50
- waitForCode(): Promise<LoopbackCallback | null>;
51
- /** Close the HTTP server and release the port. */
52
- close(): void;
53
- /** True when the server bound to a registered callback port. */
54
- readonly bound: boolean;
55
- /** The actual port the server is listening on (matches the authorize URL redirect_uri). */
56
- readonly port: number;
57
- }
58
34
  /**
59
- * Start a loopback HTTP server for OAuth authorization code callback.
60
- *
61
- * The server listens on the first available port from `ports`. If all ports
62
- * are busy, `bound` is `false` and the caller should fall back to a manual
63
- * paste prompt.
35
+ * Open a URL in the user's default browser. Best-effort — never throws.
64
36
  *
65
- * When `signal` aborts (user pressed Ctrl+C or TUI Esc), the pending
66
- * `waitForCode()` promise resolves to `null` and the server is torn down
67
- * so the login flow can return promptly.
37
+ * - Windows: `cmd /c start "" <url>`
38
+ * - macOS: `open <url>`
39
+ * - Linux: `xdg-open <url>`
68
40
  *
69
- * @param expectedState - The OAuth `state` value to validate against the callback
70
- * @param ports - Ordered list of TCP ports to try (first available wins)
71
- * @param signal - Optional external cancellation signal
72
- * @param callbackPath - The URL path the provider redirects to (default `/auth/callback`)
73
- * @param redirectHost - The hostname to listen on (default `127.0.0.1`)
41
+ * Not `detached`: the CLI flows wait on the loopback callback in the
42
+ * foreground, so the opener is a short-lived handoff. (The WebUI server's
43
+ * `openBrowser` is deliberately different it detaches and registers the pid
44
+ * as protected because it launches a browser the session must outlive.)
45
+ */
46
+ export declare function openBrowser(url: string): void;
47
+ /**
48
+ * Start a loopback HTTP server for the OAuth authorization-code callback.
49
+ *
50
+ * Binds the first available port from `ports`. If all are busy, `bound` is
51
+ * `false` and the caller falls back to a manual paste prompt. When `signal`
52
+ * aborts (Ctrl+C, TUI Esc), the pending `waitForCode()` resolves to `null` and
53
+ * the server is torn down.
54
+ *
55
+ * @param expectedState - the OAuth `state` to validate the callback against
56
+ * @param ports - ordered ports to try; first available wins
57
+ * @param signal - optional external cancellation
58
+ * @param callbackPath - path the provider redirects to (default `/auth/callback`)
59
+ * @param redirectHost - hostname to listen on (default `127.0.0.1`)
74
60
  */
75
61
  export declare function startLoopbackServer(expectedState: string, ports: readonly number[], signal?: AbortSignal, callbackPath?: string, redirectHost?: string): Promise<LoopbackServer>;
76
62
  //# sourceMappingURL=loopback-server.d.ts.map