@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,507 @@
1
+ import {
2
+ resolveHqPasswordInput
3
+ } from "./chunk-AUQDGU5D.js";
4
+ import {
5
+ detectUpdatePackageManager,
6
+ detectUpdatePackageName
7
+ } from "./chunk-DE7BZDNK.js";
8
+ import "./chunk-U3SS4NRH.js";
9
+ import "./chunk-YLQEGYV4.js";
10
+
11
+ // src/hq-service.ts
12
+ import { spawn } from "node:child_process";
13
+ import { constants as fsConstants } from "node:fs";
14
+ import * as fs from "node:fs/promises";
15
+ import * as path from "node:path";
16
+ import { buildChildEnv } from "@wrongstack/core/utils";
17
+ var SERVICE_NAME = "wrongstack-hq.service";
18
+ var UPDATE_SERVICE_NAME = "wrongstack-hq-update.service";
19
+ var UPDATE_TIMER_NAME = "wrongstack-hq-update.timer";
20
+ var SYSTEMD_DIR = "/etc/systemd/system";
21
+ var ENV_DIR = "/etc/wrongstack";
22
+ var ENV_FILE = `${ENV_DIR}/hq.env`;
23
+ var UPDATE_SCRIPT = "/usr/local/libexec/wrongstack-hq-update";
24
+ var MANAGED_MARKER = "Managed by WrongStack HQ service installer";
25
+ function unitQuote(value) {
26
+ if (/[\r\n\0]/.test(value)) throw new Error("systemd values cannot contain line breaks or NUL");
27
+ return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
28
+ }
29
+ function envQuote(value) {
30
+ if (/[\r\n\0]/.test(value))
31
+ throw new Error("HQ service secrets cannot contain line breaks or NUL");
32
+ return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
33
+ }
34
+ function shellQuote(value) {
35
+ if (/\0/.test(value)) throw new Error("shell values cannot contain NUL");
36
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
37
+ }
38
+ function renderHqServiceAssets(options) {
39
+ const bindHost = options.publicOrigin ? "127.0.0.1" : "0.0.0.0";
40
+ const execStart = [
41
+ unitQuote(options.wstackBin),
42
+ "hq",
43
+ "serve",
44
+ "--host",
45
+ bindHost,
46
+ "--port",
47
+ String(options.port),
48
+ "--strict-port"
49
+ ].join(" ");
50
+ const service = `# ${MANAGED_MARKER}
51
+ [Unit]
52
+ Description=WrongStack HQ command center
53
+ Documentation=https://github.com/WrongStack/WrongStack
54
+ Wants=network-online.target
55
+ After=network-online.target
56
+ StartLimitIntervalSec=0
57
+
58
+ [Service]
59
+ Type=simple
60
+ DynamicUser=yes
61
+ StateDirectory=wrongstack-hq
62
+ StateDirectoryMode=0700
63
+ WorkingDirectory=/var/lib/wrongstack-hq
64
+ Environment=NODE_ENV=production
65
+ Environment=HOME=/var/lib/wrongstack-hq
66
+ Environment=WRONGSTACK_HOME=/var/lib/wrongstack-hq
67
+ Environment=WRONGSTACK_HQ_DATA_DIR=/var/lib/wrongstack-hq/hq
68
+ Environment=WRONGSTACK_HQ_SUPPRESS_STARTUP_SECRETS=1
69
+ EnvironmentFile=${ENV_FILE}
70
+ ExecStartPre=/usr/bin/test -x ${unitQuote(options.wstackBin)}
71
+ ExecStart=${execStart}
72
+ Restart=always
73
+ RestartSec=5s
74
+ TimeoutStopSec=30s
75
+ KillSignal=SIGTERM
76
+ StandardOutput=journal
77
+ StandardError=journal
78
+ SyslogIdentifier=wrongstack-hq
79
+ UMask=0077
80
+ NoNewPrivileges=yes
81
+ PrivateTmp=yes
82
+ PrivateDevices=yes
83
+ ProtectSystem=strict
84
+ ProtectHome=yes
85
+ ProtectKernelTunables=yes
86
+ ProtectKernelModules=yes
87
+ ProtectControlGroups=yes
88
+ RestrictSUIDSGID=yes
89
+ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
90
+
91
+ [Install]
92
+ WantedBy=multi-user.target
93
+ `;
94
+ const updateService = `# ${MANAGED_MARKER}
95
+ [Unit]
96
+ Description=Safely update WrongStack HQ
97
+ Documentation=https://github.com/WrongStack/WrongStack
98
+ Wants=network-online.target
99
+ After=network-online.target
100
+
101
+ [Service]
102
+ Type=oneshot
103
+ User=root
104
+ CacheDirectory=wrongstack-hq-update
105
+ Environment=HOME=/var/cache/wrongstack-hq-update
106
+ ExecStart=${UPDATE_SCRIPT}
107
+ Nice=10
108
+ IOSchedulingClass=best-effort
109
+ IOSchedulingPriority=7
110
+ NoNewPrivileges=yes
111
+ PrivateTmp=yes
112
+ PrivateDevices=yes
113
+ ProtectHome=yes
114
+ ProtectKernelTunables=yes
115
+ ProtectKernelModules=yes
116
+ ProtectControlGroups=yes
117
+ RestrictSUIDSGID=yes
118
+ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
119
+ `;
120
+ const updateTimer = `# ${MANAGED_MARKER}
121
+ [Unit]
122
+ Description=Daily WrongStack HQ self-update check
123
+
124
+ [Timer]
125
+ OnBootSec=15min
126
+ OnCalendar=*-*-* 04:17:00
127
+ RandomizedDelaySec=1h
128
+ Persistent=true
129
+ Unit=${UPDATE_SERVICE_NAME}
130
+
131
+ [Install]
132
+ WantedBy=timers.target
133
+ `;
134
+ const rollback = options.packageManager === "pnpm" ? `${shellQuote(options.packageManager)} add -g --ignore-scripts "${options.packageName}@$PREVIOUS_VERSION"` : `${shellQuote(options.packageManager)} install -g --ignore-scripts "${options.packageName}@$PREVIOUS_VERSION"`;
135
+ const packageManagerEnvironment = options.packageManager === "pnpm" ? `export PNPM_HOME=${shellQuote(path.dirname(options.wstackBin))}
136
+ ` : "";
137
+ const updateScript = `#!/bin/sh
138
+ # ${MANAGED_MARKER}
139
+ set -eu
140
+
141
+ WSTACK_BIN=${shellQuote(options.wstackBin)}
142
+ PACKAGE_MANAGER=${shellQuote(options.packageManager)}
143
+ SERVICE=${shellQuote(SERVICE_NAME)}
144
+ export PATH=${shellQuote(`${path.dirname(options.wstackBin)}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`)}
145
+ ${packageManagerEnvironment}
146
+
147
+ version_of() {
148
+ "$WSTACK_BIN" --version 2>/dev/null | sed -n 's/^WrongStack \\([^ ]*\\).*/\\1/p' | head -n 1
149
+ }
150
+
151
+ CHECK_OUTPUT=$("$WSTACK_BIN" update --check-only --pm "$PACKAGE_MANAGER") || {
152
+ printf '%s\\n' "$CHECK_OUTPUT" >&2
153
+ exit 1
154
+ }
155
+ printf '%s\\n' "$CHECK_OUTPUT"
156
+ if ! printf '%s\\n' "$CHECK_OUTPUT" | grep -q '^Update available:'; then
157
+ exit 0
158
+ fi
159
+
160
+ PREVIOUS_VERSION=$(version_of)
161
+ if [ -z "$PREVIOUS_VERSION" ] || [ "$PREVIOUS_VERSION" = "dev" ]; then
162
+ echo "Cannot determine an installed release version; refusing unattended update." >&2
163
+ exit 1
164
+ fi
165
+
166
+ SERVICE_STOPPED=0
167
+ ensure_started() {
168
+ if [ "$SERVICE_STOPPED" -eq 1 ]; then
169
+ systemctl start "$SERVICE" || true
170
+ fi
171
+ }
172
+ trap ensure_started EXIT INT TERM
173
+
174
+ systemctl stop "$SERVICE"
175
+ SERVICE_STOPPED=1
176
+ if ! "$WSTACK_BIN" update --pm "$PACKAGE_MANAGER"; then
177
+ echo "Update failed; reinstalling WrongStack $PREVIOUS_VERSION." >&2
178
+ ${rollback}
179
+ exit 1
180
+ fi
181
+
182
+ UPDATED_VERSION=$(version_of)
183
+ if [ -z "$UPDATED_VERSION" ] || [ "$UPDATED_VERSION" = "$PREVIOUS_VERSION" ]; then
184
+ echo "Updated binary did not report a new release; rolling back." >&2
185
+ ${rollback}
186
+ exit 1
187
+ fi
188
+
189
+ systemctl start "$SERVICE"
190
+ SERVICE_STOPPED=0
191
+ sleep 10
192
+ if ! systemctl is-active --quiet "$SERVICE"; then
193
+ echo "Updated HQ did not stay active; rolling back to $PREVIOUS_VERSION." >&2
194
+ systemctl stop "$SERVICE" || true
195
+ SERVICE_STOPPED=1
196
+ ${rollback}
197
+ systemctl start "$SERVICE"
198
+ SERVICE_STOPPED=0
199
+ sleep 5
200
+ systemctl is-active --quiet "$SERVICE"
201
+ exit 1
202
+ fi
203
+
204
+ echo "WrongStack HQ updated: $PREVIOUS_VERSION -> $UPDATED_VERSION"
205
+ `;
206
+ const environment = [
207
+ `# ${MANAGED_MARKER}`,
208
+ `WRONGSTACK_HQ_PASSWORD=${envQuote(options.password)}`,
209
+ "WRONGSTACK_HQ_BOOTSTRAP_PASSWORD_ONLY=1",
210
+ ...options.publicOrigin ? [`WRONGSTACK_HQ_PUBLIC_URL=${envQuote(options.publicOrigin)}`] : [],
211
+ ...options.ipAllowlist ? [`WRONGSTACK_HQ_ALLOWLIST=${envQuote(options.ipAllowlist)}`] : []
212
+ ].join("\n");
213
+ return { service, updateService, updateTimer, updateScript, environment: `${environment}
214
+ ` };
215
+ }
216
+ async function runCommand(command, args) {
217
+ return await new Promise((resolve2, reject) => {
218
+ const child = spawn(command, args, {
219
+ stdio: "pipe",
220
+ windowsHide: true,
221
+ env: buildChildEnv()
222
+ });
223
+ let stdout = "";
224
+ let stderr = "";
225
+ child.stdout?.on("data", (chunk) => {
226
+ stdout += String(chunk);
227
+ });
228
+ child.stderr?.on("data", (chunk) => {
229
+ stderr += String(chunk);
230
+ });
231
+ child.on("error", reject);
232
+ child.on("close", (code) => resolve2({ code, stdout, stderr }));
233
+ });
234
+ }
235
+ async function systemctl(args, allowFailure = false) {
236
+ const result = await runCommand("systemctl", args);
237
+ if (!allowFailure && result.code !== 0) {
238
+ throw new Error(result.stderr.trim() || `systemctl ${args.join(" ")} failed`);
239
+ }
240
+ return result;
241
+ }
242
+ async function writeManagedFile(file, content, mode) {
243
+ const temporary = `${file}.tmp-${process.pid}`;
244
+ await fs.writeFile(temporary, content, { encoding: "utf8", mode });
245
+ await fs.chmod(temporary, mode);
246
+ await fs.rename(temporary, file);
247
+ }
248
+ async function hasForeignManagedTarget(file) {
249
+ try {
250
+ const content = await fs.readFile(file, "utf8");
251
+ return !content.includes(MANAGED_MARKER);
252
+ } catch (cause) {
253
+ return cause.code !== "ENOENT";
254
+ }
255
+ }
256
+ async function findExecutable(name) {
257
+ for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
258
+ if (!directory || !path.isAbsolute(directory)) continue;
259
+ const candidate = path.join(directory, name);
260
+ try {
261
+ await fs.access(candidate, fsConstants.X_OK);
262
+ return candidate;
263
+ } catch {
264
+ }
265
+ }
266
+ return void 0;
267
+ }
268
+ async function findSystemExecutable(name) {
269
+ for (const directory of ["/usr/local/bin", "/usr/bin", "/bin"]) {
270
+ const candidate = path.join(directory, name);
271
+ try {
272
+ await fs.access(candidate, fsConstants.X_OK);
273
+ return candidate;
274
+ } catch {
275
+ }
276
+ }
277
+ return void 0;
278
+ }
279
+ function requireLinuxRoot(deps) {
280
+ if (process.platform !== "linux") {
281
+ deps.renderer.writeError("HQ system service management is supported only on Linux/systemd.\n");
282
+ return false;
283
+ }
284
+ if (process.getuid?.() !== 0) {
285
+ deps.renderer.writeError("Run this command as root (for example with sudo).\n");
286
+ return false;
287
+ }
288
+ return true;
289
+ }
290
+ function serviceHelp(deps) {
291
+ deps.renderer.write(
292
+ "Usage:\n sudo -E wstack hq service install [--port 3499] [--pm npm|pnpm] [--no-auto-update]\n wstack hq service status\n sudo wstack hq service update\n sudo wstack hq service uninstall\n\nInstall requires WRONGSTACK_HQ_PASSWORD (minimum 8 characters).\nOptions: --port <n>, --hq-allowlist <ip,cidr,...>, --pm npm|pnpm, --wstack-bin <path>, --no-auto-update, --force.\n"
293
+ );
294
+ }
295
+ async function installService(deps) {
296
+ if (!requireLinuxRoot(deps)) return 1;
297
+ let password;
298
+ try {
299
+ password = await resolveHqPasswordInput();
300
+ } catch (cause) {
301
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
302
+ `);
303
+ return 1;
304
+ }
305
+ if (!password || password.length < 8) {
306
+ deps.renderer.writeError(
307
+ "Set WRONGSTACK_HQ_PASSWORD to at least 8 characters; it will be stored root-only.\n"
308
+ );
309
+ return 1;
310
+ }
311
+ const portValue = deps.flags?.["port"];
312
+ const port = typeof portValue === "string" ? Number(portValue) : 3499;
313
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
314
+ deps.renderer.writeError("--port must be an integer between 1 and 65535.\n");
315
+ return 1;
316
+ }
317
+ const requestedManager = deps.flags?.["pm"] ?? deps.flags?.["package-manager"];
318
+ const detectedManager = typeof requestedManager === "string" ? requestedManager : detectUpdatePackageManager();
319
+ if (detectedManager !== "npm" && detectedManager !== "pnpm") {
320
+ deps.renderer.writeError("HQ unattended updates support --pm npm or --pm pnpm.\n");
321
+ return 1;
322
+ }
323
+ if (!await findSystemExecutable(detectedManager)) {
324
+ deps.renderer.writeError(
325
+ `Could not find system-wide ${detectedManager}; unattended root updates cannot use a home-directory shim.
326
+ `
327
+ );
328
+ return 1;
329
+ }
330
+ const explicitBin = deps.flags?.["wstack-bin"];
331
+ const wstackBin = typeof explicitBin === "string" ? path.resolve(explicitBin) : await findExecutable("wstack");
332
+ if (!wstackBin) {
333
+ deps.renderer.writeError("Could not find a global wstack executable in PATH.\n");
334
+ return 1;
335
+ }
336
+ const resolvedWstackBin = await fs.realpath(wstackBin).catch(() => wstackBin);
337
+ if (resolvedWstackBin === "/root" || resolvedWstackBin.startsWith("/root/") || resolvedWstackBin.startsWith("/home/")) {
338
+ deps.renderer.writeError(
339
+ "The HQ system service requires a system-wide wstack executable outside /root and /home. Install with `sudo npm install -g wrongstack` and retry.\n"
340
+ );
341
+ return 1;
342
+ }
343
+ const publicOriginValue = typeof deps.flags?.["hq-public-url"] === "string" ? deps.flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
344
+ let publicOrigin;
345
+ if (publicOriginValue) {
346
+ try {
347
+ const parsed = new URL(publicOriginValue);
348
+ if (parsed.protocol !== "https:" || parsed.origin !== publicOriginValue.replace(/\/$/, "")) {
349
+ throw new Error("not an exact HTTPS origin");
350
+ }
351
+ publicOrigin = parsed.origin;
352
+ } catch {
353
+ deps.renderer.writeError("--hq-public-url must be an exact HTTPS origin.\n");
354
+ return 1;
355
+ }
356
+ }
357
+ const allowlistValue = typeof deps.flags?.["hq-allowlist"] === "string" ? deps.flags["hq-allowlist"] : process.env.WRONGSTACK_HQ_ALLOWLIST;
358
+ let ipAllowlist;
359
+ if (allowlistValue !== void 0 && allowlistValue.trim() !== "") {
360
+ try {
361
+ const { parseHqIpAllowlist } = await import("./ip-allowlist-NHXO6AKJ.js");
362
+ ipAllowlist = parseHqIpAllowlist(allowlistValue)?.join(",");
363
+ } catch (cause) {
364
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
365
+ `);
366
+ return 1;
367
+ }
368
+ }
369
+ const assets = renderHqServiceAssets({
370
+ wstackBin,
371
+ packageManager: detectedManager,
372
+ packageName: detectUpdatePackageName(),
373
+ password,
374
+ port,
375
+ ...publicOrigin ? { publicOrigin } : {},
376
+ ...ipAllowlist ? { ipAllowlist } : {}
377
+ });
378
+ try {
379
+ await fs.access("/run/systemd/system");
380
+ const systemd = await systemctl(["--version"], true);
381
+ if (systemd.code !== 0) throw new Error("systemctl is not operational");
382
+ } catch {
383
+ deps.renderer.writeError("This host is not running systemd; no service files were written.\n");
384
+ return 1;
385
+ }
386
+ const managedTargets = [
387
+ `${SYSTEMD_DIR}/${SERVICE_NAME}`,
388
+ `${SYSTEMD_DIR}/${UPDATE_SERVICE_NAME}`,
389
+ `${SYSTEMD_DIR}/${UPDATE_TIMER_NAME}`,
390
+ UPDATE_SCRIPT,
391
+ ENV_FILE
392
+ ];
393
+ if (deps.flags?.["force"] !== true) {
394
+ const foreign = [];
395
+ for (const file of managedTargets) {
396
+ if (await hasForeignManagedTarget(file)) foreign.push(file);
397
+ }
398
+ if (foreign.length > 0) {
399
+ deps.renderer.writeError(
400
+ `Refusing to overwrite files not managed by WrongStack:
401
+ ${foreign.map((file) => ` ${file}`).join("\n")}
402
+ Use --force only after reviewing them.
403
+ `
404
+ );
405
+ return 1;
406
+ }
407
+ }
408
+ await fs.mkdir(ENV_DIR, { recursive: true, mode: 448 });
409
+ await fs.mkdir(path.dirname(UPDATE_SCRIPT), { recursive: true, mode: 493 });
410
+ await writeManagedFile(ENV_FILE, assets.environment, 384);
411
+ await writeManagedFile(`${SYSTEMD_DIR}/${SERVICE_NAME}`, assets.service, 420);
412
+ await writeManagedFile(`${SYSTEMD_DIR}/${UPDATE_SERVICE_NAME}`, assets.updateService, 420);
413
+ await writeManagedFile(`${SYSTEMD_DIR}/${UPDATE_TIMER_NAME}`, assets.updateTimer, 420);
414
+ await writeManagedFile(UPDATE_SCRIPT, assets.updateScript, 493);
415
+ await systemctl(["daemon-reload"]);
416
+ await systemctl(["enable", SERVICE_NAME]);
417
+ await systemctl(["restart", SERVICE_NAME]);
418
+ if (deps.flags?.["no-auto-update"] === true) {
419
+ await systemctl(["disable", "--now", UPDATE_TIMER_NAME], true);
420
+ } else {
421
+ await systemctl(["enable", "--now", UPDATE_TIMER_NAME]);
422
+ }
423
+ deps.renderer.write(
424
+ `Installed ${SERVICE_NAME}; listening on ${publicOrigin ? "127.0.0.1" : "0.0.0.0"}:${port}, boot persistence and automatic restart are active.
425
+ ` + (ipAllowlist ? `Network allowlist is active (${ipAllowlist.split(",").length} configured rules + loopback).
426
+ ` : "Network allowlist is disabled; password authentication remains active.\n") + (deps.flags?.["no-auto-update"] === true ? "Automatic updates are disabled.\n" : `Automatic updates are scheduled by ${UPDATE_TIMER_NAME}.
427
+ `)
428
+ );
429
+ return 0;
430
+ }
431
+ async function uninstallService(deps) {
432
+ if (!requireLinuxRoot(deps)) return 1;
433
+ const targets = [
434
+ `${SYSTEMD_DIR}/${SERVICE_NAME}`,
435
+ `${SYSTEMD_DIR}/${UPDATE_SERVICE_NAME}`,
436
+ `${SYSTEMD_DIR}/${UPDATE_TIMER_NAME}`,
437
+ UPDATE_SCRIPT
438
+ ];
439
+ const timerIsForeign = await hasForeignManagedTarget(`${SYSTEMD_DIR}/${UPDATE_TIMER_NAME}`);
440
+ const serviceIsForeign = await hasForeignManagedTarget(`${SYSTEMD_DIR}/${SERVICE_NAME}`);
441
+ if (!timerIsForeign) await systemctl(["disable", "--now", UPDATE_TIMER_NAME], true);
442
+ if (!serviceIsForeign) await systemctl(["disable", "--now", SERVICE_NAME], true);
443
+ const preserved = [];
444
+ for (const file of targets) {
445
+ if (await hasForeignManagedTarget(file)) {
446
+ preserved.push(file);
447
+ continue;
448
+ }
449
+ await fs.rm(file, { force: true });
450
+ }
451
+ await systemctl(["daemon-reload"]);
452
+ deps.renderer.write(
453
+ `Removed WrongStack-managed HQ systemd files. Data and ${ENV_FILE} were preserved.
454
+ `
455
+ );
456
+ if (preserved.length > 0) {
457
+ deps.renderer.write(
458
+ `Preserved files not managed by WrongStack:
459
+ ${preserved.map((file) => ` ${file}`).join("\n")}
460
+ `
461
+ );
462
+ }
463
+ return 0;
464
+ }
465
+ var hqServiceCmd = async (args, deps) => {
466
+ const action = args[0];
467
+ if (deps.flags?.["help"] === true || action === "help" || !action) {
468
+ serviceHelp(deps);
469
+ return 0;
470
+ }
471
+ try {
472
+ if (action === "install") return await installService(deps);
473
+ if (action === "uninstall") return await uninstallService(deps);
474
+ if (action === "update") {
475
+ if (!requireLinuxRoot(deps)) return 1;
476
+ const result = await systemctl(["start", UPDATE_SERVICE_NAME]);
477
+ if (result.stdout) deps.renderer.write(result.stdout);
478
+ deps.renderer.write("HQ update check completed.\n");
479
+ return 0;
480
+ }
481
+ if (action === "status") {
482
+ if (process.platform !== "linux") {
483
+ deps.renderer.writeError("HQ system service status is supported only on Linux/systemd.\n");
484
+ return 1;
485
+ }
486
+ const result = await systemctl(
487
+ ["status", SERVICE_NAME, UPDATE_TIMER_NAME, "--no-pager"],
488
+ true
489
+ );
490
+ deps.renderer.write(result.stdout || result.stderr);
491
+ return result.code ?? 1;
492
+ }
493
+ } catch (cause) {
494
+ deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
495
+ `);
496
+ return 1;
497
+ }
498
+ deps.renderer.writeError(`Unknown HQ service action: ${action}
499
+ `);
500
+ serviceHelp(deps);
501
+ return 1;
502
+ };
503
+ export {
504
+ hqServiceCmd,
505
+ renderHqServiceAssets
506
+ };
507
+ //# sourceMappingURL=hq-service-L5INQFM4.js.map
@@ -0,0 +1,22 @@
1
+ import type { SubcommandHandler } from './subcommands/contracts.js';
2
+ type ServerPackageManager = 'npm' | 'pnpm';
3
+ export interface HqServiceAssets {
4
+ service: string;
5
+ updateService: string;
6
+ updateTimer: string;
7
+ updateScript: string;
8
+ environment: string;
9
+ }
10
+ export interface HqServiceAssetOptions {
11
+ wstackBin: string;
12
+ packageManager: ServerPackageManager;
13
+ packageName: 'wrongstack' | '@wrongstack/cli';
14
+ password: string;
15
+ port: number;
16
+ publicOrigin?: string | undefined;
17
+ ipAllowlist?: string | undefined;
18
+ }
19
+ export declare function renderHqServiceAssets(options: HqServiceAssetOptions): HqServiceAssets;
20
+ export declare const hqServiceCmd: SubcommandHandler;
21
+ export {};
22
+ //# sourceMappingURL=hq-service.d.ts.map
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  applyNodeEnvDefault,
4
4
  applySessionShellDefault
5
- } from "./chunk-O4A3HHKD.js";
5
+ } from "./chunk-672UXYL3.js";
6
6
  import {
7
7
  CLI_VERSION
8
8
  } from "./chunk-XJXDOF63.js";
9
9
  import {
10
10
  parseArgs
11
- } from "./chunk-E6RFXHWD.js";
11
+ } from "./chunk-EK2P53GL.js";
12
12
 
13
13
  // src/cli-entry-main.ts
14
14
  async function main(argv) {
@@ -16,20 +16,21 @@ async function main(argv) {
16
16
  applySessionShellDefault();
17
17
  const earlyFlags = parseArgs(argv).flags;
18
18
  if (earlyFlags["help"] === true || earlyFlags["version"] === true) {
19
- const { handleHelpVersionShortCircuit } = await import("./short-circuit-flags-ZNZX44MC.js");
19
+ const { handleHelpVersionShortCircuit } = await import("./short-circuit-flags-KBTYSJTW.js");
20
20
  const earlyExit = await handleHelpVersionShortCircuit(argv);
21
21
  if (earlyExit !== null) return earlyExit;
22
22
  }
23
- const { initializeCli } = await import("./cli-context-Q6QP534K.js");
23
+ const { initializeCli } = await import("./cli-context-BYKJDAFF.js");
24
24
  const cliCtx = await initializeCli(argv);
25
25
  if (typeof cliCtx === "number") return cliCtx;
26
- const { runInteractive } = await import("./cli-main-JQXCDZEA.js");
26
+ const { runInteractive } = await import("./cli-main-5WZBH3WA.js");
27
27
  return runInteractive(cliCtx);
28
28
  }
29
29
 
30
30
  // src/cli-entry-point.ts
31
31
  import { resolve } from "node:path";
32
32
  import { pathToFileURL } from "node:url";
33
+ import { scrubErrorText } from "@wrongstack/core/security";
33
34
  import { installCrashShield, runFatalSalvageSync, writeErr } from "@wrongstack/core/utils";
34
35
  var SQLITE_WARNING_RE = /sqlite is an experimental feature/i;
35
36
  function installSqliteWarningFilter() {
@@ -128,7 +129,9 @@ function runAsMain(mainFn) {
128
129
  },
129
130
  (err) => {
130
131
  runFatalSalvageSync();
131
- writeErr((err instanceof Error ? err.stack : String(err)) + "\n");
132
+ writeErr(
133
+ scrubErrorText(err instanceof Error ? err.stack ?? err.message : String(err)) + "\n"
134
+ );
132
135
  process.exitCode = 1;
133
136
  scheduleForcedExit(1);
134
137
  }
@@ -0,0 +1,9 @@
1
+ import {
2
+ createHqIpAllowlist,
3
+ parseHqIpAllowlist
4
+ } from "./chunk-Q7E3BPDU.js";
5
+ export {
6
+ createHqIpAllowlist,
7
+ parseHqIpAllowlist
8
+ };
9
+ //# sourceMappingURL=ip-allowlist-NHXO6AKJ.js.map
@@ -67,6 +67,8 @@ export interface LiveSettingsInput {
67
67
  cacheTtl?: 'default' | '5m' | '1h' | undefined;
68
68
  /** Show "Model Reasoning" blocks in chat history. Default: true. */
69
69
  showModelReasoning?: boolean | undefined;
70
+ /** Default presentation density for committed TUI tool results. */
71
+ toolResultViewMode?: 'minimal' | 'normal' | 'full' | undefined;
70
72
  /** Agent swarm panel placement: 'bottom' (lower region), 'sidebar' (right sidebar), or 'off' (hidden).
71
73
  * Backward-compat: legacy boolean values are coerced by the TUI settings adapter. Default: 'bottom'. */
72
74
  showAgentSwarmPanel?: 'bottom' | 'sidebar' | 'off' | boolean | undefined;
@@ -23,7 +23,11 @@ import { pathToFileURL } from "node:url";
23
23
  import { Context } from "@wrongstack/core/agent";
24
24
  import { ToolExecutor } from "@wrongstack/core/execution";
25
25
  import { ToolRegistry } from "@wrongstack/core/registry";
26
- import { AutoApprovePermissionPolicy, DefaultSecretScrubber } from "@wrongstack/core/security";
26
+ import {
27
+ AutoApprovePermissionPolicy,
28
+ DefaultSecretScrubber,
29
+ ToolCapabilities
30
+ } from "@wrongstack/core/security";
27
31
  import { normalizeTokenSavingTier } from "@wrongstack/core/types";
28
32
  import {
29
33
  DEFAULT_MCP_INSERTION_MAX_BYTES,
@@ -33,11 +37,9 @@ import {
33
37
  } from "@wrongstack/mcp";
34
38
  import { registerBuiltinToolTier } from "@wrongstack/tools/tool-tier";
35
39
  import { wireKanbanPorts } from "@wrongstack/runtime";
36
- var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
37
- async evaluate() {
38
- return { permission: "auto", source: "default" };
39
- }
40
- };
40
+ function yoloServePolicy() {
41
+ return new AutoApprovePermissionPolicy(Object.values(ToolCapabilities));
42
+ }
41
43
  function parseToolsFlag(flags, positional) {
42
44
  const raw = flags["tools"];
43
45
  const csv = typeof raw === "string" ? raw : raw === true ? positional?.[0] ?? "" : "";
@@ -226,7 +228,7 @@ async function serveMcpStdio(deps, positional) {
226
228
  controller.signal,
227
229
  resolveServeFsRestriction(deps.config)
228
230
  );
229
- const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
231
+ const permissionPolicy = yolo ? yoloServePolicy() : new AutoApprovePermissionPolicy();
230
232
  const executor = new ToolExecutor(registry, {
231
233
  permissionPolicy,
232
234
  secretScrubber: new DefaultSecretScrubber(),
@@ -440,4 +442,4 @@ function isRecord(value) {
440
442
  export {
441
443
  mcpCmd
442
444
  };
443
- //# sourceMappingURL=mcp-HOMLC4WE.js.map
445
+ //# sourceMappingURL=mcp-FX7TKPTV.js.map
@@ -1,8 +1,39 @@
1
1
  import { Context } from '@wrongstack/core/agent';
2
2
  import { ToolRegistry } from '@wrongstack/core/registry';
3
+ import { AutoApprovePermissionPolicy } from '@wrongstack/core/security';
3
4
  import type { PermissionPolicy, Tool } from '@wrongstack/core/types';
4
5
  import { type MCPServerPrompt, type MCPServerResource } from '@wrongstack/mcp';
5
6
  import type { SubcommandDeps } from './subcommands/contracts.js';
7
+ /**
8
+ * `--yolo` for `mcp serve`: widen the CAPABILITY allowlist, keep every guard.
9
+ *
10
+ * This replaced an `AllowAllPermissionPolicy extends AutoApprovePermissionPolicy`
11
+ * whose `evaluate()` returned `{ permission: 'auto' }` unconditionally. That
12
+ * override skipped the whole base implementation — the sensitive-read denial,
13
+ * the leader deny rules, the destructive-command classifier, the agent-state
14
+ * write guard, and `LOCKED_DESTRUCTIVE_KINDS`. It was the only place in the
15
+ * codebase where `agent-state` and `credential-bind` could be un-gated, so a
16
+ * third-party MCP client could have written `hooks` into config.json (boot-time
17
+ * RCE) or `auto: true` into trust.json.
18
+ *
19
+ * It did not actually get there — but only by accident, and the accident is the
20
+ * reason this is a rewrite rather than a patch. The override returned
21
+ * `source: 'default'` where the base class returns `source: 'yolo'`, so
22
+ * `ToolExecutor.capabilityDowngraded` re-armed and forced `confirm`; `mcp serve`
23
+ * has no confirmAwaiter, so the call hard-errored. Changing that one string
24
+ * literal to `'yolo'` — which reads as a trivial consistency cleanup — would
25
+ * have made the bypass live. Safety must not rest on a string nobody knows is
26
+ * load-bearing.
27
+ *
28
+ * Granting every capability is what `--yolo` actually means, and it is what
29
+ * lets `ToolExecutor` trust an `auto` instead of downgrading it (see the
30
+ * `dangerousNotAllowed` reasoning in `auto-approve-policy.ts`). The destructive
31
+ * classifier is deliberately NOT relaxed: `yoloConfirmKinds` is left at its
32
+ * default, which gates every destructive kind. Destructive calls therefore
33
+ * still require a confirmation this surface cannot answer, exactly as before —
34
+ * this change removes a latent bypass without widening anything.
35
+ */
36
+ export declare function yoloServePolicy(): AutoApprovePermissionPolicy;
6
37
  /**
7
38
  * Resolve the `--tools` whitelist for `mcp serve`.
8
39
  *