@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.
- package/package.json +1 -1
- package/src/commands/clone.ts +5 -5
- package/src/commands/down.ts +44 -69
- package/src/commands/link.ts +9 -6
- package/src/commands/new.ts +54 -76
- package/src/commands/publish.ts +7 -2
- package/src/commands/secrets.ts +2 -1
- package/src/commands/services.ts +41 -15
- package/src/commands/update.ts +2 -2
- package/src/index.ts +43 -2
- package/src/lib/agent-integration.ts +217 -0
- package/src/lib/auth/login-flow.ts +2 -1
- package/src/lib/binding-validator.ts +2 -3
- package/src/lib/build-helper.ts +7 -1
- package/src/lib/hooks.ts +101 -21
- package/src/lib/managed-down.ts +32 -55
- package/src/lib/project-detection.ts +48 -21
- package/src/lib/project-operations.ts +31 -13
- package/src/lib/prompts.ts +16 -23
- package/src/lib/services/db-execute.ts +39 -0
- package/src/lib/services/sql-classifier.test.ts +2 -2
- package/src/lib/services/sql-classifier.ts +5 -4
- package/src/lib/version-check.ts +15 -10
- package/src/lib/zip-packager.ts +16 -0
- package/src/mcp/resources/index.ts +42 -2
- package/src/templates/index.ts +63 -3
- package/templates/ai-chat/.jack.json +29 -0
- package/templates/ai-chat/bun.lock +18 -0
- package/templates/ai-chat/package.json +14 -0
- package/templates/ai-chat/public/chat.js +149 -0
- package/templates/ai-chat/public/index.html +209 -0
- package/templates/ai-chat/src/index.ts +105 -0
- package/templates/ai-chat/wrangler.jsonc +12 -0
- package/templates/semantic-search/.jack.json +26 -0
- package/templates/semantic-search/bun.lock +18 -0
- package/templates/semantic-search/package.json +12 -0
- package/templates/semantic-search/public/app.js +120 -0
- package/templates/semantic-search/public/index.html +210 -0
- package/templates/semantic-search/schema.sql +5 -0
- package/templates/semantic-search/src/index.ts +144 -0
- package/templates/semantic-search/tsconfig.json +13 -0
- 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,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
|
+
}
|