@agentconnect/cli 0.1.3 → 0.1.5
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 +1847 -139
- package/dist/providers/claude.d.ts +2 -1
- package/dist/providers/codex.d.ts +2 -1
- package/dist/providers/cursor.d.ts +10 -0
- package/dist/providers/local.d.ts +1 -0
- package/dist/providers/utils.d.ts +1 -0
- package/dist/types.d.ts +30 -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,55 @@ 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 updateClaude() {
|
|
942
|
+
const command2 = getClaudeCommand();
|
|
943
|
+
if (!commandExists(command2)) {
|
|
944
|
+
return { installed: false, loggedIn: false };
|
|
945
|
+
}
|
|
946
|
+
const resolved = resolveCommandRealPath(command2);
|
|
947
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CLAUDE_UPDATE", "");
|
|
948
|
+
const action = updateOverride.command ? null : getClaudeUpdateAction(resolved || null);
|
|
949
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
950
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
951
|
+
if (!updateCommand) {
|
|
952
|
+
throw new Error("No update command available. Please update Claude manually.");
|
|
953
|
+
}
|
|
954
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
955
|
+
debugLog("Claude", "update-run", { command: cmd, args: updateArgs });
|
|
956
|
+
const result = await runCommand(cmd, updateArgs, { env: { ...process.env, CI: "1" } });
|
|
957
|
+
debugLog("Claude", "update-result", {
|
|
958
|
+
code: result.code,
|
|
959
|
+
stdout: trimOutput(result.stdout),
|
|
960
|
+
stderr: trimOutput(result.stderr)
|
|
961
|
+
});
|
|
962
|
+
if (result.code !== 0 && result.code !== null) {
|
|
963
|
+
const message = trimOutput(`${result.stdout}
|
|
964
|
+
${result.stderr}`, 800) || "Update failed";
|
|
965
|
+
throw new Error(message);
|
|
966
|
+
}
|
|
967
|
+
claudeUpdateCache = null;
|
|
968
|
+
claudeUpdatePromise = null;
|
|
969
|
+
return getClaudeStatus();
|
|
648
970
|
}
|
|
649
971
|
async function loginClaude(options) {
|
|
650
972
|
const login = buildLoginCommand("AGENTCONNECT_CLAUDE_LOGIN", DEFAULT_LOGIN);
|
|
@@ -661,10 +983,12 @@ async function runClaudeLoginFlow(options) {
|
|
|
661
983
|
const command2 = resolveWindowsCommand(getClaudeCommand());
|
|
662
984
|
const loginMethod = resolveClaudeLoginMethod(options);
|
|
663
985
|
const loginExperience = resolveClaudeLoginExperience(options);
|
|
986
|
+
const loginHint = await resolveClaudeLoginHint(options);
|
|
664
987
|
await ensureClaudeOnboardingSettings();
|
|
665
988
|
const settingsPath = await createClaudeLoginSettingsFile(loginMethod);
|
|
666
989
|
const loginTimeoutMs = Number(process.env.AGENTCONNECT_CLAUDE_LOGIN_TIMEOUT_MS || 18e4);
|
|
667
990
|
const loginArgs = settingsPath ? ["--settings", settingsPath] : [];
|
|
991
|
+
const includeLogin = loginHint === "login";
|
|
668
992
|
let ptyProcess = null;
|
|
669
993
|
let childExited = false;
|
|
670
994
|
const cleanup = async () => {
|
|
@@ -674,7 +998,7 @@ async function runClaudeLoginFlow(options) {
|
|
|
674
998
|
} catch {
|
|
675
999
|
}
|
|
676
1000
|
}
|
|
677
|
-
if (settingsPath) {
|
|
1001
|
+
if (settingsPath && loginExperience !== "terminal") {
|
|
678
1002
|
try {
|
|
679
1003
|
await rm(settingsPath, { force: true });
|
|
680
1004
|
} catch {
|
|
@@ -683,7 +1007,13 @@ async function runClaudeLoginFlow(options) {
|
|
|
683
1007
|
};
|
|
684
1008
|
try {
|
|
685
1009
|
if (loginExperience === "terminal") {
|
|
686
|
-
await openClaudeLoginTerminal(command2, loginArgs,
|
|
1010
|
+
await openClaudeLoginTerminal(command2, loginArgs, includeLogin);
|
|
1011
|
+
if (settingsPath) {
|
|
1012
|
+
setTimeout(() => {
|
|
1013
|
+
rm(settingsPath, { force: true }).catch(() => {
|
|
1014
|
+
});
|
|
1015
|
+
}, loginTimeoutMs);
|
|
1016
|
+
}
|
|
687
1017
|
} else {
|
|
688
1018
|
const ptyModule = await loadPtyModule();
|
|
689
1019
|
if (!ptyModule) {
|
|
@@ -691,7 +1021,8 @@ async function runClaudeLoginFlow(options) {
|
|
|
691
1021
|
"Claude login requires node-pty. Reinstall AgentConnect or run `claude /login` manually."
|
|
692
1022
|
);
|
|
693
1023
|
}
|
|
694
|
-
|
|
1024
|
+
const spawnArgs = includeLogin ? [...loginArgs, "/login"] : loginArgs;
|
|
1025
|
+
ptyProcess = ptyModule.spawn(command2, spawnArgs, {
|
|
695
1026
|
name: "xterm-256color",
|
|
696
1027
|
cols: 100,
|
|
697
1028
|
rows: 30,
|
|
@@ -771,6 +1102,19 @@ function extractAssistantDelta(msg) {
|
|
|
771
1102
|
const text = msg.delta?.text;
|
|
772
1103
|
return typeof text === "string" && text ? text : null;
|
|
773
1104
|
}
|
|
1105
|
+
function extractTextFromContent(content) {
|
|
1106
|
+
if (!content) return "";
|
|
1107
|
+
if (typeof content === "string") return content;
|
|
1108
|
+
if (Array.isArray(content)) {
|
|
1109
|
+
return content.map((part) => {
|
|
1110
|
+
if (!part || typeof part !== "object") return "";
|
|
1111
|
+
const text = part.text;
|
|
1112
|
+
if (typeof text === "string") return text;
|
|
1113
|
+
return "";
|
|
1114
|
+
}).filter(Boolean).join("");
|
|
1115
|
+
}
|
|
1116
|
+
return "";
|
|
1117
|
+
}
|
|
774
1118
|
function extractAssistantText(msg) {
|
|
775
1119
|
if (String(msg.type ?? "") !== "assistant") return null;
|
|
776
1120
|
const content = msg.message?.content;
|
|
@@ -789,6 +1133,7 @@ function runClaudePrompt({
|
|
|
789
1133
|
resumeSessionId,
|
|
790
1134
|
model,
|
|
791
1135
|
cwd,
|
|
1136
|
+
providerDetailLevel,
|
|
792
1137
|
onEvent,
|
|
793
1138
|
signal
|
|
794
1139
|
}) {
|
|
@@ -821,45 +1166,189 @@ function runClaudePrompt({
|
|
|
821
1166
|
let finalSessionId = null;
|
|
822
1167
|
let didFinalize = false;
|
|
823
1168
|
let sawError = false;
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1169
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
1170
|
+
const thinkingBlocks = /* @__PURE__ */ new Set();
|
|
1171
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
1172
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
1173
|
+
const detail = { eventType };
|
|
1174
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
1175
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
1176
|
+
return detail;
|
|
828
1177
|
};
|
|
829
|
-
const
|
|
1178
|
+
const emit = (event) => {
|
|
830
1179
|
if (finalSessionId) {
|
|
831
|
-
onEvent({
|
|
1180
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
832
1181
|
} else {
|
|
833
|
-
onEvent(
|
|
1182
|
+
onEvent(event);
|
|
834
1183
|
}
|
|
835
1184
|
};
|
|
1185
|
+
const emitError = (message) => {
|
|
1186
|
+
if (sawError) return;
|
|
1187
|
+
sawError = true;
|
|
1188
|
+
emit({ type: "error", message });
|
|
1189
|
+
};
|
|
1190
|
+
const emitFinal = (text, providerDetail) => {
|
|
1191
|
+
emit({ type: "final", text, providerDetail });
|
|
1192
|
+
};
|
|
836
1193
|
const handleLine = (line) => {
|
|
837
1194
|
const parsed = safeJsonParse(line);
|
|
838
1195
|
if (!parsed || typeof parsed !== "object") {
|
|
839
1196
|
if (line.trim()) {
|
|
840
|
-
|
|
1197
|
+
emit({ type: "raw_line", line });
|
|
841
1198
|
}
|
|
842
1199
|
return;
|
|
843
1200
|
}
|
|
844
1201
|
const msg = parsed;
|
|
845
1202
|
const sid = extractSessionId(msg);
|
|
846
1203
|
if (sid) finalSessionId = sid;
|
|
1204
|
+
const msgType = String(msg.type ?? "");
|
|
1205
|
+
let handled = false;
|
|
1206
|
+
if (msgType === "assistant" || msgType === "user" || msgType === "system") {
|
|
1207
|
+
const role = msg.message?.role === "assistant" || msg.message?.role === "user" || msg.message?.role === "system" ? msg.message.role : msgType;
|
|
1208
|
+
const rawContent = msg.message?.content;
|
|
1209
|
+
const content = extractTextFromContent(rawContent);
|
|
1210
|
+
emit({
|
|
1211
|
+
type: "message",
|
|
1212
|
+
provider: "claude",
|
|
1213
|
+
role,
|
|
1214
|
+
content,
|
|
1215
|
+
contentParts: rawContent ?? null,
|
|
1216
|
+
providerDetail: buildProviderDetail(msgType, {}, msg)
|
|
1217
|
+
});
|
|
1218
|
+
handled = true;
|
|
1219
|
+
}
|
|
1220
|
+
if (msgType === "stream_event" && msg.event) {
|
|
1221
|
+
const evType = String(msg.event.type ?? "");
|
|
1222
|
+
const index = typeof msg.event.index === "number" ? msg.event.index : void 0;
|
|
1223
|
+
const block = msg.event.content_block;
|
|
1224
|
+
if (evType === "content_block_start" && block && typeof index === "number") {
|
|
1225
|
+
if (block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") {
|
|
1226
|
+
toolBlocks.set(index, { id: block.id, name: block.name });
|
|
1227
|
+
emit({
|
|
1228
|
+
type: "tool_call",
|
|
1229
|
+
provider: "claude",
|
|
1230
|
+
name: block.name,
|
|
1231
|
+
callId: block.id,
|
|
1232
|
+
input: block.input,
|
|
1233
|
+
phase: "start",
|
|
1234
|
+
providerDetail: buildProviderDetail(
|
|
1235
|
+
"content_block_start",
|
|
1236
|
+
{ blockType: block.type, index, name: block.name, id: block.id },
|
|
1237
|
+
msg
|
|
1238
|
+
)
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
if (block.type === "thinking" || block.type === "redacted_thinking") {
|
|
1242
|
+
thinkingBlocks.add(index);
|
|
1243
|
+
emit({
|
|
1244
|
+
type: "thinking",
|
|
1245
|
+
provider: "claude",
|
|
1246
|
+
phase: "start",
|
|
1247
|
+
text: typeof block.thinking === "string" ? block.thinking : void 0,
|
|
1248
|
+
providerDetail: buildProviderDetail(
|
|
1249
|
+
"content_block_start",
|
|
1250
|
+
{ blockType: block.type, index },
|
|
1251
|
+
msg
|
|
1252
|
+
)
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
handled = true;
|
|
1256
|
+
}
|
|
1257
|
+
if (evType === "content_block_delta") {
|
|
1258
|
+
const delta2 = msg.event.delta ?? {};
|
|
1259
|
+
if (delta2.type === "thinking_delta") {
|
|
1260
|
+
emit({
|
|
1261
|
+
type: "thinking",
|
|
1262
|
+
provider: "claude",
|
|
1263
|
+
phase: "delta",
|
|
1264
|
+
text: typeof delta2.thinking === "string" ? delta2.thinking : void 0,
|
|
1265
|
+
providerDetail: buildProviderDetail(
|
|
1266
|
+
"content_block_delta",
|
|
1267
|
+
{ deltaType: delta2.type, index },
|
|
1268
|
+
msg
|
|
1269
|
+
)
|
|
1270
|
+
});
|
|
1271
|
+
handled = true;
|
|
1272
|
+
}
|
|
1273
|
+
if (delta2.type === "input_json_delta") {
|
|
1274
|
+
const tool = typeof index === "number" ? toolBlocks.get(index) : void 0;
|
|
1275
|
+
emit({
|
|
1276
|
+
type: "tool_call",
|
|
1277
|
+
provider: "claude",
|
|
1278
|
+
name: tool?.name,
|
|
1279
|
+
callId: tool?.id,
|
|
1280
|
+
input: delta2.partial_json,
|
|
1281
|
+
phase: "delta",
|
|
1282
|
+
providerDetail: buildProviderDetail(
|
|
1283
|
+
"content_block_delta",
|
|
1284
|
+
{ deltaType: delta2.type, index, name: tool?.name, id: tool?.id },
|
|
1285
|
+
msg
|
|
1286
|
+
)
|
|
1287
|
+
});
|
|
1288
|
+
handled = true;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (evType === "content_block_stop" && typeof index === "number") {
|
|
1292
|
+
if (toolBlocks.has(index)) {
|
|
1293
|
+
const tool = toolBlocks.get(index);
|
|
1294
|
+
emit({
|
|
1295
|
+
type: "tool_call",
|
|
1296
|
+
provider: "claude",
|
|
1297
|
+
name: tool?.name,
|
|
1298
|
+
callId: tool?.id,
|
|
1299
|
+
phase: "completed",
|
|
1300
|
+
providerDetail: buildProviderDetail(
|
|
1301
|
+
"content_block_stop",
|
|
1302
|
+
{ index, name: tool?.name, id: tool?.id },
|
|
1303
|
+
msg
|
|
1304
|
+
)
|
|
1305
|
+
});
|
|
1306
|
+
toolBlocks.delete(index);
|
|
1307
|
+
}
|
|
1308
|
+
if (thinkingBlocks.has(index)) {
|
|
1309
|
+
emit({
|
|
1310
|
+
type: "thinking",
|
|
1311
|
+
provider: "claude",
|
|
1312
|
+
phase: "completed",
|
|
1313
|
+
providerDetail: buildProviderDetail("content_block_stop", { index }, msg)
|
|
1314
|
+
});
|
|
1315
|
+
thinkingBlocks.delete(index);
|
|
1316
|
+
}
|
|
1317
|
+
handled = true;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
847
1320
|
const delta = extractAssistantDelta(msg);
|
|
848
1321
|
if (delta) {
|
|
849
1322
|
aggregated += delta;
|
|
850
|
-
|
|
1323
|
+
emit({
|
|
1324
|
+
type: "delta",
|
|
1325
|
+
text: delta,
|
|
1326
|
+
providerDetail: buildProviderDetail(msgType || "delta", {}, msg)
|
|
1327
|
+
});
|
|
851
1328
|
return;
|
|
852
1329
|
}
|
|
853
1330
|
const assistant = extractAssistantText(msg);
|
|
854
1331
|
if (assistant && !aggregated) {
|
|
855
1332
|
aggregated = assistant;
|
|
856
|
-
|
|
1333
|
+
emit({
|
|
1334
|
+
type: "delta",
|
|
1335
|
+
text: assistant,
|
|
1336
|
+
providerDetail: buildProviderDetail(msgType || "assistant", {}, msg)
|
|
1337
|
+
});
|
|
857
1338
|
return;
|
|
858
1339
|
}
|
|
859
1340
|
const result = extractResultText(msg);
|
|
860
1341
|
if (result && !didFinalize && !sawError) {
|
|
861
1342
|
didFinalize = true;
|
|
862
|
-
emitFinal(aggregated || result);
|
|
1343
|
+
emitFinal(aggregated || result, buildProviderDetail("result", {}, msg));
|
|
1344
|
+
handled = true;
|
|
1345
|
+
}
|
|
1346
|
+
if (!handled) {
|
|
1347
|
+
emit({
|
|
1348
|
+
type: "detail",
|
|
1349
|
+
provider: "claude",
|
|
1350
|
+
providerDetail: buildProviderDetail(msgType || "unknown", {}, msg)
|
|
1351
|
+
});
|
|
863
1352
|
}
|
|
864
1353
|
};
|
|
865
1354
|
const stdoutParser = createLineParser(handleLine);
|
|
@@ -886,23 +1375,177 @@ function runClaudePrompt({
|
|
|
886
1375
|
// src/providers/codex.ts
|
|
887
1376
|
import { spawn as spawn3 } from "child_process";
|
|
888
1377
|
import { readFile as readFile2 } from "fs/promises";
|
|
1378
|
+
import https2 from "https";
|
|
889
1379
|
import os3 from "os";
|
|
890
1380
|
import path3 from "path";
|
|
891
1381
|
var CODEX_PACKAGE = "@openai/codex";
|
|
892
1382
|
var DEFAULT_LOGIN2 = "codex login";
|
|
893
1383
|
var DEFAULT_STATUS2 = "codex login status";
|
|
894
1384
|
var CODEX_MODELS_CACHE_TTL_MS = 6e4;
|
|
1385
|
+
var CODEX_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
895
1386
|
var codexModelsCache = null;
|
|
896
1387
|
var codexModelsCacheAt = 0;
|
|
897
|
-
|
|
1388
|
+
var codexUpdateCache = null;
|
|
1389
|
+
var codexUpdatePromise = null;
|
|
1390
|
+
function trimOutput2(value, limit = 400) {
|
|
898
1391
|
const cleaned = value.trim();
|
|
899
1392
|
if (!cleaned) return "";
|
|
900
1393
|
if (cleaned.length <= limit) return cleaned;
|
|
901
1394
|
return `${cleaned.slice(0, limit)}...`;
|
|
902
1395
|
}
|
|
1396
|
+
function normalizePath2(value) {
|
|
1397
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1398
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
1399
|
+
}
|
|
903
1400
|
function getCodexConfigDir() {
|
|
904
1401
|
return process.env.CODEX_CONFIG_DIR || path3.join(os3.homedir(), ".codex");
|
|
905
1402
|
}
|
|
1403
|
+
function fetchJson2(url) {
|
|
1404
|
+
return new Promise((resolve) => {
|
|
1405
|
+
https2.get(url, (res) => {
|
|
1406
|
+
let data = "";
|
|
1407
|
+
res.on("data", (chunk) => {
|
|
1408
|
+
data += chunk;
|
|
1409
|
+
});
|
|
1410
|
+
res.on("end", () => {
|
|
1411
|
+
try {
|
|
1412
|
+
resolve(JSON.parse(data));
|
|
1413
|
+
} catch {
|
|
1414
|
+
resolve(null);
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
}).on("error", () => resolve(null));
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
function parseSemver2(value) {
|
|
1421
|
+
if (!value) return null;
|
|
1422
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
1423
|
+
if (!match) return null;
|
|
1424
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1425
|
+
}
|
|
1426
|
+
function compareSemver2(a, b) {
|
|
1427
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
1428
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
1429
|
+
return a[2] - b[2];
|
|
1430
|
+
}
|
|
1431
|
+
async function fetchLatestNpmVersion2(pkg) {
|
|
1432
|
+
const encoded = encodeURIComponent(pkg);
|
|
1433
|
+
const data = await fetchJson2(`https://registry.npmjs.org/${encoded}`);
|
|
1434
|
+
if (!data || typeof data !== "object") return null;
|
|
1435
|
+
const latest = data["dist-tags"]?.latest;
|
|
1436
|
+
return typeof latest === "string" ? latest : null;
|
|
1437
|
+
}
|
|
1438
|
+
async function fetchBrewFormulaVersion(formula) {
|
|
1439
|
+
if (!commandExists("brew")) return null;
|
|
1440
|
+
const result = await runCommand("brew", ["info", "--json=v2", formula]);
|
|
1441
|
+
if (result.code !== 0) return null;
|
|
1442
|
+
try {
|
|
1443
|
+
const parsed = JSON.parse(result.stdout);
|
|
1444
|
+
const version = parsed?.formulae?.[0]?.versions?.stable;
|
|
1445
|
+
return typeof version === "string" ? version : null;
|
|
1446
|
+
} catch {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function getCodexUpdateAction(commandPath) {
|
|
1451
|
+
if (process.env.CODEX_MANAGED_BY_NPM) {
|
|
1452
|
+
return {
|
|
1453
|
+
command: "npm",
|
|
1454
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1455
|
+
source: "npm",
|
|
1456
|
+
commandLabel: "npm install -g @openai/codex"
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
if (process.env.CODEX_MANAGED_BY_BUN) {
|
|
1460
|
+
return {
|
|
1461
|
+
command: "bun",
|
|
1462
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1463
|
+
source: "bun",
|
|
1464
|
+
commandLabel: "bun install -g @openai/codex"
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
if (commandPath) {
|
|
1468
|
+
const normalized = normalizePath2(commandPath);
|
|
1469
|
+
if (normalized.includes(".bun/install/global")) {
|
|
1470
|
+
return {
|
|
1471
|
+
command: "bun",
|
|
1472
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1473
|
+
source: "bun",
|
|
1474
|
+
commandLabel: "bun install -g @openai/codex"
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
if (normalized.includes("/node_modules/.bin/") || normalized.includes("/lib/node_modules/")) {
|
|
1478
|
+
return {
|
|
1479
|
+
command: "npm",
|
|
1480
|
+
args: ["install", "-g", CODEX_PACKAGE],
|
|
1481
|
+
source: "npm",
|
|
1482
|
+
commandLabel: "npm install -g @openai/codex"
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (process.platform === "darwin" && commandPath && (commandPath.startsWith("/opt/homebrew") || commandPath.startsWith("/usr/local"))) {
|
|
1487
|
+
return {
|
|
1488
|
+
command: "brew",
|
|
1489
|
+
args: ["upgrade", "codex"],
|
|
1490
|
+
source: "brew",
|
|
1491
|
+
commandLabel: "brew upgrade codex"
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
function getCodexUpdateSnapshot(commandPath) {
|
|
1497
|
+
if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
|
|
1498
|
+
const action = getCodexUpdateAction(commandPath);
|
|
1499
|
+
return {
|
|
1500
|
+
updateAvailable: codexUpdateCache.updateAvailable,
|
|
1501
|
+
latestVersion: codexUpdateCache.latestVersion,
|
|
1502
|
+
updateCheckedAt: codexUpdateCache.checkedAt,
|
|
1503
|
+
updateSource: action?.source ?? "unknown",
|
|
1504
|
+
updateCommand: action?.commandLabel,
|
|
1505
|
+
updateMessage: codexUpdateCache.updateMessage
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
return {};
|
|
1509
|
+
}
|
|
1510
|
+
function ensureCodexUpdateCheck(currentVersion, commandPath) {
|
|
1511
|
+
if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
if (codexUpdatePromise) return;
|
|
1515
|
+
codexUpdatePromise = (async () => {
|
|
1516
|
+
const action = getCodexUpdateAction(commandPath || null);
|
|
1517
|
+
let latest = null;
|
|
1518
|
+
if (action?.source === "brew") {
|
|
1519
|
+
latest = await fetchBrewFormulaVersion("codex");
|
|
1520
|
+
} else {
|
|
1521
|
+
latest = await fetchLatestNpmVersion2(CODEX_PACKAGE);
|
|
1522
|
+
}
|
|
1523
|
+
let updateAvailable;
|
|
1524
|
+
let updateMessage;
|
|
1525
|
+
if (latest && currentVersion) {
|
|
1526
|
+
const a = parseSemver2(currentVersion);
|
|
1527
|
+
const b = parseSemver2(latest);
|
|
1528
|
+
if (a && b) {
|
|
1529
|
+
updateAvailable = compareSemver2(a, b) < 0;
|
|
1530
|
+
updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
codexUpdateCache = {
|
|
1534
|
+
checkedAt: Date.now(),
|
|
1535
|
+
updateAvailable,
|
|
1536
|
+
latestVersion: latest ?? void 0,
|
|
1537
|
+
updateMessage
|
|
1538
|
+
};
|
|
1539
|
+
debugLog("Codex", "update-check", {
|
|
1540
|
+
currentVersion,
|
|
1541
|
+
latest,
|
|
1542
|
+
updateAvailable,
|
|
1543
|
+
message: updateMessage
|
|
1544
|
+
});
|
|
1545
|
+
})().finally(() => {
|
|
1546
|
+
codexUpdatePromise = null;
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
906
1549
|
function hasAuthValue(value) {
|
|
907
1550
|
return typeof value === "string" && value.trim().length > 0;
|
|
908
1551
|
}
|
|
@@ -983,7 +1626,7 @@ async function ensureCodexInstalled() {
|
|
|
983
1626
|
});
|
|
984
1627
|
debugLog("Codex", "install-result", {
|
|
985
1628
|
code: installResult.code,
|
|
986
|
-
stderr:
|
|
1629
|
+
stderr: trimOutput2(installResult.stderr)
|
|
987
1630
|
});
|
|
988
1631
|
const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
989
1632
|
codexModelsCache = null;
|
|
@@ -1020,7 +1663,44 @@ ${result.stderr}`.toLowerCase();
|
|
|
1020
1663
|
loggedIn = await hasCodexAuth();
|
|
1021
1664
|
}
|
|
1022
1665
|
}
|
|
1023
|
-
|
|
1666
|
+
const resolved = resolveCommandRealPath(command2);
|
|
1667
|
+
if (installed) {
|
|
1668
|
+
ensureCodexUpdateCheck(versionCheck.version, resolved || null);
|
|
1669
|
+
}
|
|
1670
|
+
const updateInfo = installed ? getCodexUpdateSnapshot(resolved || null) : {};
|
|
1671
|
+
return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
|
|
1672
|
+
}
|
|
1673
|
+
async function updateCodex() {
|
|
1674
|
+
const command2 = getCodexCommand();
|
|
1675
|
+
if (!commandExists(command2)) {
|
|
1676
|
+
return { installed: false, loggedIn: false };
|
|
1677
|
+
}
|
|
1678
|
+
const resolved = resolveCommandRealPath(command2);
|
|
1679
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CODEX_UPDATE", "");
|
|
1680
|
+
const action = updateOverride.command ? null : getCodexUpdateAction(resolved || null);
|
|
1681
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
1682
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
1683
|
+
if (!updateCommand) {
|
|
1684
|
+
throw new Error("No update command available. Please update Codex manually.");
|
|
1685
|
+
}
|
|
1686
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
1687
|
+
debugLog("Codex", "update-run", { command: cmd, args: updateArgs });
|
|
1688
|
+
const result = await runCommand(cmd, updateArgs, {
|
|
1689
|
+
env: { ...process.env, CI: "1" }
|
|
1690
|
+
});
|
|
1691
|
+
debugLog("Codex", "update-result", {
|
|
1692
|
+
code: result.code,
|
|
1693
|
+
stdout: trimOutput2(result.stdout),
|
|
1694
|
+
stderr: trimOutput2(result.stderr)
|
|
1695
|
+
});
|
|
1696
|
+
if (result.code !== 0 && result.code !== null) {
|
|
1697
|
+
const message = trimOutput2(`${result.stdout}
|
|
1698
|
+
${result.stderr}`, 800) || "Update failed";
|
|
1699
|
+
throw new Error(message);
|
|
1700
|
+
}
|
|
1701
|
+
codexUpdateCache = null;
|
|
1702
|
+
codexUpdatePromise = null;
|
|
1703
|
+
return getCodexStatus();
|
|
1024
1704
|
}
|
|
1025
1705
|
async function loginCodex() {
|
|
1026
1706
|
const login = buildLoginCommand("AGENTCONNECT_CODEX_LOGIN", DEFAULT_LOGIN2);
|
|
@@ -1030,7 +1710,7 @@ async function loginCodex() {
|
|
|
1030
1710
|
const command2 = resolveWindowsCommand(login.command);
|
|
1031
1711
|
debugLog("Codex", "login", { command: command2, args: login.args });
|
|
1032
1712
|
const result = await runCommand(command2, login.args, { env: { ...process.env, CI: "1" } });
|
|
1033
|
-
debugLog("Codex", "login-result", { code: result.code, stderr:
|
|
1713
|
+
debugLog("Codex", "login-result", { code: result.code, stderr: trimOutput2(result.stderr) });
|
|
1034
1714
|
const status = await getCodexStatus();
|
|
1035
1715
|
codexModelsCache = null;
|
|
1036
1716
|
codexModelsCacheAt = 0;
|
|
@@ -1257,6 +1937,7 @@ function runCodexPrompt({
|
|
|
1257
1937
|
reasoningEffort,
|
|
1258
1938
|
repoRoot,
|
|
1259
1939
|
cwd,
|
|
1940
|
+
providerDetailLevel,
|
|
1260
1941
|
onEvent,
|
|
1261
1942
|
signal
|
|
1262
1943
|
}) {
|
|
@@ -1294,10 +1975,88 @@ function runCodexPrompt({
|
|
|
1294
1975
|
let finalSessionId = null;
|
|
1295
1976
|
let didFinalize = false;
|
|
1296
1977
|
let sawError = false;
|
|
1297
|
-
const
|
|
1978
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
1979
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
1980
|
+
const detail = { eventType };
|
|
1981
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
1982
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
1983
|
+
return detail;
|
|
1984
|
+
};
|
|
1985
|
+
const emit = (event) => {
|
|
1986
|
+
if (finalSessionId) {
|
|
1987
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
1988
|
+
} else {
|
|
1989
|
+
onEvent(event);
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
const emitError = (message, providerDetail) => {
|
|
1298
1993
|
if (sawError) return;
|
|
1299
1994
|
sawError = true;
|
|
1300
|
-
|
|
1995
|
+
emit({ type: "error", message, providerDetail });
|
|
1996
|
+
};
|
|
1997
|
+
const emitItemEvent = (item, phase) => {
|
|
1998
|
+
const itemType = typeof item.type === "string" ? item.type : "";
|
|
1999
|
+
if (!itemType) return;
|
|
2000
|
+
const providerDetail = buildProviderDetail(
|
|
2001
|
+
phase === "start" ? "item.started" : "item.completed",
|
|
2002
|
+
{
|
|
2003
|
+
itemType,
|
|
2004
|
+
itemId: item.id,
|
|
2005
|
+
status: item.status
|
|
2006
|
+
},
|
|
2007
|
+
item
|
|
2008
|
+
);
|
|
2009
|
+
if (itemType === "agent_message") {
|
|
2010
|
+
if (phase === "completed" && typeof item.text === "string") {
|
|
2011
|
+
emit({
|
|
2012
|
+
type: "message",
|
|
2013
|
+
provider: "codex",
|
|
2014
|
+
role: "assistant",
|
|
2015
|
+
content: item.text,
|
|
2016
|
+
contentParts: item,
|
|
2017
|
+
providerDetail
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
if (itemType === "reasoning") {
|
|
2023
|
+
emit({
|
|
2024
|
+
type: "thinking",
|
|
2025
|
+
provider: "codex",
|
|
2026
|
+
phase,
|
|
2027
|
+
text: typeof item.text === "string" ? item.text : void 0,
|
|
2028
|
+
providerDetail
|
|
2029
|
+
});
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (itemType === "command_execution") {
|
|
2033
|
+
const output = phase === "completed" ? {
|
|
2034
|
+
output: item.aggregated_output,
|
|
2035
|
+
exitCode: item.exit_code,
|
|
2036
|
+
status: item.status
|
|
2037
|
+
} : void 0;
|
|
2038
|
+
emit({
|
|
2039
|
+
type: "tool_call",
|
|
2040
|
+
provider: "codex",
|
|
2041
|
+
name: "command_execution",
|
|
2042
|
+
callId: item.id,
|
|
2043
|
+
input: { command: item.command },
|
|
2044
|
+
output,
|
|
2045
|
+
phase,
|
|
2046
|
+
providerDetail
|
|
2047
|
+
});
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
emit({
|
|
2051
|
+
type: "tool_call",
|
|
2052
|
+
provider: "codex",
|
|
2053
|
+
name: itemType,
|
|
2054
|
+
callId: item.id,
|
|
2055
|
+
input: phase === "start" ? item : void 0,
|
|
2056
|
+
output: phase === "completed" ? item : void 0,
|
|
2057
|
+
phase,
|
|
2058
|
+
providerDetail
|
|
2059
|
+
});
|
|
1301
2060
|
};
|
|
1302
2061
|
let sawJson = false;
|
|
1303
2062
|
const stdoutLines = [];
|
|
@@ -1307,18 +2066,14 @@ function runCodexPrompt({
|
|
|
1307
2066
|
list.push(line);
|
|
1308
2067
|
if (list.length > 12) list.shift();
|
|
1309
2068
|
};
|
|
1310
|
-
const emitFinal = (text) => {
|
|
1311
|
-
|
|
1312
|
-
onEvent({ type: "final", text, providerSessionId: finalSessionId });
|
|
1313
|
-
} else {
|
|
1314
|
-
onEvent({ type: "final", text });
|
|
1315
|
-
}
|
|
2069
|
+
const emitFinal = (text, providerDetail) => {
|
|
2070
|
+
emit({ type: "final", text, providerDetail });
|
|
1316
2071
|
};
|
|
1317
2072
|
const handleLine = (line, source) => {
|
|
1318
2073
|
const parsed = safeJsonParse2(line);
|
|
1319
2074
|
if (!parsed || typeof parsed !== "object") {
|
|
1320
2075
|
if (line.trim()) {
|
|
1321
|
-
|
|
2076
|
+
emit({ type: "raw_line", line });
|
|
1322
2077
|
}
|
|
1323
2078
|
if (source === "stdout") {
|
|
1324
2079
|
pushLine(stdoutLines, line);
|
|
@@ -1330,47 +2085,85 @@ function runCodexPrompt({
|
|
|
1330
2085
|
sawJson = true;
|
|
1331
2086
|
const ev = parsed;
|
|
1332
2087
|
const normalized = normalizeEvent(ev);
|
|
1333
|
-
onEvent({ type: "provider_event", provider: "codex", event: normalized });
|
|
1334
2088
|
const sid = extractSessionId2(ev);
|
|
1335
2089
|
if (sid) finalSessionId = sid;
|
|
2090
|
+
const eventType = typeof ev.type === "string" ? ev.type : normalized.type;
|
|
2091
|
+
const detailData = {};
|
|
2092
|
+
const threadId = ev.thread_id ?? ev.threadId;
|
|
2093
|
+
if (typeof threadId === "string" && threadId) detailData.threadId = threadId;
|
|
2094
|
+
const providerDetail = buildProviderDetail(eventType || "unknown", detailData, ev);
|
|
2095
|
+
let handled = false;
|
|
1336
2096
|
const usage = extractUsage(ev);
|
|
1337
2097
|
if (usage) {
|
|
1338
|
-
|
|
2098
|
+
emit({
|
|
1339
2099
|
type: "usage",
|
|
1340
2100
|
inputTokens: usage.input_tokens,
|
|
1341
|
-
outputTokens: usage.output_tokens
|
|
2101
|
+
outputTokens: usage.output_tokens,
|
|
2102
|
+
providerDetail
|
|
1342
2103
|
});
|
|
2104
|
+
handled = true;
|
|
1343
2105
|
}
|
|
1344
2106
|
if (normalized.type === "agent_message") {
|
|
1345
2107
|
const text = normalized.text;
|
|
1346
2108
|
if (typeof text === "string" && text) {
|
|
1347
2109
|
aggregated += text;
|
|
1348
|
-
|
|
2110
|
+
emit({ type: "delta", text, providerDetail });
|
|
2111
|
+
emit({
|
|
2112
|
+
type: "message",
|
|
2113
|
+
provider: "codex",
|
|
2114
|
+
role: "assistant",
|
|
2115
|
+
content: text,
|
|
2116
|
+
contentParts: ev,
|
|
2117
|
+
providerDetail
|
|
2118
|
+
});
|
|
2119
|
+
handled = true;
|
|
1349
2120
|
}
|
|
1350
2121
|
} else if (normalized.type === "item.completed") {
|
|
1351
2122
|
const item = normalized.item;
|
|
1352
2123
|
if (item && typeof item === "object") {
|
|
2124
|
+
const itemDetail = buildProviderDetail("item.completed", {
|
|
2125
|
+
itemType: item.type,
|
|
2126
|
+
itemId: item.id,
|
|
2127
|
+
status: item.status
|
|
2128
|
+
}, item);
|
|
1353
2129
|
if (item.type === "command_execution" && typeof item.aggregated_output === "string") {
|
|
1354
|
-
|
|
2130
|
+
emit({ type: "delta", text: item.aggregated_output, providerDetail: itemDetail });
|
|
1355
2131
|
}
|
|
1356
2132
|
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
1357
2133
|
aggregated += item.text;
|
|
1358
|
-
|
|
2134
|
+
emit({ type: "delta", text: item.text, providerDetail: itemDetail });
|
|
1359
2135
|
}
|
|
1360
2136
|
}
|
|
1361
2137
|
}
|
|
2138
|
+
if (normalized.type === "item.started" && ev.item && typeof ev.item === "object") {
|
|
2139
|
+
emitItemEvent(ev.item, "start");
|
|
2140
|
+
handled = true;
|
|
2141
|
+
}
|
|
2142
|
+
if (normalized.type === "item.completed" && ev.item && typeof ev.item === "object") {
|
|
2143
|
+
emitItemEvent(ev.item, "completed");
|
|
2144
|
+
handled = true;
|
|
2145
|
+
}
|
|
2146
|
+
if (normalized.type === "error") {
|
|
2147
|
+
emitError(normalized.message || "Codex run failed", providerDetail);
|
|
2148
|
+
handled = true;
|
|
2149
|
+
}
|
|
1362
2150
|
if (isTerminalEvent(ev) && !didFinalize) {
|
|
1363
2151
|
if (ev.type === "turn.failed") {
|
|
1364
2152
|
const message = ev.error?.message;
|
|
1365
|
-
emitError(typeof message === "string" ? message : "Codex run failed");
|
|
2153
|
+
emitError(typeof message === "string" ? message : "Codex run failed", providerDetail);
|
|
1366
2154
|
didFinalize = true;
|
|
2155
|
+
handled = true;
|
|
1367
2156
|
return;
|
|
1368
2157
|
}
|
|
1369
2158
|
if (!sawError) {
|
|
1370
2159
|
didFinalize = true;
|
|
1371
|
-
emitFinal(aggregated);
|
|
2160
|
+
emitFinal(aggregated, providerDetail);
|
|
2161
|
+
handled = true;
|
|
1372
2162
|
}
|
|
1373
2163
|
}
|
|
2164
|
+
if (!handled) {
|
|
2165
|
+
emit({ type: "detail", provider: "codex", providerDetail });
|
|
2166
|
+
}
|
|
1374
2167
|
};
|
|
1375
2168
|
const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
|
|
1376
2169
|
const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
|
|
@@ -1421,49 +2214,841 @@ function runCodexPrompt({
|
|
|
1421
2214
|
});
|
|
1422
2215
|
}
|
|
1423
2216
|
|
|
1424
|
-
// src/providers/
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2217
|
+
// src/providers/cursor.ts
|
|
2218
|
+
import { spawn as spawn4 } from "child_process";
|
|
2219
|
+
import path4 from "path";
|
|
2220
|
+
var INSTALL_UNIX2 = "curl https://cursor.com/install -fsS | bash";
|
|
2221
|
+
var DEFAULT_LOGIN3 = "cursor-agent login";
|
|
2222
|
+
var DEFAULT_STATUS3 = "cursor-agent status";
|
|
2223
|
+
var CURSOR_MODELS_COMMAND = "cursor-agent models";
|
|
2224
|
+
var CURSOR_MODELS_CACHE_TTL_MS = 6e4;
|
|
2225
|
+
var CURSOR_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
2226
|
+
var cursorModelsCache = null;
|
|
2227
|
+
var cursorModelsCacheAt = 0;
|
|
2228
|
+
var cursorUpdateCache = null;
|
|
2229
|
+
var cursorUpdatePromise = null;
|
|
2230
|
+
function trimOutput3(value, limit = 400) {
|
|
2231
|
+
const cleaned = value.trim();
|
|
2232
|
+
if (!cleaned) return "";
|
|
2233
|
+
if (cleaned.length <= limit) return cleaned;
|
|
2234
|
+
return `${cleaned.slice(0, limit)}...`;
|
|
1428
2235
|
}
|
|
1429
|
-
function
|
|
1430
|
-
|
|
2236
|
+
function normalizePath3(value) {
|
|
2237
|
+
const normalized = value.replace(/\\/g, "/");
|
|
2238
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
1431
2239
|
}
|
|
1432
|
-
function
|
|
1433
|
-
if (!
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
|
|
1437
|
-
if (raw.startsWith("local/")) return raw.slice("local/".length);
|
|
1438
|
-
return raw;
|
|
2240
|
+
function parseSemver3(value) {
|
|
2241
|
+
if (!value) return null;
|
|
2242
|
+
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
2243
|
+
if (!match) return null;
|
|
2244
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1439
2245
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
2246
|
+
function compareSemver3(a, b) {
|
|
2247
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
2248
|
+
if (a[1] !== b[1]) return a[1] - b[1];
|
|
2249
|
+
return a[2] - b[2];
|
|
2250
|
+
}
|
|
2251
|
+
async function fetchBrewCaskVersion2(cask) {
|
|
2252
|
+
if (!commandExists("brew")) return null;
|
|
2253
|
+
const result = await runCommand("brew", ["info", "--json=v2", "--cask", cask]);
|
|
2254
|
+
if (result.code !== 0) return null;
|
|
1443
2255
|
try {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
}
|
|
1448
|
-
const data = await res.json();
|
|
1449
|
-
return { ok: true, status: res.status, data };
|
|
2256
|
+
const parsed = JSON.parse(result.stdout);
|
|
2257
|
+
const version = parsed?.casks?.[0]?.version;
|
|
2258
|
+
return typeof version === "string" ? version : null;
|
|
1450
2259
|
} catch {
|
|
1451
|
-
return
|
|
1452
|
-
}
|
|
1453
|
-
|
|
2260
|
+
return null;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
function getCursorUpdateAction(commandPath) {
|
|
2264
|
+
if (!commandPath) return null;
|
|
2265
|
+
const normalized = normalizePath3(commandPath);
|
|
2266
|
+
if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/")) {
|
|
2267
|
+
return {
|
|
2268
|
+
command: "brew",
|
|
2269
|
+
args: ["upgrade", "--cask", "cursor"],
|
|
2270
|
+
source: "brew",
|
|
2271
|
+
commandLabel: "brew upgrade --cask cursor"
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
if (normalized.includes("/.local/bin/") || normalized.includes("/.local/share/cursor-agent/versions/")) {
|
|
2275
|
+
return {
|
|
2276
|
+
command: "bash",
|
|
2277
|
+
args: ["-lc", INSTALL_UNIX2],
|
|
2278
|
+
source: "script",
|
|
2279
|
+
commandLabel: INSTALL_UNIX2
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
return null;
|
|
2283
|
+
}
|
|
2284
|
+
function getCursorCommand() {
|
|
2285
|
+
const override = process.env.AGENTCONNECT_CURSOR_COMMAND;
|
|
2286
|
+
const base = override || "cursor-agent";
|
|
2287
|
+
const resolved = resolveCommandPath(base);
|
|
2288
|
+
return resolved || resolveWindowsCommand(base);
|
|
2289
|
+
}
|
|
2290
|
+
function getCursorApiKey() {
|
|
2291
|
+
return process.env.CURSOR_API_KEY || process.env.AGENTCONNECT_CURSOR_API_KEY || "";
|
|
2292
|
+
}
|
|
2293
|
+
function getCursorDefaultModel() {
|
|
2294
|
+
return process.env.AGENTCONNECT_CURSOR_MODEL?.trim() || "";
|
|
2295
|
+
}
|
|
2296
|
+
function resolveCursorEndpoint() {
|
|
2297
|
+
return process.env.AGENTCONNECT_CURSOR_ENDPOINT?.trim() || "";
|
|
2298
|
+
}
|
|
2299
|
+
function withCursorEndpoint(args2) {
|
|
2300
|
+
const endpoint = resolveCursorEndpoint();
|
|
2301
|
+
if (!endpoint) return args2;
|
|
2302
|
+
if (args2.includes("--endpoint")) return args2;
|
|
2303
|
+
return [...args2, "--endpoint", endpoint];
|
|
2304
|
+
}
|
|
2305
|
+
function resolveCursorModel(model, fallback) {
|
|
2306
|
+
if (!model) return fallback;
|
|
2307
|
+
const raw = String(model);
|
|
2308
|
+
if (raw === "cursor" || raw === "cursor-default") return fallback;
|
|
2309
|
+
if (raw.startsWith("cursor:")) return raw.slice("cursor:".length);
|
|
2310
|
+
if (raw.startsWith("cursor/")) return raw.slice("cursor/".length);
|
|
2311
|
+
return raw;
|
|
2312
|
+
}
|
|
2313
|
+
function formatCursorDefaultLabel(fallback) {
|
|
2314
|
+
if (!fallback) return "Default";
|
|
2315
|
+
return `Default \xB7 ${fallback}`;
|
|
2316
|
+
}
|
|
2317
|
+
function normalizeCursorModelId(value) {
|
|
2318
|
+
if (value.startsWith("cursor:") || value.startsWith("cursor/")) return value;
|
|
2319
|
+
return `cursor:${value}`;
|
|
2320
|
+
}
|
|
2321
|
+
function normalizeCursorModelDisplay(value) {
|
|
2322
|
+
if (value.startsWith("cursor:")) return value.slice("cursor:".length);
|
|
2323
|
+
if (value.startsWith("cursor/")) return value.slice("cursor/".length);
|
|
2324
|
+
return value;
|
|
2325
|
+
}
|
|
2326
|
+
async function listCursorModelsFromCli() {
|
|
2327
|
+
const command2 = getCursorCommand();
|
|
2328
|
+
if (!commandExists(command2)) return [];
|
|
2329
|
+
const modelsCommand = buildStatusCommand(
|
|
2330
|
+
"AGENTCONNECT_CURSOR_MODELS_COMMAND",
|
|
2331
|
+
CURSOR_MODELS_COMMAND
|
|
2332
|
+
);
|
|
2333
|
+
if (!modelsCommand.command) return [];
|
|
2334
|
+
const resolvedCommand = resolveWindowsCommand(modelsCommand.command);
|
|
2335
|
+
const result = await runCommand(resolvedCommand, withCursorEndpoint(modelsCommand.args), {
|
|
2336
|
+
env: buildCursorEnv()
|
|
2337
|
+
});
|
|
2338
|
+
const output = `${result.stdout}
|
|
2339
|
+
${result.stderr}`.trim();
|
|
2340
|
+
if (!output) return [];
|
|
2341
|
+
const parsed = safeJsonParse3(output);
|
|
2342
|
+
if (Array.isArray(parsed)) {
|
|
2343
|
+
const models = parsed.map((entry) => {
|
|
2344
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2345
|
+
const value = entry.trim();
|
|
2346
|
+
return {
|
|
2347
|
+
id: normalizeCursorModelId(value),
|
|
2348
|
+
provider: "cursor",
|
|
2349
|
+
displayName: normalizeCursorModelDisplay(value)
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
if (entry && typeof entry === "object") {
|
|
2353
|
+
const record = entry;
|
|
2354
|
+
const idRaw = typeof record.id === "string" ? record.id.trim() : "";
|
|
2355
|
+
const nameRaw = typeof record.name === "string" ? record.name.trim() : "";
|
|
2356
|
+
const displayRaw = typeof record.displayName === "string" ? record.displayName.trim() : "";
|
|
2357
|
+
const value = idRaw || nameRaw || displayRaw;
|
|
2358
|
+
if (!value) return null;
|
|
2359
|
+
return {
|
|
2360
|
+
id: normalizeCursorModelId(value),
|
|
2361
|
+
provider: "cursor",
|
|
2362
|
+
displayName: normalizeCursorModelDisplay(displayRaw || nameRaw || value)
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
return null;
|
|
2366
|
+
}).filter(Boolean);
|
|
2367
|
+
return models;
|
|
2368
|
+
}
|
|
2369
|
+
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => !line.toLowerCase().startsWith("model")).filter((line) => !/^[-=]{2,}$/.test(line));
|
|
2370
|
+
return lines.map((line) => {
|
|
2371
|
+
const cleaned = line.replace(/^[-*•]\s*/, "");
|
|
2372
|
+
const value = cleaned.split(/\s+/)[0] || cleaned;
|
|
2373
|
+
return {
|
|
2374
|
+
id: normalizeCursorModelId(value),
|
|
2375
|
+
provider: "cursor",
|
|
2376
|
+
displayName: normalizeCursorModelDisplay(value)
|
|
2377
|
+
};
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
async function listCursorModels() {
|
|
2381
|
+
if (cursorModelsCache && Date.now() - cursorModelsCacheAt < CURSOR_MODELS_CACHE_TTL_MS) {
|
|
2382
|
+
return cursorModelsCache;
|
|
2383
|
+
}
|
|
2384
|
+
const fallback = getCursorDefaultModel();
|
|
2385
|
+
const base = [
|
|
2386
|
+
{
|
|
2387
|
+
id: "cursor-default",
|
|
2388
|
+
provider: "cursor",
|
|
2389
|
+
displayName: formatCursorDefaultLabel(fallback)
|
|
2390
|
+
}
|
|
2391
|
+
];
|
|
2392
|
+
const envModels = process.env.AGENTCONNECT_CURSOR_MODELS;
|
|
2393
|
+
if (envModels) {
|
|
2394
|
+
try {
|
|
2395
|
+
const parsed = JSON.parse(envModels);
|
|
2396
|
+
if (Array.isArray(parsed)) {
|
|
2397
|
+
for (const entry of parsed) {
|
|
2398
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
2399
|
+
const trimmed = entry.trim();
|
|
2400
|
+
base.push({
|
|
2401
|
+
id: normalizeCursorModelId(trimmed),
|
|
2402
|
+
provider: "cursor",
|
|
2403
|
+
displayName: normalizeCursorModelDisplay(trimmed)
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
} catch {
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const cliModels = await listCursorModelsFromCli();
|
|
2412
|
+
base.push(...cliModels);
|
|
2413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2414
|
+
const list = base.filter((entry) => {
|
|
2415
|
+
const key = `${entry.provider}:${entry.id}`;
|
|
2416
|
+
if (seen.has(key)) return false;
|
|
2417
|
+
seen.add(key);
|
|
2418
|
+
return true;
|
|
2419
|
+
});
|
|
2420
|
+
cursorModelsCache = list;
|
|
2421
|
+
cursorModelsCacheAt = Date.now();
|
|
2422
|
+
return list;
|
|
2423
|
+
}
|
|
2424
|
+
function normalizeCursorStatusOutput(output) {
|
|
2425
|
+
const text = output.toLowerCase();
|
|
2426
|
+
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")) {
|
|
2427
|
+
return false;
|
|
2428
|
+
}
|
|
2429
|
+
if (text.includes("authenticated") || text.includes("logged in") || text.includes("signed in") || text.includes("account")) {
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2432
|
+
return null;
|
|
2433
|
+
}
|
|
2434
|
+
function getCursorUpdateSnapshot(commandPath) {
|
|
2435
|
+
if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
|
|
2436
|
+
const action = getCursorUpdateAction(commandPath);
|
|
2437
|
+
return {
|
|
2438
|
+
updateAvailable: cursorUpdateCache.updateAvailable,
|
|
2439
|
+
latestVersion: cursorUpdateCache.latestVersion,
|
|
2440
|
+
updateCheckedAt: cursorUpdateCache.checkedAt,
|
|
2441
|
+
updateSource: action?.source ?? "unknown",
|
|
2442
|
+
updateCommand: action?.commandLabel,
|
|
2443
|
+
updateMessage: cursorUpdateCache.updateMessage
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
return {};
|
|
2447
|
+
}
|
|
2448
|
+
function ensureCursorUpdateCheck(currentVersion, commandPath) {
|
|
2449
|
+
if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (cursorUpdatePromise) return;
|
|
2453
|
+
cursorUpdatePromise = (async () => {
|
|
2454
|
+
const action = getCursorUpdateAction(commandPath || null);
|
|
2455
|
+
let latest = null;
|
|
2456
|
+
let updateAvailable;
|
|
2457
|
+
let updateMessage;
|
|
2458
|
+
if (action?.source === "brew") {
|
|
2459
|
+
latest = await fetchBrewCaskVersion2("cursor");
|
|
2460
|
+
}
|
|
2461
|
+
if (latest && currentVersion) {
|
|
2462
|
+
const a = parseSemver3(currentVersion);
|
|
2463
|
+
const b = parseSemver3(latest);
|
|
2464
|
+
if (a && b) {
|
|
2465
|
+
updateAvailable = compareSemver3(a, b) < 0;
|
|
2466
|
+
updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
|
|
2467
|
+
}
|
|
2468
|
+
} else if (!action) {
|
|
2469
|
+
updateMessage = "Update check unavailable";
|
|
2470
|
+
}
|
|
2471
|
+
debugLog("Cursor", "update-check", {
|
|
2472
|
+
updateAvailable,
|
|
2473
|
+
message: updateMessage
|
|
2474
|
+
});
|
|
2475
|
+
cursorUpdateCache = {
|
|
2476
|
+
checkedAt: Date.now(),
|
|
2477
|
+
updateAvailable,
|
|
2478
|
+
latestVersion: latest ?? void 0,
|
|
2479
|
+
updateMessage
|
|
2480
|
+
};
|
|
2481
|
+
})().finally(() => {
|
|
2482
|
+
cursorUpdatePromise = null;
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
async function ensureCursorInstalled() {
|
|
2486
|
+
const command2 = getCursorCommand();
|
|
2487
|
+
const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2488
|
+
debugLog("Cursor", "install-check", {
|
|
2489
|
+
command: command2,
|
|
2490
|
+
versionOk: versionCheck.ok,
|
|
2491
|
+
version: versionCheck.version
|
|
2492
|
+
});
|
|
2493
|
+
if (versionCheck.ok) {
|
|
2494
|
+
return { installed: true, version: versionCheck.version || void 0 };
|
|
2495
|
+
}
|
|
2496
|
+
if (commandExists(command2)) {
|
|
2497
|
+
return { installed: true, version: void 0 };
|
|
2498
|
+
}
|
|
2499
|
+
const override = buildInstallCommand("AGENTCONNECT_CURSOR_INSTALL", "");
|
|
2500
|
+
let install = override;
|
|
2501
|
+
let packageManager = override.command ? "unknown" : "unknown";
|
|
2502
|
+
if (!install.command) {
|
|
2503
|
+
if (process.platform !== "win32" && commandExists("bash") && commandExists("curl")) {
|
|
2504
|
+
install = { command: "bash", args: ["-lc", INSTALL_UNIX2] };
|
|
2505
|
+
packageManager = "script";
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
if (!install.command) {
|
|
2509
|
+
return { installed: false, version: void 0, packageManager };
|
|
2510
|
+
}
|
|
2511
|
+
await runCommand(install.command, install.args, { shell: process.platform === "win32" });
|
|
2512
|
+
const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2513
|
+
return {
|
|
2514
|
+
installed: after.ok,
|
|
2515
|
+
version: after.version || void 0,
|
|
2516
|
+
packageManager
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
async function getCursorStatus() {
|
|
2520
|
+
const command2 = getCursorCommand();
|
|
2521
|
+
const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
|
|
2522
|
+
const installed = versionCheck.ok || commandExists(command2);
|
|
2523
|
+
let loggedIn = false;
|
|
2524
|
+
if (installed) {
|
|
2525
|
+
const status = buildStatusCommand("AGENTCONNECT_CURSOR_STATUS", DEFAULT_STATUS3);
|
|
2526
|
+
if (status.command) {
|
|
2527
|
+
const statusCommand = resolveWindowsCommand(status.command);
|
|
2528
|
+
const result = await runCommand(statusCommand, withCursorEndpoint(status.args), {
|
|
2529
|
+
env: buildCursorEnv()
|
|
2530
|
+
});
|
|
2531
|
+
const output = `${result.stdout}
|
|
2532
|
+
${result.stderr}`;
|
|
2533
|
+
const parsed = normalizeCursorStatusOutput(output);
|
|
2534
|
+
loggedIn = parsed ?? result.code === 0;
|
|
2535
|
+
}
|
|
2536
|
+
if (!loggedIn) {
|
|
2537
|
+
loggedIn = Boolean(getCursorApiKey().trim());
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
if (installed) {
|
|
2541
|
+
const resolved2 = resolveCommandRealPath(command2);
|
|
2542
|
+
ensureCursorUpdateCheck(versionCheck.version, resolved2 || null);
|
|
2543
|
+
}
|
|
2544
|
+
const resolved = resolveCommandRealPath(command2);
|
|
2545
|
+
const updateInfo = installed ? getCursorUpdateSnapshot(resolved || null) : {};
|
|
2546
|
+
return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
|
|
2547
|
+
}
|
|
2548
|
+
async function updateCursor() {
|
|
2549
|
+
const command2 = getCursorCommand();
|
|
2550
|
+
if (!commandExists(command2)) {
|
|
2551
|
+
return { installed: false, loggedIn: false };
|
|
2552
|
+
}
|
|
2553
|
+
const resolved = resolveCommandRealPath(command2);
|
|
2554
|
+
const updateOverride = buildStatusCommand("AGENTCONNECT_CURSOR_UPDATE", "");
|
|
2555
|
+
const action = updateOverride.command ? null : getCursorUpdateAction(resolved || null);
|
|
2556
|
+
const updateCommand = updateOverride.command || action?.command || "";
|
|
2557
|
+
const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
|
|
2558
|
+
if (!updateCommand) {
|
|
2559
|
+
throw new Error("No update command available. Please update Cursor manually.");
|
|
2560
|
+
}
|
|
2561
|
+
const cmd = resolveWindowsCommand(updateCommand);
|
|
2562
|
+
debugLog("Cursor", "update-run", { command: cmd, args: updateArgs });
|
|
2563
|
+
const result = await runCommand(cmd, updateArgs, { env: buildCursorEnv() });
|
|
2564
|
+
debugLog("Cursor", "update-result", {
|
|
2565
|
+
code: result.code,
|
|
2566
|
+
stdout: trimOutput3(result.stdout),
|
|
2567
|
+
stderr: trimOutput3(result.stderr)
|
|
2568
|
+
});
|
|
2569
|
+
if (result.code !== 0 && result.code !== null) {
|
|
2570
|
+
const message = trimOutput3(`${result.stdout}
|
|
2571
|
+
${result.stderr}`, 800) || "Update failed";
|
|
2572
|
+
throw new Error(message);
|
|
2573
|
+
}
|
|
2574
|
+
cursorUpdateCache = null;
|
|
2575
|
+
cursorUpdatePromise = null;
|
|
2576
|
+
return getCursorStatus();
|
|
2577
|
+
}
|
|
2578
|
+
function buildCursorEnv() {
|
|
2579
|
+
const env = { ...process.env };
|
|
2580
|
+
const apiKey = getCursorApiKey().trim();
|
|
2581
|
+
if (apiKey) {
|
|
2582
|
+
env.CURSOR_API_KEY = apiKey;
|
|
2583
|
+
}
|
|
2584
|
+
return env;
|
|
2585
|
+
}
|
|
2586
|
+
async function loginCursor(options) {
|
|
2587
|
+
if (typeof options?.apiKey === "string") {
|
|
2588
|
+
process.env.CURSOR_API_KEY = options.apiKey;
|
|
2589
|
+
}
|
|
2590
|
+
if (typeof options?.baseUrl === "string") {
|
|
2591
|
+
process.env.AGENTCONNECT_CURSOR_ENDPOINT = options.baseUrl;
|
|
2592
|
+
}
|
|
2593
|
+
if (typeof options?.model === "string") {
|
|
2594
|
+
process.env.AGENTCONNECT_CURSOR_MODEL = options.model;
|
|
2595
|
+
cursorModelsCache = null;
|
|
2596
|
+
cursorModelsCacheAt = 0;
|
|
2597
|
+
}
|
|
2598
|
+
if (Array.isArray(options?.models)) {
|
|
2599
|
+
process.env.AGENTCONNECT_CURSOR_MODELS = JSON.stringify(options.models.filter(Boolean));
|
|
2600
|
+
cursorModelsCache = null;
|
|
2601
|
+
cursorModelsCacheAt = 0;
|
|
2602
|
+
}
|
|
2603
|
+
if (!options?.apiKey) {
|
|
2604
|
+
const login = buildLoginCommand("AGENTCONNECT_CURSOR_LOGIN", DEFAULT_LOGIN3);
|
|
2605
|
+
if (login.command) {
|
|
2606
|
+
const command2 = resolveWindowsCommand(login.command);
|
|
2607
|
+
await runCommand(command2, withCursorEndpoint(login.args), { env: buildCursorEnv() });
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
const timeoutMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_TIMEOUT_MS || 2e4);
|
|
2611
|
+
const pollIntervalMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_POLL_MS || 1e3);
|
|
2612
|
+
const start = Date.now();
|
|
2613
|
+
let status = await getCursorStatus();
|
|
2614
|
+
while (!status.loggedIn && Date.now() - start < timeoutMs) {
|
|
2615
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2616
|
+
status = await getCursorStatus();
|
|
2617
|
+
}
|
|
2618
|
+
return { loggedIn: status.loggedIn };
|
|
2619
|
+
}
|
|
2620
|
+
function safeJsonParse3(line) {
|
|
2621
|
+
try {
|
|
2622
|
+
return JSON.parse(line);
|
|
2623
|
+
} catch {
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
function extractSessionId3(ev) {
|
|
2628
|
+
const id = ev.session_id ?? ev.sessionId;
|
|
2629
|
+
return typeof id === "string" ? id : null;
|
|
2630
|
+
}
|
|
2631
|
+
function extractUsage2(ev) {
|
|
2632
|
+
const usage = ev.usage ?? ev.token_usage ?? ev.tokenUsage ?? ev.tokens;
|
|
2633
|
+
if (!usage || typeof usage !== "object") return null;
|
|
2634
|
+
const toNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2635
|
+
const input = toNumber(
|
|
2636
|
+
usage.input_tokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.promptTokens
|
|
2637
|
+
);
|
|
2638
|
+
const output = toNumber(
|
|
2639
|
+
usage.output_tokens ?? usage.completion_tokens ?? usage.outputTokens ?? usage.completionTokens
|
|
2640
|
+
);
|
|
2641
|
+
const total = toNumber(usage.total_tokens ?? usage.totalTokens);
|
|
2642
|
+
const out = {};
|
|
2643
|
+
if (input !== void 0) out.input_tokens = input;
|
|
2644
|
+
if (output !== void 0) out.output_tokens = output;
|
|
2645
|
+
if (total !== void 0) out.total_tokens = total;
|
|
2646
|
+
return Object.keys(out).length ? out : null;
|
|
2647
|
+
}
|
|
2648
|
+
function extractTextFromContent2(content) {
|
|
2649
|
+
if (!content) return "";
|
|
2650
|
+
if (typeof content === "string") return content;
|
|
2651
|
+
if (Array.isArray(content)) {
|
|
2652
|
+
return content.map((part) => {
|
|
2653
|
+
if (!part) return "";
|
|
2654
|
+
if (typeof part === "string") return part;
|
|
2655
|
+
if (typeof part === "object") {
|
|
2656
|
+
const text = part.text;
|
|
2657
|
+
if (typeof text === "string") return text;
|
|
2658
|
+
}
|
|
2659
|
+
return "";
|
|
2660
|
+
}).join("");
|
|
2661
|
+
}
|
|
2662
|
+
if (typeof content === "object") {
|
|
2663
|
+
const text = content.text;
|
|
2664
|
+
if (typeof text === "string") return text;
|
|
2665
|
+
}
|
|
2666
|
+
return "";
|
|
2667
|
+
}
|
|
2668
|
+
function extractToolCall(ev) {
|
|
2669
|
+
if (ev.type !== "tool_call") return null;
|
|
2670
|
+
const toolCall = ev.tool_call && typeof ev.tool_call === "object" ? ev.tool_call : null;
|
|
2671
|
+
if (!toolCall) return null;
|
|
2672
|
+
const keys = Object.keys(toolCall);
|
|
2673
|
+
if (!keys.length) return null;
|
|
2674
|
+
const name = keys[0];
|
|
2675
|
+
const entry = toolCall[name];
|
|
2676
|
+
const record = entry && typeof entry === "object" ? entry : null;
|
|
2677
|
+
const input = record?.args ?? record?.input ?? void 0;
|
|
2678
|
+
const output = record?.output ?? record?.result ?? void 0;
|
|
2679
|
+
const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
|
|
2680
|
+
const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : subtype === "delta" ? "delta" : void 0;
|
|
2681
|
+
return {
|
|
2682
|
+
name,
|
|
2683
|
+
input,
|
|
2684
|
+
output,
|
|
2685
|
+
callId: typeof ev.call_id === "string" ? ev.call_id : void 0,
|
|
2686
|
+
phase
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
function extractTextFromMessage(message) {
|
|
2690
|
+
if (!message || typeof message !== "object") return "";
|
|
2691
|
+
const content = message.content;
|
|
2692
|
+
return extractTextFromContent2(content);
|
|
2693
|
+
}
|
|
2694
|
+
function extractAssistantDelta2(ev) {
|
|
2695
|
+
if (ev.type !== "assistant" && ev.message?.role !== "assistant") return null;
|
|
2696
|
+
if (typeof ev.text === "string") return ev.text;
|
|
2697
|
+
if (typeof ev.delta === "string") return ev.delta;
|
|
2698
|
+
if (ev.message) {
|
|
2699
|
+
const text = extractTextFromMessage(ev.message);
|
|
2700
|
+
return text || null;
|
|
2701
|
+
}
|
|
2702
|
+
if (ev.content) {
|
|
2703
|
+
const text = extractTextFromContent2(ev.content);
|
|
2704
|
+
return text || null;
|
|
2705
|
+
}
|
|
2706
|
+
return null;
|
|
2707
|
+
}
|
|
2708
|
+
function extractResultText2(ev) {
|
|
2709
|
+
if (typeof ev.result === "string") return ev.result;
|
|
2710
|
+
if (ev.result && typeof ev.result === "object") {
|
|
2711
|
+
const result = ev.result;
|
|
2712
|
+
if (typeof result.text === "string") return result.text;
|
|
2713
|
+
if (result.message) {
|
|
2714
|
+
const text = extractTextFromMessage(result.message);
|
|
2715
|
+
if (text) return text;
|
|
2716
|
+
}
|
|
2717
|
+
if (result.content) {
|
|
2718
|
+
const text = extractTextFromContent2(result.content);
|
|
2719
|
+
if (text) return text;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (ev.message) {
|
|
2723
|
+
const text = extractTextFromMessage(ev.message);
|
|
2724
|
+
if (text) return text;
|
|
2725
|
+
}
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
function isErrorEvent(ev) {
|
|
2729
|
+
if (ev.type === "error") return true;
|
|
2730
|
+
if (ev.type === "result" && ev.subtype) {
|
|
2731
|
+
const subtype = String(ev.subtype).toLowerCase();
|
|
2732
|
+
if (subtype.includes("error") || subtype.includes("failed")) return true;
|
|
2733
|
+
}
|
|
2734
|
+
return false;
|
|
2735
|
+
}
|
|
2736
|
+
function extractErrorMessage(ev) {
|
|
2737
|
+
if (typeof ev.error === "string") return ev.error;
|
|
2738
|
+
if (ev.error && typeof ev.error === "object" && typeof ev.error.message === "string") {
|
|
2739
|
+
return ev.error.message;
|
|
2740
|
+
}
|
|
2741
|
+
if (typeof ev.text === "string" && ev.type === "error") return ev.text;
|
|
2742
|
+
if (typeof ev.result === "string" && ev.type === "result") return ev.result;
|
|
2743
|
+
return null;
|
|
2744
|
+
}
|
|
2745
|
+
function normalizeCursorEvent(raw) {
|
|
2746
|
+
if (!raw || typeof raw !== "object") return { type: "unknown" };
|
|
2747
|
+
const type = typeof raw.type === "string" ? raw.type : "unknown";
|
|
2748
|
+
return { ...raw, type };
|
|
2749
|
+
}
|
|
2750
|
+
function runCursorPrompt({
|
|
2751
|
+
prompt,
|
|
2752
|
+
resumeSessionId,
|
|
2753
|
+
model,
|
|
2754
|
+
repoRoot,
|
|
2755
|
+
cwd,
|
|
2756
|
+
providerDetailLevel,
|
|
2757
|
+
onEvent,
|
|
2758
|
+
signal
|
|
2759
|
+
}) {
|
|
2760
|
+
return new Promise((resolve) => {
|
|
2761
|
+
const command2 = getCursorCommand();
|
|
2762
|
+
const resolvedRepoRoot = repoRoot ? path4.resolve(repoRoot) : null;
|
|
2763
|
+
const resolvedCwd = cwd ? path4.resolve(cwd) : null;
|
|
2764
|
+
const runDir = resolvedCwd || resolvedRepoRoot || process.cwd();
|
|
2765
|
+
const args2 = ["--print", "--output-format", "stream-json"];
|
|
2766
|
+
if (resumeSessionId) {
|
|
2767
|
+
args2.push("--resume", resumeSessionId);
|
|
2768
|
+
}
|
|
2769
|
+
const fallbackModel = getCursorDefaultModel();
|
|
2770
|
+
const resolvedModel = resolveCursorModel(model, fallbackModel);
|
|
2771
|
+
if (resolvedModel) {
|
|
2772
|
+
args2.push("--model", resolvedModel);
|
|
2773
|
+
}
|
|
2774
|
+
const endpoint = resolveCursorEndpoint();
|
|
2775
|
+
if (endpoint) {
|
|
2776
|
+
args2.push("--endpoint", endpoint);
|
|
2777
|
+
}
|
|
2778
|
+
args2.push(prompt);
|
|
2779
|
+
const argsPreview = [...args2];
|
|
2780
|
+
if (argsPreview.length > 0) {
|
|
2781
|
+
argsPreview[argsPreview.length - 1] = "[prompt]";
|
|
2782
|
+
}
|
|
2783
|
+
debugLog("Cursor", "spawn", {
|
|
2784
|
+
command: command2,
|
|
2785
|
+
args: argsPreview,
|
|
2786
|
+
cwd: runDir,
|
|
2787
|
+
model: resolvedModel || null,
|
|
2788
|
+
endpoint: endpoint || null,
|
|
2789
|
+
resume: resumeSessionId || null,
|
|
2790
|
+
apiKeyConfigured: Boolean(getCursorApiKey().trim()),
|
|
2791
|
+
promptChars: prompt.length
|
|
2792
|
+
});
|
|
2793
|
+
const child = spawn4(command2, args2, {
|
|
2794
|
+
cwd: runDir,
|
|
2795
|
+
env: buildCursorEnv(),
|
|
2796
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2797
|
+
});
|
|
2798
|
+
if (signal) {
|
|
2799
|
+
signal.addEventListener("abort", () => {
|
|
2800
|
+
child.kill("SIGTERM");
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
let aggregated = "";
|
|
2804
|
+
let finalSessionId = null;
|
|
2805
|
+
let didFinalize = false;
|
|
2806
|
+
let sawError = false;
|
|
2807
|
+
let sawJson = false;
|
|
2808
|
+
let rawOutput = "";
|
|
2809
|
+
const stdoutLines = [];
|
|
2810
|
+
const stderrLines = [];
|
|
2811
|
+
const includeRaw = providerDetailLevel === "raw";
|
|
2812
|
+
const buildProviderDetail = (eventType, data, raw) => {
|
|
2813
|
+
const detail = { eventType };
|
|
2814
|
+
if (data && Object.keys(data).length) detail.data = data;
|
|
2815
|
+
if (includeRaw && raw !== void 0) detail.raw = raw;
|
|
2816
|
+
return detail;
|
|
2817
|
+
};
|
|
2818
|
+
const emit = (event) => {
|
|
2819
|
+
if (finalSessionId) {
|
|
2820
|
+
onEvent({ ...event, providerSessionId: finalSessionId });
|
|
2821
|
+
} else {
|
|
2822
|
+
onEvent(event);
|
|
2823
|
+
}
|
|
2824
|
+
};
|
|
2825
|
+
const pushLine = (list, line) => {
|
|
2826
|
+
if (!line) return;
|
|
2827
|
+
list.push(line);
|
|
2828
|
+
if (list.length > 12) list.shift();
|
|
2829
|
+
};
|
|
2830
|
+
const emitError = (message, providerDetail) => {
|
|
2831
|
+
if (sawError) return;
|
|
2832
|
+
sawError = true;
|
|
2833
|
+
emit({ type: "error", message, providerDetail });
|
|
2834
|
+
};
|
|
2835
|
+
const emitFinal = (text) => {
|
|
2836
|
+
if (didFinalize) return;
|
|
2837
|
+
didFinalize = true;
|
|
2838
|
+
emit({ type: "final", text });
|
|
2839
|
+
};
|
|
2840
|
+
const handleEvent = (ev) => {
|
|
2841
|
+
const normalized = normalizeCursorEvent(ev);
|
|
2842
|
+
if (ev?.type === "system" && ev?.subtype === "init") {
|
|
2843
|
+
debugLog("Cursor", "init", {
|
|
2844
|
+
apiKeySource: ev.apiKeySource || null,
|
|
2845
|
+
cwd: ev.cwd || null,
|
|
2846
|
+
model: ev.model || null,
|
|
2847
|
+
permissionMode: ev.permissionMode || null,
|
|
2848
|
+
sessionId: ev.session_id ?? ev.sessionId ?? null
|
|
2849
|
+
});
|
|
2850
|
+
emit({
|
|
2851
|
+
type: "detail",
|
|
2852
|
+
provider: "cursor",
|
|
2853
|
+
providerDetail: buildProviderDetail(
|
|
2854
|
+
"system.init",
|
|
2855
|
+
{
|
|
2856
|
+
apiKeySource: ev.apiKeySource,
|
|
2857
|
+
cwd: ev.cwd,
|
|
2858
|
+
model: ev.model,
|
|
2859
|
+
permissionMode: ev.permissionMode
|
|
2860
|
+
},
|
|
2861
|
+
ev
|
|
2862
|
+
)
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
const sid = extractSessionId3(ev);
|
|
2866
|
+
if (sid) finalSessionId = sid;
|
|
2867
|
+
if (ev?.type === "thinking") {
|
|
2868
|
+
const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
|
|
2869
|
+
const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : "delta";
|
|
2870
|
+
emit({
|
|
2871
|
+
type: "thinking",
|
|
2872
|
+
provider: "cursor",
|
|
2873
|
+
phase,
|
|
2874
|
+
text: typeof ev.text === "string" ? ev.text : "",
|
|
2875
|
+
timestampMs: typeof ev.timestamp_ms === "number" ? ev.timestamp_ms : void 0,
|
|
2876
|
+
providerDetail: buildProviderDetail(
|
|
2877
|
+
subtype ? `thinking.${subtype}` : "thinking",
|
|
2878
|
+
{
|
|
2879
|
+
subtype: subtype || void 0
|
|
2880
|
+
},
|
|
2881
|
+
ev
|
|
2882
|
+
)
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
if (ev?.type === "assistant" || ev?.type === "user") {
|
|
2886
|
+
const role = ev.message?.role === "assistant" || ev.message?.role === "user" ? ev.message?.role : ev.type;
|
|
2887
|
+
const rawContent = ev.message?.content ?? ev.content;
|
|
2888
|
+
const content = extractTextFromContent2(rawContent);
|
|
2889
|
+
emit({
|
|
2890
|
+
type: "message",
|
|
2891
|
+
provider: "cursor",
|
|
2892
|
+
role,
|
|
2893
|
+
content,
|
|
2894
|
+
contentParts: rawContent ?? null,
|
|
2895
|
+
providerDetail: buildProviderDetail(ev.type, {}, ev)
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
const toolCall = extractToolCall(ev);
|
|
2899
|
+
if (toolCall) {
|
|
2900
|
+
emit({
|
|
2901
|
+
type: "tool_call",
|
|
2902
|
+
provider: "cursor",
|
|
2903
|
+
name: toolCall.name,
|
|
2904
|
+
callId: toolCall.callId,
|
|
2905
|
+
input: toolCall.input,
|
|
2906
|
+
output: toolCall.output,
|
|
2907
|
+
phase: toolCall.phase,
|
|
2908
|
+
providerDetail: buildProviderDetail(
|
|
2909
|
+
ev.subtype ? `tool_call.${ev.subtype}` : "tool_call",
|
|
2910
|
+
{
|
|
2911
|
+
name: toolCall.name,
|
|
2912
|
+
callId: toolCall.callId,
|
|
2913
|
+
subtype: ev.subtype
|
|
2914
|
+
},
|
|
2915
|
+
ev
|
|
2916
|
+
)
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
const usage = extractUsage2(ev);
|
|
2920
|
+
if (usage) {
|
|
2921
|
+
emit({
|
|
2922
|
+
type: "usage",
|
|
2923
|
+
inputTokens: usage.input_tokens,
|
|
2924
|
+
outputTokens: usage.output_tokens
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
if (isErrorEvent(ev)) {
|
|
2928
|
+
const message = extractErrorMessage(ev) || "Cursor run failed";
|
|
2929
|
+
emitError(message, buildProviderDetail(ev.subtype ? `error.${ev.subtype}` : "error", {}, ev));
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
const delta = extractAssistantDelta2(ev);
|
|
2933
|
+
if (delta) {
|
|
2934
|
+
aggregated += delta;
|
|
2935
|
+
emit({ type: "delta", text: delta, providerDetail: buildProviderDetail("delta", {}, ev) });
|
|
2936
|
+
}
|
|
2937
|
+
if (ev.type === "result") {
|
|
2938
|
+
const resultText = extractResultText2(ev);
|
|
2939
|
+
if (!aggregated && resultText) {
|
|
2940
|
+
aggregated = resultText;
|
|
2941
|
+
}
|
|
2942
|
+
if (!sawError) {
|
|
2943
|
+
emitFinal(aggregated || resultText || "");
|
|
2944
|
+
emit({
|
|
2945
|
+
type: "detail",
|
|
2946
|
+
provider: "cursor",
|
|
2947
|
+
providerDetail: buildProviderDetail(
|
|
2948
|
+
"result",
|
|
2949
|
+
{
|
|
2950
|
+
subtype: ev.subtype,
|
|
2951
|
+
duration_ms: typeof ev.duration_ms === "number" ? ev.duration_ms : void 0,
|
|
2952
|
+
request_id: typeof ev.request_id === "string" ? ev.request_id : void 0,
|
|
2953
|
+
is_error: typeof ev.is_error === "boolean" ? ev.is_error : void 0
|
|
2954
|
+
},
|
|
2955
|
+
ev
|
|
2956
|
+
)
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
const handleLine = (line, source) => {
|
|
2962
|
+
const trimmed = line.trim();
|
|
2963
|
+
if (!trimmed) return;
|
|
2964
|
+
const payload = trimmed.startsWith("data: ") ? trimmed.slice(6).trim() : trimmed;
|
|
2965
|
+
const parsed = safeJsonParse3(payload);
|
|
2966
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2967
|
+
emit({ type: "raw_line", line });
|
|
2968
|
+
if (source === "stdout") {
|
|
2969
|
+
rawOutput += `${line}
|
|
2970
|
+
`;
|
|
2971
|
+
pushLine(stdoutLines, line);
|
|
2972
|
+
} else {
|
|
2973
|
+
pushLine(stderrLines, line);
|
|
2974
|
+
}
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
sawJson = true;
|
|
2978
|
+
handleEvent(parsed);
|
|
2979
|
+
};
|
|
2980
|
+
const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
|
|
2981
|
+
const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
|
|
2982
|
+
child.stdout?.on("data", stdoutParser);
|
|
2983
|
+
child.stderr?.on("data", stderrParser);
|
|
2984
|
+
child.on("close", (code) => {
|
|
2985
|
+
if (!didFinalize) {
|
|
2986
|
+
if (code && code !== 0) {
|
|
2987
|
+
const hint = stderrLines.at(-1) || stdoutLines.at(-1) || "";
|
|
2988
|
+
const suffix = hint ? `: ${hint}` : "";
|
|
2989
|
+
debugLog("Cursor", "exit", { code, stderr: stderrLines, stdout: stdoutLines });
|
|
2990
|
+
emitError(`Cursor CLI exited with code ${code}${suffix}`);
|
|
2991
|
+
} else if (!sawError) {
|
|
2992
|
+
const fallback = !sawJson ? rawOutput.trim() : "";
|
|
2993
|
+
emitFinal(aggregated || fallback);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
resolve({ sessionId: finalSessionId });
|
|
2997
|
+
});
|
|
2998
|
+
child.on("error", (err) => {
|
|
2999
|
+
debugLog("Cursor", "spawn-error", { message: err?.message });
|
|
3000
|
+
emitError(err?.message ?? "Cursor failed to start");
|
|
3001
|
+
resolve({ sessionId: finalSessionId });
|
|
3002
|
+
});
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
// src/providers/local.ts
|
|
3007
|
+
function getLocalBaseUrl() {
|
|
3008
|
+
const base = process.env.AGENTCONNECT_LOCAL_BASE_URL || "http://localhost:11434/v1";
|
|
3009
|
+
return base.replace(/\/+$/, "");
|
|
3010
|
+
}
|
|
3011
|
+
function getLocalApiKey() {
|
|
3012
|
+
return process.env.AGENTCONNECT_LOCAL_API_KEY || "";
|
|
3013
|
+
}
|
|
3014
|
+
function resolveLocalModel(model, fallback) {
|
|
3015
|
+
if (!model) return fallback;
|
|
3016
|
+
const raw = String(model);
|
|
3017
|
+
if (raw === "local") return fallback;
|
|
3018
|
+
if (raw.startsWith("local:")) return raw.slice("local:".length);
|
|
3019
|
+
if (raw.startsWith("local/")) return raw.slice("local/".length);
|
|
3020
|
+
return raw;
|
|
3021
|
+
}
|
|
3022
|
+
async function fetchJson3(url, options = {}) {
|
|
3023
|
+
const controller = new AbortController();
|
|
3024
|
+
const timer = setTimeout(() => controller.abort(), 4e3);
|
|
3025
|
+
try {
|
|
3026
|
+
const res = await fetch(url, { ...options, signal: controller.signal });
|
|
3027
|
+
if (!res.ok) {
|
|
3028
|
+
return { ok: false, status: res.status, data: null };
|
|
3029
|
+
}
|
|
3030
|
+
const data = await res.json();
|
|
3031
|
+
return { ok: true, status: res.status, data };
|
|
3032
|
+
} catch {
|
|
3033
|
+
return { ok: false, status: 0, data: null };
|
|
3034
|
+
} finally {
|
|
3035
|
+
clearTimeout(timer);
|
|
1454
3036
|
}
|
|
1455
3037
|
}
|
|
1456
3038
|
async function ensureLocalInstalled() {
|
|
1457
3039
|
const base = getLocalBaseUrl();
|
|
1458
|
-
const res = await
|
|
3040
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1459
3041
|
return { installed: res.ok };
|
|
1460
3042
|
}
|
|
1461
3043
|
async function getLocalStatus() {
|
|
1462
3044
|
const base = getLocalBaseUrl();
|
|
1463
|
-
const res = await
|
|
3045
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1464
3046
|
if (!res.ok) return { installed: false, loggedIn: false };
|
|
1465
3047
|
return { installed: true, loggedIn: true };
|
|
1466
3048
|
}
|
|
3049
|
+
async function updateLocal() {
|
|
3050
|
+
return getLocalStatus();
|
|
3051
|
+
}
|
|
1467
3052
|
async function loginLocal(options = {}) {
|
|
1468
3053
|
if (typeof options.baseUrl === "string") {
|
|
1469
3054
|
process.env.AGENTCONNECT_LOCAL_BASE_URL = options.baseUrl;
|
|
@@ -1482,7 +3067,7 @@ async function loginLocal(options = {}) {
|
|
|
1482
3067
|
}
|
|
1483
3068
|
async function listLocalModels() {
|
|
1484
3069
|
const base = getLocalBaseUrl();
|
|
1485
|
-
const res = await
|
|
3070
|
+
const res = await fetchJson3(`${base}/models`);
|
|
1486
3071
|
if (!res.ok || !res.data || !Array.isArray(res.data.data)) return [];
|
|
1487
3072
|
return res.data.data.map((entry) => ({ id: entry.id, provider: "local", displayName: entry.id })).filter((entry) => entry.id);
|
|
1488
3073
|
}
|
|
@@ -1506,7 +3091,7 @@ async function runLocalPrompt({
|
|
|
1506
3091
|
const headers = { "Content-Type": "application/json" };
|
|
1507
3092
|
const apiKey = getLocalApiKey();
|
|
1508
3093
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
1509
|
-
const res = await
|
|
3094
|
+
const res = await fetchJson3(`${base}/chat/completions`, {
|
|
1510
3095
|
method: "POST",
|
|
1511
3096
|
headers,
|
|
1512
3097
|
body: JSON.stringify(payload)
|
|
@@ -1533,6 +3118,7 @@ var providers = {
|
|
|
1533
3118
|
name: "Claude",
|
|
1534
3119
|
ensureInstalled: ensureClaudeInstalled,
|
|
1535
3120
|
status: getClaudeStatus,
|
|
3121
|
+
update: updateClaude,
|
|
1536
3122
|
login: loginClaude,
|
|
1537
3123
|
logout: async () => {
|
|
1538
3124
|
},
|
|
@@ -1543,16 +3129,29 @@ var providers = {
|
|
|
1543
3129
|
name: "Codex",
|
|
1544
3130
|
ensureInstalled: ensureCodexInstalled,
|
|
1545
3131
|
status: getCodexStatus,
|
|
3132
|
+
update: updateCodex,
|
|
1546
3133
|
login: loginCodex,
|
|
1547
3134
|
logout: async () => {
|
|
1548
3135
|
},
|
|
1549
3136
|
runPrompt: runCodexPrompt
|
|
1550
3137
|
},
|
|
3138
|
+
cursor: {
|
|
3139
|
+
id: "cursor",
|
|
3140
|
+
name: "Cursor",
|
|
3141
|
+
ensureInstalled: ensureCursorInstalled,
|
|
3142
|
+
status: getCursorStatus,
|
|
3143
|
+
update: updateCursor,
|
|
3144
|
+
login: loginCursor,
|
|
3145
|
+
logout: async () => {
|
|
3146
|
+
},
|
|
3147
|
+
runPrompt: runCursorPrompt
|
|
3148
|
+
},
|
|
1551
3149
|
local: {
|
|
1552
3150
|
id: "local",
|
|
1553
3151
|
name: "Local",
|
|
1554
3152
|
ensureInstalled: ensureLocalInstalled,
|
|
1555
3153
|
status: getLocalStatus,
|
|
3154
|
+
update: updateLocal,
|
|
1556
3155
|
login: loginLocal,
|
|
1557
3156
|
logout: async () => {
|
|
1558
3157
|
},
|
|
@@ -1562,8 +3161,10 @@ var providers = {
|
|
|
1562
3161
|
async function listModels() {
|
|
1563
3162
|
const claudeModels = await listClaudeModels();
|
|
1564
3163
|
const codexModels = await listCodexModels();
|
|
3164
|
+
const cursorModels = await listCursorModels();
|
|
1565
3165
|
const base = [
|
|
1566
3166
|
...claudeModels,
|
|
3167
|
+
...cursorModels,
|
|
1567
3168
|
{ id: "local", provider: "local", displayName: "Local Model" }
|
|
1568
3169
|
];
|
|
1569
3170
|
const envModels = process.env.AGENTCONNECT_LOCAL_MODELS;
|
|
@@ -1597,6 +3198,7 @@ async function listRecentModels(providerId) {
|
|
|
1597
3198
|
}
|
|
1598
3199
|
function resolveProviderForModel(model) {
|
|
1599
3200
|
const lower = String(model || "").toLowerCase();
|
|
3201
|
+
if (lower.includes("cursor")) return "cursor";
|
|
1600
3202
|
if (lower.includes("codex")) return "codex";
|
|
1601
3203
|
if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4")) {
|
|
1602
3204
|
return "codex";
|
|
@@ -1610,15 +3212,15 @@ function resolveProviderForModel(model) {
|
|
|
1610
3212
|
|
|
1611
3213
|
// src/observed.ts
|
|
1612
3214
|
import fs from "fs";
|
|
1613
|
-
import
|
|
3215
|
+
import path5 from "path";
|
|
1614
3216
|
function createObservedTracker({
|
|
1615
3217
|
basePath,
|
|
1616
3218
|
appId,
|
|
1617
3219
|
requested = []
|
|
1618
3220
|
}) {
|
|
1619
3221
|
const requestedList = Array.isArray(requested) ? requested.filter(Boolean) : [];
|
|
1620
|
-
const dirPath =
|
|
1621
|
-
const filePath =
|
|
3222
|
+
const dirPath = path5.join(basePath, ".agentconnect");
|
|
3223
|
+
const filePath = path5.join(dirPath, "observed-capabilities.json");
|
|
1622
3224
|
const observed = /* @__PURE__ */ new Set();
|
|
1623
3225
|
let writeTimer = null;
|
|
1624
3226
|
function load() {
|
|
@@ -1691,6 +3293,15 @@ function replyError(socket, id, code, message) {
|
|
|
1691
3293
|
});
|
|
1692
3294
|
}
|
|
1693
3295
|
function sessionEvent(socket, sessionId, type, data) {
|
|
3296
|
+
if (process.env.AGENTCONNECT_DEBUG?.trim()) {
|
|
3297
|
+
try {
|
|
3298
|
+
console.log(
|
|
3299
|
+
`[AgentConnect][Session ${sessionId}] ${type} ${JSON.stringify(data)}`
|
|
3300
|
+
);
|
|
3301
|
+
} catch {
|
|
3302
|
+
console.log(`[AgentConnect][Session ${sessionId}] ${type}`);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
1694
3305
|
send(socket, {
|
|
1695
3306
|
jsonrpc: "2.0",
|
|
1696
3307
|
method: "acp.session.event",
|
|
@@ -1705,7 +3316,14 @@ function buildProviderList(statuses) {
|
|
|
1705
3316
|
name: provider.name,
|
|
1706
3317
|
installed: info.installed ?? false,
|
|
1707
3318
|
loggedIn: info.loggedIn ?? false,
|
|
1708
|
-
version: info.version
|
|
3319
|
+
version: info.version,
|
|
3320
|
+
updateAvailable: info.updateAvailable,
|
|
3321
|
+
latestVersion: info.latestVersion,
|
|
3322
|
+
updateCheckedAt: info.updateCheckedAt,
|
|
3323
|
+
updateSource: info.updateSource,
|
|
3324
|
+
updateCommand: info.updateCommand,
|
|
3325
|
+
updateMessage: info.updateMessage,
|
|
3326
|
+
updateInProgress: info.updateInProgress
|
|
1709
3327
|
};
|
|
1710
3328
|
});
|
|
1711
3329
|
}
|
|
@@ -1720,10 +3338,12 @@ function startDevHost({
|
|
|
1720
3338
|
const wss = new WebSocketServer({ server });
|
|
1721
3339
|
const sessions = /* @__PURE__ */ new Map();
|
|
1722
3340
|
const activeRuns = /* @__PURE__ */ new Map();
|
|
3341
|
+
const updatingProviders = /* @__PURE__ */ new Map();
|
|
1723
3342
|
const processTable = /* @__PURE__ */ new Map();
|
|
1724
3343
|
const backendState = /* @__PURE__ */ new Map();
|
|
1725
3344
|
const statusCache = /* @__PURE__ */ new Map();
|
|
1726
|
-
const statusCacheTtlMs =
|
|
3345
|
+
const statusCacheTtlMs = 8e3;
|
|
3346
|
+
const statusInFlight = /* @__PURE__ */ new Map();
|
|
1727
3347
|
const basePath = appPath || process.cwd();
|
|
1728
3348
|
const manifest = readManifest(basePath);
|
|
1729
3349
|
const appId = manifest?.id || "agentconnect-dev-app";
|
|
@@ -1736,7 +3356,7 @@ function startDevHost({
|
|
|
1736
3356
|
function resolveAppPathInternal(input) {
|
|
1737
3357
|
if (!input) return basePath;
|
|
1738
3358
|
const value = String(input);
|
|
1739
|
-
return
|
|
3359
|
+
return path6.isAbsolute(value) ? value : path6.resolve(basePath, value);
|
|
1740
3360
|
}
|
|
1741
3361
|
function mapFileType(stat) {
|
|
1742
3362
|
if (stat.isFile()) return "file";
|
|
@@ -1774,7 +3394,7 @@ function startDevHost({
|
|
|
1774
3394
|
}
|
|
1775
3395
|
function readManifest(root) {
|
|
1776
3396
|
try {
|
|
1777
|
-
const raw = fs2.readFileSync(
|
|
3397
|
+
const raw = fs2.readFileSync(path6.join(root, "agentconnect.app.json"), "utf8");
|
|
1778
3398
|
return JSON.parse(raw);
|
|
1779
3399
|
} catch {
|
|
1780
3400
|
return null;
|
|
@@ -1794,9 +3414,22 @@ function startDevHost({
|
|
|
1794
3414
|
if (cached && now - cached.at < statusCacheTtlMs) {
|
|
1795
3415
|
return cached.status;
|
|
1796
3416
|
}
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
3417
|
+
const existing = statusInFlight.get(provider.id);
|
|
3418
|
+
if (existing) return existing;
|
|
3419
|
+
const startedAt = Date.now();
|
|
3420
|
+
const promise = provider.status().then((status) => {
|
|
3421
|
+
debugLog("Providers", "status-check", {
|
|
3422
|
+
providerId: provider.id,
|
|
3423
|
+
durationMs: Date.now() - startedAt,
|
|
3424
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3425
|
+
});
|
|
3426
|
+
statusCache.set(provider.id, { status, at: Date.now() });
|
|
3427
|
+
return status;
|
|
3428
|
+
}).finally(() => {
|
|
3429
|
+
statusInFlight.delete(provider.id);
|
|
3430
|
+
});
|
|
3431
|
+
statusInFlight.set(provider.id, promise);
|
|
3432
|
+
return promise;
|
|
1800
3433
|
}
|
|
1801
3434
|
function invalidateStatus(providerId) {
|
|
1802
3435
|
if (!providerId) return;
|
|
@@ -1844,7 +3477,11 @@ function startDevHost({
|
|
|
1844
3477
|
})
|
|
1845
3478
|
);
|
|
1846
3479
|
const statuses = Object.fromEntries(statusEntries);
|
|
1847
|
-
|
|
3480
|
+
const list = buildProviderList(statuses).map((entry) => ({
|
|
3481
|
+
...entry,
|
|
3482
|
+
updateInProgress: updatingProviders.has(entry.id)
|
|
3483
|
+
}));
|
|
3484
|
+
reply(socket, id, { providers: list });
|
|
1848
3485
|
return;
|
|
1849
3486
|
}
|
|
1850
3487
|
if (method === "acp.providers.status") {
|
|
@@ -1861,11 +3498,66 @@ function startDevHost({
|
|
|
1861
3498
|
name: provider.name,
|
|
1862
3499
|
installed: status.installed,
|
|
1863
3500
|
loggedIn: status.loggedIn,
|
|
1864
|
-
version: status.version
|
|
3501
|
+
version: status.version,
|
|
3502
|
+
updateAvailable: status.updateAvailable,
|
|
3503
|
+
latestVersion: status.latestVersion,
|
|
3504
|
+
updateCheckedAt: status.updateCheckedAt,
|
|
3505
|
+
updateSource: status.updateSource,
|
|
3506
|
+
updateCommand: status.updateCommand,
|
|
3507
|
+
updateMessage: status.updateMessage,
|
|
3508
|
+
updateInProgress: updatingProviders.has(provider.id) || status.updateInProgress
|
|
1865
3509
|
}
|
|
1866
3510
|
});
|
|
1867
3511
|
return;
|
|
1868
3512
|
}
|
|
3513
|
+
if (method === "acp.providers.update") {
|
|
3514
|
+
const providerId = params.provider;
|
|
3515
|
+
const provider = providers[providerId];
|
|
3516
|
+
if (!provider) {
|
|
3517
|
+
replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
debugLog("Providers", "update-start", { providerId });
|
|
3521
|
+
if (!updatingProviders.has(providerId)) {
|
|
3522
|
+
const promise = provider.update().finally(() => {
|
|
3523
|
+
updatingProviders.delete(providerId);
|
|
3524
|
+
invalidateStatus(providerId);
|
|
3525
|
+
});
|
|
3526
|
+
updatingProviders.set(providerId, promise);
|
|
3527
|
+
}
|
|
3528
|
+
try {
|
|
3529
|
+
const status = await updatingProviders.get(providerId);
|
|
3530
|
+
debugLog("Providers", "update-complete", {
|
|
3531
|
+
providerId,
|
|
3532
|
+
updateAvailable: status.updateAvailable,
|
|
3533
|
+
latestVersion: status.latestVersion,
|
|
3534
|
+
updateMessage: status.updateMessage
|
|
3535
|
+
});
|
|
3536
|
+
reply(socket, id, {
|
|
3537
|
+
provider: {
|
|
3538
|
+
id: provider.id,
|
|
3539
|
+
name: provider.name,
|
|
3540
|
+
installed: status.installed,
|
|
3541
|
+
loggedIn: status.loggedIn,
|
|
3542
|
+
version: status.version,
|
|
3543
|
+
updateAvailable: status.updateAvailable,
|
|
3544
|
+
latestVersion: status.latestVersion,
|
|
3545
|
+
updateCheckedAt: status.updateCheckedAt,
|
|
3546
|
+
updateSource: status.updateSource,
|
|
3547
|
+
updateCommand: status.updateCommand,
|
|
3548
|
+
updateMessage: status.updateMessage,
|
|
3549
|
+
updateInProgress: false
|
|
3550
|
+
}
|
|
3551
|
+
});
|
|
3552
|
+
} catch (err) {
|
|
3553
|
+
debugLog("Providers", "update-error", {
|
|
3554
|
+
providerId,
|
|
3555
|
+
message: err instanceof Error ? err.message : String(err)
|
|
3556
|
+
});
|
|
3557
|
+
replyError(socket, id, "AC_ERR_INTERNAL", err?.message || "Update failed");
|
|
3558
|
+
}
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
1869
3561
|
if (method === "acp.providers.ensureInstalled") {
|
|
1870
3562
|
const providerId = params.provider;
|
|
1871
3563
|
const provider = providers[providerId];
|
|
@@ -1943,6 +3635,7 @@ function startDevHost({
|
|
|
1943
3635
|
const reasoningEffort = params.reasoningEffort || null;
|
|
1944
3636
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
|
|
1945
3637
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
|
|
3638
|
+
const providerDetailLevel = params.providerDetailLevel || void 0;
|
|
1946
3639
|
const providerId = resolveProviderForModel(model);
|
|
1947
3640
|
recordModelCapability(model);
|
|
1948
3641
|
sessions.set(sessionId, {
|
|
@@ -1952,7 +3645,8 @@ function startDevHost({
|
|
|
1952
3645
|
providerSessionId: null,
|
|
1953
3646
|
reasoningEffort,
|
|
1954
3647
|
cwd,
|
|
1955
|
-
repoRoot
|
|
3648
|
+
repoRoot,
|
|
3649
|
+
providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
|
|
1956
3650
|
});
|
|
1957
3651
|
reply(socket, id, { sessionId });
|
|
1958
3652
|
return;
|
|
@@ -1965,6 +3659,7 @@ function startDevHost({
|
|
|
1965
3659
|
const reasoningEffort = params.reasoningEffort || null;
|
|
1966
3660
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
|
|
1967
3661
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
|
|
3662
|
+
const providerDetailLevel = params.providerDetailLevel || void 0;
|
|
1968
3663
|
recordModelCapability(model);
|
|
1969
3664
|
sessions.set(sessionId, {
|
|
1970
3665
|
id: sessionId,
|
|
@@ -1973,7 +3668,8 @@ function startDevHost({
|
|
|
1973
3668
|
providerSessionId: params.providerSessionId || null,
|
|
1974
3669
|
reasoningEffort,
|
|
1975
3670
|
cwd,
|
|
1976
|
-
repoRoot
|
|
3671
|
+
repoRoot,
|
|
3672
|
+
providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
|
|
1977
3673
|
});
|
|
1978
3674
|
} else {
|
|
1979
3675
|
if (params.providerSessionId) {
|
|
@@ -1985,6 +3681,12 @@ function startDevHost({
|
|
|
1985
3681
|
if (params.repoRoot) {
|
|
1986
3682
|
existing.repoRoot = resolveAppPathInternal(params.repoRoot);
|
|
1987
3683
|
}
|
|
3684
|
+
if (params.providerDetailLevel) {
|
|
3685
|
+
const level = String(params.providerDetailLevel);
|
|
3686
|
+
if (level === "raw" || level === "minimal") {
|
|
3687
|
+
existing.providerDetailLevel = level;
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
1988
3690
|
recordModelCapability(existing.model);
|
|
1989
3691
|
}
|
|
1990
3692
|
reply(socket, id, { sessionId });
|
|
@@ -2004,6 +3706,10 @@ function startDevHost({
|
|
|
2004
3706
|
replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
|
|
2005
3707
|
return;
|
|
2006
3708
|
}
|
|
3709
|
+
if (updatingProviders.has(session.providerId)) {
|
|
3710
|
+
replyError(socket, id, "AC_ERR_BUSY", "Provider update in progress.");
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
2007
3713
|
const status = await provider.status();
|
|
2008
3714
|
if (!status.installed) {
|
|
2009
3715
|
const installed = await provider.ensureInstalled();
|
|
@@ -2015,6 +3721,7 @@ function startDevHost({
|
|
|
2015
3721
|
const controller = new AbortController();
|
|
2016
3722
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : session.cwd || basePath;
|
|
2017
3723
|
const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : session.repoRoot || basePath;
|
|
3724
|
+
const providerDetailLevel = params.providerDetailLevel === "raw" || params.providerDetailLevel === "minimal" ? params.providerDetailLevel : session.providerDetailLevel || "minimal";
|
|
2018
3725
|
activeRuns.set(sessionId, controller);
|
|
2019
3726
|
let sawError = false;
|
|
2020
3727
|
provider.runPrompt({
|
|
@@ -2024,6 +3731,7 @@ function startDevHost({
|
|
|
2024
3731
|
reasoningEffort: session.reasoningEffort,
|
|
2025
3732
|
repoRoot,
|
|
2026
3733
|
cwd,
|
|
3734
|
+
providerDetailLevel,
|
|
2027
3735
|
signal: controller.signal,
|
|
2028
3736
|
onEvent: (event) => {
|
|
2029
3737
|
if (event.type === "error") {
|
|
@@ -2089,7 +3797,7 @@ function startDevHost({
|
|
|
2089
3797
|
const filePath = resolveAppPathInternal(params.path);
|
|
2090
3798
|
const encoding = params.encoding || "utf8";
|
|
2091
3799
|
const content = params.content ?? "";
|
|
2092
|
-
await fsp.mkdir(
|
|
3800
|
+
await fsp.mkdir(path6.dirname(filePath), { recursive: true });
|
|
2093
3801
|
await fsp.writeFile(filePath, content, {
|
|
2094
3802
|
encoding,
|
|
2095
3803
|
mode: params.mode
|
|
@@ -2113,7 +3821,7 @@ function startDevHost({
|
|
|
2113
3821
|
const entries = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
2114
3822
|
const results = [];
|
|
2115
3823
|
for (const entry of entries) {
|
|
2116
|
-
const entryPath =
|
|
3824
|
+
const entryPath = path6.join(dirPath, entry.name);
|
|
2117
3825
|
let size = 0;
|
|
2118
3826
|
let type = "other";
|
|
2119
3827
|
try {
|
|
@@ -2169,7 +3877,7 @@ function startDevHost({
|
|
|
2169
3877
|
const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : basePath;
|
|
2170
3878
|
const env = { ...process.env, ...params.env || {} };
|
|
2171
3879
|
const useTty = Boolean(params.tty);
|
|
2172
|
-
const child =
|
|
3880
|
+
const child = spawn5(command2, args2, {
|
|
2173
3881
|
cwd,
|
|
2174
3882
|
env,
|
|
2175
3883
|
stdio: useTty ? "inherit" : ["pipe", "pipe", "pipe"]
|
|
@@ -2285,7 +3993,7 @@ function startDevHost({
|
|
|
2285
3993
|
}
|
|
2286
3994
|
const cwd = backendConfig.cwd ? resolveAppPathInternal(backendConfig.cwd) : basePath;
|
|
2287
3995
|
const args2 = backendConfig.args || [];
|
|
2288
|
-
const child =
|
|
3996
|
+
const child = spawn5(backendConfig.command, args2, {
|
|
2289
3997
|
cwd,
|
|
2290
3998
|
env,
|
|
2291
3999
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2364,7 +4072,7 @@ function startDevHost({
|
|
|
2364
4072
|
}
|
|
2365
4073
|
|
|
2366
4074
|
// src/paths.ts
|
|
2367
|
-
import
|
|
4075
|
+
import path7 from "path";
|
|
2368
4076
|
import { fileURLToPath } from "url";
|
|
2369
4077
|
import { promises as fs3 } from "fs";
|
|
2370
4078
|
async function exists(target) {
|
|
@@ -2376,20 +4084,20 @@ async function exists(target) {
|
|
|
2376
4084
|
}
|
|
2377
4085
|
}
|
|
2378
4086
|
function resolveAppPath(input) {
|
|
2379
|
-
const candidate = input ?
|
|
4087
|
+
const candidate = input ? path7.resolve(input) : process.cwd();
|
|
2380
4088
|
return candidate;
|
|
2381
4089
|
}
|
|
2382
4090
|
async function findSchemaDir() {
|
|
2383
4091
|
const envDir = process.env.AGENTCONNECT_SCHEMA_DIR;
|
|
2384
4092
|
if (envDir && await exists(envDir)) return envDir;
|
|
2385
|
-
const start =
|
|
4093
|
+
const start = path7.dirname(fileURLToPath(import.meta.url));
|
|
2386
4094
|
const roots = [start, process.cwd()];
|
|
2387
4095
|
for (const root of roots) {
|
|
2388
4096
|
let current = root;
|
|
2389
4097
|
for (let i = 0; i < 8; i += 1) {
|
|
2390
|
-
const candidate =
|
|
4098
|
+
const candidate = path7.join(current, "schemas");
|
|
2391
4099
|
if (await exists(candidate)) return candidate;
|
|
2392
|
-
const parent =
|
|
4100
|
+
const parent = path7.dirname(current);
|
|
2393
4101
|
if (parent === current) break;
|
|
2394
4102
|
current = parent;
|
|
2395
4103
|
}
|
|
@@ -2399,13 +4107,13 @@ async function findSchemaDir() {
|
|
|
2399
4107
|
|
|
2400
4108
|
// src/zip.ts
|
|
2401
4109
|
import fs5 from "fs";
|
|
2402
|
-
import
|
|
4110
|
+
import path9 from "path";
|
|
2403
4111
|
import { createHash } from "crypto";
|
|
2404
4112
|
import yazl from "yazl";
|
|
2405
4113
|
import yauzl from "yauzl";
|
|
2406
4114
|
|
|
2407
4115
|
// src/fs-utils.ts
|
|
2408
|
-
import
|
|
4116
|
+
import path8 from "path";
|
|
2409
4117
|
import { promises as fs4 } from "fs";
|
|
2410
4118
|
async function collectFiles(root, options = {}) {
|
|
2411
4119
|
const ignoreNames = new Set(options.ignoreNames || []);
|
|
@@ -2415,8 +4123,8 @@ async function collectFiles(root, options = {}) {
|
|
|
2415
4123
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2416
4124
|
for (const entry of entries) {
|
|
2417
4125
|
if (ignoreNames.has(entry.name)) continue;
|
|
2418
|
-
const fullPath =
|
|
2419
|
-
const rel =
|
|
4126
|
+
const fullPath = path8.join(dir, entry.name);
|
|
4127
|
+
const rel = path8.relative(root, fullPath);
|
|
2420
4128
|
if (ignorePaths.has(rel)) continue;
|
|
2421
4129
|
if (entry.isDirectory()) {
|
|
2422
4130
|
await walk(fullPath);
|
|
@@ -2433,7 +4141,7 @@ async function readJson(filePath) {
|
|
|
2433
4141
|
return JSON.parse(raw);
|
|
2434
4142
|
}
|
|
2435
4143
|
async function writeJson(filePath, data) {
|
|
2436
|
-
await fs4.mkdir(
|
|
4144
|
+
await fs4.mkdir(path8.dirname(filePath), { recursive: true });
|
|
2437
4145
|
await fs4.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
2438
4146
|
}
|
|
2439
4147
|
async function fileExists(filePath) {
|
|
@@ -2461,7 +4169,7 @@ async function zipDirectory({
|
|
|
2461
4169
|
ignoreNames = [],
|
|
2462
4170
|
ignorePaths = []
|
|
2463
4171
|
}) {
|
|
2464
|
-
await fs5.promises.mkdir(
|
|
4172
|
+
await fs5.promises.mkdir(path9.dirname(outputPath), { recursive: true });
|
|
2465
4173
|
const zipfile = new yazl.ZipFile();
|
|
2466
4174
|
const files = await collectFiles(inputDir, { ignoreNames, ignorePaths });
|
|
2467
4175
|
for (const file of files) {
|
|
@@ -2517,10 +4225,10 @@ async function readZipEntry(zipPath, entryName) {
|
|
|
2517
4225
|
}
|
|
2518
4226
|
|
|
2519
4227
|
// src/manifest.ts
|
|
2520
|
-
import
|
|
4228
|
+
import path10 from "path";
|
|
2521
4229
|
import Ajv from "ajv";
|
|
2522
4230
|
async function readManifestFromDir(appPath) {
|
|
2523
|
-
const manifestPath =
|
|
4231
|
+
const manifestPath = path10.join(appPath, "agentconnect.app.json");
|
|
2524
4232
|
if (!await fileExists(manifestPath)) {
|
|
2525
4233
|
throw new Error("agentconnect.app.json not found in app directory.");
|
|
2526
4234
|
}
|
|
@@ -2538,7 +4246,7 @@ async function loadManifestSchema() {
|
|
|
2538
4246
|
if (!schemaDir) {
|
|
2539
4247
|
throw new Error("Unable to locate schemas directory.");
|
|
2540
4248
|
}
|
|
2541
|
-
const schemaPath =
|
|
4249
|
+
const schemaPath = path10.join(schemaDir, "app-manifest.json");
|
|
2542
4250
|
return readJson(schemaPath);
|
|
2543
4251
|
}
|
|
2544
4252
|
async function validateManifest(manifest) {
|
|
@@ -2550,9 +4258,9 @@ async function validateManifest(manifest) {
|
|
|
2550
4258
|
}
|
|
2551
4259
|
|
|
2552
4260
|
// src/registry.ts
|
|
2553
|
-
import
|
|
4261
|
+
import path11 from "path";
|
|
2554
4262
|
import { promises as fs6 } from "fs";
|
|
2555
|
-
function
|
|
4263
|
+
function compareSemver4(a, b) {
|
|
2556
4264
|
const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
|
|
2557
4265
|
const pa = parse(a);
|
|
2558
4266
|
const pb = parse(b);
|
|
@@ -2574,26 +4282,26 @@ async function publishPackage({
|
|
|
2574
4282
|
if (!appId || !version) {
|
|
2575
4283
|
throw new Error("Manifest must include id and version.");
|
|
2576
4284
|
}
|
|
2577
|
-
const entryDir =
|
|
4285
|
+
const entryDir = path11.join(registryPath, "apps", appId, version);
|
|
2578
4286
|
await fs6.mkdir(entryDir, { recursive: true });
|
|
2579
|
-
const targetZip =
|
|
4287
|
+
const targetZip = path11.join(entryDir, "app.zip");
|
|
2580
4288
|
await fs6.copyFile(zipPath, targetZip);
|
|
2581
|
-
const manifestPath =
|
|
4289
|
+
const manifestPath = path11.join(entryDir, "manifest.json");
|
|
2582
4290
|
await writeJson(manifestPath, resolvedManifest);
|
|
2583
4291
|
let signatureOut = null;
|
|
2584
4292
|
if (signaturePath) {
|
|
2585
|
-
signatureOut =
|
|
4293
|
+
signatureOut = path11.join(entryDir, "signature.json");
|
|
2586
4294
|
await fs6.copyFile(signaturePath, signatureOut);
|
|
2587
4295
|
}
|
|
2588
4296
|
const hash = await hashFile(zipPath);
|
|
2589
|
-
const indexPath =
|
|
4297
|
+
const indexPath = path11.join(registryPath, "index.json");
|
|
2590
4298
|
const index = await fileExists(indexPath) ? await readJson(indexPath) : { apps: {} };
|
|
2591
4299
|
if (!index.apps) index.apps = {};
|
|
2592
4300
|
if (!index.apps[appId]) {
|
|
2593
4301
|
index.apps[appId] = { latest: version, versions: {} };
|
|
2594
4302
|
}
|
|
2595
4303
|
index.apps[appId].versions[version] = {
|
|
2596
|
-
path:
|
|
4304
|
+
path: path11.relative(registryPath, targetZip).replace(/\\/g, "/"),
|
|
2597
4305
|
manifest: resolvedManifest,
|
|
2598
4306
|
signature: signatureOut ? {
|
|
2599
4307
|
algorithm: "unknown",
|
|
@@ -2603,7 +4311,7 @@ async function publishPackage({
|
|
|
2603
4311
|
hash
|
|
2604
4312
|
};
|
|
2605
4313
|
const currentLatest = index.apps[appId].latest;
|
|
2606
|
-
if (!currentLatest ||
|
|
4314
|
+
if (!currentLatest || compareSemver4(version, currentLatest) > 0) {
|
|
2607
4315
|
index.apps[appId].latest = version;
|
|
2608
4316
|
}
|
|
2609
4317
|
await writeJson(indexPath, index);
|
|
@@ -2611,9 +4319,9 @@ async function publishPackage({
|
|
|
2611
4319
|
}
|
|
2612
4320
|
|
|
2613
4321
|
// src/registry-validate.ts
|
|
2614
|
-
import
|
|
4322
|
+
import path12 from "path";
|
|
2615
4323
|
import { createPublicKey, verify as verifySignature } from "crypto";
|
|
2616
|
-
function
|
|
4324
|
+
function compareSemver5(a, b) {
|
|
2617
4325
|
const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
|
|
2618
4326
|
const pa = parse(a);
|
|
2619
4327
|
const pb = parse(b);
|
|
@@ -2625,7 +4333,7 @@ function compareSemver2(a, b) {
|
|
|
2625
4333
|
}
|
|
2626
4334
|
function resolveEntry(registryPath, entryPath) {
|
|
2627
4335
|
if (!entryPath || typeof entryPath !== "string") return null;
|
|
2628
|
-
return
|
|
4336
|
+
return path12.resolve(registryPath, entryPath);
|
|
2629
4337
|
}
|
|
2630
4338
|
function normalizeSignatureAlg(signatureAlg) {
|
|
2631
4339
|
const value = String(signatureAlg || "").toLowerCase();
|
|
@@ -2666,7 +4374,7 @@ async function validateRegistry({
|
|
|
2666
4374
|
}) {
|
|
2667
4375
|
const errors = [];
|
|
2668
4376
|
const warnings = [];
|
|
2669
|
-
const indexPath =
|
|
4377
|
+
const indexPath = path12.join(registryPath, "index.json");
|
|
2670
4378
|
if (!await fileExists(indexPath)) {
|
|
2671
4379
|
return {
|
|
2672
4380
|
valid: false,
|
|
@@ -2721,9 +4429,9 @@ async function validateRegistry({
|
|
|
2721
4429
|
message: `App ${appId} latest (${latest}) not found in versions.`
|
|
2722
4430
|
});
|
|
2723
4431
|
}
|
|
2724
|
-
const sorted = [...versionKeys].sort(
|
|
4432
|
+
const sorted = [...versionKeys].sort(compareSemver5);
|
|
2725
4433
|
const expectedLatest = sorted[sorted.length - 1];
|
|
2726
|
-
if (latest && expectedLatest &&
|
|
4434
|
+
if (latest && expectedLatest && compareSemver5(latest, expectedLatest) !== 0) {
|
|
2727
4435
|
warnings.push({
|
|
2728
4436
|
path: `apps.${appId}`,
|
|
2729
4437
|
message: `App ${appId} latest (${latest}) is not the newest (${expectedLatest}).`
|
|
@@ -2882,7 +4590,7 @@ async function main() {
|
|
|
2882
4590
|
}
|
|
2883
4591
|
if (command === "pack") {
|
|
2884
4592
|
const appPath = resolveAppPath(getFlag("--app", "-a") ?? void 0);
|
|
2885
|
-
const outPath = getFlag("--out", "-o") ||
|
|
4593
|
+
const outPath = getFlag("--out", "-o") || path13.join(appPath, "dist", "app.zip");
|
|
2886
4594
|
const manifest = await readManifestFromDir(appPath);
|
|
2887
4595
|
const validation = await validateManifest(manifest);
|
|
2888
4596
|
if (!validation.valid) {
|
|
@@ -2892,7 +4600,7 @@ async function main() {
|
|
|
2892
4600
|
}
|
|
2893
4601
|
const ignoreNames = ["node_modules", ".git", ".DS_Store"];
|
|
2894
4602
|
const ignorePaths = [];
|
|
2895
|
-
const outputRel =
|
|
4603
|
+
const outputRel = path13.relative(appPath, outPath);
|
|
2896
4604
|
if (!outputRel.startsWith("..") && outputRel !== "") {
|
|
2897
4605
|
ignorePaths.push(outputRel);
|
|
2898
4606
|
}
|
|
@@ -2950,7 +4658,7 @@ async function main() {
|
|
|
2950
4658
|
console.error("Sign requires a zip file path.");
|
|
2951
4659
|
return 1;
|
|
2952
4660
|
}
|
|
2953
|
-
const outPath = getFlag("--out", "-o") ||
|
|
4661
|
+
const outPath = getFlag("--out", "-o") || path13.join(path13.dirname(appPath), "app.sig.json");
|
|
2954
4662
|
const manifest = await readManifestFromZip(appPath);
|
|
2955
4663
|
const hash = await hashFile(appPath);
|
|
2956
4664
|
const hashBuffer = Buffer.from(hash, "hex");
|
|
@@ -2974,7 +4682,7 @@ async function main() {
|
|
|
2974
4682
|
publicKey: publicKeyPem,
|
|
2975
4683
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2976
4684
|
};
|
|
2977
|
-
await fs7.mkdir(
|
|
4685
|
+
await fs7.mkdir(path13.dirname(outPath), { recursive: true });
|
|
2978
4686
|
await fs7.writeFile(outPath, JSON.stringify(signaturePayload, null, 2), "utf8");
|
|
2979
4687
|
console.log(`Signature written to ${outPath}`);
|
|
2980
4688
|
return 0;
|