@wrongstack/cli 1.0.3 → 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.
- package/dist/{acp-JK4ZQO3R.js → acp-AB54FWM2.js} +120 -38
- package/dist/acp-agent-list.d.ts +14 -0
- package/dist/acp-hq-telemetry.d.ts +9 -2
- package/dist/acp-mcp-servers.d.ts +49 -0
- package/dist/acp-server-agent.d.ts +18 -6
- package/dist/{auth-XD3GN5RJ.js → auth-WDMSNZ3S.js} +2 -2
- package/dist/boot/short-circuit-hq.d.ts +2 -0
- package/dist/boot/tool-registry.d.ts +18 -1
- package/dist/{chunk-C2E24IXZ.js → chunk-3I5ZJXM5.js} +147 -241
- package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
- package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
- package/dist/chunk-AUQDGU5D.js +32 -0
- package/dist/{chunk-IUEKNHPT.js → chunk-BIWE22O5.js} +10 -10
- package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
- package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
- package/dist/chunk-I7V2OYVH.js +242 -0
- package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
- package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
- package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
- package/dist/chunk-Q7E3BPDU.js +62 -0
- package/dist/{chunk-4NHD5OXJ.js → chunk-U6PGWDMZ.js} +5 -5
- package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
- package/dist/{cli-context-Q6QP534K.js → cli-context-WFC3BNGM.js} +116 -21
- package/dist/{cli-main-JQXCDZEA.js → cli-main-GU3YZLMO.js} +1221 -694
- package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
- package/dist/{execution-KH7WJNAX.js → execution-RAZDDQFC.js} +107 -15
- package/dist/execution-specialist-trigger.d.ts +28 -0
- package/dist/fleet/acp-live-progress.d.ts +19 -0
- package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
- package/dist/fleet/host-acp.d.ts +14 -2
- package/dist/fleet/host.d.ts +1 -0
- package/dist/{hq-IG3YRGCO.js → hq-5XHEIIH6.js} +127 -8
- package/dist/hq-command-controller.d.ts +31 -2
- package/dist/hq-server/client-address.d.ts +2 -0
- package/dist/hq-server/ip-allowlist.d.ts +16 -0
- package/dist/hq-server/preflight.d.ts +1 -0
- package/dist/hq-server/routes/auth/password-routes.d.ts +1 -1
- package/dist/hq-server/routes.d.ts +5 -4
- package/dist/hq-server/secret-input.d.ts +7 -0
- package/dist/hq-server/types.d.ts +3 -2
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/utils.d.ts +6 -0
- package/dist/{hq-server-OILU3NPI.js → hq-server-CNUTBWTK.js} +4 -3
- package/dist/hq-server.d.ts +4 -0
- package/dist/hq-service-L5INQFM4.js +507 -0
- package/dist/hq-service.d.ts +22 -0
- package/dist/index.js +4 -4
- package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
- package/dist/live-settings-input.d.ts +2 -0
- package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
- package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
- package/dist/projects-LMNAGGEU.js +36 -0
- package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
- package/dist/short-circuit-flags-YDMLAQ5R.js +10 -0
- package/dist/slash-commands/acp.d.ts +3 -0
- package/dist/slash-commands/command-context.d.ts +22 -0
- package/dist/slash-commands/ensemble.d.ts +1 -1
- package/dist/slash-commands/profile.d.ts +7 -0
- package/dist/subcommands/handlers/hq.d.ts +1 -0
- package/dist/subcommands/handlers/projects.d.ts +14 -0
- package/dist/{subcommands-CUTGBDKP.js → subcommands-7N4RIK6H.js} +2 -2
- package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
- package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
- package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
- package/dist/wiring/codebase-embeddings.d.ts +2 -2
- package/dist/wiring/command-host-state.d.ts +7 -0
- package/dist/wiring/pipeline.d.ts +2 -0
- package/dist/wiring/provider-runtime-setup.d.ts +1 -1
- package/dist/wiring/session-command-handlers.d.ts +2 -1
- package/dist/wiring/slash-commands.d.ts +1 -0
- package/package.json +28 -28
- package/dist/chunk-Q5GTM25S.js +0 -17
- package/dist/projects-L6C6XLDY.js +0 -37
- package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
|
@@ -107,7 +107,8 @@ export interface HqRouterMutableAuth {
|
|
|
107
107
|
/**
|
|
108
108
|
* Session record stored in the server-side `sessions` Map.
|
|
109
109
|
*
|
|
110
|
-
* - `kind: 'password'` — created by `/api/login`; full browser access
|
|
110
|
+
* - `kind: 'password'` — created by `/api/login`; full browser access unless
|
|
111
|
+
* the dedicated mobile endpoint stamped a least-privilege capability set.
|
|
111
112
|
* - `kind: 'token'` — created by `/api/auth/bootstrap`; scoped to the
|
|
112
113
|
* originating browser token's ID and capabilities so revocation/expiry
|
|
113
114
|
* of that token invalidates the session too.
|
|
@@ -117,7 +118,7 @@ export interface HqSessionEntry {
|
|
|
117
118
|
readonly kind: 'password' | 'token';
|
|
118
119
|
/** For token sessions: the browser-token ID this session was minted from. */
|
|
119
120
|
readonly tokenId?: string | undefined;
|
|
120
|
-
/**
|
|
121
|
+
/** Token capabilities, or a server-stamped scope for a mobile password session. */
|
|
121
122
|
readonly capabilities?: string[] | undefined;
|
|
122
123
|
/**
|
|
123
124
|
* Set when a password login passed but 2FA verification is still pending.
|
|
@@ -8,6 +8,7 @@ import type { Duplex } from 'node:stream';
|
|
|
8
8
|
import { type HqToken } from '@wrongstack/core/hq';
|
|
9
9
|
import type { WebSocket, WebSocketServer } from 'ws';
|
|
10
10
|
import type { HqAuthState } from './auth-state.js';
|
|
11
|
+
import type { HqIpAllowlist } from './ip-allowlist.js';
|
|
11
12
|
import type { ConnectedClient, HqSessionEntry, TranscriptRing } from './types.js';
|
|
12
13
|
interface HqUpgradeHandlerDeps {
|
|
13
14
|
host: string;
|
|
@@ -28,6 +29,7 @@ interface HqUpgradeHandlerDeps {
|
|
|
28
29
|
auditLog: import('@wrongstack/core/hq').HqCommandAuditLog;
|
|
29
30
|
snapshotBroadcaster: ReturnType<typeof import('./snapshot.js').createSnapshotBroadcaster>;
|
|
30
31
|
wss: WebSocketServer;
|
|
32
|
+
ipAllowlist?: HqIpAllowlist | undefined;
|
|
31
33
|
}
|
|
32
34
|
export declare function handleHqUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, deps: HqUpgradeHandlerDeps): void;
|
|
33
35
|
export declare function handleHqConnection(ws: WebSocket, req: IncomingMessage, pathname: string, deps: HqUpgradeHandlerDeps): void;
|
|
@@ -95,6 +95,12 @@ export declare function buildHttpUrl(host: string, port: number, token?: string)
|
|
|
95
95
|
*/
|
|
96
96
|
export declare function buildBootstrapHttpUrl(host: string, port: number, bootstrapCode: string): string;
|
|
97
97
|
export declare function buildClientWsUrl(host: string, port: number, token?: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Validate and normalize a browser-facing HQ origin supplied for a persistent
|
|
100
|
+
* HTTPS reverse proxy. Paths, credentials, query strings and fragments are
|
|
101
|
+
* refused because the value becomes a Host/Origin trust-boundary entry.
|
|
102
|
+
*/
|
|
103
|
+
export declare function normalizeHqPublicOrigin(value: string): string;
|
|
98
104
|
export declare function hqRuntimeMarkerPath(dataDir: string): string;
|
|
99
105
|
/** Non-internal IPv4 addresses, so we can print URLs reachable from other machines. */
|
|
100
106
|
export declare function lanIPv4Addresses(): string[];
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
readLocalSubagentTranscript,
|
|
11
11
|
sanitizeApiError,
|
|
12
12
|
startHqServer
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3I5ZJXM5.js";
|
|
14
14
|
import "./chunk-HXJYHAR4.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-I7V2OYVH.js";
|
|
16
|
+
import "./chunk-Q7E3BPDU.js";
|
|
16
17
|
export {
|
|
17
18
|
DEFAULT_HOST,
|
|
18
19
|
DEFAULT_PORT,
|
|
@@ -26,4 +27,4 @@ export {
|
|
|
26
27
|
sanitizeApiError,
|
|
27
28
|
startHqServer
|
|
28
29
|
};
|
|
29
|
-
//# sourceMappingURL=hq-server-
|
|
30
|
+
//# sourceMappingURL=hq-server-CNUTBWTK.js.map
|
package/dist/hq-server.d.ts
CHANGED
|
@@ -30,12 +30,16 @@ interface HqServerOptions {
|
|
|
30
30
|
clientCleanupIntervalMs?: number;
|
|
31
31
|
sessionSnapshotTtlMs?: number;
|
|
32
32
|
password?: string;
|
|
33
|
+
/** Keep an existing Settings-managed password instead of reapplying the startup secret. */
|
|
34
|
+
bootstrapPasswordOnly?: boolean;
|
|
33
35
|
allowInsecureOpen?: boolean;
|
|
34
36
|
secureCookies?: boolean;
|
|
35
37
|
requireBrowserAuth?: boolean;
|
|
36
38
|
allowFileOrigin?: boolean;
|
|
37
39
|
tokenTtlMs?: number;
|
|
38
40
|
trustedProxyHops?: number;
|
|
41
|
+
/** Optional exact-IP/CIDR admission list. Empty/absent means unrestricted. */
|
|
42
|
+
ipAllowlist?: readonly string[];
|
|
39
43
|
}
|
|
40
44
|
import type { HqServerHandle } from './hq-server/handle-types.js';
|
|
41
45
|
export type { HqFirstRunSetup, HqServerHandle, HqStartupConnectionInfo, } from './hq-server/handle-types.js';
|
|
@@ -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
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-XJXDOF63.js";
|
|
9
9
|
import {
|
|
10
10
|
parseArgs
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EK2P53GL.js";
|
|
12
12
|
|
|
13
13
|
// src/cli-entry-main.ts
|
|
14
14
|
async function main(argv) {
|
|
@@ -16,14 +16,14 @@ 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-
|
|
19
|
+
const { handleHelpVersionShortCircuit } = await import("./short-circuit-flags-YDMLAQ5R.js");
|
|
20
20
|
const earlyExit = await handleHelpVersionShortCircuit(argv);
|
|
21
21
|
if (earlyExit !== null) return earlyExit;
|
|
22
22
|
}
|
|
23
|
-
const { initializeCli } = await import("./cli-context-
|
|
23
|
+
const { initializeCli } = await import("./cli-context-WFC3BNGM.js");
|
|
24
24
|
const cliCtx = await initializeCli(argv);
|
|
25
25
|
if (typeof cliCtx === "number") return cliCtx;
|
|
26
|
-
const { runInteractive } = await import("./cli-main-
|
|
26
|
+
const { runInteractive } = await import("./cli-main-GU3YZLMO.js");
|
|
27
27
|
return runInteractive(cliCtx);
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -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;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
renderFocusedHelpToString,
|
|
8
8
|
renderGenericHelp,
|
|
9
9
|
subcommandsWithFocusedHelp
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JX6KL7OF.js";
|
|
11
11
|
import "./chunk-2VMJ3E5Z.js";
|
|
12
12
|
export {
|
|
13
13
|
deepSubcommandsWithFocusedHelp,
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
renderGenericHelp,
|
|
20
20
|
subcommandsWithFocusedHelp
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=per-subcommand-help-
|
|
22
|
+
//# sourceMappingURL=per-subcommand-help-DHTYU524.js.map
|