@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,207 @@
1
+ const QUESTION_TOOL_RE = /^(askuserquestion|askfollowupquestion|askquestion|ask_user|ask_followup_question|ask_user_question|clarify|question|userinput|getinput)/i;
2
+ /** Whether a tool name represents an agent asking the user clarifying questions. */
3
+ export function isQuestionTool(name) {
4
+ return QUESTION_TOOL_RE.test(name.replace(/ToolCall$/i, ""));
5
+ }
6
+ /**
7
+ * Parse structured clarifying questions from tool input/arguments.
8
+ * Supports Claude Code's AskUserQuestion schema as well as general key/value formats.
9
+ */
10
+ export function parseQuestionToolArgs(args) {
11
+ if (!args || typeof args !== "object")
12
+ return [];
13
+ const raw = args;
14
+ // Case 1: questions array (Claude Code / Agent SDK standard)
15
+ if (Array.isArray(raw.questions)) {
16
+ const list = [];
17
+ for (let i = 0; i < raw.questions.length; i++) {
18
+ const q = raw.questions[i];
19
+ if (typeof q === "string" && q.trim()) {
20
+ list.push({ id: `q-${i + 1}`, question: q.trim() });
21
+ }
22
+ else if (q && typeof q === "object") {
23
+ const item = q;
24
+ const questionText = String(item.question ?? item.prompt ?? item.text ?? item.title ?? "").trim();
25
+ if (!questionText)
26
+ continue;
27
+ const header = typeof item.header === "string" ? item.header.trim() : undefined;
28
+ const multiSelect = Boolean(item.multiSelect);
29
+ let options;
30
+ if (Array.isArray(item.options)) {
31
+ options = item.options
32
+ .map((opt) => {
33
+ if (typeof opt === "string" && opt.trim()) {
34
+ return { label: opt.trim() };
35
+ }
36
+ if (opt && typeof opt === "object") {
37
+ const o = opt;
38
+ const label = String(o.label ?? o.text ?? o.title ?? "").trim();
39
+ const description = typeof o.description === "string"
40
+ ? o.description.trim()
41
+ : undefined;
42
+ if (label)
43
+ return { label, description };
44
+ }
45
+ return null;
46
+ })
47
+ .filter((o) => o !== null);
48
+ }
49
+ list.push({
50
+ id: String(item.id || `q-${i + 1}`),
51
+ question: questionText,
52
+ header,
53
+ options: options && options.length ? options : undefined,
54
+ multiSelect,
55
+ });
56
+ }
57
+ }
58
+ if (list.length)
59
+ return list;
60
+ }
61
+ // Case 2: single question object
62
+ const singleText = String(raw.question ?? raw.prompt ?? raw.query ?? raw.message ?? "").trim();
63
+ if (singleText) {
64
+ let options;
65
+ if (Array.isArray(raw.options)) {
66
+ options = raw.options
67
+ .map((opt) => {
68
+ if (typeof opt === "string" && opt.trim()) {
69
+ return { label: opt.trim() };
70
+ }
71
+ if (opt && typeof opt === "object") {
72
+ const o = opt;
73
+ const label = String(o.label ?? o.text ?? o.title ?? "").trim();
74
+ const description = typeof o.description === "string" ? o.description.trim() : undefined;
75
+ if (label)
76
+ return { label, description };
77
+ }
78
+ return null;
79
+ })
80
+ .filter((o) => o !== null);
81
+ }
82
+ return [
83
+ {
84
+ id: "q-1",
85
+ question: singleText,
86
+ header: typeof raw.header === "string" ? raw.header.trim() : undefined,
87
+ options: options && options.length ? options : undefined,
88
+ multiSelect: Boolean(raw.multiSelect),
89
+ },
90
+ ];
91
+ }
92
+ return [];
93
+ }
94
+ const CLARIFYING_PROSE_CUES = [
95
+ /\bclarifying questions?\b/i,
96
+ /\ba few questions before\b/i,
97
+ /\bquestions before I (?:proceed|start|continue|make|implement)\b/i,
98
+ /\bbefore (?:we|I) (?:proceed|start|continue)\b/i,
99
+ /\bcould you (?:please )?clarify\b/i,
100
+ /\bplease clarify\b/i,
101
+ /\bwhich (?:option|approach|alternative) would you prefer\b/i,
102
+ /\bdo you prefer\b/i,
103
+ /\bwhich do you prefer\b/i,
104
+ /\bto help me proceed\b/i,
105
+ ];
106
+ /**
107
+ * Detects if an assistant response contains clarifying questions in prose/markdown.
108
+ * Returns structured questions with possible options, or null if not a clarifying message.
109
+ */
110
+ export function detectClarifyingQuestions(content) {
111
+ if (!content || !content.trim())
112
+ return null;
113
+ const trimmed = content.trim();
114
+ const hasCue = CLARIFYING_PROSE_CUES.some((cue) => cue.test(trimmed));
115
+ const hasQuestionMark = trimmed.includes("?");
116
+ if (!hasCue && !hasQuestionMark)
117
+ return null;
118
+ // Split into lines to extract numbered or bulleted questions
119
+ const lines = trimmed.split("\n");
120
+ const questions = [];
121
+ let currentQuestion = null;
122
+ let inQuestionsSection = !hasCue; // if no specific cue word but looks like questions
123
+ for (let i = 0; i < lines.length; i++) {
124
+ const line = lines[i].trim();
125
+ if (!line)
126
+ continue;
127
+ // Check if line indicates start of questions section
128
+ if (CLARIFYING_PROSE_CUES.some((cue) => cue.test(line))) {
129
+ inQuestionsSection = true;
130
+ continue;
131
+ }
132
+ // Match numbered list: "1. Question text?" or "1) Question text?"
133
+ const numberedMatch = line.match(/^(\d+)[\.\)]\s+(.+)$/);
134
+ // Match bullet list: "- Question text?" or "* Question text?"
135
+ const bulletMatch = line.match(/^[-*•]\s+(.+)$/);
136
+ if (numberedMatch) {
137
+ const text = numberedMatch[2].trim();
138
+ if (text.includes("?") || inQuestionsSection) {
139
+ if (currentQuestion)
140
+ questions.push(currentQuestion);
141
+ currentQuestion = {
142
+ id: `q-${questions.length + 1}`,
143
+ question: text.replace(/^\*\*|\*\*$/g, ""),
144
+ options: [],
145
+ };
146
+ continue;
147
+ }
148
+ }
149
+ else if (bulletMatch) {
150
+ const text = bulletMatch[1].trim();
151
+ // If we are currently inside a question, check if this bullet is an option (e.g. "- Option A")
152
+ if (currentQuestion && !text.endsWith("?")) {
153
+ const optLabel = text.replace(/^\[[ x]\]\s*/i, "").replace(/^\*\*|\*\*$/g, "").trim();
154
+ if (optLabel && optLabel.length < 80) {
155
+ if (!currentQuestion.options)
156
+ currentQuestion.options = [];
157
+ currentQuestion.options.push({ label: optLabel });
158
+ continue;
159
+ }
160
+ }
161
+ if (text.includes("?") || inQuestionsSection) {
162
+ if (currentQuestion)
163
+ questions.push(currentQuestion);
164
+ currentQuestion = {
165
+ id: `q-${questions.length + 1}`,
166
+ question: text.replace(/^\*\*|\*\*$/g, ""),
167
+ options: [],
168
+ };
169
+ continue;
170
+ }
171
+ }
172
+ else if (line.endsWith("?") && inQuestionsSection && line.length < 200) {
173
+ if (currentQuestion)
174
+ questions.push(currentQuestion);
175
+ currentQuestion = {
176
+ id: `q-${questions.length + 1}`,
177
+ question: line.replace(/^\*\*|\*\*$/g, ""),
178
+ options: [],
179
+ };
180
+ }
181
+ }
182
+ if (currentQuestion) {
183
+ questions.push(currentQuestion);
184
+ }
185
+ // Clean up options
186
+ const cleanList = questions
187
+ .filter((q) => q.question.trim().length > 0)
188
+ .map((q) => ({
189
+ ...q,
190
+ options: q.options && q.options.length ? q.options : undefined,
191
+ }));
192
+ if (cleanList.length > 0 && hasCue) {
193
+ return cleanList;
194
+ }
195
+ // If cue was present but no list extracted, and trimmed ends with ?, use entire closing sentence
196
+ if (hasCue && cleanList.length === 0 && hasQuestionMark) {
197
+ const sentences = trimmed.split(/(?<=[.?!])\s+/);
198
+ const qSentences = sentences.filter((s) => s.trim().endsWith("?"));
199
+ if (qSentences.length > 0 && qSentences.length <= 3) {
200
+ return qSentences.map((q, idx) => ({
201
+ id: `q-${idx + 1}`,
202
+ question: q.trim(),
203
+ }));
204
+ }
205
+ }
206
+ return cleanList.length > 0 ? cleanList : null;
207
+ }
@@ -0,0 +1,10 @@
1
+ import type { ModelInfo } from "./events.js";
2
+ /**
3
+ * Claude Code `--model` aliases. These always track the current generation —
4
+ * do not hardcode versioned ids here. The room page merges this list with
5
+ * Anthropic's live `/v1/models` catalog (and the local worker when available).
6
+ */
7
+ export declare const CLAUDE_MODELS: ModelInfo[];
8
+ /** Leave `--model` unset so Claude Code picks its current default. */
9
+ export declare const DEFAULT_CLAUDE_MODEL = "auto";
10
+ export declare function isClaudeModelId(id: string): boolean;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Claude Code `--model` aliases. These always track the current generation —
3
+ * do not hardcode versioned ids here. The room page merges this list with
4
+ * Anthropic's live `/v1/models` catalog (and the local worker when available).
5
+ */
6
+ export const CLAUDE_MODELS = [
7
+ {
8
+ id: "auto",
9
+ displayName: "Auto",
10
+ description: "Claude Code default — omit --model and use the CLI's latest recommended model",
11
+ },
12
+ {
13
+ id: "sonnet",
14
+ displayName: "Sonnet",
15
+ description: "Latest Sonnet alias — daily driver, tracks the current generation",
16
+ },
17
+ {
18
+ id: "opus",
19
+ displayName: "Opus",
20
+ description: "Latest Opus alias — strongest model for autonomous coding",
21
+ },
22
+ {
23
+ id: "haiku",
24
+ displayName: "Haiku",
25
+ description: "Latest Haiku alias — fastest / cheapest for small edits",
26
+ },
27
+ {
28
+ id: "fable",
29
+ displayName: "Fable",
30
+ description: "Latest Fable alias — long autonomous sessions",
31
+ },
32
+ ];
33
+ /** Leave `--model` unset so Claude Code picks its current default. */
34
+ export const DEFAULT_CLAUDE_MODEL = "auto";
35
+ const CLAUDE_ALIASES = new Set([
36
+ "auto",
37
+ "sonnet",
38
+ "opus",
39
+ "haiku",
40
+ "fable",
41
+ "sonnet[1m]",
42
+ "opus[1m]",
43
+ "opusplan",
44
+ "best",
45
+ ]);
46
+ export function isClaudeModelId(id) {
47
+ if (CLAUDE_ALIASES.has(id))
48
+ return true;
49
+ if (CLAUDE_MODELS.some((m) => m.id === id))
50
+ return true;
51
+ return id.startsWith("claude-");
52
+ }
@@ -0,0 +1,21 @@
1
+ import type { AgentBackendKind } from "./backends/types.js";
2
+ import type { ModelInfo } from "./events.js";
3
+ /** Agents that can run headless in the cloud (issues, swarms, automations). */
4
+ export declare const CLOUD_BACKENDS: readonly AgentBackendKind[];
5
+ export interface CloudBackendReadiness {
6
+ ready: boolean;
7
+ /** Why it can't run yet, phrased as what to do about it. */
8
+ reason?: string;
9
+ /** How it authenticates when ready. */
10
+ auth?: "api_key" | "subscription" | "free";
11
+ }
12
+ export interface CloudBackendOption extends CloudBackendReadiness {
13
+ backend: AgentBackendKind;
14
+ label: string;
15
+ models: ModelInfo[];
16
+ defaultModel: string;
17
+ }
18
+ export declare function cloudBackendName(backend: AgentBackendKind | null | undefined): string;
19
+ /** Provider id for ModelProviderLogo. */
20
+ export declare function cloudBackendLogo(backend: AgentBackendKind | null | undefined): "cursor" | "claude" | "codex" | "opencode";
21
+ export declare function parseCloudBackend(value: unknown): AgentBackendKind | null;
@@ -0,0 +1,29 @@
1
+ /** Agents that can run headless in the cloud (issues, swarms, automations). */
2
+ export const CLOUD_BACKENDS = ["cursor", "claude-code", "codex", "opencode"];
3
+ export function cloudBackendName(backend) {
4
+ if (backend === "claude-code")
5
+ return "Claude Code";
6
+ if (backend === "codex")
7
+ return "Codex";
8
+ if (backend === "opencode")
9
+ return "OpenCode";
10
+ return "Cursor";
11
+ }
12
+ /** Provider id for ModelProviderLogo. */
13
+ export function cloudBackendLogo(backend) {
14
+ if (backend === "claude-code")
15
+ return "claude";
16
+ if (backend === "codex")
17
+ return "codex";
18
+ if (backend === "opencode")
19
+ return "opencode";
20
+ return "cursor";
21
+ }
22
+ export function parseCloudBackend(value) {
23
+ return value === "cursor" ||
24
+ value === "claude-code" ||
25
+ value === "codex" ||
26
+ value === "opencode"
27
+ ? value
28
+ : null;
29
+ }
@@ -0,0 +1,10 @@
1
+ import type { ModelInfo } from "./events.js";
2
+ /**
3
+ * Codex CLI fallback `--model` choices.
4
+ * Do not hardcode versioned GPT ids — the room page fetches the live catalog
5
+ * from `codex debug models` and/or OpenAI `/v1/models`.
6
+ */
7
+ export declare const CODEX_MODELS: ModelInfo[];
8
+ /** Leave `--model` unset so Codex picks its current default. */
9
+ export declare const DEFAULT_CODEX_MODEL = "auto";
10
+ export declare function isCodexModelId(id: string): boolean;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Codex CLI fallback `--model` choices.
3
+ * Do not hardcode versioned GPT ids — the room page fetches the live catalog
4
+ * from `codex debug models` and/or OpenAI `/v1/models`.
5
+ */
6
+ export const CODEX_MODELS = [
7
+ {
8
+ id: "auto",
9
+ displayName: "Auto",
10
+ description: "Codex CLI default — omit --model and use the latest recommended model",
11
+ },
12
+ ];
13
+ /** Leave `--model` unset so Codex picks its current default. */
14
+ export const DEFAULT_CODEX_MODEL = "auto";
15
+ export function isCodexModelId(id) {
16
+ if (id === "auto")
17
+ return true;
18
+ if (CODEX_MODELS.some((m) => m.id === id))
19
+ return true;
20
+ return (id.startsWith("gpt-") ||
21
+ id.includes("codex") ||
22
+ id === "o1" ||
23
+ id === "o3" ||
24
+ id === "o4-mini" ||
25
+ id.startsWith("o1-") ||
26
+ id.startsWith("o3-") ||
27
+ id.startsWith("o4-"));
28
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentBackendKind } from "./backends/types.js";
2
+ export declare function contextWindowForModel(_backend: AgentBackendKind | string | null | undefined, modelId: string | null | undefined): number | null;
@@ -0,0 +1,30 @@
1
+ const K = 1000;
2
+ /**
3
+ * Context windows for models where the value is stable enough to show a
4
+ * percentage. Unknown models intentionally return null: the composer then
5
+ * shows the raw context token count instead of inventing a denominator.
6
+ */
7
+ const RULES = [
8
+ { match: /gemini-.*pro/i, tokens: 1_000 * K },
9
+ { match: /gemini/i, tokens: 1_000 * K },
10
+ { match: /gpt-4\.1/i, tokens: 1_000 * K },
11
+ { match: /gpt-4o/i, tokens: 128 * K },
12
+ { match: /gpt-5/i, tokens: 400 * K },
13
+ { match: /o[134](-|$)/i, tokens: 200 * K },
14
+ { match: /claude/i, tokens: 200 * K },
15
+ { match: /deepseek/i, tokens: 128 * K },
16
+ { match: /qwen/i, tokens: 128 * K },
17
+ { match: /kimi|moonshot/i, tokens: 256 * K },
18
+ { match: /minimax/i, tokens: 200 * K },
19
+ { match: /grok/i, tokens: 256 * K },
20
+ ];
21
+ export function contextWindowForModel(_backend, modelId) {
22
+ const id = (modelId || "").trim();
23
+ if (!id || /(^|[/:])auto$|^auto$/i.test(id))
24
+ return null;
25
+ for (const rule of RULES) {
26
+ if (rule.match.test(id))
27
+ return rule.tokens;
28
+ }
29
+ return null;
30
+ }
@@ -0,0 +1,14 @@
1
+ /** HTTP MCP server Steer attaches to a local Cursor CLI run. */
2
+ export interface SteerMcpHttpServer {
3
+ url: string;
4
+ headers?: Record<string, string>;
5
+ }
6
+ /**
7
+ * Merge Steer MCP servers into a Cursor `.cursor/mcp.json` document.
8
+ * Header values are stored as `${env:...}` references so the file on disk
9
+ * does not contain bearer tokens.
10
+ */
11
+ export declare function cursorMcpEnvConfig(existingRaw: string | null | undefined, servers: Record<string, SteerMcpHttpServer>): {
12
+ json: string;
13
+ env: Record<string, string>;
14
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Merge Steer MCP servers into a Cursor `.cursor/mcp.json` document.
3
+ * Header values are stored as `${env:...}` references so the file on disk
4
+ * does not contain bearer tokens.
5
+ */
6
+ export function cursorMcpEnvConfig(existingRaw, servers) {
7
+ let base = {};
8
+ if (existingRaw?.trim()) {
9
+ try {
10
+ const parsed = JSON.parse(existingRaw);
11
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12
+ base = parsed;
13
+ }
14
+ }
15
+ catch {
16
+ base = {};
17
+ }
18
+ }
19
+ const current = base.mcpServers &&
20
+ typeof base.mcpServers === "object" &&
21
+ !Array.isArray(base.mcpServers)
22
+ ? { ...base.mcpServers }
23
+ : {};
24
+ const env = {};
25
+ let n = 0;
26
+ for (const [name, server] of Object.entries(servers)) {
27
+ const headers = {};
28
+ for (const [key, value] of Object.entries(server.headers || {})) {
29
+ const envName = `STEER_MCP_${n}_${key.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}`.slice(0, 64);
30
+ env[envName] = value;
31
+ headers[key] = `\${env:${envName}}`;
32
+ n += 1;
33
+ }
34
+ current[name] = {
35
+ url: server.url,
36
+ ...(Object.keys(headers).length ? { headers } : {}),
37
+ };
38
+ }
39
+ return {
40
+ json: `${JSON.stringify({ ...base, mcpServers: current }, null, 2)}\n`,
41
+ env,
42
+ };
43
+ }
@@ -0,0 +1,10 @@
1
+ import { type SteerMcpHttpServer } from "./cursorMcpConfig.js";
2
+ /**
3
+ * Write Steer servers into `<repo>/.cursor/mcp.json` for one Cursor CLI run.
4
+ * Cursor has no per-invocation MCP flag; it reads this file. The previous
5
+ * file is restored when `restore` runs. Tokens stay in `env`, not on disk.
6
+ */
7
+ export declare function mountCursorProjectMcp(repoPath: string, servers: Record<string, SteerMcpHttpServer> | undefined): Promise<{
8
+ env: Record<string, string>;
9
+ restore: () => Promise<void>;
10
+ }>;
@@ -0,0 +1,42 @@
1
+ import { mkdir, readFile, unlink, writeFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { cursorMcpEnvConfig, } from "./cursorMcpConfig.js";
4
+ /**
5
+ * Write Steer servers into `<repo>/.cursor/mcp.json` for one Cursor CLI run.
6
+ * Cursor has no per-invocation MCP flag; it reads this file. The previous
7
+ * file is restored when `restore` runs. Tokens stay in `env`, not on disk.
8
+ */
9
+ export async function mountCursorProjectMcp(repoPath, servers) {
10
+ if (!servers || Object.keys(servers).length === 0) {
11
+ return { env: {}, restore: async () => { } };
12
+ }
13
+ const dir = join(repoPath, ".cursor");
14
+ const file = join(dir, "mcp.json");
15
+ let previous = null;
16
+ let existed = false;
17
+ try {
18
+ previous = await readFile(file, "utf8");
19
+ existed = true;
20
+ }
21
+ catch {
22
+ previous = null;
23
+ }
24
+ const { json, env } = cursorMcpEnvConfig(previous, servers);
25
+ await mkdir(dir, { recursive: true });
26
+ await writeFile(file, json);
27
+ return {
28
+ env,
29
+ restore: async () => {
30
+ if (existed && previous != null) {
31
+ await writeFile(file, previous);
32
+ return;
33
+ }
34
+ try {
35
+ await unlink(file);
36
+ }
37
+ catch {
38
+ // already gone
39
+ }
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Docs: lasting, shareable Markdown documents an agent writes for a room
3
+ * (reports, lead lists, comparisons, plans). Unlike uploaded files they render
4
+ * in Eigen, keep a version history, and are updated in place by agents.
5
+ */
6
+ /** Who can open a doc: members of its room (default) or its whole workspace. */
7
+ export type DocVisibility = "room" | "workspace";
8
+ /** Snapshot carried on the chat message that shows the doc card. */
9
+ export interface DocCardInfo {
10
+ id: string;
11
+ title: string;
12
+ summary: string;
13
+ /** Opening Markdown for the card preview. */
14
+ excerpt: string;
15
+ version: number;
16
+ /** "created" for the first card, "updated" when an agent revised it. */
17
+ change: "created" | "updated";
18
+ updatedAt: number;
19
+ }
20
+ export interface DocSummaryInfo {
21
+ id: string;
22
+ roomId: string;
23
+ title: string;
24
+ summary: string;
25
+ version: number;
26
+ visibility: DocVisibility;
27
+ authorLabel: string | null;
28
+ createdAt: number;
29
+ updatedAt: number;
30
+ }
31
+ export interface DocInfo extends DocSummaryInfo {
32
+ content: string;
33
+ roomName: string | null;
34
+ /** Set when this is an automation's tracking doc. */
35
+ automationId?: string | null;
36
+ automationName?: string | null;
37
+ /** Viewer may change who can open it (room owner/editor). */
38
+ canManage: boolean;
39
+ }
40
+ export type DocUpdateMode = "replace" | "append" | "replace_section";
41
+ export declare const DOC_TITLE_MAX = 200;
42
+ export declare const DOC_SUMMARY_MAX = 600;
43
+ export declare const DOC_CONTENT_MAX = 200000;
44
+ /** Opening part of the doc for the chat card, cut at a line break. */
45
+ export declare function docExcerpt(content: string): string;
46
+ /**
47
+ * Replace the section under the heading `section` (any level) up to the next
48
+ * heading of the same or higher level. `replacement` may include its own
49
+ * heading; if it doesn't, the original heading is kept.
50
+ */
51
+ export declare function replaceDocSection(content: string, section: string, replacement: string): string;
52
+ /** New doc content after an agent's update. */
53
+ export declare function applyDocUpdate(current: string, update: {
54
+ mode: DocUpdateMode;
55
+ content: string;
56
+ section?: string;
57
+ }): string;
58
+ /** `messages.docs_json` → doc cards on that message. */
59
+ export declare function parseDocCardsJson(raw: unknown): DocCardInfo[] | undefined;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Docs: lasting, shareable Markdown documents an agent writes for a room
3
+ * (reports, lead lists, comparisons, plans). Unlike uploaded files they render
4
+ * in Eigen, keep a version history, and are updated in place by agents.
5
+ */
6
+ export const DOC_TITLE_MAX = 200;
7
+ export const DOC_SUMMARY_MAX = 600;
8
+ export const DOC_CONTENT_MAX = 200_000;
9
+ const EXCERPT_CHARS = 1_400;
10
+ /** Opening part of the doc for the chat card, cut at a line break. */
11
+ export function docExcerpt(content) {
12
+ const text = content.trim();
13
+ if (text.length <= EXCERPT_CHARS)
14
+ return text;
15
+ const cut = text.lastIndexOf("\n", EXCERPT_CHARS);
16
+ return text.slice(0, cut > EXCERPT_CHARS / 2 ? cut : EXCERPT_CHARS).trimEnd();
17
+ }
18
+ const HEADING_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
19
+ function normalizeHeading(text) {
20
+ return text.replace(/^#+\s*/, "").trim().toLowerCase();
21
+ }
22
+ /**
23
+ * Replace the section under the heading `section` (any level) up to the next
24
+ * heading of the same or higher level. `replacement` may include its own
25
+ * heading; if it doesn't, the original heading is kept.
26
+ */
27
+ export function replaceDocSection(content, section, replacement) {
28
+ const lines = content.split("\n");
29
+ const wanted = normalizeHeading(section);
30
+ let inFence = false;
31
+ let start = -1;
32
+ let level = 0;
33
+ for (let i = 0; i < lines.length; i += 1) {
34
+ if (/^\s*(```|~~~)/.test(lines[i]))
35
+ inFence = !inFence;
36
+ if (inFence)
37
+ continue;
38
+ const match = lines[i].match(HEADING_RE);
39
+ if (match && normalizeHeading(match[2]) === wanted) {
40
+ start = i;
41
+ level = match[1].length;
42
+ break;
43
+ }
44
+ }
45
+ if (start < 0)
46
+ throw new Error(`No section titled "${section}" in this doc`);
47
+ let end = lines.length;
48
+ inFence = false;
49
+ for (let i = start + 1; i < lines.length; i += 1) {
50
+ if (/^\s*(```|~~~)/.test(lines[i]))
51
+ inFence = !inFence;
52
+ if (inFence)
53
+ continue;
54
+ const match = lines[i].match(HEADING_RE);
55
+ if (match && match[1].length <= level) {
56
+ end = i;
57
+ break;
58
+ }
59
+ }
60
+ const body = replacement.trim();
61
+ const hasHeading = HEADING_RE.test(body.split("\n")[0] ?? "");
62
+ const next = hasHeading ? body : `${lines[start]}\n\n${body}`;
63
+ return [...lines.slice(0, start), next, "", ...lines.slice(end)].join("\n").replace(/\n{3,}/g, "\n\n").trim();
64
+ }
65
+ /** New doc content after an agent's update. */
66
+ export function applyDocUpdate(current, update) {
67
+ if (update.mode === "append")
68
+ return `${current.trimEnd()}\n\n${update.content.trim()}`.trim();
69
+ if (update.mode === "replace_section") {
70
+ if (!update.section?.trim())
71
+ throw new Error("section is required for replace_section");
72
+ return replaceDocSection(current, update.section, update.content);
73
+ }
74
+ return update.content.trim();
75
+ }
76
+ /** `messages.docs_json` → doc cards on that message. */
77
+ export function parseDocCardsJson(raw) {
78
+ if (typeof raw !== "string" || !raw)
79
+ return undefined;
80
+ try {
81
+ const parsed = JSON.parse(raw);
82
+ return Array.isArray(parsed) && parsed.length ? parsed : undefined;
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }