@toon-protocol/townhouse 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import Docker from 'dockerode';
3
- import { C as ComposeLoaderOptions, T as TownhouseConfig, W as WalletManager, N as NodeType } from './manager-SsneW_Mj.js';
3
+ import { C as ComposeLoaderOptions, T as TownhouseConfig, W as WalletManager, N as NodeType } from './manager-BtpOFwd6.js';
4
4
 
5
5
  /**
6
6
  * Cross-platform browser opener for the wizard CLI command.
@@ -119,12 +119,6 @@ interface CliNodeCommandOverrides {
119
119
  confirm?: (question: string) => Promise<boolean>;
120
120
  apiUrl?: string;
121
121
  }
122
- /**
123
- * Main CLI entry — exported for testability (same pattern as Mill CLI).
124
- * Accepts optional dockerode instance for dependency injection in tests.
125
- * The optional `hsOverrides` bag is used by unit tests to stub out Docker,
126
- * file I/O, and admin-client calls in the `hs up` / `hs down` path.
127
- */
128
122
  declare function main(argv: string[], dockerInstance?: Docker, browserOpener?: BrowserOpener, hsOverrides?: CliHsOverrides, nodeCommandOverrides?: CliNodeCommandOverrides): Promise<void>;
129
123
 
130
124
  export { CliHelpRequested, type CliHsOverrides, type CliNodeCommandOverrides, main };
package/dist/cli.js CHANGED
@@ -23,11 +23,12 @@ import {
23
23
  materializeComposeTemplate,
24
24
  readImageManifest,
25
25
  readNodesYaml,
26
+ saveConfig,
26
27
  saveWallet,
27
28
  serviceFromContainerName,
28
29
  tailContainerLogs,
29
30
  writeHsConnectorConfig
30
- } from "./chunk-W33MEOPM.js";
31
+ } from "./chunk-B4KWPVEK.js";
31
32
  import "./chunk-5O4SBV5O.js";
32
33
  import {
33
34
  CONTAINER_PREFIX
@@ -2004,6 +2005,9 @@ Usage:
2004
2005
  townhouse node add [<type>] [--json] [-c <path>] Provision a child node (default: town)
2005
2006
  townhouse node remove <id> [--yes] [--json] [-c <path>] Deprovision a child node
2006
2007
  townhouse node list [--json] [-c <path>] List provisioned nodes
2008
+ townhouse chains list [--json] [-c <path>] List configured settlement chains (EVM/Solana/Mina)
2009
+ townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>] Add/update a settlement chain
2010
+ townhouse chains remove <chainId> [-c <path>] Remove a settlement chain
2007
2011
  townhouse channels [--json] Show open payment channels
2008
2012
  townhouse logs <node-id> [-f|--follow] [--lines N] [--json] Tail logs for a node (Ctrl-C to stop)
2009
2013
  townhouse peer <id> [--json] Show per-peer detail card
@@ -3029,8 +3033,52 @@ function isAnonBootstrapTimeout(err) {
3029
3033
  ${err.stderr ?? ""}`;
3030
3034
  return /connector.*unhealthy|dependency.*connector.*fail/i.test(text);
3031
3035
  }
3036
+ async function attachDashboard(hostname) {
3037
+ if (!shouldRenderInk()) return;
3038
+ try {
3039
+ const { mountTui } = await import("./tui-QE3ZRZO3.js");
3040
+ const apiUrlOverride = process.env["HS_TOWNHOUSE_API_URL"];
3041
+ const mountOpts = apiUrlOverride !== void 0 ? { apiUrl: apiUrlOverride } : {};
3042
+ const instance = mountTui(mountOpts);
3043
+ await instance.waitUntilExit();
3044
+ } catch (tuiErr) {
3045
+ const detail = tuiErr instanceof Error ? tuiErr.message : String(tuiErr);
3046
+ console.error("");
3047
+ console.error(`Your node is live at ${hostname}.`);
3048
+ console.error(
3049
+ `The live dashboard could not open (${detail}) \u2014 this is a display issue, not a node issue. Your node keeps running.`
3050
+ );
3051
+ console.error(
3052
+ "Stop it anytime with: npx @toon-protocol/townhouse hs down"
3053
+ );
3054
+ }
3055
+ }
3032
3056
  async function handleHsUp(_configPath, configDir, config, docker, options) {
3033
3057
  const { password, force, skipPreflight, hsOverrides } = options;
3058
+ if (!force) {
3059
+ const adminClientFactory = hsOverrides?.createAdminClient ?? ((url, t) => new ConnectorAdminClient(url, t));
3060
+ const probe = adminClientFactory(HS_CONNECTOR_ADMIN_URL, 3e3);
3061
+ try {
3062
+ const existing = await probe.getHsHostname();
3063
+ if (existing.hostname !== null) {
3064
+ console.log(`Apex live at ${existing.hostname}`);
3065
+ _writeHostJson(configDir, {
3066
+ hostname: existing.hostname,
3067
+ publishedAt: existing.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3068
+ writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3069
+ });
3070
+ await attachDashboard(existing.hostname);
3071
+ return;
3072
+ }
3073
+ } catch (probeErr) {
3074
+ const msg = probeErr instanceof Error ? probeErr.message : String(probeErr);
3075
+ if (msg.includes("anon-disabled")) {
3076
+ const { exitCode } = renderFailure(probeErr);
3077
+ process.exitCode = exitCode;
3078
+ return;
3079
+ }
3080
+ }
3081
+ }
3034
3082
  if (!skipPreflight) {
3035
3083
  const preflight = hsOverrides?.checkPortCollisions ?? ((d) => checkHsPortCollisions(d));
3036
3084
  try {
@@ -3089,29 +3137,6 @@ async function handleHsUp(_configPath, configDir, config, docker, options) {
3089
3137
  }
3090
3138
  const ribbon = new OnboardingRibbon();
3091
3139
  try {
3092
- if (!force) {
3093
- const adminClientFactory = hsOverrides?.createAdminClient ?? ((url, t) => new ConnectorAdminClient(url, t));
3094
- const probe = adminClientFactory(HS_CONNECTOR_ADMIN_URL, 3e3);
3095
- try {
3096
- const existing = await probe.getHsHostname();
3097
- if (existing.hostname !== null) {
3098
- console.log(`Apex live at ${existing.hostname}`);
3099
- _writeHostJson(configDir, {
3100
- hostname: existing.hostname,
3101
- publishedAt: existing.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3102
- writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3103
- });
3104
- return;
3105
- }
3106
- } catch (probeErr) {
3107
- const msg = probeErr instanceof Error ? probeErr.message : String(probeErr);
3108
- if (msg.includes("anon-disabled")) {
3109
- const { exitCode } = renderFailure(probeErr);
3110
- process.exitCode = exitCode;
3111
- return;
3112
- }
3113
- }
3114
- }
3115
3140
  writeHsConnectorConfig(configDir, config, { force });
3116
3141
  const materialize = hsOverrides?.materializeComposeTemplate ?? materializeComposeTemplate;
3117
3142
  const { composePath } = materialize("hs", { townhouseHome: configDir });
@@ -3141,6 +3166,7 @@ async function handleHsUp(_configPath, configDir, config, docker, options) {
3141
3166
  ribbon.start("bootstrap");
3142
3167
  }
3143
3168
  });
3169
+ ribbon.stop();
3144
3170
  if (typeof orch.pullImage === "function") {
3145
3171
  try {
3146
3172
  const apexImages = await collectApexImageRefs(configDir);
@@ -3263,25 +3289,7 @@ async function handleHsUp(_configPath, configDir, config, docker, options) {
3263
3289
  writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3264
3290
  });
3265
3291
  ribbon.start("live", hostname);
3266
- if (shouldRenderInk()) {
3267
- try {
3268
- const { mountTui } = await import("./tui-OIFXGBTL.js");
3269
- const apiUrlOverride = process.env["HS_TOWNHOUSE_API_URL"];
3270
- const mountOpts = apiUrlOverride !== void 0 ? { apiUrl: apiUrlOverride } : {};
3271
- const instance = mountTui(mountOpts);
3272
- await instance.waitUntilExit();
3273
- } catch (tuiErr) {
3274
- const detail = tuiErr instanceof Error ? tuiErr.message : String(tuiErr);
3275
- console.error("");
3276
- console.error(`Your node is live at ${hostname}.`);
3277
- console.error(
3278
- `The live dashboard could not open (${detail}) \u2014 this is a display issue, not a node issue. Your node keeps running.`
3279
- );
3280
- console.error(
3281
- "Stop it anytime with: npx @toon-protocol/townhouse hs down"
3282
- );
3283
- }
3284
- }
3292
+ await attachDashboard(hostname);
3285
3293
  } catch (err) {
3286
3294
  const { exitCode } = renderFailure(err);
3287
3295
  process.exitCode = exitCode;
@@ -3439,6 +3447,143 @@ function _runDockerComposeDown(composePath, withVolumes) {
3439
3447
  });
3440
3448
  });
3441
3449
  }
3450
+ var CHAINS_HELP = `townhouse chains \u2014 configure settlement chains (connector chainProviders)
3451
+
3452
+ The connector settles ILP payment claims on these chains. Changes take effect
3453
+ on the next 'townhouse hs down && townhouse hs up'.
3454
+
3455
+ Usage:
3456
+ townhouse chains list [--json] [-c <path>]
3457
+ townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>]
3458
+ townhouse chains remove <chainId> [-c <path>]
3459
+
3460
+ Fields by chain type:
3461
+ evm: --rpc-url <url> --registry <0x..> --token-address <0x..> --key-id <0x..>
3462
+ solana: --rpc-url <url> --program-id <addr> --key-id <id> [--ws-url <url>] [--token-mint <addr>]
3463
+ mina: --graphql-url <url> --zkapp <addr> [--key-id <id>]`;
3464
+ function buildChainProviderFromFlags(f) {
3465
+ const { chainType, chainId } = f;
3466
+ if (chainType !== "evm" && chainType !== "solana" && chainType !== "mina") {
3467
+ throw new Error("--chain-type must be one of: evm, solana, mina");
3468
+ }
3469
+ if (!chainId) throw new Error("--chain-id is required");
3470
+ const require2 = (flag, val) => {
3471
+ if (!val) throw new Error(`${flag} is required for ${chainType} chains`);
3472
+ return val;
3473
+ };
3474
+ if (chainType === "evm") {
3475
+ return {
3476
+ chainType: "evm",
3477
+ chainId,
3478
+ rpcUrl: require2("--rpc-url", f.rpcUrl),
3479
+ registryAddress: require2("--registry", f.registry),
3480
+ tokenAddress: require2("--token-address", f.tokenAddress),
3481
+ keyId: require2("--key-id", f.keyId)
3482
+ };
3483
+ }
3484
+ if (chainType === "solana") {
3485
+ return {
3486
+ chainType: "solana",
3487
+ chainId,
3488
+ rpcUrl: require2("--rpc-url", f.rpcUrl),
3489
+ ...f.wsUrl ? { wsUrl: f.wsUrl } : {},
3490
+ programId: require2("--program-id", f.programId),
3491
+ ...f.tokenMint ? { tokenMint: f.tokenMint } : {},
3492
+ keyId: require2("--key-id", f.keyId)
3493
+ };
3494
+ }
3495
+ return {
3496
+ chainType: "mina",
3497
+ chainId,
3498
+ graphqlUrl: require2("--graphql-url", f.graphqlUrl),
3499
+ zkAppAddress: require2("--zkapp", f.zkapp),
3500
+ ...f.keyId ? { keyId: f.keyId } : {}
3501
+ };
3502
+ }
3503
+ async function handleChains(action, chainIdArg, flags, configPath, jsonMode) {
3504
+ if (!action) {
3505
+ console.log(CHAINS_HELP);
3506
+ throw new CliHelpRequested();
3507
+ }
3508
+ const config = loadConfig(configPath);
3509
+ const providers = config.chainProviders ?? [];
3510
+ switch (action) {
3511
+ case "list": {
3512
+ if (jsonMode) {
3513
+ console.log(JSON.stringify(providers, null, 2));
3514
+ return;
3515
+ }
3516
+ if (providers.length === 0) {
3517
+ console.log(
3518
+ "No settlement chains configured \u2014 the connector uses a built-in dev-Anvil EVM placeholder."
3519
+ );
3520
+ console.log(
3521
+ "Add one with: townhouse chains add --chain-type evm --chain-id evm:base:8453 ..."
3522
+ );
3523
+ return;
3524
+ }
3525
+ console.log("Configured settlement chains:");
3526
+ for (const p of providers) {
3527
+ console.log(` ${p.chainType.padEnd(6)} ${p.chainId}`);
3528
+ }
3529
+ return;
3530
+ }
3531
+ case "add": {
3532
+ let entry;
3533
+ try {
3534
+ entry = buildChainProviderFromFlags(flags);
3535
+ } catch (err) {
3536
+ console.error(err instanceof Error ? err.message : String(err));
3537
+ process.exitCode = 1;
3538
+ return;
3539
+ }
3540
+ const next = providers.filter((p) => p.chainId !== entry.chainId);
3541
+ next.push(entry);
3542
+ try {
3543
+ saveConfig(configPath, { ...config, chainProviders: next });
3544
+ } catch (err) {
3545
+ console.error(
3546
+ `Invalid chain config: ${err instanceof Error ? err.message : String(err)}`
3547
+ );
3548
+ process.exitCode = 1;
3549
+ return;
3550
+ }
3551
+ console.log(
3552
+ `Added ${entry.chainType} settlement chain '${entry.chainId}'.`
3553
+ );
3554
+ console.log("Apply with: townhouse hs down && townhouse hs up");
3555
+ return;
3556
+ }
3557
+ case "remove": {
3558
+ if (!chainIdArg) {
3559
+ console.error("Usage: townhouse chains remove <chainId>");
3560
+ process.exitCode = 1;
3561
+ return;
3562
+ }
3563
+ const next = providers.filter((p) => p.chainId !== chainIdArg);
3564
+ if (next.length === providers.length) {
3565
+ console.error(
3566
+ `No settlement chain with chainId '${chainIdArg}' found.`
3567
+ );
3568
+ process.exitCode = 1;
3569
+ return;
3570
+ }
3571
+ saveConfig(configPath, {
3572
+ ...config,
3573
+ chainProviders: next.length > 0 ? next : void 0
3574
+ });
3575
+ console.log(`Removed settlement chain '${chainIdArg}'.`);
3576
+ console.log("Apply with: townhouse hs down && townhouse hs up");
3577
+ return;
3578
+ }
3579
+ default: {
3580
+ const safe = action.replace(/[\x00-\x1f\x7f]/g, "");
3581
+ console.error(`Unknown chains subcommand: ${safe}`);
3582
+ console.log(CHAINS_HELP);
3583
+ process.exitCode = 1;
3584
+ }
3585
+ }
3586
+ }
3442
3587
  async function main(argv, dockerInstance, browserOpener, hsOverrides, nodeCommandOverrides) {
3443
3588
  const { values, positionals } = parseArgs({
3444
3589
  args: argv,
@@ -3473,7 +3618,19 @@ async function main(argv, dockerInstance, browserOpener, hsOverrides, nodeComman
3473
3618
  // wallet show / wallet seed (epic-49, Phase 3)
3474
3619
  hex: { type: "boolean" },
3475
3620
  paths: { type: "boolean" },
3476
- confirm: { type: "boolean" }
3621
+ confirm: { type: "boolean" },
3622
+ // chains add (multi-chain settlement config)
3623
+ "chain-type": { type: "string" },
3624
+ "chain-id": { type: "string" },
3625
+ "rpc-url": { type: "string" },
3626
+ "ws-url": { type: "string" },
3627
+ registry: { type: "string" },
3628
+ "token-address": { type: "string" },
3629
+ "token-mint": { type: "string" },
3630
+ "program-id": { type: "string" },
3631
+ "graphql-url": { type: "string" },
3632
+ zkapp: { type: "string" },
3633
+ "key-id": { type: "string" }
3477
3634
  },
3478
3635
  strict: false,
3479
3636
  allowPositionals: true
@@ -3708,6 +3865,32 @@ async function main(argv, dockerInstance, browserOpener, hsOverrides, nodeComman
3708
3865
  }
3709
3866
  break;
3710
3867
  }
3868
+ case "chains": {
3869
+ const configPath = values.config ?? DEFAULT_CONFIG_PATH;
3870
+ const action = positionals[1];
3871
+ const chainIdArg = positionals[2];
3872
+ const flags = {
3873
+ chainType: values["chain-type"],
3874
+ chainId: values["chain-id"],
3875
+ rpcUrl: values["rpc-url"],
3876
+ wsUrl: values["ws-url"],
3877
+ registry: values["registry"],
3878
+ tokenAddress: values["token-address"],
3879
+ tokenMint: values["token-mint"],
3880
+ programId: values["program-id"],
3881
+ graphqlUrl: values["graphql-url"],
3882
+ zkapp: values["zkapp"],
3883
+ keyId: values["key-id"]
3884
+ };
3885
+ await handleChains(
3886
+ action,
3887
+ chainIdArg,
3888
+ flags,
3889
+ configPath,
3890
+ values.json === true
3891
+ );
3892
+ break;
3893
+ }
3711
3894
  default: {
3712
3895
  const sanitized = command.replace(/[\x00-\x1f\x7f]/g, "");
3713
3896
  console.error(`Unknown command: ${sanitized}`);