/** * run-quest-wizard.tsx — Standalone launcher for the QuestWizard Ink component. * * Creates and destroys its own Ink render instance, returning a Promise * that resolves with the created Quest or null if cancelled. * * This is the Ink equivalent of `runQuestWizardAsync()` from * tui-quest-wizard.ts, supporting the "standalone" render mode. * * Usage: * const quest = await runQuestWizardInk({ * projectRoot: "/path/to/project", * baseBranch: "main", * prefill: { goal: "Migrate auth to OAuth2" }, * }); * if (quest) { * console.log(`Created quest: ${quest.id}`); * } */ import React from "react"; import { getStableStdin } from "./bun-stdin"; import { render } from "ink"; import { QuestWizard, type QuestWizardPrefill } from "./quest-wizard"; import { loadQuest, saveQuest } from "../lib/quest-store"; import type { Quest } from "../lib/quest"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface RunQuestWizardOptions { /** Project root directory (for loadQuest duplicate checks, saveQuest). */ projectRoot: string; /** Base branch for the new quest's branch. */ baseBranch: string; /** Optional pre-filled values for wizard fields. */ prefill?: QuestWizardPrefill; } // --------------------------------------------------------------------------- // Standalone launcher // --------------------------------------------------------------------------- /** * Run the quest creation wizard as a standalone Ink instance. * * Creates and destroys its own Ink render context. Returns a Promise * that resolves with the created Quest or null if the user cancelled. * * This is the "standalone" render mode — for overlay mode, render * `` directly inside your existing Ink tree. */ export function runQuestWizardInk( opts: RunQuestWizardOptions ): Promise { const { projectRoot, baseBranch, prefill } = opts; return new Promise((resolve) => { const checkDuplicateId = (id: string): string | null => { const existing = loadQuest(projectRoot, id); return existing ? existing.status : null; }; const saveQuestFn = (quest: Quest): void => { saveQuest(projectRoot, quest); }; let instance: ReturnType; const handleCreated = (quest: Quest) => { // Brief delay for confirmation display, then unmount setTimeout(() => { instance.unmount(); resolve(quest); }, 1200); }; const handleCancelled = () => { instance.unmount(); resolve(null); }; process.stdin.resume(); // keep event loop alive between renders instance = render( , { exitOnCtrlC: false, stdin: getStableStdin() } ); }); }