@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,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
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Open a native OS folder picker and return an absolute path.
3
+ * Returns null if the user cancels.
4
+ */
5
+ export declare function pickFolder(): Promise<string | null>;
@@ -0,0 +1,65 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import { platform } from "os";
4
+ const execFileAsync = promisify(execFile);
5
+ /**
6
+ * Open a native OS folder picker and return an absolute path.
7
+ * Returns null if the user cancels.
8
+ */
9
+ export async function pickFolder() {
10
+ const os = platform();
11
+ try {
12
+ if (os === "darwin") {
13
+ const { stdout } = await execFileAsync("osascript", [
14
+ "-e",
15
+ 'POSIX path of (choose folder with prompt "Select a repository folder")',
16
+ ]);
17
+ const path = stdout.trim().replace(/\/$/, "");
18
+ return path || null;
19
+ }
20
+ if (os === "win32") {
21
+ const script = [
22
+ "Add-Type -AssemblyName System.Windows.Forms",
23
+ "$d = New-Object System.Windows.Forms.FolderBrowserDialog",
24
+ '$d.Description = "Select a repository folder"',
25
+ "$d.ShowNewFolderButton = $true",
26
+ "if ($d.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $d.SelectedPath }",
27
+ ].join("; ");
28
+ const { stdout } = await execFileAsync("powershell", [
29
+ "-NoProfile",
30
+ "-Command",
31
+ script,
32
+ ]);
33
+ const path = stdout.trim();
34
+ return path || null;
35
+ }
36
+ // Linux: prefer zenity, then kdialog
37
+ try {
38
+ const { stdout } = await execFileAsync("zenity", [
39
+ "--file-selection",
40
+ "--directory",
41
+ "--title=Select a repository folder",
42
+ ]);
43
+ const path = stdout.trim();
44
+ return path || null;
45
+ }
46
+ catch {
47
+ const { stdout } = await execFileAsync("kdialog", [
48
+ "--getexistingdirectory",
49
+ process.env.HOME || "/",
50
+ "--title",
51
+ "Select a repository folder",
52
+ ]);
53
+ const path = stdout.trim();
54
+ return path || null;
55
+ }
56
+ }
57
+ catch (err) {
58
+ const e = err;
59
+ // User cancel: osascript exit 1 / zenity exit 1
60
+ if (e.code === 1 || e.code === "1")
61
+ return null;
62
+ throw new Error(e.message ||
63
+ "Failed to open folder picker. On Linux install zenity or kdialog.");
64
+ }
65
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Drafted actions: an agent proposes integration writes (emails, messages, …)
3
+ * with `steer_draft_actions`; people review, edit, and Send or Discard each one
4
+ * in the chat. Sending runs the tool server-side; the agent never sends them.
5
+ */
6
+ export type ActionDraftStatus = "draft" | "sending" | "sent" | "failed" | "discarded";
7
+ /** Picks the card: email, a social post, a chat message, or every argument as a field. */
8
+ export type ActionDraftKind = "email" | "reddit" | "linkedin" | "x" | "message" | "generic";
9
+ export declare function parseActionDraftKind(value: string): ActionDraftKind;
10
+ export interface ActionDraftInfo {
11
+ id: string;
12
+ kind: ActionDraftKind;
13
+ /** Bundled Integrations Hub tool, e.g. `gmail__GMAIL_SEND_EMAIL`. */
14
+ tool: string;
15
+ /** Hub provider for the logo/label, e.g. `gmail`. */
16
+ provider: string;
17
+ title: string;
18
+ /** Connection the action runs as, e.g. the Gmail account. */
19
+ fromLabel?: string;
20
+ args: Record<string, unknown>;
21
+ status: ActionDraftStatus;
22
+ error?: string;
23
+ decidedByName?: string;
24
+ updatedAt: number;
25
+ }
26
+ export declare const ACTION_DRAFTS_MAX_PER_CALL = 50;
27
+ export declare const ACTION_DRAFT_ARGS_MAX_BYTES: number;
28
+ /** Argument names email tools use for each field (Gmail, Outlook, generic SMTP tools). */
29
+ export declare const EMAIL_FIELD_KEYS: {
30
+ readonly to: readonly ["recipient_email", "to", "to_email", "to_emails", "recipients", "recipient"];
31
+ readonly cc: readonly ["cc", "cc_emails", "cc_recipients"];
32
+ readonly bcc: readonly ["bcc", "bcc_emails", "bcc_recipients"];
33
+ readonly subject: readonly ["subject", "title"];
34
+ readonly body: readonly ["body", "message_body", "html_body", "content", "text"];
35
+ };
36
+ export type EmailField = keyof typeof EMAIL_FIELD_KEYS;
37
+ /** The argument key holding `field`, if the args have one. */
38
+ export declare function emailFieldKey(args: Record<string, unknown>, field: EmailField): string | undefined;
39
+ /** Address lists may be a string or an array; show them as "a, b". */
40
+ export declare function emailFieldText(value: unknown): string;
41
+ /** Write an edited field back in the shape the tool sent it (array stays array). */
42
+ export declare function emailFieldValue(previous: unknown, text: string): unknown;
43
+ /** Argument names social tools use. The first key present in the args wins. */
44
+ export declare const SOCIAL_FIELD_KEYS: {
45
+ readonly reddit: {
46
+ readonly subreddit: readonly ["subreddit", "sr", "subreddit_name"];
47
+ readonly title: readonly ["title"];
48
+ readonly body: readonly ["text", "body", "selftext", "markdown", "md"];
49
+ readonly link: readonly ["url", "link", "link_url"];
50
+ readonly replyTo: readonly ["thing_id", "parent_id", "parent", "post_id"];
51
+ };
52
+ readonly linkedin: {
53
+ readonly body: readonly ["commentary", "text", "comment", "comment_text", "message"];
54
+ readonly visibility: readonly ["visibility", "visibility_enum"];
55
+ readonly link: readonly ["url", "article_url", "link", "link_url"];
56
+ readonly author: readonly ["author", "actor"];
57
+ };
58
+ readonly x: {
59
+ readonly body: readonly ["text", "tweet", "status"];
60
+ readonly replyTo: readonly ["reply_in_reply_to_tweet_id", "in_reply_to_tweet_id", "reply_to"];
61
+ readonly quote: readonly ["quote_tweet_id"];
62
+ };
63
+ };
64
+ export declare const SOCIAL_LIMITS: {
65
+ readonly x: 280;
66
+ readonly linkedin: 3000;
67
+ readonly redditTitle: 300;
68
+ };
69
+ /** The argument key holding one of `keys`, if the args have it. */
70
+ export declare function argKey(args: Record<string, unknown>, keys: readonly string[]): string | undefined;
71
+ /** Reddit, LinkedIn, or X when the call is actually a post or reply. */
72
+ export declare function socialDraftKind(tool: string, args: Record<string, unknown>): "reddit" | "linkedin" | "x" | null;
73
+ export declare function actionDraftKind(tool: string, args: Record<string, unknown>): ActionDraftKind;
74
+ export declare function isActionDraftOpen(status: ActionDraftStatus): boolean;
75
+ /** `messages.action_drafts_json` → drafts shown on that message. */
76
+ export declare function parseActionDraftsJson(raw: unknown): ActionDraftInfo[] | undefined;
77
+ /** Editable text for one argument: strings as-is, address lists as "a, b", anything else as JSON. */
78
+ export declare function draftFieldText(value: unknown): string;
79
+ /**
80
+ * Rebuild the tool arguments from edited field texts, keeping each argument's
81
+ * original type. Throws when a structured (JSON) field no longer parses.
82
+ */
83
+ export declare function applyDraftEdits(original: Record<string, unknown>, edits: Record<string, string>): Record<string, unknown>;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Drafted actions: an agent proposes integration writes (emails, messages, …)
3
+ * with `steer_draft_actions`; people review, edit, and Send or Discard each one
4
+ * in the chat. Sending runs the tool server-side; the agent never sends them.
5
+ */
6
+ const ACTION_DRAFT_KINDS = [
7
+ "email",
8
+ "reddit",
9
+ "linkedin",
10
+ "x",
11
+ "message",
12
+ "generic",
13
+ ];
14
+ export function parseActionDraftKind(value) {
15
+ return ACTION_DRAFT_KINDS.includes(value) ? value : "generic";
16
+ }
17
+ export const ACTION_DRAFTS_MAX_PER_CALL = 50;
18
+ export const ACTION_DRAFT_ARGS_MAX_BYTES = 64 * 1024;
19
+ /** Argument names email tools use for each field (Gmail, Outlook, generic SMTP tools). */
20
+ export const EMAIL_FIELD_KEYS = {
21
+ to: ["recipient_email", "to", "to_email", "to_emails", "recipients", "recipient"],
22
+ cc: ["cc", "cc_emails", "cc_recipients"],
23
+ bcc: ["bcc", "bcc_emails", "bcc_recipients"],
24
+ subject: ["subject", "title"],
25
+ body: ["body", "message_body", "html_body", "content", "text"],
26
+ };
27
+ /** The argument key holding `field`, if the args have one. */
28
+ export function emailFieldKey(args, field) {
29
+ return EMAIL_FIELD_KEYS[field].find((key) => key in args);
30
+ }
31
+ /** Address lists may be a string or an array; show them as "a, b". */
32
+ export function emailFieldText(value) {
33
+ if (Array.isArray(value))
34
+ return value.map((v) => String(v)).join(", ");
35
+ return value == null ? "" : String(value);
36
+ }
37
+ /** Write an edited field back in the shape the tool sent it (array stays array). */
38
+ export function emailFieldValue(previous, text) {
39
+ if (Array.isArray(previous)) {
40
+ return text
41
+ .split(/[,;\n]/)
42
+ .map((part) => part.trim())
43
+ .filter(Boolean);
44
+ }
45
+ return text;
46
+ }
47
+ /** Argument names social tools use. The first key present in the args wins. */
48
+ export const SOCIAL_FIELD_KEYS = {
49
+ reddit: {
50
+ subreddit: ["subreddit", "sr", "subreddit_name"],
51
+ title: ["title"],
52
+ body: ["text", "body", "selftext", "markdown", "md"],
53
+ link: ["url", "link", "link_url"],
54
+ replyTo: ["thing_id", "parent_id", "parent", "post_id"],
55
+ },
56
+ linkedin: {
57
+ body: ["commentary", "text", "comment", "comment_text", "message"],
58
+ visibility: ["visibility", "visibility_enum"],
59
+ link: ["url", "article_url", "link", "link_url"],
60
+ author: ["author", "actor"],
61
+ },
62
+ x: {
63
+ body: ["text", "tweet", "status"],
64
+ replyTo: ["reply_in_reply_to_tweet_id", "in_reply_to_tweet_id", "reply_to"],
65
+ quote: ["quote_tweet_id"],
66
+ },
67
+ };
68
+ export const SOCIAL_LIMITS = { x: 280, linkedin: 3000, redditTitle: 300 };
69
+ /** The argument key holding one of `keys`, if the args have it. */
70
+ export function argKey(args, keys) {
71
+ return keys.find((key) => key in args);
72
+ }
73
+ function toolTokens(tool) {
74
+ return new Set(tool
75
+ .toLowerCase()
76
+ .split(/[^a-z0-9]+/)
77
+ .filter(Boolean));
78
+ }
79
+ /** Reddit, LinkedIn, or X when the call is actually a post or reply. */
80
+ export function socialDraftKind(tool, args) {
81
+ const tokens = toolTokens(tool);
82
+ if (tokens.has("reddit")) {
83
+ return argKey(args, SOCIAL_FIELD_KEYS.reddit.body) || argKey(args, SOCIAL_FIELD_KEYS.reddit.title)
84
+ ? "reddit"
85
+ : null;
86
+ }
87
+ if (tokens.has("linkedin")) {
88
+ return argKey(args, SOCIAL_FIELD_KEYS.linkedin.body) ? "linkedin" : null;
89
+ }
90
+ if (tokens.has("twitter") || tokens.has("tweet") || tokens.has("x")) {
91
+ return argKey(args, SOCIAL_FIELD_KEYS.x.body) ? "x" : null;
92
+ }
93
+ return null;
94
+ }
95
+ export function actionDraftKind(tool, args) {
96
+ const social = socialDraftKind(tool, args);
97
+ if (social)
98
+ return social;
99
+ const name = tool.toLowerCase();
100
+ if (/(mail|email|outlook)/.test(name) && emailFieldKey(args, "to") && emailFieldKey(args, "body")) {
101
+ return "email";
102
+ }
103
+ if (/(message|post|chat|send|reply|comment)/.test(name) && ("text" in args || "message" in args)) {
104
+ return "message";
105
+ }
106
+ return "generic";
107
+ }
108
+ export function isActionDraftOpen(status) {
109
+ return status === "draft" || status === "failed";
110
+ }
111
+ /** `messages.action_drafts_json` → drafts shown on that message. */
112
+ export function parseActionDraftsJson(raw) {
113
+ if (typeof raw !== "string" || !raw)
114
+ return undefined;
115
+ try {
116
+ const parsed = JSON.parse(raw);
117
+ return Array.isArray(parsed) && parsed.length ? parsed : undefined;
118
+ }
119
+ catch {
120
+ return undefined;
121
+ }
122
+ }
123
+ function isStringList(value) {
124
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
125
+ }
126
+ /** Editable text for one argument: strings as-is, address lists as "a, b", anything else as JSON. */
127
+ export function draftFieldText(value) {
128
+ if (value == null)
129
+ return "";
130
+ if (typeof value === "string")
131
+ return value;
132
+ if (isStringList(value))
133
+ return value.join(", ");
134
+ return JSON.stringify(value, null, 2);
135
+ }
136
+ /**
137
+ * Rebuild the tool arguments from edited field texts, keeping each argument's
138
+ * original type. Throws when a structured (JSON) field no longer parses.
139
+ */
140
+ export function applyDraftEdits(original, edits) {
141
+ const next = { ...original };
142
+ for (const [key, text] of Object.entries(edits)) {
143
+ const previous = original[key];
144
+ if (previous == null || typeof previous === "string") {
145
+ next[key] = text;
146
+ }
147
+ else if (isStringList(previous)) {
148
+ next[key] = emailFieldValue(previous, text);
149
+ }
150
+ else if (typeof previous === "number") {
151
+ const parsed = Number(text);
152
+ if (!Number.isFinite(parsed))
153
+ throw new Error(`${key} must be a number`);
154
+ next[key] = parsed;
155
+ }
156
+ else if (typeof previous === "boolean") {
157
+ next[key] = text.trim().toLowerCase() === "true";
158
+ }
159
+ else {
160
+ try {
161
+ next[key] = JSON.parse(text);
162
+ }
163
+ catch {
164
+ throw new Error(`${key} is not valid JSON`);
165
+ }
166
+ }
167
+ }
168
+ return next;
169
+ }
@@ -0,0 +1,49 @@
1
+ /** Paths Cursor cloud agents embed for walkthrough screenshots/videos. */
2
+ export declare function isArtifactPath(path: string): boolean;
3
+ /**
4
+ * Normalize agent-facing paths to the relative form the Cloud Artifacts API accepts
5
+ * (e.g. `artifacts/demo.mp4`). Returns null if the path is not an artifact path.
6
+ */
7
+ export declare function toRelativeArtifactPath(raw: string): string | null;
8
+ export declare function hasArtifactRefs(text: string): boolean;
9
+ /** Unique relative artifact paths referenced in assistant text. */
10
+ export declare function extractArtifactPaths(text: string): string[];
11
+ /** Assistant bubble ids for an agent that still embed unresolved artifact paths. */
12
+ export declare function assistantIdsNeedingArtifactHydration(messages: Array<{
13
+ id: string;
14
+ role: string;
15
+ agentId?: string | null;
16
+ content?: string | null;
17
+ }>, agentId: string): string[];
18
+ /**
19
+ * Rewrite every occurrence of an absolute/relative artifact path to a served URL.
20
+ * Keys in `replacements` must be relative (`artifacts/foo.webp`).
21
+ */
22
+ export declare function rewriteArtifactSrcs(text: string, replacements: Map<string, string>): string;
23
+ export declare function artifactFileName(relPath: string): string;
24
+ /** Paths from `listArtifacts()` — accept relative, absolute, or bare names. */
25
+ export declare function normalizeListedArtifactPath(raw: string): string | null;
26
+ export interface AgentArtifactInfo {
27
+ path: string;
28
+ name: string;
29
+ sizeBytes: number;
30
+ }
31
+ /** Cursor Cloud already writes `/opt/cursor/artifacts` and Steer hydrates those paths. */
32
+ export declare function usesNativeCursorArtifacts(backend: string, runtime: string): boolean;
33
+ /** Exact curl the agent must use. Raw file bytes, not base64 and not JSON. */
34
+ export declare const ARTIFACT_CURL_UPLOAD = "curl -fsS -X POST \"$STEER_UPLOAD_URL\" -H \"Authorization: Bearer $STEER_UPLOAD_TOKEN\" -H \"Content-Type: application/octet-stream\" -H \"X-Artifact-Name: shot.png\" --data-binary @shot.png";
35
+ /**
36
+ * Start-of-turn instructions for Cursor Cloud. Steer downloads
37
+ * `/opt/cursor/artifacts/...` after the run and shows those files to the user.
38
+ * Curl is included because that VM also has STEER_UPLOAD_URL.
39
+ */
40
+ export declare function buildCursorArtifactRules(): string;
41
+ /**
42
+ * Start-of-turn instructions for agents that do not have Cursor Cloud artifacts.
43
+ * Prepended to the prompt Steer sends the agent. With `autoAttach` (room chat),
44
+ * Steer attaches uploads to the agent's reply itself, so the model must not
45
+ * embed them; elsewhere (issue runs) the returned url goes in the reply.
46
+ */
47
+ export declare function buildArtifactUploadRules(opts?: {
48
+ autoAttach?: boolean;
49
+ }): string;
@@ -0,0 +1,169 @@
1
+ /** Paths Cursor cloud agents embed for walkthrough screenshots/videos. */
2
+ const ARTIFACT_ABS_PREFIX = "/opt/cursor/artifacts/";
3
+ const ARTIFACT_REL_PREFIX = "artifacts/";
4
+ /** Match src= on img/video tags, markdown images, and bare absolute artifact paths. */
5
+ const SRC_ATTR_RE = /(?:src\s*=\s*["']([^"']+)["']|!\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\))/gi;
6
+ const BARE_ABS_PATH_RE = /(?:^|[\s"'=(>])(\/opt\/cursor\/artifacts\/[A-Za-z0-9._\-/\u2010-\u2015]+)/g;
7
+ export function isArtifactPath(path) {
8
+ const p = path.trim();
9
+ return (p.startsWith(ARTIFACT_ABS_PREFIX) ||
10
+ p.startsWith(ARTIFACT_REL_PREFIX) ||
11
+ p.startsWith("./artifacts/"));
12
+ }
13
+ /**
14
+ * Normalize agent-facing paths to the relative form the Cloud Artifacts API accepts
15
+ * (e.g. `artifacts/demo.mp4`). Returns null if the path is not an artifact path.
16
+ */
17
+ export function toRelativeArtifactPath(raw) {
18
+ let path = raw.trim();
19
+ if (!path)
20
+ return null;
21
+ try {
22
+ if (/^https?:\/\//i.test(path)) {
23
+ const u = new URL(path);
24
+ path = u.pathname;
25
+ }
26
+ }
27
+ catch {
28
+ // keep raw
29
+ }
30
+ path = path.replace(/\\/g, "/");
31
+ if (path.startsWith(ARTIFACT_ABS_PREFIX)) {
32
+ path = ARTIFACT_REL_PREFIX + path.slice(ARTIFACT_ABS_PREFIX.length);
33
+ }
34
+ else if (path.startsWith("./artifacts/")) {
35
+ path = path.slice(2);
36
+ }
37
+ if (!path.startsWith(ARTIFACT_REL_PREFIX))
38
+ return null;
39
+ const rest = path.slice(ARTIFACT_REL_PREFIX.length);
40
+ if (!rest || rest.includes("..") || rest.startsWith("/"))
41
+ return null;
42
+ return ARTIFACT_REL_PREFIX + rest;
43
+ }
44
+ export function hasArtifactRefs(text) {
45
+ if (!text)
46
+ return false;
47
+ return (text.includes(ARTIFACT_ABS_PREFIX) ||
48
+ text.includes(ARTIFACT_REL_PREFIX) ||
49
+ text.includes("./artifacts/"));
50
+ }
51
+ /** Unique relative artifact paths referenced in assistant text. */
52
+ export function extractArtifactPaths(text) {
53
+ if (!text || !hasArtifactRefs(text))
54
+ return [];
55
+ const found = new Set();
56
+ SRC_ATTR_RE.lastIndex = 0;
57
+ let m;
58
+ while ((m = SRC_ATTR_RE.exec(text)) !== null) {
59
+ const candidate = (m[1] || m[2] || "").trim();
60
+ const rel = toRelativeArtifactPath(candidate);
61
+ if (rel)
62
+ found.add(rel);
63
+ }
64
+ BARE_ABS_PATH_RE.lastIndex = 0;
65
+ while ((m = BARE_ABS_PATH_RE.exec(text)) !== null) {
66
+ const rel = toRelativeArtifactPath(m[1]);
67
+ if (rel)
68
+ found.add(rel);
69
+ }
70
+ return [...found];
71
+ }
72
+ /** Assistant bubble ids for an agent that still embed unresolved artifact paths. */
73
+ export function assistantIdsNeedingArtifactHydration(messages, agentId) {
74
+ return messages
75
+ .filter((m) => m.agentId === agentId &&
76
+ m.role === "assistant" &&
77
+ hasArtifactRefs(m.content || ""))
78
+ .map((m) => m.id);
79
+ }
80
+ /**
81
+ * Rewrite every occurrence of an absolute/relative artifact path to a served URL.
82
+ * Keys in `replacements` must be relative (`artifacts/foo.webp`).
83
+ */
84
+ export function rewriteArtifactSrcs(text, replacements) {
85
+ if (!text || replacements.size === 0)
86
+ return text;
87
+ let out = text;
88
+ for (const [rel, url] of replacements) {
89
+ const name = rel.slice(ARTIFACT_REL_PREFIX.length);
90
+ const variants = [
91
+ ARTIFACT_ABS_PREFIX + name,
92
+ rel,
93
+ "./" + rel,
94
+ ];
95
+ for (const variant of variants) {
96
+ if (!variant || !out.includes(variant))
97
+ continue;
98
+ out = out.split(variant).join(url);
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+ export function artifactFileName(relPath) {
104
+ const base = relPath.split("/").pop() || "artifact";
105
+ return base.slice(0, 200) || "artifact";
106
+ }
107
+ /** Paths from `listArtifacts()` — accept relative, absolute, or bare names. */
108
+ export function normalizeListedArtifactPath(raw) {
109
+ const trimmed = raw.trim().replace(/\\/g, "/");
110
+ if (!trimmed)
111
+ return null;
112
+ const direct = toRelativeArtifactPath(trimmed);
113
+ if (direct)
114
+ return direct;
115
+ const bare = trimmed.replace(/^\/+/, "");
116
+ if (!bare || bare.includes(".."))
117
+ return null;
118
+ return toRelativeArtifactPath(`${ARTIFACT_REL_PREFIX}${bare}`);
119
+ }
120
+ /** Cursor Cloud already writes `/opt/cursor/artifacts` and Steer hydrates those paths. */
121
+ export function usesNativeCursorArtifacts(backend, runtime) {
122
+ return backend === "cursor" && runtime === "cloud";
123
+ }
124
+ /** Exact curl the agent must use. Raw file bytes, not base64 and not JSON. */
125
+ export const ARTIFACT_CURL_UPLOAD = 'curl -fsS -X POST "$STEER_UPLOAD_URL" -H "Authorization: Bearer $STEER_UPLOAD_TOKEN" -H "Content-Type: application/octet-stream" -H "X-Artifact-Name: shot.png" --data-binary @shot.png';
126
+ const ARTIFACT_CURL_RULES = [
127
+ "To upload a file, run this curl. Send the file itself. Do not base64 it, do not wrap it in JSON, and do not put the file contents on the command line:",
128
+ `\`${ARTIFACT_CURL_UPLOAD}\``,
129
+ "Put the filename in the X-Artifact-Name header. Point --data-binary at the file. This works up to 100MB.",
130
+ "To attach that file to a Gmail message, do not paste a link in the body. Use the curl response's artifact.url:",
131
+ "1. googledrive__GOOGLEDRIVE_UPLOAD_FROM_URL with source_url set to artifact.url, plus name and mime_type.",
132
+ "2. googledrive__GOOGLEDRIVE_DOWNLOAD_FILE with the Drive fileId from that upload (or from googledrive__GOOGLEDRIVE_FIND_FILE).",
133
+ "3. gmail__GMAIL_SEND_EMAIL with attachment set to an array of { name, mimetype, s3key }. name and mimetype come from the download. s3key is the path of the download's s3url after the host and before the question mark, including project_scope/. Do not pass the full signed s3url as s3key.",
134
+ "Example: s3url https://storage.example/project_scope/ok_1/files/report.pdf?X-Amz-Algorithm=AWS4 → s3key project_scope/ok_1/files/report.pdf.",
135
+ ];
136
+ /**
137
+ * Start-of-turn instructions for Cursor Cloud. Steer downloads
138
+ * `/opt/cursor/artifacts/...` after the run and shows those files to the user.
139
+ * Curl is included because that VM also has STEER_UPLOAD_URL.
140
+ */
141
+ export function buildCursorArtifactRules() {
142
+ return [
143
+ "<steer_artifacts>",
144
+ "When you create a file the user should look at, move it into `/opt/cursor/artifacts/` (for example `mv shot.webp /opt/cursor/artifacts/shot.webp`). Eigen shows the user the files in that folder.",
145
+ "Do this only when the user needs to see the file and it will not already arrive through GitHub. That means screenshots, screen recordings, mockups, diagrams, PDFs, and other artifacts you generated. Do not move source code, diffs, lockfiles, or anything else that belongs in the pull request.",
146
+ "In your reply, reference the moved file with its absolute path. Use an `<img>` or `<video>` tag for media, or a markdown link for other files. Example: `<img src=\"/opt/cursor/artifacts/shot.webp\" />`.",
147
+ ...ARTIFACT_CURL_RULES,
148
+ "</steer_artifacts>",
149
+ ].join("\n");
150
+ }
151
+ /**
152
+ * Start-of-turn instructions for agents that do not have Cursor Cloud artifacts.
153
+ * Prepended to the prompt Steer sends the agent. With `autoAttach` (room chat),
154
+ * Steer attaches uploads to the agent's reply itself, so the model must not
155
+ * embed them; elsewhere (issue runs) the returned url goes in the reply.
156
+ */
157
+ export function buildArtifactUploadRules(opts = {}) {
158
+ return [
159
+ "<steer_artifacts>",
160
+ "You do not have Cursor Cloud's artifacts folder (`/opt/cursor/artifacts`). When you create a file the user should look at, upload it to Eigen.",
161
+ "Upload a file only when the user needs to see it and it will not already arrive through GitHub. That means screenshots, screen recordings, mockups, diagrams, PDFs, and other artifacts you generated. Do not upload source code, diffs, lockfiles, or anything else that belongs in the pull request.",
162
+ ...ARTIFACT_CURL_RULES,
163
+ opts.autoAttach
164
+ ? "Eigen shows every uploaded file to the user alongside your reply automatically. Mention the file by name in your reply. Do not add `<img>` or `<video>` tags, markdown images, or links for it."
165
+ : "Put the `url` from the upload result in your reply. Use an `<img>` or `<video>` tag for media, or a markdown link for other files.",
166
+ "Do not invent `/opt/cursor/artifacts/...` paths or file URLs.",
167
+ "</steer_artifacts>",
168
+ ].join("\n");
169
+ }
@@ -0,0 +1,18 @@
1
+ /** Display name of a room agent. Kept in sync with @mentions and the sidebar. */
2
+ export declare const AGENT_LABEL_MAX = 80;
3
+ export declare function normalizeAgentLabel(raw: string): string;
4
+ /**
5
+ * Name of the agent created with a new session.
6
+ * Falls back only when the session name is blank.
7
+ */
8
+ export declare function initialAgentLabel(sessionName: string): string;
9
+ export declare function resolveAgentLabel(raw: string, peers: Array<{
10
+ id: string;
11
+ label: string;
12
+ }>, agentId: string): {
13
+ ok: true;
14
+ label: string;
15
+ } | {
16
+ ok: false;
17
+ error: string;
18
+ };