indusagi-coding-agent 0.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 (240) hide show
  1. package/CHANGELOG.md +2249 -0
  2. package/README.md +546 -0
  3. package/dist/cli/args.js +282 -0
  4. package/dist/cli/config-selector.js +30 -0
  5. package/dist/cli/file-processor.js +78 -0
  6. package/dist/cli/list-models.js +91 -0
  7. package/dist/cli/session-picker.js +31 -0
  8. package/dist/cli.js +10 -0
  9. package/dist/config.js +158 -0
  10. package/dist/core/agent-session.js +2097 -0
  11. package/dist/core/auth-storage.js +278 -0
  12. package/dist/core/bash-executor.js +211 -0
  13. package/dist/core/compaction/branch-summarization.js +241 -0
  14. package/dist/core/compaction/compaction.js +606 -0
  15. package/dist/core/compaction/index.js +6 -0
  16. package/dist/core/compaction/utils.js +137 -0
  17. package/dist/core/diagnostics.js +1 -0
  18. package/dist/core/event-bus.js +24 -0
  19. package/dist/core/exec.js +70 -0
  20. package/dist/core/export-html/ansi-to-html.js +248 -0
  21. package/dist/core/export-html/index.js +221 -0
  22. package/dist/core/export-html/template.css +905 -0
  23. package/dist/core/export-html/template.html +54 -0
  24. package/dist/core/export-html/template.js +1549 -0
  25. package/dist/core/export-html/tool-renderer.js +56 -0
  26. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  27. package/dist/core/export-html/vendor/marked.min.js +6 -0
  28. package/dist/core/extensions/index.js +8 -0
  29. package/dist/core/extensions/loader.js +395 -0
  30. package/dist/core/extensions/runner.js +499 -0
  31. package/dist/core/extensions/types.js +31 -0
  32. package/dist/core/extensions/wrapper.js +101 -0
  33. package/dist/core/footer-data-provider.js +133 -0
  34. package/dist/core/index.js +8 -0
  35. package/dist/core/keybindings.js +140 -0
  36. package/dist/core/messages.js +122 -0
  37. package/dist/core/model-registry.js +454 -0
  38. package/dist/core/model-resolver.js +309 -0
  39. package/dist/core/package-manager.js +1142 -0
  40. package/dist/core/prompt-templates.js +250 -0
  41. package/dist/core/resource-loader.js +569 -0
  42. package/dist/core/sdk.js +225 -0
  43. package/dist/core/session-manager.js +1078 -0
  44. package/dist/core/settings-manager.js +430 -0
  45. package/dist/core/skills.js +339 -0
  46. package/dist/core/system-prompt.js +136 -0
  47. package/dist/core/timings.js +24 -0
  48. package/dist/core/tools/bash.js +226 -0
  49. package/dist/core/tools/edit-diff.js +242 -0
  50. package/dist/core/tools/edit.js +145 -0
  51. package/dist/core/tools/find.js +205 -0
  52. package/dist/core/tools/grep.js +238 -0
  53. package/dist/core/tools/index.js +60 -0
  54. package/dist/core/tools/ls.js +117 -0
  55. package/dist/core/tools/path-utils.js +52 -0
  56. package/dist/core/tools/read.js +165 -0
  57. package/dist/core/tools/truncate.js +204 -0
  58. package/dist/core/tools/write.js +77 -0
  59. package/dist/index.js +41 -0
  60. package/dist/main.js +565 -0
  61. package/dist/migrations.js +260 -0
  62. package/dist/modes/index.js +7 -0
  63. package/dist/modes/interactive/components/armin.js +328 -0
  64. package/dist/modes/interactive/components/assistant-message.js +86 -0
  65. package/dist/modes/interactive/components/bash-execution.js +155 -0
  66. package/dist/modes/interactive/components/bordered-loader.js +47 -0
  67. package/dist/modes/interactive/components/branch-summary-message.js +41 -0
  68. package/dist/modes/interactive/components/compaction-summary-message.js +42 -0
  69. package/dist/modes/interactive/components/config-selector.js +458 -0
  70. package/dist/modes/interactive/components/countdown-timer.js +27 -0
  71. package/dist/modes/interactive/components/custom-editor.js +61 -0
  72. package/dist/modes/interactive/components/custom-message.js +80 -0
  73. package/dist/modes/interactive/components/diff.js +132 -0
  74. package/dist/modes/interactive/components/dynamic-border.js +19 -0
  75. package/dist/modes/interactive/components/extension-editor.js +96 -0
  76. package/dist/modes/interactive/components/extension-input.js +54 -0
  77. package/dist/modes/interactive/components/extension-selector.js +70 -0
  78. package/dist/modes/interactive/components/footer.js +213 -0
  79. package/dist/modes/interactive/components/index.js +31 -0
  80. package/dist/modes/interactive/components/keybinding-hints.js +60 -0
  81. package/dist/modes/interactive/components/login-dialog.js +138 -0
  82. package/dist/modes/interactive/components/model-selector.js +253 -0
  83. package/dist/modes/interactive/components/oauth-selector.js +91 -0
  84. package/dist/modes/interactive/components/scoped-models-selector.js +262 -0
  85. package/dist/modes/interactive/components/session-selector-search.js +145 -0
  86. package/dist/modes/interactive/components/session-selector.js +698 -0
  87. package/dist/modes/interactive/components/settings-selector.js +250 -0
  88. package/dist/modes/interactive/components/show-images-selector.js +33 -0
  89. package/dist/modes/interactive/components/skill-invocation-message.js +44 -0
  90. package/dist/modes/interactive/components/theme-selector.js +43 -0
  91. package/dist/modes/interactive/components/thinking-selector.js +45 -0
  92. package/dist/modes/interactive/components/tool-execution.js +608 -0
  93. package/dist/modes/interactive/components/tree-selector.js +892 -0
  94. package/dist/modes/interactive/components/user-message-selector.js +109 -0
  95. package/dist/modes/interactive/components/user-message.js +15 -0
  96. package/dist/modes/interactive/components/visual-truncate.js +32 -0
  97. package/dist/modes/interactive/interactive-mode.js +3576 -0
  98. package/dist/modes/interactive/theme/dark.json +85 -0
  99. package/dist/modes/interactive/theme/light.json +84 -0
  100. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  101. package/dist/modes/interactive/theme/theme.js +938 -0
  102. package/dist/modes/print-mode.js +96 -0
  103. package/dist/modes/rpc/rpc-client.js +390 -0
  104. package/dist/modes/rpc/rpc-mode.js +448 -0
  105. package/dist/modes/rpc/rpc-types.js +7 -0
  106. package/dist/utils/changelog.js +86 -0
  107. package/dist/utils/clipboard-image.js +116 -0
  108. package/dist/utils/clipboard.js +58 -0
  109. package/dist/utils/frontmatter.js +25 -0
  110. package/dist/utils/git.js +5 -0
  111. package/dist/utils/image-convert.js +34 -0
  112. package/dist/utils/image-resize.js +180 -0
  113. package/dist/utils/mime.js +25 -0
  114. package/dist/utils/photon.js +120 -0
  115. package/dist/utils/shell.js +164 -0
  116. package/dist/utils/sleep.js +16 -0
  117. package/dist/utils/tools-manager.js +186 -0
  118. package/docs/compaction.md +390 -0
  119. package/docs/custom-provider.md +538 -0
  120. package/docs/development.md +69 -0
  121. package/docs/extensions.md +1733 -0
  122. package/docs/images/doom-extension.png +0 -0
  123. package/docs/images/interactive-mode.png +0 -0
  124. package/docs/images/tree-view.png +0 -0
  125. package/docs/json.md +79 -0
  126. package/docs/keybindings.md +162 -0
  127. package/docs/models.md +193 -0
  128. package/docs/packages.md +163 -0
  129. package/docs/prompt-templates.md +67 -0
  130. package/docs/providers.md +147 -0
  131. package/docs/rpc.md +1048 -0
  132. package/docs/sdk.md +957 -0
  133. package/docs/session.md +412 -0
  134. package/docs/settings.md +216 -0
  135. package/docs/shell-aliases.md +13 -0
  136. package/docs/skills.md +226 -0
  137. package/docs/terminal-setup.md +65 -0
  138. package/docs/themes.md +295 -0
  139. package/docs/tree.md +219 -0
  140. package/docs/tui.md +887 -0
  141. package/docs/windows.md +17 -0
  142. package/examples/README.md +25 -0
  143. package/examples/extensions/README.md +192 -0
  144. package/examples/extensions/antigravity-image-gen.ts +414 -0
  145. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  146. package/examples/extensions/bookmark.ts +50 -0
  147. package/examples/extensions/claude-rules.ts +86 -0
  148. package/examples/extensions/confirm-destructive.ts +59 -0
  149. package/examples/extensions/custom-compaction.ts +115 -0
  150. package/examples/extensions/custom-footer.ts +65 -0
  151. package/examples/extensions/custom-header.ts +73 -0
  152. package/examples/extensions/custom-provider-anthropic/index.ts +605 -0
  153. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  154. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  155. package/examples/extensions/custom-provider-gitlab-duo/index.ts +350 -0
  156. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  157. package/examples/extensions/custom-provider-gitlab-duo/test.ts +83 -0
  158. package/examples/extensions/dirty-repo-guard.ts +56 -0
  159. package/examples/extensions/doom-overlay/README.md +46 -0
  160. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  161. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  162. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  163. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  164. package/examples/extensions/doom-overlay/doom-component.ts +133 -0
  165. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  166. package/examples/extensions/doom-overlay/doom-keys.ts +105 -0
  167. package/examples/extensions/doom-overlay/index.ts +74 -0
  168. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  169. package/examples/extensions/event-bus.ts +43 -0
  170. package/examples/extensions/file-trigger.ts +41 -0
  171. package/examples/extensions/git-checkpoint.ts +53 -0
  172. package/examples/extensions/handoff.ts +151 -0
  173. package/examples/extensions/hello.ts +25 -0
  174. package/examples/extensions/inline-bash.ts +94 -0
  175. package/examples/extensions/input-transform.ts +43 -0
  176. package/examples/extensions/interactive-shell.ts +196 -0
  177. package/examples/extensions/mac-system-theme.ts +47 -0
  178. package/examples/extensions/message-renderer.ts +60 -0
  179. package/examples/extensions/modal-editor.ts +86 -0
  180. package/examples/extensions/model-status.ts +31 -0
  181. package/examples/extensions/notify.ts +25 -0
  182. package/examples/extensions/overlay-qa-tests.ts +882 -0
  183. package/examples/extensions/overlay-test.ts +151 -0
  184. package/examples/extensions/permission-gate.ts +34 -0
  185. package/examples/extensions/pirate.ts +47 -0
  186. package/examples/extensions/plan-mode/README.md +65 -0
  187. package/examples/extensions/plan-mode/index.ts +341 -0
  188. package/examples/extensions/plan-mode/utils.ts +168 -0
  189. package/examples/extensions/preset.ts +399 -0
  190. package/examples/extensions/protected-paths.ts +30 -0
  191. package/examples/extensions/qna.ts +120 -0
  192. package/examples/extensions/question.ts +265 -0
  193. package/examples/extensions/questionnaire.ts +428 -0
  194. package/examples/extensions/rainbow-editor.ts +88 -0
  195. package/examples/extensions/sandbox/index.ts +318 -0
  196. package/examples/extensions/sandbox/package-lock.json +92 -0
  197. package/examples/extensions/sandbox/package.json +19 -0
  198. package/examples/extensions/send-user-message.ts +97 -0
  199. package/examples/extensions/session-name.ts +27 -0
  200. package/examples/extensions/shutdown-command.ts +63 -0
  201. package/examples/extensions/snake.ts +344 -0
  202. package/examples/extensions/space-invaders.ts +561 -0
  203. package/examples/extensions/ssh.ts +220 -0
  204. package/examples/extensions/status-line.ts +40 -0
  205. package/examples/extensions/subagent/README.md +172 -0
  206. package/examples/extensions/subagent/agents/planner.md +37 -0
  207. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  208. package/examples/extensions/subagent/agents/scout.md +50 -0
  209. package/examples/extensions/subagent/agents/worker.md +24 -0
  210. package/examples/extensions/subagent/agents.ts +127 -0
  211. package/examples/extensions/subagent/index.ts +964 -0
  212. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  213. package/examples/extensions/subagent/prompts/implement.md +10 -0
  214. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  215. package/examples/extensions/summarize.ts +196 -0
  216. package/examples/extensions/timed-confirm.ts +70 -0
  217. package/examples/extensions/todo.ts +300 -0
  218. package/examples/extensions/tool-override.ts +144 -0
  219. package/examples/extensions/tools.ts +147 -0
  220. package/examples/extensions/trigger-compact.ts +40 -0
  221. package/examples/extensions/truncated-tool.ts +193 -0
  222. package/examples/extensions/widget-placement.ts +17 -0
  223. package/examples/extensions/with-deps/index.ts +36 -0
  224. package/examples/extensions/with-deps/package-lock.json +31 -0
  225. package/examples/extensions/with-deps/package.json +22 -0
  226. package/examples/sdk/01-minimal.ts +22 -0
  227. package/examples/sdk/02-custom-model.ts +50 -0
  228. package/examples/sdk/03-custom-prompt.ts +55 -0
  229. package/examples/sdk/04-skills.ts +46 -0
  230. package/examples/sdk/05-tools.ts +56 -0
  231. package/examples/sdk/06-extensions.ts +88 -0
  232. package/examples/sdk/07-context-files.ts +40 -0
  233. package/examples/sdk/08-prompt-templates.ts +47 -0
  234. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  235. package/examples/sdk/10-settings.ts +38 -0
  236. package/examples/sdk/11-sessions.ts +48 -0
  237. package/examples/sdk/12-full-control.ts +82 -0
  238. package/examples/sdk/13-codex-oauth.ts +37 -0
  239. package/examples/sdk/README.md +144 -0
  240. package/package.json +85 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * One-time migrations that run on startup.
3
+ */
4
+ import chalk from "chalk";
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.js";
8
+ const MIGRATION_GUIDE_URL = "https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
9
+ const EXTENSIONS_DOC_URL = "https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/docs/extensions.md";
10
+ /**
11
+ * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
12
+ *
13
+ * @returns Array of provider names that were migrated
14
+ */
15
+ export function migrateAuthToAuthJson() {
16
+ const agentDir = getAgentDir();
17
+ const authPath = join(agentDir, "auth.json");
18
+ const oauthPath = join(agentDir, "oauth.json");
19
+ const settingsPath = join(agentDir, "settings.json");
20
+ // Skip if auth.json already exists
21
+ if (existsSync(authPath))
22
+ return [];
23
+ const migrated = {};
24
+ const providers = [];
25
+ // Migrate oauth.json
26
+ if (existsSync(oauthPath)) {
27
+ try {
28
+ const oauth = JSON.parse(readFileSync(oauthPath, "utf-8"));
29
+ for (const [provider, cred] of Object.entries(oauth)) {
30
+ migrated[provider] = { type: "oauth", ...cred };
31
+ providers.push(provider);
32
+ }
33
+ renameSync(oauthPath, `${oauthPath}.migrated`);
34
+ }
35
+ catch {
36
+ // Skip on error
37
+ }
38
+ }
39
+ // Migrate settings.json apiKeys
40
+ if (existsSync(settingsPath)) {
41
+ try {
42
+ const content = readFileSync(settingsPath, "utf-8");
43
+ const settings = JSON.parse(content);
44
+ if (settings.apiKeys && typeof settings.apiKeys === "object") {
45
+ for (const [provider, key] of Object.entries(settings.apiKeys)) {
46
+ if (!migrated[provider] && typeof key === "string") {
47
+ migrated[provider] = { type: "api_key", key };
48
+ providers.push(provider);
49
+ }
50
+ }
51
+ delete settings.apiKeys;
52
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
53
+ }
54
+ }
55
+ catch {
56
+ // Skip on error
57
+ }
58
+ }
59
+ if (Object.keys(migrated).length > 0) {
60
+ mkdirSync(dirname(authPath), { recursive: true });
61
+ writeFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });
62
+ }
63
+ return providers;
64
+ }
65
+ /**
66
+ * Migrate sessions from ~/.indusagi/agent/*.jsonl to proper session directories.
67
+ *
68
+ * Bug in v0.30.0: Sessions were saved to ~/.indusagi/agent/ instead of
69
+ * ~/.indusagi/agent/sessions/<encoded-cwd>/. This migration moves them
70
+ * to the correct location based on the cwd in their session header.
71
+ *
72
+ * See: https://github.com/badlogic/indusagi-mono/issues/320
73
+ */
74
+ export function migrateSessionsFromAgentRoot() {
75
+ const agentDir = getAgentDir();
76
+ // Find all .jsonl files directly in agentDir (not in subdirectories)
77
+ let files;
78
+ try {
79
+ files = readdirSync(agentDir)
80
+ .filter((f) => f.endsWith(".jsonl"))
81
+ .map((f) => join(agentDir, f));
82
+ }
83
+ catch {
84
+ return;
85
+ }
86
+ if (files.length === 0)
87
+ return;
88
+ for (const file of files) {
89
+ try {
90
+ // Read first line to get session header
91
+ const content = readFileSync(file, "utf8");
92
+ const firstLine = content.split("\n")[0];
93
+ if (!firstLine?.trim())
94
+ continue;
95
+ const header = JSON.parse(firstLine);
96
+ if (header.type !== "session" || !header.cwd)
97
+ continue;
98
+ const cwd = header.cwd;
99
+ // Compute the correct session directory (same encoding as session-manager.ts)
100
+ const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
101
+ const correctDir = join(agentDir, "sessions", safePath);
102
+ // Create directory if needed
103
+ if (!existsSync(correctDir)) {
104
+ mkdirSync(correctDir, { recursive: true });
105
+ }
106
+ // Move the file
107
+ const fileName = file.split("/").pop() || file.split("\\").pop();
108
+ const newPath = join(correctDir, fileName);
109
+ if (existsSync(newPath))
110
+ continue; // Skip if target exists
111
+ renameSync(file, newPath);
112
+ }
113
+ catch {
114
+ // Skip files that can't be migrated
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Migrate commands/ to prompts/ if needed.
120
+ * Works for both regular directories and symlinks.
121
+ */
122
+ function migrateCommandsToPrompts(baseDir, label) {
123
+ const commandsDir = join(baseDir, "commands");
124
+ const promptsDir = join(baseDir, "prompts");
125
+ if (existsSync(commandsDir) && !existsSync(promptsDir)) {
126
+ try {
127
+ renameSync(commandsDir, promptsDir);
128
+ console.log(chalk.green(`Migrated ${label} commands/ → prompts/`));
129
+ return true;
130
+ }
131
+ catch (err) {
132
+ console.log(chalk.yellow(`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`));
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ /**
138
+ * Move fd/rg binaries from tools/ to bin/ if they exist.
139
+ */
140
+ function migrateToolsToBin() {
141
+ const agentDir = getAgentDir();
142
+ const toolsDir = join(agentDir, "tools");
143
+ const binDir = getBinDir();
144
+ if (!existsSync(toolsDir))
145
+ return;
146
+ const binaries = ["fd", "rg", "fd.exe", "rg.exe"];
147
+ let movedAny = false;
148
+ for (const bin of binaries) {
149
+ const oldPath = join(toolsDir, bin);
150
+ const newPath = join(binDir, bin);
151
+ if (existsSync(oldPath)) {
152
+ if (!existsSync(binDir)) {
153
+ mkdirSync(binDir, { recursive: true });
154
+ }
155
+ if (!existsSync(newPath)) {
156
+ try {
157
+ renameSync(oldPath, newPath);
158
+ movedAny = true;
159
+ }
160
+ catch {
161
+ // Ignore errors
162
+ }
163
+ }
164
+ else {
165
+ // Target exists, just delete the old one
166
+ try {
167
+ rmSync?.(oldPath, { force: true });
168
+ }
169
+ catch {
170
+ // Ignore
171
+ }
172
+ }
173
+ }
174
+ }
175
+ if (movedAny) {
176
+ console.log(chalk.green(`Migrated managed binaries tools/ → bin/`));
177
+ }
178
+ }
179
+ /**
180
+ * Check for deprecated hooks/ and tools/ directories.
181
+ * Note: tools/ may contain fd/rg binaries extracted by indusagi, so only warn if it has other files.
182
+ */
183
+ function checkDeprecatedExtensionDirs(baseDir, label) {
184
+ const hooksDir = join(baseDir, "hooks");
185
+ const toolsDir = join(baseDir, "tools");
186
+ const warnings = [];
187
+ if (existsSync(hooksDir)) {
188
+ warnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);
189
+ }
190
+ if (existsSync(toolsDir)) {
191
+ // Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)
192
+ try {
193
+ const entries = readdirSync(toolsDir);
194
+ const customTools = entries.filter((e) => {
195
+ const lower = e.toLowerCase();
196
+ return (lower !== "fd" && lower !== "rg" && lower !== "fd.exe" && lower !== "rg.exe" && !e.startsWith(".") // Ignore .DS_Store and other hidden files
197
+ );
198
+ });
199
+ if (customTools.length > 0) {
200
+ warnings.push(`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`);
201
+ }
202
+ }
203
+ catch {
204
+ // Ignore read errors
205
+ }
206
+ }
207
+ return warnings;
208
+ }
209
+ /**
210
+ * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.
211
+ */
212
+ function migrateExtensionSystem(cwd) {
213
+ const agentDir = getAgentDir();
214
+ const projectDir = join(cwd, CONFIG_DIR_NAME);
215
+ // Migrate commands/ to prompts/
216
+ migrateCommandsToPrompts(agentDir, "Global");
217
+ migrateCommandsToPrompts(projectDir, "Project");
218
+ // Check for deprecated directories
219
+ const warnings = [
220
+ ...checkDeprecatedExtensionDirs(agentDir, "Global"),
221
+ ...checkDeprecatedExtensionDirs(projectDir, "Project"),
222
+ ];
223
+ return warnings;
224
+ }
225
+ /**
226
+ * Print deprecation warnings and wait for keypress.
227
+ */
228
+ export async function showDeprecationWarnings(warnings) {
229
+ if (warnings.length === 0)
230
+ return;
231
+ for (const warning of warnings) {
232
+ console.log(chalk.yellow(`Warning: ${warning}`));
233
+ }
234
+ console.log(chalk.yellow(`\nMove your extensions to the extensions/ directory.`));
235
+ console.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));
236
+ console.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));
237
+ console.log(chalk.dim(`\nPress any key to continue...`));
238
+ await new Promise((resolve) => {
239
+ process.stdin.setRawMode?.(true);
240
+ process.stdin.resume();
241
+ process.stdin.once("data", () => {
242
+ process.stdin.setRawMode?.(false);
243
+ process.stdin.pause();
244
+ resolve();
245
+ });
246
+ });
247
+ console.log();
248
+ }
249
+ /**
250
+ * Run all migrations. Called once on startup.
251
+ *
252
+ * @returns Object with migration results and deprecation warnings
253
+ */
254
+ export function runMigrations(cwd = process.cwd()) {
255
+ const migratedAuthProviders = migrateAuthToAuthJson();
256
+ migrateSessionsFromAgentRoot();
257
+ migrateToolsToBin();
258
+ const deprecationWarnings = migrateExtensionSystem(cwd);
259
+ return { migratedAuthProviders, deprecationWarnings };
260
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Run modes for the coding agent.
3
+ */
4
+ export { InteractiveMode } from "./interactive/interactive-mode.js";
5
+ export { runPrintMode } from "./print-mode.js";
6
+ export { RpcClient } from "./rpc/rpc-client.js";
7
+ export { runRpcMode } from "./rpc/rpc-mode.js";
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Armin says hi! A fun easter egg with animated XBM art.
3
+ */
4
+ import { theme } from "../theme/theme.js";
5
+ // XBM image: 31x36 pixels, LSB first, 1=background, 0=foreground
6
+ const WIDTH = 31;
7
+ const HEIGHT = 36;
8
+ const BITS = [
9
+ 0xff, 0xff, 0xff, 0x7f, 0xff, 0xf0, 0xff, 0x7f, 0xff, 0xed, 0xff, 0x7f, 0xff, 0xdb, 0xff, 0x7f, 0xff, 0xb7, 0xff,
10
+ 0x7f, 0xff, 0x77, 0xfe, 0x7f, 0x3f, 0xf8, 0xfe, 0x7f, 0xdf, 0xff, 0xfe, 0x7f, 0xdf, 0x3f, 0xfc, 0x7f, 0x9f, 0xc3,
11
+ 0xfb, 0x7f, 0x6f, 0xfc, 0xf4, 0x7f, 0xf7, 0x0f, 0xf7, 0x7f, 0xf7, 0xff, 0xf7, 0x7f, 0xf7, 0xff, 0xe3, 0x7f, 0xf7,
12
+ 0x07, 0xe8, 0x7f, 0xef, 0xf8, 0x67, 0x70, 0x0f, 0xff, 0xbb, 0x6f, 0xf1, 0x00, 0xd0, 0x5b, 0xfd, 0x3f, 0xec, 0x53,
13
+ 0xc1, 0xff, 0xef, 0x57, 0x9f, 0xfd, 0xee, 0x5f, 0x9f, 0xfc, 0xae, 0x5f, 0x1f, 0x78, 0xac, 0x5f, 0x3f, 0x00, 0x50,
14
+ 0x6c, 0x7f, 0x00, 0xdc, 0x77, 0xff, 0xc0, 0x3f, 0x78, 0xff, 0x01, 0xf8, 0x7f, 0xff, 0x03, 0x9c, 0x78, 0xff, 0x07,
15
+ 0x8c, 0x7c, 0xff, 0x0f, 0xce, 0x78, 0xff, 0xff, 0xcf, 0x7f, 0xff, 0xff, 0xcf, 0x78, 0xff, 0xff, 0xdf, 0x78, 0xff,
16
+ 0xff, 0xdf, 0x7d, 0xff, 0xff, 0x3f, 0x7e, 0xff, 0xff, 0xff, 0x7f,
17
+ ];
18
+ const BYTES_PER_ROW = Math.ceil(WIDTH / 8);
19
+ const DISPLAY_HEIGHT = Math.ceil(HEIGHT / 2); // Half-block rendering
20
+ const EFFECTS = ["typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"];
21
+ // Get pixel at (x, y): true = foreground, false = background
22
+ function getPixel(x, y) {
23
+ if (y >= HEIGHT)
24
+ return false;
25
+ const byteIndex = y * BYTES_PER_ROW + Math.floor(x / 8);
26
+ const bitIndex = x % 8;
27
+ return ((BITS[byteIndex] >> bitIndex) & 1) === 0;
28
+ }
29
+ // Get the character for a cell (2 vertical pixels packed)
30
+ function getChar(x, row) {
31
+ const upper = getPixel(x, row * 2);
32
+ const lower = getPixel(x, row * 2 + 1);
33
+ if (upper && lower)
34
+ return "█";
35
+ if (upper)
36
+ return "▀";
37
+ if (lower)
38
+ return "▄";
39
+ return " ";
40
+ }
41
+ // Build the final image grid
42
+ function buildFinalGrid() {
43
+ const grid = [];
44
+ for (let row = 0; row < DISPLAY_HEIGHT; row++) {
45
+ const line = [];
46
+ for (let x = 0; x < WIDTH; x++) {
47
+ line.push(getChar(x, row));
48
+ }
49
+ grid.push(line);
50
+ }
51
+ return grid;
52
+ }
53
+ export class ArminComponent {
54
+ constructor(ui) {
55
+ this.interval = null;
56
+ this.effectState = {};
57
+ this.cachedLines = [];
58
+ this.cachedWidth = 0;
59
+ this.gridVersion = 0;
60
+ this.cachedVersion = -1;
61
+ this.ui = ui;
62
+ this.effect = EFFECTS[Math.floor(Math.random() * EFFECTS.length)];
63
+ this.finalGrid = buildFinalGrid();
64
+ this.currentGrid = this.createEmptyGrid();
65
+ this.initEffect();
66
+ this.startAnimation();
67
+ }
68
+ invalidate() {
69
+ this.cachedWidth = 0;
70
+ }
71
+ render(width) {
72
+ if (width === this.cachedWidth && this.cachedVersion === this.gridVersion) {
73
+ return this.cachedLines;
74
+ }
75
+ const padding = 1;
76
+ const availableWidth = width - padding;
77
+ this.cachedLines = this.currentGrid.map((row) => {
78
+ // Clip row to available width before applying color
79
+ const clipped = row.slice(0, availableWidth).join("");
80
+ const padRight = Math.max(0, width - padding - clipped.length);
81
+ return ` ${theme.fg("accent", clipped)}${" ".repeat(padRight)}`;
82
+ });
83
+ // Add "ARMIN SAYS HI" at the end
84
+ const message = "ARMIN SAYS HI";
85
+ const msgPadRight = Math.max(0, width - padding - message.length);
86
+ this.cachedLines.push(` ${theme.fg("accent", message)}${" ".repeat(msgPadRight)}`);
87
+ this.cachedWidth = width;
88
+ this.cachedVersion = this.gridVersion;
89
+ return this.cachedLines;
90
+ }
91
+ createEmptyGrid() {
92
+ return Array.from({ length: DISPLAY_HEIGHT }, () => Array(WIDTH).fill(" "));
93
+ }
94
+ initEffect() {
95
+ switch (this.effect) {
96
+ case "typewriter":
97
+ this.effectState = { pos: 0 };
98
+ break;
99
+ case "scanline":
100
+ this.effectState = { row: 0 };
101
+ break;
102
+ case "rain":
103
+ // Track falling position for each column
104
+ this.effectState = {
105
+ drops: Array.from({ length: WIDTH }, () => ({
106
+ y: -Math.floor(Math.random() * DISPLAY_HEIGHT * 2),
107
+ settled: 0,
108
+ })),
109
+ };
110
+ break;
111
+ case "fade": {
112
+ // Shuffle all pixel positions
113
+ const positions = [];
114
+ for (let row = 0; row < DISPLAY_HEIGHT; row++) {
115
+ for (let x = 0; x < WIDTH; x++) {
116
+ positions.push([row, x]);
117
+ }
118
+ }
119
+ // Fisher-Yates shuffle
120
+ for (let i = positions.length - 1; i > 0; i--) {
121
+ const j = Math.floor(Math.random() * (i + 1));
122
+ [positions[i], positions[j]] = [positions[j], positions[i]];
123
+ }
124
+ this.effectState = { positions, idx: 0 };
125
+ break;
126
+ }
127
+ case "crt":
128
+ this.effectState = { expansion: 0 };
129
+ break;
130
+ case "glitch":
131
+ this.effectState = { phase: 0, glitchFrames: 8 };
132
+ break;
133
+ case "dissolve": {
134
+ // Start with random noise
135
+ this.currentGrid = Array.from({ length: DISPLAY_HEIGHT }, () => Array.from({ length: WIDTH }, () => {
136
+ const chars = [" ", "░", "▒", "▓", "█", "▀", "▄"];
137
+ return chars[Math.floor(Math.random() * chars.length)];
138
+ }));
139
+ // Shuffle positions for gradual resolve
140
+ const dissolvePositions = [];
141
+ for (let row = 0; row < DISPLAY_HEIGHT; row++) {
142
+ for (let x = 0; x < WIDTH; x++) {
143
+ dissolvePositions.push([row, x]);
144
+ }
145
+ }
146
+ for (let i = dissolvePositions.length - 1; i > 0; i--) {
147
+ const j = Math.floor(Math.random() * (i + 1));
148
+ [dissolvePositions[i], dissolvePositions[j]] = [dissolvePositions[j], dissolvePositions[i]];
149
+ }
150
+ this.effectState = { positions: dissolvePositions, idx: 0 };
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ startAnimation() {
156
+ const fps = this.effect === "glitch" ? 60 : 30;
157
+ this.interval = setInterval(() => {
158
+ const done = this.tickEffect();
159
+ this.updateDisplay();
160
+ this.ui.requestRender();
161
+ if (done) {
162
+ this.stopAnimation();
163
+ }
164
+ }, 1000 / fps);
165
+ }
166
+ stopAnimation() {
167
+ if (this.interval) {
168
+ clearInterval(this.interval);
169
+ this.interval = null;
170
+ }
171
+ }
172
+ tickEffect() {
173
+ switch (this.effect) {
174
+ case "typewriter":
175
+ return this.tickTypewriter();
176
+ case "scanline":
177
+ return this.tickScanline();
178
+ case "rain":
179
+ return this.tickRain();
180
+ case "fade":
181
+ return this.tickFade();
182
+ case "crt":
183
+ return this.tickCrt();
184
+ case "glitch":
185
+ return this.tickGlitch();
186
+ case "dissolve":
187
+ return this.tickDissolve();
188
+ default:
189
+ return true;
190
+ }
191
+ }
192
+ tickTypewriter() {
193
+ const state = this.effectState;
194
+ const pixelsPerFrame = 3;
195
+ for (let i = 0; i < pixelsPerFrame; i++) {
196
+ const row = Math.floor(state.pos / WIDTH);
197
+ const x = state.pos % WIDTH;
198
+ if (row >= DISPLAY_HEIGHT)
199
+ return true;
200
+ this.currentGrid[row][x] = this.finalGrid[row][x];
201
+ state.pos++;
202
+ }
203
+ return false;
204
+ }
205
+ tickScanline() {
206
+ const state = this.effectState;
207
+ if (state.row >= DISPLAY_HEIGHT)
208
+ return true;
209
+ // Copy row
210
+ for (let x = 0; x < WIDTH; x++) {
211
+ this.currentGrid[state.row][x] = this.finalGrid[state.row][x];
212
+ }
213
+ state.row++;
214
+ return false;
215
+ }
216
+ tickRain() {
217
+ const state = this.effectState;
218
+ let allSettled = true;
219
+ this.currentGrid = this.createEmptyGrid();
220
+ for (let x = 0; x < WIDTH; x++) {
221
+ const drop = state.drops[x];
222
+ // Draw settled pixels
223
+ for (let row = DISPLAY_HEIGHT - 1; row >= DISPLAY_HEIGHT - drop.settled; row--) {
224
+ if (row >= 0) {
225
+ this.currentGrid[row][x] = this.finalGrid[row][x];
226
+ }
227
+ }
228
+ // Check if this column is done
229
+ if (drop.settled >= DISPLAY_HEIGHT)
230
+ continue;
231
+ allSettled = false;
232
+ // Find the target row for this column (lowest non-space pixel)
233
+ let targetRow = -1;
234
+ for (let row = DISPLAY_HEIGHT - 1 - drop.settled; row >= 0; row--) {
235
+ if (this.finalGrid[row][x] !== " ") {
236
+ targetRow = row;
237
+ break;
238
+ }
239
+ }
240
+ // Move drop down
241
+ drop.y++;
242
+ // Draw falling drop
243
+ if (drop.y >= 0 && drop.y < DISPLAY_HEIGHT) {
244
+ if (targetRow >= 0 && drop.y >= targetRow) {
245
+ // Settle
246
+ drop.settled = DISPLAY_HEIGHT - targetRow;
247
+ drop.y = -Math.floor(Math.random() * 5) - 1;
248
+ }
249
+ else {
250
+ // Still falling
251
+ this.currentGrid[drop.y][x] = "▓";
252
+ }
253
+ }
254
+ }
255
+ return allSettled;
256
+ }
257
+ tickFade() {
258
+ const state = this.effectState;
259
+ const pixelsPerFrame = 15;
260
+ for (let i = 0; i < pixelsPerFrame; i++) {
261
+ if (state.idx >= state.positions.length)
262
+ return true;
263
+ const [row, x] = state.positions[state.idx];
264
+ this.currentGrid[row][x] = this.finalGrid[row][x];
265
+ state.idx++;
266
+ }
267
+ return false;
268
+ }
269
+ tickCrt() {
270
+ const state = this.effectState;
271
+ const midRow = Math.floor(DISPLAY_HEIGHT / 2);
272
+ this.currentGrid = this.createEmptyGrid();
273
+ // Draw from middle expanding outward
274
+ const top = midRow - state.expansion;
275
+ const bottom = midRow + state.expansion;
276
+ for (let row = Math.max(0, top); row <= Math.min(DISPLAY_HEIGHT - 1, bottom); row++) {
277
+ for (let x = 0; x < WIDTH; x++) {
278
+ this.currentGrid[row][x] = this.finalGrid[row][x];
279
+ }
280
+ }
281
+ state.expansion++;
282
+ return state.expansion > DISPLAY_HEIGHT;
283
+ }
284
+ tickGlitch() {
285
+ const state = this.effectState;
286
+ if (state.phase < state.glitchFrames) {
287
+ // Glitch phase: show corrupted version
288
+ this.currentGrid = this.finalGrid.map((row) => {
289
+ const offset = Math.floor(Math.random() * 7) - 3;
290
+ const glitchRow = [...row];
291
+ // Random horizontal offset
292
+ if (Math.random() < 0.3) {
293
+ const shifted = glitchRow.slice(offset).concat(glitchRow.slice(0, offset));
294
+ return shifted.slice(0, WIDTH);
295
+ }
296
+ // Random vertical swap
297
+ if (Math.random() < 0.2) {
298
+ const swapRow = Math.floor(Math.random() * DISPLAY_HEIGHT);
299
+ return [...this.finalGrid[swapRow]];
300
+ }
301
+ return glitchRow;
302
+ });
303
+ state.phase++;
304
+ return false;
305
+ }
306
+ // Final frame: show clean image
307
+ this.currentGrid = this.finalGrid.map((row) => [...row]);
308
+ return true;
309
+ }
310
+ tickDissolve() {
311
+ const state = this.effectState;
312
+ const pixelsPerFrame = 20;
313
+ for (let i = 0; i < pixelsPerFrame; i++) {
314
+ if (state.idx >= state.positions.length)
315
+ return true;
316
+ const [row, x] = state.positions[state.idx];
317
+ this.currentGrid[row][x] = this.finalGrid[row][x];
318
+ state.idx++;
319
+ }
320
+ return false;
321
+ }
322
+ updateDisplay() {
323
+ this.gridVersion++;
324
+ }
325
+ dispose() {
326
+ this.stopAnimation();
327
+ }
328
+ }
@@ -0,0 +1,86 @@
1
+ import { Container, Markdown, Spacer, Text } from "indusagi/tui";
2
+ import { getMarkdownTheme, theme } from "../theme/theme.js";
3
+ /**
4
+ * Component that renders a complete assistant message
5
+ */
6
+ export class AssistantMessageComponent extends Container {
7
+ constructor(message, hideThinkingBlock = false, markdownTheme = getMarkdownTheme()) {
8
+ super();
9
+ this.hideThinkingBlock = hideThinkingBlock;
10
+ this.markdownTheme = markdownTheme;
11
+ // Container for text/thinking content
12
+ this.contentContainer = new Container();
13
+ this.addChild(this.contentContainer);
14
+ if (message) {
15
+ this.updateContent(message);
16
+ }
17
+ }
18
+ invalidate() {
19
+ super.invalidate();
20
+ if (this.lastMessage) {
21
+ this.updateContent(this.lastMessage);
22
+ }
23
+ }
24
+ setHideThinkingBlock(hide) {
25
+ this.hideThinkingBlock = hide;
26
+ }
27
+ updateContent(message) {
28
+ this.lastMessage = message;
29
+ // Clear content container
30
+ this.contentContainer.clear();
31
+ const hasVisibleContent = message.content.some((c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
32
+ if (hasVisibleContent) {
33
+ this.contentContainer.addChild(new Spacer(1));
34
+ }
35
+ // Render content in order
36
+ for (let i = 0; i < message.content.length; i++) {
37
+ const content = message.content[i];
38
+ if (content.type === "text" && content.text.trim()) {
39
+ // Assistant text messages with no background - trim the text
40
+ // Set paddingY=0 to avoid extra spacing before tool executions
41
+ this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));
42
+ }
43
+ else if (content.type === "thinking" && content.thinking.trim()) {
44
+ // Check if there's text content after this thinking block
45
+ const hasTextAfter = message.content.slice(i + 1).some((c) => c.type === "text" && c.text.trim());
46
+ if (this.hideThinkingBlock) {
47
+ // Show static "Thinking..." label when hidden
48
+ this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0));
49
+ if (hasTextAfter) {
50
+ this.contentContainer.addChild(new Spacer(1));
51
+ }
52
+ }
53
+ else {
54
+ // Thinking traces in thinkingText color, italic
55
+ this.contentContainer.addChild(new Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {
56
+ color: (text) => theme.fg("thinkingText", text),
57
+ italic: true,
58
+ }));
59
+ this.contentContainer.addChild(new Spacer(1));
60
+ }
61
+ }
62
+ }
63
+ // Check if aborted - show after partial content
64
+ // But only if there are no tool calls (tool execution components will show the error)
65
+ const hasToolCalls = message.content.some((c) => c.type === "toolCall");
66
+ if (!hasToolCalls) {
67
+ if (message.stopReason === "aborted") {
68
+ const abortMessage = message.errorMessage && message.errorMessage !== "Request was aborted"
69
+ ? message.errorMessage
70
+ : "Operation aborted";
71
+ if (hasVisibleContent) {
72
+ this.contentContainer.addChild(new Spacer(1));
73
+ }
74
+ else {
75
+ this.contentContainer.addChild(new Spacer(1));
76
+ }
77
+ this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
78
+ }
79
+ else if (message.stopReason === "error") {
80
+ const errorMsg = message.errorMessage || "Unknown error";
81
+ this.contentContainer.addChild(new Spacer(1));
82
+ this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
83
+ }
84
+ }
85
+ }
86
+ }