rird 1.0.200

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