From 642f2f5aa1393d22b5409cba313663177787556a Mon Sep 17 00:00:00 2001 From: notgitika Date: Thu, 23 Jul 2026 19:38:30 -0400 Subject: [PATCH 1/2] fix: preserve wrapped CLI help in API docs --- scripts/extract-cli-model.mjs | 30 +++++++++++++++++++---- scripts/extract-cli-model.test.mjs | 32 ++++++++++++++++++++++++ scripts/render_adoc.py | 39 ++++++++++++++++++++++++++---- 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 scripts/extract-cli-model.test.mjs diff --git a/scripts/extract-cli-model.mjs b/scripts/extract-cli-model.mjs index fd0fc518d..2c70be687 100644 --- a/scripts/extract-cli-model.mjs +++ b/scripts/extract-cli-model.mjs @@ -19,6 +19,7 @@ import { execFileSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import { argv } from 'node:process'; +import { pathToFileURL } from 'node:url'; // Command grouping (arranged properly, per the request). Mirrors the sections // in agentcore-cli/docs/commands.md. Any command discovered from the binary but @@ -85,10 +86,11 @@ function help(args) { } // Parse a Commander.js help blob into { summary, signature, params, options }. -function parseHelp(text, name) { +export function parseHelp(text) { const lines = text.split('\n'); const out = { summary: '', signature: '', args: [], options: [] }; let section = 'head'; + let currentItem = null; const descLines = []; for (const raw of lines) { @@ -96,18 +98,22 @@ function parseHelp(text, name) { if (/^Usage:/.test(line)) { out.signature = line.replace(/^Usage:\s*/, '').trim(); section = 'desc'; + currentItem = null; continue; } if (/^Arguments:/.test(line)) { section = 'args'; + currentItem = null; continue; } if (/^Options:/.test(line)) { section = 'options'; + currentItem = null; continue; } if (/^Commands:/.test(line)) { section = 'commands'; + currentItem = null; continue; } @@ -115,10 +121,22 @@ function parseHelp(text, name) { if (line.trim()) descLines.push(line.trim()); } else if (section === 'args') { const m = line.match(/^\s+(\S+)\s{2,}(.*)$/); - if (m) out.args.push({ name: m[1], type: null, required: true, description: m[2].trim() }); + if (m) { + currentItem = { name: m[1], type: null, required: true, description: m[2].trim() }; + out.args.push(currentItem); + } else if (currentItem && /^\s+\S/.test(line)) { + currentItem.description += ` ${line.trim()}`; + } } else if (section === 'options') { const m = line.match(/^\s+(-[^\s].*?)\s{2,}(.*)$/); - if (m) out.options.push({ name: m[1].trim(), type: null, required: false, description: m[2].trim() }); + if (m) { + currentItem = { name: m[1].trim(), type: null, required: false, description: m[2].trim() }; + out.options.push(currentItem); + } else if (currentItem && /^\s+\S/.test(line)) { + currentItem.description += ` ${line.trim()}`; + } else if (line.trim()) { + currentItem = null; + } } } out.summary = descLines.join(' '); @@ -138,7 +156,7 @@ function entryForCommand(name) { `not a real CLI command (phantom/renamed). Remove it from GROUPS or fix the name.` ); } - const parsed = parseHelp(raw, name); + const parsed = parseHelp(raw); // subcommands (e.g. `add agent`, `remove tool`) show under "Commands:"— // we surface the top-level command; nested ones can be expanded later. return { @@ -225,4 +243,6 @@ function main() { process.stderr.write(`Wrote doc-model: ${groups.length} groups, version ${version}\n`); } -main(); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main(); +} diff --git a/scripts/extract-cli-model.test.mjs b/scripts/extract-cli-model.test.mjs new file mode 100644 index 000000000..0359059ed --- /dev/null +++ b/scripts/extract-cli-model.test.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { parseHelp } from "./extract-cli-model.mjs"; + +test("parseHelp preserves wrapped argument and option descriptions", () => { + const parsed = parseHelp(`Usage: agentcore invoke [options] [prompt] + +Invoke an agent. + +Arguments: + prompt Prompt to send to the agent. Also accepts piped + stdin when no prompt is provided + +Options: + --prompt-file Read the prompt from a file (for long or + structured payloads that exceed shell limits) + +Output + --json Output as JSON +`); + + assert.equal( + parsed.args[0].description, + "Prompt to send to the agent. Also accepts piped stdin when no prompt is provided", + ); + assert.equal( + parsed.options[0].description, + "Read the prompt from a file (for long or structured payloads that exceed shell limits)", + ); + assert.equal(parsed.options[1].description, "Output as JSON"); +}); diff --git a/scripts/render_adoc.py b/scripts/render_adoc.py index f0276c050..3803f1741 100644 --- a/scripts/render_adoc.py +++ b/scripts/render_adoc.py @@ -55,15 +55,44 @@ SCHEMA_VERSION = 1 +def normalize_style(text): + """Apply style-safe substitutions to generated prose.""" + if not text: + return "" + text = re.sub(r"\be\.g\.(?:,)?", "for example,", text, flags=re.IGNORECASE) + text = text.replace( + "This feature is in preview and may change in future releases.", + "This feature is in preview and might change in future releases.", + ) + return re.sub(r"\bAWS\b", "{aws}", text) + + +def normalize_param_description(text): + """Normalize recurring parameter-description style issues.""" + text = normalize_style(text).strip() + substitutions = ( + (r"^Optional\b", "An optional"), + (r"^(?:\{aws\}|AWS)\s+region\b", "The {aws} Region"), + (r"^id of\b", "The ID of"), + (r"^Behaviour\b", "The behavior"), + (r"^Behavior\b", "The behavior"), + (r"^Memory resource ID\b", "The memory resource ID"), + (r"^Strategy name\b", "The name of the memory strategy"), + (r"^Strategy ID\b", "The ID of the memory strategy"), + ) + for pattern, replacement in substitutions: + text = re.sub(pattern, replacement, text, count=1, flags=re.IGNORECASE) + return text + + def esc(text): """Escape AsciiDoc-significant characters in inline text.""" if not text: return "" # Guard the couple of chars that start AsciiDoc markup in running prose. - return ( - text.replace("|", "\\|") - .replace("{", "\\{") - ) + marker = "\0AWS_ENTITY\0" + text = normalize_style(text).replace("{aws}", marker) + return text.replace("|", "\\|").replace("{", "\\{").replace(marker, "{aws}") # Match markdown code fences that may be indented (reST/Google docstrings often @@ -116,7 +145,7 @@ def render_params(params, out): req = "" if p.get("required") else " _(optional)_" typ = f"`{p['type']}`" if p.get("type") else "" out.append(f"`{p['name']}`{req} {typ}::") - out.append(esc(p.get("description", "")) or "_No description._") + out.append(esc(normalize_param_description(p.get("description", ""))) or "_No description._") out.append("") From 90e685bc96a56727cb980b79cf8bef662496cf33 Mon Sep 17 00:00:00 2001 From: notgitika Date: Fri, 24 Jul 2026 15:58:10 -0400 Subject: [PATCH 2/2] fix: normalize generated CLI reference text --- scripts/extract-cli-model.mjs | 28 +++++++++++++++++++++++++++- scripts/extract-cli-model.test.mjs | 16 +++++++++++++++- scripts/render_adoc.py | 27 +++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/scripts/extract-cli-model.mjs b/scripts/extract-cli-model.mjs index 2c70be687..a1e36fea2 100644 --- a/scripts/extract-cli-model.mjs +++ b/scripts/extract-cli-model.mjs @@ -85,6 +85,27 @@ function help(args) { } } +const PARAMETER_DESCRIPTION_OVERRIDES = new Map([ + [ + 'bedrock, open_ai, or gemini', + 'The model provider. Valid values: `bedrock`, `open_ai`, or `gemini`.', + ], + [ + 'Override LiteLLM API base URL (harness only, lite_llm) [non-interactive]', + 'The LiteLLM API base URL override for harness invocations. ' + + 'Available only with `lite_llm` in non-interactive mode.', + ], + [ + 'Override LiteLLM additional params as a JSON object (harness only, lite_llm) [non-interactive]', + 'The additional LiteLLM parameters, as a JSON object, for harness invocations. ' + + 'Available only with `lite_llm` in non-interactive mode.', + ], +]); + +export function normalizeParameterDescription(description) { + return PARAMETER_DESCRIPTION_OVERRIDES.get(description) || description; +} + // Parse a Commander.js help blob into { summary, signature, params, options }. export function parseHelp(text) { const lines = text.split('\n'); @@ -168,7 +189,12 @@ function entryForCommand(name) { // reuse params for both positional args and flags, flagged by required. // The renderer already wraps param names in backticks, so don't add our // own (that produced double-backticks). Drop the ubiquitous help flag. - params: [...parsed.args, ...parsed.options.filter(o => !/^-h,?\s|--help\b/.test(o.name))], + params: [...parsed.args, ...parsed.options.filter(o => !/^-h,?\s|--help\b/.test(o.name))].map( + param => ({ + ...param, + description: normalizeParameterDescription(param.description), + }) + ), returns: null, raises: [], examples: [], diff --git a/scripts/extract-cli-model.test.mjs b/scripts/extract-cli-model.test.mjs index 0359059ed..661fa704a 100644 --- a/scripts/extract-cli-model.test.mjs +++ b/scripts/extract-cli-model.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { parseHelp } from "./extract-cli-model.mjs"; +import { normalizeParameterDescription, parseHelp } from "./extract-cli-model.mjs"; test("parseHelp preserves wrapped argument and option descriptions", () => { const parsed = parseHelp(`Usage: agentcore invoke [options] [prompt] @@ -30,3 +30,17 @@ Output ); assert.equal(parsed.options[1].description, "Output as JSON"); }); + +test("normalizeParameterDescription expands model provider and LiteLLM fragments", () => { + assert.equal( + normalizeParameterDescription("bedrock, open_ai, or gemini"), + "The model provider. Valid values: `bedrock`, `open_ai`, or `gemini`.", + ); + assert.equal( + normalizeParameterDescription( + "Override LiteLLM API base URL (harness only, lite_llm) [non-interactive]", + ), + "The LiteLLM API base URL override for harness invocations. " + + "Available only with `lite_llm` in non-interactive mode.", + ); +}); diff --git a/scripts/render_adoc.py b/scripts/render_adoc.py index 3803f1741..62a070cbb 100644 --- a/scripts/render_adoc.py +++ b/scripts/render_adoc.py @@ -60,16 +60,43 @@ def normalize_style(text): if not text: return "" text = re.sub(r"\be\.g\.(?:,)?", "for example,", text, flags=re.IGNORECASE) + text = re.sub( + r"\bAWS Bedrock(?: AgentCore)? Code\s*Interpreter\b", + "AgentCore Code Interpreter", + text, + flags=re.IGNORECASE, + ) + text = re.sub(r"\bAWS Bedrock AgentCore\b", "Amazon Bedrock AgentCore", text) + text = re.sub(r"\bAWS Bedrock\b", "Amazon Bedrock", text) + text = re.sub(r"\bAgentCore Memory\b", "AgentCore memory", text) + text = re.sub(r"\bAgentCore Runtime\b", "AgentCore runtime", text) text = text.replace( "This feature is in preview and may change in future releases.", "This feature is in preview and might change in future releases.", ) + text = text.replace("validation will ensure", "validation ensures") return re.sub(r"\bAWS\b", "{aws}", text) def normalize_param_description(text): """Normalize recurring parameter-description style issues.""" text = normalize_style(text).strip() + exact_substitutions = { + "bedrock, open_ai, or gemini": ( + "The model provider. Valid values: `bedrock`, `open_ai`, or `gemini`." + ), + "Override LiteLLM API base URL (harness only, lite_llm) [non-interactive]": ( + "The LiteLLM API base URL override for harness invocations. " + "Available only with `lite_llm` in non-interactive mode." + ), + "Override LiteLLM additional params as a JSON object (harness only, lite_llm) " + "[non-interactive]": ( + "The additional LiteLLM parameters, as a JSON object, for harness invocations. " + "Available only with `lite_llm` in non-interactive mode." + ), + } + if text in exact_substitutions: + return exact_substitutions[text] substitutions = ( (r"^Optional\b", "An optional"), (r"^(?:\{aws\}|AWS)\s+region\b", "The {aws} Region"),