Files
paperclip-docs/docs/reference/cli/company.md
Aron PrinsandClaude Opus 5 325c9eb819 seo: hand-write titles and descriptions for all 192 pages (#115)
## Why

The build derived `<title>` from the **sidebar label** and the
description from the **first paragraph clipped to 220 characters**. Both
are the wrong source. A sidebar label is written for a 240px-wide nav
that supplies its own context; a search result has none.

On production that produced:

- **7 pages titled "Overview"**, and 32 pages sharing a title with
another page
- **109 of 193 descriptions cut off mid-word**, and 174 over 160
characters (where Google truncates)

```
/guides/welcome/key-concepts/
  BEFORE (220):  ...and if you misunderstand them early, the product wi
```

Near-identical titles are the weak-differentiation signal behind
"crawled — currently not indexed", which is the exact symptom on this
property.

## What changed

`seo_title` and `seo_description` frontmatter on **every one of the 192
pages**, written by hand against each page's real content and optimised
for the click — lead with what the reader can do, name the concrete
things covered, no keyword stuffing, no repeating the title.

| | Before | After |
|---|---|---|
| Duplicate titles | 32 pages | **0** |
| Duplicate descriptions | — | **0** |
| Descriptions cut mid-word | 109 | **0** |
| Descriptions over 160 chars | 174 | **0** |
| Title length (incl. suffix) | up to "Overview" | **26–60** |
| Description length | 31–240 | **122–158** |

**No prose was rewritten.** I measured content overlap between the
colliding pairs first — only 17–23%, so these were always distinct pages
wearing the same label, not duplicate content. The `docs/` diff is **564
insertions, zero deletions**.

### Examples

```
BEFORE — all seven identical:  "Overview | Paperclip Docs"
AFTER:  "Adapters Overview"      "API Overview"        "CLI Overview"
        "Deployment Overview"    "Bundled Skills"      "Optional Skills"
        "Experimental Features"

BEFORE — both identical:  "Issues | Paperclip Docs"
AFTER:  "Issues: Creating and Tracking Work"   /guides/day-to-day/issues/
        "Issues API"                           /reference/api/issues/
```

```
/guides/getting-started/five-minute-path/
BEFORE (220):  The shortest route from a running Paperclip instance to an agent that has
               just completed work for you. After Paperclip is installed, the path takes
               about 5 minutes: create a company, hire a CEO agent, approve its first s
AFTER  (131):  Install Paperclip, create a company, hire a CEO agent, and approve its
               first strategy — a working AI company in about five minutes.
```

## Plumbing

- **`site/build-release.mjs`** prefers the authored fields; the derived
path stays only as a fallback the CI gate prevents ever reaching.
`<title>`, `<meta name="description">`, and the `TechArticle` JSON-LD
all read from the same source.
- **`site/app.js`** uses the same fields, so a client-side transition
cannot leave a different title on screen than the crawler was served.
Verified in-browser: navigating `/guides/day-to-day/issues/` →
`/reference/api/issues/` swaps to exactly the server-rendered values, no
JS errors.
- **`scripts/verify-seo-metadata.mjs`** (new) enforces presence,
uniqueness, length, complete-sentence endings, and the
frontmatter-safety rules. Wired into `docs:test` and CI as its own step.

I negative-tested the gate rather than assuming it works — it correctly
fails on a duplicate title, a mid-word description, and a missing field,
and passes again once restored.

## Automating it going forward

`skills/sync-docs/SKILL.md` now:

- gains an **"Authored SEO metadata"** section: why the fields exist,
the enforced rules as a table, and how to actually write them (title
names the subject not the nav position; description writes for the
click; never generate mechanically from the first paragraph — *that is
the fallback the gate exists to prevent*).
- requires both fields in the **page-authoring instruction** given to
every subagent, with guidance on when to update an existing description
versus leave it.
- runs `docs:test:seo-metadata` and `docs:test:crawlable-links` as
**fail-loud gates in Phase 7** before commit.
- documents all six build gates in the files/gates section.

So once this lands, a new page cannot ship without hand-written
metadata, and a title collision cannot reach production.

## Verified

All six verifiers pass:

```
Static route verification passed
Asset fingerprint verification passed
Hierarchical Skills nav verification passed
Skill source block verification passed (11 skill pages, 1 control page)
Crawlable-link contract passed: 40886 internal links across 194 pages resolve in one hop
Authored-SEO contract passed: 192 pages carry a unique, hand-written seo_title and seo_description
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-25 22:51:42 +02:00

249 lines
15 KiB
Markdown

---
paperclip_version: v2026.722.0
seo_title: CLI Company Commands
seo_description: Inspect and manage the top-level container that every agent, project, goal, issue, and budget lives inside, without leaving the terminal.
---
# Company Commands
Companies are the top-level container in Paperclip: every agent, project, goal, issue, and budget lives inside one. Use `paperclipai company` when you want to inspect companies, edit their settings and branding, move a whole company between instances as a portable package, pull feedback traces, or delete a company when the server policy allows it.
A board operator sees and manages every company on the instance. An agent persona is scoped to exactly one company, so company-scoped reads and the deletion guard rails resolve against that single company unless you pass an explicit ID.
> **Tip:** Add `--json` to any command when you are scripting. The human-readable output is summarized; the JSON output is the full server record.
---
## Common Options
Every command on this page accepts the standard client options: `--data-dir <path>`, `--api-base <url>`, `--api-key <token>`, `--context <path>`, `--profile <name>`, and `--json`. See [Common Options](./common-options.md) for how the API base and credentials are resolved.
Two commands — `feedback:list` and `feedback:export` — take the company as a required `-C, --company-id <id>` flag rather than a positional argument. The `import` command also accepts `-C, --company-id <id>` to name an existing target company. Everywhere else the company ID is a positional `<companyId>` argument.
---
## Read Commands
These are read-only and safe to run anywhere.
```sh
paperclipai company list
paperclipai company get <company-id>
paperclipai company current
paperclipai company stats
```
| Command | What it returns |
|---|---|
| `company list` | Every company you can see. Human output shows `id`, `name`, `status`, monthly budget and spend (in cents), and whether new agents require board approval. |
| `company get <companyId>` | The full record for one company. |
| `company current` | The full record for the company in scope, resolved from `-C, --company-id`, the context file, `PAPERCLIP_COMPANY_ID`, or the agent credential — no positional ID needed. |
| `company stats` | Instance-level company statistics from `/api/companies/stats`. |
`company list` with an agent credential returns only your scoped company; with a board credential it returns every company on the instance.
`company current` is the convenient way to read the company you are already scoped to: it resolves the current company ID the same way every other command does and returns the same full record as `company get <companyId>`. With an agent credential — which is bound to exactly one company — it always returns that company.
---
## Create and Update
`create`, `update`, and `branding:update` send raw JSON payloads through to the API. The CLI does not validate the shape — it forwards your `--payload-json` to the server, which applies the matching schema (`CreateCompany`, `UpdateCompany`, `UpdateCompanyBranding`). Build the object the API expects and pass it as a single JSON string.
```sh
paperclipai company create \
--payload-json '{"name":"Acme","issuePrefix":"ACME"}'
paperclipai company update <company-id> \
--payload-json '{"budgetMonthlyCents":500000}'
paperclipai company branding:update <company-id> \
--payload-json '{"primaryColor":"#1f6feb","logoUrl":"https://cdn.example.com/acme.png"}'
```
| Command | Method / route | Notes |
|---|---|---|
| `company create` | `POST /api/companies` | `--payload-json` is required. |
| `company update <companyId>` | `PATCH /api/companies/{id}` | `--payload-json` is required; send only the fields you want to change. |
| `company branding:update <companyId>` | `PATCH /api/companies/{id}/branding` | `--payload-json` is required; carries branding fields only. |
> **Note:** Because the payload is forwarded verbatim, a malformed or unknown field surfaces as a server-side validation error, not a CLI error. Use `--json` and read the response when a write is rejected.
---
## Archive
```sh
paperclipai company archive <company-id>
```
`company archive` posts to `/api/companies/{id}/archive` and takes no payload. Archiving is the reversible way to take a company out of active rotation. Reach for [`company delete`](#delete-a-company) only when you genuinely need the records gone and the server is configured to permit it.
---
## Export and Import
Paperclip companies are portable. `company export` writes a company to a folder of markdown-backed files (plus a `.paperclip.yaml` manifest and any binary assets), and `company import` reads that package back — from a local path, a `.zip`, or a GitHub repository — into a new or existing company.
The package is full-fidelity: alongside the company record, agents, projects, and skills, it carries tasks with their comments, labels, blocked-by relationships, documents, work products, and monitors, plus routines with their schedule triggers, and attachments as content-addressed files. Approvals, cost history, and activity log entries stay behind on the source instance — they describe what happened there rather than how the company is configured.
> **Note:** This pair of commands is how you move a company between instances. It replaces the retired host-to-host Cloud Sync, so there is no longer any need to connect one instance to another first. See [Export & Import](../../guides/power/export-import.md) for the walkthrough.
### Export a portable package
```sh
paperclipai company export <company-id> \
--out ./exports/acme \
--include company,agents,projects,issues,skills
```
| Flag | Use |
|---|---|
| `--out <path>` | Required. Output directory the package is written to. |
| `--include <values>` | Comma-separated set of `company`, `agents`, `projects`, `issues` (alias `tasks`), `skills`. Defaults to `company,agents`. |
| `--skills <values>` | Comma-separated skill slugs/keys to include. |
| `--projects <values>` | Comma-separated project shortnames/IDs to include. |
| `--issues <values>` | Comma-separated issue identifiers/IDs to include. |
| `--project-issues <values>` | Comma-separated project shortnames/IDs whose issues should be exported. |
| `--expand-referenced-skills` | Vendor skill contents into the package instead of exporting upstream references. Defaults to off. |
| `--force` | Overwrite a non-empty output directory without the interactive confirmation. Defaults to off. |
The default `--include` is deliberately small (`company,agents`) so a quick export does not drag along every task. Widen it explicitly when you want a full clone. If the output directory already contains files, the command asks you to confirm an overwrite in an interactive terminal, and refuses outright in a non-interactive one — pass `--force` to overwrite anyway, or choose an empty directory for headless runs. `--force` is what you want for automated exports (for example the nightly backup routine) that write into a directory that legitimately still holds files.
On success the command prints the resolved output path, the package root, the number of files written, the `.paperclip.yaml` extension path, and a warning count.
### Import a package
```sh
paperclipai company import ./exports/acme \
--target new \
--new-company-name "Acme (imported)"
paperclipai company import https://github.com/acme/company-pack \
--target existing --company-id <company-id> \
--collision replace --yes
```
The source argument is a local directory, a local `.zip` file, a GitHub URL, or `owner/repo[/path]` shorthand. Generic HTTP URLs are rejected — only GitHub / GitHub Enterprise URLs and local paths are supported.
| Flag | Use |
|---|---|
| `--include <values>` | Comma-separated include set (`company,agents,projects,issues,tasks,skills`). When omitted, the default import set is all five groups. |
| `--target <mode>` | `new` to create a company, `existing` to import into one you already have. Inferred from `--company-id`/context when omitted. |
| `-C, --company-id <id>` | The existing target company. Required when `--target existing`. |
| `--new-company-name <name>` | Name override when `--target new`. |
| `--agents <list>` | Comma-separated agent slugs to import, or `all`. Defaults to `all`. |
| `--collision <mode>` | How to handle existing matches: `rename` (default), `skip`, or `replace`. |
| `--ref <value>` | Git ref (branch, tag, or commit) for GitHub sources. Only valid for GitHub imports. |
| `--paperclip-url <url>` | Alias for `--api-base` on this command. |
| `--yes` | Accept the default selection and skip the pre-import confirmation prompt. |
| `--dry-run` | Run the preview only; apply nothing. |
The import always runs a server-side preview first and shows you the plan — package contents, per-group create/update/skip counts, sample entities, and any warnings or errors — before anything is written. In an interactive terminal without `--include` or `--yes`, you also get a selection menu to pick exactly which projects, tasks, agents, and skills to bring in. With `--dry-run` the command stops after the preview.
> **Warning:** Applying an import is gated. With `--json` you must pass `--yes` (run `--dry-run` first to inspect the preview as JSON). From a non-interactive terminal you must also pass `--yes`. Without `--yes` in an interactive terminal, the CLI prompts for explicit confirmation before applying.
When an imported agent has a `process` adapter with no explicit adapter, the CLI currently falls back to the `claude-local` adapter and tells you so in the preview/result info block.
Before any record is written, the server verifies every attachment in the package against the content hash it is filed under. A mismatch aborts the whole import with a message naming the offending file, so a corrupted or tampered-with package cannot leave a half-imported company behind. The CLI's folder import is also the recommended path for a very large company — the UI itself points you here when an upload is too big to preview in the browser.
Imported agents always come in with their timer heartbeats switched off, regardless of what the package says, so nothing starts spending before you have reviewed it.
### Raw API passthrough variants
If you would rather drive the server's portability routes directly with a hand-built payload, four passthrough commands post your `--payload-json` to the raw routes. These do not read or write local files — they are the API endpoints behind `export`/`import` exposed for scripting.
```sh
paperclipai company export:preview <company-id> --payload-json '{...}'
paperclipai company export:api <company-id> --payload-json '{...}'
paperclipai company import:preview <company-id> --payload-json '{...}'
paperclipai company import:apply <company-id> --payload-json '{...}'
```
| Command | Route |
|---|---|
| `company export:preview <companyId>` | `POST /api/companies/{id}/exports/preview` |
| `company export:api <companyId>` | `POST /api/companies/{id}/exports` |
| `company import:preview <companyId>` | `POST /api/companies/{id}/imports/preview` |
| `company import:apply <companyId>` | `POST /api/companies/{id}/imports/apply` |
Prefer the high-level `export`/`import` commands for day-to-day work; they handle file I/O, source resolution, previews, and confirmation for you. Use the passthrough variants only when you need precise control over the request body.
---
## Feedback Traces
Feedback traces capture votes and reviews recorded against agent work. Use these two commands to read them or pull them out for analysis. Both require `-C, --company-id <id>`.
```sh
paperclipai company feedback:list --company-id <company-id> --status open --vote down
paperclipai company feedback:export --company-id <company-id> --from 2026-01-01 --format ndjson --out ./feedback.ndjson
```
| Flag | Use |
|---|---|
| `--target-type <type>` | Filter by target type. |
| `--vote <vote>` | Filter by vote value. |
| `--status <status>` | Filter by trace status. |
| `--project-id <id>` | Filter by project. |
| `--issue-id <id>` | Filter by issue. |
| `--from <iso8601>` | Only traces created at or after this timestamp. |
| `--to <iso8601>` | Only traces created at or before this timestamp. |
| `--shared-only` | Only traces eligible for sharing/export. |
| `--include-payload` | Include stored payload snapshots in the response. |
`feedback:list` prints a compact row per trace (`id`, issue, vote, status, target type, target label) or the full records with `--json`. `feedback:export` adds two flags:
| Flag | Use |
|---|---|
| `--out <path>` | Write the export to a file instead of stdout. |
| `--format <format>` | `json` or `ndjson`. Defaults to `ndjson`. |
`feedback:export` includes payload snapshots by default (unlike `feedback:list`, where you opt in with `--include-payload`). When you pass `--out`, the command writes the file and reports the count; otherwise it streams the serialized traces to stdout, which makes it easy to pipe into another tool.
---
## Delete a Company
`company delete` is destructive and intentionally hard to trigger by accident. It removes a company and its records permanently.
```sh
paperclipai company delete PAP --yes --confirm PAP
paperclipai company delete 5cbe79ee-acb3-4597-896e-7662742593cd \
--yes --confirm 5cbe79ee-acb3-4597-896e-7662742593cd
```
The selector argument is either a company ID (UUID) or an issue prefix/shortname (for example `PAP`).
| Flag | Use |
|---|---|
| `--by <mode>` | How to interpret the selector: `auto` (default), `id`, or `prefix`. |
| `--yes` | Required safety flag. Deletion fails without it. |
| `--confirm <value>` | Required. Must equal the target company's ID or its issue prefix. |
### Guard rails
Deletion has to clear four separate checks, by design:
1. **Server policy.** The instance must have `PAPERCLIP_ENABLE_COMPANY_DELETION` enabled. If it is off, the API rejects the delete regardless of your flags.
2. **`--yes`.** Without it the command errors immediately — no API call is made.
3. **`--confirm <value>`.** The value must match the resolved company's ID or its issue prefix (prefix match is case-insensitive). A mismatch errors with the expected ID and prefix so you can correct it.
4. **Unambiguous resolution.** In `auto` mode, a selector that matches both a different company's ID and another's prefix is rejected as ambiguous — re-run with `--by id` or `--by prefix`.
Resolution order: the CLI tries an ID lookup when the selector looks like a UUID (or `--by id`), then your scoped company from context, then a board-wide company list. With an agent credential, the board-wide lookup is unavailable — deletion is company-scoped, so use the current company's ID or prefix (via `--company-id` or `PAPERCLIP_COMPANY_ID`), not another company's.
> **Warning:** There is no undo. If you only need to take a company out of rotation, use [`company archive`](#archive) instead.
On success the command prints `ok: true` with the deleted company's ID, name, and prefix.
---
## See also
- [Common Options](./common-options.md) — API base resolution, credentials, and the flags every command shares
- [Output and Scripting](./output-and-scripting.md) — using `--json` and piping CLI output
- [Project Commands](./project.md) — manage the projects inside a company
- [Agent Commands](./agent.md) — inspect agents and set up a local agent CLI session
- [Feedback Commands](./feedback.md) — work with feedback traces beyond the company-scoped views
- [Your First Company via CLI](../../guides/getting-started/your-first-company.md) — end-to-end walkthrough