AppWeaver Agent Instructions
This repo uses a CLI-based tool system for AI agents.
How to call tools
Each plugin exposes tools via bash:
```bash
bun src/cli.ts
Draft system
All mutating operations (create, update, delete) return a draft for user review. The user accepts/revises/declines via bot DM commands shown in the tool output. Never retry a mutating tool if it returned a Draft ID.
Skills
Task-specific workflows live under .claude/skills/. Load a skill when its
description matches the task; do not read unrelated skills or recursively load
documentation inventories. Core and local working instructions live in
AGENTS.md; feature context lives in the nearest README and relevant local docs.
Each skill is a folder: .claude/skills/<skill_name>/SKILL.md (OpenCode-compatible YAML frontmatter with name matching <skill_name>).
Managed skills and the disabled-skill check
The skill-status skill is always available and lists every managed AppWeaver skill with its current enabled or disabled state for this workspace.
When the user asks about a skill that is not in your available skills, do not assume it does not exist. Check the skill-status skill — if the skill is listed there as disabled, warn the user that it exists but is currently disabled, and ask them to enable it with <prefix>skills set <name> enable or the skills manager, which can be opened from the footer joggler icon. Only the user can enable or disable skills — the AI must not change skill state on its own.
User intent comes first
Before changing files, running implementation commands, linting, or applying the post-edit workflow, infer the user's intent from the whole prompt and the current conversation.
- Treat direct requests to implement, fix, update, rename, remove, add, refactor, run, verify, or commit as change requests. For those, proceed with the normal code-editing workflow.
- Treat questions, debugging hypotheses, Q&A requests, brainstorming, product/branding discussion, design exploration, wording choices, and prompts like "can it be...", "what do you think...", "why...", or "let's figure out..." as discussion intent. Answer, investigate, or ask focused follow-up questions, but do not edit files or run implementation workflow until the user clearly asks for a change.
- If the prompt mixes discussion and possible implementation, prefer discussion first. State what you understand and ask one concise question, or offer the likely next change without applying it.
- Once the user confirms a concrete direction or asks for edits, switch to implementation mode and carry the change through verification as appropriate.
Keep implementation plans current
Local documentation and stable instructions
Read the nearest
README.mdand relevant localdocs/before editing a plugin or module. Keep implementation plans beside their owning feature.Update READMEs and docs when code changes responsibilities, public behavior, entrypoints, persistence, or architecture. Use relative Markdown links between module, plugin, and core documentation instead of duplicating shared contracts.
AGENTS.mdfiles are manually maintained working instructions. Only create or edit them when the user explicitly requests instruction changes; ordinary code edits must not rewrite agent policy.When a feature has an implementation plan, whether attached to its design document or in a separate file, treat that document as the durable progress record. Locate and read it before working on the feature.
Update the plan as work progresses, not just in a final progress report: check off completed items after implementation and required verification, leave unimplemented or unverified items unchecked, and split partially completed items so the remaining work is explicit.
If the user changes requirements or scope during the conversation, revise the plan at the same time as the implementation. Add new work, remove or mark superseded steps, and keep descriptions and exit criteria consistent with the agreed direction.
Before wrapping up, reconcile the plan against the actual files and state what remains. Do not rely on conversation summaries to preserve project status across sessions or context compaction.
Web command UI
Rich command output uses WebNodeRoot and optional per-render stylesheets (Shadow DOM). See docs/WEB_RENDERER.md (section “Scoped styles”).
Widget design defaults
- Build compact widgets by default. Avoid excessive padding, redundant wrappers, unnecessary borders, shadows, and rounded corners.
- Prefer alignment, typography, spacing, and subtle background changes over decorative containers.
- Reuse space where it improves clarity: make labels, timestamps, status text, and small metadata carry actions when appropriate instead of adding extra buttons.
- Use existing CSS variables via
var(...)for colors. Define scoped variables in the widget stylesheet when a new repeated color is needed, instead of hard-coding similar one-off mixes. - Keep row/list item styles dense and readable. Use alternating row backgrounds only when they are visibly distinct from the parent surface.
- Before doing substantial design coding, propose the visual structure briefly and ask the user to confirm or adjust it. Do this especially for new widgets or major layout changes.
Web UI style conventions
- The visual language is compact retro terminal UI: square corners, hard edges, dense rows, mono typography, limited shadows, black/yellow inputs, and subtle alignment over decorative cards.
- Modals should use the shared
.modal-backdrop,.modal,.modal-header,.modal-title, and.modal-bodystructure. Close buttons should use.close-btnwitharia-label="Close"and the✕glyph. - Regular app buttons should reuse existing button classes. For web command/dialog actions, prefer adding
.web-buttonalongside context classes when the control should match Web UI actions. - Text inputs in regular web UI should reuse the form input style beginning at
web/src/styles.cssaround.field-block input[type='text']: black background, warning-colored text, no border/radius, muted placeholder via--form-input-placeholder-color, and warning background on focus. Add focused selectors there for new dialog-specific input containers instead of creating separate input skins. - Retro checkboxes should use
.checkbox-retroor.web-checkbox.web-checkbox--retroas appropriate. They should be1remsquare, no box-shadow, dim warning background by default via--form-checkbox-warning-dim, brighten to--color-warningon hover/checked, and keep the small black checkmark. - Shadow-root plugin UI uses
web/src/webview/base-web-ui.css; avoid broad visual changes there unless plugin widgets need that change. If a style is only for the regular web app, put it inweb/src/styles.cssinstead. For values that must match in both light DOM and Shadow DOM, prefer shared CSS variables defined on:rootbecause they inherit into shadow hosts; duplicate only the necessary selectors.
No plugin code under src/ or web/
src/ (bot core, shared WebNode schema, Nostr, etc.) and web/ (the web app) must stay plugin-agnostic. Do not add imports from plugins/, command-plugin–specific types, WebElementTag / WebProps values, renderer branches, or comments that exist only to support one plugin. Plugin behavior belongs under plugins/ (renderers, adapters, definitions). The Web UI wire format is still JSON (WebNodeRoot / WebNode); the web app only implements generic tags and WebAction handling once. If a feature needs a new building block, add a reusable primitive in src/web/ui-schema.ts and the client renderer, not a one-off for a given plugin. See docs/WEB_RENDERER.md (e.g. “Scoped styles”) for the renderer model.
Tool permissions and workspace boundary
Active agent permissions
- Tool permissions are enforced by the active agent runtime.
- For OpenCode agents, the source of truth is
.opencode/agents. Follow the active agent profile there instead of adding an extra approval layer from this file. - Do not ask for approval just because a command is not listed in this document. If the active runtime permits the tool/command, you may run it.
- If the active runtime denies a tool/command, do not try to bypass it.
Long commands
- If the command is very long (e.g. a
catheredoc that writes a large file), summarize it without losing intent. For example writeCAT <new python script content>orWRITE <path> <description of content>instead of pasting the full content. The user must still understand what would run.
Fallback safe commands
If the active runtime does not provide more specific command permissions, these commands are considered safe to run without asking.
Package scripts (trusted by project config)
bun run <script>when<script>exists in the nearestpackage.jsonscriptsfield.bun testwhen backed by package scripts or default bun test behavior.bun run(list scripts only, read-only).
Before running a package script, the agent should:
- Read
package.json. - Verify the script name exists.
- Run only that script command (no extra shell chaining like
&&,;, or pipes unless user explicitly approves).
Common read-only commands
bun --versionls,pwd,catfor reading filesgit status,git diff,git log
Read-only plugin CLI calls
bun src/cli.tsto list plugins/tools.bun src/cli.ts <alias>to print a plugin schema.bun src/cli.ts <alias> list '<json>'for read-only list tools.bun src/cli.ts <alias> show '<json>'for read-only show tools.bun src/cli.ts <alias> context '<json>'for read-only context tools.- Other plugin CLI calls are allowed when the active runtime permits mutation-capable commands. Mutating plugin calls usually return drafts for user review; never retry a mutating tool if it returned a Draft ID.
Destructive and sensitive commands
- NEVER run destructive or sensitive commands (e.g.
rm -rf, overwriting credentials, changing system config) without explicit user approval. Evenrm -rfis allowed after the user approves. Do not run them until the user has confirmed.
Agent workspace boundary
- You may only create, edit, or delete files under the workspace (project root). The workspace is set when the agent is invoked (e.g. the parent project or the AppWeaver directory). NEVER modify files outside that tree.
Commits
Release preparation
Before preparing a release commit:
- Inspect both unstaged and staged changes with
git --no-pager diffandgit --no-pager diff --staged. - Summarize the scope, files, and behavior changed in plain language.
- Use one concise conventional commit subject that includes the appropriate version bump flag below.
Release hooks update the version, vX.Y.Z tag, and CHANGELOG.md. To regenerate the changelog from tags only, run bun run release:changelog.
Before making a commit, include a version bump flag in the commit message:
--patch— bug fixes, small improvements (e.g.fix: description --patch)--minor— new features, backward-compatible (e.g.feat: description --minor)--major— breaking changes (e.g.chore: breaking change --major)
Example: git commit -m "chore: remove unused file --patch"
See CONTRIBUTING.md for setup (hooks, semver) and more.
TypeScript: function parameters (3+ arguments)
When writing a function that takes more than 2 parameters, use a single object parameter and define a named type for its properties.
Pattern
type MyFunctionProps = {
foo: string;
bar: number;
baz: boolean | null;
};
function myFunction({ foo, bar, baz }: MyFunctionProps): ReturnType {
// ...
}
Requirements
- Do not use optional properties (
?in TypeScript) on the props type. - Do not use default parameters (e.g.
= null) for the object’s properties. - Callers must always pass every property. If a value is absent, the caller must explicitly pass
null(orundefinedwhere the type allows it).
This keeps call sites explicit and forces every caller to acknowledge every argument.
Example (from codebase)
type ParseModelProps = {
dmBotRoot: string;
mode: AgentMode;
modelOverride: string | null | undefined;
providerName: ProviderName | null;
};
function parseModel({ dmBotRoot, mode, modelOverride, providerName }: ParseModelProps): string {
// ...
}
Post-agent lint behavior (agent mode)
When the bot runs in agent mode after an implementation/change request:
- After each agent response, the bot runs
bun run lintfor the active workspace target (parentorappweaver). - The lint result is appended to the response sent to the user.
- If lint fails, the bot performs one additional agent round and sends lint output as feedback.
- The user receives the combined output after this lint step (and optional fix round).
Agent expectations
- Assume lint may run immediately after your response in
agentmode when you changed files. - If you receive a follow-up message prefixed with
[Post-edit lint feedback], treat it as authoritative runtime feedback and fix issues directly. - Provide a concise final summary after applying lint-driven fixes.
Testing preference
- Do not add unit, regression, integration, or end-to-end tests unless the user explicitly asks for them.
- Do not run test commands unless the user explicitly asks for them.
- A narrowly scoped one-off check, such as
bun -e '...', is allowed only when it directly validates the change and is more practical than manual inspection. Do not use it as a default verification step. - Do not try to smoke-test AppWeaver in a regular Chrome/browser session. Authenticated AppWeaver flows require the user's Nostr account through a browser extension or connected bunker. Only perform browser testing when the user explicitly confirms that an authenticated browser context is available; otherwise rely on static checks and ask the user to verify authenticated behavior.
AppWeaver Codebase Context
When editing or extending AppWeaver, use this as the map. AppWeaver is an open-source app hub for running AI-powered tools from a project or workspace folder the user controls. Installable apps like todos, bookmarks, scheduled jobs, file tools, browser actions, publishing, and more can be used through chat, the web UI, or AI prompts.
File map
| File | Purpose |
|---|---|
index.ts |
Main entry: env, SQLite, Nostr pool, subscription (kind 1059), command handling, agent backend dispatch, DM sending. Version computed at startup with git rev-parse HEAD from the project root. |
scripts/run-with-restart.ts |
bun run watch: runs the Bun bot + optional Vite (WATCH_WEB_UI=0 to disable). Restarts the bot only when restart.requested is created/touched — not on every code save. Use that file so agents and humans pick a deliberate restart point. |
package.json |
Scripts: start (bot), watch (bot + watcher above), web:dev / web:build, lint. Deps: nostr-tools, @types/bun. |
.env.example |
Template for BOT_KEY, BOT_MASTER_PUBKEY, BOT_RELAYS, BOT_OPENCODE_SERVE_URL, DEBUG. |
opencode.json |
OpenCode project config: defines ask, plan, build agents with per-agent models and permissions. |
State and persistence
- SQLite at
dm-bot.sqlite(same dir asindex.ts; filename kept for compatibility):seen_events(id)– event ids already processed (avoids duplicate on restart).sessions(id, created_at, backend)– agent session IDs with backend tag (legacy records may still havecursor).session_messages(session_id, role, content, created_at)– conversation history per session.state(key, value)– key/value; keys include:current_session_id– active session IDdefault_mode–ask|plan|agentagent_backend–opencode(legacy values normalize to OpenCode)reply_transport–remote|localworkspace_target–parent|appweaver
- Restart signal: file
restart.requestedin the project root. Create/touch it to restart the bot when usingwatch; the watcher removes it and restarts the process. There is no auto-restart on file edits — that is intentional.
Agent backends
OpenCode is the only active agent backend. createBackend() uses the OpenCode SDK and the managed runtime controller. Legacy backend names in saved state normalize to OpenCode; there is no backend-switching command. Workspace model selection uses the ai-model-source capability: core exposes the normal OpenCode catalog, and installed apps may register their own source. Use <prefix>ai source to list sources and <prefix>ai source core to select the core catalog. PPQ is an optional model-source app; Routstr has no model-source plugin yet.
The canonical normal OpenCode config lives at <workspace>/.appweaver/opencode.json; AppWeaver materializes the active runtime config at <workspace>/opencode.json. See docs/MODEL_SOURCES_AND_RUNTIME.md for the managed config and model-source design, and plugins/ppq/docs/PPQ_PLUGIN_DESIGN_AND_IMPLEMENTATION_PLAN.md for PPQ-specific implementation history.
ANSI colors (local terminal only)
Colors are applied for local terminal output and stripped (stripAnsi()) before sending over Nostr.
| Element | Color |
|---|---|
<ask> prefix |
cyan |
<plan> prefix |
yellow |
<agent> prefix |
green |
| Backend name | magenta |
[bot] / [sent] |
dim / blue |
| Errors | red |
| Token/cost footer | gray |
Where to change what
- New DM commands: Add command definitions under
src/commands/; routing uses the configurable DM prefix from core DB (default/). - Agent runtime:
src/backends/factory.tscreates the OpenCode SDK backend;src/backends/opencode-runtime-controller.tscoordinates config transitions and run admission. - Model sources:
src/core/model-source/implements the core OpenCode catalog and coordinates installedai-model-sourcecapability providers. - Workspace targeting + auto session reset:
<prefix>workspace [parent|appweaver]sets the active workspace target and auto-creates a new session on change. - Post-agent lint flow (agent mode): After an
agent-mode run, bot runsbun run lintfor the active workspace; on lint errors, performs one additional agent round with lint feedback. - Reply formatting / chunking:
chunkMessage(max length),modePrefix()(colored prefix),tokenFooter()(token/cost line). - DM relay discovery:
getMasterDmRelays(kind 10050) andPROFILE_RELAYS.sendDmuses these to decide where to publish.
After editing AppWeaver code
plugins/is ignored by the root Git repo because plugins are distributed as separate Git repos. Do not assume rootgit statusorgit diffwill show plugin edits or new plugin files. For plugin changes, inspect the plugin repo directly, e.g.git -C plugins/todo statusandgit -C plugins/todo diff, or use direct file reads/listing when verifying files.- Only follow this section after the user has made or confirmed an implementation/change request. Do not run this workflow for questions, brainstorming, Q&A, or discussion-only prompts.
- Use judgment when deciding whether to run verification. For additive or narrow changes, prefer targeted ESLint with fix enabled on the files you touched (for example
bunx eslint --fix path/to/file.ts path/to/other.ts) instead of repo-widebun run lint. Do not usebun run lint path/to/file.tsfor targeted linting because the package script still includes.and will lint the whole repo. The codebase is large, and while rapid development cycle, we may lose time on linting unrelated files over and over again. - Whenever you invoke ESLint directly, always pass
--fix(for example,bunx eslint --fix path/to/file.ts).bun run lintalready enables fixes. - Run repo-wide
bun run lintwhen the change is a broad refactor, changes shared types or conventions, affects formatting across many files, or is otherwise likely to surface project-wide issues. - For small, simple edits such as docs text, comments, or a narrow CSS variable/value change, lint is optional and can be skipped.
- In native CLI/OpenCode sessions, if the implementation changed any file under
src/orplugins/, create/touchrestart.requestedin the project root after lint/verification passes and the change is ready for the user to test. This is the deliberate bot reload signal forbun run watch; do not expect restarts on every save. Changes only underweb/do not needrestart.requestedbecause the Vite web dev server handles them without restarting the bot. AppWeaver in-app chat may add stricter instructions that forbid touching this file to avoid interrupting the active chat; follow those runtime-specific instructions when present.
Codebase vs agent workspace
- Agent backends are invoked with
cwdset to the project root or the AppWeaver directory depending on the workspace setting. You may create, edit, or delete files under that root. Do not modify files outside the project root.
Environment (runtime)
- Required:
BOT_KEY,BOT_MASTER_PUBKEY,BOT_RELAYS. - Optional:
BOT_PUBKEY,DEBUG=1,BOT_OPENCODE_SERVE_URL. - See
index.tstop comment and.env.example.