sharkbait 1.0.11 → 1.0.13

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 (2) hide show
  1. package/dist/cli.js +200 -274
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -746,14 +746,6 @@ var REQUIRES_CONFIRMATION = [
746
746
  undoHint: "chown with original owner"
747
747
  }
748
748
  ];
749
- var SHELL_CHAINING_PATTERNS = [
750
- /;/,
751
- /\|\|/,
752
- /&&/,
753
- /\$\(/,
754
- /`[^`]+`/,
755
- /\|\s*\w/
756
- ];
757
749
  var DANGEROUS_PREFIXES = ["sudo", "doas", "pkexec", "exec"];
758
750
  function classifyCommand(command) {
759
751
  const trimmed = command.trim();
@@ -780,17 +772,30 @@ function classifyCommand(command) {
780
772
  reversibility: "effort"
781
773
  };
782
774
  }
783
- for (const pattern of SHELL_CHAINING_PATTERNS) {
784
- if (pattern.test(trimmed)) {
785
- return {
786
- status: "requires_confirmation",
787
- reason: "Command contains shell operators (;, &&, ||, |, $()) — each sub-command cannot be independently verified",
788
- reversibility: "effort"
789
- };
775
+ if (/\$\(/.test(trimmed) || /`[^`]+`/.test(trimmed)) {
776
+ return {
777
+ status: "requires_confirmation",
778
+ reason: "Command contains command substitution ($() or backticks)",
779
+ reversibility: "effort"
780
+ };
781
+ }
782
+ const hasChaining = /;|&&|\|\||(\|\s*\w)/.test(trimmed);
783
+ if (hasChaining) {
784
+ const subCommands = trimmed.split(/\s*(?:;|&&|\|\|)\s*/).flatMap((segment) => segment.split(/\s*\|\s*/)).map((s) => s.trim()).filter(Boolean);
785
+ for (const sub of subCommands) {
786
+ const subSafety = classifySimpleCommand(sub);
787
+ if (subSafety.status === "blocked")
788
+ return subSafety;
789
+ if (subSafety.status === "requires_confirmation")
790
+ return subSafety;
790
791
  }
792
+ return { status: "allowed" };
791
793
  }
792
- const baseCommand = extractBaseCommand(trimmed);
793
- if (isOnAllowlist(baseCommand, trimmed)) {
794
+ return classifySimpleCommand(trimmed);
795
+ }
796
+ function classifySimpleCommand(command) {
797
+ const baseCommand = extractBaseCommand(command);
798
+ if (isOnAllowlist(baseCommand, command)) {
794
799
  return { status: "allowed" };
795
800
  }
796
801
  return {
@@ -941,6 +946,57 @@ function getErrorMessage(error) {
941
946
  return "Unknown error";
942
947
  }
943
948
 
949
+ // src/utils/runtime.ts
950
+ import { spawn as nodeSpawn } from "child_process";
951
+ import { readFile, writeFile } from "fs/promises";
952
+ import { existsSync as existsSync2 } from "fs";
953
+ function exec(args, options) {
954
+ return new Promise((resolve2) => {
955
+ const [cmd, ...rest] = args;
956
+ const proc = nodeSpawn(cmd, rest, {
957
+ cwd: options?.cwd,
958
+ stdio: ["pipe", "pipe", "pipe"]
959
+ });
960
+ if (options?.stdin) {
961
+ proc.stdin?.write(options.stdin);
962
+ }
963
+ proc.stdin?.end();
964
+ let stdout = "";
965
+ let stderr = "";
966
+ proc.stdout?.on("data", (data) => {
967
+ stdout += data.toString();
968
+ });
969
+ proc.stderr?.on("data", (data) => {
970
+ stderr += data.toString();
971
+ });
972
+ proc.on("close", (code) => {
973
+ resolve2({ stdout, stderr, exitCode: code ?? 1 });
974
+ });
975
+ proc.on("error", (err) => {
976
+ resolve2({ stdout, stderr: err.message, exitCode: 1 });
977
+ });
978
+ });
979
+ }
980
+ function spawnBackground(args, options) {
981
+ const [cmd, ...rest] = args;
982
+ const proc = nodeSpawn(cmd, rest, {
983
+ cwd: options?.cwd,
984
+ stdio: ["ignore", "ignore", "ignore"],
985
+ detached: process.platform !== "win32"
986
+ });
987
+ proc.unref();
988
+ return proc.pid;
989
+ }
990
+ async function readTextFile(filePath) {
991
+ return readFile(filePath, "utf-8");
992
+ }
993
+ async function writeTextFile(filePath, content) {
994
+ await writeFile(filePath, content);
995
+ }
996
+ function fileExists(filePath) {
997
+ return existsSync2(filePath);
998
+ }
999
+
944
1000
  // src/tools/file-ops.ts
945
1001
  function enforcePath(filePath, operation) {
946
1002
  const safety = validatePath(filePath, process.cwd(), operation);
@@ -964,11 +1020,10 @@ var fileTools = [
964
1020
  async execute({ path, startLine, endLine }) {
965
1021
  const filePath = path;
966
1022
  enforcePath(filePath, "read");
967
- const file = Bun.file(filePath);
968
- if (!await file.exists()) {
1023
+ if (!fileExists(filePath)) {
969
1024
  throw new ToolError(`File not found: ${filePath}`, "read_file");
970
1025
  }
971
- const content = await file.text();
1026
+ const content = await readTextFile(filePath);
972
1027
  if (startLine !== undefined && endLine !== undefined) {
973
1028
  const lines = content.split(`
974
1029
  `);
@@ -998,7 +1053,7 @@ var fileTools = [
998
1053
  if (dir) {
999
1054
  await mkdir(dir, { recursive: true });
1000
1055
  }
1001
- await Bun.write(filePath, content);
1056
+ await writeTextFile(filePath, content);
1002
1057
  return { success: true, path: filePath };
1003
1058
  }
1004
1059
  },
@@ -1017,11 +1072,10 @@ var fileTools = [
1017
1072
  async execute({ path, oldString, newString }) {
1018
1073
  const filePath = path;
1019
1074
  enforcePath(filePath, "write");
1020
- const file = Bun.file(filePath);
1021
- if (!await file.exists()) {
1075
+ if (!fileExists(filePath)) {
1022
1076
  throw new ToolError(`File not found: ${filePath}`, "edit_file");
1023
1077
  }
1024
- const content = await file.text();
1078
+ const content = await readTextFile(filePath);
1025
1079
  const oldStr = oldString;
1026
1080
  if (!content.includes(oldStr)) {
1027
1081
  throw new ToolError(`String not found in file: ${oldStr.substring(0, 50)}...`, "edit_file");
@@ -1031,7 +1085,7 @@ var fileTools = [
1031
1085
  throw new ToolError(`String found ${occurrences} times. Please provide more context for unique match.`, "edit_file");
1032
1086
  }
1033
1087
  const newContent = content.replace(oldStr, newString);
1034
- await Bun.write(filePath, newContent);
1088
+ await writeTextFile(filePath, newContent);
1035
1089
  return { success: true, path: filePath };
1036
1090
  }
1037
1091
  },
@@ -1109,9 +1163,8 @@ var fileTools = [
1109
1163
  "-m",
1110
1164
  String(limit)
1111
1165
  ];
1112
- const proc = Bun.spawn(args, { stdout: "pipe", stderr: "pipe" });
1113
- const output = await new Response(proc.stdout).text();
1114
- return output;
1166
+ const result = await exec(args);
1167
+ return result.stdout;
1115
1168
  } catch {
1116
1169
  try {
1117
1170
  const args = [
@@ -1122,12 +1175,8 @@ var fileTools = [
1122
1175
  "--include",
1123
1176
  glob
1124
1177
  ];
1125
- const proc = Bun.spawn(args, {
1126
- stdout: "pipe",
1127
- stderr: "pipe"
1128
- });
1129
- const output = await new Response(proc.stdout).text();
1130
- return output;
1178
+ const result = await exec(args);
1179
+ return result.stdout;
1131
1180
  } catch {
1132
1181
  return "No matches found";
1133
1182
  }
@@ -1169,9 +1218,8 @@ var fileTools = [
1169
1218
  args.push("-m", String(maxResults));
1170
1219
  }
1171
1220
  try {
1172
- const proc = Bun.spawn(args, { stdout: "pipe", stderr: "pipe" });
1173
- const output = await new Response(proc.stdout).text();
1174
- return output || "No matches found";
1221
+ const result = await exec(args);
1222
+ return result.stdout || "No matches found";
1175
1223
  } catch {
1176
1224
  return "Search failed - ripgrep may not be installed";
1177
1225
  }
@@ -1228,33 +1276,23 @@ var shellTools = [
1228
1276
  if (background) {
1229
1277
  const shell = process.platform === "win32" ? "cmd" : "sh";
1230
1278
  const shellArg = process.platform === "win32" ? "/c" : "-c";
1231
- const proc = Bun.spawn([shell, shellArg, cmd], {
1232
- cwd: workingDir,
1233
- stdout: "pipe",
1234
- stderr: "pipe"
1279
+ const pid = spawnBackground([shell, shellArg, cmd], {
1280
+ cwd: workingDir
1235
1281
  });
1236
1282
  return {
1237
- pid: proc.pid,
1283
+ pid,
1238
1284
  message: "Started in background"
1239
1285
  };
1240
1286
  }
1241
1287
  try {
1242
1288
  const shell = process.platform === "win32" ? "cmd" : "sh";
1243
1289
  const shellArg = process.platform === "win32" ? "/c" : "-c";
1244
- const proc = Bun.spawn([shell, shellArg, cmd], {
1245
- cwd: workingDir,
1246
- stdout: "pipe",
1247
- stderr: "pipe"
1248
- });
1249
1290
  const timeoutPromise = new Promise((_, reject) => {
1250
1291
  setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs);
1251
1292
  });
1252
- const outputPromise = (async () => {
1253
- const stdout2 = await new Response(proc.stdout).text();
1254
- const stderr2 = await new Response(proc.stderr).text();
1255
- const exitCode2 = await proc.exited;
1256
- return { stdout: stdout2, stderr: stderr2, exitCode: exitCode2 };
1257
- })();
1293
+ const outputPromise = exec([shell, shellArg, cmd], {
1294
+ cwd: workingDir
1295
+ });
1258
1296
  const { stdout, stderr, exitCode } = await Promise.race([
1259
1297
  outputPromise,
1260
1298
  timeoutPromise
@@ -1319,11 +1357,7 @@ var shellTools = [
1319
1357
  cmd = app ? [app, target] : ["xdg-open", target];
1320
1358
  }
1321
1359
  try {
1322
- const proc = Bun.spawn(cmd, {
1323
- stdout: "pipe",
1324
- stderr: "pipe"
1325
- });
1326
- await proc.exited;
1360
+ await exec(cmd);
1327
1361
  return { success: true, message: `Opened ${target}` };
1328
1362
  } catch (error) {
1329
1363
  return { success: false, error: getErrorMessage(error) };
@@ -1335,7 +1369,7 @@ var shellTools = [
1335
1369
  // src/tools/beads.ts
1336
1370
  import { homedir as homedir2 } from "os";
1337
1371
  import { join as join3 } from "path";
1338
- import { existsSync as existsSync2 } from "fs";
1372
+ import { existsSync as existsSync3 } from "fs";
1339
1373
  function getBdPath() {
1340
1374
  const paths = [
1341
1375
  "bd",
@@ -1351,16 +1385,12 @@ function getBdPath() {
1351
1385
  var BD_PATH = getBdPath();
1352
1386
  function isBeadsInitialized(cwd) {
1353
1387
  const dir = cwd || process.cwd();
1354
- return existsSync2(join3(dir, ".beads"));
1388
+ return existsSync3(join3(dir, ".beads"));
1355
1389
  }
1356
1390
  async function isBdInstalled() {
1357
1391
  try {
1358
- const proc = Bun.spawn([BD_PATH, "--version"], {
1359
- stdout: "pipe",
1360
- stderr: "pipe"
1361
- });
1362
- await proc.exited;
1363
- return true;
1392
+ const result = await exec([BD_PATH, "--version"]);
1393
+ return result.exitCode === 0;
1364
1394
  } catch {
1365
1395
  return false;
1366
1396
  }
@@ -1419,21 +1449,14 @@ var beadsTools = [
1419
1449
  args.push("--no-db");
1420
1450
  }
1421
1451
  try {
1422
- const proc = Bun.spawn(args, {
1423
- stdout: "pipe",
1424
- stderr: "pipe",
1425
- cwd: process.cwd()
1426
- });
1427
- const output = await new Response(proc.stdout).text();
1428
- const stderr = await new Response(proc.stderr).text();
1429
- const exitCode = await proc.exited;
1430
- if (exitCode !== 0) {
1431
- throw new Error(stderr || output || "Failed to initialize beads");
1452
+ const result = await exec(args, { cwd: process.cwd() });
1453
+ if (result.exitCode !== 0) {
1454
+ throw new Error(result.stderr || result.stdout || "Failed to initialize beads");
1432
1455
  }
1433
1456
  return {
1434
1457
  success: true,
1435
1458
  message: "Beads initialized successfully.",
1436
- output: output.trim()
1459
+ output: result.stdout.trim()
1437
1460
  };
1438
1461
  } catch (error) {
1439
1462
  return {
@@ -1453,16 +1476,11 @@ var beadsTools = [
1453
1476
  },
1454
1477
  async execute() {
1455
1478
  try {
1456
- const proc = Bun.spawn([BD_PATH, "ready", "--json"], {
1457
- stdout: "pipe",
1458
- stderr: "pipe"
1459
- });
1460
- const output = await new Response(proc.stdout).text();
1461
- const exitCode = await proc.exited;
1462
- if (exitCode !== 0) {
1479
+ const result = await exec([BD_PATH, "ready", "--json"]);
1480
+ if (result.exitCode !== 0) {
1463
1481
  throw new Error("Failed to get ready tasks");
1464
1482
  }
1465
- return JSON.parse(output);
1483
+ return JSON.parse(result.stdout);
1466
1484
  } catch (error) {
1467
1485
  return { tasks: [], message: "Beads (bd) not available" };
1468
1486
  }
@@ -1497,32 +1515,25 @@ var beadsTools = [
1497
1515
  }
1498
1516
  args.push("--json");
1499
1517
  try {
1500
- const proc = Bun.spawn(args, {
1501
- stdout: "pipe",
1502
- stderr: "pipe",
1503
- cwd: process.cwd()
1504
- });
1505
- const output = await new Response(proc.stdout).text();
1506
- const stderr = await new Response(proc.stderr).text();
1507
- const exitCode = await proc.exited;
1508
- if (exitCode !== 0) {
1509
- if (stderr.includes("no beads database found")) {
1518
+ const result = await exec(args, { cwd: process.cwd() });
1519
+ if (result.exitCode !== 0) {
1520
+ if (result.stderr.includes("no beads database found")) {
1510
1521
  return {
1511
1522
  success: false,
1512
1523
  error: "Beads database not found. Use beads_init to initialize.",
1513
1524
  hint: "Run beads_init first, then retry creating the task."
1514
1525
  };
1515
1526
  }
1516
- throw new Error(stderr || "Failed to create task");
1527
+ throw new Error(result.stderr || "Failed to create task");
1517
1528
  }
1518
- const jsonStart = output.indexOf("{");
1519
- const jsonEnd = output.lastIndexOf("}");
1529
+ const jsonStart = result.stdout.indexOf("{");
1530
+ const jsonEnd = result.stdout.lastIndexOf("}");
1520
1531
  if (jsonStart === -1 || jsonEnd === -1) {
1521
- throw new Error(`No JSON in output: ${output}`);
1532
+ throw new Error(`No JSON in output: ${result.stdout}`);
1522
1533
  }
1523
- const jsonStr = output.substring(jsonStart, jsonEnd + 1);
1524
- const result = JSON.parse(jsonStr);
1525
- return { success: true, ...result };
1534
+ const jsonStr = result.stdout.substring(jsonStart, jsonEnd + 1);
1535
+ const parsed = JSON.parse(jsonStr);
1536
+ return { success: true, ...parsed };
1526
1537
  } catch (error) {
1527
1538
  return {
1528
1539
  success: false,
@@ -1543,16 +1554,11 @@ var beadsTools = [
1543
1554
  },
1544
1555
  async execute({ id }) {
1545
1556
  try {
1546
- const proc = Bun.spawn([BD_PATH, "show", id, "--json"], {
1547
- stdout: "pipe",
1548
- stderr: "pipe"
1549
- });
1550
- const output = await new Response(proc.stdout).text();
1551
- const exitCode = await proc.exited;
1552
- if (exitCode !== 0) {
1557
+ const result = await exec([BD_PATH, "show", id, "--json"]);
1558
+ if (result.exitCode !== 0) {
1553
1559
  throw new Error(`Task not found: ${id}`);
1554
1560
  }
1555
- return JSON.parse(output);
1561
+ return JSON.parse(result.stdout);
1556
1562
  } catch (error) {
1557
1563
  throw new Error(`Failed to get task: ${error}`);
1558
1564
  }
@@ -1572,12 +1578,8 @@ var beadsTools = [
1572
1578
  async execute({ id, message }) {
1573
1579
  const msg = message || "Completed";
1574
1580
  try {
1575
- const proc = Bun.spawn([BD_PATH, "close", id, "-m", msg], {
1576
- stdout: "pipe",
1577
- stderr: "pipe"
1578
- });
1579
- const exitCode = await proc.exited;
1580
- if (exitCode !== 0) {
1581
+ const result = await exec([BD_PATH, "close", id, "-m", msg]);
1582
+ if (result.exitCode !== 0) {
1581
1583
  throw new Error(`Failed to complete task: ${id}`);
1582
1584
  }
1583
1585
  return { success: true, id, message: msg };
@@ -1599,9 +1601,8 @@ var beadsTools = [
1599
1601
  },
1600
1602
  async execute({ childId, parentId }) {
1601
1603
  try {
1602
- const proc = Bun.spawn([BD_PATH, "dep", "add", childId, parentId], { stdout: "pipe", stderr: "pipe" });
1603
- const exitCode = await proc.exited;
1604
- if (exitCode !== 0) {
1604
+ const result = await exec([BD_PATH, "dep", "add", childId, parentId]);
1605
+ if (result.exitCode !== 0) {
1605
1606
  throw new Error("Failed to add dependency");
1606
1607
  }
1607
1608
  return { success: true, childId, parentId };
@@ -1632,16 +1633,11 @@ var beadsTools = [
1632
1633
  args.push("--status", status);
1633
1634
  }
1634
1635
  try {
1635
- const proc = Bun.spawn(args, {
1636
- stdout: "pipe",
1637
- stderr: "pipe"
1638
- });
1639
- const output = await new Response(proc.stdout).text();
1640
- const exitCode = await proc.exited;
1641
- if (exitCode !== 0) {
1636
+ const result = await exec(args);
1637
+ if (result.exitCode !== 0) {
1642
1638
  return { tasks: [] };
1643
1639
  }
1644
- return JSON.parse(output);
1640
+ return JSON.parse(result.stdout);
1645
1641
  } catch {
1646
1642
  return { tasks: [], message: "Beads (bd) not available" };
1647
1643
  }
@@ -1661,12 +1657,8 @@ var gitTools = [
1661
1657
  },
1662
1658
  async execute() {
1663
1659
  try {
1664
- const proc = Bun.spawn(["git", "status", "--porcelain"], {
1665
- stdout: "pipe",
1666
- stderr: "pipe"
1667
- });
1668
- const output = await new Response(proc.stdout).text();
1669
- return output.trim() || "Working tree clean";
1660
+ const { stdout } = await exec(["git", "status", "--porcelain"]);
1661
+ return stdout.trim() || "Working tree clean";
1670
1662
  } catch (error) {
1671
1663
  throw new Error("Not a git repository or git not installed");
1672
1664
  }
@@ -1692,12 +1684,8 @@ var gitTools = [
1692
1684
  args.push(file);
1693
1685
  }
1694
1686
  try {
1695
- const proc = Bun.spawn(args, {
1696
- stdout: "pipe",
1697
- stderr: "pipe"
1698
- });
1699
- const output = await new Response(proc.stdout).text();
1700
- return output.trim() || "No changes";
1687
+ const { stdout } = await exec(args);
1688
+ return stdout.trim() || "No changes";
1701
1689
  } catch (error) {
1702
1690
  throw new Error("Failed to get diff");
1703
1691
  }
@@ -1717,23 +1705,13 @@ var gitTools = [
1717
1705
  async execute({ message, all }) {
1718
1706
  try {
1719
1707
  if (all) {
1720
- const addProc = Bun.spawn(["git", "add", "-A"], {
1721
- stdout: "pipe",
1722
- stderr: "pipe"
1723
- });
1724
- await addProc.exited;
1708
+ await exec(["git", "add", "-A"]);
1725
1709
  }
1726
- const proc = Bun.spawn(["git", "commit", "-m", message], {
1727
- stdout: "pipe",
1728
- stderr: "pipe"
1729
- });
1730
- const output = await new Response(proc.stdout).text();
1731
- const exitCode = await proc.exited;
1732
- if (exitCode !== 0) {
1733
- const stderr = await new Response(proc.stderr).text();
1734
- throw new Error(stderr || "Commit failed");
1710
+ const result = await exec(["git", "commit", "-m", message]);
1711
+ if (result.exitCode !== 0) {
1712
+ throw new Error(result.stderr || "Commit failed");
1735
1713
  }
1736
- return { success: true, message, output: output.trim() };
1714
+ return { success: true, message, output: result.stdout.trim() };
1737
1715
  } catch (error) {
1738
1716
  throw new Error(`Commit failed: ${error}`);
1739
1717
  }
@@ -1758,14 +1736,9 @@ var gitTools = [
1758
1736
  args.push("origin", branch);
1759
1737
  }
1760
1738
  try {
1761
- const proc = Bun.spawn(args, {
1762
- stdout: "pipe",
1763
- stderr: "pipe"
1764
- });
1765
- const exitCode = await proc.exited;
1766
- const stderr = await new Response(proc.stderr).text();
1767
- if (exitCode !== 0) {
1768
- throw new Error(stderr || "Push failed");
1739
+ const result = await exec(args);
1740
+ if (result.exitCode !== 0) {
1741
+ throw new Error(result.stderr || "Push failed");
1769
1742
  }
1770
1743
  return { success: true, message: "Pushed successfully" };
1771
1744
  } catch (error) {
@@ -1813,21 +1786,15 @@ var gitTools = [
1813
1786
  default:
1814
1787
  throw new Error(`Unknown action: ${action}`);
1815
1788
  }
1816
- const proc = Bun.spawn(args, {
1817
- stdout: "pipe",
1818
- stderr: "pipe"
1819
- });
1820
- const output = await new Response(proc.stdout).text();
1821
- const exitCode = await proc.exited;
1822
- if (exitCode !== 0) {
1823
- const stderr = await new Response(proc.stderr).text();
1824
- throw new Error(stderr || "Branch operation failed");
1789
+ const result = await exec(args);
1790
+ if (result.exitCode !== 0) {
1791
+ throw new Error(result.stderr || "Branch operation failed");
1825
1792
  }
1826
1793
  return {
1827
1794
  success: true,
1828
1795
  branch: name,
1829
1796
  action,
1830
- output: output.trim()
1797
+ output: result.stdout.trim()
1831
1798
  };
1832
1799
  } catch (error) {
1833
1800
  throw new Error(`Branch operation failed: ${error}`);
@@ -1854,12 +1821,8 @@ var gitTools = [
1854
1821
  args.push("--oneline");
1855
1822
  }
1856
1823
  try {
1857
- const proc = Bun.spawn(args, {
1858
- stdout: "pipe",
1859
- stderr: "pipe"
1860
- });
1861
- const output = await new Response(proc.stdout).text();
1862
- return output.trim();
1824
+ const { stdout } = await exec(args);
1825
+ return stdout.trim();
1863
1826
  } catch (error) {
1864
1827
  throw new Error("Failed to get log");
1865
1828
  }
@@ -1895,17 +1858,11 @@ var githubTools = [
1895
1858
  }
1896
1859
  args.push("--json", "number,url,title");
1897
1860
  try {
1898
- const proc = Bun.spawn(args, {
1899
- stdout: "pipe",
1900
- stderr: "pipe"
1901
- });
1902
- const output = await new Response(proc.stdout).text();
1903
- const exitCode = await proc.exited;
1904
- if (exitCode !== 0) {
1905
- const stderr = await new Response(proc.stderr).text();
1906
- throw new Error(stderr || "Failed to create PR");
1861
+ const result = await exec(args);
1862
+ if (result.exitCode !== 0) {
1863
+ throw new Error(result.stderr || "Failed to create PR");
1907
1864
  }
1908
- return JSON.parse(output);
1865
+ return JSON.parse(result.stdout);
1909
1866
  } catch (error) {
1910
1867
  throw new Error(`Failed to create PR: ${error}`);
1911
1868
  }
@@ -1936,16 +1893,11 @@ var githubTools = [
1936
1893
  }
1937
1894
  args.push("--json", "number,title,author,url,state,headRefName");
1938
1895
  try {
1939
- const proc = Bun.spawn(args, {
1940
- stdout: "pipe",
1941
- stderr: "pipe"
1942
- });
1943
- const output = await new Response(proc.stdout).text();
1944
- const exitCode = await proc.exited;
1945
- if (exitCode !== 0) {
1896
+ const result = await exec(args);
1897
+ if (result.exitCode !== 0) {
1946
1898
  return { prs: [] };
1947
1899
  }
1948
- return JSON.parse(output);
1900
+ return JSON.parse(result.stdout);
1949
1901
  } catch {
1950
1902
  return { prs: [], message: "GitHub CLI (gh) not available" };
1951
1903
  }
@@ -1974,14 +1926,9 @@ var githubTools = [
1974
1926
  args.push("--delete-branch");
1975
1927
  }
1976
1928
  try {
1977
- const proc = Bun.spawn(args, {
1978
- stdout: "pipe",
1979
- stderr: "pipe"
1980
- });
1981
- const exitCode = await proc.exited;
1982
- if (exitCode !== 0) {
1983
- const stderr = await new Response(proc.stderr).text();
1984
- throw new Error(stderr || "Failed to merge PR");
1929
+ const result = await exec(args);
1930
+ if (result.exitCode !== 0) {
1931
+ throw new Error(result.stderr || "Failed to merge PR");
1985
1932
  }
1986
1933
  return { success: true, number, method: method || "squash" };
1987
1934
  } catch (error) {
@@ -2023,17 +1970,11 @@ var githubTools = [
2023
1970
  }
2024
1971
  args.push("--json", "number,url,title");
2025
1972
  try {
2026
- const proc = Bun.spawn(args, {
2027
- stdout: "pipe",
2028
- stderr: "pipe"
2029
- });
2030
- const output = await new Response(proc.stdout).text();
2031
- const exitCode = await proc.exited;
2032
- if (exitCode !== 0) {
2033
- const stderr = await new Response(proc.stderr).text();
2034
- throw new Error(stderr || "Failed to create issue");
1973
+ const result = await exec(args);
1974
+ if (result.exitCode !== 0) {
1975
+ throw new Error(result.stderr || "Failed to create issue");
2035
1976
  }
2036
- return JSON.parse(output);
1977
+ return JSON.parse(result.stdout);
2037
1978
  } catch (error) {
2038
1979
  throw new Error(`Failed to create issue: ${error}`);
2039
1980
  }
@@ -2056,16 +1997,11 @@ var githubTools = [
2056
1997
  }
2057
1998
  args.push("--json", "status,conclusion,name,createdAt,url,headBranch");
2058
1999
  try {
2059
- const proc = Bun.spawn(args, {
2060
- stdout: "pipe",
2061
- stderr: "pipe"
2062
- });
2063
- const output = await new Response(proc.stdout).text();
2064
- const exitCode = await proc.exited;
2065
- if (exitCode !== 0) {
2000
+ const result = await exec(args);
2001
+ if (result.exitCode !== 0) {
2066
2002
  return { runs: [] };
2067
2003
  }
2068
- return JSON.parse(output);
2004
+ return JSON.parse(result.stdout);
2069
2005
  } catch {
2070
2006
  return { runs: [], message: "GitHub CLI (gh) not available" };
2071
2007
  }
@@ -2091,16 +2027,11 @@ var githubTools = [
2091
2027
  "number,title,body,state,author,url,headRefName,baseRefName,additions,deletions,changedFiles"
2092
2028
  ];
2093
2029
  try {
2094
- const proc = Bun.spawn(args, {
2095
- stdout: "pipe",
2096
- stderr: "pipe"
2097
- });
2098
- const output = await new Response(proc.stdout).text();
2099
- const exitCode = await proc.exited;
2100
- if (exitCode !== 0) {
2030
+ const result = await exec(args);
2031
+ if (result.exitCode !== 0) {
2101
2032
  throw new Error(`PR #${number} not found`);
2102
2033
  }
2103
- return JSON.parse(output);
2034
+ return JSON.parse(result.stdout);
2104
2035
  } catch (error) {
2105
2036
  throw new Error(`Failed to view PR: ${error}`);
2106
2037
  }
@@ -2139,16 +2070,11 @@ var githubTools = [
2139
2070
  }
2140
2071
  args.push("--json", "number,title,state,author,labels,url");
2141
2072
  try {
2142
- const proc = Bun.spawn(args, {
2143
- stdout: "pipe",
2144
- stderr: "pipe"
2145
- });
2146
- const output = await new Response(proc.stdout).text();
2147
- const exitCode = await proc.exited;
2148
- if (exitCode !== 0) {
2073
+ const result = await exec(args);
2074
+ if (result.exitCode !== 0) {
2149
2075
  return { issues: [] };
2150
2076
  }
2151
- return JSON.parse(output);
2077
+ return JSON.parse(result.stdout);
2152
2078
  } catch {
2153
2079
  return { issues: [], message: "GitHub CLI (gh) not available" };
2154
2080
  }
@@ -2385,7 +2311,7 @@ var fetchTools = [
2385
2311
  ];
2386
2312
 
2387
2313
  // src/tools/codebase.ts
2388
- import { readdir as readdir2, stat as stat2, readFile } from "fs/promises";
2314
+ import { readdir as readdir2, stat as stat2, readFile as readFile2 } from "fs/promises";
2389
2315
  import { join as join4, extname } from "path";
2390
2316
  async function buildDirectoryTree(dir, prefix = "", maxDepth = 3, currentDepth = 0) {
2391
2317
  if (currentDepth >= maxDepth) {
@@ -2432,7 +2358,7 @@ async function buildDirectoryTree(dir, prefix = "", maxDepth = 3, currentDepth =
2432
2358
  }
2433
2359
  async function countLines(filePath) {
2434
2360
  try {
2435
- const content = await readFile(filePath, "utf-8");
2361
+ const content = await readFile2(filePath, "utf-8");
2436
2362
  return content.split(`
2437
2363
  `).length;
2438
2364
  } catch {
@@ -2480,7 +2406,7 @@ async function detectTechnologies(projectDir) {
2480
2406
  try {
2481
2407
  const pkgJsonPath = join4(projectDir, "package.json");
2482
2408
  try {
2483
- const pkgJsonContent = await readFile(pkgJsonPath, "utf-8");
2409
+ const pkgJsonContent = await readFile2(pkgJsonPath, "utf-8");
2484
2410
  const pkgJson = JSON.parse(pkgJsonContent);
2485
2411
  const allDeps = {
2486
2412
  ...pkgJson.dependencies,
@@ -2513,8 +2439,8 @@ async function detectTechnologies(projectDir) {
2513
2439
  for (const { file, tech } of configFiles) {
2514
2440
  try {
2515
2441
  const filePath = join4(projectDir, file);
2516
- const fileExists = await stat2(filePath).then(() => true).catch(() => false);
2517
- if (fileExists && !technologies.includes(tech)) {
2442
+ const fileExists2 = await stat2(filePath).then(() => true).catch(() => false);
2443
+ if (fileExists2 && !technologies.includes(tech)) {
2518
2444
  technologies.push(tech);
2519
2445
  }
2520
2446
  } catch {}
@@ -2585,7 +2511,7 @@ var codebaseTools = [
2585
2511
  async execute({ path }) {
2586
2512
  const pkgJsonPath = path || join4(process.cwd(), "package.json");
2587
2513
  try {
2588
- const content = await readFile(pkgJsonPath, "utf-8");
2514
+ const content = await readFile2(pkgJsonPath, "utf-8");
2589
2515
  const pkgJson = JSON.parse(content);
2590
2516
  const dependencies = pkgJson.dependencies || {};
2591
2517
  const devDependencies = pkgJson.devDependencies || {};
@@ -3486,7 +3412,7 @@ Platform: ${process.platform}
3486
3412
  // src/utils/config.ts
3487
3413
  import { join as join5 } from "path";
3488
3414
  import { homedir as homedir3 } from "os";
3489
- import { readFileSync, existsSync as existsSync3 } from "fs";
3415
+ import { readFileSync, existsSync as existsSync4 } from "fs";
3490
3416
  var cachedConfig = null;
3491
3417
  function deepMerge(target, source) {
3492
3418
  const result = { ...target };
@@ -3503,7 +3429,7 @@ function deepMerge(target, source) {
3503
3429
  }
3504
3430
  function loadJsonConfig(filePath) {
3505
3431
  try {
3506
- if (!existsSync3(filePath)) {
3432
+ if (!existsSync4(filePath)) {
3507
3433
  return null;
3508
3434
  }
3509
3435
  const raw = readFileSync(filePath, "utf-8");
@@ -3585,12 +3511,12 @@ function loadConfig() {
3585
3511
  }
3586
3512
  function getWorkingDir(cliOption) {
3587
3513
  if (cliOption) {
3588
- if (existsSync3(cliOption))
3514
+ if (existsSync4(cliOption))
3589
3515
  return cliOption;
3590
3516
  return process.cwd();
3591
3517
  }
3592
3518
  const config = loadConfig();
3593
- if (config.paths.defaultWorkingDir && existsSync3(config.paths.defaultWorkingDir)) {
3519
+ if (config.paths.defaultWorkingDir && existsSync4(config.paths.defaultWorkingDir)) {
3594
3520
  return config.paths.defaultWorkingDir;
3595
3521
  }
3596
3522
  return process.cwd();
@@ -7575,10 +7501,10 @@ function ConfirmDialog({
7575
7501
 
7576
7502
  // src/ui/commands/registry.ts
7577
7503
  import { resolve as resolve2, isAbsolute } from "path";
7578
- import { existsSync as existsSync4, statSync as statSync2, readFileSync as readFileSync2 } from "fs";
7504
+ import { existsSync as existsSync5, statSync as statSync2, readFileSync as readFileSync2 } from "fs";
7579
7505
 
7580
7506
  // src/commands/init.ts
7581
- import { writeFile } from "fs/promises";
7507
+ import { writeFile as writeFile2, readFile as fsReadFile } from "fs/promises";
7582
7508
  import { join as join6 } from "path";
7583
7509
  async function initProject() {
7584
7510
  log.info("Initializing Sharkbait in current project...");
@@ -7595,7 +7521,7 @@ async function initProject() {
7595
7521
  }
7596
7522
  };
7597
7523
  try {
7598
- await writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
7524
+ await writeFile2(configPath, JSON.stringify(defaultConfig, null, 2));
7599
7525
  log.success(`Created ${configPath}`);
7600
7526
  } catch (error) {
7601
7527
  log.warn(`Could not create config file: ${error}`);
@@ -7613,16 +7539,16 @@ SHARKBAIT_MAX_CONTEXT_TOKENS=100000
7613
7539
  SHARKBAIT_CONFIRM_DESTRUCTIVE=true
7614
7540
  `;
7615
7541
  try {
7616
- await writeFile(envExamplePath, envExample);
7542
+ await writeFile2(envExamplePath, envExample);
7617
7543
  log.success(`Created ${envExamplePath}`);
7618
7544
  } catch (error) {
7619
7545
  log.warn(`Could not create .env.example: ${error}`);
7620
7546
  }
7621
7547
  const gitignorePath = join6(cwd, ".gitignore");
7622
7548
  try {
7623
- const gitignoreContent = await Bun.file(gitignorePath).text();
7549
+ const gitignoreContent = await fsReadFile(gitignorePath, "utf-8");
7624
7550
  if (!gitignoreContent.includes(".env")) {
7625
- await Bun.write(gitignorePath, gitignoreContent + `
7551
+ await writeFile2(gitignorePath, gitignoreContent + `
7626
7552
  # Sharkbait
7627
7553
  .env
7628
7554
  `);
@@ -7723,7 +7649,7 @@ Usage: /cd <path>`
7723
7649
  };
7724
7650
  }
7725
7651
  const newPath = isAbsolute(args) ? args : resolve2(ctx.currentDir, args);
7726
- if (!existsSync4(newPath)) {
7652
+ if (!existsSync5(newPath)) {
7727
7653
  ctx.setPendingConfirm({ type: "mkdir", data: { path: newPath } });
7728
7654
  return {
7729
7655
  handled: true,
@@ -8092,7 +8018,7 @@ Example:
8092
8018
  }
8093
8019
  }
8094
8020
  const resolvedPath = isAbsolute(filePath) ? filePath : resolve2(ctx.currentDir, filePath);
8095
- if (!existsSync4(resolvedPath)) {
8021
+ if (!existsSync5(resolvedPath)) {
8096
8022
  return {
8097
8023
  handled: true,
8098
8024
  message: `File not found: ${resolvedPath}`,
@@ -8778,7 +8704,7 @@ ${event.consolidated}`,
8778
8704
  }
8779
8705
 
8780
8706
  // src/version.ts
8781
- var VERSION = "1.0.11";
8707
+ var VERSION = "1.0.13";
8782
8708
 
8783
8709
  // src/agent/start-chat.ts
8784
8710
  async function startChat(options = {}) {
@@ -8797,7 +8723,7 @@ async function startChat(options = {}) {
8797
8723
  await waitUntilExit();
8798
8724
  }
8799
8725
  // src/commands/init.ts
8800
- import { writeFile as writeFile2 } from "fs/promises";
8726
+ import { writeFile as writeFile3, readFile as fsReadFile2 } from "fs/promises";
8801
8727
  import { join as join7 } from "path";
8802
8728
  async function initProject2() {
8803
8729
  log.info("Initializing Sharkbait in current project...");
@@ -8814,7 +8740,7 @@ async function initProject2() {
8814
8740
  }
8815
8741
  };
8816
8742
  try {
8817
- await writeFile2(configPath, JSON.stringify(defaultConfig, null, 2));
8743
+ await writeFile3(configPath, JSON.stringify(defaultConfig, null, 2));
8818
8744
  log.success(`Created ${configPath}`);
8819
8745
  } catch (error) {
8820
8746
  log.warn(`Could not create config file: ${error}`);
@@ -8832,16 +8758,16 @@ SHARKBAIT_MAX_CONTEXT_TOKENS=100000
8832
8758
  SHARKBAIT_CONFIRM_DESTRUCTIVE=true
8833
8759
  `;
8834
8760
  try {
8835
- await writeFile2(envExamplePath, envExample);
8761
+ await writeFile3(envExamplePath, envExample);
8836
8762
  log.success(`Created ${envExamplePath}`);
8837
8763
  } catch (error) {
8838
8764
  log.warn(`Could not create .env.example: ${error}`);
8839
8765
  }
8840
8766
  const gitignorePath = join7(cwd, ".gitignore");
8841
8767
  try {
8842
- const gitignoreContent = await Bun.file(gitignorePath).text();
8768
+ const gitignoreContent = await fsReadFile2(gitignorePath, "utf-8");
8843
8769
  if (!gitignoreContent.includes(".env")) {
8844
- await Bun.write(gitignorePath, gitignoreContent + `
8770
+ await writeFile3(gitignorePath, gitignoreContent + `
8845
8771
  # Sharkbait
8846
8772
  .env
8847
8773
  `);
@@ -8965,10 +8891,10 @@ Task completed.`);
8965
8891
  import React4, { useState as useState3, useEffect as useEffect3 } from "react";
8966
8892
  import { render as render2, Box as Box13, Text as Text13, useInput as useInput2, useApp as useApp2 } from "ink";
8967
8893
  import TextInput from "ink-text-input";
8968
- import { writeFile as writeFile3, readFile as readFile2, mkdir as mkdir4, stat as stat3 } from "fs/promises";
8894
+ import { writeFile as writeFile4, readFile as readFile3, mkdir as mkdir4, stat as stat3 } from "fs/promises";
8969
8895
  import { join as join8, resolve as resolve3 } from "path";
8970
8896
  import { homedir as homedir4 } from "os";
8971
- import { existsSync as existsSync5 } from "fs";
8897
+ import { existsSync as existsSync6 } from "fs";
8972
8898
  import { execSync } from "child_process";
8973
8899
  import { jsxDEV as jsxDEV13, Fragment as Fragment3 } from "react/jsx-dev-runtime";
8974
8900
  function SetupWizardWithCallback({ onComplete }) {
@@ -8993,8 +8919,8 @@ function SetupWizardWithCallback({ onComplete }) {
8993
8919
  const configDir2 = join8(homedir4(), ".sharkbait");
8994
8920
  const configPath = join8(configDir2, "config.json");
8995
8921
  try {
8996
- if (existsSync5(configPath)) {
8997
- const content = await readFile2(configPath, "utf-8");
8922
+ if (existsSync6(configPath)) {
8923
+ const content = await readFile3(configPath, "utf-8");
8998
8924
  const existing = JSON.parse(content);
8999
8925
  setState((prev) => ({
9000
8926
  ...prev,
@@ -9126,7 +9052,7 @@ function SetupWizardWithCallback({ onComplete }) {
9126
9052
  case "working-dir": {
9127
9053
  if (value.trim()) {
9128
9054
  const resolved = resolve3(value.trim().replace(/^~/, homedir4()));
9129
- if (!existsSync5(resolved)) {
9055
+ if (!existsSync6(resolved)) {
9130
9056
  setError(`Directory not found: ${resolved}`);
9131
9057
  return;
9132
9058
  }
@@ -9172,7 +9098,7 @@ function SetupWizardWithCallback({ onComplete }) {
9172
9098
  showSpinner: true
9173
9099
  }
9174
9100
  };
9175
- await writeFile3(join8(configDir2, "config.json"), JSON.stringify(config, null, 2));
9101
+ await writeFile4(join8(configDir2, "config.json"), JSON.stringify(config, null, 2));
9176
9102
  const envLines = [
9177
9103
  "# Sharkbait Azure OpenAI Configuration",
9178
9104
  `# Generated by 'sharkbait setup' on ${new Date().toISOString()}`,
@@ -9191,7 +9117,7 @@ function SetupWizardWithCallback({ onComplete }) {
9191
9117
  const envContent = envLines.join(`
9192
9118
  `) + `
9193
9119
  `;
9194
- await writeFile3(join8(configDir2, ".env"), envContent);
9120
+ await writeFile4(join8(configDir2, ".env"), envContent);
9195
9121
  setStep("complete");
9196
9122
  } catch (err) {
9197
9123
  setError(`Failed to save: ${err}`);
@@ -9830,12 +9756,12 @@ async function runSetup() {
9830
9756
  }
9831
9757
 
9832
9758
  // src/commands/review.ts
9833
- import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
9759
+ import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
9834
9760
  import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
9835
9761
  async function runReview(filePath, options = {}) {
9836
9762
  const config = loadConfig();
9837
9763
  const resolvedPath = isAbsolute2(filePath) ? filePath : resolve4(process.cwd(), filePath);
9838
- if (!existsSync6(resolvedPath)) {
9764
+ if (!existsSync7(resolvedPath)) {
9839
9765
  console.error(`❌ File not found: ${resolvedPath}`);
9840
9766
  process.exit(1);
9841
9767
  }
@@ -9955,7 +9881,7 @@ ${"━".repeat(60)}`);
9955
9881
  }
9956
9882
 
9957
9883
  // src/version.ts
9958
- var VERSION2 = "1.0.11";
9884
+ var VERSION2 = "1.0.13";
9959
9885
 
9960
9886
  // src/ui/logo.tsx
9961
9887
  import { Box as Box14, Text as Text14 } from "ink";
@@ -9990,12 +9916,12 @@ var SHARK_LOGO2 = `
9990
9916
  `.trimEnd();
9991
9917
 
9992
9918
  // src/cli.ts
9993
- import { existsSync as existsSync7 } from "fs";
9919
+ import { existsSync as existsSync8 } from "fs";
9994
9920
  import { join as join9 } from "path";
9995
9921
  import { homedir as homedir5 } from "os";
9996
9922
  var args = process.argv.slice(2);
9997
9923
  if (args.length === 0) {
9998
- const configExists = existsSync7(join9(homedir5(), ".sharkbait", "config.json"));
9924
+ const configExists = existsSync8(join9(homedir5(), ".sharkbait", "config.json"));
9999
9925
  if (!configExists) {
10000
9926
  const completed = await runSetup();
10001
9927
  if (completed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sharkbait",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "AI-powered coding assistant for the command line. Uses OpenAI Responses API (not Chat). Autonomous agents, parallel code reviews, 36 tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",