@yargote/artifacts-cli 0.1.0-beta.1 → 0.1.0-beta.11

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.
Files changed (32) hide show
  1. package/README.md +167 -19
  2. package/package.json +9 -4
  3. package/skills/artifacts-cli/SKILL.md +80 -0
  4. package/skills/artifacts-cli/references/commands.md +250 -0
  5. package/skills/artifacts-cli/references/verify-retained.md +119 -0
  6. package/skills/artifacts-cli/scripts/prepare-retained.mjs +149 -0
  7. package/skills/artifacts-design/SKILL.md +92 -0
  8. package/skills/artifacts-design/THIRD-PARTY-NOTICES.md +155 -0
  9. package/skills/artifacts-design/fonts/OFL.txt +96 -0
  10. package/skills/artifacts-design/fonts/bricolage-grotesque.woff2 +0 -0
  11. package/skills/artifacts-design/fonts/jetbrains-mono.woff2 +0 -0
  12. package/skills/artifacts-design/fonts/newsreader-italic.woff2 +0 -0
  13. package/skills/artifacts-design/fonts/newsreader.woff2 +0 -0
  14. package/skills/artifacts-design/fonts/public-sans-italic.woff2 +0 -0
  15. package/skills/artifacts-design/fonts/public-sans.woff2 +0 -0
  16. package/skills/artifacts-design/references/canvas.md +100 -0
  17. package/skills/artifacts-design/references/catppuccin.md +38 -0
  18. package/skills/artifacts-design/references/interface-checks.md +60 -0
  19. package/skills/artifacts-design/references/local-checks.md +70 -0
  20. package/skills/artifacts-design/references/visual-explanations.md +13 -0
  21. package/skills/artifacts-design/scripts/embed-fonts.mjs +246 -0
  22. package/skills/artifacts-design/scripts/verify-local.mjs +333 -0
  23. package/skills/artifacts-design/templates/starter.html +170 -0
  24. package/src/auth.d.ts +4 -1
  25. package/src/auth.js +184 -45
  26. package/src/credentials.d.ts +2 -0
  27. package/src/credentials.js +22 -0
  28. package/src/index.js +153 -31
  29. package/src/operations.d.ts +155 -38
  30. package/src/operations.js +11 -1
  31. package/src/skills.d.ts +9 -0
  32. package/src/skills.js +181 -0
package/src/index.js CHANGED
@@ -6,56 +6,120 @@ import { homedir } from 'node:os';
6
6
  import { join, resolve } from 'node:path';
7
7
  import { parseArgs } from 'node:util';
8
8
  import { z } from 'zod';
9
- import { createSchema, publishSchema, updateSchema } from '@yargote/artifacts-contracts';
9
+ import { createSchema, listSharesSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
10
10
  import { canonicalResource } from '@yargote/artifacts-contracts/http';
11
- import { createArtifactHttpClient, HttpError, prepareArtifactEdit } from '@yargote/artifacts-client';
11
+ import { BatchOperationError, createArtifactHttpClient, HttpError, prepareArtifactEdit, prepareBatch } from '@yargote/artifacts-client';
12
12
  import { accessToken, login, logout } from "./auth.js";
13
+ import { CredentialStore } from "./credentials.js";
13
14
  import { loadOperation, retainOperation, submitOperation } from "./operations.js";
15
+ import { runSkillsCommand } from "./skills.js";
14
16
  const help = `Usage: artifacts COMMAND [OPTIONS]
15
17
 
16
18
  Global options:
17
- --server RESOURCE Canonical OAuth resource, or ARTIFACTS_SERVER
19
+ --server RESOURCE Exact REST OAuth resource, or ARTIFACTS_SERVER
20
+ Defaults to the last successful login
18
21
  --state-dir DIR Private local state, or ARTIFACTS_STATE_DIR
19
22
  Default: ~/.local/state/artifacts
20
23
  --help Print this reference
21
24
  --version Print the installed package version
22
25
 
23
26
  Commands:
27
+ skills [list]
28
+ skills get NAME [--full]
29
+ skills get --all [--full]
30
+ skills path [NAME]
31
+ skills install [NAME] [--agent claude] [--global] [--force]
24
32
  login [--no-browser]
25
33
  logout
26
34
  whoami
27
- create --snapshot FILE [--request-id ID] [--response full|compact]
35
+ create --snapshot FILE [--project NAME] [--slug SLUG] [--request-id ID]
36
+ [--response full|compact]
28
37
  create --command FILE
29
- list [--limit N] [--cursor CURSOR]
30
- read ID [--revision REV] [--response full|compact] [--out FILE]
38
+ list [--limit N] [--cursor CURSOR] [--project NAME]
39
+ projects
40
+ search QUERY [--project NAME] [--limit N] [--cursor CURSOR]
41
+ read ID [--revision REV] [--response full|compact] [--out FILE [--quiet]]
42
+ read --slug SLUG [--project NAME] [--revision REV] [--response full|compact]
43
+ [--out FILE [--quiet]]
31
44
  update ID --snapshot FILE --expected-revision REV [--request-id ID]
32
45
  [--response full|compact]
33
46
  update [ID] --command FILE
34
47
  edit ID --changes FILE [--request-id ID]
48
+ batch --command FILE
35
49
  history ID [--limit N] [--before-sequence N] [--summary]
36
50
  publish ID --revision REV --expected-version N [--request-id ID]
37
51
  publish [ID] --command FILE
38
52
  export ID --revision REV --out FILE
53
+ move ID [--project NAME | --no-project] [--slug SLUG | --no-slug]
54
+ [--request-id ID]
55
+ share ID --email EMAIL [--notify] [--request-id ID]
56
+ shares ID
57
+ unshare ID SHARE_ID [--request-id ID]
39
58
  retry REQUEST_ID
40
59
 
41
- Mutation command files contain the complete create, update, or publish JSON.
60
+ Mutation command files contain the complete create, update, publish, or batch
61
+ JSON.
42
62
  Mutation request IDs default to a random UUID. Commands are retained before
43
63
  submission. Retry sends the exact retained command for the same server and owner.
44
- Read --out saves the complete artifact JSON. Export --out saves the ZIP.
64
+ Projects group artifacts by name without regard to letter case. A project
65
+ exists while it contains an artifact. Move changes the project or the slug of
66
+ an artifact without creating a revision. It needs at least one of its four
67
+ options. --no-project removes the artifact from its project, and --no-slug
68
+ clears its slug. An option left out keeps that value, so a move to another
69
+ project keeps the slug.
70
+ A slug such as concepts/rag names an artifact within its project, or within
71
+ the unfiled artifacts without --project. Read --slug finds it without its ID.
72
+ Create or move fails with slug_conflict when the project already uses the slug.
73
+ Search finds owned artifacts whose head contains every term of QUERY, ignoring
74
+ letter case and accents. It reads Markdown and text content, the visible text
75
+ of HTML, and the title and description of every kind. Quote a QUERY with
76
+ spaces. Results are ordered by relevance with a **bold** snippet.
77
+ Batch applies 1 to 25 create, update, edit, and move operations from one command
78
+ file with a requestId and operations. All succeed or none do. A failure names the
79
+ 0-based index of its operation. An update addresses its artifact by artifactId or
80
+ by project and slug, including a page created earlier in the batch, and omits
81
+ expectedRevisionId only for such a page. An edit takes the same address and the
82
+ changes of edit --changes for a page that exists before the batch. The CLI reads
83
+ its head and sends an update with that expectedRevisionId, so a page changed
84
+ meanwhile, or written earlier in the batch, fails with conflict; use update for
85
+ such a page. Retry sends the prepared batch. A move addresses its artifact by
86
+ artifactId or by from { project, slug } and takes the move project and slug.
87
+ Later operations address a moved page by its new slug. A batch never publishes.
88
+ Read --out saves the returned artifact JSON. Export --out saves the ZIP.
89
+ Read --quiet requires --out and suppresses successful JSON stdout.
45
90
  Output files are created exclusively and never overwrite an existing path.
46
91
  JSON results go to stdout. Diagnostics and recovery guidance go to stderr.
92
+ Login accepts an MCP URL when its metadata advertises artifacts_rest_resource.
93
+ Split MCP and REST resources require this relation. Login remembers the verified
94
+ REST URL for later commands. --server takes precedence over ARTIFACTS_SERVER,
95
+ which takes precedence over the saved URL. Ordinary overrides do not replace
96
+ the saved URL. Logout keeps it for a later login.
97
+ Login checks <resource>/v1/identity before browser
98
+ authorization. Legacy /mcp
99
+ resources work when their REST API is at /mcp/v1. If the check fails, confirm
100
+ the configured REST URL with your service administrator.
101
+ Login errors report the phase, method, endpoint, and HTTP status when received.
102
+ Network failures have no HTTP status. Login verifies identity again after OAuth.
47
103
  ARTIFACTS_ACCESS_TOKEN supplies a token for automation without local login.
104
+ Skills commands work offline without a server, login, or private state.
105
+ They print plain text. Global server and state options are accepted and ignored.
106
+ Skills install defaults to artifacts-cli under .agents/skills in the current
107
+ directory. --agent claude uses .claude/skills. --global uses your home directory.
108
+ The installed discovery file loads instructions through artifacts skills get.
109
+ Identical files stay unchanged. Differing files require --force.
48
110
  `;
49
111
  class UsageError extends Error {
50
112
  }
51
113
  const commandOptions = {
52
114
  login: ['no-browser'], logout: [], whoami: [],
53
- create: ['snapshot', 'request-id', 'response', 'command'],
54
- list: ['limit', 'cursor'], read: ['revision', 'response', 'out'],
115
+ create: ['snapshot', 'project', 'slug', 'request-id', 'response', 'command'],
116
+ list: ['limit', 'cursor', 'project'], projects: [], search: ['project', 'limit', 'cursor'], read: ['slug', 'project', 'revision', 'response', 'out', 'quiet'],
55
117
  update: ['snapshot', 'expected-revision', 'request-id', 'response', 'command'],
56
- edit: ['changes', 'request-id'], history: ['limit', 'before-sequence', 'summary'],
118
+ edit: ['changes', 'request-id'], batch: ['command'], history: ['limit', 'before-sequence', 'summary'],
57
119
  publish: ['revision', 'expected-version', 'request-id', 'command'],
58
- export: ['revision', 'out'], retry: [],
120
+ share: ['email', 'notify', 'request-id'], shares: [], unshare: ['request-id'],
121
+ export: ['revision', 'out'], move: ['project', 'no-project', 'slug', 'no-slug', 'request-id'], retry: [],
122
+ skills: ['full', 'all', 'agent', 'global', 'force'],
59
123
  };
60
124
  async function jsonFile(path) {
61
125
  const file = await open(path, constants.O_RDONLY);
@@ -88,7 +152,10 @@ async function main() {
88
152
  changes: { type: 'string' }, 'request-id': { type: 'string' }, response: { type: 'string' },
89
153
  revision: { type: 'string' }, 'expected-revision': { type: 'string' }, 'expected-version': { type: 'string' },
90
154
  limit: { type: 'string' }, cursor: { type: 'string' }, 'before-sequence': { type: 'string' },
91
- summary: { type: 'boolean' }, out: { type: 'string' },
155
+ summary: { type: 'boolean' }, out: { type: 'string' }, quiet: { type: 'boolean' },
156
+ full: { type: 'boolean' }, all: { type: 'boolean' }, agent: { type: 'string' },
157
+ global: { type: 'boolean' }, force: { type: 'boolean' },
158
+ project: { type: 'string' }, 'no-project': { type: 'boolean' }, slug: { type: 'string' }, 'no-slug': { type: 'boolean' }, email: { type: 'string' }, notify: { type: 'boolean' },
92
159
  },
93
160
  });
94
161
  const seen = new Set();
@@ -114,11 +181,22 @@ async function main() {
114
181
  for (const option of seen)
115
182
  if (!['server', 'state-dir', 'help', ...commandOptions[command]].includes(option))
116
183
  throw new UsageError(`Option --${option} is not supported by ${command}`);
117
- const resourceValue = values.server ?? process.env.ARTIFACTS_SERVER;
118
- if (!resourceValue)
119
- throw new UsageError('Specify --server RESOURCE or ARTIFACTS_SERVER. No production server is selected by default.');
120
- const resource = canonicalResource(resourceValue);
184
+ if (command === 'skills')
185
+ return runSkillsCommand(arguments_, values, seen);
121
186
  const stateDir = resolve(values['state-dir'] ?? process.env.ARTIFACTS_STATE_DIR ?? join(homedir(), '.local', 'state', 'artifacts'));
187
+ let resourceValue = values.server ?? process.env.ARTIFACTS_SERVER;
188
+ if (resourceValue === undefined) {
189
+ const store = await CredentialStore.open(stateDir);
190
+ try {
191
+ resourceValue = await store.exclusive(() => store.readSelectedResource());
192
+ }
193
+ finally {
194
+ store.close();
195
+ }
196
+ }
197
+ if (resourceValue === undefined)
198
+ throw new UsageError('No server is saved. Run artifacts login --server RESOURCE or set ARTIFACTS_SERVER.');
199
+ const resource = canonicalResource(resourceValue);
122
200
  function argument(required = true) {
123
201
  if (arguments_.length > 1 || (required && arguments_.length !== 1))
124
202
  throw new UsageError(`${command} requires ${required ? 'one' : 'zero or one'} positional argument`);
@@ -142,9 +220,17 @@ async function main() {
142
220
  throw new UsageError(`--${name} requires a nonnegative safe integer`);
143
221
  return Number(value);
144
222
  }
223
+ function cleared(name, value, clear) {
224
+ if (value !== undefined && clear)
225
+ throw new UsageError(`Specify at most one of --${name} or --no-${name}`);
226
+ return clear ? null : value;
227
+ }
145
228
  const response = values.response;
146
229
  if (response !== undefined && response !== 'full' && response !== 'compact')
147
230
  throw new UsageError('--response requires full or compact');
231
+ const quiet = values.quiet ?? false;
232
+ if (quiet)
233
+ required('out');
148
234
  let commandToken;
149
235
  const client = createArtifactHttpClient({ resource, accessToken: () => commandToken ??= accessToken({ resource, stateDir }) });
150
236
  async function mutate(mutation) {
@@ -158,9 +244,9 @@ async function main() {
158
244
  if (extra.length)
159
245
  throw new UsageError('--command cannot be combined with mutation options');
160
246
  const value = await jsonFile(required('command'));
161
- if (kind === 'create') {
247
+ if (kind === 'create' || kind === 'batch') {
162
248
  none();
163
- return { kind, command: createSchema.parse(value) };
249
+ return kind === 'create' ? { kind, command: createSchema.parse(value) } : { kind, command: await prepareBatch(client, value) };
164
250
  }
165
251
  const mutation = kind === 'update' ? { kind, command: updateSchema.parse(value) } : { kind, command: publishSchema.parse(value) };
166
252
  const id = argument(false);
@@ -171,7 +257,12 @@ async function main() {
171
257
  switch (command) {
172
258
  case 'login':
173
259
  none();
174
- return login({ resource, stateDir, noBrowser: values['no-browser'] ?? false, onAuthorizationUrl: (url) => { process.stderr.write(`Authorize this login in your browser:\n${url}\n`); } });
260
+ {
261
+ const result = await login({ resource, stateDir, noBrowser: values['no-browser'] ?? false, onAuthorizationUrl: (url) => { process.stderr.write(`Authorize this login in your browser:\n${url}\n`); } });
262
+ if (result.resource !== resource)
263
+ process.stderr.write(`REST resource: ${result.resource}\nThis verified REST URL is saved for subsequent commands.\n`);
264
+ return result.identity;
265
+ }
175
266
  case 'logout':
176
267
  none();
177
268
  return logout({ resource, stateDir });
@@ -182,16 +273,25 @@ async function main() {
182
273
  if (values.command)
183
274
  return mutate(await commandFile('create'));
184
275
  none();
185
- return mutate({ kind: 'create', command: createSchema.parse({ requestId: values['request-id'] ?? randomUUID(), snapshot: await jsonFile(required('snapshot')), ...(response === undefined ? {} : { response }) }) });
276
+ return mutate({ kind: 'create', command: createSchema.parse({ requestId: values['request-id'] ?? randomUUID(), snapshot: await jsonFile(required('snapshot')), ...(values.project === undefined ? {} : { project: values.project }), ...(values.slug === undefined ? {} : { slug: values.slug }), ...(response === undefined ? {} : { response }) }) });
186
277
  }
187
278
  case 'list':
188
279
  none();
189
- return client.list({ ...(values.limit === undefined ? {} : { limit: integer('limit') }), ...(values.cursor === undefined ? {} : { cursor: values.cursor }) });
280
+ return client.list({ ...(values.limit === undefined ? {} : { limit: integer('limit') }), ...(values.cursor === undefined ? {} : { cursor: values.cursor }), ...(values.project === undefined ? {} : { project: values.project }) });
281
+ case 'projects':
282
+ none();
283
+ return client.projects();
284
+ case 'search': return client.search({ query: argument(), ...(values.project === undefined ? {} : { project: values.project }), ...(values.limit === undefined ? {} : { limit: integer('limit') }), ...(values.cursor === undefined ? {} : { cursor: values.cursor }) });
190
285
  case 'read': {
191
- const result = await client.read({ artifactId: argument(), ...(values.revision === undefined ? {} : { revisionId: values.revision }), ...(response === undefined ? {} : { response }) });
286
+ if (values.slug === undefined && values.project !== undefined)
287
+ throw new UsageError('read --project requires --slug');
288
+ if ((values.slug === undefined) !== (arguments_.length === 1) || arguments_.length > 1)
289
+ throw new UsageError('read requires exactly one of an artifact ID or --slug SLUG');
290
+ const target = values.slug === undefined ? { artifactId: arguments_[0] } : { slug: values.slug, ...(values.project === undefined ? {} : { project: values.project }) };
291
+ const result = await client.read({ ...target, ...(values.revision === undefined ? {} : { revisionId: values.revision }), ...(response === undefined ? {} : { response }) });
192
292
  if (values.out)
193
293
  await writeOutput(values.out, `${JSON.stringify(result, null, 2)}\n`);
194
- return result;
294
+ return quiet ? undefined : result;
195
295
  }
196
296
  case 'update': {
197
297
  if (values.command)
@@ -204,6 +304,7 @@ async function main() {
204
304
  const input = await prepareArtifactEdit(client, { artifactId, requestId: values['request-id'] ?? randomUUID(), changes: changes });
205
305
  return mutate({ kind: 'update', command: updateSchema.parse(input) });
206
306
  }
307
+ case 'batch': return mutate(await commandFile('batch'));
207
308
  case 'history': return client.history({ artifactId: argument(), ...(values.limit === undefined ? {} : { limit: integer('limit') }), ...(values['before-sequence'] === undefined ? {} : { beforeSequence: integer('before-sequence') }), ...(values.summary ? { response: 'summary' } : {}) });
208
309
  case 'publish': {
209
310
  if (values.command)
@@ -217,26 +318,47 @@ async function main() {
217
318
  await writeOutput(output, archive.bytes);
218
319
  return { filename: archive.filename, path: resolve(output), mediaType: archive.mediaType, sha256: archive.sha256, bytes: archive.bytes.byteLength };
219
320
  }
321
+ case 'move': {
322
+ const artifactId = argument();
323
+ const project = cleared('project', values.project, values['no-project']);
324
+ const slug = cleared('slug', values.slug, values['no-slug']);
325
+ if (project === undefined && slug === undefined)
326
+ throw new UsageError('move requires --project NAME, --no-project, --slug SLUG, or --no-slug');
327
+ return mutate({ kind: 'move', command: moveSchema.parse({ artifactId, requestId: values['request-id'] ?? randomUUID(), ...(project === undefined ? {} : { project }), ...(slug === undefined ? {} : { slug }) }) });
328
+ }
329
+ case 'share': return mutate({ kind: 'share', command: shareSchema.parse({ artifactId: argument(), requestId: values['request-id'] ?? randomUUID(), email: required('email'), ...(values.notify === undefined ? {} : { notify: values.notify }) }) });
330
+ case 'shares': return client.listShares(listSharesSchema.parse({ artifactId: argument() }));
331
+ case 'unshare': {
332
+ if (arguments_.length !== 2)
333
+ throw new UsageError('unshare requires two positional arguments');
334
+ return mutate({ kind: 'unshare', command: unshareSchema.parse({ artifactId: arguments_[0], shareId: arguments_[1], requestId: values['request-id'] ?? randomUUID() }) });
335
+ }
220
336
  case 'retry': {
221
337
  const requestId = argument();
222
338
  const owner = await client.identity();
223
339
  const operation = await loadOperation(stateDir, requestId, resource, owner.userId);
224
- process.stderr.write(`Replaying retained ${operation.kind} operation ${requestId}. The result is its retained receipt and may differ from current state.\n`);
340
+ process.stderr.write(`Replaying retained ${operation.kind} operation ${requestId}. Replay confirms the original operation and does not establish current state.\n`);
225
341
  return submitOperation(client, operation);
226
342
  }
227
343
  }
228
344
  }
345
+ function errorBody(error) {
346
+ if (error instanceof BatchOperationError) {
347
+ const body = errorBody(error.cause);
348
+ return { ...body, details: { ...body.details, index: error.index } };
349
+ }
350
+ return error instanceof HttpError
351
+ ? { code: error.code, message: error.message, status: error.status, details: error.details }
352
+ : error instanceof z.ZodError
353
+ ? { code: 'invalid_input', message: 'Input does not match the artifact command schema', details: { issues: error.issues } }
354
+ : { code: error instanceof UsageError || error instanceof TypeError || error instanceof SyntaxError ? 'invalid_arguments' : 'local_error', message: error instanceof Error ? error.message : 'Command failed', details: {} };
355
+ }
229
356
  try {
230
357
  const result = await main();
231
358
  if (result !== undefined)
232
359
  process.stdout.write(`${JSON.stringify(result)}\n`);
233
360
  }
234
361
  catch (error) {
235
- const body = error instanceof HttpError
236
- ? { code: error.code, message: error.message, status: error.status, details: error.details }
237
- : error instanceof z.ZodError
238
- ? { code: 'invalid_input', message: 'Input does not match the artifact command schema', details: { issues: error.issues } }
239
- : { code: error instanceof UsageError || error instanceof TypeError || error instanceof SyntaxError ? 'invalid_arguments' : 'local_error', message: error instanceof Error ? error.message : 'Command failed', details: {} };
240
- process.stderr.write(`${JSON.stringify({ error: body })}\n`);
362
+ process.stderr.write(`${JSON.stringify({ error: errorBody(error) })}\n`);
241
363
  process.exitCode = 1;
242
364
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createSchema, publishSchema, updateSchema } from '@yargote/artifacts-contracts';
2
+ import { batchSchema, createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
3
3
  import type { ArtifactHttpClient } from '@yargote/artifacts-client';
4
4
  export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5
5
  kind: z.ZodLiteral<"create">;
@@ -16,6 +16,7 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
16
16
  text: "text";
17
17
  html: "html";
18
18
  markdown: "markdown";
19
+ design: "design";
19
20
  }>;
20
21
  text: z.ZodString;
21
22
  }, z.core.$strict>;
@@ -29,6 +30,8 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
29
30
  data: z.ZodString;
30
31
  }, z.core.$strict>>;
31
32
  }, z.core.$strict>;
33
+ project: z.ZodOptional<z.ZodString>;
34
+ slug: z.ZodOptional<z.ZodString>;
32
35
  response: z.ZodOptional<z.ZodEnum<{
33
36
  full: "full";
34
37
  compact: "compact";
@@ -54,6 +57,7 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
54
57
  text: "text";
55
58
  html: "html";
56
59
  markdown: "markdown";
60
+ design: "design";
57
61
  }>;
58
62
  text: z.ZodString;
59
63
  }, z.core.$strict>;
@@ -86,6 +90,117 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
86
90
  version: z.ZodLiteral<1>;
87
91
  resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
88
92
  ownerId: z.ZodString;
93
+ }, z.core.$strict>, z.ZodObject<{
94
+ kind: z.ZodLiteral<"move">;
95
+ command: z.ZodObject<{
96
+ requestId: z.ZodString;
97
+ artifactId: z.ZodString;
98
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
99
+ slug: z.ZodOptional<z.ZodNullable<z.ZodString>>;
100
+ }, z.core.$strict>;
101
+ version: z.ZodLiteral<1>;
102
+ resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
103
+ ownerId: z.ZodString;
104
+ }, z.core.$strict>, z.ZodObject<{
105
+ kind: z.ZodLiteral<"share">;
106
+ command: z.ZodObject<{
107
+ requestId: z.ZodString;
108
+ artifactId: z.ZodString;
109
+ email: z.ZodPipe<z.ZodString, z.ZodEmail>;
110
+ notify: z.ZodOptional<z.ZodBoolean>;
111
+ requireNew: z.ZodOptional<z.ZodLiteral<true>>;
112
+ }, z.core.$strict>;
113
+ version: z.ZodLiteral<1>;
114
+ resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
115
+ ownerId: z.ZodString;
116
+ }, z.core.$strict>, z.ZodObject<{
117
+ kind: z.ZodLiteral<"unshare">;
118
+ command: z.ZodObject<{
119
+ requestId: z.ZodString;
120
+ artifactId: z.ZodString;
121
+ shareId: z.ZodString;
122
+ }, z.core.$strict>;
123
+ version: z.ZodLiteral<1>;
124
+ resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
125
+ ownerId: z.ZodString;
126
+ }, z.core.$strict>, z.ZodObject<{
127
+ kind: z.ZodLiteral<"batch">;
128
+ command: z.ZodObject<{
129
+ requestId: z.ZodString;
130
+ operations: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
131
+ op: z.ZodLiteral<"create">;
132
+ snapshot: z.ZodObject<{
133
+ metadata: z.ZodObject<{
134
+ title: z.ZodString;
135
+ description: z.ZodString;
136
+ custom: z.ZodOptional<z.ZodJSONSchema>;
137
+ }, z.core.$strict>;
138
+ content: z.ZodObject<{
139
+ kind: z.ZodEnum<{
140
+ text: "text";
141
+ html: "html";
142
+ markdown: "markdown";
143
+ design: "design";
144
+ }>;
145
+ text: z.ZodString;
146
+ }, z.core.$strict>;
147
+ files: z.ZodArray<z.ZodObject<{
148
+ path: z.ZodString;
149
+ mediaType: z.ZodString;
150
+ encoding: z.ZodEnum<{
151
+ utf8: "utf8";
152
+ base64: "base64";
153
+ }>;
154
+ data: z.ZodString;
155
+ }, z.core.$strict>>;
156
+ }, z.core.$strict>;
157
+ project: z.ZodOptional<z.ZodString>;
158
+ slug: z.ZodOptional<z.ZodString>;
159
+ }, z.core.$strict>, z.ZodObject<{
160
+ expectedRevisionId: z.ZodOptional<z.ZodString>;
161
+ snapshot: z.ZodObject<{
162
+ metadata: z.ZodObject<{
163
+ title: z.ZodString;
164
+ description: z.ZodString;
165
+ custom: z.ZodOptional<z.ZodJSONSchema>;
166
+ }, z.core.$strict>;
167
+ content: z.ZodObject<{
168
+ kind: z.ZodEnum<{
169
+ text: "text";
170
+ html: "html";
171
+ markdown: "markdown";
172
+ design: "design";
173
+ }>;
174
+ text: z.ZodString;
175
+ }, z.core.$strict>;
176
+ files: z.ZodArray<z.ZodObject<{
177
+ path: z.ZodString;
178
+ mediaType: z.ZodString;
179
+ encoding: z.ZodEnum<{
180
+ utf8: "utf8";
181
+ base64: "base64";
182
+ }>;
183
+ data: z.ZodString;
184
+ }, z.core.$strict>>;
185
+ }, z.core.$strict>;
186
+ artifactId: z.ZodOptional<z.ZodString>;
187
+ project: z.ZodOptional<z.ZodString>;
188
+ slug: z.ZodOptional<z.ZodString>;
189
+ op: z.ZodLiteral<"update">;
190
+ }, z.core.$strict>, z.ZodObject<{
191
+ op: z.ZodLiteral<"move">;
192
+ artifactId: z.ZodOptional<z.ZodString>;
193
+ from: z.ZodOptional<z.ZodObject<{
194
+ project: z.ZodOptional<z.ZodString>;
195
+ slug: z.ZodString;
196
+ }, z.core.$strict>>;
197
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
198
+ slug: z.ZodOptional<z.ZodNullable<z.ZodString>>;
199
+ }, z.core.$strict>], "op">>;
200
+ }, z.core.$strict>;
201
+ version: z.ZodLiteral<1>;
202
+ resource: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
203
+ ownerId: z.ZodString;
89
204
  }, z.core.$strict>], "kind">;
90
205
  export type PreparedOperation = z.infer<typeof operationSchema>;
91
206
  export type Mutation = {
@@ -97,48 +212,50 @@ export type Mutation = {
97
212
  } | {
98
213
  kind: 'publish';
99
214
  command: z.input<typeof publishSchema>;
215
+ } | {
216
+ kind: 'move';
217
+ command: z.input<typeof moveSchema>;
218
+ } | {
219
+ kind: 'share';
220
+ command: z.input<typeof shareSchema>;
221
+ } | {
222
+ kind: 'unshare';
223
+ command: z.input<typeof unshareSchema>;
224
+ } | {
225
+ kind: 'batch';
226
+ command: z.input<typeof batchSchema>;
100
227
  };
101
228
  export declare function operationPath(stateDir: string, resource: string, requestId: string): string;
102
229
  export declare function retainOperation(stateDir: string, resource: string, ownerId: string, mutation: Mutation): Promise<PreparedOperation>;
103
230
  export declare function loadOperation(stateDir: string, requestId: string, resource: string, ownerId: string): Promise<PreparedOperation>;
104
- export declare function submitOperation(client: ArtifactHttpClient, operation: PreparedOperation): Promise<{
231
+ export declare function submitOperation(client: ArtifactHttpClient, operation: PreparedOperation): Promise<void> | Promise<{
232
+ results: {
233
+ index: number;
234
+ artifactId: string;
235
+ revisionId: string;
236
+ }[];
237
+ }> | Promise<{
238
+ shareId: string;
239
+ artifactId: string;
240
+ kind: "email";
241
+ value: string;
242
+ createdAt: string;
243
+ notification?: {
244
+ status: "requested";
245
+ } | {
246
+ status: "skipped";
247
+ reason: "disabled";
248
+ } | {
249
+ status: "skipped";
250
+ reason: "rate_limited";
251
+ retryAt: string;
252
+ } | undefined;
253
+ }> | Promise<{
254
+ artifactId: string;
255
+ project: string | null;
256
+ }> | Promise<{
105
257
  revisionId: string;
106
258
  version: number;
107
259
  url: string;
108
260
  artifactId: string;
109
- }> | Promise<{
110
- artifactId: string;
111
- ownerId: string;
112
- headRevisionId: string;
113
- publication: {
114
- revisionId: string;
115
- version: number;
116
- url: string;
117
- } | null;
118
- revision: {
119
- id: string;
120
- parentRevisionId: string | null;
121
- sequence: number;
122
- authorUserId: string;
123
- authorClientId: string;
124
- createdAt: string;
125
- sha256: string;
126
- snapshot: {
127
- metadata: {
128
- title: string;
129
- description: string;
130
- custom?: z.core.util.JSONType | undefined;
131
- };
132
- content: {
133
- kind: "text" | "html" | "markdown";
134
- text: string;
135
- };
136
- files: {
137
- path: string;
138
- mediaType: string;
139
- encoding: "utf8" | "base64";
140
- data: string;
141
- }[];
142
- };
143
- };
144
- } | import("@yargote/artifacts-client").CompactArtifactRead>;
261
+ }>;
package/src/operations.js CHANGED
@@ -3,7 +3,7 @@ import { lstat, mkdir, open } from 'node:fs/promises';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
5
  import { z } from 'zod';
6
- import { createSchema, publishSchema, updateSchema } from '@yargote/artifacts-contracts';
6
+ import { batchSchema, createSchema, moveSchema, publishSchema, shareSchema, unshareSchema, updateSchema } from '@yargote/artifacts-contracts';
7
7
  import { canonicalResource } from '@yargote/artifacts-contracts/http';
8
8
  const binding = {
9
9
  version: z.literal(1),
@@ -14,6 +14,10 @@ export const operationSchema = z.discriminatedUnion('kind', [
14
14
  z.strictObject({ ...binding, kind: z.literal('create'), command: createSchema }),
15
15
  z.strictObject({ ...binding, kind: z.literal('update'), command: updateSchema }),
16
16
  z.strictObject({ ...binding, kind: z.literal('publish'), command: publishSchema }),
17
+ z.strictObject({ ...binding, kind: z.literal('move'), command: moveSchema }),
18
+ z.strictObject({ ...binding, kind: z.literal('share'), command: shareSchema }),
19
+ z.strictObject({ ...binding, kind: z.literal('unshare'), command: unshareSchema }),
20
+ z.strictObject({ ...binding, kind: z.literal('batch'), command: batchSchema }),
17
21
  ]);
18
22
  export function operationPath(stateDir, resource, requestId) {
19
23
  createSchema.shape.requestId.parse(requestId);
@@ -86,5 +90,11 @@ export function submitOperation(client, operation) {
86
90
  case 'create': return client.create(operation.command);
87
91
  case 'update': return client.update(operation.command);
88
92
  case 'publish': return client.publish(operation.command);
93
+ case 'move': return client.move(operation.command);
94
+ case 'share': return client.share(operation.command);
95
+ case 'unshare': return client.unshare(operation.command);
96
+ case 'batch': return client.batch(operation.command);
89
97
  }
98
+ const exhaustive = operation;
99
+ throw new Error(`Unsupported retained operation: ${exhaustive}`);
90
100
  }
@@ -0,0 +1,9 @@
1
+ interface SkillsOptions {
2
+ full?: boolean;
3
+ all?: boolean;
4
+ agent?: string;
5
+ global?: boolean;
6
+ force?: boolean;
7
+ }
8
+ export declare function runSkillsCommand(positionals: string[], options: SkillsOptions, seen: ReadonlySet<string>): Promise<void>;
9
+ export {};