@artifactly/mcp 0.1.9 → 0.1.10

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/README.md CHANGED
@@ -66,11 +66,15 @@ Artifact workflow notes:
66
66
  - Supported visibility values are `public`, `unlisted`, `private`, and `gated`.
67
67
  - `gated` artifacts can be published with an `accessList` of allowed email addresses or updated later with `artifactly_update_artifact`.
68
68
  - `artifactly_list_artifacts` supports `limit`, `cursor`, `visibility`, and `kind` filters and omits large artifact content by default.
69
- - `artifactly_get_artifact` returns full owner-visible artifact details.
70
- - `artifactly_update_artifact` accepts either `markdown` or `content`, not both; pass `description: ""` to clear a description.
69
+ - `artifactly_get_artifact` returns full content by default, including base64/data URL file payloads. Pass `metadataOnly: true` to omit `content` and `storageUrl` for bounded metadata reads.
70
+ - `artifactly_update_artifact` accepts either `markdown` or `content`, not both, and preserves text whitespace exactly while rejecting whitespace-only content updates.
71
+ - To replace binary/file content, call `artifactly_update_artifact` with `filePath` and optional `mimeType`; the replacement file becomes the viewer source and clears old inline text content.
72
+ - Pass `description: ""` to clear a description.
71
73
  - `artifactly_publish_file` infers MIME type from file extension unless `mimeType` is supplied; explicit `mimeType` wins.
74
+ - Image MIME types such as `image/png` publish as `kind: "image"` and can be listed with `kind: "image"`.
72
75
  - Files larger than 10 MB are rejected before upload to avoid large in-memory base64 payloads.
73
76
  - Requests time out after 30 seconds.
77
+ - This package currently exposes MCP tools only; resources, resource templates, and prompts are intentionally unsupported.
74
78
 
75
79
  Manual publish from a logged-in maintainer account:
76
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artifactly/mcp",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "files": [
6
6
  "bin",
@@ -14,5 +14,9 @@ describe("Artifactly MCP package documentation", () => {
14
14
  expect(readme).toContain("`artifactly_list_artifacts` supports `limit`, `cursor`, `visibility`, and `kind`");
15
15
  expect(readme).toContain("Files larger than 10 MB are rejected before upload");
16
16
  expect(readme).toContain("Requests time out after 30 seconds");
17
+ expect(readme).toContain("`artifactly_get_artifact` returns full content by default");
18
+ expect(readme).toContain("`metadataOnly: true`");
19
+ expect(readme).toContain("This package currently exposes MCP tools only");
20
+ expect(readme).toContain("resources, resource templates, and prompts are intentionally unsupported");
17
21
  });
18
22
  });
@@ -41,7 +41,7 @@ describe("Artifactly MCP stdio server helpers", () => {
41
41
  it("registers tool schemas and server info from package metadata", () => {
42
42
  const server = createArtifactlyMcpServer() as unknown as {
43
43
  server: { _serverInfo: { name: string; version: string } };
44
- _registeredTools: Record<string, { inputSchema: { def: { shape: Record<string, unknown> }; safeParse(value: unknown): { success: boolean } } }>;
44
+ _registeredTools: Record<string, { inputSchema: { def: { shape: Record<string, unknown> }; safeParse(value: unknown): { success: boolean } }; annotations?: Record<string, unknown>; description: string }>;
45
45
  };
46
46
 
47
47
  expect(server.server._serverInfo).toEqual({
@@ -71,6 +71,38 @@ describe("Artifactly MCP stdio server helpers", () => {
71
71
  kind: "html",
72
72
  }).success,
73
73
  ).toBe(true);
74
+ expect(
75
+ server._registeredTools.artifactly_publish_file.inputSchema.safeParse({
76
+ path: "/tmp/chart.png",
77
+ visibility: "gated",
78
+ accessList: ["agent@example.com"],
79
+ }).success,
80
+ ).toBe(true);
81
+ expect(
82
+ server._registeredTools.artifactly_update_artifact.inputSchema.safeParse({
83
+ artifactId: "artifact_1",
84
+ filePath: "/tmp/replacement.pdf",
85
+ mimeType: "application/pdf",
86
+ }).success,
87
+ ).toBe(true);
88
+ });
89
+
90
+ it("registers MCP tool annotations and accurate content descriptions", () => {
91
+ const server = createArtifactlyMcpServer() as unknown as {
92
+ _registeredTools: Record<string, { annotations?: Record<string, unknown>; description: string }>;
93
+ };
94
+
95
+ expect(server._registeredTools.artifactly_list_artifacts.annotations).toMatchObject({
96
+ readOnlyHint: true,
97
+ });
98
+ expect(server._registeredTools.artifactly_get_artifact.annotations).toMatchObject({
99
+ readOnlyHint: true,
100
+ });
101
+ expect(server._registeredTools.artifactly_delete_artifact.annotations).toMatchObject({
102
+ destructiveHint: true,
103
+ });
104
+ expect(server._registeredTools.artifactly_get_artifact.description).toMatch(/full content/i);
105
+ expect(server._registeredTools.artifactly_get_artifact.description).toMatch(/metadataOnly/i);
74
106
  });
75
107
 
76
108
  it("maps Artifactly tools to MCP-compatible tool definitions", () => {
@@ -97,6 +129,20 @@ describe("Artifactly MCP stdio server helpers", () => {
97
129
  expect(result.content).toHaveLength(1);
98
130
  expect(result.content[0]).toMatchObject({ type: "text" });
99
131
  expect(result.content[0].text).toContain('"authenticated": false');
132
+ expect(result.structuredContent).toEqual({ authenticated: false, apiBaseUrl: "https://artifactly.io" });
100
133
  expect(result.content[0].text).not.toContain("art_");
101
134
  });
135
+
136
+ it("returns typed MCP error content", async () => {
137
+ const result = await createArtifactlyMcpCallResult({
138
+ name: "artifactly_list_artifacts",
139
+ credentialPath: "/tmp/does-not-exist-artifactly-credentials.json",
140
+ });
141
+
142
+ expect(result.isError).toBe(true);
143
+ expect(result.structuredContent).toMatchObject({
144
+ error: expect.stringContaining("artifactly_request_login_code"),
145
+ errorType: "Error",
146
+ });
147
+ });
102
148
  });
@@ -27,6 +27,7 @@ type McpTextContent = {
27
27
 
28
28
  export type ArtifactlyMcpCallResult = {
29
29
  content: McpTextContent[];
30
+ structuredContent?: Record<string, unknown>;
30
31
  isError?: boolean;
31
32
  };
32
33
 
@@ -53,7 +54,12 @@ export function listArtifactlyMcpServerTools() {
53
54
  }
54
55
 
55
56
  function resultToText(value: unknown): ArtifactlyMcpCallResult {
57
+ const structuredContent =
58
+ value && typeof value === "object" && !Array.isArray(value)
59
+ ? (value as Record<string, unknown>)
60
+ : undefined;
56
61
  return {
62
+ ...(structuredContent ? { structuredContent } : {}),
57
63
  content: [
58
64
  {
59
65
  type: "text",
@@ -74,6 +80,12 @@ export async function createArtifactlyMcpCallResult(input: {
74
80
  } catch (error) {
75
81
  return {
76
82
  isError: true,
83
+ structuredContent: {
84
+ error: error instanceof Error ? error.message : String(error),
85
+ errorType: error instanceof Error ? error.name : "Error",
86
+ ...(error instanceof Error && "status" in error ? { status: (error as { status?: unknown }).status } : {}),
87
+ ...(error instanceof Error && "details" in error ? { details: (error as { details?: unknown }).details } : {}),
88
+ },
77
89
  content: [
78
90
  {
79
91
  type: "text",
@@ -110,12 +122,15 @@ const listArtifactsSchema = z.object({
110
122
  });
111
123
  const getArtifactSchema = z.object({
112
124
  artifactId: z.string().min(1),
125
+ metadataOnly: z.boolean().optional(),
113
126
  });
114
127
  const updateArtifactSchema = z.object({
115
128
  artifactId: z.string().min(1),
116
129
  title: z.string().min(1).optional(),
117
130
  markdown: z.string().min(1).optional(),
118
131
  content: z.string().min(1).optional(),
132
+ filePath: z.string().min(1).optional(),
133
+ mimeType: z.string().min(1).optional(),
119
134
  description: z.string().max(500).optional(),
120
135
  visibility: z.enum(["public", "unlisted", "private", "gated"]).optional(),
121
136
  accessList: z.array(z.string().email()).optional(),
@@ -128,6 +143,7 @@ const publishFileSchema = z.object({
128
143
  title: z.string().min(1).optional(),
129
144
  mimeType: z.string().min(1).optional(),
130
145
  visibility: z.enum(["public", "unlisted", "private", "gated"]).optional(),
146
+ accessList: z.array(z.string().email()).optional(),
131
147
  });
132
148
 
133
149
  const toolSchemas = {
@@ -143,6 +159,14 @@ const toolSchemas = {
143
159
  artifactly_publish_file: publishFileSchema,
144
160
  } as const;
145
161
 
162
+ const toolAnnotations: Record<string, Record<string, boolean>> = {
163
+ artifactly_auth_status: { readOnlyHint: true },
164
+ artifactly_list_artifacts: { readOnlyHint: true },
165
+ artifactly_get_artifact: { readOnlyHint: true },
166
+ artifactly_delete_artifact: { destructiveHint: true },
167
+ artifactly_logout: { destructiveHint: true },
168
+ };
169
+
146
170
  export function createArtifactlyMcpServer() {
147
171
  const server = new McpServer({
148
172
  name: "artifactly",
@@ -157,6 +181,7 @@ export function createArtifactlyMcpServer() {
157
181
  title: tool.name,
158
182
  description: tool.description,
159
183
  inputSchema: schema,
184
+ ...(toolAnnotations[tool.name] ? { annotations: toolAnnotations[tool.name] } : {}),
160
185
  },
161
186
  async (args: Record<string, unknown>) =>
162
187
  createArtifactlyMcpCallResult({ name: tool.name, arguments: args }),
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
@@ -487,7 +487,7 @@ describe("Artifactly MCP tools", () => {
487
487
  writeCredentials(
488
488
  {
489
489
  apiBaseUrl: "https://artifactly.example",
490
- apiKey: "art_t...et",
490
+ apiKey: "art_test_secret",
491
491
  email: "agent@example.com",
492
492
  workspace: "agent",
493
493
  },
@@ -505,4 +505,138 @@ describe("Artifactly MCP tools", () => {
505
505
 
506
506
  rmSync(dir, { recursive: true, force: true });
507
507
  });
508
+
509
+ it("rejects directory paths with a friendly file error", async () => {
510
+ const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
511
+ const credentialPath = join(dir, "credentials.json");
512
+ const directoryPath = join(dir, "folder");
513
+ mkdirSync(directoryPath);
514
+ writeCredentials(
515
+ {
516
+ apiBaseUrl: "https://artifactly.example",
517
+ apiKey: "art_test_secret",
518
+ email: "agent@example.com",
519
+ workspace: "agent",
520
+ },
521
+ credentialPath,
522
+ );
523
+
524
+ await expect(
525
+ callArtifactlyMcpTool({
526
+ name: "artifactly_publish_file",
527
+ credentialPath,
528
+ arguments: { path: directoryPath },
529
+ }),
530
+ ).rejects.toThrow(/Path must point to a file/);
531
+
532
+ rmSync(dir, { recursive: true, force: true });
533
+ });
534
+
535
+ it("publishes gated image files with access lists and image kind", async () => {
536
+ const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
537
+ const credentialPath = join(dir, "credentials.json");
538
+ const pngPath = join(dir, "chart.png");
539
+ let publishedBody: unknown;
540
+ writeFileSync(pngPath, Buffer.from([0x89, 0x50, 0x4e, 0x47]));
541
+ writeCredentials(
542
+ {
543
+ apiBaseUrl: "https://artifactly.example",
544
+ apiKey: "art_test_secret",
545
+ email: "agent@example.com",
546
+ workspace: "agent",
547
+ },
548
+ credentialPath,
549
+ );
550
+
551
+ await callArtifactlyMcpTool({
552
+ name: "artifactly_publish_file",
553
+ credentialPath,
554
+ arguments: {
555
+ path: pngPath,
556
+ mimeType: "image/png",
557
+ visibility: "gated",
558
+ accessList: ["One@Example.com"],
559
+ },
560
+ postJson: async (_path, body) => {
561
+ publishedBody = body;
562
+ return { id: "artifact_1", accessList: ["one@example.com"] };
563
+ },
564
+ });
565
+
566
+ expect(publishedBody).toMatchObject({
567
+ kind: "image",
568
+ storageUrl: "data:image/png;base64,iVBORw==",
569
+ visibility: "gated",
570
+ accessList: ["One@Example.com"],
571
+ });
572
+ rmSync(dir, { recursive: true, force: true });
573
+ });
574
+
575
+ it("preserves update whitespace and rejects whitespace-only updates locally", async () => {
576
+ const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
577
+ const credentialPath = join(dir, "credentials.json");
578
+ const requests: unknown[] = [];
579
+ writeCredentials(
580
+ {
581
+ apiBaseUrl: "https://artifactly.example",
582
+ apiKey: "art_test_secret",
583
+ email: "agent@example.com",
584
+ workspace: "agent",
585
+ },
586
+ credentialPath,
587
+ );
588
+
589
+ await callArtifactlyMcpTool({
590
+ name: "artifactly_update_artifact",
591
+ credentialPath,
592
+ arguments: { artifactId: "artifact_1", markdown: " # Title\n" },
593
+ requestJson: async (_method, _path, body) => {
594
+ requests.push(body);
595
+ return { artifact: { id: "artifact_1" } };
596
+ },
597
+ });
598
+ expect(requests).toEqual([{ markdown: " # Title\n" }]);
599
+ await expect(
600
+ callArtifactlyMcpTool({
601
+ name: "artifactly_update_artifact",
602
+ credentialPath,
603
+ arguments: { artifactId: "artifact_1", content: " " },
604
+ }),
605
+ ).rejects.toThrow(/Content cannot be whitespace-only/);
606
+
607
+ rmSync(dir, { recursive: true, force: true });
608
+ });
609
+
610
+ it("replaces artifact files explicitly through update", async () => {
611
+ const dir = mkdtempSync(join(tmpdir(), "artifactly-mcp-"));
612
+ const credentialPath = join(dir, "credentials.json");
613
+ const pdfPath = join(dir, "replacement.pdf");
614
+ let bodySent: unknown;
615
+ writeFileSync(pdfPath, Buffer.from("%PDF-1.4"));
616
+ writeCredentials(
617
+ {
618
+ apiBaseUrl: "https://artifactly.example",
619
+ apiKey: "art_test_secret",
620
+ email: "agent@example.com",
621
+ workspace: "agent",
622
+ },
623
+ credentialPath,
624
+ );
625
+
626
+ await callArtifactlyMcpTool({
627
+ name: "artifactly_update_artifact",
628
+ credentialPath,
629
+ arguments: { artifactId: "artifact_1", filePath: pdfPath, mimeType: "application/pdf" },
630
+ requestJson: async (_method, _path, body) => {
631
+ bodySent = body;
632
+ return { artifact: { id: "artifact_1", kind: "file" } };
633
+ },
634
+ });
635
+
636
+ expect(bodySent).toMatchObject({
637
+ kind: "file",
638
+ storageUrl: "data:application/pdf;base64,JVBERi0xLjQ=",
639
+ });
640
+ rmSync(dir, { recursive: true, force: true });
641
+ });
508
642
  });
@@ -35,6 +35,18 @@ type RequestJson = (
35
35
  const REQUEST_TIMEOUT_MS = 30_000;
36
36
  const MAX_UPLOAD_BYTES = 10_000_000;
37
37
 
38
+ export class ArtifactlyApiError extends Error {
39
+ status: number;
40
+ details: unknown;
41
+
42
+ constructor(message: string, status: number, details: unknown) {
43
+ super(message);
44
+ this.name = "ArtifactlyApiError";
45
+ this.status = status;
46
+ this.details = details;
47
+ }
48
+ }
49
+
38
50
  function createDefaultRequestJson(apiBaseUrl: string): RequestJson {
39
51
  return async (method, path, body, options) => {
40
52
  const response = await fetch(`${apiBaseUrl}${path}`, {
@@ -55,7 +67,7 @@ function createDefaultRequestJson(apiBaseUrl: string): RequestJson {
55
67
  typeof payload.error === "string"
56
68
  ? payload.error
57
69
  : `Artifactly API request failed with ${response.status}`;
58
- throw new Error(message);
70
+ throw new ArtifactlyApiError(message, response.status, payload);
59
71
  }
60
72
  return payload;
61
73
  };
@@ -77,6 +89,14 @@ function optionalStringArg(
77
89
  return typeof value === "string" ? value.trim() : undefined;
78
90
  }
79
91
 
92
+ function optionalRawStringArg(
93
+ args: Record<string, unknown> | undefined,
94
+ key: string,
95
+ ): string | undefined {
96
+ const value = args?.[key];
97
+ return typeof value === "string" ? value : undefined;
98
+ }
99
+
80
100
  function requireStringArg(
81
101
  args: Record<string, unknown> | undefined,
82
102
  key: string,
@@ -127,6 +147,9 @@ function booleanArg(args: Record<string, unknown> | undefined, key: string): boo
127
147
  function readFileForPublish(path: string): Buffer {
128
148
  try {
129
149
  const stats = statSync(path);
150
+ if (!stats.isFile()) {
151
+ throw new Error(`Path must point to a file: ${path}`);
152
+ }
130
153
  if (stats.size > MAX_UPLOAD_BYTES) {
131
154
  throw new Error(
132
155
  `File is too large: ${path}. Maximum supported upload size is 10 MB.`,
@@ -157,6 +180,10 @@ function inferMimeType(path: string): string {
157
180
  if (extension === ".json") return "application/json";
158
181
  if (extension === ".md" || extension === ".markdown") return "text/markdown";
159
182
  if (extension === ".txt") return "text/plain";
183
+ if (extension === ".png") return "image/png";
184
+ if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
185
+ if (extension === ".gif") return "image/gif";
186
+ if (extension === ".webp") return "image/webp";
160
187
  return "application/octet-stream";
161
188
  }
162
189
 
@@ -165,6 +192,7 @@ function kindForMime(_path: string, mimeType: string) {
165
192
  if (normalized.includes("html")) return "html";
166
193
  if (normalized.includes("json")) return "json";
167
194
  if (normalized.includes("markdown")) return "markdown";
195
+ if (normalized.startsWith("image/")) return "image";
168
196
  return "file";
169
197
  }
170
198
 
@@ -250,10 +278,10 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
250
278
  },
251
279
  {
252
280
  name: "artifactly_get_artifact",
253
- description: "Get Artifactly artifact metadata when the lookup API is available.",
281
+ description: "Get an Artifactly artifact. Returns full content by default, including base64/data URL file payloads; pass metadataOnly: true to omit content and storageUrl.",
254
282
  inputSchema: {
255
283
  type: "object",
256
- properties: { artifactId: { type: "string" } },
284
+ properties: { artifactId: { type: "string" }, metadataOnly: { type: "boolean" } },
257
285
  required: ["artifactId"],
258
286
  },
259
287
  },
@@ -268,6 +296,8 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
268
296
  title: { type: "string" },
269
297
  markdown: { type: "string" },
270
298
  content: { type: "string" },
299
+ filePath: { type: "string" },
300
+ mimeType: { type: "string" },
271
301
  description: { type: "string" },
272
302
  visibility: { type: "string", enum: ["public", "unlisted", "private", "gated"] },
273
303
  accessList: { type: "array", items: { type: "string" } },
@@ -295,6 +325,7 @@ export function listArtifactlyMcpTools(): ArtifactlyMcpTool[] {
295
325
  title: { type: "string" },
296
326
  mimeType: { type: "string" },
297
327
  visibility: { type: "string", enum: ["public", "unlisted", "private", "gated"] },
328
+ accessList: { type: "array", items: { type: "string" } },
298
329
  },
299
330
  required: ["path"],
300
331
  },
@@ -416,7 +447,15 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
416
447
  throw new Error(LOGIN_GUIDANCE);
417
448
  }
418
449
  const artifactId = requireStringArg(input.arguments, "artifactId");
419
- return objectResult(await requestJson("GET", `/api/artifacts/${artifactId}`, undefined, { apiKey: credentials.apiKey }));
450
+ const metadataOnly = booleanArg(input.arguments, "metadataOnly");
451
+ const result = objectResult(await requestJson("GET", `/api/artifacts/${artifactId}`, undefined, { apiKey: credentials.apiKey }));
452
+ if (metadataOnly && result.artifact && typeof result.artifact === "object" && !Array.isArray(result.artifact)) {
453
+ const { content: _content, storageUrl: _storageUrl, ...metadata } = result.artifact as Record<string, unknown>;
454
+ void _content;
455
+ void _storageUrl;
456
+ return { ...result, artifact: metadata };
457
+ }
458
+ return result;
420
459
  }
421
460
 
422
461
  if (input.name === "artifactly_update_artifact") {
@@ -425,22 +464,41 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
425
464
  }
426
465
  const artifactId = requireStringArg(input.arguments, "artifactId");
427
466
  const title = stringArg(input.arguments, "title");
428
- const markdown = optionalStringArg(input.arguments, "markdown");
429
- const content = optionalStringArg(input.arguments, "content");
467
+ const markdown = optionalRawStringArg(input.arguments, "markdown");
468
+ const content = optionalRawStringArg(input.arguments, "content");
469
+ const filePath = stringArg(input.arguments, "filePath");
430
470
  if (markdown !== undefined && content !== undefined) {
431
471
  throw new Error("Use either markdown or content, not both.");
432
472
  }
473
+ if ((markdown !== undefined || content !== undefined) && filePath) {
474
+ throw new Error("Use either filePath or text content, not both.");
475
+ }
476
+ const textContent = markdown ?? content;
477
+ if (textContent !== undefined && !textContent.trim()) {
478
+ throw new Error("Content cannot be whitespace-only.");
479
+ }
433
480
  const description = optionalStringArg(input.arguments, "description");
434
481
  const visibility = stringArg(input.arguments, "visibility");
435
482
  const accessList = stringArrayArg(input.arguments, "accessList");
483
+ let replacement: Record<string, unknown> = {};
484
+ if (filePath) {
485
+ const mimeType = stringArg(input.arguments, "mimeType") ?? inferMimeType(filePath);
486
+ const kind = kindForMime(filePath, mimeType);
487
+ const file = readFileForPublish(filePath);
488
+ replacement = {
489
+ kind,
490
+ storageUrl: `data:${mimeType};base64,${file.toString("base64")}`,
491
+ };
492
+ }
436
493
  return objectResult(
437
494
  await requestJson(
438
495
  "PATCH",
439
496
  `/api/artifacts/${artifactId}`,
440
497
  {
441
498
  ...(title ? { title } : {}),
442
- ...(markdown ? { markdown } : {}),
443
- ...(content ? { content } : {}),
499
+ ...(markdown !== undefined ? { markdown } : {}),
500
+ ...(content !== undefined ? { content } : {}),
501
+ ...replacement,
444
502
  ...(description !== undefined ? { description } : {}),
445
503
  ...(visibility ? { visibility } : {}),
446
504
  ...(accessList ? { accessList } : {}),
@@ -466,21 +524,25 @@ export async function callArtifactlyMcpTool(input: CallToolInput) {
466
524
  const title = stringArg(input.arguments, "title") ?? titleFromPath(path);
467
525
  const mimeType = stringArg(input.arguments, "mimeType") ?? inferMimeType(path);
468
526
  const visibility = stringArg(input.arguments, "visibility") ?? "unlisted";
527
+ const accessList = stringArrayArg(input.arguments, "accessList");
469
528
  const kind = kindForMime(path, mimeType);
470
529
  const file = readFileForPublish(path);
530
+ const fileLike = kind === "file" || kind === "image";
471
531
  const payload =
472
- kind === "file"
532
+ fileLike
473
533
  ? {
474
534
  title,
475
535
  kind,
476
536
  storageUrl: `data:${mimeType};base64,${file.toString("base64")}`,
477
537
  visibility,
538
+ ...(accessList ? { accessList } : {}),
478
539
  }
479
540
  : {
480
541
  title,
481
542
  kind,
482
543
  content: file.toString("utf8"),
483
544
  visibility,
545
+ ...(accessList ? { accessList } : {}),
484
546
  };
485
547
  return objectResult(
486
548
  await postJson("/api/artifacts", payload, { apiKey: credentials.apiKey }),