@getjack/jack 0.1.22 → 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 (42) hide show
  1. package/package.json +1 -1
  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 +54 -76
  6. package/src/commands/publish.ts +7 -2
  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 +32 -55
  17. package/src/lib/project-detection.ts +48 -21
  18. package/src/lib/project-operations.ts +31 -13
  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/ai-chat/.jack.json +29 -0
  28. package/templates/ai-chat/bun.lock +18 -0
  29. package/templates/ai-chat/package.json +14 -0
  30. package/templates/ai-chat/public/chat.js +149 -0
  31. package/templates/ai-chat/public/index.html +209 -0
  32. package/templates/ai-chat/src/index.ts +105 -0
  33. package/templates/ai-chat/wrangler.jsonc +12 -0
  34. package/templates/semantic-search/.jack.json +26 -0
  35. package/templates/semantic-search/bun.lock +18 -0
  36. package/templates/semantic-search/package.json +12 -0
  37. package/templates/semantic-search/public/app.js +120 -0
  38. package/templates/semantic-search/public/index.html +210 -0
  39. package/templates/semantic-search/schema.sql +5 -0
  40. package/templates/semantic-search/src/index.ts +144 -0
  41. package/templates/semantic-search/tsconfig.json +13 -0
  42. package/templates/semantic-search/wrangler.jsonc +27 -0
@@ -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
+ }