@agentconnect/cli 0.1.3 → 0.1.6
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/README.md +4 -0
- package/dist/index.js +1890 -139
- package/dist/providers/claude.d.ts +3 -1
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/cursor.d.ts +11 -0
- package/dist/providers/local.d.ts +1 -0
- package/dist/providers/utils.d.ts +1 -0
- package/dist/types.d.ts +31 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import path13 from "path";
|
|
5
5
|
import { promises as fs7 } from "fs";
|
|
6
6
|
import { createPrivateKey, createPublicKey as createPublicKey2, sign as signData } from "crypto";
|
|
7
7
|
|
|
8
8
|
// src/host.ts
|
|
9
9
|
import http from "http";
|
|
10
10
|
import { WebSocketServer } from "ws";
|
|
11
|
-
import { spawn as
|
|
11
|
+
import { spawn as spawn5 } from "child_process";
|
|
12
12
|
import fs2 from "fs";
|
|
13
13
|
import { promises as fsp } from "fs";
|
|
14
14
|
import net from "net";
|
|
15
|
-
import
|
|
15
|
+
import path6 from "path";
|
|
16
16
|
|
|
17
17
|
// src/providers/claude.ts
|
|
18
18
|
import { spawn as spawn2 } from "child_process";
|
|
19
19
|
import { access, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
20
|
+
import https from "https";
|
|
20
21
|
import os2 from "os";
|
|
21
22
|
import path2 from "path";
|
|
22
23
|
|
|
23
24
|
// src/providers/utils.ts
|
|
24
25
|
import { spawn } from "child_process";
|
|
25
|
-
import { existsSync } from "fs";
|
|
26
|
+
import { existsSync, realpathSync } from "fs";
|
|
26
27
|
import os from "os";
|
|
27
28
|
import path from "path";
|
|
28
29
|
var DEBUG_ENABLED = Boolean(process.env.AGENTCONNECT_DEBUG?.trim());
|
|
@@ -128,6 +129,15 @@ function resolveCommandPath(command2) {
|
|
|
128
129
|
}
|
|
129
130
|
return null;
|
|
130
131
|
}
|
|
132
|
+
function resolveCommandRealPath(command2) {
|
|
133
|
+
const resolved = resolveCommandPath(command2);
|
|
134
|
+
if (!resolved) return null;
|
|
135
|
+
try {
|
|
136
|
+
return realpathSync(resolved);
|
|
137
|
+
} catch {
|
|
138
|
+
return resolved;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
131
141
|
function commandExists(command2) {
|
|
132
142
|
return Boolean(resolveCommandPath(command2));
|
|
133
143
|
}
|
|
@@ -143,6 +153,12 @@ function runCommand(command2, args2, options = {}) {
|
|
|
143
153
|
resolve({ code: 127, stdout: "", stderr: `Executable not found in PATH: "${command2}"` });
|
|
144
154
|
return;
|
|
145
155
|
}
|
|
156
|
+
const startedAt = Date.now();
|
|
157
|
+
debugLog("Command", "run", {
|
|
158
|
+
command: resolved,
|
|
159
|
+
args: args2,
|
|
160
|
+
startedAt: new Date(startedAt).toISOString()
|
|
161
|
+
});
|
|
146
162
|
const child = spawn(resolved, args2, {
|
|
147
163
|
...spawnOptions,
|
|
148
164
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -168,10 +184,21 @@ function runCommand(command2, args2, options = {}) {
|
|
|
168
184
|
});
|
|
169
185
|
child.on("error", (err) => {
|
|
170
186
|
if (timeout) clearTimeout(timeout);
|
|
187
|
+
debugLog("Command", "result", {
|
|
188
|
+
command: resolved,
|
|
189
|
+
code: -1,
|
|
190
|
+
durationMs: Date.now() - startedAt,
|
|
191
|
+
error: err.message
|
|
192
|
+
});
|
|
171
193
|
resolve({ code: -1, stdout, stderr: `${stderr}${err.message}` });
|
|
172
194
|
});
|
|
173
195
|
child.on("close", (code) => {
|
|
174
196
|
if (timeout) clearTimeout(timeout);
|
|
197
|
+
debugLog("Command", "result", {
|
|
198
|
+
command: resolved,
|
|
199
|
+
code: code ?? 0,
|
|
200
|
+
durationMs: Date.now() - startedAt
|
|
201
|
+
});
|
|
175
202
|
resolve({ code: code ?? 0, stdout, stderr });
|
|
176
203
|
});
|
|
177
204
|
});
|
|
@@ -268,10 +295,110 @@ var DEFAULT_LOGIN = "";
|
|
|
268
295
|
var DEFAULT_STATUS = "";
|
|
269
296
|
var CLAUDE_MODELS_CACHE_TTL_MS = 6e4;
|
|
270
297
|
var CLAUDE_RECENT_MODELS_CACHE_TTL_MS = 6e4;
|
|
298
|
+
var CLAUDE_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
271
299
|
var claudeModelsCache = null;
|
|
272
300
|
var claudeModelsCacheAt = 0;
|
|
273
301
|
var claudeRecentModelsCache = null;
|
|
274
302
|
var claudeRecentModelsCacheAt = 0;
|
|
303
|
+
var claudeUpdateCache = null;
|
|
304
|
+
var claudeUpdatePromise = null;
|
|
305
|
+
var CLAUDE_LOGIN_CACHE_TTL_MS = 3e4;
|
|
306
|
+
var claudeLoginCache = null;
|
|
307
|
+
var claudeLoginPromise = null;
|
|
308
|
+
function trimOutput(value, limit = 400) {
|
|
309
|
+
const cleaned = value.trim();
|
|
310
|
+
if (!cleaned) return "";
|
|
311
|
+
if (cleaned.length <= limit) return cleaned;
|
|
312
|
+
return `${cleaned.slice(0, limit)}...`;
|
|
313
|
+
}
|
|
314
|
+
function normalizePath(value) {
|
|
315
|
+
const normalized = value.replace(/\\/g, "/");
|
|
316
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
317
|
+
}
|
|
318
|
+
function parseSemver(value) {
|
|
319
|
+
if (!value) return null;
|
|
320
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
321
|
+
if (!match) return null;
|
|
322
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
323
|
+
}
|
|
324
|
+
function compareSemver(a, b) {
|
|
325
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
326
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
327
|
+
return a[2] - b[2];
|
|
328
|
+
}
|
|
329
|
+
async function fetchLatestNpmVersion(pkg) {
|
|
330
|
+
const encoded = encodeURIComponent(pkg);
|
|
331
|
+
const data = await fetchJson(`https://registry.npmjs.org/${encoded}`);
|
|
332
|
+
if (!data || typeof data !== "object") return null;
|
|
333
|
+
const latest = data["dist-tags"]?.latest;
|
|
334
|
+
return typeof latest === "string" ? latest : null;
|
|
335
|
+
}
|
|
336
|
+
async function fetchBrewCaskVersion(cask) {
|
|
337
|
+
if (!commandExists("brew")) return null;
|
|
338
|
+
const result = await runCommand("brew", ["info", "--json=v2", "--cask", cask]);
|
|
339
|
+
if (result.code !== 0) return null;
|
|
340
|
+
try {
|
|
341
|
+
const parsed = JSON.parse(result.stdout);
|
|
342
|
+
const version = parsed?.casks?.[0]?.version;
|
|
343
|
+
return typeof version === "string" ? version : null;
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function getClaudeUpdateAction(commandPath) {
|
|
349
|
+
if (!commandPath) return null;
|
|
350
|
+
const normalized = normalizePath(commandPath);
|
|
351
|
+
const home = normalizePath(os2.homedir());
|
|
352
|
+
if (normalized.startsWith(`${home}/.bun/bin/`)) {
|
|
353
|
+
return {
|
|
354
|
+
command: "bun",
|
|
355
|
+
args: ["install", "-g", CLAUDE_PACKAGE],
|
|
356
|
+
source: "bun",
|
|
357
|
+
commandLabel: "bun install -g @anthropic-ai/claude-code"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (normalized.includes("/node_modules/.bin/")) {
|
|
361
|
+
return {
|
|
362
|
+
command: "npm",
|
|
363
|
+
args: ["install", "-g", CLAUDE_PACKAGE],
|
|
364
|
+
source: "npm",
|
|
365
|
+
commandLabel: "npm install -g @anthropic-ai/claude-code"
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/")) {
|
|
369
|
+
return {
|
|
370
|
+
command: "brew",
|
|
371
|
+
args: ["upgrade", "--cask", "claude-code"],
|
|
372
|
+
source: "brew",
|
|
373
|
+
commandLabel: "brew upgrade --cask claude-code"
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (process.platform === "win32" && (normalized.includes("/program files/claudecode") || normalized.includes("/programdata/claudecode"))) {
|
|
377
|
+
return {
|
|
378
|
+
command: "winget",
|
|
379
|
+
args: ["upgrade", "Anthropic.ClaudeCode"],
|
|
380
|
+
source: "winget",
|
|
381
|
+
commandLabel: "winget upgrade Anthropic.ClaudeCode"
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (normalized.includes("/.local/bin/") || normalized.includes("/.local/share/claude/versions/") || normalized.includes("/.local/share/claude-code/versions/")) {
|
|
385
|
+
if (process.platform === "win32") {
|
|
386
|
+
return {
|
|
387
|
+
command: "powershell",
|
|
388
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", INSTALL_WINDOWS_PS],
|
|
389
|
+
source: "script",
|
|
390
|
+
commandLabel: INSTALL_WINDOWS_PS
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
command: "bash",
|
|
395
|
+
args: ["-lc", INSTALL_UNIX],
|
|
396
|
+
source: "script",
|
|
397
|
+
commandLabel: INSTALL_UNIX
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
275
402
|
var DEFAULT_CLAUDE_MODELS = [
|
|
276
403
|
{
|
|
277
404
|
id: "default",
|
|
@@ -299,6 +426,23 @@ function getClaudeCommand() {
|
|
|
299
426
|
function getClaudeConfigDir() {
|
|
300
427
|
return process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
|
|
301
428
|
}
|
|
429
|
+
function fetchJson(url) {
|
|
430
|
+
return new Promise((resolve) => {
|
|
431
|
+
https.get(url, (res) => {
|
|
432
|
+
let data = "";
|
|
433
|
+
res.on("data", (chunk) => {
|
|
434
|
+
data += chunk;
|
|
435
|
+
});
|
|
436
|
+
res.on("end", () => {
|
|
437
|
+
try {
|
|
438
|
+
resolve(JSON.parse(data));
|
|
439
|
+
} catch {
|
|
440
|
+
resolve(null);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}).on("error", () => resolve(null));
|
|
444
|
+
});
|
|
445
|
+
}
|
|
302
446
|
function formatClaudeDisplayName(modelId) {
|
|
303
447
|
const value = modelId.trim();
|
|
304
448
|
if (!value.startsWith("claude-")) return value;
|
|
@@ -391,6 +535,18 @@ function resolveClaudeLoginExperience(options) {
|
|
|
391
535
|
if (process.env.AGENTCONNECT_HOST_MODE === "dev") return "terminal";
|
|
392
536
|
return "embedded";
|
|
393
537
|
}
|
|
538
|
+
async function resolveClaudeLoginHint(options) {
|
|
539
|
+
const raw = process.env.AGENTCONNECT_CLAUDE_LOGIN_HINT;
|
|
540
|
+
if (raw) {
|
|
541
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
542
|
+
if (normalized === "setup") return "setup";
|
|
543
|
+
if (normalized === "login") return "login";
|
|
544
|
+
}
|
|
545
|
+
if (options?.loginExperience) {
|
|
546
|
+
}
|
|
547
|
+
const status = await checkClaudeCliStatus();
|
|
548
|
+
return status.loginHint ?? "login";
|
|
549
|
+
}
|
|
394
550
|
async function createClaudeLoginSettingsFile(loginMethod) {
|
|
395
551
|
if (!loginMethod) return null;
|
|
396
552
|
const fileName = `agentconnect-claude-login-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
|
|
@@ -551,25 +707,154 @@ async function hasClaudeAuth() {
|
|
|
551
707
|
}
|
|
552
708
|
return false;
|
|
553
709
|
}
|
|
710
|
+
function isClaudeAuthErrorText(value) {
|
|
711
|
+
const text = value.toLowerCase();
|
|
712
|
+
return text.includes("authentication_error") || text.includes("authentication_failed") || text.includes("oauth token has expired") || text.includes("token has expired") || text.includes("please run /login") || text.includes("unauthorized") || text.includes("api error: 401") || text.includes("status 401") || text.includes("invalid api key");
|
|
713
|
+
}
|
|
714
|
+
function extractClaudeMessageText(content) {
|
|
715
|
+
if (Array.isArray(content)) {
|
|
716
|
+
return content.map((part) => part?.text ?? "").join(" ");
|
|
717
|
+
}
|
|
718
|
+
return typeof content === "string" ? content : "";
|
|
719
|
+
}
|
|
720
|
+
function resolveClaudeLoginHintFromSource(apiKeySource) {
|
|
721
|
+
if (apiKeySource && apiKeySource.toLowerCase() === "none") return "setup";
|
|
722
|
+
return "login";
|
|
723
|
+
}
|
|
554
724
|
async function checkClaudeCliStatus() {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
env: { ...process.env, CI: "1" },
|
|
558
|
-
input: "/status\n",
|
|
559
|
-
timeoutMs: 4e3
|
|
560
|
-
});
|
|
561
|
-
const output = `${result.stdout}
|
|
562
|
-
${result.stderr}`.toLowerCase();
|
|
563
|
-
if (!output.trim()) {
|
|
564
|
-
return null;
|
|
725
|
+
if (claudeLoginCache && Date.now() - claudeLoginCache.checkedAt < CLAUDE_LOGIN_CACHE_TTL_MS) {
|
|
726
|
+
return claudeLoginCache.status;
|
|
565
727
|
}
|
|
566
|
-
if (
|
|
567
|
-
|
|
728
|
+
if (claudeLoginPromise) return claudeLoginPromise;
|
|
729
|
+
claudeLoginPromise = (async () => {
|
|
730
|
+
const command2 = resolveWindowsCommand(getClaudeCommand());
|
|
731
|
+
const args2 = [
|
|
732
|
+
"--print",
|
|
733
|
+
"--output-format",
|
|
734
|
+
"stream-json",
|
|
735
|
+
"--no-session-persistence",
|
|
736
|
+
"--max-budget-usd",
|
|
737
|
+
"0.01"
|
|
738
|
+
];
|
|
739
|
+
const result = await runCommand(command2, args2, {
|
|
740
|
+
env: { ...process.env, CI: "1" },
|
|
741
|
+
input: "ping\n",
|
|
742
|
+
timeoutMs: 8e3
|
|
743
|
+
});
|
|
744
|
+
const output = `${result.stdout}
|
|
745
|
+
${result.stderr}`.trim();
|
|
746
|
+
if (!output) return { loggedIn: null };
|
|
747
|
+
let apiKeySource;
|
|
748
|
+
let authError = null;
|
|
749
|
+
let sawAssistant = false;
|
|
750
|
+
let sawSuccess = false;
|
|
751
|
+
const lines = output.split("\n");
|
|
752
|
+
for (const line of lines) {
|
|
753
|
+
const parsed = safeJsonParse(line);
|
|
754
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
755
|
+
const record = parsed;
|
|
756
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
757
|
+
if (type === "system" && record.subtype === "init") {
|
|
758
|
+
const source = typeof record.apiKeySource === "string" ? record.apiKeySource : void 0;
|
|
759
|
+
if (source) apiKeySource = source;
|
|
760
|
+
}
|
|
761
|
+
if (type === "result") {
|
|
762
|
+
const isError = Boolean(record.is_error);
|
|
763
|
+
const resultText = typeof record.result === "string" ? record.result : typeof record.error === "string" ? record.error : JSON.stringify(record.error || record);
|
|
764
|
+
if (isError && isClaudeAuthErrorText(resultText)) {
|
|
765
|
+
authError = authError ?? resultText;
|
|
766
|
+
}
|
|
767
|
+
if (!isError) sawSuccess = true;
|
|
768
|
+
}
|
|
769
|
+
if (type === "message") {
|
|
770
|
+
const message = record.message;
|
|
771
|
+
const role = typeof message?.role === "string" ? message.role : "";
|
|
772
|
+
if (role === "assistant") {
|
|
773
|
+
const text = extractClaudeMessageText(message?.content);
|
|
774
|
+
const errorText = typeof record.error === "string" ? record.error : typeof message?.error === "string" ? message.error : "";
|
|
775
|
+
if (isClaudeAuthErrorText(text) || errorText && isClaudeAuthErrorText(errorText)) {
|
|
776
|
+
authError = authError ?? (text || errorText);
|
|
777
|
+
} else if (text.trim()) {
|
|
778
|
+
sawAssistant = true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (authError) {
|
|
784
|
+
return {
|
|
785
|
+
loggedIn: false,
|
|
786
|
+
apiKeySource,
|
|
787
|
+
loginHint: resolveClaudeLoginHintFromSource(apiKeySource),
|
|
788
|
+
authError
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
if (sawAssistant || sawSuccess) {
|
|
792
|
+
return { loggedIn: true, apiKeySource };
|
|
793
|
+
}
|
|
794
|
+
if (apiKeySource && apiKeySource.toLowerCase() === "none") {
|
|
795
|
+
return { loggedIn: false, apiKeySource, loginHint: "setup" };
|
|
796
|
+
}
|
|
797
|
+
return { loggedIn: null, apiKeySource };
|
|
798
|
+
})().then((status) => {
|
|
799
|
+
claudeLoginCache = { checkedAt: Date.now(), status };
|
|
800
|
+
return status;
|
|
801
|
+
}).finally(() => {
|
|
802
|
+
claudeLoginPromise = null;
|
|
803
|
+
});
|
|
804
|
+
return claudeLoginPromise;
|
|
805
|
+
}
|
|
806
|
+
function getClaudeUpdateSnapshot(commandPath) {
|
|
807
|
+
if (claudeUpdateCache && Date.now() - claudeUpdateCache.checkedAt < CLAUDE_UPDATE_CACHE_TTL_MS) {
|
|
808
|
+
const action = getClaudeUpdateAction(commandPath);
|
|
809
|
+
return {
|
|
810
|
+
updateAvailable: claudeUpdateCache.updateAvailable,
|
|
811
|
+
latestVersion: claudeUpdateCache.latestVersion,
|
|
812
|
+
updateCheckedAt: claudeUpdateCache.checkedAt,
|
|
813
|
+
updateSource: action?.source ?? "unknown",
|
|
814
|
+
updateCommand: action?.commandLabel,
|
|
815
|
+
updateMessage: claudeUpdateCache.updateMessage
|
|
816
|
+
};
|
|
568
817
|
}
|
|
569
|
-
|
|
570
|
-
|
|
818
|
+
return {};
|
|
819
|
+
}
|
|
820
|
+
function ensureClaudeUpdateCheck(currentVersion, commandPath) {
|
|
821
|
+
if (claudeUpdateCache && Date.now() - claudeUpdateCache.checkedAt < CLAUDE_UPDATE_CACHE_TTL_MS) {
|
|
822
|
+
return;
|
|
571
823
|
}
|
|
572
|
-
return
|
|
824
|
+
if (claudeUpdatePromise) return;
|
|
825
|
+
claudeUpdatePromise = (async () => {
|
|
826
|
+
const action = getClaudeUpdateAction(commandPath || null);
|
|
827
|
+
let latest = null;
|
|
828
|
+
let updateAvailable;
|
|
829
|
+
let updateMessage;
|
|
830
|
+
if (action?.source === "npm" || action?.source === "bun") {
|
|
831
|
+
latest = await fetchLatestNpmVersion(CLAUDE_PACKAGE);
|
|
832
|
+
} else if (action?.source === "brew") {
|
|
833
|
+
latest = await fetchBrewCaskVersion("claude-code");
|
|
834
|
+
}
|
|
835
|
+
if (latest && currentVersion) {
|
|
836
|
+
const a = parseSemver(currentVersion);
|
|
837
|
+
const b = parseSemver(latest);
|
|
838
|
+
if (a && b) {
|
|
839
|
+
updateAvailable = compareSemver(a, b) < 0;
|
|
840
|
+
updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
|
|
841
|
+
}
|
|
842
|
+
} else if (!action) {
|
|
843
|
+
updateMessage = "Update check unavailable";
|
|
844
|
+
}
|
|
845
|
+
debugLog("Claude", "update-check", {
|
|
846
|
+
updateAvailable,
|
|
847
|
+
message: updateMessage
|
|
848
|
+
});
|
|
849
|
+
claudeUpdateCache = {
|
|
850
|
+
checkedAt: Date.now(),
|
|
851
|
+
updateAvailable,
|
|
852
|
+
latestVersion: latest ?? void 0,
|
|
853
|
+
updateMessage
|
|
854
|
+
};
|
|
855
|
+
})().finally(() => {
|
|
856
|
+
claudeUpdatePromise = null;
|
|
857
|
+
});
|
|
573
858
|
}
|
|
574
859
|
async function ensureClaudeInstalled() {
|
|
575
860
|
const command2 = getClaudeCommand();
|
|
@@ -633,18 +918,61 @@ async function getClaudeStatus() {
|
|
|
633
918
|
const result = await runCommand(statusCommand, status.args);
|
|
634
919
|
loggedIn = result.code === 0;
|
|
635
920
|
} else {
|
|
636
|
-
const hasAuth = await hasClaudeAuth();
|
|
637
921
|
const cliStatus = await checkClaudeCliStatus();
|
|
638
|
-
if (cliStatus === false) {
|
|
922
|
+
if (cliStatus.loggedIn === false) {
|
|
639
923
|
loggedIn = false;
|
|
640
|
-
} else if (cliStatus === true) {
|
|
924
|
+
} else if (cliStatus.loggedIn === true) {
|
|
641
925
|
loggedIn = true;
|
|
926
|
+
} else if (cliStatus.apiKeySource?.toLowerCase() === "none") {
|
|
927
|
+
loggedIn = false;
|
|
642
928
|
} else {
|
|
643
|
-
loggedIn =
|
|
929
|
+
loggedIn = await hasClaudeAuth();
|
|
644
930
|
}
|
|
645
931
|
}
|
|
646
932
|
}
|
|
647
|
-
|
|
933
|
+
if (installed) {
|
|
934
|
+
const resolved2 = resolveCommandRealPath(command2);
|
|
935
|
+
ensureClaudeUpdateCheck(versionCheck.version, resolved2 || null);
|
|
936
|
+
}
|
|
937
|
+
const resolved = resolveCommandRealPath(command2);
|
|
938
|
+
const updateInfo = installed ? getClaudeUpdateSnapshot(resolved || null) : {};
|
|
939
|
+
return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
|
|
940
|
+
}
|
|
941
|
+
async function getClaudeFastStatus() {
|
|
942
|
+
const command2 = getClaudeCommand();
|
|
943
|
+
const installed = commandExists(command2);
|
|
944
|
+
const loggedIn = installed ? await hasClaudeAuth() : false;
|
|
945
|
+
return { installed, loggedIn };
|
|
946
|
+
}
|
|
947
|
+
async function updateClaude() {
|
|
948
|
+
const command2 = getClaudeCommand();
|
|
949
|
+
if (!commandExists(command2)) {
|
|
950
|
+
return { installed: false, loggedIn: false };
|
|
951
|
+
}
|
|
952
|
+
const resolved = resolveCommandRealPath(command2);
|
|
953
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CLAUDE_UPDATE", "");
|
|
954
|
+
const action = updateOverride.command ? null : getClaudeUpdateAction(resolved || null);
|
|
955
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
956
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
957
|
+
if (!updateCommand) {
|
|
958
|
+
throw new Error("No update command available. Please update Claude manually.");
|
|
959
|
+
}
|
|
960
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
961
|
+
debugLog("Claude", "update-run", { command: cmd, args: updateArgs });
|
|
962
|
+
const result = await runCommand(cmd, updateArgs, { env: { ...process.env, CI: "1" } });
|
|
963
|
+
debugLog("Claude", "update-result", {
|
|
964
|
+
code: result.code,
|
|
965
|
+
stdout: trimOutput(result.stdout),
|
|
966
|
+
stderr: trimOutput(result.stderr)
|
|
967
|
+
});
|
|
968
|
+
if (result.code !== 0 && result.code !== null) {
|
|
969
|
+
const message = trimOutput(`${result.stdout}
|
|
970
|
+
${result.stderr}`, 800) || "Update failed";
|
|
971
|
+
throw new Error(message);
|
|
972
|
+
}
|
|
973
|
+
claudeUpdateCache = null;
|
|
974
|
+
claudeUpdatePromise = null;
|
|
975
|
+
return getClaudeStatus();
|
|
648
976
|
}
|
|
649
977
|
async function loginClaude(options) {
|
|
650
978
|
const login = buildLoginCommand("AGENTCONNECT_CLAUDE_LOGIN", DEFAULT_LOGIN);
|
|
@@ -661,10 +989,12 @@ async function runClaudeLoginFlow(options) {
|
|
|
661
989
|
const command2 = resolveWindowsCommand(getClaudeCommand());
|
|
662
990
|
const loginMethod = resolveClaudeLoginMethod(options);
|
|
663
991
|
const loginExperience = resolveClaudeLoginExperience(options);
|
|
992
|
+
const loginHint = await resolveClaudeLoginHint(options);
|
|
664
993
|
await ensureClaudeOnboardingSettings();
|
|
665
994
|
const settingsPath = await createClaudeLoginSettingsFile(loginMethod);
|
|
666
995
|
const loginTimeoutMs = Number(process.env.AGENTCONNECT_CLAUDE_LOGIN_TIMEOUT_MS || 18e4);
|
|
667
996
|
const loginArgs = settingsPath ? ["--settings", settingsPath] : [];
|
|
997
|
+
const includeLogin = loginHint === "login";
|
|
668
998
|
let ptyProcess = null;
|
|
669
999
|
let childExited = false;
|
|
670
1000
|
const cleanup = async () => {
|
|
@@ -674,7 +1004,7 @@ async function runClaudeLoginFlow(options) {
|
|
|
674
1004
|
} catch {
|
|
675
1005
|
}
|
|
676
1006
|
}
|
|
677
|
-
if (settingsPath) {
|
|
1007
|
+
if (settingsPath && loginExperience !== "terminal") {
|
|
678
1008
|
try {
|
|
679
1009
|
await rm(settingsPath, { force: true });
|
|
680
1010
|
} catch {
|
|
@@ -683,7 +1013,13 @@ async function runClaudeLoginFlow(options) {
|
|
|
683
1013
|
};
|
|
684
1014
|
try {
|
|
685
1015
|
if (loginExperience === "terminal") {
|
|
686
|
-
await openClaudeLoginTerminal(command2, loginArgs,
|
|
1016
|
+
await openClaudeLoginTerminal(command2, loginArgs, includeLogin);
|
|
1017
|
+
if (settingsPath) {
|
|
1018
|
+
setTimeout(() => {
|
|
1019
|
+
rm(settingsPath, { force: true }).catch(() => {
|
|
1020
|
+
});
|
|
1021
|
+
}, loginTimeoutMs);
|
|
1022
|
+
}
|
|
687
1023
|
} else {
|
|
688
1024
|
const ptyModule = await loadPtyModule();
|
|
689
1025
|
if (!ptyModule) {
|
|
@@ -691,7 +1027,8 @@ async function runClaudeLoginFlow(options) {
|
|
|
691
1027
|
"Claude login requires node-pty. Reinstall AgentConnect or run `claude /login` manually."
|
|
692
1028
|
);
|
|
693
1029
|
}
|
|
694
|
-
|
|
1030
|
+
const spawnArgs = includeLogin ? [...loginArgs, "/login"] : loginArgs;
|
|
1031
|
+
ptyProcess = ptyModule.spawn(command2, spawnArgs, {
|
|
695
1032
|
name: "xterm-256color",
|
|
696
1033
|
cols: 100,
|
|
697
1034
|
rows: 30,
|
|
@@ -771,6 +1108,19 @@ function extractAssistantDelta(msg) {
|
|
|
771
1108
|
const text = msg.delta?.text;
|
|
772
1109
|
return typeof text === "string" && text ? text : null;
|
|
773
1110
|
}
|
|
1111
|
+
function extractTextFromContent(content) {
|
|
1112
|
+
if (!content) return "";
|
|
1113
|
+
if (typeof content === "string") return content;
|
|
1114
|
+
if (Array.isArray(content)) {
|
|
1115
|
+
return content.map((part) => {
|
|
1116
|
+
if (!part || typeof part !== "object") return "";
|
|
1117
|
+
const text = part.text;
|
|
1118
|
+
if (typeof text === "string") return text;
|
|
1119
|
+
return "";
|
|
1120
|
+
}).filter(Boolean).join("");
|
|
1121
|
+
}
|
|
1122
|
+
return "";
|
|
1123
|
+
}
|
|
774
1124
|
function extractAssistantText(msg) {
|
|
775
1125
|
if (String(msg.type ?? "") !== "assistant") return null;
|
|
776
1126
|
const content = msg.message?.content;
|
|
@@ -789,6 +1139,7 @@ function runClaudePrompt({
|
|
|
789
1139
|
resumeSessionId,
|
|
790
1140
|
model,
|
|
791
1141
|
cwd,
|
|
1142
|
+
providerDetailLevel,
|
|
792
1143
|
onEvent,
|
|
793
1144
|
signal
|
|
794
1145
|
}) {
|
|
@@ -821,45 +1172,189 @@ function runClaudePrompt({
|
|
|
821
1172
|
let finalSessionId = null;
|
|
822
1173
|
let didFinalize = false;
|
|
823
1174
|
let sawError = false;
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1175
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
1176
|
+
const thinkingBlocks = /* @__PURE__ */ new Set();
|
|
1177
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
1178
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
1179
|
+
const detail = { eventType };
|
|
1180
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
1181
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
1182
|
+
return detail;
|
|
828
1183
|
};
|
|
829
|
-
const
|
|
1184
|
+
const emit = (event) => {
|
|
830
1185
|
if (finalSessionId) {
|
|
831
|
-
onEvent({
|
|
1186
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
832
1187
|
} else {
|
|
833
|
-
onEvent(
|
|
1188
|
+
onEvent(event);
|
|
834
1189
|
}
|
|
835
1190
|
};
|
|
1191
|
+
const emitError = (message) => {
|
|
1192
|
+
if (sawError) return;
|
|
1193
|
+
sawError = true;
|
|
1194
|
+
emit({ type: "error", message });
|
|
1195
|
+
};
|
|
1196
|
+
const emitFinal = (text, providerDetail) => {
|
|
1197
|
+
emit({ type: "final", text, providerDetail });
|
|
1198
|
+
};
|
|
836
1199
|
const handleLine = (line) => {
|
|
837
1200
|
const parsed = safeJsonParse(line);
|
|
838
1201
|
if (!parsed || typeof parsed !== "object") {
|
|
839
1202
|
if (line.trim()) {
|
|
840
|
-
|
|
1203
|
+
emit({ type: "raw_line", line });
|
|
841
1204
|
}
|
|
842
1205
|
return;
|
|
843
1206
|
}
|
|
844
1207
|
const msg = parsed;
|
|
845
1208
|
const sid = extractSessionId(msg);
|
|
846
1209
|
if (sid) finalSessionId = sid;
|
|
1210
|
+
const msgType = String(msg.type ?? "");
|
|
1211
|
+
let handled = false;
|
|
1212
|
+
if (msgType === "assistant" || msgType === "user" || msgType === "system") {
|
|
1213
|
+
const role = msg.message?.role === "assistant" || msg.message?.role === "user" || msg.message?.role === "system" ? msg.message.role : msgType;
|
|
1214
|
+
const rawContent = msg.message?.content;
|
|
1215
|
+
const content = extractTextFromContent(rawContent);
|
|
1216
|
+
emit({
|
|
1217
|
+
type: "message",
|
|
1218
|
+
provider: "claude",
|
|
1219
|
+
role,
|
|
1220
|
+
content,
|
|
1221
|
+
contentParts: rawContent ?? null,
|
|
1222
|
+
providerDetail: buildProviderDetail(msgType, {}, msg)
|
|
1223
|
+
});
|
|
1224
|
+
handled = true;
|
|
1225
|
+
}
|
|
1226
|
+
if (msgType === "stream_event" && msg.event) {
|
|
1227
|
+
const evType = String(msg.event.type ?? "");
|
|
1228
|
+
const index = typeof msg.event.index === "number" ? msg.event.index : void 0;
|
|
1229
|
+
const block = msg.event.content_block;
|
|
1230
|
+
if (evType === "content_block_start" && block && typeof index === "number") {
|
|
1231
|
+
if (block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") {
|
|
1232
|
+
toolBlocks.set(index, { id: block.id, name: block.name });
|
|
1233
|
+
emit({
|
|
1234
|
+
type: "tool_call",
|
|
1235
|
+
provider: "claude",
|
|
1236
|
+
name: block.name,
|
|
1237
|
+
callId: block.id,
|
|
1238
|
+
input: block.input,
|
|
1239
|
+
phase: "start",
|
|
1240
|
+
providerDetail: buildProviderDetail(
|
|
1241
|
+
"content_block_start",
|
|
1242
|
+
{ blockType: block.type, index, name: block.name, id: block.id },
|
|
1243
|
+
msg
|
|
1244
|
+
)
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
if (block.type === "thinking" || block.type === "redacted_thinking") {
|
|
1248
|
+
thinkingBlocks.add(index);
|
|
1249
|
+
emit({
|
|
1250
|
+
type: "thinking",
|
|
1251
|
+
provider: "claude",
|
|
1252
|
+
phase: "start",
|
|
1253
|
+
text: typeof block.thinking === "string" ? block.thinking : void 0,
|
|
1254
|
+
providerDetail: buildProviderDetail(
|
|
1255
|
+
"content_block_start",
|
|
1256
|
+
{ blockType: block.type, index },
|
|
1257
|
+
msg
|
|
1258
|
+
)
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
handled = true;
|
|
1262
|
+
}
|
|
1263
|
+
if (evType === "content_block_delta") {
|
|
1264
|
+
const delta2 = msg.event.delta ?? {};
|
|
1265
|
+
if (delta2.type === "thinking_delta") {
|
|
1266
|
+
emit({
|
|
1267
|
+
type: "thinking",
|
|
1268
|
+
provider: "claude",
|
|
1269
|
+
phase: "delta",
|
|
1270
|
+
text: typeof delta2.thinking === "string" ? delta2.thinking : void 0,
|
|
1271
|
+
providerDetail: buildProviderDetail(
|
|
1272
|
+
"content_block_delta",
|
|
1273
|
+
{ deltaType: delta2.type, index },
|
|
1274
|
+
msg
|
|
1275
|
+
)
|
|
1276
|
+
});
|
|
1277
|
+
handled = true;
|
|
1278
|
+
}
|
|
1279
|
+
if (delta2.type === "input_json_delta") {
|
|
1280
|
+
const tool = typeof index === "number" ? toolBlocks.get(index) : void 0;
|
|
1281
|
+
emit({
|
|
1282
|
+
type: "tool_call",
|
|
1283
|
+
provider: "claude",
|
|
1284
|
+
name: tool?.name,
|
|
1285
|
+
callId: tool?.id,
|
|
1286
|
+
input: delta2.partial_json,
|
|
1287
|
+
phase: "delta",
|
|
1288
|
+
providerDetail: buildProviderDetail(
|
|
1289
|
+
"content_block_delta",
|
|
1290
|
+
{ deltaType: delta2.type, index, name: tool?.name, id: tool?.id },
|
|
1291
|
+
msg
|
|
1292
|
+
)
|
|
1293
|
+
});
|
|
1294
|
+
handled = true;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (evType === "content_block_stop" && typeof index === "number") {
|
|
1298
|
+
if (toolBlocks.has(index)) {
|
|
1299
|
+
const tool = toolBlocks.get(index);
|
|
1300
|
+
emit({
|
|
1301
|
+
type: "tool_call",
|
|
1302
|
+
provider: "claude",
|
|
1303
|
+
name: tool?.name,
|
|
1304
|
+
callId: tool?.id,
|
|
1305
|
+
phase: "completed",
|
|
1306
|
+
providerDetail: buildProviderDetail(
|
|
1307
|
+
"content_block_stop",
|
|
1308
|
+
{ index, name: tool?.name, id: tool?.id },
|
|
1309
|
+
msg
|
|
1310
|
+
)
|
|
1311
|
+
});
|
|
1312
|
+
toolBlocks.delete(index);
|
|
1313
|
+
}
|
|
1314
|
+
if (thinkingBlocks.has(index)) {
|
|
1315
|
+
emit({
|
|
1316
|
+
type: "thinking",
|
|
1317
|
+
provider: "claude",
|
|
1318
|
+
phase: "completed",
|
|
1319
|
+
providerDetail: buildProviderDetail("content_block_stop", { index }, msg)
|
|
1320
|
+
});
|
|
1321
|
+
thinkingBlocks.delete(index);
|
|
1322
|
+
}
|
|
1323
|
+
handled = true;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
847
1326
|
const delta = extractAssistantDelta(msg);
|
|
848
1327
|
if (delta) {
|
|
849
1328
|
aggregated += delta;
|
|
850
|
-
|
|
1329
|
+
emit({
|
|
1330
|
+
type: "delta",
|
|
1331
|
+
text: delta,
|
|
1332
|
+
providerDetail: buildProviderDetail(msgType || "delta", {}, msg)
|
|
1333
|
+
});
|
|
851
1334
|
return;
|
|
852
1335
|
}
|
|
853
1336
|
const assistant = extractAssistantText(msg);
|
|
854
1337
|
if (assistant && !aggregated) {
|
|
855
1338
|
aggregated = assistant;
|
|
856
|
-
|
|
1339
|
+
emit({
|
|
1340
|
+
type: "delta",
|
|
1341
|
+
text: assistant,
|
|
1342
|
+
providerDetail: buildProviderDetail(msgType || "assistant", {}, msg)
|
|
1343
|
+
});
|
|
857
1344
|
return;
|
|
858
1345
|
}
|
|
859
1346
|
const result = extractResultText(msg);
|
|
860
1347
|
if (result && !didFinalize && !sawError) {
|
|
861
1348
|
didFinalize = true;
|
|
862
|
-
emitFinal(aggregated || result);
|
|
1349
|
+
emitFinal(aggregated || result, buildProviderDetail("result", {}, msg));
|
|
1350
|
+
handled = true;
|
|
1351
|
+
}
|
|
1352
|
+
if (!handled) {
|
|
1353
|
+
emit({
|
|
1354
|
+
type: "detail",
|
|
1355
|
+
provider: "claude",
|
|
1356
|
+
providerDetail: buildProviderDetail(msgType || "unknown", {}, msg)
|
|
1357
|
+
});
|
|
863
1358
|
}
|
|
864
1359
|
};
|
|
865
1360
|
const stdoutParser = createLineParser(handleLine);
|
|
@@ -886,23 +1381,177 @@ function runClaudePrompt({
|
|
|
886
1381
|
// src/providers/codex.ts
|
|
887
1382
|
import { spawn as spawn3 } from "child_process";
|
|
888
1383
|
import { readFile as readFile2 } from "fs/promises";
|
|
1384
|
+
import https2 from "https";
|
|
889
1385
|
import os3 from "os";
|
|
890
1386
|
import path3 from "path";
|
|
891
1387
|
var CODEX_PACKAGE = "@openai/codex";
|
|
892
1388
|
var DEFAULT_LOGIN2 = "codex login";
|
|
893
1389
|
var DEFAULT_STATUS2 = "codex login status";
|
|
894
1390
|
var CODEX_MODELS_CACHE_TTL_MS = 6e4;
|
|
1391
|
+
var CODEX_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
895
1392
|
var codexModelsCache = null;
|
|
896
1393
|
var codexModelsCacheAt = 0;
|
|
897
|
-
|
|
1394
|
+
var codexUpdateCache = null;
|
|
1395
|
+
var codexUpdatePromise = null;
|
|
1396
|
+
function trimOutput2(value, limit = 400) {
|
|
898
1397
|
const cleaned = value.trim();
|
|
899
1398
|
if (!cleaned) return "";
|
|
900
1399
|
if (cleaned.length <= limit) return cleaned;
|
|
901
1400
|
return `${cleaned.slice(0, limit)}...`;
|
|
902
1401
|
}
|
|
1402
|
+
function normalizePath2(value) {
|
|
1403
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1404
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
1405
|
+
}
|
|
903
1406
|
function getCodexConfigDir() {
|
|
904
1407
|
return process.env.CODEX_CONFIG_DIR || path3.join(os3.homedir(), ".codex");
|
|
905
1408
|
}
|
|
1409
|
+
function fetchJson2(url) {
|
|
1410
|
+
return new Promise((resolve) => {
|
|
1411
|
+
https2.get(url, (res) => {
|
|
1412
|
+
let data = "";
|
|
1413
|
+
res.on("data", (chunk) => {
|
|
1414
|
+
data += chunk;
|
|
1415
|
+
});
|
|
1416
|
+
res.on("end", () => {
|
|
1417
|
+
try {
|
|
1418
|
+
resolve(JSON.parse(data));
|
|
1419
|
+
} catch {
|
|
1420
|
+
resolve(null);
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
}).on("error", () => resolve(null));
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
function parseSemver2(value) {
|
|
1427
|
+
if (!value) return null;
|
|
1428
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
1429
|
+
if (!match) return null;
|
|
1430
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1431
|
+
}
|
|
1432
|
+
function compareSemver2(a, b) {
|
|
1433
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
1434
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
1435
|
+
return a[2] - b[2];
|
|
1436
|
+
}
|
|
1437
|
+
async function fetchLatestNpmVersion2(pkg) {
|
|
1438
|
+
const encoded = encodeURIComponent(pkg);
|
|
1439
|
+
const data = await fetchJson2(`https://registry.npmjs.org/${encoded}`);
|
|
1440
|
+
if (!data || typeof data !== "object") return null;
|
|
1441
|
+
const latest = data["dist-tags"]?.latest;
|
|
1442
|
+
return typeof latest === "string" ? latest : null;
|
|
1443
|
+
}
|
|
1444
|
+
async function fetchBrewFormulaVersion(formula) {
|
|
1445
|
+
if (!commandExists("brew")) return null;
|
|
1446
|
+
const result = await runCommand("brew", ["info", "--json=v2", formula]);
|
|
1447
|
+
if (result.code !== 0) return null;
|
|
1448
|
+
try {
|
|
1449
|
+
const parsed = JSON.parse(result.stdout);
|
|
1450
|
+
const version = parsed?.formulae?.[0]?.versions?.stable;
|
|
1451
|
+
return typeof version === "string" ? version : null;
|
|
1452
|
+
} catch {
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function getCodexUpdateAction(commandPath) {
|
|
1457
|
+
if (process.env.CODEX_MANAGED_BY_NPM) {
|
|
1458
|
+
return {
|
|
1459
|
+
command: "npm",
|
|
1460
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1461
|
+
source: "npm",
|
|
1462
|
+
commandLabel: "npm install -g @openai/codex"
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
if (process.env.CODEX_MANAGED_BY_BUN) {
|
|
1466
|
+
return {
|
|
1467
|
+
command: "bun",
|
|
1468
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1469
|
+
source: "bun",
|
|
1470
|
+
commandLabel: "bun install -g @openai/codex"
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
if (commandPath) {
|
|
1474
|
+
const normalized = normalizePath2(commandPath);
|
|
1475
|
+
if (normalized.includes(".bun/install/global")) {
|
|
1476
|
+
return {
|
|
1477
|
+
command: "bun",
|
|
1478
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1479
|
+
source: "bun",
|
|
1480
|
+
commandLabel: "bun install -g @openai/codex"
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
if (normalized.includes("/node_modules/.bin/") || normalized.includes("/lib/node_modules/")) {
|
|
1484
|
+
return {
|
|
1485
|
+
command: "npm",
|
|
1486
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1487
|
+
source: "npm",
|
|
1488
|
+
commandLabel: "npm install -g @openai/codex"
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (process.platform === "darwin" && commandPath && (commandPath.startsWith("/opt/homebrew") || commandPath.startsWith("/usr/local"))) {
|
|
1493
|
+
return {
|
|
1494
|
+
command: "brew",
|
|
1495
|
+
args: ["upgrade", "codex"],
|
|
1496
|
+
source: "brew",
|
|
1497
|
+
commandLabel: "brew upgrade codex"
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
function getCodexUpdateSnapshot(commandPath) {
|
|
1503
|
+
if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
|
|
1504
|
+
const action = getCodexUpdateAction(commandPath);
|
|
1505
|
+
return {
|
|
1506
|
+
updateAvailable: codexUpdateCache.updateAvailable,
|
|
1507
|
+
latestVersion: codexUpdateCache.latestVersion,
|
|
1508
|
+
updateCheckedAt: codexUpdateCache.checkedAt,
|
|
1509
|
+
updateSource: action?.source ?? "unknown",
|
|
1510
|
+
updateCommand: action?.commandLabel,
|
|
1511
|
+
updateMessage: codexUpdateCache.updateMessage
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
return {};
|
|
1515
|
+
}
|
|
1516
|
+
function ensureCodexUpdateCheck(currentVersion, commandPath) {
|
|
1517
|
+
if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
if (codexUpdatePromise) return;
|
|
1521
|
+
codexUpdatePromise = (async () => {
|
|
1522
|
+
const action = getCodexUpdateAction(commandPath || null);
|
|
1523
|
+
let latest = null;
|
|
1524
|
+
if (action?.source === "brew") {
|
|
1525
|
+
latest = await fetchBrewFormulaVersion("codex");
|
|
1526
|
+
} else {
|
|
1527
|
+
latest = await fetchLatestNpmVersion2(CODEX_PACKAGE);
|
|
1528
|
+
}
|
|
1529
|
+
let updateAvailable;
|
|
1530
|
+
let updateMessage;
|
|
1531
|
+
if (latest && currentVersion) {
|
|
1532
|
+
const a = parseSemver2(currentVersion);
|
|
1533
|
+
const b = parseSemver2(latest);
|
|
1534
|
+
if (a && b) {
|
|
1535
|
+
updateAvailable = compareSemver2(a, b) < 0;
|
|
1536
|
+
updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
codexUpdateCache = {
|
|
1540
|
+
checkedAt: Date.now(),
|
|
1541
|
+
updateAvailable,
|
|
1542
|
+
latestVersion: latest ?? void 0,
|
|
1543
|
+
updateMessage
|
|
1544
|
+
};
|
|
1545
|
+
debugLog("Codex", "update-check", {
|
|
1546
|
+
currentVersion,
|
|
1547
|
+
latest,
|
|
1548
|
+
updateAvailable,
|
|
1549
|
+
message: updateMessage
|
|
1550
|
+
});
|
|
1551
|
+
})().finally(() => {
|
|
1552
|
+
codexUpdatePromise = null;
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
906
1555
|
function hasAuthValue(value) {
|
|
907
1556
|
return typeof value === "string" && value.trim().length > 0;
|
|
908
1557
|
}
|
|
@@ -983,7 +1632,7 @@ async function ensureCodexInstalled() {
|
|
|
983
1632
|
});
|
|
984
1633
|
debugLog("Codex", "install-result", {
|
|
985
1634
|
code: installResult.code,
|
|
986
|
-
stderr:
|
|
1635
|
+
stderr: trimOutput2(installResult.stderr)
|
|
987
1636
|
});
|
|
988
1637
|
const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
989
1638
|
codexModelsCache = null;
|
|
@@ -1020,7 +1669,50 @@ ${result.stderr}`.toLowerCase();
|
|
|
1020
1669
|
loggedIn = await hasCodexAuth();
|
|
1021
1670
|
}
|
|
1022
1671
|
}
|
|
1023
|
-
|
|
1672
|
+
const resolved = resolveCommandRealPath(command2);
|
|
1673
|
+
if (installed) {
|
|
1674
|
+
ensureCodexUpdateCheck(versionCheck.version, resolved || null);
|
|
1675
|
+
}
|
|
1676
|
+
const updateInfo = installed ? getCodexUpdateSnapshot(resolved || null) : {};
|
|
1677
|
+
return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
|
|
1678
|
+
}
|
|
1679
|
+
async function getCodexFastStatus() {
|
|
1680
|
+
const command2 = getCodexCommand();
|
|
1681
|
+
const loggedIn = await hasCodexAuth();
|
|
1682
|
+
const installed = commandExists(command2) || loggedIn;
|
|
1683
|
+
return { installed, loggedIn };
|
|
1684
|
+
}
|
|
1685
|
+
async function updateCodex() {
|
|
1686
|
+
const command2 = getCodexCommand();
|
|
1687
|
+
if (!commandExists(command2)) {
|
|
1688
|
+
return { installed: false, loggedIn: false };
|
|
1689
|
+
}
|
|
1690
|
+
const resolved = resolveCommandRealPath(command2);
|
|
1691
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CODEX_UPDATE", "");
|
|
1692
|
+
const action = updateOverride.command ? null : getCodexUpdateAction(resolved || null);
|
|
1693
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
1694
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
1695
|
+
if (!updateCommand) {
|
|
1696
|
+
throw new Error("No update command available. Please update Codex manually.");
|
|
1697
|
+
}
|
|
1698
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
1699
|
+
debugLog("Codex", "update-run", { command: cmd, args: updateArgs });
|
|
1700
|
+
const result = await runCommand(cmd, updateArgs, {
|
|
1701
|
+
env: { ...process.env, CI: "1" }
|
|
1702
|
+
});
|
|
1703
|
+
debugLog("Codex", "update-result", {
|
|
1704
|
+
code: result.code,
|
|
1705
|
+
stdout: trimOutput2(result.stdout),
|
|
1706
|
+
stderr: trimOutput2(result.stderr)
|
|
1707
|
+
});
|
|
1708
|
+
if (result.code !== 0 && result.code !== null) {
|
|
1709
|
+
const message = trimOutput2(`${result.stdout}
|
|
1710
|
+
${result.stderr}`, 800) || "Update failed";
|
|
1711
|
+
throw new Error(message);
|
|
1712
|
+
}
|
|
1713
|
+
codexUpdateCache = null;
|
|
1714
|
+
codexUpdatePromise = null;
|
|
1715
|
+
return getCodexStatus();
|
|
1024
1716
|
}
|
|
1025
1717
|
async function loginCodex() {
|
|
1026
1718
|
const login = buildLoginCommand("AGENTCONNECT_CODEX_LOGIN", DEFAULT_LOGIN2);
|
|
@@ -1030,7 +1722,7 @@ async function loginCodex() {
|
|
|
1030
1722
|
const command2 = resolveWindowsCommand(login.command);
|
|
1031
1723
|
debugLog("Codex", "login", { command: command2, args: login.args });
|
|
1032
1724
|
const result = await runCommand(command2, login.args, { env: { ...process.env, CI: "1" } });
|
|
1033
|
-
debugLog("Codex", "login-result", { code: result.code, stderr:
|
|
1725
|
+
debugLog("Codex", "login-result", { code: result.code, stderr: trimOutput2(result.stderr) });
|
|
1034
1726
|
const status = await getCodexStatus();
|
|
1035
1727
|
codexModelsCache = null;
|
|
1036
1728
|
codexModelsCacheAt = 0;
|
|
@@ -1257,6 +1949,7 @@ function runCodexPrompt({
|
|
|
1257
1949
|
reasoningEffort,
|
|
1258
1950
|
repoRoot,
|
|
1259
1951
|
cwd,
|
|
1952
|
+
providerDetailLevel,
|
|
1260
1953
|
onEvent,
|
|
1261
1954
|
signal
|
|
1262
1955
|
}) {
|
|
@@ -1294,10 +1987,88 @@ function runCodexPrompt({
|
|
|
1294
1987
|
let finalSessionId = null;
|
|
1295
1988
|
let didFinalize = false;
|
|
1296
1989
|
let sawError = false;
|
|
1297
|
-
const
|
|
1990
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
1991
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
1992
|
+
const detail = { eventType };
|
|
1993
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
1994
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
1995
|
+
return detail;
|
|
1996
|
+
};
|
|
1997
|
+
const emit = (event) => {
|
|
1998
|
+
if (finalSessionId) {
|
|
1999
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
2000
|
+
} else {
|
|
2001
|
+
onEvent(event);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
const emitError = (message, providerDetail) => {
|
|
1298
2005
|
if (sawError) return;
|
|
1299
2006
|
sawError = true;
|
|
1300
|
-
|
|
2007
|
+
emit({ type: "error", message, providerDetail });
|
|
2008
|
+
};
|
|
2009
|
+
const emitItemEvent = (item, phase) => {
|
|
2010
|
+
const itemType = typeof item.type === "string" ? item.type : "";
|
|
2011
|
+
if (!itemType) return;
|
|
2012
|
+
const providerDetail = buildProviderDetail(
|
|
2013
|
+
phase === "start" ? "item.started" : "item.completed",
|
|
2014
|
+
{
|
|
2015
|
+
itemType,
|
|
2016
|
+
itemId: item.id,
|
|
2017
|
+
status: item.status
|
|
2018
|
+
},
|
|
2019
|
+
item
|
|
2020
|
+
);
|
|
2021
|
+
if (itemType === "agent_message") {
|
|
2022
|
+
if (phase === "completed" && typeof item.text === "string") {
|
|
2023
|
+
emit({
|
|
2024
|
+
type: "message",
|
|
2025
|
+
provider: "codex",
|
|
2026
|
+
role: "assistant",
|
|
2027
|
+
content: item.text,
|
|
2028
|
+
contentParts: item,
|
|
2029
|
+
providerDetail
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (itemType === "reasoning") {
|
|
2035
|
+
emit({
|
|
2036
|
+
type: "thinking",
|
|
2037
|
+
provider: "codex",
|
|
2038
|
+
phase,
|
|
2039
|
+
text: typeof item.text === "string" ? item.text : void 0,
|
|
2040
|
+
providerDetail
|
|
2041
|
+
});
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (itemType === "command_execution") {
|
|
2045
|
+
const output = phase === "completed" ? {
|
|
2046
|
+
output: item.aggregated_output,
|
|
2047
|
+
exitCode: item.exit_code,
|
|
2048
|
+
status: item.status
|
|
2049
|
+
} : void 0;
|
|
2050
|
+
emit({
|
|
2051
|
+
type: "tool_call",
|
|
2052
|
+
provider: "codex",
|
|
2053
|
+
name: "command_execution",
|
|
2054
|
+
callId: item.id,
|
|
2055
|
+
input: { command: item.command },
|
|
2056
|
+
output,
|
|
2057
|
+
phase,
|
|
2058
|
+
providerDetail
|
|
2059
|
+
});
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
emit({
|
|
2063
|
+
type: "tool_call",
|
|
2064
|
+
provider: "codex",
|
|
2065
|
+
name: itemType,
|
|
2066
|
+
callId: item.id,
|
|
2067
|
+
input: phase === "start" ? item : void 0,
|
|
2068
|
+
output: phase === "completed" ? item : void 0,
|
|
2069
|
+
phase,
|
|
2070
|
+
providerDetail
|
|
2071
|
+
});
|
|
1301
2072
|
};
|
|
1302
2073
|
let sawJson = false;
|
|
1303
2074
|
const stdoutLines = [];
|
|
@@ -1307,18 +2078,14 @@ function runCodexPrompt({
|
|
|
1307
2078
|
list.push(line);
|
|
1308
2079
|
if (list.length > 12) list.shift();
|
|
1309
2080
|
};
|
|
1310
|
-
const emitFinal = (text) => {
|
|
1311
|
-
|
|
1312
|
-
onEvent({ type: "final", text, providerSessionId: finalSessionId });
|
|
1313
|
-
} else {
|
|
1314
|
-
onEvent({ type: "final", text });
|
|
1315
|
-
}
|
|
2081
|
+
const emitFinal = (text, providerDetail) => {
|
|
2082
|
+
emit({ type: "final", text, providerDetail });
|
|
1316
2083
|
};
|
|
1317
2084
|
const handleLine = (line, source) => {
|
|
1318
2085
|
const parsed = safeJsonParse2(line);
|
|
1319
2086
|
if (!parsed || typeof parsed !== "object") {
|
|
1320
2087
|
if (line.trim()) {
|
|
1321
|
-
|
|
2088
|
+
emit({ type: "raw_line", line });
|
|
1322
2089
|
}
|
|
1323
2090
|
if (source === "stdout") {
|
|
1324
2091
|
pushLine(stdoutLines, line);
|
|
@@ -1330,47 +2097,85 @@ function runCodexPrompt({
|
|
|
1330
2097
|
sawJson = true;
|
|
1331
2098
|
const ev = parsed;
|
|
1332
2099
|
const normalized = normalizeEvent(ev);
|
|
1333
|
-
onEvent({ type: "provider_event", provider: "codex", event: normalized });
|
|
1334
2100
|
const sid = extractSessionId2(ev);
|
|
1335
2101
|
if (sid) finalSessionId = sid;
|
|
2102
|
+
const eventType = typeof ev.type === "string" ? ev.type : normalized.type;
|
|
2103
|
+
const detailData = {};
|
|
2104
|
+
const threadId = ev.thread_id ?? ev.threadId;
|
|
2105
|
+
if (typeof threadId === "string" && threadId) detailData.threadId = threadId;
|
|
2106
|
+
const providerDetail = buildProviderDetail(eventType || "unknown", detailData, ev);
|
|
2107
|
+
let handled = false;
|
|
1336
2108
|
const usage = extractUsage(ev);
|
|
1337
2109
|
if (usage) {
|
|
1338
|
-
|
|
2110
|
+
emit({
|
|
1339
2111
|
type: "usage",
|
|
1340
2112
|
inputTokens: usage.input_tokens,
|
|
1341
|
-
outputTokens: usage.output_tokens
|
|
2113
|
+
outputTokens: usage.output_tokens,
|
|
2114
|
+
providerDetail
|
|
1342
2115
|
});
|
|
2116
|
+
handled = true;
|
|
1343
2117
|
}
|
|
1344
2118
|
if (normalized.type === "agent_message") {
|
|
1345
2119
|
const text = normalized.text;
|
|
1346
2120
|
if (typeof text === "string" && text) {
|
|
1347
2121
|
aggregated += text;
|
|
1348
|
-
|
|
2122
|
+
emit({ type: "delta", text, providerDetail });
|
|
2123
|
+
emit({
|
|
2124
|
+
type: "message",
|
|
2125
|
+
provider: "codex",
|
|
2126
|
+
role: "assistant",
|
|
2127
|
+
content: text,
|
|
2128
|
+
contentParts: ev,
|
|
2129
|
+
providerDetail
|
|
2130
|
+
});
|
|
2131
|
+
handled = true;
|
|
1349
2132
|
}
|
|
1350
2133
|
} else if (normalized.type === "item.completed") {
|
|
1351
2134
|
const item = normalized.item;
|
|
1352
2135
|
if (item && typeof item === "object") {
|
|
2136
|
+
const itemDetail = buildProviderDetail("item.completed", {
|
|
2137
|
+
itemType: item.type,
|
|
2138
|
+
itemId: item.id,
|
|
2139
|
+
status: item.status
|
|
2140
|
+
}, item);
|
|
1353
2141
|
if (item.type === "command_execution" && typeof item.aggregated_output === "string") {
|
|
1354
|
-
|
|
2142
|
+
emit({ type: "delta", text: item.aggregated_output, providerDetail: itemDetail });
|
|
1355
2143
|
}
|
|
1356
2144
|
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
1357
2145
|
aggregated += item.text;
|
|
1358
|
-
|
|
2146
|
+
emit({ type: "delta", text: item.text, providerDetail: itemDetail });
|
|
1359
2147
|
}
|
|
1360
2148
|
}
|
|
1361
2149
|
}
|
|
2150
|
+
if (normalized.type === "item.started" && ev.item && typeof ev.item === "object") {
|
|
2151
|
+
emitItemEvent(ev.item, "start");
|
|
2152
|
+
handled = true;
|
|
2153
|
+
}
|
|
2154
|
+
if (normalized.type === "item.completed" && ev.item && typeof ev.item === "object") {
|
|
2155
|
+
emitItemEvent(ev.item, "completed");
|
|
2156
|
+
handled = true;
|
|
2157
|
+
}
|
|
2158
|
+
if (normalized.type === "error") {
|
|
2159
|
+
emitError(normalized.message || "Codex run failed", providerDetail);
|
|
2160
|
+
handled = true;
|
|
2161
|
+
}
|
|
1362
2162
|
if (isTerminalEvent(ev) && !didFinalize) {
|
|
1363
2163
|
if (ev.type === "turn.failed") {
|
|
1364
2164
|
const message = ev.error?.message;
|
|
1365
|
-
emitError(typeof message === "string" ? message : "Codex run failed");
|
|
2165
|
+
emitError(typeof message === "string" ? message : "Codex run failed", providerDetail);
|
|
1366
2166
|
didFinalize = true;
|
|
2167
|
+
handled = true;
|
|
1367
2168
|
return;
|
|
1368
2169
|
}
|
|
1369
2170
|
if (!sawError) {
|
|
1370
2171
|
didFinalize = true;
|
|
1371
|
-
emitFinal(aggregated);
|
|
2172
|
+
emitFinal(aggregated, providerDetail);
|
|
2173
|
+
handled = true;
|
|
1372
2174
|
}
|
|
1373
2175
|
}
|
|
2176
|
+
if (!handled) {
|
|
2177
|
+
emit({ type: "detail", provider: "codex", providerDetail });
|
|
2178
|
+
}
|
|
1374
2179
|
};
|
|
1375
2180
|
const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
|
|
1376
2181
|
const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
|
|
@@ -1421,32 +2226,829 @@ function runCodexPrompt({
|
|
|
1421
2226
|
});
|
|
1422
2227
|
}
|
|
1423
2228
|
|
|
1424
|
-
// src/providers/
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2229
|
+
// src/providers/cursor.ts
|
|
2230
|
+
import { spawn as spawn4 } from "child_process";
|
|
2231
|
+
import path4 from "path";
|
|
2232
|
+
var INSTALL_UNIX2 = "curl https://cursor.com/install -fsS | bash";
|
|
2233
|
+
var DEFAULT_LOGIN3 = "cursor-agent login";
|
|
2234
|
+
var DEFAULT_STATUS3 = "cursor-agent status";
|
|
2235
|
+
var CURSOR_MODELS_COMMAND = "cursor-agent models";
|
|
2236
|
+
var CURSOR_MODELS_CACHE_TTL_MS = 6e4;
|
|
2237
|
+
var CURSOR_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
2238
|
+
var cursorModelsCache = null;
|
|
2239
|
+
var cursorModelsCacheAt = 0;
|
|
2240
|
+
var cursorUpdateCache = null;
|
|
2241
|
+
var cursorUpdatePromise = null;
|
|
2242
|
+
function trimOutput3(value, limit = 400) {
|
|
2243
|
+
const cleaned = value.trim();
|
|
2244
|
+
if (!cleaned) return "";
|
|
2245
|
+
if (cleaned.length <= limit) return cleaned;
|
|
2246
|
+
return `${cleaned.slice(0, limit)}...`;
|
|
1428
2247
|
}
|
|
1429
|
-
function
|
|
1430
|
-
|
|
2248
|
+
function normalizePath3(value) {
|
|
2249
|
+
const normalized = value.replace(/\\/g, "/");
|
|
2250
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
1431
2251
|
}
|
|
1432
|
-
function
|
|
1433
|
-
if (!
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
|
|
1437
|
-
if (raw.startsWith("local/")) return raw.slice("local/".length);
|
|
1438
|
-
return raw;
|
|
2252
|
+
function parseSemver3(value) {
|
|
2253
|
+
if (!value) return null;
|
|
2254
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
2255
|
+
if (!match) return null;
|
|
2256
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1439
2257
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
2258
|
+
function compareSemver3(a, b) {
|
|
2259
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
2260
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
2261
|
+
return a[2] - b[2];
|
|
2262
|
+
}
|
|
2263
|
+
async function fetchBrewCaskVersion2(cask) {
|
|
2264
|
+
if (!commandExists("brew")) return null;
|
|
2265
|
+
const result = await runCommand("brew", ["info", "--json=v2", "--cask", cask]);
|
|
2266
|
+
if (result.code !== 0) return null;
|
|
1443
2267
|
try {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
2268
|
+
const parsed = JSON.parse(result.stdout);
|
|
2269
|
+
const version = parsed?.casks?.[0]?.version;
|
|
2270
|
+
return typeof version === "string" ? version : null;
|
|
2271
|
+
} catch {
|
|
2272
|
+
return null;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function getCursorUpdateAction(commandPath) {
|
|
2276
|
+
if (!commandPath) return null;
|
|
2277
|
+
const normalized = normalizePath3(commandPath);
|
|
2278
|
+
if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/")) {
|
|
2279
|
+
return {
|
|
2280
|
+
command: "brew",
|
|
2281
|
+
args: ["upgrade", "--cask", "cursor"],
|
|
2282
|
+
source: "brew",
|
|
2283
|
+
commandLabel: "brew upgrade --cask cursor"
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
if (normalized.includes("/.local/bin/") || normalized.includes("/.local/share/cursor-agent/versions/")) {
|
|
2287
|
+
return {
|
|
2288
|
+
command: "bash",
|
|
2289
|
+
args: ["-lc", INSTALL_UNIX2],
|
|
2290
|
+
source: "script",
|
|
2291
|
+
commandLabel: INSTALL_UNIX2
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
return null;
|
|
2295
|
+
}
|
|
2296
|
+
function getCursorCommand() {
|
|
2297
|
+
const override = process.env.AGENTCONNECT_CURSOR_COMMAND;
|
|
2298
|
+
const base = override || "cursor-agent";
|
|
2299
|
+
const resolved = resolveCommandPath(base);
|
|
2300
|
+
return resolved || resolveWindowsCommand(base);
|
|
2301
|
+
}
|
|
2302
|
+
function getCursorApiKey() {
|
|
2303
|
+
return process.env.CURSOR_API_KEY || process.env.AGENTCONNECT_CURSOR_API_KEY || "";
|
|
2304
|
+
}
|
|
2305
|
+
function getCursorDefaultModel() {
|
|
2306
|
+
return process.env.AGENTCONNECT_CURSOR_MODEL?.trim() || "";
|
|
2307
|
+
}
|
|
2308
|
+
function resolveCursorEndpoint() {
|
|
2309
|
+
return process.env.AGENTCONNECT_CURSOR_ENDPOINT?.trim() || "";
|
|
2310
|
+
}
|
|
2311
|
+
function withCursorEndpoint(args2) {
|
|
2312
|
+
const endpoint = resolveCursorEndpoint();
|
|
2313
|
+
if (!endpoint) return args2;
|
|
2314
|
+
if (args2.includes("--endpoint")) return args2;
|
|
2315
|
+
return [...args2, "--endpoint", endpoint];
|
|
2316
|
+
}
|
|
2317
|
+
function resolveCursorModel(model, fallback) {
|
|
2318
|
+
if (!model) return fallback;
|
|
2319
|
+
const raw = String(model);
|
|
2320
|
+
if (raw === "cursor" || raw === "cursor-default") return fallback;
|
|
2321
|
+
if (raw.startsWith("cursor:")) return raw.slice("cursor:".length);
|
|
2322
|
+
if (raw.startsWith("cursor/")) return raw.slice("cursor/".length);
|
|
2323
|
+
return raw;
|
|
2324
|
+
}
|
|
2325
|
+
function formatCursorDefaultLabel(fallback) {
|
|
2326
|
+
if (!fallback) return "Default";
|
|
2327
|
+
return `Default \xB7 ${fallback}`;
|
|
2328
|
+
}
|
|
2329
|
+
function normalizeCursorModelId(value) {
|
|
2330
|
+
if (value.startsWith("cursor:") || value.startsWith("cursor/")) return value;
|
|
2331
|
+
return `cursor:${value}`;
|
|
2332
|
+
}
|
|
2333
|
+
function normalizeCursorModelDisplay(value) {
|
|
2334
|
+
if (value.startsWith("cursor:")) return value.slice("cursor:".length);
|
|
2335
|
+
if (value.startsWith("cursor/")) return value.slice("cursor/".length);
|
|
2336
|
+
return value;
|
|
2337
|
+
}
|
|
2338
|
+
async function listCursorModelsFromCli() {
|
|
2339
|
+
const command2 = getCursorCommand();
|
|
2340
|
+
if (!commandExists(command2)) return [];
|
|
2341
|
+
const modelsCommand = buildStatusCommand(
|
|
2342
|
+
"AGENTCONNECT_CURSOR_MODELS_COMMAND",
|
|
2343
|
+
CURSOR_MODELS_COMMAND
|
|
2344
|
+
);
|
|
2345
|
+
if (!modelsCommand.command) return [];
|
|
2346
|
+
const resolvedCommand = resolveWindowsCommand(modelsCommand.command);
|
|
2347
|
+
const result = await runCommand(resolvedCommand, withCursorEndpoint(modelsCommand.args), {
|
|
2348
|
+
env: buildCursorEnv()
|
|
2349
|
+
});
|
|
2350
|
+
const output = `${result.stdout}
|
|
2351
|
+
${result.stderr}`.trim();
|
|
2352
|
+
if (!output) return [];
|
|
2353
|
+
const parsed = safeJsonParse3(output);
|
|
2354
|
+
if (Array.isArray(parsed)) {
|
|
2355
|
+
const models = parsed.map((entry) => {
|
|
2356
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2357
|
+
const value = entry.trim();
|
|
2358
|
+
return {
|
|
2359
|
+
id: normalizeCursorModelId(value),
|
|
2360
|
+
provider: "cursor",
|
|
2361
|
+
displayName: normalizeCursorModelDisplay(value)
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
if (entry && typeof entry === "object") {
|
|
2365
|
+
const record = entry;
|
|
2366
|
+
const idRaw = typeof record.id === "string" ? record.id.trim() : "";
|
|
2367
|
+
const nameRaw = typeof record.name === "string" ? record.name.trim() : "";
|
|
2368
|
+
const displayRaw = typeof record.displayName === "string" ? record.displayName.trim() : "";
|
|
2369
|
+
const value = idRaw || nameRaw || displayRaw;
|
|
2370
|
+
if (!value) return null;
|
|
2371
|
+
return {
|
|
2372
|
+
id: normalizeCursorModelId(value),
|
|
2373
|
+
provider: "cursor",
|
|
2374
|
+
displayName: normalizeCursorModelDisplay(displayRaw || nameRaw || value)
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
return null;
|
|
2378
|
+
}).filter(Boolean);
|
|
2379
|
+
return models;
|
|
2380
|
+
}
|
|
2381
|
+
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => !line.toLowerCase().startsWith("model")).filter((line) => !/^[-=]{2,}$/.test(line));
|
|
2382
|
+
return lines.map((line) => {
|
|
2383
|
+
const cleaned = line.replace(/^[-*•]\s*/, "");
|
|
2384
|
+
const value = cleaned.split(/\s+/)[0] || cleaned;
|
|
2385
|
+
return {
|
|
2386
|
+
id: normalizeCursorModelId(value),
|
|
2387
|
+
provider: "cursor",
|
|
2388
|
+
displayName: normalizeCursorModelDisplay(value)
|
|
2389
|
+
};
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
async function listCursorModels() {
|
|
2393
|
+
if (cursorModelsCache && Date.now() - cursorModelsCacheAt < CURSOR_MODELS_CACHE_TTL_MS) {
|
|
2394
|
+
return cursorModelsCache;
|
|
2395
|
+
}
|
|
2396
|
+
const fallback = getCursorDefaultModel();
|
|
2397
|
+
const base = [
|
|
2398
|
+
{
|
|
2399
|
+
id: "cursor-default",
|
|
2400
|
+
provider: "cursor",
|
|
2401
|
+
displayName: formatCursorDefaultLabel(fallback)
|
|
2402
|
+
}
|
|
2403
|
+
];
|
|
2404
|
+
const envModels = process.env.AGENTCONNECT_CURSOR_MODELS;
|
|
2405
|
+
if (envModels) {
|
|
2406
|
+
try {
|
|
2407
|
+
const parsed = JSON.parse(envModels);
|
|
2408
|
+
if (Array.isArray(parsed)) {
|
|
2409
|
+
for (const entry of parsed) {
|
|
2410
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2411
|
+
const trimmed = entry.trim();
|
|
2412
|
+
base.push({
|
|
2413
|
+
id: normalizeCursorModelId(trimmed),
|
|
2414
|
+
provider: "cursor",
|
|
2415
|
+
displayName: normalizeCursorModelDisplay(trimmed)
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
} catch {
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
const cliModels = await listCursorModelsFromCli();
|
|
2424
|
+
base.push(...cliModels);
|
|
2425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2426
|
+
const list = base.filter((entry) => {
|
|
2427
|
+
const key = `${entry.provider}:${entry.id}`;
|
|
2428
|
+
if (seen.has(key)) return false;
|
|
2429
|
+
seen.add(key);
|
|
2430
|
+
return true;
|
|
2431
|
+
});
|
|
2432
|
+
cursorModelsCache = list;
|
|
2433
|
+
cursorModelsCacheAt = Date.now();
|
|
2434
|
+
return list;
|
|
2435
|
+
}
|
|
2436
|
+
function normalizeCursorStatusOutput(output) {
|
|
2437
|
+
const text = output.toLowerCase();
|
|
2438
|
+
if (text.includes("not authenticated") || text.includes("not logged in") || text.includes("login required") || text.includes("please login") || text.includes("please log in") || text.includes("unauthorized")) {
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
if (text.includes("authenticated") || text.includes("logged in") || text.includes("signed in") || text.includes("account")) {
|
|
2442
|
+
return true;
|
|
2443
|
+
}
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
function getCursorUpdateSnapshot(commandPath) {
|
|
2447
|
+
if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
|
|
2448
|
+
const action = getCursorUpdateAction(commandPath);
|
|
2449
|
+
return {
|
|
2450
|
+
updateAvailable: cursorUpdateCache.updateAvailable,
|
|
2451
|
+
latestVersion: cursorUpdateCache.latestVersion,
|
|
2452
|
+
updateCheckedAt: cursorUpdateCache.checkedAt,
|
|
2453
|
+
updateSource: action?.source ?? "unknown",
|
|
2454
|
+
updateCommand: action?.commandLabel,
|
|
2455
|
+
updateMessage: cursorUpdateCache.updateMessage
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
return {};
|
|
2459
|
+
}
|
|
2460
|
+
function ensureCursorUpdateCheck(currentVersion, commandPath) {
|
|
2461
|
+
if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
if (cursorUpdatePromise) return;
|
|
2465
|
+
cursorUpdatePromise = (async () => {
|
|
2466
|
+
const action = getCursorUpdateAction(commandPath || null);
|
|
2467
|
+
let latest = null;
|
|
2468
|
+
let updateAvailable;
|
|
2469
|
+
let updateMessage;
|
|
2470
|
+
if (action?.source === "brew") {
|
|
2471
|
+
latest = await fetchBrewCaskVersion2("cursor");
|
|
2472
|
+
}
|
|
2473
|
+
if (latest && currentVersion) {
|
|
2474
|
+
const a = parseSemver3(currentVersion);
|
|
2475
|
+
const b = parseSemver3(latest);
|
|
2476
|
+
if (a && b) {
|
|
2477
|
+
updateAvailable = compareSemver3(a, b) < 0;
|
|
2478
|
+
updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
|
|
2479
|
+
}
|
|
2480
|
+
} else if (!action) {
|
|
2481
|
+
updateMessage = "Update check unavailable";
|
|
2482
|
+
}
|
|
2483
|
+
debugLog("Cursor", "update-check", {
|
|
2484
|
+
updateAvailable,
|
|
2485
|
+
message: updateMessage
|
|
2486
|
+
});
|
|
2487
|
+
cursorUpdateCache = {
|
|
2488
|
+
checkedAt: Date.now(),
|
|
2489
|
+
updateAvailable,
|
|
2490
|
+
latestVersion: latest ?? void 0,
|
|
2491
|
+
updateMessage
|
|
2492
|
+
};
|
|
2493
|
+
})().finally(() => {
|
|
2494
|
+
cursorUpdatePromise = null;
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
async function ensureCursorInstalled() {
|
|
2498
|
+
const command2 = getCursorCommand();
|
|
2499
|
+
const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2500
|
+
debugLog("Cursor", "install-check", {
|
|
2501
|
+
command: command2,
|
|
2502
|
+
versionOk: versionCheck.ok,
|
|
2503
|
+
version: versionCheck.version
|
|
2504
|
+
});
|
|
2505
|
+
if (versionCheck.ok) {
|
|
2506
|
+
return { installed: true, version: versionCheck.version || void 0 };
|
|
2507
|
+
}
|
|
2508
|
+
if (commandExists(command2)) {
|
|
2509
|
+
return { installed: true, version: void 0 };
|
|
2510
|
+
}
|
|
2511
|
+
const override = buildInstallCommand("AGENTCONNECT_CURSOR_INSTALL", "");
|
|
2512
|
+
let install = override;
|
|
2513
|
+
let packageManager = override.command ? "unknown" : "unknown";
|
|
2514
|
+
if (!install.command) {
|
|
2515
|
+
if (process.platform !== "win32" && commandExists("bash") && commandExists("curl")) {
|
|
2516
|
+
install = { command: "bash", args: ["-lc", INSTALL_UNIX2] };
|
|
2517
|
+
packageManager = "script";
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
if (!install.command) {
|
|
2521
|
+
return { installed: false, version: void 0, packageManager };
|
|
2522
|
+
}
|
|
2523
|
+
await runCommand(install.command, install.args, { shell: process.platform === "win32" });
|
|
2524
|
+
const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2525
|
+
return {
|
|
2526
|
+
installed: after.ok,
|
|
2527
|
+
version: after.version || void 0,
|
|
2528
|
+
packageManager
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
async function getCursorStatus() {
|
|
2532
|
+
const command2 = getCursorCommand();
|
|
2533
|
+
const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2534
|
+
const installed = versionCheck.ok || commandExists(command2);
|
|
2535
|
+
let loggedIn = false;
|
|
2536
|
+
if (installed) {
|
|
2537
|
+
const status = buildStatusCommand("AGENTCONNECT_CURSOR_STATUS", DEFAULT_STATUS3);
|
|
2538
|
+
if (status.command) {
|
|
2539
|
+
const statusCommand = resolveWindowsCommand(status.command);
|
|
2540
|
+
const result = await runCommand(statusCommand, withCursorEndpoint(status.args), {
|
|
2541
|
+
env: buildCursorEnv()
|
|
2542
|
+
});
|
|
2543
|
+
const output = `${result.stdout}
|
|
2544
|
+
${result.stderr}`;
|
|
2545
|
+
const parsed = normalizeCursorStatusOutput(output);
|
|
2546
|
+
loggedIn = parsed ?? result.code === 0;
|
|
2547
|
+
}
|
|
2548
|
+
if (!loggedIn) {
|
|
2549
|
+
loggedIn = Boolean(getCursorApiKey().trim());
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
if (installed) {
|
|
2553
|
+
const resolved2 = resolveCommandRealPath(command2);
|
|
2554
|
+
ensureCursorUpdateCheck(versionCheck.version, resolved2 || null);
|
|
2555
|
+
}
|
|
2556
|
+
const resolved = resolveCommandRealPath(command2);
|
|
2557
|
+
const updateInfo = installed ? getCursorUpdateSnapshot(resolved || null) : {};
|
|
2558
|
+
return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
|
|
2559
|
+
}
|
|
2560
|
+
async function getCursorFastStatus() {
|
|
2561
|
+
const command2 = getCursorCommand();
|
|
2562
|
+
const installed = commandExists(command2);
|
|
2563
|
+
if (!installed) {
|
|
2564
|
+
return { installed: false, loggedIn: false };
|
|
2565
|
+
}
|
|
2566
|
+
return { installed: true, loggedIn: true };
|
|
2567
|
+
}
|
|
2568
|
+
async function updateCursor() {
|
|
2569
|
+
const command2 = getCursorCommand();
|
|
2570
|
+
if (!commandExists(command2)) {
|
|
2571
|
+
return { installed: false, loggedIn: false };
|
|
2572
|
+
}
|
|
2573
|
+
const resolved = resolveCommandRealPath(command2);
|
|
2574
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CURSOR_UPDATE", "");
|
|
2575
|
+
const action = updateOverride.command ? null : getCursorUpdateAction(resolved || null);
|
|
2576
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
2577
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
2578
|
+
if (!updateCommand) {
|
|
2579
|
+
throw new Error("No update command available. Please update Cursor manually.");
|
|
2580
|
+
}
|
|
2581
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
2582
|
+
debugLog("Cursor", "update-run", { command: cmd, args: updateArgs });
|
|
2583
|
+
const result = await runCommand(cmd, updateArgs, { env: buildCursorEnv() });
|
|
2584
|
+
debugLog("Cursor", "update-result", {
|
|
2585
|
+
code: result.code,
|
|
2586
|
+
stdout: trimOutput3(result.stdout),
|
|
2587
|
+
stderr: trimOutput3(result.stderr)
|
|
2588
|
+
});
|
|
2589
|
+
if (result.code !== 0 && result.code !== null) {
|
|
2590
|
+
const message = trimOutput3(`${result.stdout}
|
|
2591
|
+
${result.stderr}`, 800) || "Update failed";
|
|
2592
|
+
throw new Error(message);
|
|
2593
|
+
}
|
|
2594
|
+
cursorUpdateCache = null;
|
|
2595
|
+
cursorUpdatePromise = null;
|
|
2596
|
+
return getCursorStatus();
|
|
2597
|
+
}
|
|
2598
|
+
function buildCursorEnv() {
|
|
2599
|
+
const env = { ...process.env };
|
|
2600
|
+
const apiKey = getCursorApiKey().trim();
|
|
2601
|
+
if (apiKey) {
|
|
2602
|
+
env.CURSOR_API_KEY = apiKey;
|
|
2603
|
+
}
|
|
2604
|
+
return env;
|
|
2605
|
+
}
|
|
2606
|
+
async function loginCursor(options) {
|
|
2607
|
+
if (typeof options?.apiKey === "string") {
|
|
2608
|
+
process.env.CURSOR_API_KEY = options.apiKey;
|
|
2609
|
+
}
|
|
2610
|
+
if (typeof options?.baseUrl === "string") {
|
|
2611
|
+
process.env.AGENTCONNECT_CURSOR_ENDPOINT = options.baseUrl;
|
|
2612
|
+
}
|
|
2613
|
+
if (typeof options?.model === "string") {
|
|
2614
|
+
process.env.AGENTCONNECT_CURSOR_MODEL = options.model;
|
|
2615
|
+
cursorModelsCache = null;
|
|
2616
|
+
cursorModelsCacheAt = 0;
|
|
2617
|
+
}
|
|
2618
|
+
if (Array.isArray(options?.models)) {
|
|
2619
|
+
process.env.AGENTCONNECT_CURSOR_MODELS = JSON.stringify(options.models.filter(Boolean));
|
|
2620
|
+
cursorModelsCache = null;
|
|
2621
|
+
cursorModelsCacheAt = 0;
|
|
2622
|
+
}
|
|
2623
|
+
if (!options?.apiKey) {
|
|
2624
|
+
const login = buildLoginCommand("AGENTCONNECT_CURSOR_LOGIN", DEFAULT_LOGIN3);
|
|
2625
|
+
if (login.command) {
|
|
2626
|
+
const command2 = resolveWindowsCommand(login.command);
|
|
2627
|
+
await runCommand(command2, withCursorEndpoint(login.args), { env: buildCursorEnv() });
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
const timeoutMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_TIMEOUT_MS || 2e4);
|
|
2631
|
+
const pollIntervalMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_POLL_MS || 1e3);
|
|
2632
|
+
const start = Date.now();
|
|
2633
|
+
let status = await getCursorStatus();
|
|
2634
|
+
while (!status.loggedIn && Date.now() - start < timeoutMs) {
|
|
2635
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2636
|
+
status = await getCursorStatus();
|
|
2637
|
+
}
|
|
2638
|
+
return { loggedIn: status.loggedIn };
|
|
2639
|
+
}
|
|
2640
|
+
function safeJsonParse3(line) {
|
|
2641
|
+
try {
|
|
2642
|
+
return JSON.parse(line);
|
|
2643
|
+
} catch {
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
function extractSessionId3(ev) {
|
|
2648
|
+
const id = ev.session_id ?? ev.sessionId;
|
|
2649
|
+
return typeof id === "string" ? id : null;
|
|
2650
|
+
}
|
|
2651
|
+
function extractUsage2(ev) {
|
|
2652
|
+
const usage = ev.usage ?? ev.token_usage ?? ev.tokenUsage ?? ev.tokens;
|
|
2653
|
+
if (!usage || typeof usage !== "object") return null;
|
|
2654
|
+
const toNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2655
|
+
const input = toNumber(
|
|
2656
|
+
usage.input_tokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.promptTokens
|
|
2657
|
+
);
|
|
2658
|
+
const output = toNumber(
|
|
2659
|
+
usage.output_tokens ?? usage.completion_tokens ?? usage.outputTokens ?? usage.completionTokens
|
|
2660
|
+
);
|
|
2661
|
+
const total = toNumber(usage.total_tokens ?? usage.totalTokens);
|
|
2662
|
+
const out = {};
|
|
2663
|
+
if (input !== void 0) out.input_tokens = input;
|
|
2664
|
+
if (output !== void 0) out.output_tokens = output;
|
|
2665
|
+
if (total !== void 0) out.total_tokens = total;
|
|
2666
|
+
return Object.keys(out).length ? out : null;
|
|
2667
|
+
}
|
|
2668
|
+
function extractTextFromContent2(content) {
|
|
2669
|
+
if (!content) return "";
|
|
2670
|
+
if (typeof content === "string") return content;
|
|
2671
|
+
if (Array.isArray(content)) {
|
|
2672
|
+
return content.map((part) => {
|
|
2673
|
+
if (!part) return "";
|
|
2674
|
+
if (typeof part === "string") return part;
|
|
2675
|
+
if (typeof part === "object") {
|
|
2676
|
+
const text = part.text;
|
|
2677
|
+
if (typeof text === "string") return text;
|
|
2678
|
+
}
|
|
2679
|
+
return "";
|
|
2680
|
+
}).join("");
|
|
2681
|
+
}
|
|
2682
|
+
if (typeof content === "object") {
|
|
2683
|
+
const text = content.text;
|
|
2684
|
+
if (typeof text === "string") return text;
|
|
2685
|
+
}
|
|
2686
|
+
return "";
|
|
2687
|
+
}
|
|
2688
|
+
function extractToolCall(ev) {
|
|
2689
|
+
if (ev.type !== "tool_call") return null;
|
|
2690
|
+
const toolCall = ev.tool_call && typeof ev.tool_call === "object" ? ev.tool_call : null;
|
|
2691
|
+
if (!toolCall) return null;
|
|
2692
|
+
const keys = Object.keys(toolCall);
|
|
2693
|
+
if (!keys.length) return null;
|
|
2694
|
+
const name = keys[0];
|
|
2695
|
+
const entry = toolCall[name];
|
|
2696
|
+
const record = entry && typeof entry === "object" ? entry : null;
|
|
2697
|
+
const input = record?.args ?? record?.input ?? void 0;
|
|
2698
|
+
const output = record?.output ?? record?.result ?? void 0;
|
|
2699
|
+
const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
|
|
2700
|
+
const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : subtype === "delta" ? "delta" : void 0;
|
|
2701
|
+
return {
|
|
2702
|
+
name,
|
|
2703
|
+
input,
|
|
2704
|
+
output,
|
|
2705
|
+
callId: typeof ev.call_id === "string" ? ev.call_id : void 0,
|
|
2706
|
+
phase
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
function extractTextFromMessage(message) {
|
|
2710
|
+
if (!message || typeof message !== "object") return "";
|
|
2711
|
+
const content = message.content;
|
|
2712
|
+
return extractTextFromContent2(content);
|
|
2713
|
+
}
|
|
2714
|
+
function extractAssistantDelta2(ev) {
|
|
2715
|
+
if (ev.type !== "assistant" && ev.message?.role !== "assistant") return null;
|
|
2716
|
+
if (typeof ev.text === "string") return ev.text;
|
|
2717
|
+
if (typeof ev.delta === "string") return ev.delta;
|
|
2718
|
+
if (ev.message) {
|
|
2719
|
+
const text = extractTextFromMessage(ev.message);
|
|
2720
|
+
return text || null;
|
|
2721
|
+
}
|
|
2722
|
+
if (ev.content) {
|
|
2723
|
+
const text = extractTextFromContent2(ev.content);
|
|
2724
|
+
return text || null;
|
|
2725
|
+
}
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
function extractResultText2(ev) {
|
|
2729
|
+
if (typeof ev.result === "string") return ev.result;
|
|
2730
|
+
if (ev.result && typeof ev.result === "object") {
|
|
2731
|
+
const result = ev.result;
|
|
2732
|
+
if (typeof result.text === "string") return result.text;
|
|
2733
|
+
if (result.message) {
|
|
2734
|
+
const text = extractTextFromMessage(result.message);
|
|
2735
|
+
if (text) return text;
|
|
2736
|
+
}
|
|
2737
|
+
if (result.content) {
|
|
2738
|
+
const text = extractTextFromContent2(result.content);
|
|
2739
|
+
if (text) return text;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (ev.message) {
|
|
2743
|
+
const text = extractTextFromMessage(ev.message);
|
|
2744
|
+
if (text) return text;
|
|
2745
|
+
}
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
|
+
function isErrorEvent(ev) {
|
|
2749
|
+
if (ev.type === "error") return true;
|
|
2750
|
+
if (ev.type === "result" && ev.subtype) {
|
|
2751
|
+
const subtype = String(ev.subtype).toLowerCase();
|
|
2752
|
+
if (subtype.includes("error") || subtype.includes("failed")) return true;
|
|
2753
|
+
}
|
|
2754
|
+
return false;
|
|
2755
|
+
}
|
|
2756
|
+
function extractErrorMessage(ev) {
|
|
2757
|
+
if (typeof ev.error === "string") return ev.error;
|
|
2758
|
+
if (ev.error && typeof ev.error === "object" && typeof ev.error.message === "string") {
|
|
2759
|
+
return ev.error.message;
|
|
2760
|
+
}
|
|
2761
|
+
if (typeof ev.text === "string" && ev.type === "error") return ev.text;
|
|
2762
|
+
if (typeof ev.result === "string" && ev.type === "result") return ev.result;
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
2765
|
+
function normalizeCursorEvent(raw) {
|
|
2766
|
+
if (!raw || typeof raw !== "object") return { type: "unknown" };
|
|
2767
|
+
const type = typeof raw.type === "string" ? raw.type : "unknown";
|
|
2768
|
+
return { ...raw, type };
|
|
2769
|
+
}
|
|
2770
|
+
function runCursorPrompt({
|
|
2771
|
+
prompt,
|
|
2772
|
+
resumeSessionId,
|
|
2773
|
+
model,
|
|
2774
|
+
repoRoot,
|
|
2775
|
+
cwd,
|
|
2776
|
+
providerDetailLevel,
|
|
2777
|
+
onEvent,
|
|
2778
|
+
signal
|
|
2779
|
+
}) {
|
|
2780
|
+
return new Promise((resolve) => {
|
|
2781
|
+
const command2 = getCursorCommand();
|
|
2782
|
+
const resolvedRepoRoot = repoRoot ? path4.resolve(repoRoot) : null;
|
|
2783
|
+
const resolvedCwd = cwd ? path4.resolve(cwd) : null;
|
|
2784
|
+
const runDir = resolvedCwd || resolvedRepoRoot || process.cwd();
|
|
2785
|
+
const args2 = ["--print", "--output-format", "stream-json"];
|
|
2786
|
+
if (resumeSessionId) {
|
|
2787
|
+
args2.push("--resume", resumeSessionId);
|
|
2788
|
+
}
|
|
2789
|
+
const fallbackModel = getCursorDefaultModel();
|
|
2790
|
+
const resolvedModel = resolveCursorModel(model, fallbackModel);
|
|
2791
|
+
if (resolvedModel) {
|
|
2792
|
+
args2.push("--model", resolvedModel);
|
|
2793
|
+
}
|
|
2794
|
+
const endpoint = resolveCursorEndpoint();
|
|
2795
|
+
if (endpoint) {
|
|
2796
|
+
args2.push("--endpoint", endpoint);
|
|
2797
|
+
}
|
|
2798
|
+
args2.push(prompt);
|
|
2799
|
+
const argsPreview = [...args2];
|
|
2800
|
+
if (argsPreview.length > 0) {
|
|
2801
|
+
argsPreview[argsPreview.length - 1] = "[prompt]";
|
|
2802
|
+
}
|
|
2803
|
+
debugLog("Cursor", "spawn", {
|
|
2804
|
+
command: command2,
|
|
2805
|
+
args: argsPreview,
|
|
2806
|
+
cwd: runDir,
|
|
2807
|
+
model: resolvedModel || null,
|
|
2808
|
+
endpoint: endpoint || null,
|
|
2809
|
+
resume: resumeSessionId || null,
|
|
2810
|
+
apiKeyConfigured: Boolean(getCursorApiKey().trim()),
|
|
2811
|
+
promptChars: prompt.length
|
|
2812
|
+
});
|
|
2813
|
+
const child = spawn4(command2, args2, {
|
|
2814
|
+
cwd: runDir,
|
|
2815
|
+
env: buildCursorEnv(),
|
|
2816
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2817
|
+
});
|
|
2818
|
+
if (signal) {
|
|
2819
|
+
signal.addEventListener("abort", () => {
|
|
2820
|
+
child.kill("SIGTERM");
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
let aggregated = "";
|
|
2824
|
+
let finalSessionId = null;
|
|
2825
|
+
let didFinalize = false;
|
|
2826
|
+
let sawError = false;
|
|
2827
|
+
let sawJson = false;
|
|
2828
|
+
let rawOutput = "";
|
|
2829
|
+
const stdoutLines = [];
|
|
2830
|
+
const stderrLines = [];
|
|
2831
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
2832
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
2833
|
+
const detail = { eventType };
|
|
2834
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
2835
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
2836
|
+
return detail;
|
|
2837
|
+
};
|
|
2838
|
+
const emit = (event) => {
|
|
2839
|
+
if (finalSessionId) {
|
|
2840
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
2841
|
+
} else {
|
|
2842
|
+
onEvent(event);
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
const pushLine = (list, line) => {
|
|
2846
|
+
if (!line) return;
|
|
2847
|
+
list.push(line);
|
|
2848
|
+
if (list.length > 12) list.shift();
|
|
2849
|
+
};
|
|
2850
|
+
const emitError = (message, providerDetail) => {
|
|
2851
|
+
if (sawError) return;
|
|
2852
|
+
sawError = true;
|
|
2853
|
+
emit({ type: "error", message, providerDetail });
|
|
2854
|
+
};
|
|
2855
|
+
const emitFinal = (text) => {
|
|
2856
|
+
if (didFinalize) return;
|
|
2857
|
+
didFinalize = true;
|
|
2858
|
+
emit({ type: "final", text });
|
|
2859
|
+
};
|
|
2860
|
+
const handleEvent = (ev) => {
|
|
2861
|
+
const normalized = normalizeCursorEvent(ev);
|
|
2862
|
+
if (ev?.type === "system" && ev?.subtype === "init") {
|
|
2863
|
+
debugLog("Cursor", "init", {
|
|
2864
|
+
apiKeySource: ev.apiKeySource || null,
|
|
2865
|
+
cwd: ev.cwd || null,
|
|
2866
|
+
model: ev.model || null,
|
|
2867
|
+
permissionMode: ev.permissionMode || null,
|
|
2868
|
+
sessionId: ev.session_id ?? ev.sessionId ?? null
|
|
2869
|
+
});
|
|
2870
|
+
emit({
|
|
2871
|
+
type: "detail",
|
|
2872
|
+
provider: "cursor",
|
|
2873
|
+
providerDetail: buildProviderDetail(
|
|
2874
|
+
"system.init",
|
|
2875
|
+
{
|
|
2876
|
+
apiKeySource: ev.apiKeySource,
|
|
2877
|
+
cwd: ev.cwd,
|
|
2878
|
+
model: ev.model,
|
|
2879
|
+
permissionMode: ev.permissionMode
|
|
2880
|
+
},
|
|
2881
|
+
ev
|
|
2882
|
+
)
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
const sid = extractSessionId3(ev);
|
|
2886
|
+
if (sid) finalSessionId = sid;
|
|
2887
|
+
if (ev?.type === "thinking") {
|
|
2888
|
+
const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
|
|
2889
|
+
const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : "delta";
|
|
2890
|
+
emit({
|
|
2891
|
+
type: "thinking",
|
|
2892
|
+
provider: "cursor",
|
|
2893
|
+
phase,
|
|
2894
|
+
text: typeof ev.text === "string" ? ev.text : "",
|
|
2895
|
+
timestampMs: typeof ev.timestamp_ms === "number" ? ev.timestamp_ms : void 0,
|
|
2896
|
+
providerDetail: buildProviderDetail(
|
|
2897
|
+
subtype ? `thinking.${subtype}` : "thinking",
|
|
2898
|
+
{
|
|
2899
|
+
subtype: subtype || void 0
|
|
2900
|
+
},
|
|
2901
|
+
ev
|
|
2902
|
+
)
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
if (ev?.type === "assistant" || ev?.type === "user") {
|
|
2906
|
+
const role = ev.message?.role === "assistant" || ev.message?.role === "user" ? ev.message?.role : ev.type;
|
|
2907
|
+
const rawContent = ev.message?.content ?? ev.content;
|
|
2908
|
+
const content = extractTextFromContent2(rawContent);
|
|
2909
|
+
emit({
|
|
2910
|
+
type: "message",
|
|
2911
|
+
provider: "cursor",
|
|
2912
|
+
role,
|
|
2913
|
+
content,
|
|
2914
|
+
contentParts: rawContent ?? null,
|
|
2915
|
+
providerDetail: buildProviderDetail(ev.type, {}, ev)
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
const toolCall = extractToolCall(ev);
|
|
2919
|
+
if (toolCall) {
|
|
2920
|
+
emit({
|
|
2921
|
+
type: "tool_call",
|
|
2922
|
+
provider: "cursor",
|
|
2923
|
+
name: toolCall.name,
|
|
2924
|
+
callId: toolCall.callId,
|
|
2925
|
+
input: toolCall.input,
|
|
2926
|
+
output: toolCall.output,
|
|
2927
|
+
phase: toolCall.phase,
|
|
2928
|
+
providerDetail: buildProviderDetail(
|
|
2929
|
+
ev.subtype ? `tool_call.${ev.subtype}` : "tool_call",
|
|
2930
|
+
{
|
|
2931
|
+
name: toolCall.name,
|
|
2932
|
+
callId: toolCall.callId,
|
|
2933
|
+
subtype: ev.subtype
|
|
2934
|
+
},
|
|
2935
|
+
ev
|
|
2936
|
+
)
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
const usage = extractUsage2(ev);
|
|
2940
|
+
if (usage) {
|
|
2941
|
+
emit({
|
|
2942
|
+
type: "usage",
|
|
2943
|
+
inputTokens: usage.input_tokens,
|
|
2944
|
+
outputTokens: usage.output_tokens
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
if (isErrorEvent(ev)) {
|
|
2948
|
+
const message = extractErrorMessage(ev) || "Cursor run failed";
|
|
2949
|
+
emitError(message, buildProviderDetail(ev.subtype ? `error.${ev.subtype}` : "error", {}, ev));
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
const delta = extractAssistantDelta2(ev);
|
|
2953
|
+
if (delta) {
|
|
2954
|
+
aggregated += delta;
|
|
2955
|
+
emit({ type: "delta", text: delta, providerDetail: buildProviderDetail("delta", {}, ev) });
|
|
2956
|
+
}
|
|
2957
|
+
if (ev.type === "result") {
|
|
2958
|
+
const resultText = extractResultText2(ev);
|
|
2959
|
+
if (!aggregated && resultText) {
|
|
2960
|
+
aggregated = resultText;
|
|
2961
|
+
}
|
|
2962
|
+
if (!sawError) {
|
|
2963
|
+
emitFinal(aggregated || resultText || "");
|
|
2964
|
+
emit({
|
|
2965
|
+
type: "detail",
|
|
2966
|
+
provider: "cursor",
|
|
2967
|
+
providerDetail: buildProviderDetail(
|
|
2968
|
+
"result",
|
|
2969
|
+
{
|
|
2970
|
+
subtype: ev.subtype,
|
|
2971
|
+
duration_ms: typeof ev.duration_ms === "number" ? ev.duration_ms : void 0,
|
|
2972
|
+
request_id: typeof ev.request_id === "string" ? ev.request_id : void 0,
|
|
2973
|
+
is_error: typeof ev.is_error === "boolean" ? ev.is_error : void 0
|
|
2974
|
+
},
|
|
2975
|
+
ev
|
|
2976
|
+
)
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
};
|
|
2981
|
+
const handleLine = (line, source) => {
|
|
2982
|
+
const trimmed = line.trim();
|
|
2983
|
+
if (!trimmed) return;
|
|
2984
|
+
const payload = trimmed.startsWith("data: ") ? trimmed.slice(6).trim() : trimmed;
|
|
2985
|
+
const parsed = safeJsonParse3(payload);
|
|
2986
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2987
|
+
emit({ type: "raw_line", line });
|
|
2988
|
+
if (source === "stdout") {
|
|
2989
|
+
rawOutput += `${line}
|
|
2990
|
+
`;
|
|
2991
|
+
pushLine(stdoutLines, line);
|
|
2992
|
+
} else {
|
|
2993
|
+
pushLine(stderrLines, line);
|
|
2994
|
+
}
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
sawJson = true;
|
|
2998
|
+
handleEvent(parsed);
|
|
2999
|
+
};
|
|
3000
|
+
const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
|
|
3001
|
+
const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
|
|
3002
|
+
child.stdout?.on("data", stdoutParser);
|
|
3003
|
+
child.stderr?.on("data", stderrParser);
|
|
3004
|
+
child.on("close", (code) => {
|
|
3005
|
+
if (!didFinalize) {
|
|
3006
|
+
if (code && code !== 0) {
|
|
3007
|
+
const hint = stderrLines.at(-1) || stdoutLines.at(-1) || "";
|
|
3008
|
+
const suffix = hint ? `: ${hint}` : "";
|
|
3009
|
+
debugLog("Cursor", "exit", { code, stderr: stderrLines, stdout: stdoutLines });
|
|
3010
|
+
emitError(`Cursor CLI exited with code ${code}${suffix}`);
|
|
3011
|
+
} else if (!sawError) {
|
|
3012
|
+
const fallback = !sawJson ? rawOutput.trim() : "";
|
|
3013
|
+
emitFinal(aggregated || fallback);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
resolve({ sessionId: finalSessionId });
|
|
3017
|
+
});
|
|
3018
|
+
child.on("error", (err) => {
|
|
3019
|
+
debugLog("Cursor", "spawn-error", { message: err?.message });
|
|
3020
|
+
emitError(err?.message ?? "Cursor failed to start");
|
|
3021
|
+
resolve({ sessionId: finalSessionId });
|
|
3022
|
+
});
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
// src/providers/local.ts
|
|
3027
|
+
function getLocalBaseUrl() {
|
|
3028
|
+
const base = process.env.AGENTCONNECT_LOCAL_BASE_URL || "http://localhost:11434/v1";
|
|
3029
|
+
return base.replace(/\/+$/, "");
|
|
3030
|
+
}
|
|
3031
|
+
function getLocalApiKey() {
|
|
3032
|
+
return process.env.AGENTCONNECT_LOCAL_API_KEY || "";
|
|
3033
|
+
}
|
|
3034
|
+
function resolveLocalModel(model, fallback) {
|
|
3035
|
+
if (!model) return fallback;
|
|
3036
|
+
const raw = String(model);
|
|
3037
|
+
if (raw === "local") return fallback;
|
|
3038
|
+
if (raw.startsWith("local:")) return raw.slice("local:".length);
|
|
3039
|
+
if (raw.startsWith("local/")) return raw.slice("local/".length);
|
|
3040
|
+
return raw;
|
|
3041
|
+
}
|
|
3042
|
+
async function fetchJson3(url, options = {}) {
|
|
3043
|
+
const controller = new AbortController();
|
|
3044
|
+
const timer = setTimeout(() => controller.abort(), 4e3);
|
|
3045
|
+
try {
|
|
3046
|
+
const res = await fetch(url, { ...options, signal: controller.signal });
|
|
3047
|
+
if (!res.ok) {
|
|
3048
|
+
return { ok: false, status: res.status, data: null };
|
|
3049
|
+
}
|
|
3050
|
+
const data = await res.json();
|
|
3051
|
+
return { ok: true, status: res.status, data };
|
|
1450
3052
|
} catch {
|
|
1451
3053
|
return { ok: false, status: 0, data: null };
|
|
1452
3054
|
} finally {
|
|
@@ -1455,15 +3057,18 @@ async function fetchJson(url, options = {}) {
|
|
|
1455
3057
|
}
|
|
1456
3058
|
async function ensureLocalInstalled() {
|
|
1457
3059
|
const base = getLocalBaseUrl();
|
|
1458
|
-
const res = await
|
|
3060
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1459
3061
|
return { installed: res.ok };
|
|
1460
3062
|
}
|
|
1461
3063
|
async function getLocalStatus() {
|
|
1462
3064
|
const base = getLocalBaseUrl();
|
|
1463
|
-
const res = await
|
|
3065
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1464
3066
|
if (!res.ok) return { installed: false, loggedIn: false };
|
|
1465
3067
|
return { installed: true, loggedIn: true };
|
|
1466
3068
|
}
|
|
3069
|
+
async function updateLocal() {
|
|
3070
|
+
return getLocalStatus();
|
|
3071
|
+
}
|
|
1467
3072
|
async function loginLocal(options = {}) {
|
|
1468
3073
|
if (typeof options.baseUrl === "string") {
|
|
1469
3074
|
process.env.AGENTCONNECT_LOCAL_BASE_URL = options.baseUrl;
|
|
@@ -1482,7 +3087,7 @@ async function loginLocal(options = {}) {
|
|
|
1482
3087
|
}
|
|
1483
3088
|
async function listLocalModels() {
|
|
1484
3089
|
const base = getLocalBaseUrl();
|
|
1485
|
-
const res = await
|
|
3090
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1486
3091
|
if (!res.ok || !res.data || !Array.isArray(res.data.data)) return [];
|
|
1487
3092
|
return res.data.data.map((entry) => ({ id: entry.id, provider: "local", displayName: entry.id })).filter((entry) => entry.id);
|
|
1488
3093
|
}
|
|
@@ -1506,7 +3111,7 @@ async function runLocalPrompt({
|
|
|
1506
3111
|
const headers = { "Content-Type": "application/json" };
|
|
1507
3112
|
const apiKey = getLocalApiKey();
|
|
1508
3113
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1509
|
-
const res = await
|
|
3114
|
+
const res = await fetchJson3(`${base}/chat/completions`, {
|
|
1510
3115
|
method: "POST",
|
|
1511
3116
|
headers,
|
|
1512
3117
|
body: JSON.stringify(payload)
|
|
@@ -1532,7 +3137,9 @@ var providers = {
|
|
|
1532
3137
|
id: "claude",
|
|
1533
3138
|
name: "Claude",
|
|
1534
3139
|
ensureInstalled: ensureClaudeInstalled,
|
|
3140
|
+
fastStatus: getClaudeFastStatus,
|
|
1535
3141
|
status: getClaudeStatus,
|
|
3142
|
+
update: updateClaude,
|
|
1536
3143
|
login: loginClaude,
|
|
1537
3144
|
logout: async () => {
|
|
1538
3145
|
},
|
|
@@ -1542,17 +3149,32 @@ var providers = {
|
|
|
1542
3149
|
id: "codex",
|
|
1543
3150
|
name: "Codex",
|
|
1544
3151
|
ensureInstalled: ensureCodexInstalled,
|
|
3152
|
+
fastStatus: getCodexFastStatus,
|
|
1545
3153
|
status: getCodexStatus,
|
|
3154
|
+
update: updateCodex,
|
|
1546
3155
|
login: loginCodex,
|
|
1547
3156
|
logout: async () => {
|
|
1548
3157
|
},
|
|
1549
3158
|
runPrompt: runCodexPrompt
|
|
1550
3159
|
},
|
|
3160
|
+
cursor: {
|
|
3161
|
+
id: "cursor",
|
|
3162
|
+
name: "Cursor",
|
|
3163
|
+
ensureInstalled: ensureCursorInstalled,
|
|
3164
|
+
fastStatus: getCursorFastStatus,
|
|
3165
|
+
status: getCursorStatus,
|
|
3166
|
+
update: updateCursor,
|
|
3167
|
+
login: loginCursor,
|
|
3168
|
+
logout: async () => {
|
|
3169
|
+
},
|
|
3170
|
+
runPrompt: runCursorPrompt
|
|
3171
|
+
},
|
|
1551
3172
|
local: {
|
|
1552
3173
|
id: "local",
|
|
1553
3174
|
name: "Local",
|
|
1554
3175
|
ensureInstalled: ensureLocalInstalled,
|
|
1555
3176
|
status: getLocalStatus,
|
|
3177
|
+
update: updateLocal,
|
|
1556
3178
|
login: loginLocal,
|
|
1557
3179
|
logout: async () => {
|
|
1558
3180
|
},
|
|
@@ -1562,8 +3184,10 @@ var providers = {
|
|
|
1562
3184
|
async function listModels() {
|
|
1563
3185
|
const claudeModels = await listClaudeModels();
|
|
1564
3186
|
const codexModels = await listCodexModels();
|
|
3187
|
+
const cursorModels = await listCursorModels();
|
|
1565
3188
|
const base = [
|
|
1566
3189
|
...claudeModels,
|
|
3190
|
+
...cursorModels,
|
|
1567
3191
|
{ id: "local", provider: "local", displayName: "Local Model" }
|
|
1568
3192
|
];
|
|
1569
3193
|
const envModels = process.env.AGENTCONNECT_LOCAL_MODELS;
|
|
@@ -1597,6 +3221,7 @@ async function listRecentModels(providerId) {
|
|
|
1597
3221
|
}
|
|
1598
3222
|
function resolveProviderForModel(model) {
|
|
1599
3223
|
const lower = String(model || "").toLowerCase();
|
|
3224
|
+
if (lower.includes("cursor")) return "cursor";
|
|
1600
3225
|
if (lower.includes("codex")) return "codex";
|
|
1601
3226
|
if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4")) {
|
|
1602
3227
|
return "codex";
|
|
@@ -1610,15 +3235,15 @@ function resolveProviderForModel(model) {
|
|
|
1610
3235
|
|
|
1611
3236
|
// src/observed.ts
|
|
1612
3237
|
import fs from "fs";
|
|
1613
|
-
import
|
|
3238
|
+
import path5 from "path";
|
|
1614
3239
|
function createObservedTracker({
|
|
1615
3240
|
basePath,
|
|
1616
3241
|
appId,
|
|
1617
3242
|
requested = []
|
|
1618
3243
|
}) {
|
|
1619
3244
|
const requestedList = Array.isArray(requested) ? requested.filter(Boolean) : [];
|
|
1620
|
-
const dirPath =
|
|
1621
|
-
const filePath =
|
|
3245
|
+
const dirPath = path5.join(basePath, ".agentconnect");
|
|
3246
|
+
const filePath = path5.join(dirPath, "observed-capabilities.json");
|
|
1622
3247
|
const observed = /* @__PURE__ */ new Set();
|
|
1623
3248
|
let writeTimer = null;
|
|
1624
3249
|
function load() {
|
|
@@ -1691,6 +3316,15 @@ function replyError(socket, id, code, message) {
|
|
|
1691
3316
|
});
|
|
1692
3317
|
}
|
|
1693
3318
|
function sessionEvent(socket, sessionId, type, data) {
|
|
3319
|
+
if (process.env.AGENTCONNECT_DEBUG?.trim()) {
|
|
3320
|
+
try {
|
|
3321
|
+
console.log(
|
|
3322
|
+
`[AgentConnect][Session ${sessionId}] ${type} ${JSON.stringify(data)}`
|
|
3323
|
+
);
|
|
3324
|
+
} catch {
|
|
3325
|
+
console.log(`[AgentConnect][Session ${sessionId}] ${type}`);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
1694
3328
|
send(socket, {
|
|
1695
3329
|
jsonrpc: "2.0",
|
|
1696
3330
|
method: "acp.session.event",
|
|
@@ -1705,7 +3339,14 @@ function buildProviderList(statuses) {
|
|
|
1705
3339
|
name: provider.name,
|
|
1706
3340
|
installed: info.installed ?? false,
|
|
1707
3341
|
loggedIn: info.loggedIn ?? false,
|
|
1708
|
-
version: info.version
|
|
3342
|
+
version: info.version,
|
|
3343
|
+
updateAvailable: info.updateAvailable,
|
|
3344
|
+
latestVersion: info.latestVersion,
|
|
3345
|
+
updateCheckedAt: info.updateCheckedAt,
|
|
3346
|
+
updateSource: info.updateSource,
|
|
3347
|
+
updateCommand: info.updateCommand,
|
|
3348
|
+
updateMessage: info.updateMessage,
|
|
3349
|
+
updateInProgress: info.updateInProgress
|
|
1709
3350
|
};
|
|
1710
3351
|
});
|
|
1711
3352
|
}
|
|
@@ -1720,10 +3361,12 @@ function startDevHost({
|
|
|
1720
3361
|
const wss = new WebSocketServer({ server });
|
|
1721
3362
|
const sessions = /* @__PURE__ */ new Map();
|
|
1722
3363
|
const activeRuns = /* @__PURE__ */ new Map();
|
|
3364
|
+
const updatingProviders = /* @__PURE__ */ new Map();
|
|
1723
3365
|
const processTable = /* @__PURE__ */ new Map();
|
|
1724
3366
|
const backendState = /* @__PURE__ */ new Map();
|
|
1725
3367
|
const statusCache = /* @__PURE__ */ new Map();
|
|
1726
|
-
const statusCacheTtlMs =
|
|
3368
|
+
const statusCacheTtlMs = 8e3;
|
|
3369
|
+
const statusInFlight = /* @__PURE__ */ new Map();
|
|
1727
3370
|
const basePath = appPath || process.cwd();
|
|
1728
3371
|
const manifest = readManifest(basePath);
|
|
1729
3372
|
const appId = manifest?.id || "agentconnect-dev-app";
|
|
@@ -1736,7 +3379,7 @@ function startDevHost({
|
|
|
1736
3379
|
function resolveAppPathInternal(input) {
|
|
1737
3380
|
if (!input) return basePath;
|
|
1738
3381
|
const value = String(input);
|
|
1739
|
-
return
|
|
3382
|
+
return path6.isAbsolute(value) ? value : path6.resolve(basePath, value);
|
|
1740
3383
|
}
|
|
1741
3384
|
function mapFileType(stat) {
|
|
1742
3385
|
if (stat.isFile()) return "file";
|
|
@@ -1774,7 +3417,7 @@ function startDevHost({
|
|
|
1774
3417
|
}
|
|
1775
3418
|
function readManifest(root) {
|
|
1776
3419
|
try {
|
|
1777
|
-
const raw = fs2.readFileSync(
|
|
3420
|
+
const raw = fs2.readFileSync(path6.join(root, "agentconnect.app.json"), "utf8");
|
|
1778
3421
|
return JSON.parse(raw);
|
|
1779
3422
|
} catch {
|
|
1780
3423
|
return null;
|
|
@@ -1788,15 +3431,48 @@ function startDevHost({
|
|
|
1788
3431
|
if (!providerId) return;
|
|
1789
3432
|
recordCapability(`model.${providerId}`);
|
|
1790
3433
|
}
|
|
1791
|
-
async function getCachedStatus(provider) {
|
|
3434
|
+
async function getCachedStatus(provider, options = {}) {
|
|
1792
3435
|
const cached = statusCache.get(provider.id);
|
|
1793
3436
|
const now = Date.now();
|
|
1794
3437
|
if (cached && now - cached.at < statusCacheTtlMs) {
|
|
1795
3438
|
return cached.status;
|
|
1796
3439
|
}
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
3440
|
+
const existing = statusInFlight.get(provider.id);
|
|
3441
|
+
if (existing) return existing;
|
|
3442
|
+
if (options.allowFast && provider.fastStatus) {
|
|
3443
|
+
try {
|
|
3444
|
+
const fast = await provider.fastStatus();
|
|
3445
|
+
const startedAt2 = Date.now();
|
|
3446
|
+
const promise2 = provider.status().then((status) => {
|
|
3447
|
+
debugLog("Providers", "status-check", {
|
|
3448
|
+
providerId: provider.id,
|
|
3449
|
+
durationMs: Date.now() - startedAt2,
|
|
3450
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3451
|
+
});
|
|
3452
|
+
statusCache.set(provider.id, { status, at: Date.now() });
|
|
3453
|
+
return status;
|
|
3454
|
+
}).finally(() => {
|
|
3455
|
+
statusInFlight.delete(provider.id);
|
|
3456
|
+
});
|
|
3457
|
+
statusInFlight.set(provider.id, promise2);
|
|
3458
|
+
return fast;
|
|
3459
|
+
} catch {
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
const startedAt = Date.now();
|
|
3463
|
+
const promise = provider.status().then((status) => {
|
|
3464
|
+
debugLog("Providers", "status-check", {
|
|
3465
|
+
providerId: provider.id,
|
|
3466
|
+
durationMs: Date.now() - startedAt,
|
|
3467
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3468
|
+
});
|
|
3469
|
+
statusCache.set(provider.id, { status, at: Date.now() });
|
|
3470
|
+
return status;
|
|
3471
|
+
}).finally(() => {
|
|
3472
|
+
statusInFlight.delete(provider.id);
|
|
3473
|
+
});
|
|
3474
|
+
statusInFlight.set(provider.id, promise);
|
|
3475
|
+
return promise;
|
|
1800
3476
|
}
|
|
1801
3477
|
function invalidateStatus(providerId) {
|
|
1802
3478
|
if (!providerId) return;
|
|
@@ -1837,14 +3513,18 @@ function startDevHost({
|
|
|
1837
3513
|
const statusEntries = await Promise.all(
|
|
1838
3514
|
Object.values(providers).map(async (provider) => {
|
|
1839
3515
|
try {
|
|
1840
|
-
return [provider.id, await getCachedStatus(provider)];
|
|
3516
|
+
return [provider.id, await getCachedStatus(provider, { allowFast: true })];
|
|
1841
3517
|
} catch {
|
|
1842
3518
|
return [provider.id, { installed: false, loggedIn: false }];
|
|
1843
3519
|
}
|
|
1844
3520
|
})
|
|
1845
3521
|
);
|
|
1846
3522
|
const statuses = Object.fromEntries(statusEntries);
|
|
1847
|
-
|
|
3523
|
+
const list = buildProviderList(statuses).map((entry) => ({
|
|
3524
|
+
...entry,
|
|
3525
|
+
updateInProgress: updatingProviders.has(entry.id)
|
|
3526
|
+
}));
|
|
3527
|
+
reply(socket, id, { providers: list });
|
|
1848
3528
|
return;
|
|
1849
3529
|
}
|
|
1850
3530
|
if (method === "acp.providers.status") {
|
|
@@ -1854,18 +3534,73 @@ function startDevHost({
|
|
|
1854
3534
|
replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
|
|
1855
3535
|
return;
|
|
1856
3536
|
}
|
|
1857
|
-
const status = await getCachedStatus(provider);
|
|
3537
|
+
const status = await getCachedStatus(provider, { allowFast: true });
|
|
1858
3538
|
reply(socket, id, {
|
|
1859
3539
|
provider: {
|
|
1860
3540
|
id: provider.id,
|
|
1861
3541
|
name: provider.name,
|
|
1862
3542
|
installed: status.installed,
|
|
1863
3543
|
loggedIn: status.loggedIn,
|
|
1864
|
-
version: status.version
|
|
3544
|
+
version: status.version,
|
|
3545
|
+
updateAvailable: status.updateAvailable,
|
|
3546
|
+
latestVersion: status.latestVersion,
|
|
3547
|
+
updateCheckedAt: status.updateCheckedAt,
|
|
3548
|
+
updateSource: status.updateSource,
|
|
3549
|
+
updateCommand: status.updateCommand,
|
|
3550
|
+
updateMessage: status.updateMessage,
|
|
3551
|
+
updateInProgress: updatingProviders.has(provider.id) || status.updateInProgress
|
|
1865
3552
|
}
|
|
1866
3553
|
});
|
|
1867
3554
|
return;
|
|
1868
3555
|
}
|
|
3556
|
+
if (method === "acp.providers.update") {
|
|
3557
|
+
const providerId = params.provider;
|
|
3558
|
+
const provider = providers[providerId];
|
|
3559
|
+
if (!provider) {
|
|
3560
|
+
replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
debugLog("Providers", "update-start", { providerId });
|
|
3564
|
+
if (!updatingProviders.has(providerId)) {
|
|
3565
|
+
const promise = provider.update().finally(() => {
|
|
3566
|
+
updatingProviders.delete(providerId);
|
|
3567
|
+
invalidateStatus(providerId);
|
|
3568
|
+
});
|
|
3569
|
+
updatingProviders.set(providerId, promise);
|
|
3570
|
+
}
|
|
3571
|
+
try {
|
|
3572
|
+
const status = await updatingProviders.get(providerId);
|
|
3573
|
+
debugLog("Providers", "update-complete", {
|
|
3574
|
+
providerId,
|
|
3575
|
+
updateAvailable: status.updateAvailable,
|
|
3576
|
+
latestVersion: status.latestVersion,
|
|
3577
|
+
updateMessage: status.updateMessage
|
|
3578
|
+
});
|
|
3579
|
+
reply(socket, id, {
|
|
3580
|
+
provider: {
|
|
3581
|
+
id: provider.id,
|
|
3582
|
+
name: provider.name,
|
|
3583
|
+
installed: status.installed,
|
|
3584
|
+
loggedIn: status.loggedIn,
|
|
3585
|
+
version: status.version,
|
|
3586
|
+
updateAvailable: status.updateAvailable,
|
|
3587
|
+
latestVersion: status.latestVersion,
|
|
3588
|
+
updateCheckedAt: status.updateCheckedAt,
|
|
3589
|
+
updateSource: status.updateSource,
|
|
3590
|
+
updateCommand: status.updateCommand,
|
|
3591
|
+
updateMessage: status.updateMessage,
|
|
3592
|
+
updateInProgress: false
|
|
3593
|
+
}
|
|
3594
|
+
});
|
|
3595
|
+
} catch (err) {
|
|
3596
|
+
debugLog("Providers", "update-error", {
|
|
3597
|
+
providerId,
|
|
3598
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3599
|
+
});
|
|
3600
|
+
replyError(socket, id, "AC_ERR_INTERNAL", err?.message || "Update failed");
|
|
3601
|
+
}
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
1869
3604
|
if (method === "acp.providers.ensureInstalled") {
|
|
1870
3605
|
const providerId = params.provider;
|
|
1871
3606
|
const provider = providers[providerId];
|
|
@@ -1943,6 +3678,7 @@ function startDevHost({
|
|
|
1943
3678
|
const reasoningEffort = params.reasoningEffort || null;
|
|
1944
3679
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
|
|
1945
3680
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
|
|
3681
|
+
const providerDetailLevel = params.providerDetailLevel || void 0;
|
|
1946
3682
|
const providerId = resolveProviderForModel(model);
|
|
1947
3683
|
recordModelCapability(model);
|
|
1948
3684
|
sessions.set(sessionId, {
|
|
@@ -1952,7 +3688,8 @@ function startDevHost({
|
|
|
1952
3688
|
providerSessionId: null,
|
|
1953
3689
|
reasoningEffort,
|
|
1954
3690
|
cwd,
|
|
1955
|
-
repoRoot
|
|
3691
|
+
repoRoot,
|
|
3692
|
+
providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
|
|
1956
3693
|
});
|
|
1957
3694
|
reply(socket, id, { sessionId });
|
|
1958
3695
|
return;
|
|
@@ -1965,6 +3702,7 @@ function startDevHost({
|
|
|
1965
3702
|
const reasoningEffort = params.reasoningEffort || null;
|
|
1966
3703
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
|
|
1967
3704
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
|
|
3705
|
+
const providerDetailLevel = params.providerDetailLevel || void 0;
|
|
1968
3706
|
recordModelCapability(model);
|
|
1969
3707
|
sessions.set(sessionId, {
|
|
1970
3708
|
id: sessionId,
|
|
@@ -1973,7 +3711,8 @@ function startDevHost({
|
|
|
1973
3711
|
providerSessionId: params.providerSessionId || null,
|
|
1974
3712
|
reasoningEffort,
|
|
1975
3713
|
cwd,
|
|
1976
|
-
repoRoot
|
|
3714
|
+
repoRoot,
|
|
3715
|
+
providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
|
|
1977
3716
|
});
|
|
1978
3717
|
} else {
|
|
1979
3718
|
if (params.providerSessionId) {
|
|
@@ -1985,6 +3724,12 @@ function startDevHost({
|
|
|
1985
3724
|
if (params.repoRoot) {
|
|
1986
3725
|
existing.repoRoot = resolveAppPathInternal(params.repoRoot);
|
|
1987
3726
|
}
|
|
3727
|
+
if (params.providerDetailLevel) {
|
|
3728
|
+
const level = String(params.providerDetailLevel);
|
|
3729
|
+
if (level === "raw" || level === "minimal") {
|
|
3730
|
+
existing.providerDetailLevel = level;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
1988
3733
|
recordModelCapability(existing.model);
|
|
1989
3734
|
}
|
|
1990
3735
|
reply(socket, id, { sessionId });
|
|
@@ -2004,6 +3749,10 @@ function startDevHost({
|
|
|
2004
3749
|
replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
|
|
2005
3750
|
return;
|
|
2006
3751
|
}
|
|
3752
|
+
if (updatingProviders.has(session.providerId)) {
|
|
3753
|
+
replyError(socket, id, "AC_ERR_BUSY", "Provider update in progress.");
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
2007
3756
|
const status = await provider.status();
|
|
2008
3757
|
if (!status.installed) {
|
|
2009
3758
|
const installed = await provider.ensureInstalled();
|
|
@@ -2015,6 +3764,7 @@ function startDevHost({
|
|
|
2015
3764
|
const controller = new AbortController();
|
|
2016
3765
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : session.cwd || basePath;
|
|
2017
3766
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : session.repoRoot || basePath;
|
|
3767
|
+
const providerDetailLevel = params.providerDetailLevel === "raw" || params.providerDetailLevel === "minimal" ? params.providerDetailLevel : session.providerDetailLevel || "minimal";
|
|
2018
3768
|
activeRuns.set(sessionId, controller);
|
|
2019
3769
|
let sawError = false;
|
|
2020
3770
|
provider.runPrompt({
|
|
@@ -2024,6 +3774,7 @@ function startDevHost({
|
|
|
2024
3774
|
reasoningEffort: session.reasoningEffort,
|
|
2025
3775
|
repoRoot,
|
|
2026
3776
|
cwd,
|
|
3777
|
+
providerDetailLevel,
|
|
2027
3778
|
signal: controller.signal,
|
|
2028
3779
|
onEvent: (event) => {
|
|
2029
3780
|
if (event.type === "error") {
|
|
@@ -2089,7 +3840,7 @@ function startDevHost({
|
|
|
2089
3840
|
const filePath = resolveAppPathInternal(params.path);
|
|
2090
3841
|
const encoding = params.encoding || "utf8";
|
|
2091
3842
|
const content = params.content ?? "";
|
|
2092
|
-
await fsp.mkdir(
|
|
3843
|
+
await fsp.mkdir(path6.dirname(filePath), { recursive: true });
|
|
2093
3844
|
await fsp.writeFile(filePath, content, {
|
|
2094
3845
|
encoding,
|
|
2095
3846
|
mode: params.mode
|
|
@@ -2113,7 +3864,7 @@ function startDevHost({
|
|
|
2113
3864
|
const entries = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
2114
3865
|
const results = [];
|
|
2115
3866
|
for (const entry of entries) {
|
|
2116
|
-
const entryPath =
|
|
3867
|
+
const entryPath = path6.join(dirPath, entry.name);
|
|
2117
3868
|
let size = 0;
|
|
2118
3869
|
let type = "other";
|
|
2119
3870
|
try {
|
|
@@ -2169,7 +3920,7 @@ function startDevHost({
|
|
|
2169
3920
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : basePath;
|
|
2170
3921
|
const env = { ...process.env, ...params.env || {} };
|
|
2171
3922
|
const useTty = Boolean(params.tty);
|
|
2172
|
-
const child =
|
|
3923
|
+
const child = spawn5(command2, args2, {
|
|
2173
3924
|
cwd,
|
|
2174
3925
|
env,
|
|
2175
3926
|
stdio: useTty ? "inherit" : ["pipe", "pipe", "pipe"]
|
|
@@ -2285,7 +4036,7 @@ function startDevHost({
|
|
|
2285
4036
|
}
|
|
2286
4037
|
const cwd = backendConfig.cwd ? resolveAppPathInternal(backendConfig.cwd) : basePath;
|
|
2287
4038
|
const args2 = backendConfig.args || [];
|
|
2288
|
-
const child =
|
|
4039
|
+
const child = spawn5(backendConfig.command, args2, {
|
|
2289
4040
|
cwd,
|
|
2290
4041
|
env,
|
|
2291
4042
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2364,7 +4115,7 @@ function startDevHost({
|
|
|
2364
4115
|
}
|
|
2365
4116
|
|
|
2366
4117
|
// src/paths.ts
|
|
2367
|
-
import
|
|
4118
|
+
import path7 from "path";
|
|
2368
4119
|
import { fileURLToPath } from "url";
|
|
2369
4120
|
import { promises as fs3 } from "fs";
|
|
2370
4121
|
async function exists(target) {
|
|
@@ -2376,20 +4127,20 @@ async function exists(target) {
|
|
|
2376
4127
|
}
|
|
2377
4128
|
}
|
|
2378
4129
|
function resolveAppPath(input) {
|
|
2379
|
-
const candidate = input ?
|
|
4130
|
+
const candidate = input ? path7.resolve(input) : process.cwd();
|
|
2380
4131
|
return candidate;
|
|
2381
4132
|
}
|
|
2382
4133
|
async function findSchemaDir() {
|
|
2383
4134
|
const envDir = process.env.AGENTCONNECT_SCHEMA_DIR;
|
|
2384
4135
|
if (envDir && await exists(envDir)) return envDir;
|
|
2385
|
-
const start =
|
|
4136
|
+
const start = path7.dirname(fileURLToPath(import.meta.url));
|
|
2386
4137
|
const roots = [start, process.cwd()];
|
|
2387
4138
|
for (const root of roots) {
|
|
2388
4139
|
let current = root;
|
|
2389
4140
|
for (let i = 0; i < 8; i += 1) {
|
|
2390
|
-
const candidate =
|
|
4141
|
+
const candidate = path7.join(current, "schemas");
|
|
2391
4142
|
if (await exists(candidate)) return candidate;
|
|
2392
|
-
const parent =
|
|
4143
|
+
const parent = path7.dirname(current);
|
|
2393
4144
|
if (parent === current) break;
|
|
2394
4145
|
current = parent;
|
|
2395
4146
|
}
|
|
@@ -2399,13 +4150,13 @@ async function findSchemaDir() {
|
|
|
2399
4150
|
|
|
2400
4151
|
// src/zip.ts
|
|
2401
4152
|
import fs5 from "fs";
|
|
2402
|
-
import
|
|
4153
|
+
import path9 from "path";
|
|
2403
4154
|
import { createHash } from "crypto";
|
|
2404
4155
|
import yazl from "yazl";
|
|
2405
4156
|
import yauzl from "yauzl";
|
|
2406
4157
|
|
|
2407
4158
|
// src/fs-utils.ts
|
|
2408
|
-
import
|
|
4159
|
+
import path8 from "path";
|
|
2409
4160
|
import { promises as fs4 } from "fs";
|
|
2410
4161
|
async function collectFiles(root, options = {}) {
|
|
2411
4162
|
const ignoreNames = new Set(options.ignoreNames || []);
|
|
@@ -2415,8 +4166,8 @@ async function collectFiles(root, options = {}) {
|
|
|
2415
4166
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2416
4167
|
for (const entry of entries) {
|
|
2417
4168
|
if (ignoreNames.has(entry.name)) continue;
|
|
2418
|
-
const fullPath =
|
|
2419
|
-
const rel =
|
|
4169
|
+
const fullPath = path8.join(dir, entry.name);
|
|
4170
|
+
const rel = path8.relative(root, fullPath);
|
|
2420
4171
|
if (ignorePaths.has(rel)) continue;
|
|
2421
4172
|
if (entry.isDirectory()) {
|
|
2422
4173
|
await walk(fullPath);
|
|
@@ -2433,7 +4184,7 @@ async function readJson(filePath) {
|
|
|
2433
4184
|
return JSON.parse(raw);
|
|
2434
4185
|
}
|
|
2435
4186
|
async function writeJson(filePath, data) {
|
|
2436
|
-
await fs4.mkdir(
|
|
4187
|
+
await fs4.mkdir(path8.dirname(filePath), { recursive: true });
|
|
2437
4188
|
await fs4.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
2438
4189
|
}
|
|
2439
4190
|
async function fileExists(filePath) {
|
|
@@ -2461,7 +4212,7 @@ async function zipDirectory({
|
|
|
2461
4212
|
ignoreNames = [],
|
|
2462
4213
|
ignorePaths = []
|
|
2463
4214
|
}) {
|
|
2464
|
-
await fs5.promises.mkdir(
|
|
4215
|
+
await fs5.promises.mkdir(path9.dirname(outputPath), { recursive: true });
|
|
2465
4216
|
const zipfile = new yazl.ZipFile();
|
|
2466
4217
|
const files = await collectFiles(inputDir, { ignoreNames, ignorePaths });
|
|
2467
4218
|
for (const file of files) {
|
|
@@ -2517,10 +4268,10 @@ async function readZipEntry(zipPath, entryName) {
|
|
|
2517
4268
|
}
|
|
2518
4269
|
|
|
2519
4270
|
// src/manifest.ts
|
|
2520
|
-
import
|
|
4271
|
+
import path10 from "path";
|
|
2521
4272
|
import Ajv from "ajv";
|
|
2522
4273
|
async function readManifestFromDir(appPath) {
|
|
2523
|
-
const manifestPath =
|
|
4274
|
+
const manifestPath = path10.join(appPath, "agentconnect.app.json");
|
|
2524
4275
|
if (!await fileExists(manifestPath)) {
|
|
2525
4276
|
throw new Error("agentconnect.app.json not found in app directory.");
|
|
2526
4277
|
}
|
|
@@ -2538,7 +4289,7 @@ async function loadManifestSchema() {
|
|
|
2538
4289
|
if (!schemaDir) {
|
|
2539
4290
|
throw new Error("Unable to locate schemas directory.");
|
|
2540
4291
|
}
|
|
2541
|
-
const schemaPath =
|
|
4292
|
+
const schemaPath = path10.join(schemaDir, "app-manifest.json");
|
|
2542
4293
|
return readJson(schemaPath);
|
|
2543
4294
|
}
|
|
2544
4295
|
async function validateManifest(manifest) {
|
|
@@ -2550,9 +4301,9 @@ async function validateManifest(manifest) {
|
|
|
2550
4301
|
}
|
|
2551
4302
|
|
|
2552
4303
|
// src/registry.ts
|
|
2553
|
-
import
|
|
4304
|
+
import path11 from "path";
|
|
2554
4305
|
import { promises as fs6 } from "fs";
|
|
2555
|
-
function
|
|
4306
|
+
function compareSemver4(a, b) {
|
|
2556
4307
|
const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
|
|
2557
4308
|
const pa = parse(a);
|
|
2558
4309
|
const pb = parse(b);
|
|
@@ -2574,26 +4325,26 @@ async function publishPackage({
|
|
|
2574
4325
|
if (!appId || !version) {
|
|
2575
4326
|
throw new Error("Manifest must include id and version.");
|
|
2576
4327
|
}
|
|
2577
|
-
const entryDir =
|
|
4328
|
+
const entryDir = path11.join(registryPath, "apps", appId, version);
|
|
2578
4329
|
await fs6.mkdir(entryDir, { recursive: true });
|
|
2579
|
-
const targetZip =
|
|
4330
|
+
const targetZip = path11.join(entryDir, "app.zip");
|
|
2580
4331
|
await fs6.copyFile(zipPath, targetZip);
|
|
2581
|
-
const manifestPath =
|
|
4332
|
+
const manifestPath = path11.join(entryDir, "manifest.json");
|
|
2582
4333
|
await writeJson(manifestPath, resolvedManifest);
|
|
2583
4334
|
let signatureOut = null;
|
|
2584
4335
|
if (signaturePath) {
|
|
2585
|
-
signatureOut =
|
|
4336
|
+
signatureOut = path11.join(entryDir, "signature.json");
|
|
2586
4337
|
await fs6.copyFile(signaturePath, signatureOut);
|
|
2587
4338
|
}
|
|
2588
4339
|
const hash = await hashFile(zipPath);
|
|
2589
|
-
const indexPath =
|
|
4340
|
+
const indexPath = path11.join(registryPath, "index.json");
|
|
2590
4341
|
const index = await fileExists(indexPath) ? await readJson(indexPath) : { apps: {} };
|
|
2591
4342
|
if (!index.apps) index.apps = {};
|
|
2592
4343
|
if (!index.apps[appId]) {
|
|
2593
4344
|
index.apps[appId] = { latest: version, versions: {} };
|
|
2594
4345
|
}
|
|
2595
4346
|
index.apps[appId].versions[version] = {
|
|
2596
|
-
path:
|
|
4347
|
+
path: path11.relative(registryPath, targetZip).replace(/\\/g, "/"),
|
|
2597
4348
|
manifest: resolvedManifest,
|
|
2598
4349
|
signature: signatureOut ? {
|
|
2599
4350
|
algorithm: "unknown",
|
|
@@ -2603,7 +4354,7 @@ async function publishPackage({
|
|
|
2603
4354
|
hash
|
|
2604
4355
|
};
|
|
2605
4356
|
const currentLatest = index.apps[appId].latest;
|
|
2606
|
-
if (!currentLatest ||
|
|
4357
|
+
if (!currentLatest || compareSemver4(version, currentLatest) > 0) {
|
|
2607
4358
|
index.apps[appId].latest = version;
|
|
2608
4359
|
}
|
|
2609
4360
|
await writeJson(indexPath, index);
|
|
@@ -2611,9 +4362,9 @@ async function publishPackage({
|
|
|
2611
4362
|
}
|
|
2612
4363
|
|
|
2613
4364
|
// src/registry-validate.ts
|
|
2614
|
-
import
|
|
4365
|
+
import path12 from "path";
|
|
2615
4366
|
import { createPublicKey, verify as verifySignature } from "crypto";
|
|
2616
|
-
function
|
|
4367
|
+
function compareSemver5(a, b) {
|
|
2617
4368
|
const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
|
|
2618
4369
|
const pa = parse(a);
|
|
2619
4370
|
const pb = parse(b);
|
|
@@ -2625,7 +4376,7 @@ function compareSemver2(a, b) {
|
|
|
2625
4376
|
}
|
|
2626
4377
|
function resolveEntry(registryPath, entryPath) {
|
|
2627
4378
|
if (!entryPath || typeof entryPath !== "string") return null;
|
|
2628
|
-
return
|
|
4379
|
+
return path12.resolve(registryPath, entryPath);
|
|
2629
4380
|
}
|
|
2630
4381
|
function normalizeSignatureAlg(signatureAlg) {
|
|
2631
4382
|
const value = String(signatureAlg || "").toLowerCase();
|
|
@@ -2666,7 +4417,7 @@ async function validateRegistry({
|
|
|
2666
4417
|
}) {
|
|
2667
4418
|
const errors = [];
|
|
2668
4419
|
const warnings = [];
|
|
2669
|
-
const indexPath =
|
|
4420
|
+
const indexPath = path12.join(registryPath, "index.json");
|
|
2670
4421
|
if (!await fileExists(indexPath)) {
|
|
2671
4422
|
return {
|
|
2672
4423
|
valid: false,
|
|
@@ -2721,9 +4472,9 @@ async function validateRegistry({
|
|
|
2721
4472
|
message: `App ${appId} latest (${latest}) not found in versions.`
|
|
2722
4473
|
});
|
|
2723
4474
|
}
|
|
2724
|
-
const sorted = [...versionKeys].sort(
|
|
4475
|
+
const sorted = [...versionKeys].sort(compareSemver5);
|
|
2725
4476
|
const expectedLatest = sorted[sorted.length - 1];
|
|
2726
|
-
if (latest && expectedLatest &&
|
|
4477
|
+
if (latest && expectedLatest && compareSemver5(latest, expectedLatest) !== 0) {
|
|
2727
4478
|
warnings.push({
|
|
2728
4479
|
path: `apps.${appId}`,
|
|
2729
4480
|
message: `App ${appId} latest (${latest}) is not the newest (${expectedLatest}).`
|
|
@@ -2882,7 +4633,7 @@ async function main() {
|
|
|
2882
4633
|
}
|
|
2883
4634
|
if (command === "pack") {
|
|
2884
4635
|
const appPath = resolveAppPath(getFlag("--app", "-a") ?? void 0);
|
|
2885
|
-
const outPath = getFlag("--out", "-o") ||
|
|
4636
|
+
const outPath = getFlag("--out", "-o") || path13.join(appPath, "dist", "app.zip");
|
|
2886
4637
|
const manifest = await readManifestFromDir(appPath);
|
|
2887
4638
|
const validation = await validateManifest(manifest);
|
|
2888
4639
|
if (!validation.valid) {
|
|
@@ -2892,7 +4643,7 @@ async function main() {
|
|
|
2892
4643
|
}
|
|
2893
4644
|
const ignoreNames = ["node_modules", ".git", ".DS_Store"];
|
|
2894
4645
|
const ignorePaths = [];
|
|
2895
|
-
const outputRel =
|
|
4646
|
+
const outputRel = path13.relative(appPath, outPath);
|
|
2896
4647
|
if (!outputRel.startsWith("..") && outputRel !== "") {
|
|
2897
4648
|
ignorePaths.push(outputRel);
|
|
2898
4649
|
}
|
|
@@ -2950,7 +4701,7 @@ async function main() {
|
|
|
2950
4701
|
console.error("Sign requires a zip file path.");
|
|
2951
4702
|
return 1;
|
|
2952
4703
|
}
|
|
2953
|
-
const outPath = getFlag("--out", "-o") ||
|
|
4704
|
+
const outPath = getFlag("--out", "-o") || path13.join(path13.dirname(appPath), "app.sig.json");
|
|
2954
4705
|
const manifest = await readManifestFromZip(appPath);
|
|
2955
4706
|
const hash = await hashFile(appPath);
|
|
2956
4707
|
const hashBuffer = Buffer.from(hash, "hex");
|
|
@@ -2974,7 +4725,7 @@ async function main() {
|
|
|
2974
4725
|
publicKey: publicKeyPem,
|
|
2975
4726
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2976
4727
|
};
|
|
2977
|
-
await fs7.mkdir(
|
|
4728
|
+
await fs7.mkdir(path13.dirname(outPath), { recursive: true });
|
|
2978
4729
|
await fs7.writeFile(outPath, JSON.stringify(signaturePayload, null, 2), "utf8");
|
|
2979
4730
|
console.log(`Signature written to ${outPath}`);
|
|
2980
4731
|
return 0;
|