drivethrough-cli 0.1.28 → 0.1.29

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 +39 -13
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -21659,7 +21659,7 @@ var init_names = __esm({
21659
21659
  });
21660
21660
 
21661
21661
  // ../schema/src/integrations.ts
21662
- var SECRET_NAME, ROUTE_METHODS, ROUTE_CACHE_SECONDS, SCHEDULE_EVERY, MAX_SECRETS, Headers2, RouteConfig, ActionConfig, PullConfig, ScheduleConfig, SecretsList, RoutesConfig, ActionsConfig, SchedulesConfig, HOUR, FIXED;
21662
+ var SECRET_NAME, ROUTE_METHODS, ROUTE_CACHE_SECONDS, SCHEDULE_EVERY, MAX_SECRETS, WEBHOOK_VERIFY, Headers2, RouteConfig, ActionConfig, PullConfig, ScheduleConfig, WebhookConfig, SecretsList, RoutesConfig, ActionsConfig, SchedulesConfig, WebhooksConfig, HOUR, FIXED;
21663
21663
  var init_integrations = __esm({
21664
21664
  "../schema/src/integrations.ts"() {
21665
21665
  "use strict";
@@ -21669,6 +21669,7 @@ var init_integrations = __esm({
21669
21669
  ROUTE_CACHE_SECONDS = { "0": 0, "30s": 30, "1m": 60, "5m": 300, "15m": 900, "1h": 3600 };
21670
21670
  SCHEDULE_EVERY = ["1h", "3h", "6h", "12h", "1d", "1w", "1mo"];
21671
21671
  MAX_SECRETS = 20;
21672
+ WEBHOOK_VERIFY = ["stripe", "github", "standard", "hmac_sha256", "url_token"];
21672
21673
  Headers2 = external_exports.record(external_exports.string(), external_exports.string().max(4096));
21673
21674
  RouteConfig = external_exports.object({
21674
21675
  /** Literal https URL: host plus an optional base path. */
@@ -21703,10 +21704,22 @@ var init_integrations = __esm({
21703
21704
  rotate: external_exports.string().optional(),
21704
21705
  pull: PullConfig.optional()
21705
21706
  }).strict();
21707
+ WebhookConfig = external_exports.object({
21708
+ verify: external_exports.enum(WEBHOOK_VERIFY, {
21709
+ error: 'verify must be one of "stripe", "github", "standard", "hmac_sha256", "url_token". There is no unverified webhook.'
21710
+ }),
21711
+ /** The declared secret holding the signing secret (or the URL token). */
21712
+ secret: external_exports.string().min(1),
21713
+ /** hmac_sha256 only: the header carrying the signature. */
21714
+ header: external_exports.string().optional(),
21715
+ into: external_exports.string().min(1),
21716
+ map: external_exports.record(external_exports.string(), external_exports.string())
21717
+ }).strict();
21706
21718
  SecretsList = external_exports.array(external_exports.string().regex(SECRET_NAME, "secret names are UPPER_CASE letters, numbers and underscores, like ALPACA_KEY")).max(MAX_SECRETS);
21707
21719
  RoutesConfig = external_exports.record(external_exports.string(), RouteConfig);
21708
21720
  ActionsConfig = external_exports.record(external_exports.string(), ActionConfig);
21709
21721
  SchedulesConfig = external_exports.record(external_exports.string(), ScheduleConfig);
21722
+ WebhooksConfig = external_exports.record(external_exports.string(), WebhookConfig);
21710
21723
  HOUR = 36e5;
21711
21724
  FIXED = {
21712
21725
  "1h": HOUR,
@@ -21798,13 +21811,14 @@ var init_site_config = __esm({
21798
21811
  /** Pro (§3): additive custom-schema SQL applied to the site database. */
21799
21812
  schema: external_exports.string().max(2e4).optional(),
21800
21813
  /**
21801
- * Declared integrations (Pro) — see integrations.ts. All four are optional
21814
+ * Declared integrations (Pro) — see integrations.ts. All five are optional
21802
21815
  * and additive: a bundle without them means exactly what it always did.
21803
21816
  */
21804
21817
  secrets: SecretsList.optional(),
21805
21818
  routes: RoutesConfig.optional(),
21806
21819
  actions: ActionsConfig.optional(),
21807
- schedules: SchedulesConfig.optional()
21820
+ schedules: SchedulesConfig.optional(),
21821
+ webhooks: WebhooksConfig.optional()
21808
21822
  }).strict();
21809
21823
  REMOVED_KEYS = {
21810
21824
  indexes: 'The "indexes" key was replaced by "query" \u2014 see query.<type>.fields.'
@@ -40226,7 +40240,7 @@ var init_output_schemas = __esm({
40226
40240
  manage_integrations: external_exports.looseObject({
40227
40241
  ok,
40228
40242
  // secrets
40229
- url: external_exports.string().optional().describe("secrets: the one-time link to relay to the user (they paste keys there, never in chat)"),
40243
+ url: external_exports.string().optional().describe("secrets: the one-time link to relay to the user (they paste keys there, never in chat); test of a webhook: the URL the other service POSTs to"),
40230
40244
  session: external_exports.string().optional().describe(`secrets: pass back with action "secrets" after the user says they're done`),
40231
40245
  expires_at: external_exports.string().optional(),
40232
40246
  names: external_exports.array(external_exports.string()).optional().describe("secrets: the names the link asks for"),
@@ -40237,17 +40251,19 @@ var init_output_schemas = __esm({
40237
40251
  routes: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared routes"),
40238
40252
  actions: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared actions, their fields and triggering types"),
40239
40253
  schedules: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: each schedule's enabled, next_at, last_run_at, last_status, failures, disabled_reason"),
40254
+ webhooks: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: each webhook's url (what the other service POSTs to), verify, secret_set, last_event_at, last_status and setup steps"),
40240
40255
  action_log: external_exports.array(external_exports.unknown()).optional().describe("status: the latest 20 outcomes, newest first"),
40241
40256
  // test
40242
40257
  target: external_exports.string().optional(),
40243
- kind: external_exports.string().optional().describe('test: "route", "action" or "schedule"'),
40258
+ kind: external_exports.string().optional().describe('test: "route", "action", "schedule" or "webhook"'),
40244
40259
  status: external_exports.string().optional().describe('test: "ok", "failed", "skipped", "plan" or "quota"'),
40245
40260
  http_status: external_exports.number().nullish().describe("test: the upstream's HTTP status"),
40246
40261
  duration_ms: external_exports.number().optional(),
40247
40262
  body_preview: external_exports.string().nullish().describe("test: first 1 KB of the upstream response, secrets redacted"),
40248
40263
  error: external_exports.string().nullish(),
40264
+ row: external_exports.record(external_exports.string(), external_exports.unknown()).nullish().describe("test, webhook targets: the row an event like the sample would store (nothing is written)"),
40249
40265
  next: external_exports.string().optional()
40250
- }).describe('"secrets" \u2192 a link or the set/unset map; "status" \u2192 everything declared plus recent outcomes; "test" \u2192 one live run')
40266
+ }).describe('"secrets" \u2192 a link or the set/unset map; "status" \u2192 everything declared plus recent outcomes; "test" \u2192 one live run, or a webhook dry run')
40251
40267
  };
40252
40268
  }
40253
40269
  });
@@ -41134,16 +41150,16 @@ function registerTools(server, ctx) {
41134
41150
  {
41135
41151
  outputSchema: OUTPUT_SCHEMAS.manage_integrations,
41136
41152
  title: "Manage integrations",
41137
- description: `Connect a site to another service's API \u2014 get its keys set, check what's running, and test it \u2014 with no server code (Pro plan). (1) THE SETUP NEEDS NO TOOL: declare it in drivethrough.json and deploy. "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). 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. (2) SECRETS, TWO STEPS, like create_upload: action "secrets" returns a one-time link (10 minutes, no sign-in) \u2014 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. When they say they're done, call again with the returned session to confirm every name is set. A deploy that needs an unset secret is refused with a fresh link already in the error \u2014 relay that one. Pass names to rotate a key that is already set. (3) TEST BEFORE YOU SAY IT WORKS: action "test" with target = a route plus path ("/api/ext/alpaca/v2/account"), an action name (with fields: the row values it uses), or a schedule name runs it once, now, for real (a schedule test inserts its row or advances its rotation) and returns the upstream's status and a redacted preview. Read the preview \u2014 a 200 with an error body is still a failure. action "status" shows secrets set/unset, routes, schedules (next run, last result, failures) and the latest outcomes. (4) WHAT FIRES AN ACTION: only a row written through the page-facing POST /api/data (a visitor's form, or the owner's own button on the page). write_db, imports and schedule pulls do NOT fire actions \u2014 to trigger one by hand, test it. 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 test re-enables them.`,
41153
+ description: `Connect a site to another service's API \u2014 get its keys set, check what's running, and test it \u2014 with no server code (Pro plan). (1) THE SETUP NEEDS NO TOOL: declare it in drivethrough.json and deploy. "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. (2) SECRETS, TWO STEPS, like create_upload: action "secrets" returns a one-time link (10 minutes, no sign-in) \u2014 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. When they say they're done, call again with the returned session to confirm every name is set. A deploy that needs an unset secret is refused with a fresh link already in the error \u2014 relay that one. 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 (status 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. (3) TEST BEFORE YOU SAY IT WORKS: action "test" with target = a route plus path ("/api/ext/alpaca/v2/account"), an action name (with fields: the row values it uses), or a schedule name runs it once, now, for real (a schedule test inserts its row or advances its rotation) and returns the upstream's status and a redacted preview. Read the preview \u2014 a 200 with an error body is still a failure. A webhook target ("/api/hook/payments", with fields: sample values) is a dry run that shows the row such an event would store; real delivery is proven by the other service's own "send test event" and then status. action "status" shows secrets set/unset, routes, schedules (next run, last result, failures), webhooks (URL, last event) and the latest outcomes. (4) WHAT FIRES AN ACTION: only a row written through the page-facing POST /api/data (a visitor's form, or the owner's own button on the page). write_db, imports and schedule pulls do NOT fire actions \u2014 to trigger one by hand, test it. 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 test re-enables them.`,
41138
41154
  // Open-world: tests call a third-party API on the user's behalf.
41139
41155
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
41140
41156
  inputSchema: {
41141
41157
  site: external_exports.string().describe("Site name or id"),
41142
- action: external_exports.enum(["secrets", "status", "test"]).describe('"secrets": get a link for the user to set keys (or, with session, confirm they are set); "status": what is declared and how it ran; "test": run one route, action or schedule now'),
41158
+ action: external_exports.enum(["secrets", "status", "test"]).describe('"secrets": get a link for the user to set keys (or, with session, confirm they are set); "status": what is declared and how it ran; "test": run one route, action or schedule now, or dry-run a webhook'),
41143
41159
  names: external_exports.array(external_exports.string()).optional().describe("secrets: which secret names the link asks for. Default: every declared secret that is still unset. Include a set one to replace (rotate) it."),
41144
41160
  session: external_exports.string().optional().describe("secrets: the session a previous call returned \u2014 confirms what is set now instead of minting a new link"),
41145
- target: external_exports.string().optional().describe('test: a route key plus path ("/api/ext/weather/v1/forecast?city=Oslo"), an action name, or a schedule name'),
41146
- fields: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional().describe("test, action targets only: the row values the action's {{field}} references use")
41161
+ target: external_exports.string().optional().describe('test: a route key plus path ("/api/ext/weather/v1/forecast?city=Oslo"), an action name, a schedule name, or a webhook path ("/api/hook/payments")'),
41162
+ fields: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional().describe("test, action and webhook targets only: for an action, the row values its {{field}} references use; for a webhook, sample values for the fields its map fills")
41147
41163
  }
41148
41164
  },
41149
41165
  async ({
@@ -41167,18 +41183,27 @@ function registerTools(server, ctx) {
41167
41183
  const secrets2 = await setMap();
41168
41184
  const declared = secrets2.filter((s2) => s2.declared);
41169
41185
  const unset = declared.filter((s2) => !s2.set).map((s2) => s2.name);
41186
+ if (declared.length === 0) {
41187
+ return ok2({
41188
+ session,
41189
+ set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
41190
+ next: "This site has no deployed secrets list yet, so deploy again now \u2014 the deploy checks every secret it needs and answers with a fresh link if one is still missing."
41191
+ });
41192
+ }
41170
41193
  return ok2({
41171
41194
  session,
41172
41195
  set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
41173
41196
  all_set: unset.length === 0,
41174
- 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 this tool with action "secrets" again for a fresh one.` : "Every declared secret is set. Deploy (or redeploy) the site, then test each route, action and schedule."
41197
+ 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 this tool with action "secrets" again for a fresh one.` : "Every declared secret is set. Deploy (or redeploy) the site, then test each route, action, schedule and webhook."
41175
41198
  });
41176
41199
  }
41177
41200
  const link = await ctx.api.request("POST", `${sitePath(site2)}/secrets/link`, {
41178
41201
  body: names ? { names } : {}
41179
41202
  });
41180
41203
  const secrets = await setMap();
41181
- return ok2({ ...link, set: Object.fromEntries(secrets.map((s2) => [s2.name, s2.set])) });
41204
+ const set2 = Object.fromEntries((link.names ?? []).map((name) => [name, false]));
41205
+ for (const s2 of secrets) set2[s2.name] = s2.set;
41206
+ return ok2({ ...link, set: set2 });
41182
41207
  }
41183
41208
  if (action === "status") {
41184
41209
  return ok2(await ctx.api.request("GET", `${sitePath(site2)}/integrations`));
@@ -41188,7 +41213,7 @@ function registerTools(server, ctx) {
41188
41213
  error: {
41189
41214
  code: "target_required",
41190
41215
  message: 'action "test" needs a target.',
41191
- next: 'Pass target: a route plus path ("/api/ext/name/path"), an action name, or a schedule name. action "status" lists them.'
41216
+ next: 'Pass target: a route plus path ("/api/ext/name/path"), an action name, a schedule name, or a webhook path ("/api/hook/name"). action "status" lists them.'
41192
41217
  }
41193
41218
  });
41194
41219
  }
@@ -41290,6 +41315,7 @@ No server code. Declare it in drivethrough.json and the platform enforces it:
41290
41315
  - "routes": {"/api/ext/weather": {"upstream": "https://api.example.com", "methods": ["GET"], "paths": ["/v1/forecast"], "headers": {"Authorization": "Bearer \${WEATHER_KEY}"}, "access": "public", "cache": "5m", "query": true}} \u2192 the page fetches /api/ext/weather/v1/forecast?city=\u2026 same-origin; the key is added server-side and only the listed methods and path prefixes go through.
41291
41316
  - "actions": {"notify": {"post": "https://\u2026", "headers": {\u2026}, "body": {"text": "{{message}}"}}} plus "on_write": "notify" on a type \u2192 each row a page POSTs to /api/data fires the call. {{field}} reads the row; \${NAME} reads a secret.
41292
41317
  - "schedules": {"name": {"every": "1h"|"3h"|"6h"|"12h"|"1d"|"1w"|"1mo", "rotate": "<type>", "action": "<action>"}} cycles rows through an action; {"every": \u2026, "pull": {"route", "path", "into": "<type>", "map": {"field": "$.json.path"}}} saves a route's JSON as rows (for charts and history).
41318
+ - "webhooks": {"payments": {"verify": "stripe", "secret": "STRIPE_WEBHOOK_SECRET", "into": "payment", "map": {"amount": "$.data.object.amount"}}} \u2192 the other service POSTs events to https://<site>/api/hook/payments; each is verified ("stripe", "github", "standard", "hmac_sha256" + "header", or "url_token" for services that can't sign) and stored as a row, which fires that type's on_write. Give the event type "read": "owner" unless visitors should see it.
41293
41319
  Deploy, relay the secrets link if the deploy asks for one, then manage_integrations {action: "test"} each piece before telling the user it works. Full examples are in manage_integrations' description.
41294
41320
 
41295
41321
  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.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drivethrough-cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Agent-first hosting CLI. Say what you want. Stay in the car.",
5
5
  "type": "module",
6
6
  "license": "MIT",