@cogcoin/client 1.0.0 → 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.
@@ -1,28 +1,109 @@
1
1
  import { dirname } from "node:path";
2
+ import { FileLockBusyError, acquireFileLock } from "../../wallet/fs/lock.js";
3
+ import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
4
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
2
5
  import { buildMineStartData, buildMineStopData, } from "../mining-json.js";
3
6
  import { buildMineStartPreviewData, buildMineStopPreviewData, } from "../preview-json.js";
4
- import { writeLine } from "../io.js";
7
+ import { usesTtyProgress, writeLine } from "../io.js";
5
8
  import { createTerminalPrompter } from "../prompt.js";
6
9
  import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMiningControlJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
7
10
  import { formatNextStepLines, getMineStopNextSteps, } from "../workflow-hints.js";
8
- import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
11
+ import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
12
+ import { createSyncProgressReporter } from "../sync-progress.js";
9
13
  function createCommandPrompter(parsed, context) {
10
14
  return parsed.outputMode !== "text"
11
15
  ? createTerminalPrompter(context.stdin, context.stderr)
12
16
  : context.createPrompter();
13
17
  }
14
- async function prestartManagedMiningServices(options) {
15
- let readContext = null;
18
+ async function ensureMiningProviderSetup(options) {
19
+ const setupReady = await options.context.ensureBuiltInMiningSetupIfNeeded({
20
+ provider: options.provider,
21
+ prompter: options.prompter,
22
+ paths: options.runtimePaths,
23
+ });
24
+ if (!setupReady) {
25
+ throw new Error("Built-in mining provider is not configured. Run `cogcoin mine setup`.");
26
+ }
27
+ }
28
+ async function syncManagedMiningReadiness(options) {
29
+ const ttyProgressActive = usesTtyProgress(options.parsed.progressOutput, options.context.stderr);
30
+ let controlLock = null;
31
+ let store = null;
32
+ let storeOwned = true;
33
+ let client = null;
34
+ let clientClosed = false;
16
35
  try {
17
- readContext = await options.context.openWalletReadContext({
18
- dataDir: options.dataDir,
19
- databasePath: options.databasePath,
20
- secretProvider: options.provider,
36
+ const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
21
37
  paths: options.runtimePaths,
38
+ provider: options.provider,
39
+ loadRawWalletStateEnvelope: options.context.loadRawWalletStateEnvelope,
22
40
  });
41
+ try {
42
+ controlLock = await acquireFileLock(options.runtimePaths.walletControlLockPath, {
43
+ purpose: "managed-sync",
44
+ walletRootId: walletRoot.walletRootId,
45
+ });
46
+ }
47
+ catch (error) {
48
+ if (error instanceof FileLockBusyError) {
49
+ throw new Error("wallet_control_lock_busy");
50
+ }
51
+ throw error;
52
+ }
53
+ await options.context.ensureDirectory(dirname(options.databasePath));
54
+ store = await options.context.openSqliteStore({ filename: options.databasePath });
55
+ client = await options.context.openManagedBitcoindClient({
56
+ store,
57
+ databasePath: options.databasePath,
58
+ dataDir: options.dataDir,
59
+ walletRootId: walletRoot.walletRootId,
60
+ progressOutput: options.parsed.progressOutput,
61
+ onProgress: ttyProgressActive ? undefined : createSyncProgressReporter({
62
+ progressOutput: options.parsed.progressOutput,
63
+ write: (line) => {
64
+ writeLine(options.context.stderr, line);
65
+ },
66
+ }),
67
+ });
68
+ storeOwned = false;
69
+ const stopWatcher = createStopSignalWatcher(options.context.signalSource, options.context.stderr, client, options.context.forceExit, [options.runtimePaths.walletControlLockPath]);
70
+ try {
71
+ const syncOutcome = await waitForCompletionOrStop(client.syncToTip(), stopWatcher);
72
+ if (syncOutcome.kind === "stopped") {
73
+ return syncOutcome.code;
74
+ }
75
+ const result = syncOutcome.value;
76
+ if (result.endingHeight !== null && result.endingHeight === result.bestHeight) {
77
+ stopWatcher.cleanup();
78
+ const detachPromise = typeof client.detachToBackgroundFollow === "function"
79
+ ? client.detachToBackgroundFollow()
80
+ : Promise.resolve();
81
+ try {
82
+ await detachPromise;
83
+ await client.close();
84
+ clientClosed = true;
85
+ writeLine(options.context.stderr, "Detached cleanly; background indexer follow resumed.");
86
+ return null;
87
+ }
88
+ catch {
89
+ writeLine(options.context.stderr, "Detach failed before background indexer follow was confirmed.");
90
+ return 1;
91
+ }
92
+ }
93
+ throw new Error("Managed sync did not reach the current Bitcoin tip.");
94
+ }
95
+ finally {
96
+ stopWatcher.cleanup();
97
+ if (!clientClosed) {
98
+ await client.close();
99
+ }
100
+ }
23
101
  }
24
102
  finally {
25
- await readContext?.close();
103
+ if (storeOwned && store !== null) {
104
+ await store.close().catch(() => undefined);
105
+ }
106
+ await controlLock?.release().catch(() => undefined);
26
107
  }
27
108
  }
28
109
  export async function runMiningRuntimeCommand(parsed, context) {
@@ -30,17 +111,26 @@ export async function runMiningRuntimeCommand(parsed, context) {
30
111
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
31
112
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
32
113
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
33
- await context.ensureDirectory(dirname(dbPath));
34
114
  if (parsed.command === "mine") {
35
115
  const prompter = context.createPrompter();
36
116
  const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
37
- await prestartManagedMiningServices({
117
+ await ensureMiningProviderSetup({
118
+ context,
119
+ provider,
120
+ prompter,
121
+ runtimePaths,
122
+ });
123
+ const preflightCode = await syncManagedMiningReadiness({
124
+ parsed,
38
125
  context,
39
126
  dataDir,
40
127
  databasePath: dbPath,
41
128
  provider,
42
129
  runtimePaths,
43
130
  });
131
+ if (preflightCode !== null) {
132
+ return preflightCode;
133
+ }
44
134
  const abortController = new AbortController();
45
135
  const onStop = () => {
46
136
  abortController.abort();
@@ -57,6 +147,7 @@ export async function runMiningRuntimeCommand(parsed, context) {
57
147
  stdout: context.stdout,
58
148
  stderr: context.stderr,
59
149
  progressOutput: parsed.progressOutput,
150
+ builtInSetupEnsured: true,
60
151
  paths: runtimePaths,
61
152
  });
62
153
  }
@@ -69,18 +160,29 @@ export async function runMiningRuntimeCommand(parsed, context) {
69
160
  if (parsed.command === "mine-start") {
70
161
  const prompter = createCommandPrompter(parsed, context);
71
162
  const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
72
- await prestartManagedMiningServices({
163
+ await ensureMiningProviderSetup({
164
+ context,
165
+ provider,
166
+ prompter,
167
+ runtimePaths,
168
+ });
169
+ const preflightCode = await syncManagedMiningReadiness({
170
+ parsed,
73
171
  context,
74
172
  dataDir,
75
173
  databasePath: dbPath,
76
174
  provider,
77
175
  runtimePaths,
78
176
  });
177
+ if (preflightCode !== null) {
178
+ return preflightCode;
179
+ }
79
180
  const result = await context.startBackgroundMining({
80
181
  dataDir,
81
182
  databasePath: dbPath,
82
183
  provider,
83
184
  prompter,
185
+ builtInSetupEnsured: true,
84
186
  paths: runtimePaths,
85
187
  });
86
188
  if (parsed.outputMode === "preview-json") {
@@ -1,6 +1,5 @@
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";
6
5
  import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
@@ -8,8 +7,8 @@ import { usesTtyProgress, writeLine } from "../io.js";
8
7
  import { classifyCliError, formatCliTextError } from "../output.js";
9
8
  import { createTerminalPrompter } from "../prompt.js";
10
9
  import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
10
+ import { createSyncProgressReporter } from "../sync-progress.js";
11
11
  import { formatBalanceReport } from "../wallet-format.js";
12
- const SYNC_PROGRESS_LOG_INTERVAL_MS = 5_000;
13
12
  async function writePostSyncBalanceReport(options) {
14
13
  const provider = withInteractiveWalletSecretProvider(options.context.walletSecretProvider, options.context.createPrompter?.() ?? createTerminalPrompter(options.context.stdin, options.context.stdout));
15
14
  const readContext = await options.context.openWalletReadContext({
@@ -26,95 +25,6 @@ async function writePostSyncBalanceReport(options) {
26
25
  await readContext.close().catch(() => undefined);
27
26
  }
28
27
  }
29
- function createSyncProgressReporter(options) {
30
- let lastPhase = null;
31
- let lastMessage = "";
32
- let lastDownloadPrintedAt = 0;
33
- let lastDownloadBytes = null;
34
- let lastImportPrintedAt = 0;
35
- let lastImportBlocks = null;
36
- const infoEnabled = options.progressOutput !== "none";
37
- function shouldPrintEntryMessage(message, phase) {
38
- if (message === "Waiting to start managed sync." || message === "Sync complete.") {
39
- return false;
40
- }
41
- if (message.startsWith("Warning:")) {
42
- return true;
43
- }
44
- if (!infoEnabled) {
45
- return false;
46
- }
47
- if (phase === "getblock_archive_download" || phase === "getblock_archive_import") {
48
- return true;
49
- }
50
- return phase === "snapshot_download"
51
- || phase === "wait_headers_for_snapshot"
52
- || phase === "load_snapshot"
53
- || phase === "bitcoin_sync"
54
- || phase === "cogcoin_sync"
55
- || message.includes("Getblock manifest")
56
- || message.startsWith("Fetching Getblock manifest.")
57
- || message.startsWith("Refreshing Getblock manifest.")
58
- || message.startsWith("Using Getblock range ");
59
- }
60
- function formatDownloadLine(label, event) {
61
- const current = event.progress.downloadedBytes ?? 0;
62
- const total = event.progress.totalBytes ?? 0;
63
- const percent = event.progress.percent ?? (total > 0 ? (current / total) * 100 : 0);
64
- const speed = event.progress.bytesPerSecond === null ? "--" : `${formatBytes(event.progress.bytesPerSecond)}/s`;
65
- return `${label}: ${percent.toFixed(2)}% (${formatBytes(current)} / ${formatBytes(total)}, ${speed}, ETA ${formatDuration(event.progress.etaSeconds)})`;
66
- }
67
- return (event) => {
68
- const message = event.progress.message.trim();
69
- const phaseChanged = event.phase !== lastPhase;
70
- const messageChanged = message !== lastMessage;
71
- if ((phaseChanged || messageChanged) && shouldPrintEntryMessage(message, event.phase)) {
72
- options.write(message);
73
- }
74
- if (infoEnabled && event.phase === "getblock_archive_download") {
75
- const now = Date.now();
76
- const currentBytes = event.progress.downloadedBytes ?? 0;
77
- const isComplete = (event.progress.percent ?? 0) >= 100;
78
- const shouldPrintMilestone = phaseChanged
79
- || lastDownloadBytes !== currentBytes && (isComplete
80
- || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
81
- if (shouldPrintMilestone) {
82
- options.write(formatDownloadLine("Getblock download", event));
83
- lastDownloadPrintedAt = now;
84
- lastDownloadBytes = currentBytes;
85
- }
86
- }
87
- else if (infoEnabled && event.phase === "snapshot_download") {
88
- const now = Date.now();
89
- const currentBytes = event.progress.downloadedBytes ?? 0;
90
- const isComplete = (event.progress.percent ?? 0) >= 100;
91
- const shouldPrintMilestone = phaseChanged
92
- || lastDownloadBytes !== currentBytes && (isComplete
93
- || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
94
- if (shouldPrintMilestone) {
95
- options.write(formatDownloadLine("Snapshot download", event));
96
- lastDownloadPrintedAt = now;
97
- lastDownloadBytes = currentBytes;
98
- }
99
- }
100
- else if (infoEnabled && event.phase === "getblock_archive_import") {
101
- const now = Date.now();
102
- const currentBlocks = event.progress.blocks ?? 0;
103
- const targetBlocks = event.progress.targetHeight ?? currentBlocks;
104
- const isComplete = currentBlocks >= targetBlocks;
105
- const shouldPrintMilestone = phaseChanged
106
- || lastImportBlocks !== currentBlocks && (isComplete
107
- || now - lastImportPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
108
- if (shouldPrintMilestone) {
109
- options.write(`Getblock import: Bitcoin ${currentBlocks.toLocaleString()} / ${targetBlocks.toLocaleString()}`);
110
- lastImportPrintedAt = now;
111
- lastImportBlocks = currentBlocks;
112
- }
113
- }
114
- lastPhase = event.phase;
115
- lastMessage = message;
116
- };
117
- }
118
28
  export async function runSyncCommand(parsed, context) {
119
29
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
120
30
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
@@ -10,7 +10,7 @@ import { initializeWallet, deleteImportedWalletSeed, previewResetWallet, repairW
10
10
  import { resolveWalletRuntimePathsForTesting } from "../wallet/runtime.js";
11
11
  import { openWalletReadContext } from "../wallet/read/index.js";
12
12
  import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
13
- import { followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining, } from "../wallet/mining/index.js";
13
+ import { ensureBuiltInMiningSetupIfNeeded, followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining, } from "../wallet/mining/index.js";
14
14
  import { createLazyDefaultWalletSecretProvider } from "../wallet/state/provider.js";
15
15
  import { anchorDomain, transferBitcoin, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain, } from "../wallet/tx/index.js";
16
16
  import { createTerminalPrompter } from "./prompt.js";
@@ -80,6 +80,7 @@ export function createDefaultContext(overrides = {}) {
80
80
  giveReputation: overrides.giveReputation ?? giveReputation,
81
81
  revokeReputation: overrides.revokeReputation ?? revokeReputation,
82
82
  inspectMiningControlPlane: overrides.inspectMiningControlPlane ?? inspectMiningControlPlane,
83
+ ensureBuiltInMiningSetupIfNeeded: overrides.ensureBuiltInMiningSetupIfNeeded ?? ensureBuiltInMiningSetupIfNeeded,
83
84
  runForegroundMining: overrides.runForegroundMining ?? runForegroundMining,
84
85
  startBackgroundMining: overrides.startBackgroundMining ?? startBackgroundMining,
85
86
  stopBackgroundMining: overrides.stopBackgroundMining ?? stopBackgroundMining,
@@ -0,0 +1,6 @@
1
+ import type { ManagedBitcoindProgressEvent } from "../bitcoind/types.js";
2
+ import type { ParsedCliArgs } from "./types.js";
3
+ export declare function createSyncProgressReporter(options: {
4
+ progressOutput: ParsedCliArgs["progressOutput"];
5
+ write: (line: string) => void;
6
+ }): (event: ManagedBitcoindProgressEvent) => void;
@@ -0,0 +1,91 @@
1
+ import { formatBytes, formatDuration } from "../bitcoind/progress/formatting.js";
2
+ const SYNC_PROGRESS_LOG_INTERVAL_MS = 5_000;
3
+ export function createSyncProgressReporter(options) {
4
+ let lastPhase = null;
5
+ let lastMessage = "";
6
+ let lastDownloadPrintedAt = 0;
7
+ let lastDownloadBytes = null;
8
+ let lastImportPrintedAt = 0;
9
+ let lastImportBlocks = null;
10
+ const infoEnabled = options.progressOutput !== "none";
11
+ function shouldPrintEntryMessage(message, phase) {
12
+ if (message === "Waiting to start managed sync." || message === "Sync complete.") {
13
+ return false;
14
+ }
15
+ if (message.startsWith("Warning:")) {
16
+ return true;
17
+ }
18
+ if (!infoEnabled) {
19
+ return false;
20
+ }
21
+ if (phase === "getblock_archive_download" || phase === "getblock_archive_import") {
22
+ return true;
23
+ }
24
+ return phase === "snapshot_download"
25
+ || phase === "wait_headers_for_snapshot"
26
+ || phase === "load_snapshot"
27
+ || phase === "bitcoin_sync"
28
+ || phase === "cogcoin_sync"
29
+ || message.includes("Getblock manifest")
30
+ || message.startsWith("Fetching Getblock manifest.")
31
+ || message.startsWith("Refreshing Getblock manifest.")
32
+ || message.startsWith("Using Getblock range ");
33
+ }
34
+ function formatDownloadLine(label, event) {
35
+ const current = event.progress.downloadedBytes ?? 0;
36
+ const total = event.progress.totalBytes ?? 0;
37
+ const percent = event.progress.percent ?? (total > 0 ? (current / total) * 100 : 0);
38
+ const speed = event.progress.bytesPerSecond === null ? "--" : `${formatBytes(event.progress.bytesPerSecond)}/s`;
39
+ return `${label}: ${percent.toFixed(2)}% (${formatBytes(current)} / ${formatBytes(total)}, ${speed}, ETA ${formatDuration(event.progress.etaSeconds)})`;
40
+ }
41
+ return (event) => {
42
+ const message = event.progress.message.trim();
43
+ const phaseChanged = event.phase !== lastPhase;
44
+ const messageChanged = message !== lastMessage;
45
+ if ((phaseChanged || messageChanged) && shouldPrintEntryMessage(message, event.phase)) {
46
+ options.write(message);
47
+ }
48
+ if (infoEnabled && event.phase === "getblock_archive_download") {
49
+ const now = Date.now();
50
+ const currentBytes = event.progress.downloadedBytes ?? 0;
51
+ const isComplete = (event.progress.percent ?? 0) >= 100;
52
+ const shouldPrintMilestone = phaseChanged
53
+ || lastDownloadBytes !== currentBytes && (isComplete
54
+ || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
55
+ if (shouldPrintMilestone) {
56
+ options.write(formatDownloadLine("Getblock download", event));
57
+ lastDownloadPrintedAt = now;
58
+ lastDownloadBytes = currentBytes;
59
+ }
60
+ }
61
+ else if (infoEnabled && event.phase === "snapshot_download") {
62
+ const now = Date.now();
63
+ const currentBytes = event.progress.downloadedBytes ?? 0;
64
+ const isComplete = (event.progress.percent ?? 0) >= 100;
65
+ const shouldPrintMilestone = phaseChanged
66
+ || lastDownloadBytes !== currentBytes && (isComplete
67
+ || now - lastDownloadPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
68
+ if (shouldPrintMilestone) {
69
+ options.write(formatDownloadLine("Snapshot download", event));
70
+ lastDownloadPrintedAt = now;
71
+ lastDownloadBytes = currentBytes;
72
+ }
73
+ }
74
+ else if (infoEnabled && event.phase === "getblock_archive_import") {
75
+ const now = Date.now();
76
+ const currentBlocks = event.progress.blocks ?? 0;
77
+ const targetBlocks = event.progress.targetHeight ?? currentBlocks;
78
+ const isComplete = currentBlocks >= targetBlocks;
79
+ const shouldPrintMilestone = phaseChanged
80
+ || lastImportBlocks !== currentBlocks && (isComplete
81
+ || now - lastImportPrintedAt >= SYNC_PROGRESS_LOG_INTERVAL_MS);
82
+ if (shouldPrintMilestone) {
83
+ options.write(`Getblock import: Bitcoin ${currentBlocks.toLocaleString()} / ${targetBlocks.toLocaleString()}`);
84
+ lastImportPrintedAt = now;
85
+ lastImportBlocks = currentBlocks;
86
+ }
87
+ }
88
+ lastPhase = event.phase;
89
+ lastMessage = message;
90
+ };
91
+ }
@@ -10,7 +10,7 @@ import type { WalletPrompter, initializeWallet, deleteImportedWalletSeed, previe
10
10
  import type { openWalletReadContext } from "../wallet/read/index.js";
11
11
  import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
12
12
  import type { WalletSecretProvider } from "../wallet/state/provider.js";
13
- import type { followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining } from "../wallet/mining/index.js";
13
+ import type { ensureBuiltInMiningSetupIfNeeded, followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining } from "../wallet/mining/index.js";
14
14
  import type { anchorDomain, transferBitcoin, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain } from "../wallet/tx/index.js";
15
15
  export type ProgressOutput = "auto" | "tty" | "none";
16
16
  export type OutputMode = "text" | "json" | "preview-json";
@@ -145,6 +145,7 @@ export interface CliRunnerContext {
145
145
  giveReputation?: typeof giveReputation;
146
146
  revokeReputation?: typeof revokeReputation;
147
147
  inspectMiningControlPlane?: typeof inspectMiningControlPlane;
148
+ ensureBuiltInMiningSetupIfNeeded?: typeof ensureBuiltInMiningSetupIfNeeded;
148
149
  runForegroundMining?: typeof runForegroundMining;
149
150
  startBackgroundMining?: typeof startBackgroundMining;
150
151
  stopBackgroundMining?: typeof stopBackgroundMining;
@@ -558,6 +558,9 @@ function getBalanceNextSteps(context) {
558
558
  return [`Transfer BTC to ${context.model.walletAddress ?? "this wallet address"} so your anchored root domain can keep mining.`];
559
559
  }
560
560
  if (context.fundingSpendableSats !== null && context.fundingSpendableSats > BALANCE_MINING_THRESHOLD_SATS) {
561
+ if (context.mining?.provider.status === "missing") {
562
+ return ["Run `cogcoin mine setup` to configure your mining provider."];
563
+ }
561
564
  return ["Run `cogcoin mine` to start mining with your anchored root domain."];
562
565
  }
563
566
  return [];
@@ -1,6 +1,6 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
2
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
3
- export { runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, type MiningStartResult, } from "./runner.js";
3
+ export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, type MiningStartResult, } from "./runner.js";
4
4
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
5
5
  export type { MiningSentenceCandidateV1, MiningSentenceGenerationRequestV1, MiningSentenceGenerationResponseV1, } from "./sentence-protocol.js";
6
6
  export { loadClientConfig, saveBuiltInMiningProviderConfig, saveClientConfig, } from "./config.js";
@@ -1,5 +1,5 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
2
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
3
- export { runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, } from "./runner.js";
3
+ export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, } from "./runner.js";
4
4
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
5
5
  export { loadClientConfig, saveBuiltInMiningProviderConfig, saveClientConfig, } from "./config.js";
@@ -137,6 +137,7 @@ export interface RunForegroundMiningOptions extends RunnerDependencies {
137
137
  databasePath: string;
138
138
  provider?: WalletSecretProvider;
139
139
  prompter: WalletPrompter;
140
+ builtInSetupEnsured?: boolean;
140
141
  stdout?: {
141
142
  write(chunk: string): void;
142
143
  };
@@ -154,6 +155,7 @@ export interface StartBackgroundMiningOptions extends RunnerDependencies {
154
155
  databasePath: string;
155
156
  provider?: WalletSecretProvider;
156
157
  prompter: WalletPrompter;
158
+ builtInSetupEnsured?: boolean;
157
159
  paths?: WalletRuntimePaths;
158
160
  }
159
161
  export interface StopBackgroundMiningOptions extends RunnerDependencies {
@@ -276,6 +278,11 @@ export declare function publishCandidateForTesting(options: {
276
278
  publishAttempt?: typeof publishCandidateOnce;
277
279
  appendEventFn?: typeof appendEvent;
278
280
  }): Promise<MiningPublishOutcome>;
281
+ export declare function ensureBuiltInMiningSetupIfNeeded(options: {
282
+ provider: WalletSecretProvider;
283
+ prompter: WalletPrompter;
284
+ paths: WalletRuntimePaths;
285
+ }): Promise<boolean>;
279
286
  export declare function runForegroundMining(options: RunForegroundMiningOptions): Promise<void>;
280
287
  export declare function startBackgroundMining(options: StartBackgroundMiningOptions): Promise<MiningStartResult>;
281
288
  export declare function stopBackgroundMining(options: StopBackgroundMiningOptions): Promise<MiningRuntimeStatusV1 | null>;
@@ -1811,7 +1811,7 @@ async function publishCandidate(options) {
1811
1811
  export async function publishCandidateForTesting(options) {
1812
1812
  return await publishCandidate(options);
1813
1813
  }
1814
- async function ensureBuiltInSetupIfNeeded(options) {
1814
+ export async function ensureBuiltInMiningSetupIfNeeded(options) {
1815
1815
  const config = await loadClientConfig({
1816
1816
  path: options.paths.clientConfigPath,
1817
1817
  provider: options.provider,
@@ -2732,11 +2732,13 @@ export async function runForegroundMining(options) {
2732
2732
  if (existing?.runMode === "background") {
2733
2733
  throw new Error("Background mining is already active. Run `cogcoin mine stop` first.");
2734
2734
  }
2735
- const setupReady = await ensureBuiltInSetupIfNeeded({
2736
- provider,
2737
- prompter: options.prompter,
2738
- paths,
2739
- });
2735
+ const setupReady = options.builtInSetupEnsured === true
2736
+ ? true
2737
+ : await ensureBuiltInMiningSetupIfNeeded({
2738
+ provider,
2739
+ prompter: options.prompter,
2740
+ paths,
2741
+ });
2740
2742
  if (!setupReady) {
2741
2743
  throw new Error("Built-in mining provider is not configured. Run `cogcoin mine setup`.");
2742
2744
  }
@@ -2801,11 +2803,13 @@ export async function startBackgroundMining(options) {
2801
2803
  if (existing?.runMode === "foreground") {
2802
2804
  throw new Error("Foreground mining is already active. Interrupt that process directly.");
2803
2805
  }
2804
- const setupReady = await ensureBuiltInSetupIfNeeded({
2805
- provider,
2806
- prompter: options.prompter,
2807
- paths,
2808
- });
2806
+ const setupReady = options.builtInSetupEnsured === true
2807
+ ? true
2808
+ : await ensureBuiltInMiningSetupIfNeeded({
2809
+ provider,
2810
+ prompter: options.prompter,
2811
+ paths,
2812
+ });
2809
2813
  if (!setupReady) {
2810
2814
  throw new Error("Built-in mining provider is not configured. Run `cogcoin mine setup`.");
2811
2815
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",