@ldgr/cli 0.1.1 → 0.2.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 CHANGED
@@ -15,4 +15,4 @@ ldgr send-token t1abcdef01234567 ldgr1… 10
15
15
  ldgr network set testnet
16
16
  ```
17
17
 
18
- The default owner-only wallet store lives at `~/.config/ldgr/wallet.json`. Each added wallet is encrypted separately using a passphrase requested on the terminal. `wallet list` shows one-based indexes and marks the active wallet; `wallet select <index>` changes it without exposing a secret. Record the recovery key shown at creation offline. `wallet import --testnet` imports a recovery key as a testnet wallet. Testnet prints a warning on every wallet command and only uses `https://tst.ldgr.ltd`.
18
+ The default owner-only wallet store lives at `~/.ldgr/wallet.json`. On first use, an existing `~/.config/ldgr/wallet.json` store is validated, copied to the new owner-only location, and removed from its former location. Each added wallet is encrypted separately using a passphrase requested on the terminal. `wallet list` shows one-based indexes and marks the active wallet; `wallet select <index>` changes it without exposing a secret. Record the recovery key shown at creation offline. `wallet import --testnet` imports a recovery key as a testnet wallet. Testnet prints a warning on every wallet command and only uses `https://tst.ldgr.ltd`.
package/dist/cli.js CHANGED
@@ -118,7 +118,13 @@ function defaultWalletPath(input = {}) {
118
118
  const home = input.home ?? process.env.HOME;
119
119
  if (!home)
120
120
  throw new Error("Could not determine your home directory; use --wallet <path>");
121
- const configHome = process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config");
121
+ return join(home, ".ldgr", "wallet.json");
122
+ }
123
+ function legacyDefaultWalletPath(input = {}) {
124
+ const home = input.home ?? process.env.HOME;
125
+ if (!home)
126
+ throw new Error("Could not determine your home directory; use --wallet <path>");
127
+ const configHome = input.configHome ?? (process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config"));
122
128
  return join(configHome, "ldgr", "wallet.json");
123
129
  }
124
130
  async function createEncryptedWalletFile(input) {
@@ -146,7 +152,7 @@ async function encryptWallet(input) {
146
152
  return { file, serialized: `${JSON.stringify(file, null, 2)}
147
153
  `, recoveryKey: input.recoveryKey };
148
154
  }
149
- async function writeNewWalletFile(path, file) {
155
+ async function writeNewWalletStore(path, store) {
150
156
  await mkdir(dirname(path), { recursive: true, mode: 448 });
151
157
  const handle = await open(path, "wx", 384).catch((error) => {
152
158
  if (error.code === "EEXIST")
@@ -154,12 +160,15 @@ async function writeNewWalletFile(path, file) {
154
160
  throw error;
155
161
  });
156
162
  try {
157
- await handle.writeFile(serializeWalletStore(newWalletStore(file)), "utf8");
163
+ await handle.writeFile(serializeWalletStore(store), "utf8");
158
164
  await handle.sync();
159
165
  } finally {
160
166
  await handle.close();
161
167
  }
162
168
  }
169
+ async function writeNewWalletFile(path, file) {
170
+ await writeNewWalletStore(path, newWalletStore(file));
171
+ }
163
172
  async function readWalletStore(path) {
164
173
  const details = await lstat(path).catch((error) => {
165
174
  if (error.code === "ENOENT")
@@ -178,6 +187,32 @@ async function readWalletStore(path) {
178
187
  }
179
188
  return parseWalletStore(parsed);
180
189
  }
190
+ async function migrateDefaultWalletPath(input = {}) {
191
+ const path = defaultWalletPath(input);
192
+ try {
193
+ await lstat(path);
194
+ return { path, migrated: false };
195
+ } catch (error) {
196
+ if (error.code !== "ENOENT")
197
+ throw error;
198
+ }
199
+ const legacyPath = legacyDefaultWalletPath(input);
200
+ let legacyStore;
201
+ try {
202
+ legacyStore = await readWalletStore(legacyPath);
203
+ } catch (error) {
204
+ if (error instanceof Error && error.message === `Wallet file not found: ${legacyPath}`)
205
+ return { path, migrated: false };
206
+ throw error;
207
+ }
208
+ await writeNewWalletStore(path, legacyStore);
209
+ try {
210
+ await unlink(legacyPath);
211
+ } catch (error) {
212
+ throw new Error(`Migrated wallet to ${path}, but could not remove legacy wallet file ${legacyPath}: ${error instanceof Error ? error.message : String(error)}`);
213
+ }
214
+ return { path, migrated: true };
215
+ }
181
216
  async function unlockWalletFile(file, passphrase) {
182
217
  try {
183
218
  const key = await deriveEncryptionKey(passphrase, decodeBase64Url(file.kdf.salt));
@@ -277,7 +312,7 @@ async function walletFileMetadata(path) {
277
312
 
278
313
  // src/cli.ts
279
314
  var HELP = `
280
- LDGR CLI 0.1.1 — self-custody wallet
315
+ LDGR CLI 0.2.0 — self-custody wallet
281
316
 
282
317
  Usage:
283
318
  ldgr wallet create [--testnet] [--wallet <path>]
@@ -291,7 +326,8 @@ Usage:
291
326
  ldgr network show [--wallet <path>]
292
327
  ldgr network set <mainnet|testnet> [--wallet <path>]
293
328
 
294
- The default wallet store is ~/.config/ldgr/wallet.json. Each wallet inside it
329
+ The default wallet store is ~/.ldgr/wallet.json. Existing wallets at
330
+ ~/.config/ldgr/wallet.json move there safely on first use. Each wallet inside it
295
331
  is independently encrypted with a passphrase you enter through the terminal;
296
332
  the CLI never accepts recovery keys or passphrases as arguments or environment
297
333
  variables. Use \`ldgr wallet select <index>\` to change the active wallet, or
@@ -301,6 +337,7 @@ prints a warning on every wallet command.
301
337
  function parseArguments(argv) {
302
338
  const command = [];
303
339
  let walletPath = defaultWalletPath();
340
+ let walletPathExplicit = false;
304
341
  let walletOnTestnet = false;
305
342
  for (let index = 0;index < argv.length; index += 1) {
306
343
  const value = argv[index];
@@ -309,6 +346,7 @@ function parseArguments(argv) {
309
346
  if (!path || path.startsWith("-"))
310
347
  throw new TypeError("--wallet requires a file path");
311
348
  walletPath = path;
349
+ walletPathExplicit = true;
312
350
  index += 1;
313
351
  } else if (value === "--testnet") {
314
352
  walletOnTestnet = true;
@@ -321,7 +359,7 @@ function parseArguments(argv) {
321
359
  if (walletOnTestnet && !(command[0] === "wallet" && (command[1] === "create" || command[1] === "import"))) {
322
360
  throw new TypeError("--testnet is only used with wallet create or wallet import; use `ldgr network set testnet` to switch an existing wallet");
323
361
  }
324
- return { command, walletPath, walletOnTestnet };
362
+ return { command, walletPath, walletPathExplicit, walletOnTestnet };
325
363
  }
326
364
  function networkLabel(file) {
327
365
  return file.ledgerId === 0 ? "testnet" : "mainnet";
@@ -561,26 +599,29 @@ async function main() {
561
599
  }
562
600
  p.intro("LDGR CLI");
563
601
  try {
602
+ const resolved = parsed.walletPathExplicit ? { path: parsed.walletPath, migrated: false } : await migrateDefaultWalletPath();
603
+ if (resolved.migrated)
604
+ p.log.info(`Moved legacy wallet store to ${resolved.path}`);
564
605
  if (first === "wallet" && second === "create" && third === undefined)
565
- await runCreate(parsed.walletPath, parsed.walletOnTestnet);
606
+ await runCreate(resolved.path, parsed.walletOnTestnet);
566
607
  else if (first === "wallet" && second === "import" && third === undefined)
567
- await runImport(parsed.walletPath, parsed.walletOnTestnet);
608
+ await runImport(resolved.path, parsed.walletOnTestnet);
568
609
  else if (first === "wallet" && second === "list" && third === undefined)
569
- await runWalletList(parsed.walletPath);
610
+ await runWalletList(resolved.path);
570
611
  else if (first === "wallet" && second === "select" && third && fourth === undefined)
571
- await runWalletSelect(parsed.walletPath, third);
612
+ await runWalletSelect(resolved.path, third);
572
613
  else if (first === "address" && second === undefined)
573
- await runAddress(parsed.walletPath);
614
+ await runAddress(resolved.path);
574
615
  else if (first === "balance" && second === undefined)
575
- await runBalance(parsed.walletPath);
616
+ await runBalance(resolved.path);
576
617
  else if (first === "send" && second && third && fourth === undefined)
577
- await runSend(parsed.walletPath, "dng", second, third);
618
+ await runSend(resolved.path, "dng", second, third);
578
619
  else if (first === "send-token" && second && third && fourth)
579
- await runSend(parsed.walletPath, second, third, fourth);
620
+ await runSend(resolved.path, second, third, fourth);
580
621
  else if (first === "network" && second === "show" && third === undefined)
581
- await runNetwork(parsed.walletPath);
622
+ await runNetwork(resolved.path);
582
623
  else if (first === "network" && second === "set" && third && fourth === undefined)
583
- await runNetwork(parsed.walletPath, third);
624
+ await runNetwork(resolved.path, third);
584
625
  else
585
626
  throw new TypeError("Unknown command. Run `ldgr --help` for available commands.");
586
627
  } catch (error) {
@@ -27,20 +27,33 @@ export type WalletFileMetadata = EncryptedWalletFile & {
27
27
  readonly index: number;
28
28
  readonly selected: boolean;
29
29
  };
30
+ export type DefaultWalletPathInput = {
31
+ readonly home?: string | undefined;
32
+ /** Test seam for the former XDG default; production reads XDG_CONFIG_HOME. */
33
+ readonly configHome?: string | undefined;
34
+ };
30
35
  export type CreatedEncryptedWallet = {
31
36
  readonly file: EncryptedWalletFile;
32
37
  readonly serialized: string;
33
38
  /** Display once during creation; never persist or log this value. */
34
39
  readonly recoveryKey: string;
35
40
  };
36
- export declare function defaultWalletPath(input?: {
37
- readonly home?: string | undefined;
38
- }): string;
41
+ export declare function defaultWalletPath(input?: DefaultWalletPathInput): string;
42
+ /** The v0.1 default location, used only to preserve existing local wallets. */
43
+ export declare function legacyDefaultWalletPath(input?: DefaultWalletPathInput): string;
39
44
  export declare function createEncryptedWalletFile(input: {
40
45
  readonly ledgerId: LedgerId;
41
46
  readonly passphrase: string;
42
47
  }): Promise<CreatedEncryptedWallet>;
43
48
  export declare function writeNewWalletFile(path: string, file: EncryptedWalletFile): Promise<void>;
49
+ /**
50
+ * Moves the validated former XDG default into ~/.ldgr/ on first wallet use.
51
+ * Explicit --wallet paths are deliberately never considered here.
52
+ */
53
+ export declare function migrateDefaultWalletPath(input?: DefaultWalletPathInput): Promise<{
54
+ readonly path: string;
55
+ readonly migrated: boolean;
56
+ }>;
44
57
  export declare function readEncryptedWalletFile(path: string, passphrase: string): Promise<LdgrSigner>;
45
58
  /** Adds a separately encrypted wallet and selects it. */
46
59
  export declare function addWalletFile(path: string, file: EncryptedWalletFile): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldgr/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "ldgr": "dist/cli.js"