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:
@@ -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:
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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`",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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`.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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`)})`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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[] = [];
|
||||||
|
|||||||
@@ -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}`),
|
||||||
"",
|
"",
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ export {
|
|||||||
type SecretsConfig,
|
type SecretsConfig,
|
||||||
type SecretsLocalEncryptedConfig,
|
type SecretsLocalEncryptedConfig,
|
||||||
type ConfigMeta,
|
type ConfigMeta,
|
||||||
} from "@paperclip/shared";
|
} from "@paperclipai/shared";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
80
doc/CLI.md
80
doc/CLI.md
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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=...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> = {};
|
||||||
|
|||||||
@@ -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 }];
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ export type {
|
|||||||
AdapterSessionCodec,
|
AdapterSessionCodec,
|
||||||
AdapterModel,
|
AdapterModel,
|
||||||
ServerAdapterModule,
|
ServerAdapterModule,
|
||||||
} from "@paperclip/adapter-utils";
|
} from "@paperclipai/adapter-utils";
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user