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.
- package/dist/cli.js +16372 -0
- package/dist/daemon/index.js +95 -5
- package/dist/index.js +157 -28
- package/package.json +2 -2
- package/src/cli.ts +48 -6
- package/src/daemon/models.ts +18 -2
- package/src/integrations/hermes.ts +87 -0
- package/src/integrations/registry.ts +7 -0
- package/src/tui/usage/render.ts +7 -4
- package/src/utils/clients.ts +10 -3
- package/src/utils/daemon-client.ts +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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 -
|
|
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 -
|
|
14468
|
-
const
|
|
14469
|
-
lines.push(`${COLORS.dim}${time}${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(
|
|
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
|
|
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 <=
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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 (!
|
|
15479
|
-
|
|
15576
|
+
if (!existsSync10(CONFIG_DIR)) {
|
|
15577
|
+
mkdirSync6(CONFIG_DIR, { recursive: true });
|
|
15480
15578
|
logger.log(`Created config directory: ${CONFIG_DIR}`);
|
|
15481
15579
|
}
|
|
15482
|
-
if (!
|
|
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 (!
|
|
15607
|
-
|
|
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...).
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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.
|
|
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.
|
|
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...).
|
|
934
|
-
.
|
|
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
|
|
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
|
|
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);
|