@wrongstack/cli 1.0.24 → 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.
- package/data/providers.json +44 -0
- package/dist/{acp-AJWSKWE5.js → acp-LI665LO2.js} +5 -5
- package/dist/ascii-mode-SG4PRNLP.js +7 -0
- package/dist/{auth-GRBZ7Y3D.js → auth-TE55JF4Z.js} +14 -14
- package/dist/{bench-N5K7Z5O3.js → bench-GTLPM22B.js} +2 -2
- package/dist/boot/append-system-prompt.d.ts +10 -0
- package/dist/boot/ascii-mode.d.ts +12 -0
- package/dist/boot/container-wiring.d.ts +4 -2
- package/dist/boot/dispatch-singleshot.d.ts +4 -1
- package/dist/boot/mcp-config-flag.d.ts +22 -0
- package/dist/boot/piped-stdin.d.ts +40 -0
- package/dist/boot/restricted-mode.d.ts +15 -0
- package/dist/boot/safe-mode.d.ts +30 -0
- package/dist/boot/single-shot-budget.d.ts +30 -0
- package/dist/boot/stream-json.d.ts +36 -0
- package/dist/boot/structured-output.d.ts +16 -0
- package/dist/boot/system-prompt-builder.d.ts +2 -0
- package/dist/boot/tool-restriction-flags.d.ts +36 -0
- package/dist/boot/worktree-sessions.d.ts +21 -0
- package/dist/boot-provider-selection.d.ts +67 -0
- package/dist/boot.d.ts +1 -38
- package/dist/{chunk-LFMVHT3R.js → chunk-2ERX3RPN.js} +11 -13
- package/dist/chunk-4TVYGG6G.js +62 -0
- package/dist/chunk-5JYDKG5B.js +154 -0
- package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
- package/dist/chunk-BEHGRNOQ.js +58 -0
- package/dist/chunk-CZTXF6HY.js +140 -0
- package/dist/{chunk-SKPDE62W.js → chunk-EPR42SWM.js} +36 -6
- package/dist/{chunk-YBUCXXYZ.js → chunk-F5DCFAKI.js} +1 -1
- package/dist/chunk-HLSHFXTO.js +97 -0
- package/dist/{chunk-BLD5DSVA.js → chunk-KJS5543T.js} +2 -2
- package/dist/{chunk-QFPQXKCG.js → chunk-KOEO4CJG.js} +21 -6
- package/dist/chunk-LBPLTTZ6.js +105 -0
- package/dist/{chunk-3OCUS3VN.js → chunk-LDCGZJTC.js} +4 -4
- package/dist/chunk-LKPEBZBE.js +143 -0
- package/dist/{chunk-DRM7PSW7.js → chunk-OAVBM55Q.js} +3 -3
- package/dist/chunk-PZQBGUVZ.js +18 -0
- package/dist/{chunk-MHEBWVXL.js → chunk-Q4INH2DG.js} +46 -2
- package/dist/{chunk-ZZRSG7VK.js → chunk-RZ4ZDRYA.js} +3 -3
- package/dist/{chunk-U46QR4WR.js → chunk-TI7KLHYX.js} +132 -3
- package/dist/{chunk-PGKJ2OQP.js → chunk-WQV2NP57.js} +5 -5
- package/dist/chunk-X756ZS72.js +329 -0
- package/dist/{chunk-JF2MULWF.js → chunk-YHYINL47.js} +47 -15
- package/dist/{cli-context-34Q5IJRA.js → cli-context-UVGAN3NO.js} +407 -327
- package/dist/{cli-main-AELJDBGU.js → cli-main-TN3KZ2P6.js} +2652 -1978
- package/dist/cli-update-notice.d.ts +2 -1
- package/dist/config-doctor.d.ts +1 -1
- package/dist/{diag-doctor-WIFJH3XH.js → diag-doctor-BIZAFIAT.js} +2 -2
- package/dist/{execution-PYC5FCS4.js → execution-7QIP6GRY.js} +1948 -1666
- package/dist/fleet/host-types.d.ts +3 -1
- package/dist/goal-commands.d.ts +47 -0
- package/dist/goal-host.d.ts +1 -14
- package/dist/{hq-JTUTFI4L.js → hq-GLZ7QESM.js} +6 -6
- package/dist/{hq-server-PC7GZMPT.js → hq-server-YTKDVQPY.js} +2 -2
- package/dist/{hq-service-NW56MUP2.js → hq-service-BMMJAHRT.js} +4 -4
- package/dist/import-claude-code-32PCSIQI.js +236 -0
- package/dist/index.js +12 -8
- package/dist/{mailbox-serve-WDOCH6KR.js → mailbox-serve-NW5VZG7R.js} +3 -3
- package/dist/{modeldiag-36OGAL5C.js → modeldiag-36VODVIH.js} +8 -8
- package/dist/{per-subcommand-help-XE77MTGJ.js → per-subcommand-help-QDAQASPZ.js} +2 -2
- package/dist/permission-prompt.d.ts +14 -0
- package/dist/permission-rules-view.d.ts +18 -0
- package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
- package/dist/{plugin-usage-S5LVGMBW.js → plugin-usage-CIGOOTX3.js} +2 -2
- package/dist/{plugins-RHBIVLI7.js → plugins-HQTXUD6A.js} +4 -2
- package/dist/preflight.d.ts +1 -1
- package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
- package/dist/provider-helpers.d.ts +7 -11
- package/dist/{providers-models-2CFG46RD.js → providers-models-5Z2PJTEW.js} +5 -5
- package/dist/release-asset-download.d.ts +10 -0
- package/dist/release-attestation.d.ts +28 -0
- package/dist/remote/remote-session.d.ts +64 -0
- package/dist/remote/remote-target.d.ts +69 -0
- package/dist/remote-O5WXHLWV.js +561 -0
- package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
- package/dist/services/commit-message.d.ts +2 -1
- package/dist/session-move-P2EC2COA.js +44 -0
- package/dist/session-move.d.ts +20 -0
- package/dist/session-stats.d.ts +10 -0
- package/dist/{sessions-config-IQUH2GXT.js → sessions-config-SXTTLNXA.js} +35 -5
- package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
- package/dist/slash-commands/command-context.d.ts +15 -5
- package/dist/slash-commands/goal-refiner.d.ts +4 -49
- package/dist/slash-commands/mcp.d.ts +2 -1
- package/dist/slash-commands/permissions.d.ts +4 -0
- package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
- package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
- package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
- package/dist/standalone-auto-update.d.ts +65 -0
- package/dist/standalone-update-LZO3MNLH.js +24 -0
- package/dist/standalone-update.d.ts +37 -1
- package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
- package/dist/subcommands/handlers/permissions.d.ts +9 -3
- package/dist/subcommands/handlers/remote.d.ts +6 -0
- package/dist/subcommands/handlers/version-help.d.ts +5 -0
- package/dist/{subcommands-ZTSIN7LG.js → subcommands-Q6ORSB2E.js} +2 -2
- package/dist/tui-resume-callback.d.ts +18 -0
- package/dist/{typesafe-HVEVPA6R.js → typesafe-U7YB2RRV.js} +5 -5
- package/dist/{typesafe-topic-replay-4W73BOLV.js → typesafe-topic-replay-ZILIU3WS.js} +18 -2
- package/dist/{update-7XIOEXTD.js → update-TDZX4IL2.js} +4 -4
- package/dist/{update-check-2POMBHFB.js → update-check-J5ZVKS73.js} +3 -3
- package/dist/version-JB2UAV2A.js +11 -0
- package/dist/version-help-XJJQ7YSH.js +10 -0
- package/dist/webui-server/route-contexts.d.ts +1 -1
- package/dist/webui-server/session-start-payload.d.ts +5 -1
- package/dist/{webui-server-L2SICPTB.js → webui-server-YTHLRFD2.js} +509 -366
- package/dist/webui-server-lifecycle.d.ts +57 -0
- package/dist/webui-server.d.ts +0 -14
- package/dist/wiring/brain-and-orchestration.d.ts +3 -1
- package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/lifecycle-plugins.d.ts +4 -2
- package/dist/wiring/metrics.d.ts +9 -1
- package/dist/wiring/pipeline.d.ts +2 -7
- package/dist/wiring/plugins.d.ts +3 -0
- package/dist/wiring/provider-status.d.ts +1 -0
- package/dist/wiring/provider-utility-tools.d.ts +3 -1
- package/package.json +28 -28
- package/dist/chunk-2BKFWIER.js +0 -98
- package/dist/chunk-424ORSE7.js +0 -56
- package/dist/chunk-QBBF7GIK.js +0 -65
- package/dist/short-circuit-flags-SDT53OML.js +0 -10
- package/dist/standalone-update-MW47J3AK.js +0 -164
- package/dist/version-help-WZ623NWM.js +0 -10
|
@@ -2,6 +2,115 @@ import {
|
|
|
2
2
|
theme
|
|
3
3
|
} from "./chunk-QD544J2B.js";
|
|
4
4
|
|
|
5
|
+
// src/boot/tool-restriction-flags.ts
|
|
6
|
+
function parseToolList(flag, value) {
|
|
7
|
+
if (value === void 0) return void 0;
|
|
8
|
+
if (value === true || typeof value === "string" && !value.trim()) {
|
|
9
|
+
throw new Error(`--${flag} needs a comma-separated list of tool names`);
|
|
10
|
+
}
|
|
11
|
+
const names = String(value).split(/[\s,]+/).map((name) => name.trim()).filter(Boolean);
|
|
12
|
+
const scoped = names.find((name) => name.includes("("));
|
|
13
|
+
if (scoped) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`--${flag}: scoped rules like "${scoped}" are not supported; list whole tool names (exec policy lives in config tools.exec)`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return names;
|
|
19
|
+
}
|
|
20
|
+
function resolveLaunchAllowedTools(flags) {
|
|
21
|
+
return parseToolList("allowed-tools", flags["allowed-tools"]);
|
|
22
|
+
}
|
|
23
|
+
function resolveToolRestriction(flags) {
|
|
24
|
+
const only = parseToolList("only-tools", flags["only-tools"]);
|
|
25
|
+
const deny = parseToolList("disallowed-tools", flags["disallowed-tools"]);
|
|
26
|
+
if (only === void 0 && deny === void 0) return void 0;
|
|
27
|
+
return { ...only ? { only } : {}, deny: deny ?? [] };
|
|
28
|
+
}
|
|
29
|
+
function unknownRestrictedToolNames(restriction, isRegistered) {
|
|
30
|
+
return [...restriction.only ?? [], ...restriction.deny].filter(
|
|
31
|
+
(name) => !name.endsWith("*") && !name.includes("__") && !isRegistered(name)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/boot/restricted-mode.ts
|
|
36
|
+
import { lockToProjectRoot, lockYoloOff, ToolCapabilities } from "@wrongstack/core/security";
|
|
37
|
+
var RESTRICTED_ENV = "WRONGSTACK_RESTRICTED";
|
|
38
|
+
var RESTRICTED_DENY_CAPABILITIES = [
|
|
39
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
40
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
41
|
+
ToolCapabilities.SHELL_EXEC,
|
|
42
|
+
ToolCapabilities.NET_OUTBOUND,
|
|
43
|
+
ToolCapabilities.PACKAGE_INSTALL,
|
|
44
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
45
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
46
|
+
ToolCapabilities.TOOL_MUTATE_ANY,
|
|
47
|
+
ToolCapabilities.MCP_PROXY
|
|
48
|
+
];
|
|
49
|
+
var CONFLICTING_FLAGS = [
|
|
50
|
+
"yolo",
|
|
51
|
+
"yolo-destructive",
|
|
52
|
+
"full-auto",
|
|
53
|
+
"mcp-config",
|
|
54
|
+
// Pre-approving a tool would let writes skip the prompt this mode promises.
|
|
55
|
+
"allowed-tools"
|
|
56
|
+
];
|
|
57
|
+
function isRestrictedMode(flags) {
|
|
58
|
+
return flags["restricted"] === true || process.env[RESTRICTED_ENV] === "1";
|
|
59
|
+
}
|
|
60
|
+
function validateRestrictedMode(flags) {
|
|
61
|
+
if (!isRestrictedMode(flags)) return false;
|
|
62
|
+
const conflict = CONFLICTING_FLAGS.find(
|
|
63
|
+
(name) => flags[name] !== void 0 && flags[name] !== false
|
|
64
|
+
);
|
|
65
|
+
if (conflict) throw new Error(`--${conflict} cannot be combined with --restricted`);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function withRestrictedTools(restriction, restricted) {
|
|
69
|
+
if (!restricted) return restriction;
|
|
70
|
+
return {
|
|
71
|
+
...restriction?.only ? { only: restriction.only } : {},
|
|
72
|
+
deny: [...restriction?.deny ?? [], "mcp__*"],
|
|
73
|
+
denyCapabilities: RESTRICTED_DENY_CAPABILITIES,
|
|
74
|
+
requireDeclaredCapabilities: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var RESTRICTED_NOTICE = "Restricted mode: no shell, network, installs or MCP tools; files stay inside the project; YOLO is off.";
|
|
78
|
+
var announced = false;
|
|
79
|
+
function activateRestrictedMode(flags, write) {
|
|
80
|
+
if (!validateRestrictedMode(flags)) return false;
|
|
81
|
+
lockToProjectRoot();
|
|
82
|
+
lockYoloOff();
|
|
83
|
+
process.env[RESTRICTED_ENV] = "1";
|
|
84
|
+
if (!announced) {
|
|
85
|
+
announced = true;
|
|
86
|
+
write(`${RESTRICTED_NOTICE}
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/boot/safe-mode.ts
|
|
93
|
+
var SAFE_MODE_ENV = "WRONGSTACK_SAFE_MODE";
|
|
94
|
+
function isSafeMode(flags) {
|
|
95
|
+
return flags["safe-mode"] === true || process.env[SAFE_MODE_ENV] === "1";
|
|
96
|
+
}
|
|
97
|
+
function propagateSafeMode(flags) {
|
|
98
|
+
const on = isSafeMode(flags);
|
|
99
|
+
if (on) process.env[SAFE_MODE_ENV] = "1";
|
|
100
|
+
return on;
|
|
101
|
+
}
|
|
102
|
+
var SAFE_MODE_NOTICE = "Safe mode: third-party plugins, hooks, configured MCP servers, skills and instruction overrides are off.";
|
|
103
|
+
var announced2 = false;
|
|
104
|
+
function announceSafeMode(flags, write) {
|
|
105
|
+
const on = propagateSafeMode(flags);
|
|
106
|
+
if (on && !announced2) {
|
|
107
|
+
announced2 = true;
|
|
108
|
+
write(`${SAFE_MODE_NOTICE}
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
return on;
|
|
112
|
+
}
|
|
113
|
+
|
|
5
114
|
// src/diff-renderer.ts
|
|
6
115
|
import { color, sanitizeTerminalText } from "@wrongstack/core/utils";
|
|
7
116
|
function diffLineStyle(line) {
|
|
@@ -25,6 +134,15 @@ import {
|
|
|
25
134
|
unifiedDiff,
|
|
26
135
|
writeOut
|
|
27
136
|
} from "@wrongstack/core/utils";
|
|
137
|
+
function makeHeadlessPromptDelegate(write = (text) => process.stderr.write(text)) {
|
|
138
|
+
return async (tool) => {
|
|
139
|
+
write(
|
|
140
|
+
`${theme.warn("\u26A0")} ${tool.name} needs approval but no terminal is attached to ask \u2014 denied. Grant it up front with --allowed-tools ${tool.name} or --yolo.
|
|
141
|
+
`
|
|
142
|
+
);
|
|
143
|
+
return "no";
|
|
144
|
+
};
|
|
145
|
+
}
|
|
28
146
|
function makePromptDelegate(reader) {
|
|
29
147
|
return async (tool, input, suggestedPattern, signal) => {
|
|
30
148
|
if (signal?.aborted) return "no";
|
|
@@ -57,8 +175,11 @@ ${theme.warn("\u26A0 APPROVAL REQUIRED")} ${theme.primary("\u2502")} ${theme.bol
|
|
|
57
175
|
return answer;
|
|
58
176
|
};
|
|
59
177
|
}
|
|
178
|
+
function makeStdinPromptDelegate(reader, stdinIsTTY = process.stdin.isTTY === true) {
|
|
179
|
+
return stdinIsTTY ? makePromptDelegate(reader) : makeHeadlessPromptDelegate();
|
|
180
|
+
}
|
|
60
181
|
function makeConfirmAwaiter(reader) {
|
|
61
|
-
const delegate =
|
|
182
|
+
const delegate = makeStdinPromptDelegate(reader);
|
|
62
183
|
return async (tool, input, _toolUseId, suggestedPattern) => {
|
|
63
184
|
const result = await delegate(tool, input, suggestedPattern);
|
|
64
185
|
return result;
|
|
@@ -470,12 +591,20 @@ on disconnect with exponential backoff, up to 3 attempts"._
|
|
|
470
591
|
|
|
471
592
|
export {
|
|
472
593
|
renderDiff,
|
|
473
|
-
|
|
594
|
+
makeStdinPromptDelegate,
|
|
474
595
|
makeConfirmAwaiter,
|
|
596
|
+
resolveLaunchAllowedTools,
|
|
597
|
+
resolveToolRestriction,
|
|
598
|
+
unknownRestrictedToolNames,
|
|
599
|
+
isRestrictedMode,
|
|
600
|
+
withRestrictedTools,
|
|
601
|
+
activateRestrictedMode,
|
|
602
|
+
isSafeMode,
|
|
603
|
+
announceSafeMode,
|
|
475
604
|
isSimpleUiFullAuto,
|
|
476
605
|
applySimpleUiFullAutoProfile,
|
|
477
606
|
configureSimpleUiRuntimeContext,
|
|
478
607
|
detectProjectFacts,
|
|
479
608
|
renderAgentsTemplate
|
|
480
609
|
};
|
|
481
|
-
//# sourceMappingURL=chunk-
|
|
610
|
+
//# sourceMappingURL=chunk-TI7KLHYX.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
helpCmd,
|
|
3
3
|
versionCmd
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5JYDKG5B.js";
|
|
5
5
|
import {
|
|
6
6
|
parseArgs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Q4INH2DG.js";
|
|
8
8
|
|
|
9
9
|
// src/boot/short-circuit-flags.ts
|
|
10
10
|
async function handleHelpVersionShortCircuit(argv) {
|
|
@@ -22,11 +22,11 @@ async function handleHelpVersionShortCircuit(argv) {
|
|
|
22
22
|
}
|
|
23
23
|
if (earlyFlags["help"] === true) {
|
|
24
24
|
if (positional.length > 0) {
|
|
25
|
-
const { subcommandNames } = await import("./subcommands-
|
|
25
|
+
const { subcommandNames } = await import("./subcommands-Q6ORSB2E.js");
|
|
26
26
|
if (subcommandNames.includes(positional[0])) {
|
|
27
27
|
const first = positional[0];
|
|
28
28
|
const deep = positional[1];
|
|
29
|
-
const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-
|
|
29
|
+
const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-QDAQASPZ.js");
|
|
30
30
|
if (deep && renderDeepHelp(`${first}:${deep}`, stubRenderer)) {
|
|
31
31
|
return 0;
|
|
32
32
|
}
|
|
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
|
|
|
43
43
|
export {
|
|
44
44
|
handleHelpVersionShortCircuit
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
46
|
+
//# sourceMappingURL=chunk-WQV2NP57.js.map
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STANDALONE_TARGET
|
|
3
|
+
} from "./chunk-F5DCFAKI.js";
|
|
4
|
+
|
|
5
|
+
// src/standalone-update.ts
|
|
6
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { FetchError } from "@wrongstack/core/types";
|
|
10
|
+
|
|
11
|
+
// src/release-asset-download.ts
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import * as fsp from "node:fs/promises";
|
|
14
|
+
async function downloadReleaseAsset(url, timeoutMs, maxBytes) {
|
|
15
|
+
const res = await fetch(url, {
|
|
16
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
17
|
+
headers: { "User-Agent": "wrongstack-cli" },
|
|
18
|
+
redirect: "follow"
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) throw new Error(`download ${url} responded ${res.status}`);
|
|
21
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
22
|
+
if (declared > maxBytes) throw new Error(`download ${url} is too large`);
|
|
23
|
+
if (!res.body) return Buffer.alloc(0);
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let received = 0;
|
|
26
|
+
const reader = res.body.getReader();
|
|
27
|
+
try {
|
|
28
|
+
while (true) {
|
|
29
|
+
const { done, value } = await reader.read();
|
|
30
|
+
if (done) break;
|
|
31
|
+
received += value.byteLength;
|
|
32
|
+
if (received > maxBytes) {
|
|
33
|
+
await reader.cancel();
|
|
34
|
+
throw new Error(`download ${url} is too large`);
|
|
35
|
+
}
|
|
36
|
+
chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
reader.releaseLock();
|
|
40
|
+
}
|
|
41
|
+
return Buffer.concat(chunks, received);
|
|
42
|
+
}
|
|
43
|
+
async function downloadReleaseAssetToFile(url, file, options) {
|
|
44
|
+
const timeout = AbortSignal.timeout(options.timeoutMs);
|
|
45
|
+
const res = await fetch(url, {
|
|
46
|
+
signal: options.signal ? AbortSignal.any([options.signal, timeout]) : timeout,
|
|
47
|
+
headers: { "User-Agent": "wrongstack-cli" },
|
|
48
|
+
redirect: "follow"
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) throw new Error(`download ${url} responded ${res.status}`);
|
|
51
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
52
|
+
if (declared > options.maxBytes) throw new Error(`download ${url} is too large`);
|
|
53
|
+
const hash = createHash("sha256");
|
|
54
|
+
const out = await fsp.open(file, "wx", 384);
|
|
55
|
+
let received = 0;
|
|
56
|
+
try {
|
|
57
|
+
if (res.body) {
|
|
58
|
+
const reader = res.body.getReader();
|
|
59
|
+
try {
|
|
60
|
+
while (true) {
|
|
61
|
+
const { done, value } = await reader.read();
|
|
62
|
+
if (done) break;
|
|
63
|
+
received += value.byteLength;
|
|
64
|
+
if (received > options.maxBytes) {
|
|
65
|
+
await reader.cancel();
|
|
66
|
+
throw new Error(`download ${url} is too large`);
|
|
67
|
+
}
|
|
68
|
+
hash.update(value);
|
|
69
|
+
await out.write(value);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
reader.releaseLock();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
await out.sync();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
await out.close().catch(() => void 0);
|
|
78
|
+
await fsp.rm(file, { force: true });
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
await out.close();
|
|
82
|
+
return hash.digest("hex");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/release-attestation.ts
|
|
86
|
+
var RELEASES_REPO = "WrongStack/WrongStack";
|
|
87
|
+
var RELEASES_REPO_ID = 1237163046;
|
|
88
|
+
var REPO_URL = `https://github.com/${RELEASES_REPO}`;
|
|
89
|
+
var IN_TOTO_PAYLOAD = "application/vnd.in-toto+json";
|
|
90
|
+
var IN_TOTO_STATEMENT = "https://in-toto.io/Statement/v1";
|
|
91
|
+
var SLSA_PROVENANCE = "https://slsa.dev/provenance/v1";
|
|
92
|
+
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
93
|
+
function statementOf(attestation) {
|
|
94
|
+
const envelope = obj(obj(attestation["bundle"])?.["dsseEnvelope"]);
|
|
95
|
+
if (envelope?.["payloadType"] !== IN_TOTO_PAYLOAD) return void 0;
|
|
96
|
+
const payload = envelope["payload"];
|
|
97
|
+
if (typeof payload !== "string") return void 0;
|
|
98
|
+
try {
|
|
99
|
+
return obj(JSON.parse(Buffer.from(payload, "base64").toString("utf8")));
|
|
100
|
+
} catch {
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function vouches(attestation, subject) {
|
|
105
|
+
if (attestation["repository_id"] !== RELEASES_REPO_ID) return false;
|
|
106
|
+
const statement = statementOf(attestation);
|
|
107
|
+
if (statement?.["_type"] !== IN_TOTO_STATEMENT) return false;
|
|
108
|
+
if (statement["predicateType"] !== SLSA_PROVENANCE) return false;
|
|
109
|
+
const subjects = Array.isArray(statement["subject"]) ? statement["subject"] : [];
|
|
110
|
+
const named = subjects.some((entry) => {
|
|
111
|
+
const s = obj(entry);
|
|
112
|
+
return s?.["name"] === subject.assetName && obj(s["digest"])?.["sha256"] === subject.sha256;
|
|
113
|
+
});
|
|
114
|
+
if (!named) return false;
|
|
115
|
+
const tag = `refs/tags/v${subject.version}`;
|
|
116
|
+
const predicate = obj(statement["predicate"]);
|
|
117
|
+
const build = obj(predicate?.["buildDefinition"]);
|
|
118
|
+
const workflow = obj(obj(build?.["externalParameters"])?.["workflow"]);
|
|
119
|
+
const github = obj(obj(build?.["internalParameters"])?.["github"]);
|
|
120
|
+
const builder = obj(obj(predicate?.["runDetails"])?.["builder"]);
|
|
121
|
+
return workflow?.["repository"] === REPO_URL && workflow["path"] === ".github/workflows/release.yml" && workflow["ref"] === tag && builder?.["id"] === `${REPO_URL}/.github/workflows/binaries.yml@${tag}` && github?.["event_name"] === "push" && github["runner_environment"] === "github-hosted";
|
|
122
|
+
}
|
|
123
|
+
function attestationsVouchFor(response, subject) {
|
|
124
|
+
const list = obj(response)?.["attestations"];
|
|
125
|
+
if (!Array.isArray(list)) return false;
|
|
126
|
+
return list.some((entry) => {
|
|
127
|
+
const attestation = obj(entry);
|
|
128
|
+
return attestation !== void 0 && vouches(attestation, subject);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function verifyReleaseAttestation(subject, signal) {
|
|
132
|
+
const url = `https://api.github.com/repos/${RELEASES_REPO}/attestations/sha256:${subject.sha256}`;
|
|
133
|
+
const timeout = AbortSignal.timeout(3e4);
|
|
134
|
+
const res = await fetch(url, {
|
|
135
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
136
|
+
headers: { Accept: "application/vnd.github+json", "User-Agent": "wrongstack-cli" }
|
|
137
|
+
});
|
|
138
|
+
if (res.status === 404) {
|
|
139
|
+
throw new Error(`no build attestation exists for ${subject.assetName}`);
|
|
140
|
+
}
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
const limited = res.status === 403 || res.status === 429;
|
|
143
|
+
throw new Error(
|
|
144
|
+
limited ? "GitHub API rate limit reached while checking the build attestation; try again later" : `GitHub attestations API responded ${res.status}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (!attestationsVouchFor(await res.json(), subject)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`${subject.assetName} has no attestation from the v${subject.version} release workflow`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/standalone-update.ts
|
|
155
|
+
var API_LATEST = `https://api.github.com/repos/${RELEASES_REPO}/releases/latest`;
|
|
156
|
+
var MAX_BINARY_BYTES = 512 * 1024 * 1024;
|
|
157
|
+
var MAX_CHECKSUM_BYTES = 1024 * 1024;
|
|
158
|
+
function standaloneAssetName(target) {
|
|
159
|
+
const name = `wstack-${target.replace(/^bun-/, "")}`;
|
|
160
|
+
return target.startsWith("bun-windows") ? `${name}.exe` : name;
|
|
161
|
+
}
|
|
162
|
+
async function fetchLatestRelease(timeoutMs, signal) {
|
|
163
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
164
|
+
const res = await fetch(API_LATEST, {
|
|
165
|
+
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
|
166
|
+
headers: { Accept: "application/vnd.github+json", "User-Agent": "wrongstack-cli" }
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
throw new FetchError({
|
|
170
|
+
message: `GitHub releases responded ${res.status}`,
|
|
171
|
+
status: res.status,
|
|
172
|
+
context: { op: "checkForUpdate", registry: "github-releases", url: API_LATEST }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const data = await res.json();
|
|
176
|
+
if (typeof data.tag_name !== "string") throw new Error("Latest release has no tag");
|
|
177
|
+
const assets = Array.isArray(data.assets) ? data.assets.filter(
|
|
178
|
+
(a) => typeof a?.name === "string" && typeof a?.browser_download_url === "string" && (a.digest === void 0 || a.digest === null || typeof a.digest === "string")
|
|
179
|
+
) : [];
|
|
180
|
+
return { version: data.tag_name.replace(/^v/, ""), assets };
|
|
181
|
+
}
|
|
182
|
+
async function findStandaloneRelease(target, signal) {
|
|
183
|
+
const release = await fetchLatestRelease(15e3, signal);
|
|
184
|
+
const assetName = standaloneAssetName(target);
|
|
185
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
186
|
+
const sums = release.assets.find((a) => a.name === "SHA256SUMS");
|
|
187
|
+
if (!asset || !sums) throw new Error(`release v${release.version} has no ${assetName} build`);
|
|
188
|
+
return { version: release.version, assetName, asset, sums };
|
|
189
|
+
}
|
|
190
|
+
async function verifyStandaloneDigest(release, sha256, signal) {
|
|
191
|
+
const sumsText = (await downloadReleaseAsset(release.sums.browser_download_url, 3e4, MAX_CHECKSUM_BYTES)).toString("utf8");
|
|
192
|
+
if (parseSha256Sums(sumsText).get(release.assetName) !== sha256) {
|
|
193
|
+
throw new Error("checksum does not match the release SHA256SUMS");
|
|
194
|
+
}
|
|
195
|
+
const digest = release.asset.digest;
|
|
196
|
+
if (typeof digest === "string" && digest !== `sha256:${sha256}`) {
|
|
197
|
+
throw new Error("checksum does not match the digest GitHub recorded for the asset");
|
|
198
|
+
}
|
|
199
|
+
await verifyReleaseAttestation(
|
|
200
|
+
{ assetName: release.assetName, sha256, version: release.version },
|
|
201
|
+
signal
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
async function fetchLatestStandaloneVersion(timeoutMs = 3e3, signal) {
|
|
205
|
+
return (await fetchLatestRelease(timeoutMs, signal)).version;
|
|
206
|
+
}
|
|
207
|
+
function parseSha256Sums(text) {
|
|
208
|
+
const sums = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const line of text.split(/\r?\n/)) {
|
|
210
|
+
const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
|
|
211
|
+
if (match?.[1] && match[2]) {
|
|
212
|
+
const name = match[2].trim();
|
|
213
|
+
if (sums.has(name)) throw new Error(`Duplicate SHA256SUMS entry: ${name}`);
|
|
214
|
+
sums.set(name, match[1].toLowerCase());
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return sums;
|
|
218
|
+
}
|
|
219
|
+
function replaceExecutable(target, bytes, platform = process.platform) {
|
|
220
|
+
const staged = stagingPath(target);
|
|
221
|
+
fs.writeFileSync(staged, bytes, { mode: 493, flag: "wx" });
|
|
222
|
+
swapIntoPlace(target, staged, platform);
|
|
223
|
+
}
|
|
224
|
+
function installExecutableFrom(target, source, sha256, platform = process.platform) {
|
|
225
|
+
const staged = stagingPath(target);
|
|
226
|
+
fs.copyFileSync(source, staged, fs.constants.COPYFILE_EXCL);
|
|
227
|
+
try {
|
|
228
|
+
if (sha256File(staged) !== sha256) throw new Error("staged update does not match its digest");
|
|
229
|
+
fs.chmodSync(staged, 493);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
fs.rmSync(staged, { force: true });
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
swapIntoPlace(target, staged, platform);
|
|
235
|
+
}
|
|
236
|
+
function stagingPath(target) {
|
|
237
|
+
return `${target}.new-${randomBytes(9).toString("hex")}`;
|
|
238
|
+
}
|
|
239
|
+
function sha256File(file) {
|
|
240
|
+
const hash = createHash2("sha256");
|
|
241
|
+
const fd = fs.openSync(file, "r");
|
|
242
|
+
try {
|
|
243
|
+
const chunk = Buffer.allocUnsafe(1024 * 1024);
|
|
244
|
+
let read = fs.readSync(fd, chunk);
|
|
245
|
+
while (read > 0) {
|
|
246
|
+
hash.update(chunk.subarray(0, read));
|
|
247
|
+
read = fs.readSync(fd, chunk);
|
|
248
|
+
}
|
|
249
|
+
} finally {
|
|
250
|
+
fs.closeSync(fd);
|
|
251
|
+
}
|
|
252
|
+
return hash.digest("hex");
|
|
253
|
+
}
|
|
254
|
+
function swapIntoPlace(target, staged, platform) {
|
|
255
|
+
try {
|
|
256
|
+
if (platform === "win32") {
|
|
257
|
+
const aside = `${target}.old`;
|
|
258
|
+
fs.rmSync(aside, { force: true });
|
|
259
|
+
fs.renameSync(target, aside);
|
|
260
|
+
try {
|
|
261
|
+
fs.renameSync(staged, target);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
fs.renameSync(aside, target);
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
fs.renameSync(staged, target);
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
fs.rmSync(staged, { force: true });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function updateStandaloneBinary(options) {
|
|
274
|
+
const { renderer } = options;
|
|
275
|
+
if (!STANDALONE_TARGET) {
|
|
276
|
+
renderer.write("Update failed: this executable does not record its build target.\n");
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const release = await findStandaloneRelease(STANDALONE_TARGET);
|
|
281
|
+
renderer.write(`Updating wrongstack from v${options.current} to v${release.version}...
|
|
282
|
+
`);
|
|
283
|
+
const executable = fs.realpathSync(process.execPath);
|
|
284
|
+
const staged = options.staged;
|
|
285
|
+
if (staged && staged.version === release.version) {
|
|
286
|
+
renderer.write(`Installing the v${staged.version} build already downloaded
|
|
287
|
+
`);
|
|
288
|
+
installExecutableFrom(executable, staged.file, staged.sha256);
|
|
289
|
+
} else {
|
|
290
|
+
renderer.write(`Downloading ${release.assetName}
|
|
291
|
+
`);
|
|
292
|
+
const bytes = await downloadReleaseAsset(
|
|
293
|
+
release.asset.browser_download_url,
|
|
294
|
+
6e5,
|
|
295
|
+
MAX_BINARY_BYTES
|
|
296
|
+
);
|
|
297
|
+
renderer.write("Verifying the checksum and the build attestation\n");
|
|
298
|
+
await verifyStandaloneDigest(release, createHash2("sha256").update(bytes).digest("hex"));
|
|
299
|
+
replaceExecutable(executable, bytes);
|
|
300
|
+
}
|
|
301
|
+
renderer.write(
|
|
302
|
+
`
|
|
303
|
+
Updated ${path.basename(executable)} to v${release.version}. Restart wrongstack to use it.
|
|
304
|
+
`
|
|
305
|
+
);
|
|
306
|
+
return 0;
|
|
307
|
+
} catch (err) {
|
|
308
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
309
|
+
const permission = /EACCES|EPERM/.test(msg) ? "\nThe executable is not writable by this user; rerun with the permissions it was installed with." : "";
|
|
310
|
+
renderer.write(`
|
|
311
|
+
Update failed: ${msg}${permission}
|
|
312
|
+
`);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export {
|
|
318
|
+
downloadReleaseAssetToFile,
|
|
319
|
+
MAX_BINARY_BYTES,
|
|
320
|
+
standaloneAssetName,
|
|
321
|
+
findStandaloneRelease,
|
|
322
|
+
verifyStandaloneDigest,
|
|
323
|
+
fetchLatestStandaloneVersion,
|
|
324
|
+
parseSha256Sums,
|
|
325
|
+
replaceExecutable,
|
|
326
|
+
installExecutableFrom,
|
|
327
|
+
updateStandaloneBinary
|
|
328
|
+
};
|
|
329
|
+
//# sourceMappingURL=chunk-X756ZS72.js.map
|
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
// src/wiring/plugins.ts
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { loadPlugins, resolvePluginConfig, resolvePluginEnablement } from "@wrongstack/core/plugin";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
OFFICIAL_PLUGIN_FACTORIES,
|
|
14
|
+
OFFICIAL_PLUGIN_SPECIFIERS
|
|
15
|
+
} from "@wrongstack/plugins/factories";
|
|
13
16
|
|
|
14
17
|
// src/plugin-api-factory.ts
|
|
15
18
|
import { DefaultPluginAPI } from "@wrongstack/core/plugin";
|
|
@@ -109,6 +112,28 @@ var BUILTIN_PLUGIN_FACTORIES = [
|
|
|
109
112
|
// ── Telegram plugin ─────────────────────────────────────────────────
|
|
110
113
|
async () => (await import("@wrongstack/telegram")).default
|
|
111
114
|
];
|
|
115
|
+
var BUILTIN_PLUGIN_NAME_HINTS = [
|
|
116
|
+
"wstack-prompts",
|
|
117
|
+
"wstack-sync",
|
|
118
|
+
"wstack-cloud-config-sync",
|
|
119
|
+
"wstack-chimera",
|
|
120
|
+
"wstack-auto-review",
|
|
121
|
+
"wstack-specialist-triggers",
|
|
122
|
+
"wstack-skills",
|
|
123
|
+
...OFFICIAL_PLUGIN_SPECIFIERS.map((specifier) => specifier.slice("@wrongstack/plugins/".length)),
|
|
124
|
+
"@wrongstack/plug-lsp",
|
|
125
|
+
"telegram"
|
|
126
|
+
];
|
|
127
|
+
function builtinEnablement(name, config, aliases) {
|
|
128
|
+
const auditEntry = PLUGIN_AUDIT_ENTRIES.find((entry) => entry.name === name);
|
|
129
|
+
return resolvePluginEnablement({
|
|
130
|
+
name,
|
|
131
|
+
aliases,
|
|
132
|
+
defaultState: auditEntry?.defaultState ?? "active",
|
|
133
|
+
config,
|
|
134
|
+
matches: (spec) => (builtinPluginNameFromSpec(spec) ?? spec) === name
|
|
135
|
+
});
|
|
136
|
+
}
|
|
112
137
|
async function setupPlugins(params) {
|
|
113
138
|
const {
|
|
114
139
|
config,
|
|
@@ -133,24 +158,30 @@ async function setupPlugins(params) {
|
|
|
133
158
|
vault,
|
|
134
159
|
pipelines,
|
|
135
160
|
paths,
|
|
136
|
-
hookRegistry
|
|
161
|
+
hookRegistry,
|
|
162
|
+
safeMode
|
|
137
163
|
} = params;
|
|
138
164
|
const builtinPlugins = [];
|
|
139
165
|
if (paths && config.features?.plugins !== false) {
|
|
140
|
-
for (const factory of BUILTIN_PLUGIN_FACTORIES) {
|
|
166
|
+
for (const [index, factory] of BUILTIN_PLUGIN_FACTORIES.entries()) {
|
|
167
|
+
const nameHint = BUILTIN_PLUGIN_NAME_HINTS[index];
|
|
168
|
+
if (nameHint && BUILTIN_PLUGIN_NAME_HINTS.length === BUILTIN_PLUGIN_FACTORIES.length) {
|
|
169
|
+
const { enabled, source } = builtinEnablement(
|
|
170
|
+
nameHint,
|
|
171
|
+
config,
|
|
172
|
+
nameHint === "telegram" ? ["@wrongstack/telegram"] : void 0
|
|
173
|
+
);
|
|
174
|
+
if (!enabled) {
|
|
175
|
+
if (source !== "default") {
|
|
176
|
+
log.info(`[setupPlugins] built-in plugin "${nameHint}" disabled by ${source}`);
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
141
181
|
try {
|
|
142
182
|
const plugin = await factory();
|
|
143
183
|
if (!plugin) continue;
|
|
144
|
-
const
|
|
145
|
-
const { enabled, source } = resolvePluginEnablement({
|
|
146
|
-
name: plugin.name,
|
|
147
|
-
aliases: plugin.configAliases,
|
|
148
|
-
// Built-ins outside the audit catalog (prompts, sync, skills, …)
|
|
149
|
-
// are infrastructure — they run unless explicitly turned off.
|
|
150
|
-
defaultState: auditEntry?.defaultState ?? "active",
|
|
151
|
-
config,
|
|
152
|
-
matches: (spec) => (builtinPluginNameFromSpec(spec) ?? spec) === plugin.name
|
|
153
|
-
});
|
|
184
|
+
const { enabled, source } = builtinEnablement(plugin.name, config, plugin.configAliases);
|
|
154
185
|
if (!enabled) {
|
|
155
186
|
if (source !== "default") {
|
|
156
187
|
log.info(`[setupPlugins] built-in plugin "${plugin.name}" disabled by ${source}`);
|
|
@@ -164,7 +195,7 @@ async function setupPlugins(params) {
|
|
|
164
195
|
}
|
|
165
196
|
}
|
|
166
197
|
}
|
|
167
|
-
const userPlugins = config.features?.plugins === false ? [] : await loadExternalPlugins(
|
|
198
|
+
const userPlugins = config.features?.plugins === false || safeMode === true ? [] : await loadExternalPlugins(
|
|
168
199
|
{
|
|
169
200
|
config,
|
|
170
201
|
log,
|
|
@@ -272,6 +303,7 @@ export {
|
|
|
272
303
|
pluginNameFromSpec,
|
|
273
304
|
builtinPluginNameFromSpec,
|
|
274
305
|
BUILTIN_PLUGIN_FACTORIES,
|
|
306
|
+
BUILTIN_PLUGIN_NAME_HINTS,
|
|
275
307
|
setupPlugins
|
|
276
308
|
};
|
|
277
|
-
//# sourceMappingURL=chunk-
|
|
309
|
+
//# sourceMappingURL=chunk-YHYINL47.js.map
|