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.
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
*.jsonlunder~/.claude/projectsand~/.codex/sessions. Neverauth.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.
scanshows 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/engineis 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/parsersis 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 anode_modulesbehind 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.
No comments yet
Be the first to share your take.