@wrongstack/cli 1.0.1 → 1.0.4

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 (109) hide show
  1. package/data/providers.json +21 -21
  2. package/dist/{acp-3LARESEO.js → acp-AB54FWM2.js} +122 -39
  3. package/dist/acp-agent-list.d.ts +14 -0
  4. package/dist/acp-hq-telemetry.d.ts +9 -2
  5. package/dist/acp-mcp-servers.d.ts +49 -0
  6. package/dist/acp-server-agent.d.ts +18 -6
  7. package/dist/{auth-ZUMCDNE5.js → auth-WDMSNZ3S.js} +79 -55
  8. package/dist/auth-menu/index.d.ts +1 -0
  9. package/dist/auth-menu/oauth-menu.d.ts +3 -1
  10. package/dist/auth-menu/panel-service.d.ts +2 -0
  11. package/dist/auth-menu/provider-auth-login.d.ts +12 -0
  12. package/dist/auth-menu/types.d.ts +3 -0
  13. package/dist/auto-discover-providers-DXR25GTG.js +7 -0
  14. package/dist/boot/dispatch-webui.d.ts +1 -0
  15. package/dist/boot/short-circuit-hq.d.ts +2 -0
  16. package/dist/boot/system-prompt-builder.d.ts +7 -0
  17. package/dist/boot/system-prompt.d.ts +2 -1
  18. package/dist/boot/tool-registry.d.ts +18 -1
  19. package/dist/chimera-reviewer-policy.d.ts +1 -1
  20. package/dist/{chunk-C2E24IXZ.js → chunk-3I5ZJXM5.js} +147 -241
  21. package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
  22. package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
  23. package/dist/chunk-AUQDGU5D.js +32 -0
  24. package/dist/{chunk-D2VCUN7M.js → chunk-BIWE22O5.js} +17 -17
  25. package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
  26. package/dist/chunk-D5GB5MHA.js +87 -0
  27. package/dist/chunk-DAKVBMD4.js +36 -0
  28. package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
  29. package/dist/chunk-I7V2OYVH.js +242 -0
  30. package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
  31. package/dist/{chunk-ZSZ3EYWH.js → chunk-KEPABQYT.js} +109 -6
  32. package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
  33. package/dist/{chunk-MNQQ4ZNF.js → chunk-ONOT5OJ2.js} +290 -687
  34. package/dist/chunk-Q7E3BPDU.js +62 -0
  35. package/dist/chunk-SIGUWXAA.js +43 -0
  36. package/dist/{chunk-TEBMCYZN.js → chunk-SIS37L3W.js} +58 -2
  37. package/dist/{chunk-KHWDTNBZ.js → chunk-U6PGWDMZ.js} +5 -5
  38. package/dist/{chunk-5IA5337I.js → chunk-XJ7K67PP.js} +8 -2
  39. package/dist/{cli-context-7X3M6U73.js → cli-context-WFC3BNGM.js} +131 -25
  40. package/dist/{cli-main-53KG6HAV.js → cli-main-GU3YZLMO.js} +3986 -2827
  41. package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
  42. package/dist/execute-deps.d.ts +1 -0
  43. package/dist/{execution-MIBWBYB6.js → execution-RAZDDQFC.js} +115 -16
  44. package/dist/execution-specialist-trigger.d.ts +28 -0
  45. package/dist/fleet/acp-live-progress.d.ts +19 -0
  46. package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
  47. package/dist/fleet/host-acp.d.ts +14 -2
  48. package/dist/fleet/host.d.ts +1 -0
  49. package/dist/{hq-IG3YRGCO.js → hq-5XHEIIH6.js} +127 -8
  50. package/dist/hq-command-controller.d.ts +31 -2
  51. package/dist/hq-server/client-address.d.ts +2 -0
  52. package/dist/hq-server/ip-allowlist.d.ts +16 -0
  53. package/dist/hq-server/preflight.d.ts +1 -0
  54. package/dist/hq-server/routes/auth/password-routes.d.ts +1 -1
  55. package/dist/hq-server/routes.d.ts +5 -4
  56. package/dist/hq-server/secret-input.d.ts +7 -0
  57. package/dist/hq-server/types.d.ts +3 -2
  58. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  59. package/dist/hq-server/utils.d.ts +6 -0
  60. package/dist/{hq-server-OILU3NPI.js → hq-server-CNUTBWTK.js} +4 -3
  61. package/dist/hq-server.d.ts +4 -0
  62. package/dist/hq-service-L5INQFM4.js +507 -0
  63. package/dist/hq-service.d.ts +22 -0
  64. package/dist/index.js +4 -4
  65. package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
  66. package/dist/live-settings-input.d.ts +4 -0
  67. package/dist/{mailbox-serve-VUCWZ2SJ.js → mailbox-serve-X3CR6VFW.js} +4 -3
  68. package/dist/{modeldiag-FMJT37SN.js → modeldiag-46CMWUNM.js} +31 -24
  69. package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
  70. package/dist/{plugin-usage-HFBI3TFT.js → plugin-usage-M6KET4OZ.js} +2 -2
  71. package/dist/{plugins-PZ2VZL7V.js → plugins-WJLW6ULV.js} +3 -3
  72. package/dist/projects-LMNAGGEU.js +36 -0
  73. package/dist/{providers-models-YVMVYWRS.js → providers-models-6MOGAOVK.js} +2 -2
  74. package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
  75. package/dist/short-circuit-flags-YDMLAQ5R.js +10 -0
  76. package/dist/slash-commands/acp.d.ts +3 -0
  77. package/dist/slash-commands/codebase-map.d.ts +11 -0
  78. package/dist/slash-commands/command-context.d.ts +29 -0
  79. package/dist/slash-commands/context.d.ts +10 -0
  80. package/dist/slash-commands/ensemble.d.ts +1 -1
  81. package/dist/slash-commands/openai-quota.d.ts +31 -0
  82. package/dist/slash-commands/profile.d.ts +7 -0
  83. package/dist/subcommands/handlers/hq.d.ts +1 -0
  84. package/dist/subcommands/handlers/projects.d.ts +14 -0
  85. package/dist/{subcommands-WJLBHVKC.js → subcommands-7N4RIK6H.js} +2 -2
  86. package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
  87. package/dist/{webui-server-5R35BHWZ.js → webui-server-OBV5JYJV.js} +27 -4
  88. package/dist/webui-server-options.d.ts +1 -0
  89. package/dist/wiring/atlas-prompt-contributor.d.ts +54 -0
  90. package/dist/wiring/cli-execute-builder.d.ts +1 -0
  91. package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
  92. package/dist/wiring/codebase-embeddings.d.ts +31 -0
  93. package/dist/wiring/command-host-state.d.ts +9 -0
  94. package/dist/wiring/concept-summarizer.d.ts +32 -0
  95. package/dist/wiring/external-plugins.d.ts +1 -0
  96. package/dist/wiring/lifecycle-plugins.d.ts +2 -1
  97. package/dist/wiring/pipeline.d.ts +2 -0
  98. package/dist/wiring/plugins.d.ts +2 -1
  99. package/dist/wiring/provider-runtime-setup.d.ts +1 -1
  100. package/dist/wiring/provider.d.ts +2 -1
  101. package/dist/wiring/session-command-handlers.d.ts +2 -1
  102. package/dist/wiring/slash-commands.d.ts +1 -0
  103. package/package.json +29 -29
  104. package/dist/auto-discover-providers-VQU3IZMR.js +0 -7
  105. package/dist/chunk-52C7PD2H.js +0 -61
  106. package/dist/chunk-Q5GTM25S.js +0 -17
  107. package/dist/chunk-TNK6237M.js +0 -262
  108. package/dist/projects-L6C6XLDY.js +0 -37
  109. package/dist/short-circuit-flags-UREFSYBL.js +0 -10
@@ -3,14 +3,37 @@ import {
3
3
  terminalText
4
4
  } from "./chunk-HXJYHAR4.js";
5
5
  import {
6
- resolveAuditActor
7
- } from "./chunk-Q5GTM25S.js";
6
+ MAX_AGENT_RINGS,
7
+ MAX_TRANSCRIPT_SESSIONS,
8
+ TRANSCRIPT_RING_MAX,
9
+ agentMessageToEntry,
10
+ agentRingKey,
11
+ buildBootstrapHttpUrl,
12
+ buildClientWsUrl,
13
+ buildHttpUrl,
14
+ decodePathSegment,
15
+ decodeSessionId,
16
+ evictOldest,
17
+ hqMachineKey,
18
+ hqRuntimeMarkerPath,
19
+ lanIPv4Addresses,
20
+ readLocalSubagentTranscript,
21
+ readRequestBody,
22
+ recordTimeseriesSignal,
23
+ resolveAuditActor,
24
+ sanitizeApiError,
25
+ truncateHqSummary,
26
+ writeInvalidBody
27
+ } from "./chunk-I7V2OYVH.js";
28
+ import {
29
+ createHqIpAllowlist
30
+ } from "./chunk-Q7E3BPDU.js";
8
31
 
9
32
  // src/hq-server.ts
10
33
  import { randomUUID as randomUUID6 } from "node:crypto";
11
- import * as fs5 from "node:fs/promises";
34
+ import * as fs4 from "node:fs/promises";
12
35
  import * as http from "node:http";
13
- import * as path7 from "node:path";
36
+ import * as path6 from "node:path";
14
37
  import { getSharedProjectMailbox } from "@wrongstack/core/coordination";
15
38
  import {
16
39
  assessHqExposure as assessHqExposure2,
@@ -290,7 +313,7 @@ function authenticateBrowserRequest(req, url, mutableAuth, sessions) {
290
313
  if (liveToken.capabilities !== void 0) ctx.capabilities = liveToken.capabilities;
291
314
  return ctx;
292
315
  }
293
- return { kind: "cookie" };
316
+ return session.capabilities === void 0 ? { kind: "cookie" } : { kind: "cookie", capabilities: session.capabilities };
294
317
  }
295
318
  if (session) sessions.delete(sessionId);
296
319
  }
@@ -609,6 +632,7 @@ var MIME = {
609
632
  ".mjs": "application/javascript; charset=utf-8",
610
633
  ".css": "text/css; charset=utf-8",
611
634
  ".json": "application/json; charset=utf-8",
635
+ ".webmanifest": "application/manifest+json; charset=utf-8",
612
636
  ".svg": "image/svg+xml",
613
637
  ".png": "image/png",
614
638
  ".jpg": "image/jpeg",
@@ -711,7 +735,6 @@ function isLoopbackRequest(req) {
711
735
  var HQ_AUTH_ADMIN_CAPABILITY = "auth.admin";
712
736
  function callerCanAdministerAuth(auth) {
713
737
  if (auth === void 0) return false;
714
- if (auth.kind === "cookie" && auth.tokenId === void 0) return true;
715
738
  if (auth.capabilities === void 0) return true;
716
739
  return auth.capabilities.includes(HQ_AUTH_ADMIN_CAPABILITY);
717
740
  }
@@ -762,6 +785,9 @@ function normalizeAddress(raw) {
762
785
  const trimmed = raw.trim().replace(/^\[(.*)\]$/, "$1");
763
786
  return trimmed.replace(/^::ffff:/i, "");
764
787
  }
788
+ function resolveSocketAddress(req) {
789
+ return req.socket.remoteAddress ? normalizeAddress(req.socket.remoteAddress) : void 0;
790
+ }
765
791
  function parseForwardedEntry(entry) {
766
792
  const value = entry.trim();
767
793
  if (!value) return void 0;
@@ -786,7 +812,7 @@ function forwardedChain(req) {
786
812
  return raw.split(",").map((entry) => parseForwardedEntry(entry)).filter((entry) => entry !== void 0);
787
813
  }
788
814
  function resolveClientAddress(req, trustedProxyHops = 0) {
789
- const socketAddress = req.socket.remoteAddress ? normalizeAddress(req.socket.remoteAddress) : void 0;
815
+ const socketAddress = resolveSocketAddress(req);
790
816
  const hops = Number.isFinite(trustedProxyHops) ? Math.max(0, Math.trunc(trustedProxyHops)) : 0;
791
817
  if (hops === 0) return socketAddress ?? "unknown";
792
818
  const chain = [
@@ -797,201 +823,6 @@ function resolveClientAddress(req, trustedProxyHops = 0) {
797
823
  return chain[hops] ?? socketAddress ?? "unknown";
798
824
  }
799
825
 
800
- // src/hq-server/utils.ts
801
- import * as fs3 from "node:fs/promises";
802
- import * as os from "node:os";
803
- import * as path3 from "node:path";
804
- import { resolveHqDataDir } from "@wrongstack/core/hq";
805
- import { sanitizeApiError, scrubErrorDetail } from "@wrongstack/core/security";
806
- var TRANSCRIPT_RING_MAX = 4e3;
807
- var MAX_TRANSCRIPT_SESSIONS = 400;
808
- var MAX_AGENT_RINGS = 800;
809
- function evictOldest(map, max) {
810
- while (map.size > max) {
811
- const oldest = map.keys().next().value;
812
- if (oldest === void 0) break;
813
- map.delete(oldest);
814
- }
815
- }
816
- function agentRingKey(sessionId, subId) {
817
- return sessionId ? `${sessionId}::${subId}` : subId;
818
- }
819
- function agentMessageToEntry(p) {
820
- const kind = typeof p["kind"] === "string" ? p["kind"] : "text";
821
- const role = kind === "thinking" ? "thinking" : kind === "tool_use" || kind === "tool_result" ? "tool" : kind === "error" ? "error" : kind === "status" || kind === "system" ? "system" : "assistant";
822
- return {
823
- ts: typeof p["ts"] === "string" ? p["ts"] : (/* @__PURE__ */ new Date()).toISOString(),
824
- role,
825
- text: typeof p["content"] === "string" ? p["content"] : "",
826
- ...typeof p["toolName"] === "string" ? { tool: p["toolName"] } : {},
827
- ...kind === "error" ? { isError: true } : {}
828
- };
829
- }
830
- async function readLocalSubagentTranscript(sessionId, subagentId) {
831
- try {
832
- const { getSessionRegistry } = await import("@wrongstack/core/storage");
833
- const { resolveWstackPaths, sessionScopedPath } = await import("@wrongstack/core/utils");
834
- const globalRoot = path3.dirname(resolveHqDataDir());
835
- const registry = getSessionRegistry(globalRoot);
836
- const entry = await registry.get(sessionId).catch(() => null);
837
- if (!entry) return null;
838
- const paths = resolveWstackPaths({ projectRoot: entry.projectRoot, globalRoot });
839
- const sessionDir = sessionScopedPath(paths.projectSessions, sessionId, "");
840
- if (!isSafePathSegment(subagentId)) return null;
841
- const transcriptsRoot = path3.resolve(sessionDir, "subagents", "transcripts");
842
- const file = path3.resolve(transcriptsRoot, subagentId, "transcript.jsonl");
843
- if (!file.startsWith(transcriptsRoot + path3.sep)) return null;
844
- const raw = await fs3.readFile(file, "utf8").catch(() => null);
845
- if (raw === null) return null;
846
- const out = [];
847
- for (const line of raw.split("\n")) {
848
- const trimmed = line.trim();
849
- if (trimmed.length === 0) continue;
850
- try {
851
- out.push(agentMessageToEntry(JSON.parse(trimmed)));
852
- } catch {
853
- }
854
- }
855
- return out;
856
- } catch {
857
- return null;
858
- }
859
- }
860
- function decodePathSegment(value) {
861
- let decoded;
862
- try {
863
- decoded = decodeURIComponent(value);
864
- } catch {
865
- return null;
866
- }
867
- return isSafePathSegment(decoded) ? decoded : null;
868
- }
869
- function decodeSessionId(value) {
870
- let decoded;
871
- try {
872
- decoded = decodeURIComponent(value);
873
- } catch {
874
- return null;
875
- }
876
- return isSafeSessionId(decoded) ? decoded : null;
877
- }
878
- var MAX_PATH_SEGMENT_LENGTH = 256;
879
- function isSafePathSegment(value) {
880
- if (value.length === 0 || value.length > MAX_PATH_SEGMENT_LENGTH) return false;
881
- if (value === "." || value === "..") return false;
882
- if (value.includes("/") || value.includes("\\")) return false;
883
- if (value.includes("\0")) return false;
884
- if (value.includes(":")) return false;
885
- return true;
886
- }
887
- function isSafeSessionId(value) {
888
- if (value.length === 0 || value.length > MAX_PATH_SEGMENT_LENGTH) return false;
889
- if (value.includes("\\") || value.includes("\0") || value.includes(":")) return false;
890
- const parts = value.split("/");
891
- if (parts.length > 2) return false;
892
- return parts.every((p) => p.length > 0 && p !== "." && p !== "..");
893
- }
894
- function displayHost(host) {
895
- return host === "0.0.0.0" ? "127.0.0.1" : host;
896
- }
897
- var RequestBodyTooLargeError = class extends Error {
898
- };
899
- function readRequestBody(req, maxBytes = 1e6) {
900
- return new Promise((resolve2, reject) => {
901
- const chunks = [];
902
- let size = 0;
903
- let settled = false;
904
- req.on("data", (chunk) => {
905
- if (settled) return;
906
- size += chunk.length;
907
- if (size > maxBytes) {
908
- settled = true;
909
- chunks.length = 0;
910
- reject(new RequestBodyTooLargeError("request body too large"));
911
- return;
912
- }
913
- chunks.push(chunk);
914
- });
915
- req.on("end", () => {
916
- if (!settled) resolve2(Buffer.concat(chunks).toString("utf8"));
917
- });
918
- req.on("error", (error) => {
919
- if (!settled) reject(error);
920
- });
921
- });
922
- }
923
- function writeInvalidBody(res, error) {
924
- const tooLarge = error instanceof RequestBodyTooLargeError;
925
- res.writeHead(tooLarge ? 413 : 400, { "Content-Type": "application/json" });
926
- res.end(JSON.stringify({ error: tooLarge ? "request body too large" : "invalid json body" }));
927
- }
928
- function buildHttpUrl(host, port, token) {
929
- const url = new URL(`http://${displayHost(host)}:${port}/`);
930
- if (token) url.searchParams.set("token", token);
931
- return url.toString();
932
- }
933
- function buildBootstrapHttpUrl(host, port, bootstrapCode) {
934
- const url = new URL(`http://${displayHost(host)}:${port}/`);
935
- url.hash = `bootstrap=${bootstrapCode}`;
936
- return url.toString();
937
- }
938
- function buildClientWsUrl(host, port, token) {
939
- const url = new URL(`ws://${displayHost(host)}:${port}/ws/client`);
940
- if (token) url.searchParams.set("token", token);
941
- return url.toString();
942
- }
943
- function hqRuntimeMarkerPath(dataDir) {
944
- return path3.join(dataDir, "runtime.json");
945
- }
946
- function lanIPv4Addresses() {
947
- const out = [];
948
- try {
949
- const ifaces = os.networkInterfaces();
950
- for (const name of Object.keys(ifaces)) {
951
- for (const ni of ifaces[name] ?? []) {
952
- if (ni.family === "IPv4" && !ni.internal) out.push(ni.address);
953
- }
954
- }
955
- } catch {
956
- }
957
- return out;
958
- }
959
- function truncateHqSummary(value, maxLength) {
960
- if (typeof value !== "string" || value.length === 0) return void 0;
961
- if (value.length <= maxLength) return value;
962
- const suffix = `\u2026[truncated:${value.length - maxLength}]`;
963
- if (maxLength <= suffix.length) return "\u2026";
964
- const prefixLen = maxLength - suffix.length;
965
- return `${value.slice(0, prefixLen)}${suffix}`;
966
- }
967
- function hqMachineKey(hostname, machineId) {
968
- const hn = hostname?.trim();
969
- return hn ? `host:${hn.toLowerCase()}` : `mid:${machineId || "local"}`;
970
- }
971
- function recordTimeseriesSignal(persistence, event) {
972
- try {
973
- if (event.type === "session.usage") {
974
- const p = event.payload;
975
- persistence.timeseries.record({
976
- ts: Date.parse(event.timestamp) || Date.now(),
977
- ...typeof p.costUsd === "number" ? { costUsd: p.costUsd } : {},
978
- ...typeof p.inputTokens === "number" ? { inputTokens: p.inputTokens } : {},
979
- ...typeof p.outputTokens === "number" ? { outputTokens: p.outputTokens } : {},
980
- ...typeof p.model === "string" && p.model.length > 0 ? { model: p.model } : {},
981
- ...typeof p.provider === "string" && p.provider.length > 0 ? { provider: p.provider } : {},
982
- ...typeof p.cacheRead === "number" ? { cacheRead: p.cacheRead } : {},
983
- ...typeof p.cacheWrite === "number" ? { cacheWrite: p.cacheWrite } : {}
984
- });
985
- } else if (event.type === "tool.completed") {
986
- persistence.timeseries.record({
987
- ts: Date.parse(event.timestamp) || Date.now(),
988
- toolCalls: 1
989
- });
990
- }
991
- } catch {
992
- }
993
- }
994
-
995
826
  // src/hq-server/routes/auth/password-routes.ts
996
827
  async function handleApiAuthStatus(req, res, url, mutableAuth, sessions, requireBrowserAuth, trustedPublicOrigins, secureCookies) {
997
828
  const auth = authenticateBrowserRequest(req, url, mutableAuth, sessions);
@@ -1013,7 +844,7 @@ async function handleApiAuthStatus(req, res, url, mutableAuth, sessions, require
1013
844
  })
1014
845
  );
1015
846
  }
1016
- async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, secureCookies, trustedProxyHops) {
847
+ async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, secureCookies, trustedProxyHops, sessionCapabilities) {
1017
848
  if (!mutableAuth.passwordHash) {
1018
849
  res.writeHead(403, { "Content-Type": "application/json" });
1019
850
  res.end(
@@ -1076,7 +907,8 @@ async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, se
1076
907
  createdAt: Date.now(),
1077
908
  kind: "password",
1078
909
  pending2fa: true,
1079
- lastSeenAt: Date.now()
910
+ lastSeenAt: Date.now(),
911
+ ...sessionCapabilities !== void 0 ? { capabilities: sessionCapabilities } : {}
1080
912
  });
1081
913
  setHqSessionCookie(
1082
914
  res,
@@ -1089,7 +921,12 @@ async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, se
1089
921
  }
1090
922
  loginAttempts.clearOnSuccess(clientIp, body.password);
1091
923
  const sessionId = randomUUID();
1092
- sessions.set(sessionId, { createdAt: Date.now(), kind: "password", lastSeenAt: Date.now() });
924
+ sessions.set(sessionId, {
925
+ createdAt: Date.now(),
926
+ kind: "password",
927
+ lastSeenAt: Date.now(),
928
+ ...sessionCapabilities !== void 0 ? { capabilities: sessionCapabilities } : {}
929
+ });
1093
930
  setHqSessionCookie(
1094
931
  res,
1095
932
  serializeHqSessionCookie(sessionId, mutableAuth.cookieSecret),
@@ -1359,7 +1196,8 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
1359
1196
  sessions.set(fullSessionId, {
1360
1197
  createdAt: Date.now(),
1361
1198
  kind: "password",
1362
- lastSeenAt: Date.now()
1199
+ lastSeenAt: Date.now(),
1200
+ ...session.capabilities !== void 0 ? { capabilities: session.capabilities } : {}
1363
1201
  });
1364
1202
  setHqSessionCookie(
1365
1203
  res,
@@ -1399,8 +1237,8 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
1399
1237
  await recoveryCodeLock;
1400
1238
  let releaseLock = () => {
1401
1239
  };
1402
- recoveryCodeLock = new Promise((resolve2) => {
1403
- releaseLock = resolve2;
1240
+ recoveryCodeLock = new Promise((resolve) => {
1241
+ releaseLock = resolve;
1404
1242
  });
1405
1243
  try {
1406
1244
  const next = await mutateHqAuthFile2(dataDir, (current) => {
@@ -1438,7 +1276,8 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
1438
1276
  sessions.set(fullSessionId, {
1439
1277
  createdAt: Date.now(),
1440
1278
  kind: "password",
1441
- lastSeenAt: Date.now()
1279
+ lastSeenAt: Date.now(),
1280
+ ...session.capabilities !== void 0 ? { capabilities: session.capabilities } : {}
1442
1281
  });
1443
1282
  setHqSessionCookie(
1444
1283
  res,
@@ -2255,7 +2094,7 @@ async function authorizeHqCommand(input) {
2255
2094
  }
2256
2095
 
2257
2096
  // src/hq-server/routes/mailbox-handlers.ts
2258
- import * as path4 from "node:path";
2097
+ import * as path3 from "node:path";
2259
2098
  import { resolveProjectDir } from "@wrongstack/core/coordination";
2260
2099
 
2261
2100
  // src/hq-server/project-root.ts
@@ -2311,7 +2150,7 @@ async function handleMailboxGateway(_req, res, match, authorizeMailboxGateway, g
2311
2150
  );
2312
2151
  return;
2313
2152
  }
2314
- const gatewayGlobalRoot = path4.dirname(dataDir);
2153
+ const gatewayGlobalRoot = path3.dirname(dataDir);
2315
2154
  const preliminaryAccess = authorizeMailboxGateway(_req, `project:${projectId}`);
2316
2155
  if (!preliminaryAccess.allowed) {
2317
2156
  res.writeHead(preliminaryAccess.status ?? 401, {
@@ -2377,7 +2216,7 @@ async function handleApiMailboxSend(_req, res, mutableAuth, sessions, dataDir, g
2377
2216
  res.end(JSON.stringify({ error: "audience must be all or leaders" }));
2378
2217
  return;
2379
2218
  }
2380
- const mbGlobalRoot = path4.dirname(dataDir);
2219
+ const mbGlobalRoot = path3.dirname(dataDir);
2381
2220
  const projectRoot = await resolveHqProjectRoot(mbGlobalRoot, {
2382
2221
  sessionId: mbody.sessionId,
2383
2222
  projectId: mbody.projectId
@@ -2497,7 +2336,7 @@ async function handleMailboxAction(_req, res, match, mutableAuth, sessions, data
2497
2336
  res.end(JSON.stringify({ error: "missing sessionId or projectId" }));
2498
2337
  return;
2499
2338
  }
2500
- const actGlobalRoot = path4.dirname(dataDir);
2339
+ const actGlobalRoot = path3.dirname(dataDir);
2501
2340
  const projectRoot = await resolveHqProjectRoot(actGlobalRoot, {
2502
2341
  sessionId: abody.sessionId,
2503
2342
  projectId: abody.projectId
@@ -2593,6 +2432,15 @@ async function handleApiCommand(req, res, url, mutableAuth, sessions, clients, b
2593
2432
  res.end(JSON.stringify({ error: "unrecognized or malformed command", type: body.type }));
2594
2433
  return;
2595
2434
  }
2435
+ if (validated.type === "run-command" && isCookieAuth(auth) && auth.capabilities !== void 0 && !auth.capabilities.includes("control.execute")) {
2436
+ res.writeHead(403, { "Content-Type": "application/json" });
2437
+ res.end(
2438
+ JSON.stringify({
2439
+ error: "forbidden: browser session lacks control.execute capability"
2440
+ })
2441
+ );
2442
+ return;
2443
+ }
2596
2444
  if (validated.type === "run-command" && !tokenHasCapability(target.authToken, "control.execute")) {
2597
2445
  res.writeHead(403, { "Content-Type": "application/json" });
2598
2446
  res.end(
@@ -2678,14 +2526,14 @@ async function handleApiTrendsCost(req, res, persistence) {
2678
2526
  }
2679
2527
 
2680
2528
  // src/hq-server/routes/session-handlers.ts
2681
- import * as path5 from "node:path";
2529
+ import * as path4 from "node:path";
2682
2530
  import {
2683
2531
  buildTranscriptFromEvents,
2684
- resolveHqDataDir as resolveHqDataDir2
2532
+ resolveHqDataDir
2685
2533
  } from "@wrongstack/core/hq";
2686
2534
  async function handleApiSessions(res) {
2687
2535
  const { getSessionRegistry } = await import("@wrongstack/core/storage");
2688
- const globalRoot = path5.dirname(resolveHqDataDir2());
2536
+ const globalRoot = path4.dirname(resolveHqDataDir());
2689
2537
  try {
2690
2538
  const registry = getSessionRegistry(globalRoot);
2691
2539
  const sessions = await registry.list();
@@ -2730,7 +2578,7 @@ async function handleApiSessionEvents(req, res, match, transcripts) {
2730
2578
  res.end(JSON.stringify({ error: "invalid sessionId encoding" }));
2731
2579
  return;
2732
2580
  }
2733
- const globalRoot = path5.dirname(resolveHqDataDir2());
2581
+ const globalRoot = path4.dirname(resolveHqDataDir());
2734
2582
  try {
2735
2583
  const registry = getSessionRegistry(globalRoot);
2736
2584
  const entry = await registry.get(sessionId).catch(() => null);
@@ -2957,12 +2805,26 @@ function createHqRouter(deps) {
2957
2805
  trustBoundary,
2958
2806
  bootstrapStore,
2959
2807
  applyAuthFile,
2960
- trustedProxyHops = 0
2808
+ trustedProxyHops = 0,
2809
+ ipAllowlist
2961
2810
  } = deps;
2962
2811
  return async (req, res) => {
2963
2812
  try {
2964
2813
  const url = new URL(req.url ?? "/", `http://${host}:${listeningPort()}`);
2965
2814
  setHqSecurityHeaders(res);
2815
+ if (ipAllowlist && !ipAllowlist.allows(resolveSocketAddress(req))) {
2816
+ res.writeHead(403, { "Content-Type": "application/json" });
2817
+ res.end(JSON.stringify({ error: "forbidden: source IP is not allowed" }));
2818
+ return;
2819
+ }
2820
+ if (url.pathname === "/healthz" && req.method === "GET") {
2821
+ res.writeHead(200, {
2822
+ "Content-Type": "application/json",
2823
+ "Cache-Control": "no-store"
2824
+ });
2825
+ res.end(JSON.stringify({ status: "ok" }));
2826
+ return;
2827
+ }
2966
2828
  if (!hasTrustedBrowserOrigin(
2967
2829
  req,
2968
2830
  host,
@@ -2974,7 +2836,7 @@ function createHqRouter(deps) {
2974
2836
  res.end(JSON.stringify({ error: "forbidden: untrusted request origin" }));
2975
2837
  return;
2976
2838
  }
2977
- if (url.pathname.startsWith("/api/") && url.pathname !== "/api/auth/status" && url.pathname !== "/api/login" && url.pathname !== "/api/login/verify" && url.pathname !== "/api/auth/bootstrap" && hqAuthRequired(mutableAuth, requireBrowserAuth)) {
2839
+ if (url.pathname.startsWith("/api/") && url.pathname !== "/api/auth/status" && url.pathname !== "/api/login" && url.pathname !== "/api/login/verify" && url.pathname !== "/api/mobile/login" && url.pathname !== "/api/mobile/login/verify" && url.pathname !== "/api/auth/bootstrap" && hqAuthRequired(mutableAuth, requireBrowserAuth)) {
2978
2840
  const auth = authenticateBrowserRequest(req, url, mutableAuth, sessions);
2979
2841
  if (!auth) {
2980
2842
  const tokenOnly = mutableAuth.browserTokens.size > 0 && !mutableAuth.passwordHash;
@@ -3041,7 +2903,7 @@ function createHqRouter(deps) {
3041
2903
  await handleApiAuthSessionsRevoke(req, res, url, sessions);
3042
2904
  return;
3043
2905
  }
3044
- if (url.pathname === "/api/login" && req.method === "POST") {
2906
+ if ((url.pathname === "/api/login" || url.pathname === "/api/mobile/login") && req.method === "POST") {
3045
2907
  await handleApiLogin(
3046
2908
  req,
3047
2909
  res,
@@ -3049,7 +2911,8 @@ function createHqRouter(deps) {
3049
2911
  sessions,
3050
2912
  loginAttempts,
3051
2913
  secureCookies,
3052
- trustedProxyHops
2914
+ trustedProxyHops,
2915
+ url.pathname === "/api/mobile/login" ? ["control.enqueue"] : void 0
3053
2916
  );
3054
2917
  return;
3055
2918
  }
@@ -3077,7 +2940,7 @@ function createHqRouter(deps) {
3077
2940
  await handleApiLogout(req, res, mutableAuth, sessions, secureCookies);
3078
2941
  return;
3079
2942
  }
3080
- if (url.pathname === "/api/login/verify" && req.method === "POST") {
2943
+ if ((url.pathname === "/api/login/verify" || url.pathname === "/api/mobile/login/verify") && req.method === "POST") {
3081
2944
  await handleApiLoginVerify(
3082
2945
  req,
3083
2946
  res,
@@ -3380,12 +3243,12 @@ import {
3380
3243
  ensureHqFirstRunAuthFile,
3381
3244
  HqInsecureExposureError,
3382
3245
  isTokenExpired as isTokenExpired3,
3383
- resolveHqDataDir as resolveHqDataDir3
3246
+ resolveHqDataDir as resolveHqDataDir2
3384
3247
  } from "@wrongstack/core/hq";
3385
3248
  async function prepareHqServerStart(options, defaults) {
3386
3249
  const host = options.host ?? defaults.host;
3387
3250
  const port = options.port ?? defaults.port;
3388
- const dataDir = resolveHqDataDir3(options.dataDir);
3251
+ const dataDir = resolveHqDataDir2(options.dataDir);
3389
3252
  const firstRunAuth = await ensureHqFirstRunAuthFile(dataDir, {
3390
3253
  warn: (message) => console.warn(
3391
3254
  JSON.stringify({
@@ -3397,6 +3260,7 @@ async function prepareHqServerStart(options, defaults) {
3397
3260
  ),
3398
3261
  actor: resolveAuditActor(),
3399
3262
  ...options.password !== void 0 ? { password: options.password } : {},
3263
+ ...options.bootstrapPasswordOnly !== void 0 ? { bootstrapPasswordOnly: options.bootstrapPasswordOnly } : {},
3400
3264
  ...options.tokenTtlMs !== void 0 ? { tokenTtlMs: options.tokenTtlMs } : {}
3401
3265
  });
3402
3266
  const { authFile } = firstRunAuth;
@@ -3435,21 +3299,21 @@ async function prepareHqServerStart(options, defaults) {
3435
3299
  }
3436
3300
 
3437
3301
  // src/hq-server/startup.ts
3438
- import * as fs4 from "node:fs/promises";
3439
- import * as path6 from "node:path";
3302
+ import * as fs3 from "node:fs/promises";
3303
+ import * as path5 from "node:path";
3440
3304
  import { writeHqRuntimeFile } from "@wrongstack/core/hq";
3441
3305
  var hqRuntimeMarkerPath2 = hqRuntimeMarkerPath;
3442
3306
  async function writeHqRuntimeMarker(dataDir, url) {
3443
- await fs4.mkdir(dataDir, { recursive: true });
3307
+ await fs3.mkdir(dataDir, { recursive: true });
3444
3308
  await writeHqRuntimeFile(dataDir, { url, pid: process.pid });
3445
3309
  }
3446
3310
  async function clearHqRuntimeMarker(dataDir, url) {
3447
3311
  const file = hqRuntimeMarkerPath2(dataDir);
3448
3312
  try {
3449
- const raw = await fs4.readFile(file, "utf8");
3313
+ const raw = await fs3.readFile(file, "utf8");
3450
3314
  const parsed = JSON.parse(raw);
3451
3315
  if (parsed.url === url && parsed.pid === process.pid) {
3452
- await fs4.rm(file, { force: true });
3316
+ await fs3.rm(file, { force: true });
3453
3317
  }
3454
3318
  } catch {
3455
3319
  }
@@ -3458,6 +3322,8 @@ function writeHqStartupInfo(write, handle) {
3458
3322
  const startup = handle.firstRunSetup;
3459
3323
  const browserUrl = buildHttpUrl(handle.host, handle.port);
3460
3324
  const clientUrl = buildClientWsUrl(handle.host, handle.port);
3325
+ const mobileUrl = new URL(browserUrl);
3326
+ mobileUrl.pathname = "/mobile";
3461
3327
  write(
3462
3328
  `${terminalText("WrongStack HQ", "magenta")} ${terminalText("listening on", "green")} ${terminalLink(browserUrl)}
3463
3329
  `
@@ -3465,13 +3331,39 @@ function writeHqStartupInfo(write, handle) {
3465
3331
  if (!startup) {
3466
3332
  write(`${terminalText("Browser endpoint:", "blue")} ${terminalLink(browserUrl)}
3467
3333
  `);
3334
+ write(
3335
+ `${terminalText("Mobile endpoint:", "blue")} ${terminalLink(mobileUrl.toString())} (password required)
3336
+ `
3337
+ );
3338
+ write(`${terminalText("Client endpoint:", "blue")} ${terminalLink(clientUrl)}
3339
+ `);
3340
+ writeHqLanEndpoints(write, handle, void 0);
3341
+ return;
3342
+ }
3343
+ if (process.env.WRONGSTACK_HQ_SUPPRESS_STARTUP_SECRETS === "1") {
3344
+ write(`${terminalText("Browser endpoint:", "blue")} ${terminalLink(browserUrl)}
3345
+ `);
3346
+ write(
3347
+ `${terminalText("Mobile endpoint:", "blue")} ${terminalLink(mobileUrl.toString())} (password required)
3348
+ `
3349
+ );
3468
3350
  write(`${terminalText("Client endpoint:", "blue")} ${terminalLink(clientUrl)}
3351
+ `);
3352
+ write(`Startup credentials suppressed; auth state is stored in ${startup.dataDir}.
3469
3353
  `);
3470
3354
  writeHqLanEndpoints(write, handle, void 0);
3471
3355
  return;
3472
3356
  }
3357
+ const firstRunMobileUrl = new URL(startup.browserUrl);
3358
+ firstRunMobileUrl.pathname = "/mobile";
3359
+ firstRunMobileUrl.search = "";
3360
+ firstRunMobileUrl.hash = "";
3473
3361
  write(`${terminalText("Browser endpoint:", "blue")} ${terminalLink(startup.browserUrl)}
3474
3362
  `);
3363
+ write(
3364
+ `${terminalText("Mobile endpoint:", "blue")} ${terminalLink(firstRunMobileUrl.toString())} (password required)
3365
+ `
3366
+ );
3475
3367
  write(`${terminalText("Client endpoint:", "blue")} ${terminalLink(startup.clientUrl)}
3476
3368
  `);
3477
3369
  if (startup.createdAuth) {
@@ -3489,7 +3381,7 @@ HQ auth loaded from ${startup.dataDir}
3489
3381
  if (startup.clientEnv.WRONGSTACK_HQ_TOKEN) {
3490
3382
  write(` WRONGSTACK_HQ_TOKEN=${startup.clientEnv.WRONGSTACK_HQ_TOKEN}
3491
3383
  `);
3492
- write(`Credentials are stored in ${path6.join(startup.dataDir, "auth.json")}
3384
+ write(`Credentials are stored in ${path5.join(startup.dataDir, "auth.json")}
3493
3385
  `);
3494
3386
  }
3495
3387
  writeHqLanEndpoints(write, handle, void 0);
@@ -3567,7 +3459,7 @@ function createHqServerShutdown(params) {
3567
3459
  }
3568
3460
 
3569
3461
  // src/hq-server/upgrade-handler.ts
3570
- import { isLoopbackHost as isLoopbackHost3, hqTokenVerifier as hqTokenVerifier2 } from "@wrongstack/core/hq";
3462
+ import { hqTokenVerifier as hqTokenVerifier2, isLoopbackHost as isLoopbackHost3 } from "@wrongstack/core/hq";
3571
3463
 
3572
3464
  // src/hq-server/ws.ts
3573
3465
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -4345,6 +4237,15 @@ function handleHqUpgrade(req, socket, head, deps) {
4345
4237
  socket.destroy();
4346
4238
  return;
4347
4239
  }
4240
+ if (deps.ipAllowlist && !deps.ipAllowlist.allows(resolveSocketAddress(req))) {
4241
+ socket.write(
4242
+ "HTTP/1.1 403 Forbidden\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n" + JSON.stringify({
4243
+ error: { code: "IP_NOT_ALLOWED", message: "Source IP is not allowed." }
4244
+ })
4245
+ );
4246
+ socket.destroy();
4247
+ return;
4248
+ }
4348
4249
  if (!hasTrustedBrowserOrigin2(
4349
4250
  req,
4350
4251
  deps.host,
@@ -4471,6 +4372,7 @@ async function startHqServer(options = {}) {
4471
4372
  }
4472
4373
  async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
4473
4374
  const trustBoundary = options.trustBoundary ?? createCompatibilityTrustBoundary({ policyId: "hq-trusted-host-compat-v1" });
4375
+ const ipAllowlist = createHqIpAllowlist(options.ipAllowlist);
4474
4376
  const authFile = firstRunAuth.authFile;
4475
4377
  const reassessExposureFloor = (live) => {
4476
4378
  const exposure = assessHqExposure2({
@@ -4528,10 +4430,12 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4528
4430
  browserTokenMode: mutableAuth.browserTokens.size > 0,
4529
4431
  clientTokenMode: mutableAuth.clientTokens.size > 0,
4530
4432
  passwordMode: mutableAuth.passwordHash !== void 0,
4433
+ ipAllowlistActive: ipAllowlist !== void 0,
4434
+ ipAllowlistRules: ipAllowlist?.entries.length ?? 0,
4531
4435
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4532
4436
  })
4533
4437
  );
4534
- return new Promise((resolve2, reject) => {
4438
+ return new Promise((resolve, reject) => {
4535
4439
  const trustedPublicOrigins = /* @__PURE__ */ new Set();
4536
4440
  let listeningPort = port;
4537
4441
  const clients = /* @__PURE__ */ new Map();
@@ -4577,13 +4481,13 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4577
4481
  const persistence = createHqPersistence(dataDir);
4578
4482
  const auditLog = new HqCommandAuditLog(1e3, (entry) => persistence.commandLog.append(entry));
4579
4483
  void (async () => {
4580
- const projectsDir = path7.join(path7.dirname(dataDir), "projects");
4581
- const entries = await fs5.readdir(projectsDir, { withFileTypes: true }).catch(() => []);
4484
+ const projectsDir = path6.join(path6.dirname(dataDir), "projects");
4485
+ const entries = await fs4.readdir(projectsDir, { withFileTypes: true }).catch(() => []);
4582
4486
  await Promise.all(
4583
4487
  entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
4584
- const projectDir = path7.join(projectsDir, entry.name);
4585
- const metadataPath = path7.join(projectDir, ".mailbox-server.json");
4586
- const ownerPid = await fs5.readFile(metadataPath, "utf8").then((raw) => JSON.parse(raw).pid).catch(() => void 0);
4488
+ const projectDir = path6.join(projectsDir, entry.name);
4489
+ const metadataPath = path6.join(projectDir, ".mailbox-server.json");
4490
+ const ownerPid = await fs4.readFile(metadataPath, "utf8").then((raw) => JSON.parse(raw).pid).catch(() => void 0);
4587
4491
  if (ownerPid === void 0) return;
4588
4492
  let ownerAlive = false;
4589
4493
  try {
@@ -4593,7 +4497,7 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4593
4497
  ownerAlive = false;
4594
4498
  }
4595
4499
  if (!ownerAlive) {
4596
- await fs5.rm(metadataPath, { force: true }).catch(() => {
4500
+ await fs4.rm(metadataPath, { force: true }).catch(() => {
4597
4501
  });
4598
4502
  return;
4599
4503
  }
@@ -4722,6 +4626,7 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4722
4626
  getTokenStats: () => authState.tokenStats(),
4723
4627
  applyAuthFile: (next) => authState.apply(next),
4724
4628
  trustedProxyHops: options.trustedProxyHops ?? 0,
4629
+ ipAllowlist,
4725
4630
  bootstrapStore
4726
4631
  };
4727
4632
  const handleRequest = createHqRouter(routerDeps);
@@ -4757,7 +4662,8 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4757
4662
  persistence,
4758
4663
  auditLog,
4759
4664
  snapshotBroadcaster,
4760
- wss
4665
+ wss,
4666
+ ipAllowlist
4761
4667
  };
4762
4668
  httpServer.on("upgrade", (req, socket, head) => {
4763
4669
  handleHqUpgrade(req, socket, head, upgradeDeps);
@@ -4907,7 +4813,7 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
4907
4813
  port: actualPort,
4908
4814
  firstRunSetup: startupInfo
4909
4815
  });
4910
- resolve2(handle);
4816
+ resolve(handle);
4911
4817
  })();
4912
4818
  });
4913
4819
  });
@@ -4926,4 +4832,4 @@ export {
4926
4832
  startHqServer,
4927
4833
  HqInsecureExposureError2 as HqInsecureExposureError
4928
4834
  };
4929
- //# sourceMappingURL=chunk-C2E24IXZ.js.map
4835
+ //# sourceMappingURL=chunk-3I5ZJXM5.js.map