@asnd/skill-creator 0.1.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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +1619 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/package-4Q4VX3O2.js +90 -0
- package/dist/cli/package-4Q4VX3O2.js.map +1 -0
- package/package.json +84 -0
- package/prompts/generate-skill.md +22 -0
- package/skills/skill-creator/SKILL.md +260 -0
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,1619 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/main.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { parseArgs as parseArgs2 } from "util";
|
|
7
|
+
|
|
8
|
+
// src/core/cache.ts
|
|
9
|
+
import { createHash } from "crypto";
|
|
10
|
+
import * as cacache from "cacache";
|
|
11
|
+
var CACHE_IGNORED_FIELDS = /* @__PURE__ */ new Set([
|
|
12
|
+
"cacheTtl",
|
|
13
|
+
"cache_ttl",
|
|
14
|
+
"description",
|
|
15
|
+
"include",
|
|
16
|
+
"exclude",
|
|
17
|
+
"methods"
|
|
18
|
+
]);
|
|
19
|
+
function cacheKeyFor(config) {
|
|
20
|
+
const normalized = normalizeConfig(config);
|
|
21
|
+
return createHash("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 16);
|
|
22
|
+
}
|
|
23
|
+
async function loadCached(cacheDir, key, ttlSeconds) {
|
|
24
|
+
try {
|
|
25
|
+
const info = await cacache.get.info(cacheDir, key);
|
|
26
|
+
if (info === null) return null;
|
|
27
|
+
const ageSeconds = (Date.now() - info.time) / 1e3;
|
|
28
|
+
if (ageSeconds >= ttlSeconds) return null;
|
|
29
|
+
const entry = await cacache.get(cacheDir, key);
|
|
30
|
+
return JSON.parse(entry.data.toString("utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function saveCache(cacheDir, key, data) {
|
|
36
|
+
await cacache.put(cacheDir, key, JSON.stringify(data));
|
|
37
|
+
}
|
|
38
|
+
function normalizeConfig(input) {
|
|
39
|
+
const output = {};
|
|
40
|
+
for (const [key, value] of Object.entries(input)) {
|
|
41
|
+
if (CACHE_IGNORED_FIELDS.has(key)) continue;
|
|
42
|
+
if (key === "authHeaders" || key === "auth_headers") {
|
|
43
|
+
output[key] = normalizeAuthHeaders(value);
|
|
44
|
+
} else {
|
|
45
|
+
output[key] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Object.fromEntries(Object.entries(output).sort(([a], [b]) => a.localeCompare(b)));
|
|
49
|
+
}
|
|
50
|
+
function normalizeAuthHeaders(value) {
|
|
51
|
+
if (!Array.isArray(value)) return value;
|
|
52
|
+
return [...value].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/core/coerce.ts
|
|
56
|
+
import { Ajv } from "ajv";
|
|
57
|
+
import addFormatsPlugin from "ajv-formats";
|
|
58
|
+
var ajv = new Ajv({ allErrors: true, strict: false });
|
|
59
|
+
var addFormats = addFormatsPlugin;
|
|
60
|
+
addFormats(ajv);
|
|
61
|
+
var validatorCache = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
function schemaTypeToCliType(schema) {
|
|
63
|
+
switch (schema.type) {
|
|
64
|
+
case "integer":
|
|
65
|
+
return { type: "integer", suffix: "" };
|
|
66
|
+
case "number":
|
|
67
|
+
return { type: "number", suffix: "" };
|
|
68
|
+
case "boolean":
|
|
69
|
+
return { type: "boolean", suffix: "" };
|
|
70
|
+
case "array":
|
|
71
|
+
return { type: "string", suffix: " (JSON array)" };
|
|
72
|
+
case "object":
|
|
73
|
+
return { type: "string", suffix: " (JSON object)" };
|
|
74
|
+
default:
|
|
75
|
+
return { type: "string", suffix: "" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function coerceAndValidateValue(value, schema = {}, label = "value") {
|
|
79
|
+
const coerced = coerceValue(value, schema);
|
|
80
|
+
validateValue(coerced, schema, label);
|
|
81
|
+
return coerced;
|
|
82
|
+
}
|
|
83
|
+
function validateValue(value, schema = {}, label = "value") {
|
|
84
|
+
if (Object.keys(schema).length === 0) return;
|
|
85
|
+
const validate = validatorFor(schema);
|
|
86
|
+
if (validate(value)) return;
|
|
87
|
+
throw new Error(`${label} failed validation: ${formatAjvErrors(validate.errors ?? [], label)}`);
|
|
88
|
+
}
|
|
89
|
+
function validatorFor(schema) {
|
|
90
|
+
const cached = validatorCache.get(schema);
|
|
91
|
+
if (cached !== void 0) return cached;
|
|
92
|
+
const validate = ajv.compile(normalizeSchema(schema));
|
|
93
|
+
validatorCache.set(schema, validate);
|
|
94
|
+
return validate;
|
|
95
|
+
}
|
|
96
|
+
function normalizeSchema(schema) {
|
|
97
|
+
if (schema.nullable === true && typeof schema.type === "string") {
|
|
98
|
+
return { ...schema, type: [schema.type, "null"] };
|
|
99
|
+
}
|
|
100
|
+
return schema;
|
|
101
|
+
}
|
|
102
|
+
function formatAjvErrors(errors, label) {
|
|
103
|
+
return errors.map((error) => {
|
|
104
|
+
const path = error.instancePath.length > 0 ? `${label}${error.instancePath}` : label;
|
|
105
|
+
return `${path} ${error.message ?? "is invalid"}`;
|
|
106
|
+
}).join("; ");
|
|
107
|
+
}
|
|
108
|
+
function coerceValue(value, schema = {}) {
|
|
109
|
+
if (value === null || value === void 0) return null;
|
|
110
|
+
switch (schema.type) {
|
|
111
|
+
case "array":
|
|
112
|
+
return coerceArray(value, schema.items);
|
|
113
|
+
case "object":
|
|
114
|
+
return coerceObject(value);
|
|
115
|
+
case "boolean":
|
|
116
|
+
return Boolean(value);
|
|
117
|
+
case "integer":
|
|
118
|
+
return typeof value === "number" ? Math.trunc(value) : Number.parseInt(String(value), 10);
|
|
119
|
+
case "number":
|
|
120
|
+
return typeof value === "number" ? value : Number.parseFloat(String(value));
|
|
121
|
+
default:
|
|
122
|
+
return coerceSchemaless(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function coerceArray(value, itemSchema) {
|
|
126
|
+
if (Array.isArray(value)) return value;
|
|
127
|
+
if (typeof value !== "string") return value;
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(value);
|
|
130
|
+
if (Array.isArray(parsed)) return parsed;
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
const values = value.includes(",") ? value.split(",").map((part) => part.trim()) : [value];
|
|
134
|
+
return values.map((item) => coerceItem(item, itemSchema?.type));
|
|
135
|
+
}
|
|
136
|
+
function coerceItem(value, type) {
|
|
137
|
+
switch (type) {
|
|
138
|
+
case "integer":
|
|
139
|
+
return Number.parseInt(value, 10);
|
|
140
|
+
case "number":
|
|
141
|
+
return Number.parseFloat(value);
|
|
142
|
+
case "boolean":
|
|
143
|
+
return ["true", "1", "yes"].includes(value.toLowerCase());
|
|
144
|
+
default:
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function coerceObject(value) {
|
|
149
|
+
if (typeof value !== "string") return value;
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(value);
|
|
152
|
+
} catch {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function coerceSchemaless(value) {
|
|
157
|
+
if (typeof value !== "string") return value;
|
|
158
|
+
const trimmed = value.trim();
|
|
159
|
+
if (!trimmed || !["{", "["].includes(trimmed[0] ?? "")) return value;
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(trimmed);
|
|
162
|
+
return typeof parsed === "object" && parsed !== null ? parsed : value;
|
|
163
|
+
} catch {
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/core/secrets.ts
|
|
169
|
+
import { readFile } from "fs/promises";
|
|
170
|
+
async function resolveSecret(value) {
|
|
171
|
+
if (value.startsWith("env:")) {
|
|
172
|
+
const name = value.slice(4);
|
|
173
|
+
const resolved = process.env[name];
|
|
174
|
+
if (resolved === void 0)
|
|
175
|
+
throw new Error(`environment variable ${JSON.stringify(name)} is not set`);
|
|
176
|
+
return resolved;
|
|
177
|
+
}
|
|
178
|
+
if (value.startsWith("file:")) {
|
|
179
|
+
const path = value.slice(5);
|
|
180
|
+
try {
|
|
181
|
+
return (await readFile(path, "utf8")).replace(/\n$/, "");
|
|
182
|
+
} catch {
|
|
183
|
+
throw new Error(`secret file not found: ${path}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/graphql/execute.ts
|
|
190
|
+
import {
|
|
191
|
+
Kind,
|
|
192
|
+
OperationTypeNode,
|
|
193
|
+
parse,
|
|
194
|
+
print
|
|
195
|
+
} from "graphql";
|
|
196
|
+
import { ClientError, GraphQLClient } from "graphql-request";
|
|
197
|
+
|
|
198
|
+
// src/graphql/extract.ts
|
|
199
|
+
import {
|
|
200
|
+
getNamedType,
|
|
201
|
+
isEnumType,
|
|
202
|
+
isInputObjectType,
|
|
203
|
+
isInterfaceType,
|
|
204
|
+
isListType,
|
|
205
|
+
isNonNullType,
|
|
206
|
+
isObjectType,
|
|
207
|
+
isScalarType
|
|
208
|
+
} from "graphql";
|
|
209
|
+
|
|
210
|
+
// src/core/names.ts
|
|
211
|
+
function toKebab(name) {
|
|
212
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/graphql/extract.ts
|
|
216
|
+
function extractGraphqlCommands(schema) {
|
|
217
|
+
const commands = [];
|
|
218
|
+
addRootFields(commands, schema.getQueryType()?.getFields(), "query");
|
|
219
|
+
addRootFields(commands, schema.getMutationType()?.getFields(), "mutation");
|
|
220
|
+
return commands;
|
|
221
|
+
}
|
|
222
|
+
function buildGraphqlSelectionSet(type, fields, depth = 2) {
|
|
223
|
+
if (fields !== void 0 && fields.trim().length > 0) return `{ ${fields.trim()} }`;
|
|
224
|
+
return buildDefaultSelectionSet(type, depth, /* @__PURE__ */ new Set());
|
|
225
|
+
}
|
|
226
|
+
function addRootFields(commands, fields, operationType) {
|
|
227
|
+
if (fields === void 0) return;
|
|
228
|
+
for (const field of Object.values(fields)) {
|
|
229
|
+
const description = optionalString(field.description);
|
|
230
|
+
commands.push({
|
|
231
|
+
name: toKebab(field.name),
|
|
232
|
+
...description === void 0 ? {} : { description },
|
|
233
|
+
params: field.args.map(
|
|
234
|
+
(arg) => graphqlArgToParam(arg.name, arg.type, optionalString(arg.description))
|
|
235
|
+
),
|
|
236
|
+
graphqlOperationType: operationType,
|
|
237
|
+
graphqlFieldName: field.name,
|
|
238
|
+
graphqlReturnType: field.type
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function graphqlArgToParam(name, type, description) {
|
|
243
|
+
const schema = graphqlInputTypeToJsonSchema(type);
|
|
244
|
+
const cliType = schemaTypeToCliType(schema).type;
|
|
245
|
+
return {
|
|
246
|
+
name: toKebab(name),
|
|
247
|
+
originalName: name,
|
|
248
|
+
type: cliType,
|
|
249
|
+
required: isNonNullType(type),
|
|
250
|
+
...description === void 0 ? {} : { description },
|
|
251
|
+
location: "graphql_arg",
|
|
252
|
+
schema
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function graphqlInputTypeToJsonSchema(type) {
|
|
256
|
+
if (isNonNullType(type)) {
|
|
257
|
+
return { ...graphqlInputTypeToJsonSchema(type.ofType), graphqlType: graphqlTypeToString(type) };
|
|
258
|
+
}
|
|
259
|
+
if (isListType(type)) {
|
|
260
|
+
return {
|
|
261
|
+
type: "array",
|
|
262
|
+
items: graphqlInputTypeToJsonSchema(type.ofType),
|
|
263
|
+
graphqlType: graphqlTypeToString(type)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const namedType = getNamedType(type);
|
|
267
|
+
const base = { graphqlType: graphqlTypeToString(type) };
|
|
268
|
+
if (isEnumType(namedType)) {
|
|
269
|
+
return { ...base, type: "string", enum: namedType.getValues().map((value) => value.name) };
|
|
270
|
+
}
|
|
271
|
+
if (isInputObjectType(namedType)) return { ...base, type: "object" };
|
|
272
|
+
if (isScalarType(namedType)) {
|
|
273
|
+
switch (namedType.name) {
|
|
274
|
+
case "Int":
|
|
275
|
+
return { ...base, type: "integer" };
|
|
276
|
+
case "Float":
|
|
277
|
+
return { ...base, type: "number" };
|
|
278
|
+
case "Boolean":
|
|
279
|
+
return { ...base, type: "boolean" };
|
|
280
|
+
default:
|
|
281
|
+
return { ...base, type: "string" };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { ...base, type: "string" };
|
|
285
|
+
}
|
|
286
|
+
function graphqlTypeToString(type) {
|
|
287
|
+
if (isNonNullType(type)) return `${graphqlTypeToString(type.ofType)}!`;
|
|
288
|
+
if (isListType(type)) return `[${graphqlTypeToString(type.ofType)}]`;
|
|
289
|
+
return getNamedType(type).name;
|
|
290
|
+
}
|
|
291
|
+
function optionalString(value) {
|
|
292
|
+
return value ?? void 0;
|
|
293
|
+
}
|
|
294
|
+
function buildDefaultSelectionSet(type, depth, seenTypes) {
|
|
295
|
+
const namedType = getNamedType(type);
|
|
296
|
+
if (isScalarType(namedType) || isEnumType(namedType)) return "";
|
|
297
|
+
if (depth <= 0) return "";
|
|
298
|
+
if (!isObjectType(namedType) && !isInterfaceType(namedType)) return "{ __typename }";
|
|
299
|
+
if (seenTypes.has(namedType.name)) return "";
|
|
300
|
+
const nextSeen = new Set(seenTypes);
|
|
301
|
+
nextSeen.add(namedType.name);
|
|
302
|
+
const selections = Object.values(namedType.getFields()).filter((field) => field.args.length === 0).map((field) => {
|
|
303
|
+
const fieldNamedType = getNamedType(field.type);
|
|
304
|
+
if (isScalarType(fieldNamedType) || isEnumType(fieldNamedType)) return field.name;
|
|
305
|
+
const nested = buildDefaultSelectionSet(field.type, depth - 1, nextSeen);
|
|
306
|
+
return nested ? `${field.name} ${nested}` : "";
|
|
307
|
+
}).filter((field) => field.length > 0);
|
|
308
|
+
return selections.length === 0 ? "" : `{ ${selections.join(" ")} }`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/graphql/execute.ts
|
|
312
|
+
async function executeGraphql(command, values, options) {
|
|
313
|
+
const fieldName = command.graphqlFieldName ?? command.name;
|
|
314
|
+
const variables = collectVariables(command, values);
|
|
315
|
+
const query = buildGraphqlOperation(
|
|
316
|
+
command,
|
|
317
|
+
fieldName,
|
|
318
|
+
variables,
|
|
319
|
+
options.fields,
|
|
320
|
+
options.selectionDepth ?? 2
|
|
321
|
+
);
|
|
322
|
+
const client = new GraphQLClient(options.endpoint, {
|
|
323
|
+
headers: Object.fromEntries(options.authHeaders ?? [])
|
|
324
|
+
});
|
|
325
|
+
try {
|
|
326
|
+
const data = await client.request(query, variables);
|
|
327
|
+
if (!isRecord(data)) return data;
|
|
328
|
+
return data[fieldName];
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw normalizeGraphqlRequestError(error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function collectVariables(command, values) {
|
|
334
|
+
const variables = {};
|
|
335
|
+
for (const param of command.params) {
|
|
336
|
+
const value = values[param.name] ?? values[param.originalName];
|
|
337
|
+
if (value !== void 0) {
|
|
338
|
+
variables[param.originalName] = coerceAndValidateValue(
|
|
339
|
+
value,
|
|
340
|
+
param.schema ?? {},
|
|
341
|
+
`--${param.name}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return variables;
|
|
346
|
+
}
|
|
347
|
+
function buildGraphqlOperation(command, fieldName, variables, fields, selectionDepth) {
|
|
348
|
+
const activeParams = command.params.filter(
|
|
349
|
+
(param) => param.required || variables[param.originalName] !== void 0
|
|
350
|
+
);
|
|
351
|
+
const variableDefinitions = activeParams.map(
|
|
352
|
+
(param) => ({
|
|
353
|
+
kind: Kind.VARIABLE_DEFINITION,
|
|
354
|
+
variable: {
|
|
355
|
+
kind: Kind.VARIABLE,
|
|
356
|
+
name: { kind: Kind.NAME, value: param.originalName }
|
|
357
|
+
},
|
|
358
|
+
type: parseGraphqlType(graphqlParamType(param))
|
|
359
|
+
})
|
|
360
|
+
);
|
|
361
|
+
const selectionSet = buildSelectionSetNode(command, fields, selectionDepth);
|
|
362
|
+
const field = {
|
|
363
|
+
kind: Kind.FIELD,
|
|
364
|
+
name: { kind: Kind.NAME, value: fieldName },
|
|
365
|
+
arguments: activeParams.map((param) => ({
|
|
366
|
+
kind: Kind.ARGUMENT,
|
|
367
|
+
name: { kind: Kind.NAME, value: param.originalName },
|
|
368
|
+
value: {
|
|
369
|
+
kind: Kind.VARIABLE,
|
|
370
|
+
name: { kind: Kind.NAME, value: param.originalName }
|
|
371
|
+
}
|
|
372
|
+
})),
|
|
373
|
+
...selectionSet === void 0 ? {} : { selectionSet }
|
|
374
|
+
};
|
|
375
|
+
const operation = {
|
|
376
|
+
kind: Kind.OPERATION_DEFINITION,
|
|
377
|
+
operation: command.graphqlOperationType === "mutation" ? OperationTypeNode.MUTATION : OperationTypeNode.QUERY,
|
|
378
|
+
name: { kind: Kind.NAME, value: commandName(command) },
|
|
379
|
+
...variableDefinitions.length === 0 ? {} : { variableDefinitions },
|
|
380
|
+
selectionSet: { kind: Kind.SELECTION_SET, selections: [field] }
|
|
381
|
+
};
|
|
382
|
+
const document = { kind: Kind.DOCUMENT, definitions: [operation] };
|
|
383
|
+
return print(document);
|
|
384
|
+
}
|
|
385
|
+
function buildSelectionSetNode(command, fields, selectionDepth) {
|
|
386
|
+
if (command.graphqlReturnType === void 0) return void 0;
|
|
387
|
+
const selectionSet = buildGraphqlSelectionSet(
|
|
388
|
+
command.graphqlReturnType,
|
|
389
|
+
fields,
|
|
390
|
+
selectionDepth
|
|
391
|
+
);
|
|
392
|
+
if (selectionSet.length === 0) return void 0;
|
|
393
|
+
return parseSelectionSet(selectionSet);
|
|
394
|
+
}
|
|
395
|
+
function parseGraphqlType(type) {
|
|
396
|
+
const operation = parseSingleOperation(`query __Type($value: ${type}) { __typename }`);
|
|
397
|
+
const variable = operation.variableDefinitions?.[0];
|
|
398
|
+
if (variable === void 0) throw new Error(`invalid GraphQL variable type: ${type}`);
|
|
399
|
+
return variable.type;
|
|
400
|
+
}
|
|
401
|
+
function parseSelectionSet(selectionSet) {
|
|
402
|
+
const operation = parseSingleOperation(`query __Selection { _selection ${selectionSet} }`);
|
|
403
|
+
const selection = operation.selectionSet.selections[0];
|
|
404
|
+
if (selection?.kind !== Kind.FIELD || selection.selectionSet === void 0) {
|
|
405
|
+
throw new Error("invalid GraphQL selection set");
|
|
406
|
+
}
|
|
407
|
+
return selection.selectionSet;
|
|
408
|
+
}
|
|
409
|
+
function parseSingleOperation(source) {
|
|
410
|
+
const document = parse(source);
|
|
411
|
+
const definition = document.definitions[0];
|
|
412
|
+
if (definition?.kind !== Kind.OPERATION_DEFINITION) {
|
|
413
|
+
throw new Error("failed to build GraphQL operation");
|
|
414
|
+
}
|
|
415
|
+
return definition;
|
|
416
|
+
}
|
|
417
|
+
function graphqlParamType(param) {
|
|
418
|
+
const type = param.schema?.graphqlType;
|
|
419
|
+
return typeof type === "string" ? type : jsonSchemaToGraphqlType(param.schema?.type);
|
|
420
|
+
}
|
|
421
|
+
function jsonSchemaToGraphqlType(type) {
|
|
422
|
+
switch (type) {
|
|
423
|
+
case "integer":
|
|
424
|
+
return "Int";
|
|
425
|
+
case "number":
|
|
426
|
+
return "Float";
|
|
427
|
+
case "boolean":
|
|
428
|
+
return "Boolean";
|
|
429
|
+
default:
|
|
430
|
+
return "String";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function commandName(command) {
|
|
434
|
+
return `${command.graphqlOperationType ?? "query"}_${command.graphqlFieldName ?? command.name}`.replace(
|
|
435
|
+
/[^_0-9A-Za-z]/g,
|
|
436
|
+
"_"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
function normalizeGraphqlRequestError(error) {
|
|
440
|
+
if (error instanceof ClientError) {
|
|
441
|
+
if (error.response.errors !== void 0 && error.response.errors.length > 0) {
|
|
442
|
+
return new Error(`GraphQL error: ${formatGraphqlErrors(error.response.errors)}`);
|
|
443
|
+
}
|
|
444
|
+
return new Error(
|
|
445
|
+
`GraphQL HTTP ${error.response.status}: ${JSON.stringify(error.response, null, 0)}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
449
|
+
}
|
|
450
|
+
function formatGraphqlErrors(errors) {
|
|
451
|
+
return errors.map((error) => error.message ?? "unknown error").join("; ");
|
|
452
|
+
}
|
|
453
|
+
function isRecord(value) {
|
|
454
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/graphql/load.ts
|
|
458
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
459
|
+
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
|
|
460
|
+
import ky from "ky";
|
|
461
|
+
import { JsonFileLoader } from "@graphql-tools/json-file-loader";
|
|
462
|
+
import { loadSchema } from "@graphql-tools/load";
|
|
463
|
+
import { UrlLoader } from "@graphql-tools/url-loader";
|
|
464
|
+
import {
|
|
465
|
+
buildClientSchema,
|
|
466
|
+
buildSchema,
|
|
467
|
+
getIntrospectionQuery,
|
|
468
|
+
introspectionFromSchema
|
|
469
|
+
} from "graphql";
|
|
470
|
+
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
471
|
+
async function loadGraphqlSchema(endpoint, options = {}) {
|
|
472
|
+
const authHeaders = options.authHeaders ?? [];
|
|
473
|
+
if (options.schemaSource !== void 0) {
|
|
474
|
+
return loadProvidedGraphqlSchema(options.schemaSource, authHeaders);
|
|
475
|
+
}
|
|
476
|
+
const cacheKey = `graphql-${options.cacheKey ?? cacheKeyFor({ endpoint, authHeaders: options.authHeaders ?? [] })}`;
|
|
477
|
+
const ttlSeconds = options.ttlSeconds ?? 3600;
|
|
478
|
+
if (options.cacheDir !== void 0 && !options.refresh) {
|
|
479
|
+
const cached = await loadCached(options.cacheDir, cacheKey, ttlSeconds);
|
|
480
|
+
if (cached !== null) return buildClientSchema(cached);
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const schema = await loadRemoteGraphqlSchema(endpoint, authHeaders);
|
|
484
|
+
if (options.cacheDir !== void 0) {
|
|
485
|
+
await saveCache(options.cacheDir, cacheKey, introspectionFromSchema(schema));
|
|
486
|
+
}
|
|
487
|
+
return schema;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (options.cacheDir !== void 0) {
|
|
490
|
+
const stale = await loadCached(
|
|
491
|
+
options.cacheDir,
|
|
492
|
+
cacheKey,
|
|
493
|
+
Number.POSITIVE_INFINITY
|
|
494
|
+
);
|
|
495
|
+
if (stale !== null) {
|
|
496
|
+
options.onWarning?.(
|
|
497
|
+
`Warning: using stale cached GraphQL schema because introspection failed: ${formatError(error)}`
|
|
498
|
+
);
|
|
499
|
+
return buildClientSchema(stale);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
throw new Error(
|
|
503
|
+
`GraphQL introspection is disabled or unavailable. Provide a schema with --graphql-schema ./schema.graphql or --graphql-schema ./introspection.json. (${formatError(error)})`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function loadRemoteGraphqlSchema(endpoint, authHeaders) {
|
|
508
|
+
const client = new GraphQLClient2(endpoint, {
|
|
509
|
+
headers: Object.fromEntries(authHeaders)
|
|
510
|
+
});
|
|
511
|
+
const introspection = await client.request(getIntrospectionQuery());
|
|
512
|
+
return buildClientSchema(introspection);
|
|
513
|
+
}
|
|
514
|
+
async function loadProvidedGraphqlSchema(source, authHeaders) {
|
|
515
|
+
try {
|
|
516
|
+
const schema = await loadSchema(source, {
|
|
517
|
+
loaders: [new UrlLoader(), new GraphQLFileLoader(), new JsonFileLoader()],
|
|
518
|
+
headers: Object.fromEntries(authHeaders)
|
|
519
|
+
});
|
|
520
|
+
return buildClientSchema(introspectionFromSchema(schema));
|
|
521
|
+
} catch {
|
|
522
|
+
return loadProvidedGraphqlSchemaFallback(source, authHeaders);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function loadProvidedGraphqlSchemaFallback(source, authHeaders) {
|
|
526
|
+
const text = await readSchemaSource(source, authHeaders);
|
|
527
|
+
const trimmed = text.trim();
|
|
528
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
529
|
+
const parsed = JSON.parse(trimmed);
|
|
530
|
+
return buildClientSchema(extractIntrospection(parsed));
|
|
531
|
+
}
|
|
532
|
+
return buildSchema(text);
|
|
533
|
+
}
|
|
534
|
+
async function readSchemaSource(source, authHeaders) {
|
|
535
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
536
|
+
const response = await ky.get(source, {
|
|
537
|
+
headers: Object.fromEntries(authHeaders),
|
|
538
|
+
throwHttpErrors: false
|
|
539
|
+
});
|
|
540
|
+
if (!response.ok) throw new Error(`failed to fetch GraphQL schema: HTTP ${response.status}`);
|
|
541
|
+
return response.text();
|
|
542
|
+
}
|
|
543
|
+
return readFile2(source, "utf8");
|
|
544
|
+
}
|
|
545
|
+
function extractIntrospection(value) {
|
|
546
|
+
if (!isRecord2(value)) throw new Error("GraphQL schema JSON must be an object");
|
|
547
|
+
if (isRecord2(value.data)) return extractIntrospection(value.data);
|
|
548
|
+
if (isRecord2(value.__schema)) return value;
|
|
549
|
+
throw new Error("GraphQL schema JSON must contain an introspection __schema object");
|
|
550
|
+
}
|
|
551
|
+
function formatError(error) {
|
|
552
|
+
return error instanceof Error ? error.message : String(error);
|
|
553
|
+
}
|
|
554
|
+
function isRecord2(value) {
|
|
555
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/openapi/load.ts
|
|
559
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
560
|
+
import ky2 from "ky";
|
|
561
|
+
import YAML from "yaml";
|
|
562
|
+
|
|
563
|
+
// src/openapi/refs.ts
|
|
564
|
+
import $RefParser from "@apidevtools/json-schema-ref-parser";
|
|
565
|
+
async function resolveRefs(input) {
|
|
566
|
+
return await $RefParser.dereference(input, {
|
|
567
|
+
mutateInputSchema: false,
|
|
568
|
+
dereference: { circular: "ignore" }
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/openapi/load.ts
|
|
573
|
+
async function loadOpenApiSpec(source, options = {}) {
|
|
574
|
+
const isUrl = source.startsWith("http://") || source.startsWith("https://");
|
|
575
|
+
const ttlSeconds = options.ttlSeconds ?? 3600;
|
|
576
|
+
const key = options.cacheKey ?? cacheKeyFor({ source, authHeaders: options.authHeaders ?? [] });
|
|
577
|
+
if (isUrl && options.cacheDir !== void 0 && !options.refresh) {
|
|
578
|
+
const cached = await loadCached(options.cacheDir, key, ttlSeconds);
|
|
579
|
+
if (cached !== null) return cached;
|
|
580
|
+
}
|
|
581
|
+
const raw = isUrl ? await fetchRemoteSpec(source, options.authHeaders ?? []) : await readFile3(source, "utf8");
|
|
582
|
+
const parsed = parseSpec(raw);
|
|
583
|
+
const spec = await resolveRefs(parsed);
|
|
584
|
+
if (!isOpenApiSpec(spec)) {
|
|
585
|
+
throw new Error("spec must contain 'paths'");
|
|
586
|
+
}
|
|
587
|
+
if (isUrl && options.cacheDir !== void 0) {
|
|
588
|
+
await saveCache(options.cacheDir, key, spec);
|
|
589
|
+
}
|
|
590
|
+
return spec;
|
|
591
|
+
}
|
|
592
|
+
async function fetchRemoteSpec(source, authHeaders) {
|
|
593
|
+
const response = await ky2.get(source, {
|
|
594
|
+
headers: Object.fromEntries(authHeaders),
|
|
595
|
+
throwHttpErrors: false
|
|
596
|
+
});
|
|
597
|
+
if (!response.ok) throw new Error(`failed to fetch spec: HTTP ${response.status}`);
|
|
598
|
+
return response.text();
|
|
599
|
+
}
|
|
600
|
+
function parseSpec(raw) {
|
|
601
|
+
try {
|
|
602
|
+
return JSON.parse(raw);
|
|
603
|
+
} catch {
|
|
604
|
+
return YAML.parse(raw);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function isOpenApiSpec(value) {
|
|
608
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "paths" in value && typeof value.paths === "object";
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/openapi/extract.ts
|
|
612
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch"]);
|
|
613
|
+
function extractOpenApiCommands(spec) {
|
|
614
|
+
const commands = [];
|
|
615
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
616
|
+
const paths = isObject(spec) && isObject(spec.paths) ? spec.paths : {};
|
|
617
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
618
|
+
if (!isObject(methods)) continue;
|
|
619
|
+
for (const [method, operation] of Object.entries(methods)) {
|
|
620
|
+
if (!HTTP_METHODS.has(method) || !isObject(operation)) continue;
|
|
621
|
+
let name = operation.operationId !== void 0 ? toKebab(String(operation.operationId)) : fallbackName(method, path);
|
|
622
|
+
const seen = seenNames.get(name) ?? 0;
|
|
623
|
+
seenNames.set(name, seen + 1);
|
|
624
|
+
if (seen > 0) name = `${name}-${method}`;
|
|
625
|
+
const params = extractParameters(operation);
|
|
626
|
+
const body = extractRequestBodyParams(operation);
|
|
627
|
+
params.push(...body.params);
|
|
628
|
+
commands.push({
|
|
629
|
+
name,
|
|
630
|
+
description: String(
|
|
631
|
+
operation.summary ?? operation.description ?? `${method.toUpperCase()} ${path}`
|
|
632
|
+
),
|
|
633
|
+
params,
|
|
634
|
+
hasBody: body.params.length > 0,
|
|
635
|
+
method,
|
|
636
|
+
path,
|
|
637
|
+
...body.contentType === void 0 ? {} : { contentType: body.contentType }
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return commands;
|
|
642
|
+
}
|
|
643
|
+
function extractParameters(operation) {
|
|
644
|
+
const rawParams = Array.isArray(operation.parameters) ? operation.parameters : [];
|
|
645
|
+
return rawParams.filter(isObject).map((param) => {
|
|
646
|
+
const schema = getSchema(param.schema);
|
|
647
|
+
const { type, suffix } = schemaTypeToCliType(schema);
|
|
648
|
+
const choices = Array.isArray(schema.enum) ? { choices: schema.enum } : {};
|
|
649
|
+
return {
|
|
650
|
+
name: toKebab(String(param.name)),
|
|
651
|
+
originalName: String(param.name),
|
|
652
|
+
type,
|
|
653
|
+
required: Boolean(param.required),
|
|
654
|
+
description: `${String(param.description ?? param.name)}${suffix}`,
|
|
655
|
+
...choices,
|
|
656
|
+
location: normalizeLocation(param.in),
|
|
657
|
+
schema
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function extractRequestBodyParams(operation) {
|
|
662
|
+
const requestBody = isObject(operation.requestBody) ? operation.requestBody : void 0;
|
|
663
|
+
const content = requestBody !== void 0 && isObject(requestBody.content) ? requestBody.content : {};
|
|
664
|
+
const multipartSchema = getContentSchema(content, "multipart/form-data");
|
|
665
|
+
const jsonSchema = getContentSchema(content, "application/json");
|
|
666
|
+
const multipartProps = getProperties(multipartSchema);
|
|
667
|
+
const hasBinary = Object.values(multipartProps).some((schema2) => schema2.format === "binary");
|
|
668
|
+
let schema = {};
|
|
669
|
+
let contentType;
|
|
670
|
+
if (hasBinary) {
|
|
671
|
+
schema = multipartSchema;
|
|
672
|
+
contentType = "multipart/form-data";
|
|
673
|
+
} else if (Object.keys(getProperties(jsonSchema)).length > 0) {
|
|
674
|
+
schema = jsonSchema;
|
|
675
|
+
} else if (Object.keys(multipartProps).length > 0) {
|
|
676
|
+
schema = multipartSchema;
|
|
677
|
+
contentType = "multipart/form-data";
|
|
678
|
+
}
|
|
679
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
680
|
+
const properties = getProperties(schema);
|
|
681
|
+
const params = Object.entries(properties).map(([propName, propSchema]) => {
|
|
682
|
+
const isBinary = contentType === "multipart/form-data" && propSchema.format === "binary";
|
|
683
|
+
const { type, suffix } = isBinary ? { type: "string", suffix: " (file path)" } : schemaTypeToCliType(propSchema);
|
|
684
|
+
const choices = Array.isArray(propSchema.enum) ? { choices: propSchema.enum } : {};
|
|
685
|
+
return {
|
|
686
|
+
name: toKebab(propName),
|
|
687
|
+
originalName: propName,
|
|
688
|
+
type,
|
|
689
|
+
required: required.has(propName),
|
|
690
|
+
description: `${String(propSchema.description ?? propName)}${suffix}`,
|
|
691
|
+
...choices,
|
|
692
|
+
location: isBinary ? "file" : "body",
|
|
693
|
+
schema: propSchema
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
return contentType === void 0 ? { params } : { params, contentType };
|
|
697
|
+
}
|
|
698
|
+
function getContentSchema(content, contentType) {
|
|
699
|
+
const item = content[contentType];
|
|
700
|
+
return isObject(item) ? getSchema(item.schema) : {};
|
|
701
|
+
}
|
|
702
|
+
function getProperties(schema) {
|
|
703
|
+
return isObject(schema.properties) ? Object.fromEntries(
|
|
704
|
+
Object.entries(schema.properties).filter(
|
|
705
|
+
(entry) => isObject(entry[1])
|
|
706
|
+
)
|
|
707
|
+
) : {};
|
|
708
|
+
}
|
|
709
|
+
function getSchema(value) {
|
|
710
|
+
return isObject(value) ? value : {};
|
|
711
|
+
}
|
|
712
|
+
function normalizeLocation(value) {
|
|
713
|
+
return value === "path" || value === "header" || value === "body" || value === "file" ? value : "query";
|
|
714
|
+
}
|
|
715
|
+
function fallbackName(method, path) {
|
|
716
|
+
const slug = path.replace(/^\/+|\/+$/g, "").replace(/[{}]/g, "").replace(/\//g, "-");
|
|
717
|
+
return slug ? `${method}-${slug}` : method;
|
|
718
|
+
}
|
|
719
|
+
function isObject(value) {
|
|
720
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/openapi/execute.ts
|
|
724
|
+
import ky3 from "ky";
|
|
725
|
+
|
|
726
|
+
// src/openapi/params.ts
|
|
727
|
+
function collectOpenApiParams(command, values, options = {}) {
|
|
728
|
+
let path = command.path ?? "";
|
|
729
|
+
const queryParams = {};
|
|
730
|
+
const headers = {};
|
|
731
|
+
let body = null;
|
|
732
|
+
const files = null;
|
|
733
|
+
for (const param of command.params) {
|
|
734
|
+
if (param.location !== "path") continue;
|
|
735
|
+
const value = values[param.name];
|
|
736
|
+
if (value !== void 0 && value !== null) {
|
|
737
|
+
path = path.replace(`{${param.originalName}}`, encodeURIComponent(String(value)));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
for (const param of command.params) {
|
|
741
|
+
const value = values[param.name];
|
|
742
|
+
if (value === void 0 || value === null) continue;
|
|
743
|
+
if (param.location === "query") {
|
|
744
|
+
queryParams[param.originalName] = coerceAndValidateValue(
|
|
745
|
+
value,
|
|
746
|
+
param.schema ?? {},
|
|
747
|
+
`--${param.name}`
|
|
748
|
+
);
|
|
749
|
+
} else if (param.location === "header") {
|
|
750
|
+
headers[param.originalName] = String(value);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const method = command.method?.toLowerCase() ?? "get";
|
|
754
|
+
if (method !== "get") {
|
|
755
|
+
if (options.stdinBody !== void 0) {
|
|
756
|
+
body = options.stdinBody;
|
|
757
|
+
} else {
|
|
758
|
+
const collectedBody = {};
|
|
759
|
+
for (const param of command.params) {
|
|
760
|
+
if (param.location !== "body") continue;
|
|
761
|
+
const value = values[param.name];
|
|
762
|
+
if (value !== void 0 && value !== null) {
|
|
763
|
+
collectedBody[param.originalName] = coerceAndValidateValue(
|
|
764
|
+
value,
|
|
765
|
+
param.schema ?? {},
|
|
766
|
+
`--${param.name}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
body = Object.keys(collectedBody).length > 0 ? collectedBody : null;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return { path, queryParams, headers, body, files };
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/openapi/execute.ts
|
|
777
|
+
async function executeOpenApi(command, values, options) {
|
|
778
|
+
const collected = collectOpenApiParams(command, values, {
|
|
779
|
+
stdinBody: options.stdinBody
|
|
780
|
+
});
|
|
781
|
+
const url = buildUrl(options.baseUrl, collected.path, collected.queryParams);
|
|
782
|
+
const method = (command.method ?? "get").toUpperCase();
|
|
783
|
+
const headers = {
|
|
784
|
+
...Object.fromEntries(options.authHeaders ?? []),
|
|
785
|
+
...collected.headers
|
|
786
|
+
};
|
|
787
|
+
const body = method !== "GET" && collected.body !== null ? JSON.stringify(collected.body) : void 0;
|
|
788
|
+
if (body !== void 0) headers["Content-Type"] ??= "application/json";
|
|
789
|
+
const response = await ky3(url, {
|
|
790
|
+
method,
|
|
791
|
+
headers,
|
|
792
|
+
...body === void 0 ? {} : { body },
|
|
793
|
+
throwHttpErrors: false
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
status: response.status,
|
|
797
|
+
ok: response.ok,
|
|
798
|
+
text: await response.text(),
|
|
799
|
+
contentType: response.headers.get("content-type") ?? ""
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function buildUrl(baseUrl, path, queryParams) {
|
|
803
|
+
const url = new URL(`${baseUrl.replace(/\/$/, "")}${path}`);
|
|
804
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
805
|
+
if (value === void 0 || value === null) continue;
|
|
806
|
+
if (Array.isArray(value)) {
|
|
807
|
+
for (const item of value) url.searchParams.append(key, String(item));
|
|
808
|
+
} else {
|
|
809
|
+
url.searchParams.set(key, String(value));
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return url.toString();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/mcp/extract.ts
|
|
816
|
+
function extractMcpCommands(tools) {
|
|
817
|
+
return tools.map((tool) => {
|
|
818
|
+
const schema = isJsonSchema(tool.inputSchema) ? tool.inputSchema : {};
|
|
819
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
820
|
+
const properties = getProperties2(schema);
|
|
821
|
+
const params = Object.entries(properties).map(([name, propSchema]) => {
|
|
822
|
+
const { type, suffix } = schemaTypeToCliType(propSchema);
|
|
823
|
+
const choices = Array.isArray(propSchema.enum) ? { choices: propSchema.enum } : {};
|
|
824
|
+
return {
|
|
825
|
+
name: toKebab(name),
|
|
826
|
+
originalName: name,
|
|
827
|
+
type,
|
|
828
|
+
required: required.has(name),
|
|
829
|
+
description: `${String(propSchema.description ?? name)}${suffix}`,
|
|
830
|
+
...choices,
|
|
831
|
+
location: "tool_input",
|
|
832
|
+
schema: propSchema
|
|
833
|
+
};
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
name: toKebab(tool.name),
|
|
837
|
+
description: tool.description ?? "",
|
|
838
|
+
params,
|
|
839
|
+
hasBody: params.length > 0,
|
|
840
|
+
toolName: tool.name
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
function getProperties2(schema) {
|
|
845
|
+
const properties = schema.properties;
|
|
846
|
+
if (typeof properties !== "object" || properties === null || Array.isArray(properties)) return {};
|
|
847
|
+
return Object.fromEntries(
|
|
848
|
+
Object.entries(properties).filter(
|
|
849
|
+
(entry) => isJsonSchema(entry[1])
|
|
850
|
+
)
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
function isJsonSchema(value) {
|
|
854
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/mcp/http.ts
|
|
858
|
+
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
859
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
860
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
861
|
+
|
|
862
|
+
// src/mcp/stdio.ts
|
|
863
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
864
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
865
|
+
import stringArgv from "string-argv";
|
|
866
|
+
async function listStdioTools(commandLine) {
|
|
867
|
+
return withStdioClient(commandLine, async (client) => {
|
|
868
|
+
const result = await client.listTools();
|
|
869
|
+
return result.tools.map((tool) => ({
|
|
870
|
+
name: tool.name,
|
|
871
|
+
...tool.description === void 0 ? {} : { description: tool.description },
|
|
872
|
+
inputSchema: tool.inputSchema
|
|
873
|
+
}));
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async function callStdioTool(commandLine, toolName, args) {
|
|
877
|
+
return withStdioClient(commandLine, async (client) => {
|
|
878
|
+
const result = await client.callTool({ name: toolName, arguments: args });
|
|
879
|
+
return extractMcpContent(result.content);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
async function withStdioClient(commandLine, fn) {
|
|
883
|
+
const [command, ...args] = splitCommandLine(commandLine);
|
|
884
|
+
if (command === void 0) throw new Error("--mcp-stdio command cannot be empty");
|
|
885
|
+
const client = new Client({ name: "skill-creator", version: "0.1.0" });
|
|
886
|
+
const transport = new StdioClientTransport({ command, args, stderr: "pipe" });
|
|
887
|
+
await client.connect(transport);
|
|
888
|
+
try {
|
|
889
|
+
return await fn(client);
|
|
890
|
+
} finally {
|
|
891
|
+
await client.close();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function splitCommandLine(commandLine) {
|
|
895
|
+
return stringArgv(commandLine);
|
|
896
|
+
}
|
|
897
|
+
function extractMcpContent(content) {
|
|
898
|
+
if (!Array.isArray(content)) return content;
|
|
899
|
+
const parts = content.map((part) => extractContentPart(part)).filter((part) => part !== void 0);
|
|
900
|
+
if (parts.length === 0) return "";
|
|
901
|
+
if (parts.every((part) => typeof part === "string")) return parts.join("\n");
|
|
902
|
+
return parts;
|
|
903
|
+
}
|
|
904
|
+
function extractContentPart(part) {
|
|
905
|
+
if (!isObject2(part)) return void 0;
|
|
906
|
+
if (part.type === "text" && typeof part.text === "string") return part.text;
|
|
907
|
+
if (typeof part.data === "string") return part.data;
|
|
908
|
+
if (part.type === "resource" && isObject2(part.resource)) {
|
|
909
|
+
if (typeof part.resource.text === "string") return part.resource.text;
|
|
910
|
+
if (typeof part.resource.blob === "string") return part.resource.blob;
|
|
911
|
+
}
|
|
912
|
+
return part;
|
|
913
|
+
}
|
|
914
|
+
function isObject2(value) {
|
|
915
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/mcp/http.ts
|
|
919
|
+
async function listHttpTools(url, optionsOrHeaders = {}) {
|
|
920
|
+
const options = normalizeOptions(optionsOrHeaders);
|
|
921
|
+
return withHttpClient(url, options, async (client) => {
|
|
922
|
+
const result = await client.listTools();
|
|
923
|
+
return result.tools.map((tool) => ({
|
|
924
|
+
name: tool.name,
|
|
925
|
+
...tool.description === void 0 ? {} : { description: tool.description },
|
|
926
|
+
inputSchema: tool.inputSchema
|
|
927
|
+
}));
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async function callHttpTool(url, toolName, args, optionsOrHeaders = {}) {
|
|
931
|
+
const options = normalizeOptions(optionsOrHeaders);
|
|
932
|
+
return withHttpClient(url, options, async (client) => {
|
|
933
|
+
const result = await client.callTool({ name: toolName, arguments: args });
|
|
934
|
+
return extractMcpContent(result.content);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
async function withHttpClient(url, options, fn) {
|
|
938
|
+
if (options.transport === "auto") {
|
|
939
|
+
try {
|
|
940
|
+
return await withSingleHttpClient(url, { ...options, transport: "streamable" }, fn);
|
|
941
|
+
} catch (streamableError) {
|
|
942
|
+
try {
|
|
943
|
+
return await withSingleHttpClient(url, { ...options, transport: "sse" }, fn);
|
|
944
|
+
} catch (sseError) {
|
|
945
|
+
throw new Error(
|
|
946
|
+
`failed to connect using streamable HTTP or SSE (${formatError2(streamableError)}; ${formatError2(sseError)})`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return withSingleHttpClient(url, options, fn);
|
|
952
|
+
}
|
|
953
|
+
async function withSingleHttpClient(url, options, fn) {
|
|
954
|
+
const client = new Client2({ name: "skill-creator", version: "0.1.0" });
|
|
955
|
+
const transport = createTransport(url, options);
|
|
956
|
+
await client.connect(transport);
|
|
957
|
+
try {
|
|
958
|
+
return await fn(client);
|
|
959
|
+
} finally {
|
|
960
|
+
await client.close();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function createTransport(url, options) {
|
|
964
|
+
const headers = Object.fromEntries(options.headers);
|
|
965
|
+
if (options.transport === "sse") {
|
|
966
|
+
const fetchWithHeaders = (input, init) => fetch(input, { ...init, headers: { ...headers, ...headersToObject(init?.headers) } });
|
|
967
|
+
return new SSEClientTransport(new URL(url), {
|
|
968
|
+
eventSourceInit: { fetch: fetchWithHeaders },
|
|
969
|
+
requestInit: { headers }
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
return new StreamableHTTPClientTransport(new URL(url), {
|
|
973
|
+
requestInit: { headers }
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
function normalizeOptions(optionsOrHeaders) {
|
|
977
|
+
if (Array.isArray(optionsOrHeaders)) {
|
|
978
|
+
return { headers: optionsOrHeaders, transport: "auto" };
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
headers: optionsOrHeaders.headers ?? [],
|
|
982
|
+
transport: optionsOrHeaders.transport ?? "auto"
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function headersToObject(headers) {
|
|
986
|
+
if (headers === void 0) return {};
|
|
987
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
988
|
+
}
|
|
989
|
+
function formatError2(error) {
|
|
990
|
+
return error instanceof Error ? error.message : String(error);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/cli/dynamic.ts
|
|
994
|
+
import { parseArgs } from "util";
|
|
995
|
+
|
|
996
|
+
// src/core/filter.ts
|
|
997
|
+
import { minimatch } from "minimatch";
|
|
998
|
+
function filterCommands(commands, filters = {}) {
|
|
999
|
+
let result = commands;
|
|
1000
|
+
if (filters.methods?.length) {
|
|
1001
|
+
const allowed = new Set(filters.methods.map((method) => method.toUpperCase()));
|
|
1002
|
+
result = result.filter(
|
|
1003
|
+
(command) => command.method === void 0 || allowed.has(command.method.toUpperCase())
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
if (filters.include?.length) {
|
|
1007
|
+
result = result.filter(
|
|
1008
|
+
(command) => filters.include?.some((pattern) => minimatch(command.name, pattern)) ?? false
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
if (filters.exclude?.length) {
|
|
1012
|
+
result = result.filter(
|
|
1013
|
+
(command) => !(filters.exclude?.some((pattern) => minimatch(command.name, pattern)) ?? false)
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return result;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/core/output.ts
|
|
1020
|
+
function applyHead(data, n) {
|
|
1021
|
+
if (Array.isArray(data)) return data.slice(0, n);
|
|
1022
|
+
return data;
|
|
1023
|
+
}
|
|
1024
|
+
function formatOutput(data, options = {}) {
|
|
1025
|
+
if (options.raw) {
|
|
1026
|
+
return {
|
|
1027
|
+
stdout: `${typeof data === "string" ? data : JSON.stringify(data)}
|
|
1028
|
+
`,
|
|
1029
|
+
stderr: ""
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
let value = parseJsonStringIfPossible(data);
|
|
1033
|
+
if (options.head !== void 0) value = applyHead(value, options.head);
|
|
1034
|
+
if (typeof value === "string") {
|
|
1035
|
+
return { stdout: `${value}
|
|
1036
|
+
`, stderr: "" };
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
stdout: `${JSON.stringify(value, null, options.pretty ? 2 : 0)}
|
|
1040
|
+
`,
|
|
1041
|
+
stderr: ""
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
function parseJsonStringIfPossible(data) {
|
|
1045
|
+
if (typeof data !== "string") return data;
|
|
1046
|
+
try {
|
|
1047
|
+
return JSON.parse(data);
|
|
1048
|
+
} catch {
|
|
1049
|
+
return data;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/cli/dynamic.ts
|
|
1054
|
+
async function runDynamicMode(options) {
|
|
1055
|
+
let commands = filterCommands(await options.loadCommands(), options.globals);
|
|
1056
|
+
if (options.globals.search !== void 0) {
|
|
1057
|
+
commands = searchCommands(commands, options.globals.search);
|
|
1058
|
+
}
|
|
1059
|
+
if (options.globals.list || options.globals.search !== void 0) {
|
|
1060
|
+
writeStdout(options.renderCommands(commands));
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (options.commandArgv.length === 0) {
|
|
1064
|
+
if (options.onEmptyCommand !== void 0) {
|
|
1065
|
+
await options.onEmptyCommand(commands);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
writeStdout(options.renderCommands(commands));
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const commandName2 = options.commandArgv[0];
|
|
1072
|
+
if (commandName2 === void 0) throw new Error("missing subcommand");
|
|
1073
|
+
const command = commands.find((candidate) => candidate.name === commandName2);
|
|
1074
|
+
if (command === void 0) throw new Error(`unknown subcommand: ${commandName2}`);
|
|
1075
|
+
if (options.commandArgv.includes("--help") || options.commandArgv.includes("-h")) {
|
|
1076
|
+
writeStdout(renderCommandHelp(command));
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
const prepared = await prepareCommandArgs(options, options.commandArgv.slice(1));
|
|
1080
|
+
const values = parseCommandValues(command, prepared.argv, prepared.initialValues ?? {});
|
|
1081
|
+
const result = await options.executeCommand(command, values, prepared.argv);
|
|
1082
|
+
writeFormattedOutput(result, options.globals);
|
|
1083
|
+
}
|
|
1084
|
+
function searchCommands(commands, search) {
|
|
1085
|
+
const pattern = search.toLowerCase();
|
|
1086
|
+
return commands.filter(
|
|
1087
|
+
(command) => command.name.toLowerCase().includes(pattern) || (command.description ?? "").toLowerCase().includes(pattern)
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
async function prepareCommandArgs(options, argv) {
|
|
1091
|
+
return options.prepareCommandArgs === void 0 ? { argv } : await options.prepareCommandArgs(argv);
|
|
1092
|
+
}
|
|
1093
|
+
function parseCommandValues(command, argv, initialValues = {}) {
|
|
1094
|
+
const values = { ...initialValues };
|
|
1095
|
+
const params = new Map(command.params.map((param) => [param.name, param]));
|
|
1096
|
+
validateCommandOptions(command, argv, params);
|
|
1097
|
+
const { values: parsedValues } = parseArgs({
|
|
1098
|
+
args: normalizeStringOptionValues(argv, stringParamNames(command)),
|
|
1099
|
+
options: commandOptionSpec(command),
|
|
1100
|
+
strict: true,
|
|
1101
|
+
allowPositionals: true
|
|
1102
|
+
});
|
|
1103
|
+
for (const param of command.params) {
|
|
1104
|
+
const value = parsedValues[param.name];
|
|
1105
|
+
if (value !== void 0) values[param.name] = value;
|
|
1106
|
+
}
|
|
1107
|
+
for (const param of command.params) {
|
|
1108
|
+
if (param.required && values[param.name] === void 0 && values[param.originalName] === void 0 && param.location !== "body") {
|
|
1109
|
+
throw new Error(`missing required option --${param.name}`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return values;
|
|
1113
|
+
}
|
|
1114
|
+
function validateCommandOptions(command, argv, params) {
|
|
1115
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1116
|
+
const token = argv[index];
|
|
1117
|
+
if (token === void 0 || !token.startsWith("--")) continue;
|
|
1118
|
+
const rawFlag = token.slice(2);
|
|
1119
|
+
const flag = rawFlag.includes("=") ? rawFlag.slice(0, rawFlag.indexOf("=")) : rawFlag;
|
|
1120
|
+
const param = params.get(flag);
|
|
1121
|
+
if (param === void 0) throw new Error(`unknown option for ${command.name}: --${flag}`);
|
|
1122
|
+
if (param.type !== "boolean" && !rawFlag.includes("=") && argv[index + 1] === void 0) {
|
|
1123
|
+
throw new Error(`missing value for --${flag}`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function commandOptionSpec(command) {
|
|
1128
|
+
return Object.fromEntries(
|
|
1129
|
+
command.params.map((param) => [
|
|
1130
|
+
param.name,
|
|
1131
|
+
{ type: param.type === "boolean" ? "boolean" : "string" }
|
|
1132
|
+
])
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
function stringParamNames(command) {
|
|
1136
|
+
return new Set(
|
|
1137
|
+
command.params.filter((param) => param.type !== "boolean").map((param) => param.name)
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
function normalizeStringOptionValues(argv, optionNames) {
|
|
1141
|
+
const result = [];
|
|
1142
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1143
|
+
const token = argv[index];
|
|
1144
|
+
const next = argv[index + 1];
|
|
1145
|
+
if (token !== void 0 && next !== void 0 && token.startsWith("--") && !token.includes("=") && optionNames.has(token.slice(2)) && next.startsWith("-")) {
|
|
1146
|
+
result.push(`${token}=${next}`);
|
|
1147
|
+
index += 1;
|
|
1148
|
+
} else if (token !== void 0) {
|
|
1149
|
+
result.push(token);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return result;
|
|
1153
|
+
}
|
|
1154
|
+
function renderCommandHelp(command) {
|
|
1155
|
+
const lines = [`${command.name}: ${command.description ?? ""}`, "", "Options:"];
|
|
1156
|
+
for (const param of command.params) {
|
|
1157
|
+
const required = param.required ? " (required)" : "";
|
|
1158
|
+
lines.push(
|
|
1159
|
+
` --${param.name.padEnd(24)} ${param.description ?? param.originalName}${required}`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return `${lines.join("\n")}
|
|
1163
|
+
`;
|
|
1164
|
+
}
|
|
1165
|
+
function writeFormattedOutput(data, globals) {
|
|
1166
|
+
const output = formatOutput(data, {
|
|
1167
|
+
pretty: globals.pretty,
|
|
1168
|
+
raw: globals.raw,
|
|
1169
|
+
...globals.head === void 0 ? {} : { head: globals.head }
|
|
1170
|
+
});
|
|
1171
|
+
if (output.stderr) console.error(output.stderr.replace(/\n$/, ""));
|
|
1172
|
+
writeStdout(output.stdout);
|
|
1173
|
+
}
|
|
1174
|
+
function writeStdout(text) {
|
|
1175
|
+
console.log(text.replace(/\n$/, ""));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/cli/parse.ts
|
|
1179
|
+
function splitAtSubcommand(argv, spec) {
|
|
1180
|
+
const valueOptions = new Set(spec.valueOptions);
|
|
1181
|
+
const boolOptions = new Set(spec.boolOptions);
|
|
1182
|
+
let index = 0;
|
|
1183
|
+
while (index < argv.length) {
|
|
1184
|
+
const arg = argv[index];
|
|
1185
|
+
if (arg === void 0) break;
|
|
1186
|
+
if (arg === "--") {
|
|
1187
|
+
return {
|
|
1188
|
+
globalArgv: argv.slice(0, index),
|
|
1189
|
+
commandArgv: argv.slice(index + 1)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (arg.startsWith("-")) {
|
|
1193
|
+
const optionName = arg.startsWith("--") && arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
1194
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
1195
|
+
index += 1;
|
|
1196
|
+
} else if (valueOptions.has(optionName)) {
|
|
1197
|
+
index += 2;
|
|
1198
|
+
} else if (boolOptions.has(optionName)) {
|
|
1199
|
+
index += 1;
|
|
1200
|
+
} else {
|
|
1201
|
+
index += 1;
|
|
1202
|
+
}
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
return {
|
|
1206
|
+
globalArgv: argv.slice(0, index),
|
|
1207
|
+
commandArgv: argv.slice(index)
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return { globalArgv: argv, commandArgv: [] };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/cli/main.ts
|
|
1214
|
+
var GLOBAL_OPTION_SPEC = {
|
|
1215
|
+
valueOptions: [
|
|
1216
|
+
"--spec",
|
|
1217
|
+
"--mcp",
|
|
1218
|
+
"--mcp-stdio",
|
|
1219
|
+
"--graphql",
|
|
1220
|
+
"--graphql-schema",
|
|
1221
|
+
"--base-url",
|
|
1222
|
+
"--auth-header",
|
|
1223
|
+
"--transport",
|
|
1224
|
+
"--cache-key",
|
|
1225
|
+
"--cache-ttl",
|
|
1226
|
+
"--search",
|
|
1227
|
+
"--include",
|
|
1228
|
+
"--exclude",
|
|
1229
|
+
"--methods",
|
|
1230
|
+
"--fields",
|
|
1231
|
+
"--selection-depth",
|
|
1232
|
+
"--head"
|
|
1233
|
+
],
|
|
1234
|
+
boolOptions: ["--list", "--pretty", "--raw", "--refresh", "--stdin", "--version", "--help", "-h"]
|
|
1235
|
+
};
|
|
1236
|
+
async function run(argv = process.argv.slice(2)) {
|
|
1237
|
+
if (argv[0] === "--") argv = argv.slice(1);
|
|
1238
|
+
const { globalArgv, commandArgv } = splitAtSubcommand(argv, GLOBAL_OPTION_SPEC);
|
|
1239
|
+
const globals = parseGlobalArgs(globalArgv);
|
|
1240
|
+
if (globals.version) {
|
|
1241
|
+
const pkg = await import("./package-4Q4VX3O2.js");
|
|
1242
|
+
writeStdout2(`skill-creator ${pkg.default.version}
|
|
1243
|
+
`);
|
|
1244
|
+
return 0;
|
|
1245
|
+
}
|
|
1246
|
+
if (globals.help && commandArgv.length === 0) {
|
|
1247
|
+
printHelp();
|
|
1248
|
+
return 0;
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1251
|
+
validateSourceModes(globals);
|
|
1252
|
+
globals.authHeaders = await resolveAuthHeaders(globals.authHeaders);
|
|
1253
|
+
if (globals.spec !== void 0) {
|
|
1254
|
+
await handleOpenApiMode(globals, commandArgv);
|
|
1255
|
+
return 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (globals.mcpStdio !== void 0) {
|
|
1258
|
+
await handleMcpStdioMode(globals, commandArgv);
|
|
1259
|
+
return 0;
|
|
1260
|
+
}
|
|
1261
|
+
if (globals.mcp !== void 0) {
|
|
1262
|
+
await handleMcpHttpMode(globals, commandArgv);
|
|
1263
|
+
return 0;
|
|
1264
|
+
}
|
|
1265
|
+
if (globals.graphql !== void 0) {
|
|
1266
|
+
await handleGraphqlMode(globals, commandArgv);
|
|
1267
|
+
return 0;
|
|
1268
|
+
}
|
|
1269
|
+
console.error(
|
|
1270
|
+
"Error: only --spec, --mcp-stdio, --mcp, and --graphql modes are implemented in this TypeScript port so far."
|
|
1271
|
+
);
|
|
1272
|
+
return 1;
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1275
|
+
return 1;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
async function handleOpenApiMode(globals, commandArgv) {
|
|
1279
|
+
if (globals.spec === void 0) throw new Error("--spec is required");
|
|
1280
|
+
const source = globals.spec;
|
|
1281
|
+
const spec = await loadOpenApiSpec(source, {
|
|
1282
|
+
authHeaders: globals.authHeaders,
|
|
1283
|
+
cacheDir: defaultCacheDir(),
|
|
1284
|
+
...globals.cacheKey === void 0 ? {} : { cacheKey: globals.cacheKey },
|
|
1285
|
+
ttlSeconds: globals.cacheTtl,
|
|
1286
|
+
refresh: globals.refresh
|
|
1287
|
+
});
|
|
1288
|
+
await runDynamicMode({
|
|
1289
|
+
globals,
|
|
1290
|
+
commandArgv,
|
|
1291
|
+
loadCommands: () => extractOpenApiCommands(spec),
|
|
1292
|
+
renderCommands: renderOpenApiCommands,
|
|
1293
|
+
onEmptyCommand: () => {
|
|
1294
|
+
printHelp();
|
|
1295
|
+
throw new Error("provide a subcommand, or use --list to see available commands");
|
|
1296
|
+
},
|
|
1297
|
+
executeCommand: async (command, values) => {
|
|
1298
|
+
const baseUrl = determineBaseUrl(spec, source, globals.baseUrl);
|
|
1299
|
+
const response = await executeOpenApi(command, values, {
|
|
1300
|
+
baseUrl,
|
|
1301
|
+
authHeaders: globals.authHeaders
|
|
1302
|
+
});
|
|
1303
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.text}`);
|
|
1304
|
+
return response.text;
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
async function handleGraphqlMode(globals, commandArgv) {
|
|
1309
|
+
if (globals.graphql === void 0) throw new Error("--graphql is required");
|
|
1310
|
+
const endpoint = globals.graphql;
|
|
1311
|
+
const schema = await loadGraphqlSchema(endpoint, {
|
|
1312
|
+
authHeaders: globals.authHeaders,
|
|
1313
|
+
cacheDir: defaultCacheDir(),
|
|
1314
|
+
...globals.cacheKey === void 0 ? {} : { cacheKey: globals.cacheKey },
|
|
1315
|
+
ttlSeconds: globals.cacheTtl,
|
|
1316
|
+
refresh: globals.refresh,
|
|
1317
|
+
...globals.graphqlSchema === void 0 ? {} : { schemaSource: globals.graphqlSchema },
|
|
1318
|
+
onWarning: (message) => console.error(message)
|
|
1319
|
+
});
|
|
1320
|
+
await runDynamicMode({
|
|
1321
|
+
globals,
|
|
1322
|
+
commandArgv,
|
|
1323
|
+
loadCommands: () => extractGraphqlCommands(schema),
|
|
1324
|
+
renderCommands: renderGraphqlCommands,
|
|
1325
|
+
prepareCommandArgs: async (argv) => {
|
|
1326
|
+
const stdinFlag = stripFlag(argv, "--stdin");
|
|
1327
|
+
const stdinValues = globals.stdin || stdinFlag.enabled ? await readStdinJson() : {};
|
|
1328
|
+
return { argv: stdinFlag.argv, initialValues: stdinValues };
|
|
1329
|
+
},
|
|
1330
|
+
executeCommand: (command, values) => executeGraphql(command, values, {
|
|
1331
|
+
endpoint,
|
|
1332
|
+
authHeaders: globals.authHeaders,
|
|
1333
|
+
...globals.fields === void 0 ? {} : { fields: globals.fields },
|
|
1334
|
+
selectionDepth: globals.selectionDepth
|
|
1335
|
+
})
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
async function handleMcpHttpMode(globals, commandArgv) {
|
|
1339
|
+
if (globals.mcp === void 0) throw new Error("--mcp is required");
|
|
1340
|
+
const endpoint = globals.mcp;
|
|
1341
|
+
await runDynamicMode({
|
|
1342
|
+
globals,
|
|
1343
|
+
commandArgv,
|
|
1344
|
+
loadCommands: async () => extractMcpCommands(await loadMcpHttpTools(globals)),
|
|
1345
|
+
renderCommands: renderMcpCommands,
|
|
1346
|
+
executeCommand: async (command, values) => {
|
|
1347
|
+
const toolArgs = collectMcpToolArgs(command, values);
|
|
1348
|
+
return callHttpTool(endpoint, command.toolName ?? command.name, toolArgs, {
|
|
1349
|
+
headers: globals.authHeaders,
|
|
1350
|
+
transport: globals.transport
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
async function loadMcpHttpTools(globals) {
|
|
1356
|
+
if (globals.mcp === void 0) throw new Error("--mcp is required");
|
|
1357
|
+
const cacheKey = `mcp-${globals.cacheKey ?? cacheKeyFor({
|
|
1358
|
+
source: globals.mcp,
|
|
1359
|
+
authHeaders: globals.authHeaders,
|
|
1360
|
+
transport: globals.transport
|
|
1361
|
+
})}`;
|
|
1362
|
+
const cacheDir = defaultCacheDir();
|
|
1363
|
+
if (!globals.refresh) {
|
|
1364
|
+
const cached = await loadCached(cacheDir, cacheKey, globals.cacheTtl);
|
|
1365
|
+
if (cached !== null) return cached;
|
|
1366
|
+
}
|
|
1367
|
+
const tools = await listHttpTools(globals.mcp, {
|
|
1368
|
+
headers: globals.authHeaders,
|
|
1369
|
+
transport: globals.transport
|
|
1370
|
+
});
|
|
1371
|
+
await saveCache(cacheDir, cacheKey, tools);
|
|
1372
|
+
return tools;
|
|
1373
|
+
}
|
|
1374
|
+
async function handleMcpStdioMode(globals, commandArgv) {
|
|
1375
|
+
if (globals.mcpStdio === void 0) throw new Error("--mcp-stdio is required");
|
|
1376
|
+
const commandLine = globals.mcpStdio;
|
|
1377
|
+
await runDynamicMode({
|
|
1378
|
+
globals,
|
|
1379
|
+
commandArgv,
|
|
1380
|
+
loadCommands: async () => extractMcpCommands(await listStdioTools(commandLine)),
|
|
1381
|
+
renderCommands: renderMcpCommands,
|
|
1382
|
+
executeCommand: async (command, values) => {
|
|
1383
|
+
const toolArgs = collectMcpToolArgs(command, values);
|
|
1384
|
+
return callStdioTool(commandLine, command.toolName ?? command.name, toolArgs);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
function collectMcpToolArgs(command, values) {
|
|
1389
|
+
const args = {};
|
|
1390
|
+
for (const param of command.params) {
|
|
1391
|
+
if (values[param.name] !== void 0) {
|
|
1392
|
+
args[param.originalName] = coerceAndValidateValue(
|
|
1393
|
+
values[param.name],
|
|
1394
|
+
param.schema ?? {},
|
|
1395
|
+
`--${param.name}`
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return args;
|
|
1400
|
+
}
|
|
1401
|
+
function parseGlobalArgs(argv) {
|
|
1402
|
+
assertKnownValueOptionsHaveValues(argv, GLOBAL_OPTION_SPEC.valueOptions);
|
|
1403
|
+
const { values } = parseArgs2({
|
|
1404
|
+
args: argv,
|
|
1405
|
+
options: {
|
|
1406
|
+
spec: { type: "string" },
|
|
1407
|
+
mcp: { type: "string" },
|
|
1408
|
+
"mcp-stdio": { type: "string" },
|
|
1409
|
+
graphql: { type: "string" },
|
|
1410
|
+
"graphql-schema": { type: "string" },
|
|
1411
|
+
"base-url": { type: "string" },
|
|
1412
|
+
"auth-header": { type: "string", multiple: true },
|
|
1413
|
+
transport: { type: "string" },
|
|
1414
|
+
"cache-key": { type: "string" },
|
|
1415
|
+
"cache-ttl": { type: "string" },
|
|
1416
|
+
search: { type: "string" },
|
|
1417
|
+
include: { type: "string" },
|
|
1418
|
+
exclude: { type: "string" },
|
|
1419
|
+
methods: { type: "string" },
|
|
1420
|
+
fields: { type: "string" },
|
|
1421
|
+
"selection-depth": { type: "string" },
|
|
1422
|
+
head: { type: "string" },
|
|
1423
|
+
list: { type: "boolean" },
|
|
1424
|
+
pretty: { type: "boolean" },
|
|
1425
|
+
raw: { type: "boolean" },
|
|
1426
|
+
refresh: { type: "boolean" },
|
|
1427
|
+
stdin: { type: "boolean" },
|
|
1428
|
+
version: { type: "boolean" },
|
|
1429
|
+
help: { type: "boolean", short: "h" }
|
|
1430
|
+
},
|
|
1431
|
+
strict: false,
|
|
1432
|
+
allowPositionals: false
|
|
1433
|
+
});
|
|
1434
|
+
const authHeaders = stringListOption(values["auth-header"]).map(parseHeader);
|
|
1435
|
+
const search = stringOption(values.search);
|
|
1436
|
+
return {
|
|
1437
|
+
...optionalStringProperty("spec", stringOption(values.spec)),
|
|
1438
|
+
...optionalStringProperty("mcp", stringOption(values.mcp)),
|
|
1439
|
+
...optionalStringProperty("mcpStdio", stringOption(values["mcp-stdio"])),
|
|
1440
|
+
...optionalStringProperty("graphql", stringOption(values.graphql)),
|
|
1441
|
+
...optionalStringProperty("graphqlSchema", stringOption(values["graphql-schema"])),
|
|
1442
|
+
...optionalStringProperty("baseUrl", stringOption(values["base-url"])),
|
|
1443
|
+
authHeaders,
|
|
1444
|
+
transport: parseTransport(stringOption(values.transport) ?? "auto"),
|
|
1445
|
+
...optionalStringProperty("cacheKey", stringOption(values["cache-key"])),
|
|
1446
|
+
cacheTtl: Number.parseInt(stringOption(values["cache-ttl"]) ?? "3600", 10),
|
|
1447
|
+
refresh: values.refresh === true,
|
|
1448
|
+
list: values.list === true || search !== void 0,
|
|
1449
|
+
...optionalStringProperty("search", search),
|
|
1450
|
+
...optionalStringArrayProperty("include", parseOptionalCommaList(values.include)),
|
|
1451
|
+
...optionalStringArrayProperty("exclude", parseOptionalCommaList(values.exclude)),
|
|
1452
|
+
...optionalStringArrayProperty("methods", parseOptionalCommaList(values.methods)),
|
|
1453
|
+
...optionalStringProperty("fields", stringOption(values.fields)),
|
|
1454
|
+
selectionDepth: Number.parseInt(stringOption(values["selection-depth"]) ?? "2", 10),
|
|
1455
|
+
stdin: values.stdin === true,
|
|
1456
|
+
pretty: values.pretty === true,
|
|
1457
|
+
raw: values.raw === true,
|
|
1458
|
+
...optionalNumberProperty("head", parseOptionalInteger(values.head)),
|
|
1459
|
+
help: values.help === true,
|
|
1460
|
+
version: values.version === true
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function renderOpenApiCommands(commands) {
|
|
1464
|
+
if (commands.length === 0) return "No commands found.\n";
|
|
1465
|
+
return `${commands.map(
|
|
1466
|
+
(command) => `${command.name.padEnd(32)} ${(command.method ?? "").toUpperCase().padEnd(6)} ${command.description ?? ""}`.trimEnd()
|
|
1467
|
+
).join("\n")}
|
|
1468
|
+
`;
|
|
1469
|
+
}
|
|
1470
|
+
function renderMcpCommands(commands) {
|
|
1471
|
+
if (commands.length === 0) return "No tools found.\n";
|
|
1472
|
+
return `${commands.map((command) => `${command.name.padEnd(32)} ${command.description ?? ""}`.trimEnd()).join("\n")}
|
|
1473
|
+
`;
|
|
1474
|
+
}
|
|
1475
|
+
function renderGraphqlCommands(commands) {
|
|
1476
|
+
if (commands.length === 0) return "No GraphQL operations found.\n";
|
|
1477
|
+
return `${commands.map(
|
|
1478
|
+
(command) => `${command.name.padEnd(32)} ${(command.graphqlOperationType ?? "").padEnd(8)} ${command.description ?? ""}`.trimEnd()
|
|
1479
|
+
).join("\n")}
|
|
1480
|
+
`;
|
|
1481
|
+
}
|
|
1482
|
+
function determineBaseUrl(spec, source, override) {
|
|
1483
|
+
if (override !== void 0) return override;
|
|
1484
|
+
const servers = Array.isArray(spec.servers) ? spec.servers : [];
|
|
1485
|
+
const firstServer = servers[0];
|
|
1486
|
+
const serverUrl = typeof firstServer === "object" && firstServer !== null && "url" in firstServer ? String(firstServer.url) : "";
|
|
1487
|
+
if (serverUrl.startsWith("http://") || serverUrl.startsWith("https://")) return serverUrl;
|
|
1488
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
1489
|
+
const origin = new URL(source).origin;
|
|
1490
|
+
return serverUrl ? `${origin}${serverUrl}` : origin;
|
|
1491
|
+
}
|
|
1492
|
+
throw new Error("cannot determine base URL. Use --base-url.");
|
|
1493
|
+
}
|
|
1494
|
+
function validateSourceModes(globals) {
|
|
1495
|
+
const active = [globals.spec, globals.mcp, globals.mcpStdio, globals.graphql].filter(
|
|
1496
|
+
(value) => value !== void 0
|
|
1497
|
+
).length;
|
|
1498
|
+
if (active === 0) {
|
|
1499
|
+
printHelp();
|
|
1500
|
+
throw new Error("one of --spec, --mcp, --mcp-stdio, or --graphql is required.");
|
|
1501
|
+
}
|
|
1502
|
+
if (active > 1)
|
|
1503
|
+
throw new Error("--spec, --mcp, --mcp-stdio, and --graphql are mutually exclusive.");
|
|
1504
|
+
}
|
|
1505
|
+
function assertKnownValueOptionsHaveValues(argv, options) {
|
|
1506
|
+
const valueOptions = new Set(options);
|
|
1507
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1508
|
+
const token = argv[index];
|
|
1509
|
+
if (token === void 0 || !token.startsWith("--")) continue;
|
|
1510
|
+
const option = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
|
|
1511
|
+
if (!valueOptions.has(option) || token.includes("=")) continue;
|
|
1512
|
+
if (argv[index + 1] === void 0) throw new Error(`missing value for ${option}`);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function stringOption(value) {
|
|
1516
|
+
return typeof value === "string" ? value : void 0;
|
|
1517
|
+
}
|
|
1518
|
+
function stringListOption(value) {
|
|
1519
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
1520
|
+
const single = stringOption(value);
|
|
1521
|
+
return single === void 0 ? [] : [single];
|
|
1522
|
+
}
|
|
1523
|
+
function optionalStringProperty(key, value) {
|
|
1524
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1525
|
+
}
|
|
1526
|
+
function optionalStringArrayProperty(key, value) {
|
|
1527
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1528
|
+
}
|
|
1529
|
+
function optionalNumberProperty(key, value) {
|
|
1530
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1531
|
+
}
|
|
1532
|
+
function parseOptionalCommaList(value) {
|
|
1533
|
+
const raw = stringOption(value);
|
|
1534
|
+
return raw === void 0 ? void 0 : parseCommaList(raw);
|
|
1535
|
+
}
|
|
1536
|
+
function parseOptionalInteger(value) {
|
|
1537
|
+
const raw = stringOption(value);
|
|
1538
|
+
return raw === void 0 ? void 0 : Number.parseInt(raw, 10);
|
|
1539
|
+
}
|
|
1540
|
+
function stripFlag(argv, flag) {
|
|
1541
|
+
let enabled = false;
|
|
1542
|
+
const filtered = argv.filter((token) => {
|
|
1543
|
+
if (token !== flag) return true;
|
|
1544
|
+
enabled = true;
|
|
1545
|
+
return false;
|
|
1546
|
+
});
|
|
1547
|
+
return { argv: filtered, enabled };
|
|
1548
|
+
}
|
|
1549
|
+
async function readStdinJson() {
|
|
1550
|
+
let raw = "";
|
|
1551
|
+
for await (const chunk of process.stdin) raw += String(chunk);
|
|
1552
|
+
if (raw.trim().length === 0) return {};
|
|
1553
|
+
const parsed = JSON.parse(raw);
|
|
1554
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1555
|
+
throw new Error("--stdin must contain a JSON object");
|
|
1556
|
+
}
|
|
1557
|
+
return parsed;
|
|
1558
|
+
}
|
|
1559
|
+
function parseCommaList(value) {
|
|
1560
|
+
return value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
1561
|
+
}
|
|
1562
|
+
function parseTransport(value) {
|
|
1563
|
+
if (value === "auto" || value === "streamable" || value === "sse") return value;
|
|
1564
|
+
throw new Error("--transport must be one of: auto, streamable, sse");
|
|
1565
|
+
}
|
|
1566
|
+
function parseHeader(header) {
|
|
1567
|
+
const colon = header.indexOf(":");
|
|
1568
|
+
if (colon === -1) throw new Error(`invalid auth header format: ${header}`);
|
|
1569
|
+
return [header.slice(0, colon).trim(), header.slice(colon + 1).trim()];
|
|
1570
|
+
}
|
|
1571
|
+
async function resolveAuthHeaders(headers) {
|
|
1572
|
+
return Promise.all(headers.map(async ([key, value]) => [key, await resolveSecret(value)]));
|
|
1573
|
+
}
|
|
1574
|
+
function defaultCacheDir() {
|
|
1575
|
+
return process.env.SKILL_CREATOR_CACHE_DIR ?? join(homedir(), ".cache", "skill-creator");
|
|
1576
|
+
}
|
|
1577
|
+
function printHelp() {
|
|
1578
|
+
writeStdout2(`skill-creator [global options] <subcommand> [command options]
|
|
1579
|
+
|
|
1580
|
+
Source (mutually exclusive, one required):
|
|
1581
|
+
--spec URL|FILE OpenAPI spec (JSON or YAML, local or remote)
|
|
1582
|
+
--mcp URL MCP server URL (HTTP/SSE)
|
|
1583
|
+
--mcp-stdio CMD MCP server command (stdio transport)
|
|
1584
|
+
--graphql URL GraphQL endpoint URL
|
|
1585
|
+
|
|
1586
|
+
Options:
|
|
1587
|
+
--auth-header K:V HTTP header (repeatable)
|
|
1588
|
+
--transport TYPE MCP HTTP transport: auto|streamable|sse (default: auto)
|
|
1589
|
+
--base-url URL Override base URL from spec
|
|
1590
|
+
--graphql-schema SRC GraphQL SDL or introspection JSON schema FILE|URL
|
|
1591
|
+
--cache-key KEY Custom cache key
|
|
1592
|
+
--cache-ttl SECONDS Cache TTL (default: 3600)
|
|
1593
|
+
--refresh Bypass cache
|
|
1594
|
+
--list List available subcommands
|
|
1595
|
+
--search PATTERN Search commands by name or description
|
|
1596
|
+
--include GLOBS Include command globs (comma-separated)
|
|
1597
|
+
--exclude GLOBS Exclude command globs (comma-separated)
|
|
1598
|
+
--methods METHODS OpenAPI method filter, e.g. GET,POST
|
|
1599
|
+
--fields FIELDS Override GraphQL selection set
|
|
1600
|
+
--selection-depth N GraphQL default selection depth (default: 2)
|
|
1601
|
+
--stdin Read GraphQL variables from stdin JSON
|
|
1602
|
+
--pretty Pretty-print JSON output
|
|
1603
|
+
--raw Print raw response body
|
|
1604
|
+
--head N Limit output to first N array records
|
|
1605
|
+
--help, -h Show help
|
|
1606
|
+
--version Show version
|
|
1607
|
+
`);
|
|
1608
|
+
}
|
|
1609
|
+
function writeStdout2(text) {
|
|
1610
|
+
console.log(text.replace(/\n$/, ""));
|
|
1611
|
+
}
|
|
1612
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1613
|
+
const code = await run();
|
|
1614
|
+
process.exitCode = code;
|
|
1615
|
+
}
|
|
1616
|
+
export {
|
|
1617
|
+
run
|
|
1618
|
+
};
|
|
1619
|
+
//# sourceMappingURL=main.js.map
|