@peaske7/readit 0.3.0-rc.0 → 0.3.0-rc.2
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 +3 -3
- package/dist/.vite/manifest.json +1111 -0
- package/dist/assets/_basePickBy-BMMA4Tou.js +1 -0
- package/dist/assets/_baseUniq-D40qku1I.js +1 -0
- package/dist/assets/arc-Ckg65iy8.js +1 -0
- package/dist/assets/architecture-YZFGNWBL-Dv3EY0zV.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DQnkwSaB.js +36 -0
- package/dist/assets/array-Bjz-wYpJ.js +1 -0
- package/dist/assets/blockDiagram-DXYQGD6D-UB6_S1lm.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-sn3k2GND.js +10 -0
- package/dist/assets/channel-D9wPw2fQ.js +1 -0
- package/dist/assets/chunk-2KRD3SAO-DaFfaCGO.js +1 -0
- package/dist/assets/chunk-336JU56O-C8siO5Of.js +2 -0
- package/dist/assets/chunk-426QAEUC-BB478m3j.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-DRuTD7x5.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-_l6jvVAY.js +206 -0
- package/dist/assets/chunk-55IACEB6-BExiaAoD.js +1 -0
- package/dist/assets/chunk-5FUZZQ4R-HOSFTxuG.js +62 -0
- package/dist/assets/chunk-5PVQY5BW-BRVNNRAX.js +2 -0
- package/dist/assets/chunk-67CJDMHE-DMt8LNEX.js +1 -0
- package/dist/assets/chunk-7N4EOEYR-CzLGefVf.js +1 -0
- package/dist/assets/chunk-AA7GKIK3-B6GFAk4U.js +1 -0
- package/dist/assets/chunk-BSJP7CBP-BK29yehL.js +1 -0
- package/dist/assets/chunk-CIAEETIT-D7hBXImP.js +1 -0
- package/dist/assets/chunk-Dlc7tRH4.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-CSvKh9DT.js +1 -0
- package/dist/assets/chunk-ENJZ2VHE-QApb5cYr.js +10 -0
- package/dist/assets/chunk-FMBD7UC4-2FWyCCAV.js +15 -0
- package/dist/assets/chunk-FOC6F5B3-DKFHrt4K.js +1 -0
- package/dist/assets/chunk-ICPOFSXX-agBjBxsW.js +122 -0
- package/dist/assets/chunk-K5T4RW27-D51O7IkG.js +94 -0
- package/dist/assets/chunk-KGLVRYIC-DMHSCH4T.js +1 -0
- package/dist/assets/chunk-LIHQZDEY-C2aANxt9.js +1 -0
- package/dist/assets/chunk-ORNJ4GCN-Db_37NRX.js +1 -0
- package/dist/assets/chunk-OYMX7WX6-HGUtT2Q9.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-8Lcg9gti.js +1 -0
- package/dist/assets/chunk-U2HBQHQK-BFYYQeuC.js +70 -0
- package/dist/assets/chunk-X2U36JSP-p8ehTP6s.js +1 -0
- package/dist/assets/chunk-XPW4576I-Bqbompq4.js +32 -0
- package/dist/assets/chunk-YZCP3GAM-HIMez9pG.js +1 -0
- package/dist/assets/chunk-ZZ45TVLE-DRIE_0bu.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-BawhEeUl.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CLNjgH9n.js +1 -0
- package/dist/assets/clone-BBjvuERA.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-q90QeGKv.js +1 -0
- package/dist/assets/cytoscape.esm-BfXff3fb.js +321 -0
- package/dist/assets/dagre-Dxbob2Lr.js +1 -0
- package/dist/assets/dagre-KV5264BT-BuvpNxMw.js +4 -0
- package/dist/assets/defaultLocale-BwmRmqJp.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DQLsxwwt.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Jv9Eefw4.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-D-0YgNhU.js +43 -0
- package/dist/assets/diagram-TYMM5635-BHwO7zQG.js +24 -0
- package/dist/assets/dist-BNz65Ibc.js +1 -0
- package/dist/assets/erDiagram-SMLLAGMA-BjZGGBJz.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-CFbFUm_m.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-CXk4TcBi.js +292 -0
- package/dist/assets/gitGraph-7Q5UKJZL-BGFRt2qs.js +1 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-C8yZOxjo.js +106 -0
- package/dist/assets/graphlib-DGcD9J2L.js +1 -0
- package/dist/assets/index-D-m0LiFI.js +14 -0
- package/dist/assets/index-DANHO6J0.css +2 -0
- package/dist/assets/info-OMHHGYJF-DI6-Z9vh.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-p-PXDra2.js +2 -0
- package/dist/assets/init-TPm5RB77.js +1 -0
- package/dist/assets/isArrayLikeObject-69BLnVNM.js +1 -0
- package/dist/assets/isEmpty-DUS28g5f.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-BrIoEvtb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-aZpvKa9g.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-CoOAY9ji.js +89 -0
- package/dist/assets/katex-5SGEXwpi.js +261 -0
- package/dist/assets/line-4MF1lR4d.js +1 -0
- package/dist/assets/linear-CXMqTN8N.js +1 -0
- package/dist/assets/mermaid-config-C8a4L22x.js +1 -0
- package/dist/assets/mermaid-parser.core-DREsY2u4.js +4 -0
- package/dist/assets/mermaid.core-8ysLpTJi.js +11 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-CsqUJCMn.js +96 -0
- package/dist/assets/ordinal-D7l-8DAO.js +1 -0
- package/dist/assets/packet-4T2RLAQJ-DidW3JFc.js +1 -0
- package/dist/assets/path-BVpCanzE.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-Bff2e5hg.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-k0Br4NDS.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-Be9oCSza.js +7 -0
- package/dist/assets/radar-PYXPWWZC-CsdZBH3M.js +1 -0
- package/dist/assets/requirementDiagram-MS252O5E-8ECT7dEs.js +84 -0
- package/dist/assets/rough.esm-BoTisKeL.js +1 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-CoKpeJJ0.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-BTT2fFxG.js +157 -0
- package/dist/assets/src-CrmkjRpa.js +1 -0
- package/dist/assets/stateDiagram-FHFEXIEX-CIF47NYe.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Cy1rmPfG.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Bes4B58n.js +120 -0
- package/dist/assets/treeView-SZITEDCU-DPKseaET.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-DH-7GZe_.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-3wx2huKk.js +34 -0
- package/dist/assets/wardley-RL74JXVD-AgyXyBN5.js +1 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DzViT1Yx.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-BO_dbU0r.js +7 -0
- package/{index.html → dist/index.html} +2 -1
- package/dist/index.js +2625 -0
- package/package.json +11 -1
- package/.agents/skills/remotion-best-practices/SKILL.md +0 -61
- package/.agents/skills/remotion-best-practices/rules/3d.md +0 -86
- package/.agents/skills/remotion-best-practices/rules/animations.md +0 -27
- package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +0 -178
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +0 -100
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +0 -108
- package/.agents/skills/remotion-best-practices/rules/assets.md +0 -78
- package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +0 -198
- package/.agents/skills/remotion-best-practices/rules/audio.md +0 -169
- package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +0 -134
- package/.agents/skills/remotion-best-practices/rules/can-decode.md +0 -75
- package/.agents/skills/remotion-best-practices/rules/charts.md +0 -120
- package/.agents/skills/remotion-best-practices/rules/compositions.md +0 -154
- package/.agents/skills/remotion-best-practices/rules/display-captions.md +0 -184
- package/.agents/skills/remotion-best-practices/rules/extract-frames.md +0 -229
- package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +0 -38
- package/.agents/skills/remotion-best-practices/rules/fonts.md +0 -152
- package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +0 -58
- package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +0 -68
- package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +0 -60
- package/.agents/skills/remotion-best-practices/rules/gifs.md +0 -141
- package/.agents/skills/remotion-best-practices/rules/images.md +0 -134
- package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +0 -69
- package/.agents/skills/remotion-best-practices/rules/light-leaks.md +0 -73
- package/.agents/skills/remotion-best-practices/rules/lottie.md +0 -70
- package/.agents/skills/remotion-best-practices/rules/maps.md +0 -412
- package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +0 -34
- package/.agents/skills/remotion-best-practices/rules/measuring-text.md +0 -140
- package/.agents/skills/remotion-best-practices/rules/parameters.md +0 -109
- package/.agents/skills/remotion-best-practices/rules/sequencing.md +0 -118
- package/.agents/skills/remotion-best-practices/rules/sfx.md +0 -26
- package/.agents/skills/remotion-best-practices/rules/subtitles.md +0 -36
- package/.agents/skills/remotion-best-practices/rules/tailwind.md +0 -11
- package/.agents/skills/remotion-best-practices/rules/text-animations.md +0 -20
- package/.agents/skills/remotion-best-practices/rules/timing.md +0 -179
- package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +0 -70
- package/.agents/skills/remotion-best-practices/rules/transitions.md +0 -197
- package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +0 -106
- package/.agents/skills/remotion-best-practices/rules/trimming.md +0 -51
- package/.agents/skills/remotion-best-practices/rules/videos.md +0 -171
- package/.agents/skills/remotion-best-practices/rules/voiceover.md +0 -99
- package/.agents/skills/simple/SKILL.md +0 -52
- package/.agents/skills/vercel-react-best-practices/AGENTS.md +0 -3254
- package/.agents/skills/vercel-react-best-practices/README.md +0 -123
- package/.agents/skills/vercel-react-best-practices/SKILL.md +0 -141
- package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +0 -42
- package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -39
- package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
- package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -51
- package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -107
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +0 -60
- package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
- package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +0 -30
- package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +0 -85
- package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +0 -68
- package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +0 -75
- package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -35
- package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +0 -73
- package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +0 -96
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +0 -65
- package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +0 -142
- package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/.claude/CLAUDE.md +0 -184
- package/.claude/commands/review.md +0 -120
- package/.claude/commands/sync-docs.md +0 -71
- package/.claude/roadmap.md +0 -121
- package/.claude/rules/style-guide.md +0 -830
- package/.claude/settings.json +0 -18
- package/.claude/user-stories.md +0 -333
- package/AGENTS.md +0 -68
- package/Makefile +0 -32
- package/biome.json +0 -79
- package/bun.lock +0 -854
- package/bunfig.toml +0 -2
- package/docs/design.md +0 -563
- package/docs/perf-baseline.md +0 -130
- package/docs/plans/2026-03-13-client-mode-design.md +0 -86
- package/docs/plans/2026-03-13-client-mode-plan.md +0 -605
- package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +0 -129
- package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +0 -1471
- package/docs/plans/2026-03-13-multi-document-design.md +0 -183
- package/docs/plans/2026-03-13-performance-benchmarks-design.md +0 -121
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +0 -1176
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +0 -284
- package/e2e/comments.spec.ts +0 -81
- package/e2e/document-load.spec.ts +0 -32
- package/e2e/export.spec.ts +0 -58
- package/e2e/fixtures/sample.md +0 -7
- package/e2e/perf/add-comment.spec.ts +0 -116
- package/e2e/perf/fixtures/generate.ts +0 -327
- package/e2e/perf/initial-load.spec.ts +0 -49
- package/e2e/perf/perf.setup.ts +0 -23
- package/e2e/perf/perf.teardown.ts +0 -9
- package/e2e/perf/screenshot-final.png +0 -0
- package/e2e/perf/scroll.spec.ts +0 -39
- package/e2e/perf/tab-switch.spec.ts +0 -69
- package/e2e/perf/text-selection.spec.ts +0 -119
- package/e2e/perf/utils/metrics.ts +0 -350
- package/e2e/perf/utils/perf-cli.ts +0 -86
- package/e2e/persistence-file.spec.ts +0 -357
- package/e2e/utils/cli.ts +0 -84
- package/e2e/utils/selection.ts +0 -79
- package/go/cmd/readit/main.go +0 -416
- package/go/go.mod +0 -20
- package/go/go.sum +0 -41
- package/go/internal/server/anchor.go +0 -302
- package/go/internal/server/anchor_test.go +0 -111
- package/go/internal/server/comments.go +0 -390
- package/go/internal/server/documents.go +0 -113
- package/go/internal/server/embed.go +0 -17
- package/go/internal/server/headings.go +0 -33
- package/go/internal/server/headings_test.go +0 -75
- package/go/internal/server/htmltext.go +0 -123
- package/go/internal/server/markdown.go +0 -157
- package/go/internal/server/markdown_bench_test.go +0 -42
- package/go/internal/server/markdown_test.go +0 -79
- package/go/internal/server/server.go +0 -453
- package/go/internal/server/server_bench_test.go +0 -122
- package/go/internal/server/settings.go +0 -110
- package/go/internal/server/sse.go +0 -140
- package/go/internal/server/storage.go +0 -275
- package/go/internal/server/storage_test.go +0 -152
- package/go/internal/server/template.go +0 -66
- package/go/internal/server/types.go +0 -101
- package/go/internal/server/watcher.go +0 -74
- package/lefthook.yml +0 -8
- package/nvim-readit/lua/readit/health.lua +0 -64
- package/nvim-readit/lua/readit/init.lua +0 -463
- package/nvim-readit/plugin/readit.lua +0 -19
- package/playwright.config.ts +0 -34
- package/skills-lock.json +0 -20
- package/src/App.svelte +0 -890
- package/src/cli.ts +0 -881
- package/src/components/ActionsMenu.svelte +0 -95
- package/src/components/CommentBadge.svelte +0 -67
- package/src/components/CommentErrorBanner.svelte +0 -33
- package/src/components/CommentInput.svelte +0 -75
- package/src/components/CommentListItem.svelte +0 -95
- package/src/components/CommentManager.svelte +0 -129
- package/src/components/CommentNav.svelte +0 -109
- package/src/components/DocumentViewer.svelte +0 -233
- package/src/components/FloatingComment.svelte +0 -107
- package/src/components/Header.svelte +0 -76
- package/src/components/InlineEditor.svelte +0 -72
- package/src/components/MarginNote.svelte +0 -167
- package/src/components/MarginNotesContainer.svelte +0 -33
- package/src/components/MermaidEnhancer.svelte +0 -218
- package/src/components/MermaidModal.svelte +0 -67
- package/src/components/RawModal.svelte +0 -126
- package/src/components/ReanchorConfirm.svelte +0 -30
- package/src/components/SettingsModal.svelte +0 -220
- package/src/components/ShortcutCapture.svelte +0 -82
- package/src/components/ShortcutList.svelte +0 -145
- package/src/components/TabBar.svelte +0 -52
- package/src/components/TableOfContents.svelte +0 -125
- package/src/components/ui/ActionLink.svelte +0 -40
- package/src/components/ui/Button.svelte +0 -53
- package/src/components/ui/Dialog.svelte +0 -97
- package/src/components/ui/DropdownMenu.svelte +0 -85
- package/src/components/ui/DropdownMenuItem.svelte +0 -38
- package/src/components/ui/DropdownMenuSeparator.svelte +0 -11
- package/src/components/ui/Text.svelte +0 -42
- package/src/env.d.ts +0 -6
- package/src/index.css +0 -859
- package/src/lib/__fixtures__/bench-data.ts +0 -114
- package/src/lib/anchor.bench.ts +0 -91
- package/src/lib/anchor.test.ts +0 -527
- package/src/lib/anchor.ts +0 -381
- package/src/lib/comment-storage.bench.ts +0 -49
- package/src/lib/comment-storage.test.ts +0 -694
- package/src/lib/comment-storage.ts +0 -226
- package/src/lib/export.bench.ts +0 -21
- package/src/lib/export.ts +0 -36
- package/src/lib/fetch-or-throw.test.ts +0 -59
- package/src/lib/fetch-or-throw.ts +0 -12
- package/src/lib/headings.test.ts +0 -103
- package/src/lib/headings.ts +0 -44
- package/src/lib/highlight/core.test.ts +0 -93
- package/src/lib/highlight/dom.ts +0 -187
- package/src/lib/highlight/highlight-registry.ts +0 -221
- package/src/lib/highlight/highlight.bench.ts +0 -92
- package/src/lib/highlight/highlighter.ts +0 -247
- package/src/lib/highlight/resolver.ts +0 -38
- package/src/lib/highlight/types.ts +0 -17
- package/src/lib/html-text.test.ts +0 -162
- package/src/lib/html-text.ts +0 -161
- package/src/lib/i18n/en.ts +0 -124
- package/src/lib/i18n/index.ts +0 -3
- package/src/lib/i18n/ja.ts +0 -126
- package/src/lib/i18n/translations.ts +0 -27
- package/src/lib/i18n/types.ts +0 -130
- package/src/lib/key-lock.test.ts +0 -104
- package/src/lib/key-lock.ts +0 -23
- package/src/lib/margin-layout.bench.ts +0 -61
- package/src/lib/margin-layout.ts +0 -71
- package/src/lib/markdown-renderer.test.ts +0 -154
- package/src/lib/markdown-renderer.ts +0 -178
- package/src/lib/mermaid-config.ts +0 -38
- package/src/lib/mermaid-renderer.ts +0 -162
- package/src/lib/mermaid-worker.ts +0 -60
- package/src/lib/positions.ts +0 -157
- package/src/lib/shortcut-registry.ts +0 -244
- package/src/lib/utils.ts +0 -15
- package/src/main.ts +0 -16
- package/src/schema.ts +0 -92
- package/src/server.ts +0 -1216
- package/src/stores/app.svelte.ts +0 -231
- package/src/stores/locale.svelte.ts +0 -46
- package/src/stores/settings.svelte.ts +0 -90
- package/src/stores/shortcuts.svelte.ts +0 -104
- package/src/stores/ui.svelte.ts +0 -12
- package/src/template.ts +0 -104
- package/src/test-setup.ts +0 -48
- package/svelte.config.js +0 -5
- package/test.md +0 -74
- package/tsconfig.cli.json +0 -12
- package/tsconfig.json +0 -20
- package/vite.config.ts +0 -47
- package/vitest.config.ts +0 -15
- package/vscode-readit/.mcp.json +0 -7
- package/vscode-readit/.vscodeignore +0 -7
- package/vscode-readit/bun.lock +0 -78
- package/vscode-readit/icon.svg +0 -10
- package/vscode-readit/package.json +0 -110
- package/vscode-readit/src/extension.ts +0 -117
- package/vscode-readit/src/server-manager.ts +0 -272
- package/vscode-readit/src/webview-provider.ts +0 -204
- package/vscode-readit/tsconfig.json +0 -20
package/src/cli.ts
DELETED
|
@@ -1,881 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
existsSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
readdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
realpathSync,
|
|
9
|
-
statSync,
|
|
10
|
-
} from "node:fs";
|
|
11
|
-
import * as fs from "node:fs/promises";
|
|
12
|
-
import * as os from "node:os";
|
|
13
|
-
import { join, resolve } from "node:path";
|
|
14
|
-
import { Command } from "commander";
|
|
15
|
-
import open from "open";
|
|
16
|
-
import { getCommentPath, parseCommentFile } from "./lib/comment-storage.js";
|
|
17
|
-
import { isMarkdownFile } from "./lib/utils.js";
|
|
18
|
-
import type { FileEntry } from "./server.js";
|
|
19
|
-
import { removeServerInfo, startServer } from "./server.js";
|
|
20
|
-
|
|
21
|
-
const program = new Command();
|
|
22
|
-
|
|
23
|
-
function isPermissionError(err: unknown): boolean {
|
|
24
|
-
return (
|
|
25
|
-
err instanceof Error &&
|
|
26
|
-
"code" in err &&
|
|
27
|
-
(err as NodeJS.ErrnoException).code === "EACCES"
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ServerInfo {
|
|
32
|
-
port: number;
|
|
33
|
-
pid: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface ServerTarget {
|
|
37
|
-
kind: "existing" | "started";
|
|
38
|
-
port: number;
|
|
39
|
-
url: string;
|
|
40
|
-
server?: { stop(): void };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const READIT_DIR = join(os.homedir(), ".readit");
|
|
44
|
-
const SERVER_INFO_PATH = join(READIT_DIR, "server.json");
|
|
45
|
-
const SERVER_LOCK_PATH = join(READIT_DIR, "server.lock");
|
|
46
|
-
const SERVER_LOCK_MAX_AGE_MS = 30_000;
|
|
47
|
-
const SERVER_LOCK_TIMEOUT_MS = 10_000;
|
|
48
|
-
const SERVER_LOCK_WAIT_MS = 100;
|
|
49
|
-
|
|
50
|
-
function isAlive(pid: number): boolean {
|
|
51
|
-
try {
|
|
52
|
-
process.kill(pid, 0);
|
|
53
|
-
return true;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getErrnoCode(err: unknown): string | undefined {
|
|
60
|
-
return err instanceof Error && "code" in err
|
|
61
|
-
? (err as NodeJS.ErrnoException).code
|
|
62
|
-
: undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function sleep(ms: number): Promise<void> {
|
|
66
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function clearStaleServerLock(): Promise<void> {
|
|
70
|
-
try {
|
|
71
|
-
const [stats, content] = await Promise.all([
|
|
72
|
-
fs.stat(SERVER_LOCK_PATH),
|
|
73
|
-
fs.readFile(SERVER_LOCK_PATH, "utf-8").catch(() => ""),
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
const age = Date.now() - stats.mtimeMs;
|
|
77
|
-
let pid: number | undefined;
|
|
78
|
-
|
|
79
|
-
if (content) {
|
|
80
|
-
try {
|
|
81
|
-
const lock = JSON.parse(content) as { pid?: number };
|
|
82
|
-
pid = lock.pid;
|
|
83
|
-
} catch {}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (age > SERVER_LOCK_MAX_AGE_MS || (pid !== undefined && !isAlive(pid))) {
|
|
87
|
-
await fs.unlink(SERVER_LOCK_PATH).catch(() => {});
|
|
88
|
-
}
|
|
89
|
-
} catch (err) {
|
|
90
|
-
if (getErrnoCode(err) !== "ENOENT") throw err;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function withServerLock<T>(run: () => Promise<T>): Promise<T> {
|
|
95
|
-
await fs.mkdir(READIT_DIR, { recursive: true });
|
|
96
|
-
const start = Date.now();
|
|
97
|
-
|
|
98
|
-
while (true) {
|
|
99
|
-
let handle: fs.FileHandle | undefined;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
handle = await fs.open(SERVER_LOCK_PATH, "wx");
|
|
103
|
-
await handle.writeFile(
|
|
104
|
-
JSON.stringify({ pid: process.pid, createdAt: Date.now() }),
|
|
105
|
-
"utf-8",
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
return await run();
|
|
110
|
-
} finally {
|
|
111
|
-
await handle.close().catch(() => {});
|
|
112
|
-
await fs.unlink(SERVER_LOCK_PATH).catch(() => {});
|
|
113
|
-
}
|
|
114
|
-
} catch (err) {
|
|
115
|
-
if (handle) {
|
|
116
|
-
await handle.close().catch(() => {});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (getErrnoCode(err) !== "EEXIST") {
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await clearStaleServerLock();
|
|
124
|
-
|
|
125
|
-
if (Date.now() - start >= SERVER_LOCK_TIMEOUT_MS) {
|
|
126
|
-
throw new Error("Timed out waiting for readit server lock");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
await sleep(SERVER_LOCK_WAIT_MS);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function discoverServer(): Promise<ServerInfo | null> {
|
|
135
|
-
try {
|
|
136
|
-
const content = readFileSync(SERVER_INFO_PATH, "utf-8");
|
|
137
|
-
const info: ServerInfo = JSON.parse(content);
|
|
138
|
-
|
|
139
|
-
if (!isAlive(info.pid)) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const res = await fetch(`http://127.0.0.1:${info.port}/api/health`);
|
|
145
|
-
if (!res.ok) return null;
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return info;
|
|
151
|
-
} catch {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function attachFiles(
|
|
157
|
-
server: ServerInfo,
|
|
158
|
-
files: { path: string }[],
|
|
159
|
-
): Promise<void> {
|
|
160
|
-
for (const file of files) {
|
|
161
|
-
try {
|
|
162
|
-
const res = await fetch(`http://127.0.0.1:${server.port}/api/documents`, {
|
|
163
|
-
method: "POST",
|
|
164
|
-
headers: { "Content-Type": "application/json" },
|
|
165
|
-
body: JSON.stringify({ path: file.path }),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (!res.ok) {
|
|
169
|
-
const data = await res.json();
|
|
170
|
-
console.error(`error: failed to add ${file.path}: ${data.error}`);
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const data = await res.json();
|
|
175
|
-
if (data.status === "added") {
|
|
176
|
-
console.log(`Added: ${data.fileName}`);
|
|
177
|
-
} else {
|
|
178
|
-
console.log(`Present: ${data.fileName}`);
|
|
179
|
-
}
|
|
180
|
-
} catch (err) {
|
|
181
|
-
console.error(
|
|
182
|
-
"error: failed to connect to server:",
|
|
183
|
-
err instanceof Error ? err.message : err,
|
|
184
|
-
);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function getServerTarget(
|
|
191
|
-
files: FileEntry[],
|
|
192
|
-
port: number,
|
|
193
|
-
host: string,
|
|
194
|
-
): Promise<ServerTarget> {
|
|
195
|
-
return withServerLock(async () => {
|
|
196
|
-
const server = await discoverServer();
|
|
197
|
-
if (server) {
|
|
198
|
-
return {
|
|
199
|
-
kind: "existing",
|
|
200
|
-
port: server.port,
|
|
201
|
-
url: `http://127.0.0.1:${server.port}`,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const started = await startServer({ files, port, host });
|
|
206
|
-
return {
|
|
207
|
-
kind: "started",
|
|
208
|
-
port: started.port,
|
|
209
|
-
url: started.url,
|
|
210
|
-
server: started.server,
|
|
211
|
-
};
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function findCommentFiles(dir: string): string[] {
|
|
216
|
-
const results: string[] = [];
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const entries = readdirSync(dir);
|
|
220
|
-
for (const entry of entries) {
|
|
221
|
-
const fullPath = join(dir, entry);
|
|
222
|
-
try {
|
|
223
|
-
const lstat = lstatSync(fullPath);
|
|
224
|
-
if (lstat.isSymbolicLink()) continue;
|
|
225
|
-
if (lstat.isDirectory()) {
|
|
226
|
-
results.push(...findCommentFiles(fullPath));
|
|
227
|
-
} else if (entry.endsWith(".comments.md")) {
|
|
228
|
-
results.push(fullPath);
|
|
229
|
-
}
|
|
230
|
-
} catch (err) {
|
|
231
|
-
if (isPermissionError(err)) {
|
|
232
|
-
console.warn(`Warning: Permission denied: ${fullPath}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
} catch (err) {
|
|
237
|
-
if (isPermissionError(err)) {
|
|
238
|
-
console.warn(`Warning: Permission denied: ${dir}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return results;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function findReviewableFiles(dir: string): FileEntry[] {
|
|
246
|
-
const results: FileEntry[] = [];
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const entries = readdirSync(dir);
|
|
250
|
-
for (const entry of entries) {
|
|
251
|
-
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
252
|
-
|
|
253
|
-
const fullPath = join(dir, entry);
|
|
254
|
-
try {
|
|
255
|
-
const lstat = lstatSync(fullPath);
|
|
256
|
-
if (lstat.isSymbolicLink()) continue;
|
|
257
|
-
if (lstat.isDirectory()) {
|
|
258
|
-
results.push(...findReviewableFiles(fullPath));
|
|
259
|
-
} else if (isMarkdownFile(entry)) {
|
|
260
|
-
results.push({ filePath: fullPath });
|
|
261
|
-
}
|
|
262
|
-
} catch (err) {
|
|
263
|
-
if (isPermissionError(err)) {
|
|
264
|
-
console.warn(`Warning: Permission denied: ${fullPath}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
} catch (err) {
|
|
269
|
-
if (isPermissionError(err)) {
|
|
270
|
-
console.warn(`Warning: Permission denied: ${dir}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return results;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function resolveFiles(args: string[]): FileEntry[] {
|
|
278
|
-
const seen = new Set<string>();
|
|
279
|
-
const files: FileEntry[] = [];
|
|
280
|
-
|
|
281
|
-
for (const arg of args) {
|
|
282
|
-
const inputPath = resolve(process.cwd(), arg);
|
|
283
|
-
|
|
284
|
-
if (!existsSync(inputPath)) {
|
|
285
|
-
console.error(`error: not found: ${inputPath}`);
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const filePath = realpathSync(inputPath);
|
|
290
|
-
|
|
291
|
-
const stat = statSync(filePath);
|
|
292
|
-
|
|
293
|
-
if (stat.isDirectory()) {
|
|
294
|
-
const found = findReviewableFiles(filePath);
|
|
295
|
-
for (const entry of found) {
|
|
296
|
-
if (!seen.has(entry.filePath)) {
|
|
297
|
-
seen.add(entry.filePath);
|
|
298
|
-
files.push(entry);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
} else {
|
|
302
|
-
if (seen.has(filePath)) continue;
|
|
303
|
-
|
|
304
|
-
if (!isMarkdownFile(filePath)) {
|
|
305
|
-
console.error(
|
|
306
|
-
`error: unsupported file type: ${arg} (expected .md or .markdown)`,
|
|
307
|
-
);
|
|
308
|
-
process.exit(1);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
seen.add(filePath);
|
|
312
|
-
files.push({ filePath });
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return files;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const SETTINGS_PATH = join(os.homedir(), ".readit", "settings.json");
|
|
320
|
-
|
|
321
|
-
function isOnboarded(): boolean {
|
|
322
|
-
try {
|
|
323
|
-
const content = readFileSync(SETTINGS_PATH, "utf-8");
|
|
324
|
-
const settings = JSON.parse(content);
|
|
325
|
-
return settings.onboarded === true;
|
|
326
|
-
} catch {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function markOnboarded(): Promise<void> {
|
|
332
|
-
let settings: Record<string, unknown> = {};
|
|
333
|
-
try {
|
|
334
|
-
const content = readFileSync(SETTINGS_PATH, "utf-8");
|
|
335
|
-
settings = JSON.parse(content);
|
|
336
|
-
} catch {}
|
|
337
|
-
settings.onboarded = true;
|
|
338
|
-
const dir = join(os.homedir(), ".readit");
|
|
339
|
-
await fs.mkdir(dir, { recursive: true });
|
|
340
|
-
await fs.writeFile(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const WELCOME_CONTENT = `# Welcome to readit
|
|
344
|
-
|
|
345
|
-
A simple tool for reviewing markdown with inline comments.
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## How It Works
|
|
350
|
-
|
|
351
|
-
readit follows a simple loop: **read → comment → extract**.
|
|
352
|
-
|
|
353
|
-
### 1. Read
|
|
354
|
-
|
|
355
|
-
You're already doing this. Open any markdown file with \`readit <file.md>\` and it renders in your browser with a clean reading experience.
|
|
356
|
-
|
|
357
|
-
### 2. Comment
|
|
358
|
-
|
|
359
|
-
Select any text to add a comment. Try it now — **select this sentence** and type your first comment.
|
|
360
|
-
|
|
361
|
-
Your comments appear as margin notes next to the highlighted text, just like reviewing a document in Google Docs. Add as many as you need.
|
|
362
|
-
|
|
363
|
-
### 3. Extract
|
|
364
|
-
|
|
365
|
-
When you're done reviewing, click the menu in the top-right and choose **Copy as Prompt**. This exports all your comments in a format ready for Claude, ChatGPT, or any AI assistant.
|
|
366
|
-
|
|
367
|
-
You can also export as JSON if you prefer structured data.
|
|
368
|
-
|
|
369
|
-
---
|
|
370
|
-
|
|
371
|
-
## Everything is Plain Markdown
|
|
372
|
-
|
|
373
|
-
Your comments are saved as \`.comments.md\` files in \`~/.readit/comments/\`. No database, no lock-in — just readable markdown files you can version control, search, or edit by hand.
|
|
374
|
-
|
|
375
|
-
Each comment file looks something like this:
|
|
376
|
-
|
|
377
|
-
\`\`\`markdown
|
|
378
|
-
## Comment 1
|
|
379
|
-
**Selected:** "select this sentence"
|
|
380
|
-
**Comment:** This is my first comment!
|
|
381
|
-
**Created:** 2024-01-15T10:30:00Z
|
|
382
|
-
\`\`\`
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
## Navigating Comments
|
|
387
|
-
|
|
388
|
-
Once you have multiple comments, use the navigation bar at the bottom of the screen to jump between them. You can also use keyboard shortcuts:
|
|
389
|
-
|
|
390
|
-
| Shortcut | Action |
|
|
391
|
-
|----------|--------|
|
|
392
|
-
| \`Alt + ↑\` | Previous comment |
|
|
393
|
-
| \`Alt + ↓\` | Next comment |
|
|
394
|
-
| \`⌘ + C\` | Copy selected text (raw) |
|
|
395
|
-
| \`⌘ + Shift + C\` | Copy selected text with context (for AI) |
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Quick Start
|
|
400
|
-
|
|
401
|
-
\`\`\`bash
|
|
402
|
-
# Review a markdown file
|
|
403
|
-
readit document.md
|
|
404
|
-
|
|
405
|
-
# Use a custom port
|
|
406
|
-
readit document.md --port 3000
|
|
407
|
-
|
|
408
|
-
# Start fresh (clear existing comments)
|
|
409
|
-
readit document.md --clean
|
|
410
|
-
\`\`\`
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## Try It Now
|
|
415
|
-
|
|
416
|
-
Go ahead and add a few comments to this document. When you're done, export them and see the output. That's the entire workflow — simple, transparent, and designed for reviewing AI-generated content.
|
|
417
|
-
`;
|
|
418
|
-
|
|
419
|
-
const WELCOME_PATH = join(os.homedir(), ".readit", "welcome.md");
|
|
420
|
-
|
|
421
|
-
program
|
|
422
|
-
.name("readit")
|
|
423
|
-
.description("Review Markdown documents with inline comments")
|
|
424
|
-
.version("0.2.0");
|
|
425
|
-
|
|
426
|
-
program
|
|
427
|
-
.command("list")
|
|
428
|
-
.description("List all files with comments")
|
|
429
|
-
.action(async () => {
|
|
430
|
-
const readitDir = join(os.homedir(), ".readit", "comments");
|
|
431
|
-
|
|
432
|
-
if (!existsSync(readitDir)) {
|
|
433
|
-
console.log("No comments found.");
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const commentFiles = findCommentFiles(readitDir);
|
|
438
|
-
|
|
439
|
-
if (commentFiles.length === 0) {
|
|
440
|
-
console.log("No comments found.");
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
console.log(`\nFound ${commentFiles.length} file(s) with comments:\n`);
|
|
445
|
-
|
|
446
|
-
for (const file of commentFiles) {
|
|
447
|
-
try {
|
|
448
|
-
const content = readFileSync(file, "utf-8");
|
|
449
|
-
const parsed = parseCommentFile(content);
|
|
450
|
-
const commentCount = parsed.comments.length;
|
|
451
|
-
const sourcePath = parsed.source || "(unknown source)";
|
|
452
|
-
|
|
453
|
-
console.log(` ${sourcePath}`);
|
|
454
|
-
console.log(
|
|
455
|
-
` ${commentCount} comment${commentCount !== 1 ? "s" : ""}`,
|
|
456
|
-
);
|
|
457
|
-
console.log();
|
|
458
|
-
} catch {}
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
program
|
|
463
|
-
.command("show <file>")
|
|
464
|
-
.description("Show comments for a file")
|
|
465
|
-
.action(async (file: string) => {
|
|
466
|
-
const filePath = resolve(process.cwd(), file);
|
|
467
|
-
const commentPath = getCommentPath(filePath);
|
|
468
|
-
|
|
469
|
-
if (!existsSync(commentPath)) {
|
|
470
|
-
console.log(`No comments found for: ${filePath}`);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
try {
|
|
475
|
-
const content = await fs.readFile(commentPath, "utf-8");
|
|
476
|
-
const parsed = parseCommentFile(content);
|
|
477
|
-
|
|
478
|
-
if (parsed.comments.length === 0) {
|
|
479
|
-
console.log(`No comments found for: ${filePath}`);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
console.log(`\nComments for: ${filePath}`);
|
|
484
|
-
console.log(`${"─".repeat(60)}\n`);
|
|
485
|
-
|
|
486
|
-
for (let i = 0; i < parsed.comments.length; i++) {
|
|
487
|
-
const comment = parsed.comments[i];
|
|
488
|
-
console.log(`[${i + 1}] ${comment.lineHint || "L?"}`);
|
|
489
|
-
console.log(
|
|
490
|
-
`Selected: "${comment.selectedText.slice(0, 80)}${comment.selectedText.length > 80 ? "..." : ""}"`,
|
|
491
|
-
);
|
|
492
|
-
console.log(`Comment: ${comment.comment}`);
|
|
493
|
-
console.log();
|
|
494
|
-
}
|
|
495
|
-
} catch (err) {
|
|
496
|
-
console.error(
|
|
497
|
-
"error: failed to read comments:",
|
|
498
|
-
err instanceof Error ? err.message : err,
|
|
499
|
-
);
|
|
500
|
-
process.exit(1);
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
program
|
|
505
|
-
.argument("[files...]", "Markdown files/directories to review")
|
|
506
|
-
.option("-p, --port <number>", "Port to run server on", "4567")
|
|
507
|
-
.option("--host <address>", "Host address to bind to", "127.0.0.1")
|
|
508
|
-
.option("--no-open", "Don't automatically open browser")
|
|
509
|
-
.option("--clean", "Clear all existing comments on startup")
|
|
510
|
-
.action(
|
|
511
|
-
async (
|
|
512
|
-
fileArgs: string[],
|
|
513
|
-
options: {
|
|
514
|
-
port: string;
|
|
515
|
-
host: string;
|
|
516
|
-
open: boolean;
|
|
517
|
-
clean: boolean;
|
|
518
|
-
},
|
|
519
|
-
) => {
|
|
520
|
-
let files: FileEntry[];
|
|
521
|
-
|
|
522
|
-
if (fileArgs.length === 0) {
|
|
523
|
-
if (isOnboarded()) {
|
|
524
|
-
files = [];
|
|
525
|
-
} else {
|
|
526
|
-
files = [
|
|
527
|
-
{
|
|
528
|
-
content: WELCOME_CONTENT,
|
|
529
|
-
filePath: WELCOME_PATH,
|
|
530
|
-
},
|
|
531
|
-
];
|
|
532
|
-
}
|
|
533
|
-
} else {
|
|
534
|
-
files = resolveFiles(fileArgs);
|
|
535
|
-
|
|
536
|
-
if (files.length === 0) {
|
|
537
|
-
console.error("error: no reviewable files found");
|
|
538
|
-
process.exit(1);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const preferredPort = Number.parseInt(options.port, 10);
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
Number.isNaN(preferredPort) ||
|
|
546
|
-
preferredPort < 1 ||
|
|
547
|
-
preferredPort > 65535
|
|
548
|
-
) {
|
|
549
|
-
console.error(`error: invalid port number: ${options.port}`);
|
|
550
|
-
process.exit(1);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
let previousPort: number | undefined;
|
|
554
|
-
try {
|
|
555
|
-
const info = JSON.parse(readFileSync(SERVER_INFO_PATH, "utf-8"));
|
|
556
|
-
if (!isAlive(info.pid)) {
|
|
557
|
-
previousPort = info.port;
|
|
558
|
-
}
|
|
559
|
-
} catch {}
|
|
560
|
-
|
|
561
|
-
try {
|
|
562
|
-
const { url, server } = await startServer({
|
|
563
|
-
files,
|
|
564
|
-
port: preferredPort,
|
|
565
|
-
host: options.host,
|
|
566
|
-
clean: options.clean,
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
if (files.length === 0) {
|
|
570
|
-
console.log(`
|
|
571
|
-
readit - Document Review Tool
|
|
572
|
-
|
|
573
|
-
URL: ${url}
|
|
574
|
-
|
|
575
|
-
No files specified. Add files with:
|
|
576
|
-
readit open <file.md>
|
|
577
|
-
|
|
578
|
-
Server running. Press Ctrl+C to stop.
|
|
579
|
-
`);
|
|
580
|
-
} else {
|
|
581
|
-
const fileList = files.map((f) => ` ${f.filePath}`);
|
|
582
|
-
|
|
583
|
-
console.log(`
|
|
584
|
-
readit - Document Review Tool
|
|
585
|
-
|
|
586
|
-
${files.length === 1 ? "File:" : "Files:"}
|
|
587
|
-
${fileList.join("\n")}
|
|
588
|
-
URL: ${url}
|
|
589
|
-
|
|
590
|
-
Server running. Close browser tab to stop.
|
|
591
|
-
Press Ctrl+C to force stop.
|
|
592
|
-
`);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const browserLikelyOpen =
|
|
596
|
-
previousPort === preferredPort ||
|
|
597
|
-
process.env.NODE_ENV === "development";
|
|
598
|
-
|
|
599
|
-
if (options.open && !browserLikelyOpen) {
|
|
600
|
-
open(url);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (fileArgs.length === 0) {
|
|
604
|
-
await markOnboarded();
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
process.on("SIGINT", async () => {
|
|
608
|
-
console.log("\n\nShutting down...");
|
|
609
|
-
server.stop();
|
|
610
|
-
await removeServerInfo();
|
|
611
|
-
process.exit(0);
|
|
612
|
-
});
|
|
613
|
-
} catch (error) {
|
|
614
|
-
console.error(
|
|
615
|
-
"error: failed to start server:",
|
|
616
|
-
error instanceof Error ? error.message : error,
|
|
617
|
-
);
|
|
618
|
-
process.exit(1);
|
|
619
|
-
}
|
|
620
|
-
},
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
program
|
|
624
|
-
.command("open")
|
|
625
|
-
.argument("<files...>", "Markdown files to add to running server")
|
|
626
|
-
.description("Add files to a running readit server, or start a new one")
|
|
627
|
-
.option("-p, --port <number>", "Port for new server (if starting)", "4567")
|
|
628
|
-
.option("--host <address>", "Host for new server (if starting)", "127.0.0.1")
|
|
629
|
-
.action(
|
|
630
|
-
async (fileArgs: string[], options: { port: string; host: string }) => {
|
|
631
|
-
const resolvedFiles: { path: string }[] = [];
|
|
632
|
-
for (const arg of fileArgs) {
|
|
633
|
-
const inputPath = resolve(process.cwd(), arg);
|
|
634
|
-
|
|
635
|
-
if (!existsSync(inputPath)) {
|
|
636
|
-
console.error(`error: not found: ${inputPath}`);
|
|
637
|
-
process.exit(1);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const filePath = realpathSync(inputPath);
|
|
641
|
-
|
|
642
|
-
if (!isMarkdownFile(filePath)) {
|
|
643
|
-
console.error(
|
|
644
|
-
`error: unsupported file type: ${arg} (expected .md or .markdown)`,
|
|
645
|
-
);
|
|
646
|
-
process.exit(1);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
resolvedFiles.push({ path: filePath });
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const files = resolvedFiles.map((f) => ({
|
|
653
|
-
filePath: f.path,
|
|
654
|
-
}));
|
|
655
|
-
|
|
656
|
-
const preferredPort = Number.parseInt(options.port, 10);
|
|
657
|
-
try {
|
|
658
|
-
const target = await getServerTarget(
|
|
659
|
-
files,
|
|
660
|
-
preferredPort,
|
|
661
|
-
options.host,
|
|
662
|
-
);
|
|
663
|
-
|
|
664
|
-
if (target.kind === "existing") {
|
|
665
|
-
await attachFiles(
|
|
666
|
-
{ port: target.port, pid: process.pid },
|
|
667
|
-
resolvedFiles,
|
|
668
|
-
);
|
|
669
|
-
console.log(`\nServer: ${target.url}`);
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const fileList = files.map((f) => ` ${f.filePath}`);
|
|
674
|
-
console.log(`
|
|
675
|
-
readit - Document Review Tool
|
|
676
|
-
|
|
677
|
-
${files.length === 1 ? "File:" : "Files:"}
|
|
678
|
-
${fileList.join("\n")}
|
|
679
|
-
URL: ${target.url}
|
|
680
|
-
|
|
681
|
-
Server running. Close browser tab to stop.
|
|
682
|
-
Press Ctrl+C to force stop.
|
|
683
|
-
`);
|
|
684
|
-
|
|
685
|
-
open(target.url);
|
|
686
|
-
|
|
687
|
-
process.on("SIGINT", async () => {
|
|
688
|
-
console.log("\n\nShutting down...");
|
|
689
|
-
target.server?.stop();
|
|
690
|
-
await removeServerInfo();
|
|
691
|
-
process.exit(0);
|
|
692
|
-
});
|
|
693
|
-
} catch (error) {
|
|
694
|
-
console.error(
|
|
695
|
-
"error: failed to start server:",
|
|
696
|
-
error instanceof Error ? error.message : error,
|
|
697
|
-
);
|
|
698
|
-
process.exit(1);
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
program
|
|
704
|
-
.command("completion")
|
|
705
|
-
.argument("[shell]", "Shell type (zsh, bash, fish)", "zsh")
|
|
706
|
-
.description("Output shell completion and integration script")
|
|
707
|
-
.action((shell: string) => {
|
|
708
|
-
const shellDir = join(import.meta.dir, "..", "shell");
|
|
709
|
-
|
|
710
|
-
switch (shell) {
|
|
711
|
-
case "zsh": {
|
|
712
|
-
// Output the full zsh integration:
|
|
713
|
-
// 1. _readit compdef (loaded into fpath via autoload) - handles @ prefix
|
|
714
|
-
// 2. readit.zsh widget (accept-line bracket stripping + syntax highlighting)
|
|
715
|
-
const widgetPath = join(shellDir, "readit.zsh");
|
|
716
|
-
const compPath = join(shellDir, "_readit");
|
|
717
|
-
|
|
718
|
-
if (!existsSync(widgetPath) || !existsSync(compPath)) {
|
|
719
|
-
console.log(generateInlineZshCompletion());
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// Wrap the compdef in an autoload function so eval works cleanly
|
|
724
|
-
const compdefContent = readFileSync(compPath, "utf-8");
|
|
725
|
-
const widgetContent = readFileSync(widgetPath, "utf-8");
|
|
726
|
-
|
|
727
|
-
const lines: string[] = [];
|
|
728
|
-
lines.push("# readit shell integration for zsh");
|
|
729
|
-
lines.push('# Add to your .zshrc: eval "$(readit completion zsh)"');
|
|
730
|
-
lines.push("");
|
|
731
|
-
lines.push("# ── _readit compdef (autoloaded) ──");
|
|
732
|
-
lines.push(
|
|
733
|
-
"# This handles: subcommand/option completion + @ file autocomplete",
|
|
734
|
-
);
|
|
735
|
-
lines.push(
|
|
736
|
-
"# Renders [file.md] in a native multi-column grid via compadd",
|
|
737
|
-
);
|
|
738
|
-
lines.push("");
|
|
739
|
-
// Replace #compdef with autoload -Uz _readit; _readit() { ... }
|
|
740
|
-
lines.push(
|
|
741
|
-
compdefContent
|
|
742
|
-
.replace(
|
|
743
|
-
/^#compdef readit\n/,
|
|
744
|
-
"autoload -Uz _readit\n_readit() {\n",
|
|
745
|
-
)
|
|
746
|
-
.replace(/\n_readit "\$@"\n?$/, "\n}\n"),
|
|
747
|
-
);
|
|
748
|
-
lines.push("");
|
|
749
|
-
lines.push("# ── readit.zsh (sourced) ──");
|
|
750
|
-
lines.push(
|
|
751
|
-
"# This handles: @[...] bracket stripping on Enter + syntax highlighting",
|
|
752
|
-
);
|
|
753
|
-
lines.push("");
|
|
754
|
-
// Strip the shebang and guard from the widget since it's being eval'd
|
|
755
|
-
lines.push(
|
|
756
|
-
widgetContent
|
|
757
|
-
.replace(/^#!/, "#")
|
|
758
|
-
.replace(/\n\(\( \$\+ functions\[_readit_plugin_loaded\] \)\)/, ""),
|
|
759
|
-
);
|
|
760
|
-
console.log(lines.join("\n"));
|
|
761
|
-
break;
|
|
762
|
-
}
|
|
763
|
-
case "bash":
|
|
764
|
-
console.log(generateBashCompletion());
|
|
765
|
-
break;
|
|
766
|
-
case "fish":
|
|
767
|
-
console.log(generateFishCompletion());
|
|
768
|
-
break;
|
|
769
|
-
default:
|
|
770
|
-
console.error(`error: unsupported shell: ${shell}`);
|
|
771
|
-
console.error("Supported shells: zsh, bash, fish");
|
|
772
|
-
process.exit(1);
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
program.parse();
|
|
777
|
-
|
|
778
|
-
function generateInlineZshCompletion(): string {
|
|
779
|
-
return `
|
|
780
|
-
#compdef readit
|
|
781
|
-
|
|
782
|
-
_readit_markdown_files() {
|
|
783
|
-
local -a files
|
|
784
|
-
files=( \${(f)"$(find . -type f \\( -name '*.md' -o -name '*.markdown' \\) -not -path '*/\\.*' -not -path '*/node_modules/*' 2>/dev/null | sed 's|^\\./||')"} )
|
|
785
|
-
_describe -t files 'markdown files' files
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
_readit() {
|
|
789
|
-
local context state state_descr line
|
|
790
|
-
typeset -A opt_args
|
|
791
|
-
_arguments -C '1:command:->cmd_or_files' '*::arg:->args'
|
|
792
|
-
case "$state" in
|
|
793
|
-
cmd_or_files)
|
|
794
|
-
local -a commands=(
|
|
795
|
-
'open:Add files to running server'
|
|
796
|
-
'list:List files with comments'
|
|
797
|
-
'show:Show comments for a file'
|
|
798
|
-
'completion:Output shell completion script'
|
|
799
|
-
)
|
|
800
|
-
_alternative 'commands:command:compadd -a commands' 'files:markdown file:_readit_markdown_files'
|
|
801
|
-
;;
|
|
802
|
-
args)
|
|
803
|
-
case "\${line[1]}" in
|
|
804
|
-
open) _arguments '*:file:_readit_markdown_files' ;;
|
|
805
|
-
show) _arguments '1:file:_files -g "*.md *.markdown"' ;;
|
|
806
|
-
*) _arguments '*:file:_readit_markdown_files' ;;
|
|
807
|
-
esac
|
|
808
|
-
;;
|
|
809
|
-
esac
|
|
810
|
-
}
|
|
811
|
-
_readit "$@"
|
|
812
|
-
`.trim();
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
function generateBashCompletion(): string {
|
|
816
|
-
return `
|
|
817
|
-
# readit bash completion
|
|
818
|
-
# Add to .bashrc: eval "$(readit completion bash)"
|
|
819
|
-
|
|
820
|
-
_readit_completions() {
|
|
821
|
-
local cur prev commands
|
|
822
|
-
COMPREPLY=()
|
|
823
|
-
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
824
|
-
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
825
|
-
commands="open list show completion"
|
|
826
|
-
|
|
827
|
-
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
828
|
-
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
829
|
-
# Also complete markdown files
|
|
830
|
-
local files=$(find . -type f \\( -name '*.md' -o -name '*.markdown' \\) \\
|
|
831
|
-
-not -path '*/.*' -not -path '*/node_modules/*' 2>/dev/null | sed 's|^\\./||')
|
|
832
|
-
COMPREPLY+=( $(compgen -W "\${files}" -- "\${cur}") )
|
|
833
|
-
return 0
|
|
834
|
-
fi
|
|
835
|
-
|
|
836
|
-
case "\${COMP_WORDS[1]}" in
|
|
837
|
-
open|show)
|
|
838
|
-
local files=$(find . -type f \\( -name '*.md' -o -name '*.markdown' \\) \\
|
|
839
|
-
-not -path '*/.*' -not -path '*/node_modules/*' 2>/dev/null | sed 's|^\\./||')
|
|
840
|
-
COMPREPLY=( $(compgen -W "\${files}" -- "\${cur}") )
|
|
841
|
-
;;
|
|
842
|
-
completion)
|
|
843
|
-
COMPREPLY=( $(compgen -W "zsh bash fish" -- "\${cur}") )
|
|
844
|
-
;;
|
|
845
|
-
esac
|
|
846
|
-
return 0
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
complete -F _readit_completions readit
|
|
850
|
-
`.trim();
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function generateFishCompletion(): string {
|
|
854
|
-
return `
|
|
855
|
-
# readit fish completion
|
|
856
|
-
# Add to config.fish: readit completion fish | source
|
|
857
|
-
|
|
858
|
-
# Disable file completions by default
|
|
859
|
-
complete -c readit -f
|
|
860
|
-
|
|
861
|
-
# Subcommands
|
|
862
|
-
complete -c readit -n '__fish_use_subcommand' -a 'open' -d 'Add files to running server'
|
|
863
|
-
complete -c readit -n '__fish_use_subcommand' -a 'list' -d 'List files with comments'
|
|
864
|
-
complete -c readit -n '__fish_use_subcommand' -a 'show' -d 'Show comments for a file'
|
|
865
|
-
complete -c readit -n '__fish_use_subcommand' -a 'completion' -d 'Output shell completion script'
|
|
866
|
-
|
|
867
|
-
# Options
|
|
868
|
-
complete -c readit -s p -l port -d 'Port to run server on'
|
|
869
|
-
complete -c readit -l host -d 'Host address to bind to'
|
|
870
|
-
complete -c readit -l no-open -d "Don't automatically open browser"
|
|
871
|
-
complete -c readit -l clean -d 'Clear existing comments'
|
|
872
|
-
|
|
873
|
-
# File arguments for default command and open
|
|
874
|
-
complete -c readit -n '__fish_use_subcommand' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
|
|
875
|
-
complete -c readit -n '__fish_seen_subcommand_from open' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
|
|
876
|
-
complete -c readit -n '__fish_seen_subcommand_from show' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
|
|
877
|
-
|
|
878
|
-
# Shell completions for completion subcommand
|
|
879
|
-
complete -c readit -n '__fish_seen_subcommand_from completion' -a 'zsh bash fish'
|
|
880
|
-
`.trim();
|
|
881
|
-
}
|