poe-code 3.0.185 → 3.0.186

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.
Files changed (33) hide show
  1. package/dist/index.js +6 -1
  2. package/dist/index.js.map +2 -2
  3. package/package.json +6 -1
  4. package/packages/cmdkit-openapi/dist/api-command.d.ts +7 -0
  5. package/packages/cmdkit-openapi/dist/api-command.js +4 -0
  6. package/packages/cmdkit-openapi/dist/auth/bearer-token-auth.d.ts +8 -0
  7. package/packages/cmdkit-openapi/dist/auth/bearer-token-auth.js +216 -0
  8. package/packages/cmdkit-openapi/dist/auth/types.d.ts +9 -0
  9. package/packages/cmdkit-openapi/dist/auth/types.js +1 -0
  10. package/packages/cmdkit-openapi/dist/bin/generate.d.ts +40 -0
  11. package/packages/cmdkit-openapi/dist/bin/generate.js +248 -0
  12. package/packages/cmdkit-openapi/dist/define-client.d.ts +20 -0
  13. package/packages/cmdkit-openapi/dist/define-client.js +148 -0
  14. package/packages/cmdkit-openapi/dist/generate.d.ts +210 -0
  15. package/packages/cmdkit-openapi/dist/generate.js +1091 -0
  16. package/packages/cmdkit-openapi/dist/group-by-noun.d.ts +6 -0
  17. package/packages/cmdkit-openapi/dist/group-by-noun.js +17 -0
  18. package/packages/cmdkit-openapi/dist/http.d.ts +26 -0
  19. package/packages/cmdkit-openapi/dist/http.js +123 -0
  20. package/packages/cmdkit-openapi/dist/index.d.ts +12 -0
  21. package/packages/cmdkit-openapi/dist/index.js +6 -0
  22. package/packages/cmdkit-openapi/dist/interpreter.d.ts +6 -0
  23. package/packages/cmdkit-openapi/dist/interpreter.js +289 -0
  24. package/packages/cmdkit-openapi/dist/lock.d.ts +14 -0
  25. package/packages/cmdkit-openapi/dist/lock.js +48 -0
  26. package/packages/cmdkit-openapi/dist/naming.d.ts +24 -0
  27. package/packages/cmdkit-openapi/dist/naming.js +218 -0
  28. package/packages/cmdkit-openapi/dist/request-shape.d.ts +15 -0
  29. package/packages/cmdkit-openapi/dist/request-shape.js +5 -0
  30. package/packages/cmdkit-openapi/dist/runtime.d.ts +13 -0
  31. package/packages/cmdkit-openapi/dist/runtime.js +94 -0
  32. package/packages/cmdkit-openapi/dist/spec-source.d.ts +11 -0
  33. package/packages/cmdkit-openapi/dist/spec-source.js +63 -0
@@ -0,0 +1,218 @@
1
+ import { UserError } from "@poe-code/cmdkit";
2
+ export const METHOD_DEFAULTS = {
3
+ delete: {
4
+ collection: "delete",
5
+ resource: "delete",
6
+ confirm: true
7
+ },
8
+ get: {
9
+ collection: "list",
10
+ resource: "view",
11
+ genericVerbs: ["get", "list", "view"],
12
+ preferOperationIdWhenPathTailIsGeneric: true
13
+ }
14
+ };
15
+ export function deriveNoun(operation, path, operationId) {
16
+ const noun = operation.tags?.[0];
17
+ if (typeof noun !== "string" || noun.length === 0) {
18
+ const fallbackNoun = deriveNounFromPath(path);
19
+ if (fallbackNoun === undefined) {
20
+ throw new UserError(`Operation ${JSON.stringify(operationId)} must define tags[0] or a static resource segment in the path to derive a command noun.`);
21
+ }
22
+ return fallbackNoun;
23
+ }
24
+ return toKebabCase(noun);
25
+ }
26
+ export function deriveVerb(method, path, operation, operationId, noun) {
27
+ const segments = splitPathSegments(path);
28
+ const defaults = METHOD_DEFAULTS[method];
29
+ if (defaults !== undefined) {
30
+ const lastSegment = segments.at(-1);
31
+ if (isPathTemplateSegment(lastSegment)) {
32
+ return defaults.resource;
33
+ }
34
+ if (defaults.preferOperationIdWhenPathTailIsGeneric === true) {
35
+ const derived = deriveVerbFromOperationId(method, operation.operationId, noun, defaults);
36
+ const pathTail = lastSegment === undefined ? undefined : toKebabCase(lastSegment);
37
+ if (derived !== undefined) {
38
+ if (pathTail === undefined || derived !== pathTail) {
39
+ return derived;
40
+ }
41
+ if (!operationIdStartsWithCollectionVerb(method, operation.operationId, noun, defaults)) {
42
+ return pathTail;
43
+ }
44
+ }
45
+ }
46
+ return defaults.collection;
47
+ }
48
+ const derived = deriveVerbFromOperationId(method, operation.operationId, noun);
49
+ if (derived !== undefined) {
50
+ return derived;
51
+ }
52
+ throw new UserError(`Operation ${JSON.stringify(operationId)} is missing an operationId, so cmdkit-openapi cannot derive a stable command verb.`);
53
+ }
54
+ export function normalizeParamName(name) {
55
+ return toCamelCase(name);
56
+ }
57
+ export function toPascalCase(value) {
58
+ const camel = toCamelCase(value);
59
+ return camel.length === 0 ? camel : `${camel[0]?.toUpperCase() ?? ""}${camel.slice(1)}`;
60
+ }
61
+ export function toCamelCase(value) {
62
+ return splitWords(value)
63
+ .map((word, index) => (index === 0 ? word : `${word[0]?.toUpperCase() ?? ""}${word.slice(1)}`))
64
+ .join("");
65
+ }
66
+ export function toKebabCase(value) {
67
+ return splitWords(value).join("-");
68
+ }
69
+ export function toCliFlag(value) {
70
+ return toKebabCase(value);
71
+ }
72
+ export function splitWords(value) {
73
+ const normalized = value
74
+ .replaceAll("-", " ")
75
+ .replaceAll("_", " ")
76
+ .replaceAll(".", " ")
77
+ .replaceAll("/", " ");
78
+ return normalized
79
+ .split(" ")
80
+ .flatMap(splitCamelCaseWord)
81
+ .filter((word) => word.length > 0)
82
+ .map((word) => word.toLowerCase());
83
+ }
84
+ export function toMcpPrefix(name) {
85
+ return name.replaceAll("-", "_");
86
+ }
87
+ export function isIdentifierName(value) {
88
+ return /^[$A-Z_a-z][$\w]*$/u.test(value);
89
+ }
90
+ function splitPathSegments(path) {
91
+ return path.split("/").filter((segment) => segment.length > 0);
92
+ }
93
+ function deriveNounFromPath(path) {
94
+ return splitPathSegments(path)
95
+ .find((segment) => !isPathTemplateSegment(segment) && segment !== "api" && !isVersionWord(segment))
96
+ ?.split("/")
97
+ .map((segment) => toKebabCase(segment))
98
+ .find((segment) => segment.length > 0);
99
+ }
100
+ function isPathTemplateSegment(segment) {
101
+ return segment !== undefined && segment.startsWith("{") && segment.endsWith("}");
102
+ }
103
+ function deriveVerbFromOperationId(method, operationId, noun, defaults) {
104
+ const words = normalizeOperationIdWords(method, operationId, noun);
105
+ if (words === undefined) {
106
+ return undefined;
107
+ }
108
+ const verbWords = stripLeadingGenericVerb(words, defaults?.genericVerbs);
109
+ return verbWords.length === 0 ? undefined : verbWords.join("-");
110
+ }
111
+ function operationIdStartsWithCollectionVerb(method, operationId, noun, defaults) {
112
+ const words = normalizeOperationIdWords(method, operationId, noun);
113
+ return words?.[0] === defaults.collection;
114
+ }
115
+ function normalizeOperationIdWords(method, operationId, noun) {
116
+ if (operationId === undefined) {
117
+ return undefined;
118
+ }
119
+ const nounWords = splitWords(noun);
120
+ return dedupeAdjacentWords(trimTrailingNounUnlessItConsumesAll(trimTrailingMethod(trimLeadingWords(splitWords(operationId).filter((word) => !isVersionWord(word)), nounWords), method), nounWords));
121
+ }
122
+ function stripLeadingGenericVerb(words, genericVerbs) {
123
+ if (genericVerbs === undefined || words.length <= 1 || !genericVerbs.includes(words[0] ?? "")) {
124
+ return words;
125
+ }
126
+ let start = 1;
127
+ while (start < words.length - 1 && words[start] === words[0]) {
128
+ start += 1;
129
+ }
130
+ return words.slice(start);
131
+ }
132
+ function trimTrailingNounUnlessItConsumesAll(words, nounWords) {
133
+ const withoutTrailingNoun = trimTrailingWords(words, nounWords);
134
+ return withoutTrailingNoun.length === 0 ? words : withoutTrailingNoun;
135
+ }
136
+ function trimLeadingWords(words, prefix) {
137
+ if (prefix.length === 0 || prefix.length > words.length) {
138
+ return words;
139
+ }
140
+ for (let index = 0; index < prefix.length; index += 1) {
141
+ if (words[index] !== prefix[index]) {
142
+ return words;
143
+ }
144
+ }
145
+ return words.slice(prefix.length);
146
+ }
147
+ function trimTrailingWords(words, suffix) {
148
+ if (suffix.length === 0 || suffix.length >= words.length) {
149
+ return words;
150
+ }
151
+ const start = words.length - suffix.length;
152
+ for (let index = 0; index < suffix.length; index += 1) {
153
+ if (words[start + index] !== suffix[index]) {
154
+ return words;
155
+ }
156
+ }
157
+ return words.slice(0, start);
158
+ }
159
+ function trimTrailingMethod(words, method) {
160
+ return words.at(-1) === method ? words.slice(0, -1) : words;
161
+ }
162
+ function dedupeAdjacentWords(words) {
163
+ if (words.length === 0) {
164
+ return [];
165
+ }
166
+ const deduped = [words[0] ?? ""];
167
+ for (let index = 1; index < words.length; index += 1) {
168
+ const word = words[index] ?? "";
169
+ if (word !== deduped.at(-1)) {
170
+ deduped.push(word);
171
+ }
172
+ }
173
+ return deduped;
174
+ }
175
+ function isVersionWord(word) {
176
+ if (!word.startsWith("v") || word.length < 2) {
177
+ return false;
178
+ }
179
+ for (const character of word.slice(1)) {
180
+ if (character < "0" || character > "9") {
181
+ return false;
182
+ }
183
+ }
184
+ return true;
185
+ }
186
+ function splitCamelCaseWord(value) {
187
+ if (value.length === 0) {
188
+ return [];
189
+ }
190
+ const words = [value[0] ?? ""];
191
+ for (let index = 1; index < value.length; index += 1) {
192
+ const character = value[index] ?? "";
193
+ const previous = value[index - 1] ?? "";
194
+ const next = value[index + 1];
195
+ if (shouldStartNewWord(previous, character, next)) {
196
+ words.push(character);
197
+ continue;
198
+ }
199
+ const currentWord = words.at(-1) ?? "";
200
+ words[words.length - 1] = `${currentWord}${character}`;
201
+ }
202
+ return words;
203
+ }
204
+ function shouldStartNewWord(previous, character, next) {
205
+ if (!isUppercaseLetter(character)) {
206
+ return false;
207
+ }
208
+ if (isLowercaseLetter(previous)) {
209
+ return true;
210
+ }
211
+ return isUppercaseLetter(previous) && next !== undefined && isLowercaseLetter(next);
212
+ }
213
+ function isLowercaseLetter(character) {
214
+ return character >= "a" && character <= "z";
215
+ }
216
+ function isUppercaseLetter(character) {
217
+ return character >= "A" && character <= "Z";
218
+ }
@@ -0,0 +1,15 @@
1
+ export declare const REQUEST_PARAM_SECTIONS: readonly [{
2
+ readonly location: "path";
3
+ readonly key: "pathParams";
4
+ readonly omittable: false;
5
+ }, {
6
+ readonly location: "query";
7
+ readonly key: "query";
8
+ readonly omittable: false;
9
+ }, {
10
+ readonly location: "body";
11
+ readonly key: "body";
12
+ readonly omittable: true;
13
+ }];
14
+ export type RequestParamSection = (typeof REQUEST_PARAM_SECTIONS)[number];
15
+ export type RequestSectionKey = RequestParamSection["key"];
@@ -0,0 +1,5 @@
1
+ export const REQUEST_PARAM_SECTIONS = [
2
+ { location: "path", key: "pathParams", omittable: false },
3
+ { location: "query", key: "query", omittable: false },
4
+ { location: "body", key: "body", omittable: true }
5
+ ];
@@ -0,0 +1,13 @@
1
+ import { type CommandNode } from "@poe-code/cmdkit";
2
+ import { type DefineClientOptions, type DefinedClient, type OpenApiClientServices } from "./define-client.js";
3
+ import { type OpenApiDocument } from "./generate.js";
4
+ import { type OpenApiSourceFileSystem } from "./spec-source.js";
5
+ export type OpenApiDocumentSource = OpenApiDocument | string | URL;
6
+ export interface CommandsFromSpecOptions {
7
+ cwd?: string;
8
+ fetch?: typeof globalThis.fetch;
9
+ fs?: OpenApiSourceFileSystem;
10
+ }
11
+ export type DefineClientFromSpecOptions<TServices extends object = Record<string, never>> = Omit<DefineClientOptions<TServices>, "commands"> & CommandsFromSpecOptions;
12
+ export declare function commandsFromSpec(source: OpenApiDocumentSource, options?: CommandsFromSpecOptions): Promise<CommandNode<OpenApiClientServices>[]>;
13
+ export declare function defineClientFromSpec<TServices extends object = Record<string, never>>(spec: OpenApiDocumentSource, options: DefineClientFromSpecOptions<TServices>): Promise<DefinedClient<TServices>>;
@@ -0,0 +1,94 @@
1
+ import fs from "node:fs/promises";
2
+ import { defineCommand, defineGroup, S } from "@poe-code/cmdkit";
3
+ import { defineClient } from "./define-client.js";
4
+ import { collectSchemaOptionEntries, collectGeneratedCommands } from "./generate.js";
5
+ import { groupByNoun } from "./group-by-noun.js";
6
+ import { requestJson } from "./http.js";
7
+ import { buildRequestShape, executePreflightBlocks } from "./interpreter.js";
8
+ import { parseOpenApiDocument, readOpenApiSourceText } from "./spec-source.js";
9
+ const RUNTIME_COMMAND_SCOPE = ["cli", "mcp", "sdk"];
10
+ export async function commandsFromSpec(source, options = {}) {
11
+ const document = await resolveDocument(source, options);
12
+ return createRuntimeGroups(collectGeneratedCommands(document));
13
+ }
14
+ export async function defineClientFromSpec(spec, options) {
15
+ const { cwd, fetch, fs: specFs, ...clientOptions } = options;
16
+ const commands = (await commandsFromSpec(spec, {
17
+ cwd,
18
+ fetch,
19
+ fs: specFs
20
+ }));
21
+ return defineClient({ ...clientOptions, commands });
22
+ }
23
+ async function resolveDocument(source, options) {
24
+ if (typeof source !== "string" && !(source instanceof URL)) {
25
+ return source;
26
+ }
27
+ const sourceText = await readOpenApiSourceText(source, {
28
+ cwd: options.cwd ?? process.cwd(),
29
+ fetch: options.fetch ?? globalThis.fetch,
30
+ fs: options.fs ?? fs
31
+ });
32
+ return parseOpenApiDocument(sourceText, source);
33
+ }
34
+ function createRuntimeGroups(commands) {
35
+ return groupByNoun(commands).map(({ noun, commands: nounCommands }) => defineGroup({
36
+ name: noun,
37
+ children: nounCommands.map((command) => createRuntimeCommand(command))
38
+ }));
39
+ }
40
+ function createRuntimeCommand(command) {
41
+ const paramsSchema = S.Object(Object.fromEntries(command.params.map((param) => [param.paramName, createRuntimeParamSchema(param)])), command.paramsSchemaOptions);
42
+ return defineCommand({
43
+ name: command.verb,
44
+ ...(command.description === undefined ? {} : { description: command.description }),
45
+ scope: RUNTIME_COMMAND_SCOPE,
46
+ ...(command.confirm ? { confirm: true } : {}),
47
+ params: paramsSchema,
48
+ handler: createRuntimeHandler(command)
49
+ });
50
+ }
51
+ function createRuntimeHandler(command) {
52
+ return async ({ params, baseUrl, tokenSource, fetch }) => {
53
+ const resolvedValues = executePreflightBlocks(command.preflightBlocks, params);
54
+ const requestShape = buildRequestShape(command.requestFields, command.sectionRenders, command.optionalSections, params, resolvedValues);
55
+ return requestJson({
56
+ baseUrl,
57
+ path: command.path,
58
+ method: command.method,
59
+ auth: command.auth,
60
+ tokenSource,
61
+ fetch,
62
+ dryRun: params.dryRun,
63
+ verbose: params.verbose,
64
+ ...requestShape
65
+ });
66
+ };
67
+ }
68
+ function createRuntimeParamSchema(param) {
69
+ const definition = createRuntimeDefinition(param.definition, param.description, param.shortFlag, param.scope);
70
+ return param.optional ? S.Optional(definition) : definition;
71
+ }
72
+ function createRuntimeDefinition(definition, description, shortFlag, scope) {
73
+ const options = createRuntimeSchemaOptions(definition, description, shortFlag, scope);
74
+ return RUNTIME_DEFINITION_BUILDERS[definition.kind](definition, options);
75
+ }
76
+ const RUNTIME_DEFINITION_BUILDERS = {
77
+ array: (definition, options) => {
78
+ const itemDefinition = createRuntimeDefinition(definition.itemDefinition, undefined, undefined, undefined);
79
+ return options === undefined ? S.Array(itemDefinition) : S.Array(itemDefinition, options);
80
+ },
81
+ boolean: (_definition, options) => options === undefined ? S.Boolean() : S.Boolean(options),
82
+ enum: (definition, options) => options === undefined ? S.Enum(definition.enumValues) : S.Enum(definition.enumValues, options),
83
+ number: (_definition, options) => options === undefined ? S.Number() : S.Number(options),
84
+ string: (_definition, options) => options === undefined ? S.String() : S.String(options)
85
+ };
86
+ function createRuntimeSchemaOptions(definition, description, shortFlag, scope) {
87
+ const options = Object.fromEntries(collectSchemaOptionEntries({
88
+ definition,
89
+ description,
90
+ shortFlag,
91
+ scope
92
+ }).map(({ key, value }) => [key, Array.isArray(value) ? [...value] : value]));
93
+ return Object.keys(options).length === 0 ? undefined : options;
94
+ }
@@ -0,0 +1,11 @@
1
+ import type { OpenApiDocument } from "./generate.js";
2
+ export interface OpenApiSourceFileSystem {
3
+ readFile(filePath: string, encoding: BufferEncoding): Promise<string>;
4
+ }
5
+ export interface OpenApiSourceServices {
6
+ cwd: string;
7
+ fetch: typeof globalThis.fetch;
8
+ fs: OpenApiSourceFileSystem;
9
+ }
10
+ export declare function readOpenApiSourceText(input: string | URL, services: OpenApiSourceServices): Promise<string>;
11
+ export declare function parseOpenApiDocument(sourceText: string, input: string | URL): OpenApiDocument;
@@ -0,0 +1,63 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { UserError } from "@poe-code/cmdkit";
5
+ export async function readOpenApiSourceText(input, services) {
6
+ const inputUrl = input instanceof URL ? input : tryParseUrl(input);
7
+ const sourceLabel = formatSourceLabel(input);
8
+ try {
9
+ if (typeof input === "string" && inputUrl === null) {
10
+ return await services.fs.readFile(path.resolve(services.cwd, input), "utf8");
11
+ }
12
+ if (inputUrl === null) {
13
+ throw new UserError(`Unsupported OpenAPI input ${JSON.stringify(sourceLabel)}.`);
14
+ }
15
+ if (inputUrl.protocol === "file:") {
16
+ return await services.fs.readFile(fileURLToPath(inputUrl), "utf8");
17
+ }
18
+ if (inputUrl.protocol !== "http:" && inputUrl.protocol !== "https:") {
19
+ throw new UserError(`Unsupported OpenAPI input URL protocol ${JSON.stringify(inputUrl.protocol)}.`);
20
+ }
21
+ const response = await services.fetch(inputUrl.toString());
22
+ if (!response.ok) {
23
+ throw new UserError(`Failed to fetch ${JSON.stringify(inputUrl.toString())}: ${response.status} ${response.statusText}`);
24
+ }
25
+ return await response.text();
26
+ }
27
+ catch (error) {
28
+ if (error instanceof UserError) {
29
+ throw error;
30
+ }
31
+ throw new UserError(`Failed to read OpenAPI document ${JSON.stringify(sourceLabel)}: ${getErrorMessage(error)}`);
32
+ }
33
+ }
34
+ export function parseOpenApiDocument(sourceText, input) {
35
+ let parsed;
36
+ try {
37
+ parsed = parseYaml(sourceText);
38
+ }
39
+ catch (error) {
40
+ throw new UserError(`Failed to parse OpenAPI document ${JSON.stringify(formatSourceLabel(input))}: ${getErrorMessage(error)}`);
41
+ }
42
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
43
+ throw new UserError(`OpenAPI document ${JSON.stringify(formatSourceLabel(input))} must parse to an object.`);
44
+ }
45
+ return parsed;
46
+ }
47
+ function tryParseUrl(value) {
48
+ try {
49
+ return new URL(value);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function formatSourceLabel(source) {
56
+ return source instanceof URL ? source.toString() : source;
57
+ }
58
+ function getErrorMessage(error) {
59
+ if (error instanceof Error) {
60
+ return error.message;
61
+ }
62
+ return String(error);
63
+ }