mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(cli): correct completion options and setup guidance (#7851)
* fix(cli): align MCP doctor completion with its options * fix(cli): explain zsh completion initialization * fix(cli): include launcher timing in doctor completion * style(cli): format doctor completion schema
This commit is contained in:
1 parent
f9e7571a3d
commit
60ab4da329
6 files changed
+166
-44
No files matched your search
@@ -165,6 +165,15 @@ into the current shell:
|
||||
eval "$(rea completions bash)"
|
||||
```
|
||||
|
||||
For zsh, initialize completion before loading the hook. If your shell already
|
||||
runs `compinit`, place the hook after that existing initialization:
|
||||
|
||||
```zsh
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
eval "$(rea completions zsh)"
|
||||
```
|
||||
|
||||
The Bash hook supports macOS system Bash 3.2. It uses optional completion
|
||||
features when the shell provides them and falls back to portable registration.
|
||||
Completion includes standard global options and the active command's options;
|
||||
|
||||
+17
-12
@@ -68,7 +68,7 @@ import { registerCutterCommands } from "./cli/cutterCommands.js";
|
||||
import { excludeDeclaredSensitiveArguments } from "./cli/explicitSensitiveArguments.js";
|
||||
import { rejectSurplusCliArguments } from "./cli/strictPositionals.js";
|
||||
import { presentCliArgumentErrors } from "./cli/argumentErrors.js";
|
||||
import { createCliCompletions } from "./cli/completions.js";
|
||||
import { completionHelpNote, createCliCompletions } from "./cli/completions.js";
|
||||
import { registerGuidedWorkflowCommands } from "./cli/guidedWorkflowCommands.js";
|
||||
|
||||
export { closeCliJavaScriptResources } from "./cli/javascriptApplicationAnalysis.js";
|
||||
@@ -205,16 +205,21 @@ export const createCli = (
|
||||
cli as unknown as Parameters<typeof createCliCompletions>[0],
|
||||
environment,
|
||||
);
|
||||
cli.serve = (arguments_ = process.argv.slice(2), options) =>
|
||||
complete(arguments_, options ?? {})
|
||||
? Promise.resolve()
|
||||
: serveStrict(
|
||||
normalizeCliArguments(arguments_),
|
||||
options ?? {},
|
||||
(tokens, selected) =>
|
||||
serveDeclared(tokens, selected, (declared, declaredOptions) =>
|
||||
serve(adaptCliOutputArgumentsForIncur(declared), declaredOptions),
|
||||
),
|
||||
);
|
||||
cli.serve = (arguments_ = process.argv.slice(2), options) => {
|
||||
if (complete(arguments_, options ?? {})) return Promise.resolve();
|
||||
const normalized = normalizeCliArguments(arguments_);
|
||||
const result = serveStrict(normalized, options ?? {}, (tokens, selected) =>
|
||||
serveDeclared(tokens, selected, (declared, declaredOptions) =>
|
||||
serve(adaptCliOutputArgumentsForIncur(declared), declaredOptions),
|
||||
),
|
||||
);
|
||||
const note = completionHelpNote(normalized);
|
||||
if (note === undefined) return result;
|
||||
return result.then(() => {
|
||||
const write =
|
||||
options?.stdout ?? ((text: string) => process.stdout.write(text));
|
||||
write(`\n${note}`);
|
||||
});
|
||||
};
|
||||
return cli;
|
||||
};
|
||||
+47
-16
@@ -9,19 +9,53 @@ import {
|
||||
normalizeCliArguments,
|
||||
splitIncurBuiltinArguments,
|
||||
} from "../cliBuiltinArguments.js";
|
||||
import {
|
||||
CLI_DOCUMENT_OUTPUT_FORMATS,
|
||||
CLI_OUTPUT_FORMATS,
|
||||
parseCliOutputArguments,
|
||||
} from "../cliOutput.js";
|
||||
import { CLI_OUTPUT_FORMATS, parseCliOutputArguments } from "../cliOutput.js";
|
||||
import { bashCompletionScript } from "./bashCompletionRegistration.js";
|
||||
import { MCP_DOCTOR_ALIAS, MCP_DOCTOR_OPTIONS } from "../mcpDoctorOptions.js";
|
||||
|
||||
type Commands = Parameters<typeof Completions.complete>[0];
|
||||
type CompletionCli = Parameters<typeof Cli.toCommands.get>[0];
|
||||
type ServeOptions = NonNullable<Parameters<CompletionCli["serve"]>[1]>;
|
||||
const ROOT_FLAGS = new Set(["--mcp", "--update", "--version"]);
|
||||
|
||||
const standardGlobals = (root: boolean, doctor: boolean): z.ZodObject =>
|
||||
/** Supplement Incur's built-in setup instructions without replacing its help. */
|
||||
export const completionHelpNote = (
|
||||
arguments_: readonly string[],
|
||||
): string | undefined => {
|
||||
const split = splitIncurBuiltinArguments(arguments_);
|
||||
const words =
|
||||
split.command[0] === "rea" ? split.command.slice(1) : split.command;
|
||||
if (words[0] !== "completions") return undefined;
|
||||
const rest = words.slice(1);
|
||||
if (
|
||||
rest.length > 1 ||
|
||||
(rest.length === 1 &&
|
||||
!CLI_COMPLETION_SHELLS.includes(
|
||||
rest[0] as (typeof CLI_COMPLETION_SHELLS)[number],
|
||||
))
|
||||
)
|
||||
return undefined;
|
||||
const values = new Set(split.outputValues.map(({ index }) => index));
|
||||
const flags = arguments_.filter((_, index) => !values.has(index));
|
||||
if (
|
||||
flags.some((word) =>
|
||||
[
|
||||
"--schema",
|
||||
"--llms",
|
||||
"--llms-full",
|
||||
"--mcp",
|
||||
"--update",
|
||||
"--version",
|
||||
].includes(word),
|
||||
)
|
||||
)
|
||||
return undefined;
|
||||
if (rest.length > 0 && !flags.includes("--help") && !flags.includes("-h"))
|
||||
return undefined;
|
||||
return "Zsh: initialize completion with `autoload -Uz compinit; compinit` before evaluating the zsh hook. If your shell already runs compinit, place the hook after it.\n";
|
||||
};
|
||||
|
||||
const standardGlobals = (root: boolean): z.ZodObject =>
|
||||
z.object(
|
||||
Object.fromEntries([
|
||||
...[...VALUED_OUTPUT_FLAGS].map(
|
||||
@@ -29,11 +63,7 @@ const standardGlobals = (root: boolean, doctor: boolean): z.ZodObject =>
|
||||
[
|
||||
flag.slice(2),
|
||||
flag === "--format"
|
||||
? z
|
||||
.enum(
|
||||
doctor ? CLI_DOCUMENT_OUTPUT_FORMATS : CLI_OUTPUT_FORMATS,
|
||||
)
|
||||
.optional()
|
||||
? z.enum(CLI_OUTPUT_FORMATS).optional()
|
||||
: z.string().optional(),
|
||||
] as const,
|
||||
),
|
||||
@@ -113,17 +143,18 @@ export const createCliCompletions = (
|
||||
normalizeCliArguments(words.slice(1, index)),
|
||||
).command;
|
||||
const input = [cli.name, ...before, words[index] ?? ""];
|
||||
const doctor = before[0] === "mcp" && before[1] === "doctor";
|
||||
const candidates = Completions.complete(
|
||||
completionCommands(cli),
|
||||
undefined,
|
||||
input,
|
||||
input.length - 1,
|
||||
{
|
||||
schema: standardGlobals(
|
||||
before.length === 0,
|
||||
before[0] === "mcp" && before[1] === "doctor",
|
||||
),
|
||||
alias: { help: "h" },
|
||||
schema: doctor
|
||||
? // The launcher consumes this global before doctor parses options.
|
||||
MCP_DOCTOR_OPTIONS.extend({ timings: z.boolean().optional() })
|
||||
: standardGlobals(before.length === 0),
|
||||
alias: doctor ? MCP_DOCTOR_ALIAS : { help: "h" },
|
||||
},
|
||||
);
|
||||
const result = Completions.format(shell, candidates);
|
||||
|
||||
+2
-16
@@ -1,6 +1,6 @@
|
||||
import { Client, SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { Help, z } from "incur";
|
||||
import { Help } from "incur";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { resolve } from "node:path";
|
||||
import { StringDecoder } from "node:string_decoder";
|
||||
@@ -30,25 +30,11 @@ import {
|
||||
MCP_STARTUP_POLICY,
|
||||
mcpDoctorDeadlineRecovery,
|
||||
} from "./mcpStartupPolicy.js";
|
||||
import { MCP_DOCTOR_ALIAS, MCP_DOCTOR_OPTIONS } from "./mcpDoctorOptions.js";
|
||||
|
||||
const OUTPUT_FORMATS = CLI_DOCUMENT_OUTPUT_FORMATS;
|
||||
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
||||
|
||||
/**
|
||||
* Canonical `rea mcp doctor` options. This schema is the single source of
|
||||
* truth: the argument parser accepts exactly these options (plus the `-h`
|
||||
* alias below) and the help renderer lists them. Keep them in sync by
|
||||
* changing this schema, not either consumer.
|
||||
*/
|
||||
const MCP_DOCTOR_OPTIONS = z.object({
|
||||
format: z.enum(OUTPUT_FORMATS).optional().describe("Output format"),
|
||||
json: z.boolean().optional().describe("Emit the report as JSON"),
|
||||
fullOutput: z.boolean().optional().describe("Show the full output envelope"),
|
||||
help: z.boolean().optional().describe("Show help"),
|
||||
});
|
||||
|
||||
const MCP_DOCTOR_ALIAS = { help: "h" } as const;
|
||||
|
||||
const MCP_DOCTOR_COMMAND = "rea mcp doctor";
|
||||
const MCP_DOCTOR_DESCRIPTION = "Validate MCP adapter startup and tool listing";
|
||||
const MCP_DOCTOR_SCOPE =
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from "incur";
|
||||
|
||||
import { CLI_DOCUMENT_OUTPUT_FORMATS } from "./cliOutput.js";
|
||||
|
||||
/** The options accepted by the production MCP doctor entry. */
|
||||
export const MCP_DOCTOR_OPTIONS = z.object({
|
||||
format: z
|
||||
.enum(CLI_DOCUMENT_OUTPUT_FORMATS)
|
||||
.optional()
|
||||
.describe("Output format"),
|
||||
json: z.boolean().optional().describe("Emit the report as JSON"),
|
||||
fullOutput: z.boolean().optional().describe("Show the full output envelope"),
|
||||
help: z.boolean().optional().describe("Show help"),
|
||||
});
|
||||
|
||||
export const MCP_DOCTOR_ALIAS = { help: "h" } as const;
|
||||
@@ -79,6 +79,15 @@ it("uses canonical global flag metadata and retains command options and enum hin
|
||||
"md",
|
||||
"jsonl",
|
||||
]);
|
||||
expect(complete(["rea", "mcp", "doctor", "--"])).toEqual([
|
||||
"--format",
|
||||
"--json",
|
||||
"--full-output",
|
||||
"--help",
|
||||
"--timings",
|
||||
]);
|
||||
expect(complete(["rea", "mcp", "doctor", "--t"])).toEqual(["--timings"]);
|
||||
expect(complete(["rea", "mcp", "doctor", "-h"])).toEqual(["-h"]);
|
||||
expect(complete(["rea", "--filter-output", "mcp", ""])).toEqual([
|
||||
"instructions",
|
||||
"mcp",
|
||||
@@ -116,6 +125,65 @@ it("shares Bash registration across ordinary and protocol routes while retaining
|
||||
expect(ordinary(["completions", "bash", "--schema"], {})).toBe(false);
|
||||
});
|
||||
|
||||
workspaceCliTest(
|
||||
"explains zsh completion initialization in public help",
|
||||
async ({ processes, workspace }) => {
|
||||
const environment = {
|
||||
...process.env,
|
||||
...workspace.environment,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_TEST_NODE: process.execPath,
|
||||
REA_TEST_DISPATCHER: resolve("scripts/rea.mjs"),
|
||||
};
|
||||
for (const arguments_ of [
|
||||
["completions", "--help"],
|
||||
["--json", "completions", "--help"],
|
||||
["completions"],
|
||||
]) {
|
||||
const help = await processes.run(
|
||||
process.execPath,
|
||||
[resolve("scripts/rea.mjs"), ...arguments_],
|
||||
{ env: environment },
|
||||
);
|
||||
expect(help.exitCode, help.stderr).toBe(0);
|
||||
expect(help.stdout).toContain(
|
||||
"initialize completion with `autoload -Uz compinit; compinit` before evaluating the zsh hook",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
workspaceCliTest.skipIf(process.platform !== "darwin")(
|
||||
"registers zsh completion after compinit",
|
||||
async ({ processes, workspace }) => {
|
||||
const environment = {
|
||||
...process.env,
|
||||
...workspace.environment,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_TEST_NODE: process.execPath,
|
||||
REA_TEST_DISPATCHER: resolve("scripts/rea.mjs"),
|
||||
};
|
||||
const result = await processes.run(
|
||||
"zsh",
|
||||
[
|
||||
"-f",
|
||||
"-c",
|
||||
`
|
||||
rea() { "$REA_TEST_NODE" "$REA_TEST_DISPATCHER" "$@"; }
|
||||
autoload -Uz compinit
|
||||
compinit -i -D
|
||||
eval "$(rea completions zsh)" || exit
|
||||
print -r -- "association=\${+_comps[rea]}"
|
||||
`,
|
||||
],
|
||||
{ env: environment },
|
||||
);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
expect(result.stdout).toContain("association=1\n");
|
||||
},
|
||||
);
|
||||
|
||||
workspaceCliTest.skipIf(process.platform === "win32")(
|
||||
"runs generated Bash completion with valid MCP scope and incomplete global values through the public launcher",
|
||||
async ({ processes, workspace }) => {
|
||||
@@ -136,6 +204,10 @@ COMP_WORDS=(rea instructions --format "")
|
||||
COMP_CWORD=3
|
||||
_incur_complete_rea || exit
|
||||
printf 'format=%s\\n' "\${COMPREPLY[*]}"
|
||||
COMP_WORDS=(rea mcp doctor --)
|
||||
COMP_CWORD=3
|
||||
_incur_complete_rea || exit
|
||||
printf 'doctor=%s\\n' "\${COMPREPLY[*]}"
|
||||
`,
|
||||
],
|
||||
{
|
||||
@@ -152,5 +224,8 @@ printf 'format=%s\\n' "\${COMPREPLY[*]}"
|
||||
expect(result.stderr).toBe("");
|
||||
expect(result.stdout).toContain("mcp=add doctor\n");
|
||||
expect(result.stdout).toContain("format=toon json yaml md jsonl csv tsv\n");
|
||||
expect(result.stdout).toContain(
|
||||
"doctor=--format --json --full-output --help --timings\n",
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user