@did-btcr2/cli 0.15.0 → 0.17.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/README.md +47 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +836 -122
- package/dist/esm/src/cli.js +7 -4
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/config.js +11 -15
- package/dist/esm/src/commands/config.js.map +1 -1
- package/dist/esm/src/commands/create.js +4 -1
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/deactivate.js +3 -1
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/index.js +2 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/init.js +69 -0
- package/dist/esm/src/commands/init.js.map +1 -0
- package/dist/esm/src/commands/keystore.js +180 -0
- package/dist/esm/src/commands/keystore.js.map +1 -0
- package/dist/esm/src/commands/profile.js +1 -1
- package/dist/esm/src/commands/profile.js.map +1 -1
- package/dist/esm/src/commands/update.js +3 -1
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config.js +109 -31
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/keystore/file-key-store.js +388 -32
- package/dist/esm/src/keystore/file-key-store.js.map +1 -1
- package/dist/esm/src/keystore/passphrase.js +53 -10
- package/dist/esm/src/keystore/passphrase.js.map +1 -1
- package/dist/esm/src/keystore/paths.js +6 -18
- package/dist/esm/src/keystore/paths.js.map +1 -1
- package/dist/esm/src/keystore/session.js +250 -0
- package/dist/esm/src/keystore/session.js.map +1 -0
- package/dist/esm/src/paths.js +71 -0
- package/dist/esm/src/paths.js.map +1 -0
- package/dist/esm/src/types.js.map +1 -1
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/config.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts.map +1 -1
- package/dist/types/src/commands/deactivate.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +2 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/init.d.ts +13 -0
- package/dist/types/src/commands/init.d.ts.map +1 -0
- package/dist/types/src/commands/keystore.d.ts +11 -0
- package/dist/types/src/commands/keystore.d.ts.map +1 -0
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config.d.ts +38 -14
- package/dist/types/src/config.d.ts.map +1 -1
- package/dist/types/src/keystore/file-key-store.d.ts +102 -10
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
- package/dist/types/src/keystore/passphrase.d.ts +26 -1
- package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
- package/dist/types/src/keystore/paths.d.ts +6 -10
- package/dist/types/src/keystore/paths.d.ts.map +1 -1
- package/dist/types/src/keystore/session.d.ts +105 -0
- package/dist/types/src/keystore/session.d.ts.map +1 -0
- package/dist/types/src/paths.d.ts +64 -0
- package/dist/types/src/paths.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +53 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +8 -3
- package/src/commands/config.ts +12 -14
- package/src/commands/create.ts +4 -1
- package/src/commands/deactivate.ts +3 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/init.ts +80 -0
- package/src/commands/keystore.ts +232 -0
- package/src/commands/profile.ts +1 -1
- package/src/commands/update.ts +3 -1
- package/src/config.ts +118 -35
- package/src/keystore/file-key-store.ts +498 -43
- package/src/keystore/passphrase.ts +71 -8
- package/src/keystore/paths.ts +6 -19
- package/src/keystore/session.ts +303 -0
- package/src/paths.ts +92 -0
- package/src/types.ts +16 -1
|
@@ -7,16 +7,32 @@ export const ENV_KEYSTORE_PASSPHRASE = 'BTCR2_KEYSTORE_PASSPHRASE';
|
|
|
7
7
|
* (which would leak into process listings and shell history). Resolution order:
|
|
8
8
|
* the {@link ENV_KEYSTORE_PASSPHRASE} environment variable, a passphrase file,
|
|
9
9
|
* then a non-echoing terminal prompt. Throws if none is available and standard
|
|
10
|
-
* input is not a terminal.
|
|
10
|
+
* input is not a terminal. When `forcePrompt` is set, the env var and file are
|
|
11
|
+
* skipped and a terminal entry is required.
|
|
11
12
|
*/
|
|
12
13
|
export function acquirePassphrase(options = {}) {
|
|
13
14
|
// All sources are normalized identically (at most one trailing newline
|
|
14
15
|
// removed) so the KDF input is source-independent.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if (!options.forcePrompt) {
|
|
17
|
+
const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
|
|
18
|
+
if (fromEnv)
|
|
19
|
+
return assertNonEmpty(fromEnv.replace(/\r?\n$/, ''));
|
|
20
|
+
if (options.passphraseFile) {
|
|
21
|
+
return assertNonEmpty(readFileSync(options.passphraseFile, 'utf-8').replace(/\r?\n$/, ''));
|
|
22
|
+
}
|
|
23
|
+
// A cached session (ADR 081) sits below the env var and file but above the
|
|
24
|
+
// interactive prompt, so it is consulted before the "no TTY" failure: a
|
|
25
|
+
// scripted or piped follow-on command consumes the session instead of
|
|
26
|
+
// hard-failing. Establishment never reaches here (its caller omits it).
|
|
27
|
+
//
|
|
28
|
+
// The session already holds the exact, keystore-verified passphrase (encoded
|
|
29
|
+
// and decoded byte-for-byte), not a raw source needing newline normalization.
|
|
30
|
+
// Return it verbatim: re-stripping a trailing newline here would corrupt the
|
|
31
|
+
// KDF input for a passphrase that legitimately ends in one, even though unlock
|
|
32
|
+
// itself succeeded. assertNonEmpty is a defensive guard only.
|
|
33
|
+
const fromSession = options.beforePrompt?.();
|
|
34
|
+
if (fromSession)
|
|
35
|
+
return assertNonEmpty(fromSession);
|
|
20
36
|
}
|
|
21
37
|
if (!process.stdin.isTTY) {
|
|
22
38
|
throw new KeyStoreError(`No passphrase available. Set ${ENV_KEYSTORE_PASSPHRASE}, pass --passphrase-file, or run in a terminal.`, 'PASSPHRASE_REQUIRED_ERROR');
|
|
@@ -37,9 +53,32 @@ function assertNonEmpty(passphrase) {
|
|
|
37
53
|
}
|
|
38
54
|
return passphrase;
|
|
39
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* A 4-byte shared buffer used only as an {@link Atomics.wait} target. It lets
|
|
58
|
+
* {@link promptHidden} block briefly on an empty non-blocking TTY instead of
|
|
59
|
+
* busy-spinning. It is never written to, so the wait always times out.
|
|
60
|
+
*/
|
|
61
|
+
const IDLE_WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
62
|
+
/**
|
|
63
|
+
* Milliseconds to block on each empty read. Imperceptible to a typist yet long
|
|
64
|
+
* enough that an open prompt sits idle rather than pegging a CPU core.
|
|
65
|
+
*/
|
|
66
|
+
const IDLE_POLL_MS = 20;
|
|
67
|
+
/**
|
|
68
|
+
* Removes the last whole UTF-8 character from an accumulating byte array in
|
|
69
|
+
* place: pops any trailing continuation bytes (0b10xxxxxx) then the leading
|
|
70
|
+
* byte. Exported for testing; a backspace mid-entry must not strand a fragment
|
|
71
|
+
* that later decodes to U+FFFD.
|
|
72
|
+
*/
|
|
73
|
+
export function dropLastUtf8Char(bytes) {
|
|
74
|
+
while (bytes.length > 0 && (bytes[bytes.length - 1] & 0xc0) === 0x80)
|
|
75
|
+
bytes.pop();
|
|
76
|
+
bytes.pop();
|
|
77
|
+
}
|
|
40
78
|
/**
|
|
41
79
|
* Reads a line from the terminal synchronously without echoing keystrokes.
|
|
42
|
-
* Bytes are accumulated and decoded as UTF-8 so multibyte passphrases survive
|
|
80
|
+
* Bytes are accumulated and decoded as UTF-8 so multibyte passphrases survive,
|
|
81
|
+
* including a backspace that spans a whole multibyte character.
|
|
43
82
|
* This path runs only when standard input is a terminal.
|
|
44
83
|
*/
|
|
45
84
|
function promptHidden(label) {
|
|
@@ -57,8 +96,12 @@ function promptHidden(label) {
|
|
|
57
96
|
}
|
|
58
97
|
catch (error) {
|
|
59
98
|
const code = error.code;
|
|
60
|
-
if (code === 'EAGAIN')
|
|
61
|
-
|
|
99
|
+
if (code === 'EAGAIN') {
|
|
100
|
+
// No byte ready yet on a non-blocking TTY. Block briefly instead of
|
|
101
|
+
// spinning so an open prompt does not peg a CPU core while it waits.
|
|
102
|
+
Atomics.wait(IDLE_WAIT, 0, 0, IDLE_POLL_MS);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
62
105
|
if (code === 'EOF')
|
|
63
106
|
break;
|
|
64
107
|
throw error;
|
|
@@ -72,7 +115,7 @@ function promptHidden(label) {
|
|
|
72
115
|
throw new KeyStoreError('Passphrase entry aborted.', 'PASSPHRASE_REQUIRED_ERROR');
|
|
73
116
|
}
|
|
74
117
|
if (ch === 0x7f || ch === 0x08) { // DEL or backspace
|
|
75
|
-
bytes
|
|
118
|
+
dropLastUtf8Char(bytes);
|
|
76
119
|
continue;
|
|
77
120
|
}
|
|
78
121
|
bytes.push(ch);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../../../src/keystore/passphrase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,qFAAqF;AACrF,MAAM,CAAC,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../../../src/keystore/passphrase.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { blankToUndef } from '../types.js';
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Resolution order:
|
|
11
|
-
* 1. `$XDG_DATA_HOME/btcr2/keystore.json`
|
|
12
|
-
* 2. `%LOCALAPPDATA%/btcr2/keystore.json` (Windows)
|
|
13
|
-
* 3. `~/.local/share/btcr2/keystore.json` (fallback)
|
|
2
|
+
* The default keystore path now lives alongside the config file under a single
|
|
3
|
+
* CLI home root (`<home>/keystore.json`, ADR 079). The implementation lives in
|
|
4
|
+
* `../paths.ts` (the single source of truth for on-disk state locations); it is
|
|
5
|
+
* re-exported here so existing `./paths.js` importers in the keystore layer keep
|
|
6
|
+
* their import surface.
|
|
14
7
|
*/
|
|
15
|
-
export
|
|
16
|
-
const base = blankToUndef(process.env.XDG_DATA_HOME)
|
|
17
|
-
?? blankToUndef(process.env.LOCALAPPDATA)
|
|
18
|
-
?? join(homedir(), '.local', 'share');
|
|
19
|
-
return join(base, 'btcr2', 'keystore.json');
|
|
20
|
-
}
|
|
8
|
+
export { defaultKeystorePath } from '../paths.js';
|
|
21
9
|
//# sourceMappingURL=paths.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/keystore/paths.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/keystore/paths.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { closeSync, constants, existsSync, fstatSync, openSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { utf8ToBytes } from '@noble/hashes/utils.js';
|
|
4
|
+
import { base64urlnopad } from '@scure/base';
|
|
5
|
+
import { ensureDir, writeFileAtomic } from './atomic.js';
|
|
6
|
+
/**
|
|
7
|
+
* The session unlock agent (ADR 081). `keystore unlock` caches the verified
|
|
8
|
+
* keystore passphrase in a single `<home>/session.json`, and subsequent commands
|
|
9
|
+
* read it in place of a prompt until it expires or `keystore lock` revokes it.
|
|
10
|
+
*
|
|
11
|
+
* This is an on-disk v1 design. The cached passphrase is base64url-*encoded*, not
|
|
12
|
+
* encrypted: its only protection at rest is the file's `0600` mode. That is the
|
|
13
|
+
* deliberate, documented cost of a portable, minimal-diff convenience; a future
|
|
14
|
+
* in-memory agent (v2) that never persists the secret is the real fix. Every read
|
|
15
|
+
* here is defensive and never throws, so a bad or hostile session degrades to a
|
|
16
|
+
* passphrase prompt rather than a crash.
|
|
17
|
+
*/
|
|
18
|
+
/** Current session-file format version. */
|
|
19
|
+
export const SESSION_VERSION = 1;
|
|
20
|
+
/** Default session lifetime: one hour. */
|
|
21
|
+
export const DEFAULT_SESSION_TTL_MS = 60 * 60 * 1000;
|
|
22
|
+
/** Hard cap on a cached-passphrase lifetime: 24 hours. A longer TTL is refused. */
|
|
23
|
+
export const MAX_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
24
|
+
/** Environment variable supplying a default TTL below the `--ttl` flag. */
|
|
25
|
+
export const ENV_KEYSTORE_TTL = 'BTCR2_KEYSTORE_TTL';
|
|
26
|
+
/**
|
|
27
|
+
* Writes the session file atomically at `0600` (temp sibling + rename), returning
|
|
28
|
+
* the written record so the caller can report expiry without re-reading. The
|
|
29
|
+
* caller is responsible for verifying the passphrase first; this only persists it.
|
|
30
|
+
*/
|
|
31
|
+
export function writeSession(sessionPath, input) {
|
|
32
|
+
const createdAt = Date.now();
|
|
33
|
+
const session = {
|
|
34
|
+
v: SESSION_VERSION,
|
|
35
|
+
keystore: resolve(input.keystorePath),
|
|
36
|
+
verifierId: input.verifierId,
|
|
37
|
+
passphrase: base64urlnopad.encode(utf8ToBytes(input.passphrase)),
|
|
38
|
+
allowMainnet: input.allowMainnet ?? false,
|
|
39
|
+
createdAt,
|
|
40
|
+
expiresAt: createdAt + input.ttlMs,
|
|
41
|
+
ttlSeconds: Math.round(input.ttlMs / 1000),
|
|
42
|
+
};
|
|
43
|
+
ensureDir(dirname(sessionPath), 0o700);
|
|
44
|
+
writeFileAtomic(sessionPath, `${JSON.stringify(session, null, 2)}\n`, 0o600);
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Deletes the session file and any crash-orphaned `writeFileAtomic` temp sibling
|
|
49
|
+
* (each of which would hold a plaintext passphrase). Idempotent; needs no
|
|
50
|
+
* passphrase. Returns whether a session file was present. Unlink is best-effort:
|
|
51
|
+
* it removes the name, it does not securely erase the bytes (see ADR 081).
|
|
52
|
+
*/
|
|
53
|
+
export function clearSession(sessionPath) {
|
|
54
|
+
let existed = false;
|
|
55
|
+
try {
|
|
56
|
+
existed = existsSync(sessionPath);
|
|
57
|
+
if (existed)
|
|
58
|
+
rmSync(sessionPath, { force: true });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Best effort: a session we cannot remove is reported as not-cleared below.
|
|
62
|
+
existed = false;
|
|
63
|
+
}
|
|
64
|
+
sweepSessionTemps(sessionPath);
|
|
65
|
+
return existed;
|
|
66
|
+
}
|
|
67
|
+
/** Removes `.session.json.<pid>.<n>.tmp` leftovers from an interrupted atomic write. */
|
|
68
|
+
function sweepSessionTemps(sessionPath) {
|
|
69
|
+
const dir = dirname(sessionPath);
|
|
70
|
+
const prefix = `.${basename(sessionPath)}.`;
|
|
71
|
+
try {
|
|
72
|
+
for (const name of readdirSync(dir)) {
|
|
73
|
+
if (name.startsWith(prefix) && name.endsWith('.tmp')) {
|
|
74
|
+
try {
|
|
75
|
+
rmSync(join(dir, name), { force: true });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// A temp file we cannot remove is left for the next sweep.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Directory unreadable or absent: nothing to sweep.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the cached passphrase for `keystorePath` when a live, matching session
|
|
89
|
+
* exists, else `undefined`. Never throws. A session that is expired, stale (the
|
|
90
|
+
* keystore passphrase rotated), future-dated, or malformed is pruned on read; a
|
|
91
|
+
* live session bound to a *different* keystore is left in place (it prompts for
|
|
92
|
+
* the current keystore instead).
|
|
93
|
+
*
|
|
94
|
+
* `isMainnetOperation` gates the one case the network is known at consumption: a
|
|
95
|
+
* live session that was not unlocked with `--allow-mainnet` is withheld from a
|
|
96
|
+
* `bitcoin` operation (returning `undefined` so the caller falls through to a
|
|
97
|
+
* per-use prompt) but *not* pruned, since it remains valid for the non-mainnet
|
|
98
|
+
* operations the operator unlocked for (ADR 081).
|
|
99
|
+
*/
|
|
100
|
+
export function readLiveSessionPassphrase(sessionPath, keystorePath, currentVerifierId, isMainnetOperation = false) {
|
|
101
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, Date.now());
|
|
102
|
+
if (verdict.status === 'live') {
|
|
103
|
+
// Withhold a session lacking mainnet allowance from a mainnet operation, so
|
|
104
|
+
// signing a `bitcoin` DID still authenticates per use. Leave it in place: it
|
|
105
|
+
// is a valid session, just not for this operation (like a foreign keystore).
|
|
106
|
+
if (isMainnetOperation && !verdict.session.allowMainnet)
|
|
107
|
+
return undefined;
|
|
108
|
+
try {
|
|
109
|
+
return Buffer.from(base64urlnopad.decode(verdict.session.passphrase)).toString('utf-8');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
clearSession(sessionPath);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Prune a session that is dead for everyone or dead for this keystore. A
|
|
117
|
+
// 'foreign' (live, different keystore) or 'none' session is left untouched.
|
|
118
|
+
if (verdict.status === 'expired' || verdict.status === 'stale'
|
|
119
|
+
|| verdict.status === 'future' || verdict.status === 'malformed') {
|
|
120
|
+
clearSession(sessionPath);
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Reports whether a live session exists for `keystorePath` and its remaining
|
|
126
|
+
* lifetime, without decrypting, prompting, throwing, or emitting the passphrase.
|
|
127
|
+
* An expired, foreign, stale, or malformed session reports inactive. Read-only:
|
|
128
|
+
* unlike {@link readLiveSessionPassphrase}, it does not prune.
|
|
129
|
+
*/
|
|
130
|
+
export function readSessionStatus(sessionPath, keystorePath, currentVerifierId) {
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, now);
|
|
133
|
+
if (verdict.status !== 'live')
|
|
134
|
+
return { active: false };
|
|
135
|
+
return {
|
|
136
|
+
active: true,
|
|
137
|
+
expiresAt: verdict.session.expiresAt,
|
|
138
|
+
secondsRemaining: Math.max(0, Math.round((verdict.session.expiresAt - now) / 1000)),
|
|
139
|
+
allowMainnet: verdict.session.allowMainnet,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Parses a TTL string into milliseconds: a bare integer is seconds; an `s`, `m`,
|
|
144
|
+
* or `h` suffix scales it. Returns `undefined` for any malformed input. The
|
|
145
|
+
* caller applies the default, the 24h cap, and the `<= 0` rejection.
|
|
146
|
+
*/
|
|
147
|
+
export function parseTtlToMs(raw) {
|
|
148
|
+
const match = /^(\d+)([smh]?)$/.exec(raw.trim());
|
|
149
|
+
if (!match)
|
|
150
|
+
return undefined;
|
|
151
|
+
const n = Number(match[1]);
|
|
152
|
+
if (!Number.isFinite(n))
|
|
153
|
+
return undefined;
|
|
154
|
+
const unitMs = match[2] === 'h' ? 3_600_000 : match[2] === 'm' ? 60_000 : 1_000;
|
|
155
|
+
return n * unitMs;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Classifies the session file: read it securely, then check version, shape,
|
|
159
|
+
* clock, keystore binding, and verifier fingerprint in that order. Ordering
|
|
160
|
+
* `expired` before `foreign` means an expired session is pruned regardless of
|
|
161
|
+
* which keystore it was for. Never throws.
|
|
162
|
+
*/
|
|
163
|
+
function evaluateSession(sessionPath, keystorePath, currentVerifierId, now) {
|
|
164
|
+
const session = secureReadSession(sessionPath);
|
|
165
|
+
if (!session)
|
|
166
|
+
return { status: 'none' };
|
|
167
|
+
if (session.v !== SESSION_VERSION)
|
|
168
|
+
return { status: 'malformed' };
|
|
169
|
+
if (typeof session.passphrase !== 'string' || typeof session.keystore !== 'string'
|
|
170
|
+
|| typeof session.verifierId !== 'string' || typeof session.allowMainnet !== 'boolean'
|
|
171
|
+
|| typeof session.createdAt !== 'number' || typeof session.expiresAt !== 'number') {
|
|
172
|
+
return { status: 'malformed' };
|
|
173
|
+
}
|
|
174
|
+
if (session.createdAt > now)
|
|
175
|
+
return { status: 'future' };
|
|
176
|
+
if (now >= session.expiresAt)
|
|
177
|
+
return { status: 'expired' };
|
|
178
|
+
if (resolve(session.keystore) !== resolve(keystorePath))
|
|
179
|
+
return { status: 'foreign' };
|
|
180
|
+
if (currentVerifierId === undefined || session.verifierId !== currentVerifierId)
|
|
181
|
+
return { status: 'stale' };
|
|
182
|
+
return { status: 'live', session };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Reads and parses the session file with a plaintext secret in mind. On POSIX,
|
|
186
|
+
* opens with `O_NOFOLLOW` (refusing a symlink) and refuses a non-regular file, a
|
|
187
|
+
* file not owned by this user, or one accessible by group or other, best-effort
|
|
188
|
+
* deleting a rejected file and reading only from the opened descriptor (no TOCTOU
|
|
189
|
+
* re-open). On Windows those POSIX guards are skipped (they would throw), so the
|
|
190
|
+
* file is read normally under the same directory ACL the keystore trusts, keeping
|
|
191
|
+
* the session usable rather than silently ignored. Returns `undefined` on any
|
|
192
|
+
* failure; never throws.
|
|
193
|
+
*/
|
|
194
|
+
function secureReadSession(sessionPath) {
|
|
195
|
+
let raw;
|
|
196
|
+
if (process.platform === 'win32') {
|
|
197
|
+
try {
|
|
198
|
+
raw = readFileSync(sessionPath, 'utf-8');
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
let fd;
|
|
206
|
+
try {
|
|
207
|
+
fd = openSync(sessionPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// ENOENT (no session), ELOOP (symlink refused by O_NOFOLLOW), or any other
|
|
211
|
+
// open failure: no usable session.
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const st = fstatSync(fd);
|
|
216
|
+
const myUid = typeof process.getuid === 'function' ? process.getuid() : undefined;
|
|
217
|
+
if (!st.isFile() || (myUid !== undefined && st.uid !== myUid) || (st.mode & 0o077) !== 0) {
|
|
218
|
+
// Not a regular file we own with 0600 perms: it was not written by this
|
|
219
|
+
// process. Best-effort remove it (it may hold a plaintext passphrase) and
|
|
220
|
+
// fall back to a prompt.
|
|
221
|
+
try {
|
|
222
|
+
rmSync(sessionPath, { force: true });
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Cannot remove a file we do not own; ignoring it is enough.
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
raw = readFileSync(fd, 'utf-8');
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
try {
|
|
236
|
+
closeSync(fd);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// Descriptor already gone; nothing to close.
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(raw);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../../src/keystore/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { blankToUndef } from './types.js';
|
|
4
|
+
/** Environment variable naming the CLI home directory (all state colocated). */
|
|
5
|
+
export const ENV_HOME = 'BTCR2_HOME';
|
|
6
|
+
/** The config and keystore file names, kept side by side under the home root. */
|
|
7
|
+
export const CONFIG_FILENAME = 'config.json';
|
|
8
|
+
export const KEYSTORE_FILENAME = 'keystore.json';
|
|
9
|
+
/** The session file name, holding the unlock agent's cached passphrase (ADR 081). */
|
|
10
|
+
export const SESSION_FILENAME = 'session.json';
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the CLI home directory: the single root that holds `config.json` and
|
|
13
|
+
* `keystore.json` side by side (ADR 079). Resolution order, highest wins:
|
|
14
|
+
*
|
|
15
|
+
* 1. `--home <dir>` (the {@link PathOverrides.home} flag)
|
|
16
|
+
* 2. `$BTCR2_HOME`
|
|
17
|
+
* 3. the platform default (see {@link platformDefaultHome})
|
|
18
|
+
*
|
|
19
|
+
* A blank value at any layer defers to the next, mirroring the `blankToUndef`
|
|
20
|
+
* treatment every other precedence layer uses, so an exported-but-empty
|
|
21
|
+
* `BTCR2_HOME` does not resolve the home to a bare relative path.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveHome(overrides) {
|
|
24
|
+
return blankToUndef(overrides?.home)
|
|
25
|
+
?? blankToUndef(process.env[ENV_HOME])
|
|
26
|
+
?? platformDefaultHome();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The default home when no `--home` / `$BTCR2_HOME` override is present, chosen
|
|
30
|
+
* per OS so the location is idiomatic while staying a single colocated dir:
|
|
31
|
+
*
|
|
32
|
+
* - Windows: `%LOCALAPPDATA%\btcr2` (fallback `%APPDATA%\btcr2`, then the user
|
|
33
|
+
* profile), the native place for per-user application state.
|
|
34
|
+
* - Linux / macOS: `~/.btcr2`, the short, teachable dot-directory in the same
|
|
35
|
+
* family as `~/.ssh`, `~/.aws`, and `~/.gnupg`.
|
|
36
|
+
*/
|
|
37
|
+
export function platformDefaultHome() {
|
|
38
|
+
if (process.platform === 'win32') {
|
|
39
|
+
const base = blankToUndef(process.env.LOCALAPPDATA)
|
|
40
|
+
?? blankToUndef(process.env.APPDATA)
|
|
41
|
+
?? homedir();
|
|
42
|
+
return join(base, 'btcr2');
|
|
43
|
+
}
|
|
44
|
+
return join(homedir(), '.btcr2');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Default config-file path: `<home>/config.json`. The `--config` flag, when
|
|
48
|
+
* present, overrides it wholesale (it names a specific file, not a home).
|
|
49
|
+
*/
|
|
50
|
+
export function defaultConfigPath(overrides) {
|
|
51
|
+
return join(resolveHome(overrides), CONFIG_FILENAME);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Default keystore path: `<home>/keystore.json`. The `--keystore` flag and a
|
|
55
|
+
* profile's `identity.keystore` (resolved in `config.ts`) override it; this
|
|
56
|
+
* function is the final fallback in that chain.
|
|
57
|
+
*/
|
|
58
|
+
export function defaultKeystorePath(overrides) {
|
|
59
|
+
return join(resolveHome(overrides), KEYSTORE_FILENAME);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Session file path: `<home>/session.json`, where the unlock agent caches the
|
|
63
|
+
* keystore passphrase (ADR 081). Deliberately derived from the home root alone,
|
|
64
|
+
* never from `--config` / `--keystore` or the config file, so `keystore lock`
|
|
65
|
+
* can revoke a session even under a malformed config, and so the read and write
|
|
66
|
+
* paths always agree on one location per home.
|
|
67
|
+
*/
|
|
68
|
+
export function defaultSessionPath(overrides) {
|
|
69
|
+
return join(resolveHome(overrides), SESSION_FILENAME);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAiB1C,gFAAgF;AAChF,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AAErC,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AACjD,qFAAqF;AACrF,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,SAAyB;IACnD,OAAO,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC;WAC/B,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;WACnC,mBAAmB,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB;IACjC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;eAC9C,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;eACjC,OAAO,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAyB;IACzD,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAyB;IAC3D,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAyB;IAC1D,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS;CACpE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB;IACpD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAapD,OAAO,EAA8D,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAI1G;;GAEG;AACH,qBAAa,WAAW;IACtB,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC;;;;;;;;;;;;;OAaG;gBAED,OAAO,GAAE,UAA8B,EACvC,eAAe,GAAE,UAA+B;IAkDlD;;;;OAIG;IACU,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CASjD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,OAAO,KAAK,EAAiB,aAAa,EAAiB,MAAM,aAAa,CAAC;AAG/E,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,OAAO,KAAK,EAAiB,aAAa,EAAiB,MAAM,aAAa,CAAC;AAG/E,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,CA0H1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/commands/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,cAAc,CAAC;AAKpE,OAAO,KAAK,EAAiB,aAAa,EAAiB,MAAM,aAAa,CAAC;AAS/E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAW,OAAO,EACzB,OAAO,EAAW,UAAU,EAC5B,eAAe,EAAG,UAAU,EAC5B,OAAO,EAAW,MAAM,aAAa,GACpC,IAAI,
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/commands/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,cAAc,CAAC;AAKpE,OAAO,KAAK,EAAiB,aAAa,EAAiB,MAAM,aAAa,CAAC;AAS/E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAW,OAAO,EACzB,OAAO,EAAW,UAAU,EAC5B,eAAe,EAAG,UAAU,EAC5B,OAAO,EAAW,MAAM,aAAa,GACpC,IAAI,CAyGN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deactivate.d.ts","sourceRoot":"","sources":["../../../../src/commands/deactivate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"deactivate.d.ts","sourceRoot":"","sources":["../../../../src/commands/deactivate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAiG,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAI9I,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAKvE,wBAAgB,yBAAyB,CACvC,OAAO,EAAG,OAAO,EACjB,OAAO,EAAG,UAAU,EACpB,OAAO,EAAG,MAAM,aAAa,GAC5B,IAAI,CAwGN"}
|
|
@@ -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';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { GlobalOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registers the top-level `btcr2 init`: the one-command entry point that creates
|
|
5
|
+
* the btcr2 home (ADR 079), writes a default config if none exists, and
|
|
6
|
+
* establishes the keystore if none exists (encrypted with a confirmed passphrase
|
|
7
|
+
* by default, or `--dev` for an unencrypted testnet keystore). Idempotent:
|
|
8
|
+
* existing files are left untouched unless `--force` is given. Establishing the
|
|
9
|
+
* passphrase here, up front and confirmed, is what keeps the first `key generate`
|
|
10
|
+
* off the accidental-first-seal path (ADR 080).
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerInitCommand(program: Command, globals: () => GlobalOptions): void;
|
|
13
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,aAAa,CAAC;AAEhE;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,CA2DxF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { type GlobalOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registers the `keystore` command group: establish, inspect, and re-key the
|
|
5
|
+
* encrypted keystore (ADR 080), plus the session unlock agent (ADR 081). These
|
|
6
|
+
* operate on the keystore and session files directly (no Bitcoin connection or
|
|
7
|
+
* KeyManager) and never decrypt a key except when re-sealing during
|
|
8
|
+
* `change-passphrase`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerKeystoreCommand(program: Command, globals: () => GlobalOptions): void;
|
|
11
|
+
//# sourceMappingURL=keystore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../../../../src/commands/keystore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBzC,OAAO,EAAoC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,CA0K5F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/commands/update.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/commands/update.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAiG,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAI9I,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAEvE,wBAAgB,qBAAqB,CACnC,OAAO,EAAG,OAAO,EACjB,OAAO,EAAG,UAAU,EACpB,OAAO,EAAG,MAAM,aAAa,GAC5B,IAAI,CA2GN"}
|