drivethrough-cli 0.1.28 → 0.1.30
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 +193 -77
- 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.'
|
|
@@ -23204,8 +23218,8 @@ var init_types = __esm({
|
|
|
23204
23218
|
}
|
|
23205
23219
|
} else if (check2.kind === "regex") {
|
|
23206
23220
|
check2.regex.lastIndex = 0;
|
|
23207
|
-
const
|
|
23208
|
-
if (!
|
|
23221
|
+
const testResult2 = check2.regex.test(input2.data);
|
|
23222
|
+
if (!testResult2) {
|
|
23209
23223
|
ctx = this._getOrReturnCtx(input2, ctx);
|
|
23210
23224
|
addIssueToContext(ctx, {
|
|
23211
23225
|
validation: "regex",
|
|
@@ -39999,7 +40013,7 @@ var init_remote_assets = __esm({
|
|
|
39999
40013
|
});
|
|
40000
40014
|
|
|
40001
40015
|
// ../mcp-tools/src/output-schemas.ts
|
|
40002
|
-
var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, OUTPUT_SCHEMAS;
|
|
40016
|
+
var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, testResult, OUTPUT_SCHEMAS;
|
|
40003
40017
|
var init_output_schemas = __esm({
|
|
40004
40018
|
"../mcp-tools/src/output-schemas.ts"() {
|
|
40005
40019
|
"use strict";
|
|
@@ -40063,6 +40077,17 @@ var init_output_schemas = __esm({
|
|
|
40063
40077
|
diff: external_exports.string().optional().describe("compare only: unified line diff of a changed text file"),
|
|
40064
40078
|
diff_omitted: external_exports.string().optional()
|
|
40065
40079
|
});
|
|
40080
|
+
testResult = external_exports.looseObject({
|
|
40081
|
+
ok,
|
|
40082
|
+
target: external_exports.string().optional(),
|
|
40083
|
+
kind: external_exports.string().optional().describe('"route", "action", "schedule" or "webhook"'),
|
|
40084
|
+
status: external_exports.string().optional().describe('"ok", "failed", "skipped", "plan" or "quota"'),
|
|
40085
|
+
http_status: external_exports.number().nullish().describe("The other service's HTTP status"),
|
|
40086
|
+
duration_ms: external_exports.number().optional(),
|
|
40087
|
+
body_preview: external_exports.string().nullish().describe("First 1 KB of the other service's response, secrets redacted"),
|
|
40088
|
+
error: external_exports.string().nullish(),
|
|
40089
|
+
next: external_exports.string().optional()
|
|
40090
|
+
});
|
|
40066
40091
|
OUTPUT_SCHEMAS = {
|
|
40067
40092
|
deploy_site: external_exports.looseObject({
|
|
40068
40093
|
ok,
|
|
@@ -40223,31 +40248,33 @@ var init_output_schemas = __esm({
|
|
|
40223
40248
|
domain_lost: external_exports.string().nullish().describe("A custom domain that could not be re-attached"),
|
|
40224
40249
|
message: external_exports.string().optional()
|
|
40225
40250
|
}),
|
|
40226
|
-
|
|
40251
|
+
get_integrations: external_exports.looseObject({
|
|
40252
|
+
ok,
|
|
40253
|
+
secrets: external_exports.record(external_exports.string(), external_exports.string()).optional().describe('name \u2192 "set" | "unset" (values are never returned)'),
|
|
40254
|
+
routes: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Declared routes"),
|
|
40255
|
+
actions: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Declared actions, their fields and triggering types"),
|
|
40256
|
+
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
|
+
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"),
|
|
40259
|
+
next: external_exports.string().optional()
|
|
40260
|
+
}).describe("Everything declared plus recent outcomes"),
|
|
40261
|
+
request_secrets: external_exports.looseObject({
|
|
40227
40262
|
ok,
|
|
40228
|
-
|
|
40229
|
-
|
|
40230
|
-
session: external_exports.string().optional().describe(`secrets: pass back with action "secrets" after the user says they're done`),
|
|
40263
|
+
url: external_exports.string().optional().describe("The one-time link to relay to the user (they paste keys there, never in chat)"),
|
|
40264
|
+
session: external_exports.string().optional().describe("Pass back after the user says they're done"),
|
|
40231
40265
|
expires_at: external_exports.string().optional(),
|
|
40232
|
-
names: external_exports.array(external_exports.string()).optional().describe("
|
|
40233
|
-
set: external_exports.record(external_exports.string(), external_exports.boolean()).optional().describe("
|
|
40234
|
-
all_set: external_exports.boolean().optional().describe("
|
|
40235
|
-
// status
|
|
40236
|
-
secrets: external_exports.record(external_exports.string(), external_exports.string()).optional().describe('status: name \u2192 "set" | "unset"'),
|
|
40237
|
-
routes: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared routes"),
|
|
40238
|
-
actions: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared actions, their fields and triggering types"),
|
|
40239
|
-
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"),
|
|
40240
|
-
action_log: external_exports.array(external_exports.unknown()).optional().describe("status: the latest 20 outcomes, newest first"),
|
|
40241
|
-
// test
|
|
40242
|
-
target: external_exports.string().optional(),
|
|
40243
|
-
kind: external_exports.string().optional().describe('test: "route", "action" or "schedule"'),
|
|
40244
|
-
status: external_exports.string().optional().describe('test: "ok", "failed", "skipped", "plan" or "quota"'),
|
|
40245
|
-
http_status: external_exports.number().nullish().describe("test: the upstream's HTTP status"),
|
|
40246
|
-
duration_ms: external_exports.number().optional(),
|
|
40247
|
-
body_preview: external_exports.string().nullish().describe("test: first 1 KB of the upstream response, secrets redacted"),
|
|
40248
|
-
error: external_exports.string().nullish(),
|
|
40266
|
+
names: external_exports.array(external_exports.string()).optional().describe("The names the link asks for"),
|
|
40267
|
+
set: external_exports.record(external_exports.string(), external_exports.boolean()).optional().describe("Every secret name \u2192 whether it has a value. Values are never returned."),
|
|
40268
|
+
all_set: external_exports.boolean().optional().describe("With session: true once every declared secret is set"),
|
|
40249
40269
|
next: external_exports.string().optional()
|
|
40250
|
-
}).describe(
|
|
40270
|
+
}).describe("A link, or (with session) the set/unset map"),
|
|
40271
|
+
test_route: testResult.describe("One GET through the route"),
|
|
40272
|
+
run_action: testResult.describe("One real run of the action"),
|
|
40273
|
+
run_schedule: testResult.describe("One real run of the schedule"),
|
|
40274
|
+
preview_webhook: testResult.extend({
|
|
40275
|
+
url: external_exports.string().optional().describe("The URL the other service POSTs to"),
|
|
40276
|
+
row: external_exports.record(external_exports.string(), external_exports.unknown()).nullish().describe("The row an event like the sample would store (nothing is written)")
|
|
40277
|
+
}).describe("A dry run: the row a delivery like the sample would store")
|
|
40251
40278
|
};
|
|
40252
40279
|
}
|
|
40253
40280
|
});
|
|
@@ -41129,31 +41156,43 @@ function registerTools(server, ctx) {
|
|
|
41129
41156
|
}
|
|
41130
41157
|
}
|
|
41131
41158
|
);
|
|
41159
|
+
const testTarget = (site2, kind, target, fields) => ctx.api.request("POST", `${sitePath(site2)}/integrations/test`, {
|
|
41160
|
+
body: { target, kind, ...fields ? { fields } : {} }
|
|
41161
|
+
});
|
|
41162
|
+
const fieldValues = external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]));
|
|
41132
41163
|
server.registerTool(
|
|
41133
|
-
"
|
|
41164
|
+
"get_integrations",
|
|
41134
41165
|
{
|
|
41135
|
-
outputSchema: OUTPUT_SCHEMAS.
|
|
41136
|
-
title: "
|
|
41137
|
-
description: `
|
|
41138
|
-
|
|
41139
|
-
|
|
41166
|
+
outputSchema: OUTPUT_SCHEMAS.get_integrations,
|
|
41167
|
+
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. HOW INTEGRATIONS ARE SET UP \u2014 no tool does it: declare them 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. Keys are set with request_secrets; each piece is checked with test_route, run_action, run_schedule or preview_webhook. 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, use 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.`,
|
|
41169
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
41170
|
+
inputSchema: {
|
|
41171
|
+
site: external_exports.string().describe("Site name or id")
|
|
41172
|
+
}
|
|
41173
|
+
},
|
|
41174
|
+
async ({ site: site2 }) => {
|
|
41175
|
+
try {
|
|
41176
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/integrations`));
|
|
41177
|
+
} catch (error62) {
|
|
41178
|
+
return toolError(error62);
|
|
41179
|
+
}
|
|
41180
|
+
}
|
|
41181
|
+
);
|
|
41182
|
+
server.registerTool(
|
|
41183
|
+
"request_secrets",
|
|
41184
|
+
{
|
|
41185
|
+
outputSchema: OUTPUT_SCHEMAS.request_secrets,
|
|
41186
|
+
title: "Request secrets",
|
|
41187
|
+
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 },
|
|
41140
41189
|
inputSchema: {
|
|
41141
41190
|
site: external_exports.string().describe("Site name or id"),
|
|
41142
|
-
|
|
41143
|
-
|
|
41144
|
-
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")
|
|
41191
|
+
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."),
|
|
41192
|
+
session: external_exports.string().optional().describe("The session a previous call returned \u2014 confirms what is set now instead of creating a new link")
|
|
41147
41193
|
}
|
|
41148
41194
|
},
|
|
41149
|
-
async ({
|
|
41150
|
-
site: site2,
|
|
41151
|
-
action,
|
|
41152
|
-
names,
|
|
41153
|
-
session,
|
|
41154
|
-
target,
|
|
41155
|
-
fields
|
|
41156
|
-
}) => {
|
|
41195
|
+
async ({ site: site2, names, session }) => {
|
|
41157
41196
|
try {
|
|
41158
41197
|
const setMap = async () => {
|
|
41159
41198
|
const listed = await ctx.api.request(
|
|
@@ -41162,41 +41201,117 @@ function registerTools(server, ctx) {
|
|
|
41162
41201
|
);
|
|
41163
41202
|
return listed.secrets;
|
|
41164
41203
|
};
|
|
41165
|
-
if (
|
|
41166
|
-
|
|
41167
|
-
|
|
41168
|
-
|
|
41169
|
-
|
|
41204
|
+
if (session !== void 0) {
|
|
41205
|
+
const secrets2 = await setMap();
|
|
41206
|
+
const declared = secrets2.filter((s2) => s2.declared);
|
|
41207
|
+
const unset = declared.filter((s2) => !s2.set).map((s2) => s2.name);
|
|
41208
|
+
if (declared.length === 0) {
|
|
41170
41209
|
return ok2({
|
|
41171
41210
|
session,
|
|
41172
41211
|
set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
|
|
41173
|
-
|
|
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."
|
|
41212
|
+
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."
|
|
41175
41213
|
});
|
|
41176
41214
|
}
|
|
41177
|
-
|
|
41178
|
-
|
|
41179
|
-
|
|
41180
|
-
|
|
41181
|
-
|
|
41182
|
-
}
|
|
41183
|
-
if (action === "status") {
|
|
41184
|
-
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/integrations`));
|
|
41185
|
-
}
|
|
41186
|
-
if (!target) {
|
|
41187
|
-
throw new ToolApiError(422, {
|
|
41188
|
-
error: {
|
|
41189
|
-
code: "target_required",
|
|
41190
|
-
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.'
|
|
41192
|
-
}
|
|
41215
|
+
return ok2({
|
|
41216
|
+
session,
|
|
41217
|
+
set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
|
|
41218
|
+
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."
|
|
41193
41220
|
});
|
|
41194
41221
|
}
|
|
41195
|
-
|
|
41196
|
-
|
|
41197
|
-
|
|
41198
|
-
|
|
41199
|
-
);
|
|
41222
|
+
const link = await ctx.api.request("POST", `${sitePath(site2)}/secrets/link`, {
|
|
41223
|
+
body: names ? { names } : {}
|
|
41224
|
+
});
|
|
41225
|
+
const secrets = await setMap();
|
|
41226
|
+
const set2 = Object.fromEntries((link.names ?? []).map((name) => [name, false]));
|
|
41227
|
+
for (const s2 of secrets) set2[s2.name] = s2.set;
|
|
41228
|
+
return ok2({ ...link, set: set2 });
|
|
41229
|
+
} catch (error62) {
|
|
41230
|
+
return toolError(error62);
|
|
41231
|
+
}
|
|
41232
|
+
}
|
|
41233
|
+
);
|
|
41234
|
+
server.registerTool(
|
|
41235
|
+
"test_route",
|
|
41236
|
+
{
|
|
41237
|
+
outputSchema: OUTPUT_SCHEMAS.test_route,
|
|
41238
|
+
title: "Test route",
|
|
41239
|
+
description: `Send one GET through a site's declared API route now, exactly as a page would (Pro plan), and return the other service's HTTP status and the first 1 KB of its response with every secret redacted. Only GET is ever sent, only to a path the route allows, and it counts as one call against the month's integration allowance. Read the preview before telling the user it works \u2014 a 200 with an error body is still a failure.`,
|
|
41240
|
+
// Not read-only: it reaches a third party (some GETs have effects there)
|
|
41241
|
+
// and spends one of the month's metered calls. Open-world for the same reason.
|
|
41242
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
41243
|
+
inputSchema: {
|
|
41244
|
+
site: external_exports.string().describe("Site name or id"),
|
|
41245
|
+
path: external_exports.string().describe('The route key plus a path it allows, with an optional query, e.g. "/api/ext/weather/v1/forecast?city=Oslo"')
|
|
41246
|
+
}
|
|
41247
|
+
},
|
|
41248
|
+
async ({ site: site2, path: path8 }) => {
|
|
41249
|
+
try {
|
|
41250
|
+
return ok2(await testTarget(site2, "route", path8));
|
|
41251
|
+
} catch (error62) {
|
|
41252
|
+
return toolError(error62);
|
|
41253
|
+
}
|
|
41254
|
+
}
|
|
41255
|
+
);
|
|
41256
|
+
server.registerTool(
|
|
41257
|
+
"run_action",
|
|
41258
|
+
{
|
|
41259
|
+
outputSchema: OUTPUT_SCHEMAS.run_action,
|
|
41260
|
+
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.`,
|
|
41262
|
+
// Destructive + open-world: the other service may act irreversibly on the request.
|
|
41263
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
41264
|
+
inputSchema: {
|
|
41265
|
+
site: external_exports.string().describe("Site name or id"),
|
|
41266
|
+
action: external_exports.string().describe(`The action's name under "actions" in drivethrough.json`),
|
|
41267
|
+
fields: fieldValues.describe("The row values its {{field}} references use; {} when it uses none")
|
|
41268
|
+
}
|
|
41269
|
+
},
|
|
41270
|
+
async ({ site: site2, action, fields }) => {
|
|
41271
|
+
try {
|
|
41272
|
+
return ok2(await testTarget(site2, "action", action, fields));
|
|
41273
|
+
} catch (error62) {
|
|
41274
|
+
return toolError(error62);
|
|
41275
|
+
}
|
|
41276
|
+
}
|
|
41277
|
+
);
|
|
41278
|
+
server.registerTool(
|
|
41279
|
+
"run_schedule",
|
|
41280
|
+
{
|
|
41281
|
+
outputSchema: OUTPUT_SCHEMAS.run_schedule,
|
|
41282
|
+
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.`,
|
|
41284
|
+
// Destructive + open-world: a rotate run fires an action the other service acts on.
|
|
41285
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
41286
|
+
inputSchema: {
|
|
41287
|
+
site: external_exports.string().describe("Site name or id"),
|
|
41288
|
+
schedule: external_exports.string().describe(`The schedule's name under "schedules" in drivethrough.json`)
|
|
41289
|
+
}
|
|
41290
|
+
},
|
|
41291
|
+
async ({ site: site2, schedule }) => {
|
|
41292
|
+
try {
|
|
41293
|
+
return ok2(await testTarget(site2, "schedule", schedule));
|
|
41294
|
+
} catch (error62) {
|
|
41295
|
+
return toolError(error62);
|
|
41296
|
+
}
|
|
41297
|
+
}
|
|
41298
|
+
);
|
|
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));
|
|
41200
41315
|
} catch (error62) {
|
|
41201
41316
|
return toolError(error62);
|
|
41202
41317
|
}
|
|
@@ -41286,11 +41401,12 @@ RECIPE \u2014 a searchable, filterable list (catalogue, directory, archive)
|
|
|
41286
41401
|
|
|
41287
41402
|
RECIPE \u2014 call another service's API (stock prices, a smart display, a CRM, weather; Pro plan):
|
|
41288
41403
|
No server code. Declare it in drivethrough.json and the platform enforces it:
|
|
41289
|
-
- "secrets": names of the keys the site needs, like ["WEATHER_KEY"]. Values are NEVER typed into the chat \u2014
|
|
41404
|
+
- "secrets": names of the keys the site needs, like ["WEATHER_KEY"]. Values are NEVER typed into the chat \u2014 request_secrets gives you a one-time link to send the user, who pastes the keys there.
|
|
41290
41405
|
- "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
41406
|
- "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
41407
|
- "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).
|
|
41293
|
-
|
|
41408
|
+
- "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.
|
|
41409
|
+
Deploy, relay the secrets link if the deploy asks for one, then check each piece before telling the user it works: test_route (one GET), run_action and run_schedule (real runs \u2014 the other service acts on them), preview_webhook (a dry run). get_integrations shows what is set and how it ran; full examples are in its description.
|
|
41294
41410
|
|
|
41295
41411
|
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.`;
|
|
41296
41412
|
INTEGRATION_EXAMPLE_A = JSON.stringify({
|