@geteigen/cli 0.0.0-stage → 3.0.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 +154 -2
- package/dist/agent.d.ts +32 -0
- package/dist/agent.js +246 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +33 -0
- package/dist/cli/src/agent.d.ts +24 -0
- package/dist/cli/src/agent.js +260 -0
- package/dist/cli/src/agentTools.d.ts +10 -0
- package/dist/cli/src/agentTools.js +70 -0
- package/dist/cli/src/auth.d.ts +5 -0
- package/dist/cli/src/auth.js +33 -0
- package/dist/cli/src/config.d.ts +11 -0
- package/dist/cli/src/config.js +51 -0
- package/dist/cli/src/cursorAuth.d.ts +10 -0
- package/dist/cli/src/cursorAuth.js +92 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +244 -0
- package/dist/cli/src/listModels.d.ts +14 -0
- package/dist/cli/src/listModels.js +196 -0
- package/dist/cli/src/listSessions.d.ts +9 -0
- package/dist/cli/src/listSessions.js +37 -0
- package/dist/cli/src/machineConfig.d.ts +20 -0
- package/dist/cli/src/machineConfig.js +66 -0
- package/dist/cli/src/pickFolder.d.ts +5 -0
- package/dist/cli/src/pickFolder.js +65 -0
- package/dist/cli/src/proc.d.ts +23 -0
- package/dist/cli/src/proc.js +77 -0
- package/dist/cli/src/service.d.ts +17 -0
- package/dist/cli/src/service.js +246 -0
- package/dist/cli/src/workRoots.d.ts +30 -0
- package/dist/cli/src/workRoots.js +221 -0
- package/dist/cli/src/worker.d.ts +8 -0
- package/dist/cli/src/worker.js +51 -0
- package/dist/cli/src/workerCore.d.ts +47 -0
- package/dist/cli/src/workerCore.js +702 -0
- package/dist/cli/src/workspace.d.ts +8 -0
- package/dist/cli/src/workspace.js +269 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +51 -0
- package/dist/cursorAuth.d.ts +10 -0
- package/dist/cursorAuth.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +108 -0
- package/dist/listModels.d.ts +6 -0
- package/dist/listModels.js +62 -0
- package/dist/listSessions.d.ts +9 -0
- package/dist/listSessions.js +37 -0
- package/dist/pickFolder.d.ts +5 -0
- package/dist/pickFolder.js +65 -0
- package/dist/shared/actionDrafts.d.ts +83 -0
- package/dist/shared/actionDrafts.js +169 -0
- package/dist/shared/agentArtifacts.d.ts +49 -0
- package/dist/shared/agentArtifacts.js +169 -0
- package/dist/shared/agentLabel.d.ts +18 -0
- package/dist/shared/agentLabel.js +22 -0
- package/dist/shared/agentMentions.d.ts +38 -0
- package/dist/shared/agentMentions.js +110 -0
- package/dist/shared/agentRepos.d.ts +28 -0
- package/dist/shared/agentRepos.js +60 -0
- package/dist/shared/agentRunStatus.d.ts +12 -0
- package/dist/shared/agentRunStatus.js +17 -0
- package/dist/shared/agentUsage.d.ts +24 -0
- package/dist/shared/agentUsage.js +139 -0
- package/dist/shared/approvals.d.ts +44 -0
- package/dist/shared/approvals.js +97 -0
- package/dist/shared/askEigen.d.ts +190 -0
- package/dist/shared/askEigen.js +76 -0
- package/dist/shared/attribution.d.ts +25 -0
- package/dist/shared/attribution.js +63 -0
- package/dist/shared/automations.d.ts +136 -0
- package/dist/shared/automations.js +546 -0
- package/dist/shared/backends/claudeCode.d.ts +42 -0
- package/dist/shared/backends/claudeCode.js +540 -0
- package/dist/shared/backends/codex.d.ts +15 -0
- package/dist/shared/backends/codex.js +287 -0
- package/dist/shared/backends/cursor.d.ts +102 -0
- package/dist/shared/backends/cursor.js +1197 -0
- package/dist/shared/backends/index.d.ts +18 -0
- package/dist/shared/backends/index.js +58 -0
- package/dist/shared/backends/opencode.d.ts +40 -0
- package/dist/shared/backends/opencode.js +397 -0
- package/dist/shared/backends/types.d.ts +171 -0
- package/dist/shared/backends/types.js +19 -0
- package/dist/shared/backends/usage.d.ts +8 -0
- package/dist/shared/backends/usage.js +71 -0
- package/dist/shared/billingPlans.d.ts +60 -0
- package/dist/shared/billingPlans.js +262 -0
- package/dist/shared/brain.d.ts +353 -0
- package/dist/shared/brain.js +487 -0
- package/dist/shared/clarifyingQuestions.d.ts +13 -0
- package/dist/shared/clarifyingQuestions.js +207 -0
- package/dist/shared/claudeModels.d.ts +10 -0
- package/dist/shared/claudeModels.js +52 -0
- package/dist/shared/cloudBackends.d.ts +21 -0
- package/dist/shared/cloudBackends.js +29 -0
- package/dist/shared/codexModels.d.ts +10 -0
- package/dist/shared/codexModels.js +28 -0
- package/dist/shared/contextWindows.d.ts +2 -0
- package/dist/shared/contextWindows.js +30 -0
- package/dist/shared/cursorMcpConfig.d.ts +14 -0
- package/dist/shared/cursorMcpConfig.js +43 -0
- package/dist/shared/cursorProjectMcp.d.ts +10 -0
- package/dist/shared/cursorProjectMcp.js +42 -0
- package/dist/shared/docs.d.ts +59 -0
- package/dist/shared/docs.js +87 -0
- package/dist/shared/events.d.ts +929 -0
- package/dist/shared/events.js +59 -0
- package/dist/shared/integrations.d.ts +266 -0
- package/dist/shared/integrations.js +449 -0
- package/dist/shared/issueTranscript.d.ts +64 -0
- package/dist/shared/issueTranscript.js +387 -0
- package/dist/shared/issues.d.ts +139 -0
- package/dist/shared/issues.js +217 -0
- package/dist/shared/machines.d.ts +115 -0
- package/dist/shared/machines.js +58 -0
- package/dist/shared/markdownMedia.d.ts +13 -0
- package/dist/shared/markdownMedia.js +41 -0
- package/dist/shared/notificationText.d.ts +6 -0
- package/dist/shared/notificationText.js +38 -0
- package/dist/shared/opencodeModels.d.ts +19 -0
- package/dist/shared/opencodeModels.js +62 -0
- package/dist/shared/opencodeSession.d.ts +7 -0
- package/dist/shared/opencodeSession.js +30 -0
- package/dist/shared/orchestration.d.ts +58 -0
- package/dist/shared/orchestration.js +12 -0
- package/dist/shared/orgs.d.ts +47 -0
- package/dist/shared/orgs.js +49 -0
- package/dist/shared/personalOrg.d.ts +5 -0
- package/dist/shared/personalOrg.js +12 -0
- package/dist/shared/plans.d.ts +3 -0
- package/dist/shared/plans.js +19 -0
- package/dist/shared/providerModels.d.ts +40 -0
- package/dist/shared/providerModels.js +503 -0
- package/dist/shared/roomAttention.d.ts +17 -0
- package/dist/shared/roomAttention.js +36 -0
- package/dist/shared/roomContext.d.ts +120 -0
- package/dist/shared/roomContext.js +133 -0
- package/dist/shared/roomPermissions.d.ts +56 -0
- package/dist/shared/roomPermissions.js +133 -0
- package/dist/shared/sessionSeats.d.ts +29 -0
- package/dist/shared/sessionSeats.js +57 -0
- package/dist/shared/slashCommands.d.ts +39 -0
- package/dist/shared/slashCommands.js +166 -0
- package/dist/shared/space.d.ts +33 -0
- package/dist/shared/space.js +54 -0
- package/dist/shared/spaceFeed.d.ts +78 -0
- package/dist/shared/spaceFeed.js +194 -0
- package/dist/shared/stringifyUnknown.d.ts +1 -0
- package/dist/shared/stringifyUnknown.js +1 -0
- package/dist/shared/swarm.d.ts +320 -0
- package/dist/shared/swarm.js +328 -0
- package/dist/shared/swarmVisual.d.ts +79 -0
- package/dist/shared/swarmVisual.js +212 -0
- package/dist/shared/terminalFilter.d.ts +13 -0
- package/dist/shared/terminalFilter.js +46 -0
- package/dist/shared/toolIntegration.d.ts +56 -0
- package/dist/shared/toolIntegration.js +180 -0
- package/dist/shared/typing.d.ts +9 -0
- package/dist/shared/typing.js +37 -0
- package/dist/shared/uploads.d.ts +31 -0
- package/dist/shared/uploads.js +65 -0
- package/dist/shared/voiceApprovals.d.ts +36 -0
- package/dist/shared/voiceApprovals.js +229 -0
- package/dist/shared/workspace.d.ts +47 -0
- package/dist/shared/workspace.js +40 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +266 -0
- package/package.json +51 -4
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { createInterface } from "readline";
|
|
6
|
+
import { loginWithPairingCode, logout } from "./auth.js";
|
|
7
|
+
import { DEFAULT_SERVER_URL, loadConfig } from "./config.js";
|
|
8
|
+
import { ensureCursorAuth } from "./cursorAuth.js";
|
|
9
|
+
import { startWorker } from "./worker.js";
|
|
10
|
+
import { clearMachineConfig, connectMachine, loadMachineConfig } from "./machineConfig.js";
|
|
11
|
+
import { followLogs, installService, readDaemonPid, serviceStatus, startDaemon, stopDaemon, uninstallService, } from "./service.js";
|
|
12
|
+
const program = new Command();
|
|
13
|
+
function prompt(question) {
|
|
14
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(question, (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer.trim());
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
program
|
|
23
|
+
.name("eigen")
|
|
24
|
+
.description("CLI worker for Eigen — run local Cursor, Claude Code, Codex, and OpenCode agents")
|
|
25
|
+
.version("1.1.0");
|
|
26
|
+
program
|
|
27
|
+
.command("login")
|
|
28
|
+
.description("Pair CLI with your Eigen account (Clerk via web pairing code)")
|
|
29
|
+
.action(async () => {
|
|
30
|
+
try {
|
|
31
|
+
console.log(chalk.gray("\n 1. Sign in on the web app\n 2. Open /cli-pair and generate a code\n 3. Enter that code below\n"));
|
|
32
|
+
const serverUrlRaw = await prompt(`Server URL [${DEFAULT_SERVER_URL}]: `);
|
|
33
|
+
const serverUrl = serverUrlRaw || DEFAULT_SERVER_URL;
|
|
34
|
+
const code = await prompt("Pairing code: ");
|
|
35
|
+
if (!code) {
|
|
36
|
+
console.error(chalk.red("Pairing code is required."));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const spinner = ora("Pairing…").start();
|
|
40
|
+
try {
|
|
41
|
+
const config = await loginWithPairingCode(serverUrl, code);
|
|
42
|
+
spinner.succeed(`Paired as ${chalk.cyan(config.email)}`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
spinner.fail(err.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command("logout")
|
|
55
|
+
.description("Clear stored credentials")
|
|
56
|
+
.action(() => {
|
|
57
|
+
logout();
|
|
58
|
+
console.log(chalk.green("Logged out."));
|
|
59
|
+
});
|
|
60
|
+
program
|
|
61
|
+
.command("status")
|
|
62
|
+
.description("Show connection status and logged-in user")
|
|
63
|
+
.action(async () => {
|
|
64
|
+
const config = loadConfig();
|
|
65
|
+
if (!config) {
|
|
66
|
+
console.log(chalk.yellow("Not logged in."));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.log(chalk.blue("Logged in as:"), chalk.cyan(config.email));
|
|
70
|
+
console.log(chalk.blue("Server:"), config.serverUrl);
|
|
71
|
+
const spinner = ora("Checking server…").start();
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch(`${config.serverUrl}/api/rooms`, {
|
|
74
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
spinner.fail(`Server returned ${res.status}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const rooms = (await res.json());
|
|
81
|
+
spinner.succeed(`Server online — ${rooms.length} room(s)`);
|
|
82
|
+
for (const room of rooms) {
|
|
83
|
+
console.log(chalk.gray(` • ${room.name || room.id}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
spinner.fail(`Cannot reach server: ${err.message}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
function collect(value, previous) {
|
|
91
|
+
return [...previous, value];
|
|
92
|
+
}
|
|
93
|
+
/** Roots from `--root` flags plus EIGEN_WORK_ROOT (comma/colon separated). */
|
|
94
|
+
function workRoots(flags) {
|
|
95
|
+
const fromEnv = (process.env.EIGEN_WORK_ROOT || "").split(/[,:]/).map((p) => p.trim()).filter(Boolean);
|
|
96
|
+
return [...flags, ...fromEnv];
|
|
97
|
+
}
|
|
98
|
+
program
|
|
99
|
+
.command("connect")
|
|
100
|
+
.description("Connect this computer or server with a setup token from Eigen → Machines")
|
|
101
|
+
.requiredOption("--token <token>", "Setup token (mt_…)")
|
|
102
|
+
.option("--server <url>", "API server URL", DEFAULT_SERVER_URL)
|
|
103
|
+
.option("--name <name>", "Name shown in Eigen (defaults to the token's name or the hostname)")
|
|
104
|
+
.action(async (opts) => {
|
|
105
|
+
const spinner = ora("Connecting this machine…").start();
|
|
106
|
+
try {
|
|
107
|
+
const machine = await connectMachine({ serverUrl: opts.server, token: opts.token, name: opts.name });
|
|
108
|
+
spinner.succeed(`Connected as ${chalk.cyan(machine.name)}`);
|
|
109
|
+
console.log(chalk.gray("\n Next: keep it running in the background with"));
|
|
110
|
+
console.log(` ${chalk.cyan("eigen service install")} ${chalk.gray("(starts at boot)")}`);
|
|
111
|
+
console.log(chalk.gray(" or run it in this terminal with"));
|
|
112
|
+
console.log(` ${chalk.cyan("eigen start")}\n`);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
spinner.fail(err.message);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
program
|
|
120
|
+
.command("disconnect")
|
|
121
|
+
.description("Forget this machine's credential (remove it in Eigen → Machines to revoke it)")
|
|
122
|
+
.action(() => {
|
|
123
|
+
console.log(clearMachineConfig() ? chalk.green("Machine credential removed.") : chalk.yellow("This machine isn't connected with a token."));
|
|
124
|
+
});
|
|
125
|
+
program
|
|
126
|
+
.command("start")
|
|
127
|
+
.description("Start the worker")
|
|
128
|
+
.option("--repo <path>", "Override repository path for all prompts")
|
|
129
|
+
.option("--root <dir>", "Folder agents may work in (repeatable)", collect, [])
|
|
130
|
+
.option("--daemon", "Run in the background (logs to ~/.config/steer/worker.log)")
|
|
131
|
+
.action(async (opts) => {
|
|
132
|
+
// Docker / scripted installs: trade the setup token on first start.
|
|
133
|
+
if (!loadMachineConfig() && process.env.EIGEN_MACHINE_TOKEN?.trim()) {
|
|
134
|
+
try {
|
|
135
|
+
const machine = await connectMachine({
|
|
136
|
+
serverUrl: DEFAULT_SERVER_URL,
|
|
137
|
+
token: process.env.EIGEN_MACHINE_TOKEN,
|
|
138
|
+
name: process.env.EIGEN_MACHINE_NAME,
|
|
139
|
+
});
|
|
140
|
+
console.log(chalk.green(`Connected as ${machine.name}`));
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.error(chalk.red(`Couldn't connect with EIGEN_MACHINE_TOKEN: ${err.message}`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const machine = loadMachineConfig();
|
|
148
|
+
const config = machine ? null : loadConfig();
|
|
149
|
+
if (!machine && !config) {
|
|
150
|
+
console.error(chalk.red("Not connected. Run `eigen login` (your computer) or `eigen connect --token …` (a server)."));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const roots = workRoots(opts.root);
|
|
154
|
+
if (opts.daemon) {
|
|
155
|
+
const args = [...(opts.repo ? ["--repo", opts.repo] : []), ...opts.root.flatMap((r) => ["--root", r])];
|
|
156
|
+
try {
|
|
157
|
+
const { pid, log } = startDaemon(args);
|
|
158
|
+
console.log(chalk.green(`Worker running in the background (pid ${pid}).`));
|
|
159
|
+
console.log(chalk.gray(` Logs: ${log}\n Stop: eigen stop`));
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error(chalk.red(err.message));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Signed-in laptops need Cursor's own login for local Cursor agents. Headless
|
|
168
|
+
// machines may only run Claude Code / Codex / OpenCode, and can't answer a browser login.
|
|
169
|
+
if (!machine && process.stdin.isTTY)
|
|
170
|
+
await ensureCursorAuth();
|
|
171
|
+
console.log(chalk.blue.bold("\n Eigen Worker\n"));
|
|
172
|
+
console.log(chalk.gray(` Server: ${machine?.serverUrl ?? config.serverUrl}`));
|
|
173
|
+
console.log(chalk.gray(machine ? ` Machine: ${machine.name}` : ` User: ${config.email}`));
|
|
174
|
+
if (roots.length)
|
|
175
|
+
console.log(chalk.gray(` Folders: ${roots.join(", ")}`));
|
|
176
|
+
if (opts.repo)
|
|
177
|
+
console.log(chalk.gray(` Repo: ${opts.repo}`));
|
|
178
|
+
console.log();
|
|
179
|
+
startWorker({ repo: opts.repo, roots });
|
|
180
|
+
});
|
|
181
|
+
program
|
|
182
|
+
.command("stop")
|
|
183
|
+
.description("Stop a worker started with --daemon")
|
|
184
|
+
.action(() => {
|
|
185
|
+
console.log(stopDaemon() ? chalk.green("Worker stopped.") : chalk.yellow("No background worker is running."));
|
|
186
|
+
});
|
|
187
|
+
const service = program.command("service").description("Run the worker as a background service (systemd / launchd)");
|
|
188
|
+
service
|
|
189
|
+
.command("install")
|
|
190
|
+
.description("Install and start the service (restarts on crash, starts at boot)")
|
|
191
|
+
.option("--root <dir>", "Folder agents may work in (repeatable)", collect, [])
|
|
192
|
+
.action((opts) => {
|
|
193
|
+
if (!loadMachineConfig() && !loadConfig()) {
|
|
194
|
+
console.error(chalk.red("Connect first: `eigen connect --token …` (or `eigen login`)."));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const notes = installService(opts.root.flatMap((r) => ["--root", r]));
|
|
199
|
+
console.log(chalk.green("✓ Eigen worker service installed and started"));
|
|
200
|
+
for (const note of notes)
|
|
201
|
+
console.log(chalk.gray(` ${note}`));
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
console.error(chalk.red(err.message));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
service
|
|
209
|
+
.command("uninstall")
|
|
210
|
+
.description("Stop and remove the service")
|
|
211
|
+
.action(() => {
|
|
212
|
+
try {
|
|
213
|
+
console.log(chalk.green(uninstallService()));
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
console.error(chalk.red(err.message));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
service
|
|
221
|
+
.command("status")
|
|
222
|
+
.description("Is the worker service running?")
|
|
223
|
+
.action(() => {
|
|
224
|
+
const machine = loadMachineConfig();
|
|
225
|
+
const status = serviceStatus();
|
|
226
|
+
const daemon = readDaemonPid();
|
|
227
|
+
console.log(chalk.blue("Machine:"), machine ? chalk.cyan(machine.name) : chalk.gray("(signed-in user, no machine token)"));
|
|
228
|
+
console.log(chalk.blue("Service:"), status.installed ? (status.running ? chalk.green(`running (${status.detail})`) : chalk.yellow(status.detail)) : chalk.gray("not installed"));
|
|
229
|
+
if (daemon)
|
|
230
|
+
console.log(chalk.blue("Background worker:"), chalk.green(`running (pid ${daemon})`));
|
|
231
|
+
});
|
|
232
|
+
service
|
|
233
|
+
.command("logs")
|
|
234
|
+
.description("Follow the worker's logs")
|
|
235
|
+
.action(() => {
|
|
236
|
+
try {
|
|
237
|
+
followLogs();
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.error(chalk.red(err.message));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
program.parse();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentBackendKind } from "../../shared/backends/types.js";
|
|
2
|
+
export interface ModelInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Models available to the logged-in Cursor CLI on this machine. */
|
|
8
|
+
export declare function listLocalModels(): Promise<ModelInfo[]>;
|
|
9
|
+
export declare function listLocalClaudeModels(): Promise<ModelInfo[]>;
|
|
10
|
+
export declare function listLocalCodexModels(): Promise<ModelInfo[]>;
|
|
11
|
+
/** Parse `opencode models` output (`provider/model`, model may contain `/`). */
|
|
12
|
+
export declare function parseOpencodeModels(stdout: string): ModelInfo[];
|
|
13
|
+
export declare function listLocalOpencodeModels(): Promise<ModelInfo[]>;
|
|
14
|
+
export declare function listLocalModelsForBackend(backend?: AgentBackendKind): Promise<ModelInfo[]>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { execCli } from "./proc.js";
|
|
2
|
+
import { CLAUDE_MODELS } from "../../shared/claudeModels.js";
|
|
3
|
+
import { CODEX_MODELS } from "../../shared/codexModels.js";
|
|
4
|
+
import { DEFAULT_OPENCODE_MODEL, OPENCODE_MODELS, } from "../../shared/opencodeModels.js";
|
|
5
|
+
import { fetchAnthropicModels, fetchOpenaiCodingModels, mergeModelLists, parseCodexDebugModels, } from "../../shared/providerModels.js";
|
|
6
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
7
|
+
const MODEL_LINE_RE = /^([a-zA-Z0-9][a-zA-Z0-9._-]*)\s+[-–—]\s+(.+)$/;
|
|
8
|
+
function stripAnsi(text) {
|
|
9
|
+
return text.replace(ANSI_RE, "").replace(/\r/g, "");
|
|
10
|
+
}
|
|
11
|
+
function parseModels(stdout) {
|
|
12
|
+
const models = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const raw of stripAnsi(stdout).split("\n")) {
|
|
15
|
+
const line = raw.trim();
|
|
16
|
+
if (!line || /^available models$/i.test(line))
|
|
17
|
+
continue;
|
|
18
|
+
const m = line.match(MODEL_LINE_RE);
|
|
19
|
+
if (!m)
|
|
20
|
+
continue;
|
|
21
|
+
const id = m[1];
|
|
22
|
+
if (seen.has(id))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(id);
|
|
25
|
+
models.push({ id, displayName: m[2].trim() });
|
|
26
|
+
}
|
|
27
|
+
return models;
|
|
28
|
+
}
|
|
29
|
+
async function runListModels() {
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execCli("cursor", ["agent", "--list-models"], {
|
|
32
|
+
timeout: 60_000,
|
|
33
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
NO_COLOR: "1",
|
|
37
|
+
FORCE_COLOR: "0",
|
|
38
|
+
TERM: "dumb",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return stdout || "";
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const e = err;
|
|
45
|
+
if (e.stdout && String(e.stdout).trim())
|
|
46
|
+
return String(e.stdout);
|
|
47
|
+
if (e.code === "ENOENT") {
|
|
48
|
+
throw new Error("cursor CLI not found. Install Cursor and ensure `cursor` is on PATH.");
|
|
49
|
+
}
|
|
50
|
+
throw new Error(e.stderr?.trim() ||
|
|
51
|
+
e.message ||
|
|
52
|
+
"Failed to run `cursor agent --list-models`");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Models available to the logged-in Cursor CLI on this machine. */
|
|
56
|
+
export async function listLocalModels() {
|
|
57
|
+
const stdout = await runListModels();
|
|
58
|
+
const models = parseModels(stdout);
|
|
59
|
+
if (models.length === 0) {
|
|
60
|
+
return [{ id: "auto", displayName: "Auto" }];
|
|
61
|
+
}
|
|
62
|
+
return models;
|
|
63
|
+
}
|
|
64
|
+
async function runCodexDebugModels() {
|
|
65
|
+
const attempts = [["debug", "models"], ["debug", "models", "--bundled"]];
|
|
66
|
+
let lastError = null;
|
|
67
|
+
for (const args of attempts) {
|
|
68
|
+
try {
|
|
69
|
+
const { stdout, stderr } = await execCli("codex", args, {
|
|
70
|
+
timeout: 30_000,
|
|
71
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
72
|
+
env: {
|
|
73
|
+
...process.env,
|
|
74
|
+
NO_COLOR: "1",
|
|
75
|
+
FORCE_COLOR: "0",
|
|
76
|
+
TERM: "dumb",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const text = `${stdout || ""}\n${stderr || ""}`;
|
|
80
|
+
if (text.trim())
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const e = err;
|
|
85
|
+
if (e.stdout && String(e.stdout).trim())
|
|
86
|
+
return String(e.stdout);
|
|
87
|
+
if (e.code === "ENOENT") {
|
|
88
|
+
throw new Error("codex CLI not found on PATH.");
|
|
89
|
+
}
|
|
90
|
+
lastError = new Error(e.stderr?.trim() || e.message || "Failed to run `codex debug models`");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastError || new Error("`codex debug models` returned no output");
|
|
94
|
+
}
|
|
95
|
+
export async function listLocalClaudeModels() {
|
|
96
|
+
const live = [];
|
|
97
|
+
const key = process.env.ANTHROPIC_API_KEY?.trim();
|
|
98
|
+
if (key) {
|
|
99
|
+
try {
|
|
100
|
+
live.push(...(await fetchAnthropicModels(key)));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
console.warn("[listModels] Anthropic catalog failed:", err instanceof Error ? err.message : err);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return mergeModelLists(CLAUDE_MODELS, live);
|
|
107
|
+
}
|
|
108
|
+
export async function listLocalCodexModels() {
|
|
109
|
+
const live = [];
|
|
110
|
+
try {
|
|
111
|
+
live.push(...parseCodexDebugModels(await runCodexDebugModels()));
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.warn("[listModels] `codex debug models` failed:", err instanceof Error ? err.message : err);
|
|
115
|
+
}
|
|
116
|
+
const key = process.env.OPENAI_API_KEY?.trim() || process.env.CODEX_API_KEY?.trim();
|
|
117
|
+
if (key) {
|
|
118
|
+
try {
|
|
119
|
+
live.push(...(await fetchOpenaiCodingModels(key)));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.warn("[listModels] OpenAI catalog failed:", err instanceof Error ? err.message : err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return mergeModelLists(CODEX_MODELS, live);
|
|
126
|
+
}
|
|
127
|
+
/** Parse `opencode models` output (`provider/model`, model may contain `/`). */
|
|
128
|
+
export function parseOpencodeModels(stdout) {
|
|
129
|
+
const models = [];
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
for (const raw of stripAnsi(stdout).split("\n")) {
|
|
132
|
+
const id = raw.trim();
|
|
133
|
+
const slash = id.indexOf("/");
|
|
134
|
+
if (slash <= 0 || slash === id.length - 1)
|
|
135
|
+
continue;
|
|
136
|
+
const provider = id.slice(0, slash);
|
|
137
|
+
const name = id.slice(slash + 1);
|
|
138
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(provider) || /\s/.test(name))
|
|
139
|
+
continue;
|
|
140
|
+
if (seen.has(id))
|
|
141
|
+
continue;
|
|
142
|
+
seen.add(id);
|
|
143
|
+
models.push({
|
|
144
|
+
id,
|
|
145
|
+
displayName: name,
|
|
146
|
+
description: provider[0].toUpperCase() + provider.slice(1),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return models;
|
|
150
|
+
}
|
|
151
|
+
export async function listLocalOpencodeModels() {
|
|
152
|
+
try {
|
|
153
|
+
const { stdout, stderr } = await execCli("opencode", ["models"], {
|
|
154
|
+
timeout: 60_000,
|
|
155
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
156
|
+
env: {
|
|
157
|
+
...process.env,
|
|
158
|
+
NO_COLOR: "1",
|
|
159
|
+
FORCE_COLOR: "0",
|
|
160
|
+
TERM: "dumb",
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const models = parseOpencodeModels(`${stdout || ""}\n${stderr || ""}`);
|
|
164
|
+
if (models.length > 0) {
|
|
165
|
+
return [{ id: "auto", displayName: "Auto", description: "CLI default" }, ...models];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
const e = err;
|
|
170
|
+
if (e.stdout) {
|
|
171
|
+
const models = parseOpencodeModels(String(e.stdout));
|
|
172
|
+
if (models.length > 0) {
|
|
173
|
+
return [{ id: "auto", displayName: "Auto", description: "CLI default" }, ...models];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (e.code !== "ENOENT") {
|
|
177
|
+
console.warn("[listModels] `opencode models` failed:", e.message || String(err));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const fallback = OPENCODE_MODELS.some((m) => m.id === DEFAULT_OPENCODE_MODEL)
|
|
181
|
+
? OPENCODE_MODELS
|
|
182
|
+
: [
|
|
183
|
+
...OPENCODE_MODELS,
|
|
184
|
+
{ id: DEFAULT_OPENCODE_MODEL, displayName: DEFAULT_OPENCODE_MODEL },
|
|
185
|
+
];
|
|
186
|
+
return fallback;
|
|
187
|
+
}
|
|
188
|
+
export async function listLocalModelsForBackend(backend = "cursor") {
|
|
189
|
+
if (backend === "claude-code")
|
|
190
|
+
return listLocalClaudeModels();
|
|
191
|
+
if (backend === "codex")
|
|
192
|
+
return listLocalCodexModels();
|
|
193
|
+
if (backend === "opencode")
|
|
194
|
+
return listLocalOpencodeModels();
|
|
195
|
+
return listLocalModels();
|
|
196
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CursorChatSession {
|
|
2
|
+
id: string;
|
|
3
|
+
createdAt: number;
|
|
4
|
+
updatedAt: number;
|
|
5
|
+
hasConversation: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** MD5 of resolved repo path — matches Cursor's ~/.cursor/chats layout. */
|
|
8
|
+
export declare function workspaceChatHash(repoPath: string): string;
|
|
9
|
+
export declare function listChatSessions(repoPath: string): CursorChatSession[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join, resolve } from "path";
|
|
5
|
+
/** MD5 of resolved repo path — matches Cursor's ~/.cursor/chats layout. */
|
|
6
|
+
export function workspaceChatHash(repoPath) {
|
|
7
|
+
return createHash("md5").update(resolve(repoPath)).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
export function listChatSessions(repoPath) {
|
|
10
|
+
const absRepo = resolve(repoPath);
|
|
11
|
+
const root = join(homedir(), ".cursor", "chats", workspaceChatHash(absRepo));
|
|
12
|
+
if (!existsSync(root))
|
|
13
|
+
return [];
|
|
14
|
+
const sessions = [];
|
|
15
|
+
for (const id of readdirSync(root, { withFileTypes: true })) {
|
|
16
|
+
if (!id.isDirectory())
|
|
17
|
+
continue;
|
|
18
|
+
const metaPath = join(root, id.name, "meta.json");
|
|
19
|
+
if (!existsSync(metaPath))
|
|
20
|
+
continue;
|
|
21
|
+
try {
|
|
22
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
23
|
+
if (meta.cwd && resolve(meta.cwd) !== absRepo)
|
|
24
|
+
continue;
|
|
25
|
+
sessions.push({
|
|
26
|
+
id: id.name,
|
|
27
|
+
createdAt: meta.createdAtMs ?? 0,
|
|
28
|
+
updatedAt: meta.updatedAtMs ?? meta.createdAtMs ?? 0,
|
|
29
|
+
hasConversation: Boolean(meta.hasConversation),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// skip malformed entries
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless machine credential (from `eigen connect --token mt_…`). Separate
|
|
3
|
+
* from the user login in config.json: it can only run agents on this machine
|
|
4
|
+
* and is revoked per machine from Eigen → Machines.
|
|
5
|
+
*/
|
|
6
|
+
export interface MachineConfig {
|
|
7
|
+
serverUrl: string;
|
|
8
|
+
machineId: string;
|
|
9
|
+
credential: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadMachineConfig(): MachineConfig | null;
|
|
13
|
+
export declare function saveMachineConfig(config: MachineConfig): void;
|
|
14
|
+
export declare function clearMachineConfig(): boolean;
|
|
15
|
+
/** Trade a setup token for this machine's credential and save it. */
|
|
16
|
+
export declare function connectMachine(input: {
|
|
17
|
+
serverUrl: string;
|
|
18
|
+
token: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
}): Promise<MachineConfig>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
|
+
import { arch, hostname, platform } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getConfigDir } from "./config.js";
|
|
5
|
+
const FILE = "machine.json";
|
|
6
|
+
function machinePath() {
|
|
7
|
+
return join(getConfigDir(), FILE);
|
|
8
|
+
}
|
|
9
|
+
export function loadMachineConfig() {
|
|
10
|
+
const path = machinePath();
|
|
11
|
+
if (!existsSync(path))
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
15
|
+
if (!parsed.serverUrl || !parsed.machineId || !parsed.credential)
|
|
16
|
+
return null;
|
|
17
|
+
return { serverUrl: parsed.serverUrl, machineId: parsed.machineId, credential: parsed.credential, name: parsed.name || "" };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function saveMachineConfig(config) {
|
|
24
|
+
const path = machinePath();
|
|
25
|
+
writeFileSync(path, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
26
|
+
try {
|
|
27
|
+
chmodSync(path, 0o600);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Windows: best effort
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function clearMachineConfig() {
|
|
34
|
+
const path = machinePath();
|
|
35
|
+
if (!existsSync(path))
|
|
36
|
+
return false;
|
|
37
|
+
rmSync(path);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/** Trade a setup token for this machine's credential and save it. */
|
|
41
|
+
export async function connectMachine(input) {
|
|
42
|
+
const serverUrl = input.serverUrl.replace(/\/+$/, "");
|
|
43
|
+
const res = await fetch(`${serverUrl}/api/machines/connect`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
token: input.token.trim(),
|
|
48
|
+
hostname: hostname(),
|
|
49
|
+
os: platform(),
|
|
50
|
+
arch: arch(),
|
|
51
|
+
name: input.name?.trim() || undefined,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
const data = (await res.json().catch(() => ({})));
|
|
55
|
+
if (!res.ok || !data.machineId || !data.credential) {
|
|
56
|
+
throw new Error(data.error || `Server returned ${res.status}`);
|
|
57
|
+
}
|
|
58
|
+
const config = {
|
|
59
|
+
serverUrl,
|
|
60
|
+
machineId: data.machineId,
|
|
61
|
+
credential: data.credential,
|
|
62
|
+
name: data.name || hostname(),
|
|
63
|
+
};
|
|
64
|
+
saveMachineConfig(config);
|
|
65
|
+
return config;
|
|
66
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { platform } from "os";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Open a native OS folder picker and return an absolute path.
|
|
7
|
+
* Returns null if the user cancels.
|
|
8
|
+
*/
|
|
9
|
+
export async function pickFolder() {
|
|
10
|
+
const os = platform();
|
|
11
|
+
try {
|
|
12
|
+
if (os === "darwin") {
|
|
13
|
+
const { stdout } = await execFileAsync("osascript", [
|
|
14
|
+
"-e",
|
|
15
|
+
'POSIX path of (choose folder with prompt "Select a repository folder")',
|
|
16
|
+
]);
|
|
17
|
+
const path = stdout.trim().replace(/\/$/, "");
|
|
18
|
+
return path || null;
|
|
19
|
+
}
|
|
20
|
+
if (os === "win32") {
|
|
21
|
+
const script = [
|
|
22
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
23
|
+
"$d = New-Object System.Windows.Forms.FolderBrowserDialog",
|
|
24
|
+
'$d.Description = "Select a repository folder"',
|
|
25
|
+
"$d.ShowNewFolderButton = $true",
|
|
26
|
+
"if ($d.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $d.SelectedPath }",
|
|
27
|
+
].join("; ");
|
|
28
|
+
const { stdout } = await execFileAsync("powershell", [
|
|
29
|
+
"-NoProfile",
|
|
30
|
+
"-Command",
|
|
31
|
+
script,
|
|
32
|
+
]);
|
|
33
|
+
const path = stdout.trim();
|
|
34
|
+
return path || null;
|
|
35
|
+
}
|
|
36
|
+
// Linux: prefer zenity, then kdialog
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execFileAsync("zenity", [
|
|
39
|
+
"--file-selection",
|
|
40
|
+
"--directory",
|
|
41
|
+
"--title=Select a repository folder",
|
|
42
|
+
]);
|
|
43
|
+
const path = stdout.trim();
|
|
44
|
+
return path || null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
const { stdout } = await execFileAsync("kdialog", [
|
|
48
|
+
"--getexistingdirectory",
|
|
49
|
+
process.env.HOME || "/",
|
|
50
|
+
"--title",
|
|
51
|
+
"Select a repository folder",
|
|
52
|
+
]);
|
|
53
|
+
const path = stdout.trim();
|
|
54
|
+
return path || null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const e = err;
|
|
59
|
+
// User cancel: osascript exit 1 / zenity exit 1
|
|
60
|
+
if (e.code === 1 || e.code === "1")
|
|
61
|
+
return null;
|
|
62
|
+
throw new Error(e.message ||
|
|
63
|
+
"Failed to open folder picker. On Linux install zenity or kdialog.");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ChildProcess, SpawnOptions } from "child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Spawn an agent CLI (`cursor`, `claude`, `codex`). On Windows those are
|
|
4
|
+
* npm `.cmd` shims, which plain `spawn` can't launch without a shell —
|
|
5
|
+
* cross-spawn resolves the shim and escapes arguments safely.
|
|
6
|
+
*/
|
|
7
|
+
export declare function spawnCli(command: string, args: string[], options: SpawnOptions): ChildProcess;
|
|
8
|
+
export interface ExecCliOptions {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
maxBuffer?: number;
|
|
13
|
+
}
|
|
14
|
+
/** execFile-style helper on top of spawnCli: resolves `{ stdout, stderr }`, rejects on non-zero exit. */
|
|
15
|
+
export declare function execCli(command: string, args: string[], options?: ExecCliOptions): Promise<{
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Stop a CLI run. On Windows the child is the `.cmd` shim's cmd.exe, so kill
|
|
21
|
+
* the whole tree or the real agent keeps running.
|
|
22
|
+
*/
|
|
23
|
+
export declare function killCli(child: ChildProcess): void;
|