ghosttrap-cli 0.3.10__tar.gz → 0.3.11__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.10
3
+ Version: 0.3.11
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
@@ -18,9 +18,10 @@ KNOWN_SKILL_HASHES = {
18
18
  "8564b65b8ab5c63283cda1706e30ca62bc4e111d33ba8918220f4b556ad01da1", # v0.3.1..v0.3.3
19
19
  "5759b2e0dc8ca47c3801915fd688cc8da878a7ab8d405f5183ffd7e8c8df4c55", # v0.3.4..v0.3.7
20
20
  "0651bb4247cf5c68960ff5b63d6a5d0c85ff1ce08e7966ab4823601ff02cf1f4", # v0.3.9
21
+ "38810f43867a2a91420cc3dacbc71d2acabd7125596fd5b43f222b49725c9696", # v0.3.10
21
22
  }
22
23
 
23
- __version__ = "0.3.0"
24
+ __version__ = "0.3.11"
24
25
 
25
26
  GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
26
27
  CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
@@ -60,13 +61,13 @@ description: Production error monitoring via ghosttrap.io. Trigger when starting
60
61
  # Ghosttrap
61
62
 
62
63
  Read `~/.ghosttrap/config.json` for state. It contains:
63
- - `repos`: map of `"owner/repo"` to `{"token": "t_xxx", "sdk_installed": bool, "sdk_version": str, "init_file": str}`
64
+ - `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}`. Older configs may still be keyed by `"owner/name"` — same shape inside.
64
65
  - `cursor`: last seen error ID
65
66
 
66
67
  ## On session start
67
68
 
68
- 1. Detect the current repo from `git config --get remote.origin.url`.
69
- 2. Look it up in the config. If the repo isn't there, tell the user to run `ghosttrap setup`.
69
+ 1. Detect the current repo from `git config --get remote.origin.url` (returns `owner/name`).
70
+ 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.)
70
71
  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.
71
72
  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.
72
73
 
@@ -103,16 +104,35 @@ def _save_config(config):
103
104
  json.dump(config, f, indent=2)
104
105
 
105
106
 
106
- def _is_known_repo(config, owner, name):
107
- return f"{owner}/{name}" in config.get("repos", {})
107
+ def _repo_key(r):
108
+ gid = r.get("github_id")
109
+ if gid is not None:
110
+ return str(gid)
111
+ return f"{r.get('owner')}/{r.get('name')}"
112
+
113
+
114
+ def _is_known_repo(config, repo_entry):
115
+ return _repo_key(repo_entry) in config.get("repos", {})
108
116
 
109
117
 
110
118
  def _save_repos(config, repos):
111
119
  if "repos" not in config:
112
120
  config["repos"] = {}
113
121
  for r in repos:
114
- key = f"{r['owner']}/{r['name']}"
115
- config["repos"][key] = {"token": r["token"]}
122
+ key = _repo_key(r)
123
+ existing = config["repos"].get(key, {})
124
+ # Drop any legacy slug-keyed entry now superseded by a github_id key.
125
+ if r.get("github_id") is not None:
126
+ legacy_key = f"{r.get('owner')}/{r.get('name')}"
127
+ if legacy_key in config["repos"] and legacy_key != key:
128
+ existing = {**config["repos"].pop(legacy_key), **existing}
129
+ existing.update({
130
+ "github_id": r.get("github_id"),
131
+ "owner": r["owner"],
132
+ "name": r["name"],
133
+ "token": r["token"],
134
+ })
135
+ config["repos"][key] = existing
116
136
  _save_config(config)
117
137
 
118
138
 
@@ -145,7 +165,7 @@ def _find_target_repo(repos):
145
165
  cwd_slug = _detect_repo_from_cwd()
146
166
  if cwd_slug:
147
167
  for r in repos:
148
- if f"{r['owner']}/{r['name']}" == cwd_slug:
168
+ if f"{r.get('owner')}/{r.get('name')}" == cwd_slug:
149
169
  return r
150
170
  return repos[0] if repos else None
151
171
 
@@ -184,11 +204,14 @@ def get_gh_token():
184
204
 
185
205
  def _get_repo_token(config):
186
206
  """Get the repo token for the current directory from config."""
187
- cwd_repo = _detect_repo_from_cwd()
188
- if cwd_repo and cwd_repo in config.get("repos", {}):
189
- return config["repos"][cwd_repo]["token"]
190
- # Fall back to first repo in config
191
207
  repos = config.get("repos", {})
208
+ cwd_repo = _detect_repo_from_cwd()
209
+ if cwd_repo:
210
+ for entry in repos.values():
211
+ if f"{entry.get('owner')}/{entry.get('name')}" == cwd_repo:
212
+ return entry["token"]
213
+ if cwd_repo in repos:
214
+ return repos[cwd_repo]["token"]
192
215
  if repos:
193
216
  return next(iter(repos.values()))["token"]
194
217
  print("error: no repos configured. run 'ghosttrap setup' first.", file=sys.stderr)
@@ -210,9 +233,10 @@ async def _connect_and_handle(server_url, token, config, once=False):
210
233
  repos = event.get("repos", [])
211
234
  print(f"watching {len(repos)} repo(s)", file=sys.stderr)
212
235
 
213
- new_repos = [r for r in repos if not _is_known_repo(config, r["owner"], r["name"])]
236
+ new_repos = [r for r in repos if not _is_known_repo(config, r)]
237
+ # Always sync — picks up renamed/transferred repos by github_id.
238
+ _save_repos(config, repos)
214
239
  if new_repos:
215
- _save_repos(config, repos)
216
240
  target = _find_target_repo(new_repos)
217
241
  if target:
218
242
  _print_setup_snippet(target)
@@ -220,10 +244,13 @@ async def _connect_and_handle(server_url, token, config, once=False):
220
244
  sdk_latest = event.get("sdk_latest")
221
245
  if sdk_latest:
222
246
  cwd_repo = _detect_repo_from_cwd()
223
- if cwd_repo and cwd_repo in config.get("repos", {}):
224
- installed = config["repos"][cwd_repo].get("sdk_version")
225
- if installed and installed != sdk_latest:
226
- print(f"ghosttrap-sdk {sdk_latest} available (you have {installed})", file=sys.stderr)
247
+ if cwd_repo:
248
+ for entry in config.get("repos", {}).values():
249
+ if f"{entry.get('owner')}/{entry.get('name')}" == cwd_repo:
250
+ installed = entry.get("sdk_version")
251
+ if installed and installed != sdk_latest:
252
+ print(f"ghosttrap-sdk {sdk_latest} available (you have {installed})", file=sys.stderr)
253
+ break
227
254
 
228
255
  if not once:
229
256
  print(f"waiting for errors...", file=sys.stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ghosttrap-cli
3
- Version: 0.3.10
3
+ Version: 0.3.11
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ghosttrap-cli"
7
- version = "0.3.10"
7
+ version = "0.3.11"
8
8
  description = "Watch for errors streaming from ghosttrap.io"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes