gora-cli 0.1.2__py3-none-any.whl

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.
gora/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Gora indexes local coding-agent chat histories."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.2"
gora/__main__.py ADDED
@@ -0,0 +1,13 @@
1
+ from pathlib import Path
2
+ import sys
3
+
4
+
5
+ if __package__:
6
+ from .cli import main
7
+ else: # Supports `python gora` style execution.
8
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
9
+ from gora.cli import main
10
+
11
+
12
+ if __name__ == "__main__":
13
+ raise SystemExit(main())
gora/cli.py ADDED
@@ -0,0 +1,459 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from contextlib import closing
5
+ from pathlib import Path
6
+ import shlex
7
+ import sqlite3
8
+ import sys
9
+ import textwrap
10
+
11
+ from .parsers import PROVIDERS, discover_history_files, parse_history_file
12
+ from .store import (
13
+ DEFAULT_ROLES,
14
+ connect,
15
+ default_db_path,
16
+ get_session_messages,
17
+ list_sessions,
18
+ model_counts,
19
+ provider_counts,
20
+ repo_counts,
21
+ search_messages,
22
+ source_is_current,
23
+ upsert_session,
24
+ )
25
+
26
+
27
+ def main(argv: list[str] | None = None) -> int:
28
+ parser = build_parser()
29
+ args = parser.parse_args(argv)
30
+ return args.func(args)
31
+
32
+
33
+ def build_parser() -> argparse.ArgumentParser:
34
+ parser = argparse.ArgumentParser(
35
+ prog="gora",
36
+ description="Index and query local Codex, Claude Code, and Pi chat history.",
37
+ )
38
+ parser.add_argument(
39
+ "--db",
40
+ type=Path,
41
+ default=None,
42
+ help="SQLite index path. Defaults to GORA_DB or the user data directory.",
43
+ )
44
+ parser.set_defaults(func=cmd_tui, provider=None, cwd=None, model=None, include_children=False, limit=20)
45
+
46
+ subcommands = parser.add_subparsers(dest="command")
47
+
48
+ doctor = subcommands.add_parser("doctor", help="Show configured paths and discovered files.")
49
+ doctor.add_argument("--home", type=Path, default=Path.home(), help="Home directory to scan.")
50
+ doctor.set_defaults(func=cmd_doctor)
51
+
52
+ import_cmd = subcommands.add_parser("import", help="Import provider histories into the index.")
53
+ add_provider_flags(import_cmd)
54
+ import_cmd.add_argument("--home", type=Path, default=Path.home(), help="Home directory to scan.")
55
+ import_cmd.add_argument("--force", action="store_true", help="Scan files even when unchanged.")
56
+ import_cmd.add_argument("--limit", type=positive_int, help="Import at most N files per provider.")
57
+ import_cmd.add_argument(
58
+ "--include-tool-results",
59
+ action="store_true",
60
+ help="Deprecated no-op. Tool calls and redacted tool results are imported by default.",
61
+ )
62
+ import_cmd.set_defaults(func=cmd_import)
63
+
64
+ harnesses_cmd = subcommands.add_parser("harnesses", help="List supported chat harnesses.")
65
+ harnesses_cmd.add_argument("--home", type=Path, default=Path.home(), help="Home directory to scan.")
66
+ harnesses_cmd.set_defaults(func=cmd_harnesses)
67
+
68
+ models_cmd = subcommands.add_parser("models", help="List indexed model names.")
69
+ models_cmd.add_argument("--limit", type=positive_int, default=20, help="Maximum models to show.")
70
+ models_cmd.set_defaults(func=cmd_models)
71
+
72
+ repos_cmd = subcommands.add_parser("repos", help="List indexed repos.")
73
+ add_provider_flags(repos_cmd)
74
+ repos_cmd.add_argument("--model", action="append", help="Only show repos with sessions that used this model. Can be repeated.")
75
+ add_child_session_flag(repos_cmd)
76
+ repos_cmd.add_argument("--limit", type=positive_int, default=20, help="Maximum repos to show.")
77
+ repos_cmd.set_defaults(func=cmd_repos)
78
+
79
+ recent_cmd = subcommands.add_parser("recent", help="List recent chats.")
80
+ add_provider_flags(recent_cmd)
81
+ recent_cmd.add_argument("--cwd", help="Only show sessions whose cwd contains this text.")
82
+ recent_cmd.add_argument("--model", action="append", help="Only show sessions that used this model. Can be repeated.")
83
+ add_child_session_flag(recent_cmd)
84
+ recent_cmd.add_argument("--limit", type=positive_int, default=20, help="Maximum sessions to show.")
85
+ recent_cmd.set_defaults(func=cmd_list)
86
+
87
+ list_cmd = subcommands.add_parser("list", help="List indexed sessions.")
88
+ add_provider_flags(list_cmd)
89
+ list_cmd.add_argument("--cwd", help="Only show sessions whose cwd contains this text.")
90
+ list_cmd.add_argument("--model", action="append", help="Only show sessions that used this model. Can be repeated.")
91
+ add_child_session_flag(list_cmd)
92
+ list_cmd.add_argument("--limit", type=positive_int, default=20, help="Maximum sessions to show.")
93
+ list_cmd.set_defaults(func=cmd_list)
94
+
95
+ search_cmd = subcommands.add_parser("search", help="Search indexed messages.")
96
+ add_provider_flags(search_cmd)
97
+ search_cmd.add_argument("query", nargs="+", help="Search terms.")
98
+ search_cmd.add_argument("--cwd", help="Only search sessions whose cwd contains this text.")
99
+ search_cmd.add_argument("--model", action="append", help="Only search sessions that used this model. Can be repeated.")
100
+ add_child_session_flag(search_cmd)
101
+ search_cmd.add_argument("--role", action="append", help="Role to include. Can be repeated.")
102
+ search_cmd.add_argument(
103
+ "--all-roles",
104
+ action="store_true",
105
+ help="Deprecated no-op. Search includes all roles by default.",
106
+ )
107
+ search_cmd.add_argument("--limit", type=positive_int, default=20, help="Maximum matches to show.")
108
+ search_cmd.set_defaults(func=cmd_search)
109
+
110
+ show_cmd = subcommands.add_parser("show", help="Show a session transcript from the index.")
111
+ show_cmd.add_argument("session", help="Session key, session id, or unique prefix.")
112
+ show_cmd.add_argument("--role", action="append", help="Role to include. Can be repeated.")
113
+ show_cmd.add_argument(
114
+ "--all-roles",
115
+ action="store_true",
116
+ help="Deprecated no-op. Transcripts include all roles by default.",
117
+ )
118
+ show_cmd.set_defaults(func=cmd_show)
119
+
120
+ return parser
121
+
122
+
123
+ def add_provider_flags(parser: argparse.ArgumentParser) -> None:
124
+ parser.add_argument(
125
+ "--provider",
126
+ choices=PROVIDERS,
127
+ action="append",
128
+ help="Provider to include. Can be repeated. Defaults to all providers.",
129
+ )
130
+
131
+
132
+ def add_child_session_flag(parser: argparse.ArgumentParser) -> None:
133
+ parser.add_argument(
134
+ "--include-children",
135
+ action="store_true",
136
+ help="Include Codex child/subagent rollouts. Defaults to root user chats only.",
137
+ )
138
+
139
+
140
+ def cmd_doctor(args: argparse.Namespace) -> int:
141
+ db_path = args.db or default_db_path()
142
+ print(f"db: {db_path}")
143
+ for provider in PROVIDERS:
144
+ files = discover_history_files(provider, args.home)
145
+ root = _provider_root(provider, args.home)
146
+ print(f"{provider}: {len(files)} files under {root}")
147
+
148
+ if db_path.exists():
149
+ with connect(args.db) as connection:
150
+ counts = provider_counts(connection)
151
+ indexed = ", ".join(f"{provider}={counts.get(provider, 0)}" for provider in PROVIDERS)
152
+ print(f"indexed sessions: {indexed}")
153
+ else:
154
+ print("indexed sessions: none yet")
155
+ return 0
156
+
157
+
158
+ def cmd_tui(args: argparse.Namespace) -> int:
159
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
160
+ return cmd_list(args)
161
+
162
+ with closing(connect(args.db)):
163
+ pass
164
+
165
+ from .tui import run_tui
166
+
167
+ return run_tui(args.db)
168
+
169
+
170
+ def cmd_import(args: argparse.Namespace) -> int:
171
+ providers = args.provider or list(PROVIDERS)
172
+ stats: dict[str, dict[str, int]] = {
173
+ provider: {"imported": 0, "skipped": 0, "errors": 0} for provider in providers
174
+ }
175
+
176
+ with connect(args.db) as connection:
177
+ for provider in providers:
178
+ files = discover_history_files(provider, args.home)
179
+ if args.limit is not None:
180
+ files = files[: args.limit]
181
+ for path in files:
182
+ try:
183
+ if not args.force and source_is_current(connection, path):
184
+ stats[provider]["skipped"] += 1
185
+ continue
186
+ session = parse_history_file(
187
+ provider,
188
+ path,
189
+ include_tool_results=args.include_tool_results,
190
+ )
191
+ if not session.messages:
192
+ stats[provider]["skipped"] += 1
193
+ continue
194
+ changed = upsert_session(connection, session, force=args.force)
195
+ except (OSError, ValueError, sqlite3.Error) as exc:
196
+ stats[provider]["errors"] += 1
197
+ print(f"{provider}: failed {path}: {exc}", file=sys.stderr)
198
+ continue
199
+ stats[provider]["imported" if changed else "skipped"] += 1
200
+
201
+ for provider in providers:
202
+ provider_stats = stats[provider]
203
+ print(
204
+ f"{provider}: imported={provider_stats['imported']} "
205
+ f"skipped={provider_stats['skipped']} errors={provider_stats['errors']}"
206
+ )
207
+ return 1 if any(value["errors"] for value in stats.values()) else 0
208
+
209
+
210
+ def cmd_harnesses(args: argparse.Namespace) -> int:
211
+ with connect(args.db) as connection:
212
+ counts = provider_counts(connection)
213
+
214
+ for index, provider in enumerate(PROVIDERS, start=1):
215
+ root = _provider_root(provider, args.home)
216
+ files = discover_history_files(provider, args.home)
217
+ print(f"{index}. Harness: {_provider_label(provider)}")
218
+ print(f" ID: {provider}")
219
+ print(f" Indexed chats: {counts.get(provider, 0)}")
220
+ print(f" History path: {root}")
221
+ print(f" Discovered files: {len(files)}")
222
+ print()
223
+
224
+ print("Quick command:")
225
+ print(" gora list --provider codex")
226
+ return 0
227
+
228
+
229
+ def cmd_models(args: argparse.Namespace) -> int:
230
+ with connect(args.db) as connection:
231
+ rows = model_counts(connection)[: args.limit]
232
+
233
+ if not rows:
234
+ print("No model metadata indexed yet. Run `gora import --force` to backfill from local histories.")
235
+ return 0
236
+
237
+ for index, row in enumerate(rows, start=1):
238
+ print(f"{index}. Model: {row['model']}")
239
+ if row["model_provider"]:
240
+ print(f" Provider: {row['model_provider']}")
241
+ print(f" Chats: {row['sessions']}")
242
+ print(f" Messages: {row['messages']}")
243
+ print()
244
+
245
+ print("Quick command:")
246
+ print(f" gora search \"<query>\" --model {_quote(rows[0]['model'])}")
247
+ return 0
248
+
249
+
250
+ def cmd_repos(args: argparse.Namespace) -> int:
251
+ with connect(args.db) as connection:
252
+ rows = repo_counts(
253
+ connection,
254
+ providers=args.provider,
255
+ models=args.model,
256
+ include_children=args.include_children,
257
+ limit=args.limit,
258
+ )
259
+
260
+ if not rows:
261
+ print("No indexed repos matched. Run `gora import` first.")
262
+ return 0
263
+
264
+ for index, row in enumerate(rows, start=1):
265
+ print(f"{index}. Repo: {row['cwd']}")
266
+ print(f" Chats: {row['sessions']}")
267
+ print(f" Harnesses: {row['providers'] or '-'}")
268
+ if row["models"]:
269
+ print(f" Models: {_compact_csv(row['models'], limit=4)}")
270
+ print(f" Last seen: {row['updated_at'] or '-'}")
271
+ print()
272
+
273
+ print("Quick command:")
274
+ print(f" gora list --cwd {_quote(rows[0]['cwd'])}")
275
+ return 0
276
+
277
+
278
+ def cmd_list(args: argparse.Namespace) -> int:
279
+ with connect(args.db) as connection:
280
+ rows = list_sessions(
281
+ connection,
282
+ providers=args.provider,
283
+ cwd=args.cwd,
284
+ models=args.model,
285
+ include_children=args.include_children,
286
+ limit=args.limit,
287
+ )
288
+ if not rows:
289
+ print("No indexed sessions matched. Run `gora import` first.")
290
+ return 0
291
+
292
+ for index, row in enumerate(rows, start=1):
293
+ print_session_result(index, row)
294
+
295
+ print("Quick command:")
296
+ print(f" gora show {_quote(rows[0]['session_key'])}")
297
+ return 0
298
+
299
+
300
+ def cmd_search(args: argparse.Namespace) -> int:
301
+ roles = _roles_from_args(args)
302
+ query = " ".join(args.query)
303
+
304
+ with connect(args.db) as connection:
305
+ rows = search_messages(
306
+ connection,
307
+ query,
308
+ providers=args.provider,
309
+ cwd=args.cwd,
310
+ models=args.model,
311
+ roles=roles,
312
+ include_children=args.include_children,
313
+ limit=args.limit,
314
+ )
315
+
316
+ if not rows:
317
+ print("No matches.")
318
+ return 0
319
+
320
+ for index, row in enumerate(rows, start=1):
321
+ snippet = _clean_snippet(row["snippet"] or row["text"])
322
+ print(f"{index}. Match: {row['title'] or row['session_key']}")
323
+ print(f" Session: {row['session_key']}#{row['ordinal']}")
324
+ print(f" Harness: {_provider_label(row['provider'])}")
325
+ if row["parent_session_key"]:
326
+ print(f" Parent: {row['parent_session_key']}")
327
+ print(f" Role: {row['role']}")
328
+ print(f" Time: {row['timestamp'] or '-'}")
329
+ if row["cwd"]:
330
+ print(f" Repo: {row['cwd']}")
331
+ if row["model"]:
332
+ print(f" Model: {row['model']}")
333
+ elif row["models"]:
334
+ print(f" Session models: {row['models']}")
335
+ print(f" Text: {snippet}")
336
+ print()
337
+
338
+ print("Quick command:")
339
+ print(f" gora show {_quote(rows[0]['session_key'])}")
340
+ return 0
341
+
342
+
343
+ def cmd_show(args: argparse.Namespace) -> int:
344
+ roles = _roles_from_args(args)
345
+ with connect(args.db) as connection:
346
+ try:
347
+ session, messages = get_session_messages(connection, args.session, roles=roles)
348
+ except LookupError as exc:
349
+ print(str(exc), file=sys.stderr)
350
+ return 1
351
+
352
+ print(
353
+ format_session_line(
354
+ session["session_key"],
355
+ session["provider"],
356
+ session["updated_at"] or session["started_at"] or "-",
357
+ session["message_count"],
358
+ session["cwd"],
359
+ session["title"],
360
+ )
361
+ )
362
+ print(f"source: {session['source_path']}")
363
+ if session["parent_session_key"]:
364
+ print(f"parent: {session['parent_session_key']}")
365
+ if session["thread_source"] and session["thread_source"] != "user":
366
+ print(f"thread source: {session['thread_source']}")
367
+ if session["source_label"] and session["source_label"] != "cli":
368
+ print(f"source label: {session['source_label']}")
369
+ for message in messages:
370
+ timestamp = message["timestamp"] or "-"
371
+ text = textwrap.indent(message["text"].strip(), " ")
372
+ print(f"\n[{message['ordinal']}] {timestamp} {message['role']}")
373
+ print(text)
374
+ return 0
375
+
376
+
377
+ def format_session_line(
378
+ session_key: str,
379
+ provider: str,
380
+ timestamp: str,
381
+ message_count: int,
382
+ cwd: str | None,
383
+ title: str | None,
384
+ models: str | None = None,
385
+ ) -> str:
386
+ cwd_part = f" cwd={cwd}" if cwd else ""
387
+ model_part = f" models={models}" if models else ""
388
+ title_part = f" {title}" if title else ""
389
+ return f"{session_key} {timestamp} {provider} messages={message_count}{model_part}{cwd_part}{title_part}"
390
+
391
+
392
+ def print_session_result(index: int, row: sqlite3.Row) -> None:
393
+ print(f"{index}. Chat: {row['title'] or '(untitled chat)'}")
394
+ print(f" Session: {row['session_key']}")
395
+ print(f" Harness: {_provider_label(row['provider'])}")
396
+ print(f" Updated: {row['updated_at'] or row['started_at'] or '-'}")
397
+ print(f" Messages: {row['message_count']}")
398
+ if row["cwd"]:
399
+ print(f" Repo: {row['cwd']}")
400
+ if row["models"]:
401
+ print(f" Models: {_compact_csv(row['models'], limit=4)}")
402
+ if row["parent_session_key"]:
403
+ print(f" Parent: {row['parent_session_key']}")
404
+ if row["thread_source"] and row["thread_source"] != "user":
405
+ print(f" Thread source: {row['thread_source']}")
406
+ if row["source_label"] and row["source_label"] != "cli":
407
+ print(f" Source: {row['source_label']}")
408
+ print()
409
+
410
+
411
+ def _roles_from_args(args: argparse.Namespace) -> tuple[str, ...] | None:
412
+ if getattr(args, "all_roles", False):
413
+ return None
414
+ if getattr(args, "role", None):
415
+ return tuple(role.strip().lower() for role in args.role if role.strip())
416
+ return DEFAULT_ROLES
417
+
418
+
419
+ def _provider_root(provider: str, home: Path) -> Path:
420
+ if provider == "codex":
421
+ return home / ".codex" / "sessions"
422
+ if provider == "claude":
423
+ return home / ".claude" / "projects"
424
+ return home / ".pi" / "agent" / "sessions"
425
+
426
+
427
+ def _provider_label(provider: str) -> str:
428
+ if provider == "codex":
429
+ return "Codex"
430
+ if provider == "claude":
431
+ return "Claude Code"
432
+ if provider == "pi":
433
+ return "Pi"
434
+ return provider
435
+
436
+
437
+ def _quote(value: object) -> str:
438
+ return shlex.quote(str(value))
439
+
440
+
441
+ def _compact_csv(value: str, *, limit: int) -> str:
442
+ parts = [part for part in value.split(",") if part]
443
+ if len(parts) <= limit:
444
+ return ",".join(parts)
445
+ return f"{','.join(parts[:limit])} +{len(parts) - limit}"
446
+
447
+
448
+ def _clean_snippet(value: str) -> str:
449
+ return " ".join(value.split())
450
+
451
+
452
+ def positive_int(value: str) -> int:
453
+ try:
454
+ parsed = int(value)
455
+ except ValueError as exc:
456
+ raise argparse.ArgumentTypeError("must be an integer") from exc
457
+ if parsed < 1:
458
+ raise argparse.ArgumentTypeError("must be greater than zero")
459
+ return parsed
gora/go_tui/go.mod ADDED
@@ -0,0 +1,42 @@
1
+ module gora/go_tui
2
+
3
+ go 1.25.0
4
+
5
+ require (
6
+ github.com/charmbracelet/bubbles v1.0.0
7
+ github.com/charmbracelet/bubbletea v1.3.10
8
+ github.com/charmbracelet/lipgloss v1.1.0
9
+ github.com/muesli/termenv v0.16.0
10
+ modernc.org/sqlite v1.52.0
11
+ )
12
+
13
+ require (
14
+ github.com/atotto/clipboard v0.1.4 // indirect
15
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
16
+ github.com/charmbracelet/colorprofile v0.4.1 // indirect
17
+ github.com/charmbracelet/x/ansi v0.11.6 // indirect
18
+ github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
19
+ github.com/charmbracelet/x/term v0.2.2 // indirect
20
+ github.com/clipperhouse/displaywidth v0.9.0 // indirect
21
+ github.com/clipperhouse/stringish v0.1.1 // indirect
22
+ github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
23
+ github.com/dustin/go-humanize v1.0.1 // indirect
24
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
25
+ github.com/google/uuid v1.6.0 // indirect
26
+ github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
27
+ github.com/mattn/go-isatty v0.0.20 // indirect
28
+ github.com/mattn/go-localereader v0.0.1 // indirect
29
+ github.com/mattn/go-runewidth v0.0.19 // indirect
30
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
31
+ github.com/muesli/cancelreader v0.2.2 // indirect
32
+ github.com/ncruces/go-strftime v1.0.0 // indirect
33
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
34
+ github.com/rivo/uniseg v0.4.7 // indirect
35
+ github.com/sahilm/fuzzy v0.1.1 // indirect
36
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
37
+ golang.org/x/sys v0.42.0 // indirect
38
+ golang.org/x/text v0.3.8 // indirect
39
+ modernc.org/libc v1.72.3 // indirect
40
+ modernc.org/mathutil v1.7.1 // indirect
41
+ modernc.org/memory v1.11.0 // indirect
42
+ )
gora/go_tui/go.sum ADDED
@@ -0,0 +1,106 @@
1
+ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2
+ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
4
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
5
+ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
6
+ github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
7
+ github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
8
+ github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
9
+ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
10
+ github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
11
+ github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
12
+ github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
13
+ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
14
+ github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
15
+ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
16
+ github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
17
+ github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
18
+ github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
19
+ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
20
+ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
21
+ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
22
+ github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
23
+ github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
24
+ github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
25
+ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
26
+ github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
27
+ github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
28
+ github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
29
+ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
30
+ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
31
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
32
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
33
+ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
34
+ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
35
+ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
36
+ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
37
+ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
38
+ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
39
+ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
40
+ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
41
+ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
42
+ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
43
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
44
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
45
+ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
46
+ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
47
+ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
48
+ github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
49
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
50
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
51
+ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
52
+ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
53
+ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
54
+ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
55
+ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
56
+ github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
57
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
58
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
59
+ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
60
+ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
61
+ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
62
+ github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
63
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
64
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
65
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
66
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
67
+ golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
68
+ golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
69
+ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
70
+ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
71
+ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
72
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73
+ golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
74
+ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
75
+ golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
76
+ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
77
+ golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
78
+ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
79
+ modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
80
+ modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
81
+ modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
82
+ modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
83
+ modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
84
+ modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
85
+ modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
86
+ modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
87
+ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
88
+ modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
89
+ modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
90
+ modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
91
+ modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
92
+ modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
93
+ modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
94
+ modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
95
+ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
96
+ modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
97
+ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
98
+ modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
99
+ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
100
+ modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
101
+ modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
102
+ modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
103
+ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
104
+ modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
105
+ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
106
+ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=