toolcraft 0.0.60 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-error-summary.d.ts +53 -0
- package/dist/api-error-summary.js +162 -0
- package/dist/cli.js +208 -81
- package/dist/human-in-loop/approvals-commands.js +1 -9
- package/dist/mcp.compile-check.js +1 -1
- package/dist/mcp.js +11 -3
- package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +1 -1
- package/node_modules/toolcraft-design/dist/components/help-formatter.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface HttpErrorLike {
|
|
2
|
+
name: string;
|
|
3
|
+
message: string;
|
|
4
|
+
request: {
|
|
5
|
+
method: string;
|
|
6
|
+
url: string;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
};
|
|
10
|
+
response: {
|
|
11
|
+
status: number;
|
|
12
|
+
statusText: string;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
body: unknown;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface ApiErrorSummary {
|
|
18
|
+
status: number;
|
|
19
|
+
statusText: string;
|
|
20
|
+
requestMethod: string;
|
|
21
|
+
requestUrl: string;
|
|
22
|
+
requestId?: string;
|
|
23
|
+
code?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
fieldErrors?: Array<{
|
|
26
|
+
path: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}>;
|
|
29
|
+
retryAfter?: string;
|
|
30
|
+
hint?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ToolcraftErrorEnvelope {
|
|
33
|
+
kind: "http";
|
|
34
|
+
message: string;
|
|
35
|
+
code?: string;
|
|
36
|
+
requestId?: string;
|
|
37
|
+
http: {
|
|
38
|
+
method: string;
|
|
39
|
+
url: string;
|
|
40
|
+
status: number;
|
|
41
|
+
statusText: string;
|
|
42
|
+
};
|
|
43
|
+
fieldErrors?: Array<{
|
|
44
|
+
path: string;
|
|
45
|
+
message: string;
|
|
46
|
+
}>;
|
|
47
|
+
retryAfter?: string;
|
|
48
|
+
hint?: string;
|
|
49
|
+
reportPath?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function isHttpErrorLike(error: unknown): error is HttpErrorLike;
|
|
52
|
+
export declare function summarizeHttpError(error: HttpErrorLike): ApiErrorSummary;
|
|
53
|
+
export declare function createHttpErrorEnvelope(error: HttpErrorLike, reportPath?: string): ToolcraftErrorEnvelope;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { redactHttpBody } from "./redaction.js";
|
|
2
|
+
const REQUEST_ID_FIELDS = ["request_id", "requestId", "id"];
|
|
3
|
+
const CODE_FIELDS = ["code", "error_code", "errorCode", "error"];
|
|
4
|
+
const MESSAGE_FIELDS = ["message", "detail", "title", "error_description"];
|
|
5
|
+
export function isHttpErrorLike(error) {
|
|
6
|
+
if (!isPlainObject(error)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (typeof error.name !== "string" || typeof error.message !== "string") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const request = error.request;
|
|
13
|
+
const response = error.response;
|
|
14
|
+
return (isPlainObject(request) &&
|
|
15
|
+
typeof request.method === "string" &&
|
|
16
|
+
typeof request.url === "string" &&
|
|
17
|
+
isStringRecord(request.headers) &&
|
|
18
|
+
isPlainObject(response) &&
|
|
19
|
+
typeof response.status === "number" &&
|
|
20
|
+
typeof response.statusText === "string" &&
|
|
21
|
+
isStringRecord(response.headers) &&
|
|
22
|
+
hasOwnProperty(response, "body"));
|
|
23
|
+
}
|
|
24
|
+
export function summarizeHttpError(error) {
|
|
25
|
+
const body = redactHttpBody(error.response.body);
|
|
26
|
+
const retryAfter = getHeader(error.response.headers, "retry-after");
|
|
27
|
+
const message = extractMessage(body);
|
|
28
|
+
const summary = {
|
|
29
|
+
status: error.response.status,
|
|
30
|
+
statusText: error.response.statusText,
|
|
31
|
+
requestMethod: error.request.method,
|
|
32
|
+
requestUrl: error.request.url,
|
|
33
|
+
...(message === undefined ? {} : { message }),
|
|
34
|
+
...optionalString("requestId", getHeader(error.response.headers, "x-request-id") ?? extractFirstString(body, REQUEST_ID_FIELDS)),
|
|
35
|
+
...optionalString("code", extractCode(body)),
|
|
36
|
+
...optionalString("retryAfter", retryAfter),
|
|
37
|
+
...optionalArray("fieldErrors", extractFieldErrors(body)),
|
|
38
|
+
...optionalString("hint", createHttpErrorHint(error.response.status, retryAfter))
|
|
39
|
+
};
|
|
40
|
+
return summary;
|
|
41
|
+
}
|
|
42
|
+
export function createHttpErrorEnvelope(error, reportPath) {
|
|
43
|
+
const summary = summarizeHttpError(error);
|
|
44
|
+
return {
|
|
45
|
+
kind: "http",
|
|
46
|
+
message: summary.message ?? error.message,
|
|
47
|
+
...(summary.code === undefined ? {} : { code: summary.code }),
|
|
48
|
+
...(summary.requestId === undefined ? {} : { requestId: summary.requestId }),
|
|
49
|
+
http: {
|
|
50
|
+
method: summary.requestMethod,
|
|
51
|
+
url: summary.requestUrl,
|
|
52
|
+
status: summary.status,
|
|
53
|
+
statusText: summary.statusText
|
|
54
|
+
},
|
|
55
|
+
...(summary.fieldErrors === undefined ? {} : { fieldErrors: summary.fieldErrors }),
|
|
56
|
+
...(summary.retryAfter === undefined ? {} : { retryAfter: summary.retryAfter }),
|
|
57
|
+
...(summary.hint === undefined ? {} : { hint: summary.hint }),
|
|
58
|
+
...(reportPath === undefined ? {} : { reportPath })
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function createHttpErrorHint(status, retryAfter) {
|
|
62
|
+
if (status === 401 || status === 403) {
|
|
63
|
+
return "Check the configured API credentials and permissions.";
|
|
64
|
+
}
|
|
65
|
+
if ((status === 429 || status === 503) && retryAfter !== undefined) {
|
|
66
|
+
return `Retry after ${retryAfter}.`;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function extractMessage(body) {
|
|
71
|
+
if (!isPlainObject(body)) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const graphqlMessage = extractGraphQlMessage(body);
|
|
75
|
+
if (graphqlMessage !== undefined) {
|
|
76
|
+
return graphqlMessage;
|
|
77
|
+
}
|
|
78
|
+
return extractFirstString(body, MESSAGE_FIELDS);
|
|
79
|
+
}
|
|
80
|
+
function extractCode(body) {
|
|
81
|
+
if (!isPlainObject(body)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const graphqlCode = extractGraphQlCode(body);
|
|
85
|
+
if (graphqlCode !== undefined) {
|
|
86
|
+
return graphqlCode;
|
|
87
|
+
}
|
|
88
|
+
return extractFirstString(body, CODE_FIELDS);
|
|
89
|
+
}
|
|
90
|
+
function extractGraphQlMessage(body) {
|
|
91
|
+
const [first] = Array.isArray(body.errors) ? body.errors : [];
|
|
92
|
+
return isPlainObject(first) && typeof first.message === "string" ? first.message : undefined;
|
|
93
|
+
}
|
|
94
|
+
function extractGraphQlCode(body) {
|
|
95
|
+
const [first] = Array.isArray(body.errors) ? body.errors : [];
|
|
96
|
+
if (!isPlainObject(first) || !isPlainObject(first.extensions)) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return typeof first.extensions.code === "string" ? first.extensions.code : undefined;
|
|
100
|
+
}
|
|
101
|
+
function extractFieldErrors(body) {
|
|
102
|
+
if (!isPlainObject(body)) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const candidates = [body.field_errors, body.fieldErrors, body.errors, body.non_field_errors];
|
|
106
|
+
const flattened = candidates.flatMap((candidate) => flattenFieldErrors(candidate, []));
|
|
107
|
+
return flattened.length === 0 ? undefined : flattened;
|
|
108
|
+
}
|
|
109
|
+
function flattenFieldErrors(value, path) {
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
return [{ path: formatPath(path), message: value }];
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
if (value.every((entry) => typeof entry === "string")) {
|
|
115
|
+
return value.map((message) => ({ path: formatPath(path), message }));
|
|
116
|
+
}
|
|
117
|
+
return value.flatMap((entry, index) => flattenFieldErrors(entry, [...path, String(index)]));
|
|
118
|
+
}
|
|
119
|
+
if (!isPlainObject(value)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
return Object.entries(value).flatMap(([key, nested]) => flattenFieldErrors(nested, [...path, key]));
|
|
123
|
+
}
|
|
124
|
+
function formatPath(path) {
|
|
125
|
+
return path.length === 0 ? "error" : path.join(".");
|
|
126
|
+
}
|
|
127
|
+
function extractFirstString(body, fields) {
|
|
128
|
+
if (!isPlainObject(body)) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
for (const field of fields) {
|
|
132
|
+
const value = body[field];
|
|
133
|
+
if (typeof value === "string" && value.length > 0) {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
function getHeader(headers, name) {
|
|
140
|
+
const lowerName = name.toLowerCase();
|
|
141
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
142
|
+
if (key.toLowerCase() === lowerName && value.length > 0) {
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
function optionalString(name, value) {
|
|
149
|
+
return value === undefined ? {} : { [name]: value };
|
|
150
|
+
}
|
|
151
|
+
function optionalArray(name, value) {
|
|
152
|
+
return value === undefined ? {} : { [name]: value };
|
|
153
|
+
}
|
|
154
|
+
function isStringRecord(value) {
|
|
155
|
+
return isPlainObject(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
156
|
+
}
|
|
157
|
+
function isPlainObject(value) {
|
|
158
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
function hasOwnProperty(value, name) {
|
|
161
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
162
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ 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";
|
|
7
8
|
import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
|
|
8
9
|
import { writeErrorReport } from "./error-report.js";
|
|
9
10
|
import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
|
|
@@ -746,7 +747,7 @@ function getHelpChildren(group, scope) {
|
|
|
746
747
|
function findVisibleChild(group, token, scope) {
|
|
747
748
|
return getVisibleChildren(group, scope).find((child) => child.name === token || child.aliases.includes(token));
|
|
748
749
|
}
|
|
749
|
-
function resolveHelpTarget(root, argv, scope, rootDisplayName) {
|
|
750
|
+
function resolveHelpTarget(root, argv, scope, rootUsageName, rootDisplayName) {
|
|
750
751
|
const breadcrumb = [rootDisplayName ?? root.name];
|
|
751
752
|
let current = root;
|
|
752
753
|
for (const token of argv.slice(2)) {
|
|
@@ -758,7 +759,7 @@ function resolveHelpTarget(root, argv, scope, rootDisplayName) {
|
|
|
758
759
|
}
|
|
759
760
|
const child = findVisibleChild(current, token, scope);
|
|
760
761
|
if (child === undefined) {
|
|
761
|
-
|
|
762
|
+
throw new UserError(formatUnknownHelpCommandMessage(current, token, scope, rootUsageName, breadcrumb));
|
|
762
763
|
}
|
|
763
764
|
breadcrumb.push(child.name);
|
|
764
765
|
current = child;
|
|
@@ -768,6 +769,12 @@ function resolveHelpTarget(root, argv, scope, rootDisplayName) {
|
|
|
768
769
|
node: current
|
|
769
770
|
};
|
|
770
771
|
}
|
|
772
|
+
function formatUnknownHelpCommandMessage(group, input, scope, rootUsageName, breadcrumb) {
|
|
773
|
+
const suggestions = suggest(input, getHelpChildren(group, scope).map((child) => child.name));
|
|
774
|
+
const commandPath = breadcrumb.slice(1).join(" ");
|
|
775
|
+
const helpTarget = commandPath.length === 0 ? rootUsageName : `${rootUsageName} ${commandPath}`;
|
|
776
|
+
return `${formatSuggestionMessage(`Unknown command "${input}".`, suggestions)}\nRun ${helpTarget} --help for usage.`;
|
|
777
|
+
}
|
|
771
778
|
function formatHelpFieldFlags(field, globalLongOptionFlags) {
|
|
772
779
|
if (field.positionalIndex !== undefined) {
|
|
773
780
|
return formatPositionalToken(field);
|
|
@@ -1213,6 +1220,79 @@ function renderLeafHelp(command, breadcrumb, casing, globalOptions, rootUsageNam
|
|
|
1213
1220
|
sections
|
|
1214
1221
|
});
|
|
1215
1222
|
}
|
|
1223
|
+
function renderJsonHelp(target, root, casing, globalOptions, rootUsageName) {
|
|
1224
|
+
const globalLongOptionFlags = getGlobalLongOptionFlags(globalOptions.presetsEnabled, globalOptions.showVersion, globalOptions.controls);
|
|
1225
|
+
const node = target.node;
|
|
1226
|
+
if (node.kind === "group") {
|
|
1227
|
+
const commandRows = formatCommandRows(node, "cli", casing, globalLongOptionFlags);
|
|
1228
|
+
const isRoot = node === root;
|
|
1229
|
+
return `${JSON.stringify({
|
|
1230
|
+
schemaVersion: 1,
|
|
1231
|
+
kind: "group",
|
|
1232
|
+
name: target.breadcrumb.at(-1) ?? rootUsageName,
|
|
1233
|
+
path: target.breadcrumb.filter((segment) => segment.length > 0),
|
|
1234
|
+
usage: buildUsageLine(target.breadcrumb, rootUsageName, formatGroupUsageSuffix(node, "cli", casing, globalLongOptionFlags)),
|
|
1235
|
+
...(node.description === undefined ? {} : { description: node.description }),
|
|
1236
|
+
commands: commandRows.map((row) => ({ name: row.name, description: row.description })),
|
|
1237
|
+
options: isRoot
|
|
1238
|
+
? collectSchemaGlobalFieldRows(node, "cli", casing, globalLongOptionFlags).map((row) => ({
|
|
1239
|
+
name: row.flags.split(/[ ,]+/)[0]?.replace(/^--/, "") ?? row.flags,
|
|
1240
|
+
flags: row.flags.split(", "),
|
|
1241
|
+
type: "unknown",
|
|
1242
|
+
description: row.description,
|
|
1243
|
+
required: false
|
|
1244
|
+
}))
|
|
1245
|
+
: []
|
|
1246
|
+
}, null, 2)}\n`;
|
|
1247
|
+
}
|
|
1248
|
+
const collected = collectFields(node.params, casing, globalLongOptionFlags);
|
|
1249
|
+
const fields = assignPositionals(collected.fields, node.positional);
|
|
1250
|
+
const positionalFields = fields.filter((field) => field.positionalIndex !== undefined);
|
|
1251
|
+
const usageSuffix = positionalFields.length > 0
|
|
1252
|
+
? `[OPTIONS] ${positionalFields.map(formatPositionalToken).join(" ")}`
|
|
1253
|
+
: "[OPTIONS]";
|
|
1254
|
+
return `${JSON.stringify({
|
|
1255
|
+
schemaVersion: 1,
|
|
1256
|
+
kind: "command",
|
|
1257
|
+
name: node.name,
|
|
1258
|
+
path: target.breadcrumb.filter((segment) => segment.length > 0),
|
|
1259
|
+
usage: buildUsageLine(target.breadcrumb, rootUsageName, usageSuffix),
|
|
1260
|
+
...(node.description === undefined ? {} : { description: node.description }),
|
|
1261
|
+
options: fields
|
|
1262
|
+
.filter((field) => field.global !== true)
|
|
1263
|
+
.map((field) => formatJsonHelpOption(field, globalLongOptionFlags)),
|
|
1264
|
+
secrets: Object.entries(node.secrets).map(([name, secret]) => ({
|
|
1265
|
+
name,
|
|
1266
|
+
env: secret.env,
|
|
1267
|
+
required: secret.optional !== true,
|
|
1268
|
+
...(secret.description === undefined ? {} : { description: secret.description })
|
|
1269
|
+
})),
|
|
1270
|
+
examples: node.examples
|
|
1271
|
+
}, null, 2)}\n`;
|
|
1272
|
+
}
|
|
1273
|
+
function formatJsonHelpOption(field, globalLongOptionFlags) {
|
|
1274
|
+
return {
|
|
1275
|
+
name: field.displayPath,
|
|
1276
|
+
flags: formatHelpFieldFlags(field, globalLongOptionFlags).split(", "),
|
|
1277
|
+
type: formatJsonHelpSchemaType(field.schema),
|
|
1278
|
+
...(field.description === undefined ? {} : { description: field.description }),
|
|
1279
|
+
required: field.requiredWhenActive,
|
|
1280
|
+
...(field.hasDefault ? { default: field.defaultValue } : {}),
|
|
1281
|
+
...(field.positionalIndex === undefined ? {} : { positional: true })
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function formatJsonHelpSchemaType(schema) {
|
|
1285
|
+
if (schema.kind === "enum") {
|
|
1286
|
+
return "enum";
|
|
1287
|
+
}
|
|
1288
|
+
if (schema.kind === "array") {
|
|
1289
|
+
return "array";
|
|
1290
|
+
}
|
|
1291
|
+
if (schema.kind === "json") {
|
|
1292
|
+
return "json";
|
|
1293
|
+
}
|
|
1294
|
+
return schema.kind;
|
|
1295
|
+
}
|
|
1216
1296
|
function renderHelpDocument(input) {
|
|
1217
1297
|
const title = input.breadcrumb.filter((segment) => segment.length > 0).join(" ") || input.rootUsageName;
|
|
1218
1298
|
const description = input.description ?? "";
|
|
@@ -1235,11 +1315,19 @@ function renderHelpDocument(input) {
|
|
|
1235
1315
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
1236
1316
|
}
|
|
1237
1317
|
async function renderGeneratedHelp(root, argv, options) {
|
|
1238
|
-
const target = resolveHelpTarget(root, argv, "cli", options.rootDisplayName);
|
|
1239
1318
|
const output = resolveHelpOutput(argv);
|
|
1240
1319
|
const casing = options.casing ?? "kebab";
|
|
1241
1320
|
const rootUsageName = options.rootUsageName ?? inferProgramName(argv);
|
|
1321
|
+
const target = resolveHelpTarget(root, argv, "cli", rootUsageName, options.rootDisplayName);
|
|
1242
1322
|
const controls = resolveCLIControls(options.controls);
|
|
1323
|
+
if (output === "json") {
|
|
1324
|
+
process.stdout.write(renderJsonHelp(target, root, casing, {
|
|
1325
|
+
controls,
|
|
1326
|
+
showVersion: options.version !== undefined,
|
|
1327
|
+
presetsEnabled: options.presets === true
|
|
1328
|
+
}, rootUsageName));
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1243
1331
|
await withOutputFormat(output, async () => {
|
|
1244
1332
|
const rendered = target.node.kind === "group"
|
|
1245
1333
|
? renderGroupHelp(target.node, target.breadcrumb, "cli", casing, {
|
|
@@ -2082,6 +2170,16 @@ function renderCliErrorPattern(pattern) {
|
|
|
2082
2170
|
process.exitCode = 1;
|
|
2083
2171
|
return;
|
|
2084
2172
|
}
|
|
2173
|
+
if (pattern.kind === "definition") {
|
|
2174
|
+
logger.error(`Command definition error: ${pattern.error.message}\n` +
|
|
2175
|
+
"This is a bug in the generated command definition, not in your command arguments.\n" +
|
|
2176
|
+
"Run with --debug for a stack trace.");
|
|
2177
|
+
if (pattern.debugStackMode !== undefined && pattern.error.stack) {
|
|
2178
|
+
process.stderr.write(`${formatDebugStack(pattern.error.stack, pattern.debugStackMode)}\n`);
|
|
2179
|
+
}
|
|
2180
|
+
process.exitCode = 1;
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2085
2183
|
if (pattern.kind === "toolcraft-bug") {
|
|
2086
2184
|
logger.error(`toolcraft hit an internal invariant: ${pattern.error.message}\n` +
|
|
2087
2185
|
`This is a bug in toolcraft or in the command definition; ` +
|
|
@@ -2755,7 +2853,7 @@ function isHttpErrorLike(error) {
|
|
|
2755
2853
|
if (!isPlainObject(error)) {
|
|
2756
2854
|
return false;
|
|
2757
2855
|
}
|
|
2758
|
-
if (error.name !== "
|
|
2856
|
+
if (typeof error.name !== "string" || typeof error.message !== "string") {
|
|
2759
2857
|
return false;
|
|
2760
2858
|
}
|
|
2761
2859
|
const request = error.request;
|
|
@@ -2902,6 +3000,7 @@ function formatHttpErrorSnippet(body) {
|
|
|
2902
3000
|
}
|
|
2903
3001
|
function renderHttpError(error, options) {
|
|
2904
3002
|
const detailed = options.verbose || options.debugStackMode !== undefined;
|
|
3003
|
+
const summary = summarizeHttpError(error);
|
|
2905
3004
|
const lines = [
|
|
2906
3005
|
styleHttpErrorLine(`Request: ${error.request.method} ${error.request.url}`, text.muted)
|
|
2907
3006
|
];
|
|
@@ -2916,7 +3015,24 @@ function renderHttpError(error, options) {
|
|
|
2916
3015
|
lines.push("", "Response headers:", ...formatHttpErrorHeaders(error.response.headers), "", "Response body:", indentHttpErrorBlock(formatHttpErrorBody(error.response.body)));
|
|
2917
3016
|
}
|
|
2918
3017
|
else {
|
|
2919
|
-
|
|
3018
|
+
const summaryLines = [
|
|
3019
|
+
summary.code === undefined ? undefined : `Code: ${summary.code}`,
|
|
3020
|
+
summary.message === undefined ? undefined : `Message: ${summary.message}`,
|
|
3021
|
+
summary.requestId === undefined ? undefined : `Request id: ${summary.requestId}`,
|
|
3022
|
+
summary.retryAfter === undefined ? undefined : `Retry after: ${summary.retryAfter}`,
|
|
3023
|
+
summary.hint === undefined ? undefined : `Hint: ${summary.hint}`
|
|
3024
|
+
].filter((line) => line !== undefined);
|
|
3025
|
+
if (summary.fieldErrors !== undefined && summary.fieldErrors.length > 0) {
|
|
3026
|
+
summaryLines.push("", "Field errors:", ...summary.fieldErrors.map((fieldError) => ` ${fieldError.path}: ${fieldError.message}`));
|
|
3027
|
+
}
|
|
3028
|
+
lines.push("");
|
|
3029
|
+
if (summaryLines.length > 0) {
|
|
3030
|
+
lines.push(...summaryLines);
|
|
3031
|
+
}
|
|
3032
|
+
else {
|
|
3033
|
+
lines.push(`Response body: ${formatHttpErrorSnippet(error.response.body)}`);
|
|
3034
|
+
}
|
|
3035
|
+
lines.push("Re-run with --verbose to see headers and full body.");
|
|
2920
3036
|
}
|
|
2921
3037
|
process.stderr.write(`${lines.join("\n")}\n`);
|
|
2922
3038
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
@@ -2928,17 +3044,23 @@ async function handleRunError(error, options) {
|
|
|
2928
3044
|
const logger = createLogger();
|
|
2929
3045
|
await withOutputFormat(options.output, async () => {
|
|
2930
3046
|
if (error instanceof UserError) {
|
|
2931
|
-
renderCliErrorPattern(options.userErrorPattern === "
|
|
3047
|
+
renderCliErrorPattern(options.userErrorPattern === "definition"
|
|
2932
3048
|
? {
|
|
2933
|
-
kind: "
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
commandPath: options.commandPath
|
|
3049
|
+
kind: "definition",
|
|
3050
|
+
error,
|
|
3051
|
+
debugStackMode: options.debugStackMode
|
|
2937
3052
|
}
|
|
2938
|
-
:
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3053
|
+
: options.userErrorPattern === "usage"
|
|
3054
|
+
? {
|
|
3055
|
+
kind: "usage",
|
|
3056
|
+
message: error.message,
|
|
3057
|
+
rootUsageName: options.rootUsageName,
|
|
3058
|
+
commandPath: options.commandPath
|
|
3059
|
+
}
|
|
3060
|
+
: {
|
|
3061
|
+
kind: "runtime-user",
|
|
3062
|
+
message: error.message
|
|
3063
|
+
});
|
|
2942
3064
|
return;
|
|
2943
3065
|
}
|
|
2944
3066
|
if (error instanceof Error && error.name === "ToolcraftBugError") {
|
|
@@ -3190,77 +3312,82 @@ function configureCommanderSuggestionOutput(command) {
|
|
|
3190
3312
|
export async function runCLI(roots, options = {}) {
|
|
3191
3313
|
enableSourceMaps();
|
|
3192
3314
|
const argv = [...(options.argv ?? process.argv)];
|
|
3193
|
-
const normalizedRoot = normalizeRoots(roots, argv);
|
|
3194
|
-
const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
|
|
3195
|
-
await resolveMcpProxies(root, { projectRoot: options.projectRoot });
|
|
3196
|
-
const casing = options.casing ?? "kebab";
|
|
3197
|
-
const services = (options.services ?? {});
|
|
3198
|
-
const runtimeOptions = options.humanInLoop ?? {};
|
|
3199
|
-
const runtimeFetch = options.fetch ?? globalThis.fetch;
|
|
3200
|
-
const version = options.version ?? findEntrypointPackageMetadata(argv[1])?.version;
|
|
3201
3315
|
const rootUsageName = options.rootUsageName ?? inferProgramName(argv);
|
|
3202
|
-
const controls = resolveCLIControls(options.controls);
|
|
3203
|
-
const servicesWithBuiltIns = {
|
|
3204
|
-
...services,
|
|
3205
|
-
runtimeOptions,
|
|
3206
|
-
root
|
|
3207
|
-
};
|
|
3208
|
-
const requirementOptions = {
|
|
3209
|
-
apiVersion: options.apiVersion
|
|
3210
|
-
};
|
|
3211
|
-
validateServices(services);
|
|
3212
|
-
if (hasHelpFlag(argv)) {
|
|
3213
|
-
await renderGeneratedHelp(root, argv, { ...options, version });
|
|
3214
|
-
return;
|
|
3215
|
-
}
|
|
3216
|
-
const program = new CommanderCommand();
|
|
3217
|
-
program.name(root.name);
|
|
3218
|
-
program.exitOverride();
|
|
3219
|
-
program.showHelpAfterError();
|
|
3220
|
-
program.addHelpCommand(false);
|
|
3221
|
-
const presetsEnabled = options.presets === true;
|
|
3222
|
-
const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled, version !== undefined, controls);
|
|
3223
|
-
addGlobalOptions(program, presetsEnabled, controls);
|
|
3224
|
-
if (version !== undefined) {
|
|
3225
|
-
program.version(version, "--version");
|
|
3226
|
-
}
|
|
3227
|
-
Reflect.set(program, "_toolcraftReservedChildNames", root.children
|
|
3228
|
-
.filter((child) => !isNodeVisibleInScope(child, "cli"))
|
|
3229
|
-
.flatMap((child) => getNodeCommandNames(child)));
|
|
3230
3316
|
let lastActionCommand;
|
|
3231
3317
|
let resolvedCommandPath = "";
|
|
3318
|
+
let program;
|
|
3319
|
+
let version;
|
|
3320
|
+
let userErrorPattern = "definition";
|
|
3232
3321
|
let errorReportContext;
|
|
3233
|
-
const execute = async (state) => {
|
|
3234
|
-
lastActionCommand = state.actionCommand;
|
|
3235
|
-
resolvedCommandPath = formatCliCommandPath(state.commandPath);
|
|
3236
|
-
await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, (context) => {
|
|
3237
|
-
errorReportContext = context;
|
|
3238
|
-
});
|
|
3239
|
-
};
|
|
3240
|
-
const rootChildNames = new Set(root.children
|
|
3241
|
-
.filter((candidate) => isNodeVisibleInScope(candidate, "cli"))
|
|
3242
|
-
.map((candidate) => candidate.name));
|
|
3243
|
-
for (const child of root.children) {
|
|
3244
|
-
const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls);
|
|
3245
|
-
if (command === null) {
|
|
3246
|
-
continue;
|
|
3247
|
-
}
|
|
3248
|
-
const isDefaultChild = root.default !== undefined &&
|
|
3249
|
-
root.default.scope.includes("cli") &&
|
|
3250
|
-
(command.name() === root.default.name || command.aliases().includes(root.default.name));
|
|
3251
|
-
addCommanderChild(program, command, isDefaultChild, rootChildNames);
|
|
3252
|
-
}
|
|
3253
|
-
configureCommanderSuggestionOutput(program);
|
|
3254
|
-
const unknownCommand = findUnknownCommanderCommand(program, argv);
|
|
3255
|
-
if (unknownCommand !== undefined) {
|
|
3256
|
-
createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
|
|
3257
|
-
rootUsageName,
|
|
3258
|
-
commandPath: unknownCommand.commandPath
|
|
3259
|
-
}));
|
|
3260
|
-
process.exitCode = 1;
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
3322
|
try {
|
|
3323
|
+
const normalizedRoot = normalizeRoots(roots, argv);
|
|
3324
|
+
const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
|
|
3325
|
+
await resolveMcpProxies(root, { projectRoot: options.projectRoot });
|
|
3326
|
+
const casing = options.casing ?? "kebab";
|
|
3327
|
+
const services = (options.services ?? {});
|
|
3328
|
+
const runtimeOptions = options.humanInLoop ?? {};
|
|
3329
|
+
const runtimeFetch = options.fetch ?? globalThis.fetch;
|
|
3330
|
+
version = options.version ?? findEntrypointPackageMetadata(argv[1])?.version;
|
|
3331
|
+
const controls = resolveCLIControls(options.controls);
|
|
3332
|
+
const servicesWithBuiltIns = {
|
|
3333
|
+
...services,
|
|
3334
|
+
runtimeOptions,
|
|
3335
|
+
root
|
|
3336
|
+
};
|
|
3337
|
+
const requirementOptions = {
|
|
3338
|
+
apiVersion: options.apiVersion
|
|
3339
|
+
};
|
|
3340
|
+
validateServices(services);
|
|
3341
|
+
if (hasHelpFlag(argv)) {
|
|
3342
|
+
userErrorPattern = "usage";
|
|
3343
|
+
await renderGeneratedHelp(root, argv, { ...options, version });
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
program = new CommanderCommand();
|
|
3347
|
+
program.name(root.name);
|
|
3348
|
+
program.exitOverride();
|
|
3349
|
+
program.showHelpAfterError();
|
|
3350
|
+
program.addHelpCommand(false);
|
|
3351
|
+
const presetsEnabled = options.presets === true;
|
|
3352
|
+
const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled, version !== undefined, controls);
|
|
3353
|
+
addGlobalOptions(program, presetsEnabled, controls);
|
|
3354
|
+
if (version !== undefined) {
|
|
3355
|
+
program.version(version, "--version");
|
|
3356
|
+
}
|
|
3357
|
+
Reflect.set(program, "_toolcraftReservedChildNames", root.children
|
|
3358
|
+
.filter((child) => !isNodeVisibleInScope(child, "cli"))
|
|
3359
|
+
.flatMap((child) => getNodeCommandNames(child)));
|
|
3360
|
+
const execute = async (state) => {
|
|
3361
|
+
lastActionCommand = state.actionCommand;
|
|
3362
|
+
resolvedCommandPath = formatCliCommandPath(state.commandPath);
|
|
3363
|
+
await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, (context) => {
|
|
3364
|
+
errorReportContext = context;
|
|
3365
|
+
});
|
|
3366
|
+
};
|
|
3367
|
+
const rootChildNames = new Set(root.children
|
|
3368
|
+
.filter((candidate) => isNodeVisibleInScope(candidate, "cli"))
|
|
3369
|
+
.map((candidate) => candidate.name));
|
|
3370
|
+
for (const child of root.children) {
|
|
3371
|
+
const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled, controls);
|
|
3372
|
+
if (command === null) {
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
const isDefaultChild = root.default !== undefined &&
|
|
3376
|
+
root.default.scope.includes("cli") &&
|
|
3377
|
+
(command.name() === root.default.name || command.aliases().includes(root.default.name));
|
|
3378
|
+
addCommanderChild(program, command, isDefaultChild, rootChildNames);
|
|
3379
|
+
}
|
|
3380
|
+
configureCommanderSuggestionOutput(program);
|
|
3381
|
+
const unknownCommand = findUnknownCommanderCommand(program, argv);
|
|
3382
|
+
if (unknownCommand !== undefined) {
|
|
3383
|
+
createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
|
|
3384
|
+
rootUsageName,
|
|
3385
|
+
commandPath: unknownCommand.commandPath
|
|
3386
|
+
}));
|
|
3387
|
+
process.exitCode = 1;
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
userErrorPattern = "usage";
|
|
3264
3391
|
await program.parseAsync(argv);
|
|
3265
3392
|
}
|
|
3266
3393
|
catch (error) {
|
|
@@ -3296,7 +3423,7 @@ export async function runCLI(roots, options = {}) {
|
|
|
3296
3423
|
argv,
|
|
3297
3424
|
rootUsageName,
|
|
3298
3425
|
commandPath: resolvedCommandPath,
|
|
3299
|
-
userErrorPattern: errorReportContext?.params === undefined ?
|
|
3426
|
+
userErrorPattern: errorReportContext?.params === undefined ? userErrorPattern : "runtime-user"
|
|
3300
3427
|
});
|
|
3301
3428
|
}
|
|
3302
3429
|
}
|
|
@@ -14,11 +14,7 @@ const showParams = S.Object({
|
|
|
14
14
|
approvalId: S.String()
|
|
15
15
|
});
|
|
16
16
|
const runParams = S.Object({
|
|
17
|
-
approvalId: S.String()
|
|
18
|
-
dryRun: S.Optional(S.Boolean({
|
|
19
|
-
description: "Preview the approval without prompting or executing it",
|
|
20
|
-
scope: ["cli"]
|
|
21
|
-
}))
|
|
17
|
+
approvalId: S.String()
|
|
22
18
|
});
|
|
23
19
|
export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
|
|
24
20
|
name: "approvals",
|
|
@@ -76,10 +72,6 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
|
|
|
76
72
|
scope: runScope,
|
|
77
73
|
params: runParams,
|
|
78
74
|
handler: async ({ params, runtimeOptions, root }) => {
|
|
79
|
-
if (params.dryRun === true) {
|
|
80
|
-
const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
|
|
81
|
-
return tasks.get(params.approvalId);
|
|
82
|
-
}
|
|
83
75
|
return runApproval(params.approvalId, runtimeOptions, root);
|
|
84
76
|
},
|
|
85
77
|
render: {
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, lstat, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createServer, JSON_RPC_ERROR_CODES, ToolError } from "tiny-stdio-mcp-server";
|
|
3
3
|
import { toJsonSchema } from "toolcraft-schema";
|
|
4
|
+
import { createHttpErrorEnvelope, isHttpErrorLike } from "./api-error-summary.js";
|
|
4
5
|
import { ToolcraftBugError, UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
|
|
5
6
|
import { writeErrorReport } from "./error-report.js";
|
|
6
7
|
import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
|
|
@@ -730,10 +731,17 @@ function toToolContent(result) {
|
|
|
730
731
|
const fallbackText = JSON.stringify(fallbackValue);
|
|
731
732
|
return [{ type: "text", text: fallbackText }];
|
|
732
733
|
}
|
|
733
|
-
function toToolError(error) {
|
|
734
|
+
function toToolError(error, reportPath) {
|
|
734
735
|
if (error instanceof ToolError) {
|
|
735
736
|
return error;
|
|
736
737
|
}
|
|
738
|
+
if (isHttpErrorLike(error)) {
|
|
739
|
+
const code = error.response.status >= 400 && error.response.status < 500
|
|
740
|
+
? JSON_RPC_ERROR_CODES.INVALID_PARAMS
|
|
741
|
+
: JSON_RPC_ERROR_CODES.INTERNAL_ERROR;
|
|
742
|
+
const envelope = createHttpErrorEnvelope(error, reportPath);
|
|
743
|
+
return new ToolError(code, envelope.message, envelope);
|
|
744
|
+
}
|
|
737
745
|
if (error instanceof UserError) {
|
|
738
746
|
return new ToolError(JSON_RPC_ERROR_CODES.INVALID_PARAMS, error.message);
|
|
739
747
|
}
|
|
@@ -798,7 +806,7 @@ function createResolvedMCPServer(root, options) {
|
|
|
798
806
|
if (error instanceof ApprovalDeclinedError) {
|
|
799
807
|
return renderDeclinedApproval(error);
|
|
800
808
|
}
|
|
801
|
-
await writeErrorReport({
|
|
809
|
+
const report = await writeErrorReport({
|
|
802
810
|
command: tool.command,
|
|
803
811
|
commandPath: tool.commandPath,
|
|
804
812
|
env: process.env,
|
|
@@ -808,7 +816,7 @@ function createResolvedMCPServer(root, options) {
|
|
|
808
816
|
projectRoot: options.projectRoot,
|
|
809
817
|
secrets
|
|
810
818
|
});
|
|
811
|
-
throw toToolError(error);
|
|
819
|
+
throw toToolError(error, report?.displayPath);
|
|
812
820
|
}
|
|
813
821
|
};
|
|
814
822
|
if (tool.outputSchema === undefined) {
|
|
@@ -100,7 +100,7 @@ export function formatColumns(opts) {
|
|
|
100
100
|
return [`${firstIndent}${row.left}`];
|
|
101
101
|
}
|
|
102
102
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
103
|
-
if (row.left.length
|
|
103
|
+
if (row.left.length >= leftWidth) {
|
|
104
104
|
return [
|
|
105
105
|
`${firstIndent}${row.left}`,
|
|
106
106
|
...rightLines.map((line) => `${continuationIndent}${line}`)
|
|
@@ -159,7 +159,7 @@ export function formatColumns(opts) {
|
|
|
159
159
|
return [`${firstIndent}${row.left}`];
|
|
160
160
|
}
|
|
161
161
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
162
|
-
if (visibleWidth(row.left)
|
|
162
|
+
if (visibleWidth(row.left) >= leftWidth) {
|
|
163
163
|
return [
|
|
164
164
|
`${firstIndent}${row.left}`,
|
|
165
165
|
...rightLines.map((line) => `${continuationIndent}${line}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.61",
|
|
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.
|
|
50
|
+
"toolcraft-schema": "0.0.61",
|
|
51
51
|
"commander": "^14.0.3",
|
|
52
52
|
"fast-string-width": "^3.0.2",
|
|
53
53
|
"fast-wrap-ansi": "^0.2.0",
|