Metadata-Version: 2.5
Name: stacktrace-cli
Version: 0.2.0
Summary: Placeholder CLI for Stacktrace.ai — installs the `stacktrace` command.
Project-URL: Homepage, https://stacktrace.ai
Author-email: Prasanth G <prasanth@openaca.dev>
License-Expression: LicenseRef-Proprietary
Keywords: agent-security,ai-security,openaca,stacktrace
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: httpx<1.0.dev0,>=0.28.1
Requires-Dist: openaca==0.6.0
Requires-Dist: openaidr==0.1.0
Description-Content-Type: text/markdown

# stacktrace-cli

Command-line interface for [Stacktrace.ai](https://stacktrace.ai).

The PyPI distribution is named `stacktrace-cli`  Installing this package provides
the `stacktrace` executable.

```bash
pip install stacktrace-cli
stacktrace --version
```

This is an early placeholder release. It depends on
[`openaca`](https://pypi.org/project/openaca/), the open-source Agent
Composition Analysis toolkit that Stacktrace.ai builds on.

## Design

This repository is the design home for Stacktrace Detect — the agentic AI
detection and response product this CLI grows into. OpenACA answers *what is
installed and what could it do*; Stacktrace Detect adds *what did it actually
do*, by joining agent session activity to the composition graph.

| Document | Covers |
|---|---|
| [docs/specs/aidr.md](docs/specs/aidr.md) | Umbrella — components, contracts, tenets, delivery |
| [docs/specs/session-input.md](docs/specs/session-input.md) | The seam over OpenAIDR, which collects sessions |
| [docs/specs/correlation.md](docs/specs/correlation.md) | The join: sessions against the composition graph |
| [docs/specs/detector.md](docs/specs/detector.md) | Three-stage detection and the finding family |

Decisions are in [docs/adrs/](docs/adrs/).

Built on two Apache-2.0 packages, neither of which depends on this one:
[`openaca`](https://pypi.org/project/openaca/) for composition analysis, and
[`openaidr`](https://github.com/open-agent-security/openaidr) for session
collection.

## Status

`stacktrace` is one front door over two kinds of command:

```
Analysis (OpenACA):
  bom     Generate an Agent BOM for a repository or endpoint.
  policy  Validate and compile restrictive endpoint policies.
  scan    Scan a repository or endpoint for agent-composition findings.

Stacktrace:
  detect    Find security and reliability findings in what agents did.
  remote    Configure remote endpoint services.
  sessions  Print what the agents on this machine actually did.
```

`stacktrace` is the entry point for hosted-product users. OpenACA remains the
tool itself and stays independently installable — with it installed, both
`openaca scan` and `stacktrace scan` work and do exactly the same thing,
because they are the same Click command object running in one process.
`stacktrace --version` reports both versions, which turns "which did you run?"
into one answered question.

**Pre-alpha, and not installable from PyPI yet.** `stacktrace sessions` works —
it reads what the agents on this machine did, through
[OpenAIDR](https://github.com/open-agent-security/openaidr):

```console
$ uv run stacktrace sessions --since 2d --include-content
claude-code:s1  [claude-code]  2026-08-27T09:00:00+00:00  2 turns  2 calls
  assistant: Reading the changelog before drafting the release notes.
    ok                28c  Read
        result: ## 0.4.0 - correlate, detect
  assistant: Filing the release-notes follow-up.
                        -  github/create_issue

Summary — 1 sessions, 2 turns, 2 tool calls

  agent kinds
          1  claude-code

  tools called (2 distinct)
          1  Read
          1  github/create_issue

  MCP servers reached (1 distinct)
          1  github

  0 subagent turns · 0 results abridged upstream · 1 ok

1 of 2 calls returned with no outcome the collector could establish; the agent's parser supplies no success signal.
```

The blank status on the second row is the collector's `unknown`, not a pending
call: the client recorded no outcome it could establish, and the closing line
says how many of those there were rather than filling one in.

The next step is the join: resolving what ran to the components the agent is
built from. `detect` performs it — it correlates the collected sessions against
an Agent BOM before it judges anything — but there is no longer a command that
prints the correlated view on its own. `stacktrace correlate` was withdrawn; the
correlation code stays, as the stage `detect` is built on.

Both `openaca` and `openaidr` resolve from PyPI now, like any other
dependency — no sibling checkout, no `[tool.uv.sources]` override. What still
gates a release is narrower: both float on `>=` during normal development but
must be pinned exactly (`==`) before a version is published, so a built wheel
names precisely what it was tested against.
`uv run pytest tests/test_release_readiness.py -m release_gate` checks that —
the `release-stacktrace` skill runs it as part of cutting a release.

## Adding a command

A command is a **pass-through** or it is **native**, never both — and a
pass-through never gains a flag of its own. See
[ADR-0021](docs/adrs/0021-two-command-kinds.md) for why.

- **Pass-through** — add one string to `PASSTHROUGH` in
  `src/stacktrace_cli/cli.py`, having decided it belongs. The loop registers
  OpenACA's own command object under that name; there is nothing else to
  write and nothing to keep in step with OpenACA.
- **Native** — write a Click command or group and `add_command` it, the way
  `remote` is. If it needs OpenACA it calls `openaca.core`
  ([ADR-0020](docs/adrs/0020-openaca-consumption-boundary.md)), and whatever
  it names there is added to the contract test in `tests/remote/`, because it
  now holds duplicated knowledge of an interface.

The section a command lands under in `--help` follows from which kind it is;
there is no second list to update.


## Development

`uv sync` and the four gates — `ruff check`, `ruff format --check`, `pyright`,
`pytest` — need nothing beside this repository. `openaca` resolves from PyPI at
the floor `pyproject.toml` declares. A sibling `../openaca` checkout is no
longer read: the interim `[tool.uv.sources]` path source retired at the 0.6.0
cutover.

## Cutover from `openaca remote`

`stacktrace remote` is the hosted-service client OpenACA is removing. The
behaviour, the payload and the Cloud endpoints are unchanged; only the code
that produces them moved. Machines already running `openaca remote sync
endpoint` need four steps each.

1. **Have the collector token to hand, or mint a new one.** A collector token
   cannot be recovered from the Cloud — it is returned once at issuance and
   only a hash and its last four characters are kept — so it has to come from
   wherever it was stored (an MDM secret store, a password manager). If it was
   not stored, mint a new one and deploy that; nothing goes down while you do,
   because the existing token keeps working until it is revoked.
2. **Configure this machine.** `stacktrace remote configure --token …`, or set
   `STACKTRACE_REMOTE_TOKEN` and `STACKTRACE_REMOTE_API_URL` for a scripted
   deployment.
3. **Repoint whatever schedules the sync** at `stacktrace remote sync
   endpoint`.
4. **Delete `~/.config/openaca/remote.toml`.** Once OpenACA has no `remote`
   command, that file is an unused plaintext credential sitting on disk.
   Nothing breaks if it stays, which is exactly why it will be forgotten.

### What takes care of itself

- **The asset converges.** Registration is idempotent on `(org, asset_type,
  external_id)` with the host name as `external_id`, so registering from here
  resolves to the same asset OpenACA registered — one extra round trip on the
  first run, no duplicate machine in the console.
- **Both commands may coexist during the overlap**, producing two BOM rows per
  sync. That is noise rather than corruption.

> These two statements, and the unrecoverability of a collector token in step
> 1, describe what the deployed Cloud does. They have **not** been confirmed
> against the running service for this release — they are read from the
> hosted side's design, which records what some revision implements rather
> than what production does today. Confirm them before an operator acts on
> step 1 in particular: discarding the only copy of a token on the strength of
> an unverified sentence leaves no recovery.

### What does not

- **A pending OpenACA spool is orphaned.** Its files are in
  `~/.local/state/openaca`, and this channel never reads them. Drain it by
  running the old command once before cutting over, or accept losing what it
  holds. Do not assume that is one sync's worth: the spool keeps one file per
  failed agent and accumulates across offline runs, so count the files first.
- **The MDM deploy scripts and the scheduled agent** do not exist here yet.

## Release obligations

Finishing the remote-sync work was **not** the same as being releasable, and
the gap was structural rather than a matter of polish: while `pyproject.toml`
carried a path source to a sibling checkout, `pip install stacktrace-cli` from
PyPI would have resolved an `openaca` without the consumption facade, and every
`remote` command would have failed at import.

All three are discharged, at `openaca` 0.6.0 — the first release carrying the
facade:

1. `[tool.uv.sources]` is gone; `uv.lock` resolves `openaca` from PyPI.
2. `.github/workflows/ci.yml` checks out this repository alone. The sibling
   checkout and its pinned-revision assertion went with the path source.
3. The floor is `openaca>=0.6.0`, which is where plan 005 Task 1's
   required-import probe first reports no missing name. Re-run that probe
   against any candidate release before moving the floor again; it passes only
   when it reports nothing missing.

One proof retired with them. The payload-equivalence test imported openaca's
`tools.remote` — the module 0.6.0 removed — so it cannot run against any
release that carries the facade. What it established is a fact about the
migration, not a property that a later release could re-check.
