carouselbot 0.3.1 → 0.3.2

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/src/setup.mjs +39 -10
package/README.md CHANGED
@@ -8,7 +8,9 @@ npx carouselbot@latest setup
8
8
 
9
9
  Setup pins that exact package version in the generated MCP configuration, refreshes
10
10
  the agent skill, and automatically upgrades an older shared daemon. Rerun the
11
- command when you intentionally want to update.
11
+ command when you intentionally want to update. When setup runs inside a named
12
+ Hermes profile, it also installs the skill into that profile's active
13
+ HERMES_HOME and non-interactively enables the discovered CarouselBot tools.
12
14
 
13
15
  For non-interactive agent setup, select the current client explicitly:
14
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carouselbot",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Local-first MCP companion for the hosted CarouselBot editor",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/setup.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { cp, mkdir } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { join } from "node:path";
4
+ import { join, resolve } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { EDITOR_URL, PACKAGE_NAME, PACKAGE_ROOT, PACKAGE_VERSION } from "./config.mjs";
7
7
  import { companionUpgrade } from "./companion.mjs";
@@ -44,13 +44,35 @@ function openCodeSnippet(specifier, version = commandVersion("opencode")) {
44
44
  : { mcp: { [serverName]: { ...server, enabled: true } } }, null, 2);
45
45
  }
46
46
 
47
- async function installSkill() {
48
- const source = join(PACKAGE_ROOT, "skill", "carouselbot");
47
+ export function setupSkillTargets(homeDirectory = homedir(), hermesHome = process.env.HERMES_HOME) {
49
48
  const targets = [
50
- join(homedir(), ".agents", "skills", "carouselbot"),
51
- join(homedir(), ".claude", "skills", "carouselbot"),
52
- join(homedir(), ".hermes", "skills", "carouselbot"),
49
+ join(homeDirectory, ".agents", "skills", "carouselbot"),
50
+ join(homeDirectory, ".claude", "skills", "carouselbot"),
51
+ join(homeDirectory, ".hermes", "skills", "carouselbot"),
53
52
  ];
53
+ if (hermesHome?.trim()) targets.push(join(resolve(hermesHome.trim()), "skills", "carouselbot"));
54
+ return [...new Set(targets)];
55
+ }
56
+
57
+ export function clientSpawnOptions(client) {
58
+ if (client === "hermes") {
59
+ // Hermes tool discovery prompts even after the caller approved CarouselBot
60
+ // setup, and an existing entry adds an overwrite prompt first. Feed both
61
+ // approvals explicitly; EOF otherwise looks like a successful cancel.
62
+ return { input: "y\ny\n", stdio: ["pipe", "inherit", "inherit"] };
63
+ }
64
+ return { stdio: "inherit" };
65
+ }
66
+
67
+ function clientConfigurationPresent(client) {
68
+ if (client !== "hermes") return true;
69
+ const result = spawnSync("hermes", ["mcp", "list"], { encoding: "utf8" });
70
+ return result.status === 0 && /(?:^|\s)carouselbot(?:\s|$)/m.test(String(result.stdout || "") + String(result.stderr || ""));
71
+ }
72
+
73
+ async function installSkill() {
74
+ const source = join(PACKAGE_ROOT, "skill", "carouselbot");
75
+ const targets = setupSkillTargets();
54
76
  for (const target of targets) {
55
77
  await mkdir(target, { recursive: true });
56
78
  await cp(source, target, { recursive: true, force: true });
@@ -93,10 +115,17 @@ export async function runSetup(arguments_) {
93
115
  for (const client of clients) {
94
116
  const command = shellCommand(client, specifier);
95
117
  if (!command) continue;
96
- for (const remove of removeCommands(client)) spawnSync(remove[0], remove.slice(1), { stdio: "ignore" });
97
- const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
98
- if (result.status === 0) configured.push(client);
99
- else process.stderr.write(`Could not configure ${client}; its command is printed above for manual setup.\n`);
118
+ if (client !== "hermes") {
119
+ for (const remove of removeCommands(client)) spawnSync(remove[0], remove.slice(1), { stdio: "ignore" });
120
+ }
121
+ const result = spawnSync(command[0], command.slice(1), clientSpawnOptions(client));
122
+ if (result.status === 0 && clientConfigurationPresent(client)) {
123
+ configured.push(client);
124
+ if (client === "hermes") {
125
+ const legacyRemove = removeCommands(client).find((remove) => remove.at(-1) === legacyServerName);
126
+ if (legacyRemove) spawnSync(legacyRemove[0], legacyRemove.slice(1), { input: "y\n", stdio: ["pipe", "ignore", "ignore"] });
127
+ }
128
+ } else process.stderr.write(`Could not verify ${client}'s CarouselBot config; the existing config was preserved and its command is printed above for manual setup.\n`);
100
129
  }
101
130
  const skillTargets = await installSkill();
102
131
  const companion = await companionUpgrade();