mirror of
https://github.com/Routstr/routstrd.git
synced 2026-07-22 12:22:20 +00:00
feat: add Hermes integration
- Add new installHermesIntegration to write ~/.hermes/config.yaml - Prepend model block with base_url/api_key/default model at top - Append custom_providers block at the bottom - Use 3rd model in daemon list as default - Add Hermes to setup menu and registry
This commit is contained in:
87
src/integrations/hermes.ts
Normal file
87
src/integrations/hermes.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { readFile, writeFile } from "fs/promises";
|
||||
import { dirname } from "path";
|
||||
import type { RoutstrdConfig } from "../utils/config";
|
||||
import { logger } from "../utils/logger";
|
||||
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
||||
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
||||
|
||||
export async function installHermesIntegration(
|
||||
config: RoutstrdConfig,
|
||||
apiKey: string,
|
||||
integrationConfig: IntegrationConfig,
|
||||
): Promise<void> {
|
||||
const { name, configPath } = integrationConfig;
|
||||
|
||||
logger.log(`\nInstalling routstr configuration in ${configPath}...`);
|
||||
logger.log(`Using API key for ${name}`);
|
||||
|
||||
const baseUrl = getDaemonBaseUrl(config);
|
||||
const baseUrlV1 = `${baseUrl}/v1`;
|
||||
|
||||
let defaultModel = "minimax-m2.7";
|
||||
|
||||
try {
|
||||
const data = await callDaemon("/models");
|
||||
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
|
||||
|
||||
if (models.length >= 3) {
|
||||
defaultModel = models[2]!.id;
|
||||
logger.log(`Set default model to 3rd available model: ${defaultModel}`);
|
||||
} else if (models.length > 0) {
|
||||
defaultModel = models[0]!.id;
|
||||
logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
|
||||
} else {
|
||||
logger.log("No models available from routstr daemon, using fallback default.");
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Failed to fetch models for Hermes integration:", error);
|
||||
logger.log("Using fallback default model.");
|
||||
}
|
||||
|
||||
let content = "";
|
||||
try {
|
||||
if (existsSync(configPath)) {
|
||||
content = await readFile(configPath, "utf-8");
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error reading ${configPath}, creating new one.`);
|
||||
}
|
||||
|
||||
// Remove existing model block
|
||||
content = content.replace(/^model:\n(?: .*\n)*/gm, "");
|
||||
// Remove existing custom_providers block
|
||||
content = content.replace(/^custom_providers:\n(?:- .*\n(?: .*\n)*)*/gm, "");
|
||||
// Clean up extra blank lines
|
||||
content = content.replace(/\n{3,}/g, "\n\n").trim();
|
||||
|
||||
const urlDisplay = baseUrl.replace(/^https?:\/\//, "");
|
||||
|
||||
const modelBlock = `model:
|
||||
default: ${defaultModel}
|
||||
provider: custom
|
||||
base_url: ${baseUrlV1}
|
||||
api_key: ${apiKey}`;
|
||||
|
||||
const providerBlock = `custom_providers:
|
||||
- name: Routstr (${urlDisplay})
|
||||
base_url: ${baseUrlV1}
|
||||
api_key: ${apiKey}
|
||||
model: ${defaultModel}`;
|
||||
|
||||
const parts: string[] = [modelBlock];
|
||||
if (content) {
|
||||
parts.push(content);
|
||||
}
|
||||
parts.push(providerBlock);
|
||||
|
||||
const newContent = parts.join("\n\n") + "\n";
|
||||
|
||||
try {
|
||||
mkdirSync(dirname(configPath), { recursive: true });
|
||||
await writeFile(configPath, newContent);
|
||||
logger.log(`Successfully updated ${configPath} with routstr settings.`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to write to ${configPath}:`, error);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { installOpencodeIntegration } from "./opencode";
|
||||
import { installOpenClawIntegration } from "./openclaw";
|
||||
import { installPiIntegration } from "./pi";
|
||||
import { installClaudeCodeIntegration } from "./claudecode";
|
||||
import { installHermesIntegration } from "./hermes";
|
||||
import type { IntegrationConfig } from "./registry";
|
||||
import { CLIENT_CONFIGS, runIntegrationsForClients } from "./registry";
|
||||
export { CLIENT_INTEGRATIONS, CLIENT_CONFIGS, runIntegrationsForClients } from "./registry";
|
||||
@@ -56,7 +57,7 @@ function parseChoice(input: string): number {
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(input, 10);
|
||||
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 5) {
|
||||
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 6) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -71,7 +72,8 @@ export async function setupIntegration(
|
||||
logger.log("2. OpenClaw");
|
||||
logger.log("3. Pi");
|
||||
logger.log("4. Claude Code");
|
||||
logger.log("5. Skip for now");
|
||||
logger.log("5. Hermes");
|
||||
logger.log("6. Skip for now");
|
||||
|
||||
const answer = await ask("Select integration [1]: ");
|
||||
const choice = parseChoice(answer);
|
||||
@@ -81,6 +83,7 @@ export async function setupIntegration(
|
||||
2: "openclaw",
|
||||
3: "pi-agent",
|
||||
4: "claude-code",
|
||||
5: "hermes",
|
||||
};
|
||||
|
||||
const key = integrationByChoice[choice];
|
||||
@@ -92,7 +95,6 @@ export async function setupIntegration(
|
||||
const integrationConfig = CLIENT_CONFIGS[key]!;
|
||||
const { client, created } = await addDaemonClient(
|
||||
integrationConfig.name,
|
||||
integrationConfig.clientId,
|
||||
);
|
||||
|
||||
if (created) {
|
||||
@@ -118,5 +120,11 @@ export async function setupIntegration(
|
||||
|
||||
if (key === "claude-code") {
|
||||
await installClaudeCodeIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "hermes") {
|
||||
await installHermesIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { installOpencodeIntegration } from "./opencode";
|
||||
import { installPiIntegration } from "./pi";
|
||||
import { installOpenClawIntegration } from "./openclaw";
|
||||
import { installClaudeCodeIntegration } from "./claudecode";
|
||||
import { installHermesIntegration } from "./hermes";
|
||||
|
||||
export interface IntegrationConfig {
|
||||
clientId: string;
|
||||
@@ -43,6 +44,11 @@ export const CLIENT_CONFIGS: Record<string, IntegrationConfig> = {
|
||||
name: "Claude Code",
|
||||
configPath: join(process.env.HOME || "", ".claude/settings.json"),
|
||||
},
|
||||
hermes: {
|
||||
clientId: "hermes",
|
||||
name: "Hermes",
|
||||
configPath: join(process.env.HOME || "", ".hermes/config.yaml"),
|
||||
},
|
||||
};
|
||||
|
||||
export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
||||
@@ -50,6 +56,7 @@ export const CLIENT_INTEGRATIONS: Record<string, IntegrationFn> = {
|
||||
"pi-agent": installPiIntegration,
|
||||
openclaw: installOpenClawIntegration,
|
||||
"claude-code": installClaudeCodeIntegration,
|
||||
hermes: installHermesIntegration,
|
||||
};
|
||||
|
||||
export async function runIntegrationsForClients(
|
||||
|
||||
Reference in New Issue
Block a user