@jjlabsio/claude-crew 0.1.31 → 0.1.33
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -1
- package/README.md +11 -1
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/data/provider-catalog.json +36 -14
- package/package.json +2 -1
- package/scripts/crew-codex/LICENSE +201 -0
- package/scripts/crew-codex/NOTICE +16 -0
- package/scripts/crew-codex/app-server-broker.mjs +254 -0
- package/scripts/crew-codex/lib/app-server.mjs +352 -0
- package/scripts/crew-codex/lib/args.mjs +130 -0
- package/scripts/crew-codex/lib/broker-endpoint.mjs +43 -0
- package/scripts/crew-codex/lib/broker-lifecycle.mjs +213 -0
- package/scripts/crew-codex/lib/codex.mjs +1090 -0
- package/scripts/crew-codex/lib/fs.mjs +42 -0
- package/scripts/crew-codex/lib/git.mjs +348 -0
- package/scripts/crew-codex/lib/job-control.mjs +310 -0
- package/scripts/crew-codex/lib/process.mjs +137 -0
- package/scripts/crew-codex/lib/prompts.mjs +15 -0
- package/scripts/crew-codex/lib/render.mjs +466 -0
- package/scripts/crew-codex/lib/state.mjs +424 -0
- package/scripts/crew-codex/lib/tracked-jobs.mjs +279 -0
- package/scripts/crew-codex/lib/workspace.mjs +11 -0
- package/scripts/crew-codex-companion.mjs +863 -0
- package/skills/crew-dev/SKILL.md +65 -15
- package/skills/crew-interview/SKILL.md +61 -9
- package/skills/crew-plan/SKILL.md +106 -26
- package/skills/crew-setup/SKILL.md +38 -34
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Derived from @openai/codex-plugin-cc and modified for claude-crew.
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Error & { data?: unknown, rpcCode?: number }} ProtocolError
|
|
5
|
+
* @typedef {import("./app-server-protocol").AppServerMethod} AppServerMethod
|
|
6
|
+
* @typedef {import("./app-server-protocol").AppServerNotification} AppServerNotification
|
|
7
|
+
* @typedef {import("./app-server-protocol").AppServerNotificationHandler} AppServerNotificationHandler
|
|
8
|
+
* @typedef {import("./app-server-protocol").ClientInfo} ClientInfo
|
|
9
|
+
* @typedef {import("./app-server-protocol").CodexAppServerClientOptions} CodexAppServerClientOptions
|
|
10
|
+
* @typedef {import("./app-server-protocol").InitializeCapabilities} InitializeCapabilities
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import net from "node:net";
|
|
14
|
+
import process from "node:process";
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import readline from "node:readline";
|
|
17
|
+
import { parseBrokerEndpoint } from "./broker-endpoint.mjs";
|
|
18
|
+
import { ensureBrokerSession, loadBrokerSession } from "./broker-lifecycle.mjs";
|
|
19
|
+
import { terminateProcessTree } from "./process.mjs";
|
|
20
|
+
|
|
21
|
+
const PLUGIN_MANIFEST_URL = new URL("../../../.claude-plugin/plugin.json", import.meta.url);
|
|
22
|
+
const PLUGIN_MANIFEST = JSON.parse(fs.readFileSync(PLUGIN_MANIFEST_URL, "utf8"));
|
|
23
|
+
|
|
24
|
+
export const BROKER_ENDPOINT_ENV = "CODEX_COMPANION_APP_SERVER_ENDPOINT";
|
|
25
|
+
export const BROKER_BUSY_RPC_CODE = -32001;
|
|
26
|
+
|
|
27
|
+
/** @type {ClientInfo} */
|
|
28
|
+
const DEFAULT_CLIENT_INFO = {
|
|
29
|
+
title: "Claude Crew Codex Runtime",
|
|
30
|
+
name: "claude-crew",
|
|
31
|
+
version: PLUGIN_MANIFEST.version ?? "0.0.0"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** @type {InitializeCapabilities} */
|
|
35
|
+
const DEFAULT_CAPABILITIES = {
|
|
36
|
+
experimentalApi: false,
|
|
37
|
+
optOutNotificationMethods: [
|
|
38
|
+
"item/agentMessage/delta",
|
|
39
|
+
"item/reasoning/summaryTextDelta",
|
|
40
|
+
"item/reasoning/summaryPartAdded",
|
|
41
|
+
"item/reasoning/textDelta"
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function buildJsonRpcError(code, message, data) {
|
|
46
|
+
return data === undefined ? { code, message } : { code, message, data };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createProtocolError(message, data) {
|
|
50
|
+
const error = /** @type {ProtocolError} */ (new Error(message));
|
|
51
|
+
error.data = data;
|
|
52
|
+
if (data?.code !== undefined) {
|
|
53
|
+
error.rpcCode = data.code;
|
|
54
|
+
}
|
|
55
|
+
return error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class AppServerClientBase {
|
|
59
|
+
constructor(cwd, options = {}) {
|
|
60
|
+
this.cwd = cwd;
|
|
61
|
+
this.options = options;
|
|
62
|
+
this.pending = new Map();
|
|
63
|
+
this.nextId = 1;
|
|
64
|
+
this.stderr = "";
|
|
65
|
+
this.closed = false;
|
|
66
|
+
this.exitError = null;
|
|
67
|
+
/** @type {AppServerNotificationHandler | null} */
|
|
68
|
+
this.notificationHandler = null;
|
|
69
|
+
this.lineBuffer = "";
|
|
70
|
+
this.transport = "unknown";
|
|
71
|
+
|
|
72
|
+
this.exitPromise = new Promise((resolve) => {
|
|
73
|
+
this.resolveExit = resolve;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setNotificationHandler(handler) {
|
|
78
|
+
this.notificationHandler = handler;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @template {AppServerMethod} M
|
|
83
|
+
* @param {M} method
|
|
84
|
+
* @param {import("./app-server-protocol").AppServerRequestParams<M>} params
|
|
85
|
+
* @returns {Promise<import("./app-server-protocol").AppServerResponse<M>>}
|
|
86
|
+
*/
|
|
87
|
+
request(method, params) {
|
|
88
|
+
if (this.closed) {
|
|
89
|
+
throw new Error("codex app-server client is closed.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const id = this.nextId;
|
|
93
|
+
this.nextId += 1;
|
|
94
|
+
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
this.pending.set(id, { resolve, reject, method });
|
|
97
|
+
this.sendMessage({ id, method, params });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
notify(method, params = {}) {
|
|
102
|
+
if (this.closed) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.sendMessage({ method, params });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
handleChunk(chunk) {
|
|
109
|
+
this.lineBuffer += chunk;
|
|
110
|
+
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
111
|
+
while (newlineIndex !== -1) {
|
|
112
|
+
const line = this.lineBuffer.slice(0, newlineIndex);
|
|
113
|
+
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
114
|
+
this.handleLine(line);
|
|
115
|
+
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
handleLine(line) {
|
|
120
|
+
if (!line.trim()) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let message;
|
|
125
|
+
try {
|
|
126
|
+
message = JSON.parse(line);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.handleExit(createProtocolError(`Failed to parse codex app-server JSONL: ${error.message}`, { line }));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (message.id !== undefined && message.method) {
|
|
133
|
+
this.handleServerRequest(message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (message.id !== undefined) {
|
|
138
|
+
const pending = this.pending.get(message.id);
|
|
139
|
+
if (!pending) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.pending.delete(message.id);
|
|
143
|
+
|
|
144
|
+
if (message.error) {
|
|
145
|
+
pending.reject(createProtocolError(message.error.message ?? `codex app-server ${pending.method} failed.`, message.error));
|
|
146
|
+
} else {
|
|
147
|
+
pending.resolve(message.result ?? {});
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (message.method && this.notificationHandler) {
|
|
153
|
+
this.notificationHandler(/** @type {AppServerNotification} */ (message));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
handleServerRequest(message) {
|
|
158
|
+
this.sendMessage({
|
|
159
|
+
id: message.id,
|
|
160
|
+
error: buildJsonRpcError(-32601, `Unsupported server request: ${message.method}`)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
handleExit(error) {
|
|
165
|
+
if (this.exitResolved) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.exitResolved = true;
|
|
170
|
+
this.exitError = error ?? null;
|
|
171
|
+
|
|
172
|
+
for (const pending of this.pending.values()) {
|
|
173
|
+
pending.reject(this.exitError ?? new Error("codex app-server connection closed."));
|
|
174
|
+
}
|
|
175
|
+
this.pending.clear();
|
|
176
|
+
this.resolveExit(undefined);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sendMessage(_message) {
|
|
180
|
+
throw new Error("sendMessage must be implemented by subclasses.");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class SpawnedCodexAppServerClient extends AppServerClientBase {
|
|
185
|
+
constructor(cwd, options = {}) {
|
|
186
|
+
super(cwd, options);
|
|
187
|
+
this.transport = "direct";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async initialize() {
|
|
191
|
+
this.proc = spawn("codex", ["app-server"], {
|
|
192
|
+
cwd: this.cwd,
|
|
193
|
+
env: this.options.env ?? process.env,
|
|
194
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
195
|
+
shell: process.platform === "win32",
|
|
196
|
+
windowsHide: true
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.proc.stdout.setEncoding("utf8");
|
|
200
|
+
this.proc.stderr.setEncoding("utf8");
|
|
201
|
+
|
|
202
|
+
this.proc.stderr.on("data", (chunk) => {
|
|
203
|
+
this.stderr += chunk;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
this.proc.on("error", (error) => {
|
|
207
|
+
this.handleExit(error);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.proc.on("exit", (code, signal) => {
|
|
211
|
+
const detail =
|
|
212
|
+
code === 0
|
|
213
|
+
? null
|
|
214
|
+
: createProtocolError(`codex app-server exited unexpectedly (${signal ? `signal ${signal}` : `exit ${code}`}).`);
|
|
215
|
+
this.handleExit(detail);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.readline = readline.createInterface({ input: this.proc.stdout });
|
|
219
|
+
this.readline.on("line", (line) => {
|
|
220
|
+
this.handleLine(line);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await this.request("initialize", {
|
|
224
|
+
clientInfo: this.options.clientInfo ?? DEFAULT_CLIENT_INFO,
|
|
225
|
+
capabilities: this.options.capabilities ?? DEFAULT_CAPABILITIES
|
|
226
|
+
});
|
|
227
|
+
this.notify("initialized", {});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async close() {
|
|
231
|
+
if (this.closed) {
|
|
232
|
+
await this.exitPromise;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.closed = true;
|
|
237
|
+
|
|
238
|
+
if (this.readline) {
|
|
239
|
+
this.readline.close();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this.proc && !this.proc.killed) {
|
|
243
|
+
this.proc.stdin.end();
|
|
244
|
+
setTimeout(() => {
|
|
245
|
+
if (this.proc && !this.proc.killed && this.proc.exitCode === null) {
|
|
246
|
+
// On Windows with shell: true, the direct child is cmd.exe.
|
|
247
|
+
// Use terminateProcessTree to kill the entire tree including
|
|
248
|
+
// the grandchild node process.
|
|
249
|
+
if (process.platform === "win32") {
|
|
250
|
+
try {
|
|
251
|
+
terminateProcessTree(this.proc.pid);
|
|
252
|
+
} catch {
|
|
253
|
+
// Best-effort cleanup inside an unref'd timer — swallow errors
|
|
254
|
+
// to avoid crashing the host process during shutdown.
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
this.proc.kill("SIGTERM");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}, 50).unref?.();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
await this.exitPromise;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
sendMessage(message) {
|
|
267
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
268
|
+
const stdin = this.proc?.stdin;
|
|
269
|
+
if (!stdin) {
|
|
270
|
+
throw new Error("codex app-server stdin is not available.");
|
|
271
|
+
}
|
|
272
|
+
stdin.write(line);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
class BrokerCodexAppServerClient extends AppServerClientBase {
|
|
277
|
+
constructor(cwd, options = {}) {
|
|
278
|
+
super(cwd, options);
|
|
279
|
+
this.transport = "broker";
|
|
280
|
+
this.endpoint = options.brokerEndpoint;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async initialize() {
|
|
284
|
+
await new Promise((resolve, reject) => {
|
|
285
|
+
const target = parseBrokerEndpoint(this.endpoint);
|
|
286
|
+
this.socket = net.createConnection({ path: target.path });
|
|
287
|
+
this.socket.setEncoding("utf8");
|
|
288
|
+
this.socket.on("connect", resolve);
|
|
289
|
+
this.socket.on("data", (chunk) => {
|
|
290
|
+
this.handleChunk(chunk);
|
|
291
|
+
});
|
|
292
|
+
this.socket.on("error", (error) => {
|
|
293
|
+
if (!this.exitResolved) {
|
|
294
|
+
reject(error);
|
|
295
|
+
}
|
|
296
|
+
this.handleExit(error);
|
|
297
|
+
});
|
|
298
|
+
this.socket.on("close", () => {
|
|
299
|
+
this.handleExit(this.exitError);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await this.request("initialize", {
|
|
304
|
+
clientInfo: this.options.clientInfo ?? DEFAULT_CLIENT_INFO,
|
|
305
|
+
capabilities: this.options.capabilities ?? DEFAULT_CAPABILITIES
|
|
306
|
+
});
|
|
307
|
+
this.notify("initialized", {});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async close() {
|
|
311
|
+
if (this.closed) {
|
|
312
|
+
await this.exitPromise;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.closed = true;
|
|
317
|
+
if (this.socket) {
|
|
318
|
+
this.socket.end();
|
|
319
|
+
}
|
|
320
|
+
await this.exitPromise;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
sendMessage(message) {
|
|
324
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
325
|
+
const socket = this.socket;
|
|
326
|
+
if (!socket) {
|
|
327
|
+
throw new Error("codex app-server broker connection is not connected.");
|
|
328
|
+
}
|
|
329
|
+
socket.write(line);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export class CodexAppServerClient {
|
|
334
|
+
static async connect(cwd, options = {}) {
|
|
335
|
+
let brokerEndpoint = null;
|
|
336
|
+
if (!options.disableBroker) {
|
|
337
|
+
brokerEndpoint = options.brokerEndpoint ?? options.env?.[BROKER_ENDPOINT_ENV] ?? process.env[BROKER_ENDPOINT_ENV] ?? null;
|
|
338
|
+
if (!brokerEndpoint && options.reuseExistingBroker) {
|
|
339
|
+
brokerEndpoint = loadBrokerSession(cwd)?.endpoint ?? null;
|
|
340
|
+
}
|
|
341
|
+
if (!brokerEndpoint && !options.reuseExistingBroker) {
|
|
342
|
+
const brokerSession = await ensureBrokerSession(cwd, { env: options.env });
|
|
343
|
+
brokerEndpoint = brokerSession?.endpoint ?? null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const client = brokerEndpoint
|
|
347
|
+
? new BrokerCodexAppServerClient(cwd, { ...options, brokerEndpoint })
|
|
348
|
+
: new SpawnedCodexAppServerClient(cwd, options);
|
|
349
|
+
await client.initialize();
|
|
350
|
+
return client;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Derived from @openai/codex-plugin-cc and modified for claude-crew.
|
|
3
|
+
export function parseArgs(argv, config = {}) {
|
|
4
|
+
const valueOptions = new Set(config.valueOptions ?? []);
|
|
5
|
+
const booleanOptions = new Set(config.booleanOptions ?? []);
|
|
6
|
+
const aliasMap = config.aliasMap ?? {};
|
|
7
|
+
const options = {};
|
|
8
|
+
const positionals = [];
|
|
9
|
+
let passthrough = false;
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
12
|
+
const token = argv[index];
|
|
13
|
+
|
|
14
|
+
if (passthrough) {
|
|
15
|
+
positionals.push(token);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (token === "--") {
|
|
20
|
+
passthrough = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!token.startsWith("-") || token === "-") {
|
|
25
|
+
positionals.push(token);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (token.startsWith("--")) {
|
|
30
|
+
const [rawKey, inlineValue] = token.slice(2).split("=", 2);
|
|
31
|
+
const key = aliasMap[rawKey] ?? rawKey;
|
|
32
|
+
|
|
33
|
+
if (booleanOptions.has(key)) {
|
|
34
|
+
options[key] = inlineValue === undefined ? true : inlineValue !== "false";
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (valueOptions.has(key)) {
|
|
39
|
+
const nextValue = inlineValue ?? argv[index + 1];
|
|
40
|
+
if (nextValue === undefined) {
|
|
41
|
+
throw new Error(`Missing value for --${rawKey}`);
|
|
42
|
+
}
|
|
43
|
+
options[key] = nextValue;
|
|
44
|
+
if (inlineValue === undefined) {
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
positionals.push(token);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const shortKey = token.slice(1);
|
|
55
|
+
const key = aliasMap[shortKey] ?? shortKey;
|
|
56
|
+
|
|
57
|
+
if (booleanOptions.has(key)) {
|
|
58
|
+
options[key] = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (valueOptions.has(key)) {
|
|
63
|
+
const nextValue = argv[index + 1];
|
|
64
|
+
if (nextValue === undefined) {
|
|
65
|
+
throw new Error(`Missing value for -${shortKey}`);
|
|
66
|
+
}
|
|
67
|
+
options[key] = nextValue;
|
|
68
|
+
index += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
positionals.push(token);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { options, positionals };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function splitRawArgumentString(raw) {
|
|
79
|
+
const tokens = [];
|
|
80
|
+
let current = "";
|
|
81
|
+
let quote = null;
|
|
82
|
+
let escaping = false;
|
|
83
|
+
|
|
84
|
+
for (const character of raw) {
|
|
85
|
+
if (escaping) {
|
|
86
|
+
current += character;
|
|
87
|
+
escaping = false;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (character === "\\") {
|
|
92
|
+
escaping = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (quote) {
|
|
97
|
+
if (character === quote) {
|
|
98
|
+
quote = null;
|
|
99
|
+
} else {
|
|
100
|
+
current += character;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (character === "'" || character === "\"") {
|
|
106
|
+
quote = character;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (/\s/.test(character)) {
|
|
111
|
+
if (current) {
|
|
112
|
+
tokens.push(current);
|
|
113
|
+
current = "";
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
current += character;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (escaping) {
|
|
122
|
+
current += "\\";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (current) {
|
|
126
|
+
tokens.push(current);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return tokens;
|
|
130
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Derived from @openai/codex-plugin-cc and modified for claude-crew.
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
function sanitizePipeName(value) {
|
|
7
|
+
return String(value ?? "")
|
|
8
|
+
.replace(/[^A-Za-z0-9._-]/g, "-")
|
|
9
|
+
.replace(/^-+|-+$/g, "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createBrokerEndpoint(sessionDir, platform = process.platform) {
|
|
13
|
+
if (platform === "win32") {
|
|
14
|
+
const pipeName = sanitizePipeName(`${path.win32.basename(sessionDir)}-codex-app-server`);
|
|
15
|
+
return `pipe:\\\\.\\pipe\\${pipeName}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `unix:${path.join(sessionDir, "broker.sock")}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseBrokerEndpoint(endpoint) {
|
|
22
|
+
if (typeof endpoint !== "string" || endpoint.length === 0) {
|
|
23
|
+
throw new Error("Missing broker endpoint.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (endpoint.startsWith("pipe:")) {
|
|
27
|
+
const pipePath = endpoint.slice("pipe:".length);
|
|
28
|
+
if (!pipePath) {
|
|
29
|
+
throw new Error("Broker pipe endpoint is missing its path.");
|
|
30
|
+
}
|
|
31
|
+
return { kind: "pipe", path: pipePath };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (endpoint.startsWith("unix:")) {
|
|
35
|
+
const socketPath = endpoint.slice("unix:".length);
|
|
36
|
+
if (!socketPath) {
|
|
37
|
+
throw new Error("Broker Unix socket endpoint is missing its path.");
|
|
38
|
+
}
|
|
39
|
+
return { kind: "unix", path: socketPath };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error(`Unsupported broker endpoint: ${endpoint}`);
|
|
43
|
+
}
|