@getjack/jack 0.1.34 → 0.1.36

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.
Files changed (90) hide show
  1. package/README.md +6 -6
  2. package/package.json +1 -1
  3. package/src/commands/down.ts +39 -7
  4. package/src/commands/link.ts +2 -4
  5. package/src/commands/logs.ts +2 -4
  6. package/src/commands/mcp.ts +12 -10
  7. package/src/commands/services.ts +4 -2
  8. package/src/commands/sync.ts +5 -6
  9. package/src/commands/update.ts +1 -0
  10. package/src/index.ts +8 -0
  11. package/src/lib/auth/client.ts +5 -2
  12. package/src/lib/binding-validator.ts +39 -3
  13. package/src/lib/build-helper.ts +18 -19
  14. package/src/lib/control-plane.ts +45 -0
  15. package/src/lib/do-config.ts +110 -0
  16. package/src/lib/do-export-validator.ts +26 -0
  17. package/src/lib/jsonc-edit.ts +292 -0
  18. package/src/lib/managed-deploy.ts +36 -1
  19. package/src/lib/project-link.ts +37 -0
  20. package/src/lib/project-operations.ts +31 -66
  21. package/src/lib/resources.ts +4 -5
  22. package/src/lib/schema.ts +8 -12
  23. package/src/lib/services/db-create.ts +2 -2
  24. package/src/lib/services/db-execute.ts +9 -6
  25. package/src/lib/services/db-list.ts +6 -4
  26. package/src/lib/services/endpoint-test.ts +275 -0
  27. package/src/lib/services/project-delete.ts +190 -0
  28. package/src/lib/services/project-environment.ts +579 -0
  29. package/src/lib/services/storage-config.ts +7 -309
  30. package/src/lib/services/storage-create.ts +2 -1
  31. package/src/lib/services/storage-delete.ts +3 -2
  32. package/src/lib/services/storage-info.ts +2 -1
  33. package/src/lib/services/storage-list.ts +6 -3
  34. package/src/lib/services/vectorize-config.ts +7 -264
  35. package/src/lib/services/vectorize-create.ts +2 -1
  36. package/src/lib/services/vectorize-delete.ts +6 -4
  37. package/src/lib/services/vectorize-list.ts +6 -3
  38. package/src/lib/storage/index.ts +21 -23
  39. package/src/lib/telemetry.ts +1 -0
  40. package/src/lib/wrangler-config.ts +43 -312
  41. package/src/lib/zip-packager.ts +28 -0
  42. package/src/mcp/test-utils.ts +31 -0
  43. package/src/mcp/tools/index.ts +280 -2
  44. package/src/templates/index.ts +5 -0
  45. package/src/templates/types.ts +4 -0
  46. package/templates/AI-BINDINGS.md +34 -76
  47. package/templates/CLAUDE.md +1 -1
  48. package/templates/ai-chat/src/index.ts +7 -14
  49. package/templates/ai-chat/src/jack-ai.ts +0 -6
  50. package/templates/chat/.jack.json +45 -0
  51. package/templates/chat/bun.lock +1584 -0
  52. package/templates/chat/components.json +23 -0
  53. package/templates/chat/index.html +12 -0
  54. package/templates/chat/package.json +41 -0
  55. package/templates/chat/src/chat-agent.ts +63 -0
  56. package/templates/chat/src/client/app.tsx +189 -0
  57. package/templates/chat/src/client/chat.tsx +222 -0
  58. package/templates/chat/src/client/components/prompt-kit/chat-container.tsx +47 -0
  59. package/templates/chat/src/client/components/prompt-kit/loader.tsx +33 -0
  60. package/templates/chat/src/client/components/prompt-kit/markdown.tsx +84 -0
  61. package/templates/chat/src/client/components/prompt-kit/message.tsx +54 -0
  62. package/templates/chat/src/client/components/prompt-kit/prompt-suggestion.tsx +20 -0
  63. package/templates/chat/src/client/components/prompt-kit/reasoning.tsx +134 -0
  64. package/templates/chat/src/client/components/prompt-kit/scroll-button.tsx +28 -0
  65. package/templates/chat/src/client/components/ui/button.tsx +38 -0
  66. package/templates/chat/src/client/lib/utils.ts +6 -0
  67. package/templates/chat/src/client/main.tsx +11 -0
  68. package/templates/chat/src/client/styles.css +125 -0
  69. package/templates/chat/src/index.ts +25 -0
  70. package/templates/chat/src/jack-ai.ts +94 -0
  71. package/templates/chat/tsconfig.json +18 -0
  72. package/templates/chat/vite.config.ts +14 -0
  73. package/templates/chat/wrangler.jsonc +18 -0
  74. package/templates/cron/.jack.json +18 -28
  75. package/templates/cron/schema.sql +10 -20
  76. package/templates/cron/src/admin.ts +321 -0
  77. package/templates/cron/src/index.ts +151 -81
  78. package/templates/cron/src/monitor.ts +124 -0
  79. package/templates/semantic-search/src/index.ts +5 -43
  80. package/templates/semantic-search/src/jack-ai.ts +0 -6
  81. package/templates/telegram-bot/.jack.json +56 -0
  82. package/templates/telegram-bot/bun.lock +41 -0
  83. package/templates/telegram-bot/package.json +16 -0
  84. package/templates/telegram-bot/src/index.ts +236 -0
  85. package/templates/telegram-bot/src/jack-ai.ts +100 -0
  86. package/templates/telegram-bot/tsconfig.json +11 -0
  87. package/templates/telegram-bot/wrangler.jsonc +8 -0
  88. package/templates/cron/src/jobs.ts +0 -139
  89. package/templates/cron/src/webhooks.ts +0 -95
  90. package/templates/semantic-search/src/jack-vectorize.ts +0 -169
@@ -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 () => {
@@ -290,7 +332,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
290
332
  {
291
333
  name: "create_project",
292
334
  description:
293
- "Create a new project from a template. Automatically installs dependencies, deploys, and registers the project. Also supports forking: pass a 'username/slug' template to fork a published project, or a project slug to fork your own.",
335
+ "Create a new project from a template. Returns targetDir you MUST use that path as your working directory for all subsequent file edits and tool calls (e.g. deploy_project project_path). Projects are created in ~/.jack/projects/<name>. Also supports forking: pass 'username/slug' to fork a published project.",
294
336
  inputSchema: {
295
337
  type: "object",
296
338
  properties: {
@@ -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
  });
@@ -821,7 +939,14 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
821
939
  content: [
822
940
  {
823
941
  type: "text",
824
- text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
942
+ text: JSON.stringify(
943
+ formatSuccessResponse(result, startTime, [
944
+ `IMPORTANT: Your working directory for this project is ${result.targetDir} — use this path for all file edits and as project_path in subsequent tool calls (deploy_project, execute_sql, etc).`,
945
+ `The project has AGENTS.md and CLAUDE.md with full context. Read ${result.targetDir}/AGENTS.md first.`,
946
+ ]),
947
+ null,
948
+ 2,
949
+ ),
825
950
  },
826
951
  ],
827
952
  };
@@ -1156,6 +1281,48 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
1156
1281
  };
1157
1282
  }
1158
1283
 
1284
+ case "delete_project": {
1285
+ const args = DeleteProjectSchema.parse(request.params.arguments ?? {});
1286
+ const projectPath = args.project_path ?? process.cwd();
1287
+
1288
+ const wrappedDeleteProject = withTelemetry(
1289
+ "delete_project",
1290
+ async (projectDir: string, exportDatabase: boolean) => {
1291
+ const result = await deleteProject(projectDir, { exportDatabase });
1292
+
1293
+ track(Events.PROJECT_DELETED, {
1294
+ deploy_mode: result.deployMode,
1295
+ database_deleted: result.databaseDeleted,
1296
+ database_exported: result.databaseExportPath !== null,
1297
+ platform: "mcp",
1298
+ });
1299
+
1300
+ return {
1301
+ project_name: result.projectName,
1302
+ deploy_mode: result.deployMode,
1303
+ worker_deleted: result.workerDeleted,
1304
+ database_deleted: result.databaseDeleted,
1305
+ database_name: result.databaseName,
1306
+ database_export_path: result.databaseExportPath,
1307
+ resource_results: result.resourceResults,
1308
+ warnings: result.warnings,
1309
+ };
1310
+ },
1311
+ { platform: "mcp" },
1312
+ );
1313
+
1314
+ const result = await wrappedDeleteProject(projectPath, args.export_database ?? false);
1315
+
1316
+ return {
1317
+ content: [
1318
+ {
1319
+ type: "text",
1320
+ text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
1321
+ },
1322
+ ],
1323
+ };
1324
+ }
1325
+
1159
1326
  case "create_database": {
1160
1327
  const args = CreateDatabaseSchema.parse(request.params.arguments ?? {});
1161
1328
  const projectPath = args.project_path ?? process.cwd();
@@ -1961,6 +2128,117 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
1961
2128
  };
1962
2129
  }
1963
2130
 
2131
+ case "get_project_environment": {
2132
+ const args = GetProjectEnvironmentSchema.parse(request.params.arguments ?? {});
2133
+ const projectPath = args.project_path ?? process.cwd();
2134
+
2135
+ const wrappedGetProjectEnvironment = withTelemetry(
2136
+ "get_project_environment",
2137
+ async (projectDir: string) => {
2138
+ const env = await getProjectEnvironment(projectDir);
2139
+
2140
+ track(Events.COMMAND_COMPLETED, {
2141
+ command: "get_project_environment",
2142
+ has_database: !!env.database,
2143
+ binding_count:
2144
+ (env.bindings.d1 ? 1 : 0) +
2145
+ (env.bindings.r2?.length ?? 0) +
2146
+ (env.bindings.kv?.length ?? 0) +
2147
+ (env.bindings.ai ? 1 : 0) +
2148
+ (env.bindings.vectorize?.length ?? 0) +
2149
+ (env.bindings.durable_objects?.length ?? 0),
2150
+ issue_count: env.issues.length,
2151
+ platform: "mcp",
2152
+ });
2153
+
2154
+ return env;
2155
+ },
2156
+ { platform: "mcp" },
2157
+ );
2158
+
2159
+ const result = await wrappedGetProjectEnvironment(projectPath);
2160
+
2161
+ return {
2162
+ content: [
2163
+ {
2164
+ type: "text",
2165
+ text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
2166
+ },
2167
+ ],
2168
+ };
2169
+ }
2170
+
2171
+ case "test_endpoint": {
2172
+ const args = TestEndpointSchema.parse(request.params.arguments ?? {});
2173
+ const projectPath = args.project_path ?? process.cwd();
2174
+
2175
+ const wrappedTestEndpoint = withTelemetry(
2176
+ "test_endpoint",
2177
+ async (
2178
+ projectDir: string,
2179
+ path: string,
2180
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
2181
+ headers?: Record<string, string>,
2182
+ body?: string,
2183
+ includeLogs?: boolean,
2184
+ ) => {
2185
+ const result = await testEndpoint({
2186
+ projectDir,
2187
+ path,
2188
+ method,
2189
+ headers,
2190
+ body,
2191
+ includeLogs,
2192
+ });
2193
+
2194
+ track(Events.COMMAND_COMPLETED, {
2195
+ command: "test_endpoint",
2196
+ method,
2197
+ status: result.response.status,
2198
+ duration_ms: result.response.duration_ms,
2199
+ log_count: result.logs.length,
2200
+ platform: "mcp",
2201
+ });
2202
+
2203
+ return result;
2204
+ },
2205
+ { platform: "mcp" },
2206
+ );
2207
+
2208
+ const result = await wrappedTestEndpoint(
2209
+ projectPath,
2210
+ args.path,
2211
+ args.method,
2212
+ args.headers,
2213
+ args.body,
2214
+ args.include_logs,
2215
+ );
2216
+
2217
+ // Add notes for agents about log availability
2218
+ const notes: string[] = [];
2219
+ if (args.include_logs && result.logs.length === 0) {
2220
+ const deployMode = await getDeployMode(projectPath);
2221
+ if (deployMode !== "managed") {
2222
+ notes.push(
2223
+ "Runtime logs are only available for managed (Jack Cloud) projects. Use tail_logs with a managed project.",
2224
+ );
2225
+ }
2226
+ }
2227
+
2228
+ return {
2229
+ content: [
2230
+ {
2231
+ type: "text",
2232
+ text: JSON.stringify(
2233
+ formatSuccessResponse(result, startTime, notes.length ? notes : undefined),
2234
+ null,
2235
+ 2,
2236
+ ),
2237
+ },
2238
+ ],
2239
+ };
2240
+ }
2241
+
1964
2242
  default:
1965
2243
  throw new Error(`Unknown tool: ${toolName}`);
1966
2244
  }
@@ -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"],
@@ -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;
@@ -2,17 +2,15 @@
2
2
 
3
3
  ## Quick Reference for Coding Agents
4
4
 
5
- **DO NOT add `[ai]` or `[[vectorize]]` bindings directly for jack cloud projects.**
5
+ ### AI Uses Proxy
6
6
 
7
- Jack cloud uses metered proxies for AI and Vectorize calls. Use the patterns below:
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 && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
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 Pattern
30
-
31
- ```typescript
32
- import { createJackVectorize } from "./jack-vectorize";
27
+ ### Vectorize — Direct Binding (Metered via Code Injection)
33
28
 
34
- const INDEX_NAME = "my-vectors";
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
- function getVectorize(env: Env) {
37
- if (env.__VECTORIZE_PROXY && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
38
- return createJackVectorize(env, INDEX_NAME);
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
- ## Why This Pattern?
38
+ **Do NOT create a vectorize wrapper.** The old `jack-vectorize.ts` proxy pattern is deprecated.
53
39
 
54
- Jack cloud is multi-tenant. All users share Cloudflare's AI quota (10k neurons/day). Without metering:
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
- 1. Checks per-project quota
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 these bindings:
70
- - `__AI_PROXY` - Service binding to jack-binding-proxy for AI
71
- - `__VECTORIZE_PROXY` - Service binding to jack-binding-proxy for Vectorize
72
- - `__JACK_PROJECT_ID` - Project ID for metering
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` and `env.VECTORS` are **NOT available** in jack cloud. Direct calls will fail.
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` - Direct Cloudflare AI binding for local testing
81
- - `VECTORS` - Direct Vectorize binding for local testing
59
+ - `AI` Direct Cloudflare AI binding for local testing
60
+ - `VECTORS` Direct Vectorize binding for local testing
82
61
 
83
- The helper functions automatically use the right binding based on environment.
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** - Client wrapper (copy from ai-chat or semantic-search template)
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 for local dev only:
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. **src/jack-vectorize.ts** - Client wrapper (copy from semantic-search template)
113
-
114
- 2. **Env interface** with optional bindings:
89
+ 1. **Env interface** with direct binding:
115
90
  ```typescript
116
91
  interface Env {
117
- VECTORS?: VectorizeIndex; // Local dev
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
- 3. **getVectorize() helper** that handles both environments
96
+ 2. **Use `env.VECTORS` directly** no wrapper, no helper needed
125
97
 
126
- 4. **wrangler.jsonc** with Vectorize binding for local dev only:
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 Quota
154
- ```typescript
155
- try {
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
- - `/docs/internal/specs/binding-proxy-worker.md` - Full architecture spec
180
- - `apps/binding-proxy-worker/` - Proxy implementation
181
- - `apps/control-plane/src/deployment-service.ts` - Binding injection logic
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
@@ -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 && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
29
+ if (env.__AI_PROXY) {
30
30
  return createJackAI(env);
31
31
  }
32
32
  return env.AI;
@@ -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 && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
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 = `You are a helpful AI assistant. Be concise, friendly, and helpful. Keep responses short unless detail is needed.`;
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
  });