ghosttrap-cli 0.3.15__tar.gz → 0.3.17__tar.gz
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.
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/PKG-INFO +5 -1
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/README.md +4 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli/cli.py +177 -29
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/PKG-INFO +5 -1
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/pyproject.toml +1 -1
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli/__init__.py +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/SOURCES.txt +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/dependency_links.txt +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/entry_points.txt +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/requires.txt +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/ghosttrap_cli.egg-info/top_level.txt +0 -0
- {ghosttrap_cli-0.3.15 → ghosttrap_cli-0.3.17}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghosttrap-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: Watch for errors streaming from ghosttrap.io
|
|
5
5
|
Project-URL: Homepage, https://github.com/alex-rowley/ghosttrap-cli
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -51,9 +51,13 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
|
|
|
51
51
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
52
52
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
53
53
|
| `ghosttrap watch` | Stream all errors continuously |
|
|
54
|
+
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
55
|
+
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
54
56
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
55
57
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
56
58
|
|
|
59
|
+
`peek`, `watch`, `last`, and `clear` accept `--repo owner/name` to target a specific claimed repo when you're not inside its working tree (e.g. `ghosttrap peek --repo alex-rowley/ghosttrap-cli`). Otherwise they detect the repo from cwd. `nuke` is intentionally cwd-locked.
|
|
60
|
+
|
|
57
61
|
## How it works
|
|
58
62
|
|
|
59
63
|
- **Setup** authenticates with GitHub (via the active `gh` account) to prove you have access to the repo, then saves a repo token locally. If your active `gh` account can't see the repo, setup fails with a clear message; switch with `gh auth switch` and retry.
|
|
@@ -42,9 +42,13 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
|
|
|
42
42
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
43
43
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
44
44
|
| `ghosttrap watch` | Stream all errors continuously |
|
|
45
|
+
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
46
|
+
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
45
47
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
46
48
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
47
49
|
|
|
50
|
+
`peek`, `watch`, `last`, and `clear` accept `--repo owner/name` to target a specific claimed repo when you're not inside its working tree (e.g. `ghosttrap peek --repo alex-rowley/ghosttrap-cli`). Otherwise they detect the repo from cwd. `nuke` is intentionally cwd-locked.
|
|
51
|
+
|
|
48
52
|
## How it works
|
|
49
53
|
|
|
50
54
|
- **Setup** authenticates with GitHub (via the active `gh` account) to prove you have access to the repo, then saves a repo token locally. If your active `gh` account can't see the repo, setup fails with a clear message; switch with `gh auth switch` and retry.
|
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
10
|
import time
|
|
11
|
+
import urllib.error
|
|
11
12
|
import urllib.request
|
|
12
13
|
|
|
13
14
|
import websockets
|
|
@@ -22,9 +23,10 @@ KNOWN_SKILL_HASHES = {
|
|
|
22
23
|
"19b67d913dc5214ee4db3610bd8749da67324c174b904b5da71ee6de13e23e63", # v0.3.11
|
|
23
24
|
"bf7768c3de266b7018d5c722c6c9991b487e7897786b3a406c460842cdcde8b5", # v0.3.12
|
|
24
25
|
"d3f594c4c3601a4594c18ebc5e16dfd4abad4ab97d56285ecc20634b919b6731", # v0.3.14
|
|
26
|
+
"1c3a0507fab027abc0f176ce2fd88cad28276bef4fcef46cdb497f53967346e1", # v0.3.16
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
__version__ = "0.3.
|
|
29
|
+
__version__ = "0.3.17"
|
|
28
30
|
|
|
29
31
|
GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
|
|
30
32
|
CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
|
|
@@ -83,6 +85,8 @@ Read `~/.ghosttrap/config.json` for state. It contains:
|
|
|
83
85
|
## Other commands
|
|
84
86
|
|
|
85
87
|
- `ghosttrap last` — fetch the single most recent error and exit immediately, no waiting. Useful when the user wants to look at the latest error without starting a watch. Add `--clear` to also skip everything older in one shot.
|
|
88
|
+
- `ghosttrap list [n]` — print a numbered summary of the most recent `n` errors (default 10, max 50). Does not move the cursor. Caches the ordered ids in config so a follow-up `ghosttrap show <i>` returns full details for that row.
|
|
89
|
+
- `ghosttrap show <i>` — full details for the i-th row from the most recent `ghosttrap list`. Does not move the cursor.
|
|
86
90
|
- `ghosttrap clear` — manually skip outstanding errors without waiting. Useful if the user explicitly wants to drop the queue.
|
|
87
91
|
- `ghosttrap nuke` — permanently delete every server-side row for the current repo (errors + the Repo row + its token). Requires the user to type the repo name `owner/name` to confirm. Only run if the user explicitly asks to wipe server data — never proactively. After it succeeds the token is dead; the user would need to `ghosttrap setup` again to use this repo.
|
|
88
92
|
|
|
@@ -201,18 +205,33 @@ def get_gh_token():
|
|
|
201
205
|
sys.exit(1)
|
|
202
206
|
|
|
203
207
|
|
|
204
|
-
def
|
|
205
|
-
"""
|
|
208
|
+
def _get_repo_entry(config, requested=None):
|
|
209
|
+
"""Return (key, entry) for the chosen repo. Same resolution rules as _get_repo_token."""
|
|
206
210
|
repos = config.get("repos", {})
|
|
211
|
+
if not repos:
|
|
212
|
+
print("error: no repos configured. run 'ghosttrap setup' first.", file=sys.stderr)
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
if requested:
|
|
215
|
+
for k, entry in repos.items():
|
|
216
|
+
if f"{entry.get('owner')}/{entry.get('name')}" == requested:
|
|
217
|
+
return k, entry
|
|
218
|
+
available = sorted(f"{e['owner']}/{e['name']}" for e in repos.values())
|
|
219
|
+
print(f"error: '{requested}' is not in your config.", file=sys.stderr)
|
|
220
|
+
print(f"available: {', '.join(available)}", file=sys.stderr)
|
|
221
|
+
sys.exit(1)
|
|
207
222
|
cwd_repo = _detect_repo_from_cwd()
|
|
208
223
|
if cwd_repo:
|
|
209
|
-
for entry in repos.
|
|
224
|
+
for k, entry in repos.items():
|
|
210
225
|
if f"{entry.get('owner')}/{entry.get('name')}" == cwd_repo:
|
|
211
|
-
return entry
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
return k, entry
|
|
227
|
+
k = next(iter(repos))
|
|
228
|
+
return k, repos[k]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _get_repo_token(config, requested=None):
|
|
232
|
+
"""Get the repo token. If `requested` is 'owner/name', match strictly. Else cwd, else first."""
|
|
233
|
+
_, entry = _get_repo_entry(config, requested)
|
|
234
|
+
return entry["token"]
|
|
216
235
|
|
|
217
236
|
|
|
218
237
|
async def _connect_and_handle(server_url, token, config, once=False):
|
|
@@ -388,10 +407,10 @@ def _advance_cursor(config, token):
|
|
|
388
407
|
return pending
|
|
389
408
|
|
|
390
409
|
|
|
391
|
-
def clear():
|
|
410
|
+
def clear(requested=None):
|
|
392
411
|
_require_setup()
|
|
393
412
|
config = _load_config()
|
|
394
|
-
token = _get_repo_token(config)
|
|
413
|
+
token = _get_repo_token(config, requested)
|
|
395
414
|
try:
|
|
396
415
|
pending = _advance_cursor(config, token)
|
|
397
416
|
if pending:
|
|
@@ -456,11 +475,131 @@ def nuke():
|
|
|
456
475
|
_save_config(config)
|
|
457
476
|
|
|
458
477
|
|
|
459
|
-
def
|
|
478
|
+
def _rel_time(iso):
|
|
479
|
+
if not iso:
|
|
480
|
+
return "?"
|
|
481
|
+
try:
|
|
482
|
+
from datetime import datetime, timezone
|
|
483
|
+
dt = datetime.fromisoformat(iso)
|
|
484
|
+
if dt.tzinfo is None:
|
|
485
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
486
|
+
delta = datetime.now(timezone.utc) - dt
|
|
487
|
+
s = int(delta.total_seconds())
|
|
488
|
+
if s < 60:
|
|
489
|
+
return f"{s}s ago"
|
|
490
|
+
if s < 3600:
|
|
491
|
+
return f"{s // 60}m ago"
|
|
492
|
+
if s < 86400:
|
|
493
|
+
return f"{s // 3600}h ago"
|
|
494
|
+
if s < 86400 * 30:
|
|
495
|
+
return f"{s // 86400}d ago"
|
|
496
|
+
return dt.strftime("%Y-%m-%d")
|
|
497
|
+
except Exception:
|
|
498
|
+
return iso
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _print_error_details(error):
|
|
502
|
+
print(json.dumps({"type": "error", "error": error}))
|
|
503
|
+
sys.stdout.flush()
|
|
504
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
505
|
+
print(f" {error.get('repo', '?')}", file=sys.stderr)
|
|
506
|
+
print(f" {error.get('type', '?')}: {error.get('message', '')}", file=sys.stderr)
|
|
507
|
+
frames = error.get("frames", [])
|
|
508
|
+
if frames:
|
|
509
|
+
f = frames[-1]
|
|
510
|
+
print(f" at {f.get('file', '?')}:{f.get('line', '?')} in {f.get('function', '?')}", file=sys.stderr)
|
|
511
|
+
print(f"{'='*60}", file=sys.stderr)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def list_recent(n=10, requested=None):
|
|
460
515
|
_require_setup()
|
|
461
516
|
config = _load_config()
|
|
462
517
|
_check_cli_version(config)
|
|
463
|
-
|
|
518
|
+
n = max(1, min(int(n), 50))
|
|
519
|
+
key, entry = _get_repo_entry(config, requested)
|
|
520
|
+
token = entry["token"]
|
|
521
|
+
server = GHOSTTRAP_SERVER.replace("wss://", "https://").replace("/stream/", "")
|
|
522
|
+
url = f"{server}/list/{token}/?n={n}"
|
|
523
|
+
try:
|
|
524
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ghosttrap-cli"})
|
|
525
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
526
|
+
data = json.loads(resp.read())
|
|
527
|
+
except Exception as e:
|
|
528
|
+
print(f"error: {e}", file=sys.stderr)
|
|
529
|
+
sys.exit(1)
|
|
530
|
+
|
|
531
|
+
errors = data.get("errors", [])
|
|
532
|
+
if not errors:
|
|
533
|
+
print("no errors yet", file=sys.stderr)
|
|
534
|
+
entry["recent"] = []
|
|
535
|
+
_save_config(config)
|
|
536
|
+
return
|
|
537
|
+
|
|
538
|
+
entry["recent"] = [e["id"] for e in errors]
|
|
539
|
+
_save_config(config)
|
|
540
|
+
|
|
541
|
+
width = len(str(len(errors)))
|
|
542
|
+
for i, e in enumerate(errors, 1):
|
|
543
|
+
when = _rel_time(e.get("created_at"))
|
|
544
|
+
etype = e.get("type") or "?"
|
|
545
|
+
msg = (e.get("message") or "").splitlines()[0] if e.get("message") else ""
|
|
546
|
+
if len(msg) > 60:
|
|
547
|
+
msg = msg[:57] + "..."
|
|
548
|
+
loc = ""
|
|
549
|
+
if e.get("file"):
|
|
550
|
+
loc = f"{e['file']}:{e.get('line', '?')}"
|
|
551
|
+
if e.get("function"):
|
|
552
|
+
loc += f" ({e['function']})"
|
|
553
|
+
print(f" {i:>{width}} {when:<12} {etype:<20} {msg:<60} {loc}")
|
|
554
|
+
print(f"\nrun 'ghosttrap show <n>' to see full details. cursor unchanged.", file=sys.stderr)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def show(index, requested=None):
|
|
558
|
+
_require_setup()
|
|
559
|
+
config = _load_config()
|
|
560
|
+
_check_cli_version(config)
|
|
561
|
+
key, entry = _get_repo_entry(config, requested)
|
|
562
|
+
recent = entry.get("recent") or []
|
|
563
|
+
if not recent:
|
|
564
|
+
print("error: no recent list cached. run 'ghosttrap list' first.", file=sys.stderr)
|
|
565
|
+
sys.exit(1)
|
|
566
|
+
try:
|
|
567
|
+
i = int(index)
|
|
568
|
+
except (TypeError, ValueError):
|
|
569
|
+
print(f"error: '{index}' is not a number.", file=sys.stderr)
|
|
570
|
+
sys.exit(1)
|
|
571
|
+
if i < 1 or i > len(recent):
|
|
572
|
+
print(f"error: index out of range. last list had {len(recent)} entries.", file=sys.stderr)
|
|
573
|
+
sys.exit(1)
|
|
574
|
+
db_id = recent[i - 1]
|
|
575
|
+
token = entry["token"]
|
|
576
|
+
server = GHOSTTRAP_SERVER.replace("wss://", "https://").replace("/stream/", "")
|
|
577
|
+
url = f"{server}/error/{token}/{db_id}/"
|
|
578
|
+
try:
|
|
579
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ghosttrap-cli"})
|
|
580
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
581
|
+
data = json.loads(resp.read())
|
|
582
|
+
except urllib.error.HTTPError as e:
|
|
583
|
+
if e.code == 404:
|
|
584
|
+
print(f"error: this error no longer exists on the server (id #{db_id}).", file=sys.stderr)
|
|
585
|
+
else:
|
|
586
|
+
print(f"error: {e}", file=sys.stderr)
|
|
587
|
+
sys.exit(1)
|
|
588
|
+
except Exception as e:
|
|
589
|
+
print(f"error: {e}", file=sys.stderr)
|
|
590
|
+
sys.exit(1)
|
|
591
|
+
error = data.get("error")
|
|
592
|
+
if not error:
|
|
593
|
+
print(f"error: empty response", file=sys.stderr)
|
|
594
|
+
sys.exit(1)
|
|
595
|
+
_print_error_details(error)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def last(do_clear=False, requested=None):
|
|
599
|
+
_require_setup()
|
|
600
|
+
config = _load_config()
|
|
601
|
+
_check_cli_version(config)
|
|
602
|
+
token = _get_repo_token(config, requested)
|
|
464
603
|
server = GHOSTTRAP_SERVER.replace("wss://", "https://").replace("/stream/", "")
|
|
465
604
|
url = f"{server}/last/{token}/"
|
|
466
605
|
try:
|
|
@@ -476,17 +615,7 @@ def last(do_clear=False):
|
|
|
476
615
|
print("no errors yet", file=sys.stderr)
|
|
477
616
|
return
|
|
478
617
|
|
|
479
|
-
|
|
480
|
-
sys.stdout.flush()
|
|
481
|
-
|
|
482
|
-
print(f"\n{'='*60}", file=sys.stderr)
|
|
483
|
-
print(f" {error.get('repo', '?')}", file=sys.stderr)
|
|
484
|
-
print(f" {error.get('type', '?')}: {error.get('message', '')}", file=sys.stderr)
|
|
485
|
-
frames = error.get("frames", [])
|
|
486
|
-
if frames:
|
|
487
|
-
f = frames[-1]
|
|
488
|
-
print(f" at {f.get('file', '?')}:{f.get('line', '?')} in {f.get('function', '?')}", file=sys.stderr)
|
|
489
|
-
print(f"{'='*60}", file=sys.stderr)
|
|
618
|
+
_print_error_details(error)
|
|
490
619
|
|
|
491
620
|
if do_clear:
|
|
492
621
|
try:
|
|
@@ -501,17 +630,30 @@ def main():
|
|
|
501
630
|
sub = parser.add_subparsers(dest="command")
|
|
502
631
|
|
|
503
632
|
sub.add_parser("setup", help="Claim repos and install Claude Code skill")
|
|
504
|
-
|
|
633
|
+
|
|
634
|
+
clear_parser = sub.add_parser("clear", help="Skip all outstanding errors")
|
|
635
|
+
clear_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
505
636
|
|
|
506
637
|
watch_parser = sub.add_parser("watch", help="Stream errors in real time")
|
|
507
638
|
watch_parser.add_argument("--server", default=GHOSTTRAP_SERVER, help="WebSocket server URL")
|
|
639
|
+
watch_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
508
640
|
|
|
509
641
|
peek_parser = sub.add_parser("peek", help="Wait for the next error then exit")
|
|
510
642
|
peek_parser.add_argument("--server", default=GHOSTTRAP_SERVER, help="WebSocket server URL")
|
|
511
643
|
peek_parser.add_argument("--clear", action="store_true", help="Skip outstanding errors before waiting")
|
|
644
|
+
peek_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
512
645
|
|
|
513
646
|
last_parser = sub.add_parser("last", help="Fetch the most recent error then exit")
|
|
514
647
|
last_parser.add_argument("--clear", action="store_true", help="Also skip remaining outstanding errors")
|
|
648
|
+
last_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
649
|
+
|
|
650
|
+
list_parser = sub.add_parser("list", help="List the most recent N errors (summary only, cursor unchanged)")
|
|
651
|
+
list_parser.add_argument("n", nargs="?", type=int, default=10, help="How many to list (default 10, max 50)")
|
|
652
|
+
list_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
653
|
+
|
|
654
|
+
show_parser = sub.add_parser("show", help="Show full details for an index from the last 'list' (cursor unchanged)")
|
|
655
|
+
show_parser.add_argument("index", type=int, help="1-based index from the last 'ghosttrap list'")
|
|
656
|
+
show_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
515
657
|
|
|
516
658
|
sub.add_parser("nuke", help="Permanently delete all server data for the current repo")
|
|
517
659
|
|
|
@@ -521,18 +663,18 @@ def main():
|
|
|
521
663
|
token = get_gh_token()
|
|
522
664
|
asyncio.run(setup(GHOSTTRAP_SERVER, token))
|
|
523
665
|
elif args.command == "clear":
|
|
524
|
-
clear()
|
|
666
|
+
clear(requested=args.repo)
|
|
525
667
|
elif args.command == "watch":
|
|
526
668
|
_require_setup()
|
|
527
669
|
_refresh_skill_if_stale()
|
|
528
670
|
config = _load_config()
|
|
529
|
-
token = _get_repo_token(config)
|
|
671
|
+
token = _get_repo_token(config, args.repo)
|
|
530
672
|
asyncio.run(watch(args.server, token))
|
|
531
673
|
elif args.command == "peek":
|
|
532
674
|
_require_setup()
|
|
533
675
|
_refresh_skill_if_stale()
|
|
534
676
|
config = _load_config()
|
|
535
|
-
token = _get_repo_token(config)
|
|
677
|
+
token = _get_repo_token(config, args.repo)
|
|
536
678
|
if args.clear:
|
|
537
679
|
try:
|
|
538
680
|
_advance_cursor(config, token)
|
|
@@ -542,7 +684,13 @@ def main():
|
|
|
542
684
|
asyncio.run(peek(args.server, token))
|
|
543
685
|
elif args.command == "last":
|
|
544
686
|
_refresh_skill_if_stale()
|
|
545
|
-
last(do_clear=args.clear)
|
|
687
|
+
last(do_clear=args.clear, requested=args.repo)
|
|
688
|
+
elif args.command == "list":
|
|
689
|
+
_refresh_skill_if_stale()
|
|
690
|
+
list_recent(n=args.n, requested=args.repo)
|
|
691
|
+
elif args.command == "show":
|
|
692
|
+
_refresh_skill_if_stale()
|
|
693
|
+
show(args.index, requested=args.repo)
|
|
546
694
|
elif args.command == "nuke":
|
|
547
695
|
nuke()
|
|
548
696
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghosttrap-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: Watch for errors streaming from ghosttrap.io
|
|
5
5
|
Project-URL: Homepage, https://github.com/alex-rowley/ghosttrap-cli
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -51,9 +51,13 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
|
|
|
51
51
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
52
52
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
53
53
|
| `ghosttrap watch` | Stream all errors continuously |
|
|
54
|
+
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
55
|
+
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
54
56
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
55
57
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
56
58
|
|
|
59
|
+
`peek`, `watch`, `last`, and `clear` accept `--repo owner/name` to target a specific claimed repo when you're not inside its working tree (e.g. `ghosttrap peek --repo alex-rowley/ghosttrap-cli`). Otherwise they detect the repo from cwd. `nuke` is intentionally cwd-locked.
|
|
60
|
+
|
|
57
61
|
## How it works
|
|
58
62
|
|
|
59
63
|
- **Setup** authenticates with GitHub (via the active `gh` account) to prove you have access to the repo, then saves a repo token locally. If your active `gh` account can't see the repo, setup fails with a clear message; switch with `gh auth switch` and retry.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|