rird 2.1.231 → 2.3.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 (381) hide show
  1. package/AGENTS.md +86 -0
  2. package/COMPLETED_TEST_SUITE.txt +280 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +397 -6
  5. package/RIRD_ERROR_HANDLING_SUMMARY.md +307 -0
  6. package/TESTING.md +512 -0
  7. package/TEST_IMPLEMENTATION_REPORT.md +463 -0
  8. package/TEST_SUITE.md +307 -0
  9. package/TEST_SUMMARY.txt +380 -0
  10. package/bin/rird-perf.js +37 -0
  11. package/bin/rird.js +43 -8
  12. package/bunfig.toml +4 -0
  13. package/create-wrapper.ps1 +51 -0
  14. package/docs/ARCHITECTURE.md +768 -0
  15. package/docs/CLI_REFERENCE.md +681 -0
  16. package/docs/DOCUMENTATION_MANIFEST.md +392 -0
  17. package/docs/INDEX.md +295 -0
  18. package/docs/PRODUCTION_SETUP.md +633 -0
  19. package/docs/TROUBLESHOOTING.md +914 -0
  20. package/facebook_ads_library.png +0 -0
  21. package/nul +0 -0
  22. package/nul`nif +0 -0
  23. package/package.json +104 -15
  24. package/parsers-config.ts +239 -0
  25. package/rird-1.0.199.tgz +0 -0
  26. package/rird-1.0.205.tgz +0 -0
  27. package/script/build-windows.ts +56 -0
  28. package/script/build.ts +165 -0
  29. package/{postinstall.mjs → script/postinstall.mjs} +47 -68
  30. package/script/publish-registries.ts +187 -0
  31. package/script/publish.ts +85 -0
  32. package/script/schema.ts +47 -0
  33. package/src/acp/README.md +164 -0
  34. package/src/acp/agent.ts +1063 -0
  35. package/src/acp/session.ts +101 -0
  36. package/src/acp/types.ts +22 -0
  37. package/src/agent/agent.ts +367 -0
  38. package/src/agent/generate.txt +75 -0
  39. package/src/agent/prompt/compaction.txt +12 -0
  40. package/src/agent/prompt/explore.txt +18 -0
  41. package/src/agent/prompt/summary.txt +10 -0
  42. package/src/agent/prompt/title.txt +36 -0
  43. package/src/auth/index.ts +70 -0
  44. package/src/bun/index.ts +114 -0
  45. package/src/bus/bus-event.ts +43 -0
  46. package/src/bus/global.ts +10 -0
  47. package/src/bus/index.ts +105 -0
  48. package/src/cli/bootstrap.ts +17 -0
  49. package/src/cli/cmd/acp.ts +104 -0
  50. package/src/cli/cmd/activate.ts +50 -0
  51. package/src/cli/cmd/agent.ts +256 -0
  52. package/src/cli/cmd/auth.ts +412 -0
  53. package/src/cli/cmd/cmd.ts +7 -0
  54. package/src/cli/cmd/debug/config.ts +15 -0
  55. package/src/cli/cmd/debug/file.ts +91 -0
  56. package/src/cli/cmd/debug/index.ts +43 -0
  57. package/src/cli/cmd/debug/lsp.ts +48 -0
  58. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  59. package/src/cli/cmd/debug/scrap.ts +15 -0
  60. package/src/cli/cmd/debug/skill.ts +15 -0
  61. package/src/cli/cmd/debug/snapshot.ts +48 -0
  62. package/src/cli/cmd/export.ts +88 -0
  63. package/src/cli/cmd/generate.ts +38 -0
  64. package/src/cli/cmd/github.ts +1400 -0
  65. package/src/cli/cmd/import.ts +98 -0
  66. package/src/cli/cmd/mcp.ts +654 -0
  67. package/src/cli/cmd/models.ts +68 -0
  68. package/src/cli/cmd/pr.ts +112 -0
  69. package/src/cli/cmd/run.ts +434 -0
  70. package/src/cli/cmd/serve.ts +31 -0
  71. package/src/cli/cmd/session.ts +106 -0
  72. package/src/cli/cmd/stats.ts +298 -0
  73. package/src/cli/cmd/tui/app.tsx +694 -0
  74. package/src/cli/cmd/tui/attach.ts +30 -0
  75. package/src/cli/cmd/tui/component/border.tsx +21 -0
  76. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  77. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  78. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  79. package/src/cli/cmd/tui/component/dialog-model.tsx +236 -0
  80. package/src/cli/cmd/tui/component/dialog-provider.tsx +240 -0
  81. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  82. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  83. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  84. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  85. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  86. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  87. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  88. package/src/cli/cmd/tui/component/logo.tsx +48 -0
  89. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  90. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  91. package/src/cli/cmd/tui/component/prompt/index.tsx +1087 -0
  92. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  93. package/src/cli/cmd/tui/component/tips.ts +27 -0
  94. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  95. package/src/cli/cmd/tui/context/args.tsx +14 -0
  96. package/src/cli/cmd/tui/context/directory.ts +13 -0
  97. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  98. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  99. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  100. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  101. package/src/cli/cmd/tui/context/local.tsx +345 -0
  102. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  103. package/src/cli/cmd/tui/context/route.tsx +46 -0
  104. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  105. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  106. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  107. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  108. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  109. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  110. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  111. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  112. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  113. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  114. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  115. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  116. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  117. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  118. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  119. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  120. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  121. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  122. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  123. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  124. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  125. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  126. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  127. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  128. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  129. package/src/cli/cmd/tui/context/theme/rird.json +245 -0
  130. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  131. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  132. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  133. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  134. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  135. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  136. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  137. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  138. package/src/cli/cmd/tui/event.ts +40 -0
  139. package/src/cli/cmd/tui/hooks/use-safe-terminal-dimensions.ts +12 -0
  140. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  141. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  142. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  143. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  144. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  145. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  146. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  147. package/src/cli/cmd/tui/routes/session/index.tsx +1876 -0
  148. package/src/cli/cmd/tui/routes/session/sidebar.tsx +320 -0
  149. package/src/cli/cmd/tui/spawn.ts +60 -0
  150. package/src/cli/cmd/tui/thread.ts +142 -0
  151. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  152. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  153. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  154. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  155. package/src/cli/cmd/tui/ui/dialog-select.tsx +333 -0
  156. package/src/cli/cmd/tui/ui/dialog.tsx +171 -0
  157. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  158. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  159. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  160. package/src/cli/cmd/tui/util/editor.ts +32 -0
  161. package/src/cli/cmd/tui/util/terminal.ts +146 -0
  162. package/src/cli/cmd/tui/worker.ts +63 -0
  163. package/src/cli/cmd/uninstall.ts +344 -0
  164. package/src/cli/cmd/upgrade.ts +127 -0
  165. package/src/cli/cmd/web.ts +84 -0
  166. package/src/cli/error.ts +69 -0
  167. package/src/cli/ui.ts +101 -0
  168. package/src/cli/upgrade.ts +28 -0
  169. package/src/command/index.ts +80 -0
  170. package/src/command/template/initialize.txt +10 -0
  171. package/src/command/template/review.txt +97 -0
  172. package/src/config/config.ts +994 -0
  173. package/src/config/markdown.ts +41 -0
  174. package/src/env/index.ts +26 -0
  175. package/src/file/ignore.ts +83 -0
  176. package/src/file/index.ts +328 -0
  177. package/src/file/ripgrep.ts +393 -0
  178. package/src/file/time.ts +64 -0
  179. package/src/file/watcher.ts +103 -0
  180. package/src/flag/flag.ts +84 -0
  181. package/src/format/formatter.ts +315 -0
  182. package/src/format/index.ts +137 -0
  183. package/src/global/index.ts +101 -0
  184. package/src/id/id.ts +73 -0
  185. package/src/ide/index.ts +76 -0
  186. package/src/index.ts +297 -0
  187. package/src/index.ts.backup +271 -0
  188. package/src/installation/index.ts +258 -0
  189. package/src/lib/IMPLEMENTATION_NOTES.md +345 -0
  190. package/src/lib/error-handler.ts +225 -0
  191. package/src/lib/error-testing-guide.md +258 -0
  192. package/src/lib/errors.ts +285 -0
  193. package/src/lib/performance.ts +70 -0
  194. package/src/lib/telemetry.ts +282 -0
  195. package/src/lsp/client.ts +229 -0
  196. package/src/lsp/index.ts +485 -0
  197. package/src/lsp/language.ts +116 -0
  198. package/src/lsp/server.ts +1895 -0
  199. package/src/mcp/auth.ts +135 -0
  200. package/src/mcp/index.ts +1117 -0
  201. package/src/mcp/intent-analyzer.ts +376 -0
  202. package/src/mcp/oauth-callback.ts +200 -0
  203. package/src/mcp/oauth-provider.ts +154 -0
  204. package/src/patch/index.ts +632 -0
  205. package/src/permission/index.ts +199 -0
  206. package/src/plugin/index.ts +91 -0
  207. package/src/project/bootstrap.ts +33 -0
  208. package/src/project/instance.ts +78 -0
  209. package/src/project/project.ts +236 -0
  210. package/src/project/state.ts +65 -0
  211. package/src/project/vcs.ts +76 -0
  212. package/src/provider/auth.ts +143 -0
  213. package/src/provider/models-macro.ts +55 -0
  214. package/src/provider/models.ts +161 -0
  215. package/src/provider/provider.ts +1109 -0
  216. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  217. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  218. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  219. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  220. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  221. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  222. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  223. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  224. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  225. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  226. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  227. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  228. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  229. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  230. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  231. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  232. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  233. package/src/provider/transform.ts +455 -0
  234. package/src/pty/index.ts +231 -0
  235. package/src/security/guardrails.test.ts +341 -0
  236. package/src/security/guardrails.ts +570 -0
  237. package/src/security/index.ts +19 -0
  238. package/src/server/error.ts +36 -0
  239. package/src/server/project.ts +79 -0
  240. package/src/server/server.ts +2641 -0
  241. package/src/server/tui.ts +71 -0
  242. package/src/session/compaction.ts +228 -0
  243. package/src/session/index.ts +464 -0
  244. package/src/session/llm.ts +201 -0
  245. package/src/session/message-v2.ts +695 -0
  246. package/src/session/message.ts +189 -0
  247. package/src/session/processor.ts +409 -0
  248. package/src/session/prompt/act-switch.txt +5 -0
  249. package/src/session/prompt/anthropic-20250930.txt +166 -0
  250. package/src/session/prompt/anthropic.txt +63 -0
  251. package/src/session/prompt/anthropic_spoof.txt +1 -0
  252. package/src/session/prompt/beast.txt +76 -0
  253. package/src/session/prompt/codex.txt +304 -0
  254. package/src/session/prompt/copilot-gpt-5.txt +137 -0
  255. package/src/session/prompt/gemini.txt +62 -0
  256. package/src/session/prompt/max-steps.txt +16 -0
  257. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  258. package/src/session/prompt/plan.txt +24 -0
  259. package/src/session/prompt/polaris.txt +88 -0
  260. package/src/session/prompt/qwen.txt +59 -0
  261. package/src/session/prompt.ts +1552 -0
  262. package/src/session/retry.ts +86 -0
  263. package/src/session/revert.ts +108 -0
  264. package/src/session/sensitive-filter.test.ts +327 -0
  265. package/src/session/sensitive-filter.ts +466 -0
  266. package/src/session/status.ts +76 -0
  267. package/src/session/summary.ts +209 -0
  268. package/src/session/system.ts +122 -0
  269. package/src/session/todo.ts +37 -0
  270. package/src/share/share-next.ts +222 -0
  271. package/src/share/share.ts +87 -0
  272. package/src/shell/shell.ts +67 -0
  273. package/src/skill/index.ts +1 -0
  274. package/src/skill/skill.ts +83 -0
  275. package/src/snapshot/index.ts +197 -0
  276. package/src/storage/storage.ts +226 -0
  277. package/src/tests/agent.test.ts +308 -0
  278. package/src/tests/build-guards.test.ts +267 -0
  279. package/src/tests/config.test.ts +664 -0
  280. package/src/tests/tool-registry.test.ts +589 -0
  281. package/src/tool/bash.ts +314 -0
  282. package/src/tool/bash.txt +158 -0
  283. package/src/tool/batch.ts +175 -0
  284. package/src/tool/batch.txt +24 -0
  285. package/src/tool/codesearch.ts +184 -0
  286. package/src/tool/codesearch.txt +12 -0
  287. package/src/tool/edit.ts +675 -0
  288. package/src/tool/edit.txt +10 -0
  289. package/src/tool/glob.ts +65 -0
  290. package/src/tool/glob.txt +6 -0
  291. package/src/tool/grep.ts +121 -0
  292. package/src/tool/grep.txt +8 -0
  293. package/src/tool/invalid.ts +17 -0
  294. package/src/tool/ls.ts +110 -0
  295. package/src/tool/ls.txt +1 -0
  296. package/src/tool/lsp-diagnostics.ts +26 -0
  297. package/src/tool/lsp-diagnostics.txt +1 -0
  298. package/src/tool/lsp-hover.ts +31 -0
  299. package/src/tool/lsp-hover.txt +1 -0
  300. package/src/tool/lsp.ts +87 -0
  301. package/src/tool/lsp.txt +19 -0
  302. package/src/tool/multiedit.ts +46 -0
  303. package/src/tool/multiedit.txt +41 -0
  304. package/src/tool/patch.ts +233 -0
  305. package/src/tool/patch.txt +1 -0
  306. package/src/tool/read.ts +219 -0
  307. package/src/tool/read.txt +12 -0
  308. package/src/tool/registry.ts +162 -0
  309. package/src/tool/skill.ts +100 -0
  310. package/src/tool/task.ts +136 -0
  311. package/src/tool/task.txt +51 -0
  312. package/src/tool/todo.ts +39 -0
  313. package/src/tool/todoread.txt +14 -0
  314. package/src/tool/todowrite.txt +167 -0
  315. package/src/tool/tool.ts +71 -0
  316. package/src/tool/webfetch.ts +198 -0
  317. package/src/tool/webfetch.txt +13 -0
  318. package/src/tool/websearch.ts +268 -0
  319. package/src/tool/websearch.txt +13 -0
  320. package/src/tool/write.ts +110 -0
  321. package/src/tool/write.txt +8 -0
  322. package/src/util/archive.ts +16 -0
  323. package/src/util/color.ts +19 -0
  324. package/src/util/context.ts +25 -0
  325. package/src/util/defer.ts +12 -0
  326. package/src/util/eventloop.ts +20 -0
  327. package/src/util/filesystem.ts +83 -0
  328. package/src/util/fn.ts +11 -0
  329. package/src/util/iife.ts +3 -0
  330. package/src/util/keybind.ts +102 -0
  331. package/src/util/lazy.ts +11 -0
  332. package/src/util/license.ts +362 -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 +42 -0
  338. package/src/util/scrap.ts +10 -0
  339. package/src/util/signal.ts +12 -0
  340. package/src/util/timeout.ts +14 -0
  341. package/src/util/token.ts +7 -0
  342. package/src/util/wildcard.ts +54 -0
  343. package/sst-env.d.ts +9 -0
  344. package/test/agent/agent.test.ts +146 -0
  345. package/test/bun.test.ts +53 -0
  346. package/test/cli/cmd/acp.test.ts +144 -0
  347. package/test/cli/cmd/run.test.ts +250 -0
  348. package/test/cli/github-remote.test.ts +80 -0
  349. package/test/config/agent-color.test.ts +66 -0
  350. package/test/config/config.test.ts +536 -0
  351. package/test/config/markdown.test.ts +89 -0
  352. package/test/file/ignore.test.ts +10 -0
  353. package/test/fixture/fixture.ts +37 -0
  354. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  355. package/test/helpers.ts +172 -0
  356. package/test/ide/ide.test.ts +82 -0
  357. package/test/installation/installation.test.ts +143 -0
  358. package/test/keybind.test.ts +421 -0
  359. package/test/lsp/client.test.ts +95 -0
  360. package/test/mcp/headers.test.ts +153 -0
  361. package/test/patch/patch.test.ts +348 -0
  362. package/test/preload.ts +57 -0
  363. package/test/project/project.test.ts +74 -0
  364. package/test/provider/provider.test.ts +74 -0
  365. package/test/provider/transform.test.ts +411 -0
  366. package/test/session/retry.test.ts +111 -0
  367. package/test/session/session.test.ts +71 -0
  368. package/test/skill/skill.test.ts +131 -0
  369. package/test/snapshot/snapshot.test.ts +940 -0
  370. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  371. package/test/tool/bash.test.ts +434 -0
  372. package/test/tool/grep.test.ts +108 -0
  373. package/test/tool/patch.test.ts +259 -0
  374. package/test/tool/read.test.ts +42 -0
  375. package/test/util/iife.test.ts +36 -0
  376. package/test/util/lazy.test.ts +50 -0
  377. package/test/util/license.test.ts +235 -0
  378. package/test/util/timeout.test.ts +21 -0
  379. package/test/util/wildcard.test.ts +55 -0
  380. package/tsconfig.json +16 -0
  381. package/update-versions.ps1 +65 -0
@@ -0,0 +1,20 @@
1
+ import { Log } from "./log"
2
+
3
+ export namespace EventLoop {
4
+ export async function wait() {
5
+ return new Promise<void>((resolve) => {
6
+ const check = () => {
7
+ const active = [...(process as any)._getActiveHandles(), ...(process as any)._getActiveRequests()]
8
+ Log.Default.info("eventloop", {
9
+ active,
10
+ })
11
+ if ((process as any)._getActiveHandles().length === 0 && (process as any)._getActiveRequests().length === 0) {
12
+ resolve()
13
+ } else {
14
+ setImmediate(check)
15
+ }
16
+ }
17
+ check()
18
+ })
19
+ }
20
+ }
@@ -0,0 +1,83 @@
1
+ import { realpathSync } from "fs"
2
+ import { exists } from "fs/promises"
3
+ import { dirname, join, relative } from "path"
4
+
5
+ export namespace Filesystem {
6
+ /**
7
+ * On Windows, normalize a path to its canonical casing using the filesystem.
8
+ * This is needed because Windows paths are case-insensitive but LSP servers
9
+ * may return paths with different casing than what we send them.
10
+ */
11
+ export function normalizePath(p: string): string {
12
+ if (process.platform !== "win32") return p
13
+ try {
14
+ return realpathSync.native(p)
15
+ } catch {
16
+ return p
17
+ }
18
+ }
19
+ export function overlaps(a: string, b: string) {
20
+ const relA = relative(a, b)
21
+ const relB = relative(b, a)
22
+ return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
23
+ }
24
+
25
+ export function contains(parent: string, child: string) {
26
+ return !relative(parent, child).startsWith("..")
27
+ }
28
+
29
+ export async function findUp(target: string, start: string, stop?: string) {
30
+ let current = start
31
+ const result = []
32
+ while (true) {
33
+ const search = join(current, target)
34
+ if (await exists(search)) result.push(search)
35
+ if (stop === current) break
36
+ const parent = dirname(current)
37
+ if (parent === current) break
38
+ current = parent
39
+ }
40
+ return result
41
+ }
42
+
43
+ export async function* up(options: { targets: string[]; start: string; stop?: string }) {
44
+ const { targets, start, stop } = options
45
+ let current = start
46
+ while (true) {
47
+ for (const target of targets) {
48
+ const search = join(current, target)
49
+ if (await exists(search)) yield search
50
+ }
51
+ if (stop === current) break
52
+ const parent = dirname(current)
53
+ if (parent === current) break
54
+ current = parent
55
+ }
56
+ }
57
+
58
+ export async function globUp(pattern: string, start: string, stop?: string) {
59
+ let current = start
60
+ const result = []
61
+ while (true) {
62
+ try {
63
+ const glob = new Bun.Glob(pattern)
64
+ for await (const match of glob.scan({
65
+ cwd: current,
66
+ absolute: true,
67
+ onlyFiles: true,
68
+ followSymlinks: true,
69
+ dot: true,
70
+ })) {
71
+ result.push(match)
72
+ }
73
+ } catch {
74
+ // Skip invalid glob patterns
75
+ }
76
+ if (stop === current) break
77
+ const parent = dirname(current)
78
+ if (parent === current) break
79
+ current = parent
80
+ }
81
+ return result
82
+ }
83
+ }
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,3 @@
1
+ export function iife<T>(fn: () => T) {
2
+ return fn()
3
+ }
@@ -0,0 +1,102 @@
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, b: Info): boolean {
14
+ // Normalize super field (undefined and false are equivalent)
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): string {
36
+ const parts: string[] = []
37
+
38
+ if (info.ctrl) parts.push("ctrl")
39
+ if (info.meta) parts.push("alt")
40
+ if (info.super) parts.push("super")
41
+ if (info.shift) parts.push("shift")
42
+ if (info.name) {
43
+ if (info.name === "delete") parts.push("del")
44
+ else parts.push(info.name)
45
+ }
46
+
47
+ let result = parts.join("+")
48
+
49
+ if (info.leader) {
50
+ result = result ? `<leader> ${result}` : `<leader>`
51
+ }
52
+
53
+ return result
54
+ }
55
+
56
+ export function parse(key: string): Info[] {
57
+ if (key === "none") return []
58
+
59
+ return key.split(",").map((combo) => {
60
+ // Handle <leader> syntax by replacing with leader+
61
+ const normalized = combo.replace(/<leader>/g, "leader+")
62
+ const parts = normalized.toLowerCase().split("+")
63
+ const info: Info = {
64
+ ctrl: false,
65
+ meta: false,
66
+ shift: false,
67
+ leader: false,
68
+ name: "",
69
+ }
70
+
71
+ for (const part of parts) {
72
+ switch (part) {
73
+ case "ctrl":
74
+ info.ctrl = true
75
+ break
76
+ case "alt":
77
+ case "meta":
78
+ case "option":
79
+ info.meta = true
80
+ break
81
+ case "super":
82
+ info.super = true
83
+ break
84
+ case "shift":
85
+ info.shift = true
86
+ break
87
+ case "leader":
88
+ info.leader = true
89
+ break
90
+ case "esc":
91
+ info.name = "escape"
92
+ break
93
+ default:
94
+ info.name = part
95
+ break
96
+ }
97
+ }
98
+
99
+ return info
100
+ })
101
+ }
102
+ }
@@ -0,0 +1,11 @@
1
+ export function lazy<T>(fn: () => T) {
2
+ let value: T | undefined
3
+ let loaded = false
4
+
5
+ return (): T => {
6
+ if (loaded) return value as T
7
+ loaded = true
8
+ value = fn()
9
+ return value as T
10
+ }
11
+ }
@@ -0,0 +1,362 @@
1
+ import path from "path"
2
+ import os from "os"
3
+ import fs from "fs"
4
+ import crypto from "crypto"
5
+ import { execSync } from "child_process"
6
+ import { Log } from "./log"
7
+
8
+ const LICENSE_CACHE_PATH = path.join(os.homedir(), ".rird", "license_cache.json")
9
+ const LICENSE_KEY_PATH = path.join(os.homedir(), ".rird", "license.key")
10
+ const RIRD_API_URL = "https://rird.ai/api/desktop/validate-license"
11
+ const CACHE_VALIDITY_HOURS = 24
12
+
13
+ export interface LicenseCheckResult {
14
+ valid: boolean
15
+ message: string
16
+ user?: any
17
+ }
18
+
19
+ // Store validated license key for provider use
20
+ let _validatedLicenseKey: string | null = null
21
+
22
+ /**
23
+ * Get the validated license key for use as API key.
24
+ * This is used by the "rird" provider to authenticate LLM calls.
25
+ */
26
+ export function getValidatedLicenseKey(): string | null {
27
+ return _validatedLicenseKey
28
+ }
29
+
30
+
31
+
32
+ /**
33
+ * Get primary MAC address from non-internal network interfaces.
34
+ * Returns the first non-loopback, non-internal MAC address found.
35
+ */
36
+ function getPrimaryMacAddress(): string {
37
+ try {
38
+ const interfaces = os.networkInterfaces()
39
+ for (const [name, addrs] of Object.entries(interfaces)) {
40
+ if (!addrs) continue
41
+ // Skip virtual interfaces that are commonly spoofed
42
+ if (name.toLowerCase().includes("virtual") || name.toLowerCase().includes("veth")) continue
43
+ for (const addr of addrs) {
44
+ // Get non-internal interface with valid MAC
45
+ if (!addr.internal && addr.mac && addr.mac !== "00:00:00:00:00:00") {
46
+ return addr.mac
47
+ }
48
+ }
49
+ }
50
+ } catch {
51
+ // Fallback silently
52
+ }
53
+ return ""
54
+ }
55
+
56
+ /**
57
+ * Get machine ID from system-specific locations.
58
+ * This is a persistent identifier that survives reboots but not OS reinstalls.
59
+ */
60
+ function getMachineId(): string {
61
+ const platform = os.platform()
62
+
63
+ try {
64
+ if (platform === "linux") {
65
+ // Primary: /etc/machine-id (systemd standard)
66
+ if (fs.existsSync("/etc/machine-id")) {
67
+ return fs.readFileSync("/etc/machine-id", "utf-8").trim()
68
+ }
69
+ // Fallback: /var/lib/dbus/machine-id
70
+ if (fs.existsSync("/var/lib/dbus/machine-id")) {
71
+ return fs.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim()
72
+ }
73
+ } else if (platform === "darwin") {
74
+ // macOS: IOPlatformUUID from ioreg
75
+ const output = execSync("ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID", {
76
+ encoding: "utf-8",
77
+ timeout: 5000,
78
+ })
79
+ const match = output.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/)
80
+ if (match) {
81
+ return match[1]
82
+ }
83
+ } else if (platform === "win32") {
84
+ // Windows: MachineGuid from registry
85
+ const output = execSync(
86
+ 'reg query "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid',
87
+ { encoding: "utf-8", timeout: 5000 }
88
+ )
89
+ const match = output.match(/MachineGuid\s+REG_SZ\s+([^\s\r\n]+)/)
90
+ if (match) {
91
+ return match[1]
92
+ }
93
+ }
94
+ } catch {
95
+ // Fallback silently - machine ID is optional but strengthens fingerprint
96
+ }
97
+ return ""
98
+ }
99
+
100
+ /**
101
+ * Get CPU model string for hardware binding.
102
+ */
103
+ function getCpuModel(): string {
104
+ try {
105
+ const cpus = os.cpus()
106
+ if (cpus.length > 0) {
107
+ return cpus[0].model
108
+ }
109
+ } catch {
110
+ // Fallback silently
111
+ }
112
+ return ""
113
+ }
114
+
115
+ /**
116
+ * Get disk serial number (primary disk) for hardware binding.
117
+ * This is one of the hardest identifiers to spoof.
118
+ */
119
+ function getDiskSerial(): string {
120
+ const platform = os.platform()
121
+
122
+ try {
123
+ if (platform === "linux") {
124
+ // Try to get the serial of the root disk
125
+ // First, find the root device
126
+ const rootDev = execSync("df / | tail -1 | awk '{print $1}'", {
127
+ encoding: "utf-8",
128
+ timeout: 5000,
129
+ }).trim()
130
+ // Extract base device name (e.g., /dev/sda1 -> sda)
131
+ const devName = rootDev.replace(/\/dev\//, "").replace(/[0-9]+$/, "")
132
+ // Get serial from udevadm
133
+ if (devName) {
134
+ const serial = execSync(`udevadm info --query=property --name=/dev/${devName} | grep ID_SERIAL_SHORT`, {
135
+ encoding: "utf-8",
136
+ timeout: 5000,
137
+ })
138
+ const match = serial.match(/ID_SERIAL_SHORT=(.+)/)
139
+ if (match) {
140
+ return match[1].trim()
141
+ }
142
+ }
143
+ } else if (platform === "darwin") {
144
+ // macOS: Use diskutil to get disk serial
145
+ const output = execSync("diskutil info disk0 | grep 'Device Identifier\\|Serial'", {
146
+ encoding: "utf-8",
147
+ timeout: 5000,
148
+ })
149
+ const match = output.match(/Serial Number:\s+(\S+)/i)
150
+ if (match) {
151
+ return match[1]
152
+ }
153
+ } else if (platform === "win32") {
154
+ // Windows: Use WMIC to get disk serial
155
+ const output = execSync("wmic diskdrive get SerialNumber", {
156
+ encoding: "utf-8",
157
+ timeout: 5000,
158
+ })
159
+ const lines = output.split("\n").filter((l) => l.trim() && !l.includes("SerialNumber"))
160
+ if (lines.length > 0) {
161
+ return lines[0].trim()
162
+ }
163
+ }
164
+ } catch {
165
+ // Disk serial is optional - many VMs don't have it
166
+ }
167
+ return ""
168
+ }
169
+
170
+ /**
171
+ * Get a hardware-bound device fingerprint that is difficult to spoof.
172
+ * Combines multiple hardware identifiers for robust device identification.
173
+ *
174
+ * Components (in order of uniqueness/difficulty to spoof):
175
+ * 1. Machine ID - System-generated UUID, persistent across reboots
176
+ * 2. Primary MAC address - Network interface hardware address
177
+ * 3. Disk serial - Hard drive serial number (hardest to spoof)
178
+ * 4. CPU model - Processor identification string
179
+ * 5. Total memory - System RAM in bytes
180
+ * 6. CPU core count - Number of logical processors
181
+ * 7. Platform + arch - OS and architecture (least unique, but stable)
182
+ *
183
+ * The fingerprint is a SHA-256 hash of all components, making it:
184
+ * - Deterministic: Same hardware always produces same fingerprint
185
+ * - Secure: Cannot reverse-engineer the original values
186
+ * - Tamper-resistant: Changing any component changes the fingerprint
187
+ */
188
+ function getDeviceFingerprint(): string {
189
+ const parts: string[] = []
190
+
191
+ // Hardware identifiers (hard to spoof)
192
+ const machineId = getMachineId()
193
+ const macAddress = getPrimaryMacAddress()
194
+ const diskSerial = getDiskSerial()
195
+ const cpuModel = getCpuModel()
196
+
197
+ // System characteristics
198
+ const totalMemory = os.totalmem().toString()
199
+ const cpuCount = os.cpus().length.toString()
200
+ const platform = os.platform()
201
+ const arch = os.arch()
202
+
203
+ // Build fingerprint from all available components
204
+ // Order matters - keep it consistent
205
+ parts.push(`machine:${machineId}`)
206
+ parts.push(`mac:${macAddress}`)
207
+ parts.push(`disk:${diskSerial}`)
208
+ parts.push(`cpu:${cpuModel}`)
209
+ parts.push(`mem:${totalMemory}`)
210
+ parts.push(`cores:${cpuCount}`)
211
+ parts.push(`platform:${platform}`)
212
+ parts.push(`arch:${arch}`)
213
+
214
+ // Create SHA-256 hash of combined components
215
+ const combined = parts.join("|")
216
+ const hash = crypto.createHash("sha256").update(combined).digest("hex")
217
+
218
+ // Return first 32 chars for reasonable length while maintaining security
219
+ return hash.substring(0, 32)
220
+ }
221
+
222
+ function getLicenseKey(): string | null {
223
+ if (process.env.RIRD_LICENSE_KEY) {
224
+ return process.env.RIRD_LICENSE_KEY.trim()
225
+ }
226
+ if (fs.existsSync(LICENSE_KEY_PATH)) {
227
+ try {
228
+ return fs.readFileSync(LICENSE_KEY_PATH, "utf-8").trim()
229
+ } catch {
230
+ return null
231
+ }
232
+ }
233
+ return null
234
+ }
235
+
236
+ function loadLicenseCache(): any | null {
237
+ try {
238
+ if (fs.existsSync(LICENSE_CACHE_PATH)) {
239
+ const data = JSON.parse(fs.readFileSync(LICENSE_CACHE_PATH, "utf-8"))
240
+ const cachedAt = new Date(data.cached_at)
241
+ const now = new Date()
242
+ const hoursDiff = (now.getTime() - cachedAt.getTime()) / (1000 * 60 * 60)
243
+ if (hoursDiff < CACHE_VALIDITY_HOURS) {
244
+ return data
245
+ }
246
+ }
247
+ } catch (e) {
248
+ // Ignore cache errors
249
+ }
250
+ return null
251
+ }
252
+
253
+ function saveLicenseCache(data: any) {
254
+ try {
255
+ const dir = path.dirname(LICENSE_CACHE_PATH)
256
+ if (!fs.existsSync(dir)) {
257
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
258
+ }
259
+ data.cached_at = new Date().toISOString()
260
+ fs.writeFileSync(LICENSE_CACHE_PATH, JSON.stringify(data), { mode: 0o600 })
261
+ } catch (e) {
262
+ Log.Default.error("license", { error: "Failed to save license cache", details: e })
263
+ }
264
+ }
265
+
266
+ export function saveLicenseKey(key: string): void {
267
+ const dir = path.dirname(LICENSE_KEY_PATH)
268
+ if (!fs.existsSync(dir)) {
269
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
270
+ }
271
+ fs.writeFileSync(LICENSE_KEY_PATH, key.trim(), { mode: 0o600 })
272
+ }
273
+
274
+ export function clearLicenseCache(): void {
275
+ if (fs.existsSync(LICENSE_CACHE_PATH)) {
276
+ fs.unlinkSync(LICENSE_CACHE_PATH)
277
+ }
278
+ }
279
+
280
+ export async function validateLicense(): Promise<LicenseCheckResult> {
281
+ const licenseKey = getLicenseKey()
282
+ if (!licenseKey) {
283
+ return {
284
+ valid: false,
285
+ message: "No license key found.\nGet your key at: https://rird.ai\nThen run: rird activate YOUR_LICENSE_KEY",
286
+ }
287
+ }
288
+
289
+ // Check cache first
290
+ const cached = loadLicenseCache()
291
+ if (cached && cached.license_key === licenseKey && cached.valid) {
292
+ Log.Default.debug("Using cached license validation")
293
+ // Store license key for provider use
294
+ _validatedLicenseKey = licenseKey
295
+ // Set environment variable for the internal RIRD LLM endpoint
296
+ process.env.RIRD_API_KEY = licenseKey
297
+ return {
298
+ valid: true,
299
+ message: `License valid (cached) - ${cached.email || "unknown"}`,
300
+ user: { email: cached.email, plan: cached.plan },
301
+ }
302
+ }
303
+
304
+ // Validate online
305
+ try {
306
+ const response = await fetch(RIRD_API_URL, {
307
+ method: "POST",
308
+ headers: { "Content-Type": "application/json" },
309
+ body: JSON.stringify({
310
+ license_key: licenseKey,
311
+ device_fingerprint: getDeviceFingerprint(),
312
+ version: "1.0.0", // TODO: Get from package.json
313
+ platform: os.platform(),
314
+ }),
315
+ })
316
+
317
+ if (response.status === 200) {
318
+ const data = await response.json()
319
+ if (data.valid) {
320
+ // Store license key for provider use
321
+ _validatedLicenseKey = licenseKey
322
+ // Set environment variable for the internal RIRD LLM endpoint
323
+ process.env.RIRD_API_KEY = licenseKey
324
+
325
+ saveLicenseCache({
326
+ license_key: licenseKey,
327
+ valid: true,
328
+ email: data.user?.email,
329
+ plan: data.user?.plan,
330
+ expires_at: data.user?.expires_at,
331
+ providers: undefined,
332
+ })
333
+ return {
334
+ valid: true,
335
+ message: `License valid - ${data.user?.email || "unknown"}`,
336
+ user: data.user,
337
+ }
338
+ } else {
339
+ // Clear bad cache
340
+ if (fs.existsSync(LICENSE_CACHE_PATH)) fs.unlinkSync(LICENSE_CACHE_PATH)
341
+ return { valid: false, message: data.error || "Invalid license" }
342
+ }
343
+ } else if (response.status === 401) {
344
+ return { valid: false, message: "Invalid or expired license key" }
345
+ } else if (response.status === 403) {
346
+ return { valid: false, message: "Subscription inactive - please renew at rird.ai" }
347
+ } else if (response.status === 429) {
348
+ return { valid: false, message: "Too many validation attempts - please wait" }
349
+ } else {
350
+ // Offline/Server error - allow if previously cached (but here we don't have valid cache)
351
+ // If we are here, cache was invalid or missing.
352
+ // Strict mode: fail. Lenient mode: warn.
353
+ // User requested "rate limits connected to auth", implying enforcement.
354
+ return { valid: false, message: `License server error: ${response.status}` }
355
+ }
356
+ } catch (e) {
357
+ // Network error
358
+ Log.Default.warn("License check offline")
359
+ return { valid: false, message: "Could not reach license server" } // Fail safe or fail secure?
360
+ // Given the requirement, probably fail secure unless cached.
361
+ }
362
+ }
@@ -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
+ }