@reeledge/agent-tools 0.1.2 → 0.1.4
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/apps/claude-code.js +94 -8
- package/dist/install.js +2 -0
- package/package.json +1 -1
package/dist/apps/claude-code.js
CHANGED
|
@@ -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,30 @@ 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
|
-
|
|
81
|
+
// 1. Claude Code CLI — best-effort and FULLY ISOLATED: a CLI failure must
|
|
82
|
+
// never block the desktop step below. A spawn ENOENT = not installed;
|
|
83
|
+
// "already exists" (a re-run / `update`, exit 1) or any other failure is
|
|
84
|
+
// logged as skipped, NOT thrown — the connector is already in place, and
|
|
85
|
+
// the desktop bridge still gets written.
|
|
86
|
+
try {
|
|
87
|
+
await env.exec('claude', claudeMcpAddArgs(CONNECTOR_NAME, serverUrl, token));
|
|
88
|
+
log(`Configured Claude Code CLI (${CONNECTOR_NAME})`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
log(err.code === 'ENOENT'
|
|
92
|
+
? 'Claude Code CLI not found — skipped'
|
|
93
|
+
: `Claude Code CLI not configured — skipped (${err.message})`);
|
|
94
|
+
}
|
|
95
|
+
// 2. Claude Desktop — macOS/Windows only; ALWAYS runs, independent of the CLI.
|
|
96
|
+
const cfgPath = desktopPath();
|
|
97
|
+
if (cfgPath !== null) {
|
|
98
|
+
const entry = claudeDesktopEntry(mcpEndpoint(serverUrl), token);
|
|
99
|
+
const existing = fs.existsSync(cfgPath) ? fs.readFileSync(cfgPath, 'utf8') : undefined;
|
|
100
|
+
const merged = mergeClaudeDesktopConfig(existing, CONNECTOR_NAME, entry);
|
|
101
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
102
|
+
fs.writeFileSync(cfgPath, merged, 'utf8');
|
|
103
|
+
log('Configured Claude Desktop (claude_desktop_config.json)');
|
|
104
|
+
}
|
|
27
105
|
},
|
|
28
106
|
dryRunPlan(serverUrl, token, skills) {
|
|
29
107
|
const lines = [];
|
|
@@ -34,6 +112,14 @@ export function makeClaudeCodeAdapter(env) {
|
|
|
34
112
|
}
|
|
35
113
|
const args = claudeMcpAddArgs(CONNECTOR_NAME, serverUrl, token);
|
|
36
114
|
lines.push(`would run: claude ${args.join(' ')}`);
|
|
115
|
+
const cfgPath = desktopPath();
|
|
116
|
+
if (cfgPath !== null) {
|
|
117
|
+
const entry = claudeDesktopEntry(mcpEndpoint(serverUrl), token);
|
|
118
|
+
lines.push(`would write ${cfgPath} with mcpServers.${CONNECTOR_NAME}:`);
|
|
119
|
+
for (const l of JSON.stringify(entry, null, 2).split('\n')) {
|
|
120
|
+
lines.push(` ${l}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
37
123
|
return lines;
|
|
38
124
|
},
|
|
39
125
|
};
|
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