poe-code 4.0.14 → 4.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "4.0.14",
3
+ "version": "4.0.16",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,6 @@ export declare function runPlanBrowser(options: {
9
9
  fs: DiscoveryFs & Partial<ActionFs>;
10
10
  kind?: PlanKind;
11
11
  variables?: Record<string, string | undefined>;
12
- onCreatePlan?: () => Promise<void>;
13
12
  runExplorerImpl?: RunExplorerImpl;
14
13
  }): Promise<void>;
15
14
  export {};
@@ -28,8 +28,7 @@ export async function runPlanBrowser(options) {
28
28
  plans,
29
29
  fs: options.fs,
30
30
  variables: options.variables ?? process.env,
31
- onRefresh: discover,
32
- onCreatePlan: options.onCreatePlan
31
+ onRefresh: discover
33
32
  });
34
33
  await (options.runExplorerImpl ?? runExplorer)(config);
35
34
  }
@@ -5,7 +5,6 @@ export interface BuildPlanExplorerConfigOptions {
5
5
  fs: ActionFs & DiscoveryFs;
6
6
  variables: Record<string, string | undefined>;
7
7
  onRefresh: () => Promise<PlanEntry[]>;
8
- onCreatePlan?: () => Promise<void>;
9
8
  promptSaveReason?: (entry: PlanEntry) => Promise<string | null>;
10
9
  loadDetailMarkdown?: (entry: PlanEntry, fs: ActionFs & DiscoveryFs) => Promise<string>;
11
10
  }
@@ -98,19 +98,6 @@ export function buildPlanExplorerConfig(options) {
98
98
  }
99
99
  }
100
100
  ];
101
- if (options.onCreatePlan !== undefined) {
102
- actions.push({
103
- id: "new",
104
- key: "n",
105
- primary: true,
106
- label: "New plan",
107
- predicate: () => options.onCreatePlan != null,
108
- handler: async (ctx) => {
109
- await ctx.suspendAnd(() => options.onCreatePlan());
110
- await ctx.refresh();
111
- }
112
- });
113
- }
114
101
  return {
115
102
  title: "Plans",
116
103
  rows: async () => rows,
@@ -269,4 +269,5 @@ export declare function generateSkill(document: OpenApiDocument, options?: {
269
269
  export declare function collectGeneratedCommands(document: OpenApiDocument, config?: ToolcraftConfig): GeneratedCommand[];
270
270
  export declare function collectGeneratedCommand(document: OpenApiDocument, path: string, method: HttpMethod): GeneratedCommand;
271
271
  export declare function collectSchemaOptionEntries(param: RenderSchemaOptionsInput): SchemaOptionEntry[];
272
+ export declare function collectTagDescriptions(document: OpenApiDocument): Map<string, string>;
272
273
  export {};
@@ -227,9 +227,7 @@ function refreshGeneratedCommandNames(command) {
227
227
  command.exportName = command.topLevel
228
228
  ? `${toCamelCase(command.verb)}Command`
229
229
  : `${toCamelCase(command.noun)}${toPascalCase(command.verb)}Command`;
230
- command.filePath = command.topLevel
231
- ? `${command.verb}.ts`
232
- : `${command.noun}/${command.verb}.ts`;
230
+ command.filePath = command.topLevel ? `${command.verb}.ts` : `${command.noun}/${command.verb}.ts`;
233
231
  }
234
232
  export function collectGeneratedCommand(document, path, method) {
235
233
  const normalizedDocument = normalizeOpenApiDocument(document);
@@ -616,10 +614,12 @@ function collectRequestBodyParams(document, operation, operationId, method) {
616
614
  function resolveSuccessResponse(document, operation, operationId) {
617
615
  let textualMediaType;
618
616
  let binaryMediaType;
619
- for (const [statusCode, response] of Object.entries(operation.responses ?? {})) {
620
- if (!isSuccessStatusCode(statusCode)) {
621
- continue;
622
- }
617
+ const responses = Object.entries(operation.responses ?? {});
618
+ const explicitSuccessResponses = responses.filter(([statusCode]) => isSuccessStatusCode(statusCode));
619
+ const candidateResponses = explicitSuccessResponses.length > 0
620
+ ? explicitSuccessResponses
621
+ : responses.filter(([statusCode]) => statusCode === "default");
622
+ for (const [statusCode, response] of candidateResponses) {
623
623
  const resolvedResponse = expectResponse(document, response, operationId, statusCode);
624
624
  const declaredMediaTypes = Object.entries(resolvedResponse.content ?? {})
625
625
  .filter(([, mediaType]) => mediaType !== undefined)
@@ -1163,7 +1163,7 @@ function collectPathPlaceholders(path) {
1163
1163
  return placeholders;
1164
1164
  }
1165
1165
  function isSuccessStatusCode(statusCode) {
1166
- if (statusCode === "default" || statusCode === "2XX") {
1166
+ if (statusCode === "2XX") {
1167
1167
  return true;
1168
1168
  }
1169
1169
  return (statusCode.length === 3 &&
@@ -1858,7 +1858,7 @@ function resolveQueryObjectSerialization(parameter, operationId) {
1858
1858
  }
1859
1859
  throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-object serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: deepObject with explode true.`);
1860
1860
  }
1861
- function collectTagDescriptions(document) {
1861
+ export function collectTagDescriptions(document) {
1862
1862
  const descriptions = new Map();
1863
1863
  for (const tag of document.tags ?? []) {
1864
1864
  if (typeof tag.name !== "string" || tag.name.length === 0) {
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { text as designText } from "toolcraft-design";
4
- import { HttpError, UserError, createHttpError } from "toolcraft";
4
+ import { HttpError, UserError, createHttpError, shouldEmitDiagnostic } from "toolcraft";
5
5
  import { classifyNetworkError } from "./network-error.js";
6
6
  import { redactHeaders, redactHeaderValue, redactSensitiveQueryValues } from "./redaction.js";
7
7
  export { HttpError };
@@ -29,7 +29,7 @@ export async function requestJson(options) {
29
29
  : {};
30
30
  const headers = createHeaders(token, hasBody, { ...options.headers, ...idempotencyHeader }, options.accept, options.bodyMode, options.contentType);
31
31
  emitHttpDebug(options, `${method} ${url}`, { method, url });
32
- emitHttpTrace(options, "HTTP request transcript", formatVerboseRequestTranscript(method, url, headers, options.body));
32
+ emitHttpTrace(options, "HTTP request transcript", () => formatVerboseRequestTranscript(method, url, headers, options.body));
33
33
  const response = await fetchWithRetries(options, url, {
34
34
  method,
35
35
  headers,
@@ -42,7 +42,7 @@ export async function requestJson(options) {
42
42
  if (response.ok && options.responseMode === "binary") {
43
43
  const bytes = new Uint8Array(await response.arrayBuffer());
44
44
  if (bytes.byteLength === 0) {
45
- emitHttpTrace(options, "HTTP response transcript", formatVerboseResponseTranscript(response, responseHeaders));
45
+ emitHttpTrace(options, "HTTP response transcript", () => formatVerboseResponseTranscript(response, responseHeaders));
46
46
  return formatRawResponseResult(undefined, response, options.rawResponse);
47
47
  }
48
48
  const body = {
@@ -51,17 +51,17 @@ export async function requestJson(options) {
51
51
  byteLength: bytes.byteLength,
52
52
  data: Buffer.from(bytes).toString("base64")
53
53
  };
54
- emitHttpTrace(options, "HTTP response transcript", formatVerboseResponseTranscript(response, responseHeaders, body));
54
+ emitHttpTrace(options, "HTTP response transcript", () => formatVerboseResponseTranscript(response, responseHeaders, body));
55
55
  return formatRawResponseResult(body, response, options.rawResponse);
56
56
  }
57
57
  const text = await response.text();
58
58
  if (response.ok) {
59
59
  if (text.length === 0) {
60
- emitHttpTrace(options, "HTTP response transcript", formatVerboseResponseTranscript(response, responseHeaders));
60
+ emitHttpTrace(options, "HTTP response transcript", () => formatVerboseResponseTranscript(response, responseHeaders));
61
61
  return formatRawResponseResult(undefined, response, options.rawResponse);
62
62
  }
63
63
  if (options.responseMode === "text") {
64
- emitHttpTrace(options, "HTTP response transcript", formatVerboseResponseTranscript(response, responseHeaders, text));
64
+ emitHttpTrace(options, "HTTP response transcript", () => formatVerboseResponseTranscript(response, responseHeaders, text));
65
65
  return formatRawResponseResult(text, response, options.rawResponse);
66
66
  }
67
67
  if (!isJsonContentType(contentType)) {
@@ -92,7 +92,7 @@ export async function requestJson(options) {
92
92
  message: "Expected a valid JSON response body but received malformed JSON."
93
93
  });
94
94
  }
95
- emitHttpTrace(options, "HTTP response transcript", formatVerboseResponseTranscript(response, responseHeaders, body));
95
+ emitHttpTrace(options, "HTTP response transcript", () => formatVerboseResponseTranscript(response, responseHeaders, body));
96
96
  return formatRawResponseResult(body, response, options.rawResponse);
97
97
  }
98
98
  if (response.status === 401 && options.auth === "required") {
@@ -153,13 +153,17 @@ function emitHttpDebug(options, message, data) {
153
153
  data
154
154
  });
155
155
  }
156
- function emitHttpTrace(options, message, lines) {
157
- options.diagnostics?.emit({
156
+ function emitHttpTrace(options, message, createLines) {
157
+ const diagnostics = options.diagnostics;
158
+ if (diagnostics === undefined || !shouldEmitDiagnostic("trace", diagnostics.level)) {
159
+ return;
160
+ }
161
+ diagnostics.emit({
158
162
  level: "trace",
159
163
  message,
160
164
  category: "http",
161
165
  data: {
162
- transcript: formatTranscriptLines(lines)
166
+ transcript: formatTranscriptLines(createLines())
163
167
  }
164
168
  });
165
169
  }
@@ -20,6 +20,8 @@ export function normalizeOpenApiDocument(document) {
20
20
  }
21
21
  const consumes = readStringArray(source.consumes);
22
22
  const produces = readStringArray(source.produces);
23
+ const { swagger: _swagger, ...sourceWithoutSwagger } = source;
24
+ void _swagger;
23
25
  const paths = Object.fromEntries(Object.entries(source.paths ?? {}).map(([path, pathItem]) => [
24
26
  path,
25
27
  pathItem === undefined
@@ -36,7 +38,7 @@ export function normalizeOpenApiDocument(document) {
36
38
  : { securitySchemes: rewriteReferences(source.securityDefinitions) })
37
39
  };
38
40
  return rewriteReferences({
39
- ...source,
41
+ ...sourceWithoutSwagger,
40
42
  openapi: "3.0.3",
41
43
  paths,
42
44
  components
@@ -47,7 +49,11 @@ function normalizePathItem(pathItem, documentConsumes, documentProduces, reusabl
47
49
  ...pathItem,
48
50
  ...(pathItem.parameters === undefined
49
51
  ? {}
50
- : { parameters: pathItem.parameters.map((parameter) => normalizeParameter(resolveReusableParameter(parameter, reusableParameters))).filter(isOpenApiParameter) })
52
+ : {
53
+ parameters: pathItem.parameters
54
+ .map((parameter) => normalizeParameter(resolveReusableParameter(parameter, reusableParameters)))
55
+ .filter(isOpenApiParameter)
56
+ })
51
57
  };
52
58
  for (const method of HTTP_METHODS) {
53
59
  const operation = pathItem[method];
@@ -136,7 +142,9 @@ function normalizeResponse(response, produces) {
136
142
  ...response,
137
143
  content: Object.fromEntries(mediaTypes.map((mediaType) => [
138
144
  mediaType,
139
- { schema: rewriteReferences(responseRecord.schema) }
145
+ {
146
+ schema: rewriteReferences(responseRecord.schema)
147
+ }
140
148
  ]))
141
149
  };
142
150
  }
@@ -147,7 +155,9 @@ function isSwaggerFormParameter(parameter) {
147
155
  return !isReferenceObject(parameter) && parameter.in === "formData";
148
156
  }
149
157
  function normalizeFormParameters(parameters, consumes) {
150
- const required = parameters.filter((parameter) => parameter.required === true).map((parameter) => parameter.name);
158
+ const required = parameters
159
+ .filter((parameter) => parameter.required === true)
160
+ .map((parameter) => parameter.name);
151
161
  const mediaType = consumes.find((value) => value.toLowerCase() === "multipart/form-data") ??
152
162
  "application/x-www-form-urlencoded";
153
163
  return {
@@ -161,7 +171,9 @@ function normalizeFormParameters(parameters, consumes) {
161
171
  parameter.name,
162
172
  {
163
173
  ...parameter.schema,
164
- ...(parameter.description === undefined ? {} : { description: parameter.description })
174
+ ...(parameter.description === undefined
175
+ ? {}
176
+ : { description: parameter.description })
165
177
  }
166
178
  ]))
167
179
  }
@@ -184,7 +196,9 @@ function rewriteReferences(value) {
184
196
  }
185
197
  return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
186
198
  key,
187
- key === "$ref" && typeof entry === "string" ? rewriteReference(entry) : rewriteReferences(entry)
199
+ key === "$ref" && typeof entry === "string"
200
+ ? rewriteReference(entry)
201
+ : rewriteReferences(entry)
188
202
  ]));
189
203
  }
190
204
  function rewriteReference(reference) {
@@ -194,7 +208,9 @@ function rewriteReference(reference) {
194
208
  .replace("#/responses/", "#/components/responses/");
195
209
  }
196
210
  function readStringArray(value) {
197
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
211
+ return Array.isArray(value)
212
+ ? value.filter((entry) => typeof entry === "string")
213
+ : [];
198
214
  }
199
215
  function isRecord(value) {
200
216
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1,14 +1,20 @@
1
1
  import { type CommandNode } from "../../toolcraft/dist/index.js";
2
2
  import { type DefineClientOptions, type DefinedClient, type OpenApiClientServices } from "./define-client.js";
3
3
  import { type OpenApiDocument } from "./generate.js";
4
+ import type { ToolcraftConfig } from "./config.js";
4
5
  import { type OpenApiSourceFileSystem } from "./spec-source.js";
5
6
  export type OpenApiDocumentSource = OpenApiDocument | string | URL;
6
7
  export interface CommandsFromSpecOptions {
7
8
  cwd?: string;
8
9
  fetch?: typeof globalThis.fetch;
9
10
  fs?: OpenApiSourceFileSystem;
11
+ config?: ToolcraftConfig;
10
12
  }
11
- export type DefineClientFromSpecOptions<TServices extends object = Record<string, never>> = Omit<DefineClientOptions<TServices>, "commands"> & CommandsFromSpecOptions;
13
+ export type DefineClientFromSpecOptions<TServices extends object = Record<string, never>> = Omit<DefineClientOptions<TServices>, "baseUrl" | "commands"> & CommandsFromSpecOptions & {
14
+ baseUrl?: string;
15
+ environment?: string;
16
+ env?: Record<string, string | undefined>;
17
+ };
12
18
  export interface ResolveOpenApiBaseUrlOptions {
13
19
  document: OpenApiDocument;
14
20
  environments?: Record<string, string>;
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { UserError, defineCommand, defineGroup, S } from "toolcraft";
3
3
  import { defineClient } from "./define-client.js";
4
- import { collectSchemaOptionEntries, collectGeneratedCommands } from "./generate.js";
4
+ import { collectTagDescriptions, collectSchemaOptionEntries, collectGeneratedCommands } from "./generate.js";
5
5
  import { groupByNoun } from "./group-by-noun.js";
6
6
  import { prepareMultipartFileInputs, requestJson, writeBinaryResponseOutput } from "./http.js";
7
7
  import { buildRequestShape, executePreflightBlocks } from "./interpreter.js";
@@ -9,16 +9,23 @@ import { parseOpenApiDocument, readOpenApiSourceText } from "./spec-source.js";
9
9
  const RUNTIME_COMMAND_SCOPE = ["cli", "mcp", "sdk"];
10
10
  export async function commandsFromSpec(source, options = {}) {
11
11
  const document = await resolveDocument(source, options);
12
- return createRuntimeGroups(collectGeneratedCommands(document));
12
+ return createRuntimeNodes(document, options.config);
13
13
  }
14
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 });
15
+ const { cwd, fetch, fs: specFs, config, baseUrl, environment, env, ...clientOptions } = options;
16
+ const document = await resolveDocument(spec, { cwd, fetch, fs: specFs, config });
17
+ const resolvedBaseUrl = baseUrl ??
18
+ resolveOpenApiBaseUrl({
19
+ document,
20
+ environments: config?.environments,
21
+ environment,
22
+ env: env ?? process.env
23
+ });
24
+ if (resolvedBaseUrl === undefined) {
25
+ throw new UserError("defineClientFromSpec could not resolve a base URL. Pass baseUrl, configure an environment, or define OpenAPI servers[0].url.");
26
+ }
27
+ const commands = createRuntimeNodes(document, config);
28
+ return defineClient({ ...clientOptions, baseUrl: resolvedBaseUrl, commands });
22
29
  }
23
30
  async function resolveDocument(source, options) {
24
31
  if (typeof source !== "string" && !(source instanceof URL)) {
@@ -49,17 +56,24 @@ export function resolveOpenApiBaseUrl(options) {
49
56
  function normalizeBaseUrl(value) {
50
57
  return new URL(value).toString().replace(/\/$/, "");
51
58
  }
52
- function createRuntimeGroups(commands) {
53
- return groupByNoun(commands).map(({ noun, commands: nounCommands }) => defineGroup({
54
- name: noun,
55
- children: nounCommands.map((command) => createRuntimeCommand(command))
56
- }));
59
+ function createRuntimeNodes(document, config) {
60
+ const commands = collectGeneratedCommands(document, config);
61
+ const tagDescriptions = collectTagDescriptions(document);
62
+ return [
63
+ ...commands.filter((command) => command.topLevel).map(createRuntimeCommand),
64
+ ...groupByNoun(commands).map(({ noun, commands: nounCommands }) => defineGroup({
65
+ name: noun,
66
+ description: tagDescriptions.get(noun),
67
+ children: nounCommands.map(createRuntimeCommand)
68
+ }))
69
+ ];
57
70
  }
58
71
  function createRuntimeCommand(command) {
59
72
  const paramsSchema = S.Object(Object.fromEntries(command.params.map((param) => [param.paramName, createRuntimeParamSchema(param)])), command.paramsSchemaOptions);
60
73
  return defineCommand({
61
74
  name: command.verb,
62
75
  ...(command.description === undefined ? {} : { description: command.description }),
76
+ ...(command.examples === undefined ? {} : { examples: command.examples }),
63
77
  scope: RUNTIME_COMMAND_SCOPE,
64
78
  ...(command.confirm ? { confirm: true } : {}),
65
79
  ...(command.positional.length > 0 ? { positional: command.positional } : {}),
@@ -90,22 +104,32 @@ function createRuntimeHandler(command) {
90
104
  tokenSource,
91
105
  fetch,
92
106
  diagnostics,
107
+ ...(command.rawResponse === true ? { rawResponse: params.rawResponse } : {}),
108
+ ...(command.idempotencyHeader === undefined
109
+ ? {}
110
+ : {
111
+ idempotency: {
112
+ header: command.idempotencyHeader,
113
+ enabled: true,
114
+ key: params.idempotencyKey
115
+ }
116
+ }),
93
117
  ...preparedRequestShape
94
118
  });
95
119
  return writeBinaryResponseOutput(result, command.responseMode === "binary" ? params.output : undefined, { fs, env });
96
120
  };
97
121
  }
98
122
  function createRuntimeParamSchema(param) {
99
- const definition = createRuntimeDefinition(param.definition, param.description, param.shortFlag, param.scope, param.global);
123
+ const definition = createRuntimeDefinition(param.definition, param.description, param.shortFlag, param.longAliases, param.scope, param.global);
100
124
  return param.optional ? S.Optional(definition) : definition;
101
125
  }
102
- function createRuntimeDefinition(definition, description, shortFlag, scope, global) {
103
- const options = createRuntimeSchemaOptions(definition, description, shortFlag, scope, global);
126
+ function createRuntimeDefinition(definition, description, shortFlag, longAliases, scope, global) {
127
+ const options = createRuntimeSchemaOptions(definition, description, shortFlag, longAliases, scope, global);
104
128
  return RUNTIME_DEFINITION_BUILDERS[definition.kind](definition, options);
105
129
  }
106
130
  const RUNTIME_DEFINITION_BUILDERS = {
107
131
  array: (definition, options) => {
108
- const itemDefinition = createRuntimeDefinition(definition.itemDefinition, undefined, undefined, undefined);
132
+ const itemDefinition = createRuntimeDefinition(definition.itemDefinition, undefined, undefined, undefined, undefined);
109
133
  return options === undefined ? S.Array(itemDefinition) : S.Array(itemDefinition, options);
110
134
  },
111
135
  boolean: (_definition, options) => (options === undefined ? S.Boolean() : S.Boolean(options)),
@@ -114,18 +138,19 @@ const RUNTIME_DEFINITION_BUILDERS = {
114
138
  number: (_definition, options) => (options === undefined ? S.Number() : S.Number(options)),
115
139
  object: (definition, options) => {
116
140
  const shape = Object.fromEntries(definition.properties.map((property) => {
117
- const propertySchema = createRuntimeDefinition(property.definition, undefined, undefined, undefined);
141
+ const propertySchema = createRuntimeDefinition(property.definition, undefined, undefined, undefined, undefined);
118
142
  return [property.name, property.optional ? S.Optional(propertySchema) : propertySchema];
119
143
  }));
120
144
  return options === undefined ? S.Object(shape) : S.Object(shape, options);
121
145
  },
122
146
  string: (_definition, options) => (options === undefined ? S.String() : S.String(options))
123
147
  };
124
- function createRuntimeSchemaOptions(definition, description, shortFlag, scope, global) {
148
+ function createRuntimeSchemaOptions(definition, description, shortFlag, longAliases, scope, global) {
125
149
  const options = Object.fromEntries(collectSchemaOptionEntries({
126
150
  definition,
127
151
  description,
128
152
  shortFlag,
153
+ longAliases,
129
154
  scope,
130
155
  global
131
156
  }).map(({ key, value }) => [key, Array.isArray(value) ? [...value] : value]));
@@ -48,10 +48,15 @@ export async function readOpenApiSourceText(input, services) {
48
48
  export function parseOpenApiDocument(sourceText, input) {
49
49
  let parsed;
50
50
  try {
51
- parsed = parseYaml(sourceText);
51
+ parsed = JSON.parse(sourceText);
52
52
  }
53
- catch (error) {
54
- throw new UserError(`Failed to parse OpenAPI document ${JSON.stringify(formatSourceLabel(input))}: ${formatParseErrorMessage(error, sourceText, formatSourceLabel(input))}`);
53
+ catch {
54
+ try {
55
+ parsed = parseYaml(sourceText);
56
+ }
57
+ catch (error) {
58
+ throw new UserError(`Failed to parse OpenAPI document ${JSON.stringify(formatSourceLabel(input))}: ${formatParseErrorMessage(error, sourceText, formatSourceLabel(input))}`);
59
+ }
55
60
  }
56
61
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
57
62
  throw new UserError(`OpenAPI document ${JSON.stringify(formatSourceLabel(input))} must parse to an object.`);