diffing 0.1.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/README.md +164 -0
- package/dist/cli-agent-Cfuy9fLC.mjs +179 -0
- package/dist/cli-agent-CtNivjQW.mjs +179 -0
- package/dist/cli-agent-WzJPMH52.mjs +174 -0
- package/dist/cli-agent-uYUoCA4X.mjs +179 -0
- package/dist/cli.mjs +1903 -0
- package/dist/client/assets/abap-CkQ0tsz-.js +1 -0
- package/dist/client/assets/actionscript-3-DHm0ZfF4.js +1 -0
- package/dist/client/assets/ada-DK_ua92Z.js +1 -0
- package/dist/client/assets/andromeeda-D2ccw8v4.js +1 -0
- package/dist/client/assets/angular-html-BmBRnRY-.js +1 -0
- package/dist/client/assets/angular-ts-BTywfy8Q.js +1 -0
- package/dist/client/assets/apache-DXtpFu7o.js +1 -0
- package/dist/client/assets/apex-DlnRRWpl.js +1 -0
- package/dist/client/assets/apl-C-Nh4njw.js +1 -0
- package/dist/client/assets/applescript-CShk62ad.js +1 -0
- package/dist/client/assets/ara-CWN-FDkm.js +1 -0
- package/dist/client/assets/asciidoc-Bg-PM9Vn.js +1 -0
- package/dist/client/assets/asm-DKOhR24S.js +1 -0
- package/dist/client/assets/astro-DGFZsGD0.js +1 -0
- package/dist/client/assets/aurora-x-DpaEirL8.js +1 -0
- package/dist/client/assets/awk-3ibKHffv.js +1 -0
- package/dist/client/assets/ayu-dark-ursiSH5k.js +1 -0
- package/dist/client/assets/ayu-light-DMjlrKmx.js +1 -0
- package/dist/client/assets/ayu-mirage-DZEMb75h.js +1 -0
- package/dist/client/assets/ballerina-xx7VNGxs.js +1 -0
- package/dist/client/assets/bat-DGYsZ5wR.js +1 -0
- package/dist/client/assets/beancount-D_ATR1UV.js +1 -0
- package/dist/client/assets/berry-BqYMORfH.js +1 -0
- package/dist/client/assets/bibtex-CQTBDajW.js +1 -0
- package/dist/client/assets/bicep-CfNtxPrK.js +1 -0
- package/dist/client/assets/bird2-C0dUPe3I.js +1 -0
- package/dist/client/assets/blade-CK2WgF42.js +1 -0
- package/dist/client/assets/bsl-Dx5tFn3i.js +1 -0
- package/dist/client/assets/c-MXSr0H4Y.js +1 -0
- package/dist/client/assets/c3-BUwZ5e3k.js +1 -0
- package/dist/client/assets/cadence-C3OKDYrr.js +1 -0
- package/dist/client/assets/cairo-wCCQTdA9.js +1 -0
- package/dist/client/assets/catppuccin-frappe-CMnRXVZP.js +1 -0
- package/dist/client/assets/catppuccin-latte-CqhXSjCC.js +1 -0
- package/dist/client/assets/catppuccin-macchiato-B6kneE0C.js +1 -0
- package/dist/client/assets/catppuccin-mocha-CP8YZpvj.js +1 -0
- package/dist/client/assets/clarity-Crxe8gSr.js +1 -0
- package/dist/client/assets/clojure-BuzOUPMr.js +1 -0
- package/dist/client/assets/cmake-CCpZLdLI.js +1 -0
- package/dist/client/assets/cobol-BHSU4T5M.js +1 -0
- package/dist/client/assets/codeowners-Bo1VYpHz.js +1 -0
- package/dist/client/assets/codeql-Db01hzHE.js +1 -0
- package/dist/client/assets/coffee-DiFXs9rA.js +1 -0
- package/dist/client/assets/common-lisp-CMlJIc6Y.js +1 -0
- package/dist/client/assets/coq-BlzBYp3f.js +1 -0
- package/dist/client/assets/cpp-CJWLNzrI.js +1 -0
- package/dist/client/assets/crystal-u5M0TrPu.js +1 -0
- package/dist/client/assets/csharp-kfNshAa7.js +1 -0
- package/dist/client/assets/css-BMVhDkjX.js +1 -0
- package/dist/client/assets/csv-Dr8ri5Nd.js +1 -0
- package/dist/client/assets/cue-jXfhyOwY.js +1 -0
- package/dist/client/assets/cypher-CayLeiaR.js +1 -0
- package/dist/client/assets/d-BMZQ0mAy.js +1 -0
- package/dist/client/assets/dark-plus-B__q47Nr.js +1 -0
- package/dist/client/assets/dart-gMOEMyuz.js +1 -0
- package/dist/client/assets/dax-C3t5LadU.js +1 -0
- package/dist/client/assets/desktop-DmhXLwLR.js +1 -0
- package/dist/client/assets/diff-togOXQX6.js +1 -0
- package/dist/client/assets/docker--6N01olR.js +1 -0
- package/dist/client/assets/dotenv-Bq930Yx8.js +1 -0
- package/dist/client/assets/dracula-CBkSghcw.js +1 -0
- package/dist/client/assets/dracula-soft-CCSYeoxL.js +1 -0
- package/dist/client/assets/dream-maker-BiMf3DHl.js +1 -0
- package/dist/client/assets/edge-Di-AS7wq.js +1 -0
- package/dist/client/assets/elixir-CSMS6Yja.js +1 -0
- package/dist/client/assets/elm-CgUch6k4.js +1 -0
- package/dist/client/assets/emacs-lisp-Dhht7he2.js +1 -0
- package/dist/client/assets/erb-BY53XvrH.js +1 -0
- package/dist/client/assets/erlang-CtK0kQ6z.js +1 -0
- package/dist/client/assets/everforest-dark-NqSz4oHS.js +1 -0
- package/dist/client/assets/everforest-light-a3Yzs_lT.js +1 -0
- package/dist/client/assets/fennel-BExc9TZf.js +1 -0
- package/dist/client/assets/fish-C2p-AJmZ.js +1 -0
- package/dist/client/assets/fluent-CeYzs8Qy.js +1 -0
- package/dist/client/assets/fortran-fixed-form-DBM4e0_k.js +1 -0
- package/dist/client/assets/fortran-free-form-BIQEK2Jw.js +1 -0
- package/dist/client/assets/fsharp-CmzzGQLe.js +1 -0
- package/dist/client/assets/gdresource-B_wbPqHd.js +1 -0
- package/dist/client/assets/gdscript-B5JQPh_-.js +1 -0
- package/dist/client/assets/gdshader-vVjJ_Qr8.js +1 -0
- package/dist/client/assets/genie-DGl5rqC8.js +1 -0
- package/dist/client/assets/gherkin-CY_VLUnD.js +1 -0
- package/dist/client/assets/git-commit-1z4D6NzL.js +1 -0
- package/dist/client/assets/git-rebase-Cu4zaEs3.js +1 -0
- package/dist/client/assets/github-dark-ClO4AStc.js +1 -0
- package/dist/client/assets/github-dark-default-CM0dPco8.js +1 -0
- package/dist/client/assets/github-dark-dimmed-Cb6xxpXY.js +1 -0
- package/dist/client/assets/github-dark-high-contrast-BpYtx2-M.js +1 -0
- package/dist/client/assets/github-light-CeOVFgf_.js +1 -0
- package/dist/client/assets/github-light-default-BmnCWL6x.js +1 -0
- package/dist/client/assets/github-light-high-contrast-rpagNF5O.js +1 -0
- package/dist/client/assets/gleam-BarIG9kQ.js +1 -0
- package/dist/client/assets/glimmer-js-DgxwVbEv.js +1 -0
- package/dist/client/assets/glimmer-ts-CQgpMe8l.js +1 -0
- package/dist/client/assets/glsl-C6rPllEZ.js +1 -0
- package/dist/client/assets/gn-DhYsTPYc.js +1 -0
- package/dist/client/assets/gnuplot-CucLlTBc.js +1 -0
- package/dist/client/assets/go-toi-uWvg.js +1 -0
- package/dist/client/assets/graphql-D0IXKtPr.js +1 -0
- package/dist/client/assets/groovy-Ds4dis_k.js +1 -0
- package/dist/client/assets/gruvbox-dark-hard-aPiW8pQh.js +1 -0
- package/dist/client/assets/gruvbox-dark-medium-2HJCvzsS.js +1 -0
- package/dist/client/assets/gruvbox-dark-soft-Da97ODX9.js +1 -0
- package/dist/client/assets/gruvbox-light-hard-BXlEipFe.js +1 -0
- package/dist/client/assets/gruvbox-light-medium-Cun-GjGk.js +1 -0
- package/dist/client/assets/gruvbox-light-soft-Bd3qiiSw.js +1 -0
- package/dist/client/assets/hack-Bbv4uGLh.js +1 -0
- package/dist/client/assets/haml-BEq4fVEe.js +1 -0
- package/dist/client/assets/handlebars-CGriKhdR.js +1 -0
- package/dist/client/assets/haskell-B_jvz5Po.js +1 -0
- package/dist/client/assets/haxe-BRbuo5UO.js +1 -0
- package/dist/client/assets/hcl-BzNUCtsK.js +1 -0
- package/dist/client/assets/hjson-BxVo3sS2.js +1 -0
- package/dist/client/assets/hlsl-C8Wes1nf.js +1 -0
- package/dist/client/assets/horizon-D54PjLAY.js +1 -0
- package/dist/client/assets/horizon-bright-D9RccdIr.js +1 -0
- package/dist/client/assets/houston-CMu1N_4K.js +1 -0
- package/dist/client/assets/html-DhsWBiF-.js +1 -0
- package/dist/client/assets/html-derivative-CU7SM-vj.js +1 -0
- package/dist/client/assets/http-DAZNqWUu.js +1 -0
- package/dist/client/assets/hurl-DGP3-RE2.js +1 -0
- package/dist/client/assets/hxml-DC4KwKt2.js +1 -0
- package/dist/client/assets/hy-dMrJKKU_.js +1 -0
- package/dist/client/assets/imba-DG_oO8tg.js +1 -0
- package/dist/client/assets/index-BqD_EVaR.css +1 -0
- package/dist/client/assets/index-Bsby5xp2.js +3856 -0
- package/dist/client/assets/ini-geHw79Aj.js +1 -0
- package/dist/client/assets/java-CoIvfF-o.js +1 -0
- package/dist/client/assets/javascript-D2G0Bybq.js +1 -0
- package/dist/client/assets/jinja-BRdOYMmE.js +1 -0
- package/dist/client/assets/jison-CVQZsA0e.js +1 -0
- package/dist/client/assets/json-BTFmVGAb.js +1 -0
- package/dist/client/assets/json5-CDUsNqz6.js +1 -0
- package/dist/client/assets/jsonc-D48xcwyD.js +1 -0
- package/dist/client/assets/jsonl-cpIYDXEe.js +1 -0
- package/dist/client/assets/jsonnet-Da1lhszN.js +1 -0
- package/dist/client/assets/jssm-B8BLrEGL.js +1 -0
- package/dist/client/assets/jsx-DYLgPaVb.js +1 -0
- package/dist/client/assets/julia-CJWtNtRM.js +1 -0
- package/dist/client/assets/just-CZyINJ-2.js +1 -0
- package/dist/client/assets/kanagawa-dragon-2Sk8Rx83.js +1 -0
- package/dist/client/assets/kanagawa-lotus-CQVSWfJp.js +1 -0
- package/dist/client/assets/kanagawa-wave-BKWNioLX.js +1 -0
- package/dist/client/assets/kdl-B-2tSRmp.js +1 -0
- package/dist/client/assets/kotlin-DepsEzh3.js +1 -0
- package/dist/client/assets/kusto-BcCtXIb8.js +1 -0
- package/dist/client/assets/laserwave-BUqkO46F.js +1 -0
- package/dist/client/assets/latex-mNMnTIv1.js +1 -0
- package/dist/client/assets/lean-CNO9ffpX.js +1 -0
- package/dist/client/assets/less-Dj0T9pWC.js +1 -0
- package/dist/client/assets/light-plus-C9Hnuc1T.js +1 -0
- package/dist/client/assets/liquid-BgSPl0Kv.js +1 -0
- package/dist/client/assets/llvm-DbV9b66l.js +1 -0
- package/dist/client/assets/log-BMblmPyH.js +1 -0
- package/dist/client/assets/logo-B78qMxws.js +1 -0
- package/dist/client/assets/lua-rmIsezWJ.js +1 -0
- package/dist/client/assets/luau-BUd8xsdE.js +1 -0
- package/dist/client/assets/make-Ev-6cJrd.js +1 -0
- package/dist/client/assets/markdown-CS4LI6sN.js +1 -0
- package/dist/client/assets/marko-Dfkwq7Gv.js +1 -0
- package/dist/client/assets/material-theme-5rA1zn0D.js +1 -0
- package/dist/client/assets/material-theme-darker-DGOEiL65.js +1 -0
- package/dist/client/assets/material-theme-lighter-CULfFRX0.js +1 -0
- package/dist/client/assets/material-theme-ocean-LQZ9CleP.js +1 -0
- package/dist/client/assets/material-theme-palenight-Di_f5oks.js +1 -0
- package/dist/client/assets/matlab-C0qEtAx9.js +1 -0
- package/dist/client/assets/mdc-f53p1REg.js +1 -0
- package/dist/client/assets/mdx-CzvnbLXb.js +1 -0
- package/dist/client/assets/mermaid-BPquZaKt.js +1 -0
- package/dist/client/assets/min-dark-rkuIbtJV.js +1 -0
- package/dist/client/assets/min-light-Dj_HOGsr.js +1 -0
- package/dist/client/assets/mipsasm-k5DQoS6j.js +1 -0
- package/dist/client/assets/mojo-sTTDuNnR.js +1 -0
- package/dist/client/assets/monokai-D5p-u2ij.js +1 -0
- package/dist/client/assets/moonbit-j5ChFtBC.js +1 -0
- package/dist/client/assets/move-Dj0Gmn6R.js +1 -0
- package/dist/client/assets/narrat-DJgMz2oW.js +1 -0
- package/dist/client/assets/nextflow-CApkvBGW.js +1 -0
- package/dist/client/assets/nextflow-groovy-eDLuZ_oi.js +1 -0
- package/dist/client/assets/nginx-Cn5ljkEv.js +1 -0
- package/dist/client/assets/night-owl-CEdtPl3A.js +1 -0
- package/dist/client/assets/night-owl-light-GdSldewm.js +1 -0
- package/dist/client/assets/nim-DrAyHk_n.js +1 -0
- package/dist/client/assets/nix-CxIPS3pb.js +1 -0
- package/dist/client/assets/nord-3WFAz5H9.js +1 -0
- package/dist/client/assets/nushell-GDfBmoBU.js +1 -0
- package/dist/client/assets/objective-c-MFQ_MGUa.js +1 -0
- package/dist/client/assets/objective-cpp-Y83LcBE7.js +1 -0
- package/dist/client/assets/ocaml-FxYfaJxu.js +1 -0
- package/dist/client/assets/odin-CprihY3z.js +1 -0
- package/dist/client/assets/one-dark-pro-BB6mpmmA.js +1 -0
- package/dist/client/assets/one-light-BYLoxNtg.js +1 -0
- package/dist/client/assets/openscad-Bc1FbpOi.js +1 -0
- package/dist/client/assets/pascal-Bb4d8cUL.js +1 -0
- package/dist/client/assets/perl-BLNIVaG-.js +1 -0
- package/dist/client/assets/php-CxDOF44K.js +1 -0
- package/dist/client/assets/pierre-dark-cF19s-RM.js +1 -0
- package/dist/client/assets/pierre-light-BdpX8OpD.js +1 -0
- package/dist/client/assets/pkl-tM25Sq0w.js +1 -0
- package/dist/client/assets/plastic-D3k2Xp_5.js +1 -0
- package/dist/client/assets/plsql-CLEfFhSj.js +1 -0
- package/dist/client/assets/po-CaAzdrrh.js +1 -0
- package/dist/client/assets/poimandres-bPTvT4Da.js +1 -0
- package/dist/client/assets/polar-TG7aTGdK.js +1 -0
- package/dist/client/assets/postcss-8kqfa9f0.js +1 -0
- package/dist/client/assets/powerquery-CtSu1Nfd.js +1 -0
- package/dist/client/assets/powershell-87lLBHMy.js +1 -0
- package/dist/client/assets/prisma-Dnf694DZ.js +1 -0
- package/dist/client/assets/prolog-C0XbHTWa.js +1 -0
- package/dist/client/assets/proto-BZP-HLIb.js +1 -0
- package/dist/client/assets/pug-DGZxLxM3.js +1 -0
- package/dist/client/assets/puppet-CJYWgYC5.js +1 -0
- package/dist/client/assets/purescript-B95Q6BAd.js +1 -0
- package/dist/client/assets/python-CqTCsi7P.js +1 -0
- package/dist/client/assets/qml-Dmqfyov_.js +1 -0
- package/dist/client/assets/qmldir-DlIL4hls.js +1 -0
- package/dist/client/assets/qss-BW72E0q_.js +1 -0
- package/dist/client/assets/r-F2CLYuZ7.js +1 -0
- package/dist/client/assets/racket-CCAimEsC.js +1 -0
- package/dist/client/assets/raku-mSuqElkM.js +1 -0
- package/dist/client/assets/razor-DYbbBV0d.js +1 -0
- package/dist/client/assets/red-C-DA4yBI.js +1 -0
- package/dist/client/assets/reg-CGwMBJ2c.js +1 -0
- package/dist/client/assets/regexp-TyKA4-BB.js +1 -0
- package/dist/client/assets/rel-JwrbYgX4.js +1 -0
- package/dist/client/assets/riscv-CoYyE75b.js +1 -0
- package/dist/client/assets/ron-C5wrhnmJ.js +1 -0
- package/dist/client/assets/rose-pine-DlrMPYYd.js +1 -0
- package/dist/client/assets/rose-pine-dawn-DBHFtZD2.js +1 -0
- package/dist/client/assets/rose-pine-moon-BBfrpe5q.js +1 -0
- package/dist/client/assets/rosmsg-DlimTR9t.js +1 -0
- package/dist/client/assets/rst-CMLF7yP9.js +1 -0
- package/dist/client/assets/ruby-BE4S9bsQ.js +1 -0
- package/dist/client/assets/rust-Cxnkxx3h.js +1 -0
- package/dist/client/assets/sas-DOhcrhSM.js +1 -0
- package/dist/client/assets/sass-DEyqUIpU.js +1 -0
- package/dist/client/assets/scala-i3RN3yAN.js +1 -0
- package/dist/client/assets/scheme-BcKdjYkO.js +1 -0
- package/dist/client/assets/scss-DZgJ0K1I.js +1 -0
- package/dist/client/assets/sdbl-Be_XWc3n.js +1 -0
- package/dist/client/assets/shaderlab-DXAhMdPo.js +1 -0
- package/dist/client/assets/shellscript-CWMAvx8a.js +1 -0
- package/dist/client/assets/shellsession-OJxRnKbO.js +1 -0
- package/dist/client/assets/slack-dark-3PDtm74m.js +1 -0
- package/dist/client/assets/slack-ochin-CwiuFRJD.js +1 -0
- package/dist/client/assets/smalltalk-BdBlL6ks.js +1 -0
- package/dist/client/assets/snazzy-light-Dyqc8Is2.js +1 -0
- package/dist/client/assets/solarized-dark-DZiTscIX.js +1 -0
- package/dist/client/assets/solarized-light-DBcfxrfG.js +1 -0
- package/dist/client/assets/solidity-DH-yUSWc.js +1 -0
- package/dist/client/assets/soy-C_oQW_S0.js +1 -0
- package/dist/client/assets/sparql-BdcL8cbr.js +1 -0
- package/dist/client/assets/splunk-CIjc4kxs.js +1 -0
- package/dist/client/assets/sql-DEXpTiLI.js +1 -0
- package/dist/client/assets/ssh-config-BKVd4drG.js +1 -0
- package/dist/client/assets/stata-BcrmvsDF.js +1 -0
- package/dist/client/assets/stylus-tqAsmugv.js +1 -0
- package/dist/client/assets/surrealql-Dwy3iKjf.js +1 -0
- package/dist/client/assets/svelte-BbWN9l2M.js +1 -0
- package/dist/client/assets/swift-CdDBQ3_Z.js +1 -0
- package/dist/client/assets/synthwave-84-CbbRs73K.js +1 -0
- package/dist/client/assets/system-verilog-CBqEY-9v.js +1 -0
- package/dist/client/assets/systemd-khRP5zwK.js +1 -0
- package/dist/client/assets/talonscript-D_lneH-6.js +1 -0
- package/dist/client/assets/tasl-CLymawGe.js +1 -0
- package/dist/client/assets/tcl-BRY1S87U.js +1 -0
- package/dist/client/assets/templ-BGIeAdtg.js +1 -0
- package/dist/client/assets/terraform-BqohMo2f.js +1 -0
- package/dist/client/assets/tex-9fSuF3Cs.js +1 -0
- package/dist/client/assets/tokyo-night-BWUONxad.js +1 -0
- package/dist/client/assets/toml-C4c85Lwg.js +1 -0
- package/dist/client/assets/ts-tags-BcxYxF8C.js +1 -0
- package/dist/client/assets/tsv-DsZq9HBQ.js +1 -0
- package/dist/client/assets/tsx-B_z3ONaQ.js +1 -0
- package/dist/client/assets/turtle-Dpr8zLlw.js +1 -0
- package/dist/client/assets/twig-DQ7osXl9.js +1 -0
- package/dist/client/assets/typescript-oc7bycaY.js +1 -0
- package/dist/client/assets/typespec-DcqgATB-.js +1 -0
- package/dist/client/assets/typst-CeVxmuPg.js +1 -0
- package/dist/client/assets/v-C2Qsh2-1.js +1 -0
- package/dist/client/assets/vala-CvZ3yL7c.js +1 -0
- package/dist/client/assets/vb-B_p4qDw4.js +1 -0
- package/dist/client/assets/verilog-D8MGjxD7.js +1 -0
- package/dist/client/assets/vesper-DossXiE1.js +1 -0
- package/dist/client/assets/vhdl-MLyYh_Gz.js +1 -0
- package/dist/client/assets/viml-BuYIArcz.js +1 -0
- package/dist/client/assets/vitesse-black-C3kc1227.js +1 -0
- package/dist/client/assets/vitesse-dark-kWOu5V36.js +1 -0
- package/dist/client/assets/vitesse-light-DMtiL8bK.js +1 -0
- package/dist/client/assets/vue-B3o6Rfti.js +1 -0
- package/dist/client/assets/vue-html-BB_D6k4v.js +1 -0
- package/dist/client/assets/vue-vine-CROD_1yE.js +1 -0
- package/dist/client/assets/vyper-CNqG09-_.js +1 -0
- package/dist/client/assets/wasm-BsQDV7Y0.js +1 -0
- package/dist/client/assets/wasm-DFVlQlgd.js +1 -0
- package/dist/client/assets/wenyan-BmOQge13.js +1 -0
- package/dist/client/assets/wgsl-CgWrmvIg.js +1 -0
- package/dist/client/assets/wikitext-DkwckXCk.js +1 -0
- package/dist/client/assets/wit-CtyVFGe7.js +1 -0
- package/dist/client/assets/wolfram-CXE4FrKz.js +1 -0
- package/dist/client/assets/worker-ZCX3SXK5.js +160 -0
- package/dist/client/assets/xml-BeVKeTE5.js +1 -0
- package/dist/client/assets/xsl-DaxeysAZ.js +1 -0
- package/dist/client/assets/yaml-DJD0ioac.js +1 -0
- package/dist/client/assets/zenscript-DH15wJt0.js +1 -0
- package/dist/client/assets/zig-CqXN9brD.js +1 -0
- package/dist/client/index.html +16 -0
- package/dist/mcp-CLgzC8_a.mjs +119 -0
- package/dist/mcp-LVbLV17O.mjs +119 -0
- package/dist/mcp-nMwTAut3.mjs +119 -0
- package/dist/server-lock-BbiF7u3F.mjs +493 -0
- package/dist/server-lock-Bx9ixiF6.mjs +575 -0
- package/dist/server-lock-DIkImokU.mjs +487 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# diffit
|
|
2
|
+
|
|
3
|
+
A local code review tool designed for the coding agent workflow. Review AI-generated changes in a GitHub PR-like web UI, leave inline comments, then hand them back to your coding agent to fix.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @ahmedragab/diffit
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Run in any git repository:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
diffit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This starts a local server and opens your browser with a diff review UI.
|
|
22
|
+
|
|
23
|
+
### Options
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
diffit [options] [-- <git-diff-args>]
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-p, --port <port> Server port (default: 3433)
|
|
30
|
+
--no-open Don't auto-open browser
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
diffit # Review working tree changes
|
|
34
|
+
diffit -p 8080 # Use custom port
|
|
35
|
+
diffit -- HEAD~3 # Diff against 3 commits ago
|
|
36
|
+
diffit -- main..HEAD # Diff between branches
|
|
37
|
+
diffit -- --cached -- src/ # Staged changes in src/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Split / Unified view** — Toggle between side-by-side and inline diff
|
|
43
|
+
- **Syntax highlighting** — Powered by Shiki with GitHub themes
|
|
44
|
+
- **File tree** — Hierarchical file browser with search filter and file change-type icons
|
|
45
|
+
- **Inline comments** — Click the `+` button on any line to add a review comment
|
|
46
|
+
- **Comment replies** — AI agents can reply to comments via API, displayed with bot avatar in the UI
|
|
47
|
+
- **Comment status tracker** — Sidebar widget showing open, replied, and resolved comment counts with click-to-navigate links
|
|
48
|
+
- **Send to agent** — One click hands your comments to a waiting agent automatically — no copy-paste, works with any agent/model via the `diffit` CLI or an MCP server
|
|
49
|
+
- **Copy comments** — One-click copy all comments as structured XML for AI coding agents (offline fallback)
|
|
50
|
+
- **Image preview** — Side-by-side comparison for added, modified, and deleted images
|
|
51
|
+
- **Viewed tracking** — Mark files as reviewed to track progress
|
|
52
|
+
- **Staged / Untracked toggles** — Choose which changes to include
|
|
53
|
+
- **Custom diff commands** — Pass any `git diff` arguments after `--`
|
|
54
|
+
- **EditorConfig support** — Respects `.editorconfig` for per-file tab size
|
|
55
|
+
- **Persistent settings** — Your preferences are saved across sessions
|
|
56
|
+
|
|
57
|
+
## Comment Output Format
|
|
58
|
+
|
|
59
|
+
When you click "Copy comments", the output is structured XML optimized for AI agents, featuring embedded self-documenting instructions, complete metadata, XML safety using CDATA blocks, and reply threads:
|
|
60
|
+
|
|
61
|
+
```xml
|
|
62
|
+
<code-review-comments>
|
|
63
|
+
<instructions>
|
|
64
|
+
You are an AI coding assistant. You are receiving a structured list of code review comments to address in the repository.
|
|
65
|
+
For each file, review the inline comments and apply the changes requested.
|
|
66
|
+
- Target lines are specified by the "line" attribute (e.g. line="10" or line="10-15").
|
|
67
|
+
- "side" indicates whether the comment is on "additions" (added/modified lines) or "deletions" (deleted/old lines).
|
|
68
|
+
- "status" indicates whether the comment is "open" or "resolved". Only address comments with status="open".
|
|
69
|
+
- The <code> block contains the specific code context at the reviewed lines, prefixed with "+" or "-".
|
|
70
|
+
- The <body> tag contains the review feedback or request.
|
|
71
|
+
- If developers have replied to the comment, their discussion is captured under the <replies> element.
|
|
72
|
+
- The comment "id" attribute can be used to reference or update the comment via API if available.
|
|
73
|
+
|
|
74
|
+
HOW TO REPLY OR ASK FOR CLARIFICATION:
|
|
75
|
+
If you need to ask for clarification, explain what you did, or reply to any comment:
|
|
76
|
+
|
|
77
|
+
Option A: Via the diffit CLI or MCP (Preferred — port-agnostic, no copy-paste)
|
|
78
|
+
diffit reply <comment-id> --body "Your response" --model "<your-model-name>"
|
|
79
|
+
diffit resolve <comment-id>
|
|
80
|
+
(Or the equivalent MCP tools: reply_to_comment, resolve_comment.)
|
|
81
|
+
|
|
82
|
+
Option B: Via the local HTTP API (if you know the running port)
|
|
83
|
+
POST http://localhost:<port>/api/comments/<comment-id>/replies
|
|
84
|
+
Payload: { "body": "Your response or clarification request here", "model": "<your-model-name>" }
|
|
85
|
+
PUT http://localhost:<port>/api/comments/<comment-id> Payload: { "status": "resolved" }
|
|
86
|
+
|
|
87
|
+
Option C: Via Text Response (Offline / Chat Copy-Paste)
|
|
88
|
+
If you do not have local API access, output your comments/replies inside a structured XML block at the end of your response:
|
|
89
|
+
<comment-replies>
|
|
90
|
+
<reply to="<comment-id>" model="<your-model-name>"><![CDATA[Your reply or clarification request here]]></reply>
|
|
91
|
+
</comment-replies>
|
|
92
|
+
</instructions>
|
|
93
|
+
<file path="src/utils/parser.ts">
|
|
94
|
+
<comment id="c1" line="42" side="additions" status="open" created-at="2026-05-24T22:00:00.000Z">
|
|
95
|
+
<code><![CDATA[+ const parsedToken = tokenize(input)]]></code>
|
|
96
|
+
<body><![CDATA[Rename `x` to `parsedToken` for clarity.]]></body>
|
|
97
|
+
<replies>
|
|
98
|
+
<reply id="r1" created-at="2026-05-24T22:05:00.000Z" role="agent" model="claude-3-5-sonnet">
|
|
99
|
+
<![CDATA[I agree, renamed.]]>
|
|
100
|
+
</reply>
|
|
101
|
+
</replies>
|
|
102
|
+
</comment>
|
|
103
|
+
<comment id="c2" line="15" side="deletions" status="open" created-at="2026-05-24T22:01:00.000Z">
|
|
104
|
+
<code><![CDATA[- if (input != null) {]]></code>
|
|
105
|
+
<body><![CDATA[This null check removal may cause a bug when `input` is undefined.]]></body>
|
|
106
|
+
</comment>
|
|
107
|
+
</file>
|
|
108
|
+
</code-review-comments>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each comment includes:
|
|
112
|
+
- **Instructions**: A detailed system prompt instructing the agent on how to interpret and act on the comments.
|
|
113
|
+
- **Attributes**: Every `<comment>` includes its unique `id`, targeted line range (`line`), change `side` (`additions` or `deletions`), comment `status` (`open` or `resolved`), and ISO `created-at` timestamp.
|
|
114
|
+
- **CDATA Blocks**: Code snippets (`<code>`) and comment bodies (`<body>`) are wrapped in `<![CDATA[ ... ]]>` to prevent special characters from breaking XML parsers.
|
|
115
|
+
- **Replies**: Nested conversation history (if any) is preserved within `<replies>` and `<reply>` elements.
|
|
116
|
+
|
|
117
|
+
## Agent Handoff (no copy-paste)
|
|
118
|
+
|
|
119
|
+
Instead of copying comments into a chat, you can hand them to an agent automatically — and it works regardless of which agent/model is active. The model is **"the agent waits, you release it"**:
|
|
120
|
+
|
|
121
|
+
1. The agent runs a blocking command (or MCP tool) and sleeps.
|
|
122
|
+
2. You review in the browser and click **"Send to agent"** in the toolbar (a green dot on the button means an agent is connected and waiting).
|
|
123
|
+
3. The agent instantly receives your comments, applies changes, and replies — the UI updates live as it works. Add more comments and click Send again for another round.
|
|
124
|
+
|
|
125
|
+
### CLI (any agent with a shell)
|
|
126
|
+
|
|
127
|
+
The `diffit` binary doubles as a port-agnostic client. Each subcommand discovers the running server for the current repo via a lockfile — no port needed:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
diffit await-review # block until you click "Send to agent"; prints comments as XML
|
|
131
|
+
diffit comments [--open] [--json] # one-shot dump of current comments
|
|
132
|
+
diffit reply <id> --body "…" --model "<name>" # post an agent reply
|
|
133
|
+
diffit resolve <id> # mark a comment resolved
|
|
134
|
+
diffit url # print the running server's base URL (for raw API calls)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`await-review` exits `0` when comments arrive (XML on stdout), `2` if it times out (just run it again to keep waiting), and `3` if no server is running.
|
|
138
|
+
|
|
139
|
+
### MCP server (any MCP-capable agent: Claude, Cursor, Codex, Gemini…)
|
|
140
|
+
|
|
141
|
+
Run `diffit mcp` as an MCP server over stdio. It exposes the tools `await_review`, `list_comments`, `reply_to_comment`, and `resolve_comment`. Configure your client:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{ "mcpServers": { "diffit": { "command": "diffit", "args": ["mcp"] } } }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
No port configuration is needed — the server is discovered from the repo's lockfile.
|
|
148
|
+
|
|
149
|
+
## Agent Skills
|
|
150
|
+
|
|
151
|
+
Install the diffit skills to use diffit directly from your AI coding agent:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx skills add ahmedragab20/diffit
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The review workflow uses two commands:
|
|
158
|
+
|
|
159
|
+
1. **`/diffit-start-review`** — Launches the diffit server and opens the browser. Review your changes and leave inline comments.
|
|
160
|
+
2. **`/diffit-finish-review`** — The agent runs `diffit await-review`, blocks until you click **"Send to agent"**, then applies the requested changes and marks each comment as resolved. The browser UI updates in real time as comments are resolved.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { a as formatComments, n as readServerLock, t as isLockAlive } from "./server-lock-DIkImokU.mjs";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
//#region src/cli-agent.ts
|
|
4
|
+
/**
|
|
5
|
+
* Agent-facing `diffit` subcommands. These make the user→agent handoff
|
|
6
|
+
* port-agnostic: each resolves the running server via the per-repo lockfile
|
|
7
|
+
* (`server.json`) so any agent with a shell — or a human — can drive the loop
|
|
8
|
+
* without being told a port.
|
|
9
|
+
*
|
|
10
|
+
* diffit await-review block until the human clicks "Send to agent"
|
|
11
|
+
* diffit reply <id> post an agent reply to a comment
|
|
12
|
+
* diffit resolve <id> mark a comment resolved
|
|
13
|
+
* diffit comments dump the current comments (XML or JSON)
|
|
14
|
+
*/
|
|
15
|
+
const EXIT_OK = 0;
|
|
16
|
+
const EXIT_AWAIT_TIMEOUT = 2;
|
|
17
|
+
const EXIT_NO_SERVER = 3;
|
|
18
|
+
const EXIT_NOT_FOUND = 4;
|
|
19
|
+
const EXIT_USAGE = 5;
|
|
20
|
+
/** Resolve the running server's base URL from the lockfile, or exit cleanly. */
|
|
21
|
+
function baseUrl() {
|
|
22
|
+
const lock = readServerLock();
|
|
23
|
+
if (!lock || !isLockAlive(lock)) {
|
|
24
|
+
console.error("No diffit server running for this repo. Start one with `diffit`.");
|
|
25
|
+
process.exit(EXIT_NO_SERVER);
|
|
26
|
+
}
|
|
27
|
+
return `http://${lock.host === "0.0.0.0" || lock.host === "::" ? "127.0.0.1" : lock.host}:${lock.port}`;
|
|
28
|
+
}
|
|
29
|
+
async function readStdin() {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
32
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
33
|
+
}
|
|
34
|
+
async function awaitReview(args) {
|
|
35
|
+
const { values } = parseArgs({
|
|
36
|
+
args,
|
|
37
|
+
options: {
|
|
38
|
+
timeout: {
|
|
39
|
+
type: "string",
|
|
40
|
+
short: "t"
|
|
41
|
+
},
|
|
42
|
+
since: { type: "string" }
|
|
43
|
+
},
|
|
44
|
+
allowPositionals: false
|
|
45
|
+
});
|
|
46
|
+
const totalBudgetMs = (values.timeout ? Number(values.timeout) : 570) * 1e3;
|
|
47
|
+
const base = baseUrl();
|
|
48
|
+
let sinceRound = 0;
|
|
49
|
+
try {
|
|
50
|
+
sinceRound = (await fetch(`${base}/api/review/status`).then((r) => r.json())).round ?? 0;
|
|
51
|
+
} catch {}
|
|
52
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
let res;
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(`${base}/api/review/await?timeoutMs=25000&sinceRound=${sinceRound}`, { signal: AbortSignal.timeout(3e4) });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err?.name === "TimeoutError") continue;
|
|
59
|
+
console.error(`Failed to reach diffit server: ${err?.message ?? err}`);
|
|
60
|
+
return EXIT_NO_SERVER;
|
|
61
|
+
}
|
|
62
|
+
const result = await res.json();
|
|
63
|
+
if (result.status === "released") {
|
|
64
|
+
process.stdout.write(result.payload.commentXml + "\n");
|
|
65
|
+
console.error(`DIFFIT_REVIEW_ROUND=${result.payload.round}`);
|
|
66
|
+
return EXIT_OK;
|
|
67
|
+
}
|
|
68
|
+
sinceRound = result.round ?? sinceRound;
|
|
69
|
+
}
|
|
70
|
+
console.error("DIFFIT_AWAIT_TIMEOUT");
|
|
71
|
+
console.error("No review sent within the timeout. Run `diffit await-review` again to keep waiting.");
|
|
72
|
+
return EXIT_AWAIT_TIMEOUT;
|
|
73
|
+
}
|
|
74
|
+
async function reply(args) {
|
|
75
|
+
const { values, positionals } = parseArgs({
|
|
76
|
+
args,
|
|
77
|
+
options: {
|
|
78
|
+
body: {
|
|
79
|
+
type: "string",
|
|
80
|
+
short: "b"
|
|
81
|
+
},
|
|
82
|
+
model: {
|
|
83
|
+
type: "string",
|
|
84
|
+
short: "m"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
allowPositionals: true
|
|
88
|
+
});
|
|
89
|
+
const commentId = positionals[0];
|
|
90
|
+
if (!commentId) {
|
|
91
|
+
console.error("Usage: diffit reply <commentId> --body <text> [--model <name>]");
|
|
92
|
+
return EXIT_USAGE;
|
|
93
|
+
}
|
|
94
|
+
let body = values.body;
|
|
95
|
+
if (body === "-" || body === void 0) body = (await readStdin()).trim();
|
|
96
|
+
if (!body) {
|
|
97
|
+
console.error("A reply body is required (--body <text> or pipe via stdin).");
|
|
98
|
+
return EXIT_USAGE;
|
|
99
|
+
}
|
|
100
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}/replies`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
body,
|
|
105
|
+
role: "agent",
|
|
106
|
+
model: values.model
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
if (res.status === 404) {
|
|
110
|
+
console.error(`Comment ${commentId} not found.`);
|
|
111
|
+
return EXIT_NOT_FOUND;
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
console.error(`Failed to reply: HTTP ${res.status}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
console.error(`Replied to ${commentId}.`);
|
|
118
|
+
return EXIT_OK;
|
|
119
|
+
}
|
|
120
|
+
async function resolve(args) {
|
|
121
|
+
const { positionals } = parseArgs({
|
|
122
|
+
args,
|
|
123
|
+
allowPositionals: true,
|
|
124
|
+
options: {}
|
|
125
|
+
});
|
|
126
|
+
const commentId = positionals[0];
|
|
127
|
+
if (!commentId) {
|
|
128
|
+
console.error("Usage: diffit resolve <commentId>");
|
|
129
|
+
return EXIT_USAGE;
|
|
130
|
+
}
|
|
131
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}`, {
|
|
132
|
+
method: "PUT",
|
|
133
|
+
headers: { "Content-Type": "application/json" },
|
|
134
|
+
body: JSON.stringify({ status: "resolved" })
|
|
135
|
+
});
|
|
136
|
+
if (res.status === 404) {
|
|
137
|
+
console.error(`Comment ${commentId} not found.`);
|
|
138
|
+
return EXIT_NOT_FOUND;
|
|
139
|
+
}
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
console.error(`Failed to resolve: HTTP ${res.status}`);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
console.error(`Resolved ${commentId}.`);
|
|
145
|
+
return EXIT_OK;
|
|
146
|
+
}
|
|
147
|
+
async function url() {
|
|
148
|
+
process.stdout.write(baseUrl() + "\n");
|
|
149
|
+
return EXIT_OK;
|
|
150
|
+
}
|
|
151
|
+
async function comments(args) {
|
|
152
|
+
const { values } = parseArgs({
|
|
153
|
+
args,
|
|
154
|
+
options: {
|
|
155
|
+
open: { type: "boolean" },
|
|
156
|
+
json: { type: "boolean" }
|
|
157
|
+
},
|
|
158
|
+
allowPositionals: false
|
|
159
|
+
});
|
|
160
|
+
const all = await fetch(`${baseUrl()}/api/comments`).then((r) => r.json());
|
|
161
|
+
const selected = values.open ? all.filter((c) => c.status === "open") : all;
|
|
162
|
+
if (values.json) process.stdout.write(JSON.stringify(selected, null, 2) + "\n");
|
|
163
|
+
else process.stdout.write(formatComments(selected) + "\n");
|
|
164
|
+
return EXIT_OK;
|
|
165
|
+
}
|
|
166
|
+
async function runSubcommand(name, args) {
|
|
167
|
+
switch (name) {
|
|
168
|
+
case "await-review": return awaitReview(args);
|
|
169
|
+
case "reply": return reply(args);
|
|
170
|
+
case "resolve": return resolve(args);
|
|
171
|
+
case "comments": return comments(args);
|
|
172
|
+
case "url": return url();
|
|
173
|
+
default:
|
|
174
|
+
console.error(`Unknown subcommand: ${name}`);
|
|
175
|
+
return EXIT_USAGE;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { runSubcommand };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { a as formatComments, n as readServerLock, t as isLockAlive } from "./server-lock-Bx9ixiF6.mjs";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
//#region src/cli-agent.ts
|
|
4
|
+
/**
|
|
5
|
+
* Agent-facing `diffit` subcommands. These make the user→agent handoff
|
|
6
|
+
* port-agnostic: each resolves the running server via the per-repo lockfile
|
|
7
|
+
* (`server.json`) so any agent with a shell — or a human — can drive the loop
|
|
8
|
+
* without being told a port.
|
|
9
|
+
*
|
|
10
|
+
* diffit await-review block until the human clicks "Send to agent"
|
|
11
|
+
* diffit reply <id> post an agent reply to a comment
|
|
12
|
+
* diffit resolve <id> mark a comment resolved
|
|
13
|
+
* diffit comments dump the current comments (XML or JSON)
|
|
14
|
+
*/
|
|
15
|
+
const EXIT_OK = 0;
|
|
16
|
+
const EXIT_AWAIT_TIMEOUT = 2;
|
|
17
|
+
const EXIT_NO_SERVER = 3;
|
|
18
|
+
const EXIT_NOT_FOUND = 4;
|
|
19
|
+
const EXIT_USAGE = 5;
|
|
20
|
+
/** Resolve the running server's base URL from the lockfile, or exit cleanly. */
|
|
21
|
+
function baseUrl() {
|
|
22
|
+
const lock = readServerLock();
|
|
23
|
+
if (!lock || !isLockAlive(lock)) {
|
|
24
|
+
console.error("No diffit server running for this repo. Start one with `diffit`.");
|
|
25
|
+
process.exit(EXIT_NO_SERVER);
|
|
26
|
+
}
|
|
27
|
+
return `http://${lock.host === "0.0.0.0" || lock.host === "::" ? "127.0.0.1" : lock.host}:${lock.port}`;
|
|
28
|
+
}
|
|
29
|
+
async function readStdin() {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
32
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
33
|
+
}
|
|
34
|
+
async function awaitReview(args) {
|
|
35
|
+
const { values } = parseArgs({
|
|
36
|
+
args,
|
|
37
|
+
options: {
|
|
38
|
+
timeout: {
|
|
39
|
+
type: "string",
|
|
40
|
+
short: "t"
|
|
41
|
+
},
|
|
42
|
+
since: { type: "string" }
|
|
43
|
+
},
|
|
44
|
+
allowPositionals: false
|
|
45
|
+
});
|
|
46
|
+
const totalBudgetMs = (values.timeout ? Number(values.timeout) : 570) * 1e3;
|
|
47
|
+
const base = baseUrl();
|
|
48
|
+
let sinceRound = 0;
|
|
49
|
+
try {
|
|
50
|
+
sinceRound = (await fetch(`${base}/api/review/status`).then((r) => r.json())).round ?? 0;
|
|
51
|
+
} catch {}
|
|
52
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
let res;
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(`${base}/api/review/await?timeoutMs=25000&sinceRound=${sinceRound}`, { signal: AbortSignal.timeout(3e4) });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err?.name === "TimeoutError") continue;
|
|
59
|
+
console.error(`Failed to reach diffit server: ${err?.message ?? err}`);
|
|
60
|
+
return EXIT_NO_SERVER;
|
|
61
|
+
}
|
|
62
|
+
const result = await res.json();
|
|
63
|
+
if (result.status === "released") {
|
|
64
|
+
process.stdout.write(result.payload.commentXml + "\n");
|
|
65
|
+
console.error(`DIFFIT_REVIEW_ROUND=${result.payload.round}`);
|
|
66
|
+
return EXIT_OK;
|
|
67
|
+
}
|
|
68
|
+
sinceRound = result.round ?? sinceRound;
|
|
69
|
+
}
|
|
70
|
+
console.error("DIFFIT_AWAIT_TIMEOUT");
|
|
71
|
+
console.error("No review sent within the timeout. Run `diffit await-review` again to keep waiting.");
|
|
72
|
+
return EXIT_AWAIT_TIMEOUT;
|
|
73
|
+
}
|
|
74
|
+
async function reply(args) {
|
|
75
|
+
const { values, positionals } = parseArgs({
|
|
76
|
+
args,
|
|
77
|
+
options: {
|
|
78
|
+
body: {
|
|
79
|
+
type: "string",
|
|
80
|
+
short: "b"
|
|
81
|
+
},
|
|
82
|
+
model: {
|
|
83
|
+
type: "string",
|
|
84
|
+
short: "m"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
allowPositionals: true
|
|
88
|
+
});
|
|
89
|
+
const commentId = positionals[0];
|
|
90
|
+
if (!commentId) {
|
|
91
|
+
console.error("Usage: diffit reply <commentId> --body <text> [--model <name>]");
|
|
92
|
+
return EXIT_USAGE;
|
|
93
|
+
}
|
|
94
|
+
let body = values.body;
|
|
95
|
+
if (body === "-" || body === void 0) body = (await readStdin()).trim();
|
|
96
|
+
if (!body) {
|
|
97
|
+
console.error("A reply body is required (--body <text> or pipe via stdin).");
|
|
98
|
+
return EXIT_USAGE;
|
|
99
|
+
}
|
|
100
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}/replies`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
body,
|
|
105
|
+
role: "agent",
|
|
106
|
+
model: values.model
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
if (res.status === 404) {
|
|
110
|
+
console.error(`Comment ${commentId} not found.`);
|
|
111
|
+
return EXIT_NOT_FOUND;
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
console.error(`Failed to reply: HTTP ${res.status}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
console.error(`Replied to ${commentId}.`);
|
|
118
|
+
return EXIT_OK;
|
|
119
|
+
}
|
|
120
|
+
async function resolve(args) {
|
|
121
|
+
const { positionals } = parseArgs({
|
|
122
|
+
args,
|
|
123
|
+
allowPositionals: true,
|
|
124
|
+
options: {}
|
|
125
|
+
});
|
|
126
|
+
const commentId = positionals[0];
|
|
127
|
+
if (!commentId) {
|
|
128
|
+
console.error("Usage: diffit resolve <commentId>");
|
|
129
|
+
return EXIT_USAGE;
|
|
130
|
+
}
|
|
131
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}`, {
|
|
132
|
+
method: "PUT",
|
|
133
|
+
headers: { "Content-Type": "application/json" },
|
|
134
|
+
body: JSON.stringify({ status: "resolved" })
|
|
135
|
+
});
|
|
136
|
+
if (res.status === 404) {
|
|
137
|
+
console.error(`Comment ${commentId} not found.`);
|
|
138
|
+
return EXIT_NOT_FOUND;
|
|
139
|
+
}
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
console.error(`Failed to resolve: HTTP ${res.status}`);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
console.error(`Resolved ${commentId}.`);
|
|
145
|
+
return EXIT_OK;
|
|
146
|
+
}
|
|
147
|
+
async function url() {
|
|
148
|
+
process.stdout.write(baseUrl() + "\n");
|
|
149
|
+
return EXIT_OK;
|
|
150
|
+
}
|
|
151
|
+
async function comments(args) {
|
|
152
|
+
const { values } = parseArgs({
|
|
153
|
+
args,
|
|
154
|
+
options: {
|
|
155
|
+
open: { type: "boolean" },
|
|
156
|
+
json: { type: "boolean" }
|
|
157
|
+
},
|
|
158
|
+
allowPositionals: false
|
|
159
|
+
});
|
|
160
|
+
const all = await fetch(`${baseUrl()}/api/comments`).then((r) => r.json());
|
|
161
|
+
const selected = values.open ? all.filter((c) => c.status === "open") : all;
|
|
162
|
+
if (values.json) process.stdout.write(JSON.stringify(selected, null, 2) + "\n");
|
|
163
|
+
else process.stdout.write(formatComments(selected) + "\n");
|
|
164
|
+
return EXIT_OK;
|
|
165
|
+
}
|
|
166
|
+
async function runSubcommand(name, args) {
|
|
167
|
+
switch (name) {
|
|
168
|
+
case "await-review": return awaitReview(args);
|
|
169
|
+
case "reply": return reply(args);
|
|
170
|
+
case "resolve": return resolve(args);
|
|
171
|
+
case "comments": return comments(args);
|
|
172
|
+
case "url": return url();
|
|
173
|
+
default:
|
|
174
|
+
console.error(`Unknown subcommand: ${name}`);
|
|
175
|
+
return EXIT_USAGE;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { runSubcommand };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { a as formatComments, n as readServerLock, t as isLockAlive } from "./server-lock-DIkImokU.mjs";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
//#region src/cli-agent.ts
|
|
4
|
+
/**
|
|
5
|
+
* Agent-facing `diffit` subcommands. These make the user→agent handoff
|
|
6
|
+
* port-agnostic: each resolves the running server via the per-repo lockfile
|
|
7
|
+
* (`server.json`) so any agent with a shell — or a human — can drive the loop
|
|
8
|
+
* without being told a port.
|
|
9
|
+
*
|
|
10
|
+
* diffit await-review block until the human clicks "Send to agent"
|
|
11
|
+
* diffit reply <id> post an agent reply to a comment
|
|
12
|
+
* diffit resolve <id> mark a comment resolved
|
|
13
|
+
* diffit comments dump the current comments (XML or JSON)
|
|
14
|
+
*/
|
|
15
|
+
const EXIT_OK = 0;
|
|
16
|
+
const EXIT_AWAIT_TIMEOUT = 2;
|
|
17
|
+
const EXIT_NO_SERVER = 3;
|
|
18
|
+
const EXIT_NOT_FOUND = 4;
|
|
19
|
+
const EXIT_USAGE = 5;
|
|
20
|
+
/** Resolve the running server's base URL from the lockfile, or exit cleanly. */
|
|
21
|
+
function baseUrl() {
|
|
22
|
+
const lock = readServerLock();
|
|
23
|
+
if (!lock || !isLockAlive(lock)) {
|
|
24
|
+
console.error("No diffit server running for this repo. Start one with `diffit`.");
|
|
25
|
+
process.exit(EXIT_NO_SERVER);
|
|
26
|
+
}
|
|
27
|
+
return `http://${lock.host === "0.0.0.0" || lock.host === "::" ? "127.0.0.1" : lock.host}:${lock.port}`;
|
|
28
|
+
}
|
|
29
|
+
async function readStdin() {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
32
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
33
|
+
}
|
|
34
|
+
async function awaitReview(args) {
|
|
35
|
+
const { values } = parseArgs({
|
|
36
|
+
args,
|
|
37
|
+
options: {
|
|
38
|
+
timeout: {
|
|
39
|
+
type: "string",
|
|
40
|
+
short: "t"
|
|
41
|
+
},
|
|
42
|
+
since: { type: "string" }
|
|
43
|
+
},
|
|
44
|
+
allowPositionals: false
|
|
45
|
+
});
|
|
46
|
+
const totalBudgetMs = (values.timeout ? Number(values.timeout) : 570) * 1e3;
|
|
47
|
+
const base = baseUrl();
|
|
48
|
+
let sinceRound = 0;
|
|
49
|
+
try {
|
|
50
|
+
sinceRound = (await fetch(`${base}/api/review/status`).then((r) => r.json())).round ?? 0;
|
|
51
|
+
} catch {}
|
|
52
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
let res;
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(`${base}/api/review/await?timeoutMs=25000&sinceRound=${sinceRound}`, { signal: AbortSignal.timeout(3e4) });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err?.name === "TimeoutError") continue;
|
|
59
|
+
console.error(`Failed to reach diffit server: ${err?.message ?? err}`);
|
|
60
|
+
return EXIT_NO_SERVER;
|
|
61
|
+
}
|
|
62
|
+
const result = await res.json();
|
|
63
|
+
if (result.status === "released") {
|
|
64
|
+
process.stdout.write(result.payload.commentXml + "\n");
|
|
65
|
+
console.error(`DIFFIT_REVIEW_ROUND=${result.payload.round}`);
|
|
66
|
+
return EXIT_OK;
|
|
67
|
+
}
|
|
68
|
+
sinceRound = result.round ?? sinceRound;
|
|
69
|
+
}
|
|
70
|
+
console.error("DIFFIT_AWAIT_TIMEOUT");
|
|
71
|
+
console.error("No review sent within the timeout. Run `diffit await-review` again to keep waiting.");
|
|
72
|
+
return EXIT_AWAIT_TIMEOUT;
|
|
73
|
+
}
|
|
74
|
+
async function reply(args) {
|
|
75
|
+
const { values, positionals } = parseArgs({
|
|
76
|
+
args,
|
|
77
|
+
options: {
|
|
78
|
+
body: {
|
|
79
|
+
type: "string",
|
|
80
|
+
short: "b"
|
|
81
|
+
},
|
|
82
|
+
model: {
|
|
83
|
+
type: "string",
|
|
84
|
+
short: "m"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
allowPositionals: true
|
|
88
|
+
});
|
|
89
|
+
const commentId = positionals[0];
|
|
90
|
+
if (!commentId) {
|
|
91
|
+
console.error("Usage: diffit reply <commentId> --body <text> [--model <name>]");
|
|
92
|
+
return EXIT_USAGE;
|
|
93
|
+
}
|
|
94
|
+
let body = values.body;
|
|
95
|
+
if (body === "-" || body === void 0) body = (await readStdin()).trim();
|
|
96
|
+
if (!body) {
|
|
97
|
+
console.error("A reply body is required (--body <text> or pipe via stdin).");
|
|
98
|
+
return EXIT_USAGE;
|
|
99
|
+
}
|
|
100
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}/replies`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
body,
|
|
105
|
+
role: "agent",
|
|
106
|
+
model: values.model
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
if (res.status === 404) {
|
|
110
|
+
console.error(`Comment ${commentId} not found.`);
|
|
111
|
+
return EXIT_NOT_FOUND;
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
console.error(`Failed to reply: HTTP ${res.status}`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
console.error(`Replied to ${commentId}.`);
|
|
118
|
+
return EXIT_OK;
|
|
119
|
+
}
|
|
120
|
+
async function resolve(args) {
|
|
121
|
+
const { positionals } = parseArgs({
|
|
122
|
+
args,
|
|
123
|
+
allowPositionals: true,
|
|
124
|
+
options: {}
|
|
125
|
+
});
|
|
126
|
+
const commentId = positionals[0];
|
|
127
|
+
if (!commentId) {
|
|
128
|
+
console.error("Usage: diffit resolve <commentId>");
|
|
129
|
+
return EXIT_USAGE;
|
|
130
|
+
}
|
|
131
|
+
const res = await fetch(`${baseUrl()}/api/comments/${commentId}`, {
|
|
132
|
+
method: "PUT",
|
|
133
|
+
headers: { "Content-Type": "application/json" },
|
|
134
|
+
body: JSON.stringify({ status: "resolved" })
|
|
135
|
+
});
|
|
136
|
+
if (res.status === 404) {
|
|
137
|
+
console.error(`Comment ${commentId} not found.`);
|
|
138
|
+
return EXIT_NOT_FOUND;
|
|
139
|
+
}
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
console.error(`Failed to resolve: HTTP ${res.status}`);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
console.error(`Resolved ${commentId}.`);
|
|
145
|
+
return EXIT_OK;
|
|
146
|
+
}
|
|
147
|
+
async function comments(args) {
|
|
148
|
+
const { values } = parseArgs({
|
|
149
|
+
args,
|
|
150
|
+
options: {
|
|
151
|
+
open: { type: "boolean" },
|
|
152
|
+
json: { type: "boolean" }
|
|
153
|
+
},
|
|
154
|
+
allowPositionals: false
|
|
155
|
+
});
|
|
156
|
+
const all = await fetch(`${baseUrl()}/api/comments`).then((r) => r.json());
|
|
157
|
+
const selected = values.open ? all.filter((c) => c.status === "open") : all;
|
|
158
|
+
if (values.json) process.stdout.write(JSON.stringify(selected, null, 2) + "\n");
|
|
159
|
+
else process.stdout.write(formatComments(selected) + "\n");
|
|
160
|
+
return EXIT_OK;
|
|
161
|
+
}
|
|
162
|
+
async function runSubcommand(name, args) {
|
|
163
|
+
switch (name) {
|
|
164
|
+
case "await-review": return awaitReview(args);
|
|
165
|
+
case "reply": return reply(args);
|
|
166
|
+
case "resolve": return resolve(args);
|
|
167
|
+
case "comments": return comments(args);
|
|
168
|
+
default:
|
|
169
|
+
console.error(`Unknown subcommand: ${name}`);
|
|
170
|
+
return EXIT_USAGE;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
export { runSubcommand };
|