Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 4cfd0ed0e6bf4249279f7da7fca97ef03dc39e29
parent ca1ccc9f52272deb312ec9adcae5ef3d430a4dec
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 14:30:50 -0400

plans: release 10 slice M as shipped — fetch_clip (the MCP asks the editor for clip media); editor [Unreleased] bullet

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/CHANGELOG.md | 1+
Mplans/release-10.md | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 125 insertions(+), 0 deletions(-)

diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -6,6 +6,7 @@ - **Jobs a restart left queued are settled even when a drive hangs.** At boot the editor settles those jobs after it has checked where its storage locations are. A hung network mount could stall that check forever, and the jobs then stayed "queued" on `/jobs`. The settling now waits at most 60 seconds, logs `[boot] storage pass still running after 60 s …` and carries on. The storage check keeps running and logs when it ends. A job re-queued for a channel on the hung drive itself still waits for that drive, and any re-queued after it wait too. - **`/jobs` says why a job was cancelled at boot.** A job the boot settled shows its reason under its status on `/jobs` and as *Cancelled because* on its own page: for example "server restarted; the scheduler re-derives syncs" or "superseded by a newer queued job (…)". The reason used to be only in the job's log. - **The server log says how often a queued job skips its page refresh.** When a queued job finishes outside any request, the editor skips its page refresh and notes it in the log. The note used to appear once and never again. Now the first one after a quiet spell is logged at once, any more in the next 10 minutes are counted, and one line at the end gives the count, with a running total. +- **An agent working through the MCP server asks the editor for a clip instead of running yt-dlp.** The MCP server has a new tool, `fetch_clip`. Given a citation's channel, video id, start and end and a one-line reason, it asks the local editor for that window through `POST /api/media/fetch-window`: the same paced, cookie-aware job umtool uses, which records who asked and why beside the file. It answers with the file's path in the corpus (`channels/<slug>/data/<id>/clips/`). The window is the cited span with 3 seconds either side, at most 15 minutes. `full: true` asks for the whole recording instead, which lands in the saved-video store and needs a video the editor already knows. A Rumble citation's id (the embed id the archive publishes) is mapped to the id the editor names the video's folder by, through the archive record's link. The tool waits up to 90 seconds by default (at most 300) and otherwise returns the job's id, to wait on with `job`; the fetch carries on in the editor either way. The `/ask` and `/sweep` plans now tell the agent to use it and never to run yt-dlp itself. The MCP needs `ARCHILYZER_EDITOR_URL` and `WORKER_TOKEN` (the editor's own) in its environment, so re-register it with the two `--env` lines in the README; without them the tool says so and fetches nothing. The MCP server itself still writes nothing. The README's `yt-dlp --download-sections` command is now only the fallback for a machine with no editor. ## [0.9.0] - 2026-09-26 - **Every page now has a ground and an accent to choose, and the five theme families are gone.** The theme menu (the palette button beside the quick toggle, in the editor's sidebar and in the header of every published site, the hub and the homepage) has two groups. **Base** is System, Light, Sepia or Dark; Sepia is new, a warm paper ground for long reading. **Accent** is Signal, Brass, Vermilion, Violet, Sakura, Blue or Green, with the site's own tagged *default*; a site with a custom hex offers it first as *Site colour*. The quick toggle cycles System → Light → Sepia → Dark. A published site opens on the reader's system setting, in the accent its site form sets. The hub and the homepage open on Dark, in Signal, even with JavaScript off, and the editor follows the system, in Signal. Each accent has a value for each ground that reads at 4.5:1, and a custom hex is darkened or lightened per ground to match. A reader's accent is remembered only while it differs from the site's: picking the site's own again forgets it, so the reader follows the site if its accent changes later. Base, Archive, Selenized, Swiss and Archilyzer are gone. A choice made before this update carries over once: light stays light (Archive light becomes Sepia), dark stays dark and system stays system; the family itself is dropped. Headings are Archivo, text is IBM Plex Sans and figures are IBM Plex Mono everywhere, with one corner radius. Success, warning and other status text reads at 4.5:1 on its own tinted fill on every ground; on Light, success and warning are a shade deeper than before for it. Chart colours are fixed per ground and never follow the accent; the third is a violet, well clear of the red that marks a recording as gone. The phone's browser bar takes the page's ground, not the accent. Needs a rebuild and deploy of every site, the hub and the homepage. diff --git a/plans/release-10.md b/plans/release-10.md @@ -761,6 +761,130 @@ its own file, and the next seed relinks it. `ready` with no sign that its live chat is missing. A chip note with a subs-only Retry would show it. +### Slice M, as shipped — fetch_clip (2026-09-26) + +Branch `mcp/fetch-clip` off `main` `4ac32a2e`, worktree `/home/user/Projects/mcp-fetch-clip`, one +Opus implementer. The plan is [`mcp-fetch-clip.md`](mcp-fetch-clip.md), §1–§5 and its Amendment +(`full: true` exposed). The MCP server gains ONE tool, `fetch_clip`, which asks the local editor for +the media behind a cited moment through its existing `POST /api/media/fetch-window`. So an agent +following `/ask` or `/sweep` no longer has to shell out to yt-dlp (unpaced, no cookies, bytes +outside the corpus) or stop. The guidance now makes the tool the way, and `yt-dlp +--download-sections` only the no-editor fallback. Nothing in `common/`, `editor/`, `export/`, +`scripts/` or `umtool/` changed; no settings, site or channel key; nothing on disk. + +**The client** (`mcp/src/fetchClip.ts`, pure; `env`, `fetch`, `sleep` and `now` injected, no MCP +imports). +- `parseSeconds`: a number, `ss`, `mm:ss` (minutes may pass 59) or `h:mm:ss`. +- `planWindow`: umtool's arithmetic, `from = max(0, start-pad)` and `to = end+pad`, each + `.toFixed(2)`; the 900 s cap (`MAX_CLIP_WINDOW_SECONDS`, imported) applies AFTER padding. +- `validateFetchClipArgs`: a `job` alone is a whole request and every other argument is ignored; + `full: true` ignores `start`/`end`/`pad` (not required, not validated); `reason` is required in + both modes; `wait_seconds` is clamped to [0, 300], default 90. +- `fetchClip`: POST (unless resuming), then poll every 1 s via `deps.sleep` until the job is + terminal or `deps.now()` passes the deadline. A resume polls before it sleeps. The body is + `{channelSlug, videoId, webpageUrl?, from, to, pad, requestedBy: "mcp", manifest, reason}`, the + reason cut to 400; in full mode exactly `{channelSlug, videoId, full: true, requestedBy, manifest, + reason}`. +- `renderFetchClip`: the plan's texts, verbatim where the plan gave them. +- HTTP only: the MCP process writes nothing. + +**The tool** (`mcp/src/server.ts`): the schema after `get_video_metadata`, `required: []`, +`additionalProperties: false`. `createServer(source, {fetchClipDeps})` defaults to `process.env`, +`globalThis.fetch`, a `setTimeout` sleep and `Date.now`. `source` resolves first and `withCorpus` +adds the trailer, as for every tool. `handleFetchClip`: +- **no editor configured** is said before argument validation and before any corpus read; +- `findVideo(source, video, channel)`; +- the id sent is `extractVideoId(record.webpageUrl) ?? video` (the editor's own directory naming — + a Rumble embed id `vxe1ae` becomes `v1007ay`), and the record's `webpageUrl` rides along in + window mode; +- a video `source` does not hold goes through as cited, with the plan's `note:` prefix. + +**The plans** (`mcp/src/instructions.ts`): one shared `clipStep`, after "Answer with citations" in +ask and before **Finish** in the sweep. It carries the plan's sentence with the amendment's +clause. `wait_seconds` and `full` are not backticked, and `NOT_TOOLS` is unchanged. + +**Docs.** +- `README.md`: both `claude mcp add` blocks carry the two optional `--env` lines. In "Clips and + video", step 3 is `fetch_clip` (window → `clips/`, `full: true` → the saved-video store), the + editor path no longer claims `out/clips-raw`, and yt-dlp is the no-editor fallback. +- `mcp/README.md`: the one exception to "writes nothing", the tool row, and "Still read-only" + now says the editor writes. +- `AGENTS.md`: the env lines, and the clips loop through `fetch_clip`. +- `grep -n 'download-sections' README.md AGENTS.md mcp/README.md` gives two lines, both in + no-editor fallback sentences (`AGENTS.md:87`, `README.md:366`). + +**Where the texts depart from, or fill in, the plan** (for the reviewer): +- **A 503 is a token problem only when its error says "disabled".** The plan put every 401/503 on + the token text. But `fetchWindowAction` answers 503 for an unreachable-media refusal + (`videoActions.ts`, the `!res.ok` after `runManagedFunction`), and `fetchFullSourceAction` for + any `archiveSourceVideo` error that is not low disk. Under the token text, "plug the drive in" + would have read as "fix your token". Such a 503 goes through the generic `The editor refused + (HTTP 503): <error verbatim>`. +- **The channel sent is the record's own `ch.slug` when the video is found** (the plan: the + `channel` argument). A citation that spells the channel by name or in another case still reaches + the right directory. When the video is not found, the argument goes as given. +- Texts the plan left open: + - `Already on disk — the exact window: <from>–<to>.` + - `requested by <x>` is a footer line, shown for a cached whole recording too (`SavedVideoOrigin` + carries it). + - The footer names the window's real sidecar (`7.00-23.00.json`). + - A missing start/end reads `fetch_clip: start is required (seconds, mm:ss or h:mm:ss) — or pass + full: true for the whole recording`. + - A bad pad reads `fetch_clip: pad "<p>" must be a finite number of seconds, 0 or more`. + - A done window job with no file uses the amendment's full-mode "finished but named no file" + text. + - A poll 401, or a disabled 503, adds the token sentence. + - A resume knows no channel or video, so its "Fetched …" line omits "of <channel>/<video>", and + window or full is read off the poll's answer (a window's carries `from`/`to`). + - A 409 is `isError`; only the expired wait (`Still <status> …`) is not. + +| sha | what | +|---|---| +| `55f81128` | `mcp: fetchClip` — the pure client and its texts; `fetchClip.test.ts` (30) | +| `2cd35fbf` | `mcp: fetch_clip` tool — schema, `createServer(…, {fetchClipDeps})`, `handleFetchClip` (Rumble mapping, not-found note); `EXPECTED_TOOLS` +1; `fetchClip.tool.test.ts` (9) | +| `345bf26d` | `mcp:` the ask and sweep plans' `clipStep`; `instructions.test.ts` +1 | +| `a0326dfd` | `docs:` README (both blocks, "Clips and video"), `mcp/README.md`, `AGENTS.md` | +| _this_ | `plans:` this record; the editor `[Unreleased]` bullet | + +**Gates**, all from the worktree root. +- **tsc** (`pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit`) clean on the full tree + before the first commit (`m-gate1.log`). The mcp package's `tsc --noEmit` is also clean on each of + the three code commits checked out alone (`m-tsc-per-commit.log`). The docs commit has no code. +- **mcp 259/259** (219 + 40: `fetchClip.test.ts` 30, `fetchClip.tool.test.ts` 9, + `instructions.test.ts` +1; `protocol.test.ts` still 2 tests, now with 15 names), 23 s. +- **`test:scripts` 173 + 1 skip of 174**; **common 1,954/1,954** (`m-gate2.log`). Both are `main`'s + counts: the prompt's 162 + 1 and 1,845 predate L1, L2 and S4. This slice changes neither + (`git diff --stat 4ac32a2e a0326dfd -- common scripts umtool editor export homepage` is empty; + the `plans:` commit adds only the `editor/CHANGELOG.md` line). +- **Builds and e2e: none, by design.** No editor, export, homepage or umtool code changed, and no + e2e spec names an MCP tool: `grep -rln 'fetch_clip\|get_video_metadata\|ask_plan' editor/e2e + export/e2e homepage/e2e umtool/e2e` is empty. +- **The tests bite.** With the mapping line reverted to pass the cited id, 2 of the 9 tool tests + fail: the Rumble window and the full-mode Rumble. The timeout test runs its 3 s wait (three + 1 s polls) on a fake clock that advances by each `sleep`, so no real timer is involved. +- **Numbers tools: none.** Nothing here was run against the live :3001 editor. The live proofs are + rollout step 1. + +**Found and left.** +- **`mcp/bench/smoke.ts --clip` (optional) was not done.** The rollout's live proof is an in-memory + client script, as the plan says. A `--clip` smoke would need the stdio transport to pass + `WORKER_TOKEN` through: `StdioClientTransport` gives the child only `getDefaultEnvironment()` + unless `env` is set. +- **Both plan heads still say "The MCP is read-only".** That is true of the process. The new step + sits beside it and says the editor fetches. +- **`mcp/README.md`'s own "Add to Claude Code" and `mcp.json` examples** do not carry the two env + lines. The plan named only README.md's two blocks and AGENTS.md's; the tool row names both + variables. +- **The plan's known limitations stand.** + - A video absent from `source` is passed through as cited, with the note. + - Full mode sends no `webpageUrl`, so a video the editor has never seen gets the editor's 404 + (with the added sentence), where a window can still fetch it by URL. + - A public-only setup gets the no-editor error. +- **FACTS and STATE are not edited** (shared files): the plan's rollout step 4 adds "`fetch_clip` + is the only MCP tool that causes a write, and the editor does it". +- **Registration is owed by the operator** (rollout step 2): this machine's `archilyzer` entry has + `"env": {}`, so until it is re-registered `fetch_clip` answers "no editor configured". + ## Rollout Nothing is rolled out. The live :3001 editor still runs `0213f6c8` (the pre-brand build); the five