@botcord/daemon 0.2.28 → 0.2.29

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.
@@ -54,6 +54,14 @@ export declare function ensureAgentHermesWorkspace(agentId: string, opts?: {
54
54
  hermesHome: string;
55
55
  hermesWorkspace: string;
56
56
  };
57
+ /**
58
+ * Seed BotCord's bundled Hermes skills into a user-owned Hermes profile used
59
+ * by attach mode. Unlike `ensureAgentHermesWorkspace({ attached: true })`,
60
+ * this intentionally writes only the managed `botcord*` skill directories
61
+ * under the profile's `skills/` directory; it does not touch `.env`,
62
+ * `config.yaml`, sessions, or any user-authored skills.
63
+ */
64
+ export declare function ensureAttachedHermesProfileSkills(profileHome: string): void;
57
65
  /**
58
66
  * Idempotently create the agent's home / workspace / state directories and
59
67
  * seed the workspace Markdown files. Existing files are never overwritten —
@@ -334,8 +334,8 @@ export function ensureAgentHermesWorkspace(agentId, opts = {}) {
334
334
  // Attach mode: HERMES_HOME points at the user's `~/.hermes/profiles/<n>/`
335
335
  // so we MUST NOT touch the per-agent isolated home. The cwd
336
336
  // (`hermesWorkspace`) is still ours and `prepareTurn` writes AGENTS.md
337
- // there that's the only thing the daemon is allowed to author when
338
- // attached to a user-owned profile.
337
+ // there. Profile-owned skill seeding is handled separately by
338
+ // `ensureAttachedHermesProfileSkills`.
339
339
  if (opts.attached) {
340
340
  return { hermesHome, hermesWorkspace };
341
341
  }
@@ -420,6 +420,16 @@ function seedCodexSkills(codexHome) {
420
420
  function seedHermesAgentSkills(hermesHome) {
421
421
  copyBundledSkills(path.join(hermesHome, "skills"));
422
422
  }
423
+ /**
424
+ * Seed BotCord's bundled Hermes skills into a user-owned Hermes profile used
425
+ * by attach mode. Unlike `ensureAgentHermesWorkspace({ attached: true })`,
426
+ * this intentionally writes only the managed `botcord*` skill directories
427
+ * under the profile's `skills/` directory; it does not touch `.env`,
428
+ * `config.yaml`, sessions, or any user-authored skills.
429
+ */
430
+ export function ensureAttachedHermesProfileSkills(profileHome) {
431
+ seedHermesAgentSkills(profileHome);
432
+ }
423
433
  /**
424
434
  * Idempotently create the agent's home / workspace / state directories and
425
435
  * seed the workspace Markdown files. Existing files are never overwritten —
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { agentHermesHomeDir, agentHermesWorkspaceDir, ensureAgentHermesWorkspace, } from "../../agent-workspace.js";
4
+ import { agentHermesHomeDir, agentHermesWorkspaceDir, ensureAttachedHermesProfileSkills, ensureAgentHermesWorkspace, } from "../../agent-workspace.js";
5
5
  import { buildCliEnv } from "../cli-resolver.js";
6
6
  import { AcpRuntimeAdapter, } from "./acp-stream.js";
7
7
  import { firstExistingPath, readCommandVersion, resolveCommandOnPath, resolveHomePath, } from "./probe.js";
@@ -234,9 +234,10 @@ export class HermesAgentAdapter extends AcpRuntimeAdapter {
234
234
  };
235
235
  // Attach mode: BotCord agent shares a hermes profile (state.db /
236
236
  // sessions / skills / .env) with the user's command-line `hermes`. In
237
- // this mode we DO NOT seed a private home — the profile is wholly owned
238
- // by the user, and AGENTS.md is written under the per-agent
239
- // hermes-workspace cwd (NOT into the profile root) by `prepareTurn`.
237
+ // this mode we DO NOT seed a private home — AGENTS.md is written under
238
+ // the per-agent hermes-workspace cwd (NOT into the profile root) by
239
+ // `prepareTurn`, while bundled BotCord skills are installed into the
240
+ // attached profile's `skills/` directory so hermes can discover them.
240
241
  if (opts.hermesProfile) {
241
242
  env.HERMES_HOME = hermesProfileHomeDir(opts.hermesProfile);
242
243
  }
@@ -262,6 +263,9 @@ export class HermesAgentAdapter extends AcpRuntimeAdapter {
262
263
  const { hermesWorkspace } = ensureAgentHermesWorkspace(opts.accountId, {
263
264
  attached: !!opts.hermesProfile,
264
265
  });
266
+ if (opts.hermesProfile) {
267
+ ensureAttachedHermesProfileSkills(hermesProfileHomeDir(opts.hermesProfile));
268
+ }
265
269
  const target = path.join(hermesWorkspace, "AGENTS.md");
266
270
  const tmp = path.join(hermesWorkspace, `.AGENTS.md.${process.pid}.tmp`);
267
271
  mkdirSync(hermesWorkspace, { recursive: true, mode: 0o700 });
package/dist/provision.js CHANGED
@@ -10,7 +10,7 @@ import path from "node:path";
10
10
  import { BotCordClient, CONTROL_FRAME_TYPES, defaultCredentialsFile, derivePublicKey, loadStoredCredentials, writeCredentialsFile, } from "@botcord/protocol-core";
11
11
  import { loadConfig, resolveConfiguredAgentIds, saveConfig, } from "./config.js";
12
12
  import { BOTCORD_CHANNEL_TYPE, buildManagedRoutes, prepareGatewayProfile, } from "./daemon-config-map.js";
13
- import { agentHomeDir, agentStateDir, agentWorkspaceDir, applyAgentIdentity, ensureAgentWorkspace, } from "./agent-workspace.js";
13
+ import { agentHomeDir, agentStateDir, agentWorkspaceDir, applyAgentIdentity, ensureAttachedHermesProfileSkills, ensureAgentWorkspace, } from "./agent-workspace.js";
14
14
  import { detectRuntimes, getAdapterModule } from "./adapters/runtimes.js";
15
15
  import { hermesProfileHomeDir, isValidHermesProfileName, listHermesProfiles, } from "./gateway/runtimes/hermes-agent.js";
16
16
  import { log as daemonLog } from "./log.js";
@@ -285,6 +285,9 @@ async function installLocalAgent(credentials, ctx) {
285
285
  keyId: credentials.keyId,
286
286
  savedAt: credentials.savedAt,
287
287
  });
288
+ if (credentials.runtime === "hermes-agent" && credentials.hermesProfile) {
289
+ ensureAttachedHermesProfileSkills(hermesProfileHomeDir(credentials.hermesProfile));
290
+ }
288
291
  }
289
292
  catch (err) {
290
293
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botcord/daemon",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
4
4
  "description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,7 @@ import {
18
18
  agentStateDir,
19
19
  agentWorkspaceDir,
20
20
  applyAgentIdentity,
21
+ ensureAttachedHermesProfileSkills,
21
22
  ensureAgentCodexHome,
22
23
  ensureAgentHermesWorkspace,
23
24
  ensureAgentWorkspace,
@@ -150,6 +151,23 @@ describe("ensureAgentWorkspace", () => {
150
151
  expect(reseeded).toContain("name: botcord");
151
152
  });
152
153
 
154
+ it("seeds bundled skills into an attached Hermes profile without creating private home state", () => {
155
+ const profileHome = path.join(tmpHome, ".hermes", "profiles", "coder");
156
+ mkdirSync(profileHome, { recursive: true });
157
+
158
+ const { hermesHome, hermesWorkspace } = ensureAgentHermesWorkspace("ag_hermes_attach", {
159
+ attached: true,
160
+ });
161
+ ensureAttachedHermesProfileSkills(profileHome);
162
+
163
+ expect(existsSync(path.join(profileHome, "skills", "botcord", "SKILL.md"))).toBe(true);
164
+ expect(existsSync(path.join(profileHome, "skills", "botcord-user-guide", "SKILL.md"))).toBe(
165
+ true,
166
+ );
167
+ expect(existsSync(hermesWorkspace)).toBe(true);
168
+ expect(existsSync(hermesHome)).toBe(false);
169
+ });
170
+
153
171
  it("does not overwrite a user-modified memory.md on a second call", () => {
154
172
  ensureAgentWorkspace("ag_keep", {});
155
173
  const memoryPath = path.join(agentWorkspaceDir("ag_keep"), "memory.md");
@@ -1527,6 +1527,11 @@ describe("provision_agent hermes profile attach", () => {
1527
1527
  >;
1528
1528
  expect(saved.hermesProfile).toBe("coder");
1529
1529
  expect(saved.runtime).toBe("hermes-agent");
1530
+ expect(
1531
+ fs.existsSync(
1532
+ nodePath.join(tmp, ".hermes", "profiles", "coder", "skills", "botcord", "SKILL.md"),
1533
+ ),
1534
+ ).toBe(true);
1530
1535
  });
1531
1536
  });
1532
1537
 
@@ -370,8 +370,8 @@ export function ensureAgentHermesWorkspace(
370
370
  // Attach mode: HERMES_HOME points at the user's `~/.hermes/profiles/<n>/`
371
371
  // so we MUST NOT touch the per-agent isolated home. The cwd
372
372
  // (`hermesWorkspace`) is still ours and `prepareTurn` writes AGENTS.md
373
- // there that's the only thing the daemon is allowed to author when
374
- // attached to a user-owned profile.
373
+ // there. Profile-owned skill seeding is handled separately by
374
+ // `ensureAttachedHermesProfileSkills`.
375
375
  if (opts.attached) {
376
376
  return { hermesHome, hermesWorkspace };
377
377
  }
@@ -462,6 +462,17 @@ function seedHermesAgentSkills(hermesHome: string): void {
462
462
  copyBundledSkills(path.join(hermesHome, "skills"));
463
463
  }
464
464
 
465
+ /**
466
+ * Seed BotCord's bundled Hermes skills into a user-owned Hermes profile used
467
+ * by attach mode. Unlike `ensureAgentHermesWorkspace({ attached: true })`,
468
+ * this intentionally writes only the managed `botcord*` skill directories
469
+ * under the profile's `skills/` directory; it does not touch `.env`,
470
+ * `config.yaml`, sessions, or any user-authored skills.
471
+ */
472
+ export function ensureAttachedHermesProfileSkills(profileHome: string): void {
473
+ seedHermesAgentSkills(profileHome);
474
+ }
475
+
465
476
  /**
466
477
  * Idempotently create the agent's home / workspace / state directories and
467
478
  * seed the workspace Markdown files. Existing files are never overwritten —
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import {
5
5
  agentHermesHomeDir,
6
6
  agentHermesWorkspaceDir,
7
+ ensureAttachedHermesProfileSkills,
7
8
  ensureAgentHermesWorkspace,
8
9
  } from "../../agent-workspace.js";
9
10
  import { buildCliEnv } from "../cli-resolver.js";
@@ -277,9 +278,10 @@ export class HermesAgentAdapter extends AcpRuntimeAdapter {
277
278
  };
278
279
  // Attach mode: BotCord agent shares a hermes profile (state.db /
279
280
  // sessions / skills / .env) with the user's command-line `hermes`. In
280
- // this mode we DO NOT seed a private home — the profile is wholly owned
281
- // by the user, and AGENTS.md is written under the per-agent
282
- // hermes-workspace cwd (NOT into the profile root) by `prepareTurn`.
281
+ // this mode we DO NOT seed a private home — AGENTS.md is written under
282
+ // the per-agent hermes-workspace cwd (NOT into the profile root) by
283
+ // `prepareTurn`, while bundled BotCord skills are installed into the
284
+ // attached profile's `skills/` directory so hermes can discover them.
283
285
  if (opts.hermesProfile) {
284
286
  env.HERMES_HOME = hermesProfileHomeDir(opts.hermesProfile);
285
287
  } else if (opts.accountId) {
@@ -304,6 +306,9 @@ export class HermesAgentAdapter extends AcpRuntimeAdapter {
304
306
  const { hermesWorkspace } = ensureAgentHermesWorkspace(opts.accountId, {
305
307
  attached: !!opts.hermesProfile,
306
308
  });
309
+ if (opts.hermesProfile) {
310
+ ensureAttachedHermesProfileSkills(hermesProfileHomeDir(opts.hermesProfile));
311
+ }
307
312
  const target = path.join(hermesWorkspace, "AGENTS.md");
308
313
  const tmp = path.join(hermesWorkspace, `.AGENTS.md.${process.pid}.tmp`);
309
314
  mkdirSync(hermesWorkspace, { recursive: true, mode: 0o700 });
package/src/provision.ts CHANGED
@@ -52,6 +52,7 @@ import {
52
52
  agentStateDir,
53
53
  agentWorkspaceDir,
54
54
  applyAgentIdentity,
55
+ ensureAttachedHermesProfileSkills,
55
56
  ensureAgentWorkspace,
56
57
  } from "./agent-workspace.js";
57
58
  import { detectRuntimes, getAdapterModule } from "./adapters/runtimes.js";
@@ -431,6 +432,9 @@ async function installLocalAgent(
431
432
  keyId: credentials.keyId,
432
433
  savedAt: credentials.savedAt,
433
434
  });
435
+ if (credentials.runtime === "hermes-agent" && credentials.hermesProfile) {
436
+ ensureAttachedHermesProfileSkills(hermesProfileHomeDir(credentials.hermesProfile));
437
+ }
434
438
  } catch (err) {
435
439
  try {
436
440
  unlinkSync(credentialsFile);