@wrongstack/cli 1.0.9 → 1.0.11

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 (30) hide show
  1. package/dist/{acp-CXLANMEZ.js → acp-IJ55NBMJ.js} +5 -3
  2. package/dist/acp-mcp-servers.d.ts +2 -0
  3. package/dist/arg-parser.d.ts +6 -0
  4. package/dist/{auth-3K6EN5JP.js → auth-YXK3EDUL.js} +9 -3
  5. package/dist/{chunk-EK2P53GL.js → chunk-C3Z4N6A6.js} +23 -3
  6. package/dist/{chunk-7K3IJGWC.js → chunk-OJR7K3OI.js} +8 -8
  7. package/dist/{chunk-SXJTVNWF.js → chunk-OWNC7LMD.js} +2 -2
  8. package/dist/{chunk-FSZ5QU3M.js → chunk-RDHFXOJM.js} +153 -69
  9. package/dist/{chunk-5VPDKQDH.js → chunk-TNZBQG45.js} +3 -3
  10. package/dist/{chunk-7FPN3QTH.js → chunk-YRC3I3LE.js} +172 -15
  11. package/dist/{cli-context-6GMXUOXD.js → cli-context-5IF7ZYEY.js} +7 -7
  12. package/dist/{cli-main-4E4YMCNS.js → cli-main-WC5JNIWY.js} +67 -48
  13. package/dist/{diag-doctor-PCER6KHH.js → diag-doctor-ANPRUYFM.js} +13 -3
  14. package/dist/{execution-4RXQLDAF.js → execution-EXDRBS4F.js} +2 -2
  15. package/dist/{hq-DQTUE7RC.js → hq-NO2NWGU4.js} +153 -5
  16. package/dist/hq-server/auth-state.d.ts +11 -0
  17. package/dist/hq-server/mailbox-gateway-health.d.ts +67 -0
  18. package/dist/hq-server/mailbox-gateway-manager.d.ts +38 -0
  19. package/dist/hq-server/routes/system-handlers.d.ts +27 -0
  20. package/dist/hq-server/routes.d.ts +11 -0
  21. package/dist/hq-server/snapshot.d.ts +14 -1
  22. package/dist/hq-server/upgrade-handler.d.ts +7 -0
  23. package/dist/{hq-server-JJFGFWI5.js → hq-server-KE5YDNGG.js} +2 -2
  24. package/dist/index.js +4 -4
  25. package/dist/{mcp-FX7TKPTV.js → mcp-DC5WSBOO.js} +28 -10
  26. package/dist/mcp-serve.d.ts +9 -0
  27. package/dist/services/mcp-management.d.ts +0 -6
  28. package/dist/{short-circuit-flags-LHUNLCZY.js → short-circuit-flags-72E77TJI.js} +3 -3
  29. package/dist/{subcommands-UXWY3OPC.js → subcommands-EJI7DBQS.js} +2 -2
  30. package/package.json +28 -28
@@ -6,7 +6,7 @@ import {
6
6
  removeSuggestions,
7
7
  runGit,
8
8
  setSuggestions
9
- } from "./chunk-SXJTVNWF.js";
9
+ } from "./chunk-OWNC7LMD.js";
10
10
  import {
11
11
  setupPlugins
12
12
  } from "./chunk-XJ7K67PP.js";
@@ -102,7 +102,7 @@ import {
102
102
  runAuthLocal,
103
103
  runProviderAuthLogin,
104
104
  validateFamily
105
- } from "./chunk-FSZ5QU3M.js";
105
+ } from "./chunk-RDHFXOJM.js";
106
106
  import "./chunk-2VMJ3E5Z.js";
107
107
  import {
108
108
  appendHistory
@@ -116,7 +116,7 @@ import {
116
116
  } from "./chunk-YMXXOOFN.js";
117
117
  import {
118
118
  parseSpawnFlags
119
- } from "./chunk-EK2P53GL.js";
119
+ } from "./chunk-C3Z4N6A6.js";
120
120
  import "./chunk-SIGUWXAA.js";
121
121
  import "./chunk-DAKVBMD4.js";
122
122
  import {
@@ -6231,7 +6231,7 @@ async function runCliExecution(params) {
6231
6231
  governanceHandle,
6232
6232
  setConfig
6233
6233
  } = params;
6234
- const { execute } = await import("./execution-4RXQLDAF.js");
6234
+ const { execute } = await import("./execution-EXDRBS4F.js");
6235
6235
  return execute(
6236
6236
  toExecuteDeps({
6237
6237
  core: {
@@ -8419,6 +8419,7 @@ ${diff}`;
8419
8419
  }
8420
8420
 
8421
8421
  // src/services/mcp-management.ts
8422
+ import { resolveMcpServerConfig } from "@wrongstack/core/infrastructure";
8422
8423
  import {
8423
8424
  color as color7,
8424
8425
  expectDefined as expectDefined4,
@@ -8534,7 +8535,7 @@ async function runAdd(name, enable, configured, configPath, mcpRegistry, all) {
8534
8535
  }
8535
8536
  try {
8536
8537
  if (mcpRegistry.list().some((server) => server.name === name)) {
8537
- await mcpRegistry.restart(name);
8538
+ await mcpRegistry.restart(name, { ...nextCfg, name });
8538
8539
  } else {
8539
8540
  await mcpRegistry.start(nextCfg);
8540
8541
  }
@@ -8572,24 +8573,21 @@ async function runRemove(name, configured, configPath, mcpRegistry) {
8572
8573
  async function runEnable(name, configured, configPath, mcpRegistry) {
8573
8574
  const cfg = configured[name];
8574
8575
  if (!cfg) return `Server "${name}" is not in config. Run \`/mcp add ${name} --enable\` first.`;
8576
+ const resolved = resolveMcpServerConfig(name, cfg);
8577
+ if (!resolved) return `Server "${name}" has no transport and matches no preset.`;
8578
+ const startCfg = { ...resolved, enabled: true };
8579
+ const registered = mcpRegistry.list().some((server) => server.name === name);
8575
8580
  if (cfg.enabled !== false) {
8576
- try {
8577
- await mcpRegistry.restart(name);
8578
- return `${color7.green("\u25CF")} "${name}" is already enabled and running.`;
8579
- } catch {
8580
- await mcpRegistry.start({ ...cfg, enabled: true });
8581
- return `${color7.green("Enabled")} "${name}" and started.`;
8582
- }
8581
+ if (registered) await mcpRegistry.restart(name, startCfg);
8582
+ else await mcpRegistry.start(startCfg);
8583
+ return `${color7.green("\u25CF")} "${name}" is already enabled and running.`;
8583
8584
  }
8584
8585
  await updateJsonObjectFile(configPath, (full) => {
8585
8586
  const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
8586
8587
  setJsonPath(full, ["mcpServers", name], { ...cfg, ...current[name], enabled: true });
8587
8588
  });
8588
- try {
8589
- await mcpRegistry.restart(name);
8590
- } catch {
8591
- await mcpRegistry.start({ ...cfg, enabled: true });
8592
- }
8589
+ if (registered) await mcpRegistry.restart(name, startCfg);
8590
+ else await mcpRegistry.start(startCfg);
8593
8591
  return `${color7.green("Enabled")} "${name}" and started.`;
8594
8592
  }
8595
8593
  async function runDisable(name, configured, configPath, mcpRegistry) {
@@ -8642,6 +8640,8 @@ function stateBadge(state) {
8642
8640
  return color7.dim("\u25CB disconnected");
8643
8641
  case "failed":
8644
8642
  return color7.red("\u2717 failed");
8643
+ case "dormant":
8644
+ return color7.dim("\u25CC dormant (spawns on first call)");
8645
8645
  default:
8646
8646
  return color7.dim(state);
8647
8647
  }
@@ -8781,7 +8781,7 @@ async function checkUncommittedChanges(projectRoot) {
8781
8781
  stdout += data;
8782
8782
  });
8783
8783
  child.on("error", reject);
8784
- child.on("close", (code) => resolve9({ stdout, code: code ?? 0 }));
8784
+ child.on("close", (code) => resolve9({ stdout, code: code ?? 1 }));
8785
8785
  });
8786
8786
  if (!result.stdout.trim()) return void 0;
8787
8787
  const count = result.stdout.split("\n").filter(Boolean).length;
@@ -17897,7 +17897,6 @@ function parseTaskEvidenceFlags(tokens) {
17897
17897
  }
17898
17898
  if (!inline && value.startsWith("-")) {
17899
17899
  warnings.push(`${key} expects a URL but none was provided`);
17900
- i++;
17901
17900
  continue;
17902
17901
  }
17903
17902
  if (!inline) i++;
@@ -17913,7 +17912,6 @@ function parseTaskEvidenceFlags(tokens) {
17913
17912
  }
17914
17913
  if (!inline && raw.startsWith("-")) {
17915
17914
  warnings.push(`${key} expects <checkId>=<status> but none was provided`);
17916
- i++;
17917
17915
  continue;
17918
17916
  }
17919
17917
  const sep2 = raw.lastIndexOf("=");
@@ -18148,10 +18146,16 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18148
18146
  }
18149
18147
  if (board.lifecycle?.mode === "managed") {
18150
18148
  const stageToColumn = board.lifecycle?.columns ?? {};
18151
- const currentColumnId = board.tasks.find((t) => t.id === taskId)?.columnId;
18149
+ const currentTask = board.tasks.find((t) => t.id === taskId);
18150
+ if (!currentTask) {
18151
+ return { message: color36.red("Task not found") };
18152
+ }
18152
18153
  const currentStage = Object.entries(stageToColumn).find(
18153
- ([, columnId]) => columnId === currentColumnId
18154
+ ([, columnId]) => columnId === currentTask.columnId
18154
18155
  )?.[0];
18156
+ if (currentStage === "done") {
18157
+ return { message: color36.yellow("Task is already in Done \u2014 nothing to do.") };
18158
+ }
18155
18159
  const path35 = currentStage === "backlog" ? ["todo", "running", "review", "done"] : currentStage === "todo" ? ["running", "review", "done"] : currentStage === "running" ? ["review", "done"] : currentStage === "review" ? ["done"] : null;
18156
18160
  if (path35 === null) {
18157
18161
  return {
@@ -18160,10 +18164,6 @@ async function handleTaskSubcommand(opts, projectRoot, args, showHelp) {
18160
18164
  )
18161
18165
  };
18162
18166
  }
18163
- const currentTask = board.tasks.find((t) => t.id === taskId);
18164
- if (!currentTask) {
18165
- return { message: color36.red("Task not found") };
18166
- }
18167
18167
  const preflightIssues = [];
18168
18168
  if (!note?.trim()) {
18169
18169
  return {
@@ -19920,7 +19920,7 @@ async function runAudienceMemory(store, rest) {
19920
19920
  let imported = 0;
19921
19921
  let skipped = 0;
19922
19922
  for (const entry of entries) {
19923
- if (typeof entry.text !== "string" || !entry.text.trim()) {
19923
+ if (!entry || typeof entry !== "object" || typeof entry.text !== "string" || !entry.text.trim()) {
19924
19924
  skipped++;
19925
19925
  continue;
19926
19926
  }
@@ -20234,6 +20234,8 @@ var MEMORY_KINDS = [
20234
20234
  "summary"
20235
20235
  ];
20236
20236
  var MEMORY_SCOPES = ["project", "user", "session", "file", "symbol"];
20237
+ var DECIMAL_RE = /^(?:\d+(?:\.\d*)?|\.\d+)$/;
20238
+ var INTEGER_RE = /^\d+$/;
20237
20239
  var MEMORY_STATUSES = [
20238
20240
  "active",
20239
20241
  "stale",
@@ -20258,7 +20260,7 @@ function parseMemoryFlags(tokens) {
20258
20260
  errors.push(`${name} needs a value between 0 and 1.`);
20259
20261
  return void 0;
20260
20262
  }
20261
- const parsed = Number.parseFloat(value);
20263
+ const parsed = DECIMAL_RE.test(value) ? Number.parseFloat(value) : Number.NaN;
20262
20264
  if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
20263
20265
  errors.push(`${name} must be a number between 0 and 1 (got "${value}").`);
20264
20266
  return void 0;
@@ -20368,7 +20370,7 @@ function parseForFileFlags(tokens) {
20368
20370
  out.errors.push("--line needs a 1-indexed line number.");
20369
20371
  continue;
20370
20372
  }
20371
- const parsed = Number.parseInt(next, 10);
20373
+ const parsed = INTEGER_RE.test(next) ? Number.parseInt(next, 10) : Number.NaN;
20372
20374
  if (!Number.isFinite(parsed) || parsed < 1) {
20373
20375
  out.errors.push(`--line must be a positive integer (got "${next}").`);
20374
20376
  continue;
@@ -20380,7 +20382,7 @@ function parseForFileFlags(tokens) {
20380
20382
  out.errors.push("--limit needs a value between 1 and 200.");
20381
20383
  continue;
20382
20384
  }
20383
- const parsed = Number.parseInt(next, 10);
20385
+ const parsed = INTEGER_RE.test(next) ? Number.parseInt(next, 10) : Number.NaN;
20384
20386
  if (!Number.isFinite(parsed) || parsed < 1 || parsed > 200) {
20385
20387
  out.errors.push(`--limit must be between 1 and 200 (got "${next}").`);
20386
20388
  continue;
@@ -20880,7 +20882,8 @@ async function runStats(opts) {
20880
20882
  "anti_pattern",
20881
20883
  "untyped"
20882
20884
  ];
20883
- for (const t of typeOrder) {
20885
+ const extraTypes = [...byType.keys()].filter((t) => !typeOrder.includes(t)).sort();
20886
+ for (const t of [...typeOrder.slice(0, -1), ...extraTypes, "untyped"]) {
20884
20887
  const count = byType.get(t);
20885
20888
  if (count) {
20886
20889
  const bar2 = "\u2588".repeat(Math.min(count, 20));
@@ -26831,6 +26834,11 @@ import { leaderTierPolicy, listTierIds, resolveTier } from "@wrongstack/core/coo
26831
26834
  import { ConfigError as ConfigError6 } from "@wrongstack/core/types";
26832
26835
  import { atomicWrite as atomicWrite10, color as color54, toErrorMessage as toErrorMessage25 } from "@wrongstack/core/utils";
26833
26836
  var LEADER_MODES = ["off", "propose", "auto"];
26837
+ var DECIMAL_RE2 = /^(?:\d+(?:\.\d*)?|\.\d+)$/;
26838
+ var INTEGER_RE2 = /^\d+$/;
26839
+ function strictNumber(raw, pattern) {
26840
+ return pattern.test(raw) ? Number(raw) : Number.NaN;
26841
+ }
26834
26842
  async function patchGlobalConfig3(globalConfigPath, mutate) {
26835
26843
  let raw = "{}";
26836
26844
  let fileExists2 = true;
@@ -27019,15 +27027,15 @@ function buildTierCommand(opts) {
27019
27027
  message: `${color54.amber("Usage:")} /tier budget <tier> <maxCostUsd> [maxIterations] [maxToolCalls]`
27020
27028
  };
27021
27029
  }
27022
- const usd = Number.parseFloat(parts[2] ?? "");
27030
+ const usd = strictNumber(parts[2] ?? "", DECIMAL_RE2);
27023
27031
  if (!Number.isFinite(usd) || usd < 0) {
27024
27032
  return { message: `${color54.red("Invalid")} maxCostUsd: "${parts[2] ?? ""}"` };
27025
27033
  }
27026
- const iters = parts[3] !== void 0 ? Number.parseInt(parts[3], 10) : void 0;
27034
+ const iters = parts[3] !== void 0 ? strictNumber(parts[3], INTEGER_RE2) : void 0;
27027
27035
  if (iters !== void 0 && (!Number.isFinite(iters) || iters < 0)) {
27028
27036
  return { message: `${color54.red("Invalid")} maxIterations: "${parts[3]}"` };
27029
27037
  }
27030
- const tools = parts[4] !== void 0 ? Number.parseInt(parts[4], 10) : void 0;
27038
+ const tools = parts[4] !== void 0 ? strictNumber(parts[4], INTEGER_RE2) : void 0;
27031
27039
  if (tools !== void 0 && (!Number.isFinite(tools) || tools < 0)) {
27032
27040
  return { message: `${color54.red("Invalid")} maxToolCalls: "${parts[4]}"` };
27033
27041
  }
@@ -27057,6 +27065,12 @@ function buildTierCommand(opts) {
27057
27065
  for (const [key, value] of Object.entries(routing)) {
27058
27066
  if (value === tier) delete routing[key];
27059
27067
  }
27068
+ if (tiers["default"] === tier) delete tiers["default"];
27069
+ const leader = tiers["leader"];
27070
+ if (leader && typeof leader === "object") {
27071
+ const leaderRecord = leader;
27072
+ if (leaderRecord["maxTier"] === tier) delete leaderRecord["maxTier"];
27073
+ }
27060
27074
  });
27061
27075
  return { message: `${color54.green("\u2713")} tier ${color54.cyan(tier)} removed` };
27062
27076
  }
@@ -27117,7 +27131,7 @@ function buildTierCommand(opts) {
27117
27131
  };
27118
27132
  }
27119
27133
  if (action === "dwell") {
27120
- const turns = Number.parseInt(parts[2] ?? "", 10);
27134
+ const turns = strictNumber(parts[2] ?? "", INTEGER_RE2);
27121
27135
  if (!Number.isFinite(turns) || turns < 0) {
27122
27136
  return { message: `${color54.amber("Usage:")} /tier leader dwell <turns>` };
27123
27137
  }
@@ -27180,7 +27194,7 @@ function buildAgentsCommand(opts) {
27180
27194
  return {
27181
27195
  name: "agents",
27182
27196
  category: "Agent",
27183
- description: "Monitor subagent activity: /agents [chat off|compact|full | stream on|off|status | list | show <id>]",
27197
+ description: "Monitor subagent activity: /agents [chat off|full|status | stream on|off|status | list | show <id>]",
27184
27198
  async run(args) {
27185
27199
  if ((!args.trim() || args.trim() === "list") && opts.onPanelOpen?.current) {
27186
27200
  const opened = opts.onPanelOpen.current("toggleAgentsMonitor");
@@ -28217,7 +28231,7 @@ async function runGit2(args, cwd) {
28217
28231
  stdout += d;
28218
28232
  });
28219
28233
  child.on("error", () => resolve9({ stdout, code: 1 }));
28220
- child.on("close", (code) => resolve9({ stdout, code: code ?? 0 }));
28234
+ child.on("close", (code) => resolve9({ stdout, code: code ?? 1 }));
28221
28235
  });
28222
28236
  }
28223
28237
  async function getChangedFiles(cwd) {
@@ -31323,8 +31337,8 @@ function buildTelegramSettingsCommand(opts) {
31323
31337
  message: `${color65.green("\u2713")} long-tool \u2192 ${color65.dim("off")}`
31324
31338
  };
31325
31339
  }
31326
- const ms = Number.parseInt(raw, 10);
31327
- if (Number.isNaN(ms) || ms < 0) {
31340
+ const ms = /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
31341
+ if (!Number.isSafeInteger(ms)) {
31328
31342
  return {
31329
31343
  message: `${color65.red("Invalid number")}: "${raw}". Enter milliseconds, e.g. /telegram-settings long-tool 15000`
31330
31344
  };
@@ -31343,7 +31357,7 @@ function buildTelegramSettingsCommand(opts) {
31343
31357
  message: `${color65.amber("Usage:")} /telegram-settings poll <seconds> ${color65.dim("(1\u201360)")}`
31344
31358
  };
31345
31359
  }
31346
- const sec = Number.parseInt(raw, 10);
31360
+ const sec = /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
31347
31361
  if (Number.isNaN(sec) || sec < 1 || sec > 60) {
31348
31362
  return {
31349
31363
  message: `${color65.red("Invalid value")}: "${raw}". Enter seconds between 1 and 60.`
@@ -34697,7 +34711,7 @@ import * as path29 from "node:path";
34697
34711
  import { getSharedProjectMailbox as getSharedProjectMailbox10 } from "@wrongstack/core/coordination";
34698
34712
  import { CouncilOrchestrator, OneShotOrchestrator as OneShotOrchestrator3 } from "@wrongstack/core/execution";
34699
34713
  import { countShellHooks, HookRegistry as HookRegistry2, HookRunner as HookRunner2, shellHooksEqual } from "@wrongstack/core/hooks";
34700
- import { allServers as allServers3 } from "@wrongstack/core/infrastructure";
34714
+ import { resolveMcpServerConfig as resolveMcpServerConfig2 } from "@wrongstack/core/infrastructure";
34701
34715
  import { TOKENS as TOKENS11 } from "@wrongstack/core/kernel";
34702
34716
  import { NotifierImpl } from "@wrongstack/core/notifications";
34703
34717
  import {
@@ -35767,18 +35781,23 @@ async function setupLifecycleAndPlugins(deps) {
35767
35781
  log: logger,
35768
35782
  lazyMode: normalizeTokenSavingTier5(config.features.tokenSavingMode) !== "off",
35769
35783
  cacheDir: wpaths.cacheDir,
35784
+ // Presets say `--project-root .`; resolve it against the project, not
35785
+ // wherever this process happens to be running.
35786
+ cwd: wpaths.projectRoot,
35770
35787
  authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
35771
35788
  store: mcpTokenStore
35772
35789
  }),
35773
35790
  authorizationManager: mcpAuthorizationManager
35774
35791
  });
35775
35792
  if (config.features.mcp) {
35776
- const presets = allServers3();
35777
- for (const cfg of Object.values(config.mcpServers ?? {})) {
35778
- const preset = presets[cfg.name];
35779
- const merged = preset ? { ...preset, ...cfg } : cfg;
35780
- void mcpRegistry.start(merged).catch((err) => {
35781
- logger.warn(`MCP server "${cfg.name}" failed to start`, err);
35793
+ for (const [name, entry] of Object.entries(config.mcpServers ?? {})) {
35794
+ const resolved = resolveMcpServerConfig2(name, entry);
35795
+ if (!resolved) {
35796
+ logger.warn(`MCP server "${name}" has no transport and matches no preset \u2014 skipped`);
35797
+ continue;
35798
+ }
35799
+ void mcpRegistry.start(resolved).catch((err) => {
35800
+ logger.warn(`MCP server "${name}" failed to start`, err);
35782
35801
  });
35783
35802
  }
35784
35803
  }
@@ -38936,4 +38955,4 @@ export {
38936
38955
  CLI_VERSION,
38937
38956
  runInteractive
38938
38957
  };
38939
- //# sourceMappingURL=cli-main-4E4YMCNS.js.map
38958
+ //# sourceMappingURL=cli-main-WC5JNIWY.js.map
@@ -13,6 +13,7 @@ import {
13
13
  import * as fs2 from "node:fs/promises";
14
14
  import * as os from "node:os";
15
15
  import * as path2 from "node:path";
16
+ import { resolveMcpServerConfig } from "@wrongstack/core/infrastructure";
16
17
  import { color, toErrorMessage } from "@wrongstack/core/utils";
17
18
  import {
18
19
  applyProxyConfig,
@@ -328,8 +329,17 @@ var doctorCmd = async (args, deps) => {
328
329
  });
329
330
  }
330
331
  const mcpEntries = Object.entries(cfg.mcpServers ?? {});
331
- for (const [name, srv] of mcpEntries) {
332
- if (!srv.enabled) continue;
332
+ for (const [name, entry] of mcpEntries) {
333
+ if (entry.enabled === false) continue;
334
+ const srv = resolveMcpServerConfig(name, entry);
335
+ if (!srv) {
336
+ checks.push({
337
+ name: `mcp:${name}`,
338
+ status: "fail",
339
+ detail: "no transport and no built-in preset of this name"
340
+ });
341
+ continue;
342
+ }
333
343
  if ((srv.transport === "sse" || srv.transport === "streamable-http") && !srv.url)
334
344
  checks.push({ name: `mcp:${name}`, status: "fail", detail: "transport requires url" });
335
345
  else if (srv.transport === "stdio" && !srv.command)
@@ -382,4 +392,4 @@ export {
382
392
  doctorCmd,
383
393
  proxyCmd
384
394
  };
385
- //# sourceMappingURL=diag-doctor-PCER6KHH.js.map
395
+ //# sourceMappingURL=diag-doctor-ANPRUYFM.js.map
@@ -4,7 +4,7 @@ import {
4
4
  runGit,
5
5
  setAutoSuggestions,
6
6
  setSuggestions
7
- } from "./chunk-SXJTVNWF.js";
7
+ } from "./chunk-OWNC7LMD.js";
8
8
  import {
9
9
  deriveFsAccessPair,
10
10
  filterSafeForProject,
@@ -6953,4 +6953,4 @@ export {
6953
6953
  execute,
6954
6954
  resolveReviewerFallbackModels
6955
6955
  };
6956
- //# sourceMappingURL=execution-4RXQLDAF.js.map
6956
+ //# sourceMappingURL=execution-EXDRBS4F.js.map
@@ -14,9 +14,11 @@ import {
14
14
 
15
15
  // src/subcommands/handlers/hq.ts
16
16
  import * as fs from "node:fs/promises";
17
+ import * as path from "node:path";
17
18
  import {
18
19
  HQ_AUTH_FILE_VERSION,
19
20
  HQ_CLI_DEFAULT_HOST,
21
+ HqAlertEngine,
20
22
  HqInsecureExposureError,
21
23
  hqAuthAuditPath,
22
24
  hqAuthContentHash,
@@ -25,6 +27,7 @@ import {
25
27
  logHqAuthAudit,
26
28
  mintHqToken,
27
29
  mutateHqAuthFile,
30
+ readHqAlertsConfig,
28
31
  readHqAuthFile,
29
32
  resolveHqDataDir
30
33
  } from "@wrongstack/core/hq";
@@ -48,6 +51,9 @@ var hqCmd = async (args, deps) => {
48
51
  if (sub === "audit") {
49
52
  return hqAuditCmd(args.slice(1), deps);
50
53
  }
54
+ if (sub === "alerts") {
55
+ return hqAlertsCmd(args.slice(1), deps);
56
+ }
51
57
  if (sub === "service") {
52
58
  const { hqServiceCmd } = await import("./hq-service-L5INQFM4.js");
53
59
  return hqServiceCmd(args.slice(1), deps);
@@ -62,7 +68,7 @@ var hqCmd = async (args, deps) => {
62
68
  return 1;
63
69
  };
64
70
  async function startServer(deps) {
65
- const { startHqServer } = await import("./hq-server-JJFGFWI5.js");
71
+ const { startHqServer } = await import("./hq-server-KE5YDNGG.js");
66
72
  const dataDir = resolveDataDir(deps);
67
73
  const flags = deps.flags ?? {};
68
74
  const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
@@ -188,7 +194,7 @@ async function startServer(deps) {
188
194
  ipAllowlist === void 0 ? "Network allowlist: disabled (authentication remains required when configured).\n" : `Network allowlist: active (${ipAllowlist.length} configured rule${ipAllowlist.length === 1 ? "" : "s"} + loopback).
189
195
  `
190
196
  );
191
- await new Promise((resolve) => {
197
+ await new Promise((resolve2) => {
192
198
  const shutdown = async () => {
193
199
  try {
194
200
  await handle.close();
@@ -196,7 +202,7 @@ async function startServer(deps) {
196
202
  deps.renderer.write(`HQ server close error: ${String(err)}
197
203
  `);
198
204
  }
199
- resolve();
205
+ resolve2();
200
206
  };
201
207
  process.on("SIGINT", shutdown);
202
208
  process.on("SIGTERM", shutdown);
@@ -807,7 +813,7 @@ function printAuditHelp(deps) {
807
813
  `
808
814
  );
809
815
  deps.renderer.write(
810
- ` auth.json and print it so an operator can compare
816
+ ` on-disk auth.json and print it so an operator can compare
811
817
  `
812
818
  );
813
819
  deps.renderer.write(
@@ -825,8 +831,150 @@ function printAuditHelp(deps) {
825
831
  deps.renderer.write(`Run \`wstack hq --help\` for the full HQ command list.
826
832
  `);
827
833
  }
834
+ async function hqAlertsCmd(args, deps) {
835
+ const action = args[0];
836
+ if (deps.flags?.["help"] === true || action === "help" || action === "--help") {
837
+ printAlertsHelp(deps);
838
+ return 0;
839
+ }
840
+ if (action === "eval" || action === void 0) {
841
+ return hqAlertsEval(args.slice(1), deps);
842
+ }
843
+ deps.renderer.writeError(`Unknown hq alerts subcommand: ${action ?? "(none)"}
844
+ `);
845
+ printAlertsHelp(deps);
846
+ return 1;
847
+ }
848
+ async function hqAlertsEval(_args, deps) {
849
+ const flags = deps.flags ?? {};
850
+ const snapshotArg = typeof flags["snapshot"] === "string" ? flags["snapshot"] : void 0;
851
+ let snapshotPath;
852
+ try {
853
+ if (snapshotArg !== void 0) {
854
+ snapshotPath = path.resolve(snapshotArg);
855
+ } else {
856
+ const dataDir2 = resolveDataDir(deps);
857
+ snapshotPath = path.join(dataDir2, "snapshot.json");
858
+ }
859
+ } catch (err) {
860
+ deps.renderer.writeError(`Failed to resolve snapshot path: ${err.message}
861
+ `);
862
+ return 2;
863
+ }
864
+ let raw;
865
+ try {
866
+ raw = await fs.readFile(snapshotPath, "utf8");
867
+ } catch (err) {
868
+ deps.renderer.writeError(
869
+ `Cannot read snapshot at ${snapshotPath}: ${err.message}
870
+ Pass --snapshot <path> to evaluate against a specific file.
871
+ `
872
+ );
873
+ return 2;
874
+ }
875
+ let snapshot;
876
+ try {
877
+ snapshot = JSON.parse(raw);
878
+ } catch (err) {
879
+ deps.renderer.writeError(
880
+ `Snapshot at ${snapshotPath} is not valid JSON: ${err.message}
881
+ `
882
+ );
883
+ return 2;
884
+ }
885
+ const dataDir = resolveDataDir(deps);
886
+ let thresholds;
887
+ try {
888
+ const config = await readHqAlertsConfig(dataDir);
889
+ thresholds = config.thresholds;
890
+ } catch {
891
+ }
892
+ const engine = new HqAlertEngine({
893
+ onAlert: () => {
894
+ }
895
+ });
896
+ let fired;
897
+ try {
898
+ fired = engine.evaluate(snapshot, thresholds);
899
+ } catch (err) {
900
+ deps.renderer.writeError(`Alert evaluation failed: ${err.message}
901
+ `);
902
+ return 2;
903
+ }
904
+ if (fired.length === 0) {
905
+ deps.renderer.write(`No alert rules fired against ${snapshotPath}.
906
+ `);
907
+ if (thresholds === void 0) {
908
+ deps.renderer.write(
909
+ "(No persisted thresholds; using built-in defaults. Run `wstack hq alerts` with a populated alerts-config.json to override.)\n"
910
+ );
911
+ }
912
+ return 0;
913
+ }
914
+ deps.renderer.write(`${fired.length} alert rule(s) fired against ${snapshotPath}:
915
+ `);
916
+ for (const alert of fired) {
917
+ deps.renderer.write(` [${alert.severity}] ${alert.ruleId}: ${alert.message}
918
+ `);
919
+ }
920
+ return 1;
921
+ }
922
+ function printAlertsHelp(deps) {
923
+ deps.renderer.write(`Usage: wstack hq alerts <eval>
924
+ `);
925
+ deps.renderer.write("\n");
926
+ deps.renderer.write(
927
+ ` wstack hq alerts eval [--snapshot <path>] Run HqAlertEngine.evaluate() against a
928
+ `
929
+ );
930
+ deps.renderer.write(
931
+ ` snapshot file (or the live <dataDir>/
932
+ `
933
+ );
934
+ deps.renderer.write(
935
+ ` snapshot.json). Pure-function dry run;
936
+ `
937
+ );
938
+ deps.renderer.write(
939
+ ` no server required. Exits 0 when no
940
+ `
941
+ );
942
+ deps.renderer.write(
943
+ ` rules fire, 1 when rules fire, 2 on errors.
944
+ `
945
+ );
946
+ deps.renderer.write("\n");
947
+ deps.renderer.write(
948
+ `Persisted thresholds (<dataDir>/alerts-config.json) are loaded automatically when
949
+ `
950
+ );
951
+ deps.renderer.write(
952
+ `present; otherwise the engine's built-in defaults apply. Snoozes are NOT honored
953
+ `
954
+ );
955
+ deps.renderer.write(
956
+ `by this command \u2014 eval is a "what would fire RIGHT NOW if there were no snoozes"
957
+ `
958
+ );
959
+ deps.renderer.write(`probe.
960
+ `);
961
+ deps.renderer.write("\n");
962
+ deps.renderer.write(`Flags:
963
+ `);
964
+ deps.renderer.write(
965
+ ` --snapshot <path> Evaluate against this snapshot file instead of the live one.
966
+ `
967
+ );
968
+ deps.renderer.write(
969
+ ` --data-dir <path> Override HQ data directory (default ~/.wrongstack/hq).
970
+ `
971
+ );
972
+ deps.renderer.write("\n");
973
+ deps.renderer.write(`Run \`wstack hq --help\` for the full HQ command list.
974
+ `);
975
+ }
828
976
  export {
829
977
  hqCmd,
830
978
  resolveAuditActor
831
979
  };
832
- //# sourceMappingURL=hq-DQTUE7RC.js.map
980
+ //# sourceMappingURL=hq-NO2NWGU4.js.map
@@ -26,6 +26,17 @@ interface HqAuthStateOptions {
26
26
  * makes the latch unconditional.
27
27
  */
28
28
  onApplied?: ((mutableAuth: HqRouterMutableAuth) => void) | undefined;
29
+ /**
30
+ * Invoked when browser tokens stopped being live — revoked by hand, or aged
31
+ * out — during an {@link HqAuthState.apply}. `keys` are the stored verifiers,
32
+ * `ids` the token ids of those that had one.
33
+ *
34
+ * W4 #15: an open WebSocket never re-presents its credential, so before this
35
+ * a revoked operator kept a working dashboard until the idle-eviction pass
36
+ * happened to catch them. Reporting the change at the single projection
37
+ * choke point is what lets the server close those sockets immediately.
38
+ */
39
+ onTokensRevoked?: ((keys: readonly string[], ids: readonly string[]) => void) | undefined;
29
40
  }
30
41
  /**
31
42
  * Project an `auth.json` document onto the live `mutableAuth` used by every