drivethrough-cli 0.1.27 → 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 +706 -74
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -21658,6 +21658,80 @@ var init_names = __esm({
|
|
|
21658
21658
|
}
|
|
21659
21659
|
});
|
|
21660
21660
|
|
|
21661
|
+
// ../schema/src/integrations.ts
|
|
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
|
+
var init_integrations = __esm({
|
|
21664
|
+
"../schema/src/integrations.ts"() {
|
|
21665
|
+
"use strict";
|
|
21666
|
+
init_zod();
|
|
21667
|
+
SECRET_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
21668
|
+
ROUTE_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
21669
|
+
ROUTE_CACHE_SECONDS = { "0": 0, "30s": 30, "1m": 60, "5m": 300, "15m": 900, "1h": 3600 };
|
|
21670
|
+
SCHEDULE_EVERY = ["1h", "3h", "6h", "12h", "1d", "1w", "1mo"];
|
|
21671
|
+
MAX_SECRETS = 20;
|
|
21672
|
+
WEBHOOK_VERIFY = ["stripe", "github", "standard", "hmac_sha256", "url_token"];
|
|
21673
|
+
Headers2 = external_exports.record(external_exports.string(), external_exports.string().max(4096));
|
|
21674
|
+
RouteConfig = external_exports.object({
|
|
21675
|
+
/** Literal https URL: host plus an optional base path. */
|
|
21676
|
+
upstream: external_exports.string().min(1).max(2048),
|
|
21677
|
+
methods: external_exports.array(external_exports.enum(ROUTE_METHODS)).min(1, "methods must list at least one of GET, POST, PUT, PATCH, DELETE"),
|
|
21678
|
+
paths: external_exports.array(external_exports.string().min(1).max(512)).min(1, 'paths must list at least one path prefix, like "/v2/account"').max(50),
|
|
21679
|
+
headers: Headers2.optional(),
|
|
21680
|
+
access: external_exports.enum(["owner", "viewers", "public"]).optional(),
|
|
21681
|
+
cache: external_exports.enum(Object.keys(ROUTE_CACHE_SECONDS), {
|
|
21682
|
+
error: 'cache must be one of "0", "30s", "1m", "5m", "15m", "1h"'
|
|
21683
|
+
}).optional(),
|
|
21684
|
+
query: external_exports.boolean().optional()
|
|
21685
|
+
}).strict();
|
|
21686
|
+
ActionConfig = external_exports.object({
|
|
21687
|
+
/** https URL the action POSTs to. `{{field}}` allowed in the path only. */
|
|
21688
|
+
post: external_exports.string().min(1).max(2048),
|
|
21689
|
+
headers: Headers2.optional(),
|
|
21690
|
+
/** A JSON object template: `${NAME}` and `{{field}}` substitute inside string values. */
|
|
21691
|
+
body: external_exports.record(external_exports.string(), external_exports.unknown())
|
|
21692
|
+
}).strict();
|
|
21693
|
+
PullConfig = external_exports.object({
|
|
21694
|
+
route: external_exports.string().min(1),
|
|
21695
|
+
path: external_exports.string().max(512).optional(),
|
|
21696
|
+
into: external_exports.string().min(1),
|
|
21697
|
+
map: external_exports.record(external_exports.string(), external_exports.string())
|
|
21698
|
+
}).strict();
|
|
21699
|
+
ScheduleConfig = external_exports.object({
|
|
21700
|
+
every: external_exports.enum(SCHEDULE_EVERY, {
|
|
21701
|
+
error: 'every must be one of "1h", "3h", "6h", "12h", "1d", "1w", "1mo". There is no time-of-day setting and no timezone: a schedule starts from the deploy that first declares it.'
|
|
21702
|
+
}),
|
|
21703
|
+
action: external_exports.string().optional(),
|
|
21704
|
+
rotate: external_exports.string().optional(),
|
|
21705
|
+
pull: PullConfig.optional()
|
|
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();
|
|
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);
|
|
21719
|
+
RoutesConfig = external_exports.record(external_exports.string(), RouteConfig);
|
|
21720
|
+
ActionsConfig = external_exports.record(external_exports.string(), ActionConfig);
|
|
21721
|
+
SchedulesConfig = external_exports.record(external_exports.string(), ScheduleConfig);
|
|
21722
|
+
WebhooksConfig = external_exports.record(external_exports.string(), WebhookConfig);
|
|
21723
|
+
HOUR = 36e5;
|
|
21724
|
+
FIXED = {
|
|
21725
|
+
"1h": HOUR,
|
|
21726
|
+
"3h": 3 * HOUR,
|
|
21727
|
+
"6h": 6 * HOUR,
|
|
21728
|
+
"12h": 12 * HOUR,
|
|
21729
|
+
"1d": 24 * HOUR,
|
|
21730
|
+
"1w": 7 * 24 * HOUR
|
|
21731
|
+
};
|
|
21732
|
+
}
|
|
21733
|
+
});
|
|
21734
|
+
|
|
21661
21735
|
// ../schema/src/site-config.ts
|
|
21662
21736
|
function parseSiteConfig(input2) {
|
|
21663
21737
|
const stripped = stripRemovedKeys(input2);
|
|
@@ -21686,6 +21760,7 @@ var init_site_config = __esm({
|
|
|
21686
21760
|
"use strict";
|
|
21687
21761
|
init_zod();
|
|
21688
21762
|
init_query();
|
|
21763
|
+
init_integrations();
|
|
21689
21764
|
FieldSpec = external_exports.enum(["string", "string?", "number", "number?", "boolean", "boolean?"]);
|
|
21690
21765
|
RateLimit = external_exports.string().regex(/^\d+\/(minute|hour|day)$/, 'rate_limit must look like "5/hour"');
|
|
21691
21766
|
TYPE_NAME = external_exports.string().regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/, "type names are lowercase letters, numbers, hyphens and underscores").max(64);
|
|
@@ -21709,7 +21784,13 @@ var init_site_config = __esm({
|
|
|
21709
21784
|
notify: external_exports.enum(["owner-email"]).optional(),
|
|
21710
21785
|
/** Constrains tree shape: a comment may only hang off a page, etc. */
|
|
21711
21786
|
parent_types: external_exports.array(external_exports.string().min(1)).optional(),
|
|
21712
|
-
rate_limit: RateLimit.optional()
|
|
21787
|
+
rate_limit: RateLimit.optional(),
|
|
21788
|
+
/**
|
|
21789
|
+
* An action (declared under "actions") to fire after a row of this type is
|
|
21790
|
+
* inserted or upserted through the page-facing Data API. Owner-API writes,
|
|
21791
|
+
* imports and schedule pulls never fire it.
|
|
21792
|
+
*/
|
|
21793
|
+
on_write: external_exports.string().optional()
|
|
21713
21794
|
}).strict();
|
|
21714
21795
|
SiteConfig = external_exports.object({
|
|
21715
21796
|
/** Sitename: lowercase alphanumeric + hyphens, unique per user. */
|
|
@@ -21728,7 +21809,16 @@ var init_site_config = __esm({
|
|
|
21728
21809
|
*/
|
|
21729
21810
|
query: QueryConfig.default({}),
|
|
21730
21811
|
/** Pro (§3): additive custom-schema SQL applied to the site database. */
|
|
21731
|
-
schema: external_exports.string().max(2e4).optional()
|
|
21812
|
+
schema: external_exports.string().max(2e4).optional(),
|
|
21813
|
+
/**
|
|
21814
|
+
* Declared integrations (Pro) — see integrations.ts. All five are optional
|
|
21815
|
+
* and additive: a bundle without them means exactly what it always did.
|
|
21816
|
+
*/
|
|
21817
|
+
secrets: SecretsList.optional(),
|
|
21818
|
+
routes: RoutesConfig.optional(),
|
|
21819
|
+
actions: ActionsConfig.optional(),
|
|
21820
|
+
schedules: SchedulesConfig.optional(),
|
|
21821
|
+
webhooks: WebhooksConfig.optional()
|
|
21732
21822
|
}).strict();
|
|
21733
21823
|
REMOVED_KEYS = {
|
|
21734
21824
|
indexes: 'The "indexes" key was replaced by "query" \u2014 see query.<type>.fields.'
|
|
@@ -21752,6 +21842,7 @@ var init_src = __esm({
|
|
|
21752
21842
|
init_names();
|
|
21753
21843
|
init_site_config();
|
|
21754
21844
|
init_bundle();
|
|
21845
|
+
init_integrations();
|
|
21755
21846
|
}
|
|
21756
21847
|
});
|
|
21757
21848
|
|
|
@@ -39638,7 +39729,7 @@ var init_inline_files = __esm({
|
|
|
39638
39729
|
}
|
|
39639
39730
|
});
|
|
39640
39731
|
|
|
39641
|
-
// ../
|
|
39732
|
+
// ../shared/src/outbound.ts
|
|
39642
39733
|
function isPrivateIpv4(host) {
|
|
39643
39734
|
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
39644
39735
|
if (!match) return false;
|
|
@@ -39667,7 +39758,7 @@ async function resolveViaDoh(hostname3) {
|
|
|
39667
39758
|
for (const type of ["A", "AAAA"]) {
|
|
39668
39759
|
const res = await fetch(
|
|
39669
39760
|
`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(hostname3)}&type=${type}`,
|
|
39670
|
-
{ headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(
|
|
39761
|
+
{ headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(DNS_TIMEOUT_MS) }
|
|
39671
39762
|
);
|
|
39672
39763
|
if (!res.ok) throw new Error(`DNS lookup returned HTTP ${res.status}`);
|
|
39673
39764
|
const body = await res.json();
|
|
@@ -39691,6 +39782,20 @@ async function resolutionProblem(hostname3) {
|
|
|
39691
39782
|
if (blocked) return `it resolves to a private or reserved address (${blocked})`;
|
|
39692
39783
|
return null;
|
|
39693
39784
|
}
|
|
39785
|
+
var BLOCKED_HOST_SUFFIXES, DNS_TIMEOUT_MS, resolveAddresses, IP_LITERAL, UPSTREAM_MAX_BODY, UPSTREAM_MAX_RESPONSE;
|
|
39786
|
+
var init_outbound = __esm({
|
|
39787
|
+
"../shared/src/outbound.ts"() {
|
|
39788
|
+
"use strict";
|
|
39789
|
+
BLOCKED_HOST_SUFFIXES = [".localhost", ".local", ".internal", ".home.arpa"];
|
|
39790
|
+
DNS_TIMEOUT_MS = 2e4;
|
|
39791
|
+
resolveAddresses = resolveViaDoh;
|
|
39792
|
+
IP_LITERAL = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
39793
|
+
UPSTREAM_MAX_BODY = 64 * 1024;
|
|
39794
|
+
UPSTREAM_MAX_RESPONSE = 1024 * 1024;
|
|
39795
|
+
}
|
|
39796
|
+
});
|
|
39797
|
+
|
|
39798
|
+
// ../mcp-tools/src/remote-assets.ts
|
|
39694
39799
|
function urlProblem(url2, why) {
|
|
39695
39800
|
return {
|
|
39696
39801
|
code: "invalid_source_url",
|
|
@@ -39893,24 +39998,280 @@ async function resolveDeployFiles(files, opts = {}) {
|
|
|
39893
39998
|
}
|
|
39894
39999
|
return { ok: true, files: out };
|
|
39895
40000
|
}
|
|
39896
|
-
var MAX_REMOTE_FILES, MAX_REMOTE_FILE_MB, MAX_BUNDLE_TOTAL_MB, MAX_REDIRECTS, FETCH_TIMEOUT_MS
|
|
40001
|
+
var MAX_REMOTE_FILES, MAX_REMOTE_FILE_MB, MAX_BUNDLE_TOTAL_MB, MAX_REDIRECTS, FETCH_TIMEOUT_MS;
|
|
39897
40002
|
var init_remote_assets = __esm({
|
|
39898
40003
|
"../mcp-tools/src/remote-assets.ts"() {
|
|
39899
40004
|
"use strict";
|
|
40005
|
+
init_outbound();
|
|
40006
|
+
init_outbound();
|
|
39900
40007
|
MAX_REMOTE_FILES = 20;
|
|
39901
40008
|
MAX_REMOTE_FILE_MB = 10;
|
|
39902
40009
|
MAX_BUNDLE_TOTAL_MB = 50;
|
|
39903
40010
|
MAX_REDIRECTS = 3;
|
|
39904
40011
|
FETCH_TIMEOUT_MS = 2e4;
|
|
39905
|
-
|
|
39906
|
-
|
|
39907
|
-
|
|
40012
|
+
}
|
|
40013
|
+
});
|
|
40014
|
+
|
|
40015
|
+
// ../mcp-tools/src/output-schemas.ts
|
|
40016
|
+
var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, OUTPUT_SCHEMAS;
|
|
40017
|
+
var init_output_schemas = __esm({
|
|
40018
|
+
"../mcp-tools/src/output-schemas.ts"() {
|
|
40019
|
+
"use strict";
|
|
40020
|
+
init_zod();
|
|
40021
|
+
ok = external_exports.literal(true).describe("Always true on success; failures come back as an error result instead");
|
|
40022
|
+
site = external_exports.looseObject({
|
|
40023
|
+
id: external_exports.string().optional().describe("Stable site id \u2014 accepted anywhere a site name is"),
|
|
40024
|
+
sitename: external_exports.string().optional().describe("The site's name within the account"),
|
|
40025
|
+
hostname: external_exports.string().optional().describe("DNS label on the platform domain ({username}-{sitename})"),
|
|
40026
|
+
title: external_exports.string().nullish().describe("Human title shown to guests"),
|
|
40027
|
+
visibility: external_exports.string().optional().describe('"public" or "private"'),
|
|
40028
|
+
status: external_exports.string().optional().describe('"active", "suspended" or "deleted"'),
|
|
40029
|
+
custom_domain: external_exports.string().nullish().describe("Attached custom domain, if any"),
|
|
40030
|
+
domain_status: external_exports.string().optional().describe("Custom-domain verification state, when one is attached"),
|
|
40031
|
+
current_version: external_exports.number().optional().describe("The live version number (0 = never deployed)"),
|
|
40032
|
+
url: external_exports.string().optional().describe("Public address of the site \u2014 give this to the user"),
|
|
40033
|
+
local_url: external_exports.string().optional().describe("Local-dev address of the site"),
|
|
40034
|
+
created_at: external_exports.string().optional(),
|
|
40035
|
+
deleted_at: external_exports.string().nullish().describe("Deleted sites only"),
|
|
40036
|
+
purges_after: external_exports.string().nullish().describe("Deleted sites only: when it is destroyed for good")
|
|
40037
|
+
}).describe("The site");
|
|
40038
|
+
deployResult = external_exports.looseObject({
|
|
40039
|
+
site_id: external_exports.string().optional(),
|
|
40040
|
+
hostname: external_exports.string().optional(),
|
|
40041
|
+
version: external_exports.number().describe("The new version number \u2014 a restore point for revert_site, and the base_version for the next deploy"),
|
|
40042
|
+
files: external_exports.number().optional().describe("Files in the deployed version"),
|
|
40043
|
+
total_bytes: external_exports.number().optional(),
|
|
40044
|
+
bundle_hash: external_exports.string().optional().describe("Content hash of the deployed bundle"),
|
|
40045
|
+
url: external_exports.string().optional().describe("Where the site is served"),
|
|
40046
|
+
local_url: external_exports.string().optional(),
|
|
40047
|
+
scan: external_exports.looseObject({
|
|
40048
|
+
score: external_exports.number().optional(),
|
|
40049
|
+
verdict: external_exports.string().optional().describe('"clean", or "suspended" when the abuse scan took the site down')
|
|
40050
|
+
}).optional(),
|
|
40051
|
+
query_plan: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Per data type: the indexes the query block built and what each insert now costs in billed writes"),
|
|
40052
|
+
warnings: external_exports.array(external_exports.string()).optional().describe("Runtime problems only visitors would hit (missing Turnstile, unreadable fetched types\u2026). Resolve before reporting success")
|
|
40053
|
+
});
|
|
40054
|
+
dataDoc = external_exports.looseObject({
|
|
40055
|
+
seq: external_exports.number().describe("Row number \u2014 the row's permanent identity"),
|
|
40056
|
+
type: external_exports.string(),
|
|
40057
|
+
parent_seq: external_exports.number().nullish(),
|
|
40058
|
+
json: external_exports.record(external_exports.string(), external_exports.unknown()).describe("The row's document"),
|
|
40059
|
+
created_at: external_exports.string().optional(),
|
|
40060
|
+
updated_at: external_exports.string().optional()
|
|
40061
|
+
});
|
|
40062
|
+
dnsRecord = external_exports.looseObject({
|
|
40063
|
+
type: external_exports.string(),
|
|
40064
|
+
name: external_exports.string(),
|
|
40065
|
+
value: external_exports.string(),
|
|
40066
|
+
note: external_exports.string().optional()
|
|
40067
|
+
});
|
|
40068
|
+
fileEntry = external_exports.looseObject({
|
|
40069
|
+
path: external_exports.string(),
|
|
40070
|
+
size: external_exports.number().optional(),
|
|
40071
|
+
content_type: external_exports.string().optional(),
|
|
40072
|
+
sha256: external_exports.string().optional(),
|
|
40073
|
+
content: external_exports.string().optional().describe("Inlined contents (text, or base64 when named in paths)"),
|
|
40074
|
+
encoding: external_exports.string().optional().describe('"utf8" or "base64"'),
|
|
40075
|
+
content_omitted: external_exports.string().optional().describe('Why content is absent: "binary", "too_large" or "budget"'),
|
|
40076
|
+
status: external_exports.string().optional().describe('compare only: "added", "removed" or "changed"'),
|
|
40077
|
+
diff: external_exports.string().optional().describe("compare only: unified line diff of a changed text file"),
|
|
40078
|
+
diff_omitted: external_exports.string().optional()
|
|
40079
|
+
});
|
|
40080
|
+
OUTPUT_SCHEMAS = {
|
|
40081
|
+
deploy_site: external_exports.looseObject({
|
|
40082
|
+
ok,
|
|
40083
|
+
site,
|
|
40084
|
+
deploy: deployResult.describe("The deploy itself"),
|
|
40085
|
+
created: external_exports.boolean().optional().describe("True when this call created the site")
|
|
40086
|
+
}),
|
|
40087
|
+
create_upload: external_exports.looseObject({
|
|
40088
|
+
ok,
|
|
40089
|
+
session: external_exports.string().optional().describe("Upload session id \u2014 pass it back to list what the user uploaded"),
|
|
40090
|
+
upload_url: external_exports.string().optional().describe("Mint: the drag-and-drop page to send the user as a clickable link"),
|
|
40091
|
+
put_url: external_exports.string().optional().describe("Mint: PUT target for bytes you already hold ({filename} = last path segment)"),
|
|
40092
|
+
expires_in: external_exports.number().optional().describe("Mint: seconds until the link expires"),
|
|
40093
|
+
max_mb: external_exports.number().optional(),
|
|
40094
|
+
max_files: external_exports.number().optional(),
|
|
40095
|
+
uploads: external_exports.array(
|
|
40096
|
+
external_exports.looseObject({
|
|
40097
|
+
upload_id: external_exports.string().describe("Reference this in deploy_site {path, upload_id}"),
|
|
40098
|
+
filename: external_exports.string().optional(),
|
|
40099
|
+
size_bytes: external_exports.number().optional()
|
|
40100
|
+
})
|
|
40101
|
+
).optional().describe("List (with session): the files staged so far"),
|
|
40102
|
+
next: external_exports.string().optional().describe("What to do next")
|
|
40103
|
+
}).describe("Mint returns the link fields; a call with `session` returns `uploads`"),
|
|
40104
|
+
edit_site: external_exports.looseObject({
|
|
40105
|
+
ok,
|
|
40106
|
+
site: external_exports.looseObject({ id: external_exports.string(), sitename: external_exports.string().optional() }),
|
|
40107
|
+
deploy: deployResult.describe("The patch deploy the edits produced"),
|
|
40108
|
+
files_edited: external_exports.number().optional(),
|
|
40109
|
+
replacements: external_exports.number().optional().describe("Total occurrences replaced across all edits")
|
|
40110
|
+
}),
|
|
40111
|
+
get_site: external_exports.looseObject({
|
|
40112
|
+
ok,
|
|
40113
|
+
site_id: external_exports.string().optional(),
|
|
40114
|
+
sitename: external_exports.string().optional(),
|
|
40115
|
+
version: external_exports.number().optional().describe("The version these files or matches were read from"),
|
|
40116
|
+
current_version: external_exports.number().optional().describe("What is actually live (history, or a pinned-version read)"),
|
|
40117
|
+
total_bytes: external_exports.number().optional(),
|
|
40118
|
+
bundle_hash: external_exports.string().optional(),
|
|
40119
|
+
config: external_exports.unknown().optional().describe("The deployed drivethrough.json"),
|
|
40120
|
+
files: external_exports.array(fileEntry).optional().describe("File reads: entries in deploy_site {path, content, encoding} form. compare: per-file diffs"),
|
|
40121
|
+
find: external_exports.string().optional(),
|
|
40122
|
+
files_searched: external_exports.number().optional(),
|
|
40123
|
+
matches: external_exports.array(
|
|
40124
|
+
external_exports.looseObject({
|
|
40125
|
+
path: external_exports.string(),
|
|
40126
|
+
line: external_exports.number(),
|
|
40127
|
+
text: external_exports.string().describe("The byte-exact deployed line \u2014 usable as edit_site `find` context"),
|
|
40128
|
+
occurrences: external_exports.number().optional()
|
|
40129
|
+
})
|
|
40130
|
+
).optional().describe("find: where the text occurs"),
|
|
40131
|
+
truncated: external_exports.boolean().optional(),
|
|
40132
|
+
versions: external_exports.array(
|
|
40133
|
+
external_exports.looseObject({
|
|
40134
|
+
version: external_exports.number(),
|
|
40135
|
+
created_at: external_exports.string().optional(),
|
|
40136
|
+
files: external_exports.number().optional(),
|
|
40137
|
+
total_bytes: external_exports.number().optional(),
|
|
40138
|
+
current: external_exports.boolean().optional(),
|
|
40139
|
+
changes: external_exports.unknown().optional().describe('{added, changed, removed} paths vs the version before, or "initial"')
|
|
40140
|
+
})
|
|
40141
|
+
).optional().describe("history: newest first \u2014 the map for revert_site"),
|
|
40142
|
+
from: external_exports.number().optional().describe("compare: older version"),
|
|
40143
|
+
to: external_exports.number().optional().describe("compare: newer version"),
|
|
40144
|
+
note: external_exports.string().optional()
|
|
40145
|
+
}).describe("Shape depends on the mode: file reads (files, config), find (matches), history (versions), compare (from, to, files)"),
|
|
40146
|
+
preview_site: external_exports.looseObject({
|
|
40147
|
+
ok,
|
|
40148
|
+
site_id: external_exports.string().optional(),
|
|
40149
|
+
sitename: external_exports.string().optional(),
|
|
40150
|
+
version: external_exports.number().optional().describe("The version that was rendered"),
|
|
40151
|
+
current_version: external_exports.number().optional(),
|
|
40152
|
+
path: external_exports.string().optional(),
|
|
40153
|
+
viewport: external_exports.looseObject({ width: external_exports.number(), height: external_exports.number() }).optional(),
|
|
40154
|
+
full_page: external_exports.boolean().optional(),
|
|
40155
|
+
renderer: external_exports.string().optional(),
|
|
40156
|
+
note: external_exports.string().optional()
|
|
40157
|
+
}).describe("Metadata about the screenshot; the image itself is the result's image content block"),
|
|
40158
|
+
revert_site: deployResult.extend({
|
|
40159
|
+
ok,
|
|
40160
|
+
reverted_to: external_exports.number().optional().describe("The version whose content was restored"),
|
|
40161
|
+
byte_identical: external_exports.boolean().optional().describe("True proves the new version is byte-for-byte the target version")
|
|
40162
|
+
}).describe("A revert is a new deploy: the new version number plus the proof of restoration"),
|
|
40163
|
+
create_db: external_exports.looseObject({
|
|
40164
|
+
ok,
|
|
40165
|
+
site_id: external_exports.string().optional(),
|
|
40166
|
+
d1_database_id: external_exports.string().optional(),
|
|
40167
|
+
created: external_exports.boolean().optional().describe("False when the database already existed")
|
|
40168
|
+
}),
|
|
40169
|
+
query_db: external_exports.looseObject({
|
|
40170
|
+
ok,
|
|
40171
|
+
types: external_exports.array(external_exports.looseObject({ type: external_exports.string(), count: external_exports.number() })).optional().describe("types: row count per type"),
|
|
40172
|
+
items: external_exports.array(dataDoc).optional().describe("list/get: the rows"),
|
|
40173
|
+
next_cursor: external_exports.string().nullish().describe("list: pass back as `cursor` for the next page; null at the end"),
|
|
40174
|
+
count: external_exports.number().optional(),
|
|
40175
|
+
count_capped: external_exports.boolean().optional(),
|
|
40176
|
+
partial: external_exports.boolean().optional().describe("Owner scan of an undeclared query stopped at its row cap"),
|
|
40177
|
+
declare: external_exports.string().optional().describe("The query declaration that would make this list complete and cheap"),
|
|
40178
|
+
search_status: external_exports.string().optional()
|
|
40179
|
+
}).describe('"types" returns `types`; "list" and "get" return `items`'),
|
|
40180
|
+
write_db: external_exports.looseObject({
|
|
40181
|
+
ok,
|
|
40182
|
+
item: dataDoc.optional().describe("insert/update: the row as stored"),
|
|
40183
|
+
inserted: external_exports.number().optional().describe("Bulk insert: rows written"),
|
|
40184
|
+
writes_charged: external_exports.number().optional(),
|
|
40185
|
+
first_seq: external_exports.number().optional(),
|
|
40186
|
+
last_seq: external_exports.number().optional(),
|
|
40187
|
+
deleted: external_exports.boolean().optional(),
|
|
40188
|
+
seq: external_exports.number().optional().describe("delete: the row removed")
|
|
40189
|
+
}).describe("Single insert/update \u2192 item; bulk insert \u2192 counts and seq range; delete \u2192 {deleted, seq}"),
|
|
40190
|
+
set_privacy: external_exports.looseObject({ ok, site }),
|
|
40191
|
+
share_site: external_exports.looseObject({
|
|
40192
|
+
ok,
|
|
40193
|
+
share_link: external_exports.looseObject({
|
|
40194
|
+
id: external_exports.string().optional(),
|
|
40195
|
+
url: external_exports.string().describe("The link to send \u2014 anyone holding it can view"),
|
|
40196
|
+
local_url: external_exports.string().optional(),
|
|
40197
|
+
expires_at: external_exports.string().nullish().describe("null = never expires"),
|
|
40198
|
+
revoked: external_exports.boolean().optional(),
|
|
40199
|
+
created_at: external_exports.string().optional()
|
|
40200
|
+
}).optional(),
|
|
40201
|
+
sent_to: external_exports.string().optional().describe("Invitation: the address the magic link went to")
|
|
40202
|
+
}).describe("A share link, or (with email) confirmation the invitation was sent"),
|
|
40203
|
+
list_sites: external_exports.looseObject({ ok, sites: external_exports.array(site) }),
|
|
40204
|
+
delete_site: external_exports.looseObject({
|
|
40205
|
+
ok,
|
|
40206
|
+
deleted: external_exports.boolean().optional(),
|
|
40207
|
+
site: site.optional(),
|
|
40208
|
+
recoverable_until: external_exports.string().nullish().describe("Restore with restore_site before this date"),
|
|
40209
|
+
message: external_exports.string().optional()
|
|
40210
|
+
}),
|
|
40211
|
+
check_domain: external_exports.looseObject({
|
|
40212
|
+
ok,
|
|
40213
|
+
domain: external_exports.string().optional(),
|
|
40214
|
+
status: external_exports.string().optional().describe("status: pending_dns \u2192 pending_ssl \u2192 active"),
|
|
40215
|
+
target: external_exports.string().optional(),
|
|
40216
|
+
records: external_exports.array(dnsRecord).optional().describe("The DNS records to create, exact and paste-ready"),
|
|
40217
|
+
hostnames: external_exports.array(external_exports.unknown()).optional(),
|
|
40218
|
+
supported: external_exports.boolean().optional().describe("connect: whether the registrar supports one-click setup"),
|
|
40219
|
+
provider: external_exports.string().optional(),
|
|
40220
|
+
url: external_exports.string().optional().describe("connect: the one-click registrar link to relay"),
|
|
40221
|
+
next: external_exports.string().optional()
|
|
40222
|
+
}).describe('"status" \u2192 verification progress; "connect" \u2192 one-click setup link or manual records'),
|
|
40223
|
+
manage_domain: external_exports.looseObject({
|
|
40224
|
+
ok,
|
|
40225
|
+
domain: external_exports.string().optional(),
|
|
40226
|
+
status: external_exports.string().optional(),
|
|
40227
|
+
target: external_exports.string().optional(),
|
|
40228
|
+
records: external_exports.array(dnsRecord).optional().describe("attach: the DNS records the user must add"),
|
|
40229
|
+
next: external_exports.string().optional(),
|
|
40230
|
+
detached: external_exports.string().optional().describe("detach: the domain removed")
|
|
40231
|
+
}).describe("attach \u2192 DNS records to add; detach \u2192 the removed domain"),
|
|
40232
|
+
restore_site: external_exports.looseObject({
|
|
40233
|
+
ok,
|
|
40234
|
+
restored: external_exports.boolean().optional(),
|
|
40235
|
+
site: site.optional(),
|
|
40236
|
+
renamed: external_exports.boolean().optional().describe("True when restored under a new name"),
|
|
40237
|
+
domain_lost: external_exports.string().nullish().describe("A custom domain that could not be re-attached"),
|
|
40238
|
+
message: external_exports.string().optional()
|
|
40239
|
+
}),
|
|
40240
|
+
manage_integrations: external_exports.looseObject({
|
|
40241
|
+
ok,
|
|
40242
|
+
// secrets
|
|
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"),
|
|
40244
|
+
session: external_exports.string().optional().describe(`secrets: pass back with action "secrets" after the user says they're done`),
|
|
40245
|
+
expires_at: external_exports.string().optional(),
|
|
40246
|
+
names: external_exports.array(external_exports.string()).optional().describe("secrets: the names the link asks for"),
|
|
40247
|
+
set: external_exports.record(external_exports.string(), external_exports.boolean()).optional().describe("secrets: every secret name \u2192 whether it has a value. Values are never returned."),
|
|
40248
|
+
all_set: external_exports.boolean().optional().describe("secrets (with session): true once every declared secret is set"),
|
|
40249
|
+
// status
|
|
40250
|
+
secrets: external_exports.record(external_exports.string(), external_exports.string()).optional().describe('status: name \u2192 "set" | "unset"'),
|
|
40251
|
+
routes: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared routes"),
|
|
40252
|
+
actions: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("status: declared actions, their fields and triggering types"),
|
|
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"),
|
|
40255
|
+
action_log: external_exports.array(external_exports.unknown()).optional().describe("status: the latest 20 outcomes, newest first"),
|
|
40256
|
+
// test
|
|
40257
|
+
target: external_exports.string().optional(),
|
|
40258
|
+
kind: external_exports.string().optional().describe('test: "route", "action", "schedule" or "webhook"'),
|
|
40259
|
+
status: external_exports.string().optional().describe('test: "ok", "failed", "skipped", "plan" or "quota"'),
|
|
40260
|
+
http_status: external_exports.number().nullish().describe("test: the upstream's HTTP status"),
|
|
40261
|
+
duration_ms: external_exports.number().optional(),
|
|
40262
|
+
body_preview: external_exports.string().nullish().describe("test: first 1 KB of the upstream response, secrets redacted"),
|
|
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)"),
|
|
40265
|
+
next: external_exports.string().optional()
|
|
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')
|
|
40267
|
+
};
|
|
39908
40268
|
}
|
|
39909
40269
|
});
|
|
39910
40270
|
|
|
39911
40271
|
// ../mcp-tools/src/tools.ts
|
|
39912
|
-
function
|
|
39913
|
-
|
|
40272
|
+
function ok2(data) {
|
|
40273
|
+
const structured = { ok: true, ...data };
|
|
40274
|
+
return { content: [{ type: "text", text: JSON.stringify(structured) }], structuredContent: structured };
|
|
39914
40275
|
}
|
|
39915
40276
|
function toolError(error62) {
|
|
39916
40277
|
const body = apiErrorBody(error62) ?? {
|
|
@@ -39921,8 +40282,8 @@ function toolError(error62) {
|
|
|
39921
40282
|
function problemError(problem) {
|
|
39922
40283
|
return new ToolApiError(422, { error: problem });
|
|
39923
40284
|
}
|
|
39924
|
-
function sitePath(
|
|
39925
|
-
return `/v1/sites/${encodeURIComponent(
|
|
40285
|
+
function sitePath(site2) {
|
|
40286
|
+
return `/v1/sites/${encodeURIComponent(site2)}`;
|
|
39926
40287
|
}
|
|
39927
40288
|
function registerTools(server, ctx) {
|
|
39928
40289
|
const canDeployDirectory = Boolean(ctx.localDirectories);
|
|
@@ -39931,6 +40292,7 @@ function registerTools(server, ctx) {
|
|
|
39931
40292
|
server.registerTool(
|
|
39932
40293
|
"deploy_site",
|
|
39933
40294
|
{
|
|
40295
|
+
outputSchema: OUTPUT_SCHEMAS.deploy_site,
|
|
39934
40296
|
title: "Deploy site",
|
|
39935
40297
|
description: `Deploy a site to Drivethrough. Idempotent: creates the site if needed, else ships a new version. Sites are private by default. ${deploySources} Images and other binary assets \u2014 pick the route by where the bytes live: already at a public URL \u2192 {path, source_url} (fetched server-side); ${binaryRoutes}already deployed on this site \u2192 {path, from_path} copies it server-side (with delete_paths, a move). Never base64-encode a file, by hand or in code \u2014 inline base64 is capped at 1 MB and is only for tiny pasted assets; a file shipped via upload_id is referenced, never additionally inlined. To rework an already-deployed site, read it back with get_site first, then deploy with mode "patch" \u2014 only the files that change. For small text changes to existing files, use edit_site instead: it sends find/replace pairs, never whole files. Every deploy's version number is a restore point: prior versions stay listed (get_site history), diffable, viewable (preview_site), and returnable (revert_site). A successful deploy may still return \`warnings\` \u2014 things that will bite at runtime but only for visitors, never for you: a form type anonymous visitors can write with no page loading the Turnstile widget, or a page that fetches a data type its visitors have no permission to read. Resolve them before telling the user it works.`,
|
|
39936
40298
|
// Destructive: the default "replace" mode drops every deployed file the
|
|
@@ -39977,7 +40339,7 @@ function registerTools(server, ctx) {
|
|
|
39977
40339
|
async ({
|
|
39978
40340
|
directory,
|
|
39979
40341
|
files,
|
|
39980
|
-
site,
|
|
40342
|
+
site: site2,
|
|
39981
40343
|
title,
|
|
39982
40344
|
mode,
|
|
39983
40345
|
delete_paths,
|
|
@@ -40053,7 +40415,7 @@ function registerTools(server, ctx) {
|
|
|
40053
40415
|
}
|
|
40054
40416
|
});
|
|
40055
40417
|
}
|
|
40056
|
-
const sitename =
|
|
40418
|
+
const sitename = site2 ?? config2?.site ?? fallbackName;
|
|
40057
40419
|
if (!sitename) {
|
|
40058
40420
|
throw new ToolApiError(422, {
|
|
40059
40421
|
error: {
|
|
@@ -40081,7 +40443,7 @@ function registerTools(server, ctx) {
|
|
|
40081
40443
|
`${sitePath(siteRes.site.id)}/deployments`,
|
|
40082
40444
|
{ form }
|
|
40083
40445
|
);
|
|
40084
|
-
return
|
|
40446
|
+
return ok2({ site: siteRes.site, deploy, created: siteRes.created });
|
|
40085
40447
|
} catch (error62) {
|
|
40086
40448
|
return toolError(error62);
|
|
40087
40449
|
}
|
|
@@ -40090,6 +40452,7 @@ function registerTools(server, ctx) {
|
|
|
40090
40452
|
server.registerTool(
|
|
40091
40453
|
"create_upload",
|
|
40092
40454
|
{
|
|
40455
|
+
outputSchema: OUTPUT_SCHEMAS.create_upload,
|
|
40093
40456
|
title: "Create upload link",
|
|
40094
40457
|
description: "Mint a short-lived upload link the USER opens in their browser to add local files (photos, PDFs, audio, video) \u2014 the route for binary assets when you cannot reach the bytes and they aren't hosted anywhere. Two-step: (1) relay the returned upload_url as a clickable link \u2014 it opens a drag-and-drop page (no sign-in needed) that takes up to 20 files, 25 MB each, and expires in 30 minutes. (2) When the user says they're done, call this tool again with the SAME `site` plus the returned `session` \u2014 it lists what they uploaded ({upload_id, filename, size_bytes}); ship each with deploy_site files: [{path, upload_id}] (mode \"patch\" adds them to a live site). Never base64-encode a file instead \u2014 but if the bytes are already in YOUR runtime (e.g. a chat attachment mounted in your sandbox) AND you can make HTTP requests, you may PUT them straight to the returned put_url (one file per request, its filename as the last path segment) without involving the user. Choosing a route for a binary asset: already on the public web \u2192 deploy_site {path, source_url}; this server runs on the user's machine \u2192 deploy the directory; file only in the chat or on the user's machine \u2192 this tool. Tiny pasted assets (\u22641 MB, e.g. a favicon) may still go inline as base64 content. Upload ids are single-use: the deploy that references one consumes it (the bundle keeps the bytes; the staged copy is removed and its quota refunded) \u2014 to rename or duplicate the file later, use deploy_site {path, from_path}. An upload no deploy references within 24 hours is deleted, so deploy soon after the user confirms. Pass size_bytes when the user told you a file's size \u2014 quota errors then surface here instead of failing their upload.",
|
|
40095
40458
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
@@ -40103,13 +40466,13 @@ function registerTools(server, ctx) {
|
|
|
40103
40466
|
)
|
|
40104
40467
|
}
|
|
40105
40468
|
},
|
|
40106
|
-
async ({ site, session, size_bytes }) => {
|
|
40469
|
+
async ({ site: site2, session, size_bytes }) => {
|
|
40107
40470
|
try {
|
|
40108
40471
|
if (session !== void 0) {
|
|
40109
|
-
return
|
|
40472
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/uploads/${encodeURIComponent(session)}`));
|
|
40110
40473
|
}
|
|
40111
|
-
return
|
|
40112
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40474
|
+
return ok2(
|
|
40475
|
+
await ctx.api.request("POST", `${sitePath(site2)}/uploads`, {
|
|
40113
40476
|
body: { ...size_bytes !== void 0 ? { size_bytes } : {} }
|
|
40114
40477
|
})
|
|
40115
40478
|
);
|
|
@@ -40121,6 +40484,7 @@ function registerTools(server, ctx) {
|
|
|
40121
40484
|
server.registerTool(
|
|
40122
40485
|
"edit_site",
|
|
40123
40486
|
{
|
|
40487
|
+
outputSchema: OUTPUT_SCHEMAS.edit_site,
|
|
40124
40488
|
title: "Edit site files",
|
|
40125
40489
|
description: "Edit a deployed site's text files in place \u2014 the efficient way to make small changes (wording, values, a link, a style tweak) to a live site. Send only find/replace pairs, never whole file contents: the platform reads the current version, applies the edits, and ships a new version with everything else carried forward. Prefer this over get_site + deploy_site whenever the change is small relative to the file. Each `find` must match the deployed text exactly (whitespace included) and exactly once \u2014 add surrounding context to pin down one occurrence, or set replace_all. All-or-nothing: if any edit fails to match, nothing deploys and the error says how to recover, so you can try an edit without reading the file first. Edits are never lossy: the pre-edit version stays restorable via revert_site, so to undo an edit, revert \u2014 don't hand-invert it. After visual changes, preview_site shows what actually rendered.",
|
|
40126
40490
|
// Destructive: find/replace overwrites text that is currently live.
|
|
@@ -40138,7 +40502,7 @@ function registerTools(server, ctx) {
|
|
|
40138
40502
|
).min(1).describe("Edits applied in order; later edits see earlier results")
|
|
40139
40503
|
}
|
|
40140
40504
|
},
|
|
40141
|
-
async ({ site, edits }) => {
|
|
40505
|
+
async ({ site: site2, edits }) => {
|
|
40142
40506
|
try {
|
|
40143
40507
|
const normalized = edits.map((e3) => ({ ...e3, path: e3.path.startsWith("/") ? e3.path : `/${e3.path}` }));
|
|
40144
40508
|
for (const edit of normalized) {
|
|
@@ -40154,7 +40518,7 @@ function registerTools(server, ctx) {
|
|
|
40154
40518
|
}
|
|
40155
40519
|
const paths = [...new Set(normalized.map((e3) => e3.path))];
|
|
40156
40520
|
const query = paths.map((p2) => `path=${encodeURIComponent(p2)}`).join("&");
|
|
40157
|
-
const read = await ctx.api.request("GET", `${sitePath(
|
|
40521
|
+
const read = await ctx.api.request("GET", `${sitePath(site2)}/files?${query}`);
|
|
40158
40522
|
const texts = /* @__PURE__ */ new Map();
|
|
40159
40523
|
for (const path8 of paths) {
|
|
40160
40524
|
const file2 = read.files.find((f3) => f3.path === path8);
|
|
@@ -40198,7 +40562,7 @@ function registerTools(server, ctx) {
|
|
|
40198
40562
|
`${sitePath(read.site_id)}/deployments`,
|
|
40199
40563
|
{ form: assembled.bundle.form }
|
|
40200
40564
|
);
|
|
40201
|
-
return
|
|
40565
|
+
return ok2({
|
|
40202
40566
|
site: { id: read.site_id, sitename: read.sitename },
|
|
40203
40567
|
deploy,
|
|
40204
40568
|
files_edited: paths.length,
|
|
@@ -40212,6 +40576,7 @@ function registerTools(server, ctx) {
|
|
|
40212
40576
|
server.registerTool(
|
|
40213
40577
|
"get_site",
|
|
40214
40578
|
{
|
|
40579
|
+
outputSchema: OUTPUT_SCHEMAS.get_site,
|
|
40215
40580
|
title: "Read site files & history",
|
|
40216
40581
|
description: 'Read back a deployed site: file manifest, file contents, drivethrough.json \u2014 and the site\'s version history. Use this to resume work on a site from an earlier conversation. Read the minimum you need: `find` locates text without returning file contents, `manifest_only` lists what exists, `paths` fetches only named files \u2014 a bare call inlines every text file, so reserve it for genuinely reading a whole small site. Versions: every deploy is a numbered version the platform keeps. `history: true` lists them with what each one changed (the map for revert_site), `compare: {from, to}` returns line diffs between two versions ("what did v9\u2192v10 change?"), and `version` pins any read mode to a prior version instead of the live one. Modes are exclusive per call \u2014 compare wins over history, history over find/manifest_only. Entries come back deploy_site-compatible ({path, content, encoding}); binary files are listed but only inlined when named in `paths`. For small text changes you may not need this at all \u2014 edit_site applies find/replace to the deployed files directly.',
|
|
40217
40582
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40239,7 +40604,7 @@ function registerTools(server, ctx) {
|
|
|
40239
40604
|
}
|
|
40240
40605
|
},
|
|
40241
40606
|
async ({
|
|
40242
|
-
site,
|
|
40607
|
+
site: site2,
|
|
40243
40608
|
paths,
|
|
40244
40609
|
find,
|
|
40245
40610
|
manifest_only,
|
|
@@ -40249,7 +40614,7 @@ function registerTools(server, ctx) {
|
|
|
40249
40614
|
}) => {
|
|
40250
40615
|
try {
|
|
40251
40616
|
if (compare) {
|
|
40252
|
-
return
|
|
40617
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/diff?from=${compare.from}&to=${compare.to}`));
|
|
40253
40618
|
}
|
|
40254
40619
|
if (history !== void 0 && history !== false) {
|
|
40255
40620
|
if (typeof history === "number" && history < 1) {
|
|
@@ -40262,7 +40627,7 @@ function registerTools(server, ctx) {
|
|
|
40262
40627
|
});
|
|
40263
40628
|
}
|
|
40264
40629
|
const before = typeof history === "number" ? `?before=${history}` : "";
|
|
40265
|
-
return
|
|
40630
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/versions${before}`));
|
|
40266
40631
|
}
|
|
40267
40632
|
if (find !== void 0 && find.length === 0) {
|
|
40268
40633
|
throw new ToolApiError(422, {
|
|
@@ -40279,7 +40644,7 @@ function registerTools(server, ctx) {
|
|
|
40279
40644
|
else if (manifest_only) params.set("manifest_only", "1");
|
|
40280
40645
|
if (version2 !== void 0) params.set("version", String(version2));
|
|
40281
40646
|
const query = params.toString();
|
|
40282
|
-
return
|
|
40647
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/files${query ? `?${query}` : ""}`));
|
|
40283
40648
|
} catch (error62) {
|
|
40284
40649
|
return toolError(error62);
|
|
40285
40650
|
}
|
|
@@ -40288,6 +40653,7 @@ function registerTools(server, ctx) {
|
|
|
40288
40653
|
server.registerTool(
|
|
40289
40654
|
"preview_site",
|
|
40290
40655
|
{
|
|
40656
|
+
outputSchema: OUTPUT_SCHEMAS.preview_site,
|
|
40291
40657
|
title: "Preview site (screenshot)",
|
|
40292
40658
|
description: "See a deployed site the way a visitor does: renders the page in a real browser server-side and returns a screenshot image you can look at directly. You have no browser of your own \u2014 this is your only view of rendered results, so use it to verify visual work (layout, styling, animations, responsive behavior) actually shipped correctly instead of assuming it did, and to eyeball a prior `version` before reverting to it. Works on private sites; nothing becomes public. Takes a few seconds \u2014 look once after a coherent batch of changes, not after every edit. Animations: the capture happens at page load; pass wait_ms to let one settle or reach mid-cycle.",
|
|
40293
40659
|
// Open-world: a real browser loads the page, and with it whatever the
|
|
@@ -40303,7 +40669,7 @@ function registerTools(server, ctx) {
|
|
|
40303
40669
|
}
|
|
40304
40670
|
},
|
|
40305
40671
|
async ({
|
|
40306
|
-
site,
|
|
40672
|
+
site: site2,
|
|
40307
40673
|
path: path8,
|
|
40308
40674
|
version: version2,
|
|
40309
40675
|
viewport,
|
|
@@ -40311,7 +40677,7 @@ function registerTools(server, ctx) {
|
|
|
40311
40677
|
full_page
|
|
40312
40678
|
}) => {
|
|
40313
40679
|
try {
|
|
40314
|
-
const { image_base64, content_type, ...meta3 } = await ctx.api.request("POST", `${sitePath(
|
|
40680
|
+
const { image_base64, content_type, ...meta3 } = await ctx.api.request("POST", `${sitePath(site2)}/preview`, {
|
|
40315
40681
|
body: {
|
|
40316
40682
|
...path8 !== void 0 ? { path: path8 } : {},
|
|
40317
40683
|
...version2 !== void 0 ? { version: version2 } : {},
|
|
@@ -40320,11 +40686,13 @@ function registerTools(server, ctx) {
|
|
|
40320
40686
|
...full_page !== void 0 ? { full_page } : {}
|
|
40321
40687
|
}
|
|
40322
40688
|
});
|
|
40689
|
+
const structured = { ok: true, ...meta3 };
|
|
40323
40690
|
return {
|
|
40324
40691
|
content: [
|
|
40325
40692
|
{ type: "image", data: image_base64, mimeType: content_type },
|
|
40326
|
-
{ type: "text", text: JSON.stringify(
|
|
40327
|
-
]
|
|
40693
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
40694
|
+
],
|
|
40695
|
+
structuredContent: structured
|
|
40328
40696
|
};
|
|
40329
40697
|
} catch (error62) {
|
|
40330
40698
|
return toolError(error62);
|
|
@@ -40334,6 +40702,7 @@ function registerTools(server, ctx) {
|
|
|
40334
40702
|
server.registerTool(
|
|
40335
40703
|
"revert_site",
|
|
40336
40704
|
{
|
|
40705
|
+
outputSchema: OUTPUT_SCHEMAS.revert_site,
|
|
40337
40706
|
title: "Revert site to a version",
|
|
40338
40707
|
description: `Return a site to a prior version, byte-exactly, in one call \u2014 the safe way to undo. When the user says "undo that" or "put it back how it was", use this; never reconstruct the old state by hand with inverse edits, which risks leaving the site neither old nor new. The platform redeploys the target version's exact stored files as a new version \u2014 history is preserved, so a revert can itself be reverted \u2014 and the response proves the restore: byte_identical: true means the new version's content hash equals the target's. Find the target with get_site {history: true} (each entry shows what that version changed), or use the version number a deploy/edit response reported. Undoing just one file of a multi-file change? Read that file at the old version (get_site {version, paths}) and deploy it with mode "patch" instead.`,
|
|
40339
40708
|
// Destructive: the live site's current content is replaced by the older
|
|
@@ -40346,9 +40715,9 @@ function registerTools(server, ctx) {
|
|
|
40346
40715
|
to_version: external_exports.number().int().min(1).describe("The version to return to (see get_site {history: true})")
|
|
40347
40716
|
}
|
|
40348
40717
|
},
|
|
40349
|
-
async ({ site, to_version }) => {
|
|
40718
|
+
async ({ site: site2, to_version }) => {
|
|
40350
40719
|
try {
|
|
40351
|
-
return
|
|
40720
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/revert`, { body: { to_version } }));
|
|
40352
40721
|
} catch (error62) {
|
|
40353
40722
|
return toolError(error62);
|
|
40354
40723
|
}
|
|
@@ -40357,14 +40726,15 @@ function registerTools(server, ctx) {
|
|
|
40357
40726
|
server.registerTool(
|
|
40358
40727
|
"create_db",
|
|
40359
40728
|
{
|
|
40729
|
+
outputSchema: OUTPUT_SCHEMAS.create_db,
|
|
40360
40730
|
title: "Create site database",
|
|
40361
40731
|
description: `Provision the per-site database. Idempotent. Deploying a drivethrough.json that declares types does this automatically. Site pages read and write rows via the same-origin Data API: GET /api/data?type=X lists, POST /api/data with {type, json} inserts. Anonymous writes (write: "public", or "viewers" on a public site) additionally require Cloudflare Turnstile in that POST: GET /api/config for the turnstile_site_key, load https://challenges.cloudflare.com/turnstile/v0/api.js, render <div class="cf-turnstile" data-sitekey="\u2026"></div> inside the form, and send the widget's "cf-turnstile-response" input value as "turnstile_token" in the body. The widget is usually invisible; owner and invited-viewer writes on private sites skip it entirely.`,
|
|
40362
40732
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
40363
40733
|
inputSchema: { site: external_exports.string().describe("Site name or id") }
|
|
40364
40734
|
},
|
|
40365
|
-
async ({ site }) => {
|
|
40735
|
+
async ({ site: site2 }) => {
|
|
40366
40736
|
try {
|
|
40367
|
-
return
|
|
40737
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/db`));
|
|
40368
40738
|
} catch (error62) {
|
|
40369
40739
|
return toolError(error62);
|
|
40370
40740
|
}
|
|
@@ -40373,6 +40743,7 @@ function registerTools(server, ctx) {
|
|
|
40373
40743
|
server.registerTool(
|
|
40374
40744
|
"query_db",
|
|
40375
40745
|
{
|
|
40746
|
+
outputSchema: OUTPUT_SCHEMAS.query_db,
|
|
40376
40747
|
title: "Query site database",
|
|
40377
40748
|
description: 'Read the site\'s database as its owner (read-only \u2014 writes go through write_db). Same query surface the site\'s own pages use, so what works here works there. Every row is {seq, type, parent_seq, json, created_at, updated_at}: `seq` is the row number and its permanent identity (never reused, even after a delete), `json` is the document. Newest-first is the default and always free. Filtering, sorting and search need a matching entry in the site\'s "query" block in drivethrough.json \u2014 an undeclared query is refused with the exact line to add. As the owner you get a bounded 5,000-row scan instead of a refusal, marked "partial": true, so you can look at data you have not modelled yet; a visitor page gets the refusal.',
|
|
40378
40749
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40404,11 +40775,11 @@ function registerTools(server, ctx) {
|
|
|
40404
40775
|
}
|
|
40405
40776
|
},
|
|
40406
40777
|
async (args) => {
|
|
40407
|
-
const { site, action, type, seq } = args;
|
|
40778
|
+
const { site: site2, action, type, seq } = args;
|
|
40408
40779
|
try {
|
|
40409
40780
|
switch (action) {
|
|
40410
40781
|
case "types":
|
|
40411
|
-
return
|
|
40782
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data/types`));
|
|
40412
40783
|
case "list": {
|
|
40413
40784
|
if (!type) {
|
|
40414
40785
|
throw new ToolApiError(422, {
|
|
@@ -40436,7 +40807,7 @@ function registerTools(server, ctx) {
|
|
|
40436
40807
|
if (args.cursor) params.set("cursor", args.cursor);
|
|
40437
40808
|
if (args.offset !== void 0) params.set("offset", String(args.offset));
|
|
40438
40809
|
if (args.count) params.set("count", "1");
|
|
40439
|
-
return
|
|
40810
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data?${params}`));
|
|
40440
40811
|
}
|
|
40441
40812
|
case "get": {
|
|
40442
40813
|
if (seq === void 0) {
|
|
@@ -40448,7 +40819,7 @@ function registerTools(server, ctx) {
|
|
|
40448
40819
|
}
|
|
40449
40820
|
});
|
|
40450
40821
|
}
|
|
40451
|
-
return
|
|
40822
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data?seq=${encodeURIComponent(String(seq))}`));
|
|
40452
40823
|
}
|
|
40453
40824
|
}
|
|
40454
40825
|
} catch (error62) {
|
|
@@ -40459,6 +40830,7 @@ function registerTools(server, ctx) {
|
|
|
40459
40830
|
server.registerTool(
|
|
40460
40831
|
"write_db",
|
|
40461
40832
|
{
|
|
40833
|
+
outputSchema: OUTPUT_SCHEMAS.write_db,
|
|
40462
40834
|
title: "Write site database",
|
|
40463
40835
|
description: "Write to the site's database as its owner: insert one row, import many, update, or delete (reads go through query_db). Rows are addressed by `seq`, the row number query_db returns. To change a few fields of a row, `update` with `merge` \u2014 only the fields that change, no fetch needed. Reserve `json` (wholesale replacement) for rewriting the whole document. To load a catalogue, use `rows` for a handful and `rows_url` for thousands \u2014 never loop insert, and never try to emit 100,000 rows yourself. Writes are validated against the types declared in the site's drivethrough.json (unknown types and mismatched fields are rejected, and a bulk import is checked in full BEFORE the first row is written, so it is all-or-nothing); pass validate: false only for a deliberate migration. Deleted and overwritten rows are gone for good \u2014 there is no version history for data, so read a row (query_db) before destructive rewrites you might need to undo. Deleting a row also deletes its children. Every write counts against the account's monthly write allowance, and an import larger than what remains is refused before anything is written.",
|
|
40464
40836
|
// Open-world for the same reason deploy_site is: rows_url makes the
|
|
@@ -40484,7 +40856,7 @@ function registerTools(server, ctx) {
|
|
|
40484
40856
|
}
|
|
40485
40857
|
},
|
|
40486
40858
|
async ({
|
|
40487
|
-
site,
|
|
40859
|
+
site: site2,
|
|
40488
40860
|
action,
|
|
40489
40861
|
type,
|
|
40490
40862
|
seq,
|
|
@@ -40538,8 +40910,8 @@ function registerTools(server, ctx) {
|
|
|
40538
40910
|
}
|
|
40539
40911
|
});
|
|
40540
40912
|
}
|
|
40541
|
-
return
|
|
40542
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40913
|
+
return ok2(
|
|
40914
|
+
await ctx.api.request("POST", `${sitePath(site2)}/data`, {
|
|
40543
40915
|
body: {
|
|
40544
40916
|
type,
|
|
40545
40917
|
...parent_seq !== void 0 ? { parent_seq } : {},
|
|
@@ -40559,8 +40931,8 @@ function registerTools(server, ctx) {
|
|
|
40559
40931
|
}
|
|
40560
40932
|
});
|
|
40561
40933
|
}
|
|
40562
|
-
return
|
|
40563
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40934
|
+
return ok2(
|
|
40935
|
+
await ctx.api.request("POST", `${sitePath(site2)}/data`, {
|
|
40564
40936
|
body: { type, parent_seq, json: json2, ...validate3 !== void 0 ? { validate: validate3 } : {} }
|
|
40565
40937
|
})
|
|
40566
40938
|
);
|
|
@@ -40575,8 +40947,8 @@ function registerTools(server, ctx) {
|
|
|
40575
40947
|
}
|
|
40576
40948
|
});
|
|
40577
40949
|
}
|
|
40578
|
-
return
|
|
40579
|
-
await ctx.api.request("PATCH", `${sitePath(
|
|
40950
|
+
return ok2(
|
|
40951
|
+
await ctx.api.request("PATCH", `${sitePath(site2)}/data/${encodeURIComponent(String(needSeq()))}`, {
|
|
40580
40952
|
body: {
|
|
40581
40953
|
...type !== void 0 ? { type } : {},
|
|
40582
40954
|
...parent_seq !== void 0 ? { parent_seq } : {},
|
|
@@ -40587,8 +40959,8 @@ function registerTools(server, ctx) {
|
|
|
40587
40959
|
})
|
|
40588
40960
|
);
|
|
40589
40961
|
case "delete":
|
|
40590
|
-
return
|
|
40591
|
-
await ctx.api.request("DELETE", `${sitePath(
|
|
40962
|
+
return ok2(
|
|
40963
|
+
await ctx.api.request("DELETE", `${sitePath(site2)}/data/${encodeURIComponent(String(needSeq()))}`)
|
|
40592
40964
|
);
|
|
40593
40965
|
}
|
|
40594
40966
|
} catch (error62) {
|
|
@@ -40599,6 +40971,7 @@ function registerTools(server, ctx) {
|
|
|
40599
40971
|
server.registerTool(
|
|
40600
40972
|
"set_privacy",
|
|
40601
40973
|
{
|
|
40974
|
+
outputSchema: OUTPUT_SCHEMAS.set_privacy,
|
|
40602
40975
|
title: "Set site visibility",
|
|
40603
40976
|
description: "Set a site's visibility to public or private. Privacy is a setting \u2014 nothing redeploys.",
|
|
40604
40977
|
// Open-world: going public puts the site on the open web.
|
|
@@ -40608,9 +40981,9 @@ function registerTools(server, ctx) {
|
|
|
40608
40981
|
visibility: external_exports.enum(["public", "private"]).describe("Who can see the site")
|
|
40609
40982
|
}
|
|
40610
40983
|
},
|
|
40611
|
-
async ({ site, visibility }) => {
|
|
40984
|
+
async ({ site: site2, visibility }) => {
|
|
40612
40985
|
try {
|
|
40613
|
-
return
|
|
40986
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/privacy`, { body: { visibility } }));
|
|
40614
40987
|
} catch (error62) {
|
|
40615
40988
|
return toolError(error62);
|
|
40616
40989
|
}
|
|
@@ -40619,6 +40992,7 @@ function registerTools(server, ctx) {
|
|
|
40619
40992
|
server.registerTool(
|
|
40620
40993
|
"share_site",
|
|
40621
40994
|
{
|
|
40995
|
+
outputSchema: OUTPUT_SCHEMAS.share_site,
|
|
40622
40996
|
title: "Share site",
|
|
40623
40997
|
description: "Create a share link for a private site, or send a magic-link invitation by email. Anyone with the link can view; guests never make accounts.",
|
|
40624
40998
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
@@ -40627,10 +41001,10 @@ function registerTools(server, ctx) {
|
|
|
40627
41001
|
email: external_exports.string().email().optional().describe("Send a magic-link invitation to this email instead of returning a link")
|
|
40628
41002
|
}
|
|
40629
41003
|
},
|
|
40630
|
-
async ({ site, email: email3 }) => {
|
|
41004
|
+
async ({ site: site2, email: email3 }) => {
|
|
40631
41005
|
try {
|
|
40632
|
-
if (email3) return
|
|
40633
|
-
return
|
|
41006
|
+
if (email3) return ok2(await ctx.api.request("POST", `${sitePath(site2)}/invite`, { body: { email: email3 } }));
|
|
41007
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/share`));
|
|
40634
41008
|
} catch (error62) {
|
|
40635
41009
|
return toolError(error62);
|
|
40636
41010
|
}
|
|
@@ -40639,6 +41013,7 @@ function registerTools(server, ctx) {
|
|
|
40639
41013
|
server.registerTool(
|
|
40640
41014
|
"list_sites",
|
|
40641
41015
|
{
|
|
41016
|
+
outputSchema: OUTPUT_SCHEMAS.list_sites,
|
|
40642
41017
|
title: "List sites",
|
|
40643
41018
|
description: "List the authenticated account's sites with their addresses, visibility, and status. Pass include_deleted to also see recently deleted sites (recoverable for 30 days via restore_site).",
|
|
40644
41019
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40648,7 +41023,7 @@ function registerTools(server, ctx) {
|
|
|
40648
41023
|
},
|
|
40649
41024
|
async ({ include_deleted }) => {
|
|
40650
41025
|
try {
|
|
40651
|
-
return
|
|
41026
|
+
return ok2(await ctx.api.request("GET", `/v1/sites${include_deleted ? "?include_deleted=1" : ""}`));
|
|
40652
41027
|
} catch (error62) {
|
|
40653
41028
|
return toolError(error62);
|
|
40654
41029
|
}
|
|
@@ -40657,14 +41032,15 @@ function registerTools(server, ctx) {
|
|
|
40657
41032
|
server.registerTool(
|
|
40658
41033
|
"delete_site",
|
|
40659
41034
|
{
|
|
41035
|
+
outputSchema: OUTPUT_SCHEMAS.delete_site,
|
|
40660
41036
|
title: "Delete site",
|
|
40661
41037
|
description: "Delete a site. Two-phase and safe: the site leaves the driveway immediately (guests lose access, share links are revoked, the name frees up) but stays recoverable for 30 days via restore_site. Only after 30 days is it destroyed for good.",
|
|
40662
41038
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
40663
41039
|
inputSchema: { site: external_exports.string().describe("Site name or id") }
|
|
40664
41040
|
},
|
|
40665
|
-
async ({ site }) => {
|
|
41041
|
+
async ({ site: site2 }) => {
|
|
40666
41042
|
try {
|
|
40667
|
-
return
|
|
41043
|
+
return ok2(await ctx.api.request("DELETE", sitePath(site2)));
|
|
40668
41044
|
} catch (error62) {
|
|
40669
41045
|
return toolError(error62);
|
|
40670
41046
|
}
|
|
@@ -40673,6 +41049,7 @@ function registerTools(server, ctx) {
|
|
|
40673
41049
|
server.registerTool(
|
|
40674
41050
|
"check_domain",
|
|
40675
41051
|
{
|
|
41052
|
+
outputSchema: OUTPUT_SCHEMAS.check_domain,
|
|
40676
41053
|
title: "Check custom domain",
|
|
40677
41054
|
description: `Check a site's custom domain, read-only. "status": live verification progress \u2014 pending_dns (waiting on DNS records) \u2192 pending_ssl (certificate issuing) \u2192 active (serving with TLS); poll after setup and tell the user when it's live. "connect": look up one-click setup at the user's registrar (GoDaddy, Cloudflare, IONOS, Squarespace, \u2026) \u2014 when a URL comes back, relay it and their registrar creates the records in one click; otherwise relay the copy-paste records manage_domain attach returned.`,
|
|
40678
41055
|
// Not readOnlyHint despite being the "read" half of the domain pair:
|
|
@@ -40685,10 +41062,10 @@ function registerTools(server, ctx) {
|
|
|
40685
41062
|
action: external_exports.enum(["status", "connect"]).describe('"status" = verification progress; "connect" = one-click registrar setup lookup')
|
|
40686
41063
|
}
|
|
40687
41064
|
},
|
|
40688
|
-
async ({ site, action }) => {
|
|
41065
|
+
async ({ site: site2, action }) => {
|
|
40689
41066
|
try {
|
|
40690
|
-
if (action === "connect") return
|
|
40691
|
-
return
|
|
41067
|
+
if (action === "connect") return ok2(await ctx.api.request("GET", `${sitePath(site2)}/domain/connect`));
|
|
41068
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/domain`));
|
|
40692
41069
|
} catch (error62) {
|
|
40693
41070
|
return toolError(error62);
|
|
40694
41071
|
}
|
|
@@ -40697,6 +41074,7 @@ function registerTools(server, ctx) {
|
|
|
40697
41074
|
server.registerTool(
|
|
40698
41075
|
"manage_domain",
|
|
40699
41076
|
{
|
|
41077
|
+
outputSchema: OUTPUT_SCHEMAS.manage_domain,
|
|
40700
41078
|
title: "Attach/detach custom domain",
|
|
40701
41079
|
description: 'Attach or detach a custom domain the user already owns (paid plans). "attach": connect the domain to the site \u2014 returns the exact DNS records to add; afterwards use check_domain {action: "connect"} for a one-click registrar link and check_domain {action: "status"} to poll until active. "detach": remove the domain from the site. The site keeps its drivethrough.site address throughout.',
|
|
40702
41080
|
// Open-world like check_domain: attach initiates certificate issuance
|
|
@@ -40711,7 +41089,7 @@ function registerTools(server, ctx) {
|
|
|
40711
41089
|
},
|
|
40712
41090
|
async ({
|
|
40713
41091
|
action,
|
|
40714
|
-
site,
|
|
41092
|
+
site: site2,
|
|
40715
41093
|
domain: domain2
|
|
40716
41094
|
}) => {
|
|
40717
41095
|
try {
|
|
@@ -40725,9 +41103,9 @@ function registerTools(server, ctx) {
|
|
|
40725
41103
|
}
|
|
40726
41104
|
});
|
|
40727
41105
|
}
|
|
40728
|
-
return
|
|
41106
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/domain`, { body: { domain: domain2 } }));
|
|
40729
41107
|
}
|
|
40730
|
-
return
|
|
41108
|
+
return ok2(await ctx.api.request("DELETE", `${sitePath(site2)}/domain`));
|
|
40731
41109
|
} catch (error62) {
|
|
40732
41110
|
return toolError(error62);
|
|
40733
41111
|
}
|
|
@@ -40736,6 +41114,7 @@ function registerTools(server, ctx) {
|
|
|
40736
41114
|
server.registerTool(
|
|
40737
41115
|
"restore_site",
|
|
40738
41116
|
{
|
|
41117
|
+
outputSchema: OUTPUT_SCHEMAS.restore_site,
|
|
40739
41118
|
title: "Restore deleted site",
|
|
40740
41119
|
description: "Restore a deleted site within its 30-day recovery window. It comes back exactly as it was \u2014 content, data, visibility \u2014 minus guest access (share links stay revoked; create new ones). If the old name is taken, pass `sitename` to restore under a new name. (Undoing changes to a live site is different \u2014 that's revert_site.)",
|
|
40741
41120
|
// Open-world: a site that was public comes back public, served to visitors.
|
|
@@ -40745,7 +41124,7 @@ function registerTools(server, ctx) {
|
|
|
40745
41124
|
sitename: external_exports.string().optional().describe("Restore under this new name instead (when the old name is taken)")
|
|
40746
41125
|
}
|
|
40747
41126
|
},
|
|
40748
|
-
async ({ site, sitename }) => {
|
|
41127
|
+
async ({ site: site2, sitename }) => {
|
|
40749
41128
|
try {
|
|
40750
41129
|
if (sitename !== void 0 && sitename.length === 0) {
|
|
40751
41130
|
throw new ToolApiError(422, {
|
|
@@ -40756,8 +41135,8 @@ function registerTools(server, ctx) {
|
|
|
40756
41135
|
}
|
|
40757
41136
|
});
|
|
40758
41137
|
}
|
|
40759
|
-
return
|
|
40760
|
-
await ctx.api.request("POST", `${sitePath(
|
|
41138
|
+
return ok2(
|
|
41139
|
+
await ctx.api.request("POST", `${sitePath(site2)}/restore`, {
|
|
40761
41140
|
body: sitename ? { sitename } : {}
|
|
40762
41141
|
})
|
|
40763
41142
|
);
|
|
@@ -40766,8 +41145,90 @@ function registerTools(server, ctx) {
|
|
|
40766
41145
|
}
|
|
40767
41146
|
}
|
|
40768
41147
|
);
|
|
41148
|
+
server.registerTool(
|
|
41149
|
+
"manage_integrations",
|
|
41150
|
+
{
|
|
41151
|
+
outputSchema: OUTPUT_SCHEMAS.manage_integrations,
|
|
41152
|
+
title: "Manage integrations",
|
|
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.`,
|
|
41154
|
+
// Open-world: tests call a third-party API on the user's behalf.
|
|
41155
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
41156
|
+
inputSchema: {
|
|
41157
|
+
site: external_exports.string().describe("Site name or id"),
|
|
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'),
|
|
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."),
|
|
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"),
|
|
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")
|
|
41163
|
+
}
|
|
41164
|
+
},
|
|
41165
|
+
async ({
|
|
41166
|
+
site: site2,
|
|
41167
|
+
action,
|
|
41168
|
+
names,
|
|
41169
|
+
session,
|
|
41170
|
+
target,
|
|
41171
|
+
fields
|
|
41172
|
+
}) => {
|
|
41173
|
+
try {
|
|
41174
|
+
const setMap = async () => {
|
|
41175
|
+
const listed = await ctx.api.request(
|
|
41176
|
+
"GET",
|
|
41177
|
+
`${sitePath(site2)}/secrets`
|
|
41178
|
+
);
|
|
41179
|
+
return listed.secrets;
|
|
41180
|
+
};
|
|
41181
|
+
if (action === "secrets") {
|
|
41182
|
+
if (session !== void 0) {
|
|
41183
|
+
const secrets2 = await setMap();
|
|
41184
|
+
const declared = secrets2.filter((s2) => s2.declared);
|
|
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
|
+
}
|
|
41193
|
+
return ok2({
|
|
41194
|
+
session,
|
|
41195
|
+
set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
|
|
41196
|
+
all_set: unset.length === 0,
|
|
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."
|
|
41198
|
+
});
|
|
41199
|
+
}
|
|
41200
|
+
const link = await ctx.api.request("POST", `${sitePath(site2)}/secrets/link`, {
|
|
41201
|
+
body: names ? { names } : {}
|
|
41202
|
+
});
|
|
41203
|
+
const secrets = await setMap();
|
|
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 });
|
|
41207
|
+
}
|
|
41208
|
+
if (action === "status") {
|
|
41209
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/integrations`));
|
|
41210
|
+
}
|
|
41211
|
+
if (!target) {
|
|
41212
|
+
throw new ToolApiError(422, {
|
|
41213
|
+
error: {
|
|
41214
|
+
code: "target_required",
|
|
41215
|
+
message: 'action "test" needs a target.',
|
|
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.'
|
|
41217
|
+
}
|
|
41218
|
+
});
|
|
41219
|
+
}
|
|
41220
|
+
return ok2(
|
|
41221
|
+
await ctx.api.request("POST", `${sitePath(site2)}/integrations/test`, {
|
|
41222
|
+
body: { target, ...fields ? { fields } : {} }
|
|
41223
|
+
})
|
|
41224
|
+
);
|
|
41225
|
+
} catch (error62) {
|
|
41226
|
+
return toolError(error62);
|
|
41227
|
+
}
|
|
41228
|
+
}
|
|
41229
|
+
);
|
|
40769
41230
|
}
|
|
40770
|
-
var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS;
|
|
41231
|
+
var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS, INTEGRATION_EXAMPLE_A, INTEGRATION_EXAMPLE_B;
|
|
40771
41232
|
var init_tools = __esm({
|
|
40772
41233
|
"../mcp-tools/src/tools.ts"() {
|
|
40773
41234
|
"use strict";
|
|
@@ -40776,6 +41237,7 @@ var init_tools = __esm({
|
|
|
40776
41237
|
init_edits();
|
|
40777
41238
|
init_inline_files();
|
|
40778
41239
|
init_remote_assets();
|
|
41240
|
+
init_output_schemas();
|
|
40779
41241
|
MAX_BUNDLE_MB = 50;
|
|
40780
41242
|
SERVER_INSTRUCTIONS = `Drivethrough hosts websites: deploy, share, collect data. Sites are private by default; guests never need accounts.
|
|
40781
41243
|
|
|
@@ -40847,7 +41309,52 @@ RECIPE \u2014 a searchable, filterable list (catalogue, directory, archive)
|
|
|
40847
41309
|
whose query the config doesn't declare (it will render empty for visitors),
|
|
40848
41310
|
and a declaration no page uses (pure cost on every insert).
|
|
40849
41311
|
|
|
41312
|
+
RECIPE \u2014 call another service's API (stock prices, a smart display, a CRM, weather; Pro plan):
|
|
41313
|
+
No server code. Declare it in drivethrough.json and the platform enforces it:
|
|
41314
|
+
- "secrets": names of the keys the site needs, like ["WEATHER_KEY"]. Values are NEVER typed into the chat \u2014 manage_integrations {action: "secrets"} gives you a one-time link to send the user, who pastes the keys there.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
41320
|
+
|
|
40850
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.`;
|
|
41322
|
+
INTEGRATION_EXAMPLE_A = JSON.stringify({
|
|
41323
|
+
secrets: ["ALPACA_KEY", "ALPACA_SECRET"],
|
|
41324
|
+
routes: {
|
|
41325
|
+
"/api/ext/alpaca": {
|
|
41326
|
+
upstream: "https://paper-api.alpaca.markets",
|
|
41327
|
+
methods: ["GET"],
|
|
41328
|
+
paths: ["/v2/account", "/v2/positions", "/v2/account/portfolio/history"],
|
|
41329
|
+
headers: { "APCA-API-KEY-ID": "${ALPACA_KEY}", "APCA-API-SECRET-KEY": "${ALPACA_SECRET}" },
|
|
41330
|
+
access: "viewers",
|
|
41331
|
+
cache: "30s",
|
|
41332
|
+
query: true
|
|
41333
|
+
}
|
|
41334
|
+
},
|
|
41335
|
+
types: { snapshot: { fields: { equity: "number", cash: "number" } } },
|
|
41336
|
+
schedules: {
|
|
41337
|
+
portfolio: {
|
|
41338
|
+
every: "1h",
|
|
41339
|
+
pull: { route: "/api/ext/alpaca", path: "/v2/account", into: "snapshot", map: { equity: "$.equity", cash: "$.cash" } }
|
|
41340
|
+
}
|
|
41341
|
+
}
|
|
41342
|
+
});
|
|
41343
|
+
INTEGRATION_EXAMPLE_B = JSON.stringify({
|
|
41344
|
+
secrets: ["VESTABOARD_KEY"],
|
|
41345
|
+
actions: {
|
|
41346
|
+
show: {
|
|
41347
|
+
post: "https://rw.vestaboard.com/",
|
|
41348
|
+
headers: { "X-Vestaboard-Read-Write-Key": "${VESTABOARD_KEY}" },
|
|
41349
|
+
body: { text: "{{text}}" }
|
|
41350
|
+
}
|
|
41351
|
+
},
|
|
41352
|
+
types: {
|
|
41353
|
+
quote: { fields: { text: "string" } },
|
|
41354
|
+
activation: { fields: { text: "string" }, on_write: "show" }
|
|
41355
|
+
},
|
|
41356
|
+
schedules: { cycle: { every: "3h", rotate: "quote", action: "show" } }
|
|
41357
|
+
});
|
|
40851
41358
|
}
|
|
40852
41359
|
});
|
|
40853
41360
|
|
|
@@ -40860,6 +41367,7 @@ var init_src2 = __esm({
|
|
|
40860
41367
|
init_inline_files();
|
|
40861
41368
|
init_remote_assets();
|
|
40862
41369
|
init_tools();
|
|
41370
|
+
init_output_schemas();
|
|
40863
41371
|
}
|
|
40864
41372
|
});
|
|
40865
41373
|
|
|
@@ -42788,6 +43296,28 @@ var dD = class extends x2 {
|
|
|
42788
43296
|
};
|
|
42789
43297
|
var A3;
|
|
42790
43298
|
A3 = /* @__PURE__ */ new WeakMap();
|
|
43299
|
+
var TD = Object.defineProperty;
|
|
43300
|
+
var jD2 = (e3, u4, t2) => u4 in e3 ? TD(e3, u4, { enumerable: true, configurable: true, writable: true, value: t2 }) : e3[u4] = t2;
|
|
43301
|
+
var U = (e3, u4, t2) => (jD2(e3, typeof u4 != "symbol" ? u4 + "" : u4, t2), t2);
|
|
43302
|
+
var MD = class extends x2 {
|
|
43303
|
+
constructor({ mask: u4, ...t2 }) {
|
|
43304
|
+
super(t2), U(this, "valueWithCursor", ""), U(this, "_mask", "\u2022"), this._mask = u4 ?? "\u2022", this.on("finalize", () => {
|
|
43305
|
+
this.valueWithCursor = this.masked;
|
|
43306
|
+
}), this.on("value", () => {
|
|
43307
|
+
if (this.cursor >= this.value.length) this.valueWithCursor = `${this.masked}${import_picocolors.default.inverse(import_picocolors.default.hidden("_"))}`;
|
|
43308
|
+
else {
|
|
43309
|
+
const F4 = this.masked.slice(0, this.cursor), s2 = this.masked.slice(this.cursor);
|
|
43310
|
+
this.valueWithCursor = `${F4}${import_picocolors.default.inverse(s2[0])}${s2.slice(1)}`;
|
|
43311
|
+
}
|
|
43312
|
+
});
|
|
43313
|
+
}
|
|
43314
|
+
get cursor() {
|
|
43315
|
+
return this._cursor;
|
|
43316
|
+
}
|
|
43317
|
+
get masked() {
|
|
43318
|
+
return this.value.replaceAll(/./g, this._mask);
|
|
43319
|
+
}
|
|
43320
|
+
};
|
|
42791
43321
|
var RD = class extends x2 {
|
|
42792
43322
|
get valueWithCursor() {
|
|
42793
43323
|
if (this.state === "submit") return this.value;
|
|
@@ -42833,7 +43363,7 @@ var de = u3("\u251C", "+");
|
|
|
42833
43363
|
var pe = u3("\u256F", "+");
|
|
42834
43364
|
var q2 = u3("\u25CF", "\u2022");
|
|
42835
43365
|
var D2 = u3("\u25C6", "*");
|
|
42836
|
-
var
|
|
43366
|
+
var U2 = u3("\u25B2", "!");
|
|
42837
43367
|
var K2 = u3("\u25A0", "x");
|
|
42838
43368
|
var b3 = (t2) => {
|
|
42839
43369
|
switch (t2) {
|
|
@@ -42869,6 +43399,27 @@ ${import_picocolors2.default.cyan(d3)}
|
|
|
42869
43399
|
`;
|
|
42870
43400
|
}
|
|
42871
43401
|
} }).prompt();
|
|
43402
|
+
var ge = (t2) => new MD({ validate: t2.validate, mask: t2.mask ?? $e, render() {
|
|
43403
|
+
const n2 = `${import_picocolors2.default.gray(o3)}
|
|
43404
|
+
${b3(this.state)} ${t2.message}
|
|
43405
|
+
`, r3 = this.valueWithCursor, i2 = this.masked;
|
|
43406
|
+
switch (this.state) {
|
|
43407
|
+
case "error":
|
|
43408
|
+
return `${n2.trim()}
|
|
43409
|
+
${import_picocolors2.default.yellow(o3)} ${i2}
|
|
43410
|
+
${import_picocolors2.default.yellow(d3)} ${import_picocolors2.default.yellow(this.error)}
|
|
43411
|
+
`;
|
|
43412
|
+
case "submit":
|
|
43413
|
+
return `${n2}${import_picocolors2.default.gray(o3)} ${import_picocolors2.default.dim(i2)}`;
|
|
43414
|
+
case "cancel":
|
|
43415
|
+
return `${n2}${import_picocolors2.default.gray(o3)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i2 ?? ""))}${i2 ? `
|
|
43416
|
+
${import_picocolors2.default.gray(o3)}` : ""}`;
|
|
43417
|
+
default:
|
|
43418
|
+
return `${n2}${import_picocolors2.default.cyan(o3)} ${r3}
|
|
43419
|
+
${import_picocolors2.default.cyan(d3)}
|
|
43420
|
+
`;
|
|
43421
|
+
}
|
|
43422
|
+
} }).prompt();
|
|
42872
43423
|
var ye2 = (t2) => {
|
|
42873
43424
|
const n2 = t2.active ?? "Yes", r3 = t2.inactive ?? "No";
|
|
42874
43425
|
return new dD({ active: n2, inactive: r3, initialValue: t2.initialValue ?? true, render() {
|
|
@@ -42930,7 +43481,7 @@ var M2 = { message: (t2 = "", { symbol: n2 = import_picocolors2.default.gray(o3)
|
|
|
42930
43481
|
}, step: (t2) => {
|
|
42931
43482
|
M2.message(t2, { symbol: import_picocolors2.default.green(C4) });
|
|
42932
43483
|
}, warn: (t2) => {
|
|
42933
|
-
M2.message(t2, { symbol: import_picocolors2.default.yellow(
|
|
43484
|
+
M2.message(t2, { symbol: import_picocolors2.default.yellow(U2) });
|
|
42934
43485
|
}, warning: (t2) => {
|
|
42935
43486
|
M2.warn(t2);
|
|
42936
43487
|
}, error: (t2) => {
|
|
@@ -43075,15 +43626,15 @@ var sitesCommand = defineCommand({
|
|
|
43075
43626
|
);
|
|
43076
43627
|
return;
|
|
43077
43628
|
}
|
|
43078
|
-
for (const
|
|
43079
|
-
if (
|
|
43629
|
+
for (const site2 of sites) {
|
|
43630
|
+
if (site2.status === "deleted") {
|
|
43080
43631
|
M2.message(
|
|
43081
|
-
`${
|
|
43632
|
+
`${site2.title} \u2014 ${site2.sitename} (deleted, recoverable until ${site2.purges_after ?? "?"} \u2014 \`drivethrough restore ${site2.sitename}\`)`
|
|
43082
43633
|
);
|
|
43083
43634
|
continue;
|
|
43084
43635
|
}
|
|
43085
|
-
const flags = [
|
|
43086
|
-
M2.message(`${
|
|
43636
|
+
const flags = [site2.visibility, site2.status !== "active" ? site2.status : null].filter(Boolean).join(", ");
|
|
43637
|
+
M2.message(`${site2.title} \u2014 ${site2.local_url} (${flags}, v${site2.current_version})`);
|
|
43087
43638
|
}
|
|
43088
43639
|
});
|
|
43089
43640
|
} catch (error62) {
|
|
@@ -43828,6 +44379,86 @@ var collaboratorsCommand = defineCommand({
|
|
|
43828
44379
|
subCommands: { list: listCmd, add: addCmd }
|
|
43829
44380
|
});
|
|
43830
44381
|
|
|
44382
|
+
// src/commands/secrets.ts
|
|
44383
|
+
init_api();
|
|
44384
|
+
var listCmd2 = defineCommand({
|
|
44385
|
+
meta: { name: "list", description: "List a site's secrets (names and whether each is set \u2014 never values)" },
|
|
44386
|
+
args: {
|
|
44387
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44388
|
+
json: { type: "boolean" }
|
|
44389
|
+
},
|
|
44390
|
+
async run({ args }) {
|
|
44391
|
+
const jsonArgs = { json: args.json };
|
|
44392
|
+
try {
|
|
44393
|
+
const res = await api("GET", `/v1/sites/${encodeURIComponent(args.site)}/secrets`);
|
|
44394
|
+
emit(jsonArgs, { secrets: res.secrets }, () => {
|
|
44395
|
+
if (!res.secrets.length) M2.info("This site declares no secrets.");
|
|
44396
|
+
for (const s2 of res.secrets) {
|
|
44397
|
+
const note = s2.declared ? "" : " (not in drivethrough.json)";
|
|
44398
|
+
M2.message(`${s2.name} ${s2.set ? `set ${s2.updated_at?.slice(0, 10) ?? ""}` : "NOT SET"}${note}`);
|
|
44399
|
+
}
|
|
44400
|
+
});
|
|
44401
|
+
} catch (error62) {
|
|
44402
|
+
fail(jsonArgs, error62);
|
|
44403
|
+
}
|
|
44404
|
+
}
|
|
44405
|
+
});
|
|
44406
|
+
async function readValue(name) {
|
|
44407
|
+
if (!process.stdin.isTTY) {
|
|
44408
|
+
const chunks = [];
|
|
44409
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
44410
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
44411
|
+
}
|
|
44412
|
+
const value = await ge({ message: `Value for ${name}` });
|
|
44413
|
+
if (pD2(value)) process.exit(1);
|
|
44414
|
+
return value;
|
|
44415
|
+
}
|
|
44416
|
+
var setCmd = defineCommand({
|
|
44417
|
+
meta: { name: "set", description: "Set (or replace) a secret \u2014 the value is prompted for, or read from stdin" },
|
|
44418
|
+
args: {
|
|
44419
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44420
|
+
name: { type: "positional", description: "Secret name, e.g. API_KEY", required: true },
|
|
44421
|
+
json: { type: "boolean" }
|
|
44422
|
+
},
|
|
44423
|
+
async run({ args }) {
|
|
44424
|
+
const jsonArgs = { json: args.json };
|
|
44425
|
+
try {
|
|
44426
|
+
const value = await readValue(args.name);
|
|
44427
|
+
await api("PUT", `/v1/sites/${encodeURIComponent(args.site)}/secrets/${encodeURIComponent(args.name)}`, {
|
|
44428
|
+
body: { value }
|
|
44429
|
+
});
|
|
44430
|
+
emit(jsonArgs, { name: args.name, set: true }, () => M2.success(`${args.name} is set on ${args.site}.`));
|
|
44431
|
+
} catch (error62) {
|
|
44432
|
+
fail(jsonArgs, error62);
|
|
44433
|
+
}
|
|
44434
|
+
}
|
|
44435
|
+
});
|
|
44436
|
+
var unsetCmd = defineCommand({
|
|
44437
|
+
meta: { name: "unset", description: "Remove a secret's value" },
|
|
44438
|
+
args: {
|
|
44439
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44440
|
+
name: { type: "positional", description: "Secret name", required: true },
|
|
44441
|
+
json: { type: "boolean" }
|
|
44442
|
+
},
|
|
44443
|
+
async run({ args }) {
|
|
44444
|
+
const jsonArgs = { json: args.json };
|
|
44445
|
+
try {
|
|
44446
|
+
await api("DELETE", `/v1/sites/${encodeURIComponent(args.site)}/secrets/${encodeURIComponent(args.name)}`);
|
|
44447
|
+
emit(
|
|
44448
|
+
jsonArgs,
|
|
44449
|
+
{ name: args.name, set: false },
|
|
44450
|
+
() => M2.success(`${args.name} is unset on ${args.site}. Anything that uses it now fails until it's set again.`)
|
|
44451
|
+
);
|
|
44452
|
+
} catch (error62) {
|
|
44453
|
+
fail(jsonArgs, error62);
|
|
44454
|
+
}
|
|
44455
|
+
}
|
|
44456
|
+
});
|
|
44457
|
+
var secretsCommand = defineCommand({
|
|
44458
|
+
meta: { name: "secrets", description: "API keys a site's integrations use (Pro) \u2014 write-only" },
|
|
44459
|
+
subCommands: { list: listCmd2, set: setCmd, unset: unsetCmd }
|
|
44460
|
+
});
|
|
44461
|
+
|
|
43831
44462
|
// src/commands/usage.ts
|
|
43832
44463
|
init_api();
|
|
43833
44464
|
function human(value, unit) {
|
|
@@ -44403,8 +45034,8 @@ function resolveSitename(root, explicit) {
|
|
|
44403
45034
|
const raw = localConfigRaw(root);
|
|
44404
45035
|
if (raw) {
|
|
44405
45036
|
try {
|
|
44406
|
-
const
|
|
44407
|
-
if (
|
|
45037
|
+
const site2 = JSON.parse(raw).site;
|
|
45038
|
+
if (site2) return site2;
|
|
44408
45039
|
} catch {
|
|
44409
45040
|
}
|
|
44410
45041
|
}
|
|
@@ -44746,6 +45377,7 @@ var main = defineCommand({
|
|
|
44746
45377
|
upgrade: upgradeCommand,
|
|
44747
45378
|
domain: domainCommand,
|
|
44748
45379
|
collaborators: collaboratorsCommand,
|
|
45380
|
+
secrets: secretsCommand,
|
|
44749
45381
|
usage: usageCommand,
|
|
44750
45382
|
mcp: mcpCommand,
|
|
44751
45383
|
"agent-setup": agentSetupCommand
|