toolcraft 0.0.23 → 0.0.25

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 (152) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +50 -13
  5. package/dist/error-report.js +32 -3
  6. package/dist/human-in-loop/approval-tasks.d.ts +1 -0
  7. package/dist/human-in-loop/approval-tasks.js +7 -5
  8. package/dist/human-in-loop/approvals-commands.js +51 -8
  9. package/dist/human-in-loop/runner.js +24 -19
  10. package/dist/human-in-loop/state-machine.d.ts +3 -3
  11. package/dist/human-in-loop/state-machine.js +13 -5
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +6 -1
  14. package/dist/mcp-proxy.js +85 -19
  15. package/dist/mcp.compile-check.js +1 -0
  16. package/dist/mcp.d.ts +1 -0
  17. package/dist/mcp.js +50 -8
  18. package/dist/renderer.js +119 -13
  19. package/dist/sdk.compile-check.js +1 -0
  20. package/dist/sdk.d.ts +1 -0
  21. package/dist/sdk.js +56 -11
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
  24. package/node_modules/@poe-code/agent-defs/package.json +1 -1
  25. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
  26. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
  27. package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
  28. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
  29. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
  30. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
  31. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
  32. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
  33. package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
  34. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
  35. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
  38. package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
  42. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
  43. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
  44. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
  45. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
  46. package/node_modules/@poe-code/config-mutations/package.json +1 -1
  47. package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
  48. package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
  49. package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
  50. package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
  51. package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
  52. package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
  53. package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
  54. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
  55. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
  56. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  57. package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
  58. package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
  59. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
  60. package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
  61. package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
  62. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
  63. package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
  64. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
  65. package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
  66. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
  67. package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
  68. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
  73. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
  75. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
  76. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  77. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
  78. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
  79. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
  80. package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
  81. package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
  82. package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
  83. package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
  84. package/node_modules/@poe-code/design-system/package.json +2 -1
  85. package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +1 -0
  86. package/node_modules/@poe-code/process-runner/dist/docker/args.js +11 -3
  87. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +377 -130
  88. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +78 -10
  89. package/node_modules/@poe-code/process-runner/dist/docker/env-file.d.ts +6 -0
  90. package/node_modules/@poe-code/process-runner/dist/docker/env-file.js +49 -0
  91. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
  92. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +21 -5
  93. package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
  94. package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
  95. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
  96. package/node_modules/@poe-code/process-runner/dist/types.d.ts +6 -0
  97. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +61 -0
  98. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +503 -0
  99. package/node_modules/@poe-code/process-runner/package.json +1 -1
  100. package/node_modules/@poe-code/task-list/README.md +0 -2
  101. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
  102. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
  103. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
  104. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
  105. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
  106. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
  107. package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
  108. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
  109. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  110. package/node_modules/@poe-code/task-list/dist/index.js +2 -0
  111. package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
  112. package/node_modules/@poe-code/task-list/dist/move.js +215 -0
  113. package/node_modules/@poe-code/task-list/dist/open.js +3 -4
  114. package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
  115. package/node_modules/@poe-code/task-list/dist/state.js +9 -0
  116. package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
  117. package/node_modules/@poe-code/task-list/package.json +1 -2
  118. package/node_modules/auth-store/dist/create-secret-store.js +4 -1
  119. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +8 -0
  120. package/node_modules/auth-store/dist/encrypted-file-store.js +104 -8
  121. package/node_modules/auth-store/dist/index.d.ts +1 -1
  122. package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
  123. package/node_modules/auth-store/dist/keychain-store.js +18 -16
  124. package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
  125. package/node_modules/auth-store/dist/provider-store.js +55 -7
  126. package/node_modules/auth-store/dist/types.d.ts +3 -1
  127. package/node_modules/auth-store/package.json +2 -1
  128. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
  129. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
  130. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
  131. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
  132. package/node_modules/mcp-oauth/package.json +1 -0
  133. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
  134. package/node_modules/tiny-mcp-client/dist/internal.d.ts +9 -4
  135. package/node_modules/tiny-mcp-client/dist/internal.js +244 -66
  136. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
  137. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
  138. package/node_modules/tiny-mcp-client/package.json +2 -1
  139. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
  140. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
  141. package/node_modules/tiny-mcp-client/src/internal.ts +287 -76
  142. package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +32 -0
  143. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
  144. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
  145. package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
  146. package/package.json +10 -12
  147. package/node_modules/@poe-code/file-lock/README.md +0 -52
  148. package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
  149. package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
  150. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
  151. package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
  152. package/node_modules/@poe-code/file-lock/package.json +0 -23
package/README.md CHANGED
@@ -211,8 +211,8 @@ defineCommand({
211
211
  apiVersion: ">=1.2.0", // fails if runner reports older apiVersion
212
212
  check: async (ctx) => ({
213
213
  // arbitrary async gate
214
- ok: ctx.fs.exists(".lock") === false,
215
- message: ".lock present, refusing to run"
214
+ ok: ctx.fs.exists("READY") === true,
215
+ message: "READY marker missing, refusing to run"
216
216
  })
217
217
  }
218
218
  });
@@ -13,6 +13,7 @@ const ignoredRoot = defineGroup({
13
13
  children: [ignoredCommand],
14
14
  });
15
15
  const ignoredOptions = {
16
+ approvals: false,
16
17
  casing: "kebab",
17
18
  humanInLoop: {},
18
19
  version: "1.0.0",
package/dist/cli.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
4
4
  type Casing = "kebab" | "snake";
5
5
  export interface RunCLIOptions<TServices extends object = Record<string, unknown>> {
6
6
  apiVersion?: string;
7
+ approvals?: boolean;
7
8
  casing?: Casing;
8
9
  humanInLoop?: HumanInLoopRuntimeOptions;
9
10
  projectRoot?: string;
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { access, readFile, writeFile } from "node:fs/promises";
1
+ import { access, lstat, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
4
4
  import { cancel, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "@poe-code/design-system";
@@ -608,10 +608,27 @@ function createOption(field, globalLongOptionFlags) {
608
608
  return [option];
609
609
  }
610
610
  const ALWAYS_GLOBAL_LONG_OPTION_FLAGS = ["--yes", "--output", "--debug", "--verbose"];
611
- function getGlobalLongOptionFlags(presetsEnabled) {
612
- return new Set(presetsEnabled
611
+ function getGlobalLongOptionFlags(presetsEnabled, versionEnabled = false) {
612
+ const flags = presetsEnabled
613
613
  ? ["--preset", ...ALWAYS_GLOBAL_LONG_OPTION_FLAGS]
614
- : ALWAYS_GLOBAL_LONG_OPTION_FLAGS);
614
+ : [...ALWAYS_GLOBAL_LONG_OPTION_FLAGS];
615
+ if (versionEnabled) {
616
+ flags.push("--version");
617
+ }
618
+ return new Set(flags);
619
+ }
620
+ function validateUniqueOptionFlags(fields) {
621
+ const fieldsByFlag = new Map();
622
+ for (const field of fields) {
623
+ if (field.positionalIndex !== undefined) {
624
+ continue;
625
+ }
626
+ const existing = fieldsByFlag.get(field.optionFlag);
627
+ if (existing !== undefined) {
628
+ throw new UserError(`Parameters "${existing.displayPath}" and "${field.displayPath}" use conflicting CLI flag "${field.optionFlag}".`);
629
+ }
630
+ fieldsByFlag.set(field.optionFlag, field);
631
+ }
615
632
  }
616
633
  function createCommanderOption(flags, description, field) {
617
634
  const option = new Option(flags, description);
@@ -1011,7 +1028,7 @@ function buildUsageLine(breadcrumb, rootUsageName, suffix) {
1011
1028
  }
1012
1029
  function renderGroupHelp(group, breadcrumb, scope, casing, globalOptions, rootUsageName, isRoot) {
1013
1030
  const sections = [];
1014
- const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled);
1031
+ const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion);
1015
1032
  const commandRows = formatCommandRows(group, scope, casing, globalLongOptionFlags);
1016
1033
  if (commandRows.length > 0) {
1017
1034
  sections.push(`${text.sectionHeader("Commands")}\n${formatHelpCommandList(commandRows)}`);
@@ -1037,7 +1054,7 @@ function renderGroupHelp(group, breadcrumb, scope, casing, globalOptions, rootUs
1037
1054
  }
1038
1055
  function renderLeafHelp(command, breadcrumb, casing, globalOptions, rootUsageName) {
1039
1056
  const sections = [];
1040
- const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled);
1057
+ const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion);
1041
1058
  const collected = collectFields(command.params, casing, globalLongOptionFlags);
1042
1059
  const fields = assignPositionals(collected.fields, command.positional);
1043
1060
  const optionRows = fields
@@ -1115,6 +1132,7 @@ function createNodeCommand(node, casing, globalLongOptionFlags, execute, presets
1115
1132
  const command = new CommanderCommand(node.name);
1116
1133
  const collected = collectFields(node.params, casing, globalLongOptionFlags);
1117
1134
  const fields = assignPositionals(collected.fields, node.positional);
1135
+ validateUniqueOptionFlags(fields);
1118
1136
  if (node.description !== undefined) {
1119
1137
  command.description(node.description);
1120
1138
  }
@@ -1213,18 +1231,30 @@ function setNestedValue(target, path, value) {
1213
1231
  let cursor = target;
1214
1232
  for (let index = 0; index < path.length - 1; index += 1) {
1215
1233
  const segment = path[index] ?? "";
1216
- const existing = cursor[segment];
1234
+ const existing = Object.prototype.hasOwnProperty.call(cursor, segment)
1235
+ ? cursor[segment]
1236
+ : undefined;
1217
1237
  if (typeof existing === "object" && existing !== null) {
1218
1238
  cursor = existing;
1219
1239
  continue;
1220
1240
  }
1221
1241
  const next = {};
1222
- cursor[segment] = next;
1242
+ Object.defineProperty(cursor, segment, {
1243
+ value: next,
1244
+ enumerable: true,
1245
+ configurable: true,
1246
+ writable: true
1247
+ });
1223
1248
  cursor = next;
1224
1249
  }
1225
1250
  const leaf = path[path.length - 1];
1226
1251
  if (leaf !== undefined) {
1227
- cursor[leaf] = value;
1252
+ Object.defineProperty(cursor, leaf, {
1253
+ value,
1254
+ enumerable: true,
1255
+ configurable: true,
1256
+ writable: true
1257
+ });
1228
1258
  }
1229
1259
  }
1230
1260
  function formatResolvedValue(value) {
@@ -1345,7 +1375,10 @@ function createFs() {
1345
1375
  catch {
1346
1376
  return false;
1347
1377
  }
1348
- }
1378
+ },
1379
+ lstat: async (path) => lstat(path),
1380
+ rename: async (fromPath, toPath) => rename(fromPath, toPath),
1381
+ unlink: async (path) => unlink(path)
1349
1382
  };
1350
1383
  }
1351
1384
  function createEnv(values = process.env) {
@@ -1605,7 +1638,10 @@ function createFixtureFs(definition) {
1605
1638
  return Boolean(existsEntries[filePath]);
1606
1639
  }
1607
1640
  return Object.prototype.hasOwnProperty.call(readFileEntries, filePath);
1608
- }
1641
+ },
1642
+ lstat: async () => ({ isSymbolicLink: () => false }),
1643
+ rename: async () => undefined,
1644
+ unlink: async () => undefined
1609
1645
  };
1610
1646
  }
1611
1647
  function resolveFixtureMethodResult(methodName, definition, args) {
@@ -2843,7 +2879,8 @@ function configureCommanderSuggestionOutput(command) {
2843
2879
  }
2844
2880
  export async function runCLI(roots, options = {}) {
2845
2881
  enableSourceMaps();
2846
- const root = mergeApprovalsGroup(normalizeRoots(roots, process.argv));
2882
+ const normalizedRoot = normalizeRoots(roots, process.argv);
2883
+ const root = options.approvals === false ? normalizedRoot : mergeApprovalsGroup(normalizedRoot);
2847
2884
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
2848
2885
  const casing = options.casing ?? "kebab";
2849
2886
  const services = (options.services ?? {});
@@ -2869,7 +2906,7 @@ export async function runCLI(roots, options = {}) {
2869
2906
  program.showHelpAfterError();
2870
2907
  program.addHelpCommand(false);
2871
2908
  const presetsEnabled = options.presets === true;
2872
- const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled);
2909
+ const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled, version !== undefined);
2873
2910
  addGlobalOptions(program, presetsEnabled);
2874
2911
  if (version !== undefined) {
2875
2912
  program.version(version, "--version");
@@ -1,4 +1,5 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { mkdir, realpath, writeFile } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { CommanderError } from "commander";
@@ -46,6 +47,26 @@ function resolveReportDir(option, projectRoot) {
46
47
  }
47
48
  return path.isAbsolute(configuredDir) ? configuredDir : path.join(projectRoot, configuredDir);
48
49
  }
50
+ function reportDirMustStayWithinProject(option) {
51
+ const configuredDir = typeof option === "object" ? option.dir : undefined;
52
+ return configuredDir === undefined || configuredDir.length === 0 || !path.isAbsolute(configuredDir);
53
+ }
54
+ function isWithinDirectory(parent, child) {
55
+ const relative = path.relative(parent, child);
56
+ return relative === "" || (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`));
57
+ }
58
+ async function assertReportDirWithinProject(projectRoot, reportDir) {
59
+ if (!isWithinDirectory(path.resolve(projectRoot), path.resolve(reportDir))) {
60
+ throw new Error("Error report directory resolves outside project root.");
61
+ }
62
+ const [canonicalProjectRoot, canonicalReportDir] = await Promise.all([
63
+ realpath(projectRoot),
64
+ realpath(reportDir)
65
+ ]);
66
+ if (!isWithinDirectory(canonicalProjectRoot, canonicalReportDir)) {
67
+ throw new Error("Error report directory resolves outside project root.");
68
+ }
69
+ }
49
70
  function resolveProjectRoot(projectRoot) {
50
71
  if (projectRoot !== undefined) {
51
72
  return projectRoot;
@@ -204,7 +225,12 @@ function ownStructuredFields(error) {
204
225
  if (key === "name" || key === "message" || key === "stack" || key === "cause") {
205
226
  continue;
206
227
  }
207
- fields[key] = redactStructuredErrorField(key, error[key]);
228
+ Object.defineProperty(fields, key, {
229
+ value: redactStructuredErrorField(key, error[key]),
230
+ enumerable: true,
231
+ configurable: true,
232
+ writable: true
233
+ });
208
234
  }
209
235
  return fields;
210
236
  }
@@ -318,9 +344,12 @@ export async function writeErrorReport(context) {
318
344
  }
319
345
  const projectRoot = resolveProjectRoot(context.projectRoot);
320
346
  const reportDir = resolveReportDir(context.errorReports, projectRoot);
321
- const fileName = `${formatTimestamp(new Date())}-${slugifyCommandPath(context.commandPath)}.log`;
347
+ const fileName = `${formatTimestamp(new Date())}-${slugifyCommandPath(context.commandPath)}-${randomUUID()}.log`;
322
348
  const absolutePath = path.join(reportDir, fileName);
323
349
  await mkdir(reportDir, { recursive: true });
350
+ if (reportDirMustStayWithinProject(context.errorReports)) {
351
+ await assertReportDirWithinProject(projectRoot, reportDir);
352
+ }
324
353
  await writeFile(absolutePath, buildReport(context));
325
354
  return {
326
355
  absolutePath,
@@ -12,6 +12,7 @@ export interface ApprovalPayload {
12
12
  error?: unknown;
13
13
  }
14
14
  export declare function ensureApprovalList(runtimeOptions: HumanInLoopRuntimeOptions | undefined, deps?: {
15
+ create?: boolean;
15
16
  openTaskList?: (options: OpenTaskListOptions) => Promise<TaskList>;
16
17
  }): Promise<{
17
18
  taskList: TaskList;
@@ -10,7 +10,7 @@ export async function ensureApprovalList(runtimeOptions, deps = {}) {
10
10
  throw new UserError("humanInLoop.taskList required for async-mode commands");
11
11
  }
12
12
  const listName = runtimeOptions.listName ?? DEFAULT_LIST_NAME;
13
- const taskList = await resolveTaskList(runtimeOptions, runtimeOptions.taskList, deps.openTaskList ?? openTaskList);
13
+ const taskList = await resolveTaskList(runtimeOptions, runtimeOptions.taskList, deps.openTaskList ?? openTaskList, deps.create ?? true);
14
14
  const tasks = taskList.list(listName);
15
15
  if (!isListValidated(runtimeOptions, listName)) {
16
16
  if (!isApprovalStateMachine(tasks.stateMachine)) {
@@ -52,21 +52,23 @@ export async function loadApproval(ctx) {
52
52
  throw error;
53
53
  }
54
54
  }
55
- async function resolveTaskList(runtimeOptions, taskList, openTaskListFn) {
55
+ async function resolveTaskList(runtimeOptions, taskList, openTaskListFn, create) {
56
56
  if (!isTaskListConfig(taskList)) {
57
57
  return taskList;
58
58
  }
59
- const cachedTaskList = openedTaskListsByRuntime.get(runtimeOptions);
59
+ const cachedTaskList = create ? openedTaskListsByRuntime.get(runtimeOptions) : undefined;
60
60
  if (cachedTaskList !== undefined) {
61
61
  return cachedTaskList;
62
62
  }
63
63
  const openedTaskList = openTaskListFn({
64
- create: true,
64
+ create,
65
65
  type: taskList.format,
66
66
  path: taskList.dir,
67
67
  stateMachine: approvalStateMachine
68
68
  });
69
- openedTaskListsByRuntime.set(runtimeOptions, openedTaskList);
69
+ if (create) {
70
+ openedTaskListsByRuntime.set(runtimeOptions, openedTaskList);
71
+ }
70
72
  return openedTaskList;
71
73
  }
72
74
  function cacheValidatedList(runtimeOptions, listName) {
@@ -1,3 +1,4 @@
1
+ import { TaskNotFoundError } from "@poe-code/task-list";
1
2
  import { S } from "toolcraft-schema";
2
3
  import { UserError, defineCommand, defineGroup } from "../index.js";
3
4
  import { ensureApprovalList } from "./approval-tasks.js";
@@ -11,6 +12,13 @@ const listParams = S.Object({
11
12
  const showParams = S.Object({
12
13
  approvalId: S.String()
13
14
  });
15
+ const runParams = S.Object({
16
+ approvalId: S.String(),
17
+ dryRun: S.Optional(S.Boolean({
18
+ description: "Preview the approval without prompting or executing it",
19
+ scope: ["cli"]
20
+ }))
21
+ });
14
22
  export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
15
23
  name: "approvals",
16
24
  description: "Inspect and execute queued approvals.",
@@ -21,8 +29,16 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
21
29
  scope: listScope,
22
30
  params: listParams,
23
31
  handler: async ({ params, runtimeOptions }) => {
24
- const { tasks } = await ensureApprovalList(runtimeOptions);
25
- return loadApprovals(tasks, params.state);
32
+ try {
33
+ const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
34
+ return loadApprovals(tasks, params.state);
35
+ }
36
+ catch (error) {
37
+ if (isMissingStateError(error)) {
38
+ return [];
39
+ }
40
+ throw error;
41
+ }
26
42
  },
27
43
  render: {
28
44
  rich: (result, primitives) => renderApprovalList(result, primitives),
@@ -36,8 +52,16 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
36
52
  scope: listScope,
37
53
  params: showParams,
38
54
  handler: async ({ params, runtimeOptions }) => {
39
- const { tasks } = await ensureApprovalList(runtimeOptions);
40
- return tasks.get(params.approvalId);
55
+ try {
56
+ const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
57
+ return tasks.get(params.approvalId);
58
+ }
59
+ catch (error) {
60
+ if (isMissingStateError(error)) {
61
+ throw new TaskNotFoundError(`Task "approvals/${params.approvalId}" not found.`);
62
+ }
63
+ throw error;
64
+ }
41
65
  },
42
66
  render: {
43
67
  rich: (result, primitives) => renderApprovalDetails(result, primitives),
@@ -49,8 +73,22 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
49
73
  name: "run",
50
74
  description: "Run one queued approval.",
51
75
  scope: runScope,
52
- params: showParams,
53
- handler: async ({ params, runtimeOptions, root }) => runApproval(params.approvalId, runtimeOptions, root)
76
+ params: runParams,
77
+ handler: async ({ params, runtimeOptions, root }) => {
78
+ if (params.dryRun === true) {
79
+ const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
80
+ return tasks.get(params.approvalId);
81
+ }
82
+ return runApproval(params.approvalId, runtimeOptions, root);
83
+ },
84
+ render: {
85
+ rich: (result, primitives) => {
86
+ if (result)
87
+ renderApprovalDetails(result, primitives);
88
+ },
89
+ markdown: (result) => result ? renderApprovalDetailsMarkdown(result) : "",
90
+ json: (result) => result
91
+ }
54
92
  })
55
93
  ]
56
94
  }));
@@ -62,8 +100,10 @@ export function mergeApprovalsGroup(root) {
62
100
  }
63
101
  throw new UserError("'approvals' is reserved for human-in-loop built-ins");
64
102
  }
65
- root.children = [...root.children, approvalsGroup];
66
- return root;
103
+ return {
104
+ ...root,
105
+ children: [...root.children, approvalsGroup]
106
+ };
67
107
  }
68
108
  function markApprovalsBuiltIn(group) {
69
109
  Object.defineProperty(group, approvalsGroupSymbol, {
@@ -190,3 +230,6 @@ function stringifyValue(value) {
190
230
  function escapeMarkdownCell(value) {
191
231
  return value.replaceAll("|", "\\|");
192
232
  }
233
+ function isMissingStateError(error) {
234
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
235
+ }
@@ -1,4 +1,5 @@
1
- import { access, readFile, writeFile } from "node:fs/promises";
1
+ import { access, lstat, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { InvalidTransitionError } from "@poe-code/task-list";
2
3
  import { UserError, resolveCommandSecrets } from "../index.js";
3
4
  import { ensureApprovalList } from "./approval-tasks.js";
4
5
  import { resolveProvider } from "./gate.js";
@@ -11,6 +12,19 @@ export async function runApproval(approvalId, runtimeOptions, root) {
11
12
  }
12
13
  const approval = readApprovalPayload(task);
13
14
  const provider = resolveProvider(runtimeOptions);
15
+ try {
16
+ await tasks.fire(approvalId, "claim", {
17
+ metadataPatch: {
18
+ pid: process.pid,
19
+ },
20
+ });
21
+ }
22
+ catch (error) {
23
+ if (error instanceof InvalidTransitionError) {
24
+ return;
25
+ }
26
+ throw error;
27
+ }
14
28
  try {
15
29
  const approvalResult = await provider.requestApproval({
16
30
  message: approval.message,
@@ -28,14 +42,14 @@ export async function runApproval(approvalId, runtimeOptions, root) {
28
42
  }
29
43
  }
30
44
  catch (error) {
31
- await failPendingApproval(tasks, approvalId, error);
45
+ await tasks.fire(approvalId, "fail", {
46
+ metadataPatch: {
47
+ error: errorMetadataFromUnknown(error),
48
+ },
49
+ });
32
50
  return;
33
51
  }
34
- await tasks.fire(approvalId, "start", {
35
- metadataPatch: {
36
- pid: process.pid,
37
- },
38
- });
52
+ await tasks.fire(approvalId, "start");
39
53
  try {
40
54
  const command = findCommand(root, approval.commandPath);
41
55
  const ctx = createHandlerContext(command, approval.params);
@@ -182,6 +196,9 @@ function createFs() {
182
196
  return false;
183
197
  }
184
198
  },
199
+ lstat: async (path) => lstat(path),
200
+ rename: async (fromPath, toPath) => rename(fromPath, toPath),
201
+ unlink: async (path) => unlink(path),
185
202
  };
186
203
  }
187
204
  function createEnv(values = process.env) {
@@ -223,15 +240,3 @@ function errorMetadataFromUnknown(error) {
223
240
  message: String(error),
224
241
  };
225
242
  }
226
- async function failPendingApproval(tasks, approvalId, error) {
227
- await tasks.fire(approvalId, "start", {
228
- metadataPatch: {
229
- pid: process.pid,
230
- },
231
- });
232
- await tasks.fire(approvalId, "fail", {
233
- metadataPatch: {
234
- error: errorMetadataFromUnknown(error),
235
- },
236
- });
237
- }
@@ -1,4 +1,4 @@
1
1
  import type { StateMachineDef } from "@poe-code/task-list";
2
- export type ApprovalState = "pending" | "approved-running" | "approved-done" | "approved-failed" | "declined";
3
- export type ApprovalEvent = "start" | "succeed" | "fail" | "decline";
4
- export declare const approvalStateMachine: StateMachineDef<ApprovalState, ApprovalEvent>;
2
+ export type ApprovalState = "pending" | "prompting" | "approved-running" | "approved-done" | "approved-failed" | "declined";
3
+ export type ApprovalEvent = "claim" | "start" | "succeed" | "fail" | "decline";
4
+ export declare const approvalStateMachine: Readonly<StateMachineDef<ApprovalState, ApprovalEvent>>;
@@ -1,10 +1,18 @@
1
- export const approvalStateMachine = {
1
+ const approvalStateMachineDefinition = {
2
2
  initial: "pending",
3
- states: ["pending", "approved-running", "approved-done", "approved-failed", "declined"],
3
+ states: ["pending", "prompting", "approved-running", "approved-done", "approved-failed", "declined"],
4
4
  events: {
5
- start: { from: ["pending"], to: "approved-running" },
5
+ claim: { from: ["pending"], to: "prompting" },
6
+ start: { from: ["prompting"], to: "approved-running" },
6
7
  succeed: { from: ["approved-running"], to: "approved-done" },
7
- fail: { from: ["approved-running"], to: "approved-failed" },
8
- decline: { from: ["pending"], to: "declined" },
8
+ fail: { from: ["prompting", "approved-running"], to: "approved-failed" },
9
+ decline: { from: ["pending", "prompting"], to: "declined" },
9
10
  },
10
11
  };
12
+ Object.freeze(approvalStateMachineDefinition.states);
13
+ for (const event of Object.values(approvalStateMachineDefinition.events)) {
14
+ Object.freeze(event.from);
15
+ Object.freeze(event);
16
+ }
17
+ Object.freeze(approvalStateMachineDefinition.events);
18
+ export const approvalStateMachine = Object.freeze(approvalStateMachineDefinition);
package/dist/index.d.ts CHANGED
@@ -35,6 +35,11 @@ export interface HandlerFs {
35
35
  readFile(path: string, encoding?: BufferEncoding): Promise<string>;
36
36
  writeFile(path: string, contents: string): Promise<void>;
37
37
  exists(path: string): Promise<boolean>;
38
+ lstat(path: string): Promise<{
39
+ isSymbolicLink(): boolean;
40
+ }>;
41
+ rename(fromPath: string, toPath: string): Promise<void>;
42
+ unlink(path: string): Promise<void>;
38
43
  }
39
44
  export interface HandlerEnv {
40
45
  get(key: string): string | undefined;
package/dist/index.js CHANGED
@@ -211,7 +211,12 @@ export function resolveCommandSecrets(command, env = process.env) {
211
211
  const suggestionLine = suggestions.length > 0 ? `\nDid you mean: ${suggestions.join(", ")}?` : "";
212
212
  throw new UserError(`Missing required secret ${secret.env}${details}${suggestionLine}`);
213
213
  }
214
- secrets[name] = value;
214
+ Object.defineProperty(secrets, name, {
215
+ value,
216
+ enumerable: true,
217
+ configurable: true,
218
+ writable: true
219
+ });
215
220
  }
216
221
  return secrets;
217
222
  }