mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
3585 lines
109 KiB
TypeScript
3585 lines
109 KiB
TypeScript
import {
|
|
OpenAPIRegistry,
|
|
OpenApiGeneratorV3,
|
|
extendZodWithOpenApi,
|
|
} from "@asteasolutions/zod-to-openapi";
|
|
import { z } from "zod";
|
|
import {
|
|
// Agent
|
|
createAgentSchema,
|
|
createAgentHireSchema,
|
|
updateAgentSchema,
|
|
updateAgentPermissionsSchema,
|
|
updateAgentInstructionsPathSchema,
|
|
updateAgentInstructionsBundleSchema,
|
|
upsertAgentInstructionsFileSchema,
|
|
createAgentKeySchema,
|
|
wakeAgentSchema,
|
|
resetAgentSessionSchema,
|
|
agentSkillSyncSchema,
|
|
testAdapterEnvironmentSchema,
|
|
// Issue
|
|
createIssueSchema,
|
|
updateIssueSchema,
|
|
createIssueLabelSchema,
|
|
addIssueCommentSchema,
|
|
checkoutIssueSchema,
|
|
linkIssueApprovalSchema,
|
|
createIssueWorkProductSchema,
|
|
updateIssueWorkProductSchema,
|
|
upsertIssueDocumentSchema,
|
|
restoreIssueDocumentRevisionSchema,
|
|
upsertIssueFeedbackVoteSchema,
|
|
// Project
|
|
createProjectSchema,
|
|
updateProjectSchema,
|
|
createProjectWorkspaceSchema,
|
|
updateProjectWorkspaceSchema,
|
|
// Company
|
|
createCompanySchema,
|
|
updateCompanySchema,
|
|
updateCompanyBrandingSchema,
|
|
// Routine
|
|
createRoutineSchema,
|
|
updateRoutineSchema,
|
|
createRoutineTriggerSchema,
|
|
updateRoutineTriggerSchema,
|
|
rotateRoutineTriggerSecretSchema,
|
|
runRoutineSchema,
|
|
// Goal
|
|
createGoalSchema,
|
|
updateGoalSchema,
|
|
// Secret
|
|
createSecretSchema,
|
|
updateSecretSchema,
|
|
rotateSecretSchema,
|
|
// Approval
|
|
createApprovalSchema,
|
|
resolveApprovalSchema,
|
|
requestApprovalRevisionSchema,
|
|
resubmitApprovalSchema,
|
|
addApprovalCommentSchema,
|
|
// Cost / budget
|
|
createCostEventSchema,
|
|
createFinanceEventSchema,
|
|
updateBudgetSchema,
|
|
upsertBudgetPolicySchema,
|
|
resolveBudgetIncidentSchema,
|
|
// Sidebar
|
|
upsertSidebarOrderPreferenceSchema,
|
|
// Execution workspaces
|
|
updateExecutionWorkspaceSchema,
|
|
workspaceRuntimeControlTargetSchema,
|
|
// Environments
|
|
createEnvironmentSchema,
|
|
updateEnvironmentSchema,
|
|
probeEnvironmentConfigSchema,
|
|
// Company skills
|
|
companySkillCreateSchema,
|
|
companySkillFileUpdateSchema,
|
|
companySkillImportSchema,
|
|
companySkillProjectScanRequestSchema,
|
|
// Issue tree
|
|
createIssueTreeHoldSchema,
|
|
previewIssueTreeControlSchema,
|
|
releaseIssueTreeHoldSchema,
|
|
// Issue interactions
|
|
createIssueThreadInteractionSchema,
|
|
createChildIssueSchema,
|
|
acceptIssueThreadInteractionSchema,
|
|
rejectIssueThreadInteractionSchema,
|
|
respondIssueThreadInteractionSchema,
|
|
// Auth / profile
|
|
updateCurrentUserProfileSchema,
|
|
// Company portability (legacy routes)
|
|
companyPortabilityExportSchema,
|
|
companyPortabilityPreviewSchema,
|
|
companyPortabilityImportSchema,
|
|
// Access / membership
|
|
acceptInviteSchema,
|
|
createCompanyInviteSchema,
|
|
createOpenClawInvitePromptSchema,
|
|
claimJoinRequestApiKeySchema,
|
|
createCliAuthChallengeSchema,
|
|
resolveCliAuthChallengeSchema,
|
|
updateCompanyMemberSchema,
|
|
updateCompanyMemberWithPermissionsSchema,
|
|
archiveCompanyMemberSchema,
|
|
updateMemberPermissionsSchema,
|
|
updateUserCompanyAccessSchema,
|
|
// Instance settings
|
|
patchInstanceGeneralSettingsSchema,
|
|
patchInstanceExperimentalSettingsSchema,
|
|
} from "@paperclipai/shared";
|
|
|
|
extendZodWithOpenApi(z);
|
|
|
|
const registry = new OpenAPIRegistry();
|
|
|
|
// ─── Common schemas ──────────────────────────────────────────────────────────
|
|
|
|
const ErrorSchema = registry.register(
|
|
"Error",
|
|
z.object({ error: z.string() }).openapi({ title: "Error" }),
|
|
);
|
|
|
|
const responses = {
|
|
ok: (schema: z.ZodTypeAny = z.record(z.unknown())) => ({
|
|
description: "Success",
|
|
content: { "application/json": { schema } },
|
|
}),
|
|
noContent: { description: "No content" },
|
|
badRequest: {
|
|
description: "Bad request",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
unauthorized: {
|
|
description: "Unauthorized",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
forbidden: {
|
|
description: "Forbidden",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
notFound: {
|
|
description: "Not found",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
serverError: {
|
|
description: "Internal server error",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
};
|
|
|
|
const jsonBody = (schema: z.ZodTypeAny) => ({
|
|
content: { "application/json": { schema } },
|
|
required: true as const,
|
|
});
|
|
|
|
const r = responses;
|
|
|
|
type OpenApiAuthLevel =
|
|
| "public"
|
|
| "authenticated"
|
|
| "board"
|
|
| "instance_admin";
|
|
|
|
const BOARD_SESSION_AUTH_SCHEME = "BoardSessionAuth";
|
|
const BOARD_API_KEY_AUTH_SCHEME = "BoardApiKeyAuth";
|
|
const AGENT_BEARER_AUTH_SCHEME = "AgentBearerAuth";
|
|
|
|
function securityRequirement(name: string): Record<string, string[]> {
|
|
return { [name]: [] };
|
|
}
|
|
|
|
const BOARD_SECURITY: Array<Record<string, string[]>> = [
|
|
securityRequirement(BOARD_SESSION_AUTH_SCHEME),
|
|
securityRequirement(BOARD_API_KEY_AUTH_SCHEME),
|
|
];
|
|
|
|
const AUTHENTICATED_SECURITY: Array<Record<string, string[]>> = [
|
|
...BOARD_SECURITY,
|
|
securityRequirement(AGENT_BEARER_AUTH_SCHEME),
|
|
];
|
|
|
|
const PUBLIC_OPERATIONS = new Set([
|
|
"GET /api/health",
|
|
"GET /api/openapi.json",
|
|
"GET /api/board-claim/{token}",
|
|
"POST /api/cli-auth/challenges",
|
|
"GET /api/cli-auth/challenges/{id}",
|
|
"POST /api/cli-auth/challenges/{id}/cancel",
|
|
"GET /api/invites/{token}",
|
|
"GET /api/invites/{token}/logo",
|
|
"GET /api/invites/{token}/onboarding",
|
|
"GET /api/invites/{token}/onboarding.txt",
|
|
"GET /api/invites/{token}/skills/index",
|
|
"GET /api/invites/{token}/skills/{skillName}",
|
|
"GET /api/invites/{token}/test-resolution",
|
|
"POST /api/invites/{token}/accept",
|
|
"POST /api/join-requests/{requestId}/claim-api-key",
|
|
]);
|
|
|
|
const BOARD_ONLY_PREFIXES = [
|
|
"/api/auth/",
|
|
"/api/admin/",
|
|
"/api/plugins",
|
|
"/api/instance/",
|
|
];
|
|
|
|
const BOARD_ONLY_OPERATIONS = new Set([
|
|
"GET /api/companies",
|
|
"POST /api/companies",
|
|
"GET /api/companies/stats",
|
|
"GET /api/companies/issues",
|
|
"POST /api/board-claim/{token}/claim",
|
|
"GET /api/cli-auth/me",
|
|
"POST /api/companies/{companyId}/invites",
|
|
"GET /api/companies/{companyId}/invites",
|
|
"POST /api/companies/{companyId}/openclaw/invite-prompt",
|
|
"GET /api/companies/{companyId}/join-requests",
|
|
"POST /api/companies/{companyId}/join-requests/{requestId}/approve",
|
|
"POST /api/companies/{companyId}/join-requests/{requestId}/reject",
|
|
"GET /api/companies/{companyId}/members",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}/role-and-grants",
|
|
"POST /api/companies/{companyId}/members/{memberId}/archive",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}/permissions",
|
|
"GET /api/companies/{companyId}/user-directory",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/accept",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/reject",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/respond",
|
|
]);
|
|
|
|
const INSTANCE_ADMIN_OPERATIONS = new Set([
|
|
"POST /api/companies",
|
|
"POST /api/plugins/install",
|
|
"POST /api/instance/database-backups",
|
|
"POST /api/admin/users/{userId}/promote-instance-admin",
|
|
"POST /api/admin/users/{userId}/demote-instance-admin",
|
|
"PUT /api/admin/users/{userId}/company-access",
|
|
]);
|
|
|
|
const CREATED_OPERATIONS = new Set([
|
|
"POST /api/adapters/install",
|
|
"POST /api/companies/{companyId}/agent-hires",
|
|
"POST /api/companies/{companyId}/agents",
|
|
"POST /api/agents/{id}/keys",
|
|
"POST /api/companies/{companyId}/approvals",
|
|
"POST /api/approvals/{id}/comments",
|
|
"POST /api/companies/{companyId}/assets/images",
|
|
"POST /api/companies/{companyId}/logo",
|
|
"POST /api/cli-auth/challenges",
|
|
"POST /api/companies",
|
|
"POST /api/companies/{companyId}/invites",
|
|
"POST /api/companies/{companyId}/openclaw/invite-prompt",
|
|
"POST /api/companies/{companyId}/cost-events",
|
|
"POST /api/companies/{companyId}/finance-events",
|
|
"POST /api/companies/{companyId}/environments",
|
|
"POST /api/companies/{companyId}/goals",
|
|
"POST /api/companies/{companyId}/labels",
|
|
"POST /api/issues/{id}/work-products",
|
|
"POST /api/issues/{id}/approvals",
|
|
"POST /api/companies/{companyId}/issues",
|
|
"POST /api/issues/{id}/children",
|
|
"POST /api/issues/{id}/interactions",
|
|
"POST /api/issues/{id}/comments",
|
|
"POST /api/companies/{companyId}/issues/{issueId}/attachments",
|
|
"POST /api/companies/{companyId}/projects",
|
|
"POST /api/projects/{id}/workspaces",
|
|
"POST /api/companies/{companyId}/routines",
|
|
"POST /api/routines/{id}/triggers",
|
|
"POST /api/companies/{companyId}/secrets",
|
|
"POST /api/companies/{companyId}/skills",
|
|
"POST /api/companies/{companyId}/skills/import",
|
|
"POST /api/join-requests/{requestId}/claim-api-key",
|
|
"POST /api/admin/users/{userId}/promote-instance-admin",
|
|
"POST /api/plugins/install",
|
|
"POST /api/instance/database-backups",
|
|
]);
|
|
|
|
const ACCEPTED_OPERATIONS = new Set([
|
|
"POST /api/invites/{token}/accept",
|
|
]);
|
|
|
|
const FORBIDDEN_RESPONSE = {
|
|
description: "Forbidden",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/Error" },
|
|
},
|
|
},
|
|
};
|
|
|
|
function operationKey(method: string, path: string) {
|
|
return `${method.toUpperCase()} ${path}`;
|
|
}
|
|
|
|
function isBoardOnlyOperation(method: string, path: string) {
|
|
const key = operationKey(method, path);
|
|
if (BOARD_ONLY_OPERATIONS.has(key)) return true;
|
|
return BOARD_ONLY_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
}
|
|
|
|
function resolveOperationAuthLevel(method: string, path: string): OpenApiAuthLevel {
|
|
const key = operationKey(method, path);
|
|
if (PUBLIC_OPERATIONS.has(key)) return "public";
|
|
if (INSTANCE_ADMIN_OPERATIONS.has(key)) return "instance_admin";
|
|
if (isBoardOnlyOperation(method, path)) return "board";
|
|
return "authenticated";
|
|
}
|
|
|
|
function applyOperationStatusOverride(
|
|
operation: Record<string, unknown>,
|
|
fromStatus: string,
|
|
toStatus: string,
|
|
) {
|
|
const responses = operation.responses as Record<string, unknown> | undefined;
|
|
if (!responses || !responses[fromStatus] || responses[toStatus]) return;
|
|
responses[toStatus] = responses[fromStatus];
|
|
delete responses[fromStatus];
|
|
}
|
|
|
|
function applyDocumentFixups(document: any): any {
|
|
document.components ??= {};
|
|
document.components.securitySchemes = {
|
|
[BOARD_SESSION_AUTH_SCHEME]: {
|
|
type: "apiKey",
|
|
in: "cookie",
|
|
name: "paperclip_session",
|
|
description:
|
|
"Board session cookie in authenticated mode. Paperclip uses Better Auth; cookie transport may vary by deployment.",
|
|
},
|
|
[BOARD_API_KEY_AUTH_SCHEME]: {
|
|
type: "http",
|
|
scheme: "bearer",
|
|
bearerFormat: "Board API Key",
|
|
description: "Board API key presented in the Authorization bearer header.",
|
|
},
|
|
[AGENT_BEARER_AUTH_SCHEME]: {
|
|
type: "http",
|
|
scheme: "bearer",
|
|
bearerFormat: "Agent API Key or Agent JWT",
|
|
description:
|
|
"Agent API key or Paperclip-issued local agent JWT presented in the Authorization bearer header.",
|
|
},
|
|
};
|
|
document.security = AUTHENTICATED_SECURITY;
|
|
|
|
for (const [path, pathItem] of Object.entries(document.paths ?? {})) {
|
|
for (const [method, operation] of Object.entries(pathItem as Record<string, any>)) {
|
|
const authLevel = resolveOperationAuthLevel(method, path);
|
|
if (authLevel === "public") {
|
|
operation.security = [];
|
|
} else if (authLevel === "authenticated") {
|
|
operation.security = AUTHENTICATED_SECURITY;
|
|
} else {
|
|
operation.security = BOARD_SECURITY;
|
|
}
|
|
|
|
operation["x-paperclip-authorization"] =
|
|
authLevel === "instance_admin"
|
|
? { actor: "board", instanceAdmin: true }
|
|
: authLevel === "board"
|
|
? { actor: "board" }
|
|
: authLevel === "authenticated"
|
|
? { actor: "board_or_agent" }
|
|
: { actor: "public" };
|
|
|
|
const key = operationKey(method, path);
|
|
if (authLevel !== "public") {
|
|
const responses = (operation.responses ??= {}) as Record<string, unknown>;
|
|
if (!responses["403"]) {
|
|
responses["403"] = FORBIDDEN_RESPONSE;
|
|
}
|
|
}
|
|
if (CREATED_OPERATIONS.has(key)) {
|
|
applyOperationStatusOverride(operation, "200", "201");
|
|
}
|
|
if (ACCEPTED_OPERATIONS.has(key)) {
|
|
applyOperationStatusOverride(operation, "200", "202");
|
|
}
|
|
}
|
|
}
|
|
|
|
return document;
|
|
}
|
|
|
|
// ─── Health ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/health",
|
|
tags: ["health"],
|
|
summary: "Health check",
|
|
responses: {
|
|
200: r.ok(z.object({
|
|
status: z.enum(["ok", "unhealthy"]),
|
|
version: z.string().optional(),
|
|
deploymentMode: z.string().optional(),
|
|
bootstrapStatus: z.enum(["ready", "bootstrap_pending"]).optional(),
|
|
bootstrapInviteActive: z.boolean().optional(),
|
|
})),
|
|
503: { description: "Service unavailable", content: { "application/json": { schema: ErrorSchema } } },
|
|
},
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/openapi.json",
|
|
tags: ["health"],
|
|
summary: "Get the generated OpenAPI document",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
// ─── Companies ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies",
|
|
tags: ["companies"],
|
|
summary: "List companies",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies",
|
|
tags: ["companies"],
|
|
summary: "Create a company",
|
|
request: { body: jsonBody(createCompanySchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/stats",
|
|
tags: ["companies"],
|
|
summary: "Company stats",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Get a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Update a company",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateCompanySchema.partial()),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/branding",
|
|
tags: ["companies"],
|
|
summary: "Update company branding",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateCompanyBrandingSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/archive",
|
|
tags: ["companies"],
|
|
summary: "Archive a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Delete a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/feedback-traces",
|
|
tags: ["companies"],
|
|
summary: "List company feedback traces",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/exports",
|
|
tags: ["companies"],
|
|
summary: "Export company data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/exports/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview company export",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/imports/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview company import",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/imports/apply",
|
|
tags: ["companies"],
|
|
summary: "Apply company import",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Agents ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/agents",
|
|
tags: ["agents"],
|
|
summary: "List agents in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/agents",
|
|
tags: ["agents"],
|
|
summary: "Create an agent",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createAgentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/agent-hires",
|
|
tags: ["agents"],
|
|
summary: "Hire an agent",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createAgentHireSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/agent-configurations",
|
|
tags: ["agents"],
|
|
summary: "List agent configurations for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org",
|
|
tags: ["agents"],
|
|
summary: "Get org chart data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me",
|
|
tags: ["agents"],
|
|
summary: "Get the current agent",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me/inbox-lite",
|
|
tags: ["agents"],
|
|
summary: "Get current agent inbox (lite)",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me/inbox/mine",
|
|
tags: ["agents"],
|
|
summary: "Get current agent assigned inbox items",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Get an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Update an agent",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentSchema.omit({ permissions: true })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Delete an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/permissions",
|
|
tags: ["agents"],
|
|
summary: "Update agent permissions",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/instructions-path",
|
|
tags: ["agents"],
|
|
summary: "Update agent instructions path",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentInstructionsPathSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/instructions-bundle",
|
|
tags: ["agents"],
|
|
summary: "Get agent instructions bundle",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/instructions-bundle",
|
|
tags: ["agents"],
|
|
summary: "Update agent instructions bundle",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentInstructionsBundleSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Get agent instructions file",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Upsert agent instructions file",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(upsertAgentInstructionsFileSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Delete agent instructions file",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/configuration",
|
|
tags: ["agents"],
|
|
summary: "Get agent configuration",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/config-revisions",
|
|
tags: ["agents"],
|
|
summary: "List agent config revisions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/config-revisions/{revisionId}",
|
|
tags: ["agents"],
|
|
summary: "Get an agent config revision",
|
|
request: { params: z.object({ id: z.string(), revisionId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/config-revisions/{revisionId}/rollback",
|
|
tags: ["agents"],
|
|
summary: "Roll back to a config revision",
|
|
request: { params: z.object({ id: z.string(), revisionId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/runtime-state",
|
|
tags: ["agents"],
|
|
summary: "Get agent runtime state",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/runtime-state/reset-session",
|
|
tags: ["agents"],
|
|
summary: "Reset agent session",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resetAgentSessionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/task-sessions",
|
|
tags: ["agents"],
|
|
summary: "List agent task sessions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/skills",
|
|
tags: ["agents"],
|
|
summary: "List agent skills",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/skills/sync",
|
|
tags: ["agents"],
|
|
summary: "Sync desired skills onto an agent configuration",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(agentSkillSyncSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/keys",
|
|
tags: ["agents"],
|
|
summary: "List agent API keys",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/keys",
|
|
tags: ["agents"],
|
|
summary: "Create an agent API key",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createAgentKeySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}/keys/{keyId}",
|
|
tags: ["agents"],
|
|
summary: "Delete an agent API key",
|
|
request: { params: z.object({ id: z.string(), keyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/wakeup",
|
|
tags: ["agents"],
|
|
summary: "Wake up an agent",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(wakeAgentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/pause",
|
|
tags: ["agents"],
|
|
summary: "Pause an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/resume",
|
|
tags: ["agents"],
|
|
summary: "Resume an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/terminate",
|
|
tags: ["agents"],
|
|
summary: "Terminate an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/scheduler-heartbeats",
|
|
tags: ["agents"],
|
|
summary: "List scheduler heartbeats",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Adapters ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/adapters/{type}/models",
|
|
tags: ["adapters"],
|
|
summary: "List models for an adapter type",
|
|
request: { params: z.object({ companyId: z.string(), type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/adapters/{type}/detect-model",
|
|
tags: ["adapters"],
|
|
summary: "Detect active model for an adapter",
|
|
request: { params: z.object({ companyId: z.string(), type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/adapters/{type}/test-environment",
|
|
tags: ["adapters"],
|
|
summary: "Validate adapter environment access for a company",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), type: z.string() }),
|
|
body: jsonBody(testAdapterEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issues ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/issues",
|
|
tags: ["issues"],
|
|
summary: "List issues in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/issues",
|
|
tags: ["issues"],
|
|
summary: "Create an issue",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createIssueSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Update an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateIssueSchema.partial()),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/heartbeat-context",
|
|
tags: ["issues"],
|
|
summary: "Get issue heartbeat context",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/work-products",
|
|
tags: ["issues"],
|
|
summary: "List issue work products",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/work-products",
|
|
tags: ["issues"],
|
|
summary: "Create an issue work product",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueWorkProductSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/work-products/{id}",
|
|
tags: ["issues"],
|
|
summary: "Update a work product",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateIssueWorkProductSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/work-products/{id}",
|
|
tags: ["issues"],
|
|
summary: "Delete a work product",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents",
|
|
tags: ["issues"],
|
|
summary: "List issue documents",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue document",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Upsert an issue document",
|
|
request: {
|
|
params: z.object({ id: z.string(), key: z.string() }),
|
|
body: jsonBody(upsertIssueDocumentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue document",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents/{key}/revisions",
|
|
tags: ["issues"],
|
|
summary: "List issue document revisions",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/documents/{key}/revisions/{revisionId}/restore",
|
|
tags: ["issues"],
|
|
summary: "Restore a document revision",
|
|
request: {
|
|
params: z.object({ id: z.string(), key: z.string(), revisionId: z.string() }),
|
|
body: jsonBody(restoreIssueDocumentRevisionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/comments",
|
|
tags: ["issues"],
|
|
summary: "List issue comments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/comments",
|
|
tags: ["issues"],
|
|
summary: "Add a comment to an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(addIssueCommentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/comments/{commentId}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue comment",
|
|
request: { params: z.object({ id: z.string(), commentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/approvals",
|
|
tags: ["issues"],
|
|
summary: "List issue approvals",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/approvals",
|
|
tags: ["issues"],
|
|
summary: "Link an approval to an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(linkIssueApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/approvals/{approvalId}",
|
|
tags: ["issues"],
|
|
summary: "Unlink an approval from an issue",
|
|
request: { params: z.object({ id: z.string(), approvalId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/checkout",
|
|
tags: ["issues"],
|
|
summary: "Check out an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(checkoutIssueSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/release",
|
|
tags: ["issues"],
|
|
summary: "Release an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/read",
|
|
tags: ["issues"],
|
|
summary: "Mark an issue as read",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/read",
|
|
tags: ["issues"],
|
|
summary: "Mark an issue as unread",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/inbox-archive",
|
|
tags: ["issues"],
|
|
summary: "Archive issue from inbox",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/inbox-archive",
|
|
tags: ["issues"],
|
|
summary: "Un-archive issue from inbox",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/feedback-votes",
|
|
tags: ["issues"],
|
|
summary: "List issue feedback votes",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/feedback-votes",
|
|
tags: ["issues"],
|
|
summary: "Upsert a feedback vote",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(upsertIssueFeedbackVoteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/feedback-traces",
|
|
tags: ["issues"],
|
|
summary: "List issue feedback traces",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/feedback-traces/{traceId}",
|
|
tags: ["issues"],
|
|
summary: "Get a feedback trace",
|
|
request: { params: z.object({ traceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/feedback-traces/{traceId}/bundle",
|
|
tags: ["issues"],
|
|
summary: "Get a feedback trace bundle",
|
|
request: { params: z.object({ traceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/attachments",
|
|
tags: ["issues"],
|
|
summary: "List issue attachments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/labels",
|
|
tags: ["issues"],
|
|
summary: "List labels in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/labels",
|
|
tags: ["issues"],
|
|
summary: "Create a label",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createIssueLabelSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/labels/{labelId}",
|
|
tags: ["issues"],
|
|
summary: "Delete a label",
|
|
request: { params: z.object({ labelId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Projects ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/projects",
|
|
tags: ["projects"],
|
|
summary: "List projects in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/projects",
|
|
tags: ["projects"],
|
|
summary: "Create a project",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createProjectSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Get a project",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Update a project",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateProjectSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Delete a project",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/projects/{id}/workspaces",
|
|
tags: ["projects"],
|
|
summary: "List project workspaces",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces",
|
|
tags: ["projects"],
|
|
summary: "Create a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createProjectWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}",
|
|
tags: ["projects"],
|
|
summary: "Update a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string() }),
|
|
body: jsonBody(updateProjectWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}",
|
|
tags: ["projects"],
|
|
summary: "Delete a project workspace",
|
|
request: { params: z.object({ id: z.string(), workspaceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Routines ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/routines",
|
|
tags: ["routines"],
|
|
summary: "List routines in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/routines",
|
|
tags: ["routines"],
|
|
summary: "Create a routine",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/routines/{id}",
|
|
tags: ["routines"],
|
|
summary: "Get a routine",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/routines/{id}",
|
|
tags: ["routines"],
|
|
summary: "Update a routine",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/routines/{id}/runs",
|
|
tags: ["routines"],
|
|
summary: "List runs for a routine",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routines/{id}/run",
|
|
tags: ["routines"],
|
|
summary: "Manually run a routine",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(runRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routines/{id}/triggers",
|
|
tags: ["routines"],
|
|
summary: "Create a routine trigger",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createRoutineTriggerSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/routine-triggers/{id}",
|
|
tags: ["routines"],
|
|
summary: "Update a routine trigger",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateRoutineTriggerSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/routine-triggers/{id}",
|
|
tags: ["routines"],
|
|
summary: "Delete a routine trigger",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routine-triggers/{id}/rotate-secret",
|
|
tags: ["routines"],
|
|
summary: "Rotate a routine trigger secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(rotateRoutineTriggerSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routine-triggers/public/{publicId}/fire",
|
|
tags: ["routines"],
|
|
summary: "Fire a public routine trigger",
|
|
request: { params: z.object({ publicId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Goals ───────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/goals",
|
|
tags: ["goals"],
|
|
summary: "List goals in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/goals",
|
|
tags: ["goals"],
|
|
summary: "Create a goal",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createGoalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Get a goal",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Update a goal",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateGoalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Delete a goal",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Secrets ─────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/secret-providers",
|
|
tags: ["secrets"],
|
|
summary: "List secret providers",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/secrets",
|
|
tags: ["secrets"],
|
|
summary: "List secrets in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secrets",
|
|
tags: ["secrets"],
|
|
summary: "Create a secret",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/secrets/{id}",
|
|
tags: ["secrets"],
|
|
summary: "Update a secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/secrets/{id}/rotate",
|
|
tags: ["secrets"],
|
|
summary: "Rotate a secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(rotateSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/secrets/{id}",
|
|
tags: ["secrets"],
|
|
summary: "Delete a secret",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Approvals ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/approvals",
|
|
tags: ["approvals"],
|
|
summary: "List approvals in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/approvals",
|
|
tags: ["approvals"],
|
|
summary: "Create an approval",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}",
|
|
tags: ["approvals"],
|
|
summary: "Get an approval",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}/issues",
|
|
tags: ["approvals"],
|
|
summary: "List issues linked to an approval",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/approve",
|
|
tags: ["approvals"],
|
|
summary: "Approve an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/reject",
|
|
tags: ["approvals"],
|
|
summary: "Reject an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/request-revision",
|
|
tags: ["approvals"],
|
|
summary: "Request revision on an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(requestApprovalRevisionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/resubmit",
|
|
tags: ["approvals"],
|
|
summary: "Resubmit an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resubmitApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}/comments",
|
|
tags: ["approvals"],
|
|
summary: "List approval comments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/comments",
|
|
tags: ["approvals"],
|
|
summary: "Add a comment to an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(addApprovalCommentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Costs ───────────────────────────────────────────────────────────────────
|
|
|
|
const costSummaryPaths = [
|
|
"summary", "by-agent", "by-agent-model", "by-provider",
|
|
"by-biller", "by-project", "finance-summary", "finance-by-biller",
|
|
"finance-by-kind", "finance-events", "window-spend", "quota-windows",
|
|
] as const;
|
|
|
|
for (const segment of costSummaryPaths) {
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: `/api/companies/{companyId}/costs/${segment}`,
|
|
tags: ["costs"],
|
|
summary: `Cost report: ${segment}`,
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
}
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/cost-events",
|
|
tags: ["costs"],
|
|
summary: "Record a cost event",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createCostEventSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/finance-events",
|
|
tags: ["costs"],
|
|
summary: "Record a finance event",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createFinanceEventSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/budgets/policies",
|
|
tags: ["costs"],
|
|
summary: "Create or update a budget policy",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(upsertBudgetPolicySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/budget-incidents/{incidentId}/resolve",
|
|
tags: ["costs"],
|
|
summary: "Resolve a budget incident",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), incidentId: z.string() }),
|
|
body: jsonBody(resolveBudgetIncidentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/budgets/overview",
|
|
tags: ["costs"],
|
|
summary: "Get budget overview",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/budgets",
|
|
tags: ["costs"],
|
|
summary: "Update company budget",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateBudgetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{agentId}/budgets",
|
|
tags: ["costs"],
|
|
summary: "Update agent budget",
|
|
request: {
|
|
params: z.object({ agentId: z.string() }),
|
|
body: jsonBody(updateBudgetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Activity ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/activity",
|
|
tags: ["activity"],
|
|
summary: "List company activity",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/activity",
|
|
tags: ["activity"],
|
|
summary: "Create an activity entry",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
actorType: z.enum(["agent", "user", "system", "plugin"]).optional(),
|
|
actorId: z.string().min(1),
|
|
action: z.string().min(1),
|
|
entityType: z.string().min(1),
|
|
entityId: z.string().min(1),
|
|
agentId: z.string().uuid().optional().nullable(),
|
|
details: z.record(z.unknown()).optional().nullable(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/activity",
|
|
tags: ["activity"],
|
|
summary: "List activity for an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/runs",
|
|
tags: ["activity"],
|
|
summary: "List runs for an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/issues",
|
|
tags: ["activity"],
|
|
summary: "List issues for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Dashboard ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/dashboard",
|
|
tags: ["dashboard"],
|
|
summary: "Get dashboard data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Sidebar ─────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/sidebar-badges",
|
|
tags: ["sidebar"],
|
|
summary: "Get sidebar badge counts",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Get current user sidebar preferences",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Update current user sidebar preferences",
|
|
request: { body: jsonBody(upsertSidebarOrderPreferenceSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Get sidebar preferences for company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/companies/{companyId}/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Update sidebar preferences for company",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(upsertSidebarOrderPreferenceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Inbox dismissals ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/inbox-dismissals",
|
|
tags: ["inbox"],
|
|
summary: "List inbox dismissals",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/inbox-dismissals",
|
|
tags: ["inbox"],
|
|
summary: "Create an inbox dismissal",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
itemKey: z.string().trim().min(1).regex(/^(approval|join|run):.+$/, "Unsupported inbox item key"),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Instance settings ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/settings/general",
|
|
tags: ["instance"],
|
|
summary: "Get general instance settings",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/instance/settings/general",
|
|
tags: ["instance"],
|
|
summary: "Update general instance settings",
|
|
request: { body: jsonBody(patchInstanceGeneralSettingsSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/settings/experimental",
|
|
tags: ["instance"],
|
|
summary: "Get experimental instance settings",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/instance/settings/experimental",
|
|
tags: ["instance"],
|
|
summary: "Update experimental instance settings",
|
|
request: { body: jsonBody(patchInstanceExperimentalSettingsSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Access / invites / members ───────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/invites",
|
|
tags: ["access"],
|
|
summary: "List company invites",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/invites",
|
|
tags: ["access"],
|
|
summary: "Create a company invite",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createCompanyInviteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/join-requests",
|
|
tags: ["access"],
|
|
summary: "List company join requests",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/join-requests/{requestId}/approve",
|
|
tags: ["access"],
|
|
summary: "Approve a company join request",
|
|
request: { params: z.object({ companyId: z.string(), requestId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/join-requests/{requestId}/reject",
|
|
tags: ["access"],
|
|
summary: "Reject a company join request",
|
|
request: { params: z.object({ companyId: z.string(), requestId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/invites/{inviteId}/revoke",
|
|
tags: ["access"],
|
|
summary: "Revoke an invite",
|
|
request: { params: z.object({ inviteId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}",
|
|
tags: ["access"],
|
|
summary: "Get an invite by token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/invites/{token}/accept",
|
|
tags: ["access"],
|
|
summary: "Accept an invite and create or replay a join request",
|
|
request: {
|
|
params: z.object({ token: z.string() }),
|
|
body: jsonBody(acceptInviteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/members",
|
|
tags: ["access"],
|
|
summary: "List company members",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}",
|
|
tags: ["access"],
|
|
summary: "Update a company member status or role",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateCompanyMemberSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}/role-and-grants",
|
|
tags: ["access"],
|
|
summary: "Update a company member role and explicit grants",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateCompanyMemberWithPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/members/{memberId}/archive",
|
|
tags: ["access"],
|
|
summary: "Archive a company member",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(archiveCompanyMemberSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}/permissions",
|
|
tags: ["access"],
|
|
summary: "Update explicit company member permissions",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateMemberPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/user-directory",
|
|
tags: ["access"],
|
|
summary: "Get company user directory",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/cli-auth/me",
|
|
tags: ["access"],
|
|
summary: "Get current CLI auth session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/openclaw/invite-prompt",
|
|
tags: ["access"],
|
|
summary: "Create an OpenClaw invite prompt bundle",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createOpenClawInvitePromptSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges",
|
|
tags: ["access"],
|
|
summary: "Create a CLI auth challenge",
|
|
request: { body: jsonBody(createCliAuthChallengeSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges/{id}/approve",
|
|
tags: ["access"],
|
|
summary: "Approve a CLI auth challenge",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveCliAuthChallengeSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges/{id}/cancel",
|
|
tags: ["access"],
|
|
summary: "Cancel a CLI auth challenge",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveCliAuthChallengeSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/revoke-current",
|
|
tags: ["access"],
|
|
summary: "Revoke current CLI auth session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/available",
|
|
tags: ["access"],
|
|
summary: "List available skills",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/index",
|
|
tags: ["access"],
|
|
summary: "Get skills index",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/{skillName}",
|
|
tags: ["access"],
|
|
summary: "Get a skill by name",
|
|
request: { params: z.object({ skillName: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/join-requests/{requestId}/claim-api-key",
|
|
tags: ["access"],
|
|
summary: "Claim the initial API key for an approved agent join request",
|
|
request: {
|
|
params: z.object({ requestId: z.string() }),
|
|
body: jsonBody(claimJoinRequestApiKeySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/admin/users",
|
|
tags: ["admin"],
|
|
summary: "List all users (admin)",
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
// ─── Auth / profile ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/auth/get-session",
|
|
tags: ["auth"],
|
|
summary: "Get current session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/auth/profile",
|
|
tags: ["auth"],
|
|
summary: "Get current user profile",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/auth/profile",
|
|
tags: ["auth"],
|
|
summary: "Update current user profile",
|
|
request: { body: jsonBody(updateCurrentUserProfileSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/users/{userSlug}/profile",
|
|
tags: ["auth"],
|
|
summary: "Get a user profile within a company",
|
|
request: { params: z.object({ companyId: z.string(), userSlug: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Heartbeat runs ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/heartbeat-runs",
|
|
tags: ["runs"],
|
|
summary: "List heartbeat runs for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/live-runs",
|
|
tags: ["runs"],
|
|
summary: "List live runs for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{issueId}/live-runs",
|
|
tags: ["runs"],
|
|
summary: "List live runs for an issue",
|
|
request: { params: z.object({ issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{issueId}/active-run",
|
|
tags: ["runs"],
|
|
summary: "Get active run for an issue",
|
|
request: { params: z.object({ issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}",
|
|
tags: ["runs"],
|
|
summary: "Get a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/heartbeat-runs/{runId}/cancel",
|
|
tags: ["runs"],
|
|
summary: "Cancel a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/heartbeat-runs/{runId}/watchdog-decisions",
|
|
tags: ["runs"],
|
|
summary: "Submit watchdog decisions for a run",
|
|
request: {
|
|
params: z.object({ runId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
decision: z.enum(["snooze", "continue", "dismissed_false_positive"]),
|
|
evaluationIssueId: z.string().optional().nullable(),
|
|
reason: z.string().optional().nullable(),
|
|
snoozedUntil: z.string().datetime().optional().nullable(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/events",
|
|
tags: ["runs"],
|
|
summary: "Get events for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/log",
|
|
tags: ["runs"],
|
|
summary: "Get log for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/workspace-operations",
|
|
tags: ["runs"],
|
|
summary: "List workspace operations for a run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/workspace-operations/{operationId}/log",
|
|
tags: ["runs"],
|
|
summary: "Get log for a workspace operation",
|
|
request: { params: z.object({ operationId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Agent runs & heartbeat ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/approve",
|
|
tags: ["agents"],
|
|
summary: "Approve a pending agent action",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/heartbeat/invoke",
|
|
tags: ["agents"],
|
|
summary: "Invoke agent heartbeat",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/claude-login",
|
|
tags: ["agents"],
|
|
summary: "Trigger Claude login for agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issue interactions & tree ───────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/interactions",
|
|
tags: ["issues"],
|
|
summary: "List issue thread interactions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions",
|
|
tags: ["issues"],
|
|
summary: "Create an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/accept",
|
|
tags: ["issues"],
|
|
summary: "Accept an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(acceptIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/reject",
|
|
tags: ["issues"],
|
|
summary: "Reject an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(rejectIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/respond",
|
|
tags: ["issues"],
|
|
summary: "Answer questions on an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(respondIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/children",
|
|
tags: ["issues"],
|
|
summary: "Create child issues",
|
|
request: { params: z.object({ id: z.string() }), body: jsonBody(createChildIssueSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/admin/force-release",
|
|
tags: ["issues"],
|
|
summary: "Force-release an issue (admin)",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-control/state",
|
|
tags: ["issues"],
|
|
summary: "Get issue tree control state",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-control/preview",
|
|
tags: ["issues"],
|
|
summary: "Preview issue tree control changes",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(previewIssueTreeControlSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-holds",
|
|
tags: ["issues"],
|
|
summary: "List issue tree holds",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-holds",
|
|
tags: ["issues"],
|
|
summary: "Create an issue tree hold",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueTreeHoldSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-holds/{holdId}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue tree hold",
|
|
request: { params: z.object({ id: z.string(), holdId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-holds/{holdId}/release",
|
|
tags: ["issues"],
|
|
summary: "Release an issue tree hold",
|
|
request: {
|
|
params: z.object({ id: z.string(), holdId: z.string() }),
|
|
body: jsonBody(releaseIssueTreeHoldSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Attachments ──────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/issues/{issueId}/attachments",
|
|
tags: ["assets"],
|
|
summary: "Upload an attachment to an issue",
|
|
request: { params: z.object({ companyId: z.string(), issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/attachments/{attachmentId}/content",
|
|
tags: ["assets"],
|
|
summary: "Download attachment content",
|
|
request: { params: z.object({ attachmentId: z.string() }) },
|
|
responses: { 200: { description: "File content" }, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/attachments/{attachmentId}",
|
|
tags: ["assets"],
|
|
summary: "Delete an attachment",
|
|
request: { params: z.object({ attachmentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Assets ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/assets/images",
|
|
tags: ["assets"],
|
|
summary: "Upload an image asset",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/logo",
|
|
tags: ["assets"],
|
|
summary: "Upload company logo",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/assets/{assetId}/content",
|
|
tags: ["assets"],
|
|
summary: "Download asset content",
|
|
request: { params: z.object({ assetId: z.string() }) },
|
|
responses: { 200: { description: "File content" }, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Company skills ───────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills",
|
|
tags: ["skills"],
|
|
summary: "List skills for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}",
|
|
tags: ["skills"],
|
|
summary: "Get a company skill",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/update-status",
|
|
tags: ["skills"],
|
|
summary: "Get skill update status",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/files",
|
|
tags: ["skills"],
|
|
summary: "List skill files",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills",
|
|
tags: ["skills"],
|
|
summary: "Create a company skill",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillCreateSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/files",
|
|
tags: ["skills"],
|
|
summary: "Update a skill file",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), skillId: z.string() }),
|
|
body: jsonBody(companySkillFileUpdateSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/import",
|
|
tags: ["skills"],
|
|
summary: "Import a skill",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillImportSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/scan-projects",
|
|
tags: ["skills"],
|
|
summary: "Scan project for skills",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillProjectScanRequestSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/install-update",
|
|
tags: ["skills"],
|
|
summary: "Install a skill update",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/companies/{companyId}/skills/{skillId}",
|
|
tags: ["skills"],
|
|
summary: "Delete a company skill",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Execution workspaces ─────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/execution-workspaces",
|
|
tags: ["execution-workspaces"],
|
|
summary: "List execution workspaces for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Get an execution workspace",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}/close-readiness",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Check close-readiness of a workspace",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}/workspace-operations",
|
|
tags: ["execution-workspaces"],
|
|
summary: "List workspace operations",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/execution-workspaces/{id}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Update an execution workspace",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateExecutionWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/execution-workspaces/{id}/runtime-services/{action}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Control a runtime service in a workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/execution-workspaces/{id}/runtime-commands/{action}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Run a runtime command in a workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Environments ─────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/environments",
|
|
tags: ["environments"],
|
|
summary: "List environments for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/environments/capabilities",
|
|
tags: ["environments"],
|
|
summary: "Get environment capabilities",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/environments",
|
|
tags: ["environments"],
|
|
summary: "Create an environment",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Get an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environments/{id}/leases",
|
|
tags: ["environments"],
|
|
summary: "List leases for an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environment-leases/{leaseId}",
|
|
tags: ["environments"],
|
|
summary: "Get an environment lease",
|
|
request: { params: z.object({ leaseId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Update an environment",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Delete an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/environments/{id}/probe",
|
|
tags: ["environments"],
|
|
summary: "Probe an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/environments/probe-config",
|
|
tags: ["environments"],
|
|
summary: "Probe environment config",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(probeEnvironmentConfigSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Adapters (full) ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters",
|
|
tags: ["adapters"],
|
|
summary: "List all adapters",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/install",
|
|
tags: ["adapters"],
|
|
summary: "Install an adapter",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
packageName: z.string(),
|
|
isLocalPath: z.boolean().optional(),
|
|
version: z.string().optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/adapters/{type}",
|
|
tags: ["adapters"],
|
|
summary: "Enable or disable an adapter",
|
|
request: {
|
|
params: z.object({ type: z.string() }),
|
|
body: jsonBody(z.object({ disabled: z.boolean() })),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/adapters/{type}/override",
|
|
tags: ["adapters"],
|
|
summary: "Pause or resume an adapter's override of a builtin",
|
|
request: {
|
|
params: z.object({ type: z.string() }),
|
|
body: jsonBody(z.object({ paused: z.boolean() })),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/adapters/{type}",
|
|
tags: ["adapters"],
|
|
summary: "Delete an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/{type}/reload",
|
|
tags: ["adapters"],
|
|
summary: "Reload an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/{type}/reinstall",
|
|
tags: ["adapters"],
|
|
summary: "Reinstall an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters/{type}/config-schema",
|
|
tags: ["adapters"],
|
|
summary: "Get adapter config schema",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Plugins ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins",
|
|
tags: ["plugins"],
|
|
summary: "List installed plugins",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/examples",
|
|
tags: ["plugins"],
|
|
summary: "List example plugins",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/ui-contributions",
|
|
tags: ["plugins"],
|
|
summary: "List plugin UI contributions",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/tools",
|
|
tags: ["plugins"],
|
|
summary: "List plugin tools",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/tools/execute",
|
|
tags: ["plugins"],
|
|
summary: "Execute a plugin tool",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
tool: z.string(),
|
|
parameters: z.record(z.unknown()).optional(),
|
|
runContext: z.object({
|
|
agentId: z.string(),
|
|
runId: z.string(),
|
|
companyId: z.string(),
|
|
projectId: z.string(),
|
|
}),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/install",
|
|
tags: ["plugins"],
|
|
summary: "Install a plugin",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
packageName: z.string(),
|
|
version: z.string().optional(),
|
|
isLocalPath: z.boolean().optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}",
|
|
tags: ["plugins"],
|
|
summary: "Get a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/plugins/{pluginId}",
|
|
tags: ["plugins"],
|
|
summary: "Delete a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/enable",
|
|
tags: ["plugins"],
|
|
summary: "Enable a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/disable",
|
|
tags: ["plugins"],
|
|
summary: "Disable a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/health",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin health",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/logs",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin logs",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/upgrade",
|
|
tags: ["plugins"],
|
|
summary: "Upgrade a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/config",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin config",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/config",
|
|
tags: ["plugins"],
|
|
summary: "Set plugin config",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({ configJson: z.record(z.unknown()) })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/config/test",
|
|
tags: ["plugins"],
|
|
summary: "Test plugin config",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({ configJson: z.record(z.unknown()) })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/jobs",
|
|
tags: ["plugins"],
|
|
summary: "List plugin jobs",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/jobs/{jobId}/runs",
|
|
tags: ["plugins"],
|
|
summary: "List runs for a plugin job",
|
|
request: { params: z.object({ pluginId: z.string(), jobId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/jobs/{jobId}/trigger",
|
|
tags: ["plugins"],
|
|
summary: "Trigger a plugin job",
|
|
request: { params: z.object({ pluginId: z.string(), jobId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/webhooks/{endpointKey}",
|
|
tags: ["plugins"],
|
|
summary: "Deliver an external webhook payload to a plugin",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), endpointKey: z.string() }),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/dashboard",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin dashboard data",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/bridge/data",
|
|
tags: ["plugins"],
|
|
summary: "Send data via plugin bridge",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
key: z.string(),
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/bridge/action",
|
|
tags: ["plugins"],
|
|
summary: "Send action via plugin bridge",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
key: z.string(),
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/data/{key}",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin data by key (URL-keyed bridge)",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), key: z.string() }),
|
|
body: jsonBody(z.object({
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/actions/{key}",
|
|
tags: ["plugins"],
|
|
summary: "Invoke a plugin action (URL-keyed bridge)",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), key: z.string() }),
|
|
body: jsonBody(z.object({
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Instance database backups ────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/instance/database-backups",
|
|
tags: ["instance"],
|
|
summary: "Trigger a database backup",
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
// ─── LLM text endpoints ───────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-configuration.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent configuration as plain text (for LLM context)",
|
|
responses: { 200: { description: "Plain text agent configuration" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-configuration/{adapterType}.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent configuration for a specific adapter type",
|
|
request: { params: z.object({ adapterType: z.string() }) },
|
|
responses: { 200: { description: "Plain text agent configuration" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-icons.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent icon names as plain text",
|
|
responses: { 200: { description: "Plain text icon list" }, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issues (legacy / misc) ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues",
|
|
tags: ["issues"],
|
|
summary: "Legacy — returns error directing to /api/companies/{companyId}/issues",
|
|
responses: { 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/comments/{commentId}",
|
|
tags: ["issues"],
|
|
summary: "Get a single issue comment",
|
|
request: { params: z.object({ id: z.string(), commentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Org chart images ─────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org.svg",
|
|
tags: ["companies"],
|
|
summary: "Get org chart as SVG",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: { description: "SVG image" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org.png",
|
|
tags: ["companies"],
|
|
summary: "Get org chart as PNG",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: { description: "PNG image" }, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Company portability (legacy routes) ─────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/issues",
|
|
tags: ["companies"],
|
|
summary: "Legacy — returns error directing to correct issues path",
|
|
responses: { 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/export",
|
|
tags: ["companies"],
|
|
summary: "Export a company (legacy singular form)",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companyPortabilityExportSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/import/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview a company import (legacy route)",
|
|
request: { body: jsonBody(companyPortabilityPreviewSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/import",
|
|
tags: ["companies"],
|
|
summary: "Apply a company import (legacy route)",
|
|
request: { body: jsonBody(companyPortabilityImportSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Board claim & CLI auth ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/board-claim/{token}",
|
|
tags: ["access"],
|
|
summary: "Get board claim details by token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/board-claim/{token}/claim",
|
|
tags: ["access"],
|
|
summary: "Claim a board token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/cli-auth/challenges/{id}",
|
|
tags: ["access"],
|
|
summary: "Get a CLI auth challenge",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
// ─── Invite onboarding ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/logo",
|
|
tags: ["access"],
|
|
summary: "Get company logo for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: { description: "Image file" }, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/onboarding",
|
|
tags: ["access"],
|
|
summary: "Get onboarding data for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/onboarding.txt",
|
|
tags: ["access"],
|
|
summary: "Get onboarding instructions as plain text",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: { description: "Plain text onboarding instructions" }, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/skills/index",
|
|
tags: ["access"],
|
|
summary: "Get skills index for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/skills/{skillName}",
|
|
tags: ["access"],
|
|
summary: "Get a skill by name for an invite",
|
|
request: { params: z.object({ token: z.string(), skillName: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/test-resolution",
|
|
tags: ["access"],
|
|
summary: "Test invite token resolution",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
// ─── Admin ────────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/admin/users/{userId}/company-access",
|
|
tags: ["admin"],
|
|
summary: "Get company access for a user (admin)",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/admin/users/{userId}/company-access",
|
|
tags: ["admin"],
|
|
summary: "Set company access for a user (admin)",
|
|
request: {
|
|
params: z.object({ userId: z.string() }),
|
|
body: jsonBody(updateUserCompanyAccessSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/admin/users/{userId}/promote-instance-admin",
|
|
tags: ["admin"],
|
|
summary: "Promote a user to instance admin",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/admin/users/{userId}/demote-instance-admin",
|
|
tags: ["admin"],
|
|
summary: "Demote a user from instance admin",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Project workspace runtime ────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}/runtime-services/{action}",
|
|
tags: ["projects"],
|
|
summary: "Control a runtime service in a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}/runtime-commands/{action}",
|
|
tags: ["projects"],
|
|
summary: "Run a runtime command in a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Plugin bridge stream ─────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/bridge/stream/{channel}",
|
|
tags: ["plugins"],
|
|
summary: "Subscribe to a plugin bridge SSE stream",
|
|
request: { params: z.object({ pluginId: z.string(), channel: z.string() }) },
|
|
responses: {
|
|
200: { description: "Server-sent event stream (text/event-stream)" },
|
|
401: r.unauthorized,
|
|
},
|
|
});
|
|
|
|
// ─── Plugin UI static ─────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/_plugins/{pluginId}/ui/{filePath}",
|
|
tags: ["plugins"],
|
|
summary: "Serve plugin UI static file",
|
|
request: { params: z.object({ pluginId: z.string(), filePath: z.string() }) },
|
|
responses: { 200: { description: "Static file content" }, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Adapter UI parser ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters/{type}/ui-parser.js",
|
|
tags: ["adapters"],
|
|
summary: "Get adapter UI parser script",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: { description: "JavaScript file" }, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Spec builder ─────────────────────────────────────────────────────────────
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export function buildOpenApiSpec(): any {
|
|
const generator = new OpenApiGeneratorV3(registry.definitions);
|
|
return applyDocumentFixups(generator.generateDocument({
|
|
openapi: "3.0.0",
|
|
info: {
|
|
title: "Paperclip API",
|
|
version: "1.0.0",
|
|
description: "REST API for the Paperclip AI agent management platform",
|
|
},
|
|
servers: [{ url: "/" }],
|
|
}));
|
|
}
|