Skip to content
nikcli
Docs
Documentation

Architecture

Source-oriented map of the nikcli monorepo, CLI lifecycle, server stack, session runtime, storage model, integrations, mobile host, and SDK boundary.

Overview

Forked from OpenCode

nikcli is a fork of OpenCode, the open source coding agent. The runtime boundaries described on this page — CLI lifecycle, instance model, server stack, session runtime, tool and permission layers — descend from OpenCode's design, and credit for them belongs to the upstream project and its contributors.

nikcli is a Bun monorepo centered on packages/nikcli. The CLI process initializes global paths and logging, resolves project instances, loads config through Effect layers, and then runs one of several front doors: the OpenTUI terminal app, one-shot commands, an HTTP server, workspace hosting, mobile hosting, companion UI, remote control, or automation flows. Most of those surfaces converge on the same session, provider, tool, permission, storage, and event services.

packages/nikcli

Single runtime core

CLI, TUI, server routes, mobile host, remote control, providers, tools, permissions, and storage live in the main package.

packages/sdk

Generated client

The web, TUI, mobile, and external clients use the SDK boundary instead of importing server internals.

TUI, HTTP, mobile, companion

Multiple shells

Different user surfaces route into the same Effect HttpApi server or the same session services.

Runtime state

Effect services

Config, project instance, sessions, storage, and provider services are assembled through Effect layers and per-instance context.

Read architecture from runtime edges

The fastest way to orient is to start at packages/nikcli/src/cli-main.ts, then follow the command into server/server.ts, cli/cmd/tui/thread.ts, session/index.ts, or the command-specific module. Those files reveal which shared services are actually on the hot path.

Package map

The repository is a Bun workspace. The most important boundary is that packages/nikcli owns the executable runtime, while sibling packages provide clients, UI shells, plugins, remote services, and companion surfaces.

packages/nikcli

Main CLI

Command registry, TUI, server app, session engine, provider registry, tools, permissions, config, storage, connectors, MCP, LSP, mobile host, and remote control.

packages/tui

Terminal UI

The whole OpenTUI/Solid renderer — app shell, routes, dialogs, contexts, feature plugins. It talks to the runtime only through the SDK.

packages/util

Shared utilities

Global paths, Flag, Log, filesystem helpers, and the primitives both the runtime and the TUI need.

packages/sdk

SDK

Generated JavaScript client and schemas for the server API. Regenerate it from the typed HttpApi with generate:httpapi-clients.

packages/plugin

Plugin system

Reusable plugin API surface and package-level plugin tooling.

packages/llm

LLM client

Route-based multi-provider client — protocols, providers, tool runtime — used for native provider traffic.

computer-use, browser-control, terminal-control

Automation engines

Sibling daemons with a shared CLI grammar and evidence-bundle format, driven by the computer and browser tools.

Terminal
bash
bun install
bun run typecheck
bun test
bun run --cwd packages/nikcli dev
bun run --cwd packages/nikcli generate:httpapi-clients

CLI lifecycle

packages/nikcli/src/index.ts is the process entrypoint, and it is deliberately tiny: it installs a document shim and hands off to src/cli-main.ts. Keeping the two apart is what lets the compiled binary re-enter itself as a --browser-control-daemon / --computer-use-daemon host without paying for the whole CLI module graph first.

cli-main.ts builds the yargs CLI, installs global error handlers, initializes global paths and logging, sets AGENT=1 and NIKCLI=1, registers commands, and forces process exit to avoid hanging subprocesses.

  1. Global initialization creates data, config, cache, state, log, bin, and repo paths.
  2. Logging setup respects --print-logs, --log-level, and local development defaults.
  3. Global flags are parsed before any command runs: --print-logs, --log-level, --island, and --auto (aliased as --yolo and --dangerously-skip-permissions, which set NIKCLI_AUTO_APPROVE so the TUI's worker thread sees it too).
  4. Command registration wires ACP, MCP, ads, TUI thread/attach, run, goal, analytics, generate, debug, auth, account, artifact, agent, upgrade, quickstart, doctor, uninstall, serve, workspace-serve, web, heap, models, locale, stats, export/import, GitHub, PR, session, image/speak/brain model pickers, remote, teleport, companion, mobile, routine, mission, usage, plugin, sync, connectors, and chatbot commands. --attach is also a flag on run.
  5. Failure formatting uses FormatError for known errors and points unknown errors at the log file.
  6. Process exit runs in finally to avoid hanging child processes, especially external MCP servers.
The default command is the TUI

Because TuiThreadCommand is registered as $0 [project], a bare nikcli command starts the TUI. One-shot agent execution goes through nikcli run.

Instance model

Runtime services are scoped to project instances rather than process globals wherever possible. Commands and server routes run inside an instance context that carries the active directory, worktree, config paths, and service layers. This is why the TUI worker can host a server, respond to SDK calls, and still dispose project state cleanly through Instance.disposeAll().

LayerResponsibilityWhy it matters
globalCross-project filesystem roots, cache versioning, data directories, log directories, and managed binary paths.Every process needs the same durable root model before config or storage can load.
project/instanceCurrent directory, worktree, lifecycle, and per-project state.Lets TUI, server, mobile, and workspace requests isolate project behavior.
Config.defaultLayerRemote, global, project, env, content, directory, mode, provider, command, agent, and plugin config merging.Prevents commands from manually reimplementing config precedence.
Database.defaultLayerThe shared nikcli.db SQLite client, migrations, and transactions.Unifies persistence for CLI, TUI, server, mobile, sessions, goals, and stats.
Session.defaultLayerSession lookup, creation, updates, messages, tool parts, goals, todos, summaries, and processing.Session behavior stays shared across local and HTTP surfaces.

Server stack

packages/nikcli/src/server/server.ts owns the Bun.serve listener and server/server-router.ts owns the request pipeline. Every declared route is a typed effect/unstable/httpapi group under server/httpapi/, composed into one API in httpapi/public.ts and served through HttpApiBridge. There is no Hono layer and no server/routes/ directory. See Server & API.

StageBehavior
Body limit + CORSOversized bodies are rejected before parsing; OPTIONS short-circuits with computed CORS headers.
Public shares/s/:id, /share/:id, and /api/share/:id answer before any auth check.
AuthAuth.authenticate() resolves one principal from mobile tokens, account tokens, Tailscale identity, or Basic Auth.
Global vs instanceisInstanceLessPath() sends /global, /user, /account down a branch that never binds a directory.
ContextEverything else resolves directory + workspace, proxies remote workspaces, and enters the instance context.
Raw handlersSSE feeds, prompt streaming, chatbot webhooks, and websocket upgrades claim their paths before the bridge, because a schema cannot hold an open stream.
HttpApi bridgeThe typed handler answers whenever HttpApiBridge.supports(path, method) matches.
FallbackUnmatched non-/mobile paths proxy to the hosted web app so it can be served from the same origin.
The bridge validates responses

Unlike the removed Hono handlers, the typed groups check response bodies at runtime. A response schema stricter than the real payload turns a working route into a 500, so new or tightened schemas need verifying against real data.

Session runtime

Sessions are the main execution unit. The session layer persists metadata, messages, message parts, diffs, summaries, todos, goals, and share state; coordinates agent execution; publishes events; and exposes both SDK routes and internal service calls. The one-shot command, TUI, mobile host, remote control, workspace hosting, and server API all converge here.

ConcernPrimary modulesNotes
Session metadatasession/index.tsCreate, update, delete, list, title, plan path, share lookup, and project-scoped session keys.
Messages and partssession/message-v2.ts, processorsMessage records and part records are normalized in JSON storage.
LLM request loopsession/llm.ts, session/llm/native-runtime.tsNormalizes prompts, selects provider/runtime, streams responses, handles tool calls, and repairs prompt-shape issues.
Goals and todossession/goal, todo servicesPersist planning state and task state alongside the transcript.
Sharing and remoteshare, remote, server routesExpose sessions to external clients while preserving session storage as the source of truth.

LLM and providers

Provider configuration is split from the session hot path. provider/provider.ts builds provider and model metadata from bundled SDK adapters, models.dev, config providers, saved auth, dynamic providers such as Ollama and nikcli inference, and plugin-supplied auth or model loaders. session/llm.ts is where requests are actually shaped and sent.

LayerResponsibility
Provider registryDiscovers providers, applies enabled/disabled filters, merges config, loads auth, filters deprecated or alpha models, and caches results.
Model selectionResolves provider/model ids from config, CLI args, dialogs, or session state.
Request shapingConverts stored messages into model-compatible message arrays and handles provider-specific prompt constraints.
Native runtimeUses @nikcli-ai/llm for supported providers when the native runtime path is available.
RefreshProvider.refresh() clears and rebuilds caches after auth or config changes.

Tools and permissions

Tools are available to agents through the tool registry and are guarded by the permission system where needed. Permissions are evaluated from rules, path patterns, and action defaults, with pending prompts published as bus events and persisted always-approvals stored under the project storage namespace. See Permission modes for the TUI presets (require_approval / approve_for_me / full_access / custom) that write the per-agent permission map.

AreaBehavior
Tool registryRegisters filesystem, shell, edit, search, web, MCP, LSP, and domain-specific tools according to config and feature flags.
Permission evaluationFinds the last matching allow, deny, or ask rule, expands home paths, and resolves action-specific policy.
Interactive asksPublishes pending permission requests and waits for a one-time, always, or reject reply.
PersistenceAlways approvals are stored as project-scoped records in the JSON storage layer under permission/:projectID.
LSP tool gateThe agent-facing lsp tool is available only when the experimental LSP tool flag is enabled.
Mode presetsThe TUI exposes three presets that write the agent's config.agent.<name>.permission map. The default is approve_for_me (allow trusted local work, ask for shell/web/external/sensitive reads).

Storage and state

All durable state lives in a single SQLite database, nikcli.db under Global.Path.data, reached through Drizzle. Each domain declares its tables in a *.sql.ts file, re-exported from src/database/schema.ts, and queries them through a repository namespace. See Storage.

One database, not a JSON store

The JSON object store under data/storage and the separate users.db / accounts.db / mobile_auth.db / workspaces.db files were both folded into nikcli.db. Migrations import the old data on first run.

AreaTablesDeclared in
Sessionssession_info, message_info, message_part, session_entry, session_diff, session_pending, todo_info, session_goalsrc/session/*.sql.ts
Projects & workspacesproject, workspacesrc/project, src/workspace
Automationloop, loop_run, mission, mission_exec, routine, background_run, monitorsrc/loop, src/mission, src/mobile, src/background, src/monitor
Identityusers, user_sessions, account, mobile_tokenssrc/user, src/account, src/mobile
Permissions & sharingpermission_ruleset, session_share, local_share, artifactsrc/permission, src/share, src/artifact
Syncsync_event, sync_sequence, sync_snapshot, sync_outboxsrc/sync

Things that stay files do so because a file is the interface: config (nikcli.json), provider credentials (auth.json), markdown plans, snapshots, skills, commands, agents, and themes.

Events and sync

The server publishes typed events through bus definitions and exposes event streams to SDK clients. The TUI consumes them through SDKProvider, batches deliveries, and updates Solid stores. Mobile and companion surfaces consume the same event model over server routes.

  1. Runtime module changes state, such as session update, message part append, permission request, or TUI command publish.
  2. Bus event is published with a typed schema and payload.
  3. Server event route streams it to SDK subscribers.
  4. Client sync provider handles it and updates route, transcript, provider, permission, toast, or workspace state.
  5. UI renders from synchronized state instead of manually polling each route.

Integrations

nikcli has three integration families: MCP servers, first-party connector registry entries, and plugins. MCP extends tool/resource/prompt surfaces. Connectors integrate named services and bot transports. Plugins can contribute auth, providers, commands, server hooks, and TUI routes.

IntegrationSource modulesRuntime role
MCPmcp/index.ts, mcp/auth.ts, HTTP API routesLocal stdio and remote Streamable HTTP/SSE MCP servers, OAuth, tools, prompts, and resources.
Connectorsconnectors/registry.ts, connectors/auth.ts, connectors/credentials.tsFigma, Slack, GitHub, Lovable, and bot connector metadata, operations, auth, and health checks.
Chatbotsserver/httpapi/chatbot.ts, chatbot command modulesDiscord, Slack, Teams, Google Chat, Linear, and GitHub webhook entrypoints.
Pluginsplugin/index.ts, plugin/install.ts, cli/cmd/plug.tsInternal plugins, config plugins, installable package plugins, server exports, and TUI exports.
LSPlsp/index.ts, lsp/server.ts, tool/lsp.tsOptional language-server clients and an experimental agent-facing code-intelligence tool.

Mobile and remote

Mobile hosting is a server route family, not the same feature as terminal remote control. The mobile host serves /mobile/* routes, validates bearer tokens from mobile_auth.db, streams sessions over SSE, handles permission replies, exposes routine and repository workflows, and remains compatible with hosted environments such as Railway when using the host path.

SurfaceEntryArchitecture note
TUI remote controlremote command and TUI slash commandsControls a running terminal session through server/TUI events and shared URLs.
Run against a servernikcli run --attach <url>One-shot agent execution attached to an existing nikcli server (no separate attach command).
Mobile hostmobile command and server/httpapi/mobile.tsHosts mobile app APIs, auth tokens, sessions, routines, GitHub-backed repos, notifications, and Live Activity flows.
Companioncompanion commandDesktop browser companion surface using the server and SDK event model.
Workspace serverworkspace-serve and workspace routesHosts workspace contexts, status, events, and session proxying.
Good mobile smoke test

POST /mobile/auth/token is a better first smoke than a deep bootstrap call because it proves route mounting and auth/token plumbing before requiring a full mobile session bootstrap.

SDK boundary

The generated JavaScript SDK is the contract between server routes and clients. The TUI imports @nikcli-ai/sdk/httpapi even when running in-process; it swaps only the fetch/event transport. Web, mobile, companion, and external clients use the same conceptual surface.

Server changeFollow-up
Change an endpoint in a server/httpapi/*.ts groupRegenerate the clients with bun run --cwd packages/nikcli generate:httpapi-clients.
Add or change typed HTTP API routesVerify route schemas, generated client methods, and any TUI/mobile callers.
Change event payloadsUpdate server event schema and every sync consumer that narrows on that event type.
Change auth behaviorVerify direct worker fetch, HTTP SDK clients, attach, mobile, companion, and remote control separately.

Operational map

Use this map to decide where to start for common architecture questions.

QuestionStart hereThen inspect
Why does bare nikcli open the terminal app?src/cli-main.tscli/cmd/tui/thread.ts, then packages/tui/src/app.tsx
Which route handles this API call?server/server-router.tsThe group under server/httpapi/, or a raw handler in server/public.ts / server/extra.ts
Why is config different in a worktree?config/config.tsconfig/paths.ts and environment flags
Why did a provider/model disappear?provider/provider.tsSaved auth, config provider entries, enabled/disabled filters, plugin auth/model hooks
Where is session content stored?database/schema.tssession/repo.ts, session/message-repo.ts, session/v2/
Why is a tool blocked?permission/next.tspermission/schema.ts, permission/evaluate.ts, the active permission mode (see Permission modes)
Why does mobile auth fail?server/httpapi/auth.tsmobile/auth.ts and the mobile_tokens table
Why does the TUI not receive updates?packages/tui/src/context/sdk.tsxcli/cmd/tui/worker.ts, GET /event, server auth

Source anchors

File or directoryWhat to inspect
packages/nikcli/src/index.ts, src/cli-main.tsProcess entrypoint and daemon re-entry split; then yargs setup, global flags, middleware, command registration, failure formatting, and forced exit.
@nikcli-ai/util/globalGlobal path model, data/cache/config/state/log/bin/repos roots, environment overrides, and initialization.
packages/nikcli/src/project/instance.tsPer-directory instance lifecycle and state scoping.
packages/nikcli/src/effect/Effect helpers, current instance context, and service layer execution helpers.
packages/nikcli/src/config/config.tsMain config merge order, env overrides, mode migration, provider/plugin/command/agent loading, and flag overrides.
packages/nikcli/src/server/server.ts, server/server-router.tsBun listener, port fallback, OpenAPI; then CORS, auth, the global/instance split, dispatch, and error mapping.
packages/nikcli/src/session/index.tsSession service, metadata persistence, updates, deletion, share lookup, and plan path selection.
packages/nikcli/src/session/llm.tsRuntime LLM request path, prompt normalization, provider selection, streaming, and tool call loop.
packages/nikcli/src/provider/provider.tsProvider registry, models.dev integration, config providers, saved auth, plugins, dynamic providers, and refresh.
packages/nikcli/src/toolAgent-facing tools, tool registration, and domain-specific tools.
packages/nikcli/src/permissionPermission schemas, evaluation, persisted approvals, and interactive request flow.
packages/nikcli/src/databaseSQLite client, PRAGMAs, transactions, post-commit effects, schema re-exports, and the migration list.
packages/nikcli/src/server/httpapi/mobile.tsMobile host API surface, auth token smoke path, sessions, routines, repos, permissions, and event streaming.
packages/nikcli/src/cli/cmd/tuiTerminal app command, worker transport, and the local plugin host — the runtime-side half of the TUI.
packages/tui/srcRenderer, app shell, routes, dialogs, contexts, feature plugins, and the SDK provider.
packages/nikcli/src/mcpMCP client lifecycle, OAuth, local/remote transports, tool/resource/prompt conversion, and auth storage.
packages/nikcli/src/connectorsConnector registry, credentials, auth storage, CLI commands, and bot integrations.
packages/nikcli/src/pluginInternal plugins, installable plugins, package exports, server/TUI targets, and default plugin behavior.
packages/nikcli/script/generate-httpapi-clients.tsSDK and Effect client generation from the typed HttpApi. packages/sdk/js/script/build.ts only bundles the package for publish.