void 0.9.1 → 0.9.2
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/cli/cli.mjs +2 -2
- package/dist/{domain-DxTc6HDn.mjs → domain-B7RntQ1r.mjs} +12 -27
- package/dist/runtime/ai.d.mts +20 -100
- package/dist/runtime/ai.mjs +101 -54
- package/package.json +2 -2
- package/skills/void/docs/guide/ai.md +109 -51
- package/skills/void/docs/node_modules/void/AGENTS.md +1 -1
- package/skills/void/docs/reference/api.md +9 -2
- package/skills/void/docs/reference/cli.md +4 -2
package/dist/cli/cli.mjs
CHANGED
|
@@ -1568,7 +1568,7 @@ else if (command === "deploy") {
|
|
|
1568
1568
|
} else if (command === "auth") runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (root, parsed) => import("../auth-cmd-CPkWrQR9.mjs").then(({ runAuthCommand }) => runAuthCommand(root, parsed)));
|
|
1569
1569
|
else if (command === "project") runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (root, parsed) => import("../project-cmd-CLrqSbqj.mjs").then(({ runProjectCommand }) => runProjectCommand(root, parsed)));
|
|
1570
1570
|
else if (command === "secret") runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (root, parsed) => import("../secret-C_uTY7xx.mjs").then(({ runSecretCommand }) => runSecretCommand(root, parsed)));
|
|
1571
|
-
else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-
|
|
1571
|
+
else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-B7RntQ1r.mjs").then(({ runDomainCommand }) => runDomainCommand(root, parsed)));
|
|
1572
1572
|
else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-DiddQC2v.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
|
|
1573
1573
|
else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-Bvw0wMTI.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
|
|
1574
1574
|
else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-CZdNWIaA.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
|
|
@@ -1695,7 +1695,7 @@ else if (command === "-v" || command === "--version") {
|
|
|
1695
1695
|
case "auth": return runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (r, p) => import("../auth-cmd-CPkWrQR9.mjs").then(({ runAuthCommand }) => runAuthCommand(r, p)));
|
|
1696
1696
|
case "project": return runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (r, p) => import("../project-cmd-CLrqSbqj.mjs").then(({ runProjectCommand }) => runProjectCommand(r, p)));
|
|
1697
1697
|
case "secret": return runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (r, p) => import("../secret-C_uTY7xx.mjs").then(({ runSecretCommand }) => runSecretCommand(r, p)));
|
|
1698
|
-
case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-
|
|
1698
|
+
case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-B7RntQ1r.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
|
|
1699
1699
|
case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-DiddQC2v.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
|
|
1700
1700
|
case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-Bvw0wMTI.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
|
|
1701
1701
|
case "gen": return runSubcommandGroup("gen", genSubcommands, parseGenArgs, (r, p) => import("../gen-CZdNWIaA.mjs").then(({ runGenCommand }) => runGenCommand(r, p)));
|
|
@@ -39,20 +39,13 @@ async function domainAdd(client, projectId, hostname) {
|
|
|
39
39
|
R.success(`Domain ${hostname} added (status: ${domain.status})`);
|
|
40
40
|
let cnameSection;
|
|
41
41
|
let ownershipSection;
|
|
42
|
-
let sslSection;
|
|
43
42
|
if (domain.cname_target) cnameSection = `CNAME (traffic)\n ${hostname} → ${domain.cname_target}`;
|
|
44
43
|
if (domain.ownership_verification) ownershipSection = `TXT (ownership verification)\n ${domain.ownership_verification.name} → ${domain.ownership_verification.value}`;
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
cnameSection,
|
|
51
|
-
ownershipSection,
|
|
52
|
-
sslSection,
|
|
53
|
-
summary
|
|
54
|
-
].filter((s) => s !== void 0);
|
|
55
|
-
if (sections.length > 0) Se(sections.join("\n\n"), "DNS Setup");
|
|
44
|
+
const recordSections = [cnameSection, ownershipSection].filter((s) => s !== void 0);
|
|
45
|
+
if (recordSections.length > 0) {
|
|
46
|
+
const summary = `Add ${recordSections.length === 1 ? "the record above" : "the records above"} to your DNS, then run \`void domain status ${hostname}\` to check progress.`;
|
|
47
|
+
Se([...recordSections, summary].join("\n\n"), "DNS Setup");
|
|
48
|
+
}
|
|
56
49
|
}
|
|
57
50
|
async function domainRemove(client, projectId, hostname) {
|
|
58
51
|
const domain = (await client.listDomains(projectId)).find((d) => d.hostname === hostname);
|
|
@@ -93,7 +86,7 @@ function computeRollup(status) {
|
|
|
93
86
|
};
|
|
94
87
|
if (status.cf_status === "pending_issuance" || status.cf_status === "active" && status.ssl_status !== "active") return {
|
|
95
88
|
state: "issuing_cert",
|
|
96
|
-
diagnostic: "Cloudflare is issuing the SSL certificate (typically <5 minutes)."
|
|
89
|
+
diagnostic: status.cname_target ? "Cloudflare is issuing the SSL certificate automatically (typically <5 minutes). If this persists, confirm the traffic CNAME below is published." : "Cloudflare is issuing the SSL certificate automatically (typically <5 minutes)."
|
|
97
90
|
};
|
|
98
91
|
if (status.cf_status === "pending_deployment") return {
|
|
99
92
|
state: "deploying_cert",
|
|
@@ -125,20 +118,12 @@ async function domainStatus(client, projectId, hostname, verbose) {
|
|
|
125
118
|
if (status.verification_errors && status.verification_errors.length > 0) rollupLines.push(` Errors: ${status.verification_errors.join(", ")}`);
|
|
126
119
|
}
|
|
127
120
|
Se(rollupLines.join("\n"), "Domain Status");
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const sslRecords = (status.ssl_validation_records ?? []).filter((r) => Boolean(r.txt_name) && Boolean(r.txt_value) && r.status !== "active");
|
|
135
|
-
if (sslRecords.length > 0) {
|
|
136
|
-
const lines = sslRecords.map((r) => ` ${r.txt_name} → ${r.txt_value}`).join("\n");
|
|
137
|
-
pendingSections.push(`TXT (SSL validation)\n${lines}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (pendingSections.length > 0) Se(pendingSections.join("\n\n"), "Add these DNS records to your provider");
|
|
141
|
-
}
|
|
121
|
+
let cnameSection;
|
|
122
|
+
let ownershipSection;
|
|
123
|
+
if (status.cname_target && status.ssl_status !== "active") cnameSection = `CNAME (traffic)\n ${status.hostname} → ${status.cname_target}`;
|
|
124
|
+
if (status.ownership_verification) ownershipSection = `TXT (ownership verification)\n ${status.ownership_verification.name} → ${status.ownership_verification.value}`;
|
|
125
|
+
const pendingRecords = [cnameSection, ownershipSection].filter((s) => s !== void 0);
|
|
126
|
+
if (pendingRecords.length > 0) Se(pendingRecords.join("\n\n"), pendingRecords.length === 1 ? "Add this DNS record to your provider" : "Add these DNS records to your provider");
|
|
142
127
|
}
|
|
143
128
|
//#endregion
|
|
144
129
|
export { runDomainCommand };
|
package/dist/runtime/ai.d.mts
CHANGED
|
@@ -1,102 +1,20 @@
|
|
|
1
1
|
//#region src/runtime/ai-types.d.ts
|
|
2
|
-
type
|
|
3
|
-
type
|
|
4
|
-
type ChatContent = string | Array<{
|
|
5
|
-
type: "text";
|
|
6
|
-
text: string;
|
|
7
|
-
} | {
|
|
8
|
-
type: "image_url";
|
|
9
|
-
image_url: {
|
|
10
|
-
url: string;
|
|
11
|
-
detail?: "auto" | "low" | "high";
|
|
12
|
-
};
|
|
13
|
-
}>;
|
|
14
|
-
type ChatMessage = {
|
|
15
|
-
role: "system" | "user" | "assistant";
|
|
16
|
-
content: ChatContent;
|
|
17
|
-
};
|
|
18
|
-
type ChatCompletionInputs = {
|
|
19
|
-
messages: Array<ChatMessage>;
|
|
20
|
-
temperature?: number;
|
|
21
|
-
max_tokens?: number;
|
|
22
|
-
top_p?: number;
|
|
23
|
-
frequency_penalty?: number;
|
|
24
|
-
presence_penalty?: number;
|
|
25
|
-
stop?: string | Array<string>;
|
|
26
|
-
stream?: boolean;
|
|
27
|
-
};
|
|
28
|
-
type ChatCompletionResponse = {
|
|
29
|
-
id: string;
|
|
30
|
-
object: string;
|
|
31
|
-
created: number;
|
|
32
|
-
model: string;
|
|
33
|
-
choices: Array<ChatCompletionChoice>;
|
|
34
|
-
usage: ChatCompletionUsage;
|
|
35
|
-
};
|
|
36
|
-
type ChatCompletionChoice = {
|
|
37
|
-
index: number;
|
|
38
|
-
message: ChatMessage;
|
|
39
|
-
finish_reason: string;
|
|
40
|
-
};
|
|
41
|
-
type ChatCompletionUsage = {
|
|
42
|
-
prompt_tokens: number;
|
|
43
|
-
completion_tokens: number;
|
|
44
|
-
total_tokens: number;
|
|
45
|
-
};
|
|
46
|
-
type ChatCompletionChunk = {
|
|
47
|
-
id: string;
|
|
48
|
-
choices: Array<{
|
|
49
|
-
index: number;
|
|
50
|
-
delta: {
|
|
51
|
-
role?: string;
|
|
52
|
-
content?: string;
|
|
53
|
-
};
|
|
54
|
-
finish_reason: string | null;
|
|
55
|
-
}>;
|
|
56
|
-
};
|
|
57
|
-
type ImageGatewayEndpoint = "images/generations" | "images/edits";
|
|
2
|
+
type AiProviderName = "openai" | "anthropic" | "google-ai-studio" | "groq" | "mistral" | "grok" | "deepseek" | "openrouter" | "perplexity" | "cohere" | "cerebras" | "huggingface" | "replicate" | "baseten" | "cartesia" | "deepgram" | "elevenlabs" | "fal" | "ideogram" | "parallel";
|
|
3
|
+
type AiProvider = AiProviderName | (string & {});
|
|
58
4
|
type ImageFileInput = Blob | ArrayBuffer | ArrayBufferView | {
|
|
59
5
|
data: string;
|
|
60
6
|
name?: string;
|
|
61
7
|
type?: string;
|
|
62
8
|
};
|
|
63
|
-
type
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
quality?: string;
|
|
68
|
-
response_format?: "url" | "b64_json";
|
|
69
|
-
user?: string;
|
|
70
|
-
[key: string]: unknown;
|
|
71
|
-
};
|
|
72
|
-
type ImageGenerationData = {
|
|
73
|
-
url?: string;
|
|
74
|
-
b64_json?: string;
|
|
75
|
-
revised_prompt?: string;
|
|
76
|
-
};
|
|
77
|
-
type ImageGenerationResponse = {
|
|
78
|
-
created?: number;
|
|
79
|
-
data: Array<ImageGenerationData>;
|
|
80
|
-
usage?: unknown;
|
|
81
|
-
[key: string]: unknown;
|
|
82
|
-
};
|
|
83
|
-
type ImageGenerationOptions = {
|
|
84
|
-
endpoint?: "images/generations";
|
|
85
|
-
[key: string]: unknown;
|
|
86
|
-
};
|
|
87
|
-
type ImageEditOptions = {
|
|
88
|
-
endpoint: "images/edits";
|
|
89
|
-
[key: string]: unknown;
|
|
9
|
+
type AiProviderOptions = {
|
|
10
|
+
apiKeyEnv?: string;
|
|
11
|
+
apiKeyHeader?: string;
|
|
12
|
+
apiKeyPrefix?: string;
|
|
90
13
|
};
|
|
91
|
-
type
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
n?: number;
|
|
96
|
-
size?: string;
|
|
97
|
-
response_format?: "url" | "b64_json";
|
|
98
|
-
user?: string;
|
|
99
|
-
[key: string]: unknown;
|
|
14
|
+
type AiProviderFetchInit = {
|
|
15
|
+
method?: string;
|
|
16
|
+
headers?: HeadersInit;
|
|
17
|
+
body?: unknown;
|
|
100
18
|
};
|
|
101
19
|
//#endregion
|
|
102
20
|
//#region src/runtime/ai.d.ts
|
|
@@ -107,14 +25,16 @@ type ImageEditInputs = {
|
|
|
107
25
|
* in a `Response` with SSE headers — ready to return from a route handler.
|
|
108
26
|
*/
|
|
109
27
|
type VoidAi = Omit<Ai, "run"> & {
|
|
110
|
-
/** Workers AI inference (existing) */run<Name extends keyof AiModels>(model: Name, inputs: AiModels[Name]["inputs"], options?: AiOptions): Promise<AiModels[Name]["postProcessedOutputs"]>; /**
|
|
111
|
-
run(model:
|
|
112
|
-
image<Name extends keyof AiModels>(model: Name, inputs: AiModels[Name]["inputs"], options?:
|
|
113
|
-
image(model:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
28
|
+
/** Workers AI inference (existing) */run<Name extends keyof AiModels>(model: Name, inputs: AiModels[Name]["inputs"], options?: AiOptions): Promise<AiModels[Name]["postProcessedOutputs"]>; /** Cloudflare AI Gateway model inference. Accepts Cloudflare model IDs unchanged. */
|
|
29
|
+
run(model: string, inputs: unknown, options?: AiOptions): Promise<unknown>; /** Image generation. Returns the proxy response so callers can return or parse it. */
|
|
30
|
+
image<Name extends keyof AiModels>(model: Name, inputs: AiModels[Name]["inputs"], options?: AiOptions): Promise<Response>; /** Cloudflare AI Gateway model inference returned as a Response. */
|
|
31
|
+
image(model: string, inputs: unknown, options?: AiOptions): Promise<Response>; /** Provider-native AI Gateway requests using a project secret for the provider API key. */
|
|
32
|
+
provider(provider: AiProvider, options?: AiProviderOptions): {
|
|
33
|
+
fetch(path: string, init?: AiProviderFetchInit): Promise<Response>;
|
|
34
|
+
}; /** Stream Workers AI response as SSE */
|
|
35
|
+
stream<Name extends keyof AiModels>(model: Name, inputs: Omit<AiModels[Name]["inputs"], "stream">, options?: AiOptions): Promise<Response>; /** Stream any Cloudflare AI Gateway model response as SSE */
|
|
36
|
+
stream(model: string, inputs: Record<string, unknown>, options?: AiOptions): Promise<Response>;
|
|
117
37
|
};
|
|
118
38
|
declare const ai: VoidAi;
|
|
119
39
|
//#endregion
|
|
120
|
-
export { type
|
|
40
|
+
export { type AiProvider, type AiProviderFetchInit, type AiProviderName, type AiProviderOptions, type ImageFileInput, VoidAi, ai };
|
package/dist/runtime/ai.mjs
CHANGED
|
@@ -32,9 +32,6 @@ const VOID_FILE_MARKER = "__voidFile";
|
|
|
32
32
|
function hasStreamFlag(value) {
|
|
33
33
|
return typeof value === "object" && value !== null && "stream" in value;
|
|
34
34
|
}
|
|
35
|
-
function isThirdPartyModel(model) {
|
|
36
|
-
return model.split("/")[0] in PROVIDER_KEY_MAP;
|
|
37
|
-
}
|
|
38
35
|
function mediaTypeFromContentType(contentType) {
|
|
39
36
|
if (!contentType) return null;
|
|
40
37
|
const [mediaType] = contentType.split(";", 1);
|
|
@@ -109,8 +106,70 @@ async function serializeProxyValue(value, serializeDataObjectAsFile = false) {
|
|
|
109
106
|
}
|
|
110
107
|
return value;
|
|
111
108
|
}
|
|
112
|
-
|
|
113
|
-
return
|
|
109
|
+
function formFileName(value, fallback) {
|
|
110
|
+
return fileNameFromBlob(value) ?? fallback;
|
|
111
|
+
}
|
|
112
|
+
async function serializeFormData(form) {
|
|
113
|
+
const entries = [];
|
|
114
|
+
for (const [name, value] of form.entries()) {
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
entries.push({
|
|
117
|
+
name,
|
|
118
|
+
value
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
entries.push({
|
|
123
|
+
name,
|
|
124
|
+
value: {
|
|
125
|
+
[VOID_FILE_MARKER]: true,
|
|
126
|
+
data: arrayBufferToBase64(await value.arrayBuffer()),
|
|
127
|
+
name: formFileName(value, name),
|
|
128
|
+
type: value.type || void 0
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
type: "form",
|
|
134
|
+
entries
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function serializeProviderBody(body) {
|
|
138
|
+
if (body === void 0 || body === null) return { type: "none" };
|
|
139
|
+
if (typeof body === "string") return {
|
|
140
|
+
type: "text",
|
|
141
|
+
data: body
|
|
142
|
+
};
|
|
143
|
+
if (body instanceof URLSearchParams) return {
|
|
144
|
+
type: "text",
|
|
145
|
+
data: body.toString(),
|
|
146
|
+
contentType: "application/x-www-form-urlencoded;charset=UTF-8"
|
|
147
|
+
};
|
|
148
|
+
if (body instanceof FormData) return serializeFormData(body);
|
|
149
|
+
if (body instanceof Blob) return {
|
|
150
|
+
type: "base64",
|
|
151
|
+
data: arrayBufferToBase64(await body.arrayBuffer()),
|
|
152
|
+
contentType: body.type || void 0
|
|
153
|
+
};
|
|
154
|
+
if (body instanceof ArrayBuffer) return {
|
|
155
|
+
type: "base64",
|
|
156
|
+
data: arrayBufferToBase64(body)
|
|
157
|
+
};
|
|
158
|
+
if (isBinaryView(body)) return {
|
|
159
|
+
type: "base64",
|
|
160
|
+
data: arrayBufferToBase64(copyBinaryView(body))
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
type: "json",
|
|
164
|
+
value: await serializeProxyValue(body)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function headersToRecord(headers) {
|
|
168
|
+
if (!headers) return;
|
|
169
|
+
const result = {};
|
|
170
|
+
const normalized = new Headers(headers);
|
|
171
|
+
for (const [key, value] of normalized.entries()) result[key] = value;
|
|
172
|
+
return result;
|
|
114
173
|
}
|
|
115
174
|
async function responseFromResult(result) {
|
|
116
175
|
if (result instanceof Response) return result;
|
|
@@ -132,9 +191,8 @@ function splitImageOptions(options) {
|
|
|
132
191
|
runOptions: Object.keys(runOptions).length > 0 ? runOptions : void 0
|
|
133
192
|
};
|
|
134
193
|
}
|
|
135
|
-
function resolveProviderKey(
|
|
136
|
-
const
|
|
137
|
-
const envVar = PROVIDER_KEY_MAP[provider];
|
|
194
|
+
function resolveProviderKey(provider, runtimeEnv, options) {
|
|
195
|
+
const envVar = options?.apiKeyEnv ?? PROVIDER_KEY_MAP[provider];
|
|
138
196
|
if (!envVar) throw new Error(`ai: Unknown provider '${provider}'. Use one of: ${Object.keys(PROVIDER_KEY_MAP).join(", ")}.`);
|
|
139
197
|
const key = runtimeEnv[envVar];
|
|
140
198
|
if (typeof key !== "string" || key.length === 0) throw new Error(`ai: Missing ${envVar} secret. Add it as a project secret for provider '${provider}'.`);
|
|
@@ -159,6 +217,7 @@ function resolveBackend() {
|
|
|
159
217
|
const { runOptions } = splitImageOptions(options);
|
|
160
218
|
return responseFromResult(await directAi.run(model, inputs, runOptions));
|
|
161
219
|
},
|
|
220
|
+
providerFetch: unsupportedProviderFetch,
|
|
162
221
|
models: directAi.models.bind(directAi),
|
|
163
222
|
toMarkdown: directAi.toMarkdown?.bind(directAi) ?? unsupported("toMarkdown")
|
|
164
223
|
};
|
|
@@ -169,8 +228,23 @@ function unsupported(method) {
|
|
|
169
228
|
throw new Error(`ai: ai.${method}() is not supported in this environment.`);
|
|
170
229
|
};
|
|
171
230
|
}
|
|
231
|
+
function unsupportedProviderFetch() {
|
|
232
|
+
throw new Error("ai: ai.provider().fetch() is not supported in this environment.");
|
|
233
|
+
}
|
|
234
|
+
async function providerRequestBody(provider, path, init, options, runtimeEnv) {
|
|
235
|
+
return {
|
|
236
|
+
provider,
|
|
237
|
+
path,
|
|
238
|
+
method: init?.method,
|
|
239
|
+
headers: headersToRecord(init?.headers),
|
|
240
|
+
body: await serializeProviderBody(init?.body),
|
|
241
|
+
providerKey: resolveProviderKey(provider, runtimeEnv, options),
|
|
242
|
+
apiKeyHeader: options?.apiKeyHeader,
|
|
243
|
+
apiKeyPrefix: options?.apiKeyPrefix
|
|
244
|
+
};
|
|
245
|
+
}
|
|
172
246
|
function makeServiceBindingBackend(proxy, runtimeEnv) {
|
|
173
|
-
async function proxyFetch(action, body) {
|
|
247
|
+
async function proxyFetch(action, body, options = {}) {
|
|
174
248
|
const res = await proxy.fetch(`https://ai-proxy/ai/${action}`, {
|
|
175
249
|
method: "POST",
|
|
176
250
|
headers: { "content-type": "application/json" },
|
|
@@ -179,7 +253,7 @@ function makeServiceBindingBackend(proxy, runtimeEnv) {
|
|
|
179
253
|
...body
|
|
180
254
|
})
|
|
181
255
|
});
|
|
182
|
-
if (!res.ok) {
|
|
256
|
+
if (options.throwOnError !== false && !res.ok) {
|
|
183
257
|
const text = await res.text();
|
|
184
258
|
throw new Error(`ai: Proxy request failed with ${res.status}: '${text}'.`);
|
|
185
259
|
}
|
|
@@ -189,17 +263,6 @@ function makeServiceBindingBackend(proxy, runtimeEnv) {
|
|
|
189
263
|
async run(model, inputs, options) {
|
|
190
264
|
const projectId = runtimeEnv.__PROJECT_ID;
|
|
191
265
|
const aiToken = runtimeEnv.__VOID_PROXY_TOKEN;
|
|
192
|
-
if (isThirdPartyModel(model)) {
|
|
193
|
-
const res = await proxyFetch("gateway", {
|
|
194
|
-
model,
|
|
195
|
-
inputs,
|
|
196
|
-
providerKey: resolveProviderKey(model, runtimeEnv),
|
|
197
|
-
projectId,
|
|
198
|
-
aiToken
|
|
199
|
-
});
|
|
200
|
-
if (hasStreamFlag(inputs) && inputs.stream) return res.body;
|
|
201
|
-
return readProxyResult(res);
|
|
202
|
-
}
|
|
203
266
|
const res = await proxyFetch("run", {
|
|
204
267
|
model,
|
|
205
268
|
inputs,
|
|
@@ -213,18 +276,7 @@ function makeServiceBindingBackend(proxy, runtimeEnv) {
|
|
|
213
276
|
async image(model, inputs, options) {
|
|
214
277
|
const projectId = runtimeEnv.__PROJECT_ID;
|
|
215
278
|
const aiToken = runtimeEnv.__VOID_PROXY_TOKEN;
|
|
216
|
-
const {
|
|
217
|
-
if (isThirdPartyModel(model)) {
|
|
218
|
-
const providerKey = resolveProviderKey(model, runtimeEnv);
|
|
219
|
-
return proxyFetch("gateway", {
|
|
220
|
-
model,
|
|
221
|
-
endpoint,
|
|
222
|
-
inputs: await serializeImageInputs(inputs, endpoint),
|
|
223
|
-
providerKey,
|
|
224
|
-
projectId,
|
|
225
|
-
aiToken
|
|
226
|
-
});
|
|
227
|
-
}
|
|
279
|
+
const { runOptions } = splitImageOptions(options);
|
|
228
280
|
return proxyFetch("run", {
|
|
229
281
|
model,
|
|
230
282
|
inputs,
|
|
@@ -233,6 +285,15 @@ function makeServiceBindingBackend(proxy, runtimeEnv) {
|
|
|
233
285
|
aiToken
|
|
234
286
|
});
|
|
235
287
|
},
|
|
288
|
+
async providerFetch(provider, path, init, options) {
|
|
289
|
+
const projectId = runtimeEnv.__PROJECT_ID;
|
|
290
|
+
const aiToken = runtimeEnv.__VOID_PROXY_TOKEN;
|
|
291
|
+
return proxyFetch("provider", {
|
|
292
|
+
...await providerRequestBody(provider, path, init, options, runtimeEnv),
|
|
293
|
+
projectId,
|
|
294
|
+
aiToken
|
|
295
|
+
}, { throwOnError: false });
|
|
296
|
+
},
|
|
236
297
|
async models(params) {
|
|
237
298
|
const projectId = runtimeEnv.__PROJECT_ID;
|
|
238
299
|
const aiToken = runtimeEnv.__VOID_PROXY_TOKEN;
|
|
@@ -261,7 +322,7 @@ function makeHttpBackend(token, projectId, runtimeEnv) {
|
|
|
261
322
|
authorization: `Bearer ${token}`,
|
|
262
323
|
...cfAccessHeaders(runtimeEnv)
|
|
263
324
|
};
|
|
264
|
-
async function proxyFetch(action, body) {
|
|
325
|
+
async function proxyFetch(action, body, options = {}) {
|
|
265
326
|
const res = await fetch(`${base}/${action}`, {
|
|
266
327
|
method: "POST",
|
|
267
328
|
headers,
|
|
@@ -271,7 +332,7 @@ function makeHttpBackend(token, projectId, runtimeEnv) {
|
|
|
271
332
|
projectId
|
|
272
333
|
})
|
|
273
334
|
});
|
|
274
|
-
if (!res.ok) {
|
|
335
|
+
if (options.throwOnError !== false && !res.ok) {
|
|
275
336
|
const text = await res.text();
|
|
276
337
|
throw new Error(`ai: Proxy request failed with ${res.status}: '${text}'.`);
|
|
277
338
|
}
|
|
@@ -279,15 +340,6 @@ function makeHttpBackend(token, projectId, runtimeEnv) {
|
|
|
279
340
|
}
|
|
280
341
|
return {
|
|
281
342
|
async run(model, inputs, options) {
|
|
282
|
-
if (isThirdPartyModel(model)) {
|
|
283
|
-
const res = await proxyFetch("gateway", {
|
|
284
|
-
model,
|
|
285
|
-
inputs,
|
|
286
|
-
providerKey: resolveProviderKey(model, runtimeEnv)
|
|
287
|
-
});
|
|
288
|
-
if (hasStreamFlag(inputs) && inputs.stream) return res.body;
|
|
289
|
-
return readProxyResult(res);
|
|
290
|
-
}
|
|
291
343
|
const res = await proxyFetch("run", {
|
|
292
344
|
model,
|
|
293
345
|
inputs,
|
|
@@ -297,22 +349,16 @@ function makeHttpBackend(token, projectId, runtimeEnv) {
|
|
|
297
349
|
return readProxyResult(res);
|
|
298
350
|
},
|
|
299
351
|
async image(model, inputs, options) {
|
|
300
|
-
const {
|
|
301
|
-
if (isThirdPartyModel(model)) {
|
|
302
|
-
const providerKey = resolveProviderKey(model, runtimeEnv);
|
|
303
|
-
return proxyFetch("gateway", {
|
|
304
|
-
model,
|
|
305
|
-
endpoint,
|
|
306
|
-
inputs: await serializeImageInputs(inputs, endpoint),
|
|
307
|
-
providerKey
|
|
308
|
-
});
|
|
309
|
-
}
|
|
352
|
+
const { runOptions } = splitImageOptions(options);
|
|
310
353
|
return proxyFetch("run", {
|
|
311
354
|
model,
|
|
312
355
|
inputs,
|
|
313
356
|
options: runOptions
|
|
314
357
|
});
|
|
315
358
|
},
|
|
359
|
+
async providerFetch(provider, path, init, options) {
|
|
360
|
+
return proxyFetch("provider", await providerRequestBody(provider, path, init, options, runtimeEnv), { throwOnError: false });
|
|
361
|
+
},
|
|
316
362
|
async models(params) {
|
|
317
363
|
return (await proxyFetch("models", { inputs: params })).json();
|
|
318
364
|
},
|
|
@@ -341,6 +387,7 @@ const ai = new Proxy({}, { get(_, prop) {
|
|
|
341
387
|
};
|
|
342
388
|
if (prop === "run") return backend().run;
|
|
343
389
|
if (prop === "image") return backend().image;
|
|
390
|
+
if (prop === "provider") return (provider, options) => ({ fetch: (path, init) => backend().providerFetch(provider, path, init, options) });
|
|
344
391
|
if (prop === "models") return backend().models;
|
|
345
392
|
if (prop === "toMarkdown") return backend().toMarkdown;
|
|
346
393
|
} });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "void",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
"valibot": ">=1.0.0-beta.7",
|
|
354
354
|
"vite": "^8.0.0",
|
|
355
355
|
"zod": "^3.25.0 || ^4.0.0",
|
|
356
|
-
"@void/md": "0.9.
|
|
356
|
+
"@void/md": "0.9.2"
|
|
357
357
|
},
|
|
358
358
|
"peerDependenciesMeta": {
|
|
359
359
|
"@void/md": {
|
|
@@ -4,7 +4,7 @@ outline: deep
|
|
|
4
4
|
|
|
5
5
|
# AI
|
|
6
6
|
|
|
7
|
-
Void provides a typed AI client powered by Cloudflare's [AI Gateway](https://developers.cloudflare.com/ai-gateway/).
|
|
7
|
+
Void provides a typed AI client powered by Cloudflare's [AI Gateway](https://developers.cloudflare.com/ai-gateway/). Import `ai` from `void/ai` and run inference directly from your route handlers. Usage is metered through Void.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { ai } from 'void/ai';
|
|
@@ -29,7 +29,7 @@ export const POST = defineHandler(async (c) => {
|
|
|
29
29
|
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
You can use any model available
|
|
32
|
+
You can use any model available through Cloudflare's AI binding, including Workers AI models such as `@cf/meta/llama-3.1-8b-instruct` and Cloudflare Gateway models such as `google/gemini-2.5-flash` or `openai/gpt-4.1-mini`. The input object must match the selected Cloudflare model's schema. Models that return binary data, such as generated images, are returned as a `Blob` from `ai.run()`.
|
|
33
33
|
|
|
34
34
|
## Streaming
|
|
35
35
|
|
|
@@ -87,11 +87,52 @@ Workers AI usage is metered in [**neurons**](https://developers.cloudflare.com/w
|
|
|
87
87
|
|
|
88
88
|
On the **free tier**, AI requests return a `429` error once the limit is reached. On **paid tiers**, usage beyond the included allowance is tracked as overage on your monthly bill.
|
|
89
89
|
|
|
90
|
-
##
|
|
90
|
+
## Cloudflare Gateway Models
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
`ai.run()` mirrors Cloudflare's `env.AI.run()` model naming and input schemas. Third-party models use Cloudflare model IDs and Cloudflare-managed credentials.
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
```ts
|
|
95
|
+
const result = await ai.run('google/gemini-2.5-flash', {
|
|
96
|
+
contents: [
|
|
97
|
+
{
|
|
98
|
+
role: 'user',
|
|
99
|
+
parts: [{ text: 'Explain Durable Objects in one paragraph.' }],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
OpenAI-compatible models use OpenAI-style `messages`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const result = await ai.run('openai/gpt-4.1-mini', {
|
|
109
|
+
messages: [{ role: 'user', content: 'Summarize this deploy.' }],
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Pass Cloudflare AI Gateway options as the third argument:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const result = await ai.run(
|
|
117
|
+
'openai/gpt-4.1-mini',
|
|
118
|
+
{
|
|
119
|
+
messages: [{ role: 'user', content: 'Summarize this deploy.' }],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
gateway: {
|
|
123
|
+
skipCache: true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Void always injects the `void` gateway ID and project metadata for metering.
|
|
130
|
+
|
|
131
|
+
## Provider-Native Requests
|
|
132
|
+
|
|
133
|
+
Use `ai.provider(provider).fetch(path, init)` when you want to call a provider-native API with your own provider key. The request still routes through Cloudflare AI Gateway and Void metering, but the request shape is the provider's native HTTP API.
|
|
134
|
+
|
|
135
|
+
### OpenAI
|
|
95
136
|
|
|
96
137
|
```ts
|
|
97
138
|
import { defineHandler } from 'void';
|
|
@@ -100,85 +141,99 @@ import { ai } from 'void/ai';
|
|
|
100
141
|
export const POST = defineHandler(async (c) => {
|
|
101
142
|
const { prompt } = await c.req.json();
|
|
102
143
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
144
|
+
const response = await ai.provider('openai').fetch('/chat/completions', {
|
|
145
|
+
body: {
|
|
146
|
+
model: 'gpt-4o',
|
|
147
|
+
messages: [{ role: 'user', content: prompt }],
|
|
148
|
+
max_tokens: 512,
|
|
149
|
+
},
|
|
106
150
|
});
|
|
107
151
|
|
|
152
|
+
const result = await response.json();
|
|
108
153
|
return c.json(result);
|
|
109
154
|
});
|
|
110
155
|
```
|
|
111
156
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
The options follow each provider's conventions. All third-party providers use the OpenAI-compatible chat completions format, including `messages`, `max_tokens`, and `temperature`. TypeScript narrows the input and return types based on the model string: Workers AI models get per-model typed inputs from `@cloudflare/workers-types`, while third-party models such as `"provider/model"` get `ChatCompletionInputs` and `ChatCompletionResponse`.
|
|
115
|
-
|
|
116
|
-
### Vision
|
|
117
|
-
|
|
118
|
-
Third-party chat models that accept image inputs can receive OpenAI-compatible multimodal message content:
|
|
157
|
+
### Google AI Studio
|
|
119
158
|
|
|
120
159
|
```ts
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{ type: 'text', text: 'What is in this image?' },
|
|
160
|
+
const response = await ai
|
|
161
|
+
.provider('google-ai-studio')
|
|
162
|
+
.fetch('/v1/models/gemini-2.5-flash:generateContent', {
|
|
163
|
+
body: {
|
|
164
|
+
contents: [
|
|
127
165
|
{
|
|
128
|
-
|
|
129
|
-
|
|
166
|
+
role: 'user',
|
|
167
|
+
parts: [{ text: 'What is Cloudflare?' }],
|
|
130
168
|
},
|
|
131
169
|
],
|
|
132
170
|
},
|
|
133
|
-
|
|
134
|
-
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const result = await response.json();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Custom Providers
|
|
177
|
+
|
|
178
|
+
For providers that are not in Void's default key map, pass the secret name and API-key header:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const response = await ai
|
|
182
|
+
.provider('custom-provider', {
|
|
183
|
+
apiKeyEnv: 'CUSTOM_PROVIDER_API_KEY',
|
|
184
|
+
apiKeyHeader: 'x-api-key',
|
|
185
|
+
apiKeyPrefix: '',
|
|
186
|
+
})
|
|
187
|
+
.fetch('/v1/respond', {
|
|
188
|
+
body: { prompt: 'Hello' },
|
|
189
|
+
});
|
|
135
190
|
```
|
|
136
191
|
|
|
137
192
|
### Image Generation
|
|
138
193
|
|
|
139
|
-
Use `ai.image()` for
|
|
194
|
+
Use `ai.run()` or `ai.image()` for Cloudflare-native image models:
|
|
140
195
|
|
|
141
196
|
```ts
|
|
142
197
|
export const POST = defineHandler(async (c) => {
|
|
143
198
|
const { prompt } = await c.req.json();
|
|
144
|
-
|
|
145
|
-
return ai.image('openai/gpt-image-1.5', {
|
|
146
|
-
prompt,
|
|
147
|
-
size: '1024x1024',
|
|
148
|
-
response_format: 'b64_json',
|
|
149
|
-
});
|
|
199
|
+
return ai.image('@cf/black-forest-labs/flux-1-schnell', { prompt });
|
|
150
200
|
});
|
|
151
201
|
```
|
|
152
202
|
|
|
153
|
-
|
|
203
|
+
Use `ai.provider().fetch()` for provider-native image APIs:
|
|
154
204
|
|
|
155
205
|
```ts
|
|
156
206
|
export const POST = defineHandler(async (c) => {
|
|
157
207
|
const { prompt } = await c.req.json();
|
|
158
|
-
|
|
208
|
+
|
|
209
|
+
return ai.provider('openai').fetch('/images/generations', {
|
|
210
|
+
body: {
|
|
211
|
+
model: 'gpt-image-1.5',
|
|
212
|
+
prompt,
|
|
213
|
+
size: '1024x1024',
|
|
214
|
+
response_format: 'b64_json',
|
|
215
|
+
},
|
|
216
|
+
});
|
|
159
217
|
});
|
|
160
218
|
```
|
|
161
219
|
|
|
162
|
-
For
|
|
220
|
+
For multipart provider APIs, pass a `FormData` body. Void serializes the body through the proxy and reconstructs it before forwarding to AI Gateway:
|
|
163
221
|
|
|
164
222
|
```ts
|
|
165
223
|
export const POST = defineHandler(async (c) => {
|
|
166
224
|
const body = await c.req.parseBody();
|
|
225
|
+
const form = new FormData();
|
|
226
|
+
form.set('model', 'gpt-image-1.5');
|
|
227
|
+
form.set('prompt', String(body.prompt));
|
|
228
|
+
form.set('image', body.image as Blob, 'source.png');
|
|
167
229
|
|
|
168
|
-
return ai.
|
|
169
|
-
'openai/gpt-image-1.5',
|
|
170
|
-
{
|
|
171
|
-
prompt: String(body.prompt),
|
|
172
|
-
image: body.image as Blob,
|
|
173
|
-
},
|
|
174
|
-
{ endpoint: 'images/edits' },
|
|
175
|
-
);
|
|
230
|
+
return ai.provider('openai').fetch('/images/edits', { body: form });
|
|
176
231
|
});
|
|
177
232
|
```
|
|
178
233
|
|
|
179
234
|
### Provider Key Convention
|
|
180
235
|
|
|
181
|
-
|
|
236
|
+
Provider-native requests require an API key set as a project secret. The env var name is automatically derived from the provider name:
|
|
182
237
|
|
|
183
238
|
| Provider prefix | Env var |
|
|
184
239
|
| ------------------ | --------------------- |
|
|
@@ -203,7 +258,7 @@ Each provider requires an API key set as a project secret. The env var name is a
|
|
|
203
258
|
| `ideogram` | `IDEOGRAM_API_KEY` |
|
|
204
259
|
| `parallel` | `PARALLEL_API_KEY` |
|
|
205
260
|
|
|
206
|
-
|
|
261
|
+
OpenAI-style providers use `Authorization: Bearer <key>`. Google AI Studio uses `x-goog-api-key`. Use `apiKeyHeader` and `apiKeyPrefix` for custom providers.
|
|
207
262
|
|
|
208
263
|
For production, add your API key as a project secret:
|
|
209
264
|
|
|
@@ -217,19 +272,22 @@ For local development, add it to `.env.local` in your project root:
|
|
|
217
272
|
OPENAI_API_KEY=sk-...
|
|
218
273
|
```
|
|
219
274
|
|
|
220
|
-
If the key is missing at runtime, `ai.
|
|
275
|
+
If the key is missing at runtime, `ai.provider().fetch()` throws a descriptive error telling you which env var to set.
|
|
221
276
|
|
|
222
|
-
### Streaming with
|
|
277
|
+
### Streaming with Provider-Native APIs
|
|
223
278
|
|
|
224
|
-
|
|
279
|
+
Provider-native streaming APIs return the provider response directly:
|
|
225
280
|
|
|
226
281
|
```ts
|
|
227
282
|
export const POST = defineHandler(async (c) => {
|
|
228
283
|
const { prompt } = await c.req.json();
|
|
229
284
|
|
|
230
|
-
return ai.
|
|
231
|
-
|
|
232
|
-
|
|
285
|
+
return ai.provider('openai').fetch('/chat/completions', {
|
|
286
|
+
body: {
|
|
287
|
+
model: 'gpt-4o',
|
|
288
|
+
messages: [{ role: 'user', content: prompt }],
|
|
289
|
+
stream: true,
|
|
290
|
+
},
|
|
233
291
|
});
|
|
234
292
|
});
|
|
235
293
|
```
|
|
@@ -101,7 +101,7 @@ src/
|
|
|
101
101
|
| `void/ws` | `defineRoom()`, `defineWebSocket()`, typed `connect()`, and WebSocket context types |
|
|
102
102
|
| `void/db` | `db` Drizzle D1 instance (auto-wired with user schema) + `createDb()` for custom D1 bindings |
|
|
103
103
|
| `void/queues` | `queues` typed proxy + `QueueMap` stub interface (augmented by generated `queues.d.ts`) |
|
|
104
|
-
| `void/ai` | `ai` proxy — `ai.run()`, `ai.
|
|
104
|
+
| `void/ai` | `ai` proxy — Cloudflare-native `ai.run()`/`ai.stream()`, provider-native `ai.provider().fetch()`, `ai.models()` |
|
|
105
105
|
| `void/log` | `logger.error/warn/info(msg, fields?)` — emits stringified JSON to `console.*` so Cloudflare Tail captures level + msg |
|
|
106
106
|
| `void/env` | `defineEnv()`, typed `env` proxy, built-in schema helpers (`string`, `number`, `oneOf`, …) + global Cloudflare types |
|
|
107
107
|
|
|
@@ -675,7 +675,7 @@ Imported from `"void/ai"`.
|
|
|
675
675
|
|
|
676
676
|
### `ai`
|
|
677
677
|
|
|
678
|
-
Typed AI client for
|
|
678
|
+
Typed AI client for Cloudflare AI models and provider-native AI Gateway requests.
|
|
679
679
|
|
|
680
680
|
```ts
|
|
681
681
|
import { ai } from 'void/ai';
|
|
@@ -683,9 +683,16 @@ import { ai } from 'void/ai';
|
|
|
683
683
|
const result = await ai.run('@cf/meta/llama-3.1-8b-instruct', {
|
|
684
684
|
messages: [{ role: 'user', content: 'Summarize this release note.' }],
|
|
685
685
|
});
|
|
686
|
+
|
|
687
|
+
const response = await ai.provider('openai').fetch('/chat/completions', {
|
|
688
|
+
body: {
|
|
689
|
+
model: 'gpt-4o',
|
|
690
|
+
messages: [{ role: 'user', content: 'Summarize this release note.' }],
|
|
691
|
+
},
|
|
692
|
+
});
|
|
686
693
|
```
|
|
687
694
|
|
|
688
|
-
**Key exports:** `ai`, `VoidAi`,
|
|
695
|
+
**Key exports:** `ai`, `VoidAi`, provider request types. See [AI](../guide/ai.md) for provider setup and streaming examples.
|
|
689
696
|
|
|
690
697
|
## ISR
|
|
691
698
|
|
|
@@ -572,7 +572,9 @@ See [Environment Variables](../guide/env-vars.md) for the full guide.
|
|
|
572
572
|
void domain add <hostname> [--project <name>]
|
|
573
573
|
```
|
|
574
574
|
|
|
575
|
-
Add a custom domain to a project. Prints the
|
|
575
|
+
Add a custom domain to a project. Prints the two DNS records to add at your DNS provider: a traffic **CNAME** pointing `<hostname>` at the CNAME target shown in the command output, and a non-rotating `_cf-custom-hostname` ownership **TXT**. Certificates are validated over HTTP at Cloudflare's edge and renew automatically — there are no `_acme-challenge` records to publish, at first issuance or ever. After adding the records the domain activates automatically (no polling required); run `void domain status <hostname>` to check progress.
|
|
576
|
+
|
|
577
|
+
> Wildcard custom hostnames (`*.example.com`) are not supported — register each subdomain individually.
|
|
576
578
|
|
|
577
579
|
### `void domain delete`
|
|
578
580
|
|
|
@@ -596,7 +598,7 @@ List all custom domains and their status (active/pending).
|
|
|
596
598
|
void domain status <hostname> [--project <name>] [--verbose]
|
|
597
599
|
```
|
|
598
600
|
|
|
599
|
-
Check verification and SSL status for a specific domain. Prints a rolled-up state (`awaiting_dns`, `verifying_dns`, `issuing_cert`, `deploying_cert`, `awaiting_deployment`, `active`, `error`, or `pending`) with a one-line diagnostic explaining what Cloudflare is doing and any user action required.
|
|
601
|
+
Check verification and SSL status for a specific domain. Prints a rolled-up state (`awaiting_dns`, `verifying_dns`, `issuing_cert`, `deploying_cert`, `awaiting_deployment`, `active`, `error`, or `pending`) with a one-line diagnostic explaining what Cloudflare is doing and any user action required. While the certificate is not yet active, the command also surfaces the records to configure — the traffic **CNAME** and the `_cf-custom-hostname` ownership **TXT** — so you can verify them. Activation is automatic: a background job reconciles pending domains (about every 2 minutes for the first 30 minutes after adding, then hourly), so this command is for instant feedback rather than required polling.
|
|
600
602
|
|
|
601
603
|
Pass `--verbose` to additionally print the raw multi-line status breakdown (DB status, SSL status, ownership state, verification errors) underneath the rollup.
|
|
602
604
|
|