import { z } from "zod"; import { limitAccountWrite, limitWrite } from "../../limits"; import { createScratchFromFiles, createScratchRepo, deploySiteRef } from "../../sites/api"; import type { ScratchResult } from "../../sites/api"; import { defineTool, repoArgs, scopedRepo } from "./base"; const MAX_FILES = 500; const MAX_FILE_CHARS = 1024 * 1024; function scratchText(result: ScratchResult): string { return `Live at ${result.url ?? "(sites disabled)"} until ${new Date(result.expiresAt).toISOString()}. Claim it at ${result.claimUrl} (keep the claim token secret).`; } const deploySiteTool = defineTool({ name: "deploy_site", title: "Deploy site", description: "Deploy a repository's site from a branch: its production branch goes live, any other branch deploys a preview on its own host. Requires contents:write.", input: z.object({ ...repoArgs, ref: z.string().max(250).optional().describe("Branch; defaults to the site's production branch"), }), write: true, async run({ caller }, args) { const { repo } = await scopedRepo(caller, args.owner, args.repo, "contents", "write"); await limitWrite(caller.viewer); const result = await deploySiteRef(repo, { ref: args.ref, actorId: caller.viewer.id }); return { structured: { deployment_id: result.deploymentId, environment: result.environment, url: result.url }, text: `Queued ${result.environment} deploy ${result.deploymentId}; it will be served at ${result.url ?? "(sites disabled)"}.`, repoId: repo.id, }; }, }); const scratchSiteTool = defineTool({ name: "create_scratch_site", title: "Create scratch site", description: "Publish files as a temporary site (24 h, up to 500 files and 10 MiB) with a one-time claim token; include _worker.js for a dynamic Worker. Anyone signed in can claim it to keep it.", input: z.object({ files: z .record(z.string().min(1).max(500), z.string().max(MAX_FILE_CHARS)) .refine((files) => Object.keys(files).length <= MAX_FILES, `At most ${MAX_FILES} files`) .describe('Path to UTF-8 file content, e.g. {"index.html": "

hi

"}'), }), write: true, unverifiedOk: true, async run({ caller }, args) { await limitAccountWrite(caller.viewer); const result = await createScratchFromFiles(args.files, `user:${caller.viewer.id}`); return { structured: { url: result.url, claim_url: result.claimUrl, claim_token: result.claimToken, expires_at: result.expiresAt, }, text: scratchText(result), }; }, }); const scratchRepoTool = defineTool({ name: "create_scratch_repo", title: "Create scratch repository", description: "Create a temporary git repository whose pushes deploy a site (main to production, other branches to previews). Returns a remote and push token; push with `git push https://x:@/.git main`.", input: z.object({}), write: true, unverifiedOk: true, async run({ caller }) { await limitAccountWrite(caller.viewer); const result = await createScratchRepo(`user:${caller.viewer.id}`); return { structured: { url: result.url, remote: result.remote, push_token: result.pushToken, claim_url: result.claimUrl, claim_token: result.claimToken, expires_at: result.expiresAt, }, text: `${scratchText(result)} Push to ${result.remote} with password ${result.pushToken}.`, }; }, }); export const siteTools = [deploySiteTool, scratchSiteTool, scratchRepoTool];