From a78b958e1211f7546a57aee93aeffca3e6ca8cf8 Mon Sep 17 00:00:00 2001 From: Ma Date: Mon, 13 Apr 2026 01:26:51 +0800 Subject: [PATCH] feat(tui): rebuild the interface around an Ink dashboard --- .../2026-04-11-ink-tui-dashboard-design.md | 76 + ...-04-11-ink-tui-dashboard-implementation.md | 153 ++ packages/cli/package.json | 11 +- .../src/__tests__/tui-activity-state.test.ts | 20 + .../cli/src/__tests__/tui-chat-depth.test.ts | 25 + .../cli/src/__tests__/tui-chat-draft.test.ts | 43 + .../__tests__/tui-composer-display.test.ts | 18 + .../src/__tests__/tui-control-flow.test.ts | 24 + .../cli/src/__tests__/tui-dashboard.test.tsx | 140 + .../src/__tests__/tui-effects-i18n.test.ts | 29 + packages/cli/src/__tests__/tui-i18n.test.ts | 22 + .../src/__tests__/tui-input-history.test.ts | 40 + packages/cli/src/__tests__/tui-layout.test.ts | 43 +- .../src/__tests__/tui-local-commands.test.ts | 47 + packages/cli/src/__tests__/tui-output.test.ts | 10 + .../cli/src/__tests__/tui-setup-i18n.test.ts | 18 + .../__tests__/tui-slash-autocomplete.test.ts | 31 + packages/cli/src/tui/activity-state.ts | 41 + packages/cli/src/tui/app.ts | 343 +-- packages/cli/src/tui/chat-depth.ts | 20 + packages/cli/src/tui/chat-draft.ts | 42 + packages/cli/src/tui/composer-display.ts | 16 + packages/cli/src/tui/dashboard-model.ts | 145 ++ packages/cli/src/tui/dashboard.tsx | 447 ++++ packages/cli/src/tui/effects.ts | 281 +- packages/cli/src/tui/i18n.ts | 295 +++ packages/cli/src/tui/input-history.ts | 69 + packages/cli/src/tui/local-commands.ts | 55 + packages/cli/src/tui/output.ts | 30 +- packages/cli/src/tui/setup.ts | 162 +- packages/cli/src/tui/slash-autocomplete.ts | 44 + packages/cli/src/tui/theme.ts | 4 + packages/cli/src/tui/tools.ts | 20 +- packages/cli/tsconfig.json | 1 + .../core/src/interaction/project-control.ts | 43 +- .../core/src/interaction/project-tools.ts | 14 +- packages/core/src/interaction/runtime.ts | 441 +++- packages/core/src/llm/provider.ts | 26 +- pnpm-lock.yaml | 2308 ++++++++++++++++- 39 files changed, 5012 insertions(+), 585 deletions(-) create mode 100644 docs/plans/2026-04-11-ink-tui-dashboard-design.md create mode 100644 docs/plans/2026-04-11-ink-tui-dashboard-implementation.md create mode 100644 packages/cli/src/__tests__/tui-activity-state.test.ts create mode 100644 packages/cli/src/__tests__/tui-chat-depth.test.ts create mode 100644 packages/cli/src/__tests__/tui-chat-draft.test.ts create mode 100644 packages/cli/src/__tests__/tui-composer-display.test.ts create mode 100644 packages/cli/src/__tests__/tui-dashboard.test.tsx create mode 100644 packages/cli/src/__tests__/tui-effects-i18n.test.ts create mode 100644 packages/cli/src/__tests__/tui-i18n.test.ts create mode 100644 packages/cli/src/__tests__/tui-input-history.test.ts create mode 100644 packages/cli/src/__tests__/tui-local-commands.test.ts create mode 100644 packages/cli/src/__tests__/tui-setup-i18n.test.ts create mode 100644 packages/cli/src/__tests__/tui-slash-autocomplete.test.ts create mode 100644 packages/cli/src/tui/activity-state.ts create mode 100644 packages/cli/src/tui/chat-depth.ts create mode 100644 packages/cli/src/tui/chat-draft.ts create mode 100644 packages/cli/src/tui/composer-display.ts create mode 100644 packages/cli/src/tui/dashboard-model.ts create mode 100644 packages/cli/src/tui/dashboard.tsx create mode 100644 packages/cli/src/tui/i18n.ts create mode 100644 packages/cli/src/tui/input-history.ts create mode 100644 packages/cli/src/tui/local-commands.ts create mode 100644 packages/cli/src/tui/slash-autocomplete.ts create mode 100644 packages/cli/src/tui/theme.ts diff --git a/docs/plans/2026-04-11-ink-tui-dashboard-design.md b/docs/plans/2026-04-11-ink-tui-dashboard-design.md new file mode 100644 index 00000000..2c967146 --- /dev/null +++ b/docs/plans/2026-04-11-ink-tui-dashboard-design.md @@ -0,0 +1,76 @@ +# Ink TUI Dashboard Design + +**Goal:** Replace the current readline-driven TUI shell with a fixed-layout Ink dashboard so conversation, execution status, and the composer remain visible at the same time. + +## Context + +The current TUI is a linear REPL layered on top of `readline` and ANSI helpers. That shape creates the exact UX problems reported by users: + +- the input area is only a prompt prefix, not a persistent composer +- task status is printed inline below the conversation instead of being anchored near the composer +- output pushes the input area downward, so the screen feels unstable +- the shell looks like logs, not a workspace + +The interaction core itself is not the problem. Session state, routing, execution, and persistence already exist in `@actalk/inkos-core`. + +## Proposed Architecture + +Keep the existing interaction core and replace only the CLI shell: + +- `packages/cli/src/tui/app.ts` + becomes a thin launcher that prepares project/model/tools and mounts an Ink app +- new Ink UI modules in `packages/cli/src/tui/` + own layout, input, and rendering +- existing session persistence stays in `session-store.ts` +- existing command execution still flows through `processProjectInteractionInput` + +## Layout + +The dashboard is a fixed vertical stack: + +1. `Header` + project, active book, automation mode, model, and a compact execution badge +2. `Conversation` + recent user / assistant / system messages with stable visual separation +3. `Status Rail` + current stage, pending decision, and recent events +4. `Composer` + highlighted input box, helper text, and submit state + +The composer stays visually anchored at the bottom of the dashboard. Execution state sits directly above it. + +## Rendering Strategy + +Use Ink components instead of manual `console.log` output: + +- Ink layout primitives for fixed regions +- Ink input handling for keyboard events +- an Ink text input component for the composer +- small view-model helpers that derive display data from the persisted interaction session + +No screen-level ANSI animation survives unless it can be expressed as component state. The redesign favors a stable, readable shell over decorative startup animation. + +## Interaction Model + +- user submits text in the composer +- shell enters `submitting` state +- `processProjectInteractionInput` runs with existing tools +- returned session replaces local session state +- conversation and status panes refresh from the updated session + +Errors are shown in the status rail and remain visible until the next successful interaction. + +## Non-Goals + +- changing the interaction runtime or natural-language router +- changing daemon behavior +- adding a full message history scroller in this pass +- introducing Studio-style navigation into CLI + +## Success Criteria + +- the composer is always visually distinct and highlighted +- the current execution state is visible without running `/status` +- recent events appear above the composer instead of below it +- new output no longer pushes the prompt into a visually unstable position +- the CLI package still builds and TUI-focused tests cover the new layout contract diff --git a/docs/plans/2026-04-11-ink-tui-dashboard-implementation.md b/docs/plans/2026-04-11-ink-tui-dashboard-implementation.md new file mode 100644 index 00000000..e8f1318e --- /dev/null +++ b/docs/plans/2026-04-11-ink-tui-dashboard-implementation.md @@ -0,0 +1,153 @@ +# Ink TUI Dashboard Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Rebuild the InkOS CLI TUI as a fixed-layout Ink dashboard with a persistent highlighted composer and a status rail directly above it. + +**Architecture:** Keep `@actalk/inkos-core` unchanged and swap the CLI shell from `readline + ANSI` to `Ink + React`. `app.ts` becomes the launcher; new dashboard components render header, conversation, status, and composer from persisted interaction session data. + +**Tech Stack:** TypeScript, React, Ink, Vitest + +--- + +### Task 1: Add the Ink runtime dependencies + +**Files:** +- Modify: `packages/cli/package.json` +- Modify: `pnpm-lock.yaml` + +**Step 1: Add CLI dependencies** + +Add the runtime and test dependencies needed for the new shell. + +**Step 2: Verify installation** + +Run: `pnpm --dir packages/cli install` + +Expected: dependencies resolve and lockfile updates cleanly. + +### Task 2: Add failing tests for the dashboard contract + +**Files:** +- Modify: `packages/cli/src/__tests__/tui-layout.test.ts` +- Create: `packages/cli/src/__tests__/tui-dashboard.test.tsx` + +**Step 1: Write failing tests** + +Cover: +- header displays project, book, mode, and model +- status rail appears above the composer +- composer placeholder / highlight shell renders +- conversation pane renders user and assistant messages separately + +**Step 2: Run tests to verify they fail** + +Run: `pnpm --dir packages/cli exec vitest run src/__tests__/tui-layout.test.ts src/__tests__/tui-dashboard.test.tsx` + +Expected: FAIL because the Ink dashboard components do not exist yet. + +### Task 3: Build dashboard view-model helpers + +**Files:** +- Create: `packages/cli/src/tui/dashboard-model.ts` +- Create: `packages/cli/src/tui/dashboard-model.test.ts` + +**Step 1: Write failing tests** + +Verify session data is mapped into: +- header badge data +- conversation rows +- recent event rows +- pending decision summary + +**Step 2: Run tests to verify they fail** + +Run: `pnpm --dir packages/cli exec vitest run src/tui/dashboard-model.test.ts` + +Expected: FAIL because the helpers do not exist yet. + +**Step 3: Write minimal implementation** + +Create pure helpers so the Ink components can stay thin. + +**Step 4: Run tests to verify they pass** + +Run: `pnpm --dir packages/cli exec vitest run src/tui/dashboard-model.test.ts` + +Expected: PASS + +### Task 4: Implement the Ink dashboard shell + +**Files:** +- Create: `packages/cli/src/tui/dashboard.tsx` +- Modify: `packages/cli/src/tui/app.ts` +- Optionally modify: `packages/cli/src/tui/output.ts` + +**Step 1: Write or extend a failing dashboard render test** + +Assert that the mounted dashboard contains: +- a fixed header +- conversation content +- status rail +- highlighted composer + +**Step 2: Run tests to verify they fail** + +Run: `pnpm --dir packages/cli exec vitest run src/__tests__/tui-dashboard.test.tsx` + +Expected: FAIL + +**Step 3: Write minimal implementation** + +Implement: +- `InkTuiDashboard` component +- local submit state +- composer submission through `processProjectInteractionInput` +- session-driven refresh after each interaction + +**Step 4: Run tests to verify they pass** + +Run: `pnpm --dir packages/cli exec vitest run src/__tests__/tui-dashboard.test.tsx src/__tests__/tui-layout.test.ts` + +Expected: PASS + +### Task 5: Reconcile launcher behavior and remove obsolete readline assumptions + +**Files:** +- Modify: `packages/cli/src/tui/app.ts` +- Modify: `packages/cli/src/tui/effects.ts` +- Modify or remove: old prompt-related tests as needed + +**Step 1: Replace the old REPL entry** + +Make `launchTui()` mount Ink instead of starting a `readline` loop. + +**Step 2: Verify startup / setup flow** + +Run: `pnpm --dir packages/cli exec vitest run src/__tests__/tui-command.test.ts src/__tests__/tui-layout.test.ts src/__tests__/tui-dashboard.test.tsx` + +Expected: PASS + +### Task 6: Build verification + +**Files:** +- Modify: any files touched above + +**Step 1: Run focused tests** + +Run: `pnpm --dir packages/cli exec vitest run src/__tests__/tui-layout.test.ts src/__tests__/tui-dashboard.test.tsx` + +Expected: PASS + +**Step 2: Run CLI build** + +Run: `pnpm --dir packages/cli run build` + +Expected: PASS + +**Step 3: Commit** + +```bash +git add docs/plans/2026-04-11-ink-tui-dashboard-design.md docs/plans/2026-04-11-ink-tui-dashboard-implementation.md packages/cli/package.json pnpm-lock.yaml packages/cli/src/tui/app.ts packages/cli/src/tui/dashboard.tsx packages/cli/src/tui/dashboard-model.ts packages/cli/src/__tests__/tui-layout.test.ts packages/cli/src/__tests__/tui-dashboard.test.tsx +git commit -m "feat(tui): rebuild CLI shell with Ink dashboard" +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index de7a4990..7ab67280 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,11 +48,16 @@ "commander": "^13.0.0", "dotenv": "^16.4.0", "epub-gen-memory": "^1.0.10", - "marked": "^15.0.0" + "ink": "^7.0.0", + "ink-text-input": "^6.0.0", + "marked": "^15.0.0", + "react": "^19.2.4" }, "devDependencies": { + "@types/react": "^19.2.14", + "@types/node": "^22.0.0", + "ink-testing-library": "^4.0.0", "typescript": "^5.8.0", - "vitest": "^3.0.0", - "@types/node": "^22.0.0" + "vitest": "^3.0.0" } } diff --git a/packages/cli/src/__tests__/tui-activity-state.test.ts b/packages/cli/src/__tests__/tui-activity-state.test.ts new file mode 100644 index 00000000..f5482d94 --- /dev/null +++ b/packages/cli/src/__tests__/tui-activity-state.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { describeActivityState } from "../tui/activity-state.js"; +import { getTuiCopy } from "../tui/i18n.js"; +import { WARM_ACCENT } from "../tui/theme.js"; + +describe("tui activity state", () => { + it("maps chat-like intents to thinking", () => { + const copy = getTuiCopy("en"); + expect(describeActivityState("chat", copy)).toMatchObject({ label: "thinking", accent: WARM_ACCENT }); + expect(describeActivityState("explain_status", copy)).toMatchObject({ label: "checking", accent: WARM_ACCENT }); + }); + + it("maps writing and review intents to task-specific labels", () => { + const zhCopy = getTuiCopy("zh-CN"); + expect(describeActivityState("write_next", zhCopy)).toMatchObject({ label: "写作中", accent: WARM_ACCENT }); + expect(describeActivityState("revise_chapter", zhCopy)).toMatchObject({ label: "审阅中", accent: WARM_ACCENT }); + expect(describeActivityState("rewrite_chapter", zhCopy)).toMatchObject({ label: "审阅中", accent: WARM_ACCENT }); + expect(describeActivityState("chat", zhCopy).intervalMs).toBeGreaterThanOrEqual(180); + }); +}); diff --git a/packages/cli/src/__tests__/tui-chat-depth.test.ts b/packages/cli/src/__tests__/tui-chat-depth.test.ts new file mode 100644 index 00000000..019a5df0 --- /dev/null +++ b/packages/cli/src/__tests__/tui-chat-depth.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { resolveChatDepthProfile } from "../tui/chat-depth.js"; + +describe("tui chat depth", () => { + it("maps light/normal/deep to stable chat options", () => { + expect(resolveChatDepthProfile("light")).toEqual({ + depth: "light", + temperature: 0.3, + maxTokens: 160, + label: "light", + }); + expect(resolveChatDepthProfile("normal")).toEqual({ + depth: "normal", + temperature: 0.4, + maxTokens: 240, + label: "normal", + }); + expect(resolveChatDepthProfile("deep")).toEqual({ + depth: "deep", + temperature: 0.45, + maxTokens: 420, + label: "deep", + }); + }); +}); diff --git a/packages/cli/src/__tests__/tui-chat-draft.test.ts b/packages/cli/src/__tests__/tui-chat-draft.test.ts new file mode 100644 index 00000000..b41c04fa --- /dev/null +++ b/packages/cli/src/__tests__/tui-chat-draft.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import type { InteractionSession } from "@actalk/inkos-core"; +import { + appendStreamingAssistantChunk, + createOptimisticUserMessageSession, +} from "../tui/chat-draft.js"; + +function createSession(): InteractionSession { + return { + sessionId: "session-1", + projectRoot: "/tmp/inkos-demo", + activeBookId: "harbor", + automationMode: "semi", + messages: [], + events: [], + }; +} + +describe("tui chat draft", () => { + it("optimistically appends the user message before the model returns", () => { + const next = createOptimisticUserMessageSession(createSession(), "continue current book", 100); + expect(next.messages).toEqual([ + { + role: "user", + content: "continue current book", + timestamp: 100, + }, + ]); + }); + + it("creates and extends a streaming assistant draft message", () => { + const afterUser = createOptimisticUserMessageSession(createSession(), "hi", 100); + const afterFirstChunk = appendStreamingAssistantChunk(afterUser, "hello", 101); + expect(afterFirstChunk.messages.at(-1)).toEqual({ + role: "assistant", + content: "hello", + timestamp: 101, + }); + + const afterSecondChunk = appendStreamingAssistantChunk(afterFirstChunk, " world", 101); + expect(afterSecondChunk.messages.at(-1)?.content).toBe("hello world"); + }); +}); diff --git a/packages/cli/src/__tests__/tui-composer-display.test.ts b/packages/cli/src/__tests__/tui-composer-display.test.ts new file mode 100644 index 00000000..dad6a5b3 --- /dev/null +++ b/packages/cli/src/__tests__/tui-composer-display.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { renderComposerDisplay } from "../tui/composer-display.js"; + +describe("tui composer display", () => { + it("renders placeholder when empty", () => { + expect(renderComposerDisplay("", "Ask InkOS")).toEqual({ + text: "Ask InkOS", + isPlaceholder: true, + }); + }); + + it("renders plain input text with a block cursor when typing", () => { + expect(renderComposerDisplay("continue", "Ask InkOS")).toEqual({ + text: "continue▌", + isPlaceholder: false, + }); + }); +}); diff --git a/packages/cli/src/__tests__/tui-control-flow.test.ts b/packages/cli/src/__tests__/tui-control-flow.test.ts index 156212fd..d4c4e732 100644 --- a/packages/cli/src/__tests__/tui-control-flow.test.ts +++ b/packages/cli/src/__tests__/tui-control-flow.test.ts @@ -12,6 +12,7 @@ describe("tui control flow", () => { projectRoot = await mkdtemp(join(tmpdir(), "inkos-tui-control-")); await mkdir(join(projectRoot, "books", "harbor"), { recursive: true }); await writeFile(join(projectRoot, "books", "harbor", "book.json"), "{}", "utf-8"); + await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "zh" }), "utf-8"); }); afterAll(async () => { @@ -41,6 +42,7 @@ describe("tui control flow", () => { expect(result.session.activeBookId).toBe("harbor"); expect(result.session.currentExecution?.status).toBe("waiting_human"); expect(result.session.pendingDecision?.kind).toBe("review-next-step"); + expect(result.session.pendingDecision?.summary).toContain("等待"); expect(result.session.messages.at(0)?.role).toBe("user"); expect(result.session.messages.at(-1)?.role).toBe("assistant"); }); @@ -136,4 +138,26 @@ describe("tui control flow", () => { expect(result.session.activeBookId).toBe("beta"); expect(result.session.messages.at(-1)?.content).toContain("beta"); }); + + it("formats interactive summaries in English when the project language is en", async () => { + await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "en" }), "utf-8"); + await persistProjectSession(projectRoot, createProjectSession(projectRoot)); + + const tools = { + listBooks: vi.fn(async () => ["harbor", "beta"]), + writeNextChapter: vi.fn(async () => ({ ok: true })), + reviseDraft: vi.fn(async () => ({ ok: true })), + patchChapterText: vi.fn(async () => ({ ok: true })), + renameEntity: vi.fn(async () => ({ ok: true })), + updateCurrentFocus: vi.fn(async () => ({ ok: true })), + updateAuthorIntent: vi.fn(async () => ({ ok: true })), + writeTruthFile: vi.fn(async () => ({ ok: true })), + }; + + const result = await processTuiInput(projectRoot, "/open beta", tools); + + expect(result.session.messages.at(-1)?.content).toBe("Active book: beta"); + + await writeFile(join(projectRoot, "inkos.json"), JSON.stringify({ language: "zh" }), "utf-8"); + }); }); diff --git a/packages/cli/src/__tests__/tui-dashboard.test.tsx b/packages/cli/src/__tests__/tui-dashboard.test.tsx new file mode 100644 index 00000000..b4ffa607 --- /dev/null +++ b/packages/cli/src/__tests__/tui-dashboard.test.tsx @@ -0,0 +1,140 @@ +import React from "react"; +import { render } from "ink-testing-library"; +import { describe, expect, it } from "vitest"; +import type { InteractionSession } from "@actalk/inkos-core"; + +function createSession(): InteractionSession { + return { + sessionId: "session-1", + projectRoot: "/tmp/inkos-demo", + activeBookId: "harbor", + activeChapterNumber: 12, + automationMode: "semi", + currentExecution: { + status: "writing", + bookId: "harbor", + chapterNumber: 12, + stageLabel: "writing chapter", + }, + pendingDecision: { + kind: "review", + bookId: "harbor", + chapterNumber: 12, + summary: "Review chapter 12 before publishing.", + }, + messages: [ + { role: "user", content: "continue current book", timestamp: 1 }, + { role: "assistant", content: "Working on chapter 12.", timestamp: 2 }, + ], + events: [ + { + kind: "task.started", + timestamp: 3, + status: "writing", + bookId: "harbor", + chapterNumber: 12, + detail: "Preparing chapter 12.", + }, + ], + }; +} + +describe("ink dashboard", () => { + it("renders a codex-like single column with compact status and composer", async () => { + const mod = await import("../tui/dashboard.js"); + + const { lastFrame } = render( + , + ); + + const frame = lastFrame() ?? ""; + expect(frame).toContain("inkos-demo"); + expect(frame).toContain("Night Harbor Echo"); + expect(frame).toContain("gpt-5.4 (openai)"); + expect(frame).toContain("writing chapter"); + expect(frame).toContain("Review chapter 12 before publishing."); + expect(frame).toContain("Ask InkOS to write, revise, or explain"); + expect(frame).toContain("│ continue current book"); + expect(frame).not.toContain("You continue current book"); + expect(frame).not.toContain("Header"); + expect(frame).not.toContain("Conversation"); + expect(frame).not.toContain("Status"); + expect(frame).not.toContain("Composer"); + }); + + it("renders the compact status strip directly above the composer", async () => { + const mod = await import("../tui/dashboard.js"); + + const { lastFrame } = render( + , + ); + + const frame = lastFrame() ?? ""; + expect(frame).toContain("Preparing chapter 12."); + expect(frame).toContain("continue"); + expect(frame.indexOf("Preparing chapter 12.")).toBeLessThan(frame.indexOf("› continue")); + expect(frame.indexOf("writing chapter")).toBeLessThan(frame.indexOf("› continue")); + }); + + it("renders a slash autocomplete dropdown under the composer", async () => { + const mod = await import("../tui/dashboard.js"); + + const { lastFrame } = render( + , + ); + + const frame = lastFrame() ?? ""; + expect(frame).toContain("/clear"); + expect(frame).toContain("/config"); + expect(frame.indexOf("/clear")).toBeGreaterThan(frame.indexOf("› /c")); + }); + + it("defaults dashboard chrome to Chinese when locale is zh-CN", async () => { + const mod = await import("../tui/dashboard.js"); + + const { lastFrame } = render( + , + ); + + const frame = lastFrame() ?? ""; + expect(frame).toContain("项目 inkos-demo"); + expect(frame).toContain("作品 夜港回声"); + expect(frame).toContain("深度 标准"); + expect(frame).toContain("告诉 InkOS 要写什么、修改什么,或解释什么"); + expect(frame).toContain("回车发送"); + }); +}); diff --git a/packages/cli/src/__tests__/tui-effects-i18n.test.ts b/packages/cli/src/__tests__/tui-effects-i18n.test.ts new file mode 100644 index 00000000..5df64996 --- /dev/null +++ b/packages/cli/src/__tests__/tui-effects-i18n.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { stripAnsi } from "../tui/ansi.js"; +import { buildStyledHelpSections, formatStyledStatusLines, intentToBadge } from "../tui/effects.js"; + +describe("tui effects i18n", () => { + it("builds localized help sections", () => { + const zhSections = buildStyledHelpSections("zh-CN"); + const enSections = buildStyledHelpSections("en"); + + expect(zhSections[0]?.title).toBe("写作"); + expect(zhSections[1]?.commands[0]?.[1]).toContain("列出"); + expect(enSections[0]?.title).toBe("Writing"); + }); + + it("localizes intent badges and status labels", () => { + expect(stripAnsi(intentToBadge("write_next", "zh-CN"))).toContain("写作"); + + const zhLines = formatStyledStatusLines("zh-CN", { + mode: "semi", + bookId: "harbor", + status: "writing", + events: [{ kind: "task.started", detail: "Preparing chapter 3.", status: "running" }], + }); + + expect(zhLines.join("\n")).toContain("模式"); + expect(zhLines.join("\n")).toContain("半自动"); + expect(zhLines.join("\n")).toContain("作品"); + }); +}); diff --git a/packages/cli/src/__tests__/tui-i18n.test.ts b/packages/cli/src/__tests__/tui-i18n.test.ts new file mode 100644 index 00000000..966a67ec --- /dev/null +++ b/packages/cli/src/__tests__/tui-i18n.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { formatModeLabel, getTuiCopy, normalizeStageLabel, resolveTuiLocale } from "../tui/i18n.js"; + +describe("tui i18n", () => { + it("defaults to Chinese and supports explicit English override", () => { + expect(resolveTuiLocale({})).toBe("zh-CN"); + expect(resolveTuiLocale({ INKOS_TUI_LOCALE: "en" })).toBe("en"); + expect(resolveTuiLocale({ LANG: "en_US.UTF-8" })).toBe("en"); + expect(resolveTuiLocale({}, "en")).toBe("en"); + }); + + it("normalizes common activity labels for Chinese chrome", () => { + const copy = getTuiCopy("zh-CN"); + expect(normalizeStageLabel("writing chapter", copy)).toBe("写作中"); + expect(normalizeStageLabel("thinking ...", copy)).toBe("思考中"); + expect(normalizeStageLabel("idle", copy)).toBe("就绪"); + expect(normalizeStageLabel("waiting_human", copy)).toBe("等待你的决定"); + expect(normalizeStageLabel("completed", copy)).toBe("已完成"); + expect(formatModeLabel("semi", copy)).toBe("半自动"); + expect(formatModeLabel("auto", copy)).toBe("自动"); + }); +}); diff --git a/packages/cli/src/__tests__/tui-input-history.test.ts b/packages/cli/src/__tests__/tui-input-history.test.ts new file mode 100644 index 00000000..785a219a --- /dev/null +++ b/packages/cli/src/__tests__/tui-input-history.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { + buildInputHistory, + moveHistoryCursor, + type InputHistoryState, +} from "../tui/input-history.js"; + +describe("tui input history", () => { + it("builds unique history from user messages only", () => { + const history = buildInputHistory([ + { role: "user", content: "first", timestamp: 1 }, + { role: "assistant", content: "reply", timestamp: 2 }, + { role: "user", content: "second", timestamp: 3 }, + { role: "user", content: "second", timestamp: 4 }, + { role: "user", content: "third", timestamp: 5 }, + ]); + + expect(history).toEqual(["first", "second", "third"]); + }); + + it("moves up through history and restores draft when moving back down", () => { + const entries = ["first", "second", "third"]; + const initial: InputHistoryState = { cursor: null, draft: "" }; + + const firstUp = moveHistoryCursor(entries, initial, "drafting", "up"); + expect(firstUp.value).toBe("third"); + expect(firstUp.state).toEqual({ cursor: 2, draft: "drafting" }); + + const secondUp = moveHistoryCursor(entries, firstUp.state, firstUp.value, "up"); + expect(secondUp.value).toBe("second"); + expect(secondUp.state.cursor).toBe(1); + + const down = moveHistoryCursor(entries, secondUp.state, secondUp.value, "down"); + expect(down.value).toBe("third"); + + const finalDown = moveHistoryCursor(entries, down.state, down.value, "down"); + expect(finalDown.value).toBe("drafting"); + expect(finalDown.state.cursor).toBeNull(); + }); +}); diff --git a/packages/cli/src/__tests__/tui-layout.test.ts b/packages/cli/src/__tests__/tui-layout.test.ts index 60c54f14..976862c6 100644 --- a/packages/cli/src/__tests__/tui-layout.test.ts +++ b/packages/cli/src/__tests__/tui-layout.test.ts @@ -1,26 +1,34 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { renderTuiFrame } from "../tui/app.js"; +import { drawInputHint } from "../tui/effects.js"; + +afterEach(() => { + vi.restoreAllMocks(); +}); describe("tui layout", () => { - it("renders a project-scoped idle workspace frame", () => { + it("renders a codex-like single-column workspace preview", () => { const frame = renderTuiFrame({ + locale: "zh-CN", 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("Messages:"); - expect(frame).toContain("Events:"); - expect(frame).toContain(">"); + expect(frame).toContain("项目 inkos-demo"); + expect(frame).toContain("阶段 就绪"); + expect(frame).toContain("模式 半自动"); + expect(frame).not.toContain("Header"); + expect(frame).not.toContain("Conversation"); + expect(frame).not.toContain("Status"); + expect(frame).not.toContain("Composer"); + expect(frame).toContain("告诉 InkOS"); }); - it("renders an active book and stage when one is bound", () => { + it("keeps the two-line status strip above the composer preview", () => { const frame = renderTuiFrame({ + locale: "en", projectName: "inkos-demo", activeBookTitle: "Night Harbor Echo", automationMode: "auto", @@ -29,10 +37,19 @@ describe("tui layout", () => { events: ["task.completed: Completed write_next for harbor."], }); - expect(frame).toContain("Book: Night Harbor Echo"); - expect(frame).toContain("Mode: auto"); - expect(frame).toContain("Stage: writing"); + expect(frame).toContain("Night Harbor Echo"); + expect(frame).toContain("writing"); expect(frame).toContain("user: continue"); expect(frame).toContain("task.completed: Completed write_next for harbor."); + expect(frame.indexOf("task.completed: Completed write_next for harbor.")).toBeLessThan(frame.indexOf("Ask InkOS")); + expect(frame.indexOf("Mode auto")).toBeLessThan(frame.indexOf("Ask InkOS")); + }); + + it("does not add blank lines before the readline prompt", () => { + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + drawInputHint(); + + expect(logSpy).not.toHaveBeenCalled(); }); }); diff --git a/packages/cli/src/__tests__/tui-local-commands.test.ts b/packages/cli/src/__tests__/tui-local-commands.test.ts new file mode 100644 index 00000000..6a1986ca --- /dev/null +++ b/packages/cli/src/__tests__/tui-local-commands.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; +import { classifyLocalTuiCommand, parseDepthCommand } from "../tui/local-commands.js"; + +describe("tui local commands", () => { + it("recognizes help aliases", () => { + expect(classifyLocalTuiCommand("/help")).toBe("help"); + expect(classifyLocalTuiCommand("help")).toBe("help"); + expect(classifyLocalTuiCommand("帮助")).toBe("help"); + }); + + it("recognizes status aliases", () => { + expect(classifyLocalTuiCommand("/status")).toBe("status"); + expect(classifyLocalTuiCommand("status")).toBe("status"); + expect(classifyLocalTuiCommand("状态")).toBe("status"); + }); + + it("recognizes quit aliases", () => { + expect(classifyLocalTuiCommand("/quit")).toBe("quit"); + expect(classifyLocalTuiCommand("/exit")).toBe("quit"); + expect(classifyLocalTuiCommand("quit")).toBe("quit"); + expect(classifyLocalTuiCommand("exit")).toBe("quit"); + expect(classifyLocalTuiCommand("bye")).toBe("quit"); + expect(classifyLocalTuiCommand("退出")).toBe("quit"); + }); + + it("recognizes config and clear aliases", () => { + expect(classifyLocalTuiCommand("/config")).toBe("config"); + expect(classifyLocalTuiCommand("配置")).toBe("config"); + expect(classifyLocalTuiCommand("/clear")).toBe("clear"); + expect(classifyLocalTuiCommand("清屏")).toBe("clear"); + }); + + it("returns undefined for normal chat input", () => { + expect(classifyLocalTuiCommand("hi")).toBeUndefined(); + expect(classifyLocalTuiCommand("continue current book")).toBeUndefined(); + }); + + it("parses depth commands", () => { + expect(parseDepthCommand("/depth deep")).toBe("deep"); + expect(parseDepthCommand("depth light")).toBe("light"); + expect(parseDepthCommand("/depth normal")).toBe("normal"); + expect(parseDepthCommand("深度 轻量")).toBe("light"); + expect(parseDepthCommand("/深度 标准")).toBe("normal"); + expect(parseDepthCommand("深度 深入")).toBe("deep"); + expect(parseDepthCommand("/depth weird")).toBeUndefined(); + }); +}); diff --git a/packages/cli/src/__tests__/tui-output.test.ts b/packages/cli/src/__tests__/tui-output.test.ts index 4bb25bb6..8e9dc80c 100644 --- a/packages/cli/src/__tests__/tui-output.test.ts +++ b/packages/cli/src/__tests__/tui-output.test.ts @@ -15,6 +15,7 @@ describe("tui output", () => { intent: "switch_mode", status: "completed", mode: "auto", + locale: "en", })).toContain("auto"); }); @@ -25,4 +26,13 @@ describe("tui output", () => { responseText: "Current status: harbor is at repairing chapter 3.", })).toBe("Current status: harbor is at repairing chapter 3."); }); + + it("renders Chinese summaries when locale is zh-CN", () => { + expect(formatTuiResult({ + intent: "select_book", + status: "completed", + bookId: "harbor", + locale: "zh-CN", + })).toBe("当前作品:harbor"); + }); }); diff --git a/packages/cli/src/__tests__/tui-setup-i18n.test.ts b/packages/cli/src/__tests__/tui-setup-i18n.test.ts new file mode 100644 index 00000000..7d0cac41 --- /dev/null +++ b/packages/cli/src/__tests__/tui-setup-i18n.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { buildAutoInitMessages, buildInteractiveSetupCopy } from "../tui/setup.js"; + +describe("tui setup i18n", () => { + it("builds Chinese setup copy by default", () => { + const copy = buildInteractiveSetupCopy("zh-CN"); + expect(copy.title).toBe("模型配置"); + expect(copy.subtitle).toContain("配置模型服务"); + expect(copy.steps.provider).toBe("服务提供方"); + expect(copy.steps.scope).toBe("保存范围"); + expect(copy.scopeChoices.project).toBe("当前目录"); + }); + + it("builds localized auto-init messages", () => { + expect(buildAutoInitMessages("山海", "zh-CN").initializing).toContain("正在初始化项目:山海"); + expect(buildAutoInitMessages("harbor", "en").initialized).toContain("Project initialized"); + }); +}); diff --git a/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts b/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts new file mode 100644 index 00000000..a3ff5c8d --- /dev/null +++ b/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { + applySlashSuggestion, + getSlashSuggestions, + getNextSlashSelection, + SLASH_COMMANDS, +} from "../tui/slash-autocomplete.js"; + +describe("tui slash autocomplete", () => { + it("filters slash commands by prefix", () => { + expect(getSlashSuggestions("/st", SLASH_COMMANDS)).toEqual(["/status"]); + expect(getSlashSuggestions("/c", SLASH_COMMANDS)).toEqual(["/clear", "/config"]); + expect(getSlashSuggestions("/d", SLASH_COMMANDS)).toEqual(["/depth"]); + }); + + it("does not suggest anything for non-slash input", () => { + expect(getSlashSuggestions("status", SLASH_COMMANDS)).toEqual([]); + expect(getSlashSuggestions("", SLASH_COMMANDS)).toEqual([]); + }); + + it("cycles the active suggestion index", () => { + expect(getNextSlashSelection(0, 3, "down")).toBe(1); + expect(getNextSlashSelection(2, 3, "down")).toBe(0); + expect(getNextSlashSelection(0, 3, "up")).toBe(2); + }); + + it("applies the selected suggestion to the composer input", () => { + expect(applySlashSuggestion("/st", ["/status"], 0)).toBe("/status"); + expect(applySlashSuggestion("/c", ["/clear", "/config"], 1)).toBe("/config"); + }); +}); diff --git a/packages/cli/src/tui/activity-state.ts b/packages/cli/src/tui/activity-state.ts new file mode 100644 index 00000000..2e7ca0a8 --- /dev/null +++ b/packages/cli/src/tui/activity-state.ts @@ -0,0 +1,41 @@ +import type { InteractionIntentType } from "@actalk/inkos-core"; +import type { TuiCopy } from "./i18n.js"; + +export interface ActivityState { + readonly label: string; + readonly frames: readonly string[]; + readonly accent: string; + readonly intervalMs: number; +} + +import { WARM_ACCENT } from "./theme.js"; + +const DOTS = ["· ", "·· ", "···", " ··", " ·"] as const; +const WAVE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴"] as const; +const PULSE = ["◜", "◠", "◝", "◞", "◡", "◟"] as const; + +export function describeActivityState( + intent: InteractionIntentType | "unknown", + copy: Pick, +): ActivityState { + switch (intent) { + case "write_next": + case "continue_book": + return { label: copy.activity.writing, frames: WAVE, accent: WARM_ACCENT, intervalMs: 180 }; + case "revise_chapter": + case "rewrite_chapter": + return { label: copy.activity.reviewing, frames: WAVE, accent: WARM_ACCENT, intervalMs: 180 }; + case "update_focus": + case "update_author_intent": + case "edit_truth": + return { label: copy.activity.updating, frames: PULSE, accent: WARM_ACCENT, intervalMs: 220 }; + case "list_books": + case "select_book": + case "switch_mode": + case "explain_status": + return { label: copy.activity.checking, frames: DOTS, accent: WARM_ACCENT, intervalMs: 220 }; + case "chat": + default: + return { label: copy.activity.thinking, frames: DOTS, accent: WARM_ACCENT, intervalMs: 220 }; + } +} diff --git a/packages/cli/src/tui/app.ts b/packages/cli/src/tui/app.ts index a13ba977..eaf40b03 100644 --- a/packages/cli/src/tui/app.ts +++ b/packages/cli/src/tui/app.ts @@ -1,38 +1,51 @@ -/* ── InkOS TUI — persistent REPL with themed animations ── */ - import { basename } from "node:path"; -import readline from "node:readline/promises"; import { appendInteractionMessage, processProjectInteractionInput, - routeNaturalLanguageIntent, type InteractionRuntimeTools, } from "@actalk/inkos-core"; -import { - loadProjectSession, - persistProjectSession, - resolveSessionActiveBook, -} from "./session-store.js"; -import { createInteractionTools } from "./tools.js"; +import { render } from "ink"; +import React from "react"; +import { InkTuiApp } from "./dashboard.js"; +import { formatModeLabel, getTuiCopy, normalizeStageLabel, resolveTuiLocale, type TuiLocale } from "./i18n.js"; import { formatTuiResult } from "./output.js"; -import { ensureProject, interactiveLlmSetup, detectModelInfo } from "./setup.js"; -import { - c, bold, dim, cyan, green, yellow, gray, red, brightCyan, brightWhite, - showCursor, reset, box, -} from "./ansi.js"; -import { - ThemedSpinner, - animateStartup, - formatResultCard, - intentToTheme, - printStyledHelp, - printStyledStatus, - printInputSeparator, - inputPromptPrefix, - drawInputHint, -} from "./effects.js"; +import { loadProjectSession, persistProjectSession } from "./session-store.js"; +import { detectModelInfo, detectProjectLanguage, ensureProject, interactiveLlmSetup } from "./setup.js"; +import { createInteractionTools } from "./tools.js"; +import { animateStartup } from "./effects.js"; -/* ── Version ── */ +export interface TuiFrameState { + readonly locale?: TuiLocale; + readonly projectName: string; + readonly activeBookTitle?: string; + readonly automationMode: string; + readonly status: string; + readonly messages?: ReadonlyArray; + readonly events?: ReadonlyArray; +} + +export function renderTuiFrame(state: TuiFrameState): string { + const locale = state.locale ?? resolveTuiLocale(); + const copy = getTuiCopy(locale); + const lines = [ + `${copy.labels.project} ${state.projectName}`, + `${copy.labels.stage} ${normalizeStageLabel(state.status, copy)}`, + `${copy.labels.mode} ${formatModeLabel(state.automationMode, copy)}`, + `${copy.labels.book} ${state.activeBookTitle ?? copy.labels.none}`, + "", + ...(state.messages?.length + ? state.messages.slice(-6).map((message) => `- ${message}`) + : [`- (${copy.labels.none})`]), + "", + state.events?.length + ? state.events.slice(-1).map((event) => `${copy.labels.recent} ${event}`)[0]! + : `${copy.labels.recent} (${copy.labels.none})`, + "", + copy.composer.placeholder, + "> ", + ]; + return lines.join("\n"); +} async function readVersion(): Promise { try { @@ -47,103 +60,13 @@ async function readVersion(): Promise { } } -/* ── Process input with themed spinner ── */ - -async function processInput( - projectRoot: string, - input: string, - tools: InteractionRuntimeTools, -): Promise<{ summary: string; intent: string } | undefined> { - // Detect intent for themed spinner - const session = await loadProjectSession(projectRoot); - const activeBookId = await resolveSessionActiveBook(projectRoot, session); - const routed = routeNaturalLanguageIntent(input, { activeBookId }); - const themeName = intentToTheme(routed.intent); - - const spinner = new ThemedSpinner(themeName); - const intentLabels: Record = { - write_next: "writing chapter", - revise_chapter: "revising chapter", - rewrite_chapter: "rewriting chapter", - update_focus: "updating focus", - explain_status: "checking status", - explain_failure: "investigating", - pause_book: "pausing book", - list_books: "listing books", - select_book: "selecting book", - switch_mode: "switching mode", - rename_entity: "renaming entity", - patch_chapter_text: "patching text", - edit_truth: "editing truth file", - }; - spinner.start(intentLabels[routed.intent] ?? "processing"); - - try { - const result = await processProjectInteractionInput({ - projectRoot, - input, - tools, - }); - const summary = formatTuiResult({ - intent: result.request.intent, - status: result.session.currentExecution?.status ?? "completed", - bookId: result.session.activeBookId, - mode: result.request.mode, - responseText: result.responseText, - }); - const nextSession = appendInteractionMessage(result.session, { - role: "assistant", - content: summary, - timestamp: Date.now(), - }); - await persistProjectSession(projectRoot, nextSession); - spinner.succeed(c(summary, dim)); - return { summary, intent: result.request.intent }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - spinner.fail(c(msg, red)); - return undefined; - } -} - -/* ── Legacy exports for tests ── */ - -export interface TuiFrameState { - readonly projectName: string; - readonly activeBookTitle?: string; - readonly automationMode: string; - readonly status: string; - readonly messages?: ReadonlyArray; - readonly events?: ReadonlyArray; -} - -export function renderTuiFrame(state: TuiFrameState): string { - const lines = [ - `Project: ${state.projectName}`, - `Book: ${state.activeBookTitle ?? "none"}`, - `Mode: ${state.automationMode}`, - `Stage: ${state.status}`, - "", - "Messages:", - ...(state.messages?.length - ? state.messages.slice(-3).map((message) => `- ${message}`) - : ["- (empty)"]), - "", - "Events:", - ...(state.events?.length - ? state.events.slice(-3).map((event) => `- ${event}`) - : ["- (empty)"]), - "", - "> ", - ]; - return lines.join("\n"); -} - export async function processTuiInput( projectRoot: string, input: string, tools: InteractionRuntimeTools, ) { + const projectLanguage = await detectProjectLanguage(projectRoot); + const locale = resolveTuiLocale(process.env, projectLanguage); const result = await processProjectInteractionInput({ projectRoot, input, @@ -155,6 +78,7 @@ export async function processTuiInput( bookId: result.session.activeBookId, mode: result.request.mode, responseText: result.responseText, + locale, }); const nextSession = appendInteractionMessage(result.session, { role: "assistant", @@ -165,168 +89,77 @@ export async function processTuiInput( return { ...result, session: nextSession }; } -/* ── Main REPL ── */ - export async function launchTui( projectRoot: string, toolsOverride?: InteractionRuntimeTools, ): Promise { - // 1. Auto-setup const { hasLlmConfig } = await ensureProject(projectRoot); + const projectLanguage = await detectProjectLanguage(projectRoot); + const locale = resolveTuiLocale(process.env, projectLanguage); + const copy = getTuiCopy(locale); - // 2. LLM config if missing if (!hasLlmConfig) { console.log(); - console.log(c(" No LLM configuration found.", yellow)); - console.log(c(" Let's set up your API provider first.", dim)); + console.log(copy.notes.noLlmConfig); + console.log(copy.notes.setupProvider); await interactiveLlmSetup(projectRoot); } - // 3. Load session - const session = await loadProjectSession(projectRoot); - const activeBookId = await resolveSessionActiveBook(projectRoot, session); - const version = await readVersion(); - - // 4. Detect model + animated welcome - const modelInfo = await detectModelInfo(projectRoot); - await animateStartup(version, basename(projectRoot), activeBookId, modelInfo ?? undefined); - - // 5. Bail if not interactive if (!process.stdin.isTTY || !process.stdout.isTTY) { return; } - // 6. Build tools + const session = await loadProjectSession(projectRoot); + const modelInfo = await detectModelInfo(projectRoot); + const modelLabel = modelInfo + ? `${modelInfo.model && modelInfo.model !== "unknown" ? modelInfo.model : copy.labels.unknown} (${modelInfo.provider})` + : copy.labels.notConfigured; + const version = await readVersion(); + const chatStreamBridge: { onTextDelta?: (text: string) => void } = {}; + let tools: InteractionRuntimeTools; try { - tools = toolsOverride ?? (await createInteractionTools(projectRoot)); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - console.log(c(` ${c("✗", red, bold)} Failed to initialize: ${msg}`, red)); - console.log(c(" Check your .env or run: inkos config set-global", dim)); - console.log(); + tools = toolsOverride ?? (await createInteractionTools(projectRoot, { + onChatTextDelta: (text) => { + chatStreamBridge.onTextDelta?.(text); + }, + })); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(copy.notes.toolInitFailed(message)); + console.error(copy.notes.toolInitHint); return; } - // 7. Suppress noisy Node warnings (e.g. SQLite experimental) - const origEmitWarning = process.emitWarning; + const originalEmitWarning = process.emitWarning; process.emitWarning = (() => {}) as typeof process.emitWarning; - const origStderrWrite = process.stderr.write.bind(process.stderr); + const originalStderrWrite = process.stderr.write.bind(process.stderr); process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { - const s = typeof chunk === "string" ? chunk : chunk.toString(); - if (s.includes("ExperimentalWarning") || s.includes("--trace-warnings")) { + const text = typeof chunk === "string" ? chunk : chunk.toString(); + if (text.includes("ExperimentalWarning") || text.includes("--trace-warnings")) { return true; } - return (origStderrWrite as Function)(chunk, ...args); + return (originalStderrWrite as Function)(chunk, ...args); }; - // 8. Slash command completer - const SLASH_COMMANDS = [ - "/write", "/rewrite", "/books", "/open", "/status", - "/mode", "/focus", "/config", "/clear", "/help", "/quit", - ]; - const completer = (line: string): [string[], string] => { - if (line.startsWith("/")) { - const hits = SLASH_COMMANDS.filter((cmd) => cmd.startsWith(line)); - return [hits.length > 0 ? hits : SLASH_COMMANDS, line]; - } - return [[], line]; - }; + try { + await animateStartup(version, basename(projectRoot), session.activeBookId, modelInfo); - // 9. REPL loop - const prompt = inputPromptPrefix(); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt, - completer, - }); - - const cleanup = () => { - process.emitWarning = origEmitWarning; - process.stderr.write = origStderrWrite; - process.stdout.write(showCursor); - rl.close(); - }; - - const promptInput = () => { - drawInputHint(); - rl.prompt(); - }; - - process.on("SIGINT", () => { - console.log(); - console.log(c(" ◇ goodbye", dim)); - console.log(); - cleanup(); - process.exit(0); - }); - - promptInput(); - - for await (const line of rl) { - const input = line.trim(); - - if (!input) { - promptInput(); - continue; - } - - console.log(); - - // Built-in TUI commands - if (/^\/quit$/i.test(input) || /^\/exit$/i.test(input) || /^(quit|exit|bye)$/i.test(input)) { - console.log(c(" ◇ goodbye", dim)); - console.log(); - break; - } - - if (/^\/help$/i.test(input) || /^(help|帮助)$/i.test(input)) { - printStyledHelp(); - promptInput(); - continue; - } - - if (/^\/status$/i.test(input) || /^(status|状态)$/i.test(input)) { - try { - const s = await loadProjectSession(projectRoot); - const bId = await resolveSessionActiveBook(projectRoot, s); - printStyledStatus({ - mode: s.automationMode, - bookId: bId, - status: s.currentExecution?.status ?? "idle", - events: s.events, - }); - } catch { - console.log(c(" Could not load session.", dim)); - } - promptInput(); - continue; - } - - if (/^\/config$/i.test(input)) { - await interactiveLlmSetup(projectRoot); - const newModel = await detectModelInfo(projectRoot); - if (newModel) { - console.log(` ${c("◇", cyan)} ${c("Model", gray)} ${c(newModel.model, brightWhite)} ${c(`(${newModel.provider})`, dim)}`); - } - console.log(); - promptInput(); - continue; - } - - if (/^\/clear$/i.test(input)) { - console.clear(); - promptInput(); - continue; - } - - // Delegate to interaction layer with themed animation - await processInput(projectRoot, input, tools); - console.log(); - - promptInput(); + const app = render( + React.createElement(InkTuiApp, { + locale, + projectRoot, + projectName: basename(projectRoot), + modelLabel, + initialSession: session, + tools, + chatStreamBridge, + }), + { exitOnCtrlC: true }, + ); + await app.waitUntilExit(); + } finally { + process.emitWarning = originalEmitWarning; + process.stderr.write = originalStderrWrite; } - - cleanup(); } diff --git a/packages/cli/src/tui/chat-depth.ts b/packages/cli/src/tui/chat-depth.ts new file mode 100644 index 00000000..f4fe12fc --- /dev/null +++ b/packages/cli/src/tui/chat-depth.ts @@ -0,0 +1,20 @@ +export type ChatDepth = "light" | "normal" | "deep"; + +export interface ChatDepthProfile { + readonly depth: ChatDepth; + readonly temperature: number; + readonly maxTokens: number; + readonly label: string; +} + +export function resolveChatDepthProfile(depth: ChatDepth): ChatDepthProfile { + switch (depth) { + case "light": + return { depth, temperature: 0.3, maxTokens: 160, label: "light" }; + case "deep": + return { depth, temperature: 0.45, maxTokens: 420, label: "deep" }; + case "normal": + default: + return { depth: "normal", temperature: 0.4, maxTokens: 240, label: "normal" }; + } +} diff --git a/packages/cli/src/tui/chat-draft.ts b/packages/cli/src/tui/chat-draft.ts new file mode 100644 index 00000000..a94ddb7c --- /dev/null +++ b/packages/cli/src/tui/chat-draft.ts @@ -0,0 +1,42 @@ +import { + appendInteractionMessage, + type InteractionSession, +} from "@actalk/inkos-core"; + +export function createOptimisticUserMessageSession( + session: InteractionSession, + input: string, + timestamp: number = Date.now(), +): InteractionSession { + return appendInteractionMessage(session, { + role: "user", + content: input, + timestamp, + }); +} + +export function appendStreamingAssistantChunk( + session: InteractionSession, + chunk: string, + timestamp: number = Date.now(), +): InteractionSession { + if (!chunk) { + return session; + } + + const lastMessage = session.messages.at(-1); + if (lastMessage?.role === "assistant" && lastMessage.timestamp === timestamp) { + return { + ...session, + messages: session.messages.map((message, index) => index === session.messages.length - 1 + ? { ...message, content: message.content + chunk } + : message), + }; + } + + return appendInteractionMessage(session, { + role: "assistant", + content: chunk, + timestamp, + }); +} diff --git a/packages/cli/src/tui/composer-display.ts b/packages/cli/src/tui/composer-display.ts new file mode 100644 index 00000000..534224f4 --- /dev/null +++ b/packages/cli/src/tui/composer-display.ts @@ -0,0 +1,16 @@ +export function renderComposerDisplay( + inputValue: string, + placeholder: string, +): { readonly text: string; readonly isPlaceholder: boolean } { + if (!inputValue) { + return { + text: placeholder, + isPlaceholder: true, + }; + } + + return { + text: `${inputValue}▌`, + isPlaceholder: false, + }; +} diff --git a/packages/cli/src/tui/dashboard-model.ts b/packages/cli/src/tui/dashboard-model.ts new file mode 100644 index 00000000..67b1ef33 --- /dev/null +++ b/packages/cli/src/tui/dashboard-model.ts @@ -0,0 +1,145 @@ +import type { + ExecutionStatus, + InteractionEvent, + InteractionMessage, + InteractionSession, +} from "@actalk/inkos-core"; +import { formatModeLabel, normalizeStageLabel, type TuiCopy } from "./i18n.js"; + +export interface DashboardMessageRow { + readonly key: string; + readonly label: string; + readonly role: InteractionMessage["role"]; + readonly content: string; +} + +export interface DashboardEventRow { + readonly key: string; + readonly status: ExecutionStatus; + readonly summary: string; +} + +export interface DashboardViewModel { + readonly projectName: string; + readonly activeBookTitle?: string; + readonly modelLabel: string; + readonly modeLabel: string; + readonly executionStatus: ExecutionStatus; + readonly executionLabel: string; + readonly headerLine: string; + readonly statusPrimaryLine: string; + readonly statusSecondaryLine: string; + readonly messageRows: ReadonlyArray; + readonly eventRows: ReadonlyArray; + readonly pendingDecisionSummary?: string; + readonly composerPlaceholder: string; + readonly composerHelper: string; + readonly composerStatus: string; + readonly errorText?: string; +} + +export interface BuildDashboardViewModelParams { + readonly projectName: string; + readonly activeBookTitle?: string; + readonly modelLabel: string; + readonly depthLabel?: string; + readonly copy: TuiCopy; + readonly session: InteractionSession; + readonly isSubmitting: boolean; + readonly lastError?: string; + readonly sinceTimestamp?: number; + readonly terminalRows?: number; +} + +export function buildDashboardViewModel(params: BuildDashboardViewModelParams): DashboardViewModel { + const status = params.session.currentExecution?.status ?? "idle"; + const executionLabel = normalizeStageLabel(params.session.currentExecution?.stageLabel ?? status, params.copy); + const modeLabel = formatModeLabel(params.session.automationMode, params.copy); + const bookLabel = params.activeBookTitle ?? params.session.activeBookId ?? params.copy.labels.none; + const sinceTimestamp = params.sinceTimestamp ?? 0; + const terminalRows = params.terminalRows ?? process.stdout.rows ?? 24; + const conversationLimit = Math.max(4, terminalRows - 10); + + const messageRows = params.session.messages + .filter((message) => message.timestamp >= sinceTimestamp) + .slice(-conversationLimit) + .map((message, index) => ({ + key: `${message.timestamp}-${index}`, + label: roleLabel(message.role, params.copy), + role: message.role, + content: message.content, + })); + + const eventRows = params.session.events + .filter((event) => event.timestamp >= sinceTimestamp) + .slice(-3) + .map((event, index) => ({ + key: `${event.timestamp}-${index}`, + status: event.status, + summary: summarizeEvent(event, params.copy), + })); + + const latestEventSummary = eventRows[eventRows.length - 1]?.summary; + + return { + projectName: params.projectName, + activeBookTitle: params.activeBookTitle ?? params.session.activeBookId, + modelLabel: params.modelLabel, + modeLabel, + executionStatus: status, + executionLabel, + headerLine: `${params.copy.labels.project} ${params.projectName} · ${params.copy.labels.book} ${bookLabel} · ${params.copy.labels.depth} ${params.depthLabel ?? params.copy.depthLabels.normal} · ${params.copy.labels.session} ${params.session.sessionId.slice(-4)} · ${params.copy.labels.messageCount(params.session.messages.length)}`, + statusPrimaryLine: `${params.copy.labels.stage} ${executionLabel} · ${params.copy.labels.mode} ${modeLabel} · ${params.copy.labels.model} ${params.modelLabel}`, + statusSecondaryLine: params.lastError + ? `${params.copy.labels.error} · ${compactInline(params.lastError)}` + : params.isSubmitting && latestEventSummary + ? `${params.copy.labels.recent} · ${latestEventSummary}` + : params.session.pendingDecision?.summary + ? `${params.copy.labels.pending} · ${compactInline(params.session.pendingDecision.summary)}` + : latestEventSummary + ? `${params.copy.labels.recent} · ${latestEventSummary}` + : `${params.copy.labels.ready} · ${bookLabel}`, + messageRows, + eventRows, + pendingDecisionSummary: params.session.pendingDecision?.summary, + composerPlaceholder: params.copy.composer.placeholder, + composerHelper: params.copy.composer.helper, + composerStatus: params.isSubmitting + ? params.copy.composer.submitting + : params.lastError + ? params.copy.composer.failed + : params.copy.composer.ready, + errorText: params.lastError, + }; +} + +function roleLabel(role: InteractionMessage["role"], copy: TuiCopy): string { + switch (role) { + case "user": + return copy.roles.user; + case "assistant": + return copy.roles.assistant; + case "system": + return copy.roles.system; + default: + return role; + } +} + +function summarizeEvent(event: InteractionEvent, copy: TuiCopy): string { + const base = compactInline(event.detail?.trim() || event.kind); + if (event.bookId && event.chapterNumber !== undefined) { + return copy.locale === "zh-CN" + ? `${base}(${event.bookId} 第 ${event.chapterNumber} 章)` + : `${base} (${event.bookId} ch.${event.chapterNumber})`; + } + if (event.bookId) { + return `${base} (${event.bookId})`; + } + return base; +} + +function compactInline(value: string): string { + const singleLine = value.replace(/\s+/g, " ").trim(); + return singleLine.length > 72 ? singleLine.slice(0, 69) + "..." : singleLine; +} diff --git a/packages/cli/src/tui/dashboard.tsx b/packages/cli/src/tui/dashboard.tsx new file mode 100644 index 00000000..706c9a56 --- /dev/null +++ b/packages/cli/src/tui/dashboard.tsx @@ -0,0 +1,447 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + appendInteractionMessage, + processProjectInteractionInput, + routeNaturalLanguageIntent, + type InteractionIntentType, + type InteractionRuntimeTools, + type InteractionSession, +} from "@actalk/inkos-core"; +import { Box, Text, useApp, useInput } from "ink"; +import { describeActivityState } from "./activity-state.js"; +import { resolveChatDepthProfile, type ChatDepth } from "./chat-depth.js"; +import { appendStreamingAssistantChunk, createOptimisticUserMessageSession } from "./chat-draft.js"; +import { renderComposerDisplay } from "./composer-display.js"; +import { formatTuiResult } from "./output.js"; +import { buildDashboardViewModel, type DashboardMessageRow } from "./dashboard-model.js"; +import { buildInputHistory, moveHistoryCursor } from "./input-history.js"; +import { formatModeLabel, getTuiCopy, normalizeStageLabel, type TuiLocale } from "./i18n.js"; +import { loadProjectSession, persistProjectSession, resolveSessionActiveBook } from "./session-store.js"; +import { classifyLocalTuiCommand, parseDepthCommand } from "./local-commands.js"; +import { + applySlashSuggestion, + getNextSlashSelection, + getSlashSuggestions, + SLASH_COMMANDS, +} from "./slash-autocomplete.js"; +import { WARM_ACCENT, WARM_BORDER, WARM_MUTED, WARM_REPLY } from "./theme.js"; + +export interface InkTuiDashboardProps { + readonly locale: TuiLocale; + readonly projectName: string; + readonly activeBookTitle?: string; + readonly modelLabel: string; + readonly depthLabel?: string; + readonly session: InteractionSession; + readonly inputValue: string; + readonly isSubmitting: boolean; + readonly sinceTimestamp?: number; + readonly lastError?: string; + readonly slashSuggestions?: ReadonlyArray; + readonly selectedSlashIndex?: number; + readonly onInputChange?: (value: string) => void; + readonly onSubmit?: (value: string) => void; +} + +export interface InkTuiAppProps { + readonly locale: TuiLocale; + readonly projectRoot: string; + readonly projectName: string; + readonly modelLabel: string; + readonly initialSession: InteractionSession; + readonly tools: InteractionRuntimeTools; + readonly chatStreamBridge?: { + onTextDelta?: (text: string) => void; + getChatRequestOptions?: () => { + readonly temperature?: number; + readonly maxTokens?: number; + }; + }; +} + +export function InkTuiDashboard(props: InkTuiDashboardProps): React.JSX.Element { + const copy = getTuiCopy(props.locale); + const model = buildDashboardViewModel({ + copy, + projectName: props.projectName, + activeBookTitle: props.activeBookTitle, + modelLabel: props.modelLabel, + depthLabel: props.depthLabel, + session: props.session, + isSubmitting: props.isSubmitting, + lastError: props.lastError, + sinceTimestamp: props.sinceTimestamp, + }); + const activeAccent = props.isSubmitting ? WARM_ACCENT : statusColor(model.executionStatus); + const composer = renderComposerDisplay(props.inputValue, model.composerPlaceholder); + + return ( + + {model.headerLine} + + + {model.messageRows.length > 0 ? ( + model.messageRows.map((row) => ) + ) : ( + {copy.composer.emptyConversation} + )} + + + + + + {" "} + {model.statusPrimaryLine} + + + {model.statusSecondaryLine} + + + + + + ›{" "} + + + {composer.text} + + + + {model.composerStatus} • {model.composerHelper} + + {props.slashSuggestions && props.slashSuggestions.length > 0 ? ( + + {props.slashSuggestions.slice(0, 5).map((suggestion, index) => { + const isSelected = index === (props.selectedSlashIndex ?? 0); + return ( + + {isSelected ? "› " : " "} + {suggestion} + + ); + })} + + ) : null} + + + + ); +} + +export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element { + const { exit } = useApp(); + const copy = getTuiCopy(props.locale); + const [session, setSession] = useState(props.initialSession); + const [inputValue, setInputValue] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const [lastError, setLastError] = useState(); + const [sinceTimestamp, setSinceTimestamp] = useState(); + const [selectedSlashIndex, setSelectedSlashIndex] = useState(0); + const [historyState, setHistoryState] = useState<{ cursor: number | null; draft: string }>({ + cursor: null, + draft: "", + }); + const [activityIntent, setActivityIntent] = useState("unknown"); + const [activityFrameIndex, setActivityFrameIndex] = useState(0); + const [chatDepth, setChatDepth] = useState("normal"); + const assistantDraftTimestampRef = useRef(null); + const submitLockRef = useRef(false); + const slashSuggestions = getSlashSuggestions(inputValue, SLASH_COMMANDS); + const inputHistory = buildInputHistory(session.messages); + const activity = describeActivityState(activityIntent, copy); + const chatDepthProfile = resolveChatDepthProfile(chatDepth); + + useEffect(() => { + if (!isSubmitting) { + setActivityFrameIndex(0); + return; + } + + const timer = setInterval(() => { + setActivityFrameIndex((current) => (current + 1) % activity.frames.length); + }, activity.intervalMs); + return () => clearInterval(timer); + }, [activity.frames.length, activity.intervalMs, isSubmitting]); + + if (props.chatStreamBridge) { + props.chatStreamBridge.getChatRequestOptions = () => ({ + temperature: chatDepthProfile.temperature, + maxTokens: chatDepthProfile.maxTokens, + }); + } + + props.chatStreamBridge && (props.chatStreamBridge.onTextDelta = (text: string) => { + const timestamp = assistantDraftTimestampRef.current; + if (timestamp === null) { + return; + } + + setSession((current) => appendStreamingAssistantChunk(current, text, timestamp)); + }); + + useInput((_input, key) => { + if (key.escape) { + exit(); + return; + } + + if (slashSuggestions.length > 0 && key.tab) { + setInputValue(applySlashSuggestion(inputValue, slashSuggestions, selectedSlashIndex)); + setSelectedSlashIndex(0); + return; + } + + if (key.backspace || key.delete) { + setInputValue((current) => current.slice(0, -1)); + setSelectedSlashIndex(0); + return; + } + + if (slashSuggestions.length > 0 && key.downArrow) { + setSelectedSlashIndex((current) => getNextSlashSelection(current, slashSuggestions.length, "down")); + return; + } + + if (slashSuggestions.length > 0 && key.upArrow) { + setSelectedSlashIndex((current) => getNextSlashSelection(current, slashSuggestions.length, "up")); + return; + } + + if (key.downArrow) { + const next = moveHistoryCursor(inputHistory, historyState, inputValue, "down"); + setHistoryState(next.state); + setInputValue(next.value); + return; + } + + if (key.upArrow) { + const next = moveHistoryCursor(inputHistory, historyState, inputValue, "up"); + setHistoryState(next.state); + setInputValue(next.value); + return; + } + + if (key.return) { + void handleSubmit(inputValue); + return; + } + + if (_input && !_input.includes("\r") && !_input.includes("\n") && !key.ctrl && !key.meta) { + setInputValue((current) => current + _input); + setSelectedSlashIndex(0); + } + }); + + const appendSystemNote = (content: string) => { + setLastError(undefined); + setSession((current) => appendInteractionMessage(current, { + role: "system", + content, + timestamp: Date.now(), + })); + }; + + const handleSubmit = async (rawValue: string) => { + const input = rawValue.trim(); + if (!input || isSubmitting || submitLockRef.current) { + return; + } + submitLockRef.current = true; + + try { + const localCommand = classifyLocalTuiCommand(input); + const depthCommand = parseDepthCommand(input); + if (localCommand) { + setInputValue(""); + + if (localCommand === "quit") { + exit(); + return; + } + + if (localCommand === "help") { + appendSystemNote(copy.notes.help); + return; + } + + if (localCommand === "status") { + const stage = normalizeStageLabel( + session.currentExecution?.stageLabel ?? session.currentExecution?.status ?? "idle", + copy, + ); + appendSystemNote(copy.notes.status(stage, formatModeLabel(session.automationMode, copy))); + return; + } + + if (localCommand === "clear") { + setLastError(undefined); + setSinceTimestamp(Date.now()); + return; + } + + if (localCommand === "config") { + appendSystemNote(copy.notes.config); + return; + } + } + + if (depthCommand) { + setInputValue(""); + setChatDepth(depthCommand); + appendSystemNote(copy.notes.depthSet(copy.depthLabels[depthCommand])); + return; + } + + const activeBookId = await resolveSessionActiveBook(props.projectRoot, session); + const routed = routeNaturalLanguageIntent(input, { activeBookId }); + const userTimestamp = Date.now(); + const assistantDraftTimestamp = routed.intent === "chat" ? userTimestamp + 1 : null; + assistantDraftTimestampRef.current = assistantDraftTimestamp; + setActivityIntent(routed.intent); + setIsSubmitting(true); + setLastError(undefined); + setInputValue(""); + setHistoryState({ cursor: null, draft: "" }); + setSession((current) => createOptimisticUserMessageSession(current, input, userTimestamp)); + + const result = await processProjectInteractionInput({ + projectRoot: props.projectRoot, + input, + tools: props.tools, + activeBookId, + }); + const summary = formatTuiResult({ + intent: result.request.intent, + status: result.session.currentExecution?.status ?? "completed", + bookId: result.session.activeBookId, + mode: result.request.mode, + responseText: result.responseText, + locale: props.locale, + }); + const nextSession = appendInteractionMessage(result.session, { + role: "assistant", + content: summary, + timestamp: Date.now(), + }); + await persistProjectSession(props.projectRoot, nextSession); + setSession(nextSession); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const failedSession = await loadProjectSession(props.projectRoot); + setSession(failedSession); + setLastError(message); + } finally { + assistantDraftTimestampRef.current = null; + setIsSubmitting(false); + setActivityIntent("unknown"); + submitLockRef.current = false; + } + }; + + const activitySession = isSubmitting + ? { + ...session, + currentExecution: { + status: "planning" as const, + bookId: session.activeBookId, + chapterNumber: session.activeChapterNumber, + stageLabel: `${activity.label} ${activity.frames[activityFrameIndex] ?? ""}`.trim(), + }, + } + : session; + + return ( + { + setInputValue(value); + setSelectedSlashIndex(0); + setHistoryState((current) => current.cursor === null ? current : { cursor: null, draft: value }); + }} + onSubmit={(value) => { + void handleSubmit(value); + }} + /> + ); +} + +function ConversationRow(props: { readonly row: DashboardMessageRow }): React.JSX.Element { + if (props.row.role === "user") { + return ( + + │ {props.row.content} + + ); + } + + return ( + + + {props.row.role === "assistant" ? props.row.content : `${props.row.label} ${props.row.content}`} + + + ); +} + +function ExecutionBadge(props: { readonly status: string; readonly color?: string }): React.JSX.Element { + return ( + + ● + + ); +} + +function MutedText(props: { readonly children: React.ReactNode }): React.JSX.Element { + return {props.children}; +} + +function messageColor(role: DashboardMessageRow["role"]): string { + switch (role) { + case "user": + return WARM_MUTED; + case "assistant": + return WARM_REPLY; + case "system": + return WARM_ACCENT; + default: + return WARM_REPLY; + } +} + +function statusColor(status: string): string { + switch (status) { + case "completed": + return WARM_REPLY; + case "failed": + return "red"; + case "blocked": + case "waiting_human": + return WARM_ACCENT; + case "writing": + case "repairing": + case "planning": + case "composing": + case "persisting": + return WARM_ACCENT; + default: + return WARM_MUTED; + } +} diff --git a/packages/cli/src/tui/effects.ts b/packages/cli/src/tui/effects.ts index c6a860be..25053070 100644 --- a/packages/cli/src/tui/effects.ts +++ b/packages/cli/src/tui/effects.ts @@ -8,6 +8,7 @@ import { clearLine, hideCursor, showCursor, reset, badge, sleep, stripAnsi, box, } from "./ansi.js"; +import { formatModeLabel, getTuiCopy, normalizeStageLabel, resolveTuiLocale, type TuiLocale } from "./i18n.js"; /* ── Operation themes ── */ @@ -20,6 +21,11 @@ export interface OperationTheme { readonly frames: ReadonlyArray; } +export interface StyledHelpSection { + readonly title: string; + readonly commands: ReadonlyArray; +} + const WAVE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; const PULSE_FRAMES = ["◜", "◠", "◝", "◞", "◡", "◟"]; const DOTS_FRAMES = ["· ", "·· ", "···", " ··", " ·", " "]; @@ -98,7 +104,7 @@ export class ThemedSpinner { } start(label?: string): void { - const displayLabel = label ?? this.theme.label; + const displayLabel = label ?? localizeThemeLabel(this.theme.label, resolveTuiLocale()); this.frame = 0; this.elapsed = 0; process.stdout.write(hideCursor); @@ -170,8 +176,8 @@ export function inputPromptPrefix(): string { } export function drawInputHint(): void { - console.log(); - console.log(); + // Keep the prompt anchored to the next line; extra blank lines confuse + // terminal UIs that render readline prompts inside a framed input block. } export function printInputSeparator(): void { @@ -214,31 +220,6 @@ export async function animateStartup(version: string, projectName: string, bookT } console.log(c(` v${version}`, dim)); } - - // Project info - console.log(); - const bookDisplay = bookTitle - ? c(bookTitle, brightWhite) - : c("no book yet", dim); - const modelDisplay = modelInfo - ? `${c(modelInfo.model, brightWhite)} ${c(`(${modelInfo.provider})`, dim)}` - : c("not configured — type /config to set up", yellow); - - if (isTTY) { - await typewrite(` ${c("◇", cyan)} ${c("Project", gray)} ${c(projectName, brightWhite)}`, 8); - await sleep(60); - await typewrite(` ${c("◇", cyan)} ${c("Book", gray)} ${bookDisplay}`, 8); - await sleep(60); - await typewrite(` ${c("◇", cyan)} ${c("Model", gray)} ${modelDisplay}`, 8); - await sleep(60); - } else { - console.log(` ${c("◇", cyan)} ${c("Project", gray)} ${c(projectName, brightWhite)}`); - console.log(` ${c("◇", cyan)} ${c("Book", gray)} ${bookDisplay}`); - console.log(` ${c("◇", cyan)} ${c("Model", gray)} ${modelDisplay}`); - } - - console.log(); - console.log(c(" /help for commands · Tab to autocomplete", dim)); console.log(); } @@ -280,23 +261,55 @@ export function formatResultCard(content: string, intent?: string): string { return lines.join("\n"); } -function intentToBadge(intent: string): string { - const badges: Record = { - write_next: [" WRITE ", bgMagenta], - revise_chapter: [" REVISE ", bgBlue], - rewrite_chapter: [" REWRITE ", bgBlue], - update_focus: [" FOCUS ", bgCyan], - explain_status: [" STATUS ", bgGray], - explain_failure: [" DEBUG ", bgRed], - pause_book: [" PAUSE ", bgYellow], - list_books: [" BOOKS ", bgGray], - select_book: [" SELECT ", bgGreen], - switch_mode: [" MODE ", bgCyan], - rename_entity: [" RENAME ", bgYellow], - patch_chapter_text: [" PATCH ", bgBlue], - edit_truth: [" TRUTH ", bgGreen], +export function intentToBadge(intent: string, locale: TuiLocale = resolveTuiLocale()): string { + const labels = locale === "en" + ? { + write_next: " WRITE ", + revise_chapter: " REVISE ", + rewrite_chapter: " REWRITE ", + update_focus: " FOCUS ", + explain_status: " STATUS ", + explain_failure: " DEBUG ", + pause_book: " PAUSE ", + list_books: " BOOKS ", + select_book: " SELECT ", + switch_mode: " MODE ", + rename_entity: " RENAME ", + patch_chapter_text: " PATCH ", + edit_truth: " TRUTH ", + } + : { + write_next: " 写作 ", + revise_chapter: " 修订 ", + rewrite_chapter: " 重写 ", + update_focus: " 焦点 ", + explain_status: " 状态 ", + explain_failure: " 调试 ", + pause_book: " 暂停 ", + list_books: " 作品 ", + select_book: " 选择 ", + switch_mode: " 模式 ", + rename_entity: " 改名 ", + patch_chapter_text: " 修补 ", + edit_truth: " 真相 ", + }; + const backgrounds: Record = { + write_next: bgMagenta, + revise_chapter: bgBlue, + rewrite_chapter: bgBlue, + update_focus: bgCyan, + explain_status: bgGray, + explain_failure: bgRed, + pause_book: bgYellow, + list_books: bgGray, + select_book: bgGreen, + switch_mode: bgCyan, + rename_entity: bgYellow, + patch_chapter_text: bgBlue, + edit_truth: bgGreen, }; - const [label, bg] = badges[intent] ?? [` ${intent.toUpperCase()} `, bgGray]; + const label = labels[intent as keyof typeof labels] ?? ` ${intent} `; + const bg = backgrounds[intent] ?? bgGray; return badge(label!, bg!); } @@ -324,38 +337,9 @@ export function intentToTheme(intent: string): string { /* ── Help display ── */ export function printStyledHelp(): void { - const sections = [ - { - title: "Writing", - commands: [ - ["/write", "Write the next chapter (full pipeline)"], - ["/rewrite ", "Rewrite chapter N from scratch"], - ], - }, - { - title: "Navigation", - commands: [ - ["/books", "List all books"], - ["/open ", "Select active book"], - ["/status", "Show current status"], - ], - }, - { - title: "Control", - commands: [ - ["/mode ", "Switch automation mode"], - ["/focus ", "Update current focus"], - ], - }, - { - title: "Session", - commands: [ - ["/clear", "Clear screen"], - ["/help", "Show this help"], - ["/quit", "Exit InkOS TUI"], - ], - }, - ]; + const locale = resolveTuiLocale(); + const sections = buildStyledHelpSections(locale); + const footer = buildHelpFooter(locale); console.log(); for (const section of sections) { @@ -368,8 +352,10 @@ export function printStyledHelp(): void { } console.log(); } - console.log(c(" Natural language also works:", dim)); - console.log(c(' "继续写" "写下一章" "暂停" "把林烬改成张三"', dim, italic)); + console.log(c(` ${footer.title}`, dim)); + for (const example of footer.examples) { + console.log(c(` ${example}`, dim, italic)); + } console.log(); } @@ -381,6 +367,24 @@ export function printStyledStatus(params: { readonly status: string; readonly events: ReadonlyArray<{ readonly kind: string; readonly detail?: string; readonly status: string }>; }): void { + const locale = resolveTuiLocale(); + console.log(); + for (const line of formatStyledStatusLines(locale, params)) { + console.log(line); + } + console.log(); +} + +export function formatStyledStatusLines( + locale: TuiLocale, + params: { + readonly mode: string; + readonly bookId?: string; + readonly status: string; + readonly events: ReadonlyArray<{ readonly kind: string; readonly detail?: string; readonly status: string }>; + }, +): string[] { + const copy = getTuiCopy(locale); const modeColors: Record = { auto: green, semi: yellow, @@ -397,19 +401,23 @@ export function printStyledStatus(params: { waiting_human: brightYellow, }; const statusColor = statusColors[params.status] ?? gray; - - console.log(); - console.log(` ${c("◇", cyan)} ${c("Mode", gray)} ${c(params.mode, modeColor, bold)}`); - console.log(` ${c("◇", cyan)} ${c("Book", gray)} ${params.bookId ? c(params.bookId, brightWhite) : c("none", dim)}`); - console.log(` ${c("◇", cyan)} ${c("Status", gray)} ${c(params.status, statusColor)}`); + const modeLabel = locale === "en" ? "Mode" : "模式"; + const bookLabel = locale === "en" ? "Book" : "作品"; + const statusLabel = locale === "en" ? "Status" : "状态"; + const recentLabel = locale === "en" ? "Recent" : "最近"; + const lines = [ + ` ${c("◇", cyan)} ${c(modeLabel, gray)} ${c(formatModeLabel(params.mode, copy), modeColor, bold)}`, + ` ${c("◇", cyan)} ${c(bookLabel, gray)} ${params.bookId ? c(params.bookId, brightWhite) : c(copy.labels.none, dim)}`, + ` ${c("◇", cyan)} ${c(statusLabel, gray)} ${c(normalizeStageLabel(params.status, copy), statusColor)}`, + ]; if (params.events.length > 0) { - console.log(` ${c("◇", cyan)} ${c("Recent", gray)}`); + lines.push(` ${c("◇", cyan)} ${c(recentLabel, gray)}`); for (const ev of params.events.slice(-3)) { const icon = ev.status === "completed" ? c("✓", green) : c("·", gray); - console.log(` ${icon} ${c(`${ev.kind}`, dim)} ${c(ev.detail ?? "", gray)}`); + lines.push(` ${icon} ${c(`${ev.kind}`, dim)} ${c(ev.detail ?? "", gray)}`); } } - console.log(); + return lines; } /* ── Utilities ── */ @@ -420,3 +428,104 @@ function formatElapsed(ms: number): string { const m = Math.floor(s / 60); return `${m}m${s % 60}s`; } + +export function buildStyledHelpSections(locale: TuiLocale = resolveTuiLocale()): StyledHelpSection[] { + if (locale === "en") { + return [ + { + title: "Writing", + commands: [ + ["/write", "Write the next chapter (full pipeline)"], + ["/rewrite ", "Rewrite chapter N from scratch"], + ], + }, + { + title: "Navigation", + commands: [ + ["/books", "List all books"], + ["/open ", "Select active book"], + ["/status", "Show current status"], + ], + }, + { + title: "Control", + commands: [ + ["/mode ", "Switch automation mode"], + ["/focus ", "Update current focus"], + ], + }, + { + title: "Session", + commands: [ + ["/clear", "Clear screen"], + ["/help", "Show this help"], + ["/quit", "Exit InkOS TUI"], + ], + }, + ]; + } + + return [ + { + title: "写作", + commands: [ + ["/write", "完整跑一轮下一章写作"], + ["/rewrite ", "从头重写第 N 章"], + ], + }, + { + title: "导航", + commands: [ + ["/books", "列出全部作品"], + ["/open ", "切换当前作品"], + ["/status", "查看当前状态"], + ], + }, + { + title: "控制", + commands: [ + ["/mode ", "切换自动化模式"], + ["/focus ", "更新当前焦点"], + ], + }, + { + title: "会话", + commands: [ + ["/clear", "清空当前屏幕"], + ["/help", "显示帮助"], + ["/quit", "退出 InkOS TUI"], + ], + }, + ]; +} + +function buildHelpFooter(locale: TuiLocale): { readonly title: string; readonly examples: readonly string[] } { + if (locale === "en") { + return { + title: "Natural language also works:", + examples: ['"continue writing" "write next chapter" "pause" "rename Lin Jin to Zhang San"'], + }; + } + + return { + title: "自然语言同样可用:", + examples: ['"继续写" "写下一章" "暂停" "把林烬改成张三"'], + }; +} + +function localizeThemeLabel(label: string, locale: TuiLocale): string { + if (locale === "en") { + return label; + } + + const labels: Record = { + thinking: "思考中", + writing: "写作中", + auditing: "审计中", + revising: "修订中", + planning: "规划中", + composing: "生成中", + loading: "加载中", + }; + return labels[label] ?? label; +} diff --git a/packages/cli/src/tui/i18n.ts b/packages/cli/src/tui/i18n.ts new file mode 100644 index 00000000..067f5781 --- /dev/null +++ b/packages/cli/src/tui/i18n.ts @@ -0,0 +1,295 @@ +import type { ChatDepth } from "./chat-depth.js"; + +export type TuiLocale = "zh-CN" | "en"; + +export interface TuiCopy { + readonly locale: TuiLocale; + readonly labels: { + readonly project: string; + readonly book: string; + readonly depth: string; + readonly session: string; + readonly messageCount: (count: number) => string; + readonly stage: string; + readonly mode: string; + readonly model: string; + readonly error: string; + readonly recent: string; + readonly pending: string; + readonly ready: string; + readonly none: string; + readonly notConfigured: string; + readonly unknown: string; + }; + readonly modeLabels: Record; + readonly composer: { + readonly placeholder: string; + readonly emptyConversation: string; + readonly helper: string; + readonly submitting: string; + readonly failed: string; + readonly ready: string; + }; + readonly notes: { + readonly help: string; + readonly status: (stage: string, mode: string) => string; + readonly config: string; + readonly depthSet: (depthLabel: string) => string; + readonly noLlmConfig: string; + readonly setupProvider: string; + readonly toolInitFailed: (message: string) => string; + readonly toolInitHint: string; + }; + readonly roles: { + readonly user: string; + readonly assistant: string; + readonly system: string; + }; + readonly activity: Record<"thinking" | "checking" | "writing" | "reviewing" | "updating", string>; + readonly depthLabels: Record; + readonly results: { + readonly modeSwitched: (mode: string) => string; + readonly booksListed: string; + readonly activeBook: (bookId: string) => string; + readonly completed: (intent: string) => string; + readonly intentLabels: Partial>; + }; +} + +const ZH_CN: TuiCopy = { + locale: "zh-CN", + labels: { + project: "项目", + book: "作品", + depth: "深度", + session: "会话", + messageCount: (count) => `${count} 条消息`, + stage: "阶段", + mode: "模式", + model: "模型", + error: "错误", + recent: "最近", + pending: "待确认", + ready: "就绪", + none: "无", + notConfigured: "未配置", + unknown: "未知", + }, + modeLabels: { + auto: "自动", + semi: "半自动", + manual: "手动", + }, + composer: { + placeholder: "告诉 InkOS 要写什么、修改什么,或解释什么…", + emptyConversation: "先告诉 InkOS 你要做什么。", + helper: "回车发送 • /help • /status • /clear • /config • /depth • /quit", + submitting: "处理中…", + failed: "上次请求失败", + ready: "就绪", + }, + notes: { + help: "可用命令:/help、/status、/clear、/config、/depth、/quit。也支持直接输入自然语言。", + status: (stage, mode) => `当前状态:${stage}(${mode})。`, + config: "当前 Ink 仪表盘里还不支持交互式 /config。请使用 inkos config set-global。", + depthSet: (depthLabel) => `思考深度已切换为 ${depthLabel}。`, + noLlmConfig: "未发现 LLM 配置。", + setupProvider: "先配置 API 提供方。", + toolInitFailed: (message) => `初始化 TUI 工具失败:${message}`, + toolInitHint: "请检查 .env,或运行:inkos config set-global", + }, + roles: { + user: "你", + assistant: "InkOS", + system: "系统", + }, + activity: { + thinking: "思考中", + checking: "检查中", + writing: "写作中", + reviewing: "审阅中", + updating: "更新中", + }, + depthLabels: { + light: "轻量", + normal: "标准", + deep: "深入", + }, + results: { + modeSwitched: (mode) => `已切换到 ${mode} 模式。`, + booksListed: "已列出作品。", + activeBook: (bookId) => `当前作品:${bookId}`, + completed: (intent) => `已完成 ${intent}`, + intentLabels: { + write_next: "已写完下一章", + revise_chapter: "已修订章节", + rewrite_chapter: "已重写章节", + update_focus: "已更新焦点", + explain_status: "状态说明", + explain_failure: "失败说明", + pause_book: "已暂停作品", + rename_entity: "已重命名实体", + patch_chapter_text: "已修补正文", + edit_truth: "已更新真相文件", + }, + }, +}; + +const EN: TuiCopy = { + locale: "en", + labels: { + project: "Project", + book: "Book", + depth: "Depth", + session: "Session", + messageCount: (count) => `${count} msgs`, + stage: "Stage", + mode: "Mode", + model: "Model", + error: "Error", + recent: "Recent", + pending: "Pending", + ready: "Ready", + none: "none", + notConfigured: "not configured", + unknown: "unknown", + }, + modeLabels: { + auto: "auto", + semi: "semi", + manual: "manual", + }, + composer: { + placeholder: "Ask InkOS to write, revise, or explain…", + emptyConversation: "Start by asking InkOS what to do.", + helper: "Enter to send • /help • /status • /clear • /config • /depth • /quit", + submitting: "Submitting…", + failed: "Last request failed", + ready: "Ready", + }, + notes: { + help: "Commands: /help, /status, /clear, /config, /depth, /quit. Natural language still works.", + status: (stage, mode) => `Status: ${stage} (${mode}).`, + config: "Interactive /config is not available inside the Ink dashboard yet. Use inkos config set-global.", + depthSet: (depthLabel) => `Thinking depth set to ${depthLabel}.`, + noLlmConfig: "No LLM configuration found.", + setupProvider: "Let's set up your API provider first.", + toolInitFailed: (message) => `Failed to initialize TUI tools: ${message}`, + toolInitHint: "Check your .env or run: inkos config set-global", + }, + roles: { + user: "You", + assistant: "InkOS", + system: "System", + }, + activity: { + thinking: "thinking", + checking: "checking", + writing: "writing", + reviewing: "reviewing", + updating: "updating", + }, + depthLabels: { + light: "light", + normal: "normal", + deep: "deep", + }, + results: { + modeSwitched: (mode) => `Mode switched to ${mode}.`, + booksListed: "Books listed.", + activeBook: (bookId) => `Active book: ${bookId}`, + completed: (intent) => `Completed ${intent}`, + intentLabels: { + write_next: "Chapter written", + revise_chapter: "Chapter revised", + rewrite_chapter: "Chapter rewritten", + update_focus: "Focus updated", + explain_status: "Status", + explain_failure: "Explanation", + pause_book: "Book paused", + rename_entity: "Entity renamed", + patch_chapter_text: "Text patched", + edit_truth: "Truth file updated", + }, + }, +}; + +export function resolveTuiLocale( + env: NodeJS.ProcessEnv = process.env, + preferredLanguage?: string, +): TuiLocale { + const requested = normalizeLocale(env.INKOS_TUI_LOCALE ?? env.INKOS_LOCALE); + if (requested) { + return requested; + } + + const preferred = normalizeLocale(preferredLanguage); + if (preferred) { + return preferred; + } + + const detected = normalizeLocale(env.LC_ALL ?? env.LC_MESSAGES ?? env.LANG); + return detected ?? "zh-CN"; +} + +export function getTuiCopy(locale: TuiLocale): TuiCopy { + return locale === "en" ? EN : ZH_CN; +} + +export function normalizeStageLabel(label: string, copy: TuiCopy): string { + const normalized = label.trim().toLowerCase(); + if (!normalized) { + return label; + } + + const replacements: Array<[RegExp, string]> = [ + [/^thinking\b/i, copy.activity.thinking], + [/^checking\b/i, copy.activity.checking], + [/^writing\b/i, copy.activity.writing], + [/^reviewing\b/i, copy.activity.reviewing], + [/^updating\b/i, copy.activity.updating], + [/^completed\b/i, copy.locale === "en" ? "completed" : "已完成"], + [/^failed\b/i, copy.locale === "en" ? "failed" : "失败"], + [/^blocked\b/i, copy.locale === "en" ? "blocked" : "已阻塞"], + [/^waiting_human\b/i, copy.locale === "en" ? "waiting for your decision" : "等待你的决定"], + [/^paused by user\b/i, copy.locale === "en" ? "paused by user" : "已由用户暂停"], + [/^ready to continue\b/i, copy.locale === "en" ? "ready to continue" : "可继续执行"], + ]; + + for (const [pattern, value] of replacements) { + if (pattern.test(label)) { + return copy.locale === "en" ? label : value; + } + } + + if (normalized === "idle") { + return copy.labels.ready; + } + + return label; +} + +export function formatModeLabel(mode: string, copy: TuiCopy): string { + return copy.modeLabels[mode] ?? mode; +} + +function normalizeLocale(value: string | undefined): TuiLocale | undefined { + if (!value) { + return undefined; + } + + const normalized = value.trim().toLowerCase(); + if (!normalized || normalized === "auto") { + return undefined; + } + + if (normalized.startsWith("zh")) { + return "zh-CN"; + } + + if (normalized.startsWith("en")) { + return "en"; + } + + return undefined; +} diff --git a/packages/cli/src/tui/input-history.ts b/packages/cli/src/tui/input-history.ts new file mode 100644 index 00000000..471bc45c --- /dev/null +++ b/packages/cli/src/tui/input-history.ts @@ -0,0 +1,69 @@ +import type { InteractionMessage } from "@actalk/inkos-core"; + +export interface InputHistoryState { + readonly cursor: number | null; + readonly draft: string; +} + +export type InputHistoryDirection = "up" | "down"; + +export function buildInputHistory(messages: ReadonlyArray): string[] { + const result: string[] = []; + + for (const message of messages) { + if (message.role !== "user") { + continue; + } + + const value = message.content.trim(); + if (!value || result[result.length - 1] === value) { + continue; + } + result.push(value); + } + + return result; +} + +export function moveHistoryCursor( + entries: ReadonlyArray, + state: InputHistoryState, + currentValue: string, + direction: InputHistoryDirection, +): { state: InputHistoryState; value: string } { + if (entries.length === 0) { + return { state, value: currentValue }; + } + + if (direction === "up") { + if (state.cursor === null) { + return { + state: { cursor: entries.length - 1, draft: currentValue }, + value: entries[entries.length - 1]!, + }; + } + + const nextCursor = Math.max(0, state.cursor - 1); + return { + state: { ...state, cursor: nextCursor }, + value: entries[nextCursor]!, + }; + } + + if (state.cursor === null) { + return { state, value: currentValue }; + } + + if (state.cursor >= entries.length - 1) { + return { + state: { cursor: null, draft: state.draft }, + value: state.draft, + }; + } + + const nextCursor = state.cursor + 1; + return { + state: { ...state, cursor: nextCursor }, + value: entries[nextCursor]!, + }; +} diff --git a/packages/cli/src/tui/local-commands.ts b/packages/cli/src/tui/local-commands.ts new file mode 100644 index 00000000..1d89bca4 --- /dev/null +++ b/packages/cli/src/tui/local-commands.ts @@ -0,0 +1,55 @@ +import type { ChatDepth } from "./chat-depth.js"; + +export type LocalTuiCommand = "help" | "status" | "quit" | "clear" | "config"; + +export function classifyLocalTuiCommand(input: string): LocalTuiCommand | undefined { + const value = input.trim(); + + if (/^\/help$/i.test(value) || /^(help|帮助)$/i.test(value)) { + return "help"; + } + + if (/^\/status$/i.test(value) || /^(status|状态)$/i.test(value)) { + return "status"; + } + + if (/^\/clear$/i.test(value) || /^清屏$/i.test(value)) { + return "clear"; + } + + if (/^\/config$/i.test(value) || /^(config|配置)$/i.test(value)) { + return "config"; + } + + if (/^\/quit$/i.test(value) || /^\/exit$/i.test(value) || /^(quit|exit|bye|退出)$/i.test(value)) { + return "quit"; + } + + return undefined; +} + +export function parseDepthCommand(input: string): ChatDepth | undefined { + const value = input.trim().toLowerCase(); + const englishMatch = value.match(/^\/?depth\s+(light|normal|deep)$/); + if (englishMatch?.[1]) { + return englishMatch[1] as ChatDepth; + } + + const chineseMatch = input.trim().match(/^\/?深度\s+(浅|轻量|标准|普通|深|深入)$/); + if (!chineseMatch?.[1]) { + return undefined; + } + + switch (chineseMatch[1]) { + case "浅": + case "轻量": + return "light"; + case "深": + case "深入": + return "deep"; + case "标准": + case "普通": + default: + return "normal"; + } +} diff --git a/packages/cli/src/tui/output.ts b/packages/cli/src/tui/output.ts index b4a8f946..b33f43f8 100644 --- a/packages/cli/src/tui/output.ts +++ b/packages/cli/src/tui/output.ts @@ -1,4 +1,5 @@ import type { AutomationMode, ExecutionStatus, InteractionIntentType } from "@actalk/inkos-core"; +import { formatModeLabel, getTuiCopy, resolveTuiLocale, type TuiLocale } from "./i18n.js"; export function formatTuiResult(params: { readonly intent: InteractionIntentType; @@ -6,42 +7,33 @@ export function formatTuiResult(params: { readonly bookId?: string; readonly mode?: AutomationMode; readonly responseText?: string; + readonly locale?: TuiLocale; }): string { + const copy = getTuiCopy(params.locale ?? resolveTuiLocale()); + if (params.responseText?.trim()) { return params.responseText.trim(); } if (params.intent === "switch_mode" && params.mode) { - return `Mode switched to ${params.mode}.`; + return copy.results.modeSwitched(formatModeLabel(params.mode, copy)); } if (params.intent === "list_books") { - return "Books listed."; + return copy.results.booksListed; } if (params.intent === "select_book" && params.bookId) { - return `Active book: ${params.bookId}`; + return copy.results.activeBook(params.bookId); } if (params.bookId) { - return `${intentLabel(params.intent)} — ${params.bookId}`; + return `${intentLabel(params.intent, copy)} — ${params.bookId}`; } - return intentLabel(params.intent); + return intentLabel(params.intent, copy); } -function intentLabel(intent: InteractionIntentType): string { - const labels: Partial> = { - write_next: "Chapter written", - revise_chapter: "Chapter revised", - rewrite_chapter: "Chapter rewritten", - update_focus: "Focus updated", - explain_status: "Status", - explain_failure: "Explanation", - pause_book: "Book paused", - rename_entity: "Entity renamed", - patch_chapter_text: "Text patched", - edit_truth: "Truth file updated", - }; - return labels[intent] ?? `Completed ${intent}`; +function intentLabel(intent: InteractionIntentType, copy: ReturnType): string { + return copy.results.intentLabels[intent] ?? copy.results.completed(intent); } diff --git a/packages/cli/src/tui/setup.ts b/packages/cli/src/tui/setup.ts index 06bdf6b2..82650c26 100644 --- a/packages/cli/src/tui/setup.ts +++ b/packages/cli/src/tui/setup.ts @@ -8,6 +8,7 @@ import { cyan, green, yellow, gray, red, brightCyan, brightGreen, brightWhite, } from "./ansi.js"; +import { resolveTuiLocale, type TuiLocale } from "./i18n.js"; import { GLOBAL_ENV_PATH } from "../utils.js"; const PROVIDERS = ["openai", "anthropic", "custom"] as const; @@ -17,6 +18,114 @@ interface SetupResult { readonly hasLlmConfig: boolean; } +export interface InteractiveSetupCopy { + readonly title: string; + readonly subtitle: string; + readonly steps: { + readonly provider: string; + readonly baseUrl: string; + readonly apiKey: string; + readonly model: string; + readonly scope: string; + }; + readonly hints: { + readonly provider: string; + readonly baseUrl: string; + readonly model: string; + readonly scope: string; + }; + readonly defaults: { + readonly provider: string; + readonly baseUrl: string; + readonly scope: string; + }; + readonly scopeChoices: { + readonly global: string; + readonly project: string; + }; + readonly savedTo: string; +} + +export function buildInteractiveSetupCopy(locale: TuiLocale): InteractiveSetupCopy { + if (locale === "en") { + return { + title: "LLM Setup", + subtitle: "Configure your model provider to start writing.", + steps: { + provider: "Provider", + baseUrl: "Base URL", + apiKey: "API Key", + model: "Model", + scope: "Save scope", + }, + hints: { + provider: "openai / anthropic / custom (OpenAI-compatible proxy)", + baseUrl: "Your API endpoint", + model: "e.g. gpt-4o, claude-sonnet-4-20250514, deepseek-chat", + scope: "global = all projects, project = this directory only", + }, + defaults: { + provider: "openai", + baseUrl: "(default)", + scope: "[global]", + }, + scopeChoices: { + global: "all projects", + project: "this directory", + }, + savedTo: "Saved to", + }; + } + + return { + title: "模型配置", + subtitle: "配置模型服务后即可开始使用。", + steps: { + provider: "服务提供方", + baseUrl: "接口地址", + apiKey: "API 密钥", + model: "模型", + scope: "保存范围", + }, + hints: { + provider: "openai / anthropic / custom(兼容 OpenAI 的代理)", + baseUrl: "你的 API 入口地址", + model: "例如 gpt-5.4、claude-sonnet-4-20250514、deepseek-chat", + scope: "global = 所有项目,project = 仅当前目录", + }, + defaults: { + provider: "openai", + baseUrl: "(默认)", + scope: "[global]", + }, + scopeChoices: { + global: "所有项目", + project: "当前目录", + }, + savedTo: "已保存到", + }; +} + +export function buildAutoInitMessages(projectName: string, locale: TuiLocale): { + readonly initializing: string; + readonly initialized: string; + readonly envTemplateHeader: string; +} { + if (locale === "en") { + return { + initializing: `Initializing project in ${projectName}/ ...`, + initialized: "Project initialized", + envTemplateHeader: "# LLM Configuration — run inkos tui to configure interactively", + }; + } + + return { + initializing: `正在初始化项目:${projectName}/ ...`, + initialized: "项目已初始化", + envTemplateHeader: "# LLM 配置 —— 运行 inkos tui 进行交互式配置", + }; +} + export async function ensureProject(cwd: string): Promise { const configPath = join(cwd, "inkos.json"); const hasConfig = await fileExists(configPath); @@ -32,6 +141,9 @@ export async function ensureProject(cwd: string): Promise { export async function interactiveLlmSetup( projectRoot: string, ): Promise { + const projectLanguage = await detectProjectLanguage(projectRoot); + const locale = resolveTuiLocale(process.env, projectLanguage); + const copy = buildInteractiveSetupCopy(locale); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -39,29 +151,29 @@ export async function interactiveLlmSetup( try { console.log(); - console.log(` ${c("◈", brightCyan)} ${c("LLM Setup", bold, brightWhite)}`); - console.log(c(" Configure your model provider to start writing.", dim)); + console.log(` ${c("◈", brightCyan)} ${c(copy.title, bold, brightWhite)}`); + console.log(c(` ${copy.subtitle}`, dim)); console.log(); // Provider - console.log(` ${c("1", cyan)} ${c("Provider", gray)}`); - console.log(c(" openai / anthropic / custom (OpenAI-compatible proxy)", dim)); + console.log(` ${c("1", cyan)} ${c(copy.steps.provider, gray)}`); + console.log(c(` ${copy.hints.provider}`, dim)); const providerInput = await rl.question(` ${c("❯", cyan)} `); const provider = PROVIDERS.includes(providerInput.trim() as typeof PROVIDERS[number]) ? providerInput.trim() - : "openai"; + : copy.defaults.provider; console.log(` ${c("✓", brightGreen)} ${provider}`); console.log(); // Base URL - console.log(` ${c("2", cyan)} ${c("Base URL", gray)}`); - console.log(c(" Your API endpoint", dim)); + console.log(` ${c("2", cyan)} ${c(copy.steps.baseUrl, gray)}`); + console.log(c(` ${copy.hints.baseUrl}`, dim)); const baseUrl = await rl.question(` ${c("❯", cyan)} `); - console.log(` ${c("✓", brightGreen)} ${baseUrl.trim() || "(default)"}`); + console.log(` ${c("✓", brightGreen)} ${baseUrl.trim() || copy.defaults.baseUrl}`); console.log(); // API Key - console.log(` ${c("3", cyan)} ${c("API Key", gray)}`); + console.log(` ${c("3", cyan)} ${c(copy.steps.apiKey, gray)}`); const apiKey = await rl.question(` ${c("❯", cyan)} `); const maskedKey = apiKey.trim().length > 8 ? apiKey.trim().slice(0, 4) + "···" + apiKey.trim().slice(-4) @@ -70,16 +182,16 @@ export async function interactiveLlmSetup( console.log(); // Model - console.log(` ${c("4", cyan)} ${c("Model", gray)}`); - console.log(c(" e.g. gpt-4o, claude-sonnet-4-20250514, deepseek-chat", dim)); + console.log(` ${c("4", cyan)} ${c(copy.steps.model, gray)}`); + console.log(c(` ${copy.hints.model}`, dim)); const model = await rl.question(` ${c("❯", cyan)} `); console.log(` ${c("✓", brightGreen)} ${model.trim()}`); console.log(); // Scope - console.log(` ${c("5", cyan)} ${c("Save scope", gray)}`); - console.log(c(" global = all projects, project = this directory only", dim)); - const scope = await rl.question(` ${c("❯", cyan)} ${c("[global]", dim)} `); + console.log(` ${c("5", cyan)} ${c(copy.steps.scope, gray)}`); + console.log(c(` ${copy.hints.scope}`, dim)); + const scope = await rl.question(` ${c("❯", cyan)} ${c(copy.defaults.scope, dim)} `); const useGlobal = scope.trim().toLowerCase() !== "project"; const envContent = [ @@ -94,11 +206,11 @@ export async function interactiveLlmSetup( await mkdir(globalDir, { recursive: true }); await writeFile(GLOBAL_ENV_PATH, envContent + "\n", "utf-8"); console.log(); - console.log(` ${c("✓", brightGreen, bold)} ${c("Saved to", dim)} ${c(GLOBAL_ENV_PATH, gray)}`); + console.log(` ${c("✓", brightGreen, bold)} ${c(copy.savedTo, dim)} ${c(GLOBAL_ENV_PATH, gray)}`); } else { await writeFile(join(projectRoot, ".env"), envContent + "\n", "utf-8"); console.log(); - console.log(` ${c("✓", brightGreen, bold)} ${c("Saved to", dim)} ${c(".env", gray)}`); + console.log(` ${c("✓", brightGreen, bold)} ${c(copy.savedTo, dim)} ${c(".env", gray)}`); } console.log(); } finally { @@ -108,8 +220,10 @@ export async function interactiveLlmSetup( async function autoInit(cwd: string): Promise { const projectName = basename(cwd); + const locale = resolveTuiLocale(); + const messages = buildAutoInitMessages(projectName, locale); console.log(); - console.log(` ${c("◌", cyan)} ${c(`Initializing project in ${projectName}/ ...`, dim)}`); + console.log(` ${c("◌", cyan)} ${c(messages.initializing, dim)}`); await mkdir(join(cwd, "books"), { recursive: true }); await mkdir(join(cwd, "radar"), { recursive: true }); @@ -144,7 +258,7 @@ async function autoInit(cwd: string): Promise { await writeFile( join(cwd, ".env"), [ - "# LLM Configuration — run inkos tui to configure interactively", + messages.envTemplateHeader, "INKOS_LLM_PROVIDER=openai", "INKOS_LLM_BASE_URL=", "INKOS_LLM_API_KEY=", @@ -160,7 +274,7 @@ async function autoInit(cwd: string): Promise { "utf-8", ); - console.log(` ${c("✓", brightGreen, bold)} ${c("Project initialized", dim)}`); + console.log(` ${c("✓", brightGreen, bold)} ${c(messages.initialized, dim)}`); } async function hasLlmConfig(projectRoot: string): Promise { @@ -198,6 +312,16 @@ export async function detectModelInfo(projectRoot: string): Promise { + try { + const raw = await readFile(join(projectRoot, "inkos.json"), "utf-8"); + const parsed = JSON.parse(raw) as { language?: string }; + return parsed.language; + } catch { + return undefined; + } +} + async function parseEnvModel(envPath: string): Promise { try { const content = await readFile(envPath, "utf-8"); diff --git a/packages/cli/src/tui/slash-autocomplete.ts b/packages/cli/src/tui/slash-autocomplete.ts new file mode 100644 index 00000000..8a87a5ff --- /dev/null +++ b/packages/cli/src/tui/slash-autocomplete.ts @@ -0,0 +1,44 @@ +export const SLASH_COMMANDS = [ + "/help", + "/status", + "/clear", + "/config", + "/depth", + "/quit", + "/exit", +] as const; + +export type SlashNavigationDirection = "up" | "down"; + +export function getSlashSuggestions(input: string, commands: readonly string[]): string[] { + const value = input.trim(); + if (!value.startsWith("/")) { + return []; + } + + return commands.filter((command) => command.startsWith(value)); +} + +export function getNextSlashSelection( + currentIndex: number, + suggestionCount: number, + direction: SlashNavigationDirection, +): number { + if (suggestionCount <= 0) { + return 0; + } + + if (direction === "down") { + return (currentIndex + 1) % suggestionCount; + } + + return (currentIndex - 1 + suggestionCount) % suggestionCount; +} + +export function applySlashSuggestion( + _input: string, + suggestions: readonly string[], + selectedIndex: number, +): string { + return suggestions[selectedIndex] ?? ""; +} diff --git a/packages/cli/src/tui/theme.ts b/packages/cli/src/tui/theme.ts new file mode 100644 index 00000000..d03b524f --- /dev/null +++ b/packages/cli/src/tui/theme.ts @@ -0,0 +1,4 @@ +export const WARM_ACCENT = "#c88a56"; +export const WARM_MUTED = "#8f8374"; +export const WARM_REPLY = "#f0e6d8"; +export const WARM_BORDER = "#6b6156"; diff --git a/packages/cli/src/tui/tools.ts b/packages/cli/src/tui/tools.ts index daa7c3a7..4a7e5111 100644 --- a/packages/cli/src/tui/tools.ts +++ b/packages/cli/src/tui/tools.ts @@ -8,12 +8,20 @@ import { buildPipelineConfig, loadConfig } from "../utils.js"; type CliPipelineLike = Pick; type CliStateLike = Pick; +type CliInteractionToolHooks = { + readonly onChatTextDelta?: (text: string) => void; + readonly getChatRequestOptions?: () => { + readonly temperature?: number; + readonly maxTokens?: number; + }; +}; export function createCliInteractionToolsFromDeps( pipeline: CliPipelineLike, state: CliStateLike, + hooks?: CliInteractionToolHooks, ): InteractionRuntimeTools { - return createInteractionToolsFromDeps(pipeline, state); + return createInteractionToolsFromDeps(pipeline, state, hooks); } // Backward-compatible export for the current CLI tests during the extraction phase. @@ -21,15 +29,19 @@ export function createInteractionToolsFromDepsCompat( _projectRoot: string, pipeline: CliPipelineLike, state: CliStateLike, + hooks?: CliInteractionToolHooks, ): InteractionRuntimeTools { - return createInteractionToolsFromDeps(pipeline, state); + return createInteractionToolsFromDeps(pipeline, state, hooks); } export { createInteractionToolsFromDepsCompat as createInteractionToolsFromDeps }; -export async function createInteractionTools(projectRoot: string): Promise { +export async function createInteractionTools( + projectRoot: string, + hooks?: CliInteractionToolHooks, +): Promise { const config = await loadConfig(); const pipeline = new PipelineRunner(buildPipelineConfig(config, projectRoot)); const state = new StateManager(projectRoot); - return createInteractionToolsFromDeps(pipeline, state); + return createInteractionToolsFromDeps(pipeline, state, hooks); } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index a086b149..1aca08d9 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "jsx": "react-jsx", "outDir": "dist", "rootDir": "src" }, diff --git a/packages/core/src/interaction/project-control.ts b/packages/core/src/interaction/project-control.ts index ba88d9dc..783fc0fb 100644 --- a/packages/core/src/interaction/project-control.ts +++ b/packages/core/src/interaction/project-control.ts @@ -1,3 +1,5 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; import { appendInteractionEvent, appendInteractionMessage } from "./session.js"; import { routeNaturalLanguageIntent } from "./nl-router.js"; import type { InteractionRequest } from "./intents.js"; @@ -15,9 +17,11 @@ async function processProjectInteractionRequestInternal(params: { readonly tools: InteractionRuntimeTools; readonly activeBookId?: string; }) { + const requestLanguage = await detectProjectInteractionLanguage(params.projectRoot); + const localizedRequest = attachRequestLanguage(params.request, requestLanguage); const session = await loadProjectSession(params.projectRoot); const restoredBookId = await resolveSessionActiveBook(params.projectRoot, session); - const resolvedBookId = params.activeBookId ?? params.request.bookId ?? restoredBookId; + const resolvedBookId = params.activeBookId ?? localizedRequest.bookId ?? restoredBookId; const sessionWithBook = resolvedBookId && session.activeBookId !== resolvedBookId ? { ...session, activeBookId: resolvedBookId } : session; @@ -25,13 +29,13 @@ async function processProjectInteractionRequestInternal(params: { try { const result = await runInteractionRequest({ session: sessionWithBook, - request: params.request, + request: localizedRequest, tools: params.tools, }); await persistProjectSession(params.projectRoot, result.session); return { ...result, - request: params.request, + request: localizedRequest, }; } catch (error) { const detail = error instanceof Error ? error.message : String(error); @@ -41,7 +45,7 @@ async function processProjectInteractionRequestInternal(params: { status: "failed", bookId: sessionWithBook.activeBookId, chapterNumber: sessionWithBook.activeChapterNumber, - stageLabel: `failed ${params.request.intent}`, + stageLabel: localizedRequest.language === "en" ? `failed ${localizedRequest.intent}` : `执行失败:${localizedRequest.intent}`, }, }, { kind: "task.failed", @@ -62,6 +66,7 @@ export async function processProjectInteractionInput(params: { readonly tools: InteractionRuntimeTools; readonly activeBookId?: string; }) { + const requestLanguage = await detectProjectInteractionLanguage(params.projectRoot); const session = await loadProjectSession(params.projectRoot); const restoredBookId = await resolveSessionActiveBook(params.projectRoot, session); const resolvedBookId = params.activeBookId ?? restoredBookId; @@ -73,9 +78,9 @@ export async function processProjectInteractionInput(params: { content: params.input, timestamp: Date.now(), }); - const request = routeNaturalLanguageIntent(params.input, { + const request = attachRequestLanguage(routeNaturalLanguageIntent(params.input, { activeBookId: userSession.activeBookId, - }); + }), requestLanguage); try { const result = await runInteractionRequest({ session: userSession, @@ -95,7 +100,7 @@ export async function processProjectInteractionInput(params: { status: "failed", bookId: userSession.activeBookId, chapterNumber: userSession.activeChapterNumber, - stageLabel: `failed ${request.intent}`, + stageLabel: request.language === "en" ? `failed ${request.intent}` : `执行失败:${request.intent}`, }, }, { kind: "task.failed", @@ -118,3 +123,27 @@ export async function processProjectInteractionRequest(params: { }) { return processProjectInteractionRequestInternal(params); } + +function attachRequestLanguage( + request: InteractionRequest, + language: "zh" | "en" | undefined, +): InteractionRequest { + if (request.language || !language) { + return request; + } + + return { + ...request, + language, + }; +} + +async function detectProjectInteractionLanguage(projectRoot: string): Promise<"zh" | "en" | undefined> { + try { + const raw = await readFile(join(projectRoot, "inkos.json"), "utf-8"); + const parsed = JSON.parse(raw) as { language?: string }; + return parsed.language === "en" ? "en" : parsed.language === "zh" ? "zh" : undefined; + } catch { + return undefined; + } +} diff --git a/packages/core/src/interaction/project-tools.ts b/packages/core/src/interaction/project-tools.ts index a486716a..a28c8b76 100644 --- a/packages/core/src/interaction/project-tools.ts +++ b/packages/core/src/interaction/project-tools.ts @@ -315,6 +315,13 @@ async function withPipelineInteractionTelemetry void; + readonly getChatRequestOptions?: () => { + readonly temperature?: number; + readonly maxTokens?: number; + }; + }, ): InteractionRuntimeTools { const instrumentedPipeline = pipeline as InstrumentablePipelineLike; @@ -355,6 +362,7 @@ export function createInteractionToolsFromDeps( }, chat: async (input, options) => { const bookLabel = options.bookId ?? "none"; + const chatRequestOptions = hooks?.getChatRequestOptions?.() ?? {}; const response = instrumentedPipeline.config?.client && instrumentedPipeline.config?.model ? await chatCompletion( instrumentedPipeline.config.client, @@ -374,7 +382,11 @@ export function createInteractionToolsFromDeps( content: `activeBook=${bookLabel}\nautomationMode=${options.automationMode}\nmessage=${input}`, }, ], - { temperature: 0.4, maxTokens: 240 }, + { + temperature: chatRequestOptions.temperature ?? 0.4, + maxTokens: chatRequestOptions.maxTokens ?? 240, + onTextDelta: hooks?.onChatTextDelta, + }, ) : undefined; diff --git a/packages/core/src/interaction/runtime.ts b/packages/core/src/interaction/runtime.ts index 4965b3a9..d931efa5 100644 --- a/packages/core/src/interaction/runtime.ts +++ b/packages/core/src/interaction/runtime.ts @@ -11,6 +11,7 @@ import { } from "./session.js"; type ReviseMode = "local-fix" | "rewrite"; +type RuntimeLanguage = "zh" | "en"; export interface InteractionRuntimeTools { readonly listBooks: () => Promise>; @@ -91,9 +92,30 @@ function extractToolMetadata(value: unknown): InteractionToolMetadata { }; } +function resolveRuntimeLanguage(request: InteractionRequest): RuntimeLanguage { + return request.language === "en" ? "en" : "zh"; +} + +function localize(language: RuntimeLanguage, messages: { zh: T; en: T }): T { + return language === "en" ? messages.en : messages.zh; +} + +function localizeMode(mode: AutomationMode, language: RuntimeLanguage): string { + if (language === "en") { + return mode; + } + + return { + auto: "自动", + semi: "半自动", + manual: "手动", + }[mode] ?? mode; +} + function buildTaskStartedState( session: InteractionSession, request: InteractionRequest, + language: RuntimeLanguage, ): ExecutionState { switch (request.intent) { case "write_next": @@ -102,20 +124,29 @@ function buildTaskStartedState( status: "planning", bookId: request.bookId ?? session.activeBookId, chapterNumber: session.activeChapterNumber, - stageLabel: "preparing chapter inputs", + stageLabel: localize(language, { + zh: "准备章节输入", + en: "preparing chapter inputs", + }), }; case "create_book": return { status: "planning", bookId: request.bookId ?? session.activeBookId, - stageLabel: "creating book foundation", + stageLabel: localize(language, { + zh: "创建作品基础", + en: "creating book foundation", + }), }; case "export_book": return { status: "persisting", bookId: request.bookId ?? session.activeBookId, chapterNumber: session.activeChapterNumber, - stageLabel: "exporting book artifacts", + stageLabel: localize(language, { + zh: "导出作品文件", + en: "exporting book artifacts", + }), }; case "revise_chapter": case "rewrite_chapter": @@ -123,7 +154,9 @@ function buildTaskStartedState( status: "repairing", bookId: request.bookId ?? session.activeBookId, chapterNumber: request.chapterNumber ?? session.activeChapterNumber, - stageLabel: request.intent === "rewrite_chapter" ? "rewriting chapter" : "revising chapter", + stageLabel: request.intent === "rewrite_chapter" + ? localize(language, { zh: "重写章节", en: "rewriting chapter" }) + : localize(language, { zh: "修订章节", en: "revising chapter" }), }; case "update_focus": case "update_author_intent": @@ -132,21 +165,30 @@ function buildTaskStartedState( status: "persisting", bookId: request.bookId ?? session.activeBookId, chapterNumber: session.activeChapterNumber, - stageLabel: "applying project edit", + stageLabel: localize(language, { + zh: "应用项目修改", + en: "applying project edit", + }), }; case "pause_book": return { status: "blocked", bookId: request.bookId ?? session.activeBookId, chapterNumber: session.activeChapterNumber, - stageLabel: "paused by user", + stageLabel: localize(language, { + zh: "已由用户暂停", + en: "paused by user", + }), }; default: return { status: "planning", bookId: request.bookId ?? session.activeBookId, chapterNumber: session.activeChapterNumber, - stageLabel: `handling ${request.intent}`, + stageLabel: localize(language, { + zh: `处理中:${request.intent}`, + en: `handling ${request.intent}`, + }), }; } } @@ -177,6 +219,7 @@ function shouldWaitForHuman( function buildPendingDecision( session: InteractionSession, request: InteractionRequest, + language: RuntimeLanguage, chapterNumber?: number, ): PendingDecision | undefined { if (!shouldWaitForHuman(session.automationMode, request)) { @@ -193,21 +236,31 @@ function buildPendingDecision( bookId, ...(chapterNumber !== undefined ? { chapterNumber } : {}), summary: session.automationMode === "manual" - ? "Execution finished. Choose the next action explicitly." - : "Execution finished. Waiting for your next decision.", + ? localize(language, { + zh: "执行已完成。请明确选择下一步操作。", + en: "Execution finished. Choose the next action explicitly.", + }) + : localize(language, { + zh: "执行已完成,等待你的下一步决定。", + en: "Execution finished. Waiting for your next decision.", + }), }; } function buildWaitingExecution( session: InteractionSession, request: InteractionRequest, + language: RuntimeLanguage, chapterNumber?: number, ): ExecutionState { return { status: "waiting_human", bookId: request.bookId ?? session.activeBookId, ...(chapterNumber !== undefined ? { chapterNumber } : {}), - stageLabel: "waiting for your next decision", + stageLabel: localize(language, { + zh: "等待你的下一步决定", + en: "waiting for your next decision", + }), }; } @@ -232,6 +285,7 @@ export async function runInteractionRequest(params: { readonly tools: InteractionRuntimeTools; }): Promise { const request = routeInteractionRequest(params.request); + const language = resolveRuntimeLanguage(request); let session = params.session; const addEvent = ( nextSession: InteractionSession, @@ -253,9 +307,12 @@ export async function runInteractionRequest(params: { session = clearPendingDecision({ ...session, - currentExecution: buildTaskStartedState(session, request), + currentExecution: buildTaskStartedState(session, request, language), }); - session = addEvent(session, "task.started", session.currentExecution!.status, `Started ${request.intent}.`); + session = addEvent(session, "task.started", session.currentExecution!.status, localize(language, { + zh: `开始执行 ${request.intent}。`, + en: `Started ${request.intent}.`, + })); const markCompleted = (nextSession: InteractionSession): InteractionSession => ({ ...nextSession, @@ -263,17 +320,26 @@ export async function runInteractionRequest(params: { status: "completed", bookId: nextSession.activeBookId, chapterNumber: nextSession.activeChapterNumber, - stageLabel: "completed", + stageLabel: localize(language, { + zh: "已完成", + en: "completed", + }), }, }); switch (request.intent) { case "create_book": { if (!params.tools.createBook) { - throw new Error("Book creation is not implemented in the interaction runtime yet."); + throw new Error(localize(language, { + zh: "交互运行时暂未实现创建作品。", + en: "Book creation is not implemented in the interaction runtime yet.", + })); } if (!request.title) { - throw new Error("Book creation requires a title."); + throw new Error(localize(language, { + zh: "创建作品需要标题。", + en: "Book creation requires a title.", + })); } const toolResult = await params.tools.createBook({ title: request.title, @@ -289,7 +355,10 @@ export async function runInteractionRequest(params: { ? (toolResult as { bookId: string }).bookId : undefined; if (!createdBookId) { - throw new Error("Create-book tool did not return a book id."); + throw new Error(localize(language, { + zh: "创建作品工具没有返回作品 ID。", + en: "Create-book tool did not return a book id.", + })); } session = bindActiveBook(session, createdBookId); session = appendToolEvents(session, metadata.events); @@ -298,8 +367,14 @@ export async function runInteractionRequest(params: { currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Created ${createdBookId}.`), - responseText: metadata.responseText ?? `Created ${createdBookId}.`, + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已创建作品 ${createdBookId}。`, + en: `Created ${createdBookId}.`, + })), + responseText: metadata.responseText ?? localize(language, { + zh: `已创建作品 ${createdBookId}。`, + en: `Created ${createdBookId}.`, + }), details: metadata.details, }; } @@ -307,7 +382,10 @@ export async function runInteractionRequest(params: { case "continue_book": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } const toolResult = await params.tools.writeNextChapter(bookId); const metadata = extractToolMetadata(toolResult); @@ -316,24 +394,34 @@ export async function runInteractionRequest(params: { const pendingDecision = metadata.pendingDecision ?? buildPendingDecision( session, request, + language, metadata.activeChapterNumber, ); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, metadata.activeChapterNumber), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language, metadata.activeChapterNumber), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Completed write_next for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已为 ${bookId} 完成下一章写作。`, + en: `Completed write_next for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Completed write_next for ${bookId}; waiting for your next decision.` - : `Completed write_next for ${bookId}.` + ? localize(language, { + zh: `已为 ${bookId} 完成下一章写作,等待你的下一步决定。`, + en: `Completed write_next for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已为 ${bookId} 完成下一章写作。`, + en: `Completed write_next for ${bookId}.`, + }) ), }; } @@ -341,34 +429,61 @@ export async function runInteractionRequest(params: { const books = await params.tools.listBooks(); const completed = markCompleted(session); return { - session: addEvent(completed, "task.completed", "completed", `Listed ${books.length} book(s).`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已列出 ${books.length} 本作品。`, + en: `Listed ${books.length} book(s).`, + })), responseText: books.length > 0 - ? `Books: ${books.join(", ")}` - : "No books found in this project.", + ? localize(language, { + zh: `作品列表:${books.join("、")}`, + en: `Books: ${books.join(", ")}`, + }) + : localize(language, { + zh: "当前项目下没有作品。", + en: "No books found in this project.", + }), }; } case "select_book": { if (!request.bookId) { - throw new Error("Book selection requires a book id."); + throw new Error(localize(language, { + zh: "切换作品需要提供作品 ID。", + en: "Book selection requires a book id.", + })); } const books = await params.tools.listBooks(); if (!books.includes(request.bookId)) { - throw new Error(`Book "${request.bookId}" not found in this project.`); + throw new Error(localize(language, { + zh: `当前项目中找不到作品「${request.bookId}」。`, + en: `Book "${request.bookId}" not found in this project.`, + })); } const completed = markCompleted(bindActiveBook(session, request.bookId)); return { - session: addEvent(completed, "task.completed", "completed", `Bound active book to ${request.bookId}.`), - responseText: `Opened ${request.bookId}.`, + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已切换当前作品到 ${request.bookId}。`, + en: `Bound active book to ${request.bookId}.`, + })), + responseText: localize(language, { + zh: `当前作品:${request.bookId}`, + en: `Active book: ${request.bookId}`, + }), }; } case "revise_chapter": case "rewrite_chapter": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.chapterNumber) { - throw new Error("Chapter number is required for chapter revision."); + throw new Error(localize(language, { + zh: "修订章节需要章节号。", + en: "Chapter number is required for chapter revision.", + })); } const mode: ReviseMode = request.intent === "rewrite_chapter" ? "rewrite" : "local-fix"; const toolResult = await params.tools.reviseDraft(bookId, request.chapterNumber, mode); @@ -379,34 +494,56 @@ export async function runInteractionRequest(params: { const pendingDecision = metadata.pendingDecision ?? buildPendingDecision( session, request, + language, chapterNumber, ); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, chapterNumber), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language, chapterNumber), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Completed ${request.intent} for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: request.intent === "rewrite_chapter" + ? `已为 ${bookId} 完成章节重写。` + : `已为 ${bookId} 完成章节修订。`, + en: `Completed ${request.intent} for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Completed ${request.intent} for ${bookId}; waiting for your next decision.` - : `Completed ${request.intent} for ${bookId}.` + ? localize(language, { + zh: request.intent === "rewrite_chapter" + ? `已为 ${bookId} 完成章节重写,等待你的下一步决定。` + : `已为 ${bookId} 完成章节修订,等待你的下一步决定。`, + en: `Completed ${request.intent} for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: request.intent === "rewrite_chapter" + ? `已为 ${bookId} 完成章节重写。` + : `已为 ${bookId} 完成章节修订。`, + en: `Completed ${request.intent} for ${bookId}.`, + }) ), }; } case "patch_chapter_text": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.chapterNumber || !request.targetText || !request.replacementText) { - throw new Error("Chapter patch requires chapter number, target text, and replacement text."); + throw new Error(localize(language, { + zh: "正文修补需要章节号、目标文本和替换文本。", + en: "Chapter patch requires chapter number, target text, and replacement text.", + })); } const toolResult = await params.tools.patchChapterText( bookId, @@ -421,34 +558,50 @@ export async function runInteractionRequest(params: { const pendingDecision = metadata.pendingDecision ?? buildPendingDecision( session, request, + language, chapterNumber, ); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, chapterNumber), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language, chapterNumber), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Patched chapter ${chapterNumber} for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已修补 ${bookId} 的第 ${chapterNumber} 章。`, + en: `Patched chapter ${chapterNumber} for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Patched chapter ${chapterNumber} for ${bookId}; waiting for your next decision.` - : `Patched chapter ${chapterNumber} for ${bookId}.` + ? localize(language, { + zh: `已修补 ${bookId} 的第 ${chapterNumber} 章,等待你的下一步决定。`, + en: `Patched chapter ${chapterNumber} for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已修补 ${bookId} 的第 ${chapterNumber} 章。`, + en: `Patched chapter ${chapterNumber} for ${bookId}.`, + }) ), }; } case "rename_entity": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.oldValue || !request.newValue) { - throw new Error("Entity rename requires old and new values."); + throw new Error(localize(language, { + zh: "实体改名需要旧值和新值。", + en: "Entity rename requires old and new values.", + })); } const toolResult = await params.tools.renameEntity(bookId, request.oldValue, request.newValue); const metadata = extractToolMetadata(toolResult); @@ -457,130 +610,191 @@ export async function runInteractionRequest(params: { const pendingDecision = metadata.pendingDecision ?? buildPendingDecision( session, request, + language, metadata.activeChapterNumber, ); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, metadata.activeChapterNumber), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language, metadata.activeChapterNumber), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已在 ${bookId} 中把 ${request.oldValue} 改成 ${request.newValue}。`, + en: `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}; waiting for your next decision.` - : `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}.` + ? localize(language, { + zh: `已在 ${bookId} 中把 ${request.oldValue} 改成 ${request.newValue},等待你的下一步决定。`, + en: `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已在 ${bookId} 中把 ${request.oldValue} 改成 ${request.newValue}。`, + en: `Renamed ${request.oldValue} to ${request.newValue} in ${bookId}.`, + }) ), }; } case "update_focus": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.instruction) { - throw new Error("Focus update requires instruction content."); + throw new Error(localize(language, { + zh: "更新焦点需要提供内容。", + en: "Focus update requires instruction content.", + })); } const toolResult = await params.tools.updateCurrentFocus(bookId, request.instruction); const metadata = extractToolMetadata(toolResult); session = bindActiveBook(session, bookId); session = appendToolEvents(session, metadata.events); - const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request); + const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request, language); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Updated current focus for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已更新 ${bookId} 的当前焦点。`, + en: `Updated current focus for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Updated current focus for ${bookId}; waiting for your next decision.` - : `Updated current focus for ${bookId}.` + ? localize(language, { + zh: `已更新 ${bookId} 的当前焦点,等待你的下一步决定。`, + en: `Updated current focus for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已更新 ${bookId} 的当前焦点。`, + en: `Updated current focus for ${bookId}.`, + }) ), }; } case "update_author_intent": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.instruction) { - throw new Error("Author intent update requires instruction content."); + throw new Error(localize(language, { + zh: "更新作者意图需要提供内容。", + en: "Author intent update requires instruction content.", + })); } const toolResult = await params.tools.updateAuthorIntent(bookId, request.instruction); const metadata = extractToolMetadata(toolResult); session = bindActiveBook(session, bookId); session = appendToolEvents(session, metadata.events); - const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request); + const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request, language); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Updated author intent for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已更新 ${bookId} 的作者意图。`, + en: `Updated author intent for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Updated author intent for ${bookId}; waiting for your next decision.` - : `Updated author intent for ${bookId}.` + ? localize(language, { + zh: `已更新 ${bookId} 的作者意图,等待你的下一步决定。`, + en: `Updated author intent for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已更新 ${bookId} 的作者意图。`, + en: `Updated author intent for ${bookId}.`, + }) ), }; } case "edit_truth": { const bookId = request.bookId ?? session.activeBookId; if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } if (!request.fileName || !request.instruction) { - throw new Error("Truth-file edit requires a file name and content."); + throw new Error(localize(language, { + zh: "编辑真相文件需要文件名和内容。", + en: "Truth-file edit requires a file name and content.", + })); } const toolResult = await params.tools.writeTruthFile(bookId, request.fileName, request.instruction); const metadata = extractToolMetadata(toolResult); session = bindActiveBook(session, bookId); session = appendToolEvents(session, metadata.events); - const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request); + const pendingDecision = metadata.pendingDecision ?? buildPendingDecision(session, request, language); const completed = pendingDecision ? { ...session, pendingDecision, - currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request), + currentExecution: metadata.currentExecution ?? buildWaitingExecution(session, request, language), } : { ...markCompleted(session), currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Updated ${request.fileName} for ${bookId}.`), + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已更新 ${bookId} 的 ${request.fileName}。`, + en: `Updated ${request.fileName} for ${bookId}.`, + })), responseText: metadata.responseText ?? ( pendingDecision - ? `Updated ${request.fileName} for ${bookId}; waiting for your next decision.` - : `Updated ${request.fileName} for ${bookId}.` + ? localize(language, { + zh: `已更新 ${bookId} 的 ${request.fileName},等待你的下一步决定。`, + en: `Updated ${request.fileName} for ${bookId}; waiting for your next decision.`, + }) + : localize(language, { + zh: `已更新 ${bookId} 的 ${request.fileName}。`, + en: `Updated ${request.fileName} for ${bookId}.`, + }) ), }; } case "export_book": { const bookId = request.bookId ?? session.activeBookId; if (!params.tools.exportBook) { - throw new Error("Book export is not implemented in the interaction runtime yet."); + throw new Error(localize(language, { + zh: "交互运行时暂未实现导出作品。", + en: "Book export is not implemented in the interaction runtime yet.", + })); } if (!bookId) { - throw new Error("No active book is bound to the interaction session."); + throw new Error(localize(language, { + zh: "当前交互会话还没有绑定作品。", + en: "No active book is bound to the interaction session.", + })); } const toolResult = await params.tools.exportBook(bookId, { format: request.format, @@ -595,16 +809,28 @@ export async function runInteractionRequest(params: { currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution, }; return { - session: addEvent(completed, "task.completed", "completed", `Exported ${bookId}.`), - responseText: metadata.responseText ?? `Exported ${bookId}.`, + session: addEvent(completed, "task.completed", "completed", localize(language, { + zh: `已导出 ${bookId}。`, + en: `Exported ${bookId}.`, + })), + responseText: metadata.responseText ?? localize(language, { + zh: `已导出 ${bookId}。`, + en: `Exported ${bookId}.`, + }), details: metadata.details, }; } case "switch_mode": session = markCompleted(session); return { - session: addEvent(session, "task.completed", "completed", `Switched mode to ${session.automationMode}.`), - responseText: `Switched mode to ${session.automationMode}.`, + session: addEvent(session, "task.completed", "completed", localize(language, { + zh: `已切换到${localizeMode(session.automationMode, language)}模式。`, + en: `Switched mode to ${session.automationMode}.`, + })), + responseText: localize(language, { + zh: `已切换到${localizeMode(session.automationMode, language)}模式。`, + en: `Switched mode to ${session.automationMode}.`, + }), }; case "pause_book": { const bookId = request.bookId ?? session.activeBookId; @@ -614,12 +840,21 @@ export async function runInteractionRequest(params: { status: "blocked" as const, bookId, chapterNumber: session.activeChapterNumber, - stageLabel: "paused by user", + stageLabel: localize(language, { + zh: "已由用户暂停", + en: "paused by user", + }), }, }; return { - session: addEvent(paused, "task.completed", "blocked", `Paused ${bookId ?? "current book"}.`), - responseText: `Paused ${bookId ?? "current book"}.`, + session: addEvent(paused, "task.completed", "blocked", localize(language, { + zh: `已暂停${bookId ?? "当前作品"}。`, + en: `Paused ${bookId ?? "current book"}.`, + })), + responseText: localize(language, { + zh: `已暂停${bookId ?? "当前作品"}。`, + en: `Paused ${bookId ?? "current book"}.`, + }), }; } case "resume_book": { @@ -630,12 +865,21 @@ export async function runInteractionRequest(params: { status: "completed" as const, bookId, chapterNumber: session.activeChapterNumber, - stageLabel: "ready to continue", + stageLabel: localize(language, { + zh: "可继续执行", + en: "ready to continue", + }), }, }; return { - session: addEvent(resumed, "task.completed", "completed", `Resumed ${bookId ?? "current book"}.`), - responseText: `Resumed ${bookId ?? "current book"}.`, + session: addEvent(resumed, "task.completed", "completed", localize(language, { + zh: `已恢复${bookId ?? "当前作品"}。`, + en: `Resumed ${bookId ?? "current book"}.`, + })), + responseText: localize(language, { + zh: `已恢复${bookId ?? "当前作品"}。`, + en: `Resumed ${bookId ?? "current book"}.`, + }), }; } case "chat": { @@ -651,11 +895,23 @@ export async function runInteractionRequest(params: { const responseText = metadata.responseText ?? ( /^(hi|hello|hey|你好|嗨|哈喽)$/i.test(prompt) ? (bookId - ? `Hi. Active book is ${bookId}. Ask me to continue, revise a chapter, or explain what is blocked.` - : "Hi. No active book yet. Open a book, list books, or tell me what you want to write.") + ? localize(language, { + zh: `你好。当前作品是 ${bookId}。你可以让我继续写、修订章节,或者解释当前卡住的原因。`, + en: `Hi. Active book is ${bookId}. Ask me to continue, revise a chapter, or explain what is blocked.`, + }) + : localize(language, { + zh: "你好。当前还没有激活作品。你可以先打开作品、列出作品,或者直接告诉我你要写什么。", + en: "Hi. No active book yet. Open a book, list books, or tell me what you want to write.", + })) : (bookId - ? `I’m here. Active book is ${bookId}. You can ask me to continue, revise a chapter, rewrite, change focus, or inspect why the pipeline stopped.` - : "I’m here. No active book is bound yet. Open a book, list books, or describe what you want to write.") + ? localize(language, { + zh: `我在。当前作品是 ${bookId}。你可以让我继续写、修订章节、重写、调整焦点,或者查看流水线为何停止。`, + en: `I’m here. Active book is ${bookId}. You can ask me to continue, revise a chapter, rewrite, change focus, or inspect why the pipeline stopped.`, + }) + : localize(language, { + zh: "我在。当前还没有绑定作品。先打开作品、列出作品,或者直接描述你要写什么。", + en: "I’m here. No active book is bound yet. Open a book, list books, or describe what you want to write.", + })) ); const completed = markCompleted(session); return { @@ -669,8 +925,14 @@ export async function runInteractionRequest(params: { const baselineExecution = params.session.currentExecution; const stage = baselineExecution?.stageLabel ?? baselineExecution?.status ?? "idle"; const summary = request.intent === "explain_failure" - ? `Current failure context: ${bookId ?? "no active book"} is at ${stage}.` - : `Current status: ${bookId ?? "no active book"} is at ${stage}.`; + ? localize(language, { + zh: `当前失败上下文:${bookId ?? "当前无激活作品"} 处于 ${stage}。`, + en: `Current failure context: ${bookId ?? "no active book"} is at ${stage}.`, + }) + : localize(language, { + zh: `当前状态:${bookId ?? "当前无激活作品"} 处于 ${stage}。`, + en: `Current status: ${bookId ?? "no active book"} is at ${stage}.`, + }); const completed = markCompleted(session); return { session: addEvent(completed, "task.completed", "completed", summary), @@ -678,6 +940,9 @@ export async function runInteractionRequest(params: { }; } default: - throw new Error(`Intent "${request.intent}" is not implemented in the interaction runtime yet.`); + throw new Error(localize(language, { + zh: `交互运行时暂未实现意图「${request.intent}」。`, + en: `Intent "${request.intent}" is not implemented in the interaction runtime yet.`, + })); } } diff --git a/packages/core/src/llm/provider.ts b/packages/core/src/llm/provider.ts index 52a29299..e3e1604b 100644 --- a/packages/core/src/llm/provider.ts +++ b/packages/core/src/llm/provider.ts @@ -301,6 +301,7 @@ export async function chatCompletion( readonly maxTokens?: number; readonly webSearch?: boolean; readonly onStreamProgress?: OnStreamProgress; + readonly onTextDelta?: (text: string) => void; }, ): Promise { const perCallMax = options?.maxTokens ?? client.defaults.maxTokens; @@ -314,22 +315,23 @@ export async function chatCompletion( extra: client.defaults.extra, }; const onStreamProgress = options?.onStreamProgress; + const onTextDelta = options?.onTextDelta; const errorCtx = { baseUrl: client._openai?.baseURL ?? "(anthropic)", model }; try { if (client.provider === "anthropic") { return client.stream - ? await chatCompletionAnthropic(client._anthropic!, model, messages, resolved, client.defaults.thinkingBudget, onStreamProgress) - : await chatCompletionAnthropicSync(client._anthropic!, model, messages, resolved, client.defaults.thinkingBudget); + ? await chatCompletionAnthropic(client._anthropic!, model, messages, resolved, client.defaults.thinkingBudget, onStreamProgress, onTextDelta) + : await chatCompletionAnthropicSync(client._anthropic!, model, messages, resolved, client.defaults.thinkingBudget, onTextDelta); } if (client.apiFormat === "responses") { return client.stream - ? await chatCompletionOpenAIResponses(client._openai!, model, messages, resolved, options?.webSearch, onStreamProgress) - : await chatCompletionOpenAIResponsesSync(client._openai!, model, messages, resolved, options?.webSearch); + ? await chatCompletionOpenAIResponses(client._openai!, model, messages, resolved, options?.webSearch, onStreamProgress, onTextDelta) + : await chatCompletionOpenAIResponsesSync(client._openai!, model, messages, resolved, options?.webSearch, onTextDelta); } return client.stream - ? await chatCompletionOpenAIChat(client._openai!, model, messages, resolved, options?.webSearch, onStreamProgress) - : await chatCompletionOpenAIChatSync(client._openai!, model, messages, resolved, options?.webSearch); + ? await chatCompletionOpenAIChat(client._openai!, model, messages, resolved, options?.webSearch, onStreamProgress, onTextDelta) + : await chatCompletionOpenAIChatSync(client._openai!, model, messages, resolved, options?.webSearch, onTextDelta); } catch (error) { // Stream interrupted but partial content is usable — return truncated response if (error instanceof PartialResponseError) { @@ -433,6 +435,7 @@ async function chatCompletionOpenAIChat( options: { readonly temperature: number; readonly maxTokens: number; readonly extra: Record }, webSearch?: boolean, onStreamProgress?: OnStreamProgress, + onTextDelta?: (text: string) => void, ): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const createParams: any = { @@ -458,6 +461,7 @@ async function chatCompletionOpenAIChat( if (delta) { chunks.push(delta); monitor.onChunk(delta); + onTextDelta?.(delta); } if (chunk.usage) { inputTokens = chunk.usage.prompt_tokens ?? 0; @@ -494,6 +498,7 @@ async function chatCompletionOpenAIChatSync( messages: ReadonlyArray, options: { readonly temperature: number; readonly maxTokens: number; readonly extra: Record }, _webSearch?: boolean, + onTextDelta?: (text: string) => void, ): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const syncParams: any = { @@ -508,6 +513,7 @@ async function chatCompletionOpenAIChatSync( const content = response.choices[0]?.message?.content ?? ""; if (!content) throw new Error("LLM returned empty response"); + onTextDelta?.(content); return { content, @@ -621,6 +627,7 @@ async function chatCompletionOpenAIResponses( options: { readonly temperature: number; readonly maxTokens: number }, webSearch?: boolean, onStreamProgress?: OnStreamProgress, + onTextDelta?: (text: string) => void, ): Promise { const input: OpenAI.Responses.ResponseInputItem[] = messages.map((m) => ({ role: m.role as "system" | "user" | "assistant", @@ -650,6 +657,7 @@ async function chatCompletionOpenAIResponses( if (event.type === "response.output_text.delta") { chunks.push(event.delta); monitor.onChunk(event.delta); + onTextDelta?.(event.delta); } if (event.type === "response.completed") { inputTokens = event.response.usage?.input_tokens ?? 0; @@ -686,6 +694,7 @@ async function chatCompletionOpenAIResponsesSync( messages: ReadonlyArray, options: { readonly temperature: number; readonly maxTokens: number }, _webSearch?: boolean, + onTextDelta?: (text: string) => void, ): Promise { const input: OpenAI.Responses.ResponseInputItem[] = messages.map((m) => ({ role: m.role as "system" | "user" | "assistant", @@ -708,6 +717,7 @@ async function chatCompletionOpenAIResponsesSync( .join(""); if (!content) throw new Error("LLM returned empty response"); + onTextDelta?.(content); return { content, @@ -814,6 +824,7 @@ async function chatCompletionAnthropic( options: { readonly temperature: number; readonly maxTokens: number }, thinkingBudget: number = 0, onStreamProgress?: OnStreamProgress, + onTextDelta?: (text: string) => void, ): Promise { const systemText = messages .filter((m) => m.role === "system") @@ -845,6 +856,7 @@ async function chatCompletionAnthropic( if (event.type === "content_block_delta" && event.delta.type === "text_delta") { chunks.push(event.delta.text); monitor.onChunk(event.delta.text); + onTextDelta?.(event.delta.text); } if (event.type === "message_start") { inputTokens = event.message.usage?.input_tokens ?? 0; @@ -883,6 +895,7 @@ async function chatCompletionAnthropicSync( messages: ReadonlyArray, options: { readonly temperature: number; readonly maxTokens: number }, thinkingBudget: number = 0, + onTextDelta?: (text: string) => void, ): Promise { const systemText = messages .filter((m) => m.role === "system") @@ -909,6 +922,7 @@ async function chatCompletionAnthropicSync( .join(""); if (!content) throw new Error("LLM returned empty response"); + onTextDelta?.(content); return { content, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f994a72b..0ad599bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,10 +12,10 @@ importers: dependencies: '@actalk/inkos-core': specifier: 1.1.1 - version: link:../core + version: 1.1.1(ws@8.20.0) '@actalk/inkos-studio': specifier: 1.1.1 - version: link:../studio + version: 1.1.1(@types/node@22.19.15)(@types/react@19.2.14)(typescript@5.9.3)(ws@8.20.0) commander: specifier: ^13.0.0 version: 13.1.0 @@ -25,13 +25,28 @@ importers: epub-gen-memory: specifier: ^1.0.10 version: 1.1.2 + ink: + specifier: ^7.0.0 + version: 7.0.0(@types/react@19.2.14)(react@19.2.4) + ink-text-input: + specifier: ^6.0.0 + version: 6.0.0(ink@7.0.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) marked: specifier: ^15.0.0 version: 15.0.12 + react: + specifier: ^19.2.4 + version: 19.2.4 devDependencies: '@types/node': specifier: ^22.0.0 version: 22.19.15 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + ink-testing-library: + specifier: ^4.0.0 + version: 4.0.0(@types/react@19.2.14) typescript: specifier: ^5.8.0 version: 5.9.3 @@ -52,7 +67,7 @@ importers: version: 4.1.1 openai: specifier: ^4.80.0 - version: 4.104.0(zod@3.25.76) + version: 4.104.0(ws@8.20.0)(zod@3.25.76) zod: specifier: ^3.24.0 version: 3.25.76 @@ -71,7 +86,7 @@ importers: dependencies: '@actalk/inkos-core': specifier: 1.1.1 - version: link:../core + version: 1.1.1(ws@8.20.0) '@base-ui/react': specifier: ^1.3.0 version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -133,6 +148,16 @@ importers: packages: + '@actalk/inkos-core@1.1.1': + resolution: {integrity: sha512-UeAPIKHCydx71LAm1s7ZqRjjmtFh9fkmtEYrDkMvY0clTYyvxgEcLfwDTFa1lVu5r2s8uJO8tv9TPZTnmbE0Pw==} + + '@actalk/inkos-studio@1.1.1': + resolution: {integrity: sha512-2dhS9Ax5mHcF7FiEl/HclWM8Tazsa7/pZKVm9WpHL/wEWcrnrv4Js4bLinAzYIAIx16n/EB3zuX0B5YRQT6bIw==} + + '@alcalzone/ansi-tokenize@0.3.0': + resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} + engines: {node: '>=18'} + '@anthropic-ai/sdk@0.78.0': resolution: {integrity: sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w==} hasBin: true @@ -158,14 +183,28 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} @@ -176,10 +215,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -201,6 +254,24 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -213,6 +284,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -250,6 +333,16 @@ packages: '@types/react': optional: true + '@dotenvx/dotenvx@1.61.0': + resolution: {integrity: sha512-utL3cpZoFzflyqUkjYbxYujI6STBTmO5LFn4bbin/NZnRWN6wQ7eErhr3/Vpa5h/jicPFC6kTa42r940mQftJQ==} + hasBin: true + + '@ecies/ciphers@0.2.6': + resolution: {integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==} + engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -577,6 +670,9 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@fontsource-variable/geist@5.2.8': + resolution: {integrity: sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==} + '@hono/node-server@1.19.11': resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} engines: {node: '>=18.14.1'} @@ -634,10 +730,44 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@mswjs/interceptors@0.41.3': resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} engines: {node: '>=18'} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -788,10 +918,17 @@ packages: cpu: [x64] os: [win32] + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@tailwindcss/node@4.2.1': resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} @@ -886,6 +1023,9 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@ts-morph/common@0.27.0': + resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -930,6 +1070,9 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -969,18 +1112,49 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + agentkeepalive@4.6.0: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -988,12 +1162,20 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + autoprefixer@10.4.27: resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} engines: {node: ^10 || ^12 || >=14} @@ -1004,22 +1186,46 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.10.8: resolution: {integrity: sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==} engines: {node: '>=6.0.0'} hasBin: true + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1028,6 +1234,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1039,10 +1249,37 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-boxes@4.0.1: + resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==} + engines: {node: '>=18.20 <19 || >=20.10'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@6.0.0: + resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==} + engines: {node: '>=22'} + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -1055,6 +1292,13 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1066,13 +1310,41 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -1080,6 +1352,23 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -1087,9 +1376,18 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1099,14 +1397,42 @@ packages: supports-color: optional: true + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1114,6 +1440,10 @@ packages: diacritics@1.3.0: resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} @@ -1135,10 +1465,21 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eciesjs@0.4.18: + resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -1147,9 +1488,16 @@ packages: electron-to-chromium@1.5.313: resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.20.1: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} @@ -1161,10 +1509,21 @@ packages: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + epub-gen-memory@1.1.2: resolution: {integrity: sha512-vwGM6MVNqKIskFzPZqhi4ZOs0ZTUXco9oDuHFX1vB2Il9pTAkaHWFBFgHrrl832dYmBPb/raGVUZXFvZYueRyw==} engines: {node: '>=10.0.0'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1184,6 +1543,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.45.1: + resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -1198,17 +1560,72 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1218,9 +1635,25 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + filelist@1.0.6: resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + form-data-encoder@1.7.2: resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} @@ -1232,9 +1665,25 @@ packages: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1243,6 +1692,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + fuzzysort@3.1.0: + resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1251,17 +1703,37 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-own-enumerable-keys@1.0.0: + resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} + engines: {node: '>=14.16'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1295,29 +1767,182 @@ packages: htmlparser2@7.2.0: resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ink-testing-library@4.0.0: + resolution: {integrity: sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=18.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + + ink-text-input@6.0.0: + resolution: {integrity: sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==} + engines: {node: '>=18'} + peerDependencies: + ink: '>=5' + react: '>=18' + + ink@7.0.0: + resolution: {integrity: sha512-fMie5/VwIYXofMyND0s+fOVhwVBBPYx+uuqJ6V6rUBGjui+2UYp+0fWtvhSeKT4z+X1uH98a4ge5Vj3aTlL6mg==} + engines: {node: '>=22'} + peerDependencies: + '@types/react': '>=19.2.0' + react: '>=19.2.0' + react-devtools-core: '>=6.1.2' + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ci@2.0.0: + resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==} + engines: {node: '>=20'} + hasBin: true + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -1327,6 +1952,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1342,18 +1970,38 @@ packages: engines: {node: '>=6'} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-to-ts@3.1.1: resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} engines: {node: '>=16'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -1431,10 +2079,17 @@ packages: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -1458,23 +2113,65 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} hasBin: true + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1497,6 +2194,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -1511,12 +2212,55 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + openai@4.104.0: resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} hasBin: true @@ -1529,6 +2273,10 @@ packages: zod: optional: true + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -1539,9 +2287,43 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1552,10 +2334,22 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1563,14 +2357,51 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: react: ^19.2.4 + react-reconciler@0.33.0: + resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.2.0 + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1582,27 +2413,65 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + rettime@0.10.1: resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1610,16 +2479,65 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shadcn@4.2.0: + resolution: {integrity: sha512-ZDuV340itidaUd4Gi1BxQX+Y7Ush6BHp6URZBM2RyxUUBZ6yFtOWIr4nVY+Ro+YRSpo82v7JrsmtcU5xoBCMJQ==} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slice-ansi@9.0.0: + resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} + engines: {node: '>=22'} + slugify@1.6.8: resolution: {integrity: sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==} engines: {node: '>=8.0.0'} @@ -1628,6 +2546,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1638,6 +2564,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -1645,13 +2575,41 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + stringify-object@5.0.0: + resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} + engines: {node: '>=14.16'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -1672,6 +2630,13 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + terminal-size@4.0.1: + resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} + engines: {node: '>=18'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1701,6 +2666,14 @@ packages: resolution: {integrity: sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==} hasBin: true + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -1711,15 +2684,36 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-morph@26.0.0: + resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true - type-fest@5.4.4: - resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-fest@5.5.0: + resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==} engines: {node: '>=20'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1731,6 +2725,18 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -1752,6 +2758,14 @@ packages: resolution: {integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==} engines: {node: '>=0.10.0'} + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1865,6 +2879,10 @@ packages: jsdom: optional: true + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -1875,11 +2893,29 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + widest-line@6.0.0: + resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} + engines: {node: '>=20'} + + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -1888,6 +2924,25 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1903,15 +2958,73 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-spinner@1.1.0: + resolution: {integrity: sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA==} + engines: {node: '>=18.19'} + yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: + '@actalk/inkos-core@1.1.1(ws@8.20.0)': + dependencies: + '@anthropic-ai/sdk': 0.78.0(zod@3.25.76) + dotenv: 16.6.1 + js-yaml: 4.1.1 + openai: 4.104.0(ws@8.20.0)(zod@3.25.76) + zod: 3.25.76 + transitivePeerDependencies: + - encoding + - ws + + '@actalk/inkos-studio@1.1.1(@types/node@22.19.15)(@types/react@19.2.14)(typescript@5.9.3)(ws@8.20.0)': + dependencies: + '@actalk/inkos-core': 1.1.1(ws@8.20.0) + '@base-ui/react': 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@fontsource-variable/geist': 5.2.8 + '@hono/node-server': 1.19.11(hono@4.12.8) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + dotenv: 16.6.1 + hono: 4.12.8 + lucide-react: 0.577.0(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + shadcn: 4.2.0(@types/node@22.19.15)(typescript@5.9.3) + tailwind-merge: 3.5.0 + tw-animate-css: 1.4.0 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - '@types/react' + - babel-plugin-macros + - encoding + - supports-color + - typescript + - ws + + '@alcalzone/ansi-tokenize@0.3.0': + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + '@anthropic-ai/sdk@0.78.0(zod@3.25.76)': dependencies: json-schema-to-ts: 3.1.1 @@ -1954,6 +3067,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 @@ -1962,8 +3079,28 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 @@ -1980,8 +3117,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -1997,6 +3154,24 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -2007,6 +3182,28 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.28.6': {} '@babel/template@7.28.6': @@ -2056,6 +3253,23 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@dotenvx/dotenvx@1.61.0': + dependencies: + commander: 11.1.0 + dotenv: 17.4.2 + eciesjs: 0.4.18 + execa: 5.1.1 + fdir: 6.5.0(picomatch@4.0.3) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.3 + which: 4.0.0 + yocto-spinner: 1.1.0 + + '@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)': + dependencies: + '@noble/ciphers': 1.3.0 + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -2229,12 +3443,13 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@fontsource-variable/geist@5.2.8': {} + '@hono/node-server@1.19.11(hono@4.12.8)': dependencies: hono: 4.12.8 - '@inquirer/ansi@1.0.2': - optional: true + '@inquirer/ansi@1.0.2': {} '@inquirer/confirm@5.1.21(@types/node@22.19.15)': dependencies: @@ -2242,7 +3457,6 @@ snapshots: '@inquirer/type': 3.0.10(@types/node@22.19.15) optionalDependencies: '@types/node': 22.19.15 - optional: true '@inquirer/core@10.3.2(@types/node@22.19.15)': dependencies: @@ -2256,15 +3470,12 @@ snapshots: yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 22.19.15 - optional: true - '@inquirer/figures@1.0.15': - optional: true + '@inquirer/figures@1.0.15': {} '@inquirer/type@3.0.10(@types/node@22.19.15)': optionalDependencies: '@types/node': 22.19.15 - optional: true '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -2285,6 +3496,28 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.8) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.8 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@mswjs/interceptors@0.41.3': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -2293,19 +3526,35 @@ snapshots: is-node-process: 1.2.0 outvariant: 1.4.3 strict-event-emitter: 0.5.1 - optional: true - '@open-draft/deferred-promise@2.2.0': - optional: true + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 outvariant: 1.4.3 - optional: true - '@open-draft/until@2.1.0': - optional: true + '@open-draft/until@2.1.0': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -2384,8 +3633,12 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true + '@sec-ant/readable-stream@0.4.1': {} + '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@tailwindcss/node@4.2.1': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2454,6 +3707,12 @@ snapshots: tailwindcss: 4.2.1 vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + '@ts-morph/common@0.27.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 10.2.5 + path-browserify: 1.0.1 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.2 @@ -2507,8 +3766,9 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/statuses@2.0.6': - optional: true + '@types/statuses@2.0.6': {} + + '@types/validate-npm-package-name@4.0.2': {} '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': dependencies: @@ -2569,26 +3829,56 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + agent-base@7.1.4: {} + agentkeepalive@4.6.0: dependencies: humanize-ms: 1.2.1 - ansi-regex@5.0.1: - optional: true + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - optional: true + + ansi-styles@6.2.3: {} argparse@2.0.1: {} assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + async@3.2.6: {} asynckit@0.4.0: {} + auto-bind@5.0.1: {} + autoprefixer@10.4.27(postcss@8.5.8): dependencies: browserslist: 4.28.1 @@ -2600,14 +3890,38 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.8: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.8 @@ -2616,6 +3930,12 @@ snapshots: node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -2623,6 +3943,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001780: {} @@ -2635,41 +3960,99 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@5.6.2: {} + check-error@2.1.3: {} - cli-width@4.1.0: - optional: true + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-boxes@4.0.1: {} + + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@6.0.0: + dependencies: + slice-ansi: 9.0.0 + string-width: 8.2.0 + + cli-width@4.1.0: {} cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - optional: true clsx@2.1.1: {} + code-block-writer@13.0.3: {} + + code-excerpt@4.0.0: + dependencies: + convert-to-spaces: 2.0.1 + color-convert@2.0.1: dependencies: color-name: 1.1.4 - optional: true - color-name@1.1.4: - optional: true + color-name@1.1.4: {} combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + commander@11.1.0: {} + commander@13.1.0: {} + commander@14.0.3: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} - cookie@1.1.1: - optional: true + convert-to-spaces@2.0.1: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.1(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -2680,20 +4063,41 @@ snapshots: css-what@6.2.2: {} + cssesc@3.0.0: {} + csstype@3.2.3: {} + data-uri-to-buffer@4.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 + dedent@1.7.2: {} + deep-eql@5.0.2: {} + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + delayed-stream@1.0.0: {} + depd@2.0.0: {} + detect-libc@2.1.2: {} diacritics@1.3.0: {} + diff@8.0.4: {} + dom-serializer@1.4.1: dependencies: domelementtype: 2.3.0 @@ -2718,20 +4122,34 @@ snapshots: dotenv@16.6.1: {} + dotenv@17.4.2: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + eciesjs@0.4.18: + dependencies: + '@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + + ee-first@1.1.1: {} + ejs@3.1.10: dependencies: jake: 10.9.4 electron-to-chromium@1.5.313: {} - emoji-regex@8.0.0: - optional: true + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} enhanced-resolve@5.20.1: dependencies: @@ -2742,6 +4160,10 @@ snapshots: entities@3.0.1: {} + env-paths@2.2.1: {} + + environment@1.1.0: {} + epub-gen-memory@1.1.2: dependencies: abort-controller: 3.0.0 @@ -2760,6 +4182,10 @@ snapshots: transitivePeerDependencies: - encoding + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2777,6 +4203,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-toolkit@1.45.1: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -2837,22 +4265,141 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + esprima@4.0.1: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + etag@1.8.1: {} + event-target-shim@5.0.1: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + expect-type@1.3.0: {} + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + filelist@1.0.6: dependencies: minimatch: 5.1.9 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + form-data-encoder@1.7.2: {} form-data@4.0.5: @@ -2868,17 +4415,34 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + fraction.js@5.3.4: {} + fresh@2.0.0: {} + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fsevents@2.3.3: optional: true function-bind@1.1.2: {} + fuzzysort@3.1.0: {} + gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: - optional: true + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: dependencies: @@ -2893,21 +4457,33 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-own-enumerable-keys@1.0.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-tsconfig@4.13.7: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + gopd@1.2.0: {} graceful-fs@4.2.11: {} - graphql@16.13.1: - optional: true + graphql@16.13.1: {} has-symbols@1.1.0: {} @@ -2919,8 +4495,7 @@ snapshots: dependencies: function-bind: 1.1.2 - headers-polyfill@4.0.3: - optional: true + headers-polyfill@4.0.3: {} hono@4.12.8: {} @@ -2931,24 +4506,153 @@ snapshots: domutils: 2.8.0 entities: 3.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@8.0.1: {} + humanize-ms@1.2.1: dependencies: ms: 2.1.3 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + immediate@3.0.6: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + indent-string@5.0.0: {} + inherits@2.0.4: {} - is-fullwidth-code-point@3.0.0: - optional: true + ink-testing-library@4.0.0(@types/react@19.2.14): + optionalDependencies: + '@types/react': 19.2.14 - is-node-process@1.2.0: - optional: true + ink-text-input@6.0.0(ink@7.0.0(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + chalk: 5.6.2 + ink: 7.0.0(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + type-fest: 4.41.0 + + ink@7.0.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@alcalzone/ansi-tokenize': 0.3.0 + ansi-escapes: 7.3.0 + ansi-styles: 6.2.3 + auto-bind: 5.0.1 + chalk: 5.6.2 + cli-boxes: 4.0.1 + cli-cursor: 4.0.0 + cli-truncate: 6.0.0 + code-excerpt: 4.0.0 + es-toolkit: 1.45.1 + indent-string: 5.0.0 + is-in-ci: 2.0.0 + patch-console: 2.0.0 + react: 19.2.4 + react-reconciler: 0.33.0(react@19.2.4) + scheduler: 0.27.0 + signal-exit: 3.0.7 + slice-ansi: 9.0.0 + stack-utils: 2.0.6 + string-width: 8.2.0 + terminal-size: 4.0.1 + type-fest: 5.5.0 + widest-line: 6.0.0 + wrap-ansi: 10.0.0 + ws: 8.20.0 + yoga-layout: 3.2.1 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ci@2.0.0: {} + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-node-process@1.2.0: {} + + is-number@7.0.0: {} is-obj@2.0.0: {} + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-regexp@3.1.0: {} + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@1.0.0: {} + isexe@2.0.0: {} + + isexe@3.1.5: {} + jake@10.9.4: dependencies: async: 3.2.6 @@ -2957,6 +4661,8 @@ snapshots: jiti@2.6.1: {} + jose@6.2.2: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -2967,13 +4673,25 @@ snapshots: jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-to-ts@3.1.1: dependencies: '@babel/runtime': 7.28.6 ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + json5@2.2.3: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -2981,6 +4699,10 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + kleur@3.0.3: {} + + kleur@4.1.5: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -3034,8 +4756,15 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 + lines-and-columns@1.2.4: {} + lodash.isequal@4.5.0: {} + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + loupe@3.2.1: {} lru-cache@5.1.1: @@ -3054,18 +4783,47 @@ snapshots: math-intrinsics@1.1.0: {} + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@2.6.0: {} + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@5.1.9: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + ms@2.1.3: {} msw@2.12.13(@types/node@22.19.15)(typescript@5.9.3): @@ -3085,33 +4843,79 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.1 - type-fest: 5.4.4 + type-fest: 5.5.0 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - '@types/node' - optional: true - mute-stream@2.0.0: - optional: true + mute-stream@2.0.0: {} nanoid@3.3.11: {} + negotiator@1.0.0: {} + node-domexception@1.0.0: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-releases@2.0.36: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - openai@4.104.0(zod@3.25.76): + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-treeify@1.1.33: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + openai@4.104.0(ws@8.20.0)(zod@3.25.76): dependencies: '@types/node': 18.19.130 '@types/node-fetch': 2.6.13 @@ -3121,12 +4925,24 @@ snapshots: formdata-node: 4.4.1 node-fetch: 2.7.0 optionalDependencies: + ws: 8.20.0 zod: 3.25.76 transitivePeerDependencies: - encoding - outvariant@1.4.3: - optional: true + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + outvariant@1.4.3: {} ow@0.28.2: dependencies: @@ -3138,8 +4954,32 @@ snapshots: pako@1.0.11: {} - path-to-regexp@6.3.0: - optional: true + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@4.0.0: {} + + parseurl@1.3.3: {} + + patch-console@2.0.0: {} + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.3.0: {} pathe@2.0.3: {} @@ -3147,8 +4987,17 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.3: {} + pkce-challenge@5.0.1: {} + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.5.8: @@ -3157,13 +5006,49 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + powershell-utils@0.1.0: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + process-nextick-args@2.0.1: {} + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 scheduler: 0.27.0 + react-reconciler@0.33.0(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + react-refresh@0.17.0: {} react@19.2.4: {} @@ -3178,15 +5063,37 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - require-directory@2.1.1: - optional: true + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} reselect@5.1.1: {} + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} - rettime@0.10.1: - optional: true + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rettime@0.10.1: {} + + reusify@1.1.0: {} rollup@4.59.0: dependencies: @@ -3219,48 +5126,209 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.1.2: {} + safer-buffer@2.1.2: {} + scheduler@0.27.0: {} semver@6.3.1: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + + shadcn@4.2.0(@types/node@22.19.15)(typescript@5.9.3): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@dotenvx/dotenvx': 1.61.0 + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@types/validate-npm-package-name': 4.0.2 + browserslist: 4.28.1 + commander: 14.0.3 + cosmiconfig: 9.0.1(typescript@5.9.3) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.4 + fuzzysort: 3.1.0 + https-proxy-agent: 7.0.6 + kleur: 4.1.5 + msw: 2.12.13(@types/node@22.19.15)(typescript@5.9.3) + node-fetch: 3.3.2 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + prompts: 2.4.2 + recast: 0.23.11 + stringify-object: 5.0.0 + tailwind-merge: 3.5.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - babel-plugin-macros + - supports-color + - typescript + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} - signal-exit@4.1.0: - optional: true + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slice-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 slugify@1.6.8: {} source-map-js@1.2.1: {} + source-map@0.6.1: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stackback@0.0.2: {} - statuses@2.0.2: - optional: true + statuses@2.0.2: {} std-env@3.10.0: {} - strict-event-emitter@0.5.1: - optional: true + stdin-discarder@0.2.2: {} + + strict-event-emitter@0.5.1: {} string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - optional: true + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - optional: true + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@4.0.0: {} strip-literal@3.1.0: dependencies: @@ -3268,8 +5336,7 @@ snapshots: tabbable@6.4.0: {} - tagged-tag@1.0.0: - optional: true + tagged-tag@1.0.0: {} tailwind-merge@3.5.0: {} @@ -3277,6 +5344,10 @@ snapshots: tapable@2.3.0: {} + terminal-size@4.0.1: {} + + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -3292,23 +5363,39 @@ snapshots: tinyspy@4.0.4: {} - tldts-core@7.0.26: - optional: true + tldts-core@7.0.26: {} tldts@7.0.26: dependencies: tldts-core: 7.0.26 - optional: true + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} tough-cookie@6.0.1: dependencies: tldts: 7.0.26 - optional: true tr46@0.0.3: {} ts-algebra@2.0.0: {} + ts-morph@26.0.0: + dependencies: + '@ts-morph/common': 0.27.0 + code-block-writer: 13.0.3 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + tsx@4.21.0: dependencies: esbuild: 0.27.3 @@ -3316,10 +5403,19 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-fest@5.4.4: + tw-animate-css@1.4.0: {} + + type-fest@4.41.0: {} + + type-fest@5.5.0: dependencies: tagged-tag: 1.0.0 - optional: true + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 typescript@5.9.3: {} @@ -3327,8 +5423,13 @@ snapshots: undici-types@6.21.0: {} - until-async@3.0.2: - optional: true + unicorn-magic@0.3.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + until-async@3.0.2: {} update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: @@ -3344,6 +5445,10 @@ snapshots: vali-date@1.0.0: {} + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): dependencies: cac: 6.7.14 @@ -3436,6 +5541,8 @@ snapshots: - tsx - yaml + web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} webidl-conversions@3.0.1: {} @@ -3445,32 +5552,55 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.5 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + widest-line@6.0.0: + dependencies: + string-width: 8.2.0 + + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.0 + strip-ansi: 7.2.0 + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - optional: true wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - optional: true - y18n@5.0.8: - optional: true + wrappy@1.0.2: {} + + ws@8.20.0: {} + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + y18n@5.0.8: {} yallist@3.1.1: {} - yargs-parser@21.1.1: - optional: true + yargs-parser@21.1.1: {} yargs@17.7.2: dependencies: @@ -3481,9 +5611,19 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - optional: true - yoctocolors-cjs@2.1.3: - optional: true + yocto-spinner@1.1.0: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.1.2: {} + + yoga-layout@3.2.1: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 zod@3.25.76: {}