Files
AgentHub/server/openapi-extra/artifacts.js
T
nntrivi2001 49f87c4da3 chore: v2.8.12 — clone queue and progress, session defaults, chat agents, drops as @ mentions
Whole tree from parallel sessions, plus the version bump:
- Clone: a queue with live progress (Add workspace and extra folders),
  clones run under nice/ionice so the machine stays responsive.
- Settings → AI tools → Session defaults: a new session's default
  permission mode / bypass per account.
- Chat lists the background subagents that have not reported back yet.
- A file pasted or dropped into a claude pane goes in as an @ mention.
- Changes panel: say why amend/undo is refused on a pushed commit.
- openapi.yaml regenerated (Artifacts, Host, resumeById).
2026-10-19 17:19:41 +07:00

360 lines
13 KiB
JavaScript

/**
* @file OpenAPI fragment for the Artifacts hub (`/api/artifacts/*`, server/routes/artifacts.js)
* and the Administration page's Server tab (`GET /api/host`, server/routes/host.js).
* Hand-written from the routes; docs/API.md § Artifacts hub is the long form.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const tags = [
{
name: "Artifacts",
description:
"What agents produced, shared as a SNAPSHOT: a claude.ai page link, a pinned or uploaded file, a skill or a plugin. A viewer reads only the snapshot copy (sandbox CSP, nosniff); an install writes into the caller's own account only. Every write is same-origin.",
},
{
name: "Host",
description:
"Administration → Server: the machine's hardware, load, disks and each user's share of CPU and memory. Read-only, staff only in server mode.",
},
];
const errorCodes =
"400 EBADURL/EBADPATH/EBADNAME/ENOSKILLMD/ENODESCRIPTION/EBADFIELD/EBADUPLOAD, 403 EFORBIDDEN/ESECRET/EFOREIGN/EINSTALLSOFF/EADMIN, 404 ENOTFOUND/ENOLANE/ENOFILE, 409 EEXISTS/ENOTSHARED/ENOACCOUNT/EUPGRADE, 413 ETOOBIG, 502 EPLUGIN/EMARKETFETCH/ENEEDLOGIN.";
const schemas = {
Artifact: {
type: "object",
properties: {
id: { type: "string" },
kind: { type: "string", enum: ["page", "file", "skill", "plugin"] },
title: { type: "string" },
description: { type: "string", nullable: true },
ownerId: { type: "integer", nullable: true },
owner: { type: "string", nullable: true, description: "Display name or username." },
mine: { type: "boolean" },
canChange: { type: "boolean" },
laneId: { type: "integer", nullable: true },
sessionId: { type: "string", nullable: true },
source: { type: "string", nullable: true, description: "Plugin: `name@marketplace`." },
market: { type: "string", nullable: true, description: "Plugin: owner/repo, git URL or marketplace.json URL." },
url: { type: "string", nullable: true, description: "Page: a https://claude.ai/… link." },
files: { type: "array", items: { type: "object" }, description: "The files the snapshot recorded." },
size: { type: "integer", nullable: true },
shared: { type: "boolean" },
sharedAt: { type: "string", nullable: true },
installs: { type: "integer" },
createdAt: { type: "string" },
updatedAt: { type: "string" },
},
},
};
const json = (properties, required = []) => ({
required: true,
content: { "application/json": { schema: { type: "object", required, properties } } },
});
const multipart = (properties, required = []) => ({
required: true,
content: { "multipart/form-data": { schema: { type: "object", required, properties } } },
});
const item = {
type: "object",
properties: { item: { $ref: "#/components/schemas/Artifact" } },
};
const items = {
type: "object",
properties: { items: { type: "array", items: { $ref: "#/components/schemas/Artifact" } } },
};
const idParam = { name: "id", in: "path", required: true, schema: { type: "string" } };
const laneQuery = { name: "laneId", in: "query", required: false, schema: { type: "integer" } };
const errs = { description: `Error \`{error:{code,message}}\`: ${errorCodes}` };
const created = (schema, description = "Created.") => ({
201: { description, content: { "application/json": { schema } } },
400: errs,
});
const paths = {
"/api/artifacts": {
get: {
tags: ["Artifacts"],
summary: "List artifacts",
operationId: "listArtifacts",
description:
"A developer sees their own rows plus shared ones; staff see every row in `hub` scope.",
parameters: [
{ name: "scope", in: "query", schema: { type: "string", enum: ["mine", "hub"] } },
{ name: "kind", in: "query", schema: { type: "string", enum: ["page", "file", "skill", "plugin"] } },
{ name: "q", in: "query", schema: { type: "string" } },
laneQuery,
{ name: "sessionId", in: "query", schema: { type: "string" } },
],
responses: { 200: { description: "`{items}`.", content: { "application/json": { schema: items } } } },
},
},
"/api/artifacts/settings": {
get: {
tags: ["Artifacts"],
summary: "Hub switches and what the caller may do",
operationId: "getArtifactSettings",
responses: {
200: {
description: "`{serverMode, canShare, installsEnabled, canManage}`.",
content: {
"application/json": {
schema: {
type: "object",
properties: {
serverMode: { type: "boolean" },
canShare: { type: "boolean" },
installsEnabled: { type: "boolean" },
canManage: { type: "boolean" },
},
},
},
},
},
},
},
put: {
tags: ["Artifacts"],
summary: "Turn installs on or off (admin; any caller in local mode)",
operationId: "setArtifactSettings",
description: "Audited in server mode (Settings → AI tools → Artifacts Hub).",
requestBody: json({ installsEnabled: { type: "boolean" } }, ["installsEnabled"]),
responses: {
200: { description: "`{installsEnabled}`." },
400: { description: "EBADFIELD" },
403: { description: "EADMIN / EBADORIGIN" },
},
},
},
"/api/artifacts/candidates": {
get: {
tags: ["Artifacts"],
summary: "Documents a workspace's sessions wrote (to pin)",
operationId: "listArtifactCandidates",
parameters: [laneQuery],
responses: {
200: { description: "`{items: [{path, sessionId, at, exists, pinned}]}`." },
404: errs,
},
},
},
"/api/artifacts/skills": {
get: {
tags: ["Artifacts"],
summary: "Skills the caller can share (user + workspace)",
operationId: "listArtifactSkills",
parameters: [laneQuery],
responses: { 200: { description: '`{items: [{name, from: "user"|"workspace"}]}`.' } },
},
},
"/api/artifacts/page": {
post: {
tags: ["Artifacts"],
summary: "Add a claude.ai page link",
operationId: "addArtifactPage",
description: "Only `https://claude.ai/…` is accepted; one row per owner and link.",
requestBody: json(
{ url: { type: "string" }, title: { type: "string" }, description: { type: "string" } },
["url"]
),
responses: created(item),
},
},
"/api/artifacts/file": {
post: {
tags: ["Artifacts"],
summary: "Pin a workspace file (snapshotted)",
operationId: "pinArtifactFile",
description:
"Read under the download route's guards (`lanePath`, `resolveInLane`, `ESECRET`, ≤ 25 MB) and copied once.",
requestBody: json(
{
laneId: { type: "integer" },
path: { type: "string" },
title: { type: "string" },
sessionId: { type: "string" },
},
["laneId", "path"]
),
responses: created(item),
},
},
"/api/artifacts/file/upload": {
post: {
tags: ["Artifacts"],
summary: "Keep one uploaded file (≤ 25 MB)",
operationId: "uploadArtifactFile",
requestBody: multipart({
files: { type: "string", format: "binary" },
title: { type: "string" },
description: { type: "string" },
}, ["files"]),
responses: created(item),
},
},
"/api/artifacts/skill": {
post: {
tags: ["Artifacts"],
summary: "Share an existing skill",
operationId: "addArtifactSkill",
requestBody: json(
{
name: { type: "string" },
laneId: { type: "integer" },
title: { type: "string" },
description: { type: "string" },
},
["name"]
),
responses: created(item),
},
},
"/api/artifacts/skill/upload": {
post: {
tags: ["Artifacts"],
summary: "Skills from files: a file, a folder, a .zip/.skill or several skills",
operationId: "uploadArtifactSkills",
description:
"`paths` is a JSON array of each file's relative path. Zip sizes are read before inflating; nothing is stored if any skill fails.",
requestBody: multipart({
files: { type: "array", items: { type: "string", format: "binary" } },
paths: { type: "string" },
name: { type: "string" },
title: { type: "string" },
description: { type: "string" },
}, ["files"]),
responses: created(items),
},
},
"/api/artifacts/skill/new": {
post: {
tags: ["Artifacts"],
summary: "Write a skill in the dialog",
operationId: "writeArtifactSkill",
requestBody: json(
{
name: { type: "string" },
description: { type: "string" },
content: { type: "string" },
title: { type: "string" },
},
["name", "description", "content"]
),
responses: created({ type: "object", properties: { item: item.properties.item, items: items.properties.items } }),
},
},
"/api/artifacts/plugin/inspect": {
post: {
tags: ["Artifacts"],
summary: "Read a marketplace's plugin list",
operationId: "inspectArtifactMarket",
description: "Clones a user-named https link only to read `marketplace.json`; no credential is ever sent.",
requestBody: json({ market: { type: "string" } }, ["market"]),
responses: {
200: { description: "`{market, name, description, plugins: [{name, key, description, version, category}]}`." },
502: errs,
},
},
},
"/api/artifacts/plugin": {
post: {
tags: ["Artifacts"],
summary: "Share a plugin from a marketplace",
operationId: "addArtifactPlugin",
requestBody: json(
{
key: { type: "string", description: "`name@marketplace`." },
market: { type: "string" },
title: { type: "string" },
description: { type: "string" },
},
["key", "market"]
),
responses: created(item),
},
},
"/api/artifacts/{id}": {
get: {
tags: ["Artifacts"],
summary: "One artifact",
operationId: "getArtifact",
parameters: [idParam],
responses: { 200: { description: "`{item}`.", content: { "application/json": { schema: item } } }, 404: errs },
},
patch: {
tags: ["Artifacts"],
summary: "Rename, describe, share or unshare (owner or admin)",
operationId: "updateArtifact",
parameters: [idParam],
requestBody: json({
title: { type: "string" },
description: { type: "string" },
shared: { type: "boolean" },
source: { type: "string", description: "Plugin only." },
market: { type: "string", description: "Plugin only." },
}),
responses: { 200: { description: "`{item}`.", content: { "application/json": { schema: item } } }, 403: errs, 404: errs },
},
delete: {
tags: ["Artifacts"],
summary: "Delete an artifact and its snapshot (owner or admin)",
operationId: "deleteArtifact",
parameters: [idParam],
responses: { 200: { description: "`{ok: true}`." }, 403: errs, 404: errs },
},
},
"/api/artifacts/{id}/raw": {
get: {
tags: ["Artifacts"],
summary: "One recorded file of a snapshot",
operationId: "getArtifactRaw",
description:
"Always served under a `sandbox` CSP with `nosniff`; an unlisted type, or `download=1`, is an attachment. Only a file the snapshot recorded can be named.",
parameters: [
idParam,
{ name: "path", in: "query", required: true, schema: { type: "string" } },
{ name: "download", in: "query", schema: { type: "string", enum: ["1"] } },
],
responses: { 200: { description: "The file bytes." }, 400: errs, 404: errs },
},
},
"/api/artifacts/{id}/install": {
post: {
tags: ["Artifacts"],
summary: "Install a shared skill or plugin into the caller's own account",
operationId: "installArtifact",
description:
"A skill goes to the caller's `~/.claude/skills/` (never over an existing one without `replace`; the old one is moved aside). A plugin runs `claude plugin install --scope user` as the caller.",
parameters: [idParam],
requestBody: json({ replace: { type: "boolean" } }),
responses: {
200: { description: "`{installed, replaced?}`." },
403: errs,
409: errs,
502: errs,
},
},
},
"/api/host": {
get: {
tags: ["Host"],
summary: "The machine and each user's live share of it",
operationId: "getHost",
description:
"Cached 3 s. `usage` is null off Linux. Server mode: admin/analyst only (403 EADMIN for a developer).",
responses: {
200: {
description:
"`{at, hostname, os, uptime, cpu, memory, disks, homesMount, gpus, usage}` — see docs/API.md.",
content: { "application/json": { schema: { type: "object" } } },
},
500: { description: "EHOST" },
},
},
},
};
module.exports = { tags, schemas, paths };