@did-btcr2/cli 0.13.0 → 0.15.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 (52) hide show
  1. package/README.md +95 -14
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cjs/index.js +674 -54
  4. package/dist/esm/src/cli.js +25 -3
  5. package/dist/esm/src/cli.js.map +1 -1
  6. package/dist/esm/src/commands/config.js +124 -8
  7. package/dist/esm/src/commands/config.js.map +1 -1
  8. package/dist/esm/src/commands/create.js +10 -1
  9. package/dist/esm/src/commands/create.js.map +1 -1
  10. package/dist/esm/src/commands/deactivate.js +33 -6
  11. package/dist/esm/src/commands/deactivate.js.map +1 -1
  12. package/dist/esm/src/commands/profile.js +5 -3
  13. package/dist/esm/src/commands/profile.js.map +1 -1
  14. package/dist/esm/src/commands/update.js +33 -6
  15. package/dist/esm/src/commands/update.js.map +1 -1
  16. package/dist/esm/src/config-schema.js +149 -0
  17. package/dist/esm/src/config-schema.js.map +1 -0
  18. package/dist/esm/src/config.js +515 -35
  19. package/dist/esm/src/config.js.map +1 -1
  20. package/dist/esm/src/keystore/paths.js +3 -2
  21. package/dist/esm/src/keystore/paths.js.map +1 -1
  22. package/dist/esm/src/output.js +49 -0
  23. package/dist/esm/src/output.js.map +1 -1
  24. package/dist/esm/src/types.js +11 -0
  25. package/dist/esm/src/types.js.map +1 -1
  26. package/dist/types/src/cli.d.ts.map +1 -1
  27. package/dist/types/src/commands/config.d.ts.map +1 -1
  28. package/dist/types/src/commands/create.d.ts.map +1 -1
  29. package/dist/types/src/commands/deactivate.d.ts.map +1 -1
  30. package/dist/types/src/commands/profile.d.ts.map +1 -1
  31. package/dist/types/src/commands/update.d.ts.map +1 -1
  32. package/dist/types/src/config-schema.d.ts +24 -0
  33. package/dist/types/src/config-schema.d.ts.map +1 -0
  34. package/dist/types/src/config.d.ts +228 -6
  35. package/dist/types/src/config.d.ts.map +1 -1
  36. package/dist/types/src/keystore/paths.d.ts.map +1 -1
  37. package/dist/types/src/output.d.ts +22 -0
  38. package/dist/types/src/output.d.ts.map +1 -1
  39. package/dist/types/src/types.d.ts +33 -0
  40. package/dist/types/src/types.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/cli.ts +27 -3
  43. package/src/commands/config.ts +135 -8
  44. package/src/commands/create.ts +13 -1
  45. package/src/commands/deactivate.ts +53 -6
  46. package/src/commands/profile.ts +5 -3
  47. package/src/commands/update.ts +53 -6
  48. package/src/config-schema.ts +178 -0
  49. package/src/config.ts +693 -43
  50. package/src/keystore/paths.ts +3 -2
  51. package/src/output.ts +53 -0
  52. package/src/types.ts +23 -0
@@ -2,9 +2,18 @@ import type { DidUpdateResult } from '@did-btcr2/api';
2
2
  import type { PatchOperation } from '@did-btcr2/common';
3
3
  import type { Btcr2DidDocument, ResolutionOptions } from '@did-btcr2/method';
4
4
  import type { DidResolutionResult } from '@web5/dids';
5
+ import type { DoctorReport, EffectiveConfig } from './config.js';
6
+ import type { ConfigIssue } from './config-schema.js';
5
7
  export type NetworkOption = 'bitcoin' | 'testnet3' | 'testnet4' | 'signet' | 'mutinynet' | 'regtest';
6
8
  export type OutputFormat = 'json' | 'text';
7
9
  export declare const SUPPORTED_NETWORKS: NetworkOption[];
10
+ /**
11
+ * Normalizes a blank value (empty or whitespace-only) to `undefined`, so a
12
+ * blank at any precedence layer defers to the next layer instead of masking it.
13
+ * A non-blank value is returned unchanged. This mirrors the `|| undefined`
14
+ * treatment the environment layer already applies in `readEnvOverrides`.
15
+ */
16
+ export declare function blankToUndef(value: string | undefined): string | undefined;
8
17
  export interface ResolveCommandOptions {
9
18
  identifier: string;
10
19
  options?: ResolutionOptions;
@@ -100,6 +109,24 @@ export type CommandResult = {
100
109
  } | {
101
110
  action: 'config-list';
102
111
  data: unknown;
112
+ } | {
113
+ action: 'config-validate';
114
+ data: {
115
+ ok: boolean;
116
+ issues: ConfigIssue[];
117
+ };
118
+ } | {
119
+ action: 'config-effective';
120
+ data: EffectiveConfig;
121
+ } | {
122
+ action: 'config-path';
123
+ data: {
124
+ config: string;
125
+ keystore: string;
126
+ };
127
+ } | {
128
+ action: 'config-doctor';
129
+ data: DoctorReport;
103
130
  } | {
104
131
  action: 'profile-add';
105
132
  data: {
@@ -130,6 +157,12 @@ export interface GlobalOptions {
130
157
  btcRpcUser?: string;
131
158
  btcRpcPass?: string;
132
159
  casGateway?: string;
160
+ casRpcUrl?: string;
161
+ btcTimeout?: string;
162
+ casTimeout?: string;
163
+ btcRestHeader?: string[];
164
+ btcRpcWallet?: string;
165
+ btcRpcHeader?: string[];
133
166
  keystore?: string;
134
167
  passphraseFile?: string;
135
168
  signingKey?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@did-btcr2/cli",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "description": "CLI for interacting with did-btcr2-js, the JavaScript/TypeScript reference implementation of the did:btcr2 method. Exposes various parts of multiple packages in the did-btcr2-js monorepo.",
6
6
  "main": "./dist/cjs/index.js",
@@ -61,11 +61,11 @@
61
61
  "@scure/base": "^1.2.6",
62
62
  "@web5/dids": "^1.2.0",
63
63
  "commander": "^13.1.0",
64
- "@did-btcr2/api": "^0.15.0",
64
+ "@did-btcr2/keypair": "^0.13.1",
65
+ "@did-btcr2/api": "^0.16.1",
65
66
  "@did-btcr2/key-manager": "^0.7.0",
66
67
  "@did-btcr2/common": "^9.1.0",
67
- "@did-btcr2/keypair": "^0.13.1",
68
- "@did-btcr2/method": "^0.54.0"
68
+ "@did-btcr2/method": "^0.54.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@eslint/js": "^9.21.0",
package/src/cli.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  registerResolveCommand,
11
11
  registerUpdateCommand,
12
12
  } from './commands/index.js';
13
- import { defaultApiFactory, keystoreApiFactory, type ApiFactory } from './config.js';
13
+ import { defaultApiFactory, keystoreApiFactory, resolveOutputFormat, type ApiFactory } from './config.js';
14
14
  import type { GlobalOptions } from './types.js';
15
15
  import { VERSION } from './version.js';
16
16
 
@@ -41,7 +41,7 @@ export class DidBtcr2Cli {
41
41
  this.program = new Command('btcr2')
42
42
  .version(`btcr2 ${VERSION}`, '-v, --version', 'Output the current version')
43
43
  .description('CLI tool for the did:btcr2 method')
44
- .option('-o, --output <format>', 'Output format <json|text>', 'text')
44
+ .option('-o, --output <format>', 'Output format <json|text> (default: config defaults.output, else text)')
45
45
  .option('--verbose', 'Verbose output', false)
46
46
  .option('--quiet', 'Suppress non-essential output', false)
47
47
  .option('-c, --config <path>', 'Path to config file (default: $XDG_CONFIG_HOME/btcr2/config.json)')
@@ -50,13 +50,29 @@ export class DidBtcr2Cli {
50
50
  .option('--btc-rpc-url <url>', 'Override Bitcoin Core RPC endpoint')
51
51
  .option('--btc-rpc-user <user>', 'Bitcoin Core RPC username')
52
52
  .option('--btc-rpc-pass <pass>', 'Bitcoin Core RPC password')
53
- .option('--cas-gateway <url>', 'IPFS HTTP gateway for CAS reads')
53
+ .option('--cas-gateway <url>', 'IPFS HTTP gateway for CAS reads (read-only)')
54
+ .option('--cas-rpc-url <url>', 'IPFS HTTP RPC endpoint for a writable CAS (reads + writes; enables --publish-to-cas)')
55
+ .option('--btc-timeout <ms>', 'Bitcoin REST/RPC request timeout in milliseconds (default: unbounded)')
56
+ .option('--cas-timeout <ms>', 'CAS request timeout in milliseconds (default: 30000; 0 disables)')
57
+ .option('--btc-rest-header <header>', 'Extra Bitcoin REST header "Key: Value" (repeatable)', collectHeader, [])
58
+ .option('--btc-rpc-wallet <name>', 'Bitcoin Core wallet name for wallet-scoped RPCs')
59
+ .option('--btc-rpc-header <header>', 'Extra Bitcoin Core RPC header "Key: Value" (repeatable)', collectHeader, [])
54
60
  .option('--keystore <path>', 'Path to the keystore file (default: $XDG_DATA_HOME/btcr2/keystore.json)')
55
61
  .option('--passphrase-file <path>', 'Read the keystore passphrase from a file (unattended use)')
56
62
  .option('--signing-key <ref>', 'Key for create/update/deactivate signing: a URN, fingerprint prefix, or name');
57
63
 
58
64
  const globals = (): GlobalOptions => this.program.opts() as GlobalOptions;
59
65
 
66
+ // Resolve the effective output format (flag -> BTCR2_OUTPUT -> config
67
+ // defaults.output -> 'text') before any command action runs, so a configured
68
+ // default is honored while an explicit flag still wins. The commander option
69
+ // carries no hard default (which would mask defaults.output); the resolved
70
+ // value is written back so every command reads it through globals().output.
71
+ this.program.hook('preAction', () => {
72
+ const opts = this.program.opts() as GlobalOptions;
73
+ opts.output = resolveOutputFormat({ output: opts.output, config: opts.config });
74
+ });
75
+
60
76
  registerCreateCommand(this.program, factory, keystoreFactory, globals);
61
77
  registerResolveCommand(this.program, factory, globals);
62
78
  registerUpdateCommand(this.program, keystoreFactory, globals);
@@ -83,6 +99,14 @@ export class DidBtcr2Cli {
83
99
  }
84
100
  }
85
101
 
102
+ /**
103
+ * Commander collector for a repeatable header option: accumulates each
104
+ * `Key: Value` occurrence into an array, preserving order.
105
+ */
106
+ function collectHeader(value: string, previous: string[]): string[] {
107
+ return [ ...previous, value ];
108
+ }
109
+
86
110
  /**
87
111
  * Normalizes argv to ensure it has at least two elements.
88
112
  * @param {string[]} argv - The original argv array.
@@ -5,15 +5,21 @@ import {
5
5
  CONFIG_SCHEMA_VERSION,
6
6
  defaultConfigPath,
7
7
  getConfigPath,
8
+ parseConfigFileRaw,
8
9
  readConfigFile,
10
+ resolveDefaultNetwork,
11
+ resolveEffectiveConfig,
12
+ runDoctor,
9
13
  setConfigPath,
10
14
  unsetConfigPath,
11
15
  writeConfigFile,
12
16
  } from '../config.js';
17
+ import { findConfigIssues, validateConfigSet } from '../config-schema.js';
13
18
  import { CLIError } from '../error.js';
14
19
  import { ensureDir, writeFileAtomic } from '../keystore/atomic.js';
15
- import { formatResult } from '../output.js';
16
- import type { CommandResult, GlobalOptions } from '../types.js';
20
+ import { defaultKeystorePath } from '../keystore/paths.js';
21
+ import { formatResult, REDACTED, redactSecrets, scrubUrlUserinfo } from '../output.js';
22
+ import type { CommandResult, GlobalOptions, NetworkOption } from '../types.js';
17
23
  import { SUPPORTED_NETWORKS } from '../types.js';
18
24
 
19
25
  /** Registers the `config` command group for reading and writing CLI configuration. */
@@ -44,16 +50,26 @@ export function registerConfigCommand(program: Command, globals: () => GlobalOpt
44
50
  config
45
51
  .command('get [path]')
46
52
  .description('Print a value at a dotted path, or the whole config.')
47
- .action((dotted?: string) => {
53
+ .option('--show-secrets', 'Reveal secret values (RPC password, etc.) instead of redacting them.', false)
54
+ .action((dotted: string | undefined, opts: { showSecrets?: boolean }) => {
48
55
  const file = (readConfigFile(path()) ?? {}) as Record<string, unknown>;
49
- print({ action: 'config-get', data: (dotted ? getConfigPath(file, dotted) : file) ?? null });
56
+ const raw = (dotted ? getConfigPath(file, dotted) : file) ?? null;
57
+ const leaf = dotted ? dotted.split('.').pop() : undefined;
58
+ print({ action: 'config-get', data: opts.showSecrets ? raw : redactSecrets(raw, leaf) });
50
59
  });
51
60
 
52
61
  config
53
62
  .command('set <path> <value>')
54
63
  .description('Set a value at a dotted path. The value is parsed as JSON when valid, else stored as a string.')
55
64
  .action((dotted: string, value: string) => {
56
- writeConfigFile(path(), raw => setConfigPath(raw, dotted, parseValue(value)));
65
+ const parsed = parseValue(dotted, value);
66
+ // Reject an invalid enum value for a known key up-front; warn (but still
67
+ // write) an unknown path so forward-compatible and third-party keys work.
68
+ const { unknownPath } = validateConfigSet(dotted, parsed);
69
+ if (unknownPath && !globals().quiet) {
70
+ console.error(`Warning: "${dotted}" is not a known config path; writing it anyway.`);
71
+ }
72
+ writeConfigFile(path(), raw => setConfigPath(raw, dotted, parsed));
57
73
  print({ action: 'config-set', data: { path: dotted } });
58
74
  });
59
75
 
@@ -69,16 +85,127 @@ export function registerConfigCommand(program: Command, globals: () => GlobalOpt
69
85
  .command('list')
70
86
  .alias('ls')
71
87
  .description('Print the entire config file.')
88
+ .option('--show-secrets', 'Reveal secret values (RPC password, etc.) instead of redacting them.', false)
89
+ .action((opts: { showSecrets?: boolean }) => {
90
+ const file = readConfigFile(path()) ?? {};
91
+ print({ action: 'config-list', data: opts.showSecrets ? file : redactSecrets(file) });
92
+ });
93
+
94
+ config
95
+ .command('validate')
96
+ .description('Check the config for unknown keys, invalid enum values, and an unsupported schema version.')
97
+ .action(() => {
98
+ // Read raw (bypassing the schema-version ceiling that readConfigFile
99
+ // enforces) so a newer-than-supported version is reported as a finding
100
+ // rather than aborting the very command meant to diagnose it.
101
+ const file = parseConfigFileRaw(path()) ?? {};
102
+ const issues = findConfigIssues(file, CONFIG_SCHEMA_VERSION);
103
+ if (issues.length > 0) process.exitCode ??= 1;
104
+ print({ action: 'config-validate', data: { ok: issues.length === 0, issues } });
105
+ });
106
+
107
+ config
108
+ .command('effective')
109
+ .description('Print the resolved connection config with per-value provenance (flag|env|file|default).')
110
+ .option('-n, --network <network>', 'Network to resolve for (default: config default network)')
111
+ .option('--show-secrets', 'Reveal secret values (RPC password) instead of redacting them.', false)
112
+ .action((opts: { network?: string; showSecrets?: boolean }) => {
113
+ const network = resolveIntrospectionNetwork(opts.network, globals());
114
+ const data = resolveEffectiveConfig(network, globals());
115
+ // Redact the resolved RPC password and any password embedded in an endpoint
116
+ // URL by default; provenance still shows where each value came from.
117
+ // --show-secrets reveals them for deliberate debugging.
118
+ if (!opts.showSecrets) {
119
+ if (data.btc.rpcPass.value !== undefined) {
120
+ data.btc.rpcPass = { ...data.btc.rpcPass, value: REDACTED };
121
+ }
122
+ for (const entry of [ data.btc.rest, data.btc.rpcUrl, data.cas.gateway, data.cas.rpcUrl ]) {
123
+ if (typeof entry.value === 'string') entry.value = scrubUrlUserinfo(entry.value);
124
+ }
125
+ }
126
+ print({ action: 'config-effective', data });
127
+ });
128
+
129
+ config
130
+ .command('path')
131
+ .description('Print the resolved config-file and keystore paths.')
72
132
  .action(() => {
73
- print({ action: 'config-list', data: readConfigFile(path()) ?? {} });
133
+ const data = {
134
+ config : globals().config ?? defaultConfigPath(),
135
+ keystore : globals().keystore ?? defaultKeystorePath(),
136
+ };
137
+ print({ action: 'config-path', data });
138
+ });
139
+
140
+ config
141
+ .command('doctor')
142
+ .description('Probe reachability of the resolved endpoints (read-only; touches the network).')
143
+ .option('-n, --network <network>', 'Network to resolve for (default: config default network)')
144
+ .action(async (opts: { network?: string }) => {
145
+ const network = resolveIntrospectionNetwork(opts.network, globals());
146
+ const report = await runDoctor(network, globals());
147
+ if (report.checks.some(check => !check.ok)) process.exitCode ??= 1;
148
+ print({ action: 'config-doctor', data: report });
74
149
  });
75
150
  }
76
151
 
77
- /** Parses a value as JSON when valid, otherwise treats it as a plain string. */
78
- function parseValue(value: string): unknown {
152
+ /**
153
+ * Resolves the network an introspection command (`effective`/`doctor`) operates
154
+ * on: an explicit `--network`, validated, else the config's default network.
155
+ */
156
+ function resolveIntrospectionNetwork(explicit: string | undefined, globals: GlobalOptions): NetworkOption {
157
+ if (explicit) {
158
+ if (!SUPPORTED_NETWORKS.includes(explicit as NetworkOption)) {
159
+ throw new CLIError(
160
+ `Invalid network "${explicit}". Must be one of ${SUPPORTED_NETWORKS.join(', ')}.`,
161
+ 'INVALID_ARGUMENT_ERROR',
162
+ { network: explicit },
163
+ );
164
+ }
165
+ return explicit as NetworkOption;
166
+ }
167
+ return resolveDefaultNetwork(globals);
168
+ }
169
+
170
+ /**
171
+ * Parses a `config set` value. Known scalar endpoint, credential, and defaults
172
+ * paths are stored as raw strings so a bare `8080` is not coerced to the number
173
+ * `8080` (which would flow into a host field as a non-string). Every other path
174
+ * is parsed as JSON when valid, else stored as a plain string, so structured
175
+ * values (objects, arrays, booleans, and genuinely numeric fields) still work.
176
+ */
177
+ function parseValue(dotted: string, value: string): unknown {
178
+ if (isStringScalarPath(dotted)) return value;
79
179
  try {
80
180
  return JSON.parse(value);
81
181
  } catch {
82
182
  return value;
83
183
  }
84
184
  }
185
+
186
+ /**
187
+ * Whether a dotted config path addresses a leaf that must be stored as a string
188
+ * (an endpoint URL, a credential, a network/profile/output name), so JSON
189
+ * coercion never turns it into a non-string.
190
+ */
191
+ function isStringScalarPath(dotted: string): boolean {
192
+ const segments = dotted.split('.');
193
+
194
+ if (segments.length === 2 && segments[0] === 'defaults') {
195
+ return [ 'profile', 'network', 'output' ].includes(segments[1]);
196
+ }
197
+
198
+ if (segments.length === 3 && segments[0] === 'profiles' && segments[2] === 'network') {
199
+ return true;
200
+ }
201
+
202
+ if (segments.length === 4 && segments[0] === 'profiles') {
203
+ const group = segments[2];
204
+ const leaf = segments[3];
205
+ if (group === 'btc') return [ 'rest', 'rpcUrl', 'rpcUser', 'rpcPass', 'changeAddress', 'wallet' ].includes(leaf);
206
+ if (group === 'cas') return [ 'gateway', 'rpcUrl' ].includes(leaf);
207
+ if (group === 'identity') return [ 'keystore', 'default' ].includes(leaf);
208
+ }
209
+
210
+ return false;
211
+ }
@@ -1,7 +1,7 @@
1
1
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
2
2
  import type { Command } from 'commander';
3
3
  import type { ApiFactory, ConnectionOverrides } from '../config.js';
4
- import { resolveDefaultNetwork } from '../config.js';
4
+ import { profileNetworkMismatch, resolveDefaultNetwork } from '../config.js';
5
5
  import { CLIError } from '../error.js';
6
6
  import { resolveKeyRef } from '../keystore/resolve-key-ref.js';
7
7
  import { formatResult } from '../output.js';
@@ -63,6 +63,18 @@ export function registerCreateCommand(
63
63
  const network = resolveNetwork(options.network, overrides);
64
64
  const signingKey = g.signingKey;
65
65
 
66
+ // Warn (never block) when the network being encoded disagrees with the
67
+ // active profile's declared network, so a `production` profile holding
68
+ // mainnet endpoints cannot silently mint a regtest DID.
69
+ const mismatch = profileNetworkMismatch(network, overrides);
70
+ if (mismatch && !g.quiet) {
71
+ process.stderr.write(
72
+ `Warning: creating a "${network}" identifier while the active profile `
73
+ + `"${mismatch.profile}" declares network "${mismatch.declared}". The `
74
+ + 'identifier\'s network and the profile\'s endpoints may not match.\n'
75
+ );
76
+ }
77
+
66
78
  /** Prints the result, plus a stderr provenance line in text mode. */
67
79
  const print = (result: CommandResult, note?: string): void => {
68
80
  console.log(formatResult(result, g));
@@ -1,6 +1,7 @@
1
+ import type { PublishToCasMode } from '@did-btcr2/api';
1
2
  import { KeyManagerSigner } from '@did-btcr2/key-manager';
2
3
  import type { Command } from 'commander';
3
- import { deriveNetwork, type ApiFactory } from '../config.js';
4
+ import { deriveNetwork, resolveBroadcastOptions, resolveSigningKeyRef, type ApiFactory } from '../config.js';
4
5
  import { CLIError } from '../error.js';
5
6
  import { resolveKeyRef } from '../keystore/resolve-key-ref.js';
6
7
  import { formatResult } from '../output.js';
@@ -36,11 +37,31 @@ export function registerDeactivateCommand(
36
37
  'Beacon ID as a JSON string',
37
38
  parseJsonArg('--beacon-id'),
38
39
  )
40
+ .option(
41
+ '--publish-to-cas <mode>',
42
+ 'Publish update artifacts to a writable CAS before broadcast: auto|always|never. '
43
+ + 'CAS publication is optional; the default distributes the returned artifacts via sidecar.',
44
+ parsePublishToCasMode,
45
+ 'never',
46
+ )
47
+ .option(
48
+ '--fee-rate <satsPerVByte>',
49
+ 'Fee rate in sats/vByte for the beacon transaction (default: 5). '
50
+ + 'Raise it under congestion so the transaction confirms.',
51
+ )
52
+ .option(
53
+ '--change-address <address>',
54
+ 'Send transaction change to this address instead of the beacon address, '
55
+ + 'so a DID\'s announcements are not linked on-chain (ADR 044).',
56
+ )
39
57
  .action(async (options: {
40
58
  sourceDocument : unknown;
41
59
  sourceVersionId : string;
42
60
  verificationMethodId : string;
43
61
  beaconId : unknown;
62
+ publishToCas : PublishToCasMode;
63
+ feeRate? : string;
64
+ changeAddress? : string;
44
65
  }) => {
45
66
  if (!/^\d+$/.test(options.sourceVersionId)) {
46
67
  throw new CLIError(
@@ -68,11 +89,21 @@ export function registerDeactivateCommand(
68
89
  // method has no separate deactivate path, so this routes through update.
69
90
  const network = deriveNetwork(did);
70
91
  const api = factory(network, globals());
71
- const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
92
+ const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
72
93
  const signer = new KeyManagerSigner(api.kms.kms, keyId);
73
- // The CLI's CAS is a read-only gateway (no writable CAS is configurable
74
- // yet), so CAS publication is disabled explicitly. The returned artifacts
75
- // (txid, announcement, proof) are printed for manual sidecar distribution.
94
+ // Resolve fee-rate/change-address through the flag -> env -> profile chain
95
+ // into beacon broadcast options. Undefined when neither is set, so the
96
+ // SDK defaults (5 sat/vB, change back to the beacon address) still apply.
97
+ const broadcastOptions = resolveBroadcastOptions(network, globals(), {
98
+ feeRate : options.feeRate,
99
+ changeAddress : options.changeAddress,
100
+ });
101
+ // CAS publication is optional and never required: every beacon update can
102
+ // be completed and shared via sidecar alone. It is opt-in and defaults to
103
+ // 'never'; pass --publish-to-cas auto|always to publish the signed update
104
+ // (and, for CAS beacons, the announcement) to a writable CAS configured
105
+ // via --cas-rpc-url. The returned artifacts (txid, announcement, proof)
106
+ // are always printed for sidecar distribution regardless.
76
107
  const data = await api.btcr2.update({
77
108
  sourceDocument : parsed.sourceDocument,
78
109
  patches : parsed.patches,
@@ -80,12 +111,28 @@ export function registerDeactivateCommand(
80
111
  verificationMethodId : parsed.verificationMethodId,
81
112
  beaconId : parsed.beaconId,
82
113
  signer,
83
- publishToCas : 'never',
114
+ publishToCas : options.publishToCas,
115
+ ...(broadcastOptions ? { broadcastOptions } : {}),
84
116
  });
85
117
  console.log(formatResult({ action: 'deactivate', data }, globals()));
86
118
  });
87
119
  }
88
120
 
121
+ /**
122
+ * Commander argParser for `--publish-to-cas`. Validates the value is one of the
123
+ * three {@link PublishToCasMode} policies, erroring at parse time otherwise.
124
+ */
125
+ function parsePublishToCasMode(value: string): PublishToCasMode {
126
+ if (value !== 'auto' && value !== 'always' && value !== 'never') {
127
+ throw new CLIError(
128
+ '--publish-to-cas must be one of "auto", "always", or "never".',
129
+ 'INVALID_ARGUMENT_ERROR',
130
+ { value },
131
+ );
132
+ }
133
+ return value;
134
+ }
135
+
89
136
  /**
90
137
  * Returns a commander argParser that validates JSON.
91
138
  * Errors at parse time with a clear flag reference.
@@ -1,7 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { defaultConfigPath, readConfigFile, writeConfigFile } from '../config.js';
3
3
  import { CLIError } from '../error.js';
4
- import { formatResult } from '../output.js';
4
+ import { formatResult, redactSecrets } from '../output.js';
5
5
  import type { CommandResult, GlobalOptions } from '../types.js';
6
6
 
7
7
  /** Registers the `profile` command group for managing configuration profiles. */
@@ -37,7 +37,8 @@ export function registerProfileCommand(program: Command, globals: () => GlobalOp
37
37
  profile
38
38
  .command('show [name]')
39
39
  .description('Show a profile (defaults to the active profile).')
40
- .action((name?: string) => {
40
+ .option('--show-secrets', 'Reveal secret values (RPC password, etc.) instead of redacting them.', false)
41
+ .action((name: string | undefined, opts: { showSecrets?: boolean }) => {
41
42
  const file = readConfigFile(path()) ?? {};
42
43
  const target = name ?? file.defaults?.profile;
43
44
  if (!target) {
@@ -47,7 +48,8 @@ export function registerProfileCommand(program: Command, globals: () => GlobalOp
47
48
  if (!data) {
48
49
  throw new CLIError(`Profile "${target}" not found.`, 'INVALID_ARGUMENT_ERROR', { profile: target });
49
50
  }
50
- print({ action: 'profile-show', data: { profile: target, ...data } });
51
+ const payload = { profile: target, ...data };
52
+ print({ action: 'profile-show', data: opts.showSecrets ? payload : redactSecrets(payload) });
51
53
  });
52
54
 
53
55
  profile
@@ -1,6 +1,7 @@
1
+ import type { PublishToCasMode } from '@did-btcr2/api';
1
2
  import { KeyManagerSigner } from '@did-btcr2/key-manager';
2
3
  import type { Command } from 'commander';
3
- import { deriveNetwork, type ApiFactory } from '../config.js';
4
+ import { deriveNetwork, resolveBroadcastOptions, resolveSigningKeyRef, type ApiFactory } from '../config.js';
4
5
  import { CLIError } from '../error.js';
5
6
  import { resolveKeyRef } from '../keystore/resolve-key-ref.js';
6
7
  import { formatResult } from '../output.js';
@@ -37,12 +38,32 @@ export function registerUpdateCommand(
37
38
  'Beacon ID as a JSON string',
38
39
  parseJsonArg('--beacon-id'),
39
40
  )
41
+ .option(
42
+ '--publish-to-cas <mode>',
43
+ 'Publish update artifacts to a writable CAS before broadcast: auto|always|never. '
44
+ + 'CAS publication is optional; the default distributes the returned artifacts via sidecar.',
45
+ parsePublishToCasMode,
46
+ 'never',
47
+ )
48
+ .option(
49
+ '--fee-rate <satsPerVByte>',
50
+ 'Fee rate in sats/vByte for the beacon transaction (default: 5). '
51
+ + 'Raise it under congestion so the transaction confirms.',
52
+ )
53
+ .option(
54
+ '--change-address <address>',
55
+ 'Send transaction change to this address instead of the beacon address, '
56
+ + 'so a DID\'s announcements are not linked on-chain (ADR 044).',
57
+ )
40
58
  .action(async (options: {
41
59
  sourceDocument : unknown;
42
60
  sourceVersionId : string;
43
61
  patches : unknown;
44
62
  verificationMethodId : string;
45
63
  beaconId : unknown;
64
+ publishToCas : PublishToCasMode;
65
+ feeRate? : string;
66
+ changeAddress? : string;
46
67
  }) => {
47
68
  if (!/^\d+$/.test(options.sourceVersionId)) {
48
69
  throw new CLIError(
@@ -68,11 +89,21 @@ export function registerUpdateCommand(
68
89
  }
69
90
  const network = deriveNetwork(did);
70
91
  const api = factory(network, globals());
71
- const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
92
+ const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
72
93
  const signer = new KeyManagerSigner(api.kms.kms, keyId);
73
- // The CLI's CAS is a read-only gateway (no writable CAS is configurable
74
- // yet), so CAS publication is disabled explicitly. The returned artifacts
75
- // (txid, announcement, proof) are printed for manual sidecar distribution.
94
+ // Resolve fee-rate/change-address through the flag -> env -> profile chain
95
+ // into beacon broadcast options. Undefined when neither is set, so the
96
+ // SDK defaults (5 sat/vB, change back to the beacon address) still apply.
97
+ const broadcastOptions = resolveBroadcastOptions(network, globals(), {
98
+ feeRate : options.feeRate,
99
+ changeAddress : options.changeAddress,
100
+ });
101
+ // CAS publication is optional and never required: every beacon update can
102
+ // be completed and shared via sidecar alone. It is opt-in and defaults to
103
+ // 'never'; pass --publish-to-cas auto|always to publish the signed update
104
+ // (and, for CAS beacons, the announcement) to a writable CAS configured
105
+ // via --cas-rpc-url. The returned artifacts (txid, announcement, proof)
106
+ // are always printed for sidecar distribution regardless.
76
107
  const data = await api.btcr2.update({
77
108
  sourceDocument : parsed.sourceDocument,
78
109
  patches : parsed.patches,
@@ -80,12 +111,28 @@ export function registerUpdateCommand(
80
111
  verificationMethodId : parsed.verificationMethodId,
81
112
  beaconId : parsed.beaconId,
82
113
  signer,
83
- publishToCas : 'never',
114
+ publishToCas : options.publishToCas,
115
+ ...(broadcastOptions ? { broadcastOptions } : {}),
84
116
  });
85
117
  console.log(formatResult({ action: 'update', data }, globals()));
86
118
  });
87
119
  }
88
120
 
121
+ /**
122
+ * Commander argParser for `--publish-to-cas`. Validates the value is one of the
123
+ * three {@link PublishToCasMode} policies, erroring at parse time otherwise.
124
+ */
125
+ function parsePublishToCasMode(value: string): PublishToCasMode {
126
+ if (value !== 'auto' && value !== 'always' && value !== 'never') {
127
+ throw new CLIError(
128
+ '--publish-to-cas must be one of "auto", "always", or "never".',
129
+ 'INVALID_ARGUMENT_ERROR',
130
+ { value },
131
+ );
132
+ }
133
+ return value;
134
+ }
135
+
89
136
  /**
90
137
  * Returns a commander argParser that validates JSON.
91
138
  * Errors at parse time with a clear flag reference.