The AI layer
Bring your own key, or your own machine. There is no Pagecraft-operated inference.
@pagecraft/ai can summarise a document, answer a question about it with the
page the answer came off, find personal data in it, describe its images,
translate it, propose an outline, or suggest a filename.
It holds no credentials and knows no endpoint. Every request goes through a
fetch you inject, to a provider the user configured, with a key handed over
one call at a time out of a vault encrypted on their device. There is no
Pagecraft-operated inference, proxy or relay, and nothing in the package could
reach one.
Not on npm yet. Until it is, build it from source and depend on
packages/ai by path:
git clone https://github.com/amanawasthi2025/pagecraft
cd pagecraft
pnpm install && pnpm build
The path that costs nothing
A model on the user’s own machine needs no key, costs nothing, and keeps the grounding text on the device with the document it came from. It is the default answer to “do I have to pay for any of this?”, and it is not a degraded mode — it is the same code path as every other provider.
import { ask } from '@pagecraft/ai';
const answer = await ask(
{ kind: 'ollama', baseUrl: 'http://localhost:11434', model: 'llama3.2' },
{ turns: [{ role: 'user', text: 'Summarise this.' }] },
{ fetch: window.fetch.bind(window) },
);
Ollama has to be told the page may call it — start it with OLLAMA_ORIGINS set
to the site’s address. LM Studio has the same setting in its developer tab, and
speaks the OpenAI protocol at http://localhost:1234/v1.
A provider you pay for
Six kinds: anthropic, openai, google, openai-compatible, ollama,
lm-studio. providerKinds describes each one — whether it needs a key, what
address to prefill, what has to be allowed at the other end.
import { checkProvider, openVault, indexedDbStore } from '@pagecraft/ai';
const provider = {
kind: 'anthropic',
baseUrl: 'https://api.anthropic.com',
model: 'claude-opus-5',
} as const;
// Test a key before saving it. It answers rather than throws: half the time the
// answer is no, and that is the point of asking.
const check = await checkProvider(provider, { fetch, key });
if (!check.ok) console.error(check.error.message, check.error.remedy);
const vault = await openVault(indexedDbStore());
await vault.create(passphrase); // or vault.unlock(passphrase) after the first time
await vault.put('anthropic', key);
The vault writes ciphertext and nothing else. Its store is injectable for the
same reason fetch is, and “only ciphertext is written down” is asserted in the
test suite by reading that store — because what is written down is the claim.
Grounding: what will be sent, before it is sent
An AI tool works on passages taken out of the document. Those passages are the one thing that leaves the device, so the rule is that the user sees them first — in full, byte for byte, as the string that will actually be sent.
import { ground, openSession } from '@pagecraft/ai';
const session = openSession({
provider,
fetch,
// A function, not a key: the session holds no credential of its own, so
// locking the vault stops a sitting that is already under way.
key: () => vault.get('anthropic'),
grounding: ground(passages),
});
show(session.grounding.text); // in full
show(session.estimate(question)); // tokens, and money where a price is known
session.disclose(); // once it is on the screen, not before
const answer = await session.ask(question, { onText: (piece) => append(piece) });
console.log(session.spent); // tokens and cost, and whether either was counted or estimated
session.ask before session.disclose throws. That is the whole mechanism: a
surface cannot send a document’s contents to a third party without having put
those contents in front of the person whose document it is.
Prices carry the date they were read, so an estimate can say how old its figures are rather than quoting a number as though it were current.
The tools
Each AI tool is two halves rather than one function, and the split is where the
consent lives (ADR-0034). prepare reads the documents on the device and works
out the grounding — the exact string, and the exact pictures, a request would
carry. The host shows that and calls disclose(). run asks, and cannot ask
before the host has, because the session it is given refuses.
import { detectPii } from '@pagecraft/ai';
const prepared = await detectPii.prepare(documents, {}, { engine });
show(prepared.grounding.text);
session.disclose();
const found = await detectPii.run(prepared, session, { engine });
found.findings; // [{ text: 'Priya Nair', kind: 'name', page: 2 }, …]
found.unverified; // findings whose text is not in the document as written
Each returns something structured rather than prose to parse:
| Tool | What it gives back |
|---|---|
summarize |
A summary, with citations naming the pages it came off |
chat |
An answer, with the same citations |
extractData |
The fields you named as JSON, and the ones the document did not carry |
detectPii |
Personal data by kind and page, separated into what was verified and what was not |
describeImages |
Alt text per picture, ready for alt-text |
translate |
The text in another language, page by page |
generateOutline |
Headings and pages, ready for bookmarks |
autoName |
A filename the document deserves |
Two are worth noticing. detectPii reports what it found and which of it it
could verify is in the document as written, so a name the model invented is set
aside rather than passed on. What survives feeds straight into redact, and
describeImages feeds alt-text, both in @pagecraft/core and both on the
device. The model suggests; the engine acts.
What this does not do
- It does not run a model. It speaks to one you chose, at an address you gave it.
- It does not keep a key. Keys live in the vault, encrypted with the user’s passphrase, and are lent per call.
- It does not send a document. It sends passages, after showing them. Pictures go only to the tool that is about pictures.
- It cannot reach an origin the build does not admit. In the browser the content security policy names the providers this build was configured with, and nothing else — so a compromised page could not exfiltrate to an address the policy has never heard of.
- It does not promise the model is right. A summary is a summary. Anything that changes a document goes through an operation, on the device, where you can see what it did.
Where to go next
- Architecture — how the guarantee is enforced, and how to check it.
- Getting started — the engine the AI tools hand work to.