You are a different collaborator on every project. Your session history already knows.

Reads your local Claude Code / Codex history and shows you who you are, project by project. No account, no upload, no server. One HTML file on your machine.

License Node Runtime deps Tests Network calls

English · 简体中文


You have thousands of messages sitting in ~/.claude/projects. Not the polished ones — the ones you typed at 2am when something wouldn't build. Nobody, including you, has ever read them back.

Unprompted does. It finds the two days you were most unlike yourself, puts them side by side, and tells you what you never once said.

tier        full   L1 poles · L2 density · L3 zero values · L4 verdict materials
corpus      14 project(s) · 2068 utterance(s) · 31 day(s) · 53 project×day sample(s)
span        2026-07-02 → 2026-08-06

pole A      atlas-api / 2026-07-14   "The Delegator"
pole B      side-project / 2026-08-04   "The Cross-Examiner"
confidence  normal

largest gaps
  holding back      12.0×   A 0.24 · B 0.02
  why questions      4.6×   A 0.05 · B 0.23
  pushing ahead      3.9×   A 0.08 · B 0.30
  politeness         3.1×   A 0.19 · B 0.06

L2 desire density   closer=B · A 0.07 · B 0.31 · 4.4×

L3 zero values (2)
  courtesy: 0 — against commands: 128
  selfDoubt: 0 — against why questions: 105

L4 materials  4 gap(s) · 2 zero value(s) · 4 contradiction(s) · 5 peak(s) · 104 repetition(s)

report      ~/.unprompted/unprompted-2026-08-06-143012.html  (1.4 MB, self-contained)

That's the terminal digest. The HTML report is the actual thing — every number opens onto the verbatim messages behind it, with a session ID so you can go find the moment:

Install

One command, any agent

npx skills add Dreamaker-TA/unprompted -g

That installs it user-wide through the open Agent Skills ecosystem — the same command works for Claude Code, Codex, Cursor, OpenCode and 70+ other agents; the CLI asks which ones to hook up. A tool that reads your whole history belongs everywhere, which is what -g does — drop it to install into the current project only. Update later with npx skills update -g.

Zero tooling, Claude Code / Codex

# Claude Code — personal skills directory
git clone https://github.com/Dreamaker-TA/unprompted ~/.claude/skills/unprompted

# Codex — user-level skills directory
git clone https://github.com/Dreamaker-TA/unprompted ~/.agents/skills/unprompted

That's the whole install — open a fresh session and ask. bin/unprompted.mjs is a committed, dependency-free bundle: no npm install, no build step, nothing to update behind your back. Needs Node ≥ 20.19 and nothing else. Project-scoped instead of global? Clone into .claude/skills/unprompted (Claude Code) or .agents/skills/unprompted (Codex) inside the repo. Works the same in any harness that reads SKILL.md.

Then just ask

what do my Claude Code sessions say about me?

Claude scans, shows you what it found, asks whether to read everything or one project, writes the report, and opens it. Ask for your AI-use persona and it also types you on four measured axes — then hand-writes you a one-of-a-kind, self-contained persona card.

It speaks your language. The CLI prints English; everything the assistant builds on top of it follows the conversation. Ask in English, get an English write-up and card. 用中文问,就得到中文的解读和中文的人格卡片. Any other language works the same way — the numbers stay identical, the words adapt.

As a CLI

git clone https://github.com/Dreamaker-TA/unprompted && cd unprompted
node bin/unprompted.mjs scan       # what's in there
node bin/unprompted.mjs report --open

As a library

Not on npm — the repository is the package. Clone or submodule it and point your own toolchain at the TypeScript source:

import { analyzeLocal, writeReportHtml } from "./vendor/unprompted/src/index.js";

const { report } = await analyzeLocal(
  {},
  { dayBoundaryOffsetMinutes: -new Date().getTimezoneOffset() },
);

if (report.tier === "full") {
  console.log(report.l1.poles.map((p) => p.fallbackName));
}

const { path } = await writeReportHtml(report, { open: true });

Report is a discriminated union over four tiers, so a switch that forgets one fails to compile.

Commands

Command What it does
scan Projects, utterance counts, date ranges, and the full drop tally by rule. No analysis.
report Analyze and write a self-contained HTML report.
persona The same corpus as a four-letter MBTI-style parody type — numbers only, no quotes.
naming-payload Print the exact bytes optional LLM naming would send. Sends nothing.
name Send one naming request. Requires --confirm and a key from the environment.
scope     --project <name> (repeatable)  --from <YYYY-MM-DD>  --to <YYYY-MM-DD>
          --exclude-recent-days
output    --out <path>  --open  --no-html  --quiet  --json  --quotes N
sources   --claude-dir <path>  --codex-dir <path>  --tz-offset <minutes>

Omitting --project means all of them. node bin/unprompted.mjs --help prints everything.

What it measures

Four layers, each one refusing to exist without enough evidence.

L1 — two poles. Every project × day with ≥ 10 messages becomes a sample, scored on twelve dimensions: ten conservative dictionary hit-rates (commands, holding back, pushing ahead, why questions, politeness, praise, softeners, hard stops, we language, wanting) plus message length and short-message rate. The two most distant samples become the poles. Both get a locally computed name — no model involved.

Two poles instead of clusters is a deliberate choice: k-means on this data produces groups nobody recognizes, while "you on Tuesday vs. you on Friday" is immediately legible.

L2 — desire density. How much of your talking is about what you want. Appears only when the two poles differ by ≥ 2×.

L3 — zero values. What you never said, asserted only against something you said constantly: 128 commands, 0 courtesy. The contrast needs ≥ 10 hits, because the force comes from the denominator. This is the one layer that survives a thin corpus.

L4 — verdict materials. Contradictions (one message holding back, one pushing ahead, and the minutes between them), per-day peaks by z-score, and the phrase you've typed 104 times without noticing.

Four tiers, no error pages

Tier When Contains
full ≥ 6 samples L1 · L2 · L3 · L4
noPoles ≥ 40 messages, < 6 samples L3 · weakened L4
sparse < 40 messages L3 only
empty nothing eligible corpus summary

"Not enough data" is a result, not a failure. A thin history gets a short, honest report — never a padded one. See references/report-tiers.md.

The persona, for fun

persona folds the same twelve dimensions into four axes — how much you say per message, steps handed over vs intent declared, transactional vs warm, brake vs accelerator — and hands you a four-letter MBTI-style AI-use persona:

persona     INFP — The Wishful Minimalist
corpus      2 project(s) · 98 utterance(s) in scope

axes
  E/I  I 60%   avg length 40 · short-rate 0.52
  S/N  N 97%   cmd 0.02 vs why 0.11 + want 0.17
  T/F  F 79%   polite 0.08 · praise 0.02 · we 0.27 · mod 0.09
  J/P  P 79%   hold 0.03 + ban 0.04 vs push 0.28

It is a parody and it says so: every letter is a nickname for the numbers printed on its row, deterministic over the same corpus, and its output carries no quotes by construction. Run as a skill, the assistant turns it into a write-up and a one-of-a-kind self-contained HTML card — designed fresh every run, in the language you asked in:

Axes, midpoints, the sixteen types and the card rules live in references/persona.md.

Privacy

The differentiator, so here it is precisely.

  • Reads only *.jsonl under ~/.claude/projects and ~/.codex/sessions. Never auth.json, never credential stores, never config, never sqlite.
  • Zero network calls. No server, no telemetry, no analytics, no error reporting, no update check. The report HTML has no <script> tag, no CDN, and no remote fonts — it opens on a plane.
  • Nothing is stored. No cache, no database, no run history. The only artifacts are the report and the optional persona card you asked for.
  • Filtered content is never counted. Some messages are dropped on content-safety grounds before analysis, and the number removed is never shown. There is no field for it in the data contract, so it cannot leak into a UI.
  • scan shows its work. The full drop tally by rule, so the read is auditable instead of promised.

Two exceptions, both requiring an explicit action from you:

LLM naming is off by default. It needs your key, your endpoint, and --confirm; you can print the exact payload first. It carries dimension numbers and up to five ≤80-character quotes per pole — no session IDs, no timestamps, no project names, no paths, because the payload type has no field for them. Every failure falls back silently to the local names.

The digest, when read through an assistant. Anything the command prints lands in that assistant's context. Which is why report prints no verbatim quotes by default — your actual words stay in the local HTML. --quotes N opts in; --quiet prints only the file path. persona goes further: its output has no field for quotes at all.

Full detail in references/privacy.md.

Architecture

One pipeline. The Node layer only supplies input and writes output.

~/.claude/projects        src/parsers       whitelist → Utterance[]
~/.codex/sessions             ↓
                          src/engine        pure statistics → Report (4 tiers)
                              ↓                            → Persona (parody layer)
                          src/report-html   Report → self-contained HTML
                              ↓
                          src/naming        optional, opt-in, BYO key
  • src/engine is a pure function. Same input, same output, always. No randomness (ties break on session ID), no clock, no timezone read, no DOM. That is what makes golden-fixture regression meaningful — and it's why the same engine runs unchanged in a browser.
  • The whitelist lives in exactly one place. src/parsers is the only code that decides what counts as a message.
  • Content filtering is step one. Everything downstream is computed on data that has already been filtered, so no later stage can accidentally surface it.
  • Zero runtime dependencies, by policy. A tool you drop into ~/.claude/skills/ shouldn't drag a node_modules behind it.

Comments in src/ cite the design specs as 04 §2.1, 03 §二, and so on — see SPEC.md for what those numbers refer to.

Development

npm install
npm run typecheck
npm test          # 362 tests
npm run build     # regenerate bin/unprompted.mjs
npm run verify    # lint + typecheck + test + build + bundle-freshness

bin/unprompted.mjs is a committed build artifact — that's what buys the zero-install property. Change anything in src/ and you must rebuild; npm run verify fails if the bundle has drifted from the source.

The test suite is where the real specification lives: adversarial fixtures for every prompt-injection and malformed-record shape the parsers are supposed to reject, golden snapshots over the full drop tally, and content-filter cases. A snapshot change isn't a failure — an unexplainable snapshot change is.

Also

Prompting habits are a young enough field that "here's what you actually do" beats "here's what you should do". If you find a dimension that reads as a horoscope rather than an observation, that's a bug worth filing.

License

MIT — see LICENSE.