@botcord/daemon 0.2.11 → 0.2.12
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/agent-workspace.js
CHANGED
|
@@ -164,6 +164,98 @@ function writeIfMissing(filePath, content) {
|
|
|
164
164
|
return;
|
|
165
165
|
writeFileSync(filePath, content, { mode: 0o600 });
|
|
166
166
|
}
|
|
167
|
+
const HERMES_PROVIDER_ENV_KEYS = new Set([
|
|
168
|
+
"ANTHROPIC_API_KEY",
|
|
169
|
+
"ANTHROPIC_TOKEN",
|
|
170
|
+
"AWS_ACCESS_KEY_ID",
|
|
171
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
172
|
+
"AWS_DEFAULT_REGION",
|
|
173
|
+
"AWS_PROFILE",
|
|
174
|
+
"AWS_REGION",
|
|
175
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
176
|
+
"AWS_SESSION_TOKEN",
|
|
177
|
+
"CEREBRAS_API_KEY",
|
|
178
|
+
"DEEPSEEK_API_KEY",
|
|
179
|
+
"GEMINI_API_KEY",
|
|
180
|
+
"GOOGLE_API_KEY",
|
|
181
|
+
"GROQ_API_KEY",
|
|
182
|
+
"HERMES_INFERENCE_MODEL",
|
|
183
|
+
"HERMES_INFERENCE_PROVIDER",
|
|
184
|
+
"MISTRAL_API_KEY",
|
|
185
|
+
"OPENAI_API_KEY",
|
|
186
|
+
"OPENAI_BASE_URL",
|
|
187
|
+
"OPENROUTER_API_KEY",
|
|
188
|
+
"OPENROUTER_BASE_URL",
|
|
189
|
+
"TOGETHER_API_KEY",
|
|
190
|
+
"XAI_API_KEY",
|
|
191
|
+
]);
|
|
192
|
+
function parseEnvKeys(content) {
|
|
193
|
+
const keys = new Set();
|
|
194
|
+
for (const line of content.split(/\r?\n/)) {
|
|
195
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
196
|
+
if (match)
|
|
197
|
+
keys.add(match[1]);
|
|
198
|
+
}
|
|
199
|
+
return keys;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Seed per-agent Hermes credentials from the user's normal ~/.hermes/.env.
|
|
203
|
+
* Only provider/model variables are copied; BotCord credentials, chat tokens,
|
|
204
|
+
* and unrelated integration secrets are intentionally left behind.
|
|
205
|
+
*/
|
|
206
|
+
function mergeHermesProviderEnv(targetEnv) {
|
|
207
|
+
const sourceEnv = path.join(homedir(), ".hermes", ".env");
|
|
208
|
+
if (!existsSync(sourceEnv))
|
|
209
|
+
return;
|
|
210
|
+
let targetContent = "";
|
|
211
|
+
try {
|
|
212
|
+
targetContent = existsSync(targetEnv) ? readFileSync(targetEnv, "utf8") : "";
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
targetContent = "";
|
|
216
|
+
}
|
|
217
|
+
const targetKeys = parseEnvKeys(targetContent);
|
|
218
|
+
const additions = [];
|
|
219
|
+
let sourceContent = "";
|
|
220
|
+
try {
|
|
221
|
+
sourceContent = readFileSync(sourceEnv, "utf8");
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
for (const rawLine of sourceContent.split(/\r?\n/)) {
|
|
227
|
+
const match = rawLine.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
228
|
+
if (!match)
|
|
229
|
+
continue;
|
|
230
|
+
const key = match[1];
|
|
231
|
+
if (!HERMES_PROVIDER_ENV_KEYS.has(key) || targetKeys.has(key))
|
|
232
|
+
continue;
|
|
233
|
+
additions.push(rawLine);
|
|
234
|
+
targetKeys.add(key);
|
|
235
|
+
}
|
|
236
|
+
if (additions.length === 0)
|
|
237
|
+
return;
|
|
238
|
+
const prefix = targetContent.endsWith("\n") || targetContent.length === 0 ? "" : "\n";
|
|
239
|
+
const header = targetContent.includes("Imported from ~/.hermes/.env")
|
|
240
|
+
? ""
|
|
241
|
+
: "# Imported provider credentials from ~/.hermes/.env for BotCord-managed Hermes.\n";
|
|
242
|
+
writeFileSync(targetEnv, `${targetContent}${prefix}${header}${additions.join("\n")}\n`, {
|
|
243
|
+
mode: 0o600,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function seedHermesConfig(hermesHome) {
|
|
247
|
+
const source = path.join(homedir(), ".hermes", "config.yaml");
|
|
248
|
+
const target = path.join(hermesHome, "config.yaml");
|
|
249
|
+
if (!existsSync(source) || existsSync(target))
|
|
250
|
+
return;
|
|
251
|
+
try {
|
|
252
|
+
copyFileSync(source, target);
|
|
253
|
+
chmodSync(target, 0o600);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
/* best-effort */
|
|
257
|
+
}
|
|
258
|
+
}
|
|
167
259
|
/**
|
|
168
260
|
* Best-effort link user's `~/.codex/auth.json` into the per-agent CODEX_HOME.
|
|
169
261
|
* Prefers a symlink (auto-follows `codex login` refreshes) and falls back to
|
|
@@ -233,6 +325,8 @@ export function ensureAgentHermesWorkspace(agentId) {
|
|
|
233
325
|
mkdirTolerant(hermesWorkspace);
|
|
234
326
|
writeIfMissing(path.join(hermesHome, ".env"), "# hermes-agent environment overrides for this BotCord agent.\n" +
|
|
235
327
|
"# Add e.g. HERMES_INFERENCE_PROVIDER=openrouter, OPENROUTER_API_KEY=...\n");
|
|
328
|
+
seedHermesConfig(hermesHome);
|
|
329
|
+
mergeHermesProviderEnv(path.join(hermesHome, ".env"));
|
|
236
330
|
return { hermesHome, hermesWorkspace };
|
|
237
331
|
}
|
|
238
332
|
/**
|
package/package.json
CHANGED
|
@@ -12,10 +12,12 @@ import os from "node:os";
|
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
+
agentHermesHomeDir,
|
|
15
16
|
agentHomeDir,
|
|
16
17
|
agentStateDir,
|
|
17
18
|
agentWorkspaceDir,
|
|
18
19
|
applyAgentIdentity,
|
|
20
|
+
ensureAgentHermesWorkspace,
|
|
19
21
|
ensureAgentWorkspace,
|
|
20
22
|
} from "../agent-workspace.js";
|
|
21
23
|
|
|
@@ -88,6 +90,56 @@ describe("ensureAgentWorkspace", () => {
|
|
|
88
90
|
expect(readFileSync(memoryPath, "utf8")).toBe("my custom notes\n");
|
|
89
91
|
});
|
|
90
92
|
|
|
93
|
+
it("seeds Hermes config and provider env without copying unrelated secrets", () => {
|
|
94
|
+
const globalHermes = path.join(tmpHome, ".hermes");
|
|
95
|
+
mkdirSync(globalHermes, { recursive: true });
|
|
96
|
+
writeFileSync(
|
|
97
|
+
path.join(globalHermes, ".env"),
|
|
98
|
+
[
|
|
99
|
+
"OPENAI_API_KEY=sk-test",
|
|
100
|
+
"HERMES_INFERENCE_PROVIDER=custom",
|
|
101
|
+
"BOTCORD_PRIVATE_KEY=must-not-copy",
|
|
102
|
+
"TELEGRAM_BOT_TOKEN=must-not-copy",
|
|
103
|
+
"AWS_REGION=us-east-1",
|
|
104
|
+
"",
|
|
105
|
+
].join("\n"),
|
|
106
|
+
);
|
|
107
|
+
writeFileSync(
|
|
108
|
+
path.join(globalHermes, "config.yaml"),
|
|
109
|
+
"model:\n provider: custom\n default: anthropic/claude-opus-4.6\n",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const { hermesHome } = ensureAgentHermesWorkspace("ag_hermes_seed");
|
|
113
|
+
const env = readFileSync(path.join(hermesHome, ".env"), "utf8");
|
|
114
|
+
const config = readFileSync(path.join(hermesHome, "config.yaml"), "utf8");
|
|
115
|
+
|
|
116
|
+
expect(env).toContain("OPENAI_API_KEY=sk-test");
|
|
117
|
+
expect(env).toContain("HERMES_INFERENCE_PROVIDER=custom");
|
|
118
|
+
expect(env).toContain("AWS_REGION=us-east-1");
|
|
119
|
+
expect(env).not.toContain("BOTCORD_PRIVATE_KEY");
|
|
120
|
+
expect(env).not.toContain("TELEGRAM_BOT_TOKEN");
|
|
121
|
+
expect(config).toContain("provider: custom");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not overwrite existing per-agent Hermes env values", () => {
|
|
125
|
+
const globalHermes = path.join(tmpHome, ".hermes");
|
|
126
|
+
mkdirSync(globalHermes, { recursive: true });
|
|
127
|
+
writeFileSync(
|
|
128
|
+
path.join(globalHermes, ".env"),
|
|
129
|
+
"OPENAI_API_KEY=global\nOPENROUTER_API_KEY=openrouter\n",
|
|
130
|
+
);
|
|
131
|
+
const agentHome = agentHermesHomeDir("ag_hermes_keep");
|
|
132
|
+
mkdirSync(agentHome, { recursive: true });
|
|
133
|
+
writeFileSync(path.join(agentHome, ".env"), "OPENAI_API_KEY=local\n");
|
|
134
|
+
|
|
135
|
+
ensureAgentHermesWorkspace("ag_hermes_keep");
|
|
136
|
+
const env = readFileSync(path.join(agentHome, ".env"), "utf8");
|
|
137
|
+
|
|
138
|
+
expect(env).toContain("OPENAI_API_KEY=local");
|
|
139
|
+
expect(env).not.toContain("OPENAI_API_KEY=global");
|
|
140
|
+
expect(env).toContain("OPENROUTER_API_KEY=openrouter");
|
|
141
|
+
});
|
|
142
|
+
|
|
91
143
|
it("identity.md renders the bio placeholder when bio is missing", () => {
|
|
92
144
|
ensureAgentWorkspace("ag_nobio", { displayName: "Nameless" });
|
|
93
145
|
const identity = readFileSync(path.join(agentWorkspaceDir("ag_nobio"), "identity.md"), "utf8");
|
package/src/agent-workspace.ts
CHANGED
|
@@ -195,6 +195,98 @@ function writeIfMissing(filePath: string, content: string): void {
|
|
|
195
195
|
writeFileSync(filePath, content, { mode: 0o600 });
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
const HERMES_PROVIDER_ENV_KEYS = new Set([
|
|
199
|
+
"ANTHROPIC_API_KEY",
|
|
200
|
+
"ANTHROPIC_TOKEN",
|
|
201
|
+
"AWS_ACCESS_KEY_ID",
|
|
202
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
203
|
+
"AWS_DEFAULT_REGION",
|
|
204
|
+
"AWS_PROFILE",
|
|
205
|
+
"AWS_REGION",
|
|
206
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
207
|
+
"AWS_SESSION_TOKEN",
|
|
208
|
+
"CEREBRAS_API_KEY",
|
|
209
|
+
"DEEPSEEK_API_KEY",
|
|
210
|
+
"GEMINI_API_KEY",
|
|
211
|
+
"GOOGLE_API_KEY",
|
|
212
|
+
"GROQ_API_KEY",
|
|
213
|
+
"HERMES_INFERENCE_MODEL",
|
|
214
|
+
"HERMES_INFERENCE_PROVIDER",
|
|
215
|
+
"MISTRAL_API_KEY",
|
|
216
|
+
"OPENAI_API_KEY",
|
|
217
|
+
"OPENAI_BASE_URL",
|
|
218
|
+
"OPENROUTER_API_KEY",
|
|
219
|
+
"OPENROUTER_BASE_URL",
|
|
220
|
+
"TOGETHER_API_KEY",
|
|
221
|
+
"XAI_API_KEY",
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
function parseEnvKeys(content: string): Set<string> {
|
|
225
|
+
const keys = new Set<string>();
|
|
226
|
+
for (const line of content.split(/\r?\n/)) {
|
|
227
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
228
|
+
if (match) keys.add(match[1]);
|
|
229
|
+
}
|
|
230
|
+
return keys;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Seed per-agent Hermes credentials from the user's normal ~/.hermes/.env.
|
|
235
|
+
* Only provider/model variables are copied; BotCord credentials, chat tokens,
|
|
236
|
+
* and unrelated integration secrets are intentionally left behind.
|
|
237
|
+
*/
|
|
238
|
+
function mergeHermesProviderEnv(targetEnv: string): void {
|
|
239
|
+
const sourceEnv = path.join(homedir(), ".hermes", ".env");
|
|
240
|
+
if (!existsSync(sourceEnv)) return;
|
|
241
|
+
|
|
242
|
+
let targetContent = "";
|
|
243
|
+
try {
|
|
244
|
+
targetContent = existsSync(targetEnv) ? readFileSync(targetEnv, "utf8") : "";
|
|
245
|
+
} catch {
|
|
246
|
+
targetContent = "";
|
|
247
|
+
}
|
|
248
|
+
const targetKeys = parseEnvKeys(targetContent);
|
|
249
|
+
const additions: string[] = [];
|
|
250
|
+
|
|
251
|
+
let sourceContent = "";
|
|
252
|
+
try {
|
|
253
|
+
sourceContent = readFileSync(sourceEnv, "utf8");
|
|
254
|
+
} catch {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const rawLine of sourceContent.split(/\r?\n/)) {
|
|
259
|
+
const match = rawLine.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
260
|
+
if (!match) continue;
|
|
261
|
+
const key = match[1];
|
|
262
|
+
if (!HERMES_PROVIDER_ENV_KEYS.has(key) || targetKeys.has(key)) continue;
|
|
263
|
+
additions.push(rawLine);
|
|
264
|
+
targetKeys.add(key);
|
|
265
|
+
}
|
|
266
|
+
if (additions.length === 0) return;
|
|
267
|
+
|
|
268
|
+
const prefix = targetContent.endsWith("\n") || targetContent.length === 0 ? "" : "\n";
|
|
269
|
+
const header =
|
|
270
|
+
targetContent.includes("Imported from ~/.hermes/.env")
|
|
271
|
+
? ""
|
|
272
|
+
: "# Imported provider credentials from ~/.hermes/.env for BotCord-managed Hermes.\n";
|
|
273
|
+
writeFileSync(targetEnv, `${targetContent}${prefix}${header}${additions.join("\n")}\n`, {
|
|
274
|
+
mode: 0o600,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function seedHermesConfig(hermesHome: string): void {
|
|
279
|
+
const source = path.join(homedir(), ".hermes", "config.yaml");
|
|
280
|
+
const target = path.join(hermesHome, "config.yaml");
|
|
281
|
+
if (!existsSync(source) || existsSync(target)) return;
|
|
282
|
+
try {
|
|
283
|
+
copyFileSync(source, target);
|
|
284
|
+
chmodSync(target, 0o600);
|
|
285
|
+
} catch {
|
|
286
|
+
/* best-effort */
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
198
290
|
/**
|
|
199
291
|
* Best-effort link user's `~/.codex/auth.json` into the per-agent CODEX_HOME.
|
|
200
292
|
* Prefers a symlink (auto-follows `codex login` refreshes) and falls back to
|
|
@@ -267,6 +359,8 @@ export function ensureAgentHermesWorkspace(agentId: string): {
|
|
|
267
359
|
"# hermes-agent environment overrides for this BotCord agent.\n" +
|
|
268
360
|
"# Add e.g. HERMES_INFERENCE_PROVIDER=openrouter, OPENROUTER_API_KEY=...\n",
|
|
269
361
|
);
|
|
362
|
+
seedHermesConfig(hermesHome);
|
|
363
|
+
mergeHermesProviderEnv(path.join(hermesHome, ".env"));
|
|
270
364
|
return { hermesHome, hermesWorkspace };
|
|
271
365
|
}
|
|
272
366
|
|