@getjack/jack 0.1.34 → 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/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/do-config.ts +110 -0
- package/src/lib/do-export-validator.ts +26 -0
- 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 +13 -38
- 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 +1 -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/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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { runChecks } from "./monitor";
|
|
4
|
+
import { adminHTML } from "./admin";
|
|
5
5
|
|
|
6
6
|
type Bindings = {
|
|
7
7
|
DB: D1Database;
|
|
8
|
-
|
|
8
|
+
MONITOR_URLS?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const app = new Hono<{ Bindings: Bindings }>();
|
|
@@ -13,105 +13,175 @@ const app = new Hono<{ Bindings: Bindings }>();
|
|
|
13
13
|
app.use("/*", cors());
|
|
14
14
|
|
|
15
15
|
app.get("/", (c) => {
|
|
16
|
-
return c.
|
|
17
|
-
message: "Background worker running",
|
|
18
|
-
name: "jack-template",
|
|
19
|
-
});
|
|
16
|
+
return c.html(adminHTML());
|
|
20
17
|
});
|
|
21
18
|
|
|
22
19
|
app.get("/health", (c) => {
|
|
23
20
|
return c.json({ status: "ok", timestamp: Date.now() });
|
|
24
21
|
});
|
|
25
22
|
|
|
26
|
-
//
|
|
27
|
-
app.
|
|
23
|
+
// Rich per-group uptime stats (URLs hidden)
|
|
24
|
+
app.get("/api/status", async (c) => {
|
|
28
25
|
const db = c.env.DB;
|
|
26
|
+
const now = Math.floor(Date.now() / 1000);
|
|
27
|
+
const DAY = 86400;
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
const { results: groups } = await db
|
|
30
|
+
.prepare(
|
|
31
|
+
`SELECT
|
|
32
|
+
group_name,
|
|
33
|
+
COUNT(DISTINCT url) as endpoint_count,
|
|
34
|
+
MAX(created_at) as last_check_at,
|
|
35
|
+
SUM(CASE WHEN created_at > ?1 THEN 1 ELSE 0 END) as checks_24h,
|
|
36
|
+
SUM(CASE WHEN created_at > ?1 AND ok = 1 THEN 1 ELSE 0 END) as up_24h,
|
|
37
|
+
SUM(CASE WHEN created_at > ?2 THEN 1 ELSE 0 END) as checks_7d,
|
|
38
|
+
SUM(CASE WHEN created_at > ?2 AND ok = 1 THEN 1 ELSE 0 END) as up_7d,
|
|
39
|
+
COUNT(*) as checks_30d,
|
|
40
|
+
SUM(ok) as up_30d,
|
|
41
|
+
ROUND(AVG(CASE WHEN created_at > ?1 THEN latency_ms END)) as avg_latency,
|
|
42
|
+
MIN(CASE WHEN created_at > ?1 THEN latency_ms END) as min_latency,
|
|
43
|
+
MAX(CASE WHEN created_at > ?1 THEN latency_ms END) as max_latency
|
|
44
|
+
FROM checks
|
|
45
|
+
GROUP BY group_name
|
|
46
|
+
ORDER BY group_name`,
|
|
47
|
+
)
|
|
48
|
+
.bind(now - DAY, now - 7 * DAY)
|
|
49
|
+
.all();
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
const monitors = await Promise.all(
|
|
52
|
+
(groups as Array<Record<string, unknown>>).map(async (g) => {
|
|
53
|
+
const groupName = g.group_name as string;
|
|
54
|
+
|
|
55
|
+
// Recent checks for status bar + sparkline (newest first → reverse for display)
|
|
56
|
+
const { results: recentRaw } = await db
|
|
57
|
+
.prepare(
|
|
58
|
+
"SELECT ok, latency_ms FROM checks WHERE group_name = ? ORDER BY created_at DESC LIMIT 48",
|
|
59
|
+
)
|
|
60
|
+
.bind(groupName)
|
|
61
|
+
.all();
|
|
62
|
+
const recent = (
|
|
63
|
+
recentRaw as Array<{ ok: number; latency_ms: number }>
|
|
64
|
+
).reverse();
|
|
65
|
+
|
|
66
|
+
// Uptime streak: time since last failure (or since first check if none)
|
|
67
|
+
const lastFail = (await db
|
|
68
|
+
.prepare(
|
|
69
|
+
"SELECT created_at FROM checks WHERE group_name = ? AND ok = 0 ORDER BY created_at DESC LIMIT 1",
|
|
70
|
+
)
|
|
71
|
+
.bind(groupName)
|
|
72
|
+
.first()) as { created_at: number } | null;
|
|
73
|
+
|
|
74
|
+
const firstCheck = (await db
|
|
75
|
+
.prepare(
|
|
76
|
+
"SELECT created_at FROM checks WHERE group_name = ? ORDER BY created_at ASC LIMIT 1",
|
|
77
|
+
)
|
|
78
|
+
.bind(groupName)
|
|
79
|
+
.first()) as { created_at: number } | null;
|
|
80
|
+
|
|
81
|
+
const streakStart = lastFail
|
|
82
|
+
? lastFail.created_at
|
|
83
|
+
: firstCheck
|
|
84
|
+
? firstCheck.created_at
|
|
85
|
+
: now;
|
|
86
|
+
const streakSeconds = Math.max(0, now - streakStart);
|
|
87
|
+
|
|
88
|
+
// Current status from latest check per URL
|
|
89
|
+
const { results: latestPerUrl } = await db
|
|
90
|
+
.prepare(
|
|
91
|
+
`SELECT c.ok FROM checks c
|
|
92
|
+
INNER JOIN (
|
|
93
|
+
SELECT url, MAX(created_at) as max_ts
|
|
94
|
+
FROM checks WHERE group_name = ? GROUP BY url
|
|
95
|
+
) latest ON c.url = latest.url AND c.created_at = latest.max_ts
|
|
96
|
+
WHERE c.group_name = ?`,
|
|
97
|
+
)
|
|
98
|
+
.bind(groupName, groupName)
|
|
99
|
+
.all();
|
|
100
|
+
|
|
101
|
+
const allUp =
|
|
102
|
+
latestPerUrl.length > 0 &&
|
|
103
|
+
latestPerUrl.every((r: Record<string, unknown>) => r.ok);
|
|
104
|
+
|
|
105
|
+
function pct(up: number, total: number): number | null {
|
|
106
|
+
return total > 0 ? Math.round((1000 * up) / total) / 10 : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
group_name: groupName,
|
|
111
|
+
endpoint_count: g.endpoint_count,
|
|
112
|
+
all_up: allUp,
|
|
113
|
+
streak_seconds: streakSeconds,
|
|
114
|
+
last_check_at: g.last_check_at,
|
|
115
|
+
periods: {
|
|
116
|
+
"24h": {
|
|
117
|
+
uptime_pct: pct(g.up_24h as number, g.checks_24h as number),
|
|
118
|
+
failed_checks: (g.checks_24h as number) - (g.up_24h as number),
|
|
119
|
+
total_checks: g.checks_24h,
|
|
120
|
+
},
|
|
121
|
+
"7d": {
|
|
122
|
+
uptime_pct: pct(g.up_7d as number, g.checks_7d as number),
|
|
123
|
+
failed_checks: (g.checks_7d as number) - (g.up_7d as number),
|
|
124
|
+
total_checks: g.checks_7d,
|
|
125
|
+
},
|
|
126
|
+
"30d": {
|
|
127
|
+
uptime_pct: pct(g.up_30d as number, g.checks_30d as number),
|
|
128
|
+
failed_checks: (g.checks_30d as number) - (g.up_30d as number),
|
|
129
|
+
total_checks: g.checks_30d,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
latency: {
|
|
133
|
+
avg: g.avg_latency || 0,
|
|
134
|
+
min: g.min_latency || 0,
|
|
135
|
+
max: g.max_latency || 0,
|
|
136
|
+
},
|
|
137
|
+
recent_checks: recent,
|
|
138
|
+
};
|
|
139
|
+
}),
|
|
56
140
|
);
|
|
57
|
-
if (!valid) {
|
|
58
|
-
return c.json({ error: "Invalid signature" }, 401);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Parse and log the event
|
|
62
|
-
let parsed: Record<string, unknown>;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(body);
|
|
65
|
-
} catch {
|
|
66
|
-
return c.json({ error: "Invalid JSON body" }, 400);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const eventType = (parsed.event as string) || "unknown";
|
|
70
|
-
const source = (parsed.source as string) || "unknown";
|
|
71
|
-
|
|
72
|
-
const eventId = await logWebhookEvent(db, {
|
|
73
|
-
source,
|
|
74
|
-
eventType,
|
|
75
|
-
payload: body,
|
|
76
|
-
});
|
|
77
141
|
|
|
78
|
-
|
|
79
|
-
await createJob(db, {
|
|
80
|
-
type: `webhook.${eventType}`,
|
|
81
|
-
payload: { webhookEventId: eventId, data: parsed.data || {} },
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return c.json({ received: true, id: eventId });
|
|
142
|
+
return c.json({ monitors });
|
|
85
143
|
});
|
|
86
144
|
|
|
87
|
-
//
|
|
88
|
-
app.get("/
|
|
145
|
+
// Recent checks (URLs hidden)
|
|
146
|
+
app.get("/api/checks", async (c) => {
|
|
89
147
|
const db = c.env.DB;
|
|
90
|
-
|
|
91
148
|
const { results } = await db
|
|
92
149
|
.prepare(
|
|
93
|
-
"SELECT
|
|
150
|
+
"SELECT id, group_name, status_code, latency_ms, ok, source, error, created_at FROM checks ORDER BY created_at DESC LIMIT 100",
|
|
94
151
|
)
|
|
95
152
|
.all();
|
|
96
|
-
|
|
97
|
-
return c.json({ jobs: results });
|
|
153
|
+
return c.json({ checks: results });
|
|
98
154
|
});
|
|
99
155
|
|
|
100
|
-
//
|
|
101
|
-
app.
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
156
|
+
// Manual trigger
|
|
157
|
+
app.post("/api/trigger", async (c) => {
|
|
158
|
+
const results = await runChecks(
|
|
159
|
+
c.env.DB,
|
|
160
|
+
c.env as unknown as Record<string, unknown>,
|
|
161
|
+
"manual",
|
|
162
|
+
);
|
|
163
|
+
const allOk = results.every((r) => r.ok);
|
|
164
|
+
return c.json({
|
|
165
|
+
checked: results.length,
|
|
166
|
+
all_ok: allOk,
|
|
167
|
+
results: results.map((r) => ({
|
|
168
|
+
group: r.group_name,
|
|
169
|
+
ok: r.ok,
|
|
170
|
+
latency_ms: r.latency_ms,
|
|
171
|
+
})),
|
|
172
|
+
});
|
|
173
|
+
});
|
|
113
174
|
|
|
114
|
-
|
|
175
|
+
// Cron handler
|
|
176
|
+
app.post("/__scheduled", async (c) => {
|
|
177
|
+
const results = await runChecks(
|
|
178
|
+
c.env.DB,
|
|
179
|
+
c.env as unknown as Record<string, unknown>,
|
|
180
|
+
"cron",
|
|
181
|
+
);
|
|
182
|
+
const allOk = results.every((r) => r.ok);
|
|
183
|
+
console.log(`Uptime check: ${results.length} URLs, all_ok=${allOk}`);
|
|
184
|
+
return c.json({ checked: results.length, all_ok: allOk });
|
|
115
185
|
});
|
|
116
186
|
|
|
117
187
|
export default app;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export interface MonitorGroup {
|
|
2
|
+
name: string;
|
|
3
|
+
urls: string[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CheckResult {
|
|
7
|
+
url: string;
|
|
8
|
+
group_name: string;
|
|
9
|
+
status_code: number | null;
|
|
10
|
+
latency_ms: number;
|
|
11
|
+
ok: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function checkUrl(url: string): Promise<Omit<CheckResult, "group_name">> {
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
19
|
+
return {
|
|
20
|
+
url,
|
|
21
|
+
status_code: res.status,
|
|
22
|
+
latency_ms: Date.now() - start,
|
|
23
|
+
ok: res.ok,
|
|
24
|
+
error: null,
|
|
25
|
+
};
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return {
|
|
28
|
+
url,
|
|
29
|
+
status_code: null,
|
|
30
|
+
latency_ms: Date.now() - start,
|
|
31
|
+
ok: false,
|
|
32
|
+
error: err instanceof Error ? err.message : String(err),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse MONITOR_URLS into named groups.
|
|
39
|
+
*
|
|
40
|
+
* Format: "GroupName=url1,url2;Other=url3,url4"
|
|
41
|
+
* Plain URLs without a group name go into "Default".
|
|
42
|
+
* If nothing is configured, monitors https://1.1.1.1 as "Default".
|
|
43
|
+
*/
|
|
44
|
+
export function parseMonitorConfig(env: Record<string, unknown>): MonitorGroup[] {
|
|
45
|
+
const raw = env.MONITOR_URLS as string | undefined;
|
|
46
|
+
if (!raw?.trim()) {
|
|
47
|
+
return [{ name: "Default", urls: ["https://1.1.1.1"] }];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const groups: MonitorGroup[] = [];
|
|
51
|
+
|
|
52
|
+
for (const segment of raw.split(";")) {
|
|
53
|
+
const trimmed = segment.trim();
|
|
54
|
+
if (!trimmed) continue;
|
|
55
|
+
|
|
56
|
+
const eqIndex = trimmed.indexOf("=");
|
|
57
|
+
if (eqIndex > 0) {
|
|
58
|
+
const name = trimmed.slice(0, eqIndex).trim();
|
|
59
|
+
const urls = trimmed
|
|
60
|
+
.slice(eqIndex + 1)
|
|
61
|
+
.split(",")
|
|
62
|
+
.map((u) => u.trim())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
if (urls.length) groups.push({ name, urls });
|
|
65
|
+
} else {
|
|
66
|
+
const urls = trimmed
|
|
67
|
+
.split(",")
|
|
68
|
+
.map((u) => u.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
const existing = groups.find((g) => g.name === "Default");
|
|
71
|
+
if (existing) {
|
|
72
|
+
existing.urls.push(...urls);
|
|
73
|
+
} else if (urls.length) {
|
|
74
|
+
groups.push({ name: "Default", urls });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (groups.length === 0) {
|
|
80
|
+
groups.push({ name: "Default", urls: ["https://1.1.1.1"] });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return groups;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function runChecks(
|
|
87
|
+
db: D1Database,
|
|
88
|
+
env: Record<string, unknown>,
|
|
89
|
+
source: "cron" | "manual",
|
|
90
|
+
): Promise<CheckResult[]> {
|
|
91
|
+
const groups = parseMonitorConfig(env);
|
|
92
|
+
const promises: Promise<CheckResult>[] = [];
|
|
93
|
+
|
|
94
|
+
for (const group of groups) {
|
|
95
|
+
for (const url of group.urls) {
|
|
96
|
+
promises.push(
|
|
97
|
+
checkUrl(url).then((r) => ({ ...r, group_name: group.name })),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const results = await Promise.all(promises);
|
|
103
|
+
|
|
104
|
+
for (const r of results) {
|
|
105
|
+
await db
|
|
106
|
+
.prepare(
|
|
107
|
+
"INSERT INTO checks (id, url, group_name, status_code, latency_ms, ok, source, error, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
108
|
+
)
|
|
109
|
+
.bind(
|
|
110
|
+
crypto.randomUUID(),
|
|
111
|
+
r.url,
|
|
112
|
+
r.group_name,
|
|
113
|
+
r.status_code,
|
|
114
|
+
r.latency_ms,
|
|
115
|
+
r.ok ? 1 : 0,
|
|
116
|
+
source,
|
|
117
|
+
r.error,
|
|
118
|
+
Math.floor(Date.now() / 1000),
|
|
119
|
+
)
|
|
120
|
+
.run();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return results;
|
|
124
|
+
}
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import { type JackAI, createJackAI } from "./jack-ai";
|
|
2
|
-
import { type JackVectorize, createJackVectorize } from "./jack-vectorize";
|
|
3
2
|
|
|
4
3
|
interface Env {
|
|
5
|
-
// Direct bindings (for local dev with wrangler)
|
|
6
4
|
AI?: Ai;
|
|
7
|
-
VECTORS
|
|
8
|
-
// Jack proxy bindings (injected in jack cloud)
|
|
5
|
+
VECTORS: VectorizeIndex;
|
|
9
6
|
__AI_PROXY?: Fetcher;
|
|
10
|
-
__VECTORIZE_PROXY?: Fetcher;
|
|
11
|
-
__JACK_PROJECT_ID?: string;
|
|
12
|
-
__JACK_ORG_ID?: string;
|
|
13
|
-
// Other bindings
|
|
14
7
|
DB: D1Database;
|
|
15
8
|
ASSETS: Fetcher;
|
|
16
9
|
}
|
|
17
10
|
|
|
18
|
-
// Index name must match wrangler.jsonc vectorize config
|
|
19
|
-
const VECTORIZE_INDEX_NAME = "jack-template-vectors";
|
|
20
|
-
|
|
21
11
|
// Minimal AI interface for embedding generation
|
|
22
12
|
type AIClient = {
|
|
23
13
|
run: (model: string, inputs: { text: string }) => Promise<{ data: number[][] } | unknown>;
|
|
@@ -25,10 +15,8 @@ type AIClient = {
|
|
|
25
15
|
|
|
26
16
|
function getAI(env: Env): AIClient {
|
|
27
17
|
// Prefer jack cloud proxy if available (for metering)
|
|
28
|
-
if (env.__AI_PROXY
|
|
29
|
-
return createJackAI(
|
|
30
|
-
env as Required<Pick<Env, "__AI_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">>,
|
|
31
|
-
) as AIClient;
|
|
18
|
+
if (env.__AI_PROXY) {
|
|
19
|
+
return createJackAI(env as Pick<Env, "__AI_PROXY"> & { __AI_PROXY: Fetcher }) as AIClient;
|
|
32
20
|
}
|
|
33
21
|
// Fallback to direct binding for local dev
|
|
34
22
|
if (env.AI) {
|
|
@@ -37,32 +25,6 @@ function getAI(env: Env): AIClient {
|
|
|
37
25
|
throw new Error("No AI binding available");
|
|
38
26
|
}
|
|
39
27
|
|
|
40
|
-
// Minimal Vectorize interface
|
|
41
|
-
type VectorizeClient = {
|
|
42
|
-
insert: (
|
|
43
|
-
vectors: { id: string; values: number[]; metadata?: Record<string, unknown> }[],
|
|
44
|
-
) => Promise<unknown>;
|
|
45
|
-
query: (
|
|
46
|
-
vector: number[],
|
|
47
|
-
options?: { topK?: number; returnMetadata?: "none" | "indexed" | "all" },
|
|
48
|
-
) => Promise<{ matches: { id: string; score: number; metadata?: Record<string, unknown> }[] }>;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function getVectorize(env: Env): VectorizeClient {
|
|
52
|
-
// Prefer jack cloud proxy if available (for metering)
|
|
53
|
-
if (env.__VECTORIZE_PROXY && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
|
|
54
|
-
return createJackVectorize(
|
|
55
|
-
env as Required<Pick<Env, "__VECTORIZE_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">>,
|
|
56
|
-
VECTORIZE_INDEX_NAME,
|
|
57
|
-
) as VectorizeClient;
|
|
58
|
-
}
|
|
59
|
-
// Fallback to direct binding for local dev
|
|
60
|
-
if (env.VECTORS) {
|
|
61
|
-
return env.VECTORS as unknown as VectorizeClient;
|
|
62
|
-
}
|
|
63
|
-
throw new Error("No Vectorize binding available");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
28
|
// Rate limiting: 10 requests per minute per IP
|
|
67
29
|
const RATE_LIMIT = 10;
|
|
68
30
|
const RATE_WINDOW_MS = 60_000;
|
|
@@ -142,7 +104,7 @@ export default {
|
|
|
142
104
|
}
|
|
143
105
|
|
|
144
106
|
// Store in Vectorize
|
|
145
|
-
const vectors =
|
|
107
|
+
const vectors = env.VECTORS;
|
|
146
108
|
await vectors.insert([
|
|
147
109
|
{
|
|
148
110
|
id,
|
|
@@ -188,7 +150,7 @@ export default {
|
|
|
188
150
|
}
|
|
189
151
|
|
|
190
152
|
// Search Vectorize
|
|
191
|
-
const vectors =
|
|
153
|
+
const vectors = env.VECTORS;
|
|
192
154
|
const results = await vectors.query(embeddingVector, {
|
|
193
155
|
topK: limit,
|
|
194
156
|
returnMetadata: "all",
|
|
@@ -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
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "telegram-bot",
|
|
3
|
+
"description": "Telegram bot with AI-powered responses",
|
|
4
|
+
"secrets": ["BOT_TOKEN", "WEBHOOK_SECRET"],
|
|
5
|
+
"requires": ["AI"],
|
|
6
|
+
"intent": {
|
|
7
|
+
"keywords": ["telegram", "bot", "chat", "messaging", "ai"],
|
|
8
|
+
"examples": ["telegram bot", "AI telegram assistant", "messaging bot", "chat bot"]
|
|
9
|
+
},
|
|
10
|
+
"agentContext": {
|
|
11
|
+
"summary": "A Telegram bot with grammY, AI-powered responses, and webhook delivery.",
|
|
12
|
+
"full_text": "## Project Structure\n\n- `src/index.ts` - grammY bot with webhook handler, commands, and AI integration\n- `src/jack-ai.ts` - Jack AI proxy wrapper (do not modify)\n\n## Commands\n\n- `/start` - Welcome message with available commands\n- `/help` - List all commands\n- `/ask <question>` - Ask the AI anything\n- `/status` - Bot info: region, timestamp, uptime\n\n## AI Responses\n\nThe bot responds with AI in two ways:\n1. `/ask <question>` - Explicit AI query\n2. Reply to any bot message - Treated as a follow-up question\n\nAI uses the jack metered proxy. Change the model in `src/index.ts`.\n\n## Webhook\n\nTelegram delivers updates via webhook POST to your deploy URL. The webhook is auto-registered during deployment. The `WEBHOOK_SECRET` header is verified on every request.\n\n## Environment Variables\n\n- `BOT_TOKEN` - Telegram bot token from @BotFather (secret)\n- `WEBHOOK_SECRET` - Auto-generated webhook verification token (secret)\n\n## Changing the AI Model\n\nEdit the model string in `src/index.ts`:\n```typescript\nconst MODEL = \"@cf/meta/llama-3.1-8b-instruct\";\n```\n\nAvailable models: https://developers.cloudflare.com/workers-ai/models/\n\n## Resources\n\n- [grammY Documentation](https://grammy.dev)\n- [Telegram Bot API](https://core.telegram.org/bots/api)\n- [AI Models](https://developers.cloudflare.com/workers-ai/models)"
|
|
13
|
+
},
|
|
14
|
+
"hooks": {
|
|
15
|
+
"preCreate": [
|
|
16
|
+
{
|
|
17
|
+
"action": "require",
|
|
18
|
+
"source": "secret",
|
|
19
|
+
"key": "BOT_TOKEN",
|
|
20
|
+
"message": "Telegram bot token from @BotFather",
|
|
21
|
+
"setupUrl": "https://t.me/BotFather",
|
|
22
|
+
"onMissing": "prompt",
|
|
23
|
+
"promptMessage": "Enter your Telegram Bot Token:",
|
|
24
|
+
"perProject": true,
|
|
25
|
+
"validateCommand": "curl -sf --max-time 5 https://api.telegram.org/bot{{value}}/getMe > /dev/null",
|
|
26
|
+
"validateError": "Invalid bot token. Make sure you copied it correctly from @BotFather."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"action": "require",
|
|
30
|
+
"source": "secret",
|
|
31
|
+
"key": "WEBHOOK_SECRET",
|
|
32
|
+
"message": "Generating webhook verification secret...",
|
|
33
|
+
"onMissing": "generate",
|
|
34
|
+
"generateCommand": "openssl rand -hex 32",
|
|
35
|
+
"perProject": true
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"postDeploy": [
|
|
39
|
+
{
|
|
40
|
+
"action": "shell",
|
|
41
|
+
"command": "curl -sf --max-time 10 {{url}}/register-webhook > /dev/null || true",
|
|
42
|
+
"message": "Registering Telegram webhook..."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"action": "shell",
|
|
46
|
+
"command": "BOT_LINK=$(curl -sf --max-time 10 {{url}}/bot-link) && [ -n \"$BOT_LINK\" ] && echo \"\" && echo \" Your bot is live! Open it here:\" && echo \" $BOT_LINK\" && echo \"\" && echo \" Send /start — it will respond.\" || true",
|
|
47
|
+
"message": ""
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"action": "clipboard",
|
|
51
|
+
"text": "{{url}}",
|
|
52
|
+
"message": "Deploy URL copied to clipboard"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "jack-template",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"grammy": "^1.35.0",
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@cloudflare/workers-types": "^4.20241205.0",
|
|
12
|
+
"typescript": "^5.0.0",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
"packages": {
|
|
17
|
+
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260213.0", "", {}, "sha512-dr905ft/1R0mnfdT9aun4vanLgIBN27ZyPxTCENKmhctSz6zNmBOvHbzDWAhGE0RBAKFf3X7ifMRcd0MkmBvgA=="],
|
|
18
|
+
|
|
19
|
+
"@grammyjs/types": ["@grammyjs/types@3.24.0", "", {}, "sha512-qQIEs4lN5WqUdr4aT8MeU6UFpMbGYAvcvYSW1A4OO1PABGJQHz/KLON6qvpf+5RxaNDQBxiY2k2otIhg/AG7RQ=="],
|
|
20
|
+
|
|
21
|
+
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
|
22
|
+
|
|
23
|
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
|
24
|
+
|
|
25
|
+
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
|
26
|
+
|
|
27
|
+
"grammy": ["grammy@1.40.0", "", { "dependencies": { "@grammyjs/types": "3.24.0", "abort-controller": "^3.0.0", "debug": "^4.4.3", "node-fetch": "^2.7.0" } }, "sha512-ssuE7fc1AwqlUxHr931OCVW3fU+oFDjHZGgvIedPKXfTdjXvzP19xifvVGCnPtYVUig1Kz+gwxe4A9M5WdkT4Q=="],
|
|
28
|
+
|
|
29
|
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
|
30
|
+
|
|
31
|
+
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
|
32
|
+
|
|
33
|
+
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
|
34
|
+
|
|
35
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
36
|
+
|
|
37
|
+
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
|
38
|
+
|
|
39
|
+
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jack-template",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "wrangler dev",
|
|
7
|
+
"deploy": "wrangler deploy"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"grammy": "^1.35.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@cloudflare/workers-types": "^4.20241205.0",
|
|
14
|
+
"typescript": "^5.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|