fix(cli): require explicit setup selections
This commit is contained in:
parent
1b90e7f992
commit
992ec505b2
18
README.md
18
README.md
|
|
@ -97,15 +97,17 @@ shows its complete plan and asks before applying it. Setup does not update
|
|||
Homebrew, Node.js, or npm. `npx rea-agents setup` opens with the work it
|
||||
enables: investigate local apps from an agent, recover evidence through a
|
||||
deep-analysis provider, and reuse REA's guided workflow. It summarizes the
|
||||
detected agents, then offers **Set up all available capabilities
|
||||
(recommended)**, **Customize**, or **No thanks**. Customize shows every
|
||||
concrete target with an `MCP`, `provider`, or `skill` label.
|
||||
detected agents, then asks which independent capabilities to set up: coding-agent
|
||||
access through MCP, the shared investigation skill, and—when needed—the Hopper
|
||||
provider. Nothing is preselected. Choosing MCP access opens a second empty
|
||||
checklist for the specific detected agents that should receive a registration.
|
||||
|
||||
REA keeps the journey inline so its history remains in the terminal. Before
|
||||
anything changes, it validates existing configuration, prints exact paths and
|
||||
external effects, and asks for final approval with **No** as the default. The
|
||||
screen keeps the available keys visible while you choose; Ctrl-C and declining
|
||||
leave the system unchanged.
|
||||
REA keeps the journey inline so its history remains in the terminal. Selecting
|
||||
a capability does not select every detected target or authorize a change.
|
||||
Before anything changes, REA validates existing configuration, prints exact
|
||||
paths and external effects, and asks for final approval with **No** as the
|
||||
default. The screen keeps the available keys visible while you choose; Ctrl-C
|
||||
and declining leave the system unchanged.
|
||||
|
||||
REA detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, and Devin. It configures the first six when detected; Devin is reported but left unchanged because it has no documented local MCP configuration boundary. Registrations are additive, backup-first, and read back after writing. You can safely rerun setup.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,24 +50,23 @@ mechanics:
|
|||
- recover evidence through an available deep-analysis provider;
|
||||
- use the bundled skill for a repeatable investigation workflow.
|
||||
|
||||
REA then summarizes the detected clients and presents one gateway:
|
||||
REA then summarizes the detected clients and asks which independent
|
||||
capabilities to set up. The capability picker starts empty and labels each
|
||||
choice by its role: `MCP` for coding-agent access, `provider` for Hopper, and
|
||||
`skill` for the shared REA investigation workflow. Selecting MCP access opens a
|
||||
second empty checklist for the exact detected agents that should receive a
|
||||
registration. Detection provides context; it does not preselect or authorize a
|
||||
configuration write.
|
||||
|
||||
- **Set up all available capabilities (recommended)** selects the complete
|
||||
resolved plan;
|
||||
- **Customize** opens the concrete action picker;
|
||||
- **No thanks** exits without making changes.
|
||||
Choosing no capabilities exits without changes. Selecting the skill or provider
|
||||
does not implicitly select an agent, and selecting agent access does not include
|
||||
every detected agent. The picker keeps its navigation, selection, confirmation,
|
||||
and cancellation keys visible instead of relying on a transient hint.
|
||||
|
||||
The custom picker labels each target by its role: `MCP` for an agent
|
||||
registration, `provider` for Hopper or a validated bring-your-own provider, and
|
||||
`skill` for the REA investigation skill. These labels describe different setup
|
||||
modalities rather than suggesting that every target is an MCP registration.
|
||||
The picker keeps its navigation, selection, confirmation, and cancellation keys
|
||||
visible instead of relying on a transient hint.
|
||||
|
||||
Both recommended and custom paths converge on the same exact preflight. REA
|
||||
validates the current state, prints the proposed effects, and asks for final
|
||||
approval with **No** as the default. Selection alone never authorizes a
|
||||
mutation. The plan identifies:
|
||||
Every selected path converges on the same exact preflight. REA validates the
|
||||
current state, prints the proposed effects, and asks for final approval with
|
||||
**No** as the default. Selection alone never authorizes a mutation. The plan
|
||||
identifies:
|
||||
|
||||
- an existing Hopper installation, a validated bring-your-own Ghidra environment, or the official Hopper package it proposes to install;
|
||||
- each detected agent configuration path;
|
||||
|
|
|
|||
|
|
@ -1192,7 +1192,7 @@ async function verifyInteractiveSetup(command, env) {
|
|||
|
||||
terminal.onData((data) => {
|
||||
output += data;
|
||||
if (!cancelled && output.includes("Choose a setup")) {
|
||||
if (!cancelled && output.includes("What should REA set up?")) {
|
||||
cancelled = true;
|
||||
terminal.write("\u0003");
|
||||
}
|
||||
|
|
|
|||
199
src/cliSetup.ts
199
src/cliSetup.ts
|
|
@ -5,7 +5,6 @@ import {
|
|||
isCancel,
|
||||
multiselect,
|
||||
outro,
|
||||
select,
|
||||
} from "@clack/prompts";
|
||||
|
||||
import type {
|
||||
|
|
@ -29,7 +28,13 @@ const promptStreams = {
|
|||
withGuide: true,
|
||||
} as const;
|
||||
|
||||
type SetupMode = "recommended" | "custom" | "skip";
|
||||
const agentAccessCapabilityId = "agent_access";
|
||||
|
||||
interface SetupCapability {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly hint: string;
|
||||
}
|
||||
|
||||
/** Run the inline setup picker, exact preflight, and default-No consent. */
|
||||
export const confirmInteractiveSetup = async (
|
||||
|
|
@ -40,22 +45,8 @@ export const confirmInteractiveSetup = async (
|
|||
renderValueIntroduction();
|
||||
renderDetectedSummary(actions);
|
||||
renderKeyboardHelp(accessible);
|
||||
const mode = await selectSetupMode(actions, accessible);
|
||||
if (mode === undefined) return cancelledDecision(actions);
|
||||
if (mode === "skip") {
|
||||
cancel("No changes were made.", promptStreams);
|
||||
return {
|
||||
approved: false,
|
||||
selectedActionIds: actions.map(({ id }) => id),
|
||||
};
|
||||
}
|
||||
const selectedActionIds =
|
||||
mode === "recommended"
|
||||
? actions.map(({ id }) => id)
|
||||
: accessible
|
||||
? await selectActionsAccessibly(actions)
|
||||
: await selectActions(actions);
|
||||
if (selectedActionIds === undefined) return cancelledDecision(actions);
|
||||
const selectedActionIds = await selectSetupActions(actions, accessible);
|
||||
if (selectedActionIds === undefined) return cancelledDecision();
|
||||
if (selectedActionIds.length === 0) {
|
||||
cancel("Nothing selected. No changes were made.", promptStreams);
|
||||
return { approved: false, selectedActionIds };
|
||||
|
|
@ -131,18 +122,104 @@ export const conciseSetupResult = (result: SetupResult) => ({
|
|||
: { remediation: result.remediation }),
|
||||
});
|
||||
|
||||
const selectActions = async (
|
||||
const selectSetupActions = async (
|
||||
actions: readonly SetupAction[],
|
||||
accessible: boolean,
|
||||
): Promise<readonly string[] | undefined> => {
|
||||
const clientActions = actions.filter(
|
||||
({ kind }) => kind === "configure_client",
|
||||
);
|
||||
const componentActions = actions.filter(
|
||||
({ kind }) => kind !== "configure_client",
|
||||
);
|
||||
const capabilities = setupCapabilities(clientActions, componentActions);
|
||||
const selectedCapabilities = accessible
|
||||
? await selectCapabilitiesAccessibly(capabilities)
|
||||
: await selectCapabilities(capabilities);
|
||||
if (selectedCapabilities === undefined) return undefined;
|
||||
const selectedCapabilityIds = new Set(selectedCapabilities);
|
||||
const selectedActionIds = componentActions
|
||||
.filter(({ id }) => selectedCapabilityIds.has(id))
|
||||
.map(({ id }) => id);
|
||||
if (!selectedCapabilityIds.has(agentAccessCapabilityId))
|
||||
return selectedActionIds;
|
||||
const selectedClients = accessible
|
||||
? await selectClientsAccessibly(clientActions)
|
||||
: await selectClients(clientActions);
|
||||
return selectedClients === undefined
|
||||
? undefined
|
||||
: [...selectedClients, ...selectedActionIds];
|
||||
};
|
||||
|
||||
const setupCapabilities = (
|
||||
clientActions: readonly SetupAction[],
|
||||
componentActions: readonly SetupAction[],
|
||||
): readonly SetupCapability[] => [
|
||||
...(clientActions.length === 0
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: agentAccessCapabilityId,
|
||||
label: "Coding-agent access (MCP)",
|
||||
hint: `${String(clientActions.length)} detected ${clientActions.length === 1 ? "agent" : "agents"}`,
|
||||
},
|
||||
]),
|
||||
...componentActions.map((action) => ({
|
||||
id: action.id,
|
||||
label: `${action.label} (${actionModality(action)})`,
|
||||
hint: `${action.operation} · ${action.target}`,
|
||||
})),
|
||||
];
|
||||
|
||||
const selectCapabilities = async (
|
||||
capabilities: readonly SetupCapability[],
|
||||
): Promise<readonly string[] | undefined> => {
|
||||
const selection = await multiselect({
|
||||
...promptStreams,
|
||||
message: "What should REA set up?",
|
||||
options: capabilities.map((capability) => ({
|
||||
value: capability.id,
|
||||
label: capability.label,
|
||||
hint: capability.hint,
|
||||
})),
|
||||
required: false,
|
||||
maxItems: Math.max(
|
||||
3,
|
||||
Math.min(capabilities.length, (process.stderr.rows ?? 24) - 8),
|
||||
),
|
||||
showInstructions: true,
|
||||
});
|
||||
return isCancel(selection) ? undefined : selection;
|
||||
};
|
||||
|
||||
const selectCapabilitiesAccessibly = async (
|
||||
capabilities: readonly SetupCapability[],
|
||||
): Promise<readonly string[] | undefined> => {
|
||||
const selected: string[] = [];
|
||||
for (const capability of capabilities) {
|
||||
const included = await confirm({
|
||||
...promptStreams,
|
||||
message: `Set up ${capability.label}? ${capability.hint}`,
|
||||
initialValue: false,
|
||||
vertical: true,
|
||||
});
|
||||
if (isCancel(included)) return undefined;
|
||||
if (included) selected.push(capability.id);
|
||||
}
|
||||
return selected;
|
||||
};
|
||||
|
||||
const selectClients = async (
|
||||
actions: readonly SetupAction[],
|
||||
): Promise<readonly string[] | undefined> => {
|
||||
const selection = await multiselect({
|
||||
...promptStreams,
|
||||
message: "Choose what REA should configure",
|
||||
message: "Which agents should use REA?",
|
||||
options: actions.map((action) => ({
|
||||
value: action.id,
|
||||
label: `${action.label} (${actionModality(action)})`,
|
||||
label: `${action.label} (detected)`,
|
||||
hint: `${action.operation} · ${action.target}`,
|
||||
})),
|
||||
initialValues: actions.map(({ id }) => id),
|
||||
required: false,
|
||||
maxItems: Math.max(
|
||||
3,
|
||||
|
|
@ -153,15 +230,15 @@ const selectActions = async (
|
|||
return isCancel(selection) ? undefined : selection;
|
||||
};
|
||||
|
||||
const selectActionsAccessibly = async (
|
||||
const selectClientsAccessibly = async (
|
||||
actions: readonly SetupAction[],
|
||||
): Promise<readonly string[] | undefined> => {
|
||||
const selected: string[] = [];
|
||||
for (const action of actions) {
|
||||
const included = await confirm({
|
||||
...promptStreams,
|
||||
message: `Include ${action.label} (${actionModality(action)})? ${action.target}`,
|
||||
initialValue: true,
|
||||
message: `Configure ${action.label}? ${action.target}`,
|
||||
initialValue: false,
|
||||
vertical: true,
|
||||
});
|
||||
if (isCancel(included)) return undefined;
|
||||
|
|
@ -170,55 +247,6 @@ const selectActionsAccessibly = async (
|
|||
return selected;
|
||||
};
|
||||
|
||||
const selectSetupMode = async (
|
||||
actions: readonly SetupAction[],
|
||||
accessible: boolean,
|
||||
): Promise<SetupMode | undefined> => {
|
||||
if (accessible) return selectSetupModeAccessibly();
|
||||
const mode = await select<SetupMode>({
|
||||
...promptStreams,
|
||||
message: "Choose a setup",
|
||||
options: [
|
||||
{
|
||||
value: "recommended",
|
||||
label: "Set up all available capabilities",
|
||||
hint: `recommended · ${actionCount(actions.length)}`,
|
||||
},
|
||||
{
|
||||
value: "custom",
|
||||
label: "Customize setup",
|
||||
hint: "choose integrations and components",
|
||||
},
|
||||
{
|
||||
value: "skip",
|
||||
label: "No thanks",
|
||||
hint: "make no changes",
|
||||
},
|
||||
],
|
||||
initialValue: "recommended",
|
||||
});
|
||||
return isCancel(mode) ? undefined : mode;
|
||||
};
|
||||
|
||||
const selectSetupModeAccessibly = async (): Promise<SetupMode | undefined> => {
|
||||
const recommended = await confirm({
|
||||
...promptStreams,
|
||||
message: "Use the recommended complete setup?",
|
||||
initialValue: true,
|
||||
vertical: true,
|
||||
});
|
||||
if (isCancel(recommended)) return undefined;
|
||||
if (recommended) return "recommended";
|
||||
const customize = await confirm({
|
||||
...promptStreams,
|
||||
message: "Customize integrations and components?",
|
||||
initialValue: true,
|
||||
vertical: true,
|
||||
});
|
||||
if (isCancel(customize)) return undefined;
|
||||
return customize ? "custom" : "skip";
|
||||
};
|
||||
|
||||
const renderValueIntroduction = (): void => {
|
||||
writeLine("│ Understand local apps and binaries from your coding agent.");
|
||||
writeLine("│");
|
||||
|
|
@ -237,20 +265,10 @@ const renderDetectedSummary = (actions: readonly SetupAction[]): void => {
|
|||
if (clients.length === 0) {
|
||||
writeLine("◆ No agent integrations need configuration");
|
||||
} else {
|
||||
writeLine(`◆ Detected ${humanList(clients.map(({ label }) => label))}`);
|
||||
for (const client of clients) {
|
||||
writeLine(
|
||||
`│ ${client.label} · ${actionModality(client)} · ${client.operation}`,
|
||||
);
|
||||
writeLine(`│ ${client.target}`);
|
||||
}
|
||||
}
|
||||
const components = actions.filter(({ kind }) => kind !== "configure_client");
|
||||
if (components.length > 0) {
|
||||
writeLine("│");
|
||||
writeLine(
|
||||
`│ Also available: ${components.map((action) => `${action.label} (${actionModality(action)})`).join(", ")}`,
|
||||
`◆ Found ${String(clients.length)} supported ${clients.length === 1 ? "agent" : "agents"}`,
|
||||
);
|
||||
writeLine(`│ ${humanList(clients.map(({ label }) => label))}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -312,9 +330,6 @@ const actionModality = (action: SetupAction): string => {
|
|||
return "skill";
|
||||
};
|
||||
|
||||
const actionCount = (count: number): string =>
|
||||
`${String(count)} ${count === 1 ? "change" : "changes"}`;
|
||||
|
||||
const humanList = (values: readonly string[]): string => {
|
||||
if (values.length < 2) return values[0] ?? "";
|
||||
if (values.length === 2) return `${values[0]} and ${values[1]}`;
|
||||
|
|
@ -345,13 +360,11 @@ const renderPreflight = (actions: readonly SetupAction[]): void => {
|
|||
);
|
||||
};
|
||||
|
||||
const cancelledDecision = (
|
||||
actions: readonly SetupAction[],
|
||||
): SetupConfirmationDecision => {
|
||||
const cancelledDecision = (): SetupConfirmationDecision => {
|
||||
cancel("Setup cancelled. No changes were made.", promptStreams);
|
||||
return {
|
||||
approved: false,
|
||||
selectedActionIds: actions.map(({ id }) => id),
|
||||
selectedActionIds: [],
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ const actions = [
|
|||
] as const;
|
||||
|
||||
describe("interactive setup journey", () => {
|
||||
it("leads with value, detected targets, modality labels, and durable key help", async () => {
|
||||
const result = await runJourney([step("Choose a setup", "\u0003")]);
|
||||
it("leads with value, a compact detected-target summary, and durable key help", async () => {
|
||||
const result = await runJourney([
|
||||
step("What should REA set up?", "\u0003"),
|
||||
]);
|
||||
|
||||
expect(result.output).toContain(
|
||||
"Understand local apps and binaries from your coding agent.",
|
||||
|
|
@ -50,8 +52,8 @@ describe("interactive setup journey", () => {
|
|||
expect(result.output).toContain(
|
||||
"Trace how a feature works with local evidence.",
|
||||
);
|
||||
expect(result.output).toContain("Detected Codex");
|
||||
expect(result.output).toContain("Codex · MCP · create");
|
||||
expect(result.output).toContain("Found 1 supported agent");
|
||||
expect(result.output).toContain("Codex");
|
||||
expect(result.output).toContain("Hopper deep-analysis provider (provider)");
|
||||
expect(result.output).toContain("REA reverse-engineering skill (skill)");
|
||||
expect(result.output).toContain(
|
||||
|
|
@ -60,49 +62,122 @@ describe("interactive setup journey", () => {
|
|||
expect(result.decision.approved).toBe(false);
|
||||
});
|
||||
|
||||
it("offers recommended, custom, and no-thanks intent before choosing targets", async () => {
|
||||
const result = await runJourney([
|
||||
step("Choose a setup", "\u001b[B\u001b[B\r"),
|
||||
]);
|
||||
it("does not turn available capabilities into selected setup actions", async () => {
|
||||
const result = await runJourney([step("What should REA set up?", "\r")]);
|
||||
|
||||
expect(result.output).toContain("Set up all available capabilities");
|
||||
expect(result.output).toContain("recommended · 3 changes");
|
||||
expect(result.output).toContain("Customize setup");
|
||||
expect(result.output).toContain("No thanks");
|
||||
expect(result.output).toContain("Coding-agent access (MCP)");
|
||||
expect(result.output).toContain("REA reverse-engineering skill (skill)");
|
||||
expect(result.output).toContain("Hopper deep-analysis provider (provider)");
|
||||
expect(result.output).not.toContain("Ready to review");
|
||||
expect(result.decision).toMatchObject({ approved: false });
|
||||
expect(result.output).toContain("Nothing selected. No changes were made.");
|
||||
expect(result.decision).toEqual({
|
||||
approved: false,
|
||||
selectedActionIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("lets custom mode reach the modality-labelled target picker and cancel safely", async () => {
|
||||
it("omits MCP access when no agent integration needs configuration", async () => {
|
||||
const result = await runJourney(
|
||||
[step("What should REA set up?", "\r")],
|
||||
false,
|
||||
actions.filter(({ kind }) => kind !== "configure_client"),
|
||||
);
|
||||
|
||||
expect(result.output).toContain("No agent integrations need configuration");
|
||||
expect(result.output).not.toContain("Coding-agent access (MCP)");
|
||||
expect(result.decision.selectedActionIds).toEqual([]);
|
||||
});
|
||||
|
||||
it("shows multiple detected agents without preselecting either one", async () => {
|
||||
const result = await runJourney(
|
||||
[
|
||||
step("What should REA set up?", " \r"),
|
||||
step("Which agents should use REA?", "\u0003"),
|
||||
],
|
||||
false,
|
||||
[
|
||||
actions[0],
|
||||
{
|
||||
...actions[0],
|
||||
id: "configure_client:cursor",
|
||||
label: "Cursor",
|
||||
target: "/isolated/.cursor/mcp.json",
|
||||
},
|
||||
...actions.slice(1),
|
||||
],
|
||||
);
|
||||
|
||||
expect(result.output).toContain("Codex (detected)");
|
||||
expect(result.output).toContain("Cursor (detected)");
|
||||
expect(result.decision.approved).toBe(false);
|
||||
});
|
||||
|
||||
it("asks for agent targets only after MCP access is selected", async () => {
|
||||
const result = await runJourney([
|
||||
step("Choose a setup", "\u001b[B\r"),
|
||||
step("Choose what REA should configure", "\u0003"),
|
||||
step("What should REA set up?", " \r"),
|
||||
step("Which agents should use REA?", "\u0003"),
|
||||
]);
|
||||
|
||||
expect(result.output).toContain("Choose what REA should configure");
|
||||
expect(result.output).toContain("Codex (MCP)");
|
||||
expect(result.output).toContain("Hopper deep-analysis provider (provider)");
|
||||
expect(result.output).toContain("Codex (detected)");
|
||||
expect(result.output).toContain("Setup cancelled. No changes were made.");
|
||||
expect(result.decision.approved).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the recommended path subject to a default-No exact preflight", async () => {
|
||||
it("reviews only explicitly selected actions with default-No consent", async () => {
|
||||
const result = await runJourney([
|
||||
step("Choose a setup", "\r"),
|
||||
step("Apply these 3 changes?", "\r"),
|
||||
step("What should REA set up?", " \r"),
|
||||
step("Which agents should use REA?", " \r"),
|
||||
step("Apply this change?", "\r"),
|
||||
]);
|
||||
|
||||
expect(result.output).toContain("Ready to review");
|
||||
expect(result.output).toContain("CREATE Codex");
|
||||
expect(result.output).toContain("/isolated/.codex/config.toml");
|
||||
expect(result.output).not.toContain(
|
||||
"INSTALL REA reverse-engineering skill",
|
||||
);
|
||||
expect(result.output).not.toContain(
|
||||
"INSTALL Hopper deep-analysis provider",
|
||||
);
|
||||
expect(result.output).toContain("No, cancel");
|
||||
expect(result.output).toContain("Setup cancelled. No changes were made.");
|
||||
expect(result.decision).toEqual({
|
||||
approved: false,
|
||||
selectedActionIds: actions.map(({ id }) => id),
|
||||
selectedActionIds: ["configure_client:codex"],
|
||||
});
|
||||
});
|
||||
|
||||
it("can select the shared skill without selecting an agent integration", async () => {
|
||||
const result = await runJourney([
|
||||
step("What should REA set up?", "\u001b[B \r"),
|
||||
step("Apply this change?", "\r"),
|
||||
]);
|
||||
|
||||
expect(result.output).toContain("INSTALL REA reverse-engineering skill");
|
||||
expect(result.output).not.toContain("CREATE Codex");
|
||||
expect(result.output).not.toContain(
|
||||
"INSTALL Hopper deep-analysis provider",
|
||||
);
|
||||
expect(result.decision).toEqual({
|
||||
approved: false,
|
||||
selectedActionIds: ["install_skill"],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps every accessible capability prompt defaulted to skip", async () => {
|
||||
const result = await runJourney(
|
||||
[
|
||||
step("Set up Coding-agent access (MCP)?", "\r"),
|
||||
step("Set up REA reverse-engineering skill (skill)?", "\r"),
|
||||
step("Set up Hopper deep-analysis provider (provider)?", "\r"),
|
||||
],
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result.output).not.toContain("Ready to review");
|
||||
expect(result.decision.selectedActionIds).toEqual([]);
|
||||
});
|
||||
|
||||
it("frames completion around the capabilities that are now ready", async () => {
|
||||
const { stderr } = await execute(
|
||||
process.execPath,
|
||||
|
|
@ -163,12 +238,14 @@ const step = (prompt: string, input: string): JourneyStep => ({
|
|||
|
||||
const runJourney = async (
|
||||
steps: readonly JourneyStep[],
|
||||
accessible = false,
|
||||
journeyActions: readonly object[] = actions,
|
||||
): Promise<JourneyResult> => {
|
||||
const isolatedHome = await mkdtemp(join(tmpdir(), "rea-cli-setup-test-"));
|
||||
const script = [
|
||||
'import { confirmInteractiveSetup } from "./dist/cliSetup.js";',
|
||||
`const actions = ${JSON.stringify(actions)};`,
|
||||
"const decision = await confirmInteractiveSetup(actions, false);",
|
||||
`const actions = ${JSON.stringify(journeyActions)};`,
|
||||
`const decision = await confirmInteractiveSetup(actions, ${JSON.stringify(accessible)});`,
|
||||
`process.stdout.write(${JSON.stringify(`\n${decisionMarker}`)} + JSON.stringify(decision) + "\\n");`,
|
||||
].join("\n");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue