artifacty 0.2.0 → 0.4.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 +2 -0
- package/README.md +22 -0
- package/docs/integrations.md +34 -1
- package/docs/mcp-public-api.md +68 -0
- package/package.json +2 -1
- package/src/cli.js +44 -6
- package/src/lib/check.js +29 -2
- package/src/lib/i18n.js +8 -0
- package/src/lib/render.js +88 -2
- package/src/lib/storage.js +438 -30
- package/src/mcp-server.js +337 -7
- package/src/server.js +28 -8
package/src/mcp-server.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import readline from "node:readline";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import {
|
|
4
7
|
ARTIFACT_FORMATS,
|
|
5
8
|
ARTIFACT_TYPES,
|
|
@@ -8,7 +11,7 @@ import {
|
|
|
8
11
|
createStore,
|
|
9
12
|
getArtifact,
|
|
10
13
|
listAuditEvents,
|
|
11
|
-
|
|
14
|
+
listArtifactsPage,
|
|
12
15
|
restoreArtifact,
|
|
13
16
|
updateArtifact
|
|
14
17
|
} from "./lib/storage.js";
|
|
@@ -16,6 +19,7 @@ import { convertAgentArtifact } from "./lib/converters.js";
|
|
|
16
19
|
import { resolvePublicBaseUrl } from "./lib/server-state.js";
|
|
17
20
|
|
|
18
21
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
22
|
+
const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
19
23
|
const store = createStore();
|
|
20
24
|
|
|
21
25
|
const nativeArtifactInputSchema = {
|
|
@@ -86,7 +90,8 @@ const tools = [
|
|
|
86
90
|
tag: { type: "string" },
|
|
87
91
|
sourceAgent: { type: "string" },
|
|
88
92
|
includeArchived: { type: "boolean" },
|
|
89
|
-
limit: { type: "number" }
|
|
93
|
+
limit: { type: "number" },
|
|
94
|
+
offset: { type: "number" }
|
|
90
95
|
}
|
|
91
96
|
},
|
|
92
97
|
annotations: {
|
|
@@ -254,6 +259,71 @@ const tools = [
|
|
|
254
259
|
}
|
|
255
260
|
];
|
|
256
261
|
|
|
262
|
+
const resourceTemplates = [
|
|
263
|
+
{
|
|
264
|
+
uriTemplate: "artifacty://artifacts/{id}",
|
|
265
|
+
name: "artifact-by-id",
|
|
266
|
+
title: "Artifact by ID",
|
|
267
|
+
description: "Read an Artifacty artifact with metadata, latest version, content, and browser URLs.",
|
|
268
|
+
mimeType: "application/json"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
uriTemplate: "artifacty://artifacts/{id}/raw{?version}",
|
|
272
|
+
name: "artifact-raw-content",
|
|
273
|
+
title: "Artifact Raw Content",
|
|
274
|
+
description: "Read raw artifact content by ID and optional version.",
|
|
275
|
+
mimeType: "text/plain"
|
|
276
|
+
}
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const prompts = [
|
|
280
|
+
{
|
|
281
|
+
name: "artifacty_handoff",
|
|
282
|
+
title: "Create Artifact Handoff",
|
|
283
|
+
description: "Prepare a concise continuation artifact for another agent.",
|
|
284
|
+
arguments: [
|
|
285
|
+
{ name: "goal", description: "Current goal or handoff objective.", required: false },
|
|
286
|
+
{ name: "artifactId", description: "Existing Artifacty artifact to continue from.", required: false }
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "artifacty_review",
|
|
291
|
+
title: "Create Review Artifact",
|
|
292
|
+
description: "Capture code review findings as a shareable Artifacty artifact.",
|
|
293
|
+
arguments: [
|
|
294
|
+
{ name: "scope", description: "Files, branch, PR, or behavior under review.", required: false },
|
|
295
|
+
{ name: "artifactId", description: "Existing artifact with review context.", required: false }
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "artifacty_test_report",
|
|
300
|
+
title: "Create Test Report Artifact",
|
|
301
|
+
description: "Summarize verification commands, status, failures, and residual risk.",
|
|
302
|
+
arguments: [
|
|
303
|
+
{ name: "goal", description: "Feature or release being verified.", required: false },
|
|
304
|
+
{ name: "artifactId", description: "Existing artifact with implementation context.", required: false }
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "artifacty_visual_qa",
|
|
309
|
+
title: "Create Visual QA Artifact",
|
|
310
|
+
description: "Record browser, screenshot, media, or visual regression evidence.",
|
|
311
|
+
arguments: [
|
|
312
|
+
{ name: "target", description: "URL, artifact ID, or UI surface under visual review.", required: false },
|
|
313
|
+
{ name: "artifactId", description: "Existing visual evidence artifact.", required: false }
|
|
314
|
+
]
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "artifacty_release_notes",
|
|
318
|
+
title: "Create Release Notes Artifact",
|
|
319
|
+
description: "Draft release notes from changed artifacts, tests, and known risks.",
|
|
320
|
+
arguments: [
|
|
321
|
+
{ name: "version", description: "Release version.", required: false },
|
|
322
|
+
{ name: "artifactId", description: "Existing roadmap, checklist, or handoff artifact.", required: false }
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
];
|
|
326
|
+
|
|
257
327
|
const rl = readline.createInterface({
|
|
258
328
|
input: process.stdin,
|
|
259
329
|
crlfDelay: Infinity
|
|
@@ -310,14 +380,21 @@ async function handleRequest(message) {
|
|
|
310
380
|
capabilities: {
|
|
311
381
|
tools: {
|
|
312
382
|
listChanged: false
|
|
383
|
+
},
|
|
384
|
+
resources: {
|
|
385
|
+
subscribe: false,
|
|
386
|
+
listChanged: false
|
|
387
|
+
},
|
|
388
|
+
prompts: {
|
|
389
|
+
listChanged: false
|
|
313
390
|
}
|
|
314
391
|
},
|
|
315
392
|
serverInfo: {
|
|
316
393
|
name: "artifacty",
|
|
317
394
|
title: "Artifacty",
|
|
318
|
-
version: "0.
|
|
395
|
+
version: "0.4.0"
|
|
319
396
|
},
|
|
320
|
-
instructions: "Use Artifacty to create, import, list, read, and
|
|
397
|
+
instructions: "Use Artifacty to create, import, list, read, update, and resource-read local artifacts that other agents can reuse."
|
|
321
398
|
};
|
|
322
399
|
}
|
|
323
400
|
|
|
@@ -334,6 +411,27 @@ async function handleRequest(message) {
|
|
|
334
411
|
return callTool(params.name, params.arguments || {});
|
|
335
412
|
}
|
|
336
413
|
|
|
414
|
+
if (message.method === "resources/list") {
|
|
415
|
+
return listResources();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (message.method === "resources/templates/list") {
|
|
419
|
+
return { resourceTemplates };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (message.method === "resources/read") {
|
|
423
|
+
return readResource(requireParam(message.params, "uri"));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (message.method === "prompts/list") {
|
|
427
|
+
return { prompts };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (message.method === "prompts/get") {
|
|
431
|
+
const params = message.params || {};
|
|
432
|
+
return getPrompt(requireParam(params, "name"), params.arguments || {});
|
|
433
|
+
}
|
|
434
|
+
|
|
337
435
|
throw Object.assign(new Error(`Method not found: ${message.method}`), {
|
|
338
436
|
jsonRpcCode: -32601
|
|
339
437
|
});
|
|
@@ -346,12 +444,21 @@ async function callTool(name, args) {
|
|
|
346
444
|
|
|
347
445
|
if (name === "artifacty_list") {
|
|
348
446
|
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
349
|
-
const
|
|
447
|
+
const page = await listArtifactsPage(store, args);
|
|
350
448
|
return toolResult({
|
|
351
|
-
artifacts: artifacts.map((artifact) => ({
|
|
449
|
+
artifacts: page.artifacts.map((artifact) => ({
|
|
352
450
|
...artifact,
|
|
353
451
|
url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`
|
|
354
|
-
}))
|
|
452
|
+
})),
|
|
453
|
+
pagination: {
|
|
454
|
+
total: page.total,
|
|
455
|
+
limit: page.limit,
|
|
456
|
+
offset: page.offset,
|
|
457
|
+
hasMore: page.hasMore,
|
|
458
|
+
nextOffset: page.nextOffset,
|
|
459
|
+
previousOffset: page.previousOffset
|
|
460
|
+
},
|
|
461
|
+
search: page.search
|
|
355
462
|
});
|
|
356
463
|
}
|
|
357
464
|
|
|
@@ -430,6 +537,177 @@ async function callTool(name, args) {
|
|
|
430
537
|
});
|
|
431
538
|
}
|
|
432
539
|
|
|
540
|
+
async function listResources() {
|
|
541
|
+
const page = await listArtifactsPage(store, { limit: 10 });
|
|
542
|
+
const artifactResources = page.artifacts.flatMap((artifact) => [
|
|
543
|
+
{
|
|
544
|
+
uri: artifactResourceUri(artifact.id),
|
|
545
|
+
name: `artifact:${artifact.id}`,
|
|
546
|
+
title: artifact.title,
|
|
547
|
+
description: `${artifact.sourceAgent} ${artifact.artifactType} artifact, v${artifact.latestVersion}`,
|
|
548
|
+
mimeType: "application/json"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
uri: artifactRawResourceUri(artifact.id),
|
|
552
|
+
name: `artifact-raw:${artifact.id}`,
|
|
553
|
+
title: `${artifact.title} raw`,
|
|
554
|
+
description: `Raw latest ${artifact.format || "text"} content for ${artifact.id}`,
|
|
555
|
+
mimeType: artifact.contentType || "text/plain"
|
|
556
|
+
}
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
resources: [
|
|
561
|
+
{
|
|
562
|
+
uri: "artifacty://recent",
|
|
563
|
+
name: "recent-artifacts",
|
|
564
|
+
title: "Recent Artifacts",
|
|
565
|
+
description: "Recent Artifacty artifacts with pagination metadata and browser URLs.",
|
|
566
|
+
mimeType: "application/json"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
uri: "artifacty://schema/v1",
|
|
570
|
+
name: "artifact-schema-v1",
|
|
571
|
+
title: "Artifact Schema v1",
|
|
572
|
+
description: "Artifacty schema v1 reference document.",
|
|
573
|
+
mimeType: "text/markdown"
|
|
574
|
+
},
|
|
575
|
+
...artifactResources
|
|
576
|
+
]
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async function readResource(uri) {
|
|
581
|
+
if (uri === "artifacty://recent") {
|
|
582
|
+
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
583
|
+
const page = await listArtifactsPage(store, { limit: 20 });
|
|
584
|
+
return resourceText(uri, "application/json", {
|
|
585
|
+
artifacts: page.artifacts.map((artifact) => ({
|
|
586
|
+
...artifact,
|
|
587
|
+
url: `${publicBaseUrl}/artifacts/${encodeURIComponent(artifact.id)}`
|
|
588
|
+
})),
|
|
589
|
+
pagination: {
|
|
590
|
+
total: page.total,
|
|
591
|
+
limit: page.limit,
|
|
592
|
+
offset: page.offset,
|
|
593
|
+
hasMore: page.hasMore,
|
|
594
|
+
nextOffset: page.nextOffset,
|
|
595
|
+
previousOffset: page.previousOffset
|
|
596
|
+
},
|
|
597
|
+
search: page.search
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (uri === "artifacty://schema/v1") {
|
|
602
|
+
return {
|
|
603
|
+
contents: [
|
|
604
|
+
{
|
|
605
|
+
uri,
|
|
606
|
+
mimeType: "text/markdown",
|
|
607
|
+
text: await readFile(path.join(PACKAGE_ROOT, "docs", "artifact-schema-v1.md"), "utf8")
|
|
608
|
+
}
|
|
609
|
+
]
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const parsed = parseArtifactResourceUri(uri);
|
|
614
|
+
if (parsed) {
|
|
615
|
+
const artifact = await getArtifact(store, parsed.id, {
|
|
616
|
+
version: parsed.version,
|
|
617
|
+
audit: mcpAuditContext()
|
|
618
|
+
});
|
|
619
|
+
if (parsed.raw) {
|
|
620
|
+
return {
|
|
621
|
+
contents: [
|
|
622
|
+
{
|
|
623
|
+
uri,
|
|
624
|
+
mimeType: artifact.version.contentType || "text/plain",
|
|
625
|
+
text: artifact.content
|
|
626
|
+
}
|
|
627
|
+
]
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return resourceText(uri, "application/json", await withUrls(artifact));
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
throw Object.assign(new Error(`Unknown resource: ${uri}`), {
|
|
634
|
+
jsonRpcCode: -32602
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function getPrompt(name, args) {
|
|
639
|
+
const definition = prompts.find((prompt) => prompt.name === name);
|
|
640
|
+
if (!definition) {
|
|
641
|
+
throw Object.assign(new Error(`Unknown prompt: ${name}`), {
|
|
642
|
+
jsonRpcCode: -32602
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
description: definition.description,
|
|
648
|
+
messages: [
|
|
649
|
+
{
|
|
650
|
+
role: "user",
|
|
651
|
+
content: {
|
|
652
|
+
type: "text",
|
|
653
|
+
text: promptText(name, args)
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function promptText(name, args = {}) {
|
|
661
|
+
const context = args.artifactId
|
|
662
|
+
? `First read Artifacty resource ${artifactResourceUri(args.artifactId)} and use it as context.`
|
|
663
|
+
: "Use the current session context and any relevant Artifacty resources.";
|
|
664
|
+
const common = `${context}
|
|
665
|
+
|
|
666
|
+
Create or update an Artifacty artifact through artifacty_create, artifacty_import, or artifacty_update. Use concise Markdown unless another format is clearly better. Include sourceAgent, artifactType, and tags so another agent can discover it.`;
|
|
667
|
+
|
|
668
|
+
if (name === "artifacty_handoff") {
|
|
669
|
+
return `${common}
|
|
670
|
+
|
|
671
|
+
Goal: ${args.goal || "Prepare a continuation handoff for the next agent."}
|
|
672
|
+
|
|
673
|
+
Capture: current state, changed files or artifacts, commands run, decisions, blockers, residual risk, and next steps.
|
|
674
|
+
Recommended artifactType: handoff. Recommended tags: handoff, continuation.`;
|
|
675
|
+
}
|
|
676
|
+
if (name === "artifacty_review") {
|
|
677
|
+
return `${common}
|
|
678
|
+
|
|
679
|
+
Review scope: ${args.scope || "Review the current implementation or linked artifact."}
|
|
680
|
+
|
|
681
|
+
Capture findings first, ordered by severity, with file/line references when available. Include open questions and verification gaps.
|
|
682
|
+
Recommended artifactType: code-review. Recommended tags: review.`;
|
|
683
|
+
}
|
|
684
|
+
if (name === "artifacty_test_report") {
|
|
685
|
+
return `${common}
|
|
686
|
+
|
|
687
|
+
Verification goal: ${args.goal || "Summarize test and smoke evidence."}
|
|
688
|
+
|
|
689
|
+
Capture commands, status, failures, environment, manual checks, and what remains untested.
|
|
690
|
+
Recommended artifactType: test-report. Recommended tags: verification, test-report.`;
|
|
691
|
+
}
|
|
692
|
+
if (name === "artifacty_visual_qa") {
|
|
693
|
+
return `${common}
|
|
694
|
+
|
|
695
|
+
Visual target: ${args.target || "Inspect the UI or visual artifact under review."}
|
|
696
|
+
|
|
697
|
+
Capture screenshots/media references, viewport, expected behavior, observed issues, and pass/fail verdict.
|
|
698
|
+
Recommended artifactType: design-option or bundle. Recommended tags: visual, qa.`;
|
|
699
|
+
}
|
|
700
|
+
if (name === "artifacty_release_notes") {
|
|
701
|
+
return `${common}
|
|
702
|
+
|
|
703
|
+
Release version: ${args.version || "next"}
|
|
704
|
+
|
|
705
|
+
Capture highlights, breaking changes, migration notes, tests, known limitations, and publish evidence.
|
|
706
|
+
Recommended artifactType: document. Recommended tags: release-notes.`;
|
|
707
|
+
}
|
|
708
|
+
return common;
|
|
709
|
+
}
|
|
710
|
+
|
|
433
711
|
async function createNativeArtifact(args) {
|
|
434
712
|
return createArtifact(store, {
|
|
435
713
|
title: args.title,
|
|
@@ -461,6 +739,49 @@ async function withUrls(artifact) {
|
|
|
461
739
|
};
|
|
462
740
|
}
|
|
463
741
|
|
|
742
|
+
function resourceText(uri, mimeType, data) {
|
|
743
|
+
return {
|
|
744
|
+
contents: [
|
|
745
|
+
{
|
|
746
|
+
uri,
|
|
747
|
+
mimeType,
|
|
748
|
+
text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function artifactResourceUri(id) {
|
|
755
|
+
return `artifacty://artifacts/${encodeURIComponent(id)}`;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function artifactRawResourceUri(id, version) {
|
|
759
|
+
const suffix = version ? `?version=${encodeURIComponent(String(version))}` : "";
|
|
760
|
+
return `artifacty://artifacts/${encodeURIComponent(id)}/raw${suffix}`;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function parseArtifactResourceUri(uri) {
|
|
764
|
+
let parsed;
|
|
765
|
+
try {
|
|
766
|
+
parsed = new URL(uri);
|
|
767
|
+
} catch {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (parsed.protocol !== "artifacty:" || parsed.hostname !== "artifacts") {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
775
|
+
if (parts.length !== 1 && !(parts.length === 2 && parts[1] === "raw")) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
id: decodeURIComponent(parts[0]),
|
|
780
|
+
raw: parts[1] === "raw",
|
|
781
|
+
version: parsed.searchParams.get("version") || undefined
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
464
785
|
function toolResult(data) {
|
|
465
786
|
return {
|
|
466
787
|
content: [
|
|
@@ -483,6 +804,15 @@ function requireArg(args, name) {
|
|
|
483
804
|
return args[name];
|
|
484
805
|
}
|
|
485
806
|
|
|
807
|
+
function requireParam(params = {}, name) {
|
|
808
|
+
if (!params[name]) {
|
|
809
|
+
throw Object.assign(new Error(`Missing required parameter: ${name}`), {
|
|
810
|
+
jsonRpcCode: -32602
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
return params[name];
|
|
814
|
+
}
|
|
815
|
+
|
|
486
816
|
function writeResponse(id, result, error) {
|
|
487
817
|
const response = {
|
|
488
818
|
jsonrpc: "2.0",
|
package/src/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createArtifact,
|
|
10
10
|
createStore,
|
|
11
11
|
getArtifact,
|
|
12
|
-
|
|
12
|
+
listArtifactsPage,
|
|
13
13
|
listAuditEvents,
|
|
14
14
|
MAX_ARTIFACT_BYTES,
|
|
15
15
|
restoreArtifact,
|
|
@@ -177,15 +177,19 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
177
177
|
query: url.searchParams.get("q") || "",
|
|
178
178
|
tag: url.searchParams.get("tag") || "",
|
|
179
179
|
sourceAgent: url.searchParams.get("sourceAgent") || "",
|
|
180
|
-
includeArchived: url.searchParams.get("includeArchived") === "true"
|
|
180
|
+
includeArchived: url.searchParams.get("includeArchived") === "true",
|
|
181
|
+
limit: url.searchParams.get("limit") || undefined,
|
|
182
|
+
offset: url.searchParams.get("offset") || undefined
|
|
181
183
|
};
|
|
182
|
-
const
|
|
184
|
+
const page = await listArtifactsPage(store, {
|
|
183
185
|
query: filters.query || undefined,
|
|
184
186
|
tag: filters.tag || undefined,
|
|
185
187
|
sourceAgent: filters.sourceAgent || undefined,
|
|
186
|
-
includeArchived: filters.includeArchived
|
|
188
|
+
includeArchived: filters.includeArchived,
|
|
189
|
+
limit: filters.limit,
|
|
190
|
+
offset: filters.offset
|
|
187
191
|
});
|
|
188
|
-
return sendHtml(response, renderDashboard({ artifacts, baseUrl, filters, locale, currentPath }), 200, headOnly);
|
|
192
|
+
return sendHtml(response, renderDashboard({ artifacts: page.artifacts, baseUrl, filters, pagination: page, locale, currentPath }), 200, headOnly);
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
if (method === "GET" && pathname === "/new") {
|
|
@@ -240,14 +244,19 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
if (method === "GET" && pathname === "/api/artifacts") {
|
|
243
|
-
const
|
|
247
|
+
const page = await listArtifactsPage(store, {
|
|
244
248
|
query: url.searchParams.get("q") || undefined,
|
|
245
249
|
tag: url.searchParams.get("tag") || undefined,
|
|
246
250
|
sourceAgent: url.searchParams.get("sourceAgent") || undefined,
|
|
247
251
|
includeArchived: url.searchParams.get("includeArchived") === "true",
|
|
248
|
-
limit: url.searchParams.get("limit") || undefined
|
|
252
|
+
limit: url.searchParams.get("limit") || undefined,
|
|
253
|
+
offset: url.searchParams.get("offset") || undefined
|
|
249
254
|
});
|
|
250
|
-
return sendJson(response, {
|
|
255
|
+
return sendJson(response, {
|
|
256
|
+
artifacts: page.artifacts,
|
|
257
|
+
pagination: paginationJson(page),
|
|
258
|
+
search: page.search
|
|
259
|
+
}, 200, headOnly);
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
if (method === "GET" && pathname === "/api/audit") {
|
|
@@ -442,6 +451,17 @@ export function decorateArtifactUrls(artifact, baseUrl) {
|
|
|
442
451
|
};
|
|
443
452
|
}
|
|
444
453
|
|
|
454
|
+
function paginationJson(page) {
|
|
455
|
+
return {
|
|
456
|
+
total: page.total,
|
|
457
|
+
limit: page.limit,
|
|
458
|
+
offset: page.offset,
|
|
459
|
+
hasMore: page.hasMore,
|
|
460
|
+
nextOffset: page.nextOffset,
|
|
461
|
+
previousOffset: page.previousOffset
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
445
465
|
export async function readJsonBody(request) {
|
|
446
466
|
const raw = await readBody(request, MAX_ARTIFACT_BYTES + 1024);
|
|
447
467
|
if (!raw.trim()) {
|