Просмотр исходного кода

feat(tui): scaffold project workspace shell

Ma 7 месяцев назад
Родитель
Сommit
3bf7cc704a

+ 32 - 0
packages/cli/src/__tests__/tui-layout.test.ts

@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest";
+import { renderTuiFrame } from "../tui/app.js";
+
+describe("tui layout", () => {
+  it("renders a project-scoped idle workspace frame", () => {
+    const frame = renderTuiFrame({
+      projectName: "inkos-demo",
+      activeBookTitle: undefined,
+      automationMode: "semi",
+      status: "idle",
+    });
+
+    expect(frame).toContain("Project: inkos-demo");
+    expect(frame).toContain("Book: none");
+    expect(frame).toContain("Mode: semi");
+    expect(frame).toContain("Stage: idle");
+    expect(frame).toContain(">");
+  });
+
+  it("renders an active book and stage when one is bound", () => {
+    const frame = renderTuiFrame({
+      projectName: "inkos-demo",
+      activeBookTitle: "Night Harbor Echo",
+      automationMode: "auto",
+      status: "writing",
+    });
+
+    expect(frame).toContain("Book: Night Harbor Echo");
+    expect(frame).toContain("Mode: auto");
+    expect(frame).toContain("Stage: writing");
+  });
+});

+ 6 - 2
packages/cli/src/commands/tui.ts

@@ -1,4 +1,5 @@
 import { Command } from "commander";
+import { launchTui } from "../tui/app.js";
 
 export interface TuiCommandHooks {
   readonly launchTui?: (projectRoot: string) => Promise<void> | void;
@@ -8,7 +9,10 @@ export function createTuiCommand(hooks: TuiCommandHooks = {}): Command {
   return new Command("tui")
     .description("Open the InkOS project workspace TUI")
     .action(async () => {
-      await hooks.launchTui?.(process.cwd());
+      if (hooks.launchTui) {
+        await hooks.launchTui(process.cwd());
+        return;
+      }
+      await launchTui(process.cwd());
     });
 }
-

+ 6 - 1
packages/cli/src/program.ts

@@ -27,6 +27,7 @@ import { fanficCommand } from "./commands/fanfic.js";
 import { studioCommand } from "./commands/studio.js";
 import { consolidateCommand } from "./commands/consolidate.js";
 import { createTuiCommand } from "./commands/tui.js";
+import { launchTui } from "./tui/app.js";
 
 const require = createRequire(import.meta.url);
 const { version } = require("../package.json") as { version: string };
@@ -43,7 +44,11 @@ export function createProgram(hooks: ProgramHooks = {}): Command {
     .description("InkOS — Multi-agent novel production system")
     .version(version)
     .action(async () => {
-      await hooks.launchTui?.(process.cwd());
+      if (hooks.launchTui) {
+        await hooks.launchTui(process.cwd());
+        return;
+      }
+      await launchTui(process.cwd());
     });
 
   program.addCommand(initCommand);

+ 49 - 0
packages/cli/src/tui/app.ts

@@ -0,0 +1,49 @@
+import { basename } from "node:path";
+import readline from "node:readline/promises";
+import type { AutomationMode } from "@actalk/inkos-core";
+import type { ExecutionStatus } from "@actalk/inkos-core";
+
+export interface TuiFrameState {
+  readonly projectName: string;
+  readonly activeBookTitle?: string;
+  readonly automationMode: AutomationMode;
+  readonly status: ExecutionStatus;
+}
+
+export function renderTuiFrame(state: TuiFrameState): string {
+  const lines = [
+    `Project: ${state.projectName}`,
+    `Book: ${state.activeBookTitle ?? "none"}`,
+    `Mode: ${state.automationMode}`,
+    `Stage: ${state.status}`,
+    "",
+    "> ",
+  ];
+
+  return lines.join("\n");
+}
+
+export async function launchTui(projectRoot: string): Promise<void> {
+  const frame = renderTuiFrame({
+    projectName: basename(projectRoot),
+    automationMode: "semi",
+    status: "idle",
+  });
+
+  process.stdout.write(frame);
+
+  if (!process.stdin.isTTY || !process.stdout.isTTY) {
+    return;
+  }
+
+  const rl = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+  });
+
+  try {
+    await rl.question("");
+  } finally {
+    rl.close();
+  }
+}