Instructions for any agent working in this repo.
This repo follows the intent convention
— it was extracted from this very folder. The /intent skill is the law:
every session gets a file in intent/ with the prompts
verbatim and the author, topped up early, on every correction, and at the end;
reversals resolve into intent/SUMMARY.md immediately;
the pre-push gate refuses code with no intent beside it. Run /intent before
pushing, /intent resolve when the summary lags.
Repo-specific: the early history predates the rename — sessions before
15 August live under the parent directory and the old name, voxcall, in
~/.claude/history.jsonl and ~/.claude/projects/. Pass
--also <flattened-old-path> to the seed scanner if you ever re-mine.
Jan is logged into Telnyx, Cloudflare, Stripe and the rest in his own Chrome. When a step needs a dashboard — assigning a number to a TeXML application, flipping a setting no API covers, reading a rejection reason off an order — do it with the Chrome tools rather than handing back instructions. Deploys, portal clicks, DNS, account settings: assist, don't delegate back.
The rule is not "click anything". It is:
- Do it when the action is reversible, scoped to this project, and the intent is already clear from the conversation.
- Confirm first when it spends money, touches production billing in live mode, deletes something, or affects a surface outside this project.
- Say what you did, with the values you set, so it can be checked or undone.
Do not trigger alert()/confirm() dialogs — they freeze the extension. Screenshot
before and after anything non-obvious.
Signing in. wijnand@hyre.io via Google SSO is the identity for Resend
and Telnyx. It is not a Cloudflare account — Cloudflare is a separate login
under jan@wilmake.com, and the authenticated wrangler CLI is the way in
there, not the dashboard. If a dashboard shows a login wall, click through
"Continue with Google" and pick that account; the Chrome profile usually has a
live Google session, so this is a click, not a credential.
Stop and hand back the moment it asks for anything more: a typed password, a 2FA code, a recovery prompt, or a new OAuth consent screen granting a third-party app access. Those are the user's to complete, never yours.
Values you must not transcribe. DKIM keys, API keys, signing secrets — long opaque strings that fail silently when one character is wrong. Move them with the page's own copy button and paste into the destination field. Reading one off a screenshot and retyping it is how a DNS record ends up almost right.
Push to main directly. This is a solo project and that is the established
history; no branch, no PR, unless the change is genuinely risky enough to want
review.
Commits should match the intent they came from, not the order the files happened to change. One commit per decision the user made — if a session covered the paywall, the installer and a copy fix, that is three commits, and each message should explain why the change was wanted, not just what moved.
A phone line for a team and its coding agents: a shared number an agent calls when it hits a decision only a person can make, wired to the terminals the team runs. The morning call and the nightly paper are now just two of the branded skills built on top. Five parts:
worker/— Cloudflare Worker: telephony, phone verification, teams and invites, per-org pay-as-you-go billing, the routing that lands a call in a watching terminal, and the parked briefs the morning skill is placed from.cli/— thescreenlessCLI, published as a tarball the installer fetches.site/—src/is hand-written (pages, installer);public/is generated in full bybuild.shand gitignored. Ship withnpm run deployinsite/.skills/— the branded skills, one directory each sonpx skills add janwilmake/screenless(skills.sh) installs them into every coding agent:screenless/— the watcher, the core skill:/screenlessarmsscreenless watchso the team's incoming calls land in this terminal and the agent acts on them (APPLY.mdis the return leg for a call's decisions).call-when-afk/— turns blocking questions into phone calls while the user is away.morning-pr-review/— the optional nightly loop:screenless waitis its gate (not a scheduler, see its README), it builds the call brief and the weekly paper, and itspress/subdir is the PDF toolkit (collect.mjs, the chart library, the renderer) shipped inside the skill sonpx skills addinstalls it whole.
The assistant on the phone takes no action. It has no tools and no
credentials. It collects decisions and hangs up. The loop on the user's own
machine reads screenless transcript --json afterwards and is what merges,
comments, closes and deploys.
This is a product decision, not an implementation detail: the loop already has the user's MCPs, their logged-in browser and their Claude subscription, and a hosted service cannot have any of those without asking for credentials nobody should hand over. If you find yourself adding a tool to the assistant, or writing copy that implies the call changed something, you have broken the model.
Live calls keep the rule; only the timing changed (session 12). When you
ring in while your own terminal watches, the voice is GPT-Live with client
delegation: still no tools, but each request it cannot answer is handed to
screenless watch on your machine during the call, which runs it in a fork of
the Claude Code session it was armed in and sends back what to say. The work
still happens on your machine with your access. Because nobody is at the
keyboard, every fork passes a PreToolUse gate (screenless gate,
cli/src/live.ts) in the mode the Worker gave the call.
The line never trusts a caller id (session 16). A phone call to the line is
rejected as busy and the line rings the member's verified number straight
back, so the only person who can pick up is whoever holds that phone; a spoofed
ring-in can only ring the real owner. A WhatsApp call is tied to the account,
not the caller id, and is answered at once. Telnyx gives it no route of its
own: it is told from a phone call by arriving from Meta's SIP domain
(wa.meta.vc) with the number's WhatsApp id in Meta's headers
(viaWhatsApp). Both are
full: the fork may do what the session may, except read secrets. Only a test
ring (and the fork that briefs the voice) is restricted: reads and local work
in the project. This replaced a PIN keyed on the phone and STIR/SHAKEN
attestation (session 13); do not bring either back. WhatsApp messages follow
the same rule (chat.ts in both the Worker and the CLI): the line routes them
like a call, and the watcher answers each conversation in one fork of the
session, carried on with --resume until 30 minutes of quiet; the Worker only
takes messages Telnyx signed, and only sends.
Who may do what is the owner's to say, not ours (sessions 17 and 16). A
.agents/permissions.md in the watched repo defines levels ("modes") with a
gate (listen, read = restricted, full) and free-text rules, and holds no
people; the team page assigns each member a level by name (users.line_level,
orgs.line_default, logged in level_log), and a level a repo does not
define gets that repo's default. Watchers report their file's level names
on /watch/next (levels=), which is how the page knows them without
reading a repo. A legacy callers: block decides for an unassigned caller
and only narrows an assigned one; ~/.agents/permissions.md may only narrow. The gate is
enforced by the fork's PreToolUse gate and can only lower the call's mode;
the rules go into every request's prompt and onto the finished call. The
owner keeps full unless a line names their own email or phone. Teammates
it names above listen get live calls: the watcher sends their patterns as
run= on /watch/next, which only routes; the watcher resolves the caller
again from the identity the line verified. Screenless never grows its own
permission vocabulary: the file is shared with other agent tools.
- Comments explain why, at the altitude of the decision. The existing code is the reference for density — match it rather than adding narration.
- Secrets never go in
wrangler.jsonc.wrangler secret put NAME, and list the name in the comment block at the bottom of that file. - Billing is off when
STRIPE_SECRET_KEYis unset, sowrangler devand any pre-Stripe deploy stay usable. Keep it that way. - Never edit
site/public/. It is assembled fromsite/src/,skills/andcli/dist/, and rebuilt from empty on every deploy, so an edit there is work you will lose. This is why it is gitignored rather than merely documented — a rule with an exception is a rule nobody checks. - The CLI has no dependencies, and should stay that way — it is distributed as
a few plain
.jsfiles in a tarball, and every dependency added is a thing the installer has to fetch.