scaffold: preview-site skill — start the harness locally and hand over the link (+ serve prints the preview URL) #245
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#245
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add a scaffolded skill,
preview-site, that is the one procedure for "start the harness locally and give me the link": startharness servewithout blocking the agent's shell, wait until the editor and the Hugo preview are actually ready, hand the developer the editor URL and the plain preview URL, keep it running, restart it when needed, stop it. Every other skill that needs the editor points at it instead of restatingharness serve --site .. As a small engine nicety,harness servealso prints the preview URL once Hugo is ready, so a human running it in a terminal sees both links without the skill.The goal: a developer (or an agent on their behalf) can start the site locally in one step and get a link, so the customer or the developer can check that everything works and looks the way they want before anything is committed or published.
Current behavior
harness serve --site .is a foreground process. It prints one line at listen time —deckle harness listening on http://127.0.0.1:8090/— and blocks until SIGINT/SIGTERM. The preview (Hugo) URL is only visible throughGET /api/status(hugo.previewURL) and the editor's View module; it is never printed. Readiness is two-phase: the listener is up first, Hugo becomesrunninglater (on a first run the pinned Hugo is downloaded first, which can take a minute), orcrashedwithhugo.lastBuildError/hugo.outputin the status payload.The scaffolded skills (
scaffold/site/.claude/skills/) mention the command inline in five places and say "open the printed address" —verify-sitestep 3 "Smoke-test in the editor",redo-themestep 3 "The showcase page" andreferences/showcase-page.md,setup-sitestep 4 "Report",edit-content(the "open the page once in the editor so the harness stamps them" step),migrate-sitestep 9 "Verify and hand over". None says how to run it in the background from an agent shell, how to tell it is ready, what link to hand over, what to do when the default port is busy, how to stop or restart it, or that Publish from a local run pushesmain. In practice the developer never gets the link.Desired behavior
The skill
preview-sitescaffold/site/.claude/skills/preview-site/SKILL.md, with front matter the drift test accepts and a description that covers "run, start, serve, preview, open the site locally, get the link, stop/restart the preview" — the genericrunskill in Claude Code looks for a project skill that covers launching the app, so the description must make that match. The procedure, in order:AGENTS.md"Installing the harness" (asmigrate-sitestep 0 does; do not restate the install script).GET /api/sitepayload carries the site title and languages; if that is not enough to tell two sites apart, see "Assumptions to check") and reuse it. Otherwise start the harness in the background — output to a log file, PID recorded — in a way that survives the end of the agent's tool call and does not depend on an exported PATH (AGENTS.md"Installing the harness" explains why). If the default port is taken by something that is not this site's harness, start on a free port with--addr 127.0.0.1:0; the harness prints the real address.listening online in the log (gives the editor URL), then pollGET /api/statusuntilhugo.stateisrunning. Say that a first run downloads the pinned Hugo and may take a minute. Oncrashed, reporthugo.lastBuildErrorand the tail ofhugo.output, and stop — do not hand over a link to a broken preview.http://127.0.0.1:<port>/) and the plain rendered site (hugo.previewURLfrom the status payload), plus what to open for the change at hand (the page just edited;/theme-showcase/afterredo-theme; every language's address on a multilingual site,AGENTS.md"Addresses"). The links are loopback only and that is by design (README.md:--addrrefuses non-loopback hosts); say so, so nobody tries to share them.AGENTS.md"Languages": a running serve readsharness.yamlonly at start;add-backend-layout's note ondefaultBackendLayout) — the skill points there and gives the restart as stop-then-start. Stop is SIGTERM to the recorded PID; the harness shuts Hugo down itself.main:README.md"How it works"). Clicking Publish in a local editor commits and pushesmain; on a deckle-hosted site (the "Hosting:" line inAGENTS.md"Layout") that goes live. The skill tells the developer to save freely and not to publish from a local run unless that is the intent.The skill is a procedure: it cites sections of
AGENTS.mdandREADME.mdfor every fact and restates none (CLAUDE.md "Skills are procedures, AGENTS.md and the theme README are the facts"). Facts the manual lacks today go intoAGENTS.md(below), not into the skill.The other skills point at it
Replace the inline
harness serve --site .and "open the printed address" inverify-sitestep 3,redo-themestep 3 andreferences/showcase-page.md,setup-sitestep 4 (the report ends with "the site is running at / " when the preview is up),edit-content's stamping step andmigrate-sitestep 9 with a link to../preview-site/SKILL.md(the skills link each other with relative links the drift test resolves). What each of those steps checks in the editor stays where it is; only the "get it running and get the link" part moves.verify-sitekeeps itscurl … /api/pageexample but reads the port from whatpreview-sitereported rather than hard-coding 8090.AGENTS.md--addr 127.0.0.1:0for a free port and that the real address is printed; the two readiness phases and theGET /api/statusfields that report them (hugo.state,hugo.previewURL,hugo.lastBuildError); that the preview URL is printed once Hugo is ready (the nicety below); which changes need a restart (a sentence pointing at the existing "Languages" and layout notes, or a consolidated list — one owner for that list, the other mentions point at it); and the Publish caveat. Keep it short; the host-side preview flags stay inREADME.mdand are not for sites.Engine nicety: print the preview URL when Hugo is ready
harness servetoday prints only the listen line on stdout. Once the supervisor reportsrunningfor the first time, print one more line on stdout with the preview URL, e.g.site preview ready at http://127.0.0.1:<port>/— the samehugo.previewURLvalue the status endpoint serves (so with--preview-base-urlit is the public URL). Print it once per process, not on every restart after a crash; on a crash before the firstrunning, nothing extra is printed (stderr already carries the crash log).README.md"Quick start" and theservebullet under the command list, and theharness init"Next steps" line if its wording changes, document the second line.Decisions
verify-site. Starting the preview is needed by five skills and by the plain "let me look at it" request that is none of them; a skill of its own is what the genericrunskill discovers and whatsetup-site's hand-over can end with.preview-site. Settled by the maintainer. It is a verb-noun like the others and matches what the developer asks for.:8090makes the link the same across restarts and matches the existing docs;--addr 127.0.0.1:0already exists and prints the real address, so no port-scanning logic is needed anywhere.listening onkeep working; the new line uses the status endpoint's value so the two never disagree.--open, no daemon mode, no--jsonfor serve. Backgrounding via the shell is enough for the agent, and a human in a terminal sees both links. Not needed for the goal.Key interfaces
cmd/harness/main.go:serve,parseServeArgs, thelistening onprint;printInitResult"Next steps".internal/supervisor:Supervisor.Status()→Status{State, PreviewURL, LastBuildError, Output}; statesstarting/running/crashed. The nicety needs a way to learn of the firstrunningtransition (a callback insupervisor.Config, or pollingStatus()fromserve) — the supervisor owns the state,serveonly prints.internal/server/server.go:handleStatus/statusResponse(hugo.state,hugo.previewURL,hugo.lastBuildError,hugo.output),GET /api/site(siteResponse).scaffold/skills_test.go:expectedSkills,harnessCommands,TestSkillLinksResolve,TestSkillSectionReferencesExist(every quotedAGENTS.md "…"/README.md "…"heading must exist),TestAgentsDocNamesSkills.scaffold/site/AGENTS.md,scaffold/site/.claude/skills/*/SKILL.md,README.md.Skills affected (CLAUDE.md)
preview-site.verify-site(step 3),redo-theme(step 3,references/showcase-page.md),setup-site(step 4 report),edit-content(stamping step),migrate-site(step 9).add-element,remove-element,add-page-type,add-backend-layout,create-feature(they end inverify-site, which now links on).Rules and owners
internal/supervisor(Status), served by/api/status; the skill and the new stdout line both read it, neither re-derives it.AGENTS.md"Languages" and inadd-backend-layout; after this change one place inAGENTS.mdowns the list and the others point at it.--addr: owned byparseServeArgs;AGENTS.mdstates the consequence (the link is local), not the rule.Acceptance criteria
scaffold/site/.claude/skills/preview-site/SKILL.mdexists, passesgo test ./scaffold/...(skill set, front matter, links, quoted headings, command names), and walks through the six steps above citingAGENTS.md/README.mdsections for every fact.harness initsite, with noharnesson PATH and the binary at~/.deckle/harness/harness, ends with both URLs reported, the harness still running after the tool call that started it, and a working stop. Exercise it with arun_in_background-free shell (nohup … &orsetsid) to prove it does not rely on the agent runtime.127.0.0.1:8090, the skill's fallback yields a working editor on a different port and reports that port.harness.yamlnames a Hugo version that cannot be downloaded (or a theme that fails to build), the skill reports the crash from the status payload and hands over no link.verify-site,redo-theme(+ showcase reference),setup-site,edit-content,migrate-siteno longer contain the inlineharness serve --site .start-and-open instruction; each links topreview-site.AGENTS.md's intro namespreview-site, and a "Previewing locally" (or equivalently named) section states the facts listed above;internal/element/agentsdoc_test.goandscaffold/skills_test.gostay green.harness serveprints, afterdeckle harness listening on …, exactly one… ready at <previewURL>line on stdout on the first transition torunning, none on a crash before it, none again after a crash-and-restart; a test incmd/harnesscovers the once-only behaviour with a fake provider or supervisor hook.README.mdQuick start shows both lines; theservebullet mentions the second.AGENTS.md(as the fact).How to verify
go test ./...andgo vet ./....make build, then in a scratch site from./harness init --site /tmp/x: run./harness serve --site /tmp/xin a terminal and see both lines; run it again with--addr 127.0.0.1:0and see the chosen port in the first line.preview-siteliterally from a shell with no PATH export and the binary addressed by full path; confirm the URLs open;kill <pid>; confirm Hugo is gone too (pgrep hugo).harness serve— the remaining mentions are facts about serve behaviour (restart-on-config, pinned Hugo cache), not start instructions.Assumptions to check
GET /api/sitecarries enough to tell "a harness for this site" from "a harness for another site on the same port" (title plus languages may collide between two sites). If not, either compare the site directory (serve knowssiteDir; adding it to the site payload is a small, server-owned verdict) or have the skill treat any foreign listener on the port as "use a free port" and say so.runningtransition without a polling loop inserve; checkinternal/supervisorfor an existing readiness channel/callback before adding one.runskill's project-skill discovery keys on the description text; phrase the description accordingly and spot-check once.cmd/harnessasserts stdout is exactly one line forserve.Out of scope
--openflag, a daemon/detach mode, or--jsonoutput forserve.servefall back to a free port on its own when the default is taken (the skill does it; the command's behaviour stays explicit).--preview-port,--preview-base-url) and their docs.verify-sitestep 3 andredo-themestep 4 too; whichever lands second rebases over the other. Not a blocker.Blocked by
None - can start immediately