UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

113 lines (112 loc) • 5.62 kB
import { type Target } from "./protocol.js"; import type { Entry } from "./transcripts.js"; /** * The reply being written. `delta` and `streamed` appear only on text built * from a harness's own deltas (Codex, OpenCode, phren's agent), never on text * read off a terminal: `delta` is what was appended since this socket's * previous preview frame (the whole text when the block is new to it). */ export interface TranscriptPreview { turnStartedAt: string; text: string; delta?: string; streamed?: true; } export declare const PREVIEW_INTERVAL_MS = 500; /** Only the last Claude reply after the current prompt is eligible. A missing * prompt anchor is deliberately silent: scrollback could belong to an old turn. */ export declare function claudePanePreview(rendered: string, prompt: string, previous?: string): string; /** An unbulleted tool-group summary line, e.g. " Read 1 file, ran 1 shell command". */ export declare function claudeToolSummary(line: string): boolean; /** * Claude Code's own screen text, never part of a reply: the update and * restart notices, status lines it marks ✔ / ✗ / ⚠ / ※, tips, and the * context and auto-accept hints drawn around the input box. */ export declare function claudeChrome(line: string): boolean; /** * Undoes the terminal's word wrap. Claude draws a paragraph wrapped to the * pane's width, so the phone would show each wrap as a hard break. A line is * joined to the next when the next line's first word would not have fitted * on it, which is exactly what a soft wrap looks like; the widest line * stands in for the pane width, and below 40 columns nothing is joined. Blank lines, block starts and fenced code * keep their breaks. `display` is what is joined, line for line: the text * with Markdown put back, measured by its plain twin. */ export declare function unwrapTerminalLines(lines: readonly string[], display?: readonly string[]): string; /** Claude's spinner line, as structured fields: the verb, the turn's elapsed * seconds, the token count and its direction, and whether it is thinking. * "✻ Whirlpooling… (27s · ↓ 2.3k tokens · thinking)". */ export interface ClaudeSpinner { verb: string; elapsed?: number; tokens?: { count: number; direction: "up" | "down"; }; thinking: boolean; /** "thought for 4s": how long the finished thinking took. */ thoughtFor?: number; } /** One spinner line, or undefined for anything else. The parenthesis must * start with a time or say "esc to interrupt", as Claude's does. */ export declare function parseClaudeSpinnerLine(line: string): ClaudeSpinner | undefined; /** The newest spinner line on screen. */ export declare function claudeSpinner(rendered: string): ClaudeSpinner | undefined; /** The word Claude's own spinner shows ("✻ Pondering… (12s · esc to interrupt)"). */ export declare function claudeSpinnerVerb(rendered: string): string | undefined; /** Copilot's reasoning state for the running turn, from the headers it draws * above each reasoning block ("⌄ Thought for 16s", "Thinking" while it * streams), newest below the turn's prompt line. Only the header is read: * the reasoning under it ("│ …") never leaves the Hook. */ export declare function copilotThinking(rendered: string): ClaudeSpinner | undefined; export declare function readPreviewPane(target: Target): Promise<string>; /** * One pane line as Markdown bold. Claude draws a reply's **bold** with SGR 1 * and drops the asterisks, so a plain read loses it and the phone's preview * would gain it only when the reply lands. Whitespace stays outside the * markers (Markdown does not close `**a **`); other styles are dropped. */ export declare function claudeBoldMarkdown(line: string): string; /** Older Codex rollouts may carry public text deltas between response items. * Keep a bounded raw cursor, since history intentionally filters these out. */ export declare class CodexRolloutPreview { private revision?; private cursor; private startedAt?; private text; read(file: string): Promise<TranscriptPreview | null>; } export declare function readDeltaPreview(target: Target, file?: string, rollout?: CodexRolloutPreview): Promise<TranscriptPreview | null>; /** Socket-local, ephemeral state. It never writes transcript rows or advances * the history cursor. Final rows bypass the text throttle and clear atomically. */ export declare class TranscriptPreviewStream { private readonly target; private readonly pane; private readonly delta; private startedAt?; private prompt; private landed; private ended; /** Claude's own spinner word for the running turn, sent beside frames as * `activityVerb` for older phones. */ verb: string | undefined; /** The whole spinner line for the running turn, sent as `activity`; * phones without it ignore the field. */ activity: ClaudeSpinner | undefined; private sentActivity; private lastRead; private lastSent; private current; private observedLine; private wasWorking; /** Replies that already landed as entries: a sidecar or thread a tick * behind them must not bring the same text back as a preview. */ private readonly completed; private readonly rollout; constructor(target: Target, pane?: () => Promise<string>, delta?: (file?: string) => Promise<TranscriptPreview | null>); observe(entries: Entry[], reset?: boolean): void; update(status: unknown, file?: string, now?: number): Promise<{ preview: TranscriptPreview | null; } | undefined>; }