routstrd 0.3.0 → 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
@@ -9530,15 +9530,19 @@ async function loadConfig() {
9530
9530
  function getDaemonBaseUrl(config) {
9531
9531
  return config.daemonUrl?.replace(/\/$/, "") || `http://localhost:${config.port}`;
9532
9532
  }
9533
- async function callDaemon(path, options = {}) {
9533
+ function getAuthBaseUrl(config) {
9534
+ if (config.authUrl) {
9535
+ return config.authUrl.replace(/\/$/, "");
9536
+ }
9537
+ return getDaemonBaseUrl(config);
9538
+ }
9539
+ async function _callUrl(baseUrl, path, options, config) {
9534
9540
  const { method = "GET", body } = options;
9535
- const config = await loadConfig();
9536
- const baseUrl = getDaemonBaseUrl(config);
9537
9541
  const url = `${baseUrl}${path}`;
9538
9542
  const bodyString = body ? JSON.stringify(body) : undefined;
9539
9543
  const bodyBytes = bodyString ? new TextEncoder().encode(bodyString) : undefined;
9540
9544
  let authorization;
9541
- if (config.daemonUrl && config.nsec) {
9545
+ if ((config.daemonUrl || config.authUrl) && config.nsec) {
9542
9546
  const secretKey = parseSecretKey(config.nsec);
9543
9547
  authorization = await createNIP98Authorization(secretKey, url, method, bodyBytes);
9544
9548
  }
@@ -9556,6 +9560,16 @@ async function callDaemon(path, options = {}) {
9556
9560
  }
9557
9561
  return response.json();
9558
9562
  }
9563
+ async function callDaemon(path, options = {}) {
9564
+ const config = await loadConfig();
9565
+ const baseUrl = getDaemonBaseUrl(config);
9566
+ return _callUrl(baseUrl, path, options, config);
9567
+ }
9568
+ async function callAuth(path, options = {}) {
9569
+ const config = await loadConfig();
9570
+ const baseUrl = getAuthBaseUrl(config);
9571
+ return _callUrl(baseUrl, path, options, config);
9572
+ }
9559
9573
  async function isDaemonRunning() {
9560
9574
  try {
9561
9575
  const config = await loadConfig();
@@ -13929,10 +13943,14 @@ function getTodayStart() {
13929
13943
  return new Date(now2.getFullYear(), now2.getMonth(), now2.getDate()).getTime();
13930
13944
  }
13931
13945
  function formatDate(timestamp) {
13932
- 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())}`;
13933
13949
  }
13934
13950
  function formatTime(timestamp) {
13935
- 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())}`;
13936
13954
  }
13937
13955
  function formatNumber(n) {
13938
13956
  if (n >= 1e6)
@@ -14723,19 +14741,34 @@ function renderRecent(stats, width) {
14723
14741
  if (recentEntries.length === 0)
14724
14742
  return renderBox(["No recent entries"], width, "Recent Requests");
14725
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;
14726
14748
  const lines = [];
14727
- 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}`);
14728
14750
  lines.push(COLORS.dim + "\u2500".repeat(width - 4) + COLORS.reset);
14729
14751
  for (const entry of recentEntries) {
14730
14752
  const time = formatTime(entry.timestamp).slice(0, 8);
14731
14753
  const model = entry.modelId.slice(0, 18).padEnd(18);
14732
- const tokens = `${formatNumber(entry.totalTokens).padEnd(6)} (${formatNumber(entry.promptTokens)}+${formatNumber(entry.completionTokens)})`;
14733
- const cost = `${formatCost(entry.satsCost).padEnd(8)} sats`;
14734
- 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);
14735
14768
  const clientName = (entry.client || "unknown").slice(0, clientCol - 1);
14736
14769
  const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
14737
14770
  const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
14738
- 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}`);
14739
14772
  }
14740
14773
  return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
14741
14774
  }
@@ -15066,7 +15099,6 @@ init_logger();
15066
15099
  import { join as join3 } from "path";
15067
15100
 
15068
15101
  // src/integrations/opencode.ts
15069
- init_logger();
15070
15102
  init_daemon_client();
15071
15103
  import { existsSync as existsSync3, mkdirSync } from "fs";
15072
15104
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
@@ -15074,9 +15106,9 @@ import { dirname as dirname2 } from "path";
15074
15106
  var OPENCODE_SMALL_MODEL = "routstr/minimax-m2.5";
15075
15107
  async function installOpencodeIntegration(config, apiKey, integrationConfig) {
15076
15108
  const { name, configPath } = integrationConfig;
15077
- logger.log(`
15109
+ console.log(`
15078
15110
  Installing routstr models in opencode.json...`);
15079
- logger.log(`Using API key for ${name}`);
15111
+ console.log(`Using API key for ${name}`);
15080
15112
  const baseUrl = getDaemonBaseUrl(config);
15081
15113
  let opencodeConfig;
15082
15114
  try {
@@ -15097,7 +15129,7 @@ Installing routstr models in opencode.json...`);
15097
15129
  const data = await callDaemon("/models");
15098
15130
  const models = data.output?.models || [];
15099
15131
  if (models.length === 0) {
15100
- logger.log("No models found from routstr daemon.");
15132
+ console.log("No models found from routstr daemon.");
15101
15133
  return;
15102
15134
  }
15103
15135
  const modelsObj = {};
@@ -15116,23 +15148,22 @@ Installing routstr models in opencode.json...`);
15116
15148
  };
15117
15149
  opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
15118
15150
  await writeFile2(configPath, JSON.stringify(opencodeConfig, null, 2));
15119
- logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
15151
+ console.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
15120
15152
  } catch (error) {
15121
- logger.error("Failed to install models in opencode.json:", error);
15153
+ console.error("Failed to install models in opencode.json:", error);
15122
15154
  }
15123
15155
  }
15124
15156
 
15125
15157
  // src/integrations/pi.ts
15126
- init_logger();
15127
15158
  init_daemon_client();
15128
15159
  import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
15129
15160
  import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
15130
15161
  import { dirname as dirname3 } from "path";
15131
15162
  async function installPiIntegration(config, apiKey, integrationConfig) {
15132
15163
  const { name, configPath } = integrationConfig;
15133
- logger.log(`
15164
+ console.log(`
15134
15165
  Installing routstr models in pi models.json...`);
15135
- logger.log(`Using API key for ${name}`);
15166
+ console.log(`Using API key for ${name}`);
15136
15167
  const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
15137
15168
  let piConfig = {};
15138
15169
  try {
@@ -15151,12 +15182,16 @@ Installing routstr models in pi models.json...`);
15151
15182
  const data = await callDaemon("/models");
15152
15183
  const models = data.output?.models || [];
15153
15184
  if (models.length === 0) {
15154
- logger.log("No models found from routstr daemon.");
15185
+ console.log("No models found from routstr daemon.");
15155
15186
  return;
15156
15187
  }
15157
- const providerModels = models.map((model) => ({
15158
- id: model.id
15159
- }));
15188
+ const providerModels = models.map((model) => {
15189
+ const entry = { id: model.id };
15190
+ if (model.context_length !== undefined && model.context_length > 0) {
15191
+ entry.contextWindow = model.context_length;
15192
+ }
15193
+ return entry;
15194
+ });
15160
15195
  piConfig.providers["routstr"] = {
15161
15196
  baseUrl,
15162
15197
  api: "openai-completions",
@@ -15164,14 +15199,13 @@ Installing routstr models in pi models.json...`);
15164
15199
  models: providerModels
15165
15200
  };
15166
15201
  await writeFile3(configPath, JSON.stringify(piConfig, null, 2));
15167
- logger.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
15202
+ console.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
15168
15203
  } catch (error) {
15169
- logger.error("Failed to install models in pi models.json:", error);
15204
+ console.error("Failed to install models in pi models.json:", error);
15170
15205
  }
15171
15206
  }
15172
15207
 
15173
15208
  // src/integrations/openclaw.ts
15174
- init_logger();
15175
15209
  init_daemon_client();
15176
15210
  import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
15177
15211
  import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
@@ -15181,9 +15215,9 @@ var OPENCLAW_DEFAULT_PRIMARY_MODEL = "routstr/minimax-m2.5";
15181
15215
  var OPENCLAW_DEFAULT_FALLBACK_MODEL = "routstr/kimi-k2.5";
15182
15216
  async function installOpenClawIntegration(config, apiKey, integrationConfig) {
15183
15217
  const { name, configPath } = integrationConfig;
15184
- logger.log(`
15218
+ console.log(`
15185
15219
  Installing routstr models in openclaw.json...`);
15186
- logger.log(`Using API key for ${name}`);
15220
+ console.log(`Using API key for ${name}`);
15187
15221
  const baseUrl = getDaemonBaseUrl(config);
15188
15222
  let openclawConfig = {};
15189
15223
  try {
@@ -15211,7 +15245,7 @@ Installing routstr models in openclaw.json...`);
15211
15245
  const data = await callDaemon("/models");
15212
15246
  const models = data.output?.models || [];
15213
15247
  if (models.length === 0) {
15214
- logger.log("No models found from routstr daemon.");
15248
+ console.log("No models found from routstr daemon.");
15215
15249
  return;
15216
15250
  }
15217
15251
  const providerModels = models.map((model) => ({
@@ -15240,23 +15274,22 @@ Installing routstr models in openclaw.json...`);
15240
15274
  };
15241
15275
  }
15242
15276
  await writeFile4(configPath, JSON.stringify(openclawConfig, null, 2));
15243
- logger.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
15277
+ console.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
15244
15278
  } catch (error) {
15245
- logger.error("Failed to install models in openclaw.json:", error);
15279
+ console.error("Failed to install models in openclaw.json:", error);
15246
15280
  }
15247
15281
  }
15248
15282
 
15249
15283
  // src/integrations/claudecode.ts
15250
- init_logger();
15251
15284
  init_daemon_client();
15252
15285
  import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
15253
15286
  import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
15254
15287
  import { dirname as dirname5 } from "path";
15255
15288
  async function installClaudeCodeIntegration(config, apiKey, integrationConfig) {
15256
15289
  const { name, configPath } = integrationConfig;
15257
- logger.log(`
15290
+ console.log(`
15258
15291
  Installing routstr configuration in ${configPath}...`);
15259
- logger.log(`Using API key for ${name}`);
15292
+ console.log(`Using API key for ${name}`);
15260
15293
  const baseUrl = getDaemonBaseUrl(config);
15261
15294
  let settings = {};
15262
15295
  try {
@@ -15265,7 +15298,7 @@ Installing routstr configuration in ${configPath}...`);
15265
15298
  settings = JSON.parse(content);
15266
15299
  }
15267
15300
  } catch (error) {
15268
- logger.error(`Error reading ${configPath}, creating new one.`);
15301
+ console.error(`Error reading ${configPath}, creating new one.`);
15269
15302
  }
15270
15303
  if (!settings.env) {
15271
15304
  settings.env = {};
@@ -15282,39 +15315,38 @@ Installing routstr configuration in ${configPath}...`);
15282
15315
  settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
15283
15316
  settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
15284
15317
  settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
15285
- logger.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
15318
+ console.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
15286
15319
  } else if (models.length > 0) {
15287
15320
  const model = models[0];
15288
- logger.log(`Only ${models.length} models available, falling back to defaults.`);
15321
+ console.log(`Only ${models.length} models available, falling back to defaults.`);
15289
15322
  settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model.id;
15290
15323
  settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model.id;
15291
15324
  settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model.id;
15292
15325
  } else {
15293
- logger.log("No models available from routstr daemon.");
15326
+ console.log("No models available from routstr daemon.");
15294
15327
  }
15295
15328
  } catch (error) {
15296
- logger.error("Failed to fetch models for Claude Code integration:", error);
15329
+ console.error("Failed to fetch models for Claude Code integration:", error);
15297
15330
  }
15298
15331
  try {
15299
15332
  mkdirSync4(dirname5(configPath), { recursive: true });
15300
15333
  await writeFile5(configPath, JSON.stringify(settings, null, 2));
15301
- logger.log(`Successfully updated ${configPath} with routstr settings.`);
15334
+ console.log(`Successfully updated ${configPath} with routstr settings.`);
15302
15335
  } catch (error) {
15303
- logger.error(`Failed to write to ${configPath}:`, error);
15336
+ console.error(`Failed to write to ${configPath}:`, error);
15304
15337
  }
15305
15338
  }
15306
15339
 
15307
15340
  // src/integrations/hermes.ts
15308
- init_logger();
15309
15341
  init_daemon_client();
15310
15342
  import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
15311
15343
  import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
15312
15344
  import { dirname as dirname6 } from "path";
15313
15345
  async function installHermesIntegration(config, apiKey, integrationConfig) {
15314
15346
  const { name, configPath } = integrationConfig;
15315
- logger.log(`
15347
+ console.log(`
15316
15348
  Installing routstr configuration in ${configPath}...`);
15317
- logger.log(`Using API key for ${name}`);
15349
+ console.log(`Using API key for ${name}`);
15318
15350
  const baseUrl = getDaemonBaseUrl(config);
15319
15351
  const baseUrlV1 = `${baseUrl}/v1`;
15320
15352
  let defaultModel = "minimax-m2.7";
@@ -15323,16 +15355,16 @@ Installing routstr configuration in ${configPath}...`);
15323
15355
  const models = data.output?.models || [];
15324
15356
  if (models.length >= 3) {
15325
15357
  defaultModel = models[2].id;
15326
- logger.log(`Set default model to 3rd available model: ${defaultModel}`);
15358
+ console.log(`Set default model to 3rd available model: ${defaultModel}`);
15327
15359
  } else if (models.length > 0) {
15328
15360
  defaultModel = models[0].id;
15329
- logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
15361
+ console.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
15330
15362
  } else {
15331
- logger.log("No models available from routstr daemon, using fallback default.");
15363
+ console.log("No models available from routstr daemon, using fallback default.");
15332
15364
  }
15333
15365
  } catch (error) {
15334
- logger.error("Failed to fetch models for Hermes integration:", error);
15335
- logger.log("Using fallback default model.");
15366
+ console.error("Failed to fetch models for Hermes integration:", error);
15367
+ console.log("Using fallback default model.");
15336
15368
  }
15337
15369
  let content = "";
15338
15370
  try {
@@ -15340,7 +15372,7 @@ Installing routstr configuration in ${configPath}...`);
15340
15372
  content = await readFile6(configPath, "utf-8");
15341
15373
  }
15342
15374
  } catch (error) {
15343
- logger.error(`Error reading ${configPath}, creating new one.`);
15375
+ console.error(`Error reading ${configPath}, creating new one.`);
15344
15376
  }
15345
15377
  content = content.replace(/^model:\n(?: .*\n)*/gm, "");
15346
15378
  content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
@@ -15370,9 +15402,9 @@ Installing routstr configuration in ${configPath}...`);
15370
15402
  try {
15371
15403
  mkdirSync5(dirname6(configPath), { recursive: true });
15372
15404
  await writeFile6(configPath, newContent);
15373
- logger.log(`Successfully updated ${configPath} with routstr settings.`);
15405
+ console.log(`Successfully updated ${configPath} with routstr settings.`);
15374
15406
  } catch (error) {
15375
- logger.error(`Failed to write to ${configPath}:`, error);
15407
+ console.error(`Failed to write to ${configPath}:`, error);
15376
15408
  }
15377
15409
  }
15378
15410
 
@@ -15618,14 +15650,14 @@ function parseChoice(input) {
15618
15650
  return 1;
15619
15651
  }
15620
15652
  async function setupIntegration(config) {
15621
- logger.log(`
15653
+ console.log(`
15622
15654
  Choose an integration to set up:`);
15623
- logger.log("1. OpenCode (default)");
15624
- logger.log("2. OpenClaw");
15625
- logger.log("3. Pi");
15626
- logger.log("4. Claude Code");
15627
- logger.log("5. Hermes");
15628
- logger.log("6. Skip for now");
15655
+ console.log("1. OpenCode (default)");
15656
+ console.log("2. OpenClaw");
15657
+ console.log("3. Pi");
15658
+ console.log("4. Claude Code");
15659
+ console.log("5. Hermes");
15660
+ console.log("6. Skip for now");
15629
15661
  const answer = await ask("Select integration [1]: ");
15630
15662
  const choice = parseChoice(answer);
15631
15663
  const integrationByChoice = {
@@ -15637,15 +15669,15 @@ Choose an integration to set up:`);
15637
15669
  };
15638
15670
  const key = integrationByChoice[choice];
15639
15671
  if (!key) {
15640
- logger.log("Skipping integration setup.");
15672
+ console.log("Skipping integration setup.");
15641
15673
  return;
15642
15674
  }
15643
15675
  const integrationConfig = CLIENT_CONFIGS[key];
15644
15676
  const { client, created } = await addDaemonClient(integrationConfig.name);
15645
15677
  if (created) {
15646
- logger.log(`Created new API key for ${integrationConfig.name}`);
15678
+ console.log(`Created new API key for ${integrationConfig.name}`);
15647
15679
  } else {
15648
- logger.log(`Using existing API key for ${integrationConfig.name}`);
15680
+ console.log(`Using existing API key for ${integrationConfig.name}`);
15649
15681
  }
15650
15682
  if (key === "opencode") {
15651
15683
  await installOpencodeIntegration(config, client.apiKey, integrationConfig);
@@ -15770,7 +15802,7 @@ async function isCocodInstalled(cocodPath) {
15770
15802
  // package.json
15771
15803
  var package_default = {
15772
15804
  name: "routstrd",
15773
- version: "0.3.0",
15805
+ version: "0.3.2",
15774
15806
  module: "src/index.ts",
15775
15807
  type: "module",
15776
15808
  private: false,
@@ -15794,7 +15826,7 @@ var package_default = {
15794
15826
  },
15795
15827
  dependencies: {
15796
15828
  "@cashu/cashu-ts": "^4.3.0",
15797
- "@routstr/sdk": "^0.3.8",
15829
+ "@routstr/sdk": "^0.3.10",
15798
15830
  "applesauce-core": "^5.1.0",
15799
15831
  "applesauce-relay": "^5.1.0",
15800
15832
  "applesauce-wallet-connect": "^6.0.0",
@@ -15829,17 +15861,17 @@ Invoice:
15829
15861
  ${invoice}`);
15830
15862
  }
15831
15863
  async function installCocodOrExit() {
15832
- logger.log("cocod not found. Installing globally with bun...");
15864
+ console.log("cocod not found. Installing globally with bun...");
15833
15865
  const installProc = Bun.spawn(["bun", "install", "--global", "@routstr/cocod"], {
15834
15866
  stdout: "inherit",
15835
15867
  stderr: "inherit"
15836
15868
  });
15837
15869
  const installCode = await installProc.exited;
15838
15870
  if (installCode !== 0 || !await isCocodInstalled()) {
15839
- logger.error("Failed to install cocod. Please run 'bun install --global @routstr/cocod' manually.");
15871
+ console.error("Failed to install cocod. Please run 'bun install --global @routstr/cocod' manually.");
15840
15872
  throw new Error("cocod installation failed");
15841
15873
  }
15842
- logger.log("cocod installed successfully.");
15874
+ console.log("cocod installed successfully.");
15843
15875
  }
15844
15876
  async function requireLocalDaemon() {
15845
15877
  const config = await loadConfig();
@@ -15849,10 +15881,10 @@ async function requireLocalDaemon() {
15849
15881
  }
15850
15882
  }
15851
15883
  async function initDaemon() {
15852
- logger.log("Initializing routstrd...");
15884
+ console.log("Initializing routstrd...");
15853
15885
  if (!existsSync9(CONFIG_DIR)) {
15854
15886
  mkdirSync6(CONFIG_DIR, { recursive: true });
15855
- logger.log(`Created config directory: ${CONFIG_DIR}`);
15887
+ console.log(`Created config directory: ${CONFIG_DIR}`);
15856
15888
  }
15857
15889
  if (!existsSync9(CONFIG_FILE)) {
15858
15890
  const config2 = {
@@ -15860,9 +15892,21 @@ async function initDaemon() {
15860
15892
  cocodPath: null
15861
15893
  };
15862
15894
  await Bun.write(CONFIG_FILE, JSON.stringify(config2, null, 2));
15863
- logger.log(`Created config file: ${CONFIG_FILE}`);
15895
+ console.log(`Created config file: ${CONFIG_FILE}`);
15864
15896
  }
15865
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
+ }
15866
15910
  if (!await isCocodInstalled(config.cocodPath)) {
15867
15911
  if (config.cocodPath) {
15868
15912
  logger.error(`Configured cocod executable was not found: ${config.cocodPath}`);
@@ -15903,23 +15947,23 @@ Initializing cocod...`);
15903
15947
  ${initStderr}`.toLowerCase();
15904
15948
  const alreadyInitialized = combinedOutput.includes("already initialized");
15905
15949
  if (initCode !== 0 && !alreadyInitialized) {
15906
- logger.error("Failed to initialize cocod. Please run 'cocod init' manually.");
15950
+ console.error("Failed to initialize cocod. Please run 'cocod init' manually.");
15907
15951
  return;
15908
15952
  }
15909
15953
  if (alreadyInitialized) {
15910
- logger.log("cocod is already initialized.");
15954
+ console.log("cocod is already initialized.");
15911
15955
  } else {
15912
- logger.log("cocod initialized successfully.");
15956
+ console.log("cocod initialized successfully.");
15913
15957
  }
15914
15958
  await startDaemon({ port: String(config.port || 8008) });
15915
15959
  await setupIntegration(config);
15916
- logger.log(`
15960
+ console.log(`
15917
15961
  Initialization complete!`);
15918
- logger.log(`
15962
+ console.log(`
15919
15963
  use 'routstrd receive <cashu-token>' or 'routstrd receive 2100' to top up your local wallet using Lightning!`);
15920
- logger.log(`
15964
+ console.log(`
15921
15965
  full wallet commands still work too, e.g. 'routstrd wallet receive cashu <token>' and 'routstrd wallet receive bolt11 2100'.`);
15922
- logger.log(`
15966
+ console.log(`
15923
15967
  To ensure routstrd persists across system restarts, run: 'routstrd service install'`);
15924
15968
  }
15925
15969
  program.name("routstrd").description("Routstr daemon - Manage routstr processes").version(package_default.version, "--version", "output the version number");
@@ -15992,18 +16036,29 @@ Results:`);
15992
16036
  process.exit(1);
15993
16037
  }
15994
16038
  });
15995
- program.command("remote <url>").description("Configure a remote daemon URL").action(async (url) => {
16039
+ program.command("remote <url>").description("Configure a remote daemon URL").option("--auth-url <authUrl>", "URL of the auth proxy for management commands (npubs, clients, usage)").action(async (url, options) => {
15996
16040
  try {
15997
16041
  new URL(url);
15998
16042
  } catch {
15999
16043
  console.error(`Invalid URL: ${url}`);
16000
16044
  process.exit(1);
16001
16045
  }
16046
+ if (options.authUrl) {
16047
+ try {
16048
+ new URL(options.authUrl);
16049
+ } catch {
16050
+ console.error(`Invalid auth URL: ${options.authUrl}`);
16051
+ process.exit(1);
16052
+ }
16053
+ }
16002
16054
  if (!existsSync9(CONFIG_DIR)) {
16003
16055
  mkdirSync6(CONFIG_DIR, { recursive: true });
16004
16056
  }
16005
16057
  const config = await loadConfig();
16006
16058
  const updates = { daemonUrl: url };
16059
+ if (options.authUrl) {
16060
+ updates.authUrl = options.authUrl;
16061
+ }
16007
16062
  let generatedNpub;
16008
16063
  if (!config.nsec) {
16009
16064
  const secretKey = generateSecretKey();
@@ -16018,6 +16073,9 @@ program.command("remote <url>").description("Configure a remote daemon URL").act
16018
16073
  };
16019
16074
  await Bun.write(CONFIG_FILE, JSON.stringify(updatedConfig, null, 2));
16020
16075
  console.log(`Remote daemon URL set to: ${url}`);
16076
+ if (options.authUrl) {
16077
+ console.log(`Auth proxy URL set to: ${options.authUrl}`);
16078
+ }
16021
16079
  if (generatedNpub) {
16022
16080
  console.log(`
16023
16081
  A new Nostr identity has been generated for remote authentication.`);
@@ -16195,7 +16253,7 @@ program.command("usage").description("Show recent usage logs and total sats cost
16195
16253
  await ensureDaemonRunning();
16196
16254
  const requested = Number.parseInt(options.limit, 10);
16197
16255
  const limit = Number.isFinite(requested) && requested > 0 ? Math.min(requested, 1000) : 10;
16198
- const result = await callDaemon(`/usage?limit=${limit}`);
16256
+ const result = await callAuth(`/usage?limit=${limit}`);
16199
16257
  if (result.error) {
16200
16258
  console.log(result.error);
16201
16259
  process.exit(1);
@@ -16222,9 +16280,10 @@ program.command("usage").description("Show recent usage logs and total sats cost
16222
16280
  });
16223
16281
  });
16224
16282
  var providersCmd = program.command("providers").description("List and manage providers");
16225
- 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) => {
16226
16284
  await ensureDaemonRunning();
16227
- const result = await callDaemon("/providers");
16285
+ const query = options.refresh ? "/providers?refresh=true" : "/providers";
16286
+ const result = await callDaemon(query);
16228
16287
  if (result.error) {
16229
16288
  console.log(result.error);
16230
16289
  process.exit(1);
@@ -16302,7 +16361,7 @@ npubsCmd.command("list").description("List configured npubs with their roles").a
16302
16361
  await ensureDaemonRunning();
16303
16362
  const config = await loadConfig();
16304
16363
  const userNpub = getUserNpub(config);
16305
- const result = await callDaemon("/npubs");
16364
+ const result = await callAuth("/npubs");
16306
16365
  if (result.error) {
16307
16366
  console.log(result.error);
16308
16367
  process.exit(1);
@@ -16335,7 +16394,7 @@ npubsCmd.command("register").description("Register yourself as the first admin (
16335
16394
  console.error("No Nostr identity configured. Run 'routstrd remote <url>' to set one up.");
16336
16395
  process.exit(1);
16337
16396
  }
16338
- const result = await callDaemon("/npubs");
16397
+ const result = await callAuth("/npubs");
16339
16398
  if (result.error) {
16340
16399
  console.log(result.error);
16341
16400
  process.exit(1);
@@ -16352,7 +16411,7 @@ npubsCmd.command("register").description("Register yourself as the first admin (
16352
16411
  console.error("Failed to normalize user npub.");
16353
16412
  process.exit(1);
16354
16413
  }
16355
- const addResult = await callDaemon("/npubs", {
16414
+ const addResult = await callAuth("/npubs", {
16356
16415
  method: "POST",
16357
16416
  body: { npub: npubFromPubkey(normalized) }
16358
16417
  });
@@ -16379,7 +16438,7 @@ npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...).
16379
16438
  process.exit(1);
16380
16439
  }
16381
16440
  const body = { npub: npubFromPubkey(normalized), role: options.role };
16382
- const result = await callDaemon("/npubs", {
16441
+ const result = await callAuth("/npubs", {
16383
16442
  method: "POST",
16384
16443
  body
16385
16444
  });
@@ -16403,7 +16462,7 @@ npubsCmd.command("update <npub>").description("Update the role of an existing np
16403
16462
  console.error("Invalid role. Expected 'admin' or 'user'.");
16404
16463
  process.exit(1);
16405
16464
  }
16406
- const result = await callDaemon("/npubs", {
16465
+ const result = await callAuth("/npubs", {
16407
16466
  method: "PATCH",
16408
16467
  body: { npub: npubFromPubkey(normalized), role: options.role }
16409
16468
  });
@@ -16425,7 +16484,7 @@ npubsCmd.command("delete <npub>").description("Delete an npub (hex pubkey or npu
16425
16484
  console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
16426
16485
  process.exit(1);
16427
16486
  }
16428
- const result = await callDaemon(`/npubs/${encodeURIComponent(npubFromPubkey(normalized))}`, {
16487
+ const result = await callAuth(`/npubs/${encodeURIComponent(npubFromPubkey(normalized))}`, {
16429
16488
  method: "DELETE"
16430
16489
  });
16431
16490
  if (result.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.3.0",
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.8",
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