@cueloop/client 0.1.0-alpha.0 → 0.1.0-alpha.100

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 (345) hide show
  1. package/native/build-pty.sh +51 -0
  2. package/native/build.sh +82 -0
  3. package/native/darwin-arm64/libcuelooppty.dylib +0 -0
  4. package/native/darwin-arm64/libcueloopvt.dylib +0 -0
  5. package/native/src/pty.zig +193 -0
  6. package/native/src/shim.zig +114 -0
  7. package/package.json +41 -27
  8. package/src/annotations/annotation-palette.ts +62 -0
  9. package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
  10. package/src/annotations/components/AnnotationCards.test.tsx +42 -0
  11. package/src/annotations/components/AnnotationCards.tsx +364 -0
  12. package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
  13. package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
  14. package/src/annotations/discussions.ts +77 -0
  15. package/src/annotations/thread-selection.test.ts +210 -0
  16. package/src/annotations/thread-selection.ts +242 -0
  17. package/src/annotations/use-annotation-surface.tsx +1238 -0
  18. package/src/app/App.appearance.test.tsx +53 -0
  19. package/src/app/App.autoclose.test.tsx +144 -0
  20. package/src/app/App.deleted-scroll.test.tsx +187 -0
  21. package/src/app/App.diff.test.tsx +313 -0
  22. package/src/app/App.export.test.tsx +105 -0
  23. package/src/app/App.focus.test.ts +89 -0
  24. package/src/app/App.identity.test.tsx +84 -0
  25. package/src/app/App.plan-render.test.tsx +209 -0
  26. package/src/app/App.plan-review.test.tsx +428 -0
  27. package/src/app/App.prototype-markdown.test.tsx +128 -0
  28. package/src/app/App.prototype.test.tsx +176 -0
  29. package/src/app/App.readonly.test.tsx +210 -0
  30. package/src/app/App.regression.test.tsx +99 -0
  31. package/src/app/App.rendering.test.tsx +134 -0
  32. package/src/app/App.share-dialog.test.tsx +177 -0
  33. package/src/app/App.submit-confirm.test.tsx +267 -0
  34. package/src/app/App.test.tsx +496 -0
  35. package/src/app/App.thread-agent.test.ts +12 -0
  36. package/src/app/App.toast.test.tsx +124 -0
  37. package/src/app/App.tsx +1650 -0
  38. package/src/app/App.visual.test.tsx +59 -0
  39. package/src/app/App.walk.test.tsx +299 -0
  40. package/src/app/App.workbench.test.tsx +1149 -0
  41. package/src/app/app-key-state.ts +61 -0
  42. package/src/app/app-screens.tsx +606 -0
  43. package/src/app/app-view-model.ts +168 -0
  44. package/src/app/components/AppHeader.stories.tsx +50 -0
  45. package/src/app/components/AppHeader.tsx +81 -0
  46. package/src/app/components/AppShell.brand.test.tsx +58 -0
  47. package/src/app/components/AppShell.extensions.test.tsx +191 -0
  48. package/src/app/components/AppShell.stories.tsx +89 -0
  49. package/src/app/components/AppShell.tsx +358 -0
  50. package/src/app/components/AppShell.zoom.test.tsx +149 -0
  51. package/src/app/components/Breadcrumb.stories.tsx +19 -0
  52. package/src/app/components/Breadcrumb.tsx +45 -0
  53. package/src/app/components/MenuBar.stories.tsx +28 -0
  54. package/src/app/components/MenuBar.tsx +44 -0
  55. package/src/app/components/PaneGrid.stories.tsx +84 -0
  56. package/src/app/components/PaneGrid.tsx +128 -0
  57. package/src/app/components/PanelColumn.stories.tsx +25 -0
  58. package/src/app/components/PanelColumn.tsx +93 -0
  59. package/src/app/components/ShellHeader.stories.tsx +50 -0
  60. package/src/app/components/ShellHeader.tsx +137 -0
  61. package/src/app/components/WorkspacePanels.stories.tsx +36 -0
  62. package/src/app/components/WorkspacePanels.tsx +120 -0
  63. package/src/app/components/pages/Workspace.stories.tsx +181 -0
  64. package/src/app/intent-dispatch.test.ts +728 -0
  65. package/src/app/intent-dispatch.ts +687 -0
  66. package/src/app/launch-layout.test.ts +78 -0
  67. package/src/app/launch-layout.ts +57 -0
  68. package/src/app/ready-signal.test.ts +85 -0
  69. package/src/app/ready-signal.ts +52 -0
  70. package/src/app/run.ts +109 -0
  71. package/src/{serve.test.ts → app/serve.test.ts} +24 -12
  72. package/src/app/serve.ts +111 -0
  73. package/src/app/version.ts +5 -0
  74. package/src/appearance/components/theme-context.stories.tsx +46 -0
  75. package/src/appearance/components/theme-context.tsx +37 -0
  76. package/src/appearance/theme-presets.test.ts +51 -0
  77. package/src/appearance/theme-presets.ts +219 -0
  78. package/src/appearance/theme.ts +112 -0
  79. package/src/appearance/visual/contrast.test.ts +35 -0
  80. package/src/appearance/visual/contrast.ts +33 -0
  81. package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
  82. package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
  83. package/src/diff/components/AnnotatableFileView.tsx +141 -0
  84. package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
  85. package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
  86. package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
  87. package/src/diff/components/DiffContentView.split.test.tsx +140 -0
  88. package/src/diff/components/DiffContentView.stories.tsx +80 -0
  89. package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
  90. package/src/diff/components/DiffContentView.tsx +1055 -0
  91. package/src/diff/components/WalkWizard.stories.tsx +100 -0
  92. package/src/diff/components/WalkWizard.tsx +146 -0
  93. package/src/diff/components/diff-content-view-layout.test.ts +198 -0
  94. package/src/diff/components/diff-content-view-layout.ts +84 -0
  95. package/src/diff/diff-fold.test.ts +78 -0
  96. package/src/diff/diff-fold.ts +126 -0
  97. package/src/diff/diff-hunk-curate.test.ts +315 -0
  98. package/src/diff/diff-hunk-curate.ts +152 -0
  99. package/src/diff/diff-intraline.test.ts +239 -0
  100. package/src/diff/diff-intraline.ts +238 -0
  101. package/src/diff/diff-syntax.test.ts +125 -0
  102. package/src/diff/diff-syntax.ts +186 -0
  103. package/src/diff/split-diff.test.ts +110 -0
  104. package/src/diff/split-diff.ts +144 -0
  105. package/src/diff/view-diff.test.ts +325 -0
  106. package/src/diff/view-diff.ts +522 -0
  107. package/src/diff/walk.test.ts +121 -0
  108. package/src/diff/walk.ts +73 -0
  109. package/src/index.ts +31 -2
  110. package/src/integrations/attribution.test.ts +31 -0
  111. package/src/integrations/attribution.ts +42 -0
  112. package/src/integrations/client-extension-registry.test.tsx +61 -0
  113. package/src/integrations/client-extension-registry.ts +159 -0
  114. package/src/integrations/clipboard.test.ts +23 -0
  115. package/src/integrations/clipboard.ts +33 -0
  116. package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
  117. package/src/integrations/components/ExtensionZone.tsx +145 -0
  118. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  119. package/src/integrations/components/ShareDialog.tsx +550 -0
  120. package/src/integrations/components/share-dialog-store.ts +55 -0
  121. package/src/integrations/editor.test.ts +149 -0
  122. package/src/integrations/editor.ts +160 -0
  123. package/src/integrations/github-identity.test.ts +58 -0
  124. package/src/integrations/github-identity.ts +41 -0
  125. package/src/integrations/herdr.test.ts +43 -0
  126. package/src/integrations/herdr.ts +38 -0
  127. package/src/integrations/integrations.test.ts +41 -0
  128. package/src/integrations/integrations.ts +32 -0
  129. package/src/integrations/pasted-image.test.ts +67 -0
  130. package/src/integrations/pasted-image.ts +60 -0
  131. package/src/integrations/share.ts +238 -0
  132. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  133. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  134. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  135. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  136. package/src/keyboard/components/HandleChipsInput.tsx +143 -0
  137. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  138. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  139. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  140. package/src/keyboard/components/NavModeHint.tsx +31 -0
  141. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  142. package/src/keyboard/components/SlashComposer.tsx +87 -0
  143. package/src/keyboard/key-bindings.test.ts +156 -0
  144. package/src/keyboard/key-bindings.ts +459 -0
  145. package/src/keyboard/keymap.test.ts +611 -0
  146. package/src/keyboard/keymap.ts +395 -0
  147. package/src/keyboard/skills.test.ts +74 -0
  148. package/src/keyboard/skills.ts +75 -0
  149. package/src/keyboard/slash-palette.test.ts +103 -0
  150. package/src/keyboard/slash-palette.ts +129 -0
  151. package/src/keyboard/thread-chords.test.ts +79 -0
  152. package/src/keyboard/thread-chords.ts +163 -0
  153. package/src/keyboard/use-shared-keyboard.ts +55 -0
  154. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  155. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  156. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  157. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  158. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  159. package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
  160. package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
  161. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  162. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  163. package/src/markdown/components/ThreadView.tsx +560 -0
  164. package/src/markdown/mark-runs.ts +121 -0
  165. package/src/markdown/markdown-editor-syntax-style.ts +50 -0
  166. package/src/markdown/markdown-grid.test.ts +101 -0
  167. package/src/markdown/markdown-grid.ts +203 -0
  168. package/src/markdown/markdown-highlight.test.ts +61 -0
  169. package/src/markdown/markdown-highlight.ts +124 -0
  170. package/src/markdown/thread-code-syntax.ts +45 -0
  171. package/src/markdown/use-thread-body-editing.tsx +54 -0
  172. package/src/markdown/view-plan.regression.test.ts +60 -0
  173. package/src/markdown/view-plan.test.ts +545 -0
  174. package/src/markdown/view-plan.ts +1009 -0
  175. package/src/performance/otlp-trace.test.ts +119 -0
  176. package/src/performance/otlp-trace.ts +134 -0
  177. package/src/performance/perf-report.ts +18 -0
  178. package/src/performance/perf-timings.test.ts +18 -0
  179. package/src/performance/perf-timings.ts +39 -0
  180. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  181. package/src/prototype/components/PrototypeContentView.tsx +442 -0
  182. package/src/prototype/prototype-browser.test.ts +81 -0
  183. package/src/prototype/prototype-browser.ts +321 -0
  184. package/src/prototype/prototype-pixels.tsx +35 -0
  185. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  186. package/src/settings/components/SettingsDialog.tsx +183 -0
  187. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  188. package/src/settings/components/SettingsRows.tsx +112 -0
  189. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  190. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  191. package/src/settings/components/quick-actions-editor.tsx +222 -0
  192. package/src/settings/config-identity.test.ts +78 -0
  193. package/src/settings/config.test.ts +676 -0
  194. package/src/settings/config.ts +628 -0
  195. package/src/settings/use-settings-dialog.tsx +351 -0
  196. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  197. package/src/stories/stories-app.tsx +196 -0
  198. package/src/stories/stories.test.tsx +74 -0
  199. package/src/stories/story-fixtures.ts +102 -0
  200. package/src/stories/story-tree.test.ts +108 -0
  201. package/src/stories/story.ts +154 -0
  202. package/src/terminal/ansi-screen.test.ts +28 -0
  203. package/src/terminal/ansi-screen.ts +72 -0
  204. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  205. package/src/terminal/components/AnsiScreen.tsx +28 -0
  206. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  207. package/src/terminal/components/terminal-pane.ts +196 -0
  208. package/src/terminal/ghostty-terminal.test.ts +85 -0
  209. package/src/terminal/ghostty-terminal.ts +197 -0
  210. package/src/terminal/kitty-image.test.ts +66 -0
  211. package/src/terminal/kitty-image.ts +137 -0
  212. package/src/terminal/pty.test.ts +81 -0
  213. package/src/terminal/pty.ts +246 -0
  214. package/src/testing/test-support.test.ts +264 -0
  215. package/src/testing/test-support.ts +367 -0
  216. package/src/thread/agent-continuation.test.ts +32 -0
  217. package/src/thread/agent-continuation.ts +13 -0
  218. package/src/thread/agent-transcript.test.ts +161 -0
  219. package/src/thread/agent-transcript.ts +336 -0
  220. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  221. package/src/thread/components/AgentConfigControls.test.tsx +112 -0
  222. package/src/thread/components/AgentConfigControls.tsx +114 -0
  223. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  224. package/src/thread/components/AgentThreadPane.test.tsx +829 -0
  225. package/src/thread/components/AgentThreadPane.tsx +367 -0
  226. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  227. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  228. package/src/thread/components/ThreadFooter.tsx +77 -0
  229. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  230. package/src/thread/components/ThreadTree.test.tsx +41 -0
  231. package/src/thread/components/ThreadTree.tsx +267 -0
  232. package/src/thread/components/thread-tree.test.ts +137 -0
  233. package/src/thread/components/thread-tree.ts +112 -0
  234. package/src/thread/thread-controller.curate.test.ts +502 -0
  235. package/src/thread/thread-controller.derivation.test.ts +62 -0
  236. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  237. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  238. package/src/thread/thread-controller.share.test.ts +573 -0
  239. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  240. package/src/thread/thread-controller.tree.test.ts +263 -0
  241. package/src/thread/thread-controller.ts +2018 -0
  242. package/src/thread/tree-view.test.ts +128 -0
  243. package/src/thread/tree-view.ts +146 -0
  244. package/src/thread/use-thread-agent.test.tsx +208 -0
  245. package/src/thread/use-thread-agent.ts +111 -0
  246. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  247. package/src/ui/components/CodeBlock.tsx +80 -0
  248. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  249. package/src/ui/components/ConfirmCard.tsx +151 -0
  250. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  251. package/src/ui/components/ConfirmDialog.tsx +72 -0
  252. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  253. package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
  254. package/src/ui/components/OverlayScrollbar.tsx +129 -0
  255. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  256. package/src/ui/components/PromptDialog.test.tsx +65 -0
  257. package/src/ui/components/PromptDialog.tsx +99 -0
  258. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  259. package/src/ui/components/RootOverlay.tsx +49 -0
  260. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  261. package/src/ui/components/ScrollArea.tsx +61 -0
  262. package/src/ui/components/Toast.stories.tsx +19 -0
  263. package/src/ui/components/Toast.tsx +54 -0
  264. package/src/ui/components/Tooltip.stories.tsx +26 -0
  265. package/src/ui/components/Tooltip.tsx +72 -0
  266. package/src/ui/components/menu-control.stories.tsx +35 -0
  267. package/src/ui/components/menu-control.tsx +43 -0
  268. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  269. package/src/ui/components/primitives/Button.tsx +57 -0
  270. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  271. package/src/ui/components/primitives/Card.tsx +62 -0
  272. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  273. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  274. package/src/ui/components/primitives/Dialog.tsx +69 -0
  275. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  276. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  277. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  278. package/src/ui/components/primitives/IconButton.tsx +58 -0
  279. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  280. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  281. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  282. package/src/ui/components/primitives/Switch.tsx +32 -0
  283. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  284. package/src/ui/components/primitives/Tabs.tsx +101 -0
  285. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  286. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  287. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  288. package/src/ui/components/primitives/Tree.tsx +133 -0
  289. package/src/ui/components/primitives/frame.test.ts +12 -0
  290. package/src/ui/components/primitives/frame.ts +17 -0
  291. package/src/ui/components/primitives/icons.ts +61 -0
  292. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  293. package/src/ui/components/primitives/tree-model.ts +137 -0
  294. package/src/ui/components/syntax-highlight.ts +133 -0
  295. package/src/ui/components/truncate-text.ts +7 -0
  296. package/src/ui/components/truncate-title.test.ts +31 -0
  297. package/src/ui/components/truncate-title.ts +15 -0
  298. package/src/ui/use-frame-measure.ts +74 -0
  299. package/src/ui/use-terminal-virtualizer.ts +259 -0
  300. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  301. package/src/workbench/components/ChangesColumn.tsx +203 -0
  302. package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
  303. package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
  304. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  305. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  306. package/src/workbench/components/EditorGrid.tsx +544 -0
  307. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  308. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  309. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  310. package/src/workbench/components/FileContentsView.tsx +100 -0
  311. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  312. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  313. package/src/workbench/components/GridTabContent.tsx +193 -0
  314. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  315. package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
  316. package/src/workbench/components/ProjectTreeView.tsx +151 -0
  317. package/src/workbench/components/TreePane.stories.tsx +106 -0
  318. package/src/workbench/components/TreePane.tsx +155 -0
  319. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  320. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  321. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  322. package/src/workbench/components/editor-grid.test.ts +184 -0
  323. package/src/workbench/components/editor-grid.ts +281 -0
  324. package/src/workbench/components/file-tree.test.ts +60 -0
  325. package/src/workbench/components/file-tree.ts +112 -0
  326. package/src/workbench/use-changes-workbench.ts +205 -0
  327. package/src/workbench/use-remember-layout.ts +31 -0
  328. package/src/workbench/use-repo-changes.ts +91 -0
  329. package/src/workbench/workbench-snapshot.test.ts +99 -0
  330. package/src/workbench/workbench-snapshot.ts +59 -0
  331. package/src/App.diff.test.tsx +0 -99
  332. package/src/App.export.test.tsx +0 -80
  333. package/src/App.readonly.test.tsx +0 -148
  334. package/src/App.test.tsx +0 -217
  335. package/src/App.tsx +0 -0
  336. package/src/config.test.ts +0 -116
  337. package/src/config.ts +0 -113
  338. package/src/editor.ts +0 -34
  339. package/src/run.ts +0 -28
  340. package/src/serve.ts +0 -75
  341. package/src/theme.ts +0 -46
  342. package/src/view-diff.test.ts +0 -55
  343. package/src/view-diff.ts +0 -86
  344. package/src/view.test.ts +0 -157
  345. package/src/view.ts +0 -0
package/src/config.ts DELETED
@@ -1,113 +0,0 @@
1
- /**
2
- * Layered TOML config (#20): built-in defaults → user config → trusted repo
3
- * config → env. Sections: [keys] action = "combo" (every action rebindable),
4
- * [theme] per-token overrides, [integrations.obsidian] notes-vault export.
5
- */
6
-
7
- import { existsSync, readFileSync } from "node:fs";
8
- import { homedir } from "node:os";
9
- import { join } from "node:path";
10
- import { OBSIDIAN_DEFAULTS, type ObsidianConfig } from "@cueloop/integration-obsidian";
11
- import { DARK, type Theme } from "./theme";
12
-
13
- export interface KeymapConfig {
14
- [action: string]: string | string[];
15
- }
16
-
17
- export interface IntegrationsConfig {
18
- obsidian: ObsidianConfig;
19
- }
20
-
21
- export interface CueloopConfig {
22
- keys: Record<string, string[]>;
23
- theme: Theme;
24
- integrations: IntegrationsConfig;
25
- }
26
-
27
- /** Every action in the grammar, with its default binding(s). */
28
- export const DEFAULT_KEYS: Record<string, string[]> = {
29
- down: ["j", "down"],
30
- up: ["k", "up"],
31
- top: ["g"],
32
- bottom: ["G"],
33
- span: ["v"],
34
- comment: ["c"],
35
- suggest: ["s"],
36
- cut: ["x"],
37
- edit: ["e"],
38
- next_annotation: ["n"],
39
- prev_annotation: ["p"],
40
- delete_annotation: ["backspace"],
41
- submit: ["return", "enter"],
42
- quit: ["q"],
43
- };
44
-
45
- function parseToml(text: string): Record<string, unknown> {
46
- // Bun ships a native TOML parser
47
- return Bun.TOML.parse(text) as Record<string, unknown>;
48
- }
49
-
50
- function layer(base: CueloopConfig, raw: Record<string, unknown>): CueloopConfig {
51
- const out: CueloopConfig = {
52
- keys: { ...base.keys },
53
- theme: { ...base.theme },
54
- integrations: { obsidian: { ...base.integrations.obsidian } },
55
- };
56
- const keys = raw["keys"] as KeymapConfig | undefined;
57
- if (keys) {
58
- for (const [action, combo] of Object.entries(keys)) {
59
- out.keys[action] = Array.isArray(combo) ? combo : [combo];
60
- }
61
- }
62
- const theme = raw["theme"] as Partial<Record<keyof Theme, string>> | undefined;
63
- if (theme) {
64
- for (const [token, value] of Object.entries(theme)) {
65
- if (token in out.theme && typeof value === "string") {
66
- (out.theme as unknown as Record<string, string>)[token] = value;
67
- }
68
- }
69
- }
70
- const integrations = raw["integrations"] as Record<string, unknown> | undefined;
71
- const obsidian = integrations?.["obsidian"] as Record<string, unknown> | undefined;
72
- if (obsidian) {
73
- const o = out.integrations.obsidian;
74
- if (typeof obsidian["vault"] === "string") o.vault = obsidian["vault"];
75
- if (typeof obsidian["folder"] === "string") o.folder = obsidian["folder"];
76
- if (typeof obsidian["filenameFormat"] === "string") o.filenameFormat = obsidian["filenameFormat"];
77
- const sep = obsidian["separator"];
78
- if (sep === "space" || sep === "dash" || sep === "underscore") o.separator = sep;
79
- const on = obsidian["exportOn"];
80
- if (on === "approve" || on === "resolve" || on === "manual") o.exportOn = on;
81
- }
82
- return out;
83
- }
84
-
85
- export function loadConfig(opts: { repoRoot?: string; userConfigPath?: string } = {}): CueloopConfig {
86
- let config: CueloopConfig = {
87
- keys: { ...DEFAULT_KEYS },
88
- theme: { ...DARK },
89
- integrations: { obsidian: { ...OBSIDIAN_DEFAULTS } },
90
- };
91
- const userPath =
92
- opts.userConfigPath ??
93
- process.env.CUELOOP_CONFIG ??
94
- join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "cueloop", "config.toml");
95
- for (const path of [userPath, opts.repoRoot ? join(opts.repoRoot, ".cueloop", "config.toml") : undefined]) {
96
- if (!path || !existsSync(path)) continue;
97
- try {
98
- config = layer(config, parseToml(readFileSync(path, "utf8")));
99
- } catch {
100
- // a broken config never blocks a review; defaults win
101
- }
102
- }
103
- return config;
104
- }
105
-
106
- /** Reverse lookup: key name (+shift) → action, per the loaded keymap. */
107
- export function actionFor(keys: Record<string, string[]>, name: string, shift: boolean): string | undefined {
108
- const wanted = shift && name.length === 1 ? name.toUpperCase() : name;
109
- for (const [action, combos] of Object.entries(keys)) {
110
- if (combos.includes(wanted)) return action;
111
- }
112
- return undefined;
113
- }
package/src/editor.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * File-level edit mode = the $EDITOR hand-off (#22, #11): write the working
3
- * copy to a temp file, run the user's editor on it, read the result back.
4
- * Tests point CUELOOP_EDITOR at a non-interactive command.
5
- */
6
-
7
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8
- import { tmpdir } from "node:os";
9
- import { join } from "node:path";
10
-
11
- export interface EditResult {
12
- content: string;
13
- changed: boolean;
14
- }
15
-
16
- export function editInEditor(content: string, filename = "plan.md"): EditResult {
17
- const editor = process.env.CUELOOP_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
18
- if (!editor) throw new Error("no editor: set $EDITOR (or $CUELOOP_EDITOR)");
19
- const dir = mkdtempSync(join(tmpdir(), "cueloop-edit-"));
20
- const path = join(dir, filename);
21
- writeFileSync(path, content);
22
- try {
23
- const proc = Bun.spawnSync([...editor.split(" "), path], {
24
- stdin: "inherit",
25
- stdout: "inherit",
26
- stderr: "inherit",
27
- });
28
- if (proc.exitCode !== 0) throw new Error(`editor exited ${proc.exitCode}`);
29
- const next = readFileSync(path, "utf8");
30
- return { content: next, changed: next !== content };
31
- } finally {
32
- rmSync(dir, { recursive: true, force: true });
33
- }
34
- }
package/src/run.ts DELETED
@@ -1,28 +0,0 @@
1
- /** Real-terminal entry: render the App and resolve when the user quits. */
2
-
3
- import React from "react";
4
- import { createCliRenderer } from "@opentui/core";
5
- import { createRoot } from "@opentui/react";
6
- import { App } from "./App";
7
-
8
- export interface RunClientOptions {
9
- sessionId?: string;
10
- home?: string;
11
- }
12
-
13
- export async function runClient(opts: RunClientOptions): Promise<number> {
14
- const renderer = await createCliRenderer();
15
- return new Promise<number>((resolve) => {
16
- createRoot(renderer).render(
17
- React.createElement(App, {
18
- home: opts.home,
19
- sessionId: opts.sessionId,
20
- onExit: (code: number) => {
21
- renderer.destroy();
22
- resolve(code);
23
- process.exit(code);
24
- },
25
- }),
26
- );
27
- });
28
- }
package/src/serve.ts DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * SSH-served TUI (#22 share path): `cueloop serve` lets teammates join a
3
- * review session over plain ssh. Every SSH connection renders <App> in
4
- * observer mode (readOnly) against the same local daemon; the one writable
5
- * controller stays the local owner's own `cueloop` TUI.
6
- *
7
- * Trust model - tunnel of trust, no passwords, no keys:
8
- * the server authenticates nobody ("open" auth). Access control is the act of
9
- * sharing the address deliberately: it binds to 127.0.0.1 by default, so a
10
- * remote teammate reaches it only through a channel the owner opened on
11
- * purpose (an SSH tunnel, a tailnet address via --host). Anyone who can reach
12
- * the port can watch - and only watch: observers cannot mutate the session.
13
- * The host key is generated once under CUELOOP_HOME/ssh/host_key (0600) so
14
- * fingerprints stay stable across restarts.
15
- */
16
-
17
- import { mkdirSync } from "node:fs";
18
- import { join } from "node:path";
19
- import React from "react";
20
- import { createRoot } from "@opentui/react";
21
- import { createServer } from "@opentui/ssh";
22
- import { cueloopHome } from "@cueloop/daemon";
23
- import { App } from "./App";
24
-
25
- export interface ServeOptions {
26
- /** TCP port for the SSH listener; 0 picks an ephemeral port. Default 2222. */
27
- port?: number;
28
- /** Bind address. Default 127.0.0.1 - widen deliberately (e.g. a tailnet IP). */
29
- host?: string;
30
- /** Open every connection on this session; omit for the inbox. */
31
- sessionId?: string;
32
- /** CUELOOP_HOME override (daemon socket + ssh host key live under it). */
33
- home?: string;
34
- /** Silence the @opentui/ssh startup banner (tests). Default true. */
35
- banner?: boolean;
36
- onError?: (err: unknown) => void;
37
- }
38
-
39
- export interface ServeHandle {
40
- host: string;
41
- port: number;
42
- /** SHA256 host-key fingerprints, for out-of-band verification. */
43
- fingerprints: string[];
44
- close(): Promise<void>;
45
- }
46
-
47
- export async function serveClient(opts: ServeOptions = {}): Promise<ServeHandle> {
48
- const home = opts.home ?? cueloopHome();
49
- const sshDir = join(home, "ssh");
50
- mkdirSync(sshDir, { recursive: true, mode: 0o700 });
51
-
52
- const server = createServer({
53
- // password-less by design; see the trust model in the module comment
54
- auth: "open",
55
- hostKey: { path: join(sshDir, "host_key") },
56
- startupBanner: opts.banner ?? true,
57
- idleTimeout: "2h",
58
- onError: opts.onError,
59
- }).serve((session) => {
60
- const root = createRoot(session.renderer);
61
- root.render(
62
- React.createElement(App, {
63
- home,
64
- sessionId: opts.sessionId,
65
- readOnly: true,
66
- // q disconnects only this observer, never the server
67
- onExit: () => session.end(),
68
- }),
69
- );
70
- session.onClose(() => root.unmount());
71
- });
72
-
73
- const info = await server.listen(opts.port ?? 2222, opts.host ?? "127.0.0.1");
74
- return { ...info, close: () => server.close() };
75
- }
package/src/theme.ts DELETED
@@ -1,46 +0,0 @@
1
- /**
2
- * Semantic theme tokens (#22). Values carry over from the design-system
3
- * prototypes (tokens.css). One built-in dark theme for now; the theme
4
- * registry (palette-derived themes, terminal theme, user overrides) layers
5
- * on top of these slots without touching consumers.
6
- */
7
-
8
- export interface Theme {
9
- bg: string;
10
- panel: string;
11
- elevated: string;
12
- border: string;
13
- text: string;
14
- textMuted: string;
15
- textDim: string;
16
- accent: string;
17
- accentInk: string;
18
- green: string;
19
- red: string;
20
- blue: string;
21
- cursorBg: string;
22
- markCommentBg: string;
23
- markSuggestionBg: string;
24
- insFg: string;
25
- delFg: string;
26
- }
27
-
28
- export const DARK: Theme = {
29
- bg: "#1f1f29",
30
- panel: "#1a1a23",
31
- elevated: "#23232f",
32
- border: "#3a3a4a",
33
- text: "#e4e6ec",
34
- textMuted: "#b1b6c4",
35
- textDim: "#6b7280",
36
- accent: "#f5a3a3",
37
- accentInk: "#2a1416",
38
- green: "#62d96b",
39
- red: "#ff6b6b",
40
- blue: "#84a6e8",
41
- cursorBg: "#33262a",
42
- markCommentBg: "#3d2a2e",
43
- markSuggestionBg: "#20351f",
44
- insFg: "#62d96b",
45
- delFg: "#ff6b6b",
46
- }
@@ -1,55 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { diffRowAnchor, diffRows } from "./view-diff";
3
-
4
- const PATCH = `diff --git a/src/store.ts b/src/store.ts
5
- index 111..222 100644
6
- --- a/src/store.ts
7
- +++ b/src/store.ts
8
- @@ -1,5 +1,6 @@
9
- import { join } from "node:path";
10
- export class Store {
11
- - private items = [];
12
- + private items = new Map();
13
- + private ready = false;
14
- constructor() {}
15
- @@ -20,3 +21,3 @@
16
- export function helper() {
17
- - return 1;
18
- + return 2;
19
- }
20
- `;
21
-
22
- describe("diffRows", () => {
23
- test("flattens files, hunks, and signed lines with line numbers", () => {
24
- const rows = diffRows(PATCH);
25
- expect(rows[0]).toMatchObject({ t: "file", file: "src/store.ts" });
26
- expect(rows[1]!.t).toBe("hunk");
27
- const del = rows.find((r) => r.t === "del")!;
28
- expect(del.text).toContain("private items = [];");
29
- expect(del.oldLine).toBe(3);
30
- const adds = rows.filter((r) => r.t === "add");
31
- expect(adds[0]!.text).toContain("new Map()");
32
- expect(adds[0]!.newLine).toBe(3);
33
- expect(adds[1]!.newLine).toBe(4);
34
- // two hunks
35
- expect(rows.filter((r) => r.t === "hunk").length).toBe(2);
36
- });
37
-
38
- test("context lines carry both line numbers", () => {
39
- const rows = diffRows(PATCH);
40
- const ctx = rows.find((r) => r.t === "ctx")!;
41
- expect(ctx.oldLine).toBe(1);
42
- expect(ctx.newLine).toBe(1);
43
- });
44
- });
45
-
46
- describe("diffRowAnchor", () => {
47
- test("quote is the line, neighbors are the context selectors", () => {
48
- const rows = diffRows(PATCH);
49
- const idx = rows.findIndex((r) => r.text.includes("new Map()"));
50
- const anchor = diffRowAnchor(rows, idx);
51
- expect(anchor.quote).toContain("new Map()");
52
- expect(anchor.prefix.length).toBeGreaterThan(0);
53
- expect(anchor.suffix.length).toBeGreaterThan(0);
54
- });
55
- });
package/src/view-diff.ts DELETED
@@ -1,86 +0,0 @@
1
- /**
2
- * Diff artifact projection (#18 slice 3): flatten @pierre/diffs' parsed
3
- * patch model into render rows. Line-anchored annotations use the same
4
- * quote-primary anchors as plans: quote = the line content, prefix/suffix =
5
- * the neighbor lines - so the whole anchor/feedback pipeline is shared.
6
- */
7
-
8
- import { parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs";
9
-
10
- export type DiffRowType = "file" | "hunk" | "ctx" | "add" | "del";
11
-
12
- export interface DiffRow {
13
- t: DiffRowType;
14
- text: string;
15
- file: string;
16
- oldLine?: number;
17
- newLine?: number;
18
- }
19
-
20
- export function diffRows(patchText: string): DiffRow[] {
21
- const rows: DiffRow[] = [];
22
- const patches = parsePatchFiles(patchText);
23
- for (const patch of patches) {
24
- for (const file of patch.files) {
25
- rows.push({ t: "file", text: fileLabel(file), file: file.name });
26
- for (const hunk of file.hunks) {
27
- rows.push({ t: "hunk", text: hunk.hunkSpecs ?? "@@", file: file.name });
28
- let oldLine = hunk.deletionStart;
29
- let newLine = hunk.additionStart;
30
- for (const seg of hunk.hunkContent) {
31
- if (seg.type === "context") {
32
- for (let i = 0; i < seg.lines; i++) {
33
- rows.push({
34
- t: "ctx",
35
- text: file.additionLines[seg.additionLineIndex + i] ?? "",
36
- file: file.name,
37
- oldLine: oldLine++,
38
- newLine: newLine++,
39
- });
40
- }
41
- } else {
42
- for (let i = 0; i < seg.deletions; i++) {
43
- rows.push({
44
- t: "del",
45
- text: file.deletionLines[seg.deletionLineIndex + i] ?? "",
46
- file: file.name,
47
- oldLine: oldLine++,
48
- });
49
- }
50
- for (let i = 0; i < seg.additions; i++) {
51
- rows.push({
52
- t: "add",
53
- text: file.additionLines[seg.additionLineIndex + i] ?? "",
54
- file: file.name,
55
- newLine: newLine++,
56
- });
57
- }
58
- }
59
- }
60
- }
61
- }
62
- }
63
- return rows;
64
- }
65
-
66
- function fileLabel(file: FileDiffMetadata): string {
67
- return file.prevName && file.prevName !== file.name ? `${file.prevName} → ${file.name}` : file.name;
68
- }
69
-
70
- /** Quote-primary anchor for a diff row: neighbors as context selectors. */
71
- export function diffRowAnchor(rows: DiffRow[], idx: number): { quote: string; prefix: string; suffix: string } {
72
- const row = rows[idx]!;
73
- const prev = rows[idx - 1];
74
- const next = rows[idx + 1];
75
- return {
76
- quote: row.text,
77
- prefix: prev && (prev.t === "ctx" || prev.t === "add" || prev.t === "del") ? prev.text.slice(-24) : "",
78
- suffix: next && (next.t === "ctx" || next.t === "add" || next.t === "del") ? next.text.slice(0, 24) : "",
79
- };
80
- }
81
-
82
- /** Location label for the rail and feedback: file:newLine (or old for del). */
83
- export function diffRowLocation(row: DiffRow): string {
84
- const line = row.newLine ?? row.oldLine;
85
- return line !== undefined ? `${row.file}:${line}` : row.file;
86
- }
package/src/view.test.ts DELETED
@@ -1,157 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- blockRuns,
4
- buildDisplay,
5
- cutBlock,
6
- displayText,
7
- marksByDisplay,
8
- overlayMarks,
9
- restoreBlock,
10
- restoreLine,
11
- spanKey,
12
- startSpan,
13
- wrapRuns,
14
- } from "./view";
15
- import { parseBlocks, type Annotation } from "@cueloop/schema";
16
-
17
- const BASE = `# Plan
18
-
19
- ## Context
20
-
21
- The daemon persists sessions to disk atomically.
22
-
23
- - first item
24
- - second item
25
- `;
26
-
27
- describe("buildDisplay reconciliation", () => {
28
- test("no working copy: everything is same", () => {
29
- const d = buildDisplay(BASE);
30
- expect(d.every((x) => x.type === "same")).toBe(true);
31
- });
32
-
33
- test("reworded block pairs as mod (similar)", () => {
34
- const working = BASE.replace("persists sessions to disk", "writes sessions to disk");
35
- const d = buildDisplay(BASE, working);
36
- const mod = d.find((x) => x.type === "mod")!;
37
- expect(mod.base!.text).toContain("persists");
38
- expect(mod.work!.text).toContain("writes");
39
- });
40
-
41
- test("unrelated cut + insert do NOT merge into a mod", () => {
42
- const working = BASE.replace("- second item\n", "") + "\nA totally new closing note.\n";
43
- const d = buildDisplay(BASE, working);
44
- expect(d.some((x) => x.type === "del" && x.base!.text === "second item")).toBe(true);
45
- expect(d.some((x) => x.type === "add" && x.work!.text.includes("closing note"))).toBe(true);
46
- expect(d.some((x) => x.type === "mod")).toBe(false);
47
- });
48
-
49
- test("ordered list items get running numbers", () => {
50
- const d = buildDisplay("1. one\n2. two\n3. three\n");
51
- expect(d.map((x) => x.oliNum)).toEqual([1, 2, 3]);
52
- });
53
- });
54
-
55
- describe("blockRuns + overlayMarks", () => {
56
- test("mod blocks emit word-diff runs that reconstruct both sides", () => {
57
- const d = buildDisplay("plain old text here\n", "plain new text here\n")[0]!;
58
- const runs = blockRuns(d, true);
59
- const oldSide = runs.filter((r) => r.role !== "ins").map((r) => r.text).join("");
60
- const newSide = runs.filter((r) => r.role !== "del").map((r) => r.text).join("");
61
- expect(oldSide).toBe("plain old text here");
62
- expect(newSide).toBe("plain new text here");
63
- });
64
-
65
- test("marks bind to working-text offsets and skip del runs", () => {
66
- const d = buildDisplay("old words linger\n", "new words linger\n")[0]!;
67
- const runs = blockRuns(d, true);
68
- const marked = overlayMarks(runs, [{ start: 4, end: 9, role: "mark-comment" }]); // "words"
69
- const markedText = marked.filter((r) => r.role === "mark-comment").map((r) => r.text).join("");
70
- expect(markedText).toBe("words");
71
- // del run text untouched
72
- expect(marked.some((r) => r.role === "del" && r.text === "old")).toBe(true);
73
- });
74
-
75
- test("marks never override ins emphasis", () => {
76
- const d = buildDisplay("keep\n", "keep added\n")[0]!;
77
- const runs = overlayMarks(blockRuns(d, true), [{ start: 0, end: 10, role: "mark-comment" }]);
78
- expect(runs.some((r) => r.role === "ins")).toBe(true);
79
- });
80
- });
81
-
82
- describe("marksByDisplay", () => {
83
- test("annotations resolve into display coordinates", () => {
84
- const working = BASE.replace("first item", "first item edited beyond recognition of it");
85
- const display = buildDisplay(BASE, working);
86
- const ann: Annotation = {
87
- id: "a1",
88
- kind: "comment",
89
- anchor: { quote: "atomically", prefix: "to disk ", suffix: "." },
90
- body: "define atomically",
91
- createdAt: "",
92
- };
93
- const map = marksByDisplay([ann], display);
94
- const [dispIdx, marks] = [...map.entries()][0]!;
95
- expect(displayText(display[dispIdx]!)).toContain("atomically");
96
- expect(marks[0]!.annotationId).toBe("a1");
97
- });
98
-
99
- test("orphaned annotations produce no marks", () => {
100
- const display = buildDisplay(BASE);
101
- const ann: Annotation = {
102
- id: "a1",
103
- kind: "comment",
104
- anchor: { quote: "text that is gone", prefix: "", suffix: "" },
105
- body: "",
106
- createdAt: "",
107
- };
108
- expect(marksByDisplay([ann], display).size).toBe(0);
109
- });
110
- });
111
-
112
- describe("wrapRuns", () => {
113
- test("wraps at word boundaries within width", () => {
114
- const lines = wrapRuns([{ text: "alpha beta gamma delta", role: "plain", start: 0 }], 11);
115
- const rendered = lines.map((l) => l.map((r) => r.text).join(""));
116
- expect(rendered.every((l) => l.length <= 11)).toBe(true);
117
- expect(rendered.join(" ").replace(/\s+/g, " ").trim()).toBe("alpha beta gamma delta");
118
- });
119
-
120
- test("hard-splits words longer than the width", () => {
121
- const lines = wrapRuns([{ text: "abcdefghij", role: "plain", start: 0 }], 4);
122
- expect(lines.length).toBeGreaterThan(1);
123
- });
124
-
125
- test("newlines force line breaks (code blocks)", () => {
126
- const lines = wrapRuns([{ text: "line1\nline2", role: "plain", start: 0 }], 80);
127
- expect(lines.length).toBe(2);
128
- });
129
- });
130
-
131
- describe("span mode", () => {
132
- const text = "one two three four";
133
- test("v selects the first word; l grows; w slides; $ to end", () => {
134
- let s = startSpan(0, text)!;
135
- expect(text.slice(s.start, s.end)).toBe("one");
136
- s = spanKey(s, "l", text);
137
- expect(text.slice(s.start, s.end)).toBe("one two");
138
- s = spanKey(s, "w", text);
139
- expect(text.slice(s.start, s.end)).toBe("two three");
140
- s = spanKey(s, "$", text);
141
- expect(text.slice(s.start, s.end)).toBe("two three four");
142
- });
143
- });
144
-
145
- describe("cut / restore round-trip", () => {
146
- test("cut removes the block lines; restore puts them back", () => {
147
- const blocks = parseBlocks(BASE);
148
- const li = blocks.find((b) => b.text === "second item")!;
149
- const cut = cutBlock(BASE, li);
150
- expect(cut).not.toContain("second item");
151
- const display = buildDisplay(BASE, cut);
152
- const delIdx = display.findIndex((d) => d.type === "del");
153
- const line = restoreLine(display, delIdx, cut.split("\n").length);
154
- const restored = restoreBlock(cut, "- second item", line);
155
- expect(buildDisplay(BASE, restored).every((d) => d.type === "same")).toBe(true);
156
- });
157
- });
package/src/view.ts DELETED
Binary file