@getjack/jack 0.1.33 → 0.1.35
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/README.md +6 -6
- package/package.json +1 -1
- package/src/commands/down.ts +39 -7
- package/src/commands/link.ts +2 -4
- package/src/commands/logs.ts +2 -4
- package/src/commands/mcp.ts +12 -10
- package/src/commands/secrets.ts +3 -1
- package/src/commands/services.ts +4 -2
- package/src/commands/sync.ts +5 -6
- package/src/lib/auth/client.ts +5 -2
- package/src/lib/binding-validator.ts +39 -3
- package/src/lib/build-helper.ts +18 -19
- package/src/lib/control-plane.ts +1 -0
- package/src/lib/crypto.ts +84 -0
- package/src/lib/deploy-upload.ts +7 -3
- package/src/lib/do-config.ts +110 -0
- package/src/lib/do-export-validator.ts +26 -0
- package/src/lib/hooks.ts +1 -2
- package/src/lib/jsonc-edit.ts +292 -0
- package/src/lib/managed-deploy.ts +36 -1
- package/src/lib/project-link.ts +37 -0
- package/src/lib/project-operations.ts +37 -46
- package/src/lib/prompts.ts +2 -2
- package/src/lib/resources.ts +4 -5
- package/src/lib/schema.ts +8 -12
- package/src/lib/services/db-create.ts +2 -2
- package/src/lib/services/db-execute.ts +9 -6
- package/src/lib/services/db-list.ts +6 -4
- package/src/lib/services/endpoint-test.ts +275 -0
- package/src/lib/services/project-delete.ts +190 -0
- package/src/lib/services/project-environment.ts +457 -0
- package/src/lib/services/storage-config.ts +7 -309
- package/src/lib/services/storage-create.ts +2 -1
- package/src/lib/services/storage-delete.ts +3 -2
- package/src/lib/services/storage-info.ts +2 -1
- package/src/lib/services/storage-list.ts +6 -3
- package/src/lib/services/vectorize-config.ts +7 -264
- package/src/lib/services/vectorize-create.ts +2 -1
- package/src/lib/services/vectorize-delete.ts +6 -4
- package/src/lib/services/vectorize-list.ts +6 -3
- package/src/lib/storage/index.ts +21 -23
- package/src/lib/telemetry.ts +1 -0
- package/src/lib/wrangler-config.ts +43 -312
- package/src/lib/zip-packager.ts +28 -0
- package/src/mcp/test-utils.ts +31 -0
- package/src/mcp/tools/index.ts +271 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/types.ts +4 -0
- package/templates/AI-BINDINGS.md +34 -76
- package/templates/CLAUDE.md +22 -1
- package/templates/ai-chat/src/index.ts +7 -14
- package/templates/ai-chat/src/jack-ai.ts +0 -6
- package/templates/chat/.jack.json +45 -0
- package/templates/chat/bun.lock +1588 -0
- package/templates/chat/components.json +23 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +41 -0
- package/templates/chat/src/chat-agent.ts +61 -0
- package/templates/chat/src/client/app.tsx +189 -0
- package/templates/chat/src/client/chat.tsx +222 -0
- package/templates/chat/src/client/components/prompt-kit/chat-container.tsx +47 -0
- package/templates/chat/src/client/components/prompt-kit/loader.tsx +33 -0
- package/templates/chat/src/client/components/prompt-kit/markdown.tsx +84 -0
- package/templates/chat/src/client/components/prompt-kit/message.tsx +54 -0
- package/templates/chat/src/client/components/prompt-kit/prompt-suggestion.tsx +20 -0
- package/templates/chat/src/client/components/prompt-kit/reasoning.tsx +134 -0
- package/templates/chat/src/client/components/prompt-kit/scroll-button.tsx +28 -0
- package/templates/chat/src/client/components/ui/button.tsx +38 -0
- package/templates/chat/src/client/lib/utils.ts +6 -0
- package/templates/chat/src/client/main.tsx +11 -0
- package/templates/chat/src/client/styles.css +125 -0
- package/templates/chat/src/index.ts +25 -0
- package/templates/chat/src/jack-ai.ts +94 -0
- package/templates/chat/tsconfig.json +18 -0
- package/templates/chat/vite.config.ts +14 -0
- package/templates/chat/wrangler.jsonc +18 -0
- package/templates/cron/.jack.json +18 -28
- package/templates/cron/schema.sql +10 -20
- package/templates/cron/src/admin.ts +321 -0
- package/templates/cron/src/index.ts +151 -81
- package/templates/cron/src/monitor.ts +124 -0
- package/templates/nextjs-clerk/app/layout.tsx +2 -0
- package/templates/semantic-search/src/index.ts +5 -43
- package/templates/semantic-search/src/jack-ai.ts +0 -6
- package/templates/telegram-bot/.jack.json +56 -0
- package/templates/telegram-bot/bun.lock +41 -0
- package/templates/telegram-bot/package.json +16 -0
- package/templates/telegram-bot/src/index.ts +236 -0
- package/templates/telegram-bot/src/jack-ai.ts +100 -0
- package/templates/telegram-bot/tsconfig.json +11 -0
- package/templates/telegram-bot/wrangler.jsonc +8 -0
- package/templates/cron/src/jobs.ts +0 -139
- package/templates/cron/src/webhooks.ts +0 -95
- package/templates/semantic-search/src/jack-vectorize.ts +0 -169
package/src/mcp/tools/index.ts
CHANGED
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
listDomains,
|
|
27
27
|
unassignDomain,
|
|
28
28
|
} from "../../lib/services/domain-operations.ts";
|
|
29
|
+
import { testEndpoint } from "../../lib/services/endpoint-test.ts";
|
|
30
|
+
import { deleteProject } from "../../lib/services/project-delete.ts";
|
|
31
|
+
import { getProjectEnvironment } from "../../lib/services/project-environment.ts";
|
|
29
32
|
import { createStorageBucket } from "../../lib/services/storage-create.ts";
|
|
30
33
|
import { deleteStorageBucket } from "../../lib/services/storage-delete.ts";
|
|
31
34
|
import { getStorageBucketInfo } from "../../lib/services/storage-info.ts";
|
|
@@ -224,6 +227,18 @@ const RollbackProjectSchema = z.object({
|
|
|
224
227
|
.describe("Path to project directory (defaults to current directory)"),
|
|
225
228
|
});
|
|
226
229
|
|
|
230
|
+
const DeleteProjectSchema = z.object({
|
|
231
|
+
project_path: z
|
|
232
|
+
.string()
|
|
233
|
+
.optional()
|
|
234
|
+
.describe("Path to project directory (defaults to current directory)"),
|
|
235
|
+
export_database: z
|
|
236
|
+
.boolean()
|
|
237
|
+
.optional()
|
|
238
|
+
.default(false)
|
|
239
|
+
.describe("Export database to a SQL file before deletion"),
|
|
240
|
+
});
|
|
241
|
+
|
|
227
242
|
const ListDomainsSchema = z.object({});
|
|
228
243
|
|
|
229
244
|
const ConnectDomainSchema = z.object({
|
|
@@ -281,6 +296,33 @@ const TestCronSchema = z.object({
|
|
|
281
296
|
.describe("Whether to trigger the cron handler on production (requires managed project)"),
|
|
282
297
|
});
|
|
283
298
|
|
|
299
|
+
const GetProjectEnvironmentSchema = z.object({
|
|
300
|
+
project_path: z
|
|
301
|
+
.string()
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Path to project directory (defaults to current directory)"),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const TestEndpointSchema = z.object({
|
|
307
|
+
project_path: z
|
|
308
|
+
.string()
|
|
309
|
+
.optional()
|
|
310
|
+
.describe("Path to project directory (defaults to current directory)"),
|
|
311
|
+
path: z.string().describe("URL path to test, e.g. /api/todos"),
|
|
312
|
+
method: z
|
|
313
|
+
.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])
|
|
314
|
+
.optional()
|
|
315
|
+
.default("GET")
|
|
316
|
+
.describe("HTTP method"),
|
|
317
|
+
headers: z.record(z.string()).optional().describe("Request headers"),
|
|
318
|
+
body: z.string().optional().describe("Request body (JSON string for POST/PUT/PATCH)"),
|
|
319
|
+
include_logs: z
|
|
320
|
+
.boolean()
|
|
321
|
+
.optional()
|
|
322
|
+
.default(true)
|
|
323
|
+
.describe("Capture runtime logs during the request (managed mode only)"),
|
|
324
|
+
});
|
|
325
|
+
|
|
284
326
|
export function registerTools(server: McpServer, _options: McpServerOptions, debug: DebugLogger) {
|
|
285
327
|
// Register tool list handler
|
|
286
328
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -423,6 +465,25 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
423
465
|
},
|
|
424
466
|
},
|
|
425
467
|
},
|
|
468
|
+
{
|
|
469
|
+
name: "delete_project",
|
|
470
|
+
description:
|
|
471
|
+
"Permanently delete a project and all its resources (worker, database). This is irreversible. Optionally export the database to a SQL file before deletion.",
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: "object",
|
|
474
|
+
properties: {
|
|
475
|
+
project_path: {
|
|
476
|
+
type: "string",
|
|
477
|
+
description: "Path to project directory (defaults to current directory)",
|
|
478
|
+
},
|
|
479
|
+
export_database: {
|
|
480
|
+
type: "boolean",
|
|
481
|
+
default: false,
|
|
482
|
+
description: "Export database to a SQL file before deletion",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
426
487
|
{
|
|
427
488
|
name: "create_database",
|
|
428
489
|
description:
|
|
@@ -780,6 +841,63 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
780
841
|
required: ["expression"],
|
|
781
842
|
},
|
|
782
843
|
},
|
|
844
|
+
{
|
|
845
|
+
name: "get_project_environment",
|
|
846
|
+
description:
|
|
847
|
+
"Get a consolidated snapshot of the project's runtime environment: deployed URL, bindings (D1, KV, R2, AI, Vectorize, Durable Objects), " +
|
|
848
|
+
"database schema with table definitions and row counts, cron schedules, environment variables, secret names, and configuration issues. " +
|
|
849
|
+
"Call this instead of making multiple separate calls to understand a project's full state.",
|
|
850
|
+
inputSchema: {
|
|
851
|
+
type: "object",
|
|
852
|
+
properties: {
|
|
853
|
+
project_path: {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "Path to project directory (defaults to current directory)",
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
name: "test_endpoint",
|
|
862
|
+
description:
|
|
863
|
+
"Test a deployed endpoint by making an HTTP request and capturing the response with optional runtime logs. " +
|
|
864
|
+
"Use after deploying to verify changes work, or to debug API behavior. " +
|
|
865
|
+
"Returns the full response (status, headers, body) and any console.log output from the worker.",
|
|
866
|
+
inputSchema: {
|
|
867
|
+
type: "object",
|
|
868
|
+
properties: {
|
|
869
|
+
project_path: {
|
|
870
|
+
type: "string",
|
|
871
|
+
description: "Path to project directory (defaults to current directory)",
|
|
872
|
+
},
|
|
873
|
+
path: {
|
|
874
|
+
type: "string",
|
|
875
|
+
description: "URL path to test, e.g. /api/todos",
|
|
876
|
+
},
|
|
877
|
+
method: {
|
|
878
|
+
type: "string",
|
|
879
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
880
|
+
default: "GET",
|
|
881
|
+
description: "HTTP method",
|
|
882
|
+
},
|
|
883
|
+
headers: {
|
|
884
|
+
type: "object",
|
|
885
|
+
additionalProperties: { type: "string" },
|
|
886
|
+
description: "Request headers",
|
|
887
|
+
},
|
|
888
|
+
body: {
|
|
889
|
+
type: "string",
|
|
890
|
+
description: "Request body (JSON string for POST/PUT/PATCH)",
|
|
891
|
+
},
|
|
892
|
+
include_logs: {
|
|
893
|
+
type: "boolean",
|
|
894
|
+
default: true,
|
|
895
|
+
description: "Capture runtime logs during the request (managed mode only)",
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
required: ["path"],
|
|
899
|
+
},
|
|
900
|
+
},
|
|
783
901
|
],
|
|
784
902
|
};
|
|
785
903
|
});
|
|
@@ -1156,6 +1274,48 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
1156
1274
|
};
|
|
1157
1275
|
}
|
|
1158
1276
|
|
|
1277
|
+
case "delete_project": {
|
|
1278
|
+
const args = DeleteProjectSchema.parse(request.params.arguments ?? {});
|
|
1279
|
+
const projectPath = args.project_path ?? process.cwd();
|
|
1280
|
+
|
|
1281
|
+
const wrappedDeleteProject = withTelemetry(
|
|
1282
|
+
"delete_project",
|
|
1283
|
+
async (projectDir: string, exportDatabase: boolean) => {
|
|
1284
|
+
const result = await deleteProject(projectDir, { exportDatabase });
|
|
1285
|
+
|
|
1286
|
+
track(Events.PROJECT_DELETED, {
|
|
1287
|
+
deploy_mode: result.deployMode,
|
|
1288
|
+
database_deleted: result.databaseDeleted,
|
|
1289
|
+
database_exported: result.databaseExportPath !== null,
|
|
1290
|
+
platform: "mcp",
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
return {
|
|
1294
|
+
project_name: result.projectName,
|
|
1295
|
+
deploy_mode: result.deployMode,
|
|
1296
|
+
worker_deleted: result.workerDeleted,
|
|
1297
|
+
database_deleted: result.databaseDeleted,
|
|
1298
|
+
database_name: result.databaseName,
|
|
1299
|
+
database_export_path: result.databaseExportPath,
|
|
1300
|
+
resource_results: result.resourceResults,
|
|
1301
|
+
warnings: result.warnings,
|
|
1302
|
+
};
|
|
1303
|
+
},
|
|
1304
|
+
{ platform: "mcp" },
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
const result = await wrappedDeleteProject(projectPath, args.export_database ?? false);
|
|
1308
|
+
|
|
1309
|
+
return {
|
|
1310
|
+
content: [
|
|
1311
|
+
{
|
|
1312
|
+
type: "text",
|
|
1313
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1314
|
+
},
|
|
1315
|
+
],
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1159
1319
|
case "create_database": {
|
|
1160
1320
|
const args = CreateDatabaseSchema.parse(request.params.arguments ?? {});
|
|
1161
1321
|
const projectPath = args.project_path ?? process.cwd();
|
|
@@ -1961,6 +2121,117 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
1961
2121
|
};
|
|
1962
2122
|
}
|
|
1963
2123
|
|
|
2124
|
+
case "get_project_environment": {
|
|
2125
|
+
const args = GetProjectEnvironmentSchema.parse(request.params.arguments ?? {});
|
|
2126
|
+
const projectPath = args.project_path ?? process.cwd();
|
|
2127
|
+
|
|
2128
|
+
const wrappedGetProjectEnvironment = withTelemetry(
|
|
2129
|
+
"get_project_environment",
|
|
2130
|
+
async (projectDir: string) => {
|
|
2131
|
+
const env = await getProjectEnvironment(projectDir);
|
|
2132
|
+
|
|
2133
|
+
track(Events.COMMAND_COMPLETED, {
|
|
2134
|
+
command: "get_project_environment",
|
|
2135
|
+
has_database: !!env.database,
|
|
2136
|
+
binding_count:
|
|
2137
|
+
(env.bindings.d1 ? 1 : 0) +
|
|
2138
|
+
(env.bindings.r2?.length ?? 0) +
|
|
2139
|
+
(env.bindings.kv?.length ?? 0) +
|
|
2140
|
+
(env.bindings.ai ? 1 : 0) +
|
|
2141
|
+
(env.bindings.vectorize?.length ?? 0) +
|
|
2142
|
+
(env.bindings.durable_objects?.length ?? 0),
|
|
2143
|
+
issue_count: env.issues.length,
|
|
2144
|
+
platform: "mcp",
|
|
2145
|
+
});
|
|
2146
|
+
|
|
2147
|
+
return env;
|
|
2148
|
+
},
|
|
2149
|
+
{ platform: "mcp" },
|
|
2150
|
+
);
|
|
2151
|
+
|
|
2152
|
+
const result = await wrappedGetProjectEnvironment(projectPath);
|
|
2153
|
+
|
|
2154
|
+
return {
|
|
2155
|
+
content: [
|
|
2156
|
+
{
|
|
2157
|
+
type: "text",
|
|
2158
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
2159
|
+
},
|
|
2160
|
+
],
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
case "test_endpoint": {
|
|
2165
|
+
const args = TestEndpointSchema.parse(request.params.arguments ?? {});
|
|
2166
|
+
const projectPath = args.project_path ?? process.cwd();
|
|
2167
|
+
|
|
2168
|
+
const wrappedTestEndpoint = withTelemetry(
|
|
2169
|
+
"test_endpoint",
|
|
2170
|
+
async (
|
|
2171
|
+
projectDir: string,
|
|
2172
|
+
path: string,
|
|
2173
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
|
2174
|
+
headers?: Record<string, string>,
|
|
2175
|
+
body?: string,
|
|
2176
|
+
includeLogs?: boolean,
|
|
2177
|
+
) => {
|
|
2178
|
+
const result = await testEndpoint({
|
|
2179
|
+
projectDir,
|
|
2180
|
+
path,
|
|
2181
|
+
method,
|
|
2182
|
+
headers,
|
|
2183
|
+
body,
|
|
2184
|
+
includeLogs,
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
track(Events.COMMAND_COMPLETED, {
|
|
2188
|
+
command: "test_endpoint",
|
|
2189
|
+
method,
|
|
2190
|
+
status: result.response.status,
|
|
2191
|
+
duration_ms: result.response.duration_ms,
|
|
2192
|
+
log_count: result.logs.length,
|
|
2193
|
+
platform: "mcp",
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
return result;
|
|
2197
|
+
},
|
|
2198
|
+
{ platform: "mcp" },
|
|
2199
|
+
);
|
|
2200
|
+
|
|
2201
|
+
const result = await wrappedTestEndpoint(
|
|
2202
|
+
projectPath,
|
|
2203
|
+
args.path,
|
|
2204
|
+
args.method,
|
|
2205
|
+
args.headers,
|
|
2206
|
+
args.body,
|
|
2207
|
+
args.include_logs,
|
|
2208
|
+
);
|
|
2209
|
+
|
|
2210
|
+
// Add notes for agents about log availability
|
|
2211
|
+
const notes: string[] = [];
|
|
2212
|
+
if (args.include_logs && result.logs.length === 0) {
|
|
2213
|
+
const deployMode = await getDeployMode(projectPath);
|
|
2214
|
+
if (deployMode !== "managed") {
|
|
2215
|
+
notes.push(
|
|
2216
|
+
"Runtime logs are only available for managed (Jack Cloud) projects. Use tail_logs with a managed project.",
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
return {
|
|
2222
|
+
content: [
|
|
2223
|
+
{
|
|
2224
|
+
type: "text",
|
|
2225
|
+
text: JSON.stringify(
|
|
2226
|
+
formatSuccessResponse(result, startTime, notes.length ? notes : undefined),
|
|
2227
|
+
null,
|
|
2228
|
+
2,
|
|
2229
|
+
),
|
|
2230
|
+
},
|
|
2231
|
+
],
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1964
2235
|
default:
|
|
1965
2236
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
1966
2237
|
}
|
package/src/templates/index.ts
CHANGED
|
@@ -19,10 +19,12 @@ export const BUILTIN_TEMPLATES = [
|
|
|
19
19
|
"nextjs",
|
|
20
20
|
"saas",
|
|
21
21
|
"ai-chat",
|
|
22
|
+
"chat",
|
|
22
23
|
"semantic-search",
|
|
23
24
|
"nextjs-shadcn",
|
|
24
25
|
"nextjs-clerk",
|
|
25
26
|
"nextjs-auth",
|
|
27
|
+
"telegram-bot",
|
|
26
28
|
];
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -89,6 +91,7 @@ async function loadTemplate(name: string): Promise<Template> {
|
|
|
89
91
|
envVars?: Template["envVars"];
|
|
90
92
|
capabilities?: Template["capabilities"];
|
|
91
93
|
requires?: Template["requires"];
|
|
94
|
+
crons?: Template["crons"];
|
|
92
95
|
hooks?: Template["hooks"];
|
|
93
96
|
agentContext?: Template["agentContext"];
|
|
94
97
|
intent?: Template["intent"];
|
|
@@ -107,6 +110,7 @@ async function loadTemplate(name: string): Promise<Template> {
|
|
|
107
110
|
envVars: metadata.envVars,
|
|
108
111
|
capabilities: metadata.capabilities,
|
|
109
112
|
requires: metadata.requires,
|
|
113
|
+
crons: metadata.crons,
|
|
110
114
|
hooks: metadata.hooks,
|
|
111
115
|
agentContext: metadata.agentContext,
|
|
112
116
|
intent: metadata.intent,
|
|
@@ -246,6 +250,7 @@ async function fetchRemoteTemplate(identifier: string): Promise<Template | null>
|
|
|
246
250
|
envVars: metadata.envVars as Template["envVars"],
|
|
247
251
|
capabilities: metadata.capabilities as Template["capabilities"],
|
|
248
252
|
requires: metadata.requires as Template["requires"],
|
|
253
|
+
crons: metadata.crons as Template["crons"],
|
|
249
254
|
hooks: metadata.hooks as Template["hooks"],
|
|
250
255
|
agentContext: metadata.agentContext as Template["agentContext"],
|
|
251
256
|
intent: metadata.intent as Template["intent"],
|
package/src/templates/types.ts
CHANGED
|
@@ -35,6 +35,9 @@ export type HookAction =
|
|
|
35
35
|
onMissing?: "fail" | "prompt" | "generate";
|
|
36
36
|
promptMessage?: string;
|
|
37
37
|
generateCommand?: string;
|
|
38
|
+
perProject?: boolean; // Skip global secrets store — always prompt or generate fresh
|
|
39
|
+
validateCommand?: string; // Shell command to validate prompted value ({{value}} is substituted)
|
|
40
|
+
validateError?: string; // Error message shown when validation fails
|
|
38
41
|
}
|
|
39
42
|
| {
|
|
40
43
|
action: "stripe-setup";
|
|
@@ -92,6 +95,7 @@ export interface Template {
|
|
|
92
95
|
envVars?: EnvVar[]; // environment variables (non-secret config)
|
|
93
96
|
capabilities?: Capability[]; // infrastructure requirements (deprecated, use requires)
|
|
94
97
|
requires?: ServiceTypeKey[]; // service requirements (DB, KV, CRON, QUEUE, STORAGE)
|
|
98
|
+
crons?: string[]; // cron expressions to auto-create (managed mode only)
|
|
95
99
|
description?: string; // for help text
|
|
96
100
|
hooks?: TemplateHooks;
|
|
97
101
|
agentContext?: AgentContext;
|
package/templates/AI-BINDINGS.md
CHANGED
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Quick Reference for Coding Agents
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### AI — Uses Proxy
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### AI Pattern
|
|
7
|
+
AI uses a metered proxy (`__AI_PROXY`). Templates need a `jack-ai.ts` wrapper:
|
|
10
8
|
|
|
11
9
|
```typescript
|
|
12
10
|
import { createJackAI } from "./jack-ai";
|
|
13
11
|
|
|
14
12
|
function getAI(env: Env) {
|
|
15
|
-
if (env.__AI_PROXY
|
|
13
|
+
if (env.__AI_PROXY) {
|
|
16
14
|
return createJackAI(env);
|
|
17
15
|
}
|
|
18
16
|
if (env.AI) {
|
|
@@ -26,81 +24,60 @@ const ai = getAI(env);
|
|
|
26
24
|
const result = await ai.run("@cf/meta/llama-3.2-1b-instruct", { messages });
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
### Vectorize
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { createJackVectorize } from "./jack-vectorize";
|
|
27
|
+
### Vectorize — Direct Binding (Metered via Code Injection)
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
Vectorize uses `env.VECTORS` directly. No wrapper needed. The control plane injects a metering
|
|
30
|
+
layer at deploy time that wraps the binding before your code runs.
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (env.VECTORS) {
|
|
41
|
-
return env.VECTORS;
|
|
42
|
-
}
|
|
43
|
-
throw new Error("No Vectorize binding available");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Usage:
|
|
47
|
-
const vectors = getVectorize(env);
|
|
48
|
-
const results = await vectors.query(embedding, { topK: 10 });
|
|
49
|
-
await vectors.insert([{ id: "doc1", values: embedding, metadata: {} }]);
|
|
32
|
+
```typescript
|
|
33
|
+
// Just use env.VECTORS — metering happens automatically
|
|
34
|
+
const results = await env.VECTORS.query(embedding, { topK: 10 });
|
|
35
|
+
await env.VECTORS.insert([{ id: "doc1", values: embedding, metadata: {} }]);
|
|
50
36
|
```
|
|
51
37
|
|
|
52
|
-
|
|
38
|
+
**Do NOT create a vectorize wrapper.** The old `jack-vectorize.ts` proxy pattern is deprecated.
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
- One user could exhaust the daily quota for everyone
|
|
56
|
-
- No visibility into per-project usage
|
|
57
|
-
- No ability to bill for AI usage
|
|
40
|
+
## Why Different Patterns?
|
|
58
41
|
|
|
59
|
-
The proxy
|
|
60
|
-
|
|
61
|
-
2. Logs usage to Analytics Engine
|
|
62
|
-
3. Forwards to real AI binding
|
|
63
|
-
4. Returns response unchanged
|
|
42
|
+
- **AI** is a global shared service. The proxy enforces per-project quota (sync check before forwarding).
|
|
43
|
+
- **Vectorize** is per-tenant (each project gets its own index). The control plane wraps `env.VECTORS` via code injection (`__jack_meter.mjs`) for async metering. No proxy needed.
|
|
64
44
|
|
|
65
45
|
## Environment Bindings
|
|
66
46
|
|
|
67
47
|
### Jack Cloud (Managed Deploy)
|
|
68
48
|
|
|
69
|
-
Control plane injects
|
|
70
|
-
- `__AI_PROXY`
|
|
71
|
-
- `
|
|
72
|
-
- `
|
|
73
|
-
- `__JACK_ORG_ID` - Organization ID for billing
|
|
49
|
+
Control plane injects:
|
|
50
|
+
- `__AI_PROXY` — Service binding to jack-binding-proxy for AI (metering + quota)
|
|
51
|
+
- `VECTORS` — Direct Vectorize binding to the project's own index
|
|
52
|
+
- `__JACK_VECTORIZE_USAGE` — Analytics Engine binding for vectorize metering (injected, not visible to user code)
|
|
74
53
|
|
|
75
|
-
`env.AI`
|
|
54
|
+
`env.AI` is **NOT available** in jack cloud (use `getAI()` helper). `env.VECTORS` **IS available** directly.
|
|
76
55
|
|
|
77
56
|
### Local Development
|
|
78
57
|
|
|
79
58
|
wrangler.jsonc provides:
|
|
80
|
-
- `AI`
|
|
81
|
-
- `VECTORS`
|
|
59
|
+
- `AI` — Direct Cloudflare AI binding for local testing
|
|
60
|
+
- `VECTORS` — Direct Vectorize binding for local testing
|
|
82
61
|
|
|
83
|
-
The helper
|
|
62
|
+
The `getAI()` helper automatically uses the right binding. For vectorize, `env.VECTORS` works in both environments.
|
|
84
63
|
|
|
85
64
|
## Template Pattern
|
|
86
65
|
|
|
87
66
|
### AI Templates
|
|
88
67
|
|
|
89
|
-
1. **src/jack-ai.ts**
|
|
68
|
+
1. **src/jack-ai.ts** — Client wrapper (copy from ai-chat or semantic-search template)
|
|
90
69
|
|
|
91
70
|
2. **Env interface** with optional bindings:
|
|
92
71
|
```typescript
|
|
93
72
|
interface Env {
|
|
94
73
|
AI?: Ai; // Local dev
|
|
95
74
|
__AI_PROXY?: Fetcher; // Jack cloud
|
|
96
|
-
__JACK_PROJECT_ID?: string; // Jack cloud
|
|
97
|
-
__JACK_ORG_ID?: string; // Jack cloud
|
|
98
75
|
}
|
|
99
76
|
```
|
|
100
77
|
|
|
101
78
|
3. **getAI() helper** that handles both environments
|
|
102
79
|
|
|
103
|
-
4. **wrangler.jsonc** with AI binding
|
|
80
|
+
4. **wrangler.jsonc** with AI binding:
|
|
104
81
|
```jsonc
|
|
105
82
|
{
|
|
106
83
|
"ai": { "binding": "AI" }
|
|
@@ -109,21 +86,16 @@ interface Env {
|
|
|
109
86
|
|
|
110
87
|
### Vectorize Templates
|
|
111
88
|
|
|
112
|
-
1. **
|
|
113
|
-
|
|
114
|
-
2. **Env interface** with optional bindings:
|
|
89
|
+
1. **Env interface** with direct binding:
|
|
115
90
|
```typescript
|
|
116
91
|
interface Env {
|
|
117
|
-
VECTORS
|
|
118
|
-
__VECTORIZE_PROXY?: Fetcher; // Jack cloud
|
|
119
|
-
__JACK_PROJECT_ID?: string; // Jack cloud
|
|
120
|
-
__JACK_ORG_ID?: string; // Jack cloud
|
|
92
|
+
VECTORS: VectorizeIndex;
|
|
121
93
|
}
|
|
122
94
|
```
|
|
123
95
|
|
|
124
|
-
|
|
96
|
+
2. **Use `env.VECTORS` directly** — no wrapper, no helper needed
|
|
125
97
|
|
|
126
|
-
|
|
98
|
+
3. **wrangler.jsonc** with Vectorize binding:
|
|
127
99
|
```jsonc
|
|
128
100
|
{
|
|
129
101
|
"vectorize": [{
|
|
@@ -136,8 +108,6 @@ interface Env {
|
|
|
136
108
|
|
|
137
109
|
## Error Handling
|
|
138
110
|
|
|
139
|
-
Quota exceeded returns 429:
|
|
140
|
-
|
|
141
111
|
### AI Quota
|
|
142
112
|
```typescript
|
|
143
113
|
try {
|
|
@@ -150,21 +120,9 @@ try {
|
|
|
150
120
|
}
|
|
151
121
|
```
|
|
152
122
|
|
|
153
|
-
### Vectorize
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const results = await vectors.query(embedding, { topK: 10 });
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (error.code === "VECTORIZE_QUERY_QUOTA_EXCEEDED") {
|
|
159
|
-
// Query limit (33,000/day) reached
|
|
160
|
-
console.log(`Retry in ${error.resetIn} seconds`);
|
|
161
|
-
}
|
|
162
|
-
if (error.code === "VECTORIZE_MUTATION_QUOTA_EXCEEDED") {
|
|
163
|
-
// Mutation limit (10,000/day) reached
|
|
164
|
-
console.log(`Retry in ${error.resetIn} seconds`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
```
|
|
123
|
+
### Vectorize
|
|
124
|
+
|
|
125
|
+
Vectorize calls go directly to the binding. There is no sync quota enforcement — metering is async via Analytics Engine. If Cloudflare's own rate limits are hit, the binding returns standard errors.
|
|
168
126
|
|
|
169
127
|
## BYOC Mode
|
|
170
128
|
|
|
@@ -176,6 +134,6 @@ For Bring Your Own Cloud deployments:
|
|
|
176
134
|
|
|
177
135
|
## See Also
|
|
178
136
|
|
|
179
|
-
-
|
|
180
|
-
- `apps/
|
|
181
|
-
- `apps/control-plane/src/deployment-service.ts`
|
|
137
|
+
- `apps/binding-proxy-worker/` — AI proxy implementation
|
|
138
|
+
- `apps/control-plane/src/metering-wrapper.ts` — Vectorize metering code injection
|
|
139
|
+
- `apps/control-plane/src/deployment-service.ts` — Binding injection logic
|
package/templates/CLAUDE.md
CHANGED
|
@@ -26,7 +26,7 @@ const result = await env.AI.run(model, inputs);
|
|
|
26
26
|
import { createJackAI } from "./jack-ai";
|
|
27
27
|
|
|
28
28
|
function getAI(env: Env) {
|
|
29
|
-
if (env.__AI_PROXY
|
|
29
|
+
if (env.__AI_PROXY) {
|
|
30
30
|
return createJackAI(env);
|
|
31
31
|
}
|
|
32
32
|
return env.AI;
|
|
@@ -114,6 +114,27 @@ Before shipping a new template:
|
|
|
114
114
|
- [ ] Install time < 5s (cold cache + lockfile)
|
|
115
115
|
- [ ] All scripts work without global tools except wrangler
|
|
116
116
|
- [ ] `.gitignore` includes `.env`, `.dev.vars`, `.secrets.json`
|
|
117
|
+
- [ ] Prebuilt builds without env vars: `bun run build` succeeds with zero secrets set
|
|
118
|
+
- [ ] If template has `schema.sql`, verify it's included in prebuild output
|
|
119
|
+
|
|
120
|
+
## Prebuild Compatibility (IMPORTANT)
|
|
121
|
+
|
|
122
|
+
Templates are prebuilt at release time **without any env vars or secrets**. The prebuild runs `bun run build` in a clean environment. If the build depends on runtime env vars, it will fail silently (only discovered when someone runs `prebuild-templates.ts`).
|
|
123
|
+
|
|
124
|
+
**Common failure:** A provider component (e.g., `ClerkProvider`, `AuthProvider`) in the root layout requires an API key at import time. Next.js tries to statically prerender pages and the provider throws because the key is missing.
|
|
125
|
+
|
|
126
|
+
**Fix:** Add `export const dynamic = "force-dynamic"` to the root layout. This tells Next.js to skip static prerendering for all pages, deferring to runtime where env vars are available.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// app/layout.tsx
|
|
130
|
+
export const dynamic = "force-dynamic"; // Required: provider needs runtime env vars
|
|
131
|
+
|
|
132
|
+
export default function RootLayout({ children }) {
|
|
133
|
+
return <SomeProvider>{children}</SomeProvider>;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**General rule:** If a template imports any SDK/provider that reads `process.env` or `env.*` at initialization, that template's layout or pages must be force-dynamic to survive a zero-env-var prebuild.
|
|
117
138
|
|
|
118
139
|
## Placeholder System
|
|
119
140
|
|
|
@@ -1,30 +1,25 @@
|
|
|
1
|
+
import { type Message, convertToCoreMessages, streamText } from "ai";
|
|
1
2
|
import { Hono } from "hono";
|
|
2
|
-
import { streamText, convertToCoreMessages, type Message } from "ai";
|
|
3
3
|
import { createWorkersAI } from "workers-ai-provider";
|
|
4
4
|
import { createJackAI } from "./jack-ai";
|
|
5
5
|
|
|
6
6
|
interface Env {
|
|
7
7
|
AI?: Ai;
|
|
8
8
|
__AI_PROXY?: Fetcher;
|
|
9
|
-
__JACK_PROJECT_ID?: string;
|
|
10
|
-
__JACK_ORG_ID?: string;
|
|
11
9
|
ASSETS: Fetcher;
|
|
12
10
|
DB: D1Database;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
function getAI(env: Env) {
|
|
16
|
-
if (env.__AI_PROXY
|
|
17
|
-
return createJackAI(
|
|
18
|
-
env as Required<
|
|
19
|
-
Pick<Env, "__AI_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">
|
|
20
|
-
>,
|
|
21
|
-
);
|
|
14
|
+
if (env.__AI_PROXY) {
|
|
15
|
+
return createJackAI(env as Pick<Env, "__AI_PROXY"> & { __AI_PROXY: Fetcher });
|
|
22
16
|
}
|
|
23
17
|
if (env.AI) return env.AI;
|
|
24
18
|
throw new Error("No AI binding available");
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
const SYSTEM_PROMPT =
|
|
21
|
+
const SYSTEM_PROMPT =
|
|
22
|
+
"You are a helpful AI assistant. Be concise, friendly, and helpful. Keep responses short unless detail is needed.";
|
|
28
23
|
|
|
29
24
|
// Rate limiting
|
|
30
25
|
const RATE_LIMIT = 10;
|
|
@@ -74,16 +69,14 @@ app.post("/api/chat", async (c) => {
|
|
|
74
69
|
messages: Message[];
|
|
75
70
|
chatId?: string;
|
|
76
71
|
}>();
|
|
77
|
-
if (!messages || !Array.isArray(messages)) {
|
|
72
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
78
73
|
return c.json({ error: "Invalid request." }, 400);
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
// Save user message to DB if we have a chatId
|
|
82
77
|
const lastUserMsg = messages.findLast((m: Message) => m.role === "user");
|
|
83
78
|
if (chatId && lastUserMsg) {
|
|
84
|
-
await c.env.DB.prepare(
|
|
85
|
-
"INSERT INTO messages (id, chat_id, role, content) VALUES (?, ?, ?, ?)",
|
|
86
|
-
)
|
|
79
|
+
await c.env.DB.prepare("INSERT INTO messages (id, chat_id, role, content) VALUES (?, ?, ?, ?)")
|
|
87
80
|
.bind(
|
|
88
81
|
crypto.randomUUID(),
|
|
89
82
|
chatId,
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
*
|
|
11
11
|
* interface Env {
|
|
12
12
|
* __AI_PROXY: Fetcher; // Service binding to binding-proxy worker
|
|
13
|
-
* __JACK_PROJECT_ID: string; // Injected by control plane
|
|
14
|
-
* __JACK_ORG_ID: string; // Injected by control plane
|
|
15
13
|
* }
|
|
16
14
|
*
|
|
17
15
|
* export default {
|
|
@@ -29,8 +27,6 @@
|
|
|
29
27
|
|
|
30
28
|
interface JackAIEnv {
|
|
31
29
|
__AI_PROXY: Fetcher;
|
|
32
|
-
__JACK_PROJECT_ID: string;
|
|
33
|
-
__JACK_ORG_ID: string;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
/**
|
|
@@ -56,8 +52,6 @@ export function createJackAI(env: JackAIEnv): {
|
|
|
56
52
|
method: "POST",
|
|
57
53
|
headers: {
|
|
58
54
|
"Content-Type": "application/json",
|
|
59
|
-
"X-Jack-Project-ID": env.__JACK_PROJECT_ID,
|
|
60
|
-
"X-Jack-Org-ID": env.__JACK_ORG_ID,
|
|
61
55
|
},
|
|
62
56
|
body: JSON.stringify({ model, inputs, options }),
|
|
63
57
|
});
|