Notes

Your assistant should not type your build output

A Vite, Next.js or Nuxt build reaches juudd through a one-time link and a short script, not a tool call. The post stores a version; publish_deploy goes live.

If your project is built by a framework, you do not send it through your assistant. You ask the assistant to call request_upload, which returns a one-time link and a short script. You run the script on the machine that ran the build, and it posts the output folder to the link. The post stores a version and publishes nothing. publish_deploy puts that version live.

That is the decision. The rest of this post is why the bytes take a second door, why the link is the whole credential, and why the door stops short of publishing.

The platform could host it all along

Before anything was designed, three real builds were deployed to production through the existing deploy_site, with no change to the platform: a Vite and Vue app, a Next.js static export and a Nuxt generate. All three served correctly, with deep links, client navigation and their own 404 pages.

So hosting a framework's output was never the gap. The gap was receiving it. A tool call is text the model emits, and no client lets a model emit 802 KB of minified JavaScript as the arguments of one call. Even the 87 KB Vue build sits at the edge of what a call can carry, and it spends every token of it. For a project that already exists, deploy_site is a description of something that fails.

The link is the credential

request_upload takes one argument, the site. It goes through the same ownership check deploy_site does, because the post it leads to will write under that id: a stranger cannot mint a link for a site they do not own, and a fresh id belongs to whoever asked first.

The link is good for exactly one post, to exactly one site, as exactly one account, and nothing else. Everything about it follows from that sentence:

  • It is 32 random bytes, so nothing chose it and nothing can be derived from it.
  • Only its SHA-256 digest is stored, so a leaked table hands out nothing.
  • It expires after 15 minutes: long enough to run a build, not long enough to leave in a chat log.
  • The first post spends it, whatever the post contained. A failed post spends it too, and calling the tool again gives a fresh one.

The post carries no bearer token, because the machine posting is your laptop and not your assistant's client. A link that is unknown, spent or expired gets the same 404 and the same sentence, so a guess cannot learn that it was once real.

It stores a version and publishes nothing

The post takes the body deploy_site takes: files, optional functions, optional routing. It ends in the same store and runs the same gates. The same 4 MB bound applies, text and base64 together, and a folder over it is refused in the same sentence. Every refusal is written to the account's history, so list_refusals shows it beside everything else.

What it stores is a version like any other. It is listed by list_deploys, read back by read_site and published by publish_deploy.

It does not publish, and that is the decision inside the decision. The publish path carries the database wiring, the row policy check and the carry-forward of secret values. It exists once. A second copy in the upload route is exactly the kind that drops a binding on a redeploy with nothing in the upload looking wrong. So a framework deploy is three calls where a plain deploy is one: request_upload, the script, publish_deploy. That is the price of one publish path, and it is paid in a turn, not in a site.

The recipe is in the reply, and so is the script

Tool descriptions are cached by clients, and results are not. So the reply carries everything needed now: the link, which folder each framework leaves behind, and the script itself, about forty lines of Node, printed verbatim. It needs Node 18, which anyone who just ran npm run build has, and nothing installed.

The folders it names are the ones the reply lists: dist for Vite, out for a Next.js export, .output/public for Nuxt. Server code does not come across. Next.js API routes, server rendering and middleware do not export, and Nuxt server/ routes are not in the output. Those become plain-JavaScript functions under functions/ and travel beside the folder with --functions. The tool's description says so before it is called, so an assistant designing a Next.js app here hears it before it writes a route handler.

What we did not build

  • A build service. Nothing is built on this platform's side; the customer this covers already has a terminal and has already built the project.
  • Server rendering. Both Nuxt and Next.js can produce a server bundle instead of files. That is a different bundle shape, and it is deferred with the reason written down.
  • An npm package for the script. It would add a registry account and a release process for forty lines that fit in a reply.
  • A drop zone in the panel. It may yet be wanted for a customer with no shell. Nothing here forecloses it.

What is not measured

The serving half is measured on production. The tool, the route, the one-time and expiry rules, the bound and the recorded refusals are measured against fakes in tests. The script posting a real build to a deployed route has not been recorded. That is one request_upload, one command and one publish_deploy, and the decision's own record says it is outstanding.

The rule, once

A framework project arrives as its build output, through a one-time link and not through the model, and it is published by the one tool that publishes. The tools and their limits are listed in the docs.