@neuralnomads/codenomad-dev 0.10.3-dev-20260213-ba418a85

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 (462) hide show
  1. package/README.md +126 -0
  2. package/dist/api-types.js +1 -0
  3. package/dist/auth/auth-store.js +134 -0
  4. package/dist/auth/http-auth.js +37 -0
  5. package/dist/auth/manager.js +128 -0
  6. package/dist/auth/password-hash.js +32 -0
  7. package/dist/auth/session-manager.js +17 -0
  8. package/dist/auth/token-manager.js +27 -0
  9. package/dist/background-processes/manager.js +437 -0
  10. package/dist/bin.js +24 -0
  11. package/dist/config/binaries.js +148 -0
  12. package/dist/config/location.js +57 -0
  13. package/dist/config/schema.js +70 -0
  14. package/dist/config/store.js +200 -0
  15. package/dist/events/bus.js +41 -0
  16. package/dist/filesystem/__tests__/search-cache.test.js +40 -0
  17. package/dist/filesystem/browser.js +285 -0
  18. package/dist/filesystem/search-cache.js +43 -0
  19. package/dist/filesystem/search.js +135 -0
  20. package/dist/index.js +410 -0
  21. package/dist/integrations/github/bot-signature.js +11 -0
  22. package/dist/integrations/github/git-ops.js +133 -0
  23. package/dist/integrations/github/github-types.js +1 -0
  24. package/dist/integrations/github/job-runner.js +608 -0
  25. package/dist/integrations/github/octokit.js +58 -0
  26. package/dist/integrations/github/sanitize-webhook.js +42 -0
  27. package/dist/integrations/github/webhook-verify.js +21 -0
  28. package/dist/integrations/github/workspace-context.js +10 -0
  29. package/dist/integrations/github/worktree-context.js +15 -0
  30. package/dist/launcher.js +149 -0
  31. package/dist/loader.js +21 -0
  32. package/dist/logger.js +109 -0
  33. package/dist/opencode/request-context.js +39 -0
  34. package/dist/opencode/worktree-directory.js +42 -0
  35. package/dist/opencode-config/README.md +32 -0
  36. package/dist/opencode-config/opencode.jsonc +3 -0
  37. package/dist/opencode-config/package.json +9 -0
  38. package/dist/opencode-config/plugin/codenomad.ts +32 -0
  39. package/dist/opencode-config/plugin/lib/background-process.ts +253 -0
  40. package/dist/opencode-config/plugin/lib/client.ts +133 -0
  41. package/dist/opencode-config/plugin/lib/request.ts +214 -0
  42. package/dist/opencode-config-template/README.md +32 -0
  43. package/dist/opencode-config-template/opencode.jsonc +3 -0
  44. package/dist/opencode-config-template/plugin/codenomad.ts +40 -0
  45. package/dist/opencode-config-template/plugin/lib/background-process.ts +160 -0
  46. package/dist/opencode-config-template/plugin/lib/client.ts +165 -0
  47. package/dist/opencode-config.js +26 -0
  48. package/dist/plugins/channel.js +40 -0
  49. package/dist/plugins/handlers.js +17 -0
  50. package/dist/releases/dev-release-monitor.js +75 -0
  51. package/dist/releases/release-monitor.js +107 -0
  52. package/dist/server/http-server.js +547 -0
  53. package/dist/server/network-addresses.js +72 -0
  54. package/dist/server/routes/auth-pages/login.html +134 -0
  55. package/dist/server/routes/auth-pages/token.html +93 -0
  56. package/dist/server/routes/auth.js +134 -0
  57. package/dist/server/routes/background-processes.js +60 -0
  58. package/dist/server/routes/config.js +59 -0
  59. package/dist/server/routes/events.js +43 -0
  60. package/dist/server/routes/filesystem.js +43 -0
  61. package/dist/server/routes/github-plugin.js +215 -0
  62. package/dist/server/routes/github-webhook.js +32 -0
  63. package/dist/server/routes/meta.js +47 -0
  64. package/dist/server/routes/plugin.js +52 -0
  65. package/dist/server/routes/storage.js +52 -0
  66. package/dist/server/routes/workspaces.js +89 -0
  67. package/dist/server/routes/worktrees.js +156 -0
  68. package/dist/server/tls.js +224 -0
  69. package/dist/storage/instance-store.js +56 -0
  70. package/dist/ui/__tests__/remote-ui.test.js +46 -0
  71. package/dist/ui/remote-ui.js +462 -0
  72. package/dist/workspaces/git-worktrees.js +199 -0
  73. package/dist/workspaces/instance-events.js +180 -0
  74. package/dist/workspaces/manager.js +375 -0
  75. package/dist/workspaces/opencode-auth.js +16 -0
  76. package/dist/workspaces/runtime.js +346 -0
  77. package/dist/workspaces/worktree-map.js +116 -0
  78. package/package.json +49 -0
  79. package/public/apple-touch-icon-180x180.png +0 -0
  80. package/public/assets/CodeNomad-Icon-bmTWNPXy.png +0 -0
  81. package/public/assets/abap-BdImnpbu.js +1 -0
  82. package/public/assets/actionscript-3-CfeIJUat.js +1 -0
  83. package/public/assets/ada-bCR0ucgS.js +1 -0
  84. package/public/assets/andromeeda-C-Jbm3Hp.js +1 -0
  85. package/public/assets/angular-html-CU67Zn6k.js +1 -0
  86. package/public/assets/angular-ts-BwZT4LLn.js +1 -0
  87. package/public/assets/apache-Pmp26Uib.js +1 -0
  88. package/public/assets/apex-DhZLUxFE.js +1 -0
  89. package/public/assets/apl-dKokRX4l.js +1 -0
  90. package/public/assets/applescript-Co6uUVPk.js +1 -0
  91. package/public/assets/ara-BRHolxvo.js +1 -0
  92. package/public/assets/asciidoc-Dv7Oe6Be.js +1 -0
  93. package/public/assets/asm-D_Q5rh1f.js +1 -0
  94. package/public/assets/astro-CbQHKStN.js +1 -0
  95. package/public/assets/aurora-x-D-2ljcwZ.js +1 -0
  96. package/public/assets/awk-DMzUqQB5.js +1 -0
  97. package/public/assets/ayu-dark-Cv9koXgw.js +1 -0
  98. package/public/assets/ballerina-BFfxhgS-.js +1 -0
  99. package/public/assets/bat-BkioyH1T.js +1 -0
  100. package/public/assets/beancount-k_qm7-4y.js +1 -0
  101. package/public/assets/berry-D08WgyRC.js +1 -0
  102. package/public/assets/bibtex-CHM0blh-.js +1 -0
  103. package/public/assets/bicep-Bmn6On1c.js +1 -0
  104. package/public/assets/blade-DVc8C-J4.js +1 -0
  105. package/public/assets/bsl-BO_Y6i37.js +1 -0
  106. package/public/assets/c-BIGW1oBm.js +1 -0
  107. package/public/assets/cadence-Bv_4Rxtq.js +1 -0
  108. package/public/assets/cairo-KRGpt6FW.js +1 -0
  109. package/public/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  110. package/public/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  111. package/public/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  112. package/public/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  113. package/public/assets/clarity-D53aC0YG.js +1 -0
  114. package/public/assets/clojure-P80f7IUj.js +1 -0
  115. package/public/assets/cmake-D1j8_8rp.js +1 -0
  116. package/public/assets/cobol-nwyudZeR.js +1 -0
  117. package/public/assets/codeowners-Bp6g37R7.js +1 -0
  118. package/public/assets/codeql-DsOJ9woJ.js +1 -0
  119. package/public/assets/coffee-Ch7k5sss.js +1 -0
  120. package/public/assets/common-lisp-Cg-RD9OK.js +1 -0
  121. package/public/assets/coq-DkFqJrB1.js +1 -0
  122. package/public/assets/core-BSTVzpXI.js +1 -0
  123. package/public/assets/cpp-CofmeUqb.js +1 -0
  124. package/public/assets/crystal-tKQVLTB8.js +1 -0
  125. package/public/assets/csharp-CX12Zw3r.js +1 -0
  126. package/public/assets/css-DPfMkruS.js +1 -0
  127. package/public/assets/csv-fuZLfV_i.js +1 -0
  128. package/public/assets/cue-D82EKSYY.js +1 -0
  129. package/public/assets/cypher-COkxafJQ.js +1 -0
  130. package/public/assets/d-85-TOEBH.js +1 -0
  131. package/public/assets/dark-plus-eOWES_5F.js +1 -0
  132. package/public/assets/dart-CF10PKvl.js +1 -0
  133. package/public/assets/dax-CEL-wOlO.js +1 -0
  134. package/public/assets/desktop-BmXAJ9_W.js +1 -0
  135. package/public/assets/diff-D97Zzqfu.js +1 -0
  136. package/public/assets/docker-BcOcwvcX.js +1 -0
  137. package/public/assets/dotenv-Da5cRb03.js +1 -0
  138. package/public/assets/dracula-BzJJZx-M.js +1 -0
  139. package/public/assets/dracula-soft-BXkSAIEj.js +1 -0
  140. package/public/assets/dream-maker-BtqSS_iP.js +1 -0
  141. package/public/assets/edge-BkV0erSs.js +1 -0
  142. package/public/assets/elixir-CDX3lj18.js +1 -0
  143. package/public/assets/elm-DbKCFpqz.js +1 -0
  144. package/public/assets/emacs-lisp-C9XAeP06.js +1 -0
  145. package/public/assets/erb-BOJIQeun.js +1 -0
  146. package/public/assets/erlang-DsQrWhSR.js +1 -0
  147. package/public/assets/everforest-dark-BgDCqdQA.js +1 -0
  148. package/public/assets/everforest-light-C8M2exoo.js +1 -0
  149. package/public/assets/fennel-BYunw83y.js +1 -0
  150. package/public/assets/fish-BvzEVeQv.js +1 -0
  151. package/public/assets/fluent-C4IJs8-o.js +1 -0
  152. package/public/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  153. package/public/assets/fortran-free-form-D22FLkUw.js +1 -0
  154. package/public/assets/fsharp-CXgrBDvD.js +1 -0
  155. package/public/assets/gdresource-B7Tvp0Sc.js +1 -0
  156. package/public/assets/gdscript-DTMYz4Jt.js +1 -0
  157. package/public/assets/gdshader-DkwncUOv.js +1 -0
  158. package/public/assets/genie-D0YGMca9.js +1 -0
  159. package/public/assets/gherkin-DyxjwDmM.js +1 -0
  160. package/public/assets/git-commit-F4YmCXRG.js +1 -0
  161. package/public/assets/git-rebase-r7XF79zn.js +1 -0
  162. package/public/assets/github-dark-DHJKELXO.js +1 -0
  163. package/public/assets/github-dark-default-Cuk6v7N8.js +1 -0
  164. package/public/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  165. package/public/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  166. package/public/assets/github-light-DAi9KRSo.js +1 -0
  167. package/public/assets/github-light-default-D7oLnXFd.js +1 -0
  168. package/public/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  169. package/public/assets/gleam-BspZqrRM.js +1 -0
  170. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -0
  171. package/public/assets/glimmer-ts-U6CK756n.js +1 -0
  172. package/public/assets/glsl-DplSGwfg.js +1 -0
  173. package/public/assets/gnuplot-DdkO51Og.js +1 -0
  174. package/public/assets/go-Dn2_MT6a.js +1 -0
  175. package/public/assets/graphql-ChdNCCLP.js +1 -0
  176. package/public/assets/groovy-gcz8RCvz.js +1 -0
  177. package/public/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  178. package/public/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  179. package/public/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  180. package/public/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  181. package/public/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  182. package/public/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  183. package/public/assets/hack-CaT9iCJl.js +1 -0
  184. package/public/assets/haml-B8DHNrY2.js +1 -0
  185. package/public/assets/handlebars-BL8al0AC.js +1 -0
  186. package/public/assets/haskell-Df6bDoY_.js +1 -0
  187. package/public/assets/haxe-CzTSHFRz.js +1 -0
  188. package/public/assets/hcl-BWvSN4gD.js +1 -0
  189. package/public/assets/hjson-D5-asLiD.js +1 -0
  190. package/public/assets/hlsl-D3lLCCz7.js +1 -0
  191. package/public/assets/houston-DnULxvSX.js +1 -0
  192. package/public/assets/html-GMplVEZG.js +1 -0
  193. package/public/assets/html-derivative-BFtXZ54Q.js +1 -0
  194. package/public/assets/http-jrhK8wxY.js +1 -0
  195. package/public/assets/hurl-irOxFIW8.js +1 -0
  196. package/public/assets/hxml-Bvhsp5Yf.js +1 -0
  197. package/public/assets/hy-DFXneXwc.js +1 -0
  198. package/public/assets/imba-DGztddWO.js +1 -0
  199. package/public/assets/index-D4PT0yE4.js +1 -0
  200. package/public/assets/index-DN20ggb1.js +1 -0
  201. package/public/assets/index-DdQ7zIzB.js +1 -0
  202. package/public/assets/index-Dl-rJJuP.js +1 -0
  203. package/public/assets/index-Dlo2gDiy.css +1 -0
  204. package/public/assets/ini-BEwlwnbL.js +1 -0
  205. package/public/assets/java-CylS5w8V.js +1 -0
  206. package/public/assets/javascript-wDzz0qaB.js +1 -0
  207. package/public/assets/jinja-4LBKfQ-Z.js +1 -0
  208. package/public/assets/jison-wvAkD_A8.js +1 -0
  209. package/public/assets/json-Cp-IABpG.js +1 -0
  210. package/public/assets/json5-C9tS-k6U.js +1 -0
  211. package/public/assets/jsonc-Des-eS-w.js +1 -0
  212. package/public/assets/jsonl-DcaNXYhu.js +1 -0
  213. package/public/assets/jsonnet-DFQXde-d.js +1 -0
  214. package/public/assets/jssm-C2t-YnRu.js +1 -0
  215. package/public/assets/jsx-g9-lgVsj.js +1 -0
  216. package/public/assets/julia-C8NyazO9.js +1 -0
  217. package/public/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  218. package/public/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  219. package/public/assets/kanagawa-wave-DWedfzmr.js +1 -0
  220. package/public/assets/kdl-DV7GczEv.js +1 -0
  221. package/public/assets/kotlin-BdnUsdx6.js +1 -0
  222. package/public/assets/kusto-BvAqAH-y.js +1 -0
  223. package/public/assets/laserwave-DUszq2jm.js +1 -0
  224. package/public/assets/latex-BUKiar2Z.js +1 -0
  225. package/public/assets/lean-DP1Csr6i.js +1 -0
  226. package/public/assets/less-B1dDrJ26.js +1 -0
  227. package/public/assets/light-plus-B7mTdjB0.js +1 -0
  228. package/public/assets/liquid-DYVedYrR.js +1 -0
  229. package/public/assets/llvm-BtvRca6l.js +1 -0
  230. package/public/assets/loading-CmEVQgyj.css +1 -0
  231. package/public/assets/loading-DgqIiz-T.js +1 -0
  232. package/public/assets/log-2UxHyX5q.js +1 -0
  233. package/public/assets/logo-BtOb2qkB.js +1 -0
  234. package/public/assets/lua-BbnMAYS6.js +1 -0
  235. package/public/assets/luau-CXu1NL6O.js +1 -0
  236. package/public/assets/main-CSlDZj4f.js +188 -0
  237. package/public/assets/main-HAZkIolJ.css +19 -0
  238. package/public/assets/make-CHLpvVh8.js +1 -0
  239. package/public/assets/markdown-Cvjx9yec.js +1 -0
  240. package/public/assets/marko-CPi9NSCl.js +1 -0
  241. package/public/assets/material-theme-D5KoaKCx.js +1 -0
  242. package/public/assets/material-theme-darker-BfHTSMKl.js +1 -0
  243. package/public/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  244. package/public/assets/material-theme-ocean-CyktbL80.js +1 -0
  245. package/public/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  246. package/public/assets/matlab-D7o27uSR.js +1 -0
  247. package/public/assets/mdc-DUICxH0z.js +1 -0
  248. package/public/assets/mdx-Cmh6b_Ma.js +1 -0
  249. package/public/assets/mermaid-DKYwYmdq.js +1 -0
  250. package/public/assets/min-dark-CafNBF8u.js +1 -0
  251. package/public/assets/min-light-CTRr51gU.js +1 -0
  252. package/public/assets/mipsasm-CKIfxQSi.js +1 -0
  253. package/public/assets/mojo-1DNp92w6.js +1 -0
  254. package/public/assets/monokai-D4h5O-jR.js +1 -0
  255. package/public/assets/move-Bu9oaDYs.js +1 -0
  256. package/public/assets/narrat-DRg8JJMk.js +1 -0
  257. package/public/assets/nextflow-CUEJCptM.js +1 -0
  258. package/public/assets/nginx-DknmC5AR.js +1 -0
  259. package/public/assets/night-owl-C39BiMTA.js +1 -0
  260. package/public/assets/nim-CVrawwO9.js +1 -0
  261. package/public/assets/nix-BbRYJGeE.js +1 -0
  262. package/public/assets/nord-Ddv68eIx.js +1 -0
  263. package/public/assets/nushell-C-sUppwS.js +1 -0
  264. package/public/assets/objective-c-DXmwc3jG.js +1 -0
  265. package/public/assets/objective-cpp-CLxacb5B.js +1 -0
  266. package/public/assets/ocaml-C0hk2d4L.js +1 -0
  267. package/public/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  268. package/public/assets/one-light-PoHY5YXO.js +1 -0
  269. package/public/assets/pascal-D93ZcfNL.js +1 -0
  270. package/public/assets/perl-C0TMdlhV.js +1 -0
  271. package/public/assets/php-CDn_0X-4.js +1 -0
  272. package/public/assets/pkl-u5AG7uiY.js +1 -0
  273. package/public/assets/plastic-3e1v2bzS.js +1 -0
  274. package/public/assets/plsql-ChMvpjG-.js +1 -0
  275. package/public/assets/po-BTJTHyun.js +1 -0
  276. package/public/assets/poimandres-CS3Unz2-.js +1 -0
  277. package/public/assets/polar-C0HS_06l.js +1 -0
  278. package/public/assets/postcss-CXtECtnM.js +1 -0
  279. package/public/assets/powerquery-CEu0bR-o.js +1 -0
  280. package/public/assets/powershell-Dpen1YoG.js +1 -0
  281. package/public/assets/prisma-Dd19v3D-.js +1 -0
  282. package/public/assets/prolog-CbFg5uaA.js +1 -0
  283. package/public/assets/proto-DyJlTyXw.js +1 -0
  284. package/public/assets/pug-CGlum2m_.js +1 -0
  285. package/public/assets/puppet-BMWR74SV.js +1 -0
  286. package/public/assets/purescript-CklMAg4u.js +1 -0
  287. package/public/assets/python-B6aJPvgy.js +1 -0
  288. package/public/assets/qml-3beO22l8.js +1 -0
  289. package/public/assets/qmldir-C8lEn-DE.js +1 -0
  290. package/public/assets/qss-IeuSbFQv.js +1 -0
  291. package/public/assets/r-DiinP2Uv.js +1 -0
  292. package/public/assets/racket-BqYA7rlc.js +1 -0
  293. package/public/assets/raku-DXvB9xmW.js +1 -0
  294. package/public/assets/razor-WgofotgN.js +1 -0
  295. package/public/assets/red-bN70gL4F.js +1 -0
  296. package/public/assets/reg-C-SQnVFl.js +1 -0
  297. package/public/assets/regexp-CDVJQ6XC.js +1 -0
  298. package/public/assets/rel-C3B-1QV4.js +1 -0
  299. package/public/assets/riscv-BM1_JUlF.js +1 -0
  300. package/public/assets/rose-pine-BHrmToEH.js +1 -0
  301. package/public/assets/rose-pine-dawn-CnK8MTSM.js +1 -0
  302. package/public/assets/rose-pine-moon-NleAzG8P.js +1 -0
  303. package/public/assets/rosmsg-BJDFO7_C.js +1 -0
  304. package/public/assets/rst-B0xPkSld.js +1 -0
  305. package/public/assets/ruby-BvKwtOVI.js +1 -0
  306. package/public/assets/rust-B1yitclQ.js +1 -0
  307. package/public/assets/sas-cz2c8ADy.js +1 -0
  308. package/public/assets/sass-Cj5Yp3dK.js +1 -0
  309. package/public/assets/scala-C151Ov-r.js +1 -0
  310. package/public/assets/scheme-C98Dy4si.js +1 -0
  311. package/public/assets/scss-OYdSNvt2.js +1 -0
  312. package/public/assets/sdbl-DVxCFoDh.js +1 -0
  313. package/public/assets/shaderlab-Dg9Lc6iA.js +1 -0
  314. package/public/assets/shellscript-Yzrsuije.js +1 -0
  315. package/public/assets/shellsession-BADoaaVG.js +1 -0
  316. package/public/assets/slack-dark-BthQWCQV.js +1 -0
  317. package/public/assets/slack-ochin-DqwNpetd.js +1 -0
  318. package/public/assets/smalltalk-BERRCDM3.js +1 -0
  319. package/public/assets/snazzy-light-Bw305WKR.js +1 -0
  320. package/public/assets/solarized-dark-DXbdFlpD.js +1 -0
  321. package/public/assets/solarized-light-L9t79GZl.js +1 -0
  322. package/public/assets/solidity-BbcW6ACK.js +1 -0
  323. package/public/assets/soy-Brmx7dQM.js +1 -0
  324. package/public/assets/sparql-rVzFXLq3.js +1 -0
  325. package/public/assets/splunk-BtCnVYZw.js +1 -0
  326. package/public/assets/sql-BLtJtn59.js +1 -0
  327. package/public/assets/ssh-config-_ykCGR6B.js +1 -0
  328. package/public/assets/stata-BH5u7GGu.js +1 -0
  329. package/public/assets/stylus-BEDo0Tqx.js +1 -0
  330. package/public/assets/svelte-3Dk4HxPD.js +1 -0
  331. package/public/assets/swift-Dg5xB15N.js +1 -0
  332. package/public/assets/synthwave-84-CbfX1IO0.js +1 -0
  333. package/public/assets/system-verilog-CnnmHF94.js +1 -0
  334. package/public/assets/systemd-4A_iFExJ.js +1 -0
  335. package/public/assets/talonscript-CkByrt1z.js +1 -0
  336. package/public/assets/tasl-QIJgUcNo.js +1 -0
  337. package/public/assets/tcl-dwOrl1Do.js +1 -0
  338. package/public/assets/templ-W15q3VgB.js +1 -0
  339. package/public/assets/terraform-BETggiCN.js +1 -0
  340. package/public/assets/tex-Cppo0RY3.js +1 -0
  341. package/public/assets/tokyo-night-hegEt444.js +1 -0
  342. package/public/assets/toml-vGWfd6FD.js +1 -0
  343. package/public/assets/ts-tags-zn1MmPIZ.js +1 -0
  344. package/public/assets/tsv-B_m7g4N7.js +1 -0
  345. package/public/assets/tsx-COt5Ahok.js +1 -0
  346. package/public/assets/turtle-BsS91CYL.js +1 -0
  347. package/public/assets/twig-CO9l9SDP.js +1 -0
  348. package/public/assets/typescript-BPQ3VLAy.js +1 -0
  349. package/public/assets/typespec-Df68jz8_.js +1 -0
  350. package/public/assets/typst-DHCkPAjA.js +1 -0
  351. package/public/assets/v-BcVCzyr7.js +1 -0
  352. package/public/assets/vala-CsfeWuGM.js +1 -0
  353. package/public/assets/vb-D17OF-Vu.js +1 -0
  354. package/public/assets/verilog-BQ8w6xss.js +1 -0
  355. package/public/assets/vesper-DU1UobuO.js +1 -0
  356. package/public/assets/vhdl-CeAyd5Ju.js +1 -0
  357. package/public/assets/viml-CJc9bBzg.js +1 -0
  358. package/public/assets/vitesse-black-Bkuqu6BP.js +1 -0
  359. package/public/assets/vitesse-dark-D0r3Knsf.js +1 -0
  360. package/public/assets/vitesse-light-CVO1_9PV.js +1 -0
  361. package/public/assets/vue-CCoi5OLL.js +1 -0
  362. package/public/assets/vue-html-DAAvJJDi.js +1 -0
  363. package/public/assets/vue-vine-_Ih-lPRR.js +1 -0
  364. package/public/assets/vyper-CDx5xZoG.js +1 -0
  365. package/public/assets/wasm-CG6Dc4jp.js +1 -0
  366. package/public/assets/wasm-MzD3tlZU.js +1 -0
  367. package/public/assets/wenyan-BV7otONQ.js +1 -0
  368. package/public/assets/wgsl-Dx-B1_4e.js +1 -0
  369. package/public/assets/wikitext-BhOHFoWU.js +1 -0
  370. package/public/assets/wit-5i3qLPDT.js +1 -0
  371. package/public/assets/wolfram-lXgVvXCa.js +1 -0
  372. package/public/assets/xml-sdJ4AIDG.js +1 -0
  373. package/public/assets/xsl-CtQFsRM5.js +1 -0
  374. package/public/assets/yaml-Buea-lGh.js +1 -0
  375. package/public/assets/zenscript-DVFEvuxE.js +1 -0
  376. package/public/assets/zig-VOosw3JB.js +1 -0
  377. package/public/favicon.ico +0 -0
  378. package/public/index.html +38 -0
  379. package/public/loading.html +28 -0
  380. package/public/logo.png +0 -0
  381. package/public/manifest.webmanifest +1 -0
  382. package/public/maskable-icon-512x512.png +0 -0
  383. package/public/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  384. package/public/monaco/vs/base/worker/workerMain.js +31 -0
  385. package/public/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  386. package/public/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  387. package/public/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  388. package/public/monaco/vs/basic-languages/python/python.js +10 -0
  389. package/public/monaco/vs/editor/editor.main.css +8 -0
  390. package/public/monaco/vs/editor/editor.main.js +798 -0
  391. package/public/monaco/vs/language/css/cssMode.js +13 -0
  392. package/public/monaco/vs/language/css/cssWorker.js +77 -0
  393. package/public/monaco/vs/language/html/htmlMode.js +13 -0
  394. package/public/monaco/vs/language/html/htmlWorker.js +454 -0
  395. package/public/monaco/vs/language/json/jsonMode.js +19 -0
  396. package/public/monaco/vs/language/json/jsonWorker.js +42 -0
  397. package/public/monaco/vs/language/typescript/tsMode.js +20 -0
  398. package/public/monaco/vs/language/typescript/tsWorker.js +51328 -0
  399. package/public/monaco/vs/loader.js +11 -0
  400. package/public/monaco.worker.js +7 -0
  401. package/public/pwa-192x192.png +0 -0
  402. package/public/pwa-512x512.png +0 -0
  403. package/public/pwa-64x64.png +0 -0
  404. package/public/registerSW.js +1 -0
  405. package/public/sw.js +1 -0
  406. package/public/ui-version.json +3 -0
  407. package/public/workbox-60d14903.js +1 -0
  408. package/scripts/copy-auth-pages.mjs +22 -0
  409. package/scripts/copy-opencode-config.mjs +61 -0
  410. package/scripts/copy-ui-dist.mjs +21 -0
  411. package/src/api-types.ts +326 -0
  412. package/src/auth/auth-store.ts +175 -0
  413. package/src/auth/http-auth.ts +38 -0
  414. package/src/auth/manager.ts +163 -0
  415. package/src/auth/password-hash.ts +49 -0
  416. package/src/auth/session-manager.ts +23 -0
  417. package/src/auth/token-manager.ts +32 -0
  418. package/src/background-processes/manager.ts +519 -0
  419. package/src/bin.ts +29 -0
  420. package/src/config/binaries.ts +192 -0
  421. package/src/config/location.ts +78 -0
  422. package/src/config/schema.ts +104 -0
  423. package/src/config/store.ts +244 -0
  424. package/src/events/bus.ts +45 -0
  425. package/src/filesystem/__tests__/search-cache.test.ts +61 -0
  426. package/src/filesystem/browser.ts +353 -0
  427. package/src/filesystem/search-cache.ts +66 -0
  428. package/src/filesystem/search.ts +184 -0
  429. package/src/index.ts +540 -0
  430. package/src/launcher.ts +177 -0
  431. package/src/loader.ts +21 -0
  432. package/src/logger.ts +133 -0
  433. package/src/opencode-config.ts +31 -0
  434. package/src/plugins/channel.ts +55 -0
  435. package/src/plugins/handlers.ts +36 -0
  436. package/src/releases/dev-release-monitor.ts +118 -0
  437. package/src/releases/release-monitor.ts +149 -0
  438. package/src/server/http-server.ts +693 -0
  439. package/src/server/network-addresses.ts +75 -0
  440. package/src/server/routes/auth-pages/login.html +134 -0
  441. package/src/server/routes/auth-pages/token.html +93 -0
  442. package/src/server/routes/auth.ts +164 -0
  443. package/src/server/routes/background-processes.ts +85 -0
  444. package/src/server/routes/config.ts +76 -0
  445. package/src/server/routes/events.ts +61 -0
  446. package/src/server/routes/filesystem.ts +54 -0
  447. package/src/server/routes/meta.ts +58 -0
  448. package/src/server/routes/plugin.ts +75 -0
  449. package/src/server/routes/storage.ts +66 -0
  450. package/src/server/routes/workspaces.ts +113 -0
  451. package/src/server/routes/worktrees.ts +195 -0
  452. package/src/server/tls.ts +283 -0
  453. package/src/storage/instance-store.ts +64 -0
  454. package/src/ui/__tests__/remote-ui.test.ts +58 -0
  455. package/src/ui/remote-ui.ts +571 -0
  456. package/src/workspaces/git-worktrees.ts +241 -0
  457. package/src/workspaces/instance-events.ts +226 -0
  458. package/src/workspaces/manager.ts +493 -0
  459. package/src/workspaces/opencode-auth.ts +22 -0
  460. package/src/workspaces/runtime.ts +428 -0
  461. package/src/workspaces/worktree-map.ts +129 -0
  462. package/tsconfig.json +17 -0
@@ -0,0 +1,175 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import type { Logger } from "../logger"
4
+ import { hashPassword, type PasswordHashRecord, verifyPassword } from "./password-hash"
5
+
6
+ export interface AuthFile {
7
+ version: 1
8
+ username: string
9
+ password: PasswordHashRecord
10
+ userProvided: boolean
11
+ updatedAt: string
12
+ }
13
+
14
+ export interface AuthStatus {
15
+ username: string
16
+ passwordUserProvided: boolean
17
+ }
18
+
19
+ export class AuthStore {
20
+ private cachedFile: AuthFile | null = null
21
+ private overrideAuth: AuthFile | null = null
22
+ private bootstrapUsername: string | null = null
23
+
24
+ constructor(private readonly authFilePath: string, private readonly logger: Logger) {}
25
+
26
+ getAuthFilePath() {
27
+ return this.authFilePath
28
+ }
29
+
30
+ load(): AuthFile | null {
31
+ if (this.overrideAuth) {
32
+ return this.overrideAuth
33
+ }
34
+
35
+ if (this.cachedFile) {
36
+ return this.cachedFile
37
+ }
38
+
39
+ try {
40
+ if (!fs.existsSync(this.authFilePath)) {
41
+ return null
42
+ }
43
+ const raw = fs.readFileSync(this.authFilePath, "utf-8")
44
+ const parsed = JSON.parse(raw) as AuthFile
45
+ if (!parsed || parsed.version !== 1) {
46
+ this.logger.warn({ authFilePath: this.authFilePath }, "Auth file has unsupported version")
47
+ return null
48
+ }
49
+ this.cachedFile = parsed
50
+ return parsed
51
+ } catch (error) {
52
+ this.logger.warn({ err: error, authFilePath: this.authFilePath }, "Failed to load auth file")
53
+ return null
54
+ }
55
+ }
56
+
57
+ ensureInitialized(params: {
58
+ username: string
59
+ password?: string
60
+ allowBootstrapWithoutPassword: boolean
61
+ }): void {
62
+ const password = params.password?.trim()
63
+ if (password) {
64
+ const now = new Date().toISOString()
65
+ const runtime: AuthFile = {
66
+ version: 1,
67
+ username: params.username,
68
+ password: hashPassword(password),
69
+ userProvided: true,
70
+ updatedAt: now,
71
+ }
72
+ this.overrideAuth = runtime
73
+ this.cachedFile = null
74
+ this.bootstrapUsername = null
75
+ this.logger.debug({ authFilePath: this.authFilePath }, "Using runtime auth password override; ignoring auth file")
76
+ return
77
+ }
78
+
79
+ const existing = this.load()
80
+ if (existing) {
81
+ if (existing.username !== params.username) {
82
+ // Keep existing username unless explicitly overridden later.
83
+ this.logger.debug({ existing: existing.username, requested: params.username }, "Auth username differs from requested")
84
+ }
85
+ this.bootstrapUsername = null
86
+ return
87
+ }
88
+
89
+ if (params.allowBootstrapWithoutPassword) {
90
+ this.bootstrapUsername = params.username
91
+ this.logger.debug({ authFilePath: this.authFilePath }, "No auth file present; bootstrap-only mode enabled")
92
+ return
93
+ }
94
+
95
+ throw new Error(
96
+ `No server password configured. Create ${this.authFilePath} or start with --password / CODENOMAD_SERVER_PASSWORD.`,
97
+ )
98
+ }
99
+
100
+ validateCredentials(username: string, password: string): boolean {
101
+ const auth = this.load()
102
+ if (!auth) {
103
+ return false
104
+ }
105
+
106
+ if (username !== auth.username) {
107
+ return false
108
+ }
109
+
110
+ return verifyPassword(password, auth.password)
111
+ }
112
+
113
+ setPassword(params: { password: string; markUserProvided: boolean }): AuthStatus {
114
+ if (this.overrideAuth) {
115
+ throw new Error(
116
+ "Server password is provided via CLI/env and cannot be changed while running. Restart without --password / CODENOMAD_SERVER_PASSWORD to use auth.json.",
117
+ )
118
+ }
119
+
120
+ const current = this.load()
121
+
122
+ if (!current) {
123
+ if (!this.bootstrapUsername) {
124
+ throw new Error("Auth is not initialized")
125
+ }
126
+
127
+ const created: AuthFile = {
128
+ version: 1,
129
+ username: this.bootstrapUsername,
130
+ password: hashPassword(params.password),
131
+ userProvided: params.markUserProvided,
132
+ updatedAt: new Date().toISOString(),
133
+ }
134
+
135
+ this.persist(created)
136
+ this.bootstrapUsername = null
137
+ return { username: created.username, passwordUserProvided: created.userProvided }
138
+ }
139
+
140
+ const next: AuthFile = {
141
+ ...current,
142
+ password: hashPassword(params.password),
143
+ userProvided: params.markUserProvided,
144
+ updatedAt: new Date().toISOString(),
145
+ }
146
+
147
+ this.persist(next)
148
+ return { username: next.username, passwordUserProvided: next.userProvided }
149
+ }
150
+
151
+ getStatus(): AuthStatus {
152
+ const current = this.load()
153
+ if (current) {
154
+ return { username: current.username, passwordUserProvided: current.userProvided }
155
+ }
156
+
157
+ if (this.bootstrapUsername) {
158
+ return { username: this.bootstrapUsername, passwordUserProvided: false }
159
+ }
160
+
161
+ throw new Error("Auth is not initialized")
162
+ }
163
+
164
+ private persist(auth: AuthFile) {
165
+ try {
166
+ fs.mkdirSync(path.dirname(this.authFilePath), { recursive: true })
167
+ fs.writeFileSync(this.authFilePath, JSON.stringify(auth, null, 2), "utf-8")
168
+ this.cachedFile = auth
169
+ this.logger.debug({ authFilePath: this.authFilePath }, "Persisted auth file")
170
+ } catch (error) {
171
+ this.logger.error({ err: error, authFilePath: this.authFilePath }, "Failed to persist auth file")
172
+ throw error
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,38 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify"
2
+
3
+ export function parseCookies(header: string | undefined): Record<string, string> {
4
+ const result: Record<string, string> = {}
5
+ if (!header) return result
6
+
7
+ const parts = header.split(";")
8
+ for (const part of parts) {
9
+ const index = part.indexOf("=")
10
+ if (index < 0) continue
11
+ const key = part.slice(0, index).trim()
12
+ const value = part.slice(index + 1).trim()
13
+ if (!key) continue
14
+ result[key] = decodeURIComponent(value)
15
+ }
16
+ return result
17
+ }
18
+
19
+ export function isLoopbackAddress(remoteAddress: string | undefined): boolean {
20
+ if (!remoteAddress) return false
21
+ if (remoteAddress === "127.0.0.1" || remoteAddress === "::1") return true
22
+ if (remoteAddress === "::ffff:127.0.0.1") return true
23
+ return false
24
+ }
25
+
26
+ export function wantsHtml(request: FastifyRequest): boolean {
27
+ const accept = (request.headers["accept"] ?? "").toString().toLowerCase()
28
+ return accept.includes("text/html") || accept.includes("application/xhtml")
29
+ }
30
+
31
+ export function sendUnauthorized(request: FastifyRequest, reply: FastifyReply) {
32
+ if (request.method === "GET" && !request.url.startsWith("/api/") && wantsHtml(request)) {
33
+ reply.redirect("/login")
34
+ return
35
+ }
36
+
37
+ reply.code(401).send({ error: "Unauthorized" })
38
+ }
@@ -0,0 +1,163 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify"
2
+ import path from "path"
3
+ import type { Logger } from "../logger"
4
+ import { AuthStore } from "./auth-store"
5
+ import { TokenManager } from "./token-manager"
6
+ import { SessionManager } from "./session-manager"
7
+ import { isLoopbackAddress, parseCookies } from "./http-auth"
8
+
9
+ export const BOOTSTRAP_TOKEN_STDOUT_PREFIX = "CODENOMAD_BOOTSTRAP_TOKEN:" as const
10
+ export const DEFAULT_AUTH_USERNAME = "codenomad" as const
11
+ export const DEFAULT_AUTH_COOKIE_NAME = "codenomad_session" as const
12
+
13
+ export interface AuthManagerInit {
14
+ configPath: string
15
+ username: string
16
+ password?: string
17
+ generateToken: boolean
18
+ dangerouslySkipAuth?: boolean
19
+ }
20
+
21
+ export class AuthManager {
22
+ private readonly authStore: AuthStore | null
23
+ private readonly tokenManager: TokenManager | null
24
+ private readonly sessionManager = new SessionManager()
25
+ private readonly cookieName = DEFAULT_AUTH_COOKIE_NAME
26
+ private readonly authEnabled: boolean
27
+
28
+ constructor(private readonly init: AuthManagerInit, private readonly logger: Logger) {
29
+ this.authEnabled = !Boolean(init.dangerouslySkipAuth)
30
+
31
+ if (!this.authEnabled) {
32
+ this.authStore = null
33
+ this.tokenManager = null
34
+ return
35
+ }
36
+
37
+ const authFilePath = resolveAuthFilePath(init.configPath)
38
+ this.authStore = new AuthStore(authFilePath, logger.child({ component: "auth" }))
39
+
40
+ // Startup: password comes from CLI/env, auth.json, or bootstrap-only mode.
41
+ this.authStore.ensureInitialized({
42
+ username: init.username,
43
+ password: init.password,
44
+ allowBootstrapWithoutPassword: init.generateToken,
45
+ })
46
+
47
+ this.tokenManager = init.generateToken ? new TokenManager(60_000) : null
48
+ }
49
+
50
+ isAuthEnabled(): boolean {
51
+ return this.authEnabled
52
+ }
53
+
54
+ getCookieName(): string {
55
+ return this.cookieName
56
+ }
57
+
58
+ isTokenBootstrapEnabled(): boolean {
59
+ return Boolean(this.tokenManager)
60
+ }
61
+
62
+ issueBootstrapToken(): string | null {
63
+ if (!this.tokenManager) return null
64
+ return this.tokenManager.generate()
65
+ }
66
+
67
+ consumeBootstrapToken(token: string): boolean {
68
+ if (!this.tokenManager) return false
69
+ return this.tokenManager.consume(token)
70
+ }
71
+
72
+ validateLogin(username: string, password: string): boolean {
73
+ if (!this.authEnabled) {
74
+ return true
75
+ }
76
+ return this.requireAuthStore().validateCredentials(username, password)
77
+ }
78
+
79
+ createSession(username: string) {
80
+ if (!this.authEnabled) {
81
+ return { id: "auth-disabled", createdAt: Date.now(), username: this.init.username }
82
+ }
83
+ return this.sessionManager.createSession(username)
84
+ }
85
+
86
+ getStatus() {
87
+ if (!this.authEnabled) {
88
+ return { username: this.init.username, passwordUserProvided: false }
89
+ }
90
+ return this.requireAuthStore().getStatus()
91
+ }
92
+
93
+ setPassword(password: string) {
94
+ if (!this.authEnabled) {
95
+ throw new Error("Internal authentication is disabled")
96
+ }
97
+ return this.requireAuthStore().setPassword({ password, markUserProvided: true })
98
+ }
99
+
100
+ isLoopbackRequest(request: FastifyRequest): boolean {
101
+ return isLoopbackAddress(request.socket.remoteAddress)
102
+ }
103
+
104
+ getSessionFromRequest(request: FastifyRequest): { username: string; sessionId: string } | null {
105
+ if (!this.authEnabled) {
106
+ // When auth is disabled, treat all requests as authenticated.
107
+ // We still return a stable username so callers can display it.
108
+ return { username: this.init.username, sessionId: "auth-disabled" }
109
+ }
110
+
111
+ const cookies = parseCookies(request.headers.cookie)
112
+ const sessionId = cookies[this.cookieName]
113
+ const session = this.sessionManager.getSession(sessionId)
114
+ if (!session) return null
115
+ return { username: session.username, sessionId: session.id }
116
+ }
117
+
118
+ setSessionCookie(reply: FastifyReply, sessionId: string) {
119
+ reply.header("Set-Cookie", buildSessionCookie(this.cookieName, sessionId))
120
+ }
121
+
122
+ setSessionCookieWithOptions(reply: FastifyReply, sessionId: string, options?: { secure?: boolean }) {
123
+ reply.header("Set-Cookie", buildSessionCookie(this.cookieName, sessionId, options))
124
+ }
125
+
126
+ clearSessionCookie(reply: FastifyReply) {
127
+ reply.header("Set-Cookie", buildSessionCookie(this.cookieName, "", { maxAgeSeconds: 0 }))
128
+ }
129
+
130
+ clearSessionCookieWithOptions(reply: FastifyReply, options?: { secure?: boolean }) {
131
+ reply.header("Set-Cookie", buildSessionCookie(this.cookieName, "", { maxAgeSeconds: 0, ...options }))
132
+ }
133
+
134
+ private requireAuthStore(): AuthStore {
135
+ if (!this.authStore) {
136
+ throw new Error("Auth store is unavailable")
137
+ }
138
+ return this.authStore
139
+ }
140
+ }
141
+
142
+ function resolveAuthFilePath(configPath: string) {
143
+ const resolvedConfigPath = resolvePath(configPath)
144
+ return path.join(path.dirname(resolvedConfigPath), "auth.json")
145
+ }
146
+
147
+ function resolvePath(filePath: string) {
148
+ if (filePath.startsWith("~/")) {
149
+ return path.join(process.env.HOME ?? "", filePath.slice(2))
150
+ }
151
+ return path.resolve(filePath)
152
+ }
153
+
154
+ function buildSessionCookie(name: string, value: string, options?: { maxAgeSeconds?: number; secure?: boolean }) {
155
+ const parts = [`${name}=${encodeURIComponent(value)}`, "HttpOnly", "Path=/", "SameSite=Lax"]
156
+ if (options?.secure) {
157
+ parts.push("Secure")
158
+ }
159
+ if (options?.maxAgeSeconds !== undefined) {
160
+ parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAgeSeconds))}`)
161
+ }
162
+ return parts.join("; ")
163
+ }
@@ -0,0 +1,49 @@
1
+ import crypto from "crypto"
2
+
3
+ export interface PasswordHashRecord {
4
+ algorithm: "scrypt"
5
+ saltBase64: string
6
+ hashBase64: string
7
+ keyLength: number
8
+ params: {
9
+ N: number
10
+ r: number
11
+ p: number
12
+ maxmem: number
13
+ }
14
+ }
15
+
16
+ const DEFAULT_SCRYPT_PARAMS = {
17
+ N: 16384,
18
+ r: 8,
19
+ p: 1,
20
+ maxmem: 32 * 1024 * 1024,
21
+ }
22
+
23
+ export function hashPassword(password: string): PasswordHashRecord {
24
+ const salt = crypto.randomBytes(16)
25
+ const params = DEFAULT_SCRYPT_PARAMS
26
+ const keyLength = 64
27
+ const derived = crypto.scryptSync(password, salt, keyLength, params)
28
+ return {
29
+ algorithm: "scrypt",
30
+ saltBase64: salt.toString("base64"),
31
+ hashBase64: Buffer.from(derived).toString("base64"),
32
+ keyLength,
33
+ params,
34
+ }
35
+ }
36
+
37
+ export function verifyPassword(password: string, record: PasswordHashRecord): boolean {
38
+ if (record.algorithm !== "scrypt") {
39
+ return false
40
+ }
41
+
42
+ const salt = Buffer.from(record.saltBase64, "base64")
43
+ const expected = Buffer.from(record.hashBase64, "base64")
44
+ const derived = crypto.scryptSync(password, salt, record.keyLength, record.params)
45
+ if (expected.length !== derived.length) {
46
+ return false
47
+ }
48
+ return crypto.timingSafeEqual(expected, Buffer.from(derived))
49
+ }
@@ -0,0 +1,23 @@
1
+ import crypto from "crypto"
2
+
3
+ export interface SessionInfo {
4
+ id: string
5
+ createdAt: number
6
+ username: string
7
+ }
8
+
9
+ export class SessionManager {
10
+ private sessions = new Map<string, SessionInfo>()
11
+
12
+ createSession(username: string): SessionInfo {
13
+ const id = crypto.randomBytes(32).toString("base64url")
14
+ const info: SessionInfo = { id, createdAt: Date.now(), username }
15
+ this.sessions.set(id, info)
16
+ return info
17
+ }
18
+
19
+ getSession(id: string | undefined): SessionInfo | undefined {
20
+ if (!id) return undefined
21
+ return this.sessions.get(id)
22
+ }
23
+ }
@@ -0,0 +1,32 @@
1
+ import crypto from "crypto"
2
+
3
+ export interface BootstrapToken {
4
+ token: string
5
+ createdAt: number
6
+ consumed: boolean
7
+ }
8
+
9
+ export class TokenManager {
10
+ private token: BootstrapToken | null = null
11
+
12
+ constructor(private readonly ttlMs: number) {}
13
+
14
+ generate(): string {
15
+ const token = crypto.randomBytes(32).toString("base64url")
16
+ this.token = { token, createdAt: Date.now(), consumed: false }
17
+ return token
18
+ }
19
+
20
+ consume(token: string): boolean {
21
+ if (!this.token) return false
22
+ if (this.token.consumed) return false
23
+ if (Date.now() - this.token.createdAt > this.ttlMs) return false
24
+ if (token !== this.token.token) return false
25
+ this.token.consumed = true
26
+ return true
27
+ }
28
+
29
+ peek(): string | null {
30
+ return this.token?.token ?? null
31
+ }
32
+ }