@peaske7/readit 0.1.8 → 0.2.1
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/.claude/CLAUDE.md +118 -76
- package/.claude/commands/review.md +1 -1
- package/.claude/roadmap.md +32 -9
- package/.claude/user-stories.md +100 -15
- package/AGENTS.md +30 -26
- package/Makefile +32 -0
- package/README.md +90 -5
- package/biome.json +18 -8
- package/bun.lock +426 -710
- package/bunfig.toml +2 -0
- package/docs/perf-baseline.md +130 -0
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
- package/e2e/comments.spec.ts +14 -58
- package/e2e/document-load.spec.ts +1 -23
- package/e2e/export.spec.ts +4 -4
- package/e2e/perf/add-comment.spec.ts +116 -0
- package/e2e/perf/fixtures/generate.ts +327 -0
- package/e2e/perf/initial-load.spec.ts +49 -0
- package/e2e/perf/perf.setup.ts +23 -0
- package/e2e/perf/perf.teardown.ts +9 -0
- package/e2e/perf/screenshot-final.png +0 -0
- package/e2e/perf/scroll.spec.ts +39 -0
- package/e2e/perf/tab-switch.spec.ts +69 -0
- package/e2e/perf/text-selection.spec.ts +119 -0
- package/e2e/perf/utils/metrics.ts +350 -0
- package/e2e/perf/utils/perf-cli.ts +86 -0
- package/e2e/persistence-file.spec.ts +41 -26
- package/e2e/utils/selection.ts +17 -73
- package/go/cmd/readit/main.go +416 -0
- package/go/go.mod +20 -0
- package/go/go.sum +41 -0
- package/go/internal/server/anchor.go +302 -0
- package/go/internal/server/anchor_test.go +111 -0
- package/go/internal/server/comments.go +390 -0
- package/go/internal/server/documents.go +113 -0
- package/go/internal/server/embed.go +17 -0
- package/go/internal/server/headings.go +33 -0
- package/go/internal/server/headings_test.go +75 -0
- package/go/internal/server/htmltext.go +123 -0
- package/go/internal/server/markdown.go +157 -0
- package/go/internal/server/markdown_bench_test.go +42 -0
- package/go/internal/server/markdown_test.go +79 -0
- package/go/internal/server/server.go +453 -0
- package/go/internal/server/server_bench_test.go +122 -0
- package/go/internal/server/settings.go +110 -0
- package/go/internal/server/sse.go +140 -0
- package/go/internal/server/storage.go +275 -0
- package/go/internal/server/storage_test.go +118 -0
- package/go/internal/server/template.go +66 -0
- package/go/internal/server/types.go +101 -0
- package/go/internal/server/watcher.go +74 -0
- package/index.html +4 -14
- package/nvim-readit/lua/readit/health.lua +64 -0
- package/nvim-readit/lua/readit/init.lua +463 -0
- package/nvim-readit/plugin/readit.lua +19 -0
- package/package.json +24 -41
- package/playwright.config.ts +12 -0
- package/shell/_readit +158 -0
- package/shell/readit.zsh +87 -0
- package/src/App.svelte +881 -0
- package/src/{cli/index.ts → cli.ts} +216 -70
- package/src/components/ActionsMenu.svelte +95 -0
- package/src/components/CommentBadge.svelte +67 -0
- package/src/components/CommentErrorBanner.svelte +33 -0
- package/src/components/CommentInput.svelte +75 -0
- package/src/components/CommentListItem.svelte +95 -0
- package/src/components/CommentManager.svelte +129 -0
- package/src/components/CommentNav.svelte +109 -0
- package/src/components/DocumentViewer.svelte +218 -0
- package/src/components/FloatingComment.svelte +107 -0
- package/src/components/Header.svelte +76 -0
- package/src/components/InlineEditor.svelte +72 -0
- package/src/components/MarginNote.svelte +167 -0
- package/src/components/MarginNotesContainer.svelte +33 -0
- package/src/components/RawModal.svelte +126 -0
- package/src/components/ReanchorConfirm.svelte +30 -0
- package/src/components/SettingsModal.svelte +220 -0
- package/src/components/ShortcutCapture.svelte +82 -0
- package/src/components/ShortcutList.svelte +145 -0
- package/src/components/TabBar.svelte +52 -0
- package/src/components/TableOfContents.svelte +125 -0
- package/src/components/ui/ActionLink.svelte +40 -0
- package/src/components/ui/Button.svelte +53 -0
- package/src/components/ui/Dialog.svelte +97 -0
- package/src/components/ui/DropdownMenu.svelte +85 -0
- package/src/components/ui/DropdownMenuItem.svelte +38 -0
- package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
- package/src/components/ui/Text.svelte +42 -0
- package/src/env.d.ts +6 -0
- package/src/index.css +36 -166
- package/src/lib/__fixtures__/bench-data.ts +1 -54
- package/src/lib/anchor.bench.ts +47 -68
- package/src/lib/anchor.test.ts +5 -9
- package/src/lib/anchor.ts +9 -93
- package/src/lib/comment-storage.bench.ts +6 -20
- package/src/lib/comment-storage.test.ts +45 -37
- package/src/lib/comment-storage.ts +23 -64
- package/src/lib/export.bench.ts +9 -23
- package/src/lib/export.ts +7 -14
- package/src/lib/headings.test.ts +103 -0
- package/src/lib/headings.ts +44 -0
- package/src/lib/highlight/core.test.ts +1 -6
- package/src/lib/highlight/dom.ts +53 -280
- package/src/lib/highlight/highlight-registry.ts +221 -0
- package/src/lib/highlight/highlight.bench.ts +92 -0
- package/src/lib/highlight/highlighter.ts +122 -302
- package/src/lib/highlight/{core.ts → resolver.ts} +3 -19
- package/src/lib/highlight/types.ts +0 -40
- package/src/lib/html-text.test.ts +162 -0
- package/src/lib/html-text.ts +161 -0
- package/src/lib/i18n/en.ts +13 -36
- package/src/lib/i18n/ja.ts +14 -37
- package/src/lib/i18n/types.ts +13 -36
- package/src/lib/margin-layout.bench.ts +48 -15
- package/src/lib/margin-layout.ts +2 -31
- package/src/lib/markdown-renderer.test.ts +154 -0
- package/src/lib/markdown-renderer.ts +177 -0
- package/src/lib/mermaid-config.ts +38 -0
- package/src/lib/mermaid-renderer.ts +162 -0
- package/src/lib/mermaid-worker.ts +60 -0
- package/src/lib/positions.ts +157 -0
- package/src/lib/shortcut-registry.ts +138 -103
- package/src/lib/utils.ts +2 -48
- package/src/main.ts +16 -0
- package/src/schema.ts +92 -0
- package/src/{server/index.ts → server.ts} +427 -163
- package/src/stores/app.svelte.ts +231 -0
- package/src/stores/locale.svelte.ts +46 -0
- package/src/stores/settings.svelte.ts +90 -0
- package/src/stores/shortcuts.svelte.ts +104 -0
- package/src/stores/ui.svelte.ts +12 -0
- package/src/template.ts +104 -0
- package/src/test-setup.ts +47 -0
- package/svelte.config.js +5 -0
- package/tsconfig.json +2 -2
- package/vite.config.ts +31 -3
- package/vscode-readit/.mcp.json +7 -0
- package/vscode-readit/.vscodeignore +7 -0
- package/vscode-readit/bun.lock +78 -0
- package/vscode-readit/icon.svg +10 -0
- package/vscode-readit/package.json +110 -0
- package/vscode-readit/src/extension.ts +117 -0
- package/vscode-readit/src/server-manager.ts +272 -0
- package/vscode-readit/src/webview-provider.ts +204 -0
- package/vscode-readit/tsconfig.json +20 -0
- package/e2e/fixtures/sample.html +0 -13
- package/src/App.tsx +0 -416
- package/src/components/ActionsMenu.tsx +0 -112
- package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
- package/src/components/DocumentViewer/DocumentViewer.tsx +0 -259
- package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
- package/src/components/DocumentViewer/InlineCode.tsx +0 -60
- package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -137
- package/src/components/DocumentViewer/index.ts +0 -1
- package/src/components/FloatingTOC.tsx +0 -61
- package/src/components/Header.tsx +0 -65
- package/src/components/InlineEditor.tsx +0 -74
- package/src/components/MarginNote.tsx +0 -207
- package/src/components/MarginNotes.tsx +0 -50
- package/src/components/RawModal.tsx +0 -143
- package/src/components/ReanchorConfirm.tsx +0 -36
- package/src/components/SettingsModal.tsx +0 -310
- package/src/components/ShortcutCapture.tsx +0 -48
- package/src/components/ShortcutList.tsx +0 -198
- package/src/components/TabBar.tsx +0 -60
- package/src/components/TableOfContents.tsx +0 -108
- package/src/components/comments/CommentBadge.tsx +0 -49
- package/src/components/comments/CommentInput.tsx +0 -114
- package/src/components/comments/CommentListItem.tsx +0 -92
- package/src/components/comments/CommentManager.tsx +0 -113
- package/src/components/comments/CommentMinimap.tsx +0 -62
- package/src/components/comments/CommentNav.tsx +0 -109
- package/src/components/ui/ActionBar.tsx +0 -16
- package/src/components/ui/ActionLink.tsx +0 -32
- package/src/components/ui/Button.tsx +0 -55
- package/src/components/ui/Dialog.tsx +0 -156
- package/src/components/ui/DropdownMenu.tsx +0 -114
- package/src/components/ui/SeparatorDot.tsx +0 -9
- package/src/components/ui/Text.tsx +0 -54
- package/src/contexts/CommentContext.tsx +0 -229
- package/src/contexts/LayoutContext.tsx +0 -88
- package/src/contexts/LocaleContext.tsx +0 -35
- package/src/hooks/useClickOutside.ts +0 -35
- package/src/hooks/useClipboard.ts +0 -82
- package/src/hooks/useCommentNavigation.ts +0 -130
- package/src/hooks/useComments.ts +0 -323
- package/src/hooks/useDocument.ts +0 -156
- package/src/hooks/useEditorScheme.ts +0 -51
- package/src/hooks/useFontPreference.ts +0 -59
- package/src/hooks/useHeadings.test.ts +0 -159
- package/src/hooks/useHeadings.ts +0 -129
- package/src/hooks/useKeybindings.ts +0 -108
- package/src/hooks/useKeyboardShortcuts.ts +0 -63
- package/src/hooks/useLayoutMode.ts +0 -44
- package/src/hooks/useLocalePreference.ts +0 -42
- package/src/hooks/useReanchorMode.ts +0 -33
- package/src/hooks/useScrollMetrics.ts +0 -56
- package/src/hooks/useScrollSpy.ts +0 -81
- package/src/hooks/useTextSelection.ts +0 -123
- package/src/hooks/useThemePreference.ts +0 -66
- package/src/lib/context.bench.ts +0 -41
- package/src/lib/context.test.ts +0 -224
- package/src/lib/context.ts +0 -193
- package/src/lib/editor-links.ts +0 -59
- package/src/lib/highlight/colors.ts +0 -37
- package/src/lib/highlight/index.ts +0 -23
- package/src/lib/highlight/script-builder.ts +0 -485
- package/src/lib/html-processor.test.tsx +0 -170
- package/src/lib/html-processor.tsx +0 -95
- package/src/lib/i18n/completeness.test.ts +0 -51
- package/src/lib/i18n/translations.test.ts +0 -39
- package/src/lib/layout-constants.ts +0 -12
- package/src/lib/scroll.test.ts +0 -118
- package/src/lib/scroll.ts +0 -47
- package/src/lib/shortcut-registry.test.ts +0 -173
- package/src/lib/utils.test.ts +0 -110
- package/src/main.tsx +0 -13
- package/src/store/index.test.ts +0 -242
- package/src/store/index.ts +0 -254
- package/src/types/index.ts +0 -127
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"flag"
|
|
7
|
+
"fmt"
|
|
8
|
+
"log"
|
|
9
|
+
"net/http"
|
|
10
|
+
"os"
|
|
11
|
+
"os/exec"
|
|
12
|
+
"os/signal"
|
|
13
|
+
"path/filepath"
|
|
14
|
+
"strings"
|
|
15
|
+
"syscall"
|
|
16
|
+
"time"
|
|
17
|
+
|
|
18
|
+
"github.com/peaske7/readit/go/internal/server"
|
|
19
|
+
"github.com/pkg/browser"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const version = "0.3.0"
|
|
23
|
+
|
|
24
|
+
type serverInfo struct {
|
|
25
|
+
Port int `json:"port"`
|
|
26
|
+
PID int `json:"pid"`
|
|
27
|
+
Host string `json:"host,omitempty"`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func main() {
|
|
31
|
+
if len(os.Args) < 2 {
|
|
32
|
+
fmt.Println("Usage: readit <file.md> [flags]")
|
|
33
|
+
fmt.Println(" readit list")
|
|
34
|
+
fmt.Println(" readit show <file.md>")
|
|
35
|
+
fmt.Println(" readit open <file.md>")
|
|
36
|
+
os.Exit(1)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch os.Args[1] {
|
|
40
|
+
case "list":
|
|
41
|
+
cmdList()
|
|
42
|
+
case "show":
|
|
43
|
+
cmdShow()
|
|
44
|
+
case "open":
|
|
45
|
+
cmdOpen()
|
|
46
|
+
case "version", "--version", "-v":
|
|
47
|
+
fmt.Println("readit", version)
|
|
48
|
+
case "help", "--help", "-h":
|
|
49
|
+
printHelp()
|
|
50
|
+
default:
|
|
51
|
+
cmdServe()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func cmdServe() {
|
|
56
|
+
fs := flag.NewFlagSet("serve", flag.ExitOnError)
|
|
57
|
+
port := fs.Int("port", 0, "port to listen on (default: random)")
|
|
58
|
+
host := fs.String("host", "127.0.0.1", "host to bind to")
|
|
59
|
+
noOpen := fs.Bool("no-open", false, "don't open browser")
|
|
60
|
+
clean := fs.Bool("clean", false, "clear existing comments")
|
|
61
|
+
assetsDir := fs.String("assets-dir", "", "serve assets from directory instead of embedded")
|
|
62
|
+
dev := fs.Bool("dev", false, "development mode (proxy to Vite)")
|
|
63
|
+
|
|
64
|
+
// Separate file args from flag args. Flags that take values
|
|
65
|
+
// (--port 3000, --host 0.0.0.0, --assets-dir ./dist) consume the next arg.
|
|
66
|
+
valueFlags := map[string]bool{
|
|
67
|
+
"-port": true, "--port": true,
|
|
68
|
+
"-host": true, "--host": true,
|
|
69
|
+
"-assets-dir": true, "--assets-dir": true,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var fileArgs []string
|
|
73
|
+
var flagArgs []string
|
|
74
|
+
args := os.Args[1:]
|
|
75
|
+
for i := 0; i < len(args); i++ {
|
|
76
|
+
arg := args[i]
|
|
77
|
+
if strings.HasPrefix(arg, "-") {
|
|
78
|
+
flagArgs = append(flagArgs, arg)
|
|
79
|
+
// If this flag takes a value, consume the next arg too
|
|
80
|
+
if valueFlags[arg] && i+1 < len(args) {
|
|
81
|
+
i++
|
|
82
|
+
flagArgs = append(flagArgs, args[i])
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
fileArgs = append(fileArgs, arg)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_ = fs.Parse(flagArgs)
|
|
89
|
+
|
|
90
|
+
if len(fileArgs) == 0 {
|
|
91
|
+
fmt.Fprintln(os.Stderr, "Error: at least one file is required")
|
|
92
|
+
os.Exit(1)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
files, err := resolveFiles(fileArgs)
|
|
96
|
+
if err != nil {
|
|
97
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
98
|
+
os.Exit(1)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if *port == 0 {
|
|
102
|
+
*port = 4567
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Dev mode: spawn Vite
|
|
106
|
+
var viteCmd *exec.Cmd
|
|
107
|
+
if *dev {
|
|
108
|
+
viteCmd = spawnVite()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Setup signal channel before server so the SSE shutdown callback can use it
|
|
112
|
+
sig := make(chan os.Signal, 1)
|
|
113
|
+
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
114
|
+
|
|
115
|
+
srv, err := server.NewServer(server.Options{
|
|
116
|
+
Files: files,
|
|
117
|
+
Port: *port,
|
|
118
|
+
Host: *host,
|
|
119
|
+
Clean: *clean,
|
|
120
|
+
AssetsDir: *assetsDir,
|
|
121
|
+
Dev: *dev,
|
|
122
|
+
OnShutdown: func() {
|
|
123
|
+
log.Println("All clients disconnected, shutting down")
|
|
124
|
+
sig <- syscall.SIGTERM
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
if err != nil {
|
|
128
|
+
log.Fatal(err)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
actualPort, err := srv.Start(*host, *port)
|
|
132
|
+
if err != nil {
|
|
133
|
+
log.Fatal(err)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeServerInfo(actualPort, *host)
|
|
137
|
+
|
|
138
|
+
url := fmt.Sprintf("http://%s:%d", *host, actualPort)
|
|
139
|
+
fmt.Printf("readit v%s serving at %s\n", version, url)
|
|
140
|
+
|
|
141
|
+
if !*noOpen {
|
|
142
|
+
_ = browser.OpenURL(url)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Wait for interrupt or SSE-triggered shutdown
|
|
146
|
+
<-sig
|
|
147
|
+
|
|
148
|
+
fmt.Println("\nShutting down...")
|
|
149
|
+
srv.Stop()
|
|
150
|
+
if viteCmd != nil && viteCmd.Process != nil {
|
|
151
|
+
_ = viteCmd.Process.Kill()
|
|
152
|
+
}
|
|
153
|
+
removeServerInfo()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func cmdList() {
|
|
157
|
+
home, _ := os.UserHomeDir()
|
|
158
|
+
commentsDir := filepath.Join(home, ".readit", "comments")
|
|
159
|
+
|
|
160
|
+
_ = filepath.Walk(commentsDir, func(path string, info os.FileInfo, err error) error {
|
|
161
|
+
if err != nil || info.IsDir() {
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
if !strings.HasSuffix(path, ".comments.md") {
|
|
165
|
+
return nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
data, err := os.ReadFile(path)
|
|
169
|
+
if err != nil {
|
|
170
|
+
return nil
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
cf, err := server.ParseCommentFile(data)
|
|
174
|
+
if err != nil {
|
|
175
|
+
return nil
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fmt.Printf("%s (%d comments)\n", cf.Source, len(cf.Comments))
|
|
179
|
+
return nil
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
func cmdShow() {
|
|
184
|
+
if len(os.Args) < 3 {
|
|
185
|
+
fmt.Fprintln(os.Stderr, "Usage: readit show <file.md>")
|
|
186
|
+
os.Exit(1)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
filePath, err := filepath.Abs(os.Args[2])
|
|
190
|
+
if err != nil {
|
|
191
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
192
|
+
os.Exit(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
commentPath, err := server.CommentPath(filePath)
|
|
196
|
+
if err != nil {
|
|
197
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
198
|
+
os.Exit(1)
|
|
199
|
+
}
|
|
200
|
+
data, err := os.ReadFile(commentPath)
|
|
201
|
+
if err != nil {
|
|
202
|
+
fmt.Println("No comments found for", filePath)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
cf, err := server.ParseCommentFile(data)
|
|
207
|
+
if err != nil {
|
|
208
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
209
|
+
os.Exit(1)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for i, c := range cf.Comments {
|
|
213
|
+
text := c.SelectedText
|
|
214
|
+
if len(text) > 80 {
|
|
215
|
+
text = text[:80] + "…"
|
|
216
|
+
}
|
|
217
|
+
fmt.Printf("[%d] %s\n", i+1, c.LineHint)
|
|
218
|
+
fmt.Printf(" > %s\n", strings.ReplaceAll(text, "\n", "\n > "))
|
|
219
|
+
fmt.Printf(" %s\n", c.Comment)
|
|
220
|
+
fmt.Printf(" (%s)\n\n", c.CreatedAt)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
func cmdOpen() {
|
|
225
|
+
if len(os.Args) < 3 {
|
|
226
|
+
fmt.Fprintln(os.Stderr, "Usage: readit open <file.md>")
|
|
227
|
+
os.Exit(1)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
files, err := resolveFiles(os.Args[2:])
|
|
231
|
+
if err != nil {
|
|
232
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
233
|
+
os.Exit(1)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Try to discover a running server
|
|
237
|
+
info, err := discoverServer()
|
|
238
|
+
if err == nil && info != nil {
|
|
239
|
+
// Attach to existing server
|
|
240
|
+
for _, f := range files {
|
|
241
|
+
attachFile(info, f.FilePath)
|
|
242
|
+
}
|
|
243
|
+
url := fmt.Sprintf("http://%s:%d", info.resolvedHost(), info.Port)
|
|
244
|
+
_ = browser.OpenURL(url)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// No running server — start one
|
|
249
|
+
os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
|
|
250
|
+
cmdServe()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func resolveFiles(args []string) ([]server.FileEntry, error) {
|
|
254
|
+
seen := make(map[string]bool)
|
|
255
|
+
var files []server.FileEntry
|
|
256
|
+
|
|
257
|
+
for _, arg := range args {
|
|
258
|
+
absPath, err := filepath.Abs(arg)
|
|
259
|
+
if err != nil {
|
|
260
|
+
return nil, fmt.Errorf("invalid path %s: %w", arg, err)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
absPath, err = filepath.EvalSymlinks(absPath)
|
|
264
|
+
if err != nil {
|
|
265
|
+
return nil, fmt.Errorf("file not found: %s", arg)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
info, err := os.Stat(absPath)
|
|
269
|
+
if err != nil {
|
|
270
|
+
return nil, fmt.Errorf("file not found: %s", arg)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if info.IsDir() {
|
|
274
|
+
// Scan directory for markdown files
|
|
275
|
+
_ = filepath.Walk(absPath, func(path string, fi os.FileInfo, err error) error {
|
|
276
|
+
if err != nil || fi.IsDir() {
|
|
277
|
+
if fi != nil && fi.IsDir() && strings.HasPrefix(fi.Name(), ".") {
|
|
278
|
+
return filepath.SkipDir
|
|
279
|
+
}
|
|
280
|
+
if fi != nil && fi.IsDir() && fi.Name() == "node_modules" {
|
|
281
|
+
return filepath.SkipDir
|
|
282
|
+
}
|
|
283
|
+
return nil
|
|
284
|
+
}
|
|
285
|
+
ext := strings.ToLower(filepath.Ext(path))
|
|
286
|
+
if ext == ".md" || ext == ".markdown" {
|
|
287
|
+
if !seen[path] {
|
|
288
|
+
seen[path] = true
|
|
289
|
+
files = append(files, server.FileEntry{FilePath: path})
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return nil
|
|
293
|
+
})
|
|
294
|
+
} else {
|
|
295
|
+
ext := strings.ToLower(filepath.Ext(absPath))
|
|
296
|
+
if ext != ".md" && ext != ".markdown" {
|
|
297
|
+
return nil, fmt.Errorf("unsupported file type: %s (only .md and .markdown)", arg)
|
|
298
|
+
}
|
|
299
|
+
if !seen[absPath] {
|
|
300
|
+
seen[absPath] = true
|
|
301
|
+
files = append(files, server.FileEntry{FilePath: absPath})
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if len(files) == 0 {
|
|
307
|
+
return nil, fmt.Errorf("no markdown files found")
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return files, nil
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
func serverInfoPath() string {
|
|
314
|
+
home, _ := os.UserHomeDir()
|
|
315
|
+
return filepath.Join(home, ".readit", "server.json")
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func writeServerInfo(port int, host string) {
|
|
319
|
+
info := serverInfo{Port: port, PID: os.Getpid(), Host: host}
|
|
320
|
+
data, _ := json.Marshal(info)
|
|
321
|
+
path := serverInfoPath()
|
|
322
|
+
_ = os.MkdirAll(filepath.Dir(path), 0755)
|
|
323
|
+
_ = os.WriteFile(path, data, 0644)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
func removeServerInfo() {
|
|
327
|
+
_ = os.Remove(serverInfoPath())
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
func (si *serverInfo) resolvedHost() string {
|
|
331
|
+
if si.Host != "" {
|
|
332
|
+
return si.Host
|
|
333
|
+
}
|
|
334
|
+
return "127.0.0.1"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
func discoverServer() (*serverInfo, error) {
|
|
338
|
+
data, err := os.ReadFile(serverInfoPath())
|
|
339
|
+
if err != nil {
|
|
340
|
+
return nil, err
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
var info serverInfo
|
|
344
|
+
if err := json.Unmarshal(data, &info); err != nil {
|
|
345
|
+
return nil, err
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check if process is alive
|
|
349
|
+
proc, err := os.FindProcess(info.PID)
|
|
350
|
+
if err != nil {
|
|
351
|
+
return nil, err
|
|
352
|
+
}
|
|
353
|
+
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
|
354
|
+
return nil, fmt.Errorf("process not alive")
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Health check
|
|
358
|
+
client := &http.Client{Timeout: 2 * time.Second}
|
|
359
|
+
resp, err := client.Get(fmt.Sprintf("http://%s:%d/api/health", info.resolvedHost(), info.Port))
|
|
360
|
+
if err != nil {
|
|
361
|
+
return nil, err
|
|
362
|
+
}
|
|
363
|
+
_ = resp.Body.Close()
|
|
364
|
+
if resp.StatusCode != http.StatusOK {
|
|
365
|
+
return nil, fmt.Errorf("health check failed")
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return &info, nil
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
func attachFile(info *serverInfo, filePath string) {
|
|
372
|
+
body, _ := json.Marshal(map[string]string{"path": filePath})
|
|
373
|
+
resp, err := http.Post(
|
|
374
|
+
fmt.Sprintf("http://%s:%d/api/documents", info.resolvedHost(), info.Port),
|
|
375
|
+
"application/json",
|
|
376
|
+
bytes.NewReader(body),
|
|
377
|
+
)
|
|
378
|
+
if err != nil {
|
|
379
|
+
log.Printf("Warning: failed to attach %s: %v", filePath, err)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
_ = resp.Body.Close()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
func spawnVite() *exec.Cmd {
|
|
386
|
+
cmd := exec.Command("bunx", "vite", "--port", "24678")
|
|
387
|
+
cmd.Stdout = os.Stdout
|
|
388
|
+
cmd.Stderr = os.Stderr
|
|
389
|
+
if err := cmd.Start(); err != nil {
|
|
390
|
+
log.Printf("Warning: failed to start Vite: %v", err)
|
|
391
|
+
return nil
|
|
392
|
+
}
|
|
393
|
+
// Give Vite a moment to start
|
|
394
|
+
time.Sleep(500 * time.Millisecond)
|
|
395
|
+
return cmd
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
func printHelp() {
|
|
399
|
+
fmt.Printf(`readit v%s — Review Markdown documents with inline comments
|
|
400
|
+
|
|
401
|
+
Usage:
|
|
402
|
+
readit <file.md> [flags] Start server and open browser
|
|
403
|
+
readit list List files with comments
|
|
404
|
+
readit show <file.md> Show comments for a file
|
|
405
|
+
readit open <file.md> Add file to running server
|
|
406
|
+
|
|
407
|
+
Flags:
|
|
408
|
+
--port <n> Port to listen on (default: 4567)
|
|
409
|
+
--host <addr> Host to bind to (default: 127.0.0.1)
|
|
410
|
+
--no-open Don't auto-open browser
|
|
411
|
+
--clean Clear existing comments
|
|
412
|
+
--assets-dir <d> Serve assets from directory
|
|
413
|
+
--dev Development mode (spawn Vite)
|
|
414
|
+
|
|
415
|
+
`, version)
|
|
416
|
+
}
|
package/go/go.mod
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module github.com/peaske7/readit/go
|
|
2
|
+
|
|
3
|
+
go 1.24
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
github.com/alecthomas/chroma/v2 v2.23.1
|
|
7
|
+
github.com/fsnotify/fsnotify v1.9.0
|
|
8
|
+
github.com/microcosm-cc/bluemonday v1.0.27
|
|
9
|
+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
|
10
|
+
github.com/yuin/goldmark v1.8.2
|
|
11
|
+
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
require (
|
|
15
|
+
github.com/aymerick/douceur v0.2.0 // indirect
|
|
16
|
+
github.com/dlclark/regexp2 v1.11.5 // indirect
|
|
17
|
+
github.com/gorilla/css v1.0.1 // indirect
|
|
18
|
+
golang.org/x/net v0.26.0 // indirect
|
|
19
|
+
golang.org/x/sys v0.21.0 // indirect
|
|
20
|
+
)
|
package/go/go.sum
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
|
2
|
+
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
|
3
|
+
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
|
4
|
+
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
|
5
|
+
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
|
6
|
+
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
|
7
|
+
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
|
8
|
+
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
9
|
+
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
|
10
|
+
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
11
|
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
12
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
13
|
+
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
14
|
+
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
15
|
+
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
|
16
|
+
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
17
|
+
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
|
18
|
+
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
19
|
+
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
|
20
|
+
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
|
21
|
+
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
|
22
|
+
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
23
|
+
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
|
24
|
+
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
|
25
|
+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
|
26
|
+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
|
27
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
28
|
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
29
|
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
30
|
+
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
31
|
+
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
|
32
|
+
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
|
33
|
+
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
|
34
|
+
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
|
35
|
+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
|
36
|
+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
|
37
|
+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
38
|
+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
|
39
|
+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
40
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
41
|
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|