stitchkit 0.53.1 → 0.54.0
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/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/contract/define.d.ts +27 -0
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/{index-6j4j5wf9.js → index-7vb44hy8.js} +404 -72
- package/dist/{index-yydqk4fh.js → index-b15rtha1.js} +30 -1
- package/dist/{index-pdv1mjjr.js → index-z7761jk2.js} +15 -38
- package/dist/node.js +2 -2
- package/dist/observability/event.d.ts +2 -16
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/process-signal-common.d.ts +6 -0
- package/dist/server/process-signal-common.d.ts.map +1 -0
- package/dist/server/process-signals.d.ts.map +1 -1
- package/dist/tools/cli-command.d.ts +29 -0
- package/dist/tools/cli-command.d.ts.map +1 -0
- package/dist/tools/cli.d.ts +11 -6
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts +17 -0
- package/dist/tools/define-download-tool.d.ts.map +1 -0
- package/dist/tools/define-upload-tool.d.ts +14 -0
- package/dist/tools/define-upload-tool.d.ts.map +1 -0
- package/dist/tools/define-view-file-tool.d.ts +10 -0
- package/dist/tools/define-view-file-tool.d.ts.map +1 -0
- package/dist/tools/define-wait-tool.d.ts +22 -0
- package/dist/tools/define-wait-tool.d.ts.map +1 -0
- package/dist/tools/download-core.d.ts +25 -0
- package/dist/tools/download-core.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +3 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/mcp-round.d.ts +0 -1
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mcp-stdio-signals.d.ts +33 -0
- package/dist/tools/mcp-stdio-signals.d.ts.map +1 -0
- package/dist/tools/mount-download.d.ts +5 -0
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount-wait.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +15 -0
- package/dist/tools/native-definition.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +5 -4
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/upload-core.d.ts +3 -0
- package/dist/tools/upload-core.d.ts.map +1 -0
- package/dist/tools/view-file.d.ts +80 -20
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools/wait-core.d.ts +10 -1
- package/dist/tools/wait-core.d.ts.map +1 -1
- package/dist/tools.d.ts +10 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +570 -379
- package/llms-full.txt +312 -35
- package/package.json +1 -1
|
@@ -671,7 +671,12 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
|
|
|
671
671
|
}
|
|
672
672
|
try {
|
|
673
673
|
const resolved = await extension.resolve({ ...rawArgs, ...parsed.data });
|
|
674
|
-
callContext = {
|
|
674
|
+
callContext = {
|
|
675
|
+
...context,
|
|
676
|
+
...resolved,
|
|
677
|
+
source: context.source,
|
|
678
|
+
...context.mcp !== undefined && { mcp: context.mcp }
|
|
679
|
+
};
|
|
675
680
|
hookContext = callContext;
|
|
676
681
|
callArgs = Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extensionKeys.has(key)));
|
|
677
682
|
} catch (err) {
|
|
@@ -1020,6 +1025,100 @@ function formatToolError(result, toolName, errorHint) {
|
|
|
1020
1025
|
return err;
|
|
1021
1026
|
}
|
|
1022
1027
|
|
|
1028
|
+
// src/tools/runtime-tool.ts
|
|
1029
|
+
function defineRuntimeTool(definition) {
|
|
1030
|
+
if (definition.transports?.length === 0) {
|
|
1031
|
+
throw new Error(`Runtime tool "${definition.name}" must expose at least one transport`);
|
|
1032
|
+
}
|
|
1033
|
+
return definition;
|
|
1034
|
+
}
|
|
1035
|
+
function createRuntimeToolFactory(config) {
|
|
1036
|
+
function parseContext(context) {
|
|
1037
|
+
const parsed = config.context.parse(context);
|
|
1038
|
+
return {
|
|
1039
|
+
...context,
|
|
1040
|
+
...parsed,
|
|
1041
|
+
params: undefined,
|
|
1042
|
+
input: context.input,
|
|
1043
|
+
...context.mcp !== undefined && { mcp: context.mcp }
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function define(definition) {
|
|
1047
|
+
if (definition.output !== undefined) {
|
|
1048
|
+
const { action: action2, method: method2, meta: meta2, handler: handler2, ...tool2 } = definition;
|
|
1049
|
+
const identity2 = {
|
|
1050
|
+
serviceName: config.serviceName,
|
|
1051
|
+
action: action2,
|
|
1052
|
+
method: method2,
|
|
1053
|
+
...config.scope !== undefined && { scope: config.scope },
|
|
1054
|
+
...(meta2 ?? config.meta) !== undefined && { meta: meta2 ?? config.meta }
|
|
1055
|
+
};
|
|
1056
|
+
return defineRuntimeTool({
|
|
1057
|
+
...tool2,
|
|
1058
|
+
identity: identity2,
|
|
1059
|
+
handler: (context) => handler2(parseContext(context))
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
const { action, method, meta, handler, ...tool } = definition;
|
|
1063
|
+
const identity = {
|
|
1064
|
+
serviceName: config.serviceName,
|
|
1065
|
+
action,
|
|
1066
|
+
method,
|
|
1067
|
+
...config.scope !== undefined && { scope: config.scope },
|
|
1068
|
+
...(meta ?? config.meta) !== undefined && { meta: meta ?? config.meta }
|
|
1069
|
+
};
|
|
1070
|
+
return defineRuntimeTool({
|
|
1071
|
+
...tool,
|
|
1072
|
+
identity,
|
|
1073
|
+
handler: (context) => handler(parseContext(context))
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return { define };
|
|
1077
|
+
}
|
|
1078
|
+
function runtimeToolSupports(definition, transport) {
|
|
1079
|
+
if (definition.transports?.length === 0) {
|
|
1080
|
+
throw new Error(`Runtime tool "${definition.name}" must expose at least one transport`);
|
|
1081
|
+
}
|
|
1082
|
+
if (!definition.transports)
|
|
1083
|
+
return transport !== "CLI";
|
|
1084
|
+
return definition.transports.includes(transport);
|
|
1085
|
+
}
|
|
1086
|
+
function runtimeToolIdentity(definition) {
|
|
1087
|
+
return {
|
|
1088
|
+
method: definition.identity.method,
|
|
1089
|
+
desc: definition.description,
|
|
1090
|
+
serviceName: definition.identity.serviceName,
|
|
1091
|
+
key: definition.identity.action,
|
|
1092
|
+
toolName: definition.name,
|
|
1093
|
+
scope: definition.identity.scope,
|
|
1094
|
+
meta: definition.identity.meta,
|
|
1095
|
+
annotations: definition.annotations,
|
|
1096
|
+
ui: definition.ui,
|
|
1097
|
+
mcp: definition.mcp
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function runtimeToolMountable(definition, assertName = true) {
|
|
1101
|
+
if (assertName) {
|
|
1102
|
+
assertToolName(definition.name, definition.identity.serviceName, definition.identity.action);
|
|
1103
|
+
}
|
|
1104
|
+
const method = {
|
|
1105
|
+
...runtimeToolIdentity(definition),
|
|
1106
|
+
inputSchema: definition.input,
|
|
1107
|
+
outputSchema: definition.output,
|
|
1108
|
+
handler: definition.handler
|
|
1109
|
+
};
|
|
1110
|
+
return {
|
|
1111
|
+
method,
|
|
1112
|
+
name: definition.name,
|
|
1113
|
+
argumentSchema: definition.input,
|
|
1114
|
+
presentationSchema: buildToolPresentationSchema({
|
|
1115
|
+
inputSchema: definition.input,
|
|
1116
|
+
unrepresentable: "any"
|
|
1117
|
+
}),
|
|
1118
|
+
shouldExtend: false
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1023
1122
|
// src/tools/cli-args.ts
|
|
1024
1123
|
import { z as z5 } from "zod";
|
|
1025
1124
|
var NUMERIC_VALUE = /^-(\d+\.?\d*|\.\d+)$/;
|
|
@@ -1341,6 +1440,50 @@ function parseCliArgs(argv, schema, config = {}) {
|
|
|
1341
1440
|
return { toolArgs, options };
|
|
1342
1441
|
}
|
|
1343
1442
|
|
|
1443
|
+
// src/tools/cli-command.ts
|
|
1444
|
+
function defineCliCommand(definition) {
|
|
1445
|
+
return definition;
|
|
1446
|
+
}
|
|
1447
|
+
function cliCommandPresentationSchema(definition) {
|
|
1448
|
+
return buildToolPresentationSchema({
|
|
1449
|
+
inputSchema: definition.input,
|
|
1450
|
+
unrepresentable: "any"
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
async function executeCliCommand(definition, rawArgs, options, writers, coerceJson) {
|
|
1454
|
+
let parsed;
|
|
1455
|
+
try {
|
|
1456
|
+
parsed = definition.input.safeParse(coerceJson ? coerceJsonArgs(rawArgs, definition.input) : rawArgs);
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
return toolResultFromError(error);
|
|
1459
|
+
}
|
|
1460
|
+
if (!parsed.success) {
|
|
1461
|
+
return {
|
|
1462
|
+
ok: false,
|
|
1463
|
+
code: "VALIDATION_ERROR",
|
|
1464
|
+
details: { message: `Invalid input: ${formatZodError(parsed.error)}` }
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
try {
|
|
1468
|
+
const data = await definition.handler({
|
|
1469
|
+
input: parsed.data,
|
|
1470
|
+
options,
|
|
1471
|
+
...writers
|
|
1472
|
+
});
|
|
1473
|
+
const checked = validateDeclaredOutput(definition.output, data);
|
|
1474
|
+
if (!checked.ok) {
|
|
1475
|
+
return {
|
|
1476
|
+
ok: false,
|
|
1477
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
1478
|
+
details: { message: checked.message }
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
return { ok: true, data: checked.data };
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
return toolResultFromError(error);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1344
1487
|
// src/tools/cli-format.ts
|
|
1345
1488
|
var DEFAULT_EXIT_CODES = {
|
|
1346
1489
|
VALIDATION_ERROR: 1,
|
|
@@ -1372,7 +1515,30 @@ function emitResult(result, writers, opts) {
|
|
|
1372
1515
|
// src/tools/wait-core.ts
|
|
1373
1516
|
var DEFAULT_BACKOFF = [2, 3, 5, 5, 8, 10];
|
|
1374
1517
|
var DEFAULT_TIMEOUT = 600;
|
|
1375
|
-
var defaultSleep = (ms
|
|
1518
|
+
var defaultSleep = (ms, signal) => {
|
|
1519
|
+
signal?.throwIfAborted();
|
|
1520
|
+
return new Promise((resolve, reject) => {
|
|
1521
|
+
let settled = false;
|
|
1522
|
+
const settle = (action) => {
|
|
1523
|
+
if (settled)
|
|
1524
|
+
return;
|
|
1525
|
+
settled = true;
|
|
1526
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1527
|
+
action();
|
|
1528
|
+
};
|
|
1529
|
+
const timer = setTimeout(() => {
|
|
1530
|
+
settle(resolve);
|
|
1531
|
+
}, ms);
|
|
1532
|
+
timer.unref?.();
|
|
1533
|
+
const onAbort = () => {
|
|
1534
|
+
clearTimeout(timer);
|
|
1535
|
+
settle(() => reject(signal?.reason ?? new Error("aborted")));
|
|
1536
|
+
};
|
|
1537
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1538
|
+
if (signal?.aborted)
|
|
1539
|
+
onAbort();
|
|
1540
|
+
});
|
|
1541
|
+
};
|
|
1376
1542
|
async function pollUntil(params) {
|
|
1377
1543
|
const backoff = params.backoff?.length ? params.backoff : DEFAULT_BACKOFF;
|
|
1378
1544
|
const lastBackoff = backoff[backoff.length - 1] ?? 5;
|
|
@@ -1380,7 +1546,9 @@ async function pollUntil(params) {
|
|
|
1380
1546
|
const sleep = params.sleepFn ?? defaultSleep;
|
|
1381
1547
|
const startedAt = Date.now();
|
|
1382
1548
|
for (let attempt = 0;; attempt++) {
|
|
1549
|
+
params.signal?.throwIfAborted();
|
|
1383
1550
|
const state = await params.poll();
|
|
1551
|
+
params.signal?.throwIfAborted();
|
|
1384
1552
|
if (params.done(state))
|
|
1385
1553
|
return { state, timedOut: false };
|
|
1386
1554
|
const elapsedSec = (Date.now() - startedAt) / 1000;
|
|
@@ -1388,9 +1556,20 @@ async function pollUntil(params) {
|
|
|
1388
1556
|
if (elapsedSec >= timeoutSec)
|
|
1389
1557
|
return { state, timedOut: true };
|
|
1390
1558
|
const waitSec = backoff[Math.min(attempt, backoff.length - 1)] ?? lastBackoff;
|
|
1391
|
-
await sleep(waitSec * 1000);
|
|
1559
|
+
await sleep(waitSec * 1000, params.signal);
|
|
1392
1560
|
}
|
|
1393
1561
|
}
|
|
1562
|
+
function runWaitOperation(params) {
|
|
1563
|
+
return pollUntil({
|
|
1564
|
+
poll: () => params.poll(params.input, params.signal),
|
|
1565
|
+
done: params.done,
|
|
1566
|
+
backoff: params.backoff,
|
|
1567
|
+
timeoutSec: params.timeoutSec,
|
|
1568
|
+
sleepFn: params.sleepFn,
|
|
1569
|
+
onTick: params.onTick,
|
|
1570
|
+
signal: params.signal
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1394
1573
|
|
|
1395
1574
|
// src/tools/cli-wait.ts
|
|
1396
1575
|
var DEFAULT_TIMEOUT2 = 600;
|
|
@@ -1423,6 +1602,7 @@ async function pollUntilDone(params) {
|
|
|
1423
1602
|
}
|
|
1424
1603
|
|
|
1425
1604
|
// src/tools/cli.ts
|
|
1605
|
+
import { writeSync } from "node:fs";
|
|
1426
1606
|
import { basename, resolve } from "node:path";
|
|
1427
1607
|
import process from "node:process";
|
|
1428
1608
|
|
|
@@ -1732,6 +1912,53 @@ async function writeDownload(root, target, data) {
|
|
|
1732
1912
|
await writeFile(target, data);
|
|
1733
1913
|
}
|
|
1734
1914
|
|
|
1915
|
+
// src/tools/surface.ts
|
|
1916
|
+
function duplicateLabel(transport) {
|
|
1917
|
+
if (transport === "MCP")
|
|
1918
|
+
return "MCP tool name";
|
|
1919
|
+
if (transport === "AGENT")
|
|
1920
|
+
return "agent tool name";
|
|
1921
|
+
return "CLI command";
|
|
1922
|
+
}
|
|
1923
|
+
function collectToolSurface({
|
|
1924
|
+
surface,
|
|
1925
|
+
transport,
|
|
1926
|
+
assertUniqueNames = true,
|
|
1927
|
+
...collectConfig
|
|
1928
|
+
}) {
|
|
1929
|
+
const entries = [];
|
|
1930
|
+
const names = new Set;
|
|
1931
|
+
const append = (entry) => {
|
|
1932
|
+
if (assertUniqueNames) {
|
|
1933
|
+
assertUniqueToolName(entry.mountable.name, names.has(entry.mountable.name), duplicateLabel(transport));
|
|
1934
|
+
}
|
|
1935
|
+
names.add(entry.mountable.name);
|
|
1936
|
+
entries.push(entry);
|
|
1937
|
+
};
|
|
1938
|
+
for (const service of surface.services ?? []) {
|
|
1939
|
+
for (const mountable of collectTools(service, transport, collectConfig)) {
|
|
1940
|
+
append({
|
|
1941
|
+
kind: "contract",
|
|
1942
|
+
service: service.name,
|
|
1943
|
+
action: mountable.method.key,
|
|
1944
|
+
mountable
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
for (const definition of surface.runtimeTools ?? []) {
|
|
1949
|
+
if (!runtimeToolSupports(definition, transport))
|
|
1950
|
+
continue;
|
|
1951
|
+
append({
|
|
1952
|
+
kind: "runtime",
|
|
1953
|
+
service: definition.identity.serviceName,
|
|
1954
|
+
action: definition.identity.action,
|
|
1955
|
+
mountable: runtimeToolMountable(definition, collectConfig.assertNames),
|
|
1956
|
+
definition
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return entries;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1735
1962
|
// src/tools/cli.ts
|
|
1736
1963
|
var GLOBAL_OPTIONS = [
|
|
1737
1964
|
["--json", "Emit raw JSON on stdout (for piping / scripts)"],
|
|
@@ -1809,7 +2036,7 @@ function collectPassthrough(toolArgs, field, knownKeys) {
|
|
|
1809
2036
|
const base = passthroughBase(toolArgs[field]);
|
|
1810
2037
|
toolArgs[field] = base ? { ...base, ...bag } : bag;
|
|
1811
2038
|
}
|
|
1812
|
-
function renderTopHelp(name, version,
|
|
2039
|
+
function renderTopHelp(name, version, commands) {
|
|
1813
2040
|
const lines = [
|
|
1814
2041
|
`${name} ${version}`,
|
|
1815
2042
|
"",
|
|
@@ -1817,9 +2044,9 @@ function renderTopHelp(name, version, tools) {
|
|
|
1817
2044
|
"",
|
|
1818
2045
|
"Commands:"
|
|
1819
2046
|
];
|
|
1820
|
-
const width = Math.max(0, ...[...
|
|
1821
|
-
for (const [command,
|
|
1822
|
-
lines.push(` ${padRight(command, width)} ${summarize(
|
|
2047
|
+
const width = Math.max(0, ...[...commands.keys()].map((key) => key.length));
|
|
2048
|
+
for (const [command, descriptor] of commands) {
|
|
2049
|
+
lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}`);
|
|
1823
2050
|
}
|
|
1824
2051
|
lines.push("", "Global options:");
|
|
1825
2052
|
const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
|
|
@@ -1830,9 +2057,9 @@ function renderTopHelp(name, version, tools) {
|
|
|
1830
2057
|
`)}
|
|
1831
2058
|
`;
|
|
1832
2059
|
}
|
|
1833
|
-
function renderCommandHelp(name, command,
|
|
1834
|
-
const lines = [
|
|
1835
|
-
const fields = jsonSchemaFields(
|
|
2060
|
+
function renderCommandHelp(name, command, descriptor) {
|
|
2061
|
+
const lines = [descriptor.description, "", `Usage: ${name} ${command} [args] [--flags]`, ""];
|
|
2062
|
+
const fields = jsonSchemaFields(descriptor.presentationSchema);
|
|
1836
2063
|
if (fields.length > 0) {
|
|
1837
2064
|
lines.push("Arguments:");
|
|
1838
2065
|
const width = Math.max(...fields.map((f) => f.name.length));
|
|
@@ -1847,6 +2074,55 @@ function renderCommandHelp(name, command, tool) {
|
|
|
1847
2074
|
`)}
|
|
1848
2075
|
`;
|
|
1849
2076
|
}
|
|
2077
|
+
function managedDescriptor(tool) {
|
|
2078
|
+
return {
|
|
2079
|
+
description: tool.method.desc,
|
|
2080
|
+
argumentSchema: tool.argumentSchema,
|
|
2081
|
+
presentationSchema: tool.presentationSchema
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
function nativeDescriptor(definition) {
|
|
2085
|
+
return {
|
|
2086
|
+
description: definition.description,
|
|
2087
|
+
argumentSchema: definition.input,
|
|
2088
|
+
presentationSchema: cliCommandPresentationSchema(definition)
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
function assertCommandShape(name, descriptor, exists) {
|
|
2092
|
+
assertUniqueToolName(name, exists, "CLI command");
|
|
2093
|
+
if (name === "help" || name === "version") {
|
|
2094
|
+
throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
|
|
2095
|
+
}
|
|
2096
|
+
const conflicting = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name).filter((field) => RESERVED_CLI_OPTIONS.has(field));
|
|
2097
|
+
if (conflicting.length > 0) {
|
|
2098
|
+
throw new Error(`[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
async function prepareInvocation(command, commandArgv, descriptor, config, readStdin) {
|
|
2102
|
+
let parsed;
|
|
2103
|
+
try {
|
|
2104
|
+
parsed = parseCliArgs(commandArgv, descriptor.argumentSchema, {
|
|
2105
|
+
allowUnknown: config.passthrough?.[command] !== undefined,
|
|
2106
|
+
knownFields: jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name)
|
|
2107
|
+
});
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
if (!(error instanceof CliArgumentError))
|
|
2110
|
+
throw error;
|
|
2111
|
+
return { ok: false, message: error.message };
|
|
2112
|
+
}
|
|
2113
|
+
const { toolArgs, options } = parsed;
|
|
2114
|
+
const firstUnset = jsonSchemaFields(descriptor.presentationSchema).find((field) => field.required && !(field.name in toolArgs) && field.schema.type !== "boolean");
|
|
2115
|
+
if (firstUnset) {
|
|
2116
|
+
const piped = await readStdin();
|
|
2117
|
+
if (piped !== null)
|
|
2118
|
+
toolArgs[firstUnset.name] = piped;
|
|
2119
|
+
}
|
|
2120
|
+
const passthroughField = config.passthrough?.[command];
|
|
2121
|
+
if (passthroughField) {
|
|
2122
|
+
collectPassthrough(toolArgs, passthroughField, objectShapeKeys(descriptor.argumentSchema));
|
|
2123
|
+
}
|
|
2124
|
+
return { ok: true, toolArgs, options };
|
|
2125
|
+
}
|
|
1850
2126
|
var DEFAULT_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
1851
2127
|
async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes, timeoutMs) {
|
|
1852
2128
|
const root = resolve(dir);
|
|
@@ -1873,71 +2149,133 @@ async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes
|
|
|
1873
2149
|
return succeeded;
|
|
1874
2150
|
}
|
|
1875
2151
|
async function createCli(config) {
|
|
1876
|
-
const
|
|
1877
|
-
|
|
2152
|
+
const writeFd = (fd, text) => {
|
|
2153
|
+
try {
|
|
2154
|
+
writeSync(fd, text);
|
|
2155
|
+
} catch {
|
|
2156
|
+
(fd === 1 ? process.stdout : process.stderr).write(text);
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
const stdout = config.stdout ?? ((text) => writeFd(1, text));
|
|
2160
|
+
const stderr = config.stderr ?? ((text) => writeFd(2, text));
|
|
1878
2161
|
const exit = config.exit ?? ((code) => void process.exit(code));
|
|
1879
2162
|
const argv = config.argv ?? process.argv.slice(2);
|
|
1880
2163
|
const readStdin = config.stdin ?? readPipedStdin;
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
const context = config.context?.(auth);
|
|
1884
|
-
const tools = new Map;
|
|
1885
|
-
for (const service of services) {
|
|
1886
|
-
for (const mountable of collectTools(service, "CLI")) {
|
|
1887
|
-
assertUniqueToolName(mountable.name, tools.has(mountable.name), "CLI command");
|
|
1888
|
-
if (mountable.name === "help" || mountable.name === "version") {
|
|
1889
|
-
throw new Error(`[stitchkit] CLI command "${mountable.name}" is reserved`);
|
|
1890
|
-
}
|
|
1891
|
-
const conflicting = jsonSchemaFields(mountable.presentationSchema).map((field) => field.name).filter((name) => RESERVED_CLI_OPTIONS.has(name));
|
|
1892
|
-
if (conflicting.length > 0) {
|
|
1893
|
-
throw new Error(`[stitchkit] CLI command "${mountable.name}" declares reserved option field(s): ${conflicting.join(", ")}`);
|
|
1894
|
-
}
|
|
1895
|
-
tools.set(mountable.name, mountable);
|
|
1896
|
-
}
|
|
2164
|
+
if (config.auth !== undefined && config.resolveAuth !== undefined) {
|
|
2165
|
+
throw new Error("[stitchkit] createCli: use either auth or resolveAuth, not both");
|
|
1897
2166
|
}
|
|
1898
|
-
const
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
});
|
|
1906
|
-
const [command, ...commandArgv] = argv;
|
|
1907
|
-
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
1908
|
-
stdout(renderTopHelp(config.name, config.version, tools));
|
|
1909
|
-
return exit(0);
|
|
2167
|
+
const nativeCommands = new Map;
|
|
2168
|
+
const nativeHelp = new Map;
|
|
2169
|
+
for (const definition of config.commands ?? []) {
|
|
2170
|
+
const descriptor2 = nativeDescriptor(definition);
|
|
2171
|
+
assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name));
|
|
2172
|
+
nativeCommands.set(definition.name, definition);
|
|
2173
|
+
nativeHelp.set(definition.name, descriptor2);
|
|
1910
2174
|
}
|
|
2175
|
+
const [command, ...commandArgv] = argv;
|
|
1911
2176
|
if (command === "--version" || command === "version") {
|
|
1912
2177
|
stdout(`${config.name} ${config.version}
|
|
1913
2178
|
`);
|
|
1914
2179
|
return exit(0);
|
|
1915
2180
|
}
|
|
1916
|
-
const
|
|
2181
|
+
const beforeSeparator = commandArgv.indexOf("--") === -1 ? commandArgv : commandArgv.slice(0, commandArgv.indexOf("--"));
|
|
2182
|
+
const helpRequested = beforeSeparator.includes("--help") || beforeSeparator.includes("-h");
|
|
2183
|
+
const native = command === undefined ? undefined : nativeCommands.get(command);
|
|
2184
|
+
if (native && command !== undefined) {
|
|
2185
|
+
const descriptor2 = nativeHelp.get(command);
|
|
2186
|
+
if (!descriptor2)
|
|
2187
|
+
throw new Error("[stitchkit] native CLI descriptor invariant failed");
|
|
2188
|
+
if (helpRequested) {
|
|
2189
|
+
stdout(renderCommandHelp(config.name, command, descriptor2));
|
|
2190
|
+
return exit(0);
|
|
2191
|
+
}
|
|
2192
|
+
const prepared2 = await prepareInvocation(command, commandArgv, descriptor2, config, readStdin);
|
|
2193
|
+
if (!prepared2.ok) {
|
|
2194
|
+
stderr(`${prepared2.message}
|
|
2195
|
+
`);
|
|
2196
|
+
return exit(2);
|
|
2197
|
+
}
|
|
2198
|
+
const { toolArgs: toolArgs2, options: options2 } = prepared2;
|
|
2199
|
+
if (options2.help) {
|
|
2200
|
+
stdout(renderCommandHelp(config.name, command, descriptor2));
|
|
2201
|
+
return exit(0);
|
|
2202
|
+
}
|
|
2203
|
+
if (options2.wait) {
|
|
2204
|
+
stderr(`--wait is not configured for native command "${command}"
|
|
2205
|
+
`);
|
|
2206
|
+
return exit(2);
|
|
2207
|
+
}
|
|
2208
|
+
if (options2.waitTimeout !== undefined) {
|
|
2209
|
+
stderr(`--wait-timeout requires --wait
|
|
2210
|
+
`);
|
|
2211
|
+
return exit(2);
|
|
2212
|
+
}
|
|
2213
|
+
if (options2.outputDir !== undefined) {
|
|
2214
|
+
stderr(`--output-dir is not configured for native commands
|
|
2215
|
+
`);
|
|
2216
|
+
return exit(2);
|
|
2217
|
+
}
|
|
2218
|
+
if (options2.dryRun) {
|
|
2219
|
+
stdout(`${JSON.stringify({ command, args: toolArgs2 }, null, 2)}
|
|
2220
|
+
`);
|
|
2221
|
+
return exit(0);
|
|
2222
|
+
}
|
|
2223
|
+
const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true);
|
|
2224
|
+
const exitCode2 = emitResult(result2, { stdout, stderr }, {
|
|
2225
|
+
json: options2.json,
|
|
2226
|
+
toolName: command,
|
|
2227
|
+
errorHint: config.errorHint,
|
|
2228
|
+
exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
|
|
2229
|
+
});
|
|
2230
|
+
return exit(exitCode2);
|
|
2231
|
+
}
|
|
2232
|
+
let authPromise;
|
|
2233
|
+
const resolveIdentity = () => {
|
|
2234
|
+
authPromise ??= Promise.resolve(config.resolveAuth ? config.resolveAuth() : config.auth);
|
|
2235
|
+
return authPromise;
|
|
2236
|
+
};
|
|
2237
|
+
const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
|
|
2238
|
+
const buildManagedSurface = async (forExecution) => {
|
|
2239
|
+
const auth = dynamicSurface || forExecution ? await resolveIdentity() : undefined;
|
|
2240
|
+
const services = typeof config.services === "function" ? config.services(auth) : config.services ?? [];
|
|
2241
|
+
const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools ?? [];
|
|
2242
|
+
const tools = new Map;
|
|
2243
|
+
const help = new Map(nativeHelp);
|
|
2244
|
+
for (const { mountable } of collectToolSurface({
|
|
2245
|
+
surface: { services, runtimeTools },
|
|
2246
|
+
transport: "CLI"
|
|
2247
|
+
})) {
|
|
2248
|
+
const descriptor2 = managedDescriptor(mountable);
|
|
2249
|
+
assertCommandShape(mountable.name, descriptor2, help.has(mountable.name));
|
|
2250
|
+
tools.set(mountable.name, mountable);
|
|
2251
|
+
help.set(mountable.name, descriptor2);
|
|
2252
|
+
}
|
|
2253
|
+
return { auth, help, tools };
|
|
2254
|
+
};
|
|
2255
|
+
const topLevelHelp = command === undefined || command === "help" || command === "--help" || command === "-h";
|
|
2256
|
+
const managed = await buildManagedSurface(!topLevelHelp && !helpRequested);
|
|
2257
|
+
if (topLevelHelp) {
|
|
2258
|
+
stdout(renderTopHelp(config.name, config.version, managed.help));
|
|
2259
|
+
return exit(0);
|
|
2260
|
+
}
|
|
2261
|
+
const tool = managed.tools.get(command);
|
|
1917
2262
|
if (!tool) {
|
|
1918
2263
|
stderr(`Unknown command "${command}". Run "${config.name} --help" for the command list.
|
|
1919
2264
|
`);
|
|
1920
2265
|
return exit(1);
|
|
1921
2266
|
}
|
|
1922
|
-
const
|
|
1923
|
-
if (
|
|
1924
|
-
stdout(renderCommandHelp(config.name, command,
|
|
2267
|
+
const descriptor = managedDescriptor(tool);
|
|
2268
|
+
if (helpRequested) {
|
|
2269
|
+
stdout(renderCommandHelp(config.name, command, descriptor));
|
|
1925
2270
|
return exit(0);
|
|
1926
2271
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
allowUnknown: config.passthrough?.[command] !== undefined,
|
|
1931
|
-
knownFields: jsonSchemaFields(tool.presentationSchema).map((field) => field.name)
|
|
1932
|
-
});
|
|
1933
|
-
} catch (error) {
|
|
1934
|
-
if (!(error instanceof CliArgumentError))
|
|
1935
|
-
throw error;
|
|
1936
|
-
stderr(`${error.message}
|
|
2272
|
+
const prepared = await prepareInvocation(command, commandArgv, descriptor, config, readStdin);
|
|
2273
|
+
if (!prepared.ok) {
|
|
2274
|
+
stderr(`${prepared.message}
|
|
1937
2275
|
`);
|
|
1938
2276
|
return exit(2);
|
|
1939
2277
|
}
|
|
1940
|
-
const { toolArgs, options } =
|
|
2278
|
+
const { toolArgs, options } = prepared;
|
|
1941
2279
|
if (options.wait && !config.wait?.[command]) {
|
|
1942
2280
|
stderr(`--wait is not configured for command "${command}"
|
|
1943
2281
|
`);
|
|
@@ -1954,25 +2292,22 @@ async function createCli(config) {
|
|
|
1954
2292
|
return exit(2);
|
|
1955
2293
|
}
|
|
1956
2294
|
if (options.help) {
|
|
1957
|
-
stdout(renderCommandHelp(config.name, command,
|
|
2295
|
+
stdout(renderCommandHelp(config.name, command, descriptor));
|
|
1958
2296
|
return exit(0);
|
|
1959
2297
|
}
|
|
1960
|
-
const fields = jsonSchemaFields(safeInputSchema(tool));
|
|
1961
|
-
const firstUnset = fields.find((f) => !(f.name in toolArgs) && f.schema.type !== "boolean");
|
|
1962
|
-
if (firstUnset) {
|
|
1963
|
-
const piped = await readStdin();
|
|
1964
|
-
if (piped !== null)
|
|
1965
|
-
toolArgs[firstUnset.name] = piped;
|
|
1966
|
-
}
|
|
1967
|
-
const passthroughField = config.passthrough?.[command];
|
|
1968
|
-
if (passthroughField) {
|
|
1969
|
-
collectPassthrough(toolArgs, passthroughField, objectShapeKeys(tool.argumentSchema));
|
|
1970
|
-
}
|
|
1971
2298
|
if (options.dryRun) {
|
|
1972
2299
|
stdout(`${JSON.stringify({ command, args: toolArgs }, null, 2)}
|
|
1973
2300
|
`);
|
|
1974
2301
|
return exit(0);
|
|
1975
2302
|
}
|
|
2303
|
+
const runTool = createToolRunner({
|
|
2304
|
+
source: "cli",
|
|
2305
|
+
context: config.context?.(managed.auth),
|
|
2306
|
+
hooks: config.hooks,
|
|
2307
|
+
lifecycle: config.lifecycle,
|
|
2308
|
+
errorHint: config.errorHint,
|
|
2309
|
+
coerceJsonArgs: config.coerceJsonArgs
|
|
2310
|
+
});
|
|
1976
2311
|
let result;
|
|
1977
2312
|
try {
|
|
1978
2313
|
result = await runTool(tool, toolArgs);
|
|
@@ -1985,7 +2320,7 @@ async function createCli(config) {
|
|
|
1985
2320
|
initial: result,
|
|
1986
2321
|
wait: waitConfig,
|
|
1987
2322
|
call: async (toolName, args) => {
|
|
1988
|
-
const pollTool = tools.get(toolName);
|
|
2323
|
+
const pollTool = managed.tools.get(toolName);
|
|
1989
2324
|
if (!pollTool) {
|
|
1990
2325
|
return {
|
|
1991
2326
|
ok: false,
|
|
@@ -2012,8 +2347,5 @@ async function createCli(config) {
|
|
|
2012
2347
|
});
|
|
2013
2348
|
return exit(downloadsOk ? exitCode : 1);
|
|
2014
2349
|
}
|
|
2015
|
-
function safeInputSchema(tool) {
|
|
2016
|
-
return tool.presentationSchema;
|
|
2017
|
-
}
|
|
2018
2350
|
|
|
2019
|
-
export { coerceJsonArgs, toolResultFromError, toolErrorFromResult, executeToolMethod, assertToolName, assertUniqueToolName, flattenToolJsonSchema, buildToolPresentationSchema, isObjectPresentationSchema, presentationMetadata, collectTools, createToolRunner, formatToolError, fetchGuarded, fetchPinnedDocument, readCapped, writeDownload, parseCliArgs, DEFAULT_EXIT_CODES, emitResult,
|
|
2351
|
+
export { coerceJsonArgs, toolResultFromError, toolErrorFromResult, executeToolMethod, assertToolName, assertUniqueToolName, flattenToolJsonSchema, buildToolPresentationSchema, isObjectPresentationSchema, presentationMetadata, collectTools, createToolRunner, formatToolError, defineRuntimeTool, createRuntimeToolFactory, collectToolSurface, fetchGuarded, fetchPinnedDocument, readCapped, writeDownload, parseCliArgs, defineCliCommand, DEFAULT_EXIT_CODES, emitResult, runWaitOperation, pollUntilDone, createCli };
|
|
@@ -246,4 +246,33 @@ function createImplementRegistry() {
|
|
|
246
246
|
return (contracts, handlers) => transportResult(bindRegistry(contracts, handlers));
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
// src/server/process-signal-common.ts
|
|
250
|
+
var DEFAULT_PROCESS_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
251
|
+
var defaultSignalSource = {
|
|
252
|
+
on: (signal, handler) => {
|
|
253
|
+
process.on(signal, handler);
|
|
254
|
+
},
|
|
255
|
+
off: (signal, handler) => {
|
|
256
|
+
process.off(signal, handler);
|
|
257
|
+
},
|
|
258
|
+
raiseDefault: (signal) => {
|
|
259
|
+
if (process.listenerCount(signal) > 0)
|
|
260
|
+
return false;
|
|
261
|
+
process.kill(process.pid, signal);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function reportSignalError(phase, error, onError) {
|
|
266
|
+
try {
|
|
267
|
+
onError?.(phase, error);
|
|
268
|
+
} catch {}
|
|
269
|
+
}
|
|
270
|
+
function guardSignalCallback(run, phase, onError) {
|
|
271
|
+
try {
|
|
272
|
+
run();
|
|
273
|
+
} catch (error) {
|
|
274
|
+
reportSignalError(phase, error, onError);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export { DEFAULT_CORS_ALLOW_HEADERS, DEFAULT_CORS_EXPOSE_HEADERS, assertCorsConfig, corsHeaders, corsPreflightResponse, defineMultipartStream, createMultipartStream, contractOnlyService, implement, createImplement, createScopedImplement, createScopedImplementRegistry, implementRegistry, createImplementRegistry, DEFAULT_PROCESS_SIGNALS, defaultSignalSource, reportSignalError, guardSignalCallback };
|