@wrongstack/cli 1.0.3 → 1.0.6

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 (83) hide show
  1. package/dist/{acp-JK4ZQO3R.js → acp-GOQURNC2.js} +128 -43
  2. package/dist/acp-agent-list.d.ts +14 -0
  3. package/dist/acp-hq-telemetry.d.ts +9 -2
  4. package/dist/acp-mcp-servers.d.ts +49 -0
  5. package/dist/acp-server-agent.d.ts +18 -6
  6. package/dist/{auth-XD3GN5RJ.js → auth-WBXXIQUO.js} +3 -3
  7. package/dist/boot/short-circuit-desktop.d.ts +1 -0
  8. package/dist/boot/short-circuit-hq.d.ts +2 -0
  9. package/dist/boot/tool-registry.d.ts +18 -1
  10. package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
  11. package/dist/{chunk-ONOT5OJ2.js → chunk-5A5GCSJC.js} +1 -1
  12. package/dist/chunk-5EA6OTLJ.js +239 -0
  13. package/dist/{chunk-O4A3HHKD.js → chunk-672UXYL3.js} +2 -2
  14. package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
  15. package/dist/chunk-AUQDGU5D.js +32 -0
  16. package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
  17. package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
  18. package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
  19. package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
  20. package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
  21. package/dist/{chunk-4NHD5OXJ.js → chunk-NR7BD73C.js} +5 -5
  22. package/dist/{chunk-C2E24IXZ.js → chunk-PQZPHGGD.js} +272 -299
  23. package/dist/chunk-Q7E3BPDU.js +62 -0
  24. package/dist/{chunk-IUEKNHPT.js → chunk-QDQPF4PB.js} +11 -11
  25. package/dist/{chunk-VFCJOWJB.js → chunk-VPS2SJW4.js} +4 -5
  26. package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
  27. package/dist/{cli-context-Q6QP534K.js → cli-context-BYKJDAFF.js} +129 -25
  28. package/dist/{cli-main-JQXCDZEA.js → cli-main-5WZBH3WA.js} +1345 -800
  29. package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
  30. package/dist/{execution-KH7WJNAX.js → execution-Z6A26EG6.js} +132 -40
  31. package/dist/execution-specialist-trigger.d.ts +28 -0
  32. package/dist/fleet/acp-live-progress.d.ts +19 -0
  33. package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
  34. package/dist/fleet/host-acp.d.ts +14 -2
  35. package/dist/fleet/host.d.ts +1 -0
  36. package/dist/{hq-IG3YRGCO.js → hq-V4PHEETW.js} +127 -8
  37. package/dist/hq-command-controller.d.ts +31 -2
  38. package/dist/hq-server/auth-state.d.ts +6 -0
  39. package/dist/hq-server/auth.d.ts +10 -1
  40. package/dist/hq-server/client-address.d.ts +2 -0
  41. package/dist/hq-server/ip-allowlist.d.ts +16 -0
  42. package/dist/hq-server/preflight.d.ts +1 -0
  43. package/dist/hq-server/routes/auth/common.d.ts +34 -0
  44. package/dist/hq-server/routes/auth/password-routes.d.ts +4 -2
  45. package/dist/hq-server/routes.d.ts +5 -4
  46. package/dist/hq-server/secret-input.d.ts +7 -0
  47. package/dist/hq-server/types.d.ts +21 -2
  48. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  49. package/dist/hq-server/utils.d.ts +6 -0
  50. package/dist/{hq-server-OILU3NPI.js → hq-server-MZ7DV6X7.js} +4 -3
  51. package/dist/hq-server.d.ts +4 -0
  52. package/dist/hq-service-L5INQFM4.js +507 -0
  53. package/dist/hq-service.d.ts +22 -0
  54. package/dist/index.js +9 -6
  55. package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
  56. package/dist/live-settings-input.d.ts +2 -0
  57. package/dist/{mcp-HOMLC4WE.js → mcp-FX7TKPTV.js} +10 -8
  58. package/dist/mcp-serve.d.ts +31 -0
  59. package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
  60. package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
  61. package/dist/projects-LMNAGGEU.js +36 -0
  62. package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
  63. package/dist/short-circuit-flags-KBTYSJTW.js +10 -0
  64. package/dist/slash-commands/acp.d.ts +3 -0
  65. package/dist/slash-commands/command-context.d.ts +22 -0
  66. package/dist/slash-commands/ensemble.d.ts +1 -1
  67. package/dist/slash-commands/profile.d.ts +7 -0
  68. package/dist/subcommands/handlers/hq.d.ts +1 -0
  69. package/dist/subcommands/handlers/projects.d.ts +14 -0
  70. package/dist/{subcommands-CUTGBDKP.js → subcommands-DEYSYHIM.js} +2 -2
  71. package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
  72. package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
  73. package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
  74. package/dist/wiring/codebase-embeddings.d.ts +2 -2
  75. package/dist/wiring/command-host-state.d.ts +7 -0
  76. package/dist/wiring/pipeline.d.ts +2 -0
  77. package/dist/wiring/provider-runtime-setup.d.ts +1 -1
  78. package/dist/wiring/session-command-handlers.d.ts +2 -1
  79. package/dist/wiring/slash-commands.d.ts +1 -0
  80. package/package.json +28 -28
  81. package/dist/chunk-Q5GTM25S.js +0 -17
  82. package/dist/projects-L6C6XLDY.js +0 -37
  83. package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
@@ -0,0 +1,239 @@
1
+
2
+ // src/hq-server/audit-actor.ts
3
+ import * as os from "node:os";
4
+ function resolveAuditActor() {
5
+ const user = (process.env.USERNAME ?? process.env.USER ?? "").toString();
6
+ try {
7
+ const host = os.hostname();
8
+ return user ? `${user}@${host}` : `cli@${host}`;
9
+ } catch {
10
+ return user ? `${user}@unknown` : "cli";
11
+ }
12
+ }
13
+
14
+ // src/hq-server/utils.ts
15
+ import * as fs from "node:fs/promises";
16
+ import * as os2 from "node:os";
17
+ import * as path from "node:path";
18
+ import { resolveHqDataDir } from "@wrongstack/core/hq";
19
+ import { isSafeSessionId as coreIsSafeSessionId } from "@wrongstack/core/utils";
20
+ import { sanitizeApiError, scrubErrorDetail } from "@wrongstack/core/security";
21
+ var TRANSCRIPT_RING_MAX = 4e3;
22
+ var MAX_TRANSCRIPT_SESSIONS = 400;
23
+ var MAX_AGENT_RINGS = 800;
24
+ function evictOldest(map, max) {
25
+ while (map.size > max) {
26
+ const oldest = map.keys().next().value;
27
+ if (oldest === void 0) break;
28
+ map.delete(oldest);
29
+ }
30
+ }
31
+ function agentRingKey(sessionId, subId) {
32
+ return sessionId ? `${sessionId}::${subId}` : subId;
33
+ }
34
+ function agentMessageToEntry(p) {
35
+ const kind = typeof p["kind"] === "string" ? p["kind"] : "text";
36
+ const role = kind === "thinking" ? "thinking" : kind === "tool_use" || kind === "tool_result" ? "tool" : kind === "error" ? "error" : kind === "status" || kind === "system" ? "system" : "assistant";
37
+ return {
38
+ ts: typeof p["ts"] === "string" ? p["ts"] : (/* @__PURE__ */ new Date()).toISOString(),
39
+ role,
40
+ text: typeof p["content"] === "string" ? p["content"] : "",
41
+ ...typeof p["toolName"] === "string" ? { tool: p["toolName"] } : {},
42
+ ...kind === "error" ? { isError: true } : {}
43
+ };
44
+ }
45
+ async function readLocalSubagentTranscript(sessionId, subagentId) {
46
+ try {
47
+ const { getSessionRegistry } = await import("@wrongstack/core/storage");
48
+ const { resolveWstackPaths, sessionScopedPath } = await import("@wrongstack/core/utils");
49
+ const globalRoot = path.dirname(resolveHqDataDir());
50
+ const registry = getSessionRegistry(globalRoot);
51
+ const entry = await registry.get(sessionId).catch(() => null);
52
+ if (!entry) return null;
53
+ const paths = resolveWstackPaths({ projectRoot: entry.projectRoot, globalRoot });
54
+ const sessionDir = sessionScopedPath(paths.projectSessions, sessionId, "");
55
+ if (!isSafePathSegment(subagentId)) return null;
56
+ const transcriptsRoot = path.resolve(sessionDir, "subagents", "transcripts");
57
+ const file = path.resolve(transcriptsRoot, subagentId, "transcript.jsonl");
58
+ if (!file.startsWith(transcriptsRoot + path.sep)) return null;
59
+ const raw = await fs.readFile(file, "utf8").catch(() => null);
60
+ if (raw === null) return null;
61
+ const out = [];
62
+ for (const line of raw.split("\n")) {
63
+ const trimmed = line.trim();
64
+ if (trimmed.length === 0) continue;
65
+ try {
66
+ out.push(agentMessageToEntry(JSON.parse(trimmed)));
67
+ } catch {
68
+ }
69
+ }
70
+ return out;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ function decodePathSegment(value) {
76
+ let decoded;
77
+ try {
78
+ decoded = decodeURIComponent(value);
79
+ } catch {
80
+ return null;
81
+ }
82
+ return isSafePathSegment(decoded) ? decoded : null;
83
+ }
84
+ function decodeSessionId(value) {
85
+ let decoded;
86
+ try {
87
+ decoded = decodeURIComponent(value);
88
+ } catch {
89
+ return null;
90
+ }
91
+ return isSafeSessionId(decoded) ? decoded : null;
92
+ }
93
+ var MAX_PATH_SEGMENT_LENGTH = 256;
94
+ function isSafePathSegment(value) {
95
+ if (value.length === 0 || value.length > MAX_PATH_SEGMENT_LENGTH) return false;
96
+ if (value === "." || value === "..") return false;
97
+ if (value.includes("/") || value.includes("\\")) return false;
98
+ if (value.includes("\0")) return false;
99
+ if (value.includes(":")) return false;
100
+ return true;
101
+ }
102
+ function isSafeSessionId(value) {
103
+ return coreIsSafeSessionId(value);
104
+ }
105
+ function displayHost(host) {
106
+ return host === "0.0.0.0" ? "127.0.0.1" : host;
107
+ }
108
+ var RequestBodyTooLargeError = class extends Error {
109
+ };
110
+ function readRequestBody(req, maxBytes = 1e6) {
111
+ return new Promise((resolve2, reject) => {
112
+ const chunks = [];
113
+ let size = 0;
114
+ let settled = false;
115
+ req.on("data", (chunk) => {
116
+ if (settled) return;
117
+ size += chunk.length;
118
+ if (size > maxBytes) {
119
+ settled = true;
120
+ chunks.length = 0;
121
+ reject(new RequestBodyTooLargeError("request body too large"));
122
+ return;
123
+ }
124
+ chunks.push(chunk);
125
+ });
126
+ req.on("end", () => {
127
+ if (!settled) resolve2(Buffer.concat(chunks).toString("utf8"));
128
+ });
129
+ req.on("error", (error) => {
130
+ if (!settled) reject(error);
131
+ });
132
+ });
133
+ }
134
+ function writeInvalidBody(res, error) {
135
+ const tooLarge = error instanceof RequestBodyTooLargeError;
136
+ res.writeHead(tooLarge ? 413 : 400, { "Content-Type": "application/json" });
137
+ res.end(JSON.stringify({ error: tooLarge ? "request body too large" : "invalid json body" }));
138
+ }
139
+ function buildHttpUrl(host, port, token) {
140
+ const url = new URL(`http://${displayHost(host)}:${port}/`);
141
+ if (token) url.searchParams.set("token", token);
142
+ return url.toString();
143
+ }
144
+ function buildBootstrapHttpUrl(host, port, bootstrapCode) {
145
+ const url = new URL(`http://${displayHost(host)}:${port}/`);
146
+ url.hash = `bootstrap=${bootstrapCode}`;
147
+ return url.toString();
148
+ }
149
+ function buildClientWsUrl(host, port, token) {
150
+ const url = new URL(`ws://${displayHost(host)}:${port}/ws/client`);
151
+ if (token) url.searchParams.set("token", token);
152
+ return url.toString();
153
+ }
154
+ function normalizeHqPublicOrigin(value) {
155
+ const parsed = new URL(value.trim());
156
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.pathname !== "" && parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
157
+ throw new TypeError(
158
+ "HQ public URL must be an exact HTTPS origin without a path or credentials."
159
+ );
160
+ }
161
+ return parsed.origin;
162
+ }
163
+ function hqRuntimeMarkerPath(dataDir) {
164
+ return path.join(dataDir, "runtime.json");
165
+ }
166
+ function lanIPv4Addresses() {
167
+ const out = [];
168
+ try {
169
+ const ifaces = os2.networkInterfaces();
170
+ for (const name of Object.keys(ifaces)) {
171
+ for (const ni of ifaces[name] ?? []) {
172
+ if (ni.family === "IPv4" && !ni.internal) out.push(ni.address);
173
+ }
174
+ }
175
+ } catch {
176
+ }
177
+ return out;
178
+ }
179
+ function truncateHqSummary(value, maxLength) {
180
+ if (typeof value !== "string" || value.length === 0) return void 0;
181
+ if (value.length <= maxLength) return value;
182
+ const suffix = `\u2026[truncated:${value.length - maxLength}]`;
183
+ if (maxLength <= suffix.length) return "\u2026";
184
+ const prefixLen = maxLength - suffix.length;
185
+ return `${value.slice(0, prefixLen)}${suffix}`;
186
+ }
187
+ function hqMachineKey(hostname2, machineId) {
188
+ const hn = hostname2?.trim();
189
+ return hn ? `host:${hn.toLowerCase()}` : `mid:${machineId || "local"}`;
190
+ }
191
+ function recordTimeseriesSignal(persistence, event) {
192
+ try {
193
+ if (event.type === "session.usage") {
194
+ const p = event.payload;
195
+ persistence.timeseries.record({
196
+ ts: Date.parse(event.timestamp) || Date.now(),
197
+ ...typeof p.costUsd === "number" ? { costUsd: p.costUsd } : {},
198
+ ...typeof p.inputTokens === "number" ? { inputTokens: p.inputTokens } : {},
199
+ ...typeof p.outputTokens === "number" ? { outputTokens: p.outputTokens } : {},
200
+ ...typeof p.model === "string" && p.model.length > 0 ? { model: p.model } : {},
201
+ ...typeof p.provider === "string" && p.provider.length > 0 ? { provider: p.provider } : {},
202
+ ...typeof p.cacheRead === "number" ? { cacheRead: p.cacheRead } : {},
203
+ ...typeof p.cacheWrite === "number" ? { cacheWrite: p.cacheWrite } : {}
204
+ });
205
+ } else if (event.type === "tool.completed") {
206
+ persistence.timeseries.record({
207
+ ts: Date.parse(event.timestamp) || Date.now(),
208
+ toolCalls: 1
209
+ });
210
+ }
211
+ } catch {
212
+ }
213
+ }
214
+
215
+ export {
216
+ resolveAuditActor,
217
+ TRANSCRIPT_RING_MAX,
218
+ MAX_TRANSCRIPT_SESSIONS,
219
+ MAX_AGENT_RINGS,
220
+ evictOldest,
221
+ agentRingKey,
222
+ agentMessageToEntry,
223
+ readLocalSubagentTranscript,
224
+ decodePathSegment,
225
+ decodeSessionId,
226
+ readRequestBody,
227
+ writeInvalidBody,
228
+ buildHttpUrl,
229
+ buildBootstrapHttpUrl,
230
+ buildClientWsUrl,
231
+ normalizeHqPublicOrigin,
232
+ hqRuntimeMarkerPath,
233
+ lanIPv4Addresses,
234
+ truncateHqSummary,
235
+ hqMachineKey,
236
+ recordTimeseriesSignal,
237
+ sanitizeApiError
238
+ };
239
+ //# sourceMappingURL=chunk-5EA6OTLJ.js.map
@@ -1,6 +1,6 @@
1
1
 
2
2
  // src/preflight.ts
3
- import { ensureSessionShell } from "@wrongstack/tools";
3
+ import { ensureSessionShell } from "@wrongstack/tools/session-shell";
4
4
 
5
5
  // src/cli-update-notice.ts
6
6
  import { writeErr } from "@wrongstack/core/utils";
@@ -62,4 +62,4 @@ export {
62
62
  applySessionShellDefault,
63
63
  runPreflight
64
64
  };
65
- //# sourceMappingURL=chunk-O4A3HHKD.js.map
65
+ //# sourceMappingURL=chunk-672UXYL3.js.map
@@ -67,12 +67,12 @@ var helpCmd = async (_args, deps) => {
67
67
  color.bold("Common flags"),
68
68
  " --yolo / --no-yolo Force auto-approval on or off at startup",
69
69
  " --confirm-destructive Deprecated \u2014 YOLO no longer prompts by destructiveness",
70
- " --yolo-destructive Deprecated compatibility flag; YOLO no longer prompts by destructiveness",
70
+ " --yolo-destructive Let YOLO run every destructive kind it is allowed to (see /yolo confirm)",
71
71
  " --tui / --no-tui Force or disable TUI mode",
72
72
  " --mouse Full mouse mode in the TUI (in-app scroll + clickable UI)",
73
73
  " --desktop Open WrongStack Desktop (requires @wrongstack/desktop)",
74
- " --hq [--host <h>] [--port <n>] [--password <secret>] [--tunnel] [--open]",
75
- " Start HQ; --tunnel publishes a temporary HTTPS URL",
74
+ " --hq [--host <h>] [--port <n>] [--password <secret>] [--hq-allowlist <ip,cidr,...>] [--tunnel] [--hq-public-url <https-origin>] [--open]",
75
+ " Start HQ; publish through a temporary tunnel or persistent TLS proxy",
76
76
  " --webui [--host <h>] [--port <n>] [--webui-token <t>] [--open]",
77
77
  " [--webui-public-url <url>] [--webui-public-ws-url <url>] [--webui-require-token]",
78
78
  " Serve the browser UI + WS bridge (prints a token URL,",
@@ -93,4 +93,4 @@ export {
93
93
  versionCmd,
94
94
  helpCmd
95
95
  };
96
- //# sourceMappingURL=chunk-FPIWTJ2G.js.map
96
+ //# sourceMappingURL=chunk-6WRKAACA.js.map
@@ -0,0 +1,32 @@
1
+
2
+ // src/hq-server/secret-input.ts
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ var MAX_PASSWORD_FILE_BYTES = 4096;
6
+ async function resolveHqPasswordInput(options = {}) {
7
+ const environment = options.environment ?? process.env;
8
+ const direct = options.explicitPassword ?? environment.WRONGSTACK_HQ_PASSWORD;
9
+ const file = environment.WRONGSTACK_HQ_PASSWORD_FILE;
10
+ if (direct !== void 0 && file !== void 0) {
11
+ throw new Error(
12
+ "Set only one of --password/WRONGSTACK_HQ_PASSWORD or WRONGSTACK_HQ_PASSWORD_FILE."
13
+ );
14
+ }
15
+ if (file === void 0) return direct;
16
+ if (!path.isAbsolute(file)) {
17
+ throw new Error("WRONGSTACK_HQ_PASSWORD_FILE must be an absolute path.");
18
+ }
19
+ const stat2 = await fs.stat(file);
20
+ if (!stat2.isFile()) throw new Error("WRONGSTACK_HQ_PASSWORD_FILE must point to a regular file.");
21
+ if (stat2.size > MAX_PASSWORD_FILE_BYTES) {
22
+ throw new Error(`WRONGSTACK_HQ_PASSWORD_FILE exceeds ${MAX_PASSWORD_FILE_BYTES} bytes.`);
23
+ }
24
+ const raw = await fs.readFile(file, "utf8");
25
+ if (raw.includes("\0")) throw new Error("WRONGSTACK_HQ_PASSWORD_FILE contains a NUL byte.");
26
+ return raw.replace(/\r?\n$/, "");
27
+ }
28
+
29
+ export {
30
+ resolveHqPasswordInput
31
+ };
32
+ //# sourceMappingURL=chunk-AUQDGU5D.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startProxyProbe
3
- } from "./chunk-JHPXJRRI.js";
3
+ } from "./chunk-4XCBEUBX.js";
4
4
 
5
5
  // src/wiring/proxy-wiring.ts
6
6
  import { applyProxyConfig } from "@wrongstack/core/wiring/proxy-rewrite";
@@ -53,4 +53,4 @@ export {
53
53
  bootstrapWrongProxy,
54
54
  shutdownWrongProxy
55
55
  };
56
- //# sourceMappingURL=chunk-VOTTENR5.js.map
56
+ //# sourceMappingURL=chunk-CDY4D5QK.js.map
@@ -12,7 +12,6 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
12
12
  "version",
13
13
  "yes",
14
14
  "no-banner",
15
- "no-features",
16
15
  "tui",
17
16
  "no-tui",
18
17
  // Recovery switches. `--recover` reopens the most recent session with no
@@ -64,6 +63,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
64
63
  "pnpm",
65
64
  "yarn",
66
65
  "bun",
66
+ // `wstack hq service install` toggles.
67
+ "no-auto-update",
67
68
  // `wstack acp` booleans. Without these, parseArgs treats the value after
68
69
  // `--fs`/`--echo` as the flag's value and eats a following positional
69
70
  // (e.g. the agent list in `acp bench --fs gemini-cli,codex-cli`).
@@ -237,4 +238,4 @@ export {
237
238
  parseAuthFlags,
238
239
  parseSpawnFlags
239
240
  };
240
- //# sourceMappingURL=chunk-E6RFXHWD.js.map
241
+ //# sourceMappingURL=chunk-EK2P53GL.js.map
@@ -1043,9 +1043,9 @@ var helpTable = {
1043
1043
  // -- HQ (dashboard server) ──────────────────────────────────────────
1044
1044
  hq: {
1045
1045
  name: "hq",
1046
- title: "wstack hq \u2014 start HQ command center or manage tokens",
1047
- description: "Start the HQ server \u2014 a web dashboard for monitoring sessions, fleet status, and agent activity across projects \u2014 or manage browser and client authentication tokens.",
1048
- usage: "wstack hq [serve] [--port <n>] [--password <secret>] [--tunnel] [--open] | wstack hq token [create|list|revoke]",
1046
+ title: "wstack hq \u2014 serve HQ, manage authentication, or install its system service",
1047
+ description: "Start the HQ server \u2014 a web dashboard for monitoring sessions, fleet status, and agent activity across projects; manage browser/client authentication, or install the persistent Ubuntu systemd service.",
1048
+ usage: "wstack hq [serve] [...] | wstack hq token [create|list|revoke] | wstack hq service [install|status|update|uninstall]",
1049
1049
  subcommands: [
1050
1050
  { name: "serve", description: "Start the HQ dashboard server (default)." },
1051
1051
  {
@@ -1053,7 +1053,17 @@ var helpTable = {
1053
1053
  description: "Mint a browser or client authentication token."
1054
1054
  },
1055
1055
  { name: "token list", description: "List active HQ authentication tokens." },
1056
- { name: "token revoke <id>", description: "Revoke an issued HQ authentication token." }
1056
+ { name: "token revoke <id>", description: "Revoke an issued HQ authentication token." },
1057
+ {
1058
+ name: "service install",
1059
+ description: "Install an always-on systemd service and guarded automatic updater."
1060
+ },
1061
+ { name: "service status", description: "Inspect the HQ service and update timer." },
1062
+ { name: "service update", description: "Run the guarded update job immediately." },
1063
+ {
1064
+ name: "service uninstall",
1065
+ description: "Remove systemd units while preserving HQ data and secrets."
1066
+ }
1057
1067
  ],
1058
1068
  seeAlso: "wstack sessions fleet (CLI equivalent for fleet status)"
1059
1069
  },
@@ -1202,4 +1212,4 @@ export {
1202
1212
  subcommandsWithFocusedHelp,
1203
1213
  renderGenericHelp
1204
1214
  };
1205
- //# sourceMappingURL=chunk-MXEJEYT7.js.map
1215
+ //# sourceMappingURL=chunk-JX6KL7OF.js.map
@@ -4,6 +4,104 @@ import {
4
4
  isCodexCatalogModel
5
5
  } from "./chunk-SIGUWXAA.js";
6
6
 
7
+ // src/acp-agent-list.ts
8
+ import { REGISTRY_ID_ALIASES } from "@wrongstack/acp";
9
+ function reverseAliases(aliases = REGISTRY_ID_ALIASES) {
10
+ const out = {};
11
+ for (const [ours, theirs] of Object.entries(aliases)) {
12
+ if (ours !== theirs) out[theirs] = ours;
13
+ }
14
+ return out;
15
+ }
16
+ function distKind(command) {
17
+ if (command === "npx") return "npx";
18
+ if (command === "uvx") return "uvx";
19
+ return "local";
20
+ }
21
+ function localProbe(registryId, alias, detectedById) {
22
+ return detectedById.get(registryId) ?? (alias ? detectedById.get(alias) : void 0);
23
+ }
24
+ function formatRegistryRow(agent, alias, local, idWidth) {
25
+ const kind = distKind(agent.acp.command);
26
+ const aliasBit = alias ? ` (also ${alias})` : "";
27
+ if (kind === "local") {
28
+ if (local?.installed) {
29
+ const ver = local.version?.split("\n")[0]?.trim();
30
+ const note2 = ver ? ` ${ver}` : " on PATH";
31
+ return ` \u2713 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit}${note2}`;
32
+ }
33
+ const reason = local?.reason ?? "not on PATH";
34
+ return ` \u2717 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit} (${reason})`;
35
+ }
36
+ const pkg = (agent.acp.args ?? []).find((a) => a !== "-y" && !a.startsWith("--"));
37
+ const note = pkg ? `${kind} ${pkg}` : kind;
38
+ return ` \u25B8 ${agent.id.padEnd(idWidth)} ${agent.displayName}${aliasBit} ${note}`;
39
+ }
40
+ function formatAcpAgentList(opts) {
41
+ const { live, detected } = opts;
42
+ const detectedById = new Map(detected.map((d) => [d.id, d]));
43
+ if (live && live.agents.length > 0) {
44
+ const aliasOf = reverseAliases();
45
+ const when = live.fetchedAt ? live.fetchedAt.slice(0, 19).replace("T", " ") : "locally";
46
+ const rows = [...live.agents].sort((a, b) => {
47
+ const rank = (command) => {
48
+ const k = distKind(command);
49
+ return k === "local" ? 0 : k === "uvx" ? 1 : 2;
50
+ };
51
+ const r = rank(a.acp.command) - rank(b.acp.command);
52
+ return r !== 0 ? r : a.id.localeCompare(b.id);
53
+ });
54
+ const idWidth = Math.max(16, ...rows.map((a) => a.id.length));
55
+ const lines2 = [
56
+ `Official ACP registry \u2014 ${live.agents.length} agents (synced ${when}).`,
57
+ "Spawn ids below are what `/acp <id> <task>` and `/acp <id> --bg` accept.",
58
+ ""
59
+ ];
60
+ for (const agent of rows) {
61
+ const alias = aliasOf[agent.id];
62
+ lines2.push(
63
+ formatRegistryRow(agent, alias, localProbe(agent.id, alias, detectedById), idWidth)
64
+ );
65
+ }
66
+ const leftover = detected.filter((d) => {
67
+ if (live.byId[d.id]) return false;
68
+ const mapped = REGISTRY_ID_ALIASES[d.id];
69
+ return !(mapped && live.byId[mapped]);
70
+ });
71
+ if (leftover.length > 0) {
72
+ lines2.push("");
73
+ lines2.push("Bundled extras (not in the official registry \u2014 experimental):");
74
+ for (const d of leftover) {
75
+ const mark = d.installed ? "\u2713" : "\u2717";
76
+ const extra = d.installed ? d.version?.split("\n")[0] ?? "" : d.reason ?? "not installed";
77
+ lines2.push(` ${mark} ${d.id.padEnd(16)} ${d.displayName} (${extra})`);
78
+ }
79
+ }
80
+ lines2.push("");
81
+ lines2.push(
82
+ "Spawn: /acp <id> <task> Background: /acp <id> --bg <task> Probe: /acp probe <id>"
83
+ );
84
+ return lines2.join("\n");
85
+ }
86
+ const installed = detected.filter((a) => a.installed);
87
+ const missing = detected.filter((a) => !a.installed);
88
+ const lines = [
89
+ "Bundled offline catalog (not the official registry).",
90
+ "Run `/acp sync` to replace this list with agentclientprotocol/registry.",
91
+ ""
92
+ ];
93
+ for (const a of installed) {
94
+ const ver = a.version ? ` (${a.version.split("\n")[0]})` : "";
95
+ lines.push(` \u2713 ${a.id.padEnd(16)} ${a.displayName}${ver}`);
96
+ }
97
+ for (const a of missing) {
98
+ lines.push(` \u2717 ${a.id.padEnd(16)} ${a.displayName} (${a.reason ?? "not installed"})`);
99
+ }
100
+ lines.push("");
101
+ lines.push(`${installed.length} of ${detected.length} bundled agents installed locally.`);
102
+ return lines.join("\n");
103
+ }
104
+
7
105
  // src/acp-registry-cache.ts
8
106
  import * as fs from "node:fs/promises";
9
107
  import * as path from "node:path";
@@ -261,7 +359,7 @@ async function setupProvider(params) {
261
359
  } catch (err) {
262
360
  throw new ConfigError({
263
361
  message: `Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
264
- Try \`wstack models refresh\` once you have network access, or run with --no-features.`,
362
+ Try \`wstack models refresh\` once you have network access.`,
265
363
  code: "CONFIG_INVALID",
266
364
  context: { phase: "registry-build", provider: config.provider },
267
365
  cause: err
@@ -304,6 +402,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
304
402
  }
305
403
 
306
404
  export {
405
+ formatAcpAgentList,
307
406
  loadCachedAcpRegistry,
308
407
  refreshAcpRegistry,
309
408
  resolveProviderCfg,
@@ -311,4 +410,4 @@ export {
311
410
  setupProvider,
312
411
  createGracefulShutdown
313
412
  };
314
- //# sourceMappingURL=chunk-GMY45LO6.js.map
413
+ //# sourceMappingURL=chunk-KEPABQYT.js.map
@@ -1,5 +1,42 @@
1
1
 
2
2
  // src/hq-command-controller.ts
3
+ function createProjectKanbanTransitionHandler(projectRoot) {
4
+ return async (input) => {
5
+ const { transitionTask } = await import("@wrongstack/kanban");
6
+ const result = await transitionTask(projectRoot, input.boardId, input.taskId, {
7
+ to: input.to,
8
+ actor: "hq-operator",
9
+ comment: input.comment,
10
+ sessionId: input.sessionId ?? "hq-mobile"
11
+ });
12
+ if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
13
+ return `task ${input.taskId} transitioned to ${input.to}`;
14
+ };
15
+ }
16
+ function createProjectKanbanAssignHandler(projectRoot) {
17
+ return async (input) => {
18
+ const { assignTask } = await import("@wrongstack/kanban");
19
+ const result = await assignTask(
20
+ projectRoot,
21
+ input.boardId,
22
+ input.taskId,
23
+ {
24
+ agentId: input.agentId,
25
+ name: input.assignee,
26
+ assignee: input.assignee,
27
+ status: "assigned",
28
+ protectActiveAssignment: true
29
+ },
30
+ {
31
+ actor: "hq-operator",
32
+ note: input.comment,
33
+ sessionId: input.sessionId ?? "hq-mobile"
34
+ }
35
+ );
36
+ if (result === null) throw new Error(`kanban task not found: ${input.taskId}`);
37
+ return `task ${input.taskId} assigned to ${input.assignee}`;
38
+ };
39
+ }
3
40
  function createHqCommandDispatcher(controller) {
4
41
  return async (command) => {
5
42
  const cmd = command;
@@ -159,6 +196,45 @@ async function dispatch(controller, commandId, type, payload) {
159
196
  const subagentId = await controller.spawnAgent(role, task, maxIterations, sessionId);
160
197
  return { commandId, status: "completed", message: `spawned ${subagentId}` };
161
198
  }
199
+ case "kanban-transition": {
200
+ if (controller.kanbanTransition === void 0) {
201
+ return { commandId, status: "rejected", message: "kanban control is unavailable" };
202
+ }
203
+ const message = await controller.kanbanTransition({
204
+ boardId: payload["boardId"],
205
+ taskId: payload["taskId"],
206
+ to: payload["to"],
207
+ comment: payload["comment"],
208
+ ...sessionId !== void 0 ? { sessionId } : {}
209
+ });
210
+ return { commandId, status: "completed", message };
211
+ }
212
+ case "kanban-assign": {
213
+ if (controller.kanbanAssign === void 0) {
214
+ return { commandId, status: "rejected", message: "kanban assignment is unavailable" };
215
+ }
216
+ const message = await controller.kanbanAssign({
217
+ boardId: payload["boardId"],
218
+ taskId: payload["taskId"],
219
+ agentId: payload["agentId"],
220
+ assignee: payload["assignee"],
221
+ comment: payload["comment"],
222
+ ...sessionId !== void 0 ? { sessionId } : {}
223
+ });
224
+ return { commandId, status: "completed", message };
225
+ }
226
+ case "kanban-dispatch": {
227
+ if (controller.kanbanDispatch === void 0) {
228
+ return { commandId, status: "rejected", message: "kanban dispatch is unavailable" };
229
+ }
230
+ const message = await controller.kanbanDispatch({
231
+ boardId: payload["boardId"],
232
+ taskId: payload["taskId"],
233
+ comment: payload["comment"],
234
+ ...sessionId !== void 0 ? { sessionId } : {}
235
+ });
236
+ return { commandId, status: "completed", message };
237
+ }
162
238
  case "run-command": {
163
239
  if (!controller.allowRunCommand()) {
164
240
  return {
@@ -194,6 +270,8 @@ ${command}
194
270
  }
195
271
 
196
272
  export {
273
+ createProjectKanbanTransitionHandler,
274
+ createProjectKanbanAssignHandler,
197
275
  createHqCommandDispatcher
198
276
  };
199
- //# sourceMappingURL=chunk-IMWXXF4J.js.map
277
+ //# sourceMappingURL=chunk-KNMIBKLV.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  helpCmd,
3
3
  versionCmd
4
- } from "./chunk-FPIWTJ2G.js";
4
+ } from "./chunk-6WRKAACA.js";
5
5
  import {
6
6
  parseArgs
7
- } from "./chunk-E6RFXHWD.js";
7
+ } from "./chunk-EK2P53GL.js";
8
8
 
9
9
  // src/boot/short-circuit-flags.ts
10
10
  async function handleHelpVersionShortCircuit(argv) {
@@ -22,11 +22,11 @@ async function handleHelpVersionShortCircuit(argv) {
22
22
  }
23
23
  if (earlyFlags["help"] === true) {
24
24
  if (positional.length > 0) {
25
- const { subcommandNames } = await import("./subcommands-CUTGBDKP.js");
25
+ const { subcommandNames } = await import("./subcommands-DEYSYHIM.js");
26
26
  if (subcommandNames.includes(positional[0])) {
27
27
  const first = positional[0];
28
28
  const deep = positional[1];
29
- const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-BWBHYVJO.js");
29
+ const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-DHTYU524.js");
30
30
  if (deep && renderDeepHelp(`${first}:${deep}`, stubRenderer)) {
31
31
  return 0;
32
32
  }
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
43
43
  export {
44
44
  handleHelpVersionShortCircuit
45
45
  };
46
- //# sourceMappingURL=chunk-4NHD5OXJ.js.map
46
+ //# sourceMappingURL=chunk-NR7BD73C.js.map