drivethrough-cli 0.1.31 → 0.1.33

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 +99 -36
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -40013,7 +40013,7 @@ var init_remote_assets = __esm({
40013
40013
  });
40014
40014
 
40015
40015
  // ../mcp-tools/src/output-schemas.ts
40016
- var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, testResult, OUTPUT_SCHEMAS;
40016
+ var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, testResult, renderedRequest, OUTPUT_SCHEMAS;
40017
40017
  var init_output_schemas = __esm({
40018
40018
  "../mcp-tools/src/output-schemas.ts"() {
40019
40019
  "use strict";
@@ -40088,6 +40088,12 @@ var init_output_schemas = __esm({
40088
40088
  error: external_exports.string().nullish(),
40089
40089
  next: external_exports.string().optional()
40090
40090
  });
40091
+ renderedRequest = external_exports.looseObject({
40092
+ method: external_exports.string(),
40093
+ url: external_exports.string(),
40094
+ headers: external_exports.record(external_exports.string(), external_exports.string()),
40095
+ body: external_exports.string().nullable()
40096
+ }).describe("The request exactly as it would be sent, secret values replaced by [secret]");
40091
40097
  OUTPUT_SCHEMAS = {
40092
40098
  deploy_site: external_exports.looseObject({
40093
40099
  ok,
@@ -40255,7 +40261,8 @@ var init_output_schemas = __esm({
40255
40261
  actions: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Declared actions, their fields and triggering types"),
40256
40262
  schedules: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Each schedule's enabled, next_at, last_run_at, last_status, failures, disabled_reason"),
40257
40263
  webhooks: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Each webhook's url (what the other service POSTs to), verify, secret_set, last_event_at, last_status and setup steps"),
40258
- action_log: external_exports.array(external_exports.unknown()).optional().describe("The latest 20 outcomes, newest first"),
40264
+ action_log: external_exports.array(external_exports.unknown()).optional().describe("The latest 20 outcomes, newest first \u2014 actions, schedules, webhooks and sampled page use of routes"),
40265
+ reference: external_exports.string().optional().describe("With reference: true \u2014 the drivethrough.json integrations format and two complete examples"),
40259
40266
  next: external_exports.string().optional()
40260
40267
  }).describe("Everything declared plus recent outcomes"),
40261
40268
  request_secrets: external_exports.looseObject({
@@ -40271,6 +40278,13 @@ var init_output_schemas = __esm({
40271
40278
  test_route: testResult.describe("One GET through the route"),
40272
40279
  run_action: testResult.describe("One real run of the action"),
40273
40280
  run_schedule: testResult.describe("One real run of the schedule"),
40281
+ preview_action: testResult.extend({ request: renderedRequest.nullish() }).describe("The request the action would send, secrets redacted; nothing was sent"),
40282
+ preview_schedule: testResult.extend({
40283
+ request: renderedRequest.nullish(),
40284
+ next_row_seq: external_exports.number().nullish().describe("rotate: the row the next run would send"),
40285
+ into: external_exports.string().optional().describe("pull: the type a response would be saved as"),
40286
+ map: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("pull: field \u2192 $.path in the response")
40287
+ }).describe("What the next run would send, secrets redacted; nothing was sent"),
40274
40288
  preview_webhook: testResult.extend({
40275
40289
  url: external_exports.string().optional().describe("The URL the other service POSTs to"),
40276
40290
  row: external_exports.record(external_exports.string(), external_exports.unknown()).nullish().describe("The row an event like the sample would store (nothing is written)")
@@ -41156,24 +41170,27 @@ function registerTools(server, ctx) {
41156
41170
  }
41157
41171
  }
41158
41172
  );
41159
- const testTarget = (site2, kind, target, fields) => ctx.api.request("POST", `${sitePath(site2)}/integrations/test`, {
41160
- body: { target, kind, ...fields ? { fields } : {} }
41173
+ const testTarget = (site2, kind, target, opts = {}) => ctx.api.request("POST", `${sitePath(site2)}/integrations/test`, {
41174
+ body: { target, kind, ...opts.fields ? { fields: opts.fields } : {}, ...opts.dry ? { dry: true } : {} }
41161
41175
  });
41162
41176
  const fieldValues = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]));
41177
+ const liveCheck = "To see a real run, ask the user to use the page once (submit the form, press the button, load the page), then read get_integrations' action_log.";
41163
41178
  server.registerTool(
41164
41179
  "get_integrations",
41165
41180
  {
41166
41181
  outputSchema: OUTPUT_SCHEMAS.get_integrations,
41167
41182
  title: "Get integrations",
41168
- description: `Show a site's connections to other services' APIs (Pro plan) and how they ran: which secrets are set (never their values), the declared routes and actions, each schedule's next run, last result and failures, each webhook's URL and last event, and the latest 20 outcomes. Read-only. THE CONFIGURATION FORMAT \u2014 integrations are declared in the site's drivethrough.json and deployed with deploy_site; once deployed, the platform runs them for the site, and the only operations you can perform on them are this tool, request_secrets, test_route, run_action, run_schedule and preview_webhook. A site can declare: "secrets" names the keys; "routes" proxies same-origin paths under /api/ext/<name> to an upstream, allowing only the listed methods and path prefixes and adding secret headers server-side (\${NAME}); "actions" POST a JSON body ({{field}} reads a row's field) when a type with "on_write" gets a row; "schedules" run every 1h/3h/6h/12h/1d/1w/1mo \u2014 "rotate" sends the next row of a type through an action, "pull" saves a route's JSON response as a new row ("map" uses $.dotted.paths; numeric strings become numbers); "webhooks" receive events another service POSTs to https://<site>/api/hook/<name> \u2014 "verify" is "stripe", "github", "standard" (Standard Webhooks: Resend, Clerk, Svix\u2026), "hmac_sha256" (with "header": the signature header's name) or, only for services that can't sign, "url_token"; "secret" names the declared secret; each verified event is mapped ("map", same $.paths) into a row of "into", which fires that type's on_write. Unverified, duplicate and replayed deliveries are never stored. No cron strings, no times of day, no timezones. Two complete examples \u2014 a read-only brokerage dashboard that also snapshots equity hourly for a chart: \`${INTEGRATION_EXAMPLE_A}\` \u2014 and a display that shows a quote when the owner clicks, and cycles through saved quotes every 3 hours: \`${INTEGRATION_EXAMPLE_B}\`. Every mistake is refused at deploy time with the corrected snippet \u2014 follow it. Keys are set with request_secrets; each piece is checked with test_route, run_action, run_schedule or preview_webhook. WHEN A DEPLOYED ACTION RUNS: when a visitor's form (or the owner's own button) on the page writes a row of a type with "on_write" \u2014 never from write_db, imports or schedule pulls. The only way for you to run one is run_action. Route access defaults to the owner only; set "access": "viewers" or "public" for pages others use. Schedules pause after 10 failures in a row (the owner gets one email); a deploy or a successful run_schedule re-enables them.`,
41183
+ description: `Show a site's connections to other services' APIs (Pro plan) and how they last went: which secrets are set (never their values), the declared routes, actions, schedules (next run, last result, failures) and webhooks (URL, last event), and the latest 20 outcomes, including real page use. Read-only. Pass reference: true for the drivethrough.json format these are declared in, with two complete examples \u2014 read it before adding one to a site.`,
41169
41184
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41170
41185
  inputSchema: {
41171
- site: external_exports.string().describe("Site name or id")
41186
+ site: external_exports.string().describe("Site name or id"),
41187
+ reference: external_exports.boolean().optional().describe("true: also return the drivethrough.json integrations format and two complete examples")
41172
41188
  }
41173
41189
  },
41174
- async ({ site: site2 }) => {
41190
+ async ({ site: site2, reference }) => {
41175
41191
  try {
41176
- return ok2(await ctx.api.request("GET", `${sitePath(site2)}/integrations`));
41192
+ const status = await ctx.api.request("GET", `${sitePath(site2)}/integrations`);
41193
+ return ok2(reference ? { ...status, reference: INTEGRATION_REFERENCE } : status);
41177
41194
  } catch (error62) {
41178
41195
  return toolError(error62);
41179
41196
  }
@@ -41185,7 +41202,10 @@ function registerTools(server, ctx) {
41185
41202
  outputSchema: OUTPUT_SCHEMAS.request_secrets,
41186
41203
  title: "Request secrets",
41187
41204
  description: `Get a one-time link where the user sets the API keys a site's integrations need (Pro plan) \u2014 two steps, like create_upload. Call it with the site: it returns a link (10 minutes, one use, no sign-in). Send it to the user as a clickable link and have them paste the keys there. NEVER ask for a key in the chat, and never put one in a file \u2014 this tool never receives or returns a key's value. When they say they're done, call again with the returned session: it reports which names are set. A deploy that needs an unset secret is refused with a fresh link already in the error \u2014 relay that one instead. Pass names to rotate a key that is already set. A webhook's secret comes FROM the other service: tell the user to add a webhook there pointing at its URL (get_integrations and the deploy error give it) and paste the signing secret it shows into the link page; a url_token secret is generated on the link page, which then shows the full URL to paste.`,
41188
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
41205
+ // Destructive: a link can replace a key that is already set, and the old
41206
+ // value is gone once the user saves (OpenAI's guidelines reserve false
41207
+ // for purely additive writes).
41208
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
41189
41209
  inputSchema: {
41190
41210
  site: external_exports.string().describe("Site name or id"),
41191
41211
  names: external_exports.array(external_exports.string()).optional().describe("Which secret names the link asks for. Default: every declared secret that is still unset. Include a set one to replace (rotate) it."),
@@ -41216,7 +41236,7 @@ function registerTools(server, ctx) {
41216
41236
  session,
41217
41237
  set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
41218
41238
  all_set: unset.length === 0,
41219
- next: unset.length ? `Still unset: ${unset.join(", ")}. Ask the user to finish on the page \u2014 or, if the link was used or expired, call request_secrets again without a session for a fresh one.` : "Every declared secret is set. Deploy (or redeploy) the site, then check each piece with test_route, run_action, run_schedule or preview_webhook."
41239
+ next: unset.length ? `Still unset: ${unset.join(", ")}. Ask the user to finish on the page \u2014 or, if the link was used or expired, call request_secrets again without a session for a fresh one.` : "Every declared secret is set. Deploy (or redeploy) the site, then check each piece with preview_action, preview_schedule or preview_webhook, and have the user use the page once."
41220
41240
  });
41221
41241
  }
41222
41242
  const link = await ctx.api.request("POST", `${sitePath(site2)}/secrets/link`, {
@@ -41231,6 +41251,69 @@ function registerTools(server, ctx) {
41231
41251
  }
41232
41252
  }
41233
41253
  );
41254
+ server.registerTool(
41255
+ "preview_action",
41256
+ {
41257
+ outputSchema: OUTPUT_SCHEMAS.preview_action,
41258
+ title: "Preview action",
41259
+ description: `Show the exact request one of a site's declared actions would send for a sample row (Pro plan) \u2014 method, URL, headers and JSON body, with every secret value redacted \u2014 or why it can't be built (a secret not set, a bad URL). Nothing is sent, stored or charged. Check that the {{field}} values landed where they should. ${liveCheck}`,
41260
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41261
+ inputSchema: {
41262
+ site: external_exports.string().describe("Site name or id"),
41263
+ action: external_exports.string().describe(`The action's name under "actions" in drivethrough.json`),
41264
+ fields: fieldValues.describe("Sample row values for its {{field}} references; {} when it uses none")
41265
+ }
41266
+ },
41267
+ async ({ site: site2, action, fields }) => {
41268
+ try {
41269
+ return ok2(await testTarget(site2, "action", action, { fields, dry: true }));
41270
+ } catch (error62) {
41271
+ return toolError(error62);
41272
+ }
41273
+ }
41274
+ );
41275
+ server.registerTool(
41276
+ "preview_schedule",
41277
+ {
41278
+ outputSchema: OUTPUT_SCHEMAS.preview_schedule,
41279
+ title: "Preview schedule",
41280
+ description: `Show what a site's declared schedule would send on its next run (Pro plan), with every secret value redacted: for "rotate", the row that comes next and the request its action would send; for "pull", the GET it would make and the type and map its response would fill. Nothing is sent, stored or charged, and the rotation doesn't move. get_integrations shows when it next runs and how its last run went.`,
41281
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41282
+ inputSchema: {
41283
+ site: external_exports.string().describe("Site name or id"),
41284
+ schedule: external_exports.string().describe(`The schedule's name under "schedules" in drivethrough.json`)
41285
+ }
41286
+ },
41287
+ async ({ site: site2, schedule }) => {
41288
+ try {
41289
+ return ok2(await testTarget(site2, "schedule", schedule, { dry: true }));
41290
+ } catch (error62) {
41291
+ return toolError(error62);
41292
+ }
41293
+ }
41294
+ );
41295
+ server.registerTool(
41296
+ "preview_webhook",
41297
+ {
41298
+ outputSchema: OUTPUT_SCHEMAS.preview_webhook,
41299
+ title: "Preview webhook",
41300
+ description: `Dry-run one of a site's declared webhooks (Pro plan): builds an event from sample field values, takes it through the same mapping and validation a real delivery gets, and returns the row it would store and the URL the other service must POST to. Nothing is stored, sent or charged. Real delivery is proven by the other service's own "send test event", then get_integrations (last event).`,
41301
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41302
+ inputSchema: {
41303
+ site: external_exports.string().describe("Site name or id"),
41304
+ webhook: external_exports.string().describe(`The webhook's name under "webhooks" in drivethrough.json (the <name> in /api/hook/<name>)`),
41305
+ fields: fieldValues.describe("Sample values for the fields its map fills")
41306
+ }
41307
+ },
41308
+ async ({ site: site2, webhook, fields }) => {
41309
+ try {
41310
+ return ok2(await testTarget(site2, "webhook", `/api/hook/${webhook.replace(/^\/?api\/hook\//, "")}`, { fields }));
41311
+ } catch (error62) {
41312
+ return toolError(error62);
41313
+ }
41314
+ }
41315
+ );
41316
+ if (!canDeployDirectory) return;
41234
41317
  server.registerTool(
41235
41318
  "test_route",
41236
41319
  {
@@ -41258,7 +41341,7 @@ function registerTools(server, ctx) {
41258
41341
  {
41259
41342
  outputSchema: OUTPUT_SCHEMAS.run_action,
41260
41343
  title: "Run action",
41261
- description: `Run one of a site's declared actions now, for real (Pro plan): it sends the action's POST to the other service with fields standing in for the row that would normally trigger it, and returns that service's HTTP status and a redacted preview of its response. The other service acts on it as it would on any request \u2014 a display shows the text, a message is sent \u2014 so confirm with the user before running anything that can't be taken back. This is the way to trigger an action by hand: write_db, imports and schedule pulls never fire actions.`,
41344
+ description: `Run one of a site's declared actions now, for real (Pro plan): it sends the action's POST to the other service with fields standing in for the row that would normally trigger it, and returns that service's HTTP status and a redacted preview of its response. The other service acts on it as it would on any request \u2014 a display shows the text, a message is sent \u2014 so confirm with the user before running anything that can't be taken back. preview_action shows the same request without sending it.`,
41262
41345
  // Destructive + open-world: the other service may act irreversibly on the request.
41263
41346
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
41264
41347
  inputSchema: {
@@ -41269,7 +41352,7 @@ function registerTools(server, ctx) {
41269
41352
  },
41270
41353
  async ({ site: site2, action, fields }) => {
41271
41354
  try {
41272
- return ok2(await testTarget(site2, "action", action, fields));
41355
+ return ok2(await testTarget(site2, "action", action, { fields }));
41273
41356
  } catch (error62) {
41274
41357
  return toolError(error62);
41275
41358
  }
@@ -41280,7 +41363,7 @@ function registerTools(server, ctx) {
41280
41363
  {
41281
41364
  outputSchema: OUTPUT_SCHEMAS.run_schedule,
41282
41365
  title: "Run schedule",
41283
- description: `Run one of a site's declared schedules once, now, for real (Pro plan), and return the outcome. A "pull" schedule calls its route and saves the response as a new row; a "rotate" schedule sends the next row through its action (the other service acts on it) and advances the rotation. A successful run also re-enables a schedule that was paused after repeated failures. The regular timetable is unchanged.`,
41366
+ description: `Run one of a site's declared schedules once, now, for real (Pro plan), and return the outcome. A "pull" schedule calls its route and saves the response as a new row; a "rotate" schedule sends the next row through its action (the other service acts on it) and advances the rotation. A successful run also re-enables a schedule that was paused after repeated failures. The regular timetable is unchanged. preview_schedule shows the same run without sending anything.`,
41284
41367
  // Destructive + open-world: a rotate run fires an action the other service acts on.
41285
41368
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
41286
41369
  inputSchema: {
@@ -41296,29 +41379,8 @@ function registerTools(server, ctx) {
41296
41379
  }
41297
41380
  }
41298
41381
  );
41299
- server.registerTool(
41300
- "preview_webhook",
41301
- {
41302
- outputSchema: OUTPUT_SCHEMAS.preview_webhook,
41303
- title: "Preview webhook",
41304
- description: `Dry-run one of a site's declared webhooks (Pro plan): builds an event from sample field values, takes it through the same mapping and validation a real delivery gets, and returns the row it would store and the URL the other service must POST to. Nothing is stored, sent or charged. Real delivery is proven by the other service's own "send test event", then get_integrations (last event).`,
41305
- annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41306
- inputSchema: {
41307
- site: external_exports.string().describe("Site name or id"),
41308
- webhook: external_exports.string().describe(`The webhook's name under "webhooks" in drivethrough.json (the <name> in /api/hook/<name>)`),
41309
- fields: fieldValues.describe("Sample values for the fields its map fills")
41310
- }
41311
- },
41312
- async ({ site: site2, webhook, fields }) => {
41313
- try {
41314
- return ok2(await testTarget(site2, "webhook", `/api/hook/${webhook.replace(/^\/?api\/hook\//, "")}`, fields));
41315
- } catch (error62) {
41316
- return toolError(error62);
41317
- }
41318
- }
41319
- );
41320
41382
  }
41321
- var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS, INTEGRATION_EXAMPLE_A, INTEGRATION_EXAMPLE_B;
41383
+ var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS, INTEGRATION_EXAMPLE_A, INTEGRATION_EXAMPLE_B, INTEGRATION_REFERENCE;
41322
41384
  var init_tools = __esm({
41323
41385
  "../mcp-tools/src/tools.ts"() {
41324
41386
  "use strict";
@@ -41399,7 +41461,7 @@ RECIPE \u2014 a searchable, filterable list (catalogue, directory, archive)
41399
41461
  whose query the config doesn't declare (it will render empty for visitors),
41400
41462
  and a declaration no page uses (pure cost on every insert).
41401
41463
 
41402
- Connecting a site to another service's API (Pro plan): this is site configuration, not something you run. The site's drivethrough.json declares it and deploy_site deploys it, like any other file; the format and two complete examples are in get_integrations' description. Every integration operation you can perform is its own tool: get_integrations (what is declared and how it ran), request_secrets (a one-time link where the user sets API keys \u2014 never ask for a key in the chat), test_route, run_action, run_schedule (one real run each) and preview_webhook (a dry run). Check each piece with them before telling the user it works.
41464
+ Connecting a site to another service's API (Pro plan): this is site configuration, not something you run. The site's drivethrough.json declares it and deploy_site deploys it, like any other file; get_integrations {reference: true} returns the format with two complete examples. The integration tools: get_integrations (what is declared and how it went), request_secrets (a one-time link where the user sets API keys \u2014 never ask for a key in the chat), and preview_action, preview_schedule, preview_webhook (show what would be sent or stored; nothing is sent). Preview each piece, then have the user use the page once and confirm the outcome in get_integrations before telling them it works.
41403
41465
 
41404
41466
  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.`;
41405
41467
  INTEGRATION_EXAMPLE_A = JSON.stringify({
@@ -41438,6 +41500,7 @@ Custom domains (paid plans): manage_domain attaches a domain the user already ow
41438
41500
  },
41439
41501
  schedules: { cycle: { every: "3h", rotate: "quote", action: "show" } }
41440
41502
  });
41503
+ INTEGRATION_REFERENCE = `Integrations are declared in the site's drivethrough.json and deployed with deploy_site, like any other file. Keys: "secrets" lists the key names (values are set by the user via request_secrets, never in chat or a file). "routes" maps a same-origin path under /api/ext/<name> to an upstream, allowing only the listed "methods" and "paths" prefixes, adding "headers" server-side (\${NAME} inserts a secret); "access" is "owner" (default), "viewers" or "public"; "cache" and "query" are optional. "actions" declare a POST ("post" URL, "headers", JSON "body" where {{field}} inserts a row's field); a type with "on_write": "<action>" sends it when a visitor's form or the owner's button on the page saves a row \u2014 never for write_db, imports or schedule pulls. "schedules" repeat every 1h/3h/6h/12h/1d/1w/1mo (no cron strings, times of day or timezones): "rotate": "<type>" with "action" sends the next row of the type through the action; "pull": {route, path, into, map} saves a route's JSON response as a new row ("map" uses $.dotted.paths; numeric strings become numbers). They pause after 10 failures in a row (the owner gets one email) until the next deploy. "webhooks" receive events another service POSTs to https://<site>/api/hook/<name>: "verify" is "stripe", "github", "standard" (Standard Webhooks: Resend, Clerk, Svix\u2026), "hmac_sha256" (with "header": the signature header's name) or, only for services that can't sign, "url_token"; "secret" names the declared secret; "map" (same $.paths) fills a row of "into". Unverified, duplicate and replayed deliveries are never stored. Every mistake is refused at deploy time with the corrected snippet \u2014 follow it. Two complete examples \u2014 a read-only brokerage dashboard that also snapshots equity hourly for a chart: ${INTEGRATION_EXAMPLE_A} \u2014 and a display that shows a quote when the owner clicks, and cycles through saved quotes every 3 hours: ${INTEGRATION_EXAMPLE_B}.`;
41441
41504
  }
41442
41505
  });
41443
41506
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drivethrough-cli",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Agent-first hosting CLI. Say what you want. Stay in the car.",
5
5
  "type": "module",
6
6
  "license": "MIT",