@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,260 @@
|
|
|
1
|
+
import { killCli, spawnCli } from "./proc.js";
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { existsSync, promises as fsPromises } from "fs";
|
|
4
|
+
import { resolve, relative, isAbsolute } from "path";
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
import { getBackend, isEditTool, } from "../../shared/backends/index.js";
|
|
8
|
+
import { mountCursorProjectMcp } from "../../shared/cursorProjectMcp.js";
|
|
9
|
+
import { opencodeSessionInDirectory } from "../../shared/opencodeSession.js";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
export { isEditTool };
|
|
12
|
+
// ── Concurrent child registry ────────────────────────────────────────
|
|
13
|
+
const activeChildren = new Map();
|
|
14
|
+
export function abortRun(runKey) {
|
|
15
|
+
const child = activeChildren.get(runKey);
|
|
16
|
+
if (child) {
|
|
17
|
+
killCli(child);
|
|
18
|
+
activeChildren.delete(runKey);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function abortAll() {
|
|
22
|
+
for (const [key, child] of activeChildren) {
|
|
23
|
+
killCli(child);
|
|
24
|
+
activeChildren.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** @deprecated Use abortRun(runKey) or abortAll(). */
|
|
28
|
+
export function abortAgent(runKey) {
|
|
29
|
+
if (runKey)
|
|
30
|
+
abortRun(runKey);
|
|
31
|
+
else
|
|
32
|
+
abortAll();
|
|
33
|
+
}
|
|
34
|
+
export function runAgent(repoPath, prompt, modelId, onEvent, sessionId, runKey = `run_${Date.now()}`, backendKind = "cursor", mode = "agent") {
|
|
35
|
+
return runAgentWithHandle(repoPath, prompt, modelId, onEvent, sessionId, runKey, backendKind, mode).promise;
|
|
36
|
+
}
|
|
37
|
+
export function runAgentWithHandle(repoPath, prompt, modelId, onEvent, sessionId, runKey = `run_${Date.now()}`, backendKind = "cursor", mode = "agent",
|
|
38
|
+
/** Claude Code / Codex / OpenCode: MCP servers attached for this run (Cursor reads its own mcp.json). */
|
|
39
|
+
mcpServers,
|
|
40
|
+
/** Extra env for this run only (team machines: workspace provider keys). Never logged or stored. */
|
|
41
|
+
runEnv) {
|
|
42
|
+
const kind = backendKind === "claude-code" ||
|
|
43
|
+
backendKind === "codex" ||
|
|
44
|
+
backendKind === "opencode"
|
|
45
|
+
? backendKind
|
|
46
|
+
: "cursor";
|
|
47
|
+
const backend = getBackend(kind);
|
|
48
|
+
const promise = new Promise((resolvePromise, reject) => {
|
|
49
|
+
const cursorMcp = kind === "cursor" && mcpServers && Object.keys(mcpServers).length
|
|
50
|
+
? mcpServers
|
|
51
|
+
: undefined;
|
|
52
|
+
const start = async () => {
|
|
53
|
+
// OpenCode resolves its project from $PWD, not the spawn cwd.
|
|
54
|
+
const baseEnv = { ...process.env, ...(runEnv || {}), PWD: repoPath };
|
|
55
|
+
let resumeId = sessionId || null;
|
|
56
|
+
if (kind === "opencode" &&
|
|
57
|
+
resumeId &&
|
|
58
|
+
!(await opencodeSessionInDirectory(repoPath, resumeId, baseEnv))) {
|
|
59
|
+
resumeId = null;
|
|
60
|
+
}
|
|
61
|
+
const buildOpts = {
|
|
62
|
+
prompt,
|
|
63
|
+
modelId,
|
|
64
|
+
sessionId: resumeId,
|
|
65
|
+
mode,
|
|
66
|
+
cwd: repoPath,
|
|
67
|
+
mcpServers,
|
|
68
|
+
};
|
|
69
|
+
const args = backend.buildArgs(buildOpts);
|
|
70
|
+
const backendEnv = backend.buildEnv?.(buildOpts);
|
|
71
|
+
const mounted = await mountCursorProjectMcp(repoPath, cursorMcp);
|
|
72
|
+
const child = spawnCli(backend.command, args, {
|
|
73
|
+
cwd: repoPath,
|
|
74
|
+
env: { ...baseEnv, ...(backendEnv || {}), ...mounted.env },
|
|
75
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
76
|
+
});
|
|
77
|
+
activeChildren.set(runKey, child);
|
|
78
|
+
let stderr = "";
|
|
79
|
+
let settled = false;
|
|
80
|
+
const ctx = {
|
|
81
|
+
assistantBuf: { value: "" },
|
|
82
|
+
thinkingBuf: { value: "" },
|
|
83
|
+
gotTerminalEvent: { value: false },
|
|
84
|
+
stderr: "",
|
|
85
|
+
};
|
|
86
|
+
let terminalFlushed = false;
|
|
87
|
+
const flushTerminal = () => {
|
|
88
|
+
const events = backend.flush?.(ctx) ?? [];
|
|
89
|
+
for (const event of events)
|
|
90
|
+
onEvent(event);
|
|
91
|
+
return events.some((event) => event.kind === "done");
|
|
92
|
+
};
|
|
93
|
+
const terminalTimer = setInterval(() => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
if (flushTerminal()) {
|
|
97
|
+
terminalFlushed = true;
|
|
98
|
+
child.kill();
|
|
99
|
+
}
|
|
100
|
+
}, 2_500);
|
|
101
|
+
terminalTimer.unref?.();
|
|
102
|
+
const finish = (err) => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
settled = true;
|
|
106
|
+
clearInterval(terminalTimer);
|
|
107
|
+
if (activeChildren.get(runKey) === child) {
|
|
108
|
+
activeChildren.delete(runKey);
|
|
109
|
+
}
|
|
110
|
+
void mounted.restore().finally(() => {
|
|
111
|
+
if (err)
|
|
112
|
+
reject(err);
|
|
113
|
+
else
|
|
114
|
+
resolvePromise();
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
child.stderr.on("data", (chunk) => {
|
|
118
|
+
stderr += chunk.toString();
|
|
119
|
+
ctx.stderr = stderr;
|
|
120
|
+
});
|
|
121
|
+
const rl = createInterface({ input: child.stdout });
|
|
122
|
+
rl.on("line", (line) => {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
if (!trimmed)
|
|
125
|
+
return;
|
|
126
|
+
let ev;
|
|
127
|
+
try {
|
|
128
|
+
ev = JSON.parse(trimmed);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const event of backend.parseLine(ev, ctx)) {
|
|
134
|
+
onEvent(event);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
child.on("error", (err) => {
|
|
138
|
+
const message = err.code === "ENOENT"
|
|
139
|
+
? `${backend.command} CLI not found — install it and ensure it is on PATH`
|
|
140
|
+
: err.message;
|
|
141
|
+
if (!ctx.gotTerminalEvent.value) {
|
|
142
|
+
onEvent({ kind: "error", message });
|
|
143
|
+
}
|
|
144
|
+
finish(new Error(message));
|
|
145
|
+
});
|
|
146
|
+
child.on("close", (code) => {
|
|
147
|
+
if (settled)
|
|
148
|
+
return;
|
|
149
|
+
terminalFlushed = flushTerminal() || terminalFlushed;
|
|
150
|
+
if (code === 0 || (terminalFlushed && ctx.gotTerminalEvent.value)) {
|
|
151
|
+
if (!ctx.gotTerminalEvent.value && ctx.assistantBuf.value) {
|
|
152
|
+
onEvent({ kind: "done", result: ctx.assistantBuf.value });
|
|
153
|
+
}
|
|
154
|
+
finish();
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const msg = stderr.trim() || `Agent exited with code ${code ?? "unknown"}`;
|
|
158
|
+
if (!ctx.gotTerminalEvent.value) {
|
|
159
|
+
onEvent({ kind: "error", message: msg });
|
|
160
|
+
}
|
|
161
|
+
finish(new Error(msg));
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
void start().catch((err) => {
|
|
166
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
runKey,
|
|
171
|
+
abort: () => abortRun(runKey),
|
|
172
|
+
promise,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ── File diff ────────────────────────────────────────────────────────
|
|
176
|
+
function resolveInRepo(repoPath, filePath) {
|
|
177
|
+
const abs = isAbsolute(filePath) ? filePath : resolve(repoPath, filePath);
|
|
178
|
+
const rel = relative(repoPath, abs);
|
|
179
|
+
if (rel.startsWith("..") || rel === "")
|
|
180
|
+
return null;
|
|
181
|
+
return rel.replace(/\\/g, "/");
|
|
182
|
+
}
|
|
183
|
+
async function runGit(repoPath, args) {
|
|
184
|
+
try {
|
|
185
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
186
|
+
cwd: repoPath,
|
|
187
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
188
|
+
encoding: "utf8",
|
|
189
|
+
});
|
|
190
|
+
return stdout;
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
const e = err;
|
|
194
|
+
return e.stdout || "";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function isTracked(repoPath, rel) {
|
|
198
|
+
try {
|
|
199
|
+
await execFileAsync("git", ["ls-files", "--error-unmatch", "--", rel], {
|
|
200
|
+
cwd: repoPath,
|
|
201
|
+
encoding: "utf8",
|
|
202
|
+
});
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export async function getFileDiff(repoPath, filePath) {
|
|
210
|
+
const rel = resolveInRepo(repoPath, filePath);
|
|
211
|
+
if (!rel)
|
|
212
|
+
return "";
|
|
213
|
+
const abs = resolve(repoPath, rel);
|
|
214
|
+
let patch = await runGit(repoPath, ["diff", "HEAD", "--", rel]);
|
|
215
|
+
if (!patch)
|
|
216
|
+
patch = await runGit(repoPath, ["diff", "--", rel]);
|
|
217
|
+
if (!patch)
|
|
218
|
+
patch = await runGit(repoPath, ["diff", "--cached", "--", rel]);
|
|
219
|
+
if (!patch && existsSync(abs) && !(await isTracked(repoPath, rel))) {
|
|
220
|
+
patch = await runGit(repoPath, [
|
|
221
|
+
"diff",
|
|
222
|
+
"--no-index",
|
|
223
|
+
"--",
|
|
224
|
+
"/dev/null",
|
|
225
|
+
rel,
|
|
226
|
+
]);
|
|
227
|
+
}
|
|
228
|
+
return patch.trim();
|
|
229
|
+
}
|
|
230
|
+
export async function revertFiles(repoPath, filePaths) {
|
|
231
|
+
const reverted = [];
|
|
232
|
+
const errors = [];
|
|
233
|
+
for (const rawPath of filePaths) {
|
|
234
|
+
if (!rawPath || !rawPath.trim())
|
|
235
|
+
continue;
|
|
236
|
+
const rel = resolveInRepo(repoPath, rawPath.trim());
|
|
237
|
+
if (!rel)
|
|
238
|
+
continue;
|
|
239
|
+
const abs = resolve(repoPath, rel);
|
|
240
|
+
try {
|
|
241
|
+
const tracked = await isTracked(repoPath, rel);
|
|
242
|
+
if (tracked) {
|
|
243
|
+
await runGit(repoPath, ["reset", "HEAD", "--", rel]);
|
|
244
|
+
await runGit(repoPath, ["checkout", "HEAD", "--", rel]);
|
|
245
|
+
reverted.push(rel);
|
|
246
|
+
}
|
|
247
|
+
else if (existsSync(abs)) {
|
|
248
|
+
await fsPromises.rm(abs, { force: true, recursive: true });
|
|
249
|
+
reverted.push(rel);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
reverted.push(rel);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
errors.push(`Failed to revert ${rel}: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return { reverted, errors };
|
|
260
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MachineAgentTool } from "../../shared/machines.js";
|
|
2
|
+
/**
|
|
3
|
+
* Which agent CLIs this machine can run. Shared by the CLI worker (reported
|
|
4
|
+
* to the server on connect) and the desktop app (Machines page).
|
|
5
|
+
*/
|
|
6
|
+
/** Built per call so a PATH merged after import (desktop login shell) is used. */
|
|
7
|
+
export declare function noColorEnv(): NodeJS.ProcessEnv;
|
|
8
|
+
/** Absolute path of `command` on the current PATH, like `which`. */
|
|
9
|
+
export declare function findOnPath(command: string): string | null;
|
|
10
|
+
export declare function detectAgentTools(): Promise<MachineAgentTool[]>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { delimiter, join } from "path";
|
|
4
|
+
import { execCli } from "./proc.js";
|
|
5
|
+
/**
|
|
6
|
+
* Which agent CLIs this machine can run. Shared by the CLI worker (reported
|
|
7
|
+
* to the server on connect) and the desktop app (Machines page).
|
|
8
|
+
*/
|
|
9
|
+
/** Built per call so a PATH merged after import (desktop login shell) is used. */
|
|
10
|
+
export function noColorEnv() {
|
|
11
|
+
return { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" };
|
|
12
|
+
}
|
|
13
|
+
const WINDOWS_EXTS = [".exe", ".cmd", ".bat", ""];
|
|
14
|
+
/** Absolute path of `command` on the current PATH, like `which`. */
|
|
15
|
+
export function findOnPath(command) {
|
|
16
|
+
const exts = process.platform === "win32" ? WINDOWS_EXTS : [""];
|
|
17
|
+
for (const dir of (process.env.PATH || "").split(delimiter)) {
|
|
18
|
+
if (!dir)
|
|
19
|
+
continue;
|
|
20
|
+
for (const ext of exts) {
|
|
21
|
+
const candidate = join(dir, command + ext);
|
|
22
|
+
try {
|
|
23
|
+
if (!statSync(candidate).isFile())
|
|
24
|
+
continue;
|
|
25
|
+
if (process.platform !== "win32")
|
|
26
|
+
accessSync(candidate, constants.X_OK);
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// not here
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function displayPath(path) {
|
|
37
|
+
const home = homedir();
|
|
38
|
+
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
39
|
+
}
|
|
40
|
+
async function versionOf(command, args) {
|
|
41
|
+
try {
|
|
42
|
+
const { stdout } = await execCli(command, args, { timeout: 15_000, env: noColorEnv() });
|
|
43
|
+
return stdout.trim().split("\n")[0] || "installed";
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err.code === "ENOENT")
|
|
47
|
+
return null;
|
|
48
|
+
// Installed but the version flag misbehaved — still usable.
|
|
49
|
+
return "installed";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const TOOLS = [
|
|
53
|
+
{ id: "cursor", label: "Cursor", command: "cursor", args: ["agent", "--version"] },
|
|
54
|
+
{ id: "claude", label: "Claude Code", command: "claude", args: ["--version"] },
|
|
55
|
+
{ id: "codex", label: "Codex", command: "codex", args: ["--version"] },
|
|
56
|
+
{ id: "opencode", label: "OpenCode", command: "opencode", args: ["--version"] },
|
|
57
|
+
];
|
|
58
|
+
export async function detectAgentTools() {
|
|
59
|
+
return Promise.all(TOOLS.map(async (tool) => {
|
|
60
|
+
const version = await versionOf(tool.command, tool.args);
|
|
61
|
+
const path = findOnPath(tool.command);
|
|
62
|
+
return {
|
|
63
|
+
id: tool.id,
|
|
64
|
+
label: tool.label,
|
|
65
|
+
installed: Boolean(version),
|
|
66
|
+
...(version ? { version } : {}),
|
|
67
|
+
...(path ? { path: displayPath(path) } : {}),
|
|
68
|
+
};
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Config } from "./config.js";
|
|
2
|
+
/** Pair CLI with a Clerk-authenticated web account via one-time code. */
|
|
3
|
+
export declare function loginWithPairingCode(serverUrl: string, code: string): Promise<Config>;
|
|
4
|
+
export declare function logout(): void;
|
|
5
|
+
export declare function getAuthHeaders(): Record<string, string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { saveConfig, clearConfig, loadConfig } from "./config.js";
|
|
2
|
+
/** Pair CLI with a Clerk-authenticated web account via one-time code. */
|
|
3
|
+
export async function loginWithPairingCode(serverUrl, code) {
|
|
4
|
+
const base = serverUrl.replace(/\/+$/, "");
|
|
5
|
+
const normalized = code.trim().toUpperCase().replace(/\s+/g, "");
|
|
6
|
+
const res = await fetch(`${base}/api/auth/pairing/claim`, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
body: JSON.stringify({ code: normalized }),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
const err = (await res.json().catch(() => ({})));
|
|
13
|
+
throw new Error(err.error || `Pairing failed (${res.status})`);
|
|
14
|
+
}
|
|
15
|
+
const data = (await res.json());
|
|
16
|
+
const config = {
|
|
17
|
+
serverUrl: base,
|
|
18
|
+
token: data.token,
|
|
19
|
+
email: data.user.email,
|
|
20
|
+
};
|
|
21
|
+
saveConfig(config);
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
export function logout() {
|
|
25
|
+
clearConfig();
|
|
26
|
+
}
|
|
27
|
+
export function getAuthHeaders() {
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
if (!config) {
|
|
30
|
+
throw new Error("Not logged in. Run `eigen login` first.");
|
|
31
|
+
}
|
|
32
|
+
return { Authorization: `Bearer ${config.token}` };
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
email: string;
|
|
5
|
+
}
|
|
6
|
+
/** Production API (override with STEER_SERVER_URL for local/dev). */
|
|
7
|
+
export declare const DEFAULT_SERVER_URL: string;
|
|
8
|
+
export declare function getConfigDir(): string;
|
|
9
|
+
export declare function loadConfig(): Config | null;
|
|
10
|
+
export declare function saveConfig(config: Config): void;
|
|
11
|
+
export declare function clearConfig(): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
/** Production API (override with STEER_SERVER_URL for local/dev). */
|
|
5
|
+
export const DEFAULT_SERVER_URL = (process.env.STEER_SERVER_URL ||
|
|
6
|
+
"https://cursor-multiplayer-agent.onrender.com").replace(/\/+$/, "");
|
|
7
|
+
const CONFIG_DIR_NAME = "steer";
|
|
8
|
+
const LEGACY_CONFIG_DIR_NAME = "shared-agent";
|
|
9
|
+
const CONFIG_FILE = "config.json";
|
|
10
|
+
export function getConfigDir() {
|
|
11
|
+
const dir = join(homedir(), ".config", CONFIG_DIR_NAME);
|
|
12
|
+
if (!existsSync(dir)) {
|
|
13
|
+
mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
export function loadConfig() {
|
|
18
|
+
const filePath = join(getConfigDir(), CONFIG_FILE);
|
|
19
|
+
const legacyPath = join(homedir(), ".config", LEGACY_CONFIG_DIR_NAME, CONFIG_FILE);
|
|
20
|
+
const path = existsSync(filePath)
|
|
21
|
+
? filePath
|
|
22
|
+
: existsSync(legacyPath)
|
|
23
|
+
? legacyPath
|
|
24
|
+
: null;
|
|
25
|
+
if (!path)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(path, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (!parsed.serverUrl || !parsed.token || !parsed.email)
|
|
31
|
+
return null;
|
|
32
|
+
// Migrate legacy config into ~/.config/steer/
|
|
33
|
+
if (path === legacyPath) {
|
|
34
|
+
saveConfig(parsed);
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function saveConfig(config) {
|
|
43
|
+
const filePath = join(getConfigDir(), CONFIG_FILE);
|
|
44
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
45
|
+
}
|
|
46
|
+
export function clearConfig() {
|
|
47
|
+
const filePath = join(getConfigDir(), CONFIG_FILE);
|
|
48
|
+
if (existsSync(filePath)) {
|
|
49
|
+
rmSync(filePath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getCursorAuth(): Promise<{
|
|
2
|
+
authenticated: boolean;
|
|
3
|
+
email?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Ensure the Cursor CLI is logged in before starting the worker.
|
|
8
|
+
* Prompts interactive `cursor agent login` if not authenticated.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureCursorAuth(): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { execCli, spawnCli } from "./proc.js";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
export async function getCursorAuth() {
|
|
5
|
+
try {
|
|
6
|
+
const { stdout } = await execCli("cursor", ["agent", "whoami", "--format", "json"], {
|
|
7
|
+
timeout: 30_000,
|
|
8
|
+
maxBuffer: 1024 * 1024,
|
|
9
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
10
|
+
});
|
|
11
|
+
const data = JSON.parse(stdout.trim());
|
|
12
|
+
if (data.isAuthenticated || data.status === "authenticated") {
|
|
13
|
+
return {
|
|
14
|
+
authenticated: true,
|
|
15
|
+
email: data.userInfo?.email,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return { authenticated: false };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const e = err;
|
|
22
|
+
// cursor sometimes prints JSON on stdout even with non-zero exit
|
|
23
|
+
if (e.stdout) {
|
|
24
|
+
try {
|
|
25
|
+
const data = JSON.parse(String(e.stdout).trim());
|
|
26
|
+
if (data.isAuthenticated || data.status === "authenticated") {
|
|
27
|
+
return {
|
|
28
|
+
authenticated: true,
|
|
29
|
+
email: data.userInfo?.email,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { authenticated: false };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// fall through
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (e.code === "ENOENT") {
|
|
39
|
+
return {
|
|
40
|
+
authenticated: false,
|
|
41
|
+
error: "`cursor` CLI not found. Install Cursor CLI first: https://cursor.com",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
authenticated: false,
|
|
46
|
+
error: e.stderr?.trim() || e.message || "Failed to check Cursor auth",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function runCursorLogin() {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const child = spawnCli("cursor", ["agent", "login"], {
|
|
53
|
+
stdio: "inherit",
|
|
54
|
+
env: process.env,
|
|
55
|
+
});
|
|
56
|
+
child.on("error", (err) => {
|
|
57
|
+
console.error(chalk.red(`Failed to start cursor login: ${err.message}`));
|
|
58
|
+
resolve(1);
|
|
59
|
+
});
|
|
60
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ensure the Cursor CLI is logged in before starting the worker.
|
|
65
|
+
* Prompts interactive `cursor agent login` if not authenticated.
|
|
66
|
+
*/
|
|
67
|
+
export async function ensureCursorAuth() {
|
|
68
|
+
const spinner = ora("Checking Cursor CLI login…").start();
|
|
69
|
+
let auth = await getCursorAuth();
|
|
70
|
+
if (auth.error && auth.error.includes("not found")) {
|
|
71
|
+
spinner.fail(auth.error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (auth.authenticated) {
|
|
75
|
+
spinner.succeed(`Cursor CLI logged in as ${chalk.cyan(auth.email || "unknown")}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
spinner.warn("Cursor CLI is not logged in");
|
|
79
|
+
console.log(chalk.yellow("\n Local agents need Cursor CLI auth. Opening login…\n"));
|
|
80
|
+
const code = await runCursorLogin();
|
|
81
|
+
if (code !== 0) {
|
|
82
|
+
console.error(chalk.red("\nCursor login failed or was cancelled. Run `cursor agent login` and try again."));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const verify = ora("Verifying Cursor login…").start();
|
|
86
|
+
auth = await getCursorAuth();
|
|
87
|
+
if (!auth.authenticated) {
|
|
88
|
+
verify.fail("Still not logged into Cursor. Run `cursor agent login` and try again.");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
verify.succeed(`Cursor CLI logged in as ${chalk.cyan(auth.email || "unknown")}`);
|
|
92
|
+
}
|