@getjack/jack 0.1.32 → 0.1.34

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/commands/deploys.ts +95 -0
  3. package/src/commands/link.ts +8 -0
  4. package/src/commands/mcp.ts +179 -4
  5. package/src/commands/rollback.ts +53 -0
  6. package/src/commands/secrets.ts +3 -1
  7. package/src/commands/services.ts +11 -1
  8. package/src/commands/ship.ts +3 -1
  9. package/src/commands/tokens.ts +16 -1
  10. package/src/commands/whoami.ts +43 -8
  11. package/src/index.ts +16 -0
  12. package/src/lib/agent-files.ts +54 -4
  13. package/src/lib/agent-integration.ts +4 -166
  14. package/src/lib/claude-hooks-installer.ts +55 -0
  15. package/src/lib/control-plane.ts +78 -40
  16. package/src/lib/crypto.ts +84 -0
  17. package/src/lib/debug.ts +2 -1
  18. package/src/lib/deploy-upload.ts +13 -3
  19. package/src/lib/hooks.ts +4 -3
  20. package/src/lib/managed-deploy.ts +12 -9
  21. package/src/lib/project-link.ts +6 -0
  22. package/src/lib/project-operations.ts +92 -30
  23. package/src/lib/prompts.ts +2 -2
  24. package/src/lib/telemetry.ts +2 -0
  25. package/src/mcp/README.md +1 -1
  26. package/src/mcp/resources/index.ts +1 -16
  27. package/src/mcp/server.ts +23 -0
  28. package/src/mcp/tools/index.ts +133 -17
  29. package/src/mcp/types.ts +1 -0
  30. package/src/mcp/utils.ts +2 -1
  31. package/src/templates/index.ts +25 -73
  32. package/templates/CLAUDE.md +62 -0
  33. package/templates/ai-chat/.jack.json +10 -5
  34. package/templates/ai-chat/bun.lock +50 -1
  35. package/templates/ai-chat/package.json +5 -0
  36. package/templates/ai-chat/public/app.js +73 -0
  37. package/templates/ai-chat/public/index.html +14 -197
  38. package/templates/ai-chat/schema.sql +14 -0
  39. package/templates/ai-chat/src/index.ts +86 -102
  40. package/templates/ai-chat/wrangler.jsonc +8 -1
  41. package/templates/cron/.jack.json +66 -0
  42. package/templates/cron/bun.lock +23 -0
  43. package/templates/cron/package.json +16 -0
  44. package/templates/cron/schema.sql +24 -0
  45. package/templates/cron/src/index.ts +117 -0
  46. package/templates/cron/src/jobs.ts +139 -0
  47. package/templates/cron/src/webhooks.ts +95 -0
  48. package/templates/cron/tsconfig.json +17 -0
  49. package/templates/cron/wrangler.jsonc +11 -0
  50. package/templates/miniapp/.jack.json +1 -1
  51. package/templates/nextjs/.jack.json +1 -1
  52. package/templates/nextjs-auth/.jack.json +44 -0
  53. package/templates/nextjs-auth/app/api/auth/[...all]/route.ts +11 -0
  54. package/templates/nextjs-auth/app/dashboard/loading.tsx +53 -0
  55. package/templates/nextjs-auth/app/dashboard/page.tsx +73 -0
  56. package/templates/nextjs-auth/app/error.tsx +44 -0
  57. package/templates/nextjs-auth/app/globals.css +1 -0
  58. package/templates/nextjs-auth/app/health/route.ts +3 -0
  59. package/templates/nextjs-auth/app/layout.tsx +24 -0
  60. package/templates/nextjs-auth/app/login/page.tsx +10 -0
  61. package/templates/nextjs-auth/app/page.tsx +86 -0
  62. package/templates/nextjs-auth/app/signup/page.tsx +10 -0
  63. package/templates/nextjs-auth/bun.lock +1065 -0
  64. package/templates/nextjs-auth/cloudflare-env.d.ts +8 -0
  65. package/templates/nextjs-auth/components/auth-form.tsx +191 -0
  66. package/templates/nextjs-auth/components/header.tsx +50 -0
  67. package/templates/nextjs-auth/components/user-menu.tsx +23 -0
  68. package/templates/nextjs-auth/lib/auth-client.ts +3 -0
  69. package/templates/nextjs-auth/lib/auth.ts +43 -0
  70. package/templates/nextjs-auth/lib/utils.ts +6 -0
  71. package/templates/nextjs-auth/middleware.ts +33 -0
  72. package/templates/nextjs-auth/next.config.ts +8 -0
  73. package/templates/nextjs-auth/open-next.config.ts +6 -0
  74. package/templates/nextjs-auth/package.json +33 -0
  75. package/templates/nextjs-auth/postcss.config.mjs +8 -0
  76. package/templates/nextjs-auth/schema.sql +49 -0
  77. package/templates/nextjs-auth/tsconfig.json +28 -0
  78. package/templates/nextjs-auth/wrangler.jsonc +23 -0
  79. package/templates/nextjs-clerk/.jack.json +54 -0
  80. package/templates/nextjs-clerk/app/dashboard/page.tsx +69 -0
  81. package/templates/nextjs-clerk/app/globals.css +1 -0
  82. package/templates/nextjs-clerk/app/health/route.ts +3 -0
  83. package/templates/nextjs-clerk/app/layout.tsx +28 -0
  84. package/templates/nextjs-clerk/app/page.tsx +86 -0
  85. package/templates/nextjs-clerk/app/sign-in/[[...sign-in]]/page.tsx +9 -0
  86. package/templates/nextjs-clerk/app/sign-up/[[...sign-up]]/page.tsx +9 -0
  87. package/templates/nextjs-clerk/bun.lock +1055 -0
  88. package/templates/nextjs-clerk/cloudflare-env.d.ts +3 -0
  89. package/templates/nextjs-clerk/components/header.tsx +40 -0
  90. package/templates/nextjs-clerk/lib/utils.ts +6 -0
  91. package/templates/nextjs-clerk/middleware.ts +18 -0
  92. package/templates/nextjs-clerk/next.config.ts +8 -0
  93. package/templates/nextjs-clerk/open-next.config.ts +6 -0
  94. package/templates/nextjs-clerk/package.json +31 -0
  95. package/templates/nextjs-clerk/postcss.config.mjs +8 -0
  96. package/templates/nextjs-clerk/tsconfig.json +28 -0
  97. package/templates/nextjs-clerk/wrangler.jsonc +17 -0
  98. package/templates/nextjs-shadcn/.jack.json +34 -0
  99. package/templates/nextjs-shadcn/app/dashboard/data.json +614 -0
  100. package/templates/nextjs-shadcn/app/dashboard/page.tsx +55 -0
  101. package/templates/nextjs-shadcn/app/globals.css +126 -0
  102. package/templates/nextjs-shadcn/app/health/route.ts +3 -0
  103. package/templates/nextjs-shadcn/app/layout.tsx +24 -0
  104. package/templates/nextjs-shadcn/app/login/page.tsx +19 -0
  105. package/templates/nextjs-shadcn/app/page.tsx +180 -0
  106. package/templates/nextjs-shadcn/app/showcase.tsx +1262 -0
  107. package/templates/nextjs-shadcn/bun.lock +1789 -0
  108. package/templates/nextjs-shadcn/cloudflare-env.d.ts +4 -0
  109. package/templates/nextjs-shadcn/components/app-sidebar.tsx +175 -0
  110. package/templates/nextjs-shadcn/components/chart-area-interactive.tsx +291 -0
  111. package/templates/nextjs-shadcn/components/data-table.tsx +807 -0
  112. package/templates/nextjs-shadcn/components/login-form.tsx +95 -0
  113. package/templates/nextjs-shadcn/components/nav-documents.tsx +92 -0
  114. package/templates/nextjs-shadcn/components/nav-main.tsx +73 -0
  115. package/templates/nextjs-shadcn/components/nav-projects.tsx +89 -0
  116. package/templates/nextjs-shadcn/components/nav-secondary.tsx +42 -0
  117. package/templates/nextjs-shadcn/components/nav-user.tsx +114 -0
  118. package/templates/nextjs-shadcn/components/section-cards.tsx +102 -0
  119. package/templates/nextjs-shadcn/components/site-header.tsx +30 -0
  120. package/templates/nextjs-shadcn/components/team-switcher.tsx +91 -0
  121. package/templates/nextjs-shadcn/components/ui/accordion.tsx +66 -0
  122. package/templates/nextjs-shadcn/components/ui/alert-dialog.tsx +196 -0
  123. package/templates/nextjs-shadcn/components/ui/alert.tsx +66 -0
  124. package/templates/nextjs-shadcn/components/ui/aspect-ratio.tsx +11 -0
  125. package/templates/nextjs-shadcn/components/ui/avatar.tsx +109 -0
  126. package/templates/nextjs-shadcn/components/ui/badge.tsx +48 -0
  127. package/templates/nextjs-shadcn/components/ui/breadcrumb.tsx +109 -0
  128. package/templates/nextjs-shadcn/components/ui/button-group.tsx +83 -0
  129. package/templates/nextjs-shadcn/components/ui/button.tsx +64 -0
  130. package/templates/nextjs-shadcn/components/ui/calendar.tsx +220 -0
  131. package/templates/nextjs-shadcn/components/ui/card.tsx +92 -0
  132. package/templates/nextjs-shadcn/components/ui/carousel.tsx +241 -0
  133. package/templates/nextjs-shadcn/components/ui/chart.tsx +357 -0
  134. package/templates/nextjs-shadcn/components/ui/checkbox.tsx +32 -0
  135. package/templates/nextjs-shadcn/components/ui/collapsible.tsx +33 -0
  136. package/templates/nextjs-shadcn/components/ui/combobox.tsx +310 -0
  137. package/templates/nextjs-shadcn/components/ui/command.tsx +184 -0
  138. package/templates/nextjs-shadcn/components/ui/context-menu.tsx +252 -0
  139. package/templates/nextjs-shadcn/components/ui/dialog.tsx +158 -0
  140. package/templates/nextjs-shadcn/components/ui/direction.tsx +22 -0
  141. package/templates/nextjs-shadcn/components/ui/drawer.tsx +135 -0
  142. package/templates/nextjs-shadcn/components/ui/dropdown-menu.tsx +257 -0
  143. package/templates/nextjs-shadcn/components/ui/empty.tsx +104 -0
  144. package/templates/nextjs-shadcn/components/ui/field.tsx +248 -0
  145. package/templates/nextjs-shadcn/components/ui/form.tsx +167 -0
  146. package/templates/nextjs-shadcn/components/ui/hover-card.tsx +44 -0
  147. package/templates/nextjs-shadcn/components/ui/input-group.tsx +170 -0
  148. package/templates/nextjs-shadcn/components/ui/input-otp.tsx +77 -0
  149. package/templates/nextjs-shadcn/components/ui/input.tsx +21 -0
  150. package/templates/nextjs-shadcn/components/ui/item.tsx +193 -0
  151. package/templates/nextjs-shadcn/components/ui/kbd.tsx +28 -0
  152. package/templates/nextjs-shadcn/components/ui/label.tsx +24 -0
  153. package/templates/nextjs-shadcn/components/ui/menubar.tsx +276 -0
  154. package/templates/nextjs-shadcn/components/ui/native-select.tsx +53 -0
  155. package/templates/nextjs-shadcn/components/ui/navigation-menu.tsx +168 -0
  156. package/templates/nextjs-shadcn/components/ui/pagination.tsx +127 -0
  157. package/templates/nextjs-shadcn/components/ui/popover.tsx +89 -0
  158. package/templates/nextjs-shadcn/components/ui/progress.tsx +31 -0
  159. package/templates/nextjs-shadcn/components/ui/radio-group.tsx +45 -0
  160. package/templates/nextjs-shadcn/components/ui/resizable.tsx +53 -0
  161. package/templates/nextjs-shadcn/components/ui/scroll-area.tsx +58 -0
  162. package/templates/nextjs-shadcn/components/ui/select.tsx +190 -0
  163. package/templates/nextjs-shadcn/components/ui/separator.tsx +28 -0
  164. package/templates/nextjs-shadcn/components/ui/sheet.tsx +143 -0
  165. package/templates/nextjs-shadcn/components/ui/sidebar.tsx +726 -0
  166. package/templates/nextjs-shadcn/components/ui/skeleton.tsx +13 -0
  167. package/templates/nextjs-shadcn/components/ui/slider.tsx +63 -0
  168. package/templates/nextjs-shadcn/components/ui/sonner.tsx +40 -0
  169. package/templates/nextjs-shadcn/components/ui/spinner.tsx +16 -0
  170. package/templates/nextjs-shadcn/components/ui/switch.tsx +35 -0
  171. package/templates/nextjs-shadcn/components/ui/table.tsx +116 -0
  172. package/templates/nextjs-shadcn/components/ui/tabs.tsx +91 -0
  173. package/templates/nextjs-shadcn/components/ui/textarea.tsx +18 -0
  174. package/templates/nextjs-shadcn/components/ui/toggle-group.tsx +83 -0
  175. package/templates/nextjs-shadcn/components/ui/toggle.tsx +47 -0
  176. package/templates/nextjs-shadcn/components/ui/tooltip.tsx +57 -0
  177. package/templates/nextjs-shadcn/components.json +23 -0
  178. package/templates/nextjs-shadcn/hooks/use-mobile.ts +19 -0
  179. package/templates/nextjs-shadcn/lib/utils.ts +6 -0
  180. package/templates/nextjs-shadcn/next-env.d.ts +6 -0
  181. package/templates/nextjs-shadcn/next.config.ts +8 -0
  182. package/templates/nextjs-shadcn/open-next.config.ts +6 -0
  183. package/templates/nextjs-shadcn/package.json +55 -0
  184. package/templates/nextjs-shadcn/postcss.config.mjs +8 -0
  185. package/templates/nextjs-shadcn/tsconfig.json +28 -0
  186. package/templates/nextjs-shadcn/wrangler.jsonc +23 -0
  187. package/templates/resend/.jack.json +64 -0
  188. package/templates/resend/bun.lock +23 -0
  189. package/templates/resend/package.json +16 -0
  190. package/templates/resend/schema.sql +13 -0
  191. package/templates/resend/src/email.ts +165 -0
  192. package/templates/resend/src/index.ts +108 -0
  193. package/templates/resend/tsconfig.json +17 -0
  194. package/templates/resend/wrangler.jsonc +11 -0
  195. package/templates/saas/.jack.json +1 -1
  196. package/templates/ai-chat/public/chat.js +0 -149
@@ -0,0 +1,73 @@
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { useChat } from "@ai-sdk/react";
4
+
5
+ const { useState, useEffect, useRef } = React;
6
+
7
+ function App() {
8
+ const [chatId, setChatId] = useState(null);
9
+ const messagesEndRef = useRef(null);
10
+
11
+ // Create a new chat on mount
12
+ useEffect(() => {
13
+ fetch("/api/chat/new", { method: "POST" })
14
+ .then((r) => r.json())
15
+ .then((data) => setChatId(data.id));
16
+ }, []);
17
+
18
+ const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
19
+ api: "/api/chat",
20
+ body: { chatId },
21
+ });
22
+
23
+ // Auto-scroll
24
+ useEffect(() => {
25
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
26
+ }, [messages]);
27
+
28
+ return React.createElement("div", { style: styles.container },
29
+ React.createElement("header", { style: styles.header },
30
+ React.createElement("h1", { style: styles.title }, "AI Chat"),
31
+ ),
32
+ React.createElement("div", { style: styles.messages },
33
+ messages.length === 0 && React.createElement("div", { style: styles.empty }, "Send a message to start chatting"),
34
+ messages.map((m) =>
35
+ React.createElement("div", { key: m.id, style: { ...styles.message, ...(m.role === "user" ? styles.userMessage : styles.assistantMessage) } },
36
+ React.createElement("div", { style: styles.messageRole }, m.role === "user" ? "You" : "AI"),
37
+ React.createElement("div", { style: styles.messageContent }, m.content || (m.parts?.map(p => p.text).join("") || "")),
38
+ )
39
+ ),
40
+ React.createElement("div", { ref: messagesEndRef }),
41
+ ),
42
+ React.createElement("form", { onSubmit: handleSubmit, style: styles.form },
43
+ React.createElement("input", {
44
+ value: input,
45
+ onChange: handleInputChange,
46
+ placeholder: "Type a message...",
47
+ style: styles.input,
48
+ disabled: isLoading,
49
+ }),
50
+ React.createElement("button", { type: "submit", style: styles.button, disabled: isLoading || !input.trim() },
51
+ isLoading ? "..." : "Send"
52
+ ),
53
+ ),
54
+ );
55
+ }
56
+
57
+ const styles = {
58
+ container: { display: "flex", flexDirection: "column", height: "100vh", maxWidth: "800px", margin: "0 auto" },
59
+ header: { padding: "16px 20px", borderBottom: "1px solid #262626" },
60
+ title: { fontSize: "18px", fontWeight: "600" },
61
+ messages: { flex: 1, overflow: "auto", padding: "20px", display: "flex", flexDirection: "column", gap: "16px" },
62
+ empty: { color: "#737373", textAlign: "center", marginTop: "40px" },
63
+ message: { padding: "12px 16px", borderRadius: "12px", maxWidth: "80%" },
64
+ userMessage: { alignSelf: "flex-end", background: "#2563eb", color: "white" },
65
+ assistantMessage: { alignSelf: "flex-start", background: "#1c1c1c", border: "1px solid #262626" },
66
+ messageRole: { fontSize: "11px", fontWeight: "600", marginBottom: "4px", opacity: 0.7, textTransform: "uppercase" },
67
+ messageContent: { fontSize: "14px", lineHeight: "1.5", whiteSpace: "pre-wrap" },
68
+ form: { padding: "16px 20px", borderTop: "1px solid #262626", display: "flex", gap: "8px" },
69
+ input: { flex: 1, padding: "10px 14px", borderRadius: "8px", border: "1px solid #333", background: "#1c1c1c", color: "#e5e5e5", fontSize: "14px", outline: "none" },
70
+ button: { padding: "10px 20px", borderRadius: "8px", border: "none", background: "#2563eb", color: "white", fontSize: "14px", fontWeight: "500", cursor: "pointer" },
71
+ };
72
+
73
+ createRoot(document.getElementById("root")).render(React.createElement(App));
@@ -5,205 +5,22 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>AI Chat</title>
7
7
  <style>
8
- * {
9
- box-sizing: border-box;
10
- margin: 0;
11
- padding: 0;
12
- }
13
-
14
- body {
15
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
- background: #f5f5f5;
17
- min-height: 100vh;
18
- display: flex;
19
- justify-content: center;
20
- padding: 1rem;
21
- }
22
-
23
- .chat-container {
24
- width: 100%;
25
- max-width: 700px;
26
- display: flex;
27
- flex-direction: column;
28
- height: calc(100vh - 2rem);
29
- }
30
-
31
- header {
32
- text-align: center;
33
- padding: 1rem;
34
- }
35
-
36
- header h1 {
37
- font-size: 1.5rem;
38
- color: #333;
39
- margin-bottom: 0.25rem;
40
- }
41
-
42
- header p {
43
- font-size: 0.875rem;
44
- color: #666;
45
- }
46
-
47
- .messages {
48
- flex: 1;
49
- overflow-y: auto;
50
- padding: 1rem;
51
- background: white;
52
- border-radius: 12px;
53
- margin-bottom: 1rem;
54
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
55
- }
56
-
57
- .message {
58
- padding: 0.75rem 1rem;
59
- margin-bottom: 0.75rem;
60
- border-radius: 12px;
61
- max-width: 85%;
62
- line-height: 1.5;
63
- word-wrap: break-word;
64
- }
65
-
66
- .message.user {
67
- background: #007bff;
68
- color: white;
69
- margin-left: auto;
70
- border-bottom-right-radius: 4px;
71
- }
72
-
73
- .message.assistant {
74
- background: #e9ecef;
75
- color: #333;
76
- margin-right: auto;
77
- border-bottom-left-radius: 4px;
78
- }
79
-
80
- .message.error {
81
- background: #fee;
82
- color: #c00;
83
- border: 1px solid #fcc;
84
- }
85
-
86
- .message.typing {
87
- color: #666;
88
- font-style: italic;
89
- }
90
-
91
- .input-area {
92
- display: flex;
93
- gap: 0.5rem;
94
- background: white;
95
- padding: 1rem;
96
- border-radius: 12px;
97
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
98
- }
99
-
100
- .input-area input {
101
- flex: 1;
102
- padding: 0.75rem 1rem;
103
- border: 1px solid #ddd;
104
- border-radius: 8px;
105
- font-size: 1rem;
106
- outline: none;
107
- transition: border-color 0.2s;
108
- }
109
-
110
- .input-area input:focus {
111
- border-color: #007bff;
112
- }
113
-
114
- .input-area input:disabled {
115
- background: #f5f5f5;
116
- cursor: not-allowed;
117
- }
118
-
119
- .input-area button {
120
- padding: 0.75rem 1.5rem;
121
- background: #007bff;
122
- color: white;
123
- border: none;
124
- border-radius: 8px;
125
- font-size: 1rem;
126
- cursor: pointer;
127
- transition: background 0.2s;
128
- }
129
-
130
- .input-area button:hover:not(:disabled) {
131
- background: #0056b3;
132
- }
133
-
134
- .input-area button:disabled {
135
- background: #ccc;
136
- cursor: not-allowed;
137
- }
138
-
139
- .empty-state {
140
- text-align: center;
141
- color: #999;
142
- padding: 3rem 1rem;
143
- }
144
-
145
- .empty-state p {
146
- font-size: 1.1rem;
147
- margin-bottom: 0.5rem;
148
- }
149
-
150
- .empty-state small {
151
- font-size: 0.875rem;
152
- }
153
-
154
- @media (max-width: 480px) {
155
- body {
156
- padding: 0.5rem;
157
- }
158
-
159
- .chat-container {
160
- height: calc(100vh - 1rem);
161
- }
162
-
163
- header h1 {
164
- font-size: 1.25rem;
165
- }
166
-
167
- .message {
168
- max-width: 90%;
169
- padding: 0.625rem 0.875rem;
170
- }
171
-
172
- .input-area {
173
- padding: 0.75rem;
174
- }
175
-
176
- .input-area button {
177
- padding: 0.75rem 1rem;
178
- }
179
- }
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a0a; color: #e5e5e5; height: 100vh; }
10
+ #root { height: 100%; }
180
11
  </style>
181
12
  </head>
182
13
  <body>
183
- <div class="chat-container">
184
- <header>
185
- <h1>AI Chat</h1>
186
- <p>Powered by Cloudflare AI (Mistral 7B)</p>
187
- </header>
188
-
189
- <div id="messages" class="messages">
190
- <div class="empty-state">
191
- <p>Start a conversation</p>
192
- <small>Type a message below to begin</small>
193
- </div>
194
- </div>
195
-
196
- <div class="input-area">
197
- <input
198
- type="text"
199
- id="input"
200
- placeholder="Type your message..."
201
- autocomplete="off"
202
- />
203
- <button id="send">Send</button>
204
- </div>
205
- </div>
206
-
207
- <script src="/chat.js"></script>
14
+ <div id="root"></div>
15
+ <script type="importmap">
16
+ {
17
+ "imports": {
18
+ "react": "https://esm.sh/react@19",
19
+ "react-dom/client": "https://esm.sh/react-dom@19/client",
20
+ "@ai-sdk/react": "https://esm.sh/@ai-sdk/react@1?external=react"
21
+ }
22
+ }
23
+ </script>
24
+ <script type="module" src="/app.js"></script>
208
25
  </body>
209
26
  </html>
@@ -0,0 +1,14 @@
1
+ CREATE TABLE IF NOT EXISTS chats (
2
+ id TEXT PRIMARY KEY,
3
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
4
+ );
5
+
6
+ CREATE TABLE IF NOT EXISTS messages (
7
+ id TEXT PRIMARY KEY,
8
+ chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
9
+ role TEXT NOT NULL,
10
+ content TEXT NOT NULL,
11
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
12
+ );
13
+
14
+ CREATE INDEX IF NOT EXISTS idx_messages_chat_id ON messages(chat_id);
@@ -1,49 +1,32 @@
1
+ import { Hono } from "hono";
2
+ import { streamText, convertToCoreMessages, type Message } from "ai";
3
+ import { createWorkersAI } from "workers-ai-provider";
1
4
  import { createJackAI } from "./jack-ai";
2
5
 
3
6
  interface Env {
4
- // Direct AI binding (for local dev with wrangler)
5
7
  AI?: Ai;
6
- // Jack proxy bindings (injected in jack cloud)
7
8
  __AI_PROXY?: Fetcher;
8
9
  __JACK_PROJECT_ID?: string;
9
10
  __JACK_ORG_ID?: string;
10
- // Assets binding
11
11
  ASSETS: Fetcher;
12
+ DB: D1Database;
12
13
  }
13
14
 
14
15
  function getAI(env: Env) {
15
- // Prefer jack cloud proxy if available (for metering)
16
16
  if (env.__AI_PROXY && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
17
17
  return createJackAI(
18
- env as Required<Pick<Env, "__AI_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">>,
18
+ env as Required<
19
+ Pick<Env, "__AI_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">
20
+ >,
19
21
  );
20
22
  }
21
- // Fallback to direct binding for local dev
22
- if (env.AI) {
23
- return env.AI;
24
- }
23
+ if (env.AI) return env.AI;
25
24
  throw new Error("No AI binding available");
26
25
  }
27
26
 
28
- interface ChatMessage {
29
- role: "user" | "assistant" | "system";
30
- content: string;
31
- }
32
-
33
- // System prompt - customize this to change the AI's personality
34
- const SYSTEM_PROMPT = `You are a helpful AI assistant built with jack (getjack.sh).
35
-
36
- jack helps developers ship ideas fast - from "what if" to a live URL in seconds. You're running on Cloudflare's edge network, close to users worldwide.
37
-
38
- Be concise, friendly, and helpful. If asked about jack:
39
- - jack new creates projects from templates
40
- - jack ship deploys to production
41
- - jack open opens your app in browser
42
- - Docs: https://docs.getjack.sh
27
+ const SYSTEM_PROMPT = `You are a helpful AI assistant. Be concise, friendly, and helpful. Keep responses short unless detail is needed.`;
43
28
 
44
- Focus on being useful. Keep responses short unless detail is needed.`;
45
-
46
- // Rate limiting: 10 requests per minute per IP
29
+ // Rate limiting
47
30
  const RATE_LIMIT = 10;
48
31
  const RATE_WINDOW_MS = 60_000;
49
32
  const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
@@ -51,91 +34,92 @@ const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
51
34
  function checkRateLimit(ip: string): boolean {
52
35
  const now = Date.now();
53
36
  const entry = rateLimitMap.get(ip);
54
-
55
37
  if (!entry || now >= entry.resetAt) {
56
38
  rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
57
39
  return true;
58
40
  }
59
-
60
- if (entry.count >= RATE_LIMIT) {
61
- return false;
62
- }
63
-
41
+ if (entry.count >= RATE_LIMIT) return false;
64
42
  entry.count++;
65
43
  return true;
66
44
  }
67
45
 
68
- // Clean up old entries periodically to prevent memory leaks
69
- function cleanupRateLimitMap(): void {
70
- const now = Date.now();
71
- for (const [ip, entry] of rateLimitMap) {
72
- if (now >= entry.resetAt) {
73
- rateLimitMap.delete(ip);
74
- }
46
+ const app = new Hono<{ Bindings: Env }>();
47
+
48
+ // Create new chat
49
+ app.post("/api/chat/new", async (c) => {
50
+ const id = crypto.randomUUID();
51
+ await c.env.DB.prepare("INSERT INTO chats (id) VALUES (?)").bind(id).run();
52
+ return c.json({ id });
53
+ });
54
+
55
+ // Load chat history
56
+ app.get("/api/chat/:id", async (c) => {
57
+ const chatId = c.req.param("id");
58
+ const { results } = await c.env.DB.prepare(
59
+ "SELECT id, role, content, created_at FROM messages WHERE chat_id = ? ORDER BY created_at ASC",
60
+ )
61
+ .bind(chatId)
62
+ .all();
63
+ return c.json({ messages: results || [] });
64
+ });
65
+
66
+ // Chat endpoint with streaming
67
+ app.post("/api/chat", async (c) => {
68
+ const ip = c.req.header("cf-connecting-ip") || "unknown";
69
+ if (!checkRateLimit(ip)) {
70
+ return c.json({ error: "Too many requests. Please wait a moment." }, 429);
75
71
  }
76
- }
77
72
 
78
- export default {
79
- async fetch(request: Request, env: Env): Promise<Response> {
80
- const url = new URL(request.url);
81
-
82
- // Serve static assets for non-API routes
83
- if (request.method === "GET" && !url.pathname.startsWith("/api")) {
84
- return env.ASSETS.fetch(request);
85
- }
86
-
87
- // POST /api/chat - Streaming chat endpoint
88
- if (request.method === "POST" && url.pathname === "/api/chat") {
89
- const ip = request.headers.get("cf-connecting-ip") || "unknown";
90
-
91
- // Check rate limit
92
- if (!checkRateLimit(ip)) {
93
- // Cleanup old entries occasionally
94
- cleanupRateLimitMap();
95
- return Response.json(
96
- { error: "Too many requests. Please wait a moment and try again." },
97
- { status: 429 },
98
- );
99
- }
73
+ const { messages, chatId } = await c.req.json<{
74
+ messages: Message[];
75
+ chatId?: string;
76
+ }>();
77
+ if (!messages || !Array.isArray(messages)) {
78
+ return c.json({ error: "Invalid request." }, 400);
79
+ }
100
80
 
101
- try {
102
- const body = (await request.json()) as { messages?: ChatMessage[] };
103
- let messages = body.messages;
104
-
105
- if (!messages || !Array.isArray(messages)) {
106
- return Response.json(
107
- { error: "Invalid request. Please provide a messages array." },
108
- { status: 400 },
109
- );
110
- }
111
-
112
- // Prepend system prompt if not already present
113
- if (messages.length === 0 || messages[0].role !== "system") {
114
- messages = [{ role: "system", content: SYSTEM_PROMPT }, ...messages];
115
- }
116
-
117
- // Stream response using Llama 3.2 1B - cheapest model with good quality
118
- // See: https://developers.cloudflare.com/workers-ai/models/
119
- const ai = getAI(env);
120
- const stream = await ai.run("@cf/meta/llama-3.2-1b-instruct", {
121
- messages,
122
- stream: true,
123
- max_tokens: 1024,
124
- });
125
-
126
- return new Response(stream, {
127
- headers: {
128
- "Content-Type": "text/event-stream",
129
- "Cache-Control": "no-cache",
130
- Connection: "keep-alive",
131
- },
132
- });
133
- } catch (err) {
134
- console.error("Chat error:", err);
135
- return Response.json({ error: "Something went wrong. Please try again." }, { status: 500 });
81
+ // Save user message to DB if we have a chatId
82
+ const lastUserMsg = messages.findLast((m: Message) => m.role === "user");
83
+ if (chatId && lastUserMsg) {
84
+ await c.env.DB.prepare(
85
+ "INSERT INTO messages (id, chat_id, role, content) VALUES (?, ?, ?, ?)",
86
+ )
87
+ .bind(
88
+ crypto.randomUUID(),
89
+ chatId,
90
+ "user",
91
+ typeof lastUserMsg.content === "string"
92
+ ? lastUserMsg.content
93
+ : JSON.stringify(lastUserMsg.content),
94
+ )
95
+ .run();
96
+ }
97
+
98
+ const ai = getAI(c.env);
99
+ const provider = createWorkersAI({ binding: ai as Ai });
100
+
101
+ const result = streamText({
102
+ model: provider("@cf/meta/llama-3.3-70b-instruct-fp8-fast"),
103
+ system: SYSTEM_PROMPT,
104
+ messages: convertToCoreMessages(messages),
105
+ onFinish: async ({ text }) => {
106
+ // Save assistant response to DB
107
+ if (chatId && text) {
108
+ await c.env.DB.prepare(
109
+ "INSERT INTO messages (id, chat_id, role, content) VALUES (?, ?, ?, ?)",
110
+ )
111
+ .bind(crypto.randomUUID(), chatId, "assistant", text)
112
+ .run();
136
113
  }
137
- }
114
+ },
115
+ });
116
+
117
+ return result.toDataStreamResponse();
118
+ });
119
+
120
+ // Serve static assets for non-API routes
121
+ app.get("*", async (c) => {
122
+ return c.env.ASSETS.fetch(c.req.raw);
123
+ });
138
124
 
139
- return Response.json({ error: "Not found" }, { status: 404 });
140
- },
141
- };
125
+ export default app;
@@ -2,11 +2,18 @@
2
2
  "name": "jack-template",
3
3
  "main": "src/index.ts",
4
4
  "compatibility_date": "2024-12-01",
5
+ "compatibility_flags": ["nodejs_compat"],
5
6
  "ai": {
6
7
  "binding": "AI"
7
8
  },
8
9
  "assets": {
9
10
  "directory": "public",
10
11
  "binding": "ASSETS"
11
- }
12
+ },
13
+ "d1_databases": [
14
+ {
15
+ "binding": "DB",
16
+ "database_name": "jack-template-db"
17
+ }
18
+ ]
12
19
  }
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "cron",
3
+ "description": "Background tasks with cron scheduling and webhook ingestion",
4
+ "secrets": [],
5
+ "capabilities": ["db"],
6
+ "requires": ["DB", "CRON"],
7
+ "intent": {
8
+ "keywords": [
9
+ "cron",
10
+ "background",
11
+ "jobs",
12
+ "webhook",
13
+ "scheduled",
14
+ "queue",
15
+ "worker",
16
+ "tasks"
17
+ ],
18
+ "examples": [
19
+ "background job processor",
20
+ "scheduled tasks",
21
+ "webhook handler",
22
+ "job queue"
23
+ ]
24
+ },
25
+ "agentContext": {
26
+ "summary": "A background task worker with cron scheduling, D1 job queue, and webhook ingestion.",
27
+ "full_text": "## Project Structure\n\n- `src/index.ts` - Hono API with cron handler, webhook endpoint, and job status routes\n- `src/jobs.ts` - Job queue: create, process, retry with D1 backend\n- `src/webhooks.ts` - Webhook ingestion with HMAC-SHA256 signature verification\n- `schema.sql` - D1 schema (jobs, webhook_events)\n\n## Cron\n\nThe `POST /__scheduled` route runs on a cron schedule (default: every 5 minutes). It processes pending jobs and retries failed ones.\n\n## Jobs\n\n```typescript\n// Create a job\nawait createJob(db, { type: 'process-order', payload: { orderId: '123' } });\n\n// Jobs are processed automatically by cron\n// Failed jobs retry up to 3 times with exponential backoff\n```\n\n## Webhooks\n\n```\nPOST /webhook\nX-Signature: sha256=abc123...\nContent-Type: application/json\n\n{ \"event\": \"order.completed\", \"data\": { ... } }\n```\n\nWebhooks are verified using HMAC-SHA256, logged to D1, and can create jobs for async processing.\n\n## Endpoints\n\n- `POST /__scheduled` - Cron handler (called by scheduler)\n- `POST /webhook` - Inbound webhook receiver with signature verification\n- `GET /jobs` - List recent jobs with status\n- `GET /health` - Health check\n\n## Environment Variables\n\n- `WEBHOOK_SECRET` - HMAC signing secret for webhook verification (auto-generated)\n\n## Resources\n\n- [Hono Documentation](https://hono.dev)"
28
+ },
29
+ "hooks": {
30
+ "preCreate": [
31
+ {
32
+ "action": "require",
33
+ "source": "secret",
34
+ "key": "WEBHOOK_SECRET",
35
+ "message": "Generating webhook signing secret...",
36
+ "onMissing": "generate",
37
+ "generateCommand": "openssl rand -hex 32"
38
+ }
39
+ ],
40
+ "postDeploy": [
41
+ {
42
+ "action": "clipboard",
43
+ "text": "{{url}}",
44
+ "message": "Deploy URL copied to clipboard"
45
+ },
46
+ {
47
+ "action": "shell",
48
+ "command": "curl -s {{url}}/health | head -c 200",
49
+ "message": "Testing worker health..."
50
+ },
51
+ {
52
+ "action": "box",
53
+ "title": "Background worker live: {{name}}",
54
+ "lines": [
55
+ "{{url}}",
56
+ "",
57
+ "Endpoints:",
58
+ " POST {{url}}/webhook — inbound webhook receiver",
59
+ " GET {{url}}/jobs — job queue status",
60
+ "",
61
+ "Cron: runs every 5 minutes"
62
+ ]
63
+ }
64
+ ]
65
+ }
66
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "jack-template",
7
+ "dependencies": {
8
+ "hono": "^4.6.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.20260212.0", "", {}, "sha512-ZK+e8T/2tWBCrE8PoAi9oqTxcBen9Apq+dxbsy1R5LFVdB6M4pY+oP49OFuHTTezrvNXbyvmzbf/vjtrCPGdNg=="],
18
+
19
+ "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="],
20
+
21
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
22
+ }
23
+ }
@@ -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
+ "hono": "^4.6.0"
11
+ },
12
+ "devDependencies": {
13
+ "@cloudflare/workers-types": "^4.20241205.0",
14
+ "typescript": "^5.0.0"
15
+ }
16
+ }