@toon-protocol/townhouse 0.1.0-rc5

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/dist/cli.js ADDED
@@ -0,0 +1,684 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+ import {
4
+ ConnectorAdminClient,
5
+ DEFAULT_ATOR_PROXY,
6
+ DockerOrchestrator,
7
+ TransportProbe,
8
+ WalletManager,
9
+ createApiServer,
10
+ createWizardApiServer,
11
+ decryptWallet,
12
+ encryptWallet,
13
+ getDefaultConfig,
14
+ loadConfig,
15
+ loadWallet,
16
+ saveWallet
17
+ } from "./chunk-IB6TNCUQ.js";
18
+ import "./chunk-UTFWPLTB.js";
19
+
20
+ // src/cli.ts
21
+ import { parseArgs } from "util";
22
+ import { mkdirSync, writeFileSync, existsSync } from "fs";
23
+ import { join, resolve } from "path";
24
+ import { homedir } from "os";
25
+ import { pathToFileURL } from "url";
26
+ import { stringify } from "yaml";
27
+ import Docker from "dockerode";
28
+
29
+ // src/cli/browser-opener.ts
30
+ import { spawn } from "child_process";
31
+ var RealBrowserOpener = class {
32
+ async open(url) {
33
+ let cmd;
34
+ let args;
35
+ switch (process.platform) {
36
+ case "darwin":
37
+ cmd = "open";
38
+ args = [url];
39
+ break;
40
+ case "win32":
41
+ cmd = "cmd";
42
+ args = ["/c", "start", "", url];
43
+ break;
44
+ default:
45
+ cmd = "xdg-open";
46
+ args = [url];
47
+ break;
48
+ }
49
+ return new Promise((resolve2) => {
50
+ let settled = false;
51
+ const settle = () => {
52
+ if (settled) return;
53
+ settled = true;
54
+ resolve2();
55
+ };
56
+ try {
57
+ const child = spawn(cmd, args, {
58
+ stdio: ["ignore", "ignore", "ignore"],
59
+ detached: true
60
+ });
61
+ child.once("error", (err) => {
62
+ console.warn(
63
+ `[Townhouse] Could not open browser via ${cmd}: ${err.message}`
64
+ );
65
+ settle();
66
+ });
67
+ child.once("spawn", () => {
68
+ child.unref();
69
+ settle();
70
+ });
71
+ } catch (err) {
72
+ const msg = err instanceof Error ? err.message : String(err);
73
+ console.warn(`[Townhouse] Could not open browser: ${msg}`);
74
+ settle();
75
+ }
76
+ });
77
+ }
78
+ };
79
+
80
+ // src/cli.ts
81
+ var CliHelpRequested = class extends Error {
82
+ constructor() {
83
+ super(HELP_TEXT);
84
+ this.name = "CliHelpRequested";
85
+ }
86
+ };
87
+ var HELP_TEXT = `townhouse \u2014 TOON node orchestrator
88
+
89
+ Usage:
90
+ townhouse setup [--no-browser] [--port <n>] [--config-dir <dir>] Run the first-run setup wizard
91
+ townhouse init [--force] [--config-dir <dir>] [--password <pw>] [--preset <name>] [--yes] Initialize config + wallet
92
+ townhouse up [--town] [--mill] [--dvm] [-c <path>] [--password <pw>] Start nodes
93
+ townhouse down [-c <path>] Stop all nodes
94
+ townhouse status [-c <path>] Show node status
95
+ townhouse metrics [-c <path>] Show connector metrics
96
+ townhouse wallet show [-c <path>] [--password <pw>] Show derived addresses
97
+ townhouse --help Show this help
98
+
99
+ Flags:
100
+ --town Start Town (Nostr relay) node
101
+ --mill Start Mill (swap) node
102
+ --dvm Start DVM (compute) node
103
+ --password Wallet password (non-interactive mode)
104
+ --no-browser Skip opening the browser automatically (setup command)
105
+ --port Override the API port (setup command, default 9400)
106
+ --preset Init from a named preset (init only). Supported: demo
107
+ --yes Non-interactive (init only); with --preset=demo uses demo password if --password absent
108
+ If no flags given, starts all enabled nodes from config.`;
109
+ var DEFAULT_CONFIG_DIR = join(homedir(), ".townhouse");
110
+ var DEFAULT_CONFIG_PATH = join(DEFAULT_CONFIG_DIR, "config.yaml");
111
+ async function handleInit(force, configDir, password, preset, yes) {
112
+ const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);
113
+ const configPath = join(dir, "config.yaml");
114
+ if (existsSync(configPath) && !force) {
115
+ console.error(
116
+ `Config already exists at ${configPath}. Use --force to overwrite.`
117
+ );
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+ mkdirSync(dir, { recursive: true, mode: 448 });
122
+ let configToWrite;
123
+ if (preset === "demo") {
124
+ const { buildDemoConfig, DEMO_DETERMINISTIC_PASSWORD } = await import("./demo-MJR47QHZ.js");
125
+ configToWrite = buildDemoConfig({ walletPath: join(dir, "wallet.enc") });
126
+ if (yes && !password) {
127
+ password = DEMO_DETERMINISTIC_PASSWORD;
128
+ console.log(
129
+ "[demo preset] Using deterministic demo password (insecure \u2014 demo only)."
130
+ );
131
+ }
132
+ } else {
133
+ configToWrite = getDefaultConfig();
134
+ configToWrite.wallet.encrypted_path = join(dir, "wallet.enc");
135
+ }
136
+ const yamlContent = stringify(configToWrite);
137
+ writeFileSync(configPath, yamlContent, {
138
+ encoding: "utf-8",
139
+ mode: 384
140
+ });
141
+ console.log(`Config created at ${configPath}`);
142
+ const walletPath = join(dir, "wallet.enc");
143
+ if (existsSync(walletPath) && !force) {
144
+ console.log(
145
+ `Wallet already exists at ${walletPath}. Skipping wallet generation.`
146
+ );
147
+ return;
148
+ }
149
+ const walletPassword = password ?? process.env["TOWNHOUSE_WALLET_PASSWORD"];
150
+ if (!walletPassword) {
151
+ console.error(
152
+ "Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var."
153
+ );
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ const walletManager = new WalletManager({ encryptedPath: walletPath });
158
+ const { mnemonic } = await walletManager.generate();
159
+ console.log("");
160
+ console.log("=== IMPORTANT: Back up your seed phrase ===");
161
+ console.log("");
162
+ console.log(` ${mnemonic}`);
163
+ console.log("");
164
+ console.log("This is the ONLY time your seed phrase will be shown.");
165
+ console.log("Store it safely. You will need it to recover your node keys.");
166
+ console.log("============================================");
167
+ console.log("");
168
+ const encrypted = encryptWallet(mnemonic, walletPassword);
169
+ await saveWallet(walletPath, encrypted);
170
+ console.log(`Wallet saved to ${walletPath}`);
171
+ console.log("");
172
+ console.log("Derived Node Addresses:");
173
+ console.log("-----------------------");
174
+ const allKeys = walletManager.getAllKeys();
175
+ for (const info of allKeys) {
176
+ console.log(` ${info.nodeType.padEnd(6)} Nostr: ${info.nostrPubkey}`);
177
+ console.log(` ${"".padEnd(6)} EVM: ${info.evmAddress}`);
178
+ }
179
+ walletManager.lock();
180
+ }
181
+ async function handleSetup(configDir, port, noBrowser, dockerInstance, browserOpener) {
182
+ const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);
183
+ const configPath = join(dir, "config.yaml");
184
+ const walletPath = join(dir, "wallet.enc");
185
+ if (existsSync(configPath) && existsSync(walletPath)) {
186
+ console.log("Already initialized \u2014 run `townhouse up` to start your nodes");
187
+ return;
188
+ }
189
+ if (existsSync(configPath) && !existsSync(walletPath)) {
190
+ console.error(
191
+ `Found ${configPath} but no wallet at ${walletPath}.
192
+ Delete the orphan config and re-run \`townhouse setup\`, or restore the wallet from backup.`
193
+ );
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+ const docker = dockerInstance ?? new Docker();
198
+ const opener = browserOpener ?? new RealBrowserOpener();
199
+ const wizardServer = await createWizardApiServer({
200
+ configDir: dir,
201
+ configPath,
202
+ walletPath,
203
+ port,
204
+ docker
205
+ });
206
+ const url = `http://127.0.0.1:${port}/wizard`;
207
+ try {
208
+ await wizardServer.app.listen({ host: "127.0.0.1", port });
209
+ } catch (err) {
210
+ const e = err;
211
+ if (e.code === "EADDRINUSE") {
212
+ console.error(
213
+ `Port ${port} is already in use. Pass \`--port <n>\` to choose a different port.`
214
+ );
215
+ process.exitCode = 1;
216
+ try {
217
+ await wizardServer.close();
218
+ } catch {
219
+ }
220
+ return;
221
+ }
222
+ throw err;
223
+ }
224
+ console.log(`Wizard ready at ${url}`);
225
+ if (!noBrowser) {
226
+ await opener.open(url);
227
+ }
228
+ let shuttingDown = false;
229
+ const shutdown = async (sig) => {
230
+ if (shuttingDown) return;
231
+ shuttingDown = true;
232
+ console.log(`
233
+ Received ${sig}, shutting down...`);
234
+ try {
235
+ await wizardServer.close();
236
+ } catch {
237
+ }
238
+ process.exit(0);
239
+ };
240
+ process.once("SIGINT", () => {
241
+ void shutdown("SIGINT");
242
+ });
243
+ process.once("SIGTERM", () => {
244
+ void shutdown("SIGTERM");
245
+ });
246
+ }
247
+ async function handleWalletShow(config, password) {
248
+ const walletPath = config.wallet.encrypted_path;
249
+ const result = await loadWallet(walletPath);
250
+ if (!result) {
251
+ console.error("No wallet found. Run `townhouse init` first.");
252
+ process.exitCode = 1;
253
+ return;
254
+ }
255
+ if (result.permissionsWarning) {
256
+ console.error(result.permissionsWarning);
257
+ }
258
+ const walletPassword = password ?? process.env["TOWNHOUSE_WALLET_PASSWORD"];
259
+ if (!walletPassword) {
260
+ console.error(
261
+ "Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var."
262
+ );
263
+ process.exitCode = 1;
264
+ return;
265
+ }
266
+ const walletManager = new WalletManager({ encryptedPath: walletPath });
267
+ try {
268
+ await walletManager.fromMnemonic(
269
+ decryptWallet(result.wallet, walletPassword)
270
+ );
271
+ } catch (err) {
272
+ const msg = err instanceof Error ? err.message : String(err);
273
+ console.error(`Failed to decrypt wallet: ${msg}`);
274
+ process.exitCode = 1;
275
+ return;
276
+ }
277
+ console.log(
278
+ "Node Type | Nostr Pubkey | EVM Address | Derivation Path"
279
+ );
280
+ console.log(
281
+ "-----------|------------------------------------------------------------------|--------------------------------------------|--------------------------"
282
+ );
283
+ const allKeys = walletManager.getAllKeys();
284
+ for (const info of allKeys) {
285
+ console.log(
286
+ `${info.nodeType.padEnd(10)} | ${info.nostrPubkey} | ${info.evmAddress} | ${info.nostrDerivationPath}`
287
+ );
288
+ }
289
+ walletManager.lock();
290
+ }
291
+ async function handleStatus(docker, config) {
292
+ const orchestrator = new DockerOrchestrator(docker, config);
293
+ const statuses = await orchestrator.status();
294
+ console.log("Node Status:");
295
+ console.log("------------");
296
+ for (const s of statuses) {
297
+ const health = s.health ? ` (${s.health})` : "";
298
+ console.log(` ${s.name.padEnd(12)} ${s.state}${health}`);
299
+ }
300
+ const connectorHs = config.transport.hiddenService;
301
+ const relayHs = config.transport.relayHiddenService;
302
+ if (config.transport.mode === "ator" || connectorHs?.externalUrl || relayHs?.externalUrl || config.transport.externalUrl) {
303
+ console.log("");
304
+ console.log("Hidden Services:");
305
+ console.log("----------------");
306
+ const connectorUrl = connectorHs?.externalUrl ?? config.transport.externalUrl;
307
+ if (connectorUrl) {
308
+ console.log(` Connector (BTP): ${connectorUrl}`);
309
+ }
310
+ if (relayHs?.externalUrl) {
311
+ console.log(` Relay (Nostr): ${relayHs.externalUrl}`);
312
+ }
313
+ if (!connectorUrl && !relayHs?.externalUrl) {
314
+ console.log(" (ator mode set but no externalUrl configured)");
315
+ }
316
+ }
317
+ try {
318
+ const adminClient = new ConnectorAdminClient(
319
+ `http://127.0.0.1:${config.connector.adminPort}`
320
+ );
321
+ const metrics = await adminClient.getMetrics();
322
+ const peers = await adminClient.getPeers();
323
+ const activePeers = peers.filter((p) => p.connected).length;
324
+ console.log("");
325
+ console.log("Connector Metrics:");
326
+ console.log("------------------");
327
+ console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);
328
+ console.log(` Active peers: ${activePeers}/${peers.length}`);
329
+ } catch {
330
+ console.log("");
331
+ console.log("Connector Metrics: unavailable");
332
+ }
333
+ }
334
+ async function handleMetrics(config) {
335
+ const adminClient = new ConnectorAdminClient(
336
+ `http://127.0.0.1:${config.connector.adminPort}`
337
+ );
338
+ try {
339
+ const metrics = await adminClient.getMetrics();
340
+ const peers = await adminClient.getPeers();
341
+ const peerMetrics = new Map(metrics.peers.map((p) => [p.peerId, p]));
342
+ console.log("Connector Metrics:");
343
+ console.log("------------------");
344
+ console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);
345
+ console.log(` Packets rejected: ${metrics.aggregate.packetsRejected}`);
346
+ console.log(` Bytes sent: ${metrics.aggregate.bytesSent}`);
347
+ console.log("");
348
+ console.log("Peers:");
349
+ console.log("------");
350
+ if (peers.length === 0) {
351
+ console.log(" No peers connected");
352
+ } else {
353
+ for (const peer of peers) {
354
+ const status = peer.connected ? "connected" : "disconnected";
355
+ const packets = peerMetrics.get(peer.id)?.packetsForwarded ?? 0;
356
+ console.log(` ${peer.id.padEnd(12)} ${status} (${packets} packets)`);
357
+ }
358
+ }
359
+ } catch (error) {
360
+ const msg = error instanceof Error ? error.message : String(error);
361
+ console.error(`Failed to fetch connector metrics: ${msg}`);
362
+ process.exitCode = 1;
363
+ }
364
+ }
365
+ function resolveProfiles(values, config) {
366
+ const explicitFlags = [];
367
+ if (values["town"]) explicitFlags.push("town");
368
+ if (values["mill"]) explicitFlags.push("mill");
369
+ if (values["dvm"]) explicitFlags.push("dvm");
370
+ if (explicitFlags.length > 0) {
371
+ return explicitFlags;
372
+ }
373
+ const enabled = [];
374
+ if (config.nodes.town.enabled) enabled.push("town");
375
+ if (config.nodes.mill.enabled) enabled.push("mill");
376
+ if (config.nodes.dvm.enabled) enabled.push("dvm");
377
+ return enabled;
378
+ }
379
+ async function handleUp(configPath, config, profiles, docker, password, dryRun = false) {
380
+ if (profiles.length === 0) {
381
+ console.log(
382
+ "No nodes enabled in config. Enable nodes in config.yaml first."
383
+ );
384
+ return;
385
+ }
386
+ const walletPath = config.wallet.encrypted_path;
387
+ let walletManager;
388
+ if (!existsSync(walletPath)) {
389
+ console.error(
390
+ `Wallet not found at ${walletPath}. Run \`townhouse setup\` first (or restore your wallet backup).`
391
+ );
392
+ process.exitCode = 1;
393
+ return;
394
+ } else {
395
+ const walletPassword = password ?? process.env["TOWNHOUSE_WALLET_PASSWORD"];
396
+ if (!walletPassword) {
397
+ throw new Error(
398
+ "Wallet password required to start the API. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var."
399
+ );
400
+ }
401
+ const loaded = await loadWallet(walletPath);
402
+ if (!loaded) {
403
+ throw new Error(`Wallet at ${walletPath} could not be read.`);
404
+ }
405
+ if (loaded.permissionsWarning) {
406
+ console.error(loaded.permissionsWarning);
407
+ }
408
+ walletManager = new WalletManager({ encryptedPath: walletPath });
409
+ try {
410
+ await walletManager.fromMnemonic(
411
+ decryptWallet(loaded.wallet, walletPassword)
412
+ );
413
+ } catch (err) {
414
+ const msg = err instanceof Error ? err.message : String(err);
415
+ throw new Error(`Failed to decrypt wallet: ${msg}`);
416
+ }
417
+ }
418
+ const orchestrator = new DockerOrchestrator(docker, config, walletManager);
419
+ orchestrator.on(
420
+ "containerState",
421
+ (event) => {
422
+ console.log(` ${event.name}: ${event.state}`);
423
+ }
424
+ );
425
+ orchestrator.on(
426
+ "pullProgress",
427
+ (event) => {
428
+ const progress = event.progress ? ` ${event.progress}` : "";
429
+ console.log(` [pull] ${event.image}: ${event.status}${progress}`);
430
+ }
431
+ );
432
+ let apiServer;
433
+ const sigintHandler = async () => {
434
+ console.log("\nReceived SIGINT, shutting down gracefully...");
435
+ if (apiServer) {
436
+ try {
437
+ await apiServer.close();
438
+ } catch {
439
+ }
440
+ }
441
+ try {
442
+ await orchestrator.down();
443
+ } catch {
444
+ }
445
+ process.exit(0);
446
+ };
447
+ process.on("SIGINT", sigintHandler);
448
+ const sigtermHandler = async () => {
449
+ console.log("\nReceived SIGTERM, shutting down gracefully...");
450
+ if (apiServer) {
451
+ try {
452
+ await apiServer.close();
453
+ } catch {
454
+ }
455
+ }
456
+ try {
457
+ await orchestrator.down();
458
+ } catch {
459
+ }
460
+ process.exit(0);
461
+ };
462
+ process.on("SIGTERM", sigtermHandler);
463
+ let serverStarted = false;
464
+ if (profiles.includes("dvm") && config.nodes.dvm.enabled && !process.env["TURBO_TOKEN"]) {
465
+ console.warn(
466
+ "[townhouse] WARN: TURBO_TOKEN is not set \u2014 Arweave DVM (kind:5094) uploads will fail at first job."
467
+ );
468
+ console.warn(
469
+ "[townhouse] Export TURBO_TOKEN=<arweave-jwk-json> before `townhouse up` to enable uploads."
470
+ );
471
+ }
472
+ try {
473
+ console.log(`Starting nodes: ${profiles.join(", ")}...`);
474
+ if (!dryRun) {
475
+ await orchestrator.up(profiles);
476
+ console.log("All nodes started successfully.");
477
+ } else {
478
+ console.log("[dry-run] Skipped orchestrator.up()");
479
+ }
480
+ if (walletManager) {
481
+ const connectorAdmin = new ConnectorAdminClient(
482
+ `http://127.0.0.1:${config.connector.adminPort}`
483
+ );
484
+ const transportProbe = new TransportProbe({
485
+ proxyUrl: config.transport.mode === "ator" ? config.transport.socksProxy ?? DEFAULT_ATOR_PROXY : ""
486
+ });
487
+ if (config.transport.mode === "ator") {
488
+ transportProbe.start();
489
+ }
490
+ const apiDeps = {
491
+ configPath,
492
+ config,
493
+ orchestrator,
494
+ wallet: walletManager,
495
+ connectorAdmin,
496
+ transportProbe
497
+ };
498
+ apiServer = await createApiServer(apiDeps);
499
+ const { host, port } = config.api;
500
+ if (!dryRun) {
501
+ await apiServer.app.listen({
502
+ host: host ?? "127.0.0.1",
503
+ port: port ?? 9400
504
+ });
505
+ serverStarted = true;
506
+ console.log(
507
+ `
508
+ [Townhouse API] listening on http://${host ?? "127.0.0.1"}:${port ?? 9400}`
509
+ );
510
+ console.log(
511
+ " GET /nodes, GET /nodes/:type, PATCH /nodes/:type/config, GET /wallet, WS /metrics"
512
+ );
513
+ } else {
514
+ console.log(
515
+ `[dry-run] API factory invoked: configPath=${configPath} host=${host ?? "127.0.0.1"} port=${port ?? 9400} connectorAdmin=http://127.0.0.1:${config.connector.adminPort} wallet=WalletManager`
516
+ );
517
+ await apiServer.close();
518
+ apiServer = void 0;
519
+ }
520
+ }
521
+ } catch (error) {
522
+ const msg = error instanceof Error ? error.message : String(error);
523
+ if (msg.includes("Docker is not running") || msg.includes("ENOENT") || msg.includes("ECONNREFUSED") || msg.includes("socket")) {
524
+ throw new Error(
525
+ `Docker is not available. Please ensure Docker is running and try again. (${msg})`
526
+ );
527
+ }
528
+ throw error;
529
+ } finally {
530
+ if (!serverStarted) {
531
+ process.removeListener("SIGINT", sigintHandler);
532
+ process.removeListener("SIGTERM", sigtermHandler);
533
+ }
534
+ }
535
+ }
536
+ async function handleDown(config, docker) {
537
+ const orchestrator = new DockerOrchestrator(docker, config);
538
+ orchestrator.on(
539
+ "containerState",
540
+ (event) => {
541
+ console.log(` ${event.name}: ${event.state}`);
542
+ }
543
+ );
544
+ console.log("Stopping nodes...");
545
+ await orchestrator.down();
546
+ console.log("All nodes stopped.");
547
+ }
548
+ async function main(argv, dockerInstance, browserOpener) {
549
+ const { values, positionals } = parseArgs({
550
+ args: argv,
551
+ options: {
552
+ help: { type: "boolean" },
553
+ force: { type: "boolean" },
554
+ config: { type: "string", short: "c" },
555
+ "config-dir": { type: "string" },
556
+ town: { type: "boolean" },
557
+ mill: { type: "boolean" },
558
+ dvm: { type: "boolean" },
559
+ password: { type: "string" },
560
+ "dry-run": { type: "boolean" },
561
+ "no-browser": { type: "boolean" },
562
+ port: { type: "string" },
563
+ preset: { type: "string" },
564
+ yes: { type: "boolean" }
565
+ },
566
+ strict: false,
567
+ allowPositionals: true
568
+ });
569
+ if (values.help) {
570
+ console.log(HELP_TEXT);
571
+ throw new CliHelpRequested();
572
+ }
573
+ const command = positionals[0];
574
+ if (!command) {
575
+ console.log(HELP_TEXT);
576
+ throw new CliHelpRequested();
577
+ }
578
+ switch (command) {
579
+ case "setup": {
580
+ const portStr = values["port"];
581
+ const port = portStr ? Number(portStr) : 9400;
582
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
583
+ console.error("--port must be an integer between 1 and 65535");
584
+ process.exitCode = 1;
585
+ break;
586
+ }
587
+ await handleSetup(
588
+ values["config-dir"],
589
+ port,
590
+ values["no-browser"] === true,
591
+ dockerInstance,
592
+ browserOpener
593
+ );
594
+ break;
595
+ }
596
+ case "init": {
597
+ const presetVal = values.preset;
598
+ if (presetVal !== void 0 && presetVal !== "demo") {
599
+ console.error(`Unknown preset: ${presetVal}. Supported: demo`);
600
+ process.exitCode = 1;
601
+ break;
602
+ }
603
+ await handleInit(
604
+ values.force === true,
605
+ values["config-dir"],
606
+ values.password,
607
+ presetVal,
608
+ values.yes === true
609
+ );
610
+ break;
611
+ }
612
+ case "wallet": {
613
+ const subCommand = positionals[1];
614
+ if (subCommand === "show") {
615
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
616
+ const config = loadConfig(configPath);
617
+ await handleWalletShow(config, values.password);
618
+ } else {
619
+ console.error(
620
+ "Usage: townhouse wallet show [-c <path>] [--password <pw>]"
621
+ );
622
+ process.exitCode = 1;
623
+ }
624
+ break;
625
+ }
626
+ case "status": {
627
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
628
+ const config = loadConfig(configPath);
629
+ const docker = dockerInstance ?? new Docker();
630
+ await handleStatus(docker, config);
631
+ break;
632
+ }
633
+ case "up": {
634
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
635
+ const config = loadConfig(configPath);
636
+ const docker = dockerInstance ?? new Docker();
637
+ const profiles = resolveProfiles(values, config);
638
+ await handleUp(
639
+ configPath,
640
+ config,
641
+ profiles,
642
+ docker,
643
+ values.password,
644
+ values["dry-run"] === true
645
+ );
646
+ break;
647
+ }
648
+ case "down": {
649
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
650
+ const config = loadConfig(configPath);
651
+ const docker = dockerInstance ?? new Docker();
652
+ await handleDown(config, docker);
653
+ break;
654
+ }
655
+ case "metrics": {
656
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
657
+ const config = loadConfig(configPath);
658
+ await handleMetrics(config);
659
+ break;
660
+ }
661
+ default: {
662
+ const sanitized = command.replace(/[\x00-\x1f\x7f]/g, "");
663
+ console.error(`Unknown command: ${sanitized}`);
664
+ console.log(HELP_TEXT);
665
+ process.exitCode = 1;
666
+ }
667
+ }
668
+ }
669
+ var invokedFile = process.argv[1];
670
+ var invokedDirectly = typeof invokedFile === "string" && import.meta.url === pathToFileURL(invokedFile).href;
671
+ if (invokedDirectly) {
672
+ main(process.argv.slice(2)).catch((error) => {
673
+ if (error instanceof CliHelpRequested) {
674
+ process.exit(0);
675
+ }
676
+ console.error("[Townhouse] Error:", error);
677
+ process.exit(1);
678
+ });
679
+ }
680
+ export {
681
+ CliHelpRequested,
682
+ main
683
+ };
684
+ //# sourceMappingURL=cli.js.map