@getjack/jack 0.1.20 → 0.1.23

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 (43) hide show
  1. package/package.json +5 -2
  2. package/src/commands/clone.ts +5 -5
  3. package/src/commands/down.ts +44 -69
  4. package/src/commands/link.ts +9 -6
  5. package/src/commands/new.ts +55 -25
  6. package/src/commands/publish.ts +8 -3
  7. package/src/commands/secrets.ts +2 -1
  8. package/src/commands/services.ts +41 -15
  9. package/src/commands/update.ts +2 -2
  10. package/src/index.ts +43 -2
  11. package/src/lib/agent-integration.ts +217 -0
  12. package/src/lib/auth/login-flow.ts +2 -1
  13. package/src/lib/binding-validator.ts +2 -3
  14. package/src/lib/build-helper.ts +7 -1
  15. package/src/lib/hooks.ts +101 -21
  16. package/src/lib/managed-down.ts +40 -42
  17. package/src/lib/project-detection.ts +48 -21
  18. package/src/lib/project-operations.ts +38 -44
  19. package/src/lib/prompts.ts +16 -23
  20. package/src/lib/services/db-execute.ts +39 -0
  21. package/src/lib/services/sql-classifier.test.ts +2 -2
  22. package/src/lib/services/sql-classifier.ts +5 -4
  23. package/src/lib/version-check.ts +15 -10
  24. package/src/lib/zip-packager.ts +16 -0
  25. package/src/mcp/resources/index.ts +42 -2
  26. package/src/templates/index.ts +63 -3
  27. package/templates/CLAUDE.md +117 -53
  28. package/templates/ai-chat/.jack.json +29 -0
  29. package/templates/ai-chat/bun.lock +18 -0
  30. package/templates/ai-chat/package.json +14 -0
  31. package/templates/ai-chat/public/chat.js +149 -0
  32. package/templates/ai-chat/public/index.html +209 -0
  33. package/templates/ai-chat/src/index.ts +105 -0
  34. package/templates/ai-chat/wrangler.jsonc +12 -0
  35. package/templates/semantic-search/.jack.json +26 -0
  36. package/templates/semantic-search/bun.lock +18 -0
  37. package/templates/semantic-search/package.json +12 -0
  38. package/templates/semantic-search/public/app.js +120 -0
  39. package/templates/semantic-search/public/index.html +210 -0
  40. package/templates/semantic-search/schema.sql +5 -0
  41. package/templates/semantic-search/src/index.ts +144 -0
  42. package/templates/semantic-search/tsconfig.json +13 -0
  43. package/templates/semantic-search/wrangler.jsonc +27 -0
@@ -0,0 +1,105 @@
1
+ interface Env {
2
+ AI: Ai;
3
+ ASSETS: Fetcher;
4
+ }
5
+
6
+ interface ChatMessage {
7
+ role: "user" | "assistant" | "system";
8
+ content: string;
9
+ }
10
+
11
+ // Rate limiting: 10 requests per minute per IP
12
+ const RATE_LIMIT = 10;
13
+ const RATE_WINDOW_MS = 60_000;
14
+ const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
15
+
16
+ function checkRateLimit(ip: string): boolean {
17
+ const now = Date.now();
18
+ const entry = rateLimitMap.get(ip);
19
+
20
+ if (!entry || now >= entry.resetAt) {
21
+ rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
22
+ return true;
23
+ }
24
+
25
+ if (entry.count >= RATE_LIMIT) {
26
+ return false;
27
+ }
28
+
29
+ entry.count++;
30
+ return true;
31
+ }
32
+
33
+ // Clean up old entries periodically to prevent memory leaks
34
+ function cleanupRateLimitMap(): void {
35
+ const now = Date.now();
36
+ for (const [ip, entry] of rateLimitMap) {
37
+ if (now >= entry.resetAt) {
38
+ rateLimitMap.delete(ip);
39
+ }
40
+ }
41
+ }
42
+
43
+ export default {
44
+ async fetch(request: Request, env: Env): Promise<Response> {
45
+ const url = new URL(request.url);
46
+
47
+ // Serve static assets for non-API routes
48
+ if (request.method === "GET" && !url.pathname.startsWith("/api")) {
49
+ return env.ASSETS.fetch(request);
50
+ }
51
+
52
+ // POST /api/chat - Streaming chat endpoint
53
+ if (request.method === "POST" && url.pathname === "/api/chat") {
54
+ const ip = request.headers.get("cf-connecting-ip") || "unknown";
55
+
56
+ // Check rate limit
57
+ if (!checkRateLimit(ip)) {
58
+ // Cleanup old entries occasionally
59
+ cleanupRateLimitMap();
60
+ return Response.json(
61
+ { error: "Too many requests. Please wait a moment and try again." },
62
+ { status: 429 },
63
+ );
64
+ }
65
+
66
+ try {
67
+ const body = (await request.json()) as { messages?: ChatMessage[] };
68
+ const messages = body.messages;
69
+
70
+ if (!messages || !Array.isArray(messages)) {
71
+ return Response.json(
72
+ { error: "Invalid request. Please provide a messages array." },
73
+ { status: 400 },
74
+ );
75
+ }
76
+
77
+ // Stream response using SSE
78
+ const stream = await env.AI.run(
79
+ "@cf/mistral/mistral-7b-instruct-v0.1",
80
+ {
81
+ messages,
82
+ stream: true,
83
+ max_tokens: 1024,
84
+ },
85
+ );
86
+
87
+ return new Response(stream, {
88
+ headers: {
89
+ "Content-Type": "text/event-stream",
90
+ "Cache-Control": "no-cache",
91
+ Connection: "keep-alive",
92
+ },
93
+ });
94
+ } catch (err) {
95
+ console.error("Chat error:", err);
96
+ return Response.json(
97
+ { error: "Something went wrong. Please try again." },
98
+ { status: 500 },
99
+ );
100
+ }
101
+ }
102
+
103
+ return Response.json({ error: "Not found" }, { status: 404 });
104
+ },
105
+ };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "jack-template",
3
+ "main": "src/index.ts",
4
+ "compatibility_date": "2024-12-01",
5
+ "ai": {
6
+ "binding": "AI"
7
+ },
8
+ "assets": {
9
+ "directory": "public",
10
+ "binding": "ASSETS"
11
+ }
12
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "semantic-search",
3
+ "description": "Semantic search with AI embeddings + Vectorize",
4
+ "secrets": [],
5
+ "capabilities": ["ai", "db"],
6
+ "intent": {
7
+ "keywords": ["rag", "semantic", "search", "embeddings", "vectorize", "vector", "similarity"],
8
+ "examples": ["RAG app", "document search", "semantic search", "knowledge base"]
9
+ },
10
+ "hooks": {
11
+ "postDeploy": [
12
+ { "action": "clipboard", "text": "{{url}}", "message": "URL copied" },
13
+ {
14
+ "action": "box",
15
+ "title": "Semantic Search: {{name}}",
16
+ "lines": [
17
+ "{{url}}",
18
+ "",
19
+ "Open in browser to index documents and search!",
20
+ "",
21
+ "Note: Local dev requires 'wrangler dev --remote'"
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "jack-template",
7
+ "devDependencies": {
8
+ "@cloudflare/workers-types": "^4.20241205.0",
9
+ "typescript": "^5.0.0",
10
+ },
11
+ },
12
+ },
13
+ "packages": {
14
+ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260120.0", "", {}, "sha512-B8pueG+a5S+mdK3z8oKu1ShcxloZ7qWb68IEyLLaepvdryIbNC7JVPcY0bWsjS56UQVKc5fnyRge3yZIwc9bxw=="],
15
+
16
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
17
+ }
18
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "jack-template",
3
+ "type": "module",
4
+ "scripts": {
5
+ "dev": "wrangler dev --remote",
6
+ "deploy": "wrangler deploy"
7
+ },
8
+ "devDependencies": {
9
+ "@cloudflare/workers-types": "^4.20241205.0",
10
+ "typescript": "^5.0.0"
11
+ }
12
+ }
@@ -0,0 +1,120 @@
1
+ const docIdEl = document.getElementById("doc-id");
2
+ const docContentEl = document.getElementById("doc-content");
3
+ const indexBtn = document.getElementById("index-btn");
4
+ const indexStatus = document.getElementById("index-status");
5
+ const searchQueryEl = document.getElementById("search-query");
6
+ const searchBtn = document.getElementById("search-btn");
7
+ const resultsEl = document.getElementById("results");
8
+
9
+ // Index document
10
+ async function indexDocument() {
11
+ const id = docIdEl.value.trim();
12
+ const content = docContentEl.value.trim();
13
+
14
+ if (!id || !content) {
15
+ showStatus(indexStatus, "Please enter both ID and content", "error");
16
+ return;
17
+ }
18
+
19
+ indexBtn.disabled = true;
20
+ indexBtn.innerHTML = '<span class="loading"></span>Indexing...';
21
+
22
+ try {
23
+ const response = await fetch("/api/index", {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({ id, content }),
27
+ });
28
+
29
+ const data = await response.json();
30
+
31
+ if (response.ok) {
32
+ showStatus(indexStatus, `Document "${id}" indexed successfully!`, "success");
33
+ docIdEl.value = "";
34
+ docContentEl.value = "";
35
+ } else {
36
+ showStatus(indexStatus, data.error || "Failed to index document", "error");
37
+ }
38
+ } catch (err) {
39
+ showStatus(indexStatus, "Network error. Please try again.", "error");
40
+ } finally {
41
+ indexBtn.disabled = false;
42
+ indexBtn.textContent = "Index Document";
43
+ }
44
+ }
45
+
46
+ // Search documents
47
+ async function searchDocuments() {
48
+ const query = searchQueryEl.value.trim();
49
+
50
+ if (!query) {
51
+ resultsEl.innerHTML = '<div class="no-results">Please enter a search query</div>';
52
+ return;
53
+ }
54
+
55
+ searchBtn.disabled = true;
56
+ searchBtn.innerHTML = '<span class="loading"></span>Searching...';
57
+ resultsEl.innerHTML = "";
58
+
59
+ try {
60
+ const response = await fetch("/api/search", {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify({ query, limit: 5 }),
64
+ });
65
+
66
+ const data = await response.json();
67
+
68
+ if (response.ok) {
69
+ if (data.results && data.results.length > 0) {
70
+ resultsEl.innerHTML = data.results
71
+ .map(
72
+ (result) => `
73
+ <div class="result">
74
+ <div class="result-header">
75
+ <span class="result-id">${escapeHtml(result.id)}</span>
76
+ <span class="result-score">Score: ${(result.score * 100).toFixed(1)}%</span>
77
+ </div>
78
+ <div class="result-preview">${escapeHtml(result.metadata?.preview || "No preview available")}</div>
79
+ </div>
80
+ `,
81
+ )
82
+ .join("");
83
+ } else {
84
+ resultsEl.innerHTML = '<div class="no-results">No matching documents found</div>';
85
+ }
86
+ } else {
87
+ resultsEl.innerHTML = `<div class="no-results">${escapeHtml(data.error || "Search failed")}</div>`;
88
+ }
89
+ } catch (err) {
90
+ resultsEl.innerHTML = '<div class="no-results">Network error. Please try again.</div>';
91
+ } finally {
92
+ searchBtn.disabled = false;
93
+ searchBtn.textContent = "Search";
94
+ }
95
+ }
96
+
97
+ // Helper functions
98
+ function showStatus(el, message, type) {
99
+ el.textContent = message;
100
+ el.className = `status ${type}`;
101
+ }
102
+
103
+ function escapeHtml(text) {
104
+ const div = document.createElement("div");
105
+ div.textContent = text;
106
+ return div.innerHTML;
107
+ }
108
+
109
+ // Event listeners
110
+ indexBtn.addEventListener("click", indexDocument);
111
+ searchBtn.addEventListener("click", searchDocuments);
112
+
113
+ // Enter key support
114
+ docIdEl.addEventListener("keypress", (e) => {
115
+ if (e.key === "Enter") docContentEl.focus();
116
+ });
117
+
118
+ searchQueryEl.addEventListener("keypress", (e) => {
119
+ if (e.key === "Enter") searchDocuments();
120
+ });
@@ -0,0 +1,210 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Semantic Search</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
+ line-height: 1.6;
15
+ max-width: 800px;
16
+ margin: 0 auto;
17
+ padding: 2rem 1rem;
18
+ background: #f5f5f5;
19
+ color: #333;
20
+ }
21
+
22
+ h1 {
23
+ text-align: center;
24
+ color: #1a1a1a;
25
+ margin-bottom: 0.5rem;
26
+ }
27
+
28
+ .subtitle {
29
+ text-align: center;
30
+ color: #666;
31
+ margin-bottom: 2rem;
32
+ }
33
+
34
+ .section {
35
+ background: white;
36
+ border-radius: 12px;
37
+ padding: 1.5rem;
38
+ margin-bottom: 1.5rem;
39
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
40
+ }
41
+
42
+ h2 {
43
+ margin-top: 0;
44
+ color: #1a1a1a;
45
+ font-size: 1.25rem;
46
+ }
47
+
48
+ label {
49
+ display: block;
50
+ margin-bottom: 0.5rem;
51
+ font-weight: 500;
52
+ color: #444;
53
+ }
54
+
55
+ input[type="text"] {
56
+ width: 100%;
57
+ padding: 0.75rem;
58
+ border: 1px solid #ddd;
59
+ border-radius: 8px;
60
+ font-size: 1rem;
61
+ margin-bottom: 1rem;
62
+ transition: border-color 0.2s;
63
+ }
64
+
65
+ input[type="text"]:focus {
66
+ outline: none;
67
+ border-color: #0066cc;
68
+ }
69
+
70
+ textarea {
71
+ width: 100%;
72
+ padding: 0.75rem;
73
+ border: 1px solid #ddd;
74
+ border-radius: 8px;
75
+ font-size: 1rem;
76
+ min-height: 120px;
77
+ resize: vertical;
78
+ font-family: inherit;
79
+ margin-bottom: 1rem;
80
+ }
81
+
82
+ textarea:focus {
83
+ outline: none;
84
+ border-color: #0066cc;
85
+ }
86
+
87
+ button {
88
+ background: #0066cc;
89
+ color: white;
90
+ border: none;
91
+ padding: 0.75rem 1.5rem;
92
+ border-radius: 8px;
93
+ font-size: 1rem;
94
+ cursor: pointer;
95
+ transition: background 0.2s;
96
+ }
97
+
98
+ button:hover {
99
+ background: #0052a3;
100
+ }
101
+
102
+ button:disabled {
103
+ background: #999;
104
+ cursor: not-allowed;
105
+ }
106
+
107
+ .status {
108
+ margin-top: 1rem;
109
+ padding: 0.75rem;
110
+ border-radius: 8px;
111
+ display: none;
112
+ }
113
+
114
+ .status.success {
115
+ display: block;
116
+ background: #e6f4ea;
117
+ color: #1e7e34;
118
+ }
119
+
120
+ .status.error {
121
+ display: block;
122
+ background: #fce8e6;
123
+ color: #c5221f;
124
+ }
125
+
126
+ .results {
127
+ margin-top: 1rem;
128
+ }
129
+
130
+ .result {
131
+ background: #f8f9fa;
132
+ border-radius: 8px;
133
+ padding: 1rem;
134
+ margin-bottom: 0.75rem;
135
+ border-left: 4px solid #0066cc;
136
+ }
137
+
138
+ .result-header {
139
+ display: flex;
140
+ justify-content: space-between;
141
+ align-items: center;
142
+ margin-bottom: 0.5rem;
143
+ }
144
+
145
+ .result-id {
146
+ font-weight: 600;
147
+ color: #1a1a1a;
148
+ }
149
+
150
+ .result-score {
151
+ font-size: 0.85rem;
152
+ color: #666;
153
+ background: #e8e8e8;
154
+ padding: 0.25rem 0.5rem;
155
+ border-radius: 4px;
156
+ }
157
+
158
+ .result-preview {
159
+ color: #555;
160
+ font-size: 0.95rem;
161
+ }
162
+
163
+ .no-results {
164
+ text-align: center;
165
+ color: #666;
166
+ padding: 2rem;
167
+ }
168
+
169
+ .loading {
170
+ display: inline-block;
171
+ width: 16px;
172
+ height: 16px;
173
+ border: 2px solid #fff;
174
+ border-radius: 50%;
175
+ border-top-color: transparent;
176
+ animation: spin 0.8s linear infinite;
177
+ margin-right: 0.5rem;
178
+ vertical-align: middle;
179
+ }
180
+
181
+ @keyframes spin {
182
+ to { transform: rotate(360deg); }
183
+ }
184
+ </style>
185
+ </head>
186
+ <body>
187
+ <h1>Semantic Search</h1>
188
+ <p class="subtitle">Index documents and search by meaning using AI embeddings</p>
189
+
190
+ <div class="section">
191
+ <h2>Index Document</h2>
192
+ <label for="doc-id">Document ID</label>
193
+ <input type="text" id="doc-id" placeholder="e.g., doc-001" />
194
+ <label for="doc-content">Content</label>
195
+ <textarea id="doc-content" placeholder="Enter the document content to index..."></textarea>
196
+ <button id="index-btn">Index Document</button>
197
+ <div id="index-status" class="status"></div>
198
+ </div>
199
+
200
+ <div class="section">
201
+ <h2>Search</h2>
202
+ <label for="search-query">Search Query</label>
203
+ <input type="text" id="search-query" placeholder="Enter your search query..." />
204
+ <button id="search-btn">Search</button>
205
+ <div id="results" class="results"></div>
206
+ </div>
207
+
208
+ <script src="/app.js"></script>
209
+ </body>
210
+ </html>
@@ -0,0 +1,5 @@
1
+ CREATE TABLE IF NOT EXISTS documents (
2
+ id TEXT PRIMARY KEY,
3
+ content TEXT NOT NULL,
4
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
5
+ );
@@ -0,0 +1,144 @@
1
+ interface Env {
2
+ AI: Ai;
3
+ VECTORS: VectorizeIndex;
4
+ DB: D1Database;
5
+ ASSETS: Fetcher;
6
+ }
7
+
8
+ // Rate limiting: 10 requests per minute per IP
9
+ const RATE_LIMIT = 10;
10
+ const RATE_WINDOW_MS = 60_000;
11
+ const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
12
+
13
+ function checkRateLimit(ip: string): boolean {
14
+ const now = Date.now();
15
+ const entry = rateLimitMap.get(ip);
16
+
17
+ if (!entry || now > entry.resetAt) {
18
+ rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
19
+ return true;
20
+ }
21
+
22
+ if (entry.count >= RATE_LIMIT) {
23
+ return false;
24
+ }
25
+
26
+ entry.count++;
27
+ return true;
28
+ }
29
+
30
+ /**
31
+ * Extract embedding vector from AI response
32
+ * Handles union type from @cf/baai/bge-base-en-v1.5
33
+ */
34
+ function getEmbeddingVector(response: Awaited<ReturnType<Ai["run"]>>): number[] | null {
35
+ if (
36
+ response &&
37
+ typeof response === "object" &&
38
+ "data" in response &&
39
+ Array.isArray(response.data) &&
40
+ response.data.length > 0
41
+ ) {
42
+ return response.data[0] as number[];
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export default {
48
+ async fetch(request: Request, env: Env): Promise<Response> {
49
+ const url = new URL(request.url);
50
+
51
+ // Serve static assets for non-API routes
52
+ if (request.method === "GET" && !url.pathname.startsWith("/api")) {
53
+ return env.ASSETS.fetch(request);
54
+ }
55
+
56
+ // Rate limiting for API routes
57
+ const ip = request.headers.get("cf-connecting-ip") || "unknown";
58
+ if (!checkRateLimit(ip)) {
59
+ return Response.json({ error: "Too many requests. Please wait a moment." }, { status: 429 });
60
+ }
61
+
62
+ // POST /api/index - Index a document
63
+ if (request.method === "POST" && url.pathname === "/api/index") {
64
+ try {
65
+ const body = (await request.json()) as {
66
+ id?: string;
67
+ content?: string;
68
+ };
69
+ const { id, content } = body;
70
+
71
+ if (!id || !content) {
72
+ return Response.json({ error: "Missing id or content" }, { status: 400 });
73
+ }
74
+
75
+ // Generate embedding using free Cloudflare AI
76
+ const embedding = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
77
+ text: content,
78
+ });
79
+
80
+ const embeddingVector = getEmbeddingVector(embedding);
81
+ if (!embeddingVector) {
82
+ return Response.json({ error: "Failed to generate embedding" }, { status: 500 });
83
+ }
84
+
85
+ // Store in Vectorize
86
+ await env.VECTORS.insert([
87
+ {
88
+ id,
89
+ values: embeddingVector,
90
+ metadata: { preview: content.slice(0, 100) },
91
+ },
92
+ ]);
93
+
94
+ // Store full content in D1
95
+ await env.DB.prepare("INSERT OR REPLACE INTO documents (id, content) VALUES (?, ?)")
96
+ .bind(id, content)
97
+ .run();
98
+
99
+ return Response.json({ success: true, id });
100
+ } catch (err) {
101
+ console.error("Index error:", err);
102
+ return Response.json({ error: "Failed to index document" }, { status: 500 });
103
+ }
104
+ }
105
+
106
+ // POST /api/search - Semantic search
107
+ if (request.method === "POST" && url.pathname === "/api/search") {
108
+ try {
109
+ const body = (await request.json()) as {
110
+ query?: string;
111
+ limit?: number;
112
+ };
113
+ const { query, limit = 5 } = body;
114
+
115
+ if (!query) {
116
+ return Response.json({ error: "Missing query" }, { status: 400 });
117
+ }
118
+
119
+ // Generate query embedding
120
+ const embedding = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
121
+ text: query,
122
+ });
123
+
124
+ const embeddingVector = getEmbeddingVector(embedding);
125
+ if (!embeddingVector) {
126
+ return Response.json({ error: "Failed to generate embedding" }, { status: 500 });
127
+ }
128
+
129
+ // Search Vectorize
130
+ const results = await env.VECTORS.query(embeddingVector, {
131
+ topK: limit,
132
+ returnMetadata: "all",
133
+ });
134
+
135
+ return Response.json({ results: results.matches });
136
+ } catch (err) {
137
+ console.error("Search error:", err);
138
+ return Response.json({ error: "Search failed" }, { status: 500 });
139
+ }
140
+ }
141
+
142
+ return Response.json({ error: "Not found" }, { status: 404 });
143
+ },
144
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "types": ["@cloudflare/workers-types"]
11
+ },
12
+ "include": ["src/**/*"]
13
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "jack-template",
3
+ "main": "src/index.ts",
4
+ "compatibility_date": "2024-12-01",
5
+ "ai": {
6
+ "binding": "AI"
7
+ },
8
+ "vectorize": [
9
+ {
10
+ "binding": "VECTORS",
11
+ "index_name": "jack-template-vectors",
12
+ // preset: cloudflare = 768 dimensions, cosine metric
13
+ // matches @cf/baai/bge-base-en-v1.5 embedding model
14
+ "preset": "cloudflare"
15
+ }
16
+ ],
17
+ "d1_databases": [
18
+ {
19
+ "binding": "DB",
20
+ "database_name": "jack-template-db"
21
+ }
22
+ ],
23
+ "assets": {
24
+ "directory": "public",
25
+ "binding": "ASSETS"
26
+ }
27
+ }