stacktrace-cli 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import logging as _logging
4
4
 
5
- __version__ = "0.5.2"
5
+ __version__ = "0.5.3"
6
6
 
7
7
  # Only the daemon attaches a handler (ADR-0048). Without this one, a WARNING
8
8
  # logged in any other process would reach Python's last-resort handler and
@@ -204,7 +204,7 @@ def analyse(
204
204
  root=root,
205
205
  session_ids=session_ids,
206
206
  )
207
- return _analyse_sessions(
207
+ found = _analyse_sessions(
208
208
  sessions,
209
209
  window_start=window_start,
210
210
  window_end=window_end,
@@ -220,6 +220,7 @@ def analyse(
220
220
  jev_key=jev_key,
221
221
  history=history,
222
222
  )
223
+ return scoped_to_window(found)
223
224
 
224
225
 
225
226
  def analyse_sessions(
@@ -373,6 +374,65 @@ def _correlated(
373
374
  )
374
375
 
375
376
 
377
+ def _reaches(detection: Any, window_start: datetime, window_end: datetime) -> bool:
378
+ """Whether this finding's own span overlaps the window.
379
+
380
+ Both ends are tested, because both can be crossed. `occurred_end` against
381
+ the start, so a rule firing across a run of turns belongs in the window
382
+ its last citation reaches; `occurred_start` against the end, so a finding
383
+ straddling the boundary is still reported rather than dropped for
384
+ finishing late.
385
+
386
+ The upper bound is not ceremony. `analyse` takes `window_end` *before*
387
+ collection -- deliberately, so a window cannot claim to cover a session
388
+ that started while the pipeline ran -- and collection then reads files
389
+ that may have been appended to in between. A finding can therefore carry
390
+ a clock later than the end the caller is shown, and reporting it would
391
+ contradict the heading the window prints.
392
+
393
+ A finding with no clock, or one whose clock is naive and cannot be
394
+ compared against an aware bound, is reported rather than judged: hiding a
395
+ real finding over a missing field is the more expensive mistake.
396
+ """
397
+ end = detection.occurred_end or detection.occurred_start
398
+ start = detection.occurred_start or detection.occurred_end
399
+ if end is None or start is None or end.tzinfo is None or start.tzinfo is None:
400
+ return True
401
+ return end >= window_start and start <= window_end
402
+
403
+
404
+ def scoped_to_window(analysis: Analysis) -> Analysis:
405
+ """The same analysis, reporting only the findings the window reaches.
406
+
407
+ A window selects whole transcripts by file mtime, so a session written
408
+ inside it is collected entire and every rule walks all of its turns. A
409
+ long session still being typed into therefore carried its whole history
410
+ into whatever range the caller asked for: a 538-turn session last active a
411
+ minute ago put an 18-minute-old block in a five-minute window.
412
+
413
+ Only ever narrowing, and that is not a coincidence. A finding's clock
414
+ comes from the turns it cites, and a transcript's mtime is its last write,
415
+ so mtime is never earlier than `occurred_end`. A finding inside the window
416
+ therefore always sits in a file the window already admitted -- this can
417
+ drop what the mtime rule over-admitted, and can never need to recover
418
+ anything it excluded.
419
+
420
+ Applied by `analyse`, so `detect`, `monitor` and `sync detect` cannot
421
+ disagree about one `--since` (`cli.py` states that invariant). Not by
422
+ `analyse_sessions`: the daemon passes a watermark rather than a range a
423
+ person chose, and its findings are the record that feeds the store, the
424
+ webhook and Fleet, where a window has no business narrowing anything.
425
+ """
426
+ kept = tuple(
427
+ d
428
+ for d in analysis.run.detections
429
+ if _reaches(d, analysis.window_start, analysis.window_end)
430
+ )
431
+ if len(kept) == len(analysis.run.detections):
432
+ return analysis
433
+ return replace(analysis, run=replace(analysis.run, detections=kept))
434
+
435
+
376
436
  def _unplaced(sessions: SessionView, *, window_start: datetime, window_end: datetime) -> Analysis:
377
437
  """The sessions as read, before anything has been correlated or judged.
378
438
 
@@ -498,11 +558,13 @@ def analyse_progressively(
498
558
  stage carrying fewer sessions than the preview did would make the count
499
559
  on the page go backwards.
500
560
  """
501
- return Analysis(
502
- run=run,
503
- view=replace(view, sessions=sessions),
504
- window_start=window_start,
505
- window_end=window_end,
561
+ return scoped_to_window(
562
+ Analysis(
563
+ run=run,
564
+ view=replace(view, sessions=sessions),
565
+ window_start=window_start,
566
+ window_end=window_end,
567
+ )
506
568
  )
507
569
 
508
570
  # The detector derives these from the view, and a stage that has not judged
@@ -39,9 +39,14 @@
39
39
  # from STACKTRACE_ASSET_EXTERNAL_ID in the environment
40
40
  # --root DIR treat DIR as / (for testing)
41
41
  #
42
- # The remote token and API URL are read by the boot hook at session time from
43
- # STACKTRACE_REMOTE_TOKEN and STACKTRACE_REMOTE_API_URL, not here: they belong
44
- # to the environment's variables, not to the snapshot this seeds.
42
+ # The remote token and API URL, and the Slack webhook URL, are read by the boot
43
+ # hook at session time from STACKTRACE_REMOTE_TOKEN, STACKTRACE_REMOTE_API_URL
44
+ # and STACKTRACE_SLACK_WEBHOOK_URL, not here: they belong to the environment's
45
+ # variables, not to the snapshot this seeds. The webhook in particular must be
46
+ # configured by the boot hook and not by this script, because
47
+ # `webhook configure` writes `$HOME/.config/stacktrace/webhook.toml` and this
48
+ # script runs as root — a webhook configured here would land in /root and the
49
+ # daemon, running as the session user, would read a home that has none.
45
50
  set -uo pipefail
46
51
 
47
52
  ASSET_ID="${STACKTRACE_ASSET_EXTERNAL_ID:-}"
@@ -54,7 +59,7 @@ while [ $# -gt 0 ]; do
54
59
  # the loop spins forever — a malformed invocation would hang setup.
55
60
  --asset-id) [ $# -ge 2 ] || { echo "✗ --asset-id needs a value" >&2; exit 2; }; ASSET_ID="$2"; shift 2 ;;
56
61
  --root) [ $# -ge 2 ] || { echo "✗ --root needs a value" >&2; exit 2; }; ROOT="$2"; shift 2 ;;
57
- -h|--help) sed -n '2,44p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
62
+ -h|--help) sed -n '2,49p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
58
63
  *) echo "✗ unknown argument: $1" >&2; exit 2 ;;
59
64
  esac
60
65
  done
@@ -108,14 +113,36 @@ cat > "$BOOT" <<'BOOTSCRIPT' || { echo "✗ cannot write $BOOT" >&2; exit 1; }
108
113
  set -u
109
114
  [ "${CLAUDE_CODE_REMOTE:-}" = "true" ] || exit 0
110
115
  command -v stacktrace >/dev/null 2>&1 || exit 0
111
- [ -n "${STACKTRACE_REMOTE_TOKEN:-}" ] || exit 0
116
+ # A webhook is an independent subscriber, not a Fleet feature (ADR-0067): a
117
+ # session with only STACKTRACE_SLACK_WEBHOOK_URL set still needs the daemon
118
+ # running to drain it, so the remote token gates `remote configure` alone,
119
+ # never this whole hook.
120
+ if [ -z "${STACKTRACE_REMOTE_TOKEN:-}" ] && [ -z "${STACKTRACE_SLACK_WEBHOOK_URL:-}" ]; then
121
+ exit 0
122
+ fi
112
123
 
113
124
  STATE="$HOME/.local/state/stacktrace"
114
125
  mkdir -p "$STATE" && chmod 700 "$STATE"
115
126
 
116
- stacktrace remote configure \
117
- --token "$STACKTRACE_REMOTE_TOKEN" \
118
- --api-url "${STACKTRACE_REMOTE_API_URL:-https://api.stacktrace.ai}" >/dev/null 2>&1 || true
127
+ if [ -n "${STACKTRACE_REMOTE_TOKEN:-}" ]; then
128
+ stacktrace remote configure \
129
+ --token "$STACKTRACE_REMOTE_TOKEN" \
130
+ --api-url "${STACKTRACE_REMOTE_API_URL:-https://api.stacktrace.ai}" >/dev/null 2>&1 || true
131
+ fi
132
+
133
+ # The Slack endpoint is session-time configuration like the token above, not
134
+ # part of the snapshot: written here it lands in the session user's home, which
135
+ # is the home the daemon started below reads `webhook.toml` from. Rotating the
136
+ # URL is then an environment-variable change, with no snapshot to rebuild.
137
+ # `--secret` is always passed explicitly, empty if unset: the option prompts
138
+ # when neither it nor STACKTRACE_WEBHOOK_SECRET is given, and a prompt in a hook
139
+ # with no terminal aborts the command. Configured before the daemon starts, so
140
+ # the daemon reads the endpoint at startup rather than waiting for a drain.
141
+ if [ -n "${STACKTRACE_SLACK_WEBHOOK_URL:-}" ]; then
142
+ stacktrace webhook configure \
143
+ --url "$STACKTRACE_SLACK_WEBHOOK_URL" \
144
+ --secret "${STACKTRACE_WEBHOOK_SECRET:-}" >/dev/null 2>&1 || true
145
+ fi
119
146
 
120
147
  # Streams redirected because own_std_streams only re-points fd 1/2 on log
121
148
  # rollover; without this the daemon inherits the hook's pipe to Claude Code.
@@ -140,13 +140,27 @@ def run_command(
140
140
  _reject_unobservable_kinds(config)
141
141
  except ValueError as error:
142
142
  raise click.ClickException(str(error)) from error
143
- from .lifecycle import DETACHED_ENV
143
+ from .lifecycle import DETACHED_ENV, StartOutcome
144
+
145
+ detached = os.environ.get(DETACHED_ENV) == "1"
146
+
147
+ def report_ready() -> None:
148
+ if not detached:
149
+ click.echo(
150
+ "Stacktrace daemon is running in the foreground. Press Ctrl-C to stop.",
151
+ err=True,
152
+ )
144
153
 
145
154
  status = run_daemon(
146
155
  paths,
147
156
  config,
148
- own_std_streams=os.environ.get(DETACHED_ENV) == "1",
157
+ own_std_streams=detached,
158
+ on_ready=report_ready,
149
159
  )
160
+ if status is StartOutcome.ALREADY_RUNNING:
161
+ if not detached:
162
+ click.echo("Stacktrace daemon is already running.")
163
+ return
150
164
  if status:
151
165
  raise click.exceptions.Exit(status)
152
166
 
@@ -324,6 +338,7 @@ def start() -> None:
324
338
  from .lifecycle import StartOutcome, start_daemon, transition_lock
325
339
 
326
340
  paths = RuntimePaths.from_environment()
341
+ click.echo("Starting Stacktrace daemon...", err=True)
327
342
  try:
328
343
  with transition_lock(paths.transition_lock):
329
344
  outcome = start_daemon(paths)
@@ -342,6 +357,7 @@ def stop() -> None:
342
357
  from .lifecycle import daemon_lock_held, transition_lock
343
358
 
344
359
  paths = RuntimePaths.from_environment()
360
+ click.echo("Stopping Stacktrace daemon...", err=True)
345
361
  with transition_lock(paths.transition_lock):
346
362
  if not daemon_lock_held(paths.lock):
347
363
  click.echo("Stacktrace daemon is not running.")
@@ -416,6 +432,7 @@ def flush() -> None:
416
432
  "no daemon is running, so there is no queue to drain; this endpoint "
417
433
  "uploads by scanning instead — run `stacktrace remote sync detect`"
418
434
  )
435
+ click.echo("Flushing queued findings...", err=True)
419
436
  try:
420
437
  result = request_flush(paths.socket)
421
438
  except (OSError, ProtocolError) as error:
@@ -451,6 +468,7 @@ def sync_bom() -> None:
451
468
  raise click.ClickException(
452
469
  "no daemon is running; to upload without one, run `stacktrace remote sync endpoint`"
453
470
  )
471
+ click.echo("Syncing endpoint composition...", err=True)
454
472
  try:
455
473
  result = request_sync_bom(paths.socket)
456
474
  except (OSError, ProtocolError) as error:
@@ -15,7 +15,7 @@ from contextlib import ExitStack, contextmanager, suppress
15
15
  from datetime import UTC, datetime
16
16
  from pathlib import Path
17
17
  from types import FrameType
18
- from typing import TextIO, TypeVar
18
+ from typing import Literal, TextIO, TypeVar
19
19
 
20
20
  from stacktrace_cli.build import version_label
21
21
  from stacktrace_cli.correlate.orchestrate import build_compositions
@@ -98,13 +98,18 @@ def follow_notifications(
98
98
  maximum_delay: float = 2.0,
99
99
  ) -> None:
100
100
  reported_disconnect = False
101
+ subscribed_once = False
101
102
  delay = reconnect_delay
102
103
 
103
104
  def _on_subscribed() -> None:
104
- nonlocal reported_disconnect, delay
105
+ nonlocal reported_disconnect, subscribed_once, delay
105
106
  if reported_disconnect:
106
107
  stderr.write("Stacktrace monitor reconnected\n")
107
108
  stderr.flush()
109
+ elif not subscribed_once:
110
+ stderr.write("Stacktrace monitor connected\n")
111
+ stderr.flush()
112
+ subscribed_once = True
108
113
  reported_disconnect = False
109
114
  delay = reconnect_delay
110
115
 
@@ -192,8 +197,12 @@ def _await_initial_compositions(
192
197
 
193
198
 
194
199
  def run_daemon(
195
- paths: RuntimePaths, config: DaemonConfig | None = None, *, own_std_streams: bool = False
196
- ) -> int:
200
+ paths: RuntimePaths,
201
+ config: DaemonConfig | None = None,
202
+ *,
203
+ own_std_streams: bool = False,
204
+ on_ready: Callable[[], None] = lambda: None,
205
+ ) -> int | Literal[StartOutcome.ALREADY_RUNNING]:
197
206
  """The resident process: a socket, a scan cadence, and two ways to stop.
198
207
 
199
208
  `config` is resolved by the caller (`daemon/cli.py`) so a bad value fails
@@ -203,10 +212,10 @@ def run_daemon(
203
212
  with _daemon_lock(paths.lock) as acquired:
204
213
  if not acquired:
205
214
  report_start(StartOutcome.ALREADY_RUNNING)
206
- return 0
215
+ return StartOutcome.ALREADY_RUNNING
207
216
  if daemon_is_running(paths.socket):
208
217
  report_start(StartOutcome.ALREADY_RUNNING)
209
- return 0
218
+ return StartOutcome.ALREADY_RUNNING
210
219
  # Opened only by the lock holder: two processes rotating one file
211
220
  # would each rename the other's writes away (ADR-0048).
212
221
  handler = activity_log.configure(paths.log, own_std_streams=own_std_streams)
@@ -425,7 +434,12 @@ def run_daemon(
425
434
  # that raises must not skip `compositions.close()`.
426
435
  webhook_worker.start()
427
436
  try:
428
- service.run(on_ready=lambda: report_start(StartOutcome.STARTED))
437
+
438
+ def ready() -> None:
439
+ report_start(StartOutcome.STARTED)
440
+ on_ready()
441
+
442
+ service.run(on_ready=ready)
429
443
  finally:
430
444
  webhook_worker.close()
431
445
  try:
@@ -29,6 +29,7 @@ from collections.abc import Callable, Iterable
29
29
  from dataclasses import replace
30
30
  from pathlib import Path
31
31
  from threading import Lock
32
+ from time import monotonic
32
33
  from typing import Any
33
34
 
34
35
  from stacktrace_cli.analysis import Analysis, analyse_progressively
@@ -103,6 +104,13 @@ def _last_active(analysis: Analysis, by_stem: dict[str, float]) -> dict[tuple[st
103
104
  return dated
104
105
 
105
106
 
107
+ #: How long the wait between passes sleeps at a time. Short enough that a
108
+ #: reader clicking a range is answered as if there were no wait at all, long
109
+ #: enough that an idle monitor is not spinning: ten wake-ups a second on a
110
+ #: thread that does nothing with them.
111
+ _WAKE_SLICE = 0.1
112
+
113
+
106
114
  class Watcher:
107
115
  """Runs the pipeline when the machine has changed, and publishes the result."""
108
116
 
@@ -133,6 +141,9 @@ class Watcher:
133
141
  #: because the tick reads those on another thread and a change landing
134
142
  #: mid-pass would give one snapshot two windows.
135
143
  self._pending_window: str | None = None
144
+ #: Set when a reader asks for a different range, so the wait between
145
+ #: passes ends and the new window is read now rather than next tick.
146
+ self._wake = threading.Event()
136
147
  #: Bumped by everything that says "the last answer is stale". The
137
148
  #: fingerprint alone cannot carry that: computing one is a walk of
138
149
  #: every transcript, and an invalidation arriving during the walk would
@@ -254,6 +265,9 @@ class Watcher:
254
265
  return
255
266
  self._pending_window = since
256
267
  self._invalidate_locked()
268
+ # Outside the lock: the waiting thread takes it at the top of a tick,
269
+ # and setting an event is not work worth holding a lock for.
270
+ self._wake.set()
257
271
 
258
272
  def _take_pending_window(self) -> int:
259
273
  """Apply the newest asked-for window, once, at the top of a tick.
@@ -549,10 +563,32 @@ class Watcher:
549
563
  Waits on the event rather than sleeping, so a shutdown is immediate
550
564
  instead of taking up to one interval — the difference between Ctrl-C
551
565
  feeling instant and feeling broken.
566
+
567
+ A range the reader asked for cuts the wait the same way. Without that
568
+ the click was answered by the *next* scheduled pass, so a switch cost
569
+ up to a whole interval of nothing happening before the work even
570
+ started, on top of the pass itself.
552
571
  """
553
572
  while not stop.is_set():
573
+ self._wake.clear()
554
574
  self.tick()
555
- stop.wait(self._interval)
575
+ self._wait_between_passes(stop)
576
+
577
+ def _wait_between_passes(self, stop: threading.Event) -> None:
578
+ """Sleep off the interval, ending early for a stop or a new range.
579
+
580
+ Polled in slices rather than waited on directly: `stop` belongs to the
581
+ caller — a test builds its own — so it cannot be made to signal this
582
+ watcher's wake, and waiting on the wake alone would leave a shutdown
583
+ sitting out the interval. The slice is what buys both, and it is short
584
+ enough that a click is answered as if the wait were not there.
585
+ """
586
+ deadline = monotonic() + self._interval
587
+ while not stop.is_set() and not self._wake.is_set():
588
+ remaining = deadline - monotonic()
589
+ if remaining <= 0:
590
+ return
591
+ stop.wait(min(_WAKE_SLICE, remaining))
556
592
 
557
593
  def start(self) -> tuple[threading.Thread, threading.Event]:
558
594
  stop = threading.Event()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stacktrace-cli
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: CLI for Stacktrace — Detection and Response platform for AI Agents.
5
5
  Project-URL: Homepage, https://stacktrace.ai
6
6
  Author-email: "Stacktrace AI, Inc" <founders@stacktrace.ai>
@@ -51,7 +51,7 @@ Requires Python 3.11 or newer.
51
51
 
52
52
  ```console
53
53
  $ stacktrace --version
54
- stacktrace 0.5.2 (openaca 0.7.0)
54
+ stacktrace 0.5.3 (openaca 0.7.0)
55
55
  ```
56
56
 
57
57
  ## Quick start
@@ -1,12 +1,12 @@
1
- stacktrace_cli/__init__.py,sha256=ZqejOM2oRFblYAQI6BF48n2snpTnk1kBMcgBRK6YUfg,390
1
+ stacktrace_cli/__init__.py,sha256=uZez61mRGIufzqZ0mhABA187Aaeu263jKebIpCIQ9lE,390
2
2
  stacktrace_cli/__main__.py,sha256=F_tqj3PRzeBtY-uvBs923H8oc-s9bebhIh6UnAUwNYc,448
3
- stacktrace_cli/analysis.py,sha256=cNsFO0Awr2gxzQWfEC1befhQ-6XOZDcv0VMmR5QYFug,23766
3
+ stacktrace_cli/analysis.py,sha256=dZ47s-a6CYQhjzEK_GWklEfw8XZsgqYTb04Etkt9mA8,26849
4
4
  stacktrace_cli/build.py,sha256=lXMXBMoQtXC5qVMAWBsJavcVfXPmJiAZgV3cRBqUvyk,4846
5
5
  stacktrace_cli/cli.py,sha256=WXcuCNlOSI0qbn9_gD2q_QkVCUCincCtxr_ccK8p_jw,23522
6
6
  stacktrace_cli/options.py,sha256=PbbBkl4GB8mCcR6InhUQddu8MVJNkQajMBh6HZ9rAUk,5510
7
7
  stacktrace_cli/private_state.py,sha256=Lt_JyvWpnpFXviZInYMZVUCLpZE4dLzoyxstnd7sp58,1301
8
8
  stacktrace_cli/cloud/__init__.py,sha256=gs5wqqMA4D6DcoTkmfp8ZKwV9LzkEzH7zDYndtUJoZY,1704
9
- stacktrace_cli/cloud/setup.sh,sha256=siFTc4QmVugu4aJpdZRg-aP_7P9S7cDVu7PLjKU80Ak,10977
9
+ stacktrace_cli/cloud/setup.sh,sha256=K-xTA7_DUpZ5WIdC2p9Jy_QHtgpRp7xH0sbFVCeF68E,12555
10
10
  stacktrace_cli/correlate/__init__.py,sha256=SexF0pF83OGfBW_569yoyKG94-tb-jGr_ih1q4fHFgE,81
11
11
  stacktrace_cli/correlate/acquire.py,sha256=uyKb8kVbHXl7XLpdFUiHJtWyqvPB853eqgUKttrFTRc,25158
12
12
  stacktrace_cli/correlate/composition.py,sha256=ZFP3qPOjZvrGVexGWaH7Iq0lIyQLEY-1YxOZ3iMZIbk,20503
@@ -19,7 +19,7 @@ stacktrace_cli/correlate/render.py,sha256=hJZhZcqE4bKAeXIegddY7uViCyp7NAPTQl1cj9
19
19
  stacktrace_cli/daemon/__init__.py,sha256=YP0eKpNcRl1GzKO1GXh9gOoigViU2duV-PEOw0lLLwQ,59
20
20
  stacktrace_cli/daemon/activity_log.py,sha256=0DRxWBpcDElsbJp6DXYURjxQ1jnJD3snQVyWqvGnnqc,3243
21
21
  stacktrace_cli/daemon/bom.py,sha256=Y0DIFTs_srqcgSSUrd5x0eySfMdiN6f0UKBb9SwZ0-M,2231
22
- stacktrace_cli/daemon/cli.py,sha256=8ghqrcVNcCY7jm5MFUSovyXOCEa6XG0aZQy-ybjyak8,21026
22
+ stacktrace_cli/daemon/cli.py,sha256=9u5GkvnEiAvcGkUEaennNKm7YM-dhL_qhdUrYHe_ojI,21690
23
23
  stacktrace_cli/daemon/client.py,sha256=Enn2I5eZb0AMBkOAh2z1HCqFTPR4U1jP6l4oDdl8ttY,13928
24
24
  stacktrace_cli/daemon/composition.py,sha256=XCwTitMb7co1UMgE_xYQQIeSlPpLQIDaPkt9dlkEiTU,9867
25
25
  stacktrace_cli/daemon/config.py,sha256=bPBh7aPPSxVGscyCCMG50LJ1tDEKQtz0ZepAGgG9jzs,7694
@@ -32,7 +32,7 @@ stacktrace_cli/daemon/observer.py,sha256=Fz8V2fYPPOx3v_YVG2GF-aQ9eft8yeKQlaJTSEM
32
32
  stacktrace_cli/daemon/paths.py,sha256=pm9tYLzD6dPRPksLnrwPHOij_pX3CgQMIzGiDdMioXo,1574
33
33
  stacktrace_cli/daemon/presentation.py,sha256=LPm9ibm4Kw42RtB3H4PNT3iJgHUyWn-rlhgvymmlHrk,2751
34
34
  stacktrace_cli/daemon/protocol.py,sha256=dEvI8KZiAbkpjU9V2-v2OCGqn1WiPQhphJ3yXTEX2jY,5334
35
- stacktrace_cli/daemon/runtime.py,sha256=3PF5u0WJIEEmefPNniYZjtPGKerb7oYhsM1qBIbbuG4,32273
35
+ stacktrace_cli/daemon/runtime.py,sha256=wNU-DEroy73FYuvSIUvHrVkTrx_qSF-6r13Tpb-FoJg,32722
36
36
  stacktrace_cli/daemon/scan.py,sha256=rNSIMKt3nBXLngRluUk9N2FHk9u0Zvj1UGl81y6c7pQ,12337
37
37
  stacktrace_cli/daemon/server.py,sha256=ZkQTJVweyqFCffLsgx_lY_oP4yCrjta9rbB-mdsDV04,11590
38
38
  stacktrace_cli/daemon/service.py,sha256=CR3FwJUuLc8dBLyYiQzALBWnbyvtwQMi-wmvtjotURY,5296
@@ -66,7 +66,7 @@ stacktrace_cli/monitor/render.py,sha256=O_ls0s4Q43S0pWJ_mH8WcLwf4VPn2SWqnZytvOdf
66
66
  stacktrace_cli/monitor/server.py,sha256=ULf9Aa7NSIyD93jJhhUqSNEbw0JcSiTkHqmd8KyniFM,31597
67
67
  stacktrace_cli/monitor/state.py,sha256=AW0xJ5TgqpJi_ngq2waGSGO6pkGIe6-Rn-erGSubR-k,8240
68
68
  stacktrace_cli/monitor/verdicts.py,sha256=-0IEiaQ1h4DYHuAcgaI14CMNqm5Buqw2J8BmL6-IVJQ,2277
69
- stacktrace_cli/monitor/watch.py,sha256=TqoKQR7dY_3P0EQP3ogNPwN2_XhoPOTE6R71SkPVUGA,28879
69
+ stacktrace_cli/monitor/watch.py,sha256=Z0Uc-smzm6qOv5WhrVzY-0CC0ymBDA8F6eekCcA0zOA,30671
70
70
  stacktrace_cli/monitor/site/app.js,sha256=dBxSZHbMFaJ-iEIPKQeDuxTZMrRY9tyLXgkidWU8AAI,131385
71
71
  stacktrace_cli/monitor/site/index.html,sha256=mvStBpCJhHxbFLGFn1_CBRiifCh-xjJ5TZlXx0OcWm8,6049
72
72
  stacktrace_cli/monitor/site/styles.css,sha256=aYbBy9k9CvvhQCNbERKzoKcDkPKoR8fwhnRF_fQqWbA,44898
@@ -101,7 +101,7 @@ stacktrace_cli/telemetry/state.py,sha256=-7UmK8bBFj3Zh3bzX0kJwxn7-CvreMar_0x1NiZ
101
101
  stacktrace_cli/webhook/__init__.py,sha256=6knjTNQA1ErM-AO6fQ60_TZqZzbVjQeDMU1XIPPCpo0,35
102
102
  stacktrace_cli/webhook/cli.py,sha256=cWXOila3aVqal9hpkEFacQE_UNcMem5qCZiZHtyc1-Q,6165
103
103
  stacktrace_cli/webhook/config.py,sha256=Uv6_5xtKlO5RQG3FkhtSS8TFKYlc5CAZ0Kwd0CCq99c,8655
104
- stacktrace_cli-0.5.2.dist-info/METADATA,sha256=4Viuk3G3MfIuoDydS9sv8YS46-1LvPMdaH3nnES-1jY,9160
105
- stacktrace_cli-0.5.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
106
- stacktrace_cli-0.5.2.dist-info/entry_points.txt,sha256=HXQBr0nneh_o4A5t-4kZzmhjbh358P0G4M59kWdnejc,111
107
- stacktrace_cli-0.5.2.dist-info/RECORD,,
104
+ stacktrace_cli-0.5.3.dist-info/METADATA,sha256=roeYUreCATO9wqwzhaEkpCgPZ-xRFsebP6GGvNNt6hQ,9160
105
+ stacktrace_cli-0.5.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
106
+ stacktrace_cli-0.5.3.dist-info/entry_points.txt,sha256=HXQBr0nneh_o4A5t-4kZzmhjbh358P0G4M59kWdnejc,111
107
+ stacktrace_cli-0.5.3.dist-info/RECORD,,