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.
- package/dist/main.mjs +39 -13
- 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
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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.`;
|