clay-server 2.31.0 → 2.32.0-beta.10
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/lib/browser-mcp-server.js +32 -44
- package/lib/codex-defaults.js +18 -0
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +9 -6
- package/lib/project-debate.js +8 -0
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mate-interaction.js +102 -16
- package/lib/project-mcp.js +4 -0
- package/lib/project-notifications.js +9 -0
- package/lib/project-sessions.js +94 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +234 -99
- package/lib/public/app.js +135 -454
- package/lib/public/codex-avatar.png +0 -0
- package/lib/public/css/debate.css +3 -2
- package/lib/public/css/filebrowser.css +91 -1
- package/lib/public/css/icon-strip.css +21 -5
- package/lib/public/css/input.css +338 -104
- package/lib/public/css/mates.css +43 -0
- package/lib/public/css/mention.css +48 -4
- package/lib/public/css/menus.css +1 -1
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/notifications-center.css +26 -0
- package/lib/public/css/tooltip.css +47 -0
- package/lib/public/index.html +78 -26
- package/lib/public/modules/app-connection.js +138 -37
- package/lib/public/modules/app-cursors.js +18 -17
- package/lib/public/modules/app-debate-ui.js +9 -9
- package/lib/public/modules/app-dm.js +175 -131
- package/lib/public/modules/app-favicon.js +28 -26
- package/lib/public/modules/app-header.js +79 -68
- package/lib/public/modules/app-home-hub.js +55 -47
- package/lib/public/modules/app-loop-ui.js +34 -18
- package/lib/public/modules/app-loop-wizard.js +6 -6
- package/lib/public/modules/app-messages.js +199 -153
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +119 -9
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +161 -150
- package/lib/public/modules/app-rate-limit.js +5 -4
- package/lib/public/modules/app-rendering.js +149 -101
- package/lib/public/modules/app-skills-install.js +4 -4
- package/lib/public/modules/context-sources.js +102 -66
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +122 -0
- package/lib/public/modules/markdown.js +5 -1
- package/lib/public/modules/mate-sidebar.js +38 -0
- package/lib/public/modules/mention.js +24 -6
- package/lib/public/modules/scheduler.js +1 -1
- package/lib/public/modules/sidebar-mates.js +79 -35
- package/lib/public/modules/sidebar-mobile.js +34 -30
- package/lib/public/modules/sidebar-projects.js +60 -57
- package/lib/public/modules/sidebar-sessions.js +75 -69
- package/lib/public/modules/sidebar.js +12 -20
- package/lib/public/modules/skills.js +8 -9
- package/lib/public/modules/sticky-notes.js +1 -2
- package/lib/public/modules/store.js +9 -2
- package/lib/public/modules/stt.js +4 -1
- package/lib/public/modules/terminal.js +12 -0
- package/lib/public/modules/tools.js +18 -13
- package/lib/public/modules/tooltip.js +32 -5
- package/lib/sdk-bridge.js +562 -1114
- package/lib/sdk-message-processor.js +150 -135
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +81 -37
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1483 -0
- package/lib/yoke/adapters/codex.js +1121 -0
- package/lib/yoke/adapters/gemini.js +709 -0
- package/lib/yoke/codex-app-server.js +307 -0
- package/lib/yoke/index.js +199 -0
- package/lib/yoke/instructions.js +62 -0
- package/lib/yoke/interface.js +98 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- package/package.json +3 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// Codex App-Server Protocol Client
|
|
2
|
+
// ---------------------------------
|
|
3
|
+
// Manages a codex app-server child process with bidirectional JSON-RPC
|
|
4
|
+
// communication over stdin/stdout. Replaces the SDK exec mode to enable
|
|
5
|
+
// interactive approval flows.
|
|
6
|
+
|
|
7
|
+
var { spawn } = require("child_process");
|
|
8
|
+
var readline = require("readline");
|
|
9
|
+
var path = require("path");
|
|
10
|
+
var { createRequire } = require("module");
|
|
11
|
+
|
|
12
|
+
// --- Find the codex binary path ---
|
|
13
|
+
// Mirrors the logic from @openai/codex-sdk findCodexPath()
|
|
14
|
+
|
|
15
|
+
var PLATFORM_PACKAGE_BY_TARGET = {
|
|
16
|
+
"aarch64-apple-darwin": "@openai/codex-darwin-arm64",
|
|
17
|
+
"x86_64-apple-darwin": "@openai/codex-darwin-x64",
|
|
18
|
+
"aarch64-unknown-linux-gnu": "@openai/codex-linux-arm64",
|
|
19
|
+
"x86_64-unknown-linux-gnu": "@openai/codex-linux-x64",
|
|
20
|
+
"x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getTargetTriple() {
|
|
24
|
+
var arch = process.arch;
|
|
25
|
+
var platform = process.platform;
|
|
26
|
+
if (platform === "darwin") {
|
|
27
|
+
return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
|
|
28
|
+
}
|
|
29
|
+
if (platform === "linux") {
|
|
30
|
+
return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu";
|
|
31
|
+
}
|
|
32
|
+
if (platform === "win32") {
|
|
33
|
+
return "x86_64-pc-windows-msvc";
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findCodexPath() {
|
|
39
|
+
var triple = getTargetTriple();
|
|
40
|
+
if (!triple) throw new Error("Unsupported platform: " + process.platform + "/" + process.arch);
|
|
41
|
+
|
|
42
|
+
var platformPkg = PLATFORM_PACKAGE_BY_TARGET[triple];
|
|
43
|
+
if (!platformPkg) throw new Error("No codex binary package for: " + triple);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
var codexPkgJson = require.resolve("@openai/codex/package.json");
|
|
47
|
+
var codexRequire = createRequire(codexPkgJson);
|
|
48
|
+
var platformPkgJson = codexRequire.resolve(platformPkg + "/package.json");
|
|
49
|
+
var vendorRoot = path.join(path.dirname(platformPkgJson), "vendor");
|
|
50
|
+
var binaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
51
|
+
return path.join(vendorRoot, triple, "codex", binaryName);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new Error("Could not find codex binary: " + e.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- Config serialization ---
|
|
58
|
+
// Flattens a nested config object into --config key=value pairs.
|
|
59
|
+
// Values are serialized as TOML literals (strings quoted, others raw).
|
|
60
|
+
// e.g. { mcp_servers: { "clay-tools": { command: "node", args: ["a.js"] } } }
|
|
61
|
+
// -> ["mcp_servers.clay-tools.command=\"node\"", "mcp_servers.clay-tools.args=[\"a.js\"]"]
|
|
62
|
+
|
|
63
|
+
function serializeConfig(obj, prefix) {
|
|
64
|
+
var result = [];
|
|
65
|
+
var keys = Object.keys(obj);
|
|
66
|
+
for (var i = 0; i < keys.length; i++) {
|
|
67
|
+
var key = keys[i];
|
|
68
|
+
var val = obj[key];
|
|
69
|
+
var fullKey = prefix ? prefix + "." + key : key;
|
|
70
|
+
|
|
71
|
+
if (val === null || val === undefined) continue;
|
|
72
|
+
|
|
73
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
74
|
+
// Recurse for nested objects
|
|
75
|
+
var nested = serializeConfig(val, fullKey);
|
|
76
|
+
for (var j = 0; j < nested.length; j++) {
|
|
77
|
+
result.push(nested[j]);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// Leaf value: serialize as TOML
|
|
81
|
+
result.push(fullKey + "=" + toTomlValue(val));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toTomlValue(val) {
|
|
88
|
+
if (typeof val === "string") return JSON.stringify(val);
|
|
89
|
+
if (typeof val === "boolean") return val ? "true" : "false";
|
|
90
|
+
if (typeof val === "number") return String(val);
|
|
91
|
+
if (Array.isArray(val)) return "[" + val.map(function(v) { return toTomlValue(v); }).join(", ") + "]";
|
|
92
|
+
return JSON.stringify(val);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- CodexAppServer ---
|
|
96
|
+
|
|
97
|
+
function CodexAppServer(executablePath, opts) {
|
|
98
|
+
this.proc = null;
|
|
99
|
+
this.rl = null;
|
|
100
|
+
this.nextId = 1;
|
|
101
|
+
this.pendingRequests = {}; // id -> { resolve, reject, timer }
|
|
102
|
+
this.eventHandler = null; // function(notification) for server-initiated events
|
|
103
|
+
this.executablePath = executablePath || findCodexPath();
|
|
104
|
+
this.opts = opts || {};
|
|
105
|
+
this.started = false;
|
|
106
|
+
this._stderrBuf = "";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
CodexAppServer.prototype.start = function() {
|
|
110
|
+
var self = this;
|
|
111
|
+
|
|
112
|
+
return new Promise(function(resolve, reject) {
|
|
113
|
+
try {
|
|
114
|
+
var args = ["app-server"];
|
|
115
|
+
var env = Object.assign({}, process.env, self.opts.env || {});
|
|
116
|
+
|
|
117
|
+
// Pass config overrides via --config key=value flags
|
|
118
|
+
if (self.opts.config) {
|
|
119
|
+
var configArgs = serializeConfig(self.opts.config, "");
|
|
120
|
+
for (var ci = 0; ci < configArgs.length; ci++) {
|
|
121
|
+
args.push("--config", configArgs[ci]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log("[codex-app-server] Spawning:", self.executablePath, args.join(" "));
|
|
126
|
+
|
|
127
|
+
self.proc = spawn(self.executablePath, args, {
|
|
128
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
129
|
+
env: env,
|
|
130
|
+
cwd: self.opts.cwd || process.cwd(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
self.proc.on("error", function(err) {
|
|
134
|
+
console.error("[codex-app-server] Process error:", err.message);
|
|
135
|
+
if (!self.started) {
|
|
136
|
+
reject(err);
|
|
137
|
+
}
|
|
138
|
+
self._rejectAllPending(err);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
self.proc.on("exit", function(code, signal) {
|
|
142
|
+
console.log("[codex-app-server] Process exited: code=" + code + " signal=" + signal);
|
|
143
|
+
self.started = false;
|
|
144
|
+
self._rejectAllPending(new Error("Process exited: code=" + code));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Collect stderr for debugging
|
|
148
|
+
self.proc.stderr.on("data", function(chunk) {
|
|
149
|
+
var text = chunk.toString();
|
|
150
|
+
self._stderrBuf += text;
|
|
151
|
+
// Print stderr lines as they come
|
|
152
|
+
var lines = self._stderrBuf.split("\n");
|
|
153
|
+
while (lines.length > 1) {
|
|
154
|
+
var line = lines.shift();
|
|
155
|
+
if (line.trim()) console.log("[codex-app-server stderr]", line);
|
|
156
|
+
}
|
|
157
|
+
self._stderrBuf = lines[0] || "";
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Set up line-based JSON-RPC reading from stdout
|
|
161
|
+
self.rl = readline.createInterface({
|
|
162
|
+
input: self.proc.stdout,
|
|
163
|
+
crlfDelay: Infinity,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
self.rl.on("line", function(line) {
|
|
167
|
+
if (!line.trim()) return;
|
|
168
|
+
try {
|
|
169
|
+
var msg = JSON.parse(line);
|
|
170
|
+
self._handleMessage(msg);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error("[codex-app-server] Failed to parse line:", line.substring(0, 200));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
self.rl.on("close", function() {
|
|
177
|
+
console.log("[codex-app-server] stdout closed");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
self.started = true;
|
|
181
|
+
resolve();
|
|
182
|
+
} catch (e) {
|
|
183
|
+
reject(e);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
CodexAppServer.prototype._handleMessage = function(msg) {
|
|
189
|
+
// Response to a request we sent
|
|
190
|
+
if (msg.id !== undefined && msg.id !== null && (msg.result !== undefined || msg.error !== undefined)) {
|
|
191
|
+
var pending = this.pendingRequests[msg.id];
|
|
192
|
+
if (pending) {
|
|
193
|
+
delete this.pendingRequests[msg.id];
|
|
194
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
195
|
+
if (msg.error) {
|
|
196
|
+
pending.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
197
|
+
} else {
|
|
198
|
+
pending.resolve(msg.result);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Server-initiated request (has id + method) or notification (has method, no id)
|
|
205
|
+
if (msg.method) {
|
|
206
|
+
if (this.eventHandler) {
|
|
207
|
+
this.eventHandler(msg);
|
|
208
|
+
} else {
|
|
209
|
+
console.log("[codex-app-server] Unhandled event:", msg.method);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Send a JSON-RPC request (expects a response)
|
|
215
|
+
CodexAppServer.prototype.send = function(method, params, timeoutMs) {
|
|
216
|
+
var self = this;
|
|
217
|
+
var id = this.nextId++;
|
|
218
|
+
timeoutMs = timeoutMs || 30000;
|
|
219
|
+
|
|
220
|
+
return new Promise(function(resolve, reject) {
|
|
221
|
+
if (!self.proc || !self.started) {
|
|
222
|
+
return reject(new Error("App-server not started"));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
var timer = setTimeout(function() {
|
|
226
|
+
delete self.pendingRequests[id];
|
|
227
|
+
reject(new Error("Request timeout: " + method + " (id=" + id + ")"));
|
|
228
|
+
}, timeoutMs);
|
|
229
|
+
|
|
230
|
+
self.pendingRequests[id] = { resolve: resolve, reject: reject, timer: timer };
|
|
231
|
+
|
|
232
|
+
var msg = { jsonrpc: "2.0", id: id, method: method };
|
|
233
|
+
if (params !== undefined) msg.params = params;
|
|
234
|
+
|
|
235
|
+
self._write(msg);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Send a JSON-RPC notification (no response expected)
|
|
240
|
+
CodexAppServer.prototype.notify = function(method, params) {
|
|
241
|
+
if (!this.proc || !this.started) return;
|
|
242
|
+
|
|
243
|
+
var msg = { jsonrpc: "2.0", method: method };
|
|
244
|
+
if (params !== undefined) msg.params = params;
|
|
245
|
+
|
|
246
|
+
this._write(msg);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Respond to a server-initiated request
|
|
250
|
+
CodexAppServer.prototype.respond = function(id, result) {
|
|
251
|
+
if (!this.proc || !this.started) return;
|
|
252
|
+
|
|
253
|
+
var msg = { jsonrpc: "2.0", id: id, result: result };
|
|
254
|
+
this._write(msg);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Respond with an error to a server-initiated request
|
|
258
|
+
CodexAppServer.prototype.respondError = function(id, code, message) {
|
|
259
|
+
if (!this.proc || !this.started) return;
|
|
260
|
+
|
|
261
|
+
var msg = { jsonrpc: "2.0", id: id, error: { code: code || -1, message: message || "Error" } };
|
|
262
|
+
this._write(msg);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
CodexAppServer.prototype._write = function(msg) {
|
|
266
|
+
if (!this.proc || !this.proc.stdin || this.proc.stdin.destroyed) return;
|
|
267
|
+
try {
|
|
268
|
+
this.proc.stdin.write(JSON.stringify(msg) + "\n");
|
|
269
|
+
} catch (e) {
|
|
270
|
+
console.error("[codex-app-server] Write error:", e.message);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
CodexAppServer.prototype._rejectAllPending = function(err) {
|
|
275
|
+
var ids = Object.keys(this.pendingRequests);
|
|
276
|
+
for (var i = 0; i < ids.length; i++) {
|
|
277
|
+
var pending = this.pendingRequests[ids[i]];
|
|
278
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
279
|
+
pending.reject(err);
|
|
280
|
+
}
|
|
281
|
+
this.pendingRequests = {};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
CodexAppServer.prototype.stop = function() {
|
|
285
|
+
this.started = false;
|
|
286
|
+
this._rejectAllPending(new Error("Stopped"));
|
|
287
|
+
|
|
288
|
+
if (this.rl) {
|
|
289
|
+
this.rl.close();
|
|
290
|
+
this.rl = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (this.proc) {
|
|
294
|
+
try {
|
|
295
|
+
this.proc.stdin.end();
|
|
296
|
+
} catch (e) {}
|
|
297
|
+
try {
|
|
298
|
+
this.proc.kill("SIGTERM");
|
|
299
|
+
} catch (e) {}
|
|
300
|
+
this.proc = null;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
module.exports = {
|
|
305
|
+
CodexAppServer: CodexAppServer,
|
|
306
|
+
findCodexPath: findCodexPath,
|
|
307
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// YOKE - Yoke Overrides Known Engines
|
|
2
|
+
// Public entry point.
|
|
3
|
+
|
|
4
|
+
var iface = require("./interface");
|
|
5
|
+
var instructions = require("./instructions");
|
|
6
|
+
var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
|
|
7
|
+
var createGeminiAdapter = require("./adapters/gemini").createGeminiAdapter;
|
|
8
|
+
var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Wrap adapter.createQuery to inject cross-vendor project instructions.
|
|
12
|
+
*
|
|
13
|
+
* Scans the project directory for instruction files (CLAUDE.md, AGENTS.md,
|
|
14
|
+
* .cursorrules, etc.) that the current vendor does NOT natively read,
|
|
15
|
+
* and merges them into queryOpts.systemPrompt before calling the real
|
|
16
|
+
* createQuery. This way every adapter gets project context regardless
|
|
17
|
+
* of which vendor wrote the instruction file.
|
|
18
|
+
*/
|
|
19
|
+
function wrapCreateQuery(adapter, defaultCwd) {
|
|
20
|
+
var originalCreateQuery = adapter.createQuery.bind(adapter);
|
|
21
|
+
|
|
22
|
+
adapter.createQuery = function(queryOpts) {
|
|
23
|
+
var projectDir = (queryOpts && queryOpts.cwd) || defaultCwd;
|
|
24
|
+
var merged = instructions.scanAndMerge(projectDir, adapter.vendor);
|
|
25
|
+
|
|
26
|
+
if (merged) {
|
|
27
|
+
var parts = [];
|
|
28
|
+
if (queryOpts.systemPrompt) parts.push(queryOpts.systemPrompt);
|
|
29
|
+
parts.push(merged);
|
|
30
|
+
queryOpts.systemPrompt = parts.join("\n\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return originalCreateQuery(queryOpts);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a YOKE adapter.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {string} [opts.vendor="claude"] - Adapter vendor name
|
|
42
|
+
* @param {string} opts.cwd - Project working directory
|
|
43
|
+
* @param {object} [opts.adapterOpts] - Vendor-specific adapter construction options
|
|
44
|
+
* @returns {Adapter}
|
|
45
|
+
*/
|
|
46
|
+
function createAdapter(opts) {
|
|
47
|
+
var vendor = (opts && opts.vendor) || "claude";
|
|
48
|
+
var adapter;
|
|
49
|
+
if (vendor === "claude") {
|
|
50
|
+
adapter = createClaudeAdapter(opts);
|
|
51
|
+
} else if (vendor === "gemini") {
|
|
52
|
+
adapter = createGeminiAdapter(opts);
|
|
53
|
+
} else if (vendor === "codex") {
|
|
54
|
+
adapter = createCodexAdapter(opts);
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error("[YOKE] Unknown adapter vendor: " + vendor);
|
|
57
|
+
}
|
|
58
|
+
iface.validateAdapter(adapter);
|
|
59
|
+
wrapCreateQuery(adapter, opts.cwd);
|
|
60
|
+
return adapter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check which vendors have valid auth credentials.
|
|
65
|
+
* Result is cached after first call (auth state doesn't change during runtime).
|
|
66
|
+
* Call invalidateAuthCache() to force re-check (e.g. after login).
|
|
67
|
+
*/
|
|
68
|
+
var _authCache = null;
|
|
69
|
+
|
|
70
|
+
function checkAuth() {
|
|
71
|
+
if (_authCache) return _authCache;
|
|
72
|
+
|
|
73
|
+
var execSync = require("child_process").execSync;
|
|
74
|
+
|
|
75
|
+
function checkClaude() {
|
|
76
|
+
try {
|
|
77
|
+
var out = execSync("claude auth status", { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
78
|
+
var parsed = JSON.parse(out);
|
|
79
|
+
return !!(parsed && parsed.loggedIn);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function checkCodex() {
|
|
86
|
+
try {
|
|
87
|
+
var path = require("path");
|
|
88
|
+
var codexBin = path.join(__dirname, "../../node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex/codex");
|
|
89
|
+
execSync(codexBin + " login status", { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
90
|
+
return true;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_authCache = { claude: checkClaude(), codex: checkCodex(), gemini: false };
|
|
97
|
+
console.log("[yoke] Auth check: claude=" + _authCache.claude + " codex=" + _authCache.codex);
|
|
98
|
+
return _authCache;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check which vendor binaries are installed (regardless of auth status).
|
|
103
|
+
*/
|
|
104
|
+
function checkInstalled() {
|
|
105
|
+
var fs = require("fs");
|
|
106
|
+
var path = require("path");
|
|
107
|
+
var execSync = require("child_process").execSync;
|
|
108
|
+
var result = { claude: false, codex: false };
|
|
109
|
+
try {
|
|
110
|
+
execSync("which claude", { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
111
|
+
result.claude = true;
|
|
112
|
+
} catch (e) {}
|
|
113
|
+
try {
|
|
114
|
+
var codexBin = path.join(__dirname, "../../node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex/codex");
|
|
115
|
+
if (fs.existsSync(codexBin)) result.codex = true;
|
|
116
|
+
} catch (e) {}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function invalidateAuthCache() {
|
|
121
|
+
_authCache = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create adapters for all authenticated vendors.
|
|
126
|
+
* Adapters are singletons shared across all projects (they are stateless factories).
|
|
127
|
+
* The cwd is passed per-query, not per-adapter.
|
|
128
|
+
* Returns { adapters: { vendor: Adapter }, auth: { vendor: boolean } }
|
|
129
|
+
*/
|
|
130
|
+
var _sharedAdapters = null;
|
|
131
|
+
var _sharedAuth = null;
|
|
132
|
+
|
|
133
|
+
function createAdapters(opts) {
|
|
134
|
+
if (_sharedAdapters) {
|
|
135
|
+
return { adapters: _sharedAdapters, auth: _sharedAuth };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var auth = checkAuth();
|
|
139
|
+
var adapters = {};
|
|
140
|
+
var vendors = Object.keys(auth);
|
|
141
|
+
|
|
142
|
+
for (var i = 0; i < vendors.length; i++) {
|
|
143
|
+
var vendor = vendors[i];
|
|
144
|
+
if (!auth[vendor]) continue;
|
|
145
|
+
try {
|
|
146
|
+
adapters[vendor] = createAdapter({ vendor: vendor, cwd: opts.cwd });
|
|
147
|
+
console.log("[yoke] Adapter created: " + vendor);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error("[yoke] Failed to create adapter for " + vendor + ":", e.message);
|
|
150
|
+
auth[vendor] = false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_sharedAdapters = adapters;
|
|
155
|
+
_sharedAuth = auth;
|
|
156
|
+
return { adapters: adapters, auth: auth };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Lazy-create an adapter for a vendor that wasn't available at startup.
|
|
161
|
+
* Re-checks auth, creates adapter if now logged in.
|
|
162
|
+
* Returns the adapter or null.
|
|
163
|
+
*/
|
|
164
|
+
function lazyCreateAdapter(adapters, vendor, opts) {
|
|
165
|
+
if (_sharedAdapters && _sharedAdapters[vendor]) {
|
|
166
|
+
adapters[vendor] = _sharedAdapters[vendor];
|
|
167
|
+
return adapters[vendor];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Force re-check since user may have logged in after server start
|
|
171
|
+
invalidateAuthCache();
|
|
172
|
+
_sharedAdapters = null;
|
|
173
|
+
_sharedAuth = null;
|
|
174
|
+
var auth = checkAuth();
|
|
175
|
+
if (!auth[vendor]) return null;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
var ad = createAdapter({ vendor: vendor, cwd: opts.cwd });
|
|
179
|
+
console.log("[yoke] Lazy adapter created: " + vendor);
|
|
180
|
+
if (_sharedAdapters) _sharedAdapters[vendor] = ad;
|
|
181
|
+
adapters[vendor] = ad;
|
|
182
|
+
return ad;
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error("[yoke] Failed to lazy-create adapter for " + vendor + ":", e.message);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
createAdapter: createAdapter,
|
|
191
|
+
createAdapters: createAdapters,
|
|
192
|
+
lazyCreateAdapter: lazyCreateAdapter,
|
|
193
|
+
checkAuth: checkAuth,
|
|
194
|
+
checkInstalled: checkInstalled,
|
|
195
|
+
invalidateAuthCache: invalidateAuthCache,
|
|
196
|
+
TOOL_POLICIES: iface.TOOL_POLICIES,
|
|
197
|
+
validateAdapter: iface.validateAdapter,
|
|
198
|
+
validateQueryHandle: iface.validateQueryHandle,
|
|
199
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// YOKE Instruction Scanner
|
|
2
|
+
// ------------------------
|
|
3
|
+
// Scans a project directory for vendor-specific instruction files
|
|
4
|
+
// (CLAUDE.md, AGENTS.md, .cursorrules, etc.) and merges them into
|
|
5
|
+
// a single string that any adapter can inject as context.
|
|
6
|
+
//
|
|
7
|
+
// Each adapter declares which files its vendor reads natively so
|
|
8
|
+
// those are excluded from the merged output (no double-injection).
|
|
9
|
+
|
|
10
|
+
var fs = require("fs");
|
|
11
|
+
var path = require("path");
|
|
12
|
+
|
|
13
|
+
// Known instruction files in priority order.
|
|
14
|
+
// { file: relative path, label: human-readable label }
|
|
15
|
+
var KNOWN_FILES = [
|
|
16
|
+
{ file: "CLAUDE.md", label: "CLAUDE.md" },
|
|
17
|
+
{ file: "AGENTS.md", label: "AGENTS.md" },
|
|
18
|
+
{ file: ".cursorrules", label: ".cursorrules" },
|
|
19
|
+
{ file: ".github/copilot-instructions.md", label: ".github/copilot-instructions.md" },
|
|
20
|
+
{ file: "COPILOT.md", label: "COPILOT.md" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Files each vendor reads natively (skip these to avoid duplication).
|
|
24
|
+
var NATIVE_FILES = {
|
|
25
|
+
claude: ["CLAUDE.md"],
|
|
26
|
+
codex: ["AGENTS.md"],
|
|
27
|
+
gemini: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Scan projectDir for instruction files and return merged text.
|
|
31
|
+
// Excludes files the given vendor already reads natively.
|
|
32
|
+
//
|
|
33
|
+
// Returns "" if no files found (callers can skip injection).
|
|
34
|
+
function scanAndMerge(projectDir, vendor) {
|
|
35
|
+
if (!projectDir) return "";
|
|
36
|
+
|
|
37
|
+
var exclude = NATIVE_FILES[vendor] || [];
|
|
38
|
+
var sections = [];
|
|
39
|
+
|
|
40
|
+
for (var i = 0; i < KNOWN_FILES.length; i++) {
|
|
41
|
+
var entry = KNOWN_FILES[i];
|
|
42
|
+
if (exclude.indexOf(entry.file) !== -1) continue;
|
|
43
|
+
|
|
44
|
+
var filePath = path.join(projectDir, entry.file);
|
|
45
|
+
try {
|
|
46
|
+
var content = fs.readFileSync(filePath, "utf8").trim();
|
|
47
|
+
if (content) {
|
|
48
|
+
sections.push("--- Instructions from " + entry.label + " ---\n" + content);
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// File doesn't exist or unreadable, skip.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return sections.join("\n\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
scanAndMerge: scanAndMerge,
|
|
60
|
+
KNOWN_FILES: KNOWN_FILES,
|
|
61
|
+
NATIVE_FILES: NATIVE_FILES,
|
|
62
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// YOKE Interface Definition
|
|
2
|
+
// -------------------------
|
|
3
|
+
// This file defines the contract that every adapter must implement.
|
|
4
|
+
// It does NOT contain runtime logic; it is the authoritative reference
|
|
5
|
+
// for Phase 3 and beyond.
|
|
6
|
+
//
|
|
7
|
+
// Adapter objects must satisfy two shapes:
|
|
8
|
+
// 1. Adapter (returned by createAdapter)
|
|
9
|
+
// 2. QueryHandle (returned by adapter.createQuery)
|
|
10
|
+
|
|
11
|
+
var TOOL_POLICIES = ["ask", "allow-all"];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate that an adapter object implements all required methods.
|
|
15
|
+
* Throws if any are missing. Development-time safety net only.
|
|
16
|
+
*
|
|
17
|
+
* Adapter shape:
|
|
18
|
+
* .vendor : string - e.g. "claude", "opencode", "codex"
|
|
19
|
+
* .init(opts) : Promise<InitResult>
|
|
20
|
+
* .supportedModels(): Promise<string[]>
|
|
21
|
+
* .createToolServer(def): ToolServer (opaque)
|
|
22
|
+
* .createQuery(opts): QueryHandle
|
|
23
|
+
*
|
|
24
|
+
* Lightweight utilities:
|
|
25
|
+
* .generateTitle(messages, opts) : Promise<string> - generate a short session title
|
|
26
|
+
* messages: string[] - user messages to derive the title from
|
|
27
|
+
* opts: { cwd }
|
|
28
|
+
* Returns a short (3-8 word) title string.
|
|
29
|
+
*
|
|
30
|
+
* Additional session management (Claude SDK specific, may vary per adapter):
|
|
31
|
+
* .getSessionInfo(sessionId, opts): Promise<object|null>
|
|
32
|
+
* .listSessions(opts) : Promise<Array>
|
|
33
|
+
* .renameSession(sessionId, title, opts): Promise
|
|
34
|
+
* .forkSession(sessionId, opts) : Promise<object>
|
|
35
|
+
*
|
|
36
|
+
* QueryHandle shape:
|
|
37
|
+
* [Symbol.asyncIterator]() - yields SDK events (raw in Phase 3, normalized later)
|
|
38
|
+
* .pushMessage(text, images)
|
|
39
|
+
* .setModel(model)
|
|
40
|
+
* .setEffort(effort)
|
|
41
|
+
* .setToolPolicy(policy) - "ask" | "allow-all"
|
|
42
|
+
* .stopTask(taskId)
|
|
43
|
+
* .getContextUsage() - Promise<object|null>
|
|
44
|
+
* .abort()
|
|
45
|
+
* .close()
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
var ADAPTER_METHODS = [
|
|
49
|
+
"init",
|
|
50
|
+
"supportedModels",
|
|
51
|
+
"createToolServer",
|
|
52
|
+
"createQuery",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
var QUERY_HANDLE_METHODS = [
|
|
56
|
+
"pushMessage",
|
|
57
|
+
"setModel",
|
|
58
|
+
"setEffort",
|
|
59
|
+
"setToolPolicy",
|
|
60
|
+
"stopTask",
|
|
61
|
+
"getContextUsage",
|
|
62
|
+
"abort",
|
|
63
|
+
"close",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
function validateAdapter(adapter) {
|
|
67
|
+
if (!adapter) throw new Error("[YOKE] Adapter is null or undefined");
|
|
68
|
+
if (typeof adapter.vendor !== "string" || !adapter.vendor) {
|
|
69
|
+
throw new Error("[YOKE] Adapter must have a non-empty 'vendor' string property");
|
|
70
|
+
}
|
|
71
|
+
for (var i = 0; i < ADAPTER_METHODS.length; i++) {
|
|
72
|
+
var m = ADAPTER_METHODS[i];
|
|
73
|
+
if (typeof adapter[m] !== "function") {
|
|
74
|
+
throw new Error("[YOKE] Adapter '" + adapter.vendor + "' missing required method: " + m);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateQueryHandle(handle) {
|
|
80
|
+
if (!handle) throw new Error("[YOKE] QueryHandle is null or undefined");
|
|
81
|
+
if (typeof handle[Symbol.asyncIterator] !== "function") {
|
|
82
|
+
throw new Error("[YOKE] QueryHandle must implement Symbol.asyncIterator");
|
|
83
|
+
}
|
|
84
|
+
for (var i = 0; i < QUERY_HANDLE_METHODS.length; i++) {
|
|
85
|
+
var m = QUERY_HANDLE_METHODS[i];
|
|
86
|
+
if (typeof handle[m] !== "function") {
|
|
87
|
+
throw new Error("[YOKE] QueryHandle missing required method: " + m);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
TOOL_POLICIES: TOOL_POLICIES,
|
|
94
|
+
ADAPTER_METHODS: ADAPTER_METHODS,
|
|
95
|
+
QUERY_HANDLE_METHODS: QUERY_HANDLE_METHODS,
|
|
96
|
+
validateAdapter: validateAdapter,
|
|
97
|
+
validateQueryHandle: validateQueryHandle,
|
|
98
|
+
};
|