@cleocode/adapters 2.0.0
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/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1927 -0
- package/dist/index.js.map +7 -0
- package/dist/providers/claude-code/adapter.d.ts +75 -0
- package/dist/providers/claude-code/adapter.d.ts.map +1 -0
- package/dist/providers/claude-code/adapter.js +154 -0
- package/dist/providers/claude-code/adapter.js.map +1 -0
- package/dist/providers/claude-code/context-monitor.d.ts +24 -0
- package/dist/providers/claude-code/context-monitor.d.ts.map +1 -0
- package/dist/providers/claude-code/context-monitor.js +148 -0
- package/dist/providers/claude-code/context-monitor.js.map +1 -0
- package/dist/providers/claude-code/hooks.d.ts +59 -0
- package/dist/providers/claude-code/hooks.d.ts.map +1 -0
- package/dist/providers/claude-code/hooks.js +77 -0
- package/dist/providers/claude-code/hooks.js.map +1 -0
- package/dist/providers/claude-code/index.d.ts +24 -0
- package/dist/providers/claude-code/index.d.ts.map +1 -0
- package/dist/providers/claude-code/index.js +26 -0
- package/dist/providers/claude-code/index.js.map +1 -0
- package/dist/providers/claude-code/install.d.ts +75 -0
- package/dist/providers/claude-code/install.d.ts.map +1 -0
- package/dist/providers/claude-code/install.js +237 -0
- package/dist/providers/claude-code/install.js.map +1 -0
- package/dist/providers/claude-code/paths.d.ts +24 -0
- package/dist/providers/claude-code/paths.d.ts.map +1 -0
- package/dist/providers/claude-code/paths.js +33 -0
- package/dist/providers/claude-code/paths.js.map +1 -0
- package/dist/providers/claude-code/spawn.d.ts +60 -0
- package/dist/providers/claude-code/spawn.d.ts.map +1 -0
- package/dist/providers/claude-code/spawn.js +160 -0
- package/dist/providers/claude-code/spawn.js.map +1 -0
- package/dist/providers/claude-code/statusline.d.ts +24 -0
- package/dist/providers/claude-code/statusline.d.ts.map +1 -0
- package/dist/providers/claude-code/statusline.js +85 -0
- package/dist/providers/claude-code/statusline.js.map +1 -0
- package/dist/providers/claude-code/task-sync.d.ts +27 -0
- package/dist/providers/claude-code/task-sync.d.ts.map +1 -0
- package/dist/providers/claude-code/task-sync.js +124 -0
- package/dist/providers/claude-code/task-sync.js.map +1 -0
- package/dist/providers/claude-code/transport.d.ts +14 -0
- package/dist/providers/claude-code/transport.d.ts.map +1 -0
- package/dist/providers/claude-code/transport.js +18 -0
- package/dist/providers/claude-code/transport.js.map +1 -0
- package/dist/providers/cursor/adapter.d.ts +62 -0
- package/dist/providers/cursor/adapter.d.ts.map +1 -0
- package/dist/providers/cursor/adapter.js +124 -0
- package/dist/providers/cursor/adapter.js.map +1 -0
- package/dist/providers/cursor/hooks.d.ts +48 -0
- package/dist/providers/cursor/hooks.d.ts.map +1 -0
- package/dist/providers/cursor/hooks.js +55 -0
- package/dist/providers/cursor/hooks.js.map +1 -0
- package/dist/providers/cursor/index.d.ts +19 -0
- package/dist/providers/cursor/index.d.ts.map +1 -0
- package/dist/providers/cursor/index.js +21 -0
- package/dist/providers/cursor/index.js.map +1 -0
- package/dist/providers/cursor/install.d.ts +94 -0
- package/dist/providers/cursor/install.d.ts.map +1 -0
- package/dist/providers/cursor/install.js +241 -0
- package/dist/providers/cursor/install.js.map +1 -0
- package/dist/providers/cursor/spawn.d.ts +50 -0
- package/dist/providers/cursor/spawn.d.ts.map +1 -0
- package/dist/providers/cursor/spawn.js +59 -0
- package/dist/providers/cursor/spawn.js.map +1 -0
- package/dist/providers/opencode/adapter.d.ts +67 -0
- package/dist/providers/opencode/adapter.d.ts.map +1 -0
- package/dist/providers/opencode/adapter.js +144 -0
- package/dist/providers/opencode/adapter.js.map +1 -0
- package/dist/providers/opencode/hooks.d.ts +66 -0
- package/dist/providers/opencode/hooks.d.ts.map +1 -0
- package/dist/providers/opencode/hooks.js +89 -0
- package/dist/providers/opencode/hooks.js.map +1 -0
- package/dist/providers/opencode/index.d.ts +20 -0
- package/dist/providers/opencode/index.d.ts.map +1 -0
- package/dist/providers/opencode/index.js +22 -0
- package/dist/providers/opencode/index.js.map +1 -0
- package/dist/providers/opencode/install.d.ts +65 -0
- package/dist/providers/opencode/install.d.ts.map +1 -0
- package/dist/providers/opencode/install.js +183 -0
- package/dist/providers/opencode/install.js.map +1 -0
- package/dist/providers/opencode/spawn.d.ts +72 -0
- package/dist/providers/opencode/spawn.d.ts.map +1 -0
- package/dist/providers/opencode/spawn.js +219 -0
- package/dist/providers/opencode/spawn.js.map +1 -0
- package/dist/registry.d.ts +36 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +55 -0
- package/dist/registry.js.map +1 -0
- package/package.json +32 -0
- package/src/index.d.ts +27 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +28 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +37 -0
- package/src/providers/claude-code/__tests__/adapter.test.d.ts +7 -0
- package/src/providers/claude-code/__tests__/adapter.test.d.ts.map +1 -0
- package/src/providers/claude-code/__tests__/adapter.test.js +249 -0
- package/src/providers/claude-code/__tests__/adapter.test.js.map +1 -0
- package/src/providers/claude-code/__tests__/adapter.test.ts +291 -0
- package/src/providers/claude-code/adapter.d.ts +75 -0
- package/src/providers/claude-code/adapter.d.ts.map +1 -0
- package/src/providers/claude-code/adapter.js +154 -0
- package/src/providers/claude-code/adapter.js.map +1 -0
- package/src/providers/claude-code/adapter.ts +175 -0
- package/src/providers/claude-code/context-monitor.d.ts +24 -0
- package/src/providers/claude-code/context-monitor.d.ts.map +1 -0
- package/src/providers/claude-code/context-monitor.js +148 -0
- package/src/providers/claude-code/context-monitor.js.map +1 -0
- package/src/providers/claude-code/context-monitor.ts +175 -0
- package/src/providers/claude-code/hooks.d.ts +59 -0
- package/src/providers/claude-code/hooks.d.ts.map +1 -0
- package/src/providers/claude-code/hooks.js +77 -0
- package/src/providers/claude-code/hooks.js.map +1 -0
- package/src/providers/claude-code/hooks.ts +85 -0
- package/src/providers/claude-code/index.d.ts +24 -0
- package/src/providers/claude-code/index.d.ts.map +1 -0
- package/src/providers/claude-code/index.js +26 -0
- package/src/providers/claude-code/index.js.map +1 -0
- package/src/providers/claude-code/index.ts +33 -0
- package/src/providers/claude-code/install.d.ts +75 -0
- package/src/providers/claude-code/install.d.ts.map +1 -0
- package/src/providers/claude-code/install.js +237 -0
- package/src/providers/claude-code/install.js.map +1 -0
- package/src/providers/claude-code/install.ts +267 -0
- package/src/providers/claude-code/manifest.json +26 -0
- package/src/providers/claude-code/paths.d.ts +24 -0
- package/src/providers/claude-code/paths.d.ts.map +1 -0
- package/src/providers/claude-code/paths.js +33 -0
- package/src/providers/claude-code/paths.js.map +1 -0
- package/src/providers/claude-code/paths.ts +38 -0
- package/src/providers/claude-code/spawn.d.ts +60 -0
- package/src/providers/claude-code/spawn.d.ts.map +1 -0
- package/src/providers/claude-code/spawn.js +160 -0
- package/src/providers/claude-code/spawn.js.map +1 -0
- package/src/providers/claude-code/spawn.ts +178 -0
- package/src/providers/claude-code/statusline.d.ts +24 -0
- package/src/providers/claude-code/statusline.d.ts.map +1 -0
- package/src/providers/claude-code/statusline.js +85 -0
- package/src/providers/claude-code/statusline.js.map +1 -0
- package/src/providers/claude-code/statusline.ts +99 -0
- package/src/providers/claude-code/task-sync.d.ts +27 -0
- package/src/providers/claude-code/task-sync.d.ts.map +1 -0
- package/src/providers/claude-code/task-sync.js +124 -0
- package/src/providers/claude-code/task-sync.js.map +1 -0
- package/src/providers/claude-code/task-sync.ts +158 -0
- package/src/providers/claude-code/transport.d.ts +14 -0
- package/src/providers/claude-code/transport.d.ts.map +1 -0
- package/src/providers/claude-code/transport.js +18 -0
- package/src/providers/claude-code/transport.js.map +1 -0
- package/src/providers/claude-code/transport.ts +21 -0
- package/src/providers/cursor/__tests__/adapter.test.d.ts +7 -0
- package/src/providers/cursor/__tests__/adapter.test.d.ts.map +1 -0
- package/src/providers/cursor/__tests__/adapter.test.js +246 -0
- package/src/providers/cursor/__tests__/adapter.test.js.map +1 -0
- package/src/providers/cursor/__tests__/adapter.test.ts +291 -0
- package/src/providers/cursor/adapter.d.ts +62 -0
- package/src/providers/cursor/adapter.d.ts.map +1 -0
- package/src/providers/cursor/adapter.js +124 -0
- package/src/providers/cursor/adapter.js.map +1 -0
- package/src/providers/cursor/adapter.ts +145 -0
- package/src/providers/cursor/hooks.d.ts +48 -0
- package/src/providers/cursor/hooks.d.ts.map +1 -0
- package/src/providers/cursor/hooks.js +55 -0
- package/src/providers/cursor/hooks.js.map +1 -0
- package/src/providers/cursor/hooks.ts +61 -0
- package/src/providers/cursor/index.d.ts +19 -0
- package/src/providers/cursor/index.d.ts.map +1 -0
- package/src/providers/cursor/index.js +21 -0
- package/src/providers/cursor/index.js.map +1 -0
- package/src/providers/cursor/index.ts +24 -0
- package/src/providers/cursor/install.d.ts +94 -0
- package/src/providers/cursor/install.d.ts.map +1 -0
- package/src/providers/cursor/install.js +241 -0
- package/src/providers/cursor/install.js.map +1 -0
- package/src/providers/cursor/install.ts +271 -0
- package/src/providers/cursor/manifest.json +26 -0
- package/src/providers/cursor/spawn.d.ts +50 -0
- package/src/providers/cursor/spawn.d.ts.map +1 -0
- package/src/providers/cursor/spawn.js +59 -0
- package/src/providers/cursor/spawn.js.map +1 -0
- package/src/providers/cursor/spawn.ts +66 -0
- package/src/providers/opencode/__tests__/adapter.test.d.ts +7 -0
- package/src/providers/opencode/__tests__/adapter.test.d.ts.map +1 -0
- package/src/providers/opencode/__tests__/adapter.test.js +263 -0
- package/src/providers/opencode/__tests__/adapter.test.js.map +1 -0
- package/src/providers/opencode/__tests__/adapter.test.ts +309 -0
- package/src/providers/opencode/adapter.d.ts +67 -0
- package/src/providers/opencode/adapter.d.ts.map +1 -0
- package/src/providers/opencode/adapter.js +144 -0
- package/src/providers/opencode/adapter.js.map +1 -0
- package/src/providers/opencode/adapter.ts +165 -0
- package/src/providers/opencode/hooks.d.ts +66 -0
- package/src/providers/opencode/hooks.d.ts.map +1 -0
- package/src/providers/opencode/hooks.js +89 -0
- package/src/providers/opencode/hooks.js.map +1 -0
- package/src/providers/opencode/hooks.ts +97 -0
- package/src/providers/opencode/index.d.ts +20 -0
- package/src/providers/opencode/index.d.ts.map +1 -0
- package/src/providers/opencode/index.js +22 -0
- package/src/providers/opencode/index.js.map +1 -0
- package/src/providers/opencode/index.ts +25 -0
- package/src/providers/opencode/install.d.ts +65 -0
- package/src/providers/opencode/install.d.ts.map +1 -0
- package/src/providers/opencode/install.js +183 -0
- package/src/providers/opencode/install.js.map +1 -0
- package/src/providers/opencode/install.ts +206 -0
- package/src/providers/opencode/manifest.json +26 -0
- package/src/providers/opencode/spawn.d.ts +72 -0
- package/src/providers/opencode/spawn.d.ts.map +1 -0
- package/src/providers/opencode/spawn.js +219 -0
- package/src/providers/opencode/spawn.js.map +1 -0
- package/src/providers/opencode/spawn.ts +253 -0
- package/src/registry.d.ts +36 -0
- package/src/registry.d.ts.map +1 -0
- package/src/registry.js +55 -0
- package/src/registry.js.map +1 -0
- package/src/registry.ts +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1927 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// packages/adapters/src/providers/claude-code/paths.js
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
var ClaudeCodePathProvider;
|
|
15
|
+
var init_paths = __esm({
|
|
16
|
+
"packages/adapters/src/providers/claude-code/paths.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
ClaudeCodePathProvider = class {
|
|
19
|
+
getProviderDir() {
|
|
20
|
+
return process.env["CLAUDE_HOME"] ?? join(homedir(), ".claude");
|
|
21
|
+
}
|
|
22
|
+
getSettingsPath() {
|
|
23
|
+
return process.env["CLAUDE_SETTINGS"] ?? join(this.getProviderDir(), "settings.json");
|
|
24
|
+
}
|
|
25
|
+
getAgentInstallDir() {
|
|
26
|
+
return join(this.getProviderDir(), "agents");
|
|
27
|
+
}
|
|
28
|
+
getMemoryDbPath() {
|
|
29
|
+
return process.env["CLAUDE_MEM_DB"] ?? join(homedir(), ".claude-mem", "claude-mem.db");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// packages/adapters/src/providers/claude-code/context-monitor.js
|
|
36
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
37
|
+
import { mkdir } from "node:fs/promises";
|
|
38
|
+
import { homedir as homedir2 } from "node:os";
|
|
39
|
+
import { dirname, join as join2 } from "node:path";
|
|
40
|
+
function getContextStatusFromPercentage(percentage) {
|
|
41
|
+
if (percentage >= THRESHOLDS.EMERGENCY)
|
|
42
|
+
return "emergency";
|
|
43
|
+
if (percentage >= THRESHOLDS.CRITICAL)
|
|
44
|
+
return "critical";
|
|
45
|
+
if (percentage >= THRESHOLDS.CAUTION)
|
|
46
|
+
return "caution";
|
|
47
|
+
if (percentage >= THRESHOLDS.WARNING)
|
|
48
|
+
return "warning";
|
|
49
|
+
return "ok";
|
|
50
|
+
}
|
|
51
|
+
var THRESHOLDS, ClaudeCodeContextMonitorProvider;
|
|
52
|
+
var init_context_monitor = __esm({
|
|
53
|
+
"packages/adapters/src/providers/claude-code/context-monitor.js"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
init_paths();
|
|
56
|
+
THRESHOLDS = {
|
|
57
|
+
WARNING: 50,
|
|
58
|
+
CAUTION: 70,
|
|
59
|
+
CRITICAL: 85,
|
|
60
|
+
EMERGENCY: 95
|
|
61
|
+
};
|
|
62
|
+
ClaudeCodeContextMonitorProvider = class {
|
|
63
|
+
pathProvider = new ClaudeCodePathProvider();
|
|
64
|
+
async processContextInput(input, cwd) {
|
|
65
|
+
const typed = input;
|
|
66
|
+
const contextSize = typed.context_window?.context_window_size ?? 2e5;
|
|
67
|
+
const usage = typed.context_window?.current_usage;
|
|
68
|
+
if (!usage)
|
|
69
|
+
return "-- no data";
|
|
70
|
+
const inputTokens = usage.input_tokens ?? 0;
|
|
71
|
+
const outputTokens = usage.output_tokens ?? 0;
|
|
72
|
+
const cacheCreate = usage.cache_creation_input_tokens ?? 0;
|
|
73
|
+
const totalTokens = inputTokens + outputTokens + cacheCreate;
|
|
74
|
+
const percentage = Math.floor(totalTokens * 100 / contextSize);
|
|
75
|
+
const status = getContextStatusFromPercentage(percentage);
|
|
76
|
+
const cleoDir = cwd ? join2(cwd, ".cleo") : ".cleo";
|
|
77
|
+
if (existsSync(cleoDir)) {
|
|
78
|
+
const stateDir = join2(cleoDir, "context-states");
|
|
79
|
+
const statePath = join2(stateDir, ".context-state.json");
|
|
80
|
+
const state = {
|
|
81
|
+
$schema: "https://cleo-dev.com/schemas/v1/context-state.schema.json",
|
|
82
|
+
version: "1.0.0",
|
|
83
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
84
|
+
staleAfterMs: 5e3,
|
|
85
|
+
contextWindow: {
|
|
86
|
+
maxTokens: contextSize,
|
|
87
|
+
currentTokens: totalTokens,
|
|
88
|
+
percentage,
|
|
89
|
+
breakdown: {
|
|
90
|
+
inputTokens,
|
|
91
|
+
outputTokens,
|
|
92
|
+
cacheCreationTokens: cacheCreate,
|
|
93
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
thresholds: {
|
|
97
|
+
warning: THRESHOLDS.WARNING,
|
|
98
|
+
caution: THRESHOLDS.CAUTION,
|
|
99
|
+
critical: THRESHOLDS.CRITICAL,
|
|
100
|
+
emergency: THRESHOLDS.EMERGENCY
|
|
101
|
+
},
|
|
102
|
+
status,
|
|
103
|
+
cleoSessionId: ""
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
107
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return `${percentage}% | ${totalTokens}/${contextSize}`;
|
|
112
|
+
}
|
|
113
|
+
checkStatuslineIntegration() {
|
|
114
|
+
const settingsPath = this.pathProvider.getSettingsPath();
|
|
115
|
+
if (!settingsPath || !existsSync(settingsPath))
|
|
116
|
+
return "no_settings";
|
|
117
|
+
try {
|
|
118
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
119
|
+
const statusLine = settings.statusLine;
|
|
120
|
+
if (!statusLine?.type)
|
|
121
|
+
return "not_configured";
|
|
122
|
+
if (statusLine.type !== "command")
|
|
123
|
+
return "custom_no_cleo";
|
|
124
|
+
const cmd = statusLine.command ?? "";
|
|
125
|
+
if (cmd.includes("context-monitor.sh") || cmd.includes("cleo-statusline") || cmd.includes(".context-state.json") || cmd.includes("context-states")) {
|
|
126
|
+
return "configured";
|
|
127
|
+
}
|
|
128
|
+
const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir2()) : cmd;
|
|
129
|
+
if (existsSync(scriptPath)) {
|
|
130
|
+
try {
|
|
131
|
+
const content = readFileSync(scriptPath, "utf-8");
|
|
132
|
+
if (content.includes("context-state.json"))
|
|
133
|
+
return "configured";
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return "custom_no_cleo";
|
|
138
|
+
} catch {
|
|
139
|
+
return "no_settings";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
getStatuslineConfig() {
|
|
143
|
+
return {
|
|
144
|
+
statusLine: {
|
|
145
|
+
type: "command",
|
|
146
|
+
command: join2(homedir2(), ".cleo", "lib", "session", "context-monitor.sh")
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
getSetupInstructions() {
|
|
151
|
+
const settingsPath = this.pathProvider.getSettingsPath() ?? "~/.claude/settings.json";
|
|
152
|
+
return [
|
|
153
|
+
"To enable context monitoring, add to your Claude Code settings:",
|
|
154
|
+
`File: ${settingsPath}`,
|
|
155
|
+
"",
|
|
156
|
+
JSON.stringify(this.getStatuslineConfig(), null, 2),
|
|
157
|
+
"",
|
|
158
|
+
"This enables real-time context window tracking in the CLI."
|
|
159
|
+
].join("\n");
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// packages/adapters/src/providers/claude-code/hooks.js
|
|
166
|
+
var CLAUDE_CODE_EVENT_MAP, ClaudeCodeHookProvider;
|
|
167
|
+
var init_hooks = __esm({
|
|
168
|
+
"packages/adapters/src/providers/claude-code/hooks.js"() {
|
|
169
|
+
"use strict";
|
|
170
|
+
CLAUDE_CODE_EVENT_MAP = {
|
|
171
|
+
SessionStart: "onSessionStart",
|
|
172
|
+
PostToolUse: "onToolComplete",
|
|
173
|
+
UserPromptSubmit: "onPromptSubmit",
|
|
174
|
+
Stop: "onSessionEnd"
|
|
175
|
+
};
|
|
176
|
+
ClaudeCodeHookProvider = class {
|
|
177
|
+
registered = false;
|
|
178
|
+
/**
|
|
179
|
+
* Map a Claude Code native event name to a CAAMP hook event name.
|
|
180
|
+
*
|
|
181
|
+
* @param providerEvent - Claude Code event name (e.g. "SessionStart", "PostToolUse")
|
|
182
|
+
* @returns CAAMP event name or null if unmapped
|
|
183
|
+
*/
|
|
184
|
+
mapProviderEvent(providerEvent) {
|
|
185
|
+
return CLAUDE_CODE_EVENT_MAP[providerEvent] ?? null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Register native hooks for a project.
|
|
189
|
+
*
|
|
190
|
+
* For Claude Code, hooks are registered via the plugin system
|
|
191
|
+
* (hooks.json descriptor), which is handled by the
|
|
192
|
+
* install provider. This method is a no-op since registration
|
|
193
|
+
* is managed through the plugin install lifecycle.
|
|
194
|
+
*
|
|
195
|
+
* @param _projectDir - Project directory (unused; hooks are global)
|
|
196
|
+
*/
|
|
197
|
+
async registerNativeHooks(_projectDir) {
|
|
198
|
+
this.registered = true;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Unregister native hooks.
|
|
202
|
+
*
|
|
203
|
+
* For Claude Code, this is a no-op since hooks are managed through
|
|
204
|
+
* the plugin system. Unregistration happens via the install provider's
|
|
205
|
+
* uninstall method.
|
|
206
|
+
*/
|
|
207
|
+
async unregisterNativeHooks() {
|
|
208
|
+
this.registered = false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check whether hooks have been registered via registerNativeHooks.
|
|
212
|
+
*/
|
|
213
|
+
isRegistered() {
|
|
214
|
+
return this.registered;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get the full event mapping for introspection/debugging.
|
|
218
|
+
*/
|
|
219
|
+
getEventMap() {
|
|
220
|
+
return { ...CLAUDE_CODE_EVENT_MAP };
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// packages/adapters/src/providers/claude-code/install.js
|
|
227
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
228
|
+
import { homedir as homedir3 } from "node:os";
|
|
229
|
+
import { join as join3 } from "node:path";
|
|
230
|
+
var INSTRUCTION_REFERENCES, MCP_SERVER_KEY, ClaudeCodeInstallProvider;
|
|
231
|
+
var init_install = __esm({
|
|
232
|
+
"packages/adapters/src/providers/claude-code/install.js"() {
|
|
233
|
+
"use strict";
|
|
234
|
+
INSTRUCTION_REFERENCES = [
|
|
235
|
+
"@~/.cleo/templates/CLEO-INJECTION.md",
|
|
236
|
+
"@.cleo/memory-bridge.md"
|
|
237
|
+
];
|
|
238
|
+
MCP_SERVER_KEY = "cleo";
|
|
239
|
+
ClaudeCodeInstallProvider = class {
|
|
240
|
+
installedProjectDir = null;
|
|
241
|
+
/**
|
|
242
|
+
* Install CLEO into a Claude Code project.
|
|
243
|
+
*
|
|
244
|
+
* @param options - Installation options including project directory and MCP server path
|
|
245
|
+
* @returns Result describing what was installed
|
|
246
|
+
*/
|
|
247
|
+
async install(options) {
|
|
248
|
+
const { projectDir, mcpServerPath } = options;
|
|
249
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
250
|
+
let instructionFileUpdated = false;
|
|
251
|
+
let mcpRegistered = false;
|
|
252
|
+
const details = {};
|
|
253
|
+
if (mcpServerPath) {
|
|
254
|
+
mcpRegistered = this.registerMcpServer(projectDir, mcpServerPath);
|
|
255
|
+
if (mcpRegistered) {
|
|
256
|
+
details.mcpConfigPath = join3(projectDir, ".mcp.json");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
instructionFileUpdated = this.updateInstructionFile(projectDir);
|
|
260
|
+
if (instructionFileUpdated) {
|
|
261
|
+
details.instructionFile = join3(projectDir, "CLAUDE.md");
|
|
262
|
+
}
|
|
263
|
+
const pluginResult = this.registerPlugin();
|
|
264
|
+
if (pluginResult) {
|
|
265
|
+
details.plugin = pluginResult;
|
|
266
|
+
}
|
|
267
|
+
this.installedProjectDir = projectDir;
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
installedAt,
|
|
271
|
+
instructionFileUpdated,
|
|
272
|
+
mcpRegistered,
|
|
273
|
+
details
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Uninstall CLEO from the current Claude Code project.
|
|
278
|
+
*
|
|
279
|
+
* Removes the MCP server registration from .mcp.json.
|
|
280
|
+
* Does not remove CLAUDE.md references (they are harmless if CLEO is not present).
|
|
281
|
+
*/
|
|
282
|
+
async uninstall() {
|
|
283
|
+
if (!this.installedProjectDir)
|
|
284
|
+
return;
|
|
285
|
+
const mcpPath = join3(this.installedProjectDir, ".mcp.json");
|
|
286
|
+
if (existsSync2(mcpPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const raw = readFileSync2(mcpPath, "utf-8");
|
|
289
|
+
const config = JSON.parse(raw);
|
|
290
|
+
const mcpServers = config.mcpServers;
|
|
291
|
+
if (mcpServers && MCP_SERVER_KEY in mcpServers) {
|
|
292
|
+
delete mcpServers[MCP_SERVER_KEY];
|
|
293
|
+
writeFileSync2(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this.installedProjectDir = null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Check whether CLEO is installed in the current environment.
|
|
302
|
+
*
|
|
303
|
+
* Checks for:
|
|
304
|
+
* 1. MCP server registered in .mcp.json
|
|
305
|
+
* 2. Plugin enabled in ~/.claude/settings.json
|
|
306
|
+
*
|
|
307
|
+
* Returns true if either condition is met (partial install counts).
|
|
308
|
+
*/
|
|
309
|
+
async isInstalled() {
|
|
310
|
+
const settingsPath = join3(homedir3(), ".claude", "settings.json");
|
|
311
|
+
if (existsSync2(settingsPath)) {
|
|
312
|
+
try {
|
|
313
|
+
const settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
314
|
+
const plugins = settings.enabledPlugins;
|
|
315
|
+
if (plugins && plugins["cleo@cleocode"] === true) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const mcpPath = join3(process.cwd(), ".mcp.json");
|
|
322
|
+
if (existsSync2(mcpPath)) {
|
|
323
|
+
try {
|
|
324
|
+
const config = JSON.parse(readFileSync2(mcpPath, "utf-8"));
|
|
325
|
+
const mcpServers = config.mcpServers;
|
|
326
|
+
if (mcpServers && MCP_SERVER_KEY in mcpServers) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Ensure CLAUDE.md contains @-references to CLEO instruction files.
|
|
336
|
+
*
|
|
337
|
+
* Creates CLAUDE.md if it does not exist. Appends any missing references.
|
|
338
|
+
*
|
|
339
|
+
* @param projectDir - Project root directory
|
|
340
|
+
*/
|
|
341
|
+
async ensureInstructionReferences(projectDir) {
|
|
342
|
+
this.updateInstructionFile(projectDir);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Register the CLEO MCP server in .mcp.json.
|
|
346
|
+
*
|
|
347
|
+
* @returns true if registration was performed or updated
|
|
348
|
+
*/
|
|
349
|
+
registerMcpServer(projectDir, mcpServerPath) {
|
|
350
|
+
const mcpPath = join3(projectDir, ".mcp.json");
|
|
351
|
+
let config = {};
|
|
352
|
+
if (existsSync2(mcpPath)) {
|
|
353
|
+
try {
|
|
354
|
+
config = JSON.parse(readFileSync2(mcpPath, "utf-8"));
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
359
|
+
config.mcpServers = {};
|
|
360
|
+
}
|
|
361
|
+
const mcpServers = config.mcpServers;
|
|
362
|
+
mcpServers[MCP_SERVER_KEY] = {
|
|
363
|
+
command: "node",
|
|
364
|
+
args: [mcpServerPath]
|
|
365
|
+
};
|
|
366
|
+
writeFileSync2(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Update CLAUDE.md with CLEO @-references.
|
|
371
|
+
*
|
|
372
|
+
* @returns true if the file was created or modified
|
|
373
|
+
*/
|
|
374
|
+
updateInstructionFile(projectDir) {
|
|
375
|
+
const claudeMdPath = join3(projectDir, "CLAUDE.md");
|
|
376
|
+
let content = "";
|
|
377
|
+
let existed = false;
|
|
378
|
+
if (existsSync2(claudeMdPath)) {
|
|
379
|
+
content = readFileSync2(claudeMdPath, "utf-8");
|
|
380
|
+
existed = true;
|
|
381
|
+
}
|
|
382
|
+
const missingRefs = INSTRUCTION_REFERENCES.filter((ref) => !content.includes(ref));
|
|
383
|
+
if (missingRefs.length === 0) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const refsBlock = missingRefs.join("\n");
|
|
387
|
+
if (existed) {
|
|
388
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
389
|
+
content = content + separator + refsBlock + "\n";
|
|
390
|
+
} else {
|
|
391
|
+
content = refsBlock + "\n";
|
|
392
|
+
}
|
|
393
|
+
writeFileSync2(claudeMdPath, content, "utf-8");
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Register the CLEO brain plugin in ~/.claude/settings.json.
|
|
398
|
+
*
|
|
399
|
+
* @returns Description of what was registered, or null if no change needed
|
|
400
|
+
*/
|
|
401
|
+
registerPlugin() {
|
|
402
|
+
const home = homedir3();
|
|
403
|
+
const settingsPath = join3(home, ".claude", "settings.json");
|
|
404
|
+
let settings = {};
|
|
405
|
+
if (existsSync2(settingsPath)) {
|
|
406
|
+
try {
|
|
407
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const enabledPlugins = settings.enabledPlugins ?? {};
|
|
412
|
+
const pluginKey = "cleo@cleocode";
|
|
413
|
+
if (enabledPlugins[pluginKey] === true) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
if (enabledPlugins["claude-mem@thedotmack"] === true) {
|
|
417
|
+
enabledPlugins["claude-mem@thedotmack"] = false;
|
|
418
|
+
}
|
|
419
|
+
enabledPlugins[pluginKey] = true;
|
|
420
|
+
settings.enabledPlugins = enabledPlugins;
|
|
421
|
+
mkdirSync(join3(home, ".claude"), { recursive: true });
|
|
422
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
423
|
+
return `Enabled ${pluginKey} in ~/.claude/settings.json`;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// packages/adapters/src/providers/claude-code/spawn.js
|
|
430
|
+
import { exec, spawn as nodeSpawn } from "node:child_process";
|
|
431
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
432
|
+
import { promisify } from "node:util";
|
|
433
|
+
var execAsync, ClaudeCodeSpawnProvider;
|
|
434
|
+
var init_spawn = __esm({
|
|
435
|
+
"packages/adapters/src/providers/claude-code/spawn.js"() {
|
|
436
|
+
"use strict";
|
|
437
|
+
execAsync = promisify(exec);
|
|
438
|
+
ClaudeCodeSpawnProvider = class {
|
|
439
|
+
/** Map of instance IDs to tracked process info. */
|
|
440
|
+
processMap = /* @__PURE__ */ new Map();
|
|
441
|
+
/**
|
|
442
|
+
* Check if the Claude CLI is available in PATH.
|
|
443
|
+
*
|
|
444
|
+
* @returns true if `claude` is found via `which`
|
|
445
|
+
*/
|
|
446
|
+
async canSpawn() {
|
|
447
|
+
try {
|
|
448
|
+
await execAsync("which claude");
|
|
449
|
+
return true;
|
|
450
|
+
} catch {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Spawn a subagent via Claude CLI.
|
|
456
|
+
*
|
|
457
|
+
* Writes the prompt to a temporary file and spawns a detached Claude
|
|
458
|
+
* process. The process runs independently of the parent.
|
|
459
|
+
*
|
|
460
|
+
* @param context - Spawn context with taskId, prompt, and options
|
|
461
|
+
* @returns Spawn result with instance ID and status
|
|
462
|
+
*/
|
|
463
|
+
async spawn(context) {
|
|
464
|
+
const instanceId = `claude-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
465
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
466
|
+
let tmpFile;
|
|
467
|
+
try {
|
|
468
|
+
tmpFile = `/tmp/claude-spawn-${instanceId}.txt`;
|
|
469
|
+
await writeFile(tmpFile, context.prompt, "utf-8");
|
|
470
|
+
const args = ["--allow-insecure", "--no-upgrade-check", tmpFile];
|
|
471
|
+
const spawnOpts = {
|
|
472
|
+
detached: true,
|
|
473
|
+
stdio: "ignore"
|
|
474
|
+
};
|
|
475
|
+
if (context.workingDirectory) {
|
|
476
|
+
spawnOpts.cwd = context.workingDirectory;
|
|
477
|
+
}
|
|
478
|
+
const child = nodeSpawn("claude", args, spawnOpts);
|
|
479
|
+
child.unref();
|
|
480
|
+
if (child.pid) {
|
|
481
|
+
this.processMap.set(instanceId, {
|
|
482
|
+
pid: child.pid,
|
|
483
|
+
taskId: context.taskId,
|
|
484
|
+
startTime
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const capturedTmpFile = tmpFile;
|
|
488
|
+
child.on("exit", async () => {
|
|
489
|
+
this.processMap.delete(instanceId);
|
|
490
|
+
try {
|
|
491
|
+
await unlink(capturedTmpFile);
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
return {
|
|
496
|
+
instanceId,
|
|
497
|
+
taskId: context.taskId,
|
|
498
|
+
providerId: "claude-code",
|
|
499
|
+
status: "running",
|
|
500
|
+
startTime
|
|
501
|
+
};
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (tmpFile) {
|
|
504
|
+
try {
|
|
505
|
+
await unlink(tmpFile);
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
instanceId,
|
|
511
|
+
taskId: context.taskId,
|
|
512
|
+
providerId: "claude-code",
|
|
513
|
+
status: "failed",
|
|
514
|
+
startTime,
|
|
515
|
+
endTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* List currently running Claude subagent processes.
|
|
521
|
+
*
|
|
522
|
+
* Checks each tracked process via kill(pid, 0) to verify it is still alive.
|
|
523
|
+
* Dead processes are automatically cleaned from the tracking map.
|
|
524
|
+
*
|
|
525
|
+
* @returns Array of spawn results for running processes
|
|
526
|
+
*/
|
|
527
|
+
async listRunning() {
|
|
528
|
+
const running = [];
|
|
529
|
+
for (const [instanceId, tracked] of this.processMap.entries()) {
|
|
530
|
+
try {
|
|
531
|
+
process.kill(tracked.pid, 0);
|
|
532
|
+
running.push({
|
|
533
|
+
instanceId,
|
|
534
|
+
taskId: tracked.taskId,
|
|
535
|
+
providerId: "claude-code",
|
|
536
|
+
status: "running",
|
|
537
|
+
startTime: tracked.startTime
|
|
538
|
+
});
|
|
539
|
+
} catch {
|
|
540
|
+
this.processMap.delete(instanceId);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return running;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Terminate a running spawn by instance ID.
|
|
547
|
+
*
|
|
548
|
+
* Sends SIGTERM to the tracked process. If the process is not found
|
|
549
|
+
* or has already exited, this is a no-op.
|
|
550
|
+
*
|
|
551
|
+
* @param instanceId - ID of the spawn instance to terminate
|
|
552
|
+
*/
|
|
553
|
+
async terminate(instanceId) {
|
|
554
|
+
const tracked = this.processMap.get(instanceId);
|
|
555
|
+
if (!tracked)
|
|
556
|
+
return;
|
|
557
|
+
try {
|
|
558
|
+
process.kill(tracked.pid, "SIGTERM");
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
this.processMap.delete(instanceId);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// packages/adapters/src/providers/claude-code/task-sync.js
|
|
568
|
+
import { readFile, rm, stat } from "node:fs/promises";
|
|
569
|
+
import { join as join4 } from "node:path";
|
|
570
|
+
function parseTaskId(content) {
|
|
571
|
+
const match = content.match(/^\[T(\d+)\]/);
|
|
572
|
+
return match ? `T${match[1]}` : null;
|
|
573
|
+
}
|
|
574
|
+
function stripPrefixes(content) {
|
|
575
|
+
return content.replace(/^\[T\d+\]\s*/, "").replace(/^\[!\]\s*/, "").replace(/^\[BLOCKED\]\s*/, "");
|
|
576
|
+
}
|
|
577
|
+
function mapStatus(twStatus) {
|
|
578
|
+
switch (twStatus) {
|
|
579
|
+
case "completed":
|
|
580
|
+
return "completed";
|
|
581
|
+
case "in_progress":
|
|
582
|
+
return "active";
|
|
583
|
+
case "pending":
|
|
584
|
+
return "pending";
|
|
585
|
+
default:
|
|
586
|
+
return "pending";
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getTodoWriteFilePath(projectDir) {
|
|
590
|
+
return join4(projectDir, ".cleo", "sync", "todowrite-state.json");
|
|
591
|
+
}
|
|
592
|
+
var ClaudeCodeTaskSyncProvider;
|
|
593
|
+
var init_task_sync = __esm({
|
|
594
|
+
"packages/adapters/src/providers/claude-code/task-sync.js"() {
|
|
595
|
+
"use strict";
|
|
596
|
+
ClaudeCodeTaskSyncProvider = class {
|
|
597
|
+
customFilePath;
|
|
598
|
+
constructor(options) {
|
|
599
|
+
this.customFilePath = options?.filePath;
|
|
600
|
+
}
|
|
601
|
+
async getExternalTasks(projectDir) {
|
|
602
|
+
const filePath = this.customFilePath ?? getTodoWriteFilePath(projectDir);
|
|
603
|
+
try {
|
|
604
|
+
await stat(filePath);
|
|
605
|
+
} catch {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
const raw = await readFile(filePath, "utf-8");
|
|
609
|
+
let state;
|
|
610
|
+
try {
|
|
611
|
+
state = JSON.parse(raw);
|
|
612
|
+
} catch {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
if (!state.todos || !Array.isArray(state.todos)) {
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
const tasks = [];
|
|
619
|
+
let syntheticIndex = 0;
|
|
620
|
+
for (const item of state.todos) {
|
|
621
|
+
const cleoTaskId = parseTaskId(item.content);
|
|
622
|
+
const title = cleoTaskId ? stripPrefixes(item.content).trim() : item.content.trim();
|
|
623
|
+
if (!title)
|
|
624
|
+
continue;
|
|
625
|
+
tasks.push({
|
|
626
|
+
externalId: cleoTaskId ?? `tw-new-${syntheticIndex++}`,
|
|
627
|
+
cleoTaskId,
|
|
628
|
+
title,
|
|
629
|
+
status: mapStatus(item.status),
|
|
630
|
+
providerMeta: {
|
|
631
|
+
source: "todowrite",
|
|
632
|
+
activeForm: item.activeForm,
|
|
633
|
+
rawContent: item.content
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
return tasks;
|
|
638
|
+
}
|
|
639
|
+
async cleanup(projectDir) {
|
|
640
|
+
const filePath = this.customFilePath ?? getTodoWriteFilePath(projectDir);
|
|
641
|
+
try {
|
|
642
|
+
await rm(filePath);
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// packages/adapters/src/providers/claude-code/transport.js
|
|
651
|
+
var ClaudeCodeTransportProvider;
|
|
652
|
+
var init_transport = __esm({
|
|
653
|
+
"packages/adapters/src/providers/claude-code/transport.js"() {
|
|
654
|
+
"use strict";
|
|
655
|
+
ClaudeCodeTransportProvider = class {
|
|
656
|
+
transportName = "claude-code";
|
|
657
|
+
createTransport() {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// packages/adapters/src/providers/claude-code/adapter.js
|
|
665
|
+
import { exec as exec2 } from "node:child_process";
|
|
666
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
667
|
+
import { join as join5 } from "node:path";
|
|
668
|
+
import { homedir as homedir4 } from "node:os";
|
|
669
|
+
import { promisify as promisify2 } from "node:util";
|
|
670
|
+
var execAsync2, ClaudeCodeAdapter;
|
|
671
|
+
var init_adapter = __esm({
|
|
672
|
+
"packages/adapters/src/providers/claude-code/adapter.js"() {
|
|
673
|
+
"use strict";
|
|
674
|
+
init_context_monitor();
|
|
675
|
+
init_hooks();
|
|
676
|
+
init_install();
|
|
677
|
+
init_paths();
|
|
678
|
+
init_spawn();
|
|
679
|
+
init_task_sync();
|
|
680
|
+
init_transport();
|
|
681
|
+
execAsync2 = promisify2(exec2);
|
|
682
|
+
ClaudeCodeAdapter = class {
|
|
683
|
+
id = "claude-code";
|
|
684
|
+
name = "Claude Code";
|
|
685
|
+
version = "1.0.0";
|
|
686
|
+
capabilities = {
|
|
687
|
+
supportsHooks: true,
|
|
688
|
+
supportedHookEvents: [
|
|
689
|
+
"onSessionStart",
|
|
690
|
+
"onSessionEnd",
|
|
691
|
+
"onToolStart",
|
|
692
|
+
"onToolComplete",
|
|
693
|
+
"onError"
|
|
694
|
+
],
|
|
695
|
+
supportsSpawn: true,
|
|
696
|
+
supportsInstall: true,
|
|
697
|
+
supportsMcp: true,
|
|
698
|
+
supportsInstructionFiles: true,
|
|
699
|
+
instructionFilePattern: "CLAUDE.md",
|
|
700
|
+
supportsContextMonitor: true,
|
|
701
|
+
supportsStatusline: true,
|
|
702
|
+
supportsProviderPaths: true,
|
|
703
|
+
supportsTransport: true,
|
|
704
|
+
supportsTaskSync: true
|
|
705
|
+
};
|
|
706
|
+
hooks;
|
|
707
|
+
spawn;
|
|
708
|
+
install;
|
|
709
|
+
paths;
|
|
710
|
+
contextMonitor;
|
|
711
|
+
transport;
|
|
712
|
+
taskSync;
|
|
713
|
+
projectDir = null;
|
|
714
|
+
initialized = false;
|
|
715
|
+
constructor() {
|
|
716
|
+
this.hooks = new ClaudeCodeHookProvider();
|
|
717
|
+
this.spawn = new ClaudeCodeSpawnProvider();
|
|
718
|
+
this.install = new ClaudeCodeInstallProvider();
|
|
719
|
+
this.paths = new ClaudeCodePathProvider();
|
|
720
|
+
this.contextMonitor = new ClaudeCodeContextMonitorProvider();
|
|
721
|
+
this.transport = new ClaudeCodeTransportProvider();
|
|
722
|
+
this.taskSync = new ClaudeCodeTaskSyncProvider();
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Initialize the adapter for a given project directory.
|
|
726
|
+
*
|
|
727
|
+
* Validates the environment by checking for the Claude CLI
|
|
728
|
+
* and Claude Code configuration directory.
|
|
729
|
+
*
|
|
730
|
+
* @param projectDir - Root directory of the project
|
|
731
|
+
*/
|
|
732
|
+
async initialize(projectDir) {
|
|
733
|
+
this.projectDir = projectDir;
|
|
734
|
+
this.initialized = true;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Dispose the adapter and clean up resources.
|
|
738
|
+
*
|
|
739
|
+
* Unregisters hooks and releases any tracked state.
|
|
740
|
+
*/
|
|
741
|
+
async dispose() {
|
|
742
|
+
if (this.hooks.isRegistered()) {
|
|
743
|
+
await this.hooks.unregisterNativeHooks();
|
|
744
|
+
}
|
|
745
|
+
this.initialized = false;
|
|
746
|
+
this.projectDir = null;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Run a health check to verify Claude Code is accessible.
|
|
750
|
+
*
|
|
751
|
+
* Checks:
|
|
752
|
+
* 1. Adapter has been initialized
|
|
753
|
+
* 2. Claude CLI is available in PATH
|
|
754
|
+
* 3. ~/.claude/ configuration directory exists
|
|
755
|
+
*
|
|
756
|
+
* @returns Health status with details about each check
|
|
757
|
+
*/
|
|
758
|
+
async healthCheck() {
|
|
759
|
+
const details = {};
|
|
760
|
+
if (!this.initialized) {
|
|
761
|
+
return {
|
|
762
|
+
healthy: false,
|
|
763
|
+
provider: this.id,
|
|
764
|
+
details: { error: "Adapter not initialized" }
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
let cliAvailable = false;
|
|
768
|
+
try {
|
|
769
|
+
const { stdout } = await execAsync2("which claude");
|
|
770
|
+
cliAvailable = stdout.trim().length > 0;
|
|
771
|
+
details.cliPath = stdout.trim();
|
|
772
|
+
} catch {
|
|
773
|
+
details.cliAvailable = false;
|
|
774
|
+
}
|
|
775
|
+
const claudeConfigDir = join5(homedir4(), ".claude");
|
|
776
|
+
const configExists = existsSync3(claudeConfigDir);
|
|
777
|
+
details.configDirExists = configExists;
|
|
778
|
+
const entrypointSet = process.env.CLAUDE_CODE_ENTRYPOINT !== void 0;
|
|
779
|
+
details.entrypointEnvSet = entrypointSet;
|
|
780
|
+
const healthy = cliAvailable;
|
|
781
|
+
details.cliAvailable = cliAvailable;
|
|
782
|
+
return {
|
|
783
|
+
healthy,
|
|
784
|
+
provider: this.id,
|
|
785
|
+
details
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Check whether the adapter has been initialized.
|
|
790
|
+
*/
|
|
791
|
+
isInitialized() {
|
|
792
|
+
return this.initialized;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get the project directory this adapter was initialized with.
|
|
796
|
+
*/
|
|
797
|
+
getProjectDir() {
|
|
798
|
+
return this.projectDir;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// packages/adapters/src/providers/claude-code/statusline.js
|
|
805
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
806
|
+
import { homedir as homedir5 } from "node:os";
|
|
807
|
+
import { join as join6 } from "node:path";
|
|
808
|
+
function getClaudeSettingsPath() {
|
|
809
|
+
return process.env["CLAUDE_SETTINGS"] ?? join6(process.env["CLAUDE_HOME"] ?? join6(homedir5(), ".claude"), "settings.json");
|
|
810
|
+
}
|
|
811
|
+
function checkStatuslineIntegration() {
|
|
812
|
+
const settingsPath = getClaudeSettingsPath();
|
|
813
|
+
if (!existsSync4(settingsPath))
|
|
814
|
+
return "no_settings";
|
|
815
|
+
try {
|
|
816
|
+
const settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
817
|
+
const statusLine = settings.statusLine;
|
|
818
|
+
if (!statusLine?.type)
|
|
819
|
+
return "not_configured";
|
|
820
|
+
if (statusLine.type !== "command")
|
|
821
|
+
return "custom_no_cleo";
|
|
822
|
+
const cmd = statusLine.command ?? "";
|
|
823
|
+
if (cmd.includes("context-monitor.sh") || cmd.includes("cleo-statusline") || cmd.includes(".context-state.json") || cmd.includes("context-states")) {
|
|
824
|
+
return "configured";
|
|
825
|
+
}
|
|
826
|
+
const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir5()) : cmd;
|
|
827
|
+
if (existsSync4(scriptPath)) {
|
|
828
|
+
try {
|
|
829
|
+
const content = readFileSync3(scriptPath, "utf-8");
|
|
830
|
+
if (content.includes("context-state.json"))
|
|
831
|
+
return "configured";
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return "custom_no_cleo";
|
|
836
|
+
} catch {
|
|
837
|
+
return "no_settings";
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function getStatuslineConfig(cleoHome) {
|
|
841
|
+
return {
|
|
842
|
+
statusLine: {
|
|
843
|
+
type: "command",
|
|
844
|
+
command: join6(cleoHome, "lib", "session", "context-monitor.sh")
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function getSetupInstructions(cleoHome) {
|
|
849
|
+
const settingsPath = getClaudeSettingsPath();
|
|
850
|
+
return [
|
|
851
|
+
"To enable context monitoring, add to your Claude Code settings:",
|
|
852
|
+
`File: ${settingsPath}`,
|
|
853
|
+
"",
|
|
854
|
+
JSON.stringify(getStatuslineConfig(cleoHome), null, 2),
|
|
855
|
+
"",
|
|
856
|
+
"This enables real-time context window tracking in the CLI."
|
|
857
|
+
].join("\n");
|
|
858
|
+
}
|
|
859
|
+
var init_statusline = __esm({
|
|
860
|
+
"packages/adapters/src/providers/claude-code/statusline.js"() {
|
|
861
|
+
"use strict";
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
// packages/adapters/src/providers/claude-code/index.js
|
|
866
|
+
var claude_code_exports = {};
|
|
867
|
+
__export(claude_code_exports, {
|
|
868
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
869
|
+
ClaudeCodeContextMonitorProvider: () => ClaudeCodeContextMonitorProvider,
|
|
870
|
+
ClaudeCodeHookProvider: () => ClaudeCodeHookProvider,
|
|
871
|
+
ClaudeCodeInstallProvider: () => ClaudeCodeInstallProvider,
|
|
872
|
+
ClaudeCodePathProvider: () => ClaudeCodePathProvider,
|
|
873
|
+
ClaudeCodeSpawnProvider: () => ClaudeCodeSpawnProvider,
|
|
874
|
+
ClaudeCodeTransportProvider: () => ClaudeCodeTransportProvider,
|
|
875
|
+
checkStatuslineIntegration: () => checkStatuslineIntegration,
|
|
876
|
+
createAdapter: () => createAdapter,
|
|
877
|
+
default: () => claude_code_default,
|
|
878
|
+
getSetupInstructions: () => getSetupInstructions,
|
|
879
|
+
getStatuslineConfig: () => getStatuslineConfig
|
|
880
|
+
});
|
|
881
|
+
function createAdapter() {
|
|
882
|
+
return new ClaudeCodeAdapter();
|
|
883
|
+
}
|
|
884
|
+
var claude_code_default;
|
|
885
|
+
var init_claude_code = __esm({
|
|
886
|
+
"packages/adapters/src/providers/claude-code/index.js"() {
|
|
887
|
+
"use strict";
|
|
888
|
+
init_adapter();
|
|
889
|
+
init_adapter();
|
|
890
|
+
init_context_monitor();
|
|
891
|
+
init_hooks();
|
|
892
|
+
init_install();
|
|
893
|
+
init_paths();
|
|
894
|
+
init_spawn();
|
|
895
|
+
init_transport();
|
|
896
|
+
init_statusline();
|
|
897
|
+
claude_code_default = ClaudeCodeAdapter;
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
// packages/adapters/src/providers/opencode/hooks.js
|
|
902
|
+
var OPENCODE_EVENT_MAP, OpenCodeHookProvider;
|
|
903
|
+
var init_hooks2 = __esm({
|
|
904
|
+
"packages/adapters/src/providers/opencode/hooks.js"() {
|
|
905
|
+
"use strict";
|
|
906
|
+
OPENCODE_EVENT_MAP = {
|
|
907
|
+
"session.start": "onSessionStart",
|
|
908
|
+
"session.end": "onSessionEnd",
|
|
909
|
+
"tool.start": "onToolStart",
|
|
910
|
+
"tool.complete": "onToolComplete",
|
|
911
|
+
"error": "onError",
|
|
912
|
+
"prompt.submit": "onPromptSubmit"
|
|
913
|
+
};
|
|
914
|
+
OpenCodeHookProvider = class {
|
|
915
|
+
registered = false;
|
|
916
|
+
/**
|
|
917
|
+
* Map an OpenCode native event name to a CAAMP hook event name.
|
|
918
|
+
*
|
|
919
|
+
* @param providerEvent - OpenCode event name (e.g. "session.start", "tool.complete")
|
|
920
|
+
* @returns CAAMP event name or null if unmapped
|
|
921
|
+
*/
|
|
922
|
+
mapProviderEvent(providerEvent) {
|
|
923
|
+
return OPENCODE_EVENT_MAP[providerEvent] ?? null;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Register native hooks for a project.
|
|
927
|
+
*
|
|
928
|
+
* For OpenCode, hooks are registered via the config system
|
|
929
|
+
* (.opencode/config.json), which is handled by the install provider.
|
|
930
|
+
* This method marks hooks as registered without performing
|
|
931
|
+
* filesystem operations.
|
|
932
|
+
*
|
|
933
|
+
* @param _projectDir - Project directory (unused; config manages registration)
|
|
934
|
+
*/
|
|
935
|
+
async registerNativeHooks(_projectDir) {
|
|
936
|
+
this.registered = true;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Unregister native hooks.
|
|
940
|
+
*
|
|
941
|
+
* For OpenCode, this is a no-op since hooks are managed through
|
|
942
|
+
* the config system. Unregistration happens via the install
|
|
943
|
+
* provider's uninstall method.
|
|
944
|
+
*/
|
|
945
|
+
async unregisterNativeHooks() {
|
|
946
|
+
this.registered = false;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Check whether hooks have been registered via registerNativeHooks.
|
|
950
|
+
*/
|
|
951
|
+
isRegistered() {
|
|
952
|
+
return this.registered;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Get the full event mapping for introspection/debugging.
|
|
956
|
+
*/
|
|
957
|
+
getEventMap() {
|
|
958
|
+
return { ...OPENCODE_EVENT_MAP };
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// packages/adapters/src/providers/opencode/install.js
|
|
965
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
966
|
+
import { join as join7 } from "node:path";
|
|
967
|
+
var INSTRUCTION_REFERENCES2, MCP_SERVER_KEY2, OpenCodeInstallProvider;
|
|
968
|
+
var init_install2 = __esm({
|
|
969
|
+
"packages/adapters/src/providers/opencode/install.js"() {
|
|
970
|
+
"use strict";
|
|
971
|
+
INSTRUCTION_REFERENCES2 = [
|
|
972
|
+
"@~/.cleo/templates/CLEO-INJECTION.md",
|
|
973
|
+
"@.cleo/memory-bridge.md"
|
|
974
|
+
];
|
|
975
|
+
MCP_SERVER_KEY2 = "cleo";
|
|
976
|
+
OpenCodeInstallProvider = class {
|
|
977
|
+
installedProjectDir = null;
|
|
978
|
+
/**
|
|
979
|
+
* Install CLEO into an OpenCode project.
|
|
980
|
+
*
|
|
981
|
+
* @param options - Installation options including project directory and MCP server path
|
|
982
|
+
* @returns Result describing what was installed
|
|
983
|
+
*/
|
|
984
|
+
async install(options) {
|
|
985
|
+
const { projectDir, mcpServerPath } = options;
|
|
986
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
987
|
+
let instructionFileUpdated = false;
|
|
988
|
+
let mcpRegistered = false;
|
|
989
|
+
const details = {};
|
|
990
|
+
if (mcpServerPath) {
|
|
991
|
+
mcpRegistered = this.registerMcpServer(projectDir, mcpServerPath);
|
|
992
|
+
if (mcpRegistered) {
|
|
993
|
+
details.mcpConfigPath = join7(projectDir, ".opencode", "config.json");
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
instructionFileUpdated = this.updateInstructionFile(projectDir);
|
|
997
|
+
if (instructionFileUpdated) {
|
|
998
|
+
details.instructionFile = join7(projectDir, "AGENTS.md");
|
|
999
|
+
}
|
|
1000
|
+
this.installedProjectDir = projectDir;
|
|
1001
|
+
return {
|
|
1002
|
+
success: true,
|
|
1003
|
+
installedAt,
|
|
1004
|
+
instructionFileUpdated,
|
|
1005
|
+
mcpRegistered,
|
|
1006
|
+
details
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Uninstall CLEO from the current OpenCode project.
|
|
1011
|
+
*
|
|
1012
|
+
* Removes the MCP server registration from .opencode/config.json.
|
|
1013
|
+
* Does not remove AGENTS.md references (they are harmless if CLEO is not present).
|
|
1014
|
+
*/
|
|
1015
|
+
async uninstall() {
|
|
1016
|
+
if (!this.installedProjectDir)
|
|
1017
|
+
return;
|
|
1018
|
+
const configPath = join7(this.installedProjectDir, ".opencode", "config.json");
|
|
1019
|
+
if (existsSync5(configPath)) {
|
|
1020
|
+
try {
|
|
1021
|
+
const raw = readFileSync4(configPath, "utf-8");
|
|
1022
|
+
const config = JSON.parse(raw);
|
|
1023
|
+
const mcpServers = config.mcpServers;
|
|
1024
|
+
if (mcpServers && MCP_SERVER_KEY2 in mcpServers) {
|
|
1025
|
+
delete mcpServers[MCP_SERVER_KEY2];
|
|
1026
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
this.installedProjectDir = null;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Check whether CLEO is installed in the current environment.
|
|
1035
|
+
*
|
|
1036
|
+
* Checks for MCP server registered in .opencode/config.json.
|
|
1037
|
+
* Returns true if the CLEO MCP server entry is found.
|
|
1038
|
+
*/
|
|
1039
|
+
async isInstalled() {
|
|
1040
|
+
const configPath = join7(process.cwd(), ".opencode", "config.json");
|
|
1041
|
+
if (existsSync5(configPath)) {
|
|
1042
|
+
try {
|
|
1043
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1044
|
+
const mcpServers = config.mcpServers;
|
|
1045
|
+
if (mcpServers && MCP_SERVER_KEY2 in mcpServers) {
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Ensure AGENTS.md contains @-references to CLEO instruction files.
|
|
1055
|
+
*
|
|
1056
|
+
* Creates AGENTS.md if it does not exist. Appends any missing references.
|
|
1057
|
+
*
|
|
1058
|
+
* @param projectDir - Project root directory
|
|
1059
|
+
*/
|
|
1060
|
+
async ensureInstructionReferences(projectDir) {
|
|
1061
|
+
this.updateInstructionFile(projectDir);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Register the CLEO MCP server in .opencode/config.json.
|
|
1065
|
+
*
|
|
1066
|
+
* OpenCode stores its MCP server configuration in .opencode/config.json
|
|
1067
|
+
* under the mcpServers key.
|
|
1068
|
+
*
|
|
1069
|
+
* @returns true if registration was performed or updated
|
|
1070
|
+
*/
|
|
1071
|
+
registerMcpServer(projectDir, mcpServerPath) {
|
|
1072
|
+
const openCodeDir = join7(projectDir, ".opencode");
|
|
1073
|
+
const configPath = join7(openCodeDir, "config.json");
|
|
1074
|
+
let config = {};
|
|
1075
|
+
mkdirSync2(openCodeDir, { recursive: true });
|
|
1076
|
+
if (existsSync5(configPath)) {
|
|
1077
|
+
try {
|
|
1078
|
+
config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1083
|
+
config.mcpServers = {};
|
|
1084
|
+
}
|
|
1085
|
+
const mcpServers = config.mcpServers;
|
|
1086
|
+
mcpServers[MCP_SERVER_KEY2] = {
|
|
1087
|
+
command: "node",
|
|
1088
|
+
args: [mcpServerPath]
|
|
1089
|
+
};
|
|
1090
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Update AGENTS.md with CLEO @-references.
|
|
1095
|
+
*
|
|
1096
|
+
* @returns true if the file was created or modified
|
|
1097
|
+
*/
|
|
1098
|
+
updateInstructionFile(projectDir) {
|
|
1099
|
+
const agentsMdPath = join7(projectDir, "AGENTS.md");
|
|
1100
|
+
let content = "";
|
|
1101
|
+
let existed = false;
|
|
1102
|
+
if (existsSync5(agentsMdPath)) {
|
|
1103
|
+
content = readFileSync4(agentsMdPath, "utf-8");
|
|
1104
|
+
existed = true;
|
|
1105
|
+
}
|
|
1106
|
+
const missingRefs = INSTRUCTION_REFERENCES2.filter((ref) => !content.includes(ref));
|
|
1107
|
+
if (missingRefs.length === 0) {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
const refsBlock = missingRefs.join("\n");
|
|
1111
|
+
if (existed) {
|
|
1112
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
1113
|
+
content = content + separator + refsBlock + "\n";
|
|
1114
|
+
} else {
|
|
1115
|
+
content = refsBlock + "\n";
|
|
1116
|
+
}
|
|
1117
|
+
writeFileSync3(agentsMdPath, content, "utf-8");
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// packages/adapters/src/providers/opencode/spawn.js
|
|
1125
|
+
import { exec as exec3, spawn as nodeSpawn2 } from "node:child_process";
|
|
1126
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1127
|
+
import { join as join8 } from "node:path";
|
|
1128
|
+
import { promisify as promisify3 } from "node:util";
|
|
1129
|
+
function buildOpenCodeAgentMarkdown(description, instructions) {
|
|
1130
|
+
const normalizedDesc = description.replace(/\s+/g, " ").trim();
|
|
1131
|
+
return [
|
|
1132
|
+
"---",
|
|
1133
|
+
`description: ${JSON.stringify(normalizedDesc)}`,
|
|
1134
|
+
"mode: subagent",
|
|
1135
|
+
"hidden: true",
|
|
1136
|
+
"---",
|
|
1137
|
+
"",
|
|
1138
|
+
instructions.trim(),
|
|
1139
|
+
""
|
|
1140
|
+
].join("\n");
|
|
1141
|
+
}
|
|
1142
|
+
async function ensureSubagentDefinition(workingDirectory) {
|
|
1143
|
+
const agentDir = join8(workingDirectory, ".opencode", "agent");
|
|
1144
|
+
const agentPath = join8(agentDir, `${OPENCODE_SUBAGENT_NAME}.md`);
|
|
1145
|
+
const description = "CLEO task executor with protocol compliance.";
|
|
1146
|
+
const instructions = [
|
|
1147
|
+
"# CLEO Subagent",
|
|
1148
|
+
"",
|
|
1149
|
+
"You are a CLEO subagent executing a delegated task.",
|
|
1150
|
+
"Follow the CLEO protocol and complete the assigned work.",
|
|
1151
|
+
"",
|
|
1152
|
+
"@~/.cleo/templates/CLEO-INJECTION.md"
|
|
1153
|
+
].join("\n");
|
|
1154
|
+
const content = buildOpenCodeAgentMarkdown(description, instructions);
|
|
1155
|
+
await mkdir2(agentDir, { recursive: true });
|
|
1156
|
+
let existing = null;
|
|
1157
|
+
try {
|
|
1158
|
+
existing = await readFile2(agentPath, "utf-8");
|
|
1159
|
+
} catch {
|
|
1160
|
+
existing = null;
|
|
1161
|
+
}
|
|
1162
|
+
if (existing !== content) {
|
|
1163
|
+
await writeFile2(agentPath, content, "utf-8");
|
|
1164
|
+
}
|
|
1165
|
+
return OPENCODE_SUBAGENT_NAME;
|
|
1166
|
+
}
|
|
1167
|
+
var execAsync3, OPENCODE_SUBAGENT_NAME, OPENCODE_FALLBACK_AGENT, OpenCodeSpawnProvider;
|
|
1168
|
+
var init_spawn2 = __esm({
|
|
1169
|
+
"packages/adapters/src/providers/opencode/spawn.js"() {
|
|
1170
|
+
"use strict";
|
|
1171
|
+
execAsync3 = promisify3(exec3);
|
|
1172
|
+
OPENCODE_SUBAGENT_NAME = "cleo-subagent";
|
|
1173
|
+
OPENCODE_FALLBACK_AGENT = "general";
|
|
1174
|
+
OpenCodeSpawnProvider = class {
|
|
1175
|
+
/** Map of instance IDs to tracked process info. */
|
|
1176
|
+
processMap = /* @__PURE__ */ new Map();
|
|
1177
|
+
/**
|
|
1178
|
+
* Check if the OpenCode CLI is available in PATH.
|
|
1179
|
+
*
|
|
1180
|
+
* @returns true if `opencode` is found via `which`
|
|
1181
|
+
*/
|
|
1182
|
+
async canSpawn() {
|
|
1183
|
+
try {
|
|
1184
|
+
await execAsync3("which opencode");
|
|
1185
|
+
return true;
|
|
1186
|
+
} catch {
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Spawn a subagent via OpenCode CLI.
|
|
1192
|
+
*
|
|
1193
|
+
* Ensures the CLEO subagent definition exists in the project's
|
|
1194
|
+
* .opencode/agent/ directory, then spawns a detached OpenCode
|
|
1195
|
+
* process. The process runs independently of the parent.
|
|
1196
|
+
*
|
|
1197
|
+
* @param context - Spawn context with taskId, prompt, and options
|
|
1198
|
+
* @returns Spawn result with instance ID and status
|
|
1199
|
+
*/
|
|
1200
|
+
async spawn(context) {
|
|
1201
|
+
const instanceId = `opencode-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1202
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1203
|
+
const workingDirectory = context.workingDirectory ?? process.cwd();
|
|
1204
|
+
try {
|
|
1205
|
+
let agentName;
|
|
1206
|
+
try {
|
|
1207
|
+
agentName = await ensureSubagentDefinition(workingDirectory);
|
|
1208
|
+
} catch {
|
|
1209
|
+
agentName = OPENCODE_FALLBACK_AGENT;
|
|
1210
|
+
}
|
|
1211
|
+
const child = nodeSpawn2("opencode", [
|
|
1212
|
+
"run",
|
|
1213
|
+
"--format",
|
|
1214
|
+
"json",
|
|
1215
|
+
"--agent",
|
|
1216
|
+
agentName,
|
|
1217
|
+
"--title",
|
|
1218
|
+
`CLEO ${context.taskId}`,
|
|
1219
|
+
context.prompt
|
|
1220
|
+
], {
|
|
1221
|
+
cwd: workingDirectory,
|
|
1222
|
+
detached: true,
|
|
1223
|
+
stdio: "ignore"
|
|
1224
|
+
});
|
|
1225
|
+
child.unref();
|
|
1226
|
+
if (child.pid) {
|
|
1227
|
+
this.processMap.set(instanceId, {
|
|
1228
|
+
pid: child.pid,
|
|
1229
|
+
taskId: context.taskId,
|
|
1230
|
+
startTime
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
child.on("exit", () => {
|
|
1234
|
+
this.processMap.delete(instanceId);
|
|
1235
|
+
});
|
|
1236
|
+
return {
|
|
1237
|
+
instanceId,
|
|
1238
|
+
taskId: context.taskId,
|
|
1239
|
+
providerId: "opencode",
|
|
1240
|
+
status: "running",
|
|
1241
|
+
startTime
|
|
1242
|
+
};
|
|
1243
|
+
} catch {
|
|
1244
|
+
return {
|
|
1245
|
+
instanceId,
|
|
1246
|
+
taskId: context.taskId,
|
|
1247
|
+
providerId: "opencode",
|
|
1248
|
+
status: "failed",
|
|
1249
|
+
startTime,
|
|
1250
|
+
endTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* List currently running OpenCode subagent processes.
|
|
1256
|
+
*
|
|
1257
|
+
* Checks each tracked process via kill(pid, 0) to verify it is still alive.
|
|
1258
|
+
* Dead processes are automatically cleaned from the tracking map.
|
|
1259
|
+
*
|
|
1260
|
+
* @returns Array of spawn results for running processes
|
|
1261
|
+
*/
|
|
1262
|
+
async listRunning() {
|
|
1263
|
+
const running = [];
|
|
1264
|
+
for (const [instanceId, tracked] of this.processMap.entries()) {
|
|
1265
|
+
try {
|
|
1266
|
+
process.kill(tracked.pid, 0);
|
|
1267
|
+
running.push({
|
|
1268
|
+
instanceId,
|
|
1269
|
+
taskId: tracked.taskId,
|
|
1270
|
+
providerId: "opencode",
|
|
1271
|
+
status: "running",
|
|
1272
|
+
startTime: tracked.startTime
|
|
1273
|
+
});
|
|
1274
|
+
} catch {
|
|
1275
|
+
this.processMap.delete(instanceId);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return running;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Terminate a running spawn by instance ID.
|
|
1282
|
+
*
|
|
1283
|
+
* Sends SIGTERM to the tracked process. If the process is not found
|
|
1284
|
+
* or has already exited, this is a no-op.
|
|
1285
|
+
*
|
|
1286
|
+
* @param instanceId - ID of the spawn instance to terminate
|
|
1287
|
+
*/
|
|
1288
|
+
async terminate(instanceId) {
|
|
1289
|
+
const tracked = this.processMap.get(instanceId);
|
|
1290
|
+
if (!tracked)
|
|
1291
|
+
return;
|
|
1292
|
+
try {
|
|
1293
|
+
process.kill(tracked.pid, "SIGTERM");
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
this.processMap.delete(instanceId);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
// packages/adapters/src/providers/opencode/adapter.js
|
|
1303
|
+
import { exec as exec4 } from "node:child_process";
|
|
1304
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1305
|
+
import { join as join9 } from "node:path";
|
|
1306
|
+
import { promisify as promisify4 } from "node:util";
|
|
1307
|
+
var execAsync4, OpenCodeAdapter;
|
|
1308
|
+
var init_adapter2 = __esm({
|
|
1309
|
+
"packages/adapters/src/providers/opencode/adapter.js"() {
|
|
1310
|
+
"use strict";
|
|
1311
|
+
init_hooks2();
|
|
1312
|
+
init_install2();
|
|
1313
|
+
init_spawn2();
|
|
1314
|
+
execAsync4 = promisify4(exec4);
|
|
1315
|
+
OpenCodeAdapter = class {
|
|
1316
|
+
id = "opencode";
|
|
1317
|
+
name = "OpenCode";
|
|
1318
|
+
version = "1.0.0";
|
|
1319
|
+
capabilities = {
|
|
1320
|
+
supportsHooks: true,
|
|
1321
|
+
supportedHookEvents: [
|
|
1322
|
+
"onSessionStart",
|
|
1323
|
+
"onSessionEnd",
|
|
1324
|
+
"onToolStart",
|
|
1325
|
+
"onToolComplete",
|
|
1326
|
+
"onError",
|
|
1327
|
+
"onPromptSubmit"
|
|
1328
|
+
],
|
|
1329
|
+
supportsSpawn: true,
|
|
1330
|
+
supportsInstall: true,
|
|
1331
|
+
supportsMcp: true,
|
|
1332
|
+
supportsInstructionFiles: true,
|
|
1333
|
+
instructionFilePattern: "AGENTS.md",
|
|
1334
|
+
supportsContextMonitor: false,
|
|
1335
|
+
supportsStatusline: false,
|
|
1336
|
+
supportsProviderPaths: true,
|
|
1337
|
+
supportsTransport: false,
|
|
1338
|
+
supportsTaskSync: false
|
|
1339
|
+
};
|
|
1340
|
+
hooks;
|
|
1341
|
+
spawn;
|
|
1342
|
+
install;
|
|
1343
|
+
projectDir = null;
|
|
1344
|
+
initialized = false;
|
|
1345
|
+
constructor() {
|
|
1346
|
+
this.hooks = new OpenCodeHookProvider();
|
|
1347
|
+
this.spawn = new OpenCodeSpawnProvider();
|
|
1348
|
+
this.install = new OpenCodeInstallProvider();
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Initialize the adapter for a given project directory.
|
|
1352
|
+
*
|
|
1353
|
+
* Validates the environment by checking for the OpenCode CLI
|
|
1354
|
+
* and OpenCode configuration directory.
|
|
1355
|
+
*
|
|
1356
|
+
* @param projectDir - Root directory of the project
|
|
1357
|
+
*/
|
|
1358
|
+
async initialize(projectDir) {
|
|
1359
|
+
this.projectDir = projectDir;
|
|
1360
|
+
this.initialized = true;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Dispose the adapter and clean up resources.
|
|
1364
|
+
*
|
|
1365
|
+
* Unregisters hooks and releases any tracked state.
|
|
1366
|
+
*/
|
|
1367
|
+
async dispose() {
|
|
1368
|
+
if (this.hooks.isRegistered()) {
|
|
1369
|
+
await this.hooks.unregisterNativeHooks();
|
|
1370
|
+
}
|
|
1371
|
+
this.initialized = false;
|
|
1372
|
+
this.projectDir = null;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Run a health check to verify OpenCode is accessible.
|
|
1376
|
+
*
|
|
1377
|
+
* Checks:
|
|
1378
|
+
* 1. Adapter has been initialized
|
|
1379
|
+
* 2. OpenCode CLI is available in PATH
|
|
1380
|
+
* 3. .opencode/ configuration directory exists in the project
|
|
1381
|
+
*
|
|
1382
|
+
* @returns Health status with details about each check
|
|
1383
|
+
*/
|
|
1384
|
+
async healthCheck() {
|
|
1385
|
+
const details = {};
|
|
1386
|
+
if (!this.initialized) {
|
|
1387
|
+
return {
|
|
1388
|
+
healthy: false,
|
|
1389
|
+
provider: this.id,
|
|
1390
|
+
details: { error: "Adapter not initialized" }
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
let cliAvailable = false;
|
|
1394
|
+
try {
|
|
1395
|
+
const { stdout } = await execAsync4("which opencode");
|
|
1396
|
+
cliAvailable = stdout.trim().length > 0;
|
|
1397
|
+
details.cliPath = stdout.trim();
|
|
1398
|
+
} catch {
|
|
1399
|
+
details.cliAvailable = false;
|
|
1400
|
+
}
|
|
1401
|
+
if (this.projectDir) {
|
|
1402
|
+
const openCodeConfigDir = join9(this.projectDir, ".opencode");
|
|
1403
|
+
const configExists = existsSync6(openCodeConfigDir);
|
|
1404
|
+
details.configDirExists = configExists;
|
|
1405
|
+
}
|
|
1406
|
+
const versionEnvSet = process.env.OPENCODE_VERSION !== void 0;
|
|
1407
|
+
details.versionEnvSet = versionEnvSet;
|
|
1408
|
+
const healthy = cliAvailable;
|
|
1409
|
+
details.cliAvailable = cliAvailable;
|
|
1410
|
+
return {
|
|
1411
|
+
healthy,
|
|
1412
|
+
provider: this.id,
|
|
1413
|
+
details
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Check whether the adapter has been initialized.
|
|
1418
|
+
*/
|
|
1419
|
+
isInitialized() {
|
|
1420
|
+
return this.initialized;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Get the project directory this adapter was initialized with.
|
|
1424
|
+
*/
|
|
1425
|
+
getProjectDir() {
|
|
1426
|
+
return this.projectDir;
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
// packages/adapters/src/providers/opencode/index.js
|
|
1433
|
+
var opencode_exports = {};
|
|
1434
|
+
__export(opencode_exports, {
|
|
1435
|
+
OpenCodeAdapter: () => OpenCodeAdapter,
|
|
1436
|
+
OpenCodeHookProvider: () => OpenCodeHookProvider,
|
|
1437
|
+
OpenCodeInstallProvider: () => OpenCodeInstallProvider,
|
|
1438
|
+
OpenCodeSpawnProvider: () => OpenCodeSpawnProvider,
|
|
1439
|
+
createAdapter: () => createAdapter2,
|
|
1440
|
+
default: () => opencode_default
|
|
1441
|
+
});
|
|
1442
|
+
function createAdapter2() {
|
|
1443
|
+
return new OpenCodeAdapter();
|
|
1444
|
+
}
|
|
1445
|
+
var opencode_default;
|
|
1446
|
+
var init_opencode = __esm({
|
|
1447
|
+
"packages/adapters/src/providers/opencode/index.js"() {
|
|
1448
|
+
"use strict";
|
|
1449
|
+
init_adapter2();
|
|
1450
|
+
init_adapter2();
|
|
1451
|
+
init_hooks2();
|
|
1452
|
+
init_spawn2();
|
|
1453
|
+
init_install2();
|
|
1454
|
+
opencode_default = OpenCodeAdapter;
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
// packages/adapters/src/providers/cursor/hooks.js
|
|
1459
|
+
var CursorHookProvider;
|
|
1460
|
+
var init_hooks3 = __esm({
|
|
1461
|
+
"packages/adapters/src/providers/cursor/hooks.js"() {
|
|
1462
|
+
"use strict";
|
|
1463
|
+
CursorHookProvider = class {
|
|
1464
|
+
registered = false;
|
|
1465
|
+
/**
|
|
1466
|
+
* Map a provider event name to a CAAMP hook event name.
|
|
1467
|
+
*
|
|
1468
|
+
* Always returns null since Cursor does not emit hook events.
|
|
1469
|
+
*
|
|
1470
|
+
* @param _providerEvent - Ignored; Cursor has no hook events
|
|
1471
|
+
* @returns null (no mapping available)
|
|
1472
|
+
*/
|
|
1473
|
+
mapProviderEvent(_providerEvent) {
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Register native hooks for a project.
|
|
1478
|
+
*
|
|
1479
|
+
* No-op for Cursor since it has no hook system.
|
|
1480
|
+
*
|
|
1481
|
+
* @param _projectDir - Ignored
|
|
1482
|
+
*/
|
|
1483
|
+
async registerNativeHooks(_projectDir) {
|
|
1484
|
+
this.registered = true;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Unregister native hooks.
|
|
1488
|
+
*
|
|
1489
|
+
* No-op for Cursor since it has no hook system.
|
|
1490
|
+
*/
|
|
1491
|
+
async unregisterNativeHooks() {
|
|
1492
|
+
this.registered = false;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Check whether hooks have been registered.
|
|
1496
|
+
*/
|
|
1497
|
+
isRegistered() {
|
|
1498
|
+
return this.registered;
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
// packages/adapters/src/providers/cursor/install.js
|
|
1505
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1506
|
+
import { join as join10 } from "node:path";
|
|
1507
|
+
var INSTRUCTION_REFERENCES3, MCP_SERVER_KEY3, CursorInstallProvider;
|
|
1508
|
+
var init_install3 = __esm({
|
|
1509
|
+
"packages/adapters/src/providers/cursor/install.js"() {
|
|
1510
|
+
"use strict";
|
|
1511
|
+
INSTRUCTION_REFERENCES3 = [
|
|
1512
|
+
"@~/.cleo/templates/CLEO-INJECTION.md",
|
|
1513
|
+
"@.cleo/memory-bridge.md"
|
|
1514
|
+
];
|
|
1515
|
+
MCP_SERVER_KEY3 = "cleo";
|
|
1516
|
+
CursorInstallProvider = class {
|
|
1517
|
+
installedProjectDir = null;
|
|
1518
|
+
/**
|
|
1519
|
+
* Install CLEO into a Cursor project.
|
|
1520
|
+
*
|
|
1521
|
+
* @param options - Installation options including project directory and MCP server path
|
|
1522
|
+
* @returns Result describing what was installed
|
|
1523
|
+
*/
|
|
1524
|
+
async install(options) {
|
|
1525
|
+
const { projectDir, mcpServerPath } = options;
|
|
1526
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1527
|
+
let instructionFileUpdated = false;
|
|
1528
|
+
let mcpRegistered = false;
|
|
1529
|
+
const details = {};
|
|
1530
|
+
if (mcpServerPath) {
|
|
1531
|
+
mcpRegistered = this.registerMcpServer(projectDir, mcpServerPath);
|
|
1532
|
+
if (mcpRegistered) {
|
|
1533
|
+
details.mcpConfigPath = join10(projectDir, ".cursor", "mcp.json");
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
instructionFileUpdated = this.updateInstructionFiles(projectDir);
|
|
1537
|
+
if (instructionFileUpdated) {
|
|
1538
|
+
details.instructionFiles = this.getUpdatedFileList(projectDir);
|
|
1539
|
+
}
|
|
1540
|
+
this.installedProjectDir = projectDir;
|
|
1541
|
+
return {
|
|
1542
|
+
success: true,
|
|
1543
|
+
installedAt,
|
|
1544
|
+
instructionFileUpdated,
|
|
1545
|
+
mcpRegistered,
|
|
1546
|
+
details
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Uninstall CLEO from the current Cursor project.
|
|
1551
|
+
*
|
|
1552
|
+
* Removes the MCP server registration from .cursor/mcp.json.
|
|
1553
|
+
* Does not remove instruction file references (they are harmless if CLEO is not present).
|
|
1554
|
+
*/
|
|
1555
|
+
async uninstall() {
|
|
1556
|
+
if (!this.installedProjectDir)
|
|
1557
|
+
return;
|
|
1558
|
+
const mcpPath = join10(this.installedProjectDir, ".cursor", "mcp.json");
|
|
1559
|
+
if (existsSync7(mcpPath)) {
|
|
1560
|
+
try {
|
|
1561
|
+
const raw = readFileSync5(mcpPath, "utf-8");
|
|
1562
|
+
const config = JSON.parse(raw);
|
|
1563
|
+
const mcpServers = config.mcpServers;
|
|
1564
|
+
if (mcpServers && MCP_SERVER_KEY3 in mcpServers) {
|
|
1565
|
+
delete mcpServers[MCP_SERVER_KEY3];
|
|
1566
|
+
writeFileSync4(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1567
|
+
}
|
|
1568
|
+
} catch {
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
this.installedProjectDir = null;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Check whether CLEO is installed in the current environment.
|
|
1575
|
+
*
|
|
1576
|
+
* Checks for MCP server registered in .cursor/mcp.json.
|
|
1577
|
+
*/
|
|
1578
|
+
async isInstalled() {
|
|
1579
|
+
const mcpPath = join10(process.cwd(), ".cursor", "mcp.json");
|
|
1580
|
+
if (existsSync7(mcpPath)) {
|
|
1581
|
+
try {
|
|
1582
|
+
const config = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
1583
|
+
const mcpServers = config.mcpServers;
|
|
1584
|
+
if (mcpServers && MCP_SERVER_KEY3 in mcpServers) {
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
} catch {
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Ensure instruction files contain @-references to CLEO.
|
|
1594
|
+
*
|
|
1595
|
+
* Updates .cursorrules (legacy) and creates .cursor/rules/cleo.mdc (modern).
|
|
1596
|
+
*
|
|
1597
|
+
* @param projectDir - Project root directory
|
|
1598
|
+
*/
|
|
1599
|
+
async ensureInstructionReferences(projectDir) {
|
|
1600
|
+
this.updateInstructionFiles(projectDir);
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Register the CLEO MCP server in .cursor/mcp.json.
|
|
1604
|
+
*
|
|
1605
|
+
* Cursor stores MCP server configuration in .cursor/mcp.json
|
|
1606
|
+
* under the mcpServers key.
|
|
1607
|
+
*
|
|
1608
|
+
* @returns true if registration was performed or updated
|
|
1609
|
+
*/
|
|
1610
|
+
registerMcpServer(projectDir, mcpServerPath) {
|
|
1611
|
+
const cursorDir = join10(projectDir, ".cursor");
|
|
1612
|
+
const mcpPath = join10(cursorDir, "mcp.json");
|
|
1613
|
+
let config = {};
|
|
1614
|
+
mkdirSync3(cursorDir, { recursive: true });
|
|
1615
|
+
if (existsSync7(mcpPath)) {
|
|
1616
|
+
try {
|
|
1617
|
+
config = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
1618
|
+
} catch {
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1622
|
+
config.mcpServers = {};
|
|
1623
|
+
}
|
|
1624
|
+
const mcpServers = config.mcpServers;
|
|
1625
|
+
mcpServers[MCP_SERVER_KEY3] = {
|
|
1626
|
+
command: "node",
|
|
1627
|
+
args: [mcpServerPath]
|
|
1628
|
+
};
|
|
1629
|
+
writeFileSync4(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Update instruction files with CLEO @-references.
|
|
1634
|
+
*
|
|
1635
|
+
* Handles both legacy (.cursorrules) and modern (.cursor/rules/cleo.mdc) formats.
|
|
1636
|
+
*
|
|
1637
|
+
* @returns true if any file was created or modified
|
|
1638
|
+
*/
|
|
1639
|
+
updateInstructionFiles(projectDir) {
|
|
1640
|
+
let updated = false;
|
|
1641
|
+
if (this.updateLegacyRules(projectDir)) {
|
|
1642
|
+
updated = true;
|
|
1643
|
+
}
|
|
1644
|
+
if (this.updateModernRules(projectDir)) {
|
|
1645
|
+
updated = true;
|
|
1646
|
+
}
|
|
1647
|
+
return updated;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Update legacy .cursorrules file with @-references.
|
|
1651
|
+
* Only modifies the file if it already exists (does not create it).
|
|
1652
|
+
*
|
|
1653
|
+
* @returns true if the file was modified
|
|
1654
|
+
*/
|
|
1655
|
+
updateLegacyRules(projectDir) {
|
|
1656
|
+
const rulesPath = join10(projectDir, ".cursorrules");
|
|
1657
|
+
if (!existsSync7(rulesPath)) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
let content = readFileSync5(rulesPath, "utf-8");
|
|
1661
|
+
const missingRefs = INSTRUCTION_REFERENCES3.filter((ref) => !content.includes(ref));
|
|
1662
|
+
if (missingRefs.length === 0) {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
1666
|
+
content = content + separator + missingRefs.join("\n") + "\n";
|
|
1667
|
+
writeFileSync4(rulesPath, content, "utf-8");
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Create or update .cursor/rules/cleo.mdc with CLEO references.
|
|
1672
|
+
*
|
|
1673
|
+
* MDC (Markdown Component) format is Cursor's modern rule file format.
|
|
1674
|
+
* Each .mdc file in .cursor/rules/ is loaded as a rule set.
|
|
1675
|
+
*
|
|
1676
|
+
* @returns true if the file was created or modified
|
|
1677
|
+
*/
|
|
1678
|
+
updateModernRules(projectDir) {
|
|
1679
|
+
const rulesDir = join10(projectDir, ".cursor", "rules");
|
|
1680
|
+
const mdcPath = join10(rulesDir, "cleo.mdc");
|
|
1681
|
+
const expectedContent = [
|
|
1682
|
+
"---",
|
|
1683
|
+
"description: CLEO task management protocol references",
|
|
1684
|
+
'globs: "**/*"',
|
|
1685
|
+
"alwaysApply: true",
|
|
1686
|
+
"---",
|
|
1687
|
+
"",
|
|
1688
|
+
...INSTRUCTION_REFERENCES3,
|
|
1689
|
+
""
|
|
1690
|
+
].join("\n");
|
|
1691
|
+
if (existsSync7(mdcPath)) {
|
|
1692
|
+
const existing = readFileSync5(mdcPath, "utf-8");
|
|
1693
|
+
if (existing === expectedContent) {
|
|
1694
|
+
return false;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
mkdirSync3(rulesDir, { recursive: true });
|
|
1698
|
+
writeFileSync4(mdcPath, expectedContent, "utf-8");
|
|
1699
|
+
return true;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Get list of instruction files that were updated.
|
|
1703
|
+
*/
|
|
1704
|
+
getUpdatedFileList(projectDir) {
|
|
1705
|
+
const files = [];
|
|
1706
|
+
if (existsSync7(join10(projectDir, ".cursorrules"))) {
|
|
1707
|
+
files.push(join10(projectDir, ".cursorrules"));
|
|
1708
|
+
}
|
|
1709
|
+
files.push(join10(projectDir, ".cursor", "rules", "cleo.mdc"));
|
|
1710
|
+
return files;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
// packages/adapters/src/providers/cursor/adapter.js
|
|
1717
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1718
|
+
import { join as join11 } from "node:path";
|
|
1719
|
+
var CursorAdapter;
|
|
1720
|
+
var init_adapter3 = __esm({
|
|
1721
|
+
"packages/adapters/src/providers/cursor/adapter.js"() {
|
|
1722
|
+
"use strict";
|
|
1723
|
+
init_hooks3();
|
|
1724
|
+
init_install3();
|
|
1725
|
+
CursorAdapter = class {
|
|
1726
|
+
id = "cursor";
|
|
1727
|
+
name = "Cursor";
|
|
1728
|
+
version = "1.0.0";
|
|
1729
|
+
capabilities = {
|
|
1730
|
+
supportsHooks: false,
|
|
1731
|
+
supportedHookEvents: [],
|
|
1732
|
+
supportsSpawn: false,
|
|
1733
|
+
supportsInstall: true,
|
|
1734
|
+
supportsMcp: true,
|
|
1735
|
+
supportsInstructionFiles: true,
|
|
1736
|
+
instructionFilePattern: ".cursor/rules/*.mdc",
|
|
1737
|
+
supportsContextMonitor: false,
|
|
1738
|
+
supportsStatusline: false,
|
|
1739
|
+
supportsProviderPaths: true,
|
|
1740
|
+
supportsTransport: false,
|
|
1741
|
+
supportsTaskSync: false
|
|
1742
|
+
};
|
|
1743
|
+
hooks;
|
|
1744
|
+
install;
|
|
1745
|
+
projectDir = null;
|
|
1746
|
+
initialized = false;
|
|
1747
|
+
constructor() {
|
|
1748
|
+
this.hooks = new CursorHookProvider();
|
|
1749
|
+
this.install = new CursorInstallProvider();
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Initialize the adapter for a given project directory.
|
|
1753
|
+
*
|
|
1754
|
+
* @param projectDir - Root directory of the project
|
|
1755
|
+
*/
|
|
1756
|
+
async initialize(projectDir) {
|
|
1757
|
+
this.projectDir = projectDir;
|
|
1758
|
+
this.initialized = true;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Dispose the adapter and clean up resources.
|
|
1762
|
+
*/
|
|
1763
|
+
async dispose() {
|
|
1764
|
+
if (this.hooks.isRegistered()) {
|
|
1765
|
+
await this.hooks.unregisterNativeHooks();
|
|
1766
|
+
}
|
|
1767
|
+
this.initialized = false;
|
|
1768
|
+
this.projectDir = null;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Run a health check to verify Cursor is accessible.
|
|
1772
|
+
*
|
|
1773
|
+
* Checks:
|
|
1774
|
+
* 1. Adapter has been initialized
|
|
1775
|
+
* 2. .cursor/ configuration directory exists in the project
|
|
1776
|
+
* 3. CURSOR_EDITOR env var is set
|
|
1777
|
+
*
|
|
1778
|
+
* @returns Health status with details about each check
|
|
1779
|
+
*/
|
|
1780
|
+
async healthCheck() {
|
|
1781
|
+
const details = {};
|
|
1782
|
+
if (!this.initialized) {
|
|
1783
|
+
return {
|
|
1784
|
+
healthy: false,
|
|
1785
|
+
provider: this.id,
|
|
1786
|
+
details: { error: "Adapter not initialized" }
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
let configExists = false;
|
|
1790
|
+
if (this.projectDir) {
|
|
1791
|
+
const cursorConfigDir = join11(this.projectDir, ".cursor");
|
|
1792
|
+
configExists = existsSync8(cursorConfigDir);
|
|
1793
|
+
details.configDirExists = configExists;
|
|
1794
|
+
}
|
|
1795
|
+
const editorEnvSet = process.env.CURSOR_EDITOR !== void 0;
|
|
1796
|
+
details.editorEnvSet = editorEnvSet;
|
|
1797
|
+
if (this.projectDir) {
|
|
1798
|
+
const legacyRulesExist = existsSync8(join11(this.projectDir, ".cursorrules"));
|
|
1799
|
+
details.legacyRulesExist = legacyRulesExist;
|
|
1800
|
+
}
|
|
1801
|
+
const healthy = configExists || editorEnvSet;
|
|
1802
|
+
details.detected = healthy;
|
|
1803
|
+
return {
|
|
1804
|
+
healthy,
|
|
1805
|
+
provider: this.id,
|
|
1806
|
+
details
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Check whether the adapter has been initialized.
|
|
1811
|
+
*/
|
|
1812
|
+
isInitialized() {
|
|
1813
|
+
return this.initialized;
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Get the project directory this adapter was initialized with.
|
|
1817
|
+
*/
|
|
1818
|
+
getProjectDir() {
|
|
1819
|
+
return this.projectDir;
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
// packages/adapters/src/providers/cursor/index.js
|
|
1826
|
+
var cursor_exports = {};
|
|
1827
|
+
__export(cursor_exports, {
|
|
1828
|
+
CursorAdapter: () => CursorAdapter,
|
|
1829
|
+
CursorHookProvider: () => CursorHookProvider,
|
|
1830
|
+
CursorInstallProvider: () => CursorInstallProvider,
|
|
1831
|
+
createAdapter: () => createAdapter3,
|
|
1832
|
+
default: () => cursor_default
|
|
1833
|
+
});
|
|
1834
|
+
function createAdapter3() {
|
|
1835
|
+
return new CursorAdapter();
|
|
1836
|
+
}
|
|
1837
|
+
var cursor_default;
|
|
1838
|
+
var init_cursor = __esm({
|
|
1839
|
+
"packages/adapters/src/providers/cursor/index.js"() {
|
|
1840
|
+
"use strict";
|
|
1841
|
+
init_adapter3();
|
|
1842
|
+
init_adapter3();
|
|
1843
|
+
init_hooks3();
|
|
1844
|
+
init_install3();
|
|
1845
|
+
cursor_default = CursorAdapter;
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
// packages/adapters/src/registry.js
|
|
1850
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1851
|
+
import { dirname as dirname2, join as join12, resolve } from "node:path";
|
|
1852
|
+
import { fileURLToPath } from "node:url";
|
|
1853
|
+
var PROVIDER_IDS = ["claude-code", "opencode", "cursor"];
|
|
1854
|
+
function getProviderManifests() {
|
|
1855
|
+
const manifests = [];
|
|
1856
|
+
const baseDir = resolve(dirname2(fileURLToPath(import.meta.url)), "providers");
|
|
1857
|
+
for (const providerId of PROVIDER_IDS) {
|
|
1858
|
+
try {
|
|
1859
|
+
const manifestPath = join12(baseDir, providerId, "manifest.json");
|
|
1860
|
+
const raw = readFileSync6(manifestPath, "utf-8");
|
|
1861
|
+
manifests.push(JSON.parse(raw));
|
|
1862
|
+
} catch {
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return manifests;
|
|
1866
|
+
}
|
|
1867
|
+
async function discoverProviders() {
|
|
1868
|
+
const providers = /* @__PURE__ */ new Map();
|
|
1869
|
+
providers.set("claude-code", async () => {
|
|
1870
|
+
const { ClaudeCodeAdapter: ClaudeCodeAdapter2 } = await Promise.resolve().then(() => (init_claude_code(), claude_code_exports));
|
|
1871
|
+
return new ClaudeCodeAdapter2();
|
|
1872
|
+
});
|
|
1873
|
+
providers.set("opencode", async () => {
|
|
1874
|
+
const { OpenCodeAdapter: OpenCodeAdapter2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
1875
|
+
return new OpenCodeAdapter2();
|
|
1876
|
+
});
|
|
1877
|
+
providers.set("cursor", async () => {
|
|
1878
|
+
const { CursorAdapter: CursorAdapter2 } = await Promise.resolve().then(() => (init_cursor(), cursor_exports));
|
|
1879
|
+
return new CursorAdapter2();
|
|
1880
|
+
});
|
|
1881
|
+
return providers;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// packages/adapters/src/index.ts
|
|
1885
|
+
init_claude_code();
|
|
1886
|
+
init_claude_code();
|
|
1887
|
+
init_claude_code();
|
|
1888
|
+
init_claude_code();
|
|
1889
|
+
init_claude_code();
|
|
1890
|
+
init_claude_code();
|
|
1891
|
+
init_claude_code();
|
|
1892
|
+
init_claude_code();
|
|
1893
|
+
init_opencode();
|
|
1894
|
+
init_opencode();
|
|
1895
|
+
init_opencode();
|
|
1896
|
+
init_opencode();
|
|
1897
|
+
init_cursor();
|
|
1898
|
+
init_cursor();
|
|
1899
|
+
init_cursor();
|
|
1900
|
+
init_claude_code();
|
|
1901
|
+
init_opencode();
|
|
1902
|
+
init_cursor();
|
|
1903
|
+
export {
|
|
1904
|
+
ClaudeCodeAdapter,
|
|
1905
|
+
ClaudeCodeContextMonitorProvider,
|
|
1906
|
+
ClaudeCodeHookProvider,
|
|
1907
|
+
ClaudeCodeInstallProvider,
|
|
1908
|
+
ClaudeCodePathProvider,
|
|
1909
|
+
ClaudeCodeSpawnProvider,
|
|
1910
|
+
ClaudeCodeTransportProvider,
|
|
1911
|
+
CursorAdapter,
|
|
1912
|
+
CursorHookProvider,
|
|
1913
|
+
CursorInstallProvider,
|
|
1914
|
+
OpenCodeAdapter,
|
|
1915
|
+
OpenCodeHookProvider,
|
|
1916
|
+
OpenCodeInstallProvider,
|
|
1917
|
+
OpenCodeSpawnProvider,
|
|
1918
|
+
checkStatuslineIntegration,
|
|
1919
|
+
createAdapter as createClaudeCodeAdapter,
|
|
1920
|
+
createAdapter3 as createCursorAdapter,
|
|
1921
|
+
createAdapter2 as createOpenCodeAdapter,
|
|
1922
|
+
discoverProviders,
|
|
1923
|
+
getProviderManifests,
|
|
1924
|
+
getSetupInstructions,
|
|
1925
|
+
getStatuslineConfig
|
|
1926
|
+
};
|
|
1927
|
+
//# sourceMappingURL=index.js.map
|