@did-btcr2/cli 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +889 -43
- package/dist/esm/src/cli.js +30 -12
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/completion.js +36 -0
- package/dist/esm/src/commands/completion.js.map +1 -0
- package/dist/esm/src/commands/config.js +69 -0
- package/dist/esm/src/commands/config.js.map +1 -0
- package/dist/esm/src/commands/deactivate.js +21 -8
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/index.js +4 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/key.js +175 -0
- package/dist/esm/src/commands/key.js.map +1 -0
- package/dist/esm/src/commands/profile.js +63 -0
- package/dist/esm/src/commands/profile.js.map +1 -0
- package/dist/esm/src/commands/update.js +19 -9
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config.js +100 -13
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/keystore/atomic.js +64 -0
- package/dist/esm/src/keystore/atomic.js.map +1 -0
- package/dist/esm/src/keystore/envelope.js +123 -0
- package/dist/esm/src/keystore/envelope.js.map +1 -0
- package/dist/esm/src/keystore/error.js +16 -0
- package/dist/esm/src/keystore/error.js.map +1 -0
- package/dist/esm/src/keystore/file-backed-key-manager.js +78 -0
- package/dist/esm/src/keystore/file-backed-key-manager.js.map +1 -0
- package/dist/esm/src/keystore/file-key-store.js +184 -0
- package/dist/esm/src/keystore/file-key-store.js.map +1 -0
- package/dist/esm/src/keystore/passphrase.js +87 -0
- package/dist/esm/src/keystore/passphrase.js.map +1 -0
- package/dist/esm/src/keystore/paths.js +20 -0
- package/dist/esm/src/keystore/paths.js.map +1 -0
- package/dist/esm/src/keystore/resolve-key-ref.js +47 -0
- package/dist/esm/src/keystore/resolve-key-ref.js.map +1 -0
- package/dist/types/src/cli.d.ts +6 -2
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/completion.d.ts +5 -0
- package/dist/types/src/commands/completion.d.ts.map +1 -0
- package/dist/types/src/commands/config.d.ts +5 -0
- package/dist/types/src/commands/config.d.ts.map +1 -0
- package/dist/types/src/commands/deactivate.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +4 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/key.d.ts +10 -0
- package/dist/types/src/commands/key.d.ts.map +1 -0
- package/dist/types/src/commands/profile.d.ts +5 -0
- package/dist/types/src/commands/profile.d.ts.map +1 -0
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config.d.ts +50 -7
- package/dist/types/src/config.d.ts.map +1 -1
- package/dist/types/src/keystore/atomic.d.ts +19 -0
- package/dist/types/src/keystore/atomic.d.ts.map +1 -0
- package/dist/types/src/keystore/envelope.d.ts +64 -0
- package/dist/types/src/keystore/envelope.d.ts.map +1 -0
- package/dist/types/src/keystore/error.d.ts +14 -0
- package/dist/types/src/keystore/error.d.ts.map +1 -0
- package/dist/types/src/keystore/file-backed-key-manager.d.ts +41 -0
- package/dist/types/src/keystore/file-backed-key-manager.d.ts.map +1 -0
- package/dist/types/src/keystore/file-key-store.d.ts +52 -0
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -0
- package/dist/types/src/keystore/passphrase.d.ts +20 -0
- package/dist/types/src/keystore/passphrase.d.ts.map +1 -0
- package/dist/types/src/keystore/paths.d.ts +13 -0
- package/dist/types/src/keystore/paths.d.ts.map +1 -0
- package/dist/types/src/keystore/resolve-key-ref.d.ts +19 -0
- package/dist/types/src/keystore/resolve-key-ref.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +91 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/cli.ts +36 -11
- package/src/commands/completion.ts +40 -0
- package/src/commands/config.ts +84 -0
- package/src/commands/deactivate.ts +25 -12
- package/src/commands/index.ts +4 -0
- package/src/commands/key.ts +193 -0
- package/src/commands/profile.ts +65 -0
- package/src/commands/update.ts +23 -13
- package/src/config.ts +144 -22
- package/src/keystore/atomic.ts +73 -0
- package/src/keystore/envelope.ts +172 -0
- package/src/keystore/error.ts +16 -0
- package/src/keystore/file-backed-key-manager.ts +99 -0
- package/src/keystore/file-key-store.ts +242 -0
- package/src/keystore/passphrase.ts +99 -0
- package/src/keystore/paths.ts +20 -0
- package/src/keystore/resolve-key-ref.ts +62 -0
- package/src/types.ts +30 -11
package/dist/cjs/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CLIError: () => CLIError,
|
|
24
|
+
CONFIG_SCHEMA_VERSION: () => CONFIG_SCHEMA_VERSION,
|
|
24
25
|
DidBtcr2Cli: () => DidBtcr2Cli,
|
|
25
26
|
ENV_VARS: () => ENV_VARS,
|
|
26
27
|
SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
|
|
@@ -29,21 +30,31 @@ __export(index_exports, {
|
|
|
29
30
|
defaultConfigPath: () => defaultConfigPath,
|
|
30
31
|
deriveNetwork: () => deriveNetwork,
|
|
31
32
|
formatResult: () => formatResult,
|
|
33
|
+
getConfigPath: () => getConfigPath,
|
|
34
|
+
keystoreApiFactory: () => keystoreApiFactory,
|
|
32
35
|
profileToOverrides: () => profileToOverrides,
|
|
33
36
|
readConfigFile: () => readConfigFile,
|
|
34
37
|
readEnvOverrides: () => readEnvOverrides,
|
|
38
|
+
registerCompletionCommand: () => registerCompletionCommand,
|
|
39
|
+
registerConfigCommand: () => registerConfigCommand,
|
|
35
40
|
registerCreateCommand: () => registerCreateCommand,
|
|
36
41
|
registerDeactivateCommand: () => registerDeactivateCommand,
|
|
42
|
+
registerKeyCommand: () => registerKeyCommand,
|
|
43
|
+
registerProfileCommand: () => registerProfileCommand,
|
|
37
44
|
registerResolveCommand: () => registerResolveCommand,
|
|
38
|
-
registerUpdateCommand: () => registerUpdateCommand
|
|
45
|
+
registerUpdateCommand: () => registerUpdateCommand,
|
|
46
|
+
setConfigPath: () => setConfigPath,
|
|
47
|
+
unsetConfigPath: () => unsetConfigPath,
|
|
48
|
+
writeConfigFile: () => writeConfigFile
|
|
39
49
|
});
|
|
40
50
|
module.exports = __toCommonJS(index_exports);
|
|
41
51
|
|
|
42
|
-
// ../../node_modules/.pnpm/tsup@8.5.
|
|
52
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_typescript@5.7.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js
|
|
43
53
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
44
54
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
45
55
|
|
|
46
56
|
// src/cli.ts
|
|
57
|
+
var import_common3 = require("@did-btcr2/common");
|
|
47
58
|
var import_commander = require("commander");
|
|
48
59
|
|
|
49
60
|
// src/error.ts
|
|
@@ -139,9 +150,503 @@ var import_promises = require("fs/promises");
|
|
|
139
150
|
|
|
140
151
|
// src/config.ts
|
|
141
152
|
var import_api = require("@did-btcr2/api");
|
|
153
|
+
var import_node_fs4 = require("fs");
|
|
154
|
+
var import_node_os2 = require("os");
|
|
155
|
+
var import_node_path4 = require("path");
|
|
156
|
+
|
|
157
|
+
// src/keystore/atomic.ts
|
|
142
158
|
var import_node_fs = require("fs");
|
|
143
|
-
var import_node_os = require("os");
|
|
144
159
|
var import_node_path = require("path");
|
|
160
|
+
|
|
161
|
+
// src/keystore/error.ts
|
|
162
|
+
var import_common2 = require("@did-btcr2/common");
|
|
163
|
+
var KeyStoreError = class extends import_common2.DidMethodError {
|
|
164
|
+
constructor(message, type = "KeyStoreError", data) {
|
|
165
|
+
super(message, { type, name: type, data });
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// src/keystore/atomic.ts
|
|
170
|
+
var isWindows = process.platform === "win32";
|
|
171
|
+
var permsWarned = false;
|
|
172
|
+
var tmpCounter = 0;
|
|
173
|
+
function ensureDir(dir, mode) {
|
|
174
|
+
(0, import_node_fs.mkdirSync)(dir, { recursive: true, mode });
|
|
175
|
+
if (!isWindows) {
|
|
176
|
+
try {
|
|
177
|
+
(0, import_node_fs.chmodSync)(dir, mode);
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function writeFileAtomic(path, data, mode) {
|
|
183
|
+
const tmp = (0, import_node_path.join)((0, import_node_path.dirname)(path), `.${(0, import_node_path.basename)(path)}.${process.pid}.${tmpCounter++}.tmp`);
|
|
184
|
+
try {
|
|
185
|
+
(0, import_node_fs.writeFileSync)(tmp, data, { mode });
|
|
186
|
+
if (!isWindows) (0, import_node_fs.chmodSync)(tmp, mode);
|
|
187
|
+
(0, import_node_fs.renameSync)(tmp, path);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
try {
|
|
190
|
+
(0, import_node_fs.rmSync)(tmp, { force: true });
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
throw new KeyStoreError(
|
|
194
|
+
`Failed to write keystore at ${path}.`,
|
|
195
|
+
"ATOMIC_WRITE_ERROR",
|
|
196
|
+
{ path, cause: error instanceof Error ? error.message : String(error) }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function assertSecurePerms(path) {
|
|
201
|
+
if (isWindows) {
|
|
202
|
+
if (!permsWarned) {
|
|
203
|
+
process.stderr.write(
|
|
204
|
+
"warning: file permissions are not enforced on Windows; protect the keystore directory manually.\n"
|
|
205
|
+
);
|
|
206
|
+
permsWarned = true;
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const mode = (0, import_node_fs.statSync)(path).mode & 511;
|
|
211
|
+
if ((mode & 63) !== 0) {
|
|
212
|
+
throw new KeyStoreError(
|
|
213
|
+
`Keystore at ${path} has insecure permissions 0${mode.toString(8)}; expected 0600.`,
|
|
214
|
+
"KEYSTORE_PERMISSION_ERROR",
|
|
215
|
+
{ path, mode: `0${mode.toString(8)}` }
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/keystore/file-backed-key-manager.ts
|
|
221
|
+
var import_key_manager = require("@did-btcr2/key-manager");
|
|
222
|
+
|
|
223
|
+
// src/keystore/file-key-store.ts
|
|
224
|
+
var import_node_fs2 = require("fs");
|
|
225
|
+
var import_node_path3 = require("path");
|
|
226
|
+
var import_base2 = require("@scure/base");
|
|
227
|
+
|
|
228
|
+
// src/keystore/envelope.ts
|
|
229
|
+
var import_chacha = require("@noble/ciphers/chacha.js");
|
|
230
|
+
var import_argon2 = require("@noble/hashes/argon2.js");
|
|
231
|
+
var import_utils = require("@noble/hashes/utils.js");
|
|
232
|
+
var import_base = require("@scure/base");
|
|
233
|
+
var ENVELOPE_VERSION = 1;
|
|
234
|
+
var SALT_BYTES = 16;
|
|
235
|
+
var NONCE_BYTES = 24;
|
|
236
|
+
var KEY_BYTES = 32;
|
|
237
|
+
var DEFAULT_ARGON_PARAMS = { t: 3, m: 65536, p: 4, dkLen: KEY_BYTES };
|
|
238
|
+
function buildHeader(saltB64, params) {
|
|
239
|
+
return {
|
|
240
|
+
v: ENVELOPE_VERSION,
|
|
241
|
+
kdf: {
|
|
242
|
+
alg: "argon2id",
|
|
243
|
+
salt: saltB64,
|
|
244
|
+
t: params.t,
|
|
245
|
+
m: params.m,
|
|
246
|
+
p: params.p,
|
|
247
|
+
dkLen: params.dkLen
|
|
248
|
+
},
|
|
249
|
+
cipher: "xchacha20poly1305"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function headerAad(header) {
|
|
253
|
+
return (0, import_utils.utf8ToBytes)(JSON.stringify(header));
|
|
254
|
+
}
|
|
255
|
+
function deriveKey(passphrase, salt, params) {
|
|
256
|
+
const password = (0, import_utils.utf8ToBytes)(passphrase);
|
|
257
|
+
try {
|
|
258
|
+
return (0, import_argon2.argon2id)(password, salt, { t: params.t, m: params.m, p: params.p, dkLen: params.dkLen });
|
|
259
|
+
} finally {
|
|
260
|
+
password.fill(0);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function encryptSecret(secret, passphrase, params = DEFAULT_ARGON_PARAMS) {
|
|
264
|
+
if (secret.length === 0) {
|
|
265
|
+
throw new KeyStoreError("Cannot encrypt an empty secret.", "ENVELOPE_ENCRYPT_ERROR");
|
|
266
|
+
}
|
|
267
|
+
const salt = (0, import_utils.randomBytes)(SALT_BYTES);
|
|
268
|
+
const nonce = (0, import_utils.randomBytes)(NONCE_BYTES);
|
|
269
|
+
const header = buildHeader(import_base.base64urlnopad.encode(salt), params);
|
|
270
|
+
const key = deriveKey(passphrase, salt, params);
|
|
271
|
+
try {
|
|
272
|
+
const ciphertext = (0, import_chacha.xchacha20poly1305)(key, nonce, headerAad(header)).encrypt(secret);
|
|
273
|
+
return {
|
|
274
|
+
...header,
|
|
275
|
+
nonce: import_base.base64urlnopad.encode(nonce),
|
|
276
|
+
ciphertext: import_base.base64urlnopad.encode(ciphertext)
|
|
277
|
+
};
|
|
278
|
+
} finally {
|
|
279
|
+
key.fill(0);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function decryptSecret(env, passphrase) {
|
|
283
|
+
if (env.v !== ENVELOPE_VERSION) {
|
|
284
|
+
throw new KeyStoreError(
|
|
285
|
+
`Unsupported keystore envelope version: ${String(env.v)}.`,
|
|
286
|
+
"ENVELOPE_VERSION_ERROR",
|
|
287
|
+
{ version: env.v }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (env.kdf?.alg !== "argon2id" || env.cipher !== "xchacha20poly1305") {
|
|
291
|
+
throw new KeyStoreError("Unsupported keystore envelope algorithm.", "ENVELOPE_VERSION_ERROR");
|
|
292
|
+
}
|
|
293
|
+
const params = { t: env.kdf.t, m: env.kdf.m, p: env.kdf.p, dkLen: env.kdf.dkLen };
|
|
294
|
+
const salt = import_base.base64urlnopad.decode(env.kdf.salt);
|
|
295
|
+
const nonce = import_base.base64urlnopad.decode(env.nonce);
|
|
296
|
+
const ciphertext = import_base.base64urlnopad.decode(env.ciphertext);
|
|
297
|
+
const header = buildHeader(env.kdf.salt, params);
|
|
298
|
+
const key = deriveKey(passphrase, salt, params);
|
|
299
|
+
try {
|
|
300
|
+
return (0, import_chacha.xchacha20poly1305)(key, nonce, headerAad(header)).decrypt(ciphertext);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error instanceof KeyStoreError) throw error;
|
|
303
|
+
throw new KeyStoreError(
|
|
304
|
+
"Keystore decryption failed: wrong passphrase or corrupted keystore.",
|
|
305
|
+
"DECRYPT_ERROR"
|
|
306
|
+
);
|
|
307
|
+
} finally {
|
|
308
|
+
key.fill(0);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/keystore/paths.ts
|
|
313
|
+
var import_node_os = require("os");
|
|
314
|
+
var import_node_path2 = require("path");
|
|
315
|
+
function defaultKeystorePath() {
|
|
316
|
+
const base = process.env.XDG_DATA_HOME ?? process.env.LOCALAPPDATA ?? (0, import_node_path2.join)((0, import_node_os.homedir)(), ".local", "share");
|
|
317
|
+
return (0, import_node_path2.join)(base, "btcr2", "keystore.json");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/keystore/file-key-store.ts
|
|
321
|
+
var KEYSTORE_VERSION = 1;
|
|
322
|
+
var FileKeyStore = class {
|
|
323
|
+
#path;
|
|
324
|
+
#getPassphrase;
|
|
325
|
+
#argonParams;
|
|
326
|
+
#cache = /* @__PURE__ */ new Map();
|
|
327
|
+
#active;
|
|
328
|
+
constructor(options) {
|
|
329
|
+
this.#path = options.path ?? defaultKeystorePath();
|
|
330
|
+
this.#getPassphrase = options.getPassphrase;
|
|
331
|
+
this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
|
|
332
|
+
ensureDir((0, import_node_path3.dirname)(this.#path), 448);
|
|
333
|
+
this.#load();
|
|
334
|
+
}
|
|
335
|
+
#load() {
|
|
336
|
+
if (!(0, import_node_fs2.existsSync)(this.#path)) return;
|
|
337
|
+
assertSecurePerms(this.#path);
|
|
338
|
+
let parsed;
|
|
339
|
+
try {
|
|
340
|
+
parsed = JSON.parse((0, import_node_fs2.readFileSync)(this.#path, "utf-8"));
|
|
341
|
+
} catch {
|
|
342
|
+
throw new KeyStoreError(
|
|
343
|
+
`Keystore at ${this.#path} is corrupt or unreadable.`,
|
|
344
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
345
|
+
{ path: this.#path }
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (parsed.v !== KEYSTORE_VERSION) {
|
|
349
|
+
throw new KeyStoreError(
|
|
350
|
+
`Unsupported keystore version: ${String(parsed.v)}.`,
|
|
351
|
+
"KEYSTORE_VERSION_ERROR",
|
|
352
|
+
{ version: parsed.v }
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
this.#active = parsed.active;
|
|
356
|
+
for (const [id, stored] of Object.entries(parsed.keys ?? {})) {
|
|
357
|
+
let publicKey;
|
|
358
|
+
try {
|
|
359
|
+
if (typeof stored.publicKey !== "string") throw new Error("missing publicKey");
|
|
360
|
+
publicKey = import_base2.base64urlnopad.decode(stored.publicKey);
|
|
361
|
+
} catch {
|
|
362
|
+
throw new KeyStoreError(
|
|
363
|
+
`Keystore entry ${id} has a malformed public key.`,
|
|
364
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
365
|
+
{ path: this.#path, keyId: id }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (publicKey.length !== 33) {
|
|
369
|
+
throw new KeyStoreError(
|
|
370
|
+
`Keystore entry ${id} has a ${publicKey.length}-byte public key; expected 33.`,
|
|
371
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
372
|
+
{ path: this.#path, keyId: id }
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
this.#cache.set(id, {
|
|
376
|
+
publicKey,
|
|
377
|
+
...stored.tags && { tags: stored.tags },
|
|
378
|
+
...stored.secret && { secret: stored.secret }
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
#flush() {
|
|
383
|
+
const keys = {};
|
|
384
|
+
for (const [id, entry] of this.#cache) {
|
|
385
|
+
keys[id] = {
|
|
386
|
+
publicKey: import_base2.base64urlnopad.encode(entry.publicKey),
|
|
387
|
+
...entry.tags && { tags: entry.tags },
|
|
388
|
+
...entry.secret && { secret: entry.secret }
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const file = {
|
|
392
|
+
v: KEYSTORE_VERSION,
|
|
393
|
+
...this.#active && { active: this.#active },
|
|
394
|
+
keys
|
|
395
|
+
};
|
|
396
|
+
writeFileAtomic(this.#path, `${JSON.stringify(file, null, 2)}
|
|
397
|
+
`, 384);
|
|
398
|
+
}
|
|
399
|
+
get(id) {
|
|
400
|
+
const entry = this.#cache.get(id);
|
|
401
|
+
if (!entry) return void 0;
|
|
402
|
+
const result = {
|
|
403
|
+
publicKey: entry.publicKey,
|
|
404
|
+
...entry.tags && { tags: entry.tags }
|
|
405
|
+
};
|
|
406
|
+
if (entry.secret) {
|
|
407
|
+
const sealed = entry.secret;
|
|
408
|
+
Object.defineProperty(result, "secretKey", {
|
|
409
|
+
configurable: true,
|
|
410
|
+
enumerable: false,
|
|
411
|
+
get: () => {
|
|
412
|
+
entry.decrypted ??= decryptSecret(sealed, this.#getPassphrase());
|
|
413
|
+
return entry.decrypted;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
has(id) {
|
|
420
|
+
return this.#cache.has(id);
|
|
421
|
+
}
|
|
422
|
+
set(id, value) {
|
|
423
|
+
const secret = value.secretKey ? encryptSecret(value.secretKey, this.#getPassphrase(), this.#argonParams) : void 0;
|
|
424
|
+
this.#cache.set(id, {
|
|
425
|
+
publicKey: value.publicKey,
|
|
426
|
+
...value.tags && { tags: value.tags },
|
|
427
|
+
...secret && { secret },
|
|
428
|
+
...value.secretKey && { decrypted: value.secretKey }
|
|
429
|
+
});
|
|
430
|
+
this.#flush();
|
|
431
|
+
}
|
|
432
|
+
delete(id) {
|
|
433
|
+
const existed = this.#cache.delete(id);
|
|
434
|
+
if (existed) {
|
|
435
|
+
if (this.#active === id) this.#active = void 0;
|
|
436
|
+
this.#flush();
|
|
437
|
+
}
|
|
438
|
+
return existed;
|
|
439
|
+
}
|
|
440
|
+
clear() {
|
|
441
|
+
this.#cache.clear();
|
|
442
|
+
this.#active = void 0;
|
|
443
|
+
this.#flush();
|
|
444
|
+
}
|
|
445
|
+
/** All stored values with secret keys omitted. Never decrypts, never prompts. */
|
|
446
|
+
list() {
|
|
447
|
+
return this.entries().map(([, value]) => value);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* All entries as id-value tuples with secret keys omitted. Never decrypts,
|
|
451
|
+
* never prompts: {@link FileKeyStore.get} is the only secret-materializing
|
|
452
|
+
* path, so callers that only need identifiers (such as `listKeys`) do not
|
|
453
|
+
* force a passphrase prompt. This deviates intentionally from the in-memory
|
|
454
|
+
* store, which returns stored values verbatim.
|
|
455
|
+
*/
|
|
456
|
+
entries() {
|
|
457
|
+
const out = [];
|
|
458
|
+
for (const [id, entry] of this.#cache) {
|
|
459
|
+
out.push([id, {
|
|
460
|
+
publicKey: entry.publicKey,
|
|
461
|
+
...entry.tags && { tags: entry.tags }
|
|
462
|
+
}]);
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
close() {
|
|
467
|
+
for (const entry of this.#cache.values()) {
|
|
468
|
+
entry.decrypted?.fill(0);
|
|
469
|
+
entry.decrypted = void 0;
|
|
470
|
+
}
|
|
471
|
+
this.#cache.clear();
|
|
472
|
+
}
|
|
473
|
+
/** The persisted active-key identifier, or undefined if none is set. */
|
|
474
|
+
getActive() {
|
|
475
|
+
return this.#active;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Persists the active-key pointer in the keystore file. Passing undefined
|
|
479
|
+
* clears it. Throws if the identifier is not a known key.
|
|
480
|
+
*/
|
|
481
|
+
setActive(id) {
|
|
482
|
+
if (id !== void 0 && !this.#cache.has(id)) {
|
|
483
|
+
throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, "KEY_NOT_FOUND_ERROR", { keyId: id });
|
|
484
|
+
}
|
|
485
|
+
this.#active = id;
|
|
486
|
+
this.#flush();
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/keystore/file-backed-key-manager.ts
|
|
491
|
+
var FileBackedKeyManager = class {
|
|
492
|
+
/** Capability probe: the local store supports exporting secret material. */
|
|
493
|
+
canExport = true;
|
|
494
|
+
#store;
|
|
495
|
+
#inner;
|
|
496
|
+
constructor(options) {
|
|
497
|
+
this.#store = new FileKeyStore(options);
|
|
498
|
+
this.#inner = new import_key_manager.LocalKeyManager(this.#store);
|
|
499
|
+
const active = this.#store.getActive();
|
|
500
|
+
if (active && this.#store.has(active)) this.#inner.setActiveKey(active);
|
|
501
|
+
}
|
|
502
|
+
get activeKeyId() {
|
|
503
|
+
return this.#inner.activeKeyId;
|
|
504
|
+
}
|
|
505
|
+
setActiveKey(id) {
|
|
506
|
+
this.#inner.setActiveKey(id);
|
|
507
|
+
this.#store.setActive(id);
|
|
508
|
+
}
|
|
509
|
+
importKey(keyPair, options) {
|
|
510
|
+
const id = this.#inner.importKey(keyPair, options);
|
|
511
|
+
if (options?.setActive) this.#store.setActive(id);
|
|
512
|
+
return id;
|
|
513
|
+
}
|
|
514
|
+
generateKey(options) {
|
|
515
|
+
const id = this.#inner.generateKey(options);
|
|
516
|
+
if (options?.setActive) this.#store.setActive(id);
|
|
517
|
+
return id;
|
|
518
|
+
}
|
|
519
|
+
removeKey(id, options) {
|
|
520
|
+
this.#inner.removeKey(id, options);
|
|
521
|
+
}
|
|
522
|
+
listKeys() {
|
|
523
|
+
return this.#inner.listKeys();
|
|
524
|
+
}
|
|
525
|
+
getPublicKey(id) {
|
|
526
|
+
return this.#inner.getPublicKey(id);
|
|
527
|
+
}
|
|
528
|
+
getEntry(id) {
|
|
529
|
+
return this.#inner.getEntry(id);
|
|
530
|
+
}
|
|
531
|
+
sign(data, id, options) {
|
|
532
|
+
return this.#inner.sign(data, id, options);
|
|
533
|
+
}
|
|
534
|
+
verify(signature, data, id, options) {
|
|
535
|
+
return this.#inner.verify(signature, data, id, options);
|
|
536
|
+
}
|
|
537
|
+
digest(data) {
|
|
538
|
+
return this.#inner.digest(data);
|
|
539
|
+
}
|
|
540
|
+
exportKey(id) {
|
|
541
|
+
return this.#inner.exportKey(id);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// src/keystore/passphrase.ts
|
|
546
|
+
var import_node_fs3 = require("fs");
|
|
547
|
+
var ENV_KEYSTORE_PASSPHRASE = "BTCR2_KEYSTORE_PASSPHRASE";
|
|
548
|
+
function acquirePassphrase(options = {}) {
|
|
549
|
+
const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
|
|
550
|
+
if (fromEnv) return assertNonEmpty(fromEnv.replace(/\r?\n$/, ""));
|
|
551
|
+
if (options.passphraseFile) {
|
|
552
|
+
return assertNonEmpty((0, import_node_fs3.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
|
|
553
|
+
}
|
|
554
|
+
if (!process.stdin.isTTY) {
|
|
555
|
+
throw new KeyStoreError(
|
|
556
|
+
`No passphrase available. Set ${ENV_KEYSTORE_PASSPHRASE}, pass --passphrase-file, or run in a terminal.`,
|
|
557
|
+
"PASSPHRASE_REQUIRED_ERROR"
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const passphrase = promptHidden(options.prompt ?? "Keystore passphrase: ");
|
|
561
|
+
if (options.confirm) {
|
|
562
|
+
const again = promptHidden("Confirm passphrase: ");
|
|
563
|
+
if (passphrase !== again) {
|
|
564
|
+
throw new KeyStoreError("Passphrases did not match.", "PASSPHRASE_MISMATCH_ERROR");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return assertNonEmpty(passphrase);
|
|
568
|
+
}
|
|
569
|
+
function assertNonEmpty(passphrase) {
|
|
570
|
+
if (passphrase.trim() === "") {
|
|
571
|
+
throw new KeyStoreError("A non-empty keystore passphrase is required.", "PASSPHRASE_REQUIRED_ERROR");
|
|
572
|
+
}
|
|
573
|
+
return passphrase;
|
|
574
|
+
}
|
|
575
|
+
function promptHidden(label) {
|
|
576
|
+
process.stderr.write(label);
|
|
577
|
+
const stdin = process.stdin;
|
|
578
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
579
|
+
stdin.setRawMode(true);
|
|
580
|
+
const byte = Buffer.alloc(1);
|
|
581
|
+
const bytes = [];
|
|
582
|
+
try {
|
|
583
|
+
for (; ; ) {
|
|
584
|
+
let read = 0;
|
|
585
|
+
try {
|
|
586
|
+
read = (0, import_node_fs3.readSync)(stdin.fd, byte, 0, 1, null);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
const code = error.code;
|
|
589
|
+
if (code === "EAGAIN") continue;
|
|
590
|
+
if (code === "EOF") break;
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
if (read === 0) break;
|
|
594
|
+
const ch = byte[0];
|
|
595
|
+
if (ch === 10 || ch === 13) break;
|
|
596
|
+
if (ch === 3) {
|
|
597
|
+
throw new KeyStoreError("Passphrase entry aborted.", "PASSPHRASE_REQUIRED_ERROR");
|
|
598
|
+
}
|
|
599
|
+
if (ch === 127 || ch === 8) {
|
|
600
|
+
bytes.pop();
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
bytes.push(ch);
|
|
604
|
+
}
|
|
605
|
+
} finally {
|
|
606
|
+
stdin.setRawMode(wasRaw);
|
|
607
|
+
process.stderr.write("\n");
|
|
608
|
+
}
|
|
609
|
+
return Buffer.from(bytes).toString("utf-8");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/config.ts
|
|
613
|
+
var CONFIG_SCHEMA_VERSION = 1;
|
|
614
|
+
function writeConfigFile(path, mutate) {
|
|
615
|
+
const raw = readConfigFile(path) ?? {};
|
|
616
|
+
mutate(raw);
|
|
617
|
+
raw.schemaVersion = CONFIG_SCHEMA_VERSION;
|
|
618
|
+
ensureDir((0, import_node_path4.dirname)(path), 448);
|
|
619
|
+
writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
620
|
+
`, 384);
|
|
621
|
+
}
|
|
622
|
+
function getConfigPath(config, path) {
|
|
623
|
+
return path.split(".").reduce(
|
|
624
|
+
(node, key) => node?.[key],
|
|
625
|
+
config
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
function setConfigPath(config, path, value) {
|
|
629
|
+
const keys = path.split(".");
|
|
630
|
+
const last = keys.pop();
|
|
631
|
+
if (!last) throw new CLIError("Config path must be non-empty.", "INVALID_ARGUMENT_ERROR");
|
|
632
|
+
let node = config;
|
|
633
|
+
for (const key of keys) {
|
|
634
|
+
if (typeof node[key] !== "object" || node[key] === null) node[key] = {};
|
|
635
|
+
node = node[key];
|
|
636
|
+
}
|
|
637
|
+
node[last] = value;
|
|
638
|
+
}
|
|
639
|
+
function unsetConfigPath(config, path) {
|
|
640
|
+
const keys = path.split(".");
|
|
641
|
+
const last = keys.pop();
|
|
642
|
+
if (!last) return;
|
|
643
|
+
let node = config;
|
|
644
|
+
for (const key of keys) {
|
|
645
|
+
node = node?.[key];
|
|
646
|
+
if (!node) return;
|
|
647
|
+
}
|
|
648
|
+
delete node[last];
|
|
649
|
+
}
|
|
145
650
|
var ENV_VARS = {
|
|
146
651
|
BTC_REST: "BTCR2_BTC_REST",
|
|
147
652
|
BTC_RPC_URL: "BTCR2_BTC_RPC_URL",
|
|
@@ -160,12 +665,12 @@ function readEnvOverrides() {
|
|
|
160
665
|
};
|
|
161
666
|
}
|
|
162
667
|
function defaultConfigPath() {
|
|
163
|
-
const base = process.env.XDG_CONFIG_HOME ?? process.env.APPDATA ?? (0,
|
|
164
|
-
return (0,
|
|
668
|
+
const base = process.env.XDG_CONFIG_HOME ?? process.env.APPDATA ?? (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".config");
|
|
669
|
+
return (0, import_node_path4.join)(base, "btcr2", "config.json");
|
|
165
670
|
}
|
|
166
671
|
function readConfigFile(path) {
|
|
167
672
|
try {
|
|
168
|
-
const content = (0,
|
|
673
|
+
const content = (0, import_node_fs4.readFileSync)(path, "utf-8");
|
|
169
674
|
return JSON.parse(content);
|
|
170
675
|
} catch {
|
|
171
676
|
return void 0;
|
|
@@ -182,11 +687,11 @@ function profileToOverrides(config, profileName) {
|
|
|
182
687
|
casGateway: profile.cas?.gateway
|
|
183
688
|
};
|
|
184
689
|
}
|
|
185
|
-
function
|
|
186
|
-
if (!network) return
|
|
690
|
+
function resolveConnectionConfig(network, overrides) {
|
|
691
|
+
if (!network) return {};
|
|
187
692
|
const configPath = overrides?.config ?? defaultConfigPath();
|
|
188
|
-
const profileName = overrides?.profile ?? network;
|
|
189
693
|
const file = readConfigFile(configPath);
|
|
694
|
+
const profileName = overrides?.profile ?? file?.defaults?.profile ?? network;
|
|
190
695
|
const fileOverrides = file ? profileToOverrides(file, profileName) : {};
|
|
191
696
|
const env = readEnvOverrides();
|
|
192
697
|
const merged = {
|
|
@@ -208,7 +713,22 @@ function defaultApiFactory(network, overrides) {
|
|
|
208
713
|
};
|
|
209
714
|
}
|
|
210
715
|
const cas = merged.casGateway ? { gateway: merged.casGateway } : void 0;
|
|
211
|
-
return
|
|
716
|
+
return { btc, ...cas && { cas } };
|
|
717
|
+
}
|
|
718
|
+
function defaultApiFactory(network, overrides) {
|
|
719
|
+
return (0, import_api.createApi)(resolveConnectionConfig(network, overrides));
|
|
720
|
+
}
|
|
721
|
+
function buildKeystoreKms(overrides) {
|
|
722
|
+
return new FileBackedKeyManager({
|
|
723
|
+
path: overrides?.keystore ?? defaultKeystorePath(),
|
|
724
|
+
getPassphrase: () => acquirePassphrase({ passphraseFile: overrides?.passphraseFile })
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
function keystoreApiFactory(network, overrides) {
|
|
728
|
+
return (0, import_api.createApi)({
|
|
729
|
+
...resolveConnectionConfig(network, overrides),
|
|
730
|
+
kms: buildKeystoreKms(overrides)
|
|
731
|
+
});
|
|
212
732
|
}
|
|
213
733
|
function deriveNetwork(did) {
|
|
214
734
|
const { network } = import_api.Identifier.decode(did);
|
|
@@ -261,6 +781,47 @@ async function validateResolveOptions(options) {
|
|
|
261
781
|
return { identifier: options.identifier, options: resolutionOptions };
|
|
262
782
|
}
|
|
263
783
|
|
|
784
|
+
// src/commands/update.ts
|
|
785
|
+
var import_key_manager2 = require("@did-btcr2/key-manager");
|
|
786
|
+
|
|
787
|
+
// src/keystore/resolve-key-ref.ts
|
|
788
|
+
function fingerprintOf(id) {
|
|
789
|
+
return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
|
|
790
|
+
}
|
|
791
|
+
function resolveKeyRef(kms, ref) {
|
|
792
|
+
if (!ref) {
|
|
793
|
+
if (!kms.activeKeyId) {
|
|
794
|
+
throw new CLIError(
|
|
795
|
+
"No key specified and no active key is set. Use --key <ref> or set one with `btcr2 key use <ref>`.",
|
|
796
|
+
"INVALID_ARGUMENT_ERROR"
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return kms.activeKeyId;
|
|
800
|
+
}
|
|
801
|
+
const ids = kms.listKeys();
|
|
802
|
+
if (ids.includes(ref)) return ref;
|
|
803
|
+
const prefix = ref.toLowerCase();
|
|
804
|
+
const byPrefix = ids.filter((id) => fingerprintOf(id)?.startsWith(prefix));
|
|
805
|
+
if (byPrefix.length === 1) return byPrefix[0];
|
|
806
|
+
if (byPrefix.length > 1) {
|
|
807
|
+
throw new CLIError(
|
|
808
|
+
`Ambiguous key reference "${ref}" matches ${byPrefix.length} keys by fingerprint.`,
|
|
809
|
+
"KEY_REF_AMBIGUOUS_ERROR",
|
|
810
|
+
{ ref }
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const byName = ids.filter((id) => kms.getEntry(id).tags?.name === ref);
|
|
814
|
+
if (byName.length === 1) return byName[0];
|
|
815
|
+
if (byName.length > 1) {
|
|
816
|
+
throw new CLIError(
|
|
817
|
+
`Ambiguous key name "${ref}" matches ${byName.length} keys.`,
|
|
818
|
+
"KEY_REF_AMBIGUOUS_ERROR",
|
|
819
|
+
{ ref }
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
throw new CLIError(`No key matches reference "${ref}".`, "KEY_NOT_FOUND_ERROR", { ref });
|
|
823
|
+
}
|
|
824
|
+
|
|
264
825
|
// src/commands/update.ts
|
|
265
826
|
function registerUpdateCommand(program, factory, globals) {
|
|
266
827
|
program.command("update").description("Update a did:btcr2 document.").requiredOption(
|
|
@@ -282,6 +843,13 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
282
843
|
"Beacon ID as a JSON string",
|
|
283
844
|
parseJsonArg("--beacon-id")
|
|
284
845
|
).action(async (options) => {
|
|
846
|
+
if (!/^\d+$/.test(options.sourceVersionId)) {
|
|
847
|
+
throw new CLIError(
|
|
848
|
+
"--source-version-id must be a non-negative integer.",
|
|
849
|
+
"INVALID_ARGUMENT_ERROR",
|
|
850
|
+
{ value: options.sourceVersionId }
|
|
851
|
+
);
|
|
852
|
+
}
|
|
285
853
|
const parsed = {
|
|
286
854
|
sourceDocument: options.sourceDocument,
|
|
287
855
|
patches: options.patches,
|
|
@@ -297,15 +865,19 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
297
865
|
options
|
|
298
866
|
);
|
|
299
867
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
868
|
+
const network = deriveNetwork(did);
|
|
869
|
+
const api = factory(network, globals());
|
|
870
|
+
const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
|
|
871
|
+
const signer = new import_key_manager2.KeyManagerSigner(api.kms.kms, keyId);
|
|
872
|
+
const data = await api.btcr2.update({
|
|
873
|
+
sourceDocument: parsed.sourceDocument,
|
|
874
|
+
patches: parsed.patches,
|
|
875
|
+
sourceVersionId: parsed.sourceVersionId,
|
|
876
|
+
verificationMethodId: parsed.verificationMethodId,
|
|
877
|
+
beaconId: parsed.beaconId,
|
|
878
|
+
signer
|
|
879
|
+
});
|
|
880
|
+
console.log(formatResult({ action: "update", data }, globals()));
|
|
309
881
|
});
|
|
310
882
|
}
|
|
311
883
|
function parseJsonArg(flagName) {
|
|
@@ -323,6 +895,7 @@ function parseJsonArg(flagName) {
|
|
|
323
895
|
}
|
|
324
896
|
|
|
325
897
|
// src/commands/deactivate.ts
|
|
898
|
+
var import_key_manager3 = require("@did-btcr2/key-manager");
|
|
326
899
|
var DEACTIVATION_PATCH = [{ op: "add", path: "/deactivated", value: true }];
|
|
327
900
|
function registerDeactivateCommand(program, factory, globals) {
|
|
328
901
|
program.command("deactivate").alias("delete").description("Deactivate the did:btcr2 identifier permanently. This is irreversible.").requiredOption(
|
|
@@ -340,6 +913,13 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
340
913
|
"Beacon ID as a JSON string",
|
|
341
914
|
parseJsonArg2("--beacon-id")
|
|
342
915
|
).action(async (options) => {
|
|
916
|
+
if (!/^\d+$/.test(options.sourceVersionId)) {
|
|
917
|
+
throw new CLIError(
|
|
918
|
+
"--source-version-id must be a non-negative integer.",
|
|
919
|
+
"INVALID_ARGUMENT_ERROR",
|
|
920
|
+
{ value: options.sourceVersionId }
|
|
921
|
+
);
|
|
922
|
+
}
|
|
343
923
|
const parsed = {
|
|
344
924
|
sourceDocument: options.sourceDocument,
|
|
345
925
|
patches: DEACTIVATION_PATCH,
|
|
@@ -355,15 +935,19 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
355
935
|
options
|
|
356
936
|
);
|
|
357
937
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
938
|
+
const network = deriveNetwork(did);
|
|
939
|
+
const api = factory(network, globals());
|
|
940
|
+
const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
|
|
941
|
+
const signer = new import_key_manager3.KeyManagerSigner(api.kms.kms, keyId);
|
|
942
|
+
const data = await api.btcr2.update({
|
|
943
|
+
sourceDocument: parsed.sourceDocument,
|
|
944
|
+
patches: parsed.patches,
|
|
945
|
+
sourceVersionId: parsed.sourceVersionId,
|
|
946
|
+
verificationMethodId: parsed.verificationMethodId,
|
|
947
|
+
beaconId: parsed.beaconId,
|
|
948
|
+
signer
|
|
949
|
+
});
|
|
950
|
+
console.log(formatResult({ action: "deactivate", data }, globals()));
|
|
367
951
|
});
|
|
368
952
|
}
|
|
369
953
|
function parseJsonArg2(flagName) {
|
|
@@ -380,19 +964,263 @@ function parseJsonArg2(flagName) {
|
|
|
380
964
|
};
|
|
381
965
|
}
|
|
382
966
|
|
|
967
|
+
// src/commands/key.ts
|
|
968
|
+
var import_keypair = require("@did-btcr2/keypair");
|
|
969
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
970
|
+
var import_node_fs5 = require("fs");
|
|
971
|
+
function registerKeyCommand(program, factory, globals) {
|
|
972
|
+
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
973
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
974
|
+
key.command("generate").description("Generate a new keypair and store it.").option("--name <name>", "A human-friendly name, stored as a tag and usable as a key reference.").option("--set-active", "Make this the active key.", false).action((options) => {
|
|
975
|
+
const api = factory(void 0, globals());
|
|
976
|
+
assertNameAvailable(api.kms.kms, options.name);
|
|
977
|
+
const setActive = options.setActive ?? false;
|
|
978
|
+
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
979
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils2.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
980
|
+
});
|
|
981
|
+
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
982
|
+
const kms = factory(void 0, globals()).kms.kms;
|
|
983
|
+
const active = kms.activeKeyId;
|
|
984
|
+
const data = kms.listKeys().map((id) => {
|
|
985
|
+
const entry = kms.getEntry(id);
|
|
986
|
+
return {
|
|
987
|
+
keyId: id,
|
|
988
|
+
fingerprint: id.split(":").pop() ?? id,
|
|
989
|
+
...entry.tags?.name && { name: entry.tags.name },
|
|
990
|
+
active: id === active
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
print({ action: "key-list", data });
|
|
994
|
+
});
|
|
995
|
+
key.command("show <ref>").description("Show a key's public material and tags. Never prints the secret.").action((ref) => {
|
|
996
|
+
const kms = factory(void 0, globals()).kms.kms;
|
|
997
|
+
const id = resolveKeyRef(kms, ref);
|
|
998
|
+
const entry = kms.getEntry(id);
|
|
999
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils2.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
1000
|
+
});
|
|
1001
|
+
key.command("import").description("Import a key: a secret from a hex file, or a public key as watch-only.").option("--secret-file <path>", "Path to a file containing a 32-byte secret key as hex.").option("--public <hex>", "A 33-byte compressed public key as hex (imported watch-only).").option("--name <name>", "A human-friendly name, stored as a tag.").option("--set-active", "Make this the active key.", false).action((options) => {
|
|
1002
|
+
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
1003
|
+
throw new CLIError("Provide exactly one of --secret-file or --public.", "INVALID_ARGUMENT_ERROR");
|
|
1004
|
+
}
|
|
1005
|
+
const api = factory(void 0, globals());
|
|
1006
|
+
assertNameAvailable(api.kms.kms, options.name);
|
|
1007
|
+
const keyPair = options.secretFile ? new import_keypair.SchnorrKeyPair({ secretKey: readHexFile(options.secretFile, 32, "--secret-file") }) : new import_keypair.SchnorrKeyPair({ publicKey: parseHex(options.public ?? "", 33, "--public") });
|
|
1008
|
+
const setActive = options.setActive ?? false;
|
|
1009
|
+
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
1010
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils2.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
1011
|
+
});
|
|
1012
|
+
key.command("export <ref>").description("Export a key. Public material by default; --secret writes the secret to a file.").option("--secret", "Export the secret key. Requires --out.", false).option("--out <path>", "Write the exported secret to this file (created 0600).").action((ref, options) => {
|
|
1013
|
+
const api = factory(void 0, globals());
|
|
1014
|
+
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1015
|
+
if (!options.secret) {
|
|
1016
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils2.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (!options.out) {
|
|
1020
|
+
throw new CLIError("Exporting a secret requires --out <file> so it is not written to the terminal.", "INVALID_ARGUMENT_ERROR");
|
|
1021
|
+
}
|
|
1022
|
+
const keyPair = api.kms.export(id);
|
|
1023
|
+
if (!keyPair.hasSecretKey) {
|
|
1024
|
+
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
1025
|
+
}
|
|
1026
|
+
process.stderr.write("warning: writing an unencrypted secret key to disk. Protect this file and delete it when done.\n");
|
|
1027
|
+
writeSecretFile(options.out, (0, import_utils2.bytesToHex)(keyPair.secretKey.bytes));
|
|
1028
|
+
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
1029
|
+
});
|
|
1030
|
+
key.command("delete <ref>").alias("rm").description("Delete a key from the keystore.").option("--force", "Delete even if it is the active key.", false).action((ref, options) => {
|
|
1031
|
+
const api = factory(void 0, globals());
|
|
1032
|
+
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1033
|
+
api.kms.removeKey(id, { force: options.force ?? false });
|
|
1034
|
+
print({ action: "key-delete", data: { keyId: id, deleted: true } });
|
|
1035
|
+
});
|
|
1036
|
+
key.command("use <ref>").description("Set the active key, persisted across invocations.").action((ref) => {
|
|
1037
|
+
const api = factory(void 0, globals());
|
|
1038
|
+
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1039
|
+
api.kms.setActive(id);
|
|
1040
|
+
print({ action: "key-use", data: { keyId: id, active: true } });
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
function assertNameAvailable(kms, name) {
|
|
1044
|
+
if (!name) return;
|
|
1045
|
+
if (kms.listKeys().some((id) => kms.getEntry(id).tags?.name === name)) {
|
|
1046
|
+
throw new CLIError(`A key named "${name}" already exists.`, "INVALID_ARGUMENT_ERROR", { name });
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function parseHex(hex, expectedBytes, label) {
|
|
1050
|
+
let bytes;
|
|
1051
|
+
try {
|
|
1052
|
+
bytes = (0, import_utils2.hexToBytes)(hex.trim());
|
|
1053
|
+
} catch {
|
|
1054
|
+
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
1055
|
+
}
|
|
1056
|
+
if (bytes.length !== expectedBytes) {
|
|
1057
|
+
throw new CLIError(
|
|
1058
|
+
`${label} must be ${expectedBytes} bytes (${expectedBytes * 2} hex chars), got ${bytes.length}.`,
|
|
1059
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1060
|
+
{ label }
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return bytes;
|
|
1064
|
+
}
|
|
1065
|
+
function readHexFile(path, expectedBytes, label) {
|
|
1066
|
+
let content;
|
|
1067
|
+
try {
|
|
1068
|
+
content = (0, import_node_fs5.readFileSync)(path, "utf-8");
|
|
1069
|
+
} catch {
|
|
1070
|
+
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
1071
|
+
}
|
|
1072
|
+
return parseHex(content, expectedBytes, label);
|
|
1073
|
+
}
|
|
1074
|
+
function writeSecretFile(path, contents) {
|
|
1075
|
+
let fd;
|
|
1076
|
+
try {
|
|
1077
|
+
fd = (0, import_node_fs5.openSync)(path, "wx", 384);
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
if (error.code === "EEXIST") {
|
|
1080
|
+
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
1081
|
+
}
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
try {
|
|
1085
|
+
(0, import_node_fs5.writeFileSync)(fd, contents);
|
|
1086
|
+
} finally {
|
|
1087
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/commands/config.ts
|
|
1092
|
+
var import_node_fs6 = require("fs");
|
|
1093
|
+
var import_node_path5 = require("path");
|
|
1094
|
+
function registerConfigCommand(program, globals) {
|
|
1095
|
+
const config = program.command("config").description("Read and write CLI configuration.");
|
|
1096
|
+
const path = () => globals().config ?? defaultConfigPath();
|
|
1097
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1098
|
+
config.command("init").description("Create a default config file with one profile per network.").option("--force", "Overwrite an existing config file.", false).action((options) => {
|
|
1099
|
+
const p = path();
|
|
1100
|
+
if ((0, import_node_fs6.existsSync)(p) && !options.force) {
|
|
1101
|
+
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
1102
|
+
}
|
|
1103
|
+
const scaffold = {
|
|
1104
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1105
|
+
defaults: { output: "text" },
|
|
1106
|
+
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1107
|
+
};
|
|
1108
|
+
ensureDir((0, import_node_path5.dirname)(p), 448);
|
|
1109
|
+
writeFileAtomic(p, `${JSON.stringify(scaffold, null, 2)}
|
|
1110
|
+
`, 384);
|
|
1111
|
+
print({ action: "config-init", data: { path: p } });
|
|
1112
|
+
});
|
|
1113
|
+
config.command("get [path]").description("Print a value at a dotted path, or the whole config.").action((dotted) => {
|
|
1114
|
+
const file = readConfigFile(path()) ?? {};
|
|
1115
|
+
print({ action: "config-get", data: (dotted ? getConfigPath(file, dotted) : file) ?? null });
|
|
1116
|
+
});
|
|
1117
|
+
config.command("set <path> <value>").description("Set a value at a dotted path. The value is parsed as JSON when valid, else stored as a string.").action((dotted, value) => {
|
|
1118
|
+
writeConfigFile(path(), (raw) => setConfigPath(raw, dotted, parseValue(value)));
|
|
1119
|
+
print({ action: "config-set", data: { path: dotted } });
|
|
1120
|
+
});
|
|
1121
|
+
config.command("unset <path>").description("Delete a value at a dotted path.").action((dotted) => {
|
|
1122
|
+
writeConfigFile(path(), (raw) => unsetConfigPath(raw, dotted));
|
|
1123
|
+
print({ action: "config-unset", data: { path: dotted } });
|
|
1124
|
+
});
|
|
1125
|
+
config.command("list").alias("ls").description("Print the entire config file.").action(() => {
|
|
1126
|
+
print({ action: "config-list", data: readConfigFile(path()) ?? {} });
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
function parseValue(value) {
|
|
1130
|
+
try {
|
|
1131
|
+
return JSON.parse(value);
|
|
1132
|
+
} catch {
|
|
1133
|
+
return value;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/commands/profile.ts
|
|
1138
|
+
function registerProfileCommand(program, globals) {
|
|
1139
|
+
const profile = program.command("profile").description("Manage configuration profiles.");
|
|
1140
|
+
const path = () => globals().config ?? defaultConfigPath();
|
|
1141
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1142
|
+
profile.command("add <name>").description("Add an empty profile.").action((name) => {
|
|
1143
|
+
writeConfigFile(path(), (raw) => {
|
|
1144
|
+
if (raw.profiles === void 0 || raw.profiles === null) raw.profiles = {};
|
|
1145
|
+
const profiles = raw.profiles;
|
|
1146
|
+
if (profiles[name]) throw new CLIError(`Profile "${name}" already exists.`, "INVALID_ARGUMENT_ERROR", { name });
|
|
1147
|
+
profiles[name] = {};
|
|
1148
|
+
});
|
|
1149
|
+
print({ action: "profile-add", data: { profile: name } });
|
|
1150
|
+
});
|
|
1151
|
+
profile.command("use <name>").description("Set the active profile (writes defaults.profile).").action((name) => {
|
|
1152
|
+
writeConfigFile(path(), (raw) => {
|
|
1153
|
+
if (raw.defaults === void 0 || raw.defaults === null) raw.defaults = {};
|
|
1154
|
+
raw.defaults.profile = name;
|
|
1155
|
+
});
|
|
1156
|
+
print({ action: "profile-use", data: { profile: name } });
|
|
1157
|
+
});
|
|
1158
|
+
profile.command("show [name]").description("Show a profile (defaults to the active profile).").action((name) => {
|
|
1159
|
+
const file = readConfigFile(path()) ?? {};
|
|
1160
|
+
const target = name ?? file.defaults?.profile;
|
|
1161
|
+
if (!target) {
|
|
1162
|
+
throw new CLIError("No profile specified and no active profile is set.", "INVALID_ARGUMENT_ERROR");
|
|
1163
|
+
}
|
|
1164
|
+
const data = file.profiles?.[target];
|
|
1165
|
+
if (!data) {
|
|
1166
|
+
throw new CLIError(`Profile "${target}" not found.`, "INVALID_ARGUMENT_ERROR", { profile: target });
|
|
1167
|
+
}
|
|
1168
|
+
print({ action: "profile-show", data: { profile: target, ...data } });
|
|
1169
|
+
});
|
|
1170
|
+
profile.command("remove <name>").alias("rm").description("Remove a profile.").action((name) => {
|
|
1171
|
+
writeConfigFile(path(), (raw) => {
|
|
1172
|
+
const profiles = raw.profiles;
|
|
1173
|
+
if (!profiles?.[name]) throw new CLIError(`Profile "${name}" not found.`, "INVALID_ARGUMENT_ERROR", { name });
|
|
1174
|
+
delete profiles[name];
|
|
1175
|
+
});
|
|
1176
|
+
print({ action: "profile-remove", data: { profile: name } });
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/commands/completion.ts
|
|
1181
|
+
var COMMANDS = "create resolve read update deactivate delete key config profile completion";
|
|
1182
|
+
function registerCompletionCommand(program, _globals) {
|
|
1183
|
+
program.command("completion [shell]").description("Print a shell completion script (bash, zsh, or fish) to stdout.").action((shell = "bash") => {
|
|
1184
|
+
console.log(completionScript(shell));
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
function completionScript(shell) {
|
|
1188
|
+
switch (shell) {
|
|
1189
|
+
case "bash":
|
|
1190
|
+
return [
|
|
1191
|
+
'# btcr2 bash completion. Install with: eval "$(btcr2 completion bash)"',
|
|
1192
|
+
'_btcr2() { COMPREPLY=( $(compgen -W "' + COMMANDS + '" -- "${COMP_WORDS[COMP_CWORD]}") ); }',
|
|
1193
|
+
"complete -F _btcr2 btcr2"
|
|
1194
|
+
].join("\n");
|
|
1195
|
+
case "zsh":
|
|
1196
|
+
return [
|
|
1197
|
+
'# btcr2 zsh completion. Install with: eval "$(btcr2 completion zsh)"',
|
|
1198
|
+
"_btcr2() { compadd " + COMMANDS + " }",
|
|
1199
|
+
"compdef _btcr2 btcr2"
|
|
1200
|
+
].join("\n");
|
|
1201
|
+
case "fish":
|
|
1202
|
+
return [
|
|
1203
|
+
"# btcr2 fish completion. Save to ~/.config/fish/completions/btcr2.fish",
|
|
1204
|
+
'complete -c btcr2 -f -a "' + COMMANDS + '"'
|
|
1205
|
+
].join("\n");
|
|
1206
|
+
default:
|
|
1207
|
+
throw new CLIError(`Unsupported shell "${shell}". Use bash, zsh, or fish.`, "INVALID_ARGUMENT_ERROR", { shell });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
383
1211
|
// src/version.ts
|
|
384
|
-
var
|
|
385
|
-
var
|
|
1212
|
+
var import_node_fs7 = require("fs");
|
|
1213
|
+
var import_node_path6 = require("path");
|
|
386
1214
|
var import_node_url = require("url");
|
|
387
1215
|
function readVersion() {
|
|
388
|
-
let dir = (0,
|
|
1216
|
+
let dir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
389
1217
|
for (let i = 0; i < 5; i++) {
|
|
390
1218
|
try {
|
|
391
|
-
const pkg = JSON.parse((0,
|
|
1219
|
+
const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
|
|
392
1220
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
393
1221
|
} catch {
|
|
394
1222
|
}
|
|
395
|
-
dir = (0,
|
|
1223
|
+
dir = (0, import_node_path6.dirname)(dir);
|
|
396
1224
|
}
|
|
397
1225
|
return "0.0.0";
|
|
398
1226
|
}
|
|
@@ -409,15 +1237,23 @@ var DidBtcr2Cli = class {
|
|
|
409
1237
|
* {@link defaultApiFactory} which uses public endpoints (mempool.space)
|
|
410
1238
|
* for known networks and localhost Polar for regtest.
|
|
411
1239
|
*
|
|
412
|
-
* @param factory - Optional API factory
|
|
1240
|
+
* @param factory - Optional API factory for keystore-free commands (create,
|
|
1241
|
+
* resolve). Defaults to {@link defaultApiFactory}.
|
|
1242
|
+
* @param keystoreFactory - Optional keystore-aware API factory for commands
|
|
1243
|
+
* that need a signing identity (key, update, deactivate). Defaults to
|
|
1244
|
+
* {@link keystoreApiFactory}.
|
|
413
1245
|
*/
|
|
414
|
-
constructor(factory = defaultApiFactory) {
|
|
415
|
-
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text>", "text").option("--verbose", "Verbose output", false).option("--quiet", "Suppress non-essential output", false).option("-c, --config <path>", "Path to config file (default: $XDG_CONFIG_HOME/btcr2/config.json)").option("--profile <name>", "Config profile name (default: auto-detected from network)").option("--btc-rest <url>", "Override Bitcoin REST endpoint (Esplora API)").option("--btc-rpc-url <url>", "Override Bitcoin Core RPC endpoint").option("--btc-rpc-user <user>", "Bitcoin Core RPC username").option("--btc-rpc-pass <pass>", "Bitcoin Core RPC password").option("--cas-gateway <url>", "IPFS HTTP gateway for CAS reads");
|
|
1246
|
+
constructor(factory = defaultApiFactory, keystoreFactory = keystoreApiFactory) {
|
|
1247
|
+
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text>", "text").option("--verbose", "Verbose output", false).option("--quiet", "Suppress non-essential output", false).option("-c, --config <path>", "Path to config file (default: $XDG_CONFIG_HOME/btcr2/config.json)").option("--profile <name>", "Config profile name (default: auto-detected from network)").option("--btc-rest <url>", "Override Bitcoin REST endpoint (Esplora API)").option("--btc-rpc-url <url>", "Override Bitcoin Core RPC endpoint").option("--btc-rpc-user <user>", "Bitcoin Core RPC username").option("--btc-rpc-pass <pass>", "Bitcoin Core RPC password").option("--cas-gateway <url>", "IPFS HTTP gateway for CAS reads").option("--keystore <path>", "Path to the keystore file (default: $XDG_DATA_HOME/btcr2/keystore.json)").option("--passphrase-file <path>", "Read the keystore passphrase from a file (unattended use)").option("--signing-key <ref>", "Key for update/deactivate signing: a URN, fingerprint prefix, or name");
|
|
416
1248
|
const globals = () => this.program.opts();
|
|
417
1249
|
registerCreateCommand(this.program, factory, globals);
|
|
418
1250
|
registerResolveCommand(this.program, factory, globals);
|
|
419
|
-
registerUpdateCommand(this.program,
|
|
420
|
-
registerDeactivateCommand(this.program,
|
|
1251
|
+
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
1252
|
+
registerDeactivateCommand(this.program, keystoreFactory, globals);
|
|
1253
|
+
registerKeyCommand(this.program, keystoreFactory, globals);
|
|
1254
|
+
registerConfigCommand(this.program, globals);
|
|
1255
|
+
registerProfileCommand(this.program, globals);
|
|
1256
|
+
registerCompletionCommand(this.program, globals);
|
|
421
1257
|
}
|
|
422
1258
|
/**
|
|
423
1259
|
* Runs the CLI with the provided argv or process.argv.
|
|
@@ -430,7 +1266,7 @@ var DidBtcr2Cli = class {
|
|
|
430
1266
|
await this.program.parseAsync(normalized, { from: "node" });
|
|
431
1267
|
if (!this.program.args.length) this.program.outputHelp();
|
|
432
1268
|
} catch (error) {
|
|
433
|
-
handleError(error);
|
|
1269
|
+
handleError(error, Boolean(this.program.opts().verbose));
|
|
434
1270
|
}
|
|
435
1271
|
}
|
|
436
1272
|
};
|
|
@@ -439,12 +1275,12 @@ function normalizeArgv(argv) {
|
|
|
439
1275
|
if (argv.length === 1) return ["node", argv[0]];
|
|
440
1276
|
return ["node", "btcr2"];
|
|
441
1277
|
}
|
|
442
|
-
function handleError(error) {
|
|
1278
|
+
function handleError(error, verbose) {
|
|
443
1279
|
if (error instanceof import_commander.CommanderError && (error.code === "commander.helpDisplayed" || error.code === "commander.help")) {
|
|
444
1280
|
return;
|
|
445
1281
|
}
|
|
446
|
-
if (error instanceof
|
|
447
|
-
console.error(error.message);
|
|
1282
|
+
if (error instanceof import_common3.DidMethodError) {
|
|
1283
|
+
console.error(verbose ? error : error.message);
|
|
448
1284
|
process.exitCode ??= 1;
|
|
449
1285
|
return;
|
|
450
1286
|
}
|
|
@@ -454,6 +1290,7 @@ function handleError(error) {
|
|
|
454
1290
|
// Annotate the CommonJS export names for ESM import in node:
|
|
455
1291
|
0 && (module.exports = {
|
|
456
1292
|
CLIError,
|
|
1293
|
+
CONFIG_SCHEMA_VERSION,
|
|
457
1294
|
DidBtcr2Cli,
|
|
458
1295
|
ENV_VARS,
|
|
459
1296
|
SUPPORTED_NETWORKS,
|
|
@@ -462,11 +1299,20 @@ function handleError(error) {
|
|
|
462
1299
|
defaultConfigPath,
|
|
463
1300
|
deriveNetwork,
|
|
464
1301
|
formatResult,
|
|
1302
|
+
getConfigPath,
|
|
1303
|
+
keystoreApiFactory,
|
|
465
1304
|
profileToOverrides,
|
|
466
1305
|
readConfigFile,
|
|
467
1306
|
readEnvOverrides,
|
|
1307
|
+
registerCompletionCommand,
|
|
1308
|
+
registerConfigCommand,
|
|
468
1309
|
registerCreateCommand,
|
|
469
1310
|
registerDeactivateCommand,
|
|
1311
|
+
registerKeyCommand,
|
|
1312
|
+
registerProfileCommand,
|
|
470
1313
|
registerResolveCommand,
|
|
471
|
-
registerUpdateCommand
|
|
1314
|
+
registerUpdateCommand,
|
|
1315
|
+
setConfigPath,
|
|
1316
|
+
unsetConfigPath,
|
|
1317
|
+
writeConfigFile
|
|
472
1318
|
});
|