ghosttrap-cli 0.3.20__tar.gz → 0.3.22__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.20 → ghosttrap_cli-0.3.22}/PKG-INFO +5 -4
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/README.md +4 -3
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli/cli.py +61 -7
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/PKG-INFO +5 -4
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/pyproject.toml +1 -1
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli/__init__.py +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/SOURCES.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/dependency_links.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/entry_points.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/requires.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/ghosttrap_cli.egg-info/top_level.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.22}/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.22
|
|
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
|
|
@@ -52,18 +52,19 @@ If you want to manually flag a caught exception or a non-exception condition, ca
|
|
|
52
52
|
| `ghosttrap peek --clear` | Skip outstanding errors, then wait for the next one |
|
|
53
53
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
54
54
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
55
|
-
| `ghosttrap watch` |
|
|
55
|
+
| `ghosttrap watch` | Deprecated — `peek` reconnects until an error arrives, which covers the streaming case |
|
|
56
56
|
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
57
57
|
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
58
58
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
59
59
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Every command except `setup` and `nuke` accepts `--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 the repo is detected from cwd. `nuke` is intentionally cwd-locked.
|
|
62
62
|
|
|
63
63
|
## How it works
|
|
64
64
|
|
|
65
65
|
- **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.
|
|
66
|
-
- **Peek**
|
|
66
|
+
- **Peek** connects to ghosttrap.io using that token — no GitHub auth needed after setup
|
|
67
|
+
- If the connection drops or the server closes an idle socket, peek reconnects with a 60-second backoff for as long as it runs — it only exits once it has delivered an error (or hit a real failure, which it reports on stderr)
|
|
67
68
|
- Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
|
|
68
69
|
- Repos are tracked by GitHub's immutable repo id, so a rename or transfer doesn't require any action — the next connect picks up the new `owner/name` and your token keeps working
|
|
69
70
|
- Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
|
|
@@ -43,18 +43,19 @@ If you want to manually flag a caught exception or a non-exception condition, ca
|
|
|
43
43
|
| `ghosttrap peek --clear` | Skip outstanding errors, then wait for the next one |
|
|
44
44
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
45
45
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
46
|
-
| `ghosttrap watch` |
|
|
46
|
+
| `ghosttrap watch` | Deprecated — `peek` reconnects until an error arrives, which covers the streaming case |
|
|
47
47
|
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
48
48
|
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
49
49
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
50
50
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Every command except `setup` and `nuke` accepts `--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 the repo is detected from cwd. `nuke` is intentionally cwd-locked.
|
|
53
53
|
|
|
54
54
|
## How it works
|
|
55
55
|
|
|
56
56
|
- **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.
|
|
57
|
-
- **Peek**
|
|
57
|
+
- **Peek** connects to ghosttrap.io using that token — no GitHub auth needed after setup
|
|
58
|
+
- If the connection drops or the server closes an idle socket, peek reconnects with a 60-second backoff for as long as it runs — it only exits once it has delivered an error (or hit a real failure, which it reports on stderr)
|
|
58
59
|
- Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
|
|
59
60
|
- Repos are tracked by GitHub's immutable repo id, so a rename or transfer doesn't require any action — the next connect picks up the new `owner/name` and your token keeps working
|
|
60
61
|
- Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
|
|
@@ -4,6 +4,7 @@ import argparse
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import signal
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
9
10
|
import tempfile
|
|
@@ -13,7 +14,20 @@ import urllib.request
|
|
|
13
14
|
|
|
14
15
|
import websockets
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
def _harden_signals():
|
|
19
|
+
"""Explicitly ignore SIGURG so no process supervisor can nudge peek out
|
|
20
|
+
with an out-of-band signal. POSIX default is already ignore; this makes
|
|
21
|
+
it defensive against layers that change the disposition.
|
|
22
|
+
"""
|
|
23
|
+
s = getattr(signal, "SIGURG", None)
|
|
24
|
+
if s is not None:
|
|
25
|
+
try:
|
|
26
|
+
signal.signal(s, signal.SIG_IGN)
|
|
27
|
+
except (OSError, ValueError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
__version__ = "0.3.22"
|
|
17
31
|
|
|
18
32
|
GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
|
|
19
33
|
CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
|
|
@@ -53,8 +67,9 @@ description: Production error monitoring via ghosttrap.io. Trigger when starting
|
|
|
53
67
|
# Ghosttrap
|
|
54
68
|
|
|
55
69
|
Read `~/.ghosttrap/config.json` for state. It contains:
|
|
56
|
-
- `repos`: map keyed by GitHub repo id (stringified int) to `{"github_id": int, "owner": str, "name": str, "token": "t_xxx", "sdk_installed": bool, "sdk_version": str, "init_file": str}`.
|
|
70
|
+
- `repos`: map keyed by GitHub repo id (stringified int) to `{"github_id": int, "owner": str, "name": str, "token": "t_xxx", "sdk_installed": bool, "sdk_version": str, "init_file": str}`. Entries may also carry `recent`, the error ids cached by the last `ghosttrap list` (managed by the CLI — leave it alone).
|
|
57
71
|
- `cursor`: last seen error ID
|
|
72
|
+
- `skill_baseline`: the previous release's skill text, used to 3-way-merge skill updates with local edits. Never edit or delete it.
|
|
58
73
|
|
|
59
74
|
## On session start
|
|
60
75
|
|
|
@@ -75,17 +90,19 @@ For caught exceptions or non-exception conditions the user explicitly wants repo
|
|
|
75
90
|
|
|
76
91
|
## Other commands
|
|
77
92
|
|
|
78
|
-
- `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
|
|
93
|
+
- `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 blocking on a peek. Add `--clear` to also skip everything older in one shot.
|
|
79
94
|
- `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.
|
|
80
95
|
- `ghosttrap show <i>` — full details for the i-th row from the most recent `ghosttrap list`. Does not move the cursor.
|
|
81
96
|
- `ghosttrap clear` — manually skip outstanding errors without waiting. Useful if the user explicitly wants to drop the queue.
|
|
82
97
|
- `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.
|
|
83
98
|
|
|
99
|
+
`peek` and every command above except `nuke` accept `--repo owner/name` to target another claimed repo when the cwd isn't inside it (e.g. `ghosttrap list --repo owner/name`).
|
|
100
|
+
|
|
84
101
|
## Rules
|
|
85
102
|
|
|
86
103
|
- Always `run_in_background: true` for peek — it blocks.
|
|
87
104
|
- Don't run multiple peeks at once.
|
|
88
|
-
-
|
|
105
|
+
- Peek reconnects by itself (60s backoff) when the connection drops — a quiet peek is waiting, not hung. It only exits after printing an error event, or with a message on stderr if something is actually wrong; restart it only in that second case.
|
|
89
106
|
- After installing/updating the SDK, write the state back to config.json.
|
|
90
107
|
"""
|
|
91
108
|
|
|
@@ -416,6 +433,31 @@ async def setup(server_url, token):
|
|
|
416
433
|
sys.exit(1)
|
|
417
434
|
|
|
418
435
|
|
|
436
|
+
# Exception types we knowingly retry on. All represent a transient network/transport
|
|
437
|
+
# blip rather than a semantic error from the server:
|
|
438
|
+
# - ConnectionClosed: peer closed the WebSocket (either side of the handshake)
|
|
439
|
+
# - InvalidStatus: non-101 HTTP response during upgrade (e.g. 502 from a proxy)
|
|
440
|
+
# - ConnectionError: builtin — refused, reset, unreachable
|
|
441
|
+
# - OSError: DNS failure, transient socket errors (gaierror is a subclass)
|
|
442
|
+
# Anything else escapes and prints a diagnostic line first so we can add it here
|
|
443
|
+
# in the next release. Semantic rejections from the server ({"type": "rejected"})
|
|
444
|
+
# raise SystemExit, which we deliberately do NOT catch — those are real errors.
|
|
445
|
+
_RETRYABLE = (
|
|
446
|
+
websockets.ConnectionClosed,
|
|
447
|
+
websockets.InvalidStatus,
|
|
448
|
+
ConnectionError,
|
|
449
|
+
OSError,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _log_unexpected(e):
|
|
454
|
+
print(
|
|
455
|
+
f"unexpected {type(e).__module__}.{type(e).__name__}: {e} — "
|
|
456
|
+
f"not currently in the retry list; please report so we can add it.",
|
|
457
|
+
file=sys.stderr,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
|
|
419
461
|
async def watch(server_url, token):
|
|
420
462
|
config = _load_config()
|
|
421
463
|
print(f"connecting to {server_url}...", file=sys.stderr)
|
|
@@ -424,8 +466,11 @@ async def watch(server_url, token):
|
|
|
424
466
|
try:
|
|
425
467
|
await _connect_and_handle(server_url, token, config, once=False)
|
|
426
468
|
print("connection closed by server, reconnecting...", file=sys.stderr)
|
|
427
|
-
except
|
|
469
|
+
except _RETRYABLE:
|
|
428
470
|
print("connection lost, reconnecting...", file=sys.stderr)
|
|
471
|
+
except Exception as e:
|
|
472
|
+
_log_unexpected(e)
|
|
473
|
+
raise
|
|
429
474
|
await asyncio.sleep(60)
|
|
430
475
|
|
|
431
476
|
|
|
@@ -438,8 +483,11 @@ async def peek(server_url, token):
|
|
|
438
483
|
if got_error:
|
|
439
484
|
return
|
|
440
485
|
print("connection closed by server, reconnecting...", file=sys.stderr)
|
|
441
|
-
except
|
|
486
|
+
except _RETRYABLE:
|
|
442
487
|
print("connection lost, reconnecting...", file=sys.stderr)
|
|
488
|
+
except Exception as e:
|
|
489
|
+
_log_unexpected(e)
|
|
490
|
+
raise
|
|
443
491
|
await asyncio.sleep(60)
|
|
444
492
|
|
|
445
493
|
|
|
@@ -676,6 +724,7 @@ def last(do_clear=False, requested=None):
|
|
|
676
724
|
|
|
677
725
|
|
|
678
726
|
def main():
|
|
727
|
+
_harden_signals()
|
|
679
728
|
parser = argparse.ArgumentParser(prog="ghosttrap", description="Watch for errors from ghosttrap.io")
|
|
680
729
|
sub = parser.add_subparsers(dest="command")
|
|
681
730
|
|
|
@@ -684,7 +733,7 @@ def main():
|
|
|
684
733
|
clear_parser = sub.add_parser("clear", help="Skip all outstanding errors")
|
|
685
734
|
clear_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
686
735
|
|
|
687
|
-
watch_parser = sub.add_parser("watch", help="
|
|
736
|
+
watch_parser = sub.add_parser("watch", help="Deprecated: stream errors in real time (use peek)")
|
|
688
737
|
watch_parser.add_argument("--server", default=GHOSTTRAP_SERVER, help="WebSocket server URL")
|
|
689
738
|
watch_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
|
|
690
739
|
|
|
@@ -715,6 +764,11 @@ def main():
|
|
|
715
764
|
elif args.command == "clear":
|
|
716
765
|
clear(requested=args.repo)
|
|
717
766
|
elif args.command == "watch":
|
|
767
|
+
print(
|
|
768
|
+
"warning: 'watch' is deprecated and may be removed in a future release — "
|
|
769
|
+
"'peek' now reconnects until an error arrives.",
|
|
770
|
+
file=sys.stderr,
|
|
771
|
+
)
|
|
718
772
|
_require_setup()
|
|
719
773
|
_refresh_skill_if_stale()
|
|
720
774
|
config = _load_config()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghosttrap-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.22
|
|
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
|
|
@@ -52,18 +52,19 @@ If you want to manually flag a caught exception or a non-exception condition, ca
|
|
|
52
52
|
| `ghosttrap peek --clear` | Skip outstanding errors, then wait for the next one |
|
|
53
53
|
| `ghosttrap last` | Fetch the most recent error and exit (no waiting) |
|
|
54
54
|
| `ghosttrap last --clear` | Fetch the most recent error and skip everything older |
|
|
55
|
-
| `ghosttrap watch` |
|
|
55
|
+
| `ghosttrap watch` | Deprecated — `peek` reconnects until an error arrives, which covers the streaming case |
|
|
56
56
|
| `ghosttrap list [n]` | Print a numbered summary of the most recent `n` errors (default 10, max 50). Doesn't move the cursor. |
|
|
57
57
|
| `ghosttrap show <i>` | Full details for row `i` from the last `list` (1-based). Doesn't move the cursor. |
|
|
58
58
|
| `ghosttrap clear` | Skip all outstanding errors |
|
|
59
59
|
| `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Every command except `setup` and `nuke` accepts `--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 the repo is detected from cwd. `nuke` is intentionally cwd-locked.
|
|
62
62
|
|
|
63
63
|
## How it works
|
|
64
64
|
|
|
65
65
|
- **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.
|
|
66
|
-
- **Peek**
|
|
66
|
+
- **Peek** connects to ghosttrap.io using that token — no GitHub auth needed after setup
|
|
67
|
+
- If the connection drops or the server closes an idle socket, peek reconnects with a 60-second backoff for as long as it runs — it only exits once it has delivered an error (or hit a real failure, which it reports on stderr)
|
|
67
68
|
- Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
|
|
68
69
|
- Repos are tracked by GitHub's immutable repo id, so a rename or transfer doesn't require any action — the next connect picks up the new `owner/name` and your token keeps working
|
|
69
70
|
- Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|