@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.
- package/LICENSE +21 -0
- package/README.md +34 -28
- package/bin/porcelain.js +1 -404
- package/package.json +15 -21
- package/server/drizzle/0000_current-schema.sql +98 -0
- package/server/drizzle/0001_light_iceman.sql +9 -0
- package/server/drizzle/0002_backfill_comment_authors.sql +25 -0
- package/server/drizzle/0003_pairing_and_devices.sql +23 -0
- package/server/drizzle/0004_worktrees_from_git.sql +146 -0
- package/server/drizzle/0005_names_and_status.sql +31 -0
- package/server/drizzle/0006_stale_reviewed_files.sql +1 -0
- package/server/drizzle/0007_normalize_comment_messages.sql +66 -0
- package/server/drizzle/0008_published_reviews.sql +30 -0
- package/server/drizzle/0009_drop_unused_git_action_tables.sql +5 -0
- package/server/drizzle/0010_cascade_deletes_through_foreign_keys.sql +207 -0
- package/server/drizzle/0011_receipts_per_worktree.sql +35 -0
- package/server/drizzle/0012_receipts_as_columns.sql +68 -0
- package/server/drizzle/0013_monotonic_comment_revision.sql +21 -0
- package/server/drizzle/0014_reviewed_layers_without_stale.sql +1 -0
- package/server/drizzle/0015_remote_access.sql +9 -0
- package/server/drizzle/0016_reviewed_file_scopes.sql +17 -0
- package/server/drizzle/0017_comment_edits.sql +1 -0
- package/server/drizzle/0018_reviewed_file_branches.sql +18 -0
- package/server/drizzle/0019_review_proof.sql +10 -0
- package/server/drizzle/0020_environment_name.sql +5 -0
- package/server/drizzle/0021_device_routes.sql +35 -0
- package/server/drizzle/0022_lan_network.sql +18 -0
- package/server/drizzle/0023_tailnet_serve_target.sql +1 -0
- package/server/drizzle/0024_lan_router.sql +2 -0
- package/server/drizzle/0025_tailnet_hostname.sql +23 -0
- package/server/drizzle/0026_drop_removed_branch_receipts.sql +1 -0
- package/server/drizzle/0027_trusted_devices.sql +2 -0
- package/server/drizzle/meta/_journal.json +202 -0
- package/server/src/cli/main.mjs +32293 -0
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/_paired-B3CMUVff.js +1 -0
- package/web/dist/assets/_paired-B8xCvrtN.js +1 -0
- package/web/dist/assets/_paired-DqFYC4oC.css +1 -0
- package/web/dist/assets/_paired-YiNqIqrL.js +1 -0
- package/web/dist/assets/_paired-rl3uQlkr.js +1 -0
- package/web/dist/assets/_section-DJ3o28t3.js +1 -0
- package/web/dist/assets/_worktreeId-B3CMUVff.js +1 -0
- package/web/dist/assets/_worktreeId-B8xCvrtN.js +1 -0
- package/web/dist/assets/_worktreeId-DS7C0JfB.js +1 -0
- package/web/dist/assets/_worktreeId-DzWffY3l.js +1 -0
- package/web/dist/assets/abap-CLvhMVsD.js +1 -0
- package/web/dist/assets/access-yiNkcsHW.js +1 -0
- package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
- package/web/dist/assets/ada-C5qYipkI.js +1 -0
- package/web/dist/assets/ahk-CA9gdOdF.js +1 -0
- package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
- package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
- package/web/dist/assets/angular-html-B8mrqUL5.js +1 -0
- package/web/dist/assets/angular-ts-Bzix210W.js +1 -0
- package/web/dist/assets/apache-U0d_L8uA.js +1 -0
- package/web/dist/assets/apex-KFGFyKwG.js +1 -0
- package/web/dist/assets/apl-Dhin1L29.js +1 -0
- package/web/dist/assets/applescript-CCn79oCD.js +1 -0
- package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
- package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
- package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
- package/web/dist/assets/astro-B-4NNgOP.js +1 -0
- package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
- package/web/dist/assets/awk-BWXHIvNe.js +1 -0
- package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
- package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
- package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
- package/web/dist/assets/beancount-D-usSTwE.js +1 -0
- package/web/dist/assets/berry-DKpUyyne.js +1 -0
- package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
- package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
- package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
- package/web/dist/assets/blade-C6Gedziy.js +1 -0
- package/web/dist/assets/bsl-_VjlB9mv.js +1 -0
- package/web/dist/assets/c-iCihek07.js +1 -0
- package/web/dist/assets/c3-D0apG41V.js +1 -0
- package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
- package/web/dist/assets/cairo-DLTphjLi.js +1 -0
- package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/web/dist/assets/chapel-DmtgEXO_.js +1 -0
- package/web/dist/assets/clarity-CtfBBspg.js +1 -0
- package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
- package/web/dist/assets/cloneFileDiffMetadata-zVRGqYgv.js +1 -0
- package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
- package/web/dist/assets/cobol-Cw2-KvLW.js +1 -0
- package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
- package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
- package/web/dist/assets/coffee-CiiSLhB6.js +1 -0
- package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/web/dist/assets/connected-workspace-HwRJynH2.js +1 -0
- package/web/dist/assets/coq-DuBRPxYg.js +1 -0
- package/web/dist/assets/cpp-71xkOQct.js +1 -0
- package/web/dist/assets/crystal-DGPAtLeY.js +1 -0
- package/web/dist/assets/csharp-oqKa8noW.js +1 -0
- package/web/dist/assets/css-CNuuLpMC.js +1 -0
- package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
- package/web/dist/assets/cue-CE9AQfxI.js +1 -0
- package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
- package/web/dist/assets/d-qD-0Kul2.js +1 -0
- package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
- package/web/dist/assets/dart-CnvKMtbv.js +1 -0
- package/web/dist/assets/dax-BkyTk9wS.js +1 -0
- package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
- package/web/dist/assets/desktop-UhAM-aem.js +14 -0
- package/web/dist/assets/desktop-actions-_GPqaBVU.js +12 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/docker-IyjqRm3v.js +1 -0
- package/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
- package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
- package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
- package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/web/dist/assets/edge-DCY0a-ow.js +1 -0
- package/web/dist/assets/edit-file-DOZdUUk9.js +1568 -0
- package/web/dist/assets/elixir-DP304Xio.js +1 -0
- package/web/dist/assets/elm-o4NJxfws.js +1 -0
- package/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
- package/web/dist/assets/erb-C7MPl934.js +1 -0
- package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
- package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
- package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
- package/web/dist/assets/file-editor-DvHSzrST.js +588 -0
- package/web/dist/assets/files-DGG-rGAk.js +4 -0
- package/web/dist/assets/fish-BJitypiv.js +1 -0
- package/web/dist/assets/fluent-C03EYrpw.js +1 -0
- package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
- package/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
- package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
- package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
- package/web/dist/assets/genie-CV2tkWYe.js +1 -0
- package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
- package/web/dist/assets/git-actions-PSyqF5Ms.js +1345 -0
- package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
- package/web/dist/assets/git-rebase-BDPpS8w6.js +1 -0
- package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
- package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
- package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
- package/web/dist/assets/glimmer-js-BONyzAsu.js +1 -0
- package/web/dist/assets/glimmer-ts-f9c0GCOe.js +1 -0
- package/web/dist/assets/glsl-D5MA2a_X.js +1 -0
- package/web/dist/assets/gn-ilITqXS6.js +1 -0
- package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/graphql-Ctjuxyvl.js +1 -0
- package/web/dist/assets/groovy-CacY0gHj.js +1 -0
- package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/web/dist/assets/hack-Dh_ZLRbs.js +1 -0
- package/web/dist/assets/haml-CwEpbK1d.js +1 -0
- package/web/dist/assets/handlebars-ryMP1rBR.js +1 -0
- package/web/dist/assets/haskell-D8IpX4py.js +1 -0
- package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
- package/web/dist/assets/hcl-Dh228itO.js +1 -0
- package/web/dist/assets/hjson-CxZEssPk.js +1 -0
- package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
- package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
- package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/web/dist/assets/houston-CsvMBhTu.js +1 -0
- package/web/dist/assets/html-XwrE-1r6.js +1 -0
- package/web/dist/assets/html-derivative-i4STDFnl.js +1 -0
- package/web/dist/assets/http-kmH8AyfE.js +1 -0
- package/web/dist/assets/hurl-DCZHXKU4.js +1 -0
- package/web/dist/assets/hxml-Dcm88srW.js +1 -0
- package/web/dist/assets/hy-CZbG8q4J.js +1 -0
- package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
- package/web/dist/assets/index-Cia9YleJ.js +10 -0
- package/web/dist/assets/index-DUPxq0qw.css +2 -0
- package/web/dist/assets/ini-B5eOa1yu.js +1 -0
- package/web/dist/assets/java-DdHNQ9hk.js +1 -0
- package/web/dist/assets/javascript-obGGvMPs.js +1 -0
- package/web/dist/assets/jinja-B-tLrNsM.js +1 -0
- package/web/dist/assets/jison-CBjhYtSs.js +1 -0
- package/web/dist/assets/json-DAgxKhMr.js +1 -0
- package/web/dist/assets/json5-BR5RXkoi.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
- package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
- package/web/dist/assets/jsx-gTgmArGx.js +1 -0
- package/web/dist/assets/julia-CXJVy74_.js +1 -0
- package/web/dist/assets/just-B9Xn_Dp4.js +1 -0
- package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
- package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
- package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
- package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
- package/web/dist/assets/latex-o9DRzaEe.js +1 -0
- package/web/dist/assets/lazyRouteComponent-C0cxtC0x.js +1 -0
- package/web/dist/assets/lean-CewbzKMR.js +1 -0
- package/web/dist/assets/less-DVTAwKKz.js +1 -0
- package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
- package/web/dist/assets/link-Cr7ETqcH.js +1 -0
- package/web/dist/assets/liquid-G0MH4h_m.js +1 -0
- package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
- package/web/dist/assets/log-BNLmms1o.js +1 -0
- package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
- package/web/dist/assets/lua-FONpmTUP.js +1 -0
- package/web/dist/assets/luau-C2pBCSgt.js +1 -0
- package/web/dist/assets/make-Dixweg8N.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/marko-DV5GrDmo.js +1 -0
- package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
- package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
- package/web/dist/assets/mdc-BKfPFPZw.js +1 -0
- package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
- package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
- package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
- package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
- package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
- package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
- package/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
- package/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
- package/web/dist/assets/move-BYnGhZoM.js +1 -0
- package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
- package/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
- package/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
- package/web/dist/assets/nginx-BTMw9COX.js +1 -0
- package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
- package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/web/dist/assets/nim-B6yJ_yaC.js +1 -0
- package/web/dist/assets/nix-IvuFDN5E.js +1 -0
- package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
- package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
- package/web/dist/assets/nushell-DHYfO66s.js +1 -0
- package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
- package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
- package/web/dist/assets/odin-B1RWQWA5.js +1 -0
- package/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
- package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
- package/web/dist/assets/org-Ddi8Gs6E.js +1 -0
- package/web/dist/assets/pair-B3CMUVff.js +1 -0
- package/web/dist/assets/pair-pADUBfPY.js +1 -0
- package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
- package/web/dist/assets/perl-DEgXgjuC.js +1 -0
- package/web/dist/assets/php-B9UPq-vz.js +1 -0
- package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
- package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
- package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
- package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
- package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
- package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
- package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
- package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
- package/web/dist/assets/po-BiJDBrnU.js +1 -0
- package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
- package/web/dist/assets/polar-C7UOKdEL.js +1 -0
- package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
- package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
- package/web/dist/assets/powershell-CCSX75yM.js +1 -0
- package/web/dist/assets/preferences-gCf_-ng1.js +1 -0
- package/web/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
- package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
- package/web/dist/assets/proto-DB4EqR-F.js +1 -0
- package/web/dist/assets/pug-DkgDHZci.js +1 -0
- package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
- package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/qml-_a-QhfPD.js +1 -0
- package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
- package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
- package/web/dist/assets/r-BHcO8Vjq.js +1 -0
- package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
- package/web/dist/assets/raku-B3gFvitq.js +1 -0
- package/web/dist/assets/razor-BMJ0eI3p.js +1 -0
- package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
- package/web/dist/assets/red-DX1BQOs8.js +1 -0
- package/web/dist/assets/reg-CRGYupPL.js +1 -0
- package/web/dist/assets/regexp-DR20uMnK.js +1 -0
- package/web/dist/assets/rel-BtDbiS_P.js +1 -0
- package/web/dist/assets/review-diagram-CK76VybL.js +7 -0
- package/web/dist/assets/review-diagram-DLioOiRN.css +1 -0
- package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
- package/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/web/dist/assets/ron-VUp2lXgN.js +1 -0
- package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
- package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
- package/web/dist/assets/rst-BQCbfb9V.js +1 -0
- package/web/dist/assets/ruby-B8JcIHv0.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/sas-CBpMWA2M.js +1 -0
- package/web/dist/assets/sass-DXrisJhu.js +1 -0
- package/web/dist/assets/scala-BiFt_7mu.js +1 -0
- package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
- package/web/dist/assets/scss-YesllqfO.js +1 -0
- package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
- package/web/dist/assets/search-YI2tNMg7.js +1 -0
- package/web/dist/assets/server-CO1nBw31.js +1 -0
- package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
- package/web/dist/assets/shellscript-Da78Ox-3.js +1 -0
- package/web/dist/assets/shellsession-CVsQGYfZ.js +1 -0
- package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
- package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
- package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
- package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
- package/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
- package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
- package/web/dist/assets/soy-DEd-qcad.js +1 -0
- package/web/dist/assets/sparql-D_iOobhT.js +1 -0
- package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
- package/web/dist/assets/sql-DdD7vxS4.js +1 -0
- package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
- package/web/dist/assets/stata-CMv7m7Gi.js +1 -0
- package/web/dist/assets/stylus-B6D30XZt.js +1 -0
- package/web/dist/assets/surrealql-BZoq2lXR.js +1 -0
- package/web/dist/assets/svelte-Bj3Hmc9F.js +1 -0
- package/web/dist/assets/swift-CyEgAFGc.js +1 -0
- package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
- package/web/dist/assets/systemd-BxMlprV5.js +1 -0
- package/web/dist/assets/talonscript-CohzipZa.js +1 -0
- package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
- package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
- package/web/dist/assets/templ-TjxbArHn.js +1 -0
- package/web/dist/assets/terraform-DswuEJGm.js +1 -0
- package/web/dist/assets/tex-BK-H2Wn4.js +1 -0
- package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/ts-tags-Cc5do7zu.js +1 -0
- package/web/dist/assets/tsv-sltzmVWM.js +1 -0
- package/web/dist/assets/tsx-CUOciP0b.js +1 -0
- package/web/dist/assets/turtle-ByJddavk.js +1 -0
- package/web/dist/assets/twig-BVSnuTf-.js +1 -0
- package/web/dist/assets/typescript-BydbNFcO.js +1 -0
- package/web/dist/assets/typespec-BRdr0IET.js +1 -0
- package/web/dist/assets/typst-C40W_gC6.js +1 -0
- package/web/dist/assets/useRouterState-DpORzQ8V.js +1 -0
- package/web/dist/assets/v-DBj4K9d_.js +1 -0
- package/web/dist/assets/vala-zf12oZj6.js +1 -0
- package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
- package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
- package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
- package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
- package/web/dist/assets/viml-DvXPmvsu.js +1 -0
- package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
- package/web/dist/assets/vue-BNQF7r6F.js +1 -0
- package/web/dist/assets/vue-html-DkKkQnZL.js +1 -0
- package/web/dist/assets/vue-vine-DFaKYU4X.js +1 -0
- package/web/dist/assets/vyper-CgoNMtux.js +1 -0
- package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
- package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
- package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
- package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
- package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
- package/web/dist/assets/wit-DdvCle-K.js +1 -0
- package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
- package/web/dist/assets/worker-Bs0l0ezO.js +162 -0
- package/web/dist/assets/workspace-error-DILP6VTh.js +1 -0
- package/web/dist/assets/workspace-pending-6RKmflS9.js +1 -0
- package/web/dist/assets/xml-BlnrEAEy.js +1 -0
- package/web/dist/assets/xsl-BOPS8gLt.js +1 -0
- package/web/dist/assets/yaml-Du_6cdrH.js +1 -0
- package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
- package/web/dist/assets/zig-CMLA9XwU.js +1 -0
- package/web/dist/favicon-32.png +0 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/icon-192.png +0 -0
- package/web/dist/icon-512.png +0 -0
- package/web/dist/index.html +33 -0
- package/web/dist/manifest.webmanifest +24 -0
- package/main/contracts/protocol.js +0 -36
- package/main/daemon/server.js +0 -17067
- package/renderer/apple-touch-icon.png +0 -0
- package/renderer/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/renderer/assets/browser-DjHhN4s0.js +0 -8
- package/renderer/assets/canvas-view-DhzIJ8ZV.js +0 -1
- package/renderer/assets/changeset-view-yBQtdgrZ.js +0 -1
- package/renderer/assets/commit-view-Cz3IR74x.js +0 -2
- package/renderer/assets/diff-mode-toggle-DTeqhuLC.js +0 -1
- package/renderer/assets/diff-view-DqanYaSe.js +0 -1
- package/renderer/assets/file-content-BX3OYiQC.js +0 -5
- package/renderer/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/renderer/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/renderer/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/renderer/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/renderer/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/renderer/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/renderer/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/renderer/assets/ghostty-write-pty-CVPirtOQ.wasm +0 -0
- package/renderer/assets/html-view-BPOrX4do.js +0 -30
- package/renderer/assets/hunks-view-CXQMQon9.js +0 -1
- package/renderer/assets/index-CS0Dcjbz.css +0 -1
- package/renderer/assets/index-CuWbxCsX.js +0 -282
- package/renderer/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/renderer/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/renderer/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/renderer/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/renderer/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/renderer/assets/line-selection-CDQurM1g.js +0 -3
- package/renderer/assets/logo-CUD_B5qJ.png +0 -0
- package/renderer/assets/search-view-BDExg3Xl.js +0 -1
- package/renderer/assets/surface-BsSU1Fob.js +0 -2
- package/renderer/assets/unfold-vertical-CIpACs4g.js +0 -1
- package/renderer/assets/virtual-rows-CsZNdhfp.js +0 -1
- package/renderer/favicon-16.png +0 -0
- package/renderer/favicon-32.png +0 -0
- package/renderer/icon-192.png +0 -0
- package/renderer/icon-512.png +0 -0
- package/renderer/index.html +0 -41
- package/renderer/manifest.webmanifest +0 -16
- /package/{renderer → web/dist}/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- /package/{renderer → web/dist}/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- /package/{renderer → web/dist}/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- /package/{renderer → web/dist}/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- /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.
|
|
1
|
+
# @fabiofiorita/porcelain (0.65.0)
|
|
2
2
|
|
|
3
|
-
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
|
-
##
|
|
5
|
+
## Quick start
|
|
7
6
|
|
|
8
|
-
The host needs Node
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
npx @fabiofiorita/porcelain@latest
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
42
|
+
## Repository development
|
|
41
43
|
|
|
42
44
|
```sh
|
|
43
|
-
pnpm build
|
|
45
|
+
pnpm build:package
|
|
46
|
+
|
|
47
|
+
# Inspect the exact publishable archive:
|
|
48
|
+
cd dist-porcelain && npm pack
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
`dist-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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": ">=
|
|
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
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"README.md"
|
|
20
|
+
"server",
|
|
21
|
+
"web",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
23
24
|
],
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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`);
|