@did-btcr2/cli 0.5.3 → 0.8.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 (42) hide show
  1. package/README.md +137 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cjs/index.js +163 -30
  4. package/dist/esm/src/cli.js +22 -11
  5. package/dist/esm/src/cli.js.map +1 -1
  6. package/dist/esm/src/commands/create.js +2 -1
  7. package/dist/esm/src/commands/create.js.map +1 -1
  8. package/dist/esm/src/commands/deactivate.js +14 -5
  9. package/dist/esm/src/commands/deactivate.js.map +1 -1
  10. package/dist/esm/src/commands/resolve.js +4 -3
  11. package/dist/esm/src/commands/resolve.js.map +1 -1
  12. package/dist/esm/src/commands/update.js +15 -5
  13. package/dist/esm/src/commands/update.js.map +1 -1
  14. package/dist/esm/src/config.js +142 -0
  15. package/dist/esm/src/config.js.map +1 -0
  16. package/dist/esm/src/index.js +1 -0
  17. package/dist/esm/src/index.js.map +1 -1
  18. package/dist/types/src/cli.d.ts +10 -5
  19. package/dist/types/src/cli.d.ts.map +1 -1
  20. package/dist/types/src/commands/create.d.ts +2 -2
  21. package/dist/types/src/commands/create.d.ts.map +1 -1
  22. package/dist/types/src/commands/deactivate.d.ts +2 -2
  23. package/dist/types/src/commands/deactivate.d.ts.map +1 -1
  24. package/dist/types/src/commands/resolve.d.ts +2 -2
  25. package/dist/types/src/commands/resolve.d.ts.map +1 -1
  26. package/dist/types/src/commands/update.d.ts +2 -2
  27. package/dist/types/src/commands/update.d.ts.map +1 -1
  28. package/dist/types/src/config.d.ts +131 -0
  29. package/dist/types/src/config.d.ts.map +1 -0
  30. package/dist/types/src/index.d.ts +1 -0
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/types.d.ts +7 -0
  33. package/dist/types/src/types.d.ts.map +1 -1
  34. package/package.json +5 -5
  35. package/src/cli.ts +22 -12
  36. package/src/commands/create.ts +3 -2
  37. package/src/commands/deactivate.ts +22 -6
  38. package/src/commands/resolve.ts +4 -4
  39. package/src/commands/update.ts +23 -6
  40. package/src/config.ts +226 -0
  41. package/src/index.ts +1 -0
  42. package/src/types.ts +10 -3
@@ -1,12 +1,11 @@
1
- import type { DidBtcr2Api } from '@did-btcr2/api';
2
1
  import type { Command } from 'commander';
2
+ import { deriveNetwork, type ApiFactory } from '../config.js';
3
3
  import { CLIError } from '../error.js';
4
- import { formatResult } from '../output.js';
5
4
  import type { GlobalOptions, UpdateCommandOptions } from '../types.js';
6
5
 
7
6
  export function registerUpdateCommand(
8
7
  program : Command,
9
- api : DidBtcr2Api,
8
+ factory : ApiFactory,
10
9
  globals : () => GlobalOptions,
11
10
  ): void {
12
11
  program
@@ -49,9 +48,27 @@ export function registerUpdateCommand(
49
48
  verificationMethodId : options.verificationMethodId,
50
49
  beaconId : options.beaconId as UpdateCommandOptions['beaconId'],
51
50
  };
52
- const data = await api.btcr2.update(parsed);
53
- const result = { action: 'update' as const, data };
54
- console.log(formatResult(result, globals()));
51
+ const did = parsed.sourceDocument?.id;
52
+ if (!did) {
53
+ throw new CLIError(
54
+ 'Source document must contain an "id" field.',
55
+ 'INVALID_ARGUMENT_ERROR',
56
+ options
57
+ );
58
+ }
59
+ // The CLI does not yet have a way to load signing material (keystore,
60
+ // KMS config, hardware wallet, etc.), so signed updates from the CLI are
61
+ // not yet wired up. Drive the SDK directly with a `Signer` for now.
62
+ // Variables above are kept so command parsing + validation still works.
63
+ void deriveNetwork(did);
64
+ void factory;
65
+ void globals;
66
+ void parsed;
67
+ throw new CLIError(
68
+ 'CLI signing is not yet implemented. Use @did-btcr2/api with a Signer directly.',
69
+ 'NOT_IMPLEMENTED_ERROR',
70
+ { command: 'update' }
71
+ );
55
72
  });
56
73
  }
57
74
 
package/src/config.ts ADDED
@@ -0,0 +1,226 @@
1
+ import { createApi, Identifier, type BitcoinApiConfig, type DidBtcr2Api } from '@did-btcr2/api';
2
+ import { readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { CLIError } from './error.js';
6
+ import { SUPPORTED_NETWORKS, type NetworkOption } from './types.js';
7
+
8
+ /**
9
+ * Endpoint overrides provided via CLI flags, env vars, or config file.
10
+ * These override the per-network defaults from
11
+ * `DEFAULT_BITCOIN_NETWORK_CONFIG`.
12
+ *
13
+ * `config` and `profile` control config-file resolution and are only
14
+ * meaningful when passed through the full merge chain.
15
+ */
16
+ export type ConnectionOverrides = {
17
+ btcRest? : string;
18
+ btcRpcUrl? : string;
19
+ btcRpcUser?: string;
20
+ btcRpcPass?: string;
21
+ casGateway?: string;
22
+ config? : string;
23
+ profile? : string;
24
+ };
25
+
26
+ /**
27
+ * On-disk config file schema.
28
+ *
29
+ * @example
30
+ * ```json
31
+ * {
32
+ * "profiles": {
33
+ * "regtest": {
34
+ * "btc": {
35
+ * "rest": "http://localhost:3000",
36
+ * "rpcUrl": "http://localhost:18443",
37
+ * "rpcUser": "polaruser",
38
+ * "rpcPass": "polarpass"
39
+ * }
40
+ * },
41
+ * "bitcoin": {
42
+ * "btc": { "rest": "https://my-mempool/api" },
43
+ * "cas": { "gateway": "https://ipfs.io" }
44
+ * }
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ export type ConfigFile = {
50
+ profiles?: Record<string, {
51
+ btc?: {
52
+ rest? : string;
53
+ rpcUrl? : string;
54
+ rpcUser? : string;
55
+ rpcPass? : string;
56
+ };
57
+ cas?: {
58
+ gateway?: string;
59
+ };
60
+ }>;
61
+ };
62
+
63
+ /**
64
+ * Factory function that creates a configured {@link DidBtcr2Api} instance.
65
+ *
66
+ * When `network` is provided, the returned API is wired to that network's
67
+ * default Bitcoin endpoints (mempool.space for public networks, localhost
68
+ * Polar for regtest). Optional `overrides` let callers replace individual
69
+ * endpoints on top of the defaults. When `network` is omitted, no Bitcoin
70
+ * or CAS is configured — suitable for offline operations like `create`.
71
+ */
72
+ export type ApiFactory = (network?: NetworkOption, overrides?: ConnectionOverrides) => DidBtcr2Api;
73
+
74
+ /**
75
+ * Environment variable names consulted by {@link defaultApiFactory}.
76
+ *
77
+ * | Variable | Equivalent flag |
78
+ * |-----------------------|--------------------|
79
+ * | `BTCR2_BTC_REST` | `--btc-rest` |
80
+ * | `BTCR2_BTC_RPC_URL` | `--btc-rpc-url` |
81
+ * | `BTCR2_BTC_RPC_USER` | `--btc-rpc-user` |
82
+ * | `BTCR2_BTC_RPC_PASS` | `--btc-rpc-pass` |
83
+ * | `BTCR2_CAS_GATEWAY` | `--cas-gateway` |
84
+ */
85
+ export const ENV_VARS = {
86
+ BTC_REST : 'BTCR2_BTC_REST',
87
+ BTC_RPC_URL : 'BTCR2_BTC_RPC_URL',
88
+ BTC_RPC_USER : 'BTCR2_BTC_RPC_USER',
89
+ BTC_RPC_PASS : 'BTCR2_BTC_RPC_PASS',
90
+ CAS_GATEWAY : 'BTCR2_CAS_GATEWAY',
91
+ } as const;
92
+
93
+ /**
94
+ * Reads {@link ConnectionOverrides} from environment variables.
95
+ * Only defined (non-empty) values are included.
96
+ */
97
+ export function readEnvOverrides(): ConnectionOverrides {
98
+ const env = (key: string): string | undefined => process.env[key] || undefined;
99
+ return {
100
+ btcRest : env(ENV_VARS.BTC_REST),
101
+ btcRpcUrl : env(ENV_VARS.BTC_RPC_URL),
102
+ btcRpcUser : env(ENV_VARS.BTC_RPC_USER),
103
+ btcRpcPass : env(ENV_VARS.BTC_RPC_PASS),
104
+ casGateway : env(ENV_VARS.CAS_GATEWAY),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Default config file path following the XDG Base Directory Specification.
110
+ *
111
+ * Resolution order:
112
+ * 1. `$XDG_CONFIG_HOME/btcr2/config.json`
113
+ * 2. `%APPDATA%/btcr2/config.json` (Windows)
114
+ * 3. `~/.config/btcr2/config.json` (fallback)
115
+ */
116
+ export function defaultConfigPath(): string {
117
+ const base = process.env.XDG_CONFIG_HOME
118
+ ?? process.env.APPDATA
119
+ ?? join(homedir(), '.config');
120
+ return join(base, 'btcr2', 'config.json');
121
+ }
122
+
123
+ /**
124
+ * Reads and parses a config file. Returns `undefined` if the file does
125
+ * not exist or cannot be parsed.
126
+ */
127
+ export function readConfigFile(path: string): ConfigFile | undefined {
128
+ try {
129
+ const content = readFileSync(path, 'utf-8');
130
+ return JSON.parse(content) as ConfigFile;
131
+ } catch {
132
+ return undefined;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Extracts {@link ConnectionOverrides} from a named profile in a
138
+ * {@link ConfigFile}. Returns an empty object if the profile does not exist.
139
+ */
140
+ export function profileToOverrides(
141
+ config : ConfigFile,
142
+ profileName : string,
143
+ ): ConnectionOverrides {
144
+ const profile = config.profiles?.[profileName];
145
+ if (!profile) return {};
146
+ return {
147
+ btcRest : profile.btc?.rest,
148
+ btcRpcUrl : profile.btc?.rpcUrl,
149
+ btcRpcUser : profile.btc?.rpcUser,
150
+ btcRpcPass : profile.btc?.rpcPass,
151
+ casGateway : profile.cas?.gateway,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Default {@link ApiFactory} backed by network defaults from
157
+ * `@did-btcr2/bitcoin` (mempool.space for public networks, localhost for
158
+ * regtest).
159
+ *
160
+ * Override precedence (highest wins):
161
+ * CLI flags → env vars → config file profile → network defaults.
162
+ *
163
+ * When no `--profile` is given, the network name is used as the profile
164
+ * key (e.g. a regtest DID auto-selects the `"regtest"` profile).
165
+ */
166
+ export function defaultApiFactory(network?: NetworkOption, overrides?: ConnectionOverrides): DidBtcr2Api {
167
+ if (!network) return createApi();
168
+
169
+ // Layer 1: Config file profile (lowest precedence of the three override layers)
170
+ const configPath = overrides?.config ?? defaultConfigPath();
171
+ const profileName = overrides?.profile ?? network;
172
+ const file = readConfigFile(configPath);
173
+ const fileOverrides = file ? profileToOverrides(file, profileName) : {};
174
+
175
+ // Layer 2: Environment variables
176
+ const env = readEnvOverrides();
177
+
178
+ // Merge: CLI flags → env vars → config file → (network defaults handled by BitcoinConnection)
179
+ const merged: ConnectionOverrides = {
180
+ btcRest : overrides?.btcRest ?? env.btcRest ?? fileOverrides.btcRest,
181
+ btcRpcUrl : overrides?.btcRpcUrl ?? env.btcRpcUrl ?? fileOverrides.btcRpcUrl,
182
+ btcRpcUser : overrides?.btcRpcUser ?? env.btcRpcUser ?? fileOverrides.btcRpcUser,
183
+ btcRpcPass : overrides?.btcRpcPass ?? env.btcRpcPass ?? fileOverrides.btcRpcPass,
184
+ casGateway : overrides?.casGateway ?? env.casGateway ?? fileOverrides.casGateway,
185
+ };
186
+
187
+ const btc: BitcoinApiConfig = { network };
188
+
189
+ if (merged.btcRest) {
190
+ btc.rest = { host: merged.btcRest };
191
+ }
192
+
193
+ if (merged.btcRpcUrl) {
194
+ btc.rpc = {
195
+ host : merged.btcRpcUrl,
196
+ username : merged.btcRpcUser,
197
+ password : merged.btcRpcPass,
198
+ };
199
+ }
200
+
201
+ const cas = merged.casGateway ? { gateway: merged.casGateway } : undefined;
202
+
203
+ return createApi({ btc, ...(cas && { cas }) });
204
+ }
205
+
206
+ /**
207
+ * Extracts and validates the Bitcoin network from a DID string.
208
+ *
209
+ * Decodes the DID via {@link Identifier.decode}, then checks that the
210
+ * embedded network is one of the supported values.
211
+ *
212
+ * @param did A `did:btcr2:...` identifier string.
213
+ * @returns The validated {@link NetworkOption}.
214
+ * @throws {CLIError} If the network is unsupported.
215
+ */
216
+ export function deriveNetwork(did: string): NetworkOption {
217
+ const { network } = Identifier.decode(did);
218
+ if (!SUPPORTED_NETWORKS.includes(network as NetworkOption)) {
219
+ throw new CLIError(
220
+ `Unsupported network "${network}" in DID.`,
221
+ 'INVALID_ARGUMENT_ERROR',
222
+ { did, network }
223
+ );
224
+ }
225
+ return network as NetworkOption;
226
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './cli.js';
2
2
  export * from './commands/index.js';
3
+ export * from './config.js';
3
4
  export * from './error.js';
4
5
  export * from './output.js';
5
6
  export * from './types.js';
package/src/types.ts CHANGED
@@ -36,7 +36,14 @@ export type CommandResult =
36
36
  | { action: 'deactivate'; data: SignedBTCR2Update };
37
37
 
38
38
  export interface GlobalOptions {
39
- output : OutputFormat;
40
- verbose : boolean;
41
- quiet : boolean;
39
+ output : OutputFormat;
40
+ verbose : boolean;
41
+ quiet : boolean;
42
+ config? : string;
43
+ profile? : string;
44
+ btcRest? : string;
45
+ btcRpcUrl? : string;
46
+ btcRpcUser?: string;
47
+ btcRpcPass?: string;
48
+ casGateway?: string;
42
49
  }