@agentoryhq/adapter-claude-code 0.1.1

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.
@@ -0,0 +1,8 @@
1
+ import type { ApplyAlignOpts, ApplyAlignResult } from "@agentoryhq/runtime-contract";
2
+ /**
3
+ * Write confirmed align patch to Claude Code project files.
4
+ * - MCP → project root `.mcp.json`
5
+ * - Skills name list → `.claude/agentory-skills.json` (tracking; does not install skill bodies)
6
+ * Snapshots prior `.mcp.json` under `.claude/agentory-snapshots/`.
7
+ */
8
+ export declare function applyAlignPatch(opts: ApplyAlignOpts): ApplyAlignResult;
package/dist/align.js ADDED
@@ -0,0 +1,69 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * Write confirmed align patch to Claude Code project files.
5
+ * - MCP → project root `.mcp.json`
6
+ * - Skills name list → `.claude/agentory-skills.json` (tracking; does not install skill bodies)
7
+ * Snapshots prior `.mcp.json` under `.claude/agentory-snapshots/`.
8
+ */
9
+ export function applyAlignPatch(opts) {
10
+ const mcpPath = path.join(opts.projectRoot, ".mcp.json");
11
+ const skillsPath = path.join(opts.projectRoot, ".claude", "agentory-skills.json");
12
+ if (!opts.confirmed) {
13
+ return {
14
+ ok: false,
15
+ error: "refusing write: align not confirmed",
16
+ mcpPath,
17
+ skillsPath,
18
+ };
19
+ }
20
+ const claudeDir = path.join(opts.projectRoot, ".claude");
21
+ fs.mkdirSync(claudeDir, { recursive: true });
22
+ let snapshotPath;
23
+ let priorMcp;
24
+ try {
25
+ if (fs.existsSync(mcpPath)) {
26
+ priorMcp = fs.readFileSync(mcpPath, "utf8");
27
+ const snapDir = path.join(claudeDir, "agentory-snapshots");
28
+ fs.mkdirSync(snapDir, { recursive: true });
29
+ snapshotPath = path.join(snapDir, `mcp-${Date.now()}.json`);
30
+ fs.writeFileSync(snapshotPath, priorMcp, "utf8");
31
+ }
32
+ const mcpServers = {};
33
+ for (const item of opts.patch.mcp) {
34
+ const command = item.command?.trim() || "echo";
35
+ if (/^https?:\/\//i.test(command)) {
36
+ mcpServers[item.name] = { type: "http", url: command };
37
+ continue;
38
+ }
39
+ const parts = command.split(/\s+/);
40
+ mcpServers[item.name] = {
41
+ command: parts[0] ?? "echo",
42
+ args: parts.length > 1 ? parts.slice(1) : undefined,
43
+ };
44
+ }
45
+ const mcpBody = JSON.stringify({ mcpServers }, null, 2);
46
+ const skillsBody = JSON.stringify({ skills: opts.patch.skills }, null, 2);
47
+ fs.writeFileSync(mcpPath, `${mcpBody}\n`, "utf8");
48
+ try {
49
+ fs.writeFileSync(skillsPath, `${skillsBody}\n`, "utf8");
50
+ }
51
+ catch (err) {
52
+ if (priorMcp !== undefined)
53
+ fs.writeFileSync(mcpPath, priorMcp, "utf8");
54
+ else if (fs.existsSync(mcpPath))
55
+ fs.unlinkSync(mcpPath);
56
+ throw err;
57
+ }
58
+ return { ok: true, mcpPath, skillsPath, snapshotPath };
59
+ }
60
+ catch (err) {
61
+ return {
62
+ ok: false,
63
+ error: err instanceof Error ? err.message : String(err),
64
+ mcpPath,
65
+ skillsPath,
66
+ snapshotPath,
67
+ };
68
+ }
69
+ }
@@ -0,0 +1,9 @@
1
+ import type { AdapterContext } from "@agentoryhq/runtime-contract";
2
+ import type { DiscoveredAgent, LiveMcpBinding } from "@agentoryhq/shared";
3
+ /** Parse mcpServers map (project `.mcp.json` or user `~/.claude.json`). */
4
+ export declare function mcpFromClaudeConfig(raw: unknown): LiveMcpBinding[];
5
+ export declare function detectClaudeCode(ctx: AdapterContext): boolean;
6
+ /**
7
+ * Prefer project `.mcp.json`; else user-scope `mcpServers` in `~/.claude.json`.
8
+ */
9
+ export declare function discoverClaudeCodeConfig(ctx: AdapterContext): DiscoveredAgent | null;
@@ -0,0 +1,87 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ function readJsonFile(file) {
4
+ try {
5
+ return JSON.parse(fs.readFileSync(file, "utf8"));
6
+ }
7
+ catch {
8
+ return undefined;
9
+ }
10
+ }
11
+ /** Parse mcpServers map (project `.mcp.json` or user `~/.claude.json`). */
12
+ export function mcpFromClaudeConfig(raw) {
13
+ if (typeof raw !== "object" || raw === null)
14
+ return [];
15
+ const servers = raw.mcpServers;
16
+ if (typeof servers !== "object" || servers === null)
17
+ return [];
18
+ const out = [];
19
+ for (const [name, cfg] of Object.entries(servers)) {
20
+ if (!name)
21
+ continue;
22
+ let command;
23
+ if (typeof cfg === "object" && cfg !== null) {
24
+ const c = cfg;
25
+ if (typeof c.command === "string") {
26
+ const args = Array.isArray(c.args) ? c.args.map(String).join(" ") : "";
27
+ command = args ? `${c.command} ${args}` : c.command;
28
+ }
29
+ else if (typeof c.url === "string") {
30
+ command = c.url;
31
+ }
32
+ }
33
+ out.push({ name, command, status: "unknown" });
34
+ }
35
+ return out;
36
+ }
37
+ function skillsFromSkillsRoot(dir) {
38
+ try {
39
+ return fs
40
+ .readdirSync(dir, { withFileTypes: true })
41
+ .filter((d) => d.isDirectory())
42
+ .map((d) => d.name);
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ }
48
+ export function detectClaudeCode(ctx) {
49
+ const markers = [
50
+ path.join(ctx.cwd, ".claude"),
51
+ path.join(ctx.homeDir, ".claude"),
52
+ path.join(ctx.cwd, ".mcp.json"),
53
+ path.join(ctx.homeDir, ".claude.json"),
54
+ ];
55
+ return markers.some((p) => fs.existsSync(p));
56
+ }
57
+ /**
58
+ * Prefer project `.mcp.json`; else user-scope `mcpServers` in `~/.claude.json`.
59
+ */
60
+ export function discoverClaudeCodeConfig(ctx) {
61
+ if (!detectClaudeCode(ctx))
62
+ return null;
63
+ const projectMcp = path.join(ctx.cwd, ".mcp.json");
64
+ const userClaudeJson = path.join(ctx.homeDir, ".claude.json");
65
+ let mcp = [];
66
+ if (fs.existsSync(projectMcp)) {
67
+ mcp = mcpFromClaudeConfig(readJsonFile(projectMcp));
68
+ }
69
+ else if (fs.existsSync(userClaudeJson)) {
70
+ mcp = mcpFromClaudeConfig(readJsonFile(userClaudeJson));
71
+ }
72
+ const skillNames = new Set();
73
+ for (const d of [
74
+ path.join(ctx.cwd, ".claude", "skills"),
75
+ path.join(ctx.homeDir, ".claude", "skills"),
76
+ ]) {
77
+ for (const name of skillsFromSkillsRoot(d))
78
+ skillNames.add(name);
79
+ }
80
+ return {
81
+ runtime: "claude-code",
82
+ supported: true,
83
+ displayName: `Claude Code · ${path.basename(ctx.cwd) || "workspace"}`,
84
+ mcp,
85
+ skills: [...skillNames],
86
+ };
87
+ }
@@ -0,0 +1,4 @@
1
+ import type { RuntimeAdapter } from "@agentoryhq/runtime-contract";
2
+ export { applyAlignPatch } from "./align.js";
3
+ export { detectClaudeCode, discoverClaudeCodeConfig, mcpFromClaudeConfig } from "./discover.js";
4
+ export declare const claudeCodeAdapter: RuntimeAdapter;
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { ADAPTER_API_VERSION } from "@agentoryhq/runtime-contract";
2
+ import { applyAlignPatch } from "./align.js";
3
+ import { detectClaudeCode, discoverClaudeCodeConfig } from "./discover.js";
4
+ export { applyAlignPatch } from "./align.js";
5
+ export { detectClaudeCode, discoverClaudeCodeConfig, mcpFromClaudeConfig } from "./discover.js";
6
+ export const claudeCodeAdapter = {
7
+ manifest: {
8
+ runtime: "claude-code",
9
+ adapterApi: ADAPTER_API_VERSION,
10
+ displayName: "Claude Code",
11
+ capabilities: ["detect", "discoverConfig", "applyAlign"],
12
+ },
13
+ detect: detectClaudeCode,
14
+ discoverConfig: discoverClaudeCodeConfig,
15
+ applyAlign: applyAlignPatch,
16
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { test } from "node:test";
6
+ import { applyAlignPatch, discoverClaudeCodeConfig } from "./index.js";
7
+ test("discoverClaudeCodeConfig reads project .mcp.json and skills", () => {
8
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "agy-claude-d-"));
9
+ const home = path.join(root, "home");
10
+ const cwd = path.join(root, "proj");
11
+ fs.mkdirSync(cwd, { recursive: true });
12
+ fs.mkdirSync(home, { recursive: true });
13
+ fs.writeFileSync(path.join(cwd, ".mcp.json"), JSON.stringify({
14
+ mcpServers: {
15
+ docs: { type: "http", url: "https://example.com/mcp" },
16
+ local: { command: "npx", args: ["-y", "svc"] },
17
+ },
18
+ }));
19
+ fs.mkdirSync(path.join(cwd, ".claude", "skills", "brief"), { recursive: true });
20
+ fs.writeFileSync(path.join(cwd, ".claude", "skills", "brief", "SKILL.md"), "# x\n");
21
+ const agent = discoverClaudeCodeConfig({ homeDir: home, cwd });
22
+ assert.ok(agent);
23
+ assert.equal(agent.supported, true);
24
+ assert.equal(agent.runtime, "claude-code");
25
+ assert.equal(agent.mcp.length, 2);
26
+ assert.ok(agent.mcp.find((m) => m.name === "docs" && m.command === "https://example.com/mcp"));
27
+ assert.ok(agent.mcp.find((m) => m.name === "local" && m.command?.includes("npx")));
28
+ assert.ok(agent.skills.includes("brief"));
29
+ });
30
+ test("applyAlignPatch Claude refuses unconfirmed; writes .mcp.json on confirm", () => {
31
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "agy-claude-a-"));
32
+ fs.writeFileSync(path.join(root, ".mcp.json"), JSON.stringify({ mcpServers: { a: { command: "old" } } }));
33
+ const denied = applyAlignPatch({
34
+ projectRoot: root,
35
+ patch: { mcp: [{ name: "a", command: "npx new" }], skills: ["s1"] },
36
+ confirmed: false,
37
+ });
38
+ assert.equal(denied.ok, false);
39
+ assert.match(fs.readFileSync(path.join(root, ".mcp.json"), "utf8"), /old/);
40
+ const ok = applyAlignPatch({
41
+ projectRoot: root,
42
+ patch: {
43
+ mcp: [
44
+ { name: "a", command: "npx new" },
45
+ { name: "remote", command: "https://mcp.example/x" },
46
+ ],
47
+ skills: ["s1"],
48
+ },
49
+ confirmed: true,
50
+ });
51
+ assert.equal(ok.ok, true);
52
+ assert.ok(ok.snapshotPath && fs.existsSync(ok.snapshotPath));
53
+ const after = JSON.parse(fs.readFileSync(path.join(root, ".mcp.json"), "utf8"));
54
+ assert.equal(after.mcpServers.a.command, "npx");
55
+ assert.deepEqual(after.mcpServers.a.args, ["new"]);
56
+ assert.equal(after.mcpServers.remote.type, "http");
57
+ assert.equal(after.mcpServers.remote.url, "https://mcp.example/x");
58
+ const skills = JSON.parse(fs.readFileSync(path.join(root, ".claude", "agentory-skills.json"), "utf8"));
59
+ assert.deepEqual(skills.skills, ["s1"]);
60
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@agentoryhq/adapter-claude-code",
3
+ "version": "0.1.1",
4
+ "description": "Agentory RuntimeAdapter for Claude Code",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "files": ["dist"],
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json"
16
+ },
17
+ "dependencies": {
18
+ "@agentoryhq/runtime-contract": "*",
19
+ "@agentoryhq/shared": "*"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/tunsuy/agentory-node.git",
24
+ "directory": "adapters/claude-code"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "engines": {
30
+ "node": ">=20"
31
+ }
32
+ }