jonsoc 1.1.50 → 1.1.51

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 (420) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/PUBLISHING_GUIDE.md +151 -0
  4. package/README.md +58 -0
  5. package/bin/jonsoc +256 -256
  6. package/bunfig.toml +7 -0
  7. package/package.json +142 -8
  8. package/package.json.placeholder +11 -0
  9. package/parsers-config.ts +253 -0
  10. package/script/build.ts +115 -0
  11. package/script/publish-registries.ts +197 -0
  12. package/script/publish.ts +149 -0
  13. package/script/schema.ts +47 -0
  14. package/script/seed-e2e.ts +50 -0
  15. package/src/acp/README.md +164 -0
  16. package/src/acp/agent.ts +1437 -0
  17. package/src/acp/session.ts +105 -0
  18. package/src/acp/types.ts +22 -0
  19. package/src/agent/agent.ts +345 -0
  20. package/src/agent/generate.txt +75 -0
  21. package/src/agent/prompt/compaction.txt +12 -0
  22. package/src/agent/prompt/explore.txt +18 -0
  23. package/src/agent/prompt/summary.txt +11 -0
  24. package/src/agent/prompt/title.txt +44 -0
  25. package/src/auth/index.ts +73 -0
  26. package/src/brand/index.ts +89 -0
  27. package/src/bun/index.ts +139 -0
  28. package/src/bus/bus-event.ts +43 -0
  29. package/src/bus/global.ts +10 -0
  30. package/src/bus/index.ts +105 -0
  31. package/src/cli/bootstrap.ts +17 -0
  32. package/src/cli/cmd/acp.ts +69 -0
  33. package/src/cli/cmd/agent.ts +257 -0
  34. package/src/cli/cmd/auth.ts +405 -0
  35. package/src/cli/cmd/cmd.ts +7 -0
  36. package/src/cli/cmd/debug/agent.ts +166 -0
  37. package/src/cli/cmd/debug/config.ts +16 -0
  38. package/src/cli/cmd/debug/file.ts +97 -0
  39. package/src/cli/cmd/debug/index.ts +48 -0
  40. package/src/cli/cmd/debug/lsp.ts +52 -0
  41. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  42. package/src/cli/cmd/debug/scrap.ts +16 -0
  43. package/src/cli/cmd/debug/skill.ts +16 -0
  44. package/src/cli/cmd/debug/snapshot.ts +52 -0
  45. package/src/cli/cmd/export.ts +88 -0
  46. package/src/cli/cmd/generate.ts +38 -0
  47. package/src/cli/cmd/github.ts +1547 -0
  48. package/src/cli/cmd/import.ts +99 -0
  49. package/src/cli/cmd/mcp.ts +765 -0
  50. package/src/cli/cmd/models.ts +77 -0
  51. package/src/cli/cmd/pr.ts +112 -0
  52. package/src/cli/cmd/run.ts +395 -0
  53. package/src/cli/cmd/serve.ts +20 -0
  54. package/src/cli/cmd/session.ts +135 -0
  55. package/src/cli/cmd/stats.ts +402 -0
  56. package/src/cli/cmd/tui/app.tsx +923 -0
  57. package/src/cli/cmd/tui/attach.ts +39 -0
  58. package/src/cli/cmd/tui/component/border.tsx +21 -0
  59. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  60. package/src/cli/cmd/tui/component/dialog-command.tsx +162 -0
  61. package/src/cli/cmd/tui/component/dialog-error-log.tsx +155 -0
  62. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  64. package/src/cli/cmd/tui/component/dialog-provider.tsx +256 -0
  65. package/src/cli/cmd/tui/component/dialog-session-list.tsx +114 -0
  66. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  67. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  68. package/src/cli/cmd/tui/component/dialog-status.tsx +164 -0
  69. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  70. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  71. package/src/cli/cmd/tui/component/dynamic-layout.tsx +86 -0
  72. package/src/cli/cmd/tui/component/inspector-overlay.tsx +247 -0
  73. package/src/cli/cmd/tui/component/logo.tsx +88 -0
  74. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +653 -0
  75. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  76. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  77. package/src/cli/cmd/tui/component/prompt/index.tsx +1347 -0
  78. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  79. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  80. package/src/cli/cmd/tui/component/tips.tsx +153 -0
  81. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  82. package/src/cli/cmd/tui/context/args.tsx +14 -0
  83. package/src/cli/cmd/tui/context/directory.ts +13 -0
  84. package/src/cli/cmd/tui/context/error-log.tsx +56 -0
  85. package/src/cli/cmd/tui/context/exit.tsx +26 -0
  86. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  87. package/src/cli/cmd/tui/context/inspector.tsx +57 -0
  88. package/src/cli/cmd/tui/context/keybind.tsx +108 -0
  89. package/src/cli/cmd/tui/context/kv.tsx +53 -0
  90. package/src/cli/cmd/tui/context/layout.tsx +240 -0
  91. package/src/cli/cmd/tui/context/local.tsx +402 -0
  92. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  93. package/src/cli/cmd/tui/context/route.tsx +51 -0
  94. package/src/cli/cmd/tui/context/sdk.tsx +94 -0
  95. package/src/cli/cmd/tui/context/sync.tsx +449 -0
  96. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  97. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  98. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  99. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  100. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  101. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  102. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  103. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  104. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  105. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  106. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  107. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  108. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  109. package/src/cli/cmd/tui/context/theme/jonsoc.json +245 -0
  110. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  111. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  112. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  113. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  114. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  115. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  116. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  117. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  118. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  119. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  120. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  121. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  122. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  123. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  124. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  125. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  126. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  127. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  128. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  129. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  130. package/src/cli/cmd/tui/event.ts +48 -0
  131. package/src/cli/cmd/tui/hooks/use-command-registry.tsx +184 -0
  132. package/src/cli/cmd/tui/routes/home.tsx +198 -0
  133. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  134. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  135. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  136. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  137. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  138. package/src/cli/cmd/tui/routes/session/git-commit.tsx +59 -0
  139. package/src/cli/cmd/tui/routes/session/git-history.tsx +122 -0
  140. package/src/cli/cmd/tui/routes/session/header.tsx +185 -0
  141. package/src/cli/cmd/tui/routes/session/index.tsx +2363 -0
  142. package/src/cli/cmd/tui/routes/session/navigator-ui.tsx +214 -0
  143. package/src/cli/cmd/tui/routes/session/navigator.tsx +1124 -0
  144. package/src/cli/cmd/tui/routes/session/panel-explorer.tsx +553 -0
  145. package/src/cli/cmd/tui/routes/session/panel-viewer.tsx +386 -0
  146. package/src/cli/cmd/tui/routes/session/permission.tsx +501 -0
  147. package/src/cli/cmd/tui/routes/session/question.tsx +507 -0
  148. package/src/cli/cmd/tui/routes/session/sidebar.tsx +365 -0
  149. package/src/cli/cmd/tui/routes/session/vcs-diff-viewer.tsx +37 -0
  150. package/src/cli/cmd/tui/routes/ui-settings.tsx +449 -0
  151. package/src/cli/cmd/tui/thread.ts +172 -0
  152. package/src/cli/cmd/tui/ui/dialog-alert.tsx +90 -0
  153. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  154. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  155. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  156. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  157. package/src/cli/cmd/tui/ui/dialog-select.tsx +384 -0
  158. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  159. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  160. package/src/cli/cmd/tui/ui/spinner.ts +375 -0
  161. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  162. package/src/cli/cmd/tui/util/clipboard.ts +255 -0
  163. package/src/cli/cmd/tui/util/editor.ts +32 -0
  164. package/src/cli/cmd/tui/util/signal.ts +7 -0
  165. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  166. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  167. package/src/cli/cmd/tui/worker.ts +152 -0
  168. package/src/cli/cmd/uninstall.ts +362 -0
  169. package/src/cli/cmd/upgrade.ts +73 -0
  170. package/src/cli/cmd/web.ts +81 -0
  171. package/src/cli/error.ts +57 -0
  172. package/src/cli/network.ts +53 -0
  173. package/src/cli/ui.ts +119 -0
  174. package/src/cli/upgrade.ts +25 -0
  175. package/src/command/index.ts +131 -0
  176. package/src/command/template/initialize.txt +10 -0
  177. package/src/command/template/review.txt +99 -0
  178. package/src/config/config.ts +1404 -0
  179. package/src/config/markdown.ts +93 -0
  180. package/src/env/index.ts +26 -0
  181. package/src/file/ignore.ts +83 -0
  182. package/src/file/index.ts +432 -0
  183. package/src/file/ripgrep.ts +407 -0
  184. package/src/file/time.ts +69 -0
  185. package/src/file/watcher.ts +127 -0
  186. package/src/flag/flag.ts +80 -0
  187. package/src/format/formatter.ts +357 -0
  188. package/src/format/index.ts +137 -0
  189. package/src/global/index.ts +58 -0
  190. package/src/id/id.ts +83 -0
  191. package/src/ide/index.ts +76 -0
  192. package/src/index.ts +208 -0
  193. package/src/installation/index.ts +258 -0
  194. package/src/lsp/client.ts +252 -0
  195. package/src/lsp/index.ts +485 -0
  196. package/src/lsp/language.ts +119 -0
  197. package/src/lsp/server.ts +2046 -0
  198. package/src/mcp/auth.ts +135 -0
  199. package/src/mcp/index.ts +934 -0
  200. package/src/mcp/oauth-callback.ts +200 -0
  201. package/src/mcp/oauth-provider.ts +155 -0
  202. package/src/patch/index.ts +680 -0
  203. package/src/permission/arity.ts +163 -0
  204. package/src/permission/index.ts +210 -0
  205. package/src/permission/next.ts +280 -0
  206. package/src/plugin/codex.ts +500 -0
  207. package/src/plugin/copilot.ts +283 -0
  208. package/src/plugin/index.ts +135 -0
  209. package/src/project/bootstrap.ts +35 -0
  210. package/src/project/instance.ts +91 -0
  211. package/src/project/project.ts +371 -0
  212. package/src/project/state.ts +66 -0
  213. package/src/project/vcs.ts +151 -0
  214. package/src/provider/auth.ts +147 -0
  215. package/src/provider/models-macro.ts +14 -0
  216. package/src/provider/models.ts +114 -0
  217. package/src/provider/provider.ts +1220 -0
  218. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  219. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  220. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  221. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  222. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  223. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  224. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  225. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  226. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
  227. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  228. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  229. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  230. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  231. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  232. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  233. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  234. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  235. package/src/provider/transform.ts +742 -0
  236. package/src/pty/index.ts +241 -0
  237. package/src/question/index.ts +176 -0
  238. package/src/scheduler/index.ts +61 -0
  239. package/src/server/error.ts +36 -0
  240. package/src/server/event.ts +7 -0
  241. package/src/server/mdns.ts +59 -0
  242. package/src/server/routes/config.ts +92 -0
  243. package/src/server/routes/experimental.ts +208 -0
  244. package/src/server/routes/file.ts +227 -0
  245. package/src/server/routes/global.ts +135 -0
  246. package/src/server/routes/mcp.ts +225 -0
  247. package/src/server/routes/permission.ts +68 -0
  248. package/src/server/routes/project.ts +82 -0
  249. package/src/server/routes/provider.ts +165 -0
  250. package/src/server/routes/pty.ts +169 -0
  251. package/src/server/routes/question.ts +98 -0
  252. package/src/server/routes/session.ts +939 -0
  253. package/src/server/routes/tui.ts +379 -0
  254. package/src/server/server.ts +663 -0
  255. package/src/session/compaction.ts +225 -0
  256. package/src/session/index.ts +498 -0
  257. package/src/session/llm.ts +288 -0
  258. package/src/session/message-v2.ts +740 -0
  259. package/src/session/message.ts +189 -0
  260. package/src/session/processor.ts +406 -0
  261. package/src/session/prompt/anthropic-20250930.txt +168 -0
  262. package/src/session/prompt/anthropic.txt +172 -0
  263. package/src/session/prompt/anthropic_spoof.txt +1 -0
  264. package/src/session/prompt/beast.txt +149 -0
  265. package/src/session/prompt/build-switch.txt +5 -0
  266. package/src/session/prompt/codex_header.txt +81 -0
  267. package/src/session/prompt/copilot-gpt-5.txt +145 -0
  268. package/src/session/prompt/gemini.txt +157 -0
  269. package/src/session/prompt/max-steps.txt +16 -0
  270. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  271. package/src/session/prompt/plan.txt +26 -0
  272. package/src/session/prompt/qwen.txt +111 -0
  273. package/src/session/prompt.ts +1815 -0
  274. package/src/session/retry.ts +90 -0
  275. package/src/session/revert.ts +121 -0
  276. package/src/session/status.ts +76 -0
  277. package/src/session/summary.ts +150 -0
  278. package/src/session/system.ts +156 -0
  279. package/src/session/todo.ts +37 -0
  280. package/src/share/share-next.ts +205 -0
  281. package/src/share/share.ts +95 -0
  282. package/src/shell/shell.ts +67 -0
  283. package/src/skill/index.ts +1 -0
  284. package/src/skill/skill.ts +135 -0
  285. package/src/snapshot/index.ts +236 -0
  286. package/src/storage/storage.ts +227 -0
  287. package/src/tool/apply_patch.ts +279 -0
  288. package/src/tool/apply_patch.txt +33 -0
  289. package/src/tool/bash.ts +258 -0
  290. package/src/tool/bash.txt +115 -0
  291. package/src/tool/batch.ts +175 -0
  292. package/src/tool/batch.txt +24 -0
  293. package/src/tool/codesearch.ts +132 -0
  294. package/src/tool/codesearch.txt +12 -0
  295. package/src/tool/edit.ts +645 -0
  296. package/src/tool/edit.txt +10 -0
  297. package/src/tool/external-directory.ts +32 -0
  298. package/src/tool/glob.ts +77 -0
  299. package/src/tool/glob.txt +6 -0
  300. package/src/tool/grep.ts +154 -0
  301. package/src/tool/grep.txt +8 -0
  302. package/src/tool/invalid.ts +17 -0
  303. package/src/tool/ls.ts +121 -0
  304. package/src/tool/ls.txt +1 -0
  305. package/src/tool/lsp.ts +96 -0
  306. package/src/tool/lsp.txt +19 -0
  307. package/src/tool/multiedit.ts +46 -0
  308. package/src/tool/multiedit.txt +41 -0
  309. package/src/tool/plan-enter.txt +14 -0
  310. package/src/tool/plan-exit.txt +13 -0
  311. package/src/tool/plan.ts +130 -0
  312. package/src/tool/question.ts +33 -0
  313. package/src/tool/question.txt +10 -0
  314. package/src/tool/read.ts +202 -0
  315. package/src/tool/read.txt +12 -0
  316. package/src/tool/registry.ts +162 -0
  317. package/src/tool/skill.ts +82 -0
  318. package/src/tool/task.ts +188 -0
  319. package/src/tool/task.txt +60 -0
  320. package/src/tool/todo.ts +53 -0
  321. package/src/tool/todoread.txt +14 -0
  322. package/src/tool/todowrite.txt +167 -0
  323. package/src/tool/tool.ts +88 -0
  324. package/src/tool/truncation.ts +106 -0
  325. package/src/tool/webfetch.ts +182 -0
  326. package/src/tool/webfetch.txt +13 -0
  327. package/src/tool/websearch.ts +150 -0
  328. package/src/tool/websearch.txt +14 -0
  329. package/src/tool/write.ts +80 -0
  330. package/src/tool/write.txt +8 -0
  331. package/src/util/archive.ts +16 -0
  332. package/src/util/color.ts +19 -0
  333. package/src/util/context.ts +25 -0
  334. package/src/util/defer.ts +12 -0
  335. package/src/util/eventloop.ts +20 -0
  336. package/src/util/filesystem.ts +93 -0
  337. package/src/util/fn.ts +11 -0
  338. package/src/util/format.ts +20 -0
  339. package/src/util/iife.ts +3 -0
  340. package/src/util/keybind.ts +103 -0
  341. package/src/util/lazy.ts +18 -0
  342. package/src/util/locale.ts +81 -0
  343. package/src/util/lock.ts +98 -0
  344. package/src/util/log.ts +180 -0
  345. package/src/util/queue.ts +32 -0
  346. package/src/util/rpc.ts +66 -0
  347. package/src/util/scrap.ts +10 -0
  348. package/src/util/signal.ts +12 -0
  349. package/src/util/timeout.ts +14 -0
  350. package/src/util/token.ts +7 -0
  351. package/src/util/wildcard.ts +56 -0
  352. package/src/worktree/index.ts +524 -0
  353. package/sst-env.d.ts +9 -0
  354. package/test/acp/agent-interface.test.ts +51 -0
  355. package/test/acp/event-subscription.test.ts +436 -0
  356. package/test/agent/agent.test.ts +638 -0
  357. package/test/bun.test.ts +53 -0
  358. package/test/cli/cmd/tui/fileref.test.ts +30 -0
  359. package/test/cli/github-action.test.ts +129 -0
  360. package/test/cli/github-remote.test.ts +80 -0
  361. package/test/cli/tui/navigator_logic.test.ts +99 -0
  362. package/test/cli/tui/transcript.test.ts +297 -0
  363. package/test/cli/ui.test.ts +80 -0
  364. package/test/config/agent-color.test.ts +66 -0
  365. package/test/config/config.test.ts +1613 -0
  366. package/test/config/fixtures/empty-frontmatter.md +4 -0
  367. package/test/config/fixtures/frontmatter.md +28 -0
  368. package/test/config/fixtures/no-frontmatter.md +1 -0
  369. package/test/config/markdown.test.ts +192 -0
  370. package/test/file/ignore.test.ts +10 -0
  371. package/test/file/path-traversal.test.ts +198 -0
  372. package/test/fixture/fixture.ts +45 -0
  373. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  374. package/test/ide/ide.test.ts +82 -0
  375. package/test/keybind.test.ts +421 -0
  376. package/test/lsp/client.test.ts +95 -0
  377. package/test/mcp/headers.test.ts +153 -0
  378. package/test/mcp/oauth-browser.test.ts +261 -0
  379. package/test/patch/patch.test.ts +348 -0
  380. package/test/permission/arity.test.ts +33 -0
  381. package/test/permission/next.test.ts +690 -0
  382. package/test/permission-task.test.ts +319 -0
  383. package/test/plugin/codex.test.ts +123 -0
  384. package/test/preload.ts +67 -0
  385. package/test/project/project.test.ts +120 -0
  386. package/test/provider/amazon-bedrock.test.ts +268 -0
  387. package/test/provider/gitlab-duo.test.ts +286 -0
  388. package/test/provider/provider.test.ts +2149 -0
  389. package/test/provider/transform.test.ts +1631 -0
  390. package/test/question/question.test.ts +300 -0
  391. package/test/scheduler.test.ts +73 -0
  392. package/test/server/session-list.test.ts +39 -0
  393. package/test/server/session-select.test.ts +78 -0
  394. package/test/session/compaction.test.ts +293 -0
  395. package/test/session/llm.test.ts +90 -0
  396. package/test/session/message-v2.test.ts +786 -0
  397. package/test/session/retry.test.ts +131 -0
  398. package/test/session/revert-compact.test.ts +285 -0
  399. package/test/session/session.test.ts +71 -0
  400. package/test/skill/skill.test.ts +185 -0
  401. package/test/snapshot/snapshot.test.ts +939 -0
  402. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  403. package/test/tool/apply_patch.test.ts +499 -0
  404. package/test/tool/bash.test.ts +320 -0
  405. package/test/tool/external-directory.test.ts +126 -0
  406. package/test/tool/fixtures/large-image.png +0 -0
  407. package/test/tool/fixtures/models-api.json +33453 -0
  408. package/test/tool/grep.test.ts +109 -0
  409. package/test/tool/question.test.ts +105 -0
  410. package/test/tool/read.test.ts +332 -0
  411. package/test/tool/registry.test.ts +76 -0
  412. package/test/tool/truncation.test.ts +159 -0
  413. package/test/util/filesystem.test.ts +39 -0
  414. package/test/util/format.test.ts +59 -0
  415. package/test/util/iife.test.ts +36 -0
  416. package/test/util/lazy.test.ts +50 -0
  417. package/test/util/lock.test.ts +72 -0
  418. package/test/util/timeout.test.ts +21 -0
  419. package/test/util/wildcard.test.ts +75 -0
  420. package/tsconfig.json +16 -0
@@ -0,0 +1,87 @@
1
+ import { useDialog } from "@tui/ui/dialog"
2
+ import { DialogSelect } from "@tui/ui/dialog-select"
3
+ import { createMemo, createSignal } from "solid-js"
4
+ import { Locale } from "@/util/locale"
5
+ import { useTheme } from "../context/theme"
6
+ import { useKeybind } from "../context/keybind"
7
+ import { usePromptStash, type StashEntry } from "./prompt/stash"
8
+
9
+ function getRelativeTime(timestamp: number): string {
10
+ const now = Date.now()
11
+ const diff = now - timestamp
12
+ const seconds = Math.floor(diff / 1000)
13
+ const minutes = Math.floor(seconds / 60)
14
+ const hours = Math.floor(minutes / 60)
15
+ const days = Math.floor(hours / 24)
16
+
17
+ if (seconds < 60) return "just now"
18
+ if (minutes < 60) return `${minutes}m ago`
19
+ if (hours < 24) return `${hours}h ago`
20
+ if (days < 7) return `${days}d ago`
21
+ return Locale.datetime(timestamp)
22
+ }
23
+
24
+ function getStashPreview(input: string, maxLength: number = 50): string {
25
+ const firstLine = input.split("\n")[0].trim()
26
+ return Locale.truncate(firstLine, maxLength)
27
+ }
28
+
29
+ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
30
+ const dialog = useDialog()
31
+ const stash = usePromptStash()
32
+ const { theme } = useTheme()
33
+ const keybind = useKeybind()
34
+
35
+ const [toDelete, setToDelete] = createSignal<number>()
36
+
37
+ const options = createMemo(() => {
38
+ const entries = stash.list()
39
+ // Show most recent first
40
+ return entries
41
+ .map((entry, index) => {
42
+ const isDeleting = toDelete() === index
43
+ const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
44
+ return {
45
+ title: isDeleting ? `Press ${keybind.print("stash_delete")} again to confirm` : getStashPreview(entry.input),
46
+ bg: isDeleting ? theme.error : undefined,
47
+ value: index,
48
+ description: getRelativeTime(entry.timestamp),
49
+ footer: lineCount > 1 ? `~${lineCount} lines` : undefined,
50
+ }
51
+ })
52
+ .toReversed()
53
+ })
54
+
55
+ return (
56
+ <DialogSelect
57
+ title="Stash"
58
+ options={options()}
59
+ onMove={() => {
60
+ setToDelete(undefined)
61
+ }}
62
+ onSelect={(option) => {
63
+ const entries = stash.list()
64
+ const entry = entries[option.value]
65
+ if (entry) {
66
+ stash.remove(option.value)
67
+ props.onSelect(entry)
68
+ }
69
+ dialog.clear()
70
+ }}
71
+ keybind={[
72
+ {
73
+ keybind: keybind.all.stash_delete?.[0],
74
+ title: "delete",
75
+ onTrigger: (option) => {
76
+ if (toDelete() === option.value) {
77
+ stash.remove(option.value)
78
+ setToDelete(undefined)
79
+ return
80
+ }
81
+ setToDelete(option.value)
82
+ },
83
+ },
84
+ ]}
85
+ />
86
+ )
87
+ }
@@ -0,0 +1,164 @@
1
+ import { TextAttributes } from "@opentui/core"
2
+ import { useTheme } from "../context/theme"
3
+ import { useSync } from "@tui/context/sync"
4
+ import { For, Match, Switch, Show, createMemo } from "solid-js"
5
+ import { Installation } from "@/installation"
6
+
7
+ export type DialogStatusProps = {}
8
+
9
+ export function DialogStatus() {
10
+ const sync = useSync()
11
+ const { theme } = useTheme()
12
+
13
+ const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
14
+
15
+ const plugins = createMemo(() => {
16
+ const list = sync.data.config.plugin ?? []
17
+ const result = list.map((value) => {
18
+ if (value.startsWith("file://")) {
19
+ const path = value.substring("file://".length)
20
+ const parts = path.split("/")
21
+ const filename = parts.pop() || path
22
+ if (!filename.includes(".")) return { name: filename }
23
+ const basename = filename.split(".")[0]
24
+ if (basename === "index") {
25
+ const dirname = parts.pop()
26
+ const name = dirname || basename
27
+ return { name }
28
+ }
29
+ return { name: basename }
30
+ }
31
+ const index = value.lastIndexOf("@")
32
+ if (index <= 0) return { name: value, version: "latest" }
33
+ const name = value.substring(0, index)
34
+ const version = value.substring(index + 1)
35
+ return { name, version }
36
+ })
37
+ return result.toSorted((a, b) => a.name.localeCompare(b.name))
38
+ })
39
+
40
+ return (
41
+ <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
42
+ <box flexDirection="row" justifyContent="space-between">
43
+ <text fg={theme.text} attributes={TextAttributes.BOLD}>
44
+ Status
45
+ </text>
46
+ <text fg={theme.textMuted}>esc</text>
47
+ </box>
48
+ <text fg={theme.textMuted}>JonsOC v{Installation.VERSION}</text>
49
+ <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
50
+ <box>
51
+ <text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
52
+ <For each={Object.entries(sync.data.mcp)}>
53
+ {([key, item]) => (
54
+ <box flexDirection="row" gap={1}>
55
+ <text
56
+ flexShrink={0}
57
+ style={{
58
+ fg: (
59
+ {
60
+ connected: theme.success,
61
+ failed: theme.error,
62
+ disabled: theme.textMuted,
63
+ needs_auth: theme.warning,
64
+ needs_client_registration: theme.error,
65
+ } as Record<string, typeof theme.success>
66
+ )[item.status],
67
+ }}
68
+ >
69
+
70
+ </text>
71
+ <text fg={theme.text} wrapMode="word">
72
+ <b>{key}</b>{" "}
73
+ <span style={{ fg: theme.textMuted }}>
74
+ <Switch fallback={item.status}>
75
+ <Match when={item.status === "connected"}>Connected</Match>
76
+ <Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
77
+ <Match when={item.status === "disabled"}>Disabled in configuration</Match>
78
+ <Match when={(item.status as string) === "needs_auth"}>
79
+ Needs authentication (run: jonsoc mcp auth {key})
80
+ </Match>
81
+ <Match when={(item.status as string) === "needs_client_registration" && item}>
82
+ {(val) => (val() as { error: string }).error}
83
+ </Match>
84
+ </Switch>
85
+ </span>
86
+ </text>
87
+ </box>
88
+ )}
89
+ </For>
90
+ </box>
91
+ </Show>
92
+ {sync.data.lsp.length > 0 && (
93
+ <box>
94
+ <text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
95
+ <For each={sync.data.lsp}>
96
+ {(item) => (
97
+ <box flexDirection="row" gap={1}>
98
+ <text
99
+ flexShrink={0}
100
+ style={{
101
+ fg: {
102
+ connected: theme.success,
103
+ error: theme.error,
104
+ }[item.status],
105
+ }}
106
+ >
107
+
108
+ </text>
109
+ <text fg={theme.text} wrapMode="word">
110
+ <b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
111
+ </text>
112
+ </box>
113
+ )}
114
+ </For>
115
+ </box>
116
+ )}
117
+ <Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
118
+ <box>
119
+ <text fg={theme.text}>{enabledFormatters().length} Formatters</text>
120
+ <For each={enabledFormatters()}>
121
+ {(item) => (
122
+ <box flexDirection="row" gap={1}>
123
+ <text
124
+ flexShrink={0}
125
+ style={{
126
+ fg: theme.success,
127
+ }}
128
+ >
129
+
130
+ </text>
131
+ <text wrapMode="word" fg={theme.text}>
132
+ <b>{item.name}</b>
133
+ </text>
134
+ </box>
135
+ )}
136
+ </For>
137
+ </box>
138
+ </Show>
139
+ <Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
140
+ <box>
141
+ <text fg={theme.text}>{plugins().length} Plugins</text>
142
+ <For each={plugins()}>
143
+ {(item) => (
144
+ <box flexDirection="row" gap={1}>
145
+ <text
146
+ flexShrink={0}
147
+ style={{
148
+ fg: theme.success,
149
+ }}
150
+ >
151
+
152
+ </text>
153
+ <text wrapMode="word" fg={theme.text}>
154
+ <b>{item.name}</b>
155
+ {item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
156
+ </text>
157
+ </box>
158
+ )}
159
+ </For>
160
+ </box>
161
+ </Show>
162
+ </box>
163
+ )
164
+ }
@@ -0,0 +1,44 @@
1
+ import { createMemo, createResource } from "solid-js"
2
+ import { DialogSelect } from "@tui/ui/dialog-select"
3
+ import { useDialog } from "@tui/ui/dialog"
4
+ import { useSDK } from "@tui/context/sdk"
5
+ import { createStore } from "solid-js/store"
6
+
7
+ export function DialogTag(props: { onSelect?: (value: string) => void }) {
8
+ const sdk = useSDK()
9
+ const dialog = useDialog()
10
+
11
+ const [store] = createStore({
12
+ filter: "",
13
+ })
14
+
15
+ const [files] = createResource(
16
+ () => [store.filter],
17
+ async () => {
18
+ const result = await sdk.client.find.files({
19
+ query: store.filter,
20
+ })
21
+ if (result.error) return []
22
+ const sliced = (result.data ?? []).slice(0, 5)
23
+ return sliced
24
+ },
25
+ )
26
+
27
+ const options = createMemo(() =>
28
+ (files() ?? []).map((file) => ({
29
+ value: file,
30
+ title: file,
31
+ })),
32
+ )
33
+
34
+ return (
35
+ <DialogSelect
36
+ title="Autocomplete"
37
+ options={options()}
38
+ onSelect={(option) => {
39
+ props.onSelect?.(option.value)
40
+ dialog.clear()
41
+ }}
42
+ />
43
+ )
44
+ }
@@ -0,0 +1,50 @@
1
+ import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select"
2
+ import { useTheme } from "../context/theme"
3
+ import { useDialog } from "../ui/dialog"
4
+ import { onCleanup, onMount } from "solid-js"
5
+
6
+ export function DialogThemeList() {
7
+ const theme = useTheme()
8
+ const options = Object.keys(theme.all())
9
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
10
+ .map((value) => ({
11
+ title: value,
12
+ value: value,
13
+ }))
14
+ const dialog = useDialog()
15
+ let confirmed = false
16
+ let ref: DialogSelectRef<string>
17
+ const initial = theme.selected
18
+
19
+ onCleanup(() => {
20
+ if (!confirmed) theme.set(initial)
21
+ })
22
+
23
+ return (
24
+ <DialogSelect
25
+ title="Themes"
26
+ options={options}
27
+ current={initial}
28
+ onMove={(opt) => {
29
+ theme.set(opt.value)
30
+ }}
31
+ onSelect={(opt) => {
32
+ theme.set(opt.value)
33
+ confirmed = true
34
+ dialog.clear()
35
+ }}
36
+ ref={(r) => {
37
+ ref = r
38
+ }}
39
+ onFilter={(query) => {
40
+ if (query.length === 0) {
41
+ theme.set(initial)
42
+ return
43
+ }
44
+
45
+ const first = ref.filtered[0]
46
+ if (first) theme.set(first.value)
47
+ }}
48
+ />
49
+ )
50
+ }
@@ -0,0 +1,86 @@
1
+ import { createMemo, For, Show } from "solid-js"
2
+ import { useLayout, type PanelType, type PanelPosition } from "@tui/context/layout"
3
+ import { useTheme } from "@tui/context/theme"
4
+ import { useTerminalDimensions } from "@opentui/solid"
5
+ import type { JSX } from "solid-js"
6
+
7
+ interface DynamicLayoutProps {
8
+ chat: JSX.Element
9
+ explorer: JSX.Element
10
+ viewer: JSX.Element
11
+ }
12
+
13
+ export function DynamicLayout(props: DynamicLayoutProps) {
14
+ const layout = useLayout()
15
+ const { theme } = useTheme()
16
+ const dimensions = useTerminalDimensions()
17
+
18
+ const totalWidth = createMemo(() => {
19
+ const dims = dimensions()
20
+ return dims?.width ? dims.width - 4 : 80
21
+ })
22
+
23
+ const getPanelWidth = (position: PanelPosition) => {
24
+ const panel = layout.getPanelAt(position)
25
+ if (!panel || !panel.visible) return 0
26
+ const total = totalWidth()
27
+
28
+ // Calculate total width of all visible panels for normalization
29
+ const visiblePanels = layout.panels.filter((p) => p.visible)
30
+ const totalVisibleWidth = visiblePanels.reduce((sum, p) => sum + p.width, 0)
31
+
32
+ // Normalize width so visible panels fill 100% of available space
33
+ if (totalVisibleWidth === 0) return 0
34
+ const normalizedWidth = (panel.width / totalVisibleWidth) * 100
35
+ return Math.floor((total * normalizedWidth) / 100)
36
+ }
37
+
38
+ const getPanelContent = (type: PanelType) => {
39
+ switch (type) {
40
+ case "chat":
41
+ return props.chat
42
+ case "explorer":
43
+ return props.explorer
44
+ case "viewer":
45
+ return props.viewer
46
+ }
47
+ }
48
+
49
+ const safeDimensions = createMemo(() => {
50
+ const dims = dimensions()
51
+ return {
52
+ width: dims?.width ?? 80,
53
+ height: dims?.height ?? 24,
54
+ }
55
+ })
56
+
57
+ return (
58
+ <box width={safeDimensions().width} height={safeDimensions().height} flexDirection="row">
59
+ <For each={[0, 1, 2]}>
60
+ {(position) => {
61
+ const panel = createMemo(() => layout.getPanelAt(position as PanelPosition))
62
+ const width = createMemo(() => getPanelWidth(position as PanelPosition))
63
+
64
+ return (
65
+ <>
66
+ {/* Thin border between panels using line character */}
67
+ <Show
68
+ when={position > 0 && layout.getPanelAt((position - 1) as PanelPosition)?.visible && panel()?.visible}
69
+ >
70
+ <box width={1} height="100%">
71
+ <text fg={theme.border}>{"│\n".repeat(safeDimensions().height)}</text>
72
+ </box>
73
+ </Show>
74
+
75
+ <Show when={panel()?.visible}>
76
+ <box width={width()} height="100%">
77
+ {getPanelContent(panel()!.type)}
78
+ </box>
79
+ </Show>
80
+ </>
81
+ )
82
+ }}
83
+ </For>
84
+ </box>
85
+ )
86
+ }
@@ -0,0 +1,247 @@
1
+ import { useRenderer, useTerminalDimensions } from "@opentui/solid"
2
+ import { RGBA, type Renderable, ScrollBoxRenderable } from "@opentui/core"
3
+ import { createEffect, createMemo, Show, createSignal } from "solid-js"
4
+ import { useInspector } from "../context/inspector"
5
+ import { useTheme } from "../context/theme"
6
+ import { Clipboard } from "@tui/util/clipboard"
7
+ import { useToast } from "../ui/toast"
8
+
9
+ // Get absolute position
10
+ // renderable.x/y already recursively include all parent offsets and scroll translations
11
+ function getAbsolutePosition(renderable: Renderable): { x: number; y: number } {
12
+ return { x: renderable.x, y: renderable.y }
13
+ }
14
+
15
+ // Check if point is inside renderable bounds
16
+ function isPointInside(renderable: Renderable, px: number, py: number): boolean {
17
+ const pos = getAbsolutePosition(renderable)
18
+ return px >= pos.x && px < pos.x + renderable.width && py >= pos.y && py < pos.y + renderable.height
19
+ }
20
+
21
+ // Find the topmost element at position (excluding the overlay itself)
22
+ function findElementAtPositionTree(root: Renderable, x: number, y: number, excludeId?: string): Renderable | null {
23
+ let found: Renderable | null = null
24
+ let foundArea = Infinity
25
+
26
+ function traverse(renderable: Renderable) {
27
+ // Skip if this is the element to exclude
28
+ if (excludeId && renderable.id === excludeId) return
29
+
30
+ // Check if point is inside this renderable
31
+ if (isPointInside(renderable, x, y)) {
32
+ const area = renderable.width * renderable.height
33
+ // Prefer smaller elements (more specific)
34
+ if (area < foundArea) {
35
+ found = renderable
36
+ foundArea = area
37
+ }
38
+ }
39
+
40
+ // Check children (recurse into all children)
41
+ for (const child of renderable.getChildren()) {
42
+ traverse(child)
43
+ }
44
+ }
45
+
46
+ // Start traversal from root's children (skip root itself)
47
+ for (const child of root.getChildren()) {
48
+ traverse(child)
49
+ }
50
+
51
+ return found
52
+ }
53
+
54
+ // Normalize margin/padding values to numbers
55
+ function normalizeSpacing(value: string | number | null | undefined): number {
56
+ if (typeof value === "number") return value
57
+ if (typeof value === "string" && value.endsWith("%")) return 0 // Can't easily calc %
58
+ if (value === "auto") return 0
59
+ return 0
60
+ }
61
+
62
+ // Build a path from root to this element for context
63
+ function getElementPath(renderable: Renderable): string {
64
+ const parts: string[] = []
65
+ let current: Renderable | null = renderable
66
+
67
+ while (current) {
68
+ // Skip auto-generated IDs, use type instead
69
+ const isAutoGen = /^box-\d+$|^text-\d+$/.test(current.id)
70
+ const name = isAutoGen ? current.constructor.name.replace("Renderable", "") : current.id
71
+ parts.unshift(name)
72
+ current = current.parent
73
+ }
74
+
75
+ return parts.join(" > ")
76
+ }
77
+
78
+ // Get element info for display
79
+ function getElementInfo(renderable: Renderable) {
80
+ const pos = getAbsolutePosition(renderable)
81
+ const path = getElementPath(renderable)
82
+
83
+ // Try to find a meaningful name
84
+ const isAutoGen = /^box-\d+$|^text-\d+$/.test(renderable.id)
85
+ const displayName = isAutoGen ? path.split(" > ").pop() || renderable.id : renderable.id
86
+
87
+ // Get source location if available
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const source = (
90
+ renderable as unknown as { __source?: { fileName?: string; lineNumber?: number; componentName?: string } }
91
+ ).__source
92
+
93
+ return {
94
+ id: renderable.id,
95
+ displayName,
96
+ path,
97
+ type: renderable.constructor.name,
98
+ x: pos.x,
99
+ y: pos.y,
100
+ width: renderable.width,
101
+ height: renderable.height,
102
+ source,
103
+ margin: {
104
+ top: normalizeSpacing(renderable.marginTop),
105
+ right: normalizeSpacing(renderable.marginRight),
106
+ bottom: normalizeSpacing(renderable.marginBottom),
107
+ left: normalizeSpacing(renderable.marginLeft),
108
+ },
109
+ padding: {
110
+ top: normalizeSpacing(renderable.paddingTop),
111
+ right: normalizeSpacing(renderable.paddingRight),
112
+ bottom: normalizeSpacing(renderable.paddingBottom),
113
+ left: normalizeSpacing(renderable.paddingLeft),
114
+ },
115
+ }
116
+ }
117
+
118
+ export function InspectorOverlay() {
119
+ const inspector = useInspector()
120
+ const { theme } = useTheme()
121
+ const dimensions = useTerminalDimensions()
122
+ const renderer = useRenderer()
123
+ const toast = useToast()
124
+
125
+ const [hoveredElement, setHoveredElement] = createSignal<Renderable | null>(null)
126
+
127
+ const isEnabled = () => inspector.enabled()
128
+
129
+ // Colors for different visual elements - more transparent
130
+ const colors = createMemo(() => ({
131
+ content: theme.primary,
132
+ padding: theme.warning,
133
+ margin: theme.info,
134
+ text: theme.text,
135
+ panelBg: RGBA.fromInts(30, 30, 30, 220), // 80% opacity dark background
136
+ overlayBg: RGBA.fromInts(0, 0, 0, 51), // 20% opacity = 80% transparent
137
+ border: theme.border,
138
+ marginBg: RGBA.fromInts(255, 140, 0, 25), // Orange - more transparent
139
+ paddingBg: RGBA.fromInts(255, 215, 0, 25), // Yellow - more transparent
140
+ contentBg: RGBA.fromInts(0, 255, 127, 15), // Green - more transparent
141
+ marginBorder: RGBA.fromInts(255, 140, 0, 120),
142
+ paddingBorder: RGBA.fromInts(255, 215, 0, 120),
143
+ contentBorder: RGBA.fromInts(0, 255, 127, 120),
144
+ }))
145
+
146
+ createEffect(() => {
147
+ if (!isEnabled()) {
148
+ inspector.setHoveredInfo(null)
149
+ setHoveredElement(null)
150
+ }
151
+ })
152
+
153
+ const mouse = () => inspector.mousePos()
154
+ const dims = () => dimensions()
155
+
156
+ // Find element under cursor (manually traverse to skip the overlay)
157
+ const findElementAtPosition = (x: number, y: number): Renderable | null => {
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ return findElementAtPositionTree(renderer.root as any, x, y, "inspector-overlay")
160
+ }
161
+
162
+ // Get element info for display
163
+ const elementInfo = createMemo(() => {
164
+ const el = hoveredElement()
165
+ if (!el) return null
166
+ return getElementInfo(el)
167
+ })
168
+
169
+ return (
170
+ <Show when={isEnabled()}>
171
+ <box
172
+ id="inspector-overlay"
173
+ position="absolute"
174
+ top={0}
175
+ left={0}
176
+ width={dims().width}
177
+ height={dims().height}
178
+ zIndex={9999}
179
+ backgroundColor={colors().overlayBg}
180
+ onMouseMove={(e) => {
181
+ inspector.setMousePos({ x: e.x, y: e.y })
182
+ const el = findElementAtPosition(e.x, e.y)
183
+ setHoveredElement(el)
184
+ }}
185
+ onMouseUp={async () => {
186
+ const el = hoveredElement()
187
+ if (el) {
188
+ const info = getElementInfo(el)
189
+ // Build source info string
190
+ const sourceInfo = info.source
191
+ ? `${info.source.componentName || info.displayName} ${info.width}x${info.height} ${info.source.fileName}:${info.source.lineNumber}`
192
+ : `${info.displayName} ${info.width}x${info.height}`
193
+ await Clipboard.copy(sourceInfo)
194
+ .then(() => toast.show({ message: `Copied: ${info.displayName}`, variant: "info" }))
195
+ .catch(() => toast.show({ message: "Failed to copy", variant: "error" }))
196
+ }
197
+ inspector.setEnabled(false)
198
+ }}
199
+ >
200
+ {/* Element highlight - exact element bounds */}
201
+ <Show when={elementInfo()}>
202
+ {(info) => (
203
+ <box
204
+ position="absolute"
205
+ top={info().y}
206
+ left={info().x}
207
+ width={info().width}
208
+ height={info().height}
209
+ border={["left", "right", "top", "bottom"]}
210
+ borderColor={colors().contentBorder}
211
+ backgroundColor={colors().contentBg}
212
+ />
213
+ )}
214
+ </Show>
215
+
216
+ {/* Position tooltip near cursor */}
217
+ <Show when={mouse().y > 4 && elementInfo()}>
218
+ {(info) => {
219
+ const source = info().source
220
+ const displayText = source
221
+ ? `${source.componentName || info().displayName} ${info().width}x${info().height}`
222
+ : `${info().displayName} ${info().width}x${info().height}`
223
+ const sourceText = source ? `${source.fileName}:${source.lineNumber}` : ""
224
+
225
+ return (
226
+ <box
227
+ position="absolute"
228
+ top={mouse().y - 2}
229
+ left={Math.min(mouse().x + 2, dims().width - 20)}
230
+ paddingLeft={1}
231
+ paddingRight={1}
232
+ backgroundColor={theme.backgroundElement}
233
+ border={["left", "right", "top", "bottom"]}
234
+ borderColor={theme.border}
235
+ >
236
+ <text fg={theme.primary}>{displayText}</text>
237
+ <Show when={sourceText}>
238
+ <text fg={theme.textMuted}>{sourceText}</text>
239
+ </Show>
240
+ </box>
241
+ )
242
+ }}
243
+ </Show>
244
+ </box>
245
+ </Show>
246
+ )
247
+ }