@cogcoin/client 0.5.3 → 0.5.5

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 (70) hide show
  1. package/README.md +11 -3
  2. package/dist/app-paths.d.ts +2 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/art/wallet.txt +10 -0
  5. package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
  6. package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
  7. package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
  8. package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
  9. package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
  10. package/dist/bitcoind/bootstrap/constants.js +3 -1
  11. package/dist/bitcoind/bootstrap/controller.d.ts +6 -1
  12. package/dist/bitcoind/bootstrap/controller.js +14 -7
  13. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
  14. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
  15. package/dist/bitcoind/bootstrap/download.js +177 -83
  16. package/dist/bitcoind/bootstrap/headers.d.ts +4 -2
  17. package/dist/bitcoind/bootstrap/headers.js +29 -4
  18. package/dist/bitcoind/bootstrap/state.d.ts +11 -1
  19. package/dist/bitcoind/bootstrap/state.js +50 -23
  20. package/dist/bitcoind/bootstrap/types.d.ts +12 -1
  21. package/dist/bitcoind/client/internal-types.d.ts +1 -0
  22. package/dist/bitcoind/client/managed-client.js +27 -13
  23. package/dist/bitcoind/client/sync-engine.js +42 -5
  24. package/dist/bitcoind/errors.js +9 -0
  25. package/dist/bitcoind/indexer-daemon.d.ts +9 -0
  26. package/dist/bitcoind/indexer-daemon.js +51 -14
  27. package/dist/bitcoind/service.d.ts +9 -0
  28. package/dist/bitcoind/service.js +65 -24
  29. package/dist/bitcoind/testing.d.ts +2 -2
  30. package/dist/bitcoind/testing.js +2 -2
  31. package/dist/bitcoind/types.d.ts +9 -0
  32. package/dist/cli/commands/service-runtime.d.ts +2 -0
  33. package/dist/cli/commands/service-runtime.js +432 -0
  34. package/dist/cli/commands/wallet-admin.js +227 -132
  35. package/dist/cli/commands/wallet-mutation.js +597 -580
  36. package/dist/cli/context.js +23 -1
  37. package/dist/cli/mutation-json.d.ts +17 -1
  38. package/dist/cli/mutation-json.js +42 -0
  39. package/dist/cli/output.js +113 -2
  40. package/dist/cli/parse.d.ts +1 -1
  41. package/dist/cli/parse.js +65 -0
  42. package/dist/cli/preview-json.d.ts +19 -1
  43. package/dist/cli/preview-json.js +31 -0
  44. package/dist/cli/prompt.js +40 -12
  45. package/dist/cli/runner.js +12 -0
  46. package/dist/cli/signals.d.ts +1 -0
  47. package/dist/cli/signals.js +44 -0
  48. package/dist/cli/types.d.ts +24 -2
  49. package/dist/cli/types.js +6 -0
  50. package/dist/cli/wallet-format.js +3 -0
  51. package/dist/cli/workflow-hints.d.ts +1 -0
  52. package/dist/cli/workflow-hints.js +3 -0
  53. package/dist/wallet/fs/lock.d.ts +2 -0
  54. package/dist/wallet/fs/lock.js +32 -0
  55. package/dist/wallet/lifecycle.d.ts +19 -1
  56. package/dist/wallet/lifecycle.js +315 -8
  57. package/dist/wallet/material.d.ts +2 -0
  58. package/dist/wallet/material.js +8 -1
  59. package/dist/wallet/mnemonic-art.d.ts +2 -0
  60. package/dist/wallet/mnemonic-art.js +54 -0
  61. package/dist/wallet/reset.d.ts +61 -0
  62. package/dist/wallet/reset.js +781 -0
  63. package/dist/wallet/runtime.d.ts +2 -0
  64. package/dist/wallet/runtime.js +2 -0
  65. package/dist/wallet/state/pending-init.d.ts +24 -0
  66. package/dist/wallet/state/pending-init.js +59 -0
  67. package/dist/wallet/state/provider.d.ts +1 -0
  68. package/dist/wallet/state/provider.js +7 -1
  69. package/dist/wallet/types.d.ts +8 -0
  70. package/package.json +6 -4
@@ -0,0 +1,432 @@
1
+ import { dirname } from "node:path";
2
+ import { loadBundledGenesisParameters } from "@cogcoin/indexer";
3
+ import { UNINITIALIZED_WALLET_ROOT_ID, resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
4
+ import { writeLine } from "../io.js";
5
+ import { createSuccessEnvelope, describeCanonicalCommand, writeHandledCliError, writeJsonValue, } from "../output.js";
6
+ function formatBool(value) {
7
+ return value === null ? "unknown" : (value ? "yes" : "no");
8
+ }
9
+ function formatMaybe(value) {
10
+ return value === null || value === undefined ? "unavailable" : String(value);
11
+ }
12
+ function formatCompatibility(value) {
13
+ return value.replaceAll("-", " ");
14
+ }
15
+ async function resolveEffectiveWalletRootId(dataDir, context) {
16
+ const paths = context.resolveWalletRuntimePaths();
17
+ try {
18
+ const loaded = await context.loadWalletState({
19
+ primaryPath: paths.walletStatePath,
20
+ backupPath: paths.walletStateBackupPath,
21
+ }, {
22
+ provider: context.walletSecretProvider,
23
+ });
24
+ return {
25
+ walletRootId: loaded.state.walletRootId,
26
+ source: "wallet-state",
27
+ };
28
+ }
29
+ catch {
30
+ // fall through
31
+ }
32
+ try {
33
+ const unlockSession = await context.loadUnlockSession(paths.walletUnlockSessionPath, {
34
+ provider: context.walletSecretProvider,
35
+ });
36
+ return {
37
+ walletRootId: unlockSession.walletRootId,
38
+ source: "unlock-session",
39
+ };
40
+ }
41
+ catch {
42
+ // fall through
43
+ }
44
+ try {
45
+ const explicitLock = await context.loadWalletExplicitLock(paths.walletExplicitLockPath);
46
+ if (explicitLock?.walletRootId) {
47
+ return {
48
+ walletRootId: explicitLock.walletRootId,
49
+ source: "explicit-lock",
50
+ };
51
+ }
52
+ }
53
+ catch {
54
+ // fall through
55
+ }
56
+ const fallbackProbe = await context.probeManagedBitcoindService({
57
+ dataDir,
58
+ chain: "main",
59
+ startHeight: 0,
60
+ walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
61
+ });
62
+ if (fallbackProbe.status?.walletRootId) {
63
+ return {
64
+ walletRootId: fallbackProbe.status.walletRootId,
65
+ source: "bitcoind-status",
66
+ };
67
+ }
68
+ return {
69
+ walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
70
+ source: "default-uninitialized",
71
+ };
72
+ }
73
+ async function inspectManagedBitcoindStatus(dataDir, context) {
74
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
75
+ const probe = await context.probeManagedBitcoindService({
76
+ dataDir,
77
+ chain: "main",
78
+ startHeight: 0,
79
+ walletRootId: resolution.walletRootId,
80
+ });
81
+ let node = null;
82
+ let nodeError = null;
83
+ if (probe.compatibility === "compatible" && probe.status !== null) {
84
+ try {
85
+ const rpc = context.createBitcoinRpcClient(probe.status.rpc);
86
+ const [blockchainInfo, networkInfo] = await Promise.all([
87
+ rpc.getBlockchainInfo(),
88
+ rpc.getNetworkInfo(),
89
+ ]);
90
+ node = {
91
+ bestHeight: blockchainInfo.blocks,
92
+ headerHeight: blockchainInfo.headers,
93
+ bestHash: blockchainInfo.bestblockhash,
94
+ verificationProgress: blockchainInfo.verificationprogress ?? null,
95
+ initialBlockDownload: blockchainInfo.initialblockdownload ?? null,
96
+ networkActive: networkInfo.networkactive,
97
+ connections: networkInfo.connections,
98
+ inboundConnections: networkInfo.connections_in ?? null,
99
+ outboundConnections: networkInfo.connections_out ?? null,
100
+ };
101
+ }
102
+ catch (error) {
103
+ nodeError = error instanceof Error ? error.message : String(error);
104
+ }
105
+ }
106
+ return {
107
+ dataDir,
108
+ walletRootId: resolution.walletRootId,
109
+ walletRootSource: resolution.source,
110
+ compatibility: probe.compatibility,
111
+ service: probe.status,
112
+ node,
113
+ nodeError,
114
+ };
115
+ }
116
+ async function inspectManagedIndexerStatus(dataDir, context) {
117
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
118
+ const runtimeRoot = resolveManagedServicePaths(dataDir, resolution.walletRootId).walletRuntimeRoot;
119
+ const probe = await context.probeIndexerDaemon({
120
+ dataDir,
121
+ walletRootId: resolution.walletRootId,
122
+ });
123
+ let source = "probe";
124
+ let daemon = probe.status;
125
+ if (probe.compatibility === "unreachable") {
126
+ daemon = await context.readObservedIndexerDaemonStatus({
127
+ dataDir,
128
+ walletRootId: resolution.walletRootId,
129
+ });
130
+ source = daemon === null ? "none" : "status-file";
131
+ }
132
+ return {
133
+ dataDir,
134
+ walletRootId: resolution.walletRootId,
135
+ walletRootSource: resolution.source,
136
+ compatibility: probe.compatibility,
137
+ source,
138
+ daemon: daemon === null
139
+ ? null
140
+ : {
141
+ ...daemon,
142
+ runtimeRoot,
143
+ },
144
+ };
145
+ }
146
+ function formatBitcoinStatusReport(payload) {
147
+ const lines = [
148
+ "Managed Bitcoind Status",
149
+ `Bitcoin datadir: ${payload.dataDir}`,
150
+ `Wallet root: ${payload.walletRootId}`,
151
+ `Wallet root source: ${payload.walletRootSource}`,
152
+ `Compatibility: ${formatCompatibility(payload.compatibility)}`,
153
+ ];
154
+ if (payload.service !== null) {
155
+ lines.push(`Service state: ${payload.service.state}`);
156
+ lines.push(`Process id: ${formatMaybe(payload.service.processId)}`);
157
+ lines.push(`Service instance: ${payload.service.serviceInstanceId}`);
158
+ lines.push(`Runtime root: ${payload.service.runtimeRoot}`);
159
+ lines.push(`Chain: ${payload.service.chain}`);
160
+ lines.push(`RPC: ${payload.service.rpc.url}`);
161
+ lines.push(`RPC cookie: ${payload.service.rpc.cookieFile}`);
162
+ lines.push(`ZMQ: ${payload.service.zmq.endpoint}`);
163
+ lines.push(`P2P port: ${payload.service.p2pPort}`);
164
+ lines.push(`Started at: ${payload.service.startedAtUnixMs}`);
165
+ lines.push(`Heartbeat at: ${payload.service.heartbeatAtUnixMs}`);
166
+ lines.push(`Updated at: ${payload.service.updatedAtUnixMs}`);
167
+ lines.push(`Managed Core wallet: ${payload.service.walletReplica?.proofStatus ?? "unavailable"}`);
168
+ if (payload.service.lastError !== null) {
169
+ lines.push(`Service error: ${payload.service.lastError}`);
170
+ }
171
+ }
172
+ else {
173
+ lines.push("Service state: unavailable");
174
+ }
175
+ if (payload.node !== null) {
176
+ lines.push(`Bitcoin best height: ${payload.node.bestHeight}`);
177
+ lines.push(`Bitcoin headers: ${payload.node.headerHeight}`);
178
+ lines.push(`Bitcoin best hash: ${payload.node.bestHash}`);
179
+ lines.push(`Verification progress: ${formatMaybe(payload.node.verificationProgress)}`);
180
+ lines.push(`Initial block download: ${formatBool(payload.node.initialBlockDownload)}`);
181
+ lines.push(`Network active: ${formatBool(payload.node.networkActive)}`);
182
+ lines.push(`Connections: ${payload.node.connections}`);
183
+ lines.push(`Inbound connections: ${formatMaybe(payload.node.inboundConnections)}`);
184
+ lines.push(`Outbound connections: ${formatMaybe(payload.node.outboundConnections)}`);
185
+ }
186
+ else {
187
+ lines.push("Bitcoin node: unavailable");
188
+ }
189
+ if (payload.nodeError !== null) {
190
+ lines.push(`Node error: ${payload.nodeError}`);
191
+ }
192
+ if (payload.compatibility === "unreachable") {
193
+ lines.push("Recommended next step: Run `cogcoin bitcoin start` to start the managed Bitcoin service.");
194
+ }
195
+ return `${lines.join("\n")}\n`;
196
+ }
197
+ function formatIndexerStatusReport(payload) {
198
+ const lines = [
199
+ "Managed Indexer Status",
200
+ `Bitcoin datadir: ${payload.dataDir}`,
201
+ `Wallet root: ${payload.walletRootId}`,
202
+ `Wallet root source: ${payload.walletRootSource}`,
203
+ `Compatibility: ${formatCompatibility(payload.compatibility)}`,
204
+ `Observed source: ${payload.source}`,
205
+ ];
206
+ if (payload.daemon !== null) {
207
+ lines.push(`Daemon state: ${payload.daemon.state}`);
208
+ lines.push(`Process id: ${formatMaybe(payload.daemon.processId)}`);
209
+ lines.push(`Daemon instance: ${payload.daemon.daemonInstanceId}`);
210
+ lines.push(`Runtime root: ${payload.daemon.runtimeRoot}`);
211
+ lines.push(`Schema version: ${payload.daemon.schemaVersion}`);
212
+ lines.push(`Started at: ${payload.daemon.startedAtUnixMs}`);
213
+ lines.push(`Heartbeat at: ${payload.daemon.heartbeatAtUnixMs}`);
214
+ lines.push(`Updated at: ${payload.daemon.updatedAtUnixMs}`);
215
+ lines.push(`IPC ready: ${formatBool(payload.daemon.ipcReady)}`);
216
+ lines.push(`RPC reachable: ${formatBool(payload.daemon.rpcReachable)}`);
217
+ lines.push(`Core best height: ${formatMaybe(payload.daemon.coreBestHeight)}`);
218
+ lines.push(`Core best hash: ${formatMaybe(payload.daemon.coreBestHash)}`);
219
+ lines.push(`Applied tip height: ${formatMaybe(payload.daemon.appliedTipHeight)}`);
220
+ lines.push(`Applied tip hash: ${formatMaybe(payload.daemon.appliedTipHash)}`);
221
+ lines.push(`Snapshot sequence: ${formatMaybe(payload.daemon.snapshotSeq)}`);
222
+ lines.push(`Backlog blocks: ${formatMaybe(payload.daemon.backlogBlocks)}`);
223
+ lines.push(`Reorg depth: ${formatMaybe(payload.daemon.reorgDepth)}`);
224
+ lines.push(`Active snapshots: ${payload.daemon.activeSnapshotCount}`);
225
+ lines.push(`Last applied at: ${formatMaybe(payload.daemon.lastAppliedAtUnixMs)}`);
226
+ if (payload.daemon.lastError !== null) {
227
+ lines.push(`Daemon error: ${payload.daemon.lastError}`);
228
+ }
229
+ }
230
+ else {
231
+ lines.push("Daemon state: unavailable");
232
+ }
233
+ if (payload.compatibility === "unreachable") {
234
+ lines.push("Recommended next step: Run `cogcoin indexer start` to start the managed Cogcoin indexer.");
235
+ }
236
+ return `${lines.join("\n")}\n`;
237
+ }
238
+ function buildStatusMessages(payload) {
239
+ const warnings = [];
240
+ const explanations = [];
241
+ const nextSteps = [];
242
+ if (payload.compatibility !== "compatible") {
243
+ warnings.push(`Managed service compatibility is ${payload.compatibility}.`);
244
+ }
245
+ if ("nodeError" in payload && payload.nodeError !== null) {
246
+ explanations.push(payload.nodeError);
247
+ }
248
+ if ("service" in payload && payload.service?.lastError) {
249
+ explanations.push(payload.service.lastError);
250
+ }
251
+ if ("daemon" in payload && payload.daemon?.lastError) {
252
+ explanations.push(payload.daemon.lastError);
253
+ }
254
+ if ("service" in payload && payload.compatibility === "unreachable") {
255
+ nextSteps.push("Run `cogcoin bitcoin start` to start the managed Bitcoin service.");
256
+ }
257
+ if ("daemon" in payload && payload.compatibility === "unreachable") {
258
+ nextSteps.push("Run `cogcoin indexer start` to start the managed Cogcoin indexer.");
259
+ }
260
+ return {
261
+ warnings,
262
+ explanations,
263
+ nextSteps,
264
+ };
265
+ }
266
+ export async function runServiceRuntimeCommand(parsed, context) {
267
+ try {
268
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
269
+ if (parsed.command === "bitcoin-status") {
270
+ const payload = await inspectManagedBitcoindStatus(dataDir, context);
271
+ const messages = buildStatusMessages(payload);
272
+ if (parsed.outputMode === "json") {
273
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/bitcoin-status/v1", describeCanonicalCommand(parsed), payload, messages));
274
+ return 0;
275
+ }
276
+ context.stdout.write(formatBitcoinStatusReport(payload));
277
+ return 0;
278
+ }
279
+ if (parsed.command === "indexer-status") {
280
+ const payload = await inspectManagedIndexerStatus(dataDir, context);
281
+ const messages = buildStatusMessages(payload);
282
+ if (parsed.outputMode === "json") {
283
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/indexer-status/v1", describeCanonicalCommand(parsed), payload, messages));
284
+ return 0;
285
+ }
286
+ context.stdout.write(formatIndexerStatusReport(payload));
287
+ return 0;
288
+ }
289
+ if (parsed.command === "bitcoin-start") {
290
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
291
+ const probe = await context.probeManagedBitcoindService({
292
+ dataDir,
293
+ chain: "main",
294
+ startHeight: 0,
295
+ walletRootId: resolution.walletRootId,
296
+ });
297
+ const genesis = await loadBundledGenesisParameters();
298
+ await context.attachManagedBitcoindService({
299
+ dataDir,
300
+ chain: "main",
301
+ startHeight: genesis.genesisBlock,
302
+ walletRootId: resolution.walletRootId,
303
+ });
304
+ const bitcoindStatus = probe.compatibility === "compatible" ? "already-running" : "started";
305
+ const payload = {
306
+ dataDir,
307
+ walletRootId: resolution.walletRootId,
308
+ walletRootSource: resolution.source,
309
+ bitcoind: {
310
+ status: bitcoindStatus,
311
+ },
312
+ };
313
+ if (parsed.outputMode === "json") {
314
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/bitcoin-start/v1", describeCanonicalCommand(parsed), payload, {
315
+ nextSteps: [
316
+ "Run `cogcoin bitcoin status` to inspect the managed Bitcoin node.",
317
+ "Run `cogcoin indexer start` or `cogcoin sync` when you want the managed Cogcoin indexer.",
318
+ ],
319
+ }));
320
+ return 0;
321
+ }
322
+ writeLine(context.stdout, bitcoindStatus === "already-running" ? "Managed bitcoind already running." : "Managed bitcoind started.");
323
+ writeLine(context.stdout, `Wallet root: ${resolution.walletRootId}`);
324
+ return 0;
325
+ }
326
+ if (parsed.command === "bitcoin-stop") {
327
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
328
+ const indexer = await context.stopIndexerDaemonService({
329
+ dataDir,
330
+ walletRootId: resolution.walletRootId,
331
+ });
332
+ const bitcoind = await context.stopManagedBitcoindService({
333
+ dataDir,
334
+ walletRootId: resolution.walletRootId,
335
+ });
336
+ const payload = {
337
+ dataDir,
338
+ walletRootId: resolution.walletRootId,
339
+ walletRootSource: resolution.source,
340
+ bitcoind,
341
+ indexer,
342
+ };
343
+ if (parsed.outputMode === "json") {
344
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/bitcoin-stop/v1", describeCanonicalCommand(parsed), payload));
345
+ return 0;
346
+ }
347
+ writeLine(context.stdout, bitcoind.status === "stopped" ? "Managed bitcoind stopped." : "Managed bitcoind already stopped.");
348
+ writeLine(context.stdout, indexer.status === "stopped" ? "Paired indexer stopped." : "Paired indexer already stopped.");
349
+ return 0;
350
+ }
351
+ if (parsed.command === "indexer-start") {
352
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
353
+ const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
354
+ await context.ensureDirectory(dirname(dbPath));
355
+ const genesis = await loadBundledGenesisParameters();
356
+ const bitcoindProbe = await context.probeManagedBitcoindService({
357
+ dataDir,
358
+ chain: "main",
359
+ startHeight: 0,
360
+ walletRootId: resolution.walletRootId,
361
+ });
362
+ await context.attachManagedBitcoindService({
363
+ dataDir,
364
+ chain: "main",
365
+ startHeight: genesis.genesisBlock,
366
+ walletRootId: resolution.walletRootId,
367
+ });
368
+ const indexerProbe = await context.probeIndexerDaemon({
369
+ dataDir,
370
+ walletRootId: resolution.walletRootId,
371
+ });
372
+ await context.attachIndexerDaemon({
373
+ dataDir,
374
+ databasePath: dbPath,
375
+ walletRootId: resolution.walletRootId,
376
+ });
377
+ const payload = {
378
+ dataDir,
379
+ databasePath: dbPath,
380
+ walletRootId: resolution.walletRootId,
381
+ walletRootSource: resolution.source,
382
+ bitcoind: {
383
+ status: bitcoindProbe.compatibility === "compatible" ? "already-running" : "started",
384
+ },
385
+ indexer: {
386
+ status: indexerProbe.compatibility === "compatible" ? "already-running" : "started",
387
+ },
388
+ };
389
+ if (parsed.outputMode === "json") {
390
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/indexer-start/v1", describeCanonicalCommand(parsed), payload, {
391
+ nextSteps: [
392
+ "Run `cogcoin indexer status` to inspect the managed Cogcoin indexer.",
393
+ ],
394
+ }));
395
+ return 0;
396
+ }
397
+ writeLine(context.stdout, payload.indexer.status === "already-running" ? "Managed indexer already running." : "Managed indexer started.");
398
+ if (payload.bitcoind.status === "started") {
399
+ writeLine(context.stdout, "Managed bitcoind started automatically.");
400
+ }
401
+ return 0;
402
+ }
403
+ if (parsed.command === "indexer-stop") {
404
+ const resolution = await resolveEffectiveWalletRootId(dataDir, context);
405
+ const indexer = await context.stopIndexerDaemonService({
406
+ dataDir,
407
+ walletRootId: resolution.walletRootId,
408
+ });
409
+ const payload = {
410
+ dataDir,
411
+ walletRootId: resolution.walletRootId,
412
+ walletRootSource: resolution.source,
413
+ indexer,
414
+ };
415
+ if (parsed.outputMode === "json") {
416
+ writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/indexer-stop/v1", describeCanonicalCommand(parsed), payload));
417
+ return 0;
418
+ }
419
+ writeLine(context.stdout, indexer.status === "stopped" ? "Managed indexer stopped." : "Managed indexer already stopped.");
420
+ return 0;
421
+ }
422
+ throw new Error(`service runtime command not implemented: ${parsed.command}`);
423
+ }
424
+ catch (error) {
425
+ return writeHandledCliError({
426
+ parsed,
427
+ stdout: context.stdout,
428
+ stderr: context.stderr,
429
+ error,
430
+ });
431
+ }
432
+ }