@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,8 @@
|
|
|
1
|
+
import { type WorkspaceGitSnapshot, type WorkspacePathMatch, type WorkspaceRepoEntry, type WorkspaceRepoFile } from "../../shared/workspace.js";
|
|
2
|
+
export declare function getWorkspaceGit(repoPath: string): Promise<WorkspaceGitSnapshot>;
|
|
3
|
+
export declare function commitWorkspaceGit(repoPath: string, rawMessage: string): Promise<void>;
|
|
4
|
+
export declare function pushWorkspaceGit(repoPath: string): Promise<void>;
|
|
5
|
+
export declare function checkoutWorkspaceGit(repoPath: string, rawBranch: string): Promise<void>;
|
|
6
|
+
export declare function listWorkspaceDir(repoPath: string, rawPath: string): Promise<WorkspaceRepoEntry[]>;
|
|
7
|
+
export declare function readWorkspaceFile(repoPath: string, rawPath: string): Promise<WorkspaceRepoFile>;
|
|
8
|
+
export declare function searchWorkspaceFiles(repoPath: string, query: string): Promise<WorkspacePathMatch[]>;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from "fs";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
3
|
+
import { execCli } from "./proc.js";
|
|
4
|
+
import { assertCommitMessage, assertGitBranchName, parseGitBranches, } from "../../shared/workspace.js";
|
|
5
|
+
const MAX_GIT_DIFF_CHARS = 400_000;
|
|
6
|
+
const MAX_REPO_FILE_BYTES = 512 * 1024;
|
|
7
|
+
const MAX_REPO_IMAGE_BYTES = 3 * 1024 * 1024;
|
|
8
|
+
const MAX_SEARCH_PATHS = 50_000;
|
|
9
|
+
const EXCLUDED_DIRS = new Set([
|
|
10
|
+
"node_modules",
|
|
11
|
+
".git",
|
|
12
|
+
"dist",
|
|
13
|
+
"build",
|
|
14
|
+
".next",
|
|
15
|
+
"target",
|
|
16
|
+
"__pycache__",
|
|
17
|
+
".venv",
|
|
18
|
+
"venv",
|
|
19
|
+
"coverage",
|
|
20
|
+
".turbo",
|
|
21
|
+
".cache",
|
|
22
|
+
]);
|
|
23
|
+
const IMAGE_MIME = {
|
|
24
|
+
png: "image/png",
|
|
25
|
+
jpg: "image/jpeg",
|
|
26
|
+
jpeg: "image/jpeg",
|
|
27
|
+
gif: "image/gif",
|
|
28
|
+
webp: "image/webp",
|
|
29
|
+
svg: "image/svg+xml",
|
|
30
|
+
avif: "image/avif",
|
|
31
|
+
bmp: "image/bmp",
|
|
32
|
+
};
|
|
33
|
+
async function git(repoPath, args) {
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execCli("git", args, {
|
|
36
|
+
cwd: repoPath,
|
|
37
|
+
timeout: 30_000,
|
|
38
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
39
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
40
|
+
});
|
|
41
|
+
return stdout;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const e = err;
|
|
45
|
+
return e.stdout || "";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function resolveInside(repoPath, rawPath) {
|
|
49
|
+
const abs = isAbsolute(rawPath) ? rawPath : resolve(repoPath, rawPath || "");
|
|
50
|
+
const rel = relative(repoPath, abs);
|
|
51
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
52
|
+
throw new Error("Path is outside the repo");
|
|
53
|
+
}
|
|
54
|
+
return { abs, rel: rel.replace(/\\/g, "/") };
|
|
55
|
+
}
|
|
56
|
+
export async function getWorkspaceGit(repoPath) {
|
|
57
|
+
if (!repoPath || !existsSync(repoPath))
|
|
58
|
+
throw new Error("Repository folder not found");
|
|
59
|
+
const branchRaw = await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
60
|
+
const statusRaw = await git(repoPath, ["status", "--porcelain"]);
|
|
61
|
+
const logRaw = await git(repoPath, ["log", "-n", "25", "--pretty=format:%h%x09%s%x09%an%x09%cr"]);
|
|
62
|
+
const aheadRaw = (await git(repoPath, ["rev-list", "--count", "@{u}..HEAD"])).trim();
|
|
63
|
+
const branchList = await git(repoPath, ["branch", "-a", "--format=%(refname:short)"]);
|
|
64
|
+
const diffRaw = await git(repoPath, ["diff", "HEAD"]);
|
|
65
|
+
const changes = statusRaw
|
|
66
|
+
.split("\n")
|
|
67
|
+
.filter((line) => line.trim())
|
|
68
|
+
.map((line) => ({ status: line.slice(0, 2).trim() || "?", path: line.slice(3) }));
|
|
69
|
+
const commits = logRaw
|
|
70
|
+
.split("\n")
|
|
71
|
+
.filter((line) => line.trim())
|
|
72
|
+
.map((line) => {
|
|
73
|
+
const [sha = "", subject = "", author = "", relativeTime = ""] = line.split("\t");
|
|
74
|
+
return { sha, subject, author, relativeTime };
|
|
75
|
+
});
|
|
76
|
+
const ahead = Number(aheadRaw);
|
|
77
|
+
return {
|
|
78
|
+
branch: branchRaw.trim() || null,
|
|
79
|
+
changes,
|
|
80
|
+
diff: diffRaw.slice(0, MAX_GIT_DIFF_CHARS),
|
|
81
|
+
diffTruncated: diffRaw.length > MAX_GIT_DIFF_CHARS,
|
|
82
|
+
commits,
|
|
83
|
+
ahead: Number.isFinite(ahead) ? ahead : null,
|
|
84
|
+
branches: parseGitBranches(branchList, branchRaw.trim() || null),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function gitChecked(repoPath, args, timeout = 60_000) {
|
|
88
|
+
try {
|
|
89
|
+
const { stdout } = await execCli("git", args, {
|
|
90
|
+
cwd: repoPath,
|
|
91
|
+
timeout,
|
|
92
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
93
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
94
|
+
});
|
|
95
|
+
return stdout;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const e = err;
|
|
99
|
+
throw new Error((e.stderr || e.message || "Git failed").trim().slice(0, 800));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function commitWorkspaceGit(repoPath, rawMessage) {
|
|
103
|
+
const message = assertCommitMessage(rawMessage);
|
|
104
|
+
await gitChecked(repoPath, ["add", "-A"]);
|
|
105
|
+
const status = await git(repoPath, ["status", "--porcelain"]);
|
|
106
|
+
if (!status.trim())
|
|
107
|
+
throw new Error("Nothing to commit");
|
|
108
|
+
await gitChecked(repoPath, [
|
|
109
|
+
"-c",
|
|
110
|
+
"user.name=Eigen",
|
|
111
|
+
"-c",
|
|
112
|
+
"user.email=agent@users.noreply.github.com",
|
|
113
|
+
"commit",
|
|
114
|
+
"-m",
|
|
115
|
+
message,
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
export async function pushWorkspaceGit(repoPath) {
|
|
119
|
+
const branch = (await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
120
|
+
if (!branch || branch === "HEAD")
|
|
121
|
+
throw new Error("Checkout a branch before pushing");
|
|
122
|
+
await gitChecked(repoPath, ["push", "-u", "origin", "HEAD"], 180_000);
|
|
123
|
+
}
|
|
124
|
+
export async function checkoutWorkspaceGit(repoPath, rawBranch) {
|
|
125
|
+
const branch = assertGitBranchName(rawBranch);
|
|
126
|
+
const known = parseGitBranches(await git(repoPath, ["branch", "-a", "--format=%(refname:short)"]), null);
|
|
127
|
+
if (!known.includes(branch))
|
|
128
|
+
throw new Error("That branch is not in this repository");
|
|
129
|
+
if ((await git(repoPath, ["status", "--porcelain"])).trim()) {
|
|
130
|
+
throw new Error("Commit or discard changes before switching branches");
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await gitChecked(repoPath, ["checkout", branch]);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
await gitChecked(repoPath, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export async function listWorkspaceDir(repoPath, rawPath) {
|
|
140
|
+
const { abs, rel } = resolveInside(repoPath, rawPath);
|
|
141
|
+
let dir;
|
|
142
|
+
try {
|
|
143
|
+
dir = await fs.readdir(abs, { withFileTypes: true });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new Error("Folder not found");
|
|
147
|
+
}
|
|
148
|
+
const entries = await Promise.all(dir
|
|
149
|
+
.filter((entry) => entry.name !== ".git")
|
|
150
|
+
.map(async (entry) => {
|
|
151
|
+
if (entry.isSymbolicLink())
|
|
152
|
+
return null;
|
|
153
|
+
const entryPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
154
|
+
if (entry.isDirectory()) {
|
|
155
|
+
return { name: entry.name, path: entryPath, type: "dir" };
|
|
156
|
+
}
|
|
157
|
+
if (!entry.isFile())
|
|
158
|
+
return null;
|
|
159
|
+
let sizeBytes = 0;
|
|
160
|
+
try {
|
|
161
|
+
sizeBytes = (await fs.stat(resolve(abs, entry.name))).size;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// keep 0
|
|
165
|
+
}
|
|
166
|
+
return { name: entry.name, path: entryPath, type: "file", sizeBytes };
|
|
167
|
+
}));
|
|
168
|
+
return entries
|
|
169
|
+
.filter((entry) => Boolean(entry))
|
|
170
|
+
.sort((a, b) => (a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1));
|
|
171
|
+
}
|
|
172
|
+
export async function readWorkspaceFile(repoPath, rawPath) {
|
|
173
|
+
if (!rawPath)
|
|
174
|
+
throw new Error("Invalid path");
|
|
175
|
+
const { abs, rel } = resolveInside(repoPath, rawPath);
|
|
176
|
+
let stat;
|
|
177
|
+
try {
|
|
178
|
+
stat = await fs.stat(abs);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
throw new Error("File not found");
|
|
182
|
+
}
|
|
183
|
+
if (!stat.isFile())
|
|
184
|
+
throw new Error("Not a file");
|
|
185
|
+
const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase();
|
|
186
|
+
const imageMime = abs.includes(".") ? IMAGE_MIME[ext] : undefined;
|
|
187
|
+
const cap = imageMime ? MAX_REPO_IMAGE_BYTES : MAX_REPO_FILE_BYTES;
|
|
188
|
+
const handle = await fs.open(abs, "r");
|
|
189
|
+
try {
|
|
190
|
+
const length = Math.min(stat.size, cap);
|
|
191
|
+
const bytes = Buffer.alloc(length);
|
|
192
|
+
if (length)
|
|
193
|
+
await handle.read(bytes, 0, length, 0);
|
|
194
|
+
const truncated = stat.size > length;
|
|
195
|
+
if (imageMime) {
|
|
196
|
+
return {
|
|
197
|
+
path: rel,
|
|
198
|
+
sizeBytes: stat.size,
|
|
199
|
+
content: null,
|
|
200
|
+
binary: true,
|
|
201
|
+
truncated,
|
|
202
|
+
imageDataUrl: truncated ? undefined : `data:${imageMime};base64,${bytes.toString("base64")}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const binary = bytes.subarray(0, 8192).includes(0);
|
|
206
|
+
return {
|
|
207
|
+
path: rel,
|
|
208
|
+
sizeBytes: stat.size,
|
|
209
|
+
content: binary ? null : bytes.toString("utf8"),
|
|
210
|
+
binary,
|
|
211
|
+
truncated,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
await handle.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function searchScore(path, query) {
|
|
219
|
+
const lower = path.toLowerCase();
|
|
220
|
+
const base = lower.slice(lower.lastIndexOf("/") + 1);
|
|
221
|
+
if (base === query)
|
|
222
|
+
return 1000;
|
|
223
|
+
if (base.startsWith(query))
|
|
224
|
+
return 800;
|
|
225
|
+
if (base.includes(query))
|
|
226
|
+
return 600;
|
|
227
|
+
if (lower.includes(query))
|
|
228
|
+
return 400;
|
|
229
|
+
let last = -1;
|
|
230
|
+
let score = 0;
|
|
231
|
+
for (const ch of query) {
|
|
232
|
+
const at = lower.indexOf(ch, last + 1);
|
|
233
|
+
if (at < 0)
|
|
234
|
+
return null;
|
|
235
|
+
score += at === last + 1 ? 3 : 1;
|
|
236
|
+
last = at;
|
|
237
|
+
}
|
|
238
|
+
return 200 + score;
|
|
239
|
+
}
|
|
240
|
+
export async function searchWorkspaceFiles(repoPath, query) {
|
|
241
|
+
const q = query.trim().toLowerCase().slice(0, 200);
|
|
242
|
+
if (!q)
|
|
243
|
+
return [];
|
|
244
|
+
const tracked = await git(repoPath, ["ls-files", "-co", "--exclude-standard"]);
|
|
245
|
+
const paths = new Set();
|
|
246
|
+
for (const raw of tracked.split("\n")) {
|
|
247
|
+
const path = raw.trim();
|
|
248
|
+
if (!path)
|
|
249
|
+
continue;
|
|
250
|
+
const parts = path.split("/");
|
|
251
|
+
if (parts.some((part) => EXCLUDED_DIRS.has(part)))
|
|
252
|
+
continue;
|
|
253
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
254
|
+
paths.add(`${parts.slice(0, i).join("/")}/`);
|
|
255
|
+
}
|
|
256
|
+
paths.add(path);
|
|
257
|
+
if (paths.size >= MAX_SEARCH_PATHS)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
return [...paths]
|
|
261
|
+
.map((path) => ({ path, score: searchScore(path, q) }))
|
|
262
|
+
.filter((item) => item.score !== null)
|
|
263
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
264
|
+
.slice(0, 60)
|
|
265
|
+
.map(({ path }) => ({
|
|
266
|
+
path: path.replace(/\/+$/, ""),
|
|
267
|
+
type: path.endsWith("/") ? "dir" : "file",
|
|
268
|
+
}));
|
|
269
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -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;
|
package/dist/config.js
ADDED
|
@@ -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,95 @@
|
|
|
1
|
+
import { spawn, execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
export async function getCursorAuth() {
|
|
7
|
+
try {
|
|
8
|
+
const { stdout } = await execFileAsync("cursor", ["agent", "whoami", "--format", "json"], {
|
|
9
|
+
timeout: 30_000,
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
maxBuffer: 1024 * 1024,
|
|
12
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
13
|
+
});
|
|
14
|
+
const data = JSON.parse(stdout.trim());
|
|
15
|
+
if (data.isAuthenticated || data.status === "authenticated") {
|
|
16
|
+
return {
|
|
17
|
+
authenticated: true,
|
|
18
|
+
email: data.userInfo?.email,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { authenticated: false };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const e = err;
|
|
25
|
+
// cursor sometimes prints JSON on stdout even with non-zero exit
|
|
26
|
+
if (e.stdout) {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(String(e.stdout).trim());
|
|
29
|
+
if (data.isAuthenticated || data.status === "authenticated") {
|
|
30
|
+
return {
|
|
31
|
+
authenticated: true,
|
|
32
|
+
email: data.userInfo?.email,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { authenticated: false };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// fall through
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (e.code === "ENOENT") {
|
|
42
|
+
return {
|
|
43
|
+
authenticated: false,
|
|
44
|
+
error: "`cursor` CLI not found. Install Cursor CLI first: https://cursor.com",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
authenticated: false,
|
|
49
|
+
error: e.stderr?.trim() || e.message || "Failed to check Cursor auth",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runCursorLogin() {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const child = spawn("cursor", ["agent", "login"], {
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
env: process.env,
|
|
58
|
+
});
|
|
59
|
+
child.on("error", (err) => {
|
|
60
|
+
console.error(chalk.red(`Failed to start cursor login: ${err.message}`));
|
|
61
|
+
resolve(1);
|
|
62
|
+
});
|
|
63
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Ensure the Cursor CLI is logged in before starting the worker.
|
|
68
|
+
* Prompts interactive `cursor agent login` if not authenticated.
|
|
69
|
+
*/
|
|
70
|
+
export async function ensureCursorAuth() {
|
|
71
|
+
const spinner = ora("Checking Cursor CLI login…").start();
|
|
72
|
+
let auth = await getCursorAuth();
|
|
73
|
+
if (auth.error && auth.error.includes("not found")) {
|
|
74
|
+
spinner.fail(auth.error);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
if (auth.authenticated) {
|
|
78
|
+
spinner.succeed(`Cursor CLI logged in as ${chalk.cyan(auth.email || "unknown")}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
spinner.warn("Cursor CLI is not logged in");
|
|
82
|
+
console.log(chalk.yellow("\n Local agents need Cursor CLI auth. Opening login…\n"));
|
|
83
|
+
const code = await runCursorLogin();
|
|
84
|
+
if (code !== 0) {
|
|
85
|
+
console.error(chalk.red("\nCursor login failed or was cancelled. Run `cursor agent login` and try again."));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const verify = ora("Verifying Cursor login…").start();
|
|
89
|
+
auth = await getCursorAuth();
|
|
90
|
+
if (!auth.authenticated) {
|
|
91
|
+
verify.fail("Still not logged into Cursor. Run `cursor agent login` and try again.");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
verify.succeed(`Cursor CLI logged in as ${chalk.cyan(auth.email || "unknown")}`);
|
|
95
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
const program = new Command();
|
|
11
|
+
function prompt(question) {
|
|
12
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
program
|
|
21
|
+
.name("steer")
|
|
22
|
+
.description("CLI worker for Steer — run local Cursor agents")
|
|
23
|
+
.version("0.2.0");
|
|
24
|
+
program
|
|
25
|
+
.command("login")
|
|
26
|
+
.description("Pair CLI with your Steer account (Clerk via web pairing code)")
|
|
27
|
+
.action(async () => {
|
|
28
|
+
try {
|
|
29
|
+
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"));
|
|
30
|
+
const serverUrlRaw = await prompt(`Server URL [${DEFAULT_SERVER_URL}]: `);
|
|
31
|
+
const serverUrl = serverUrlRaw || DEFAULT_SERVER_URL;
|
|
32
|
+
const code = await prompt("Pairing code: ");
|
|
33
|
+
if (!code) {
|
|
34
|
+
console.error(chalk.red("Pairing code is required."));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const spinner = ora("Pairing…").start();
|
|
38
|
+
try {
|
|
39
|
+
const config = await loginWithPairingCode(serverUrl, code);
|
|
40
|
+
spinner.succeed(`Paired as ${chalk.cyan(config.email)}`);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
spinner.fail(err.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
program
|
|
52
|
+
.command("logout")
|
|
53
|
+
.description("Clear stored credentials")
|
|
54
|
+
.action(() => {
|
|
55
|
+
logout();
|
|
56
|
+
console.log(chalk.green("Logged out."));
|
|
57
|
+
});
|
|
58
|
+
program
|
|
59
|
+
.command("status")
|
|
60
|
+
.description("Show connection status and logged-in user")
|
|
61
|
+
.action(async () => {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
if (!config) {
|
|
64
|
+
console.log(chalk.yellow("Not logged in."));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.blue("Logged in as:"), chalk.cyan(config.email));
|
|
68
|
+
console.log(chalk.blue("Server:"), config.serverUrl);
|
|
69
|
+
const spinner = ora("Checking server…").start();
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`${config.serverUrl}/api/rooms`, {
|
|
72
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
spinner.fail(`Server returned ${res.status}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const rooms = (await res.json());
|
|
79
|
+
spinner.succeed(`Server online — ${rooms.length} room(s)`);
|
|
80
|
+
for (const room of rooms) {
|
|
81
|
+
console.log(chalk.gray(` • ${room.name || room.id}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
spinner.fail(`Cannot reach server: ${err.message}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
program
|
|
89
|
+
.command("start")
|
|
90
|
+
.description("Start the worker daemon")
|
|
91
|
+
.option("--repo <path>", "Override repository path for all prompts")
|
|
92
|
+
.action(async (opts) => {
|
|
93
|
+
const config = loadConfig();
|
|
94
|
+
if (!config) {
|
|
95
|
+
console.error(chalk.red("Not logged in. Run `steer login` first."));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// Require Cursor CLI login before accepting Local prompts
|
|
99
|
+
await ensureCursorAuth();
|
|
100
|
+
console.log(chalk.blue.bold("\n Steer Worker\n"));
|
|
101
|
+
console.log(chalk.gray(` Server: ${config.serverUrl}`));
|
|
102
|
+
console.log(chalk.gray(` User: ${config.email}`));
|
|
103
|
+
if (opts.repo)
|
|
104
|
+
console.log(chalk.gray(` Repo: ${opts.repo}`));
|
|
105
|
+
console.log();
|
|
106
|
+
startWorker(opts.repo);
|
|
107
|
+
});
|
|
108
|
+
program.parse();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
5
|
+
const MODEL_LINE_RE = /^([a-zA-Z0-9][a-zA-Z0-9._-]*)\s+[-–—]\s+(.+)$/;
|
|
6
|
+
function stripAnsi(text) {
|
|
7
|
+
return text.replace(ANSI_RE, "").replace(/\r/g, "");
|
|
8
|
+
}
|
|
9
|
+
function parseModels(stdout) {
|
|
10
|
+
const models = [];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
for (const raw of stripAnsi(stdout).split("\n")) {
|
|
13
|
+
const line = raw.trim();
|
|
14
|
+
if (!line || /^available models$/i.test(line))
|
|
15
|
+
continue;
|
|
16
|
+
const m = line.match(MODEL_LINE_RE);
|
|
17
|
+
if (!m)
|
|
18
|
+
continue;
|
|
19
|
+
const id = m[1];
|
|
20
|
+
if (seen.has(id))
|
|
21
|
+
continue;
|
|
22
|
+
seen.add(id);
|
|
23
|
+
models.push({ id, displayName: m[2].trim() });
|
|
24
|
+
}
|
|
25
|
+
return models;
|
|
26
|
+
}
|
|
27
|
+
async function runListModels() {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await execFileAsync("cursor", ["agent", "--list-models"], {
|
|
30
|
+
timeout: 60_000,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
NO_COLOR: "1",
|
|
36
|
+
FORCE_COLOR: "0",
|
|
37
|
+
TERM: "dumb",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return stdout || "";
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const e = err;
|
|
44
|
+
if (e.stdout && String(e.stdout).trim())
|
|
45
|
+
return String(e.stdout);
|
|
46
|
+
if (e.code === "ENOENT") {
|
|
47
|
+
throw new Error("cursor CLI not found. Install Cursor and ensure `cursor` is on PATH.");
|
|
48
|
+
}
|
|
49
|
+
throw new Error(e.stderr?.trim() ||
|
|
50
|
+
e.message ||
|
|
51
|
+
"Failed to run `cursor agent --list-models`");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Models available to the logged-in Cursor CLI on this machine. */
|
|
55
|
+
export async function listLocalModels() {
|
|
56
|
+
const stdout = await runListModels();
|
|
57
|
+
const models = parseModels(stdout);
|
|
58
|
+
if (models.length === 0) {
|
|
59
|
+
return [{ id: "auto", displayName: "Auto" }];
|
|
60
|
+
}
|
|
61
|
+
return models;
|
|
62
|
+
}
|
|
@@ -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
|
+
}
|