diff --git a/.agents/skills/tuistory b/.agents/skills/tuistory new file mode 120000 index 0000000000..40ece4a678 --- /dev/null +++ b/.agents/skills/tuistory @@ -0,0 +1 @@ +../../.cline/skills/tuistory \ No newline at end of file diff --git a/.claude/skills/tuistory b/.claude/skills/tuistory new file mode 120000 index 0000000000..40ece4a678 --- /dev/null +++ b/.claude/skills/tuistory @@ -0,0 +1 @@ +../../.cline/skills/tuistory \ No newline at end of file diff --git a/.cline/skills/tuistory/SKILL.md b/.cline/skills/tuistory/SKILL.md new file mode 100644 index 0000000000..31eea9ca0c --- /dev/null +++ b/.cline/skills/tuistory/SKILL.md @@ -0,0 +1,107 @@ +--- +name: tuistory +description: | + Drive and test terminal apps (especially the Cline CLI TUI in apps/cli) through tuistory — named background PTY sessions that agents can read, wait on, snapshot, screenshot, and type into. Like Playwright/tmux for terminals, with reactive waiting instead of blind `sleep`. + + Use this skill when you need to: + - Manually test or reproduce bugs in the interactive Cline TUI (`bun run cli -i`) from a headless environment + - Run a dev server or any long-lived/interactive process in the background without hanging your tool call + - Write or extend Playwright-style e2e tests for the TUI (`bun run test:e2e:tuistory` in apps/cli) + - Capture text snapshots or styled PNG screenshots of a TUI screen as evidence +--- + +# tuistory + +[tuistory](https://github.com/remorses/tuistory) wraps any terminal command in a named background PTY session backed by a Ghostty terminal emulator. Agents interact with the session via short CLI calls that return instantly; humans can `tuistory attach` to the same session to watch or intervene. No real terminal or display (`DISPLAY`) is needed — it works fully headless, which makes it the preferred way for cloud agents to exercise the Cline TUI. + +It is installed as a devDependency of `@cline/cli`, so the pinned binary resolves when you run from `apps/cli`: + +```bash +cd apps/cli +bunx tuistory --help # source of truth for commands, options, and syntax +``` + +For full upstream docs: `curl -s https://raw.githubusercontent.com/remorses/tuistory/refs/heads/main/README.md` + +## Driving the Cline TUI headlessly + +Launch the TUI in an isolated environment so you don't touch real user config (`~/.cline`): + +```bash +cd apps/cli +DATA_DIR=$(mktemp -d) && HOME_DIR=$(mktemp -d) +bunx tuistory -s cline --cols 120 --rows 36 \ + --env HOME=$HOME_DIR --env CLINE_DATA_DIR=$DATA_DIR \ + --env CLINE_DISABLE_CLINE_PASS_NOTICE=1 --env CLINE_TELEMETRY_DISABLED=1 \ + -- bun src/index.ts --provider anthropic -m claude-sonnet-4-6 -k test-key +``` + +The dummy `-k test-key` renders the full chat UI; only an actual agent turn would fail. For recorded LLM turns, use the VCR cassettes described in `apps/cli/src/tests/helpers/env.ts` (`CLINE_VCR=playback` + `CLINE_VCR_CASSETTE`). Real turns need a provider credential (e.g. `ANTHROPIC_API_KEY`, `CLINE_API_KEY`). + +Then use an **observe → act → observe** loop: + +```bash +# Wait reactively for the chat view — never use sleep +bunx tuistory -s cline wait "What can I do for you?" --timeout 30000 + +# Act, then always observe the resulting screen state +bunx tuistory -s cline type "/settings" +bunx tuistory -s cline snapshot --trim +bunx tuistory -s cline press enter +bunx tuistory -s cline snapshot --trim + +# Styled PNG of the current screen (prints the file path) — good for artifacts +bunx tuistory -s cline screenshot + +# Full raw output stream (snapshot shows only the visible screen) +bunx tuistory read -s cline --all + +# Tear down a session YOU started (double Ctrl+C exits the TUI cleanly) +bunx tuistory -s cline press ctrl c +bunx tuistory -s cline press ctrl c +bunx tuistory -s cline close +``` + +## Background processes (instead of tmux) + +```bash +bunx tuistory -s my-server -- bun run dev:sidecar # returns immediately +bunx tuistory -s my-server wait "/listening|ready/i" --timeout 30000 +bunx tuistory read -s my-server # new output since last read +bunx tuistory -s my-server restart # after code changes +``` + +## Key rules + +- **Options before `--`, command after.** Everything after the first `--` is passed verbatim to the child: `tuistory -s name --cols 150 -- bun src/index.ts` is correct. +- **Snapshot after every action.** TUIs are stateful; dialogs and errors can render over the view you expect. `snapshot` reflects what the user actually sees (occluded text does not count), unlike grepping the raw stream. +- **Wait, never sleep.** `wait "text"` / `wait "/regex/i"` (case-sensitive by default) reacts as fast as the terminal updates; `wait-idle` when you don't know what to expect. Always pass `--timeout`. +- **Keys land instantly.** Unlike sleep-based scripts, a queued second keypress can leak into the next view (e.g. one Enter both accepts a slash completion and submits it). +- **Never close a session you didn't start.** Sessions are shared with humans (`tuistory attach -s name`) and other agents. Default to leaving sessions running; use `read`/`wait`/`snapshot` to inspect without disrupting. +- `--cols`/`--rows` affect TUI layout (assertions are width-sensitive); `--pixel-ratio 2` gives sharper screenshots. + +## Writing e2e tests with the library API + +`apps/cli/src/cli.tuistory.e2e.test.ts` (run: `bun run test:e2e:tuistory`) is the reference. The programmatic API runs in-process — no daemon: + +```ts +import { launchTerminal } from "tuistory"; + +const session = await launchTerminal({ + command: "bun", + args: ["src/index.ts", "--provider", "anthropic", "-k", "test-key"], + cwd: cliRoot, + env: isolatedEnv, // see createCliEnv() in the reference test + cols: 120, + rows: 36, + waitForDataTimeout: 30_000, // CLI cold start compiles a large TS graph +}); + +await session.waitForText("What can I do for you?", { timeout: 30_000 }); +const screen = await session.text({ trimEnd: true }); // emulated screen state +await session.type("/settings"); +await session.press("enter"); +session.close(); // always close in test teardown +``` + +Screen-state assertions can check that stale UI is *gone* (`expect(screen).not.toContain(...)`), which stream-grepping harnesses cannot. `session.text({ only: { bold: true } })` filters by style; `session.read()` returns the raw stream since the last read. diff --git a/apps/cli/DEVELOPMENT.md b/apps/cli/DEVELOPMENT.md index 4fde2d96bc..24ea3a7c82 100644 --- a/apps/cli/DEVELOPMENT.md +++ b/apps/cli/DEVELOPMENT.md @@ -339,6 +339,9 @@ bun run test:e2e:interactive # TUI-specific E2E tests (uses @microsoft/tui-test) bun run test:e2e:cli:tui +# TUI E2E tests driven through tuistory (PTY + Ghostty terminal emulator) +bun run test:e2e:tuistory + # Type checking bun run typecheck @@ -364,6 +367,34 @@ bun run dev -- --interactive --config /tmp/cline-test Or set `CLINE_FORCE_ONBOARDING=1` to force the onboarding view regardless of existing config. +### Manually testing the TUI (agents / headless environments) + +[tuistory](https://github.com/remorses/tuistory) is installed as a devDependency. It wraps the TUI in a named background PTY session that can be scripted from a plain shell — no real terminal or display needed. This is the preferred way for AI agents (or anyone in a headless environment) to poke at the interactive TUI: + +```bash +cd apps/cli + +# Launch the TUI in a background session +bunx tuistory -s cline --cols 120 --rows 36 -- bun src/index.ts --provider anthropic -m claude-sonnet-4-6 -k test-key + +# Wait reactively for the chat view (no sleep guessing) +bunx tuistory -s cline wait "What can I do for you?" --timeout 30000 + +# Interact and inspect +bunx tuistory -s cline type "/settings" +bunx tuistory -s cline press enter +bunx tuistory -s cline snapshot --trim # current screen as text +bunx tuistory -s cline screenshot # current screen as a styled PNG + +# A human can watch/drive the same session from another terminal +tuistory attach -s cline + +# Tear down +bunx tuistory -s cline close +``` + +The same engine powers the `test:e2e:tuistory` vitest suite (`src/cli.tuistory.e2e.test.ts`), which uses the programmatic `launchTerminal()` API for assertions against the emulated screen. + ### Adding a new TUI component 1. Create a `.tsx` file in `src/tui/components/` diff --git a/apps/cli/package.json b/apps/cli/package.json index 9da8d4c830..d32fff3f2e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -62,6 +62,7 @@ "test:unit": "vitest run --config vitest.config.ts", "test:e2e": "vitest run --config vitest.e2e.config.ts", "test:e2e:interactive": "vitest run --config vitest.interactive.e2e.config.ts", + "test:e2e:tuistory": "vitest run --config vitest.tuistory.e2e.config.ts", "test:watch": "vitest --config vitest.config.ts", "test:e2e:cli:tui": "cd src/tests && tui-test", "link": "bun unlink && bun link" @@ -99,8 +100,9 @@ "@cline/core": "workspace:*", "@cline/shared": "workspace:*", "@microsoft/tui-test": "^0.0.2", + "@types/bun": "^1.3.10", "@types/react": "19.2.14", - "vitest": "^4.0.18", - "@types/bun": "^1.3.10" + "tuistory": "^0.10.1", + "vitest": "^4.0.18" } } diff --git a/apps/cli/src/cli.tuistory.e2e.test.ts b/apps/cli/src/cli.tuistory.e2e.test.ts new file mode 100644 index 0000000000..d9bc193334 --- /dev/null +++ b/apps/cli/src/cli.tuistory.e2e.test.ts @@ -0,0 +1,205 @@ +// --------------------------------------------------------------------------- +// Proof-of-concept: driving the interactive TUI with tuistory +// (https://github.com/remorses/tuistory) instead of `script` + timed printf. +// +// Compare with `cli.interactive.e2e.test.ts`, which pipes keystrokes through +// the Unix `script` utility on a fixed sleep schedule and greps the raw +// output dump. Here each test launches the CLI in a real PTY backed by a +// Ghostty terminal emulator, waits reactively for screen content +// (`waitForText` resolves as soon as the text renders), and asserts against +// the emulated screen state rather than the raw byte stream. +// +// Run with: bun run test:e2e:tuistory +// --------------------------------------------------------------------------- + +import { mkdtempSync, rmSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { launchTerminal, type Session } from "tuistory"; +import { afterEach, describe, expect, it } from "vitest"; + +const cliRoot = path.resolve(__dirname, ".."); +const cliEntry = path.join(cliRoot, "src", "index.ts"); +const bunExec = process.env.BUN_EXEC_PATH ?? "bun"; + +const LAUNCH_TIMEOUT_MS = 30_000; +const UI_TIMEOUT_MS = 15_000; + +const tempDirs: string[] = []; +const sessions: Session[] = []; + +function createCliEnv(): Record { + const homeDir = mkdtempSync(path.join(os.tmpdir(), "cli-tuistory-home-")); + const dataDir = mkdtempSync(path.join(os.tmpdir(), "cli-tuistory-data-")); + const sessionDir = mkdtempSync( + path.join(os.tmpdir(), "cli-tuistory-sessions-"), + ); + const teamDir = mkdtempSync(path.join(os.tmpdir(), "cli-tuistory-teams-")); + tempDirs.push(homeDir, dataDir, sessionDir, teamDir); + + return { + HOME: homeDir, + CLINE_DATA_DIR: dataDir, + CLINE_DB_DATA_DIR: path.join(dataDir, "db"), + CLINE_SESSION_DATA_DIR: sessionDir, + CLINE_TEAM_DATA_DIR: teamDir, + CLINE_SESSION_BACKEND_MODE: "local", + CLINE_PROVIDER_SETTINGS_PATH: path.join( + dataDir, + "settings", + "providers.json", + ), + CLINE_HOOKS_LOG_PATH: path.join(dataDir, "logs", "hooks.jsonl"), + CLINE_TELEMETRY_DISABLED: "1", + CLINE_NO_AUTO_UPDATE: "1", + // Without this, the ClinePass promo dialog renders over the chat view. + // The stream-grepping interactive suite doesn't notice the overlay, but + // tuistory's screen snapshot reflects what the user actually sees. + CLINE_DISABLE_CLINE_PASS_NOTICE: "1", + // The parent vitest process sets CI/VITEST; clear them so the spawned + // CLI renders as a real interactive terminal. + CI: undefined, + VITEST: undefined, + }; +} + +async function launchCli(extraArgs: string[] = []): Promise { + const session = await launchTerminal({ + command: bunExec, + args: [ + cliEntry, + "--provider", + "anthropic", + "-m", + "claude-sonnet-4-6", + "-k", + "test-key", + ...extraArgs, + ], + cwd: cliRoot, + env: createCliEnv(), + cols: 120, + rows: 36, + // The CLI compiles a large TS graph on cold start; don't gate launch + // on the default 5s first-data timeout. + waitForDataTimeout: LAUNCH_TIMEOUT_MS, + }); + sessions.push(session); + return session; +} + +/** Wait for the chat view to be fully rendered. */ +async function waitForChatView(session: Session): Promise { + await session.waitForText("What can I do for you?", { + timeout: LAUNCH_TIMEOUT_MS, + }); +} + +describe("cli tuistory e2e", () => { + afterEach(async () => { + for (const session of sessions.splice(0)) { + try { + // Double Ctrl+C exits the TUI cleanly (first press shows the + // "press again to exit" hint) before the PTY is torn down. + await session.press(["ctrl", "c"]); + await session.press(["ctrl", "c"]); + await session.waitIdle({ timeout: 3_000 }); + } catch { + // Session may already be dead; close() below still cleans up. + } + session.close(); + } + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("shows the interactive chat view on launch", async () => { + const session = await launchCli(); + await waitForChatView(session); + + const screen = await session.text({ trimEnd: true }); + expect(screen).toContain("What can I do for you?"); + expect(screen).toContain("○ Plan ● Act (Tab)"); + expect(screen).toContain("Auto-approve all enabled (Shift+Tab)"); + }); + + it("toggles plan/act mode with Tab", async () => { + const session = await launchCli(); + await waitForChatView(session); + expect(await session.text()).toContain("○ Plan ● Act (Tab)"); + + await session.press("tab"); + // Reactive wait: resolves as soon as the toggled indicator renders. + await session.waitForText("● Plan ○ Act (Tab)", { + timeout: UI_TIMEOUT_MS, + }); + + // Unlike stream-grepping, the emulated screen reflects current state: + // the old indicator is gone, not just buried in scrollback. + const screen = await session.text(); + expect(screen).toContain("● Plan ○ Act (Tab)"); + expect(screen).not.toContain("○ Plan ● Act (Tab)"); + }); + + it("toggles auto-approve-all with Shift+Tab", async () => { + const session = await launchCli(); + await waitForChatView(session); + expect(await session.text()).toContain( + "Auto-approve all enabled (Shift+Tab)", + ); + + await session.press(["shift", "tab"]); + await session.waitForText("Auto-approve all disabled (Shift+Tab)", { + timeout: UI_TIMEOUT_MS, + }); + + const screen = await session.text(); + expect(screen).not.toContain("Auto-approve all enabled (Shift+Tab)"); + }); + + it("opens /settings, navigates tabs, and closes with Escape", async () => { + const session = await launchCli(); + await waitForChatView(session); + + await session.type("/settings"); + // Slash menu completion for the settings command. + await session.waitForText("Modify agent configuration", { + timeout: UI_TIMEOUT_MS, + }); + // A single Enter accepts the highlighted completion and submits it. + // (The `script`-based suite pressed Enter twice with 250ms sleeps; with + // reactive key delivery the second Enter would leak into the settings + // view and activate the focused row.) + await session.press("enter"); + await session.waitForText("←/→ switch tabs", { timeout: UI_TIMEOUT_MS }); + + const settingsScreen = await session.text(); + expect(settingsScreen).toContain("Settings"); + expect(settingsScreen).toContain("▸ Provider"); + + // Switch from the General tab to the MCP tab; the body swaps from the + // provider/model rows to MCP content. + await session.press("right"); + await session.text({ + waitFor: (text) => !text.includes("Compaction"), + timeout: UI_TIMEOUT_MS, + }); + + await session.press("escape"); + await session.waitForText("Use / for slash commands", { + timeout: UI_TIMEOUT_MS, + }); + expect(await session.text()).not.toContain("←/→ switch tabs"); + }); + + it("launches config view directly with `cline config`", async () => { + const session = await launchCli(["config"]); + await session.waitForText("←/→ switch tabs", { + timeout: LAUNCH_TIMEOUT_MS, + }); + const screen = await session.text(); + expect(screen).toContain("Settings"); + expect(screen).toContain("▸ Provider"); + }); +}); diff --git a/apps/cli/vitest.e2e.config.ts b/apps/cli/vitest.e2e.config.ts index f2329dd5c1..cd9b16e5ac 100644 --- a/apps/cli/vitest.e2e.config.ts +++ b/apps/cli/vitest.e2e.config.ts @@ -4,7 +4,10 @@ export default defineConfig({ test: { environment: "node", include: ["src/**/*.e2e.test.ts"], - exclude: ["src/**/*.interactive.e2e.test.ts"], + exclude: [ + "src/**/*.interactive.e2e.test.ts", + "src/**/*.tuistory.e2e.test.ts", + ], testTimeout: 60_000, hookTimeout: 60_000, }, diff --git a/apps/cli/vitest.tuistory.e2e.config.ts b/apps/cli/vitest.tuistory.e2e.config.ts new file mode 100644 index 0000000000..e6c0f46a8e --- /dev/null +++ b/apps/cli/vitest.tuistory.e2e.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.tuistory.e2e.test.ts"], + testTimeout: 60_000, + hookTimeout: 60_000, + }, +}); diff --git a/bun.lock b/bun.lock index c37e0c1e47..631e7517cf 100644 --- a/bun.lock +++ b/bun.lock @@ -58,6 +58,7 @@ "@microsoft/tui-test": "^0.0.2", "@types/bun": "^1.3.10", "@types/react": "19.2.14", + "tuistory": "^0.10.1", "vitest": "^4.0.18", }, }, @@ -354,7 +355,7 @@ }, "apps/vscode": { "name": "claude-dev", - "version": "4.0.0", + "version": "4.1.2", "dependencies": { "@anthropic-ai/sdk": "^0.37.0", "@bufbuild/protobuf": "^2.2.5", @@ -1477,6 +1478,8 @@ "@hono/node-server": ["@hono/node-server@1.19.15", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg=="], + "@hono/node-ws": ["@hono/node-ws@1.3.1", "", { "dependencies": { "ws": "^8.17.0" }, "peerDependencies": { "@hono/node-server": "^1.19.11", "hono": "^4.6.0" } }, "sha512-vo/MwCnpJAVHBkGzWjCJ28wF45fYHAfbPZcH2rodZODHtch2GHA94KtMfusmVycTUtsLAsaNsHhtY6P8X3RQsA=="], + "@hookform/resolvers": ["@hookform/resolvers@3.10.0", "", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="], "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], @@ -2057,6 +2060,8 @@ "@react-types/tooltip": ["@react-types/tooltip@3.5.2", "", { "dependencies": { "@react-types/overlays": "^3.9.4", "@react-types/shared": "^3.33.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-FvSuZ2WP08NEWefrpCdBYpEEZh/5TvqvGjq0wqGzWg2OPwpc14HjD8aE7I3MOuylXkD4MSlMjl7J4DlvlcCs3Q=="], + "@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.6.2", "", {}, "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw=="], + "@rive-app/react-webgl2": ["@rive-app/react-webgl2@4.29.5", "", { "dependencies": { "@rive-app/webgl2": "2.38.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0" } }, "sha512-yiUT1UwWbj4ImP40uHH2UUes6cfSfZNohec2QefWwsJdTJ6s+kB9Ht3ruFjdsKnol/H41qnPcpsSURu6aHQGaQ=="], "@rive-app/webgl2": ["@rive-app/webgl2@2.38.5", "", {}, "sha512-CS0nuUZ0B1fXXk9uOQGGW4aU4qJSVs7uHvHVIkBsKV9QRGzFRlehssQ+znTfocBxbV6D1KCGKSR0uTvsbdINiQ=="], @@ -2939,7 +2944,7 @@ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], @@ -3147,6 +3152,8 @@ "default-shell": ["default-shell@2.2.0", "", {}, "sha512-sPpMZcVhRQ0nEMDtuMJ+RtCxt7iHPAMBU+I4tAlo5dU1sjRpNax0crj6nR3qKpvVnckaQ9U38enXcwW9nZJeCw=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], @@ -3251,6 +3258,8 @@ "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], + "errore": ["errore@0.11.0", "", { "bin": { "errore": "dist/cli.js" } }, "sha512-/uJh8o4SYfJAPGSDynpLgKRuRWX5yTSP2BXspHVQu8XmwaX1d6ysxr1cBhjTzC1Um2Xov9BQJ2kigT9lvxHYaA=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -3467,8 +3476,12 @@ "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + "get-them-args": ["get-them-args@1.3.2", "", {}, "sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "ghostty-opentui": ["ghostty-opentui@1.5.0", "", { "dependencies": { "@resvg/resvg-wasm": "^2.6.2", "strip-ansi": "^7.1.2", "wcwidth": "^1.0.1" }, "peerDependencies": { "@opentui/core": "*" }, "optionalPeers": ["@opentui/core"] }, "sha512-1Kux7BjVtCevjz6Y/tsNPahXmEzJwAc3MqbmsX8chO6CybDG8YOna3gVbQuOgBRte4/CxOtGYJ9rgAKpqGKFPA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -3479,6 +3492,8 @@ "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], + "goke": ["goke@6.14.1", "", {}, "sha512-ttrqT/tfynw+0AnV7+0GZQomYr2/mlQ+UCmXL2jJEF6GNXSVPXLOvTVnyqTP9xKbW9oTsrLkAXmr42hQEKZ20Q=="], + "google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -3773,6 +3788,8 @@ "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + "kill-port-process": ["kill-port-process@4.0.2", "", { "dependencies": { "get-them-args": "1.3.2", "pid-port": "2.0.1" }, "bin": { "kill-port": "dist/bin/kill-port-process.js" } }, "sha512-fO8gc45EYJQUQWozPBmdTpsR0GDvldsmrhP2I4FPoNejwyBY4Liiwj9Is7P/5rj6k07ZQ5Ob0g0k2dqQcslW/w=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], @@ -4261,6 +4278,8 @@ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + "pid-port": ["pid-port@2.0.1", "", { "dependencies": { "execa": "^9.6.0" } }, "sha512-pnLo01AmMclw8l+/gfknsP2N351oe8VkVmCLFUvJZ11NRPPmghJrv0OcwsdgPQxsZkFYwm6hPWW0JKmXYCaXAw=="], + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], @@ -4661,6 +4680,8 @@ "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + "string-dedent": ["string-dedent@3.0.2", "", {}, "sha512-M4q+HpHCtGXlbyzYDOcOo7V185dlq6YXvGUPcWZqL4vttCX9gFYoWIOxcPd7v5CAYcTJsGLs3ZJCAH2TXONF/g=="], + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -4821,6 +4842,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tuistory": ["tuistory@0.10.1", "", { "dependencies": { "@clack/prompts": "^1.2.0", "@hono/node-server": "^1.19.9", "@hono/node-ws": "^1.3.0", "@opentui/core": "^0.2.12", "@opentui/react": "^0.2.12", "errore": "^0.11.0", "ghostty-opentui": "^1.5.0", "goke": "^6.12.1", "hono": "^4.11.7", "kill-port-process": "^4.0.2", "picocolors": "^1.1.1", "react": "^19", "std-env": "^4.1.0", "string-dedent": "^3.0.1", "zod": "4.3.6" }, "optionalDependencies": { "zigpty": "^0.2.0" }, "bin": { "tuistory": "dist/cli.js" } }, "sha512-+dtDUSeiN5FOpqJzVjTQWqtnwKsOzg0CBMo5fXPWwC8Er+jLwMm2g4X/LKG2HyAn0/gECnDareRT9dOz0XfvAQ=="], + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], @@ -4949,6 +4972,8 @@ "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], @@ -5037,6 +5062,8 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "zigpty": ["zigpty@0.2.1", "", {}, "sha512-MR9JqJx2wf5f4wz8zpx050AlqrmWeIW+1h0SO5iEyhG3HFRjY5luC3szS2ux2EGuPjE5OU9ZAuiCBeMWBTrqZw=="], + "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], @@ -5905,6 +5932,8 @@ "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "node-cache/clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], @@ -6129,6 +6158,8 @@ "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], + "tuistory/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "unbzip2-stream/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],