@wrongstack/tools 0.273.0 → 0.274.0
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/audit.js.map +1 -1
- package/dist/bash.js +193 -14
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +461 -161
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +24 -16
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +24 -16
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/exec.d.ts +20 -1
- package/dist/exec.js +132 -71
- package/dist/exec.js.map +1 -1
- package/dist/format.js.map +1 -1
- package/dist/glob.js +61 -11
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +92 -39
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +111 -2
- package/dist/index.js +532 -191
- package/dist/index.js.map +1 -1
- package/dist/install.js.map +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +461 -161
- package/dist/pack.js.map +1 -1
- package/dist/test.js.map +1 -1
- package/dist/typecheck.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7,11 +7,12 @@ import { spawn, execFileSync } from 'node:child_process';
|
|
|
7
7
|
import * as os2 from 'node:os';
|
|
8
8
|
import * as fs8 from 'node:fs';
|
|
9
9
|
import { statSync, mkdirSync, createWriteStream } from 'node:fs';
|
|
10
|
+
import { toErrorMessage as toErrorMessage$1 } from '@wrongstack/core/utils/error';
|
|
10
11
|
import * as dns from 'node:dns/promises';
|
|
11
12
|
import * as net from 'node:net';
|
|
12
13
|
import { Agent } from 'undici';
|
|
13
14
|
import TurndownService from 'turndown';
|
|
14
|
-
import { toErrorMessage as toErrorMessage$
|
|
15
|
+
import { toErrorMessage as toErrorMessage$2 } from '@wrongstack/core/utils';
|
|
15
16
|
import { randomUUID } from 'node:crypto';
|
|
16
17
|
import { createRequire } from 'node:module';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
@@ -777,7 +778,27 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
777
778
|
await walk(base);
|
|
778
779
|
return results;
|
|
779
780
|
}
|
|
781
|
+
|
|
782
|
+
// src/_concurrency.ts
|
|
783
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
784
|
+
if (items.length === 0) return [];
|
|
785
|
+
const effectiveLimit = Math.max(1, Math.min(limit | 0, items.length));
|
|
786
|
+
const results = new Array(items.length);
|
|
787
|
+
let nextIndex = 0;
|
|
788
|
+
const worker2 = async () => {
|
|
789
|
+
while (true) {
|
|
790
|
+
const i = nextIndex++;
|
|
791
|
+
if (i >= items.length) return;
|
|
792
|
+
results[i] = await fn(items[i]);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
await Promise.all(Array.from({ length: effectiveLimit }, worker2));
|
|
796
|
+
return results;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/glob.ts
|
|
780
800
|
var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"];
|
|
801
|
+
var WALK_CONCURRENCY = 16;
|
|
781
802
|
var globTool = {
|
|
782
803
|
name: "glob",
|
|
783
804
|
category: "Filesystem",
|
|
@@ -815,6 +836,22 @@ var globTool = {
|
|
|
815
836
|
const re = compileGlob(input.pattern);
|
|
816
837
|
const results = [];
|
|
817
838
|
let truncated = false;
|
|
839
|
+
const pushResult = async (full) => {
|
|
840
|
+
if (truncated || results.length >= limit) {
|
|
841
|
+
truncated = true;
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
const st = await fs7.stat(full);
|
|
846
|
+
if (truncated || results.length >= limit) {
|
|
847
|
+
truncated = true;
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
results.push({ rel: full, mtime: st.mtimeMs });
|
|
851
|
+
if (results.length >= limit) truncated = true;
|
|
852
|
+
} catch {
|
|
853
|
+
}
|
|
854
|
+
};
|
|
818
855
|
const walk = async (dir, relPrefix) => {
|
|
819
856
|
if (results.length >= limit) {
|
|
820
857
|
truncated = true;
|
|
@@ -826,6 +863,8 @@ var globTool = {
|
|
|
826
863
|
} catch {
|
|
827
864
|
return;
|
|
828
865
|
}
|
|
866
|
+
const subdirs = [];
|
|
867
|
+
const matchedFiles = [];
|
|
829
868
|
for (const e of entries) {
|
|
830
869
|
const name = e.name;
|
|
831
870
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
@@ -833,22 +872,36 @@ var globTool = {
|
|
|
833
872
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
834
873
|
const full = path.join(dir, name);
|
|
835
874
|
if (e.isDirectory()) {
|
|
836
|
-
|
|
837
|
-
if (truncated) return;
|
|
875
|
+
subdirs.push({ full, rel });
|
|
838
876
|
} else if (e.isFile()) {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
877
|
+
re.lastIndex = 0;
|
|
878
|
+
const relMatch = re.test(rel);
|
|
879
|
+
re.lastIndex = 0;
|
|
880
|
+
const nameMatch = re.test(name);
|
|
881
|
+
if (relMatch || nameMatch) {
|
|
882
|
+
matchedFiles.push(full);
|
|
883
|
+
}
|
|
884
|
+
} else if (e.isSymbolicLink()) {
|
|
885
|
+
try {
|
|
886
|
+
const st = await fs7.stat(full);
|
|
887
|
+
if (st.isDirectory()) {
|
|
888
|
+
subdirs.push({ full, rel });
|
|
889
|
+
} else if (st.isFile()) {
|
|
890
|
+
re.lastIndex = 0;
|
|
891
|
+
const relMatch = re.test(rel);
|
|
892
|
+
re.lastIndex = 0;
|
|
893
|
+
const nameMatch = re.test(name);
|
|
894
|
+
if (relMatch || nameMatch) matchedFiles.push(full);
|
|
848
895
|
}
|
|
896
|
+
} catch {
|
|
849
897
|
}
|
|
850
898
|
}
|
|
899
|
+
if (truncated) return;
|
|
851
900
|
}
|
|
901
|
+
await mapWithConcurrency(matchedFiles, WALK_CONCURRENCY, pushResult);
|
|
902
|
+
if (truncated) return;
|
|
903
|
+
const remainingSubdirs = truncated ? [] : subdirs;
|
|
904
|
+
await mapWithConcurrency(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) => walk(full, rel));
|
|
852
905
|
};
|
|
853
906
|
await walk(base, "");
|
|
854
907
|
results.sort((a, b) => b.mtime - a.mtime);
|
|
@@ -865,6 +918,8 @@ async function readGitignore(dir) {
|
|
|
865
918
|
}
|
|
866
919
|
var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
867
920
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
921
|
+
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
922
|
+
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
868
923
|
var grepTool = {
|
|
869
924
|
name: "grep",
|
|
870
925
|
category: "Search",
|
|
@@ -1089,7 +1144,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1089
1144
|
const re = compiled.regex;
|
|
1090
1145
|
const globRe = input.glob ? compileGlob(input.glob) : null;
|
|
1091
1146
|
const matches = [];
|
|
1092
|
-
const
|
|
1147
|
+
const countOnlyFirstHit = mode === "count" && limit === 1;
|
|
1148
|
+
const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
|
|
1093
1149
|
let total = 0;
|
|
1094
1150
|
let stopped = false;
|
|
1095
1151
|
const scanFile = async (full, name) => {
|
|
@@ -1098,34 +1154,79 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1098
1154
|
if (globRe) globRe.lastIndex = 0;
|
|
1099
1155
|
try {
|
|
1100
1156
|
const stat11 = await fs7.stat(full);
|
|
1101
|
-
if (stat11.size >
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1157
|
+
if (!stat11.isFile() || stat11.size > maxBytes || stopped || signal.aborted) return;
|
|
1158
|
+
const file = await fs7.open(full, "r");
|
|
1159
|
+
try {
|
|
1160
|
+
let bytesReadTotal = 0;
|
|
1161
|
+
let lineNumber = 0;
|
|
1162
|
+
let fileHits = 0;
|
|
1163
|
+
let leftover = "";
|
|
1164
|
+
let binaryChecked = false;
|
|
1165
|
+
const buffer = Buffer.allocUnsafe(Math.min(NATIVE_READ_CHUNK_BYTES, maxBytes));
|
|
1166
|
+
while (!stopped && !signal.aborted && bytesReadTotal < maxBytes) {
|
|
1167
|
+
const remaining = maxBytes - bytesReadTotal;
|
|
1168
|
+
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, remaining), null);
|
|
1169
|
+
if (bytesRead === 0) break;
|
|
1170
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
1171
|
+
if (!binaryChecked) {
|
|
1172
|
+
binaryChecked = true;
|
|
1173
|
+
if (isBinaryBuffer(chunk)) return;
|
|
1174
|
+
}
|
|
1175
|
+
bytesReadTotal += bytesRead;
|
|
1176
|
+
const text = leftover + chunk.toString("utf8");
|
|
1177
|
+
const lines = text.split(/\r?\n/);
|
|
1178
|
+
leftover = lines.pop() ?? "";
|
|
1179
|
+
for (const rawLine of lines) {
|
|
1180
|
+
if (stopped || signal.aborted) break;
|
|
1181
|
+
lineNumber++;
|
|
1182
|
+
const ln = capSubject(rawLine);
|
|
1183
|
+
re.lastIndex = 0;
|
|
1184
|
+
if (!re.test(ln)) continue;
|
|
1185
|
+
fileHits++;
|
|
1186
|
+
total++;
|
|
1187
|
+
if (mode === "content") {
|
|
1188
|
+
if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
|
|
1189
|
+
} else if (mode === "files_with_matches") {
|
|
1190
|
+
if (fileHits === 1 && matches.length < limit) matches.push(full);
|
|
1191
|
+
break;
|
|
1192
|
+
} else if (fileHits === 1 && matches.length < limit) {
|
|
1193
|
+
matches.push(`${full}:${countOnlyFirstHit ? 1 : 0}`);
|
|
1194
|
+
}
|
|
1195
|
+
if (countOnlyFirstHit || mode !== "content" && matches.length >= limit) {
|
|
1196
|
+
stopped = true;
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1116
1199
|
}
|
|
1200
|
+
if (mode === "files_with_matches" && fileHits > 0) break;
|
|
1117
1201
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1202
|
+
if (!stopped && !signal.aborted && leftover.length > 0) {
|
|
1203
|
+
lineNumber++;
|
|
1204
|
+
const ln = capSubject(leftover);
|
|
1205
|
+
re.lastIndex = 0;
|
|
1206
|
+
if (re.test(ln)) {
|
|
1207
|
+
fileHits++;
|
|
1208
|
+
total++;
|
|
1209
|
+
if (mode === "content") {
|
|
1210
|
+
if (matches.length < limit) matches.push(`${full}:${lineNumber}:${ln}`);
|
|
1211
|
+
} else if (mode === "files_with_matches") {
|
|
1212
|
+
if (matches.length < limit) matches.push(full);
|
|
1213
|
+
} else if (matches.length < limit) {
|
|
1214
|
+
matches.push(`${full}:${countOnlyFirstHit ? 1 : fileHits}`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1123
1217
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1218
|
+
if (fileHits > 0) {
|
|
1219
|
+
if (mode === "count") {
|
|
1220
|
+
const idx = matches.findIndex((entry) => entry.startsWith(`${full}:`));
|
|
1221
|
+
if (idx !== -1) matches[idx] = `${full}:${fileHits}`;
|
|
1222
|
+
}
|
|
1223
|
+
if (mode === "files_with_matches" && matches.length >= limit) stopped = true;
|
|
1126
1224
|
}
|
|
1225
|
+
if (mode === "content" && matches.length >= limit) stopped = true;
|
|
1226
|
+
if (mode === "count" && matches.length >= limit && (countOnlyFirstHit || mode !== "count")) stopped = true;
|
|
1227
|
+
} finally {
|
|
1228
|
+
await file.close();
|
|
1127
1229
|
}
|
|
1128
|
-
if (matches.length >= limit) stopped = true;
|
|
1129
1230
|
} catch {
|
|
1130
1231
|
}
|
|
1131
1232
|
};
|
|
@@ -1138,18 +1239,20 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1138
1239
|
return;
|
|
1139
1240
|
}
|
|
1140
1241
|
const files = [];
|
|
1242
|
+
const subdirs = [];
|
|
1141
1243
|
for (const e of entries) {
|
|
1142
1244
|
if (stopped) return;
|
|
1143
1245
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
1144
1246
|
if (e.isSymbolicLink()) continue;
|
|
1145
1247
|
const full = path.join(dir, e.name);
|
|
1146
1248
|
if (e.isDirectory()) {
|
|
1147
|
-
|
|
1249
|
+
subdirs.push(full);
|
|
1148
1250
|
} else if (e.isFile()) {
|
|
1149
1251
|
files.push({ full, name: e.name });
|
|
1150
1252
|
}
|
|
1151
1253
|
}
|
|
1152
1254
|
await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
|
|
1255
|
+
await mapWithConcurrency(subdirs, Math.min(16, NATIVE_SCAN_CONCURRENCY), walk);
|
|
1153
1256
|
};
|
|
1154
1257
|
await walk(base);
|
|
1155
1258
|
return {
|
|
@@ -1159,20 +1262,6 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1159
1262
|
used: "native"
|
|
1160
1263
|
};
|
|
1161
1264
|
}
|
|
1162
|
-
async function mapWithConcurrency(items, concurrency, fn) {
|
|
1163
|
-
if (items.length === 0) return;
|
|
1164
|
-
let next = 0;
|
|
1165
|
-
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
1166
|
-
const workers = Array.from({ length: workerCount }, async () => {
|
|
1167
|
-
for (; ; ) {
|
|
1168
|
-
const idx = next++;
|
|
1169
|
-
if (idx >= items.length) return;
|
|
1170
|
-
const item = items[idx];
|
|
1171
|
-
if (item !== void 0) await fn(item);
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
await Promise.all(workers);
|
|
1175
|
-
}
|
|
1176
1265
|
var SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1177
1266
|
var SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
|
|
1178
1267
|
var sweepStarted = false;
|
|
@@ -1214,7 +1303,7 @@ function createOutputSpool(opts) {
|
|
|
1214
1303
|
let filePath = null;
|
|
1215
1304
|
let failed = false;
|
|
1216
1305
|
let finalized = false;
|
|
1217
|
-
const
|
|
1306
|
+
const open2 = () => {
|
|
1218
1307
|
if (stream || failed) return;
|
|
1219
1308
|
try {
|
|
1220
1309
|
const dir = toolOutputDir();
|
|
@@ -1247,7 +1336,7 @@ function createOutputSpool(opts) {
|
|
|
1247
1336
|
return;
|
|
1248
1337
|
}
|
|
1249
1338
|
head += text;
|
|
1250
|
-
|
|
1339
|
+
open2();
|
|
1251
1340
|
head = "";
|
|
1252
1341
|
return;
|
|
1253
1342
|
}
|
|
@@ -2379,6 +2468,155 @@ async function checkAndBlockKillCommand(command) {
|
|
|
2379
2468
|
}
|
|
2380
2469
|
return { blocked: false };
|
|
2381
2470
|
}
|
|
2471
|
+
function pickShell(platform4, command, env) {
|
|
2472
|
+
const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
|
|
2473
|
+
if (override === "cmd" || override === "cmd.exe") return "cmd";
|
|
2474
|
+
if (override === "powershell" || override === "powershell.exe") return "powershell";
|
|
2475
|
+
if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
|
|
2476
|
+
if (looksLikePowerShell(command)) return "pwsh";
|
|
2477
|
+
return "cmd";
|
|
2478
|
+
}
|
|
2479
|
+
function looksLikePowerShell(command) {
|
|
2480
|
+
if (!command) return false;
|
|
2481
|
+
const trimmed = command.trimStart();
|
|
2482
|
+
if (/\.ps1\b/i.test(trimmed)) return true;
|
|
2483
|
+
if (/^\s*#requires\s/i.test(trimmed)) return true;
|
|
2484
|
+
if (/^\s*param\s*\(/i.test(trimmed)) return true;
|
|
2485
|
+
if (/\$[\w:{]/i.test(trimmed)) return true;
|
|
2486
|
+
if (/\$\(/.test(trimmed)) return true;
|
|
2487
|
+
if (/@\s*['"]/.test(trimmed)) return true;
|
|
2488
|
+
if (/&\s+\$/.test(trimmed)) return true;
|
|
2489
|
+
if (/(^|\s)@\s*\(/.test(trimmed)) return true;
|
|
2490
|
+
if (/(^|\s)@\{/.test(trimmed)) return true;
|
|
2491
|
+
if (/(?:^|[\s\[\(\{,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\]\)\},;])/i.test(trimmed)) {
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
if (PS_VERB_RE.test(trimmed)) return true;
|
|
2495
|
+
if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps|sl|rm|cat|cp|mv)\b/i.test(trimmed)) {
|
|
2496
|
+
return true;
|
|
2497
|
+
}
|
|
2498
|
+
if (looksLikePowerShellExtended(command)) return true;
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2501
|
+
function looksLikePowerShellExtended(command) {
|
|
2502
|
+
if (!command) return false;
|
|
2503
|
+
const trimmed = command.trimStart();
|
|
2504
|
+
if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
|
|
2505
|
+
return true;
|
|
2506
|
+
}
|
|
2507
|
+
if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(trimmed)) {
|
|
2508
|
+
return true;
|
|
2509
|
+
}
|
|
2510
|
+
if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
|
|
2511
|
+
return true;
|
|
2512
|
+
}
|
|
2513
|
+
if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
|
|
2514
|
+
if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
|
|
2515
|
+
return true;
|
|
2516
|
+
}
|
|
2517
|
+
if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
|
|
2518
|
+
if (/(?:^|\s)[-/\/](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(trimmed)) {
|
|
2519
|
+
return true;
|
|
2520
|
+
}
|
|
2521
|
+
return false;
|
|
2522
|
+
}
|
|
2523
|
+
function wrapPowerShellScript(command) {
|
|
2524
|
+
const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
|
|
2525
|
+
return "\uFEFF" + bootstrap + "\ntry {\n" + command + "\n} finally { exit $LASTEXITCODE }";
|
|
2526
|
+
}
|
|
2527
|
+
var PS_VERB_RE = new RegExp(
|
|
2528
|
+
// Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
|
|
2529
|
+
"(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
|
|
2530
|
+
"i"
|
|
2531
|
+
);
|
|
2532
|
+
function shellArgs(shell) {
|
|
2533
|
+
if (shell === "powershell" || shell === "pwsh") {
|
|
2534
|
+
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"];
|
|
2535
|
+
}
|
|
2536
|
+
return ["/c"];
|
|
2537
|
+
}
|
|
2538
|
+
function diagnoseBashism(command, shell) {
|
|
2539
|
+
if (!command) return void 0;
|
|
2540
|
+
const isCmd = shell === "cmd";
|
|
2541
|
+
const hints = [];
|
|
2542
|
+
const add = (h) => {
|
|
2543
|
+
if (!hints.includes(h)) hints.push(h);
|
|
2544
|
+
};
|
|
2545
|
+
if (/\/dev\/null/.test(command)) {
|
|
2546
|
+
add(
|
|
2547
|
+
isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
|
|
2551
|
+
add(
|
|
2552
|
+
isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
|
|
2556
|
+
add(
|
|
2557
|
+
isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
|
|
2561
|
+
add("Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)");
|
|
2562
|
+
}
|
|
2563
|
+
if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
|
|
2564
|
+
add(
|
|
2565
|
+
isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
if (/\bwhich\s+\S/.test(command)) {
|
|
2569
|
+
add(isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`");
|
|
2570
|
+
}
|
|
2571
|
+
if (hints.length === 0) return void 0;
|
|
2572
|
+
const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
|
|
2573
|
+
return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
|
|
2574
|
+
}
|
|
2575
|
+
function resolveWin32Command(cmd) {
|
|
2576
|
+
if (process.platform !== "win32") return cmd;
|
|
2577
|
+
if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
|
|
2578
|
+
return cmd;
|
|
2579
|
+
}
|
|
2580
|
+
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
2581
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path.delimiter);
|
|
2582
|
+
for (const dir of pathDirs) {
|
|
2583
|
+
const base = path.join(dir, cmd);
|
|
2584
|
+
for (const ext of pathext) {
|
|
2585
|
+
const full = `${base}${ext}`;
|
|
2586
|
+
try {
|
|
2587
|
+
fs8.accessSync(full, fs8.constants.X_OK);
|
|
2588
|
+
return full;
|
|
2589
|
+
} catch {
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return cmd;
|
|
2594
|
+
}
|
|
2595
|
+
function resolvePowerShell(cmd) {
|
|
2596
|
+
if (process.platform !== "win32") return cmd;
|
|
2597
|
+
const lower = cmd.toLowerCase();
|
|
2598
|
+
if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
|
|
2599
|
+
return resolveWin32Command(cmd);
|
|
2600
|
+
}
|
|
2601
|
+
const primary = lower.startsWith("pwsh") ? "pwsh.exe" : "powershell.exe";
|
|
2602
|
+
const fallback = lower.startsWith("pwsh") ? "powershell.exe" : "pwsh.exe";
|
|
2603
|
+
const resolved = resolveWin32Command(primary);
|
|
2604
|
+
if (resolved !== primary) {
|
|
2605
|
+
const fb = resolveWin32Command(fallback);
|
|
2606
|
+
return fb === fallback ? cmd : fb;
|
|
2607
|
+
}
|
|
2608
|
+
return resolved;
|
|
2609
|
+
}
|
|
2610
|
+
var WIN32_SHELL_META = /[&|<>\r\n\0]/;
|
|
2611
|
+
function assertSafeWin32ShellArgs(args) {
|
|
2612
|
+
for (const a of args) {
|
|
2613
|
+
if (typeof a === "string" && WIN32_SHELL_META.test(a)) {
|
|
2614
|
+
throw new Error(
|
|
2615
|
+
"win32 shell spawn: argument contains a shell metacharacter (one of & | < > or a newline) that could enable command injection through the .cmd/.bat wrapper \u2014 refusing to run. Offending argument: " + JSON.stringify(a)
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2382
2620
|
|
|
2383
2621
|
// src/bash.ts
|
|
2384
2622
|
var MAX_OUTPUT = 32768;
|
|
@@ -2475,18 +2713,36 @@ var bashTool = {
|
|
|
2475
2713
|
}
|
|
2476
2714
|
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
|
|
2477
2715
|
const isWin3 = os2.platform() === "win32";
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2716
|
+
let plan;
|
|
2717
|
+
let winShellKind;
|
|
2718
|
+
if (isWin3) {
|
|
2719
|
+
const shell2 = pickShell("win32", input.command, {
|
|
2720
|
+
get: (k) => process.env[k]
|
|
2721
|
+
});
|
|
2722
|
+
winShellKind = shell2;
|
|
2723
|
+
const bin = shell2 === "powershell" ? resolvePowerShell("powershell.exe") : shell2 === "pwsh" ? resolvePowerShell("pwsh.exe") : process.env["COMSPEC"] ?? "cmd.exe";
|
|
2724
|
+
plan = {
|
|
2725
|
+
bin,
|
|
2726
|
+
argv: shellArgs(shell2),
|
|
2727
|
+
useStdin: shell2 === "powershell" || shell2 === "pwsh",
|
|
2728
|
+
stdinBody: shell2 === "powershell" || shell2 === "pwsh" ? wrapPowerShellScript(input.command) : void 0
|
|
2729
|
+
};
|
|
2730
|
+
} else {
|
|
2731
|
+
const explicit = process.env["WRONGSTACK_SHELL"];
|
|
2732
|
+
let bin;
|
|
2733
|
+
if (explicit) bin = explicit;
|
|
2734
|
+
else {
|
|
2735
|
+
const fromEnv = process.env["SHELL"];
|
|
2736
|
+
if (fromEnv) {
|
|
2737
|
+
const name = fromEnv.split("/").pop() ?? "";
|
|
2738
|
+
if (["bash", "zsh", "sh", "dash", "fish"].includes(name)) bin = fromEnv;
|
|
2739
|
+
else bin = "/bin/bash";
|
|
2740
|
+
} else bin = "/bin/bash";
|
|
2741
|
+
}
|
|
2742
|
+
plan = { bin, argv: ["-c"], useStdin: false, stdinBody: void 0 };
|
|
2743
|
+
}
|
|
2744
|
+
const shell = plan.bin;
|
|
2745
|
+
const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
|
|
2490
2746
|
const env = buildChildEnv(ctx.session?.id);
|
|
2491
2747
|
const detached = !isWin3;
|
|
2492
2748
|
const startedAt = Date.now();
|
|
@@ -2496,7 +2752,9 @@ var bashTool = {
|
|
|
2496
2752
|
const child2 = spawn(shell, args, {
|
|
2497
2753
|
cwd: ctx.projectRoot,
|
|
2498
2754
|
env,
|
|
2499
|
-
|
|
2755
|
+
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
2756
|
+
// and POSIX shells ignore stdin when given the command inline.
|
|
2757
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
2500
2758
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
2501
2759
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
2502
2760
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -2507,6 +2765,13 @@ var bashTool = {
|
|
|
2507
2765
|
detached: !isWin3,
|
|
2508
2766
|
windowsHide: true
|
|
2509
2767
|
});
|
|
2768
|
+
if (plan.useStdin) {
|
|
2769
|
+
try {
|
|
2770
|
+
child2.stdin?.write(plan.stdinBody ?? input.command);
|
|
2771
|
+
child2.stdin?.end();
|
|
2772
|
+
} catch {
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2510
2775
|
const pid2 = child2.pid;
|
|
2511
2776
|
if (typeof pid2 === "number") {
|
|
2512
2777
|
registry.register({
|
|
@@ -2561,11 +2826,20 @@ var bashTool = {
|
|
|
2561
2826
|
const child = spawn(shell, args, {
|
|
2562
2827
|
cwd: ctx.projectRoot,
|
|
2563
2828
|
env,
|
|
2564
|
-
|
|
2829
|
+
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
2830
|
+
// and POSIX shells ignore stdin when given the command inline.
|
|
2831
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
2565
2832
|
detached,
|
|
2566
2833
|
windowsHide: true,
|
|
2567
2834
|
...isWin3 ? {} : { signal: opts.signal }
|
|
2568
2835
|
});
|
|
2836
|
+
if (plan.useStdin) {
|
|
2837
|
+
try {
|
|
2838
|
+
child.stdin?.write(plan.stdinBody ?? input.command);
|
|
2839
|
+
child.stdin?.end();
|
|
2840
|
+
} catch {
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2569
2843
|
const pid = child.pid;
|
|
2570
2844
|
if (typeof pid === "number") {
|
|
2571
2845
|
registry.register({
|
|
@@ -2716,10 +2990,13 @@ var bashTool = {
|
|
|
2716
2990
|
yield { type: "partial_output", text: remainder };
|
|
2717
2991
|
}
|
|
2718
2992
|
const spooled = spool.finalize();
|
|
2993
|
+
const hint = !timedOut && typeof c.code === "number" && c.code !== 0 && winShellKind ? diagnoseBashism(input.command, winShellKind) : void 0;
|
|
2719
2994
|
yield {
|
|
2720
2995
|
type: "final",
|
|
2721
2996
|
output: {
|
|
2722
|
-
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "")
|
|
2997
|
+
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "") + (hint ? `
|
|
2998
|
+
|
|
2999
|
+
${hint}` : ""),
|
|
2723
3000
|
exit_code: c.code,
|
|
2724
3001
|
timed_out: timedOut
|
|
2725
3002
|
}
|
|
@@ -2747,82 +3024,115 @@ var bashTool = {
|
|
|
2747
3024
|
}
|
|
2748
3025
|
}
|
|
2749
3026
|
};
|
|
2750
|
-
function resolveWin32Command(cmd) {
|
|
2751
|
-
if (process.platform !== "win32") return cmd;
|
|
2752
|
-
if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
|
|
2753
|
-
return cmd;
|
|
2754
|
-
}
|
|
2755
|
-
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
2756
|
-
const pathDirs = (process.env["PATH"] ?? "").split(path.delimiter);
|
|
2757
|
-
for (const dir of pathDirs) {
|
|
2758
|
-
const base = path.join(dir, cmd);
|
|
2759
|
-
for (const ext of pathext) {
|
|
2760
|
-
const full = `${base}${ext}`;
|
|
2761
|
-
try {
|
|
2762
|
-
fs8.accessSync(full, fs8.constants.X_OK);
|
|
2763
|
-
return full;
|
|
2764
|
-
} catch {
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
return cmd;
|
|
2769
|
-
}
|
|
2770
|
-
var WIN32_SHELL_META = /[&|<>\r\n\0]/;
|
|
2771
|
-
function assertSafeWin32ShellArgs(args) {
|
|
2772
|
-
for (const a of args) {
|
|
2773
|
-
if (typeof a === "string" && WIN32_SHELL_META.test(a)) {
|
|
2774
|
-
throw new Error(
|
|
2775
|
-
"win32 shell spawn: argument contains a shell metacharacter (one of & | < > or a newline) that could enable command injection through the .cmd/.bat wrapper \u2014 refusing to run. Offending argument: " + JSON.stringify(a)
|
|
2776
|
-
);
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
3027
|
|
|
2781
|
-
// src/
|
|
3028
|
+
// src/_session-shell.ts
|
|
3029
|
+
function normalizeShell(value) {
|
|
3030
|
+
const v = value?.trim().toLowerCase();
|
|
3031
|
+
if (v === "cmd" || v === "cmd.exe") return "cmd";
|
|
3032
|
+
if (v === "powershell" || v === "powershell.exe") return "powershell";
|
|
3033
|
+
if (v === "pwsh" || v === "pwsh.exe") return "pwsh";
|
|
3034
|
+
return void 0;
|
|
3035
|
+
}
|
|
3036
|
+
function resolveSessionShell(platform4, env, deps = {}) {
|
|
3037
|
+
if (platform4 !== "win32") return void 0;
|
|
3038
|
+
const override = normalizeShell(env.get("WRONGSTACK_SHELL"));
|
|
3039
|
+
if (override) return override;
|
|
3040
|
+
const hasBinary = deps.hasBinary ?? ((bin) => resolveWin32Command(bin) !== bin);
|
|
3041
|
+
if (hasBinary("pwsh.exe")) return "pwsh";
|
|
3042
|
+
if (hasBinary("powershell.exe")) return "powershell";
|
|
3043
|
+
return "cmd";
|
|
3044
|
+
}
|
|
3045
|
+
function ensureSessionShell(opts = {}) {
|
|
3046
|
+
const env = opts.env ?? process.env;
|
|
3047
|
+
const platform4 = opts.platform ?? process.platform;
|
|
3048
|
+
if (platform4 !== "win32") return void 0;
|
|
3049
|
+
const existing = normalizeShell(env["WRONGSTACK_SHELL"]);
|
|
3050
|
+
if (existing) return existing;
|
|
3051
|
+
const chosen = resolveSessionShell(platform4, { get: (k) => env[k] }, { hasBinary: opts.hasBinary }) ?? "cmd";
|
|
3052
|
+
env["WRONGSTACK_SHELL"] = chosen;
|
|
3053
|
+
return chosen;
|
|
3054
|
+
}
|
|
2782
3055
|
var isWin = process.platform === "win32";
|
|
2783
|
-
var
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
3056
|
+
var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
3057
|
+
// JS / TS toolchain
|
|
3058
|
+
"node",
|
|
3059
|
+
"npm",
|
|
3060
|
+
"pnpm",
|
|
3061
|
+
"yarn",
|
|
3062
|
+
"npx",
|
|
3063
|
+
"bun",
|
|
3064
|
+
"deno",
|
|
3065
|
+
"tsc",
|
|
3066
|
+
"vitest",
|
|
3067
|
+
"jest",
|
|
3068
|
+
"biome",
|
|
3069
|
+
"eslint",
|
|
3070
|
+
"prettier",
|
|
3071
|
+
// version control
|
|
3072
|
+
"git",
|
|
3073
|
+
// Rust
|
|
3074
|
+
"cargo",
|
|
3075
|
+
"rustc",
|
|
3076
|
+
// Go
|
|
3077
|
+
"go",
|
|
3078
|
+
// Python
|
|
3079
|
+
"python",
|
|
3080
|
+
"python3",
|
|
3081
|
+
"pip",
|
|
3082
|
+
"pip3",
|
|
3083
|
+
// Ruby
|
|
3084
|
+
"ruby",
|
|
3085
|
+
"gem",
|
|
3086
|
+
"bundle",
|
|
3087
|
+
// JVM
|
|
3088
|
+
"java",
|
|
3089
|
+
"javac",
|
|
3090
|
+
"mvn",
|
|
3091
|
+
"gradle",
|
|
3092
|
+
"gradlew",
|
|
3093
|
+
// .NET
|
|
3094
|
+
"dotnet",
|
|
3095
|
+
// C / C++ / native build
|
|
3096
|
+
"make",
|
|
3097
|
+
"cmake",
|
|
3098
|
+
// containers / orchestration (read-only subcommands; see BLOCKED_ARG_PATTERNS)
|
|
3099
|
+
"docker",
|
|
3100
|
+
"kubectl",
|
|
3101
|
+
// common POSIX file/text utilities
|
|
3102
|
+
"ls",
|
|
3103
|
+
"cat",
|
|
3104
|
+
"head",
|
|
3105
|
+
"tail",
|
|
3106
|
+
"wc",
|
|
3107
|
+
"grep",
|
|
3108
|
+
"find",
|
|
3109
|
+
"echo",
|
|
3110
|
+
"mkdir",
|
|
3111
|
+
"cp",
|
|
3112
|
+
"mv",
|
|
3113
|
+
"rm",
|
|
3114
|
+
"touch"
|
|
3115
|
+
]);
|
|
3116
|
+
var allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
3117
|
+
var normalizeCmd = (c) => c.trim();
|
|
3118
|
+
function configureExecPolicy(opts = {}) {
|
|
3119
|
+
const next = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
3120
|
+
for (const c of opts.allow ?? []) {
|
|
3121
|
+
const n = normalizeCmd(c);
|
|
3122
|
+
if (n) next.add(n);
|
|
3123
|
+
}
|
|
3124
|
+
for (const c of opts.deny ?? []) next.delete(normalizeCmd(c));
|
|
3125
|
+
allowedCommands = next;
|
|
3126
|
+
}
|
|
3127
|
+
function resetExecPolicy() {
|
|
3128
|
+
allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
3129
|
+
}
|
|
3130
|
+
function isExecCommandAllowed(cmd) {
|
|
3131
|
+
return allowedCommands.has(normalizeCmd(cmd));
|
|
3132
|
+
}
|
|
3133
|
+
function getExecAllowlist() {
|
|
3134
|
+
return [...allowedCommands].sort();
|
|
3135
|
+
}
|
|
2826
3136
|
var MAX_ARGS = 20;
|
|
2827
3137
|
var MAX_OUTPUT2 = 2e5;
|
|
2828
3138
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
@@ -2884,7 +3194,7 @@ var execTool = {
|
|
|
2884
3194
|
name: "exec",
|
|
2885
3195
|
category: "Shell",
|
|
2886
3196
|
description: "Execute a **whitelisted, restricted set of commands** with strict argument validation. This is the **preferred and safer** alternative to the `bash` tool for running development tools (node, npm, pnpm, tsc, git, tests, linters, etc.). It prevents arbitrary command injection and limits what the model can do.",
|
|
2887
|
-
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be
|
|
3197
|
+
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be in the allowlist. Defaults cover JS (node/npm/pnpm/yarn/bun/deno/tsc/vitest/eslint/biome), Go (`go build`/`go test`), Rust (cargo), Python (python/pip), Ruby (gem/bundle), JVM (java/mvn/gradle), .NET (dotnet), native (make/cmake), and git. Users can extend it via `tools.exec.allow` in config.\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- If a command is not allowlisted, the error explains how to add it; for one-off arbitrary commands, fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
|
|
2888
3198
|
permission: "confirm",
|
|
2889
3199
|
mutating: true,
|
|
2890
3200
|
riskTier: "standard",
|
|
@@ -2938,12 +3248,12 @@ var execTool = {
|
|
|
2938
3248
|
truncated: false,
|
|
2939
3249
|
allowed: false
|
|
2940
3250
|
};
|
|
2941
|
-
if (!(cmd
|
|
3251
|
+
if (!isExecCommandAllowed(cmd)) {
|
|
2942
3252
|
return {
|
|
2943
3253
|
command: cmd,
|
|
2944
3254
|
args: input.args ?? [],
|
|
2945
3255
|
stdout: "",
|
|
2946
|
-
stderr: `Command "${cmd}" not in allowlist.
|
|
3256
|
+
stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
2947
3257
|
exitCode: 1,
|
|
2948
3258
|
truncated: false,
|
|
2949
3259
|
allowed: false
|
|
@@ -2986,19 +3296,58 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
2986
3296
|
let stdout = "";
|
|
2987
3297
|
let stderr = "";
|
|
2988
3298
|
let killed = false;
|
|
3299
|
+
const resolvedOnce = { value: false };
|
|
3300
|
+
const finish = (result) => {
|
|
3301
|
+
if (resolvedOnce.value) return;
|
|
3302
|
+
resolvedOnce.value = true;
|
|
3303
|
+
resolve6(result);
|
|
3304
|
+
};
|
|
2989
3305
|
const startedAt = Date.now();
|
|
2990
3306
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
2991
3307
|
const resolved = resolveWin32Command(cmd);
|
|
2992
3308
|
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
2993
3309
|
const spawnCmd = needsShell ? cmd : resolved;
|
|
2994
3310
|
if (needsShell) assertSafeWin32ShellArgs(args);
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3311
|
+
let child;
|
|
3312
|
+
try {
|
|
3313
|
+
child = spawn(spawnCmd, args, {
|
|
3314
|
+
cwd,
|
|
3315
|
+
env: buildChildEnv(sessionId),
|
|
3316
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3317
|
+
windowsHide: true,
|
|
3318
|
+
...isWin ? {} : { signal },
|
|
3319
|
+
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
3320
|
+
});
|
|
3321
|
+
} catch (err) {
|
|
3322
|
+
spool.finalize();
|
|
3323
|
+
finish({
|
|
3324
|
+
command: cmd,
|
|
3325
|
+
args,
|
|
3326
|
+
stdout: "",
|
|
3327
|
+
stderr: `spawn failed: ${toErrorMessage$1(err)}`,
|
|
3328
|
+
exitCode: 1,
|
|
3329
|
+
truncated: false,
|
|
3330
|
+
allowed: true
|
|
3331
|
+
});
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
child.on("error", (err) => {
|
|
3335
|
+
const isAbort = err && err.code === "ABORT_ERR";
|
|
3336
|
+
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
3337
|
+
clearTimeout(timer);
|
|
3338
|
+
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
3339
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
3340
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
3341
|
+
spool.finalize();
|
|
3342
|
+
finish({
|
|
3343
|
+
command: cmd,
|
|
3344
|
+
args,
|
|
3345
|
+
stdout: normalizeCommandOutput(stdout),
|
|
3346
|
+
stderr: stderrText,
|
|
3347
|
+
exitCode: isAbort ? 124 : 1,
|
|
3348
|
+
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
3349
|
+
allowed: true
|
|
3350
|
+
});
|
|
3002
3351
|
});
|
|
3003
3352
|
const registry = getProcessRegistry();
|
|
3004
3353
|
const pid = child.pid;
|
|
@@ -3038,7 +3387,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3038
3387
|
const exitCode = killed ? 124 : code ?? 1;
|
|
3039
3388
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
3040
3389
|
const spooled = spool.finalize();
|
|
3041
|
-
|
|
3390
|
+
finish({
|
|
3042
3391
|
command: cmd,
|
|
3043
3392
|
args,
|
|
3044
3393
|
stdout: normalizeCommandOutput(stdout) + (spooled ? spoolNote(spooled) : ""),
|
|
@@ -3048,22 +3397,6 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3048
3397
|
allowed: true
|
|
3049
3398
|
});
|
|
3050
3399
|
});
|
|
3051
|
-
child.on("error", (err) => {
|
|
3052
|
-
clearTimeout(timer);
|
|
3053
|
-
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
3054
|
-
if (typeof pid === "number") registry.unregister(pid);
|
|
3055
|
-
registry.afterCall(Date.now() - startedAt, true);
|
|
3056
|
-
spool.finalize();
|
|
3057
|
-
resolve6({
|
|
3058
|
-
command: cmd,
|
|
3059
|
-
args,
|
|
3060
|
-
stdout: normalizeCommandOutput(stdout),
|
|
3061
|
-
stderr: err.message,
|
|
3062
|
-
exitCode: 1,
|
|
3063
|
-
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
3064
|
-
allowed: true
|
|
3065
|
-
});
|
|
3066
|
-
});
|
|
3067
3400
|
});
|
|
3068
3401
|
}
|
|
3069
3402
|
var TD = new TurndownService({
|
|
@@ -3428,7 +3761,7 @@ async function duckduckgoSearch(query2, num, signal) {
|
|
|
3428
3761
|
truncated: results.length >= num
|
|
3429
3762
|
};
|
|
3430
3763
|
} catch (err) {
|
|
3431
|
-
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$
|
|
3764
|
+
console.log(JSON.stringify({ level: "debug", event: "search_failed", query: query2, error: toErrorMessage$2(err) }));
|
|
3432
3765
|
return {
|
|
3433
3766
|
query: query2,
|
|
3434
3767
|
results: [{ title: "Search unavailable", url: "", snippet: "Could not reach DuckDuckGo" }],
|
|
@@ -3916,13 +4249,13 @@ ${formatTaskList(taskFile.tasks)}`
|
|
|
3916
4249
|
}
|
|
3917
4250
|
};
|
|
3918
4251
|
function mkResult(plan, ok, message, todos) {
|
|
3919
|
-
const
|
|
4252
|
+
const open2 = plan.items.filter((i) => i.status !== "done").length;
|
|
3920
4253
|
const result = {
|
|
3921
4254
|
ok,
|
|
3922
4255
|
message,
|
|
3923
4256
|
plan: formatPlan(plan),
|
|
3924
4257
|
count: plan.items.length,
|
|
3925
|
-
open
|
|
4258
|
+
open: open2
|
|
3926
4259
|
};
|
|
3927
4260
|
if (todos !== void 0) result.todos = todos;
|
|
3928
4261
|
return result;
|
|
@@ -7667,7 +8000,7 @@ function loadDatabaseSync() {
|
|
|
7667
8000
|
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
7668
8001
|
} catch (err) {
|
|
7669
8002
|
throw new Error(
|
|
7670
|
-
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$
|
|
8003
|
+
`The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage$2(err)}`
|
|
7671
8004
|
);
|
|
7672
8005
|
}
|
|
7673
8006
|
return DatabaseSyncCtor;
|
|
@@ -7794,6 +8127,8 @@ var IndexStore = class {
|
|
|
7794
8127
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)");
|
|
7795
8128
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)");
|
|
7796
8129
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
8130
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
8131
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
7797
8132
|
this.db.exec(`
|
|
7798
8133
|
CREATE TABLE IF NOT EXISTS refs (
|
|
7799
8134
|
id INTEGER PRIMARY KEY,
|
|
@@ -8706,9 +9041,9 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
8706
9041
|
}
|
|
8707
9042
|
}
|
|
8708
9043
|
async function parseSymbols3(opts) {
|
|
8709
|
-
const { file, lang } = opts;
|
|
9044
|
+
const { file, content, lang } = opts;
|
|
8710
9045
|
try {
|
|
8711
|
-
return await syncPyParse(file, lang);
|
|
9046
|
+
return await syncPyParse(file, content, lang);
|
|
8712
9047
|
} catch {
|
|
8713
9048
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
8714
9049
|
}
|
|
@@ -8776,8 +9111,7 @@ syms = []
|
|
|
8776
9111
|
errors = []
|
|
8777
9112
|
|
|
8778
9113
|
try:
|
|
8779
|
-
|
|
8780
|
-
source = f.read()
|
|
9114
|
+
source = sys.stdin.read()
|
|
8781
9115
|
tree = ast.parse(source, filename=sys.argv[1])
|
|
8782
9116
|
except Exception as e:
|
|
8783
9117
|
errors.append(str(e))
|
|
@@ -8917,16 +9251,21 @@ visitor.visit(tree)
|
|
|
8917
9251
|
|
|
8918
9252
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
8919
9253
|
`;
|
|
8920
|
-
|
|
9254
|
+
var _cachedScriptPath = null;
|
|
9255
|
+
async function syncPyParse(filePath, content, lang) {
|
|
8921
9256
|
try {
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
9257
|
+
if (!_cachedScriptPath) {
|
|
9258
|
+
const tmpDir = path.join(os2.tmpdir(), "ws-py-parse");
|
|
9259
|
+
await fs7.mkdir(tmpDir, { recursive: true });
|
|
9260
|
+
_cachedScriptPath = path.join(tmpDir, "parse.py");
|
|
9261
|
+
await fs7.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9262
|
+
}
|
|
9263
|
+
const proc = spawn("python", [_cachedScriptPath, filePath], {
|
|
8927
9264
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8928
9265
|
windowsHide: true
|
|
8929
9266
|
});
|
|
9267
|
+
proc.stdin?.write(content);
|
|
9268
|
+
proc.stdin?.end();
|
|
8930
9269
|
let stdout = "";
|
|
8931
9270
|
proc.stdout?.on("data", (chunk) => {
|
|
8932
9271
|
stdout += chunk.toString();
|
|
@@ -9593,7 +9932,7 @@ async function parseFile(file, content, lang) {
|
|
|
9593
9932
|
case "go":
|
|
9594
9933
|
return parseSymbols2({ file, content, lang: "go" });
|
|
9595
9934
|
case "py":
|
|
9596
|
-
return parseSymbols3({ file, lang: "py" });
|
|
9935
|
+
return parseSymbols3({ file, content, lang: "py" });
|
|
9597
9936
|
case "rs":
|
|
9598
9937
|
return parseSymbols4({ file, content, lang: "rs" });
|
|
9599
9938
|
case "json":
|
|
@@ -9624,10 +9963,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9624
9963
|
let symbolsIndexed = 0;
|
|
9625
9964
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
9626
9965
|
let files;
|
|
9966
|
+
let discoveredFiles = null;
|
|
9627
9967
|
if (opts.files && opts.files.length > 0) {
|
|
9628
9968
|
files = opts.files.map((f) => path.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path.relative(projectRoot, f).replace(/\\/g, "/"), false));
|
|
9629
9969
|
} else {
|
|
9630
9970
|
files = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
9971
|
+
discoveredFiles = new Set(files);
|
|
9631
9972
|
}
|
|
9632
9973
|
if (langs && langs.length > 0) {
|
|
9633
9974
|
const langSet = new Set(langs);
|
|
@@ -9745,11 +10086,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
9745
10086
|
filesIndexed++;
|
|
9746
10087
|
}
|
|
9747
10088
|
}
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
10089
|
+
if (discoveredFiles) {
|
|
10090
|
+
for (const [file_] of existingMeta) {
|
|
10091
|
+
if (!discoveredFiles.has(file_)) {
|
|
10092
|
+
store.deleteFile(file_);
|
|
10093
|
+
}
|
|
9753
10094
|
}
|
|
9754
10095
|
}
|
|
9755
10096
|
const durationMs = Date.now() - startMs;
|
|
@@ -10358,7 +10699,7 @@ var setWorkingDirTool = {
|
|
|
10358
10699
|
} catch (err) {
|
|
10359
10700
|
return {
|
|
10360
10701
|
current: ctx.workingDir,
|
|
10361
|
-
error: toErrorMessage$
|
|
10702
|
+
error: toErrorMessage$2(err)
|
|
10362
10703
|
};
|
|
10363
10704
|
}
|
|
10364
10705
|
try {
|
|
@@ -11004,6 +11345,6 @@ var TOOL_ICON_CONFIG = {
|
|
|
11004
11345
|
};
|
|
11005
11346
|
var FALLBACK_ICON = "fallback";
|
|
11006
11347
|
|
|
11007
|
-
export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, createGlobalPsSlashCommand, createModeTool, diffTool, documentTool, editTool, enqueueReindex, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetIndexCircuitBreaker, resetPersistentProcessRegistry, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
11348
|
+
export { CircuitBreaker, CircuitOpenError, FALLBACK_ICON, IndexCircuitBreaker, IndexTimeoutError, OPTIONAL_TOOLS, TIER1_TOOLS, TIER2_TOOLS, TIER3_TOOLS, TOOL_ICON_CONFIG, TOOL_ICON_MAP, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, cancelPendingReindexes, codebaseIndexStats, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, configureExecPolicy, createGlobalPsSlashCommand, createModeTool, diffTool, documentTool, editTool, enqueueReindex, ensureSessionShell, execTool, fetchTool, forgetTool, formatGlobalStatus, formatInstanceList, formatInstanceSummary, formatTool, getExecAllowlist, getIndexState, getInstanceCount, getPersistentProcessRegistry, getProcessGuardian, getProcessRegistry, getToolIcon, gitTool, globTool, grepTool, indexCircuitBreaker, installTool, isExecCommandAllowed, isIndexReady, isIndexableFile, isIndexing, jsonTool, lintTool, listInstances, logsTool, normalizeShell, onIndexStateChange, outdatedTool, patchTool, planTool, readTool, relatedMemoryTool, rememberTool, replaceTool, resetExecPolicy, resetIndexCircuitBreaker, resetPersistentProcessRegistry, resolveSessionShell, runStartupIndex, scaffoldTool, searchCodebaseIndex, searchMemoryTool, searchTool, shutdownCodebaseIndexHost, startProcessGuardian, stopProcessGuardian, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
11008
11349
|
//# sourceMappingURL=index.js.map
|
|
11009
11350
|
//# sourceMappingURL=index.js.map
|