drivethrough-cli 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +23 -13
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -23865,19 +23865,24 @@ function sitePath(site) {
23865
23865
  return `/v1/sites/${encodeURIComponent(site)}`;
23866
23866
  }
23867
23867
  function registerTools(server, ctx) {
23868
+ const canDeployDirectory = Boolean(ctx.localDirectories);
23869
+ const deploySources = canDeployDirectory ? "Pass `files` with generated content (the platform assembles the bundle server-side \u2014 no local filesystem or build toolchain needed), or `directory` when this tool runs on the user's machine." : "Pass `files` with generated content \u2014 the platform assembles the bundle server-side, so no local filesystem or build toolchain is needed. Content you generated counts even once it is sitting in a file: if you wrote the page to your own sandbox, read that text back and send it as files[].content.";
23870
+ const binaryRoutes = canDeployDirectory ? "a local file \u2014 the user's, or an attachment already in your runtime \u2014 and no directory deploy here \u2192 create_upload first, then {path, upload_id}; a local project directory with filesystem access \u2192 `directory` carries binaries by itself; " : "a local file \u2014 the user's, or an attachment already in your runtime \u2014 \u2192 create_upload first, then {path, upload_id}; ";
23868
23871
  server.registerTool(
23869
23872
  "deploy_site",
23870
23873
  {
23871
23874
  title: "Deploy site",
23872
- description: "Deploy a site to Drivethrough. Idempotent: creates the site if needed, else ships a new version. Sites are private by default. Pass `files` with generated content (the platform assembles the bundle server-side \u2014 no local filesystem or build toolchain needed), or `directory` when this tool runs on the user's machine. Images and other binary assets \u2014 pick the route by where the bytes live: already at a public URL \u2192 {path, source_url} (fetched server-side); a local file \u2014 the user's, or an attachment already in your runtime \u2014 and no directory deploy here \u2192 create_upload first, then {path, upload_id}; a local project directory with filesystem access \u2192 `directory` carries binaries by itself; already deployed on this site \u2192 {path, from_path} copies it server-side (with delete_paths, a move). Never base64-encode a file, by hand or in code \u2014 inline base64 is capped at 1 MB and is only for tiny pasted assets; a file shipped via upload_id is referenced, never additionally inlined. To rework an already-deployed site, read it back with get_site first, then deploy with mode \"patch\" \u2014 only the files that change. For small text changes to existing files, use edit_site instead: it sends find/replace pairs, never whole files. Every deploy's version number is a restore point: prior versions stay listed (get_site history), diffable, viewable (preview_site), and returnable (revert_site). A successful deploy may still return `warnings` (e.g. a form type anonymous visitors can write with no page loading the Turnstile widget) \u2014 resolve them before telling the user it works.",
23875
+ description: `Deploy a site to Drivethrough. Idempotent: creates the site if needed, else ships a new version. Sites are private by default. ${deploySources} Images and other binary assets \u2014 pick the route by where the bytes live: already at a public URL \u2192 {path, source_url} (fetched server-side); ${binaryRoutes}already deployed on this site \u2192 {path, from_path} copies it server-side (with delete_paths, a move). Never base64-encode a file, by hand or in code \u2014 inline base64 is capped at 1 MB and is only for tiny pasted assets; a file shipped via upload_id is referenced, never additionally inlined. To rework an already-deployed site, read it back with get_site first, then deploy with mode "patch" \u2014 only the files that change. For small text changes to existing files, use edit_site instead: it sends find/replace pairs, never whole files. Every deploy's version number is a restore point: prior versions stay listed (get_site history), diffable, viewable (preview_site), and returnable (revert_site). A successful deploy may still return \`warnings\` \u2014 things that will bite at runtime but only for visitors, never for you: a form type anonymous visitors can write with no page loading the Turnstile widget, or a page that fetches a data type its visitors have no permission to read. Resolve them before telling the user it works.`,
23873
23876
  // Destructive: the default "replace" mode drops every deployed file the
23874
23877
  // call doesn't re-send, and delete_paths removes files outright. Prior
23875
23878
  // versions stay revertible, but the live site does lose content.
23876
23879
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
23877
23880
  inputSchema: {
23878
- directory: external_exports.string().optional().describe(
23879
- "Path to a local project directory to deploy (stdio/CLI transport only; uses ./dist inside it when present)"
23880
- ),
23881
+ ...canDeployDirectory ? {
23882
+ directory: external_exports.string().optional().describe(
23883
+ "Path to a local project directory to deploy (stdio/CLI transport only; uses ./dist inside it when present)"
23884
+ )
23885
+ } : {},
23881
23886
  files: external_exports.array(
23882
23887
  external_exports.object({
23883
23888
  path: external_exports.string().describe('Site-relative path like "/index.html"'),
@@ -23896,7 +23901,7 @@ function registerTools(server, ctx) {
23896
23901
  )
23897
23902
  })
23898
23903
  ).optional().describe(
23899
- "Site files passed inline \u2014 use when there is no local filesystem (chat/remote agents). Include a drivethrough.json entry to declare data types."
23904
+ canDeployDirectory ? "Site files passed inline \u2014 use when there is no local filesystem (chat/remote agents). Include a drivethrough.json entry to declare data types." : "The site's files, passed inline \u2014 the whole deploy path here. Text you generated is deployable as-is, including a file you wrote to your own sandbox: read its contents and send them as `content`. Include a drivethrough.json entry to declare data types."
23900
23905
  ),
23901
23906
  site: external_exports.string().optional().describe('Site name (defaults to drivethrough.json "site" or the folder name)'),
23902
23907
  title: external_exports.string().optional().describe("Human title shown to guests"),
@@ -23979,8 +23984,12 @@ function registerTools(server, ctx) {
23979
23984
  throw new ToolApiError(422, {
23980
23985
  error: {
23981
23986
  code: "missing_source",
23982
- message: "Nothing to deploy: pass `files` (inline site content) or `directory` (local project path).",
23983
- next: 'For chat-based deploys, pass files like [{ "path": "/index.html", "content": "..." }].'
23987
+ message: canDeployDirectory ? "Nothing to deploy: pass `files` (inline site content) or `directory` (local project path)." : (
23988
+ // A stale client may still send `directory`; schema validation
23989
+ // strips it before this handler sees it, so name it here.
23990
+ "Nothing to deploy: this deploy carried no `files`. There is no `directory` option on this connection \u2014 it has no filesystem of yours to read."
23991
+ ),
23992
+ next: 'Pass files like [{ "path": "/index.html", "content": "..." }] \u2014 content you generated, including text you wrote to a file in your own sandbox.'
23984
23993
  }
23985
23994
  });
23986
23995
  }
@@ -24648,7 +24657,7 @@ var init_tools = __esm({
24648
24657
  Work token-efficiently \u2014 generated output is your scarcest resource:
24649
24658
  - LOCATE, don't read: to find where text lives on a deployed site, call get_site with \`find\` \u2014 it returns byte-exact matching lines. Use \`manifest_only\` to see what files exist. Read full contents (\`paths\`) only when you genuinely need them; a bare get_site inlines every text file.
24650
24659
  - EDIT, don't resend: change existing files with edit_site (exact find/replace). Never re-emit a whole file to change a small part. Use deploy_site mode "patch" for new files or wholesale rewrites \u2014 unchanged files carry forward automatically.
24651
- - REFERENCE, don't inline: never base64-encode a file \u2014 not by hand, not in code, and never as a data: URI in HTML. To ship an image or other binary asset, pick the route by where the bytes live: already at a public URL \u2192 deploy_site {path, source_url}, fetched server-side; a local file (the user's, or a chat attachment already in your runtime) \u2192 create_upload returns a link the user opens in their browser to drag files in; when they say done, call create_upload {session} for the upload_ids, then deploy_site {path, upload_id}. (Agents with a local filesystem deploy the directory instead \u2014 binaries ride along.) To move or duplicate an ALREADY-DEPLOYED file, {path, from_path} copies it server-side \u2014 never re-upload unchanged bytes, and once an upload_id exists, reference it rather than inlining the same bytes again. Inline base64 content is capped at 1 MB and exists only for tiny pasted assets like a favicon.
24660
+ - REFERENCE, don't inline: never base64-encode a file \u2014 not by hand, not in code, and never as a data: URI in HTML. To ship an image or other binary asset, pick the route by where the bytes live: already at a public URL \u2192 deploy_site {path, source_url}, fetched server-side; a local file (the user's, or a chat attachment already in your runtime) \u2192 create_upload returns a link the user opens in their browser to drag files in; when they say done, call create_upload {session} for the upload_ids, then deploy_site {path, upload_id}. (Only when this server itself runs on the user's machine \u2014 the CLI \u2014 is there a \`directory\` parameter to deploy instead, binaries riding along; your own sandbox is not that machine, and files you generate there deploy fine as \`content\`.) To move or duplicate an ALREADY-DEPLOYED file, {path, from_path} copies it server-side \u2014 never re-upload unchanged bytes, and once an upload_id exists, reference it rather than inlining the same bytes again. Inline base64 content is capped at 1 MB and exists only for tiny pasted assets like a favicon.
24652
24661
  - MERGE, don't replace: to change a few fields of a database row, write_db update with \`merge\` (null deletes a field). No need to fetch the row first.
24653
24662
  - UNDO by version, never by inverse edits: every deploy is a numbered version the platform keeps. When the user wants a change undone, call revert_site \u2014 it restores the exact bytes of any prior version in one call and proves it. Reconstructing old content from memory with edit_site is how sites end up in a state that is neither old nor new.
24654
24663
  - LOOK at what shipped: you have no browser, but preview_site returns a real rendered screenshot of any version \u2014 your only way to see visual results. Use it after layout/style/animation work, or to eyeball an old version before reverting.
@@ -24664,11 +24673,12 @@ RECIPE \u2014 add an uploaded image to a live site (the complete flow, 4 calls):
24664
24673
  4. edit_site to place it: insert/replace an <img src="/images/photo.jpg" alt="\u2026"> where it belongs. Then preview_site to see it.
24665
24674
 
24666
24675
  RECIPE \u2014 public form that saves to the site's database (RSVPs, leads, guestbooks):
24667
- 1. Declare the type in drivethrough.json and deploy it (mode "patch" on a live site): {"types": {"lead": {"write": "public", "fields": {"name": "string", "email": "string", "note": "string?"}, "notify": "owner-email"}}}. Deploying it provisions the database; submissions are owner-only readable by default. Optional: "unique_per": "session" makes a visitor's row update instead of duplicate (RSVPs); "rate_limit" like "5/hour" throttles.
24668
- 2. Wire the page, same origin, no SDK: on submit, POST /api/data with JSON body {type: "lead", json: {name, email, note}, turnstile_token}. GET /api/data?type=lead \u2192 {items} lists rows the caller may read.
24669
- 3. Turnstile (required for anonymous writes, usually invisible to visitors): GET /api/config \u2192 {turnstile_site_key}; load https://challenges.cloudflare.com/turnstile/v0/api.js; put <div class="cf-turnstile" data-sitekey="\u2026"></div> inside the form; send its "cf-turnstile-response" input value as turnstile_token.
24670
- 4. Anonymous visitors only reach a public site (set_privacy) or a shared one \u2014 on a private site the form works for invited viewers.
24671
- 5. Verify: the deploy response includes a warning when an anonymously-writable type is declared but no page loads the Turnstile widget \u2014 fix it before telling the user it works. End-to-end test: have the user submit once, then query_db {action: "list", type: "lead"} to show the row landed. (Local dev accepts any turnstile_token \u2014 the test secret always passes \u2014 so there a scripted POST to /api/data proves the whole path.)
24676
+ 1. Declare the type in drivethrough.json and deploy it (mode "patch" on a live site): {"types": {"lead": {"write": "public", "fields": {"name": "string", "email": "string", "note": "string?"}, "notify": "owner-email"}}}. Deploying it provisions the database. Optional: "unique_per": "session" makes a visitor's row update instead of duplicate (RSVPs); "rate_limit" like "5/hour" throttles.
24677
+ 2. Decide who may SEE the submissions \u2014 "write" and "read" are separate, and "anyone may write" never implies "anyone may read". Collected rows default to owner-only, so a page that lists them is empty for everyone but you. If the user wants the entries shown back on the page \u2014 a potluck signup list, a guestbook, a public RSVP list, "show who's coming" \u2014 you MUST add "read": "public" to that type (the only valid values are "public" | "viewers" | "owner"). This is a deliberate publishing decision about other people's names and words: make it when the user asked for a visible list, tell them their guests' entries will be public, and say so on the form itself so people know before they type. Leave it owner-only for anything that collects private data \u2014 emails, addresses, orders, applications \u2014 and show the user those rows with query_db instead.
24678
+ 3. Wire the page, same origin, no SDK: on submit, POST /api/data with JSON body {type: "lead", json: {name, email, note}, turnstile_token}. GET /api/data?type=lead \u2192 {items} lists rows the caller may read.
24679
+ 4. Turnstile (required for anonymous writes, usually invisible to visitors): GET /api/config \u2192 {turnstile_site_key}; load https://challenges.cloudflare.com/turnstile/v0/api.js; put <div class="cf-turnstile" data-sitekey="\u2026"></div> inside the form; send its "cf-turnstile-response" input value as turnstile_token.
24680
+ 5. Anonymous visitors only reach a public site (set_privacy) or a shared one \u2014 on a private site the form works for invited viewers.
24681
+ 6. Verify \u2014 you have no browser, so the deploy response is your feedback loop: it returns \`warnings\` when an anonymously-writable type has no Turnstile widget on any page, and when a page fetches a type its visitors aren't allowed to read (the fetch 403s inside THEIR browser; the page looks perfect to you because you're the owner). Never report success while a warning stands. Then have the user submit once and query_db {action: "list", type: "lead"} to show the row landed. (Local dev accepts any turnstile_token \u2014 the test secret always passes \u2014 so there a scripted POST to /api/data proves the whole path.)
24672
24682
 
24673
24683
  Custom domains (paid plans): manage_domain attaches a domain the user already owns. After attaching, check_domain {action: "connect"} looks up one-click setup at their registrar (relay the URL when offered, else the exact DNS records from attach), and check_domain {action: "status"} polls until "active" \u2014 DNS seen, then TLS issued.`;
24674
24684
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drivethrough-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Agent-first hosting CLI. Say what you want. Stay in the car.",
5
5
  "type": "module",
6
6
  "license": "MIT",