UNPKG

framework

Version:

The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.

282 lines 16.3 kB
import { type ClaudeCodeDriverOptions } from './driver/index.js'; import { type DriverName } from './driver-cli.js'; import type { ParsedPullRequest } from './turn-gate.js'; import { type FrameworkEvent } from './events.js'; import { type AgentKind } from './agent.js'; import { type AgentLocation } from './agent-location.js'; import { type HandoffLevel } from './handoff-level.js'; import { type AgentSpec } from './agent-spec.js'; import { type FrameworkFileConfig } from './config.js'; import { type ResolvedAgentConfig } from './config-layers.js'; import { type VersionFetcher } from './update-check.js'; import { AgentStore, type StoreFs } from './store/index.js'; import { type OnBeforeMergeableContext } from './on-before-mergeable-prompt.js'; /** * The default link shown for a live agent: the generic Claude Code entry point, * surfaced as "Open Claude Code" (not a per-agent live session). We drive Claude * Code headless, which is not Remote-Controlled, so there is no per-session deep * link to construct (#214); a cloud run reports its real link through its own * driver events instead. */ export declare const CLAUDE_CODE_SESSION_LIST = "https://claude.ai/code"; /** * The session link to show for an agent: the generic Claude Code entry point for * a live agent, nothing for a fake one (which has no real session). Pure, so the * default is unit-testable without a live agent. * * The default is Claude Code's *own* entry point, so it is only honest on a * Claude run: pointing a Codex session at claude.ai/code offers the user a link * to somewhere their run isn't. Codex keeps its sessions locally with nothing * equivalent to open, so another agent gets no default link at all (#542). */ export declare function chooseSessionLink(opts: Pick<AgentOptions, 'driver'>, fake: boolean): string | undefined; /** Where the CLI writes. Injectable so tests capture output. */ export interface CliIO { out: (line: string) => void; err: (line: string) => void; } export declare function frameworkVersion(): string; /** * One agent's resolved configuration, as it reads it itself (D4). * * These used to be command-line flags, which made every one of them a human surface as well as * the dashboard's process API. They arrive as a {@link AgentSpec} now — one JSON blob on a temp * file — so what is left here is a plain options object with no argv semantics: no tri-state * `--no-*` spellings, no mutual validation, no help text. */ export interface AgentOptions { /** The `FakeDriver` (`FRAMEWORK_FAKE=1`): an offline stand-in for the agent, for tests and e2e. */ fake: boolean; /** What the session was asked to do. */ intent: string; /** Which coding-agent CLI does the work (#542). Default `claude`. */ driver: DriverName; /** `--run-on <local|actions|web>` (#1050/#610): where the agent executes. `actions` drives it on a * GitHub Actions runner via ActionsDriver (#934); `web` hands it to a Claude Code cloud session * via CloudDriver (#610); absent / `local` runs on this device as before. */ target?: AgentLocation | undefined; cwd?: string | undefined; /** * `--run-id <id>` (#736): the id the daemon allocated for this agent before spawning it. Its * presence says the framework owns this agent's checkout — `--cwd` is a worktree the daemon * created on a `tf-agent-<id>` branch, which the agent renames once the agent names * the session. Absent for a plain `framework "..."`, which runs in the user's own checkout. */ agentId?: string | undefined; /** * `--continue-run` (#762): this agent continues the agent `--run-id` names rather than starting a new * one. The store reopens that agent's log instead of truncating it, so messaging a stopped agent * stays one row in the history. */ continueAgent?: boolean | undefined; model?: string | undefined; /** Continue a finished agent's agent session (#720) — the prompt resumes that conversation (full prior context). Set by the dashboard when you message an agent that has ended. */ resumeSession?: string | undefined; /** The `tickets/<file>.md` this agent is implementing (#1117). Set by the daemon when it starts a drain agent, from the ticket its queue entry links to; recorded on the agent's meta. */ ticket?: string | undefined; /** The {@link ticket} is being planned, not implemented (#1327), so the PR title must not inherit its issue as `(fix #42)` (#1334) — the plan's merge would close the issue with the work still undone. */ planAgent?: boolean; /** No human is watching (#846), so choice gates take the recommended option. */ unattended?: boolean; scope: 'prototype' | 'full'; /** * The mode toggles are tri-state (#841): `undefined` is "this agent said nothing", so the repo's * the-framework.yml decides, while an explicit `--no-*` turns the mode off over the file. */ /** Remove the built-in #326 system prompt entirely, keeping the session controls (#314). */ vanilla?: boolean | undefined; /** Transparent mode (#625): run the wrapped agent fully raw — no framework system prompt, emit * protocols, consumption guard, dashboard, or TODO loop, so an agent is identical to `claude -p`. */ transparent?: boolean | undefined; /** In-context directories, added as one `Context:` line (#439). */ context: string[]; /** Fire the built-in on-before-mergeable (#326) prompt when the agent signals setReadyForMerge(), queueing the quality follow-ups as TODO entries. */ onBeforeMergeable: boolean; /** Give the agent a real browser via chrome-devtools-mcp (navigate, console, network, DOM, screenshot) during the agent (#452). */ browser: boolean; /** * How far this session publishes itself when it finishes (#1102/#1216/B5). `undefined` is "this * session said nothing", so the repo's the-framework.yml decides, and nobody setting it resolves * to `pr` — which is what makes the handoff zero-config. */ handoff?: HandoffLevel | undefined; todoLoop: boolean; /** Whether the session records itself to `.the-framework/`. Always true outside tests. */ persist: boolean; /** {@link AgentSpec.kind} `research`: run the Research preset as a direct prompt (#331). */ research: boolean; /** {@link AgentSpec.kind} `prompt`: run one prompt verbatim through the direct path (#353). */ directPrompt: boolean; } /** What the CLI itself accepts: four options, no verbs (D4). */ export interface CliArgs { help: boolean; version: boolean; /** `--port <n>`: the port the dashboard binds. Default {@link DEFAULT_DAEMON_PORT}; `0` is ephemeral. */ port?: number; /** * `--host <addr>` (#1051): the dashboard's bind address. Default loopback; a non-loopback * address exposes it to the network and gates every route behind the generated shared token. */ host?: string; /** * `--agent <path>`: run the session described by the JSON spec at `path` (D4). The dashboard's * process API, not a human option — it is how one session is spawned, and the file is consumed. */ session?: string; error?: string; } /** * Parse argv (without the node/script prefix). Pure and testable. * * Four options and nothing else. Everything a session needs used to be a flag here — sixty-seven * of them, twenty-seven with no human user at all, because the dashboard serialized * `StartAgentOptions` onto a command line. Those travel as a {@link AgentSpec} now. `--host` and * `--port` survive because they are the two things a browser cannot be asked and a dashboard * cannot serve about itself; `--help` and `--version` because a command with options owes the * user both. */ export declare function parseArgs(argv: string[]): CliArgs; /** * Read a session's options off the spec the dashboard wrote (D4). * * The handoff pair and the mode toggles are left unset when the spec says nothing about them, on * purpose: that is what lets the repo's `the-framework.yml` decide, with nobody setting them * resolving to on (#1102/#841). JSON distinguishes "absent" from `false` without needing a * `--no-*` spelling for each, which is the whole reason this stopped being an argv. */ export declare function agentOptions(spec: AgentSpec, env?: NodeJS.ProcessEnv): AgentOptions; /** * Resolve the Claude Code driver options for a live session. A session is a headless autonomous * builder: every turn is `claude -p`, which cannot answer an interactive approval. The driver's * library default (`acceptEdits`) silently denies installs/builds/tests, so the production-grade * checklist can never verify the app actually builds/runs (#225). `bypassPermissions`, so the full * loop runs unattended. * * There used to be a `--permission-mode` and a `--dangerously-skip-permissions` to override it. * Neither had a dashboard control, so with the flags gone (D4) nothing sets them and the mode is * simply what it always resolved to. */ export declare function claudeDriverOptions(): ClaudeCodeDriverOptions; /** * The settings the picked driver cannot honor (#542), as lines to print at startup. * * A setting that silently does nothing is worse than one that errors. So the session says which * settings are not in force, rather than letting them imply they are. */ export declare function unguardedNotices(opts: Pick<AgentOptions, 'driver' | 'browser'>): string[]; /** Which flow an agent starts under (#1467), recorded on its meta: the direct paths are prompts, a * build agent is a build. Transparent (#625) routes a build-kind agent through the raw prompt path too, * so it records as a prompt. */ export declare function agentLogKind(opts: Pick<AgentOptions, 'directPrompt' | 'research'>, transparent?: boolean): AgentKind; type AgentConfigFlags = Pick<AgentOptions, 'vanilla' | 'transparent' | 'handoff'>; /** * Resolve an agent's config over its layers, nearest wins (#841): the agent's flags, then the repo's * `the-framework.yml`. #800 slots the project-user and global tiers in between and at the end. */ export declare function mergeAgentConfig(opts: AgentConfigFlags, file: FrameworkFileConfig): ResolvedAgentConfig; /** * The `framework` command. Wires the parsed options into {@link runFramework} * over a live dashboard + terminal narration, and resolves with an exit code. * Returns 0 on success, 1 on an agent error, 2 on a usage error. */ /** * Whether this agent can be steered over `.the-framework/control.jsonl` (#344): Stop, a choice pick, * a live message. True when its own dashboard is up (#427), or when whoever spawned it handed it a * agent id — the dashboard spawns each session with one in its spec, and steers it * from its own process. * * This used to have a third clause, "a daemon is alive somewhere on this machine", read from a * global state file. That file is gone with the background daemon (D4b), and it was never a fact * about *this* run in the first place: it went missing while the daemon was very much alive * (#922), and every Stop press then landed in control.jsonl and was read by nobody, in silence * (#905). An agent id holds when a file about another process does not. */ export declare function isSteerable(opts: { persist: boolean; agentId?: string | undefined; }): boolean; /** * Whether this session should stay open for the user's own messages once it settles (#714). * * The other half of #905. Being steerable only means someone *could* reach it; staying open means * a human is expected to keep talking to it. A headless agent is neither, but it used to inherit the * chat queue purely because a daemon happened to be alive elsewhere on the machine, and then * parked forever on a message nothing could send. #714 said as much: "headless / CI runs end when * done, exactly as today." * * So: the dashboard started it (an agent id) and therefore has a UI to carry on the conversation in. * Stop and gate picks keep working either way. */ export declare function isInteractive(opts: { agentId?: string | undefined; }): boolean; /** What the agent journal exposes to the epilogue. See {@link createAgentJournal}. */ export interface AgentJournal { onEvent: (event: FrameworkEvent) => void; /** The session name the agent chose via setSessionName() (#326), once it has. */ sessionName: () => string | undefined; /** The agent signalled setReadyForMerge() this agent (#326). */ sawReadyForMerge: () => boolean; /** The pull request the agent asked for via an `open-pr` block (#1567/#1618), if any. */ pullRequest: () => ParsedPullRequest | undefined; /** The agent stopped cleanly (user interrupt / budget cap #322) rather than failed. */ stoppedCleanly: () => boolean; /** Hold the browser preview's port until the session opens (#829/#813). */ announceBrowserPort: (port: number) => void; /** The page the browser preview is on (#1455 item 6b): emitted as a `browser` event once a * session is open, held until then, and re-said after every later `session` so the row * survives the dashboard's last-session slice. */ announceBrowserUrl: (url: string) => void; } /** * The agent's event sink and the state its epilogue reads. One event arrives and this prints it, * persists it, tracks the settle flags (#322/#326), renames the framework-owned branch once the * agent names its session (#736), and re-emits a held browser-stream port right after `session` so * it lands in the slice the dashboard renders (#829). These jobs sat inline in runCli across six * mutable locals; the journal is their one owner, and runCli reads the getters. Exported for the * rename-records-the-branch test (#1277). */ export declare function createAgentJournal(deps: { io: CliIO; cwd: string; store: AgentStore | undefined; agentId: string | undefined; }): AgentJournal; export declare function runCli(argv: string[], io?: CliIO): Promise<number>; /** * The startup footer every dashboard path prints (#312): where prompts come from, the version, * and then — once npm answers — whether that version is the latest. * * The update line is deliberately not awaited before the static lines. #312 asks for the static * info first, and the foreground path (bare `framework`) blocks on the server forever, so a line * printed after the await would never appear there at all. `checkForUpdate` is already forgiving: * offline or slow (2.5s cap) resolves to 'unknown', which prints nothing. */ export declare function printStartupFooter(io: CliIO, opts?: { fetchLatest?: VersionFetcher; }): Promise<void>; /** * The session spec a spawned on-before-mergeable child runs with (D4). Pure so a test can assert * it: note it carries **no** `onBeforeMergeable`, which is the recursion guard — a quality pass * must not trigger its own suite. */ export declare function promptAgentSpec(prompt: string, cwd: string, vanilla?: boolean): AgentSpec; /** How the on-before-mergeable prompt is spawned; injectable so tests observe it without spawning. */ export type PromptRunner = (prompt: string, cwd: string, binPath: string) => Promise<boolean>; /** * Fire the built-in on-before-mergeable (#326) prompt after an agent signalled setReadyForMerge(): one * `framework prompt` child on the same workspace that appends the quality follow-ups to the * session's TODO file, for the backlog loop (#323/#538) to pick up. * * It used to run maintainability, readability and security-audit inline instead, as three * child runs back to back (#556). Queueing is both what the doc says and the cheaper thing: * one short turn that writes a few TODO lines, rather than three full preset passes serialized * on the same git index. Best-effort, like the suite was: a failure is logged, never thrown. * * Returns how it went so the caller can emit it as an event (#835); the `io` lines stay for * a terminal agent, which is the one surface that can still read them. */ export declare function runOnBeforeMergeable(cwd: string, binPath: string, io: CliIO, tf: OnBeforeMergeableContext, agent?: PromptRunner, fs?: StoreFs): Promise<'queued' | 'incomplete'>; export {}; //# sourceMappingURL=cli.d.ts.map