From b031c167976aca7aae0f8a419fdaafd7c8524084 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Sep 2026 10:37:45 +0000 Subject: [PATCH 1/2] fix: hide unlabeled legacy traces from default CLI discovery Rename unversioned deprecated resources to legacy-*-v1, omit them from default help/schema unless --include-deprecated, and label remaining help with Cloud removal plus the Observations API v2 recipe. Self-hosted v3 snapshots keep traces as current commands. Co-authored-by: Max Deichmann --- README.md | 7 +- conformance/goldens/4.10.0.json | 45 +++----- conformance/goldens/4.35.0.json | 45 +++----- src/cli.test.ts | 65 ++++++++++- src/cli.ts | 194 +++++++++++++++++++++++--------- src/contracts/naming.test.ts | 47 ++++++++ src/contracts/naming.ts | 2 +- src/flags.ts | 7 +- 8 files changed, 292 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index e6b89e9..aa4bc5f 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ langfuse api prompts create help # Machine-readable discovery langfuse api schema --json +# Include deprecated Cloud v3 commands (legacy-traces-v1, etc.) +langfuse --include-deprecated api schema --json # Create a prompt langfuse api prompts create --json-body '{"name":"my-prompt","type":"text","prompt":"Hello {{name}}"}' @@ -108,7 +110,10 @@ langfuse api dataset-items list --dataset-name my-dataset # Scores langfuse api scores list --limit 20 -# Use an API snapshot compatible with an older self-hosted deployment +# Use an API snapshot compatible with an older self-hosted deployment. +# On Cloud, v3 trace/observation reads are hidden unless you pass +# --include-deprecated; they still cannot be called. Self-hosted v3 keeps +# `traces list` as a current command: langfuse --api-version 3 api traces list langfuse --api-version 3.150.0 api traces list diff --git a/conformance/goldens/4.10.0.json b/conformance/goldens/4.10.0.json index b8b42ec..852bc41 100644 --- a/conformance/goldens/4.10.0.json +++ b/conformance/goldens/4.10.0.json @@ -156,7 +156,7 @@ { "operationId": "datasetRunItems_create", "key": "POST /api/public/dataset-run-items", - "resource": "legacy-dataset-run-items", + "resource": "legacy-dataset-run-items-v1", "action": "create", "aliases": [ { @@ -170,7 +170,7 @@ { "operationId": "datasetRunItems_list", "key": "GET /api/public/dataset-run-items", - "resource": "legacy-dataset-run-items", + "resource": "legacy-dataset-run-items-v1", "action": "list", "aliases": [ { @@ -197,7 +197,7 @@ { "operationId": "datasets_deleteRun", "key": "DELETE /api/public/datasets/{datasetName}/runs/{runName}", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "delete-run", "aliases": [ { @@ -224,7 +224,7 @@ { "operationId": "datasets_getRun", "key": "GET /api/public/datasets/{datasetName}/runs/{runName}", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "get-run", "aliases": [ { @@ -238,7 +238,7 @@ { "operationId": "datasets_getRuns", "key": "GET /api/public/datasets/{datasetName}/runs", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "list-runs", "aliases": [ { @@ -289,7 +289,7 @@ { "operationId": "ingestion_batch", "key": "POST /api/public/ingestion", - "resource": "legacy-ingestion", + "resource": "legacy-ingestion-v1", "action": "batch", "aliases": [ { @@ -303,32 +303,20 @@ { "operationId": "legacy_metricsV1_metrics", "key": "GET /api/public/metrics", - "resource": "legacy-metrics", + "resource": "legacy-metrics-v1", "action": "get", - "aliases": [ - { - "resource": "legacy-metrics-v1", - "action": "get", - "source": "tag" - } - ], "deprecated": true }, { "operationId": "legacy_observationsV1_get", "key": "GET /api/public/observations/{observationId}", - "resource": "legacy-observations", + "resource": "legacy-observations-v1", "action": "get", "aliases": [ { "resource": "observations", "action": "get", "source": "path" - }, - { - "resource": "legacy-observations-v1", - "action": "get", - "source": "tag" } ], "deprecated": true @@ -336,15 +324,8 @@ { "operationId": "legacy_observationsV1_getMany", "key": "GET /api/public/observations", - "resource": "legacy-observations", + "resource": "legacy-observations-v1", "action": "list", - "aliases": [ - { - "resource": "legacy-observations-v1", - "action": "list", - "source": "tag" - } - ], "deprecated": true }, { @@ -733,7 +714,7 @@ { "operationId": "sessions_get", "key": "GET /api/public/sessions/{sessionId}", - "resource": "legacy-sessions", + "resource": "legacy-sessions-v1", "action": "get", "aliases": [ { @@ -747,7 +728,7 @@ { "operationId": "sessions_list", "key": "GET /api/public/sessions", - "resource": "legacy-sessions", + "resource": "legacy-sessions-v1", "action": "list", "aliases": [ { @@ -787,7 +768,7 @@ { "operationId": "trace_get", "key": "GET /api/public/traces/{traceId}", - "resource": "legacy-traces", + "resource": "legacy-traces-v1", "action": "get", "aliases": [ { @@ -806,7 +787,7 @@ { "operationId": "trace_list", "key": "GET /api/public/traces", - "resource": "legacy-traces", + "resource": "legacy-traces-v1", "action": "list", "aliases": [ { diff --git a/conformance/goldens/4.35.0.json b/conformance/goldens/4.35.0.json index 241409e..b5463b9 100644 --- a/conformance/goldens/4.35.0.json +++ b/conformance/goldens/4.35.0.json @@ -156,7 +156,7 @@ { "operationId": "datasetRunItems_create", "key": "POST /api/public/dataset-run-items", - "resource": "legacy-dataset-run-items", + "resource": "legacy-dataset-run-items-v1", "action": "create", "aliases": [ { @@ -170,7 +170,7 @@ { "operationId": "datasetRunItems_list", "key": "GET /api/public/dataset-run-items", - "resource": "legacy-dataset-run-items", + "resource": "legacy-dataset-run-items-v1", "action": "list", "aliases": [ { @@ -197,7 +197,7 @@ { "operationId": "datasets_deleteRun", "key": "DELETE /api/public/datasets/{datasetName}/runs/{runName}", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "delete-run", "aliases": [ { @@ -224,7 +224,7 @@ { "operationId": "datasets_getRun", "key": "GET /api/public/datasets/{datasetName}/runs/{runName}", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "get-run", "aliases": [ { @@ -238,7 +238,7 @@ { "operationId": "datasets_getRuns", "key": "GET /api/public/datasets/{datasetName}/runs", - "resource": "legacy-datasets", + "resource": "legacy-datasets-v1", "action": "list-runs", "aliases": [ { @@ -432,7 +432,7 @@ { "operationId": "ingestion_batch", "key": "POST /api/public/ingestion", - "resource": "legacy-ingestion", + "resource": "legacy-ingestion-v1", "action": "batch", "aliases": [ { @@ -446,32 +446,20 @@ { "operationId": "legacy_metricsV1_metrics", "key": "GET /api/public/metrics", - "resource": "legacy-metrics", + "resource": "legacy-metrics-v1", "action": "get", - "aliases": [ - { - "resource": "legacy-metrics-v1", - "action": "get", - "source": "tag" - } - ], "deprecated": true }, { "operationId": "legacy_observationsV1_get", "key": "GET /api/public/observations/{observationId}", - "resource": "legacy-observations", + "resource": "legacy-observations-v1", "action": "get", "aliases": [ { "resource": "observations", "action": "get", "source": "path" - }, - { - "resource": "legacy-observations-v1", - "action": "get", - "source": "tag" } ], "deprecated": true @@ -479,15 +467,8 @@ { "operationId": "legacy_observationsV1_getMany", "key": "GET /api/public/observations", - "resource": "legacy-observations", + "resource": "legacy-observations-v1", "action": "list", - "aliases": [ - { - "resource": "legacy-observations-v1", - "action": "list", - "source": "tag" - } - ], "deprecated": true }, { @@ -882,7 +863,7 @@ { "operationId": "sessions_get", "key": "GET /api/public/sessions/{sessionId}", - "resource": "legacy-sessions", + "resource": "legacy-sessions-v1", "action": "get", "aliases": [ { @@ -896,7 +877,7 @@ { "operationId": "sessions_list", "key": "GET /api/public/sessions", - "resource": "legacy-sessions", + "resource": "legacy-sessions-v1", "action": "list", "aliases": [ { @@ -936,7 +917,7 @@ { "operationId": "trace_get", "key": "GET /api/public/traces/{traceId}", - "resource": "legacy-traces", + "resource": "legacy-traces-v1", "action": "get", "aliases": [ { @@ -955,7 +936,7 @@ { "operationId": "trace_list", "key": "GET /api/public/traces", - "resource": "legacy-traces", + "resource": "legacy-traces-v1", "action": "list", "aliases": [ { diff --git a/src/cli.test.ts b/src/cli.test.ts index 25c5093..1891102 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -7,6 +7,7 @@ import { assertOperationCallable, assertPaginationUsage, callAllPages, + deprecationHeadline, extractPaginationFlags, operationByCommand, parseOperationInput, @@ -238,13 +239,72 @@ paths: "Use `GET /api/public/v3/prompts` instead.", ); - const schema = schemaOutput({ + const hidden = schemaOutput({ schemaVersion: 1, apiVersion: "4.10.0", sourceSha256: "test", operations: [operation], }); + expect(hidden.includeDeprecated).toBe(false); + expect(hidden.resources).toEqual([]); + + const schema = schemaOutput( + { + schemaVersion: 1, + apiVersion: "4.10.0", + sourceSha256: "test", + operations: [operation], + }, + { includeDeprecated: true }, + ); expect(schema.resources[0].actions[0].deprecated).toBe(true); + expect(schema.resources[0].actions[0].deprecation).toBe("deprecated."); + }); + + test("labels legacy traces with Cloud removal and the v2 observations recipe", () => { + const operation: ApiOperation = { + ...promptGet, + deprecated: true, + method: "GET", + path: "/api/public/traces", + operationId: "trace_list", + command: { resource: "legacy-traces-v1", action: "list" }, + summary: "Get list of traces", + description: + "**Deprecated:** On Langfuse Cloud, Langfuse v3 is deprecated and this endpoint will be removed on November 16, 2026. In Langfuse v4, read span and trace data via `GET /api/public/v2/observations`.", + }; + + expect(deprecationHeadline(operation)).toBe( + "deprecated; Cloud removal 2026-11-16; use observations list / Observations API v2.", + ); + + const current: ApiOperation = { + ...promptGet, + method: "GET", + path: "/api/public/v2/observations", + operationId: "observations_getMany", + command: { resource: "observations", action: "list" }, + }; + const mixed = schemaOutput({ + schemaVersion: 1, + apiVersion: "4.35.0", + sourceSha256: "test", + operations: [operation, current], + }); + expect(mixed.resources.map((resource) => resource.name)).toEqual([ + "observations", + ]); + expect( + schemaOutput( + { + schemaVersion: 1, + apiVersion: "4.35.0", + sourceSha256: "test", + operations: [operation, current], + }, + { includeDeprecated: true }, + ).resources.map((resource) => resource.name), + ).toEqual(["legacy-traces-v1", "observations"]); }); test("rejects nested body flags without consuming a positional", async () => { @@ -626,6 +686,7 @@ describe("API version reporting", () => { json: false, curl: false, showSecrets: false, + includeDeprecated: false, }; test("versions current prints the resolved major selection", async () => { @@ -898,6 +959,7 @@ describe("exit code taxonomy", () => { json: true, curl: false, showSecrets: false, + includeDeprecated: false, }, ), ); @@ -921,6 +983,7 @@ describe("result output", () => { json: false, curl: false, showSecrets: false, + includeDeprecated: false, output, }, ); diff --git a/src/cli.ts b/src/cli.ts index 785792d..6c157af 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -54,6 +54,7 @@ interface RuntimeConfig { json: boolean; curl: boolean; showSecrets: boolean; + includeDeprecated: boolean; output?: string; } @@ -138,6 +139,7 @@ async function runtimeConfig(globals: ParsedGlobals): Promise { json: globals.booleans.has("json"), curl: globals.booleans.has("curl"), showSecrets: globals.booleans.has("show-secrets"), + includeDeprecated: globals.booleans.has("include-deprecated"), output: globals.values.output, }; } @@ -194,6 +196,7 @@ Options: --host Langfuse host (default: ${DEFAULT_HOST}) --env Load env vars from a file --api-version Exact/major version, latest, or auto + --include-deprecated Include deprecated Cloud v3 commands in help/schema --timeout Request timeout (default: ${DEFAULT_TIMEOUT_MS}) -h, --help Show help --version Show CLI version @@ -215,9 +218,39 @@ interface CommandBinding { alias: boolean; } -function canonicalResourceMap(contract: ApiContract): Map { +const MONTH_NUMBERS: Record = { + january: "01", + february: "02", + march: "03", + april: "04", + may: "05", + june: "06", + july: "07", + august: "08", + september: "09", + october: "10", + november: "11", + december: "12", +}; + +function discoverableOperations( + operations: ApiOperation[], + includeDeprecated: boolean, +): ApiOperation[] { + return includeDeprecated + ? operations + : operations.filter((operation) => !operation.deprecated); +} + +function canonicalResourceMap( + contract: ApiContract, + includeDeprecated = true, +): Map { const resources = new Map(); - for (const operation of contract.operations) { + for (const operation of discoverableOperations( + contract.operations, + includeDeprecated, + )) { const existing = resources.get(operation.command.resource) ?? []; existing.push(operation); resources.set(operation.command.resource, existing); @@ -253,9 +286,12 @@ function resourceMap(contract: ApiContract): Map { return resources; } -function printApiHelp(contract: ApiContract): void { - const resources = [...canonicalResourceMap(contract)].sort(([left], [right]) => - left.localeCompare(right), +function printApiHelp( + contract: ApiContract, + includeDeprecated: boolean, +): void { + const resources = [...canonicalResourceMap(contract, includeDeprecated)].sort( + ([left], [right]) => left.localeCompare(right), ); process.stdout.write(`Usage: langfuse api [options] @@ -274,7 +310,10 @@ Discovery: api schema --json Machine-readable command schema api __schema --json Legacy command alias api versions list Bundled historical snapshots + --include-deprecated Show deprecated Cloud v3 commands + --api-version 3 Self-hosted v3 snapshots (legacy traces remain current) Path commands are canonical; OpenAPI tag and route-version aliases also work + Deprecated Cloud v3 reads are hidden unless --include-deprecated is set Action options: --body-json Lossless JSON request body @@ -285,19 +324,32 @@ Action options: `); } -function printResourceHelp(contract: ApiContract, resource: string): void { +function printResourceHelp( + contract: ApiContract, + resource: string, + includeDeprecated: boolean, +): void { const bindings = resourceMap(contract).get(resource); if (!bindings) throw new CliError(`Unknown API resource: ${resource}`); + const visible = includeDeprecated + ? bindings + : bindings.filter((binding) => !binding.operation.deprecated); + const listed = visible.length > 0 ? visible : bindings; + const hidden = + !includeDeprecated && + visible.length === 0 && + bindings.some((binding) => binding.operation.deprecated); process.stdout.write(`Usage: langfuse api ${resource} [options] - +${hidden ? "\nThis resource is deprecated on the selected API snapshot. Pass --include-deprecated to treat it as discoverable, or --api-version 3 for self-hosted v3.\n" : ""} Actions: -${bindings - .map( - (binding) => { - const label = `${binding.action}${binding.alias ? " [alias]" : ""}${binding.operation.deprecated ? " [deprecated]" : ""}`; - return ` ${label.padEnd(43)} ${binding.operation.method} ${binding.operation.path}`; - }, - ) +${listed + .map((binding) => { + const deprecated = Boolean(binding.operation.deprecated); + const label = `${binding.action}${binding.alias ? " [alias]" : ""}${deprecated ? " [deprecated]" : ""}`; + const headline = deprecated ? deprecationHeadline(binding.operation) : undefined; + const line = ` ${label.padEnd(43)} ${binding.operation.method} ${binding.operation.path}`; + return headline ? `${line}\n ${headline}` : line; + }) .join("\n")} `); } @@ -309,12 +361,39 @@ function explicitDeprecationNote(operation: ApiOperation): string | undefined { } return description .split(/\n\s*\n/, 1)[0] - .replace(/^\*\*Deprecated\.\*\*\s*/i, "") - .replace(/^Deprecated\.?\s*/i, "") + .replace(/^\*\*Deprecated:?\*\*\s*/i, "") + .replace(/^Deprecated:?\s*/i, "") .replace(/\s*\n\s*/g, " ") .trim(); } +function cloudRemovalDate(description?: string): string | undefined { + const match = description?.match(/removed on ([A-Za-z]+) (\d{1,2}), (\d{4})/i); + if (!match) return undefined; + const month = MONTH_NUMBERS[match[1].toLowerCase()]; + if (!month) return undefined; + return `${match[3]}-${month}-${match[2].padStart(2, "0")}`; +} + +function replacementHint(operation: ApiOperation): string | undefined { + if ( + /\/traces(?:\/|$|\?)/.test(operation.path) || + /\/observations(?:\/|$|\?)/.test(operation.path) + ) { + return "use observations list / Observations API v2"; + } + return undefined; +} + +export function deprecationHeadline(operation: ApiOperation): string { + const parts = ["deprecated"]; + const date = cloudRemovalDate(operation.description); + if (date) parts.push(`Cloud removal ${date}`); + const replacement = replacementHint(operation); + if (replacement) parts.push(replacement); + return `${parts.join("; ")}.`; +} + export function assertOperationCallable( operation: ApiOperation, apiVersion: string, @@ -322,9 +401,9 @@ export function assertOperationCallable( if (!operation.deprecated) return; const note = explicitDeprecationNote(operation); throw new CliError( - `Cannot call deprecated API operation "${operation.command.resource} ${operation.command.action}" (${operation.method} ${operation.path}) in API ${apiVersion}.` + + `Cannot call deprecated API operation "${operation.command.resource} ${operation.command.action}" (${operation.method} ${operation.path}) in API ${apiVersion}. ${deprecationHeadline(operation)}` + (note ? ` ${note}` : " No replacement is declared in its OpenAPI description.") + - ` Use "langfuse api help ${operation.command.resource}" or "langfuse api schema --json" to find supported operations.`, + ` Use "langfuse api schema --json" for current operations, or --api-version 3 for self-hosted v3.`, ); } @@ -434,14 +513,14 @@ function printOperationHelp(operation: ApiOperation): void { ); } process.stdout.write(`Usage: langfuse api ${operation.command.resource} ${operation.command.action}${positionals ? ` ${positionals}` : ""} [options] - -${operation.summary ?? operation.operationId} -${operation.description ? `\n${operation.description}\n` : ""} ${ operation.deprecated - ? `\nDEPRECATED\nThis operation is discoverable but cannot be called by this CLI.${explicitDeprecationNote(operation) ? ` ${explicitDeprecationNote(operation)}` : ""}\n` + ? `\n${deprecationHeadline(operation)}\nThis operation is hidden from default help/schema and cannot be called by this CLI.${explicitDeprecationNote(operation) ? ` ${explicitDeprecationNote(operation)}` : ""} Self-hosted v3 remains available with --api-version 3.\n` : "" -}${bodyFieldsSection(operation)} +} +${operation.summary ?? operation.operationId} +${operation.description ? `\n${operation.description}\n` : ""} +${bodyFieldsSection(operation)} Options: ${lines.length ? lines.join("\n") : " (no operation-specific options)"} --json JSON response envelope @@ -998,33 +1077,43 @@ export async function callAllPages( }; } -export function schemaOutput(contract: ApiContract) { +export function schemaOutput( + contract: ApiContract, + options: { includeDeprecated?: boolean } = {}, +) { + const includeDeprecated = Boolean(options.includeDeprecated); return { schemaVersion: 1, apiVersion: contract.apiVersion, sourceSha256: contract.sourceSha256, - resources: [...canonicalResourceMap(contract)].map(([name, operations]) => ({ - name, - actions: operations.map((operation) => ({ - name: operation.command.action, - aliases: operation.command.aliases ?? [], - operationId: operation.operationId, - method: operation.method, - path: operation.path, - deprecated: Boolean(operation.deprecated), - ...(operation.pagination ? { pagination: operation.pagination } : {}), - auth: operation.auth, - pathParameterOrder: operation.pathParameterOrder, - parameters: operation.parameters, - ...(operation.requestBody - ? { requestBody: operation.requestBody } - : {}), - ...(operation.summary ? { summary: operation.summary } : {}), - ...(operation.description - ? { description: operation.description } - : {}), - })), - })), + includeDeprecated, + resources: [...canonicalResourceMap(contract, includeDeprecated)].map( + ([name, operations]) => ({ + name, + actions: operations.map((operation) => ({ + name: operation.command.action, + aliases: operation.command.aliases ?? [], + operationId: operation.operationId, + method: operation.method, + path: operation.path, + deprecated: Boolean(operation.deprecated), + ...(operation.deprecated + ? { deprecation: deprecationHeadline(operation) } + : {}), + ...(operation.pagination ? { pagination: operation.pagination } : {}), + auth: operation.auth, + pathParameterOrder: operation.pathParameterOrder, + parameters: operation.parameters, + ...(operation.requestBody + ? { requestBody: operation.requestBody } + : {}), + ...(operation.summary ? { summary: operation.summary } : {}), + ...(operation.description + ? { description: operation.description } + : {}), + })), + }), + ), }; } @@ -1104,30 +1193,31 @@ export async function runApi( catalog, }); const contract = await loadApiContract(resolved.version); + const includeDeprecated = config.includeDeprecated; if ( args.length === 0 || (args[0] === "help" && args.length === 1) || args[0] === "--help" || args[0] === "-h" ) { - printApiHelp(contract); + printApiHelp(contract, includeDeprecated); return; } if (["schema", "__schema", "__spec"].includes(args[0])) { - const schema = schemaOutput(contract); + const schema = schemaOutput(contract, { includeDeprecated }); if (config.json) process.stdout.write(`${JSON.stringify(schema)}\n`); - else printApiHelp(contract); + else printApiHelp(contract, includeDeprecated); return; } if (args[0] === "help") { - if (!args[1]) printApiHelp(contract); - else if (!args[2]) printResourceHelp(contract, args[1]); + if (!args[1]) printApiHelp(contract, includeDeprecated); + else if (!args[2]) printResourceHelp(contract, args[1], includeDeprecated); else printOperationHelp(operationByCommand(contract, args[1], args[2])); return; } const resource = args[0]; if (!args[1] || args[1] === "help" || args[1] === "--help" || args[1] === "-h") { - printResourceHelp(contract, resource); + printResourceHelp(contract, resource, includeDeprecated); return; } const operation = operationByCommand(contract, resource, args[1]); diff --git a/src/contracts/naming.test.ts b/src/contracts/naming.test.ts index 2f3512d..1ae5b36 100644 --- a/src/contracts/naming.test.ts +++ b/src/contracts/naming.test.ts @@ -118,4 +118,51 @@ describe("stable CLI naming policy", () => { }, ]); }); + + test("names unversioned deprecated reads legacy--v1", () => { + expect( + planCommandNames([ + { + operationId: "trace_list", + method: "GET", + path: "/api/public/traces", + tags: ["Trace"], + deprecated: true, + }, + { + operationId: "legacy_observationsV1_getMany", + method: "GET", + path: "/api/public/observations", + tags: ["LegacyObservationsV1"], + deprecated: true, + }, + { + operationId: "observations_getMany", + method: "GET", + path: "/api/public/v2/observations", + tags: ["Observations"], + }, + ]), + ).toEqual([ + { + resource: "legacy-traces-v1", + action: "list", + aliases: [ + { resource: "traces", action: "list", source: "path" }, + { resource: "trace", action: "list", source: "tag" }, + ], + }, + { + resource: "legacy-observations-v1", + action: "list", + }, + { + resource: "observations", + action: "list", + aliases: [ + { resource: "observations-v2", action: "list", source: "version" }, + ], + }, + ]); + }); }); diff --git a/src/contracts/naming.ts b/src/contracts/naming.ts index ebeb157..4953370 100644 --- a/src/contracts/naming.ts +++ b/src/contracts/naming.ts @@ -183,7 +183,7 @@ export function planCommandNames(inputs: NamingInput[]): CommandName[] { const resource = input.deprecated ? route.version ? `${route.resource}-${route.version}` - : `legacy-${route.resource}` + : `legacy-${route.resource}-v1` : route.resource; return { input, diff --git a/src/flags.ts b/src/flags.ts index d6f6e78..b7c172f 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -11,7 +11,12 @@ export const GLOBAL_VALUE_FLAG_NAMES = [ "output", ] as const; -export const GLOBAL_BOOLEAN_FLAG_NAMES = ["json", "curl", "show-secrets"] as const; +export const GLOBAL_BOOLEAN_FLAG_NAMES = [ + "json", + "curl", + "show-secrets", + "include-deprecated", +] as const; export const BODY_CHANNEL_FLAG_NAMES = ["body-json", "body-file"] as const; From c930e005e568570008b3f1c78e229d04a7dc7962 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Sep 2026 10:53:53 +0000 Subject: [PATCH 2/2] fix: strip both period and colon deprecation markers in help The OpenAPI note parser must accept **Deprecated.** as well as **Deprecated:** so CLI errors do not leak markdown. Co-authored-by: Max Deichmann --- src/cli.test.ts | 10 +++++++--- src/cli.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cli.test.ts b/src/cli.test.ts index 1891102..3ecb2ec 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -235,9 +235,13 @@ paths: expect(() => assertOperationCallable(operation, "4.10.0")).toThrow( 'Cannot call deprecated API operation "prompts get"', ); - expect(() => assertOperationCallable(operation, "4.10.0")).toThrow( - "Use `GET /api/public/v3/prompts` instead.", - ); + try { + assertOperationCallable(operation, "4.10.0"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + expect(message).toContain("Use `GET /api/public/v3/prompts` instead."); + expect(message).not.toContain("**Deprecated"); + } const hidden = schemaOutput({ schemaVersion: 1, diff --git a/src/cli.ts b/src/cli.ts index 6c157af..ced6cff 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -361,8 +361,8 @@ function explicitDeprecationNote(operation: ApiOperation): string | undefined { } return description .split(/\n\s*\n/, 1)[0] - .replace(/^\*\*Deprecated:?\*\*\s*/i, "") - .replace(/^Deprecated:?\s*/i, "") + .replace(/^\*\*Deprecated[.:]?\*\*\s*/i, "") + .replace(/^Deprecated[.:]?\s*/i, "") .replace(/\s*\n\s*/g, " ") .trim(); }