From a336a6c6909bab3ae28226d7cbfabeb116b03623 Mon Sep 17 00:00:00 2001 From: agent-cto Date: Tue, 3 Nov 2026 10:32:51 +0000 Subject: [PATCH] site: restore the remote SSH copy and say eighteen agents (FRE-997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FRE-323 correction took the remote story off the site against claims the copy never made: hosts do 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). The removals also dropped the agent count instead of correcting it: the page said thirteen, AgentSpec.all holds eighteen. - Revert c1c498e: the Remote collage tile and its mock, the "Any SSH box, one click away" breakdown with its RemoteHostDemo, and the "One-click SSH setup" chip are back. - Revert c718370: /agents says remote sessions report into the same grid. - The approvals bullet no longer claims "every hooked agent"; it names the three agents that cannot mirror. Muse, Cline and Amp are .unsupported for remote in AgentSpec.remote, so Cline and Amp rows now disclose it too. - Count is 18 on the landing bullet, the marquee chip and all of /agents (title, description, ogTitle, H1 paragraph). - The five missing rows are on /agents: Kimi, GitHub Copilot CLI, Crush, Cursor and Amp, 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+PermissionRequest fire before the host's own check (awaitPolicyTable has no entry; kimi's PermissionRequest is notification-only), Crush documents one event, a silent Cursor permission hook blocks the action, Amp's plugin API has no permission-wait event. - agents.test.ts asserts equality with the 18-name registry again, and the count; remote-claims.test.ts keeps only the two guards worth keeping (no universal remote claim, no stale thirteen). - positioning.md: the held remote row becomes a publishable row with its backing, the count row becomes publishable at 18, and marketing/drafts/remote-ssh/ is deleted. bun test src/routes/nook: 17 pass, 0 fail. Against d44133f with these two test files the same suites give 10 fail (7 pass), so the guards prove the fix. bun run build: ✔ build done (router client, SSR, server-fns, vercel preset). bun test src/routes: 124 pass / 20 fail / 2 errors; the same 20 fail and 2 errors on d44133f, so they are pre-existing environment failures. --- marketing/drafts/remote-ssh/copy.md | 79 ------------- marketing/drafts/remote-ssh/rationale.md | 30 ----- marketing/positioning.md | 40 ++++--- src/components/nook/FeatureBreakdowns.tsx | 127 +++++++++++++++++++- src/components/nook/FeatureCollage.tsx | 50 +++++++- src/components/nook/IslandExtras.tsx | 3 +- src/routes/nook/agents.test.ts | 80 ++++++------- src/routes/nook/agents.tsx | 117 +++++++++++++++--- src/routes/nook/remote-claims.test.ts | 138 +++++++++++----------- 9 files changed, 407 insertions(+), 257 deletions(-) delete mode 100644 marketing/drafts/remote-ssh/copy.md delete mode 100644 marketing/drafts/remote-ssh/rationale.md 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"); - }); -});