Add M0 server skeleton with SQLite store, SSE events, worktrees, and tailscale service

This commit is contained in:
Ian Gulliver
2026-10-13 22:19:20 -07:00
parent 555a55efc8
commit 2328904131
26 changed files with 3030 additions and 194 deletions
+28 -4
View File
@@ -2,6 +2,7 @@ package claude
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"encoding/hex"
@@ -103,6 +104,7 @@ type Session struct {
writeMu sync.Mutex
transcript io.Writer
scrub []byte
pendingMu sync.Mutex
pending map[string]chan controlResult
@@ -360,6 +362,10 @@ func (s *Session) record(dir string, line []byte) {
return
}
if len(s.scrub) > 0 {
line = bytes.ReplaceAll(line, s.scrub, []byte("[redacted]"))
}
b, err := json.Marshal(map[string]any{"dir": dir, "msg": json.RawMessage(line)})
if err != nil {
slog.Error("record transcript", "err", err)
@@ -389,10 +395,6 @@ func (s *Session) readLoop(stdout io.Reader) {
}
func (s *Session) handleLine(line []byte) {
s.writeMu.Lock()
s.record("recv", line)
s.writeMu.Unlock()
env := envelope{}
err := json.Unmarshal(line, &env)
if err != nil {
@@ -400,6 +402,13 @@ func (s *Session) handleLine(line []byte) {
return
}
s.writeMu.Lock()
if env.Type == "control_response" {
s.learnScrub(env.Response)
}
s.record("recv", line)
s.writeMu.Unlock()
switch env.Type {
case "control_response":
s.handleControlResponse(env.Response)
@@ -422,6 +431,21 @@ func (s *Session) handleLine(line []byte) {
s.events <- Event{Type: env.Type, Subtype: env.Subtype, Raw: append(json.RawMessage{}, line...)}
}
func (s *Session) learnScrub(raw json.RawMessage) {
resp := struct {
Response struct {
Account struct {
Email string `json:"email"`
} `json:"account"`
} `json:"response"`
}{}
err := json.Unmarshal(raw, &resp)
if err != nil || resp.Response.Account.Email == "" {
return
}
s.scrub = []byte(resp.Response.Account.Email)
}
func (s *Session) handleControlResponse(raw json.RawMessage) {
resp := struct {
Subtype string `json:"subtype"`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long