diff --git a/marketing/drafts/remote-ssh/copy.md b/marketing/drafts/remote-ssh/copy.md
deleted file mode 100644
index b7a449f..0000000
--- a/marketing/drafts/remote-ssh/copy.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Remote SSH copy — HELD, do not publish
-
-**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.
-**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.
diff --git a/marketing/drafts/remote-ssh/rationale.md b/marketing/drafts/remote-ssh/rationale.md
deleted file mode 100644
index 104c08b..0000000
--- a/marketing/drafts/remote-ssh/rationale.md
+++ /dev/null
@@ -1,30 +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 `freno-dev` source now says only what the product can back today: local
-agents, the card, and the notch. That correction is committed locally and is
-**not deployed** — the live page still carries the remote sentences until the
-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
-
-| 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.
diff --git a/marketing/positioning.md b/marketing/positioning.md
index 174e42d..9864972 100644
--- a/marketing/positioning.md
+++ b/marketing/positioning.md
@@ -4,12 +4,13 @@ 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
silently diverging.
-**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.
+**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
@@ -17,6 +18,7 @@ no longer exists; the verdicts below are the source of truth it was pointing at.
[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
@@ -113,11 +115,16 @@ one thing forbidden: say "TestFlight beta" when the phone exists in a sentence a
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
-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 stays parked in
-`marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow **and** a
-recording shows a remote agent joining the notch.
+Remote SSH is **publishable**, and the site sells it again ([FRE-997]). The
+copy that was pulled on 2026-10-29 had been checked against claims it 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 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
@@ -125,7 +132,7 @@ 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** —
nothing posts itself.
-## Claims: status at 2026-10-29
+## Claims: status at 2026-11-03
Each row names the evidence, not a feeling. Re-read the verdict before using a row.
@@ -136,16 +143,17 @@ Each row names the evidence, not a feeling. Re-read the verdict before using a r
| 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. |
+| 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 | **Held** | [FRE-315] §1, [FRE-320], [FRE-323]. Needs a verified one-click install plus a recording. |
+| 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
-- Short, concrete, no hype adjectives. Numbers over adjectives — but no coverage
- counts (see the excluded row above).
+- Short, concrete, no hype adjectives. Numbers over adjectives — the only count
+ that ships is eighteen, and it is the registry's own size (see the coverage row
+ 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.
- Banned phrasings: competitor names (see rule 1), "AI-powered", "revolutionary",
diff --git a/src/components/nook/FeatureBreakdowns.tsx b/src/components/nook/FeatureBreakdowns.tsx
index cfbae76..220cec2 100644
--- a/src/components/nook/FeatureBreakdowns.tsx
+++ b/src/components/nook/FeatureBreakdowns.tsx
@@ -1,5 +1,6 @@
import {
For,
+ Show,
createEffect,
createSignal,
onCleanup,
@@ -20,6 +21,7 @@ import { Campfire, type CampfireState } from "./Campfire";
import {
ApprovalCardMock,
SessionRowMock,
+ Glyph,
CLAUDE,
CODEX
} from "./ModuleMocks";
@@ -35,7 +37,7 @@ import {
* Detailed feature breakdowns: alternating copy + live-demo sections.
* Each demo stages the real interaction loop: attention flares, approval
* 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.
*/
@@ -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 (
+
+ {/* the host's card in Settings ▸ Agents ▸ Remote hosts */}
+
+
+
+
+
gpu-rig
+
tailnet peer
+
+
+
+
+
+
+ {chip().label}
+
+
+ Set up
+
+ }
+ >
+
+
+ Remove
+
+
+
+
+ {working() ? SETUP_STEPS[step()] : " "}
+
+
+
+
+ {/* its agents arrive as ordinary rows, the machine named in the chip */}
+
+
+
+
+
+ );
+}
+
/* ── Demo 8: campfire states ───────────────────────────────────────── */
function CampfireDemo() {
@@ -851,7 +959,7 @@ export default function FeatureBreakdowns() {
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."
bullets={[
- "Claude Code, Codex, Gemini CLI, OpenCode, Pi, Muse, Cursor 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",
"Slot rankings — you choose which data earns the notch",
"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",
"Allow for session waves through the rest of that session's requests",
"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
>
@@ -887,6 +996,20 @@ export default function FeatureBreakdowns() {
+
+
+
+
+
+
+
+ );
+}
+
/* Deterministic usage wander for the traces. */
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);
@@ -221,6 +256,19 @@ export default function FeatureCollage() {
+ {/* Remote agents */}
+
+
+
+
+
+
+
+
{/* Gauges */}
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)", () => {
- it("lists only registry agents, in registry order", () => {
- expect(entries.length).toBeGreaterThan(0);
- expect(subsetInOrder(entries, REGISTRY)).toBe(true);
+describe("Nook /agents roster", () => {
+ it("lists every registry agent, once, in registry order", () => {
+ expect(entries).toEqual(REGISTRY);
});
- it("lists each agent at most once", () => {
- expect(new Set(entries).size).toBe(entries.length);
+ it("states the registry's size", () => {
+ expect(entries.length).toBe(18);
+ expect(COPY).toContain("all eighteen");
+ expect(COPY).toContain("The 18 agents");
+ expect(COPY).toContain("eighteen 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 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/);
+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("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);
- }
+ 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);
});
});
diff --git a/src/routes/nook/agents.tsx b/src/routes/nook/agents.tsx
index c43ff93..7a104aa 100644
--- a/src/routes/nook/agents.tsx
+++ b/src/routes/nook/agents.tsx
@@ -1,11 +1,10 @@
/**
- * `/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).
+ * `/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
- * (~/Code/the-nook/Sources/NookCore/Agents/AgentSpec.swift) and each agent's
- * installed hook surface (hook/setup.go) or plugin event subscription
+ * (~/Code/the-nook/Sources/NookCore/Agents/AgentRegistry.swift) and each
+ * agent's installed hook surface (hook/setup.go) or plugin event subscription
* (plugins/*), normalized onto the shared event vocabulary
* (~/Code/the-nook/docs/agent-interface.md). Copy follows
* marketing/positioning.md: category nouns, honest scoping, no unverifiable
@@ -210,7 +209,8 @@ const AGENTS: AgentEntry[] = [
},
{
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: [
"SessionStart",
"SessionEnd",
@@ -261,6 +261,93 @@ const AGENTS: AgentEntry[] = [
],
gating:
"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"
}
];
@@ -284,25 +371,27 @@ export default function NookAgentsPage() {
<>
- Agent coverage
+ Supported agents
- The Nook watches coding agents in one place — your menu bar and notch
- show sessions that are running, thinking, or blocked, and a card can
- answer a permission prompt without leaving your keyboard.
+ 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
+ card answers permission prompts without leaving your keyboard.
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;
"Observe" means every event still appears live, but the agent keeps
- its own prompting.
+ its own prompting. Agents can run on this Mac or on remote machines
+ over SSH — remote sessions report into the same grid. Muse, Cline and
+ Amp run on this Mac only.
diff --git a/src/routes/nook/remote-claims.test.ts b/src/routes/nook/remote-claims.test.ts
index f15f6bb..8280f6b 100644
--- a/src/routes/nook/remote-claims.test.ts
+++ b/src/routes/nook/remote-claims.test.ts
@@ -1,31 +1,27 @@
/**
- * Regression test for the removed remote / SSH claims and the removed agent
- * count (FRE-323, FRE-359 verdict B).
+ * 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 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.
+ * 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:
*
- * 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.
+ * - "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`).
- *
- * 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";
@@ -33,7 +29,7 @@ import { join } from "node:path";
const ROOT = join(import.meta.dir, "../../..");
-/** Shipped page and component source. Tests and drafts may quote the old copy. */
+/** Shipped page and component source. */
const AUDITED = [
"src/routes/nook/index.tsx",
"src/routes/nook/agents.tsx",
@@ -43,25 +39,15 @@ const AUDITED = [
"src/components/nook/IslandExtras.tsx"
];
-/** Every sentence FRE-323 ordered off the site. */
+/** The overstatements the restored copy must never carry again. */
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",
+ // 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",
- "every coding agent",
- "Supported agents"
+ "all thirteen",
+ "thirteen coding agents"
];
/** User-facing copy only: comments are not copy and may name the old wording. */
@@ -71,9 +57,46 @@ function copyOf(file: string): string {
.replace(/^\s*\/\/.*$/gm, "");
}
-describe("remote claims stay off the site (FRE-323)", () => {
+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} does not restore a removed remote sentence`, () => {
+ 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());
@@ -81,34 +104,9 @@ describe("remote claims stay off the site (FRE-323)", () => {
});
}
- 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"
+ 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"
);
});
});
-
-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");
- });
-});