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.
- stacktrace_cli/__init__.py +1 -1
- stacktrace_cli/analysis.py +68 -6
- stacktrace_cli/cloud/setup.sh +35 -8
- stacktrace_cli/daemon/cli.py +20 -2
- stacktrace_cli/daemon/runtime.py +21 -7
- stacktrace_cli/monitor/watch.py +37 -1
- {stacktrace_cli-0.5.2.dist-info → stacktrace_cli-0.5.3.dist-info}/METADATA +2 -2
- {stacktrace_cli-0.5.2.dist-info → stacktrace_cli-0.5.3.dist-info}/RECORD +10 -10
- {stacktrace_cli-0.5.2.dist-info → stacktrace_cli-0.5.3.dist-info}/WHEEL +0 -0
- {stacktrace_cli-0.5.2.dist-info → stacktrace_cli-0.5.3.dist-info}/entry_points.txt +0 -0
stacktrace_cli/__init__.py
CHANGED
stacktrace_cli/analysis.py
CHANGED
|
@@ -204,7 +204,7 @@ def analyse(
|
|
|
204
204
|
root=root,
|
|
205
205
|
session_ids=session_ids,
|
|
206
206
|
)
|
|
207
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
stacktrace_cli/cloud/setup.sh
CHANGED
|
@@ -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
|
|
43
|
-
#
|
|
44
|
-
#
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
stacktrace_cli/daemon/cli.py
CHANGED
|
@@ -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=
|
|
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:
|
stacktrace_cli/daemon/runtime.py
CHANGED
|
@@ -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,
|
|
196
|
-
|
|
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
|
|
215
|
+
return StartOutcome.ALREADY_RUNNING
|
|
207
216
|
if daemon_is_running(paths.socket):
|
|
208
217
|
report_start(StartOutcome.ALREADY_RUNNING)
|
|
209
|
-
return
|
|
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
|
-
|
|
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:
|
stacktrace_cli/monitor/watch.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
105
|
-
stacktrace_cli-0.5.
|
|
106
|
-
stacktrace_cli-0.5.
|
|
107
|
-
stacktrace_cli-0.5.
|
|
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,,
|
|
File without changes
|
|
File without changes
|