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

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

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

View File

@@ -172,7 +172,7 @@ Paperclip handles the hard orchestration details correctly.
Open source. Self-hosted. No Paperclip account required. Open source. Self-hosted. No Paperclip account required.
```bash ```bash
npx paperclip onboard npx paperclipai onboard
``` ```
Or manually: Or manually:

View File

@@ -1,11 +1,22 @@
{ {
"name": "@paperclip/cli", "name": "paperclipai",
"version": "0.0.1", "version": "0.1.0",
"private": true, "description": "Paperclip CLI — orchestrate AI agent teams to run a business",
"type": "module", "type": "module",
"bin": { "bin": {
"paperclip": "./dist/index.js" "paperclipai": "./dist/index.js"
}, },
"keywords": ["paperclip", "ai", "agents", "orchestration", "cli"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/paperclipai/paperclip.git",
"directory": "cli"
},
"homepage": "https://github.com/paperclipai/paperclip",
"files": [
"dist"
],
"scripts": { "scripts": {
"dev": "tsx src/index.ts", "dev": "tsx src/index.ts",
"build": "tsc", "build": "tsc",
@@ -13,13 +24,13 @@
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^0.10.0", "@clack/prompts": "^0.10.0",
"@paperclip/adapter-claude-local": "workspace:*", "@paperclipai/adapter-claude-local": "workspace:*",
"@paperclip/adapter-codex-local": "workspace:*", "@paperclipai/adapter-codex-local": "workspace:*",
"@paperclip/adapter-openclaw": "workspace:*", "@paperclipai/adapter-openclaw": "workspace:*",
"@paperclip/adapter-utils": "workspace:*", "@paperclipai/adapter-utils": "workspace:*",
"@paperclip/db": "workspace:*", "@paperclipai/db": "workspace:*",
"@paperclip/server": "workspace:*", "@paperclipai/server": "workspace:*",
"@paperclip/shared": "workspace:*", "@paperclipai/shared": "workspace:*",
"drizzle-orm": "0.38.4", "drizzle-orm": "0.38.4",
"dotenv": "^17.0.1", "dotenv": "^17.0.1",
"commander": "^13.1.0", "commander": "^13.1.0",

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { Company } from "@paperclip/shared"; import type { Company } from "@paperclipai/shared";
import { assertDeleteConfirmation, resolveCompanyForDeletion } from "../commands/client/company.js"; import { assertDeleteConfirmation, resolveCompanyForDeletion } from "../commands/client/company.js";
function makeCompany(overrides: Partial<Company>): Company { function makeCompany(overrides: Partial<Company>): Company {

View File

@@ -1,4 +1,4 @@
import type { CLIAdapterModule } from "@paperclip/adapter-utils"; import type { CLIAdapterModule } from "@paperclipai/adapter-utils";
import { printHttpStdoutEvent } from "./format-event.js"; import { printHttpStdoutEvent } from "./format-event.js";
export const httpCLIAdapter: CLIAdapterModule = { export const httpCLIAdapter: CLIAdapterModule = {

View File

@@ -1,2 +1,2 @@
export { getCLIAdapter } from "./registry.js"; export { getCLIAdapter } from "./registry.js";
export type { CLIAdapterModule } from "@paperclip/adapter-utils"; export type { CLIAdapterModule } from "@paperclipai/adapter-utils";

View File

@@ -1,4 +1,4 @@
import type { CLIAdapterModule } from "@paperclip/adapter-utils"; import type { CLIAdapterModule } from "@paperclipai/adapter-utils";
import { printProcessStdoutEvent } from "./format-event.js"; import { printProcessStdoutEvent } from "./format-event.js";
export const processCLIAdapter: CLIAdapterModule = { export const processCLIAdapter: CLIAdapterModule = {

View File

@@ -1,7 +1,7 @@
import type { CLIAdapterModule } from "@paperclip/adapter-utils"; import type { CLIAdapterModule } from "@paperclipai/adapter-utils";
import { printClaudeStreamEvent } from "@paperclip/adapter-claude-local/cli"; import { printClaudeStreamEvent } from "@paperclipai/adapter-claude-local/cli";
import { printCodexStreamEvent } from "@paperclip/adapter-codex-local/cli"; import { printCodexStreamEvent } from "@paperclipai/adapter-codex-local/cli";
import { printOpenClawStreamEvent } from "@paperclip/adapter-openclaw/cli"; import { printOpenClawStreamEvent } from "@paperclipai/adapter-openclaw/cli";
import { processCLIAdapter } from "./process/index.js"; import { processCLIAdapter } from "./process/index.js";
import { httpCLIAdapter } from "./http/index.js"; import { httpCLIAdapter } from "./http/index.js";

View File

@@ -10,7 +10,7 @@ export function configCheck(configPath?: string): CheckResult {
status: "fail", status: "fail",
message: `Config file not found at ${filePath}`, message: `Config file not found at ${filePath}`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip onboard` to create one", repairHint: "Run `paperclipai onboard` to create one",
}; };
} }
@@ -27,7 +27,7 @@ export function configCheck(configPath?: string): CheckResult {
status: "fail", status: "fail",
message: `Invalid config: ${err instanceof Error ? err.message : String(err)}`, message: `Invalid config: ${err instanceof Error ? err.message : String(err)}`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section database` (or `paperclip onboard` to recreate)", repairHint: "Run `paperclipai configure --section database` (or `paperclipai onboard` to recreate)",
}; };
} }
} }

View File

@@ -11,12 +11,12 @@ export async function databaseCheck(config: PaperclipConfig, configPath?: string
status: "fail", status: "fail",
message: "PostgreSQL mode selected but no connection string configured", message: "PostgreSQL mode selected but no connection string configured",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section database`", repairHint: "Run `paperclipai configure --section database`",
}; };
} }
try { try {
const { createDb } = await import("@paperclip/db"); const { createDb } = await import("@paperclipai/db");
const db = createDb(config.database.connectionString); const db = createDb(config.database.connectionString);
await db.execute("SELECT 1"); await db.execute("SELECT 1");
return { return {
@@ -62,6 +62,6 @@ export async function databaseCheck(config: PaperclipConfig, configPath?: string
status: "fail", status: "fail",
message: `Unknown database mode: ${String(config.database.mode)}`, message: `Unknown database mode: ${String(config.database.mode)}`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section database`", repairHint: "Run `paperclipai configure --section database`",
}; };
} }

View File

@@ -18,7 +18,7 @@ export function deploymentAuthCheck(config: PaperclipConfig): CheckResult {
status: "fail", status: "fail",
message: `local_trusted requires loopback host binding (found ${config.server.host})`, message: `local_trusted requires loopback host binding (found ${config.server.host})`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section server` and set host to 127.0.0.1", repairHint: "Run `paperclipai configure --section server` and set host to 127.0.0.1",
}; };
} }
return { return {
@@ -47,7 +47,7 @@ export function deploymentAuthCheck(config: PaperclipConfig): CheckResult {
status: "fail", status: "fail",
message: "auth.baseUrlMode=explicit requires auth.publicBaseUrl", message: "auth.baseUrlMode=explicit requires auth.publicBaseUrl",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section server` and provide a base URL", repairHint: "Run `paperclipai configure --section server` and provide a base URL",
}; };
} }
@@ -58,7 +58,7 @@ export function deploymentAuthCheck(config: PaperclipConfig): CheckResult {
status: "fail", status: "fail",
message: "authenticated/public requires explicit auth.publicBaseUrl", message: "authenticated/public requires explicit auth.publicBaseUrl",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section server` and select public exposure", repairHint: "Run `paperclipai configure --section server` and select public exposure",
}; };
} }
try { try {
@@ -78,7 +78,7 @@ export function deploymentAuthCheck(config: PaperclipConfig): CheckResult {
status: "fail", status: "fail",
message: "auth.publicBaseUrl is not a valid URL", message: "auth.publicBaseUrl is not a valid URL",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section server` and provide a valid URL", repairHint: "Run `paperclipai configure --section server` and provide a valid URL",
}; };
} }
} }

View File

@@ -8,7 +8,7 @@ export async function llmCheck(config: PaperclipConfig): Promise<CheckResult> {
status: "warn", status: "warn",
message: "No LLM provider configured", message: "No LLM provider configured",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section llm` to set one up", repairHint: "Run `paperclipai configure --section llm` to set one up",
}; };
} }
@@ -18,7 +18,7 @@ export async function llmCheck(config: PaperclipConfig): Promise<CheckResult> {
status: "warn", status: "warn",
message: `${config.llm.provider} configured but no API key set`, message: `${config.llm.provider} configured but no API key set`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section llm`", repairHint: "Run `paperclipai configure --section llm`",
}; };
} }
@@ -46,7 +46,7 @@ export async function llmCheck(config: PaperclipConfig): Promise<CheckResult> {
status: "fail", status: "fail",
message: "Claude API key is invalid (401)", message: "Claude API key is invalid (401)",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section llm`", repairHint: "Run `paperclipai configure --section llm`",
}; };
} }
return { return {
@@ -67,7 +67,7 @@ export async function llmCheck(config: PaperclipConfig): Promise<CheckResult> {
status: "fail", status: "fail",
message: "OpenAI API key is invalid (401)", message: "OpenAI API key is invalid (401)",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section llm`", repairHint: "Run `paperclipai configure --section llm`",
}; };
} }
return { return {

View File

@@ -53,7 +53,7 @@ export function secretsCheck(config: PaperclipConfig, configPath?: string): Chec
status: "fail", status: "fail",
message: `${provider} is configured, but this build only supports local_encrypted`, message: `${provider} is configured, but this build only supports local_encrypted`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section secrets` and set provider to local_encrypted", repairHint: "Run `paperclipai configure --section secrets` and set provider to local_encrypted",
}; };
} }

View File

@@ -45,7 +45,7 @@ export function storageCheck(config: PaperclipConfig, configPath?: string): Chec
status: "fail", status: "fail",
message: "S3 storage requires non-empty bucket and region", message: "S3 storage requires non-empty bucket and region",
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section storage`", repairHint: "Run `paperclipai configure --section storage`",
}; };
} }

View File

@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
import * as p from "@clack/prompts"; import * as p from "@clack/prompts";
import pc from "picocolors"; import pc from "picocolors";
import { and, eq, gt, isNull } from "drizzle-orm"; import { and, eq, gt, isNull } from "drizzle-orm";
import { createDb, instanceUserRoles, invites } from "@paperclip/db"; import { createDb, instanceUserRoles, invites } from "@paperclipai/db";
import { readConfig, resolveConfigPath } from "../config/store.js"; import { readConfig, resolveConfigPath } from "../config/store.js";
function hashToken(token: string) { function hashToken(token: string) {

View File

@@ -1,5 +1,5 @@
import { Command } from "commander"; import { Command } from "commander";
import type { ActivityEvent } from "@paperclip/shared"; import type { ActivityEvent } from "@paperclipai/shared";
import { import {
addCommonClientOptions, addCommonClientOptions,
formatInlineRecord, formatInlineRecord,

View File

@@ -1,5 +1,5 @@
import { Command } from "commander"; import { Command } from "commander";
import type { Agent } from "@paperclip/shared"; import type { Agent } from "@paperclipai/shared";
import { import {
addCommonClientOptions, addCommonClientOptions,
formatInlineRecord, formatInlineRecord,

View File

@@ -6,7 +6,7 @@ import {
resubmitApprovalSchema, resubmitApprovalSchema,
type Approval, type Approval,
type ApprovalComment, type ApprovalComment,
} from "@paperclip/shared"; } from "@paperclipai/shared";
import { import {
addCommonClientOptions, addCommonClientOptions,
formatInlineRecord, formatInlineRecord,

View File

@@ -65,7 +65,7 @@ export function resolveCommandContext(
if (opts?.requireCompany && !companyId) { if (opts?.requireCompany && !companyId) {
throw new Error( throw new Error(
"Company ID is required. Pass --company-id, set PAPERCLIP_COMPANY_ID, or set context profile companyId via `paperclip context set`.", "Company ID is required. Pass --company-id, set PAPERCLIP_COMPANY_ID, or set context profile companyId via `paperclipai context set`.",
); );
} }

View File

@@ -8,7 +8,7 @@ import type {
CompanyPortabilityManifest, CompanyPortabilityManifest,
CompanyPortabilityPreviewResult, CompanyPortabilityPreviewResult,
CompanyPortabilityImportResult, CompanyPortabilityImportResult,
} from "@paperclip/shared"; } from "@paperclipai/shared";
import { ApiRequestError } from "../../client/http.js"; import { ApiRequestError } from "../../client/http.js";
import { import {
addCommonClientOptions, addCommonClientOptions,

View File

@@ -1,5 +1,5 @@
import { Command } from "commander"; import { Command } from "commander";
import type { DashboardSummary } from "@paperclip/shared"; import type { DashboardSummary } from "@paperclipai/shared";
import { import {
addCommonClientOptions, addCommonClientOptions,
handleCommandError, handleCommandError,

View File

@@ -6,7 +6,7 @@ import {
updateIssueSchema, updateIssueSchema,
type Issue, type Issue,
type IssueComment, type IssueComment,
} from "@paperclip/shared"; } from "@paperclipai/shared";
import { import {
addCommonClientOptions, addCommonClientOptions,
formatInlineRecord, formatInlineRecord,

View File

@@ -67,7 +67,7 @@ export async function configure(opts: {
const configPath = resolveConfigPath(opts.config); const configPath = resolveConfigPath(opts.config);
if (!configExists(opts.config)) { if (!configExists(opts.config)) {
p.log.error("No config file found. Run `paperclip onboard` first."); p.log.error("No config file found. Run `paperclipai onboard` first.");
p.outro(""); p.outro("");
return; return;
} }

View File

@@ -49,7 +49,7 @@ export async function doctor(opts: {
status: "fail", status: "fail",
message: `Could not read config: ${err instanceof Error ? err.message : String(err)}`, message: `Could not read config: ${err instanceof Error ? err.message : String(err)}`,
canRepair: false, canRepair: false,
repairHint: "Run `paperclip configure --section database` or `paperclip onboard`", repairHint: "Run `paperclipai configure --section database` or `paperclipai onboard`",
}; };
results.push(readResult); results.push(readResult);
printResult(readResult); printResult(readResult);

View File

@@ -1,6 +1,6 @@
import { setTimeout as delay } from "node:timers/promises"; import { setTimeout as delay } from "node:timers/promises";
import pc from "picocolors"; import pc from "picocolors";
import type { Agent, HeartbeatRun, HeartbeatRunEvent, HeartbeatRunStatus } from "@paperclip/shared"; import type { Agent, HeartbeatRun, HeartbeatRunEvent, HeartbeatRunStatus } from "@paperclipai/shared";
import { getCLIAdapter } from "../adapters/index.js"; import { getCLIAdapter } from "../adapters/index.js";
import { resolveCommandContext } from "./client/common.js"; import { resolveCommandContext } from "./client/common.js";

View File

@@ -14,7 +14,7 @@ import { describeLocalInstancePaths, resolvePaperclipInstanceId } from "../confi
import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js"; import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js";
export async function onboard(opts: { config?: string }): Promise<void> { export async function onboard(opts: { config?: string }): Promise<void> {
p.intro(pc.bgCyan(pc.black(" paperclip onboard "))); p.intro(pc.bgCyan(pc.black(" paperclipai onboard ")));
const instance = describeLocalInstancePaths(resolvePaperclipInstanceId()); const instance = describeLocalInstancePaths(resolvePaperclipInstanceId());
p.log.message( p.log.message(
pc.dim( pc.dim(
@@ -46,12 +46,12 @@ export async function onboard(opts: { config?: string }): Promise<void> {
const s = p.spinner(); const s = p.spinner();
s.start("Testing database connection..."); s.start("Testing database connection...");
try { try {
const { createDb } = await import("@paperclip/db"); const { createDb } = await import("@paperclipai/db");
const db = createDb(database.connectionString); const db = createDb(database.connectionString);
await db.execute("SELECT 1"); await db.execute("SELECT 1");
s.stop("Database connection successful"); s.stop("Database connection successful");
} catch (err) { } catch (err) {
s.stop(pc.yellow("Could not connect to database — you can fix this later with `paperclip doctor`")); s.stop(pc.yellow("Could not connect to database — you can fix this later with `paperclipai doctor`"));
} }
} }
@@ -172,7 +172,7 @@ export async function onboard(opts: { config?: string }): Promise<void> {
"Configuration saved", "Configuration saved",
); );
p.log.info(`Run ${pc.cyan("pnpm paperclip doctor")} to verify your setup.`); p.log.info(`Run ${pc.cyan("pnpm paperclipai doctor")} to verify your setup.`);
p.log.message( p.log.message(
`Before starting Paperclip, export ${pc.cyan("PAPERCLIP_AGENT_JWT_SECRET")} from ${pc.dim(envFilePath)} (for example: ${pc.dim(`set -a; source ${envFilePath}; set +a`)})`, `Before starting Paperclip, export ${pc.cyan("PAPERCLIP_AGENT_JWT_SECRET")} from ${pc.dim(envFilePath)} (for example: ${pc.dim(`set -a; source ${envFilePath}; set +a`)})`,
); );

View File

@@ -32,7 +32,7 @@ export async function runCommand(opts: RunOptions): Promise<void> {
const configPath = resolveConfigPath(opts.config); const configPath = resolveConfigPath(opts.config);
process.env.PAPERCLIP_CONFIG = configPath; process.env.PAPERCLIP_CONFIG = configPath;
p.intro(pc.bgCyan(pc.black(" paperclip run "))); p.intro(pc.bgCyan(pc.black(" paperclipai run ")));
p.log.message(pc.dim(`Home: ${paths.homeDir}`)); p.log.message(pc.dim(`Home: ${paths.homeDir}`));
p.log.message(pc.dim(`Instance: ${paths.instanceId}`)); p.log.message(pc.dim(`Instance: ${paths.instanceId}`));
p.log.message(pc.dim(`Config: ${configPath}`)); p.log.message(pc.dim(`Config: ${configPath}`));
@@ -40,7 +40,7 @@ export async function runCommand(opts: RunOptions): Promise<void> {
if (!configExists(configPath)) { if (!configExists(configPath)) {
if (!process.stdin.isTTY || !process.stdout.isTTY) { if (!process.stdin.isTTY || !process.stdout.isTTY) {
p.log.error("No config found and terminal is non-interactive."); p.log.error("No config found and terminal is non-interactive.");
p.log.message(`Run ${pc.cyan("paperclip onboard")} once, then retry ${pc.cyan("paperclip run")}.`); p.log.message(`Run ${pc.cyan("paperclipai onboard")} once, then retry ${pc.cyan("paperclipai run")}.`);
process.exit(1); process.exit(1);
} }
@@ -72,8 +72,8 @@ async function importServerEntry(): Promise<void> {
]; ];
const specifierCandidates: string[] = [ const specifierCandidates: string[] = [
"@paperclip/server/dist/index.js", "@paperclipai/server/dist/index.js",
"@paperclip/server/src/index.ts", "@paperclipai/server/src/index.ts",
]; ];
const importErrors: string[] = []; const importErrors: string[] = [];

View File

@@ -25,7 +25,7 @@ function parseEnvFile(contents: string) {
function renderEnvFile(entries: Record<string, string>) { function renderEnvFile(entries: Record<string, string>) {
const lines = [ const lines = [
"# Paperclip environment variables", "# Paperclip environment variables",
"# Generated by `paperclip onboard`", "# Generated by `paperclipai onboard`",
...Object.entries(entries).map(([key, value]) => `${key}=${value}`), ...Object.entries(entries).map(([key, value]) => `${key}=${value}`),
"", "",
]; ];

View File

@@ -23,4 +23,4 @@ export {
type SecretsConfig, type SecretsConfig,
type SecretsLocalEncryptedConfig, type SecretsLocalEncryptedConfig,
type ConfigMeta, type ConfigMeta,
} from "@paperclip/shared"; } from "@paperclipai/shared";

View File

@@ -22,9 +22,9 @@ const DATA_DIR_OPTION_HELP =
"Paperclip data directory root (isolates state from ~/.paperclip)"; "Paperclip data directory root (isolates state from ~/.paperclip)";
program program
.name("paperclip") .name("paperclipai")
.description("Paperclip CLI — setup, diagnose, and configure your instance") .description("Paperclip CLI — setup, diagnose, and configure your instance")
.version("0.0.1"); .version("0.1.0");
program.hook("preAction", (_thisCommand, actionCommand) => { program.hook("preAction", (_thisCommand, actionCommand) => {
const options = actionCommand.optsWithGlobals() as DataDirOptionLike; const options = actionCommand.optsWithGlobals() as DataDirOptionLike;

View File

@@ -1,5 +1,5 @@
import * as p from "@clack/prompts"; import * as p from "@clack/prompts";
import type { SecretProvider } from "@paperclip/shared"; import type { SecretProvider } from "@paperclipai/shared";
import type { SecretsConfig } from "../config/schema.js"; import type { SecretsConfig } from "../config/schema.js";
import { resolveDefaultSecretsKeyFilePath, resolvePaperclipInstanceId } from "../config/home.js"; import { resolveDefaultSecretsKeyFilePath, resolvePaperclipInstanceId } from "../config/home.js";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,7 +76,7 @@ Return structured diagnostics:
## Step 5: CLI Module ## Step 5: CLI Module
`format-event.ts` — pretty-prints stdout for `paperclip run --watch` using `picocolors`. `format-event.ts` — pretty-prints stdout for `paperclipai run --watch` using `picocolors`.
## Step 6: Register ## Step 6: Register

View File

@@ -39,7 +39,7 @@ packages/adapters/<name>/
parse-stdout.ts # Stdout -> transcript entries for run viewer parse-stdout.ts # Stdout -> transcript entries for run viewer
build-config.ts # Form values -> adapterConfig JSON build-config.ts # Form values -> adapterConfig JSON
cli/ cli/
format-event.ts # Terminal output for `paperclip run --watch` format-event.ts # Terminal output for `paperclipai run --watch`
``` ```
Three registries consume these modules: Three registries consume these modules:

View File

@@ -9,38 +9,38 @@ Client-side commands for managing issues, agents, approvals, and more.
```sh ```sh
# List issues # List issues
pnpm paperclip issue list [--status todo,in_progress] [--assignee-agent-id <id>] [--match text] pnpm paperclipai issue list [--status todo,in_progress] [--assignee-agent-id <id>] [--match text]
# Get issue details # Get issue details
pnpm paperclip issue get <issue-id-or-identifier> pnpm paperclipai issue get <issue-id-or-identifier>
# Create issue # Create issue
pnpm paperclip issue create --title "..." [--description "..."] [--status todo] [--priority high] pnpm paperclipai issue create --title "..." [--description "..."] [--status todo] [--priority high]
# Update issue # Update issue
pnpm paperclip issue update <issue-id> [--status in_progress] [--comment "..."] pnpm paperclipai issue update <issue-id> [--status in_progress] [--comment "..."]
# Add comment # Add comment
pnpm paperclip issue comment <issue-id> --body "..." [--reopen] pnpm paperclipai issue comment <issue-id> --body "..." [--reopen]
# Checkout task # Checkout task
pnpm paperclip issue checkout <issue-id> --agent-id <agent-id> pnpm paperclipai issue checkout <issue-id> --agent-id <agent-id>
# Release task # Release task
pnpm paperclip issue release <issue-id> pnpm paperclipai issue release <issue-id>
``` ```
## Company Commands ## Company Commands
```sh ```sh
pnpm paperclip company list pnpm paperclipai company list
pnpm paperclip company get <company-id> pnpm paperclipai company get <company-id>
# Export to portable folder package (writes manifest + markdown files) # Export to portable folder package (writes manifest + markdown files)
pnpm paperclip company export <company-id> --out ./exports/acme --include company,agents pnpm paperclipai company export <company-id> --out ./exports/acme --include company,agents
# Preview import (no writes) # Preview import (no writes)
pnpm paperclip company import \ pnpm paperclipai company import \
--from https://github.com/<owner>/<repo>/tree/main/<path> \ --from https://github.com/<owner>/<repo>/tree/main/<path> \
--target existing \ --target existing \
--company-id <company-id> \ --company-id <company-id> \
@@ -48,7 +48,7 @@ pnpm paperclip company import \
--dry-run --dry-run
# Apply import # Apply import
pnpm paperclip company import \ pnpm paperclipai company import \
--from ./exports/acme \ --from ./exports/acme \
--target new \ --target new \
--new-company-name "Acme Imported" \ --new-company-name "Acme Imported" \
@@ -58,52 +58,52 @@ pnpm paperclip company import \
## Agent Commands ## Agent Commands
```sh ```sh
pnpm paperclip agent list pnpm paperclipai agent list
pnpm paperclip agent get <agent-id> pnpm paperclipai agent get <agent-id>
``` ```
## Approval Commands ## Approval Commands
```sh ```sh
# List approvals # List approvals
pnpm paperclip approval list [--status pending] pnpm paperclipai approval list [--status pending]
# Get approval # Get approval
pnpm paperclip approval get <approval-id> pnpm paperclipai approval get <approval-id>
# Create approval # Create approval
pnpm paperclip approval create --type hire_agent --payload '{"name":"..."}' [--issue-ids <id1,id2>] pnpm paperclipai approval create --type hire_agent --payload '{"name":"..."}' [--issue-ids <id1,id2>]
# Approve # Approve
pnpm paperclip approval approve <approval-id> [--decision-note "..."] pnpm paperclipai approval approve <approval-id> [--decision-note "..."]
# Reject # Reject
pnpm paperclip approval reject <approval-id> [--decision-note "..."] pnpm paperclipai approval reject <approval-id> [--decision-note "..."]
# Request revision # Request revision
pnpm paperclip approval request-revision <approval-id> [--decision-note "..."] pnpm paperclipai approval request-revision <approval-id> [--decision-note "..."]
# Resubmit # Resubmit
pnpm paperclip approval resubmit <approval-id> [--payload '{"..."}'] pnpm paperclipai approval resubmit <approval-id> [--payload '{"..."}']
# Comment # Comment
pnpm paperclip approval comment <approval-id> --body "..." pnpm paperclipai approval comment <approval-id> --body "..."
``` ```
## Activity Commands ## Activity Commands
```sh ```sh
pnpm paperclip activity list [--agent-id <id>] [--entity-type issue] [--entity-id <id>] pnpm paperclipai activity list [--agent-id <id>] [--entity-type issue] [--entity-id <id>]
``` ```
## Dashboard ## Dashboard
```sh ```sh
pnpm paperclip dashboard get pnpm paperclipai dashboard get
``` ```
## Heartbeat ## Heartbeat
```sh ```sh
pnpm paperclip heartbeat run --agent-id <agent-id> [--api-base http://localhost:3100] pnpm paperclipai heartbeat run --agent-id <agent-id> [--api-base http://localhost:3100]
``` ```

View File

@@ -8,7 +8,7 @@ The Paperclip CLI handles instance setup, diagnostics, and control-plane operati
## Usage ## Usage
```sh ```sh
pnpm paperclip --help pnpm paperclipai --help
``` ```
## Global Options ## Global Options
@@ -29,7 +29,7 @@ Company-scoped commands also accept `--company-id <id>`.
For clean local instances, pass `--data-dir` on the command you run: For clean local instances, pass `--data-dir` on the command you run:
```sh ```sh
pnpm paperclip run --data-dir ./tmp/paperclip-dev pnpm paperclipai run --data-dir ./tmp/paperclip-dev
``` ```
## Context Profiles ## Context Profiles
@@ -38,22 +38,22 @@ Store defaults to avoid repeating flags:
```sh ```sh
# Set defaults # Set defaults
pnpm paperclip context set --api-base http://localhost:3100 --company-id <id> pnpm paperclipai context set --api-base http://localhost:3100 --company-id <id>
# View current context # View current context
pnpm paperclip context show pnpm paperclipai context show
# List profiles # List profiles
pnpm paperclip context list pnpm paperclipai context list
# Switch profile # Switch profile
pnpm paperclip context use default pnpm paperclipai context use default
``` ```
To avoid storing secrets in context, use an env var: To avoid storing secrets in context, use an env var:
```sh ```sh
pnpm paperclip context set --api-key-env-var-name PAPERCLIP_API_KEY pnpm paperclipai context set --api-key-env-var-name PAPERCLIP_API_KEY
export PAPERCLIP_API_KEY=... export PAPERCLIP_API_KEY=...
``` ```

View File

@@ -5,32 +5,32 @@ summary: Onboard, run, doctor, and configure
Instance setup and diagnostics commands. Instance setup and diagnostics commands.
## `paperclip run` ## `paperclipai run`
One-command bootstrap and start: One-command bootstrap and start:
```sh ```sh
pnpm paperclip run pnpm paperclipai run
``` ```
Does: Does:
1. Auto-onboards if config is missing 1. Auto-onboards if config is missing
2. Runs `paperclip doctor` with repair enabled 2. Runs `paperclipai doctor` with repair enabled
3. Starts the server when checks pass 3. Starts the server when checks pass
Choose a specific instance: Choose a specific instance:
```sh ```sh
pnpm paperclip run --instance dev pnpm paperclipai run --instance dev
``` ```
## `paperclip onboard` ## `paperclipai onboard`
Interactive first-time setup: Interactive first-time setup:
```sh ```sh
pnpm paperclip onboard pnpm paperclipai onboard
``` ```
Prompts for: Prompts for:
@@ -40,13 +40,13 @@ Prompts for:
3. Public URL (if authenticated + public) 3. Public URL (if authenticated + public)
4. Database and secrets configuration 4. Database and secrets configuration
## `paperclip doctor` ## `paperclipai doctor`
Health checks with optional auto-repair: Health checks with optional auto-repair:
```sh ```sh
pnpm paperclip doctor pnpm paperclipai doctor
pnpm paperclip doctor --repair pnpm paperclipai doctor --repair
``` ```
Validates: Validates:
@@ -57,30 +57,30 @@ Validates:
- Storage configuration - Storage configuration
- Missing key files - Missing key files
## `paperclip configure` ## `paperclipai configure`
Update configuration sections: Update configuration sections:
```sh ```sh
pnpm paperclip configure --section server pnpm paperclipai configure --section server
pnpm paperclip configure --section secrets pnpm paperclipai configure --section secrets
pnpm paperclip configure --section storage pnpm paperclipai configure --section storage
``` ```
## `paperclip env` ## `paperclipai env`
Show resolved environment configuration: Show resolved environment configuration:
```sh ```sh
pnpm paperclip env pnpm paperclipai env
``` ```
## `paperclip allowed-hostname` ## `paperclipai allowed-hostname`
Allow a private hostname for authenticated/private mode: Allow a private hostname for authenticated/private mode:
```sh ```sh
pnpm paperclip allowed-hostname my-tailscale-host pnpm paperclipai allowed-hostname my-tailscale-host
``` ```
## Local Storage Paths ## Local Storage Paths
@@ -96,12 +96,12 @@ pnpm paperclip allowed-hostname my-tailscale-host
Override with: Override with:
```sh ```sh
PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclip run PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run
``` ```
Or pass `--data-dir` directly on any command: Or pass `--data-dir` directly on any command:
```sh ```sh
pnpm paperclip run --data-dir ./tmp/paperclip-dev pnpm paperclipai run --data-dir ./tmp/paperclip-dev
pnpm paperclip doctor --data-dir ./tmp/paperclip-dev pnpm paperclipai doctor --data-dir ./tmp/paperclip-dev
``` ```

View File

@@ -16,7 +16,7 @@ The default mode. Optimized for single-operator local use.
```sh ```sh
# Set during onboard # Set during onboard
pnpm paperclip onboard pnpm paperclipai onboard
# Choose "local_trusted" # Choose "local_trusted"
``` ```
@@ -33,14 +33,14 @@ For private network access (Tailscale, VPN, LAN).
- **Host trust**: private-host trust policy required - **Host trust**: private-host trust policy required
```sh ```sh
pnpm paperclip onboard pnpm paperclipai onboard
# Choose "authenticated" -> "private" # Choose "authenticated" -> "private"
``` ```
Allow custom Tailscale hostnames: Allow custom Tailscale hostnames:
```sh ```sh
pnpm paperclip allowed-hostname my-machine pnpm paperclipai allowed-hostname my-machine
``` ```
### `authenticated` + `public` ### `authenticated` + `public`
@@ -52,7 +52,7 @@ For internet-facing deployment.
- **Security**: stricter deployment checks in doctor - **Security**: stricter deployment checks in doctor
```sh ```sh
pnpm paperclip onboard pnpm paperclipai onboard
# Choose "authenticated" -> "public" # Choose "authenticated" -> "public"
``` ```
@@ -75,11 +75,11 @@ A signed-in user visits this URL to claim board ownership. This:
Update the deployment mode: Update the deployment mode:
```sh ```sh
pnpm paperclip configure --section server pnpm paperclipai configure --section server
``` ```
Runtime override via environment variable: Runtime override via environment variable:
```sh ```sh
PAPERCLIP_DEPLOYMENT_MODE=authenticated pnpm paperclip run PAPERCLIP_DEPLOYMENT_MODE=authenticated pnpm paperclipai run
``` ```

View File

@@ -29,13 +29,13 @@ No Docker or external database required. Paperclip uses embedded PostgreSQL auto
For a first-time install: For a first-time install:
```sh ```sh
pnpm paperclip run pnpm paperclipai run
``` ```
This does: This does:
1. Auto-onboards if config is missing 1. Auto-onboards if config is missing
2. Runs `paperclip doctor` with repair enabled 2. Runs `paperclipai doctor` with repair enabled
3. Starts the server when checks pass 3. Starts the server when checks pass
## Tailscale/Private Auth Dev Mode ## Tailscale/Private Auth Dev Mode
@@ -51,7 +51,7 @@ This binds the server to `0.0.0.0` for private-network access.
Allow additional private hostnames: Allow additional private hostnames:
```sh ```sh
pnpm paperclip allowed-hostname dotta-macbook-pro pnpm paperclipai allowed-hostname dotta-macbook-pro
``` ```
## Health Checks ## Health Checks
@@ -86,5 +86,5 @@ pnpm dev
Override with environment variables: Override with environment variables:
```sh ```sh
PAPERCLIP_HOME=/custom/path PAPERCLIP_INSTANCE_ID=dev pnpm paperclip run PAPERCLIP_HOME=/custom/path PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run
``` ```

View File

@@ -45,11 +45,11 @@ Paperclip supports three deployment configurations, from zero-friction local to
Set the mode during onboarding: Set the mode during onboarding:
```sh ```sh
pnpm paperclip onboard pnpm paperclipai onboard
``` ```
Or update it later: Or update it later:
```sh ```sh
pnpm paperclip configure --section server pnpm paperclipai configure --section server
``` ```

View File

@@ -22,19 +22,19 @@ This key is auto-created during onboarding. The key never leaves your machine.
Onboarding writes default secrets config: Onboarding writes default secrets config:
```sh ```sh
pnpm paperclip onboard pnpm paperclipai onboard
``` ```
Update secrets settings: Update secrets settings:
```sh ```sh
pnpm paperclip configure --section secrets pnpm paperclipai configure --section secrets
``` ```
Validate secrets config: Validate secrets config:
```sh ```sh
pnpm paperclip doctor pnpm paperclipai doctor
``` ```
### Environment Overrides ### Environment Overrides

View File

@@ -22,7 +22,7 @@ For production or multi-node deployments, use S3-compatible object storage (AWS
Configure via CLI: Configure via CLI:
```sh ```sh
pnpm paperclip configure --section storage pnpm paperclipai configure --section storage
``` ```
## Configuration ## Configuration

View File

@@ -85,7 +85,7 @@ Adapters are the bridge between Paperclip and agent runtimes. Each adapter is a
- **Server module** — `execute()` function that spawns/calls the agent, plus environment diagnostics - **Server module** — `execute()` function that spawns/calls the agent, plus environment diagnostics
- **UI module** — stdout parser for the run viewer, config form fields for agent creation - **UI module** — stdout parser for the run viewer, config form fields for agent creation
- **CLI module** — terminal formatter for `paperclip run --watch` - **CLI module** — terminal formatter for `paperclipai run --watch`
Built-in adapters: `claude_local`, `codex_local`, `process`, `http`. You can create custom adapters for any runtime. Built-in adapters: `claude_local`, `codex_local`, `process`, `http`. You can create custom adapters for any runtime.

View File

@@ -38,7 +38,7 @@ No Docker or external database required — Paperclip uses an embedded PostgreSQ
## Option 3: One-Command Bootstrap ## Option 3: One-Command Bootstrap
```sh ```sh
pnpm paperclip run pnpm paperclipai run
``` ```
This auto-onboards if config is missing, runs health checks with auto-repair, and starts the server. This auto-onboards if config is missing, runs health checks with auto-repair, and starts the server.

View File

@@ -5,17 +5,17 @@
"scripts": { "scripts": {
"dev": "node scripts/dev-runner.mjs dev", "dev": "node scripts/dev-runner.mjs dev",
"dev:watch": "PAPERCLIP_MIGRATION_PROMPT=never node scripts/dev-runner.mjs watch", "dev:watch": "PAPERCLIP_MIGRATION_PROMPT=never node scripts/dev-runner.mjs watch",
"dev:server": "pnpm --filter @paperclip/server dev", "dev:server": "pnpm --filter @paperclipai/server dev",
"dev:ui": "pnpm --filter @paperclip/ui dev", "dev:ui": "pnpm --filter @paperclipai/ui dev",
"build": "pnpm -r build", "build": "pnpm -r build",
"typecheck": "pnpm -r typecheck", "typecheck": "pnpm -r typecheck",
"test": "vitest", "test": "vitest",
"test:run": "vitest run", "test:run": "vitest run",
"db:generate": "pnpm --filter @paperclip/db generate", "db:generate": "pnpm --filter @paperclipai/db generate",
"db:migrate": "pnpm --filter @paperclip/db migrate", "db:migrate": "pnpm --filter @paperclipai/db migrate",
"secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts", "secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts",
"db:backup": "./scripts/backup-db.sh", "db:backup": "./scripts/backup-db.sh",
"paperclip": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts", "paperclipai": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts",
"docs:dev": "cd docs && npx mintlify dev" "docs:dev": "cd docs && npx mintlify dev"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/adapter-utils", "name": "@paperclipai/adapter-utils",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/adapter-claude-local", "name": "@paperclipai/adapter-claude-local",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@paperclip/adapter-utils": "workspace:*", "@paperclipai/adapter-utils": "workspace:*",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclip/adapter-utils"; import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
import type { RunProcessResult } from "@paperclip/adapter-utils/server-utils"; import type { RunProcessResult } from "@paperclipai/adapter-utils/server-utils";
import { import {
asString, asString,
asNumber, asNumber,
@@ -18,7 +18,7 @@ import {
ensurePathInEnv, ensurePathInEnv,
renderTemplate, renderTemplate,
runChildProcess, runChildProcess,
} from "@paperclip/adapter-utils/server-utils"; } from "@paperclipai/adapter-utils/server-utils";
import { import {
parseClaudeStreamJson, parseClaudeStreamJson,
describeClaudeFailure, describeClaudeFailure,

View File

@@ -6,7 +6,7 @@ export {
isClaudeMaxTurnsResult, isClaudeMaxTurnsResult,
isClaudeUnknownSessionError, isClaudeUnknownSessionError,
} from "./parse.js"; } from "./parse.js";
import type { AdapterSessionCodec } from "@paperclip/adapter-utils"; import type { AdapterSessionCodec } from "@paperclipai/adapter-utils";
function readNonEmptyString(value: unknown): string | null { function readNonEmptyString(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;

View File

@@ -1,5 +1,5 @@
import type { UsageSummary } from "@paperclip/adapter-utils"; import type { UsageSummary } from "@paperclipai/adapter-utils";
import { asString, asNumber, parseObject, parseJson } from "@paperclip/adapter-utils/server-utils"; import { asString, asNumber, parseObject, parseJson } from "@paperclipai/adapter-utils/server-utils";
const CLAUDE_AUTH_REQUIRED_RE = /(?:not\s+logged\s+in|please\s+log\s+in|please\s+run\s+`?claude\s+login`?|login\s+required|requires\s+login|unauthorized|authentication\s+required)/i; const CLAUDE_AUTH_REQUIRED_RE = /(?:not\s+logged\s+in|please\s+log\s+in|please\s+run\s+`?claude\s+login`?|login\s+required|requires\s+login|unauthorized|authentication\s+required)/i;
const URL_RE = /(https?:\/\/[^\s'"`<>()[\]{};,!?]+[^\s'"`<>()[\]{};,!.?:]+)/gi; const URL_RE = /(https?:\/\/[^\s'"`<>()[\]{};,!?]+[^\s'"`<>()[\]{};,!.?:]+)/gi;

View File

@@ -2,14 +2,14 @@ import type {
AdapterEnvironmentCheck, AdapterEnvironmentCheck,
AdapterEnvironmentTestContext, AdapterEnvironmentTestContext,
AdapterEnvironmentTestResult, AdapterEnvironmentTestResult,
} from "@paperclip/adapter-utils"; } from "@paperclipai/adapter-utils";
import { import {
asString, asString,
parseObject, parseObject,
ensureAbsoluteDirectory, ensureAbsoluteDirectory,
ensureCommandResolvable, ensureCommandResolvable,
ensurePathInEnv, ensurePathInEnv,
} from "@paperclip/adapter-utils/server-utils"; } from "@paperclipai/adapter-utils/server-utils";
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
if (checks.some((check) => check.level === "error")) return "fail"; if (checks.some((check) => check.level === "error")) return "fail";

View File

@@ -1,4 +1,4 @@
import type { CreateConfigValues } from "@paperclip/adapter-utils"; import type { CreateConfigValues } from "@paperclipai/adapter-utils";
function parseCommaArgs(value: string): string[] { function parseCommaArgs(value: string): string[] {
return value return value

View File

@@ -1,4 +1,4 @@
import type { TranscriptEntry } from "@paperclip/adapter-utils"; import type { TranscriptEntry } from "@paperclipai/adapter-utils";
function asRecord(value: unknown): Record<string, unknown> | null { function asRecord(value: unknown): Record<string, unknown> | null {
if (typeof value !== "object" || value === null || Array.isArray(value)) return null; if (typeof value !== "object" || value === null || Array.isArray(value)) return null;

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/adapter-codex-local", "name": "@paperclipai/adapter-codex-local",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@paperclip/adapter-utils": "workspace:*", "@paperclipai/adapter-utils": "workspace:*",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclip/adapter-utils"; import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
import { import {
asString, asString,
asNumber, asNumber,
@@ -16,7 +16,7 @@ import {
ensurePathInEnv, ensurePathInEnv,
renderTemplate, renderTemplate,
runChildProcess, runChildProcess,
} from "@paperclip/adapter-utils/server-utils"; } from "@paperclipai/adapter-utils/server-utils";
import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js"; import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
const PAPERCLIP_SKILLS_DIR = path.resolve( const PAPERCLIP_SKILLS_DIR = path.resolve(

View File

@@ -1,7 +1,7 @@
export { execute } from "./execute.js"; export { execute } from "./execute.js";
export { testEnvironment } from "./test.js"; export { testEnvironment } from "./test.js";
export { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js"; export { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
import type { AdapterSessionCodec } from "@paperclip/adapter-utils"; import type { AdapterSessionCodec } from "@paperclipai/adapter-utils";
function readNonEmptyString(value: unknown): string | null { function readNonEmptyString(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;

View File

@@ -1,4 +1,4 @@
import { asString, asNumber, parseObject, parseJson } from "@paperclip/adapter-utils/server-utils"; import { asString, asNumber, parseObject, parseJson } from "@paperclipai/adapter-utils/server-utils";
export function parseCodexJsonl(stdout: string) { export function parseCodexJsonl(stdout: string) {
let sessionId: string | null = null; let sessionId: string | null = null;

View File

@@ -2,14 +2,14 @@ import type {
AdapterEnvironmentCheck, AdapterEnvironmentCheck,
AdapterEnvironmentTestContext, AdapterEnvironmentTestContext,
AdapterEnvironmentTestResult, AdapterEnvironmentTestResult,
} from "@paperclip/adapter-utils"; } from "@paperclipai/adapter-utils";
import { import {
asString, asString,
parseObject, parseObject,
ensureAbsoluteDirectory, ensureAbsoluteDirectory,
ensureCommandResolvable, ensureCommandResolvable,
ensurePathInEnv, ensurePathInEnv,
} from "@paperclip/adapter-utils/server-utils"; } from "@paperclipai/adapter-utils/server-utils";
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
if (checks.some((check) => check.level === "error")) return "fail"; if (checks.some((check) => check.level === "error")) return "fail";

View File

@@ -1,4 +1,4 @@
import type { CreateConfigValues } from "@paperclip/adapter-utils"; import type { CreateConfigValues } from "@paperclipai/adapter-utils";
function parseCommaArgs(value: string): string[] { function parseCommaArgs(value: string): string[] {
return value return value

View File

@@ -1,4 +1,4 @@
import type { TranscriptEntry } from "@paperclip/adapter-utils"; import type { TranscriptEntry } from "@paperclipai/adapter-utils";
function safeJsonParse(text: string): unknown { function safeJsonParse(text: string): unknown {
try { try {

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/adapter-openclaw", "name": "@paperclipai/adapter-openclaw",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@paperclip/adapter-utils": "workspace:*", "@paperclipai/adapter-utils": "workspace:*",
"picocolors": "^1.1.1" "picocolors": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,5 +1,5 @@
import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclip/adapter-utils"; import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
import { asNumber, asString, parseObject } from "@paperclip/adapter-utils/server-utils"; import { asNumber, asString, parseObject } from "@paperclipai/adapter-utils/server-utils";
import { parseOpenClawResponse } from "./parse.js"; import { parseOpenClawResponse } from "./parse.js";
function nonEmpty(value: unknown): string | null { function nonEmpty(value: unknown): string | null {

View File

@@ -2,8 +2,8 @@ import type {
AdapterEnvironmentCheck, AdapterEnvironmentCheck,
AdapterEnvironmentTestContext, AdapterEnvironmentTestContext,
AdapterEnvironmentTestResult, AdapterEnvironmentTestResult,
} from "@paperclip/adapter-utils"; } from "@paperclipai/adapter-utils";
import { asString, parseObject } from "@paperclip/adapter-utils/server-utils"; import { asString, parseObject } from "@paperclipai/adapter-utils/server-utils";
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
if (checks.some((check) => check.level === "error")) return "fail"; if (checks.some((check) => check.level === "error")) return "fail";
@@ -58,7 +58,7 @@ function pushDeploymentDiagnostics(
code: "openclaw_private_bind_hostname_not_allowed", code: "openclaw_private_bind_hostname_not_allowed",
level: "warn", level: "warn",
message: `Paperclip bind host "${bindHost}" is not in allowed hostnames.`, message: `Paperclip bind host "${bindHost}" is not in allowed hostnames.`,
hint: `Run pnpm paperclip allowed-hostname ${bindHost} so remote OpenClaw callbacks can pass host checks.`, hint: `Run pnpm paperclipai allowed-hostname ${bindHost} so remote OpenClaw callbacks can pass host checks.`,
}); });
} }
@@ -76,7 +76,7 @@ function pushDeploymentDiagnostics(
code: "openclaw_private_no_allowed_hostnames", code: "openclaw_private_no_allowed_hostnames",
level: "warn", level: "warn",
message: "No explicit allowed hostnames are configured for authenticated/private mode.", message: "No explicit allowed hostnames are configured for authenticated/private mode.",
hint: "Set one with pnpm paperclip allowed-hostname <host> when OpenClaw runs on another machine.", hint: "Set one with pnpm paperclipai allowed-hostname <host> when OpenClaw runs on another machine.",
}); });
} }
} }

View File

@@ -1,4 +1,4 @@
import type { CreateConfigValues } from "@paperclip/adapter-utils"; import type { CreateConfigValues } from "@paperclipai/adapter-utils";
export function buildOpenClawConfig(v: CreateConfigValues): Record<string, unknown> { export function buildOpenClawConfig(v: CreateConfigValues): Record<string, unknown> {
const ac: Record<string, unknown> = {}; const ac: Record<string, unknown> = {};

View File

@@ -1,4 +1,4 @@
import type { TranscriptEntry } from "@paperclip/adapter-utils"; import type { TranscriptEntry } from "@paperclipai/adapter-utils";
export function parseOpenClawStdoutLine(line: string, ts: string): TranscriptEntry[] { export function parseOpenClawStdoutLine(line: string, ts: string): TranscriptEntry[] {
return [{ kind: "stdout", ts, text: line }]; return [{ kind: "stdout", ts, text: line }];

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/db", "name": "@paperclipai/db",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -14,7 +14,7 @@
"seed": "tsx src/seed.ts" "seed": "tsx src/seed.ts"
}, },
"dependencies": { "dependencies": {
"@paperclip/shared": "workspace:*", "@paperclipai/shared": "workspace:*",
"drizzle-orm": "^0.38.4", "drizzle-orm": "^0.38.4",
"postgres": "^3.4.5" "postgres": "^3.4.5"
}, },

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/shared", "name": "@paperclipai/shared",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",

View File

@@ -46,7 +46,7 @@ const pnpmBin = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const serverScript = mode === "watch" ? "dev:watch" : "dev"; const serverScript = mode === "watch" ? "dev:watch" : "dev";
const child = spawn( const child = spawn(
pnpmBin, pnpmBin,
["--filter", "@paperclip/server", serverScript, ...forwardedArgs], ["--filter", "@paperclipai/server", serverScript, ...forwardedArgs],
{ stdio: "inherit", env }, { stdio: "inherit", env },
); );

View File

@@ -1,5 +1,5 @@
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { agents, createDb } from "@paperclip/db"; import { agents, createDb } from "@paperclipai/db";
import { secretService } from "../server/src/services/secrets.js"; import { secretService } from "../server/src/services/secrets.js";
const SENSITIVE_ENV_KEY_RE = const SENSITIVE_ENV_KEY_RE =

View File

@@ -1,5 +1,5 @@
{ {
"name": "@paperclip/server", "name": "@paperclipai/server",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -11,12 +11,12 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@paperclip/adapter-claude-local": "workspace:*", "@paperclipai/adapter-claude-local": "workspace:*",
"@paperclip/adapter-codex-local": "workspace:*", "@paperclipai/adapter-codex-local": "workspace:*",
"@paperclip/adapter-openclaw": "workspace:*", "@paperclipai/adapter-openclaw": "workspace:*",
"@paperclip/adapter-utils": "workspace:*", "@paperclipai/adapter-utils": "workspace:*",
"@paperclip/db": "workspace:*", "@paperclipai/db": "workspace:*",
"@paperclip/shared": "workspace:*", "@paperclipai/shared": "workspace:*",
"@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/client-s3": "^3.888.0",
"better-auth": "^1.3.8", "better-auth": "^1.3.8",
"detect-port": "^2.1.0", "detect-port": "^2.1.0",

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import { models as codexFallbackModels } from "@paperclip/adapter-codex-local"; import { models as codexFallbackModels } from "@paperclipai/adapter-codex-local";
import { listAdapterModels } from "../adapters/index.js"; import { listAdapterModels } from "../adapters/index.js";
import { resetCodexModelsCacheForTests } from "../adapters/codex-models.js"; import { resetCodexModelsCacheForTests } from "../adapters/codex-models.js";

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { sessionCodec as claudeSessionCodec } from "@paperclip/adapter-claude-local/server"; import { sessionCodec as claudeSessionCodec } from "@paperclipai/adapter-claude-local/server";
import { sessionCodec as codexSessionCodec, isCodexUnknownSessionError } from "@paperclip/adapter-codex-local/server"; import { sessionCodec as codexSessionCodec, isCodexUnknownSessionError } from "@paperclipai/adapter-codex-local/server";
describe("adapter session codecs", () => { describe("adapter session codecs", () => {
it("normalizes claude session params with cwd", () => { it("normalizes claude session params with cwd", () => {

View File

@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { testEnvironment } from "@paperclip/adapter-claude-local/server"; import { testEnvironment } from "@paperclipai/adapter-claude-local/server";
const ORIGINAL_ANTHROPIC = process.env.ANTHROPIC_API_KEY; const ORIGINAL_ANTHROPIC = process.env.ANTHROPIC_API_KEY;

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { isClaudeMaxTurnsResult } from "@paperclip/adapter-claude-local/server"; import { isClaudeMaxTurnsResult } from "@paperclipai/adapter-claude-local/server";
describe("claude_local max-turn detection", () => { describe("claude_local max-turn detection", () => {
it("detects max-turn exhaustion by subtype", () => { it("detects max-turn exhaustion by subtype", () => {

View File

@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { isCodexUnknownSessionError, parseCodexJsonl } from "@paperclip/adapter-codex-local/server"; import { isCodexUnknownSessionError, parseCodexJsonl } from "@paperclipai/adapter-codex-local/server";
import { parseCodexStdoutLine } from "@paperclip/adapter-codex-local/ui"; import { parseCodexStdoutLine } from "@paperclipai/adapter-codex-local/ui";
import { printCodexStreamEvent } from "@paperclip/adapter-codex-local/cli"; import { printCodexStreamEvent } from "@paperclipai/adapter-codex-local/cli";
describe("codex_local parser", () => { describe("codex_local parser", () => {
it("extracts session, summary, usage, and terminal error message", () => { it("extracts session, summary, usage, and terminal error message", () => {

View File

@@ -44,13 +44,13 @@ describe("privateHostnameGuard", () => {
const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] }); const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] });
const res = await request(app).get("/api/health").set("Host", "dotta-macbook-pro:3100"); const res = await request(app).get("/api/health").set("Host", "dotta-macbook-pro:3100");
expect(res.status).toBe(403); expect(res.status).toBe(403);
expect(res.body?.error).toContain("please run pnpm paperclip allowed-hostname dotta-macbook-pro"); expect(res.body?.error).toContain("please run pnpm paperclipai allowed-hostname dotta-macbook-pro");
}); });
it("blocks unknown hostnames on page routes with plain-text remediation command", async () => { it("blocks unknown hostnames on page routes with plain-text remediation command", async () => {
const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] }); const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] });
const res = await request(app).get("/dashboard").set("Host", "dotta-macbook-pro:3100"); const res = await request(app).get("/dashboard").set("Host", "dotta-macbook-pro:3100");
expect(res.status).toBe(403); expect(res.status).toBe(403);
expect(res.text).toContain("please run pnpm paperclip allowed-hostname dotta-macbook-pro"); expect(res.text).toContain("please run pnpm paperclipai allowed-hostname dotta-macbook-pro");
}); });
}); });

View File

@@ -1,5 +1,5 @@
import type { AdapterModel } from "./types.js"; import type { AdapterModel } from "./types.js";
import { models as codexFallbackModels } from "@paperclip/adapter-codex-local"; import { models as codexFallbackModels } from "@paperclipai/adapter-codex-local";
import { readConfigFile } from "../config-file.js"; import { readConfigFile } from "../config-file.js";
const OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models"; const OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models";

View File

@@ -13,5 +13,5 @@ export type {
UsageSummary, UsageSummary,
AdapterAgent, AdapterAgent,
AdapterRuntime, AdapterRuntime,
} from "@paperclip/adapter-utils"; } from "@paperclipai/adapter-utils";
export { runningProcesses } from "./utils.js"; export { runningProcesses } from "./utils.js";

View File

@@ -3,22 +3,22 @@ import {
execute as claudeExecute, execute as claudeExecute,
testEnvironment as claudeTestEnvironment, testEnvironment as claudeTestEnvironment,
sessionCodec as claudeSessionCodec, sessionCodec as claudeSessionCodec,
} from "@paperclip/adapter-claude-local/server"; } from "@paperclipai/adapter-claude-local/server";
import { agentConfigurationDoc as claudeAgentConfigurationDoc, models as claudeModels } from "@paperclip/adapter-claude-local"; import { agentConfigurationDoc as claudeAgentConfigurationDoc, models as claudeModels } from "@paperclipai/adapter-claude-local";
import { import {
execute as codexExecute, execute as codexExecute,
testEnvironment as codexTestEnvironment, testEnvironment as codexTestEnvironment,
sessionCodec as codexSessionCodec, sessionCodec as codexSessionCodec,
} from "@paperclip/adapter-codex-local/server"; } from "@paperclipai/adapter-codex-local/server";
import { agentConfigurationDoc as codexAgentConfigurationDoc, models as codexModels } from "@paperclip/adapter-codex-local"; import { agentConfigurationDoc as codexAgentConfigurationDoc, models as codexModels } from "@paperclipai/adapter-codex-local";
import { import {
execute as openclawExecute, execute as openclawExecute,
testEnvironment as openclawTestEnvironment, testEnvironment as openclawTestEnvironment,
} from "@paperclip/adapter-openclaw/server"; } from "@paperclipai/adapter-openclaw/server";
import { import {
agentConfigurationDoc as openclawAgentConfigurationDoc, agentConfigurationDoc as openclawAgentConfigurationDoc,
models as openclawModels, models as openclawModels,
} from "@paperclip/adapter-openclaw"; } from "@paperclipai/adapter-openclaw";
import { listCodexModels } from "./codex-models.js"; import { listCodexModels } from "./codex-models.js";
import { processAdapter } from "./process/index.js"; import { processAdapter } from "./process/index.js";
import { httpAdapter } from "./http/index.js"; import { httpAdapter } from "./http/index.js";

View File

@@ -16,4 +16,4 @@ export type {
AdapterSessionCodec, AdapterSessionCodec,
AdapterModel, AdapterModel,
ServerAdapterModule, ServerAdapterModule,
} from "@paperclip/adapter-utils"; } from "@paperclipai/adapter-utils";

View File

@@ -22,11 +22,11 @@ export {
ensurePathInEnv, ensurePathInEnv,
ensureAbsoluteDirectory, ensureAbsoluteDirectory,
ensureCommandResolvable, ensureCommandResolvable,
} from "@paperclip/adapter-utils/server-utils"; } from "@paperclipai/adapter-utils/server-utils";
// Re-export runChildProcess with the server's pino logger wired in. // Re-export runChildProcess with the server's pino logger wired in.
import { runChildProcess as _runChildProcess } from "@paperclip/adapter-utils/server-utils"; import { runChildProcess as _runChildProcess } from "@paperclipai/adapter-utils/server-utils";
import type { RunProcessResult } from "@paperclip/adapter-utils/server-utils"; import type { RunProcessResult } from "@paperclipai/adapter-utils/server-utils";
export async function runChildProcess( export async function runChildProcess(
runId: string, runId: string,

View File

@@ -2,8 +2,8 @@ import express, { Router, type Request as ExpressRequest } from "express";
import path from "node:path"; import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import type { DeploymentExposure, DeploymentMode } from "@paperclip/shared"; import type { DeploymentExposure, DeploymentMode } from "@paperclipai/shared";
import type { StorageService } from "./storage/types.js"; import type { StorageService } from "./storage/types.js";
import { httpLogger, errorHandler } from "./middleware/index.js"; import { httpLogger, errorHandler } from "./middleware/index.js";
import { actorMiddleware } from "./middleware/auth.js"; import { actorMiddleware } from "./middleware/auth.js";

View File

@@ -3,13 +3,13 @@ import type { IncomingHttpHeaders } from "node:http";
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { toNodeHandler } from "better-auth/node"; import { toNodeHandler } from "better-auth/node";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import { import {
authAccounts, authAccounts,
authSessions, authSessions,
authUsers, authUsers,
authVerifications, authVerifications,
} from "@paperclip/db"; } from "@paperclipai/db";
import type { Config } from "../config.js"; import type { Config } from "../config.js";
export type BetterAuthSessionUser = { export type BetterAuthSessionUser = {

View File

@@ -1,8 +1,8 @@
import { randomBytes } from "node:crypto"; import { randomBytes } from "node:crypto";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import { companies, companyMemberships, instanceUserRoles } from "@paperclip/db"; import { companies, companyMemberships, instanceUserRoles } from "@paperclipai/db";
import type { DeploymentMode } from "@paperclip/shared"; import type { DeploymentMode } from "@paperclipai/shared";
const LOCAL_BOARD_USER_ID = "local-board"; const LOCAL_BOARD_USER_ID = "local-board";
const CLAIM_TTL_MS = 1000 * 60 * 60 * 24; const CLAIM_TTL_MS = 1000 * 60 * 60 * 24;

View File

@@ -1,5 +1,5 @@
import fs from "node:fs"; import fs from "node:fs";
import { paperclipConfigSchema, type PaperclipConfig } from "@paperclip/shared"; import { paperclipConfigSchema, type PaperclipConfig } from "@paperclipai/shared";
import { resolvePaperclipConfigPath } from "./paths.js"; import { resolvePaperclipConfigPath } from "./paths.js";
export function readConfigFile(): PaperclipConfig | null { export function readConfigFile(): PaperclipConfig | null {

View File

@@ -13,7 +13,7 @@ import {
type DeploymentMode, type DeploymentMode,
type SecretProvider, type SecretProvider,
type StorageProvider, type StorageProvider,
} from "@paperclip/shared"; } from "@paperclipai/shared";
import { import {
resolveDefaultEmbeddedPostgresDir, resolveDefaultEmbeddedPostgresDir,
resolveDefaultSecretsKeyFilePath, resolveDefaultSecretsKeyFilePath,

View File

@@ -15,7 +15,7 @@ import {
companies, companies,
companyMemberships, companyMemberships,
instanceUserRoles, instanceUserRoles,
} from "@paperclip/db"; } from "@paperclipai/db";
import detectPort from "detect-port"; import detectPort from "detect-port";
import { createApp } from "./app.js"; import { createApp } from "./app.js";
import { loadConfig } from "./config.js"; import { loadConfig } from "./config.js";

View File

@@ -1,10 +1,10 @@
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import type { Request, RequestHandler } from "express"; import type { Request, RequestHandler } from "express";
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import { agentApiKeys, agents, companyMemberships, instanceUserRoles } from "@paperclip/db"; import { agentApiKeys, agents, companyMemberships, instanceUserRoles } from "@paperclipai/db";
import { verifyLocalAgentJwt } from "../agent-auth-jwt.js"; import { verifyLocalAgentJwt } from "../agent-auth-jwt.js";
import type { DeploymentMode } from "@paperclip/shared"; import type { DeploymentMode } from "@paperclipai/shared";
import type { BetterAuthSessionResult } from "../auth/better-auth.js"; import type { BetterAuthSessionResult } from "../auth/better-auth.js";
import { logger } from "./logger.js"; import { logger } from "./logger.js";

View File

@@ -45,7 +45,7 @@ export function resolvePrivateHostnameAllowSet(opts: { allowedHostnames: string[
function blockedHostnameMessage(hostname: string): string { function blockedHostnameMessage(hostname: string): string {
return ( return (
`Hostname '${hostname}' is not allowed for this Paperclip instance. ` + `Hostname '${hostname}' is not allowed for this Paperclip instance. ` +
`If you want to allow this hostname, please run pnpm paperclip allowed-hostname ${hostname}` `If you want to allow this hostname, please run pnpm paperclipai allowed-hostname ${hostname}`
); );
} }
@@ -68,7 +68,7 @@ export function privateHostnameGuard(opts: {
const wantsJson = req.path.startsWith("/api") || req.accepts(["json", "html", "text"]) === "json"; const wantsJson = req.path.startsWith("/api") || req.accepts(["json", "html", "text"]) === "json";
if (!hostname) { if (!hostname) {
const error = "Missing Host header. If you want to allow a hostname, run pnpm paperclip allowed-hostname <host>."; const error = "Missing Host header. If you want to allow a hostname, run pnpm paperclipai allowed-hostname <host>.";
if (wantsJson) { if (wantsJson) {
res.status(403).json({ error }); res.status(403).json({ error });
} else { } else {

View File

@@ -2,9 +2,9 @@ import { createHash } from "node:crypto";
import type { IncomingMessage, Server as HttpServer } from "node:http"; import type { IncomingMessage, Server as HttpServer } from "node:http";
import type { Duplex } from "node:stream"; import type { Duplex } from "node:stream";
import { and, eq, isNull } from "drizzle-orm"; import { and, eq, isNull } from "drizzle-orm";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import { agentApiKeys, companyMemberships, instanceUserRoles } from "@paperclip/db"; import { agentApiKeys, companyMemberships, instanceUserRoles } from "@paperclipai/db";
import type { DeploymentMode } from "@paperclip/shared"; import type { DeploymentMode } from "@paperclipai/shared";
import { WebSocket, WebSocketServer } from "ws"; import { WebSocket, WebSocketServer } from "ws";
import type { BetterAuthSessionResult } from "../auth/better-auth.js"; import type { BetterAuthSessionResult } from "../auth/better-auth.js";
import { logger } from "../middleware/logger.js"; import { logger } from "../middleware/logger.js";

View File

@@ -5,13 +5,13 @@ import { fileURLToPath } from "node:url";
import { Router } from "express"; import { Router } from "express";
import type { Request } from "express"; import type { Request } from "express";
import { and, eq, isNull, desc } from "drizzle-orm"; import { and, eq, isNull, desc } from "drizzle-orm";
import type { Db } from "@paperclip/db"; import type { Db } from "@paperclipai/db";
import { import {
agentApiKeys, agentApiKeys,
authUsers, authUsers,
invites, invites,
joinRequests, joinRequests,
} from "@paperclip/db"; } from "@paperclipai/db";
import { import {
acceptInviteSchema, acceptInviteSchema,
claimJoinRequestApiKeySchema, claimJoinRequestApiKeySchema,
@@ -20,8 +20,8 @@ import {
updateMemberPermissionsSchema, updateMemberPermissionsSchema,
updateUserCompanyAccessSchema, updateUserCompanyAccessSchema,
PERMISSION_KEYS, PERMISSION_KEYS,
} from "@paperclip/shared"; } from "@paperclipai/shared";
import type { DeploymentExposure, DeploymentMode } from "@paperclip/shared"; import type { DeploymentExposure, DeploymentMode } from "@paperclipai/shared";
import { forbidden, conflict, notFound, unauthorized, badRequest } from "../errors.js"; import { forbidden, conflict, notFound, unauthorized, badRequest } from "../errors.js";
import { validate } from "../middleware/validate.js"; import { validate } from "../middleware/validate.js";
import { accessService, agentService, logActivity } from "../services/index.js"; import { accessService, agentService, logActivity } from "../services/index.js";
@@ -155,7 +155,7 @@ function buildJoinConnectivityDiagnostics(input: {
code: "openclaw_private_bind_not_allowed", code: "openclaw_private_bind_not_allowed",
level: "warn", level: "warn",
message: `Paperclip bind host \"${bindHost}\" is not in allowed hostnames.`, message: `Paperclip bind host \"${bindHost}\" is not in allowed hostnames.`,
hint: `Run pnpm paperclip allowed-hostname ${bindHost}`, hint: `Run pnpm paperclipai allowed-hostname ${bindHost}`,
}); });
} }
if (callbackHost && !isLoopbackHost(callbackHost) && allowSet.size === 0) { if (callbackHost && !isLoopbackHost(callbackHost) && allowSet.size === 0) {
@@ -163,7 +163,7 @@ function buildJoinConnectivityDiagnostics(input: {
code: "openclaw_private_allowed_hostnames_empty", code: "openclaw_private_allowed_hostnames_empty",
level: "warn", level: "warn",
message: "No explicit allowed hostnames are configured for authenticated/private mode.", message: "No explicit allowed hostnames are configured for authenticated/private mode.",
hint: "Set one with pnpm paperclip allowed-hostname <host> when OpenClaw runs off-host.", hint: "Set one with pnpm paperclipai allowed-hostname <host> when OpenClaw runs off-host.",
}); });
} }
} }
@@ -355,7 +355,7 @@ function buildInviteOnboardingManifest(
allowedHostnames: opts.allowedHostnames, allowedHostnames: opts.allowedHostnames,
guidance: guidance:
opts.deploymentMode === "authenticated" && opts.deploymentExposure === "private" opts.deploymentMode === "authenticated" && opts.deploymentExposure === "private"
? "If OpenClaw runs on another machine, ensure the Paperclip hostname is reachable and allowed via `pnpm paperclip allowed-hostname <host>`." ? "If OpenClaw runs on another machine, ensure the Paperclip hostname is reachable and allowed via `pnpm paperclipai allowed-hostname <host>`."
: "Ensure OpenClaw can reach this Paperclip API base URL for callbacks and claims.", : "Ensure OpenClaw can reach this Paperclip API base URL for callbacks and claims.",
}, },
skill: { skill: {

Some files were not shown because too many files have changed in this diff Show More