@reeledge/agent-tools 0.1.2 → 0.1.3

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.
@@ -1,20 +1,75 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
- import { claudeMcpAddArgs } from '../mcp.js';
3
+ import { claudeMcpAddArgs, mcpEndpoint } from '../mcp.js';
3
4
  import { writeSkillTree } from '../skills.js';
4
5
  import { APP_LABELS, CONNECTOR_NAME, } from './types.js';
5
- /**
6
- * Claude Code adapter. Skills are per-skill `SKILL.md` directories under
7
- * `~/.claude/skills/` (overridable via `--skills-dir`); the MCP connector is
8
- * registered by spawning `claude mcp add --transport http …` (the documented
9
- * Claude Code path), which embeds the bearer token in an `Authorization` header.
10
- */
11
6
  /** Resolve the skills dir: explicit override, else `~/.claude/skills`. */
12
7
  function claudeSkillsDir(env) {
13
8
  return env.skillsDirOverride ?? path.join(env.homeDir, '.claude', 'skills');
14
9
  }
10
+ /**
11
+ * Absolute path to the Claude Desktop config file, or `null` on platforms where
12
+ * Claude Desktop doesn't ship (so the caller skips the desktop surface). Pure —
13
+ * `homeDir` + `platform` in, path out:
14
+ * - darwin → `<homeDir>/Library/Application Support/Claude/claude_desktop_config.json`
15
+ * - win32 → `<%APPDATA% or homeDir/AppData/Roaming>/Claude/claude_desktop_config.json`
16
+ * - else → null
17
+ */
18
+ export function claudeDesktopConfigPath(homeDir, platform) {
19
+ if (platform === 'darwin') {
20
+ return path.join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
21
+ }
22
+ if (platform === 'win32') {
23
+ const appData = process.env.APPDATA ?? path.join(homeDir, 'AppData', 'Roaming');
24
+ return path.join(appData, 'Claude', 'claude_desktop_config.json');
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * The `mcp-remote` bridge entry for Claude Desktop. The header value lives in an
30
+ * `env` var and is referenced as `Authorization:${AUTH_HEADER}` (NO space):
31
+ * Claude Desktop splits each arg on spaces, so an inline `Bearer <token>` would
32
+ * be torn apart — keeping it in env is the supported escape hatch.
33
+ */
34
+ export function claudeDesktopEntry(endpoint, token) {
35
+ return {
36
+ command: 'npx',
37
+ args: ['-y', 'mcp-remote', endpoint, '--header', 'Authorization:${AUTH_HEADER}'],
38
+ env: { AUTH_HEADER: `Bearer ${token}` },
39
+ };
40
+ }
41
+ /**
42
+ * Merge a server entry into an existing `claude_desktop_config.json` body (or
43
+ * `undefined` when no file exists). Malformed JSON is replaced with a fresh file
44
+ * (mirroring how the CLI's own config loader tolerates corruption). Other servers
45
+ * + top-level keys are preserved. Returns pretty-printed JSON + trailing newline.
46
+ */
47
+ export function mergeClaudeDesktopConfig(existing, name, entry) {
48
+ let root = {};
49
+ if (existing && existing.trim() !== '') {
50
+ try {
51
+ const parsed = JSON.parse(existing);
52
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
53
+ root = parsed;
54
+ }
55
+ }
56
+ catch {
57
+ root = {};
58
+ }
59
+ }
60
+ const servers = root.mcpServers && typeof root.mcpServers === 'object' && !Array.isArray(root.mcpServers)
61
+ ? root.mcpServers
62
+ : {};
63
+ servers[name] = entry;
64
+ root.mcpServers = servers;
65
+ return `${JSON.stringify(root, null, 2)}\n`;
66
+ }
15
67
  /** Construct the Claude Code adapter for the given environment. */
16
68
  export function makeClaudeCodeAdapter(env) {
17
69
  const skillsDir = () => claudeSkillsDir(env);
70
+ const platform = () => env.platform ?? process.platform;
71
+ const log = (line) => env.log?.(line);
72
+ const desktopPath = () => claudeDesktopConfigPath(env.homeDir, platform());
18
73
  return {
19
74
  id: 'claude-code',
20
75
  label: APP_LABELS['claude-code'],
@@ -23,7 +78,31 @@ export function makeClaudeCodeAdapter(env) {
23
78
  return skills.flatMap((s) => writeSkillTree(skillsDir(), s.slug, s.files));
24
79
  },
25
80
  async configureMcp(serverUrl, token) {
26
- await env.exec('claude', claudeMcpAddArgs(CONNECTOR_NAME, serverUrl, token));
81
+ // 1. Claude Code CLI — only if `claude` is on PATH. A spawn ENOENT means
82
+ // "not installed": skip gracefully. Any other failure is a real error
83
+ // and propagates so the caller can warn.
84
+ try {
85
+ await env.exec('claude', claudeMcpAddArgs(CONNECTOR_NAME, serverUrl, token));
86
+ log(`Configured Claude Code CLI (${CONNECTOR_NAME})`);
87
+ }
88
+ catch (err) {
89
+ if (err.code === 'ENOENT') {
90
+ log('Claude Code CLI not found — skipped');
91
+ }
92
+ else {
93
+ throw err;
94
+ }
95
+ }
96
+ // 2. Claude Desktop — macOS/Windows only; bridge via `mcp-remote`.
97
+ const cfgPath = desktopPath();
98
+ if (cfgPath !== null) {
99
+ const entry = claudeDesktopEntry(mcpEndpoint(serverUrl), token);
100
+ const existing = fs.existsSync(cfgPath) ? fs.readFileSync(cfgPath, 'utf8') : undefined;
101
+ const merged = mergeClaudeDesktopConfig(existing, CONNECTOR_NAME, entry);
102
+ fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
103
+ fs.writeFileSync(cfgPath, merged, 'utf8');
104
+ log('Configured Claude Desktop (claude_desktop_config.json)');
105
+ }
27
106
  },
28
107
  dryRunPlan(serverUrl, token, skills) {
29
108
  const lines = [];
@@ -34,6 +113,14 @@ export function makeClaudeCodeAdapter(env) {
34
113
  }
35
114
  const args = claudeMcpAddArgs(CONNECTOR_NAME, serverUrl, token);
36
115
  lines.push(`would run: claude ${args.join(' ')}`);
116
+ const cfgPath = desktopPath();
117
+ if (cfgPath !== null) {
118
+ const entry = claudeDesktopEntry(mcpEndpoint(serverUrl), token);
119
+ lines.push(`would write ${cfgPath} with mcpServers.${CONNECTOR_NAME}:`);
120
+ for (const l of JSON.stringify(entry, null, 2).split('\n')) {
121
+ lines.push(` ${l}`);
122
+ }
123
+ }
37
124
  return lines;
38
125
  },
39
126
  };
package/dist/install.js CHANGED
@@ -98,6 +98,8 @@ async function run(opts, deps, mode, allowedSkills) {
98
98
  homeDir: deps.homeDir ?? os.homedir(),
99
99
  exec: deps.exec,
100
100
  skillsDirOverride: opts.skillsDir,
101
+ platform: process.platform,
102
+ log: deps.log,
101
103
  };
102
104
  const adapters = (deps.makeAdapters ?? defaultMakeAdapters)(appIds, env);
103
105
  // 1. Fetch the catalog. This is the first authenticated call — only once it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reeledge/agent-tools",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "description": "Reel Edge agent-tools installer CLI — installs/updates the Reel Edge MCP connector + skills for Claude Code / Codex / Droid.",
6
6
  "type": "module",