Metadata-Version: 2.5
Name: verbatim-linkedin
Version: 2.4.1
Summary: Local web app over a Verbatim instance: LinkedIn posts built from an interview, never from thin air.
Project-URL: Homepage, https://github.com/alexis-morain/verbatim-linkedin
Project-URL: Source, https://github.com/alexis-morain/verbatim-linkedin
Project-URL: Issues, https://github.com/alexis-morain/verbatim-linkedin/issues
Project-URL: Changelog, https://github.com/alexis-morain/verbatim-linkedin/blob/main/docs/releases.md
Author: Alexis Morain
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Natural Language :: French
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.27
Requires-Dist: jinja2>=3.1
Requires-Dist: markdown-it-py>=4.2.0
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: pyyaml>=6
Requires-Dist: uvicorn>=0.30
Provides-Extra: test
Description-Content-Type: text/markdown

# Verbatim

**The LinkedIn post skill that interviews you first.**

A Claude skill bundle that interviews you before it writes anything, then
drafts a LinkedIn post in your voice, checks it against a language specific
style pass, archives it, and publishes it.

The name is the mechanism: no angle is proposed unless it can be traced to a
verbatim quote of something you said in the interview that produced it.

**It cannot write anything you did not say.** Every fact in a generated post
traces back to your profile, to your published corpus, or to a sentence you
spoke in the interview that produced it. When nothing traces, nothing gets
written. That constraint is the product; the writing is a consequence of it.

MIT licensed. Self hosted or no host at all. No account, no subscription, no
service in the middle.

![The draft, and every claim of it checked against the interview](https://raw.githubusercontent.com/alexis-morain/verbatim-linkedin/main/docs/screenshots/traceability.png)

*Every claim of a draft against what backs it, and where that backing lives:
a sentence you said, or a line of the sheet you approved. Highlighted means no
quote backs it, which is honest and is yours to check; red means the engine
named a source that does not hold the quote. The example instance in
[`examples/`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/examples/) is a fictional persona; nothing here is anybody's real
material.*

A backing also says where it lives: a sentence you said, or a line of the
validation sheet you approved. The panel words the two differently, because
an approval is consent rather than speech, and a quote is checked against the
one source it names: a line of the sheet offered as something you said comes
back fabricated, and so does anything lifted from your profile.

## Why an interview

Most tools of this kind assume the hard part is writing. It is not. The hard
part is getting a specific, true, defensible thing out of your head and onto a
page, and a text box does not do that. A template does not do it either: it
produces a well shaped post about nothing, because a template can be filled
without you having said anything.

So the interview comes first, one question at a time, and it refuses to advance
on an abstract answer. It asks for the instance again: which one, when, how
many, with whom. Between four and six turns, then it stops, because the test is
whether there is a scene, a position and a consequence, not whether a counter
reached six.

Then, before a single line is drafted, it hands you a validation sheet where
every bullet has to trace to something you said. You approve it or you correct
it. Nothing is written until you do.

## Two ways to run it

**As a skill bundle**, inside Claude Code or any agent that reads skills. You
talk, it interviews you, it writes the files. This is the original shape and
it needs no Python at all.

**As a local web app**, `verbatim`, which drives the same skills against the
same directory and gives you screens for the parts that are decisions rather
than conversation: the validation sheet you approve, the traceability panel
above, the archive form, the publish plan. It also edits the files one
section at a time, keeps the idea bank, and reads the measurement store
across posts: what is due at J+7, sums per pillar, format and objective,
and the status of every pattern at the thresholds of
[`references/measure.md`](https://github.com/alexis-morain/verbatim-linkedin/blob/main/references/measure.md), with nothing averaged. It
binds to 127.0.0.1 and nothing about it is hosted.

```bash
uvx verbatim-linkedin ~/my-profile     # or: pipx install verbatim-linkedin
```

From a clone, which is also how you get the skills, it is one command and no
install:

```bash
uv run --project app verbatim ~/my-profile
```

Either way it opens on the conformance report if that directory is not a
profile yet, and tells you to run `linkedin-setup` first.

The two are the same engine over the same files. Use whichever you are in
front of; a directory written by one is read by the other.

**On macOS there is also a window**, `Verbatim.dmg` on the
[releases page](https://github.com/alexis-morain/verbatim-linkedin/releases):
the same app, Apple Silicon, needing neither Python nor `uv` on your machine
because it carries [uv](https://github.com/astral-sh/uv) (Apache-2.0 OR MIT)
and installs the engine from PyPI the first time you open it. It is not
signed, so macOS will call it damaged and the release notes carry the one line
that clears it. If that trade is not for you, the command above is the same
app with no warning, and it is the one this page recommends.

![The overview: status, next session, posts per pillar, latest posts](https://raw.githubusercontent.com/alexis-morain/verbatim-linkedin/main/docs/screenshots/overview.png)

## Engine and profile

Two things, kept apart on purpose.

**The engine** is this repository. It holds mechanism: the interview ladder,
the formats, the validation sheet, the measurement schema, the deterministic
style pass. It contains nothing about any particular person.

**The profile** is yours. Your positioning, your pillars, your provable facts,
the names you cannot cite, your signature. It lives in a directory you choose,
on your machine, and `.gitignore` here is written to make sure it never ends up
in this repository by accident.

The seam between them is three lines at the top of your profile:

```
## Status
- filled: no
- source: template
- updated: --
```

While `filled: no`, every skill falls back to generic rules, says so, and
offers to set you up. No skill pretends to know you.

## Getting started

```bash
git clone https://github.com/alexis-morain/verbatim-linkedin.git ~/verbatim-linkedin
ln -s ~/verbatim-linkedin ~/.claude/skills/verbatim
```

**The bundle installs as one unit.** The router at the root dispatches to the
skills inside it, and that is what lets every skill resolve `references/`,
`locales/` and `lib/` by the same relative path. Symlinking a single skill
directory on its own will break those paths.

Then say you want to set up your LinkedIn profile.
`linkedin-setup` runs about twenty minutes and ends on a written post, not on a
folder.

The app runs from the clone you just made:

```bash
uv run --project app verbatim ~/my-profile
```

It needs a model to run an interview, and it is told which one by three
environment variables rather than by an account. Before the first turn it
shows an order of magnitude for four to six turns at the model's input rate,
and says on the same line what that figure rests on. `.env.example` documents them.
Local and hosted are the same code path and neither is the recommended one:
what decides is whether the model can hold a 6400 token system block, answer a
forced tool call, and produce a five field validation sheet when asked.
[`docs/smoke.md`](https://github.com/alexis-morain/verbatim-linkedin/blob/main/docs/smoke.md) carries the measurements and says plainly what
the first release ships untested.

Read [`examples/`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/examples/) first if you want to see the shape of a filled
profile before you fill your own. The persona in there is fictional and is
deliberately not in the maintainer's field.

## What ships

| Skill | Does |
|---|---|
| [`linkedin-setup`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/skills/linkedin-setup/) | Builds your profile, your pillars, your voice file and your idea bank from a short interview, then hands over to the first post. |
| [`linkedin-post`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/skills/linkedin-post/) | Interview, validation sheet, draft, style pass, revisions, archive, publish, measure at J+7. |
| [`linkedin-profile`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/skills/linkedin-profile/) | Audits and rewrites the nine sections of your public LinkedIn page, headline and About first, from material you can prove. |
| [`verbatim`](https://github.com/alexis-morain/verbatim-linkedin/tree/main/app/) | The local app: the same skills, driven from screens, over the same directory. |

One more is deliberately held back: a measurement skill that advises on the
store across posts. The app's Measure screen computes what the files say; the
skill would say what it means, and it waits for real measured posts to be
built against, because advice written from imagined data measures the
imagination.

![The Measure screen: what is due, then sums per pillar, format and objective, with a status per threshold](https://raw.githubusercontent.com/alexis-morain/verbatim-linkedin/main/docs/screenshots/measure.png)

Under two measured posts, that screen concludes nothing and says so on the
line. Nothing on it is an average.

## Languages

Three axes, and they are independent:

- The **engine** is in English. Once, by the maintainer.
- The **interview** happens in your language.
- The **output** is per post, defaulting to the interview language.

The last two really are separate. Plenty of people want to be interviewed in
their own language and publish in English.

`en` and `fr` ship today. A language pack is four files, and it is **never a
translation of another pack**: the ten categories in
[`references/style-taxonomy.md`](https://github.com/alexis-morain/verbatim-linkedin/blob/main/references/style-taxonomy.md) are shared, the
word lists that fill them are not. `scalable` is a marketing tell in French and
an ordinary word in English. "Force est de constater" has no English twin.
Negative parallelism is the dominant English tell of 2026 and merely common in
French.

The contract and the acceptance criteria are in
[`locales/_template/README.md`](https://github.com/alexis-morain/verbatim-linkedin/blob/main/locales/_template/README.md). You do not have
to be a maintainer to propose a pack; you have to be a native speaker who
publishes in the language.

## The style pass

`lib/lint.py` is deterministic. No model, no network, no AI detector.

```bash
python3 lib/lint.py --lang fr - < draft.txt
```

It reports and the human decides. Only the rules a pack marks hard block a
draft, and that set is deliberately tiny. A flagged word that you actually said,
inside a quote, stays in.

It runs on the standard library alone. PyYAML is used when it is installed and
a small built-in reader takes over when it is not.

## Publishing

Three tiers. The default needs no configuration.

| `LINKEDIN_PUBLISH` | Does |
|---|---|
| `copy` (default) | Prints the post, ready to paste. Nothing leaves your machine. |
| `postiz` | Self hosted [Postiz](https://postiz.com). Needs `POSTIZ_INTEGRATION_ID`. |
| `command` | Runs your own binary, post on stdin. `LINKEDIN_PUBLISH_CMD`. |

Anything that leaves the machine needs `--confirm`, and without it the script
prints the target channel and stops. That guard exists because the maintainer
has already published to the wrong channel: a personal profile and a company
page are two lines in a config file and two very different things in a feed.

In the app the same guard is two clicks with a reading between them. You draw
a plan, which is `lib/publish.py` printing what would happen, and the confirm
button carries a digest of exactly that plan: if the channel, the time or the
post moved since it was drawn, the click sends nothing and shows you what
moved. A plan is confirmed once, so a reload or a double click cannot make two
posts out of one.

![The publish plan: tier, target channel by name, when, length, first line](https://raw.githubusercontent.com/alexis-morain/verbatim-linkedin/main/docs/screenshots/publish-plan.png)

A post carrying a link gets one more line, asking whether it needs a
disclosure. Nothing here decides that for you, because nothing here can know
whether there is a material connection behind a link. What is mechanical is
that a post with no link never raises the question. The wording that satisfies
your market is in `locales/<lang>/market.md`, and the reason this exists at all
is that a disclosure once survived a draft here and not the published version.

**Publishing does not set the state of a post.** A tier accepting something is
not the same fact as a post being live: the copy tier printed a post nobody
has pasted yet, and a scheduling payload still has to be sent by whatever holds
the account. `state` and `published_ref` are yours to write, on the same
screen, exactly like the pillar and the format the archive form asks for rather
than guesses.

## What this will not do

- **No hook formulas calibrated on a viral corpus.** They invert the mechanism.
  Here the angle descends from a sentence you said; there it descends from a
  shape that performed for somebody else.
- **No writing against an AI detector.** Optimising for a classifier is writing
  for the classifier.
- **No engagement pods, no comment gate by default.**
- **No invented facts, including inside a revision.** Revisions are where this
  usually breaks, so the traceability check runs again after every one.

## Where it comes from

Built out of a working setup, not out of a specification. The post it was
calibrated on is real and public: Alexis Morain, [the La Growth Machine
workflow](https://www.linkedin.com/feed/update/urn:li:share:7488195323551481856),
29 July 2026, 2,200 characters.

The scars in this bundle are from that setup. The validation sheet exists
because a draft once claimed client experience that did not exist. The
publishing guard exists because three test posts went to a company page. The
disclosure rule exists because an affiliate disclosure survived the draft and
not the published version.

## License

MIT. See [LICENSE](https://github.com/alexis-morain/verbatim-linkedin/blob/main/LICENSE).
