routstrd 0.2.14 → 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/bun.lock +41 -22
- package/dist/cli.js +16372 -0
- package/dist/daemon/index.js +20559 -13810
- package/dist/index.js +3300 -3295
- package/package.json +1 -1
- package/src/cli.ts +65 -18
- 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/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,19 +828,25 @@ 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
|
},
|
|
833
835
|
);
|
|
834
836
|
|
|
835
|
-
// Npubs - manage admin
|
|
837
|
+
// Npubs - manage npubs (admin and user roles)
|
|
836
838
|
const npubsCmd = program
|
|
837
839
|
.command("npubs")
|
|
838
|
-
.description("Manage
|
|
840
|
+
.description("Manage npubs on the daemon (admin or user roles)");
|
|
841
|
+
|
|
842
|
+
type NpubEntry = {
|
|
843
|
+
npub: string;
|
|
844
|
+
role: string;
|
|
845
|
+
};
|
|
839
846
|
|
|
840
847
|
npubsCmd
|
|
841
848
|
.command("list")
|
|
842
|
-
.description("List configured
|
|
849
|
+
.description("List configured npubs with their roles")
|
|
843
850
|
.action(async () => {
|
|
844
851
|
await ensureDaemonRunning();
|
|
845
852
|
const config = await loadConfig();
|
|
@@ -850,20 +857,20 @@ npubsCmd
|
|
|
850
857
|
process.exit(1);
|
|
851
858
|
}
|
|
852
859
|
// Handle both wrapped { output: { npubs } } and direct { npubs } response formats
|
|
853
|
-
const data = (result.output as { npubs?:
|
|
860
|
+
const data = (result.output as { npubs?: NpubEntry[] } | undefined)?.npubs
|
|
854
861
|
? result.output
|
|
855
862
|
: result;
|
|
856
|
-
const npubs = (data as { npubs?:
|
|
863
|
+
const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? [];
|
|
857
864
|
if (npubs.length === 0) {
|
|
858
865
|
console.log("No admin npubs configured. Run 'routstrd npubs register' to register yourself as the first admin.");
|
|
859
866
|
return;
|
|
860
867
|
}
|
|
861
|
-
console.log(`
|
|
868
|
+
console.log(`Npubs (${npubs.length}):`);
|
|
862
869
|
let found = false;
|
|
863
|
-
for (const
|
|
864
|
-
const marker = npub === userNpub ? " → you" : "";
|
|
865
|
-
if (npub === userNpub) found = true;
|
|
866
|
-
console.log(`- ${npub}${marker}`);
|
|
870
|
+
for (const entry of npubs) {
|
|
871
|
+
const marker = entry.npub === userNpub ? " → you" : "";
|
|
872
|
+
if (entry.npub === userNpub) found = true;
|
|
873
|
+
console.log(`- ${entry.npub} [${entry.role}]${marker}`);
|
|
867
874
|
}
|
|
868
875
|
if (userNpub && !found) {
|
|
869
876
|
console.log("");
|
|
@@ -892,10 +899,10 @@ npubsCmd
|
|
|
892
899
|
console.log(result.error);
|
|
893
900
|
process.exit(1);
|
|
894
901
|
}
|
|
895
|
-
const data = (result.output as { npubs?:
|
|
902
|
+
const data = (result.output as { npubs?: NpubEntry[] } | undefined)?.npubs
|
|
896
903
|
? result.output
|
|
897
904
|
: result;
|
|
898
|
-
const npubs = (data as { npubs?:
|
|
905
|
+
const npubs = (data as { npubs?: NpubEntry[] } | undefined)?.npubs ?? [];
|
|
899
906
|
if (npubs.length > 0) {
|
|
900
907
|
console.log(`Admin npubs already configured (${npubs.length}). Ask your admin to add your npub. \n Your npub: ${userNpub}`);
|
|
901
908
|
return;
|
|
@@ -925,35 +932,75 @@ npubsCmd
|
|
|
925
932
|
|
|
926
933
|
npubsCmd
|
|
927
934
|
.command("add <npub>")
|
|
928
|
-
.description("Add
|
|
929
|
-
.
|
|
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 }) => {
|
|
930
938
|
await ensureDaemonRunning();
|
|
931
939
|
const normalized = normalizeNostrPubkey(npubArg);
|
|
932
940
|
if (!normalized) {
|
|
933
941
|
console.error("Invalid npub value. Use npub1... or 64-char hex pubkey.");
|
|
934
942
|
process.exit(1);
|
|
935
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 };
|
|
936
949
|
const result = await callDaemon("/npubs", {
|
|
937
950
|
method: "POST",
|
|
938
|
-
body
|
|
951
|
+
body,
|
|
939
952
|
});
|
|
940
953
|
if (result.error) {
|
|
941
954
|
console.log(result.error);
|
|
942
955
|
process.exit(1);
|
|
943
956
|
}
|
|
944
957
|
const output = result.output as
|
|
945
|
-
| { npub?: string; added?: boolean; error?: string }
|
|
958
|
+
| { npub?: string; role?: string; added?: boolean; error?: string }
|
|
946
959
|
| undefined;
|
|
947
960
|
if (output?.npub) {
|
|
948
961
|
console.log(
|
|
949
|
-
`${output.added ? "Added" : "Already configured"}
|
|
962
|
+
`${output.added ? "Added" : "Already configured"} npub: ${output.npub} [${output.role ?? "user"}]`,
|
|
950
963
|
);
|
|
951
964
|
}
|
|
952
965
|
});
|
|
953
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
|
+
|
|
954
1001
|
npubsCmd
|
|
955
1002
|
.command("delete <npub>")
|
|
956
|
-
.description("Delete an
|
|
1003
|
+
.description("Delete an npub (hex pubkey or npub1...)")
|
|
957
1004
|
.action(async (npubArg: string) => {
|
|
958
1005
|
await ensureDaemonRunning();
|
|
959
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(
|
package/src/utils/clients.ts
CHANGED
|
@@ -187,6 +187,7 @@ export interface AddClientOptions {
|
|
|
187
187
|
openclaw?: boolean;
|
|
188
188
|
piAgent?: boolean;
|
|
189
189
|
claudeCode?: boolean;
|
|
190
|
+
hermes?: boolean;
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
export async function addClientAction(options: AddClientOptions): Promise<void> {
|
|
@@ -198,6 +199,7 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
|
|
|
198
199
|
if (options.openclaw) integrationKeys.push("openclaw");
|
|
199
200
|
if (options.piAgent) integrationKeys.push("pi-agent");
|
|
200
201
|
if (options.claudeCode) integrationKeys.push("claude-code");
|
|
202
|
+
if (options.hermes) integrationKeys.push("hermes");
|
|
201
203
|
|
|
202
204
|
if (integrationKeys.length > 0) {
|
|
203
205
|
for (const key of integrationKeys) {
|
|
@@ -233,9 +235,14 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
|
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
if (!options.name) {
|
|
236
|
-
console.error(
|
|
237
|
-
|
|
238
|
-
);
|
|
238
|
+
console.error("error: either provide a client name or specify an integration flag.\n");
|
|
239
|
+
console.error("Options:");
|
|
240
|
+
console.error(" -n, --name <name> Client name");
|
|
241
|
+
console.error(" --opencode Set up OpenCode integration");
|
|
242
|
+
console.error(" --openclaw Set up OpenClaw integration");
|
|
243
|
+
console.error(" --pi-agent Set up Pi Agent integration");
|
|
244
|
+
console.error(" --claude-code Set up Claude Code integration");
|
|
245
|
+
console.error(" --hermes Set up Hermes integration");
|
|
239
246
|
process.exit(1);
|
|
240
247
|
}
|
|
241
248
|
|
|
@@ -37,7 +37,7 @@ export function getDaemonBaseUrl(config: RoutstrdConfig): string {
|
|
|
37
37
|
|
|
38
38
|
export async function callDaemon(
|
|
39
39
|
path: string,
|
|
40
|
-
options: { method?: "GET" | "POST" | "DELETE"; body?: object } = {},
|
|
40
|
+
options: { method?: "GET" | "POST" | "PATCH" | "DELETE"; body?: object } = {},
|
|
41
41
|
): Promise<CommandResponse> {
|
|
42
42
|
const { method = "GET", body } = options;
|
|
43
43
|
const config = await loadConfig();
|