@did-btcr2/cli 0.14.0 → 0.16.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 (83) hide show
  1. package/README.md +106 -17
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cjs/index.js +1160 -131
  4. package/dist/esm/src/cli.js +29 -5
  5. package/dist/esm/src/cli.js.map +1 -1
  6. package/dist/esm/src/commands/config.js +130 -18
  7. package/dist/esm/src/commands/config.js.map +1 -1
  8. package/dist/esm/src/commands/create.js +13 -1
  9. package/dist/esm/src/commands/create.js.map +1 -1
  10. package/dist/esm/src/commands/deactivate.js +16 -2
  11. package/dist/esm/src/commands/deactivate.js.map +1 -1
  12. package/dist/esm/src/commands/index.js +2 -0
  13. package/dist/esm/src/commands/index.js.map +1 -1
  14. package/dist/esm/src/commands/init.js +63 -0
  15. package/dist/esm/src/commands/init.js.map +1 -0
  16. package/dist/esm/src/commands/keystore.js +81 -0
  17. package/dist/esm/src/commands/keystore.js.map +1 -0
  18. package/dist/esm/src/commands/profile.js +6 -4
  19. package/dist/esm/src/commands/profile.js.map +1 -1
  20. package/dist/esm/src/commands/update.js +16 -2
  21. package/dist/esm/src/commands/update.js.map +1 -1
  22. package/dist/esm/src/config-schema.js +149 -0
  23. package/dist/esm/src/config-schema.js.map +1 -0
  24. package/dist/esm/src/config.js +579 -55
  25. package/dist/esm/src/config.js.map +1 -1
  26. package/dist/esm/src/keystore/file-key-store.js +340 -32
  27. package/dist/esm/src/keystore/file-key-store.js.map +1 -1
  28. package/dist/esm/src/keystore/passphrase.js +40 -10
  29. package/dist/esm/src/keystore/passphrase.js.map +1 -1
  30. package/dist/esm/src/keystore/paths.js +6 -17
  31. package/dist/esm/src/keystore/paths.js.map +1 -1
  32. package/dist/esm/src/output.js +49 -0
  33. package/dist/esm/src/output.js.map +1 -1
  34. package/dist/esm/src/paths.js +59 -0
  35. package/dist/esm/src/paths.js.map +1 -0
  36. package/dist/esm/src/types.js +11 -0
  37. package/dist/esm/src/types.js.map +1 -1
  38. package/dist/types/src/cli.d.ts.map +1 -1
  39. package/dist/types/src/commands/config.d.ts.map +1 -1
  40. package/dist/types/src/commands/create.d.ts.map +1 -1
  41. package/dist/types/src/commands/deactivate.d.ts.map +1 -1
  42. package/dist/types/src/commands/index.d.ts +2 -0
  43. package/dist/types/src/commands/index.d.ts.map +1 -1
  44. package/dist/types/src/commands/init.d.ts +13 -0
  45. package/dist/types/src/commands/init.d.ts.map +1 -0
  46. package/dist/types/src/commands/keystore.d.ts +10 -0
  47. package/dist/types/src/commands/keystore.d.ts.map +1 -0
  48. package/dist/types/src/commands/profile.d.ts.map +1 -1
  49. package/dist/types/src/commands/update.d.ts.map +1 -1
  50. package/dist/types/src/config-schema.d.ts +24 -0
  51. package/dist/types/src/config-schema.d.ts.map +1 -0
  52. package/dist/types/src/config.d.ts +252 -14
  53. package/dist/types/src/config.d.ts.map +1 -1
  54. package/dist/types/src/keystore/file-key-store.d.ts +86 -10
  55. package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
  56. package/dist/types/src/keystore/passphrase.d.ts +16 -1
  57. package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
  58. package/dist/types/src/keystore/paths.d.ts +6 -10
  59. package/dist/types/src/keystore/paths.d.ts.map +1 -1
  60. package/dist/types/src/output.d.ts +22 -0
  61. package/dist/types/src/output.d.ts.map +1 -1
  62. package/dist/types/src/paths.d.ts +54 -0
  63. package/dist/types/src/paths.d.ts.map +1 -0
  64. package/dist/types/src/types.d.ts +70 -0
  65. package/dist/types/src/types.d.ts.map +1 -1
  66. package/package.json +5 -5
  67. package/src/cli.ts +32 -4
  68. package/src/commands/config.ts +143 -18
  69. package/src/commands/create.ts +16 -1
  70. package/src/commands/deactivate.ts +24 -2
  71. package/src/commands/index.ts +2 -0
  72. package/src/commands/init.ts +74 -0
  73. package/src/commands/keystore.ts +98 -0
  74. package/src/commands/profile.ts +6 -4
  75. package/src/commands/update.ts +24 -2
  76. package/src/config-schema.ts +178 -0
  77. package/src/config.ts +752 -58
  78. package/src/keystore/file-key-store.ts +455 -43
  79. package/src/keystore/passphrase.ts +48 -8
  80. package/src/keystore/paths.ts +6 -18
  81. package/src/output.ts +53 -0
  82. package/src/paths.ts +79 -0
  83. package/src/types.ts +34 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * State-location overrides that influence where the CLI keeps its home
3
+ * directory and its two state files. A subset of the broader
4
+ * `ConnectionOverrides`, restated here so this module (the single source of
5
+ * truth for on-disk locations) has no runtime dependency on `config.ts`.
6
+ */
7
+ export interface PathOverrides {
8
+ /** Explicit home root from the `--home` flag. Wins over `$BTCR2_HOME`. */
9
+ home?: string;
10
+ /** Explicit config-file path from the `--config` flag. Overrides the home default. */
11
+ config?: string;
12
+ /** Explicit keystore path from the `--keystore` flag. Overrides the home default. */
13
+ keystore?: string;
14
+ }
15
+ /** Environment variable naming the CLI home directory (all state colocated). */
16
+ export declare const ENV_HOME = "BTCR2_HOME";
17
+ /** The config and keystore file names, kept side by side under the home root. */
18
+ export declare const CONFIG_FILENAME = "config.json";
19
+ export declare const KEYSTORE_FILENAME = "keystore.json";
20
+ /**
21
+ * Resolves the CLI home directory: the single root that holds `config.json` and
22
+ * `keystore.json` side by side (ADR 079). Resolution order, highest wins:
23
+ *
24
+ * 1. `--home <dir>` (the {@link PathOverrides.home} flag)
25
+ * 2. `$BTCR2_HOME`
26
+ * 3. the platform default (see {@link platformDefaultHome})
27
+ *
28
+ * A blank value at any layer defers to the next, mirroring the `blankToUndef`
29
+ * treatment every other precedence layer uses, so an exported-but-empty
30
+ * `BTCR2_HOME` does not resolve the home to a bare relative path.
31
+ */
32
+ export declare function resolveHome(overrides?: PathOverrides): string;
33
+ /**
34
+ * The default home when no `--home` / `$BTCR2_HOME` override is present, chosen
35
+ * per OS so the location is idiomatic while staying a single colocated dir:
36
+ *
37
+ * - Windows: `%LOCALAPPDATA%\btcr2` (fallback `%APPDATA%\btcr2`, then the user
38
+ * profile), the native place for per-user application state.
39
+ * - Linux / macOS: `~/.btcr2`, the short, teachable dot-directory in the same
40
+ * family as `~/.ssh`, `~/.aws`, and `~/.gnupg`.
41
+ */
42
+ export declare function platformDefaultHome(): string;
43
+ /**
44
+ * Default config-file path: `<home>/config.json`. The `--config` flag, when
45
+ * present, overrides it wholesale (it names a specific file, not a home).
46
+ */
47
+ export declare function defaultConfigPath(overrides?: PathOverrides): string;
48
+ /**
49
+ * Default keystore path: `<home>/keystore.json`. The `--keystore` flag and a
50
+ * profile's `identity.keystore` (resolved in `config.ts`) override it; this
51
+ * function is the final fallback in that chain.
52
+ */
53
+ export declare function defaultKeystorePath(overrides?: PathOverrides): string;
54
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/paths.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,IAAI,CAAC,EAAO,MAAM,CAAC;IACnB,sFAAsF;IACtF,MAAM,CAAC,EAAK,MAAM,CAAC;IACnB,qFAAqF;IACrF,QAAQ,CAAC,EAAG,MAAM,CAAC;CACpB;AAED,gFAAgF;AAChF,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,iFAAiF;AACjF,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,MAAM,CAI7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,MAAM,CAErE"}
@@ -2,9 +2,24 @@ 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';
9
+ /**
10
+ * How a keystore protects its secrets, as reported by `keystore status` and
11
+ * `btcr2 init`: `encrypted` (passphrase-sealed), `dev` (plaintext, testnet-only),
12
+ * or `absent` (no keystore file yet).
13
+ */
14
+ export type KeystoreProtectionLabel = 'encrypted' | 'dev' | 'absent';
7
15
  export declare const SUPPORTED_NETWORKS: NetworkOption[];
16
+ /**
17
+ * Normalizes a blank value (empty or whitespace-only) to `undefined`, so a
18
+ * blank at any precedence layer defers to the next layer instead of masking it.
19
+ * A non-blank value is returned unchanged. This mirrors the `|| undefined`
20
+ * treatment the environment layer already applies in `readEnvOverrides`.
21
+ */
22
+ export declare function blankToUndef(value: string | undefined): string | undefined;
8
23
  export interface ResolveCommandOptions {
9
24
  identifier: string;
10
25
  options?: ResolutionOptions;
@@ -79,6 +94,15 @@ export type CommandResult = {
79
94
  keyId: string;
80
95
  active: true;
81
96
  };
97
+ } | {
98
+ action: 'init';
99
+ data: {
100
+ home: string;
101
+ config: string;
102
+ keystore: string;
103
+ created: string[];
104
+ protection: KeystoreProtectionLabel;
105
+ };
82
106
  } | {
83
107
  action: 'config-init';
84
108
  data: {
@@ -100,6 +124,46 @@ export type CommandResult = {
100
124
  } | {
101
125
  action: 'config-list';
102
126
  data: unknown;
127
+ } | {
128
+ action: 'config-validate';
129
+ data: {
130
+ ok: boolean;
131
+ issues: ConfigIssue[];
132
+ };
133
+ } | {
134
+ action: 'config-effective';
135
+ data: EffectiveConfig;
136
+ } | {
137
+ action: 'config-path';
138
+ data: {
139
+ home: string;
140
+ config: string;
141
+ keystore: string;
142
+ };
143
+ } | {
144
+ action: 'config-doctor';
145
+ data: DoctorReport;
146
+ } | {
147
+ action: 'keystore-init';
148
+ data: {
149
+ path: string;
150
+ protection: 'encrypted' | 'dev';
151
+ };
152
+ } | {
153
+ action: 'keystore-status';
154
+ data: {
155
+ path: string;
156
+ protection: KeystoreProtectionLabel;
157
+ established: boolean;
158
+ keyCount: number;
159
+ active: string | undefined;
160
+ };
161
+ } | {
162
+ action: 'keystore-change-passphrase';
163
+ data: {
164
+ path: string;
165
+ rekeyed: number;
166
+ };
103
167
  } | {
104
168
  action: 'profile-add';
105
169
  data: {
@@ -123,6 +187,7 @@ export interface GlobalOptions {
123
187
  output: OutputFormat;
124
188
  verbose: boolean;
125
189
  quiet: boolean;
190
+ home?: string;
126
191
  config?: string;
127
192
  profile?: string;
128
193
  btcRest?: string;
@@ -131,6 +196,11 @@ export interface GlobalOptions {
131
196
  btcRpcPass?: string;
132
197
  casGateway?: string;
133
198
  casRpcUrl?: string;
199
+ btcTimeout?: string;
200
+ casTimeout?: string;
201
+ btcRestHeader?: string[];
202
+ btcRpcWallet?: string;
203
+ btcRpcHeader?: string[];
134
204
  keystore?: string;
135
205
  passphraseFile?: string;
136
206
  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.14.0",
3
+ "version": "0.16.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/common": "^9.1.0",
64
+ "@did-btcr2/api": "^0.16.1",
65
65
  "@did-btcr2/keypair": "^0.13.1",
66
- "@did-btcr2/method": "^0.54.0",
67
- "@did-btcr2/api": "^0.16.0",
68
- "@did-btcr2/key-manager": "^0.7.0"
66
+ "@did-btcr2/common": "^9.1.0",
67
+ "@did-btcr2/key-manager": "^0.7.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
@@ -5,12 +5,14 @@ import {
5
5
  registerConfigCommand,
6
6
  registerCreateCommand,
7
7
  registerDeactivateCommand,
8
+ registerInitCommand,
8
9
  registerKeyCommand,
10
+ registerKeystoreCommand,
9
11
  registerProfileCommand,
10
12
  registerResolveCommand,
11
13
  registerUpdateCommand,
12
14
  } from './commands/index.js';
13
- import { defaultApiFactory, keystoreApiFactory, type ApiFactory } from './config.js';
15
+ import { defaultApiFactory, keystoreApiFactory, resolveOutputFormat, type ApiFactory } from './config.js';
14
16
  import type { GlobalOptions } from './types.js';
15
17
  import { VERSION } from './version.js';
16
18
 
@@ -41,10 +43,11 @@ export class DidBtcr2Cli {
41
43
  this.program = new Command('btcr2')
42
44
  .version(`btcr2 ${VERSION}`, '-v, --version', 'Output the current version')
43
45
  .description('CLI tool for the did:btcr2 method')
44
- .option('-o, --output <format>', 'Output format <json|text>', 'text')
46
+ .option('-o, --output <format>', 'Output format <json|text> (default: config defaults.output, else text)')
45
47
  .option('--verbose', 'Verbose output', false)
46
48
  .option('--quiet', 'Suppress non-essential output', false)
47
- .option('-c, --config <path>', 'Path to config file (default: $XDG_CONFIG_HOME/btcr2/config.json)')
49
+ .option('--home <dir>', 'btcr2 home directory holding config.json + keystore.json (default: ~/.btcr2, %LOCALAPPDATA%\\btcr2 on Windows; overrides $BTCR2_HOME)')
50
+ .option('-c, --config <path>', 'Path to config file (default: <home>/config.json)')
48
51
  .option('--profile <name>', 'Config profile name (default: auto-detected from network)')
49
52
  .option('--btc-rest <url>', 'Override Bitcoin REST endpoint (Esplora API)')
50
53
  .option('--btc-rpc-url <url>', 'Override Bitcoin Core RPC endpoint')
@@ -52,17 +55,34 @@ export class DidBtcr2Cli {
52
55
  .option('--btc-rpc-pass <pass>', 'Bitcoin Core RPC password')
53
56
  .option('--cas-gateway <url>', 'IPFS HTTP gateway for CAS reads (read-only)')
54
57
  .option('--cas-rpc-url <url>', 'IPFS HTTP RPC endpoint for a writable CAS (reads + writes; enables --publish-to-cas)')
55
- .option('--keystore <path>', 'Path to the keystore file (default: $XDG_DATA_HOME/btcr2/keystore.json)')
58
+ .option('--btc-timeout <ms>', 'Bitcoin REST/RPC request timeout in milliseconds (default: unbounded)')
59
+ .option('--cas-timeout <ms>', 'CAS request timeout in milliseconds (default: 30000; 0 disables)')
60
+ .option('--btc-rest-header <header>', 'Extra Bitcoin REST header "Key: Value" (repeatable)', collectHeader, [])
61
+ .option('--btc-rpc-wallet <name>', 'Bitcoin Core wallet name for wallet-scoped RPCs')
62
+ .option('--btc-rpc-header <header>', 'Extra Bitcoin Core RPC header "Key: Value" (repeatable)', collectHeader, [])
63
+ .option('--keystore <path>', 'Path to the keystore file (default: <home>/keystore.json)')
56
64
  .option('--passphrase-file <path>', 'Read the keystore passphrase from a file (unattended use)')
57
65
  .option('--signing-key <ref>', 'Key for create/update/deactivate signing: a URN, fingerprint prefix, or name');
58
66
 
59
67
  const globals = (): GlobalOptions => this.program.opts() as GlobalOptions;
60
68
 
69
+ // Resolve the effective output format (flag -> BTCR2_OUTPUT -> config
70
+ // defaults.output -> 'text') before any command action runs, so a configured
71
+ // default is honored while an explicit flag still wins. The commander option
72
+ // carries no hard default (which would mask defaults.output); the resolved
73
+ // value is written back so every command reads it through globals().output.
74
+ this.program.hook('preAction', () => {
75
+ const opts = this.program.opts() as GlobalOptions;
76
+ opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
77
+ });
78
+
79
+ registerInitCommand(this.program, globals);
61
80
  registerCreateCommand(this.program, factory, keystoreFactory, globals);
62
81
  registerResolveCommand(this.program, factory, globals);
63
82
  registerUpdateCommand(this.program, keystoreFactory, globals);
64
83
  registerDeactivateCommand(this.program, keystoreFactory, globals);
65
84
  registerKeyCommand(this.program, keystoreFactory, globals);
85
+ registerKeystoreCommand(this.program, globals);
66
86
  registerConfigCommand(this.program, globals);
67
87
  registerProfileCommand(this.program, globals);
68
88
  registerCompletionCommand(this.program, globals);
@@ -84,6 +104,14 @@ export class DidBtcr2Cli {
84
104
  }
85
105
  }
86
106
 
107
+ /**
108
+ * Commander collector for a repeatable header option: accumulates each
109
+ * `Key: Value` occurrence into an array, preserving order.
110
+ */
111
+ function collectHeader(value: string, previous: string[]): string[] {
112
+ return [ ...previous, value ];
113
+ }
114
+
87
115
  /**
88
116
  * Normalizes argv to ensure it has at least two elements.
89
117
  * @param {string[]} argv - The original argv array.
@@ -1,25 +1,31 @@
1
1
  import type { Command } from 'commander';
2
2
  import { existsSync } from 'node:fs';
3
- import { dirname } from 'node:path';
4
3
  import {
5
4
  CONFIG_SCHEMA_VERSION,
6
5
  defaultConfigPath,
7
6
  getConfigPath,
7
+ parseConfigFileRaw,
8
8
  readConfigFile,
9
+ resolveDefaultNetwork,
10
+ resolveEffectiveConfig,
11
+ resolveKeystorePath,
12
+ runDoctor,
9
13
  setConfigPath,
10
14
  unsetConfigPath,
11
15
  writeConfigFile,
16
+ writeDefaultConfigFile,
12
17
  } from '../config.js';
18
+ import { findConfigIssues, validateConfigSet } from '../config-schema.js';
13
19
  import { CLIError } from '../error.js';
14
- import { ensureDir, writeFileAtomic } from '../keystore/atomic.js';
15
- import { formatResult } from '../output.js';
16
- import type { CommandResult, GlobalOptions } from '../types.js';
20
+ import { formatResult, REDACTED, redactSecrets, scrubUrlUserinfo } from '../output.js';
21
+ import { resolveHome } from '../paths.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. */
20
26
  export function registerConfigCommand(program: Command, globals: () => GlobalOptions): void {
21
27
  const config = program.command('config').description('Read and write CLI configuration.');
22
- const path = (): string => globals().config ?? defaultConfigPath();
28
+ const path = (): string => globals().config ?? defaultConfigPath(globals());
23
29
  const print = (result: CommandResult): void => console.log(formatResult(result, globals()));
24
30
 
25
31
  config
@@ -31,29 +37,33 @@ export function registerConfigCommand(program: Command, globals: () => GlobalOpt
31
37
  if (existsSync(p) && !options.force) {
32
38
  throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, 'INVALID_ARGUMENT_ERROR', { path: p });
33
39
  }
34
- const scaffold = {
35
- schemaVersion : CONFIG_SCHEMA_VERSION,
36
- defaults : { output: 'text' },
37
- profiles : Object.fromEntries(SUPPORTED_NETWORKS.map(n => [ n, {} ])),
38
- };
39
- ensureDir(dirname(p), 0o700);
40
- writeFileAtomic(p, `${JSON.stringify(scaffold, null, 2)}\n`, 0o600);
40
+ writeDefaultConfigFile(p);
41
41
  print({ action: 'config-init', data: { path: p } });
42
42
  });
43
43
 
44
44
  config
45
45
  .command('get [path]')
46
46
  .description('Print a value at a dotted path, or the whole config.')
47
- .action((dotted?: string) => {
47
+ .option('--show-secrets', 'Reveal secret values (RPC password, etc.) instead of redacting them.', false)
48
+ .action((dotted: string | undefined, opts: { showSecrets?: boolean }) => {
48
49
  const file = (readConfigFile(path()) ?? {}) as Record<string, unknown>;
49
- print({ action: 'config-get', data: (dotted ? getConfigPath(file, dotted) : file) ?? null });
50
+ const raw = (dotted ? getConfigPath(file, dotted) : file) ?? null;
51
+ const leaf = dotted ? dotted.split('.').pop() : undefined;
52
+ print({ action: 'config-get', data: opts.showSecrets ? raw : redactSecrets(raw, leaf) });
50
53
  });
51
54
 
52
55
  config
53
56
  .command('set <path> <value>')
54
57
  .description('Set a value at a dotted path. The value is parsed as JSON when valid, else stored as a string.')
55
58
  .action((dotted: string, value: string) => {
56
- writeConfigFile(path(), raw => setConfigPath(raw, dotted, parseValue(value)));
59
+ const parsed = parseValue(dotted, value);
60
+ // Reject an invalid enum value for a known key up-front; warn (but still
61
+ // write) an unknown path so forward-compatible and third-party keys work.
62
+ const { unknownPath } = validateConfigSet(dotted, parsed);
63
+ if (unknownPath && !globals().quiet) {
64
+ console.error(`Warning: "${dotted}" is not a known config path; writing it anyway.`);
65
+ }
66
+ writeConfigFile(path(), raw => setConfigPath(raw, dotted, parsed));
57
67
  print({ action: 'config-set', data: { path: dotted } });
58
68
  });
59
69
 
@@ -69,16 +79,131 @@ export function registerConfigCommand(program: Command, globals: () => GlobalOpt
69
79
  .command('list')
70
80
  .alias('ls')
71
81
  .description('Print the entire config file.')
82
+ .option('--show-secrets', 'Reveal secret values (RPC password, etc.) instead of redacting them.', false)
83
+ .action((opts: { showSecrets?: boolean }) => {
84
+ const file = readConfigFile(path()) ?? {};
85
+ print({ action: 'config-list', data: opts.showSecrets ? file : redactSecrets(file) });
86
+ });
87
+
88
+ config
89
+ .command('validate')
90
+ .description('Check the config for unknown keys, invalid enum values, and an unsupported schema version.')
72
91
  .action(() => {
73
- print({ action: 'config-list', data: readConfigFile(path()) ?? {} });
92
+ // Read raw (bypassing the schema-version ceiling that readConfigFile
93
+ // enforces) so a newer-than-supported version is reported as a finding
94
+ // rather than aborting the very command meant to diagnose it.
95
+ const file = parseConfigFileRaw(path()) ?? {};
96
+ const issues = findConfigIssues(file, CONFIG_SCHEMA_VERSION);
97
+ if (issues.length > 0) process.exitCode ??= 1;
98
+ print({ action: 'config-validate', data: { ok: issues.length === 0, issues } });
99
+ });
100
+
101
+ config
102
+ .command('effective')
103
+ .description('Print the resolved connection config with per-value provenance (flag|env|file|default).')
104
+ .option('-n, --network <network>', 'Network to resolve for (default: config default network)')
105
+ .option('--show-secrets', 'Reveal secret values (RPC password) instead of redacting them.', false)
106
+ .action((opts: { network?: string; showSecrets?: boolean }) => {
107
+ const network = resolveIntrospectionNetwork(opts.network, globals());
108
+ const data = resolveEffectiveConfig(network, globals());
109
+ // Redact the resolved RPC password and any password embedded in an endpoint
110
+ // URL by default; provenance still shows where each value came from.
111
+ // --show-secrets reveals them for deliberate debugging.
112
+ if (!opts.showSecrets) {
113
+ if (data.btc.rpcPass.value !== undefined) {
114
+ data.btc.rpcPass = { ...data.btc.rpcPass, value: REDACTED };
115
+ }
116
+ for (const entry of [ data.btc.rest, data.btc.rpcUrl, data.cas.gateway, data.cas.rpcUrl ]) {
117
+ if (typeof entry.value === 'string') entry.value = scrubUrlUserinfo(entry.value);
118
+ }
119
+ }
120
+ print({ action: 'config-effective', data });
121
+ });
122
+
123
+ config
124
+ .command('path')
125
+ .description('Print the resolved home directory, config-file, and keystore paths.')
126
+ .action(() => {
127
+ const g = globals();
128
+ const data = {
129
+ home : resolveHome(g),
130
+ config : g.config ?? defaultConfigPath(g),
131
+ // Diagnostic command: report the path even when the config is malformed
132
+ // (this is a command you run to find and fix a broken config).
133
+ keystore : resolveKeystorePath(g, { lenient: true }),
134
+ };
135
+ print({ action: 'config-path', data });
136
+ });
137
+
138
+ config
139
+ .command('doctor')
140
+ .description('Probe reachability of the resolved endpoints (read-only; touches the network).')
141
+ .option('-n, --network <network>', 'Network to resolve for (default: config default network)')
142
+ .action(async (opts: { network?: string }) => {
143
+ const network = resolveIntrospectionNetwork(opts.network, globals());
144
+ const report = await runDoctor(network, globals());
145
+ if (report.checks.some(check => !check.ok)) process.exitCode ??= 1;
146
+ print({ action: 'config-doctor', data: report });
74
147
  });
75
148
  }
76
149
 
77
- /** Parses a value as JSON when valid, otherwise treats it as a plain string. */
78
- function parseValue(value: string): unknown {
150
+ /**
151
+ * Resolves the network an introspection command (`effective`/`doctor`) operates
152
+ * on: an explicit `--network`, validated, else the config's default network.
153
+ */
154
+ function resolveIntrospectionNetwork(explicit: string | undefined, globals: GlobalOptions): NetworkOption {
155
+ if (explicit) {
156
+ if (!SUPPORTED_NETWORKS.includes(explicit as NetworkOption)) {
157
+ throw new CLIError(
158
+ `Invalid network "${explicit}". Must be one of ${SUPPORTED_NETWORKS.join(', ')}.`,
159
+ 'INVALID_ARGUMENT_ERROR',
160
+ { network: explicit },
161
+ );
162
+ }
163
+ return explicit as NetworkOption;
164
+ }
165
+ return resolveDefaultNetwork(globals);
166
+ }
167
+
168
+ /**
169
+ * Parses a `config set` value. Known scalar endpoint, credential, and defaults
170
+ * paths are stored as raw strings so a bare `8080` is not coerced to the number
171
+ * `8080` (which would flow into a host field as a non-string). Every other path
172
+ * is parsed as JSON when valid, else stored as a plain string, so structured
173
+ * values (objects, arrays, booleans, and genuinely numeric fields) still work.
174
+ */
175
+ function parseValue(dotted: string, value: string): unknown {
176
+ if (isStringScalarPath(dotted)) return value;
79
177
  try {
80
178
  return JSON.parse(value);
81
179
  } catch {
82
180
  return value;
83
181
  }
84
182
  }
183
+
184
+ /**
185
+ * Whether a dotted config path addresses a leaf that must be stored as a string
186
+ * (an endpoint URL, a credential, a network/profile/output name), so JSON
187
+ * coercion never turns it into a non-string.
188
+ */
189
+ function isStringScalarPath(dotted: string): boolean {
190
+ const segments = dotted.split('.');
191
+
192
+ if (segments.length === 2 && segments[0] === 'defaults') {
193
+ return [ 'profile', 'network', 'output' ].includes(segments[1]);
194
+ }
195
+
196
+ if (segments.length === 3 && segments[0] === 'profiles' && segments[2] === 'network') {
197
+ return true;
198
+ }
199
+
200
+ if (segments.length === 4 && segments[0] === 'profiles') {
201
+ const group = segments[2];
202
+ const leaf = segments[3];
203
+ if (group === 'btc') return [ 'rest', 'rpcUrl', 'rpcUser', 'rpcPass', 'changeAddress', 'wallet' ].includes(leaf);
204
+ if (group === 'cas') return [ 'gateway', 'rpcUrl' ].includes(leaf);
205
+ if (group === 'identity') return [ 'keystore', 'default' ].includes(leaf);
206
+ }
207
+
208
+ return false;
209
+ }
@@ -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 { assertKeystoreAllowedForNetwork, 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));
@@ -120,6 +132,8 @@ export function registerCreateCommand(
120
132
  }
121
133
 
122
134
  // Generate: mint a fresh key, persist it, and set it active (passphrase prompt).
135
+ // Refuse to seal a fresh mainnet key into an unencrypted dev keystore (ADR 080).
136
+ assertKeystoreAllowedForNetwork(network, overrides);
123
137
  const api = keystoreFactory(undefined, overrides);
124
138
  const { did, keyId } = api.generateDid({ network, setActive: true });
125
139
  const publicKey = bytesToHex(api.kms.getPublicKey(keyId));
@@ -133,6 +147,7 @@ export function registerCreateCommand(
133
147
  /** Builds the keystore- and config-resolution overrides from the global flags. */
134
148
  function overridesFromGlobals(g: GlobalOptions): ConnectionOverrides {
135
149
  return {
150
+ home : g.home,
136
151
  config : g.config,
137
152
  profile : g.profile,
138
153
  keystore : g.keystore,
@@ -1,7 +1,7 @@
1
1
  import type { PublishToCasMode } from '@did-btcr2/api';
2
2
  import { KeyManagerSigner } from '@did-btcr2/key-manager';
3
3
  import type { Command } from 'commander';
4
- import { deriveNetwork, type ApiFactory } from '../config.js';
4
+ import { assertKeystoreAllowedForNetwork, deriveNetwork, resolveBroadcastOptions, resolveSigningKeyRef, type ApiFactory } 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';
@@ -44,12 +44,24 @@ export function registerDeactivateCommand(
44
44
  parsePublishToCasMode,
45
45
  'never',
46
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
+ )
47
57
  .action(async (options: {
48
58
  sourceDocument : unknown;
49
59
  sourceVersionId : string;
50
60
  verificationMethodId : string;
51
61
  beaconId : unknown;
52
62
  publishToCas : PublishToCasMode;
63
+ feeRate? : string;
64
+ changeAddress? : string;
53
65
  }) => {
54
66
  if (!/^\d+$/.test(options.sourceVersionId)) {
55
67
  throw new CLIError(
@@ -76,9 +88,18 @@ export function registerDeactivateCommand(
76
88
  // Deactivation is an update that applies the deactivation patch. The core
77
89
  // method has no separate deactivate path, so this routes through update.
78
90
  const network = deriveNetwork(did);
91
+ // Refuse to sign a mainnet deactivation with an unencrypted dev keystore (ADR 080).
92
+ assertKeystoreAllowedForNetwork(network, globals());
79
93
  const api = factory(network, globals());
80
- const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
94
+ const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
81
95
  const signer = new KeyManagerSigner(api.kms.kms, keyId);
96
+ // Resolve fee-rate/change-address through the flag -> env -> profile chain
97
+ // into beacon broadcast options. Undefined when neither is set, so the
98
+ // SDK defaults (5 sat/vB, change back to the beacon address) still apply.
99
+ const broadcastOptions = resolveBroadcastOptions(network, globals(), {
100
+ feeRate : options.feeRate,
101
+ changeAddress : options.changeAddress,
102
+ });
82
103
  // CAS publication is optional and never required: every beacon update can
83
104
  // be completed and shared via sidecar alone. It is opt-in and defaults to
84
105
  // 'never'; pass --publish-to-cas auto|always to publish the signed update
@@ -93,6 +114,7 @@ export function registerDeactivateCommand(
93
114
  beaconId : parsed.beaconId,
94
115
  signer,
95
116
  publishToCas : options.publishToCas,
117
+ ...(broadcastOptions ? { broadcastOptions } : {}),
96
118
  });
97
119
  console.log(formatResult({ action: 'deactivate', data }, globals()));
98
120
  });
@@ -1,8 +1,10 @@
1
+ export { registerInitCommand } from './init.js';
1
2
  export { registerCreateCommand } from './create.js';
2
3
  export { registerResolveCommand } from './resolve.js';
3
4
  export { registerUpdateCommand } from './update.js';
4
5
  export { registerDeactivateCommand } from './deactivate.js';
5
6
  export { registerKeyCommand } from './key.js';
7
+ export { registerKeystoreCommand } from './keystore.js';
6
8
  export { registerConfigCommand } from './config.js';
7
9
  export { registerProfileCommand } from './profile.js';
8
10
  export { registerCompletionCommand } from './completion.js';