ghosttrap-cli 0.3.17__tar.gz → 0.3.19__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.17 → ghosttrap_cli-0.3.19}/PKG-INFO +3 -1
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/README.md +2 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli/cli.py +66 -23
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/PKG-INFO +3 -1
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/pyproject.toml +1 -1
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli/__init__.py +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/SOURCES.txt +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/dependency_links.txt +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/entry_points.txt +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/requires.txt +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/ghosttrap_cli.egg-info/top_level.txt +0 -0
- {ghosttrap_cli-0.3.17 → ghosttrap_cli-0.3.19}/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.19
|
|
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
|
|
@@ -41,6 +41,8 @@ After fixing, Claude restarts peek and waits for the next one. Errors become a r
|
|
|
41
41
|
|
|
42
42
|
Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to report errors. The Claude Code skill handles the integration automatically — it installs the SDK, wires it into your app, and adds Django/Celery hooks if applicable. You shouldn't need to touch the SDK manually.
|
|
43
43
|
|
|
44
|
+
If you want to manually flag a caught exception or a non-exception condition, call `ghosttrap.trap(exc_or_message)` from your app code — see the [SDK README](https://github.com/alex-rowley/ghosttrap-sdk#manually-trap-an-event).
|
|
45
|
+
|
|
44
46
|
## Commands
|
|
45
47
|
|
|
46
48
|
| Command | What it does |
|
|
@@ -32,6 +32,8 @@ After fixing, Claude restarts peek and waits for the next one. Errors become a r
|
|
|
32
32
|
|
|
33
33
|
Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to report errors. The Claude Code skill handles the integration automatically — it installs the SDK, wires it into your app, and adds Django/Celery hooks if applicable. You shouldn't need to touch the SDK manually.
|
|
34
34
|
|
|
35
|
+
If you want to manually flag a caught exception or a non-exception condition, call `ghosttrap.trap(exc_or_message)` from your app code — see the [SDK README](https://github.com/alex-rowley/ghosttrap-sdk#manually-trap-an-event).
|
|
36
|
+
|
|
35
37
|
## Commands
|
|
36
38
|
|
|
37
39
|
| Command | What it does |
|
|
@@ -2,31 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
-
import hashlib
|
|
6
5
|
import json
|
|
7
6
|
import os
|
|
8
7
|
import subprocess
|
|
9
8
|
import sys
|
|
9
|
+
import tempfile
|
|
10
10
|
import time
|
|
11
11
|
import urllib.error
|
|
12
12
|
import urllib.request
|
|
13
13
|
|
|
14
14
|
import websockets
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
"aeda67bc5971bd8af4d7ebe819ebcce5acead562fa618227a1798b4b5ae7143e", # v0.2.0
|
|
18
|
-
"0f2d2f4105e393fc69084d404d5a8154ba5d97fd23f92810c51345e3dc68e9a0", # v0.3.0
|
|
19
|
-
"8564b65b8ab5c63283cda1706e30ca62bc4e111d33ba8918220f4b556ad01da1", # v0.3.1..v0.3.3
|
|
20
|
-
"5759b2e0dc8ca47c3801915fd688cc8da878a7ab8d405f5183ffd7e8c8df4c55", # v0.3.4..v0.3.7
|
|
21
|
-
"0651bb4247cf5c68960ff5b63d6a5d0c85ff1ce08e7966ab4823601ff02cf1f4", # v0.3.9
|
|
22
|
-
"38810f43867a2a91420cc3dacbc71d2acabd7125596fd5b43f222b49725c9696", # v0.3.10
|
|
23
|
-
"19b67d913dc5214ee4db3610bd8749da67324c174b904b5da71ee6de13e23e63", # v0.3.11
|
|
24
|
-
"bf7768c3de266b7018d5c722c6c9991b487e7897786b3a406c460842cdcde8b5", # v0.3.12
|
|
25
|
-
"d3f594c4c3601a4594c18ebc5e16dfd4abad4ab97d56285ecc20634b919b6731", # v0.3.14
|
|
26
|
-
"1c3a0507fab027abc0f176ce2fd88cad28276bef4fcef46cdb497f53967346e1", # v0.3.16
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
__version__ = "0.3.17"
|
|
16
|
+
__version__ = "0.3.19"
|
|
30
17
|
|
|
31
18
|
GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
|
|
32
19
|
CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
|
|
@@ -76,6 +63,10 @@ Read `~/.ghosttrap/config.json` for state. It contains:
|
|
|
76
63
|
3. If `sdk_installed` is false or missing: install the SDK (`pip install ghosttrap-sdk`), wire `ghosttrap.init("<token>")` into the app startup. For Django projects, also add `"ghosttrap.django.GhostTrapApp"` to INSTALLED_APPS (re-attaches logging handler after Django's dictConfig) and `"ghosttrap.django.GhostTrapMiddleware"` to MIDDLEWARE (catches unhandled view exceptions). The SDK auto-hooks into Celery task_failure if Celery is installed, and attaches a logging handler for logger.exception() calls. Use whatever pattern the project already uses for configuration (env vars, settings files, hardcoded — match the existing style). Then update the config: set `sdk_installed: true`, `sdk_version`, `init_file` to record what you did. Only pass `send_user=True` to `init()` if the user explicitly asks for user context in reports — it's PII and stays off by default.
|
|
77
64
|
4. Run `ghosttrap peek --clear` with `run_in_background: true`. The `--clear` flag skips any stale backlog from prior sessions so you only get fresh errors.
|
|
78
65
|
|
|
66
|
+
## Manual capture
|
|
67
|
+
|
|
68
|
+
For caught exceptions or non-exception conditions the user explicitly wants reported, use `ghosttrap.trap(exc_or_message)` from app code — pass an exception instance or a string. Synthetic string events arrive as type `TrappedEvent` with the caller's stack. Only wire this in when the user asks for it; don't add `trap()` calls speculatively.
|
|
69
|
+
|
|
79
70
|
## When peek returns
|
|
80
71
|
|
|
81
72
|
1. **Immediately restart peek** in the background before doing anything else — this ensures you're listening for the next error while you work on the current one. Use plain `ghosttrap peek` here (no `--clear`) — you only want to skip backlog at session start.
|
|
@@ -215,9 +206,14 @@ def _get_repo_entry(config, requested=None):
|
|
|
215
206
|
for k, entry in repos.items():
|
|
216
207
|
if f"{entry.get('owner')}/{entry.get('name')}" == requested:
|
|
217
208
|
return k, entry
|
|
218
|
-
available = sorted(
|
|
209
|
+
available = sorted(
|
|
210
|
+
f"{e.get('owner')}/{e.get('name')}"
|
|
211
|
+
for e in repos.values()
|
|
212
|
+
if e.get('owner') and e.get('name')
|
|
213
|
+
)
|
|
219
214
|
print(f"error: '{requested}' is not in your config.", file=sys.stderr)
|
|
220
|
-
|
|
215
|
+
if available:
|
|
216
|
+
print(f"available: {', '.join(available)}", file=sys.stderr)
|
|
221
217
|
sys.exit(1)
|
|
222
218
|
cwd_repo = _detect_repo_from_cwd()
|
|
223
219
|
if cwd_repo:
|
|
@@ -306,22 +302,69 @@ def _require_setup():
|
|
|
306
302
|
sys.exit(1)
|
|
307
303
|
|
|
308
304
|
|
|
309
|
-
def _write_skill():
|
|
305
|
+
def _write_skill(config=None):
|
|
310
306
|
os.makedirs(SKILL_DIR, exist_ok=True)
|
|
311
307
|
with open(SKILL_FILE, "w") as f:
|
|
312
308
|
f.write(SKILL_CONTENT)
|
|
309
|
+
if config is None:
|
|
310
|
+
config = _load_config()
|
|
311
|
+
config["skill_baseline"] = SKILL_CONTENT
|
|
312
|
+
_save_config(config)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _merge_skill(base, local, remote):
|
|
316
|
+
"""3-way merge via `git merge-file -p`. Returns (merged_text, clean)."""
|
|
317
|
+
with tempfile.TemporaryDirectory() as d:
|
|
318
|
+
bp = os.path.join(d, "base")
|
|
319
|
+
lp = os.path.join(d, "local")
|
|
320
|
+
rp = os.path.join(d, "remote")
|
|
321
|
+
for path, text in [(bp, base), (lp, local), (rp, remote)]:
|
|
322
|
+
with open(path, "w") as f:
|
|
323
|
+
f.write(text)
|
|
324
|
+
result = subprocess.run(
|
|
325
|
+
["git", "merge-file", "-p",
|
|
326
|
+
"-L", "your edits", "-L", "previous release", "-L", "new release",
|
|
327
|
+
lp, bp, rp],
|
|
328
|
+
capture_output=True, text=True,
|
|
329
|
+
)
|
|
330
|
+
return result.stdout, result.returncode == 0
|
|
313
331
|
|
|
314
332
|
|
|
315
333
|
def _refresh_skill_if_stale():
|
|
316
334
|
if not os.path.exists(SKILL_FILE):
|
|
317
335
|
return
|
|
318
336
|
with open(SKILL_FILE) as f:
|
|
319
|
-
|
|
320
|
-
if
|
|
337
|
+
on_disk = f.read()
|
|
338
|
+
if on_disk == SKILL_CONTENT:
|
|
339
|
+
return
|
|
340
|
+
config = _load_config()
|
|
341
|
+
baseline = config.get("skill_baseline")
|
|
342
|
+
if baseline is None:
|
|
343
|
+
# Pre-baseline install: adopt current on-disk content as the baseline
|
|
344
|
+
# so future releases can 3-way-merge instead of clobbering local edits.
|
|
345
|
+
config["skill_baseline"] = on_disk
|
|
346
|
+
_save_config(config)
|
|
321
347
|
return
|
|
322
|
-
if
|
|
323
|
-
_write_skill()
|
|
348
|
+
if baseline == on_disk:
|
|
349
|
+
_write_skill(config)
|
|
324
350
|
print("ghosttrap skill file updated", file=sys.stderr)
|
|
351
|
+
return
|
|
352
|
+
merged, clean = _merge_skill(baseline, on_disk, SKILL_CONTENT)
|
|
353
|
+
if clean:
|
|
354
|
+
with open(SKILL_FILE, "w") as f:
|
|
355
|
+
f.write(merged)
|
|
356
|
+
config["skill_baseline"] = SKILL_CONTENT
|
|
357
|
+
_save_config(config)
|
|
358
|
+
print("ghosttrap skill file updated (merged with your local edits)", file=sys.stderr)
|
|
359
|
+
return
|
|
360
|
+
new_path = SKILL_FILE + ".new"
|
|
361
|
+
with open(new_path, "w") as f:
|
|
362
|
+
f.write(merged)
|
|
363
|
+
print(
|
|
364
|
+
f"ghosttrap skill update has conflicts with your local edits; "
|
|
365
|
+
f"merged candidate at {new_path} — resolve, copy to {SKILL_FILE}, and rerun.",
|
|
366
|
+
file=sys.stderr,
|
|
367
|
+
)
|
|
325
368
|
|
|
326
369
|
|
|
327
370
|
async def setup(server_url, token):
|
|
@@ -350,7 +393,7 @@ async def setup(server_url, token):
|
|
|
350
393
|
|
|
351
394
|
repos = event.get("repos", [])
|
|
352
395
|
_save_repos(config, repos)
|
|
353
|
-
_write_skill()
|
|
396
|
+
_write_skill(config)
|
|
354
397
|
|
|
355
398
|
target = repos[0] if repos else None
|
|
356
399
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghosttrap-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.19
|
|
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
|
|
@@ -41,6 +41,8 @@ After fixing, Claude restarts peek and waits for the next one. Errors become a r
|
|
|
41
41
|
|
|
42
42
|
Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to report errors. The Claude Code skill handles the integration automatically — it installs the SDK, wires it into your app, and adds Django/Celery hooks if applicable. You shouldn't need to touch the SDK manually.
|
|
43
43
|
|
|
44
|
+
If you want to manually flag a caught exception or a non-exception condition, call `ghosttrap.trap(exc_or_message)` from your app code — see the [SDK README](https://github.com/alex-rowley/ghosttrap-sdk#manually-trap-an-event).
|
|
45
|
+
|
|
44
46
|
## Commands
|
|
45
47
|
|
|
46
48
|
| Command | What it does |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|