@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
@@ -1,5 +1,6 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { join } from 'node:path';
3
+ import { blankToUndef } from '../types.js';
3
4
 
4
5
  /**
5
6
  * Default keystore file path, following the XDG Base Directory Specification's
@@ -13,8 +14,8 @@ import { join } from 'node:path';
13
14
  * 3. `~/.local/share/btcr2/keystore.json` (fallback)
14
15
  */
15
16
  export function defaultKeystorePath(): string {
16
- const base = process.env.XDG_DATA_HOME
17
- ?? process.env.LOCALAPPDATA
17
+ const base = blankToUndef(process.env.XDG_DATA_HOME)
18
+ ?? blankToUndef(process.env.LOCALAPPDATA)
18
19
  ?? join(homedir(), '.local', 'share');
19
20
  return join(base, 'btcr2', 'keystore.json');
20
21
  }
package/src/output.ts CHANGED
@@ -1,5 +1,58 @@
1
1
  import type { CommandResult, GlobalOptions } from './types.js';
2
2
 
3
+ /** Placeholder printed in place of a redacted secret value. */
4
+ export const REDACTED = '********';
5
+
6
+ /**
7
+ * Key names whose (scalar) values are treated as secrets and redacted in printed
8
+ * output. Covers RPC passwords plus header credentials (`Authorization`, API
9
+ * keys, bearer tokens) that a profile can now carry in `btc.headers`/`rpcHeaders`.
10
+ */
11
+ const SECRET_KEY = /(pass|secret|token|auth|api[-_]?key|credential|bearer)/i;
12
+
13
+ /** Whether a config key name looks like a secret (so its scalar value should be redacted). */
14
+ export function isSecretKey(key: string): boolean {
15
+ return SECRET_KEY.test(key);
16
+ }
17
+
18
+ /**
19
+ * Masks the password in a `scheme://user:pass@host` URL, leaving the rest of the
20
+ * value verbatim. A credential embedded in an endpoint URL (e.g. `rpcUrl`) is not
21
+ * caught by key-name matching, so it is scrubbed here regardless of the key.
22
+ */
23
+ export function scrubUrlUserinfo(value: string | undefined): string | undefined {
24
+ if (value === undefined) return undefined;
25
+ return value.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/@:]+):[^/@]*@/i, `$1:${REDACTED}@`);
26
+ }
27
+
28
+ /**
29
+ * Returns a deep copy of `value` with secret scalar values replaced by
30
+ * {@link REDACTED}, so routine introspection (`config get`/`list`/`effective`)
31
+ * does not print RPC passwords or other secrets into terminal scrollback and CI
32
+ * logs. A secret-named key redacts only a scalar value (an object under such a
33
+ * key, e.g. a profile named `access-token`, is still traversed, not wholesale
34
+ * masked). Passwords embedded in URL values are scrubbed regardless of key name.
35
+ * When `keyName` is a secret name, a directly-passed scalar value is redacted
36
+ * (used when a single leaf value is printed). Display-only: the stored config and
37
+ * the value used to connect are untouched.
38
+ */
39
+ export function redactSecrets(value: unknown, keyName?: string): unknown {
40
+ if (Array.isArray(value)) {
41
+ return value.map(item => redactSecrets(item));
42
+ }
43
+ if (value !== null && typeof value === 'object') {
44
+ const out: Record<string, unknown> = {};
45
+ for (const [ key, val ] of Object.entries(value as Record<string, unknown>)) {
46
+ out[key] = redactSecrets(val, key);
47
+ }
48
+ return out;
49
+ }
50
+ if (keyName !== undefined && isSecretKey(keyName)) {
51
+ return value === undefined ? undefined : REDACTED;
52
+ }
53
+ return typeof value === 'string' ? scrubUrlUserinfo(value) : value;
54
+ }
55
+
3
56
  /**
4
57
  * Formats a CommandResult for console output.
5
58
  * In 'json' mode, the full result is serialized.
package/src/types.ts CHANGED
@@ -2,6 +2,8 @@ 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
 
6
8
  export type NetworkOption = 'bitcoin' | 'testnet3' | 'testnet4' | 'signet' | 'mutinynet' | 'regtest';
7
9
  export type OutputFormat = 'json' | 'text';
@@ -10,6 +12,17 @@ export const SUPPORTED_NETWORKS: NetworkOption[] = [
10
12
  'bitcoin', 'testnet3', 'testnet4', 'signet', 'mutinynet', 'regtest'
11
13
  ];
12
14
 
15
+ /**
16
+ * Normalizes a blank value (empty or whitespace-only) to `undefined`, so a
17
+ * blank at any precedence layer defers to the next layer instead of masking it.
18
+ * A non-blank value is returned unchanged. This mirrors the `|| undefined`
19
+ * treatment the environment layer already applies in `readEnvOverrides`.
20
+ */
21
+ export function blankToUndef(value: string | undefined): string | undefined {
22
+ if (value === undefined) return undefined;
23
+ return value.trim() === '' ? undefined : value;
24
+ }
25
+
13
26
  export interface ResolveCommandOptions {
14
27
  identifier : string;
15
28
  options? : ResolutionOptions;
@@ -40,6 +53,10 @@ export type CommandResult =
40
53
  | { action: 'config-set'; data: { path: string } }
41
54
  | { action: 'config-unset'; data: { path: string } }
42
55
  | { action: 'config-list'; data: unknown }
56
+ | { action: 'config-validate'; data: { ok: boolean; issues: ConfigIssue[] } }
57
+ | { action: 'config-effective'; data: EffectiveConfig }
58
+ | { action: 'config-path'; data: { config: string; keystore: string } }
59
+ | { action: 'config-doctor'; data: DoctorReport }
43
60
  | { action: 'profile-add'; data: { profile: string } }
44
61
  | { action: 'profile-use'; data: { profile: string } }
45
62
  | { action: 'profile-show'; data: unknown }
@@ -56,6 +73,12 @@ export interface GlobalOptions {
56
73
  btcRpcUser? : string;
57
74
  btcRpcPass? : string;
58
75
  casGateway? : string;
76
+ casRpcUrl? : string;
77
+ btcTimeout? : string;
78
+ casTimeout? : string;
79
+ btcRestHeader? : string[];
80
+ btcRpcWallet? : string;
81
+ btcRpcHeader? : string[];
59
82
  keystore? : string;
60
83
  passphraseFile?: string;
61
84
  signingKey? : string;