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,137 @@
1
+ /**
2
+ * Shared utilities for compaction and branch summarization.
3
+ */
4
+ export function createFileOps() {
5
+ return {
6
+ read: new Set(),
7
+ written: new Set(),
8
+ edited: new Set(),
9
+ };
10
+ }
11
+ /**
12
+ * Extract file operations from tool calls in an assistant message.
13
+ */
14
+ export function extractFileOpsFromMessage(message, fileOps) {
15
+ if (message.role !== "assistant")
16
+ return;
17
+ if (!("content" in message) || !Array.isArray(message.content))
18
+ return;
19
+ for (const block of message.content) {
20
+ if (typeof block !== "object" || block === null)
21
+ continue;
22
+ if (!("type" in block) || block.type !== "toolCall")
23
+ continue;
24
+ if (!("arguments" in block) || !("name" in block))
25
+ continue;
26
+ const args = block.arguments;
27
+ if (!args)
28
+ continue;
29
+ const path = typeof args.path === "string" ? args.path : undefined;
30
+ if (!path)
31
+ continue;
32
+ switch (block.name) {
33
+ case "read":
34
+ fileOps.read.add(path);
35
+ break;
36
+ case "write":
37
+ fileOps.written.add(path);
38
+ break;
39
+ case "edit":
40
+ fileOps.edited.add(path);
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ /**
46
+ * Compute final file lists from file operations.
47
+ * Returns readFiles (files only read, not modified) and modifiedFiles.
48
+ */
49
+ export function computeFileLists(fileOps) {
50
+ const modified = new Set([...fileOps.edited, ...fileOps.written]);
51
+ const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
52
+ const modifiedFiles = [...modified].sort();
53
+ return { readFiles: readOnly, modifiedFiles };
54
+ }
55
+ /**
56
+ * Format file operations as XML tags for summary.
57
+ */
58
+ export function formatFileOperations(readFiles, modifiedFiles) {
59
+ const sections = [];
60
+ if (readFiles.length > 0) {
61
+ sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
62
+ }
63
+ if (modifiedFiles.length > 0) {
64
+ sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
65
+ }
66
+ if (sections.length === 0)
67
+ return "";
68
+ return `\n\n${sections.join("\n\n")}`;
69
+ }
70
+ // ============================================================================
71
+ // Message Serialization
72
+ // ============================================================================
73
+ /**
74
+ * Serialize LLM messages to text for summarization.
75
+ * This prevents the model from treating it as a conversation to continue.
76
+ * Call convertToLlm() first to handle custom message types.
77
+ */
78
+ export function serializeConversation(messages) {
79
+ const parts = [];
80
+ for (const msg of messages) {
81
+ if (msg.role === "user") {
82
+ const content = typeof msg.content === "string"
83
+ ? msg.content
84
+ : msg.content
85
+ .filter((c) => c.type === "text")
86
+ .map((c) => c.text)
87
+ .join("");
88
+ if (content)
89
+ parts.push(`[User]: ${content}`);
90
+ }
91
+ else if (msg.role === "assistant") {
92
+ const textParts = [];
93
+ const thinkingParts = [];
94
+ const toolCalls = [];
95
+ for (const block of msg.content) {
96
+ if (block.type === "text") {
97
+ textParts.push(block.text);
98
+ }
99
+ else if (block.type === "thinking") {
100
+ thinkingParts.push(block.thinking);
101
+ }
102
+ else if (block.type === "toolCall") {
103
+ const args = block.arguments;
104
+ const argsStr = Object.entries(args)
105
+ .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
106
+ .join(", ");
107
+ toolCalls.push(`${block.name}(${argsStr})`);
108
+ }
109
+ }
110
+ if (thinkingParts.length > 0) {
111
+ parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
112
+ }
113
+ if (textParts.length > 0) {
114
+ parts.push(`[Assistant]: ${textParts.join("\n")}`);
115
+ }
116
+ if (toolCalls.length > 0) {
117
+ parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
118
+ }
119
+ }
120
+ else if (msg.role === "toolResult") {
121
+ const content = msg.content
122
+ .filter((c) => c.type === "text")
123
+ .map((c) => c.text)
124
+ .join("");
125
+ if (content) {
126
+ parts.push(`[Tool result]: ${content}`);
127
+ }
128
+ }
129
+ }
130
+ return parts.join("\n\n");
131
+ }
132
+ // ============================================================================
133
+ // Summarization System Prompt
134
+ // ============================================================================
135
+ export const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
136
+
137
+ Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from "node:events";
2
+ export function createEventBus() {
3
+ const emitter = new EventEmitter();
4
+ return {
5
+ emit: (channel, data) => {
6
+ emitter.emit(channel, data);
7
+ },
8
+ on: (channel, handler) => {
9
+ const safeHandler = async (data) => {
10
+ try {
11
+ await handler(data);
12
+ }
13
+ catch (err) {
14
+ console.error(`Event handler error (${channel}):`, err);
15
+ }
16
+ };
17
+ emitter.on(channel, safeHandler);
18
+ return () => emitter.off(channel, safeHandler);
19
+ },
20
+ clear: () => {
21
+ emitter.removeAllListeners();
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared command execution utilities for extensions and custom tools.
3
+ */
4
+ import { spawn } from "node:child_process";
5
+ /**
6
+ * Execute a shell command and return stdout/stderr/code.
7
+ * Supports timeout and abort signal.
8
+ */
9
+ export async function execCommand(command, args, cwd, options) {
10
+ return new Promise((resolve) => {
11
+ const proc = spawn(command, args, {
12
+ cwd,
13
+ shell: false,
14
+ stdio: ["ignore", "pipe", "pipe"],
15
+ });
16
+ let stdout = "";
17
+ let stderr = "";
18
+ let killed = false;
19
+ let timeoutId;
20
+ const killProcess = () => {
21
+ if (!killed) {
22
+ killed = true;
23
+ proc.kill("SIGTERM");
24
+ // Force kill after 5 seconds if SIGTERM doesn't work
25
+ setTimeout(() => {
26
+ if (!proc.killed) {
27
+ proc.kill("SIGKILL");
28
+ }
29
+ }, 5000);
30
+ }
31
+ };
32
+ // Handle abort signal
33
+ if (options?.signal) {
34
+ if (options.signal.aborted) {
35
+ killProcess();
36
+ }
37
+ else {
38
+ options.signal.addEventListener("abort", killProcess, { once: true });
39
+ }
40
+ }
41
+ // Handle timeout
42
+ if (options?.timeout && options.timeout > 0) {
43
+ timeoutId = setTimeout(() => {
44
+ killProcess();
45
+ }, options.timeout);
46
+ }
47
+ proc.stdout?.on("data", (data) => {
48
+ stdout += data.toString();
49
+ });
50
+ proc.stderr?.on("data", (data) => {
51
+ stderr += data.toString();
52
+ });
53
+ proc.on("close", (code) => {
54
+ if (timeoutId)
55
+ clearTimeout(timeoutId);
56
+ if (options?.signal) {
57
+ options.signal.removeEventListener("abort", killProcess);
58
+ }
59
+ resolve({ stdout, stderr, code: code ?? 0, killed });
60
+ });
61
+ proc.on("error", (_err) => {
62
+ if (timeoutId)
63
+ clearTimeout(timeoutId);
64
+ if (options?.signal) {
65
+ options.signal.removeEventListener("abort", killProcess);
66
+ }
67
+ resolve({ stdout, stderr, code: 1, killed });
68
+ });
69
+ });
70
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * ANSI escape code to HTML converter.
3
+ *
4
+ * Converts terminal ANSI color/style codes to HTML with inline styles.
5
+ * Supports:
6
+ * - Standard foreground colors (30-37) and bright variants (90-97)
7
+ * - Standard background colors (40-47) and bright variants (100-107)
8
+ * - 256-color palette (38;5;N and 48;5;N)
9
+ * - RGB true color (38;2;R;G;B and 48;2;R;G;B)
10
+ * - Text styles: bold (1), dim (2), italic (3), underline (4)
11
+ * - Reset (0)
12
+ */
13
+ // Standard ANSI color palette (0-15)
14
+ const ANSI_COLORS = [
15
+ "#000000", // 0: black
16
+ "#800000", // 1: red
17
+ "#008000", // 2: green
18
+ "#808000", // 3: yellow
19
+ "#000080", // 4: blue
20
+ "#800080", // 5: magenta
21
+ "#008080", // 6: cyan
22
+ "#c0c0c0", // 7: white
23
+ "#808080", // 8: bright black
24
+ "#ff0000", // 9: bright red
25
+ "#00ff00", // 10: bright green
26
+ "#ffff00", // 11: bright yellow
27
+ "#0000ff", // 12: bright blue
28
+ "#ff00ff", // 13: bright magenta
29
+ "#00ffff", // 14: bright cyan
30
+ "#ffffff", // 15: bright white
31
+ ];
32
+ /**
33
+ * Convert 256-color index to hex.
34
+ */
35
+ function color256ToHex(index) {
36
+ // Standard colors (0-15)
37
+ if (index < 16) {
38
+ return ANSI_COLORS[index];
39
+ }
40
+ // Color cube (16-231): 6x6x6 = 216 colors
41
+ if (index < 232) {
42
+ const cubeIndex = index - 16;
43
+ const r = Math.floor(cubeIndex / 36);
44
+ const g = Math.floor((cubeIndex % 36) / 6);
45
+ const b = cubeIndex % 6;
46
+ const toComponent = (n) => (n === 0 ? 0 : 55 + n * 40);
47
+ const toHex = (n) => toComponent(n).toString(16).padStart(2, "0");
48
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
49
+ }
50
+ // Grayscale (232-255): 24 shades
51
+ const gray = 8 + (index - 232) * 10;
52
+ const grayHex = gray.toString(16).padStart(2, "0");
53
+ return `#${grayHex}${grayHex}${grayHex}`;
54
+ }
55
+ /**
56
+ * Escape HTML special characters.
57
+ */
58
+ function escapeHtml(text) {
59
+ return text
60
+ .replace(/&/g, "&amp;")
61
+ .replace(/</g, "&lt;")
62
+ .replace(/>/g, "&gt;")
63
+ .replace(/"/g, "&quot;")
64
+ .replace(/'/g, "&#039;");
65
+ }
66
+ function createEmptyStyle() {
67
+ return {
68
+ fg: null,
69
+ bg: null,
70
+ bold: false,
71
+ dim: false,
72
+ italic: false,
73
+ underline: false,
74
+ };
75
+ }
76
+ function styleToInlineCSS(style) {
77
+ const parts = [];
78
+ if (style.fg)
79
+ parts.push(`color:${style.fg}`);
80
+ if (style.bg)
81
+ parts.push(`background-color:${style.bg}`);
82
+ if (style.bold)
83
+ parts.push("font-weight:bold");
84
+ if (style.dim)
85
+ parts.push("opacity:0.6");
86
+ if (style.italic)
87
+ parts.push("font-style:italic");
88
+ if (style.underline)
89
+ parts.push("text-decoration:underline");
90
+ return parts.join(";");
91
+ }
92
+ function hasStyle(style) {
93
+ return style.fg !== null || style.bg !== null || style.bold || style.dim || style.italic || style.underline;
94
+ }
95
+ /**
96
+ * Parse ANSI SGR (Select Graphic Rendition) codes and update style.
97
+ */
98
+ function applySgrCode(params, style) {
99
+ let i = 0;
100
+ while (i < params.length) {
101
+ const code = params[i];
102
+ if (code === 0) {
103
+ // Reset all
104
+ style.fg = null;
105
+ style.bg = null;
106
+ style.bold = false;
107
+ style.dim = false;
108
+ style.italic = false;
109
+ style.underline = false;
110
+ }
111
+ else if (code === 1) {
112
+ style.bold = true;
113
+ }
114
+ else if (code === 2) {
115
+ style.dim = true;
116
+ }
117
+ else if (code === 3) {
118
+ style.italic = true;
119
+ }
120
+ else if (code === 4) {
121
+ style.underline = true;
122
+ }
123
+ else if (code === 22) {
124
+ // Reset bold/dim
125
+ style.bold = false;
126
+ style.dim = false;
127
+ }
128
+ else if (code === 23) {
129
+ style.italic = false;
130
+ }
131
+ else if (code === 24) {
132
+ style.underline = false;
133
+ }
134
+ else if (code >= 30 && code <= 37) {
135
+ // Standard foreground colors
136
+ style.fg = ANSI_COLORS[code - 30];
137
+ }
138
+ else if (code === 38) {
139
+ // Extended foreground color
140
+ if (params[i + 1] === 5 && params.length > i + 2) {
141
+ // 256-color: 38;5;N
142
+ style.fg = color256ToHex(params[i + 2]);
143
+ i += 2;
144
+ }
145
+ else if (params[i + 1] === 2 && params.length > i + 4) {
146
+ // RGB: 38;2;R;G;B
147
+ const r = params[i + 2];
148
+ const g = params[i + 3];
149
+ const b = params[i + 4];
150
+ style.fg = `rgb(${r},${g},${b})`;
151
+ i += 4;
152
+ }
153
+ }
154
+ else if (code === 39) {
155
+ // Default foreground
156
+ style.fg = null;
157
+ }
158
+ else if (code >= 40 && code <= 47) {
159
+ // Standard background colors
160
+ style.bg = ANSI_COLORS[code - 40];
161
+ }
162
+ else if (code === 48) {
163
+ // Extended background color
164
+ if (params[i + 1] === 5 && params.length > i + 2) {
165
+ // 256-color: 48;5;N
166
+ style.bg = color256ToHex(params[i + 2]);
167
+ i += 2;
168
+ }
169
+ else if (params[i + 1] === 2 && params.length > i + 4) {
170
+ // RGB: 48;2;R;G;B
171
+ const r = params[i + 2];
172
+ const g = params[i + 3];
173
+ const b = params[i + 4];
174
+ style.bg = `rgb(${r},${g},${b})`;
175
+ i += 4;
176
+ }
177
+ }
178
+ else if (code === 49) {
179
+ // Default background
180
+ style.bg = null;
181
+ }
182
+ else if (code >= 90 && code <= 97) {
183
+ // Bright foreground colors
184
+ style.fg = ANSI_COLORS[code - 90 + 8];
185
+ }
186
+ else if (code >= 100 && code <= 107) {
187
+ // Bright background colors
188
+ style.bg = ANSI_COLORS[code - 100 + 8];
189
+ }
190
+ // Ignore unrecognized codes
191
+ i++;
192
+ }
193
+ }
194
+ // Match ANSI escape sequences: ESC[ followed by params and ending with 'm'
195
+ const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
196
+ /**
197
+ * Convert ANSI-escaped text to HTML with inline styles.
198
+ */
199
+ export function ansiToHtml(text) {
200
+ const style = createEmptyStyle();
201
+ let result = "";
202
+ let lastIndex = 0;
203
+ let inSpan = false;
204
+ // Reset regex state
205
+ ANSI_REGEX.lastIndex = 0;
206
+ let match = ANSI_REGEX.exec(text);
207
+ while (match !== null) {
208
+ // Add text before this escape sequence
209
+ const beforeText = text.slice(lastIndex, match.index);
210
+ if (beforeText) {
211
+ result += escapeHtml(beforeText);
212
+ }
213
+ // Parse SGR parameters
214
+ const paramStr = match[1];
215
+ const params = paramStr ? paramStr.split(";").map((p) => parseInt(p, 10) || 0) : [0];
216
+ // Close existing span if we have one
217
+ if (inSpan) {
218
+ result += "</span>";
219
+ inSpan = false;
220
+ }
221
+ // Apply the codes
222
+ applySgrCode(params, style);
223
+ // Open new span if we have any styling
224
+ if (hasStyle(style)) {
225
+ result += `<span style="${styleToInlineCSS(style)}">`;
226
+ inSpan = true;
227
+ }
228
+ lastIndex = match.index + match[0].length;
229
+ match = ANSI_REGEX.exec(text);
230
+ }
231
+ // Add remaining text
232
+ const remainingText = text.slice(lastIndex);
233
+ if (remainingText) {
234
+ result += escapeHtml(remainingText);
235
+ }
236
+ // Close any open span
237
+ if (inSpan) {
238
+ result += "</span>";
239
+ }
240
+ return result;
241
+ }
242
+ /**
243
+ * Convert array of ANSI-escaped lines to HTML.
244
+ * Each line is wrapped in a div element.
245
+ */
246
+ export function ansiLinesToHtml(lines) {
247
+ return lines.map((line) => `<div class="ansi-line">${ansiToHtml(line) || "&nbsp;"}</div>`).join("\n");
248
+ }