@abacus-ai/cli 2.0.0-canary.1 → 2.0.0-canary.11

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 (198) hide show
  1. package/README.md +25 -0
  2. package/dist/index.mjs +466 -438
  3. package/package.json +4 -1
  4. package/.oxlintrc.json +0 -8
  5. package/resources/abacus.ico +0 -0
  6. package/resources/entitlements.plist +0 -9
  7. package/src/__e2e__/README.md +0 -196
  8. package/src/__e2e__/agent-interactions.e2e.test.tsx +0 -61
  9. package/src/__e2e__/cli-commands.e2e.test.tsx +0 -77
  10. package/src/__e2e__/conversation-throttle.e2e.test.ts +0 -453
  11. package/src/__e2e__/conversation.e2e.test.tsx +0 -56
  12. package/src/__e2e__/diff-preview.e2e.test.tsx +0 -3399
  13. package/src/__e2e__/file-creation.e2e.test.tsx +0 -149
  14. package/src/__e2e__/helpers/test-helpers.ts +0 -449
  15. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +0 -34
  16. package/src/__e2e__/llm-models.e2e.test.ts +0 -402
  17. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +0 -71
  18. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +0 -167
  19. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +0 -185
  20. package/src/__e2e__/repl.e2e.test.tsx +0 -78
  21. package/src/__e2e__/shell-compatibility.e2e.test.tsx +0 -76
  22. package/src/__e2e__/theme-mcp.e2e.test.tsx +0 -98
  23. package/src/__e2e__/tool-permissions.e2e.test.tsx +0 -66
  24. package/src/args.ts +0 -22
  25. package/src/components/__tests__/react-compiler.test.tsx +0 -78
  26. package/src/components/__tests__/status-indicator.test.tsx +0 -403
  27. package/src/components/composer/__tests__/bash-runner.test.tsx +0 -263
  28. package/src/components/composer/agent-mode-indicator.tsx +0 -63
  29. package/src/components/composer/bash-runner.tsx +0 -54
  30. package/src/components/composer/commands/default-commands.tsx +0 -615
  31. package/src/components/composer/commands/handler.tsx +0 -59
  32. package/src/components/composer/commands/picker.tsx +0 -273
  33. package/src/components/composer/commands/registry.ts +0 -233
  34. package/src/components/composer/commands/types.ts +0 -33
  35. package/src/components/composer/context.tsx +0 -88
  36. package/src/components/composer/file-mention-picker.tsx +0 -83
  37. package/src/components/composer/help.tsx +0 -44
  38. package/src/components/composer/index.tsx +0 -1007
  39. package/src/components/composer/mentions.ts +0 -57
  40. package/src/components/composer/message-queue.tsx +0 -70
  41. package/src/components/composer/mode-panel.tsx +0 -35
  42. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +0 -755
  43. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +0 -1108
  44. package/src/components/composer/modes/bash-handler.tsx +0 -132
  45. package/src/components/composer/modes/bash-renderer.tsx +0 -175
  46. package/src/components/composer/modes/default-handlers.tsx +0 -33
  47. package/src/components/composer/modes/index.ts +0 -41
  48. package/src/components/composer/modes/types.ts +0 -21
  49. package/src/components/composer/persistent-shell.ts +0 -283
  50. package/src/components/composer/process.ts +0 -65
  51. package/src/components/composer/types.ts +0 -9
  52. package/src/components/composer/use-mention-search.ts +0 -68
  53. package/src/components/error-boundry.tsx +0 -60
  54. package/src/components/exit-message.tsx +0 -29
  55. package/src/components/expanded-view.tsx +0 -74
  56. package/src/components/file-completion.tsx +0 -127
  57. package/src/components/header.tsx +0 -47
  58. package/src/components/logo.tsx +0 -37
  59. package/src/components/segments.tsx +0 -356
  60. package/src/components/status-indicator.tsx +0 -306
  61. package/src/components/tool-group-summary.tsx +0 -263
  62. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +0 -319
  63. package/src/components/tool-permissions/diff-preview.tsx +0 -359
  64. package/src/components/tool-permissions/index.ts +0 -5
  65. package/src/components/tool-permissions/permission-options.tsx +0 -401
  66. package/src/components/tool-permissions/permission-preview-header.tsx +0 -57
  67. package/src/components/tool-permissions/tool-permission-ui.tsx +0 -420
  68. package/src/components/tools/agent/ask-user-question.tsx +0 -107
  69. package/src/components/tools/agent/enter-plan-mode.tsx +0 -55
  70. package/src/components/tools/agent/exit-plan-mode.tsx +0 -83
  71. package/src/components/tools/agent/handoff-to-main.tsx +0 -27
  72. package/src/components/tools/agent/subagent.tsx +0 -37
  73. package/src/components/tools/agent/todo-write.tsx +0 -104
  74. package/src/components/tools/browser/close-tab.tsx +0 -58
  75. package/src/components/tools/browser/computer.tsx +0 -70
  76. package/src/components/tools/browser/get-interactive-elements.tsx +0 -54
  77. package/src/components/tools/browser/get-tab-content.tsx +0 -51
  78. package/src/components/tools/browser/navigate-to.tsx +0 -59
  79. package/src/components/tools/browser/new-tab.tsx +0 -60
  80. package/src/components/tools/browser/perform-action.tsx +0 -63
  81. package/src/components/tools/browser/refresh-tab.tsx +0 -43
  82. package/src/components/tools/browser/switch-tab.tsx +0 -58
  83. package/src/components/tools/filesystem/delete-file.tsx +0 -104
  84. package/src/components/tools/filesystem/edit.tsx +0 -220
  85. package/src/components/tools/filesystem/list-dir.tsx +0 -78
  86. package/src/components/tools/filesystem/read-file.tsx +0 -180
  87. package/src/components/tools/filesystem/upload-image.tsx +0 -76
  88. package/src/components/tools/ide/ide-diagnostics.tsx +0 -62
  89. package/src/components/tools/index.ts +0 -91
  90. package/src/components/tools/mcp/mcp-tool.tsx +0 -158
  91. package/src/components/tools/search/fetch-url.tsx +0 -73
  92. package/src/components/tools/search/file-search.tsx +0 -78
  93. package/src/components/tools/search/grep.tsx +0 -90
  94. package/src/components/tools/search/semantic-search.tsx +0 -66
  95. package/src/components/tools/search/web-search.tsx +0 -71
  96. package/src/components/tools/shared/index.tsx +0 -48
  97. package/src/components/tools/shared/zod-coercion.ts +0 -35
  98. package/src/components/tools/terminal/bash-tool-output.tsx +0 -188
  99. package/src/components/tools/terminal/get-terminal-output.tsx +0 -91
  100. package/src/components/tools/terminal/run-in-terminal.tsx +0 -131
  101. package/src/components/tools/types.ts +0 -16
  102. package/src/components/tools.tsx +0 -68
  103. package/src/components/ui/__tests__/divider.test.tsx +0 -61
  104. package/src/components/ui/__tests__/gradient.test.tsx +0 -125
  105. package/src/components/ui/__tests__/input.test.tsx +0 -166
  106. package/src/components/ui/__tests__/select.test.tsx +0 -273
  107. package/src/components/ui/__tests__/shimmer.test.tsx +0 -99
  108. package/src/components/ui/blinking-indicator.tsx +0 -27
  109. package/src/components/ui/divider.tsx +0 -162
  110. package/src/components/ui/gradient.tsx +0 -56
  111. package/src/components/ui/input.tsx +0 -228
  112. package/src/components/ui/select.tsx +0 -151
  113. package/src/components/ui/shimmer.tsx +0 -76
  114. package/src/context/agent-mode.tsx +0 -95
  115. package/src/context/extension-file.tsx +0 -136
  116. package/src/context/network-activity.tsx +0 -45
  117. package/src/context/notification.tsx +0 -62
  118. package/src/context/shell-size.tsx +0 -49
  119. package/src/context/shell-title.tsx +0 -38
  120. package/src/entrypoints/print-mode.ts +0 -312
  121. package/src/entrypoints/repl.tsx +0 -389
  122. package/src/hooks/use-agent.ts +0 -15
  123. package/src/hooks/use-api-client.ts +0 -1
  124. package/src/hooks/use-available-height.ts +0 -8
  125. package/src/hooks/use-cleanup.ts +0 -29
  126. package/src/hooks/use-interrupt-manager.ts +0 -242
  127. package/src/hooks/use-models.ts +0 -22
  128. package/src/index.ts +0 -217
  129. package/src/lib/__tests__/ansi.test.ts +0 -255
  130. package/src/lib/__tests__/cli.test.ts +0 -122
  131. package/src/lib/__tests__/commands.test.ts +0 -325
  132. package/src/lib/__tests__/constants.test.ts +0 -15
  133. package/src/lib/__tests__/focusables.test.ts +0 -25
  134. package/src/lib/__tests__/fs.test.ts +0 -231
  135. package/src/lib/__tests__/markdown.test.tsx +0 -348
  136. package/src/lib/__tests__/mcpCommandHandler.test.ts +0 -173
  137. package/src/lib/__tests__/mcpManagement.test.ts +0 -38
  138. package/src/lib/__tests__/path-paste.test.ts +0 -144
  139. package/src/lib/__tests__/path.test.ts +0 -300
  140. package/src/lib/__tests__/queries.test.ts +0 -39
  141. package/src/lib/__tests__/standaloneMcpService.test.ts +0 -71
  142. package/src/lib/__tests__/text-buffer.test.ts +0 -328
  143. package/src/lib/__tests__/text-utils.test.ts +0 -32
  144. package/src/lib/__tests__/timing.test.ts +0 -78
  145. package/src/lib/__tests__/utils.test.ts +0 -238
  146. package/src/lib/__tests__/vim-buffer-actions.test.ts +0 -154
  147. package/src/lib/ansi.ts +0 -150
  148. package/src/lib/cli-push-server.ts +0 -112
  149. package/src/lib/cli.ts +0 -44
  150. package/src/lib/clipboard.ts +0 -226
  151. package/src/lib/command-utils.ts +0 -93
  152. package/src/lib/commands.ts +0 -270
  153. package/src/lib/constants.ts +0 -3
  154. package/src/lib/extension-connection.ts +0 -181
  155. package/src/lib/focusables.ts +0 -7
  156. package/src/lib/fs.ts +0 -533
  157. package/src/lib/markdown/code-block.tsx +0 -63
  158. package/src/lib/markdown/index.ts +0 -4
  159. package/src/lib/markdown/link.tsx +0 -19
  160. package/src/lib/markdown/markdown.tsx +0 -372
  161. package/src/lib/markdown/types.ts +0 -15
  162. package/src/lib/mcpCommandHandler.ts +0 -121
  163. package/src/lib/mcpManagement.ts +0 -44
  164. package/src/lib/path-paste.ts +0 -185
  165. package/src/lib/path.ts +0 -179
  166. package/src/lib/queries.ts +0 -15
  167. package/src/lib/standaloneMcpService.ts +0 -688
  168. package/src/lib/status-utils.ts +0 -237
  169. package/src/lib/test-utils.tsx +0 -72
  170. package/src/lib/text-buffer.ts +0 -2415
  171. package/src/lib/text-utils.ts +0 -272
  172. package/src/lib/timing.ts +0 -63
  173. package/src/lib/types.ts +0 -295
  174. package/src/lib/utils.ts +0 -182
  175. package/src/lib/vim-buffer-actions.ts +0 -732
  176. package/src/providers/agent.tsx +0 -1063
  177. package/src/providers/api-client.tsx +0 -43
  178. package/src/services/logger.ts +0 -85
  179. package/src/terminal/detection.ts +0 -187
  180. package/src/terminal/exit.ts +0 -279
  181. package/src/terminal/notification.ts +0 -83
  182. package/src/terminal/progress.ts +0 -201
  183. package/src/terminal/setup.ts +0 -797
  184. package/src/terminal/types.ts +0 -51
  185. package/src/theme/context.tsx +0 -57
  186. package/src/theme/index.ts +0 -4
  187. package/src/theme/themed.tsx +0 -35
  188. package/src/theme/themes.json +0 -546
  189. package/src/theme/types.ts +0 -110
  190. package/src/tools/types.ts +0 -59
  191. package/src/tools/utils/__tests__/zod-coercion.test.ts +0 -33
  192. package/src/tools/utils/tool-ui-components.tsx +0 -649
  193. package/src/tools/utils/zod-coercion.ts +0 -35
  194. package/tsconfig.json +0 -16
  195. package/tsconfig.node.json +0 -29
  196. package/tsconfig.test.json +0 -27
  197. package/tsdown.config.ts +0 -17
  198. package/vitest.config.ts +0 -76
@@ -1,238 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import { describe, expect, it } from "vitest";
4
-
5
- import { clamp, deepClone, createDeferredPromise, getSegmentContent, isWindows } from "../utils.js";
6
-
7
- describe.concurrent("utils", () => {
8
- describe.concurrent("clamp", () => {
9
- it("should return value when within range", () => {
10
- expect(clamp(5, 0, 10)).toBe(5);
11
- });
12
-
13
- it("should return min when value is less than min", () => {
14
- expect(clamp(-5, 0, 10)).toBe(0);
15
- });
16
-
17
- it("should return max when value is greater than max", () => {
18
- expect(clamp(15, 0, 10)).toBe(10);
19
- });
20
-
21
- it("should handle edge cases", () => {
22
- expect(clamp(0, 0, 10)).toBe(0);
23
- expect(clamp(10, 0, 10)).toBe(10);
24
- });
25
-
26
- it("should handle negative ranges", () => {
27
- expect(clamp(-15, -10, -5)).toBe(-10);
28
- expect(clamp(-3, -10, -5)).toBe(-5);
29
- expect(clamp(-7, -10, -5)).toBe(-7);
30
- });
31
- });
32
-
33
- describe.concurrent("deepClone", () => {
34
- it("should clone primitive values", () => {
35
- expect(deepClone(5)).toBe(5);
36
- expect(deepClone("test")).toBe("test");
37
- expect(deepClone(true)).toBe(true);
38
- expect(deepClone(null)).toBe(null);
39
- expect(deepClone(undefined)).toBe(undefined);
40
- });
41
-
42
- it("should clone arrays", () => {
43
- const original = [1, 2, 3];
44
- const cloned = deepClone(original);
45
- expect(cloned).toEqual(original);
46
- expect(cloned).not.toBe(original);
47
- cloned.push(4);
48
- expect(original).toHaveLength(3);
49
- });
50
-
51
- it("should clone objects", () => {
52
- const original: Record<string, number> = { a: 1, b: 2 };
53
- const cloned = deepClone(original);
54
- expect(cloned).toEqual(original);
55
- expect(cloned).not.toBe(original);
56
- cloned.c = 3;
57
- expect(original).not.toHaveProperty("c");
58
- });
59
-
60
- it("should deep clone nested objects", () => {
61
- const original = { a: { b: { c: 1 } } };
62
- const cloned = deepClone(original);
63
- expect(cloned).toEqual(original);
64
- expect(cloned.a).not.toBe(original.a);
65
- expect(cloned.a.b).not.toBe(original.a.b);
66
- cloned.a.b.c = 2;
67
- expect(original.a.b.c).toBe(1);
68
- });
69
-
70
- it("should deep clone nested arrays", () => {
71
- const original = [
72
- [1, 2],
73
- [3, 4],
74
- ];
75
- const cloned = deepClone(original);
76
- expect(cloned).toEqual(original);
77
- expect(cloned).not.toBe(original);
78
- expect(cloned[0]).not.toBe(original[0]);
79
- cloned[0].push(5);
80
- expect(original[0]).toHaveLength(2);
81
- });
82
-
83
- it("should handle mixed nested structures", () => {
84
- const original = { a: [1, { b: 2 }], c: { d: [3, 4] } };
85
- const cloned = deepClone(original);
86
- expect(cloned).toEqual(original);
87
- (cloned.a[1] as { b: number }).b = 5;
88
- expect((original.a[1] as { b: number }).b).toBe(2);
89
- });
90
-
91
- it("should return RegExp as-is", () => {
92
- // TODO: LIMITATION - RegExp objects are returned as-is (not cloned).
93
- // This means mutations to the cloned object's regex will affect the original.
94
- // Consider: Whether this is intentional or if RegExp should be cloned.
95
- const regex = /test/gi;
96
- const cloned = deepClone(regex);
97
- expect(cloned).toBe(regex);
98
- });
99
-
100
- it("should handle empty objects and arrays", () => {
101
- expect(deepClone({})).toEqual({});
102
- expect(deepClone([])).toEqual([]);
103
- });
104
-
105
- it("should handle Date objects", () => {
106
- // TODO: BUG - deepClone does not properly handle Date objects.
107
- // Dates will be cloned as plain objects, losing their Date prototype methods.
108
- // Fix: Add Date handling: if (obj instanceof Date) return new Date(obj.getTime())
109
- const date = new Date("2023-01-01");
110
- const cloned = deepClone(date);
111
- // Current behavior: cloned is a plain object, not a Date
112
- expect(cloned).not.toBeInstanceOf(Date);
113
- expect(typeof cloned).toBe("object");
114
- });
115
-
116
- it("should handle circular references", () => {
117
- // TODO: BUG - deepClone will cause infinite recursion with circular references.
118
- // Fix: Add a WeakSet to track visited objects and skip circular references.
119
- // This test documents the current broken behavior - it will stack overflow.
120
- const obj: any = { a: 1 };
121
- obj.self = obj; // Circular reference
122
- // Note: This will cause a stack overflow in the current implementation
123
- // Uncomment the expect below to verify the bug, but it will crash the test
124
- // expect(() => deepClone(obj)).toThrow();
125
- });
126
-
127
- it("should handle Map and Set", () => {
128
- // TODO: LIMITATION - deepClone does not handle Map or Set objects.
129
- // They will be cloned as plain objects, losing their Map/Set behavior.
130
- // Fix: Add Map/Set handling similar to arrays.
131
- const map = new Map([["key", "value"]]);
132
- const cloned = deepClone(map);
133
- expect(cloned).not.toBeInstanceOf(Map);
134
- expect(typeof cloned).toBe("object");
135
- });
136
- });
137
-
138
- describe.concurrent("createDeferredPromise", () => {
139
- it("should create a promise with resolve and reject functions", async () => {
140
- const deferred = createDeferredPromise<string>();
141
- expect(deferred).toHaveProperty("promise");
142
- expect(deferred).toHaveProperty("resolve");
143
- expect(deferred).toHaveProperty("reject");
144
- expect(typeof deferred.resolve).toBe("function");
145
- expect(typeof deferred.reject).toBe("function");
146
- });
147
-
148
- it("should resolve the promise when resolve is called", async () => {
149
- const deferred = createDeferredPromise<string>();
150
- deferred.resolve("test");
151
- await expect(deferred.promise).resolves.toBe("test");
152
- });
153
-
154
- it("should reject the promise when reject is called", async () => {
155
- const deferred = createDeferredPromise<string>();
156
- const error = new Error("test error");
157
- deferred.reject(error);
158
- await expect(deferred.promise).rejects.toBe(error);
159
- });
160
-
161
- it("should work with void type", async () => {
162
- const deferred = createDeferredPromise<void>();
163
- deferred.resolve();
164
- await expect(deferred.promise).resolves.toBeUndefined();
165
- });
166
-
167
- it("should work with object types", async () => {
168
- const deferred = createDeferredPromise<{ value: number }>();
169
- deferred.resolve({ value: 42 });
170
- await expect(deferred.promise).resolves.toEqual({ value: 42 });
171
- });
172
- });
173
-
174
- describe.concurrent("getSegmentContent", () => {
175
- it("should return string when segment is a string", () => {
176
- expect(getSegmentContent("test")).toBe("test");
177
- });
178
-
179
- it("should return segment property when segment is an object", () => {
180
- expect(getSegmentContent({ segment: "test" })).toBe("test");
181
- });
182
-
183
- it("should return null when segment is undefined", () => {
184
- expect(getSegmentContent(undefined)).toBe(null);
185
- });
186
-
187
- it("should return null when segment is null", () => {
188
- expect(getSegmentContent(null as unknown as undefined)).toBe(null);
189
- });
190
-
191
- it("should return null when segment is an object without segment property", () => {
192
- expect(getSegmentContent({ other: "value" } as unknown as { segment: string })).toBe(null);
193
- });
194
-
195
- it("should handle empty strings", () => {
196
- expect(getSegmentContent("")).toBe("");
197
- expect(getSegmentContent({ segment: "" })).toBe("");
198
- });
199
- });
200
-
201
- describe.concurrent("isWindows", () => {
202
- it("should return true on Windows platform", () => {
203
- const originalPlatform = process.platform;
204
- Object.defineProperty(process, "platform", {
205
- value: "win32",
206
- writable: true,
207
- configurable: true,
208
- });
209
- expect(isWindows()).toBe(true);
210
- Object.defineProperty(process, "platform", {
211
- value: originalPlatform,
212
- writable: true,
213
- configurable: true,
214
- });
215
- });
216
-
217
- it("should return false on non-Windows platforms", () => {
218
- const originalPlatform = process.platform;
219
- Object.defineProperty(process, "platform", {
220
- value: "linux",
221
- writable: true,
222
- configurable: true,
223
- });
224
- expect(isWindows()).toBe(false);
225
- Object.defineProperty(process, "platform", {
226
- value: "darwin",
227
- writable: true,
228
- configurable: true,
229
- });
230
- expect(isWindows()).toBe(false);
231
- Object.defineProperty(process, "platform", {
232
- value: originalPlatform,
233
- writable: true,
234
- configurable: true,
235
- });
236
- });
237
- });
238
- });
@@ -1,154 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import { describe, expect, it } from "vitest";
4
-
5
- import type { TextBufferState } from "../text-buffer.js";
6
-
7
- import { handleVimAction, assumeExhaustive } from "../vim-buffer-actions.js";
8
-
9
- describe.concurrent("vim-buffer-actions", () => {
10
- const createState = (
11
- lines: string[] = ["hello world"],
12
- cursorRow = 0,
13
- cursorCol = 0,
14
- ): TextBufferState => ({
15
- lines,
16
- cursorRow,
17
- cursorCol,
18
- preferredCol: null,
19
- undoStack: [],
20
- redoStack: [],
21
- clipboard: null,
22
- selectionAnchor: null,
23
- viewportWidth: 80,
24
- viewportHeight: 24,
25
- visualLayout: {
26
- visualLines: lines,
27
- logicalToVisualMap: lines.map((_, i) => [[i, 0]]),
28
- visualToLogicalMap: lines.map((_, i) => [i, 0]),
29
- },
30
- });
31
-
32
- describe.concurrent("assumeExhaustive", () => {
33
- it("should be a function", () => {
34
- expect(typeof assumeExhaustive).toBe("function");
35
- });
36
-
37
- it("should not throw when called", () => {
38
- expect(() => assumeExhaustive(null as never)).not.toThrow();
39
- });
40
- });
41
-
42
- describe.concurrent("handleVimAction", () => {
43
- it("should handle vim_move_left", () => {
44
- const state = createState(["hello"], 0, 5);
45
- const newState = handleVimAction(state, { type: "vim_move_left", payload: { count: 1 } });
46
- expect(newState.cursorCol).toBe(4);
47
- });
48
-
49
- it("should handle vim_move_right", () => {
50
- const state = createState(["hello"], 0, 0);
51
- const newState = handleVimAction(state, { type: "vim_move_right", payload: { count: 1 } });
52
- expect(newState.cursorCol).toBe(1);
53
- });
54
-
55
- it("should handle vim_move_to_line_start", () => {
56
- const state = createState(["hello world"], 0, 5);
57
- const newState = handleVimAction(state, { type: "vim_move_to_line_start" });
58
- expect(newState.cursorCol).toBe(0);
59
- });
60
-
61
- it("should handle vim_move_to_line_end", () => {
62
- // TODO: IMPROVEMENT - Test should verify exact position. For "hello" (5 chars),
63
- // cursor should be at position 4 (last character), not just > 0.
64
- // Current implementation: lineLength > 0 ? lineLength - 1 : 0
65
- const state = createState(["hello"], 0, 0);
66
- const newState = handleVimAction(state, { type: "vim_move_to_line_end" });
67
- expect(newState.cursorCol).toBeGreaterThan(0);
68
- });
69
-
70
- it("should handle vim_move_to_first_nonwhitespace", () => {
71
- const state = createState([" hello"], 0, 0);
72
- const newState = handleVimAction(state, { type: "vim_move_to_first_nonwhitespace" });
73
- expect(newState.cursorCol).toBe(3);
74
- });
75
-
76
- it("should handle vim_move_to_first_line", () => {
77
- const state = createState(["line1", "line2", "line3"], 2, 0);
78
- const newState = handleVimAction(state, { type: "vim_move_to_first_line" });
79
- expect(newState.cursorRow).toBe(0);
80
- });
81
-
82
- it("should handle vim_move_to_last_line", () => {
83
- const state = createState(["line1", "line2", "line3"], 0, 0);
84
- const newState = handleVimAction(state, { type: "vim_move_to_last_line" });
85
- expect(newState.cursorRow).toBe(2);
86
- });
87
-
88
- it("should handle vim_move_to_line", () => {
89
- // TODO: CLARIFICATION - The API is confusing: lineNumber is 1-indexed (user-facing)
90
- // but internally converted to 0-indexed. Consider documenting this clearly or
91
- // using a more explicit parameter name like `lineNumberOneIndexed`.
92
- // Current behavior: lineNumber 2 -> row index 1 (second line)
93
- const state = createState(["line1", "line2", "line3"], 0, 0);
94
- const newState = handleVimAction(state, {
95
- type: "vim_move_to_line",
96
- payload: { lineNumber: 2 },
97
- });
98
- expect(newState.cursorRow).toBe(1); // 0-indexed
99
- });
100
-
101
- it("should handle vim_delete_char", () => {
102
- const state = createState(["hello"], 0, 2);
103
- const newState = handleVimAction(state, { type: "vim_delete_char", payload: { count: 1 } });
104
- expect(newState.lines[0].length).toBeLessThan(state.lines[0].length);
105
- });
106
-
107
- it("should handle vim_move_word_forward", () => {
108
- const state = createState(["hello world test"], 0, 0);
109
- const newState = handleVimAction(state, {
110
- type: "vim_move_word_forward",
111
- payload: { count: 1 },
112
- });
113
- expect(newState.cursorCol).toBeGreaterThan(0);
114
- });
115
-
116
- it("should handle vim_move_word_backward", () => {
117
- const state = createState(["hello world"], 0, 11);
118
- const newState = handleVimAction(state, {
119
- type: "vim_move_word_backward",
120
- payload: { count: 1 },
121
- });
122
- expect(newState.cursorCol).toBeLessThan(11);
123
- });
124
-
125
- it("should handle vim_delete_word_forward", () => {
126
- const state = createState(["hello world"], 0, 0);
127
- const newState = handleVimAction(state, {
128
- type: "vim_delete_word_forward",
129
- payload: { count: 1 },
130
- });
131
- expect(newState.lines[0]).not.toContain("hello");
132
- });
133
-
134
- it("should handle vim_delete_to_end_of_line", () => {
135
- const state = createState(["hello world"], 0, 5);
136
- const newState = handleVimAction(state, { type: "vim_delete_to_end_of_line" });
137
- expect(newState.lines[0]).toBe("hello");
138
- });
139
-
140
- it("should handle vim_open_line_below", () => {
141
- const state = createState(["line1"], 0, 5);
142
- const newState = handleVimAction(state, { type: "vim_open_line_below" });
143
- expect(newState.lines.length).toBe(2);
144
- expect(newState.cursorRow).toBe(1);
145
- });
146
-
147
- it("should handle vim_open_line_above", () => {
148
- const state = createState(["line1"], 0, 5);
149
- const newState = handleVimAction(state, { type: "vim_open_line_above" });
150
- expect(newState.lines.length).toBe(2);
151
- expect(newState.cursorRow).toBe(0);
152
- });
153
- });
154
- });
package/src/lib/ansi.ts DELETED
@@ -1,150 +0,0 @@
1
- export enum ANSI {
2
- ESC = "\x1b",
3
- BEL = "\x07",
4
- ST = "\x1b\\",
5
- ENABLE_FOCUS_REPORTING = `${ANSI.ESC}[?1004h`,
6
- DISABLE_FOCUS_REPORTING = `${ANSI.ESC}[?1004l`,
7
- FOCUS_IN = `${ANSI.ESC}[I`,
8
- FOCUS_OUT = `${ANSI.ESC}[O`,
9
- SHOW_CURSOR = `${ANSI.ESC}[?25h`,
10
- HIDE_CURSOR = `${ANSI.ESC}[?25l`,
11
- SET_TITLE = `${ANSI.ESC}]2;`,
12
- RESET_SGR = `${ANSI.ESC}[0m`,
13
- SOFT_RESET = `${ANSI.ESC}[!p`,
14
- HARD_RESET = `${ANSI.ESC}c`,
15
- ENABLE_BRACKETED_PASTE = `${ANSI.ESC}[?2004h`,
16
- DISABLE_BRACKETED_PASTE = `${ANSI.ESC}[?2004l`,
17
- PASTE_MODE_PREFIX = `${ANSI.ESC}[200~`,
18
- PASTE_MODE_SUFFIX = `${ANSI.ESC}[201~`,
19
- DIM = `${ANSI.ESC}[2m`,
20
- BOLD = `${ANSI.ESC}[1m`,
21
- RESET_BOLD = `${ANSI.ESC}[22m`,
22
- ITALIC = `${ANSI.ESC}[3m`,
23
- RESET_ITALIC = `${ANSI.ESC}[23m`,
24
- STRIKETHROUGH = `${ANSI.ESC}[9m`,
25
- RESET_STRIKETHROUGH = `${ANSI.ESC}[29m`,
26
- UNDERLINE = `${ANSI.ESC}[4m`,
27
- RESET_UNDERLINE = `${ANSI.ESC}[24m`,
28
- }
29
-
30
- /**
31
- * OSC 9 - iTerm2 notification
32
- * Format: ESC ] 9 ; message BEL
33
- */
34
- export function osc9(message: string): string {
35
- return `${ANSI.ESC}]9;\n\n${message}${ANSI.BEL}`;
36
- }
37
-
38
- /**
39
- * OSC 777 - Ghostty/urxvt notification
40
- * Format: ESC ] 777 ; notify ; title ; body BEL
41
- */
42
- export function osc777(title: string, body: string): string {
43
- return `${ANSI.ESC}]777;notify;${title};${body}${ANSI.BEL}`;
44
- }
45
-
46
- /**
47
- * OSC 99 - Kitty desktop notification
48
- * Format: ESC ] 99 ; metadata ; payload ST
49
- */
50
- export function osc99(title: string, body: string): string {
51
- const notificationId = Math.floor(Math.random() * 10000);
52
- const titlePart = `${ANSI.ESC}]99;i=${notificationId}:d=0:p=title;${title}${ANSI.ST}`;
53
- const bodyPart = `${ANSI.ESC}]99;i=${notificationId}:p=body;${body}${ANSI.ST}`;
54
- const donePart = `${ANSI.ESC}]99;i=${notificationId}:d=1:a=focus;${ANSI.ST}`;
55
- return titlePart + bodyPart + donePart;
56
- }
57
-
58
- export type ProgressState = 0 | 1 | 2 | 3 | 4;
59
-
60
- /**
61
- * OSC 9;4 - Progress bar (ConEmu/Ghostty/iTerm2 style)
62
- * Format: ESC ] 9 ; 4 ; state ; progress [BEL/ST]
63
- * @param state - 0 (hide), 1 (default), 2 (error), 3 (indeterminate), 4 (warning)
64
- * @param progress - Progress percentage (0-100), required when state is 1, 2, or 4
65
- */
66
- export function osc9Progress(state: ProgressState, progress?: number): string {
67
- const p = progress !== undefined ? Math.max(0, Math.min(100, Math.round(progress))) : 0;
68
- const payload = `9;4;${state};${p}`;
69
- // Emit both terminators for maximum compatibility (iTerm2 prefers BEL, Ghostty prefers ST)
70
- return `${ANSI.ESC}]${payload}${ANSI.BEL}${ANSI.ESC}]${payload}${ANSI.ST}`;
71
- }
72
-
73
- /**
74
- * OSC 1337 - iTerm2 Progress indicator
75
- */
76
- function sanitizeItermValue(value: string): string {
77
- return value
78
- .replaceAll("\x1b", "")
79
- .replaceAll("\x07", "")
80
- .replaceAll("\n", " ")
81
- .replaceAll("\r", " ")
82
- .replaceAll(";", ":")
83
- .trim()
84
- .slice(0, 120);
85
- }
86
-
87
- function itermOsc(payload: string): string {
88
- // Emit both terminators for maximum compatibility: BEL and ST.
89
- return `${ANSI.ESC}]${payload}${ANSI.BEL}${ANSI.ESC}]${payload}${ANSI.ST}`;
90
- }
91
-
92
- export function osc1337ProgressClear(): string {
93
- return itermOsc("1337;Progress=");
94
- }
95
-
96
- export function osc1337ProgressState(state: 0 | 1 | 2): string {
97
- return itermOsc(`1337;Progress=${state}`);
98
- }
99
-
100
- export function osc1337ProgressPercent(percent: number, message?: string): string {
101
- const progressValue = Math.max(0, Math.min(100, Math.round(percent)));
102
- const msg = message ? sanitizeItermValue(message) : "";
103
-
104
- if (msg) {
105
- return (
106
- itermOsc(`1337;Progress=${progressValue};${msg}`) +
107
- itermOsc(`1337;Progress=message=${msg};percent=${progressValue}`)
108
- );
109
- }
110
-
111
- return itermOsc(`1337;Progress=${progressValue}`);
112
- }
113
-
114
- /**
115
- * Creates a regex pattern that matches ANSI escape sequences
116
- * @param options.onlyFirst - If true, matches only the first occurrence
117
- * @returns Regular expression for matching ANSI sequences
118
- */
119
- export function ansiRegex({ onlyFirst = false } = {}): RegExp {
120
- // Valid string terminator sequences are BEL, ESC\, and 0x9c
121
- const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
122
-
123
- // OSC sequences only: ESC ] ... ST (non-greedy until the first ST)
124
- const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
125
-
126
- // CSI and related: ESC/C1, optional intermediates, optional params (supports ; and :) then final byte
127
- const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
128
-
129
- const pattern = `${osc}|${csi}`;
130
-
131
- return new RegExp(pattern, onlyFirst ? undefined : "g");
132
- }
133
-
134
- const stripAnsiRegex = ansiRegex();
135
-
136
- /**
137
- * Strip ANSI escape codes from a string
138
- * @param string - The string to strip ANSI codes from
139
- * @returns The string with ANSI codes removed
140
- */
141
- export function stripAnsi(string: string): string {
142
- if (typeof string !== "string") {
143
- throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
144
- }
145
-
146
- // Even though the regex is global, we don't need to reset the `.lastIndex`
147
- // because unlike `.exec()` and `.test()`, `.replace()` does it automatically
148
- // and doing it manually has a performance penalty.
149
- return string.replace(stripAnsiRegex, "");
150
- }
@@ -1,112 +0,0 @@
1
- import type { SocketServer } from "@codellm/comms";
2
-
3
- import { cliContract, createSocketServer } from "@codellm/comms";
4
- import { product } from "@codellm/product";
5
- import { implement } from "@orpc/server";
6
- import { StandardRPCHandler } from "@orpc/server/standard";
7
- import * as fs from "node:fs";
8
- import * as os from "node:os";
9
- import * as path from "node:path";
10
-
11
- export interface EditorState {
12
- file: string | undefined;
13
- startLine: number | undefined;
14
- endLine: number | undefined;
15
- }
16
-
17
- export type ToolDecisionValue = "accept" | "reject" | "allowAlways";
18
-
19
- export class CliPushServer {
20
- private socketServer: SocketServer | undefined;
21
- readonly socketPath: string;
22
- private editorState: EditorState = {
23
- file: undefined,
24
- startLine: undefined,
25
- endLine: undefined,
26
- };
27
- private onEditorStateCallback: ((state: EditorState) => void) | null = null;
28
- private pendingDecisions = new Map<
29
- string,
30
- { resolve: (d: ToolDecisionValue) => void; cleanup: () => void }
31
- >();
32
-
33
- constructor() {
34
- const socketDir = path.join(os.homedir(), product.configDirName, "cli");
35
- this.socketPath = path.join(socketDir, `${process.pid}.sock`);
36
- }
37
-
38
- getEditorState(): EditorState {
39
- return { ...this.editorState };
40
- }
41
-
42
- onEditorChange(callback: (state: EditorState) => void): void {
43
- this.onEditorStateCallback = callback;
44
- }
45
-
46
- waitForDecision(toolId: string, signal?: AbortSignal): Promise<ToolDecisionValue> {
47
- return new Promise<ToolDecisionValue>((resolve, reject) => {
48
- if (signal?.aborted) {
49
- reject(new Error("Aborted"));
50
- return;
51
- }
52
-
53
- const cleanup = () => {
54
- signal?.removeEventListener("abort", onAbort);
55
- this.pendingDecisions.delete(toolId);
56
- };
57
- const onAbort = () => {
58
- cleanup();
59
- reject(new Error("Aborted"));
60
- };
61
- signal?.addEventListener("abort", onAbort, { once: true });
62
-
63
- this.pendingDecisions.set(toolId, {
64
- resolve: (d) => {
65
- cleanup();
66
- resolve(d);
67
- },
68
- cleanup,
69
- });
70
- });
71
- }
72
-
73
- async start(): Promise<void> {
74
- const socketDir = path.dirname(this.socketPath);
75
- if (!fs.existsSync(socketDir)) {
76
- fs.mkdirSync(socketDir, { recursive: true });
77
- }
78
-
79
- const cc = implement(cliContract);
80
-
81
- type EditorStateInput = { file: string | null; startLine?: number; endLine?: number };
82
- type ToolDecisionInput = { toolId: string; decision: "accept" | "reject" | "allowAlways" };
83
-
84
- const router = cc.router({
85
- editorStateChanged: cc.editorStateChanged.handler(
86
- async ({ input }: { input: EditorStateInput }) => {
87
- this.editorState = {
88
- file: input.file ?? undefined,
89
- startLine: input.startLine,
90
- endLine: input.endLine,
91
- };
92
- this.onEditorStateCallback?.(this.editorState);
93
- return { ok: true };
94
- },
95
- ),
96
- toolDecision: cc.toolDecision.handler(async ({ input }: { input: ToolDecisionInput }) => {
97
- const pending = this.pendingDecisions.get(input.toolId);
98
- if (pending) pending.resolve(input.decision);
99
- return { ok: true };
100
- }),
101
- });
102
-
103
- this.socketServer = await createSocketServer(this.socketPath, new StandardRPCHandler(router));
104
- }
105
-
106
- dispose(): void {
107
- for (const [, entry] of this.pendingDecisions) entry.cleanup();
108
- this.pendingDecisions.clear();
109
- this.socketServer?.dispose();
110
- this.socketServer = undefined;
111
- }
112
- }
package/src/lib/cli.ts DELETED
@@ -1,44 +0,0 @@
1
- import { spawn } from "child_process";
2
-
3
- export async function* runCli(command: string, args: string[]): AsyncGenerator<string> {
4
- const proc = spawn(command, args, {
5
- stdio: ["ignore", "pipe", "pipe"],
6
- cwd: process.cwd(),
7
- });
8
-
9
- const streams = [proc.stdout, proc.stderr];
10
-
11
- const readers = streams.map((stream) => stream[Symbol.asyncIterator]());
12
-
13
- try {
14
- while (true) {
15
- const results = await Promise.all(readers.map((reader) => reader.next()));
16
- let doneCount = 0;
17
- for (let i = 0; i < results.length; i++) {
18
- const { value, done } = results[i];
19
- if (done) {
20
- doneCount++;
21
- continue;
22
- }
23
- if (value) {
24
- yield value.toString();
25
- }
26
- }
27
- if (doneCount === readers.length) {
28
- break;
29
- }
30
- }
31
- } finally {
32
- // Attempt graceful shutdown
33
- proc.kill("SIGTERM");
34
-
35
- const timeout = setTimeout(() => {
36
- // Force termination if still running
37
- if (!proc.killed) {
38
- proc.kill("SIGKILL");
39
- }
40
- }, 5000);
41
-
42
- proc.on("close", () => clearTimeout(timeout));
43
- }
44
- }