@wrongstack/cli 1.0.19 → 1.0.26

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 (195) hide show
  1. package/data/providers.json +44 -0
  2. package/dist/{acp-INCNOH42.js → acp-LI665LO2.js} +18 -7
  3. package/dist/arg-parser.d.ts +1 -0
  4. package/dist/ascii-mode-SG4PRNLP.js +7 -0
  5. package/dist/{auth-YXK3EDUL.js → auth-TE55JF4Z.js} +68 -22
  6. package/dist/auth-menu/direct.d.ts +1 -0
  7. package/dist/auth-menu/local.d.ts +2 -0
  8. package/dist/auth-menu/oauth-menu.d.ts +3 -1
  9. package/dist/auth-menu/panel-service.d.ts +20 -0
  10. package/dist/{bench-YBU6BVQP.js → bench-GTLPM22B.js} +2 -2
  11. package/dist/boot/append-system-prompt.d.ts +10 -0
  12. package/dist/boot/ascii-mode.d.ts +12 -0
  13. package/dist/boot/config-backup-recovery.d.ts +2 -1
  14. package/dist/boot/container-wiring.d.ts +4 -2
  15. package/dist/boot/dispatch-singleshot.d.ts +4 -1
  16. package/dist/boot/dispatch-webui.d.ts +11 -0
  17. package/dist/boot/mcp-config-flag.d.ts +22 -0
  18. package/dist/boot/piped-stdin.d.ts +40 -0
  19. package/dist/boot/restricted-mode.d.ts +15 -0
  20. package/dist/boot/safe-mode.d.ts +30 -0
  21. package/dist/boot/short-circuit-desktop.d.ts +1 -0
  22. package/dist/boot/single-shot-budget.d.ts +30 -0
  23. package/dist/boot/stream-json.d.ts +36 -0
  24. package/dist/boot/structured-output.d.ts +16 -0
  25. package/dist/boot/system-prompt-builder.d.ts +2 -0
  26. package/dist/boot/tool-restriction-flags.d.ts +36 -0
  27. package/dist/boot/tui-project-picker-callback.d.ts +1 -1
  28. package/dist/boot/tui-settings-adapter.d.ts +15 -2
  29. package/dist/boot/worktree-sessions.d.ts +21 -0
  30. package/dist/{boot-auto-thin-MANHOYHE.js → boot-auto-thin-6Q56FY3F.js} +3 -2
  31. package/dist/boot-provider-selection.d.ts +67 -0
  32. package/dist/boot.d.ts +1 -38
  33. package/dist/{chunk-JY52HWL6.js → chunk-2EBVTYJR.js} +2 -2
  34. package/dist/{chunk-RDHFXOJM.js → chunk-2ERX3RPN.js} +112 -65
  35. package/dist/chunk-4TVYGG6G.js +62 -0
  36. package/dist/chunk-5JYDKG5B.js +154 -0
  37. package/dist/{chunk-KEPABQYT.js → chunk-5SEYGOCH.js} +8 -82
  38. package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
  39. package/dist/chunk-BBHWW6LR.js +549 -0
  40. package/dist/chunk-BEHGRNOQ.js +58 -0
  41. package/dist/chunk-CZTXF6HY.js +140 -0
  42. package/dist/{chunk-JX6KL7OF.js → chunk-EPR42SWM.js} +39 -8
  43. package/dist/chunk-F5DCFAKI.js +36 -0
  44. package/dist/chunk-HLSHFXTO.js +97 -0
  45. package/dist/{chunk-CJTBLTS4.js → chunk-KJS5543T.js} +4 -3
  46. package/dist/{chunk-DE7BZDNK.js → chunk-KOEO4CJG.js} +27 -4
  47. package/dist/{chunk-SIS37L3W.js → chunk-L2RVEQWO.js} +211 -193
  48. package/dist/chunk-LBPLTTZ6.js +105 -0
  49. package/dist/chunk-LDCGZJTC.js +46 -0
  50. package/dist/chunk-LKPEBZBE.js +143 -0
  51. package/dist/chunk-LUMETJFO.js +171 -0
  52. package/dist/{chunk-KK2LB3ZS.js → chunk-OAVBM55Q.js} +9 -5
  53. package/dist/chunk-PZQBGUVZ.js +18 -0
  54. package/dist/{chunk-C3Z4N6A6.js → chunk-Q4INH2DG.js} +51 -3
  55. package/dist/chunk-R4QGL6GB.js +10 -0
  56. package/dist/{chunk-YLQEGYV4.js → chunk-RZ4ZDRYA.js} +14 -4
  57. package/dist/chunk-S4FFHDJG.js +93 -0
  58. package/dist/chunk-SFBFYIOW.js +271 -0
  59. package/dist/{chunk-RMQNCHGO.js → chunk-TI7KLHYX.js} +136 -11
  60. package/dist/{chunk-2ENZU66X.js → chunk-WQV2NP57.js} +5 -5
  61. package/dist/{chunk-B4JLSXZB.js → chunk-WSTVGC2C.js} +73 -45
  62. package/dist/chunk-X756ZS72.js +329 -0
  63. package/dist/{chunk-XJ7K67PP.js → chunk-YHYINL47.js} +63 -24
  64. package/dist/{chunk-2VMJ3E5Z.js → chunk-YTEOAWOJ.js} +6 -2
  65. package/dist/{chunk-GPPRNAKW.js → chunk-ZMX5HRLI.js} +2 -2
  66. package/dist/{cli-context-33F74CSZ.js → cli-context-UVGAN3NO.js} +496 -384
  67. package/dist/cli-entry-point.d.ts +2 -0
  68. package/dist/{cli-main-FNGMKPBA.js → cli-main-TN3KZ2P6.js} +3561 -2724
  69. package/dist/cli-update-notice.d.ts +2 -1
  70. package/dist/config-doctor.d.ts +10 -2
  71. package/dist/{config-transfer-2YF3CRTN.js → config-transfer-RUWLDCNK.js} +34 -13
  72. package/dist/{diag-doctor-ANPRUYFM.js → diag-doctor-BIZAFIAT.js} +2 -2
  73. package/dist/execute-deps.d.ts +2 -1
  74. package/dist/{execution-A6WJLSOC.js → execution-7QIP6GRY.js} +2384 -2055
  75. package/dist/fleet/host-types.d.ts +3 -1
  76. package/dist/goal-commands.d.ts +47 -0
  77. package/dist/goal-host.d.ts +1 -14
  78. package/dist/{hq-XQWQTRDA.js → hq-GLZ7QESM.js} +277 -261
  79. package/dist/hq-command-controller.d.ts +1 -1
  80. package/dist/{hq-server-IIBZQZU4.js → hq-server-YTKDVQPY.js} +2 -2
  81. package/dist/{hq-service-L5INQFM4.js → hq-service-BMMJAHRT.js} +11 -4
  82. package/dist/import-claude-code-32PCSIQI.js +236 -0
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.js +19 -10
  85. package/dist/kanban-supervisor-67ECABTD.js +7 -0
  86. package/dist/live-settings-input.d.ts +7 -0
  87. package/dist/{mailbox-serve-3GFOK2I7.js → mailbox-serve-NW5VZG7R.js} +3 -3
  88. package/dist/{mcp-DC5WSBOO.js → mcp-LBS45HWZ.js} +4 -4
  89. package/dist/modeldiag-36VODVIH.js +828 -0
  90. package/dist/{per-subcommand-help-DHTYU524.js → per-subcommand-help-QDAQASPZ.js} +3 -3
  91. package/dist/permission-prompt-mirror.d.ts +1 -1
  92. package/dist/permission-prompt.d.ts +16 -2
  93. package/dist/permission-rules-view.d.ts +18 -0
  94. package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
  95. package/dist/plugin-config.d.ts +12 -0
  96. package/dist/plugin-management-types.d.ts +31 -0
  97. package/dist/plugin-management.d.ts +2 -30
  98. package/dist/plugin-package-install.d.ts +3 -0
  99. package/dist/{plugin-usage-M6KET4OZ.js → plugin-usage-CIGOOTX3.js} +6 -5
  100. package/dist/{plugins-WJLW6ULV.js → plugins-HQTXUD6A.js} +6 -3
  101. package/dist/preflight.d.ts +1 -1
  102. package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
  103. package/dist/provider-config-utils.d.ts +1 -6
  104. package/dist/provider-helpers.d.ts +7 -11
  105. package/dist/{providers-models-LFNZ5WI7.js → providers-models-5Z2PJTEW.js} +44 -34
  106. package/dist/release-asset-download.d.ts +13 -0
  107. package/dist/release-attestation.d.ts +28 -0
  108. package/dist/remote/remote-session.d.ts +64 -0
  109. package/dist/remote/remote-target.d.ts +69 -0
  110. package/dist/remote-O5WXHLWV.js +561 -0
  111. package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
  112. package/dist/services/commit-message.d.ts +2 -1
  113. package/dist/services/dispatch-classifier.d.ts +39 -5
  114. package/dist/session-move-P2EC2COA.js +44 -0
  115. package/dist/session-move.d.ts +20 -0
  116. package/dist/session-stats.d.ts +10 -0
  117. package/dist/{sessions-config-UL45XKX7.js → sessions-config-SXTTLNXA.js} +47 -9
  118. package/dist/settings-menu.d.ts +2 -0
  119. package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
  120. package/dist/{simpleui-dist-CXSIDAXZ.js → simpleui-dist-7BQISSEP.js} +7 -3
  121. package/dist/skill-suggest-L2EVWB6R.js +290 -0
  122. package/dist/slash-commands/command-context.d.ts +15 -5
  123. package/dist/slash-commands/effort.d.ts +1 -1
  124. package/dist/slash-commands/fallback.d.ts +1 -1
  125. package/dist/slash-commands/gitid.d.ts +1 -1
  126. package/dist/slash-commands/goal-refiner.d.ts +4 -49
  127. package/dist/slash-commands/jev.d.ts +4 -0
  128. package/dist/slash-commands/mcp.d.ts +2 -1
  129. package/dist/slash-commands/openai-quota.d.ts +15 -0
  130. package/dist/slash-commands/permissions.d.ts +4 -0
  131. package/dist/slash-commands/sdd-task-mutations.d.ts +4 -0
  132. package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
  133. package/dist/slash-commands/settings-keys.d.ts +2 -0
  134. package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
  135. package/dist/slash-commands/settings-reset.d.ts +6 -0
  136. package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
  137. package/dist/standalone-auto-update.d.ts +65 -0
  138. package/dist/standalone-update-LZO3MNLH.js +24 -0
  139. package/dist/standalone-update.d.ts +55 -0
  140. package/dist/subcommands/handlers/hq-alerts.d.ts +3 -0
  141. package/dist/subcommands/handlers/hq-audit.d.ts +46 -0
  142. package/dist/subcommands/handlers/hq-data-dir.d.ts +13 -0
  143. package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
  144. package/dist/subcommands/handlers/permissions.d.ts +9 -3
  145. package/dist/subcommands/handlers/remote.d.ts +6 -0
  146. package/dist/subcommands/handlers/skill-suggest.d.ts +21 -0
  147. package/dist/subcommands/handlers/typesafe-brain-replay.d.ts +26 -0
  148. package/dist/subcommands/handlers/typesafe-judgment-check.d.ts +3 -0
  149. package/dist/subcommands/handlers/typesafe-topic-replay.d.ts +19 -0
  150. package/dist/subcommands/handlers/typesafe-triage-replay.d.ts +18 -0
  151. package/dist/subcommands/handlers/typesafe.d.ts +23 -0
  152. package/dist/subcommands/handlers/version-help.d.ts +5 -0
  153. package/dist/{subcommands-XSLXMIDP.js → subcommands-Q6ORSB2E.js} +2 -2
  154. package/dist/tui-resume-callback.d.ts +18 -0
  155. package/dist/typesafe-U7YB2RRV.js +316 -0
  156. package/dist/typesafe-brain-replay-C5276FVR.js +222 -0
  157. package/dist/typesafe-judgment-check-GGYYV5UF.js +29 -0
  158. package/dist/typesafe-topic-replay-ZILIU3WS.js +234 -0
  159. package/dist/typesafe-triage-replay-FGAW7QM7.js +215 -0
  160. package/dist/{update-5DPB4YD4.js → update-TDZX4IL2.js} +4 -3
  161. package/dist/{update-check-BS3DHTSI.js → update-check-J5ZVKS73.js} +3 -2
  162. package/dist/update-check.d.ts +1 -1
  163. package/dist/version-JB2UAV2A.js +11 -0
  164. package/dist/version-help-XJJQ7YSH.js +10 -0
  165. package/dist/version.d.ts +3 -2
  166. package/dist/webui-client-observability.d.ts +7 -0
  167. package/dist/webui-server/route-contexts.d.ts +1 -1
  168. package/dist/webui-server/session-start-payload.d.ts +5 -1
  169. package/dist/{webui-server-MRPGTTKL.js → webui-server-YTHLRFD2.js} +577 -370
  170. package/dist/webui-server-lifecycle.d.ts +57 -0
  171. package/dist/webui-server-options.d.ts +11 -0
  172. package/dist/webui-server.d.ts +1 -1
  173. package/dist/wiring/brain-and-orchestration.d.ts +3 -1
  174. package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
  175. package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
  176. package/dist/wiring/domain-glossary.d.ts +5 -4
  177. package/dist/wiring/lifecycle-plugins.d.ts +4 -2
  178. package/dist/wiring/metrics.d.ts +9 -1
  179. package/dist/wiring/pipeline.d.ts +2 -7
  180. package/dist/wiring/plugins.d.ts +4 -1
  181. package/dist/wiring/provider-persisters.d.ts +24 -0
  182. package/dist/wiring/provider-runtime.d.ts +9 -1
  183. package/dist/wiring/provider-status.d.ts +1 -0
  184. package/dist/wiring/provider-utility-tools.d.ts +3 -1
  185. package/dist/wiring/runtime-dispatch-state.d.ts +3 -1
  186. package/package.json +28 -28
  187. package/dist/chunk-2CZEORLV.js +0 -54
  188. package/dist/chunk-672UXYL3.js +0 -65
  189. package/dist/chunk-6WRKAACA.js +0 -96
  190. package/dist/chunk-DR2OXU46.js +0 -174
  191. package/dist/chunk-JACOMTIH.js +0 -275
  192. package/dist/chunk-XJXDOF63.js +0 -28
  193. package/dist/modeldiag-CJSFHZ3J.js +0 -1356
  194. package/dist/short-circuit-flags-7GQIGMVD.js +0 -10
  195. package/dist/version-help-WXI42PZ3.js +0 -10
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `wstack remote user@host:/path`: the agent, its tools and its project
3
+ * daemons run on the remote machine; the WebUI is opened locally through an
4
+ * SSH tunnel. WrongStack's own build is uploaded to the remote on first use
5
+ * (per version, next to nothing the user installed), the WebUI host is started
6
+ * detached so a dropped connection does not end it, and the tunnel is reopened
7
+ * when it drops.
8
+ *
9
+ * SSH is the system `ssh`, so the user's `~/.ssh/config`, agent and known
10
+ * hosts apply. Credentials are not copied: the remote WrongStack uses its own
11
+ * configuration, which the remote WebUI sets up like a local first run.
12
+ *
13
+ * @module remote/remote-session
14
+ */
15
+ import { type ChildProcess } from 'node:child_process';
16
+ import type { Readable } from 'node:stream';
17
+ import { type RemoteTarget } from './remote-target.js';
18
+ export interface SshResult {
19
+ code: number;
20
+ stdout: string;
21
+ }
22
+ /** Runs one command on the remote host; `input` is its stdin. */
23
+ export type SshRunner = (remoteCommand: string, input: string | Readable) => Promise<SshResult>;
24
+ /** Opens the tunnel; the returned process lives as long as the tunnel does. */
25
+ export type TunnelOpener = (localPort: number, remotePort: number) => ChildProcess;
26
+ export interface RemoteBinary {
27
+ file: string;
28
+ /** Names the build on the remote machine. */
29
+ version: string;
30
+ }
31
+ export interface RemoteSessionOptions {
32
+ target: RemoteTarget;
33
+ /** Preferred local port for the WebUI (the next free one is used). */
34
+ localPort: number;
35
+ /** Leave the remote WebUI running after this command ends. */
36
+ keep: boolean;
37
+ open: boolean;
38
+ signal: AbortSignal;
39
+ log: (line: string) => void;
40
+ ssh: SshRunner;
41
+ tunnel: TunnelOpener;
42
+ /** The build for a remote `buildTarget` (`bun-linux-x64`, …); may download it. */
43
+ resolveBinary: (buildTarget: string, installed: readonly string[]) => Promise<RemoteBinary>;
44
+ openUrl?: ((url: string) => void) | undefined;
45
+ /** Waits before a reconnect attempt; injected in tests. */
46
+ sleep?: ((ms: number) => Promise<void>) | undefined;
47
+ /** How long the WebUI gets to answer through a new tunnel. */
48
+ readyTimeoutMs?: number | undefined;
49
+ }
50
+ export declare function sha256File(file: string): Promise<string>;
51
+ /** The first free port at or above `start` on the local loopback. */
52
+ export declare function freeLocalPort(start: number): Promise<number>;
53
+ export declare function runRemoteSession(opts: RemoteSessionOptions): Promise<number>;
54
+ export declare function systemSsh(target: RemoteTarget, options: {
55
+ sshConfig?: string | undefined;
56
+ batch: boolean;
57
+ }): {
58
+ run: SshRunner;
59
+ tunnel: TunnelOpener;
60
+ };
61
+ /** Cached download location for release builds. */
62
+ export declare function releaseCacheFile(root: string, version: string, assetName: string): string;
63
+ export declare function cachedFile(file: string): Promise<string | undefined>;
64
+ //# sourceMappingURL=remote-session.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The pure half of `wstack remote`: where to connect, which build the remote
3
+ * machine needs, and the POSIX sh scripts run there. No process is started
4
+ * here, so all of it is testable without an SSH server.
5
+ *
6
+ * @module remote/remote-target
7
+ */
8
+ export interface RemoteTarget {
9
+ /** What `ssh` connects to: `user@host`, `host`, or a `~/.ssh/config` alias. */
10
+ destination: string;
11
+ port?: number | undefined;
12
+ /** Project directory on the remote machine. */
13
+ path: string;
14
+ }
15
+ /**
16
+ * `user@host:/path`, `host:~/path`, or `ssh://user@host:2222/path`. The path
17
+ * is required: it is the remote project the agent works in.
18
+ */
19
+ export declare function parseRemoteTarget(input: string): RemoteTarget;
20
+ /** One argument for a POSIX shell, single-quoted. */
21
+ export declare function shQuote(value: string): string;
22
+ /**
23
+ * The build for a machine, from `uname -s`, `uname -m` and whether its libc is
24
+ * musl. Windows hosts are not supported: the remote scripts are POSIX sh.
25
+ */
26
+ export declare function buildTargetFor(os: string, arch: string, musl: boolean): string;
27
+ export interface ProbeResult {
28
+ os: string;
29
+ arch: string;
30
+ musl: boolean;
31
+ pathExists: boolean;
32
+ /** Versions already installed under `~/.wrongstack/remote`. */
33
+ versions: string[];
34
+ /** A server a previous run left running for this path. */
35
+ server?: {
36
+ pid: number;
37
+ port: number;
38
+ version: string;
39
+ token: string;
40
+ } | undefined;
41
+ }
42
+ /** Reports the machine, the project path, the builds present and a running server. */
43
+ export declare function probeScript(path: string): string;
44
+ export declare function parseProbe(output: string): ProbeResult;
45
+ /** The remote file a version's build lives in. */
46
+ export declare function remoteBinaryPath(version: string): string;
47
+ /**
48
+ * Receives a build on stdin, makes it executable, and prints its SHA-256 so
49
+ * the caller can compare it with what it sent. Run with `sh -c`.
50
+ */
51
+ export declare function installScript(version: string): string;
52
+ /**
53
+ * Starts the WebUI host for `path` detached from the SSH session (a dropped
54
+ * connection must not end it), bound to the remote loopback only, and waits
55
+ * for it to report its port. The token travels inside this script, which is
56
+ * sent on stdin: it never appears on a command line another user can see.
57
+ *
58
+ * A remote machine WrongStack has not been set up on has no provider it can
59
+ * use; the host is then started in setup mode, and one is chosen in the WebUI.
60
+ */
61
+ export declare function startScript(options: {
62
+ path: string;
63
+ version: string;
64
+ token: string;
65
+ preferredPort: number;
66
+ }): string;
67
+ /** Stops the server a start left for `path`, if it is still running. */
68
+ export declare function stopScript(path: string): string;
69
+ //# sourceMappingURL=remote-target.d.ts.map
@@ -0,0 +1,561 @@
1
+ import {
2
+ MAX_BINARY_BYTES,
3
+ downloadReleaseAssetToFile,
4
+ findStandaloneRelease,
5
+ verifyStandaloneDigest
6
+ } from "./chunk-X756ZS72.js";
7
+ import {
8
+ CLI_VERSION,
9
+ STANDALONE_TARGET
10
+ } from "./chunk-F5DCFAKI.js";
11
+ import {
12
+ openBrowser
13
+ } from "./chunk-DAKVBMD4.js";
14
+
15
+ // src/subcommands/handlers/remote.ts
16
+ import * as fsp2 from "node:fs/promises";
17
+ import * as path2 from "node:path";
18
+ import { isStandaloneBinary, toErrorMessage as toErrorMessage2, wstackGlobalRoot } from "@wrongstack/core/utils";
19
+
20
+ // src/remote/remote-session.ts
21
+ import { spawn } from "node:child_process";
22
+ import { createHash, randomBytes } from "node:crypto";
23
+ import { createReadStream, existsSync } from "node:fs";
24
+ import * as fsp from "node:fs/promises";
25
+ import * as net from "node:net";
26
+ import * as path from "node:path";
27
+ import { buildChildEnv, toErrorMessage } from "@wrongstack/core/utils";
28
+
29
+ // src/remote/remote-target.ts
30
+ function parseRemoteTarget(input) {
31
+ const text = input.trim();
32
+ if (text.startsWith("ssh://")) {
33
+ let url;
34
+ try {
35
+ url = new URL(text);
36
+ } catch {
37
+ throw new Error(`Not a valid ssh:// address: ${text}`);
38
+ }
39
+ const user = url.username ? `${decodeURIComponent(url.username)}@` : "";
40
+ const path4 = decodeURIComponent(url.pathname);
41
+ if (!url.hostname || !path4 || path4 === "/") {
42
+ throw new Error(`Give the remote project directory: ssh://user@host/path (got ${text})`);
43
+ }
44
+ return {
45
+ destination: `${user}${url.hostname}`,
46
+ ...url.port ? { port: Number(url.port) } : {},
47
+ path: path4
48
+ };
49
+ }
50
+ const colon = text.indexOf(":");
51
+ const destination = colon > 0 ? text.slice(0, colon) : "";
52
+ const path3 = colon > 0 ? text.slice(colon + 1) : "";
53
+ if (!destination || !path3 || /\s/.test(destination)) {
54
+ throw new Error(`Give the remote as user@host:/path/to/project (got ${text || "nothing"})`);
55
+ }
56
+ return { destination, path: path3 };
57
+ }
58
+ function shQuote(value) {
59
+ return `'${value.replace(/'/g, `'\\''`)}'`;
60
+ }
61
+ function shPath(path3) {
62
+ if (path3 === "~") return '"$HOME"';
63
+ if (path3.startsWith("~/")) return `"$HOME"/${shQuote(path3.slice(2))}`;
64
+ return shQuote(path3);
65
+ }
66
+ function buildTargetFor(os, arch, musl) {
67
+ const system = os.trim().toLowerCase();
68
+ const machine = arch.trim().toLowerCase();
69
+ const cpu = machine === "x86_64" || machine === "amd64" ? "x64" : machine === "aarch64" || machine === "arm64" ? "arm64" : void 0;
70
+ if (!cpu) throw new Error(`No WrongStack build for a ${arch} remote machine.`);
71
+ if (system === "linux") return `bun-linux-${cpu}${musl ? "-musl" : ""}`;
72
+ if (system === "darwin") return `bun-darwin-${cpu}`;
73
+ throw new Error(`Remote machines running ${os} are not supported (Linux and macOS are).`);
74
+ }
75
+ var REMOTE_BIN_DIR = '"$HOME"/.wrongstack/remote';
76
+ function probeScript(path3) {
77
+ return `set -u
78
+ printf 'os=%s\\n' "$(uname -s 2>/dev/null)"
79
+ printf 'arch=%s\\n' "$(uname -m 2>/dev/null)"
80
+ if ls /lib/ld-musl-* >/dev/null 2>&1; then echo musl=1; else echo musl=0; fi
81
+ dir=${shPath(path3)}
82
+ if [ -d "$dir" ]; then echo path=1; else echo path=0; fi
83
+ for f in ${REMOTE_BIN_DIR}/wstack-*; do
84
+ [ -x "$f" ] && printf 'version=%s\\n' "\${f##*/wstack-}"
85
+ done
86
+ state="$dir/.wrongstack/remote-webui.json"
87
+ if [ -f "$state" ] && [ -f "$dir/.wrongstack/remote-webui.token" ]; then
88
+ pid=$(sed -n 's/.*"pid":\\([0-9]*\\).*/\\1/p' "$state")
89
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
90
+ printf 'server=%s\\n' "$(tr -d '\\n' < "$state")"
91
+ printf 'token=%s\\n' "$(cat "$dir/.wrongstack/remote-webui.token")"
92
+ fi
93
+ fi
94
+ `;
95
+ }
96
+ function parseProbe(output) {
97
+ const values = /* @__PURE__ */ new Map();
98
+ for (const line of output.split(/\r?\n/)) {
99
+ const eq = line.indexOf("=");
100
+ if (eq <= 0) continue;
101
+ const key = line.slice(0, eq);
102
+ values.set(key, [...values.get(key) ?? [], line.slice(eq + 1)]);
103
+ }
104
+ const one = (key) => values.get(key)?.[0] ?? "";
105
+ let server;
106
+ try {
107
+ const state = JSON.parse(one("server") || "null");
108
+ const token = one("token");
109
+ if (state && typeof state.pid === "number" && typeof state.port === "number" && typeof state.version === "string" && token) {
110
+ server = { pid: state.pid, port: state.port, version: state.version, token };
111
+ }
112
+ } catch {
113
+ server = void 0;
114
+ }
115
+ return {
116
+ os: one("os"),
117
+ arch: one("arch"),
118
+ musl: one("musl") === "1",
119
+ pathExists: one("path") === "1",
120
+ versions: values.get("version") ?? [],
121
+ ...server ? { server } : {}
122
+ };
123
+ }
124
+ function remoteBinaryPath(version) {
125
+ return `${REMOTE_BIN_DIR}/${shQuote(`wstack-${version}`)}`;
126
+ }
127
+ function installScript(version) {
128
+ const target = remoteBinaryPath(version);
129
+ return `set -eu
130
+ mkdir -p ${REMOTE_BIN_DIR}
131
+ chmod 700 ${REMOTE_BIN_DIR}
132
+ part=${REMOTE_BIN_DIR}/.upload-$$
133
+ cat > "$part"
134
+ chmod 700 "$part"
135
+ sum=$( (sha256sum "$part" 2>/dev/null || shasum -a 256 "$part") | cut -d' ' -f1)
136
+ mv -f "$part" ${target}
137
+ echo "sha256=$sum"
138
+ `;
139
+ }
140
+ var NO_PROVIDER = ["No provider or model configured", "Saved provider/model is no longer usable"];
141
+ function startScript(options) {
142
+ const bin = remoteBinaryPath(options.version);
143
+ return `set -u
144
+ dir=${shPath(options.path)}
145
+ cd "$dir" || { echo error=path; exit 3; }
146
+ umask 077
147
+ mkdir -p .wrongstack
148
+ printf '%s' ${shQuote(options.token)} > .wrongstack/remote-webui.token
149
+ log=.wrongstack/remote-webui.log
150
+ export WEBUI_TOKEN=${shQuote(options.token)}
151
+ launch() {
152
+ : > "$log"
153
+ if command -v setsid >/dev/null 2>&1; then
154
+ nohup setsid ${bin} --webui --host 127.0.0.1 --port ${options.preferredPort} "$@" > "$log" 2>&1 < /dev/null &
155
+ else
156
+ nohup ${bin} --webui --host 127.0.0.1 --port ${options.preferredPort} "$@" > "$log" 2>&1 < /dev/null &
157
+ fi
158
+ pid=$!
159
+ }
160
+ # 0: running and "port" is set, 1: the host exited, 2: no port in time.
161
+ await_port() {
162
+ i=0
163
+ while [ "$i" -lt 240 ]; do
164
+ port=$(sed -n 's/.*WebUI running.*127\\.0\\.0\\.1:\\([0-9][0-9]*\\).*/\\1/p' "$log" | head -n 1)
165
+ [ -n "$port" ] && return 0
166
+ kill -0 "$pid" 2>/dev/null || return 1
167
+ sleep 0.5
168
+ i=$((i + 1))
169
+ done
170
+ return 2
171
+ }
172
+ launch
173
+ await_port
174
+ state=$?
175
+ if [ "$state" -eq 1 ] && grep -q ${NO_PROVIDER.map((line) => `-e ${shQuote(line)}`).join(" ")} "$log"; then
176
+ echo setup=1
177
+ launch --provider wrongstack-setup --model no-api-key
178
+ await_port
179
+ state=$?
180
+ fi
181
+ if [ "$state" -eq 0 ]; then
182
+ printf '{"pid":%s,"port":%s,"version":"%s"}\\n' "$pid" "$port" ${shQuote(options.version)} > .wrongstack/remote-webui.json
183
+ echo "port=$port"
184
+ echo "pid=$pid"
185
+ exit 0
186
+ fi
187
+ rm -f .wrongstack/remote-webui.token
188
+ if [ "$state" -eq 1 ]; then
189
+ echo error=exited
190
+ tail -n 30 "$log" | sed 's/^/log=/'
191
+ exit 4
192
+ fi
193
+ kill "$pid" 2>/dev/null
194
+ echo error=timeout
195
+ exit 5
196
+ `;
197
+ }
198
+ function stopScript(path3) {
199
+ return `set -u
200
+ dir=${shPath(path3)}
201
+ state="$dir/.wrongstack/remote-webui.json"
202
+ [ -f "$state" ] || exit 0
203
+ pid=$(sed -n 's/.*"pid":\\([0-9]*\\).*/\\1/p' "$state")
204
+ [ -n "$pid" ] && kill "$pid" 2>/dev/null
205
+ rm -f "$state" "$dir/.wrongstack/remote-webui.token"
206
+ echo stopped=1
207
+ `;
208
+ }
209
+
210
+ // src/remote/remote-session.ts
211
+ var RECONNECT_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3, 3e4];
212
+ var READY_TIMEOUT_MS = 3e4;
213
+ async function sha256File(file) {
214
+ const hash = createHash("sha256");
215
+ for await (const chunk of createReadStream(file)) hash.update(chunk);
216
+ return hash.digest("hex");
217
+ }
218
+ async function freeLocalPort(start) {
219
+ for (let port = start; port < start + 200; port++) {
220
+ const free = await new Promise((resolve2) => {
221
+ const server = net.createServer();
222
+ server.once("error", () => resolve2(false));
223
+ server.listen(port, "127.0.0.1", () => server.close(() => resolve2(true)));
224
+ });
225
+ if (free) return port;
226
+ }
227
+ throw new Error(`No free local port from ${start} to ${start + 199}.`);
228
+ }
229
+ var REMOTE_ERRORS = {
230
+ path: "cannot enter the project directory",
231
+ exited: "the WrongStack host exited",
232
+ timeout: "the WrongStack host did not report its port within two minutes"
233
+ };
234
+ function sshError(step, result) {
235
+ const lines = result.stdout.split(/\r?\n/);
236
+ const code = lines.find((line) => line.startsWith("error="))?.slice("error=".length);
237
+ const log = lines.filter((line) => line.startsWith("log=")).map((line) => line.slice(4));
238
+ const reason = code ? REMOTE_ERRORS[code] ?? code : `ssh exited with ${result.code}`;
239
+ const detail = log.length > 0 ? `:
240
+ ${log.join("\n")}` : code ? "." : ". See the ssh message above.";
241
+ return new Error(`Remote ${step} failed: ${reason}${detail}`);
242
+ }
243
+ async function ensureBinary(opts, probe) {
244
+ const buildTarget = buildTargetFor(probe.os, probe.arch, probe.musl);
245
+ const binary = await opts.resolveBinary(buildTarget, probe.versions);
246
+ if (probe.versions.includes(binary.version)) return binary;
247
+ opts.log(
248
+ `Uploading WrongStack ${binary.version} (${buildTarget}) to ${opts.target.destination}\u2026`
249
+ );
250
+ const sent = await sha256File(binary.file);
251
+ const result = await opts.ssh(
252
+ `sh -c ${shQuote(installScript(binary.version))}`,
253
+ createReadStream(binary.file)
254
+ );
255
+ const received = /^sha256=([a-f0-9]{64})$/m.exec(result.stdout)?.[1];
256
+ if (result.code !== 0 || !received) throw sshError("upload", result);
257
+ if (received !== sent) {
258
+ throw new Error("The build that arrived on the remote machine does not match the one sent.");
259
+ }
260
+ return binary;
261
+ }
262
+ async function ensureServer(opts, probe, binary) {
263
+ if (probe.server && probe.server.version === binary.version) {
264
+ opts.log(`Reusing the WrongStack host already running on ${opts.target.destination}.`);
265
+ return { port: probe.server.port, token: probe.server.token };
266
+ }
267
+ if (probe.server) await opts.ssh("sh -s", stopScript(opts.target.path));
268
+ const token = randomBytes(24).toString("base64url");
269
+ opts.log(`Starting WrongStack on ${opts.target.destination}:${opts.target.path}\u2026`);
270
+ const result = await opts.ssh(
271
+ "sh -s",
272
+ startScript({ path: opts.target.path, version: binary.version, token, preferredPort: 3456 })
273
+ );
274
+ if (/^setup=1$/m.test(result.stdout)) {
275
+ opts.log("No usable provider is set up there yet; choose one in the WebUI.");
276
+ }
277
+ const port = Number(/^port=(\d+)$/m.exec(result.stdout)?.[1]);
278
+ if (result.code !== 0 || !Number.isInteger(port) || port <= 0) {
279
+ throw sshError("start", result);
280
+ }
281
+ return { port, token };
282
+ }
283
+ async function probeRemote(opts) {
284
+ const result = await opts.ssh("sh -s", probeScript(opts.target.path));
285
+ if (result.code !== 0) throw sshError("connection", result);
286
+ const probe = parseProbe(result.stdout);
287
+ if (!probe.pathExists) {
288
+ throw new Error(`${opts.target.path} does not exist on ${opts.target.destination}.`);
289
+ }
290
+ return probe;
291
+ }
292
+ async function waitForWebui(localPort, token, tunnel, signal, timeoutMs) {
293
+ const deadline = Date.now() + timeoutMs;
294
+ while (Date.now() < deadline && tunnel.exitCode === null && !signal.aborted) {
295
+ try {
296
+ const res = await fetch(`http://127.0.0.1:${localPort}/ws-auth`, {
297
+ headers: { "X-WS-Token": token },
298
+ signal: AbortSignal.timeout(3e3)
299
+ });
300
+ if (res.ok) return true;
301
+ } catch {
302
+ }
303
+ await new Promise((resolve2) => setTimeout(resolve2, 400));
304
+ }
305
+ return false;
306
+ }
307
+ function exited(child) {
308
+ return child.exitCode !== null ? Promise.resolve(child.exitCode) : new Promise((resolve2) => child.once("exit", (code) => resolve2(code ?? 1)));
309
+ }
310
+ async function runRemoteSession(opts) {
311
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
312
+ let probe = await probeRemote(opts);
313
+ const binary = await ensureBinary(opts, probe);
314
+ let server = await ensureServer(opts, probe, binary);
315
+ const localPort = await freeLocalPort(opts.localPort);
316
+ let announced = "";
317
+ let failures = 0;
318
+ try {
319
+ while (!opts.signal.aborted) {
320
+ const tunnel = opts.tunnel(localPort, server.port);
321
+ const stopTunnel = () => tunnel.kill();
322
+ opts.signal.addEventListener("abort", stopTunnel, { once: true });
323
+ const ready = await waitForWebui(
324
+ localPort,
325
+ server.token,
326
+ tunnel,
327
+ opts.signal,
328
+ opts.readyTimeoutMs ?? READY_TIMEOUT_MS
329
+ );
330
+ if (!ready && !opts.signal.aborted) {
331
+ tunnel.kill();
332
+ if (!announced) {
333
+ await exited(tunnel);
334
+ opts.signal.removeEventListener("abort", stopTunnel);
335
+ throw new Error(
336
+ `The WebUI on ${opts.target.destination} did not answer through the SSH tunnel. Check that its sshd allows port forwarding (AllowTcpForwarding) and see ${opts.target.path}/.wrongstack/remote-webui.log there.`
337
+ );
338
+ }
339
+ }
340
+ if (ready) {
341
+ failures = 0;
342
+ const url = `http://127.0.0.1:${localPort}/?token=${server.token}`;
343
+ if (url !== announced) {
344
+ opts.log(`WebUI for ${opts.target.destination}:${opts.target.path} \u2192 ${url}`);
345
+ opts.log("Press Ctrl+C to disconnect.");
346
+ if (opts.open && !announced) opts.openUrl?.(url);
347
+ announced = url;
348
+ } else {
349
+ opts.log("Reconnected.");
350
+ }
351
+ }
352
+ await exited(tunnel);
353
+ opts.signal.removeEventListener("abort", stopTunnel);
354
+ if (opts.signal.aborted) break;
355
+ const delay = RECONNECT_DELAYS_MS[Math.min(failures, RECONNECT_DELAYS_MS.length - 1)] ?? 3e4;
356
+ failures += 1;
357
+ opts.log(
358
+ `The connection to ${opts.target.destination} dropped; reconnecting in ${delay / 1e3}s\u2026`
359
+ );
360
+ await sleep(delay);
361
+ if (opts.signal.aborted) break;
362
+ try {
363
+ probe = await probeRemote(opts);
364
+ server = await ensureServer(opts, probe, binary);
365
+ } catch (err) {
366
+ opts.log(toErrorMessage(err));
367
+ }
368
+ }
369
+ } finally {
370
+ if (!opts.keep) {
371
+ await opts.ssh("sh -s", stopScript(opts.target.path)).then(() => opts.log(`Stopped WrongStack on ${opts.target.destination}.`)).catch(() => opts.log("Could not stop the remote WrongStack host; it keeps running."));
372
+ } else {
373
+ opts.log(`WrongStack keeps running on ${opts.target.destination} (--keep).`);
374
+ }
375
+ }
376
+ return 0;
377
+ }
378
+ function sshAgentEnv() {
379
+ const env = {};
380
+ for (const key of [
381
+ "SSH_AUTH_SOCK",
382
+ "SSH_AGENT_PID",
383
+ "SSH_ASKPASS",
384
+ "SSH_ASKPASS_REQUIRE",
385
+ "DISPLAY"
386
+ ]) {
387
+ const value = process.env[key];
388
+ if (value !== void 0) env[key] = value;
389
+ }
390
+ return env;
391
+ }
392
+ function sshCommandLine(target, options) {
393
+ return [
394
+ ...options.sshConfig ? ["-F", options.sshConfig] : [],
395
+ ...target.port ? ["-p", String(target.port)] : [],
396
+ // Without a terminal (or an askpass helper) a password prompt would hang.
397
+ ...options.batch ? ["-o", "BatchMode=yes"] : [],
398
+ "-o",
399
+ "ConnectTimeout=20",
400
+ "-o",
401
+ "ServerAliveInterval=15",
402
+ "-o",
403
+ "ServerAliveCountMax=3"
404
+ ];
405
+ }
406
+ function systemSsh(target, options) {
407
+ const base = sshCommandLine(target, options);
408
+ const run = (remoteCommand, input) => new Promise((resolve2) => {
409
+ const child = spawn("ssh", [...base, target.destination, remoteCommand], {
410
+ stdio: ["pipe", "pipe", "inherit"],
411
+ env: { ...buildChildEnv(), ...sshAgentEnv() },
412
+ windowsHide: true
413
+ });
414
+ let stdout = "";
415
+ child.stdout?.setEncoding("utf8");
416
+ child.stdout?.on("data", (chunk) => {
417
+ if (stdout.length < 1e6) stdout += chunk;
418
+ });
419
+ child.on("error", (err) => resolve2({ code: 127, stdout: `error=${err.message}` }));
420
+ child.on("close", (code) => resolve2({ code: code ?? 1, stdout }));
421
+ child.stdin?.on("error", () => void 0);
422
+ if (typeof input === "string") child.stdin?.end(input);
423
+ else input.pipe(child.stdin);
424
+ });
425
+ const tunnel = (localPort, remotePort) => spawn(
426
+ "ssh",
427
+ [
428
+ ...base,
429
+ "-N",
430
+ "-o",
431
+ "ExitOnForwardFailure=yes",
432
+ "-L",
433
+ `127.0.0.1:${localPort}:127.0.0.1:${remotePort}`,
434
+ target.destination
435
+ ],
436
+ {
437
+ stdio: ["ignore", "ignore", "inherit"],
438
+ env: { ...buildChildEnv(), ...sshAgentEnv() },
439
+ windowsHide: true
440
+ }
441
+ );
442
+ return { run, tunnel };
443
+ }
444
+ function releaseCacheFile(root, version, assetName) {
445
+ return path.join(root, "remote-builds", `${version}-${assetName}`);
446
+ }
447
+ async function cachedFile(file) {
448
+ if (!existsSync(file)) return void 0;
449
+ const stat2 = await fsp.stat(file);
450
+ return stat2.size > 0 ? file : void 0;
451
+ }
452
+
453
+ // src/subcommands/handlers/remote.ts
454
+ var USAGE = `Usage: wstack remote <user@host:/path | ssh://user@host:port/path> [options]
455
+
456
+ Runs WrongStack on the remote machine, in that project directory, and opens its
457
+ WebUI here through an SSH tunnel. The agent, its tools and its daemons run
458
+ remotely. The system ssh is used, so ~/.ssh/config, your agent and known_hosts
459
+ apply. Nothing from your local configuration is copied; set up a provider in the
460
+ remote WebUI the first time.
461
+
462
+ Options:
463
+ --port <n> Local port for the WebUI (default 3456, next free one)
464
+ --open Open the WebUI in your browser
465
+ --keep Leave WrongStack running remotely when you disconnect
466
+ --ssh-config <file> Use this ssh config file (ssh -F)
467
+ --remote-binary <f> Upload this WrongStack build instead of the release one
468
+ `;
469
+ function flagString(flags, name) {
470
+ const value = flags[name];
471
+ return typeof value === "string" && value ? value : void 0;
472
+ }
473
+ async function resolveBinary(buildTarget, installed, explicit, log) {
474
+ if (explicit) {
475
+ const file2 = path2.resolve(explicit);
476
+ const sha2 = await sha256File(file2);
477
+ return { file: file2, version: `build-${sha2.slice(0, 16)}` };
478
+ }
479
+ if (isStandaloneBinary() && STANDALONE_TARGET === buildTarget) {
480
+ return { file: process.execPath, version: CLI_VERSION };
481
+ }
482
+ const release = await findStandaloneRelease(buildTarget);
483
+ if (installed.includes(release.version)) return { file: "", version: release.version };
484
+ const file = releaseCacheFile(wstackGlobalRoot(), release.version, release.assetName);
485
+ const cached = await cachedFile(file);
486
+ if (cached) {
487
+ await verifyStandaloneDigest(release, await sha256File(cached));
488
+ return { file: cached, version: release.version };
489
+ }
490
+ log(`Downloading WrongStack ${release.version} for ${buildTarget}\u2026`);
491
+ await fsp2.mkdir(path2.dirname(file), { recursive: true });
492
+ await fsp2.rm(file, { force: true });
493
+ const sha = await downloadReleaseAssetToFile(release.asset.browser_download_url, file, {
494
+ timeoutMs: 10 * 6e4,
495
+ maxBytes: MAX_BINARY_BYTES
496
+ });
497
+ try {
498
+ await verifyStandaloneDigest(release, sha);
499
+ } catch (err) {
500
+ await fsp2.rm(file, { force: true });
501
+ throw err;
502
+ }
503
+ return { file, version: release.version };
504
+ }
505
+ async function runRemoteCommand(args, flags, renderer) {
506
+ const write = (line) => renderer.writeLine(line);
507
+ const targetText = flagString(flags, "remote") ?? args[0];
508
+ if (flags["help"] === true || !targetText) {
509
+ write(USAGE);
510
+ return flags["help"] === true ? 0 : 2;
511
+ }
512
+ let target;
513
+ try {
514
+ target = parseRemoteTarget(targetText);
515
+ } catch (err) {
516
+ write(toErrorMessage2(err));
517
+ return 2;
518
+ }
519
+ const portText = flagString(flags, "port");
520
+ const localPort = portText ? Number(portText) : 3456;
521
+ if (!Number.isInteger(localPort) || localPort < 1 || localPort > 65535) {
522
+ write(`--port must be a TCP port, got ${portText}`);
523
+ return 2;
524
+ }
525
+ const sshConfig = flagString(flags, "ssh-config");
526
+ const explicitBinary = flagString(flags, "remote-binary");
527
+ const ssh = systemSsh(target, {
528
+ ...sshConfig ? { sshConfig } : {},
529
+ batch: !process.stdin.isTTY && !process.env["SSH_ASKPASS"]
530
+ });
531
+ const controller = new AbortController();
532
+ const onInterrupt = () => controller.abort();
533
+ process.once("SIGINT", onInterrupt);
534
+ process.once("SIGTERM", onInterrupt);
535
+ try {
536
+ return await runRemoteSession({
537
+ target,
538
+ localPort,
539
+ keep: flags["keep"] === true,
540
+ open: flags["open"] === true,
541
+ signal: controller.signal,
542
+ log: write,
543
+ ssh: ssh.run,
544
+ tunnel: ssh.tunnel,
545
+ resolveBinary: (buildTarget, installed) => resolveBinary(buildTarget, installed, explicitBinary, write),
546
+ openUrl: openBrowser
547
+ });
548
+ } catch (err) {
549
+ write(toErrorMessage2(err));
550
+ return 1;
551
+ } finally {
552
+ process.off("SIGINT", onInterrupt);
553
+ process.off("SIGTERM", onInterrupt);
554
+ }
555
+ }
556
+ var remoteCmd = (args, deps) => runRemoteCommand(args, deps.flags ?? {}, deps.renderer);
557
+ export {
558
+ remoteCmd,
559
+ runRemoteCommand
560
+ };
561
+ //# sourceMappingURL=remote-O5WXHLWV.js.map