ghosttrap-cli 0.3.14__tar.gz → 0.3.16__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghosttrap-cli
3
- Version: 0.3.14
3
+ Version: 0.3.16
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
@@ -54,12 +54,15 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
54
54
  | `ghosttrap clear` | Skip all outstanding errors |
55
55
  | `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
56
56
 
57
+ `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.
58
+
57
59
  ## How it works
58
60
 
59
- - **Setup** authenticates with GitHub to prove you own the repo, then saves a token locally
61
+ - **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.
60
62
  - **Peek** and **watch** connect to ghosttrap.io using that token — no GitHub auth needed after setup
61
63
  - Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
62
- - Local state is stored in `~/.ghosttrap/config.json`
64
+ - 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
65
+ - Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
63
66
 
64
67
  ## Requirements
65
68
 
@@ -70,4 +73,8 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
70
73
 
71
74
  ## Privacy
72
75
 
73
- Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo ownership; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
76
+ Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo access; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
77
+
78
+ User context (Django user id + username) is **never** sent unless you opt in with `ghosttrap.init(token, send_user=True)` in your app. Server hostname is captured automatically.
79
+
80
+ Run `ghosttrap nuke` from inside a repo to permanently delete every server-side row for that repo (errors + the token itself). Requires typing the repo name to confirm.
@@ -45,12 +45,15 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
45
45
  | `ghosttrap clear` | Skip all outstanding errors |
46
46
  | `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
47
47
 
48
+ `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.
49
+
48
50
  ## How it works
49
51
 
50
- - **Setup** authenticates with GitHub to prove you own the repo, then saves a token locally
52
+ - **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.
51
53
  - **Peek** and **watch** connect to ghosttrap.io using that token — no GitHub auth needed after setup
52
54
  - Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
53
- - Local state is stored in `~/.ghosttrap/config.json`
55
+ - 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
56
+ - Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
54
57
 
55
58
  ## Requirements
56
59
 
@@ -61,4 +64,8 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
61
64
 
62
65
  ## Privacy
63
66
 
64
- Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo ownership; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
67
+ Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo access; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
68
+
69
+ User context (Django user id + username) is **never** sent unless you opt in with `ghosttrap.init(token, send_user=True)` in your app. Server hostname is captured automatically.
70
+
71
+ Run `ghosttrap nuke` from inside a repo to permanently delete every server-side row for that repo (errors + the token itself). Requires typing the repo name to confirm.
@@ -21,9 +21,10 @@ KNOWN_SKILL_HASHES = {
21
21
  "38810f43867a2a91420cc3dacbc71d2acabd7125596fd5b43f222b49725c9696", # v0.3.10
22
22
  "19b67d913dc5214ee4db3610bd8749da67324c174b904b5da71ee6de13e23e63", # v0.3.11
23
23
  "bf7768c3de266b7018d5c722c6c9991b487e7897786b3a406c460842cdcde8b5", # v0.3.12
24
+ "d3f594c4c3601a4594c18ebc5e16dfd4abad4ab97d56285ecc20634b919b6731", # v0.3.14
24
25
  }
25
26
 
26
- __version__ = "0.3.14"
27
+ __version__ = "0.3.16"
27
28
 
28
29
  GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
29
30
  CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
@@ -70,7 +71,7 @@ Read `~/.ghosttrap/config.json` for state. It contains:
70
71
 
71
72
  1. Detect the current repo from `git config --get remote.origin.url` (returns `owner/name`).
72
73
  2. Find a matching entry in config by looking for one whose `owner`/`name` equals the detected slug. If no match, tell the user to run `ghosttrap setup`. (The owner/name on a config entry auto-refreshes from the server when the repo is renamed or transferred, so always match against the entry's stored owner/name, not the config key.)
73
- 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.
74
+ 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.
74
75
  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.
75
76
 
76
77
  ## When peek returns
@@ -200,18 +201,26 @@ def get_gh_token():
200
201
  sys.exit(1)
201
202
 
202
203
 
203
- def _get_repo_token(config):
204
- """Get the repo token for the current directory from config."""
204
+ def _get_repo_token(config, requested=None):
205
+ """Get the repo token. If `requested` is 'owner/name', match strictly. Else cwd, else first."""
205
206
  repos = config.get("repos", {})
207
+ if not repos:
208
+ print("error: no repos configured. run 'ghosttrap setup' first.", file=sys.stderr)
209
+ sys.exit(1)
210
+ if requested:
211
+ for entry in repos.values():
212
+ if f"{entry.get('owner')}/{entry.get('name')}" == requested:
213
+ return entry["token"]
214
+ available = sorted(f"{e['owner']}/{e['name']}" for e in repos.values())
215
+ print(f"error: '{requested}' is not in your config.", file=sys.stderr)
216
+ print(f"available: {', '.join(available)}", file=sys.stderr)
217
+ sys.exit(1)
206
218
  cwd_repo = _detect_repo_from_cwd()
207
219
  if cwd_repo:
208
220
  for entry in repos.values():
209
221
  if f"{entry.get('owner')}/{entry.get('name')}" == cwd_repo:
210
222
  return entry["token"]
211
- if repos:
212
- return next(iter(repos.values()))["token"]
213
- print("error: no repos configured. run 'ghosttrap setup' first.", file=sys.stderr)
214
- sys.exit(1)
223
+ return next(iter(repos.values()))["token"]
215
224
 
216
225
 
217
226
  async def _connect_and_handle(server_url, token, config, once=False):
@@ -387,10 +396,10 @@ def _advance_cursor(config, token):
387
396
  return pending
388
397
 
389
398
 
390
- def clear():
399
+ def clear(requested=None):
391
400
  _require_setup()
392
401
  config = _load_config()
393
- token = _get_repo_token(config)
402
+ token = _get_repo_token(config, requested)
394
403
  try:
395
404
  pending = _advance_cursor(config, token)
396
405
  if pending:
@@ -455,11 +464,11 @@ def nuke():
455
464
  _save_config(config)
456
465
 
457
466
 
458
- def last(do_clear=False):
467
+ def last(do_clear=False, requested=None):
459
468
  _require_setup()
460
469
  config = _load_config()
461
470
  _check_cli_version(config)
462
- token = _get_repo_token(config)
471
+ token = _get_repo_token(config, requested)
463
472
  server = GHOSTTRAP_SERVER.replace("wss://", "https://").replace("/stream/", "")
464
473
  url = f"{server}/last/{token}/"
465
474
  try:
@@ -500,17 +509,22 @@ def main():
500
509
  sub = parser.add_subparsers(dest="command")
501
510
 
502
511
  sub.add_parser("setup", help="Claim repos and install Claude Code skill")
503
- sub.add_parser("clear", help="Skip all outstanding errors")
512
+
513
+ clear_parser = sub.add_parser("clear", help="Skip all outstanding errors")
514
+ clear_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
504
515
 
505
516
  watch_parser = sub.add_parser("watch", help="Stream errors in real time")
506
517
  watch_parser.add_argument("--server", default=GHOSTTRAP_SERVER, help="WebSocket server URL")
518
+ watch_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
507
519
 
508
520
  peek_parser = sub.add_parser("peek", help="Wait for the next error then exit")
509
521
  peek_parser.add_argument("--server", default=GHOSTTRAP_SERVER, help="WebSocket server URL")
510
522
  peek_parser.add_argument("--clear", action="store_true", help="Skip outstanding errors before waiting")
523
+ peek_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
511
524
 
512
525
  last_parser = sub.add_parser("last", help="Fetch the most recent error then exit")
513
526
  last_parser.add_argument("--clear", action="store_true", help="Also skip remaining outstanding errors")
527
+ last_parser.add_argument("--repo", help="Target repo as owner/name (overrides cwd detection)")
514
528
 
515
529
  sub.add_parser("nuke", help="Permanently delete all server data for the current repo")
516
530
 
@@ -520,18 +534,18 @@ def main():
520
534
  token = get_gh_token()
521
535
  asyncio.run(setup(GHOSTTRAP_SERVER, token))
522
536
  elif args.command == "clear":
523
- clear()
537
+ clear(requested=args.repo)
524
538
  elif args.command == "watch":
525
539
  _require_setup()
526
540
  _refresh_skill_if_stale()
527
541
  config = _load_config()
528
- token = _get_repo_token(config)
542
+ token = _get_repo_token(config, args.repo)
529
543
  asyncio.run(watch(args.server, token))
530
544
  elif args.command == "peek":
531
545
  _require_setup()
532
546
  _refresh_skill_if_stale()
533
547
  config = _load_config()
534
- token = _get_repo_token(config)
548
+ token = _get_repo_token(config, args.repo)
535
549
  if args.clear:
536
550
  try:
537
551
  _advance_cursor(config, token)
@@ -541,7 +555,7 @@ def main():
541
555
  asyncio.run(peek(args.server, token))
542
556
  elif args.command == "last":
543
557
  _refresh_skill_if_stale()
544
- last(do_clear=args.clear)
558
+ last(do_clear=args.clear, requested=args.repo)
545
559
  elif args.command == "nuke":
546
560
  nuke()
547
561
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghosttrap-cli
3
- Version: 0.3.14
3
+ Version: 0.3.16
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
@@ -54,12 +54,15 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
54
54
  | `ghosttrap clear` | Skip all outstanding errors |
55
55
  | `ghosttrap nuke` | Permanently delete every server-side row for the current repo (errors + token). Requires typed confirmation. |
56
56
 
57
+ `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.
58
+
57
59
  ## How it works
58
60
 
59
- - **Setup** authenticates with GitHub to prove you own the repo, then saves a token locally
61
+ - **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.
60
62
  - **Peek** and **watch** connect to ghosttrap.io using that token — no GitHub auth needed after setup
61
63
  - Errors that arrive while you're offline are replayed on next connect (cursor-based, no duplicates)
62
- - Local state is stored in `~/.ghosttrap/config.json`
64
+ - 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
65
+ - Local state is stored in `~/.ghosttrap/config.json`, keyed by GitHub repo id
63
66
 
64
67
  ## Requirements
65
68
 
@@ -70,4 +73,8 @@ Your app needs [ghosttrap-sdk](https://github.com/alex-rowley/ghosttrap-sdk) to
70
73
 
71
74
  ## Privacy
72
75
 
73
- Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo ownership; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
76
+ Error data (tracebacks, exception messages, file paths) is routed through ghosttrap.io. The server is not open source yet — if there's demand for self-hosting, we'll open it up. Your GitHub token is used only during `setup` to verify repo access; it's never stored on the server. After setup, all communication uses a repo-specific token that grants access only to that repo's error stream — it cannot access your GitHub account.
77
+
78
+ User context (Django user id + username) is **never** sent unless you opt in with `ghosttrap.init(token, send_user=True)` in your app. Server hostname is captured automatically.
79
+
80
+ Run `ghosttrap nuke` from inside a repo to permanently delete every server-side row for that repo (errors + the token itself). Requires typing the repo name to confirm.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ghosttrap-cli"
7
- version = "0.3.14"
7
+ version = "0.3.16"
8
8
  description = "Watch for errors streaming from ghosttrap.io"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes