@bloxystudios/bloxycode 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -0
  3. package/bin/bloxycode +84 -0
  4. package/package.json +133 -0
  5. package/src/acp/README.md +164 -0
  6. package/src/acp/agent.ts +1437 -0
  7. package/src/acp/session.ts +105 -0
  8. package/src/acp/types.ts +22 -0
  9. package/src/agent/agent.ts +356 -0
  10. package/src/agent/generate.txt +75 -0
  11. package/src/agent/prompt/bloxy.txt +46 -0
  12. package/src/agent/prompt/compaction.txt +12 -0
  13. package/src/agent/prompt/explore.txt +18 -0
  14. package/src/agent/prompt/summary.txt +11 -0
  15. package/src/agent/prompt/title.txt +44 -0
  16. package/src/auth/index.ts +73 -0
  17. package/src/bloxy/event.ts +41 -0
  18. package/src/bloxy/index.ts +5 -0
  19. package/src/bloxy/parser.ts +263 -0
  20. package/src/bloxy/prompt.ts +121 -0
  21. package/src/bloxy/runner.ts +193 -0
  22. package/src/bloxy/state.ts +246 -0
  23. package/src/bun/index.ts +134 -0
  24. package/src/bus/bus-event.ts +43 -0
  25. package/src/bus/global.ts +10 -0
  26. package/src/bus/index.ts +105 -0
  27. package/src/cli/bootstrap.ts +17 -0
  28. package/src/cli/cmd/acp.ts +69 -0
  29. package/src/cli/cmd/agent.ts +257 -0
  30. package/src/cli/cmd/auth.ts +400 -0
  31. package/src/cli/cmd/cmd.ts +7 -0
  32. package/src/cli/cmd/debug/agent.ts +167 -0
  33. package/src/cli/cmd/debug/config.ts +16 -0
  34. package/src/cli/cmd/debug/file.ts +97 -0
  35. package/src/cli/cmd/debug/index.ts +48 -0
  36. package/src/cli/cmd/debug/lsp.ts +52 -0
  37. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  38. package/src/cli/cmd/debug/scrap.ts +16 -0
  39. package/src/cli/cmd/debug/skill.ts +16 -0
  40. package/src/cli/cmd/debug/snapshot.ts +52 -0
  41. package/src/cli/cmd/export.ts +88 -0
  42. package/src/cli/cmd/generate.ts +38 -0
  43. package/src/cli/cmd/github.ts +1548 -0
  44. package/src/cli/cmd/import.ts +98 -0
  45. package/src/cli/cmd/mcp.ts +755 -0
  46. package/src/cli/cmd/models.ts +77 -0
  47. package/src/cli/cmd/pr.ts +112 -0
  48. package/src/cli/cmd/run.ts +395 -0
  49. package/src/cli/cmd/serve.ts +20 -0
  50. package/src/cli/cmd/session.ts +135 -0
  51. package/src/cli/cmd/stats.ts +402 -0
  52. package/src/cli/cmd/tui/app.tsx +771 -0
  53. package/src/cli/cmd/tui/attach.ts +39 -0
  54. package/src/cli/cmd/tui/component/border.tsx +21 -0
  55. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  56. package/src/cli/cmd/tui/component/dialog-command.tsx +148 -0
  57. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  58. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  59. package/src/cli/cmd/tui/component/dialog-provider.tsx +256 -0
  60. package/src/cli/cmd/tui/component/dialog-session-list.tsx +114 -0
  61. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  62. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  63. package/src/cli/cmd/tui/component/dialog-status.tsx +164 -0
  64. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  65. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  66. package/src/cli/cmd/tui/component/logo.tsx +102 -0
  67. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +653 -0
  68. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  69. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  70. package/src/cli/cmd/tui/component/prompt/index.tsx +1138 -0
  71. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  72. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  73. package/src/cli/cmd/tui/component/tips.tsx +153 -0
  74. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  75. package/src/cli/cmd/tui/context/args.tsx +14 -0
  76. package/src/cli/cmd/tui/context/directory.ts +13 -0
  77. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  78. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  79. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  80. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  81. package/src/cli/cmd/tui/context/local.tsx +402 -0
  82. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  83. package/src/cli/cmd/tui/context/route.tsx +46 -0
  84. package/src/cli/cmd/tui/context/sdk.tsx +94 -0
  85. package/src/cli/cmd/tui/context/sync.tsx +470 -0
  86. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  87. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  88. package/src/cli/cmd/tui/context/theme/bloxycode.json +245 -0
  89. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  90. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  91. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  93. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  94. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  95. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  96. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  97. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  98. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  99. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  100. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  101. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  102. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  103. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  104. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  105. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  106. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  107. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  108. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  109. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  110. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  111. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  112. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  113. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  114. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  115. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  116. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  117. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  118. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  119. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  120. package/src/cli/cmd/tui/event.ts +48 -0
  121. package/src/cli/cmd/tui/routes/home.tsx +140 -0
  122. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  123. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  126. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  127. package/src/cli/cmd/tui/routes/session/header.tsx +142 -0
  128. package/src/cli/cmd/tui/routes/session/index.tsx +2048 -0
  129. package/src/cli/cmd/tui/routes/session/permission.tsx +508 -0
  130. package/src/cli/cmd/tui/routes/session/question.tsx +453 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  132. package/src/cli/cmd/tui/thread.ts +165 -0
  133. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  134. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  135. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  136. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  137. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  138. package/src/cli/cmd/tui/ui/dialog-select.tsx +385 -0
  139. package/src/cli/cmd/tui/ui/dialog.tsx +167 -0
  140. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  141. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  142. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  143. package/src/cli/cmd/tui/util/clipboard.ts +160 -0
  144. package/src/cli/cmd/tui/util/editor.ts +32 -0
  145. package/src/cli/cmd/tui/util/signal.ts +7 -0
  146. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  147. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  148. package/src/cli/cmd/tui/worker.ts +152 -0
  149. package/src/cli/cmd/uninstall.ts +357 -0
  150. package/src/cli/cmd/upgrade.ts +73 -0
  151. package/src/cli/cmd/web.ts +81 -0
  152. package/src/cli/error.ts +57 -0
  153. package/src/cli/network.ts +53 -0
  154. package/src/cli/ui.ts +86 -0
  155. package/src/cli/upgrade.ts +25 -0
  156. package/src/command/index.ts +173 -0
  157. package/src/command/template/bloxy-resume.txt +15 -0
  158. package/src/command/template/bloxy-status.txt +25 -0
  159. package/src/command/template/bloxy-validate.txt +22 -0
  160. package/src/command/template/bloxy.txt +14 -0
  161. package/src/command/template/initialize.txt +10 -0
  162. package/src/command/template/review.txt +99 -0
  163. package/src/config/config.ts +1367 -0
  164. package/src/config/markdown.ts +93 -0
  165. package/src/env/index.ts +26 -0
  166. package/src/file/ignore.ts +83 -0
  167. package/src/file/index.ts +415 -0
  168. package/src/file/ripgrep.ts +407 -0
  169. package/src/file/time.ts +69 -0
  170. package/src/file/watcher.ts +127 -0
  171. package/src/flag/flag.ts +79 -0
  172. package/src/format/formatter.ts +357 -0
  173. package/src/format/index.ts +137 -0
  174. package/src/global/index.ts +55 -0
  175. package/src/id/id.ts +83 -0
  176. package/src/ide/index.ts +76 -0
  177. package/src/index.ts +159 -0
  178. package/src/installation/index.ts +246 -0
  179. package/src/lsp/client.ts +252 -0
  180. package/src/lsp/index.ts +485 -0
  181. package/src/lsp/language.ts +119 -0
  182. package/src/lsp/server.ts +2046 -0
  183. package/src/mcp/auth.ts +135 -0
  184. package/src/mcp/index.ts +934 -0
  185. package/src/mcp/oauth-callback.ts +200 -0
  186. package/src/mcp/oauth-provider.ts +154 -0
  187. package/src/patch/index.ts +680 -0
  188. package/src/permission/arity.ts +163 -0
  189. package/src/permission/index.ts +210 -0
  190. package/src/permission/next.ts +280 -0
  191. package/src/plugin/antigravity.ts +378 -0
  192. package/src/plugin/codex.ts +506 -0
  193. package/src/plugin/copilot.ts +298 -0
  194. package/src/plugin/index.ts +136 -0
  195. package/src/project/bootstrap.ts +35 -0
  196. package/src/project/instance.ts +91 -0
  197. package/src/project/project.ts +371 -0
  198. package/src/project/state.ts +66 -0
  199. package/src/project/vcs.ts +76 -0
  200. package/src/provider/auth.ts +147 -0
  201. package/src/provider/models-snapshot.ts +2 -0
  202. package/src/provider/models.ts +133 -0
  203. package/src/provider/provider.ts +1241 -0
  204. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  205. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  206. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  208. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  209. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  210. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  211. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  212. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
  213. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  214. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  215. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  216. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  217. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  218. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  219. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  220. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  221. package/src/provider/transform.ts +741 -0
  222. package/src/pty/index.ts +241 -0
  223. package/src/question/index.ts +171 -0
  224. package/src/scheduler/index.ts +61 -0
  225. package/src/server/error.ts +36 -0
  226. package/src/server/event.ts +7 -0
  227. package/src/server/mdns.ts +59 -0
  228. package/src/server/routes/config.ts +92 -0
  229. package/src/server/routes/experimental.ts +208 -0
  230. package/src/server/routes/file.ts +197 -0
  231. package/src/server/routes/global.ts +135 -0
  232. package/src/server/routes/mcp.ts +225 -0
  233. package/src/server/routes/permission.ts +68 -0
  234. package/src/server/routes/project.ts +82 -0
  235. package/src/server/routes/provider.ts +165 -0
  236. package/src/server/routes/pty.ts +169 -0
  237. package/src/server/routes/question.ts +98 -0
  238. package/src/server/routes/session.ts +939 -0
  239. package/src/server/routes/tui.ts +379 -0
  240. package/src/server/server.ts +604 -0
  241. package/src/session/compaction.ts +225 -0
  242. package/src/session/fallback.ts +246 -0
  243. package/src/session/index.ts +498 -0
  244. package/src/session/instruction.ts +164 -0
  245. package/src/session/llm.ts +298 -0
  246. package/src/session/message-v2.ts +747 -0
  247. package/src/session/message.ts +189 -0
  248. package/src/session/processor.ts +450 -0
  249. package/src/session/prompt/anthropic-20250930.txt +166 -0
  250. package/src/session/prompt/anthropic.txt +105 -0
  251. package/src/session/prompt/beast.txt +147 -0
  252. package/src/session/prompt/build-switch.txt +5 -0
  253. package/src/session/prompt/codex_header.txt +79 -0
  254. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  255. package/src/session/prompt/gemini.txt +155 -0
  256. package/src/session/prompt/max-steps.txt +16 -0
  257. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  258. package/src/session/prompt/plan.txt +26 -0
  259. package/src/session/prompt/qwen.txt +109 -0
  260. package/src/session/prompt.ts +1822 -0
  261. package/src/session/retry.ts +99 -0
  262. package/src/session/revert.ts +121 -0
  263. package/src/session/status.ts +100 -0
  264. package/src/session/summary.ts +217 -0
  265. package/src/session/system.ts +52 -0
  266. package/src/session/todo.ts +37 -0
  267. package/src/share/share-next.ts +200 -0
  268. package/src/share/share.ts +92 -0
  269. package/src/shell/shell.ts +67 -0
  270. package/src/skill/index.ts +1 -0
  271. package/src/skill/skill.ts +135 -0
  272. package/src/snapshot/index.ts +236 -0
  273. package/src/storage/storage.ts +227 -0
  274. package/src/tool/apply_patch.ts +281 -0
  275. package/src/tool/apply_patch.txt +33 -0
  276. package/src/tool/bash.ts +258 -0
  277. package/src/tool/bash.txt +115 -0
  278. package/src/tool/batch.ts +175 -0
  279. package/src/tool/batch.txt +24 -0
  280. package/src/tool/bloxy-control.ts +123 -0
  281. package/src/tool/bloxy-control.txt +13 -0
  282. package/src/tool/codesearch.ts +132 -0
  283. package/src/tool/codesearch.txt +12 -0
  284. package/src/tool/edit.ts +655 -0
  285. package/src/tool/edit.txt +10 -0
  286. package/src/tool/external-directory.ts +32 -0
  287. package/src/tool/glob.ts +77 -0
  288. package/src/tool/glob.txt +6 -0
  289. package/src/tool/grep.ts +154 -0
  290. package/src/tool/grep.txt +8 -0
  291. package/src/tool/invalid.ts +17 -0
  292. package/src/tool/ls.ts +121 -0
  293. package/src/tool/ls.txt +1 -0
  294. package/src/tool/lsp.ts +96 -0
  295. package/src/tool/lsp.txt +19 -0
  296. package/src/tool/multiedit.ts +46 -0
  297. package/src/tool/multiedit.txt +41 -0
  298. package/src/tool/plan-enter.txt +14 -0
  299. package/src/tool/plan-exit.txt +13 -0
  300. package/src/tool/plan.ts +130 -0
  301. package/src/tool/question.ts +33 -0
  302. package/src/tool/question.txt +10 -0
  303. package/src/tool/read.ts +211 -0
  304. package/src/tool/read.txt +12 -0
  305. package/src/tool/registry.ts +161 -0
  306. package/src/tool/skill.ts +82 -0
  307. package/src/tool/task.ts +191 -0
  308. package/src/tool/task.txt +60 -0
  309. package/src/tool/todo.ts +53 -0
  310. package/src/tool/todoread.txt +14 -0
  311. package/src/tool/todowrite.txt +167 -0
  312. package/src/tool/tool.ts +89 -0
  313. package/src/tool/truncation.ts +106 -0
  314. package/src/tool/webfetch.ts +188 -0
  315. package/src/tool/webfetch.txt +13 -0
  316. package/src/tool/websearch.ts +150 -0
  317. package/src/tool/websearch.txt +14 -0
  318. package/src/tool/write.ts +85 -0
  319. package/src/tool/write.txt +8 -0
  320. package/src/util/archive.ts +16 -0
  321. package/src/util/binary.ts +41 -0
  322. package/src/util/color.ts +19 -0
  323. package/src/util/context.ts +25 -0
  324. package/src/util/defer.ts +12 -0
  325. package/src/util/error.ts +54 -0
  326. package/src/util/eventloop.ts +20 -0
  327. package/src/util/filesystem.ts +93 -0
  328. package/src/util/fn.ts +11 -0
  329. package/src/util/format.ts +20 -0
  330. package/src/util/iife.ts +3 -0
  331. package/src/util/keybind.ts +103 -0
  332. package/src/util/lazy.ts +23 -0
  333. package/src/util/locale.ts +81 -0
  334. package/src/util/lock.ts +98 -0
  335. package/src/util/log.ts +180 -0
  336. package/src/util/queue.ts +32 -0
  337. package/src/util/rpc.ts +66 -0
  338. package/src/util/scrap.ts +10 -0
  339. package/src/util/signal.ts +12 -0
  340. package/src/util/slug.ts +74 -0
  341. package/src/util/timeout.ts +14 -0
  342. package/src/util/token.ts +7 -0
  343. package/src/util/wildcard.ts +56 -0
  344. package/src/worktree/index.ts +549 -0
@@ -0,0 +1,93 @@
1
+ import { realpathSync } from "fs"
2
+ import { dirname, join, relative } from "path"
3
+
4
+ export namespace Filesystem {
5
+ export const exists = (p: string) =>
6
+ Bun.file(p)
7
+ .stat()
8
+ .then(() => true)
9
+ .catch(() => false)
10
+
11
+ export const isDir = (p: string) =>
12
+ Bun.file(p)
13
+ .stat()
14
+ .then((s) => s.isDirectory())
15
+ .catch(() => false)
16
+ /**
17
+ * On Windows, normalize a path to its canonical casing using the filesystem.
18
+ * This is needed because Windows paths are case-insensitive but LSP servers
19
+ * may return paths with different casing than what we send them.
20
+ */
21
+ export function normalizePath(p: string): string {
22
+ if (process.platform !== "win32") return p
23
+ try {
24
+ return realpathSync.native(p)
25
+ } catch {
26
+ return p
27
+ }
28
+ }
29
+ export function overlaps(a: string, b: string) {
30
+ const relA = relative(a, b)
31
+ const relB = relative(b, a)
32
+ return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
33
+ }
34
+
35
+ export function contains(parent: string, child: string) {
36
+ return !relative(parent, child).startsWith("..")
37
+ }
38
+
39
+ export async function findUp(target: string, start: string, stop?: string) {
40
+ let current = start
41
+ const result = []
42
+ while (true) {
43
+ const search = join(current, target)
44
+ if (await exists(search)) result.push(search)
45
+ if (stop === current) break
46
+ const parent = dirname(current)
47
+ if (parent === current) break
48
+ current = parent
49
+ }
50
+ return result
51
+ }
52
+
53
+ export async function* up(options: { targets: string[]; start: string; stop?: string }) {
54
+ const { targets, start, stop } = options
55
+ let current = start
56
+ while (true) {
57
+ for (const target of targets) {
58
+ const search = join(current, target)
59
+ if (await exists(search)) yield search
60
+ }
61
+ if (stop === current) break
62
+ const parent = dirname(current)
63
+ if (parent === current) break
64
+ current = parent
65
+ }
66
+ }
67
+
68
+ export async function globUp(pattern: string, start: string, stop?: string) {
69
+ let current = start
70
+ const result = []
71
+ while (true) {
72
+ try {
73
+ const glob = new Bun.Glob(pattern)
74
+ for await (const match of glob.scan({
75
+ cwd: current,
76
+ absolute: true,
77
+ onlyFiles: true,
78
+ followSymlinks: true,
79
+ dot: true,
80
+ })) {
81
+ result.push(match)
82
+ }
83
+ } catch {
84
+ // Skip invalid glob patterns
85
+ }
86
+ if (stop === current) break
87
+ const parent = dirname(current)
88
+ if (parent === current) break
89
+ current = parent
90
+ }
91
+ return result
92
+ }
93
+ }
package/src/util/fn.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { z } from "zod"
2
+
3
+ export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
4
+ const result = (input: z.infer<T>) => {
5
+ const parsed = schema.parse(input)
6
+ return cb(parsed)
7
+ }
8
+ result.force = (input: z.infer<T>) => cb(input)
9
+ result.schema = schema
10
+ return result
11
+ }
@@ -0,0 +1,20 @@
1
+ export function formatDuration(secs: number) {
2
+ if (secs <= 0) return ""
3
+ if (secs < 60) return `${secs}s`
4
+ if (secs < 3600) {
5
+ const mins = Math.floor(secs / 60)
6
+ const remaining = secs % 60
7
+ return remaining > 0 ? `${mins}m ${remaining}s` : `${mins}m`
8
+ }
9
+ if (secs < 86400) {
10
+ const hours = Math.floor(secs / 3600)
11
+ const remaining = Math.floor((secs % 3600) / 60)
12
+ return remaining > 0 ? `${hours}h ${remaining}m` : `${hours}h`
13
+ }
14
+ if (secs < 604800) {
15
+ const days = Math.floor(secs / 86400)
16
+ return days === 1 ? "~1 day" : `~${days} days`
17
+ }
18
+ const weeks = Math.floor(secs / 604800)
19
+ return weeks === 1 ? "~1 week" : `~${weeks} weeks`
20
+ }
@@ -0,0 +1,3 @@
1
+ export function iife<T>(fn: () => T) {
2
+ return fn()
3
+ }
@@ -0,0 +1,103 @@
1
+ import { isDeepEqual } from "remeda"
2
+ import type { ParsedKey } from "@opentui/core"
3
+
4
+ export namespace Keybind {
5
+ /**
6
+ * Keybind info derived from OpenTUI's ParsedKey with our custom `leader` field.
7
+ * This ensures type compatibility and catches missing fields at compile time.
8
+ */
9
+ export type Info = Pick<ParsedKey, "name" | "ctrl" | "meta" | "shift" | "super"> & {
10
+ leader: boolean // our custom field
11
+ }
12
+
13
+ export function match(a: Info | undefined, b: Info): boolean {
14
+ if (!a) return false
15
+ const normalizedA = { ...a, super: a.super ?? false }
16
+ const normalizedB = { ...b, super: b.super ?? false }
17
+ return isDeepEqual(normalizedA, normalizedB)
18
+ }
19
+
20
+ /**
21
+ * Convert OpenTUI's ParsedKey to our Keybind.Info format.
22
+ * This helper ensures all required fields are present and avoids manual object creation.
23
+ */
24
+ export function fromParsedKey(key: ParsedKey, leader = false): Info {
25
+ return {
26
+ name: key.name,
27
+ ctrl: key.ctrl,
28
+ meta: key.meta,
29
+ shift: key.shift,
30
+ super: key.super ?? false,
31
+ leader,
32
+ }
33
+ }
34
+
35
+ export function toString(info: Info | undefined): string {
36
+ if (!info) return ""
37
+ const parts: string[] = []
38
+
39
+ if (info.ctrl) parts.push("ctrl")
40
+ if (info.meta) parts.push("alt")
41
+ if (info.super) parts.push("super")
42
+ if (info.shift) parts.push("shift")
43
+ if (info.name) {
44
+ if (info.name === "delete") parts.push("del")
45
+ else parts.push(info.name)
46
+ }
47
+
48
+ let result = parts.join("+")
49
+
50
+ if (info.leader) {
51
+ result = result ? `<leader> ${result}` : `<leader>`
52
+ }
53
+
54
+ return result
55
+ }
56
+
57
+ export function parse(key: string): Info[] {
58
+ if (key === "none") return []
59
+
60
+ return key.split(",").map((combo) => {
61
+ // Handle <leader> syntax by replacing with leader+
62
+ const normalized = combo.replace(/<leader>/g, "leader+")
63
+ const parts = normalized.toLowerCase().split("+")
64
+ const info: Info = {
65
+ ctrl: false,
66
+ meta: false,
67
+ shift: false,
68
+ leader: false,
69
+ name: "",
70
+ }
71
+
72
+ for (const part of parts) {
73
+ switch (part) {
74
+ case "ctrl":
75
+ info.ctrl = true
76
+ break
77
+ case "alt":
78
+ case "meta":
79
+ case "option":
80
+ info.meta = true
81
+ break
82
+ case "super":
83
+ info.super = true
84
+ break
85
+ case "shift":
86
+ info.shift = true
87
+ break
88
+ case "leader":
89
+ info.leader = true
90
+ break
91
+ case "esc":
92
+ info.name = "escape"
93
+ break
94
+ default:
95
+ info.name = part
96
+ break
97
+ }
98
+ }
99
+
100
+ return info
101
+ })
102
+ }
103
+ }
@@ -0,0 +1,23 @@
1
+ export interface LazyFunction<T> {
2
+ (): T
3
+ reset(): void
4
+ }
5
+
6
+ export function lazy<T>(fn: () => T): LazyFunction<T> {
7
+ let value: T | undefined
8
+ let loaded = false
9
+
10
+ const result = (): T => {
11
+ if (loaded) return value as T
12
+ loaded = true
13
+ value = fn()
14
+ return value as T
15
+ }
16
+
17
+ result.reset = () => {
18
+ loaded = false
19
+ value = undefined
20
+ }
21
+
22
+ return result
23
+ }
@@ -0,0 +1,81 @@
1
+ export namespace Locale {
2
+ export function titlecase(str: string) {
3
+ return str.replace(/\b\w/g, (c) => c.toUpperCase())
4
+ }
5
+
6
+ export function time(input: number): string {
7
+ const date = new Date(input)
8
+ return date.toLocaleTimeString(undefined, { timeStyle: "short" })
9
+ }
10
+
11
+ export function datetime(input: number): string {
12
+ const date = new Date(input)
13
+ const localTime = time(input)
14
+ const localDate = date.toLocaleDateString()
15
+ return `${localTime} · ${localDate}`
16
+ }
17
+
18
+ export function todayTimeOrDateTime(input: number): string {
19
+ const date = new Date(input)
20
+ const now = new Date()
21
+ const isToday =
22
+ date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()
23
+
24
+ if (isToday) {
25
+ return time(input)
26
+ } else {
27
+ return datetime(input)
28
+ }
29
+ }
30
+
31
+ export function number(num: number): string {
32
+ if (num >= 1000000) {
33
+ return (num / 1000000).toFixed(1) + "M"
34
+ } else if (num >= 1000) {
35
+ return (num / 1000).toFixed(1) + "K"
36
+ }
37
+ return num.toString()
38
+ }
39
+
40
+ export function duration(input: number) {
41
+ if (input < 1000) {
42
+ return `${input}ms`
43
+ }
44
+ if (input < 60000) {
45
+ return `${(input / 1000).toFixed(1)}s`
46
+ }
47
+ if (input < 3600000) {
48
+ const minutes = Math.floor(input / 60000)
49
+ const seconds = Math.floor((input % 60000) / 1000)
50
+ return `${minutes}m ${seconds}s`
51
+ }
52
+ if (input < 86400000) {
53
+ const hours = Math.floor(input / 3600000)
54
+ const minutes = Math.floor((input % 3600000) / 60000)
55
+ return `${hours}h ${minutes}m`
56
+ }
57
+ const hours = Math.floor(input / 3600000)
58
+ const days = Math.floor((input % 3600000) / 86400000)
59
+ return `${days}d ${hours}h`
60
+ }
61
+
62
+ export function truncate(str: string, len: number): string {
63
+ if (str.length <= len) return str
64
+ return str.slice(0, len - 1) + "…"
65
+ }
66
+
67
+ export function truncateMiddle(str: string, maxLength: number = 35): string {
68
+ if (str.length <= maxLength) return str
69
+
70
+ const ellipsis = "…"
71
+ const keepStart = Math.ceil((maxLength - ellipsis.length) / 2)
72
+ const keepEnd = Math.floor((maxLength - ellipsis.length) / 2)
73
+
74
+ return str.slice(0, keepStart) + ellipsis + str.slice(-keepEnd)
75
+ }
76
+
77
+ export function pluralize(count: number, singular: string, plural: string): string {
78
+ const template = count === 1 ? singular : plural
79
+ return template.replace("{}", count.toString())
80
+ }
81
+ }
@@ -0,0 +1,98 @@
1
+ export namespace Lock {
2
+ const locks = new Map<
3
+ string,
4
+ {
5
+ readers: number
6
+ writer: boolean
7
+ waitingReaders: (() => void)[]
8
+ waitingWriters: (() => void)[]
9
+ }
10
+ >()
11
+
12
+ function get(key: string) {
13
+ if (!locks.has(key)) {
14
+ locks.set(key, {
15
+ readers: 0,
16
+ writer: false,
17
+ waitingReaders: [],
18
+ waitingWriters: [],
19
+ })
20
+ }
21
+ return locks.get(key)!
22
+ }
23
+
24
+ function process(key: string) {
25
+ const lock = locks.get(key)
26
+ if (!lock || lock.writer || lock.readers > 0) return
27
+
28
+ // Prioritize writers to prevent starvation
29
+ if (lock.waitingWriters.length > 0) {
30
+ const nextWriter = lock.waitingWriters.shift()!
31
+ nextWriter()
32
+ return
33
+ }
34
+
35
+ // Wake up all waiting readers
36
+ while (lock.waitingReaders.length > 0) {
37
+ const nextReader = lock.waitingReaders.shift()!
38
+ nextReader()
39
+ }
40
+
41
+ // Clean up empty locks
42
+ if (lock.readers === 0 && !lock.writer && lock.waitingReaders.length === 0 && lock.waitingWriters.length === 0) {
43
+ locks.delete(key)
44
+ }
45
+ }
46
+
47
+ export async function read(key: string): Promise<Disposable> {
48
+ const lock = get(key)
49
+
50
+ return new Promise((resolve) => {
51
+ if (!lock.writer && lock.waitingWriters.length === 0) {
52
+ lock.readers++
53
+ resolve({
54
+ [Symbol.dispose]: () => {
55
+ lock.readers--
56
+ process(key)
57
+ },
58
+ })
59
+ } else {
60
+ lock.waitingReaders.push(() => {
61
+ lock.readers++
62
+ resolve({
63
+ [Symbol.dispose]: () => {
64
+ lock.readers--
65
+ process(key)
66
+ },
67
+ })
68
+ })
69
+ }
70
+ })
71
+ }
72
+
73
+ export async function write(key: string): Promise<Disposable> {
74
+ const lock = get(key)
75
+
76
+ return new Promise((resolve) => {
77
+ if (!lock.writer && lock.readers === 0) {
78
+ lock.writer = true
79
+ resolve({
80
+ [Symbol.dispose]: () => {
81
+ lock.writer = false
82
+ process(key)
83
+ },
84
+ })
85
+ } else {
86
+ lock.waitingWriters.push(() => {
87
+ lock.writer = true
88
+ resolve({
89
+ [Symbol.dispose]: () => {
90
+ lock.writer = false
91
+ process(key)
92
+ },
93
+ })
94
+ })
95
+ }
96
+ })
97
+ }
98
+ }
@@ -0,0 +1,180 @@
1
+ import path from "path"
2
+ import fs from "fs/promises"
3
+ import { Global } from "../global"
4
+ import z from "zod"
5
+
6
+ export namespace Log {
7
+ export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
8
+ export type Level = z.infer<typeof Level>
9
+
10
+ const levelPriority: Record<Level, number> = {
11
+ DEBUG: 0,
12
+ INFO: 1,
13
+ WARN: 2,
14
+ ERROR: 3,
15
+ }
16
+
17
+ let level: Level = "INFO"
18
+
19
+ function shouldLog(input: Level): boolean {
20
+ return levelPriority[input] >= levelPriority[level]
21
+ }
22
+
23
+ export type Logger = {
24
+ debug(message?: any, extra?: Record<string, any>): void
25
+ info(message?: any, extra?: Record<string, any>): void
26
+ error(message?: any, extra?: Record<string, any>): void
27
+ warn(message?: any, extra?: Record<string, any>): void
28
+ tag(key: string, value: string): Logger
29
+ clone(): Logger
30
+ time(
31
+ message: string,
32
+ extra?: Record<string, any>,
33
+ ): {
34
+ stop(): void
35
+ [Symbol.dispose](): void
36
+ }
37
+ }
38
+
39
+ const loggers = new Map<string, Logger>()
40
+
41
+ export const Default = create({ service: "default" })
42
+
43
+ export interface Options {
44
+ print: boolean
45
+ dev?: boolean
46
+ level?: Level
47
+ }
48
+
49
+ let logpath = ""
50
+ export function file() {
51
+ return logpath
52
+ }
53
+ let write = (msg: any) => {
54
+ process.stderr.write(msg)
55
+ return msg.length
56
+ }
57
+
58
+ export async function init(options: Options) {
59
+ if (options.level) level = options.level
60
+ cleanup(Global.Path.log)
61
+ if (options.print) return
62
+ logpath = path.join(
63
+ Global.Path.log,
64
+ options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
65
+ )
66
+ const logfile = Bun.file(logpath)
67
+ await fs.truncate(logpath).catch(() => {})
68
+ const writer = logfile.writer()
69
+ write = async (msg: any) => {
70
+ const num = writer.write(msg)
71
+ writer.flush()
72
+ return num
73
+ }
74
+ }
75
+
76
+ async function cleanup(dir: string) {
77
+ const glob = new Bun.Glob("????-??-??T??????.log")
78
+ const files = await Array.fromAsync(
79
+ glob.scan({
80
+ cwd: dir,
81
+ absolute: true,
82
+ }),
83
+ )
84
+ if (files.length <= 5) return
85
+
86
+ const filesToDelete = files.slice(0, -10)
87
+ await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
88
+ }
89
+
90
+ function formatError(error: Error, depth = 0): string {
91
+ const result = error.message
92
+ return error.cause instanceof Error && depth < 10
93
+ ? result + " Caused by: " + formatError(error.cause, depth + 1)
94
+ : result
95
+ }
96
+
97
+ let last = Date.now()
98
+ export function create(tags?: Record<string, any>) {
99
+ tags = tags || {}
100
+
101
+ const service = tags["service"]
102
+ if (service && typeof service === "string") {
103
+ const cached = loggers.get(service)
104
+ if (cached) {
105
+ return cached
106
+ }
107
+ }
108
+
109
+ function build(message: any, extra?: Record<string, any>) {
110
+ const prefix = Object.entries({
111
+ ...tags,
112
+ ...extra,
113
+ })
114
+ .filter(([_, value]) => value !== undefined && value !== null)
115
+ .map(([key, value]) => {
116
+ const prefix = `${key}=`
117
+ if (value instanceof Error) return prefix + formatError(value)
118
+ if (typeof value === "object") return prefix + JSON.stringify(value)
119
+ return prefix + value
120
+ })
121
+ .join(" ")
122
+ const next = new Date()
123
+ const diff = next.getTime() - last
124
+ last = next.getTime()
125
+ return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
126
+ }
127
+ const result: Logger = {
128
+ debug(message?: any, extra?: Record<string, any>) {
129
+ if (shouldLog("DEBUG")) {
130
+ write("DEBUG " + build(message, extra))
131
+ }
132
+ },
133
+ info(message?: any, extra?: Record<string, any>) {
134
+ if (shouldLog("INFO")) {
135
+ write("INFO " + build(message, extra))
136
+ }
137
+ },
138
+ error(message?: any, extra?: Record<string, any>) {
139
+ if (shouldLog("ERROR")) {
140
+ write("ERROR " + build(message, extra))
141
+ }
142
+ },
143
+ warn(message?: any, extra?: Record<string, any>) {
144
+ if (shouldLog("WARN")) {
145
+ write("WARN " + build(message, extra))
146
+ }
147
+ },
148
+ tag(key: string, value: string) {
149
+ if (tags) tags[key] = value
150
+ return result
151
+ },
152
+ clone() {
153
+ return Log.create({ ...tags })
154
+ },
155
+ time(message: string, extra?: Record<string, any>) {
156
+ const now = Date.now()
157
+ result.info(message, { status: "started", ...extra })
158
+ function stop() {
159
+ result.info(message, {
160
+ status: "completed",
161
+ duration: Date.now() - now,
162
+ ...extra,
163
+ })
164
+ }
165
+ return {
166
+ stop,
167
+ [Symbol.dispose]() {
168
+ stop()
169
+ },
170
+ }
171
+ },
172
+ }
173
+
174
+ if (service && typeof service === "string") {
175
+ loggers.set(service, result)
176
+ }
177
+
178
+ return result
179
+ }
180
+ }
@@ -0,0 +1,32 @@
1
+ export class AsyncQueue<T> implements AsyncIterable<T> {
2
+ private queue: T[] = []
3
+ private resolvers: ((value: T) => void)[] = []
4
+
5
+ push(item: T) {
6
+ const resolve = this.resolvers.shift()
7
+ if (resolve) resolve(item)
8
+ else this.queue.push(item)
9
+ }
10
+
11
+ async next(): Promise<T> {
12
+ if (this.queue.length > 0) return this.queue.shift()!
13
+ return new Promise((resolve) => this.resolvers.push(resolve))
14
+ }
15
+
16
+ async *[Symbol.asyncIterator]() {
17
+ while (true) yield await this.next()
18
+ }
19
+ }
20
+
21
+ export async function work<T>(concurrency: number, items: T[], fn: (item: T) => Promise<void>) {
22
+ const pending = [...items]
23
+ await Promise.all(
24
+ Array.from({ length: concurrency }, async () => {
25
+ while (true) {
26
+ const item = pending.pop()
27
+ if (item === undefined) return
28
+ await fn(item)
29
+ }
30
+ }),
31
+ )
32
+ }