toolcraft 0.0.67 → 0.0.68

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.
@@ -18,10 +18,15 @@ const ignoredOptions = {
18
18
  fetch: globalThis.fetch,
19
19
  controls: {
20
20
  debug: true,
21
+ logLevel: true,
21
22
  output: true,
22
23
  verbose: true,
23
24
  yes: true,
24
25
  },
26
+ logLevel: "warn",
27
+ logger: (event) => {
28
+ event.level;
29
+ },
25
30
  humanInLoop: {},
26
31
  version: "1.0.0",
27
32
  };
package/dist/cli.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { configureTheme } from "toolcraft-design";
2
- import type { Group } from "./index.js";
2
+ import type { Group, LogLevel, RuntimeLoggerInput } from "./index.js";
3
3
  import { type ErrorReportsOption } from "./error-report.js";
4
4
  import type { HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
5
5
  export { configureTheme };
6
6
  type Casing = "kebab" | "snake";
7
7
  export interface CLIControls {
8
8
  debug?: boolean;
9
+ logLevel?: boolean;
9
10
  output?: boolean;
10
11
  verbose?: boolean;
11
12
  yes?: boolean;
@@ -18,6 +19,8 @@ export interface RunCLIOptions<TServices extends object = Record<string, unknown
18
19
  controls?: CLIControls;
19
20
  fetch?: typeof globalThis.fetch;
20
21
  humanInLoop?: HumanInLoopRuntimeOptions;
22
+ logLevel?: LogLevel;
23
+ logger?: RuntimeLoggerInput;
21
24
  projectRoot?: string;
22
25
  rootDisplayName?: string;
23
26
  rootUsageName?: string;
package/dist/cli.js CHANGED
@@ -4,7 +4,6 @@ import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Opti
4
4
  import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
5
5
  import { ApprovalDeclinedError, UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
6
6
  import { hasOwnErrorCode } from "./error-codes.js";
7
- import { summarizeHttpError } from "./api-error-summary.js";
8
7
  import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
9
8
  import { writeErrorReport } from "./error-report.js";
10
9
  import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
@@ -12,6 +11,8 @@ import { resolveMcpProxies } from "./mcp-proxy.js";
12
11
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
13
12
  import { findEntrypointPackageMetadata } from "./package-metadata.js";
14
13
  import { redactHttpBody, redactHttpHeaderValue } from "./redaction.js";
14
+ import { createRuntimeLogger, isLogLevel, LOG_LEVELS } from "./runtime-logging.js";
15
+ import { summarizeHttpError } from "./api-error-summary.js";
15
16
  import { renderResult } from "./renderer.js";
16
17
  import { renderSourceSnippet } from "./source-snippet.js";
17
18
  import { enableSourceMaps, formatDebugStack } from "./stack-trim.js";
@@ -25,11 +26,12 @@ const RESERVED_SERVICE_NAMES = new Set([
25
26
  "fetch",
26
27
  "fs",
27
28
  "env",
29
+ "diagnostics",
28
30
  "progress",
29
31
  "runtimeOptions",
30
32
  "root"
31
33
  ]);
32
- const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
34
+ const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, diagnostics, progress, runtimeOptions, root.";
33
35
  const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
34
36
  function inferProgramName(argv) {
35
37
  const entrypoint = argv[1];
@@ -641,6 +643,7 @@ function createOption(field, globalLongOptionFlags) {
641
643
  function resolveCLIControls(controls) {
642
644
  return {
643
645
  debug: controls?.debug === true,
646
+ logLevel: controls?.logLevel === true,
644
647
  output: controls?.output === true,
645
648
  verbose: controls?.verbose === true,
646
649
  yes: controls?.yes === true
@@ -660,6 +663,9 @@ function getGlobalLongOptionFlags(presetsEnabled, versionEnabled, controls) {
660
663
  if (controls.debug) {
661
664
  flags.push("--debug");
662
665
  }
666
+ if (controls.logLevel) {
667
+ flags.push("--log-level");
668
+ }
663
669
  if (controls.verbose) {
664
670
  flags.push("--verbose");
665
671
  }
@@ -699,6 +705,9 @@ function createCommanderOption(flags, description, field) {
699
705
  function hasHelpFlag(argv) {
700
706
  return argv.some((token) => HELP_FLAGS.has(token));
701
707
  }
708
+ function normalizeVerboseAlias(argv) {
709
+ return argv.map((token) => (token === "-v" ? "--verbose" : token));
710
+ }
702
711
  function resolveHelpOutput(argv) {
703
712
  for (let index = 0; index < argv.length; index += 1) {
704
713
  const token = argv[index] ?? "";
@@ -1095,11 +1104,17 @@ function formatGlobalOptionsLine(ctx) {
1095
1104
  if (ctx.controls.output) {
1096
1105
  flags.push("--output <format>");
1097
1106
  }
1107
+ if (ctx.controls.verbose) {
1108
+ flags.push("-v, --verbose");
1109
+ }
1098
1110
  if (ctx.showVersion) {
1099
1111
  flags.push("--version");
1100
1112
  }
1101
1113
  return flags.length > 0 ? `${text.section("Options:")} ${flags.join(" ")}` : "";
1102
1114
  }
1115
+ function formatLeafGlobalOptionsLine(ctx) {
1116
+ return ctx.controls.verbose ? `${text.section("Options:")} -v, --verbose` : "";
1117
+ }
1103
1118
  function collectSchemaGlobalFieldRows(group, scope, casing, globalLongOptionFlags) {
1104
1119
  const seen = new Map();
1105
1120
  const visit = (node) => {
@@ -1200,6 +1215,10 @@ function renderLeafHelp(command, breadcrumb, casing, globalOptions, rootUsageNam
1200
1215
  if (optionRows.length > 0) {
1201
1216
  sections.push(`${text.sectionHeader("Options")}\n${formatHelpOptionList(optionRows)}`);
1202
1217
  }
1218
+ const builtInLine = formatLeafGlobalOptionsLine(globalOptions);
1219
+ if (builtInLine.length > 0) {
1220
+ sections.push(builtInLine);
1221
+ }
1203
1222
  const secretRows = formatSecretRows(command.secrets);
1204
1223
  if (secretRows.length > 0) {
1205
1224
  sections.push(`${text.sectionHeader("Secrets (environment)")}\n${formatHelpOptionList(secretRows)}`);
@@ -1444,7 +1463,9 @@ function isToolcraftHiddenCommander(command) {
1444
1463
  }
1445
1464
  function getToolcraftHiddenDefaultNames(command) {
1446
1465
  const value = Reflect.get(command, "_toolcraftHiddenDefaultNames");
1447
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1466
+ return Array.isArray(value)
1467
+ ? value.filter((item) => typeof item === "string")
1468
+ : [];
1448
1469
  }
1449
1470
  function getToolcraftReservedChildNames(command) {
1450
1471
  const value = Reflect.get(command, "_toolcraftReservedChildNames");
@@ -1482,6 +1503,9 @@ function addGlobalOptions(command, presetsEnabled, controls) {
1482
1503
  .preset("trim")
1483
1504
  .argParser(parseDebugStackMode));
1484
1505
  }
1506
+ if (controls.logLevel) {
1507
+ options.push(new Option("--log-level <level>", "Set runtime diagnostic log level.").argParser(parseLogLevel));
1508
+ }
1485
1509
  if (controls.verbose) {
1486
1510
  options.push(new Option("--verbose", "Print detailed runtime diagnostics."));
1487
1511
  }
@@ -1499,6 +1523,26 @@ function parseDebugStackMode(value) {
1499
1523
  }
1500
1524
  throw new InvalidArgumentError(formatInvalidEnumMessage("--debug", String(value), ["raw"], { candidates: ["raw"] }));
1501
1525
  }
1526
+ function parseLogLevel(value) {
1527
+ if (isLogLevel(value)) {
1528
+ return value;
1529
+ }
1530
+ throw new InvalidArgumentError(formatInvalidEnumMessage("--log-level", value, [...LOG_LEVELS], {
1531
+ candidates: ["warn", "debug", "trace"],
1532
+ threshold: 3
1533
+ }));
1534
+ }
1535
+ function writeCLIDiagnosticEvent(event) {
1536
+ const transcript = event.data?.transcript;
1537
+ if (typeof transcript === "string") {
1538
+ process.stderr.write(transcript);
1539
+ return;
1540
+ }
1541
+ if (event.category === "progress" || event.level === "trace") {
1542
+ return;
1543
+ }
1544
+ process.stderr.write(`${event.message}\n`);
1545
+ }
1502
1546
  function setNestedValue(target, path, value) {
1503
1547
  let cursor = target;
1504
1548
  for (let index = 0; index < path.length - 1; index += 1) {
@@ -2763,7 +2807,7 @@ function getResolvedFlags(command) {
2763
2807
  const flags = command.optsWithGlobals();
2764
2808
  return flags;
2765
2809
  }
2766
- async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, onErrorReportContext) {
2810
+ async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, diagnosticsOptions, onErrorReportContext) {
2767
2811
  const logger = createLogger();
2768
2812
  const primitives = {
2769
2813
  logger,
@@ -2774,6 +2818,13 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
2774
2818
  const optionValues = state.actionCommand.optsWithGlobals();
2775
2819
  const resolvedFlags = optionValues;
2776
2820
  const output = resolveOutput(resolvedFlags);
2821
+ const diagnostics = createRuntimeLogger({
2822
+ level: resolvedFlags.logLevel ??
2823
+ (diagnosticsOptions.verboseControlEnabled && resolvedFlags.verbose
2824
+ ? "trace"
2825
+ : diagnosticsOptions.logLevel),
2826
+ logger: diagnosticsOptions.logger ?? writeCLIDiagnosticEvent
2827
+ });
2777
2828
  const shouldPrompt = !resolvedFlags.yes && Boolean(process.stdin.isTTY);
2778
2829
  const runtime = await resolveFixtureRuntime(state.command, services, requirementOptions, runtimeFetch);
2779
2830
  const preflightContext = {
@@ -2782,7 +2833,9 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
2782
2833
  fetch: runtime.fetch,
2783
2834
  fs: runtime.fs,
2784
2835
  env: runtime.env,
2836
+ diagnostics,
2785
2837
  progress(message) {
2838
+ diagnostics.emit({ level: "info", message, category: "progress" });
2786
2839
  logger.info(message);
2787
2840
  }
2788
2841
  };
@@ -3311,7 +3364,10 @@ function configureCommanderSuggestionOutput(command) {
3311
3364
  }
3312
3365
  export async function runCLI(roots, options = {}) {
3313
3366
  enableSourceMaps();
3314
- const argv = [...(options.argv ?? process.argv)];
3367
+ const controls = resolveCLIControls(options.controls);
3368
+ const argv = controls.verbose
3369
+ ? normalizeVerboseAlias([...(options.argv ?? process.argv)])
3370
+ : [...(options.argv ?? process.argv)];
3315
3371
  const rootUsageName = options.rootUsageName ?? inferProgramName(argv);
3316
3372
  let lastActionCommand;
3317
3373
  let resolvedCommandPath = "";
@@ -3328,7 +3384,6 @@ export async function runCLI(roots, options = {}) {
3328
3384
  const runtimeOptions = options.humanInLoop ?? {};
3329
3385
  const runtimeFetch = options.fetch ?? globalThis.fetch;
3330
3386
  version = options.version ?? findEntrypointPackageMetadata(argv[1])?.version;
3331
- const controls = resolveCLIControls(options.controls);
3332
3387
  const servicesWithBuiltIns = {
3333
3388
  ...services,
3334
3389
  runtimeOptions,
@@ -3343,6 +3398,11 @@ export async function runCLI(roots, options = {}) {
3343
3398
  await renderGeneratedHelp(root, argv, { ...options, version });
3344
3399
  return;
3345
3400
  }
3401
+ if (argv.length <= 2 && root.default?.scope.includes("cli") !== true) {
3402
+ userErrorPattern = "usage";
3403
+ await renderGeneratedHelp(root, argv, { ...options, version });
3404
+ return;
3405
+ }
3346
3406
  program = new CommanderCommand();
3347
3407
  program.name(root.name);
3348
3408
  program.exitOverride();
@@ -3360,7 +3420,11 @@ export async function runCLI(roots, options = {}) {
3360
3420
  const execute = async (state) => {
3361
3421
  lastActionCommand = state.actionCommand;
3362
3422
  resolvedCommandPath = formatCliCommandPath(state.commandPath);
3363
- await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, (context) => {
3423
+ await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, {
3424
+ logLevel: options.logLevel,
3425
+ logger: options.logger,
3426
+ verboseControlEnabled: controls.verbose
3427
+ }, (context) => {
3364
3428
  errorReportContext = context;
3365
3429
  });
3366
3430
  };
@@ -3415,9 +3479,7 @@ export async function runCLI(roots, options = {}) {
3415
3479
  debugStackMode: resolvedFlags !== undefined
3416
3480
  ? resolveDebugStackMode(resolvedFlags.debug)
3417
3481
  : getDebugStackModeFromArgv(argv),
3418
- output: resolvedFlags !== undefined
3419
- ? resolveOutput(resolvedFlags)
3420
- : resolveOutputFromArgv(argv),
3482
+ output: resolvedFlags !== undefined ? resolveOutput(resolvedFlags) : resolveOutputFromArgv(argv),
3421
3483
  verbose: resolvedFlags ? Boolean(resolvedFlags.verbose) : argv.includes("--verbose"),
3422
3484
  program,
3423
3485
  argv,
@@ -3,6 +3,7 @@ import { InvalidTransitionError } from "@poe-code/task-list";
3
3
  import { UserError, resolveCommandSecrets } from "../index.js";
4
4
  import { ensureApprovalList } from "./approval-tasks.js";
5
5
  import { resolveProvider } from "./gate.js";
6
+ import { createRuntimeLogger } from "../runtime-logging.js";
6
7
  const MAX_AVAILABLE_COMMAND_PATHS = 20;
7
8
  export async function runApproval(approvalId, runtimeOptions, root) {
8
9
  const { tasks } = await ensureApprovalList(runtimeOptions);
@@ -15,8 +16,8 @@ export async function runApproval(approvalId, runtimeOptions, root) {
15
16
  try {
16
17
  await tasks.fire(approvalId, "claim", {
17
18
  metadataPatch: {
18
- pid: process.pid,
19
- },
19
+ pid: process.pid
20
+ }
20
21
  });
21
22
  }
22
23
  catch (error) {
@@ -28,15 +29,15 @@ export async function runApproval(approvalId, runtimeOptions, root) {
28
29
  try {
29
30
  const approvalResult = await provider.requestApproval({
30
31
  message: approval.message,
31
- declineInputPrompt: approval.declineInputPrompt ?? undefined,
32
+ declineInputPrompt: approval.declineInputPrompt ?? undefined
32
33
  });
33
34
  if (approvalResult.outcome === "declined") {
34
35
  await tasks.fire(approvalId, "decline", {
35
36
  metadataPatch: {
36
37
  error: {
37
- reason: approvalResult.reason,
38
- },
39
- },
38
+ reason: approvalResult.reason
39
+ }
40
+ }
40
41
  });
41
42
  return;
42
43
  }
@@ -44,8 +45,8 @@ export async function runApproval(approvalId, runtimeOptions, root) {
44
45
  catch (error) {
45
46
  await tasks.fire(approvalId, "fail", {
46
47
  metadataPatch: {
47
- error: errorMetadataFromUnknown(error),
48
- },
48
+ error: errorMetadataFromUnknown(error)
49
+ }
49
50
  });
50
51
  return;
51
52
  }
@@ -59,23 +60,23 @@ export async function runApproval(approvalId, runtimeOptions, root) {
59
60
  await tasks.fire(approvalId, "fail", {
60
61
  metadataPatch: {
61
62
  error: {
62
- message: "result not JSON-serializable",
63
- },
64
- },
63
+ message: "result not JSON-serializable"
64
+ }
65
+ }
65
66
  });
66
67
  return;
67
68
  }
68
69
  await tasks.fire(approvalId, "succeed", {
69
70
  metadataPatch: {
70
- result: serializedResult.value,
71
- },
71
+ result: serializedResult.value
72
+ }
72
73
  });
73
74
  }
74
75
  catch (error) {
75
76
  await tasks.fire(approvalId, "fail", {
76
77
  metadataPatch: {
77
- error: errorMetadataFromUnknown(error),
78
- },
78
+ error: errorMetadataFromUnknown(error)
79
+ }
79
80
  });
80
81
  }
81
82
  }
@@ -105,7 +106,7 @@ function readApprovalPayload(task) {
105
106
  enqueuedAt: typeof metadata.enqueuedAt === "string" ? metadata.enqueuedAt : undefined,
106
107
  pid: typeof metadata.pid === "number" || metadata.pid === null ? metadata.pid : undefined,
107
108
  result: metadata.result,
108
- error: metadata.error,
109
+ error: metadata.error
109
110
  };
110
111
  }
111
112
  function findCommand(root, commandPath) {
@@ -170,15 +171,17 @@ function getVisibleCliChildren(root) {
170
171
  return root.kind === "group" ? root.children.filter(isNodeVisibleInCli) : [];
171
172
  }
172
173
  function createHandlerContext(command, params) {
174
+ const diagnostics = createRuntimeLogger();
173
175
  return {
174
176
  params,
175
177
  secrets: resolveCommandSecrets(command),
176
178
  fetch: globalThis.fetch,
177
179
  fs: createFs(),
178
180
  env: createEnv(),
179
- progress() {
180
- return undefined;
181
- },
181
+ diagnostics,
182
+ progress(message) {
183
+ diagnostics.emit({ level: "info", message, category: "progress" });
184
+ }
182
185
  };
183
186
  }
184
187
  function createFs() {
@@ -198,14 +201,14 @@ function createFs() {
198
201
  },
199
202
  lstat: async (path) => lstat(path),
200
203
  rename: async (fromPath, toPath) => rename(fromPath, toPath),
201
- unlink: async (path) => unlink(path),
204
+ unlink: async (path) => unlink(path)
202
205
  };
203
206
  }
204
207
  function createEnv(values = process.env) {
205
208
  return {
206
209
  get(key) {
207
210
  return values[key];
208
- },
211
+ }
209
212
  };
210
213
  }
211
214
  function serializeJsonResult(value) {
@@ -213,17 +216,17 @@ function serializeJsonResult(value) {
213
216
  const serialized = JSON.stringify(value);
214
217
  if (serialized === undefined) {
215
218
  return {
216
- ok: false,
219
+ ok: false
217
220
  };
218
221
  }
219
222
  return {
220
223
  ok: true,
221
- value: JSON.parse(serialized),
224
+ value: JSON.parse(serialized)
222
225
  };
223
226
  }
224
227
  catch {
225
228
  return {
226
- ok: false,
229
+ ok: false
227
230
  };
228
231
  }
229
232
  }
@@ -232,11 +235,11 @@ function errorMetadataFromUnknown(error) {
232
235
  return {
233
236
  name: error.name,
234
237
  message: error.message,
235
- stack: error.stack,
238
+ stack: error.stack
236
239
  };
237
240
  }
238
241
  return {
239
242
  name: "Error",
240
- message: String(error),
243
+ message: String(error)
241
244
  };
242
245
  }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { LoggerOutput, RenderTableOptions, ThemePalette } from "toolcraft-d
4
4
  import { ApprovalDeclinedError } from "./human-in-loop/types.js";
5
5
  import type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions } from "./human-in-loop/types.js";
6
6
  import { ToolcraftBugError, UserError } from "./user-error.js";
7
+ import type { RuntimeLogger } from "./runtime-logging.js";
7
8
  type ScopeValue = "cli" | "mcp" | "sdk";
8
9
  type AnyObjectSchema = ObjectSchema<Record<string, never>>;
9
10
  type EmptyServices = Record<string, never>;
@@ -68,6 +69,7 @@ export type GroupCheckContext<TServices extends object = EmptyServices> = TServi
68
69
  fetch: typeof globalThis.fetch;
69
70
  fs: HandlerFs;
70
71
  env: HandlerEnv;
72
+ diagnostics: RuntimeLogger;
71
73
  progress(message: string): void;
72
74
  };
73
75
  export type CommandCheckContext<TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TSecrets extends SecretDeclarations | undefined = undefined, TServices extends object = EmptyServices> = TServices & {
@@ -76,6 +78,7 @@ export type CommandCheckContext<TParamsSchema extends ObjectSchema<any> = AnyObj
76
78
  fetch: typeof globalThis.fetch;
77
79
  fs: HandlerFs;
78
80
  env: HandlerEnv;
81
+ diagnostics: RuntimeLogger;
79
82
  progress(message: string): void;
80
83
  };
81
84
  export interface Requires<TContext = unknown> {
@@ -94,6 +97,7 @@ export type HandlerContext<TParamsSchema extends ObjectSchema<any> = AnyObjectSc
94
97
  fetch: typeof globalThis.fetch;
95
98
  fs: HandlerFs;
96
99
  env: HandlerEnv;
100
+ diagnostics: RuntimeLogger;
97
101
  progress(message: string): void;
98
102
  };
99
103
  export interface CommandConfig<TServices extends object, TParamsSchema extends ObjectSchema<any>, TSecrets extends SecretDeclarations | undefined, TResult> {
@@ -200,7 +204,9 @@ export { S, toJsonSchema } from "toolcraft-schema";
200
204
  export { AuthenticationError, BadRequestError, ClientError, ConflictError, HttpError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, ServiceUnavailableError, UnprocessableEntityError, createHttpError } from "./http-errors.js";
201
205
  export type { HttpErrorRequest, HttpErrorResponse } from "./http-errors.js";
202
206
  export { ApprovalDeclinedError, ToolcraftBugError, UserError };
207
+ export { createRuntimeLogger, isLogLevel, shouldEmitDiagnostic } from "./runtime-logging.js";
203
208
  export { findPackageMetadata, packageMetadata } from "./package-metadata.js";
204
209
  export type { PackageMetadata } from "./package-metadata.js";
210
+ export type { DiagnosticLogEvent, LogLevel, RuntimeLogger, RuntimeLoggerInput } from "./runtime-logging.js";
205
211
  export type { AnySchema, ArraySchema, BooleanSchema, EnumSchema, JsonSchema, NumberSchema, ObjectSchema, OptionalSchema, Static, StringSchema } from "toolcraft-schema";
206
212
  export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions };
package/dist/index.js CHANGED
@@ -236,8 +236,7 @@ function suggestSecretEnv(input, candidates) {
236
236
  const lastPart = inputParts[inputParts.length - 1];
237
237
  const relatedCandidates = candidates.filter((candidate) => {
238
238
  const candidateParts = candidate.split("_");
239
- return (candidateParts[0] === firstPart &&
240
- candidateParts[candidateParts.length - 1] === lastPart);
239
+ return (candidateParts[0] === firstPart && candidateParts[candidateParts.length - 1] === lastPart);
241
240
  });
242
241
  const expandedSuggestions = suggest(input, relatedCandidates, {
243
242
  threshold: Math.max(4, Math.floor(input.length / 4))
@@ -473,4 +472,5 @@ export function getCommandSourcePath(command) {
473
472
  export { S, toJsonSchema } from "toolcraft-schema";
474
473
  export { AuthenticationError, BadRequestError, ClientError, ConflictError, HttpError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, ServiceUnavailableError, UnprocessableEntityError, createHttpError } from "./http-errors.js";
475
474
  export { ApprovalDeclinedError, ToolcraftBugError, UserError };
475
+ export { createRuntimeLogger, isLogLevel, shouldEmitDiagnostic } from "./runtime-logging.js";
476
476
  export { findPackageMetadata, packageMetadata } from "./package-metadata.js";
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type SDKTransport, type Server as TinyServer } from "tiny-stdio-mcp-server";
2
- import type { Group } from "./index.js";
2
+ import type { Group, LogLevel, RuntimeLoggerInput } from "./index.js";
3
3
  import { type ErrorReportsOption } from "./error-report.js";
4
4
  import { type HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
5
5
  type Casing = "snake" | "camel";
@@ -13,6 +13,8 @@ export interface RunMCPOptions<TServices extends object = Record<string, unknown
13
13
  version?: string;
14
14
  humanInLoop?: HumanInLoopRuntimeOptions;
15
15
  projectRoot?: string;
16
+ logLevel?: LogLevel;
17
+ logger?: RuntimeLoggerInput;
16
18
  /**
17
19
  * Optional allowlist of MCP tool names or group prefixes.
18
20
  *
package/dist/mcp.js CHANGED
@@ -13,17 +13,19 @@ import { filterSchemaForScope } from "./schema-scope.js";
13
13
  import { enableSourceMaps } from "./stack-trim.js";
14
14
  import { suggest } from "./suggest.js";
15
15
  import { throwValidationErrors } from "./validation-errors.js";
16
+ import { createRuntimeLogger } from "./runtime-logging.js";
16
17
  const RESERVED_SERVICE_NAMES = new Set([
17
18
  "params",
18
19
  "secrets",
19
20
  "fetch",
20
21
  "fs",
21
22
  "env",
23
+ "diagnostics",
22
24
  "progress",
23
25
  "runtimeOptions",
24
26
  "root"
25
27
  ]);
26
- const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
28
+ const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, diagnostics, progress, runtimeOptions, root.";
27
29
  function normalizeRoots(roots) {
28
30
  if (!Array.isArray(roots)) {
29
31
  return roots;
@@ -755,6 +757,10 @@ function createResolvedMCPServer(root, options) {
755
757
  const services = (options.services ?? {});
756
758
  const runtimeOptions = options.humanInLoop ?? {};
757
759
  const runtimeFetch = options.fetch ?? globalThis.fetch;
760
+ const diagnostics = createRuntimeLogger({
761
+ level: options.logLevel,
762
+ logger: options.logger
763
+ });
758
764
  const servicesWithBuiltIns = {
759
765
  ...services,
760
766
  runtimeOptions,
@@ -780,8 +786,9 @@ function createResolvedMCPServer(root, options) {
780
786
  fetch: runtimeFetch,
781
787
  fs: createFs(),
782
788
  env: createEnv(),
783
- progress() {
784
- return undefined;
789
+ diagnostics,
790
+ progress(message) {
791
+ diagnostics.emit({ level: "info", message, category: "progress" });
785
792
  }
786
793
  };
787
794
  await assertCommandRequirements(tool.command, { ...baseContext, params: undefined });
@@ -0,0 +1,19 @@
1
+ export type LogLevel = "silent" | "error" | "warn" | "info" | "debug" | "trace";
2
+ export interface DiagnosticLogEvent {
3
+ level: Exclude<LogLevel, "silent">;
4
+ message: string;
5
+ category?: "runtime" | "http" | "auth" | "retry" | "progress";
6
+ data?: Record<string, unknown>;
7
+ }
8
+ export interface RuntimeLogger {
9
+ level: LogLevel;
10
+ emit(event: DiagnosticLogEvent): void;
11
+ }
12
+ export type RuntimeLoggerInput = RuntimeLogger | ((event: DiagnosticLogEvent) => void);
13
+ export declare const LOG_LEVELS: readonly ["silent", "error", "warn", "info", "debug", "trace"];
14
+ export declare function isLogLevel(value: string): value is LogLevel;
15
+ export declare function shouldEmitDiagnostic(eventLevel: LogLevel, configuredLevel: LogLevel): boolean;
16
+ export declare function createRuntimeLogger(options?: {
17
+ level?: LogLevel;
18
+ logger?: RuntimeLoggerInput;
19
+ }): RuntimeLogger;
@@ -0,0 +1,42 @@
1
+ const LOG_LEVEL_RANK = {
2
+ silent: 0,
3
+ error: 1,
4
+ warn: 2,
5
+ info: 3,
6
+ debug: 4,
7
+ trace: 5
8
+ };
9
+ export const LOG_LEVELS = Object.freeze([
10
+ "silent",
11
+ "error",
12
+ "warn",
13
+ "info",
14
+ "debug",
15
+ "trace"
16
+ ]);
17
+ export function isLogLevel(value) {
18
+ return LOG_LEVELS.includes(value);
19
+ }
20
+ export function shouldEmitDiagnostic(eventLevel, configuredLevel) {
21
+ if (eventLevel === "silent" || configuredLevel === "silent") {
22
+ return false;
23
+ }
24
+ return LOG_LEVEL_RANK[eventLevel] <= LOG_LEVEL_RANK[configuredLevel];
25
+ }
26
+ export function createRuntimeLogger(options = {}) {
27
+ const level = options.level ?? "warn";
28
+ const sink = options.logger;
29
+ return {
30
+ level,
31
+ emit(event) {
32
+ if (!shouldEmitDiagnostic(event.level, level)) {
33
+ return;
34
+ }
35
+ if (typeof sink === "function") {
36
+ sink(event);
37
+ return;
38
+ }
39
+ sink?.emit(event);
40
+ }
41
+ };
42
+ }
package/dist/sdk.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ObjectSchema, Static } from "toolcraft-schema";
2
- import type { Group, Scope } from "./index.js";
2
+ import type { Group, LogLevel, RuntimeLoggerInput, Scope } from "./index.js";
3
3
  import { type ErrorReportsOption } from "./error-report.js";
4
4
  import type { HumanInLoopPending, HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
5
5
  type ScopeInput = readonly Scope[] | undefined;
@@ -69,6 +69,8 @@ export interface CreateSDKOptions<TServices extends object = Record<string, unkn
69
69
  apiVersion?: string;
70
70
  projectRoot?: string;
71
71
  errorReports?: ErrorReportsOption;
72
+ logLevel?: LogLevel;
73
+ logger?: RuntimeLoggerInput;
72
74
  }
73
75
  export declare function createSDK<TRootInfo, TServices extends object = Record<string, unknown>>(root: Group<any> & {
74
76
  readonly __agentKitGroupTypeInfo: TRootInfo;
package/dist/sdk.js CHANGED
@@ -9,17 +9,19 @@ import { filterSchemaForScope } from "./schema-scope.js";
9
9
  import { enableSourceMaps } from "./stack-trim.js";
10
10
  import { suggest } from "./suggest.js";
11
11
  import { throwValidationErrors } from "./validation-errors.js";
12
+ import { createRuntimeLogger } from "./runtime-logging.js";
12
13
  const RESERVED_SERVICE_NAMES = new Set([
13
14
  "params",
14
15
  "secrets",
15
16
  "fetch",
16
17
  "fs",
17
18
  "env",
19
+ "diagnostics",
18
20
  "progress",
19
21
  "runtimeOptions",
20
22
  "root"
21
23
  ]);
22
- const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
24
+ const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, diagnostics, progress, runtimeOptions, root.";
23
25
  function splitWords(value) {
24
26
  const words = [];
25
27
  let current = "";
@@ -362,6 +364,10 @@ function createResolvedSDK(root, options = {}) {
362
364
  const services = options.services ?? {};
363
365
  const runtimeOptions = options.humanInLoop ?? {};
364
366
  const runtimeFetch = options.fetch ?? globalThis.fetch;
367
+ const diagnostics = createRuntimeLogger({
368
+ level: options.logLevel,
369
+ logger: options.logger
370
+ });
365
371
  void options.casing;
366
372
  validateServices(services);
367
373
  function build(node, path) {
@@ -384,8 +390,9 @@ function createResolvedSDK(root, options = {}) {
384
390
  fetch: runtimeFetch,
385
391
  fs: createFs(),
386
392
  env: createEnv(),
387
- progress() {
388
- return undefined;
393
+ diagnostics,
394
+ progress(message) {
395
+ diagnostics.emit({ level: "info", message, category: "progress" });
389
396
  }
390
397
  };
391
398
  await assertCommandRequirements(node, { ...baseContext, params: undefined }, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.67",
3
+ "version": "0.0.68",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter @poe-code/agent-mcp-config @poe-code/agent-human-in-loop @poe-code/task-list @poe-code/agent-defs @poe-code/config-mutations @poe-code/process-runner tiny-mcp-client mcp-oauth auth-store"
48
48
  },
49
49
  "dependencies": {
50
- "toolcraft-schema": "0.0.67",
50
+ "toolcraft-schema": "0.0.68",
51
51
  "commander": "^14.0.3",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",