From 47cc722d3370a4faf2bef7c1fc7ce8be976db812 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 08:47:58 -0700 Subject: [PATCH 01/33] chore: add crawlkit module dependency --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 18e8e33..0fc32a3 100644 --- a/go.mod +++ b/go.mod @@ -39,3 +39,5 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) + +replace github.com/vincentkoc/crawlkit => /Users/vincentkoc/GIT/_Perso/crawlkit From c352cb4e6a9c8cbaa392ab5294bc8677738e7c4c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 08:49:49 -0700 Subject: [PATCH 02/33] refactor(config): route paths through crawlkit --- go.mod | 3 +- internal/config/config.go | 68 ++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 0fc32a3..1208458 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/charmbracelet/x/ansi v0.11.7 github.com/mattn/go-isatty v0.0.22 github.com/pelletier/go-toml/v2 v2.3.1 + github.com/vincentkoc/crawlkit v0.3.16 modernc.org/sqlite v1.50.0 ) @@ -39,5 +40,3 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) - -replace github.com/vincentkoc/crawlkit => /Users/vincentkoc/GIT/_Perso/crawlkit diff --git a/internal/config/config.go b/internal/config/config.go index cd5db41..b19cfb0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/pelletier/go-toml/v2" + "github.com/vincentkoc/crawlkit/configkit" ) const ( @@ -49,15 +49,24 @@ type TokenResolution struct { Source string } +var appConfig = configkit.App{Name: "gitcrawl", ConfigEnv: DefaultConfigEnv} + func Default() Config { - home := homeDir() - base := filepath.Join(home, ".config", "gitcrawl") + paths, err := appConfig.DefaultPaths() + if err != nil { + paths = configkit.Paths{ + DBPath: filepath.Join(homeDir(), ".config", "gitcrawl", "gitcrawl.db"), + CacheDir: filepath.Join(homeDir(), ".config", "gitcrawl", "cache"), + LogDir: filepath.Join(homeDir(), ".config", "gitcrawl", "logs"), + } + } + base := filepath.Dir(paths.DBPath) return Config{ Version: 1, - DBPath: filepath.Join(base, "gitcrawl.db"), - CacheDir: filepath.Join(base, "cache"), + DBPath: paths.DBPath, + CacheDir: paths.CacheDir, VectorDir: filepath.Join(base, "vectors"), - LogDir: filepath.Join(base, "logs"), + LogDir: paths.LogDir, EmbeddingBasis: "title_original", GitHub: GitHubConfig{ TokenEnv: DefaultTokenEnv, @@ -77,26 +86,19 @@ func Default() Config { } func ResolvePath(flagPath string) string { - if strings.TrimSpace(flagPath) != "" { - return expandHome(flagPath) + path, err := appConfig.ResolveConfigPath(flagPath) + if err != nil { + return filepath.Join(homeDir(), ".config", "gitcrawl", "config.toml") } - if envPath := strings.TrimSpace(os.Getenv(DefaultConfigEnv)); envPath != "" { - return expandHome(envPath) - } - home := homeDir() - return filepath.Join(home, ".config", "gitcrawl", "config.toml") + return path } func Load(path string) (Config, error) { cfg := Default() resolved := ResolvePath(path) - data, err := os.ReadFile(resolved) - if err != nil { + if err := configkit.LoadTOML(resolved, &cfg); err != nil { return Config{}, err } - if err := toml.Unmarshal(data, &cfg); err != nil { - return Config{}, fmt.Errorf("parse config: %w", err) - } if err := cfg.Normalize(); err != nil { return Config{}, err } @@ -108,21 +110,19 @@ func Save(path string, cfg Config) error { return err } resolved := ResolvePath(path) - if err := os.MkdirAll(filepath.Dir(resolved), 0o755); err != nil { - return fmt.Errorf("create config dir: %w", err) - } - data, err := toml.Marshal(cfg) - if err != nil { - return fmt.Errorf("marshal config: %w", err) - } - return os.WriteFile(resolved, data, 0o600) + return configkit.WriteTOML(resolved, cfg, 0o600) } func EnsureRuntimeDirs(cfg Config) error { - for _, path := range []string{cfg.CacheDir, cfg.VectorDir, cfg.LogDir, filepath.Dir(cfg.DBPath)} { - if err := os.MkdirAll(expandHome(path), 0o755); err != nil { - return fmt.Errorf("create runtime dir %s: %w", path, err) - } + if err := configkit.EnsureRuntimeDirs(configkit.RuntimeConfig{ + DBPath: cfg.DBPath, + CacheDir: cfg.CacheDir, + LogDir: cfg.LogDir, + }); err != nil { + return err + } + if err := os.MkdirAll(configkit.ExpandHome(cfg.VectorDir), 0o755); err != nil { + return fmt.Errorf("create runtime dir %s: %w", cfg.VectorDir, err) } return nil } @@ -200,13 +200,7 @@ func envOrDefault(primary, fallback string) string { } func expandHome(path string) string { - if path == "~" { - return homeDir() - } - if strings.HasPrefix(path, "~/") { - return filepath.Join(homeDir(), strings.TrimPrefix(path, "~/")) - } - return path + return configkit.ExpandHome(path) } func homeDir() string { From 511603d0b1d49f062de1f3bd2ddb40982262abc9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 08:54:32 -0700 Subject: [PATCH 03/33] refactor(store): use crawlkit sqlite openers --- internal/store/store.go | 80 +++++------------------------------------ 1 file changed, 9 insertions(+), 71 deletions(-) diff --git a/internal/store/store.go b/internal/store/store.go index 0c92cba..812c751 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -4,12 +4,9 @@ import ( "context" "database/sql" "fmt" - "os" - "path/filepath" - "runtime" "time" - _ "modernc.org/sqlite" + "github.com/vincentkoc/crawlkit/sqlitekit" ) const ( @@ -39,64 +36,33 @@ type Status struct { } func Open(ctx context.Context, path string) (*Store, error) { - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return nil, fmt.Errorf("create db dir: %w", err) - } - if err := ensureDBFile(path); err != nil { - return nil, err - } - dsn := fmt.Sprintf( - "file:%s?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=temp_store(MEMORY)&_pragma=mmap_size(268435456)&_pragma=busy_timeout(5000)", - path, - ) - db, err := sql.Open("sqlite", dsn) + base, err := sqlitekit.Open(ctx, sqlitekit.Options{Path: path}) if err != nil { - return nil, fmt.Errorf("open sqlite: %w", err) - } - db.SetMaxOpenConns(1) - db.SetMaxIdleConns(1) - if err := db.PingContext(ctx); err != nil { - _ = db.Close() - return nil, fmt.Errorf("ping sqlite: %w", err) - } - if err := tightenDBFilePerms(path); err != nil { - _ = db.Close() return nil, err } + db := base.DB() st := &Store{db: db, path: path} if err := st.migrate(ctx); err != nil { - _ = db.Close() + _ = base.Close() return nil, err } return st, nil } func OpenReadOnly(ctx context.Context, path string) (*Store, error) { - if _, err := os.Stat(path); err != nil { - return nil, fmt.Errorf("stat db file: %w", err) - } - dsn := fmt.Sprintf( - "file:%s?mode=ro&_pragma=query_only(1)&_pragma=foreign_keys(1)&_pragma=temp_store(MEMORY)&_pragma=mmap_size(268435456)&_pragma=busy_timeout(5000)", - path, - ) - db, err := sql.Open("sqlite", dsn) + base, err := sqlitekit.OpenReadOnly(ctx, path) if err != nil { - return nil, fmt.Errorf("open sqlite readonly: %w", err) - } - db.SetMaxOpenConns(1) - db.SetMaxIdleConns(1) - if err := db.PingContext(ctx); err != nil { - _ = db.Close() - return nil, fmt.Errorf("ping sqlite readonly: %w", err) + return nil, err } + db := base.DB() st := &Store{db: db, path: path} current, err := st.schemaVersion(ctx) if err != nil { - _ = db.Close() + _ = base.Close() return nil, err } if current > schemaVersion { - _ = db.Close() + _ = base.Close() return nil, fmt.Errorf("database schema version %d is newer than supported version %d", current, schemaVersion) } return st, nil @@ -273,31 +239,3 @@ func (s *Store) schemaVersion(ctx context.Context) (int, error) { } return version, nil } - -func ensureDBFile(path string) error { - if _, err := os.Stat(path); err == nil { - return nil - } else if !os.IsNotExist(err) { - return fmt.Errorf("stat db file: %w", err) - } - file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) - if err != nil && !os.IsExist(err) { - return fmt.Errorf("create db file: %w", err) - } - if file != nil { - if err := file.Close(); err != nil { - return fmt.Errorf("close db file: %w", err) - } - } - return nil -} - -func tightenDBFilePerms(path string) error { - if runtime.GOOS == "windows" { - return nil - } - if err := os.Chmod(path, 0o600); err != nil { - return fmt.Errorf("chmod db file: %w", err) - } - return nil -} From afed848dc7e5092047f01ff9b9770f53cccaa892 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 09:35:00 -0700 Subject: [PATCH 04/33] chore: use crawlkit v0.1.0 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 04a525e..484aeea 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/vincentkoc/crawlkit v0.1.0 h1:8SedL2Pww6SSJqg6iLtqYh/LGIeyRfsEkHRonSEr/K4= +github.com/vincentkoc/crawlkit v0.1.0/go.mod h1:lJEfUjoyHIH/5s5uKU4NW8E7zCHy7+i2OtByGaa/oGc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From af0ea88c982d66bd68b8b25aae105a85bc84c657 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 09:50:50 -0700 Subject: [PATCH 05/33] chore: use crawlkit v0.1.1 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 484aeea..534e7cd 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.1.0 h1:8SedL2Pww6SSJqg6iLtqYh/LGIeyRfsEkHRonSEr/K4= -github.com/vincentkoc/crawlkit v0.1.0/go.mod h1:lJEfUjoyHIH/5s5uKU4NW8E7zCHy7+i2OtByGaa/oGc= +github.com/vincentkoc/crawlkit v0.1.1 h1:CEK6zf3O/RhKHPrSyqaPgf/cPSBGlhiJZs301OICsyA= +github.com/vincentkoc/crawlkit v0.1.1/go.mod h1:lJEfUjoyHIH/5s5uKU4NW8E7zCHy7+i2OtByGaa/oGc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 90c90204c1e420742441bc1d81eb477349710c4e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 10:41:48 -0700 Subject: [PATCH 06/33] docs(tui): mark gitcrawl as browser reference --- .github/workflows/ci.yml | 7 +++++++ CHANGELOG.md | 2 ++ README.md | 1 + 3 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 399a52e..70bd6ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,13 @@ jobs: - name: Build run: go build -ldflags "-X github.com/openclaw/gitcrawl/internal/cli.version=${GITHUB_SHA:0:7}" -o bin/gitcrawl ./cmd/gitcrawl + - name: Smoke test TUI help + run: | + set -euo pipefail + output="$(./bin/gitcrawl help tui)" + printf '%s\n' "$output" + printf '%s' "$output" | grep -q "gitcrawl tui" + - name: Snapshot release build uses: goreleaser/goreleaser-action@v7.1.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bb0ab..bff025e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Auto-hydrate one exact pull request when local PR detail reads miss or check/run data is stale, using `gh auth token` if `GITHUB_TOKEN` is absent, then retry from SQLite before falling back to live `gh`. - Cache more ghx-style read-only fallthroughs, including release, workflow, secret, variable, project, ruleset, gist, org, and search reads; cache repeat read failures by default; and clear the fallthrough cache after the corresponding mutating `gh` commands. - Promote portable backups to the v2 format: keep compact comments, PR files, commits, checks, and workflow runs while stripping raw JSON, generated documents, vectors, clusters, and run history. +- Route config path handling and SQLite openers through `crawlkit` so GitHub archive tooling shares the same foundation as the Slack, Discord, and Notion crawlers. +- Keep the existing `gitcrawl tui` as the family reference terminal interface and add CI smoke coverage for its help surface. ## 0.1.2 - 2026-05-01 diff --git a/README.md b/README.md index a38c448..48cecad 100644 --- a/README.md +++ b/README.md @@ -91,4 +91,5 @@ go build -ldflags "-X github.com/openclaw/gitcrawl/internal/cli.version=$(git de ```bash go test ./... go build ./cmd/gitcrawl +go run ./cmd/gitcrawl help tui ``` From 8cd92156f805d51cc41ff1f011a6523ce3455d0c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 10:43:17 -0700 Subject: [PATCH 07/33] chore: use crawlkit v0.2.0 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 534e7cd..442ad17 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.1.1 h1:CEK6zf3O/RhKHPrSyqaPgf/cPSBGlhiJZs301OICsyA= -github.com/vincentkoc/crawlkit v0.1.1/go.mod h1:lJEfUjoyHIH/5s5uKU4NW8E7zCHy7+i2OtByGaa/oGc= +github.com/vincentkoc/crawlkit v0.2.0 h1:w6jfcckVsKOcx6fFwKj9mvyTJHmSDg3ro+j2/TgNkuE= +github.com/vincentkoc/crawlkit v0.2.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 863d8599e5d2a251d2d5e4b6c0dc78a8071b99a1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 12:38:07 -0700 Subject: [PATCH 08/33] refactor: use crawlkit package nouns --- go.sum | 2 ++ internal/config/config.go | 16 ++++++++-------- internal/store/store.go | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/go.sum b/go.sum index 442ad17..3f18916 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/vincentkoc/crawlkit v0.2.0 h1:w6jfcckVsKOcx6fFwKj9mvyTJHmSDg3ro+j2/TgNkuE= github.com/vincentkoc/crawlkit v0.2.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.0 h1:37QsucIaUmaRygk2fn7NQniD6YSAAi0DR1vzL8UimoI= +github.com/vincentkoc/crawlkit v0.3.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= diff --git a/internal/config/config.go b/internal/config/config.go index b19cfb0..f8fd11d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/vincentkoc/crawlkit/configkit" + crawlconfig "github.com/vincentkoc/crawlkit/config" ) const ( @@ -49,12 +49,12 @@ type TokenResolution struct { Source string } -var appConfig = configkit.App{Name: "gitcrawl", ConfigEnv: DefaultConfigEnv} +var appConfig = crawlconfig.App{Name: "gitcrawl", ConfigEnv: DefaultConfigEnv} func Default() Config { paths, err := appConfig.DefaultPaths() if err != nil { - paths = configkit.Paths{ + paths = crawlconfig.Paths{ DBPath: filepath.Join(homeDir(), ".config", "gitcrawl", "gitcrawl.db"), CacheDir: filepath.Join(homeDir(), ".config", "gitcrawl", "cache"), LogDir: filepath.Join(homeDir(), ".config", "gitcrawl", "logs"), @@ -96,7 +96,7 @@ func ResolvePath(flagPath string) string { func Load(path string) (Config, error) { cfg := Default() resolved := ResolvePath(path) - if err := configkit.LoadTOML(resolved, &cfg); err != nil { + if err := crawlconfig.LoadTOML(resolved, &cfg); err != nil { return Config{}, err } if err := cfg.Normalize(); err != nil { @@ -110,18 +110,18 @@ func Save(path string, cfg Config) error { return err } resolved := ResolvePath(path) - return configkit.WriteTOML(resolved, cfg, 0o600) + return crawlconfig.WriteTOML(resolved, cfg, 0o600) } func EnsureRuntimeDirs(cfg Config) error { - if err := configkit.EnsureRuntimeDirs(configkit.RuntimeConfig{ + if err := crawlconfig.EnsureRuntimeDirs(crawlconfig.RuntimeConfig{ DBPath: cfg.DBPath, CacheDir: cfg.CacheDir, LogDir: cfg.LogDir, }); err != nil { return err } - if err := os.MkdirAll(configkit.ExpandHome(cfg.VectorDir), 0o755); err != nil { + if err := os.MkdirAll(crawlconfig.ExpandHome(cfg.VectorDir), 0o755); err != nil { return fmt.Errorf("create runtime dir %s: %w", cfg.VectorDir, err) } return nil @@ -200,7 +200,7 @@ func envOrDefault(primary, fallback string) string { } func expandHome(path string) string { - return configkit.ExpandHome(path) + return crawlconfig.ExpandHome(path) } func homeDir() string { diff --git a/internal/store/store.go b/internal/store/store.go index 812c751..0d202d1 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - "github.com/vincentkoc/crawlkit/sqlitekit" + crawlstore "github.com/vincentkoc/crawlkit/store" ) const ( @@ -36,7 +36,7 @@ type Status struct { } func Open(ctx context.Context, path string) (*Store, error) { - base, err := sqlitekit.Open(ctx, sqlitekit.Options{Path: path}) + base, err := crawlstore.Open(ctx, crawlstore.Options{Path: path}) if err != nil { return nil, err } @@ -50,7 +50,7 @@ func Open(ctx context.Context, path string) (*Store, error) { } func OpenReadOnly(ctx context.Context, path string) (*Store, error) { - base, err := sqlitekit.OpenReadOnly(ctx, path) + base, err := crawlstore.OpenReadOnly(ctx, path) if err != nil { return nil, err } From 77f981725ea516bbc37817a66ee03807d11c0a73 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 12:42:55 -0700 Subject: [PATCH 09/33] chore: tidy crawlkit module sums --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 3f18916..c520f20 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.2.0 h1:w6jfcckVsKOcx6fFwKj9mvyTJHmSDg3ro+j2/TgNkuE= -github.com/vincentkoc/crawlkit v0.2.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/vincentkoc/crawlkit v0.3.0 h1:37QsucIaUmaRygk2fn7NQniD6YSAAi0DR1vzL8UimoI= github.com/vincentkoc/crawlkit v0.3.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= From 92c839cae2aad24a0ea7a924b9e55b0e842593b4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 13:25:04 -0700 Subject: [PATCH 10/33] chore: bump crawlkit to v0.3.1 --- go.mod | 3 +-- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 1208458..c2d81df 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,7 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect modernc.org/libc v1.72.1 // indirect diff --git a/go.sum b/go.sum index c520f20..8a7e459 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.0 h1:37QsucIaUmaRygk2fn7NQniD6YSAAi0DR1vzL8UimoI= -github.com/vincentkoc/crawlkit v0.3.0/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.1 h1:z5q7s+oAkdlgGjdT1N/CsWkc4GZ2uNqCmVDtTd2tQSM= +github.com/vincentkoc/crawlkit v0.3.1/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From e13976fbea7a8895489116fb764797e395f97fe3 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 16:09:37 -0700 Subject: [PATCH 11/33] feat(cli): add crawlkit control surface --- go.mod | 3 +- go.sum | 2 + internal/cli/app.go | 114 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c2d81df..1208458 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,8 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect modernc.org/libc v1.72.1 // indirect diff --git a/go.sum b/go.sum index 8a7e459..ba6cc77 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/vincentkoc/crawlkit v0.3.1 h1:z5q7s+oAkdlgGjdT1N/CsWkc4GZ2uNqCmVDtTd2tQSM= github.com/vincentkoc/crawlkit v0.3.1/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.2 h1:/K8GZvgGtYtZY3iaLPhXw+N50sOYrnaQO+egHD9HcAE= +github.com/vincentkoc/crawlkit v0.3.2/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= diff --git a/internal/cli/app.go b/internal/cli/app.go index a52099d..ed6707a 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -23,6 +23,7 @@ import ( "github.com/openclaw/gitcrawl/internal/store" "github.com/openclaw/gitcrawl/internal/syncer" "github.com/openclaw/gitcrawl/internal/vector" + "github.com/vincentkoc/crawlkit/control" ) const ( @@ -124,12 +125,16 @@ func (a *App) Run(ctx context.Context, args []string) error { switch rest[0] { case "version": return a.writeOutput("version", map[string]string{"version": version}, false) + case "metadata": + return a.runMetadata(rest[1:]) case "serve": return usageErr(fmt.Errorf("serve is not supported in gitcrawl")) case "init": return a.runInit(ctx, rest[1:]) case "doctor": return a.runDoctor(ctx, rest[1:]) + case "status": + return a.runStatus(ctx, rest[1:]) case "sync": return a.runSync(ctx, rest[1:]) case "threads": @@ -2197,6 +2202,113 @@ func (a *App) runDoctor(ctx context.Context, args []string) error { }, true) } +func (a *App) runMetadata(args []string) error { + fs := flag.NewFlagSet("metadata", flag.ContinueOnError) + fs.SetOutput(io.Discard) + jsonOut := fs.Bool("json", false, "write JSON output") + if err := fs.Parse(normalizeCommandArgs(args, nil)); err != nil { + return usageErr(err) + } + a.applyCommandJSON(*jsonOut) + if fs.NArg() != 0 { + return usageErr(fmt.Errorf("metadata takes flags only")) + } + cfg := config.Default() + manifest := control.NewManifest("gitcrawl", "Git Crawl", "gitcrawl") + manifest.Description = "Local-first GitHub issue and pull request crawler." + manifest.Branding = control.Branding{SymbolName: "point.3.connected.trianglepath.dotted", AccentColor: "#2da44e"} + manifest.Paths = control.Paths{ + DefaultConfig: config.ResolvePath(""), + ConfigEnv: config.DefaultConfigEnv, + DefaultDatabase: cfg.DBPath, + DefaultCache: cfg.CacheDir, + DefaultLogs: cfg.LogDir, + } + manifest.Capabilities = []string{"metadata", "status", "doctor", "sync", "search", "tui", "portable", "clusters", "embeddings"} + manifest.Privacy = control.Privacy{ContainsPrivateMessages: false, ExportsSecrets: false, LocalOnlyScopes: []string{"github", "sqlite", "portable"}} + manifest.Commands = map[string]control.Command{ + "status": {Title: "Status", Argv: []string{"gitcrawl", "status", "--json"}, JSON: true}, + "doctor": {Title: "Doctor", Argv: []string{"gitcrawl", "doctor", "--json"}, JSON: true}, + "sync": {Title: "Sync repository", Argv: []string{"gitcrawl", "sync", "--json"}, JSON: true, Mutates: true}, + "search": {Title: "Search", Argv: []string{"gitcrawl", "search", "--json"}, JSON: true}, + "tui": {Title: "Terminal cluster browser", Argv: []string{"gitcrawl", "tui"}}, + "tui-json": {Title: "Terminal cluster data", Argv: []string{"gitcrawl", "tui", "--json"}, JSON: true}, + "portable": {Title: "Portable store tools", Argv: []string{"gitcrawl", "portable", "--json"}, JSON: true, Mutates: true}, + "clusters": {Title: "Clusters", Argv: []string{"gitcrawl", "clusters", "--json"}, JSON: true}, + "legacy-sync-api": {Title: "Legacy sync-status alias", Argv: []string{"gitcrawl", "sync-status"}, Legacy: true, Deprecated: true}, + } + return a.writeOutput("metadata", manifest, false) +} + +func (a *App) runStatus(ctx context.Context, args []string) error { + fs := flag.NewFlagSet("status", flag.ContinueOnError) + fs.SetOutput(io.Discard) + jsonOut := fs.Bool("json", false, "write JSON output") + if err := fs.Parse(normalizeCommandArgs(args, nil)); err != nil { + return usageErr(err) + } + a.applyCommandJSON(*jsonOut) + if fs.NArg() != 0 { + return usageErr(fmt.Errorf("status takes flags only")) + } + cfg, err := config.Load(a.configPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + cfg = config.Default() + if err := cfg.Normalize(); err != nil { + return err + } + } + status := store.Status{DBPath: cfg.DBPath} + if _, err := os.Stat(cfg.DBPath); err == nil { + st, err := store.OpenReadOnly(ctx, cfg.DBPath) + if err != nil { + return err + } + defer st.Close() + status, err = st.Status(ctx) + if err != nil { + return err + } + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + status.DBPath = cfg.DBPath + return a.writeOutput("status", controlStatus(config.ResolvePath(a.configPath), cfg, status), false) +} + +func controlStatus(configPath string, cfg config.Config, status store.Status) control.Status { + counts := []control.Count{ + control.NewCount("repositories", "Repositories", int64(status.RepositoryCount)), + control.NewCount("threads", "Threads", int64(status.ThreadCount)), + control.NewCount("open_threads", "Open threads", int64(status.OpenThreadCount)), + control.NewCount("clusters", "Clusters", int64(status.ClusterCount)), + } + out := control.NewStatus("gitcrawl", fmt.Sprintf("%d threads across %d repositories", status.ThreadCount, status.RepositoryCount)) + out.State = "current" + out.ConfigPath = configPath + out.DatabasePath = status.DBPath + out.Counts = counts + if !status.LastSyncAt.IsZero() { + out.LastSyncAt = status.LastSyncAt.UTC().Format(time.RFC3339) + } + db := control.SQLiteDatabase("primary", "GitHub archive", "archive", status.DBPath, true, counts) + out.DatabaseBytes = db.Bytes + out.WALBytes = fileSize(status.DBPath + "-wal") + out.Databases = []control.Database{db} + return out +} + +func fileSize(path string) int64 { + info, err := os.Stat(path) + if err != nil { + return 0 + } + return info.Size() +} + func (a *App) applyCommandJSON(enabled bool) { if enabled { a.format = FormatJSON @@ -2704,6 +2816,8 @@ Global flags: --version print version Core commands: + metadata print crawlkit control metadata + status print fast read-only archive status init create config, optionally from a portable store doctor check config, token, and database readiness sync sync GitHub issue and pull request metadata From 3bfaef076148fb5b6621ef20945c07547c55cac7 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 16:10:14 -0700 Subject: [PATCH 12/33] ci: smoke crawlkit control surface --- .github/workflows/ci.yml | 3 +++ CHANGELOG.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70bd6ab..ec9cc7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,9 @@ jobs: - name: Smoke test TUI help run: | set -euo pipefail + test -n "$(./bin/gitcrawl --version)" + ./bin/gitcrawl metadata --json | grep -q '"schema_version"' + ./bin/gitcrawl status --json | grep -q '"databases"' output="$(./bin/gitcrawl help tui)" printf '%s\n' "$output" printf '%s' "$output" | grep -q "gitcrawl tui" diff --git a/CHANGELOG.md b/CHANGELOG.md index bff025e..789c7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Auto-hydrate one exact pull request when local PR detail reads miss or check/run data is stale, using `gh auth token` if `GITHUB_TOKEN` is absent, then retry from SQLite before falling back to live `gh`. - Cache more ghx-style read-only fallthroughs, including release, workflow, secret, variable, project, ruleset, gist, org, and search reads; cache repeat read failures by default; and clear the fallthrough cache after the corresponding mutating `gh` commands. - Promote portable backups to the v2 format: keep compact comments, PR files, commits, checks, and workflow runs while stripping raw JSON, generated documents, vectors, clusters, and run history. +- Add crawlkit control metadata/status surfaces with command-local `metadata --json`, `status --json`, and `doctor --json`. +- Include the primary SQLite database inventory in status JSON so local control surfaces can discover archive storage without opening live stores. - Route config path handling and SQLite openers through `crawlkit` so GitHub archive tooling shares the same foundation as the Slack, Discord, and Notion crawlers. - Keep the existing `gitcrawl tui` as the family reference terminal interface and add CI smoke coverage for its help surface. From bf21271477ae20bf3fb5cc9f2f16c3deea87b04b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 16:13:42 -0700 Subject: [PATCH 13/33] chore(deps): tidy crawlkit module sums --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index ba6cc77..2e0dab1 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.1 h1:z5q7s+oAkdlgGjdT1N/CsWkc4GZ2uNqCmVDtTd2tQSM= -github.com/vincentkoc/crawlkit v0.3.1/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/vincentkoc/crawlkit v0.3.2 h1:/K8GZvgGtYtZY3iaLPhXw+N50sOYrnaQO+egHD9HcAE= github.com/vincentkoc/crawlkit v0.3.2/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= From 7a9bac31b5cfc839f64ec4637de51920e2b7f75f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 1 May 2026 16:21:19 -0700 Subject: [PATCH 14/33] fix(cli): document portable help --- internal/cli/app.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index ed6707a..ca9bcf6 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -1892,6 +1892,8 @@ func (a *App) runPortable(ctx context.Context, args []string) error { return usageErr(fmt.Errorf("portable requires a subcommand")) } switch args[0] { + case "help", "--help", "-h": + return a.printCommandUsage("portable") case "prune": return a.runPortablePrune(ctx, args[1:]) default: @@ -2233,7 +2235,7 @@ func (a *App) runMetadata(args []string) error { "search": {Title: "Search", Argv: []string{"gitcrawl", "search", "--json"}, JSON: true}, "tui": {Title: "Terminal cluster browser", Argv: []string{"gitcrawl", "tui"}}, "tui-json": {Title: "Terminal cluster data", Argv: []string{"gitcrawl", "tui", "--json"}, JSON: true}, - "portable": {Title: "Portable store tools", Argv: []string{"gitcrawl", "portable", "--json"}, JSON: true, Mutates: true}, + "portable": {Title: "Portable store tools", Argv: []string{"gitcrawl", "portable", "prune", "--json"}, JSON: true, Mutates: true}, "clusters": {Title: "Clusters", Argv: []string{"gitcrawl", "clusters", "--json"}, JSON: true}, "legacy-sync-api": {Title: "Legacy sync-status alias", Argv: []string{"gitcrawl", "sync-status"}, Legacy: true, Deprecated: true}, } @@ -2795,6 +2797,9 @@ func (a *App) printUsage() { func (a *App) printCommandUsage(command string) error { switch command { + case "portable": + fmt.Fprint(a.Stdout, portableUsageText) + return nil case "tui": fmt.Fprint(a.Stdout, tuiUsageText) return nil @@ -2862,3 +2867,12 @@ Press n to load neighbors for the selected issue or PR. Enter from the members pane also loads neighbors before opening detail. The TUI quietly refreshes from the local store every 15 seconds and leaves the current status alone when nothing changed. ` + +const portableUsageText = `gitcrawl portable manages local portable-store snapshots. + +Usage: + gitcrawl portable prune [--body-chars N] [--no-vacuum] [--json] + +Subcommands: + prune prune volatile payloads from the configured portable store +` From 75215c9389cc1a04346382f611913bafca4866e1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 07:40:13 -0700 Subject: [PATCH 15/33] fix(tui): use crawlkit safe renderer --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 2e0dab1..941646c 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.2 h1:/K8GZvgGtYtZY3iaLPhXw+N50sOYrnaQO+egHD9HcAE= -github.com/vincentkoc/crawlkit v0.3.2/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.3 h1:BDgFVjiM/DxuaY6GKdZsQ0BZmKcxdQqdp1uLOB6B3ec= +github.com/vincentkoc/crawlkit v0.3.3/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 6b3032649b4e77124cae2ca7df46a7cbee864188 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 07:46:16 -0700 Subject: [PATCH 16/33] fix(tui): use crawlkit empty-json fix --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 941646c..5169e45 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.3 h1:BDgFVjiM/DxuaY6GKdZsQ0BZmKcxdQqdp1uLOB6B3ec= -github.com/vincentkoc/crawlkit v0.3.3/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.4 h1:f3v9Fiuw3oqR4kVmUWj1/1u7HtUBMj6NUnCW6lU/+0c= +github.com/vincentkoc/crawlkit v0.3.4/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From b543bdc172fcf13ab6e52158469b531c6db896e6 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 07:52:49 -0700 Subject: [PATCH 17/33] chore(deps): update crawlkit to v0.3.5 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 5169e45..0cb5f2a 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.4 h1:f3v9Fiuw3oqR4kVmUWj1/1u7HtUBMj6NUnCW6lU/+0c= -github.com/vincentkoc/crawlkit v0.3.4/go.mod h1:Zp6k0f6owZ81wccG26jPbLSDGmfjoxPdzgPXZcUpmW4= +github.com/vincentkoc/crawlkit v0.3.5 h1:pWA+mTVjZPUsrE/Ono/Cj31u4vqrUzq8zKO8qDy9cpc= +github.com/vincentkoc/crawlkit v0.3.5/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 73de21871dbe6f19a1b0d6a1c0876ddf529b8a8a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 09:04:43 -0700 Subject: [PATCH 18/33] chore(deps): update crawlkit to v0.3.6 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 0cb5f2a..acb8ba2 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.5 h1:pWA+mTVjZPUsrE/Ono/Cj31u4vqrUzq8zKO8qDy9cpc= -github.com/vincentkoc/crawlkit v0.3.5/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.6 h1:hb9DFowpo/VVSbF21dsvxJJo7RIATZEWRB4UgymIONI= +github.com/vincentkoc/crawlkit v0.3.6/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 360037b3ad8f41c1b7f13ffbb53742c9e83376ed Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 09:25:06 -0700 Subject: [PATCH 19/33] chore(deps): update crawlkit to v0.3.7 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index acb8ba2..723db3a 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.6 h1:hb9DFowpo/VVSbF21dsvxJJo7RIATZEWRB4UgymIONI= -github.com/vincentkoc/crawlkit v0.3.6/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.7 h1:mWfDISVIpnSIAMLG40Xwsu3nEyTz/47RxxgiNZDfl+E= +github.com/vincentkoc/crawlkit v0.3.7/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 87616b1860b2355344c56074a97d7cfa564ec385 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 09:36:26 -0700 Subject: [PATCH 20/33] docs(changelog): note TUI alignment --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 789c7d3..26288d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Add crawlkit control metadata/status surfaces with command-local `metadata --json`, `status --json`, and `doctor --json`. - Include the primary SQLite database inventory in status JSON so local control surfaces can discover archive storage without opening live stores. - Route config path handling and SQLite openers through `crawlkit` so GitHub archive tooling shares the same foundation as the Slack, Discord, and Notion crawlers. +- Keep shared crawl app TUI nomenclature aligned while `gitcrawl tui` remains the richer cluster-browser reference implementation. - Keep the existing `gitcrawl tui` as the family reference terminal interface and add CI smoke coverage for its help surface. ## 0.1.2 - 2026-05-01 From be832aa57c018769682469f0d0e2bc1988ac3067 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 09:44:39 -0700 Subject: [PATCH 21/33] chore(deps): update crawlkit to v0.3.8 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 723db3a..1f5833f 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.7 h1:mWfDISVIpnSIAMLG40Xwsu3nEyTz/47RxxgiNZDfl+E= -github.com/vincentkoc/crawlkit v0.3.7/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.8 h1:hr60QTPDCN6HoZRZLXT4t3Mvb94PJ7lhJ0zVGK3OZv4= +github.com/vincentkoc/crawlkit v0.3.8/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 8c460f1a3487582ba0932e00169454c78abb24de Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 10:04:02 -0700 Subject: [PATCH 22/33] chore(deps): update crawlkit to v0.3.9 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 1f5833f..a8649cf 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/vincentkoc/crawlkit v0.3.8 h1:hr60QTPDCN6HoZRZLXT4t3Mvb94PJ7lhJ0zVGK3OZv4= github.com/vincentkoc/crawlkit v0.3.8/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.9 h1:dwzDPFBl5yAuE42nfGs31l2Cc4iRui6cSSC5p2xDi7U= +github.com/vincentkoc/crawlkit v0.3.9/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From b68852bde21ebab8ff85da500fdc25e70ca78862 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 10:07:51 -0700 Subject: [PATCH 23/33] chore(deps): tidy crawlkit checksum --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index a8649cf..4614ba2 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.8 h1:hr60QTPDCN6HoZRZLXT4t3Mvb94PJ7lhJ0zVGK3OZv4= -github.com/vincentkoc/crawlkit v0.3.8/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/vincentkoc/crawlkit v0.3.9 h1:dwzDPFBl5yAuE42nfGs31l2Cc4iRui6cSSC5p2xDi7U= github.com/vincentkoc/crawlkit v0.3.9/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= From ef89a1d8767b2fa06ddca01237ef56b7338b0fb8 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 10:21:39 -0700 Subject: [PATCH 24/33] chore(deps): update crawlkit to v0.3.10 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 4614ba2..e964185 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/vincentkoc/crawlkit v0.3.9 h1:dwzDPFBl5yAuE42nfGs31l2Cc4iRui6cSSC5p2xDi7U= github.com/vincentkoc/crawlkit v0.3.9/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.10 h1:/uhAPx1KTlrM/yf7CEqesyESjwpKa4PI8X+ObOHDFFU= +github.com/vincentkoc/crawlkit v0.3.10/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 990d2616d68f36f7f57695bdc412b0fb72199698 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 10:35:09 -0700 Subject: [PATCH 25/33] chore(deps): tidy crawlkit checksums --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index e964185..4799c9b 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.9 h1:dwzDPFBl5yAuE42nfGs31l2Cc4iRui6cSSC5p2xDi7U= -github.com/vincentkoc/crawlkit v0.3.9/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/vincentkoc/crawlkit v0.3.10 h1:/uhAPx1KTlrM/yf7CEqesyESjwpKa4PI8X+ObOHDFFU= github.com/vincentkoc/crawlkit v0.3.10/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= From c39fad757fb1b23e4f5e45cad818d9ecd9e45d03 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 11:42:46 -0700 Subject: [PATCH 26/33] chore(deps): update crawlkit to v0.3.11 --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 4799c9b..dc11187 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.10 h1:/uhAPx1KTlrM/yf7CEqesyESjwpKa4PI8X+ObOHDFFU= -github.com/vincentkoc/crawlkit v0.3.10/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.11 h1:UhhRD6ZWa0j4+x9flWLhNe3fqlNwmP61eDZwdb0AyTU= +github.com/vincentkoc/crawlkit v0.3.11/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 32f4a13a8e7a915ccb19be055551c9e12b0f8539 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 14:55:14 -0700 Subject: [PATCH 27/33] chore(deps): bump crawlkit to v0.3.12 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index dc11187..5a7fa06 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/vincentkoc/crawlkit v0.3.11 h1:UhhRD6ZWa0j4+x9flWLhNe3fqlNwmP61eDZwdb0AyTU= github.com/vincentkoc/crawlkit v0.3.11/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.12 h1:2hs4DXk6LkI4sdbgnFU+mUNaC2gmhQfkMx5C+bbDzJE= +github.com/vincentkoc/crawlkit v0.3.12/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From ec8de7a53db8f48e7f18b515f5634c52457de754 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 15:13:30 -0700 Subject: [PATCH 28/33] chore(deps): bump crawlkit to v0.3.13 --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 5a7fa06..2402a81 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/vincentkoc/crawlkit v0.3.11 h1:UhhRD6ZWa0j4+x9flWLhNe3fqlNwmP61eDZwdb github.com/vincentkoc/crawlkit v0.3.11/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/vincentkoc/crawlkit v0.3.12 h1:2hs4DXk6LkI4sdbgnFU+mUNaC2gmhQfkMx5C+bbDzJE= github.com/vincentkoc/crawlkit v0.3.12/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.13 h1:8QDpI1KXRhvxGlHpomHn641+S3aq7nGLtD8mMLZvCHo= +github.com/vincentkoc/crawlkit v0.3.13/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 5d8b59c79b6880f1f01f4efb496f5014d5484397 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 19:34:14 -0700 Subject: [PATCH 29/33] fix(sync): log thread progress percentages --- go.sum | 2 ++ internal/cli/app.go | 13 +++++++++++++ internal/syncer/syncer.go | 21 +++++++++++++++++++++ internal/syncer/syncer_test.go | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/go.sum b/go.sum index 2402a81..a2b2cf6 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/vincentkoc/crawlkit v0.3.12 h1:2hs4DXk6LkI4sdbgnFU+mUNaC2gmhQfkMx5C+b github.com/vincentkoc/crawlkit v0.3.12/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/vincentkoc/crawlkit v0.3.13 h1:8QDpI1KXRhvxGlHpomHn641+S3aq7nGLtD8mMLZvCHo= github.com/vincentkoc/crawlkit v0.3.13/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.14 h1:+bE9yPjfE2VRvquJEpHvh+35qcX70RiqisZv/7vChW0= +github.com/vincentkoc/crawlkit v0.3.14/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= diff --git a/internal/cli/app.go b/internal/cli/app.go index ca9bcf6..3551577 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io" + "log/slog" "os" "os/exec" "path/filepath" @@ -1821,6 +1822,7 @@ func (a *App) syncRepository(ctx context.Context, owner, repo string, options sy Reporter: func(message string) { fmt.Fprintln(a.Stderr, message) }, + Logger: progressLogger(a.Stderr), }) if err != nil { return syncer.Stats{}, err @@ -1828,6 +1830,17 @@ func (a *App) syncRepository(ctx context.Context, owner, repo string, options sy return stats, nil } +func progressLogger(w io.Writer) *slog.Logger { + return slog.New(slog.NewTextHandler(w, &slog.HandlerOptions{ + ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr { + if attr.Key == slog.TimeKey { + return slog.Attr{} + } + return attr + }, + })) +} + func (a *App) runInit(ctx context.Context, args []string) error { fs := flag.NewFlagSet("init", flag.ContinueOnError) fs.SetOutput(io.Discard) diff --git a/internal/syncer/syncer.go b/internal/syncer/syncer.go index bb47944..d41c8a3 100644 --- a/internal/syncer/syncer.go +++ b/internal/syncer/syncer.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "log/slog" "strconv" "strings" "time" @@ -13,6 +14,7 @@ import ( "github.com/openclaw/gitcrawl/internal/documents" gh "github.com/openclaw/gitcrawl/internal/github" "github.com/openclaw/gitcrawl/internal/store" + "github.com/vincentkoc/crawlkit/progress" ) type GitHubClient interface { @@ -45,6 +47,7 @@ type Options struct { IncludeComments bool IncludePRDetails bool Reporter gh.Reporter + Logger *slog.Logger } type Stats struct { @@ -132,6 +135,15 @@ func (s *Syncer) Sync(ctx context.Context, options Options) (Stats, error) { MetadataOnly: !options.IncludeComments, StartedAt: started, } + tracker := progress.New(options.Logger, progress.Options{ + Name: "sync", + Unit: "threads", + Total: int64(len(rows)), + Attrs: []any{ + "repository", stats.Repository, + "state", state, + }, + }) persist := func(st *store.Store) error { for _, row := range rows { thread := mapIssueToThread(repoID, row, s.now().Format(time.RFC3339Nano)) @@ -169,6 +181,11 @@ func (s *Syncer) Sync(ctx context.Context, options Options) (Stats, error) { } else { stats.IssuesSynced++ } + tracker.Add(1, + "number", thread.Number, + "kind", thread.Kind, + "thread_state", thread.State, + ) } if len(numbers) == 0 && state == "open" && since != "" && options.Limit <= 0 { closed, err := s.applyClosedOverlapSweep(ctx, st, repoID, options, since) @@ -193,13 +210,17 @@ func (s *Syncer) Sync(ctx context.Context, options Options) (Stats, error) { } if !options.IncludeComments { if err := s.store.WithTx(ctx, persist); err != nil { + tracker.Finish(err) return Stats{}, err } + tracker.Finish(nil) return stats, nil } if err := persist(s.store); err != nil { + tracker.Finish(err) return Stats{}, err } + tracker.Finish(nil) return stats, nil } diff --git a/internal/syncer/syncer_test.go b/internal/syncer/syncer_test.go index 7a2a606..8ac8c85 100644 --- a/internal/syncer/syncer_test.go +++ b/internal/syncer/syncer_test.go @@ -1,9 +1,12 @@ package syncer import ( + "bytes" "context" "encoding/json" + "log/slog" "path/filepath" + "strings" "testing" "time" @@ -286,7 +289,13 @@ func TestSyncPersistsIssuesAndPullRequests(t *testing.T) { s := New(fakeGitHub{}, st) s.now = func() time.Time { return time.Date(2026, 4, 26, 0, 0, 0, 0, time.UTC) } - stats, err := s.Sync(ctx, Options{Owner: "openclaw", Repo: "gitcrawl", IncludeComments: true}) + var progressLogs bytes.Buffer + stats, err := s.Sync(ctx, Options{ + Owner: "openclaw", + Repo: "gitcrawl", + IncludeComments: true, + Logger: testProgressLogger(&progressLogs), + }) if err != nil { t.Fatalf("sync: %v", err) } @@ -321,6 +330,18 @@ func TestSyncPersistsIssuesAndPullRequests(t *testing.T) { if documentCount != 1 { t.Fatalf("document count: got %d want 1", documentCount) } + for _, want := range []string{ + `msg="sync progress"`, + `state=finished`, + `unit=threads`, + `percent=100.0`, + `completion=100.0%`, + `repository=openclaw/gitcrawl`, + } { + if !strings.Contains(progressLogs.String(), want) { + t.Fatalf("missing %q in progress logs:\n%s", want, progressLogs.String()) + } + } } func TestSyncHydratesPullReviewComments(t *testing.T) { @@ -681,3 +702,14 @@ func TestMappingFallbackBranches(t *testing.T) { t.Fatalf("thread = %+v", thread) } } + +func testProgressLogger(out *bytes.Buffer) *slog.Logger { + return slog.New(slog.NewTextHandler(out, &slog.HandlerOptions{ + ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr { + if attr.Key == slog.TimeKey { + return slog.Attr{} + } + return attr + }, + })) +} From 11455a6a1738228f2a1bb12df77238f6ed739a11 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 00:07:05 -0700 Subject: [PATCH 30/33] fix(tui): pick up crawlkit renderer --- go.sum | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/go.sum b/go.sum index a2b2cf6..454942d 100644 --- a/go.sum +++ b/go.sum @@ -56,14 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.11 h1:UhhRD6ZWa0j4+x9flWLhNe3fqlNwmP61eDZwdb0AyTU= -github.com/vincentkoc/crawlkit v0.3.11/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= -github.com/vincentkoc/crawlkit v0.3.12 h1:2hs4DXk6LkI4sdbgnFU+mUNaC2gmhQfkMx5C+bbDzJE= -github.com/vincentkoc/crawlkit v0.3.12/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= -github.com/vincentkoc/crawlkit v0.3.13 h1:8QDpI1KXRhvxGlHpomHn641+S3aq7nGLtD8mMLZvCHo= -github.com/vincentkoc/crawlkit v0.3.13/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= -github.com/vincentkoc/crawlkit v0.3.14 h1:+bE9yPjfE2VRvquJEpHvh+35qcX70RiqisZv/7vChW0= -github.com/vincentkoc/crawlkit v0.3.14/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.15 h1:8DbNcwI7lP4MDSr1Sc9Uyxw1kTCZMueiSZdJ6RtUJm8= +github.com/vincentkoc/crawlkit v0.3.15/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From b78370f2bab2548fa6f6ed4cfc1ee1775e812ded Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 00:14:32 -0700 Subject: [PATCH 31/33] fix(tui): use compact-pane crawlkit --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 454942d..8667362 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.15 h1:8DbNcwI7lP4MDSr1Sc9Uyxw1kTCZMueiSZdJ6RtUJm8= -github.com/vincentkoc/crawlkit v0.3.15/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.3.16 h1:RotBLgFmWMPUrwv43xUQNnT4yj77SwjmDKf3J97udWY= +github.com/vincentkoc/crawlkit v0.3.16/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From b7176c35693887dbaeb0759c4e5d0c6720d8b7b3 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 5 May 2026 02:12:56 -0700 Subject: [PATCH 32/33] chore(deps): use crawlkit v0.4.0 --- go.mod | 6 +++--- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1208458..8fca390 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,7 @@ require ( github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/charmbracelet/x/ansi v0.11.7 github.com/mattn/go-isatty v0.0.22 - github.com/pelletier/go-toml/v2 v2.3.1 - github.com/vincentkoc/crawlkit v0.3.16 - modernc.org/sqlite v1.50.0 + github.com/vincentkoc/crawlkit v0.4.0 ) require ( @@ -31,6 +29,7 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -39,4 +38,5 @@ require ( modernc.org/libc v1.72.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.50.0 // indirect ) diff --git a/go.sum b/go.sum index 8667362..ebd0ef5 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/vincentkoc/crawlkit v0.3.16 h1:RotBLgFmWMPUrwv43xUQNnT4yj77SwjmDKf3J97udWY= -github.com/vincentkoc/crawlkit v0.3.16/go.mod h1:tSSR6CmUqKmfoxzxxRJGARm95sH+Acu63nhzrXkpXo0= +github.com/vincentkoc/crawlkit v0.4.0 h1:1jQZAYbBivy6d7ewNdMZ8THgmJVwb+pQT0kH5Z9COHI= +github.com/vincentkoc/crawlkit v0.4.0/go.mod h1:/ioLA/tyZ/927kAOGg0M8Mrqk7pnTZLpCKWfpul9zoE= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= From 7342912545191d6081e55990f923a6b1a23703d1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 5 May 2026 02:23:27 -0700 Subject: [PATCH 33/33] fix(tui): allow empty json smoke --- internal/cli/app.go | 62 ++++++++++++++++++++++++++++++++++------ internal/cli/app_test.go | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 9 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index 3551577..d9611d1 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -1083,23 +1083,35 @@ func (a *App) runTUI(ctx context.Context, args []string) error { rt, err = a.openLocalRuntimeReadOnly(ctx) } if err != nil { + if !interactive && errors.Is(err, os.ErrNotExist) { + cfg := config.Default() + if cfgErr := cfg.Normalize(); cfgErr != nil { + return cfgErr + } + sort, sortErr := resolveTUISort(*sortMode, cfg) + if sortErr != nil { + return sortErr + } + return a.writeOutput("tui", emptyClusterBrowserPayload(ctx, cfg, cfg.DBPath, sort, minSize, limit, *hideClosed), true) + } return err } defer rt.Store.Close() repo, inferred, err := a.resolveOptionalRepository(ctx, rt, fs.Args()) if err != nil { + if !interactive && len(fs.Args()) == 0 && strings.Contains(err.Error(), "no local repositories found") { + sort, sortErr := resolveTUISort(*sortMode, rt.Config) + if sortErr != nil { + return sortErr + } + return a.writeOutput("tui", emptyClusterBrowserPayload(ctx, rt.Config, rt.SourceDBPath, sort, minSize, limit, *hideClosed), true) + } return err } - sort := strings.TrimSpace(*sortMode) - if sort == "" { - sort = strings.TrimSpace(rt.Config.TUI.DefaultSort) - } - if sort == "" { - sort = "size" - } - if sort != "recent" && sort != "oldest" && sort != "size" { - return usageErr(fmt.Errorf("unsupported sort %q", sort)) + sort, err := resolveTUISort(*sortMode, rt.Config) + if err != nil { + return err } showClosed := !*hideClosed || *includeClosed @@ -1154,6 +1166,38 @@ func (a *App) runTUI(ctx context.Context, args []string) error { return a.runInteractiveTUI(ctx, rt.Store, repo.ID, payload) } +func resolveTUISort(raw string, cfg config.Config) (string, error) { + sort := strings.TrimSpace(raw) + if sort == "" { + sort = strings.TrimSpace(cfg.TUI.DefaultSort) + } + if sort == "" { + sort = "size" + } + if sort != "recent" && sort != "oldest" && sort != "size" { + return "", usageErr(fmt.Errorf("unsupported sort %q", sort)) + } + return sort, nil +} + +func emptyClusterBrowserPayload(ctx context.Context, cfg config.Config, sourceDBPath, sort string, minSize, limit int, hideClosed bool) clusterBrowserPayload { + if strings.TrimSpace(sourceDBPath) == "" { + sourceDBPath = cfg.DBPath + } + return clusterBrowserPayload{ + Mode: "cluster-browser", + DBSource: databaseSourceKind(sourceDBPath), + DBLocation: databaseSourceLocation(ctx, sourceDBPath), + Sort: sort, + MinSize: minSize, + Limit: limit, + HideClosed: hideClosed, + EmbedModel: cfg.OpenAI.EmbedModel, + EmbeddingBasis: cfg.EmbeddingBasis, + Clusters: []store.ClusterSummary{}, + } +} + func databaseSourceKind(dbPath string) string { if _, ok := portableStoreRoot(dbPath); ok { return "remote" diff --git a/internal/cli/app_test.go b/internal/cli/app_test.go index e61349b..7935a92 100644 --- a/internal/cli/app_test.go +++ b/internal/cli/app_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" @@ -1022,6 +1023,60 @@ func TestTUIInfersRepository(t *testing.T) { } } +func TestTUIJSONUsesDefaultsWhenConfigMissing(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + configPath := filepath.Join(dir, "missing.toml") + t.Setenv("GITCRAWL_DB_PATH", filepath.Join(dir, "missing.db")) + + run := New() + var stdout bytes.Buffer + run.Stdout = &stdout + if err := run.Run(ctx, []string{"--config", configPath, "tui", "--json"}); err != nil { + t.Fatalf("tui: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { + t.Fatalf("decode tui payload: %v\n%s", err, stdout.String()) + } + if payload["mode"] != "cluster-browser" { + t.Fatalf("mode = %#v", payload["mode"]) + } + clusters, ok := payload["clusters"].([]any) + if !ok || len(clusters) != 0 { + t.Fatalf("clusters = %#v", payload["clusters"]) + } + if _, err := os.Stat(configPath); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("config file should not be created, stat err=%v", err) + } +} + +func TestTUIJSONHandlesEmptyStoreWithoutRepository(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + configPath := filepath.Join(dir, "config.toml") + dbPath := filepath.Join(dir, "gitcrawl.db") + app := New() + if err := app.Run(ctx, []string{"--config", configPath, "init", "--db", dbPath}); err != nil { + t.Fatalf("init: %v", err) + } + + run := New() + var stdout bytes.Buffer + run.Stdout = &stdout + if err := run.Run(ctx, []string{"--config", configPath, "tui", "--json"}); err != nil { + t.Fatalf("tui: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { + t.Fatalf("decode tui payload: %v\n%s", err, stdout.String()) + } + clusters, ok := payload["clusters"].([]any) + if !ok || len(clusters) != 0 { + t.Fatalf("clusters = %#v", payload["clusters"]) + } +} + func TestTUIRequiresInteractiveTerminalByDefault(t *testing.T) { ctx := context.Background() dir := t.TempDir()