Refine company package export format
This commit is contained in:
parent
56a34a8f8a
commit
1d8f514d10
13 changed files with 1684 additions and 304 deletions
|
|
@ -32,6 +32,9 @@ interface CompanyDeleteOptions extends BaseClientOptions {
|
||||||
interface CompanyExportOptions extends BaseClientOptions {
|
interface CompanyExportOptions extends BaseClientOptions {
|
||||||
out?: string;
|
out?: string;
|
||||||
include?: string;
|
include?: string;
|
||||||
|
projects?: string;
|
||||||
|
issues?: string;
|
||||||
|
projectIssues?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompanyImportOptions extends BaseClientOptions {
|
interface CompanyImportOptions extends BaseClientOptions {
|
||||||
|
|
@ -54,14 +57,16 @@ function normalizeSelector(input: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInclude(input: string | undefined): CompanyPortabilityInclude {
|
function parseInclude(input: string | undefined): CompanyPortabilityInclude {
|
||||||
if (!input || !input.trim()) return { company: true, agents: true };
|
if (!input || !input.trim()) return { company: true, agents: true, projects: false, issues: false };
|
||||||
const values = input.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
const values = input.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
||||||
const include = {
|
const include = {
|
||||||
company: values.includes("company"),
|
company: values.includes("company"),
|
||||||
agents: values.includes("agents"),
|
agents: values.includes("agents"),
|
||||||
|
projects: values.includes("projects"),
|
||||||
|
issues: values.includes("issues"),
|
||||||
};
|
};
|
||||||
if (!include.company && !include.agents) {
|
if (!include.company && !include.agents && !include.projects && !include.issues) {
|
||||||
throw new Error("Invalid --include value. Use one or both of: company,agents");
|
throw new Error("Invalid --include value. Use one or more of: company,agents,projects,issues");
|
||||||
}
|
}
|
||||||
return include;
|
return include;
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +80,11 @@ function parseAgents(input: string | undefined): "all" | string[] {
|
||||||
return Array.from(new Set(values));
|
return Array.from(new Set(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseCsvValues(input: string | undefined): string[] {
|
||||||
|
if (!input || !input.trim()) return [];
|
||||||
|
return Array.from(new Set(input.split(",").map((part) => part.trim()).filter(Boolean)));
|
||||||
|
}
|
||||||
|
|
||||||
function isHttpUrl(input: string): boolean {
|
function isHttpUrl(input: string): boolean {
|
||||||
return /^https?:\/\//i.test(input.trim());
|
return /^https?:\/\//i.test(input.trim());
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +102,10 @@ async function collectPackageFiles(root: string, current: string, files: Record<
|
||||||
await collectPackageFiles(root, absolutePath, files);
|
await collectPackageFiles(root, absolutePath, files);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
if (!entry.isFile()) continue;
|
||||||
|
const isMarkdown = entry.name.endsWith(".md");
|
||||||
|
const isPaperclipYaml = entry.name === ".paperclip.yaml" || entry.name === ".paperclip.yml";
|
||||||
|
if (!isMarkdown && !isPaperclipYaml) continue;
|
||||||
const relativePath = path.relative(root, absolutePath).replace(/\\/g, "/");
|
const relativePath = path.relative(root, absolutePath).replace(/\\/g, "/");
|
||||||
files[relativePath] = await readFile(absolutePath, "utf8");
|
files[relativePath] = await readFile(absolutePath, "utf8");
|
||||||
}
|
}
|
||||||
|
|
@ -261,14 +274,22 @@ export function registerCompanyCommands(program: Command): void {
|
||||||
.description("Export a company into a portable markdown package")
|
.description("Export a company into a portable markdown package")
|
||||||
.argument("<companyId>", "Company ID")
|
.argument("<companyId>", "Company ID")
|
||||||
.requiredOption("--out <path>", "Output directory")
|
.requiredOption("--out <path>", "Output directory")
|
||||||
.option("--include <values>", "Comma-separated include set: company,agents", "company,agents")
|
.option("--include <values>", "Comma-separated include set: company,agents,projects,issues", "company,agents")
|
||||||
|
.option("--projects <values>", "Comma-separated project shortnames/ids to export")
|
||||||
|
.option("--issues <values>", "Comma-separated issue identifiers/ids to export")
|
||||||
|
.option("--project-issues <values>", "Comma-separated project shortnames/ids whose issues should be exported")
|
||||||
.action(async (companyId: string, opts: CompanyExportOptions) => {
|
.action(async (companyId: string, opts: CompanyExportOptions) => {
|
||||||
try {
|
try {
|
||||||
const ctx = resolveCommandContext(opts);
|
const ctx = resolveCommandContext(opts);
|
||||||
const include = parseInclude(opts.include);
|
const include = parseInclude(opts.include);
|
||||||
const exported = await ctx.api.post<CompanyPortabilityExportResult>(
|
const exported = await ctx.api.post<CompanyPortabilityExportResult>(
|
||||||
`/api/companies/${companyId}/export`,
|
`/api/companies/${companyId}/export`,
|
||||||
{ include },
|
{
|
||||||
|
include,
|
||||||
|
projects: parseCsvValues(opts.projects),
|
||||||
|
issues: parseCsvValues(opts.issues),
|
||||||
|
projectIssues: parseCsvValues(opts.projectIssues),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!exported) {
|
if (!exported) {
|
||||||
throw new Error("Export request returned no data");
|
throw new Error("Export request returned no data");
|
||||||
|
|
@ -280,6 +301,7 @@ export function registerCompanyCommands(program: Command): void {
|
||||||
out: path.resolve(opts.out!),
|
out: path.resolve(opts.out!),
|
||||||
rootPath: exported.rootPath,
|
rootPath: exported.rootPath,
|
||||||
filesWritten: Object.keys(exported.files).length,
|
filesWritten: Object.keys(exported.files).length,
|
||||||
|
paperclipExtensionPath: exported.paperclipExtensionPath,
|
||||||
warningCount: exported.warnings.length,
|
warningCount: exported.warnings.length,
|
||||||
},
|
},
|
||||||
{ json: ctx.json },
|
{ json: ctx.json },
|
||||||
|
|
@ -300,7 +322,7 @@ export function registerCompanyCommands(program: Command): void {
|
||||||
.command("import")
|
.command("import")
|
||||||
.description("Import a portable markdown company package from local path, URL, or GitHub")
|
.description("Import a portable markdown company package from local path, URL, or GitHub")
|
||||||
.requiredOption("--from <pathOrUrl>", "Source path or URL")
|
.requiredOption("--from <pathOrUrl>", "Source path or URL")
|
||||||
.option("--include <values>", "Comma-separated include set: company,agents", "company,agents")
|
.option("--include <values>", "Comma-separated include set: company,agents,projects,issues", "company,agents")
|
||||||
.option("--target <mode>", "Target mode: new | existing")
|
.option("--target <mode>", "Target mode: new | existing")
|
||||||
.option("-C, --company-id <id>", "Existing target company ID")
|
.option("-C, --company-id <id>", "Existing target company ID")
|
||||||
.option("--new-company-name <name>", "Name override for --target new")
|
.option("--new-company-name <name>", "Name override for --target new")
|
||||||
|
|
|
||||||
|
|
@ -810,20 +810,27 @@ V1 is complete only when all criteria are true:
|
||||||
|
|
||||||
V1 supports company import/export using a portable package contract:
|
V1 supports company import/export using a portable package contract:
|
||||||
|
|
||||||
- exactly one JSON entrypoint: `paperclip.manifest.json`
|
- markdown-first package rooted at `COMPANY.md`
|
||||||
- all other package files are markdown with frontmatter
|
- implicit folder discovery by convention
|
||||||
- agent convention:
|
- `.paperclip.yaml` sidecar for Paperclip-specific fidelity
|
||||||
- `agents/<slug>/AGENTS.md` (required for V1 export/import)
|
- canonical base package is vendor-neutral and aligned with `docs/companies/companies-spec.md`
|
||||||
- `agents/<slug>/HEARTBEAT.md` (optional, import accepted)
|
- common conventions:
|
||||||
- `agents/<slug>/*.md` (optional, import accepted)
|
- `agents/<slug>/AGENTS.md`
|
||||||
|
- `teams/<slug>/TEAM.md`
|
||||||
|
- `projects/<slug>/PROJECT.md`
|
||||||
|
- `projects/<slug>/tasks/<slug>/TASK.md`
|
||||||
|
- `tasks/<slug>/TASK.md`
|
||||||
|
- `skills/<slug>/SKILL.md`
|
||||||
|
|
||||||
Export/import behavior in V1:
|
Export/import behavior in V1:
|
||||||
|
|
||||||
- export includes company metadata and/or agents based on selection
|
- export emits a clean vendor-neutral markdown package plus `.paperclip.yaml`
|
||||||
- export strips environment-specific paths (`cwd`, local instruction file paths)
|
- projects and starter tasks are opt-in export content rather than default package content
|
||||||
- export never includes secret values; secret requirements are reported
|
- export strips environment-specific paths (`cwd`, local instruction file paths, inline prompt duplication)
|
||||||
|
- export never includes secret values; env inputs are reported as portable declarations instead
|
||||||
- import supports target modes:
|
- import supports target modes:
|
||||||
- create a new company
|
- create a new company
|
||||||
- import into an existing company
|
- import into an existing company
|
||||||
- import supports collision strategies: `rename`, `skip`, `replace`
|
- import supports collision strategies: `rename`, `skip`, `replace`
|
||||||
- import supports preview (dry-run) before apply
|
- import supports preview (dry-run) before apply
|
||||||
|
- GitHub imports warn on unpinned refs instead of blocking
|
||||||
|
|
|
||||||
|
|
@ -26,24 +26,26 @@ This plan is about implementation and rollout inside Paperclip.
|
||||||
|
|
||||||
## 2. Executive Summary
|
## 2. Executive Summary
|
||||||
|
|
||||||
Paperclip already has a V1 portability feature:
|
Paperclip already has portability primitives in the repo:
|
||||||
|
|
||||||
- server import/export/preview APIs
|
- server import/export/preview APIs
|
||||||
- CLI import/export commands
|
- CLI import/export commands
|
||||||
- a `paperclip.manifest.json` plus markdown payload format
|
- shared portability types and validators
|
||||||
- company metadata + agent portability only
|
|
||||||
|
|
||||||
That is useful, but it is not the right long-term authoring format.
|
Those primitives are being cut over to the new package model rather than extended for backward compatibility.
|
||||||
|
|
||||||
The new direction is:
|
The new direction is:
|
||||||
|
|
||||||
1. markdown-first package authoring
|
1. markdown-first package authoring
|
||||||
2. GitHub repo or local folder as the default source of truth
|
2. GitHub repo or local folder as the default source of truth
|
||||||
3. the company package model is explicitly an extension of Agent Skills
|
3. a vendor-neutral base package spec for agent-company runtimes, not just Paperclip
|
||||||
4. no future dependency on `paperclip.manifest.json`
|
4. the company package model is explicitly an extension of Agent Skills
|
||||||
5. package graph resolution at import time
|
5. no future dependency on `paperclip.manifest.json`
|
||||||
6. entity-level import UI with dependency-aware tree selection
|
6. implicit folder discovery by convention for the common case
|
||||||
7. adapter-aware skill sync surfaces so Paperclip can read, diff, enable, disable, and reconcile skills where the adapter supports it
|
7. an always-emitted `.paperclip.yaml` sidecar for high-fidelity Paperclip-specific details
|
||||||
|
8. package graph resolution at import time
|
||||||
|
9. entity-level import UI with dependency-aware tree selection
|
||||||
|
10. adapter-aware skill sync surfaces so Paperclip can read, diff, enable, disable, and reconcile skills where the adapter supports it
|
||||||
|
|
||||||
## 3. Product Goals
|
## 3. Product Goals
|
||||||
|
|
||||||
|
|
@ -55,6 +57,7 @@ The new direction is:
|
||||||
- company definition
|
- company definition
|
||||||
- org subtree / team definition
|
- org subtree / team definition
|
||||||
- agent definitions
|
- agent definitions
|
||||||
|
- optional starter projects and tasks
|
||||||
- reusable skills
|
- reusable skills
|
||||||
- A user can import into:
|
- A user can import into:
|
||||||
- a new company
|
- a new company
|
||||||
|
|
@ -66,6 +69,7 @@ The new direction is:
|
||||||
- what is referenced externally
|
- what is referenced externally
|
||||||
- what needs secrets or approvals
|
- what needs secrets or approvals
|
||||||
- Export preserves attribution, licensing, and pinned upstream references.
|
- Export preserves attribution, licensing, and pinned upstream references.
|
||||||
|
- Export produces a clean vendor-neutral package plus a Paperclip sidecar.
|
||||||
- `companies.sh` can later act as a discovery/index layer over repos implementing this format.
|
- `companies.sh` can later act as a discovery/index layer over repos implementing this format.
|
||||||
|
|
||||||
### 3.2 Non-Goals
|
### 3.2 Non-Goals
|
||||||
|
|
@ -92,11 +96,11 @@ Current implementation exists here:
|
||||||
|
|
||||||
Current product limitations:
|
Current product limitations:
|
||||||
|
|
||||||
1. Portability model is only `company` + `agents`.
|
1. Import/export UX still needs deeper tree-selection and skill/package management polish.
|
||||||
2. Current import/export contract is JSON-entrypoint-first.
|
2. Adapter-specific skill sync remains uneven across adapters and must degrade cleanly when unsupported.
|
||||||
3. UI API methods exist but there is no real Company Settings import/export UX.
|
3. Projects and starter tasks should stay opt-in on export rather than default package content.
|
||||||
4. Import is lossy relative to export.
|
4. Import/export still needs stronger coverage around attribution, pin verification, and executable-package warnings.
|
||||||
5. The current markdown frontmatter parser is too primitive for the richer package model.
|
5. The current markdown frontmatter parser is intentionally lightweight and should stay constrained to the documented shape.
|
||||||
|
|
||||||
## 5. Canonical Package Direction
|
## 5. Canonical Package Direction
|
||||||
|
|
||||||
|
|
@ -107,6 +111,8 @@ The canonical authoring format becomes a markdown-first package rooted in one of
|
||||||
- `COMPANY.md`
|
- `COMPANY.md`
|
||||||
- `TEAM.md`
|
- `TEAM.md`
|
||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
|
- `PROJECT.md`
|
||||||
|
- `TASK.md`
|
||||||
- `SKILL.md`
|
- `SKILL.md`
|
||||||
|
|
||||||
The normative draft is:
|
The normative draft is:
|
||||||
|
|
@ -121,10 +127,24 @@ Rules:
|
||||||
|
|
||||||
- `SKILL.md` stays Agent Skills compatible
|
- `SKILL.md` stays Agent Skills compatible
|
||||||
- the company package model is an extension of Agent Skills
|
- the company package model is an extension of Agent Skills
|
||||||
- Paperclip-specific extensions live under metadata
|
- the base package is vendor-neutral and intended for any agent-company runtime
|
||||||
|
- Paperclip-specific fidelity lives in `.paperclip.yaml`
|
||||||
- Paperclip may resolve and install `SKILL.md` packages, but it must not require a Paperclip-only skill format
|
- Paperclip may resolve and install `SKILL.md` packages, but it must not require a Paperclip-only skill format
|
||||||
|
|
||||||
### 5.3 Relationship To Current V1 Manifest
|
### 5.3 Base Package Vs Paperclip Extension
|
||||||
|
|
||||||
|
The repo format should have two layers:
|
||||||
|
|
||||||
|
- base package:
|
||||||
|
- minimal, readable, social, vendor-neutral
|
||||||
|
- implicit folder discovery by convention
|
||||||
|
- no Paperclip-only runtime fields by default
|
||||||
|
- Paperclip extension:
|
||||||
|
- `.paperclip.yaml`
|
||||||
|
- adapter/runtime/permissions/budget/workspace fidelity
|
||||||
|
- emitted by Paperclip tools as a sidecar while the base package stays readable
|
||||||
|
|
||||||
|
### 5.4 Relationship To Current V1 Manifest
|
||||||
|
|
||||||
`paperclip.manifest.json` is not part of the future package direction.
|
`paperclip.manifest.json` is not part of the future package direction.
|
||||||
|
|
||||||
|
|
@ -143,13 +163,9 @@ Paperclip import/export should support these entity kinds:
|
||||||
- company
|
- company
|
||||||
- team
|
- team
|
||||||
- agent
|
- agent
|
||||||
- skill
|
|
||||||
|
|
||||||
Future optional kinds:
|
|
||||||
|
|
||||||
- project
|
- project
|
||||||
- goal
|
- task
|
||||||
- seed task bundle
|
- skill
|
||||||
|
|
||||||
### 6.2 Team Semantics
|
### 6.2 Team Semantics
|
||||||
|
|
||||||
|
|
@ -179,6 +195,7 @@ Examples:
|
||||||
- selecting an agent auto-selects its required docs and skill refs
|
- selecting an agent auto-selects its required docs and skill refs
|
||||||
- selecting a team auto-selects its subtree
|
- selecting a team auto-selects its subtree
|
||||||
- selecting a company auto-selects all included entities by default
|
- selecting a company auto-selects all included entities by default
|
||||||
|
- selecting a project auto-selects its starter tasks
|
||||||
|
|
||||||
The preview output should reflect graph resolution explicitly.
|
The preview output should reflect graph resolution explicitly.
|
||||||
|
|
||||||
|
|
@ -272,7 +289,7 @@ Every import preview should surface:
|
||||||
- referenced external content
|
- referenced external content
|
||||||
- missing files
|
- missing files
|
||||||
- hash mismatch or pinning issues
|
- hash mismatch or pinning issues
|
||||||
- required secrets
|
- env inputs, including required vs optional and default values when present
|
||||||
- unsupported content types
|
- unsupported content types
|
||||||
- trust/licensing warnings
|
- trust/licensing warnings
|
||||||
|
|
||||||
|
|
@ -342,21 +359,34 @@ Exports should:
|
||||||
- omit timestamps and counters unless explicitly needed
|
- omit timestamps and counters unless explicitly needed
|
||||||
- omit secret values
|
- omit secret values
|
||||||
- omit local absolute paths
|
- omit local absolute paths
|
||||||
|
- omit duplicated inline prompt content from `.paperclip.yaml` when `AGENTS.md` already carries the instructions
|
||||||
- preserve references and attribution
|
- preserve references and attribution
|
||||||
|
- emit `.paperclip.yaml` alongside the base package
|
||||||
|
- express adapter env/secrets as portable env input declarations rather than exported secret binding ids
|
||||||
- preserve compatible `SKILL.md` content as-is
|
- preserve compatible `SKILL.md` content as-is
|
||||||
|
|
||||||
### 9.3 Export Modes
|
Projects and issues should not be exported by default.
|
||||||
|
|
||||||
Initial export modes:
|
They should be opt-in through selectors such as:
|
||||||
|
|
||||||
|
- `--projects project-shortname-1,project-shortname-2`
|
||||||
|
- `--issues PAP-1,PAP-3`
|
||||||
|
- `--project-issues project-shortname-1,project-shortname-2`
|
||||||
|
|
||||||
|
This supports “clean public company package” workflows where a maintainer exports a follower-facing company package without bundling active work items every time.
|
||||||
|
|
||||||
|
### 9.3 Export Units
|
||||||
|
|
||||||
|
Initial export units:
|
||||||
|
|
||||||
- company package
|
- company package
|
||||||
- team package
|
- team package
|
||||||
- single agent package
|
- single agent package
|
||||||
|
|
||||||
Later optional modes:
|
Later optional units:
|
||||||
|
|
||||||
- skill pack export
|
- skill pack export
|
||||||
- seed projects/goals bundle
|
- seed projects/tasks bundle
|
||||||
|
|
||||||
## 10. Storage Model Inside Paperclip
|
## 10. Storage Model Inside Paperclip
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
# Company Packages Specification
|
# Agent Companies Specification
|
||||||
|
|
||||||
Extension of the Agent Skills Specification
|
Extension of the Agent Skills Specification
|
||||||
|
|
||||||
Version: `0.1-draft`
|
Version: `agentcompanies/v1-draft`
|
||||||
|
|
||||||
## 1. Purpose
|
## 1. Purpose
|
||||||
|
|
||||||
A Company Package is a filesystem- and GitHub-native format for describing a company, team, agent, and associated skills using markdown files with YAML frontmatter.
|
An Agent Company package is a filesystem- and GitHub-native format for describing a company, team, agent, project, task, and associated skills using markdown files with YAML frontmatter.
|
||||||
|
|
||||||
This specification is an extension of the Agent Skills specification, not a replacement for it.
|
This specification is an extension of the Agent Skills specification, not a replacement for it.
|
||||||
|
|
||||||
It defines how company-, team-, and agent-level package structure composes around the existing `SKILL.md` model.
|
It defines how company-, team-, and agent-level package structure composes around the existing `SKILL.md` model.
|
||||||
|
|
||||||
|
This specification is vendor-neutral. It is intended to be usable by any agent-company runtime, not only Paperclip.
|
||||||
|
|
||||||
The format is designed to:
|
The format is designed to:
|
||||||
|
|
||||||
- be readable and writable by humans
|
- be readable and writable by humans
|
||||||
|
|
@ -30,6 +32,8 @@ The format is designed to:
|
||||||
5. External references must be pinnable to immutable Git commits.
|
5. External references must be pinnable to immutable Git commits.
|
||||||
6. Attribution and license metadata must survive import/export.
|
6. Attribution and license metadata must survive import/export.
|
||||||
7. Slugs and relative paths are the portable identity layer, not database ids.
|
7. Slugs and relative paths are the portable identity layer, not database ids.
|
||||||
|
8. Conventional folder structure should work without verbose wiring.
|
||||||
|
9. Vendor-specific fidelity belongs in optional extensions, not the base package.
|
||||||
|
|
||||||
## 3. Package Kinds
|
## 3. Package Kinds
|
||||||
|
|
||||||
|
|
@ -38,6 +42,8 @@ A package root is identified by one primary markdown file:
|
||||||
- `COMPANY.md` for a company package
|
- `COMPANY.md` for a company package
|
||||||
- `TEAM.md` for a team package
|
- `TEAM.md` for a team package
|
||||||
- `AGENTS.md` for an agent package
|
- `AGENTS.md` for an agent package
|
||||||
|
- `PROJECT.md` for a project package
|
||||||
|
- `TASK.md` for a task package
|
||||||
- `SKILL.md` for a skill package defined by the Agent Skills specification
|
- `SKILL.md` for a skill package defined by the Agent Skills specification
|
||||||
|
|
||||||
A GitHub repo may contain one package at root or many packages in subdirectories.
|
A GitHub repo may contain one package at root or many packages in subdirectories.
|
||||||
|
|
@ -50,11 +56,17 @@ Common conventions:
|
||||||
COMPANY.md
|
COMPANY.md
|
||||||
TEAM.md
|
TEAM.md
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
PROJECT.md
|
||||||
|
TASK.md
|
||||||
SKILL.md
|
SKILL.md
|
||||||
|
|
||||||
agents/<slug>/AGENTS.md
|
agents/<slug>/AGENTS.md
|
||||||
teams/<slug>/TEAM.md
|
teams/<slug>/TEAM.md
|
||||||
|
projects/<slug>/PROJECT.md
|
||||||
|
projects/<slug>/tasks/<slug>/TASK.md
|
||||||
|
tasks/<slug>/TASK.md
|
||||||
skills/<slug>/SKILL.md
|
skills/<slug>/SKILL.md
|
||||||
|
.paperclip.yaml
|
||||||
|
|
||||||
HEARTBEAT.md
|
HEARTBEAT.md
|
||||||
SOUL.md
|
SOUL.md
|
||||||
|
|
@ -73,11 +85,11 @@ Rules:
|
||||||
|
|
||||||
## 5. Common Frontmatter
|
## 5. Common Frontmatter
|
||||||
|
|
||||||
All package root docs should support these fields:
|
Package docs may support these fields:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
schema: company-packages/v0.1
|
schema: agentcompanies/v1
|
||||||
kind: company | team | agent
|
kind: company | team | agent | project | task
|
||||||
slug: my-slug
|
slug: my-slug
|
||||||
name: Human Readable Name
|
name: Human Readable Name
|
||||||
description: Short description
|
description: Short description
|
||||||
|
|
@ -95,11 +107,12 @@ sources: []
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `schema` is required for `COMPANY.md`, `TEAM.md`, and `AGENTS.md`
|
- `schema` is optional and should usually appear only at the package root
|
||||||
- `kind` is required
|
- `kind` is optional when file path and file name already make the kind obvious
|
||||||
- `slug` should be URL-safe and stable
|
- `slug` should be URL-safe and stable
|
||||||
- `sources` is for provenance and external references
|
- `sources` is for provenance and external references
|
||||||
- `metadata` is for tool-specific extensions
|
- `metadata` is for tool-specific extensions
|
||||||
|
- exporters should omit empty or default-valued fields
|
||||||
|
|
||||||
## 6. COMPANY.md
|
## 6. COMPANY.md
|
||||||
|
|
||||||
|
|
@ -108,11 +121,10 @@ Notes:
|
||||||
### Required fields
|
### Required fields
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
schema: company-packages/v0.1
|
|
||||||
kind: company
|
|
||||||
slug: lean-dev-shop
|
|
||||||
name: Lean Dev Shop
|
name: Lean Dev Shop
|
||||||
description: Small engineering-focused AI company
|
description: Small engineering-focused AI company
|
||||||
|
slug: lean-dev-shop
|
||||||
|
schema: agentcompanies/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Recommended fields
|
### Recommended fields
|
||||||
|
|
@ -122,15 +134,10 @@ version: 1.0.0
|
||||||
license: MIT
|
license: MIT
|
||||||
authors:
|
authors:
|
||||||
- name: Example Org
|
- name: Example Org
|
||||||
brandColor: "#22c55e"
|
|
||||||
goals:
|
goals:
|
||||||
- Build and ship software products
|
- Build and ship software products
|
||||||
defaults:
|
|
||||||
requireBoardApprovalForNewAgents: true
|
|
||||||
includes:
|
includes:
|
||||||
- path: agents/ceo/AGENTS.md
|
- https://github.com/example/shared-company-parts/blob/0123456789abcdef0123456789abcdef01234567/teams/engineering/TEAM.md
|
||||||
- path: teams/engineering/TEAM.md
|
|
||||||
- path: skills/review/SKILL.md
|
|
||||||
requirements:
|
requirements:
|
||||||
secrets:
|
secrets:
|
||||||
- OPENAI_API_KEY
|
- OPENAI_API_KEY
|
||||||
|
|
@ -139,8 +146,10 @@ requirements:
|
||||||
### Semantics
|
### Semantics
|
||||||
|
|
||||||
- `includes` defines the package graph
|
- `includes` defines the package graph
|
||||||
|
- local package contents should be discovered implicitly by folder convention
|
||||||
|
- `includes` is optional and should be used mainly for external refs or nonstandard locations
|
||||||
- included items may be local or external references
|
- included items may be local or external references
|
||||||
- `COMPANY.md` may include agents directly, teams, or skills
|
- `COMPANY.md` may include agents directly, teams, projects, tasks, or skills
|
||||||
- a company importer may render `includes` as the tree/checkbox import UI
|
- a company importer may render `includes` as the tree/checkbox import UI
|
||||||
|
|
||||||
## 7. TEAM.md
|
## 7. TEAM.md
|
||||||
|
|
@ -150,17 +159,15 @@ requirements:
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
schema: company-packages/v0.1
|
|
||||||
kind: team
|
|
||||||
slug: engineering
|
|
||||||
name: Engineering
|
name: Engineering
|
||||||
description: Product and platform engineering team
|
description: Product and platform engineering team
|
||||||
manager:
|
schema: agentcompanies/v1
|
||||||
path: ../cto/AGENTS.md
|
slug: engineering
|
||||||
|
manager: ../cto/AGENTS.md
|
||||||
includes:
|
includes:
|
||||||
- path: ../platform-lead/AGENTS.md
|
- ../platform-lead/AGENTS.md
|
||||||
- path: ../frontend-lead/AGENTS.md
|
- ../frontend-lead/AGENTS.md
|
||||||
- path: ../../skills/review/SKILL.md
|
- ../../skills/review/SKILL.md
|
||||||
tags:
|
tags:
|
||||||
- team
|
- team
|
||||||
- engineering
|
- engineering
|
||||||
|
|
@ -180,37 +187,11 @@ tags:
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
schema: company-packages/v0.1
|
|
||||||
kind: agent
|
|
||||||
slug: ceo
|
|
||||||
name: CEO
|
name: CEO
|
||||||
role: ceo
|
|
||||||
title: Chief Executive Officer
|
title: Chief Executive Officer
|
||||||
description: Sets strategy and manages executives
|
|
||||||
icon: crown
|
|
||||||
capabilities:
|
|
||||||
- strategy
|
|
||||||
- delegation
|
|
||||||
reportsTo: null
|
reportsTo: null
|
||||||
adapter:
|
|
||||||
type: codex_local
|
|
||||||
config:
|
|
||||||
model: gpt-5
|
|
||||||
runtime:
|
|
||||||
heartbeat:
|
|
||||||
intervalSec: 3600
|
|
||||||
permissions:
|
|
||||||
canCreateAgents: true
|
|
||||||
skills:
|
skills:
|
||||||
- path: ../../skills/plan-ceo-review/SKILL.md
|
- ../../skills/plan-ceo-review/SKILL.md
|
||||||
docs:
|
|
||||||
instructions: AGENTS.md
|
|
||||||
heartbeat: HEARTBEAT.md
|
|
||||||
soul: SOUL.md
|
|
||||||
requirements:
|
|
||||||
secrets:
|
|
||||||
- OPENAI_API_KEY
|
|
||||||
metadata: {}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Semantics
|
### Semantics
|
||||||
|
|
@ -218,10 +199,111 @@ metadata: {}
|
||||||
- body content is the canonical default instruction content for the agent
|
- body content is the canonical default instruction content for the agent
|
||||||
- `docs` points to sibling markdown docs when present
|
- `docs` points to sibling markdown docs when present
|
||||||
- `skills` references reusable `SKILL.md` packages
|
- `skills` references reusable `SKILL.md` packages
|
||||||
- `adapter.config` and `runtime` should contain only portable values
|
- vendor-specific adapter/runtime config should not live in the base package
|
||||||
- local absolute paths, machine-specific cwd values, and secret values must not be exported as canonical package data
|
- local absolute paths, machine-specific cwd values, and secret values must not be exported as canonical package data
|
||||||
|
|
||||||
## 9. SKILL.md Compatibility
|
## 9. PROJECT.md
|
||||||
|
|
||||||
|
`PROJECT.md` defines a lightweight project package.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Q2 Launch
|
||||||
|
description: Ship the Q2 launch plan and supporting assets
|
||||||
|
owner: cto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantics
|
||||||
|
|
||||||
|
- a project package groups related starter tasks and supporting markdown
|
||||||
|
- `owner` should reference an agent slug when there is a clear project owner
|
||||||
|
- a conventional `tasks/` subfolder should be discovered implicitly
|
||||||
|
- `includes` may contain `TASK.md`, `SKILL.md`, or supporting docs when explicit wiring is needed
|
||||||
|
- project packages are intended to seed planned work, not represent runtime task state
|
||||||
|
|
||||||
|
## 10. TASK.md
|
||||||
|
|
||||||
|
`TASK.md` defines a lightweight starter task.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Monday Review
|
||||||
|
assignee: ceo
|
||||||
|
project: q2-launch
|
||||||
|
schedule:
|
||||||
|
timezone: America/Chicago
|
||||||
|
startsAt: 2026-03-16T09:00:00-05:00
|
||||||
|
recurrence:
|
||||||
|
frequency: weekly
|
||||||
|
interval: 1
|
||||||
|
weekdays:
|
||||||
|
- monday
|
||||||
|
time:
|
||||||
|
hour: 9
|
||||||
|
minute: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantics
|
||||||
|
|
||||||
|
- body content is the canonical markdown task description
|
||||||
|
- `assignee` should reference an agent slug inside the package
|
||||||
|
- `project` should reference a project slug when the task belongs to a `PROJECT.md`
|
||||||
|
- tasks are intentionally basic seed work: title, markdown body, assignee, and optional recurrence
|
||||||
|
- tools may also support optional fields like `priority`, `labels`, or `metadata`, but they should not require them in the base package
|
||||||
|
|
||||||
|
### Scheduling
|
||||||
|
|
||||||
|
The scheduling model is intentionally lightweight. It should cover common recurring patterns such as:
|
||||||
|
|
||||||
|
- every 6 hours
|
||||||
|
- every weekday at 9:00
|
||||||
|
- every Monday morning
|
||||||
|
- every month on the 1st
|
||||||
|
- every first Monday of the month
|
||||||
|
- every year on January 1
|
||||||
|
|
||||||
|
Suggested shape:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schedule:
|
||||||
|
timezone: America/Chicago
|
||||||
|
startsAt: 2026-03-14T09:00:00-05:00
|
||||||
|
recurrence:
|
||||||
|
frequency: hourly | daily | weekly | monthly | yearly
|
||||||
|
interval: 1
|
||||||
|
weekdays:
|
||||||
|
- monday
|
||||||
|
- wednesday
|
||||||
|
monthDays:
|
||||||
|
- 1
|
||||||
|
- 15
|
||||||
|
ordinalWeekdays:
|
||||||
|
- weekday: monday
|
||||||
|
ordinal: 1
|
||||||
|
months:
|
||||||
|
- 1
|
||||||
|
- 6
|
||||||
|
time:
|
||||||
|
hour: 9
|
||||||
|
minute: 0
|
||||||
|
until: 2026-12-31T23:59:59-06:00
|
||||||
|
count: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `timezone` should use an IANA timezone like `America/Chicago`
|
||||||
|
- `startsAt` anchors the first occurrence
|
||||||
|
- `frequency` and `interval` are the only required recurrence fields
|
||||||
|
- `weekdays`, `monthDays`, `ordinalWeekdays`, and `months` are optional narrowing rules
|
||||||
|
- `ordinalWeekdays` uses `ordinal` values like `1`, `2`, `3`, `4`, or `-1` for “last”
|
||||||
|
- `time.hour` and `time.minute` keep common “morning / 9:00 / end of day” scheduling human-readable
|
||||||
|
- `until` and `count` are optional recurrence end bounds
|
||||||
|
- tools may accept richer calendar syntaxes such as RFC5545 `RRULE`, but exporters should prefer the structured form above
|
||||||
|
|
||||||
|
## 11. SKILL.md Compatibility
|
||||||
|
|
||||||
A skill package must remain a valid Agent Skills package.
|
A skill package must remain a valid Agent Skills package.
|
||||||
|
|
||||||
|
|
@ -259,7 +341,7 @@ metadata:
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
## 10. Source References
|
## 12. Source References
|
||||||
|
|
||||||
A package may point to upstream content instead of vendoring it.
|
A package may point to upstream content instead of vendoring it.
|
||||||
|
|
||||||
|
|
@ -301,7 +383,7 @@ sources:
|
||||||
- branch-only refs may be allowed in development mode but must warn
|
- branch-only refs may be allowed in development mode but must warn
|
||||||
- exporters should default to `referenced` for third-party content unless redistribution is clearly allowed
|
- exporters should default to `referenced` for third-party content unless redistribution is clearly allowed
|
||||||
|
|
||||||
## 11. Resolution Rules
|
## 13. Resolution Rules
|
||||||
|
|
||||||
Given a package root, an importer resolves in this order:
|
Given a package root, an importer resolves in this order:
|
||||||
|
|
||||||
|
|
@ -326,13 +408,15 @@ An importer must surface:
|
||||||
- referenced upstream content that requires network fetch
|
- referenced upstream content that requires network fetch
|
||||||
- executable content in skills or scripts
|
- executable content in skills or scripts
|
||||||
|
|
||||||
## 12. Import Graph
|
## 14. Import Graph
|
||||||
|
|
||||||
A package importer should build a graph from:
|
A package importer should build a graph from:
|
||||||
|
|
||||||
- `COMPANY.md`
|
- `COMPANY.md`
|
||||||
- `TEAM.md`
|
- `TEAM.md`
|
||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
|
- `PROJECT.md`
|
||||||
|
- `TASK.md`
|
||||||
- `SKILL.md`
|
- `SKILL.md`
|
||||||
- local and external refs
|
- local and external refs
|
||||||
|
|
||||||
|
|
@ -342,9 +426,71 @@ Suggested import UI behavior:
|
||||||
- checkbox at entity level, not raw file level
|
- checkbox at entity level, not raw file level
|
||||||
- selecting an agent auto-selects required docs and referenced skills
|
- selecting an agent auto-selects required docs and referenced skills
|
||||||
- selecting a team auto-selects its subtree
|
- selecting a team auto-selects its subtree
|
||||||
|
- selecting a project auto-selects its included tasks
|
||||||
|
- selecting a recurring task should surface its schedule before import
|
||||||
- selecting referenced third-party content shows attribution, license, and fetch policy
|
- selecting referenced third-party content shows attribution, license, and fetch policy
|
||||||
|
|
||||||
## 13. Export Rules
|
## 15. Vendor Extensions
|
||||||
|
|
||||||
|
Vendor-specific data should live outside the base package shape.
|
||||||
|
|
||||||
|
For Paperclip, the preferred fidelity extension is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.paperclip.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Example uses:
|
||||||
|
|
||||||
|
- adapter type and adapter config
|
||||||
|
- adapter env inputs and defaults
|
||||||
|
- runtime settings
|
||||||
|
- permissions
|
||||||
|
- budgets
|
||||||
|
- approval policies
|
||||||
|
- project execution workspace policies
|
||||||
|
- issue/task Paperclip-only metadata
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- the base package must remain readable without the extension
|
||||||
|
- tools that do not understand a vendor extension should ignore it
|
||||||
|
- Paperclip tools may emit the vendor extension by default as a sidecar while keeping the base markdown clean
|
||||||
|
|
||||||
|
Suggested Paperclip shape:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema: paperclip/v1
|
||||||
|
agents:
|
||||||
|
claudecoder:
|
||||||
|
adapter:
|
||||||
|
type: claude_local
|
||||||
|
config:
|
||||||
|
model: claude-opus-4-6
|
||||||
|
inputs:
|
||||||
|
env:
|
||||||
|
ANTHROPIC_API_KEY:
|
||||||
|
kind: secret
|
||||||
|
requirement: optional
|
||||||
|
default: ""
|
||||||
|
GH_TOKEN:
|
||||||
|
kind: secret
|
||||||
|
requirement: optional
|
||||||
|
CLAUDE_BIN:
|
||||||
|
kind: plain
|
||||||
|
requirement: optional
|
||||||
|
default: claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional rules for Paperclip exporters:
|
||||||
|
|
||||||
|
- do not duplicate `promptTemplate` when `AGENTS.md` already contains the agent instructions
|
||||||
|
- do not export provider-specific secret bindings such as `secretId`, `version`, or `type: secret_ref`
|
||||||
|
- export env inputs as portable declarations with `required` or `optional` semantics and optional defaults
|
||||||
|
- warn on system-dependent values such as absolute commands and absolute `PATH` overrides
|
||||||
|
- omit empty and default-valued Paperclip fields when possible
|
||||||
|
|
||||||
|
## 16. Export Rules
|
||||||
|
|
||||||
A compliant exporter should:
|
A compliant exporter should:
|
||||||
|
|
||||||
|
|
@ -352,11 +498,15 @@ A compliant exporter should:
|
||||||
- omit machine-local ids and timestamps
|
- omit machine-local ids and timestamps
|
||||||
- omit secret values
|
- omit secret values
|
||||||
- omit machine-specific paths
|
- omit machine-specific paths
|
||||||
|
- preserve task descriptions and recurrence definitions when exporting tasks
|
||||||
|
- omit empty/default fields
|
||||||
|
- default to the vendor-neutral base package
|
||||||
|
- Paperclip exporters should emit `.paperclip.yaml` as a sidecar by default
|
||||||
- preserve attribution and source references
|
- preserve attribution and source references
|
||||||
- prefer `referenced` over silent vendoring for third-party content
|
- prefer `referenced` over silent vendoring for third-party content
|
||||||
- preserve `SKILL.md` as-is when exporting compatible skills
|
- preserve `SKILL.md` as-is when exporting compatible skills
|
||||||
|
|
||||||
## 14. Licensing And Attribution
|
## 17. Licensing And Attribution
|
||||||
|
|
||||||
A compliant tool must:
|
A compliant tool must:
|
||||||
|
|
||||||
|
|
@ -366,7 +516,7 @@ A compliant tool must:
|
||||||
- surface missing license metadata as a warning
|
- surface missing license metadata as a warning
|
||||||
- surface restrictive or unknown licenses before install/import if content is vendored or mirrored
|
- surface restrictive or unknown licenses before install/import if content is vendored or mirrored
|
||||||
|
|
||||||
## 15. Optional Lock File
|
## 18. Optional Lock File
|
||||||
|
|
||||||
Authoring does not require a lock file.
|
Authoring does not require a lock file.
|
||||||
|
|
||||||
|
|
@ -388,23 +538,30 @@ Rules:
|
||||||
- lock files are generated artifacts, not canonical authoring input
|
- lock files are generated artifacts, not canonical authoring input
|
||||||
- the markdown package remains the source of truth
|
- the markdown package remains the source of truth
|
||||||
|
|
||||||
## 16. Paperclip Mapping
|
## 19. Paperclip Mapping
|
||||||
|
|
||||||
Paperclip can map this spec to its runtime model like this:
|
Paperclip can map this spec to its runtime model like this:
|
||||||
|
|
||||||
- `COMPANY.md` -> company metadata
|
- base package:
|
||||||
- `TEAM.md` -> importable org subtree
|
- `COMPANY.md` -> company metadata
|
||||||
- `AGENTS.md` -> agent records plus adapter/runtime config
|
- `TEAM.md` -> importable org subtree
|
||||||
- `SKILL.md` -> imported skill package, ideally as a managed reusable skill reference
|
- `AGENTS.md` -> agent identity and instructions
|
||||||
- `sources[]` -> provenance and pinned upstream refs
|
- `PROJECT.md` -> starter project definition
|
||||||
|
- `TASK.md` -> starter issue/task definition, or automation template when recurrence is present
|
||||||
|
- `SKILL.md` -> imported skill package
|
||||||
|
- `sources[]` -> provenance and pinned upstream refs
|
||||||
|
- Paperclip extension:
|
||||||
|
- `.paperclip.yaml` -> adapter config, runtime config, env input declarations, permissions, budgets, and other Paperclip-specific fidelity
|
||||||
|
|
||||||
Paperclip-specific data should live under:
|
Inline Paperclip-only metadata that must live inside a shared markdown file should use:
|
||||||
|
|
||||||
- `metadata.paperclip`
|
- `metadata.paperclip`
|
||||||
|
|
||||||
That keeps the base format broader than Paperclip.
|
That keeps the base format broader than Paperclip.
|
||||||
|
|
||||||
## 17. Cutover
|
This specification itself remains vendor-neutral and intended for any agent-company runtime, not only Paperclip.
|
||||||
|
|
||||||
|
## 20. Cutover
|
||||||
|
|
||||||
Paperclip should cut over to this markdown-first package model as the primary portability format.
|
Paperclip should cut over to this markdown-first package model as the primary portability format.
|
||||||
|
|
||||||
|
|
@ -412,7 +569,7 @@ Paperclip should cut over to this markdown-first package model as the primary po
|
||||||
|
|
||||||
For Paperclip, this should be treated as a hard cutover in product direction rather than a long-lived dual-format strategy.
|
For Paperclip, this should be treated as a hard cutover in product direction rather than a long-lived dual-format strategy.
|
||||||
|
|
||||||
## 18. Minimal Example
|
## 21. Minimal Example
|
||||||
|
|
||||||
```text
|
```text
|
||||||
lean-dev-shop/
|
lean-dev-shop/
|
||||||
|
|
@ -420,10 +577,24 @@ lean-dev-shop/
|
||||||
├── agents/
|
├── agents/
|
||||||
│ ├── ceo/AGENTS.md
|
│ ├── ceo/AGENTS.md
|
||||||
│ └── cto/AGENTS.md
|
│ └── cto/AGENTS.md
|
||||||
|
├── projects/
|
||||||
|
│ └── q2-launch/
|
||||||
|
│ ├── PROJECT.md
|
||||||
|
│ └── tasks/
|
||||||
|
│ └── monday-review/
|
||||||
|
│ └── TASK.md
|
||||||
├── teams/
|
├── teams/
|
||||||
│ └── engineering/TEAM.md
|
│ └── engineering/TEAM.md
|
||||||
|
├── tasks/
|
||||||
|
│ └── weekly-review/TASK.md
|
||||||
└── skills/
|
└── skills/
|
||||||
└── review/SKILL.md
|
└── review/SKILL.md
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.paperclip.yaml
|
||||||
|
```
|
||||||
```
|
```
|
||||||
|
|
||||||
**Recommendation**
|
**Recommendation**
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,11 @@ export type {
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
InstanceUserRoleGrant,
|
InstanceUserRoleGrant,
|
||||||
CompanyPortabilityInclude,
|
CompanyPortabilityInclude,
|
||||||
CompanyPortabilitySecretRequirement,
|
CompanyPortabilityEnvInput,
|
||||||
CompanyPortabilityCompanyManifestEntry,
|
CompanyPortabilityCompanyManifestEntry,
|
||||||
CompanyPortabilityAgentManifestEntry,
|
CompanyPortabilityAgentManifestEntry,
|
||||||
|
CompanyPortabilityProjectManifestEntry,
|
||||||
|
CompanyPortabilityIssueManifestEntry,
|
||||||
CompanyPortabilityManifest,
|
CompanyPortabilityManifest,
|
||||||
CompanyPortabilityExportResult,
|
CompanyPortabilityExportResult,
|
||||||
CompanyPortabilitySource,
|
CompanyPortabilitySource,
|
||||||
|
|
@ -126,6 +128,8 @@ export type {
|
||||||
CompanyPortabilityCollisionStrategy,
|
CompanyPortabilityCollisionStrategy,
|
||||||
CompanyPortabilityPreviewRequest,
|
CompanyPortabilityPreviewRequest,
|
||||||
CompanyPortabilityPreviewAgentPlan,
|
CompanyPortabilityPreviewAgentPlan,
|
||||||
|
CompanyPortabilityPreviewProjectPlan,
|
||||||
|
CompanyPortabilityPreviewIssuePlan,
|
||||||
CompanyPortabilityPreviewResult,
|
CompanyPortabilityPreviewResult,
|
||||||
CompanyPortabilityImportRequest,
|
CompanyPortabilityImportRequest,
|
||||||
CompanyPortabilityImportResult,
|
CompanyPortabilityImportResult,
|
||||||
|
|
@ -235,7 +239,7 @@ export {
|
||||||
type UpdateMemberPermissions,
|
type UpdateMemberPermissions,
|
||||||
type UpdateUserCompanyAccess,
|
type UpdateUserCompanyAccess,
|
||||||
portabilityIncludeSchema,
|
portabilityIncludeSchema,
|
||||||
portabilitySecretRequirementSchema,
|
portabilityEnvInputSchema,
|
||||||
portabilityCompanyManifestEntrySchema,
|
portabilityCompanyManifestEntrySchema,
|
||||||
portabilityAgentManifestEntrySchema,
|
portabilityAgentManifestEntrySchema,
|
||||||
portabilityManifestSchema,
|
portabilityManifestSchema,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
export interface CompanyPortabilityInclude {
|
export interface CompanyPortabilityInclude {
|
||||||
company: boolean;
|
company: boolean;
|
||||||
agents: boolean;
|
agents: boolean;
|
||||||
|
projects: boolean;
|
||||||
|
issues: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilitySecretRequirement {
|
export interface CompanyPortabilityEnvInput {
|
||||||
key: string;
|
key: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
agentSlug: string | null;
|
agentSlug: string | null;
|
||||||
providerHint: string | null;
|
kind: "secret" | "plain";
|
||||||
|
requirement: "required" | "optional";
|
||||||
|
defaultValue: string | null;
|
||||||
|
portability: "portable" | "system_dependent";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilityCompanyManifestEntry {
|
export interface CompanyPortabilityCompanyManifestEntry {
|
||||||
|
|
@ -18,6 +23,38 @@ export interface CompanyPortabilityCompanyManifestEntry {
|
||||||
requireBoardApprovalForNewAgents: boolean;
|
requireBoardApprovalForNewAgents: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CompanyPortabilityProjectManifestEntry {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
description: string | null;
|
||||||
|
ownerAgentSlug: string | null;
|
||||||
|
leadAgentSlug: string | null;
|
||||||
|
targetDate: string | null;
|
||||||
|
color: string | null;
|
||||||
|
status: string | null;
|
||||||
|
executionWorkspacePolicy: Record<string, unknown> | null;
|
||||||
|
metadata: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyPortabilityIssueManifestEntry {
|
||||||
|
slug: string;
|
||||||
|
identifier: string | null;
|
||||||
|
title: string;
|
||||||
|
path: string;
|
||||||
|
projectSlug: string | null;
|
||||||
|
assigneeAgentSlug: string | null;
|
||||||
|
description: string | null;
|
||||||
|
recurrence: Record<string, unknown> | null;
|
||||||
|
status: string | null;
|
||||||
|
priority: string | null;
|
||||||
|
labelIds: string[];
|
||||||
|
billingCode: string | null;
|
||||||
|
executionWorkspaceSettings: Record<string, unknown> | null;
|
||||||
|
assigneeAdapterOverrides: Record<string, unknown> | null;
|
||||||
|
metadata: Record<string, unknown> | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilityAgentManifestEntry {
|
export interface CompanyPortabilityAgentManifestEntry {
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -45,7 +82,9 @@ export interface CompanyPortabilityManifest {
|
||||||
includes: CompanyPortabilityInclude;
|
includes: CompanyPortabilityInclude;
|
||||||
company: CompanyPortabilityCompanyManifestEntry | null;
|
company: CompanyPortabilityCompanyManifestEntry | null;
|
||||||
agents: CompanyPortabilityAgentManifestEntry[];
|
agents: CompanyPortabilityAgentManifestEntry[];
|
||||||
requiredSecrets: CompanyPortabilitySecretRequirement[];
|
projects: CompanyPortabilityProjectManifestEntry[];
|
||||||
|
issues: CompanyPortabilityIssueManifestEntry[];
|
||||||
|
envInputs: CompanyPortabilityEnvInput[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilityExportResult {
|
export interface CompanyPortabilityExportResult {
|
||||||
|
|
@ -53,6 +92,7 @@ export interface CompanyPortabilityExportResult {
|
||||||
manifest: CompanyPortabilityManifest;
|
manifest: CompanyPortabilityManifest;
|
||||||
files: Record<string, string>;
|
files: Record<string, string>;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
|
paperclipExtensionPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyPortabilitySource =
|
export type CompanyPortabilitySource =
|
||||||
|
|
@ -100,6 +140,21 @@ export interface CompanyPortabilityPreviewAgentPlan {
|
||||||
reason: string | null;
|
reason: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CompanyPortabilityPreviewProjectPlan {
|
||||||
|
slug: string;
|
||||||
|
action: "create" | "update" | "skip";
|
||||||
|
plannedName: string;
|
||||||
|
existingProjectId: string | null;
|
||||||
|
reason: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyPortabilityPreviewIssuePlan {
|
||||||
|
slug: string;
|
||||||
|
action: "create" | "skip";
|
||||||
|
plannedTitle: string;
|
||||||
|
reason: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilityPreviewResult {
|
export interface CompanyPortabilityPreviewResult {
|
||||||
include: CompanyPortabilityInclude;
|
include: CompanyPortabilityInclude;
|
||||||
targetCompanyId: string | null;
|
targetCompanyId: string | null;
|
||||||
|
|
@ -109,8 +164,10 @@ export interface CompanyPortabilityPreviewResult {
|
||||||
plan: {
|
plan: {
|
||||||
companyAction: "none" | "create" | "update";
|
companyAction: "none" | "create" | "update";
|
||||||
agentPlans: CompanyPortabilityPreviewAgentPlan[];
|
agentPlans: CompanyPortabilityPreviewAgentPlan[];
|
||||||
|
projectPlans: CompanyPortabilityPreviewProjectPlan[];
|
||||||
|
issuePlans: CompanyPortabilityPreviewIssuePlan[];
|
||||||
};
|
};
|
||||||
requiredSecrets: CompanyPortabilitySecretRequirement[];
|
envInputs: CompanyPortabilityEnvInput[];
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
errors: string[];
|
errors: string[];
|
||||||
}
|
}
|
||||||
|
|
@ -130,10 +187,13 @@ export interface CompanyPortabilityImportResult {
|
||||||
name: string;
|
name: string;
|
||||||
reason: string | null;
|
reason: string | null;
|
||||||
}[];
|
}[];
|
||||||
requiredSecrets: CompanyPortabilitySecretRequirement[];
|
envInputs: CompanyPortabilityEnvInput[];
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyPortabilityExportRequest {
|
export interface CompanyPortabilityExportRequest {
|
||||||
include?: Partial<CompanyPortabilityInclude>;
|
include?: Partial<CompanyPortabilityInclude>;
|
||||||
|
projects?: string[];
|
||||||
|
issues?: string[];
|
||||||
|
projectIssues?: string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,11 @@ export type {
|
||||||
} from "./access.js";
|
} from "./access.js";
|
||||||
export type {
|
export type {
|
||||||
CompanyPortabilityInclude,
|
CompanyPortabilityInclude,
|
||||||
CompanyPortabilitySecretRequirement,
|
CompanyPortabilityEnvInput,
|
||||||
CompanyPortabilityCompanyManifestEntry,
|
CompanyPortabilityCompanyManifestEntry,
|
||||||
CompanyPortabilityAgentManifestEntry,
|
CompanyPortabilityAgentManifestEntry,
|
||||||
|
CompanyPortabilityProjectManifestEntry,
|
||||||
|
CompanyPortabilityIssueManifestEntry,
|
||||||
CompanyPortabilityManifest,
|
CompanyPortabilityManifest,
|
||||||
CompanyPortabilityExportResult,
|
CompanyPortabilityExportResult,
|
||||||
CompanyPortabilitySource,
|
CompanyPortabilitySource,
|
||||||
|
|
@ -81,6 +83,8 @@ export type {
|
||||||
CompanyPortabilityCollisionStrategy,
|
CompanyPortabilityCollisionStrategy,
|
||||||
CompanyPortabilityPreviewRequest,
|
CompanyPortabilityPreviewRequest,
|
||||||
CompanyPortabilityPreviewAgentPlan,
|
CompanyPortabilityPreviewAgentPlan,
|
||||||
|
CompanyPortabilityPreviewProjectPlan,
|
||||||
|
CompanyPortabilityPreviewIssuePlan,
|
||||||
CompanyPortabilityPreviewResult,
|
CompanyPortabilityPreviewResult,
|
||||||
CompanyPortabilityImportRequest,
|
CompanyPortabilityImportRequest,
|
||||||
CompanyPortabilityImportResult,
|
CompanyPortabilityImportResult,
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,19 @@ export const portabilityIncludeSchema = z
|
||||||
.object({
|
.object({
|
||||||
company: z.boolean().optional(),
|
company: z.boolean().optional(),
|
||||||
agents: z.boolean().optional(),
|
agents: z.boolean().optional(),
|
||||||
|
projects: z.boolean().optional(),
|
||||||
|
issues: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
export const portabilitySecretRequirementSchema = z.object({
|
export const portabilityEnvInputSchema = z.object({
|
||||||
key: z.string().min(1),
|
key: z.string().min(1),
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
agentSlug: z.string().min(1).nullable(),
|
agentSlug: z.string().min(1).nullable(),
|
||||||
providerHint: z.string().nullable(),
|
kind: z.enum(["secret", "plain"]),
|
||||||
|
requirement: z.enum(["required", "optional"]),
|
||||||
|
defaultValue: z.string().nullable(),
|
||||||
|
portability: z.enum(["portable", "system_dependent"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const portabilityCompanyManifestEntrySchema = z.object({
|
export const portabilityCompanyManifestEntrySchema = z.object({
|
||||||
|
|
@ -39,6 +44,38 @@ export const portabilityAgentManifestEntrySchema = z.object({
|
||||||
metadata: z.record(z.unknown()).nullable(),
|
metadata: z.record(z.unknown()).nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const portabilityProjectManifestEntrySchema = z.object({
|
||||||
|
slug: z.string().min(1),
|
||||||
|
name: z.string().min(1),
|
||||||
|
path: z.string().min(1),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
ownerAgentSlug: z.string().min(1).nullable(),
|
||||||
|
leadAgentSlug: z.string().min(1).nullable(),
|
||||||
|
targetDate: z.string().nullable(),
|
||||||
|
color: z.string().nullable(),
|
||||||
|
status: z.string().nullable(),
|
||||||
|
executionWorkspacePolicy: z.record(z.unknown()).nullable(),
|
||||||
|
metadata: z.record(z.unknown()).nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const portabilityIssueManifestEntrySchema = z.object({
|
||||||
|
slug: z.string().min(1),
|
||||||
|
identifier: z.string().min(1).nullable(),
|
||||||
|
title: z.string().min(1),
|
||||||
|
path: z.string().min(1),
|
||||||
|
projectSlug: z.string().min(1).nullable(),
|
||||||
|
assigneeAgentSlug: z.string().min(1).nullable(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
recurrence: z.record(z.unknown()).nullable(),
|
||||||
|
status: z.string().nullable(),
|
||||||
|
priority: z.string().nullable(),
|
||||||
|
labelIds: z.array(z.string().min(1)).default([]),
|
||||||
|
billingCode: z.string().nullable(),
|
||||||
|
executionWorkspaceSettings: z.record(z.unknown()).nullable(),
|
||||||
|
assigneeAdapterOverrides: z.record(z.unknown()).nullable(),
|
||||||
|
metadata: z.record(z.unknown()).nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
export const portabilityManifestSchema = z.object({
|
export const portabilityManifestSchema = z.object({
|
||||||
schemaVersion: z.number().int().positive(),
|
schemaVersion: z.number().int().positive(),
|
||||||
generatedAt: z.string().datetime(),
|
generatedAt: z.string().datetime(),
|
||||||
|
|
@ -51,10 +88,14 @@ export const portabilityManifestSchema = z.object({
|
||||||
includes: z.object({
|
includes: z.object({
|
||||||
company: z.boolean(),
|
company: z.boolean(),
|
||||||
agents: z.boolean(),
|
agents: z.boolean(),
|
||||||
|
projects: z.boolean(),
|
||||||
|
issues: z.boolean(),
|
||||||
}),
|
}),
|
||||||
company: portabilityCompanyManifestEntrySchema.nullable(),
|
company: portabilityCompanyManifestEntrySchema.nullable(),
|
||||||
agents: z.array(portabilityAgentManifestEntrySchema),
|
agents: z.array(portabilityAgentManifestEntrySchema),
|
||||||
requiredSecrets: z.array(portabilitySecretRequirementSchema).default([]),
|
projects: z.array(portabilityProjectManifestEntrySchema).default([]),
|
||||||
|
issues: z.array(portabilityIssueManifestEntrySchema).default([]),
|
||||||
|
envInputs: z.array(portabilityEnvInputSchema).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const portabilitySourceSchema = z.discriminatedUnion("type", [
|
export const portabilitySourceSchema = z.discriminatedUnion("type", [
|
||||||
|
|
@ -93,6 +134,9 @@ export const portabilityCollisionStrategySchema = z.enum(["rename", "skip", "rep
|
||||||
|
|
||||||
export const companyPortabilityExportSchema = z.object({
|
export const companyPortabilityExportSchema = z.object({
|
||||||
include: portabilityIncludeSchema.optional(),
|
include: portabilityIncludeSchema.optional(),
|
||||||
|
projects: z.array(z.string().min(1)).optional(),
|
||||||
|
issues: z.array(z.string().min(1)).optional(),
|
||||||
|
projectIssues: z.array(z.string().min(1)).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CompanyPortabilityExport = z.infer<typeof companyPortabilityExportSchema>;
|
export type CompanyPortabilityExport = z.infer<typeof companyPortabilityExportSchema>;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export {
|
||||||
} from "./adapter-skills.js";
|
} from "./adapter-skills.js";
|
||||||
export {
|
export {
|
||||||
portabilityIncludeSchema,
|
portabilityIncludeSchema,
|
||||||
portabilitySecretRequirementSchema,
|
portabilityEnvInputSchema,
|
||||||
portabilityCompanyManifestEntrySchema,
|
portabilityCompanyManifestEntrySchema,
|
||||||
portabilityAgentManifestEntrySchema,
|
portabilityAgentManifestEntrySchema,
|
||||||
portabilityManifestSchema,
|
portabilityManifestSchema,
|
||||||
|
|
|
||||||
204
server/src/__tests__/company-portability.test.ts
Normal file
204
server/src/__tests__/company-portability.test.ts
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const companySvc = {
|
||||||
|
getById: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const agentSvc = {
|
||||||
|
list: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const accessSvc = {
|
||||||
|
ensureMembership: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectSvc = {
|
||||||
|
list: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueSvc = {
|
||||||
|
list: vi.fn(),
|
||||||
|
getById: vi.fn(),
|
||||||
|
getByIdentifier: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mock("../services/companies.js", () => ({
|
||||||
|
companyService: () => companySvc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../services/agents.js", () => ({
|
||||||
|
agentService: () => agentSvc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../services/access.js", () => ({
|
||||||
|
accessService: () => accessSvc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../services/projects.js", () => ({
|
||||||
|
projectService: () => projectSvc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../services/issues.js", () => ({
|
||||||
|
issueService: () => issueSvc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { companyPortabilityService } = await import("../services/company-portability.js");
|
||||||
|
|
||||||
|
describe("company portability", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
companySvc.getById.mockResolvedValue({
|
||||||
|
id: "company-1",
|
||||||
|
name: "Paperclip",
|
||||||
|
description: null,
|
||||||
|
brandColor: "#5c5fff",
|
||||||
|
requireBoardApprovalForNewAgents: true,
|
||||||
|
});
|
||||||
|
agentSvc.list.mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: "agent-1",
|
||||||
|
name: "ClaudeCoder",
|
||||||
|
status: "idle",
|
||||||
|
role: "engineer",
|
||||||
|
title: "Software Engineer",
|
||||||
|
icon: "code",
|
||||||
|
reportsTo: null,
|
||||||
|
capabilities: "Writes code",
|
||||||
|
adapterType: "claude_local",
|
||||||
|
adapterConfig: {
|
||||||
|
promptTemplate: "You are ClaudeCoder.",
|
||||||
|
instructionsFilePath: "/tmp/ignored.md",
|
||||||
|
cwd: "/tmp/ignored",
|
||||||
|
command: "/Users/dotta/.local/bin/claude",
|
||||||
|
model: "claude-opus-4-6",
|
||||||
|
env: {
|
||||||
|
ANTHROPIC_API_KEY: {
|
||||||
|
type: "secret_ref",
|
||||||
|
secretId: "secret-1",
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
GH_TOKEN: {
|
||||||
|
type: "secret_ref",
|
||||||
|
secretId: "secret-2",
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
PATH: {
|
||||||
|
type: "plain",
|
||||||
|
value: "/usr/bin:/bin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtimeConfig: {
|
||||||
|
heartbeat: {
|
||||||
|
intervalSec: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
budgetMonthlyCents: 0,
|
||||||
|
permissions: {
|
||||||
|
canCreateAgents: false,
|
||||||
|
},
|
||||||
|
metadata: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
projectSvc.list.mockResolvedValue([]);
|
||||||
|
issueSvc.list.mockResolvedValue([]);
|
||||||
|
issueSvc.getById.mockResolvedValue(null);
|
||||||
|
issueSvc.getByIdentifier.mockResolvedValue(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exports a clean base package with sanitized Paperclip extension data", async () => {
|
||||||
|
const portability = companyPortabilityService({} as any);
|
||||||
|
|
||||||
|
const exported = await portability.exportBundle("company-1", {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
agents: true,
|
||||||
|
projects: false,
|
||||||
|
issues: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(exported.files["COMPANY.md"]).toContain('name: "Paperclip"');
|
||||||
|
expect(exported.files["COMPANY.md"]).toContain('schema: "agentcompanies/v1"');
|
||||||
|
expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain("You are ClaudeCoder.");
|
||||||
|
|
||||||
|
const extension = exported.files[".paperclip.yaml"];
|
||||||
|
expect(extension).toContain('schema: "paperclip/v1"');
|
||||||
|
expect(extension).not.toContain("promptTemplate");
|
||||||
|
expect(extension).not.toContain("instructionsFilePath");
|
||||||
|
expect(extension).not.toContain("command:");
|
||||||
|
expect(extension).not.toContain("secretId");
|
||||||
|
expect(extension).not.toContain('type: "secret_ref"');
|
||||||
|
expect(extension).toContain("inputs:");
|
||||||
|
expect(extension).toContain("ANTHROPIC_API_KEY:");
|
||||||
|
expect(extension).toContain('requirement: "optional"');
|
||||||
|
expect(extension).toContain('default: ""');
|
||||||
|
expect(extension).not.toContain("PATH:");
|
||||||
|
expect(extension).not.toContain("requireBoardApprovalForNewAgents: true");
|
||||||
|
expect(extension).not.toContain("budgetMonthlyCents: 0");
|
||||||
|
expect(exported.warnings).toContain("Agent claudecoder command /Users/dotta/.local/bin/claude was omitted from export because it is system-dependent.");
|
||||||
|
expect(exported.warnings).toContain("Agent claudecoder PATH override was omitted from export because it is system-dependent.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads env inputs back from .paperclip.yaml during preview import", async () => {
|
||||||
|
const portability = companyPortabilityService({} as any);
|
||||||
|
|
||||||
|
const exported = await portability.exportBundle("company-1", {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
agents: true,
|
||||||
|
projects: false,
|
||||||
|
issues: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const preview = await portability.previewImport({
|
||||||
|
source: {
|
||||||
|
type: "inline",
|
||||||
|
rootPath: exported.rootPath,
|
||||||
|
files: exported.files,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
agents: true,
|
||||||
|
projects: false,
|
||||||
|
issues: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
mode: "new_company",
|
||||||
|
newCompanyName: "Imported Paperclip",
|
||||||
|
},
|
||||||
|
agents: "all",
|
||||||
|
collisionStrategy: "rename",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(preview.errors).toEqual([]);
|
||||||
|
expect(preview.envInputs).toEqual([
|
||||||
|
{
|
||||||
|
key: "ANTHROPIC_API_KEY",
|
||||||
|
description: "Provide ANTHROPIC_API_KEY for agent claudecoder",
|
||||||
|
agentSlug: "claudecoder",
|
||||||
|
kind: "secret",
|
||||||
|
requirement: "optional",
|
||||||
|
defaultValue: "",
|
||||||
|
portability: "portable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "GH_TOKEN",
|
||||||
|
description: "Provide GH_TOKEN for agent claudecoder",
|
||||||
|
agentSlug: "claudecoder",
|
||||||
|
kind: "secret",
|
||||||
|
requirement: "optional",
|
||||||
|
defaultValue: "",
|
||||||
|
portability: "portable",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -27,7 +27,15 @@ export const companiesApi = {
|
||||||
) => api.patch<Company>(`/companies/${companyId}`, data),
|
) => api.patch<Company>(`/companies/${companyId}`, data),
|
||||||
archive: (companyId: string) => api.post<Company>(`/companies/${companyId}/archive`, {}),
|
archive: (companyId: string) => api.post<Company>(`/companies/${companyId}/archive`, {}),
|
||||||
remove: (companyId: string) => api.delete<{ ok: true }>(`/companies/${companyId}`),
|
remove: (companyId: string) => api.delete<{ ok: true }>(`/companies/${companyId}`),
|
||||||
exportBundle: (companyId: string, data: { include?: { company?: boolean; agents?: boolean } }) =>
|
exportBundle: (
|
||||||
|
companyId: string,
|
||||||
|
data: {
|
||||||
|
include?: { company?: boolean; agents?: boolean; projects?: boolean; issues?: boolean };
|
||||||
|
projects?: string[];
|
||||||
|
issues?: string[];
|
||||||
|
projectIssues?: string[];
|
||||||
|
},
|
||||||
|
) =>
|
||||||
api.post<CompanyPortabilityExportResult>(`/companies/${companyId}/export`, data),
|
api.post<CompanyPortabilityExportResult>(`/companies/${companyId}/export`, data),
|
||||||
importPreview: (data: CompanyPortabilityPreviewRequest) =>
|
importPreview: (data: CompanyPortabilityPreviewRequest) =>
|
||||||
api.post<CompanyPortabilityPreviewResult>("/companies/import/preview", data),
|
api.post<CompanyPortabilityPreviewResult>("/companies/import/preview", data),
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,9 @@ export function CompanySettings() {
|
||||||
const packageInclude = useMemo(
|
const packageInclude = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
company: packageIncludeCompany,
|
company: packageIncludeCompany,
|
||||||
agents: packageIncludeAgents
|
agents: packageIncludeAgents,
|
||||||
|
projects: false,
|
||||||
|
issues: false
|
||||||
}),
|
}),
|
||||||
[packageIncludeAgents, packageIncludeCompany]
|
[packageIncludeAgents, packageIncludeCompany]
|
||||||
);
|
);
|
||||||
|
|
@ -376,7 +378,7 @@ export function CompanySettings() {
|
||||||
pushToast({
|
pushToast({
|
||||||
tone: "success",
|
tone: "success",
|
||||||
title: "Local package loaded",
|
title: "Local package loaded",
|
||||||
body: `${Object.keys(parsed.files).length} markdown file${Object.keys(parsed.files).length === 1 ? "" : "s"} ready for preview.`
|
body: `${Object.keys(parsed.files).length} package file${Object.keys(parsed.files).length === 1 ? "" : "s"} ready for preview.`
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLocalPackage(null);
|
setLocalPackage(null);
|
||||||
|
|
@ -666,6 +668,9 @@ export function CompanySettings() {
|
||||||
Include agents
|
Include agents
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Export always includes `.paperclip.yaml` as a Paperclip sidecar while keeping the markdown package readable and shareable.
|
||||||
|
</p>
|
||||||
|
|
||||||
{exportMutation.data && (
|
{exportMutation.data && (
|
||||||
<div className="rounded-md border border-border bg-muted/20 p-3">
|
<div className="rounded-md border border-border bg-muted/20 p-3">
|
||||||
|
|
@ -675,7 +680,8 @@ export function CompanySettings() {
|
||||||
<div className="mt-2 text-sm">
|
<div className="mt-2 text-sm">
|
||||||
{exportMutation.data.rootPath}.tar with{" "}
|
{exportMutation.data.rootPath}.tar with{" "}
|
||||||
{Object.keys(exportMutation.data.files).length} file
|
{Object.keys(exportMutation.data.files).length} file
|
||||||
{Object.keys(exportMutation.data.files).length === 1 ? "" : "s"}.
|
{Object.keys(exportMutation.data.files).length === 1 ? "" : "s"}. Includes{" "}
|
||||||
|
<span className="font-mono">{exportMutation.data.paperclipExtensionPath}</span>.
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex flex-wrap gap-2 text-xs text-muted-foreground">
|
<div className="mt-2 flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||||
{Object.keys(exportMutation.data.files).map((filePath) => (
|
{Object.keys(exportMutation.data.files).map((filePath) => (
|
||||||
|
|
@ -773,7 +779,7 @@ export function CompanySettings() {
|
||||||
{localPackage && (
|
{localPackage && (
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{localPackage.rootPath ?? "package"} with{" "}
|
{localPackage.rootPath ?? "package"} with{" "}
|
||||||
{Object.keys(localPackage.files).length} markdown file
|
{Object.keys(localPackage.files).length} package file
|
||||||
{Object.keys(localPackage.files).length === 1 ? "" : "s"}
|
{Object.keys(localPackage.files).length === 1 ? "" : "s"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -889,7 +895,7 @@ export function CompanySettings() {
|
||||||
|
|
||||||
{importPreview && (
|
{importPreview && (
|
||||||
<div className="space-y-3 rounded-md border border-border bg-muted/20 p-3">
|
<div className="space-y-3 rounded-md border border-border bg-muted/20 p-3">
|
||||||
<div className="grid gap-2 md:grid-cols-2">
|
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-4">
|
||||||
<div className="rounded-md border border-border bg-background/70 px-3 py-2">
|
<div className="rounded-md border border-border bg-background/70 px-3 py-2">
|
||||||
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
Company action
|
Company action
|
||||||
|
|
@ -906,6 +912,22 @@ export function CompanySettings() {
|
||||||
{importPreview.plan.agentPlans.length}
|
{importPreview.plan.agentPlans.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-md border border-border bg-background/70 px-3 py-2">
|
||||||
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
|
Project actions
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-sm font-medium">
|
||||||
|
{importPreview.plan.projectPlans.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-md border border-border bg-background/70 px-3 py-2">
|
||||||
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
|
Task actions
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-sm font-medium">
|
||||||
|
{importPreview.plan.issuePlans.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{importPreview.plan.agentPlans.length > 0 && (
|
{importPreview.plan.agentPlans.length > 0 && (
|
||||||
|
|
@ -933,18 +955,72 @@ export function CompanySettings() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{importPreview.requiredSecrets.length > 0 && (
|
{importPreview.plan.projectPlans.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{importPreview.plan.projectPlans.map((projectPlan) => (
|
||||||
|
<div
|
||||||
|
key={projectPlan.slug}
|
||||||
|
className="rounded-md border border-border bg-background/70 px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-2 text-sm">
|
||||||
|
<span className="font-medium">
|
||||||
|
{projectPlan.slug} {"->"} {projectPlan.plannedName}
|
||||||
|
</span>
|
||||||
|
<span className="rounded-full border border-border px-2 py-0.5 text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
|
{projectPlan.action}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{projectPlan.reason && (
|
||||||
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
|
{projectPlan.reason}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{importPreview.plan.issuePlans.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{importPreview.plan.issuePlans.map((issuePlan) => (
|
||||||
|
<div
|
||||||
|
key={issuePlan.slug}
|
||||||
|
className="rounded-md border border-border bg-background/70 px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-2 text-sm">
|
||||||
|
<span className="font-medium">
|
||||||
|
{issuePlan.slug} {"->"} {issuePlan.plannedTitle}
|
||||||
|
</span>
|
||||||
|
<span className="rounded-full border border-border px-2 py-0.5 text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
|
{issuePlan.action}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{issuePlan.reason && (
|
||||||
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
|
{issuePlan.reason}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{importPreview.envInputs.length > 0 && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
Required secrets
|
Environment inputs
|
||||||
</div>
|
</div>
|
||||||
{importPreview.requiredSecrets.map((secret) => (
|
{importPreview.envInputs.map((inputValue) => (
|
||||||
<div
|
<div
|
||||||
key={`${secret.agentSlug ?? "company"}:${secret.key}`}
|
key={`${inputValue.agentSlug ?? "company"}:${inputValue.key}`}
|
||||||
className="text-xs text-muted-foreground"
|
className="text-xs text-muted-foreground"
|
||||||
>
|
>
|
||||||
{secret.key}
|
{inputValue.key}
|
||||||
{secret.agentSlug ? ` for ${secret.agentSlug}` : ""}
|
{inputValue.agentSlug ? ` for ${inputValue.agentSlug}` : ""}
|
||||||
|
{` · ${inputValue.kind}`}
|
||||||
|
{` · ${inputValue.requirement}`}
|
||||||
|
{inputValue.defaultValue !== null ? ` · default ${JSON.stringify(inputValue.defaultValue)}` : ""}
|
||||||
|
{inputValue.portability === "system_dependent" ? " · system-dependent" : ""}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1039,14 +1115,18 @@ async function readLocalPackageSelection(fileList: FileList): Promise<{
|
||||||
/\\/g,
|
/\\/g,
|
||||||
"/"
|
"/"
|
||||||
) || file.name;
|
) || file.name;
|
||||||
if (!relativePath.endsWith(".md")) continue;
|
const isMarkdown = relativePath.endsWith(".md");
|
||||||
|
const isPaperclipYaml =
|
||||||
|
relativePath.endsWith(".paperclip.yaml") ||
|
||||||
|
relativePath.endsWith(".paperclip.yml");
|
||||||
|
if (!isMarkdown && !isPaperclipYaml) continue;
|
||||||
const topLevel = relativePath.split("/")[0] ?? null;
|
const topLevel = relativePath.split("/")[0] ?? null;
|
||||||
if (!rootPath && topLevel) rootPath = topLevel;
|
if (!rootPath && topLevel) rootPath = topLevel;
|
||||||
files[relativePath] = await file.text();
|
files[relativePath] = await file.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(files).length === 0) {
|
if (Object.keys(files).length === 0) {
|
||||||
throw new Error("No markdown files were found in the selected folder.");
|
throw new Error("No package files were found in the selected folder.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { rootPath, files };
|
return { rootPath, files };
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue