New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@saidso/cli

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@saidso/cli

See what is going on in a Claude Code session: the map of the code as it is built, what was asked, said and changed, and where they part ways.

latest
Source
npmnpm
Version
0.1.3
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

Saidso

See what's going on in a Claude Code session. Saidso watches the session without taking part and shows you:

  • the map: the code as it's actually being built, derived from what's on disk at every checkpoint, plus the as-built view of main, open PRs and branches;
  • the streams: each unit of work as what was asked, what the agent said it did and what changed, in order and by who (main agent, subagents, tasks, your own edits);
  • where to look: the places where the agent's account and the code part ways: claims that don't hold, changes nobody mentioned, quiet edits to tests and hooks.

The checks are there to keep your understanding honest: the agent's summary is how you'd otherwise learn what happened, so Saidso marks where it holds and where you need the diff. See SPEC.md for the design and CLAUDE.md for working conventions.

Status: Phases 1–3 (capture, claims and reconciliation, component map) are done; Phase 5 as-built S1 (from git and CI, published by GitHub Actions) and S2 (per PR) and S2.5 (the live As-built view in the app) are done, S3 (claims from a PR's evidence block), S4 (cloud sessions pushed to an inbox repo and reconciled there) and S5 (one daemon for local and inbox sessions) are built. Each window's report and commit messages are turned into claims by the extractor and checked against the tree diff, the tool ledger and test runs (VERIFIED, CONTRADICTED, NO_EVIDENCE, UNVERIFIABLE), alongside TAMPER and SILENT_CHANGE.

Use

From npm (Linux and WSL2, Node 22 or later), once a version is published:

npm install --global @saidso/cli
saidso install                            # settings hooks + collector scripts (use --dry-run to preview)
saidso daemon                             # web UI on http://127.0.0.1:4317/

From this repo:

pnpm install && pnpm build
node packages/cli/dist/bin.js install     # settings hooks + collector scripts (use --dry-run to preview)
node packages/cli/dist/bin.js daemon      # ingest, observe, extract, reconcile; web UI on http://127.0.0.1:4317/
node packages/cli/dist/bin.js report      # per-window observations and flags (--open, --json, --session)
node packages/cli/dist/bin.js doctor
node packages/cli/dist/bin.js uninstall   # restores ~/.claude/settings.json exactly when nothing else changed

node scripts/pack-npm.mjs builds the npm package (@saidso/cli, which installs the saidso and saidsod commands, with the other workspace packages and the tree-sitter WASM grammars bundled inside) after pnpm build, and scripts/smoke-npm.sh <tarball> installs it into a throwaway prefix and checks it runs. Pushing a v<version> tag that matches packages/cli/package.json publishes it from .github/workflows/release.yml through npm trusted publishing (no provenance while the repo is private) (this repo and release.yml are @saidso/cli's trusted publisher).

report catches up on the spool (and extracts claims) itself when the daemon isn't running.

As-built (no hooks needed)

node packages/cli/dist/bin.js as-built                         # Markdown to stdout: map + build log of HEAD's first-parent history
node packages/cli/dist/bin.js as-built --base main --out out   # a PR branch's own commits, as AS-BUILT.md, as-built.json, as-built.html
node packages/cli/dist/bin.js as-built --checks-from github --out out --publish   # CI evidence from check runs; commit to saidso/as-built
node packages/cli/dist/bin.js as-built --checks-from github --view                # open the as-built page in your browser
node packages/cli/dist/bin.js as-built --pr 12 --checks-from github               # one PR: what it builds, its evidence, and colliding PRs
node packages/cli/dist/bin.js as-built --follow                                   # follow this repo in the daemon's live view: http://127.0.0.1:4317/as-built

What exists in the code and how well CI proves it, from commits and CI alone (SPEC.md Phase 5, S1). Components are directories two levels deep; each is evidenced (a test job passed after its last change), touched (no passing test job since), or flagged (a test job failed on its last change). Test jobs are check runs matching --test-jobs (default test|vitest|jest|pytest). --checks-from github reads GITHUB_TOKEN and GITHUB_REPOSITORY (or the origin remote); file:<path> reads the same data from JSON. --publish writes only the orphan saidso/as-built branch, never HEAD, the index or the working tree. On this repo, .github/workflows/saidso.yml runs it after CI on main; the result is on the saidso/as-built branch. After CI on a pull request, the same workflow posts a neutral Saidso check and one PR comment it keeps up to date (S2), reading the PR's commits without running them. With the daemon running, as-built --follow adds the repo to the live As-built view at /as-built (S2.5): main's published versions on a timeline, open PRs and local branches laid over the map, refreshed every minute. It fetches only into refs/saidso/ and uses your GITHUB_TOKEN or gh auth token for PRs.

Evidence block (S3)

A PR description can say what was run, so the PR's claims are checked against CI with no hooks and no model calls. Put this in the PR body (for agents, once in the project's instructions):

```saidso-evidence
- step: unit tests
  command: pnpm test
  exit_code: 0
  metric: "142 passed"
```

Only step, command, exit_code and metric are allowed; raw output is rejected. Each test command is checked against CI's test jobs on the PR head (VERIFIED, CONTRADICTED, NO_EVIDENCE); other commands are UNVERIFIABLE until the session's tool ledger is available. Results appear under Claims in the Saidso check and comment, and the change moves from the ci tier to claimed. A PR without a block is never flagged for it.

Cloud sessions via the inbox (S4)

Cloud sessions can't reach a daemon on your machine, so they go through a private repo you own. Each session is one snapshot commit on sessions/<login>/<session>, with its events redacted, its checkpoint trees (untracked files' contents replaced by a placeholder), and the commits it saw. The inbox's workflow reconciles it with the same rules and posts a Saidso observed commit status and one comment, kept up to date, to the session's PR. (Not a check run: GitHub only lets Apps create those, and the inbox uses a personal token.) Only verdicts and their reasons leave the inbox.

node packages/cli/dist/bin.js inbox init --repo you/saidso-inbox     # private repo + workflow (needs gh)
node packages/cli/dist/bin.js inbox reconcile --repo <inbox clone> --ref sessions/<login>/<session> [--post] [--out dir]

The inbox workflow needs a SAIDSO_TOKEN secret, a fine-grained token with Contents read on Saidso's repo and Commit statuses + Pull requests read and write on the repos your sessions work in, and optionally ANTHROPIC_API_KEY for claims. To see cloud sessions in the app next to local ones (S5), node packages/cli/dist/bin.js inbox follow --repo you/saidso-inbox: the daemon fetches it every minute (SAIDSO_INBOX_POLL) into its own state directory and shows each session under the local checkout of the same GitHub repo. To record cloud sessions, add this to the cloud environment's setup script (Project settings → Cloud environment → the gear beside it → Setup script), with the inbox among the environment's repositories:

npm i -g @saidso/cli && saidso install --cloud

--cloud installs the hooks into the container's own ~/.claude/settings.json along with the pusher, so it works however many repositories a session has. A plain saidso install never pushes. Each push's outcome is one line in ~/.local/state/saidso/push/push.log (pushed, unchanged, or why it stopped, with git's error), which is the place to look when a session doesn't reach the inbox.

Alternatively, a session with one repository runs the hooks in that repo's .claude/settings.json (cloud sessions don't install plugins a repo enables there), so you can register the collector hooks there directly, the same entries as packages/collector/hooks/hooks.json with ${CLAUDE_PLUGIN_ROOT} replaced by the collector's path in the checkout:

{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/packages/collector/bin/saidso-plugin checkpoint", "async": true }] }] } }

This repo does that in its own .claude/settings.json. The pusher reaches the inbox through the session's git credentials, so the inbox has to be one of the session's repositories. The hooks (through saidso-plugin) only run in cloud sessions (CLAUDE_CODE_REMOTE=true) and stands down wherever saidso install has run (~/.local/state/saidso/install-state.json, which also covers Remote Control sessions on your machine), so each event is spooled once. On Stop and SessionEnd, after the checkpoint, the session's snapshot goes to the inbox (packages/collector/cloud/saidso-push, run by the checkpoint wherever the pusher sits beside it): events redacted and truncated in the container, untracked files' contents replaced by a placeholder, built in Saidso's own scratch repo so the session's repo isn't written to. The inbox defaults to <origin owner>/saidso-inbox on the same host as origin (SAIDSO_INBOX takes owner/name or a URL) and the branch's login to the inbox's owner (SAIDSO_LOGIN). Each push replaces the session's one snapshot and keeps every event's spool name, so ingest takes each event once however often it's pushed or followed.

Claim extraction

The extractor is the only non-deterministic step. Its output is validated (every claim's quote must appear verbatim in the report), cached by sha256(version + model + input) and stored; the reconciler only reads stored claims, so the same events and cache always give the same verdicts.

VariableDefaultMeaning
SAIDSO_EXTRACTORclaude-cliclaude-cli (claude -p with hooks disabled and SAIDSO_DISABLE=1), anthropic-api (needs ANTHROPIC_API_KEY), fixture:<file.json> or none
SAIDSO_EXTRACTOR_MODELhaiku / claude-haiku-5-5Model for the chosen backend
SAIDSO_EXTRACT_CONCURRENCY1Background extraction workers (1–2)
SAIDSO_PORT4317HTTP port

A failed extraction gives the window one UNVERIFIABLE "extraction failed" verdict; observations, TAMPER and SILENT_CHANGE are unaffected.

Web UI and API

saidso daemon serves the UI and JSON API on 127.0.0.1:4317. Every view reads the same three streams: what was instructed, what the agent claimed, and what Saidso observed.

  • Projects (#/): the home page. One card per repo with a small map of what changed since you last looked, a live dot while a session runs, and what's worth a look; beside them, every open flag and every running session. Before anything is recorded it shows a Get started checklist that updates itself; the guide, a worked example of one turn, is always behind ?.
  • Project map (#/p/<repo>): the project as it stands, by dependency depth, glowing by how recently any session changed each component. Every session's turns run along the foot on their own lanes: point at one to light what it touched, click to open it.
  • Turn (#/t/<checkpoint>): what was asked, what the agent said and what git saw change, side by side, with the map dimmed to what the turn touched and anything worth a look one click from its evidence.
  • Sessions (#/sessions, or a project's Sessions tab): each session as a strip of its windows, with claim dots, lines changed, test runs and commits. A project's Ledger tab (#/r/<repo>/ledger) is the components × sessions ledger.
  • Streams (#/s/<id>): the session's windows side by side, each claim dropping a line to the evidence under it, and changes nobody mentioned gathered under "unclaimed".
  • Window (#/s/<id>/w/<checkpoint>): one window's instruction, claims against what was observed, what went unsaid, and the tool calls behind it. CONTRADICTED, TAMPER and NO_EVIDENCE flags can be acknowledged or dismissed with a note.
  • Session map (#/s/<id>/map/<checkpoint>): the files by dependency depth at that checkpoint, with tests and config as chips underneath; ← and → step through checkpoints.
  • As-built (/as-built): the live as-built of followed repos.

⌘K (Ctrl K) or / jumps to any project, session or file; Esc closes a panel; ? opens the guide.

A claim's dot is filled when it holds, hollow when it can't be checked, ringed orange when it doesn't hold and dashed orange when there's no evidence. Updates arrive live over SSE (/api/stream).

saidso daemon --bind <addr> listens on another address and requires the bearer token in ~/.config/saidso/token (created on first use). Open the UI as http://<addr>:4317/#token=<token>.

Running Saidso from this checkout, scripts/restart-daemon.sh pulls main, builds, refreshes the installed hooks, stops the running daemon and starts the new one detached (log in ~/.local/state/saidso/daemon.log), returning once /healthz answers. --no-pull rebuilds and restarts whatever is checked out.

Upgrading from Lookout

Saidso was called Lookout. An existing install carries over without steps of its own:

  • State. saidso install and the daemon's start move ~/.local/state/lookout and ~/.config/lookout to their saidso names, once, when the new directory doesn't exist yet; lookout.db becomes saidso.db. saidso install also moves ~/.local/share/lookout, since it writes the new collector scripts straight after. Both refuse while a daemon started as Lookout is still running. The old hook commands become forwarding scripts so sessions that were already running keep reporting until they restart; saidso uninstall removes them.
  • Hooks. saidso install replaces Lookout's hook entries (.../lookout/bin/lookout-*) with its own. The record of the settings from before the first install moves with the state, so saidso uninstall still restores them byte for byte.
  • Git. New pins go under refs/saidso/; existing refs/lookout/<session>/* pins stay until retention prunes their session, which covers both prefixes. As-built publishes only to saidso/as-built; the workflow's first publish carries on from lookout/as-built, and the live view reads lookout/as-built until saidso/as-built exists. The PR comment updates an earlier one carrying either marker.

Layout

PackageWhat's in it
packages/collectorsaidso-hook and saidso-checkpoint, POSIX sh hot-path scripts
packages/coreModel, hook shapes, ingest, redaction, git access, observer and reconcile rules (src/rules/), claim extraction (src/extract/), verdicts, report, API read models, component map (src/map/), as-built (src/as-built/)
packages/daemonsaidsod: watches the spool, runs ingest + observe + reconcile passes, the extraction queue, and the Fastify API with SSE
packages/webVite + TypeScript UI served by the daemon
packages/clisaidso install / uninstall / daemon / status / report / doctor / as-built

Hook payload shapes for the installed Claude Code version are recorded in docs/hook-shapes.md.

Develop

pnpm typecheck and pnpm test run through lefthook on commit and push, and in CI.

FAQs

Package last updated on 22 Oct 2026

Related posts