routstrd 0.3.1 → 0.3.2

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/index.js CHANGED
@@ -13943,10 +13943,14 @@ function getTodayStart() {
13943
13943
  return new Date(now2.getFullYear(), now2.getMonth(), now2.getDate()).getTime();
13944
13944
  }
13945
13945
  function formatDate(timestamp) {
13946
- return new Date(timestamp).toISOString().split("T")[0] ?? "";
13946
+ const d = new Date(timestamp);
13947
+ const p = (n) => String(n).padStart(2, "0");
13948
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
13947
13949
  }
13948
13950
  function formatTime(timestamp) {
13949
- return new Date(timestamp).toISOString().split("T")[1]?.slice(0, 8) ?? "";
13951
+ const d = new Date(timestamp);
13952
+ const p = (n) => String(n).padStart(2, "0");
13953
+ return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
13950
13954
  }
13951
13955
  function formatNumber(n) {
13952
13956
  if (n >= 1e6)
@@ -14737,19 +14741,34 @@ function renderRecent(stats, width) {
14737
14741
  if (recentEntries.length === 0)
14738
14742
  return renderBox(["No recent entries"], width, "Recent Requests");
14739
14743
  const clientCol = 14;
14744
+ const tokensCol = 18;
14745
+ const costCol = 18;
14746
+ const providerCol = Math.max(16, width - 4 - 10 - 18 - tokensCol - costCol - clientCol - 5);
14747
+ const msatsToSats = (msats) => typeof msats === "number" ? msats / 1000 : 0;
14740
14748
  const lines = [];
14741
- lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"TOKENS".padEnd(10)} ${"COST".padEnd(12)} ${"PROVIDER".padEnd(16)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
14749
+ lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"I/CR/CW/O".padEnd(tokensCol)} ${"I/O/T in sats".padEnd(costCol)} ${"BASE:PROVIDER".padEnd(providerCol)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
14742
14750
  lines.push(COLORS.dim + "\u2500".repeat(width - 4) + COLORS.reset);
14743
14751
  for (const entry of recentEntries) {
14744
14752
  const time = formatTime(entry.timestamp).slice(0, 8);
14745
14753
  const model = entry.modelId.slice(0, 18).padEnd(18);
14746
- const tokens = `${formatNumber(entry.totalTokens).padEnd(6)} (${formatNumber(entry.promptTokens)}+${formatNumber(entry.completionTokens)})`;
14747
- const cost = `${formatCost(entry.satsCost).padEnd(8)} sats`;
14748
- const provider = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "").slice(0, 16).padEnd(16);
14754
+ const tokens = [
14755
+ entry.promptTokens,
14756
+ entry.cacheReadInputTokens || 0,
14757
+ entry.cacheCreationInputTokens || 0,
14758
+ entry.completionTokens
14759
+ ].map(formatNumber).join("/");
14760
+ const totalSats = typeof entry.totalMsats === "number" ? entry.totalMsats / 1000 : entry.satsCost;
14761
+ const cost = [
14762
+ formatCost(msatsToSats(entry.inputMsats)),
14763
+ formatCost(msatsToSats(entry.outputMsats)),
14764
+ formatCost(totalSats)
14765
+ ].join("/");
14766
+ const baseUrl = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "");
14767
+ const provider = `${baseUrl}:${entry.provider || "unknown"}`.slice(0, providerCol).padEnd(providerCol);
14749
14768
  const clientName = (entry.client || "unknown").slice(0, clientCol - 1);
14750
14769
  const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
14751
14770
  const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
14752
- lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(10)} ${COLORS.green}${cost}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
14771
+ lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(tokensCol)} ${COLORS.green}${cost.padEnd(costCol)}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
14753
14772
  }
14754
14773
  return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
14755
14774
  }
@@ -15440,7 +15459,7 @@ async function runIntegrationsForClients(clientIds, config) {
15440
15459
 
15441
15460
  // src/utils/clients.ts
15442
15461
  async function getClientsList() {
15443
- const result = await callAuth("/clients");
15462
+ const result = await callDaemon("/clients");
15444
15463
  const clients = result.output?.clients;
15445
15464
  if (!clients) {
15446
15465
  return [];
@@ -15469,7 +15488,7 @@ async function addDaemonClient(name) {
15469
15488
  };
15470
15489
  return { client, created: false };
15471
15490
  }
15472
- const result = await callAuth("/clients/add", {
15491
+ const result = await callDaemon("/clients/add", {
15473
15492
  method: "POST",
15474
15493
  body: { name, id: derivedId }
15475
15494
  });
@@ -15507,7 +15526,7 @@ async function listClientsAction() {
15507
15526
  }
15508
15527
  async function deleteClientAction(id) {
15509
15528
  await ensureDaemonRunning();
15510
- const result = await callAuth("/clients/delete", {
15529
+ const result = await callDaemon("/clients/delete", {
15511
15530
  method: "POST",
15512
15531
  body: { id }
15513
15532
  });
@@ -15783,7 +15802,7 @@ async function isCocodInstalled(cocodPath) {
15783
15802
  // package.json
15784
15803
  var package_default = {
15785
15804
  name: "routstrd",
15786
- version: "0.3.1",
15805
+ version: "0.3.2",
15787
15806
  module: "src/index.ts",
15788
15807
  type: "module",
15789
15808
  private: false,
@@ -15807,7 +15826,7 @@ var package_default = {
15807
15826
  },
15808
15827
  dependencies: {
15809
15828
  "@cashu/cashu-ts": "^4.3.0",
15810
- "@routstr/sdk": "^0.3.9",
15829
+ "@routstr/sdk": "^0.3.10",
15811
15830
  "applesauce-core": "^5.1.0",
15812
15831
  "applesauce-relay": "^5.1.0",
15813
15832
  "applesauce-wallet-connect": "^6.0.0",
@@ -15876,6 +15895,18 @@ async function initDaemon() {
15876
15895
  console.log(`Created config file: ${CONFIG_FILE}`);
15877
15896
  }
15878
15897
  const config = await loadConfig();
15898
+ if (!config.nsec) {
15899
+ const secretKey = generateSecretKey();
15900
+ const nsec = nip19_exports.nsecEncode(secretKey);
15901
+ const npub = npubFromSecretKey(secretKey);
15902
+ config.nsec = nsec;
15903
+ await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
15904
+ console.log(`
15905
+ A new Nostr identity has been generated for authentication.`);
15906
+ console.log(`Your npub: ${npub}`);
15907
+ console.log(`You can view it in the config file at: ${CONFIG_FILE}
15908
+ `);
15909
+ }
15879
15910
  if (!await isCocodInstalled(config.cocodPath)) {
15880
15911
  if (config.cocodPath) {
15881
15912
  logger.error(`Configured cocod executable was not found: ${config.cocodPath}`);
@@ -16249,9 +16280,10 @@ program.command("usage").description("Show recent usage logs and total sats cost
16249
16280
  });
16250
16281
  });
16251
16282
  var providersCmd = program.command("providers").description("List and manage providers");
16252
- providersCmd.command("list").description("List all providers with their enabled/disabled status").action(async () => {
16283
+ providersCmd.command("list").description("List all providers with their enabled/disabled status").option("--refresh", "Force re-fetch all Nostr events and refresh models from all enabled providers", false).action(async (options) => {
16253
16284
  await ensureDaemonRunning();
16254
- const result = await callDaemon("/providers");
16285
+ const query = options.refresh ? "/providers?refresh=true" : "/providers";
16286
+ const result = await callDaemon(query);
16255
16287
  if (result.error) {
16256
16288
  console.log(result.error);
16257
16289
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@cashu/cashu-ts": "^4.3.0",
27
- "@routstr/sdk": "^0.3.9",
27
+ "@routstr/sdk": "^0.3.10",
28
28
  "applesauce-core": "^5.1.0",
29
29
  "applesauce-relay": "^5.1.0",
30
30
  "applesauce-wallet-connect": "^6.0.0",
package/say_hi.sh ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LANGUAGES=(
5
+ "Mandarin Chinese"
6
+ "Spanish"
7
+ "English"
8
+ "Hindi"
9
+ "Arabic"
10
+ "Portuguese"
11
+ "Bengali"
12
+ "Russian"
13
+ "Japanese"
14
+ "German"
15
+ "French"
16
+ "Italian"
17
+ "Korean"
18
+ "Turkish"
19
+ "Vietnamese"
20
+ "Tamil"
21
+ "Polish"
22
+ "Dutch"
23
+ "Swahili"
24
+ "Thai"
25
+ "Greek"
26
+ )
27
+
28
+ for lang in "${LANGUAGES[@]}"; do
29
+ echo "=== $lang ==="
30
+ pi -p -c "in $lang"
31
+ echo
32
+ done
package/src/cli.ts CHANGED
@@ -134,6 +134,17 @@ async function initDaemon(): Promise<void> {
134
134
 
135
135
  const config = await loadConfig();
136
136
 
137
+ if (!config.nsec) {
138
+ const secretKey = generateSecretKey();
139
+ const nsec = nip19.nsecEncode(secretKey);
140
+ const npub = npubFromSecretKey(secretKey);
141
+ config.nsec = nsec;
142
+ await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
143
+ console.log("\nA new Nostr identity has been generated for authentication.");
144
+ console.log(`Your npub: ${npub}`);
145
+ console.log(`You can view it in the config file at: ${CONFIG_FILE}\n`);
146
+ }
147
+
137
148
  if (!(await isCocodInstalled(config.cocodPath))) {
138
149
  if (config.cocodPath) {
139
150
  logger.error(
@@ -732,10 +743,12 @@ const providersCmd = program
732
743
  providersCmd
733
744
  .command("list")
734
745
  .description("List all providers with their enabled/disabled status")
735
- .action(async () => {
746
+ .option("--refresh", "Force re-fetch all Nostr events and refresh models from all enabled providers", false)
747
+ .action(async (options: { refresh: boolean }) => {
736
748
  await ensureDaemonRunning();
737
749
 
738
- const result = await callDaemon("/providers");
750
+ const query = options.refresh ? "/providers?refresh=true" : "/providers";
751
+ const result = await callDaemon(query);
739
752
  if (result.error) {
740
753
  console.log(result.error);
741
754
  process.exit(1);
@@ -120,9 +120,64 @@ export function createModelService(modelManager: ModelManager, store: SdkStore)
120
120
  };
121
121
  };
122
122
 
123
+ /**
124
+ * Force-refresh everything: re-fetch Nostr provider discovery events,
125
+ * routstr21 model list, Nostr review events, and models from all enabled
126
+ * providers. Syncs the discovered provider list into the store.
127
+ */
128
+ const refreshProvidersAndModels = async (): Promise<void> => {
129
+ // Reset the bootstrap promise so we don't reuse cached results
130
+ providerBootstrapPromise = null;
131
+
132
+ console.log("Force-refreshing providers from Nostr...");
133
+
134
+ // Force-refresh provider discovery from Nostr (kind 38421)
135
+ const providers = await modelManager.bootstrapProviders(false, true);
136
+ console.log(`Discovered ${providers.length} providers from Nostr`);
137
+
138
+ // Force-refresh routstr21 models from Nostr (kind 38423)
139
+ const routstr21ModelIds = await modelManager.fetchRoutstr21Models(true);
140
+ console.log(`Fetched ${routstr21ModelIds.length} routstr21 model IDs from Nostr`);
141
+
142
+ // Force-refresh models from all providers
143
+ const models = await modelManager.fetchModels(providers, true);
144
+ console.log(`Fetched ${models.length} models from ${providers.length} providers`);
145
+
146
+ // Sync review events from Nostr (kind 38425) and apply disabled status
147
+ const reviewedDisabled = await modelManager.syncReviewedProvidersFromNostr(
148
+ providers,
149
+ undefined,
150
+ true,
151
+ );
152
+ if (reviewedDisabled.length > 0) {
153
+ console.log(
154
+ `Review sync disabled ${reviewedDisabled.length} provider(s): ${reviewedDisabled.join(", ")}`,
155
+ );
156
+ }
157
+
158
+ // Sync discovered providers into the store
159
+ const { baseUrlsList, setBaseUrlsList, disabledProviders, setDisabledProviders } =
160
+ store.getState() as any;
161
+
162
+ // Replace baseUrlsList with the fresh provider list
163
+ setBaseUrlsList(providers);
164
+
165
+ // Merge review-disabled providers into the store's disabled list
166
+ const existingDisabled = new Set(disabledProviders || []);
167
+ for (const url of reviewedDisabled) {
168
+ existingDisabled.add(url);
169
+ }
170
+ setDisabledProviders([...existingDisabled]);
171
+
172
+ console.log(
173
+ `Provider refresh complete: ${providers.length} total, ${existingDisabled.size} disabled`,
174
+ );
175
+ };
176
+
123
177
  return {
124
178
  ensureProvidersBootstrapped,
125
179
  getRoutstr21Models,
126
180
  getModelProviders,
181
+ refreshProvidersAndModels,
127
182
  };
128
183
  }
@@ -11,6 +11,12 @@ export type UsageData = {
11
11
  totalTokens: number;
12
12
  cost: number;
13
13
  satsCost: number;
14
+ provider?: string;
15
+ inputMsats?: number;
16
+ outputMsats?: number;
17
+ totalMsats?: number;
18
+ cacheReadInputTokens?: number;
19
+ cacheCreationInputTokens?: number;
14
20
  };
15
21
 
16
22
  export type UsageTrackingEntry = UsageData & {
@@ -118,11 +118,15 @@ export function getTodayStart(): number {
118
118
  }
119
119
 
120
120
  export function formatDate(timestamp: number): string {
121
- return new Date(timestamp).toISOString().split("T")[0] ?? "";
121
+ const d = new Date(timestamp);
122
+ const p = (n: number) => String(n).padStart(2, "0");
123
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
122
124
  }
123
125
 
124
126
  export function formatTime(timestamp: number): string {
125
- return new Date(timestamp).toISOString().split("T")[1]?.slice(0, 8) ?? "";
127
+ const d = new Date(timestamp);
128
+ const p = (n: number) => String(n).padStart(2, "0");
129
+ return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
126
130
  }
127
131
 
128
132
  export function formatNumber(n: number): string {
@@ -609,20 +609,35 @@ export function renderRecent(stats: UsageStats, width: number): string {
609
609
  if (recentEntries.length === 0) return renderBox(["No recent entries"], width, "Recent Requests");
610
610
 
611
611
  const clientCol = 14;
612
+ const tokensCol = 18;
613
+ const costCol = 18;
614
+ const providerCol = Math.max(16, width - 4 - 10 - 18 - tokensCol - costCol - clientCol - 5);
615
+ const msatsToSats = (msats?: number) => typeof msats === "number" ? msats / 1000 : 0;
612
616
  const lines: string[] = [];
613
- lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"TOKENS".padEnd(10)} ${"COST".padEnd(12)} ${"PROVIDER".padEnd(16)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
617
+ lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"I/CR/CW/O".padEnd(tokensCol)} ${"I/O/T in sats".padEnd(costCol)} ${"BASE:PROVIDER".padEnd(providerCol)} ${"CLIENT".slice(0, clientCol)}${COLORS.reset}`);
614
618
  lines.push(COLORS.dim + "─".repeat(width - 4) + COLORS.reset);
615
619
 
616
620
  for (const entry of recentEntries) {
617
621
  const time = formatTime(entry.timestamp).slice(0, 8);
618
622
  const model = entry.modelId.slice(0, 18).padEnd(18);
619
- const tokens = `${formatNumber(entry.totalTokens).padEnd(6)} (${formatNumber(entry.promptTokens)}+${formatNumber(entry.completionTokens)})`;
620
- const cost = `${formatCost(entry.satsCost).padEnd(8)} sats`;
621
- const provider = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "").slice(0, 16).padEnd(16);
623
+ const tokens = [
624
+ entry.promptTokens,
625
+ entry.cacheReadInputTokens || 0,
626
+ entry.cacheCreationInputTokens || 0,
627
+ entry.completionTokens,
628
+ ].map(formatNumber).join("/");
629
+ const totalSats = typeof entry.totalMsats === "number" ? entry.totalMsats / 1000 : entry.satsCost;
630
+ const cost = [
631
+ formatCost(msatsToSats(entry.inputMsats)),
632
+ formatCost(msatsToSats(entry.outputMsats)),
633
+ formatCost(totalSats),
634
+ ].join("/");
635
+ const baseUrl = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "");
636
+ const provider = `${baseUrl}:${entry.provider || "unknown"}`.slice(0, providerCol).padEnd(providerCol);
622
637
  const clientName = (entry.client || "unknown").slice(0, clientCol - 1);
623
638
  const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
624
639
  const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
625
- lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(10)} ${COLORS.green}${cost}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
640
+ lines.push(`${COLORS.dim}${time}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(tokensCol)} ${COLORS.green}${cost.padEnd(costCol)}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset}`);
626
641
  }
627
642
 
628
643
  return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
@@ -1,5 +1,4 @@
1
1
  import {
2
- callAuth,
3
2
  callDaemon,
4
3
  loadConfig,
5
4
  getDaemonBaseUrl,
@@ -57,7 +56,7 @@ export function getClientsFromStore(store: { getState(): any }): ClientEntry[] {
57
56
  * Use this when running remotely (CLI in remote mode).
58
57
  */
59
58
  export async function getClientsList(): Promise<ClientEntry[]> {
60
- const result = await callAuth("/clients");
59
+ const result = await callDaemon("/clients");
61
60
  const clients = (
62
61
  result.output as
63
62
  | {
@@ -108,7 +107,7 @@ export async function addDaemonClient(
108
107
  return { client, created: false };
109
108
  }
110
109
 
111
- const result = await callAuth("/clients/add", {
110
+ const result = await callDaemon("/clients/add", {
112
111
  method: "POST",
113
112
  body: { name, id: derivedId },
114
113
  });
@@ -160,7 +159,7 @@ export async function listClientsAction(): Promise<void> {
160
159
  export async function deleteClientAction(id: string): Promise<void> {
161
160
  await ensureDaemonRunning();
162
161
 
163
- const result = await callAuth("/clients/delete", {
162
+ const result = await callDaemon("/clients/delete", {
164
163
  method: "POST",
165
164
  body: { id },
166
165
  });
@@ -41,6 +41,9 @@ export interface RoutstrdConfig {
41
41
  nsec?: string;
42
42
  /** Nostr hex pubkey for routstr review/model events (kind 38425/38423). */
43
43
  routstrPubkey?: string;
44
+ /** Nostr relay URLs for provider/model discovery (kinds 38421/38423/38425).
45
+ * When unset, each method uses its own built-in defaults. */
46
+ relays?: string[];
44
47
  /** NWC integration configuration */
45
48
  nwc?: NwcConfig;
46
49
  }
package/new-task.md DELETED
@@ -1,60 +0,0 @@
1
- # Hot-reload NWC connection (remove restart requirement)
2
-
3
- ## Problem
4
-
5
- `nwc connect` and `nwc disconnect` require a daemon restart to take effect. The
6
- CLI writes the new connection string to disk, but the daemon never re-reads it.
7
-
8
- ## Current flow
9
-
10
- 1. **Startup** — `createWalletAdapter` reads `options.nwcConnectionString` and
11
- creates a `WalletConnect` (relay pool + NWC wallet service) once:
12
- ```ts
13
- // src/daemon/wallet/index.ts:70-78
14
- if (options.nwcConnectionString) {
15
- wallet = WalletConnect.fromConnectURI(options.nwcConnectionString, { pool });
16
- }
17
- ```
18
-
19
- 2. **CLI `nwc connect`** — HTTP handler at `POST /nwc/connect` only calls
20
- `saveDaemonConfig(config)`. The running `WalletConnect` instance is untouched.
21
-
22
- 3. **Result** — the old connection (or lack thereof) stays active until restart.
23
-
24
- ## What needs to happen
25
-
26
- Provide a way for the daemon to tear down the old `WalletConnect` and create a
27
- new one from the updated connection string, triggered by the HTTP handler,
28
- **without restarting the process**.
29
-
30
- ### Key pieces
31
-
32
- - **`src/daemon/wallet/index.ts`** — `createWalletAdapter` needs to expose a
33
- `reconnect(connectionString: string)` method that:
34
- - Closes the old relay pool / WalletConnect
35
- - Creates a new `WalletConnect` from the new connection string
36
- - Updates the adapter's internal `wallet` reference
37
- - If connection string is empty/undefined, disconnects and sets `wallet` to
38
- `undefined`
39
-
40
- - **`src/daemon/http/index.ts`** — `POST /nwc/connect` and `POST /nwc/disconnect`
41
- handlers should call `reconnect()` after saving config, and drop the "Restart
42
- daemon" message.
43
-
44
- - **`src/daemon/index.ts`** — may need to store the `reconnect` function and pass
45
- it through `DaemonDeps` to the HTTP handler.
46
-
47
- - **`src/cli.ts`** — remove `console.log("\nRun 'routstrd restart' to connect...")`
48
- from the connect/disconnect commands.
49
-
50
- ### Considerations
51
-
52
- - The `WalletConnect` instance is referenced in the auto-refill loop (passed to
53
- `startAutoRefillLoop`). After a reconnect, the auto-refill loop's captured
54
- `wallet` reference would be stale. The loop already reads config from disk
55
- each cycle, so it should also reference the latest `wallet` instance.
56
- - Could make the auto-refill loop accept a `getWallet: () => WalletConnect |
57
- undefined` getter, same pattern as the config getter.
58
- - Relay pool cleanup — `applesauce-wallet-connect` / `applesauce-relay` may need
59
- explicit `pool.close()` or `pool.destroy()` to free connections.
60
- - Edge case: a refill is in progress when reconnect is triggered.