import { type Registration } from "./registration.js"; import { type BaseLink } from "./links.js"; import type { RunnerAgent } from "./agent-runner.js"; import type { Store } from "./store.js"; /** What Staves can see about this project right now: the files that decide whether the server * starts, the boards behind them, and the work waiting on either side of the handoff. One shape, * so the person in the terminal and the agent over MCP are never told two different things. */ export interface StavesState { mode: "local" | "hosted"; root: string; registered: { claude: boolean; cursor: boolean; codex: boolean; }; skills: { claude: boolean; codex: boolean; }; instructionsCurrent: boolean; stalePackages: string[]; /** A credential on disk and a credential the account still honours are different things, and * they need different advice. `accountConnected` is `account === "connected"`, derived once. */ account: "connected" | "no-credential" | "unreachable"; /** What the account said instead of opening, when `account` is "unreachable". */ accountReason?: string; accountConnected: boolean; /** Whether this machine holds a Staves account credential at all, for any server — which this * project's own `account` cannot say, because an unregistered project points at nothing. It is * the difference between a first run and a second project on a machine already signed in. */ machineCredential?: boolean; /** What a hosted connection may do, as the account answers it now: read or contribute, and how * many boards are left to create. Undefined for a local project, and whenever the account could * not be asked — a connection nobody could reach is not a connection that refused. */ allowance?: { permission: "read" | "contribute"; remaining: number; }; agentsInstalled: RunnerAgent[]; /** Where these boards open, and whether anything is serving them. Every screen built from a * state can then hand over a link, rather than describing a board nobody can reach. */ base: BaseLink; boards: string[]; queued: { board: string; id: string; intent: "assess" | "discuss" | "implement"; }[]; failed: { board: string; id: string; note?: string; }[]; pendingProposals: { board: string; count: number; }[]; /** Where the MCP server would find its Langfuse credentials. `board-connected` is the awkward * case: a board names a Langfuse project and nothing anywhere holds the keys to read it. */ langfuse: "unset" | "shell-only" | "mcp-env" | "board-connected"; /** Whether staves.io already shows this hosted connection's runs, as the account answers it now. * Asked only when the coding agent has keys to hand over; undefined whenever it was not, or could not be, asked. */ hostedRuns?: boolean; /** Set when a source could not be read in time, or at all. What is above is what was readable; * it is never guessed at and never filled in from somewhere else. */ partial?: boolean; /** Why, in one clause, so a screen can say so rather than quietly understate the work waiting. */ partialReason?: string; } export interface Recommendation { audience: "human" | "agent" | "both"; priority: number; title: string; why: string; command?: string; tool?: string; url?: string; } /** A value, or the work of getting one. Opening an account or probing PATH can fail or hang, and * both belong inside the budget rather than in every caller's own try/catch. */ export type Source = T | (() => T | Promise); export interface CollectStateInput { /** The boards this project's registration points at. A hosted project whose account cannot be * opened must fail here: substituting the local `.staves` would report someone else's boards. */ store: Source; root: string; registrations: Registration[]; env: NodeJS.ProcessEnv; agents: Source; /** The instruction text a registration would write now. Without it currency cannot be judged, so * a managed block is taken at face value — an MCP server has no business regenerating the text. */ instructions?: string; /** Boards opened to find queued requests and proposals. Every board is named either way. */ boardLimit?: number; /** Where the boards open. A caller that already knows (the MCP server is serving them) says so; * otherwise src/links.ts resolveBase asks the daemon lock. Unreadable is not fatal: the base a * local project would use is still the truthful answer, with `live` false. */ base?: Source; } export interface CollectStateOptions { /** One wall-clock budget for the whole collection. `boardLimit` bounds how many reads happen; * only this bounds how long they take. */ signal?: AbortSignal; } /** What a person can ask their agent for once a board exists. The same list the connect screen * prints, so the two never drift into describing different products. */ export declare const ASK_FOR: string[]; export declare function collectState(input: CollectStateInput, options?: CollectStateOptions): Promise; /** The next move, from the state alone: no clock, no network, no opinion about who is asking. * The rules are in the order they are meant to be read, and the list is sorted by that order. */ export declare function recommend(state: StavesState): Recommendation[]; /** The one line that admits a screen is only part of the answer. Every terminal screen built from * a state prints it, so doctor and the home screen cannot say different things about the same gap. */ export declare function incompleteLine(state: StavesState): string[]; /** The heading above `rightNow`, which says so when what follows is only part of the answer. */ export declare function rightNowHeading(state: StavesState): string; /** How many boards are worth naming on one screen. Only the board lines are ever capped: what is * queued, what failed and what waits for a person are the reason to read this block at all. */ export interface RightNowOptions { boards?: number; } /** What is waiting, in as few lines as it takes. Empty categories are not lines. Each board is its * own line so it can carry the link that opens it: a name alone is not somewhere a person can go. */ export declare function rightNow(state: StavesState, options?: RightNowOptions): string[]; /** The same recommendations, addressed to whoever is reading. An agent gets tool names because * that is what it can act on; a person gets the command, because that is what they can run. */ export declare function formatRecommendations(all: Recommendation[], audience: "human" | "agent", limit?: number): string[]; /** The whole terminal home screen: where you are, what is set up, what is waiting, what to do. */ export declare function homeScreen(state: StavesState, setup: string[]): string;