This file is for agentic coding tools. It's a map β read linked docs for depth.
Nexu is a desktop-first OpenClaw platform. Users create AI bots, connect them to Slack, and the local controller generates OpenClaw config for the embedded runtime.
- Monorepo: pnpm workspaces
apps/controllerβ Single-user local control plane for Nexu config, OpenClaw sync, and runtime orchestrationapps/desktopβ Electron desktop runtime shell and sidecar orchestratorapps/webβ React + Ant Design + Viteopenclaw-runtimeβ Repo-local packaged OpenClaw runtime for local dev and desktop packaging; replaces globalopenclawCLIpackages/sharedβ Shared Zod schemas
Nexu is a desktop-first OpenClaw product. Users create AI bots via a dashboard and connect them to Slack. The system dynamically generates OpenClaw configuration and hot-loads it into the local runtime managed by the controller.
All commands use pnpm. Target a single app with pnpm --filter <package>.
pnpm install # Install
pnpm dev # Local controller-first web stack (Controller + Web)
pnpm dev:controller # Controller only
pnpm start # Build and launch the desktop local runtime stack
pnpm stop # Stop the desktop local runtime stack
pnpm restart # Restart the desktop local runtime stack
pnpm reset-state # Stop desktop runtime and delete repo-local desktop state
pnpm status # Show desktop local runtime status
pnpm dist:mac # Build signed macOS desktop distributables
pnpm dist:mac:unsigned # Build unsigned macOS desktop distributables
pnpm probe:slack prepare # Launch Chrome Canary with the dedicated Slack probe profile
pnpm probe:slack run # Run the local Slack reply smoke probe against an authenticated DM
pnpm --filter @nexu/web dev # Web only
pnpm build # Build all
pnpm check:esm-imports # Scan built dist for extensionless relative ESM specifiers
pnpm typecheck # Typecheck all
pnpm lint # Biome lint
pnpm format # Biome format
pnpm test # Vitest
pnpm generate-types # OpenAPI spec β frontend SDKAfter API route/schema changes: pnpm generate-types then pnpm typecheck.
This repo is desktop-first. Prefer the controller-first path and remove or ignore legacy API/gateway/container-era assets when encountered.
mainis the integration branch and should stay releasable.- Do feature work on short-lived branches named with a clear prefix such as
feat/...,fix/..., orchore/.... - Prefer merging the latest
maininto long-running feature branches instead of rewriting shared history once a PR is under review. - After a PR merges, sync local
main, then delete the merged feature branch locally and remotely when it is no longer needed.
- Use
pnpm installfirst, thenpnpm start/pnpm stop/pnpm restart/pnpm statusas the standard local desktop workflow. - The repo also includes a local Slack reply smoke probe at
scripts/probe/slack-reply-probe.mjs(pnpm probe:slack prepare/pnpm probe:slack run) for verifying the end-to-end Slack DM reply path after local runtime or OpenClaw changes. - The Slack smoke probe is not zero-setup: install Chrome Canary first, then manually log into Slack in the opened Canary window before running
pnpm probe:slack run. - The desktop dev launcher is
apps/desktop/dev.sh; it is the source of truth for tmux orchestration, sidecar builds, runtime cleanup, and stable repo-local path setup during local development. - Treat
pnpm startas the canonical cold-start entrypoint for the full local desktop runtime. - The active desktop runtime path is controller-first: desktop launches
controller + web + openclawand no longer starts localapi,gateway, orpglitesidecars. - Desktop local runtime should not depend on PostgreSQL; controller-owned local state lives under
~/.nexu/, while desktop dev runtime state remains repo-scoped under.tmp/desktop/. tmuxis required for the desktop local-dev workflow.- Local desktop runtime state is repo-scoped under
.tmp/desktop/in development. - For startup troubleshooting, use
pnpm logsand./apps/desktop/dev.sh devlog. pnpm reset-stateis a dev-only cleanup shortcut for./apps/desktop/dev.sh reset-state; it stops the stack and removes repo-local desktop runtime state under.tmp/desktop/, but it does not delete controller-owned state in~/.nexu/.- To fully reset local desktop + controller state, stop the stack, remove
.tmp/desktop/, then remove~/.nexu/. - If
pnpm startexits immediately becauseelectron/cli.jscannot be resolved fromapps/desktop, validatepnpm -C apps/desktop exec electron --versionand consultspecs/guides/desktop-runtime-guide.mdbefore changing the launcher flow. - Desktop already exposes an agent-friendly runtime observability surface; prefer subscribing/querying before adding temporary UI or ad hoc debug logging.
- For deeper desktop runtime inspection, use the existing event/query path (
onRuntimeEvent(...),runtime:query-events,queryRuntimeEvents(...)) instead of rebuilding one-off diagnostics. - Use
actionId,reasonCode, andcursor/nextCursoras the primary correlation and incremental-fetch primitives for desktop runtime debugging. - To fully clear local desktop runtime state, use
./apps/desktop/dev.sh reset-state. - Desktop runtime guide:
specs/guides/desktop-runtime-guide.md. - The controller sidecar is packaged by
apps/desktop/scripts/prepare-controller-sidecar.mjswhich deep-copies all controllerdependenciesand their transitive deps into.dist-runtime/controller/node_modules/. Keep controller deps minimal to avoid bloating the desktop distributable. - SkillHub (catalog, install, uninstall) runs in the controller via HTTP β not in the Electron main process via IPC. The web app always uses HTTP SDK for skill operations.
- Never use
any. Useunknownwith narrowing orz.infer<typeof schema>. - No foreign keys in Drizzle schema β application-level joins only.
- Credentials (bot tokens, signing secrets) must never appear in logs or errors.
- Frontend must use generated SDK (
apps/web/lib/api/), never rawfetch. - All API routes must use
createRoute()+app.openapi()from@hono/zod-openapi. Never use plainapp.get()/app.post()etc β those bypass OpenAPI spec generation and the SDK won't have corresponding functions. - All request bodies, path params, query params, and responses must have Zod schemas. Shared schemas go in
packages/shared/src/schemas/, route-local param schemas (e.g.z.object({ id: z.string() })) can stay in the route file. - After adding or modifying API routes: run
pnpm generate-typesto regenerateopenapi.json->sdk.gen.ts->types.gen.ts, then update frontend call sites to use the new SDK functions. - Config generator output must match
specs/references/openclaw-config-schema.md. - Do not add dependencies without explicit approval.
- Do not modify OpenClaw source code.
- Never commit code changes until explicitly told to do so.
- Desktop packaged app: never use
npx,npm,pnpm, or any shell command that relies on the user's PATH. The packaged Electron app has no shell profile β resolve bin paths programmatically viarequire.resolve()and execute withprocess.execPath. The app must be fully self-contained. - Controller sidecar packaging: every dependency in
apps/controller/package.jsonis recursively deep-copied into the desktop distributable viaprepare-controller-sidecarβcopyRuntimeDependencyClosure. Never add heavy transitive-dependency packages (e.g.npm,yarn) to the controller. If the controller needs to shell out to a CLI tool, use PATH-basedexecFile("npm", ...)instead of bundling it as a dependency. Each MB added to controller deps adds ~1 MB to the final DMG/ZIP. - Native Node.js addons (e.g.
better-sqlite3) must live in the controller, NOT in the desktop Electron main process. Electron's built-in Node.js has a different ABI version (NODE_MODULE_VERSION) from system Node.js, requiringelectron-rebuildto recompile native modules. The controller runs as a regular Node.js process (ELECTRON_RUN_AS_NODE=1), so native addons work without recompilation.
- Request-level tracing must be created uniformly by middleware as the root trace.
- Logic with monitoring value must be split into named functions and annotated with
@Trace/@Span. - Do not introduce function-wrapper transitional APIs such as
runTrace/runSpan. - Iterate incrementally: add Trace/Span within established code patterns first, then refine based on metrics.
- Logger usage source of truth should follow the active package you are editing; prefer established nearby logger patterns in controller and desktop code.
pnpm typecheckβ after any TypeScript changespnpm lintβ after any code changespnpm generate-typesβ after API route/schema changespnpm testβ after logic changes
See ARCHITECTURE.md for the full bird's-eye view. Key points:
- Monorepo:
apps/controller(Hono),apps/web(React),apps/desktop(Electron),packages/shared(Zod schemas),nexu-skills/(skill repo) - Type safety: Zod -> OpenAPI -> generated frontend SDK. Never duplicate types.
- Config generator:
apps/controller/src/lib/openclaw-config-compiler.tsbuilds OpenClaw config from local controller state - Local runtime flow:
apps/controllerowns Nexu config/state, writes OpenClaw config/skills/templates, and managesopenclaw-runtimedirectly; desktop wraps that controller-first stack with Electron + web sidecars - Key data flows: local config compilation, desktop runtime boot, channel sync, file-based skill catalog
- Biome: 2-space indent, double quotes, semicolons always
- Files:
kebab-case/ Types:PascalCase/ Variables:camelCase - Zod schemas:
camelCase+Schemasuffix - DB tables:
snake_casein Drizzle - Public IDs: cuid2 (
@paralleldrive/cuid2), never exposepk - Errors: throw
HTTPExceptionwith status + contextual message - Logging: structured (pino or console JSON), never log credentials
| Topic | Location |
|---|---|
| Architecture & data flows | ARCHITECTURE.md |
| System design | specs/designs/openclaw-multi-tenant.md |
| OpenClaw internals | specs/designs/openclaw-architecture-internals.md |
| Engineering principles | specs/design-docs/core-beliefs.md |
| Config schema & pitfalls | specs/references/openclaw-config-schema.md |
| API coding patterns | specs/references/api-patterns.md |
| Workspace templates | specs/guides/workspace-templates.md |
| Local Slack testing | specs/references/local-slack-testing.md |
| Local Slack smoke probe | scripts/probe/README.md, scripts/probe/slack-reply-probe.mjs |
| Frontend conventions | specs/FRONTEND.md |
| Desktop runtime guide | specs/guides/desktop-runtime-guide.md |
| Security posture | specs/SECURITY.md |
| Reliability | specs/RELIABILITY.md |
| Product model | specs/PRODUCT_SENSE.md |
| Quality signals | specs/QUALITY_SCORE.md |
| Product specs | specs/product-specs/ |
| Execution plans | specs/exec-plans/ |
| Documentation sync | skills/localdev/sync-specs/SKILL.md |
| Nano Banana (image gen) | skills/nexubot/nano-banana/SKILL.md |
| Skill repo & catalog | nexu-skills/, apps/controller/src/services/skillhub/ |
| File-based skills design | specs/plans/2026-03-15-skill-repo-design.md |
| Feishu channel setup | apps/web/src/components/channel-setup/feishu-setup-view.tsx |
After significant code changes, verify documentation is current.
git diff --name-only $(git merge-base HEAD origin/main)...HEAD| Changed area | Affected docs |
|---|---|
apps/web/src/pages/ or routing |
specs/FRONTEND.md |
apps/controller/src/routes/ |
specs/references/api-patterns.md, specs/product-specs/*.md |
apps/controller/src/runtime/ |
ARCHITECTURE.md, specs/RELIABILITY.md |
apps/web/src/components/channel-setup/ |
specs/FRONTEND.md |
nexu-skills/ |
ARCHITECTURE.md (monorepo layout) |
packages/shared/src/schemas/ |
ARCHITECTURE.md (type safety) |
package.json scripts |
AGENTS.md Commands section |
| New/moved doc files | AGENTS.md Where to look |
AGENTS.mdWhere to look table β all paths validspecs/DESIGN.md<->specs/design-specs/+specs/designs/(indexed)specs/product-specs/index.md<-> actual spec filesspecs/FRONTEND.mdPages <->apps/web/src/app.tsxroutes
- Regenerate
specs/generated/db-schema.mdfully from schema source - Preserve original language (English/Chinese)
- Do not auto-commit; present changes for review
Full reference: skills/localdev/sync-specs/SKILL.md
Nexu work must be synced into the team knowledge repo at:
agent-digital-cowork/clone/
When producing artifacts in this repo, sync them to the cross-project repo using this mapping:
| Artifact type | Target in agent-digital-cowork/clone/ |
|---|---|
| Design plans / architecture proposals | design/ |
| Debug summaries / incident analysis | debug/ |
| Ideas / product notes | ideas/ |
| Stable facts / decisions / runbooks | knowledge/ |
| Open blockers / follow-ups | blockers/ |
Project memory directory:
/Users/alche/.claude/projects/-Users-alche-Documents-digit-sutando-nexu/memory/
Keep these memory notes up to date:
- Cross-project sync rules memory (source of truth for sync expectations)
- Skills hot-reload findings memory (
skills-hotreload.md) - DB/dev environment quick-reference memory
For OpenClaw skills behavior and troubleshooting, maintain and consult:
skills-hotreload.mdin the Nexu memory directory above.
This note should track:
- End-to-end pipeline status (
Controller store -> compiler -> runtime writers -> OpenClaw) - Why
openclaw-managedskills may be missing from session snapshots - Watcher/snapshot refresh caveats and validation steps
- Controller env path:
apps/controller/.env - OpenClaw managed skills dir (expected default):
~/.openclaw/skills/ - Slack smoke probe setup: install Chrome Canary, set
PROBE_SLACK_URL, runpnpm probe:slack prepare, then manually log into Slack in Canary beforepnpm probe:slack run openclaw-runtimeis installed implicitly bypnpm install; local development should normally not use a globalopenclawCLI- Prefer
./openclaw-wrapperover globalopenclawin local development; it executesopenclaw-runtime/node_modules/openclaw/openclaw.mjs - When OpenClaw is started manually, set
RUNTIME_MANAGE_OPENCLAW_PROCESS=falsefor@nexu/controllerto avoid launching a second OpenClaw process - If behavior differs, verify effective
OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATHused by the running controller process.