@interop/did-cli 0.6.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +1307 -0
  4. package/dist/commands/did.d.ts +3 -0
  5. package/dist/commands/did.d.ts.map +1 -0
  6. package/dist/commands/did.js +605 -0
  7. package/dist/commands/did.js.map +1 -0
  8. package/dist/commands/key.d.ts +3 -0
  9. package/dist/commands/key.d.ts.map +1 -0
  10. package/dist/commands/key.js +430 -0
  11. package/dist/commands/key.js.map +1 -0
  12. package/dist/commands/vc.d.ts +79 -0
  13. package/dist/commands/vc.d.ts.map +1 -0
  14. package/dist/commands/vc.js +528 -0
  15. package/dist/commands/vc.js.map +1 -0
  16. package/dist/commands/wallet.d.ts +14 -0
  17. package/dist/commands/wallet.d.ts.map +1 -0
  18. package/dist/commands/wallet.js +48 -0
  19. package/dist/commands/wallet.js.map +1 -0
  20. package/dist/commands/was.d.ts +500 -0
  21. package/dist/commands/was.d.ts.map +1 -0
  22. package/dist/commands/was.js +1833 -0
  23. package/dist/commands/was.js.map +1 -0
  24. package/dist/commands/zcap.d.ts +85 -0
  25. package/dist/commands/zcap.d.ts.map +1 -0
  26. package/dist/commands/zcap.js +447 -0
  27. package/dist/commands/zcap.js.map +1 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +20 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/keys/ecdsa.d.ts +40 -0
  33. package/dist/keys/ecdsa.d.ts.map +1 -0
  34. package/dist/keys/ecdsa.js +73 -0
  35. package/dist/keys/ecdsa.js.map +1 -0
  36. package/dist/meta.d.ts +164 -0
  37. package/dist/meta.d.ts.map +1 -0
  38. package/dist/meta.js +286 -0
  39. package/dist/meta.js.map +1 -0
  40. package/dist/storage.d.ts +191 -0
  41. package/dist/storage.d.ts.map +1 -0
  42. package/dist/storage.js +307 -0
  43. package/dist/storage.js.map +1 -0
  44. package/dist/table.d.ts +43 -0
  45. package/dist/table.d.ts.map +1 -0
  46. package/dist/table.js +61 -0
  47. package/dist/table.js.map +1 -0
  48. package/dist/vc/fixtures/welcomeCredential.d.ts +22 -0
  49. package/dist/vc/fixtures/welcomeCredential.d.ts.map +1 -0
  50. package/dist/vc/fixtures/welcomeCredential.js +25 -0
  51. package/dist/vc/fixtures/welcomeCredential.js.map +1 -0
  52. package/dist/vc/issue.d.ts +24 -0
  53. package/dist/vc/issue.d.ts.map +1 -0
  54. package/dist/vc/issue.js +211 -0
  55. package/dist/vc/issue.js.map +1 -0
  56. package/dist/vc/registries.d.ts +30 -0
  57. package/dist/vc/registries.d.ts.map +1 -0
  58. package/dist/vc/registries.js +53 -0
  59. package/dist/vc/registries.js.map +1 -0
  60. package/dist/vc/registryManager.d.ts +25 -0
  61. package/dist/vc/registryManager.d.ts.map +1 -0
  62. package/dist/vc/registryManager.js +29 -0
  63. package/dist/vc/registryManager.js.map +1 -0
  64. package/dist/vc/suites/expirationSuite.d.ts +23 -0
  65. package/dist/vc/suites/expirationSuite.d.ts.map +1 -0
  66. package/dist/vc/suites/expirationSuite.js +84 -0
  67. package/dist/vc/suites/expirationSuite.js.map +1 -0
  68. package/dist/vc/suites/issuerDetailsSuite.d.ts +22 -0
  69. package/dist/vc/suites/issuerDetailsSuite.d.ts.map +1 -0
  70. package/dist/vc/suites/issuerDetailsSuite.js +69 -0
  71. package/dist/vc/suites/issuerDetailsSuite.js.map +1 -0
  72. package/dist/vc/verify.d.ts +46 -0
  73. package/dist/vc/verify.d.ts.map +1 -0
  74. package/dist/vc/verify.js +147 -0
  75. package/dist/vc/verify.js.map +1 -0
  76. package/dist/was/address.d.ts +44 -0
  77. package/dist/was/address.d.ts.map +1 -0
  78. package/dist/was/address.js +98 -0
  79. package/dist/was/address.js.map +1 -0
  80. package/dist/was/capability.d.ts +65 -0
  81. package/dist/was/capability.d.ts.map +1 -0
  82. package/dist/was/capability.js +108 -0
  83. package/dist/was/capability.js.map +1 -0
  84. package/dist/was/client.d.ts +108 -0
  85. package/dist/was/client.d.ts.map +1 -0
  86. package/dist/was/client.js +142 -0
  87. package/dist/was/client.js.map +1 -0
  88. package/dist/was/io.d.ts +71 -0
  89. package/dist/was/io.d.ts.map +1 -0
  90. package/dist/was/io.js +146 -0
  91. package/dist/was/io.js.map +1 -0
  92. package/dist/was/registry.d.ts +79 -0
  93. package/dist/was/registry.d.ts.map +1 -0
  94. package/dist/was/registry.js +99 -0
  95. package/dist/was/registry.js.map +1 -0
  96. package/dist/zcap/create.d.ts +20 -0
  97. package/dist/zcap/create.d.ts.map +1 -0
  98. package/dist/zcap/create.js +29 -0
  99. package/dist/zcap/create.js.map +1 -0
  100. package/dist/zcap/delegate.d.ts +44 -0
  101. package/dist/zcap/delegate.d.ts.map +1 -0
  102. package/dist/zcap/delegate.js +77 -0
  103. package/dist/zcap/delegate.js.map +1 -0
  104. package/dist/zcap/encoding.d.ts +17 -0
  105. package/dist/zcap/encoding.d.ts.map +1 -0
  106. package/dist/zcap/encoding.js +37 -0
  107. package/dist/zcap/encoding.js.map +1 -0
  108. package/dist/zcap/signer.d.ts +20 -0
  109. package/dist/zcap/signer.d.ts.map +1 -0
  110. package/dist/zcap/signer.js +62 -0
  111. package/dist/zcap/signer.js.map +1 -0
  112. package/dist/zcap/ttl.d.ts +19 -0
  113. package/dist/zcap/ttl.d.ts.map +1 -0
  114. package/dist/zcap/ttl.js +40 -0
  115. package/dist/zcap/ttl.js.map +1 -0
  116. package/package.json +64 -0
@@ -0,0 +1,1833 @@
1
+ /**
2
+ * `was` command -- Wallet Attached Storage (WAS) operations.
3
+ *
4
+ * Talks to WAS servers via `@interop/was-client`, signing every request with
5
+ * a `did:key` DID stored in the local wallet. Spaces are addressed by a
6
+ * single positional WAS path -- `SPACE[/COLLECTION[/RESOURCE]]` -- where the
7
+ * space part is a local registry handle, a bare space id, or a full space
8
+ * https URL (see `src/was/address.ts`). The local space registry
9
+ * (`~/.config/did-cli-wallet/was-spaces/`) records each space's server URL and controller
10
+ * DID so day-to-day commands need no `--server`/`--did` flags.
11
+ *
12
+ * Subcommand groups: `space` (`create`, `list`, `show`, `update` alias
13
+ * `configure`, `delete`, `forget`, `add`), `collection` (alias `coll`;
14
+ * `create`, `list`, `show`, `update`, `delete`), and `resource` (alias
15
+ * `res`; `add`, `put`, `get`, `list`, `delete`). The top-level shorthand
16
+ * verbs `ls`, `get`, `put`, and `rm` dispatch on the path depth, mirroring
17
+ * the client's uniform-verbs-at-every-level design. Resource payloads come
18
+ * from a file argument or stdin, with JSON-vs-binary detection in
19
+ * `src/was/io.ts`.
20
+ *
21
+ * Data goes to stdout, diagnostics to stderr. Exit codes: 0 success, 1
22
+ * operation error (typed WAS errors and not-found/not-visible reads), 2
23
+ * input error (bad path syntax, unknown handle/DID, missing server URL).
24
+ */
25
+ import { readFile } from 'node:fs/promises';
26
+ import { Command, Option } from 'commander';
27
+ import { WasError } from '@interop/was-client';
28
+ import { parseWasAddress } from '../was/address.js';
29
+ import { buildWasClient, resolveWasTarget } from '../was/client.js';
30
+ import { resolveCapabilityTarget } from '../was/capability.js';
31
+ import { readInputBytes, readPayload, writeBytesOutput, writeResourceOutput } from '../was/io.js';
32
+ import { listSpaceRecords, removeSpaceRecord, resolveSpaceRef, saveSpaceRecord } from '../was/registry.js';
33
+ import { resolveDidRef } from '../meta.js';
34
+ import { saveToCollection } from '../storage.js';
35
+ import { encodeCapability } from '../zcap/encoding.js';
36
+ import { expiresFromTtl } from '../zcap/ttl.js';
37
+ import { storageIdFor, writeCreateMeta } from './zcap.js';
38
+ import { renderTable } from '../table.js';
39
+ /**
40
+ * Awaits a command's run function and exits the process with its code when
41
+ * non-zero -- the shared tail of every `was` command action.
42
+ *
43
+ * @param run {Promise<number>}
44
+ * @returns {Promise<void>}
45
+ */
46
+ async function runAndExit(run) {
47
+ const code = await run;
48
+ if (code !== 0) {
49
+ process.exit(code);
50
+ }
51
+ }
52
+ /**
53
+ * The shared command options, as factories (commander `Option` instances
54
+ * are bound to the command they are added to, so they cannot be shared).
55
+ * Centralizing them keeps the flag names and help text consistent across
56
+ * the whole command tree.
57
+ *
58
+ * @returns {Option}
59
+ */
60
+ function serverOption() {
61
+ return new Option('--server <url>', 'the WAS server base URL (or WAS_SERVER_URL)');
62
+ }
63
+ function didOption() {
64
+ return new Option('--did <did>', 'DID or stored-DID handle to sign with (or WAS_DID)');
65
+ }
66
+ function capabilityOption() {
67
+ return new Option('--capability <ref>', 'a received capability (encoded string, JSON file, or stored zcap ' +
68
+ 'id/handle) instead of a path');
69
+ }
70
+ function contentTypeOption() {
71
+ return new Option('--content-type <type>', 'send the payload as-is with this content type (skips JSON detection)');
72
+ }
73
+ /** The capability actions WAS servers match against (HTTP verbs). */
74
+ const WAS_ACTIONS = ['GET', 'PUT', 'POST', 'DELETE'];
75
+ /**
76
+ * Normalizes grant action verbs to their canonical uppercase form,
77
+ * rejecting anything that is not a WAS-supported HTTP verb.
78
+ *
79
+ * @param actions {string[]}
80
+ * @returns {WasAction[]}
81
+ */
82
+ function normalizeActions(actions) {
83
+ return actions.map(action => {
84
+ const verb = action.toUpperCase();
85
+ if (!WAS_ACTIONS.includes(verb)) {
86
+ throw new Error(`Unknown action "${action}" (supported: GET, PUT, POST, DELETE).`);
87
+ }
88
+ return verb;
89
+ });
90
+ }
91
+ /**
92
+ * Guards a run function against receiving both path- and capability-based
93
+ * addressing (or neither).
94
+ *
95
+ * @param options {object}
96
+ * @param [options.address] {string}
97
+ * @param [options.capability] {string}
98
+ * @returns {void}
99
+ */
100
+ function assertOneAddressing({ address, capability }) {
101
+ if (address && capability) {
102
+ throw new Error('Provide either a path or --capability, not both.');
103
+ }
104
+ if (!address && !capability) {
105
+ throw new Error('Provide a path or --capability.');
106
+ }
107
+ }
108
+ /**
109
+ * Builds the canonical URL of a space, collection, or resource on its
110
+ * server, for messages and output.
111
+ *
112
+ * @param options {object}
113
+ * @param options.server {string}
114
+ * @param options.spaceId {string}
115
+ * @param [options.collectionId] {string}
116
+ * @param [options.resourceId] {string}
117
+ * @returns {string}
118
+ */
119
+ function wasUrl({ server, spaceId, collectionId, resourceId }) {
120
+ const segments = [spaceId, collectionId, resourceId]
121
+ .filter((segment) => segment !== undefined)
122
+ .map(encodeURIComponent);
123
+ return `${server.replace(/\/$/, '')}/space/${segments.join('/')}`;
124
+ }
125
+ /**
126
+ * Prints a one-line error for a failed command and classifies its exit code:
127
+ * 1 for operation errors (typed WAS errors from the server exchange), 2 for
128
+ * input errors (bad addresses, unknown handles/DIDs, missing server URL).
129
+ *
130
+ * @param options {object}
131
+ * @param options.action {string} What failed, e.g. `create the space`.
132
+ * @param options.err {unknown}
133
+ * @returns {number} The process exit code.
134
+ */
135
+ function reportError({ action, err }) {
136
+ const message = err instanceof Error ? err.message : String(err);
137
+ console.error(`Could not ${action}: ${message}`);
138
+ return err instanceof WasError ? 1 : 2;
139
+ }
140
+ /**
141
+ * Parses a WAS address and asserts it addresses a space only (no
142
+ * collection/resource segments), as the `space` subcommands require.
143
+ *
144
+ * @param address {string}
145
+ * @returns {{server?: string, spaceRef: string}}
146
+ */
147
+ function parseSpaceAddress(address) {
148
+ const parsed = parseWasAddress(address);
149
+ if (parsed.collectionId !== undefined) {
150
+ throw new Error(`"${address}" addresses a collection or resource; ` +
151
+ 'space commands take a space address.');
152
+ }
153
+ return parsed;
154
+ }
155
+ /**
156
+ * Asserts a resolved target addresses a collection (`SPACE/COLLECTION`,
157
+ * no resource segment) and narrows its type accordingly.
158
+ *
159
+ * @param options {object}
160
+ * @param options.target {ResolvedWasTarget}
161
+ * @param options.address {string} The original address, for error messages.
162
+ * @returns {ResolvedWasTarget & {collectionId: string}}
163
+ */
164
+ function requireCollectionTarget({ target, address }) {
165
+ if (target.collectionId === undefined || target.resourceId !== undefined) {
166
+ throw new Error(`"${address}" must address a collection (SPACE/COLLECTION).`);
167
+ }
168
+ return target;
169
+ }
170
+ /**
171
+ * Asserts a resolved target addresses a resource
172
+ * (`SPACE/COLLECTION/RESOURCE`) and narrows its type accordingly.
173
+ *
174
+ * @param options {object}
175
+ * @param options.target {ResolvedWasTarget}
176
+ * @param options.address {string} The original address, for error messages.
177
+ * @returns {ResolvedWasTarget & {collectionId: string, resourceId: string}}
178
+ */
179
+ function requireResourceTarget({ target, address }) {
180
+ if (target.collectionId === undefined || target.resourceId === undefined) {
181
+ throw new Error(`"${address}" must address a resource (SPACE/COLLECTION/RESOURCE).`);
182
+ }
183
+ return target;
184
+ }
185
+ /**
186
+ * Creates a space on the server and prints `{ id, url, name?, controller }`.
187
+ *
188
+ * @param options {object}
189
+ * @param [options.name] {string} The space's display name.
190
+ * @param [options.server] {string} The server base URL.
191
+ * @param [options.did] {string} The signing DID or stored-DID handle.
192
+ * @param [options.id] {string} A caller-chosen space id.
193
+ * @param [options.save] {boolean} Register the space in the local wallet.
194
+ * @param [options.handle] {string} Short tag for the registry entry.
195
+ * @param [options.description] {string} Longer registry entry description.
196
+ * @returns {Promise<number>} The process exit code.
197
+ */
198
+ export async function runSpaceCreate(options) {
199
+ try {
200
+ const { client, server, did } = await buildWasClient({
201
+ server: options.server,
202
+ did: options.did
203
+ });
204
+ const space = await client.createSpace({
205
+ ...(options.name !== undefined && { name: options.name }),
206
+ ...(options.id !== undefined && { id: options.id })
207
+ });
208
+ const url = wasUrl({ server, spaceId: space.id });
209
+ if (options.save) {
210
+ const filePath = await saveSpaceRecord({
211
+ record: {
212
+ id: space.id,
213
+ ...(options.name !== undefined && { name: options.name }),
214
+ server,
215
+ controller: did
216
+ },
217
+ handle: options.handle,
218
+ description: options.description
219
+ });
220
+ console.error(`Space registered in ${filePath}`);
221
+ }
222
+ console.log(JSON.stringify({
223
+ id: space.id,
224
+ url,
225
+ ...(options.name !== undefined && { name: options.name }),
226
+ controller: did
227
+ }, null, 2));
228
+ return 0;
229
+ }
230
+ catch (err) {
231
+ return reportError({ action: 'create the space', err });
232
+ }
233
+ }
234
+ /**
235
+ * Lists the locally registered spaces (the working model while servers do
236
+ * not implement List Spaces); `--remote` asks the server instead.
237
+ *
238
+ * @param options {object}
239
+ * @param [options.json] {boolean} Output a JSON array with metadata.
240
+ * @param [options.plain] {boolean} Output one space id per line.
241
+ * @param [options.remote] {boolean} List spaces on the server instead.
242
+ * @param [options.server] {string} The server base URL (with `--remote`).
243
+ * @param [options.did] {string} The signing DID (with `--remote`).
244
+ * @returns {Promise<number>} The process exit code.
245
+ */
246
+ export async function runSpaceList(options) {
247
+ try {
248
+ if (options.remote) {
249
+ const { client } = await buildWasClient({
250
+ server: options.server,
251
+ did: options.did
252
+ });
253
+ const listing = await client.listSpaces();
254
+ console.log(JSON.stringify(listing, null, 2));
255
+ return 0;
256
+ }
257
+ const entries = await listSpaceRecords();
258
+ if (options.plain) {
259
+ const spaceIds = entries.map(entry => entry.record.id).sort();
260
+ for (const spaceId of spaceIds) {
261
+ console.log(spaceId);
262
+ }
263
+ return 0;
264
+ }
265
+ if (options.json) {
266
+ const output = entries.map(entry => ({
267
+ id: entry.record.id,
268
+ server: entry.record.server,
269
+ ...(entry.record.name && { name: entry.record.name }),
270
+ ...(entry.record.controller && {
271
+ controller: entry.record.controller
272
+ }),
273
+ ...(entry.meta?.created && { created: entry.meta.created }),
274
+ ...(entry.meta?.handle && { handle: entry.meta.handle }),
275
+ ...(entry.meta?.description && {
276
+ description: entry.meta.description
277
+ })
278
+ }));
279
+ console.log(JSON.stringify(output, null, 2));
280
+ return 0;
281
+ }
282
+ if (entries.length === 0) {
283
+ return 0;
284
+ }
285
+ const rows = entries.map(entry => [
286
+ entry.meta?.handle ?? '',
287
+ entry.record.name ?? '',
288
+ entry.record.id,
289
+ entry.record.server,
290
+ entry.meta?.created?.slice(0, 10) ?? ''
291
+ ]);
292
+ console.log(renderTable({
293
+ columns: [
294
+ { header: 'HANDLE', maxWidth: 16 },
295
+ { header: 'NAME', maxWidth: 20 },
296
+ { header: 'SPACE ID', maxWidth: 40 },
297
+ { header: 'SERVER', maxWidth: 32 },
298
+ { header: 'CREATED' }
299
+ ],
300
+ rows
301
+ }));
302
+ return 0;
303
+ }
304
+ catch (err) {
305
+ return reportError({ action: 'list spaces', err });
306
+ }
307
+ }
308
+ /**
309
+ * Shows a space: its Space Description from the server, or (`--meta`) its
310
+ * local registry record and metadata.
311
+ *
312
+ * @param options {object}
313
+ * @param options.address {string} The space address.
314
+ * @param [options.meta] {boolean} Show the local registry metadata instead.
315
+ * @param [options.json] {boolean} With `--meta`, output JSON.
316
+ * @param [options.server] {string} The server base URL.
317
+ * @param [options.did] {string} The signing DID or stored-DID handle.
318
+ * @returns {Promise<number>} The process exit code.
319
+ */
320
+ export async function runSpaceShow(options) {
321
+ try {
322
+ const parsed = parseSpaceAddress(options.address);
323
+ if (options.meta) {
324
+ const entry = await resolveSpaceRef({ ref: parsed.spaceRef });
325
+ if (!entry) {
326
+ throw new Error(`No locally registered space found for "${parsed.spaceRef}".`);
327
+ }
328
+ if (options.json) {
329
+ const output = {
330
+ id: entry.record.id,
331
+ server: entry.record.server,
332
+ ...(entry.record.name && { name: entry.record.name }),
333
+ ...(entry.record.controller && {
334
+ controller: entry.record.controller
335
+ }),
336
+ ...(entry.meta?.created && { created: entry.meta.created }),
337
+ ...(entry.meta?.handle && { handle: entry.meta.handle }),
338
+ ...(entry.meta?.description && {
339
+ description: entry.meta.description
340
+ })
341
+ };
342
+ console.log(JSON.stringify(output, null, 2));
343
+ return 0;
344
+ }
345
+ const rows = [
346
+ ['ID', entry.record.id],
347
+ ['Name', entry.record.name ?? ''],
348
+ ['Server', entry.record.server],
349
+ ['Controller', entry.record.controller ?? ''],
350
+ ['Handle', entry.meta?.handle ?? ''],
351
+ ['Created', entry.meta?.created ?? ''],
352
+ ['Description', entry.meta?.description ?? '']
353
+ ];
354
+ console.log(renderTable({
355
+ columns: [{ header: 'FIELD' }, { header: 'VALUE' }],
356
+ rows
357
+ }));
358
+ return 0;
359
+ }
360
+ const target = await resolveWasTarget({
361
+ address: options.address,
362
+ server: options.server,
363
+ did: options.did
364
+ });
365
+ const description = await target.client.space(target.spaceId).describe();
366
+ if (description === null) {
367
+ console.error('Not found (or not visible to you): ' +
368
+ wasUrl({ server: target.server, spaceId: target.spaceId }));
369
+ return 1;
370
+ }
371
+ console.log(JSON.stringify(description, null, 2));
372
+ return 0;
373
+ }
374
+ catch (err) {
375
+ return reportError({ action: 'show the space', err });
376
+ }
377
+ }
378
+ /**
379
+ * Updates a space's description fields on the server (upsert via
380
+ * `configure()`), refreshing the name in the local registry entry when one
381
+ * exists.
382
+ *
383
+ * @param options {object}
384
+ * @param options.address {string} The space address.
385
+ * @param [options.name] {string} The new display name.
386
+ * @param [options.server] {string} The server base URL.
387
+ * @param [options.did] {string} The signing DID or stored-DID handle.
388
+ * @returns {Promise<number>} The process exit code.
389
+ */
390
+ export async function runSpaceUpdate(options) {
391
+ try {
392
+ parseSpaceAddress(options.address);
393
+ const target = await resolveWasTarget({
394
+ address: options.address,
395
+ server: options.server,
396
+ did: options.did
397
+ });
398
+ const description = await target.client.space(target.spaceId).configure({
399
+ ...(options.name !== undefined && { name: options.name })
400
+ });
401
+ if (target.entry && options.name !== undefined) {
402
+ await saveSpaceRecord({
403
+ record: { ...target.entry.record, name: options.name }
404
+ });
405
+ }
406
+ console.log(JSON.stringify(description, null, 2));
407
+ return 0;
408
+ }
409
+ catch (err) {
410
+ return reportError({ action: 'update the space', err });
411
+ }
412
+ }
413
+ /**
414
+ * Deletes a space on the server (idempotent) and removes its local registry
415
+ * entry when one exists. The local-only counterpart is `forget`.
416
+ *
417
+ * @param options {object}
418
+ * @param options.address {string} The space address.
419
+ * @param [options.server] {string} The server base URL.
420
+ * @param [options.did] {string} The signing DID or stored-DID handle.
421
+ * @returns {Promise<number>} The process exit code.
422
+ */
423
+ export async function runSpaceDelete(options) {
424
+ try {
425
+ parseSpaceAddress(options.address);
426
+ const target = await resolveWasTarget({
427
+ address: options.address,
428
+ server: options.server,
429
+ did: options.did
430
+ });
431
+ await target.client.space(target.spaceId).delete();
432
+ console.error('Deleted ' +
433
+ wasUrl({ server: target.server, spaceId: target.spaceId }) +
434
+ ' on the server.');
435
+ if (target.entry) {
436
+ const removed = await removeSpaceRecord({
437
+ storageId: target.entry.storageId
438
+ });
439
+ for (const filePath of removed) {
440
+ console.error(`Removed ${filePath}`);
441
+ }
442
+ }
443
+ return 0;
444
+ }
445
+ catch (err) {
446
+ return reportError({ action: 'delete the space', err });
447
+ }
448
+ }
449
+ /**
450
+ * Removes a space's local registry entry only; the server-side space is
451
+ * untouched. The remote counterpart is `delete`.
452
+ *
453
+ * @param options {object}
454
+ * @param options.address {string} The space address.
455
+ * @returns {Promise<number>} The process exit code.
456
+ */
457
+ export async function runSpaceForget(options) {
458
+ try {
459
+ const parsed = parseSpaceAddress(options.address);
460
+ const entry = await resolveSpaceRef({ ref: parsed.spaceRef });
461
+ if (!entry) {
462
+ throw new Error(`No locally registered space found for "${parsed.spaceRef}".`);
463
+ }
464
+ const removed = await removeSpaceRecord({ storageId: entry.storageId });
465
+ for (const filePath of removed) {
466
+ console.error(`Removed ${filePath}`);
467
+ }
468
+ return 0;
469
+ }
470
+ catch (err) {
471
+ return reportError({ action: 'forget the space', err });
472
+ }
473
+ }
474
+ /**
475
+ * Registers an existing remote space (e.g. created elsewhere or received via
476
+ * delegation) in the local registry, verifying it first with `describe()`.
477
+ *
478
+ * @param options {object}
479
+ * @param options.address {string} A full space https URL or a bare space id.
480
+ * @param [options.server] {string} The server base URL (with a bare id).
481
+ * @param [options.did] {string} The signing DID or stored-DID handle.
482
+ * @param [options.handle] {string} Short tag for the registry entry.
483
+ * @param [options.description] {string} Longer registry entry description.
484
+ * @returns {Promise<number>} The process exit code.
485
+ */
486
+ export async function runSpaceAdd(options) {
487
+ try {
488
+ parseSpaceAddress(options.address);
489
+ const target = await resolveWasTarget({
490
+ address: options.address,
491
+ server: options.server,
492
+ did: options.did
493
+ });
494
+ const description = await target.client.space(target.spaceId).describe();
495
+ if (description === null) {
496
+ console.error('Not found (or not visible to you): ' +
497
+ wasUrl({ server: target.server, spaceId: target.spaceId }));
498
+ return 1;
499
+ }
500
+ const filePath = await saveSpaceRecord({
501
+ record: {
502
+ id: target.spaceId,
503
+ ...(description.name && { name: description.name }),
504
+ server: target.server,
505
+ controller: description.controller ?? target.did
506
+ },
507
+ handle: options.handle,
508
+ description: options.description
509
+ });
510
+ console.error(`Space registered in ${filePath}`);
511
+ console.log(JSON.stringify(description, null, 2));
512
+ return 0;
513
+ }
514
+ catch (err) {
515
+ return reportError({ action: 'add the space', err });
516
+ }
517
+ }
518
+ /**
519
+ * Creates a collection within a space and prints `{ id, url, name? }`.
520
+ *
521
+ * @param options {object}
522
+ * @param options.address {string} The space address.
523
+ * @param [options.name] {string} The collection's display name.
524
+ * @param [options.id] {string} A caller-chosen collection id.
525
+ * @param [options.server] {string} The server base URL.
526
+ * @param [options.did] {string} The signing DID or stored-DID handle.
527
+ * @returns {Promise<number>} The process exit code.
528
+ */
529
+ export async function runCollectionCreate(options) {
530
+ try {
531
+ parseSpaceAddress(options.address);
532
+ const target = await resolveWasTarget({
533
+ address: options.address,
534
+ server: options.server,
535
+ did: options.did
536
+ });
537
+ const collection = await target.client
538
+ .space(target.spaceId)
539
+ .createCollection({
540
+ ...(options.name !== undefined && { name: options.name }),
541
+ ...(options.id !== undefined && { id: options.id })
542
+ });
543
+ const url = wasUrl({
544
+ server: target.server,
545
+ spaceId: target.spaceId,
546
+ collectionId: collection.id
547
+ });
548
+ console.log(JSON.stringify({
549
+ id: collection.id,
550
+ url,
551
+ ...(options.name !== undefined && { name: options.name })
552
+ }, null, 2));
553
+ return 0;
554
+ }
555
+ catch (err) {
556
+ return reportError({ action: 'create the collection', err });
557
+ }
558
+ }
559
+ /**
560
+ * Lists the collections in a space.
561
+ *
562
+ * @param options {object}
563
+ * @param options.address {string} The space address.
564
+ * @param [options.json] {boolean} Output the raw listing JSON.
565
+ * @param [options.plain] {boolean} Output one collection id per line.
566
+ * @param [options.server] {string} The server base URL.
567
+ * @param [options.did] {string} The signing DID or stored-DID handle.
568
+ * @returns {Promise<number>} The process exit code.
569
+ */
570
+ export async function runCollectionList(options) {
571
+ try {
572
+ parseSpaceAddress(options.address);
573
+ const target = await resolveWasTarget({
574
+ address: options.address,
575
+ server: options.server,
576
+ did: options.did
577
+ });
578
+ const listing = await target.client.space(target.spaceId).collections();
579
+ if (listing === null) {
580
+ console.error('Not found (or not visible to you): ' +
581
+ wasUrl({ server: target.server, spaceId: target.spaceId }));
582
+ return 1;
583
+ }
584
+ printCollectionListing({
585
+ listing,
586
+ json: options.json,
587
+ plain: options.plain
588
+ });
589
+ return 0;
590
+ }
591
+ catch (err) {
592
+ return reportError({ action: 'list collections', err });
593
+ }
594
+ }
595
+ /**
596
+ * Renders a listing as a table, raw JSON, or one item id per line -- the
597
+ * shared output logic of `collection list`, `resource list`, and `ls`.
598
+ *
599
+ * @param options {object}
600
+ * @param options.listing {{items: T[]}}
601
+ * @param [options.json] {boolean}
602
+ * @param [options.plain] {boolean}
603
+ * @param options.columns {Column[]}
604
+ * @param options.toRow {(item: T) => string[]}
605
+ * @returns {void}
606
+ */
607
+ function printListing({ listing, json, plain, columns, toRow }) {
608
+ if (json) {
609
+ console.log(JSON.stringify(listing, null, 2));
610
+ return;
611
+ }
612
+ if (plain) {
613
+ const ids = listing.items.map(item => item.id).sort();
614
+ for (const id of ids) {
615
+ console.log(id);
616
+ }
617
+ return;
618
+ }
619
+ if (listing.items.length === 0) {
620
+ return;
621
+ }
622
+ console.log(renderTable({ columns, rows: listing.items.map(toRow) }));
623
+ }
624
+ /**
625
+ * Renders a collection listing as a table, raw JSON, or one id per line.
626
+ *
627
+ * @param options {object}
628
+ * @param options.listing {CollectionListing}
629
+ * @param [options.json] {boolean}
630
+ * @param [options.plain] {boolean}
631
+ * @returns {void}
632
+ */
633
+ function printCollectionListing({ listing, json, plain }) {
634
+ printListing({
635
+ listing,
636
+ json,
637
+ plain,
638
+ columns: [
639
+ { header: 'ID', maxWidth: 30 },
640
+ { header: 'NAME', maxWidth: 20 },
641
+ { header: 'URL' }
642
+ ],
643
+ toRow: item => [item.id, item.name, item.url]
644
+ });
645
+ }
646
+ /**
647
+ * Shows a collection's description from the server.
648
+ *
649
+ * @param options {object}
650
+ * @param options.address {string} The collection address.
651
+ * @param [options.server] {string} The server base URL.
652
+ * @param [options.did] {string} The signing DID or stored-DID handle.
653
+ * @returns {Promise<number>} The process exit code.
654
+ */
655
+ export async function runCollectionShow(options) {
656
+ try {
657
+ const target = requireCollectionTarget({
658
+ target: await resolveWasTarget({
659
+ address: options.address,
660
+ server: options.server,
661
+ did: options.did
662
+ }),
663
+ address: options.address
664
+ });
665
+ const description = await target.client
666
+ .space(target.spaceId)
667
+ .collection(target.collectionId)
668
+ .describe();
669
+ if (description === null) {
670
+ console.error('Not found (or not visible to you): ' +
671
+ wasUrl({
672
+ server: target.server,
673
+ spaceId: target.spaceId,
674
+ collectionId: target.collectionId
675
+ }));
676
+ return 1;
677
+ }
678
+ console.log(JSON.stringify(description, null, 2));
679
+ return 0;
680
+ }
681
+ catch (err) {
682
+ return reportError({ action: 'show the collection', err });
683
+ }
684
+ }
685
+ /**
686
+ * Updates a collection's description fields on the server (upsert via
687
+ * `configure()`).
688
+ *
689
+ * @param options {object}
690
+ * @param options.address {string} The collection address.
691
+ * @param options.name {string} The collection's new display name.
692
+ * @param [options.server] {string} The server base URL.
693
+ * @param [options.did] {string} The signing DID or stored-DID handle.
694
+ * @returns {Promise<number>} The process exit code.
695
+ */
696
+ export async function runCollectionUpdate(options) {
697
+ try {
698
+ const target = requireCollectionTarget({
699
+ target: await resolveWasTarget({
700
+ address: options.address,
701
+ server: options.server,
702
+ did: options.did
703
+ }),
704
+ address: options.address
705
+ });
706
+ const description = await target.client
707
+ .space(target.spaceId)
708
+ .collection(target.collectionId)
709
+ .configure({ name: options.name });
710
+ console.log(JSON.stringify(description, null, 2));
711
+ return 0;
712
+ }
713
+ catch (err) {
714
+ return reportError({ action: 'update the collection', err });
715
+ }
716
+ }
717
+ /**
718
+ * Deletes a whole collection and its contents on the server. Idempotent.
719
+ *
720
+ * @param options {object}
721
+ * @param options.address {string} The collection address.
722
+ * @param [options.server] {string} The server base URL.
723
+ * @param [options.did] {string} The signing DID or stored-DID handle.
724
+ * @returns {Promise<number>} The process exit code.
725
+ */
726
+ export async function runCollectionDelete(options) {
727
+ try {
728
+ const target = requireCollectionTarget({
729
+ target: await resolveWasTarget({
730
+ address: options.address,
731
+ server: options.server,
732
+ did: options.did
733
+ }),
734
+ address: options.address
735
+ });
736
+ await target.client
737
+ .space(target.spaceId)
738
+ .collection(target.collectionId)
739
+ .delete();
740
+ console.error('Deleted ' +
741
+ wasUrl({
742
+ server: target.server,
743
+ spaceId: target.spaceId,
744
+ collectionId: target.collectionId
745
+ }) +
746
+ ' on the server.');
747
+ return 0;
748
+ }
749
+ catch (err) {
750
+ return reportError({ action: 'delete the collection', err });
751
+ }
752
+ }
753
+ /**
754
+ * Adds a resource to a collection (server-generated id) and prints the
755
+ * `{ id, url, contentType }` add result. The collection comes from a path
756
+ * or a `--capability` targeting one.
757
+ *
758
+ * @param options {object}
759
+ * @param [options.address] {string} The collection address.
760
+ * @param [options.capability] {string} A capability reference instead of
761
+ * a path.
762
+ * @param [options.file] {string} The payload file; stdin when omitted.
763
+ * @param [options.contentType] {string} Explicit payload content type.
764
+ * @param [options.server] {string} The server base URL.
765
+ * @param [options.did] {string} The signing DID or stored-DID handle.
766
+ * @returns {Promise<number>} The process exit code.
767
+ */
768
+ export async function runResourceAdd(options) {
769
+ try {
770
+ assertOneAddressing(options);
771
+ let collection;
772
+ if (options.capability) {
773
+ const resolved = await resolveCapabilityTarget({
774
+ ref: options.capability,
775
+ did: options.did
776
+ });
777
+ if (resolved.depth !== 'collection') {
778
+ throw new Error(`The capability targets a ${resolved.depth}; ` +
779
+ 'add needs a collection capability.');
780
+ }
781
+ collection = resolved.handle;
782
+ }
783
+ else {
784
+ const target = requireCollectionTarget({
785
+ target: await resolveWasTarget({
786
+ address: options.address,
787
+ server: options.server,
788
+ did: options.did
789
+ }),
790
+ address: options.address
791
+ });
792
+ collection = target.client
793
+ .space(target.spaceId)
794
+ .collection(target.collectionId);
795
+ }
796
+ const payload = await readPayload({
797
+ file: options.file,
798
+ contentType: options.contentType
799
+ });
800
+ const result = await collection.add(payload.data, {
801
+ ...(payload.contentType !== undefined && {
802
+ contentType: payload.contentType
803
+ })
804
+ });
805
+ console.log(JSON.stringify(result, null, 2));
806
+ return 0;
807
+ }
808
+ catch (err) {
809
+ return reportError({ action: 'add the resource', err });
810
+ }
811
+ }
812
+ /**
813
+ * Creates or replaces a resource at a known id (upsert) and prints
814
+ * `{ id, url }`. The resource comes from a path or a `--capability`
815
+ * targeting one.
816
+ *
817
+ * @param options {object}
818
+ * @param [options.address] {string} The resource address.
819
+ * @param [options.capability] {string} A capability reference instead of
820
+ * a path.
821
+ * @param [options.file] {string} The payload file; stdin when omitted.
822
+ * @param [options.contentType] {string} Explicit payload content type.
823
+ * @param [options.server] {string} The server base URL.
824
+ * @param [options.did] {string} The signing DID or stored-DID handle.
825
+ * @returns {Promise<number>} The process exit code.
826
+ */
827
+ export async function runResourcePut(options) {
828
+ try {
829
+ assertOneAddressing(options);
830
+ let resource;
831
+ let url;
832
+ if (options.capability) {
833
+ const resolved = await resolveCapabilityTarget({
834
+ ref: options.capability,
835
+ did: options.did
836
+ });
837
+ if (resolved.depth !== 'resource') {
838
+ throw new Error(`The capability targets a ${resolved.depth}; ` +
839
+ 'put needs a resource capability.');
840
+ }
841
+ resource = resolved.handle;
842
+ url = resolved.url;
843
+ }
844
+ else {
845
+ const target = requireResourceTarget({
846
+ target: await resolveWasTarget({
847
+ address: options.address,
848
+ server: options.server,
849
+ did: options.did
850
+ }),
851
+ address: options.address
852
+ });
853
+ resource = target.client
854
+ .space(target.spaceId)
855
+ .collection(target.collectionId)
856
+ .resource(target.resourceId);
857
+ url = wasUrl({
858
+ server: target.server,
859
+ spaceId: target.spaceId,
860
+ collectionId: target.collectionId,
861
+ resourceId: target.resourceId
862
+ });
863
+ }
864
+ const payload = await readPayload({
865
+ file: options.file,
866
+ contentType: options.contentType
867
+ });
868
+ await resource.put(payload.data, {
869
+ ...(payload.contentType !== undefined && {
870
+ contentType: payload.contentType
871
+ })
872
+ });
873
+ console.log(JSON.stringify({ id: resource.id, url }, null, 2));
874
+ return 0;
875
+ }
876
+ catch (err) {
877
+ return reportError({ action: 'put the resource', err });
878
+ }
879
+ }
880
+ /**
881
+ * Reads a resource: JSON pretty-printed to stdout, binary written raw
882
+ * (`--output` for files). The resource comes from a path or a
883
+ * `--capability` targeting one.
884
+ *
885
+ * @param options {object}
886
+ * @param [options.address] {string} The resource address.
887
+ * @param [options.capability] {string} A capability reference instead of
888
+ * a path.
889
+ * @param [options.output] {string} The output file path; stdout when
890
+ * omitted.
891
+ * @param [options.server] {string} The server base URL.
892
+ * @param [options.did] {string} The signing DID or stored-DID handle.
893
+ * @returns {Promise<number>} The process exit code.
894
+ */
895
+ export async function runResourceGet(options) {
896
+ try {
897
+ assertOneAddressing(options);
898
+ let resource;
899
+ let url;
900
+ if (options.capability) {
901
+ const resolved = await resolveCapabilityTarget({
902
+ ref: options.capability,
903
+ did: options.did
904
+ });
905
+ if (resolved.depth !== 'resource') {
906
+ throw new Error(`The capability targets a ${resolved.depth}; ` +
907
+ 'get needs a resource capability.');
908
+ }
909
+ resource = resolved.handle;
910
+ url = resolved.url;
911
+ }
912
+ else {
913
+ const target = requireResourceTarget({
914
+ target: await resolveWasTarget({
915
+ address: options.address,
916
+ server: options.server,
917
+ did: options.did
918
+ }),
919
+ address: options.address
920
+ });
921
+ resource = target.client
922
+ .space(target.spaceId)
923
+ .collection(target.collectionId)
924
+ .resource(target.resourceId);
925
+ url = wasUrl({
926
+ server: target.server,
927
+ spaceId: target.spaceId,
928
+ collectionId: target.collectionId,
929
+ resourceId: target.resourceId
930
+ });
931
+ }
932
+ const data = await resource.get();
933
+ if (data === null) {
934
+ console.error(`Not found (or not visible to you): ${url}`);
935
+ return 1;
936
+ }
937
+ await writeResourceOutput({ data, output: options.output });
938
+ return 0;
939
+ }
940
+ catch (err) {
941
+ return reportError({ action: 'get the resource', err });
942
+ }
943
+ }
944
+ /**
945
+ * Lists the resources in a collection.
946
+ *
947
+ * @param options {object}
948
+ * @param options.address {string} The collection address.
949
+ * @param [options.json] {boolean} Output the raw listing JSON.
950
+ * @param [options.plain] {boolean} Output one resource id per line.
951
+ * @param [options.server] {string} The server base URL.
952
+ * @param [options.did] {string} The signing DID or stored-DID handle.
953
+ * @returns {Promise<number>} The process exit code.
954
+ */
955
+ export async function runResourceList(options) {
956
+ try {
957
+ const target = requireCollectionTarget({
958
+ target: await resolveWasTarget({
959
+ address: options.address,
960
+ server: options.server,
961
+ did: options.did
962
+ }),
963
+ address: options.address
964
+ });
965
+ const listing = await target.client
966
+ .space(target.spaceId)
967
+ .collection(target.collectionId)
968
+ .list();
969
+ if (listing === null) {
970
+ console.error('Not found (or not visible to you): ' +
971
+ wasUrl({
972
+ server: target.server,
973
+ spaceId: target.spaceId,
974
+ collectionId: target.collectionId
975
+ }));
976
+ return 1;
977
+ }
978
+ printResourceListing({ listing, json: options.json, plain: options.plain });
979
+ return 0;
980
+ }
981
+ catch (err) {
982
+ return reportError({ action: 'list resources', err });
983
+ }
984
+ }
985
+ /**
986
+ * Renders a resource listing as a table, raw JSON, or one id per line.
987
+ *
988
+ * @param options {object}
989
+ * @param options.listing {ResourceListing}
990
+ * @param [options.json] {boolean}
991
+ * @param [options.plain] {boolean}
992
+ * @returns {void}
993
+ */
994
+ function printResourceListing({ listing, json, plain }) {
995
+ printListing({
996
+ listing,
997
+ json,
998
+ plain,
999
+ columns: [
1000
+ { header: 'ID', maxWidth: 30 },
1001
+ { header: 'CONTENT TYPE', maxWidth: 24 },
1002
+ { header: 'URL' }
1003
+ ],
1004
+ toRow: item => [item.id, item.contentType, item.url]
1005
+ });
1006
+ }
1007
+ /**
1008
+ * Deletes a resource on the server. Idempotent.
1009
+ *
1010
+ * @param options {object}
1011
+ * @param options.address {string} The resource address.
1012
+ * @param [options.server] {string} The server base URL.
1013
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1014
+ * @returns {Promise<number>} The process exit code.
1015
+ */
1016
+ export async function runResourceDelete(options) {
1017
+ try {
1018
+ const target = requireResourceTarget({
1019
+ target: await resolveWasTarget({
1020
+ address: options.address,
1021
+ server: options.server,
1022
+ did: options.did
1023
+ }),
1024
+ address: options.address
1025
+ });
1026
+ await target.client
1027
+ .space(target.spaceId)
1028
+ .collection(target.collectionId)
1029
+ .resource(target.resourceId)
1030
+ .delete();
1031
+ console.error('Deleted ' +
1032
+ wasUrl({
1033
+ server: target.server,
1034
+ spaceId: target.spaceId,
1035
+ collectionId: target.collectionId,
1036
+ resourceId: target.resourceId
1037
+ }) +
1038
+ ' on the server.');
1039
+ return 0;
1040
+ }
1041
+ catch (err) {
1042
+ return reportError({ action: 'delete the resource', err });
1043
+ }
1044
+ }
1045
+ /**
1046
+ * The `was ls` shorthand: lists the collections of a space or the resources
1047
+ * of a collection, depending on the depth of the path (or of a
1048
+ * `--capability`'s invocation target).
1049
+ *
1050
+ * @param options {object}
1051
+ * @param [options.address] {string} A space or collection address.
1052
+ * @param [options.capability] {string} A capability reference instead of
1053
+ * a path.
1054
+ * @param [options.json] {boolean} Output the raw listing JSON.
1055
+ * @param [options.plain] {boolean} Output one id per line.
1056
+ * @param [options.server] {string} The server base URL.
1057
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1058
+ * @returns {Promise<number>} The process exit code.
1059
+ */
1060
+ export async function runLs(options) {
1061
+ if (options.capability) {
1062
+ try {
1063
+ assertOneAddressing(options);
1064
+ const resolved = await resolveCapabilityTarget({
1065
+ ref: options.capability,
1066
+ did: options.did
1067
+ });
1068
+ if (resolved.depth === 'resource') {
1069
+ throw new Error('the capability targets a resource; ' +
1070
+ 'ls takes a space or collection capability.');
1071
+ }
1072
+ if (resolved.depth === 'space') {
1073
+ const listing = await resolved.handle.collections();
1074
+ if (listing === null) {
1075
+ console.error(`Not found (or not visible to you): ${resolved.url}`);
1076
+ return 1;
1077
+ }
1078
+ printCollectionListing({
1079
+ listing,
1080
+ json: options.json,
1081
+ plain: options.plain
1082
+ });
1083
+ }
1084
+ else {
1085
+ const listing = await resolved.handle.list();
1086
+ if (listing === null) {
1087
+ console.error(`Not found (or not visible to you): ${resolved.url}`);
1088
+ return 1;
1089
+ }
1090
+ printResourceListing({
1091
+ listing,
1092
+ json: options.json,
1093
+ plain: options.plain
1094
+ });
1095
+ }
1096
+ return 0;
1097
+ }
1098
+ catch (err) {
1099
+ return reportError({ action: 'list the path', err });
1100
+ }
1101
+ }
1102
+ let parsed;
1103
+ try {
1104
+ assertOneAddressing(options);
1105
+ parsed = parseWasAddress(options.address);
1106
+ }
1107
+ catch (err) {
1108
+ return reportError({ action: 'list the path', err });
1109
+ }
1110
+ if (parsed.resourceId !== undefined) {
1111
+ console.error(`Could not list the path: "${options.address}" is a resource; ` +
1112
+ 'ls takes a space or collection address.');
1113
+ return 2;
1114
+ }
1115
+ if (parsed.collectionId !== undefined) {
1116
+ return runResourceList({ ...options, address: options.address });
1117
+ }
1118
+ return runCollectionList({ ...options, address: options.address });
1119
+ }
1120
+ /**
1121
+ * The `was rm` shorthand: deletes whatever the path (or a `--capability`'s
1122
+ * invocation target) points at -- a space, a collection, or a resource (the
1123
+ * client's uniform `delete()` design).
1124
+ *
1125
+ * @param options {object}
1126
+ * @param [options.address] {string} A space, collection, or resource
1127
+ * address.
1128
+ * @param [options.capability] {string} A capability reference instead of
1129
+ * a path.
1130
+ * @param [options.server] {string} The server base URL.
1131
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1132
+ * @returns {Promise<number>} The process exit code.
1133
+ */
1134
+ export async function runRm(options) {
1135
+ if (options.capability) {
1136
+ try {
1137
+ assertOneAddressing(options);
1138
+ const resolved = await resolveCapabilityTarget({
1139
+ ref: options.capability,
1140
+ did: options.did
1141
+ });
1142
+ await resolved.handle.delete();
1143
+ console.error(`Deleted ${resolved.url} on the server.`);
1144
+ return 0;
1145
+ }
1146
+ catch (err) {
1147
+ return reportError({ action: 'delete the path', err });
1148
+ }
1149
+ }
1150
+ let parsed;
1151
+ try {
1152
+ assertOneAddressing(options);
1153
+ parsed = parseWasAddress(options.address);
1154
+ }
1155
+ catch (err) {
1156
+ return reportError({ action: 'delete the path', err });
1157
+ }
1158
+ const pathOptions = { ...options, address: options.address };
1159
+ if (parsed.resourceId !== undefined) {
1160
+ return runResourceDelete(pathOptions);
1161
+ }
1162
+ if (parsed.collectionId !== undefined) {
1163
+ return runCollectionDelete(pathOptions);
1164
+ }
1165
+ return runSpaceDelete(pathOptions);
1166
+ }
1167
+ /**
1168
+ * Builds the access handle (space, collection, or resource) a resolved
1169
+ * target addresses, together with its URL. Policies and publishing work at
1170
+ * every depth, so these commands dispatch through this helper.
1171
+ *
1172
+ * @param target {ResolvedWasTarget}
1173
+ * @returns {{handle: Space | Collection | Resource, url: string}}
1174
+ */
1175
+ function handleForTarget(target) {
1176
+ const url = wasUrl({
1177
+ server: target.server,
1178
+ spaceId: target.spaceId,
1179
+ collectionId: target.collectionId,
1180
+ resourceId: target.resourceId
1181
+ });
1182
+ const space = target.client.space(target.spaceId);
1183
+ if (target.collectionId === undefined) {
1184
+ return { handle: space, url };
1185
+ }
1186
+ const collection = space.collection(target.collectionId);
1187
+ if (target.resourceId === undefined) {
1188
+ return { handle: collection, url };
1189
+ }
1190
+ return { handle: collection.resource(target.resourceId), url };
1191
+ }
1192
+ /**
1193
+ * Shows the access-control policy of a space, collection, or resource.
1194
+ *
1195
+ * @param options {object}
1196
+ * @param options.address {string} A space/collection/resource address.
1197
+ * @param [options.server] {string} The server base URL.
1198
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1199
+ * @returns {Promise<number>} The process exit code.
1200
+ */
1201
+ export async function runPolicyShow(options) {
1202
+ try {
1203
+ const target = await resolveWasTarget({
1204
+ address: options.address,
1205
+ server: options.server,
1206
+ did: options.did
1207
+ });
1208
+ const { handle, url } = handleForTarget(target);
1209
+ const policy = await handle.getPolicy();
1210
+ if (policy === null) {
1211
+ console.error(`No policy set (or not visible to you): ${url}`);
1212
+ return 1;
1213
+ }
1214
+ console.log(JSON.stringify(policy, null, 2));
1215
+ return 0;
1216
+ }
1217
+ catch (err) {
1218
+ return reportError({ action: 'show the policy', err });
1219
+ }
1220
+ }
1221
+ /**
1222
+ * Parses the `policy set` arguments -- `--type <type>` for a simple
1223
+ * type-only policy, or a JSON file for richer ones -- into a policy
1224
+ * document.
1225
+ *
1226
+ * @param options {object}
1227
+ * @param [options.type] {string}
1228
+ * @param [options.file] {string}
1229
+ * @returns {Promise<PolicyDocument>}
1230
+ */
1231
+ async function resolvePolicyInput({ type, file }) {
1232
+ if (type && file) {
1233
+ throw new Error('Provide either --type or a policy file, not both.');
1234
+ }
1235
+ if (type) {
1236
+ return { type };
1237
+ }
1238
+ if (!file) {
1239
+ throw new Error('Provide --type <type> or a policy JSON file.');
1240
+ }
1241
+ let parsed;
1242
+ try {
1243
+ parsed = JSON.parse(await readFile(file, 'utf8'));
1244
+ }
1245
+ catch (err) {
1246
+ throw new Error(`${file} does not contain policy JSON: ` +
1247
+ `${err instanceof Error ? err.message : String(err)}`, { cause: err });
1248
+ }
1249
+ if (parsed === null ||
1250
+ typeof parsed !== 'object' ||
1251
+ Array.isArray(parsed) ||
1252
+ typeof parsed.type !== 'string') {
1253
+ throw new Error(`${file} must hold a policy object with a "type" field.`);
1254
+ }
1255
+ return parsed;
1256
+ }
1257
+ /**
1258
+ * Sets (creates or replaces) the access-control policy of a space,
1259
+ * collection, or resource, and prints the policy document that was set.
1260
+ *
1261
+ * @param options {object}
1262
+ * @param options.address {string} A space/collection/resource address.
1263
+ * @param [options.file] {string} A policy JSON file.
1264
+ * @param [options.type] {string} A simple type-only policy (e.g.
1265
+ * PublicCanRead).
1266
+ * @param [options.server] {string} The server base URL.
1267
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1268
+ * @returns {Promise<number>} The process exit code.
1269
+ */
1270
+ export async function runPolicySet(options) {
1271
+ try {
1272
+ const policy = await resolvePolicyInput({
1273
+ type: options.type,
1274
+ file: options.file
1275
+ });
1276
+ const target = await resolveWasTarget({
1277
+ address: options.address,
1278
+ server: options.server,
1279
+ did: options.did
1280
+ });
1281
+ const { handle, url } = handleForTarget(target);
1282
+ await handle.setPolicy(policy);
1283
+ console.error(`Policy set on ${url}`);
1284
+ console.log(JSON.stringify(policy, null, 2));
1285
+ return 0;
1286
+ }
1287
+ catch (err) {
1288
+ return reportError({ action: 'set the policy', err });
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Removes the access-control policy of a space, collection, or resource,
1293
+ * reverting it to capability-only access. Idempotent.
1294
+ *
1295
+ * @param options {object}
1296
+ * @param options.address {string} A space/collection/resource address.
1297
+ * @param [options.server] {string} The server base URL.
1298
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1299
+ * @returns {Promise<number>} The process exit code.
1300
+ */
1301
+ export async function runPolicyClear(options) {
1302
+ try {
1303
+ const target = await resolveWasTarget({
1304
+ address: options.address,
1305
+ server: options.server,
1306
+ did: options.did
1307
+ });
1308
+ const { handle, url } = handleForTarget(target);
1309
+ await handle.clearPolicy();
1310
+ console.error(`Cleared the policy on ${url} (capability-only access).`);
1311
+ return 0;
1312
+ }
1313
+ catch (err) {
1314
+ return reportError({ action: 'clear the policy', err });
1315
+ }
1316
+ }
1317
+ /**
1318
+ * Makes a space, collection, or resource world-readable (the
1319
+ * `PublicCanRead` policy) and prints its public URL -- the "share via
1320
+ * public link" case.
1321
+ *
1322
+ * @param options {object}
1323
+ * @param options.address {string} A space/collection/resource address.
1324
+ * @param [options.server] {string} The server base URL.
1325
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1326
+ * @returns {Promise<number>} The process exit code.
1327
+ */
1328
+ export async function runPublish(options) {
1329
+ try {
1330
+ const target = await resolveWasTarget({
1331
+ address: options.address,
1332
+ server: options.server,
1333
+ did: options.did
1334
+ });
1335
+ const { handle, url } = handleForTarget(target);
1336
+ await handle.setPublic();
1337
+ console.error(`Published (world-readable): ${url}`);
1338
+ console.log(url);
1339
+ return 0;
1340
+ }
1341
+ catch (err) {
1342
+ return reportError({ action: 'publish the path', err });
1343
+ }
1344
+ }
1345
+ /**
1346
+ * Reverts a published space, collection, or resource to capability-only
1347
+ * access (clears its policy). Idempotent.
1348
+ *
1349
+ * @param options {object}
1350
+ * @param options.address {string} A space/collection/resource address.
1351
+ * @param [options.server] {string} The server base URL.
1352
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1353
+ * @returns {Promise<number>} The process exit code.
1354
+ */
1355
+ export async function runUnpublish(options) {
1356
+ try {
1357
+ const target = await resolveWasTarget({
1358
+ address: options.address,
1359
+ server: options.server,
1360
+ did: options.did
1361
+ });
1362
+ const { handle, url } = handleForTarget(target);
1363
+ await handle.clearPolicy();
1364
+ console.error(`Unpublished (capability-only access): ${url}`);
1365
+ return 0;
1366
+ }
1367
+ catch (err) {
1368
+ return reportError({ action: 'unpublish the path', err });
1369
+ }
1370
+ }
1371
+ /**
1372
+ * Exports a whole space as a tar archive, written to `--output` or raw to
1373
+ * stdout.
1374
+ *
1375
+ * @param options {object}
1376
+ * @param options.address {string} The space address.
1377
+ * @param [options.output] {string} The output file path; stdout when
1378
+ * omitted.
1379
+ * @param [options.server] {string} The server base URL.
1380
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1381
+ * @returns {Promise<number>} The process exit code.
1382
+ */
1383
+ export async function runSpaceExport(options) {
1384
+ try {
1385
+ parseSpaceAddress(options.address);
1386
+ const target = await resolveWasTarget({
1387
+ address: options.address,
1388
+ server: options.server,
1389
+ did: options.did
1390
+ });
1391
+ const bytes = await target.client.space(target.spaceId).export();
1392
+ await writeBytesOutput({ bytes, output: options.output });
1393
+ return 0;
1394
+ }
1395
+ catch (err) {
1396
+ return reportError({ action: 'export the space', err });
1397
+ }
1398
+ }
1399
+ /**
1400
+ * Imports (merges) a tar archive into a space and prints the import stats
1401
+ * summary.
1402
+ *
1403
+ * @param options {object}
1404
+ * @param options.address {string} The space address.
1405
+ * @param [options.file] {string} The tar file; stdin when omitted.
1406
+ * @param [options.server] {string} The server base URL.
1407
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1408
+ * @returns {Promise<number>} The process exit code.
1409
+ */
1410
+ export async function runSpaceImport(options) {
1411
+ try {
1412
+ parseSpaceAddress(options.address);
1413
+ const target = await resolveWasTarget({
1414
+ address: options.address,
1415
+ server: options.server,
1416
+ did: options.did
1417
+ });
1418
+ const tar = await readInputBytes({ file: options.file });
1419
+ const stats = await target.client.space(target.spaceId).import(tar);
1420
+ console.log(JSON.stringify(stats, null, 2));
1421
+ return 0;
1422
+ }
1423
+ catch (err) {
1424
+ return reportError({ action: 'import into the space', err });
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Delegates access to a space, collection, or resource: signs a capability
1429
+ * for the `--to` DID with the given actions and expiration, and prints
1430
+ * `{ delegatedCapability, encoded }` (the same shape as `zcap delegate`).
1431
+ * `--save` stores the capability in the local zcap store
1432
+ * (`~/.config/did-cli-wallet/zcaps/`).
1433
+ *
1434
+ * @param options {object}
1435
+ * @param options.address {string} The space/collection/resource address.
1436
+ * @param options.to {string} The delegatee DID (or stored-DID handle).
1437
+ * @param options.action {string[]} Allowed actions (HTTP verbs; lowercase
1438
+ * accepted).
1439
+ * @param [options.ttl] {string} Time-to-live for expiration (default 1y).
1440
+ * @param [options.expires] {string} Explicit ISO 8601 expiration
1441
+ * (overrides --ttl).
1442
+ * @param [options.save] {boolean} Save the capability to the zcap store.
1443
+ * @param [options.handle] {string} Short tag for the saved zcap.
1444
+ * @param [options.description] {string} Longer description for the saved
1445
+ * zcap.
1446
+ * @param [options.server] {string} The server base URL.
1447
+ * @param [options.did] {string} The signing DID or stored-DID handle.
1448
+ * @returns {Promise<number>} The process exit code.
1449
+ */
1450
+ export async function runGrant(options) {
1451
+ try {
1452
+ const actions = normalizeActions(options.action);
1453
+ const delegatee = await resolveDidRef({ ref: options.to });
1454
+ if (!delegatee) {
1455
+ throw new Error(`No locally stored DID found for "${options.to}".`);
1456
+ }
1457
+ const expires = options.expires ?? expiresFromTtl(options.ttl ?? '1y').toISOString();
1458
+ const target = await resolveWasTarget({
1459
+ address: options.address,
1460
+ server: options.server,
1461
+ did: options.did
1462
+ });
1463
+ const url = wasUrl({
1464
+ server: target.server,
1465
+ spaceId: target.spaceId,
1466
+ collectionId: target.collectionId,
1467
+ resourceId: target.resourceId
1468
+ });
1469
+ const delegatedCapability = await target.client.grant({
1470
+ to: delegatee,
1471
+ actions,
1472
+ expires,
1473
+ target: url
1474
+ });
1475
+ const encoded = encodeCapability(delegatedCapability);
1476
+ if (options.save) {
1477
+ const storageId = storageIdFor(delegatedCapability.id);
1478
+ const filePath = await saveToCollection('zcaps', storageId, delegatedCapability);
1479
+ await writeCreateMeta({
1480
+ storageId,
1481
+ created: new Date().toISOString(),
1482
+ handle: options.handle,
1483
+ description: options.description
1484
+ });
1485
+ console.error(`Capability saved to ${filePath}`);
1486
+ }
1487
+ console.log(JSON.stringify({ delegatedCapability, encoded }, null, 2));
1488
+ return 0;
1489
+ }
1490
+ catch (err) {
1491
+ return reportError({ action: 'grant access', err });
1492
+ }
1493
+ }
1494
+ export function makeWasCommand() {
1495
+ const was = new Command('was').description('Wallet Attached Storage (WAS) operations');
1496
+ const space = new Command('space').description('Manage WAS spaces');
1497
+ space
1498
+ .command('create')
1499
+ .description('Create a new space on a WAS server')
1500
+ .option('--name <name>', "the space's display name")
1501
+ .addOption(serverOption())
1502
+ .addOption(didOption())
1503
+ .option('--id <id>', 'a caller-chosen space id (server-generated otherwise)')
1504
+ .option('--save', 'register the space in the local wallet (~/.config/did-cli-wallet/was-spaces/)')
1505
+ .option('--handle <handle>', 'short tag for the registered space (requires --save)')
1506
+ .option('--description <description>', 'longer description of the registered space (requires --save)')
1507
+ .action(async (options) => {
1508
+ if ((options.handle !== undefined || options.description !== undefined) &&
1509
+ !options.save) {
1510
+ console.error('--handle and --description require --save');
1511
+ process.exit(2);
1512
+ return;
1513
+ }
1514
+ await runAndExit(runSpaceCreate(options));
1515
+ });
1516
+ space
1517
+ .command('list')
1518
+ .description('List locally registered spaces')
1519
+ .option('--json', 'output the list as a JSON array of objects with metadata')
1520
+ .option('--plain', 'output one space id per line, sorted (no metadata)')
1521
+ .option('--remote', 'list the spaces on the server instead (requires server support)')
1522
+ .option('--server <url>', 'the WAS server base URL (with --remote)')
1523
+ .option('--did <did>', 'DID or stored-DID handle to sign with (with --remote)')
1524
+ .action(async (options) => {
1525
+ await runAndExit(runSpaceList(options));
1526
+ });
1527
+ space
1528
+ .command('show <space>')
1529
+ .aliases(['view', 'cat'])
1530
+ .description("Show a space's description from the server (--meta for its local " +
1531
+ 'registry metadata)')
1532
+ .option('--meta', 'show the local registry metadata instead')
1533
+ .option('--json', 'with --meta, output the metadata as JSON')
1534
+ .addOption(serverOption())
1535
+ .addOption(didOption())
1536
+ .action(async (address, options) => {
1537
+ await runAndExit(runSpaceShow({ address, ...options }));
1538
+ });
1539
+ space
1540
+ .command('update <space>')
1541
+ .alias('configure')
1542
+ .description("Update a space's description fields on the server (upsert)")
1543
+ .option('--name <name>', "the space's new display name")
1544
+ .addOption(serverOption())
1545
+ .addOption(didOption())
1546
+ .action(async (address, options) => {
1547
+ await runAndExit(runSpaceUpdate({ address, ...options }));
1548
+ });
1549
+ space
1550
+ .command('delete <space>')
1551
+ .alias('rm')
1552
+ .description('Delete a space on the server (idempotent) and remove its local ' +
1553
+ 'registry entry')
1554
+ .addOption(serverOption())
1555
+ .addOption(didOption())
1556
+ .action(async (address, options) => {
1557
+ await runAndExit(runSpaceDelete({ address, ...options }));
1558
+ });
1559
+ space
1560
+ .command('forget <space>')
1561
+ .description('Remove only the local registry entry of a space (the server-side ' +
1562
+ 'space is untouched)')
1563
+ .action(async (address) => {
1564
+ await runAndExit(runSpaceForget({ address }));
1565
+ });
1566
+ space
1567
+ .command('add <space>')
1568
+ .description('Register an existing remote space (a full space URL, or a space id ' +
1569
+ 'plus --server) in the local registry')
1570
+ .addOption(serverOption())
1571
+ .addOption(didOption())
1572
+ .option('--handle <handle>', 'short tag for the registered space')
1573
+ .option('--description <description>', 'longer description of the registered space')
1574
+ .action(async (address, options) => {
1575
+ await runAndExit(runSpaceAdd({ address, ...options }));
1576
+ });
1577
+ space
1578
+ .command('export <space>')
1579
+ .description('Export a whole space as a tar archive')
1580
+ .option('--output <file>', 'write the tar to a file (stdout otherwise)')
1581
+ .addOption(serverOption())
1582
+ .addOption(didOption())
1583
+ .action(async (address, options) => {
1584
+ await runAndExit(runSpaceExport({ address, ...options }));
1585
+ });
1586
+ space
1587
+ .command('import <space> [file]')
1588
+ .description('Import (merge) a tar archive into a space; tar from file or stdin')
1589
+ .addOption(serverOption())
1590
+ .addOption(didOption())
1591
+ .action(async (address, file, options) => {
1592
+ await runAndExit(runSpaceImport({ address, file, ...options }));
1593
+ });
1594
+ was.addCommand(space);
1595
+ const collection = new Command('collection')
1596
+ .alias('coll')
1597
+ .description('Manage WAS collections');
1598
+ collection
1599
+ .command('create <space>')
1600
+ .description('Create a new collection within a space')
1601
+ .option('--name <name>', "the collection's display name")
1602
+ .option('--id <id>', 'a caller-chosen collection id (server-generated otherwise)')
1603
+ .addOption(serverOption())
1604
+ .addOption(didOption())
1605
+ .action(async (address, options) => {
1606
+ await runAndExit(runCollectionCreate({ address, ...options }));
1607
+ });
1608
+ collection
1609
+ .command('list <space>')
1610
+ .description('List the collections in a space')
1611
+ .option('--json', 'output the raw listing JSON')
1612
+ .option('--plain', 'output one collection id per line, sorted')
1613
+ .addOption(serverOption())
1614
+ .addOption(didOption())
1615
+ .action(async (address, options) => {
1616
+ await runAndExit(runCollectionList({ address, ...options }));
1617
+ });
1618
+ collection
1619
+ .command('show <path>')
1620
+ .aliases(['view', 'cat'])
1621
+ .description("Show a collection's description from the server (SPACE/COLLECTION)")
1622
+ .addOption(serverOption())
1623
+ .addOption(didOption())
1624
+ .action(async (address, options) => {
1625
+ await runAndExit(runCollectionShow({ address, ...options }));
1626
+ });
1627
+ collection
1628
+ .command('update <path>')
1629
+ .alias('configure')
1630
+ .description("Update a collection's description fields on the server (upsert)")
1631
+ .requiredOption('--name <name>', "the collection's new display name")
1632
+ .addOption(serverOption())
1633
+ .addOption(didOption())
1634
+ .action(async (address, options) => {
1635
+ await runAndExit(runCollectionUpdate({ address, ...options }));
1636
+ });
1637
+ collection
1638
+ .command('delete <path>')
1639
+ .alias('rm')
1640
+ .description('Delete a whole collection and its contents on the server (idempotent)')
1641
+ .addOption(serverOption())
1642
+ .addOption(didOption())
1643
+ .action(async (address, options) => {
1644
+ await runAndExit(runCollectionDelete({ address, ...options }));
1645
+ });
1646
+ was.addCommand(collection);
1647
+ const resource = new Command('resource')
1648
+ .alias('res')
1649
+ .description('Manage WAS resources');
1650
+ resource
1651
+ .command('add [path] [file]')
1652
+ .description('Add a resource to a collection (server-generated id); payload from ' +
1653
+ 'file or stdin')
1654
+ .addOption(contentTypeOption())
1655
+ .addOption(capabilityOption())
1656
+ .addOption(serverOption())
1657
+ .addOption(didOption())
1658
+ .action(async (address, file, options) => {
1659
+ // With --capability the path is omitted, so a single positional
1660
+ // argument is the payload file.
1661
+ if (options.capability && file === undefined) {
1662
+ file = address;
1663
+ address = undefined;
1664
+ }
1665
+ await runAndExit(runResourceAdd({ address, file, ...options }));
1666
+ });
1667
+ resource
1668
+ .command('put [path] [file]')
1669
+ .description('Create or replace a resource at a known id (upsert); payload from ' +
1670
+ 'file or stdin')
1671
+ .addOption(contentTypeOption())
1672
+ .addOption(capabilityOption())
1673
+ .addOption(serverOption())
1674
+ .addOption(didOption())
1675
+ .action(async (address, file, options) => {
1676
+ // With --capability the path is omitted, so a single positional
1677
+ // argument is the payload file.
1678
+ if (options.capability && file === undefined) {
1679
+ file = address;
1680
+ address = undefined;
1681
+ }
1682
+ await runAndExit(runResourcePut({ address, file, ...options }));
1683
+ });
1684
+ resource
1685
+ .command('get [path]')
1686
+ .description('Read a resource: JSON pretty-printed to stdout, binary written raw')
1687
+ .option('--output <file>', 'write the resource content to a file')
1688
+ .addOption(capabilityOption())
1689
+ .addOption(serverOption())
1690
+ .addOption(didOption())
1691
+ .action(async (address, options) => {
1692
+ await runAndExit(runResourceGet({ address, ...options }));
1693
+ });
1694
+ resource
1695
+ .command('list <path>')
1696
+ .description('List the resources in a collection (SPACE/COLLECTION)')
1697
+ .option('--json', 'output the raw listing JSON')
1698
+ .option('--plain', 'output one resource id per line, sorted')
1699
+ .addOption(serverOption())
1700
+ .addOption(didOption())
1701
+ .action(async (address, options) => {
1702
+ await runAndExit(runResourceList({ address, ...options }));
1703
+ });
1704
+ resource
1705
+ .command('delete <path>')
1706
+ .alias('rm')
1707
+ .description('Delete a resource on the server (idempotent)')
1708
+ .addOption(serverOption())
1709
+ .addOption(didOption())
1710
+ .action(async (address, options) => {
1711
+ await runAndExit(runResourceDelete({ address, ...options }));
1712
+ });
1713
+ was.addCommand(resource);
1714
+ was
1715
+ .command('ls [path]')
1716
+ .description('List the collections of a space, or the resources of a collection')
1717
+ .option('--json', 'output the raw listing JSON')
1718
+ .option('--plain', 'output one id per line, sorted')
1719
+ .addOption(capabilityOption())
1720
+ .addOption(serverOption())
1721
+ .addOption(didOption())
1722
+ .action(async (address, options) => {
1723
+ await runAndExit(runLs({ address, ...options }));
1724
+ });
1725
+ was
1726
+ .command('get [path]')
1727
+ .description('Read a resource (shorthand for "resource get")')
1728
+ .option('--output <file>', 'write the resource content to a file')
1729
+ .addOption(capabilityOption())
1730
+ .addOption(serverOption())
1731
+ .addOption(didOption())
1732
+ .action(async (address, options) => {
1733
+ await runAndExit(runResourceGet({ address, ...options }));
1734
+ });
1735
+ was
1736
+ .command('put [path] [file]')
1737
+ .description('Create or replace a resource (shorthand for "resource put")')
1738
+ .addOption(contentTypeOption())
1739
+ .addOption(capabilityOption())
1740
+ .addOption(serverOption())
1741
+ .addOption(didOption())
1742
+ .action(async (address, file, options) => {
1743
+ // With --capability the path is omitted, so a single positional
1744
+ // argument is the payload file.
1745
+ if (options.capability && file === undefined) {
1746
+ file = address;
1747
+ address = undefined;
1748
+ }
1749
+ await runAndExit(runResourcePut({ address, file, ...options }));
1750
+ });
1751
+ was
1752
+ .command('rm [path]')
1753
+ .description('Delete whatever the path points at: a space, a collection, or a ' +
1754
+ 'resource')
1755
+ .addOption(capabilityOption())
1756
+ .addOption(serverOption())
1757
+ .addOption(didOption())
1758
+ .action(async (address, options) => {
1759
+ await runAndExit(runRm({ address, ...options }));
1760
+ });
1761
+ const policy = new Command('policy').description('Manage access-control policies (at space, collection, or resource depth)');
1762
+ policy
1763
+ .command('show <path>')
1764
+ .aliases(['view', 'cat'])
1765
+ .description('Show the access-control policy of a path')
1766
+ .addOption(serverOption())
1767
+ .addOption(didOption())
1768
+ .action(async (address, options) => {
1769
+ await runAndExit(runPolicyShow({ address, ...options }));
1770
+ });
1771
+ policy
1772
+ .command('set <path> [file]')
1773
+ .description('Set the access-control policy of a path: --type for a simple ' +
1774
+ 'type-only policy, or a policy JSON file')
1775
+ .option('--type <type>', 'a simple type-only policy, e.g. PublicCanRead')
1776
+ .addOption(serverOption())
1777
+ .addOption(didOption())
1778
+ .action(async (address, file, options) => {
1779
+ await runAndExit(runPolicySet({ address, file, ...options }));
1780
+ });
1781
+ policy
1782
+ .command('clear <path>')
1783
+ .description('Remove the access-control policy of a path (back to capability-only ' +
1784
+ 'access; idempotent)')
1785
+ .addOption(serverOption())
1786
+ .addOption(didOption())
1787
+ .action(async (address, options) => {
1788
+ await runAndExit(runPolicyClear({ address, ...options }));
1789
+ });
1790
+ was.addCommand(policy);
1791
+ was
1792
+ .command('publish <path>')
1793
+ .description('Make a space, collection, or resource world-readable and print its ' +
1794
+ 'public URL')
1795
+ .addOption(serverOption())
1796
+ .addOption(didOption())
1797
+ .action(async (address, options) => {
1798
+ await runAndExit(runPublish({ address, ...options }));
1799
+ });
1800
+ was
1801
+ .command('unpublish <path>')
1802
+ .description('Revert a published space, collection, or resource to capability-only ' +
1803
+ 'access')
1804
+ .addOption(serverOption())
1805
+ .addOption(didOption())
1806
+ .action(async (address, options) => {
1807
+ await runAndExit(runUnpublish({ address, ...options }));
1808
+ });
1809
+ was
1810
+ .command('grant <path>')
1811
+ .description('Delegate access to a space, collection, or resource (prints the ' +
1812
+ 'signed capability and its encoded form)')
1813
+ .requiredOption('--to <did>', 'the DID (or stored-DID handle) to delegate to')
1814
+ .requiredOption('--action <verb...>', 'allowed action(s): GET, PUT, POST, DELETE (lowercase accepted)')
1815
+ .option('--ttl <duration>', 'time to live, e.g. 1y, 30d, 24h', '1y')
1816
+ .option('--expires <iso>', 'explicit ISO 8601 expiration (overrides --ttl)')
1817
+ .option('--save', 'save the capability to local wallet storage (~/.config/did-cli-wallet/zcaps/)')
1818
+ .option('--handle <handle>', 'short tag for the saved capability (requires --save)')
1819
+ .option('--description <description>', 'longer description of the saved capability (requires --save)')
1820
+ .addOption(serverOption())
1821
+ .addOption(didOption())
1822
+ .action(async (address, options) => {
1823
+ if ((options.handle !== undefined || options.description !== undefined) &&
1824
+ !options.save) {
1825
+ console.error('--handle and --description require --save');
1826
+ process.exit(2);
1827
+ return;
1828
+ }
1829
+ await runAndExit(runGrant({ address, ...options }));
1830
+ });
1831
+ return was;
1832
+ }
1833
+ //# sourceMappingURL=was.js.map