routstrd 0.2.15 → 0.2.17

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.
@@ -37216,7 +37216,7 @@ var init_dist3 = __esm(() => {
37216
37216
  // src/daemon/index.ts
37217
37217
  init_dist3();
37218
37218
  import { createServer } from "http";
37219
- import { existsSync as existsSync8 } from "fs";
37219
+ import { existsSync as existsSync9 } from "fs";
37220
37220
 
37221
37221
  // src/utils/config.ts
37222
37222
  var HOME = process.env.HOME || process.env.USERPROFILE || "";
@@ -37553,6 +37553,7 @@ var createBunSqliteUsageTrackingDriver2 = (options = {}) => {
37553
37553
 
37554
37554
  // src/daemon/wallet/index.ts
37555
37555
  init_cashu_ts_es();
37556
+ init_dist3();
37556
37557
 
37557
37558
  // src/daemon/wallet/cocod-client.ts
37558
37559
  import { createHash } from "crypto";
@@ -37908,6 +37909,9 @@ async function createWalletAdapter(options = {}) {
37908
37909
  await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
37909
37910
  continue;
37910
37911
  }
37912
+ if (errorMessage.includes("Not enough proofs")) {
37913
+ throw new InsufficientBalanceError(amount, 0);
37914
+ }
37911
37915
  logger3.error("Error in walletAdapter sendToken:", error);
37912
37916
  throw error;
37913
37917
  }
@@ -37940,7 +37944,7 @@ async function createWalletAdapter(options = {}) {
37940
37944
  }
37941
37945
 
37942
37946
  // src/daemon/models.ts
37943
- function createModelService(modelManager) {
37947
+ function createModelService(modelManager, store) {
37944
37948
  let providerBootstrapPromise = null;
37945
37949
  const ensureProvidersBootstrapped = () => {
37946
37950
  if (!providerBootstrapPromise) {
@@ -37949,6 +37953,16 @@ function createModelService(modelManager) {
37949
37953
  const providers = await modelManager.bootstrapProviders(false);
37950
37954
  logger3.log(`Bootstrapped ${providers.length} providers`);
37951
37955
  await modelManager.fetchModels(providers);
37956
+ const { baseUrlsList, setBaseUrlsList } = store.getState();
37957
+ const existing = new Set(baseUrlsList);
37958
+ const merged = [
37959
+ ...baseUrlsList,
37960
+ ...providers.filter((url2) => !existing.has(url2))
37961
+ ];
37962
+ if (merged.length !== baseUrlsList.length) {
37963
+ setBaseUrlsList(merged);
37964
+ logger3.log(`Synced ${merged.length - baseUrlsList.length} new provider(s) into store`);
37965
+ }
37952
37966
  logger3.log("Provider bootstrap complete.");
37953
37967
  })().catch((error) => {
37954
37968
  logger3.error("Provider bootstrap failed:", error);
@@ -42368,6 +42382,76 @@ Installing routstr configuration in ${configPath}...`);
42368
42382
  }
42369
42383
  }
42370
42384
 
42385
+ // src/integrations/hermes.ts
42386
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
42387
+ import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
42388
+ import { dirname as dirname6 } from "path";
42389
+ async function installHermesIntegration(config, apiKey, integrationConfig) {
42390
+ const { name, configPath } = integrationConfig;
42391
+ logger3.log(`
42392
+ Installing routstr configuration in ${configPath}...`);
42393
+ logger3.log(`Using API key for ${name}`);
42394
+ const baseUrl = getDaemonBaseUrl(config);
42395
+ const baseUrlV1 = `${baseUrl}/v1`;
42396
+ let defaultModel = "minimax-m2.7";
42397
+ try {
42398
+ const data = await callDaemon("/models");
42399
+ const models = data.output?.models || [];
42400
+ if (models.length >= 3) {
42401
+ defaultModel = models[2].id;
42402
+ logger3.log(`Set default model to 3rd available model: ${defaultModel}`);
42403
+ } else if (models.length > 0) {
42404
+ defaultModel = models[0].id;
42405
+ logger3.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
42406
+ } else {
42407
+ logger3.log("No models available from routstr daemon, using fallback default.");
42408
+ }
42409
+ } catch (error) {
42410
+ logger3.error("Failed to fetch models for Hermes integration:", error);
42411
+ logger3.log("Using fallback default model.");
42412
+ }
42413
+ let content2 = "";
42414
+ try {
42415
+ if (existsSync8(configPath)) {
42416
+ content2 = await readFile6(configPath, "utf-8");
42417
+ }
42418
+ } catch (error) {
42419
+ logger3.error(`Error reading ${configPath}, creating new one.`);
42420
+ }
42421
+ content2 = content2.replace(/^model:\n(?: .*\n)*/gm, "");
42422
+ content2 = content2.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
42423
+ content2 = content2.replace(/\n{3,}/g, `
42424
+
42425
+ `).trim();
42426
+ const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
42427
+ const modelBlock = `model:
42428
+ default: ${defaultModel}
42429
+ provider: custom
42430
+ base_url: ${baseUrlV1}
42431
+ api_key: ${apiKey}`;
42432
+ const providerBlock = `custom_providers:
42433
+ - name: Routstr (${urlDisplay})
42434
+ base_url: ${baseUrlV1}
42435
+ api_key: ${apiKey}
42436
+ model: ${defaultModel}`;
42437
+ const parts = [modelBlock];
42438
+ if (content2) {
42439
+ parts.push(content2);
42440
+ }
42441
+ parts.push(providerBlock);
42442
+ const newContent = parts.join(`
42443
+
42444
+ `) + `
42445
+ `;
42446
+ try {
42447
+ mkdirSync5(dirname6(configPath), { recursive: true });
42448
+ await writeFile6(configPath, newContent);
42449
+ logger3.log(`Successfully updated ${configPath} with routstr settings.`);
42450
+ } catch (error) {
42451
+ logger3.error(`Failed to write to ${configPath}:`, error);
42452
+ }
42453
+ }
42454
+
42371
42455
  // src/integrations/registry.ts
42372
42456
  var CLIENT_CONFIGS = {
42373
42457
  opencode: {
@@ -42389,13 +42473,19 @@ var CLIENT_CONFIGS = {
42389
42473
  clientId: "claude-code",
42390
42474
  name: "Claude Code",
42391
42475
  configPath: join5(process.env.HOME || "", ".claude/settings.json")
42476
+ },
42477
+ hermes: {
42478
+ clientId: "hermes",
42479
+ name: "Hermes",
42480
+ configPath: join5(process.env.HOME || "", ".hermes/config.yaml")
42392
42481
  }
42393
42482
  };
42394
42483
  var CLIENT_INTEGRATIONS = {
42395
42484
  opencode: installOpencodeIntegration,
42396
42485
  "pi-agent": installPiIntegration,
42397
42486
  openclaw: installOpenClawIntegration,
42398
- "claude-code": installClaudeCodeIntegration
42487
+ "claude-code": installClaudeCodeIntegration,
42488
+ hermes: installHermesIntegration
42399
42489
  };
42400
42490
  async function runIntegrationsForClients(clientIds, config) {
42401
42491
  for (const client2 of clientIds) {
@@ -43273,7 +43363,7 @@ async function main() {
43273
43363
  const storageAdapter = createStorageAdapterFromStore(store);
43274
43364
  const modelManager = new ModelManager(discoveryAdapter);
43275
43365
  const providerManager = new ProviderManager(providerRegistry, store);
43276
- const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders } = createModelService(modelManager);
43366
+ const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders } = createModelService(modelManager, store);
43277
43367
  const walletClient = createCocodClient({ cocodPath: config.cocodPath });
43278
43368
  const walletAdapter = await createWalletAdapter({
43279
43369
  cocodPath: config.cocodPath,
@@ -43299,7 +43389,7 @@ async function main() {
43299
43389
  }));
43300
43390
  Bun.write(PID_FILE, String(process.pid));
43301
43391
  try {
43302
- if (existsSync8(SOCKET_PATH)) {
43392
+ if (existsSync9(SOCKET_PATH)) {
43303
43393
  Bun.spawn(["rm", SOCKET_PATH]);
43304
43394
  }
43305
43395
  } catch {
package/dist/index.js CHANGED
@@ -14456,17 +14456,20 @@ function renderRecent(stats, width) {
14456
14456
  const recentEntries = stats.entries.slice(0, 50);
14457
14457
  if (recentEntries.length === 0)
14458
14458
  return renderBox(["No recent entries"], width, "Recent Requests");
14459
+ const clientCol = 10;
14459
14460
  const lines = [];
14460
- lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"MODEL".padEnd(18)} ${"TOKENS".padEnd(10)} ${"COST".padEnd(12)} ${"PROVIDER".slice(0, Math.max(0, width - 60))}${COLORS.reset}`);
14461
+ lines.push(`${COLORS.bold}${"TIME".padEnd(10)} ${"CLIENT".padEnd(clientCol)} ${"MODEL".padEnd(18)} ${"TOKENS".padEnd(10)} ${"COST".padEnd(12)} ${"PROVIDER".slice(0, Math.max(0, width - 70))}${COLORS.reset}`);
14461
14462
  lines.push(COLORS.dim + "\u2500".repeat(width - 4) + COLORS.reset);
14462
14463
  for (const entry of recentEntries) {
14463
14464
  const time = formatTime(entry.timestamp).slice(0, 8);
14465
+ const clientName = (entry.client || "unknown").slice(0, clientCol - 1).padEnd(clientCol);
14466
+ const clientColor = CLIENT_COLORS[entry.client || "unknown"] || CLIENT_COLORS.default || COLORS.white;
14464
14467
  const model = entry.modelId.slice(0, 18).padEnd(18);
14465
14468
  const tokens = `${formatNumber(entry.totalTokens).padEnd(6)} (${formatNumber(entry.promptTokens)}+${formatNumber(entry.completionTokens)})`;
14466
14469
  const cost = `${formatCost(entry.satsCost).padEnd(8)} sats`;
14467
- const provider = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "").slice(0, Math.max(0, width - 60));
14468
- const color = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
14469
- lines.push(`${COLORS.dim}${time}${COLORS.reset} ${color}${model}${COLORS.reset} ${tokens.padEnd(10)} ${COLORS.green}${cost}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset}`);
14470
+ const provider = (entry.baseUrl || "unknown").replace("https://", "").replace("http://", "").slice(0, Math.max(0, width - 70));
14471
+ const modelColor = MODEL_COLORS[entry.modelId] || MODEL_COLORS.default;
14472
+ lines.push(`${COLORS.dim}${time}${COLORS.reset} ${clientColor}${clientName}${COLORS.reset} ${modelColor}${model}${COLORS.reset} ${tokens.padEnd(10)} ${COLORS.green}${cost}${COLORS.reset} ${COLORS.dim}${provider}${COLORS.reset}`);
14470
14473
  }
14471
14474
  return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
14472
14475
  }
@@ -15025,6 +15028,78 @@ Installing routstr configuration in ${configPath}...`);
15025
15028
  }
15026
15029
  }
15027
15030
 
15031
+ // src/integrations/hermes.ts
15032
+ init_logger();
15033
+ init_daemon_client();
15034
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
15035
+ import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
15036
+ import { dirname as dirname6 } from "path";
15037
+ async function installHermesIntegration(config, apiKey, integrationConfig) {
15038
+ const { name, configPath } = integrationConfig;
15039
+ logger.log(`
15040
+ Installing routstr configuration in ${configPath}...`);
15041
+ logger.log(`Using API key for ${name}`);
15042
+ const baseUrl = getDaemonBaseUrl(config);
15043
+ const baseUrlV1 = `${baseUrl}/v1`;
15044
+ let defaultModel = "minimax-m2.7";
15045
+ try {
15046
+ const data = await callDaemon("/models");
15047
+ const models = data.output?.models || [];
15048
+ if (models.length >= 3) {
15049
+ defaultModel = models[2].id;
15050
+ logger.log(`Set default model to 3rd available model: ${defaultModel}`);
15051
+ } else if (models.length > 0) {
15052
+ defaultModel = models[0].id;
15053
+ logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
15054
+ } else {
15055
+ logger.log("No models available from routstr daemon, using fallback default.");
15056
+ }
15057
+ } catch (error) {
15058
+ logger.error("Failed to fetch models for Hermes integration:", error);
15059
+ logger.log("Using fallback default model.");
15060
+ }
15061
+ let content = "";
15062
+ try {
15063
+ if (existsSync8(configPath)) {
15064
+ content = await readFile6(configPath, "utf-8");
15065
+ }
15066
+ } catch (error) {
15067
+ logger.error(`Error reading ${configPath}, creating new one.`);
15068
+ }
15069
+ content = content.replace(/^model:\n(?: .*\n)*/gm, "");
15070
+ content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
15071
+ content = content.replace(/\n{3,}/g, `
15072
+
15073
+ `).trim();
15074
+ const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
15075
+ const modelBlock = `model:
15076
+ default: ${defaultModel}
15077
+ provider: custom
15078
+ base_url: ${baseUrlV1}
15079
+ api_key: ${apiKey}`;
15080
+ const providerBlock = `custom_providers:
15081
+ - name: Routstr (${urlDisplay})
15082
+ base_url: ${baseUrlV1}
15083
+ api_key: ${apiKey}
15084
+ model: ${defaultModel}`;
15085
+ const parts = [modelBlock];
15086
+ if (content) {
15087
+ parts.push(content);
15088
+ }
15089
+ parts.push(providerBlock);
15090
+ const newContent = parts.join(`
15091
+
15092
+ `) + `
15093
+ `;
15094
+ try {
15095
+ mkdirSync5(dirname6(configPath), { recursive: true });
15096
+ await writeFile6(configPath, newContent);
15097
+ logger.log(`Successfully updated ${configPath} with routstr settings.`);
15098
+ } catch (error) {
15099
+ logger.error(`Failed to write to ${configPath}:`, error);
15100
+ }
15101
+ }
15102
+
15028
15103
  // src/integrations/registry.ts
15029
15104
  var CLIENT_CONFIGS = {
15030
15105
  opencode: {
@@ -15046,13 +15121,19 @@ var CLIENT_CONFIGS = {
15046
15121
  clientId: "claude-code",
15047
15122
  name: "Claude Code",
15048
15123
  configPath: join3(process.env.HOME || "", ".claude/settings.json")
15124
+ },
15125
+ hermes: {
15126
+ clientId: "hermes",
15127
+ name: "Hermes",
15128
+ configPath: join3(process.env.HOME || "", ".hermes/config.yaml")
15049
15129
  }
15050
15130
  };
15051
15131
  var CLIENT_INTEGRATIONS = {
15052
15132
  opencode: installOpencodeIntegration,
15053
15133
  "pi-agent": installPiIntegration,
15054
15134
  openclaw: installOpenClawIntegration,
15055
- "claude-code": installClaudeCodeIntegration
15135
+ "claude-code": installClaudeCodeIntegration,
15136
+ hermes: installHermesIntegration
15056
15137
  };
15057
15138
  async function runIntegrationsForClients(clientIds, config) {
15058
15139
  for (const client of clientIds) {
@@ -15162,6 +15243,8 @@ async function addClientAction(options) {
15162
15243
  integrationKeys.push("pi-agent");
15163
15244
  if (options.claudeCode)
15164
15245
  integrationKeys.push("claude-code");
15246
+ if (options.hermes)
15247
+ integrationKeys.push("hermes");
15165
15248
  if (integrationKeys.length > 0) {
15166
15249
  for (const key of integrationKeys) {
15167
15250
  const integrationFn = CLIENT_INTEGRATIONS[key];
@@ -15190,7 +15273,15 @@ async function addClientAction(options) {
15190
15273
  return;
15191
15274
  }
15192
15275
  if (!options.name) {
15193
- console.error("error: required option '-n, --name <name>' not specified");
15276
+ console.error(`error: either provide a client name or specify an integration flag.
15277
+ `);
15278
+ console.error("Options:");
15279
+ console.error(" -n, --name <name> Client name");
15280
+ console.error(" --opencode Set up OpenCode integration");
15281
+ console.error(" --openclaw Set up OpenClaw integration");
15282
+ console.error(" --pi-agent Set up Pi Agent integration");
15283
+ console.error(" --claude-code Set up Claude Code integration");
15284
+ console.error(" --hermes Set up Hermes integration");
15194
15285
  process.exit(1);
15195
15286
  }
15196
15287
  try {
@@ -15221,7 +15312,7 @@ async function addClientAction(options) {
15221
15312
  // src/cli.ts
15222
15313
  init_config();
15223
15314
  init_logger();
15224
- import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
15315
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
15225
15316
  import { execSync } from "child_process";
15226
15317
 
15227
15318
  // src/integrations/index.ts
@@ -15245,7 +15336,7 @@ function parseChoice(input) {
15245
15336
  return 1;
15246
15337
  }
15247
15338
  const parsed = Number.parseInt(input, 10);
15248
- if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 5) {
15339
+ if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 6) {
15249
15340
  return parsed;
15250
15341
  }
15251
15342
  return 1;
@@ -15257,14 +15348,16 @@ Choose an integration to set up:`);
15257
15348
  logger.log("2. OpenClaw");
15258
15349
  logger.log("3. Pi");
15259
15350
  logger.log("4. Claude Code");
15260
- logger.log("5. Skip for now");
15351
+ logger.log("5. Hermes");
15352
+ logger.log("6. Skip for now");
15261
15353
  const answer = await ask("Select integration [1]: ");
15262
15354
  const choice = parseChoice(answer);
15263
15355
  const integrationByChoice = {
15264
15356
  1: "opencode",
15265
15357
  2: "openclaw",
15266
15358
  3: "pi-agent",
15267
- 4: "claude-code"
15359
+ 4: "claude-code",
15360
+ 5: "hermes"
15268
15361
  };
15269
15362
  const key = integrationByChoice[choice];
15270
15363
  if (!key) {
@@ -15272,7 +15365,7 @@ Choose an integration to set up:`);
15272
15365
  return;
15273
15366
  }
15274
15367
  const integrationConfig = CLIENT_CONFIGS[key];
15275
- const { client, created } = await addDaemonClient(integrationConfig.name, integrationConfig.clientId);
15368
+ const { client, created } = await addDaemonClient(integrationConfig.name);
15276
15369
  if (created) {
15277
15370
  logger.log(`Created new API key for ${integrationConfig.name}`);
15278
15371
  } else {
@@ -15292,6 +15385,11 @@ Choose an integration to set up:`);
15292
15385
  }
15293
15386
  if (key === "claude-code") {
15294
15387
  await installClaudeCodeIntegration(config, client.apiKey, integrationConfig);
15388
+ return;
15389
+ }
15390
+ if (key === "hermes") {
15391
+ await installHermesIntegration(config, client.apiKey, integrationConfig);
15392
+ return;
15295
15393
  }
15296
15394
  }
15297
15395
 
@@ -15368,7 +15466,7 @@ init_nip98();
15368
15466
  init_esm();
15369
15467
 
15370
15468
  // src/daemon/wallet/cocod-client.ts
15371
- import { existsSync as existsSync8 } from "fs";
15469
+ import { existsSync as existsSync9 } from "fs";
15372
15470
  init_logger();
15373
15471
  init_process_lock();
15374
15472
  var DEFAULT_CONFIG_DIR = process.env.COCOD_DIR || `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
@@ -15380,7 +15478,7 @@ function resolveCocodExecutable(cocodPath) {
15380
15478
  async function isCocodInstalled(cocodPath) {
15381
15479
  const executable = resolveCocodExecutable(cocodPath);
15382
15480
  if (executable.includes("/")) {
15383
- return existsSync8(executable);
15481
+ return existsSync9(executable);
15384
15482
  }
15385
15483
  try {
15386
15484
  const proc = Bun.spawn({
@@ -15396,7 +15494,7 @@ async function isCocodInstalled(cocodPath) {
15396
15494
  // package.json
15397
15495
  var package_default = {
15398
15496
  name: "routstrd",
15399
- version: "0.2.15",
15497
+ version: "0.2.17",
15400
15498
  module: "src/index.ts",
15401
15499
  type: "module",
15402
15500
  private: false,
@@ -15420,7 +15518,7 @@ var package_default = {
15420
15518
  },
15421
15519
  dependencies: {
15422
15520
  "@cashu/cashu-ts": "^3.1.1",
15423
- "@routstr/sdk": "^0.3.2",
15521
+ "@routstr/sdk": "^0.3.3",
15424
15522
  "applesauce-core": "^5.1.0",
15425
15523
  "applesauce-relay": "^5.1.0",
15426
15524
  commander: "^14.0.2",
@@ -15475,11 +15573,11 @@ async function requireLocalDaemon() {
15475
15573
  }
15476
15574
  async function initDaemon() {
15477
15575
  logger.log("Initializing routstrd...");
15478
- if (!existsSync9(CONFIG_DIR)) {
15479
- mkdirSync5(CONFIG_DIR, { recursive: true });
15576
+ if (!existsSync10(CONFIG_DIR)) {
15577
+ mkdirSync6(CONFIG_DIR, { recursive: true });
15480
15578
  logger.log(`Created config directory: ${CONFIG_DIR}`);
15481
15579
  }
15482
- if (!existsSync9(CONFIG_FILE)) {
15580
+ if (!existsSync10(CONFIG_FILE)) {
15483
15581
  const config2 = {
15484
15582
  ...DEFAULT_CONFIG,
15485
15583
  cocodPath: null
@@ -15603,8 +15701,8 @@ program.command("remote <url>").description("Configure a remote daemon URL").act
15603
15701
  console.error(`Invalid URL: ${url}`);
15604
15702
  process.exit(1);
15605
15703
  }
15606
- if (!existsSync9(CONFIG_DIR)) {
15607
- mkdirSync5(CONFIG_DIR, { recursive: true });
15704
+ if (!existsSync10(CONFIG_DIR)) {
15705
+ mkdirSync6(CONFIG_DIR, { recursive: true });
15608
15706
  }
15609
15707
  const config = await loadConfig();
15610
15708
  const updates = { daemonUrl: url };
@@ -15898,7 +15996,7 @@ clientsCmd.command("list").description("List all clients").action(async () => {
15898
15996
  clientsCmd.command("delete <id>").description("Delete a client by its ID").action(async (id) => {
15899
15997
  await deleteClientAction(id);
15900
15998
  });
15901
- clientsCmd.command("add").description("Add a new client or set up client integrations").option("-n, --name <name>", "Client name").option("--opencode", "Set up OpenCode integration").option("--openclaw", "Set up OpenClaw integration").option("--pi-agent", "Set up Pi Agent integration").option("--claude-code", "Set up Claude Code integration").action(async (options) => {
15999
+ clientsCmd.command("add").description("Add a new client or set up client integrations").option("-n, --name <name>", "Client name").option("--opencode", "Set up OpenCode integration").option("--openclaw", "Set up OpenClaw integration").option("--pi-agent", "Set up Pi Agent integration").option("--claude-code", "Set up Claude Code integration").option("--hermes", "Set up Hermes integration").action(async (options) => {
15902
16000
  await addClientAction(options);
15903
16001
  });
15904
16002
  var npubsCmd = program.command("npubs").description("Manage npubs on the daemon (admin or user roles)");
@@ -15971,16 +16069,21 @@ npubsCmd.command("register").description("Register yourself as the first admin (
15971
16069
  console.log(`Successfully registered as first admin npub: ${userNpub}`);
15972
16070
  }
15973
16071
  });
15974
- npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...). First becomes admin, subsequent become user.").action(async (npubArg) => {
16072
+ npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...). Defaults to 'user' role unless --role is specified.").option("-r, --role <role>", "Role for the npub: 'admin' or 'user' (default: 'user')", "user").action(async (npubArg, options) => {
15975
16073
  await ensureDaemonRunning();
15976
16074
  const normalized = normalizeNostrPubkey(npubArg);
15977
16075
  if (!normalized) {
15978
16076
  console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
15979
16077
  process.exit(1);
15980
16078
  }
16079
+ if (options.role !== "admin" && options.role !== "user") {
16080
+ console.error("Invalid role. Expected 'admin' or 'user'.");
16081
+ process.exit(1);
16082
+ }
16083
+ const body = { npub: npubFromPubkey(normalized), role: options.role };
15981
16084
  const result = await callDaemon("/npubs", {
15982
16085
  method: "POST",
15983
- body: { npub: npubFromPubkey(normalized) }
16086
+ body
15984
16087
  });
15985
16088
  if (result.error) {
15986
16089
  console.log(result.error);
@@ -15988,10 +16091,36 @@ npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...).
15988
16091
  }
15989
16092
  const output = result.output;
15990
16093
  if (output?.npub) {
15991
- console.log(`${output.added ? "Added" : "Already configured"} npub: ${output.npub}`);
16094
+ console.log(`${output.added ? "Added" : "Already configured"} npub: ${output.npub} [${output.role ?? "user"}]`);
16095
+ }
16096
+ });
16097
+ npubsCmd.command("update <npub>").description("Update the role of an existing npub (requires admin)").requiredOption("-r, --role <role>", "New role: 'admin' or 'user'").action(async (npubArg, options) => {
16098
+ await ensureDaemonRunning();
16099
+ const normalized = normalizeNostrPubkey(npubArg);
16100
+ if (!normalized) {
16101
+ console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
16102
+ process.exit(1);
16103
+ }
16104
+ if (options.role !== "admin" && options.role !== "user") {
16105
+ console.error("Invalid role. Expected 'admin' or 'user'.");
16106
+ process.exit(1);
16107
+ }
16108
+ const result = await callDaemon("/npubs", {
16109
+ method: "PATCH",
16110
+ body: { npub: npubFromPubkey(normalized), role: options.role }
16111
+ });
16112
+ if (result.error) {
16113
+ console.log(result.error);
16114
+ process.exit(1);
16115
+ }
16116
+ const data = result.output ?? result;
16117
+ if (data?.npub) {
16118
+ console.log(`Updated npub ${data.npub} role to '${data.role}'.`);
16119
+ } else {
16120
+ console.log("Npub not found or update failed.");
15992
16121
  }
15993
16122
  });
15994
- npubsCmd.command("delete <npub>").description("Delete an admin npub (hex pubkey or npub1...)").action(async (npubArg) => {
16123
+ npubsCmd.command("delete <npub>").description("Delete an npub (hex pubkey or npub1...)").action(async (npubArg) => {
15995
16124
  await ensureDaemonRunning();
15996
16125
  const normalized = normalizeNostrPubkey(npubArg);
15997
16126
  if (!normalized) {
@@ -16167,7 +16296,7 @@ serviceCmd.command("install").description("Install and start routstrd using PM2
16167
16296
  const path = import.meta.require("path");
16168
16297
  daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
16169
16298
  }
16170
- if (!existsSync9(daemonPath)) {
16299
+ if (!existsSync10(daemonPath)) {
16171
16300
  console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
16172
16301
  process.exit(1);
16173
16302
  }
@@ -16305,14 +16434,14 @@ program.command("logs").description("View daemon logs").option("-f, --follow", "
16305
16434
  const yesterday = new Date;
16306
16435
  yesterday.setDate(yesterday.getDate() - 1);
16307
16436
  const yesterdayFile = getLogFileForDate2(yesterday);
16308
- if (!existsSync9(todayFile) && !existsSync9(yesterdayFile)) {
16437
+ if (!existsSync10(todayFile) && !existsSync10(yesterdayFile)) {
16309
16438
  console.log("No log files found. Daemon may not have started yet.");
16310
16439
  console.log(`Logs directory: ${LOGS_DIR2}`);
16311
16440
  process.exit(1);
16312
16441
  }
16313
16442
  const lines = parseInt(options.lines, 10);
16314
16443
  const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
16315
- return existsSync9(file) && files.indexOf(file) === index;
16444
+ return existsSync10(file) && files.indexOf(file) === index;
16316
16445
  });
16317
16446
  if (options.follow) {
16318
16447
  const proc2 = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routstrd",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
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": "^3.1.1",
27
- "@routstr/sdk": "^0.3.2",
27
+ "@routstr/sdk": "^0.3.3",
28
28
  "applesauce-core": "^5.1.0",
29
29
  "applesauce-relay": "^5.1.0",
30
30
  "commander": "^14.0.2",
package/src/cli.ts CHANGED
@@ -820,6 +820,7 @@ clientsCmd
820
820
  .option("--openclaw", "Set up OpenClaw integration")
821
821
  .option("--pi-agent", "Set up Pi Agent integration")
822
822
  .option("--claude-code", "Set up Claude Code integration")
823
+ .option("--hermes", "Set up Hermes integration")
823
824
  .action(
824
825
  async (options: {
825
826
  name?: string;
@@ -827,6 +828,7 @@ clientsCmd
827
828
  openclaw?: boolean;
828
829
  piAgent?: boolean;
829
830
  claudeCode?: boolean;
831
+ hermes?: boolean;
830
832
  }) => {
831
833
  await addClientAction(options);
832
834
  },
@@ -930,35 +932,75 @@ npubsCmd
930
932
 
931
933
  npubsCmd
932
934
  .command("add <npub>")
933
- .description("Add a npub (hex pubkey or npub1...). First becomes admin, subsequent become user.")
934
- .action(async (npubArg: string) => {
935
+ .description("Add a npub (hex pubkey or npub1...). Defaults to 'user' role unless --role is specified.")
936
+ .option("-r, --role <role>", "Role for the npub: 'admin' or 'user' (default: 'user')", "user")
937
+ .action(async (npubArg: string, options: { role: string }) => {
935
938
  await ensureDaemonRunning();
936
939
  const normalized = normalizeNostrPubkey(npubArg);
937
940
  if (!normalized) {
938
941
  console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
939
942
  process.exit(1);
940
943
  }
944
+ if (options.role !== "admin" && options.role !== "user") {
945
+ console.error("Invalid role. Expected 'admin' or 'user'.");
946
+ process.exit(1);
947
+ }
948
+ const body: Record<string, string> = { npub: npubFromPubkey(normalized), role: options.role };
941
949
  const result = await callDaemon("/npubs", {
942
950
  method: "POST",
943
- body: { npub: npubFromPubkey(normalized) },
951
+ body,
944
952
  });
945
953
  if (result.error) {
946
954
  console.log(result.error);
947
955
  process.exit(1);
948
956
  }
949
957
  const output = result.output as
950
- | { npub?: string; added?: boolean; error?: string }
958
+ | { npub?: string; role?: string; added?: boolean; error?: string }
951
959
  | undefined;
952
960
  if (output?.npub) {
953
961
  console.log(
954
- `${output.added ? "Added" : "Already configured"} npub: ${output.npub}`,
962
+ `${output.added ? "Added" : "Already configured"} npub: ${output.npub} [${output.role ?? "user"}]`,
955
963
  );
956
964
  }
957
965
  });
958
966
 
967
+ npubsCmd
968
+ .command("update <npub>")
969
+ .description("Update the role of an existing npub (requires admin)")
970
+ .requiredOption("-r, --role <role>", "New role: 'admin' or 'user'")
971
+ .action(async (npubArg: string, options: { role: string }) => {
972
+ await ensureDaemonRunning();
973
+ const normalized = normalizeNostrPubkey(npubArg);
974
+ if (!normalized) {
975
+ console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
976
+ process.exit(1);
977
+ }
978
+ if (options.role !== "admin" && options.role !== "user") {
979
+ console.error("Invalid role. Expected 'admin' or 'user'.");
980
+ process.exit(1);
981
+ }
982
+ const result = await callDaemon("/npubs", {
983
+ method: "PATCH",
984
+ body: { npub: npubFromPubkey(normalized), role: options.role },
985
+ });
986
+ if (result.error) {
987
+ console.log(result.error);
988
+ process.exit(1);
989
+ }
990
+ // PATCH /npubs returns { npub, pubkey, role } at the top level, not wrapped in { output }
991
+ const data = (result.output ?? result) as
992
+ | { npub?: string; pubkey?: string; role?: string; error?: string }
993
+ | undefined;
994
+ if (data?.npub) {
995
+ console.log(`Updated npub ${data.npub} role to '${data.role}'.`);
996
+ } else {
997
+ console.log("Npub not found or update failed.");
998
+ }
999
+ });
1000
+
959
1001
  npubsCmd
960
1002
  .command("delete <npub>")
961
- .description("Delete an admin npub (hex pubkey or npub1...)")
1003
+ .description("Delete an npub (hex pubkey or npub1...)")
962
1004
  .action(async (npubArg: string) => {
963
1005
  await ensureDaemonRunning();
964
1006
  const normalized = normalizeNostrPubkey(npubArg);