@cogcoin/client 0.5.15 → 1.0.1

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 (174) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +142 -5
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +49 -92
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +5 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/sync-progress.d.ts +6 -0
  75. package/dist/cli/sync-progress.js +91 -0
  76. package/dist/cli/types.d.ts +9 -17
  77. package/dist/cli/types.js +0 -2
  78. package/dist/cli/wallet-format.d.ts +1 -0
  79. package/dist/cli/wallet-format.js +208 -144
  80. package/dist/cli/workflow-hints.d.ts +3 -3
  81. package/dist/cli/workflow-hints.js +11 -8
  82. package/dist/client/default-client.d.ts +3 -1
  83. package/dist/client/default-client.js +45 -2
  84. package/dist/client/factory.js +1 -1
  85. package/dist/client/initialization.js +23 -0
  86. package/dist/client/persistence.js +5 -5
  87. package/dist/client/store-adapter.js +1 -0
  88. package/dist/sqlite/checkpoints.d.ts +1 -0
  89. package/dist/sqlite/checkpoints.js +7 -0
  90. package/dist/sqlite/store.js +14 -1
  91. package/dist/types.d.ts +1 -0
  92. package/dist/wallet/coin-control.d.ts +41 -12
  93. package/dist/wallet/coin-control.js +100 -428
  94. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  95. package/dist/wallet/descriptor-normalization.js +0 -16
  96. package/dist/wallet/lifecycle.d.ts +7 -99
  97. package/dist/wallet/lifecycle.js +513 -968
  98. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  99. package/dist/wallet/managed-core-wallet.js +20 -0
  100. package/dist/wallet/mining/constants.d.ts +5 -12
  101. package/dist/wallet/mining/constants.js +5 -12
  102. package/dist/wallet/mining/control.d.ts +1 -13
  103. package/dist/wallet/mining/control.js +45 -349
  104. package/dist/wallet/mining/index.d.ts +4 -5
  105. package/dist/wallet/mining/index.js +2 -3
  106. package/dist/wallet/mining/runner.d.ts +123 -13
  107. package/dist/wallet/mining/runner.js +899 -511
  108. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  109. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  110. package/dist/wallet/mining/sentence-protocol.js +123 -0
  111. package/dist/wallet/mining/sentences.d.ts +4 -8
  112. package/dist/wallet/mining/sentences.js +3 -52
  113. package/dist/wallet/mining/state.d.ts +11 -6
  114. package/dist/wallet/mining/state.js +7 -6
  115. package/dist/wallet/mining/types.d.ts +2 -30
  116. package/dist/wallet/mining/visualizer.d.ts +31 -3
  117. package/dist/wallet/mining/visualizer.js +135 -13
  118. package/dist/wallet/read/context.d.ts +0 -2
  119. package/dist/wallet/read/context.js +119 -140
  120. package/dist/wallet/read/filter.js +2 -11
  121. package/dist/wallet/read/index.d.ts +1 -1
  122. package/dist/wallet/read/project.js +24 -77
  123. package/dist/wallet/read/types.d.ts +10 -25
  124. package/dist/wallet/reset.d.ts +0 -1
  125. package/dist/wallet/reset.js +60 -138
  126. package/dist/wallet/root-resolution.d.ts +1 -5
  127. package/dist/wallet/root-resolution.js +0 -18
  128. package/dist/wallet/runtime.d.ts +0 -6
  129. package/dist/wallet/runtime.js +0 -8
  130. package/dist/wallet/state/client-password-agent.js +208 -0
  131. package/dist/wallet/state/client-password.d.ts +65 -0
  132. package/dist/wallet/state/client-password.js +952 -0
  133. package/dist/wallet/state/crypto.d.ts +1 -20
  134. package/dist/wallet/state/crypto.js +0 -63
  135. package/dist/wallet/state/provider.d.ts +23 -11
  136. package/dist/wallet/state/provider.js +248 -290
  137. package/dist/wallet/state/storage.d.ts +2 -2
  138. package/dist/wallet/state/storage.js +48 -16
  139. package/dist/wallet/tx/anchor.d.ts +3 -28
  140. package/dist/wallet/tx/anchor.js +349 -1250
  141. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  142. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  143. package/dist/wallet/tx/cog.d.ts +5 -1
  144. package/dist/wallet/tx/cog.js +149 -185
  145. package/dist/wallet/tx/common.d.ts +61 -8
  146. package/dist/wallet/tx/common.js +266 -146
  147. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  148. package/dist/wallet/tx/domain-admin.js +61 -99
  149. package/dist/wallet/tx/domain-market.d.ts +5 -1
  150. package/dist/wallet/tx/domain-market.js +221 -228
  151. package/dist/wallet/tx/field.d.ts +4 -10
  152. package/dist/wallet/tx/field.js +83 -924
  153. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  154. package/dist/wallet/tx/identity-selector.js +17 -35
  155. package/dist/wallet/tx/index.d.ts +3 -1
  156. package/dist/wallet/tx/index.js +2 -1
  157. package/dist/wallet/tx/register.d.ts +3 -1
  158. package/dist/wallet/tx/register.js +62 -220
  159. package/dist/wallet/tx/reputation.d.ts +3 -1
  160. package/dist/wallet/tx/reputation.js +58 -95
  161. package/dist/wallet/types.d.ts +8 -122
  162. package/package.json +5 -5
  163. package/dist/wallet/archive.d.ts +0 -4
  164. package/dist/wallet/archive.js +0 -41
  165. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  166. package/dist/wallet/mining/hook-protocol.js +0 -161
  167. package/dist/wallet/mining/hook-runner.js +0 -52
  168. package/dist/wallet/mining/hooks.d.ts +0 -38
  169. package/dist/wallet/mining/hooks.js +0 -520
  170. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  171. package/dist/wallet/state/explicit-lock.js +0 -19
  172. package/dist/wallet/state/session.d.ts +0 -12
  173. package/dist/wallet/state/session.js +0 -23
  174. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -1,100 +1,29 @@
1
1
  import { dirname } from "node:path";
2
2
  import { formatManagedSyncErrorMessage } from "../../bitcoind/errors.js";
3
- import { formatBytes, formatDuration } from "../../bitcoind/progress/formatting.js";
4
3
  import { FileLockBusyError, acquireFileLock } from "../../wallet/fs/lock.js";
5
4
  import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
5
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
6
6
  import { usesTtyProgress, writeLine } from "../io.js";
7
7
  import { classifyCliError, formatCliTextError } from "../output.js";
8
+ import { createTerminalPrompter } from "../prompt.js";
8
9
  import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
9
- const SYNC_PROGRESS_LOG_INTERVAL_MS = 5_000;
10
- function createSyncProgressReporter(options) {
11
- let lastPhase = null;
12
- let lastMessage = "";
13
- let lastDownloadPrintedAt = 0;
14
- let lastDownloadBytes = null;
15
- let lastImportPrintedAt = 0;
16
- let lastImportBlocks = null;
17
- const infoEnabled = options.progressOutput !== "none";
18
- function shouldPrintEntryMessage(message, phase) {
19
- if (message === "Waiting to start managed sync." || message === "Sync complete.") {
20
- return false;
21
- }
22
- if (message.startsWith("Warning:")) {
23
- return true;
24
- }
25
- if (!infoEnabled) {
26
- return false;
27
- }
28
- if (phase === "getblock_archive_download" || phase === "getblock_archive_import") {
29
- return true;
30
- }
31
- return phase === "snapshot_download"
32
- || phase === "wait_headers_for_snapshot"
33
- || phase === "load_snapshot"
34
- || phase === "bitcoin_sync"
35
- || phase === "cogcoin_sync"
36
- || message.includes("Getblock manifest")
37
- || message.startsWith("Fetching Getblock manifest.")
38
- || message.startsWith("Refreshing Getblock manifest.")
39
- || message.startsWith("Using Getblock range ");
10
+ import { createSyncProgressReporter } from "../sync-progress.js";
11
+ import { formatBalanceReport } from "../wallet-format.js";
12
+ async function writePostSyncBalanceReport(options) {
13
+ const provider = withInteractiveWalletSecretProvider(options.context.walletSecretProvider, options.context.createPrompter?.() ?? createTerminalPrompter(options.context.stdin, options.context.stdout));
14
+ const readContext = await options.context.openWalletReadContext({
15
+ dataDir: options.dataDir,
16
+ databasePath: options.databasePath,
17
+ secretProvider: provider,
18
+ walletControlLockHeld: true,
19
+ paths: options.runtimePaths,
20
+ });
21
+ try {
22
+ writeLine(options.context.stdout, formatBalanceReport(readContext));
40
23
  }
41
- function formatDownloadLine(label, event) {
42
- const current = event.progress.downloadedBytes ?? 0;
43
- const total = event.progress.totalBytes ?? 0;
44
- const percent = event.progress.percent ?? (total > 0 ? (current / total) * 100 : 0);
45
- const speed = event.progress.bytesPerSecond === null ? "--" : `${formatBytes(event.progress.bytesPerSecond)}/s`;
46
- return `${label}: ${percent.toFixed(2)}% (${formatBytes(current)} / ${formatBytes(total)}, ${speed}, ETA ${formatDuration(event.progress.etaSeconds)})`;
24
+ finally {
25
+ await readContext.close().catch(() => undefined);
47
26
  }
48
- return (event) => {
49
- const message = event.progress.message.trim();
50
- const phaseChanged = event.phase !== lastPhase;
51
- const messageChanged = message !== lastMessage;
52
- if ((phaseChanged || messageChanged) && shouldPrintEntryMessage(message, event.phase)) {
53
- options.write(message);
54
- }
55
- if (infoEnabled && event.phase === "getblock_archive_download") {
56
- const now = Date.now();
57
- const currentBytes = event.progress.downloadedBytes ?? 0;
58
- const isComplete = (event.progress.percent ?? 0) >= 100;
59
- const shouldPrintMilestone = phaseChanged
60
- || lastDownloadBytes !== currentBytes && (isComplete
61
- || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
62
- if (shouldPrintMilestone) {
63
- options.write(formatDownloadLine("Getblock download", event));
64
- lastDownloadPrintedAt = now;
65
- lastDownloadBytes = currentBytes;
66
- }
67
- }
68
- else if (infoEnabled && event.phase === "snapshot_download") {
69
- const now = Date.now();
70
- const currentBytes = event.progress.downloadedBytes ?? 0;
71
- const isComplete = (event.progress.percent ?? 0) >= 100;
72
- const shouldPrintMilestone = phaseChanged
73
- || lastDownloadBytes !== currentBytes && (isComplete
74
- || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
75
- if (shouldPrintMilestone) {
76
- options.write(formatDownloadLine("Snapshot download", event));
77
- lastDownloadPrintedAt = now;
78
- lastDownloadBytes = currentBytes;
79
- }
80
- }
81
- else if (infoEnabled && event.phase === "getblock_archive_import") {
82
- const now = Date.now();
83
- const currentBlocks = event.progress.blocks ?? 0;
84
- const targetBlocks = event.progress.targetHeight ?? currentBlocks;
85
- const isComplete = currentBlocks >= targetBlocks;
86
- const shouldPrintMilestone = phaseChanged
87
- || lastImportBlocks !== currentBlocks && (isComplete
88
- || now - lastImportPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
89
- if (shouldPrintMilestone) {
90
- options.write(`Getblock import: Bitcoin ${currentBlocks.toLocaleString()} / ${targetBlocks.toLocaleString()}`);
91
- lastImportPrintedAt = now;
92
- lastImportBlocks = currentBlocks;
93
- }
94
- }
95
- lastPhase = event.phase;
96
- lastMessage = message;
97
- };
98
27
  }
99
28
  export async function runSyncCommand(parsed, context) {
100
29
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
@@ -104,13 +33,13 @@ export async function runSyncCommand(parsed, context) {
104
33
  let controlLock = null;
105
34
  let store = null;
106
35
  let storeOwned = true;
36
+ let client = null;
37
+ let clientClosed = false;
107
38
  try {
108
39
  const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
109
40
  paths: runtimePaths,
110
41
  provider: context.walletSecretProvider,
111
42
  loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
112
- loadUnlockSession: context.loadUnlockSession,
113
- loadWalletExplicitLock: context.loadWalletExplicitLock,
114
43
  });
115
44
  try {
116
45
  controlLock = await acquireFileLock(runtimePaths.walletControlLockPath, {
@@ -126,7 +55,7 @@ export async function runSyncCommand(parsed, context) {
126
55
  }
127
56
  await context.ensureDirectory(dirname(dbPath));
128
57
  store = await context.openSqliteStore({ filename: dbPath });
129
- const client = await context.openManagedBitcoindClient({
58
+ client = await context.openManagedBitcoindClient({
130
59
  store,
131
60
  databasePath: dbPath,
132
61
  dataDir,
@@ -147,6 +76,32 @@ export async function runSyncCommand(parsed, context) {
147
76
  return syncOutcome.code;
148
77
  }
149
78
  const result = syncOutcome.value;
79
+ if (result.endingHeight !== null && result.endingHeight === result.bestHeight) {
80
+ stopWatcher.cleanup();
81
+ const detachPromise = typeof client.detachToBackgroundFollow === "function"
82
+ ? client.detachToBackgroundFollow()
83
+ : Promise.resolve();
84
+ if (typeof client.playSyncCompletionScene === "function") {
85
+ await client.playSyncCompletionScene().catch(() => undefined);
86
+ }
87
+ try {
88
+ await detachPromise;
89
+ await client.close();
90
+ clientClosed = true;
91
+ writeLine(context.stderr, "Detached cleanly; background indexer follow resumed.");
92
+ await writePostSyncBalanceReport({
93
+ context,
94
+ dataDir,
95
+ databasePath: dbPath,
96
+ runtimePaths,
97
+ }).catch(() => undefined);
98
+ return 0;
99
+ }
100
+ catch {
101
+ writeLine(context.stderr, "Detach failed before background indexer follow was confirmed.");
102
+ return 1;
103
+ }
104
+ }
150
105
  if (typeof client.playSyncCompletionScene === "function") {
151
106
  const completionOutcome = await waitForCompletionOrStop(client.playSyncCompletionScene().catch(() => undefined), stopWatcher);
152
107
  if (completionOutcome.kind === "stopped") {
@@ -161,7 +116,9 @@ export async function runSyncCommand(parsed, context) {
161
116
  }
162
117
  finally {
163
118
  stopWatcher.cleanup();
164
- await client.close();
119
+ if (!clientClosed) {
120
+ await client.close();
121
+ }
165
122
  }
166
123
  }
167
124
  catch (error) {
@@ -1,11 +1,14 @@
1
- import { parseUnlockDurationToMs } from "../../wallet/lifecycle.js";
2
- import { buildInitMutationData, buildResetMutationData, buildRestoreMutationData, buildWalletDeleteMutationData, buildUnlockMutationData, buildRepairMutationData, buildWalletExportMutationData, buildWalletImportMutationData, buildWalletLockMutationData, } from "../mutation-json.js";
3
- import { buildResetPreviewData, buildRepairPreviewData, buildWalletLockPreviewData, } from "../preview-json.js";
1
+ import { buildInitMutationData, buildResetMutationData, buildRestoreMutationData, buildWalletDeleteMutationData, buildRepairMutationData, } from "../mutation-json.js";
2
+ import { buildResetPreviewData, buildRepairPreviewData, } from "../preview-json.js";
4
3
  import { writeLine } from "../io.js";
5
4
  import { createTerminalPrompter } from "../prompt.js";
6
5
  import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMutationJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
7
- import { formatNextStepLines, getFundingQuickstartGuidance, getInitNextSteps, getRestoreNextSteps, } from "../workflow-hints.js";
6
+ import { loadWelcomeArtText } from "../art.js";
7
+ import { formatNextStepLines, getFundingQuickstartGuidance, getInitNextSteps, getRestoreNextSteps, getSetupUnlockGuidanceLines, } from "../workflow-hints.js";
8
8
  import { createOwnedLockCleanupSignalWatcher, waitForCompletionOrStop, } from "../signals.js";
9
+ import { runSyncCommand } from "./sync.js";
10
+ import { CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS } from "../../wallet/state/client-password.js";
11
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
9
12
  function createCommandPrompter(parsed, context) {
10
13
  return parsed.outputMode !== "text"
11
14
  ? createTerminalPrompter(context.stdin, context.stderr)
@@ -21,24 +24,32 @@ function getResetWarnings(result) {
21
24
  ? ["Some existing Cogcoin secret-provider entries could not be discovered from the remaining local wallet artifacts and may need manual cleanup."]
22
25
  : [];
23
26
  }
27
+ function writeSetupUnlockGuidance(stdout) {
28
+ for (const line of getSetupUnlockGuidanceLines(CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS)) {
29
+ writeLine(stdout, line);
30
+ }
31
+ }
24
32
  function getResetNextSteps(result) {
25
33
  return result.walletAction === "deleted" || result.walletAction === "not-present"
26
34
  ? ["Run `cogcoin init` to create a new wallet."]
27
35
  : ["Run `cogcoin sync` to bootstrap assumeutxo and the managed Bitcoin/indexer state."];
28
36
  }
37
+ function getRepairNextSteps() {
38
+ return ["Run `cogcoin status` to review the repaired local state."];
39
+ }
29
40
  function formatResetBitcoinDataDirStatus(result) {
30
41
  if (result.bitcoinDataDir.status === "outside-reset-scope") {
31
42
  return "preserved (outside reset scope)";
32
43
  }
33
44
  return result.bitcoinDataDir.status;
34
45
  }
35
- function resetTextEntry(label, value, ok) {
46
+ function sectionTextEntry(label, value, ok) {
36
47
  return {
37
48
  text: `${label}: ${value}`,
38
49
  ok,
39
50
  };
40
51
  }
41
- function formatResetSection(header, entries) {
52
+ function formatAdminSection(header, entries) {
42
53
  return [header, ...entries.map((entry) => `${entry.ok ? "✓" : "✗"} ${entry.text}`)].join("\n");
43
54
  }
44
55
  function formatResetResultText(result) {
@@ -47,30 +58,30 @@ function formatResetResultText(result) {
47
58
  const secretCleanupOk = result.secretCleanupStatus !== "unknown" && result.secretCleanupStatus !== "failed";
48
59
  const managedCleanupOk = result.stoppedProcesses.survivors === 0;
49
60
  const outcomeEntries = [
50
- resetTextEntry("Wallet action", result.walletAction, true),
51
- resetTextEntry("Snapshot", result.bootstrapSnapshot.status, true),
52
- resetTextEntry("Bitcoin datadir", formatResetBitcoinDataDirStatus(result), true),
53
- resetTextEntry("Secret cleanup", result.secretCleanupStatus, secretCleanupOk),
61
+ sectionTextEntry("Wallet action", result.walletAction, true),
62
+ sectionTextEntry("Snapshot", result.bootstrapSnapshot.status, true),
63
+ sectionTextEntry("Bitcoin datadir", formatResetBitcoinDataDirStatus(result), true),
64
+ sectionTextEntry("Secret cleanup", result.secretCleanupStatus, secretCleanupOk),
54
65
  ];
55
66
  if (result.walletAction !== "retain-mnemonic" && result.walletOldRootId !== null) {
56
- outcomeEntries.push(resetTextEntry("Previous wallet root", result.walletOldRootId, true));
67
+ outcomeEntries.push(sectionTextEntry("Previous wallet root", result.walletOldRootId, true));
57
68
  }
58
69
  if (result.walletAction !== "retain-mnemonic" && result.walletNewRootId !== null) {
59
- outcomeEntries.push(resetTextEntry("New wallet root", result.walletNewRootId, true));
70
+ outcomeEntries.push(sectionTextEntry("New wallet root", result.walletNewRootId, true));
60
71
  }
61
72
  const sections = [
62
- formatResetSection("Paths", [
63
- resetTextEntry("Data root", result.dataRoot, true),
73
+ formatAdminSection("Paths", [
74
+ sectionTextEntry("Data root", result.dataRoot, true),
64
75
  ]),
65
- formatResetSection("Reset Outcome", outcomeEntries),
66
- formatResetSection("Managed Cleanup", [
67
- resetTextEntry("Managed bitcoind processes stopped", String(result.stoppedProcesses.managedBitcoind), managedCleanupOk),
68
- resetTextEntry("Indexer daemons stopped", String(result.stoppedProcesses.indexerDaemon), managedCleanupOk),
69
- resetTextEntry("Background miners stopped", String(result.stoppedProcesses.backgroundMining), managedCleanupOk),
76
+ formatAdminSection("Reset Outcome", outcomeEntries),
77
+ formatAdminSection("Managed Cleanup", [
78
+ sectionTextEntry("Managed bitcoind processes stopped", String(result.stoppedProcesses.managedBitcoind), managedCleanupOk),
79
+ sectionTextEntry("Indexer daemons stopped", String(result.stoppedProcesses.indexerDaemon), managedCleanupOk),
80
+ sectionTextEntry("Background miners stopped", String(result.stoppedProcesses.backgroundMining), managedCleanupOk),
70
81
  ]),
71
82
  ];
72
83
  if (warnings.length > 0) {
73
- sections.push(formatResetSection("Warnings", warnings.map((warning) => resetTextEntry("Warning", warning, false))));
84
+ sections.push(formatAdminSection("Warnings", warnings.map((warning) => sectionTextEntry("Warning", warning, false))));
74
85
  }
75
86
  const parts = [
76
87
  "\n⛭ Cogcoin Reset ⛭",
@@ -81,6 +92,68 @@ function formatResetResultText(result) {
81
92
  }
82
93
  return parts.join("\n\n");
83
94
  }
95
+ function isRepairMiningResumeActionOk(action) {
96
+ return action === "none"
97
+ || action === "skipped-not-resumable"
98
+ || action === "resumed-background";
99
+ }
100
+ function buildRepairWarningEntries(result) {
101
+ const entries = [];
102
+ if (result.miningResumeError !== null) {
103
+ entries.push(sectionTextEntry("Mining resume error", result.miningResumeError, false));
104
+ }
105
+ for (const warning of getRepairWarnings(result)) {
106
+ if (result.miningResumeError !== null && warning.includes(result.miningResumeError)) {
107
+ continue;
108
+ }
109
+ entries.push(sectionTextEntry("Warning", warning, false));
110
+ }
111
+ return entries;
112
+ }
113
+ function formatRepairResultText(result) {
114
+ const nextStep = getRepairNextSteps()[0] ?? null;
115
+ const warningEntries = buildRepairWarningEntries(result);
116
+ const sections = [
117
+ formatAdminSection("Wallet", [
118
+ sectionTextEntry("Wallet root", result.walletRootId, true),
119
+ sectionTextEntry("Recovered from backup", result.recoveredFromBackup ? "yes" : "no", true),
120
+ sectionTextEntry("Managed Core wallet recreated", result.recreatedManagedCoreWallet ? "yes" : "no", true),
121
+ ]),
122
+ formatAdminSection("Managed Bitcoind", [
123
+ sectionTextEntry("Managed bitcoind action", result.bitcoindServiceAction, true),
124
+ sectionTextEntry("Managed bitcoind compatibility issue", result.bitcoindCompatibilityIssue, result.bitcoindCompatibilityIssue === "none"),
125
+ sectionTextEntry("Managed Core replica action", result.managedCoreReplicaAction, true),
126
+ sectionTextEntry("Managed bitcoind post-repair health", result.bitcoindPostRepairHealth, result.bitcoindPostRepairHealth === "ready"),
127
+ ]),
128
+ formatAdminSection("Indexer", [
129
+ sectionTextEntry("Indexer database reset", result.resetIndexerDatabase ? "yes" : "no", true),
130
+ sectionTextEntry("Indexer daemon action", result.indexerDaemonAction, true),
131
+ sectionTextEntry("Indexer compatibility issue", result.indexerCompatibilityIssue, result.indexerCompatibilityIssue === "none"),
132
+ sectionTextEntry("Indexer post-repair health", result.indexerPostRepairHealth, result.indexerPostRepairHealth === "synced"),
133
+ ]),
134
+ formatAdminSection("Mining", [
135
+ sectionTextEntry("Mining mode before repair", result.miningPreRepairRunMode, true),
136
+ sectionTextEntry("Mining resume action", result.miningResumeAction, isRepairMiningResumeActionOk(result.miningResumeAction)),
137
+ sectionTextEntry("Mining mode after repair", result.miningPostRepairRunMode, true),
138
+ ]),
139
+ ];
140
+ if (result.note !== null) {
141
+ sections.push(formatAdminSection("Notes", [
142
+ sectionTextEntry("Note", result.note, true),
143
+ ]));
144
+ }
145
+ if (warningEntries.length > 0) {
146
+ sections.push(formatAdminSection("Warnings", warningEntries));
147
+ }
148
+ const parts = [
149
+ "\n⛭ Cogcoin Repair ⛭",
150
+ ...sections,
151
+ ];
152
+ if (nextStep !== null) {
153
+ parts.push(`Next step: ${nextStep}`);
154
+ }
155
+ return parts.join("\n\n");
156
+ }
84
157
  export async function runWalletAdminCommand(parsed, context) {
85
158
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
86
159
  const stopWatcher = createOwnedLockCleanupSignalWatcher(context.signalSource, context.forceExit, [
@@ -89,15 +162,17 @@ export async function runWalletAdminCommand(parsed, context) {
89
162
  runtimePaths.bitcoindLockPath,
90
163
  runtimePaths.indexerDaemonLockPath,
91
164
  ]);
165
+ let shouldAutoSyncAfterInit = false;
92
166
  try {
93
167
  const outcome = await waitForCompletionOrStop((async () => {
94
168
  const provider = context.walletSecretProvider;
95
169
  if (parsed.command === "init" || parsed.command === "wallet-init") {
96
170
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
97
171
  const prompter = createCommandPrompter(parsed, context);
172
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, prompter);
98
173
  const result = await context.initializeWallet({
99
174
  dataDir,
100
- provider,
175
+ provider: interactiveProvider,
101
176
  prompter,
102
177
  paths: runtimePaths,
103
178
  });
@@ -109,22 +184,42 @@ export async function runWalletAdminCommand(parsed, context) {
109
184
  }));
110
185
  return 0;
111
186
  }
112
- writeLine(context.stdout, `Wallet initialized.`);
113
- writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
114
- writeLine(context.stdout, `Funding address: ${result.fundingAddress}`);
115
- writeLine(context.stdout, `Unlocked until: ${new Date(result.unlockUntilUnixMs).toISOString()}`);
116
- writeLine(context.stdout, `Quickstart: ${getFundingQuickstartGuidance()}`);
117
- for (const line of formatNextStepLines(nextSteps)) {
118
- writeLine(context.stdout, line);
187
+ writeLine(context.stdout, "");
188
+ writeLine(context.stdout, loadWelcomeArtText());
189
+ writeLine(context.stdout, "");
190
+ writeLine(context.stdout, result.walletAction === "already-initialized"
191
+ ? "Wallet already initialized."
192
+ : "Wallet initialized.");
193
+ if (result.walletAction === "already-initialized") {
194
+ writeLine(context.stdout, "");
195
+ writeLine(context.stdout, "Wallet");
196
+ writeLine(context.stdout, `✓ Client password: ${result.passwordAction}`);
197
+ writeLine(context.stdout, `✓ Wallet root: ${result.walletRootId}`);
198
+ writeLine(context.stdout, `✓ Funding address: ${result.fundingAddress}`);
199
+ if (result.passwordAction !== "already-configured") {
200
+ writeSetupUnlockGuidance(context.stdout);
201
+ }
202
+ }
203
+ else {
204
+ writeLine(context.stdout, `Client password: ${result.passwordAction}`);
205
+ if (result.passwordAction !== "already-configured") {
206
+ writeSetupUnlockGuidance(context.stdout);
207
+ }
208
+ writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
209
+ writeLine(context.stdout, `Funding address: ${result.fundingAddress}`);
119
210
  }
211
+ writeLine(context.stdout, "");
212
+ writeLine(context.stdout, `Quickstart: ${getFundingQuickstartGuidance()}`);
213
+ shouldAutoSyncAfterInit = true;
120
214
  return 0;
121
215
  }
122
216
  if (parsed.command === "restore" || parsed.command === "wallet-restore") {
123
217
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
124
218
  const prompter = createCommandPrompter(parsed, context);
219
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, prompter);
125
220
  const result = await context.restoreWalletFromMnemonic({
126
221
  dataDir,
127
- provider,
222
+ provider: interactiveProvider,
128
223
  prompter,
129
224
  paths: runtimePaths,
130
225
  });
@@ -139,9 +234,11 @@ export async function runWalletAdminCommand(parsed, context) {
139
234
  return 0;
140
235
  }
141
236
  writeLine(context.stdout, `Wallet seed "${result.seedName}" restored from mnemonic.`);
237
+ if (result.passwordAction !== "already-configured") {
238
+ writeSetupUnlockGuidance(context.stdout);
239
+ }
142
240
  writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
143
241
  writeLine(context.stdout, `Funding address: ${result.fundingAddress}`);
144
- writeLine(context.stdout, `Unlocked until: ${new Date(result.unlockUntilUnixMs).toISOString()}`);
145
242
  writeLine(context.stdout, "Note: Managed Bitcoin/indexer bootstrap is deferred until you run `cogcoin sync`.");
146
243
  for (const warning of result.warnings ?? []) {
147
244
  writeLine(context.stdout, `Warning: ${warning}`);
@@ -154,9 +251,10 @@ export async function runWalletAdminCommand(parsed, context) {
154
251
  if (parsed.command === "wallet-delete") {
155
252
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
156
253
  const prompter = createCommandPrompter(parsed, context);
254
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, prompter);
157
255
  const result = await context.deleteImportedWalletSeed({
158
256
  dataDir,
159
- provider,
257
+ provider: interactiveProvider,
160
258
  prompter,
161
259
  assumeYes: parsed.assumeYes,
162
260
  paths: runtimePaths,
@@ -172,29 +270,13 @@ export async function runWalletAdminCommand(parsed, context) {
172
270
  if (parsed.command === "wallet-show-mnemonic") {
173
271
  const prompter = createCommandPrompter(parsed, context);
174
272
  await context.showWalletMnemonic({
175
- provider,
273
+ provider: withInteractiveWalletSecretProvider(provider, prompter),
176
274
  prompter,
177
275
  paths: runtimePaths,
178
276
  });
179
277
  return 0;
180
278
  }
181
279
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
182
- if (parsed.command === "unlock" || parsed.command === "wallet-unlock") {
183
- const durationMs = parseUnlockDurationToMs(parsed.unlockFor);
184
- const result = await context.unlockWallet({
185
- provider,
186
- unlockDurationMs: durationMs,
187
- paths: runtimePaths,
188
- });
189
- if (parsed.outputMode === "json") {
190
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "unlocked", buildUnlockMutationData(result)));
191
- return 0;
192
- }
193
- writeLine(context.stdout, `Wallet unlocked.`);
194
- writeLine(context.stdout, `Wallet root: ${result.state.walletRootId}`);
195
- writeLine(context.stdout, `Unlocked until: ${new Date(result.unlockUntilUnixMs).toISOString()}`);
196
- return 0;
197
- }
198
280
  if (parsed.command === "reset") {
199
281
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
200
282
  if (parsed.outputMode === "preview-json") {
@@ -208,7 +290,7 @@ export async function runWalletAdminCommand(parsed, context) {
208
290
  const prompter = createCommandPrompter(parsed, context);
209
291
  const result = await context.resetWallet({
210
292
  dataDir,
211
- provider,
293
+ provider: withInteractiveWalletSecretProvider(provider, prompter),
212
294
  prompter,
213
295
  });
214
296
  if (parsed.outputMode === "json") {
@@ -221,113 +303,33 @@ export async function runWalletAdminCommand(parsed, context) {
221
303
  writeLine(context.stdout, formatResetResultText(result));
222
304
  return 0;
223
305
  }
224
- if (parsed.command === "wallet-export") {
225
- const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
226
- const prompter = createCommandPrompter(parsed, context);
227
- const result = await context.exportWallet({
228
- archivePath: parsed.args[0],
229
- dataDir,
230
- databasePath: dbPath,
231
- provider,
232
- prompter,
233
- paths: runtimePaths,
234
- });
235
- if (parsed.outputMode === "json") {
236
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "exported", buildWalletExportMutationData(result)));
237
- return 0;
238
- }
239
- writeLine(context.stdout, `Wallet exported.`);
240
- writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
241
- writeLine(context.stdout, `Archive path: ${result.archivePath}`);
242
- return 0;
243
- }
244
- if (parsed.command === "wallet-import") {
245
- const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
246
- const prompter = createCommandPrompter(parsed, context);
247
- const result = await context.importWallet({
248
- archivePath: parsed.args[0],
249
- dataDir,
250
- databasePath: dbPath,
251
- provider,
252
- prompter,
253
- paths: runtimePaths,
254
- });
255
- if (parsed.outputMode === "json") {
256
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "imported", buildWalletImportMutationData(result)));
257
- return 0;
258
- }
259
- writeLine(context.stdout, `Wallet imported.`);
260
- writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
261
- writeLine(context.stdout, `Funding address: ${result.fundingAddress}`);
262
- writeLine(context.stdout, `Unlocked until: ${new Date(result.unlockUntilUnixMs).toISOString()}`);
263
- return 0;
264
- }
265
- if (parsed.command === "wallet-lock") {
266
- const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
267
- const result = await context.lockWallet({
268
- dataDir,
269
- provider,
270
- paths: runtimePaths,
271
- });
272
- if (parsed.outputMode === "preview-json") {
273
- writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "locked", buildWalletLockPreviewData(result)));
274
- return 0;
275
- }
276
- if (parsed.outputMode === "json") {
277
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), "cogcoin wallet lock", "locked", buildWalletLockMutationData(result)));
278
- return 0;
279
- }
280
- writeLine(context.stdout, `Wallet locked.`);
281
- writeLine(context.stdout, `Wallet root: ${result.walletRootId ?? "none"}`);
282
- return 0;
283
- }
284
306
  if (parsed.command === "repair") {
285
307
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
308
+ const repairProvider = parsed.outputMode === "preview-json"
309
+ ? provider
310
+ : withInteractiveWalletSecretProvider(provider, createCommandPrompter(parsed, context));
286
311
  const result = await context.repairWallet({
287
312
  dataDir,
288
313
  databasePath: dbPath,
289
- provider,
314
+ provider: repairProvider,
290
315
  assumeYes: parsed.assumeYes,
291
316
  paths: runtimePaths,
292
317
  });
293
318
  if (parsed.outputMode === "preview-json") {
294
319
  writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "completed", buildRepairPreviewData(result), {
295
- nextSteps: ["Run `cogcoin status` to review the repaired local state."],
320
+ nextSteps: getRepairNextSteps(),
296
321
  warnings: getRepairWarnings(result),
297
322
  }));
298
323
  return 0;
299
324
  }
300
325
  if (parsed.outputMode === "json") {
301
326
  writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), "cogcoin repair", "completed", buildRepairMutationData(result), {
302
- nextSteps: ["Run `cogcoin status` to review the repaired local state."],
327
+ nextSteps: getRepairNextSteps(),
303
328
  warnings: getRepairWarnings(result),
304
329
  }));
305
330
  return 0;
306
331
  }
307
- writeLine(context.stdout, `Wallet repair completed.`);
308
- writeLine(context.stdout, `Wallet root: ${result.walletRootId}`);
309
- writeLine(context.stdout, `Recovered from backup: ${result.recoveredFromBackup ? "yes" : "no"}`);
310
- writeLine(context.stdout, `Managed Core wallet recreated: ${result.recreatedManagedCoreWallet ? "yes" : "no"}`);
311
- writeLine(context.stdout, `Managed bitcoind action: ${result.bitcoindServiceAction}`);
312
- writeLine(context.stdout, `Managed bitcoind compatibility issue: ${result.bitcoindCompatibilityIssue}`);
313
- writeLine(context.stdout, `Managed Core replica action: ${result.managedCoreReplicaAction}`);
314
- writeLine(context.stdout, `Managed bitcoind post-repair health: ${result.bitcoindPostRepairHealth}`);
315
- writeLine(context.stdout, `Indexer database reset: ${result.resetIndexerDatabase ? "yes" : "no"}`);
316
- writeLine(context.stdout, `Indexer daemon action: ${result.indexerDaemonAction}`);
317
- writeLine(context.stdout, `Indexer compatibility issue: ${result.indexerCompatibilityIssue}`);
318
- writeLine(context.stdout, `Indexer post-repair health: ${result.indexerPostRepairHealth}`);
319
- writeLine(context.stdout, `Mining mode before repair: ${result.miningPreRepairRunMode}`);
320
- writeLine(context.stdout, `Mining resume action: ${result.miningResumeAction}`);
321
- writeLine(context.stdout, `Mining mode after repair: ${result.miningPostRepairRunMode}`);
322
- if (result.miningResumeError !== null) {
323
- writeLine(context.stdout, `Mining resume error: ${result.miningResumeError}`);
324
- }
325
- if (result.note !== null) {
326
- writeLine(context.stdout, `Note: ${result.note}`);
327
- }
328
- for (const warning of getRepairWarnings(result)) {
329
- writeLine(context.stdout, `Warning: ${warning}`);
330
- }
332
+ writeLine(context.stdout, formatRepairResultText(result));
331
333
  return 0;
332
334
  }
333
335
  writeLine(context.stderr, `wallet admin command not implemented: ${parsed.command}`);
@@ -336,6 +338,10 @@ export async function runWalletAdminCommand(parsed, context) {
336
338
  if (outcome.kind === "stopped") {
337
339
  return outcome.code;
338
340
  }
341
+ if (shouldAutoSyncAfterInit && outcome.value === 0) {
342
+ stopWatcher.cleanup();
343
+ return await runSyncCommand(parsed, context);
344
+ }
339
345
  return outcome.value;
340
346
  }
341
347
  catch (error) {