Compare commits
No commits in common. "main" and "feat/models-command" have entirely different histories.
main
...
feat/model
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@shazhou/hermes-harness",
|
"name": "hermes-harness",
|
||||||
"version": "0.1.1",
|
"version": "0.1.0",
|
||||||
"description": "Hermes Agent CLI harness tools",
|
"description": "Hermes Agent CLI harness tools",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { readFileSync, writeFileSync, existsSync } from "fs";
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
||||||
import { execSync } from "child_process";
|
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { parse, stringify } from "yaml";
|
import { parse, stringify } from "yaml";
|
||||||
|
|
||||||
const CONFIG_PATH = join(homedir(), ".hermes", "config.yaml");
|
const CONFIG_PATH = join(homedir(), ".hermes", "config.yaml");
|
||||||
const ENV_PATH = join(homedir(), ".hermes", ".env");
|
const ENV_PATH = join(homedir(), ".hermes", ".env");
|
||||||
const CFG_PROVIDERS_KEY = "HERMES_CUSTOM_PROVIDERS";
|
|
||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────────────
|
// ── Types ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -38,7 +36,7 @@ interface Config {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_AUXILIARY_TASKS = [
|
const AUXILIARY_TASKS = [
|
||||||
"vision",
|
"vision",
|
||||||
"web_extract",
|
"web_extract",
|
||||||
"compression",
|
"compression",
|
||||||
@ -49,18 +47,6 @@ const DEFAULT_AUXILIARY_TASKS = [
|
|||||||
"approval",
|
"approval",
|
||||||
];
|
];
|
||||||
|
|
||||||
function getAuxiliaryTasks(): string[] {
|
|
||||||
try {
|
|
||||||
const config = loadConfig();
|
|
||||||
const aux = config.auxiliary || {};
|
|
||||||
const tasks = Object.keys(aux).filter(
|
|
||||||
(k) => typeof aux[k] === "object" && aux[k]?.provider !== undefined
|
|
||||||
);
|
|
||||||
if (tasks.length > 0) return tasks;
|
|
||||||
} catch {}
|
|
||||||
return DEFAULT_AUXILIARY_TASKS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────────────
|
// ── Helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function loadConfig(): Config {
|
function loadConfig(): Config {
|
||||||
@ -74,43 +60,10 @@ function saveConfig(config: Config) {
|
|||||||
writeFileSync(CONFIG_PATH, stringify(config, { lineWidth: 120 }), "utf-8");
|
writeFileSync(CONFIG_PATH, stringify(config, { lineWidth: 120 }), "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read all available providers from cfg (central registry).
|
|
||||||
* Falls back to config.yaml custom_providers if cfg unavailable.
|
|
||||||
*/
|
|
||||||
function getCfgProviders(): CustomProvider[] {
|
|
||||||
try {
|
|
||||||
const raw = execSync(`cfg get ${CFG_PROVIDERS_KEY}`, {
|
|
||||||
encoding: "utf-8",
|
|
||||||
timeout: 5000,
|
|
||||||
}).trim();
|
|
||||||
if (!raw) return [];
|
|
||||||
return JSON.parse(raw) as CustomProvider[];
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get providers from cfg (authoritative source).
|
|
||||||
* The config.yaml custom_providers is now only a "currently active" slot.
|
|
||||||
*/
|
|
||||||
function getAllProviders(): CustomProvider[] {
|
|
||||||
const cfgProviders = getCfgProviders();
|
|
||||||
if (cfgProviders.length > 0) return cfgProviders;
|
|
||||||
// Fallback to config.yaml if cfg has nothing
|
|
||||||
const config = loadConfig();
|
|
||||||
return config.custom_providers || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCustomProviders(config: Config): CustomProvider[] {
|
function getCustomProviders(config: Config): CustomProvider[] {
|
||||||
return config.custom_providers || [];
|
return config.custom_providers || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findProviderFromRegistry(name: string): CustomProvider | undefined {
|
|
||||||
return getAllProviders().find((p) => p.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findProvider(
|
function findProvider(
|
||||||
config: Config,
|
config: Config,
|
||||||
name: string
|
name: string
|
||||||
@ -158,41 +111,42 @@ function getAuthHeaders(provider: CustomProvider): Record<string, string> {
|
|||||||
|
|
||||||
function providers() {
|
function providers() {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const allProviders = getAllProviders();
|
const customProviders = getCustomProviders(config);
|
||||||
const mainProvider = config.model?.provider || "auto";
|
const mainProvider = config.model?.provider || "auto";
|
||||||
const mainModel = config.model?.default || "(not set)";
|
const mainModel = config.model?.default || "(not set)";
|
||||||
|
|
||||||
console.log(`Main: provider=${mainProvider} model=${mainModel}\n`);
|
console.log(`Main: provider=${mainProvider} model=${mainModel}\n`);
|
||||||
|
|
||||||
if (allProviders.length === 0) {
|
if (customProviders.length === 0) {
|
||||||
console.log("No custom providers configured.");
|
console.log("No custom providers configured.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Custom providers (from cfg):\n");
|
console.log("Custom providers:\n");
|
||||||
for (const p of allProviders) {
|
for (const p of customProviders) {
|
||||||
const active = p.name === mainProvider || `custom:${p.name}` === mainProvider ? " ◀ active" : "";
|
const active = p.name === mainProvider || `custom:${p.name}` === mainProvider ? " ◀ active" : "";
|
||||||
console.log(` ${p.name}${active}`);
|
console.log(` ${p.name}${active}`);
|
||||||
console.log(` url: ${p.base_url}`);
|
console.log(` url: ${p.base_url}`);
|
||||||
if (p.api_mode) console.log(` mode: ${p.api_mode}`);
|
if (p.api_mode) console.log(` mode: ${p.api_mode}`);
|
||||||
}
|
}
|
||||||
console.log(`\nTotal: ${allProviders.length}`);
|
console.log(`\nTotal: ${customProviders.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── list ────────────────────────────────────────────────────────────────
|
// ── list ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function list(providerName?: string) {
|
async function list(providerName?: string) {
|
||||||
const allProviders = getAllProviders();
|
const config = loadConfig();
|
||||||
|
const customProviders = getCustomProviders(config);
|
||||||
|
|
||||||
const targets: CustomProvider[] = providerName
|
const targets: CustomProvider[] = providerName
|
||||||
? allProviders.filter((p) => p.name === providerName)
|
? customProviders.filter((p) => p.name === providerName)
|
||||||
: allProviders;
|
: customProviders;
|
||||||
|
|
||||||
if (targets.length === 0) {
|
if (targets.length === 0) {
|
||||||
if (providerName) {
|
if (providerName) {
|
||||||
console.error(`Provider not found: ${providerName}`);
|
console.error(`Provider not found: ${providerName}`);
|
||||||
console.error(
|
console.error(
|
||||||
`Available: ${allProviders.map((p) => p.name).join(", ")}`
|
`Available: ${customProviders.map((p) => p.name).join(", ")}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log("No custom providers configured.");
|
console.log("No custom providers configured.");
|
||||||
@ -254,11 +208,12 @@ async function list(providerName?: string) {
|
|||||||
// ── test ────────────────────────────────────────────────────────────────
|
// ── test ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function test(providerName?: string) {
|
async function test(providerName?: string) {
|
||||||
const allProviders = getAllProviders();
|
const config = loadConfig();
|
||||||
|
const customProviders = getCustomProviders(config);
|
||||||
|
|
||||||
const targets: CustomProvider[] = providerName
|
const targets: CustomProvider[] = providerName
|
||||||
? allProviders.filter((p) => p.name === providerName)
|
? customProviders.filter((p) => p.name === providerName)
|
||||||
: allProviders;
|
: customProviders;
|
||||||
|
|
||||||
if (targets.length === 0) {
|
if (targets.length === 0) {
|
||||||
if (providerName) {
|
if (providerName) {
|
||||||
@ -344,74 +299,32 @@ function switchConfig(opts: SwitchOptions) {
|
|||||||
// Switch auxiliary tasks
|
// Switch auxiliary tasks
|
||||||
if (!config.auxiliary) config.auxiliary = {} as Record<string, AuxiliaryEntry>;
|
if (!config.auxiliary) config.auxiliary = {} as Record<string, AuxiliaryEntry>;
|
||||||
|
|
||||||
// Look up provider from cfg registry to get base_url & api_key
|
|
||||||
const registryProvider = findProviderFromRegistry(opts.provider);
|
|
||||||
const providerValue = registryProvider ? `custom:${registryProvider.name}` : opts.provider;
|
|
||||||
|
|
||||||
// Ensure custom_providers in config.yaml contains this provider
|
|
||||||
if (registryProvider) {
|
|
||||||
if (!config.custom_providers) config.custom_providers = [];
|
|
||||||
const existing = config.custom_providers.find((p) => p.name === registryProvider.name);
|
|
||||||
if (!existing) {
|
|
||||||
config.custom_providers.push(registryProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const task of opts.auxTasks) {
|
for (const task of opts.auxTasks) {
|
||||||
if (!config.auxiliary[task]) {
|
if (!config.auxiliary[task]) {
|
||||||
config.auxiliary[task] = { provider: "", model: "", timeout: 30 };
|
config.auxiliary[task] = { provider: "", model: "", timeout: 30 };
|
||||||
}
|
}
|
||||||
config.auxiliary[task].provider = providerValue;
|
config.auxiliary[task].provider = opts.provider;
|
||||||
if (registryProvider) {
|
|
||||||
config.auxiliary[task].base_url = registryProvider.base_url;
|
|
||||||
const apiKey = getProviderApiKey(registryProvider);
|
|
||||||
if (apiKey) config.auxiliary[task].api_key = apiKey;
|
|
||||||
}
|
|
||||||
if (opts.model) {
|
if (opts.model) {
|
||||||
config.auxiliary[task].model = opts.model;
|
config.auxiliary[task].model = opts.model;
|
||||||
}
|
}
|
||||||
console.log(` auxiliary.${task} → provider=${providerValue}${opts.model ? ` model=${opts.model}` : ""}`);
|
console.log(` auxiliary.${task} → provider=${opts.provider}${opts.model ? ` model=${opts.model}` : ""}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Switch main provider — look up from cfg registry
|
// Switch main provider
|
||||||
const registryProvider = findProviderFromRegistry(opts.provider);
|
const providerValue = findProvider(config, opts.provider)
|
||||||
|
? `custom:${opts.provider}`
|
||||||
|
: opts.provider;
|
||||||
|
|
||||||
if (registryProvider) {
|
|
||||||
// Write this provider into config.yaml custom_providers (replace all)
|
|
||||||
config.custom_providers = [registryProvider];
|
|
||||||
const providerValue = `custom:${registryProvider.name}`;
|
|
||||||
if (!config.model) config.model = {};
|
if (!config.model) config.model = {};
|
||||||
config.model.provider = providerValue;
|
config.model.provider = providerValue;
|
||||||
if (opts.model) {
|
if (opts.model) {
|
||||||
config.model.default = opts.model;
|
config.model.default = opts.model;
|
||||||
}
|
}
|
||||||
console.log(` main → provider=${providerValue}${opts.model ? ` model=${opts.model}` : ""}`);
|
console.log(` main → provider=${providerValue}${opts.model ? ` model=${opts.model}` : ""}`);
|
||||||
console.log(` config.yaml custom_providers updated with ${registryProvider.name}`);
|
|
||||||
} else {
|
|
||||||
// Standard provider (openrouter, anthropic, etc.)
|
|
||||||
if (!config.model) config.model = {};
|
|
||||||
config.model.provider = opts.provider;
|
|
||||||
if (opts.model) {
|
|
||||||
config.model.default = opts.model;
|
|
||||||
}
|
|
||||||
// Clear custom_providers since switching to standard
|
|
||||||
delete config.custom_providers;
|
|
||||||
console.log(` main → provider=${opts.provider}${opts.model ? ` model=${opts.model}` : ""}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveConfig(config);
|
saveConfig(config);
|
||||||
|
console.log("\nConfig saved. New sessions will use the updated settings.");
|
||||||
// Restart gateway
|
|
||||||
try {
|
|
||||||
execSync("systemctl --user restart hermes-gateway 2>/dev/null || true", {
|
|
||||||
encoding: "utf-8",
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
console.log("\nConfig saved & gateway restarted.");
|
|
||||||
} catch {
|
|
||||||
console.log("\nConfig saved. Restart gateway manually to apply.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchTelegram(opts: SwitchOptions) {
|
async function switchTelegram(opts: SwitchOptions) {
|
||||||
@ -473,19 +386,16 @@ async function switchTelegram(opts: SwitchOptions) {
|
|||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
console.log(`Sent: ${command}`);
|
console.log(`Sent: ${command}`);
|
||||||
console.log(opts.global ? "Persistent change (--global)." : "Current session only.");
|
console.log(opts.global ? "Persistent change (--global)." : "Current session only.");
|
||||||
// Clean up the command message after a delay so hermes can process it
|
// Clean up the command message
|
||||||
if (result.result?.message_id) {
|
if (result.result?.message_id) {
|
||||||
const msgId = result.result.message_id;
|
|
||||||
setTimeout(async () => {
|
|
||||||
await fetch(`https://api.telegram.org/bot${token}/deleteMessage`, {
|
await fetch(`https://api.telegram.org/bot${token}/deleteMessage`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: msgId,
|
message_id: result.result.message_id,
|
||||||
}),
|
}),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, 3000);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`Telegram API error: ${result.description}`);
|
console.error(`Telegram API error: ${result.description}`);
|
||||||
@ -566,10 +476,9 @@ export async function models(args: string[]) {
|
|||||||
i++;
|
i++;
|
||||||
} else if (args[i] === "--aux" && i + 1 < args.length) {
|
} else if (args[i] === "--aux" && i + 1 < args.length) {
|
||||||
const task = args[i + 1];
|
const task = args[i + 1];
|
||||||
const knownTasks = getAuxiliaryTasks();
|
if (!AUXILIARY_TASKS.includes(task)) {
|
||||||
if (!knownTasks.includes(task)) {
|
|
||||||
console.error(`Unknown auxiliary task: ${task}`);
|
console.error(`Unknown auxiliary task: ${task}`);
|
||||||
console.error(`Available: ${knownTasks.join(", ")}`);
|
console.error(`Available: ${AUXILIARY_TASKS.join(", ")}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
auxTasks.push(task);
|
auxTasks.push(task);
|
||||||
|
|||||||
@ -3,9 +3,7 @@ import { homedir } from "os";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { parse, stringify } from "yaml";
|
import { parse, stringify } from "yaml";
|
||||||
|
|
||||||
const HERMES_HOME = process.env.HERMES_HOME || join(homedir(), ".hermes");
|
const CONFIG_PATH = join(homedir(), ".hermes", "config.yaml");
|
||||||
const CONFIG_PATH = join(HERMES_HOME, "config.yaml");
|
|
||||||
const SOUL_PATH = join(HERMES_HOME, "SOUL.md");
|
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
agent?: {
|
agent?: {
|
||||||
@ -53,30 +51,6 @@ function getPreview(value: string | PersonalityDict): string {
|
|||||||
return value.description || (value.system_prompt || "").slice(0, 60) + "...";
|
return value.description || (value.system_prompt || "").slice(0, 60) + "...";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SOUL.md management ---
|
|
||||||
|
|
||||||
function loadConventions(): string {
|
|
||||||
try {
|
|
||||||
const { execSync } = require("child_process");
|
|
||||||
const result = execSync("cfg get HERMES_AGENT_CONVENTIONS", {
|
|
||||||
encoding: "utf-8",
|
|
||||||
timeout: 5000,
|
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
|
||||||
}).trim();
|
|
||||||
return result || "";
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeSoulMd(personalityPrompt: string) {
|
|
||||||
const conventions = loadConventions();
|
|
||||||
const parts = [personalityPrompt];
|
|
||||||
if (conventions) parts.push(conventions);
|
|
||||||
const content = parts.filter(Boolean).join("\n\n");
|
|
||||||
writeFileSync(SOUL_PATH, content + "\n", "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Actions ---
|
// --- Actions ---
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
@ -152,7 +126,6 @@ function switchLocal(name: string) {
|
|||||||
if (!config.agent) config.agent = {};
|
if (!config.agent) config.agent = {};
|
||||||
config.agent.system_prompt = "";
|
config.agent.system_prompt = "";
|
||||||
saveConfig(config);
|
saveConfig(config);
|
||||||
writeSoulMd("");
|
|
||||||
console.log("Personality cleared.");
|
console.log("Personality cleared.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -167,9 +140,7 @@ function switchLocal(name: string) {
|
|||||||
if (!config.agent) config.agent = {};
|
if (!config.agent) config.agent = {};
|
||||||
config.agent.system_prompt = prompt;
|
config.agent.system_prompt = prompt;
|
||||||
saveConfig(config);
|
saveConfig(config);
|
||||||
writeSoulMd(prompt);
|
|
||||||
console.log(`Switched to: ${name}`);
|
console.log(`Switched to: ${name}`);
|
||||||
console.log("SOUL.md updated.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchTelegram(name: string) {
|
async function switchTelegram(name: string) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user