agentplane 0.1.6 → 0.1.8

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 (138) hide show
  1. package/assets/AGENTS.md +1 -1
  2. package/assets/agents/ORCHESTRATOR.json +1 -1
  3. package/assets/agents/UPGRADER.json +1 -1
  4. package/dist/backends/task-backend.d.ts +16 -0
  5. package/dist/backends/task-backend.d.ts.map +1 -1
  6. package/dist/backends/task-backend.js +44 -0
  7. package/dist/backends/task-index.d.ts.map +1 -1
  8. package/dist/backends/task-index.js +3 -6
  9. package/dist/cli/command-guide.d.ts.map +1 -1
  10. package/dist/cli/command-guide.js +4 -4
  11. package/dist/cli/help.d.ts.map +1 -1
  12. package/dist/cli/help.js +7 -5
  13. package/dist/cli/run-cli.d.ts.map +1 -1
  14. package/dist/cli/run-cli.js +39 -78
  15. package/dist/commands/backend.d.ts.map +1 -1
  16. package/dist/commands/backend.js +17 -2
  17. package/dist/commands/branch/index.d.ts +60 -0
  18. package/dist/commands/branch/index.d.ts.map +1 -0
  19. package/dist/commands/branch/index.js +513 -0
  20. package/dist/commands/guard/index.d.ts +67 -0
  21. package/dist/commands/guard/index.d.ts.map +1 -0
  22. package/dist/commands/guard/index.js +367 -0
  23. package/dist/commands/hooks/index.d.ts +18 -0
  24. package/dist/commands/hooks/index.d.ts.map +1 -0
  25. package/dist/commands/hooks/index.js +290 -0
  26. package/dist/commands/pr/index.d.ts +46 -0
  27. package/dist/commands/pr/index.d.ts.map +1 -0
  28. package/dist/commands/pr/index.js +857 -0
  29. package/dist/commands/recipes.d.ts.map +1 -1
  30. package/dist/commands/recipes.js +67 -23
  31. package/dist/commands/shared/git-diff.d.ts +9 -0
  32. package/dist/commands/shared/git-diff.d.ts.map +1 -0
  33. package/dist/commands/shared/git-diff.js +41 -0
  34. package/dist/commands/shared/git-ops.d.ts +24 -0
  35. package/dist/commands/shared/git-ops.d.ts.map +1 -0
  36. package/dist/commands/shared/git-ops.js +181 -0
  37. package/dist/commands/shared/git-worktree.d.ts +8 -0
  38. package/dist/commands/shared/git-worktree.d.ts.map +1 -0
  39. package/dist/commands/shared/git-worktree.js +48 -0
  40. package/dist/commands/shared/git.d.ts +4 -0
  41. package/dist/commands/shared/git.d.ts.map +1 -0
  42. package/dist/commands/shared/git.js +14 -0
  43. package/dist/commands/shared/network-approval.d.ts +8 -0
  44. package/dist/commands/shared/network-approval.d.ts.map +1 -0
  45. package/dist/commands/shared/network-approval.js +25 -0
  46. package/dist/commands/shared/path.d.ts +3 -0
  47. package/dist/commands/shared/path.d.ts.map +1 -0
  48. package/dist/commands/shared/path.js +14 -0
  49. package/dist/commands/shared/pr-meta.d.ts +21 -0
  50. package/dist/commands/shared/pr-meta.d.ts.map +1 -0
  51. package/dist/commands/shared/pr-meta.js +72 -0
  52. package/dist/commands/shared/task-backend.d.ts +15 -0
  53. package/dist/commands/shared/task-backend.d.ts.map +1 -0
  54. package/dist/commands/shared/task-backend.js +61 -0
  55. package/dist/commands/task/add.d.ts +8 -0
  56. package/dist/commands/task/add.d.ts.map +1 -0
  57. package/dist/commands/task/add.js +164 -0
  58. package/dist/commands/task/block.d.ts +19 -0
  59. package/dist/commands/task/block.d.ts.map +1 -0
  60. package/dist/commands/task/block.js +86 -0
  61. package/dist/commands/task/comment.d.ts +8 -0
  62. package/dist/commands/task/comment.d.ts.map +1 -0
  63. package/dist/commands/task/comment.js +29 -0
  64. package/dist/commands/task/doc.d.ts +17 -0
  65. package/dist/commands/task/doc.d.ts.map +1 -0
  66. package/dist/commands/task/doc.js +220 -0
  67. package/dist/commands/task/export.d.ts +5 -0
  68. package/dist/commands/task/export.d.ts.map +1 -0
  69. package/dist/commands/task/export.js +27 -0
  70. package/dist/commands/task/finish.d.ts +27 -0
  71. package/dist/commands/task/finish.d.ts.map +1 -0
  72. package/dist/commands/task/finish.js +132 -0
  73. package/dist/commands/task/index.d.ts +26 -0
  74. package/dist/commands/task/index.d.ts.map +1 -0
  75. package/dist/commands/task/index.js +25 -0
  76. package/dist/commands/task/lint.d.ts +5 -0
  77. package/dist/commands/task/lint.d.ts.map +1 -0
  78. package/dist/commands/task/lint.js +22 -0
  79. package/dist/commands/task/list.d.ts +11 -0
  80. package/dist/commands/task/list.d.ts.map +1 -0
  81. package/dist/commands/task/list.js +54 -0
  82. package/dist/commands/task/migrate-doc.d.ts +8 -0
  83. package/dist/commands/task/migrate-doc.d.ts.map +1 -0
  84. package/dist/commands/task/migrate-doc.js +147 -0
  85. package/dist/commands/task/migrate.d.ts +6 -0
  86. package/dist/commands/task/migrate.d.ts.map +1 -0
  87. package/dist/commands/task/migrate.js +70 -0
  88. package/dist/commands/task/new.d.ts +8 -0
  89. package/dist/commands/task/new.d.ts.map +1 -0
  90. package/dist/commands/task/new.js +117 -0
  91. package/dist/commands/task/next.d.ts +6 -0
  92. package/dist/commands/task/next.d.ts.map +1 -0
  93. package/dist/commands/task/next.js +45 -0
  94. package/dist/commands/task/normalize.d.ts +6 -0
  95. package/dist/commands/task/normalize.d.ts.map +1 -0
  96. package/dist/commands/task/normalize.js +46 -0
  97. package/dist/commands/task/plan.d.ts +14 -0
  98. package/dist/commands/task/plan.d.ts.map +1 -0
  99. package/dist/commands/task/plan.js +217 -0
  100. package/dist/commands/task/ready.d.ts +6 -0
  101. package/dist/commands/task/ready.d.ts.map +1 -0
  102. package/dist/commands/task/ready.js +57 -0
  103. package/dist/commands/task/scaffold.d.ts +8 -0
  104. package/dist/commands/task/scaffold.d.ts.map +1 -0
  105. package/dist/commands/task/scaffold.js +142 -0
  106. package/dist/commands/task/scrub.d.ts +8 -0
  107. package/dist/commands/task/scrub.d.ts.map +1 -0
  108. package/dist/commands/task/scrub.js +121 -0
  109. package/dist/commands/task/search.d.ts +7 -0
  110. package/dist/commands/task/search.d.ts.map +1 -0
  111. package/dist/commands/task/search.js +79 -0
  112. package/dist/commands/task/set-status.d.ts +19 -0
  113. package/dist/commands/task/set-status.d.ts.map +1 -0
  114. package/dist/commands/task/set-status.js +123 -0
  115. package/dist/commands/task/shared.d.ts +48 -0
  116. package/dist/commands/task/shared.d.ts.map +1 -0
  117. package/dist/commands/task/shared.js +312 -0
  118. package/dist/commands/task/show.d.ts +6 -0
  119. package/dist/commands/task/show.d.ts.map +1 -0
  120. package/dist/commands/task/show.js +35 -0
  121. package/dist/commands/task/start.d.ts +19 -0
  122. package/dist/commands/task/start.d.ts.map +1 -0
  123. package/dist/commands/task/start.js +110 -0
  124. package/dist/commands/task/update.d.ts +8 -0
  125. package/dist/commands/task/update.d.ts.map +1 -0
  126. package/dist/commands/task/update.js +144 -0
  127. package/dist/commands/task/verify-record.d.ts +16 -0
  128. package/dist/commands/task/verify-record.d.ts.map +1 -0
  129. package/dist/commands/task/verify-record.js +277 -0
  130. package/dist/commands/task/verify.d.ts +2 -0
  131. package/dist/commands/task/verify.d.ts.map +1 -0
  132. package/dist/commands/task/verify.js +1 -0
  133. package/dist/commands/upgrade.d.ts.map +1 -1
  134. package/dist/commands/upgrade.js +17 -2
  135. package/dist/commands/workflow.d.ts +5 -364
  136. package/dist/commands/workflow.d.ts.map +1 -1
  137. package/dist/commands/workflow.js +6 -4617
  138. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/commands/recipes.ts"],"names":[],"mappings":"AA2kEA,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwGlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB"}
1
+ {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/commands/recipes.ts"],"names":[],"mappings":"AAinEA,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyGlB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB"}
@@ -1,10 +1,10 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { createPublicKey, verify } from "node:crypto";
3
- import { cp, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, writeFile, } from "node:fs/promises";
3
+ import { cp, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { resolveProject } from "@agentplaneorg/core";
7
+ import { atomicWriteFile, defaultConfig, loadConfig, resolveProject } from "@agentplaneorg/core";
8
8
  import { extractArchive } from "../cli/archive.js";
9
9
  import { sha256File } from "../cli/checksum.js";
10
10
  import { mapCoreError } from "../cli/error-map.js";
@@ -12,6 +12,7 @@ import { fileExists, getPathKind } from "../cli/fs-utils.js";
12
12
  import { downloadToFile, fetchJson, fetchText } from "../cli/http.js";
13
13
  import { emptyStateMessage, infoMessage, invalidFieldMessage, invalidPathMessage, missingValueMessage, missingFileMessage, requiredFieldMessage, successMessage, usageMessage, } from "../cli/output.js";
14
14
  import { CliError } from "../shared/errors.js";
15
+ import { ensureNetworkApproved } from "./shared/network-approval.js";
15
16
  const execFileAsync = promisify(execFile);
16
17
  const INSTALLED_RECIPES_NAME = "recipes.json";
17
18
  const RECIPES_DIR_NAME = "recipes";
@@ -25,7 +26,7 @@ const RECIPE_INFO_USAGE = "Usage: agentplane recipes info <id>";
25
26
  const RECIPE_INFO_USAGE_EXAMPLE = "agentplane recipes info viewer";
26
27
  const RECIPE_EXPLAIN_USAGE = "Usage: agentplane recipes explain <id>";
27
28
  const RECIPE_EXPLAIN_USAGE_EXAMPLE = "agentplane recipes explain viewer";
28
- const RECIPE_INSTALL_USAGE = "Usage: agentplane recipes install --name <id> [--index <path|url>] [--refresh] | --path <path> | --url <url>";
29
+ const RECIPE_INSTALL_USAGE = "Usage: agentplane recipes install --name <id> [--index <path|url>] [--refresh] [--yes] | --path <path> | --url <url> [--yes]";
29
30
  const RECIPE_INSTALL_USAGE_EXAMPLE = "agentplane recipes install --name viewer";
30
31
  const RECIPE_REMOVE_USAGE = "Usage: agentplane recipes remove <id>";
31
32
  const RECIPE_REMOVE_USAGE_EXAMPLE = "agentplane recipes remove viewer";
@@ -33,7 +34,7 @@ const RECIPE_CACHE_USAGE = "Usage: agentplane recipes cache <prune> [args]";
33
34
  const RECIPE_CACHE_USAGE_EXAMPLE = "agentplane recipes cache prune --dry-run";
34
35
  const RECIPE_CACHE_PRUNE_USAGE = "Usage: agentplane recipes cache prune [--dry-run] [--all]";
35
36
  const RECIPE_CACHE_PRUNE_USAGE_EXAMPLE = "agentplane recipes cache prune --dry-run";
36
- const RECIPE_LIST_REMOTE_USAGE = "Usage: agentplane recipes list-remote [--refresh] [--index <path|url>]";
37
+ const RECIPE_LIST_REMOTE_USAGE = "Usage: agentplane recipes list-remote [--refresh] [--index <path|url>] [--yes]";
37
38
  const RECIPE_LIST_REMOTE_USAGE_EXAMPLE = "agentplane recipes list-remote --refresh";
38
39
  const DEFAULT_RECIPES_INDEX_URL = "https://raw.githubusercontent.com/basilisk-labs/agentplane-recipes/main/index.json";
39
40
  const RECIPES_INDEX_PUBLIC_KEYS_ENV = "AGENTPLANE_RECIPES_INDEX_PUBLIC_KEYS";
@@ -166,7 +167,7 @@ async function writeScenarioReport(opts) {
166
167
  steps: opts.steps,
167
168
  git: opts.gitSummary,
168
169
  };
169
- await writeFile(path.join(opts.runDir, SCENARIO_REPORT_NAME), `${JSON.stringify(report, null, 2)}\n`, "utf8");
170
+ await atomicWriteFile(path.join(opts.runDir, SCENARIO_REPORT_NAME), `${JSON.stringify(report, null, 2)}\n`, "utf8");
170
171
  }
171
172
  async function maybeResolveProject(opts) {
172
173
  try {
@@ -185,7 +186,7 @@ async function maybeResolveProject(opts) {
185
186
  }
186
187
  }
187
188
  function parseRecipeListRemoteFlags(args) {
188
- const out = { refresh: false };
189
+ const out = { refresh: false, yes: false };
189
190
  for (let i = 0; i < args.length; i++) {
190
191
  const arg = args[i];
191
192
  if (!arg)
@@ -201,6 +202,10 @@ function parseRecipeListRemoteFlags(args) {
201
202
  out.refresh = true;
202
203
  continue;
203
204
  }
205
+ if (arg === "--yes") {
206
+ out.yes = true;
207
+ continue;
208
+ }
204
209
  const next = args[i + 1];
205
210
  if (!next) {
206
211
  throw new CliError({ exitCode: 2, code: "E_USAGE", message: missingValueMessage(arg) });
@@ -575,7 +580,7 @@ async function writeInstalledRecipesFile(filePath, file) {
575
580
  updated_at: new Date().toISOString(),
576
581
  });
577
582
  await mkdir(path.dirname(filePath), { recursive: true });
578
- await writeFile(filePath, `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
583
+ await atomicWriteFile(filePath, `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
579
584
  }
580
585
  async function readRecipeManifest(manifestPath) {
581
586
  const raw = JSON.parse(await readFile(manifestPath, "utf8"));
@@ -624,6 +629,7 @@ function parseRecipeInstallArgs(args) {
624
629
  let url = null;
625
630
  let index;
626
631
  let refresh = false;
632
+ let yes = false;
627
633
  const positional = [];
628
634
  for (let i = 0; i < args.length; i++) {
629
635
  const arg = args[i];
@@ -681,6 +687,10 @@ function parseRecipeInstallArgs(args) {
681
687
  refresh = true;
682
688
  continue;
683
689
  }
690
+ if (arg === "--yes") {
691
+ yes = true;
692
+ continue;
693
+ }
684
694
  if (arg === "--on-conflict") {
685
695
  const next = args[i + 1];
686
696
  if (!next)
@@ -732,13 +742,13 @@ function parseRecipeInstallArgs(args) {
732
742
  });
733
743
  }
734
744
  if (name)
735
- return { source: { type: "name", value: name }, index, refresh, onConflict };
745
+ return { source: { type: "name", value: name }, index, refresh, onConflict, yes };
736
746
  if (localPath)
737
- return { source: { type: "path", value: localPath }, index, refresh, onConflict };
747
+ return { source: { type: "path", value: localPath }, index, refresh, onConflict, yes };
738
748
  if (url)
739
- return { source: { type: "url", value: url }, index, refresh, onConflict };
749
+ return { source: { type: "url", value: url }, index, refresh, onConflict, yes };
740
750
  if (positional.length === 1) {
741
- return { source: { type: "auto", value: positional[0] }, index, refresh, onConflict };
751
+ return { source: { type: "auto", value: positional[0] }, index, refresh, onConflict, yes };
742
752
  }
743
753
  throw new CliError({
744
754
  exitCode: 2,
@@ -853,7 +863,7 @@ async function applyRecipeAgents(opts) {
853
863
  }
854
864
  }
855
865
  rawAgent.id = targetId;
856
- await writeFile(targetPath, `${JSON.stringify(rawAgent, null, 2)}\n`, "utf8");
866
+ await atomicWriteFile(targetPath, `${JSON.stringify(rawAgent, null, 2)}\n`, "utf8");
857
867
  }
858
868
  }
859
869
  async function applyRecipeScenarios(opts) {
@@ -881,7 +891,7 @@ async function applyRecipeScenarios(opts) {
881
891
  }
882
892
  if (payload.scenarios.length === 0)
883
893
  return;
884
- await writeFile(scenariosIndexPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
894
+ await atomicWriteFile(scenariosIndexPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
885
895
  }
886
896
  function isHttpUrl(value) {
887
897
  return value.startsWith("http://") || value.startsWith("https://");
@@ -913,8 +923,8 @@ async function loadRecipesRemoteIndex(opts) {
913
923
  verifyRecipesIndexSignature(indexText, signature);
914
924
  rawIndex = JSON.parse(indexText);
915
925
  await mkdir(cacheDir, { recursive: true });
916
- await writeFile(cachePath, indexText, "utf8");
917
- await writeFile(cacheSigPath, `${JSON.stringify(signature, null, 2)}\n`, "utf8");
926
+ await atomicWriteFile(cachePath, indexText, "utf8");
927
+ await atomicWriteFile(cacheSigPath, `${JSON.stringify(signature, null, 2)}\n`, "utf8");
918
928
  }
919
929
  else {
920
930
  const [indexText, sigText] = await Promise.all([
@@ -968,6 +978,22 @@ async function cmdRecipeList(opts) {
968
978
  async function cmdRecipeListRemote(opts) {
969
979
  const flags = parseRecipeListRemoteFlags(opts.args);
970
980
  try {
981
+ const project = await maybeResolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride });
982
+ let config = defaultConfig();
983
+ if (project) {
984
+ const loaded = await loadConfig(project.agentplaneDir);
985
+ config = loaded.config;
986
+ }
987
+ const source = flags.index ?? DEFAULT_RECIPES_INDEX_URL;
988
+ const cachePath = resolveRecipesIndexCachePath();
989
+ const willFetchRemote = (flags.refresh || !(await fileExists(cachePath))) && isHttpUrl(source);
990
+ if (willFetchRemote) {
991
+ await ensureNetworkApproved({
992
+ config,
993
+ yes: flags.yes,
994
+ reason: "recipes list-remote fetches the remote recipes index",
995
+ });
996
+ }
971
997
  const index = await loadRecipesRemoteIndex({
972
998
  cwd: opts.cwd,
973
999
  source: flags.index,
@@ -1256,8 +1282,8 @@ async function cmdScenarioRun(opts) {
1256
1282
  env,
1257
1283
  });
1258
1284
  const durationMs = Date.now() - startedAt;
1259
- await writeFile(path.join(stepDir, "stdout.log"), result.stdout, "utf8");
1260
- await writeFile(path.join(stepDir, "stderr.log"), result.stderr, "utf8");
1285
+ await atomicWriteFile(path.join(stepDir, "stdout.log"), result.stdout, "utf8");
1286
+ await atomicWriteFile(path.join(stepDir, "stderr.log"), result.stderr, "utf8");
1261
1287
  stepsMeta.push({
1262
1288
  tool: step.tool,
1263
1289
  runtime,
@@ -1287,7 +1313,7 @@ async function cmdScenarioRun(opts) {
1287
1313
  steps: stepsReport,
1288
1314
  gitSummary,
1289
1315
  });
1290
- await writeFile(path.join(runDir, "meta.json"), `${JSON.stringify({
1316
+ await atomicWriteFile(path.join(runDir, "meta.json"), `${JSON.stringify({
1291
1317
  recipe: recipeId,
1292
1318
  scenario: scenarioId,
1293
1319
  run_id: runId,
@@ -1311,7 +1337,7 @@ async function cmdScenarioRun(opts) {
1311
1337
  steps: stepsReport,
1312
1338
  gitSummary,
1313
1339
  });
1314
- await writeFile(path.join(runDir, "meta.json"), `${JSON.stringify({
1340
+ await atomicWriteFile(path.join(runDir, "meta.json"), `${JSON.stringify({
1315
1341
  recipe: recipeId,
1316
1342
  scenario: scenarioId,
1317
1343
  run_id: runId,
@@ -1464,6 +1490,19 @@ async function cmdRecipeExplain(opts) {
1464
1490
  }
1465
1491
  async function cmdRecipeInstall(opts) {
1466
1492
  try {
1493
+ const project = await maybeResolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride });
1494
+ let config = defaultConfig();
1495
+ if (project) {
1496
+ const loaded = await loadConfig(project.agentplaneDir);
1497
+ config = loaded.config;
1498
+ }
1499
+ let networkApproved = false;
1500
+ const ensureApproved = async (reason) => {
1501
+ if (networkApproved)
1502
+ return;
1503
+ await ensureNetworkApproved({ config, yes: opts.yes, reason });
1504
+ networkApproved = true;
1505
+ };
1467
1506
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "agentplane-recipe-"));
1468
1507
  try {
1469
1508
  let sourcePath = "";
@@ -1471,6 +1510,12 @@ async function cmdRecipeInstall(opts) {
1471
1510
  let expectedSha = "";
1472
1511
  let indexTags = [];
1473
1512
  const resolveFromIndex = async (recipeId) => {
1513
+ const indexSource = opts.index ?? DEFAULT_RECIPES_INDEX_URL;
1514
+ const cachePath = resolveRecipesIndexCachePath();
1515
+ const willFetchRemote = (opts.refresh || !(await fileExists(cachePath))) && isHttpUrl(indexSource);
1516
+ if (willFetchRemote) {
1517
+ await ensureApproved("recipes install fetches the remote recipes index");
1518
+ }
1474
1519
  const index = await loadRecipesRemoteIndex({
1475
1520
  cwd: opts.cwd,
1476
1521
  source: opts.index,
@@ -1498,6 +1543,7 @@ async function cmdRecipeInstall(opts) {
1498
1543
  sourceLabel = `${entry.id}@${latest.version}`;
1499
1544
  indexTags = normalizeRecipeTags(latest.tags ?? []);
1500
1545
  if (isHttpUrl(latest.url)) {
1546
+ await ensureApproved("recipes install downloads a recipe archive");
1501
1547
  const url = new URL(latest.url);
1502
1548
  const filename = path.basename(url.pathname) || "recipe.tar.gz";
1503
1549
  const target = path.join(tempRoot, filename);
@@ -1518,6 +1564,7 @@ async function cmdRecipeInstall(opts) {
1518
1564
  if (source.type === "name")
1519
1565
  return await resolveFromIndex(source.value);
1520
1566
  if (source.type === "url") {
1567
+ await ensureApproved("recipes install downloads a recipe archive");
1521
1568
  const url = new URL(source.value);
1522
1569
  const filename = path.basename(url.pathname) || "recipe.tar.gz";
1523
1570
  const target = path.join(tempRoot, filename);
@@ -1595,10 +1642,6 @@ async function cmdRecipeInstall(opts) {
1595
1642
  }
1596
1643
  }
1597
1644
  try {
1598
- const project = await maybeResolveProject({
1599
- cwd: opts.cwd,
1600
- rootOverride: opts.rootOverride,
1601
- });
1602
1645
  if (project) {
1603
1646
  await applyRecipeAgents({
1604
1647
  manifest: manifestWithTags,
@@ -1819,6 +1862,7 @@ export async function cmdRecipes(opts) {
1819
1862
  index: parsed.index,
1820
1863
  refresh: parsed.refresh,
1821
1864
  onConflict: parsed.onConflict,
1865
+ yes: parsed.yes,
1822
1866
  });
1823
1867
  }
1824
1868
  if (subcommand === "remove") {
@@ -0,0 +1,9 @@
1
+ export declare function toGitPath(filePath: string): string;
2
+ export declare function gitShowFile(cwd: string, ref: string, relPath: string): Promise<string>;
3
+ export declare function gitDiffNames(cwd: string, base: string, branch: string): Promise<string[]>;
4
+ export declare function gitDiffStat(cwd: string, base: string, branch: string): Promise<string>;
5
+ export declare function gitAheadBehind(cwd: string, base: string, branch: string): Promise<{
6
+ ahead: number;
7
+ behind: number;
8
+ }>;
9
+ //# sourceMappingURL=git-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git-diff.ts"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5F;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAS/F;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5F;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAa5C"}
@@ -0,0 +1,41 @@
1
+ import path from "node:path";
2
+ import { execFileAsync, gitEnv } from "./git.js";
3
+ export function toGitPath(filePath) {
4
+ return filePath.split(path.sep).join("/");
5
+ }
6
+ export async function gitShowFile(cwd, ref, relPath) {
7
+ const { stdout } = await execFileAsync("git", ["show", `${ref}:${relPath}`], {
8
+ cwd,
9
+ env: gitEnv(),
10
+ });
11
+ return stdout;
12
+ }
13
+ export async function gitDiffNames(cwd, base, branch) {
14
+ const { stdout } = await execFileAsync("git", ["diff", "--name-only", `${base}...${branch}`], {
15
+ cwd,
16
+ env: gitEnv(),
17
+ });
18
+ return stdout
19
+ .split("\n")
20
+ .map((line) => line.trim())
21
+ .filter((line) => line.length > 0);
22
+ }
23
+ export async function gitDiffStat(cwd, base, branch) {
24
+ const { stdout } = await execFileAsync("git", ["diff", "--stat", `${base}...${branch}`], {
25
+ cwd,
26
+ env: gitEnv(),
27
+ });
28
+ return stdout.trimEnd();
29
+ }
30
+ export async function gitAheadBehind(cwd, base, branch) {
31
+ const { stdout } = await execFileAsync("git", ["rev-list", "--left-right", "--count", `${base}...${branch}`], { cwd, env: gitEnv() });
32
+ const trimmed = stdout.trim();
33
+ if (!trimmed)
34
+ return { ahead: 0, behind: 0 };
35
+ const parts = trimmed.split(/\s+/);
36
+ if (parts.length !== 2)
37
+ return { ahead: 0, behind: 0 };
38
+ const behind = Number.parseInt(parts[0] ?? "0", 10) || 0;
39
+ const ahead = Number.parseInt(parts[1] ?? "0", 10) || 0;
40
+ return { ahead, behind };
41
+ }
@@ -0,0 +1,24 @@
1
+ export declare function gitRevParse(cwd: string, args: string[]): Promise<string>;
2
+ export declare function gitCurrentBranch(cwd: string): Promise<string>;
3
+ export declare function gitBranchExists(cwd: string, branch: string): Promise<boolean>;
4
+ export declare function gitListBranches(cwd: string): Promise<string[]>;
5
+ export declare function gitStagedPaths(cwd: string): Promise<string[]>;
6
+ export declare function gitAddPaths(cwd: string, paths: string[]): Promise<void>;
7
+ export declare function gitCommit(cwd: string, message: string, opts?: {
8
+ env?: NodeJS.ProcessEnv;
9
+ skipHooks?: boolean;
10
+ }): Promise<void>;
11
+ export declare function gitInitRepo(cwd: string, branch: string): Promise<void>;
12
+ export declare function resolveInitBaseBranch(gitRoot: string, fallback: string): Promise<string>;
13
+ export declare function promptInitBaseBranch(opts: {
14
+ gitRoot: string;
15
+ fallback: string;
16
+ }): Promise<string>;
17
+ export declare function ensureInitCommit(opts: {
18
+ gitRoot: string;
19
+ baseBranch: string;
20
+ installPaths: string[];
21
+ version: string;
22
+ skipHooks: boolean;
23
+ }): Promise<void>;
24
+ //# sourceMappingURL=git-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git-ops.ts"],"names":[],"mappings":"AAKA,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9E;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBnE;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnF;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASpE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CASnE;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAe9F;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBhB"}
@@ -0,0 +1,181 @@
1
+ import { execFileAsync, gitEnv } from "./git.js";
2
+ import { setPinnedBaseBranch } from "@agentplaneorg/core";
3
+ import { promptChoice, promptInput } from "../../cli/prompts.js";
4
+ import { CliError } from "../../shared/errors.js";
5
+ export async function gitRevParse(cwd, args) {
6
+ const { stdout } = await execFileAsync("git", ["rev-parse", ...args], { cwd, env: gitEnv() });
7
+ const trimmed = stdout.trim();
8
+ if (!trimmed)
9
+ throw new Error("Failed to resolve git path");
10
+ return trimmed;
11
+ }
12
+ export async function gitCurrentBranch(cwd) {
13
+ try {
14
+ const { stdout } = await execFileAsync("git", ["symbolic-ref", "--short", "HEAD"], {
15
+ cwd,
16
+ env: gitEnv(),
17
+ });
18
+ const trimmed = stdout.trim();
19
+ if (trimmed)
20
+ return trimmed;
21
+ }
22
+ catch {
23
+ // fall through
24
+ }
25
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
26
+ cwd,
27
+ env: gitEnv(),
28
+ });
29
+ const trimmed = stdout.trim();
30
+ if (!trimmed || trimmed === "HEAD")
31
+ throw new Error("Failed to resolve git branch");
32
+ return trimmed;
33
+ }
34
+ export async function gitBranchExists(cwd, branch) {
35
+ try {
36
+ await execFileAsync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
37
+ cwd,
38
+ env: gitEnv(),
39
+ });
40
+ return true;
41
+ }
42
+ catch (err) {
43
+ const code = err?.code;
44
+ if (code === 1)
45
+ return false;
46
+ throw err;
47
+ }
48
+ }
49
+ export async function gitListBranches(cwd) {
50
+ const { stdout } = await execFileAsync("git", ["branch", "--format=%(refname:short)"], {
51
+ cwd,
52
+ env: gitEnv(),
53
+ });
54
+ return stdout
55
+ .split("\n")
56
+ .map((line) => line.trim())
57
+ .filter((line) => line.length > 0);
58
+ }
59
+ export async function gitStagedPaths(cwd) {
60
+ const { stdout } = await execFileAsync("git", ["diff", "--cached", "--name-only"], {
61
+ cwd,
62
+ env: gitEnv(),
63
+ });
64
+ return stdout
65
+ .split("\n")
66
+ .map((line) => line.trim())
67
+ .filter((line) => line.length > 0);
68
+ }
69
+ export async function gitAddPaths(cwd, paths) {
70
+ if (paths.length === 0)
71
+ return;
72
+ await execFileAsync("git", ["add", "--", ...paths], { cwd, env: gitEnv() });
73
+ }
74
+ export async function gitCommit(cwd, message, opts) {
75
+ const args = ["commit", "-m", message];
76
+ if (opts?.skipHooks)
77
+ args.push("--no-verify");
78
+ const env = opts?.env ? { ...gitEnv(), ...opts.env } : gitEnv();
79
+ await execFileAsync("git", args, { cwd, env });
80
+ }
81
+ export async function gitInitRepo(cwd, branch) {
82
+ try {
83
+ await execFileAsync("git", ["init", "-q", "-b", branch], { cwd, env: gitEnv() });
84
+ return;
85
+ }
86
+ catch {
87
+ await execFileAsync("git", ["init", "-q"], { cwd, env: gitEnv() });
88
+ }
89
+ try {
90
+ const current = await gitCurrentBranch(cwd);
91
+ if (current !== branch) {
92
+ await execFileAsync("git", ["checkout", "-q", "-b", branch], { cwd, env: gitEnv() });
93
+ }
94
+ }
95
+ catch {
96
+ await execFileAsync("git", ["checkout", "-q", "-b", branch], { cwd, env: gitEnv() });
97
+ }
98
+ }
99
+ export async function resolveInitBaseBranch(gitRoot, fallback) {
100
+ let current = null;
101
+ try {
102
+ current = await gitCurrentBranch(gitRoot);
103
+ }
104
+ catch {
105
+ current = null;
106
+ }
107
+ const branches = await gitListBranches(gitRoot);
108
+ if (current)
109
+ return current;
110
+ if (branches.includes(fallback))
111
+ return fallback;
112
+ if (branches.length > 0) {
113
+ const first = branches[0];
114
+ if (first)
115
+ return first;
116
+ }
117
+ return fallback;
118
+ }
119
+ export async function promptInitBaseBranch(opts) {
120
+ const branches = await gitListBranches(opts.gitRoot);
121
+ let current = null;
122
+ try {
123
+ current = await gitCurrentBranch(opts.gitRoot);
124
+ }
125
+ catch {
126
+ current = null;
127
+ }
128
+ const promptNewBranch = async (hasBranches) => {
129
+ const raw = await promptInput(`Enter new base branch name (default ${opts.fallback}): `);
130
+ const candidate = raw.trim() || opts.fallback;
131
+ if (!candidate) {
132
+ throw new CliError({
133
+ exitCode: 2,
134
+ code: "E_USAGE",
135
+ message: "Base branch name cannot be empty",
136
+ });
137
+ }
138
+ if (await gitBranchExists(opts.gitRoot, candidate))
139
+ return candidate;
140
+ try {
141
+ await execFileAsync("git", hasBranches ? ["branch", candidate] : ["checkout", "-q", "-b", candidate], { cwd: opts.gitRoot, env: gitEnv() });
142
+ }
143
+ catch (err) {
144
+ const message = err instanceof Error ? err.message : `Failed to create branch ${candidate}`;
145
+ throw new CliError({ exitCode: 5, code: "E_GIT", message });
146
+ }
147
+ return candidate;
148
+ };
149
+ if (branches.length === 0) {
150
+ return await promptNewBranch(false);
151
+ }
152
+ const createLabel = "Create new branch";
153
+ const defaultChoice = current && branches.includes(current) ? current : (branches[0] ?? opts.fallback);
154
+ const choice = await promptChoice("Select base branch", [...branches, createLabel], defaultChoice);
155
+ if (choice === createLabel) {
156
+ return await promptNewBranch(true);
157
+ }
158
+ return choice;
159
+ }
160
+ export async function ensureInitCommit(opts) {
161
+ const stagedBefore = await gitStagedPaths(opts.gitRoot);
162
+ if (stagedBefore.length > 0) {
163
+ throw new CliError({
164
+ exitCode: 5,
165
+ code: "E_GIT",
166
+ message: "Git index has staged changes; commit or unstage them before running agentplane init.",
167
+ });
168
+ }
169
+ await setPinnedBaseBranch({
170
+ cwd: opts.gitRoot,
171
+ rootOverride: opts.gitRoot,
172
+ value: opts.baseBranch,
173
+ });
174
+ const dedupedPaths = [...new Set(opts.installPaths)].filter((entry) => entry.length > 0);
175
+ await gitAddPaths(opts.gitRoot, dedupedPaths);
176
+ const staged = await gitStagedPaths(opts.gitRoot);
177
+ if (staged.length === 0)
178
+ return;
179
+ const message = `chore: install agentplane ${opts.version}`;
180
+ await gitCommit(opts.gitRoot, message, { skipHooks: opts.skipHooks });
181
+ }
@@ -0,0 +1,8 @@
1
+ export declare function listWorktrees(cwd: string): Promise<{
2
+ path: string;
3
+ branch: string | null;
4
+ }[]>;
5
+ export declare function findWorktreeForBranch(cwd: string, branch: string): Promise<string | null>;
6
+ export declare function parseTaskIdFromBranch(prefix: string, branch: string): string | null;
7
+ export declare function gitListTaskBranches(cwd: string, prefix: string): Promise<string[]>;
8
+ //# sourceMappingURL=git-worktree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-worktree.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git-worktree.ts"],"names":[],"mappings":"AAEA,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,CAAC,CAoBpD;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ/F;AAMD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnF;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUxF"}
@@ -0,0 +1,48 @@
1
+ import { execFileAsync, gitEnv } from "./git.js";
2
+ export async function listWorktrees(cwd) {
3
+ const { stdout } = await execFileAsync("git", ["worktree", "list", "--porcelain"], {
4
+ cwd,
5
+ env: gitEnv(),
6
+ });
7
+ const worktrees = [];
8
+ const lines = stdout.split("\n");
9
+ let current = null;
10
+ for (const line of lines) {
11
+ if (line.startsWith("worktree ")) {
12
+ if (current)
13
+ worktrees.push(current);
14
+ current = { path: line.slice("worktree ".length).trim(), branch: null };
15
+ continue;
16
+ }
17
+ if (line.startsWith("branch ") && current) {
18
+ current.branch = line.slice("branch ".length).trim();
19
+ }
20
+ }
21
+ if (current)
22
+ worktrees.push(current);
23
+ return worktrees;
24
+ }
25
+ export async function findWorktreeForBranch(cwd, branch) {
26
+ const target = branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`;
27
+ const worktrees = await listWorktrees(cwd);
28
+ const match = worktrees.find((entry) => entry.branch === branch || entry.branch === target || entry.branch === `refs/heads/${branch}`);
29
+ return match ? match.path : null;
30
+ }
31
+ function stripBranchRef(branch) {
32
+ return branch.startsWith("refs/heads/") ? branch.slice("refs/heads/".length) : branch;
33
+ }
34
+ export function parseTaskIdFromBranch(prefix, branch) {
35
+ const normalized = stripBranchRef(branch);
36
+ if (!normalized.startsWith(`${prefix}/`))
37
+ return null;
38
+ const rest = normalized.slice(prefix.length + 1);
39
+ const taskId = rest.split("/", 1)[0];
40
+ return taskId ? taskId.trim() : null;
41
+ }
42
+ export async function gitListTaskBranches(cwd, prefix) {
43
+ const { stdout } = await execFileAsync("git", ["for-each-ref", "--format=%(refname:short)", `refs/heads/${prefix}`], { cwd, env: gitEnv() });
44
+ return stdout
45
+ .split("\n")
46
+ .map((line) => line.trim())
47
+ .filter((line) => line.length > 0);
48
+ }
@@ -0,0 +1,4 @@
1
+ import { execFile } from "node:child_process";
2
+ export declare const execFileAsync: typeof execFile.__promisify__;
3
+ export declare function gitEnv(): NodeJS.ProcessEnv;
4
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C,eAAO,MAAM,aAAa,+BAAsB,CAAC;AAGjD,wBAAgB,MAAM,IAAI,MAAM,CAAC,UAAU,CAS1C"}
@@ -0,0 +1,14 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ export const execFileAsync = promisify(execFile);
4
+ // Avoid leaking worktree-index overrides into git subprocesses.
5
+ export function gitEnv() {
6
+ const env = { ...process.env };
7
+ delete env.GIT_DIR;
8
+ delete env.GIT_WORK_TREE;
9
+ delete env.GIT_COMMON_DIR;
10
+ delete env.GIT_INDEX_FILE;
11
+ delete env.GIT_OBJECT_DIRECTORY;
12
+ delete env.GIT_ALTERNATE_OBJECT_DIRECTORIES;
13
+ return env;
14
+ }
@@ -0,0 +1,8 @@
1
+ import type { AgentplaneConfig } from "@agentplaneorg/core";
2
+ export declare function ensureNetworkApproved(opts: {
3
+ config: AgentplaneConfig;
4
+ yes: boolean;
5
+ reason: string;
6
+ interactive?: boolean;
7
+ }): Promise<void>;
8
+ //# sourceMappingURL=network-approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-approval.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/network-approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAK5D,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhB"}
@@ -0,0 +1,25 @@
1
+ import { promptYesNo } from "../../cli/prompts.js";
2
+ import { CliError } from "../../shared/errors.js";
3
+ export async function ensureNetworkApproved(opts) {
4
+ const requireNetwork = opts.config.agents?.approvals.require_network === true;
5
+ if (!requireNetwork)
6
+ return;
7
+ if (opts.yes)
8
+ return;
9
+ const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
10
+ if (!interactive) {
11
+ throw new CliError({
12
+ exitCode: 3,
13
+ code: "E_VALIDATION",
14
+ message: `Network access requires explicit approval (pass --yes): ${opts.reason}`,
15
+ });
16
+ }
17
+ const approved = await promptYesNo(`Allow network access? ${opts.reason}`, false);
18
+ if (!approved) {
19
+ throw new CliError({
20
+ exitCode: 3,
21
+ code: "E_VALIDATION",
22
+ message: `Network access denied: ${opts.reason}`,
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ export declare function resolvePathFallback(filePath: string): Promise<string>;
2
+ export declare function isPathWithin(parent: string, candidate: string): boolean;
3
+ //# sourceMappingURL=path.d.ts.map