copilot-hub 0.1.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/LICENSE +21 -0
- package/README.md +215 -0
- package/apps/agent-engine/.env.example +41 -0
- package/apps/agent-engine/LICENSE +21 -0
- package/apps/agent-engine/README.md +57 -0
- package/apps/agent-engine/bot-registry.example.json +28 -0
- package/apps/agent-engine/capabilities/example/index.js +3 -0
- package/apps/agent-engine/capabilities/example/manifest.json +14 -0
- package/apps/agent-engine/dist/agent-worker.js +241 -0
- package/apps/agent-engine/dist/config.js +225 -0
- package/apps/agent-engine/dist/index.js +352 -0
- package/apps/agent-engine/dist/test/project-fingerprint.test.js +40 -0
- package/apps/agent-engine/dist/test/thread-id.test.js +12 -0
- package/apps/agent-engine/package.json +28 -0
- package/apps/control-plane/.env.example +25 -0
- package/apps/control-plane/README.md +35 -0
- package/apps/control-plane/bot-registry.example.json +40 -0
- package/apps/control-plane/capabilities/example/index.js +3 -0
- package/apps/control-plane/capabilities/example/manifest.json +14 -0
- package/apps/control-plane/dist/agent-worker.js +243 -0
- package/apps/control-plane/dist/channels/channel-factory.js +21 -0
- package/apps/control-plane/dist/channels/hub-ops-commands.js +752 -0
- package/apps/control-plane/dist/channels/telegram-channel.js +743 -0
- package/apps/control-plane/dist/channels/whatsapp-channel.js +35 -0
- package/apps/control-plane/dist/config.js +230 -0
- package/apps/control-plane/dist/copilot-hub.js +138 -0
- package/apps/control-plane/dist/index.js +349 -0
- package/apps/control-plane/dist/kernel/admin-contract.js +51 -0
- package/apps/control-plane/dist/test/project-fingerprint.test.js +40 -0
- package/apps/control-plane/dist/test/thread-id.test.js +12 -0
- package/apps/control-plane/package.json +27 -0
- package/package.json +89 -0
- package/packages/contracts/README.md +10 -0
- package/packages/contracts/dist/control-plane.d.ts +24 -0
- package/packages/contracts/dist/control-plane.js +37 -0
- package/packages/contracts/dist/control-plane.js.map +1 -0
- package/packages/contracts/dist/index.d.ts +1 -0
- package/packages/contracts/dist/index.js +2 -0
- package/packages/contracts/dist/index.js.map +1 -0
- package/packages/contracts/package.json +27 -0
- package/packages/core/README.md +33 -0
- package/packages/core/dist/agent-supervisor.d.ts +39 -0
- package/packages/core/dist/agent-supervisor.js +552 -0
- package/packages/core/dist/agent-supervisor.js.map +1 -0
- package/packages/core/dist/bot-manager.d.ts +66 -0
- package/packages/core/dist/bot-manager.js +333 -0
- package/packages/core/dist/bot-manager.js.map +1 -0
- package/packages/core/dist/bot-registry.d.ts +60 -0
- package/packages/core/dist/bot-registry.js +381 -0
- package/packages/core/dist/bot-registry.js.map +1 -0
- package/packages/core/dist/bot-runtime.d.ts +135 -0
- package/packages/core/dist/bot-runtime.js +349 -0
- package/packages/core/dist/bot-runtime.js.map +1 -0
- package/packages/core/dist/bridge-service.d.ts +39 -0
- package/packages/core/dist/bridge-service.js +272 -0
- package/packages/core/dist/bridge-service.js.map +1 -0
- package/packages/core/dist/capability-manager.d.ts +18 -0
- package/packages/core/dist/capability-manager.js +335 -0
- package/packages/core/dist/capability-manager.js.map +1 -0
- package/packages/core/dist/capability-scaffold.d.ts +26 -0
- package/packages/core/dist/capability-scaffold.js +118 -0
- package/packages/core/dist/capability-scaffold.js.map +1 -0
- package/packages/core/dist/channel-factory.d.ts +6 -0
- package/packages/core/dist/channel-factory.js +22 -0
- package/packages/core/dist/channel-factory.js.map +1 -0
- package/packages/core/dist/codex-app-client.d.ts +56 -0
- package/packages/core/dist/codex-app-client.js +762 -0
- package/packages/core/dist/codex-app-client.js.map +1 -0
- package/packages/core/dist/codex-provider.d.ts +31 -0
- package/packages/core/dist/codex-provider.js +64 -0
- package/packages/core/dist/codex-provider.js.map +1 -0
- package/packages/core/dist/control-permission.d.ts +19 -0
- package/packages/core/dist/control-permission.js +106 -0
- package/packages/core/dist/control-permission.js.map +1 -0
- package/packages/core/dist/control-plane-actions.d.ts +1 -0
- package/packages/core/dist/control-plane-actions.js +2 -0
- package/packages/core/dist/control-plane-actions.js.map +1 -0
- package/packages/core/dist/example-capability.d.ts +17 -0
- package/packages/core/dist/example-capability.js +22 -0
- package/packages/core/dist/example-capability.js.map +1 -0
- package/packages/core/dist/extension-contract.d.ts +22 -0
- package/packages/core/dist/extension-contract.js +28 -0
- package/packages/core/dist/extension-contract.js.map +1 -0
- package/packages/core/dist/index.d.ts +26 -0
- package/packages/core/dist/index.js +27 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/instance-lock.d.ts +9 -0
- package/packages/core/dist/instance-lock.js +74 -0
- package/packages/core/dist/instance-lock.js.map +1 -0
- package/packages/core/dist/kernel-control-plane.d.ts +16 -0
- package/packages/core/dist/kernel-control-plane.js +500 -0
- package/packages/core/dist/kernel-control-plane.js.map +1 -0
- package/packages/core/dist/kernel-version.d.ts +1 -0
- package/packages/core/dist/kernel-version.js +2 -0
- package/packages/core/dist/kernel-version.js.map +1 -0
- package/packages/core/dist/project-fingerprint.d.ts +11 -0
- package/packages/core/dist/project-fingerprint.js +33 -0
- package/packages/core/dist/project-fingerprint.js.map +1 -0
- package/packages/core/dist/provider-factory.d.ts +7 -0
- package/packages/core/dist/provider-factory.js +21 -0
- package/packages/core/dist/provider-factory.js.map +1 -0
- package/packages/core/dist/secret-store.d.ts +18 -0
- package/packages/core/dist/secret-store.js +110 -0
- package/packages/core/dist/secret-store.js.map +1 -0
- package/packages/core/dist/state-store.d.ts +50 -0
- package/packages/core/dist/state-store.js +324 -0
- package/packages/core/dist/state-store.js.map +1 -0
- package/packages/core/dist/telegram-channel.d.ts +27 -0
- package/packages/core/dist/telegram-channel.js +951 -0
- package/packages/core/dist/telegram-channel.js.map +1 -0
- package/packages/core/dist/thread-id.d.ts +1 -0
- package/packages/core/dist/thread-id.js +12 -0
- package/packages/core/dist/thread-id.js.map +1 -0
- package/packages/core/dist/whatsapp-channel.d.ts +26 -0
- package/packages/core/dist/whatsapp-channel.js +36 -0
- package/packages/core/dist/whatsapp-channel.js.map +1 -0
- package/packages/core/dist/workspace-paths.d.ts +5 -0
- package/packages/core/dist/workspace-paths.js +77 -0
- package/packages/core/dist/workspace-paths.js.map +1 -0
- package/packages/core/dist/workspace-policy.d.ts +30 -0
- package/packages/core/dist/workspace-policy.js +104 -0
- package/packages/core/dist/workspace-policy.js.map +1 -0
- package/packages/core/package.json +126 -0
- package/scripts/cli.mjs +537 -0
- package/scripts/configure.mjs +254 -0
- package/scripts/ensure-shared-build.mjs +96 -0
- package/scripts/run-node-tests.mjs +52 -0
- package/scripts/supervisor.mjs +332 -0
package/scripts/cli.mjs
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process, { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const repoRoot = path.resolve(__dirname, "..");
|
|
12
|
+
const nodeBin = process.execPath;
|
|
13
|
+
const agentEngineEnvPath = path.join(repoRoot, "apps", "agent-engine", ".env");
|
|
14
|
+
const controlPlaneEnvPath = path.join(repoRoot, "apps", "control-plane", ".env");
|
|
15
|
+
const codexNpmPackage = "@openai/codex";
|
|
16
|
+
const codexInstallCommand = `npm install -g ${codexNpmPackage}`;
|
|
17
|
+
|
|
18
|
+
const action = String(process.argv[2] ?? "start")
|
|
19
|
+
.trim()
|
|
20
|
+
.toLowerCase();
|
|
21
|
+
|
|
22
|
+
await main();
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
switch (action) {
|
|
26
|
+
case "start": {
|
|
27
|
+
runNode(["scripts/configure.mjs", "--required-only"]);
|
|
28
|
+
runNode(["scripts/ensure-shared-build.mjs"]);
|
|
29
|
+
await ensureCodexLogin();
|
|
30
|
+
runNode(["scripts/supervisor.mjs", "up"]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
case "stop": {
|
|
34
|
+
runNode(["scripts/supervisor.mjs", "down"]);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
case "restart": {
|
|
38
|
+
runNode(["scripts/ensure-shared-build.mjs"]);
|
|
39
|
+
runNode(["scripts/supervisor.mjs", "restart"]);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
case "status": {
|
|
43
|
+
runNode(["scripts/supervisor.mjs", "status"]);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
case "logs": {
|
|
47
|
+
runNode(["scripts/supervisor.mjs", "logs"]);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
case "configure": {
|
|
51
|
+
runNode(["scripts/configure.mjs"]);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
default: {
|
|
55
|
+
printUsage();
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runNode(scriptArgs) {
|
|
62
|
+
const result = spawnSync(nodeBin, scriptArgs, {
|
|
63
|
+
cwd: repoRoot,
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
shell: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
69
|
+
if (code !== 0) {
|
|
70
|
+
process.exit(code);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function ensureCodexLogin() {
|
|
75
|
+
const resolved = resolveCodexBinForStart();
|
|
76
|
+
let codexBin = resolved.bin;
|
|
77
|
+
let status = runCodex(codexBin, ["login", "status"], "pipe");
|
|
78
|
+
if (status.ok) {
|
|
79
|
+
console.log("Codex login already configured.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (status.errorCode === "ENOENT") {
|
|
84
|
+
codexBin = await recoverCodexBinary({
|
|
85
|
+
resolved,
|
|
86
|
+
status,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
status = runCodex(codexBin, ["login", "status"], "pipe");
|
|
90
|
+
if (status.ok) {
|
|
91
|
+
console.log("Codex login already configured.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const reason = status.errorMessage || status.stderr || status.stdout;
|
|
97
|
+
if (!process.stdin.isTTY) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
[
|
|
100
|
+
"Codex login is required and this terminal is non-interactive.",
|
|
101
|
+
`Run '${codexBin} login' once, then retry 'npm run start'.`,
|
|
102
|
+
reason ? `Details: ${firstLine(reason)}` : "",
|
|
103
|
+
]
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.join("\n"),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log("Codex login is not configured on this machine.");
|
|
110
|
+
if (reason) {
|
|
111
|
+
console.log(`Status details: ${firstLine(reason)}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const rl = createInterface({ input, output });
|
|
115
|
+
try {
|
|
116
|
+
const proceed = await askYesNo(rl, `Run '${codexBin} login' now?`, true);
|
|
117
|
+
if (!proceed) {
|
|
118
|
+
throw new Error("Codex login is required before starting services.");
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
rl.close();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const login = runCodex(codexBin, ["login"], "inherit");
|
|
125
|
+
if (!login.ok) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
[
|
|
128
|
+
`Codex login failed for '${codexBin}'.`,
|
|
129
|
+
login.errorMessage ||
|
|
130
|
+
firstLine(login.stderr) ||
|
|
131
|
+
firstLine(login.stdout) ||
|
|
132
|
+
"Unknown error.",
|
|
133
|
+
]
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.join("\n"),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const verify = runCodex(codexBin, ["login", "status"], "pipe");
|
|
140
|
+
if (!verify.ok) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
[
|
|
143
|
+
"Codex login still not detected after login flow.",
|
|
144
|
+
verify.errorMessage ||
|
|
145
|
+
firstLine(verify.stderr) ||
|
|
146
|
+
firstLine(verify.stdout) ||
|
|
147
|
+
"Unknown error.",
|
|
148
|
+
]
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.join("\n"),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log("Codex login configured.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function recoverCodexBinary({ resolved, status }) {
|
|
158
|
+
const detected = findDetectedCodexBin();
|
|
159
|
+
if (detected && detected !== resolved.bin) {
|
|
160
|
+
const probe = runCodex(detected, ["--version"], "pipe");
|
|
161
|
+
if (probe.ok) {
|
|
162
|
+
console.log(`Detected Codex binary: ${detected}`);
|
|
163
|
+
return detected;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (resolved.userConfigured) {
|
|
168
|
+
return resolved.bin;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!process.stdin.isTTY) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
[
|
|
174
|
+
status.errorMessage || `Codex binary '${resolved.bin}' was not found.`,
|
|
175
|
+
`Install Codex CLI with '${codexInstallCommand}' or set CODEX_BIN, then retry 'npm run start'.`,
|
|
176
|
+
]
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.join("\n"),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log("Codex CLI was not found on this machine.");
|
|
183
|
+
const rl = createInterface({ input, output });
|
|
184
|
+
let installNow = false;
|
|
185
|
+
try {
|
|
186
|
+
installNow = await askYesNo(rl, `Install Codex CLI now (${codexInstallCommand})?`, true);
|
|
187
|
+
} finally {
|
|
188
|
+
rl.close();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!installNow) {
|
|
192
|
+
throw new Error("Codex CLI is required before starting services.");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const install = runNpm(["install", "-g", codexNpmPackage], "inherit");
|
|
196
|
+
if (!install.ok) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
[
|
|
199
|
+
"Codex CLI installation failed.",
|
|
200
|
+
install.errorMessage ||
|
|
201
|
+
firstLine(install.stderr) ||
|
|
202
|
+
firstLine(install.stdout) ||
|
|
203
|
+
"Unknown error.",
|
|
204
|
+
]
|
|
205
|
+
.filter(Boolean)
|
|
206
|
+
.join("\n"),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const installed = resolveInstalledCodexBin();
|
|
211
|
+
if (!installed) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
[
|
|
214
|
+
"Codex CLI appears installed, but no runnable 'codex' binary was detected.",
|
|
215
|
+
"Set CODEX_BIN to the full Codex executable path, then retry 'npm run start'.",
|
|
216
|
+
].join("\n"),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`Codex CLI installed. Using '${installed}'.`);
|
|
221
|
+
return installed;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function runCodex(codexBin, args, stdioMode) {
|
|
225
|
+
const stdio = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
|
|
226
|
+
const result = spawnSync(codexBin, args, {
|
|
227
|
+
cwd: repoRoot,
|
|
228
|
+
stdio,
|
|
229
|
+
shell: false,
|
|
230
|
+
encoding: "utf8",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (result.error) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
stdout: "",
|
|
237
|
+
stderr: "",
|
|
238
|
+
errorMessage: formatCodexSpawnError(codexBin, result.error),
|
|
239
|
+
errorCode: normalizeErrorCode(result.error),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
244
|
+
return {
|
|
245
|
+
ok: code === 0,
|
|
246
|
+
stdout: String(result.stdout ?? "").trim(),
|
|
247
|
+
stderr: String(result.stderr ?? "").trim(),
|
|
248
|
+
errorMessage: "",
|
|
249
|
+
errorCode: "",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveCodexBinForStart() {
|
|
254
|
+
const fromEnv = nonEmpty(process.env.CODEX_BIN);
|
|
255
|
+
if (fromEnv) {
|
|
256
|
+
return {
|
|
257
|
+
bin: fromEnv,
|
|
258
|
+
source: "process_env",
|
|
259
|
+
userConfigured: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const [source, envPath] of [
|
|
264
|
+
["agent_env", agentEngineEnvPath],
|
|
265
|
+
["control_plane_env", controlPlaneEnvPath],
|
|
266
|
+
]) {
|
|
267
|
+
const value = readEnvValue(envPath, "CODEX_BIN");
|
|
268
|
+
if (value) {
|
|
269
|
+
return {
|
|
270
|
+
bin: value,
|
|
271
|
+
source,
|
|
272
|
+
userConfigured: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const detected = findDetectedCodexBin();
|
|
278
|
+
if (detected) {
|
|
279
|
+
return {
|
|
280
|
+
bin: detected,
|
|
281
|
+
source: "detected",
|
|
282
|
+
userConfigured: false,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
bin: "codex",
|
|
288
|
+
source: "default",
|
|
289
|
+
userConfigured: false,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function resolveInstalledCodexBin() {
|
|
294
|
+
const candidates = dedupe(
|
|
295
|
+
["codex", findDetectedCodexBin(), findWindowsNpmGlobalCodexBin(), findVscodeCodexExe()].filter(
|
|
296
|
+
Boolean,
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
for (const candidate of candidates) {
|
|
301
|
+
const status = runCodex(candidate, ["--version"], "pipe");
|
|
302
|
+
if (status.ok) {
|
|
303
|
+
return candidate;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function findDetectedCodexBin() {
|
|
311
|
+
if (process.platform !== "win32") {
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return findVscodeCodexExe() || findWindowsNpmGlobalCodexBin() || "";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function findVscodeCodexExe() {
|
|
319
|
+
const userProfile = nonEmpty(process.env.USERPROFILE);
|
|
320
|
+
if (!userProfile) {
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const extensionsDir = path.join(userProfile, ".vscode", "extensions");
|
|
325
|
+
if (!fs.existsSync(extensionsDir)) {
|
|
326
|
+
return "";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const candidates = fs
|
|
330
|
+
.readdirSync(extensionsDir, { withFileTypes: true })
|
|
331
|
+
.filter((entry) => entry.isDirectory())
|
|
332
|
+
.map((entry) => entry.name)
|
|
333
|
+
.filter((name) => name.startsWith("openai.chatgpt-"))
|
|
334
|
+
.sort()
|
|
335
|
+
.reverse();
|
|
336
|
+
|
|
337
|
+
for (const folder of candidates) {
|
|
338
|
+
const exePath = path.join(extensionsDir, folder, "bin", "windows-x86_64", "codex.exe");
|
|
339
|
+
if (fs.existsSync(exePath)) {
|
|
340
|
+
return exePath;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function findWindowsNpmGlobalCodexBin() {
|
|
348
|
+
if (process.platform !== "win32") {
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const candidates = [];
|
|
353
|
+
const appData = nonEmpty(process.env.APPDATA);
|
|
354
|
+
if (appData) {
|
|
355
|
+
candidates.push(path.join(appData, "npm", "codex.cmd"));
|
|
356
|
+
candidates.push(path.join(appData, "npm", "codex.exe"));
|
|
357
|
+
candidates.push(path.join(appData, "npm", "codex"));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const npmPrefix = readNpmPrefix();
|
|
361
|
+
if (npmPrefix) {
|
|
362
|
+
candidates.push(path.join(npmPrefix, "codex.cmd"));
|
|
363
|
+
candidates.push(path.join(npmPrefix, "codex.exe"));
|
|
364
|
+
candidates.push(path.join(npmPrefix, "codex"));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const candidate of dedupe(candidates)) {
|
|
368
|
+
if (fs.existsSync(candidate)) {
|
|
369
|
+
return candidate;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return "";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function readNpmPrefix() {
|
|
377
|
+
const result = spawnNpm(["config", "get", "prefix"], {
|
|
378
|
+
cwd: repoRoot,
|
|
379
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
380
|
+
encoding: "utf8",
|
|
381
|
+
});
|
|
382
|
+
if (result.error || result.status !== 0) {
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const value = String(result.stdout ?? "").trim();
|
|
387
|
+
if (!value || value.toLowerCase() === "undefined") {
|
|
388
|
+
return "";
|
|
389
|
+
}
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function runNpm(args, stdioMode) {
|
|
394
|
+
const stdio = stdioMode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"];
|
|
395
|
+
const result = spawnNpm(args, {
|
|
396
|
+
cwd: repoRoot,
|
|
397
|
+
stdio,
|
|
398
|
+
encoding: "utf8",
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
if (result.error) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
stdout: "",
|
|
405
|
+
stderr: "",
|
|
406
|
+
errorMessage: formatNpmSpawnError(result.error),
|
|
407
|
+
errorCode: normalizeErrorCode(result.error),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
412
|
+
return {
|
|
413
|
+
ok: code === 0,
|
|
414
|
+
stdout: String(result.stdout ?? "").trim(),
|
|
415
|
+
stderr: String(result.stderr ?? "").trim(),
|
|
416
|
+
errorMessage: "",
|
|
417
|
+
errorCode: "",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function readEnvValue(filePath, key) {
|
|
422
|
+
if (!fs.existsSync(filePath)) {
|
|
423
|
+
return "";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
427
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=\\s*(.*)\\s*$`);
|
|
428
|
+
for (const line of lines) {
|
|
429
|
+
const match = line.match(pattern);
|
|
430
|
+
if (!match) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
return unquote(match[1]);
|
|
434
|
+
}
|
|
435
|
+
return "";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function unquote(value) {
|
|
439
|
+
const raw = String(value ?? "").trim();
|
|
440
|
+
if (!raw) {
|
|
441
|
+
return "";
|
|
442
|
+
}
|
|
443
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
444
|
+
return raw.slice(1, -1).trim();
|
|
445
|
+
}
|
|
446
|
+
return raw;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function askYesNo(rl, label, defaultYes) {
|
|
450
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
451
|
+
const answer = await rl.question(`${label} ${suffix}: `);
|
|
452
|
+
const value = String(answer ?? "")
|
|
453
|
+
.trim()
|
|
454
|
+
.toLowerCase();
|
|
455
|
+
if (!value) {
|
|
456
|
+
return defaultYes;
|
|
457
|
+
}
|
|
458
|
+
if (value === "y" || value === "yes") {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
if (value === "n" || value === "no") {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return defaultYes;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function escapeRegex(value) {
|
|
468
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function nonEmpty(value) {
|
|
472
|
+
const normalized = String(value ?? "").trim();
|
|
473
|
+
return normalized || "";
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function firstLine(value) {
|
|
477
|
+
return (
|
|
478
|
+
String(value ?? "")
|
|
479
|
+
.split(/\r?\n/)
|
|
480
|
+
.map((line) => line.trim())
|
|
481
|
+
.find(Boolean) ?? ""
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function formatCodexSpawnError(command, error) {
|
|
486
|
+
const code = normalizeErrorCode(error);
|
|
487
|
+
if (code === "ENOENT") {
|
|
488
|
+
return `Codex binary '${command}' was not found. Install Codex CLI or set CODEX_BIN.`;
|
|
489
|
+
}
|
|
490
|
+
if (code === "EPERM") {
|
|
491
|
+
return `Codex binary '${command}' cannot be executed (EPERM). Check permissions or CODEX_BIN.`;
|
|
492
|
+
}
|
|
493
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
494
|
+
return `Failed to execute '${command}': ${firstLine(message)}`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function formatNpmSpawnError(error) {
|
|
498
|
+
const code = normalizeErrorCode(error);
|
|
499
|
+
if (code === "ENOENT") {
|
|
500
|
+
return "npm was not found. Install Node.js/npm, then retry.";
|
|
501
|
+
}
|
|
502
|
+
if (code === "EPERM") {
|
|
503
|
+
return "npm cannot be executed (EPERM). Check permissions.";
|
|
504
|
+
}
|
|
505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
506
|
+
return `Failed to execute 'npm': ${firstLine(message)}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function normalizeErrorCode(error) {
|
|
510
|
+
return String(error?.code ?? "")
|
|
511
|
+
.trim()
|
|
512
|
+
.toUpperCase();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function dedupe(values) {
|
|
516
|
+
return [...new Set(values.map((value) => String(value).trim()).filter(Boolean))];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function spawnNpm(args, options) {
|
|
520
|
+
if (process.platform === "win32") {
|
|
521
|
+
const comspec = process.env.ComSpec || "cmd.exe";
|
|
522
|
+
const commandLine = ["npm", ...args].join(" ");
|
|
523
|
+
return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
|
|
524
|
+
...options,
|
|
525
|
+
shell: false,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return spawnSync("npm", args, {
|
|
530
|
+
...options,
|
|
531
|
+
shell: false,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function printUsage() {
|
|
536
|
+
console.log("Usage: node scripts/cli.mjs <start|stop|restart|status|logs|configure>");
|
|
537
|
+
}
|