# Developing This project can run fully in local dev without setting up PostgreSQL manually. ## Deployment Modes For mode definitions and intended CLI behavior, see `doc/DEPLOYMENT-MODES.md`. Current implementation status: - canonical model: `local_trusted` and `authenticated` (with `private/public` exposure) ## Prerequisites - Node.js 20+ - pnpm 9+ ## Dependency Lockfile Policy GitHub Actions owns `pnpm-lock.yaml`. - Do not commit `pnpm-lock.yaml` in pull requests unless the PR changes package-manager inputs such as `package.json`, `pnpm-workspace.yaml`, `.npmrc`, or `pnpmfile.*`. - If a PR changes package-manager inputs, it must include the synchronized minimal `pnpm-lock.yaml` diff. - Pull request CI validates dependency resolution when manifests change and will fail if the committed lockfile does not match the manifest changes. - Pushes to `master` regenerate `pnpm-lock.yaml` with `pnpm install --lockfile-only --no-frozen-lockfile`, commit it back if needed, and then run verification with `--frozen-lockfile`. ## Start Dev From repo root: ```sh pnpm install pnpm dev ``` This starts: - API server: `http://localhost:3100` - UI: served by the API server in dev middleware mode (same origin as API) `pnpm dev` runs the server in watch mode and restarts on changes from workspace packages (including adapter packages). Use `pnpm dev:once` to run without file watching. `pnpm dev:once` auto-applies pending local migrations by default before starting the dev server. `pnpm dev` and `pnpm dev:once` are now idempotent for the current repo and instance: if the matching Paperclip dev runner is already alive, Paperclip reports the existing process instead of starting a duplicate. Issue execution may also use project execution workspace policies and workspace runtime services for per-project worktrees, preview servers, and managed dev commands. Configure those through the project workspace/runtime surfaces rather than starting long-running unmanaged processes when a task needs a reusable service. ## Storybook The board UI Storybook keeps stories and Storybook config under `ui/storybook/` so component review files stay out of the app source routes. ```sh pnpm storybook pnpm build-storybook ``` These run the `@penclipai/ui` Storybook on port `6006` and build the static output to `ui/storybook-static/`. Inspect or stop the current repo's managed dev runner: ```sh pnpm dev:list pnpm dev:stop ``` ## Electron Desktop The repository also ships a cross-platform Electron wrapper for local desktop packaging. Desktop commands from repo root: ```sh pnpm desktop:dev pnpm desktop:build pnpm desktop:build:release pnpm desktop:typecheck pnpm desktop:prepare-stage pnpm desktop:pack pnpm desktop:dist pnpm desktop:dist:win pnpm desktop:dist:mac pnpm desktop:dist:linux pnpm smoke:desktop --mode dev pnpm smoke:desktop --mode packaged pnpm smoke:desktop:acceptance pnpm smoke:desktop:acceptance:full ``` What they do: - `pnpm desktop:dev` builds the Electron shell and launches a desktop window against the local Paperclip server entrypoint with `PAPERCLIP_UI_DEV_MIDDLEWARE=true` - `pnpm desktop:build` builds the Electron shell for local development - `pnpm desktop:build:release` builds the Electron shell for packaged release staging - `pnpm desktop:typecheck` runs the desktop package TypeScript check - `pnpm desktop:prepare-stage` stages the packaged server/runtime payload without creating an installer - `pnpm desktop:pack` creates a verified unpacked desktop output for the current platform target without producing installer files - `pnpm desktop:dist` runs the desktop distribution script with its default platform/arch behavior - `pnpm desktop:dist:win` stages a packaged runtime in `packages/desktop-electron/.stage/app-runtime`, builds a small Electron shell, bundles `app-runtime` as extra resources, and creates the Windows installer plus unpacked app output - `pnpm desktop:dist:mac` builds a macOS desktop package for the current host arch - `pnpm desktop:dist:linux` builds a Linux x64 desktop package - `pnpm smoke:desktop --mode dev` launches the dev Electron shell, captures a startup splash screenshot, waits for `/api/health`, then captures the loaded board - `pnpm smoke:desktop --mode packaged` launches the packaged desktop app discovered from `packages/desktop-electron/release/desktop-artifacts.json` and performs the same splash + board checks - `pnpm smoke:desktop:acceptance` runs the faster `core` dev acceptance flow: desktop business pages + bundled example plugin validation - `pnpm smoke:desktop:acceptance:full` runs the slower full dev acceptance flow: `core` coverage plus multi-agent orchestration, real Claude CLI output capture, and third-party plugin installation For a release-shaped local installer build, inject the stable version explicitly instead of using the package's placeholder `0.0.1`: ```sh PAPERCLIP_DESKTOP_RELEASE_VERSION=2026.413.0 pnpm desktop:dist:win ``` Current scope and notes: - desktop packaging now targets: - Windows x64 - macOS x64 - macOS arm64 - Linux x64 - macOS builds are separate x64 and arm64 artifacts, not a universal bundle - macOS and Linux artifacts are unsigned in this phase - `pnpm` stays on the repo-standard `9.15.4` - the desktop shell uses `custom-electron-titlebar` with native Windows controls exposed via `titleBarOverlay` - packaged desktop assets now live under `resources/app-runtime/{server,node_modules,skills}` instead of using the server runtime as the Electron app directory - the app content height is adjusted for Electron chrome through `--paperclip-available-height`; shared UI layout should not bypass that variable with a raw full-viewport height, or packaged windows can show both a document scrollbar and an internal content scrollbar - startup splash screenshots are written to `packages/desktop-electron/.artifacts/smoke//` - acceptance evidence is written to `packages/desktop-electron/.artifacts/smoke/acceptance-dev-core/` or `acceptance-dev-full/` ### Desktop vs CLI/server local paths Paperclip CN currently uses two different local-state roots depending on how it is started: - **CLI/server default path**: `~/.paperclip` - **Desktop Electron default path**: the OS app-data directory with a fixed slug `penclip` Expected desktop defaults: - Windows: `C:\Users\\AppData\Roaming\penclip\...` - macOS: `~/Library/Application Support/penclip/...` - Linux: `~/.config/penclip/...` (or `$XDG_CONFIG_HOME/penclip/...` when set) Expected CLI/server defaults: - all platforms: `~/.paperclip/...` Important boundary: - `Paperclip CN` is the **visible product name** - `penclip` is the **desktop storage directory slug** - `.paperclip` is the **CLI/server storage root** Do not “normalize” these into one name during upstream merges unless the product intentionally changes its storage model. In particular: - do not derive desktop storage paths from `productName`, window title, or visible brand strings - do not rewrite CLI/server defaults from `~/.paperclip` to the desktop app-data path - do not treat `AppData/Roaming/penclip` as a reason to rename `PAPERCLIP_*` env vars or repo-local `.paperclip/` files Acceptance speed tips: - use `pnpm desktop:pack` for routine packaged-runtime regression - use `pnpm smoke:desktop:acceptance` for routine desktop acceptance regression - use `pnpm smoke:desktop:acceptance:full` only for release-level deep validation - add `--skip-build` when you already have fresh local builds and only want to re-run the Electron acceptance flow: ```sh pnpm smoke:desktop:acceptance -- --skip-build pnpm smoke:desktop:acceptance:full -- --skip-build ``` `pnpm dev:once` now tracks backend-relevant file changes and pending migrations. When the current boot is stale, the board UI shows a `Restart required` banner. You can also enable guarded auto-restart in `Instance Settings > Experimental`, which waits for queued/running local agent runs to finish before restarting the dev server. Tailscale/private-auth dev mode: ```sh pnpm dev --bind lan ``` This runs dev as `authenticated/private` with a private-network bind preset. On a fresh authenticated/private instance, open the app, sign in or create an account, and use the setup screen to claim the first instance admin from the browser. The CLI fallback remains: ```sh pnpm penclip auth bootstrap-ceo ``` For Tailscale-only reachability on a detected tailnet address: ```sh pnpm dev --bind tailnet ``` Legacy aliases still map to the old broad private-network behavior: ```sh pnpm dev --tailscale-auth pnpm dev --authenticated-private ``` Allow additional private hostnames (for example custom Tailscale hostnames): ```sh pnpm penclip allowed-hostname dotta-macbook-pro ``` ## Test Commands Use the cheap local default unless you are specifically working on browser flows: ```sh pnpm test ``` `pnpm test` runs the Vitest suite only. For interactive Vitest watch mode use: ```sh pnpm test:watch ``` Browser suites stay separate: ```sh pnpm test:e2e pnpm test:release-smoke ``` These browser suites are intended for targeted local verification and CI, not the default agent/human test command. For normal issue work, start with the smallest targeted check that proves the change. Reserve repo-wide typecheck/build/test runs for PR-ready handoff or changes broad enough that narrow checks do not cover the risk. ## One-Command Local Run For a first-time local install, you can bootstrap and run in one command: ```sh pnpm penclip run ``` `penclip run` does: > **Note: private npm registry `.npmrc` + first-run onboarding** > > The first-run experience often starts with `npx penclip onboard --yes` before you have a repo checkout. If your global `~/.npmrc` sets `registry` to a private registry, for example GitHub Packages, `npx` may try to resolve `penclip` from that private registry and fail with `E404`. > > Diagnostic: > > ```sh > npm config get registry > ``` > > Workaround, cross-platform, force the public npm registry for this command: > > ```sh > npx --registry https://registry.npmjs.org penclip onboard --yes > ``` 1. auto-onboard if config is missing 2. `penclip doctor` with repair enabled 3. starts the server when checks pass ## Docker Quickstart (No local Node install) Build and run Paperclip in Docker: ```sh docker build -t paperclip-local . docker run --name paperclip \ -p 3100:3100 \ -e HOST=0.0.0.0 \ -e PAPERCLIP_HOME=/paperclip \ -v "$(pwd)/data/docker-paperclip:/paperclip" \ paperclip-local ``` Or use Compose: ```sh docker compose -f docker/docker-compose.quickstart.yml up --build ``` See `doc/DOCKER.md` for API key wiring (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) and persistence details. ## Docker For Untrusted PR Review For a separate review-oriented container that keeps `codex`/`claude` login state in Docker volumes and checks out PRs into an isolated scratch workspace, see `doc/UNTRUSTED-PR-REVIEW.md`. ## Local Instance Layout Every local install keeps runtime state directly under the selected instance root: ```text ~/.paperclip/instances/default/ # instance root config.json # runtime config .env # instance env file db/ # embedded PostgreSQL data data/ storage/ # local_disk uploads backups/ # automatic DB backups logs/ secrets/master.key # local_encrypted master key workspaces// # default agent workspaces projects/ # project execution workspaces companies//agents//codex-home/ # per-agent codex_local home ``` `PAPERCLIP_HOME` and `PAPERCLIP_INSTANCE_ID` override the home root and instance id respectively. `paperclipai onboard` echoes the resolved values in its banner (`Local home: | instance: | config: `) so you can confirm where state will land before continuing. ## Database in Dev (Auto-Handled) For local development, leave `DATABASE_URL` unset. The server will automatically use embedded PostgreSQL and persist data at: - `~/.paperclip/instances/default/db` Override home or instance: ```sh PAPERCLIP_HOME=/custom/path PAPERCLIP_INSTANCE_ID=dev pnpm penclip run ``` No Docker or external database is required for this mode. ## Storage in Dev (Auto-Handled) For local development, the default storage provider is `local_disk`, which persists uploaded images/attachments at: - `~/.paperclip/instances/default/data/storage` Configure storage provider/settings: ```sh pnpm penclip configure --section storage ``` ## Agent Artifact Uploads When an agent generates a file that a board user or reviewer should inspect as a deliverable, attach it to the issue before marking the task complete. Do not rely on a local workspace path as the only access path. Use the helper bundled with the Paperclip skill from the repo root: ```sh skills/paperclip/scripts/paperclip-upload-artifact.sh dist/demo.mp4 \ --title "Demo video render" \ --summary "MP4 render for board review" ``` For WebM output: ```sh skills/paperclip/scripts/paperclip-upload-artifact.sh out/walkthrough.webm \ --title "Walkthrough video" \ --summary "WebM walkthrough render" ``` The helper uploads the file as an issue attachment, creates an artifact work product by default, and prints markdown links for the final issue comment. See `doc/AGENT-ARTIFACTS.md` for the full completion pattern and direct API shape. If a file intentionally remains workspace-only, create a work product with `metadata.resourceRef.kind: "workspace_file"` and include the workspace-relative path in the final comment. Use browse/search only as the fallback for recovering that file, not as the main completion path for deliverables. ## Default Agent Workspaces When a local agent run has no resolved project/session workspace, Paperclip falls back to an agent home workspace under the instance root: - `~/.paperclip/instances/default/workspaces/` This path honors `PAPERCLIP_HOME` and `PAPERCLIP_INSTANCE_ID` in non-default setups. For `codex_local`, Paperclip assigns new and updated agents an isolated Codex home under the instance root and blocks shared host/company Codex homes: - `~/.paperclip/instances/default/companies//agents//codex-home` Paperclip also persists an empty `OPENAI_API_KEY` override for those agents so a host-level `OPENAI_API_KEY` cannot leak into Codex runs through process inheritance. If an operator explicitly configures `adapterConfig.env.CODEX_HOME`, it must not point at the shared company `codex-home`, `$CODEX_HOME`, or `~/.codex`. If the `codex` CLI is not installed or not on `PATH`, `codex_local` agent runs fail at execution time with a clear adapter error. Quota polling uses a short-lived `codex app-server` subprocess: when `codex` cannot be spawned, that provider reports `ok: false` in aggregated quota results and the API server keeps running (it must not exit on a missing binary). Local adapters require their corresponding CLI/session setup on the machine running Paperclip. External adapters are installed through the adapter/plugin flow and should not require hardcoded imports in `server/` or `ui/`. ## Config Freshness Agent, project, environment, secret, skill, and workspace config edits are sampled at the next run boundary. A heartbeat that is already running finishes with the config it started with. When effective run config changes, Paperclip may intentionally skip a saved adapter session, refresh persisted workspace runtime config, replace a reused execution workspace, or avoid reusing a sandbox/environment lease. Fresh execution can lose adapter-specific session, workspace, or sandbox state; correctness of the next run's config takes priority over continuity. Plain environment values affect freshness through value hashes; run result JSON and workspace operation logs expose only the non-sensitive freshness decision categories, without storing secret values, full env maps, provider credentials, or private path details. ## Worktree-local Instances When developing from multiple git worktrees, do not point two Paperclip servers at the same embedded PostgreSQL data directory. Instead, create a repo-local Paperclip config plus an isolated instance for the worktree: ```sh penclip worktree init # or create the git worktree and initialize it in one step: pnpm penclip worktree:make paperclip-pr-432 ``` This command: - writes repo-local files at `.paperclip/config.json` and `.paperclip/.env` - creates an isolated instance under `~/.paperclip-worktrees/instances//` - when run inside a linked git worktree, mirrors the effective git hooks into that worktree's private git dir - picks a free app port and embedded PostgreSQL port - by default seeds the isolated DB in `minimal` mode from the current effective Paperclip instance/config (repo-local worktree config when present, otherwise the default instance) via a logical SQL snapshot Seed modes: - `minimal` keeps core app state like companies, projects, issues, comments, approvals, and auth state, preserves schema for all tables, but omits row data from heavy operational history such as heartbeat runs, wake requests, activity logs, runtime services, and agent session state - `full` makes a full logical clone of the source instance - `--no-seed` creates an empty isolated instance Seeded worktree instances quarantine copied live execution by default for both `minimal` and `full` seeds. During restore, Paperclip disables copied agent timer heartbeats, resets copied `running` agents to `idle`, blocks and unassigns copied agent-owned `in_progress` issues, and unassigns copied agent-owned `todo`/`in_review` issues. This keeps a freshly booted worktree from starting agents for work already owned by the source instance. Pass `--preserve-live-work` only when you intentionally want the isolated worktree to resume copied assignments. After `worktree init`, both the server and the CLI auto-load the repo-local `.paperclip/.env` when run inside that worktree, so normal commands like `pnpm dev`, `penclip doctor`, and `penclip db:backup` stay scoped to the worktree instance. `pnpm dev` now fails fast in a linked git worktree when `.paperclip/.env` is missing, instead of silently booting against the default instance/port. If that happens, run `penclip worktree init` in the worktree first. Provisioned git worktrees also pause seeded routines that still have enabled schedule triggers in the isolated worktree database by default. This prevents copied daily/cron routines from firing unexpectedly inside the new workspace instance during development without disabling webhook/API-only routines. That repo-local env also sets: - `PAPERCLIP_IN_WORKTREE=true` - `PAPERCLIP_WORKTREE_NAME=` - `PAPERCLIP_WORKTREE_COLOR=` The server/UI use those values for worktree-specific branding such as the top banner and dynamically colored favicon. Authenticated worktree servers also use the `PAPERCLIP_INSTANCE_ID` value to scope Better Auth cookie names. Browser cookies are shared by host rather than port, so this prevents logging into one `127.0.0.1:` worktree from replacing another worktree server's session cookie. Print shell exports explicitly when needed: ```sh penclip worktree env # or: eval "$(penclip worktree env)" ``` ### Worktree CLI Reference **`pnpm penclip worktree init [options]`** — Create repo-local config/env and an isolated instance for the current worktree. | Option | Description | |---|---| | `--name ` | Display name used to derive the instance id | | `--instance ` | Explicit isolated instance id | | `--home ` | Home root for worktree instances (default: `~/.paperclip-worktrees`) | | `--from-config ` | Source config.json to seed from | | `--from-data-dir ` | Source PAPERCLIP_HOME used when deriving the source config | | `--from-instance ` | Source instance id (default: `default`) | | `--server-port ` | Preferred server port | | `--db-port ` | Preferred embedded Postgres port | | `--seed-mode ` | Seed profile: `minimal` or `full` (default: `minimal`) | | `--no-seed` | Skip database seeding from the source instance | | `--force` | Replace existing repo-local config and isolated instance data | Examples: ```sh penclip worktree init --no-seed penclip worktree init --seed-mode full penclip worktree init --from-instance default penclip worktree init --from-data-dir ~/.paperclip penclip worktree init --force ``` Repair an already-created repo-managed worktree and reseed its isolated instance from the main default install. Point `--from-config` at the instance config: ```sh cd ~/.paperclip/worktrees/PAP-884-ai-commits-component pnpm penclip worktree init --force --seed-mode minimal \ --name PAP-884-ai-commits-component \ --from-config ~/.paperclip/instances/default/config.json ``` That rewrites the worktree-local `.paperclip/config.json` + `.paperclip/.env`, recreates the isolated instance under `~/.paperclip-worktrees/instances//`, and preserves the git worktree contents themselves. For existing worktrees, prefer the dedicated reseed command instead of rebuilding the `worktree init --force` flags manually: For an already-created worktree where you want the CLI to decide whether to rebuild missing worktree metadata or just reseed the isolated DB, use `worktree repair`. **`pnpm penclip worktree repair [options]`** — Repair the current linked worktree by default, or create/repair a named linked worktree under `.paperclip/worktrees/` when `--branch` is provided. The command never targets the primary checkout unless you explicitly pass `--branch`. | Option | Description | |---|---| | `--branch ` | Existing branch/worktree selector to repair, or a branch name to create under `.paperclip/worktrees` | | `--home ` | Home root for worktree instances (default: `~/.paperclip-worktrees`) | | `--from-config ` | Source config.json to seed from | | `--from-data-dir ` | Source `PAPERCLIP_HOME` used when deriving the source config | | `--from-instance ` | Source instance id when deriving the source config (default: `default`) | | `--seed-mode ` | Seed profile: `minimal` or `full` (default: `minimal`) | | `--no-seed` | Repair metadata only when bootstrapping a missing worktree config | | `--allow-live-target` | Override the guard that requires the target worktree DB to be stopped first | Examples: ```sh # From inside a linked worktree, rebuild missing .paperclip metadata and reseed it from the default instance. cd /path/to/paperclip/.paperclip/worktrees/PAP-1132-assistant-ui-pap-1131-make-issues-comments-be-like-a-chat pnpm penclip worktree repair # From the primary checkout, create or repair a linked worktree for a branch under .paperclip/worktrees/. cd /path/to/paperclip pnpm penclip worktree repair --branch PAP-1132-assistant-ui-pap-1131-make-issues-comments-be-like-a-chat ``` For existing worktrees, prefer the dedicated reseed command instead of rebuilding the `worktree init --force` flags manually: ```sh cd /path/to/existing/worktree pnpm penclip worktree reseed --from-config /path/to/source/.paperclip/config.json --seed-mode full ``` `worktree reseed` preserves the current worktree's instance id, ports, and branding while replacing only that worktree's isolated Paperclip instance data from the chosen source. **`pnpm penclip worktree:make [options]`** — Create `~/NAME` as a git worktree, then initialize an isolated Paperclip instance inside it. This combines `git worktree add` with `worktree init` in a single step. | Option | Description | |---|---| | `--start-point ` | Remote ref to base the new branch on (e.g. `origin/main`) | | `--instance ` | Explicit isolated instance id | | `--home ` | Home root for worktree instances (default: `~/.paperclip-worktrees`) | | `--from-config ` | Source config.json to seed from | | `--from-data-dir ` | Source PAPERCLIP_HOME used when deriving the source config | | `--from-instance ` | Source instance id (default: `default`) | | `--server-port ` | Preferred server port | | `--db-port ` | Preferred embedded Postgres port | | `--seed-mode ` | Seed profile: `minimal` or `full` (default: `minimal`) | | `--no-seed` | Skip database seeding from the source instance | | `--force` | Replace existing repo-local config and isolated instance data | Examples: ```sh pnpm penclip worktree:make paperclip-pr-432 pnpm penclip worktree:make my-feature --start-point origin/main pnpm penclip worktree:make experiment --no-seed ``` **`pnpm penclip worktree env [options]`** — Print shell exports for the current worktree-local Paperclip instance. | Option | Description | |---|---| | `-c, --config ` | Path to config file | | `--json` | Print JSON instead of shell exports | Examples: ```sh pnpm penclip worktree env pnpm penclip worktree env --json eval "$(pnpm penclip worktree env)" ``` For project execution worktrees, Paperclip can also run a project-defined provision command after it creates or reuses an isolated git worktree. Configure this on the project's execution workspace policy (`workspaceStrategy.provisionCommand`). The command runs inside the derived worktree and receives `PAPERCLIP_WORKSPACE_*`, `PAPERCLIP_PROJECT_ID`, `PAPERCLIP_AGENT_ID`, and `PAPERCLIP_ISSUE_*` environment variables so each repo can bootstrap itself however it wants. ## App-Shipped Skills Catalog The Paperclip app ships a curated catalog of company skills out of the box. The catalog is a workspace package at `packages/skills-catalog`: ```text packages/skills-catalog/ catalog/ bundled///SKILL.md # recommended defaults optional///SKILL.md # role/domain-specific generated/catalog.json # checked-in manifest scripts/ build-catalog-manifest.ts # regenerate generated/catalog.json validate-catalog.ts # validation only src/ # builder + types consumed by server/CLI ``` Server and CLI import the generated manifest; they do not crawl repository paths at request time. Root `skills/` remains reserved for Paperclip runtime skills and is not part of the catalog. Validate the catalog without writing the manifest: ```sh pnpm --filter @penclipai/skills-catalog validate ``` Regenerate `generated/catalog.json` after editing any catalog `SKILL.md`, frontmatter, file inventory, category, or slug: ```sh pnpm --filter @penclipai/skills-catalog build:manifest ``` The package's `build` script runs `build:manifest` and then `tsc`; tests live under `pnpm --filter @penclipai/skills-catalog test`. Validation fails when: - a catalog entry is not under `catalog/bundled//` or `catalog/optional//` - `SKILL.md` is missing or the frontmatter `name`/`description` is empty - the frontmatter `key` disagrees with the generated canonical key - two catalog entries share an `id`, `key`, or `slug` - file inventory contains absolute paths, `..`, broken symlinks, or files outside the skill directory - the regenerated manifest differs from the checked-in `generated/catalog.json` Trust level is derived from inventory: `markdown_only` (markdown + references only), `assets` (other non-script files), or `scripts_executables` (any executable script). The build contract is documented in `doc/plans/2026-05-26-skills-cli-catalog-contract.md`. CI runs `pnpm --filter @penclipai/skills-catalog validate` and the package's vitest suite, so always regenerate the manifest in the same commit as the catalog change. ## App-Shipped Teams Catalog The team catalog package mirrors the skills catalog workflow for agentcompanies/v1 team packages: ```text packages/teams-catalog/ catalog/ bundled///TEAM.md optional///TEAM.md generated/catalog.json scripts/ build-catalog-manifest.ts validate-catalog.ts ``` Validate without writing the manifest: ```sh pnpm --filter @penclipai/teams-catalog validate ``` Regenerate `generated/catalog.json` after editing catalog team files: ```sh pnpm --filter @penclipai/teams-catalog build:manifest ``` Team install/preview APIs enforce source policy. External skill sources require explicit approval flags, and local-path skill sources are development-only unless `allowLocalPathSources` is set by the caller. ## Quick Health Checks In another terminal: ```sh curl http://localhost:3100/api/health curl http://localhost:3100/api/companies ``` Expected: - `/api/health` returns `{"status":"ok"}` - `/api/companies` returns a JSON array ## Reset Local Dev Database To wipe local dev data and start fresh: ```sh rm -rf ~/.paperclip/instances/default/db pnpm dev ``` ## Optional: Use External Postgres If you set `DATABASE_URL`, the server will use that instead of embedded PostgreSQL. ## Automatic DB Backups Paperclip can run automatic logical database backups on a timer. These backups cover non-system database schemas, including migration history and plugin-owned database schemas. Defaults: - enabled - every 60 minutes - retain 30 days - backup dir: `~/.paperclip/instances/default/data/backups` Configure these in: ```sh pnpm penclip configure --section database ``` Run a one-off backup manually: ```sh pnpm penclip db:backup # or: pnpm db:backup ``` Environment overrides: - `PAPERCLIP_DB_BACKUP_ENABLED=true|false` - `PAPERCLIP_DB_BACKUP_INTERVAL_MINUTES=` - `PAPERCLIP_DB_BACKUP_RETENTION_DAYS=` - `PAPERCLIP_DB_BACKUP_DIR=/absolute/or/~/path` DB backups are not full instance filesystem backups. For full local disaster recovery, also back up local storage files and the local encrypted secrets key if those providers are enabled. ## Secrets in Dev Agent env vars now support secret references. By default, secret values are stored with local encryption and only secret refs are persisted in agent config. - Default local key path: `~/.paperclip/instances/default/secrets/master.key` - Override key material directly: `PAPERCLIP_SECRETS_MASTER_KEY` - Override key file path: `PAPERCLIP_SECRETS_MASTER_KEY_FILE` - Back up the key file and database together; either one alone is not enough to restore local encrypted secrets. Strict mode (recommended outside local trusted machines): ```sh PAPERCLIP_SECRETS_STRICT_MODE=true ``` When strict mode is enabled, sensitive env keys (for example `*_API_KEY`, `*_TOKEN`, `*_SECRET`) must use secret references instead of inline plain values. Authenticated deployments default strict mode on unless explicitly overridden. CLI configuration support: - `pnpm penclip onboard` writes a default `secrets` config section (`local_encrypted`, strict mode off, key file path set) and creates a local key file when needed. - `pnpm penclip configure --section secrets` lets you update provider/strict mode/key path and creates the local key file when needed. - `pnpm penclip doctor` validates secrets adapter configuration, can create a missing local key file with `--repair`, and reports missing AWS Secrets Manager bootstrap env when that provider is selected. - Provider health is available at `GET /api/companies/:companyId/secret-providers/health` and reports local key permission warnings plus backup guidance. Per-company provider vaults are configured in the board UI under `Company Settings → Secrets → Provider vaults`, backed by `/api/companies/{companyId}/secret-provider-configs`. The CLI does not own vault lifecycle today. See `docs/deploy/secrets.md` (`Provider Vaults` section) for the operator model. Migration helper for existing inline env secrets: ```sh pnpm secrets:migrate-inline-env # dry run pnpm secrets:migrate-inline-env --apply # apply migration ``` ## Company Deletion Toggle Company deletion is intended as a dev/debug capability and can be disabled at runtime: ```sh PAPERCLIP_ENABLE_COMPANY_DELETION=false ``` Default behavior: - `local_trusted`: enabled - `authenticated`: disabled ## CLI Client Operations Paperclip CLI now includes client-side control-plane commands in addition to setup commands. Quick examples: ```sh pnpm penclip issue list --company-id pnpm penclip issue create --company-id --title "Investigate checkout conflict" pnpm penclip issue update --status in_progress --comment "Started triage" ``` Set defaults once with context profiles: ```sh pnpm penclip context set --api-base http://localhost:3100 --company-id ``` Then run commands without repeating flags: ```sh pnpm penclip issue list pnpm penclip dashboard get ``` See full command reference in `doc/CLI.md`. ## Agent Invite Onboarding Endpoints Agent-oriented invite onboarding now exposes machine-readable API docs: The board UI generates agent onboarding prompts from the add-agent modal (`+` in the agent sidebar), so agent onboarding sits with the rest of agent creation rather than company member invite settings. - `GET /api/invites/:token` returns invite summary plus onboarding and skills index links. - `GET /api/invites/:token/onboarding` returns onboarding manifest details (registration endpoint, claim endpoint template, skill install hints). - `GET /api/invites/:token/onboarding.txt` returns a plain-text onboarding doc intended for both human operators and agents (llm.txt-style handoff), including optional inviter message and suggested network host candidates. - `GET /api/skills/index` lists available skill documents. - `GET /api/skills/paperclip` returns the Paperclip heartbeat skill markdown. Hermes gateway agents use this same generic agent invite flow with `adapterType=hermes_gateway` and `agentDefaultsPayload.apiBaseUrl` / `agentDefaultsPayload.apiKey`. Install a Hermes gateway adapter plugin first so the type key is available at runtime. ## OpenClaw Join Smoke Test Run the end-to-end OpenClaw join smoke harness: ```sh pnpm smoke:openclaw-join ``` What it validates: - invite creation for agent-only join - agent join request using `adapterType=openclaw_gateway` - board approval + one-time API key claim semantics - callback delivery on wakeup to a dockerized OpenClaw-style webhook receiver Required permissions: - This script performs board-governed actions (create invite, approve join, wakeup another agent). - In authenticated mode, run with board auth via `PAPERCLIP_AUTH_HEADER` or `PAPERCLIP_COOKIE`. Optional auth flags (for authenticated mode): - `PAPERCLIP_AUTH_HEADER` (for example `Bearer ...`) - `PAPERCLIP_COOKIE` (session cookie header value) ## OpenClaw Docker UI One-Command Script To boot OpenClaw in Docker and print a host-browser dashboard URL in one command: ```sh pnpm smoke:openclaw-docker-ui ``` This script lives at `scripts/smoke/openclaw-docker-ui.sh` and automates clone/build/config/start for Compose-based local OpenClaw UI testing. Pairing behavior for this smoke script: - default `OPENCLAW_DISABLE_DEVICE_AUTH=1` (no Control UI pairing prompt for local smoke; no extra pairing env vars required) - set `OPENCLAW_DISABLE_DEVICE_AUTH=0` to require standard device pairing Model behavior for this smoke script: - defaults to OpenAI models (`openai/gpt-5.2` + OpenAI fallback) so it does not require Anthropic auth by default State behavior for this smoke script: - defaults to isolated config dir `~/.openclaw-paperclip-smoke` - resets smoke agent state each run by default (`OPENCLAW_RESET_STATE=1`) to avoid stale provider/auth drift Networking behavior for this smoke script: - auto-detects and prints a Paperclip host URL reachable from inside OpenClaw Docker - default container-side host alias is `host.docker.internal` (override with `PAPERCLIP_HOST_FROM_CONTAINER` / `PAPERCLIP_HOST_PORT`) - if Paperclip rejects container hostnames in authenticated/private mode, allow `host.docker.internal` via `pnpm penclip allowed-hostname host.docker.internal` and restart Paperclip