Files
PaperClipAI/docs/guides/board-operator/importing-and-exporting.md
T
Devin Foley 9c03443c48 feat: hideable company settings pages, with import floored on cloud-managed instances (#12199)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The app can run self-hosted or as a cloud-managed instance, where a
hosting platform provisions the instance with its company already
materialized (the existing `isCloudManagedInstance()` predicate and
`cloud_managed` floors)
> - The company Import/Export surface lets an operator materialize whole
companies from an export bundle; on a cloud-managed instance this
bypasses the existing `cloud_managed` company-creation floor and
conflicts with platform-owned provisioning
> - Importing should be disabled on cloud-managed instances, while
export stays open as the data-portability escape hatch
> - This pull request floors every import route with 403
`code=cloud_managed` on cloud-managed instances and hides the Import UI
there, using the existing predicate and the established floor pattern
> - It also extends the operator-hidden settings registry with keys for
every top-level company settings page, so a hosting operator can hide
any of them with `PAPERCLIP_HIDDEN_SETTINGS` alone next time
> - The benefit is one consistent managed-instance policy: cloud-managed
instances cannot import companies, self-hosted installs keep the full
import surface unchanged

## Linked Issues or Issue Description

No public issue exists; the underlying problem follows the enhancement
template.

**What existing behavior does this improve?**

The company import surface (`/api/companies/import*`,
`/api/companies/:companyId/imports/*`) and its UI entry points on
cloud-managed instances.

**Subsystem affected**

Server routes (`server/src/routes/companies.ts`) and UI navigation/pages
(settings sidebar, settings tabs, org chart, `/company/import` route).

**Current behavior**

A cloud-managed instance floors direct company creation (`POST
/api/companies` answers 403 `cloud_managed`), but the import routes
still accept company bundles, so an import can materialize companies the
hosting platform did not provision. The UI offers Import entry points
that lead to a surface that is not available on cloud-managed instances.

**Proposed behavior**

On instances where `isCloudManagedInstance()` is true, every import
route answers 403 `code=cloud_managed` before auth and body work, and
the Import UI (sidebar entry, settings tab, org-chart button,
`/company/import` route) is hidden or redirected. Export remains fully
available. Self-hosted instances are unchanged.

**Reason and benefit**

Cloud-managed instances keep one consistent provisioning authority, and
users do not see an Import surface that dead-ends in a 403.

## What Changed

- `server/src/routes/companies.ts`: a router-level floor mounted at the
`/import` and `/:companyId/imports` prefixes. It covers the single-shot
upload, preview, job polling, chunked transfer
declare/part-upload/status/preview/apply, and the agent-safe per-company
import routes. It throws `forbidden(..., { code: "cloud_managed" })` on
cloud-managed instances, or `403 settings_operator_managed` when the
operator hides `company.import` — both before auth and body validation,
mirroring the company-creation floor.
- `packages/shared/src/settings-visibility.ts`: new
`HIDEABLE_COMPANY_PAGES` registry group — `company.members`,
`company.invites`, `company.secrets`, `company.export`, `company.import`
— with a `hidesCompanyPage` helper. The company General page stays
non-hideable (settings root). `company.import` floors its API; the other
keys are UI-visibility only, as documented in the registry, so
membership/invite/secret/export APIs stay live for agents.
- `ui/src/components/CloudManagedPageGate.tsx` (new): route gate that
redirects cloud-managed instances to `/company/settings`, modeled on
`HiddenSettingsPageGate`.
- `ui/src/App.tsx`: wraps the `company/import` route in
`CloudManagedPageGate`.
- `ui/src/components/CompanySettingsSidebar.tsx`,
`ui/src/components/access/CompanySettingsNav.tsx`,
`ui/src/pages/OrgChart.tsx`: hide the Import entry points when
`useCloudInstance()` reports a managed instance, and honor the new
`company.*` hidden-settings keys for every company page entry (sidebar
item, tab, org-chart buttons).
- `ui/src/App.tsx`: `HiddenSettingsPageGate` route gates for the members
(incl. the legacy access route), invites, secrets, export, and import
pages under their `company.*` keys.
- `docs/deploy/environment-variables.md`: documents the new keys and
their semantics; the CLI and board-operator guides note that import is
unavailable on cloud-managed instances.
- Tests: new `server/src/__tests__/company-import-cloud-floor.test.ts`
and `ui/src/components/CloudManagedPageGate.test.tsx`, registry cases in
`packages/shared/src/settings-visibility.test.ts`, plus cloud and
hidden-key cases in the sidebar, settings-nav, and org-chart suites.

## Verification

- TypeScript typechecks pass for every workspace package (`tsc` in
shared, server, ui; the runner's Rust leg needs a local cargo toolchain
and is covered by CI).
- `pnpm test` on this branch fails only in 9 files that also fail on a
clean `origin/master` checkout on the same machine
(environment-dependent suites: live-listener probes,
workspace/native-runtime spawns, skill materialization). Zero
branch-only failures against that baseline; every suite touched by this
change passes.
- `server/src/__tests__/company-import-cloud-floor.test.ts` asserts:
every import route answers 403 `cloud_managed` under the server-token
signal; the managed-config signal alone also floors; every import route
answers 403 `settings_operator_managed` when `company.import` is hidden;
hiding other company pages leaves import open; the floor applies before
auth and body validation; export stays open on cloud-managed instances;
self-hosted import preview and job polling still work.
- `packages/shared/src/settings-visibility.test.ts` covers the new
`company.*` keys and `hidesCompanyPage`.
- UI suites assert the Import tab, sidebar entry, and org-chart button
disappear on a cloud-managed instance while Export stays, that
`/company/import` redirects through the gate, and that the `company.*`
keys hide their sidebar entries and tabs.

## Risks

- Low risk for self-hosted installs: the floor is inert unless a cloud
signal (`PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN` or
`PAPERCLIP_MANAGED_CONFIG`) is present, and the self-hosted paths are
regression-tested.
- On cloud-managed instances this is a deliberate behavioral removal:
import (including agent-driven safe imports and resumable transfers)
stops working the moment an instance runs this build. In-flight chunked
transfers on such instances cannot be applied afterward; they answer
403.
- CLI import commands against a cloud-managed instance now fail with the
`cloud_managed` error; the message names the reason.
- The new `company.*` keys change nothing unless an operator sets them:
`PAPERCLIP_HIDDEN_SETTINGS` unset keeps behavior identical, and older
images ignore unknown keys by design. The four non-import company keys
hide UI only; their APIs stay live, which the registry documents
explicitly.

## Model Used

Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding session with tool use (code search, editing, local test
execution).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-25 16:06:35 -07:00

9.4 KiB

title, summary
title summary
Importing & Exporting Companies Export companies to portable packages and import them from local paths or GitHub

Paperclip companies can be exported to portable markdown packages and imported from local directories or GitHub repositories. This lets you share company configurations, duplicate setups, and version-control your agent teams.

Package Format

Exported packages follow the Agent Companies specification and use a markdown-first structure:

my-company/
├── COMPANY.md          # Company metadata
├── agents/
│   ├── ceo/AGENT.md    # Agent instructions + frontmatter
│   └── cto/AGENT.md
├── projects/
│   └── main/PROJECT.md
├── skills/
│   └── review/SKILL.md
├── tasks/
│   └── onboarding/TASK.md
└── .paperclip.yaml     # Adapter config, env inputs, routines
  • COMPANY.md defines company name, description, and metadata.
  • AGENT.md files contain agent identity, role, and instructions.
  • SKILL.md files are compatible with the Agent Skills ecosystem.
  • .paperclip.yaml holds Paperclip-specific config (adapter types, env inputs, budgets) as an optional sidecar.

Export & Import in the App

Both flows are also available in the web UI as company settings pages: Export and Import appear in the company settings navigation.

Cloud-managed instances: when a hosting platform manages the instance, the company is provisioned by the platform and importing is disabled — the Import page and buttons are hidden, and every import API route answers 403 with code: "cloud_managed". Export stays available, so you can always take your company's data with you. Self-hosted instances keep the full import surface.

The Export page lets you pick exactly which files go into the bundle before downloading it. Above the file tree it shows a "Not included in this export" panel — the export fidelity report — listing data the bundle will not carry (for example attachments, approvals, cost history, or activity log entries), with blocking issues highlighted.

The Import page previews the package, lets you resolve name collisions and adapter assignments, and applies the import. A "Start imported agents and routines paused" checkbox (on by default) makes imported agents and routines land paused instead of live. After the import finishes, an "Activate imported agents and routines" panel lists everything that was imported paused so you can resume the agents and activate the routines you select — nothing starts running until you say so.

Exporting a Company

Export a company into a portable folder:

paperclipai company export <company-id> --out ./my-export

Options

Option Description Default
--out <path> Output directory (required) —
--include <values> Comma-separated set: company, agents, projects, issues, tasks, skills company,agents
--skills <values> Export only specific skill slugs all
--projects <values> Export only specific project shortnames or IDs all
--issues <values> Export specific issue identifiers or IDs none
--project-issues <values> Export issues belonging to specific projects none
--expand-referenced-skills Vendor skill file contents instead of keeping upstream references false

Examples

# Export company with agents and projects
paperclipai company export abc123 --out ./backup --include company,agents,projects

# Export everything including tasks and skills
paperclipai company export abc123 --out ./full-export --include company,agents,projects,tasks,skills

# Export only specific skills
paperclipai company export abc123 --out ./skills-only --include skills --skills review,deploy

What Gets Exported

  • Company name, description, and metadata
  • Agent names, roles, reporting structure, and instructions
  • Project definitions and workspace config
  • Task/issue descriptions (when included)
  • Skill packages (as references or vendored content)
  • Adapter type and env input declarations in .paperclip.yaml

Secret values, machine-local paths, and database IDs are never exported.

Importing a Company

Import from a local directory, GitHub URL, or GitHub shorthand:

# From a local folder
paperclipai company import ./my-export

# From a GitHub URL
paperclipai company import https://github.com/org/repo

# From a GitHub subfolder
paperclipai company import https://github.com/org/repo/tree/main/companies/acme

# From GitHub shorthand
paperclipai company import org/repo
paperclipai company import org/repo/companies/acme

Options

Option Description Default
--target <mode> new (create a new company) or existing (merge into existing) inferred from context
--company-id <id> Target company ID for --target existing current context
--new-company-name <name> Override company name for --target new from package
--include <values> Comma-separated set: company, agents, projects, issues, tasks, skills auto-detected
--agents <list> Comma-separated agent slugs to import, or all all
--collision <mode> How to handle name conflicts: rename, skip, or replace rename
--ref <value> Git ref for GitHub imports (branch, tag, or commit) default branch
--dry-run Preview what would be imported without applying false
--yes Skip the interactive confirmation prompt false
--json Output result as JSON false

Target Modes

  • new — Creates a fresh company from the package. Good for duplicating a company template.
  • existing — Merges the package into an existing company. Use --company-id to specify the target.

If --target is not specified, Paperclip infers it: if a --company-id is provided (or one exists in context), it defaults to existing; otherwise new.

Collision Strategies

When importing into an existing company, agent or project names may conflict with existing ones:

  • rename (default) — Appends a suffix to avoid conflicts (e.g., ceo becomes ceo-2).
  • skip — Skips entities that already exist.
  • replace — Overwrites existing entities. Only available for non-safe imports (not available through the CEO API).

Interactive Selection

When running interactively (no --yes or --json flags), the import command shows a selection picker before applying. You can choose exactly which agents, projects, skills, and tasks to import using a checkbox interface.

Preview Before Applying

Always preview first with --dry-run:

paperclipai company import org/repo --target existing --company-id abc123 --dry-run

The preview shows:

  • Package contents — How many agents, projects, tasks, and skills are in the source
  • Import plan — What will be created, renamed, skipped, or replaced
  • Env inputs — Environment variables that may need values after import
  • Warnings — Potential issues like missing skills or unresolved references

Imported agents always land with timer heartbeats disabled. Assignment/on-demand wake behavior from the package is preserved, but scheduled runs stay off until a board operator re-enables them.

Imports can additionally request pauseAutomations (the default in the app's Import page) so imported agents and routines land fully paused. Use the post-import activation panel — or resume the agents and activate the routines individually — when you are ready for them to run.

Common Workflows

Clone a company template from GitHub:

paperclipai company import org/company-templates/engineering-team \
  --target new \
  --new-company-name "My Engineering Team"

Add agents from a package into your existing company:

paperclipai company import ./shared-agents \
  --target existing \
  --company-id abc123 \
  --include agents \
  --collision rename

Import a specific branch or tag:

paperclipai company import org/repo --ref v2.0.0 --dry-run

Non-interactive import (CI/scripts):

paperclipai company import ./package \
  --target new \
  --yes \
  --json

API Endpoints

The CLI commands use these API endpoints under the hood:

Action Endpoint
Export company POST /api/companies/{companyId}/export
Export fidelity report GET /api/companies/{companyId}/export/fidelity
Preview import (existing company) POST /api/companies/{companyId}/imports/preview
Apply import (existing company) POST /api/companies/{companyId}/imports/apply
Preview import (new company) POST /api/companies/import/preview
Apply import (new company) POST /api/companies/import

Import apply requests accept pauseAutomations: true to create imported agents and routines in a paused state.

CEO agents can also use the safe import routes (/imports/preview and /imports/apply) which enforce non-destructive rules: replace is rejected, collisions resolve with rename or skip, and issues are always created as new.

GitHub Sources

Paperclip supports several GitHub URL formats:

  • Full URL: https://github.com/org/repo
  • Subfolder URL: https://github.com/org/repo/tree/main/path/to/company
  • Shorthand: org/repo
  • Shorthand with path: org/repo/path/to/company

Use --ref to pin to a specific branch, tag, or commit hash when importing from GitHub.