@fabiofiorita/porcelain 0.63.4 → 0.65.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 (440) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -28
  3. package/bin/porcelain.js +1 -404
  4. package/package.json +15 -21
  5. package/server/drizzle/0000_current-schema.sql +98 -0
  6. package/server/drizzle/0001_light_iceman.sql +9 -0
  7. package/server/drizzle/0002_backfill_comment_authors.sql +25 -0
  8. package/server/drizzle/0003_pairing_and_devices.sql +23 -0
  9. package/server/drizzle/0004_worktrees_from_git.sql +146 -0
  10. package/server/drizzle/0005_names_and_status.sql +31 -0
  11. package/server/drizzle/0006_stale_reviewed_files.sql +1 -0
  12. package/server/drizzle/0007_normalize_comment_messages.sql +66 -0
  13. package/server/drizzle/0008_published_reviews.sql +30 -0
  14. package/server/drizzle/0009_drop_unused_git_action_tables.sql +5 -0
  15. package/server/drizzle/0010_cascade_deletes_through_foreign_keys.sql +207 -0
  16. package/server/drizzle/0011_receipts_per_worktree.sql +35 -0
  17. package/server/drizzle/0012_receipts_as_columns.sql +68 -0
  18. package/server/drizzle/0013_monotonic_comment_revision.sql +21 -0
  19. package/server/drizzle/0014_reviewed_layers_without_stale.sql +1 -0
  20. package/server/drizzle/0015_remote_access.sql +9 -0
  21. package/server/drizzle/0016_reviewed_file_scopes.sql +17 -0
  22. package/server/drizzle/0017_comment_edits.sql +1 -0
  23. package/server/drizzle/0018_reviewed_file_branches.sql +18 -0
  24. package/server/drizzle/0019_review_proof.sql +10 -0
  25. package/server/drizzle/0020_environment_name.sql +5 -0
  26. package/server/drizzle/0021_device_routes.sql +35 -0
  27. package/server/drizzle/0022_lan_network.sql +18 -0
  28. package/server/drizzle/0023_tailnet_serve_target.sql +1 -0
  29. package/server/drizzle/0024_lan_router.sql +2 -0
  30. package/server/drizzle/0025_tailnet_hostname.sql +23 -0
  31. package/server/drizzle/0026_drop_removed_branch_receipts.sql +1 -0
  32. package/server/drizzle/0027_trusted_devices.sql +2 -0
  33. package/server/drizzle/meta/_journal.json +202 -0
  34. package/server/src/cli/main.mjs +32293 -0
  35. package/web/dist/apple-touch-icon.png +0 -0
  36. package/web/dist/assets/_paired-B3CMUVff.js +1 -0
  37. package/web/dist/assets/_paired-B8xCvrtN.js +1 -0
  38. package/web/dist/assets/_paired-DqFYC4oC.css +1 -0
  39. package/web/dist/assets/_paired-YiNqIqrL.js +1 -0
  40. package/web/dist/assets/_paired-rl3uQlkr.js +1 -0
  41. package/web/dist/assets/_section-DJ3o28t3.js +1 -0
  42. package/web/dist/assets/_worktreeId-B3CMUVff.js +1 -0
  43. package/web/dist/assets/_worktreeId-B8xCvrtN.js +1 -0
  44. package/web/dist/assets/_worktreeId-DS7C0JfB.js +1 -0
  45. package/web/dist/assets/_worktreeId-DzWffY3l.js +1 -0
  46. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  47. package/web/dist/assets/access-yiNkcsHW.js +1 -0
  48. package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  49. package/web/dist/assets/ada-C5qYipkI.js +1 -0
  50. package/web/dist/assets/ahk-CA9gdOdF.js +1 -0
  51. package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  52. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  53. package/web/dist/assets/angular-html-B8mrqUL5.js +1 -0
  54. package/web/dist/assets/angular-ts-Bzix210W.js +1 -0
  55. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  56. package/web/dist/assets/apex-KFGFyKwG.js +1 -0
  57. package/web/dist/assets/apl-Dhin1L29.js +1 -0
  58. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  59. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  60. package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  61. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  62. package/web/dist/assets/astro-B-4NNgOP.js +1 -0
  63. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  64. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  65. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  66. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  67. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  68. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  69. package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  70. package/web/dist/assets/beancount-D-usSTwE.js +1 -0
  71. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  72. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  73. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  74. package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  75. package/web/dist/assets/blade-C6Gedziy.js +1 -0
  76. package/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  77. package/web/dist/assets/c-iCihek07.js +1 -0
  78. package/web/dist/assets/c3-D0apG41V.js +1 -0
  79. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  80. package/web/dist/assets/cairo-DLTphjLi.js +1 -0
  81. package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  82. package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  83. package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  84. package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  85. package/web/dist/assets/chapel-DmtgEXO_.js +1 -0
  86. package/web/dist/assets/clarity-CtfBBspg.js +1 -0
  87. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  88. package/web/dist/assets/cloneFileDiffMetadata-zVRGqYgv.js +1 -0
  89. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  90. package/web/dist/assets/cobol-Cw2-KvLW.js +1 -0
  91. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  92. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  93. package/web/dist/assets/coffee-CiiSLhB6.js +1 -0
  94. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  95. package/web/dist/assets/connected-workspace-HwRJynH2.js +1 -0
  96. package/web/dist/assets/coq-DuBRPxYg.js +1 -0
  97. package/web/dist/assets/cpp-71xkOQct.js +1 -0
  98. package/web/dist/assets/crystal-DGPAtLeY.js +1 -0
  99. package/web/dist/assets/csharp-oqKa8noW.js +1 -0
  100. package/web/dist/assets/css-CNuuLpMC.js +1 -0
  101. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  102. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  103. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  104. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  105. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  106. package/web/dist/assets/dart-CnvKMtbv.js +1 -0
  107. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  108. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  109. package/web/dist/assets/desktop-UhAM-aem.js +14 -0
  110. package/web/dist/assets/desktop-actions-_GPqaBVU.js +12 -0
  111. package/web/dist/assets/diff-woXpYk--.js +1 -0
  112. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  113. package/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  114. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  115. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  116. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  117. package/web/dist/assets/edge-DCY0a-ow.js +1 -0
  118. package/web/dist/assets/edit-file-DOZdUUk9.js +1568 -0
  119. package/web/dist/assets/elixir-DP304Xio.js +1 -0
  120. package/web/dist/assets/elm-o4NJxfws.js +1 -0
  121. package/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  122. package/web/dist/assets/erb-C7MPl934.js +1 -0
  123. package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  124. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  125. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  126. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  127. package/web/dist/assets/file-editor-DvHSzrST.js +588 -0
  128. package/web/dist/assets/files-DGG-rGAk.js +4 -0
  129. package/web/dist/assets/fish-BJitypiv.js +1 -0
  130. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  131. package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  132. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  133. package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  134. package/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  135. package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  136. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  137. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  138. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  139. package/web/dist/assets/git-actions-PSyqF5Ms.js +1345 -0
  140. package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  141. package/web/dist/assets/git-rebase-BDPpS8w6.js +1 -0
  142. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  143. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  144. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  145. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  146. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  147. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  148. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  149. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  150. package/web/dist/assets/glimmer-js-BONyzAsu.js +1 -0
  151. package/web/dist/assets/glimmer-ts-f9c0GCOe.js +1 -0
  152. package/web/dist/assets/glsl-D5MA2a_X.js +1 -0
  153. package/web/dist/assets/gn-ilITqXS6.js +1 -0
  154. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  155. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  156. package/web/dist/assets/graphql-Ctjuxyvl.js +1 -0
  157. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  158. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  159. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  160. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  161. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  162. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  163. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  164. package/web/dist/assets/hack-Dh_ZLRbs.js +1 -0
  165. package/web/dist/assets/haml-CwEpbK1d.js +1 -0
  166. package/web/dist/assets/handlebars-ryMP1rBR.js +1 -0
  167. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  168. package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  169. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  170. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  171. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  172. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  173. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  174. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  175. package/web/dist/assets/html-XwrE-1r6.js +1 -0
  176. package/web/dist/assets/html-derivative-i4STDFnl.js +1 -0
  177. package/web/dist/assets/http-kmH8AyfE.js +1 -0
  178. package/web/dist/assets/hurl-DCZHXKU4.js +1 -0
  179. package/web/dist/assets/hxml-Dcm88srW.js +1 -0
  180. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  181. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  182. package/web/dist/assets/index-Cia9YleJ.js +10 -0
  183. package/web/dist/assets/index-DUPxq0qw.css +2 -0
  184. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  185. package/web/dist/assets/java-DdHNQ9hk.js +1 -0
  186. package/web/dist/assets/javascript-obGGvMPs.js +1 -0
  187. package/web/dist/assets/jinja-B-tLrNsM.js +1 -0
  188. package/web/dist/assets/jison-CBjhYtSs.js +1 -0
  189. package/web/dist/assets/json-DAgxKhMr.js +1 -0
  190. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  191. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  192. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  193. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  194. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  195. package/web/dist/assets/jsx-gTgmArGx.js +1 -0
  196. package/web/dist/assets/julia-CXJVy74_.js +1 -0
  197. package/web/dist/assets/just-B9Xn_Dp4.js +1 -0
  198. package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  199. package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  200. package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  201. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  202. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  203. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  204. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  205. package/web/dist/assets/latex-o9DRzaEe.js +1 -0
  206. package/web/dist/assets/lazyRouteComponent-C0cxtC0x.js +1 -0
  207. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  208. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  209. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  210. package/web/dist/assets/link-Cr7ETqcH.js +1 -0
  211. package/web/dist/assets/liquid-G0MH4h_m.js +1 -0
  212. package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  213. package/web/dist/assets/log-BNLmms1o.js +1 -0
  214. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  215. package/web/dist/assets/lua-FONpmTUP.js +1 -0
  216. package/web/dist/assets/luau-C2pBCSgt.js +1 -0
  217. package/web/dist/assets/make-Dixweg8N.js +1 -0
  218. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  219. package/web/dist/assets/marko-DV5GrDmo.js +1 -0
  220. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  221. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  222. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  223. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  224. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  225. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  226. package/web/dist/assets/mdc-BKfPFPZw.js +1 -0
  227. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  228. package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  229. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  230. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  231. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  232. package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  233. package/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
  234. package/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
  235. package/web/dist/assets/move-BYnGhZoM.js +1 -0
  236. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  237. package/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  238. package/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  239. package/web/dist/assets/nginx-BTMw9COX.js +1 -0
  240. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  241. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  242. package/web/dist/assets/nim-B6yJ_yaC.js +1 -0
  243. package/web/dist/assets/nix-IvuFDN5E.js +1 -0
  244. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  245. package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  246. package/web/dist/assets/nushell-DHYfO66s.js +1 -0
  247. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  248. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  249. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  250. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  251. package/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  252. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  253. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  254. package/web/dist/assets/org-Ddi8Gs6E.js +1 -0
  255. package/web/dist/assets/pair-B3CMUVff.js +1 -0
  256. package/web/dist/assets/pair-pADUBfPY.js +1 -0
  257. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  258. package/web/dist/assets/perl-DEgXgjuC.js +1 -0
  259. package/web/dist/assets/php-B9UPq-vz.js +1 -0
  260. package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  261. package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  262. package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  263. package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  264. package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  265. package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  266. package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  267. package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  268. package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  269. package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  270. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  271. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  272. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  273. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  274. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  275. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  276. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  277. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  278. package/web/dist/assets/powershell-CCSX75yM.js +1 -0
  279. package/web/dist/assets/preferences-gCf_-ng1.js +1 -0
  280. package/web/dist/assets/preload-helper-Czpn1I53.js +1 -0
  281. package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  282. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  283. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  284. package/web/dist/assets/pug-DkgDHZci.js +1 -0
  285. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  286. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  287. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  288. package/web/dist/assets/qml-_a-QhfPD.js +1 -0
  289. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  290. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  291. package/web/dist/assets/r-BHcO8Vjq.js +1 -0
  292. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  293. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  294. package/web/dist/assets/razor-BMJ0eI3p.js +1 -0
  295. package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  296. package/web/dist/assets/red-DX1BQOs8.js +1 -0
  297. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  298. package/web/dist/assets/regexp-DR20uMnK.js +1 -0
  299. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  300. package/web/dist/assets/review-diagram-CK76VybL.js +7 -0
  301. package/web/dist/assets/review-diagram-DLioOiRN.css +1 -0
  302. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  303. package/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  304. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  305. package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  306. package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  307. package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  308. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  309. package/web/dist/assets/rst-BQCbfb9V.js +1 -0
  310. package/web/dist/assets/ruby-B8JcIHv0.js +1 -0
  311. package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  312. package/web/dist/assets/sas-CBpMWA2M.js +1 -0
  313. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  314. package/web/dist/assets/scala-BiFt_7mu.js +1 -0
  315. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  316. package/web/dist/assets/scss-YesllqfO.js +1 -0
  317. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  318. package/web/dist/assets/search-YI2tNMg7.js +1 -0
  319. package/web/dist/assets/server-CO1nBw31.js +1 -0
  320. package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  321. package/web/dist/assets/shellscript-Da78Ox-3.js +1 -0
  322. package/web/dist/assets/shellsession-CVsQGYfZ.js +1 -0
  323. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  324. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  325. package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  326. package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  327. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  328. package/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  329. package/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  330. package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  331. package/web/dist/assets/soy-DEd-qcad.js +1 -0
  332. package/web/dist/assets/sparql-D_iOobhT.js +1 -0
  333. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  334. package/web/dist/assets/sql-DdD7vxS4.js +1 -0
  335. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  336. package/web/dist/assets/stata-CMv7m7Gi.js +1 -0
  337. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  338. package/web/dist/assets/surrealql-BZoq2lXR.js +1 -0
  339. package/web/dist/assets/svelte-Bj3Hmc9F.js +1 -0
  340. package/web/dist/assets/swift-CyEgAFGc.js +1 -0
  341. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  342. package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  343. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  344. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  345. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  346. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  347. package/web/dist/assets/templ-TjxbArHn.js +1 -0
  348. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  349. package/web/dist/assets/tex-BK-H2Wn4.js +1 -0
  350. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  351. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  352. package/web/dist/assets/ts-tags-Cc5do7zu.js +1 -0
  353. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  354. package/web/dist/assets/tsx-CUOciP0b.js +1 -0
  355. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  356. package/web/dist/assets/twig-BVSnuTf-.js +1 -0
  357. package/web/dist/assets/typescript-BydbNFcO.js +1 -0
  358. package/web/dist/assets/typespec-BRdr0IET.js +1 -0
  359. package/web/dist/assets/typst-C40W_gC6.js +1 -0
  360. package/web/dist/assets/useRouterState-DpORzQ8V.js +1 -0
  361. package/web/dist/assets/v-DBj4K9d_.js +1 -0
  362. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  363. package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  364. package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  365. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  366. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  367. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  368. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  369. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  370. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  371. package/web/dist/assets/vue-BNQF7r6F.js +1 -0
  372. package/web/dist/assets/vue-html-DkKkQnZL.js +1 -0
  373. package/web/dist/assets/vue-vine-DFaKYU4X.js +1 -0
  374. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  375. package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  376. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  377. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  378. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  379. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  380. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  381. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  382. package/web/dist/assets/worker-Bs0l0ezO.js +162 -0
  383. package/web/dist/assets/workspace-error-DILP6VTh.js +1 -0
  384. package/web/dist/assets/workspace-pending-6RKmflS9.js +1 -0
  385. package/web/dist/assets/xml-BlnrEAEy.js +1 -0
  386. package/web/dist/assets/xsl-BOPS8gLt.js +1 -0
  387. package/web/dist/assets/yaml-Du_6cdrH.js +1 -0
  388. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  389. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  390. package/web/dist/favicon-32.png +0 -0
  391. package/web/dist/favicon.ico +0 -0
  392. package/web/dist/icon-192.png +0 -0
  393. package/web/dist/icon-512.png +0 -0
  394. package/web/dist/index.html +33 -0
  395. package/web/dist/manifest.webmanifest +24 -0
  396. package/main/contracts/protocol.js +0 -36
  397. package/main/daemon/server.js +0 -17067
  398. package/renderer/apple-touch-icon.png +0 -0
  399. package/renderer/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  400. package/renderer/assets/browser-DjHhN4s0.js +0 -8
  401. package/renderer/assets/canvas-view-DhzIJ8ZV.js +0 -1
  402. package/renderer/assets/changeset-view-yBQtdgrZ.js +0 -1
  403. package/renderer/assets/commit-view-Cz3IR74x.js +0 -2
  404. package/renderer/assets/diff-mode-toggle-DTeqhuLC.js +0 -1
  405. package/renderer/assets/diff-view-DqanYaSe.js +0 -1
  406. package/renderer/assets/file-content-BX3OYiQC.js +0 -5
  407. package/renderer/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  408. package/renderer/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  409. package/renderer/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  410. package/renderer/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  411. package/renderer/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  412. package/renderer/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  413. package/renderer/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  414. package/renderer/assets/ghostty-write-pty-CVPirtOQ.wasm +0 -0
  415. package/renderer/assets/html-view-BPOrX4do.js +0 -30
  416. package/renderer/assets/hunks-view-CXQMQon9.js +0 -1
  417. package/renderer/assets/index-CS0Dcjbz.css +0 -1
  418. package/renderer/assets/index-CuWbxCsX.js +0 -282
  419. package/renderer/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  420. package/renderer/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  421. package/renderer/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  422. package/renderer/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  423. package/renderer/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  424. package/renderer/assets/line-selection-CDQurM1g.js +0 -3
  425. package/renderer/assets/logo-CUD_B5qJ.png +0 -0
  426. package/renderer/assets/search-view-BDExg3Xl.js +0 -1
  427. package/renderer/assets/surface-BsSU1Fob.js +0 -2
  428. package/renderer/assets/unfold-vertical-CIpACs4g.js +0 -1
  429. package/renderer/assets/virtual-rows-CsZNdhfp.js +0 -1
  430. package/renderer/favicon-16.png +0 -0
  431. package/renderer/favicon-32.png +0 -0
  432. package/renderer/icon-192.png +0 -0
  433. package/renderer/icon-512.png +0 -0
  434. package/renderer/index.html +0 -41
  435. package/renderer/manifest.webmanifest +0 -16
  436. /package/{renderer → web/dist}/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  437. /package/{renderer → web/dist}/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  438. /package/{renderer → web/dist}/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  439. /package/{renderer → web/dist}/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  440. /package/{renderer → web/dist}/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabio Fiorita
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,46 +1,52 @@
1
- # @fabiofiorita/porcelain (0.63.4)
1
+ # @fabiofiorita/porcelain (0.65.0)
2
2
 
3
- The Electron-free Porcelain daemon and browser client, packaged for plain Node. Run it on the
4
- machine that owns the repositories and terminals you want to review.
3
+ The Porcelain review server and bundled web app for a plain Node installation.
5
4
 
6
- ## Start and pair
5
+ ## Quick start
7
6
 
8
- The host needs Node 22+, Git, and a C toolchain for the first `node-pty` build:
7
+ The host needs Node 24+, Git, and the file watcher supported on its platform:
9
8
 
10
9
  ```sh
11
10
  npx @fabiofiorita/porcelain@latest serve
12
- npx @fabiofiorita/porcelain@latest access issue --name "My device"
13
- ```
14
11
 
15
- Porcelain always binds loopback and never `0.0.0.0`. Add `--lan`, `--tailnet`, or
16
- `--cloudflare` only for the route you intend to expose. Every client uses its own one-time pairing
17
- link; the host administrator token is never shared. Manage paired devices on the host:
12
+ # Permit other devices on the local network to connect:
13
+ npx @fabiofiorita/porcelain@latest serve --lan
18
14
 
19
- ```sh
20
- npx @fabiofiorita/porcelain@latest access list
21
- npx @fabiofiorita/porcelain@latest access revoke <id>
22
- npx @fabiofiorita/porcelain@latest share status
15
+ # Install this exact CLI version as a user service:
16
+ npx @fabiofiorita/porcelain@latest service install --lan
23
17
  ```
24
18
 
25
- For exposure flags, browser origins, systemd, updates, and troubleshooting, use the current
26
- [remote-access guide](https://github.com/FabioFiorita/porcelain/blob/main/docs/remote-access.md) and
27
- `porcelain --help` rather than copying a service file from an older package version.
19
+ State lives in `~/.porcelain/`; pass `--data-directory` to keep it somewhere
20
+ else. Use `porcelain serve --help` for all options.
28
21
 
29
- ## Agent connection
22
+ Nothing can reach the server until you pair a device. On the machine running
23
+ it, `porcelain pair "Phone" --address <the address serve printed>` prints a
24
+ link that works once; open it on the device itself. The address must be one
25
+ this server answers at, and the not-paired screen in the browser shows the
26
+ whole command with that device's own origin already filled in. `porcelain devices` lists what is paired and
27
+ `porcelain revoke <id>` ends it. Coding agents connect over MCP with
28
+ `porcelain mcp`, which needs no credential: it reaches a local socket only a
29
+ process on this machine can open.
30
30
 
31
- Install the Porcelain plugin through the agent's plugin manager. Its bundled stdio connector
32
- forwards MCP to the matching daemon's profile-scoped local OS socket; it is not exposed through the
33
- daemon's LAN, Tailscale, Cloudflare, or renderer HTTP routes. Claude Code can install it with:
31
+ ## Background service
34
32
 
35
- ```text
36
- /plugin marketplace add FabioFiorita/porcelain
37
- /plugin install porcelain@porcelain
38
- ```
33
+ `porcelain service install|status|update|uninstall` manages a systemd user
34
+ service on Linux. Install and update persist the
35
+ invoked package version outside the npx cache. Updates stop the service and
36
+ back up SQLite before the new runtime can migrate it. An older CLI refuses to
37
+ replace a newer runtime unless `service update --allow-downgrade` is explicit.
38
+ Uninstall removes the service and runtime while retaining data, configuration,
39
+ logs and database backups. Run service commands as the regular user, never
40
+ with sudo.
39
41
 
40
- ## Repository packaging
42
+ ## Repository development
41
43
 
42
44
  ```sh
43
- pnpm build && pnpm daemon:dist
45
+ pnpm build:package
46
+
47
+ # Inspect the exact publishable archive:
48
+ cd dist-porcelain && npm pack
44
49
  ```
45
50
 
46
- `dist-daemon/` is generated from the completed product build; do not edit it by hand.
51
+ `dist-porcelain/` is generated from a completed web and server build. Do not
52
+ edit it by hand.
package/bin/porcelain.js CHANGED
@@ -1,405 +1,2 @@
1
1
  #!/usr/bin/env node
2
- // Narrow host launcher for the published @fabiofiorita/porcelain package.
3
- // Copied into dist-daemon/bin/ by scripts/build-daemon-dist.mjs — not run from
4
- // the monorepo root (paths resolve relative to the installed package layout).
5
- //
6
- // Goal: t3-style one-liner on a remote box —
7
- // npx @fabiofiorita/porcelain@latest serve --tailnet
8
- // instead of scp'ing a dist tarball and wiring systemd.
9
-
10
- const { randomBytes } = require('node:crypto')
11
- const {
12
- chmodSync,
13
- existsSync,
14
- mkdirSync,
15
- readFileSync,
16
- renameSync,
17
- writeFileSync,
18
- } = require('node:fs')
19
- const { homedir } = require('node:os')
20
- const { dirname, join } = require('node:path')
21
- const { createTRPCUntypedClient, httpLink } = require('@trpc/client')
22
-
23
- const DEFAULT_PORT = 43117
24
- const DEFAULT_USER_DATA = join(homedir(), '.local', 'share', 'porcelain')
25
- // PORCELAIN_HOME redirects token + channels (dev stack uses ~/.porcelain-dev).
26
- const porcelainHome = () => process.env.PORCELAIN_HOME ?? join(homedir(), '.porcelain')
27
- const ADMIN_TOKEN_PATH = () =>
28
- process.env.PORCELAIN_ADMIN_TOKEN_FILE ?? join(porcelainHome(), 'admin-token')
29
-
30
- const HELP = `porcelain — headless Porcelain backend (plain Node, no Electron)
31
-
32
- Usage:
33
- porcelain serve [options]
34
- porcelain [options] (same as serve)
35
- porcelain access issue --name <device> [--base-url <url>]
36
- porcelain access list
37
- porcelain access revoke <id>
38
- porcelain share status
39
-
40
- Options:
41
- --port <n> Listen port for loopback AND LAN/tailnet (default ${DEFAULT_PORT})
42
- --user-data <path> Config dir (default ${DEFAULT_USER_DATA})
43
- --lan Also bind RFC1918 LAN addresses (same --port)
44
- --tailnet Also bind the Tailscale interface (same --port)
45
- --cloudflare Publish loopback over Cloudflare (named tunnel if
46
- PORCELAIN_CLOUDFLARE_TOKEN is set, else a quick tunnel)
47
- --cloudflare-hostname <host>
48
- Public https hostname of a named tunnel
49
- --allowed-origin <origin>
50
- Trust a browser Hub origin for cross-origin API/WS (repeatable)
51
- --no-watchdog Disable stdin parent-death watchdog (required under systemd)
52
- -h, --help Show this help
53
-
54
- Host-management options:
55
- --daemon-url <url> Loopback daemon URL when not using the default port
56
- --base-url <url> Reachable URL to embed in a new connection link
57
-
58
- Examples:
59
- npx @fabiofiorita/porcelain@latest serve --lan
60
- npx @fabiofiorita/porcelain@latest serve --lan --tailnet
61
- npx @fabiofiorita/porcelain@latest serve --lan --cloudflare --cloudflare-hostname review.example.com
62
- npx @fabiofiorita/porcelain@latest access issue --name "My phone"
63
- npx @fabiofiorita/porcelain@latest serve --port 43118 --lan
64
-
65
- Env (same as the raw daemon; flags set these when passed):
66
- PORCELAIN_USER_DATA, PORCELAIN_DAEMON_PORT, PORCELAIN_ADMIN_TOKEN,
67
- PORCELAIN_ALLOWED_ORIGIN (comma-separated), PORCELAIN_ALLOWED_ORIGINS (comma-separated),
68
- PORCELAIN_TAILNET_BIND, PORCELAIN_LAN_BIND, PORCELAIN_CLOUDFLARE_BIND,
69
- PORCELAIN_CLOUDFLARE_HOSTNAME, PORCELAIN_CLOUDFLARE_TOKEN,
70
- PORCELAIN_NO_STDIN_WATCHDOG
71
-
72
- Notes:
73
- • Always binds 127.0.0.1; --tailnet / --lan add private interfaces only
74
- (never 0.0.0.0). Cloudflare uses a separate loopback ingress without MCP.
75
- • Named tunnels: set PORCELAIN_CLOUDFLARE_TOKEN (never a flag) and
76
- --cloudflare-hostname. Without a token, --cloudflare is a quick tunnel
77
- whose URL changes every start.
78
- • --tailnet and --cloudflare are mutually exclusive. LAN can combine with either.
79
- • Host administration lives at ~/.porcelain/admin-token (0600) and is never shared.
80
- • Pairing links are one-time credentials; clients receive individually revocable access.
81
- • Use @latest so each invoke can pick up a newer published package.
82
- • First install compiles node-pty for this host (needs a C toolchain).
83
- `
84
-
85
- function fail(message) {
86
- console.error(`[porcelain] ${message}`)
87
- process.exit(1)
88
- }
89
-
90
- /**
91
- * Minimal argv parse — no deps in the published package beyond the daemon's.
92
- * Unknown flags / missing values exit non-zero with a short message.
93
- */
94
- function parseArgs(argv) {
95
- const opts = {
96
- command: 'serve',
97
- port: DEFAULT_PORT,
98
- userData: DEFAULT_USER_DATA,
99
- tailnet: false,
100
- lan: false,
101
- cloudflare: false,
102
- cloudflareHostname: null,
103
- noWatchdog: false,
104
- allowedOrigins: [],
105
- help: false,
106
- }
107
-
108
- let i = 0
109
- // Bare invocation and explicit `serve` both mean serve (t3-compatible).
110
- if (argv[0] === 'serve') {
111
- i = 1
112
- } else if (argv[0] === 'help' || argv[0] === '--help' || argv[0] === '-h') {
113
- opts.help = true
114
- return opts
115
- }
116
-
117
- while (i < argv.length) {
118
- const arg = argv[i]
119
- if (arg === '-h' || arg === '--help') {
120
- opts.help = true
121
- i += 1
122
- continue
123
- }
124
- if (arg === '--tailnet') {
125
- opts.tailnet = true
126
- i += 1
127
- continue
128
- }
129
- if (arg === '--lan') {
130
- opts.lan = true
131
- i += 1
132
- continue
133
- }
134
- if (arg === '--cloudflare') {
135
- opts.cloudflare = true
136
- i += 1
137
- continue
138
- }
139
- if (arg === '--cloudflare-hostname') {
140
- const raw = argv[i + 1]
141
- if (raw === undefined) fail('--cloudflare-hostname requires a hostname')
142
- opts.cloudflareHostname = raw
143
- opts.cloudflare = true
144
- i += 2
145
- continue
146
- }
147
- if (arg === '--funnel') {
148
- fail('Tailscale Funnel was removed. Use --cloudflare for public HTTPS.')
149
- }
150
- if (arg === '--no-watchdog') {
151
- opts.noWatchdog = true
152
- i += 1
153
- continue
154
- }
155
- if (arg === '--allowed-origin' || arg === '--allowed-origins') {
156
- const raw = argv[i + 1]
157
- if (raw === undefined) fail(`${arg} requires a value`)
158
- opts.allowedOrigins.push(raw)
159
- i += 2
160
- continue
161
- }
162
- if (arg === '--port') {
163
- const raw = argv[i + 1]
164
- if (raw === undefined) fail('--port requires a value')
165
- const port = Number(raw)
166
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
167
- fail(`--port must be an integer 1–65535 (got ${raw})`)
168
- }
169
- opts.port = port
170
- i += 2
171
- continue
172
- }
173
- if (arg === '--user-data') {
174
- const raw = argv[i + 1]
175
- if (raw === undefined) fail('--user-data requires a path')
176
- opts.userData = raw
177
- i += 2
178
- continue
179
- }
180
- fail(`unknown argument: ${arg}\n\n${HELP}`)
181
- }
182
-
183
- return opts
184
- }
185
-
186
- /** Same semantics as apps/daemon/src/net/admin-token.ts (plain CJS copy). */
187
- function ensureAdminToken(path = ADMIN_TOKEN_PATH()) {
188
- try {
189
- const existing = readFileSync(path, 'utf8').trim()
190
- if (existing !== '') {
191
- chmodSync(path, 0o600)
192
- return existing
193
- }
194
- } catch {
195
- // absent — mint
196
- }
197
- const token = randomBytes(32).toString('hex')
198
- mkdirSync(dirname(path), { recursive: true })
199
- const tmp = `${path}.tmp`
200
- writeFileSync(tmp, token, { encoding: 'utf8', mode: 0o600 })
201
- renameSync(tmp, path)
202
- return token
203
- }
204
-
205
- // The built contracts module (scripts/build-node.mjs), in the two layouts this script
206
- // ships in: the installed package (bin/ beside main/) and the monorepo checkout (scripts/
207
- // beside apps/desktop/out/main/). Same relative-resolution mechanism the serve path already
208
- // uses for main/daemon/server.js.
209
- const PROTOCOL_MODULES = [
210
- join(__dirname, '..', 'main', 'contracts', 'protocol.js'),
211
- join(__dirname, '..', 'apps', 'desktop', 'out', 'main', 'contracts', 'protocol.js'),
212
- ]
213
-
214
- /**
215
- * The daemon refuses any request that does not announce its exact wire protocol, so this
216
- * CLI announces it too — read from the built contracts, never a literal copied here, which
217
- * would silently become a lie the day the protocol moves.
218
- */
219
- function protocolHeaders() {
220
- const modulePath = PROTOCOL_MODULES.find((candidate) => existsSync(candidate))
221
- if (modulePath === undefined) {
222
- fail('protocol contracts missing — run `pnpm build` in the monorepo, or reinstall the package')
223
- }
224
- const { PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER } = require(modulePath)
225
- return { [PROTOCOL_VERSION_HEADER]: String(PROTOCOL_VERSION) }
226
- }
227
-
228
- function adminClient(daemonUrl, token) {
229
- return createTRPCUntypedClient({
230
- links: [
231
- httpLink({
232
- url: `${daemonUrl}/trpc`,
233
- headers: { authorization: `Bearer ${token}`, ...protocolHeaders() },
234
- }),
235
- ],
236
- })
237
- }
238
-
239
- function adminCommandOptions(argv) {
240
- const result = {
241
- args: [],
242
- daemonUrl: `http://127.0.0.1:${process.env.PORCELAIN_DAEMON_PORT || DEFAULT_PORT}`,
243
- baseUrl: null,
244
- name: null,
245
- }
246
- for (let i = 0; i < argv.length; i += 1) {
247
- const arg = argv[i]
248
- if (arg === '--daemon-url' || arg === '--base-url' || arg === '--name') {
249
- const value = argv[i + 1]
250
- if (!value) fail(`${arg} requires a value`)
251
- if (arg === '--daemon-url') result.daemonUrl = value.replace(/\/+$/, '')
252
- else if (arg === '--base-url') result.baseUrl = value
253
- else result.name = value
254
- i += 1
255
- } else {
256
- result.args.push(arg)
257
- }
258
- }
259
- return result
260
- }
261
-
262
- async function suggestedBaseUrl(client) {
263
- const lan = await client.query('lanStatus').catch(() => null)
264
- if (lan?.numericUrl || lan?.url) return lan.numericUrl || lan.url
265
- const tailnet = await client.query('tailnetStatus').catch(() => null)
266
- if (tailnet?.url) return tailnet.url
267
- const cloudflare = await client.query('cloudflareStatus').catch(() => null)
268
- return cloudflare?.url || null
269
- }
270
-
271
- async function runAccessCommand(argv) {
272
- const options = adminCommandOptions(argv)
273
- const [action, id] = options.args
274
- const client = adminClient(options.daemonUrl, ensureAdminToken())
275
- if (action === 'issue') {
276
- if (!options.name) fail('access issue requires --name <device>')
277
- const baseUrl = options.baseUrl || (await suggestedBaseUrl(client))
278
- if (!baseUrl) fail('no reachable endpoint is enabled; pass --base-url explicitly')
279
- const result = await client.mutation('issuePairingLink', {
280
- label: options.name,
281
- baseUrl,
282
- })
283
- process.stdout.write(`${result.url}\n`)
284
- return
285
- }
286
- if (action === 'list') {
287
- const status = await client.query('accessStatus')
288
- process.stdout.write(`${JSON.stringify(status, null, 2)}\n`)
289
- return
290
- }
291
- if (action === 'revoke' && id) {
292
- const status = await client.query('accessStatus')
293
- if (status.clients.some((entry) => entry.id === id)) {
294
- await client.mutation('revokeAuthorizedClient', id)
295
- } else if (status.pairings.some((entry) => entry.id === id)) {
296
- await client.mutation('revokePairingLink', id)
297
- } else {
298
- fail(`no device or pairing link has id ${id}`)
299
- }
300
- process.stdout.write(`Revoked ${id}\n`)
301
- return
302
- }
303
- fail('usage: porcelain access issue --name <device> | list | revoke <id>')
304
- }
305
-
306
- async function runShareCommand(argv) {
307
- const options = adminCommandOptions(argv)
308
- const [target] = options.args
309
- const client = adminClient(options.daemonUrl, ensureAdminToken())
310
- if (target === 'status') {
311
- const [lan, tailnet, cloudflare] = await Promise.all([
312
- client.query('lanStatus'),
313
- client.query('tailnetStatus'),
314
- client.query('cloudflareStatus'),
315
- ])
316
- process.stdout.write(`${JSON.stringify({ lan, tailnet, cloudflare }, null, 2)}\n`)
317
- return
318
- }
319
- fail('usage: porcelain share status')
320
- }
321
-
322
- async function main() {
323
- const argv = process.argv.slice(2)
324
- if (argv[0] === 'access') {
325
- await runAccessCommand(argv.slice(1))
326
- return
327
- }
328
- if (argv[0] === 'share') {
329
- await runShareCommand(argv.slice(1))
330
- return
331
- }
332
- const opts = parseArgs(argv)
333
- if (opts.help) {
334
- process.stdout.write(HELP)
335
- process.exit(0)
336
- }
337
-
338
- // Prefer an already-set env (systemd unit, shell export) over flag defaults.
339
- if (!process.env.PORCELAIN_USER_DATA) {
340
- process.env.PORCELAIN_USER_DATA = opts.userData
341
- }
342
- if (!process.env.PORCELAIN_DAEMON_PORT) {
343
- process.env.PORCELAIN_DAEMON_PORT = String(opts.port)
344
- }
345
- // Preserve an explicitly supplied service environment. A flag supplies the same
346
- // comma-separated format as PORCELAIN_ALLOWED_ORIGIN and is repeated for several Hubs.
347
- if (
348
- opts.allowedOrigins.length > 0 &&
349
- !process.env.PORCELAIN_ALLOWED_ORIGIN &&
350
- !process.env.PORCELAIN_ALLOWED_ORIGINS
351
- ) {
352
- process.env.PORCELAIN_ALLOWED_ORIGIN = opts.allowedOrigins.join(',')
353
- }
354
- if (opts.tailnet && opts.cloudflare) {
355
- fail('--tailnet and --cloudflare cannot be used together. Pick one off-network route.')
356
- }
357
- if (opts.tailnet) process.env.PORCELAIN_TAILNET_BIND = '1'
358
- if (opts.lan) process.env.PORCELAIN_LAN_BIND = '1'
359
- if (opts.cloudflare) process.env.PORCELAIN_CLOUDFLARE_BIND = '1'
360
- if (opts.cloudflareHostname && !process.env.PORCELAIN_CLOUDFLARE_HOSTNAME) {
361
- process.env.PORCELAIN_CLOUDFLARE_HOSTNAME = opts.cloudflareHostname
362
- }
363
- if (opts.noWatchdog) process.env.PORCELAIN_NO_STDIN_WATCHDOG = '1'
364
-
365
- const token = process.env.PORCELAIN_ADMIN_TOKEN || ensureAdminToken()
366
- process.env.PORCELAIN_ADMIN_TOKEN = token
367
-
368
- const userData = process.env.PORCELAIN_USER_DATA
369
- const port = process.env.PORCELAIN_DAEMON_PORT
370
- const binds = ['127.0.0.1']
371
- if (process.env.PORCELAIN_TAILNET_BIND === '1') binds.push('tailnet')
372
- if (process.env.PORCELAIN_LAN_BIND === '1') binds.push('lan')
373
- if (process.env.PORCELAIN_CLOUDFLARE_BIND === '1') binds.push('cloudflare')
374
- const allowedOriginValue =
375
- process.env.PORCELAIN_ALLOWED_ORIGINS || process.env.PORCELAIN_ALLOWED_ORIGIN || ''
376
- const allowedOriginCount = allowedOriginValue
377
- .split(',')
378
- .map((origin) => origin.trim())
379
- .filter(Boolean).length
380
-
381
- // Human-facing status on stderr; the daemon still owns the one stdout port line.
382
- console.error(`[porcelain] user data ${userData}`)
383
- console.error(`[porcelain] port ${port}`)
384
- console.error(`[porcelain] binds ${binds.join(', ')}`)
385
- console.error(
386
- `[porcelain] cors ${allowedOriginCount === 0 ? 'same-origin only' : `${allowedOriginCount} trusted Hub origin(s) configured`}`,
387
- )
388
- console.error(`[porcelain] admin file ${ADMIN_TOKEN_PATH()}`)
389
- console.error('[porcelain] pair with: porcelain access issue --name <device>')
390
- console.error('[porcelain] starting… Ctrl+C to stop')
391
-
392
- const serverEntry = [
393
- join(__dirname, '..', 'main', 'daemon', 'server.js'),
394
- // Monorepo fallback used by the composed proof; the published package uses
395
- // the first layout above after `daemon:dist` assembles it.
396
- join(__dirname, '..', 'apps', 'desktop', 'out', 'main', 'daemon', 'server.js'),
397
- ].find((candidate) => existsSync(candidate))
398
- if (serverEntry === undefined) {
399
- fail('daemon entry missing — package is corrupt; reinstall')
400
- }
401
- // Side-effect entry: boots the HTTP/WS listeners (same as `node main/daemon/server.js`).
402
- require(serverEntry)
403
- }
404
-
405
- main().catch((error) => fail(error instanceof Error ? error.message : String(error)))
2
+ import '../server/src/cli/main.mjs';
package/package.json CHANGED
@@ -1,42 +1,36 @@
1
1
  {
2
2
  "name": "@fabiofiorita/porcelain",
3
- "version": "0.63.4",
4
- "description": "Headless Porcelain daemon — plain Node backend for remote machines (npx @fabiofiorita/porcelain@latest serve)",
3
+ "version": "0.65.0",
4
+ "description": "Review coding agents’ changes from a browser, Mac or phone",
5
5
  "license": "MIT",
6
- "author": "Fabio Fiorita <fabiolfp@gmail.com>",
7
6
  "repository": {
8
7
  "type": "git",
9
8
  "url": "git+https://github.com/FabioFiorita/porcelain.git"
10
9
  },
11
10
  "homepage": "https://github.com/FabioFiorita/porcelain",
11
+ "type": "module",
12
12
  "engines": {
13
- "node": ">=22"
13
+ "node": ">=24.20.0 <25"
14
14
  },
15
15
  "bin": {
16
16
  "porcelain": "bin/porcelain.js"
17
17
  },
18
18
  "files": [
19
19
  "bin",
20
- "main",
21
- "renderer",
22
- "README.md"
20
+ "server",
21
+ "web",
22
+ "README.md",
23
+ "LICENSE"
23
24
  ],
24
25
  "dependencies": {
25
- "@trpc/client": "^11.17.0",
26
- "@trpc/server": "^11.17.0",
27
- "node-pty": "^1.1.0",
28
- "trash": "^10.1.1",
29
- "ws": "^8.21.0",
30
- "zod": "^4.4.3"
26
+ "@effect/platform-node": "4.0.1",
27
+ "@parcel/watcher": "2.6.0",
28
+ "effect": "4.0.1",
29
+ "qrcode-terminal": "0.12.0",
30
+ "trash": "10.1.1",
31
+ "ws": "8.21.3"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"
34
- },
35
- "keywords": [
36
- "porcelain",
37
- "daemon",
38
- "code-review",
39
- "remote",
40
- "tailscale"
41
- ]
35
+ }
42
36
  }
@@ -0,0 +1,98 @@
1
+ CREATE TABLE `artifacts` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `worktree_id` text NOT NULL,
4
+ `name` text NOT NULL,
5
+ `content` text NOT NULL,
6
+ `size_bytes` integer NOT NULL,
7
+ `created_at` text NOT NULL
8
+ );
9
+ --> statement-breakpoint
10
+ CREATE INDEX `artifacts_worktree` ON `artifacts` (`worktree_id`);--> statement-breakpoint
11
+ CREATE TABLE `comment_threads` (
12
+ `sequence` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
13
+ `id` text NOT NULL,
14
+ `worktree_id` text NOT NULL,
15
+ `data` text NOT NULL
16
+ );
17
+ --> statement-breakpoint
18
+ CREATE UNIQUE INDEX `comment_threads_id_unique` ON `comment_threads` (`id`);--> statement-breakpoint
19
+ CREATE TABLE `commit_review_layer_sets` (
20
+ `project_id` text NOT NULL,
21
+ `commit_oid` text NOT NULL,
22
+ `data` text NOT NULL,
23
+ PRIMARY KEY(`project_id`, `commit_oid`),
24
+ FOREIGN KEY (`project_id`) REFERENCES `inventory_projects`(`id`) ON UPDATE no action ON DELETE cascade
25
+ );
26
+ --> statement-breakpoint
27
+ CREATE TABLE `environment` (
28
+ `singleton` integer PRIMARY KEY NOT NULL,
29
+ `id` text NOT NULL,
30
+ CONSTRAINT "environment_singleton" CHECK("environment"."singleton" = 1)
31
+ );
32
+ --> statement-breakpoint
33
+ CREATE TABLE `project_file_preferences` (
34
+ `project_id` text NOT NULL,
35
+ `path` text NOT NULL,
36
+ `pinned` integer NOT NULL,
37
+ `hidden` integer NOT NULL,
38
+ PRIMARY KEY(`project_id`, `path`),
39
+ CONSTRAINT "project_preference_pinned" CHECK("project_file_preferences"."pinned" IN (0, 1)),
40
+ CONSTRAINT "project_preference_hidden" CHECK("project_file_preferences"."hidden" IN (0, 1))
41
+ );
42
+ --> statement-breakpoint
43
+ CREATE TABLE `git_action_blocks` (
44
+ `project_id` text PRIMARY KEY NOT NULL
45
+ );
46
+ --> statement-breakpoint
47
+ CREATE TABLE `git_action_preparations` (
48
+ `id` text PRIMARY KEY NOT NULL,
49
+ `value` text NOT NULL,
50
+ `consumed` integer DEFAULT false NOT NULL
51
+ );
52
+ --> statement-breakpoint
53
+ CREATE TABLE `git_action_receipts` (
54
+ `request_id` text PRIMARY KEY NOT NULL,
55
+ `value` text NOT NULL
56
+ );
57
+ --> statement-breakpoint
58
+ CREATE TABLE `project_worktrees` (
59
+ `worktree_id` text PRIMARY KEY NOT NULL,
60
+ `project_id` text NOT NULL,
61
+ FOREIGN KEY (`project_id`) REFERENCES `inventory_projects`(`id`) ON UPDATE no action ON DELETE cascade
62
+ );
63
+ --> statement-breakpoint
64
+ CREATE INDEX `project_worktrees_project` ON `project_worktrees` (`project_id`);--> statement-breakpoint
65
+ CREATE TABLE `inventory_projects` (
66
+ `id` text PRIMARY KEY NOT NULL,
67
+ `name` text NOT NULL,
68
+ `common_directory` text NOT NULL,
69
+ `repository_identity` text NOT NULL,
70
+ `available` integer NOT NULL,
71
+ `position` integer NOT NULL,
72
+ CONSTRAINT "project_id_present" CHECK("inventory_projects"."id" IS NOT NULL),
73
+ CONSTRAINT "project_available" CHECK("inventory_projects"."available" IN (0, 1))
74
+ );
75
+ --> statement-breakpoint
76
+ CREATE UNIQUE INDEX `inventory_projects_repository_identity_unique` ON `inventory_projects` (`repository_identity`);--> statement-breakpoint
77
+ CREATE TABLE `review_layer_sets` (
78
+ `worktree_id` text PRIMARY KEY NOT NULL,
79
+ `revision` integer NOT NULL,
80
+ `layers` text NOT NULL
81
+ );
82
+ --> statement-breakpoint
83
+ CREATE TABLE `worktrees` (
84
+ `id` text PRIMARY KEY NOT NULL,
85
+ `project_id` text NOT NULL,
86
+ `path` text NOT NULL,
87
+ `metadata_identity` text,
88
+ `main` integer NOT NULL,
89
+ `branch` text,
90
+ `available` integer NOT NULL,
91
+ `position` integer NOT NULL,
92
+ FOREIGN KEY (`project_id`) REFERENCES `inventory_projects`(`id`) ON UPDATE no action ON DELETE cascade,
93
+ CONSTRAINT "worktree_id_present" CHECK("worktrees"."id" IS NOT NULL),
94
+ CONSTRAINT "worktree_main" CHECK("worktrees"."main" IN (0, 1)),
95
+ CONSTRAINT "worktree_available" CHECK("worktrees"."available" IN (0, 1))
96
+ );
97
+ --> statement-breakpoint
98
+ CREATE UNIQUE INDEX `worktree_order` ON `worktrees` (`project_id`,`position`);
@@ -0,0 +1,9 @@
1
+ CREATE TABLE `reviewed_files` (
2
+ `worktree_id` text NOT NULL,
3
+ `path` text NOT NULL,
4
+ `fingerprint` text NOT NULL,
5
+ `reviewed_at` text NOT NULL,
6
+ PRIMARY KEY(`worktree_id`, `path`)
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE INDEX `reviewed_files_worktree` ON `reviewed_files` (`worktree_id`);