indusagi-coding-agent 0.50.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,448 @@
1
+ /**
2
+ * RPC mode: Headless operation with JSON stdin/stdout protocol.
3
+ *
4
+ * Used for embedding the agent in other applications.
5
+ * Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
6
+ *
7
+ * Protocol:
8
+ * - Commands: JSON objects with `type` field, optional `id` for correlation
9
+ * - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
10
+ * - Events: AgentSessionEvent objects streamed as they occur
11
+ * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
12
+ */
13
+ import * as crypto from "node:crypto";
14
+ import * as readline from "readline";
15
+ import { theme } from "../interactive/theme/theme.js";
16
+ /**
17
+ * Run in RPC mode.
18
+ * Listens for JSON commands on stdin, outputs events and responses on stdout.
19
+ */
20
+ export async function runRpcMode(session) {
21
+ const output = (obj) => {
22
+ console.log(JSON.stringify(obj));
23
+ };
24
+ const success = (id, command, data) => {
25
+ if (data === undefined) {
26
+ return { id, type: "response", command, success: true };
27
+ }
28
+ return { id, type: "response", command, success: true, data };
29
+ };
30
+ const error = (id, command, message) => {
31
+ return { id, type: "response", command, success: false, error: message };
32
+ };
33
+ // Pending extension UI requests waiting for response
34
+ const pendingExtensionRequests = new Map();
35
+ // Shutdown request flag
36
+ let shutdownRequested = false;
37
+ /** Helper for dialog methods with signal/timeout support */
38
+ function createDialogPromise(opts, defaultValue, request, parseResponse) {
39
+ if (opts?.signal?.aborted)
40
+ return Promise.resolve(defaultValue);
41
+ const id = crypto.randomUUID();
42
+ return new Promise((resolve, reject) => {
43
+ let timeoutId;
44
+ const cleanup = () => {
45
+ if (timeoutId)
46
+ clearTimeout(timeoutId);
47
+ opts?.signal?.removeEventListener("abort", onAbort);
48
+ pendingExtensionRequests.delete(id);
49
+ };
50
+ const onAbort = () => {
51
+ cleanup();
52
+ resolve(defaultValue);
53
+ };
54
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
55
+ if (opts?.timeout) {
56
+ timeoutId = setTimeout(() => {
57
+ cleanup();
58
+ resolve(defaultValue);
59
+ }, opts.timeout);
60
+ }
61
+ pendingExtensionRequests.set(id, {
62
+ resolve: (response) => {
63
+ cleanup();
64
+ resolve(parseResponse(response));
65
+ },
66
+ reject,
67
+ });
68
+ output({ type: "extension_ui_request", id, ...request });
69
+ });
70
+ }
71
+ /**
72
+ * Create an extension UI context that uses the RPC protocol.
73
+ */
74
+ const createExtensionUIContext = () => ({
75
+ select: (title, options, opts) => createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
76
+ confirm: (title, message, opts) => createDialogPromise(opts, false, { method: "confirm", title, message, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? false : "confirmed" in r ? r.confirmed : false),
77
+ input: (title, placeholder, opts) => createDialogPromise(opts, undefined, { method: "input", title, placeholder, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
78
+ notify(message, type) {
79
+ // Fire and forget - no response needed
80
+ output({
81
+ type: "extension_ui_request",
82
+ id: crypto.randomUUID(),
83
+ method: "notify",
84
+ message,
85
+ notifyType: type,
86
+ });
87
+ },
88
+ setStatus(key, text) {
89
+ // Fire and forget - no response needed
90
+ output({
91
+ type: "extension_ui_request",
92
+ id: crypto.randomUUID(),
93
+ method: "setStatus",
94
+ statusKey: key,
95
+ statusText: text,
96
+ });
97
+ },
98
+ setWorkingMessage(_message) {
99
+ // Working message not supported in RPC mode - requires TUI loader access
100
+ },
101
+ setWidget(key, content, options) {
102
+ // Only support string arrays in RPC mode - factory functions are ignored
103
+ if (content === undefined || Array.isArray(content)) {
104
+ output({
105
+ type: "extension_ui_request",
106
+ id: crypto.randomUUID(),
107
+ method: "setWidget",
108
+ widgetKey: key,
109
+ widgetLines: content,
110
+ widgetPlacement: options?.placement,
111
+ });
112
+ }
113
+ // Component factories are not supported in RPC mode - would need TUI access
114
+ },
115
+ setFooter(_factory) {
116
+ // Custom footer not supported in RPC mode - requires TUI access
117
+ },
118
+ setHeader(_factory) {
119
+ // Custom header not supported in RPC mode - requires TUI access
120
+ },
121
+ setTitle(title) {
122
+ // Fire and forget - host can implement terminal title control
123
+ output({
124
+ type: "extension_ui_request",
125
+ id: crypto.randomUUID(),
126
+ method: "setTitle",
127
+ title,
128
+ });
129
+ },
130
+ async custom() {
131
+ // Custom UI not supported in RPC mode
132
+ return undefined;
133
+ },
134
+ setEditorText(text) {
135
+ // Fire and forget - host can implement editor control
136
+ output({
137
+ type: "extension_ui_request",
138
+ id: crypto.randomUUID(),
139
+ method: "set_editor_text",
140
+ text,
141
+ });
142
+ },
143
+ getEditorText() {
144
+ // Synchronous method can't wait for RPC response
145
+ // Host should track editor state locally if needed
146
+ return "";
147
+ },
148
+ async editor(title, prefill) {
149
+ const id = crypto.randomUUID();
150
+ return new Promise((resolve, reject) => {
151
+ pendingExtensionRequests.set(id, {
152
+ resolve: (response) => {
153
+ if ("cancelled" in response && response.cancelled) {
154
+ resolve(undefined);
155
+ }
156
+ else if ("value" in response) {
157
+ resolve(response.value);
158
+ }
159
+ else {
160
+ resolve(undefined);
161
+ }
162
+ },
163
+ reject,
164
+ });
165
+ output({ type: "extension_ui_request", id, method: "editor", title, prefill });
166
+ });
167
+ },
168
+ setEditorComponent() {
169
+ // Custom editor components not supported in RPC mode
170
+ },
171
+ get theme() {
172
+ return theme;
173
+ },
174
+ getAllThemes() {
175
+ return [];
176
+ },
177
+ getTheme(_name) {
178
+ return undefined;
179
+ },
180
+ setTheme(_theme) {
181
+ // Theme switching not supported in RPC mode
182
+ return { success: false, error: "Theme switching not supported in RPC mode" };
183
+ },
184
+ });
185
+ // Set up extensions with RPC-based UI context
186
+ const extensionRunner = session.extensionRunner;
187
+ if (extensionRunner) {
188
+ await session.bindExtensions({
189
+ uiContext: createExtensionUIContext(),
190
+ commandContextActions: {
191
+ waitForIdle: () => session.agent.waitForIdle(),
192
+ newSession: async (options) => {
193
+ const success = await session.newSession({ parentSession: options?.parentSession });
194
+ if (success && options?.setup) {
195
+ await options.setup(session.sessionManager);
196
+ }
197
+ return { cancelled: !success };
198
+ },
199
+ fork: async (entryId) => {
200
+ const result = await session.fork(entryId);
201
+ return { cancelled: result.cancelled };
202
+ },
203
+ navigateTree: async (targetId, options) => {
204
+ const result = await session.navigateTree(targetId, {
205
+ summarize: options?.summarize,
206
+ customInstructions: options?.customInstructions,
207
+ replaceInstructions: options?.replaceInstructions,
208
+ label: options?.label,
209
+ });
210
+ return { cancelled: result.cancelled };
211
+ },
212
+ },
213
+ shutdownHandler: () => {
214
+ shutdownRequested = true;
215
+ },
216
+ onError: (err) => {
217
+ output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
218
+ },
219
+ });
220
+ }
221
+ // Output all agent events as JSON
222
+ session.subscribe((event) => {
223
+ output(event);
224
+ });
225
+ // Handle a single command
226
+ const handleCommand = async (command) => {
227
+ const id = command.id;
228
+ switch (command.type) {
229
+ // =================================================================
230
+ // Prompting
231
+ // =================================================================
232
+ case "prompt": {
233
+ // Don't await - events will stream
234
+ // Extension commands are executed immediately, file prompt templates are expanded
235
+ // If streaming and streamingBehavior specified, queues via steer/followUp
236
+ session
237
+ .prompt(command.message, {
238
+ images: command.images,
239
+ streamingBehavior: command.streamingBehavior,
240
+ source: "rpc",
241
+ })
242
+ .catch((e) => output(error(id, "prompt", e.message)));
243
+ return success(id, "prompt");
244
+ }
245
+ case "steer": {
246
+ await session.steer(command.message);
247
+ return success(id, "steer");
248
+ }
249
+ case "follow_up": {
250
+ await session.followUp(command.message);
251
+ return success(id, "follow_up");
252
+ }
253
+ case "abort": {
254
+ await session.abort();
255
+ return success(id, "abort");
256
+ }
257
+ case "new_session": {
258
+ const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
259
+ const cancelled = !(await session.newSession(options));
260
+ return success(id, "new_session", { cancelled });
261
+ }
262
+ // =================================================================
263
+ // State
264
+ // =================================================================
265
+ case "get_state": {
266
+ const state = {
267
+ model: session.model,
268
+ thinkingLevel: session.thinkingLevel,
269
+ isStreaming: session.isStreaming,
270
+ isCompacting: session.isCompacting,
271
+ steeringMode: session.steeringMode,
272
+ followUpMode: session.followUpMode,
273
+ sessionFile: session.sessionFile,
274
+ sessionId: session.sessionId,
275
+ autoCompactionEnabled: session.autoCompactionEnabled,
276
+ messageCount: session.messages.length,
277
+ pendingMessageCount: session.pendingMessageCount,
278
+ };
279
+ return success(id, "get_state", state);
280
+ }
281
+ // =================================================================
282
+ // Model
283
+ // =================================================================
284
+ case "set_model": {
285
+ const models = await session.modelRegistry.getAvailable();
286
+ const model = models.find((m) => m.provider === command.provider && m.id === command.modelId);
287
+ if (!model) {
288
+ return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
289
+ }
290
+ await session.setModel(model);
291
+ return success(id, "set_model", model);
292
+ }
293
+ case "cycle_model": {
294
+ const result = await session.cycleModel();
295
+ if (!result) {
296
+ return success(id, "cycle_model", null);
297
+ }
298
+ return success(id, "cycle_model", result);
299
+ }
300
+ case "get_available_models": {
301
+ const models = await session.modelRegistry.getAvailable();
302
+ return success(id, "get_available_models", { models });
303
+ }
304
+ // =================================================================
305
+ // Thinking
306
+ // =================================================================
307
+ case "set_thinking_level": {
308
+ session.setThinkingLevel(command.level);
309
+ return success(id, "set_thinking_level");
310
+ }
311
+ case "cycle_thinking_level": {
312
+ const level = session.cycleThinkingLevel();
313
+ if (!level) {
314
+ return success(id, "cycle_thinking_level", null);
315
+ }
316
+ return success(id, "cycle_thinking_level", { level });
317
+ }
318
+ // =================================================================
319
+ // Queue Modes
320
+ // =================================================================
321
+ case "set_steering_mode": {
322
+ session.setSteeringMode(command.mode);
323
+ return success(id, "set_steering_mode");
324
+ }
325
+ case "set_follow_up_mode": {
326
+ session.setFollowUpMode(command.mode);
327
+ return success(id, "set_follow_up_mode");
328
+ }
329
+ // =================================================================
330
+ // Compaction
331
+ // =================================================================
332
+ case "compact": {
333
+ const result = await session.compact(command.customInstructions);
334
+ return success(id, "compact", result);
335
+ }
336
+ case "set_auto_compaction": {
337
+ session.setAutoCompactionEnabled(command.enabled);
338
+ return success(id, "set_auto_compaction");
339
+ }
340
+ // =================================================================
341
+ // Retry
342
+ // =================================================================
343
+ case "set_auto_retry": {
344
+ session.setAutoRetryEnabled(command.enabled);
345
+ return success(id, "set_auto_retry");
346
+ }
347
+ case "abort_retry": {
348
+ session.abortRetry();
349
+ return success(id, "abort_retry");
350
+ }
351
+ // =================================================================
352
+ // Bash
353
+ // =================================================================
354
+ case "bash": {
355
+ const result = await session.executeBash(command.command);
356
+ return success(id, "bash", result);
357
+ }
358
+ case "abort_bash": {
359
+ session.abortBash();
360
+ return success(id, "abort_bash");
361
+ }
362
+ // =================================================================
363
+ // Session
364
+ // =================================================================
365
+ case "get_session_stats": {
366
+ const stats = session.getSessionStats();
367
+ return success(id, "get_session_stats", stats);
368
+ }
369
+ case "export_html": {
370
+ const path = await session.exportToHtml(command.outputPath);
371
+ return success(id, "export_html", { path });
372
+ }
373
+ case "switch_session": {
374
+ const cancelled = !(await session.switchSession(command.sessionPath));
375
+ return success(id, "switch_session", { cancelled });
376
+ }
377
+ case "fork": {
378
+ const result = await session.fork(command.entryId);
379
+ return success(id, "fork", { text: result.selectedText, cancelled: result.cancelled });
380
+ }
381
+ case "get_fork_messages": {
382
+ const messages = session.getUserMessagesForForking();
383
+ return success(id, "get_fork_messages", { messages });
384
+ }
385
+ case "get_last_assistant_text": {
386
+ const text = session.getLastAssistantText();
387
+ return success(id, "get_last_assistant_text", { text });
388
+ }
389
+ // =================================================================
390
+ // Messages
391
+ // =================================================================
392
+ case "get_messages": {
393
+ return success(id, "get_messages", { messages: session.messages });
394
+ }
395
+ default: {
396
+ const unknownCommand = command;
397
+ return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
398
+ }
399
+ }
400
+ };
401
+ /**
402
+ * Check if shutdown was requested and perform shutdown if so.
403
+ * Called after handling each command when waiting for the next command.
404
+ */
405
+ async function checkShutdownRequested() {
406
+ if (!shutdownRequested)
407
+ return;
408
+ const currentRunner = session.extensionRunner;
409
+ if (currentRunner?.hasHandlers("session_shutdown")) {
410
+ await currentRunner.emit({ type: "session_shutdown" });
411
+ }
412
+ // Close readline interface to stop waiting for input
413
+ rl.close();
414
+ process.exit(0);
415
+ }
416
+ // Listen for JSON input
417
+ const rl = readline.createInterface({
418
+ input: process.stdin,
419
+ output: process.stdout,
420
+ terminal: false,
421
+ });
422
+ rl.on("line", async (line) => {
423
+ try {
424
+ const parsed = JSON.parse(line);
425
+ // Handle extension UI responses
426
+ if (parsed.type === "extension_ui_response") {
427
+ const response = parsed;
428
+ const pending = pendingExtensionRequests.get(response.id);
429
+ if (pending) {
430
+ pendingExtensionRequests.delete(response.id);
431
+ pending.resolve(response);
432
+ }
433
+ return;
434
+ }
435
+ // Handle regular commands
436
+ const command = parsed;
437
+ const response = await handleCommand(command);
438
+ output(response);
439
+ // Check for deferred shutdown request (idle between commands)
440
+ await checkShutdownRequested();
441
+ }
442
+ catch (e) {
443
+ output(error(undefined, "parse", `Failed to parse command: ${e.message}`));
444
+ }
445
+ });
446
+ // Keep process alive forever
447
+ return new Promise(() => { });
448
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RPC protocol types for headless operation.
3
+ *
4
+ * Commands are sent as JSON lines on stdin.
5
+ * Responses and events are emitted as JSON lines on stdout.
6
+ */
7
+ export {};
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ /**
3
+ * Parse changelog entries from CHANGELOG.md
4
+ * Scans for ## lines and collects content until next ## or EOF
5
+ */
6
+ export function parseChangelog(changelogPath) {
7
+ if (!existsSync(changelogPath)) {
8
+ return [];
9
+ }
10
+ try {
11
+ const content = readFileSync(changelogPath, "utf-8");
12
+ const lines = content.split("\n");
13
+ const entries = [];
14
+ let currentLines = [];
15
+ let currentVersion = null;
16
+ for (const line of lines) {
17
+ // Check if this is a version header (## [x.y.z] ...)
18
+ if (line.startsWith("## ")) {
19
+ // Save previous entry if exists
20
+ if (currentVersion && currentLines.length > 0) {
21
+ entries.push({
22
+ ...currentVersion,
23
+ content: currentLines.join("\n").trim(),
24
+ });
25
+ }
26
+ // Try to parse version from this line
27
+ const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
28
+ if (versionMatch) {
29
+ currentVersion = {
30
+ major: Number.parseInt(versionMatch[1], 10),
31
+ minor: Number.parseInt(versionMatch[2], 10),
32
+ patch: Number.parseInt(versionMatch[3], 10),
33
+ };
34
+ currentLines = [line];
35
+ }
36
+ else {
37
+ // Reset if we can't parse version
38
+ currentVersion = null;
39
+ currentLines = [];
40
+ }
41
+ }
42
+ else if (currentVersion) {
43
+ // Collect lines for current version
44
+ currentLines.push(line);
45
+ }
46
+ }
47
+ // Save last entry
48
+ if (currentVersion && currentLines.length > 0) {
49
+ entries.push({
50
+ ...currentVersion,
51
+ content: currentLines.join("\n").trim(),
52
+ });
53
+ }
54
+ return entries;
55
+ }
56
+ catch (error) {
57
+ console.error(`Warning: Could not parse changelog: ${error}`);
58
+ return [];
59
+ }
60
+ }
61
+ /**
62
+ * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
63
+ */
64
+ export function compareVersions(v1, v2) {
65
+ if (v1.major !== v2.major)
66
+ return v1.major - v2.major;
67
+ if (v1.minor !== v2.minor)
68
+ return v1.minor - v2.minor;
69
+ return v1.patch - v2.patch;
70
+ }
71
+ /**
72
+ * Get entries newer than lastVersion
73
+ */
74
+ export function getNewEntries(entries, lastVersion) {
75
+ // Parse lastVersion
76
+ const parts = lastVersion.split(".").map(Number);
77
+ const last = {
78
+ major: parts[0] || 0,
79
+ minor: parts[1] || 0,
80
+ patch: parts[2] || 0,
81
+ content: "",
82
+ };
83
+ return entries.filter((entry) => compareVersions(entry, last) > 0);
84
+ }
85
+ // Re-export getChangelogPath from paths.ts for convenience
86
+ export { getChangelogPath } from "../config.js";
@@ -0,0 +1,116 @@
1
+ import Clipboard from "@mariozechner/clipboard";
2
+ import { spawnSync } from "child_process";
3
+ const PREFERRED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
4
+ const DEFAULT_LIST_TIMEOUT_MS = 1000;
5
+ const DEFAULT_READ_TIMEOUT_MS = 3000;
6
+ const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
7
+ export function isWaylandSession(env = process.env) {
8
+ return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
9
+ }
10
+ function baseMimeType(mimeType) {
11
+ return mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
12
+ }
13
+ export function extensionForImageMimeType(mimeType) {
14
+ switch (baseMimeType(mimeType)) {
15
+ case "image/png":
16
+ return "png";
17
+ case "image/jpeg":
18
+ return "jpg";
19
+ case "image/webp":
20
+ return "webp";
21
+ case "image/gif":
22
+ return "gif";
23
+ default:
24
+ return null;
25
+ }
26
+ }
27
+ function selectPreferredImageMimeType(mimeTypes) {
28
+ const normalized = mimeTypes
29
+ .map((t) => t.trim())
30
+ .filter(Boolean)
31
+ .map((t) => ({ raw: t, base: baseMimeType(t) }));
32
+ for (const preferred of PREFERRED_IMAGE_MIME_TYPES) {
33
+ const match = normalized.find((t) => t.base === preferred);
34
+ if (match) {
35
+ return match.raw;
36
+ }
37
+ }
38
+ const anyImage = normalized.find((t) => t.base.startsWith("image/"));
39
+ return anyImage?.raw ?? null;
40
+ }
41
+ function runCommand(command, args, options) {
42
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;
43
+ const maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
44
+ const result = spawnSync(command, args, {
45
+ timeout: timeoutMs,
46
+ maxBuffer: maxBufferBytes,
47
+ });
48
+ if (result.error) {
49
+ return { ok: false, stdout: Buffer.alloc(0) };
50
+ }
51
+ if (result.status !== 0) {
52
+ return { ok: false, stdout: Buffer.alloc(0) };
53
+ }
54
+ const stdout = Buffer.isBuffer(result.stdout)
55
+ ? result.stdout
56
+ : Buffer.from(result.stdout ?? "", typeof result.stdout === "string" ? "utf-8" : undefined);
57
+ return { ok: true, stdout };
58
+ }
59
+ function readClipboardImageViaWlPaste() {
60
+ const list = runCommand("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
61
+ if (!list.ok) {
62
+ return null;
63
+ }
64
+ const types = list.stdout
65
+ .toString("utf-8")
66
+ .split(/\r?\n/)
67
+ .map((t) => t.trim())
68
+ .filter(Boolean);
69
+ const selectedType = selectPreferredImageMimeType(types);
70
+ if (!selectedType) {
71
+ return null;
72
+ }
73
+ const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
74
+ if (!data.ok || data.stdout.length === 0) {
75
+ return null;
76
+ }
77
+ return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
78
+ }
79
+ function readClipboardImageViaXclip() {
80
+ const targets = runCommand("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
81
+ timeoutMs: DEFAULT_LIST_TIMEOUT_MS,
82
+ });
83
+ let candidateTypes = [];
84
+ if (targets.ok) {
85
+ candidateTypes = targets.stdout
86
+ .toString("utf-8")
87
+ .split(/\r?\n/)
88
+ .map((t) => t.trim())
89
+ .filter(Boolean);
90
+ }
91
+ const preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;
92
+ const tryTypes = preferred ? [preferred, ...PREFERRED_IMAGE_MIME_TYPES] : [...PREFERRED_IMAGE_MIME_TYPES];
93
+ for (const mimeType of tryTypes) {
94
+ const data = runCommand("xclip", ["-selection", "clipboard", "-t", mimeType, "-o"]);
95
+ if (data.ok && data.stdout.length > 0) {
96
+ return { bytes: data.stdout, mimeType: baseMimeType(mimeType) };
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ export async function readClipboardImage(options) {
102
+ const env = options?.env ?? process.env;
103
+ const platform = options?.platform ?? process.platform;
104
+ if (platform === "linux" && isWaylandSession(env)) {
105
+ return readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
106
+ }
107
+ if (!Clipboard.hasImage()) {
108
+ return null;
109
+ }
110
+ const imageData = await Clipboard.getImageBinary();
111
+ if (!imageData || imageData.length === 0) {
112
+ return null;
113
+ }
114
+ const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
115
+ return { bytes, mimeType: "image/png" };
116
+ }