@geteigen/cli 0.0.0-stage → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +154 -2
  2. package/dist/agent.d.ts +32 -0
  3. package/dist/agent.js +246 -0
  4. package/dist/auth.d.ts +5 -0
  5. package/dist/auth.js +33 -0
  6. package/dist/cli/src/agent.d.ts +24 -0
  7. package/dist/cli/src/agent.js +263 -0
  8. package/dist/cli/src/agentTools.d.ts +6 -0
  9. package/dist/cli/src/agentTools.js +104 -0
  10. package/dist/cli/src/auth.d.ts +5 -0
  11. package/dist/cli/src/auth.js +33 -0
  12. package/dist/cli/src/cliEnv.d.ts +6 -0
  13. package/dist/cli/src/cliEnv.js +26 -0
  14. package/dist/cli/src/config.d.ts +11 -0
  15. package/dist/cli/src/config.js +51 -0
  16. package/dist/cli/src/cursorAuth.d.ts +10 -0
  17. package/dist/cli/src/cursorAuth.js +92 -0
  18. package/dist/cli/src/gitAuthEnv.d.ts +5 -0
  19. package/dist/cli/src/gitAuthEnv.js +35 -0
  20. package/dist/cli/src/index.d.ts +2 -0
  21. package/dist/cli/src/index.js +244 -0
  22. package/dist/cli/src/listModels.d.ts +14 -0
  23. package/dist/cli/src/listModels.js +196 -0
  24. package/dist/cli/src/listSessions.d.ts +9 -0
  25. package/dist/cli/src/listSessions.js +37 -0
  26. package/dist/cli/src/machineConfig.d.ts +20 -0
  27. package/dist/cli/src/machineConfig.js +66 -0
  28. package/dist/cli/src/pickFolder.d.ts +5 -0
  29. package/dist/cli/src/pickFolder.js +65 -0
  30. package/dist/cli/src/proc.d.ts +31 -0
  31. package/dist/cli/src/proc.js +97 -0
  32. package/dist/cli/src/service.d.ts +17 -0
  33. package/dist/cli/src/service.js +255 -0
  34. package/dist/cli/src/workRoots.d.ts +30 -0
  35. package/dist/cli/src/workRoots.js +228 -0
  36. package/dist/cli/src/worker.d.ts +8 -0
  37. package/dist/cli/src/worker.js +51 -0
  38. package/dist/cli/src/workerCore.d.ts +47 -0
  39. package/dist/cli/src/workerCore.js +721 -0
  40. package/dist/cli/src/workspace.d.ts +10 -0
  41. package/dist/cli/src/workspace.js +329 -0
  42. package/dist/config.d.ts +11 -0
  43. package/dist/config.js +51 -0
  44. package/dist/cursorAuth.d.ts +10 -0
  45. package/dist/cursorAuth.js +95 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +108 -0
  48. package/dist/listModels.d.ts +6 -0
  49. package/dist/listModels.js +62 -0
  50. package/dist/listSessions.d.ts +9 -0
  51. package/dist/listSessions.js +37 -0
  52. package/dist/pickFolder.d.ts +5 -0
  53. package/dist/pickFolder.js +65 -0
  54. package/dist/shared/actionDrafts.d.ts +83 -0
  55. package/dist/shared/actionDrafts.js +169 -0
  56. package/dist/shared/agentArtifacts.d.ts +49 -0
  57. package/dist/shared/agentArtifacts.js +169 -0
  58. package/dist/shared/agentLabel.d.ts +18 -0
  59. package/dist/shared/agentLabel.js +22 -0
  60. package/dist/shared/agentMentions.d.ts +38 -0
  61. package/dist/shared/agentMentions.js +110 -0
  62. package/dist/shared/agentRepos.d.ts +28 -0
  63. package/dist/shared/agentRepos.js +60 -0
  64. package/dist/shared/agentRunStatus.d.ts +12 -0
  65. package/dist/shared/agentRunStatus.js +17 -0
  66. package/dist/shared/agentUsage.d.ts +24 -0
  67. package/dist/shared/agentUsage.js +139 -0
  68. package/dist/shared/approvals.d.ts +44 -0
  69. package/dist/shared/approvals.js +97 -0
  70. package/dist/shared/askEigen.d.ts +190 -0
  71. package/dist/shared/askEigen.js +76 -0
  72. package/dist/shared/attribution.d.ts +25 -0
  73. package/dist/shared/attribution.js +63 -0
  74. package/dist/shared/automations.d.ts +135 -0
  75. package/dist/shared/automations.js +553 -0
  76. package/dist/shared/backends/claudeCode.d.ts +44 -0
  77. package/dist/shared/backends/claudeCode.js +548 -0
  78. package/dist/shared/backends/codex.d.ts +15 -0
  79. package/dist/shared/backends/codex.js +287 -0
  80. package/dist/shared/backends/cursor.d.ts +111 -0
  81. package/dist/shared/backends/cursor.js +1216 -0
  82. package/dist/shared/backends/index.d.ts +18 -0
  83. package/dist/shared/backends/index.js +58 -0
  84. package/dist/shared/backends/opencode.d.ts +43 -0
  85. package/dist/shared/backends/opencode.js +415 -0
  86. package/dist/shared/backends/types.d.ts +171 -0
  87. package/dist/shared/backends/types.js +19 -0
  88. package/dist/shared/backends/usage.d.ts +8 -0
  89. package/dist/shared/backends/usage.js +71 -0
  90. package/dist/shared/billingPlans.d.ts +60 -0
  91. package/dist/shared/billingPlans.js +262 -0
  92. package/dist/shared/brain.d.ts +353 -0
  93. package/dist/shared/brain.js +487 -0
  94. package/dist/shared/clarifyingQuestions.d.ts +13 -0
  95. package/dist/shared/clarifyingQuestions.js +207 -0
  96. package/dist/shared/claudeModels.d.ts +10 -0
  97. package/dist/shared/claudeModels.js +52 -0
  98. package/dist/shared/cloudBackends.d.ts +21 -0
  99. package/dist/shared/cloudBackends.js +29 -0
  100. package/dist/shared/codexModels.d.ts +10 -0
  101. package/dist/shared/codexModels.js +28 -0
  102. package/dist/shared/contextWindows.d.ts +2 -0
  103. package/dist/shared/contextWindows.js +30 -0
  104. package/dist/shared/cursorMcpConfig.d.ts +14 -0
  105. package/dist/shared/cursorMcpConfig.js +43 -0
  106. package/dist/shared/cursorProjectMcp.d.ts +10 -0
  107. package/dist/shared/cursorProjectMcp.js +42 -0
  108. package/dist/shared/docs.d.ts +59 -0
  109. package/dist/shared/docs.js +87 -0
  110. package/dist/shared/events.d.ts +932 -0
  111. package/dist/shared/events.js +57 -0
  112. package/dist/shared/integrations.d.ts +266 -0
  113. package/dist/shared/integrations.js +449 -0
  114. package/dist/shared/issueTranscript.d.ts +64 -0
  115. package/dist/shared/issueTranscript.js +387 -0
  116. package/dist/shared/issues.d.ts +139 -0
  117. package/dist/shared/issues.js +217 -0
  118. package/dist/shared/machines.d.ts +119 -0
  119. package/dist/shared/machines.js +58 -0
  120. package/dist/shared/markdownMedia.d.ts +13 -0
  121. package/dist/shared/markdownMedia.js +41 -0
  122. package/dist/shared/notificationText.d.ts +6 -0
  123. package/dist/shared/notificationText.js +38 -0
  124. package/dist/shared/opencodeModels.d.ts +19 -0
  125. package/dist/shared/opencodeModels.js +62 -0
  126. package/dist/shared/opencodeSession.d.ts +7 -0
  127. package/dist/shared/opencodeSession.js +30 -0
  128. package/dist/shared/orchestration.d.ts +58 -0
  129. package/dist/shared/orchestration.js +12 -0
  130. package/dist/shared/orgs.d.ts +47 -0
  131. package/dist/shared/orgs.js +49 -0
  132. package/dist/shared/personalOrg.d.ts +5 -0
  133. package/dist/shared/personalOrg.js +12 -0
  134. package/dist/shared/plans.d.ts +3 -0
  135. package/dist/shared/plans.js +19 -0
  136. package/dist/shared/providerModels.d.ts +40 -0
  137. package/dist/shared/providerModels.js +503 -0
  138. package/dist/shared/roomAttention.d.ts +17 -0
  139. package/dist/shared/roomAttention.js +36 -0
  140. package/dist/shared/roomContext.d.ts +120 -0
  141. package/dist/shared/roomContext.js +133 -0
  142. package/dist/shared/roomPermissions.d.ts +56 -0
  143. package/dist/shared/roomPermissions.js +133 -0
  144. package/dist/shared/sessionSeats.d.ts +29 -0
  145. package/dist/shared/sessionSeats.js +57 -0
  146. package/dist/shared/slashCommands.d.ts +39 -0
  147. package/dist/shared/slashCommands.js +166 -0
  148. package/dist/shared/space.d.ts +33 -0
  149. package/dist/shared/space.js +54 -0
  150. package/dist/shared/spaceFeed.d.ts +78 -0
  151. package/dist/shared/spaceFeed.js +194 -0
  152. package/dist/shared/stringifyUnknown.d.ts +1 -0
  153. package/dist/shared/stringifyUnknown.js +1 -0
  154. package/dist/shared/swarm.d.ts +320 -0
  155. package/dist/shared/swarm.js +328 -0
  156. package/dist/shared/swarmVisual.d.ts +79 -0
  157. package/dist/shared/swarmVisual.js +212 -0
  158. package/dist/shared/terminalFilter.d.ts +13 -0
  159. package/dist/shared/terminalFilter.js +46 -0
  160. package/dist/shared/toolIntegration.d.ts +56 -0
  161. package/dist/shared/toolIntegration.js +180 -0
  162. package/dist/shared/typing.d.ts +9 -0
  163. package/dist/shared/typing.js +37 -0
  164. package/dist/shared/uploads.d.ts +31 -0
  165. package/dist/shared/uploads.js +65 -0
  166. package/dist/shared/voiceApprovals.d.ts +36 -0
  167. package/dist/shared/voiceApprovals.js +229 -0
  168. package/dist/shared/workspace.d.ts +49 -0
  169. package/dist/shared/workspace.js +53 -0
  170. package/dist/worker.d.ts +1 -0
  171. package/dist/worker.js +266 -0
  172. package/package.json +51 -4
@@ -0,0 +1,10 @@
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 createWorkspaceGitBranch(repoPath: string, rawBranch: string): Promise<void>;
7
+ export declare function discardWorkspaceGit(repoPath: string, rawPath?: string): Promise<void>;
8
+ export declare function listWorkspaceDir(repoPath: string, rawPath: string): Promise<WorkspaceRepoEntry[]>;
9
+ export declare function readWorkspaceFile(repoPath: string, rawPath: string): Promise<WorkspaceRepoFile>;
10
+ export declare function searchWorkspaceFiles(repoPath: string, query: string): Promise<WorkspacePathMatch[]>;
@@ -0,0 +1,329 @@
1
+ import { existsSync, promises as fs } from "fs";
2
+ import { isAbsolute, relative, resolve } from "path";
3
+ import { execCli } from "./proc.js";
4
+ import { storedGithubCredentialEnv } from "./gitAuthEnv.js";
5
+ import { assertCommitMessage, assertGitBranchName, assertGitChangePath, parseGitBranches, } from "../../shared/workspace.js";
6
+ const MAX_GIT_DIFF_CHARS = 400_000;
7
+ const MAX_REPO_FILE_BYTES = 512 * 1024;
8
+ const MAX_REPO_IMAGE_BYTES = 3 * 1024 * 1024;
9
+ const MAX_SEARCH_PATHS = 50_000;
10
+ const EXCLUDED_DIRS = new Set([
11
+ "node_modules",
12
+ ".git",
13
+ "dist",
14
+ "build",
15
+ ".next",
16
+ "target",
17
+ "__pycache__",
18
+ ".venv",
19
+ "venv",
20
+ "coverage",
21
+ ".turbo",
22
+ ".cache",
23
+ ]);
24
+ const IMAGE_MIME = {
25
+ png: "image/png",
26
+ jpg: "image/jpeg",
27
+ jpeg: "image/jpeg",
28
+ gif: "image/gif",
29
+ webp: "image/webp",
30
+ svg: "image/svg+xml",
31
+ avif: "image/avif",
32
+ bmp: "image/bmp",
33
+ };
34
+ async function git(repoPath, args) {
35
+ try {
36
+ const { stdout } = await execCli("git", args, {
37
+ cwd: repoPath,
38
+ timeout: 30_000,
39
+ maxBuffer: 8 * 1024 * 1024,
40
+ env: { ...process.env, ...storedGithubCredentialEnv() },
41
+ });
42
+ return stdout;
43
+ }
44
+ catch (err) {
45
+ const e = err;
46
+ return e.stdout || "";
47
+ }
48
+ }
49
+ function resolveInside(repoPath, rawPath) {
50
+ const abs = isAbsolute(rawPath) ? rawPath : resolve(repoPath, rawPath || "");
51
+ const rel = relative(repoPath, abs);
52
+ if (rel.startsWith("..") || isAbsolute(rel)) {
53
+ throw new Error("Path is outside the repo");
54
+ }
55
+ return { abs, rel: rel.replace(/\\/g, "/") };
56
+ }
57
+ export async function getWorkspaceGit(repoPath) {
58
+ if (!repoPath || !existsSync(repoPath))
59
+ throw new Error("Repository folder not found");
60
+ const [branchRaw, statusRaw, logRaw, aheadCount, branchList, diffRaw] = await Promise.all([
61
+ git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]),
62
+ git(repoPath, ["status", "--porcelain"]),
63
+ git(repoPath, ["log", "-n", "25", "--pretty=format:%h%x09%s%x09%an%x09%cr"]),
64
+ git(repoPath, ["rev-list", "--count", "@{u}..HEAD"]),
65
+ git(repoPath, ["branch", "-a", "--format=%(refname:short)"]),
66
+ git(repoPath, ["diff", "HEAD"]),
67
+ ]);
68
+ const aheadRaw = aheadCount.trim();
69
+ const changes = statusRaw
70
+ .split("\n")
71
+ .filter((line) => line.trim())
72
+ .map((line) => ({ status: line.slice(0, 2).trim() || "?", path: line.slice(3) }));
73
+ const commits = logRaw
74
+ .split("\n")
75
+ .filter((line) => line.trim())
76
+ .map((line) => {
77
+ const [sha = "", subject = "", author = "", relativeTime = ""] = line.split("\t");
78
+ return { sha, subject, author, relativeTime };
79
+ });
80
+ const ahead = Number(aheadRaw);
81
+ return {
82
+ branch: branchRaw.trim() || null,
83
+ changes,
84
+ diff: diffRaw.slice(0, MAX_GIT_DIFF_CHARS),
85
+ diffTruncated: diffRaw.length > MAX_GIT_DIFF_CHARS,
86
+ commits,
87
+ ahead: Number.isFinite(ahead) ? ahead : null,
88
+ branches: parseGitBranches(branchList, branchRaw.trim() || null),
89
+ };
90
+ }
91
+ async function gitChecked(repoPath, args, timeout = 60_000) {
92
+ try {
93
+ const { stdout } = await execCli("git", args, {
94
+ cwd: repoPath,
95
+ timeout,
96
+ maxBuffer: 8 * 1024 * 1024,
97
+ env: { ...process.env, ...storedGithubCredentialEnv() },
98
+ });
99
+ return stdout;
100
+ }
101
+ catch (err) {
102
+ const e = err;
103
+ throw new Error((e.stderr || e.message || "Git failed").trim().slice(0, 800));
104
+ }
105
+ }
106
+ async function githubCliIdentity() {
107
+ try {
108
+ const { stdout } = await execCli("gh", ["api", "user"], { timeout: 15_000 });
109
+ const user = JSON.parse(stdout);
110
+ const login = String(user.login || "").trim();
111
+ if (!login)
112
+ return null;
113
+ const email = typeof user.email === "string" && user.email.includes("@")
114
+ ? user.email.trim()
115
+ : `${user.id ? `${user.id}+` : ""}${login}@users.noreply.github.com`;
116
+ return { name: String(user.name || "").trim() || login, email };
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ /** Prefer the machine git identity, then the signed-in GitHub CLI user. */
123
+ async function resolveCommitIdentity(repoPath) {
124
+ const name = (await git(repoPath, ["config", "--get", "user.name"])).trim();
125
+ const email = (await git(repoPath, ["config", "--get", "user.email"])).trim();
126
+ if (name && email)
127
+ return { name, email };
128
+ const gh = await githubCliIdentity();
129
+ if (gh)
130
+ return { name: name || gh.name, email: email || gh.email };
131
+ throw new Error("Set git user.name and user.email, or sign in with GitHub CLI (gh auth login)");
132
+ }
133
+ export async function commitWorkspaceGit(repoPath, rawMessage) {
134
+ const message = assertCommitMessage(rawMessage);
135
+ const author = await resolveCommitIdentity(repoPath);
136
+ await gitChecked(repoPath, ["add", "-A"]);
137
+ const status = await git(repoPath, ["status", "--porcelain"]);
138
+ if (!status.trim())
139
+ throw new Error("Nothing to commit");
140
+ await gitChecked(repoPath, [
141
+ "-c",
142
+ `user.name=${author.name}`,
143
+ "-c",
144
+ `user.email=${author.email}`,
145
+ "commit",
146
+ "-m",
147
+ message,
148
+ ]);
149
+ }
150
+ export async function pushWorkspaceGit(repoPath) {
151
+ const branch = (await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
152
+ if (!branch || branch === "HEAD")
153
+ throw new Error("Checkout a branch before pushing");
154
+ await gitChecked(repoPath, ["fetch", "origin"], 180_000).catch(() => undefined);
155
+ await gitChecked(repoPath, ["push", "-u", "origin", "HEAD"], 180_000);
156
+ }
157
+ export async function checkoutWorkspaceGit(repoPath, rawBranch) {
158
+ const branch = assertGitBranchName(rawBranch);
159
+ const known = parseGitBranches(await git(repoPath, ["branch", "-a", "--format=%(refname:short)"]), null);
160
+ if (!known.includes(branch))
161
+ throw new Error("That branch is not in this repository");
162
+ if ((await git(repoPath, ["status", "--porcelain"])).trim()) {
163
+ throw new Error("Commit or discard changes before switching branches");
164
+ }
165
+ try {
166
+ await gitChecked(repoPath, ["checkout", branch]);
167
+ }
168
+ catch {
169
+ await gitChecked(repoPath, ["checkout", "-B", branch, `origin/${branch}`]);
170
+ }
171
+ }
172
+ export async function createWorkspaceGitBranch(repoPath, rawBranch) {
173
+ const branch = assertGitBranchName(rawBranch);
174
+ const current = (await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
175
+ if (current === branch)
176
+ return;
177
+ const known = parseGitBranches(await git(repoPath, ["branch", "-a", "--format=%(refname:short)"]), current);
178
+ if (known.includes(branch))
179
+ throw new Error("That branch already exists");
180
+ await gitChecked(repoPath, ["checkout", "-b", branch]);
181
+ }
182
+ export async function discardWorkspaceGit(repoPath, rawPath = "") {
183
+ const path = assertGitChangePath(rawPath);
184
+ if (!path) {
185
+ await gitChecked(repoPath, ["reset", "--hard", "HEAD"]);
186
+ await gitChecked(repoPath, ["clean", "-fd"]);
187
+ return;
188
+ }
189
+ const tracked = await git(repoPath, ["ls-files", "--error-unmatch", "--", path]);
190
+ if (tracked.trim()) {
191
+ await gitChecked(repoPath, ["restore", "--source=HEAD", "--staged", "--worktree", "--", path]).catch(async () => {
192
+ await gitChecked(repoPath, ["checkout", "HEAD", "--", path]);
193
+ });
194
+ return;
195
+ }
196
+ const { abs } = resolveInside(repoPath, path);
197
+ await fs.rm(abs, { recursive: true, force: true });
198
+ }
199
+ export async function listWorkspaceDir(repoPath, rawPath) {
200
+ const { abs, rel } = resolveInside(repoPath, rawPath);
201
+ let dir;
202
+ try {
203
+ dir = await fs.readdir(abs, { withFileTypes: true });
204
+ }
205
+ catch {
206
+ throw new Error("Folder not found");
207
+ }
208
+ const entries = await Promise.all(dir
209
+ .filter((entry) => entry.name !== ".git")
210
+ .map(async (entry) => {
211
+ if (entry.isSymbolicLink())
212
+ return null;
213
+ const entryPath = rel ? `${rel}/${entry.name}` : entry.name;
214
+ if (entry.isDirectory()) {
215
+ return { name: entry.name, path: entryPath, type: "dir" };
216
+ }
217
+ if (!entry.isFile())
218
+ return null;
219
+ let sizeBytes = 0;
220
+ try {
221
+ sizeBytes = (await fs.stat(resolve(abs, entry.name))).size;
222
+ }
223
+ catch {
224
+ // keep 0
225
+ }
226
+ return { name: entry.name, path: entryPath, type: "file", sizeBytes };
227
+ }));
228
+ return entries
229
+ .filter((entry) => Boolean(entry))
230
+ .sort((a, b) => (a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1));
231
+ }
232
+ export async function readWorkspaceFile(repoPath, rawPath) {
233
+ if (!rawPath)
234
+ throw new Error("Invalid path");
235
+ const { abs, rel } = resolveInside(repoPath, rawPath);
236
+ let stat;
237
+ try {
238
+ stat = await fs.stat(abs);
239
+ }
240
+ catch {
241
+ throw new Error("File not found");
242
+ }
243
+ if (!stat.isFile())
244
+ throw new Error("Not a file");
245
+ const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase();
246
+ const imageMime = abs.includes(".") ? IMAGE_MIME[ext] : undefined;
247
+ const cap = imageMime ? MAX_REPO_IMAGE_BYTES : MAX_REPO_FILE_BYTES;
248
+ const handle = await fs.open(abs, "r");
249
+ try {
250
+ const length = Math.min(stat.size, cap);
251
+ const bytes = Buffer.alloc(length);
252
+ if (length)
253
+ await handle.read(bytes, 0, length, 0);
254
+ const truncated = stat.size > length;
255
+ if (imageMime) {
256
+ return {
257
+ path: rel,
258
+ sizeBytes: stat.size,
259
+ content: null,
260
+ binary: true,
261
+ truncated,
262
+ imageDataUrl: truncated ? undefined : `data:${imageMime};base64,${bytes.toString("base64")}`,
263
+ };
264
+ }
265
+ const binary = bytes.subarray(0, 8192).includes(0);
266
+ return {
267
+ path: rel,
268
+ sizeBytes: stat.size,
269
+ content: binary ? null : bytes.toString("utf8"),
270
+ binary,
271
+ truncated,
272
+ };
273
+ }
274
+ finally {
275
+ await handle.close();
276
+ }
277
+ }
278
+ function searchScore(path, query) {
279
+ const lower = path.toLowerCase();
280
+ const base = lower.slice(lower.lastIndexOf("/") + 1);
281
+ if (base === query)
282
+ return 1000;
283
+ if (base.startsWith(query))
284
+ return 800;
285
+ if (base.includes(query))
286
+ return 600;
287
+ if (lower.includes(query))
288
+ return 400;
289
+ let last = -1;
290
+ let score = 0;
291
+ for (const ch of query) {
292
+ const at = lower.indexOf(ch, last + 1);
293
+ if (at < 0)
294
+ return null;
295
+ score += at === last + 1 ? 3 : 1;
296
+ last = at;
297
+ }
298
+ return 200 + score;
299
+ }
300
+ export async function searchWorkspaceFiles(repoPath, query) {
301
+ const q = query.trim().toLowerCase().slice(0, 200);
302
+ if (!q)
303
+ return [];
304
+ const tracked = await git(repoPath, ["ls-files", "-co", "--exclude-standard"]);
305
+ const paths = new Set();
306
+ for (const raw of tracked.split("\n")) {
307
+ const path = raw.trim();
308
+ if (!path)
309
+ continue;
310
+ const parts = path.split("/");
311
+ if (parts.some((part) => EXCLUDED_DIRS.has(part)))
312
+ continue;
313
+ for (let i = 1; i < parts.length; i += 1) {
314
+ paths.add(`${parts.slice(0, i).join("/")}/`);
315
+ }
316
+ paths.add(path);
317
+ if (paths.size >= MAX_SEARCH_PATHS)
318
+ break;
319
+ }
320
+ return [...paths]
321
+ .map((path) => ({ path, score: searchScore(path, q) }))
322
+ .filter((item) => item.score !== null)
323
+ .sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
324
+ .slice(0, 60)
325
+ .map(({ path }) => ({
326
+ path: path.replace(/\/+$/, ""),
327
+ type: path.endsWith("/") ? "dir" : "file",
328
+ }));
329
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,6 @@
1
+ export interface ModelInfo {
2
+ id: string;
3
+ displayName: string;
4
+ }
5
+ /** Models available to the logged-in Cursor CLI on this machine. */
6
+ export declare function listLocalModels(): Promise<ModelInfo[]>;