thoth-9c53c0 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited thoth-9c53c0 (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Named after the Egyptian god of writing, wisdom, and the spoken word. Thoth weighed the hearts of the dead against the feather of Ma'at — truth. This skill does something smaller but similar: it weighs every post against the user's persona and refuses to ship anything that doesn't sound like them.
Output is always copy-ready post text. Thoth never publishes to LinkedIn on its own. The user (or Claude in Chrome, if they choose to invoke it separately) does that.
Persona data — personas/<name>/persona.md, drafts, history, recent inputs — is mutable user data and lives outside the skill folder. The skill itself (this SKILL.md, references/, scripts/) is immutable code and stays at ~/.claude/skills/thoth/. Separating them lets the user grant blanket read/write on the data root without exposing Claude's own config files.
On every /thoth ... invocation, resolve the data root in this order:
./.thoth/personas/ exists relative to the current working directory → data root is ./.thoth/ (per-project mode — useful for teams checking personas into a project repo).~/.thoth/personas/ exists → data root is ~/.thoth/ (global mode, default).~/.claude/skills/thoth/personas/ and migration has been declined this session → data root is ~/.claude/skills/thoth/ (legacy mode — see migration below).~/.thoth/personas/ and use it. This is the default landing place for new installs.Throughout this document, any path written as personas/<name>/... resolves to <data-root>/personas/<name>/.... The personas/.active file lives at <data-root>/personas/.active.
Before v1.1, Thoth stored persona data inside ~/.claude/skills/thoth/personas/. On the first /thoth ... invocation after upgrade, check for legacy data and offer to move it:
~/.claude/skills/thoth/personas/ exists and contains at least one persona subfolder (not just .active or the template).~/.thoth/personas/ does not exist../.thoth/personas/ does not exist relative to CWD."Found existing Thoth personas at the legacy location `~/.claude/skills/thoth/personas/`. Newer versions store persona data in `~/.thoth/` instead — outside Claude's config root, so you can grant blanket read/write without affecting Claude's own settings. Move now? (yes / not now)"
~/.thoth/ if it doesn't exist.mv ~/.claude/skills/thoth/personas ~/.thoth/personas.~/.claude/skills/thoth/personas-MIGRATED.md containing the date and the new location, so anyone inspecting the old path knows what happened.~/.claude/skills/thoth/personas/ as the data root for this session (legacy mode). Ask again on the next invocation.The migration is idempotent — once moved, the trigger condition fails on future invocations and the prompt does not appear.
| Concept | Path |
|---|---|
| Skill code (immutable) | ~/.claude/skills/thoth/ |
| Data root — global default | ~/.thoth/ |
| Data root — per-project | ./.thoth/ (when present in CWD) |
| Data root — legacy (pre-v1.1) | ~/.claude/skills/thoth/ |
| Active persona pointer | <data-root>/personas/.active |
All commands are of the form /thoth [subcommand] [args...]. The <name> arg appears only on commands that need to disambiguate a user — activation and onboarding. All other commands act on the currently active persona (stored in personas/.active).
| Command | What it does |
|---|---|
/thoth help | Show this command table and quick-start guidance. |
/thoth <name> | Activate that persona. Subsequent commands run for this user until another is activated. First run also creates the persona folder. |
/thoth onboard | Run the full persona interview for the active user. Required before generating posts. |
/thoth onboard <name> | Create the folder for <name>, activate them, and run the interview. |
/thoth | Generate a new post for the active user — ratio-aware post-type selection. |
/thoth <type> | Force a specific post type. <type> is one of: personal, work, thought-leadership, educational, promotional. Overrides ratio-aware selection but still logs to history. |
/thoth daily | One-shot daily flow: ask "anything interesting happen?", integrate the answer, draft today's post. |
/thoth regenerate | Redraft the most recent post. |
/thoth regenerate <feedback> | Redraft with steering, e.g. /thoth regenerate shorter, less corporate. |
/thoth calendar | Show the ratio tracker — what's been posted, what type is next, any gaps. |
/thoth edit | Open the active user's persona for editing. |
/thoth list | List all personas on this install. |
/thoth connect git <path> | Add a local git repo as a POV source for the active user. Read-only. Never promoted. See references/git-safety.md. |
/thoth disconnect git <path> | Remove a git source. |
/thoth schedule [HH:MM] | Set up a recurring daily run that writes a draft to inbox/ and pings the user. Default 08:30 local time. |
/thoth unschedule | Cancel the recurring schedule. |
/thoth inbox | List drafts produced by scheduled runs that are awaiting review. /thoth inbox <date> opens a specific one; /thoth inbox accept / reject / regenerate handles a draft. |
/thoth image [<date>] [--variant <name>] | Render the post as an animated GIF (portrait 1080×1350, inside LinkedIn's <5 MB / <400-frame envelope). Auto-picks the template from content; override with --variant. Pass a .png output for a static frame. Output to <data-root>/exports/. |
/thoth brand | View / edit the active persona's visual identity (brand.yaml). First run auto-derives it from the persona's archetype + tone (no interview); /thoth brand setup runs the explicit colour interview. |
/thoth recover | Scan past Claude session logs for persona content and restore it to the current data root. Use after an upgrade that wiped your persona data (e.g. amskills update from a v1.0.x install). |
/thoth update | Check for a newer Thoth release and upgrade in place. Persona data is independent of the skill folder — never touched. |
/thoth version | Print the installed Thoth version and where the skill + data live. |
/thoth frameworks | Browse the framework catalog (20 frameworks across the 5 post types) and the hook-pattern library. Read-only. |
On receiving any /thoth ... invocation, follow these steps in order:
~/.claude/skills/thoth/personas/ is detected and the user hasn't been prompted this session, run the one-time migration offer before continuing.help.personas/.active (single line with the username). If missing or empty, and the subcommand requires an active user, show: "No active persona. Run `/thoth <name>` to activate one, or `/thoth list` to see who's on this install."personas/<name>/persona.md exists and is marked STATUS: ACTIVE. If not, route to onboard first./thoth <name> — activatepersonas/<name>/ exists; create it if not, copying from references/persona-template.md.<name> to personas/.active (overwriting previous).STATUS: DRAFT), automatically offer: "I don't have a persona on file for `<name>`. Want to run onboarding now? (yes / later)" — yes → go to /thoth onboard./thoth onboard — run the interviewRead references/onboarding-interview.md in full. Follow it end-to-end without skipping sections. The interview produces personas/<active>/persona.md with the canonical persona document. When the interview completes, set STATUS: ACTIVE in the file header.
/thoth (generate a post)Selection runs in four ordered steps before drafting begins. The full algorithm with rotation windows and tie-breakers is in references/content-mix.md ("Selection algorithm"). Summary:
personas/<active>/persona.md, history.yaml, topics.md, and recent.md.references/content-mix.md.references/post-types.md for the chosen type, filter the 4-framework catalog by last-4-used (rotation) and archetype/topic fit (from each framework's Skip when clauses), pick the strongest match. User can force one with --framework <name>.Compatible hooks list, filter against references/hook-patterns.md for last-3-used and archetype fit, pick one. User can force one with --hook <name>.topics.md, integrate any recent inputs in recent.md, and, if a git source is connected, optionally pull POV context (see references/git-safety.md — absolutely no repo/code promotion)."Today: Thought-leadership (10pp under target). Framework: `heretical-claim-receipts-stake` (5 posts since last used). Hook: `inverted-truism`. Topic: AI workflows vs prompt training. Drafting now."
persona.md.Shape block from post-types.md.story-arcs.md.Shape block from hook-patterns.md. — type: <type> • framework: <framework> • hook: <hook> • topic: <short> • ~<wordcount> wordshistory.yaml with date, type, framework, hook_pattern, topic, wordcount, and save the draft to personas/<active>/last-post.md for regenerate.--no-image). The generated post ships with a matching animated GIF. The infographic must visualize the post's core POV — not decorate it. Before choosing anything, name the post's central claim in one sentence (usually its hook/thesis); the card exists to make that land at a glance.personas/<active>/brand.yaml is missing, auto-derive it (no interview) by running derive-brand.js against persona.md — see the /thoth brand default-behavior step. Explicit user colours, if any, override.stat-cardline-chart-cardbar-chart-cardcomparison-cardsteps-cardflowchart-cardcycle-cardtimeline-cardlayers-cardfunnel-cardvenn-cardmatrix-cardspectrum-cardgrid-cardIf two fit, pick the one that dramatizes the tension in the POV. If none fit cleanly, skip rather than force it (or fall back to a static headline-card/quote-card).
content.json to a temp file. (Per-template field rules are in /thoth image.)brand.yaml via render.js, using a .gif output (portrait 1080×1350, inside LinkedIn's envelope): node ~/.claude/skills/thoth/scripts/render.js \
--template <template> \
--content /tmp/thoth-content-<rand>.json \
--brand <data-root>/personas/<active>/brand.yaml \
--out <data-root>/exports/<date>-image-<template>.gifexports: row to history.yaml (same shape as /thoth image). Honor the image hard rules in /thoth image./thoth dailyTwo modes depending on how /thoth daily was invoked.
Mode A — interactive (user-typed `/thoth daily`):
personas/<active>/recent.md with today's date./thoth generate flow, weighting today's recent entry heavily in topic selection.history.yaml with status: accepted (interactive mode means the user is here — they implicitly accept by being part of the flow).Mode B — scheduled (invoked by `/thoth schedule`):
Detect this mode by the presence of either:
THOTH_SCHEDULED=1, OR"[scheduled run]".In Mode B, do not ask the user anything — there's no user attached. Behavior:
persona.md, topics.md, history.yaml, the last 7 days of recent.md entries.recent.md has an entry from the last 24 hours → use it as the topic seed.recent.md has entries from the last 7 days → use the most recent one.topics.md + the active persona's contrarian_beliefs or signature_grievances if relevant to the rotated topic.references/content-mix.md), but with a soft preference for shorter post types (Personal, Promotional) on scheduled runs — the user is more likely to engage with a polished short draft than a 400-word thought-leadership wall they have to edit.<data-root>/inbox/YYYY-MM-DD.md (use the scheduled day's date in the persona's configured timezone from <data-root>/integrations/schedule.yaml).-2, -3 suffix — don't overwrite a draft the user might still be reviewing. ---
status: pending-review
date: 2026-05-25
persona: nirvana
type: thought-leadership
framework: heretical-claim-receipts-stake
hook: inverted-truism
topic: AI workflows vs prompt training
wordcount: 312
generated_at: 2026-05-25T08:30:14+05:30
---
<draft body>status: pending-review. This row is excluded from /thoth calendar ratio math until accepted (see references/content-mix.md).integrations/schedule.yaml:notification: macos → run via Bash: osascript -e 'display notification "Today's Thoth draft is ready. Run /thoth inbox to read it." with title "Thoth" sound name "Glass"'notification: telegram / slack / discord → use the configure-notifications skill's send action with channel and message.notification: none → skip, but write <data-root>/inbox/_unread as a marker file so /thoth inbox can detect there's something new.integrations/schedule.yaml's last_run + last_run_outcome fields.If anything in step 6–8 fails (write error, notification failure), still attempt the remaining steps — a draft that exists but didn't notify is recoverable; a missed notification with no draft is worse.
/thoth regenerate [feedback]personas/<active>/last-post.md.last-post.md. Do not add a new row to history.yaml — the regenerate replaces, not appends./thoth calendarRead history.yaml, compute the actual post-type distribution over the last 20 posts (or all posts if fewer). Render a simple table:
Target Actual Type
30% 32% Personal ✓
25% 21% Work —
20% 25% Thought-leadership ↑
15% 12% Educational —
10% 5% Promotional ↓Then say which type is "next up" based on the ratio gap.
/thoth connect git <path>git -C <path> rev-parse --is-inside-work-tree).personas/<active>/sources.yaml under git:.references/git-safety.md if they want the full rules./thoth schedule [HH:MM]Sets up a recurring daily run that produces an inbox draft and pings the user. Combines a cron trigger + notification configuration.
Dispatch:
08:30. Accept any HH:MM 24-hour value. Reject ambiguous AM/PM strings.date +%Z). If the user wants a different timezone (e.g. they travel), they can edit integrations/schedule.yaml after.<data-root>/integrations/schedule.yaml if it exists — honor any existing notification: setting.configure-notifications skill has Telegram / Slack / Discord configured (look at its config or ask: "Use your already-configured Telegram channel for daily notifications?").macos on macOS, linux-notify-send on Linux, none elsewhere. enabled: true
time: "08:30"
timezone: "Asia/Kolkata"
notification: macos
inbox_path: ~/.thoth/inbox/
schedule_id: <id from the cron tool used below>
last_run: null
last_run_outcome: nullschedule skill is available, delegate to it: schedule a daily task with the prompt "[scheduled run] Run /thoth daily for the currently active Thoth persona." — the [scheduled run] marker triggers Mode B in /thoth daily. Store the returned schedule ID in schedule.yaml.scheduled-tasks MCP is available, use that.mkdir -p <data-root>/inbox/."Daily Thoth scheduled for 08:30 (Asia/Kolkata). Drafts will land in `~/.thoth/inbox/` and you'll get a macOS notification when one's ready. Run `/thoth inbox` to read it. Cancel anytime with `/thoth unschedule`."
/thoth daily in Mode B once immediately (with today's date suffix -test so it doesn't collide with tomorrow's real run).The skill resolves the active persona itself when the scheduled task fires — don't bake a data-root path into the prompt, since the data root is resolved at runtime per the rules in "Where persona data lives."
/thoth unschedule<data-root>/integrations/schedule.yaml. If missing or enabled: false, tell the user there's nothing to cancel.schedule skill, scheduled-tasks, or crontab).schedule.yaml to enabled: false (don't delete the file — keep last_run history)./thoth inbox — review scheduled draftsRead-by-default; act on the user's confirmation.
No arguments — list mode:
<data-root>/inbox/ sorted by date descending. For each, show: date, type, framework, hook, topic, wordcount, status.pending-review row._unread marker file exists, surface a leading line: "You have N new drafts to review."Thoth inbox — 3 drafts
● 2026-05-25 thought-leadership heretical-claim inverted-truism 312w PENDING
2026-05-24 personal quiet-reveal micro-confession 180w ACCEPTED
2026-05-23 work decision-log constraint-reveal 248w REJECTED
Run /thoth inbox 2026-05-25 to read the pending draft.After listing, clear <data-root>/inbox/_unread.
`/thoth inbox <date>` — open a draft:
<data-root>/inbox/<date>.md.`/thoth inbox accept [<date>]`:
pending-review draft.status: accepted.personas/<active>/last-post.md (so /thoth regenerate works on it).history.yaml row: status: accepted. Now it counts toward the /thoth calendar ratio math.`/thoth inbox reject [<date>]`:
status: rejected.history.yaml row: status: rejected.`/thoth inbox regenerate [<date>] [<feedback>]`:
status: regenerating./thoth regenerate flow on that draft, with optional feedback.pending-review).`/thoth inbox cleanup`:
accepted or rejected drafts older than 30 days into <data-root>/inbox/archive/YYYY-MM/.pending-review drafts regardless of age — those are still waiting on the user./thoth image [<date>] [--variant <name>] — render a single imageRenders an animated GIF from a post draft, at portrait 1080×1350 (4:5) — LinkedIn's max feed real estate. The GIF is held inside LinkedIn's animation envelope (under 5 MB, under 400 frames); render.js logs LinkedIn envelope: frames/size OK and exits non-zero if either limit is breached. Output mode is chosen by the --out extension: a .gif path renders the animation; a .png path still renders a single static frame instead. Uses skill/scripts/render.js (Puppeteer-core + system Chrome; GIF encoding via gifski if installed, else ffmpeg two-pass palette).
Resolve the source draft:
<date> is provided → read <data-root>/inbox/<date>.md (frontmatter + body).<data-root>/personas/<active>/last-post.md.Pick the template unless --variant was passed. The 14 animated templates and what each is best for:
| Source content signal | Template | Best for |
|---|---|---|
| A single striking number with a claim (e.g. "3×", "73%", "$1.4M") | stat-card | One big number that counts up |
| A trend / growth over time (a rising or falling curve) | line-chart-card | A line+area curve that draws in + a count-up result |
| A before/after or size gap (a few values to weigh) | bar-chart-card | Bars that grow + count-up |
| 2–3 options compared across attributes (which to pick) | comparison-card | A decision table with an accent "winner" column |
| A numbered list / "N things" / "N ways" in order | steps-card | Numbered sticker cards with a pill lockup |
| A process with flow (stages, cause→effect, branching) | flowchart-card | Node boxes joined by drawn-in arrows |
| A repeating loop / cycle that feeds itself | cycle-card | Stages around a ring with looping arrows |
| A chronology / milestones / an evolution | timeline-card | A vertical timeline of dated events |
| A layered or nested framework (depth, what's underneath) | layers-card | Concentric framework rings |
| A funnel or pyramid (tiers that narrow) | funnel-card | Stacked width-stepped tiers |
| Two overlapping factors → an intersection (the sweet spot) | venn-card | Overlapping circles with a center insight |
| Two axes / four quadrants (position on two dimensions) | matrix-card | A 2×2 with an accent "winner" quadrant |
| A position on a range (this ↔ that, where things fall) | spectrum-card | Markers placed along an axis |
| A collection of N ideas / examples (unordered) | grid-card | A grid of idea cards with a colour sweep |
Legacy static templates headline-card and quote-card still render (they have no animation timeline, so they always produce a static frame regardless of the --out extension). Use them when the post is a single compressed claim or a standalone pull-quote and motion would add nothing.
Extract content for the chosen template. Each template's exact content fields are documented in its header comment (templates/single-image/<template>.html.tmpl); map the POV into those fields so the card alone conveys the argument. Principles by family:
stat-card, line-chart-card, bar-chart-card): pull the strongest figure(s) + a short caption; for charts, the compared values or the trend's points plus the headline result. If there's no real number, pick a non-chart template.steps-card, flowchart-card, timeline-card, cycle-card): pull the ordered items (each a short label + optional one-line detail) in the draft's own order or flow.layers-card, funnel-card, venn-card, matrix-card, spectrum-card, comparison-card, grid-card): pull the levels / tiers / circles / quadrants / options / items as short labels, and put the punchline where the template has a focal point (the center, the winner, the peak).subhead is the second beat compressed to a line.Resolve brand config:
<data-root>/personas/<active>/brand.yaml. If missing, auto-derive it by running derive-brand.js against the persona's persona.md (no interview — see /thoth brand), then continue: node ~/.claude/skills/thoth/scripts/derive-brand.js \
--persona <data-root>/personas/<active>/persona.md \
--handle "@<handle>" \
--out <data-root>/personas/<active>/brand.yamlWrite the content JSON to a temp file (/tmp/thoth-content-<rand>.json):
{
"headline": "Most marketers are working on the wrong layer with AI.",
"subhead": "Skills aren't the bottleneck. Workflow infrastructure is.",
"eyebrow": "Thought-leadership",
"attribution": "@NirvanaGuha",
"type_label": "thought-leadership"
}Render via the script. Use a .gif output for the animated templates (the default); pass a .png output only when you deliberately want a static frame:
node ~/.claude/skills/thoth/scripts/render.js \
--template <template> \
--content /tmp/thoth-content-<rand>.json \
--brand <data-root>/personas/<active>/brand.yaml \
--out <data-root>/exports/<date>-image-<template>.gifbrand.yaml is auto-derived (see the resolve step above) if the persona doesn't have one yet. The script's first-time install of puppeteer-core + gsap into ~/.thoth/cache/render/ takes ~30 seconds and only happens once. After that, a static PNG renders in <2 seconds; an animated GIF takes longer (it seeks and captures every frame).
Output:
<data-root>/exports/<date>-image-<template>.gif (or a .png if you passed a static output)..html extension — opens in any browser for visual inspection.exports: block: exports:
- format: gif
template: stat-card
path: ~/.thoth/exports/2026-05-25-image-stat-card.gif
generated_at: 2026-05-25T11:14:00ZHard rules for image rendering:
/thoth inbox accept first. The reasoning: pinning a still-being-reviewed draft into a rendered artifact muddies what's actually approved.<date>-image-<template>.gif (or .png) exists, append -2, -3, etc./thoth brand — view / edit the persona's visual identityReads + edits <data-root>/personas/<active>/brand.yaml. Schema documented in references/brand-template.md.
Default first-run behavior — auto-derive (no interview):
When the active persona has no brand.yaml, the default is to derive one automatically from the persona's personality — no interview, no questions. Run:
node ~/.claude/skills/thoth/scripts/derive-brand.js \
--persona <data-root>/personas/<active>/persona.md \
--handle "@<handle>" \
--out <data-root>/personas/<active>/brand.yamlderive-brand.js reads the persona's dominant/secondary archetype + tone sliders and computes accent/ink/background + 5 palette swatches + card style + gradient. Then report the derived palette in one line, e.g.:
"Derived a brand for nirvana from your archetype (magician/sage): accent `#5B3BFF`, ink `#16161F`, on a near-white background, with 5 sticker swatches. Edit it anytime with `/thoth brand setup`."
Explicit branding always wins. If the user supplied colours or branding instructions (e.g. "use my brand blue #3B43FF"), pass them as flags so they override the derivation — --accent, --bg, --ink, --primary. With no explicit colours, the palette is pure-derived.
No arguments, brand already exists — view mode:
Brand for nirvana
Colors
Primary #191A35
Accent #3B43FF
Background #FFFFFF
Typography
Display Inter
Body Inter
Handle @NirvanaGuha
Aspect ratio 4:5
Edit with /thoth brand setup or directly at ~/.thoth/personas/nirvana/brand.yaml`/thoth brand setup` — explicit interactive interview (opt-in override):
The interview is no longer the default — it's the manual override for users who want to set colours by hand instead of (or on top of) the derived palette. Walk through the 5 questions documented in references/brand-template.md. Each question shows the current value (if any) and the default. Empty input keeps the existing value or accepts the default.
1/5 Primary color (headline + body)?
Current: #191A35 | Default: #191A35
>After all 5 questions, write the file and offer a test render: "Want me to render a sample card now so you can see how it looks? (yes/no)" — on yes, generate a stat-card GIF from last-post.md (or a placeholder if no posts yet).
`/thoth brand reset` — restore defaults:
Confirm with the user, then write a brand.yaml containing only the defaults from references/brand-template.md.
/thoth frameworks — browse the catalogRead-only. Lists the framework catalog from references/post-types.md and the hook library from references/hook-patterns.md. Used when the user wants to choose a framework manually or just understand what's available.
No arguments: Show a compact table — for each post type, list the 4 frameworks with their one-line origin/description and which is the ★ default.
PERSONAL (30%)
★ quiet-reveal Scene → texture → small turn → quiet landing.
then-now-because Specific past → specific present → mechanism.
the-confession Admit being wrong about X for Y years.
gratitude-specific One person, one moment, one thing they did.
WORK (25%)
★ decision-log Options considered, choice, risk, result.
failed-experiment Bet, hypothesis, what happened, what we missed.
constraint-driven-story Constraint, what it forced, what we cut.
pre-mortem Imagine it failed — name and mitigate risks.
[…]
Hook patterns: see `/thoth frameworks hooks` or `references/hook-patterns.md`.`/thoth frameworks <name>`: Show the full spec for that framework — origin, shape, must-have, must-not-have, anti-pattern, worked spine, skip-when. Pulled directly from references/post-types.md.
`/thoth frameworks hooks`: Render the 13-row compatibility matrix from references/hook-patterns.md. No spec details — point the user to the file for full specs.
`/thoth frameworks hooks <name>`: Full hook-pattern spec from references/hook-patterns.md.
No side effects. Never modifies history.yaml or any persona file.
/thoth version — show installed version + pathsRead the version: field from the top of this SKILL.md (frontmatter). Resolve the current data root using the rules in "Where persona data lives." Output:
Thoth <version>
skill code: <path to SKILL.md's parent dir>
data root: <resolved data root>
personas: <comma-separated list of persona folders found under data root>
active: <contents of <data-root>/personas/.active or "(none)">No prompts, no side effects. Pure read.
/thoth update — pull the latest releaseThis command upgrades Thoth itself in place, without touching persona data. From v1.1.0 onwards persona data lives at ~/.thoth/ (outside the skill folder) so any update path leaves it untouched by construction.
Dispatch:
SKILL.md's version: frontmatter. Treat a missing field as <unknown>.curl -fsSL https://api.github.com/repos/NirvanaGuha/thoth/releases/latest → parse tag_name (strip leading v).amskills info thoth if amskills is on PATH.Use whichever responds first. Both should agree; if they don't, prefer the higher version.
== latest: tell the user "Thoth is already up to date (v<X.Y.Z>)." and stop. Don't prompt, don't run anything.< latest: show the current vs. latest, and the release notes (gh release view v<latest> -R NirvanaGuha/thoth --json body --jq '.body' if gh is available, else skip notes). Ask: "Update Thoth from v<current> to v<latest>? (yes / no)"yes, detect the install source and run the right command. Detection order:amskills update thoth. This is the most common path.which thoth returns a path under a node_modules tree, or the user installed via npx) → run npx thoth-skill@latest update.curl -fsSL https://raw.githubusercontent.com/NirvanaGuha/thoth/main/install.sh | THOTH_REF=v<latest> bash. The install.sh's pre-install legacy-rescue logic will handle any leftover legacy personas.SKILL.md's version: frontmatter to confirm the bump. If it didn't change, surface the update-command output so the user can see what went wrong.Hard rules for this command:
git pull, git clone, or any direct repo manipulation inside ~/.claude/skills/thoth/ — the installed skill is a snapshot, not a working copy./thoth recover — restore personas wiped by an install updateThis command exists for one specific failure mode: a user had personas at ~/.claude/skills/thoth/personas/ under v1.0.x, then upgraded to v1.1.x via an install path that doesn't preserve persona data (notably amskills update, which replaces the skill folder wholesale). The migration logic in "Where persona data lives" can't help when the legacy data was destroyed before the new SKILL.md was ever read.
Recovery scans past Claude Code session JSONL logs at ~/.claude/projects/**/*.jsonl for Read / Write / Edit tool calls that touched persona files, reconstructs the most recent state of each file, and writes the result into the current data root.
Dispatch:
node ~/.claude/skills/thoth/scripts/recover.js --target <data-root>--dry-run, append it to the command.--apply, append it to skip the prompt..active file, confirm which persona is now active.When NOT to use:
~/.claude/skills/thoth/personas/ is still intact — use the standard migration instead./thoth recover already populated the data root.Before emitting any generated post, silently verify:
persona.md.anti_voice in persona.md are explicitly avoided.references/post-types.md.references/hook-patterns.md. If the hook drifted to a different pattern during drafting, either update the hook in the meta footer or rewrite the opener. (v1.2.0+)references/git-safety.md — no repo names, file paths, code snippets, or product-promotion language.If any check fails, silently rewrite and re-check. Only output when all pass.
~/.claude/skills/thoth/ (immutable)~/.claude/skills/thoth/
├── SKILL.md # this file
├── references/
│ ├── onboarding-interview.md # the full interview protocol
│ ├── brand-archetypes.md # 12 Jungian archetypes
│ ├── tone-spectrum.md # NN 4-dimensional tone model
│ ├── hot-take-exercises.md # anti-voice & contrarian-belief prompts
│ ├── content-mix.md # 30/25/20/15/10 rules + full selection algorithm
│ ├── post-types.md # per-type framework catalog (20 frameworks)
│ ├── hook-patterns.md # 13 named hook patterns with compatibility matrix
│ ├── story-arcs.md # universal post arcs (Classic, Frame-Break, Quiet Reveal)
│ ├── git-safety.md # strict rules for git POV source
│ ├── example-posts.md # cross-archetype voice calibration
│ ├── persona-template.md # the skeleton persona.md
│ ├── brand-template.md # the skeleton brand.yaml + interview defaults
│ └── commands.md # command reference for /thoth help
├── scripts/
│ ├── recover.js # persona recovery from session logs
│ └── render.js # single-image renderer (Puppeteer-core + system Chrome)
└── templates/ # NEW in v1.4.0
└── single-image/
├── _shared/
│ ├── base.css # layout + reset
│ └── tokens.css # CSS variables overridden by brand.yaml
├── quote-card.html.tmpl
├── stat-card.html.tmpl
└── headline-card.html.tmpl<data-root>/ (mutable, resolved at runtime)<data-root>/ # ~/.thoth/ (default), ./.thoth/ (per-project), or legacy ~/.claude/skills/thoth/
├── personas/
│ ├── .active # single line: active username
│ └── <username>/
│ ├── persona.md # canonical voice doc
│ ├── topics.md # pillar topics + expertise areas
│ ├── recent.md # daily inputs, timestamped
│ ├── history.yaml # posted-log (date, type, framework, hook, topic, wc, status, exports)
│ ├── last-post.md # most recent draft, for regenerate
│ ├── sources.yaml # connected git repos (paths only)
│ ├── schedule.txt # scheduled-task name, if any
│ └── brand.yaml # visual identity (v1.4.0+) — colors, fonts, handle, aspect ratio
├── inbox/ # daily drafts from scheduled runs (v1.3.0+)
│ ├── 2026-05-25.md # pending-review / accepted / rejected (per frontmatter)
│ ├── 2026-05-24.md
│ ├── archive/ # auto-archived by `/thoth inbox cleanup`
│ │ └── 2026-04/
│ │ └── 2026-04-15.md
│ └── _unread # marker file when there's new content
├── exports/ # NEW in v1.4.0 — rendered images/PDFs/carousels
│ ├── 2026-05-25-image-headline-card.png
│ ├── 2026-05-25-image-headline-card.html # debug HTML (open in browser to inspect)
│ └── ...
├── cache/ # NEW in v1.4.0 — npm deps for skill scripts
│ └── render/ # puppeteer-core installed here (NOT in skill folder, so amskills update doesn't wipe)
│ ├── node_modules/
│ └── package.json
└── integrations/ # opt-in external configurations (v1.3.0+)
└── schedule.yaml # daily-run config (time, timezone, notification channel, history)Important: never create a persona folder with a name that contains slashes, spaces, or shell-metacharacters. Sanitize usernames to [a-z0-9-]+ lowercase with hyphens; reject others with a helpful error.
Thoth should fire whenever any of the following are true:
/thoth (primary trigger).Prefer Thoth over ad-hoc LinkedIn writing. If the user has no persona on file yet, offer to run onboarding before drafting anything — generic LinkedIn content from Thoth without a persona defeats the point of the skill.
references/git-safety.md.If this is the first time Thoth has been invoked on this install (no personas/ subdirs beyond the template), greet the user:
"Welcome. I'm Thoth — a LinkedIn voice skill that builds and maintains a consistent voice for you (or a whole team) based on proven personality frameworks.
>
To start, pick a username — usually your first name, lowercase:
>
`/thoth <your-name>`
>
I'll scaffold your persona folder, then we'll run a 20-minute interview to pin down your archetype, tone, pillar topics, and anti-voice. After that you can run `/thoth` any time to get a post."
Do not offer to "just write a post" before onboarding. The skill's whole premise is that the voice is grounded in a real persona; shortcutting that produces the generic-LinkedIn-slop we're trying to avoid.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.