fix(setup): show a known manual step in the interactive dry run

The approval prompt now discloses a client's manual completion step,
but the interactive TTY dry run still printed only the planned file
changes, so `rea setup --dry-run --client grok_bot` hid the Grok Bot
connector step. The dry-run renderer now prints the planned result's
remediation the same way.

Refs #7692

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
gensokyo-devandgensokyo-dev authored and Ysopwnable committed 2026-11-14 14:16:39 +01:00
1 parent bbad1db998
commit cf58037726
3 files changed
+62 -26

No files matched your search

+9 -1
View File
@@ -94,9 +94,17 @@ export const renderSetupProgress = (event: SetupProgressEvent): void => {
};
/** Show the resolved dry-run plan to a human at an interactive terminal. */
export const renderSetupDryRun = (actions: readonly SetupAction[]): void => {
export const renderSetupDryRun = (
actions: readonly SetupAction[],
remediation?: string,
): void => {
intro("REA setup plan", promptStreams);
renderPreflight(actions);
// A known manual step belongs to the plan, as it does at approval.
if (remediation !== undefined) {
writeLine("│");
writeLine(`│ ${remediation}`);
}
outro("Dry run complete. No changes were made.", promptStreams);
};
+2 -1
View File
@@ -282,7 +282,8 @@ const runSetupCommand = async (
!input.formatExplicit &&
hasInteractiveTerminal()
) {
if (result.status === "planned") renderSetupDryRun(result.plannedActions);
if (result.status === "planned")
renderSetupDryRun(result.plannedActions, result.remediation);
else renderInteractiveSetupResult(result, environment);
}
return result;
+51 -24
View File
@@ -277,38 +277,47 @@ const runSetupInTty = async (
return { exitCode, output };
};
const manualConnectorFixture = async (): Promise<{
readonly home: string;
readonly environment: NodeJS.ProcessEnv;
}> => {
const home = await createTestTempDirectory("rea-cli-setup-manual-");
const bin = join(home, "bin");
await mkdir(bin);
const hopper = join(bin, "hopper");
await writeFile(hopper, "#!/bin/sh\necho 'Hopper Disassembler v6.0.0'\n", {
mode: 0o755,
});
// Linux's executable probe runs ldd before accepting this inert launcher.
await writeFile(join(bin, "ldd"), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
for (const runner of ["npx", "npm", "bun"])
await writeFile(join(bin, runner), "#!/bin/sh\nexit 99\n", {
mode: 0o755,
});
return {
home,
environment: {
PATH: `${bin}:${process.env.PATH ?? ""}`,
HOPPER_LAUNCHER_PATH: hopper,
DSH_HOME: join(home, ".dsh"),
DSH_AGENTS_HOME: join(home, ".dsh-agents"),
XDG_DATA_HOME: join(home, ".local/share"),
XDG_CACHE_HOME: join(home, ".cache"),
XDG_STATE_HOME: join(home, ".local/state"),
},
};
};
describe("manual connector approval", () => {
it.skipIf(process.platform === "win32").each([true, false])(
"discloses the manual step before approval (apply: %s)",
async (approved) => {
const home = await createTestTempDirectory("rea-cli-setup-manual-");
const bin = join(home, "bin");
await mkdir(bin);
const hopper = join(bin, "hopper");
await writeFile(
hopper,
"#!/bin/sh\necho 'Hopper Disassembler v6.0.0'\n",
{ mode: 0o755 },
);
// Linux's executable probe runs ldd before accepting this inert launcher.
await writeFile(join(bin, "ldd"), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
for (const runner of ["npx", "npm", "bun"])
await writeFile(join(bin, runner), "#!/bin/sh\nexit 99\n", {
mode: 0o755,
});
const { home, environment } = await manualConnectorFixture();
const { output, exitCode } = await runSetupInTty(
home,
["--client", "grok_bot"],
approved,
{
PATH: `${bin}:${process.env.PATH ?? ""}`,
HOPPER_LAUNCHER_PATH: hopper,
DSH_HOME: join(home, ".dsh"),
DSH_AGENTS_HOME: join(home, ".dsh-agents"),
XDG_DATA_HOME: join(home, ".local/share"),
XDG_CACHE_HOME: join(home, ".cache"),
XDG_STATE_HOME: join(home, ".local/state"),
},
environment,
);
const prompt = output.indexOf("Apply this change?");
const remediation = manualRegistrationRemediation("grok_bot");
@@ -328,6 +337,24 @@ describe("manual connector approval", () => {
}
},
);
it.skipIf(process.platform === "win32")(
"shows the manual step in a TTY dry run",
async () => {
const { home, environment } = await manualConnectorFixture();
const { output, exitCode } = await runSetupInTty(
home,
["--dry-run", "--client", "grok_bot"],
undefined,
environment,
);
const remediation = manualRegistrationRemediation("grok_bot");
expect(remediation).toBeDefined();
expect(exitCode, output).toBe(0);
expect(output).toContain(remediation);
expect(output).toContain("Dry run complete. No changes were made.");
},
);
});
describe("TTY setup dry run", () => {