5 Commits

Author SHA1 Message Date
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
8 changed files with 282 additions and 71 deletions

View File

@@ -1,6 +1,9 @@
# Remote SSH copy — HELD, do not publish # Remote SSH copy — HELD, do not publish
**Status:** removed from the live site on 2026-10-29 in the FRE-323 correction. **Status:** removed from the `freno-dev` source on 2026-10-29 in the FRE-323
correction, and **not yet off the live site** — the deploy is blocked on a write
credential to `mikefreno/freno-dev` (card `82d1e1b7`). `nook.freno.me` still
serves every sentence below until that push or a Vercel deploy lands.
**Owner:** CMO. **Verified by:** CTO, on the FRE-320/FRE-205 work. **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 **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. verifies that flow, and a recording shows a remote agent joining the island.

View File

@@ -10,10 +10,12 @@ 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 unverified and demo-gated. The CTO named it the largest launch risk because it
was already in front of buyers. was already in front of buyers.
So the landing page now says only what the product can back today: local agents, So the `freno-dev` source now says only what the product can back today: local
the card, and the notch. The remote story is not deleted — it is parked here with agents, the card, and the notch. That correction is committed locally and is
the FRE-320/FRE-205 work so it can return the day the one-click install ships and **not deployed** — the live page still carries the remote sentences until the
is recorded. push in FRE-323 card `82d1e1b7` lands. 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 ## What has to be true before it returns

View File

@@ -4,13 +4,37 @@ 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** against the CTO's verdicts ([FRE-315], [FRE-322],
[FRE-354]), 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
## 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 +46,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 +75,49 @@ 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 **held**, not approved: the one-click install has not
has not been recorded (FRE-320, FRE-205), and the current path needs hand-edited been verified end to end ([FRE-315] §1) and the shipped path needs hand-edited
`~/.ssh/config` and a one-time `ssh-copy-id` (FRE-320). Its copy is parked in `~/.ssh/config` and a one-time `ssh-copy-id` ([FRE-320]). Its copy stays parked in
`marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow and a `marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow **and** a
demo exists. recording shows a remote agent joining the notch.
### 5. Humans hold every account ### 5. Humans hold every account
@@ -79,28 +125,29 @@ 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-10-29
| 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 ("thirteen agents", "8 agents", "all your agents") | **Excluded — verdict B, count removed** | [FRE-359] verdict B (2026-10-29): the page's 13 rows are the registry's first 13 of 18, so no number is publishable and `every` / `all` / `the agents The Nook supports` are banned. Removed from `agents.tsx`, the landing island section and the marquee. [FRE-322] §1 matrix for the sets. |
| 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 | **Held** | [FRE-315] §1, [FRE-320], [FRE-323]. Needs a verified one-click install plus a recording. |
| "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 — but no coverage
"one tap", "$10 once"). counts (see the excluded row above).
- The notch/island is the product's home; agents are the product's point. - 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

@@ -851,7 +851,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", "Claude Code, Codex, Gemini CLI, OpenCode, Pi, Muse, Cursor 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"

View File

@@ -722,7 +722,7 @@ function LayoutMock() {
/* ── Marquee ─────────────────────────────────────────────────────── */ /* ── Marquee ─────────────────────────────────────────────────────── */
const CHIPS_A = [ const CHIPS_A = [
"13 agents", "Agents wired in",
"Custom agents", "Custom agents",
"Six chiptunes", "Six chiptunes",
"Focus-aware slots", "Focus-aware slots",

View File

@@ -1,5 +1,6 @@
/** /**
* Regression test for the /agents roster (NOOK-336). * Regression test for the /agents roster (NOOK-336) and its coverage wording
* (FRE-359).
* *
* 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 +9,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-359 verdict B: the page no longer states a count or claims coverage
* ("all thirteen", "every coding agent The Nook supports"). The page still
* lists 13 of the registry's 18 rows — the table is hand-mirrored and its
* regeneration is its own ticket — so the roster check here is a subset
* check in registry order, not an equality check.
*
* 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 +23,10 @@ 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, "");
/** 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 +40,59 @@ 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 NUMBER_WORDS = ["twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen"];
const entries = [...SOURCE.matchAll(/^\s{4}name: "([^"]+)",$/gm)].map((m) => m[1]); const entries = [...SOURCE.matchAll(/^\s{4}name: "([^"]+)",$/gm)].map((m) => m[1]);
/** The page's rows are a subset of the registry, in registry order. */
function subsetInOrder(page: string[], registry: string[]): boolean {
let cursor = 0;
for (const name of page) {
const at = registry.indexOf(name, cursor);
if (at === -1) return false;
cursor = at + 1;
}
return true;
}
describe("Nook /agents roster (NOOK-336)", () => { describe("Nook /agents roster (NOOK-336)", () => {
it("lists every registry agent, in registry order", () => { it("lists only registry agents, in registry order", () => {
expect(entries).toEqual(REGISTRY); expect(entries.length).toBeGreaterThan(0);
expect(subsetInOrder(entries, REGISTRY)).toBe(true);
}); });
it("states the same count everywhere it names one", () => { it("lists each agent at most once", () => {
const word = NUMBER_WORDS[entries.length - 12]; expect(new Set(entries).size).toBe(entries.length);
for (const other of NUMBER_WORDS.filter((w) => w !== word)) { });
expect(SOURCE.toLowerCase()).not.toContain(other); });
describe("Nook /agents coverage wording (FRE-359)", () => {
it("states no agent count anywhere on the page", () => {
for (const word of NUMBER_WORDS) {
expect(COPY.toLowerCase()).not.toContain(word);
}
// A bare numeral count ("13 agents", "The 13") is just as precise and just
// as unverified; the page must not state one in any form.
expect(COPY).not.toMatch(/\b\d{1,2} agents\b/);
expect(COPY).not.toMatch(/\ball \d{1,2}\b/);
});
it("makes no completeness claim over the agent set", () => {
for (const banned of [
"every coding agent",
"the agents the nook supports",
"agents the nook supports",
"supported agents"
]) {
expect(COPY.toLowerCase()).not.toContain(banned);
} }
expect(SOURCE).toContain(`all ${word}`);
expect(SOURCE).toContain(`The ${entries.length} agents`);
expect(SOURCE).toContain(`watches ${word} coding agents`);
}); });
}); });

View File

@@ -1,5 +1,7 @@
/** /**
* `/agents` on the Nook subdomain — supported agents and their event coverage. * `/agents` on the Nook subdomain — the agent roster and each row's event
* coverage. It states no agent count and claims no completeness over the
* registry (FRE-359 verdict B).
* *
* 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/AgentSpec.swift) and each agent's
@@ -282,26 +284,25 @@ export default function NookAgentsPage() {
<> <>
<EdgeCacheHeaders maxAge={300} /> <EdgeCacheHeaders maxAge={300} />
<PageHead <PageHead
title="Supported agents" title="Agent coverage"
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="The coding agents The Nook connects to, with the events each hook reports 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 coding agents The Nook connects to, 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]">
<h1 class="text-text mb-3 text-3xl font-bold tracking-tight sm:text-4xl"> <h1 class="text-text mb-3 text-3xl font-bold tracking-tight sm:text-4xl">
Supported agents Agent coverage
</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 coding agents in one place — your menu bar and notch
notch show every session that is running, thinking, or blocked, and a show sessions that are running, thinking, or blocked, and a card can
card answers permission prompts without leaving your keyboard. answer a permission prompt without leaving your keyboard.
</p> </p>
<p class="text-subtext1 mb-10 max-w-2xl text-sm"> <p class="text-subtext1 mb-10 max-w-2xl text-sm">
Each agent reports its own set of events. "Allow / Deny from a card" Each agent reports its own set of events. "Allow / Deny from a card"
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.
over SSH — remote sessions report into the same grid.
</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,114 @@
/**
* Regression test for the removed remote / SSH claims and the removed agent
* count (FRE-323, FRE-359 verdict B).
*
* FRE-323 took the remote story off the site because it was unverified and
* demo-gated (FRE-315, FRE-320, FRE-322 §3 correction 4): the "one click
* installs the nook-hook" 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-359 verdict B removed the coverage count for the same
* reason: the /agents table lists 13 of the registry's 18 rows.
*
* The copy was not thrown away — it is parked in
* `marketing/drafts/remote-ssh/` with the restore gate, and this test asserts
* it is still there so nobody deletes the parked story as stale. It goes back
* on the site only when the one-click install ships and is recorded.
*
* STATIC SOURCE AUDIT, the same pattern as `agents.test.ts` and
* `downloads.test.ts` (SolidJS does not render under `bun:test`).
*
* Deliberate exceptions, not audited here — both were weighed with the Chief
* of Staff at the go/no-go and left in place on purpose:
* - `src/routes/nook/agents.tsx` Muse row: "This Mac only — remote hosts skip
* it" is a limitation disclosure, not a capability claim.
* - `src/components/nook/IslandExtras.tsx` "Tailscale hosts" chip: adjacent to
* the removed remote story, but tailnet host lookup does exist. Raised on
* FRE-323 rather than widened into this correction.
*/
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. Tests and drafts may quote the old copy. */
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"
];
/** Every sentence FRE-323 ordered off the site. */
const BANNED_REMOTE_CLAIMS = [
"Agents on other machines",
"one click installs the nook-hook",
"Any SSH box",
"Works across every hooked agent, local or remote",
"Remote sessions are ordinary rows",
"One-click SSH setup",
"remote machines over SSH",
"this Mac or remote machines"
];
/** FRE-359 verdict B: no count, no coverage claim, in any form. */
const BANNED_COVERAGE = [
"all thirteen",
"thirteen coding agents",
"13 agents",
"every coding agent",
"Supported 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("remote claims stay off the site (FRE-323)", () => {
for (const file of AUDITED) {
it(`${file} does not restore a removed remote sentence`, () => {
const copy = copyOf(file);
for (const phrase of BANNED_REMOTE_CLAIMS) {
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
}
});
}
it("/agents no longer offers remote sessions", () => {
const copy = copyOf("src/routes/nook/agents.tsx");
expect(copy).not.toContain("remote sessions report into the same grid");
expect(copy).not.toContain("on this Mac or on remote");
});
it("the parked copy still exists, so restoring the story is a revert and not a rewrite", () => {
const parked = readFileSync(join(ROOT, "marketing/drafts/remote-ssh/copy.md"), "utf8");
for (const phrase of ["Agents on other machines", "Any SSH box, one click away"]) {
expect(parked).toContain(phrase);
}
expect(readFileSync(join(ROOT, "marketing/drafts/remote-ssh/rationale.md"), "utf8")).toContain(
"FRE-320"
);
});
});
describe("agent coverage count stays off the page (FRE-359 verdict B)", () => {
for (const file of AUDITED) {
it(`${file} states no coverage count`, () => {
const copy = copyOf(file);
for (const phrase of BANNED_COVERAGE) {
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
}
});
}
it("/agents titles the table Agent coverage", () => {
expect(copyOf("src/routes/nook/agents.tsx")).toContain("Agent coverage");
});
});