@agentconnect/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/fs-utils.d.ts +9 -0
- package/dist/fs-utils.js +43 -0
- package/dist/host.d.ts +7 -0
- package/dist/host.js +663 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3020 -0
- package/dist/manifest.d.ts +10 -0
- package/dist/manifest.js +34 -0
- package/dist/observed.d.ts +7 -0
- package/dist/observed.js +69 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +36 -0
- package/dist/providers/claude.d.ts +10 -0
- package/dist/providers/claude.js +672 -0
- package/dist/providers/codex.d.ts +9 -0
- package/dist/providers/codex.js +509 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +90 -0
- package/dist/providers/local.d.ts +8 -0
- package/dist/providers/local.js +111 -0
- package/dist/providers/utils.d.ts +32 -0
- package/dist/providers/utils.js +256 -0
- package/dist/registry-validate.d.ts +6 -0
- package/dist/registry-validate.js +209 -0
- package/dist/registry.d.ts +17 -0
- package/dist/registry.js +66 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.js +1 -0
- package/dist/zip.d.ts +9 -0
- package/dist/zip.js +71 -0
- package/package.json +45 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { access, mkdir, readFile, rm, writeFile } from 'fs/promises';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { buildInstallCommand, buildInstallCommandAuto, buildLoginCommand, buildStatusCommand, checkCommandVersion, commandExists, createLineParser, resolveWindowsCommand, resolveCommandPath, runCommand, } from './utils.js';
|
|
6
|
+
const CLAUDE_PACKAGE = '@anthropic-ai/claude-code';
|
|
7
|
+
const INSTALL_UNIX = 'curl -fsSL https://claude.ai/install.sh | bash';
|
|
8
|
+
const INSTALL_WINDOWS_PS = 'irm https://claude.ai/install.ps1 | iex';
|
|
9
|
+
const INSTALL_WINDOWS_CMD = 'curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd';
|
|
10
|
+
const DEFAULT_LOGIN = '';
|
|
11
|
+
const DEFAULT_STATUS = '';
|
|
12
|
+
const CLAUDE_MODELS_CACHE_TTL_MS = 60_000;
|
|
13
|
+
const CLAUDE_RECENT_MODELS_CACHE_TTL_MS = 60_000;
|
|
14
|
+
let claudeModelsCache = null;
|
|
15
|
+
let claudeModelsCacheAt = 0;
|
|
16
|
+
let claudeRecentModelsCache = null;
|
|
17
|
+
let claudeRecentModelsCacheAt = 0;
|
|
18
|
+
const DEFAULT_CLAUDE_MODELS = [
|
|
19
|
+
{
|
|
20
|
+
id: 'default',
|
|
21
|
+
displayName: 'Default · Opus 4.5',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'sonnet',
|
|
25
|
+
displayName: 'Sonnet 4.5',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'haiku',
|
|
29
|
+
displayName: 'Haiku 4.5',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'opus',
|
|
33
|
+
displayName: 'Opus',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
export function getClaudeCommand() {
|
|
37
|
+
const override = process.env.AGENTCONNECT_CLAUDE_COMMAND;
|
|
38
|
+
const base = override || 'claude';
|
|
39
|
+
const resolved = resolveCommandPath(base);
|
|
40
|
+
return resolved || resolveWindowsCommand(base);
|
|
41
|
+
}
|
|
42
|
+
function getClaudeConfigDir() {
|
|
43
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
44
|
+
}
|
|
45
|
+
function formatClaudeDisplayName(modelId) {
|
|
46
|
+
const value = modelId.trim();
|
|
47
|
+
if (!value.startsWith('claude-'))
|
|
48
|
+
return value;
|
|
49
|
+
const parts = value.replace(/^claude-/, '').split('-').filter(Boolean);
|
|
50
|
+
if (!parts.length)
|
|
51
|
+
return value;
|
|
52
|
+
const family = parts[0];
|
|
53
|
+
const numeric = parts.slice(1).filter((entry) => /^\d+$/.test(entry));
|
|
54
|
+
let version = '';
|
|
55
|
+
if (numeric.length >= 2) {
|
|
56
|
+
version = `${numeric[0]}.${numeric[1]}`;
|
|
57
|
+
}
|
|
58
|
+
else if (numeric.length === 1) {
|
|
59
|
+
version = numeric[0];
|
|
60
|
+
}
|
|
61
|
+
const familyLabel = family.charAt(0).toUpperCase() + family.slice(1);
|
|
62
|
+
return `Claude ${familyLabel}${version ? ` ${version}` : ''}`;
|
|
63
|
+
}
|
|
64
|
+
async function readClaudeStatsModels() {
|
|
65
|
+
const statsPath = path.join(getClaudeConfigDir(), 'stats-cache.json');
|
|
66
|
+
try {
|
|
67
|
+
const raw = await readFile(statsPath, 'utf8');
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
const usage = parsed?.modelUsage;
|
|
70
|
+
if (!usage || typeof usage !== 'object')
|
|
71
|
+
return [];
|
|
72
|
+
return Object.keys(usage).filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function listClaudeModels() {
|
|
79
|
+
if (claudeModelsCache && Date.now() - claudeModelsCacheAt < CLAUDE_MODELS_CACHE_TTL_MS) {
|
|
80
|
+
return claudeModelsCache;
|
|
81
|
+
}
|
|
82
|
+
const list = DEFAULT_CLAUDE_MODELS.map((entry) => ({
|
|
83
|
+
id: entry.id,
|
|
84
|
+
provider: 'claude',
|
|
85
|
+
displayName: entry.displayName,
|
|
86
|
+
}));
|
|
87
|
+
claudeModelsCache = list;
|
|
88
|
+
claudeModelsCacheAt = Date.now();
|
|
89
|
+
return list;
|
|
90
|
+
}
|
|
91
|
+
export async function listClaudeRecentModels() {
|
|
92
|
+
if (claudeRecentModelsCache &&
|
|
93
|
+
Date.now() - claudeRecentModelsCacheAt < CLAUDE_RECENT_MODELS_CACHE_TTL_MS) {
|
|
94
|
+
return claudeRecentModelsCache;
|
|
95
|
+
}
|
|
96
|
+
const discovered = await readClaudeStatsModels();
|
|
97
|
+
const mapped = [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const modelId of discovered) {
|
|
100
|
+
const id = modelId.trim();
|
|
101
|
+
if (!id || seen.has(id))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(id);
|
|
104
|
+
mapped.push({
|
|
105
|
+
id,
|
|
106
|
+
provider: 'claude',
|
|
107
|
+
displayName: formatClaudeDisplayName(id),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
claudeRecentModelsCache = mapped;
|
|
111
|
+
claudeRecentModelsCacheAt = Date.now();
|
|
112
|
+
return mapped;
|
|
113
|
+
}
|
|
114
|
+
function getClaudeAuthPaths() {
|
|
115
|
+
const home = os.homedir();
|
|
116
|
+
return [
|
|
117
|
+
path.join(home, '.claude.json'),
|
|
118
|
+
path.join(home, '.claude', 'settings.json'),
|
|
119
|
+
path.join(home, '.config', 'claude', 'auth.json'),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
function resolveClaudeTheme() {
|
|
123
|
+
const raw = process.env.AGENTCONNECT_CLAUDE_THEME;
|
|
124
|
+
return raw && raw.trim() ? raw.trim() : 'dark';
|
|
125
|
+
}
|
|
126
|
+
function resolveClaudeLoginMethod(options) {
|
|
127
|
+
const raw = options?.loginMethod ?? process.env.AGENTCONNECT_CLAUDE_LOGIN_METHOD;
|
|
128
|
+
if (!raw)
|
|
129
|
+
return 'claudeai';
|
|
130
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
131
|
+
if (normalized === 'console')
|
|
132
|
+
return 'console';
|
|
133
|
+
if (normalized === 'claudeai' || normalized === 'claude')
|
|
134
|
+
return 'claudeai';
|
|
135
|
+
return 'claudeai';
|
|
136
|
+
}
|
|
137
|
+
function resolveClaudeLoginExperience(options) {
|
|
138
|
+
const raw = options?.loginExperience ??
|
|
139
|
+
process.env.AGENTCONNECT_CLAUDE_LOGIN_EXPERIENCE ??
|
|
140
|
+
process.env.AGENTCONNECT_LOGIN_EXPERIENCE;
|
|
141
|
+
if (raw) {
|
|
142
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
143
|
+
if (normalized === 'terminal' || normalized === 'manual')
|
|
144
|
+
return 'terminal';
|
|
145
|
+
if (normalized === 'embedded' || normalized === 'pty')
|
|
146
|
+
return 'embedded';
|
|
147
|
+
}
|
|
148
|
+
if (process.env.AGENTCONNECT_HOST_MODE === 'dev')
|
|
149
|
+
return 'terminal';
|
|
150
|
+
return 'embedded';
|
|
151
|
+
}
|
|
152
|
+
async function createClaudeLoginSettingsFile(loginMethod) {
|
|
153
|
+
if (!loginMethod)
|
|
154
|
+
return null;
|
|
155
|
+
const fileName = `agentconnect-claude-login-${Date.now()}-${Math.random()
|
|
156
|
+
.toString(36)
|
|
157
|
+
.slice(2, 8)}.json`;
|
|
158
|
+
const filePath = path.join(os.tmpdir(), fileName);
|
|
159
|
+
const theme = resolveClaudeTheme();
|
|
160
|
+
const payload = {
|
|
161
|
+
forceLoginMethod: loginMethod,
|
|
162
|
+
theme,
|
|
163
|
+
hasCompletedOnboarding: true,
|
|
164
|
+
};
|
|
165
|
+
await writeFile(filePath, JSON.stringify(payload), 'utf8');
|
|
166
|
+
return filePath;
|
|
167
|
+
}
|
|
168
|
+
async function ensureClaudeOnboardingSettings() {
|
|
169
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
170
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
171
|
+
let settings = {};
|
|
172
|
+
try {
|
|
173
|
+
const raw = await readFile(settingsPath, 'utf8');
|
|
174
|
+
try {
|
|
175
|
+
settings = JSON.parse(raw);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const code = err?.code;
|
|
183
|
+
if (code && code !== 'ENOENT')
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let updated = false;
|
|
187
|
+
if (!settings.theme) {
|
|
188
|
+
settings.theme = resolveClaudeTheme();
|
|
189
|
+
updated = true;
|
|
190
|
+
}
|
|
191
|
+
if (settings.hasCompletedOnboarding !== true) {
|
|
192
|
+
settings.hasCompletedOnboarding = true;
|
|
193
|
+
updated = true;
|
|
194
|
+
}
|
|
195
|
+
if (!updated)
|
|
196
|
+
return;
|
|
197
|
+
await mkdir(configDir, { recursive: true });
|
|
198
|
+
await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
|
|
199
|
+
}
|
|
200
|
+
async function loadPtyModule() {
|
|
201
|
+
try {
|
|
202
|
+
const mod = (await import('node-pty'));
|
|
203
|
+
if (typeof mod.spawn === 'function')
|
|
204
|
+
return mod;
|
|
205
|
+
if (mod.default && typeof mod.default.spawn === 'function')
|
|
206
|
+
return mod.default;
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function shellEscape(value) {
|
|
214
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
215
|
+
}
|
|
216
|
+
function cmdEscape(value) {
|
|
217
|
+
if (!value)
|
|
218
|
+
return '""';
|
|
219
|
+
const escaped = value.replace(/"/g, '""');
|
|
220
|
+
return `"${escaped}"`;
|
|
221
|
+
}
|
|
222
|
+
function buildClaudeCommand(command, args, includeLogin = false) {
|
|
223
|
+
const parts = includeLogin ? [command, ...args, '/login'] : [command, ...args];
|
|
224
|
+
return parts.map(shellEscape).join(' ');
|
|
225
|
+
}
|
|
226
|
+
function buildClaudeCmd(command, args, includeLogin = false) {
|
|
227
|
+
const parts = includeLogin ? [command, ...args, '/login'] : [command, ...args];
|
|
228
|
+
return parts.map(cmdEscape).join(' ');
|
|
229
|
+
}
|
|
230
|
+
async function openClaudeLoginTerminal(command, args, includeLogin = false) {
|
|
231
|
+
const message = 'AgentConnect: complete Claude login in this terminal. If login does not start automatically, run /login.';
|
|
232
|
+
if (process.platform === 'win32') {
|
|
233
|
+
const cmdLine = `echo ${message} & ${buildClaudeCmd(command, args, includeLogin)}`;
|
|
234
|
+
await runCommand('cmd', ['/c', 'start', '', 'cmd', '/k', cmdLine], { shell: true });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const loginCommand = buildClaudeCommand(command, args, includeLogin);
|
|
238
|
+
const shellCommand = `printf "%s\\n\\n" ${shellEscape(message)}; ${loginCommand}`;
|
|
239
|
+
if (process.platform === 'darwin') {
|
|
240
|
+
const script = `tell application "Terminal"
|
|
241
|
+
activate
|
|
242
|
+
do script "${shellCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"
|
|
243
|
+
end tell`;
|
|
244
|
+
await runCommand('osascript', ['-e', script]);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (commandExists('x-terminal-emulator')) {
|
|
248
|
+
await runCommand('x-terminal-emulator', ['-e', 'bash', '-lc', shellCommand]);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (commandExists('gnome-terminal')) {
|
|
252
|
+
await runCommand('gnome-terminal', ['--', 'bash', '-lc', shellCommand]);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (commandExists('konsole')) {
|
|
256
|
+
await runCommand('konsole', ['-e', 'bash', '-lc', shellCommand]);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (commandExists('xterm')) {
|
|
260
|
+
await runCommand('xterm', ['-e', 'bash', '-lc', shellCommand]);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
throw new Error('No terminal emulator found to launch Claude login.');
|
|
264
|
+
}
|
|
265
|
+
function maybeAdvanceClaudeOnboarding(output, loginMethod, write) {
|
|
266
|
+
const text = output.toLowerCase();
|
|
267
|
+
if (text.includes('select login method') || text.includes('claude account with subscription')) {
|
|
268
|
+
if (loginMethod === 'console') {
|
|
269
|
+
write('\x1b[B');
|
|
270
|
+
}
|
|
271
|
+
write('\r');
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (text.includes('choose the text style') || text.includes('text style that looks best')) {
|
|
275
|
+
write('\r');
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
if (text.includes('press enter') || text.includes('enter to confirm')) {
|
|
279
|
+
write('\r');
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
async function hasClaudeAuth() {
|
|
285
|
+
if (typeof process.env.CLAUDE_CODE_OAUTH_TOKEN === 'string') {
|
|
286
|
+
return process.env.CLAUDE_CODE_OAUTH_TOKEN.trim().length > 0;
|
|
287
|
+
}
|
|
288
|
+
for (const filePath of getClaudeAuthPaths()) {
|
|
289
|
+
try {
|
|
290
|
+
await access(filePath);
|
|
291
|
+
const raw = await readFile(filePath, 'utf8');
|
|
292
|
+
const parsed = JSON.parse(raw);
|
|
293
|
+
const auth = parsed?.claudeAiOauth;
|
|
294
|
+
if (typeof auth?.accessToken === 'string' && auth.accessToken.trim()) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
if (typeof parsed.primaryApiKey === 'string' && parsed.primaryApiKey.trim()) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (typeof parsed.accessToken === 'string' && parsed.accessToken.trim()) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (typeof parsed.token === 'string' && parsed.token.trim()) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
const oauthAccount = parsed.oauthAccount;
|
|
307
|
+
if (typeof oauthAccount?.emailAddress === 'string' && oauthAccount.emailAddress.trim()) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
if (typeof oauthAccount?.accountUuid === 'string' && oauthAccount.accountUuid.trim()) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
const oauth = parsed.oauth;
|
|
314
|
+
if (typeof oauth?.access_token === 'string' && oauth.access_token.trim()) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// try next path
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
async function checkClaudeCliStatus() {
|
|
325
|
+
const command = resolveWindowsCommand(getClaudeCommand());
|
|
326
|
+
const result = await runCommand(command, ['--print'], {
|
|
327
|
+
env: { ...process.env, CI: '1' },
|
|
328
|
+
input: '/status\n',
|
|
329
|
+
timeoutMs: 4000,
|
|
330
|
+
});
|
|
331
|
+
const output = `${result.stdout}\n${result.stderr}`.toLowerCase();
|
|
332
|
+
if (!output.trim()) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
if (output.includes('not logged in') ||
|
|
336
|
+
output.includes('not authenticated') ||
|
|
337
|
+
output.includes('please log in') ||
|
|
338
|
+
output.includes('please login') ||
|
|
339
|
+
output.includes('run /login') ||
|
|
340
|
+
output.includes('sign in') ||
|
|
341
|
+
output.includes('invalid api key')) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
if (output.includes('logged in') ||
|
|
345
|
+
output.includes('authenticated') ||
|
|
346
|
+
output.includes('signed in') ||
|
|
347
|
+
output.includes('account') ||
|
|
348
|
+
output.includes('@')) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
export async function ensureClaudeInstalled() {
|
|
354
|
+
const command = getClaudeCommand();
|
|
355
|
+
const versionCheck = await checkCommandVersion(command, [['--version'], ['-V']]);
|
|
356
|
+
if (versionCheck.ok) {
|
|
357
|
+
return { installed: true, version: versionCheck.version || undefined };
|
|
358
|
+
}
|
|
359
|
+
if (commandExists(command)) {
|
|
360
|
+
return { installed: true, version: undefined };
|
|
361
|
+
}
|
|
362
|
+
const override = buildInstallCommand('AGENTCONNECT_CLAUDE_INSTALL', '');
|
|
363
|
+
let install = override;
|
|
364
|
+
let packageManager = override.command ? 'unknown' : 'unknown';
|
|
365
|
+
if (!install.command) {
|
|
366
|
+
if (process.platform === 'win32') {
|
|
367
|
+
if (commandExists('powershell')) {
|
|
368
|
+
install = {
|
|
369
|
+
command: 'powershell',
|
|
370
|
+
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', INSTALL_WINDOWS_PS],
|
|
371
|
+
};
|
|
372
|
+
packageManager = 'script';
|
|
373
|
+
}
|
|
374
|
+
else if (commandExists('pwsh')) {
|
|
375
|
+
install = {
|
|
376
|
+
command: 'pwsh',
|
|
377
|
+
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', INSTALL_WINDOWS_PS],
|
|
378
|
+
};
|
|
379
|
+
packageManager = 'script';
|
|
380
|
+
}
|
|
381
|
+
else if (commandExists('cmd') && commandExists('curl')) {
|
|
382
|
+
install = { command: 'cmd', args: ['/c', INSTALL_WINDOWS_CMD] };
|
|
383
|
+
packageManager = 'script';
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (commandExists('bash') && commandExists('curl')) {
|
|
387
|
+
install = { command: 'bash', args: ['-lc', INSTALL_UNIX] };
|
|
388
|
+
packageManager = 'script';
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const auto = await buildInstallCommandAuto(CLAUDE_PACKAGE);
|
|
392
|
+
install = { command: auto.command, args: auto.args };
|
|
393
|
+
packageManager = auto.packageManager;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!install.command) {
|
|
397
|
+
return { installed: false, version: undefined, packageManager };
|
|
398
|
+
}
|
|
399
|
+
await runCommand(install.command, install.args, { shell: process.platform === 'win32' });
|
|
400
|
+
const after = await checkCommandVersion(command, [['--version'], ['-V']]);
|
|
401
|
+
return {
|
|
402
|
+
installed: after.ok,
|
|
403
|
+
version: after.version || undefined,
|
|
404
|
+
packageManager,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
export async function getClaudeStatus() {
|
|
408
|
+
const command = getClaudeCommand();
|
|
409
|
+
const versionCheck = await checkCommandVersion(command, [['--version'], ['-V']]);
|
|
410
|
+
const installed = versionCheck.ok || commandExists(command);
|
|
411
|
+
let loggedIn = false;
|
|
412
|
+
if (installed) {
|
|
413
|
+
const status = buildStatusCommand('AGENTCONNECT_CLAUDE_STATUS', DEFAULT_STATUS);
|
|
414
|
+
if (status.command) {
|
|
415
|
+
const statusCommand = resolveWindowsCommand(status.command);
|
|
416
|
+
const result = await runCommand(statusCommand, status.args);
|
|
417
|
+
loggedIn = result.code === 0;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const hasAuth = await hasClaudeAuth();
|
|
421
|
+
const cliStatus = await checkClaudeCliStatus();
|
|
422
|
+
if (cliStatus === false) {
|
|
423
|
+
loggedIn = false;
|
|
424
|
+
}
|
|
425
|
+
else if (cliStatus === true) {
|
|
426
|
+
loggedIn = true;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
loggedIn = hasAuth;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return { installed, loggedIn, version: versionCheck.version || undefined };
|
|
434
|
+
}
|
|
435
|
+
export async function loginClaude(options) {
|
|
436
|
+
const login = buildLoginCommand('AGENTCONNECT_CLAUDE_LOGIN', DEFAULT_LOGIN);
|
|
437
|
+
if (login.command) {
|
|
438
|
+
const command = resolveWindowsCommand(login.command);
|
|
439
|
+
await runCommand(command, login.args);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
await runClaudeLoginFlow(options);
|
|
443
|
+
}
|
|
444
|
+
const status = await getClaudeStatus();
|
|
445
|
+
return { loggedIn: status.loggedIn };
|
|
446
|
+
}
|
|
447
|
+
async function runClaudeLoginFlow(options) {
|
|
448
|
+
const command = resolveWindowsCommand(getClaudeCommand());
|
|
449
|
+
const loginMethod = resolveClaudeLoginMethod(options);
|
|
450
|
+
const loginExperience = resolveClaudeLoginExperience(options);
|
|
451
|
+
await ensureClaudeOnboardingSettings();
|
|
452
|
+
const settingsPath = await createClaudeLoginSettingsFile(loginMethod);
|
|
453
|
+
const loginTimeoutMs = Number(process.env.AGENTCONNECT_CLAUDE_LOGIN_TIMEOUT_MS || 180_000);
|
|
454
|
+
const loginArgs = settingsPath ? ['--settings', settingsPath] : [];
|
|
455
|
+
let ptyProcess = null;
|
|
456
|
+
let childExited = false;
|
|
457
|
+
const cleanup = async () => {
|
|
458
|
+
if (ptyProcess) {
|
|
459
|
+
try {
|
|
460
|
+
ptyProcess.kill();
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// ignore
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (settingsPath) {
|
|
467
|
+
try {
|
|
468
|
+
await rm(settingsPath, { force: true });
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// ignore
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
if (loginExperience === 'terminal') {
|
|
477
|
+
await openClaudeLoginTerminal(command, loginArgs, false);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const ptyModule = await loadPtyModule();
|
|
481
|
+
if (!ptyModule) {
|
|
482
|
+
throw new Error('Claude login requires node-pty. Reinstall AgentConnect or run `claude /login` manually.');
|
|
483
|
+
}
|
|
484
|
+
ptyProcess = ptyModule.spawn(command, [...loginArgs, '/login'], {
|
|
485
|
+
name: 'xterm-256color',
|
|
486
|
+
cols: 100,
|
|
487
|
+
rows: 30,
|
|
488
|
+
cwd: os.homedir(),
|
|
489
|
+
env: { ...process.env, TERM: 'xterm-256color' },
|
|
490
|
+
});
|
|
491
|
+
let outputBuffer = '';
|
|
492
|
+
ptyProcess.onData((data) => {
|
|
493
|
+
outputBuffer += data;
|
|
494
|
+
if (outputBuffer.length > 6000) {
|
|
495
|
+
outputBuffer = outputBuffer.slice(-3000);
|
|
496
|
+
}
|
|
497
|
+
const advanced = maybeAdvanceClaudeOnboarding(outputBuffer, loginMethod, (input) => {
|
|
498
|
+
ptyProcess?.write(input);
|
|
499
|
+
});
|
|
500
|
+
if (advanced)
|
|
501
|
+
outputBuffer = '';
|
|
502
|
+
});
|
|
503
|
+
ptyProcess.onExit(() => {
|
|
504
|
+
childExited = true;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const start = Date.now();
|
|
508
|
+
while (Date.now() - start < loginTimeoutMs) {
|
|
509
|
+
const authed = await hasClaudeAuth();
|
|
510
|
+
if (authed) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (childExited) {
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
517
|
+
}
|
|
518
|
+
throw new Error('Claude login timed out. Finish login in your browser or run `claude` manually to authenticate.');
|
|
519
|
+
}
|
|
520
|
+
finally {
|
|
521
|
+
await cleanup();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function safeJsonParse(line) {
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(line);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function mapClaudeModel(model) {
|
|
533
|
+
if (!model)
|
|
534
|
+
return null;
|
|
535
|
+
const value = String(model).toLowerCase();
|
|
536
|
+
if (value === 'default' || value === 'recommended')
|
|
537
|
+
return null;
|
|
538
|
+
if (value === 'claude-default' || value === 'claude-recommended')
|
|
539
|
+
return null;
|
|
540
|
+
if (value.includes('opus'))
|
|
541
|
+
return 'opus';
|
|
542
|
+
if (value.includes('sonnet'))
|
|
543
|
+
return 'sonnet';
|
|
544
|
+
if (value.includes('haiku'))
|
|
545
|
+
return 'haiku';
|
|
546
|
+
if (value.startsWith('claude-'))
|
|
547
|
+
return value.replace('claude-', '');
|
|
548
|
+
return model;
|
|
549
|
+
}
|
|
550
|
+
function extractSessionId(msg) {
|
|
551
|
+
const direct = msg.session_id ?? msg.sessionId;
|
|
552
|
+
if (typeof direct === 'string' && direct)
|
|
553
|
+
return direct;
|
|
554
|
+
const nested = msg.message?.session_id ?? msg.message?.sessionId;
|
|
555
|
+
return typeof nested === 'string' && nested ? nested : null;
|
|
556
|
+
}
|
|
557
|
+
function extractAssistantDelta(msg) {
|
|
558
|
+
const type = String(msg.type ?? '');
|
|
559
|
+
if (type === 'stream_event') {
|
|
560
|
+
const ev = msg.event ?? {};
|
|
561
|
+
if (ev.type === 'content_block_delta') {
|
|
562
|
+
const text = ev.delta?.text;
|
|
563
|
+
return typeof text === 'string' && text ? text : null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (type === 'content_block_delta') {
|
|
567
|
+
const text = msg.delta?.text;
|
|
568
|
+
return typeof text === 'string' && text ? text : null;
|
|
569
|
+
}
|
|
570
|
+
const text = msg.delta?.text;
|
|
571
|
+
return typeof text === 'string' && text ? text : null;
|
|
572
|
+
}
|
|
573
|
+
function extractAssistantText(msg) {
|
|
574
|
+
if (String(msg.type ?? '') !== 'assistant')
|
|
575
|
+
return null;
|
|
576
|
+
const content = msg.message?.content;
|
|
577
|
+
if (!Array.isArray(content))
|
|
578
|
+
return null;
|
|
579
|
+
const textBlock = content.find((c) => c && typeof c === 'object' && c.type === 'text');
|
|
580
|
+
const text = textBlock?.text;
|
|
581
|
+
return typeof text === 'string' && text ? text : null;
|
|
582
|
+
}
|
|
583
|
+
function extractResultText(msg) {
|
|
584
|
+
if (String(msg.type ?? '') !== 'result')
|
|
585
|
+
return null;
|
|
586
|
+
const text = msg.result;
|
|
587
|
+
return typeof text === 'string' && text ? text : null;
|
|
588
|
+
}
|
|
589
|
+
export function runClaudePrompt({ prompt, resumeSessionId, model, cwd, onEvent, signal, }) {
|
|
590
|
+
return new Promise((resolve) => {
|
|
591
|
+
const command = getClaudeCommand();
|
|
592
|
+
const args = [
|
|
593
|
+
'-p',
|
|
594
|
+
'--output-format=stream-json',
|
|
595
|
+
'--verbose',
|
|
596
|
+
'--permission-mode',
|
|
597
|
+
'bypassPermissions',
|
|
598
|
+
];
|
|
599
|
+
const modelValue = mapClaudeModel(model);
|
|
600
|
+
if (modelValue) {
|
|
601
|
+
args.push('--model', modelValue);
|
|
602
|
+
}
|
|
603
|
+
if (resumeSessionId)
|
|
604
|
+
args.push('--resume', resumeSessionId);
|
|
605
|
+
args.push(prompt);
|
|
606
|
+
const child = spawn(command, args, {
|
|
607
|
+
cwd,
|
|
608
|
+
env: { ...process.env },
|
|
609
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
610
|
+
});
|
|
611
|
+
if (signal) {
|
|
612
|
+
signal.addEventListener('abort', () => {
|
|
613
|
+
child.kill('SIGTERM');
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
let aggregated = '';
|
|
617
|
+
let finalSessionId = null;
|
|
618
|
+
let didFinalize = false;
|
|
619
|
+
let sawError = false;
|
|
620
|
+
const emitError = (message) => {
|
|
621
|
+
if (sawError)
|
|
622
|
+
return;
|
|
623
|
+
sawError = true;
|
|
624
|
+
onEvent({ type: 'error', message });
|
|
625
|
+
};
|
|
626
|
+
const handleLine = (line) => {
|
|
627
|
+
const parsed = safeJsonParse(line);
|
|
628
|
+
if (!parsed || typeof parsed !== 'object')
|
|
629
|
+
return;
|
|
630
|
+
const msg = parsed;
|
|
631
|
+
const sid = extractSessionId(msg);
|
|
632
|
+
if (sid)
|
|
633
|
+
finalSessionId = sid;
|
|
634
|
+
const delta = extractAssistantDelta(msg);
|
|
635
|
+
if (delta) {
|
|
636
|
+
aggregated += delta;
|
|
637
|
+
onEvent({ type: 'delta', text: delta });
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const assistant = extractAssistantText(msg);
|
|
641
|
+
if (assistant && !aggregated) {
|
|
642
|
+
aggregated = assistant;
|
|
643
|
+
onEvent({ type: 'delta', text: assistant });
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const result = extractResultText(msg);
|
|
647
|
+
if (result && !didFinalize && !sawError) {
|
|
648
|
+
didFinalize = true;
|
|
649
|
+
onEvent({ type: 'final', text: aggregated || result });
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const stdoutParser = createLineParser(handleLine);
|
|
653
|
+
const stderrParser = createLineParser(handleLine);
|
|
654
|
+
child.stdout?.on('data', stdoutParser);
|
|
655
|
+
child.stderr?.on('data', stderrParser);
|
|
656
|
+
child.on('close', (code) => {
|
|
657
|
+
if (!didFinalize) {
|
|
658
|
+
if (code && code !== 0) {
|
|
659
|
+
emitError(`Claude exited with code ${code}`);
|
|
660
|
+
}
|
|
661
|
+
else if (!sawError) {
|
|
662
|
+
onEvent({ type: 'final', text: aggregated });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
resolve({ sessionId: finalSessionId });
|
|
666
|
+
});
|
|
667
|
+
child.on('error', (err) => {
|
|
668
|
+
emitError(err?.message ?? 'Claude failed to start');
|
|
669
|
+
resolve({ sessionId: finalSessionId });
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProviderStatus, ModelInfo, RunPromptOptions, RunPromptResult, InstallResult } from '../types.js';
|
|
2
|
+
export declare function getCodexCommand(): string;
|
|
3
|
+
export declare function ensureCodexInstalled(): Promise<InstallResult>;
|
|
4
|
+
export declare function getCodexStatus(): Promise<ProviderStatus>;
|
|
5
|
+
export declare function loginCodex(): Promise<{
|
|
6
|
+
loggedIn: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function listCodexModels(): Promise<ModelInfo[]>;
|
|
9
|
+
export declare function runCodexPrompt({ prompt, resumeSessionId, model, reasoningEffort, repoRoot, cwd, onEvent, signal, }: RunPromptOptions): Promise<RunPromptResult>;
|