drivethrough-cli 0.1.26 → 0.1.28
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 +683 -75
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -21658,6 +21658,67 @@ 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, Headers2, RouteConfig, ActionConfig, PullConfig, ScheduleConfig, SecretsList, RoutesConfig, ActionsConfig, SchedulesConfig, 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
|
+
Headers2 = external_exports.record(external_exports.string(), external_exports.string().max(4096));
|
|
21673
|
+
RouteConfig = external_exports.object({
|
|
21674
|
+
/** Literal https URL: host plus an optional base path. */
|
|
21675
|
+
upstream: external_exports.string().min(1).max(2048),
|
|
21676
|
+
methods: external_exports.array(external_exports.enum(ROUTE_METHODS)).min(1, "methods must list at least one of GET, POST, PUT, PATCH, DELETE"),
|
|
21677
|
+
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),
|
|
21678
|
+
headers: Headers2.optional(),
|
|
21679
|
+
access: external_exports.enum(["owner", "viewers", "public"]).optional(),
|
|
21680
|
+
cache: external_exports.enum(Object.keys(ROUTE_CACHE_SECONDS), {
|
|
21681
|
+
error: 'cache must be one of "0", "30s", "1m", "5m", "15m", "1h"'
|
|
21682
|
+
}).optional(),
|
|
21683
|
+
query: external_exports.boolean().optional()
|
|
21684
|
+
}).strict();
|
|
21685
|
+
ActionConfig = external_exports.object({
|
|
21686
|
+
/** https URL the action POSTs to. `{{field}}` allowed in the path only. */
|
|
21687
|
+
post: external_exports.string().min(1).max(2048),
|
|
21688
|
+
headers: Headers2.optional(),
|
|
21689
|
+
/** A JSON object template: `${NAME}` and `{{field}}` substitute inside string values. */
|
|
21690
|
+
body: external_exports.record(external_exports.string(), external_exports.unknown())
|
|
21691
|
+
}).strict();
|
|
21692
|
+
PullConfig = external_exports.object({
|
|
21693
|
+
route: external_exports.string().min(1),
|
|
21694
|
+
path: external_exports.string().max(512).optional(),
|
|
21695
|
+
into: external_exports.string().min(1),
|
|
21696
|
+
map: external_exports.record(external_exports.string(), external_exports.string())
|
|
21697
|
+
}).strict();
|
|
21698
|
+
ScheduleConfig = external_exports.object({
|
|
21699
|
+
every: external_exports.enum(SCHEDULE_EVERY, {
|
|
21700
|
+
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.'
|
|
21701
|
+
}),
|
|
21702
|
+
action: external_exports.string().optional(),
|
|
21703
|
+
rotate: external_exports.string().optional(),
|
|
21704
|
+
pull: PullConfig.optional()
|
|
21705
|
+
}).strict();
|
|
21706
|
+
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
|
+
RoutesConfig = external_exports.record(external_exports.string(), RouteConfig);
|
|
21708
|
+
ActionsConfig = external_exports.record(external_exports.string(), ActionConfig);
|
|
21709
|
+
SchedulesConfig = external_exports.record(external_exports.string(), ScheduleConfig);
|
|
21710
|
+
HOUR = 36e5;
|
|
21711
|
+
FIXED = {
|
|
21712
|
+
"1h": HOUR,
|
|
21713
|
+
"3h": 3 * HOUR,
|
|
21714
|
+
"6h": 6 * HOUR,
|
|
21715
|
+
"12h": 12 * HOUR,
|
|
21716
|
+
"1d": 24 * HOUR,
|
|
21717
|
+
"1w": 7 * 24 * HOUR
|
|
21718
|
+
};
|
|
21719
|
+
}
|
|
21720
|
+
});
|
|
21721
|
+
|
|
21661
21722
|
// ../schema/src/site-config.ts
|
|
21662
21723
|
function parseSiteConfig(input2) {
|
|
21663
21724
|
const stripped = stripRemovedKeys(input2);
|
|
@@ -21686,6 +21747,7 @@ var init_site_config = __esm({
|
|
|
21686
21747
|
"use strict";
|
|
21687
21748
|
init_zod();
|
|
21688
21749
|
init_query();
|
|
21750
|
+
init_integrations();
|
|
21689
21751
|
FieldSpec = external_exports.enum(["string", "string?", "number", "number?", "boolean", "boolean?"]);
|
|
21690
21752
|
RateLimit = external_exports.string().regex(/^\d+\/(minute|hour|day)$/, 'rate_limit must look like "5/hour"');
|
|
21691
21753
|
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 +21771,13 @@ var init_site_config = __esm({
|
|
|
21709
21771
|
notify: external_exports.enum(["owner-email"]).optional(),
|
|
21710
21772
|
/** Constrains tree shape: a comment may only hang off a page, etc. */
|
|
21711
21773
|
parent_types: external_exports.array(external_exports.string().min(1)).optional(),
|
|
21712
|
-
rate_limit: RateLimit.optional()
|
|
21774
|
+
rate_limit: RateLimit.optional(),
|
|
21775
|
+
/**
|
|
21776
|
+
* An action (declared under "actions") to fire after a row of this type is
|
|
21777
|
+
* inserted or upserted through the page-facing Data API. Owner-API writes,
|
|
21778
|
+
* imports and schedule pulls never fire it.
|
|
21779
|
+
*/
|
|
21780
|
+
on_write: external_exports.string().optional()
|
|
21713
21781
|
}).strict();
|
|
21714
21782
|
SiteConfig = external_exports.object({
|
|
21715
21783
|
/** Sitename: lowercase alphanumeric + hyphens, unique per user. */
|
|
@@ -21728,7 +21796,15 @@ var init_site_config = __esm({
|
|
|
21728
21796
|
*/
|
|
21729
21797
|
query: QueryConfig.default({}),
|
|
21730
21798
|
/** Pro (§3): additive custom-schema SQL applied to the site database. */
|
|
21731
|
-
schema: external_exports.string().max(2e4).optional()
|
|
21799
|
+
schema: external_exports.string().max(2e4).optional(),
|
|
21800
|
+
/**
|
|
21801
|
+
* Declared integrations (Pro) — see integrations.ts. All four are optional
|
|
21802
|
+
* and additive: a bundle without them means exactly what it always did.
|
|
21803
|
+
*/
|
|
21804
|
+
secrets: SecretsList.optional(),
|
|
21805
|
+
routes: RoutesConfig.optional(),
|
|
21806
|
+
actions: ActionsConfig.optional(),
|
|
21807
|
+
schedules: SchedulesConfig.optional()
|
|
21732
21808
|
}).strict();
|
|
21733
21809
|
REMOVED_KEYS = {
|
|
21734
21810
|
indexes: 'The "indexes" key was replaced by "query" \u2014 see query.<type>.fields.'
|
|
@@ -21752,6 +21828,7 @@ var init_src = __esm({
|
|
|
21752
21828
|
init_names();
|
|
21753
21829
|
init_site_config();
|
|
21754
21830
|
init_bundle();
|
|
21831
|
+
init_integrations();
|
|
21755
21832
|
}
|
|
21756
21833
|
});
|
|
21757
21834
|
|
|
@@ -39638,7 +39715,7 @@ var init_inline_files = __esm({
|
|
|
39638
39715
|
}
|
|
39639
39716
|
});
|
|
39640
39717
|
|
|
39641
|
-
// ../
|
|
39718
|
+
// ../shared/src/outbound.ts
|
|
39642
39719
|
function isPrivateIpv4(host) {
|
|
39643
39720
|
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
39644
39721
|
if (!match) return false;
|
|
@@ -39667,7 +39744,7 @@ async function resolveViaDoh(hostname3) {
|
|
|
39667
39744
|
for (const type of ["A", "AAAA"]) {
|
|
39668
39745
|
const res = await fetch(
|
|
39669
39746
|
`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(hostname3)}&type=${type}`,
|
|
39670
|
-
{ headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(
|
|
39747
|
+
{ headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(DNS_TIMEOUT_MS) }
|
|
39671
39748
|
);
|
|
39672
39749
|
if (!res.ok) throw new Error(`DNS lookup returned HTTP ${res.status}`);
|
|
39673
39750
|
const body = await res.json();
|
|
@@ -39691,6 +39768,20 @@ async function resolutionProblem(hostname3) {
|
|
|
39691
39768
|
if (blocked) return `it resolves to a private or reserved address (${blocked})`;
|
|
39692
39769
|
return null;
|
|
39693
39770
|
}
|
|
39771
|
+
var BLOCKED_HOST_SUFFIXES, DNS_TIMEOUT_MS, resolveAddresses, IP_LITERAL, UPSTREAM_MAX_BODY, UPSTREAM_MAX_RESPONSE;
|
|
39772
|
+
var init_outbound = __esm({
|
|
39773
|
+
"../shared/src/outbound.ts"() {
|
|
39774
|
+
"use strict";
|
|
39775
|
+
BLOCKED_HOST_SUFFIXES = [".localhost", ".local", ".internal", ".home.arpa"];
|
|
39776
|
+
DNS_TIMEOUT_MS = 2e4;
|
|
39777
|
+
resolveAddresses = resolveViaDoh;
|
|
39778
|
+
IP_LITERAL = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
39779
|
+
UPSTREAM_MAX_BODY = 64 * 1024;
|
|
39780
|
+
UPSTREAM_MAX_RESPONSE = 1024 * 1024;
|
|
39781
|
+
}
|
|
39782
|
+
});
|
|
39783
|
+
|
|
39784
|
+
// ../mcp-tools/src/remote-assets.ts
|
|
39694
39785
|
function urlProblem(url2, why) {
|
|
39695
39786
|
return {
|
|
39696
39787
|
code: "invalid_source_url",
|
|
@@ -39893,24 +39984,278 @@ async function resolveDeployFiles(files, opts = {}) {
|
|
|
39893
39984
|
}
|
|
39894
39985
|
return { ok: true, files: out };
|
|
39895
39986
|
}
|
|
39896
|
-
var MAX_REMOTE_FILES, MAX_REMOTE_FILE_MB, MAX_BUNDLE_TOTAL_MB, MAX_REDIRECTS, FETCH_TIMEOUT_MS
|
|
39987
|
+
var MAX_REMOTE_FILES, MAX_REMOTE_FILE_MB, MAX_BUNDLE_TOTAL_MB, MAX_REDIRECTS, FETCH_TIMEOUT_MS;
|
|
39897
39988
|
var init_remote_assets = __esm({
|
|
39898
39989
|
"../mcp-tools/src/remote-assets.ts"() {
|
|
39899
39990
|
"use strict";
|
|
39991
|
+
init_outbound();
|
|
39992
|
+
init_outbound();
|
|
39900
39993
|
MAX_REMOTE_FILES = 20;
|
|
39901
39994
|
MAX_REMOTE_FILE_MB = 10;
|
|
39902
39995
|
MAX_BUNDLE_TOTAL_MB = 50;
|
|
39903
39996
|
MAX_REDIRECTS = 3;
|
|
39904
39997
|
FETCH_TIMEOUT_MS = 2e4;
|
|
39905
|
-
|
|
39906
|
-
|
|
39907
|
-
|
|
39998
|
+
}
|
|
39999
|
+
});
|
|
40000
|
+
|
|
40001
|
+
// ../mcp-tools/src/output-schemas.ts
|
|
40002
|
+
var ok, site, deployResult, dataDoc, dnsRecord, fileEntry, OUTPUT_SCHEMAS;
|
|
40003
|
+
var init_output_schemas = __esm({
|
|
40004
|
+
"../mcp-tools/src/output-schemas.ts"() {
|
|
40005
|
+
"use strict";
|
|
40006
|
+
init_zod();
|
|
40007
|
+
ok = external_exports.literal(true).describe("Always true on success; failures come back as an error result instead");
|
|
40008
|
+
site = external_exports.looseObject({
|
|
40009
|
+
id: external_exports.string().optional().describe("Stable site id \u2014 accepted anywhere a site name is"),
|
|
40010
|
+
sitename: external_exports.string().optional().describe("The site's name within the account"),
|
|
40011
|
+
hostname: external_exports.string().optional().describe("DNS label on the platform domain ({username}-{sitename})"),
|
|
40012
|
+
title: external_exports.string().nullish().describe("Human title shown to guests"),
|
|
40013
|
+
visibility: external_exports.string().optional().describe('"public" or "private"'),
|
|
40014
|
+
status: external_exports.string().optional().describe('"active", "suspended" or "deleted"'),
|
|
40015
|
+
custom_domain: external_exports.string().nullish().describe("Attached custom domain, if any"),
|
|
40016
|
+
domain_status: external_exports.string().optional().describe("Custom-domain verification state, when one is attached"),
|
|
40017
|
+
current_version: external_exports.number().optional().describe("The live version number (0 = never deployed)"),
|
|
40018
|
+
url: external_exports.string().optional().describe("Public address of the site \u2014 give this to the user"),
|
|
40019
|
+
local_url: external_exports.string().optional().describe("Local-dev address of the site"),
|
|
40020
|
+
created_at: external_exports.string().optional(),
|
|
40021
|
+
deleted_at: external_exports.string().nullish().describe("Deleted sites only"),
|
|
40022
|
+
purges_after: external_exports.string().nullish().describe("Deleted sites only: when it is destroyed for good")
|
|
40023
|
+
}).describe("The site");
|
|
40024
|
+
deployResult = external_exports.looseObject({
|
|
40025
|
+
site_id: external_exports.string().optional(),
|
|
40026
|
+
hostname: external_exports.string().optional(),
|
|
40027
|
+
version: external_exports.number().describe("The new version number \u2014 a restore point for revert_site, and the base_version for the next deploy"),
|
|
40028
|
+
files: external_exports.number().optional().describe("Files in the deployed version"),
|
|
40029
|
+
total_bytes: external_exports.number().optional(),
|
|
40030
|
+
bundle_hash: external_exports.string().optional().describe("Content hash of the deployed bundle"),
|
|
40031
|
+
url: external_exports.string().optional().describe("Where the site is served"),
|
|
40032
|
+
local_url: external_exports.string().optional(),
|
|
40033
|
+
scan: external_exports.looseObject({
|
|
40034
|
+
score: external_exports.number().optional(),
|
|
40035
|
+
verdict: external_exports.string().optional().describe('"clean", or "suspended" when the abuse scan took the site down')
|
|
40036
|
+
}).optional(),
|
|
40037
|
+
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"),
|
|
40038
|
+
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")
|
|
40039
|
+
});
|
|
40040
|
+
dataDoc = external_exports.looseObject({
|
|
40041
|
+
seq: external_exports.number().describe("Row number \u2014 the row's permanent identity"),
|
|
40042
|
+
type: external_exports.string(),
|
|
40043
|
+
parent_seq: external_exports.number().nullish(),
|
|
40044
|
+
json: external_exports.record(external_exports.string(), external_exports.unknown()).describe("The row's document"),
|
|
40045
|
+
created_at: external_exports.string().optional(),
|
|
40046
|
+
updated_at: external_exports.string().optional()
|
|
40047
|
+
});
|
|
40048
|
+
dnsRecord = external_exports.looseObject({
|
|
40049
|
+
type: external_exports.string(),
|
|
40050
|
+
name: external_exports.string(),
|
|
40051
|
+
value: external_exports.string(),
|
|
40052
|
+
note: external_exports.string().optional()
|
|
40053
|
+
});
|
|
40054
|
+
fileEntry = external_exports.looseObject({
|
|
40055
|
+
path: external_exports.string(),
|
|
40056
|
+
size: external_exports.number().optional(),
|
|
40057
|
+
content_type: external_exports.string().optional(),
|
|
40058
|
+
sha256: external_exports.string().optional(),
|
|
40059
|
+
content: external_exports.string().optional().describe("Inlined contents (text, or base64 when named in paths)"),
|
|
40060
|
+
encoding: external_exports.string().optional().describe('"utf8" or "base64"'),
|
|
40061
|
+
content_omitted: external_exports.string().optional().describe('Why content is absent: "binary", "too_large" or "budget"'),
|
|
40062
|
+
status: external_exports.string().optional().describe('compare only: "added", "removed" or "changed"'),
|
|
40063
|
+
diff: external_exports.string().optional().describe("compare only: unified line diff of a changed text file"),
|
|
40064
|
+
diff_omitted: external_exports.string().optional()
|
|
40065
|
+
});
|
|
40066
|
+
OUTPUT_SCHEMAS = {
|
|
40067
|
+
deploy_site: external_exports.looseObject({
|
|
40068
|
+
ok,
|
|
40069
|
+
site,
|
|
40070
|
+
deploy: deployResult.describe("The deploy itself"),
|
|
40071
|
+
created: external_exports.boolean().optional().describe("True when this call created the site")
|
|
40072
|
+
}),
|
|
40073
|
+
create_upload: external_exports.looseObject({
|
|
40074
|
+
ok,
|
|
40075
|
+
session: external_exports.string().optional().describe("Upload session id \u2014 pass it back to list what the user uploaded"),
|
|
40076
|
+
upload_url: external_exports.string().optional().describe("Mint: the drag-and-drop page to send the user as a clickable link"),
|
|
40077
|
+
put_url: external_exports.string().optional().describe("Mint: PUT target for bytes you already hold ({filename} = last path segment)"),
|
|
40078
|
+
expires_in: external_exports.number().optional().describe("Mint: seconds until the link expires"),
|
|
40079
|
+
max_mb: external_exports.number().optional(),
|
|
40080
|
+
max_files: external_exports.number().optional(),
|
|
40081
|
+
uploads: external_exports.array(
|
|
40082
|
+
external_exports.looseObject({
|
|
40083
|
+
upload_id: external_exports.string().describe("Reference this in deploy_site {path, upload_id}"),
|
|
40084
|
+
filename: external_exports.string().optional(),
|
|
40085
|
+
size_bytes: external_exports.number().optional()
|
|
40086
|
+
})
|
|
40087
|
+
).optional().describe("List (with session): the files staged so far"),
|
|
40088
|
+
next: external_exports.string().optional().describe("What to do next")
|
|
40089
|
+
}).describe("Mint returns the link fields; a call with `session` returns `uploads`"),
|
|
40090
|
+
edit_site: external_exports.looseObject({
|
|
40091
|
+
ok,
|
|
40092
|
+
site: external_exports.looseObject({ id: external_exports.string(), sitename: external_exports.string().optional() }),
|
|
40093
|
+
deploy: deployResult.describe("The patch deploy the edits produced"),
|
|
40094
|
+
files_edited: external_exports.number().optional(),
|
|
40095
|
+
replacements: external_exports.number().optional().describe("Total occurrences replaced across all edits")
|
|
40096
|
+
}),
|
|
40097
|
+
get_site: external_exports.looseObject({
|
|
40098
|
+
ok,
|
|
40099
|
+
site_id: external_exports.string().optional(),
|
|
40100
|
+
sitename: external_exports.string().optional(),
|
|
40101
|
+
version: external_exports.number().optional().describe("The version these files or matches were read from"),
|
|
40102
|
+
current_version: external_exports.number().optional().describe("What is actually live (history, or a pinned-version read)"),
|
|
40103
|
+
total_bytes: external_exports.number().optional(),
|
|
40104
|
+
bundle_hash: external_exports.string().optional(),
|
|
40105
|
+
config: external_exports.unknown().optional().describe("The deployed drivethrough.json"),
|
|
40106
|
+
files: external_exports.array(fileEntry).optional().describe("File reads: entries in deploy_site {path, content, encoding} form. compare: per-file diffs"),
|
|
40107
|
+
find: external_exports.string().optional(),
|
|
40108
|
+
files_searched: external_exports.number().optional(),
|
|
40109
|
+
matches: external_exports.array(
|
|
40110
|
+
external_exports.looseObject({
|
|
40111
|
+
path: external_exports.string(),
|
|
40112
|
+
line: external_exports.number(),
|
|
40113
|
+
text: external_exports.string().describe("The byte-exact deployed line \u2014 usable as edit_site `find` context"),
|
|
40114
|
+
occurrences: external_exports.number().optional()
|
|
40115
|
+
})
|
|
40116
|
+
).optional().describe("find: where the text occurs"),
|
|
40117
|
+
truncated: external_exports.boolean().optional(),
|
|
40118
|
+
versions: external_exports.array(
|
|
40119
|
+
external_exports.looseObject({
|
|
40120
|
+
version: external_exports.number(),
|
|
40121
|
+
created_at: external_exports.string().optional(),
|
|
40122
|
+
files: external_exports.number().optional(),
|
|
40123
|
+
total_bytes: external_exports.number().optional(),
|
|
40124
|
+
current: external_exports.boolean().optional(),
|
|
40125
|
+
changes: external_exports.unknown().optional().describe('{added, changed, removed} paths vs the version before, or "initial"')
|
|
40126
|
+
})
|
|
40127
|
+
).optional().describe("history: newest first \u2014 the map for revert_site"),
|
|
40128
|
+
from: external_exports.number().optional().describe("compare: older version"),
|
|
40129
|
+
to: external_exports.number().optional().describe("compare: newer version"),
|
|
40130
|
+
note: external_exports.string().optional()
|
|
40131
|
+
}).describe("Shape depends on the mode: file reads (files, config), find (matches), history (versions), compare (from, to, files)"),
|
|
40132
|
+
preview_site: external_exports.looseObject({
|
|
40133
|
+
ok,
|
|
40134
|
+
site_id: external_exports.string().optional(),
|
|
40135
|
+
sitename: external_exports.string().optional(),
|
|
40136
|
+
version: external_exports.number().optional().describe("The version that was rendered"),
|
|
40137
|
+
current_version: external_exports.number().optional(),
|
|
40138
|
+
path: external_exports.string().optional(),
|
|
40139
|
+
viewport: external_exports.looseObject({ width: external_exports.number(), height: external_exports.number() }).optional(),
|
|
40140
|
+
full_page: external_exports.boolean().optional(),
|
|
40141
|
+
renderer: external_exports.string().optional(),
|
|
40142
|
+
note: external_exports.string().optional()
|
|
40143
|
+
}).describe("Metadata about the screenshot; the image itself is the result's image content block"),
|
|
40144
|
+
revert_site: deployResult.extend({
|
|
40145
|
+
ok,
|
|
40146
|
+
reverted_to: external_exports.number().optional().describe("The version whose content was restored"),
|
|
40147
|
+
byte_identical: external_exports.boolean().optional().describe("True proves the new version is byte-for-byte the target version")
|
|
40148
|
+
}).describe("A revert is a new deploy: the new version number plus the proof of restoration"),
|
|
40149
|
+
create_db: external_exports.looseObject({
|
|
40150
|
+
ok,
|
|
40151
|
+
site_id: external_exports.string().optional(),
|
|
40152
|
+
d1_database_id: external_exports.string().optional(),
|
|
40153
|
+
created: external_exports.boolean().optional().describe("False when the database already existed")
|
|
40154
|
+
}),
|
|
40155
|
+
query_db: external_exports.looseObject({
|
|
40156
|
+
ok,
|
|
40157
|
+
types: external_exports.array(external_exports.looseObject({ type: external_exports.string(), count: external_exports.number() })).optional().describe("types: row count per type"),
|
|
40158
|
+
items: external_exports.array(dataDoc).optional().describe("list/get: the rows"),
|
|
40159
|
+
next_cursor: external_exports.string().nullish().describe("list: pass back as `cursor` for the next page; null at the end"),
|
|
40160
|
+
count: external_exports.number().optional(),
|
|
40161
|
+
count_capped: external_exports.boolean().optional(),
|
|
40162
|
+
partial: external_exports.boolean().optional().describe("Owner scan of an undeclared query stopped at its row cap"),
|
|
40163
|
+
declare: external_exports.string().optional().describe("The query declaration that would make this list complete and cheap"),
|
|
40164
|
+
search_status: external_exports.string().optional()
|
|
40165
|
+
}).describe('"types" returns `types`; "list" and "get" return `items`'),
|
|
40166
|
+
write_db: external_exports.looseObject({
|
|
40167
|
+
ok,
|
|
40168
|
+
item: dataDoc.optional().describe("insert/update: the row as stored"),
|
|
40169
|
+
inserted: external_exports.number().optional().describe("Bulk insert: rows written"),
|
|
40170
|
+
writes_charged: external_exports.number().optional(),
|
|
40171
|
+
first_seq: external_exports.number().optional(),
|
|
40172
|
+
last_seq: external_exports.number().optional(),
|
|
40173
|
+
deleted: external_exports.boolean().optional(),
|
|
40174
|
+
seq: external_exports.number().optional().describe("delete: the row removed")
|
|
40175
|
+
}).describe("Single insert/update \u2192 item; bulk insert \u2192 counts and seq range; delete \u2192 {deleted, seq}"),
|
|
40176
|
+
set_privacy: external_exports.looseObject({ ok, site }),
|
|
40177
|
+
share_site: external_exports.looseObject({
|
|
40178
|
+
ok,
|
|
40179
|
+
share_link: external_exports.looseObject({
|
|
40180
|
+
id: external_exports.string().optional(),
|
|
40181
|
+
url: external_exports.string().describe("The link to send \u2014 anyone holding it can view"),
|
|
40182
|
+
local_url: external_exports.string().optional(),
|
|
40183
|
+
expires_at: external_exports.string().nullish().describe("null = never expires"),
|
|
40184
|
+
revoked: external_exports.boolean().optional(),
|
|
40185
|
+
created_at: external_exports.string().optional()
|
|
40186
|
+
}).optional(),
|
|
40187
|
+
sent_to: external_exports.string().optional().describe("Invitation: the address the magic link went to")
|
|
40188
|
+
}).describe("A share link, or (with email) confirmation the invitation was sent"),
|
|
40189
|
+
list_sites: external_exports.looseObject({ ok, sites: external_exports.array(site) }),
|
|
40190
|
+
delete_site: external_exports.looseObject({
|
|
40191
|
+
ok,
|
|
40192
|
+
deleted: external_exports.boolean().optional(),
|
|
40193
|
+
site: site.optional(),
|
|
40194
|
+
recoverable_until: external_exports.string().nullish().describe("Restore with restore_site before this date"),
|
|
40195
|
+
message: external_exports.string().optional()
|
|
40196
|
+
}),
|
|
40197
|
+
check_domain: external_exports.looseObject({
|
|
40198
|
+
ok,
|
|
40199
|
+
domain: external_exports.string().optional(),
|
|
40200
|
+
status: external_exports.string().optional().describe("status: pending_dns \u2192 pending_ssl \u2192 active"),
|
|
40201
|
+
target: external_exports.string().optional(),
|
|
40202
|
+
records: external_exports.array(dnsRecord).optional().describe("The DNS records to create, exact and paste-ready"),
|
|
40203
|
+
hostnames: external_exports.array(external_exports.unknown()).optional(),
|
|
40204
|
+
supported: external_exports.boolean().optional().describe("connect: whether the registrar supports one-click setup"),
|
|
40205
|
+
provider: external_exports.string().optional(),
|
|
40206
|
+
url: external_exports.string().optional().describe("connect: the one-click registrar link to relay"),
|
|
40207
|
+
next: external_exports.string().optional()
|
|
40208
|
+
}).describe('"status" \u2192 verification progress; "connect" \u2192 one-click setup link or manual records'),
|
|
40209
|
+
manage_domain: external_exports.looseObject({
|
|
40210
|
+
ok,
|
|
40211
|
+
domain: external_exports.string().optional(),
|
|
40212
|
+
status: external_exports.string().optional(),
|
|
40213
|
+
target: external_exports.string().optional(),
|
|
40214
|
+
records: external_exports.array(dnsRecord).optional().describe("attach: the DNS records the user must add"),
|
|
40215
|
+
next: external_exports.string().optional(),
|
|
40216
|
+
detached: external_exports.string().optional().describe("detach: the domain removed")
|
|
40217
|
+
}).describe("attach \u2192 DNS records to add; detach \u2192 the removed domain"),
|
|
40218
|
+
restore_site: external_exports.looseObject({
|
|
40219
|
+
ok,
|
|
40220
|
+
restored: external_exports.boolean().optional(),
|
|
40221
|
+
site: site.optional(),
|
|
40222
|
+
renamed: external_exports.boolean().optional().describe("True when restored under a new name"),
|
|
40223
|
+
domain_lost: external_exports.string().nullish().describe("A custom domain that could not be re-attached"),
|
|
40224
|
+
message: external_exports.string().optional()
|
|
40225
|
+
}),
|
|
40226
|
+
manage_integrations: external_exports.looseObject({
|
|
40227
|
+
ok,
|
|
40228
|
+
// secrets
|
|
40229
|
+
url: external_exports.string().optional().describe("secrets: the one-time link to relay to the user (they paste keys there, never in chat)"),
|
|
40230
|
+
session: external_exports.string().optional().describe(`secrets: pass back with action "secrets" after the user says they're done`),
|
|
40231
|
+
expires_at: external_exports.string().optional(),
|
|
40232
|
+
names: external_exports.array(external_exports.string()).optional().describe("secrets: the names the link asks for"),
|
|
40233
|
+
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."),
|
|
40234
|
+
all_set: external_exports.boolean().optional().describe("secrets (with session): true once every declared secret is set"),
|
|
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(),
|
|
40249
|
+
next: external_exports.string().optional()
|
|
40250
|
+
}).describe('"secrets" \u2192 a link or the set/unset map; "status" \u2192 everything declared plus recent outcomes; "test" \u2192 one live run')
|
|
40251
|
+
};
|
|
39908
40252
|
}
|
|
39909
40253
|
});
|
|
39910
40254
|
|
|
39911
40255
|
// ../mcp-tools/src/tools.ts
|
|
39912
|
-
function
|
|
39913
|
-
|
|
40256
|
+
function ok2(data) {
|
|
40257
|
+
const structured = { ok: true, ...data };
|
|
40258
|
+
return { content: [{ type: "text", text: JSON.stringify(structured) }], structuredContent: structured };
|
|
39914
40259
|
}
|
|
39915
40260
|
function toolError(error62) {
|
|
39916
40261
|
const body = apiErrorBody(error62) ?? {
|
|
@@ -39921,8 +40266,8 @@ function toolError(error62) {
|
|
|
39921
40266
|
function problemError(problem) {
|
|
39922
40267
|
return new ToolApiError(422, { error: problem });
|
|
39923
40268
|
}
|
|
39924
|
-
function sitePath(
|
|
39925
|
-
return `/v1/sites/${encodeURIComponent(
|
|
40269
|
+
function sitePath(site2) {
|
|
40270
|
+
return `/v1/sites/${encodeURIComponent(site2)}`;
|
|
39926
40271
|
}
|
|
39927
40272
|
function registerTools(server, ctx) {
|
|
39928
40273
|
const canDeployDirectory = Boolean(ctx.localDirectories);
|
|
@@ -39931,6 +40276,7 @@ function registerTools(server, ctx) {
|
|
|
39931
40276
|
server.registerTool(
|
|
39932
40277
|
"deploy_site",
|
|
39933
40278
|
{
|
|
40279
|
+
outputSchema: OUTPUT_SCHEMAS.deploy_site,
|
|
39934
40280
|
title: "Deploy site",
|
|
39935
40281
|
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
40282
|
// Destructive: the default "replace" mode drops every deployed file the
|
|
@@ -39977,7 +40323,7 @@ function registerTools(server, ctx) {
|
|
|
39977
40323
|
async ({
|
|
39978
40324
|
directory,
|
|
39979
40325
|
files,
|
|
39980
|
-
site,
|
|
40326
|
+
site: site2,
|
|
39981
40327
|
title,
|
|
39982
40328
|
mode,
|
|
39983
40329
|
delete_paths,
|
|
@@ -40053,7 +40399,7 @@ function registerTools(server, ctx) {
|
|
|
40053
40399
|
}
|
|
40054
40400
|
});
|
|
40055
40401
|
}
|
|
40056
|
-
const sitename =
|
|
40402
|
+
const sitename = site2 ?? config2?.site ?? fallbackName;
|
|
40057
40403
|
if (!sitename) {
|
|
40058
40404
|
throw new ToolApiError(422, {
|
|
40059
40405
|
error: {
|
|
@@ -40081,7 +40427,7 @@ function registerTools(server, ctx) {
|
|
|
40081
40427
|
`${sitePath(siteRes.site.id)}/deployments`,
|
|
40082
40428
|
{ form }
|
|
40083
40429
|
);
|
|
40084
|
-
return
|
|
40430
|
+
return ok2({ site: siteRes.site, deploy, created: siteRes.created });
|
|
40085
40431
|
} catch (error62) {
|
|
40086
40432
|
return toolError(error62);
|
|
40087
40433
|
}
|
|
@@ -40090,6 +40436,7 @@ function registerTools(server, ctx) {
|
|
|
40090
40436
|
server.registerTool(
|
|
40091
40437
|
"create_upload",
|
|
40092
40438
|
{
|
|
40439
|
+
outputSchema: OUTPUT_SCHEMAS.create_upload,
|
|
40093
40440
|
title: "Create upload link",
|
|
40094
40441
|
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
40442
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
@@ -40103,13 +40450,13 @@ function registerTools(server, ctx) {
|
|
|
40103
40450
|
)
|
|
40104
40451
|
}
|
|
40105
40452
|
},
|
|
40106
|
-
async ({ site, session, size_bytes }) => {
|
|
40453
|
+
async ({ site: site2, session, size_bytes }) => {
|
|
40107
40454
|
try {
|
|
40108
40455
|
if (session !== void 0) {
|
|
40109
|
-
return
|
|
40456
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/uploads/${encodeURIComponent(session)}`));
|
|
40110
40457
|
}
|
|
40111
|
-
return
|
|
40112
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40458
|
+
return ok2(
|
|
40459
|
+
await ctx.api.request("POST", `${sitePath(site2)}/uploads`, {
|
|
40113
40460
|
body: { ...size_bytes !== void 0 ? { size_bytes } : {} }
|
|
40114
40461
|
})
|
|
40115
40462
|
);
|
|
@@ -40121,6 +40468,7 @@ function registerTools(server, ctx) {
|
|
|
40121
40468
|
server.registerTool(
|
|
40122
40469
|
"edit_site",
|
|
40123
40470
|
{
|
|
40471
|
+
outputSchema: OUTPUT_SCHEMAS.edit_site,
|
|
40124
40472
|
title: "Edit site files",
|
|
40125
40473
|
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
40474
|
// Destructive: find/replace overwrites text that is currently live.
|
|
@@ -40138,7 +40486,7 @@ function registerTools(server, ctx) {
|
|
|
40138
40486
|
).min(1).describe("Edits applied in order; later edits see earlier results")
|
|
40139
40487
|
}
|
|
40140
40488
|
},
|
|
40141
|
-
async ({ site, edits }) => {
|
|
40489
|
+
async ({ site: site2, edits }) => {
|
|
40142
40490
|
try {
|
|
40143
40491
|
const normalized = edits.map((e3) => ({ ...e3, path: e3.path.startsWith("/") ? e3.path : `/${e3.path}` }));
|
|
40144
40492
|
for (const edit of normalized) {
|
|
@@ -40154,7 +40502,7 @@ function registerTools(server, ctx) {
|
|
|
40154
40502
|
}
|
|
40155
40503
|
const paths = [...new Set(normalized.map((e3) => e3.path))];
|
|
40156
40504
|
const query = paths.map((p2) => `path=${encodeURIComponent(p2)}`).join("&");
|
|
40157
|
-
const read = await ctx.api.request("GET", `${sitePath(
|
|
40505
|
+
const read = await ctx.api.request("GET", `${sitePath(site2)}/files?${query}`);
|
|
40158
40506
|
const texts = /* @__PURE__ */ new Map();
|
|
40159
40507
|
for (const path8 of paths) {
|
|
40160
40508
|
const file2 = read.files.find((f3) => f3.path === path8);
|
|
@@ -40198,7 +40546,7 @@ function registerTools(server, ctx) {
|
|
|
40198
40546
|
`${sitePath(read.site_id)}/deployments`,
|
|
40199
40547
|
{ form: assembled.bundle.form }
|
|
40200
40548
|
);
|
|
40201
|
-
return
|
|
40549
|
+
return ok2({
|
|
40202
40550
|
site: { id: read.site_id, sitename: read.sitename },
|
|
40203
40551
|
deploy,
|
|
40204
40552
|
files_edited: paths.length,
|
|
@@ -40212,6 +40560,7 @@ function registerTools(server, ctx) {
|
|
|
40212
40560
|
server.registerTool(
|
|
40213
40561
|
"get_site",
|
|
40214
40562
|
{
|
|
40563
|
+
outputSchema: OUTPUT_SCHEMAS.get_site,
|
|
40215
40564
|
title: "Read site files & history",
|
|
40216
40565
|
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
40566
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40239,7 +40588,7 @@ function registerTools(server, ctx) {
|
|
|
40239
40588
|
}
|
|
40240
40589
|
},
|
|
40241
40590
|
async ({
|
|
40242
|
-
site,
|
|
40591
|
+
site: site2,
|
|
40243
40592
|
paths,
|
|
40244
40593
|
find,
|
|
40245
40594
|
manifest_only,
|
|
@@ -40249,7 +40598,7 @@ function registerTools(server, ctx) {
|
|
|
40249
40598
|
}) => {
|
|
40250
40599
|
try {
|
|
40251
40600
|
if (compare) {
|
|
40252
|
-
return
|
|
40601
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/diff?from=${compare.from}&to=${compare.to}`));
|
|
40253
40602
|
}
|
|
40254
40603
|
if (history !== void 0 && history !== false) {
|
|
40255
40604
|
if (typeof history === "number" && history < 1) {
|
|
@@ -40262,7 +40611,7 @@ function registerTools(server, ctx) {
|
|
|
40262
40611
|
});
|
|
40263
40612
|
}
|
|
40264
40613
|
const before = typeof history === "number" ? `?before=${history}` : "";
|
|
40265
|
-
return
|
|
40614
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/versions${before}`));
|
|
40266
40615
|
}
|
|
40267
40616
|
if (find !== void 0 && find.length === 0) {
|
|
40268
40617
|
throw new ToolApiError(422, {
|
|
@@ -40279,7 +40628,7 @@ function registerTools(server, ctx) {
|
|
|
40279
40628
|
else if (manifest_only) params.set("manifest_only", "1");
|
|
40280
40629
|
if (version2 !== void 0) params.set("version", String(version2));
|
|
40281
40630
|
const query = params.toString();
|
|
40282
|
-
return
|
|
40631
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/files${query ? `?${query}` : ""}`));
|
|
40283
40632
|
} catch (error62) {
|
|
40284
40633
|
return toolError(error62);
|
|
40285
40634
|
}
|
|
@@ -40288,9 +40637,12 @@ function registerTools(server, ctx) {
|
|
|
40288
40637
|
server.registerTool(
|
|
40289
40638
|
"preview_site",
|
|
40290
40639
|
{
|
|
40640
|
+
outputSchema: OUTPUT_SCHEMAS.preview_site,
|
|
40291
40641
|
title: "Preview site (screenshot)",
|
|
40292
40642
|
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
|
-
|
|
40643
|
+
// Open-world: a real browser loads the page, and with it whatever the
|
|
40644
|
+
// page pulls in from elsewhere (fonts, scripts, images, embeds).
|
|
40645
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
40294
40646
|
inputSchema: {
|
|
40295
40647
|
site: external_exports.string().describe("Site name or id"),
|
|
40296
40648
|
path: external_exports.string().optional().describe('Page to render, like "/about.html" (default "/")'),
|
|
@@ -40301,7 +40653,7 @@ function registerTools(server, ctx) {
|
|
|
40301
40653
|
}
|
|
40302
40654
|
},
|
|
40303
40655
|
async ({
|
|
40304
|
-
site,
|
|
40656
|
+
site: site2,
|
|
40305
40657
|
path: path8,
|
|
40306
40658
|
version: version2,
|
|
40307
40659
|
viewport,
|
|
@@ -40309,7 +40661,7 @@ function registerTools(server, ctx) {
|
|
|
40309
40661
|
full_page
|
|
40310
40662
|
}) => {
|
|
40311
40663
|
try {
|
|
40312
|
-
const { image_base64, content_type, ...meta3 } = await ctx.api.request("POST", `${sitePath(
|
|
40664
|
+
const { image_base64, content_type, ...meta3 } = await ctx.api.request("POST", `${sitePath(site2)}/preview`, {
|
|
40313
40665
|
body: {
|
|
40314
40666
|
...path8 !== void 0 ? { path: path8 } : {},
|
|
40315
40667
|
...version2 !== void 0 ? { version: version2 } : {},
|
|
@@ -40318,11 +40670,13 @@ function registerTools(server, ctx) {
|
|
|
40318
40670
|
...full_page !== void 0 ? { full_page } : {}
|
|
40319
40671
|
}
|
|
40320
40672
|
});
|
|
40673
|
+
const structured = { ok: true, ...meta3 };
|
|
40321
40674
|
return {
|
|
40322
40675
|
content: [
|
|
40323
40676
|
{ type: "image", data: image_base64, mimeType: content_type },
|
|
40324
|
-
{ type: "text", text: JSON.stringify(
|
|
40325
|
-
]
|
|
40677
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
40678
|
+
],
|
|
40679
|
+
structuredContent: structured
|
|
40326
40680
|
};
|
|
40327
40681
|
} catch (error62) {
|
|
40328
40682
|
return toolError(error62);
|
|
@@ -40332,6 +40686,7 @@ function registerTools(server, ctx) {
|
|
|
40332
40686
|
server.registerTool(
|
|
40333
40687
|
"revert_site",
|
|
40334
40688
|
{
|
|
40689
|
+
outputSchema: OUTPUT_SCHEMAS.revert_site,
|
|
40335
40690
|
title: "Revert site to a version",
|
|
40336
40691
|
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.`,
|
|
40337
40692
|
// Destructive: the live site's current content is replaced by the older
|
|
@@ -40344,9 +40699,9 @@ function registerTools(server, ctx) {
|
|
|
40344
40699
|
to_version: external_exports.number().int().min(1).describe("The version to return to (see get_site {history: true})")
|
|
40345
40700
|
}
|
|
40346
40701
|
},
|
|
40347
|
-
async ({ site, to_version }) => {
|
|
40702
|
+
async ({ site: site2, to_version }) => {
|
|
40348
40703
|
try {
|
|
40349
|
-
return
|
|
40704
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/revert`, { body: { to_version } }));
|
|
40350
40705
|
} catch (error62) {
|
|
40351
40706
|
return toolError(error62);
|
|
40352
40707
|
}
|
|
@@ -40355,14 +40710,15 @@ function registerTools(server, ctx) {
|
|
|
40355
40710
|
server.registerTool(
|
|
40356
40711
|
"create_db",
|
|
40357
40712
|
{
|
|
40713
|
+
outputSchema: OUTPUT_SCHEMAS.create_db,
|
|
40358
40714
|
title: "Create site database",
|
|
40359
40715
|
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.`,
|
|
40360
40716
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
40361
40717
|
inputSchema: { site: external_exports.string().describe("Site name or id") }
|
|
40362
40718
|
},
|
|
40363
|
-
async ({ site }) => {
|
|
40719
|
+
async ({ site: site2 }) => {
|
|
40364
40720
|
try {
|
|
40365
|
-
return
|
|
40721
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/db`));
|
|
40366
40722
|
} catch (error62) {
|
|
40367
40723
|
return toolError(error62);
|
|
40368
40724
|
}
|
|
@@ -40371,6 +40727,7 @@ function registerTools(server, ctx) {
|
|
|
40371
40727
|
server.registerTool(
|
|
40372
40728
|
"query_db",
|
|
40373
40729
|
{
|
|
40730
|
+
outputSchema: OUTPUT_SCHEMAS.query_db,
|
|
40374
40731
|
title: "Query site database",
|
|
40375
40732
|
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.',
|
|
40376
40733
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40402,11 +40759,11 @@ function registerTools(server, ctx) {
|
|
|
40402
40759
|
}
|
|
40403
40760
|
},
|
|
40404
40761
|
async (args) => {
|
|
40405
|
-
const { site, action, type, seq } = args;
|
|
40762
|
+
const { site: site2, action, type, seq } = args;
|
|
40406
40763
|
try {
|
|
40407
40764
|
switch (action) {
|
|
40408
40765
|
case "types":
|
|
40409
|
-
return
|
|
40766
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data/types`));
|
|
40410
40767
|
case "list": {
|
|
40411
40768
|
if (!type) {
|
|
40412
40769
|
throw new ToolApiError(422, {
|
|
@@ -40434,7 +40791,7 @@ function registerTools(server, ctx) {
|
|
|
40434
40791
|
if (args.cursor) params.set("cursor", args.cursor);
|
|
40435
40792
|
if (args.offset !== void 0) params.set("offset", String(args.offset));
|
|
40436
40793
|
if (args.count) params.set("count", "1");
|
|
40437
|
-
return
|
|
40794
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data?${params}`));
|
|
40438
40795
|
}
|
|
40439
40796
|
case "get": {
|
|
40440
40797
|
if (seq === void 0) {
|
|
@@ -40446,7 +40803,7 @@ function registerTools(server, ctx) {
|
|
|
40446
40803
|
}
|
|
40447
40804
|
});
|
|
40448
40805
|
}
|
|
40449
|
-
return
|
|
40806
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/data?seq=${encodeURIComponent(String(seq))}`));
|
|
40450
40807
|
}
|
|
40451
40808
|
}
|
|
40452
40809
|
} catch (error62) {
|
|
@@ -40457,6 +40814,7 @@ function registerTools(server, ctx) {
|
|
|
40457
40814
|
server.registerTool(
|
|
40458
40815
|
"write_db",
|
|
40459
40816
|
{
|
|
40817
|
+
outputSchema: OUTPUT_SCHEMAS.write_db,
|
|
40460
40818
|
title: "Write site database",
|
|
40461
40819
|
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.",
|
|
40462
40820
|
// Open-world for the same reason deploy_site is: rows_url makes the
|
|
@@ -40482,7 +40840,7 @@ function registerTools(server, ctx) {
|
|
|
40482
40840
|
}
|
|
40483
40841
|
},
|
|
40484
40842
|
async ({
|
|
40485
|
-
site,
|
|
40843
|
+
site: site2,
|
|
40486
40844
|
action,
|
|
40487
40845
|
type,
|
|
40488
40846
|
seq,
|
|
@@ -40536,8 +40894,8 @@ function registerTools(server, ctx) {
|
|
|
40536
40894
|
}
|
|
40537
40895
|
});
|
|
40538
40896
|
}
|
|
40539
|
-
return
|
|
40540
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40897
|
+
return ok2(
|
|
40898
|
+
await ctx.api.request("POST", `${sitePath(site2)}/data`, {
|
|
40541
40899
|
body: {
|
|
40542
40900
|
type,
|
|
40543
40901
|
...parent_seq !== void 0 ? { parent_seq } : {},
|
|
@@ -40557,8 +40915,8 @@ function registerTools(server, ctx) {
|
|
|
40557
40915
|
}
|
|
40558
40916
|
});
|
|
40559
40917
|
}
|
|
40560
|
-
return
|
|
40561
|
-
await ctx.api.request("POST", `${sitePath(
|
|
40918
|
+
return ok2(
|
|
40919
|
+
await ctx.api.request("POST", `${sitePath(site2)}/data`, {
|
|
40562
40920
|
body: { type, parent_seq, json: json2, ...validate3 !== void 0 ? { validate: validate3 } : {} }
|
|
40563
40921
|
})
|
|
40564
40922
|
);
|
|
@@ -40573,8 +40931,8 @@ function registerTools(server, ctx) {
|
|
|
40573
40931
|
}
|
|
40574
40932
|
});
|
|
40575
40933
|
}
|
|
40576
|
-
return
|
|
40577
|
-
await ctx.api.request("PATCH", `${sitePath(
|
|
40934
|
+
return ok2(
|
|
40935
|
+
await ctx.api.request("PATCH", `${sitePath(site2)}/data/${encodeURIComponent(String(needSeq()))}`, {
|
|
40578
40936
|
body: {
|
|
40579
40937
|
...type !== void 0 ? { type } : {},
|
|
40580
40938
|
...parent_seq !== void 0 ? { parent_seq } : {},
|
|
@@ -40585,8 +40943,8 @@ function registerTools(server, ctx) {
|
|
|
40585
40943
|
})
|
|
40586
40944
|
);
|
|
40587
40945
|
case "delete":
|
|
40588
|
-
return
|
|
40589
|
-
await ctx.api.request("DELETE", `${sitePath(
|
|
40946
|
+
return ok2(
|
|
40947
|
+
await ctx.api.request("DELETE", `${sitePath(site2)}/data/${encodeURIComponent(String(needSeq()))}`)
|
|
40590
40948
|
);
|
|
40591
40949
|
}
|
|
40592
40950
|
} catch (error62) {
|
|
@@ -40597,6 +40955,7 @@ function registerTools(server, ctx) {
|
|
|
40597
40955
|
server.registerTool(
|
|
40598
40956
|
"set_privacy",
|
|
40599
40957
|
{
|
|
40958
|
+
outputSchema: OUTPUT_SCHEMAS.set_privacy,
|
|
40600
40959
|
title: "Set site visibility",
|
|
40601
40960
|
description: "Set a site's visibility to public or private. Privacy is a setting \u2014 nothing redeploys.",
|
|
40602
40961
|
// Open-world: going public puts the site on the open web.
|
|
@@ -40606,9 +40965,9 @@ function registerTools(server, ctx) {
|
|
|
40606
40965
|
visibility: external_exports.enum(["public", "private"]).describe("Who can see the site")
|
|
40607
40966
|
}
|
|
40608
40967
|
},
|
|
40609
|
-
async ({ site, visibility }) => {
|
|
40968
|
+
async ({ site: site2, visibility }) => {
|
|
40610
40969
|
try {
|
|
40611
|
-
return
|
|
40970
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/privacy`, { body: { visibility } }));
|
|
40612
40971
|
} catch (error62) {
|
|
40613
40972
|
return toolError(error62);
|
|
40614
40973
|
}
|
|
@@ -40617,6 +40976,7 @@ function registerTools(server, ctx) {
|
|
|
40617
40976
|
server.registerTool(
|
|
40618
40977
|
"share_site",
|
|
40619
40978
|
{
|
|
40979
|
+
outputSchema: OUTPUT_SCHEMAS.share_site,
|
|
40620
40980
|
title: "Share site",
|
|
40621
40981
|
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.",
|
|
40622
40982
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
@@ -40625,10 +40985,10 @@ function registerTools(server, ctx) {
|
|
|
40625
40985
|
email: external_exports.string().email().optional().describe("Send a magic-link invitation to this email instead of returning a link")
|
|
40626
40986
|
}
|
|
40627
40987
|
},
|
|
40628
|
-
async ({ site, email: email3 }) => {
|
|
40988
|
+
async ({ site: site2, email: email3 }) => {
|
|
40629
40989
|
try {
|
|
40630
|
-
if (email3) return
|
|
40631
|
-
return
|
|
40990
|
+
if (email3) return ok2(await ctx.api.request("POST", `${sitePath(site2)}/invite`, { body: { email: email3 } }));
|
|
40991
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/share`));
|
|
40632
40992
|
} catch (error62) {
|
|
40633
40993
|
return toolError(error62);
|
|
40634
40994
|
}
|
|
@@ -40637,6 +40997,7 @@ function registerTools(server, ctx) {
|
|
|
40637
40997
|
server.registerTool(
|
|
40638
40998
|
"list_sites",
|
|
40639
40999
|
{
|
|
41000
|
+
outputSchema: OUTPUT_SCHEMAS.list_sites,
|
|
40640
41001
|
title: "List sites",
|
|
40641
41002
|
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).",
|
|
40642
41003
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -40646,7 +41007,7 @@ function registerTools(server, ctx) {
|
|
|
40646
41007
|
},
|
|
40647
41008
|
async ({ include_deleted }) => {
|
|
40648
41009
|
try {
|
|
40649
|
-
return
|
|
41010
|
+
return ok2(await ctx.api.request("GET", `/v1/sites${include_deleted ? "?include_deleted=1" : ""}`));
|
|
40650
41011
|
} catch (error62) {
|
|
40651
41012
|
return toolError(error62);
|
|
40652
41013
|
}
|
|
@@ -40655,14 +41016,15 @@ function registerTools(server, ctx) {
|
|
|
40655
41016
|
server.registerTool(
|
|
40656
41017
|
"delete_site",
|
|
40657
41018
|
{
|
|
41019
|
+
outputSchema: OUTPUT_SCHEMAS.delete_site,
|
|
40658
41020
|
title: "Delete site",
|
|
40659
41021
|
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.",
|
|
40660
41022
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
40661
41023
|
inputSchema: { site: external_exports.string().describe("Site name or id") }
|
|
40662
41024
|
},
|
|
40663
|
-
async ({ site }) => {
|
|
41025
|
+
async ({ site: site2 }) => {
|
|
40664
41026
|
try {
|
|
40665
|
-
return
|
|
41027
|
+
return ok2(await ctx.api.request("DELETE", sitePath(site2)));
|
|
40666
41028
|
} catch (error62) {
|
|
40667
41029
|
return toolError(error62);
|
|
40668
41030
|
}
|
|
@@ -40671,6 +41033,7 @@ function registerTools(server, ctx) {
|
|
|
40671
41033
|
server.registerTool(
|
|
40672
41034
|
"check_domain",
|
|
40673
41035
|
{
|
|
41036
|
+
outputSchema: OUTPUT_SCHEMAS.check_domain,
|
|
40674
41037
|
title: "Check custom domain",
|
|
40675
41038
|
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.`,
|
|
40676
41039
|
// Not readOnlyHint despite being the "read" half of the domain pair:
|
|
@@ -40683,10 +41046,10 @@ function registerTools(server, ctx) {
|
|
|
40683
41046
|
action: external_exports.enum(["status", "connect"]).describe('"status" = verification progress; "connect" = one-click registrar setup lookup')
|
|
40684
41047
|
}
|
|
40685
41048
|
},
|
|
40686
|
-
async ({ site, action }) => {
|
|
41049
|
+
async ({ site: site2, action }) => {
|
|
40687
41050
|
try {
|
|
40688
|
-
if (action === "connect") return
|
|
40689
|
-
return
|
|
41051
|
+
if (action === "connect") return ok2(await ctx.api.request("GET", `${sitePath(site2)}/domain/connect`));
|
|
41052
|
+
return ok2(await ctx.api.request("GET", `${sitePath(site2)}/domain`));
|
|
40690
41053
|
} catch (error62) {
|
|
40691
41054
|
return toolError(error62);
|
|
40692
41055
|
}
|
|
@@ -40695,6 +41058,7 @@ function registerTools(server, ctx) {
|
|
|
40695
41058
|
server.registerTool(
|
|
40696
41059
|
"manage_domain",
|
|
40697
41060
|
{
|
|
41061
|
+
outputSchema: OUTPUT_SCHEMAS.manage_domain,
|
|
40698
41062
|
title: "Attach/detach custom domain",
|
|
40699
41063
|
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.',
|
|
40700
41064
|
// Open-world like check_domain: attach initiates certificate issuance
|
|
@@ -40709,7 +41073,7 @@ function registerTools(server, ctx) {
|
|
|
40709
41073
|
},
|
|
40710
41074
|
async ({
|
|
40711
41075
|
action,
|
|
40712
|
-
site,
|
|
41076
|
+
site: site2,
|
|
40713
41077
|
domain: domain2
|
|
40714
41078
|
}) => {
|
|
40715
41079
|
try {
|
|
@@ -40723,9 +41087,9 @@ function registerTools(server, ctx) {
|
|
|
40723
41087
|
}
|
|
40724
41088
|
});
|
|
40725
41089
|
}
|
|
40726
|
-
return
|
|
41090
|
+
return ok2(await ctx.api.request("POST", `${sitePath(site2)}/domain`, { body: { domain: domain2 } }));
|
|
40727
41091
|
}
|
|
40728
|
-
return
|
|
41092
|
+
return ok2(await ctx.api.request("DELETE", `${sitePath(site2)}/domain`));
|
|
40729
41093
|
} catch (error62) {
|
|
40730
41094
|
return toolError(error62);
|
|
40731
41095
|
}
|
|
@@ -40734,6 +41098,7 @@ function registerTools(server, ctx) {
|
|
|
40734
41098
|
server.registerTool(
|
|
40735
41099
|
"restore_site",
|
|
40736
41100
|
{
|
|
41101
|
+
outputSchema: OUTPUT_SCHEMAS.restore_site,
|
|
40737
41102
|
title: "Restore deleted site",
|
|
40738
41103
|
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.)",
|
|
40739
41104
|
// Open-world: a site that was public comes back public, served to visitors.
|
|
@@ -40743,7 +41108,7 @@ function registerTools(server, ctx) {
|
|
|
40743
41108
|
sitename: external_exports.string().optional().describe("Restore under this new name instead (when the old name is taken)")
|
|
40744
41109
|
}
|
|
40745
41110
|
},
|
|
40746
|
-
async ({ site, sitename }) => {
|
|
41111
|
+
async ({ site: site2, sitename }) => {
|
|
40747
41112
|
try {
|
|
40748
41113
|
if (sitename !== void 0 && sitename.length === 0) {
|
|
40749
41114
|
throw new ToolApiError(422, {
|
|
@@ -40754,8 +41119,8 @@ function registerTools(server, ctx) {
|
|
|
40754
41119
|
}
|
|
40755
41120
|
});
|
|
40756
41121
|
}
|
|
40757
|
-
return
|
|
40758
|
-
await ctx.api.request("POST", `${sitePath(
|
|
41122
|
+
return ok2(
|
|
41123
|
+
await ctx.api.request("POST", `${sitePath(site2)}/restore`, {
|
|
40759
41124
|
body: sitename ? { sitename } : {}
|
|
40760
41125
|
})
|
|
40761
41126
|
);
|
|
@@ -40764,8 +41129,81 @@ function registerTools(server, ctx) {
|
|
|
40764
41129
|
}
|
|
40765
41130
|
}
|
|
40766
41131
|
);
|
|
41132
|
+
server.registerTool(
|
|
41133
|
+
"manage_integrations",
|
|
41134
|
+
{
|
|
41135
|
+
outputSchema: OUTPUT_SCHEMAS.manage_integrations,
|
|
41136
|
+
title: "Manage integrations",
|
|
41137
|
+
description: `Connect a site to another service's API \u2014 get its keys set, check what's running, and test it \u2014 with no server code (Pro plan). (1) THE SETUP NEEDS NO TOOL: declare it in drivethrough.json and deploy. "secrets" names the keys; "routes" proxies same-origin paths under /api/ext/<name> to an upstream, allowing only the listed methods and path prefixes and adding secret headers server-side (\${NAME}); "actions" POST a JSON body ({{field}} reads a row's field) when a type with "on_write" gets a row; "schedules" run every 1h/3h/6h/12h/1d/1w/1mo \u2014 "rotate" sends the next row of a type through an action, "pull" saves a route's JSON response as a new row ("map" uses $.dotted.paths; numeric strings become numbers). No cron strings, no times of day, no timezones. Two complete examples \u2014 a read-only brokerage dashboard that also snapshots equity hourly for a chart: \`${INTEGRATION_EXAMPLE_A}\` \u2014 and a display that shows a quote when the owner clicks, and cycles through saved quotes every 3 hours: \`${INTEGRATION_EXAMPLE_B}\`. Every mistake is refused at deploy time with the corrected snippet \u2014 follow it. (2) SECRETS, TWO STEPS, like create_upload: action "secrets" returns a one-time link (10 minutes, no sign-in) \u2014 send it to the user as a clickable link and have them paste the keys there. NEVER ask for a key in the chat, and never put one in a file. When they say they're done, call again with the returned session to confirm every name is set. A deploy that needs an unset secret is refused with a fresh link already in the error \u2014 relay that one. Pass names to rotate a key that is already set. (3) TEST BEFORE YOU SAY IT WORKS: action "test" with target = a route plus path ("/api/ext/alpaca/v2/account"), an action name (with fields: the row values it uses), or a schedule name runs it once, now, for real (a schedule test inserts its row or advances its rotation) and returns the upstream's status and a redacted preview. Read the preview \u2014 a 200 with an error body is still a failure. action "status" shows secrets set/unset, routes, schedules (next run, last result, failures) and the latest outcomes. (4) WHAT FIRES AN ACTION: only a row written through the page-facing POST /api/data (a visitor's form, or the owner's own button on the page). write_db, imports and schedule pulls do NOT fire actions \u2014 to trigger one by hand, test it. Route access defaults to the owner only; set "access": "viewers" or "public" for pages others use. Schedules pause after 10 failures in a row (the owner gets one email); a deploy or a successful test re-enables them.`,
|
|
41138
|
+
// Open-world: tests call a third-party API on the user's behalf.
|
|
41139
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
41140
|
+
inputSchema: {
|
|
41141
|
+
site: external_exports.string().describe("Site name or id"),
|
|
41142
|
+
action: external_exports.enum(["secrets", "status", "test"]).describe('"secrets": get a link for the user to set keys (or, with session, confirm they are set); "status": what is declared and how it ran; "test": run one route, action or schedule now'),
|
|
41143
|
+
names: external_exports.array(external_exports.string()).optional().describe("secrets: which secret names the link asks for. Default: every declared secret that is still unset. Include a set one to replace (rotate) it."),
|
|
41144
|
+
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")
|
|
41147
|
+
}
|
|
41148
|
+
},
|
|
41149
|
+
async ({
|
|
41150
|
+
site: site2,
|
|
41151
|
+
action,
|
|
41152
|
+
names,
|
|
41153
|
+
session,
|
|
41154
|
+
target,
|
|
41155
|
+
fields
|
|
41156
|
+
}) => {
|
|
41157
|
+
try {
|
|
41158
|
+
const setMap = async () => {
|
|
41159
|
+
const listed = await ctx.api.request(
|
|
41160
|
+
"GET",
|
|
41161
|
+
`${sitePath(site2)}/secrets`
|
|
41162
|
+
);
|
|
41163
|
+
return listed.secrets;
|
|
41164
|
+
};
|
|
41165
|
+
if (action === "secrets") {
|
|
41166
|
+
if (session !== void 0) {
|
|
41167
|
+
const secrets2 = await setMap();
|
|
41168
|
+
const declared = secrets2.filter((s2) => s2.declared);
|
|
41169
|
+
const unset = declared.filter((s2) => !s2.set).map((s2) => s2.name);
|
|
41170
|
+
return ok2({
|
|
41171
|
+
session,
|
|
41172
|
+
set: Object.fromEntries(secrets2.map((s2) => [s2.name, s2.set])),
|
|
41173
|
+
all_set: unset.length === 0,
|
|
41174
|
+
next: unset.length ? `Still unset: ${unset.join(", ")}. Ask the user to finish on the page \u2014 or, if the link was used or expired, call this tool with action "secrets" again for a fresh one.` : "Every declared secret is set. Deploy (or redeploy) the site, then test each route, action and schedule."
|
|
41175
|
+
});
|
|
41176
|
+
}
|
|
41177
|
+
const link = await ctx.api.request("POST", `${sitePath(site2)}/secrets/link`, {
|
|
41178
|
+
body: names ? { names } : {}
|
|
41179
|
+
});
|
|
41180
|
+
const secrets = await setMap();
|
|
41181
|
+
return ok2({ ...link, set: Object.fromEntries(secrets.map((s2) => [s2.name, s2.set])) });
|
|
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
|
+
}
|
|
41193
|
+
});
|
|
41194
|
+
}
|
|
41195
|
+
return ok2(
|
|
41196
|
+
await ctx.api.request("POST", `${sitePath(site2)}/integrations/test`, {
|
|
41197
|
+
body: { target, ...fields ? { fields } : {} }
|
|
41198
|
+
})
|
|
41199
|
+
);
|
|
41200
|
+
} catch (error62) {
|
|
41201
|
+
return toolError(error62);
|
|
41202
|
+
}
|
|
41203
|
+
}
|
|
41204
|
+
);
|
|
40767
41205
|
}
|
|
40768
|
-
var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS;
|
|
41206
|
+
var MAX_BUNDLE_MB, SERVER_INSTRUCTIONS, INTEGRATION_EXAMPLE_A, INTEGRATION_EXAMPLE_B;
|
|
40769
41207
|
var init_tools = __esm({
|
|
40770
41208
|
"../mcp-tools/src/tools.ts"() {
|
|
40771
41209
|
"use strict";
|
|
@@ -40774,6 +41212,7 @@ var init_tools = __esm({
|
|
|
40774
41212
|
init_edits();
|
|
40775
41213
|
init_inline_files();
|
|
40776
41214
|
init_remote_assets();
|
|
41215
|
+
init_output_schemas();
|
|
40777
41216
|
MAX_BUNDLE_MB = 50;
|
|
40778
41217
|
SERVER_INSTRUCTIONS = `Drivethrough hosts websites: deploy, share, collect data. Sites are private by default; guests never need accounts.
|
|
40779
41218
|
|
|
@@ -40845,7 +41284,51 @@ RECIPE \u2014 a searchable, filterable list (catalogue, directory, archive)
|
|
|
40845
41284
|
whose query the config doesn't declare (it will render empty for visitors),
|
|
40846
41285
|
and a declaration no page uses (pure cost on every insert).
|
|
40847
41286
|
|
|
41287
|
+
RECIPE \u2014 call another service's API (stock prices, a smart display, a CRM, weather; Pro plan):
|
|
41288
|
+
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 manage_integrations {action: "secrets"} gives you a one-time link to send the user, who pastes the keys there.
|
|
41290
|
+
- "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
|
+
- "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
|
+
- "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
|
+
Deploy, relay the secrets link if the deploy asks for one, then manage_integrations {action: "test"} each piece before telling the user it works. Full examples are in manage_integrations' description.
|
|
41294
|
+
|
|
40848
41295
|
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
|
+
INTEGRATION_EXAMPLE_A = JSON.stringify({
|
|
41297
|
+
secrets: ["ALPACA_KEY", "ALPACA_SECRET"],
|
|
41298
|
+
routes: {
|
|
41299
|
+
"/api/ext/alpaca": {
|
|
41300
|
+
upstream: "https://paper-api.alpaca.markets",
|
|
41301
|
+
methods: ["GET"],
|
|
41302
|
+
paths: ["/v2/account", "/v2/positions", "/v2/account/portfolio/history"],
|
|
41303
|
+
headers: { "APCA-API-KEY-ID": "${ALPACA_KEY}", "APCA-API-SECRET-KEY": "${ALPACA_SECRET}" },
|
|
41304
|
+
access: "viewers",
|
|
41305
|
+
cache: "30s",
|
|
41306
|
+
query: true
|
|
41307
|
+
}
|
|
41308
|
+
},
|
|
41309
|
+
types: { snapshot: { fields: { equity: "number", cash: "number" } } },
|
|
41310
|
+
schedules: {
|
|
41311
|
+
portfolio: {
|
|
41312
|
+
every: "1h",
|
|
41313
|
+
pull: { route: "/api/ext/alpaca", path: "/v2/account", into: "snapshot", map: { equity: "$.equity", cash: "$.cash" } }
|
|
41314
|
+
}
|
|
41315
|
+
}
|
|
41316
|
+
});
|
|
41317
|
+
INTEGRATION_EXAMPLE_B = JSON.stringify({
|
|
41318
|
+
secrets: ["VESTABOARD_KEY"],
|
|
41319
|
+
actions: {
|
|
41320
|
+
show: {
|
|
41321
|
+
post: "https://rw.vestaboard.com/",
|
|
41322
|
+
headers: { "X-Vestaboard-Read-Write-Key": "${VESTABOARD_KEY}" },
|
|
41323
|
+
body: { text: "{{text}}" }
|
|
41324
|
+
}
|
|
41325
|
+
},
|
|
41326
|
+
types: {
|
|
41327
|
+
quote: { fields: { text: "string" } },
|
|
41328
|
+
activation: { fields: { text: "string" }, on_write: "show" }
|
|
41329
|
+
},
|
|
41330
|
+
schedules: { cycle: { every: "3h", rotate: "quote", action: "show" } }
|
|
41331
|
+
});
|
|
40849
41332
|
}
|
|
40850
41333
|
});
|
|
40851
41334
|
|
|
@@ -40858,6 +41341,7 @@ var init_src2 = __esm({
|
|
|
40858
41341
|
init_inline_files();
|
|
40859
41342
|
init_remote_assets();
|
|
40860
41343
|
init_tools();
|
|
41344
|
+
init_output_schemas();
|
|
40861
41345
|
}
|
|
40862
41346
|
});
|
|
40863
41347
|
|
|
@@ -42786,6 +43270,28 @@ var dD = class extends x2 {
|
|
|
42786
43270
|
};
|
|
42787
43271
|
var A3;
|
|
42788
43272
|
A3 = /* @__PURE__ */ new WeakMap();
|
|
43273
|
+
var TD = Object.defineProperty;
|
|
43274
|
+
var jD2 = (e3, u4, t2) => u4 in e3 ? TD(e3, u4, { enumerable: true, configurable: true, writable: true, value: t2 }) : e3[u4] = t2;
|
|
43275
|
+
var U = (e3, u4, t2) => (jD2(e3, typeof u4 != "symbol" ? u4 + "" : u4, t2), t2);
|
|
43276
|
+
var MD = class extends x2 {
|
|
43277
|
+
constructor({ mask: u4, ...t2 }) {
|
|
43278
|
+
super(t2), U(this, "valueWithCursor", ""), U(this, "_mask", "\u2022"), this._mask = u4 ?? "\u2022", this.on("finalize", () => {
|
|
43279
|
+
this.valueWithCursor = this.masked;
|
|
43280
|
+
}), this.on("value", () => {
|
|
43281
|
+
if (this.cursor >= this.value.length) this.valueWithCursor = `${this.masked}${import_picocolors.default.inverse(import_picocolors.default.hidden("_"))}`;
|
|
43282
|
+
else {
|
|
43283
|
+
const F4 = this.masked.slice(0, this.cursor), s2 = this.masked.slice(this.cursor);
|
|
43284
|
+
this.valueWithCursor = `${F4}${import_picocolors.default.inverse(s2[0])}${s2.slice(1)}`;
|
|
43285
|
+
}
|
|
43286
|
+
});
|
|
43287
|
+
}
|
|
43288
|
+
get cursor() {
|
|
43289
|
+
return this._cursor;
|
|
43290
|
+
}
|
|
43291
|
+
get masked() {
|
|
43292
|
+
return this.value.replaceAll(/./g, this._mask);
|
|
43293
|
+
}
|
|
43294
|
+
};
|
|
42789
43295
|
var RD = class extends x2 {
|
|
42790
43296
|
get valueWithCursor() {
|
|
42791
43297
|
if (this.state === "submit") return this.value;
|
|
@@ -42831,7 +43337,7 @@ var de = u3("\u251C", "+");
|
|
|
42831
43337
|
var pe = u3("\u256F", "+");
|
|
42832
43338
|
var q2 = u3("\u25CF", "\u2022");
|
|
42833
43339
|
var D2 = u3("\u25C6", "*");
|
|
42834
|
-
var
|
|
43340
|
+
var U2 = u3("\u25B2", "!");
|
|
42835
43341
|
var K2 = u3("\u25A0", "x");
|
|
42836
43342
|
var b3 = (t2) => {
|
|
42837
43343
|
switch (t2) {
|
|
@@ -42867,6 +43373,27 @@ ${import_picocolors2.default.cyan(d3)}
|
|
|
42867
43373
|
`;
|
|
42868
43374
|
}
|
|
42869
43375
|
} }).prompt();
|
|
43376
|
+
var ge = (t2) => new MD({ validate: t2.validate, mask: t2.mask ?? $e, render() {
|
|
43377
|
+
const n2 = `${import_picocolors2.default.gray(o3)}
|
|
43378
|
+
${b3(this.state)} ${t2.message}
|
|
43379
|
+
`, r3 = this.valueWithCursor, i2 = this.masked;
|
|
43380
|
+
switch (this.state) {
|
|
43381
|
+
case "error":
|
|
43382
|
+
return `${n2.trim()}
|
|
43383
|
+
${import_picocolors2.default.yellow(o3)} ${i2}
|
|
43384
|
+
${import_picocolors2.default.yellow(d3)} ${import_picocolors2.default.yellow(this.error)}
|
|
43385
|
+
`;
|
|
43386
|
+
case "submit":
|
|
43387
|
+
return `${n2}${import_picocolors2.default.gray(o3)} ${import_picocolors2.default.dim(i2)}`;
|
|
43388
|
+
case "cancel":
|
|
43389
|
+
return `${n2}${import_picocolors2.default.gray(o3)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i2 ?? ""))}${i2 ? `
|
|
43390
|
+
${import_picocolors2.default.gray(o3)}` : ""}`;
|
|
43391
|
+
default:
|
|
43392
|
+
return `${n2}${import_picocolors2.default.cyan(o3)} ${r3}
|
|
43393
|
+
${import_picocolors2.default.cyan(d3)}
|
|
43394
|
+
`;
|
|
43395
|
+
}
|
|
43396
|
+
} }).prompt();
|
|
42870
43397
|
var ye2 = (t2) => {
|
|
42871
43398
|
const n2 = t2.active ?? "Yes", r3 = t2.inactive ?? "No";
|
|
42872
43399
|
return new dD({ active: n2, inactive: r3, initialValue: t2.initialValue ?? true, render() {
|
|
@@ -42928,7 +43455,7 @@ var M2 = { message: (t2 = "", { symbol: n2 = import_picocolors2.default.gray(o3)
|
|
|
42928
43455
|
}, step: (t2) => {
|
|
42929
43456
|
M2.message(t2, { symbol: import_picocolors2.default.green(C4) });
|
|
42930
43457
|
}, warn: (t2) => {
|
|
42931
|
-
M2.message(t2, { symbol: import_picocolors2.default.yellow(
|
|
43458
|
+
M2.message(t2, { symbol: import_picocolors2.default.yellow(U2) });
|
|
42932
43459
|
}, warning: (t2) => {
|
|
42933
43460
|
M2.warn(t2);
|
|
42934
43461
|
}, error: (t2) => {
|
|
@@ -43073,15 +43600,15 @@ var sitesCommand = defineCommand({
|
|
|
43073
43600
|
);
|
|
43074
43601
|
return;
|
|
43075
43602
|
}
|
|
43076
|
-
for (const
|
|
43077
|
-
if (
|
|
43603
|
+
for (const site2 of sites) {
|
|
43604
|
+
if (site2.status === "deleted") {
|
|
43078
43605
|
M2.message(
|
|
43079
|
-
`${
|
|
43606
|
+
`${site2.title} \u2014 ${site2.sitename} (deleted, recoverable until ${site2.purges_after ?? "?"} \u2014 \`drivethrough restore ${site2.sitename}\`)`
|
|
43080
43607
|
);
|
|
43081
43608
|
continue;
|
|
43082
43609
|
}
|
|
43083
|
-
const flags = [
|
|
43084
|
-
M2.message(`${
|
|
43610
|
+
const flags = [site2.visibility, site2.status !== "active" ? site2.status : null].filter(Boolean).join(", ");
|
|
43611
|
+
M2.message(`${site2.title} \u2014 ${site2.local_url} (${flags}, v${site2.current_version})`);
|
|
43085
43612
|
}
|
|
43086
43613
|
});
|
|
43087
43614
|
} catch (error62) {
|
|
@@ -43826,6 +44353,86 @@ var collaboratorsCommand = defineCommand({
|
|
|
43826
44353
|
subCommands: { list: listCmd, add: addCmd }
|
|
43827
44354
|
});
|
|
43828
44355
|
|
|
44356
|
+
// src/commands/secrets.ts
|
|
44357
|
+
init_api();
|
|
44358
|
+
var listCmd2 = defineCommand({
|
|
44359
|
+
meta: { name: "list", description: "List a site's secrets (names and whether each is set \u2014 never values)" },
|
|
44360
|
+
args: {
|
|
44361
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44362
|
+
json: { type: "boolean" }
|
|
44363
|
+
},
|
|
44364
|
+
async run({ args }) {
|
|
44365
|
+
const jsonArgs = { json: args.json };
|
|
44366
|
+
try {
|
|
44367
|
+
const res = await api("GET", `/v1/sites/${encodeURIComponent(args.site)}/secrets`);
|
|
44368
|
+
emit(jsonArgs, { secrets: res.secrets }, () => {
|
|
44369
|
+
if (!res.secrets.length) M2.info("This site declares no secrets.");
|
|
44370
|
+
for (const s2 of res.secrets) {
|
|
44371
|
+
const note = s2.declared ? "" : " (not in drivethrough.json)";
|
|
44372
|
+
M2.message(`${s2.name} ${s2.set ? `set ${s2.updated_at?.slice(0, 10) ?? ""}` : "NOT SET"}${note}`);
|
|
44373
|
+
}
|
|
44374
|
+
});
|
|
44375
|
+
} catch (error62) {
|
|
44376
|
+
fail(jsonArgs, error62);
|
|
44377
|
+
}
|
|
44378
|
+
}
|
|
44379
|
+
});
|
|
44380
|
+
async function readValue(name) {
|
|
44381
|
+
if (!process.stdin.isTTY) {
|
|
44382
|
+
const chunks = [];
|
|
44383
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
44384
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
44385
|
+
}
|
|
44386
|
+
const value = await ge({ message: `Value for ${name}` });
|
|
44387
|
+
if (pD2(value)) process.exit(1);
|
|
44388
|
+
return value;
|
|
44389
|
+
}
|
|
44390
|
+
var setCmd = defineCommand({
|
|
44391
|
+
meta: { name: "set", description: "Set (or replace) a secret \u2014 the value is prompted for, or read from stdin" },
|
|
44392
|
+
args: {
|
|
44393
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44394
|
+
name: { type: "positional", description: "Secret name, e.g. API_KEY", required: true },
|
|
44395
|
+
json: { type: "boolean" }
|
|
44396
|
+
},
|
|
44397
|
+
async run({ args }) {
|
|
44398
|
+
const jsonArgs = { json: args.json };
|
|
44399
|
+
try {
|
|
44400
|
+
const value = await readValue(args.name);
|
|
44401
|
+
await api("PUT", `/v1/sites/${encodeURIComponent(args.site)}/secrets/${encodeURIComponent(args.name)}`, {
|
|
44402
|
+
body: { value }
|
|
44403
|
+
});
|
|
44404
|
+
emit(jsonArgs, { name: args.name, set: true }, () => M2.success(`${args.name} is set on ${args.site}.`));
|
|
44405
|
+
} catch (error62) {
|
|
44406
|
+
fail(jsonArgs, error62);
|
|
44407
|
+
}
|
|
44408
|
+
}
|
|
44409
|
+
});
|
|
44410
|
+
var unsetCmd = defineCommand({
|
|
44411
|
+
meta: { name: "unset", description: "Remove a secret's value" },
|
|
44412
|
+
args: {
|
|
44413
|
+
site: { type: "positional", description: "Site name", required: true },
|
|
44414
|
+
name: { type: "positional", description: "Secret name", required: true },
|
|
44415
|
+
json: { type: "boolean" }
|
|
44416
|
+
},
|
|
44417
|
+
async run({ args }) {
|
|
44418
|
+
const jsonArgs = { json: args.json };
|
|
44419
|
+
try {
|
|
44420
|
+
await api("DELETE", `/v1/sites/${encodeURIComponent(args.site)}/secrets/${encodeURIComponent(args.name)}`);
|
|
44421
|
+
emit(
|
|
44422
|
+
jsonArgs,
|
|
44423
|
+
{ name: args.name, set: false },
|
|
44424
|
+
() => M2.success(`${args.name} is unset on ${args.site}. Anything that uses it now fails until it's set again.`)
|
|
44425
|
+
);
|
|
44426
|
+
} catch (error62) {
|
|
44427
|
+
fail(jsonArgs, error62);
|
|
44428
|
+
}
|
|
44429
|
+
}
|
|
44430
|
+
});
|
|
44431
|
+
var secretsCommand = defineCommand({
|
|
44432
|
+
meta: { name: "secrets", description: "API keys a site's integrations use (Pro) \u2014 write-only" },
|
|
44433
|
+
subCommands: { list: listCmd2, set: setCmd, unset: unsetCmd }
|
|
44434
|
+
});
|
|
44435
|
+
|
|
43829
44436
|
// src/commands/usage.ts
|
|
43830
44437
|
init_api();
|
|
43831
44438
|
function human(value, unit) {
|
|
@@ -44401,8 +45008,8 @@ function resolveSitename(root, explicit) {
|
|
|
44401
45008
|
const raw = localConfigRaw(root);
|
|
44402
45009
|
if (raw) {
|
|
44403
45010
|
try {
|
|
44404
|
-
const
|
|
44405
|
-
if (
|
|
45011
|
+
const site2 = JSON.parse(raw).site;
|
|
45012
|
+
if (site2) return site2;
|
|
44406
45013
|
} catch {
|
|
44407
45014
|
}
|
|
44408
45015
|
}
|
|
@@ -44744,6 +45351,7 @@ var main = defineCommand({
|
|
|
44744
45351
|
upgrade: upgradeCommand,
|
|
44745
45352
|
domain: domainCommand,
|
|
44746
45353
|
collaborators: collaboratorsCommand,
|
|
45354
|
+
secrets: secretsCommand,
|
|
44747
45355
|
usage: usageCommand,
|
|
44748
45356
|
mcp: mcpCommand,
|
|
44749
45357
|
"agent-setup": agentSetupCommand
|