refactor: rename packages to @paperclipai and CLI binary to paperclipai

Rename all workspace packages from @paperclip/* to @paperclipai/* and
the CLI binary from `paperclip` to `paperclipai` in preparation for
npm publishing. Bump CLI version to 0.1.0 and add package metadata
(description, keywords, license, repository, files). Update all
imports, documentation, user-facing messages, and tests accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotta
2026-03-03 08:45:26 -06:00
parent 5a5549fc54
commit f60c1001ec
196 changed files with 501 additions and 490 deletions

View File

@@ -10,19 +10,19 @@ Paperclip CLI now supports both:
Use repo script in development:
```sh
pnpm paperclip --help
pnpm paperclipai --help
```
First-time local bootstrap + run:
```sh
pnpm paperclip run
pnpm paperclipai run
```
Choose local instance:
```sh
pnpm paperclip run --instance dev
pnpm paperclipai run --instance dev
```
## Deployment Modes
@@ -31,16 +31,16 @@ Mode taxonomy and design intent are documented in `doc/DEPLOYMENT-MODES.md`.
Current CLI behavior:
- `paperclip onboard` and `paperclip configure --section server` set deployment mode in config
- `paperclipai onboard` and `paperclipai configure --section server` set deployment mode in config
- runtime can override mode with `PAPERCLIP_DEPLOYMENT_MODE`
- `paperclip run` and `paperclip doctor` do not yet expose a direct `--mode` flag
- `paperclipai run` and `paperclipai doctor` do not yet expose a direct `--mode` flag
Target behavior (planned) is documented in `doc/DEPLOYMENT-MODES.md` section 5.
Allow an authenticated/private hostname (for example custom Tailscale DNS):
```sh
pnpm paperclip allowed-hostname dotta-macbook-pro
pnpm paperclipai allowed-hostname dotta-macbook-pro
```
All client commands support:
@@ -57,8 +57,8 @@ Company-scoped commands also support `--company-id <id>`.
Use `--data-dir` on any CLI command to isolate all default local state (config/context/db/logs/storage/secrets) away from `~/.paperclip`:
```sh
pnpm paperclip run --data-dir ./tmp/paperclip-dev
pnpm paperclip issue list --data-dir ./tmp/paperclip-dev
pnpm paperclipai run --data-dir ./tmp/paperclip-dev
pnpm paperclipai issue list --data-dir ./tmp/paperclip-dev
```
## Context Profiles
@@ -66,32 +66,32 @@ pnpm paperclip issue list --data-dir ./tmp/paperclip-dev
Store local defaults in `~/.paperclip/context.json`:
```sh
pnpm paperclip context set --api-base http://localhost:3100 --company-id <company-id>
pnpm paperclip context show
pnpm paperclip context list
pnpm paperclip context use default
pnpm paperclipai context set --api-base http://localhost:3100 --company-id <company-id>
pnpm paperclipai context show
pnpm paperclipai context list
pnpm paperclipai context use default
```
To avoid storing secrets in context, set `apiKeyEnvVarName` and keep the key in env:
```sh
pnpm paperclip context set --api-key-env-var-name PAPERCLIP_API_KEY
pnpm paperclipai context set --api-key-env-var-name PAPERCLIP_API_KEY
export PAPERCLIP_API_KEY=...
```
## Company Commands
```sh
pnpm paperclip company list
pnpm paperclip company get <company-id>
pnpm paperclip company delete <company-id-or-prefix> --yes --confirm <same-id-or-prefix>
pnpm paperclipai company list
pnpm paperclipai company get <company-id>
pnpm paperclipai company delete <company-id-or-prefix> --yes --confirm <same-id-or-prefix>
```
Examples:
```sh
pnpm paperclip company delete PAP --yes --confirm PAP
pnpm paperclip company delete 5cbe79ee-acb3-4597-896e-7662742593cd --yes --confirm 5cbe79ee-acb3-4597-896e-7662742593cd
pnpm paperclipai company delete PAP --yes --confirm PAP
pnpm paperclipai company delete 5cbe79ee-acb3-4597-896e-7662742593cd --yes --confirm 5cbe79ee-acb3-4597-896e-7662742593cd
```
Notes:
@@ -102,45 +102,45 @@ Notes:
## Issue Commands
```sh
pnpm paperclip issue list --company-id <company-id> [--status todo,in_progress] [--assignee-agent-id <agent-id>] [--match text]
pnpm paperclip issue get <issue-id-or-identifier>
pnpm paperclip issue create --company-id <company-id> --title "..." [--description "..."] [--status todo] [--priority high]
pnpm paperclip issue update <issue-id> [--status in_progress] [--comment "..."]
pnpm paperclip issue comment <issue-id> --body "..." [--reopen]
pnpm paperclip issue checkout <issue-id> --agent-id <agent-id> [--expected-statuses todo,backlog,blocked]
pnpm paperclip issue release <issue-id>
pnpm paperclipai issue list --company-id <company-id> [--status todo,in_progress] [--assignee-agent-id <agent-id>] [--match text]
pnpm paperclipai issue get <issue-id-or-identifier>
pnpm paperclipai issue create --company-id <company-id> --title "..." [--description "..."] [--status todo] [--priority high]
pnpm paperclipai issue update <issue-id> [--status in_progress] [--comment "..."]
pnpm paperclipai issue comment <issue-id> --body "..." [--reopen]
pnpm paperclipai issue checkout <issue-id> --agent-id <agent-id> [--expected-statuses todo,backlog,blocked]
pnpm paperclipai issue release <issue-id>
```
## Agent Commands
```sh
pnpm paperclip agent list --company-id <company-id>
pnpm paperclip agent get <agent-id>
pnpm paperclipai agent list --company-id <company-id>
pnpm paperclipai agent get <agent-id>
```
## Approval Commands
```sh
pnpm paperclip approval list --company-id <company-id> [--status pending]
pnpm paperclip approval get <approval-id>
pnpm paperclip approval create --company-id <company-id> --type hire_agent --payload '{"name":"..."}' [--issue-ids <id1,id2>]
pnpm paperclip approval approve <approval-id> [--decision-note "..."]
pnpm paperclip approval reject <approval-id> [--decision-note "..."]
pnpm paperclip approval request-revision <approval-id> [--decision-note "..."]
pnpm paperclip approval resubmit <approval-id> [--payload '{"...":"..."}']
pnpm paperclip approval comment <approval-id> --body "..."
pnpm paperclipai approval list --company-id <company-id> [--status pending]
pnpm paperclipai approval get <approval-id>
pnpm paperclipai approval create --company-id <company-id> --type hire_agent --payload '{"name":"..."}' [--issue-ids <id1,id2>]
pnpm paperclipai approval approve <approval-id> [--decision-note "..."]
pnpm paperclipai approval reject <approval-id> [--decision-note "..."]
pnpm paperclipai approval request-revision <approval-id> [--decision-note "..."]
pnpm paperclipai approval resubmit <approval-id> [--payload '{"...":"..."}']
pnpm paperclipai approval comment <approval-id> --body "..."
```
## Activity Commands
```sh
pnpm paperclip activity list --company-id <company-id> [--agent-id <agent-id>] [--entity-type issue] [--entity-id <id>]
pnpm paperclipai activity list --company-id <company-id> [--agent-id <agent-id>] [--entity-type issue] [--entity-id <id>]
```
## Dashboard Commands
```sh
pnpm paperclip dashboard get --company-id <company-id>
pnpm paperclipai dashboard get --company-id <company-id>
```
## Heartbeat Command
@@ -148,7 +148,7 @@ pnpm paperclip dashboard get --company-id <company-id>
`heartbeat run` now also supports context/api-key options and uses the shared client stack:
```sh
pnpm paperclip heartbeat run --agent-id <agent-id> [--api-base http://localhost:3100] [--api-key <token>]
pnpm paperclipai heartbeat run --agent-id <agent-id> [--api-base http://localhost:3100] [--api-key <token>]
```
## Local Storage Defaults
@@ -164,7 +164,7 @@ Default local instance root is `~/.paperclip/instances/default`:
Override base home or instance with env vars:
```sh
PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclip run
PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run
```
## Storage Configuration
@@ -172,7 +172,7 @@ PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclip run
Configure storage provider and settings:
```sh
pnpm paperclip configure --section storage
pnpm paperclipai configure --section storage
```
Supported providers:

View File

@@ -209,7 +209,7 @@ ClipHub is a **separate service** from Paperclip itself. Paperclip is self-hoste
|---|---|
| **ClipHub Web** | Browse, search, discover, comment, star — the website |
| **ClipHub API** | Registry API for publishing, downloading, searching programmatically |
| **Paperclip CLI** | `paperclip install`, `paperclip publish`, `paperclip cliphub sync` — built into Paperclip |
| **Paperclip CLI** | `paperclipai install`, `paperclipai publish`, `paperclipai cliphub sync` — built into Paperclip |
| **Paperclip UI** | "Browse ClipHub" panel in the Paperclip web UI for discovering templates without leaving the app |
### Tech Stack
@@ -260,7 +260,7 @@ Report
1. Open ClipHub, browse by category or search "dev shop for building SaaS"
2. Find a template that fits — "Lean SaaS Dev Shop (CEO + CTO + 3 Engineers)"
3. Read the description, inspect the org chart, check the comments
4. Run `paperclip install cliphub:acme/lean-saas-shop`
4. Run `paperclipai install cliphub:acme/lean-saas-shop`
5. Paperclip creates the company locally with all agents pre-configured
6. Set your API keys, adjust budgets, add your initial tasks
7. Hit go
@@ -268,8 +268,8 @@ Report
### "I built something great and want to share it"
1. Build and iterate on a company in Paperclip until it works well
2. Export: `paperclip export --template my-agency`
3. Publish: `paperclip publish cliphub my-agency`
2. Export: `paperclipai export --template my-agency`
3. Publish: `paperclipai publish cliphub my-agency`
4. Fill in description, category, tags on the web UI
5. Template is live — others can find and install it
@@ -285,7 +285,7 @@ Report
1. Search ClipHub for agent templates: "senior python engineer"
2. Find a well-starred agent config
3. Install just that agent: `paperclip install cliphub:acme/senior-python-eng --agent`
3. Install just that agent: `paperclipai install cliphub:acme/senior-python-eng --agent`
4. Assign it to a manager in your existing company
5. Done
@@ -311,7 +311,7 @@ ClipHub is to Paperclip what a package registry is to a language runtime: option
- [ ] Template browsing (list, filter by category)
- [ ] Template detail page (description, org chart, agent list, install command)
- [ ] Semantic search (vector embeddings)
- [ ] `paperclip install cliphub:<publisher>/<slug>` CLI command
- [ ] `paperclipai install cliphub:<publisher>/<slug>` CLI command
- [ ] GitHub OAuth authentication
- [ ] Stars
- [ ] Download counts
@@ -326,7 +326,7 @@ ClipHub is to Paperclip what a package registry is to a language runtime: option
- [ ] Verified publisher badges
- [ ] Automated security scanning of adapter configs
- [ ] "Browse ClipHub" panel in Paperclip web UI
- [ ] `paperclip cliphub sync` for bulk publishing
- [ ] `paperclipai cliphub sync` for bulk publishing
- [ ] Publisher profiles and portfolios
### Not in Scope

View File

@@ -150,7 +150,7 @@ PAPERCLIP_SECRETS_STRICT_MODE=true
You can set strict mode and provider defaults via:
```sh
pnpm paperclip configure --section secrets
pnpm paperclipai configure --section secrets
```
Inline secret migration command:

View File

@@ -50,7 +50,7 @@ This keeps one authenticated auth stack while still separating low-friction priv
Default onboarding remains interactive and flagless:
```sh
pnpm paperclip onboard
pnpm paperclipai onboard
```
Server prompt behavior:
@@ -71,7 +71,7 @@ Server prompt behavior:
Default doctor remains flagless:
```sh
pnpm paperclip doctor
pnpm paperclipai doctor
```
Doctor reads configured mode/exposure and applies mode-aware checks. Optional override flags are secondary.

View File

@@ -40,7 +40,7 @@ This runs dev as `authenticated/private` and binds the server to `0.0.0.0` for p
Allow additional private hostnames (for example custom Tailscale hostnames):
```sh
pnpm paperclip allowed-hostname dotta-macbook-pro
pnpm paperclipai allowed-hostname dotta-macbook-pro
```
## One-Command Local Run
@@ -48,13 +48,13 @@ pnpm paperclip allowed-hostname dotta-macbook-pro
For a first-time local install, you can bootstrap and run in one command:
```sh
pnpm paperclip run
pnpm paperclipai run
```
`paperclip run` does:
`paperclipai run` does:
1. auto-onboard if config is missing
2. `paperclip doctor` with repair enabled
2. `paperclipai doctor` with repair enabled
3. starts the server when checks pass
## Docker Quickstart (No local Node install)
@@ -89,7 +89,7 @@ The server will automatically use embedded PostgreSQL and persist data at:
Override home and instance:
```sh
PAPERCLIP_HOME=/custom/path PAPERCLIP_INSTANCE_ID=dev pnpm paperclip run
PAPERCLIP_HOME=/custom/path PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run
```
No Docker or external database is required for this mode.
@@ -103,7 +103,7 @@ For local development, the default storage provider is `local_disk`, which persi
Configure storage provider/settings:
```sh
pnpm paperclip configure --section storage
pnpm paperclipai configure --section storage
```
## Default Agent Workspaces
@@ -159,9 +159,9 @@ When strict mode is enabled, sensitive env keys (for example `*_API_KEY`, `*_TOK
CLI configuration support:
- `pnpm paperclip 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 paperclip configure --section secrets` lets you update provider/strict mode/key path and creates the local key file when needed.
- `pnpm paperclip doctor` validates secrets adapter configuration and can create a missing local key file with `--repair`.
- `pnpm paperclipai 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 paperclipai configure --section secrets` lets you update provider/strict mode/key path and creates the local key file when needed.
- `pnpm paperclipai doctor` validates secrets adapter configuration and can create a missing local key file with `--repair`.
Migration helper for existing inline env secrets:
@@ -190,22 +190,22 @@ Paperclip CLI now includes client-side control-plane commands in addition to set
Quick examples:
```sh
pnpm paperclip issue list --company-id <company-id>
pnpm paperclip issue create --company-id <company-id> --title "Investigate checkout conflict"
pnpm paperclip issue update <issue-id> --status in_progress --comment "Started triage"
pnpm paperclipai issue list --company-id <company-id>
pnpm paperclipai issue create --company-id <company-id> --title "Investigate checkout conflict"
pnpm paperclipai issue update <issue-id> --status in_progress --comment "Started triage"
```
Set defaults once with context profiles:
```sh
pnpm paperclip context set --api-base http://localhost:3100 --company-id <company-id>
pnpm paperclipai context set --api-base http://localhost:3100 --company-id <company-id>
```
Then run commands without repeating flags:
```sh
pnpm paperclip issue list
pnpm paperclip dashboard get
pnpm paperclipai issue list
pnpm paperclipai dashboard get
```
See full command reference in `doc/CLI.md`.

View File

@@ -455,11 +455,11 @@ Files:
Commands:
1. `paperclip auth bootstrap-ceo`
1. `paperclipai auth bootstrap-ceo`
- create bootstrap invite
- print one-time URL
2. `paperclip onboard`
2. `paperclipai onboard`
- in cloud mode with `bootstrap_pending`, print bootstrap URL and next steps
- in local mode, skip bootstrap requirement

View File

@@ -17,7 +17,7 @@ Current V1 assumptions are centered on one board operator. We now need:
- multi-human collaboration with per-user permissions
- safe cloud deployment defaults (no accidental loginless production)
- local mode that still feels instant (`npx paperclip run` and go)
- local mode that still feels instant (`npx paperclipai run` and go)
- agent-to-human task delegation, including a human inbox
- one user account with access to multiple companies in one deployment
- instance admins who can manage company access across the instance
@@ -106,9 +106,9 @@ Problem:
Bootstrap flow:
1. If no `instance_admin` user exists for the deployment, instance is in `bootstrap_pending` state.
2. CLI command `pnpm paperclip auth bootstrap-ceo` creates a one-time CEO onboarding invite URL for that instance.
3. `pnpm paperclip onboard` runs this bootstrap check and prints the invite URL automatically when `bootstrap_pending`.
4. Visiting the app while `bootstrap_pending` shows a blocking setup page with the exact CLI command to run (`pnpm paperclip onboard`).
2. CLI command `pnpm paperclipai auth bootstrap-ceo` creates a one-time CEO onboarding invite URL for that instance.
3. `pnpm paperclipai onboard` runs this bootstrap check and prints the invite URL automatically when `bootstrap_pending`.
4. Visiting the app while `bootstrap_pending` shows a blocking setup page with the exact CLI command to run (`pnpm paperclipai onboard`).
5. Accepting that CEO invite creates the first admin user and exits bootstrap mode.
Security rules:
@@ -323,7 +323,7 @@ This plan introduces instance-level concerns (for example bootstrap state, insta
V1 approach:
- add a minimal `Instance Settings` page for instance admins
- expose key instance settings in API + CLI (`paperclip configure` / `paperclip onboard`)
- expose key instance settings in API + CLI (`paperclipai configure` / `paperclipai onboard`)
- show read-only instance status indicators in the main UI until full settings UX exists
## Implementation phases
@@ -385,7 +385,7 @@ V1 approach:
4. `cloud_hosted` cannot start without auth configured.
5. No request in `cloud_hosted` can mutate data without authenticated actor.
6. If no initial admin exists, app shows bootstrap instructions with CLI command.
7. `pnpm paperclip onboard` outputs a CEO onboarding invite URL when bootstrap is pending.
7. `pnpm paperclipai onboard` outputs a CEO onboarding invite URL when bootstrap is pending.
8. One `company_join` link supports both human and agent onboarding via join-type selection on the invite landing page.
9. Invite delivery in V1 is copy-link only (no built-in email delivery).
10. Share-link acceptance creates a pending join request; it does not grant immediate access.

View File

@@ -203,7 +203,7 @@ On approval, the approver sets:
| **P1** | Invite link + onboarding endpoint | `POST /api/companies/:id/invites`, `GET /api/invite/:token`, `POST /api/invite/:token/register`. |
| **P1** | Approval flow | UI + API for reviewing and approving pending agent registrations. |
| **P2** | OpenClaw integration | First real external agent onboarding via invite link. |
| **P3** | CLI auth flow | `paperclip auth login` for developer-managed remote agents. |
| **P3** | CLI auth flow | `paperclipai auth login` for developer-managed remote agents. |
## P0 Implementation Plan

View File

@@ -76,7 +76,7 @@ This is one authenticated mode with two safety policies, not two different auth
Default command remains:
```sh
pnpm paperclip onboard
pnpm paperclipai onboard
```
Interactive server step:
@@ -97,7 +97,7 @@ Flags are optional power-user overrides, not required for normal setup.
Default command remains interactive:
```sh
pnpm paperclip configure --section server
pnpm paperclipai configure --section server
```
Same mode/exposure questions and defaults as onboarding.
@@ -107,7 +107,7 @@ Same mode/exposure questions and defaults as onboarding.
Default command remains flagless:
```sh
pnpm paperclip doctor
pnpm paperclipai doctor
```
Doctor reads configured mode/exposure and applies relevant checks.
@@ -209,9 +209,9 @@ This change is a clean cut:
## Acceptance Criteria
1. `pnpm paperclip onboard` is interactive-first and defaults to `local_trusted`.
1. `pnpm paperclipai onboard` is interactive-first and defaults to `local_trusted`.
2. authenticated mode is one runtime mode with `private/public` exposure guidance.
3. `pnpm paperclip doctor` works flagless with mode-aware checks.
3. `pnpm paperclipai doctor` works flagless with mode-aware checks.
4. no extra compatibility aliases for dropped naming variants.
5. Board identity is represented by real DB user/role/membership integration points, enabling consistent task assignment and permission behavior.

View File

@@ -104,7 +104,7 @@ Modules live in a top-level `modules/` directory. Each module is a pnpm workspac
Key fields:
- **`id`**: Unique identifier, used as the npm package name suffix (`@paperclip/mod-observability`)
- **`id`**: Unique identifier, used as the npm package name suffix (`@paperclipai/mod-observability`)
- **`slot`**: Optional exclusive category. If set, only one module with this slot can be active. Omit for modules that can coexist freely.
- **`hooks`**: Which core events this module subscribes to. Declared upfront so the core knows what to emit.
- **`routes.prefix`**: Mounted under `/api/modules/<prefix>`. The module owns this namespace.
@@ -118,7 +118,7 @@ Key fields:
The module's `src/index.ts` exports a `register` function that receives the module API:
```typescript
import type { ModuleAPI } from "@paperclip/core";
import type { ModuleAPI } from "@paperclipai/core";
import { createRouter } from "./routes.js";
import { onHeartbeat, onBudgetThreshold } from "./hooks.js";
@@ -236,7 +236,7 @@ This keeps the core fast and resilient. If you need pre-commit validation (e.g.,
```typescript
// modules/observability/src/hooks.ts
import type { Db } from "@paperclip/db";
import type { Db } from "@paperclipai/db";
import { tokenMetrics } from "./schema.js";
export function createHeartbeatHandler(db: Db) {
@@ -382,7 +382,7 @@ export const modulePages = [
{
path: "/observability",
label: "Observability",
component: lazy(() => import("@paperclip/mod-observability/ui")),
component: lazy(() => import("@paperclipai/mod-observability/ui")),
},
];
@@ -391,7 +391,7 @@ export const dashboardWidgets = [
id: "token-burn-rate",
label: "Token Burn Rate",
placement: "dashboard",
component: lazy(() => import("@paperclip/mod-observability/ui").then(m => ({ default: m.TokenBurnRateWidget }))),
component: lazy(() => import("@paperclipai/mod-observability/ui").then(m => ({ default: m.TokenBurnRateWidget }))),
},
];
```
@@ -587,10 +587,10 @@ The Company Store is a registry for discovering and installing modules and templ
### CLI Commands
```bash
pnpm paperclip store list # browse available modules and templates
pnpm paperclip store install <module-id> # install a module
pnpm paperclip store import <template-id> # import a company template
pnpm paperclip store export # export current company as template
pnpm paperclipai store list # browse available modules and templates
pnpm paperclipai store install <module-id> # install a module
pnpm paperclipai store import <template-id> # import a company template
pnpm paperclipai store export # export current company as template
```
---
@@ -627,7 +627,7 @@ pnpm paperclip store export # export current company as templat
### Phase 1: Core infrastructure
Add to `@paperclip/server`:
Add to `@paperclipai/server`:
1. **HookBus** — Event emitter with `register()` and `emit()`, using `Promise.allSettled`
2. **Module loader** — Scans `modules/`, validates manifests, calls `register(api)`
@@ -636,7 +636,7 @@ Add to `@paperclip/server`:
5. **Module migration runner** — Extends `db:migrate` to discover and run module migrations
6. **Emit hooks from core services** — Add `hookBus.emit()` calls to existing CRUD operations
Add to `@paperclip/ui`:
Add to `@paperclipai/ui`:
7. **Module page loader** — Reads module manifests, generates lazy routes
8. **Dashboard widget slots** — Render module-contributed widgets on the Dashboard page
@@ -644,7 +644,7 @@ Add to `@paperclip/ui`:
Add new package:
10. **`@paperclip/module-sdk`** — TypeScript types for `ModuleAPI`, `HookEvent`, `HookHandler`, manifest schema
10. **`@paperclipai/module-sdk`** — TypeScript types for `ModuleAPI`, `HookEvent`, `HookHandler`, manifest schema
### Phase 2: First module (observability)

View File

@@ -56,8 +56,8 @@ Add a single storage subsystem for Paperclip that supports:
### Acceptance Criteria
- `paperclip onboard` writes a valid `storage` config block by default.
- `paperclip configure --section storage` can switch between local and s3 modes.
- `paperclipai onboard` writes a valid `storage` config block by default.
- `paperclipai configure --section storage` can switch between local and s3 modes.
- Server startup reads storage config without env-only hacks.
## Phase 2: Server Storage Subsystem + Providers
@@ -161,7 +161,7 @@ Add a single storage subsystem for Paperclip that supports:
### Acceptance Criteria
- `paperclip doctor` reports actionable storage status.
- `paperclipai doctor` reports actionable storage status.
- Local single-user install works without extra cloud credentials.
- Cloud config supports S3-compatible endpoint without code changes.