@didcid/keymaster 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,150 +125,105 @@ keymaster list-ids
125
125
 
126
126
  #### Commands
127
127
 
128
- ##### Wallet Management
129
-
130
- | Command | Description |
131
- |---------|-------------|
132
- | `create-wallet` | Create a new wallet (or show existing) |
133
- | `new-wallet` | Create a new wallet |
134
- | `show-wallet` | Display wallet contents |
135
- | `check-wallet` | Validate DIDs in wallet |
136
- | `fix-wallet` | Remove invalid DIDs from wallet |
137
- | `import-wallet <phrase>` | Create wallet from recovery phrase |
138
- | `show-mnemonic` | Show recovery phrase |
139
- | `backup-wallet-file <file>` | Backup wallet to file |
140
- | `restore-wallet-file <file>` | Restore wallet from file |
141
- | `backup-wallet-did` | Backup wallet to encrypted DID |
142
- | `recover-wallet-did [did]` | Recover wallet from DID |
143
-
144
- ##### Identity Management
145
-
146
- | Command | Description |
147
- |---------|-------------|
148
- | `create-id <name>` | Create a new identity |
149
- | `list-ids` | List all identities |
150
- | `use-id <name>` | Set current identity |
151
- | `remove-id <name>` | Delete an identity |
152
- | `rename-id <old> <new>` | Rename an identity |
153
- | `resolve-id` | Resolve current identity |
154
- | `rotate-keys` | Generate new keys for current ID |
155
- | `backup-id` | Backup current ID to registry |
156
- | `recover-id <did>` | Recover ID from DID |
157
-
158
- ##### DID Operations
159
-
160
- | Command | Description |
161
- |---------|-------------|
162
- | `resolve-did <did>` | Resolve a DID document |
163
- | `resolve-did-version <did> <ver>` | Resolve specific version |
164
- | `revoke-did <did>` | Permanently revoke a DID |
165
-
166
- ##### Encryption & Signing
167
-
168
- | Command | Description |
169
- |---------|-------------|
170
- | `encrypt-message <msg> <did>` | Encrypt message for recipient |
171
- | `encrypt-file <file> <did>` | Encrypt file for recipient |
172
- | `decrypt-did <did>` | Decrypt an encrypted message |
173
- | `decrypt-json <did>` | Decrypt encrypted JSON |
174
- | `sign-file <file>` | Sign a JSON file |
175
- | `verify-file <file>` | Verify signature in file |
176
-
177
- ##### Credentials
178
-
179
- | Command | Description |
180
- |---------|-------------|
181
- | `bind-credential <schema> <subject>` | Create bound credential |
182
- | `issue-credential <file>` | Issue a credential |
183
- | `list-issued` | List issued credentials |
184
- | `revoke-credential <did>` | Revoke a credential |
185
- | `accept-credential <did>` | Accept a credential |
186
- | `list-credentials` | List held credentials |
187
- | `get-credential <did>` | Get credential by DID |
188
- | `publish-credential <did>` | Publish credential existence |
189
- | `reveal-credential <did>` | Reveal credential publicly |
190
- | `unpublish-credential <did>` | Remove from manifest |
191
-
192
- ##### Challenges & Responses
193
-
194
- | Command | Description |
195
- |---------|-------------|
196
- | `create-challenge [file]` | Create a challenge |
197
- | `create-challenge-cc <did>` | Create challenge from credential |
198
- | `create-response <challenge>` | Respond to a challenge |
199
- | `verify-response <response>` | Verify a response |
200
-
201
- ##### Aliases
202
-
203
- | Command | Description |
204
- |---------|-------------|
205
- | `add-alias <alias> <did>` | Add alias for DID |
206
- | `get-alias <alias>` | Get DID by alias |
207
- | `remove-alias <alias>` | Remove alias |
208
- | `list-aliases` | List all aliases |
209
-
210
- ##### Groups
211
-
212
- | Command | Description |
213
- |---------|-------------|
214
- | `create-group <name>` | Create a group |
215
- | `list-groups` | List owned groups |
216
- | `get-group <did>` | Get group details |
217
- | `add-group-member <group> <member>` | Add member to group |
218
- | `remove-group-member <group> <member>` | Remove member |
219
- | `test-group <group> [member]` | Test group membership |
220
-
221
- ##### Schemas
222
-
223
- | Command | Description |
224
- |---------|-------------|
225
- | `create-schema <file>` | Create schema from file |
226
- | `list-schemas` | List owned schemas |
227
- | `get-schema <did>` | Get schema by DID |
228
- | `create-schema-template <schema>` | Generate template |
229
-
230
- ##### Assets
231
-
232
- | Command | Description |
233
- |---------|-------------|
234
- | `create-asset` | Create empty asset |
235
- | `create-asset-json <file>` | Create from JSON file |
236
- | `create-asset-image <file>` | Create from image |
237
- | `create-asset-file <file>` | Create from file |
238
- | `get-asset <id>` | Get asset by ID |
239
- | `update-asset-json <id> <file>` | Update with JSON |
240
- | `update-asset-image <id> <file>` | Update with image |
241
- | `update-asset-file <id> <file>` | Update with file |
242
- | `transfer-asset <id> <controller>` | Transfer ownership |
243
- | `clone-asset <id>` | Clone an asset |
244
- | `set-property <id> <key> [value]` | Set asset property |
245
- | `list-assets` | List owned assets |
246
-
247
- ##### Polls
248
-
249
- | Command | Description |
250
- |---------|-------------|
251
- | `create-poll-template` | Create poll template |
252
- | `create-poll <file>` | Create poll from file |
253
- | `view-poll <poll>` | View poll details |
254
- | `vote-poll <poll> <vote>` | Vote in poll |
255
- | `update-poll <ballot>` | Add ballot to poll |
256
- | `publish-poll <poll>` | Publish results (hidden) |
257
- | `reveal-poll <poll>` | Publish results (revealed) |
258
- | `unpublish-poll <poll>` | Remove results |
259
-
260
- ##### Vaults
261
-
262
- | Command | Description |
263
- |---------|-------------|
264
- | `create-vault` | Create a vault |
265
- | `list-vault-items <id>` | List vault items |
266
- | `add-vault-member <id> <member>` | Add vault member |
267
- | `remove-vault-member <id> <member>` | Remove member |
268
- | `list-vault-members <id>` | List members |
269
- | `add-vault-item <id> <file>` | Add file to vault |
270
- | `remove-vault-item <id> <item>` | Remove item |
271
- | `get-vault-item <id> <item> <file>` | Download item |
128
+ | Category | Command | Description |
129
+ |----------|---------|-------------|
130
+ | Wallet | `create-wallet` | Create a new wallet (or show existing) |
131
+ | Wallet | `new-wallet` | Create a new wallet |
132
+ | Wallet | `show-wallet` | Display wallet contents |
133
+ | Wallet | `check-wallet` | Validate DIDs in wallet |
134
+ | Wallet | `fix-wallet` | Remove invalid DIDs from wallet |
135
+ | Wallet | `import-wallet <phrase>` | Create wallet from recovery phrase |
136
+ | Wallet | `show-mnemonic` | Show recovery phrase |
137
+ | Wallet | `backup-wallet-file <file>` | Backup wallet to file |
138
+ | Wallet | `restore-wallet-file <file>` | Restore wallet from file |
139
+ | Wallet | `backup-wallet-did` | Backup wallet to encrypted DID |
140
+ | Wallet | `recover-wallet-did [did]` | Recover wallet from DID |
141
+ | Wallet | `change-passphrase <new>` | Re-encrypt wallet with a new passphrase |
142
+ | Identity | `create-id <name>` | Create a new identity |
143
+ | Identity | `list-ids` | List all identities |
144
+ | Identity | `use-id <name>` | Set current identity |
145
+ | Identity | `remove-id <name>` | Delete an identity |
146
+ | Identity | `rename-id <old> <new>` | Rename an identity |
147
+ | Identity | `resolve-id` | Resolve current identity |
148
+ | Identity | `rotate-keys` | Generate new keys for current ID |
149
+ | Identity | `backup-id` | Backup current ID to registry |
150
+ | Identity | `recover-id <did>` | Recover ID from DID |
151
+ | DID | `resolve-did <did>` | Resolve a DID document |
152
+ | DID | `resolve-did-version <did> <ver>` | Resolve specific version |
153
+ | DID | `revoke-did <did>` | Permanently revoke a DID |
154
+ | Encryption | `encrypt-message <msg> <did>` | Encrypt message for recipient |
155
+ | Encryption | `encrypt-file <file> <did>` | Encrypt file for recipient |
156
+ | Encryption | `decrypt-did <did>` | Decrypt an encrypted message |
157
+ | Encryption | `decrypt-json <did>` | Decrypt encrypted JSON |
158
+ | Encryption | `sign-file <file>` | Sign a JSON file |
159
+ | Encryption | `verify-file <file>` | Verify signature in file |
160
+ | Credentials | `bind-credential <schema> <subject>` | Create bound credential |
161
+ | Credentials | `issue-credential <file>` | Issue a credential |
162
+ | Credentials | `list-issued` | List issued credentials |
163
+ | Credentials | `revoke-credential <did>` | Revoke a credential |
164
+ | Credentials | `accept-credential <did>` | Accept a credential |
165
+ | Credentials | `list-credentials` | List held credentials |
166
+ | Credentials | `get-credential <did>` | Get credential by DID |
167
+ | Credentials | `publish-credential <did>` | Publish credential existence |
168
+ | Credentials | `reveal-credential <did>` | Reveal credential publicly |
169
+ | Credentials | `unpublish-credential <did>` | Remove from manifest |
170
+ | Challenges | `create-challenge [file]` | Create a challenge |
171
+ | Challenges | `create-challenge-cc <did>` | Create challenge from credential |
172
+ | Challenges | `create-response <challenge>` | Respond to a challenge |
173
+ | Challenges | `verify-response <response>` | Verify a response |
174
+ | Aliases | `add-alias <alias> <did>` | Add alias for DID |
175
+ | Aliases | `get-alias <alias>` | Get DID by alias |
176
+ | Aliases | `remove-alias <alias>` | Remove alias |
177
+ | Aliases | `list-aliases` | List all aliases |
178
+ | Groups | `create-group <name>` | Create a group |
179
+ | Groups | `list-groups` | List owned groups |
180
+ | Groups | `get-group <did>` | Get group details |
181
+ | Groups | `add-group-member <group> <member>` | Add member to group |
182
+ | Groups | `remove-group-member <group> <member>` | Remove member |
183
+ | Groups | `test-group <group> [member]` | Test group membership |
184
+ | Schemas | `create-schema <file>` | Create schema from file |
185
+ | Schemas | `list-schemas` | List owned schemas |
186
+ | Schemas | `get-schema <did>` | Get schema by DID |
187
+ | Schemas | `create-schema-template <schema>` | Generate template |
188
+ | Assets | `create-asset` | Create empty asset |
189
+ | Assets | `create-asset-json <file>` | Create from JSON file |
190
+ | Assets | `create-asset-image <file>` | Create from image |
191
+ | Assets | `create-asset-file <file>` | Create from file |
192
+ | Assets | `get-asset <id>` | Get asset by ID |
193
+ | Assets | `update-asset-json <id> <file>` | Update with JSON |
194
+ | Assets | `update-asset-image <id> <file>` | Update with image |
195
+ | Assets | `update-asset-file <id> <file>` | Update with file |
196
+ | Assets | `transfer-asset <id> <controller>` | Transfer ownership |
197
+ | Assets | `clone-asset <id>` | Clone an asset |
198
+ | Assets | `set-property <id> <key> [value]` | Set asset property |
199
+ | Assets | `list-assets` | List owned assets |
200
+ | Polls | `create-poll-template` | Create poll template |
201
+ | Polls | `create-poll <file>` | Create poll from file |
202
+ | Polls | `view-poll <poll>` | View poll details |
203
+ | Polls | `vote-poll <poll> <vote>` | Vote in poll |
204
+ | Polls | `update-poll <ballot>` | Add ballot to poll |
205
+ | Polls | `publish-poll <poll>` | Publish results (hidden) |
206
+ | Polls | `reveal-poll <poll>` | Publish results (revealed) |
207
+ | Polls | `unpublish-poll <poll>` | Remove results |
208
+ | Vaults | `create-vault` | Create a vault |
209
+ | Vaults | `list-vault-items <id>` | List vault items |
210
+ | Vaults | `add-vault-member <id> <member>` | Add vault member |
211
+ | Vaults | `remove-vault-member <id> <member>` | Remove member |
212
+ | Vaults | `list-vault-members <id>` | List members |
213
+ | Vaults | `add-vault-item <id> <file>` | Add file to vault |
214
+ | Vaults | `remove-vault-item <id> <item>` | Remove item |
215
+ | Vaults | `get-vault-item <id> <item> <file>` | Download item |
216
+ | Lightning | `add-lightning [id]` | Create a Lightning wallet for a DID |
217
+ | Lightning | `remove-lightning [id]` | Remove Lightning wallet from a DID |
218
+ | Lightning | `lightning-balance [id]` | Check Lightning wallet balance |
219
+ | Lightning | `lightning-invoice <amount> <memo> [id]` | Create invoice to receive sats |
220
+ | Lightning | `lightning-pay <bolt11> [id]` | Pay a Lightning invoice |
221
+ | Lightning | `lightning-check <hash> [id]` | Check status of a payment |
222
+ | Lightning | `lightning-decode <bolt11>` | Decode a BOLT11 invoice |
223
+ | Lightning | `lightning-zap <recipient> <amount> [memo]` | Send sats to a DID, alias, or Lightning Address |
224
+ | Lightning | `lightning-payments [id]` | Show payment history |
225
+ | Lightning | `publish-lightning [id]` | Publish Lightning service endpoint for a DID |
226
+ | Lightning | `unpublish-lightning [id]` | Remove Lightning service endpoint from a DID |
272
227
 
273
228
  #### Command Options
274
229
 
@@ -78,6 +78,15 @@ class KeymasterClient {
78
78
  console.log('Keymaster service is ready!');
79
79
  }
80
80
  }
81
+ async getVersion() {
82
+ try {
83
+ const response = await this.axios.get(`${this.API}/version`);
84
+ return response.data;
85
+ }
86
+ catch (error) {
87
+ throwError(error);
88
+ }
89
+ }
81
90
  async isReady() {
82
91
  try {
83
92
  const response = await this.axios.get(`${this.API}/ready`);
@@ -276,6 +285,15 @@ class KeymasterClient {
276
285
  throwError(error);
277
286
  }
278
287
  }
288
+ async changeRegistry(id, registry) {
289
+ try {
290
+ const response = await this.axios.post(`${this.API}/ids/${id}/change-registry`, { registry });
291
+ return response.data.ok;
292
+ }
293
+ catch (error) {
294
+ throwError(error);
295
+ }
296
+ }
279
297
  async backupId(id) {
280
298
  try {
281
299
  if (!id) {
@@ -1024,7 +1042,7 @@ class KeymasterClient {
1024
1042
  const response = await this.axios.post(`${this.API}/files`, data, {
1025
1043
  headers: {
1026
1044
  'Content-Type': 'application/octet-stream',
1027
- 'X-Options': JSON.stringify(options), // Pass options as a custom header
1045
+ 'X-Options': JSON.stringify(options),
1028
1046
  }
1029
1047
  });
1030
1048
  return response.data.did;
@@ -1033,12 +1051,28 @@ class KeymasterClient {
1033
1051
  throwError(error);
1034
1052
  }
1035
1053
  }
1054
+ async createFileStream(stream, options = {}) {
1055
+ try {
1056
+ const response = await this.axios.post(`${this.API}/files`, stream, {
1057
+ headers: {
1058
+ 'Content-Type': 'application/octet-stream',
1059
+ 'X-Options': JSON.stringify(options),
1060
+ },
1061
+ maxBodyLength: Infinity,
1062
+ maxContentLength: Infinity,
1063
+ });
1064
+ return response.data.did;
1065
+ }
1066
+ catch (error) {
1067
+ throwError(error);
1068
+ }
1069
+ }
1036
1070
  async updateFile(id, data, options = {}) {
1037
1071
  try {
1038
1072
  const response = await this.axios.put(`${this.API}/files/${id}`, data, {
1039
1073
  headers: {
1040
1074
  'Content-Type': 'application/octet-stream',
1041
- 'X-Options': JSON.stringify(options), // Pass options as a custom header
1075
+ 'X-Options': JSON.stringify(options),
1042
1076
  }
1043
1077
  });
1044
1078
  return response.data.ok;
@@ -1047,6 +1081,22 @@ class KeymasterClient {
1047
1081
  throwError(error);
1048
1082
  }
1049
1083
  }
1084
+ async updateFileStream(id, stream, options = {}) {
1085
+ try {
1086
+ const response = await this.axios.put(`${this.API}/files/${id}`, stream, {
1087
+ headers: {
1088
+ 'Content-Type': 'application/octet-stream',
1089
+ 'X-Options': JSON.stringify(options),
1090
+ },
1091
+ maxBodyLength: Infinity,
1092
+ maxContentLength: Infinity,
1093
+ });
1094
+ return response.data.ok;
1095
+ }
1096
+ catch (error) {
1097
+ throwError(error);
1098
+ }
1099
+ }
1050
1100
  async getFile(id) {
1051
1101
  try {
1052
1102
  const response = await this.axios.get(`${this.API}/files/${id}`);
@@ -2678,6 +2678,23 @@ function base64urlToHex(b64) {
2678
2678
  const bytes = base64url.baseDecode(b64);
2679
2679
  return Buffer.from(bytes).toString('hex');
2680
2680
  }
2681
+ function isPrivateHostname(hostname) {
2682
+ return /^(localhost|127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(hostname);
2683
+ }
2684
+ const REMOTE_NAME_LOOKUP_TIMEOUT_MS = 2000;
2685
+ function isRemoteNameReference(value) {
2686
+ if (typeof value !== 'string') {
2687
+ return false;
2688
+ }
2689
+ if (value.startsWith('did:') || value.startsWith('mailto:') || value.includes('://')) {
2690
+ return false;
2691
+ }
2692
+ if (/[/?#]/.test(value)) {
2693
+ return false;
2694
+ }
2695
+ const parts = value.split('@');
2696
+ return parts.length === 2 && !!parts[0] && !!parts[1];
2697
+ }
2681
2698
  const DefaultSchema = {
2682
2699
  "$schema": "http://json-schema.org/draft-07/schema#",
2683
2700
  "type": "object",
@@ -3361,6 +3378,24 @@ class Keymaster {
3361
3378
  const file = await this.generateFileAsset(filename, buffer);
3362
3379
  return this.mergeData(id, { file });
3363
3380
  }
3381
+ async generateFileAssetFromStream(filename, stream, contentType, bytes) {
3382
+ const cid = await this.gatekeeper.addDataStream(stream);
3383
+ return { cid, filename, type: contentType, bytes };
3384
+ }
3385
+ async createFileStream(stream, options = {}) {
3386
+ const filename = options.filename || 'file';
3387
+ const contentType = options.contentType || 'application/octet-stream';
3388
+ const bytes = options.bytes || 0;
3389
+ const file = await this.generateFileAssetFromStream(filename, stream, contentType, bytes);
3390
+ return this.createAsset({ file }, options);
3391
+ }
3392
+ async updateFileStream(id, stream, options = {}) {
3393
+ const filename = options.filename || 'file';
3394
+ const contentType = options.contentType || 'application/octet-stream';
3395
+ const bytes = options.bytes || 0;
3396
+ const file = await this.generateFileAssetFromStream(filename, stream, contentType, bytes);
3397
+ return this.mergeData(id, { file });
3398
+ }
3364
3399
  async getFile(id) {
3365
3400
  const asset = await this.resolveAsset(id);
3366
3401
  const file = asset.file;
@@ -3565,6 +3600,23 @@ class Keymaster {
3565
3600
  }
3566
3601
  return ok;
3567
3602
  }
3603
+ async changeRegistry(id, registry) {
3604
+ if (!registry) {
3605
+ throw new InvalidParameterError('registry');
3606
+ }
3607
+ const did = await this.lookupDID(id);
3608
+ const current = await this.resolveDID(did);
3609
+ const currentRegistry = current.didDocumentRegistration?.registry;
3610
+ if (registry === currentRegistry) {
3611
+ return true;
3612
+ }
3613
+ return this.updateDID(did, {
3614
+ didDocumentRegistration: {
3615
+ ...current.didDocumentRegistration,
3616
+ registry,
3617
+ },
3618
+ });
3619
+ }
3568
3620
  async addToOwned(did, owner) {
3569
3621
  await this.mutateWallet(async (wallet) => {
3570
3622
  const id = await this.fetchIdInfo(owner, wallet);
@@ -3623,8 +3675,47 @@ class Keymaster {
3623
3675
  if (wallet.ids && name in wallet.ids) {
3624
3676
  return wallet.ids[name].did;
3625
3677
  }
3678
+ const remoteDid = await this.resolveRemoteName(name);
3679
+ if (remoteDid) {
3680
+ return remoteDid;
3681
+ }
3626
3682
  throw new UnknownIDError();
3627
3683
  }
3684
+ async resolveRemoteName(name) {
3685
+ if (!isRemoteNameReference(name)) {
3686
+ return null;
3687
+ }
3688
+ const [localName, domain] = name.split('@');
3689
+ let url;
3690
+ try {
3691
+ url = new URL(`https://${domain}/.well-known/names/${encodeURIComponent(localName)}`);
3692
+ }
3693
+ catch {
3694
+ return null;
3695
+ }
3696
+ if (isPrivateHostname(url.hostname)) {
3697
+ return null;
3698
+ }
3699
+ const controller = new AbortController();
3700
+ const timeout = setTimeout(() => controller.abort(), REMOTE_NAME_LOOKUP_TIMEOUT_MS);
3701
+ try {
3702
+ const response = await fetch(url.toString(), { signal: controller.signal });
3703
+ if (!response.ok) {
3704
+ return null;
3705
+ }
3706
+ const data = await response.json();
3707
+ if (typeof data.did !== 'string' || !isValidDID(data.did)) {
3708
+ return null;
3709
+ }
3710
+ return data.did;
3711
+ }
3712
+ catch {
3713
+ return null;
3714
+ }
3715
+ finally {
3716
+ clearTimeout(timeout);
3717
+ }
3718
+ }
3628
3719
  async resolveDID(did, options) {
3629
3720
  const actualDid = await this.lookupDID(did);
3630
3721
  const docs = await this.gatekeeper.resolveDID(actualDid, options);
@@ -5560,9 +5651,16 @@ class Keymaster {
5560
5651
  }
5561
5652
  throw new InvalidParameterError(`Invalid recipient: ${id}`);
5562
5653
  }
5563
- if (isValidDID(id)) {
5564
- newList.push(id);
5565
- continue;
5654
+ try {
5655
+ const did = await this.lookupDID(id);
5656
+ const isAgent = await this.testAgent(did);
5657
+ if (isAgent) {
5658
+ newList.push(did);
5659
+ continue;
5660
+ }
5661
+ }
5662
+ catch {
5663
+ // Fall through to recipient-specific error below.
5566
5664
  }
5567
5665
  throw new InvalidParameterError(`Invalid recipient: ${id}`);
5568
5666
  }
package/dist/esm/cli.js CHANGED
@@ -335,6 +335,18 @@ program
335
335
  console.error(`cannot revoke ${did}`);
336
336
  }
337
337
  });
338
+ program
339
+ .command('change-registry <id> <registry>')
340
+ .description('Changes the registry for an existing DID')
341
+ .action(async (id, registry) => {
342
+ try {
343
+ const ok = await keymaster.changeRegistry(id, registry);
344
+ console.log(ok ? UPDATE_OK : UPDATE_FAILED);
345
+ }
346
+ catch (error) {
347
+ console.error(error.error || error.message || error);
348
+ }
349
+ });
338
350
  // Encryption commands
339
351
  program
340
352
  .command('encrypt-message <message> <did>')
@@ -1056,9 +1068,10 @@ program
1056
1068
  .action(async (file, options) => {
1057
1069
  try {
1058
1070
  const { alias, registry } = options;
1059
- const data = fs.readFileSync(file);
1060
1071
  const filename = path.basename(file);
1061
- const did = await keymaster.createFile(data, { filename, alias, registry });
1072
+ const bytes = fs.statSync(file).size;
1073
+ const stream = fs.createReadStream(file);
1074
+ const did = await keymaster.createFileStream(stream, { filename, alias, registry, bytes });
1062
1075
  console.log(did);
1063
1076
  }
1064
1077
  catch (error) {
@@ -1158,9 +1171,10 @@ program
1158
1171
  .description('Update an asset from a file')
1159
1172
  .action(async (id, file) => {
1160
1173
  try {
1161
- const data = fs.readFileSync(file);
1162
1174
  const filename = path.basename(file);
1163
- const ok = await keymaster.updateFile(id, data, { filename });
1175
+ const bytes = fs.statSync(file).size;
1176
+ const stream = fs.createReadStream(file);
1177
+ const ok = await keymaster.updateFileStream(id, stream, { filename, bytes });
1164
1178
  console.log(ok ? UPDATE_OK : UPDATE_FAILED);
1165
1179
  }
1166
1180
  catch (error) {