routstrd 0.2.15 → 0.2.16
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 +79 -3
- package/dist/index.js +149 -23
- package/package.json +1 -1
- package/src/cli.ts +48 -6
- package/src/integrations/hermes.ts +87 -0
- package/src/integrations/registry.ts +7 -0
- 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 || "";
|
|
@@ -42368,6 +42368,76 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
42368
42368
|
}
|
|
42369
42369
|
}
|
|
42370
42370
|
|
|
42371
|
+
// src/integrations/hermes.ts
|
|
42372
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
42373
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
42374
|
+
import { dirname as dirname6 } from "path";
|
|
42375
|
+
async function installHermesIntegration(config, apiKey, integrationConfig) {
|
|
42376
|
+
const { name, configPath } = integrationConfig;
|
|
42377
|
+
logger3.log(`
|
|
42378
|
+
Installing routstr configuration in ${configPath}...`);
|
|
42379
|
+
logger3.log(`Using API key for ${name}`);
|
|
42380
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
42381
|
+
const baseUrlV1 = `${baseUrl}/v1`;
|
|
42382
|
+
let defaultModel = "minimax-m2.7";
|
|
42383
|
+
try {
|
|
42384
|
+
const data = await callDaemon("/models");
|
|
42385
|
+
const models = data.output?.models || [];
|
|
42386
|
+
if (models.length >= 3) {
|
|
42387
|
+
defaultModel = models[2].id;
|
|
42388
|
+
logger3.log(`Set default model to 3rd available model: ${defaultModel}`);
|
|
42389
|
+
} else if (models.length > 0) {
|
|
42390
|
+
defaultModel = models[0].id;
|
|
42391
|
+
logger3.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
|
|
42392
|
+
} else {
|
|
42393
|
+
logger3.log("No models available from routstr daemon, using fallback default.");
|
|
42394
|
+
}
|
|
42395
|
+
} catch (error) {
|
|
42396
|
+
logger3.error("Failed to fetch models for Hermes integration:", error);
|
|
42397
|
+
logger3.log("Using fallback default model.");
|
|
42398
|
+
}
|
|
42399
|
+
let content2 = "";
|
|
42400
|
+
try {
|
|
42401
|
+
if (existsSync8(configPath)) {
|
|
42402
|
+
content2 = await readFile6(configPath, "utf-8");
|
|
42403
|
+
}
|
|
42404
|
+
} catch (error) {
|
|
42405
|
+
logger3.error(`Error reading ${configPath}, creating new one.`);
|
|
42406
|
+
}
|
|
42407
|
+
content2 = content2.replace(/^model:\n(?: .*\n)*/gm, "");
|
|
42408
|
+
content2 = content2.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
|
|
42409
|
+
content2 = content2.replace(/\n{3,}/g, `
|
|
42410
|
+
|
|
42411
|
+
`).trim();
|
|
42412
|
+
const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
|
|
42413
|
+
const modelBlock = `model:
|
|
42414
|
+
default: ${defaultModel}
|
|
42415
|
+
provider: custom
|
|
42416
|
+
base_url: ${baseUrlV1}
|
|
42417
|
+
api_key: ${apiKey}`;
|
|
42418
|
+
const providerBlock = `custom_providers:
|
|
42419
|
+
- name: Routstr (${urlDisplay})
|
|
42420
|
+
base_url: ${baseUrlV1}
|
|
42421
|
+
api_key: ${apiKey}
|
|
42422
|
+
model: ${defaultModel}`;
|
|
42423
|
+
const parts = [modelBlock];
|
|
42424
|
+
if (content2) {
|
|
42425
|
+
parts.push(content2);
|
|
42426
|
+
}
|
|
42427
|
+
parts.push(providerBlock);
|
|
42428
|
+
const newContent = parts.join(`
|
|
42429
|
+
|
|
42430
|
+
`) + `
|
|
42431
|
+
`;
|
|
42432
|
+
try {
|
|
42433
|
+
mkdirSync5(dirname6(configPath), { recursive: true });
|
|
42434
|
+
await writeFile6(configPath, newContent);
|
|
42435
|
+
logger3.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
42436
|
+
} catch (error) {
|
|
42437
|
+
logger3.error(`Failed to write to ${configPath}:`, error);
|
|
42438
|
+
}
|
|
42439
|
+
}
|
|
42440
|
+
|
|
42371
42441
|
// src/integrations/registry.ts
|
|
42372
42442
|
var CLIENT_CONFIGS = {
|
|
42373
42443
|
opencode: {
|
|
@@ -42389,13 +42459,19 @@ var CLIENT_CONFIGS = {
|
|
|
42389
42459
|
clientId: "claude-code",
|
|
42390
42460
|
name: "Claude Code",
|
|
42391
42461
|
configPath: join5(process.env.HOME || "", ".claude/settings.json")
|
|
42462
|
+
},
|
|
42463
|
+
hermes: {
|
|
42464
|
+
clientId: "hermes",
|
|
42465
|
+
name: "Hermes",
|
|
42466
|
+
configPath: join5(process.env.HOME || "", ".hermes/config.yaml")
|
|
42392
42467
|
}
|
|
42393
42468
|
};
|
|
42394
42469
|
var CLIENT_INTEGRATIONS = {
|
|
42395
42470
|
opencode: installOpencodeIntegration,
|
|
42396
42471
|
"pi-agent": installPiIntegration,
|
|
42397
42472
|
openclaw: installOpenClawIntegration,
|
|
42398
|
-
"claude-code": installClaudeCodeIntegration
|
|
42473
|
+
"claude-code": installClaudeCodeIntegration,
|
|
42474
|
+
hermes: installHermesIntegration
|
|
42399
42475
|
};
|
|
42400
42476
|
async function runIntegrationsForClients(clientIds, config) {
|
|
42401
42477
|
for (const client2 of clientIds) {
|
|
@@ -43299,7 +43375,7 @@ async function main() {
|
|
|
43299
43375
|
}));
|
|
43300
43376
|
Bun.write(PID_FILE, String(process.pid));
|
|
43301
43377
|
try {
|
|
43302
|
-
if (
|
|
43378
|
+
if (existsSync9(SOCKET_PATH)) {
|
|
43303
43379
|
Bun.spawn(["rm", SOCKET_PATH]);
|
|
43304
43380
|
}
|
|
43305
43381
|
} catch {
|
package/dist/index.js
CHANGED
|
@@ -15025,6 +15025,78 @@ Installing routstr configuration in ${configPath}...`);
|
|
|
15025
15025
|
}
|
|
15026
15026
|
}
|
|
15027
15027
|
|
|
15028
|
+
// src/integrations/hermes.ts
|
|
15029
|
+
init_logger();
|
|
15030
|
+
init_daemon_client();
|
|
15031
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
15032
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
15033
|
+
import { dirname as dirname6 } from "path";
|
|
15034
|
+
async function installHermesIntegration(config, apiKey, integrationConfig) {
|
|
15035
|
+
const { name, configPath } = integrationConfig;
|
|
15036
|
+
logger.log(`
|
|
15037
|
+
Installing routstr configuration in ${configPath}...`);
|
|
15038
|
+
logger.log(`Using API key for ${name}`);
|
|
15039
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
15040
|
+
const baseUrlV1 = `${baseUrl}/v1`;
|
|
15041
|
+
let defaultModel = "minimax-m2.7";
|
|
15042
|
+
try {
|
|
15043
|
+
const data = await callDaemon("/models");
|
|
15044
|
+
const models = data.output?.models || [];
|
|
15045
|
+
if (models.length >= 3) {
|
|
15046
|
+
defaultModel = models[2].id;
|
|
15047
|
+
logger.log(`Set default model to 3rd available model: ${defaultModel}`);
|
|
15048
|
+
} else if (models.length > 0) {
|
|
15049
|
+
defaultModel = models[0].id;
|
|
15050
|
+
logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
|
|
15051
|
+
} else {
|
|
15052
|
+
logger.log("No models available from routstr daemon, using fallback default.");
|
|
15053
|
+
}
|
|
15054
|
+
} catch (error) {
|
|
15055
|
+
logger.error("Failed to fetch models for Hermes integration:", error);
|
|
15056
|
+
logger.log("Using fallback default model.");
|
|
15057
|
+
}
|
|
15058
|
+
let content = "";
|
|
15059
|
+
try {
|
|
15060
|
+
if (existsSync8(configPath)) {
|
|
15061
|
+
content = await readFile6(configPath, "utf-8");
|
|
15062
|
+
}
|
|
15063
|
+
} catch (error) {
|
|
15064
|
+
logger.error(`Error reading ${configPath}, creating new one.`);
|
|
15065
|
+
}
|
|
15066
|
+
content = content.replace(/^model:\n(?: .*\n)*/gm, "");
|
|
15067
|
+
content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
|
|
15068
|
+
content = content.replace(/\n{3,}/g, `
|
|
15069
|
+
|
|
15070
|
+
`).trim();
|
|
15071
|
+
const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
|
|
15072
|
+
const modelBlock = `model:
|
|
15073
|
+
default: ${defaultModel}
|
|
15074
|
+
provider: custom
|
|
15075
|
+
base_url: ${baseUrlV1}
|
|
15076
|
+
api_key: ${apiKey}`;
|
|
15077
|
+
const providerBlock = `custom_providers:
|
|
15078
|
+
- name: Routstr (${urlDisplay})
|
|
15079
|
+
base_url: ${baseUrlV1}
|
|
15080
|
+
api_key: ${apiKey}
|
|
15081
|
+
model: ${defaultModel}`;
|
|
15082
|
+
const parts = [modelBlock];
|
|
15083
|
+
if (content) {
|
|
15084
|
+
parts.push(content);
|
|
15085
|
+
}
|
|
15086
|
+
parts.push(providerBlock);
|
|
15087
|
+
const newContent = parts.join(`
|
|
15088
|
+
|
|
15089
|
+
`) + `
|
|
15090
|
+
`;
|
|
15091
|
+
try {
|
|
15092
|
+
mkdirSync5(dirname6(configPath), { recursive: true });
|
|
15093
|
+
await writeFile6(configPath, newContent);
|
|
15094
|
+
logger.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
15095
|
+
} catch (error) {
|
|
15096
|
+
logger.error(`Failed to write to ${configPath}:`, error);
|
|
15097
|
+
}
|
|
15098
|
+
}
|
|
15099
|
+
|
|
15028
15100
|
// src/integrations/registry.ts
|
|
15029
15101
|
var CLIENT_CONFIGS = {
|
|
15030
15102
|
opencode: {
|
|
@@ -15046,13 +15118,19 @@ var CLIENT_CONFIGS = {
|
|
|
15046
15118
|
clientId: "claude-code",
|
|
15047
15119
|
name: "Claude Code",
|
|
15048
15120
|
configPath: join3(process.env.HOME || "", ".claude/settings.json")
|
|
15121
|
+
},
|
|
15122
|
+
hermes: {
|
|
15123
|
+
clientId: "hermes",
|
|
15124
|
+
name: "Hermes",
|
|
15125
|
+
configPath: join3(process.env.HOME || "", ".hermes/config.yaml")
|
|
15049
15126
|
}
|
|
15050
15127
|
};
|
|
15051
15128
|
var CLIENT_INTEGRATIONS = {
|
|
15052
15129
|
opencode: installOpencodeIntegration,
|
|
15053
15130
|
"pi-agent": installPiIntegration,
|
|
15054
15131
|
openclaw: installOpenClawIntegration,
|
|
15055
|
-
"claude-code": installClaudeCodeIntegration
|
|
15132
|
+
"claude-code": installClaudeCodeIntegration,
|
|
15133
|
+
hermes: installHermesIntegration
|
|
15056
15134
|
};
|
|
15057
15135
|
async function runIntegrationsForClients(clientIds, config) {
|
|
15058
15136
|
for (const client of clientIds) {
|
|
@@ -15162,6 +15240,8 @@ async function addClientAction(options) {
|
|
|
15162
15240
|
integrationKeys.push("pi-agent");
|
|
15163
15241
|
if (options.claudeCode)
|
|
15164
15242
|
integrationKeys.push("claude-code");
|
|
15243
|
+
if (options.hermes)
|
|
15244
|
+
integrationKeys.push("hermes");
|
|
15165
15245
|
if (integrationKeys.length > 0) {
|
|
15166
15246
|
for (const key of integrationKeys) {
|
|
15167
15247
|
const integrationFn = CLIENT_INTEGRATIONS[key];
|
|
@@ -15190,7 +15270,15 @@ async function addClientAction(options) {
|
|
|
15190
15270
|
return;
|
|
15191
15271
|
}
|
|
15192
15272
|
if (!options.name) {
|
|
15193
|
-
console.error(
|
|
15273
|
+
console.error(`error: either provide a client name or specify an integration flag.
|
|
15274
|
+
`);
|
|
15275
|
+
console.error("Options:");
|
|
15276
|
+
console.error(" -n, --name <name> Client name");
|
|
15277
|
+
console.error(" --opencode Set up OpenCode integration");
|
|
15278
|
+
console.error(" --openclaw Set up OpenClaw integration");
|
|
15279
|
+
console.error(" --pi-agent Set up Pi Agent integration");
|
|
15280
|
+
console.error(" --claude-code Set up Claude Code integration");
|
|
15281
|
+
console.error(" --hermes Set up Hermes integration");
|
|
15194
15282
|
process.exit(1);
|
|
15195
15283
|
}
|
|
15196
15284
|
try {
|
|
@@ -15221,7 +15309,7 @@ async function addClientAction(options) {
|
|
|
15221
15309
|
// src/cli.ts
|
|
15222
15310
|
init_config();
|
|
15223
15311
|
init_logger();
|
|
15224
|
-
import { existsSync as
|
|
15312
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
15225
15313
|
import { execSync } from "child_process";
|
|
15226
15314
|
|
|
15227
15315
|
// src/integrations/index.ts
|
|
@@ -15245,7 +15333,7 @@ function parseChoice(input) {
|
|
|
15245
15333
|
return 1;
|
|
15246
15334
|
}
|
|
15247
15335
|
const parsed = Number.parseInt(input, 10);
|
|
15248
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <=
|
|
15336
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 6) {
|
|
15249
15337
|
return parsed;
|
|
15250
15338
|
}
|
|
15251
15339
|
return 1;
|
|
@@ -15257,14 +15345,16 @@ Choose an integration to set up:`);
|
|
|
15257
15345
|
logger.log("2. OpenClaw");
|
|
15258
15346
|
logger.log("3. Pi");
|
|
15259
15347
|
logger.log("4. Claude Code");
|
|
15260
|
-
logger.log("5.
|
|
15348
|
+
logger.log("5. Hermes");
|
|
15349
|
+
logger.log("6. Skip for now");
|
|
15261
15350
|
const answer = await ask("Select integration [1]: ");
|
|
15262
15351
|
const choice = parseChoice(answer);
|
|
15263
15352
|
const integrationByChoice = {
|
|
15264
15353
|
1: "opencode",
|
|
15265
15354
|
2: "openclaw",
|
|
15266
15355
|
3: "pi-agent",
|
|
15267
|
-
4: "claude-code"
|
|
15356
|
+
4: "claude-code",
|
|
15357
|
+
5: "hermes"
|
|
15268
15358
|
};
|
|
15269
15359
|
const key = integrationByChoice[choice];
|
|
15270
15360
|
if (!key) {
|
|
@@ -15272,7 +15362,7 @@ Choose an integration to set up:`);
|
|
|
15272
15362
|
return;
|
|
15273
15363
|
}
|
|
15274
15364
|
const integrationConfig = CLIENT_CONFIGS[key];
|
|
15275
|
-
const { client, created } = await addDaemonClient(integrationConfig.name
|
|
15365
|
+
const { client, created } = await addDaemonClient(integrationConfig.name);
|
|
15276
15366
|
if (created) {
|
|
15277
15367
|
logger.log(`Created new API key for ${integrationConfig.name}`);
|
|
15278
15368
|
} else {
|
|
@@ -15292,6 +15382,11 @@ Choose an integration to set up:`);
|
|
|
15292
15382
|
}
|
|
15293
15383
|
if (key === "claude-code") {
|
|
15294
15384
|
await installClaudeCodeIntegration(config, client.apiKey, integrationConfig);
|
|
15385
|
+
return;
|
|
15386
|
+
}
|
|
15387
|
+
if (key === "hermes") {
|
|
15388
|
+
await installHermesIntegration(config, client.apiKey, integrationConfig);
|
|
15389
|
+
return;
|
|
15295
15390
|
}
|
|
15296
15391
|
}
|
|
15297
15392
|
|
|
@@ -15368,7 +15463,7 @@ init_nip98();
|
|
|
15368
15463
|
init_esm();
|
|
15369
15464
|
|
|
15370
15465
|
// src/daemon/wallet/cocod-client.ts
|
|
15371
|
-
import { existsSync as
|
|
15466
|
+
import { existsSync as existsSync9 } from "fs";
|
|
15372
15467
|
init_logger();
|
|
15373
15468
|
init_process_lock();
|
|
15374
15469
|
var DEFAULT_CONFIG_DIR = process.env.COCOD_DIR || `${process.env.HOME || process.env.USERPROFILE || ""}/.cocod`;
|
|
@@ -15380,7 +15475,7 @@ function resolveCocodExecutable(cocodPath) {
|
|
|
15380
15475
|
async function isCocodInstalled(cocodPath) {
|
|
15381
15476
|
const executable = resolveCocodExecutable(cocodPath);
|
|
15382
15477
|
if (executable.includes("/")) {
|
|
15383
|
-
return
|
|
15478
|
+
return existsSync9(executable);
|
|
15384
15479
|
}
|
|
15385
15480
|
try {
|
|
15386
15481
|
const proc = Bun.spawn({
|
|
@@ -15396,7 +15491,7 @@ async function isCocodInstalled(cocodPath) {
|
|
|
15396
15491
|
// package.json
|
|
15397
15492
|
var package_default = {
|
|
15398
15493
|
name: "routstrd",
|
|
15399
|
-
version: "0.2.
|
|
15494
|
+
version: "0.2.16",
|
|
15400
15495
|
module: "src/index.ts",
|
|
15401
15496
|
type: "module",
|
|
15402
15497
|
private: false,
|
|
@@ -15475,11 +15570,11 @@ async function requireLocalDaemon() {
|
|
|
15475
15570
|
}
|
|
15476
15571
|
async function initDaemon() {
|
|
15477
15572
|
logger.log("Initializing routstrd...");
|
|
15478
|
-
if (!
|
|
15479
|
-
|
|
15573
|
+
if (!existsSync10(CONFIG_DIR)) {
|
|
15574
|
+
mkdirSync6(CONFIG_DIR, { recursive: true });
|
|
15480
15575
|
logger.log(`Created config directory: ${CONFIG_DIR}`);
|
|
15481
15576
|
}
|
|
15482
|
-
if (!
|
|
15577
|
+
if (!existsSync10(CONFIG_FILE)) {
|
|
15483
15578
|
const config2 = {
|
|
15484
15579
|
...DEFAULT_CONFIG,
|
|
15485
15580
|
cocodPath: null
|
|
@@ -15603,8 +15698,8 @@ program.command("remote <url>").description("Configure a remote daemon URL").act
|
|
|
15603
15698
|
console.error(`Invalid URL: ${url}`);
|
|
15604
15699
|
process.exit(1);
|
|
15605
15700
|
}
|
|
15606
|
-
if (!
|
|
15607
|
-
|
|
15701
|
+
if (!existsSync10(CONFIG_DIR)) {
|
|
15702
|
+
mkdirSync6(CONFIG_DIR, { recursive: true });
|
|
15608
15703
|
}
|
|
15609
15704
|
const config = await loadConfig();
|
|
15610
15705
|
const updates = { daemonUrl: url };
|
|
@@ -15898,7 +15993,7 @@ clientsCmd.command("list").description("List all clients").action(async () => {
|
|
|
15898
15993
|
clientsCmd.command("delete <id>").description("Delete a client by its ID").action(async (id) => {
|
|
15899
15994
|
await deleteClientAction(id);
|
|
15900
15995
|
});
|
|
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) => {
|
|
15996
|
+
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
15997
|
await addClientAction(options);
|
|
15903
15998
|
});
|
|
15904
15999
|
var npubsCmd = program.command("npubs").description("Manage npubs on the daemon (admin or user roles)");
|
|
@@ -15971,16 +16066,21 @@ npubsCmd.command("register").description("Register yourself as the first admin (
|
|
|
15971
16066
|
console.log(`Successfully registered as first admin npub: ${userNpub}`);
|
|
15972
16067
|
}
|
|
15973
16068
|
});
|
|
15974
|
-
npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...).
|
|
16069
|
+
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
16070
|
await ensureDaemonRunning();
|
|
15976
16071
|
const normalized = normalizeNostrPubkey(npubArg);
|
|
15977
16072
|
if (!normalized) {
|
|
15978
16073
|
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
|
15979
16074
|
process.exit(1);
|
|
15980
16075
|
}
|
|
16076
|
+
if (options.role !== "admin" && options.role !== "user") {
|
|
16077
|
+
console.error("Invalid role. Expected 'admin' or 'user'.");
|
|
16078
|
+
process.exit(1);
|
|
16079
|
+
}
|
|
16080
|
+
const body = { npub: npubFromPubkey(normalized), role: options.role };
|
|
15981
16081
|
const result = await callDaemon("/npubs", {
|
|
15982
16082
|
method: "POST",
|
|
15983
|
-
body
|
|
16083
|
+
body
|
|
15984
16084
|
});
|
|
15985
16085
|
if (result.error) {
|
|
15986
16086
|
console.log(result.error);
|
|
@@ -15988,10 +16088,36 @@ npubsCmd.command("add <npub>").description("Add a npub (hex pubkey or npub1...).
|
|
|
15988
16088
|
}
|
|
15989
16089
|
const output = result.output;
|
|
15990
16090
|
if (output?.npub) {
|
|
15991
|
-
console.log(`${output.added ? "Added" : "Already configured"} npub: ${output.npub}`);
|
|
16091
|
+
console.log(`${output.added ? "Added" : "Already configured"} npub: ${output.npub} [${output.role ?? "user"}]`);
|
|
16092
|
+
}
|
|
16093
|
+
});
|
|
16094
|
+
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) => {
|
|
16095
|
+
await ensureDaemonRunning();
|
|
16096
|
+
const normalized = normalizeNostrPubkey(npubArg);
|
|
16097
|
+
if (!normalized) {
|
|
16098
|
+
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
|
16099
|
+
process.exit(1);
|
|
16100
|
+
}
|
|
16101
|
+
if (options.role !== "admin" && options.role !== "user") {
|
|
16102
|
+
console.error("Invalid role. Expected 'admin' or 'user'.");
|
|
16103
|
+
process.exit(1);
|
|
16104
|
+
}
|
|
16105
|
+
const result = await callDaemon("/npubs", {
|
|
16106
|
+
method: "PATCH",
|
|
16107
|
+
body: { npub: npubFromPubkey(normalized), role: options.role }
|
|
16108
|
+
});
|
|
16109
|
+
if (result.error) {
|
|
16110
|
+
console.log(result.error);
|
|
16111
|
+
process.exit(1);
|
|
16112
|
+
}
|
|
16113
|
+
const data = result.output ?? result;
|
|
16114
|
+
if (data?.npub) {
|
|
16115
|
+
console.log(`Updated npub ${data.npub} role to '${data.role}'.`);
|
|
16116
|
+
} else {
|
|
16117
|
+
console.log("Npub not found or update failed.");
|
|
15992
16118
|
}
|
|
15993
16119
|
});
|
|
15994
|
-
npubsCmd.command("delete <npub>").description("Delete an
|
|
16120
|
+
npubsCmd.command("delete <npub>").description("Delete an npub (hex pubkey or npub1...)").action(async (npubArg) => {
|
|
15995
16121
|
await ensureDaemonRunning();
|
|
15996
16122
|
const normalized = normalizeNostrPubkey(npubArg);
|
|
15997
16123
|
if (!normalized) {
|
|
@@ -16167,7 +16293,7 @@ serviceCmd.command("install").description("Install and start routstrd using PM2
|
|
|
16167
16293
|
const path = import.meta.require("path");
|
|
16168
16294
|
daemonPath = path.join(path.dirname(import.meta.url).replace("file://", ""), "daemon", "index.js");
|
|
16169
16295
|
}
|
|
16170
|
-
if (!
|
|
16296
|
+
if (!existsSync10(daemonPath)) {
|
|
16171
16297
|
console.error(`Could not find daemon at ${daemonPath}. Did you run 'bun run build'?`);
|
|
16172
16298
|
process.exit(1);
|
|
16173
16299
|
}
|
|
@@ -16305,14 +16431,14 @@ program.command("logs").description("View daemon logs").option("-f, --follow", "
|
|
|
16305
16431
|
const yesterday = new Date;
|
|
16306
16432
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
16307
16433
|
const yesterdayFile = getLogFileForDate2(yesterday);
|
|
16308
|
-
if (!
|
|
16434
|
+
if (!existsSync10(todayFile) && !existsSync10(yesterdayFile)) {
|
|
16309
16435
|
console.log("No log files found. Daemon may not have started yet.");
|
|
16310
16436
|
console.log(`Logs directory: ${LOGS_DIR2}`);
|
|
16311
16437
|
process.exit(1);
|
|
16312
16438
|
}
|
|
16313
16439
|
const lines = parseInt(options.lines, 10);
|
|
16314
16440
|
const logFiles = [yesterdayFile, todayFile].filter((file, index, files) => {
|
|
16315
|
-
return
|
|
16441
|
+
return existsSync10(file) && files.indexOf(file) === index;
|
|
16316
16442
|
});
|
|
16317
16443
|
if (options.follow) {
|
|
16318
16444
|
const proc2 = Bun.spawn(["tail", "-n", String(lines), "-f", todayFile], {
|
package/package.json
CHANGED
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);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import type { RoutstrdConfig } from "../utils/config";
|
|
5
|
+
import { logger } from "../utils/logger";
|
|
6
|
+
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
7
|
+
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
8
|
+
|
|
9
|
+
export async function installHermesIntegration(
|
|
10
|
+
config: RoutstrdConfig,
|
|
11
|
+
apiKey: string,
|
|
12
|
+
integrationConfig: IntegrationConfig,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const { name, configPath } = integrationConfig;
|
|
15
|
+
|
|
16
|
+
logger.log(`\nInstalling routstr configuration in ${configPath}...`);
|
|
17
|
+
logger.log(`Using API key for ${name}`);
|
|
18
|
+
|
|
19
|
+
const baseUrl = getDaemonBaseUrl(config);
|
|
20
|
+
const baseUrlV1 = `${baseUrl}/v1`;
|
|
21
|
+
|
|
22
|
+
let defaultModel = "minimax-m2.7";
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const data = await callDaemon("/models");
|
|
26
|
+
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
|
27
|
+
|
|
28
|
+
if (models.length >= 3) {
|
|
29
|
+
defaultModel = models[2]!.id;
|
|
30
|
+
logger.log(`Set default model to 3rd available model: ${defaultModel}`);
|
|
31
|
+
} else if (models.length > 0) {
|
|
32
|
+
defaultModel = models[0]!.id;
|
|
33
|
+
logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
|
|
34
|
+
} else {
|
|
35
|
+
logger.log("No models available from routstr daemon, using fallback default.");
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error("Failed to fetch models for Hermes integration:", error);
|
|
39
|
+
logger.log("Using fallback default model.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let content = "";
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync(configPath)) {
|
|
45
|
+
content = await readFile(configPath, "utf-8");
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.error(`Error reading ${configPath}, creating new one.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Remove existing model block
|
|
52
|
+
content = content.replace(/^model:\n(?: .*\n)*/gm, "");
|
|
53
|
+
// Remove existing custom_providers block
|
|
54
|
+
content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
|
|
55
|
+
// Clean up extra blank lines
|
|
56
|
+
content = content.replace(/\n{3,}/g, "\n\n").trim();
|
|
57
|
+
|
|
58
|
+
const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
|
|
59
|
+
|
|
60
|
+
const modelBlock = `model:
|
|
61
|
+
default: ${defaultModel}
|
|
62
|
+
provider: custom
|
|
63
|
+
base_url: ${baseUrlV1}
|
|
64
|
+
api_key: ${apiKey}`;
|
|
65
|
+
|
|
66
|
+
const providerBlock = `custom_providers:
|
|
67
|
+
- name: Routstr (${urlDisplay})
|
|
68
|
+
base_url: ${baseUrlV1}
|
|
69
|
+
api_key: ${apiKey}
|
|
70
|
+
model: ${defaultModel}`;
|
|
71
|
+
|
|
72
|
+
const parts: string[] = [modelBlock];
|
|
73
|
+
if (content) {
|
|
74
|
+
parts.push(content);
|
|
75
|
+
}
|
|
76
|
+
parts.push(providerBlock);
|
|
77
|
+
|
|
78
|
+
const newContent = parts.join("\n\n") + "\n";
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
82
|
+
await writeFile(configPath, newContent);
|
|
83
|
+
logger.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.error(`Failed to write to ${configPath}:`, error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -4,6 +4,7 @@ import { installOpencodeIntegration } from "./opencode";
|
|
|
4
4
|
import { installPiIntegration } from "./pi";
|
|
5
5
|
import { installOpenClawIntegration } from "./openclaw";
|
|
6
6
|
import { installClaudeCodeIntegration } from "./claudecode";
|
|
7
|
+
import { installHermesIntegration } from "./hermes";
|
|
7
8
|
|
|
8
9
|
export interface IntegrationConfig {
|
|
9
10
|
clientId: string;
|
|
@@ -43,6 +44,11 @@ export const CLIENT_CONFIGS: Record<string, IntegrationConfig> = {
|
|
|
43
44
|
name: "Claude Code",
|
|
44
45
|
configPath: join(process.env.HOME || "", ".claude/settings.json"),
|
|
45
46
|
},
|
|
47
|
+
hermes: {
|
|
48
|
+
clientId: "hermes",
|
|
49
|
+
name: "Hermes",
|
|
50
|
+
configPath: join(process.env.HOME || "", ".hermes/config.yaml"),
|
|
51
|
+
},
|
|
46
52
|
};
|
|
47
53
|
|
|
48
54
|
export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
|
@@ -50,6 +56,7 @@ export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
|
|
50
56
|
"pi-agent": installPiIntegration,
|
|
51
57
|
openclaw: installOpenClawIntegration,
|
|
52
58
|
"claude-code": installClaudeCodeIntegration,
|
|
59
|
+
hermes: installHermesIntegration,
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
export async function runIntegrationsForClients(
|