Imported from HenriGrimm/Minnow (
AGENTS.md). Install upstream withnpx skills add HenriGrimm/Minnow. Copyright stays with the author.
AGENTS.md
Guidance for AI coding agents (Cursor, Claude Code, etc.) working in the Minnow repo.
Overview
Minnow is a Vite + TypeScript SPA plus a Node tool server (server.js) and an Electron shell (Minnow Shell). It is a full agentic development workspace — editor, agents, terminal, git, issues, planning, knowledge, and local model hosting in one app — running on LM Studio and other OpenAI-compatible providers. The surfaces share one chat engine, one tool set, one session store and one workspace root; that shared spine is the product, so changes that break it matter more than they look.
Code is the product surface. Every other app supports work done there; when a change is ambiguous, favour the build loop over rail completeness. Product framing lives in PRODUCT.md.
- Four composer modes — General, Build, Plan (no-destructive guard), Debug — plus Orchestrate (opens the Boards surface from the sidebar, not a chat; not in the composer picker) and Onboarding as non-composer modes. Persisted
'desktop'and'email'chats remap to General vianormalizeModeId;'super-plan'remaps to'plan'while Super Plan is disabled for release (src/config/super-plan-enabled.ts) — the mode id stays valid in the persisted schema so old records load, andlistModes()filters it out. Defined insrc/chat/modes/registry.ts; prompts insrc/chat/prompts/modes/. Reef mode was removed (MIN-473) — do not reintroduce it. - Built-in tools (counts derive from the generated inventory) across web / utility / files / git / code / agents / browser / lsp (
src/tools/definitions.ts). Includesissue_*tools and the read-onlyminnow_docs_*tools (search the shipped user manual underdocumentation/manual/only). No entries carry anappId; the shipped catalog is inventoried from its registry. V1 board tools (board_initand siblings) were deleted in MIN-715. - Built-in slash skills (see the generated inventory): core helpers (
git-commit,code-review,fix-ci,ask-user,kickoff,plan-work,orchestrate-plan, …),impeccable(default-on),caveman,ui-designer,frontend-design,partymode,build-plugin. Third-party packs — including the Matt Pocock pack (19 skills) — install from Settings → Skills Library; nothing else is bundled. Seedocumentation/context.md§ Skills. - Minnow apps — released: Code (the primary surface and default after picking a project), Dashboard (
homeinternally), Source Control, Models, Brain, Scheduler, Issues, Settings — allcore(not user-disableable). Legacy#/app/chatredirects to#/app/code/chat. Hidden (releaseState: 'hidden'): Compare, Bench, Experts (MIN-471), and Research (disabled for release — broken) — code stays in tree but is omitted from dock, onboarding, Settings, routes, notifications, andlaunch_minnow_app. Research'sdeep-researchSettings section is omitted too, since its config has no other consumer. Registry:src/os/app-registry.ts. - Persistence lives under
~/.minnowwhen the tool server runs.
The authoritative reference is documentation/context.md — read it before touching unfamiliar subsystems. Product overview: README.md. Setup and scripts: documentation/contributor/setup-from-source.md and documentation/contributor/commands.md. Full doc index: documentation/.
Plugin packages: Settings → Plugins manages live API v1 packages (tools, encrypted connection fields, sandboxed panels, namespaced skills). Agent tooling: plugin_inspect, plugin_manage, /build-plugin. Native handlers are trusted local code in disposable workers, not an OS security sandbox. Contract: documentation/manual/plugins.md.
Running the app
npm startis the recommended dev command — Vite + the Node tool server on port 9473 (or next free port ifPORTis set) and launches the Electron desktop shell by default.MINNOW_BROWSER=1opens the system browser instead;BROWSER=noneorMINNOW_HEADLESS=1suppresses auto-open.npm run desktop/npm run electron:devare HMR-friendly Electron aliases.npm run devis Vite-only (no tool server) — fine for pure UI work, but most tool-dependent features won't function.- Headless CLI:
minnow run --prompt "…"(ornpm run minnow:run -- --prompt "…") drives the same generations + server tools without the SPA. Requiresnpm start(or--start-server). Seeminnow run --help. - Backup CLI:
minnow backup/minnow restorerun the backup engine against the Minnow home with no server (--helpon each). Test them withMINNOW_HOMEpointing at a scratch folder, never the live profile. - Health checks:
curl http://localhost:9473/api/tools/ping,/api/config/ping,/api/memory/ping,/api/brain/ping(substitute yourPORTif overridden). - LM Studio headless daemon (
llmster): install withcurl -fsSL https://lmstudio.ai/install.sh | bash;lms daemon up && lms server start;lms get <model> -y;lms load <model> -y. CLI at~/.lmstudio/bin/lms.
Testing
npm testruns the full suite viatest/run-all.mjs— auto-discoverstest/**/*.test.{js,mjs,mts,ts}and batches by runner (node --test,tsx+test/test-loader.mjs). New test files undertest/are picked up with zeropackage.jsonedits.npm run test:check-coveragefails if any discoverable test file is not covered (CI gate).- CI:
.github/workflows/ci.ymlon every PR + push tomain—npm ci,test:check-coverage,npx tsc --noEmit,npm test(Windows + Ubuntu + macOS), plus fast Ubuntu jobs product wiki (npm run test:product-wiki) and board scenario contract. Enable branch protection per.github/BRANCH_PROTECTION.md. npx tsc --noEmitfor type checking (no separate ESLint config).- Scoped suites:
npm run test:memory|brain|engine|lsp|mcp|browser|skills|attachments|research|benchmark|evals|webhooks|notifications|voice|servers|plugins|terminal-pty|ui-designer|scheduler|backup|onboarding|issues|orchestrator(test:boardaliasestest:orchestrator). Seepackage.jsonfor exact globs. Board testing: documentation/contributor/orchestrate-board-testing.md. - Many TS/UI suites run under
tsxwith--import ./test/test-loader.mjs(the loader stubs.cssand xterm). Some use--experimental-test-module-mocks.
Building & packaging
npm run build→tsc && vite build→dist/. Theprebuildstep generatessrc/skills/builtin-manifest.json.npm run package→ build +electron:build+electron-builder(Windows NSIS →release/).package:dirproduces an unpacked directory.- Headless runner bundle: an installed build ships neither tsx nor
src/**/*.ts, so Scheduler jobs there spawndist-headless/minnow-run.mjs—src/headless/cli-main.tsbundled bynpm run headless:build, which everypackage*script runs. Anythingsrc/headless/imports must bundle for plain Node (test/headless/bundle.test.mjs).
Performance budgets (MIN-400)
Perf regressions fail CI the same way as impeccable:detect — loudly and with numbers.
- Bundle ceilings live in
budgets.json(entry JS/CSS, largest lazy JS chunk, totaldist/assetsexcluding benchmark data packs). Afternpm run build, runnpm run check:performance-budgets(ornpm run report:bundle-size -- --check). Breaches print the chunk name and KB delta vs the limit and vsscripts/bundle-size-baseline.json. - Raising a budget requires editing
budgets.jsonin the same PR with a short rationale in the commit/PR body, then refresh the baseline:npm run build && node scripts/check-performance-budgets.mjs --update-baseline. - Startup is measured at
markAppReady()(src/boot/app-ready.ts); boot metrics surface in the dev console whenMINNOW_DEBUG=1, under Settings → About → Performance diagnostics, and in CI via the happy-dom harness (test/boot/boot-budget-ci.test.mts). - Long tasks (dev only): with
MINNOW_DEBUG=1, main-thread stalls ≥100ms log to the console (src/boot/long-task-observer.ts) — local only, no telemetry.
Key gotchas
postinstallrunsscripts/sync-impeccable-skill.mjs(vendors the Impeccable skill) andscripts/ensure-electron.mjs. Both are expected and idempotent.- Browser-only tools (
get_datetime,calculate,ask_question, sub-agent/board tools, mode handoff,browser_*) run client-side; calling them viaPOST /api/toolsreturns "Not implemented". browser_*automation requires the Electron shell and an origin allowlist; hidden in a plain browser tab.- The
[providers] fetch failedlog on startup is normal without LM Studio (provider discovery can't reachlocalhost:1234). - Streaming SSE is parsed in
src/api/sse-parse.ts(event boundaries + glued JSON); non-streaming fallback usesparseCompletionResponseBody— do not callResponse.json()on the generations shim. Some non–OpenAI providers (e.g.llmster) may still yield empty text; verify the provider'schatCompletionsPathis/v1/chat/completions. - Secrets are encrypted at rest with
~/.minnow/.key. Deleting/rotating the key makes existing encrypted secrets unrecoverable. - Backup catalog: a new top-level entry in
~/.minnowmust be added to a category inserver/backup/catalog.jsor to itsNEVER_BACKED_UPlist —test/backup/catalog.test.mjsfails otherwise. Anything encrypted under.keytravels only in passphrase-protected backups; never add a path that puts.keyor a decrypted secret into a plaintext one. A restore is staged, then swapped in at boot byapplyPendingRestore()before any store opens the home — never overwrite the home in place, and keepserver/backup/restore-apply.jsfree of store imports. - Path safety: file/git tools resolve under the workspace root unless
TOOLS_ALLOW_ALL_PATHS=1. - LAN access is opt-in (
Settings → General → Network accessorMINNOW_NETWORK=lan); default is loopback-only — restart after toggling. - Release gating: an app is only reachable when
releaseState: 'released'insrc/os/app-registry.ts. Tests and code for hidden apps (Compare, Bench, Experts) still run — don't delete them — but nothing user-facing may assume they are visible. New or unfinished apps shiphiddenfirst. - Plan mode denies mutating file edits and git writes; allows
save_file/make_directoryunderdocumentation/plans/only (seetool-groups.ts+plan-write-guard.ts). Shell/code-exec is allowed per the mode matrix (MIN-332).issue_*tools stay allowed in Plan; the default Planner work-agentallowedToolsmust include that group or send-path filtering strips them even when the mode matrix allows them. - GitHub in agent chats: when tools include
execute_command, the composed system prompt appendsgithub-cliguidance — use the localghCLI for this repo's PRs, issues, and CI; do not scrape github.com via browser or web-fetch tools (MIN-558).
Conventions
- Match the surrounding code's style, naming, and comment density.
- Application CSS uses
--mn-*tokens; hex/rgba literals live only insrc/styles/tokens.css. SeeDESIGN.md. - Keep
documentation/context.mdupdated when you ship a feature that changes architecture, APIs, or storage. - User-facing docs describe what ships.
documentation/manual/andREADME.mdcover the build a reader installed — no competitor comparisons, no "replaces X", nothing in the future tense. Direction goes indocumentation/ROADMAP.md, which is the only page allowed to discuss unshipped work. Release-gated apps are an implementation detail: keep them out of the manual and the README.
Cursor Cloud specific instructions
Dependencies are already installed by the startup update script (npm install). Notes below are non-obvious caveats for running/testing in the headless cloud VM; standard commands live in setup-from-source.md and commands.md.
- Headless startup: the VM has no display/Electron GUI. Start the stack with
MINNOW_HEADLESS=1 BROWSER=none npm startand open the SPA athttp://localhost:9473in the in-VM browser. Plainnpm starttries to launch the Electron desktop shell. - API auth gate: every
/api/*call needs the per-boot token —curl -H "X-Minnow-Token: $(cat ~/.minnow/session-token)" http://localhost:9473/api/tools/ping. A tokenless request returns401(the gate working), not a broken server. - Chatting without LM Studio: run
node scripts/fake-model-server.mjs --port 18765for an OpenAI-v1 stub. The reservedfake-boardprovider id is intentionally inert in the UI —proxyModelsinserver/providers/proxy.jsreturns an empty model list for it unless the in-server board-testing fake host is running (needsMINNOW_DEBUG=1). To get a UI-selectable model, register the stub under a different provider id and set it active:POST /api/providerswith{"id":"local-fake","label":"Local Fake (dev)","baseUrl":"http://127.0.0.1:18765","apiKind":"openai-v1"}, thenPOST /api/providers/local-fake/set-active. A general-mode chat then streams the stub's default reply (Done.). - Benign startup noise:
[servers] auto-provision searxng failed: tar: This does not look like a tar archiveis expected in the network-restricted VM and does not block the server. - Test suite reality:
npm testruns end to end, but a small number of tests currently fail onmainfor reasons unrelated to environment setup (in-code default/fixture spec drift — e.g.test/config/editor-ai-completion-meta.test.js, the fake LSP formatting fixture). Don't assume a fully green suite; scope expectations to the area you touch. - Fixture side effects: the scratch Minnow homes under
test/fixtures/(lsp-*-home,mcp-secrets-home) are runtime-generated — tests wipe and rebuild them each run, and they are gitignored (SQLite DBs, configs, diagnostics logs, secrets). No need to reset them before committing. If any other tracked fixture gets dirtied, reset withgit checkout -- test/fixtures/ && git clean -fd test/fixtures/.
