# polymath-society

> **Proprietary — All Rights Reserved (source-available, not open source).** You may run the unmodified npm package to analyze your own data; copying, modifying, redistributing, reverse-engineering the bundles, or using its methods to build a competing product is prohibited. See [LICENSE](./LICENSE).


A **local app** that shows you who you are from your own **chat history**
(ChatGPT / claude.ai / Notion / Obsidian) and **Claude Code / Codex / Cursor**
agent logs — a full personal report with verbatim receipts, plus deterministic
coding metrics: parallelism, flow-state, throughput, per-project contributions.

Three things, in one package:

1. **Deterministic coding metrics** — computed in-process from the JSONL on
   your disk. No model call, no upload. Facts, not judgments. (Instant and
   free.)
2. **Local AI analysis** *(opt-in, `--grade`)* — the full chat analysis and
   report over your approved chat/notes exports, by invoking the **local
   `claude -p` / `codex` binary you're already logged into**. No API key; it
   spends your own subscription. Your prompts never leave the machine. The
   coding metrics themselves stay fully deterministic (no AI reads your
   individual coding sessions unless you pass `--code-ai`).
3. **A local web UI** *(`serve`)* — your report served on a free port: the
   public portrait once analysis finishes (an honest progress view until
   then), the full coding report linked from it, and Share in the header.

---

## What it reads

- `~/.claude/projects/**/*.jsonl` — Claude Code sessions
- `~/.codex/sessions/**/*.jsonl` — Codex sessions

(Override with `CLAUDE_PROJECTS_DIR` / `CODEX_SESSIONS_DIR` env vars.)

It separates **your real interactive sessions** from headless agent runs,
auth-ping sessions, and machine-assembled mega-prompts (the `klass` /
`klassReason` on every parsed session is transparent and auditable), de-dupes
resume/fork copies, and then computes everything below over the canonical set.

## What it computes

| Metric | What it is |
| --- | --- |
| **sessions** | per-session structural facts: duration, active time, turns, tool counts, models, tokens, modes, renames |
| **parallelism** | sweep-line concurrency across sessions — max concurrent, minutes spent at each level, per-day overlap, top windows |
| **day-chart** | per-day Gantt of every conversation's active spans (clipped to local midnight) |
| **flow** | every 10-minute slot classified in-flow / active-pause / away (rapid rhythm + dictation beat the gaps) |
| **throughput** | words you actually *typed* per day (pasted code stripped) → a 1–11 score, plus a proven ceiling |
| **projects** | GitHub-style per-project days / hours / range |
| **aggregate** | average throughput over substantial days + flow stats (work window, % in flow, when flow peaks) |

---

## Install

```bash
npm install -g polymath-society
# or run the CLI without installing:
npx polymath-society
```

(The CLI installs as `polymath-society`; `polymath-analyzer` is the same
binary. Already installed? `npm install -g polymath-society@latest` updates
it — in an interactive terminal a newer version also offers to update itself
at launch.)

## If you are an AI agent (Claude Code, Codex) setting this up for someone

People often paste this README to their coding agent and say "run this for me."
If that agent is you, this section is your runbook. Follow it in order.

**The one rule you cannot route around: including at least one chat or notes
source is COMPULSORY.** The analysis is built on the person's chat history
(ChatGPT, claude.ai, or Notion export); coding logs alone are not enough. The
wizard will not move past the chat + notes step with nothing included, and `serve --grade`
refuses to start on a machine with no approved chat source. Do not look for a
bypass; get the person an export instead.

1. **Install or self-update, then launch the wizard** in an interactive
   terminal, keeping stdin open (the wizard is prompt-driven; closing stdin
   with no sources makes it exit on purpose). Run the install line even when
   `polymath-society` is already on the machine: it upgrades an old install to
   the latest published version, and agent-driven runs never see the
   interactive auto-update prompt (it is TTY-gated), so skipping this step
   strands the person on whatever version they installed months ago:

   ```bash
   npm install -g polymath-society@latest
   polymath-society
   ```

2. **Get a chat export.** You cannot request one yourself (it needs the
   person's login), so send them to the right button and wait. Fastest first:

   | Source | Where | Arrives |
   | --- | --- | --- |
   | claude.ai | https://claude.ai/settings/data-privacy-controls → Privacy settings → Export data | immediately, link by email |
   | Notion (optional) | https://www.notion.so/settings → Settings → General → Export all workspace content | ~15 min |
   | ChatGPT | https://chatgpt.com/#settings/DataControls → Settings → Data controls → Export data | a few hours to ~2 days, link by email |
   | Obsidian | no export needed; point the wizard at the local vault folder | instant |

   The claude.ai export usually lands while the wizard is still open. Exports
   download to `~/Downloads`; the wizard watches that folder live and lists
   every export it finds. If the file is elsewhere, type its folder path at
   the chat + notes prompt. If the export has not arrived yet (ChatGPT can
   take up to two days), Ctrl-C is a supported stopping point: their email,
   LinkedIn and other links are already saved and come back as Enter-defaults,
   and the export reminder still reaches them. The source picks are the only
   thing not yet written, so re-running `polymath-society` when the export
   lands just replays those first few prompts.

3. **Answer the wizard's prompts on the person's behalf only with their
   consent.** Enter accepts the sensible default at every prompt (Enter at the
   source picker includes everything found), with ONE exception: the LinkedIn
   URL step requires the person's profile URL (a bare handle also works;
   Enter only keeps a previously saved value). Ask the person for it rather
   than inventing one. Consent about which sources to analyze belongs to the
   person; relay the question rather than guessing.

4. **Let it run — and know what "done" looks like, because it is NOT the
   first link.** `your reports are live → http://localhost:<port>` prints
   within the first minute or two, when the local server starts: it means the
   page is watchable, not that the analysis is finished. Do not tell the
   person their analysis is complete when you see it. What to expect:

   - The deterministic coding metrics land in the first minutes. The AI
     stages (tagging every conversation, grading, the report tail) are the
     long part and run for HOURS of wall-clock time. A full run takes roughly
     90% of one 5-hour window on the $200 Claude or Codex plan. If 60% of the
     current 5-hour window is used, it pauses, so your own work is never
     interrupted, and it resumes automatically in the next 5-hour window, so
     a full run usually spans two. Ending a run and re-running the command
     resumes progress.
   - While it runs, the report page shows a progress bar and the current
     stage ("still being made"), never a finished report. If the page
     instead shows report tabs with empty grades, the install is an old
     version — run step 1's install line again; the re-run resumes.
   - Keep the `polymath-society` process alive (leave the terminal open, or
     run it so it survives your own turn ending). Killing it loses nothing —
     a re-run resumes from disk — but nothing grades while it is dead.
   - Done is the CLI's completion summary at the end of the run (with a ⚠️
     block when stages failed), and the report page rendering the finished
     report instead of the progress view. Only then tell the person their
     report is ready.

5. **If anything stops** (reboot, closed laptop, a failed stage in the ⚠️
   block), re-running resumes from what is on disk and redoes only the missing
   work:

   ```bash
   polymath-society serve --grade
   ```

6. **One failure re-running cannot fix: a rejected login.** If the ⚠️ block
   says the Claude or Codex login was rejected (401 / expired / revoked), the
   stored credential is dead server-side and every re-run fails identically.
   **You cannot fix this yourself** — logging in is interactive and belongs to
   the person. Stop, tell them exactly this, and wait:

   - Claude: run `claude` in a terminal and log in (inside a running `claude`,
     `/login`)
   - Codex: run `codex login`

   Then re-run `polymath-society serve --grade`; finished stages are kept.

## CLI

```bash
polymath-society                 # first run: the setup wizard; later runs resume
polymath-society --json          # full deterministic profile JSON to stdout
polymath-society --out p.json    # write full profile JSON to a file

polymath-society projects        # just the projects/contributions view (JSON)
polymath-society flow            # just the per-day flow slots + totals (JSON)
polymath-society aggregate       # just the corpus aggregate (JSON)

polymath-society grade           # OPT-IN: AI-grade your sessions on your OWN
                                 #   Claude Code / Codex login (no API key)
polymath-society serve           # scrape → (optionally --grade) → open the web UI
polymath-society serve --grade   # …plus the full chat analysis and report
```

Flags: `--no-codex` · `--idle <min>` · `--tz <IANA>` · `--span <hours>` ·
`--json` · `--out <file>` · `--grade` · `--adapter <cli|codex>` · `--model <id>` ·
`--max-sessions <n>` · `--port <n>` · `--share-url <url>` · `--open`

Example summary:

```
========== DETERMINISTIC CODING PROFILE ==========
timezone America/Los_Angeles · idle-gap 5m
sessions: 106 interactive (canonical)  ·  1101 agent/excluded  ·  19 resume-copies dropped
active work time: 147.5h   tool calls: 16,017   subagents: 41   queue-ahead: 2271

PARALLELISM
  max concurrent sessions: 6

THROUGHPUT
  proven ceiling (≥3 days): 10/11
  AVG over substantial days: 7/11 — 5960 words/day over 30/50 days

FLOW
  work window: 12:30pm–6:40pm  over 47 active days
  in flow: 63% of active time  ·  86.5h total (1.8h/day)

PROJECTS
  4 projects · 37 active days · 102h
```

## The local app (`serve`)

```bash
polymath-society serve            # deterministic only — instant, free
polymath-society serve --grade    # + the full chat analysis and report (spends
                                  #   your subscription; the coding metrics
                                  #   themselves stay deterministic)
polymath-society serve --grade --open --port 8765
```

`serve` scrapes `~/.claude/projects` + `~/.codex/sessions`, optionally grades,
starts a tiny zero-dependency HTTP server on a free port, and prints the
clickable `http://localhost:<port>` URL. All analysis lives in the data home
`~/.polymath-society/data` (never the current directory; override with
`POLYMATH_DATA_DIR`) — re-run from anywhere and it resumes (already-graded
work is skipped) instead of starting from scratch. While a `--grade` run is
still working, the page shows a progress bar and the current stage; once the
analysis finishes it renders the report — the public portrait on top, with
sign-in and Share in the header, and the full coding report (warm palette,
contributions heatmap, flow stats, throughput scatter, criterion cards with
verbatim receipts) linked from it.

The server exposes:

| Route | What |
| --- | --- |
| `GET /` | the bundled static UI |
| `GET /profile.json` | the computed profile, organized into the share-contract sections |
| `GET /meta.json` | `{ version, shareUrl }` for the share bar |
| `POST /share` | proxies the chosen sections to the share endpoint (server-side — no browser CORS) |

### Local LLM grading (no API key)

Grading is **opt-in** because it spends your tokens; the deterministic metrics
stay instant and free. With `--grade`, the package invokes the **local
`claude -p` (or `codex`) binary you're already logged into** — billed to your
Claude / ChatGPT subscription, never an API key. On startup it detects whether a
backend is installed *and* logged in; if not, it prints the exact fix (e.g. *run
`claude` to log in*) instead of failing obscurely, and `serve` falls back to
deterministic-only.

```bash
polymath-society grade                       # grade + write graded.json, print means
polymath-society grade --adapter codex       # pin the Codex backend
polymath-society grade --max-sessions 20     # cap the token/time budget
```

The five graded criteria — **abstraction, taste, delegation, expertise,
frontier** — are ported from the main Polymath pipeline (same rubric, 1–11 ladder
with verbatim quotes, per-conversation ceiling 10). Grading plugs into the
`analyze()` `gradeProvider` seam (see Library below).

### Share — per-section selection

Every section in the UI has a checkbox; tick the ones you want and hit **Share
selected** in the footer bar. The browser sends only the ticked keys to the local
server, which forwards exactly this contract to the share endpoint:

```http
POST <shareUrl>
Content-Type: application/json

{
  "source": "coding-analyzer",
  "version": "<pkg version>",
  "name": "<optional user-provided name>",
  "sharedAt": "<ISO8601>",
  "sections": { "<key>": <that section's data> }
}
```

`key ∈ { howYouWork, snapshot, workstyle, parallelism, lixiThroughput, topDays,
abstraction, taste, delegation, expertise, frontier, operate }` — only ticked
keys appear in `sections`. The endpoint defaults to
`https://polymath-society.com/api/coding-share` and is overridable with
`--share-url <url>` or `PS_SHARE_URL`. The local server does the POST
server-side, so the browser never hits CORS.

### How the bundled UI is produced / rebuilt

The viewer sources live in `web/` (React, `entry.tsx`, sharing the repo's
report components). `npm run build` runs the TypeScript bundle then
`web/build-web.mjs`, which **esbuild**-bundles the viewer → `dist/web/app.js`,
shipped inside the published package and served by the local server. To
rebuild only the UI: `npm run build:web`.

## Library

```ts
import { analyze } from "polymath-society";

const profile = await analyze({
  codex: true,            // include ~/.codex/sessions (default)
  idleGapMin: 5,          // gap that breaks a session into active intervals
  tz: "America/Los_Angeles",
  spanHourMin: 4,         // "substantial day" threshold for the throughput avg
});

console.log(profile.concurrency.maxConcurrency);
console.log(profile.throughput.ceiling);          // 1–11, proven on ≥3 days
console.log(profile.flow.totals.flowFraction);
console.log(profile.projects.projects);
```

Every building block is exported too, so you can compose your own pipeline:

```ts
import {
  readAllSessions, toSessionRecords, buildConcurrency, buildDayChart,
  markDuplicates, computeFlow, computeProjects, computeAggregate,
  proseWords, throughputScore, throughputCeiling,
} from "polymath-society";

const raw = await readAllSessions();
const interactive = raw.filter((s) => s.klass === "interactive");
const records = toSessionRecords(interactive);
await markDuplicates(records);
const canon = records.filter((r) => !r.duplicateOf);
const conc = buildConcurrency(interactive, canon);
const flow = await computeFlow(canon);
// …
```

### Local LLM grading via `analyze()`'s `gradeProvider`

`analyze()` is deterministic by default and never calls a model. The grading
seam is the optional `gradeProvider`: pass one and `analyze()` attaches a
`graded` block of per-criterion means. The package ships `LocalGradeProvider`,
which implements that seam by grading on your **local Claude Code / Codex** CLI:

```ts
import { analyze, LocalGradeProvider } from "polymath-society";

const provider = new LocalGradeProvider({
  adapter: "cli",       // or "codex"; omit for auto (first installed + logged-in)
  model: "sonnet",      // grading model (cli only; codex ignores it)
  maxSessions: 20,      // cap the token/time budget
});

const profile = await analyze({ gradeProvider: provider });
profile.graded;         // { abstraction: { mean, n }, taste: { mean, n }, … }
provider.detail();      // richer per-criterion distributions + per-session takes (UI)
```

Or supply your own `gradeProvider` (any object with `criteriaMean(records)`) to
fold in grades from a different pipeline.

```ts
const profile = await analyze({
  gradeProvider: { criteriaMean: () => ({ taste: { mean: 8.2, n: 40 } }) },
});
```

### Serving programmatically

```ts
import { analyze, LocalGradeProvider, buildWebProfile, startServer } from "polymath-society";

const provider = new LocalGradeProvider();
const profile = await analyze({ gradeProvider: provider });
const web = buildWebProfile(profile, provider.detail());
const { url } = await startServer({ profile: web, version: "0.2.0", shareUrl: "https://…/api/coding-share" });
console.log(url);
```

## Project aliases

A session's project is its working directory, so the same project shows up under
several paths (parent-folder runs, old names). Pass `aliases` to canonicalize:

```ts
await analyze({
  aliases: [
    { name: "Polymath", match: ["/polymath-society", "/polymath"], note: "merged old name" },
  ],
});
```

Without aliases, the leaf directory name is used.

## Configuration

| Option (lib) | Flag (CLI) | Env | Default |
| --- | --- | --- | --- |
| `codex` | `--no-codex` | | `true` |
| `idleGapMin` | `--idle` | | `5` |
| `tz` | `--tz` | `CODING_TZ` | `America/Los_Angeles` |
| `spanHourMin` | `--span` | | `4` |
| — | `--port` | | free OS-assigned |
| — | `--share-url` | `PS_SHARE_URL` | `http://localhost:3100/api/coding-share` |
| — | `--adapter` | `CODING_ANALYZER_ADAPTER` | auto (`cli` → `codex`) |
| — | — | `CLAUDE_BIN` / `CODEX_BIN` | resolved from PATH |
| — | — | `CLAUDE_PROJECTS_DIR` | `~/.claude/projects` |
| — | — | `CODEX_SESSIONS_DIR` | `~/.codex/sessions` |
| — | — | `PS_SUPABASE_URL=""` | disables every central call (run receipts, calibration, sign-in, share) |
| — | — | `POLYMATH_NO_UPDATE_CHECK=1` | skips the npm version check entirely |

## Privacy

**The claim, stated exactly: your transcripts are analyzed on your machine and
are never uploaded to Polymath.** Grading runs by invoking the local `claude -p`
/ `codex exec` binary you're already logged into, so your prompts go from your
disk to your own subscription and nowhere else. That is the guarantee worth
making, and it is the whole of it — everything else is written out below rather
than summarized as "nothing leaves your machine", which was the old wording here
and was not true.

### Secrets are stripped before a model sees them

Agent logs are full of credentials nobody meant to save: a key pasted into a
prompt, an agent that ran `cat .env`, a connection string in a tool result. The
grader reads transcripts and quotes them verbatim as report "receipts", so a key
in a log could otherwise end up in a section you later share.

Every transcript passes through one redaction gate
(`redactSecrets`) before it reaches a model or an artifact — at the invocation
boundary and again at materialization. It strips provider API keys (Anthropic,
OpenAI, AWS, GitHub, Google, Slack, Stripe, npm, SendGrid, HuggingFace, GitLab,
Linear, Supabase), JWTs and bearer tokens, PEM private-key blocks, passwords
inside connection URLs, and any `*SECRET*`/`*TOKEN*`/`*API_KEY*`/`*PASSWORD*`
assignment, replacing each with `[redacted:<kind>]`. References like
`process.env.GITHUB_TOKEN` are left alone.

It is a filter, not a proof. Redaction is pattern-based and cannot recognize a
credential with no recognizable shape, so **rotate anything you know is sitting
in your logs** — that advice does not stop being good because there's a filter.

If you ran a version before 0.2.32, artifacts already on your disk (materialized
transcripts, grade files with verbatim quotes) can still contain credentials —
nothing re-reads an already-graded session, so they would never be cleaned on
their own. The first run after upgrading sweeps them once and leaves a stamp so
it never scans again. Your own imported files are never rewritten.

### What leaves your machine

| Call | When | What's in it | Turn it off |
| --- | --- | --- | --- |
| **Run receipt** → Supabase | automatically, every `--grade` run | `begin`: platform (`darwin`), `"npm-package"`, which pipeline, stage count. Per stage: its script name, duration, ok/failed, and the sha256 of the file it wrote. `close`: one commitment hash over the artifact hashes. **No transcript content, no report content, no file paths, no identifiers.** | `PS_SUPABASE_URL=""` |
| **Version check** → registry.npmjs.org | automatically, at most twice a day | a plain GET for the latest published version. Nothing is sent about you. | `POLYMATH_NO_UPDATE_CHECK=1` |
| **Usage check** → api.anthropic.com | automatically while a `--grade` run is working, at most once a minute | your own Claude sign-in token (already on this machine — it is how the analysis runs), sent only to Anthropic, who issued it, asking how full your 5-hour window is so the run can step aside before taking your share. Nothing else is in it, and it is read-only. | `POLYMATH_NO_USAGE_PROBE=1` (the ceiling then runs on an on-disk estimate) |
| **Usage check** → chatgpt.com | automatically while a stage is running on the Codex backend, at most once a minute | your own Codex sign-in token (already on this machine), sent only to OpenAI, who issued it, asking how full your Codex usage window is so the run can step aside before taking your share. Nothing else is in it, and it is read-only. | `POLYMATH_NO_USAGE_PROBE=1` (the ceiling then reads the usage figures Codex already writes into its own local logs) |
| **Calibration lookup** → Supabase | automatically, when the local UI loads | a read of the published percentile bands. Nothing is sent about you. | `PS_SUPABASE_URL=""` |
| **`POST /share`** | only when you tick sections and click Share | exactly the sections you ticked | don't share |
| **Sign-in** | only if you sign in | the standard OAuth exchange | don't sign in |
| **Section feedback** | only when you submit feedback on a section | that section's text plus your note, after the local redaction preview | don't submit |
| **Report feedback box** | only when you type in a report section's feedback box and press Send | your note, plus which report and section it was about | don't press Send |
| **Error reports** | only if you said yes to sending error reports, and only when the run crashes or a stage fails | the redacted error text (secrets, emails, URLs and home paths stripped), the package/node version, and your OS — never transcripts or report content | say no when asked, or `POLYMATH_REPORT_CRASHES=0` |
| **LinkedIn enrichment** | only when you supplied a LinkedIn URL in the wizard | that URL, sent to Polymath's server, which fetches the public profile for your report's background section | don't supply a LinkedIn URL |

The run receipt exists to make a shared report **tamper-evident**: it can be
checked against the hashes recorded at the moment the run closed, so a report
can be shown not to have been hand-edited afterwards. The report content is **not** recoverable from
those hashes — but it is still outbound telemetry, it is on by default, and it
belongs on this list. `PS_SUPABASE_URL=""` disables it and every other central
call in the table.

### The one thing that reads your whole home directory

The setup wizard offers to look for existing ChatGPT / Claude / Notion export
zips. If you say yes, it runs `claude -p` with the **Bash** tool and
`--add-dir $HOME` — an LLM running shell commands (`mdfind`, `find`) across your
home directory, bounded by its prompt rather than by a sandbox. It is opt-in and
**defaults to no**. Say no and it never runs; you can always point the importer
at a zip yourself instead.

### Smaller caveats, stated plainly

- Prompts are passed to the CLI as arguments (`claude -p <prompt>`), so their
  text is visible to anything that can run `ps` on your machine while a stage is
  running. Redaction applies before this, so credentials aren't in that argv.
- `serve` binds `127.0.0.1` and has no authentication or CSRF protection: while
  it's running, any process on your machine (and any web page that can reach
  loopback) can read the analysis it serves. Stop the server when you're done.
- Analysis artifacts under your data home are plain files, unencrypted.
- In an interactive terminal, a newer version asks before updating. Enter or
  `y` installs it and relaunches the same command. Non-interactive runs never
  prompt or install, and `POLYMATH_NO_UPDATE_CHECK=1` disables the check.

## Requirements

Node.js ≥ 18, ESM only. Grading additionally needs **Claude Code** or **Codex**
installed and logged in (`claude` / `codex login`).

## License

**Proprietary — All Rights Reserved (source-available, not open source).** See
[LICENSE](./LICENSE) and the notice at the top of this file. (This section
previously read "MIT", which contradicted both.)
