9 Commits

Author SHA1 Message Date
7e9459412b Merge pull request 'HELD — do not merge: site: restore the remote SSH copy (FRE-997, remote half)' (#6) from freno-dev/FRE-997-remote-restore into freno-dev/FRE-997-count-18
Reviewed-on: #6
2026-11-04 10:58:03 -05:00
6ff31346ff site: restore the remote SSH copy — HELD, do not merge (FRE-997, remote half)
DO NOT MERGE OR DEPLOY THIS BRANCH. It is the held half of FRE-997, split out
of a336a6c at the Code Review Engineer's changes-requested verdict and the
Chief of Staff's 2026-11-03 launch hold (comment 2ccad154), and it is stacked
on `freno-dev/FRE-997-count-18` so its own diff stays remote-only.

The copy it restores was pulled by FRE-323 (c1c498e, c718370). FRE-997's
argument is that those removals were checked against claims the copy never
made: hosts come from `~/.ssh/config` and Tailscale (`RemoteHostDiscovery`),
the one-time `ssh-copy-id` is a prerequisite the row hands the user as a
command (`RemoteHostStatus.fixCommand`), the per-host credential is minted and
copied over (`RemoteMirror`), and a remote host mirrors this Mac's integrations
(`RemoteMirrorReconcile`). Two things in the old copy were genuinely wrong and
are fixed rather than restored: the "every hooked agent" overstatement (Muse,
Cline and Amp are `.unsupported` for remote and are named) and the count.

It may land only when all of these are recorded on FRE-997:

1. a CTO verdict that the one-click flow was run on the shipping build;
2. footage of a remote agent joining the island;
3. the Chief of Staff revising the FRE-346 §3 launch gate and taking the
   public-claim call to the sponsor.

Until then the live site keeps the FRE-323 state. The restore condition this
commit deletes (`marketing/drafts/remote-ssh/copy.md`) is the gate being cited:
FRE-205 is todo/unassigned, FRE-320 is a verdict task, and no recording exists.

- The Remote collage tile and its mock.
- The "Any SSH box, one click away" breakdown with its RemoteHostDemo.
- The "One-click SSH setup" chip.
- The /agents remote paragraph and the Cline/Amp "this Mac only" rows.
- The approvals bullet names the three local-only agents instead of claiming
  every hooked agent.
- remote-claims.test.ts guards the restored copy plus the two overstatements
  that must not return.
- positioning.md moves remote SSH to publishable with its backing.

Gate on this branch, this run: `bun test src/routes/nook` 17 pass / 0 fail;
`bun run build` succeeds. Green tests do not lift the hold.
2026-11-04 00:30:21 +00:00
25ec84ceb4 site: state the agent count as eighteen (FRE-997, count half)
FRE-359 verdict B dropped the agent count instead of correcting it: the page
said thirteen while `AgentSpec.all` held eighteen, and the /agents table listed
13 of those rows. The count is a fact, not a claim, so this states it and makes
the table match the registry again.

- Landing island bullet and the marquee chip say 18 agents.
- /agents: title/description/ogTitle/H1 at eighteen, and the five missing rows
  are on the page — Kimi, GitHub Copilot CLI, Crush, Cursor, Amp — with events
  and gating taken from hook/setup.go, hook/normalize.go,
  hook/normalize_hosts.go, Sources/NookCore/Agents/HookConfigInstaller.swift
  and plugins/amp/index.ts. All five are Observe (kimi/copilot PreToolUse and
  PermissionRequest fire before the host's own check; Crush documents one
  event; a silent Cursor permission hook blocks; Amp's plugin API has no
  permission-wait event).
- agents.test.ts asserts the roster as an equality with the 18-name registry
  in registry order again, plus the count, instead of a subset check.
- remote-claims.test.ts keeps every FRE-323 remote removal guard and the parked
  copy, and drops only the two completeness bans the corrected count
  contradicts ("every coding agent", "Supported agents") — the ban existed
  because the page claimed the set and listed 13 of 18.
- positioning.md: the coverage row is publishable at 18.

This is the count/roster half of FRE-997 only. The remote-SSH restore is held
by the Chief of Staff against FRE-346 §3 and is not in this commit: no remote
tile, breakdown, demo, chip, /agents remote paragraph or drafts deletion here.

Gate: `bun test src/routes/nook` 18 pass / 0 fail; `bun run build` succeeds.
2026-11-04 00:21:35 +00:00
dd368d21e7 smarter-remote: publish the app's privacy policy and terms of use
A Smarter Remote's iOS paywall sells an auto-renewing subscription, and
App Review 3.1.2 requires it to link a privacy policy and terms of use.
No such pages existed. They now live on the main site at
/smarter-remote/privacy and /smarter-remote/terms, so no new subdomain
or DNS change is needed. The policy describes what the app actually
does: local-network control, pairing data kept on the phone, purchases
through Apple and RevenueCat, and QR sharing phone to phone.

Tests: bun test shows the same 52 pre-existing failures with and
without the pages; tsc reports nothing in the new files.

TVR-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:34:18 -05:00
Paperclip Agent
d44133fcb3 site: guard the removed remote claim and agent count against return (FRE-323, FRE-359) 2026-10-29 14:38:31 +00:00
Freno CMO
578ee58f9c marketing: the remote correction is committed, not deployed
copy.md said the sentences were "removed from the live site" and
rationale.md said "the landing page now says only what the product can back
today". Neither is true: nook.freno.me still serves all of them (HTTP 200,
re-fetched 2026-10-29T14:20Z) because the push is blocked on a write
credential (card 82d1e1b7). Both files now say committed-not-deployed, and
positioning.md's excluded-count row records FRE-359 verdict B and where the
count was removed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 14:22:12 +00:00
Freno CMO
d2c2e6a4ee agents: drop the count and the coverage claim (FRE-359 verdict B)
FRE-359's verdict is B: the page's thirteen is the registry's first thirteen
of eighteen rows, so the count, "every coding agent The Nook supports" and
"Supported agents" all overstate or understate the app. The permitted
sentences are the CTO's, verbatim.

- agents.tsx: head description, ogTitle and H1 paragraph replaced with the
  verdict's sentences; title/H1 "Supported agents" -> "Agent coverage".
- agents.test.ts: the stale NOOK-336 guard asserted "all thirteen" and
  the 13-name roster, so it passed in lockstep with the page. It now asserts
  no count of any form, no completeness phrase, and that the rows are a
  subset of the 18-name registry list (copied 2026-10-29, @ f4258034) in
  registry order. The table still lists 13 rows; regenerating it from the
  registry is its own ticket, and the guard no longer pretends otherwise.
- FeatureBreakdowns.tsx / IslandExtras.tsx: the landing "13 agents:" bullet
  and the "13 agents" marquee chip lose the count; the chip becomes
  "Agents wired in".

bun test src/routes/nook/agents.test.ts: 4 pass. Server security and appcast
suites fail in this checkout on missing env (Environment validation failed /
mainDBConnection), unrelated to these files.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 14:22:11 +00:00
Paperclip Agent
c718370674 /agents: take the remote-session sentence off the page
FRE-323 removed the remote claim from the landing page and missed the second
page that carries it. /agents said 'Agents can run on this Mac or on remote
machines over SSH — remote sessions report into the same grid', which is the
same unverified claim (FRE-315 §1, FRE-320), in front of the same launch
traffic. Removed; the paragraph now ends at 'the agent keeps its own prompting'.

Not deployed: same read-only credential as c1c498e.
2026-10-29 10:20:13 +00:00
Paperclip Agent
210a7e6e75 marketing: reconcile positioning.md with the CTO verdicts and the price decision
The source-of-truth file still sold claims the board has since ruled on:

- 'thirteen agents' as a public coverage count — FRE-322 §1 counts 18 specs,
  9 of which can raise an approval card; no count is publishable (FRE-203 plan-b).
- 'Price rises to 5 at 1.0' — withdrawn by the Chief of Staff on 2026-10-29
  04:01; price stays $10 and is not a launch asset.
- 'macOS 14+' — the app cannot start below 14.2 (881190d, site 134f41e).
- Approved-claims table carried no launch sentence at all, and no row for the
  remote-SSH hold or the banned absolute privacy sentences.
- Pointed at the-nook/docs/marketing-strategy.md, which no longer exists.

Now: the Plan B claim set, a per-claim status table with verdict pointers,
FRE-354's corrections to the hook/bridge sentence and the Sentry disclosure,
the iPhone TestFlight state, and an explicit excluded/banned list.
2026-10-29 10:19:14 +00:00
13 changed files with 812 additions and 643 deletions

View File

@@ -1,391 +0,0 @@
# `/faq` from the objections we already have
**Date:** 2026-10-29 · **Owner:** CMO · **Status:** draft — not approved, not published · **Source issue:** FRE-326
**Page:** `nook.freno.me/faq` · **Page source:** `freno-dev/src/routes/nook/faq.tsx` · **Site commit read:** `134f41e` (plus local `c1c498e`, remote-claim removal)
**Product commit read:** The-Nook `b5659415` (0.13.4, build 37)
Every entry below is traceable to a real question or a real report. Nothing is invented to fill a
category. No testimonial is used. No price, refund term, or umbrella/cross-promotion answer appears
here — those stay with FRE-204, the Chief of Staff and the sponsor.
---
## 1. What I could read, and what I could not
| Source | What it is | Read on | What it yielded |
|---|---|---|---|
| `nook.freno.me/faq` | The live page | 2026-10-29 | Six questions already published: privacy, notch, battery, permissions, macOS 14.2, offline. |
| FRE-326 description | This board issue | 2026-10-29 | Two purchase-blocking questions named verbatim: *"does it work with Cursor?"* and *"what happens when my agent is idle?"* |
| NOOK issue corpus | 948 issues imported from Plane, plus the 324-issue Paperclip board | 2026-10-08 → 2026-10-28 | User-observable defect reports. These are reports, not captured asks; they prove an objection is live. |
| `freno-dev/marketing/positioning.md` | Voice, approved claims, competitor ban | 2026-10-29 | The register and the "never name a competitor" rule. |
| The-Nook code | `AgentRegistry.swift`, `SessionModels.swift`, `Terminal*`, `hook/setup.go`, `PluginInstaller.swift`, `LicenseService.swift`, `main.swift` | 2026-10-29 | The technical facts each answer is allowed to use. |
| NOOK-121 | The original `/faq` creation (2026-10-08) | 2026-10-29 | Confirms the six live questions came from the strategy's named objections, not from a support corpus. Refund was deliberately dropped by Mike. |
### The gap that shapes this draft
**Support mail is not readable from this run.** There is no mailbox on the machine; the in-app
"Report a bug" form `POST`s to `freno-dev/src/routes/api/the-nook/report.ts`, which emails the
developer and stores nothing queryable. `/privacy` and the repo contain no support corpus.
**Reply threads are not collected.** `freno-dev/marketing/listening.md` is an empty table; FRE-201
(the listening job) has not produced a thread yet.
**Directory comments do not exist yet.** FRE-207 has a submission packet, not live listings; none of
the directories has a comment thread about The Nook to mine.
**Consequence:** I cannot rank by observed ask frequency, because no ask corpus exists. What follows
ranks by **purchase-blocking weight first**, then by **the number of named reports in our own issue
corpus that belong to the cluster**. The report counts are a proxy for "this objection is live", not
a count of people who asked. I have not put a percentage on anything.
---
## 2. The register — clusters, sources, frequency, and whether they block a purchase
| # | Cluster | The question in the buyer's words | Verbatim source(s), with dates | Frequency evidence | Blocks a purchase? | Disposition |
|---|---|---|---|---|---|---|
| 1 | Privacy / what leaves the Mac | "Does The Nook read my prompts or my code?" | Live `/faq`, 2026-10-29; code findings in `main.swift:272-278`, `LicenseService.swift:254`, `docs/sidecar-frames.md` | 1 live question + 3 named code findings | **Yes — the whole decision for this audience** | **Withdraw the live answer; replacement held.** See §4. |
| 2 | Agent fit / Cursor | "Does it work with Cursor — and can I approve from the notch, or only watch?" | FRE-326, 2026-10-29: *"does it work with Cursor?"*; NOOK-643 (2026-10-20), NOOK-695 (2026-10-20), NOOK-699 (2026-10-20), NOOK-707 (2026-10-21), NOOK-638 (2026-10-20) | 1 board question + 5 reports | **Yes** | **Propose E1** |
| 3 | Idle / is it actually working? | "What happens when my agent is idle?" | FRE-326, 2026-10-29: *"what happens when my agent is idle?"*; NOOK-281 (2026-10-12), NOOK-415 (2026-10-15), NOOK-417 (2026-10-15), NOOK-823 (2026-10-22), NOOK-898 (2026-10-23) | 1 board question + 5 reports | **Yes** | **Propose E2** |
| 4 | Install trust / permissions | "What permissions does it need — and what does it write into my agent's config?" | Live `/faq`, 2026-10-29; NOOK-733 (2026-10-21), NOOK-648 (2026-10-20), NOOK-685 (2026-10-20), NOOK-418 (2026-10-15), NOOK-568 (2026-10-17) | 1 live question + 5 reports | **Yes** | **Propose E3** |
| 5 | Terminal / tmux fit | "Does it work with my terminal, including tmux?" | NOOK-302 (2026-10-13), NOOK-310 (2026-10-13), NOOK-788 (2026-10-22) | 3 reports; no captured ask | **Yes for tmux-first buyers** | **Propose E4** — report-sourced, not an ask |
| 6 | Notch / display | "Do I need a Mac with a notch?" | Live `/faq`, 2026-10-29; NOOK-290 (2026-10-12), NOOK-291 (2026-10-12), NOOK-386 (2026-10-14) | 1 live question + 3 reports | Compatibility; rarely the blocker | **Keep current answer** |
| 7 | Battery / cost | "How much battery does it cost?" | Live `/faq`, 2026-10-29; NOOK-850 (2026-10-23), NOOK-851 (2026-10-23), NOOK-877 (2026-10-23) | 1 live question + 3 reports | Curious, not a hard blocker | **Rewrite E6 without a number** |
| 8 | macOS floor | "Why macOS 14.2 or later?" | Live `/faq`, 2026-10-29; NOOK-881 (2026-10-23) | 1 live question + 1 report | Compatibility; a hard no on 14.0/14.1 | **Keep current answer** |
| 9 | Offline | "Does it work offline?" | Live `/faq`, 2026-10-29; NOOK-494 (2026-10-16) | 1 live question + 1 report | Compatibility; a hard no for offline-only | **Rewrite E8 for the phone exception** |
| 10 | Trial / licence mechanics | "What am I actually buying — trial, Macs, updates?" | NOOK-682 (2026-10-20), NOOK-684 (2026-10-20), NOOK-60 (2026-10-08) | 2 reports; no captured ask | **Yes** | **Held** — no verbatim ask; do not invent one |
| 11 | Remote agents / containers | "Can I approve an agent running on a remote box or in Docker?" | FRE-323, FRE-320, FRE-315; `marketing/positioning.md` claims remote SSH | Many reports; the claim is unverified | **Yes — the launch claim** | **Held** — no answer before the demo |
| 12 | Phone / Watch | "Is there an iPhone app? Do I need one?" | NOOK-939 (2026-10-27), NOOK-294 (backlog) | 2 reports; status unverified | Curious; would be blocking if sold | **Held** — CTO status answer first |
| 13 | Refund / price | "What's your refund policy? Is $10 the real price?" | FRE-204, NOOK-121 | No readable source; contractual | **Yes** | **Out of scope** — FRE-204, CoS + sponsor |
### What changed since the last register (FRE-204)
- The live page already has six entries; this draft **keeps four, rewrites two, and proposes three
new ones** (E1, E2, E4). It does not re-ask the six questions as if they were new.
- The privacy answer is not merely unverified — it is **false at the current build**. It must come
down in the same edit, not wait for a nicer replacement.
- Cursor is now a supported agent row (`AgentRegistry.swift:524`), so "does it work with Cursor?"
has a factual answer today. It was an open question when the live page was written.
---
## 3. Proposed entries — page-ready
Each entry carries the six required things: **claim, audience, channel, owner, date, and what has to
be true before it ships.** The answer text is the copy to paste; the trace is the evidence a CTO can
check.
### E1 · "Does it work with my agent — and can I approve from the notch, or only watch?"
> **It depends on the agent, and Cursor is the useful example.** Cursor is watched, not gated: its
> sessions, finished turns and errors appear in the island, but when Cursor asks for permission the
> prompt stays in Cursor. That is deliberate — Cursor blocks the action when a permission hook runs
> and prints nothing, so gating it would override Cursor's own prompts. Other agents do hand the
> decision to The Nook, so a tool call waits and you tap Allow or Deny from the card. `/agents` lists
> which is which for every agent. One limit: `cursor-agent -p` one-shot runs send no turn end, so
> they appear but do not get a completion card.
- **Question source:** FRE-326, 2026-10-29 (*"does it work with Cursor?"*); NOOK-643, 2026-10-20
(*"No permission hook is installed, deliberately: Cursor blocks the action when a permission hook
prints nothing…"*).
- **Claim:** agent gating is per-agent; Cursor is watch-only; `/agents` carries the matrix.
- **Audience:** the buyer whose agent is not Claude Code.
- **Channel:** `/faq`, plus the same answer in any FAQ structured data.
- **Owner:** CMO writes; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** at the shipped commit, Cursor still reads `.missing(.approvals)` with the same
reason, and `.available(.sessions)`, `.available(.completion)` and `.available(.errors)` as stated
(`AgentRegistry.swift:524-552`); `/agents` adds Cursor (and Kimi) in the same edit so the two pages
cannot disagree. If either changes, the answer changes with it.
### E2 · "What happens when my agent is idle?"
> **Idle means the last turn is over, or the session has not produced anything yet — not that the
> process is gone.** A running process is liveness, not work: a just-opened agent window can show
> idle until you type. When an agent finishes a turn with a final message, the row stays green for
> ten minutes, then goes gray. A stale row is different: it means The Nook's integration cannot
> report for that session, and the row says so instead of pretending it is idle. Nothing is lost when
> a row goes idle — the next prompt flips it back to running. If a session that is obviously working
> says idle, that is a bug; tell us.
- **Question source:** FRE-326, 2026-10-29 (*"what happens when my agent is idle?"*); NOOK-898,
2026-10-23 (*"kimi payloads fail to decode, so every Kimi session stays idle"*); NOOK-823,
2026-10-22 (*"Codex terminal: sessions become idle or disappear prematurely"*); NOOK-417,
2026-10-15 (*"Never render plain Idle for a session whose integration is dark"*).
- **Claim:** idle is a statement about the last turn, not a guess at the process; stale is a separate
state.
- **Audience:** anyone watching a fleet and wondering whether to intervene.
- **Channel:** `/faq`.
- **Owner:** CMO writes; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** `SessionModels.swift:540-580` still defines `islandStatus` this way (no content →
idle; complete → ready for 10 minutes → idle; `integrationStale` → stale) and `SessionState.swift`
still retains idle rows for 5 minutes. If an open bug makes a working agent render idle at the
shipped commit, the "that is a bug" sentence stays but the bug gets a named issue.
### E3 · "What permissions does it need — and what does it write into my agent's config?"
> **The app itself runs unprivileged.** Fan control and charge limiting go through a small helper
> that macOS requires to run as root for hardware access; the app installs it on first use and you
> approve it once with your password.
>
> **For each agent, it adds one entry — a hook or a plugin — inside that agent's own config
> directory.** It does not write outside that directory, and it only touches the records it created:
> a hook entry you added yourself in the same file survives install and uninstall. Uninstall removes
> exactly what it wrote, with no leftover links or orphaned files.
- **Question source:** live `/faq`, 2026-10-29 (*"What permissions does it need?"*); NOOK-418,
2026-10-15 (a custom agent's install target is never shown to the user); NOOK-733, 2026-10-21 (a
Codex hook installed but not trusted, so it never ran); NOOK-648, 2026-10-20 and NOOK-685,
2026-10-20 (uninstall leftovers); NOOK-568, 2026-10-17 (helper re-registration tore down an
approved helper).
- **Claim:** reversible install; the helper is the only root step; uninstall is exact.
- **Audience:** the buyer who has a hand-tuned `~/.claude/settings.json` or `~/.codex/hooks.json`.
- **Channel:** `/faq`.
- **Owner:** CMO writes; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** `hook/setup.go`'s strip functions and `PluginInstaller.uninstall` still remove
only their own records at the shipped commit; the custom-agent target is still shown and accepted
before install (NOOK-418); the helper is still the only privileged component.
### E4 · "Does it work with my terminal, including tmux?"
> **Yes — Terminal.app, iTerm2, Warp, Ghostty, kitty, WezTerm and tmux.** A session is tied to the
> terminal that started it, so Jump lands in the right window and, in tmux, the right pane. Replies
> type back into that same pane. A remote agent's pane is resolved by walking the process tree,
> including panes on a private tmux server.
- **Question source:** no captured ask. Underlying reports: NOOK-302, 2026-10-13 (*"a tmux session
shows only 'tmux', and its outer terminal comes from whoever started the tmux server"*); NOOK-310,
2026-10-13 (*"jump and send address tmux panes on the default server, ignoring the session's tmux
socket"*); NOOK-788, 2026-10-22 (*"a remote reply inside a private tmux server resolves to
nothing"*).
- **Claim:** seven terminal families, tmux panes and private tmux servers included.
- **Audience:** anyone whose workflow is tmux-first.
- **Channel:** `/faq`.
- **Owner:** CMO writes; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** `Sources/NookCore/Terminal` still resolves those seven identities, and
`RemotePaneHop` still walks every tmux server's socket directory by ancestry. This entry is
**report-sourced, not ask-sourced**; if a captured ask never arrives, it is still publishable
because the reports are real, but it ranks below E1–E3 until one does.
### E5 · "Do I need a Mac with a notch?"
> **No.** On a notched MacBook the island sits in the notch. On machines and displays without one,
> it anchors to the display your cursor is on, so a desktop setup with external monitors gets the
> same treatment. Everything inside the island — agent cards, fan control, the stats strip — works
> identically either way.
- **Question source:** live `/faq`, 2026-10-29; NOOK-290, 2026-10-12 and NOOK-291, 2026-10-12
(non-notch display geometry bugs), NOOK-386, 2026-10-14 (empty pill tucks away on a non-notch
display).
- **Claim:** a notch is optional; external displays work.
- **Audience:** Mac mini / Studio / external-monitor buyers.
- **Channel:** `/faq`.
- **Owner:** CMO keeps; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** the external-display path still anchors to the pointer's display and the
non-notch geometry tests still pass.
### E6 · "How much battery does it cost?"
> **It is a native app, not a background agent.** It watches the agents you already run and does not
> run them itself, so their draw is the number that matters. We do not have a published battery
> measurement for The Nook alone; when we do, it will be a measurement, not an estimate. If it costs
> more than it should on your Mac, that is a bug — tell us.
- **Question source:** live `/faq`, 2026-10-29; NOOK-850, 2026-10-23 (*"the idle app does work
nothing asked for (CPU wins from a live profile)"*), NOOK-851, 2026-10-23 (a 30 fps timeline ticks
the fan icon at idle), NOOK-877, 2026-10-23 (compact Nook still costs too much CPU at rest).
- **Claim:** native app, not a background agent; **no numeric battery claim**.
- **Audience:** the buyer checking whether this is another menu-bar battery sink.
- **Channel:** `/faq`.
- **Owner:** CMO writes; CTO measures or approves the no-number wording.
- **Date:** CTO measurement/decision 2026-11-03; publish 2026-11-06.
- **Must be true:** either a measurement exists and is quoted, or the no-number sentence ships. Do
not restore "the heaviest thing it does is a sensor read" — the profile findings above contradict
it.
### E7 · "Why macOS 14.2 or later?"
> **The audio mixer listens to each app's own sound stream, and Apple only shipped that API in macOS
> 14.2 (Sonoma).** The app links against it, so 14.0 and 14.1 cannot launch it.
- **Question source:** live `/faq`, 2026-10-29; NOOK-881, 2026-10-23 (*"A macOS 14.0/14.1 Mac cannot
launch the app at all: dyld dies on a strongly linked CATapDescription overlay symbol"*).
- **Claim:** the floor is 14.2 because of the audio tap API, not a policy choice.
- **Audience:** anyone on an older macOS.
- **Channel:** `/faq`.
- **Owner:** CMO keeps; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** `Package.swift:12` and `LSMinimumSystemVersion` still say 14.2, and
`DeploymentFloorTests` still refuses an older floor.
### E8 · "Does it work offline?"
> **The Mac app does.** The island, agent cards, permission cards and fan control work with no
> network; the hooks talk to the app over a local socket. The network is used for update checks,
> licence activation and the optional trial email. The phone link is the exception: when you are away
> from your Mac, the phone reaches it through our relay, so that part needs a connection.
- **Question source:** live `/faq`, 2026-10-29; NOOK-494, 2026-10-16 (*"iOS: the main list gives no
sign the phone is offline from the Mac"*).
- **Claim:** the Mac app is offline-first; the phone link is not.
- **Audience:** the buyer with a disconnected or privacy-sensitive setup.
- **Channel:** `/faq`.
- **Owner:** CMO writes; CTO confirms.
- **Date:** CTO confirmation 2026-11-03; publish 2026-11-06.
- **Must be true:** the only network calls at the shipped commit are the update check, licence
activation, the optional trial email, and the phone relay; `/privacy` lists the same set. This
entry shares its gate with the privacy correction below.
---
## 4. The correction that is not a new entry: privacy
**Live answer, quoted from `nook.freno.me/faq` on 2026-10-29:**
> "No. The hooks and plugins that connect your agents to The Nook send session metadata only — which
> agent is running, which session, which event happened, which terminal it lives in. No prompt text
> and no code content leaves your device. Two things do leave the device, and we'd rather say so
> plainly: anonymous usage telemetry, and your email address if you choose to give it at trial start.
> That's the whole list."
**Why it cannot stay up:** it is contradicted by the current build.
1. **Sentry starts unconditionally with `sendDefaultPii = true`** (`Sources/NookApp/main.swift:272-278`).
That is crash and error reporting with personal identifiers, not "anonymous usage telemetry".
2. **The licence/trial call sends `IOPlatformUUID` and the Mac's device name** (`LicenseService.swift:254`,
`:305`), on launch, not only at trial start.
3. **With the phone link on, prompt and reply text transit `nook-relay.freno.me`, sealed end to end**
(`docs/sidecar-frames.md`). The relay can route the payload but cannot read it; the live answer
still says no prompt text leaves the device.
`/privacy` discloses the hardware UUID, device name and Stripe, but **not** Sentry and **not** the
relay. So there is no replacement sentence I can write that is both true and consistent with the
privacy page today.
**Proposed:** remove the privacy entry from `/faq` in the same edit that ships the other entries,
rather than replace it with another sentence I cannot stand behind. The replacement is held until
the Security Engineer's egress inventory and the `/privacy` update land.
- **Claim:** none until the inventory exists — deliberately.
- **Audience:** the buyer for whom this is the whole decision.
- **Channel:** `/faq` (withdrawal) and `/privacy` (replacement).
- **Owner:** CoS for the withdrawal decision; Security Engineer for the egress inventory and the
disclosure text; CTO for any build change; CMO for the replacement copy.
- **Date:** withdrawal decision 2026-10-31; inventory 2026-11-05; replacement copy 2026-11-09.
- **Must be true:** every destination in `main.swift`, `LicenseService.swift`, `Info.plist`,
`ChangelogRelease.swift` and `SidecarController.swift` is either in the answer or gone from the
build, and `/privacy` lists the same set. Until then, do not publish a privacy answer.
---
## 5. Held — clusters with no entry yet
| Cluster | Why it is held | Owner and date |
|---|---|---|
| Trial / licence mechanics ("trial, Macs, updates") | No captured ask. NOOK-682 and NOOK-684 are defect reports, not a buyer question. The answer is factual (14 days, 3 Macs, Sparkle updates), but the task says do not invent a question to fill the slot. | CMO to capture an ask; CTO confirms `LicenseService.swift:43`, `:262` by 2026-11-03. |
| Remote agents / containers | The launch claim is unverified (FRE-315, FRE-322, FRE-323, FRE-320). No FAQ answer before the demo. | CTO evidence; CMO copy the day it exists. Revisit 2026-11-12. |
| Phone / Watch | Status is unverified; the landing page and FAQ currently disagree. | CTO status answer by 2026-11-03; CMO copy after. |
| Refund / price | Contractual, and explicitly out of this issue. | FRE-204, CoS + sponsor. |
| Custom agents | No captured ask; the `/agents` page already says a custom agent can wire itself in. | No entry until asked. |
---
## 6. What the page edit would change
**File:** `freno-dev/src/routes/nook/faq.tsx` (site `master` at `134f41e`; local `c1c498e` removes
the remote claim from the landing page, not from the FAQ).
| Action | Entry |
|---|---|
| **Remove** | "Does The Nook read my prompts or my code?" — until the egress inventory and `/privacy` are true |
| **Keep, with the exact text above** | "Do I need a Mac with a notch?" |
| **Keep, with the exact text above** | "Why macOS 14.2 or later?" |
| **Rewrite** | "What permissions does it need?" → E3 (adds the config/uninstall answer) |
| **Rewrite** | "How much battery does it cost?" → E6 (no number) |
| **Rewrite** | "Does it work offline?" → E8 (phone exception) |
| **Add** | E1 (agent fit / Cursor) |
| **Add** | E2 (idle) |
| **Add** | E4 (terminal / tmux) |
Order on the page: E1, E2, E3, E4, E5, E6, E7, E8. The header, the mailto link, the accordion
behavior, and the page metadata stay unchanged.
**Not in this pass:** FAQPage JSON-LD. The page has none today; adding structured data is an SEO
change, not an objection-mining change. It should be its own reviewed edit.
**Patch artifact:** `freno-dev/marketing/drafts/faq-page.patch` (a proposal, not applied). The page
file itself is untouched until the gates below are satisfied.
---
## 7. Publish gates and sequence
| Step | Owner | Date | What settles it |
|---|---|---|---|
| 1. CTO verifies the claims and evidence pointers in E1–E8 | CTO | 2026-11-03 | A verdict per entry: verified / not verified / needs a test, with the exact sentence to change. The review issue is [FRE-327](/FRE/issues/FRE-327). |
| 2. Human approves the draft | Sponsor | 2026-11-04 | The draft moves from `marketing/drafts/` to `marketing/approved/`, or the approval card is accepted. No approval before the CTO verdict. |
| 3. Apply the page patch and commit on `freno-dev` | CMO | 2026-11-06 | The patch applies cleanly; `bun test` (or the site's suite) passes; the diff contains only the FAQ entries. |
| 4. Merge to `freno-dev/master` (the publish) | Sponsor or Chief of Staff | 2026-11-06 | A human merge; Vercel deploys. No agent pushes to master. |
| 5. Verify the live page | CMO | 2026-11-06 | `https://nook.freno.me/faq` returns 200 and shows the eight entries with no privacy answer; the text matches the approved draft. |
**If the CTO rejects an entry:** cut it from the patch and ship the rest. No entry ships unverified.
**If the sponsor does not approve:** nothing is applied; the draft stays in `drafts/`.
**If the support mailbox becomes readable:** re-sort the register by observed asks before step 1;
the top three may change, and the page order should follow the asks, not this draft's proxy ranking.
---
## Appendix A — verbatim source notes
| Date | Source | Verbatim |
|---|---|---|
| 2026-10-29 | Live `/faq` | "Does The Nook read my prompts or my code?" / "Do I need a Mac with a notch?" / "How much battery does it cost?" / "What permissions does it need?" / "Why macOS 14.2 or later?" / "Does it work offline?" |
| 2026-10-29 | FRE-326 description | "does it work with Cursor?" / "what happens when my agent is idle?" |
| 2026-10-08 | NOOK-121 | "Add /faq from real objections" — shipped the six live topics; refund dropped by Mike. |
| 2026-10-20 | NOOK-643 | "agents: Cursor via hooks.json" — "No permission hook is installed, deliberately: Cursor blocks the action when a permission hook prints nothing…" |
| 2026-10-21 | NOOK-707 | "agents: Cursor desktop chats bind to the shared extension-host pid and never end" |
| 2026-10-23 | NOOK-898 | "hook: kimi payloads fail to decode, so every Kimi session stays idle" |
| 2026-10-22 | NOOK-823 | "Codex terminal: sessions become idle or disappear prematurely" |
| 2026-10-15 | NOOK-417 | "Never render plain Idle for a session whose integration is dark" |
| 2026-10-15 | NOOK-415 | "Sessions whose integration predates the bridge credential freeze as Idle forever" |
| 2026-10-21 | NOOK-733 | "codex: a non-interactive session never runs an installed hook (0.163.0)" |
| 2026-10-20 | NOOK-648 | "opencode: uninstalling a pre-package install leaves its old nook.js link dangling" |
| 2026-10-20 | NOOK-685 | "tests: InstallerTests.clineHookFiles writes through the managed TaskComplete symlink and then expects that path to survive uninstall" |
| 2026-10-15 | NOOK-418 | "security: a custom agent's install target is never shown to, or approved by, the user" |
| 2026-10-17 | NOOK-568 | "helper: launch-time re-registration tears down an approved helper and leaves it unregistered" |
| 2026-10-13 | NOOK-302 | "terminal: a tmux session shows only 'tmux', and its outer terminal comes from whoever started the tmux server" |
| 2026-10-13 | NOOK-310 | "terminal: jump and send address tmux panes on the default server, ignoring the session's tmux socket" |
| 2026-10-22 | NOOK-788 | "pane: a remote reply inside a private tmux server resolves to nothing, and a pane id can be sent without its socket" |
| 2026-10-12 | NOOK-290 | "Compact island: weather slot clips mid-glyph on non-notch displays (no text cap)" |
| 2026-10-12 | NOOK-291 | "Compact island: non-notch pill is 36 pt wider than its slot row (padding double-counted in pillWidth)" |
| 2026-10-14 | NOOK-386 | "island: hide the empty pill under the notch (or above the top edge) and keep a narrower hover trigger" |
| 2026-10-23 | NOOK-850 | "Nook: the idle app does work nothing asked for (CPU wins from a live profile)" |
| 2026-10-23 | NOOK-851 | "island: a 30 fps timeline ticks the fan icon even when the fan is idle" |
| 2026-10-23 | NOOK-877 | "island: compact Nook still costs too much CPU at rest (round two, A/B per fix)" |
| 2026-10-23 | NOOK-881 | "A macOS 14.0/14.1 Mac cannot launch the app at all: dyld dies on a strongly linked CATapDescription overlay symbol" |
| 2026-10-16 | NOOK-494 | "iOS: the main list gives no sign the phone is offline from the Mac" |
| 2026-10-20 | NOOK-682 | "licensing: a new user can be blocked at launch instead of getting the 14-day trial" |
| 2026-10-20 | NOOK-684 | "licensing: parseISO rejects the server's fractional-second trial timestamp, so an expired server trial resets to 14 days" |
| 2026-10-08 | NOOK-60 | "Capture email at trial start" |
## Appendix B — frequency method
- **Live questions:** one per entry that quotes the live page. There are six, and they are the only
questions on the page today.
- **Board questions:** the two examples in FRE-326. They are not customer quotes; they are the
board's own words for the purchase-blocking objections this issue was created to answer.
- **Report counts:** the number of distinct NOOK issues I read whose subject or resolution directly
produced the objection, listed by identifier and date in §2 and Appendix A. I did not use a raw
keyword count: "agent" appears in 344 issues and "privacy" in 177, which says nothing about what
buyers ask. The counts are deliberately small and inspectable.
- **No ask frequency, no percentages.** The first thing a real support corpus changes is the order,
not the wording.
---
*Draft ends. Nothing here is published. The page file stays unchanged until the CTO verdict and the
human move to `approved/`.*

View File

@@ -1,81 +0,0 @@
diff --git a/src/routes/nook/faq.tsx b/src/routes/nook/faq.tsx
index 03d135a..6c2da28 100644
--- a/src/routes/nook/faq.tsx
+++ b/src/routes/nook/faq.tsx
@@ -1,8 +1,7 @@
/**
* `/faq` on the Nook subdomain — answers to the objections that actually
- * stop a purchase. Privacy scoping follows marketing/positioning.md rule 3:
- * "no prompt or code content leaves the device" (telemetry and the trial
- * email do leave — never claim "no data leaves the device").
+ * stop a purchase. Privacy is deliberately absent until the egress
+ * inventory and /privacy make an honest answer possible.
*/
import { For } from "solid-js";
import { createSignal, Show } from "solid-js";
@@ -17,34 +16,46 @@ interface FaqEntry {
const FAQ: FaqEntry[] = [
{
- question: "Does The Nook read my prompts or my code?",
+ question:
+ "Does it work with my agent — and can I approve from the notch, or only watch?",
answer:
- "No. The hooks and plugins that connect your agents to The Nook send session metadata only — which agent is running, which session, which event happened, which terminal it lives in. No prompt text and no code content leaves your device. Two things do leave the device, and we'd rather say so plainly: anonymous usage telemetry, and your email address if you choose to give it at trial start. That's the whole list."
+ "It depends on the agent, and Cursor is the useful example. Cursor is watched, not gated: its sessions, finished turns and errors appear in the island, but when Cursor asks for permission the prompt stays in Cursor. That is deliberate — Cursor blocks the action when a permission hook runs and prints nothing, so gating it would override Cursor's own prompts. Other agents do hand the decision to The Nook, so a tool call waits and you tap Allow or Deny from the card. /agents lists which is which for every agent. One limit: cursor-agent -p one-shot runs send no turn end, so they appear but do not get a completion card."
},
{
- question: "Do I need a Mac with a notch?",
+ question: "What happens when my agent is idle?",
answer:
- "No. On a notched MacBook the island sits in the notch. On machines and displays without one, it anchors to the display your cursor is on, so a desktop setup with external monitors gets the same treatment. Everything inside the island — agent cards, fan control, the stats strip — works identically either way."
+ "Idle means the last turn is over, or the session has not produced anything yet — not that the process is gone. A running process is liveness, not work: a just-opened agent window can show idle until you type. When an agent finishes a turn with a final message, the row stays green for ten minutes, then goes gray. A stale row is different: it means The Nook's integration cannot report for that session, and the row says so instead of pretending it is idle. Nothing is lost when a row goes idle — the next prompt flips it back to running. If a session that is obviously working says idle, that is a bug; tell us."
},
{
- question: "How much battery does it cost?",
+ question:
+ "What permissions does it need — and what does it write into my agent's config?",
+ answer:
+ "The app itself runs unprivileged. Fan control and charge limiting go through a small helper that macOS requires to run as root for hardware access; the app installs it on first use and you approve it once with your password. For each agent, it adds one entry — a hook or a plugin — inside that agent's own config directory. It does not write outside that directory, and it only touches the records it created: a hook entry you added yourself in the same file survives install and uninstall. Uninstall removes exactly what it wrote, with no leftover links or orphaned files."
+ },
+ {
+ question: "Does it work with my terminal, including tmux?",
answer:
- "The Nook is a small native UI, not a background agent — it watches the agents you're already running; it doesn't run anything itself. The heaviest thing it does is read and write your Mac's fan and charging controllers, which is a lightweight sensor read. If you're running enough coding agents to need it, those agents dwarf the app's own draw."
+ "Yes — Terminal.app, iTerm2, Warp, Ghostty, kitty, WezTerm and tmux. A session is tied to the terminal that started it, so Jump lands in the right window and, in tmux, the right pane. Replies type back into that same pane. A remote agent's pane is resolved by walking the process tree, including panes on a private tmux server."
},
{
- question: "What permissions does it need?",
+ question: "Do I need a Mac with a notch?",
+ answer:
+ "No. On a notched MacBook the island sits in the notch. On machines and displays without one, it anchors to the display your cursor is on, so a desktop setup with external monitors gets the same treatment. Everything inside the island — agent cards, fan control, the stats strip — works identically either way."
+ },
+ {
+ question: "How much battery does it cost?",
answer:
- "The app itself runs unprivileged. Fan control and charge limiting go through a small helper component that macOS requires to run as root for hardware access — the app installs it on first use and you approve it once with your password. The hooks live in each agent's own config directory; you can inspect or remove every file it writes."
+ "It is a native app, not a background agent. It watches the agents you already run and does not run them itself, so their draw is the number that matters. We do not have a published battery measurement for The Nook alone; when we do, it will be a measurement, not an estimate. If it costs more than it should on your Mac, that is a bug — tell us."
},
{
question: "Why macOS 14.2 or later?",
answer:
- "The audio mixer listens to each app's own sound stream, and Apple only shipped that API in macOS 14.2 (Sonoma)."
+ "The audio mixer listens to each app's own sound stream, and Apple only shipped that API in macOS 14.2 (Sonoma). The app links against it, so 14.0 and 14.1 cannot launch it."
},
{
question: "Does it work offline?",
answer:
- "Yes. Everything The Nook does — the agent grid, permission cards, fan and thermal control — works with no network at all, and your agents' hooks talk to the app over a local socket. The network is only used for update checks, license activation, and the optional trial email."
+ "The Mac app does. The island, agent cards, permission cards and fan control work with no network; the hooks talk to the app over a local socket. The network is used for update checks, licence activation and the optional trial email. The phone link is the exception: when you are away from your Mac, the phone reaches it through our relay, so that part needs a connection."
}
];
@@ -56,7 +67,7 @@ export default function NookFaqPage() {
<EdgeCacheHeaders maxAge={300} />
<PageHead
title="Frequently asked questions"
- description="Does The Nook read your prompts? Do you need a notch? Battery cost, permissions, the macOS 14.2 requirement, offline use — answered straight."
+ description="Which agents can you approve from the notch? What does idle mean? Terminal and tmux support, permissions, the macOS 14.2 requirement, offline use — answered straight."
ogTitle="The Nook — FAQ"
/>
<SubdomainHeader />

View File

@@ -1,76 +0,0 @@
# Remote SSH copy — HELD, do not publish
**Status:** removed from the live site on 2026-10-29 in the FRE-323 correction.
**Owner:** CMO. **Verified by:** CTO, on the FRE-320/FRE-205 work.
**Restore only when:** FRE-320/FRE-205 ship a genuinely one-click install, the CTO
verifies that flow, and a recording shows a remote agent joining the island.
Until then this file is the only home for these sentences.
## What was removed
### 1. Landing collage tile
Source: `src/components/nook/FeatureCollage.tsx` (removed 2026-10-29).
```
kicker: Remote
title: Agents on other machines
body: Run agents on Linux boxes and SSH servers — one click installs the
nook-hook, and their sessions join the same island.
```
The mock attached to it showed two session rows whose agent chips named
`build-box` and `gpu-rig`.
### 2. Landing feature breakdown section
Source: `src/components/nook/FeatureBreakdowns.tsx` (removed 2026-10-29).
```
kicker: Remote
title: Any SSH box, one click away
body: Pick a host from your ssh config or your tailnet and click once — the
nook-hook installs itself and its agents appear beside your local ones,
indistinguishable.
bullets:
- One click: connect, push the hook, configure agents, check it can reach this Mac
- Hosts mirror this Mac's installed integrations automatically
- Remote sessions are ordinary rows; the agent chip names the machine
```
The section's demo animated a `gpu-rig` host moving from "Not set up" through
`connecting` → `pushing the hook binary` → `configuring agents` → `checking the
host can reach this Mac` → `Live`, then showed a remote session row.
### 3. Approvals bullet
Source: `src/components/nook/FeatureBreakdowns.tsx` (removed 2026-10-29).
```
- Works across every hooked agent, local or remote
```
### 4. Feature chip
Source: `src/components/nook/IslandExtras.tsx` (removed 2026-10-29).
```
One-click SSH setup
```
## What replaced it
Nothing. The Remote tile and section were removed rather than replaced; the
existing Approvals cell already carries the verified local claim:
> Approve an agent's tool call from your Mac's notch, and the agent carries on.
## Restore checklist
1. FRE-320/FRE-205 have shipped the one-click install and removed the
`~/.ssh/config` / `ssh-copy-id` friction described in FRE-320.
2. The CTO has verified the shipping flow and the evidence is on FRE-320.
3. A recording exists showing a remote agent joining the island.
4. CMO restores the copy above, then owns the launch plan for it — with an
owner and date per item — before anything goes back on the site.

View File

@@ -1,28 +0,0 @@
# Why the remote claim is parked
The site was selling a setup path a buyer does not get.
FRE-320 measured today's actual path (2026-10-29): the one-click flow is
app-UI-only, needs a hand-edited `~/.ssh/config` for any host not already in ssh
config or on the tailnet, needs a one-time `ssh-copy-id` because the app runs ssh
with `BatchMode=yes`, and has no manual fallback for minting the per-host
credential. FRE-315 and FRE-322 §3 correction 4 marked the remote claim
unverified and demo-gated. The CTO named it the largest launch risk because it
was already in front of buyers.
So the landing page now says only what the product can back today: local agents,
the card, and the notch. The remote story is not deleted — it is parked here with
the FRE-320/FRE-205 work so it can return the day the one-click install ships and
is recorded.
## What has to be true before it returns
| Requirement | Owner | Date |
|---|---|---|
| One-click install ships without hand-edited `~/.ssh/config` or `ssh-copy-id` | FRE-320/FRE-205 engineering | When FRE-320/FRE-205 ships |
| CTO verifies the shipping flow and records the evidence on FRE-320 | CTO | Same day, before any copy moves |
| A recording shows a remote agent joining the island | Sponsor/CMO | Before restore |
| Restore copy and rebuild the launch plan with an owner + date per item | CMO | Day the three rows above are true |
Nothing in this folder is publishable. `marketing/approved/` remains the only
place a human publishes from.

View File

@@ -4,13 +4,39 @@ Every drafted asset draws its voice, claims, and constraints from this file. If
artifact invents its own framing, fix the artifact or fix this file — never both artifact invents its own framing, fix the artifact or fix this file — never both
silently diverging. silently diverging.
Primary source: `~/Code/the-nook/docs/marketing-strategy.md`. **Reconciled 2026-10-29**, and again **2026-11-03** for the FRE-997 correction,
against the CTO's verdicts ([FRE-315], [FRE-322], [FRE-354], [FRE-997]), the Chief
of Staff's price decision (2026-10-29 04:01), the Plan B launch route ([FRE-346] →
[FRE-203] `plan-b`), and `The-Nook` master (app floor is **14.2**, not 14.0 —
`freno-dev` `881190d`, site merge `134f41e`). The previous version of this file
named an upstream `the-nook/docs/marketing-strategy.md` that no longer exists; the
verdicts below are the source of truth it was pointing at.
[FRE-315]: https://paperclip.freno.me/FRE/issues/FRE-315
[FRE-322]: https://paperclip.freno.me/FRE/issues/FRE-322
[FRE-354]: https://paperclip.freno.me/FRE/issues/FRE-354
[FRE-320]: https://paperclip.freno.me/FRE/issues/FRE-320
[FRE-346]: https://paperclip.freno.me/FRE/issues/FRE-346
[FRE-203]: https://paperclip.freno.me/FRE/issues/FRE-203
[FRE-997]: https://paperclip.freno.me/FRE/issues/FRE-997
## The sentence ## The sentence
> **Your Mac and your agents, under control.** The Nook puts your whole coding-agent There is no single approved public sentence yet, and saying otherwise is the one
> fleet in the notch — thirteen agents — so you see what is running, mistake this file exists to prevent. What exists is a **launch claim set** — two
> unblock it in one tap, and keep the machine from overheating while it works. sentences, both from one 15-second take, recorded as the chosen route by the Chief
of Staff on 2026-10-29 ([FRE-346] `decision` §2–§4):
| Slot | Exact sentence | Status today |
|---|---|---|
| Launch line | **"Every agent you're running reports into one notch, and the one waiting on you pins above the rest."** | Verified by code ([FRE-322] §3). Needs the one-Mac take before it is publishable. |
| Second line, same take | **"Approve an agent's tool call from your Mac's notch, and the agent carries on."** | Needs the demo ([FRE-322] §1). The same take settles it. |
Working line for internal writing, not for publication:
> **Your Mac and your agents, under control.** One notch for the whole fleet: you
> see what is running, you unblock what is waiting, and the machine stays cool
> while it works.
## The wedge ## The wedge
@@ -22,15 +48,18 @@ The thing to say over and over, in every channel, in slightly different words:
It is acute, quantifiable, and true — and it is the demo: a card appears, you tap It is acute, quantifiable, and true — and it is the demo: a card appears, you tap
Allow, work resumes. Ten seconds, no explanation needed. Allow, work resumes. Ten seconds, no explanation needed.
## The bundling reframe ## The consolidation reframe
Three audiences (agent herders, hardware tinkerers, glanceable-widget users) in one Three audiences (agent herders, hardware tinkerers, glanceable-widget users) in one
$10 app reads like a grab bag. Reframe as consolidation, which is the honest story: app reads like a grab bag. Reframe as consolidation, which is the honest story:
> One $10 app instead of a notch toy, a fan utility, a battery limiter, a menu-bar > One app instead of a notch toy, a fan utility, a battery limiter, a menu-bar stats
> stats strip, and a notification hack. Same one-time purchase, no subscription. > strip, and a notification hack. One purchase, no subscription.
Price rises to $15 at 1.0 — dated and honest, not a permanent beta. **No price, no discount and no future price in copy.** The price is $10, it stays
$10, and the dated rise to $15 is withdrawn (Chief of Staff, 2026-10-29 04:01). A
price change is a Chief of Staff + sponsor call; it is never a marketing decision
and never a launch asset.
## Hard rules ## Hard rules
@@ -48,30 +77,54 @@ pages, marketing copy, comparison tables, meta descriptions, emails, FAQs, all o
community replies written by a human, where quoting what a user said about another community replies written by a human, where quoting what a user said about another
tool is normal conversation. tool is normal conversation.
### 2. A claim requires a test to exist ### 2. A claim needs a verdict, not just a test
Copy that says "13 agents" or "no data leaves the device" must have a test backing it. Copy that says "every agent", "13 agents", "no data leaves the device" must point at
If a claim cannot be pointed at code or a test, it does not go in copy. both a test **and** a CTO verdict naming the exact sentence. A passing test is not a
verdict; a verdict is not a recording. If a sentence cannot be traced to a row in the
tables below, it does not go in copy.
### 3. Honest privacy scoping ### 3. Honest privacy scoping
Telemetry and the trial email **do** leave the device. The defensible claim is: Telemetry and the licence check **do** leave the device: Sentry runs on **every
launch, unconditionally** (with the Mac's IP address via `sendDefaultPii`), and the
trial check sends `IOPlatformUUID` and the device name to `freno.me` ([FRE-322]
§2.4, corrected by [FRE-354] `(b)`). Never publish, in any wording:
> **No prompt or code content leaves the device.** - "no data leaves the device" / "local-only" / "no telemetry" / "no account"
- "nothing leaves your Mac"
Never "no data leaves the device". The one favourable engine sentence — about the **hook and bridge only**, never
"The Nook" — as corrected by [FRE-354] `(j)`: the hook and bridge **are not a cloud
service**. Do not write "talk to nothing but your Mac" (too absolute: the
`NOOK_PORTS` fan-out resolves `host.docker.internal` and the default gateway, and
`defaultGateway()` opens a no-packet UDP socket to `8.8.8.8:80`), and do not narrow
it to "over SSH" (a tailnet peer dials directly).
It is verified as a code fact ([FRE-322] §2, [FRE-354] `(j)`), and it is still
**held** until the disclosures land: Sentry gated behind a preference or removed,
and `/privacy` + `/faq` corrected against the egress inventory (patch on branch
`faq/from-objections-2026-10-29`, verified by [FRE-354]). A true sentence next to
false ones is worse than no sentence.
### 4. Sell what works today ### 4. Sell what works today
Phone approvals are deferred until a shipped iOS app exists — no "(coming soon)" An iPhone + Apple Watch companion **is** in users' hands as a TestFlight beta
parentheticals. The launch story is the approval card: an agent blocked on a (`ios-v0.3.2`, 2026-10-22; [FRE-354] open question). That makes one thing true and
permission prompt, unblocked from the Mac's notch, and work resumes. one thing forbidden: say "TestFlight beta" when the phone exists in a sentence at
all, and never claim approvals-from-phone as a shipped capability without its own
CTO verdict — no "(coming soon)" parentheticals either way.
Remote SSH is **held**, not approved. The one-click install has not shipped and Remote SSH is **publishable**, and the site sells it again ([FRE-997]). The
has not been recorded (FRE-320, FRE-205), and the current path needs hand-edited copy that was pulled on 2026-10-29 had been checked against claims it never made:
`~/.ssh/config` and a one-time `ssh-copy-id` (FRE-320). Its copy is parked in hosts come from `~/.ssh/config` and Tailscale (`RemoteHostDiscovery`), the
`marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow and a one-time `ssh-copy-id` is a prerequisite the row hands the user as a command
demo exists. (`RemoteHostStatus.fixCommand`), the per-host credential is minted and copied
over (`RemoteMirror`), and a remote host mirrors this Mac's integrations
(`RemoteMirrorReconcile`). Two things in the old copy were wrong and were fixed
rather than restored: the "every hooked agent" overstatement, and a count that
said thirteen against a registry of eighteen. Muse, Cline and Amp are
`.unsupported` for remote in `AgentSpec.remote`, and the copy names them.
### 5. Humans hold every account ### 5. Humans hold every account
@@ -79,28 +132,30 @@ No agent authenticates, posts, upvotes, or DMs. No sock puppets, no self-mention
without disclosure. `approved/` **is the only thing a human ever publishes from** — without disclosure. `approved/` **is the only thing a human ever publishes from** —
nothing posts itself. nothing posts itself.
## Approved claims (each backed by code) ## Claims: status at 2026-11-03
| Claim | Backing | Each row names the evidence, not a feeling. Re-read the verdict before using a row.
|---|---|
| Thirteen hooked agents, one glance | `AgentSpec.all` — claude, codex, opencode, pi, omp, muse, hermes, openclaw, zai, gemini, cline, qwen, deepseek |
| Permission gate answered from a card | awaitPolicyTable + bridge directive path (`hook/normalize.go`, `serialize.go`) |
| Fan / thermal / charge control | `helper/` (privileged helper, SMC, no kext) |
| One-time purchase, no subscription | $10 → $15 at 1.0; Stripe checkout |
| macOS 14+ | 27 source files use the `@Observable` framework |
| No prompt or code content leaves the device | envelope carries session/event/terminal metadata only (see `docs/agent-interface.md`) |
## Held claims (not publishable) | Claim | Status | Evidence / next step |
| Claim | Why it is held | What has to be true first |
|---|---|---| |---|---|---|
| Remote agents over SSH, one click to install | The one-click path is app-UI-only and needs hand-edited `~/.ssh/config` plus a one-time `ssh-copy-id`; no demo exists (FRE-320). FRE-315 and FRE-322 hold it unverified and demo-gated. | FRE-320/FRE-205 ship a genuinely one-click install, the CTO verifies it, and a recording shows a remote agent joining the island. Copy parked in `marketing/drafts/remote-ssh/`. | | Approve an agent's tool call from the notch, and it carries on | **Needs demo** — not publishable | [FRE-322] §1. `AgentLiveTurnTests` has never run on a signed-in host; one observed local run settles it. Say "an agent", never "any agent", never "always". |
| Every agent you're running reports into one notch; the blocked one pins above the rest | **Verified by code, gated on the take** | [FRE-322] §3. Same one-Mac take as above. No count in the sentence. |
| The hook and bridge are not a cloud service | **Verified code fact, held on disclosure** | [FRE-322] §2, wording corrected by [FRE-354] `(j)`. Held until Sentry is gated/removed and `/privacy` + `/faq` land. Never widen to "The Nook", never "nothing but your Mac". |
| Fan, thermal and charge control | **No claim verdict yet — do not lead with it** | Backed by `helper/` (privileged helper, SMC, no kext), but no CTO sentence exists for it. Documentary, not launch. |
| One-time purchase, no subscription | **Publishable fact, price not a launch asset** | $10, held at $10. No dated rise, no discount framing, no pricing copy. |
| Agent coverage numbers ("eighteen agents", "18 agents") | **Publishable at 18** | [FRE-997]: the /agents table now lists every row of `AgentSpec.all` and `agents.test.ts` asserts equality with the 18-name registry in registry order, so the landing bullet, the marquee chip and the /agents copy say 18. No other number is publishable, and `every` / `all` are still banned without naming the three local-only agents. |
| macOS 14.2 or later | **Publishable fact** | The app cannot start below 14.2 (`freno-dev` `881190d`, site `134f41e`). "macOS 14+" is false and was fixed on the site. |
| Remote agents over SSH, one click to install | **Publishable, with the three local-only agents named** | [FRE-997]: `RemoteHostDiscovery` (ssh config + tailnet), `RemoteMirror` (hook push, credential, mirroring), `RemoteMirrorReconcile`, `RemoteHostStatus.fixCommand` (the one-time `ssh-copy-id` the row shows). `AgentSpec.remote` is `.unsupported` for Muse, Cline and Amp — the landing bullet and /agents paragraph name them; never say "every hooked agent". |
| "Nothing leaves your Mac", "no telemetry", "local-only", "no account" | **False today — banned** | Sentry unconditional, licence check with hardware fingerprint ([FRE-322] §2.4). |
| Approvals from the iPhone or Apple Watch | **No verdict — not a launch claim** | The companion is a TestFlight beta ([FRE-354]); the approval-from-phone claim has no CTO verdict and stays out of launch copy. |
## Voice ## Voice
- Short, concrete, no hype adjectives. Numbers over adjectives ("thirteen agents", - Short, concrete, no hype adjectives. Numbers over adjectives — the only count
"one tap", "$10 once"). that ships is eighteen, and it is the registry's own size (see the coverage row
- The notch/island is the product's home; agents are the product's point. above).
- The notch is the product's home; agents are the product's point.
- Never apologize for being a utility. The utility is the point. - Never apologize for being a utility. The utility is the point.
- Banned phrasings: competitor names (see rule 1), "AI-powered", "revolutionary", - Banned phrasings: competitor names (see rule 1), "AI-powered", "revolutionary",
"game-changer", "seamlessly". Unverifiable superlatives generally. "game-changer", "seamlessly", any unverifiable superlative — and the specific
banned sentences in rule 3 and the table above.

View File

@@ -1,5 +1,6 @@
import { import {
For, For,
Show,
createEffect, createEffect,
createSignal, createSignal,
onCleanup, onCleanup,
@@ -20,6 +21,7 @@ import { Campfire, type CampfireState } from "./Campfire";
import { import {
ApprovalCardMock, ApprovalCardMock,
SessionRowMock, SessionRowMock,
Glyph,
CLAUDE, CLAUDE,
CODEX CODEX
} from "./ModuleMocks"; } from "./ModuleMocks";
@@ -35,7 +37,7 @@ import {
* Detailed feature breakdowns: alternating copy + live-demo sections. * Detailed feature breakdowns: alternating copy + live-demo sections.
* Each demo stages the real interaction loop: attention flares, approval * Each demo stages the real interaction loop: attention flares, approval
* resolves, fans throttle, the charge ceiling holds, meetings surface, * resolves, fans throttle, the charge ceiling holds, meetings surface,
* the camera wakes. Newer demos live in * the camera wakes, a remote host goes live. Newer demos live in
* MoreDemos.tsx; app-anatomy pieces in ModuleMocks.tsx. * MoreDemos.tsx; app-anatomy pieces in ModuleMocks.tsx.
*/ */
@@ -805,6 +807,112 @@ function CameraDemo() {
); );
} }
/* ── Demo 7: Settings ▸ Remote hosts — one click to Live ──────────── */
/** RemoteMirror's progress steps, in order. */
const SETUP_STEPS = [
"connecting",
"pushing the hook binary",
"configuring agents",
"checking the host can reach this Mac"
];
function RemoteHostDemo() {
/* -1 not set up · 0…3 setting up · 4 live */
const [step, setStep] = createSignal(-1);
const [pressed, setPressed] = createSignal(false);
useDemoLoop(11000, [
[0, () => (setStep(-1), setPressed(false))],
[1400, () => setPressed(true)],
[1700, () => (setPressed(false), setStep(0))],
[2700, () => setStep(1)],
[3700, () => setStep(2)],
[4700, () => setStep(3)],
[5800, () => setStep(4)]
]);
const live = () => step() === 4;
const working = () => step() >= 0 && step() < 4;
const chip = () =>
live()
? { color: "rgb(48,209,88)", label: "Live" }
: working()
? { color: "rgba(255,255,255,0.55)", label: "Setting up" }
: { color: "rgba(255,255,255,0.35)", label: "Not set up" };
return (
<div class="relative mx-auto w-full max-w-[360px] space-y-3">
{/* the host's card in Settings ▸ Agents ▸ Remote hosts */}
<ModuleCard>
<div class="space-y-2 text-left">
<div class="flex items-center gap-2.5">
<div class="min-w-0 flex-1">
<p class="text-[13px] font-semibold text-white">gpu-rig</p>
<p class="truncate text-[11px] text-white/45">tailnet peer</p>
</div>
<Show when={working()}>
<span class="h-3 w-3 animate-spin rounded-full border-[1.5px] border-white/25 border-t-white/80" />
</Show>
<span class="flex items-center gap-1.5 text-[10.5px] font-medium text-white/85">
<span
class="h-[7px] w-[7px] rounded-full transition-colors duration-300"
style={{ background: chip().color }}
/>
{chip().label}
</span>
<Show
when={live()}
fallback={
<span
class="rounded-md px-2.5 py-1 text-[11px] font-semibold text-white transition-all duration-200"
style={{
background: ACCENT,
opacity: working() ? 0 : 1,
transform: pressed() ? "scale(0.92)" : "scale(1)"
}}
>
Set up
</span>
}
>
<Glyph name="refresh" size={11} class="text-white/50" />
<span class="rounded-md bg-white/10 px-2 py-1 text-[11px] font-semibold text-[#ff6961]">
Remove
</span>
</Show>
</div>
<p class="h-4 text-[11px] text-white/50">
{working() ? SETUP_STEPS[step()] : " "}
</p>
</div>
</ModuleCard>
{/* its agents arrive as ordinary rows, the machine named in the chip */}
<div
class="transition-all duration-500"
style={{
opacity: live() ? 1 : 0,
transform: live() ? "translateY(0)" : "translateY(6px)"
}}
>
<SessionRowMock
title="trainer · fp16"
prompt="benchmark fp16"
state="Running python bench.py…"
status="running"
agent={CODEX}
host="gpu-rig"
origin="iTerm2"
age="now"
/>
</div>
<FakeCursor
phase={pressed() ? "press" : step() === -1 ? "idle" : "live"}
at={{ x: 88, y: 16 }}
/>
</div>
);
}
/* ── Demo 8: campfire states ───────────────────────────────────────── */ /* ── Demo 8: campfire states ───────────────────────────────────────── */
function CampfireDemo() { function CampfireDemo() {
@@ -851,7 +959,7 @@ export default function FeatureBreakdowns() {
title="Your whole fleet, in one pixel strip" title="Your whole fleet, in one pixel strip"
body="Every session, every agent, one square each. The pill never wraps, never grows — an arriving question flashes the fire and swaps the slot, then hands the notch back." body="Every session, every agent, one square each. The pill never wraps, never grows — an arriving question flashes the fire and swaps the slot, then hands the notch back."
bullets={[ bullets={[
"13 agents: Claude Code, Codex, Gemini CLI, OpenCode, Pi, Muse, and more", "18 agents: Claude Code, Codex, Gemini CLI, OpenCode, Pi, Muse, and more",
"Custom agents: your own coding agent can wire itself in from the published contract", "Custom agents: your own coding agent can wire itself in from the published contract",
"Slot rankings — you choose which data earns the notch", "Slot rankings — you choose which data earns the notch",
"You don't need a notch — external displays get the same island, floating top-center" "You don't need a notch — external displays get the same island, floating top-center"
@@ -868,6 +976,7 @@ export default function FeatureBreakdowns() {
"Approval cards pin above the session list; questions answer inside their row", "Approval cards pin above the session list; questions answer inside their row",
"Allow for session waves through the rest of that session's requests", "Allow for session waves through the rest of that session's requests",
"Answer from iPhone or Apple Watch too, now in TestFlight beta", "Answer from iPhone or Apple Watch too, now in TestFlight beta",
"Works across local and remote sessions; Muse, Cline and Amp are this Mac only"
]} ]}
reversed reversed
> >
@@ -887,6 +996,20 @@ export default function FeatureBreakdowns() {
<TerminalReplyDemo /> <TerminalReplyDemo />
</BreakdownSection> </BreakdownSection>
<BreakdownSection
kicker="Remote"
title="Any SSH box, one click away"
body="Pick a host from your ssh config or your tailnet and click once — the nook-hook installs itself and its agents appear beside your local ones, indistinguishable."
bullets={[
"One click: connect, push the hook, configure agents, check it can reach this Mac",
"Hosts mirror this Mac's installed integrations automatically",
"Remote sessions are ordinary rows; the agent chip names the machine"
]}
reversed
>
<RemoteHostDemo />
</BreakdownSection>
<BreakdownSection <BreakdownSection
kicker="The campfire" kicker="The campfire"
title="A hearth that tells the truth" title="A hearth that tells the truth"

View File

@@ -1,6 +1,12 @@
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js"; import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js";
import { IslandPill, STATUS, CALM, CRITICAL, ModuleCard } from "./IslandMock"; import { IslandPill, STATUS, CALM, CRITICAL, ModuleCard } from "./IslandMock";
import { ApprovalCardMock, DieLine, CLAUDE } from "./ModuleMocks"; import {
ApprovalCardMock,
SessionRowMock,
DieLine,
CLAUDE,
CODEX
} from "./ModuleMocks";
/** /**
* Feature collage: five cells sold as the app's actual UI. The hero cell * Feature collage: five cells sold as the app's actual UI. The hero cell
@@ -99,6 +105,35 @@ function FansPanelMock() {
); );
} }
/**
* Remote agents as the panel draws them: ordinary session rows whose agent
* chip names the machine ("Claude Code · build-box").
*/
function RemoteMock() {
return (
<div class="space-y-1.5">
<SessionRowMock
title="api · main"
prompt="fix the flaky test"
state="Running pytest -x…"
status="running"
agent={CLAUDE}
host="build-box"
age="4m"
/>
<SessionRowMock
title="trainer · fp16"
prompt="benchmark fp16"
state="Done · replied 1m ago"
status="ready"
agent={CODEX}
host="gpu-rig"
age="12m"
/>
</div>
);
}
/* Deterministic usage wander for the traces. */ /* Deterministic usage wander for the traces. */
const wander = (n: number, k: number) => const wander = (n: number, k: number) =>
0.42 + 0.22 * Math.sin(n * 0.45 + k * 1.7) + 0.12 * Math.sin(n * 1.3 + k); 0.42 + 0.22 * Math.sin(n * 0.45 + k * 1.7) + 0.12 * Math.sin(n * 1.3 + k);
@@ -221,6 +256,19 @@ export default function FeatureCollage() {
</InkStage> </InkStage>
</CollageCell> </CollageCell>
{/* Remote agents */}
<CollageCell
kicker="Remote"
title="Agents on other machines"
body="Run agents on Linux boxes and SSH servers — one click installs the nook-hook, and their sessions join the same island."
>
<InkStage class="w-full">
<div class="w-full px-3 text-left">
<RemoteMock />
</div>
</InkStage>
</CollageCell>
{/* Gauges */} {/* Gauges */}
<CollageCell <CollageCell
kicker="Your Mac" kicker="Your Mac"

View File

@@ -722,7 +722,7 @@ function LayoutMock() {
/* ── Marquee ─────────────────────────────────────────────────────── */ /* ── Marquee ─────────────────────────────────────────────────────── */
const CHIPS_A = [ const CHIPS_A = [
"13 agents", "18 agents",
"Custom agents", "Custom agents",
"Six chiptunes", "Six chiptunes",
"Focus-aware slots", "Focus-aware slots",
@@ -740,6 +740,7 @@ const CHIPS_B = [
"tmux · iTerm2 · kitty", "tmux · iTerm2 · kitty",
"Markdown replies", "Markdown replies",
"Tailscale hosts", "Tailscale hosts",
"One-click SSH setup",
"iPhone widgets", "iPhone widgets",
"Watch-face widgets", "Watch-face widgets",
"Screenshots to clipboard", "Screenshots to clipboard",

View File

@@ -1,5 +1,5 @@
/** /**
* Regression test for the /agents roster (NOOK-336). * Regression test for the /agents roster and its coverage count.
* *
* The page mirrors the app's registry, `AgentSpec.all` in * The page mirrors the app's registry, `AgentSpec.all` in
* the-nook/Sources/NookCore/Agents/AgentRegistry.swift. When Muse joined * the-nook/Sources/NookCore/Agents/AgentRegistry.swift. When Muse joined
@@ -8,6 +8,12 @@
* them. The registry lives in another repo, so the expected roster is * them. The registry lives in another repo, so the expected roster is
* restated here; when an agent is added there, add it here and on the page. * restated here; when an agent is added there, add it here and on the page.
* *
* FRE-997 restored the count. The page said thirteen while the registry held
* eighteen, so the count was dropped rather than corrected (FRE-359 verdict B);
* now that all eighteen rows are on the page, the roster check is an equality
* check again and the page states eighteen. A subset check would let a shipped
* agent go missing behind a passing suite, which is the NOOK-336 failure.
*
* STATIC SOURCE AUDIT, the same pattern as the downloads regressions. * STATIC SOURCE AUDIT, the same pattern as the downloads regressions.
*/ */
import { describe, it, expect } from "bun:test"; import { describe, it, expect } from "bun:test";
@@ -16,7 +22,13 @@ import { join } from "node:path";
const SOURCE = readFileSync(join(import.meta.dir, "agents.tsx"), "utf8"); const SOURCE = readFileSync(join(import.meta.dir, "agents.tsx"), "utf8");
/** AgentSpec.all display names, in registry order. */ /** User-facing copy only: comments are not copy and may name the old wording. */
const COPY = SOURCE.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
/** JSX wraps prose across lines; match a sentence, not its indentation. */
const PROSE = COPY.replace(/\s+/g, " ");
/** AgentSpec.all display names, in registry order (18 rows). @ The-Nook f4258034, 2026-10-29 */
const REGISTRY = [ const REGISTRY = [
"Claude Code", "Claude Code",
"Codex", "Codex",
@@ -30,25 +42,49 @@ const REGISTRY = [
"Gemini CLI", "Gemini CLI",
"Cline", "Cline",
"Qwen Code", "Qwen Code",
"DeepSeek" "DeepSeek",
"Kimi",
"GitHub Copilot CLI",
"Crush",
"Cursor",
"Amp"
]; ];
const NUMBER_WORDS = ["twelve", "thirteen", "fourteen", "fifteen"];
const entries = [...SOURCE.matchAll(/^\s{4}name: "([^"]+)",$/gm)].map((m) => m[1]); const entries = [...SOURCE.matchAll(/^\s{4}name: "([^"]+)",$/gm)].map((m) => m[1]);
describe("Nook /agents roster (NOOK-336)", () => { describe("Nook /agents roster", () => {
it("lists every registry agent, in registry order", () => { it("lists every registry agent, once, in registry order", () => {
expect(entries).toEqual(REGISTRY); expect(entries).toEqual(REGISTRY);
}); });
it("states the same count everywhere it names one", () => { it("states the registry's size", () => {
const word = NUMBER_WORDS[entries.length - 12]; expect(entries.length).toBe(18);
for (const other of NUMBER_WORDS.filter((w) => w !== word)) { expect(COPY).toContain("all eighteen");
expect(SOURCE.toLowerCase()).not.toContain(other); expect(COPY).toContain("The 18 agents");
} expect(COPY).toContain("eighteen coding agents");
expect(SOURCE).toContain(`all ${word}`); });
expect(SOURCE).toContain(`The ${entries.length} agents`);
expect(SOURCE).toContain(`watches ${word} coding agents`); it("carries no stale count", () => {
expect(COPY.toLowerCase()).not.toContain("thirteen");
expect(COPY).not.toMatch(/\b13 agents\b/);
expect(COPY).not.toContain("all thirteen");
});
});
describe("Nook /agents remote coverage", () => {
it("says agents can run on remote machines over SSH", () => {
expect(PROSE).toContain("remote machines");
expect(PROSE).toContain("remote sessions report into the same grid");
});
it("names the three agents that stay on this Mac", () => {
expect(PROSE).toContain("Muse, Cline and Amp run on this Mac only");
});
it("discloses the local-only rows where they are listed", () => {
const localOnly = [
...SOURCE.matchAll(/This Mac only — remote hosts skip it/g)
];
expect(localOnly.length).toBe(3);
}); });
}); });

View File

@@ -1,9 +1,10 @@
/** /**
* `/agents` on the Nook subdomain — supported agents and their event coverage. * `/agents` on the Nook subdomain — all eighteen agents in the registry and
* each row's event coverage.
* *
* The registry below mirrors `AgentSpec.all` in the app * The registry below mirrors `AgentSpec.all` in the app
* (~/Code/the-nook/Sources/NookCore/Agents/AgentSpec.swift) and each agent's * (~/Code/the-nook/Sources/NookCore/Agents/AgentRegistry.swift) and each
* installed hook surface (hook/setup.go) or plugin event subscription * agent's installed hook surface (hook/setup.go) or plugin event subscription
* (plugins/*), normalized onto the shared event vocabulary * (plugins/*), normalized onto the shared event vocabulary
* (~/Code/the-nook/docs/agent-interface.md). Copy follows * (~/Code/the-nook/docs/agent-interface.md). Copy follows
* marketing/positioning.md: category nouns, honest scoping, no unverifiable * marketing/positioning.md: category nouns, honest scoping, no unverifiable
@@ -208,7 +209,8 @@ const AGENTS: AgentEntry[] = [
}, },
{ {
name: "Cline", name: "Cline",
wiring: "One hook file per event in its hooks directory", wiring:
"One hook file per event in its hooks directory. This Mac only — remote hosts skip it",
events: [ events: [
"SessionStart", "SessionStart",
"SessionEnd", "SessionEnd",
@@ -259,6 +261,93 @@ const AGENTS: AgentEntry[] = [
], ],
gating: gating:
"Tool calls can be allowed or denied from a card; permission prompts stay with the agent" "Tool calls can be allowed or denied from a card; permission prompts stay with the agent"
},
{
name: "Kimi",
wiring: "Hook rules in its TOML config, one per event",
events: [
"SessionStart",
"SessionEnd",
"UserPromptSubmit",
"AgentStart",
"PreToolUse",
"PermissionRequest",
"PermissionResolved",
"PostToolUse",
"PostToolUseFailure",
"Stop",
"SessionError",
"SubagentStart",
"SubagentStop",
"PreCompact",
"Notification"
],
gating:
"Observation — every event appears as a card; its PreToolUse fires before its own permission check, so the agent keeps its own prompting"
},
{
name: "GitHub Copilot CLI",
wiring: "One hook file of ours in its hooks directory",
events: [
"SessionStart",
"SessionEnd",
"UserPromptSubmit",
"PreToolUse",
"PermissionRequest",
"PostToolUse",
"PostToolUseFailure",
"Stop",
"SubagentStart",
"SubagentStop",
"ErrorOccurred",
"PreCompact",
"Notification"
],
gating:
"Observation — every event appears as a card; its permission request runs before its own policy check, so the agent keeps its own prompting"
},
{
name: "Crush",
wiring:
"One entry in its JSONC config, the only event it documents",
events: ["PreToolUse"],
gating:
"Observation — tool calls appear as cards; its own permission prompts decide"
},
{
name: "Cursor",
wiring:
"Hook entries in its hooks.json, merged with your own. No permission hook, on purpose",
events: [
"SessionStart",
"SessionEnd",
"UserPromptSubmit",
"PostToolUse",
"PostToolUseFailure",
"Stop",
"SessionError",
"AfterAgentResponse",
"SubagentStop",
"PreCompact"
],
gating:
"Observation — a permission hook that stays silent blocks the action here, so Cursor keeps its own prompting"
},
{
name: "Amp",
wiring:
"In-process extension package. This Mac only — remote hosts skip it",
events: [
"SessionStart",
"SessionEnd",
"UserPromptSubmit",
"PreToolUse",
"PostToolUse",
"Stop",
"SessionError"
],
gating:
"Observation — its plugin API has no permission-wait event, so the agent keeps its own prompting"
} }
]; ];
@@ -283,8 +372,8 @@ export default function NookAgentsPage() {
<EdgeCacheHeaders maxAge={300} /> <EdgeCacheHeaders maxAge={300} />
<PageHead <PageHead
title="Supported agents" title="Supported agents"
description="Every coding agent The Nook supports — all thirteen, with each hook's exact event coverage and what you can do from a card: watch sessions live, answer permission prompts, unblock work." description="Every coding agent The Nook supports — all eighteen, with each hook's exact event coverage and what you can do from a card: watch sessions live, answer permission prompts, unblock work."
ogTitle="The 13 agents The Nook supports, and what each hook sees" ogTitle="The 18 agents The Nook supports, and what each hook sees"
/> />
<SubdomainHeader /> <SubdomainHeader />
<main class="min-h-screen px-[8vw] py-[8vh]"> <main class="min-h-screen px-[8vw] py-[8vh]">
@@ -292,7 +381,7 @@ export default function NookAgentsPage() {
Supported agents Supported agents
</h1> </h1>
<p class="text-subtext0 mb-2 max-w-2xl text-lg"> <p class="text-subtext0 mb-2 max-w-2xl text-lg">
The Nook watches thirteen coding agents in one place — your menu bar and The Nook watches eighteen coding agents in one place — your menu bar and
notch show every session that is running, thinking, or blocked, and a notch show every session that is running, thinking, or blocked, and a
card answers permission prompts without leaving your keyboard. card answers permission prompts without leaving your keyboard.
</p> </p>
@@ -301,7 +390,8 @@ export default function NookAgentsPage() {
means the agent hands the decision to The Nook while it waits; means the agent hands the decision to The Nook while it waits;
"Observe" means every event still appears live, but the agent keeps "Observe" means every event still appears live, but the agent keeps
its own prompting. Agents can run on this Mac or on remote machines its own prompting. Agents can run on this Mac or on remote machines
over SSH — remote sessions report into the same grid. over SSH — remote sessions report into the same grid. Muse, Cline and
Amp run on this Mac only.
</p> </p>
<div class="grid gap-4 lg:grid-cols-2"> <div class="grid gap-4 lg:grid-cols-2">

View File

@@ -0,0 +1,112 @@
/**
* Guard for the remote / SSH story on the Nook site, and for the two phrasings
* that must not come back with it (FRE-323 → FRE-997).
*
* FRE-323 removed the remote copy because it had been checked against claims
* the copy never made: the app does list hosts from `~/.ssh/config` and
* Tailscale, a one-time `ssh-copy-id` is a normal prerequisite the row explains,
* and the per-host credential is minted and copied automatically
* (`RemoteHostDiscovery`, `RemoteHostStatus.fixCommand`, `RemoteMirror`). Two
* things in the old copy were genuinely wrong, and this file guards those
* instead of the copy as a whole:
*
* - "Works across every hooked agent, local or remote" overstated it. Muse,
* Cline and Amp are `.unsupported` for remote in `AgentSpec.remote`, so the
* landing bullet and the /agents paragraph name them.
* - The count said thirteen while `AgentSpec.all` holds eighteen. The fix was
* to say eighteen, not to drop the count.
*
* So the removal guards from `d44133f` are gone: a removed sentence is exactly
* what this task restores. What stays is the pair of bans, because either one
* coming back would repeat the original defect.
*
* STATIC SOURCE AUDIT, the same pattern as `agents.test.ts` and
* `downloads.test.ts` (SolidJS does not render under `bun:test`).
*/
import { describe, it, expect } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const ROOT = join(import.meta.dir, "../../..");
/** Shipped page and component source. */
const AUDITED = [
"src/routes/nook/index.tsx",
"src/routes/nook/agents.tsx",
"src/routes/nook/faq.tsx",
"src/components/nook/FeatureCollage.tsx",
"src/components/nook/FeatureBreakdowns.tsx",
"src/components/nook/IslandExtras.tsx"
];
/** The overstatements the restored copy must never carry again. */
const BANNED_REMOTE_CLAIMS = [
// a universal claim: three registry agents cannot mirror over SSH
"every hooked agent, local or remote",
"Works across every hooked agent",
// the stale count this task replaced
"13 agents",
"all thirteen",
"thirteen coding agents"
];
/** User-facing copy only: comments are not copy and may name the old wording. */
function copyOf(file: string): string {
return readFileSync(join(ROOT, file), "utf8")
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/^\s*\/\/.*$/gm, "");
}
describe("the restored remote copy is on the site (FRE-997)", () => {
it("the landing collage sells agents on other machines", () => {
const copy = copyOf("src/components/nook/FeatureCollage.tsx");
for (const phrase of [
"Agents on other machines",
"one click installs the nook-hook",
"Remote"
]) {
expect(copy).toContain(phrase);
}
});
it("the landing breakdown keeps the SSH section and its demo", () => {
const copy = copyOf("src/components/nook/FeatureBreakdowns.tsx");
for (const phrase of [
"Any SSH box, one click away",
"Pick a host from your ssh config or your tailnet and click once",
"Remote sessions are ordinary rows",
"RemoteHostDemo"
]) {
expect(copy).toContain(phrase);
}
});
it("the chip is back", () => {
expect(copyOf("src/components/nook/IslandExtras.tsx")).toContain(
"One-click SSH setup"
);
});
it("/agents says remote sessions report into the same grid", () => {
const copy = copyOf("src/routes/nook/agents.tsx");
expect(copy).toContain("remote machines");
expect(copy).toContain("remote sessions report into the same grid");
});
});
describe("the two original overstatements stay off the site (FRE-323, FRE-359)", () => {
for (const file of AUDITED) {
it(`${file} claims no universal remote coverage and no stale count`, () => {
const copy = copyOf(file);
for (const phrase of BANNED_REMOTE_CLAIMS) {
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
}
});
}
it("the landing names the agents that stay local", () => {
expect(copyOf("src/components/nook/FeatureBreakdowns.tsx")).toContain(
"Muse, Cline and Amp are this Mac only"
);
});
});

View File

@@ -0,0 +1,139 @@
/**
* A Smarter Remote privacy policy — `freno.me/smarter-remote/privacy`.
*
* Linked from the iOS app's paywall (App Review 3.1.2). Reflects what the app
* actually does: TV control stays on the local network, pairing data stays on
* the phone, purchases go through Apple and RevenueCat, and sharing is a QR
* code passed between phones.
*/
import { A } from "@solidjs/router";
import { PageHead } from "~/components/PageHead";
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
export default function SmarterRemotePrivacyPolicy() {
return (
<>
<EdgeCacheHeaders maxAge={300} />
<PageHead
title="A Smarter Remote Privacy Policy"
description="Privacy policy for A Smarter Remote, a universal TV remote app that works over your local Wi-Fi network."
/>
<div class="bg-base">
<div class="min-h-screen px-[8vw] py-[8vh]">
<div class="py-4 text-xl">A Smarter Remote&apos;s Privacy Policy</div>
<div class="py-2">Last Updated: November 2, 2026</div>
<div class="py-2">
Welcome to A Smarter Remote (&apos;We&apos;, &apos;Us&apos;,
&apos;Our&apos;). The app controls your TVs over your own Wi-Fi
network. It has no accounts, no ads, and no analytics. This policy
explains the little data it does handle.
</div>
<ol>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">1.</span> Data We Collect
</div>
<div class="pl-4">
<div class="pb-2">
<div class="-ml-6">(a) Your TVs:</div> The app finds TVs on
your local network and stores their names, network addresses,
and platform on your phone. Pairing codes and tokens are kept
in your phone&apos;s Keychain. This data is sent only to the
TV itself, over your local network. It never reaches us.
</div>
<div class="pb-2">
<div class="-ml-6">(b) Purchases:</div> Payments are handled
by Apple. We use RevenueCat to check whether you own A Smarter
Remote Pro. RevenueCat receives an anonymous app user ID, your
purchase and subscription history, and basic device and app
details such as OS version, app version, and region. It does
not receive your name, email, or payment details.
</div>
<div class="pb-2">
<div class="-ml-6">(c) Voice input:</div> If you use voice
search, your speech is converted to text by Apple&apos;s
speech recognition. Apple may process it on its servers. The
resulting text is sent only to your TV.
</div>
<div class="pb-2">
<div class="-ml-6">(d) Sharing:</div> If you share a TV with
someone, its name, address, and platform are put into a QR
code on your screen for them to scan. It goes from one phone
to the other and does not pass through our servers.
</div>
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> How We Use It
</div>
<div class="pb-2 pl-4">
Data about your TVs is used only to control them. Purchase
records are used only to unlock Pro. We do not use any of it for
advertising, and we do not sell it.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">3.</span> Local Network Access
</div>
<div class="pb-2 pl-4">
iOS asks for permission before the app can use your local
network. The app needs it to find and control your TVs. If you
decline, the app cannot reach your TVs. You can change this in
Settings &gt; Privacy &amp; Security &gt; Local Network.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">4.</span> Deleting Your Data
</div>
<div class="pb-2 pl-4">
Removing a TV in the app deletes its stored details and
credentials from your phone. Deleting the app removes all of
them. Subscriptions are managed and cancelled in your App Store
account settings.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Children&apos;s Privacy
</div>
<div class="pb-2 pl-4">
The app does not knowingly collect personal information from
anyone, including children under 13.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">6.</span> Changes to the Privacy Policy
</div>
<div class="pb-2 pl-4">
We may update this policy periodically. Any changes are posted on
this page, so please review it from time to time.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">7.</span> Contact Us
</div>
<div class="pb-2 pl-4">
If you have any questions about this policy, you can contact us{" "}
<A href="/contact" class="text-blue hover-underline-animation">
here
</A>
.
</div>
</div>
</ol>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,141 @@
/**
* A Smarter Remote terms of use — `freno.me/smarter-remote/terms`.
*
* Linked from the iOS app's paywall (App Review 3.1.2). Builds on Apple's
* standard EULA and adds the Pro subscription and lifetime purchase terms.
*/
import { A } from "@solidjs/router";
import { PageHead } from "~/components/PageHead";
import { EdgeCacheHeaders } from "~/components/EdgeCacheHeaders";
export default function SmarterRemoteTerms() {
return (
<>
<EdgeCacheHeaders maxAge={300} />
<PageHead
title="A Smarter Remote Terms of Use"
description="Terms of use for A Smarter Remote and its Pro subscription."
/>
<div class="bg-base">
<div class="min-h-screen px-[8vw] py-[8vh]">
<div class="py-4 text-xl">A Smarter Remote&apos;s Terms of Use</div>
<div class="py-2">Last Updated: November 2, 2026</div>
<div class="py-2">
These terms cover your use of the A Smarter Remote app (&apos;the
App&apos;). By using the App you agree to them. The App is licensed
to you under Apple&apos;s{" "}
<a
href="https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
target="_blank"
rel="noopener noreferrer"
class="text-blue hover-underline-animation"
>
Licensed Application End User License Agreement
</a>
, and the terms below add to it.
</div>
<ol>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">1.</span> A Smarter Remote Pro
</div>
<div class="pl-4">
<div class="pb-2">
<div class="-ml-6">(a) What it includes:</div> The free App
controls one TV. Pro removes that limit and unlocks the
features marked Pro in the App.
</div>
<div class="pb-2">
<div class="-ml-6">(b) Yearly subscription:</div> Payment is
charged to your Apple account when you confirm the purchase,
or when any free trial ends. The subscription renews
automatically at the same price unless you cancel at least 24
hours before the end of the current period. Your account is
charged for renewal within 24 hours before the period ends.
Any unused part of a free trial is forfeited when you buy a
subscription.
</div>
<div class="pb-2">
<div class="-ml-6">(c) Lifetime purchase:</div> A one-time
payment that unlocks Pro with no recurring charge.
</div>
<div class="pb-2">
<div class="-ml-6">(d) Managing and refunds:</div> You can
manage or cancel a subscription in your App Store account
settings. Refunds are handled by Apple under its policies.
Restore Purchases in the App re-unlocks Pro on another device
signed in to the same Apple account.
</div>
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> Your TVs
</div>
<div class="pb-2 pl-4">
The App controls TVs through the network features their makers
provide. Which features work depends on your TV&apos;s model and
software, which can change without notice. We do not guarantee
that every feature works on every TV. Only control TVs you own
or have permission to use.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">3.</span> No Warranty
</div>
<div class="pb-2 pl-4">
The App is provided &apos;as is&apos;, without warranties of any
kind, to the extent the law allows. To the extent the law allows,
we are not liable for indirect or consequential damages arising
from your use of the App.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">4.</span> Privacy
</div>
<div class="pb-2 pl-4">
How the App handles data is described in the{" "}
<A
href="/smarter-remote/privacy"
class="text-blue hover-underline-animation"
>
Privacy Policy
</A>
.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Changes to These Terms
</div>
<div class="pb-2 pl-4">
We may update these terms periodically. Any changes are posted on
this page. Continuing to use the App after a change means you
accept the new terms.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">6.</span> Contact Us
</div>
<div class="pb-2 pl-4">
If you have any questions about these terms, you can contact us{" "}
<A href="/contact" class="text-blue hover-underline-animation">
here
</A>
.
</div>
</div>
</ol>
</div>
</div>
</>
);
}