artifacty 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +52 -0
- package/CLAUDE.md +64 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/docs/artifact-schema-v1.md +96 -0
- package/docs/assets/artifacty.png +0 -0
- package/docs/integrations.md +196 -0
- package/docs/release-checklist.md +30 -0
- package/package.json +55 -0
- package/scripts/smoke.sh +104 -0
- package/src/cli.js +348 -0
- package/src/client/editor.js +260 -0
- package/src/lib/backup.js +75 -0
- package/src/lib/check.js +124 -0
- package/src/lib/converters.js +586 -0
- package/src/lib/diff.js +69 -0
- package/src/lib/editor-assets.js +59 -0
- package/src/lib/i18n.js +175 -0
- package/src/lib/installer.js +187 -0
- package/src/lib/render.js +1181 -0
- package/src/lib/security.js +114 -0
- package/src/lib/server-state.js +53 -0
- package/src/lib/service.js +105 -0
- package/src/lib/storage.js +846 -0
- package/src/mcp-server.js +495 -0
- package/src/server.js +576 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import {
|
|
4
|
+
archiveArtifact,
|
|
5
|
+
createArtifact,
|
|
6
|
+
createStore,
|
|
7
|
+
getArtifact,
|
|
8
|
+
listAuditEvents,
|
|
9
|
+
listArtifacts,
|
|
10
|
+
restoreArtifact,
|
|
11
|
+
updateArtifact
|
|
12
|
+
} from "./lib/storage.js";
|
|
13
|
+
import { convertAgentArtifact } from "./lib/converters.js";
|
|
14
|
+
import { resolvePublicBaseUrl } from "./lib/server-state.js";
|
|
15
|
+
|
|
16
|
+
const PROTOCOL_VERSION = "2025-06-18";
|
|
17
|
+
const store = createStore();
|
|
18
|
+
|
|
19
|
+
const nativeArtifactInputSchema = {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
title: { type: "string", description: "Human-readable artifact title." },
|
|
23
|
+
content: { type: "string", description: "Artifact content." },
|
|
24
|
+
format: {
|
|
25
|
+
type: "string",
|
|
26
|
+
enum: ["html", "markdown", "text", "json"],
|
|
27
|
+
description: "Content format."
|
|
28
|
+
},
|
|
29
|
+
artifactType: {
|
|
30
|
+
type: "string",
|
|
31
|
+
enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
|
|
32
|
+
},
|
|
33
|
+
schemaVersion: {
|
|
34
|
+
type: "number",
|
|
35
|
+
enum: [1]
|
|
36
|
+
},
|
|
37
|
+
sourceAgent: { type: "string", description: "Agent or tool that produced the artifact." },
|
|
38
|
+
tags: {
|
|
39
|
+
type: "array",
|
|
40
|
+
items: { type: "string" },
|
|
41
|
+
description: "Searchable labels."
|
|
42
|
+
},
|
|
43
|
+
metadata: {
|
|
44
|
+
type: "object",
|
|
45
|
+
description: "Optional JSON metadata."
|
|
46
|
+
},
|
|
47
|
+
allowSecrets: {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
description: "Set true only when intentionally storing content that matches secret patterns."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
required: ["title", "content"]
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const mutatingArtifactAnnotations = {
|
|
56
|
+
destructiveHint: false,
|
|
57
|
+
idempotentHint: false,
|
|
58
|
+
openWorldHint: false
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const tools = [
|
|
62
|
+
{
|
|
63
|
+
name: "artifacty_create",
|
|
64
|
+
title: "Create Artifact",
|
|
65
|
+
description: "Create a new Artifacty-native artifact from title, content, format, tags, and metadata.",
|
|
66
|
+
inputSchema: nativeArtifactInputSchema,
|
|
67
|
+
annotations: mutatingArtifactAnnotations
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "artifacty_publish",
|
|
71
|
+
title: "Publish Artifact",
|
|
72
|
+
description: "Backwards-compatible alias for artifacty_create.",
|
|
73
|
+
inputSchema: nativeArtifactInputSchema,
|
|
74
|
+
annotations: mutatingArtifactAnnotations
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "artifacty_list",
|
|
78
|
+
title: "List Artifacts",
|
|
79
|
+
description: "List artifacts from the local Artifacty store.",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
query: { type: "string" },
|
|
84
|
+
tag: { type: "string" },
|
|
85
|
+
sourceAgent: { type: "string" },
|
|
86
|
+
includeArchived: { type: "boolean" },
|
|
87
|
+
limit: { type: "number" }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
annotations: {
|
|
91
|
+
readOnlyHint: true,
|
|
92
|
+
openWorldHint: false
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "artifacty_import",
|
|
97
|
+
title: "Import Agent Artifact",
|
|
98
|
+
description: "Convert an artifact produced by Claude, Codex, Gemini, or another agent into Artifacty format and save it.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
agent: {
|
|
103
|
+
type: "string",
|
|
104
|
+
enum: ["auto", "claude", "codex", "gemini", "artifacty", "generic"],
|
|
105
|
+
description: "Original agent family. Use auto when unsure."
|
|
106
|
+
},
|
|
107
|
+
title: { type: "string", description: "Optional title override." },
|
|
108
|
+
content: { type: "string", description: "Raw artifact file contents or serialized agent payload." },
|
|
109
|
+
payload: { type: "object", description: "Structured agent payload when available." },
|
|
110
|
+
format: {
|
|
111
|
+
type: "string",
|
|
112
|
+
enum: ["html", "markdown", "text", "json"]
|
|
113
|
+
},
|
|
114
|
+
artifactType: {
|
|
115
|
+
type: "string",
|
|
116
|
+
enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
|
|
117
|
+
},
|
|
118
|
+
schemaVersion: {
|
|
119
|
+
type: "number",
|
|
120
|
+
enum: [1]
|
|
121
|
+
},
|
|
122
|
+
contentType: { type: "string" },
|
|
123
|
+
fileName: { type: "string" },
|
|
124
|
+
sourcePath: { type: "string" },
|
|
125
|
+
tags: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: { type: "string" }
|
|
128
|
+
},
|
|
129
|
+
metadata: { type: "object" },
|
|
130
|
+
allowSecrets: {
|
|
131
|
+
type: "boolean",
|
|
132
|
+
description: "Set true only when intentionally storing content that matches secret patterns."
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
required: []
|
|
136
|
+
},
|
|
137
|
+
annotations: mutatingArtifactAnnotations
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "artifacty_get",
|
|
141
|
+
title: "Get Artifact",
|
|
142
|
+
description: "Get artifact metadata and content by ID.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
id: { type: "string", description: "Artifact ID." },
|
|
147
|
+
version: { type: "number", description: "Optional version number." },
|
|
148
|
+
includeContent: { type: "boolean", description: "Include content in the response. Defaults to true." }
|
|
149
|
+
},
|
|
150
|
+
required: ["id"]
|
|
151
|
+
},
|
|
152
|
+
annotations: {
|
|
153
|
+
readOnlyHint: true,
|
|
154
|
+
openWorldHint: false
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "artifacty_update",
|
|
159
|
+
title: "Update Artifact",
|
|
160
|
+
description: "Append a new version to an existing artifact.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
id: { type: "string", description: "Artifact ID." },
|
|
165
|
+
title: { type: "string" },
|
|
166
|
+
content: { type: "string" },
|
|
167
|
+
format: {
|
|
168
|
+
type: "string",
|
|
169
|
+
enum: ["html", "markdown", "text", "json"]
|
|
170
|
+
},
|
|
171
|
+
artifactType: {
|
|
172
|
+
type: "string",
|
|
173
|
+
enum: ["document", "html-page", "handoff", "code-review", "test-report", "dashboard", "design-option", "diff-walkthrough", "bundle", "asset", "unknown"]
|
|
174
|
+
},
|
|
175
|
+
schemaVersion: {
|
|
176
|
+
type: "number",
|
|
177
|
+
enum: [1]
|
|
178
|
+
},
|
|
179
|
+
sourceAgent: { type: "string" },
|
|
180
|
+
tags: {
|
|
181
|
+
type: "array",
|
|
182
|
+
items: { type: "string" }
|
|
183
|
+
},
|
|
184
|
+
metadata: { type: "object" },
|
|
185
|
+
allowSecrets: {
|
|
186
|
+
type: "boolean",
|
|
187
|
+
description: "Set true only when intentionally storing content that matches secret patterns."
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
required: ["id", "content"]
|
|
191
|
+
},
|
|
192
|
+
annotations: {
|
|
193
|
+
destructiveHint: false,
|
|
194
|
+
idempotentHint: false,
|
|
195
|
+
openWorldHint: false
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "artifacty_archive",
|
|
200
|
+
title: "Archive Artifact",
|
|
201
|
+
description: "Mark an artifact archived without deleting its versions.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
id: { type: "string", description: "Artifact ID." }
|
|
206
|
+
},
|
|
207
|
+
required: ["id"]
|
|
208
|
+
},
|
|
209
|
+
annotations: mutatingArtifactAnnotations
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "artifacty_restore",
|
|
213
|
+
title: "Restore Artifact",
|
|
214
|
+
description: "Restore a previously archived artifact.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
id: { type: "string", description: "Artifact ID." }
|
|
219
|
+
},
|
|
220
|
+
required: ["id"]
|
|
221
|
+
},
|
|
222
|
+
annotations: mutatingArtifactAnnotations
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "artifacty_audit",
|
|
226
|
+
title: "List Audit Events",
|
|
227
|
+
description: "List recent audit events for the Artifacty store or a single artifact.",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
artifactId: { type: "string" },
|
|
232
|
+
limit: { type: "number" }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
annotations: {
|
|
236
|
+
readOnlyHint: true,
|
|
237
|
+
openWorldHint: false
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "artifacty_info",
|
|
242
|
+
title: "Artifacty Info",
|
|
243
|
+
description: "Return local Artifacty store and browser URL information.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {}
|
|
247
|
+
},
|
|
248
|
+
annotations: {
|
|
249
|
+
readOnlyHint: true,
|
|
250
|
+
openWorldHint: false
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const rl = readline.createInterface({
|
|
256
|
+
input: process.stdin,
|
|
257
|
+
crlfDelay: Infinity
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
rl.on("line", (line) => {
|
|
261
|
+
if (!line.trim()) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
handleLine(line).catch((error) => {
|
|
265
|
+
process.stderr.write(`${error.stack || error.message}\n`);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
async function handleLine(line) {
|
|
270
|
+
let message;
|
|
271
|
+
try {
|
|
272
|
+
message = JSON.parse(line);
|
|
273
|
+
} catch {
|
|
274
|
+
writeResponse(null, undefined, {
|
|
275
|
+
code: -32700,
|
|
276
|
+
message: "Parse error"
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!message.id && message.id !== 0) {
|
|
282
|
+
await handleNotification(message);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const result = await handleRequest(message);
|
|
288
|
+
writeResponse(message.id, result);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
writeResponse(message.id, undefined, {
|
|
291
|
+
code: error.jsonRpcCode || -32603,
|
|
292
|
+
message: error.message
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function handleNotification(message) {
|
|
298
|
+
if (message.method === "notifications/initialized") {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
process.stderr.write(`Ignoring MCP notification: ${message.method}\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function handleRequest(message) {
|
|
305
|
+
if (message.method === "initialize") {
|
|
306
|
+
return {
|
|
307
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
308
|
+
capabilities: {
|
|
309
|
+
tools: {
|
|
310
|
+
listChanged: false
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
serverInfo: {
|
|
314
|
+
name: "artifacty",
|
|
315
|
+
title: "Artifacty",
|
|
316
|
+
version: "0.1.0"
|
|
317
|
+
},
|
|
318
|
+
instructions: "Use Artifacty to create, import, list, read, and update local artifacts that other agents can reuse."
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (message.method === "ping") {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (message.method === "tools/list") {
|
|
327
|
+
return { tools };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (message.method === "tools/call") {
|
|
331
|
+
const params = message.params || {};
|
|
332
|
+
return callTool(params.name, params.arguments || {});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
throw Object.assign(new Error(`Method not found: ${message.method}`), {
|
|
336
|
+
jsonRpcCode: -32601
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function callTool(name, args) {
|
|
341
|
+
if (name === "artifacty_create" || name === "artifacty_publish") {
|
|
342
|
+
return toolResult(await withUrls(await createNativeArtifact(args)));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (name === "artifacty_list") {
|
|
346
|
+
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
347
|
+
const artifacts = await listArtifacts(store, args);
|
|
348
|
+
return toolResult({
|
|
349
|
+
artifacts: artifacts.map((artifact) => ({
|
|
350
|
+
...artifact,
|
|
351
|
+
url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`
|
|
352
|
+
}))
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (name === "artifacty_import") {
|
|
357
|
+
const converted = convertAgentArtifact(args);
|
|
358
|
+
const artifact = await createArtifact(store, {
|
|
359
|
+
...converted,
|
|
360
|
+
allowSecrets: args.allowSecrets,
|
|
361
|
+
auditAction: "import",
|
|
362
|
+
audit: mcpAuditContext()
|
|
363
|
+
});
|
|
364
|
+
return toolResult({
|
|
365
|
+
...await withUrls(artifact),
|
|
366
|
+
converted
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (name === "artifacty_get") {
|
|
371
|
+
const artifact = await getArtifact(store, requireArg(args, "id"), {
|
|
372
|
+
version: args.version,
|
|
373
|
+
audit: mcpAuditContext()
|
|
374
|
+
});
|
|
375
|
+
const decorated = await withUrls(artifact);
|
|
376
|
+
if (args.includeContent === false) {
|
|
377
|
+
delete decorated.content;
|
|
378
|
+
}
|
|
379
|
+
return toolResult(decorated);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (name === "artifacty_update") {
|
|
383
|
+
const artifact = await updateArtifact(store, requireArg(args, "id"), {
|
|
384
|
+
title: args.title,
|
|
385
|
+
content: args.content,
|
|
386
|
+
format: args.format,
|
|
387
|
+
artifactType: args.artifactType,
|
|
388
|
+
schemaVersion: args.schemaVersion,
|
|
389
|
+
sourceAgent: args.sourceAgent || "mcp",
|
|
390
|
+
tags: args.tags || [],
|
|
391
|
+
metadata: args.metadata || {},
|
|
392
|
+
allowSecrets: args.allowSecrets,
|
|
393
|
+
audit: mcpAuditContext()
|
|
394
|
+
});
|
|
395
|
+
return toolResult(await withUrls(artifact));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (name === "artifacty_archive" || name === "artifacty_restore") {
|
|
399
|
+
const artifact = name === "artifacty_archive"
|
|
400
|
+
? await archiveArtifact(store, requireArg(args, "id"), { audit: mcpAuditContext() })
|
|
401
|
+
: await restoreArtifact(store, requireArg(args, "id"), { audit: mcpAuditContext() });
|
|
402
|
+
return toolResult(await withUrls(artifact));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (name === "artifacty_audit") {
|
|
406
|
+
return toolResult({
|
|
407
|
+
events: await listAuditEvents(store, {
|
|
408
|
+
artifactId: args.artifactId,
|
|
409
|
+
limit: args.limit
|
|
410
|
+
})
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (name === "artifacty_info") {
|
|
415
|
+
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
416
|
+
return toolResult({
|
|
417
|
+
name: "artifacty",
|
|
418
|
+
store: store.home,
|
|
419
|
+
url: publicBaseUrl,
|
|
420
|
+
mcpProtocolVersion: PROTOCOL_VERSION,
|
|
421
|
+
serverCommand: "node src/mcp-server.js",
|
|
422
|
+
browserCommand: "npm start"
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
throw Object.assign(new Error(`Unknown tool: ${name}`), {
|
|
427
|
+
jsonRpcCode: -32602
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function createNativeArtifact(args) {
|
|
432
|
+
return createArtifact(store, {
|
|
433
|
+
title: args.title,
|
|
434
|
+
content: args.content,
|
|
435
|
+
format: args.format,
|
|
436
|
+
artifactType: args.artifactType,
|
|
437
|
+
schemaVersion: args.schemaVersion,
|
|
438
|
+
sourceAgent: args.sourceAgent || "mcp",
|
|
439
|
+
tags: args.tags || [],
|
|
440
|
+
metadata: args.metadata || {},
|
|
441
|
+
allowSecrets: args.allowSecrets,
|
|
442
|
+
audit: mcpAuditContext()
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function mcpAuditContext() {
|
|
447
|
+
return {
|
|
448
|
+
surface: "mcp",
|
|
449
|
+
actor: "mcp-client"
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function withUrls(artifact) {
|
|
454
|
+
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
455
|
+
return {
|
|
456
|
+
...artifact,
|
|
457
|
+
url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`,
|
|
458
|
+
rawUrl: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}/raw?version=${artifact.version.version}`
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function toolResult(data) {
|
|
463
|
+
return {
|
|
464
|
+
content: [
|
|
465
|
+
{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: JSON.stringify(data, null, 2)
|
|
468
|
+
}
|
|
469
|
+
],
|
|
470
|
+
structuredContent: data,
|
|
471
|
+
isError: false
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function requireArg(args, name) {
|
|
476
|
+
if (!args[name]) {
|
|
477
|
+
throw Object.assign(new Error(`Missing required argument: ${name}`), {
|
|
478
|
+
jsonRpcCode: -32602
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return args[name];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function writeResponse(id, result, error) {
|
|
485
|
+
const response = {
|
|
486
|
+
jsonrpc: "2.0",
|
|
487
|
+
id
|
|
488
|
+
};
|
|
489
|
+
if (error) {
|
|
490
|
+
response.error = error;
|
|
491
|
+
} else {
|
|
492
|
+
response.result = result;
|
|
493
|
+
}
|
|
494
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
495
|
+
}
|