English
Architecture
Uni-CLI is a local command runtime for operating real software. This page describes the runtime in v1.7.0. Its main abstraction is an operation with a name, arguments, target, effect, execution operator, and result contract.
Runtime flow
text
intent
→ compiled intent plan
→ feasible catalog candidates
→ ranked operations with evidence
→ operation contract
→ argument and permission checks
→ execution operator
→ AgentEnvelope
→ repair or next actionThe same operation can be discovered from the CLI, MCP, ACP, generated agent assets, and the documentation catalog. architecture tree and architecture audit inspect the nine executable control stages (intent, select, govern, act, observe, diagnose, repair-or-reroute, deliver, and expose). unicli search and unicli do rank catalog operations. unicli do remains plan-only so the caller selects and runs the next command explicitly.
Catalog
The catalog combines four sources.
- packaged YAML and TypeScript adapters
- fixed core commands
- user adapters under
~/.unicli/adapters/ - plugins and host-discovered external CLIs
unicli search compiles the task and ranks the catalog. unicli describe returns the selected operation's contract. User adapters can replace packaged entries with the same site and command during local development.
Intent compilation and ranking
Discovery compiles each request once before it reads posting lists. The plan keeps the remaining task text together with entity, cardinality, site, operation family, operator, browser-negation, and other hard requirements.
Exact site ids and maintained aliases resolve through hash indexes. Multi-word names resolve through bounded phrase matching. A precomputed symmetric-delete index proposes typo candidates, then bounded Damerau-Levenshtein distance accepts only one nearest provider. Catalog vocabulary remains ordinary query text, so a term such as computer keeps its meaning during typo resolution. Ambiguous spellings add no site constraint.
The bilingual inverted index combines BM25 and TF-IDF. Entity, workflow, operation-family, and feasibility signals refine the lexical candidate set. Hard capability requirements remove incompatible commands before bounded top-k selection. Every result includes ranking.lexical_score, ranking.semantic_score, ranking.prior, and named ranking.signals so an agent can inspect the selection basis.
Host-discovered macOS commands reuse the index derived from the current inventory snapshot. Inventory refresh, configuration changes, and explicit invalidation update the registry, affected compiled command keys, and derived index together. User-authored commands retain priority over host inventory in both runtime and manifest discovery. Resident CLI consumers and MCP share the inventory owner. Hardware and algorithm vocabulary resolves the unified ai commands directly during discovery.
Evidence retrieval
retrieval search selects registered read-only sources and dispatches them through the control kernel with bounded concurrency. Source metadata owns argument projection and result classification. Each result retains its source record, canonical URL, timestamps, and next-read command. Source failures include an exact retry command.
Reciprocal rank fusion counts the best rank for each distinct source and canonical result. Repeated rows from one source contribute once. The requested relevance or timestamp ordering is applied before bounded top-k selection. Equal ordering keys retain their first-seen order. Partial failures remain attached to usable results. An all-source failure retains provider causes and retry routes; successful sources with no matches produce empty_result.
The evidence reader follows redirects and records the original and final URLs. HTML cleanup selects one semantic content root for both Markdown and plain text. Turndown owns Markdown rendering, and html-to-text owns plain-text rendering so literal punctuation and code survive conversion. The evidence envelope owns output truncation and link resolution. Binary responses, PDFs, and anti-bot challenge responses fail closed or route through a declared artifact reader such as scholar-artifacts.read-pdf. GitHub issue and pull-request URLs route to the structured gh.issue-thread and gh.pr-thread readers.
AI primary-source intelligence combines three registry-backed layers in src/commands/ai.ts, src/commands/ai-landscape.ts, and src/commands/ai-content.ts. The first layer is a maintained primary-source directory for laboratories, hardware vendors, model hubs, runtimes, research venues, benchmarks, and embodied or world-model projects, exposed without network I/O by ai landscape. The second layer defines practitioner role profiles (ai profiles) that map daily concerns to source refs and query scopes. The third layer runs ai search, ai pulse, and ai read across compatible native adapters, including direct modelscope, opencsg, and bluesky APIs alongside openreview, openalex, crossref, acl-anthology, social, video, and community commands. Every invocation reads current upstream state and retains provider timestamps, provenance, partial failures, and authentication state. Authenticated sources remain opt-in (--include-auth). Exact vendor or entity queries select matched maintainer domains before profile fallback. Hosted papers, models, and datasets retain platform provenance under hosted-artifact and hosting_platform. Paper results set next_read to unicli scholar read.
Scholarly routing
scholar searchruns each source through its queryable search command (findScholarQueryableSearchCommandinsrc/commands/scholar.ts), so the arXiv source usesarxiv searchand its rows match the query.--yeartakes one year or an inclusive range such as2024-2026(src/adapters/_shared/year-range.ts). The arXiv, Crossref, OpenAlex, PubMed and Semantic Scholar searches apply the range in their own APIs. DBLP applies a single year.- An empty answer is a definite miss (
*_NOT_FOUND, exit 66) only when every source answered. When a source failed and no rows came back, the envelope code isSCHOLAR_SOURCES_UNAVAILABLEwith each source's state indetails.sources, exit 75 when the failure is retryable and 69 otherwise (exitScholarEmpty). - arXiv matches word stems, so
arxiv searchkeeps every row the API returned and ranks exact term and phrase matches first.--sortacceptsrelevance,newest(submission date) andupdated. - OpenReview (
api2.openreview.net) answers anonymousPOST /notes/searchrequests.GET /notes, forum pages and PDFs answer only sessions holding a clearance cookie or a login.openreview paper,venueandreviewsread notes through the search API first (byids, byvenueidorinvitation, and by the reply group<domain>/Submission<N>) and useGET /notesas the second route (readNotesinsrc/adapters/openreview/papers.ts). Venue listings include rejected and withdrawn submissions with the decision invenue.
Operation contract
An operation contract records the following fields.
- site, command, description, and argument schema
- target surface and compatible platforms
- execution operator and minimum capability
- operation family, effect, and idempotency
- authentication and interaction requirements
- adapter source and repair metadata
These fields are shared by discovery, dry-run, execution, permission checks, and protocol projections.
Control kernel
The control kernel validates input and coordinates policy before an operator acquires its target. It handles the following work.
- argument schemas and input channels
- permission profiles and approvals
- browser session and target ownership
- run recording and effect metadata
- error mapping and process status
The kernel returns one execution plan to the selected operator.
Execution operators
| Operator | Typical boundary |
|---|---|
structured-api | HTTP API or structured page endpoint |
browser-protocol | CDP or browser network protocol |
native-cli | Installed external command |
browser-semantic | DOM and accessibility actions in a browser |
desktop-accessibility | OS accessibility tree and native controls |
visual-observation | Screenshot or pixel observation |
visual-coordinate | Coordinate-based desktop action |
local-runtime | Files, local functions, and core services |
Adapter type, strategy, and operator describe different parts of the call. A browser adapter can expose a protocol operation; a web namespace can wrap a native CLI.
Adapter engine
YAML adapters describe a pipeline of registered actions such as fetch, transform, browser interaction, file download, and control flow. Transport-native actions add platform capabilities for visual and accessibility providers.
TypeScript adapters use the same metadata and envelope contract for SDKs, streaming services, and custom state machines.
Browser runtime
The Browser Runtime Broker owns providers, profile partitions, agent sessions, targets, and leases. Browser commands select one of these visibility modes.
- hidden managed browser
- background existing Chrome
- foreground existing Chrome
- configured remote provider
State reads produce refs tied to a snapshot and target. Later actions resolve those refs through the same session. Auto-start launches the broker from the compiled runtime-broker-main.js artifact in the installed distribution or repository build output (src/browser/runtime-launch.ts). Missing source-mode builds return a structured error with the npm run build recovery command.
- A managed browser runs inside the long-lived broker process, so the broker never reads the caller's environment.
createBrowserInvocationScopeputs the caller'sUNICLI_BROWSER_PROFILE_IDinto a managed, non-ephemeral scope when no explicit profile id is given, and the scope carries it to the broker with every request (src/browser/invocation-scope.ts). The same variable already selects the profile for cookie-backed YAML adapters, so one setting serves both adapter kinds. broker.statusreportspackage_version(src/browser/runtime-broker.tsandsrc/browser/runtime-protocol.ts).ensureBrokerreuses a running broker of the same version, or of any version with live sessions (src/browser/runtime-launch.ts). It retires an idle broker of another version and starts its own, because a broker left by an older install imports modules that the upgrade removed. A broker that reportsshutting_downis waited for (waitForBrokerExit) until it stops answering, and then a new broker starts.
Social post rows
twitter search,timeline,tweets,user-tweets,user-timeline,list-tweets,thread,likesandbookmarksshare the columns inTWEET_COLUMNS(id,author,text,created_atin ISO 8601,likes,retweets,views,url). The counts are integers. The browser path (buildTwitterTweetExtractionScriptinsrc/adapters/twitter/browser-extraction.ts) reads them from accessibility labels, which carry exact integers in every interface language; visible text such as45Kis rounded and follows the interface language. The GraphQL path (extractTweetinsrc/adapters/twitter/client.ts) convertslegacy.created_atand the view count, and reads the author fromuser.core.screen_name, where X moved it fromuser.legacyin 2026.- X removes posts that leave the viewport.
collectTweetstherefore extracts after every scroll, keeps posts by id, and stops when the requested number is reached or three scrolls in a row add nothing.--limitontwitter search, the timeline commands andlist-tweetshas no fixed maximum. - X rotates the GraphQL query ids of its web client.
resolveOperationinsrc/adapters/twitter/operations.tsreads the signed-inx.com/homepage with the twitter cookies, takes themain.<hash>.jsbundle and the inline webpack chunk maps (chunk id to name and chunk id to hash, served as<name>.<hash>a.js), and scans main first, then the chunks 16 at a time, forqueryId:"…",operationName:"<name>". The scan stops once the name is found. Every id found and every file read is cached for that main bundle in~/.unicli/cache/twitter-graphql-operations.json, so a later lookup costs one HTML request until X ships a new main bundle.twitterFetchandtwitterPostFetchresolve the id when the adapter passes an empty id or X answers 404, retry once, and keep the resolved id for the rest of the process. X answers 404 before it runs the operation, so a retried mutation runs once. A 400 that lists missing features (The following features cannot be null: a, b) is retried with those features set to true, at most twice. Each request sends the adapter's hard-coded id first, and that id serves as the fallback when resolution fails. twitter timelinereads the For You feed (HomeTimeline),timeline --followingthe chronological Following feed (HomeLatestTimeline), andlist-tweetsa list (ListLatestTweetsTimeline). All three follow the bottom cursor until--limitrows (default 20).--sincetakes an ISO 8601 date or datetime, where a bare date means UTC midnight. It drops older rows, and on the Following feed and lists it also ends paging at the first entry whose newest post is older. A conversation entry can hold older parent posts, so the test uses the newest post of the entry. Promoted posts are skipped (collectTimelineTweetsinsrc/adapters/twitter/client.ts).twitter followingtakes a handle (nameor@name, resolved throughUserByScreenName) or a numeric id.followingandlist-membersread 100 users per page up to--limit(default 20). Their rows carryUSER_COLUMNS(id,name,screen_name,followers,following,description) fromuser.core,user.relationship_countsanduser.profile_bio.twitter list-create <name> [--private] [--description <text>]returnsid,name,modeandurl(src/adapters/twitter/list.ts).list-addandlist-removetake--users a,@b,123,--users-file <path>with one handle or id per line, or both. They read the current members throughListMembers, look up handles that are not members, and sendListAddMemberorListRemoveMemberonly for users whose membership differs,--delayseconds apart (1.5 by default). After a 429 they wait forx-rate-limit-resetwhen the window resets within 15 minutes and try once more. Each row carriesuser,id,status(added,removed,already,not-foundorfailed) anderror, wherealreadymeans the user was in the requested state before the call. X attaches aDecodeExceptionerror string to successful mutation answers, so success is read fromdata.list.member_count.zhihu articleslists newest first by default (--sort created;--sort voteupsgives the most upvoted first) and each row carriesid,created_at,updated_at,url, vote and comment counts (src/adapters/zhihu/articles.yaml). It takes aurl_token;zhihu user(src/adapters/zhihu/user.yaml) returns thaturl_tokenand also accepts the hashed id thatzhihu searchreturns.
Desktop and visual runtime
compute routes actions to available providers on macOS, Windows, Linux, Electron, or an attached visual driver. Snapshot responses describe the selected provider and return refs for follow-up actions. Health commands report the setup supported by the current host.
Cua Driver owns native coordinate conversion and capture mapping. Uni-CLI forwards screenshot-pixel coordinates, capture identity, and the provider session recorded with the observation. Combined native capture binds the exact window identity across snapshot and screenshot (src/compute/capture.ts). Compute element refs persist in target-sharded atomic records under a recoverable file-store lock (src/transport/refs.ts and src/runtime/recoverable-file-lock.ts). Empty latest buckets publish tombstones so older refs remain retired. Driver idle cleanup owns the provider session lifetime. unicli doctor compute --providers --install uses the official installer for a missing driver and probes the installed contract again. The compute guide describes platform setup and observation lifetimes.
Result envelope
Every normal command returns a v2 AgentEnvelope. The outer fields remain stable across interfaces.
json
{
"ok": true,
"schema_version": "2",
"command": "site.command",
"meta": {},
"data": {},
"error": null
}Failed calls place their stable code, message, suggestion, and available repair data under error. The process exit code mirrors the broad failure class.
Repair
Adapter failures can expose adapter_path and step. The agent updates that owned source and runs unicli repair, which invokes the original target as the verifier. User adapters provide a local path for repairs under evaluation.
Harness evolution kernel
The evolution kernel turns selected run traces into a controlled adapter update. It owns five boundaries.
runs distillcreates a private evidence packet without replay arguments or secret event fields. Its provenance marks local trace content as untrusted and keeps raw traces behind local references.evolve adaptercopies the baseline and candidate into separate user-adapter overlays. A changed candidate must declare a hypothesis, expected fixes, and optional at-risk cases. The scope keeps identity, input and output contracts, pipeline actions, request methods and headers, and existing subprocess invocations fixed. New network origins require--allow-originat session creation.- Proposal runs, validation runs, and held-out runs remain disjoint. Eval files may label cases with
train,validation, orheld-out. - Baseline and candidate cases execute through the public CLI in alternating order. Every attempt preserves its exact candidate, patch, and report with content hashes. Rejected attempts remain available after a later candidate succeeds.
evolve adapter --candidate ... --promoteinstalls the candidate only after strict validation improvement and a held-out result without regressions. Promotion prepares the exact rollback artifact before installation. Promotion and rollback resume after an interrupted write.
The upstream Agent proposes and edits the candidate. Uni-CLI owns execution evidence and the promotion decision. Promotion reuses the latest eligible attempt when its candidate hash is unchanged, so review does not trigger another evaluation. Attempt commits, promotion, and rollback are serialized across Agent processes. Session format v2 verifies immutable artifacts and migrates the attempt history written by the development v1 format. Invalid sessions remain visible in evolve inspect. Version 1.2 limits editable evolution components to one YAML adapter per session. Mutating commands require an explicit evaluation opt-in.
Protocol projections
The native CLI exposes the complete runtime. MCP offers compact, deferred, and expanded tool profiles. Fixed core commands can appear in MCP discovery and return unsupported_surface with their native CLI invocation when called through unicli_run (src/mcp/handler.ts). ACP and other generated surfaces project selected operations from the same catalog. unicli mcp health reports the exact MCP profile and tool counts on the installed version.
Design decisions
Local diagnostics and release packaging enforce two additional runtime contracts.
- Local diagnostic logs write bounded owner-only JSONL under
~/.unicli/logs/events/and serialize reads, pruning, and appends with the shared recoverable file-store lock (src/runtime/local-event-log.tsandsrc/runtime/recoverable-file-lock.ts). Lock election uses hard-linked owner records, inode-checked dead-owner reclamation, and no-follow bounded reads. Process source identity (src/runtime/source-identity.ts) records the packagedbuild-identity.jsonartifact or verifies two agreeing worktree content passes before labeling a dirty digest. A failure before the first pipeline step carries step-1in its error envelope, and the local event log recordserror_steponly for steps from 0, so such a failure writes no validation warning (src/engine/kernel/execute.tsandsrc/runtime/cli-invocation-log.ts). - Packed releases enforce production dependency closure in
scripts/release-truth-check.ts. Every external package loaded through a literal non-testsrc/module specifier, includingimport,export,import = require(), dynamicimport(),createRequire, andrequire.resolvealiases (such as@xmldom/xmldominpubmedandrxiv), is declared inpackage.jsondependenciesoroptionalDependencieswith a non-devpackage-lock.jsonentry. The checker uses TypeScript symbol binding to exclude lexical shadows, type-only imports, Node built-ins, relative modules, and package self-imports. It also rejects mutable loader reassignments, root manifest-to-lock mismatches, and unbundled Windows process-owner sidecars.
Source map
| Area | Source |
|---|---|
| Catalog and registry | src/registry.ts, src/adapters/ |
| Intent compilation | src/discovery/intent-plan.ts |
| Catalog ranking | src/discovery/search.ts |
| Ranking semantics | src/discovery/intent-ranking.ts |
| Site identity resolution | src/discovery/site-resolver.ts |
| Capability feasibility | src/discovery/feasibility.ts |
| Engine and pipeline | src/engine/ |
| Harness evolution | src/engine/evolution/ |
| Command surface | src/commands/ |
| Browser runtime | src/browser/ |
| Desktop and visual control | src/compute/, src/transport/ |
| Shared contracts | src/types.ts, src/core/ |
| Protocol servers | src/mcp/, src/commands/acp.ts |