@zokizuan/satori-cli 0.1.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/assets/skills/satori-indexing/SKILL.md +36 -0
- package/assets/skills/satori-navigation/SKILL.md +36 -0
- package/assets/skills/satori-search/SKILL.md +36 -0
- package/dist/args.d.ts +54 -0
- package/dist/args.js +474 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +101 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +18 -0
- package/dist/format.d.ts +15 -0
- package/dist/format.js +100 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +301 -0
- package/dist/install.d.ts +30 -0
- package/dist/install.js +333 -0
- package/dist/managed-package.d.ts +11 -0
- package/dist/managed-package.js +39 -0
- package/dist/package-installability.d.ts +14 -0
- package/dist/package-installability.js +123 -0
- package/dist/resolve-server-entry.d.ts +2 -0
- package/dist/resolve-server-entry.js +17 -0
- package/package.json +52 -0
package/dist/args.js
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { CliError } from "./errors.js";
|
|
3
|
+
const RESERVED_SUBCOMMANDS = new Set(["tools", "tool", "help", "version", "install", "uninstall"]);
|
|
4
|
+
const PRIMITIVE_TYPES = new Set(["string", "number", "integer", "boolean"]);
|
|
5
|
+
function parsePositiveInteger(value, flagName) {
|
|
6
|
+
const parsed = Number.parseInt(value, 10);
|
|
7
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
8
|
+
throw new CliError("E_USAGE", `${flagName} must be a positive integer.`, 2);
|
|
9
|
+
}
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
function normalizeFlagToken(token) {
|
|
13
|
+
return token.replace(/^--/, "").replace(/-/g, "_");
|
|
14
|
+
}
|
|
15
|
+
function stripFlagPrefix(token) {
|
|
16
|
+
if (!token.startsWith("--")) {
|
|
17
|
+
throw new CliError("E_USAGE", `Expected a flag but found '${token}'.`, 2);
|
|
18
|
+
}
|
|
19
|
+
return token.slice(2);
|
|
20
|
+
}
|
|
21
|
+
function parseGlobalOptions(argv) {
|
|
22
|
+
const globals = {
|
|
23
|
+
startupTimeoutMs: 180000,
|
|
24
|
+
callTimeoutMs: 600000,
|
|
25
|
+
format: "json",
|
|
26
|
+
debug: false,
|
|
27
|
+
};
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < argv.length) {
|
|
30
|
+
const token = argv[i];
|
|
31
|
+
switch (token) {
|
|
32
|
+
case "--startup-timeout-ms": {
|
|
33
|
+
const next = argv[i + 1];
|
|
34
|
+
if (!next) {
|
|
35
|
+
throw new CliError("E_USAGE", "Missing value for --startup-timeout-ms.", 2);
|
|
36
|
+
}
|
|
37
|
+
globals.startupTimeoutMs = parsePositiveInteger(next, "--startup-timeout-ms");
|
|
38
|
+
i += 2;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "--call-timeout-ms": {
|
|
42
|
+
const next = argv[i + 1];
|
|
43
|
+
if (!next) {
|
|
44
|
+
throw new CliError("E_USAGE", "Missing value for --call-timeout-ms.", 2);
|
|
45
|
+
}
|
|
46
|
+
globals.callTimeoutMs = parsePositiveInteger(next, "--call-timeout-ms");
|
|
47
|
+
i += 2;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "--format": {
|
|
51
|
+
const next = argv[i + 1];
|
|
52
|
+
if (!next || (next !== "json" && next !== "text")) {
|
|
53
|
+
throw new CliError("E_USAGE", "--format must be one of: json, text.", 2);
|
|
54
|
+
}
|
|
55
|
+
globals.format = next;
|
|
56
|
+
i += 2;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "--debug": {
|
|
60
|
+
globals.debug = true;
|
|
61
|
+
i += 1;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
return {
|
|
66
|
+
globals,
|
|
67
|
+
rest: argv.slice(i),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { globals, rest: [] };
|
|
73
|
+
}
|
|
74
|
+
function parseRawArgsMode(args) {
|
|
75
|
+
let rawArgsMode = { kind: "none" };
|
|
76
|
+
const remaining = [];
|
|
77
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
78
|
+
const token = args[i];
|
|
79
|
+
if (token === "--args-json") {
|
|
80
|
+
if (rawArgsMode.kind !== "none") {
|
|
81
|
+
throw new CliError("E_USAGE", "Use only one of --args-json or --args-file.", 2);
|
|
82
|
+
}
|
|
83
|
+
const next = args[i + 1];
|
|
84
|
+
if (!next) {
|
|
85
|
+
throw new CliError("E_USAGE", "Missing value for --args-json.", 2);
|
|
86
|
+
}
|
|
87
|
+
rawArgsMode = next === "@-"
|
|
88
|
+
? { kind: "stdin-json" }
|
|
89
|
+
: { kind: "json", value: next };
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (token === "--args-file") {
|
|
94
|
+
if (rawArgsMode.kind !== "none") {
|
|
95
|
+
throw new CliError("E_USAGE", "Use only one of --args-json or --args-file.", 2);
|
|
96
|
+
}
|
|
97
|
+
const next = args[i + 1];
|
|
98
|
+
if (!next) {
|
|
99
|
+
throw new CliError("E_USAGE", "Missing value for --args-file.", 2);
|
|
100
|
+
}
|
|
101
|
+
rawArgsMode = { kind: "file", path: next };
|
|
102
|
+
i += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
remaining.push(token);
|
|
106
|
+
}
|
|
107
|
+
if (rawArgsMode.kind !== "none" && remaining.length > 0) {
|
|
108
|
+
throw new CliError("E_USAGE", "Tool argument flags cannot be combined with --args-json/--args-file.", 2);
|
|
109
|
+
}
|
|
110
|
+
return { rawArgsMode, remaining };
|
|
111
|
+
}
|
|
112
|
+
function parseInstallCommand(kind, args) {
|
|
113
|
+
let client = "all";
|
|
114
|
+
let dryRun = false;
|
|
115
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
116
|
+
const token = args[i];
|
|
117
|
+
if (token === "--client") {
|
|
118
|
+
const next = args[i + 1];
|
|
119
|
+
if (next !== "all" && next !== "claude" && next !== "codex") {
|
|
120
|
+
throw new CliError("E_USAGE", "--client must be one of: all, claude, codex.", 2);
|
|
121
|
+
}
|
|
122
|
+
client = next;
|
|
123
|
+
i += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (token === "--dry-run") {
|
|
127
|
+
dryRun = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
throw new CliError("E_USAGE", `Unknown arguments for ${kind}: ${args.slice(i).join(" ")}`, 2);
|
|
131
|
+
}
|
|
132
|
+
return { kind, client, dryRun };
|
|
133
|
+
}
|
|
134
|
+
export function parseCliArgs(argv) {
|
|
135
|
+
const { globals, rest } = parseGlobalOptions(argv);
|
|
136
|
+
if (rest.length === 0 || rest[0] === "help" || rest.includes("--help") || rest.includes("-h")) {
|
|
137
|
+
return {
|
|
138
|
+
globals,
|
|
139
|
+
command: { kind: "help" }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (rest[0] === "version" || rest.includes("--version") || rest.includes("-v")) {
|
|
143
|
+
return {
|
|
144
|
+
globals,
|
|
145
|
+
command: { kind: "version" }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (rest[0] === "install") {
|
|
149
|
+
return {
|
|
150
|
+
globals,
|
|
151
|
+
command: parseInstallCommand("install", rest.slice(1))
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (rest[0] === "uninstall") {
|
|
155
|
+
return {
|
|
156
|
+
globals,
|
|
157
|
+
command: parseInstallCommand("uninstall", rest.slice(1))
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (rest[0] === "tools") {
|
|
161
|
+
if (rest.length === 2 && rest[1] === "list") {
|
|
162
|
+
return {
|
|
163
|
+
globals,
|
|
164
|
+
command: { kind: "tools-list" }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
throw new CliError("E_USAGE", "Unsupported tools subcommand. Use: tools list", 2);
|
|
168
|
+
}
|
|
169
|
+
if (rest[0] === "tool") {
|
|
170
|
+
if (rest[1] !== "call") {
|
|
171
|
+
throw new CliError("E_USAGE", "Unsupported tool subcommand. Use: tool call <toolName>", 2);
|
|
172
|
+
}
|
|
173
|
+
const toolName = rest[2];
|
|
174
|
+
if (!toolName) {
|
|
175
|
+
throw new CliError("E_USAGE", "Missing tool name. Use: tool call <toolName>", 2);
|
|
176
|
+
}
|
|
177
|
+
const { rawArgsMode, remaining } = parseRawArgsMode(rest.slice(3));
|
|
178
|
+
if (remaining.length > 0) {
|
|
179
|
+
throw new CliError("E_USAGE", `Unknown arguments for tool call: ${remaining.join(" ")}`, 2);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
globals,
|
|
183
|
+
command: {
|
|
184
|
+
kind: "tool-call",
|
|
185
|
+
toolName,
|
|
186
|
+
rawArgsMode
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (RESERVED_SUBCOMMANDS.has(rest[0])) {
|
|
191
|
+
throw new CliError("E_USAGE", `Unsupported command '${rest[0]}'.`, 2);
|
|
192
|
+
}
|
|
193
|
+
const toolName = rest[0];
|
|
194
|
+
const { rawArgsMode, remaining } = parseRawArgsMode(rest.slice(1));
|
|
195
|
+
return {
|
|
196
|
+
globals,
|
|
197
|
+
command: {
|
|
198
|
+
kind: "wrapper",
|
|
199
|
+
toolName,
|
|
200
|
+
rawArgsMode,
|
|
201
|
+
wrapperArgs: remaining
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function readStdin(stdin, timeoutMs) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const chunks = [];
|
|
208
|
+
let settled = false;
|
|
209
|
+
const timeout = setTimeout(() => {
|
|
210
|
+
if (settled) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
settled = true;
|
|
214
|
+
reject(new CliError("E_USAGE", "Timed out while reading stdin JSON for --args-json @-.", 2));
|
|
215
|
+
}, timeoutMs);
|
|
216
|
+
timeout.unref();
|
|
217
|
+
stdin.setEncoding("utf8");
|
|
218
|
+
stdin.on("data", (chunk) => {
|
|
219
|
+
chunks.push(String(chunk));
|
|
220
|
+
});
|
|
221
|
+
stdin.on("error", (error) => {
|
|
222
|
+
if (settled) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
settled = true;
|
|
226
|
+
clearTimeout(timeout);
|
|
227
|
+
reject(new CliError("E_USAGE", `Failed to read stdin: ${error.message}`, 2));
|
|
228
|
+
});
|
|
229
|
+
stdin.on("end", () => {
|
|
230
|
+
if (settled) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
settled = true;
|
|
234
|
+
clearTimeout(timeout);
|
|
235
|
+
resolve(chunks.join(""));
|
|
236
|
+
});
|
|
237
|
+
stdin.resume();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function parseJsonObject(value, source) {
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
parsed = JSON.parse(value);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
throw new CliError("E_USAGE", `Invalid JSON from ${source}: ${message}`, 2);
|
|
248
|
+
}
|
|
249
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
250
|
+
throw new CliError("E_USAGE", `JSON from ${source} must be an object.`, 2);
|
|
251
|
+
}
|
|
252
|
+
return parsed;
|
|
253
|
+
}
|
|
254
|
+
export async function resolveRawArguments(rawArgsMode, options) {
|
|
255
|
+
switch (rawArgsMode.kind) {
|
|
256
|
+
case "none":
|
|
257
|
+
return {};
|
|
258
|
+
case "json":
|
|
259
|
+
return parseJsonObject(rawArgsMode.value, "--args-json");
|
|
260
|
+
case "file": {
|
|
261
|
+
if (!fs.existsSync(rawArgsMode.path)) {
|
|
262
|
+
throw new CliError("E_USAGE", `Arguments file not found: ${rawArgsMode.path}`, 2);
|
|
263
|
+
}
|
|
264
|
+
const content = fs.readFileSync(rawArgsMode.path, "utf8");
|
|
265
|
+
return parseJsonObject(content, "--args-file");
|
|
266
|
+
}
|
|
267
|
+
case "stdin-json": {
|
|
268
|
+
const text = await readStdin(options.stdin || process.stdin, options.stdinTimeoutMs);
|
|
269
|
+
if (text.trim().length === 0) {
|
|
270
|
+
throw new CliError("E_USAGE", "stdin was empty for --args-json @-.", 2);
|
|
271
|
+
}
|
|
272
|
+
return parseJsonObject(text, "--args-json @-");
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
return {};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function isPrimitiveEnum(enumValues) {
|
|
279
|
+
return enumValues.every((value) => {
|
|
280
|
+
const valueType = typeof value;
|
|
281
|
+
return valueType === "string" || valueType === "number" || valueType === "boolean";
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function unsupportedSchemaReason(schema) {
|
|
285
|
+
if (!schema || typeof schema !== "object") {
|
|
286
|
+
return "schema is not an object";
|
|
287
|
+
}
|
|
288
|
+
if ("oneOf" in schema) {
|
|
289
|
+
return "oneOf is not supported in wrapper mode";
|
|
290
|
+
}
|
|
291
|
+
if ("anyOf" in schema) {
|
|
292
|
+
return "anyOf is not supported in wrapper mode";
|
|
293
|
+
}
|
|
294
|
+
if ("allOf" in schema) {
|
|
295
|
+
return "allOf is not supported in wrapper mode";
|
|
296
|
+
}
|
|
297
|
+
if ("$ref" in schema) {
|
|
298
|
+
return "$ref is not supported in wrapper mode";
|
|
299
|
+
}
|
|
300
|
+
if ("patternProperties" in schema) {
|
|
301
|
+
return "patternProperties is not supported in wrapper mode";
|
|
302
|
+
}
|
|
303
|
+
if (Array.isArray(schema.enum) && !isPrimitiveEnum(schema.enum)) {
|
|
304
|
+
return "enum values must be primitive";
|
|
305
|
+
}
|
|
306
|
+
if (schema.type === "array") {
|
|
307
|
+
const itemSchema = schema.items;
|
|
308
|
+
if (!itemSchema || typeof itemSchema !== "object") {
|
|
309
|
+
return "array items schema is missing";
|
|
310
|
+
}
|
|
311
|
+
const itemReason = unsupportedSchemaReason(itemSchema);
|
|
312
|
+
if (itemReason) {
|
|
313
|
+
return `array item schema unsupported: ${itemReason}`;
|
|
314
|
+
}
|
|
315
|
+
if (typeof itemSchema.type === "string" && !PRIMITIVE_TYPES.has(itemSchema.type) && !Array.isArray(itemSchema.enum)) {
|
|
316
|
+
return "array item type must be primitive";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (typeof schema.type === "string" && schema.type !== "object" && !PRIMITIVE_TYPES.has(schema.type) && !Array.isArray(schema.enum)) {
|
|
320
|
+
return `schema type '${schema.type}' is not supported`;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
function parseBooleanValue(value) {
|
|
325
|
+
if (value === "true") {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (value === "false") {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
throw new CliError("E_USAGE", `Invalid boolean value '${value}'. Use true or false.`, 2);
|
|
332
|
+
}
|
|
333
|
+
function parseEnumValue(raw, enumValues) {
|
|
334
|
+
for (const entry of enumValues) {
|
|
335
|
+
if (typeof entry === "string" && entry === raw) {
|
|
336
|
+
return entry;
|
|
337
|
+
}
|
|
338
|
+
if (typeof entry === "number" && Number(raw) === entry) {
|
|
339
|
+
return entry;
|
|
340
|
+
}
|
|
341
|
+
if (typeof entry === "boolean") {
|
|
342
|
+
if (raw === "true" && entry === true) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
if (raw === "false" && entry === false) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
throw new CliError("E_USAGE", `Value '${raw}' is not in enum [${enumValues.map(String).join(", ")}].`, 2);
|
|
351
|
+
}
|
|
352
|
+
function parsePrimitive(schema, raw) {
|
|
353
|
+
if (Array.isArray(schema.enum)) {
|
|
354
|
+
return parseEnumValue(raw, schema.enum);
|
|
355
|
+
}
|
|
356
|
+
switch (schema.type) {
|
|
357
|
+
case "string":
|
|
358
|
+
return raw;
|
|
359
|
+
case "number": {
|
|
360
|
+
const parsed = Number(raw);
|
|
361
|
+
if (!Number.isFinite(parsed)) {
|
|
362
|
+
throw new CliError("E_USAGE", `Value '${raw}' must be a finite number.`, 2);
|
|
363
|
+
}
|
|
364
|
+
return parsed;
|
|
365
|
+
}
|
|
366
|
+
case "integer": {
|
|
367
|
+
const parsed = Number(raw);
|
|
368
|
+
if (!Number.isInteger(parsed)) {
|
|
369
|
+
throw new CliError("E_USAGE", `Value '${raw}' must be an integer.`, 2);
|
|
370
|
+
}
|
|
371
|
+
return parsed;
|
|
372
|
+
}
|
|
373
|
+
case "boolean":
|
|
374
|
+
return parseBooleanValue(raw);
|
|
375
|
+
default:
|
|
376
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `Primitive parsing unsupported for schema type '${String(schema.type)}'. Use --args-json/--args-file.`, 2);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
export function parseWrapperArgumentsFromSchema(toolName, inputSchema, wrapperArgs) {
|
|
380
|
+
if (!inputSchema || typeof inputSchema !== "object") {
|
|
381
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName} schema is missing or invalid. Use --args-json/--args-file.`, 2);
|
|
382
|
+
}
|
|
383
|
+
const schema = inputSchema;
|
|
384
|
+
const rootReason = unsupportedSchemaReason(schema);
|
|
385
|
+
if (rootReason) {
|
|
386
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName} schema unsupported (${rootReason}). Use --args-json/--args-file.`, 2);
|
|
387
|
+
}
|
|
388
|
+
const properties = schema.properties;
|
|
389
|
+
if (!properties || typeof properties !== "object") {
|
|
390
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName} schema has no object properties. Use --args-json/--args-file.`, 2);
|
|
391
|
+
}
|
|
392
|
+
const requiredProps = Array.isArray(schema.required)
|
|
393
|
+
? schema.required.filter((entry) => typeof entry === "string")
|
|
394
|
+
: [];
|
|
395
|
+
const normalizedToCanonical = new Map();
|
|
396
|
+
const propertySchemas = new Map();
|
|
397
|
+
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
398
|
+
const unsupportedReason = unsupportedSchemaReason(propertySchema);
|
|
399
|
+
if (unsupportedReason) {
|
|
400
|
+
throw new CliError("E_SCHEMA_UNSUPPORTED", `${toolName}.${propertyName} uses unsupported schema (${unsupportedReason}). Use --args-json/--args-file.`, 2);
|
|
401
|
+
}
|
|
402
|
+
const normalized = normalizeFlagToken(`--${propertyName}`);
|
|
403
|
+
normalizedToCanonical.set(normalized, propertyName);
|
|
404
|
+
propertySchemas.set(propertyName, propertySchema);
|
|
405
|
+
}
|
|
406
|
+
const parsed = {};
|
|
407
|
+
for (let i = 0; i < wrapperArgs.length; i += 1) {
|
|
408
|
+
const token = wrapperArgs[i];
|
|
409
|
+
if (!token.startsWith("--")) {
|
|
410
|
+
throw new CliError("E_USAGE", `Unexpected positional argument '${token}'.`, 2);
|
|
411
|
+
}
|
|
412
|
+
const normalizedFlag = normalizeFlagToken(token);
|
|
413
|
+
const isJsonFlag = normalizedFlag.endsWith("_json");
|
|
414
|
+
const baseNormalized = isJsonFlag ? normalizedFlag.slice(0, -5) : normalizedFlag;
|
|
415
|
+
const canonicalName = normalizedToCanonical.get(baseNormalized);
|
|
416
|
+
if (!canonicalName) {
|
|
417
|
+
throw new CliError("E_USAGE", `Unknown flag '${token}' for tool '${toolName}'.`, 2);
|
|
418
|
+
}
|
|
419
|
+
const propertySchema = propertySchemas.get(canonicalName);
|
|
420
|
+
if (isJsonFlag) {
|
|
421
|
+
const next = wrapperArgs[i + 1];
|
|
422
|
+
if (!next) {
|
|
423
|
+
throw new CliError("E_USAGE", `Missing JSON value for ${token}.`, 2);
|
|
424
|
+
}
|
|
425
|
+
parsed[canonicalName] = parseJsonObject(next, token);
|
|
426
|
+
i += 1;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (propertySchema?.type === "object") {
|
|
430
|
+
throw new CliError("E_USAGE", `Flag '${token}' requires JSON input. Use --${stripFlagPrefix(token)}-json '<json>'.`, 2);
|
|
431
|
+
}
|
|
432
|
+
if (propertySchema?.type === "array") {
|
|
433
|
+
const next = wrapperArgs[i + 1];
|
|
434
|
+
if (!next || next.startsWith("--")) {
|
|
435
|
+
throw new CliError("E_USAGE", `Missing value for ${token}.`, 2);
|
|
436
|
+
}
|
|
437
|
+
const itemSchema = propertySchema.items;
|
|
438
|
+
const parsedValue = parsePrimitive(itemSchema, next);
|
|
439
|
+
const existing = parsed[canonicalName];
|
|
440
|
+
if (!Array.isArray(existing)) {
|
|
441
|
+
parsed[canonicalName] = [parsedValue];
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
existing.push(parsedValue);
|
|
445
|
+
}
|
|
446
|
+
i += 1;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (propertySchema?.type === "boolean") {
|
|
450
|
+
const next = wrapperArgs[i + 1];
|
|
451
|
+
if (!next || next.startsWith("--")) {
|
|
452
|
+
parsed[canonicalName] = true;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
parsed[canonicalName] = parseBooleanValue(next);
|
|
456
|
+
i += 1;
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const next = wrapperArgs[i + 1];
|
|
461
|
+
if (!next || next.startsWith("--")) {
|
|
462
|
+
throw new CliError("E_USAGE", `Missing value for ${token}.`, 2);
|
|
463
|
+
}
|
|
464
|
+
parsed[canonicalName] = parsePrimitive(propertySchema, next);
|
|
465
|
+
i += 1;
|
|
466
|
+
}
|
|
467
|
+
for (const requiredProp of requiredProps) {
|
|
468
|
+
if (!(requiredProp in parsed)) {
|
|
469
|
+
throw new CliError("E_USAGE", `Missing required flag for '${toolName}': --${requiredProp}.`, 2);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return parsed;
|
|
473
|
+
}
|
|
474
|
+
//# sourceMappingURL=args.js.map
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
interface SessionOptions {
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
env: Record<string, string | undefined>;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
startupTimeoutMs: number;
|
|
9
|
+
callTimeoutMs: number;
|
|
10
|
+
writeStderr: (text: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare class CliMcpSession {
|
|
13
|
+
private readonly client;
|
|
14
|
+
private readonly transport;
|
|
15
|
+
private readonly callTimeoutMs;
|
|
16
|
+
private readonly writeStderr;
|
|
17
|
+
constructor(client: Client, transport: StdioClientTransport, callTimeoutMs: number, writeStderr: (text: string) => void);
|
|
18
|
+
listTools(): Promise<any>;
|
|
19
|
+
callTool(name: string, args: Record<string, unknown>): Promise<any>;
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
logProtocolFailure(error: unknown): never;
|
|
22
|
+
wireStderr(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function connectCliMcpSession(options: SessionOptions): Promise<CliMcpSession>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { CliError } from "./errors.js";
|
|
4
|
+
function sanitizeEnv(env) {
|
|
5
|
+
const sanitized = {};
|
|
6
|
+
for (const [key, value] of Object.entries(env)) {
|
|
7
|
+
if (typeof value === "string") {
|
|
8
|
+
sanitized[key] = value;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return sanitized;
|
|
12
|
+
}
|
|
13
|
+
function createTimeout(promise, timeoutMs, token, message) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
reject(new CliError(token, message, 3));
|
|
17
|
+
}, timeoutMs);
|
|
18
|
+
timer.unref();
|
|
19
|
+
promise.then((value) => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
resolve(value);
|
|
22
|
+
}).catch((error) => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
reject(error);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export class CliMcpSession {
|
|
29
|
+
constructor(client, transport, callTimeoutMs, writeStderr) {
|
|
30
|
+
this.client = client;
|
|
31
|
+
this.transport = transport;
|
|
32
|
+
this.callTimeoutMs = callTimeoutMs;
|
|
33
|
+
this.writeStderr = writeStderr;
|
|
34
|
+
}
|
|
35
|
+
async listTools() {
|
|
36
|
+
return createTimeout(this.client.listTools(), this.callTimeoutMs, "E_CALL_TIMEOUT", `Timed out after ${this.callTimeoutMs}ms while calling tools/list.`);
|
|
37
|
+
}
|
|
38
|
+
async callTool(name, args) {
|
|
39
|
+
return createTimeout(this.client.callTool({ name, arguments: args }), this.callTimeoutMs, "E_CALL_TIMEOUT", `Timed out after ${this.callTimeoutMs}ms while calling tools/call for '${name}'.`);
|
|
40
|
+
}
|
|
41
|
+
async close() {
|
|
42
|
+
const stderr = this.transport.stderr;
|
|
43
|
+
try {
|
|
44
|
+
await this.client.close();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Best-effort close.
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await this.transport.close();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Best-effort close.
|
|
54
|
+
}
|
|
55
|
+
stderr?.removeAllListeners("data");
|
|
56
|
+
}
|
|
57
|
+
logProtocolFailure(error) {
|
|
58
|
+
if (error instanceof CliError) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
throw new CliError("E_PROTOCOL_FAILURE", message, 3);
|
|
63
|
+
}
|
|
64
|
+
wireStderr() {
|
|
65
|
+
const stderr = this.transport.stderr;
|
|
66
|
+
if (!stderr) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
stderr.on("data", (chunk) => {
|
|
70
|
+
this.writeStderr(String(chunk));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function connectCliMcpSession(options) {
|
|
75
|
+
const transport = new StdioClientTransport({
|
|
76
|
+
command: options.command,
|
|
77
|
+
args: options.args,
|
|
78
|
+
env: sanitizeEnv(options.env),
|
|
79
|
+
cwd: options.cwd,
|
|
80
|
+
stderr: "pipe",
|
|
81
|
+
});
|
|
82
|
+
const client = new Client({
|
|
83
|
+
name: "satori-cli",
|
|
84
|
+
version: "1.1.1",
|
|
85
|
+
});
|
|
86
|
+
const session = new CliMcpSession(client, transport, options.callTimeoutMs, options.writeStderr);
|
|
87
|
+
session.wireStderr();
|
|
88
|
+
try {
|
|
89
|
+
await createTimeout(client.connect(transport), options.startupTimeoutMs, "E_STARTUP_TIMEOUT", `Timed out after ${options.startupTimeoutMs}ms while starting MCP server.`);
|
|
90
|
+
return session;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
await session.close();
|
|
94
|
+
if (error instanceof CliError) {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
throw new CliError("E_PROTOCOL_FAILURE", message, 3);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=client.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class CliError extends Error {
|
|
2
|
+
readonly token: string;
|
|
3
|
+
readonly exitCode: number;
|
|
4
|
+
constructor(token: string, message: string, exitCode: number);
|
|
5
|
+
}
|
|
6
|
+
export declare function asCliError(error: unknown): CliError;
|
|
7
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class CliError extends Error {
|
|
2
|
+
constructor(token, message, exitCode) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "CliError";
|
|
5
|
+
this.token = token;
|
|
6
|
+
this.exitCode = exitCode;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function asCliError(error) {
|
|
10
|
+
if (error instanceof CliError) {
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
if (error instanceof Error) {
|
|
14
|
+
return new CliError("E_PROTOCOL_FAILURE", error.message, 3);
|
|
15
|
+
}
|
|
16
|
+
return new CliError("E_PROTOCOL_FAILURE", String(error), 3);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=errors.js.map
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CliWriters {
|
|
2
|
+
writeStdout: (text: string) => void;
|
|
3
|
+
writeStderr: (text: string) => void;
|
|
4
|
+
}
|
|
5
|
+
export interface StructuredEnvelopeSummary {
|
|
6
|
+
status: string;
|
|
7
|
+
reason?: string;
|
|
8
|
+
hintStatus?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export type ManageStatusState = "indexing" | "indexed" | "indexfailed" | "requires_reindex" | "not_indexed" | "unknown";
|
|
11
|
+
export declare function emitJson(writers: CliWriters, payload: unknown): void;
|
|
12
|
+
export declare function emitError(writers: CliWriters, token: string, message: string): void;
|
|
13
|
+
export declare function parseStructuredEnvelope(result: unknown): StructuredEnvelopeSummary | null;
|
|
14
|
+
export declare function inferManageStatusState(result: unknown): ManageStatusState;
|
|
15
|
+
//# sourceMappingURL=format.d.ts.map
|