@getjack/jack 0.1.32 → 0.1.33

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 (193) 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/services.ts +11 -1
  7. package/src/commands/ship.ts +3 -1
  8. package/src/commands/tokens.ts +16 -1
  9. package/src/commands/whoami.ts +43 -8
  10. package/src/index.ts +16 -0
  11. package/src/lib/agent-files.ts +54 -4
  12. package/src/lib/agent-integration.ts +4 -166
  13. package/src/lib/claude-hooks-installer.ts +55 -0
  14. package/src/lib/control-plane.ts +78 -40
  15. package/src/lib/debug.ts +2 -1
  16. package/src/lib/deploy-upload.ts +6 -0
  17. package/src/lib/hooks.ts +3 -1
  18. package/src/lib/managed-deploy.ts +12 -9
  19. package/src/lib/project-link.ts +6 -0
  20. package/src/lib/project-operations.ts +68 -22
  21. package/src/lib/telemetry.ts +2 -0
  22. package/src/mcp/README.md +1 -1
  23. package/src/mcp/resources/index.ts +1 -16
  24. package/src/mcp/server.ts +23 -0
  25. package/src/mcp/tools/index.ts +133 -17
  26. package/src/mcp/types.ts +1 -0
  27. package/src/mcp/utils.ts +2 -1
  28. package/src/templates/index.ts +25 -73
  29. package/templates/CLAUDE.md +41 -0
  30. package/templates/ai-chat/.jack.json +10 -5
  31. package/templates/ai-chat/bun.lock +50 -1
  32. package/templates/ai-chat/package.json +5 -0
  33. package/templates/ai-chat/public/app.js +73 -0
  34. package/templates/ai-chat/public/index.html +14 -197
  35. package/templates/ai-chat/schema.sql +14 -0
  36. package/templates/ai-chat/src/index.ts +86 -102
  37. package/templates/ai-chat/wrangler.jsonc +8 -1
  38. package/templates/cron/.jack.json +66 -0
  39. package/templates/cron/bun.lock +23 -0
  40. package/templates/cron/package.json +16 -0
  41. package/templates/cron/schema.sql +24 -0
  42. package/templates/cron/src/index.ts +117 -0
  43. package/templates/cron/src/jobs.ts +139 -0
  44. package/templates/cron/src/webhooks.ts +95 -0
  45. package/templates/cron/tsconfig.json +17 -0
  46. package/templates/cron/wrangler.jsonc +11 -0
  47. package/templates/miniapp/.jack.json +1 -1
  48. package/templates/nextjs/.jack.json +1 -1
  49. package/templates/nextjs-auth/.jack.json +44 -0
  50. package/templates/nextjs-auth/app/api/auth/[...all]/route.ts +11 -0
  51. package/templates/nextjs-auth/app/dashboard/loading.tsx +53 -0
  52. package/templates/nextjs-auth/app/dashboard/page.tsx +73 -0
  53. package/templates/nextjs-auth/app/error.tsx +44 -0
  54. package/templates/nextjs-auth/app/globals.css +1 -0
  55. package/templates/nextjs-auth/app/health/route.ts +3 -0
  56. package/templates/nextjs-auth/app/layout.tsx +24 -0
  57. package/templates/nextjs-auth/app/login/page.tsx +10 -0
  58. package/templates/nextjs-auth/app/page.tsx +86 -0
  59. package/templates/nextjs-auth/app/signup/page.tsx +10 -0
  60. package/templates/nextjs-auth/bun.lock +1065 -0
  61. package/templates/nextjs-auth/cloudflare-env.d.ts +8 -0
  62. package/templates/nextjs-auth/components/auth-form.tsx +191 -0
  63. package/templates/nextjs-auth/components/header.tsx +50 -0
  64. package/templates/nextjs-auth/components/user-menu.tsx +23 -0
  65. package/templates/nextjs-auth/lib/auth-client.ts +3 -0
  66. package/templates/nextjs-auth/lib/auth.ts +43 -0
  67. package/templates/nextjs-auth/lib/utils.ts +6 -0
  68. package/templates/nextjs-auth/middleware.ts +33 -0
  69. package/templates/nextjs-auth/next.config.ts +8 -0
  70. package/templates/nextjs-auth/open-next.config.ts +6 -0
  71. package/templates/nextjs-auth/package.json +33 -0
  72. package/templates/nextjs-auth/postcss.config.mjs +8 -0
  73. package/templates/nextjs-auth/schema.sql +49 -0
  74. package/templates/nextjs-auth/tsconfig.json +28 -0
  75. package/templates/nextjs-auth/wrangler.jsonc +23 -0
  76. package/templates/nextjs-clerk/.jack.json +54 -0
  77. package/templates/nextjs-clerk/app/dashboard/page.tsx +69 -0
  78. package/templates/nextjs-clerk/app/globals.css +1 -0
  79. package/templates/nextjs-clerk/app/health/route.ts +3 -0
  80. package/templates/nextjs-clerk/app/layout.tsx +26 -0
  81. package/templates/nextjs-clerk/app/page.tsx +86 -0
  82. package/templates/nextjs-clerk/app/sign-in/[[...sign-in]]/page.tsx +9 -0
  83. package/templates/nextjs-clerk/app/sign-up/[[...sign-up]]/page.tsx +9 -0
  84. package/templates/nextjs-clerk/bun.lock +1055 -0
  85. package/templates/nextjs-clerk/cloudflare-env.d.ts +3 -0
  86. package/templates/nextjs-clerk/components/header.tsx +40 -0
  87. package/templates/nextjs-clerk/lib/utils.ts +6 -0
  88. package/templates/nextjs-clerk/middleware.ts +18 -0
  89. package/templates/nextjs-clerk/next.config.ts +8 -0
  90. package/templates/nextjs-clerk/open-next.config.ts +6 -0
  91. package/templates/nextjs-clerk/package.json +31 -0
  92. package/templates/nextjs-clerk/postcss.config.mjs +8 -0
  93. package/templates/nextjs-clerk/tsconfig.json +28 -0
  94. package/templates/nextjs-clerk/wrangler.jsonc +17 -0
  95. package/templates/nextjs-shadcn/.jack.json +34 -0
  96. package/templates/nextjs-shadcn/app/dashboard/data.json +614 -0
  97. package/templates/nextjs-shadcn/app/dashboard/page.tsx +55 -0
  98. package/templates/nextjs-shadcn/app/globals.css +126 -0
  99. package/templates/nextjs-shadcn/app/health/route.ts +3 -0
  100. package/templates/nextjs-shadcn/app/layout.tsx +24 -0
  101. package/templates/nextjs-shadcn/app/login/page.tsx +19 -0
  102. package/templates/nextjs-shadcn/app/page.tsx +180 -0
  103. package/templates/nextjs-shadcn/app/showcase.tsx +1262 -0
  104. package/templates/nextjs-shadcn/bun.lock +1789 -0
  105. package/templates/nextjs-shadcn/cloudflare-env.d.ts +4 -0
  106. package/templates/nextjs-shadcn/components/app-sidebar.tsx +175 -0
  107. package/templates/nextjs-shadcn/components/chart-area-interactive.tsx +291 -0
  108. package/templates/nextjs-shadcn/components/data-table.tsx +807 -0
  109. package/templates/nextjs-shadcn/components/login-form.tsx +95 -0
  110. package/templates/nextjs-shadcn/components/nav-documents.tsx +92 -0
  111. package/templates/nextjs-shadcn/components/nav-main.tsx +73 -0
  112. package/templates/nextjs-shadcn/components/nav-projects.tsx +89 -0
  113. package/templates/nextjs-shadcn/components/nav-secondary.tsx +42 -0
  114. package/templates/nextjs-shadcn/components/nav-user.tsx +114 -0
  115. package/templates/nextjs-shadcn/components/section-cards.tsx +102 -0
  116. package/templates/nextjs-shadcn/components/site-header.tsx +30 -0
  117. package/templates/nextjs-shadcn/components/team-switcher.tsx +91 -0
  118. package/templates/nextjs-shadcn/components/ui/accordion.tsx +66 -0
  119. package/templates/nextjs-shadcn/components/ui/alert-dialog.tsx +196 -0
  120. package/templates/nextjs-shadcn/components/ui/alert.tsx +66 -0
  121. package/templates/nextjs-shadcn/components/ui/aspect-ratio.tsx +11 -0
  122. package/templates/nextjs-shadcn/components/ui/avatar.tsx +109 -0
  123. package/templates/nextjs-shadcn/components/ui/badge.tsx +48 -0
  124. package/templates/nextjs-shadcn/components/ui/breadcrumb.tsx +109 -0
  125. package/templates/nextjs-shadcn/components/ui/button-group.tsx +83 -0
  126. package/templates/nextjs-shadcn/components/ui/button.tsx +64 -0
  127. package/templates/nextjs-shadcn/components/ui/calendar.tsx +220 -0
  128. package/templates/nextjs-shadcn/components/ui/card.tsx +92 -0
  129. package/templates/nextjs-shadcn/components/ui/carousel.tsx +241 -0
  130. package/templates/nextjs-shadcn/components/ui/chart.tsx +357 -0
  131. package/templates/nextjs-shadcn/components/ui/checkbox.tsx +32 -0
  132. package/templates/nextjs-shadcn/components/ui/collapsible.tsx +33 -0
  133. package/templates/nextjs-shadcn/components/ui/combobox.tsx +310 -0
  134. package/templates/nextjs-shadcn/components/ui/command.tsx +184 -0
  135. package/templates/nextjs-shadcn/components/ui/context-menu.tsx +252 -0
  136. package/templates/nextjs-shadcn/components/ui/dialog.tsx +158 -0
  137. package/templates/nextjs-shadcn/components/ui/direction.tsx +22 -0
  138. package/templates/nextjs-shadcn/components/ui/drawer.tsx +135 -0
  139. package/templates/nextjs-shadcn/components/ui/dropdown-menu.tsx +257 -0
  140. package/templates/nextjs-shadcn/components/ui/empty.tsx +104 -0
  141. package/templates/nextjs-shadcn/components/ui/field.tsx +248 -0
  142. package/templates/nextjs-shadcn/components/ui/form.tsx +167 -0
  143. package/templates/nextjs-shadcn/components/ui/hover-card.tsx +44 -0
  144. package/templates/nextjs-shadcn/components/ui/input-group.tsx +170 -0
  145. package/templates/nextjs-shadcn/components/ui/input-otp.tsx +77 -0
  146. package/templates/nextjs-shadcn/components/ui/input.tsx +21 -0
  147. package/templates/nextjs-shadcn/components/ui/item.tsx +193 -0
  148. package/templates/nextjs-shadcn/components/ui/kbd.tsx +28 -0
  149. package/templates/nextjs-shadcn/components/ui/label.tsx +24 -0
  150. package/templates/nextjs-shadcn/components/ui/menubar.tsx +276 -0
  151. package/templates/nextjs-shadcn/components/ui/native-select.tsx +53 -0
  152. package/templates/nextjs-shadcn/components/ui/navigation-menu.tsx +168 -0
  153. package/templates/nextjs-shadcn/components/ui/pagination.tsx +127 -0
  154. package/templates/nextjs-shadcn/components/ui/popover.tsx +89 -0
  155. package/templates/nextjs-shadcn/components/ui/progress.tsx +31 -0
  156. package/templates/nextjs-shadcn/components/ui/radio-group.tsx +45 -0
  157. package/templates/nextjs-shadcn/components/ui/resizable.tsx +53 -0
  158. package/templates/nextjs-shadcn/components/ui/scroll-area.tsx +58 -0
  159. package/templates/nextjs-shadcn/components/ui/select.tsx +190 -0
  160. package/templates/nextjs-shadcn/components/ui/separator.tsx +28 -0
  161. package/templates/nextjs-shadcn/components/ui/sheet.tsx +143 -0
  162. package/templates/nextjs-shadcn/components/ui/sidebar.tsx +726 -0
  163. package/templates/nextjs-shadcn/components/ui/skeleton.tsx +13 -0
  164. package/templates/nextjs-shadcn/components/ui/slider.tsx +63 -0
  165. package/templates/nextjs-shadcn/components/ui/sonner.tsx +40 -0
  166. package/templates/nextjs-shadcn/components/ui/spinner.tsx +16 -0
  167. package/templates/nextjs-shadcn/components/ui/switch.tsx +35 -0
  168. package/templates/nextjs-shadcn/components/ui/table.tsx +116 -0
  169. package/templates/nextjs-shadcn/components/ui/tabs.tsx +91 -0
  170. package/templates/nextjs-shadcn/components/ui/textarea.tsx +18 -0
  171. package/templates/nextjs-shadcn/components/ui/toggle-group.tsx +83 -0
  172. package/templates/nextjs-shadcn/components/ui/toggle.tsx +47 -0
  173. package/templates/nextjs-shadcn/components/ui/tooltip.tsx +57 -0
  174. package/templates/nextjs-shadcn/components.json +23 -0
  175. package/templates/nextjs-shadcn/hooks/use-mobile.ts +19 -0
  176. package/templates/nextjs-shadcn/lib/utils.ts +6 -0
  177. package/templates/nextjs-shadcn/next-env.d.ts +6 -0
  178. package/templates/nextjs-shadcn/next.config.ts +8 -0
  179. package/templates/nextjs-shadcn/open-next.config.ts +6 -0
  180. package/templates/nextjs-shadcn/package.json +55 -0
  181. package/templates/nextjs-shadcn/postcss.config.mjs +8 -0
  182. package/templates/nextjs-shadcn/tsconfig.json +28 -0
  183. package/templates/nextjs-shadcn/wrangler.jsonc +23 -0
  184. package/templates/resend/.jack.json +64 -0
  185. package/templates/resend/bun.lock +23 -0
  186. package/templates/resend/package.json +16 -0
  187. package/templates/resend/schema.sql +13 -0
  188. package/templates/resend/src/email.ts +165 -0
  189. package/templates/resend/src/index.ts +108 -0
  190. package/templates/resend/tsconfig.json +17 -0
  191. package/templates/resend/wrangler.jsonc +11 -0
  192. package/templates/saas/.jack.json +1 -1
  193. package/templates/ai-chat/public/chat.js +0 -149
@@ -1,165 +1,18 @@
1
1
  /**
2
2
  * Agent integration module
3
3
  *
4
- * Ensures AI agents have proper context for jack projects.
4
+ * Ensures AI agents have MCP configured for jack projects.
5
5
  * Called during both project creation and first BYO deploy.
6
6
  */
7
7
 
8
- import { existsSync } from "node:fs";
9
- import { join } from "node:path";
10
- import type { Template } from "../templates/types.ts";
11
8
  import { installMcpConfigsToAllApps, isAppInstalled } from "./mcp-config.ts";
12
9
 
13
10
  export interface EnsureAgentResult {
14
11
  mcpInstalled: string[];
15
- jackMdCreated: boolean;
16
- referencesAdded: string[];
17
12
  }
18
13
 
19
14
  export interface EnsureAgentOptions {
20
- template?: Template;
21
15
  silent?: boolean;
22
- projectName?: string;
23
- }
24
-
25
- /**
26
- * Generate JACK.md content
27
- * Uses template agentContext if available, otherwise generic jack instructions
28
- */
29
- export function generateJackMd(projectName?: string, template?: Template): string {
30
- const header = projectName ? `# ${projectName}\n\n` : "# Jack\n\n";
31
-
32
- const templateSummary = template?.agentContext?.summary;
33
- const templateFullText = template?.agentContext?.full_text;
34
-
35
- const summarySection = templateSummary ? `> ${templateSummary}\n\n` : "";
36
-
37
- const templateSection = templateFullText ? `${templateFullText}\n\n` : "";
38
-
39
- return `${header}${summarySection}This project is deployed and managed via jack.
40
-
41
- ## Quick Commands
42
-
43
- | Command | What it does |
44
- |---------|--------------|
45
- | \`jack ship\` | Deploy to production |
46
- | \`jack logs\` | Stream live logs |
47
- | \`jack services\` | Manage databases, KV, and other bindings |
48
- | \`jack secrets\` | Manage environment secrets |
49
-
50
- ## Important
51
-
52
- - **Never run \`wrangler\` commands directly** - jack handles all infrastructure
53
- - Use \`jack services db\` to create and query databases
54
- - Secrets sync automatically across deploys
55
-
56
- ## Services & Bindings
57
-
58
- Jack manages your project's services. To add a database:
59
-
60
- \`\`\`bash
61
- jack services db create
62
- \`\`\`
63
-
64
- To query it:
65
-
66
- \`\`\`bash
67
- jack services db query "SELECT * FROM users"
68
- \`\`\`
69
-
70
- More bindings (KV, R2, queues) coming soon.
71
-
72
- ${templateSection}## For AI Agents
73
-
74
- ### MCP Tools
75
-
76
- If jack MCP is connected, prefer these tools over CLI commands:
77
-
78
- | Tool | Use for |
79
- |------|---------|
80
- | \`mcp__jack__deploy_project\` | Deploy changes |
81
- | \`mcp__jack__create_database\` | Create a new database |
82
- | \`mcp__jack__execute_sql\` | Query the database |
83
- | \`mcp__jack__list_projects\` | List all projects |
84
- | \`mcp__jack__get_project_status\` | Check deployment status |
85
-
86
- ### Documentation
87
-
88
- Full jack documentation: https://docs.getjack.org/llms-full.txt
89
- `;
90
- }
91
-
92
- /**
93
- * Create JACK.md if it doesn't exist
94
- */
95
- async function ensureJackMd(
96
- projectPath: string,
97
- projectName?: string,
98
- template?: Template,
99
- ): Promise<boolean> {
100
- const jackMdPath = join(projectPath, "JACK.md");
101
-
102
- if (existsSync(jackMdPath)) {
103
- return false;
104
- }
105
-
106
- const content = generateJackMd(projectName, template);
107
- await Bun.write(jackMdPath, content);
108
- return true;
109
- }
110
-
111
- /**
112
- * Append JACK.md reference to existing agent files (CLAUDE.md, AGENTS.md)
113
- */
114
- async function appendJackMdReferences(projectPath: string): Promise<string[]> {
115
- const filesToCheck = ["CLAUDE.md", "AGENTS.md"];
116
- const referencesAdded: string[] = [];
117
- const jackMdPath = join(projectPath, "JACK.md");
118
-
119
- // Only add references if JACK.md exists
120
- if (!existsSync(jackMdPath)) {
121
- return referencesAdded;
122
- }
123
-
124
- const referenceBlock = `<!-- Added by jack -->
125
- > **Jack project** - See [JACK.md](./JACK.md) for deployment, services, and bindings.
126
-
127
- `;
128
-
129
- for (const filename of filesToCheck) {
130
- const filePath = join(projectPath, filename);
131
-
132
- if (!existsSync(filePath)) {
133
- continue;
134
- }
135
-
136
- try {
137
- const content = await Bun.file(filePath).text();
138
-
139
- // Skip if reference already exists
140
- if (content.includes("JACK.md") || content.includes("<!-- Added by jack -->")) {
141
- continue;
142
- }
143
-
144
- // Find position after first heading, or prepend if no heading
145
- const headingMatch = content.match(/^#[^\n]*\n/m);
146
- let newContent: string;
147
-
148
- if (headingMatch && headingMatch.index !== undefined) {
149
- const insertPos = headingMatch.index + headingMatch[0].length;
150
- newContent = content.slice(0, insertPos) + "\n" + referenceBlock + content.slice(insertPos);
151
- } else {
152
- newContent = referenceBlock + content;
153
- }
154
-
155
- await Bun.write(filePath, newContent);
156
- referencesAdded.push(filename);
157
- } catch {
158
- // Ignore errors reading/writing individual files
159
- }
160
- }
161
-
162
- return referencesAdded;
163
16
  }
164
17
 
165
18
  /**
@@ -186,31 +39,16 @@ async function ensureMcpConfigured(): Promise<string[]> {
186
39
  /**
187
40
  * Ensure agent integration is set up for a project
188
41
  *
189
- * This function:
190
- * 1. Creates JACK.md if not exists (with template context if available)
191
- * 2. Appends JACK.md reference to existing CLAUDE.md/AGENTS.md
192
- * 3. Installs MCP config to detected AI apps
193
- *
42
+ * Installs MCP config to detected AI apps.
194
43
  * Safe to call multiple times - all operations are idempotent.
195
44
  */
196
45
  export async function ensureAgentIntegration(
197
- projectPath: string,
198
- options: EnsureAgentOptions = {},
46
+ _projectPath: string,
47
+ _options: EnsureAgentOptions = {},
199
48
  ): Promise<EnsureAgentResult> {
200
- const { template, projectName } = options;
201
-
202
- // 1. Create JACK.md if not exists
203
- const jackMdCreated = await ensureJackMd(projectPath, projectName, template);
204
-
205
- // 2. Append references to existing agent files
206
- const referencesAdded = await appendJackMdReferences(projectPath);
207
-
208
- // 3. Ensure MCP is configured
209
49
  const mcpInstalled = await ensureMcpConfigured();
210
50
 
211
51
  return {
212
52
  mcpInstalled,
213
- jackMdCreated,
214
- referencesAdded,
215
53
  };
216
54
  }
@@ -0,0 +1,55 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+
5
+ const HOOK_COMMAND = "jack mcp context 2>/dev/null || true";
6
+
7
+ /**
8
+ * Install a Claude Code SessionStart hook to the project's .claude/settings.json.
9
+ * Only fires when Claude Code is opened in this project directory.
10
+ * Non-destructive: preserves existing hooks and deduplicates.
11
+ */
12
+ export async function installClaudeCodeHooks(projectPath: string): Promise<boolean> {
13
+ try {
14
+ const claudeDir = join(projectPath, ".claude");
15
+ const settingsPath = join(claudeDir, "settings.json");
16
+
17
+ // Ensure .claude directory exists
18
+ if (!existsSync(claudeDir)) {
19
+ await mkdir(claudeDir, { recursive: true });
20
+ }
21
+
22
+ let settings: Record<string, unknown> = {};
23
+ if (existsSync(settingsPath)) {
24
+ try {
25
+ settings = await Bun.file(settingsPath).json();
26
+ } catch {
27
+ settings = {};
28
+ }
29
+ }
30
+
31
+ const hooks = (settings.hooks as Record<string, unknown[]>) ?? {};
32
+ const sessionStart = (hooks.SessionStart as Array<Record<string, unknown>>) ?? [];
33
+
34
+ // Check if jack hook is already installed
35
+ for (const entry of sessionStart) {
36
+ const entryHooks = entry.hooks as Array<Record<string, string>> | undefined;
37
+ if (entryHooks?.some((h) => h.command?.includes("jack mcp context"))) {
38
+ return true;
39
+ }
40
+ }
41
+
42
+ sessionStart.push({
43
+ matcher: "",
44
+ hooks: [{ type: "command", command: HOOK_COMMAND }],
45
+ });
46
+
47
+ hooks.SessionStart = sessionStart;
48
+ settings.hooks = hooks;
49
+
50
+ await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { debug } from "./debug.ts";
6
- import { formatSize } from "./format.ts";
7
6
 
8
7
  const DEFAULT_CONTROL_API_URL = "https://control.getjack.org";
9
8
 
@@ -16,12 +15,14 @@ export interface CreateProjectRequest {
16
15
  slug?: string;
17
16
  template?: string;
18
17
  use_prebuilt?: boolean;
18
+ forked_from?: string;
19
19
  }
20
20
 
21
21
  export interface CreateManagedProjectOptions {
22
22
  slug?: string;
23
23
  template?: string;
24
24
  usePrebuilt?: boolean;
25
+ forkedFrom?: string;
25
26
  }
26
27
 
27
28
  export interface CreateProjectResponse {
@@ -111,6 +112,9 @@ export async function createManagedProject(
111
112
  if (options?.usePrebuilt !== undefined) {
112
113
  requestBody.use_prebuilt = options.usePrebuilt;
113
114
  }
115
+ if (options?.forkedFrom) {
116
+ requestBody.forked_from = options.forkedFrom;
117
+ }
114
118
 
115
119
  debug("Creating managed project", {
116
120
  name,
@@ -451,6 +455,79 @@ export async function fetchProjectResources(projectId: string): Promise<ProjectR
451
455
  return data.resources;
452
456
  }
453
457
 
458
+ export interface DeploymentInfo {
459
+ id: string;
460
+ status: "queued" | "building" | "live" | "failed";
461
+ source: string;
462
+ error_message: string | null;
463
+ message: string | null;
464
+ created_at: string;
465
+ updated_at: string;
466
+ }
467
+
468
+ export interface DeploymentListResult {
469
+ deployments: DeploymentInfo[];
470
+ total: number;
471
+ }
472
+
473
+ /**
474
+ * Fetch recent deployments for a managed project.
475
+ * Returns deployments (up to 10) and total count.
476
+ */
477
+ export async function fetchDeployments(projectId: string): Promise<DeploymentListResult> {
478
+ const { authFetch } = await import("./auth/index.ts");
479
+
480
+ const response = await authFetch(`${getControlApiUrl()}/v1/projects/${projectId}/deployments`);
481
+
482
+ if (!response.ok) {
483
+ return { deployments: [], total: 0 }; // Silent fail — deployment history is supplementary
484
+ }
485
+
486
+ const data = (await response.json()) as { deployments: DeploymentInfo[]; total: number };
487
+ return { deployments: data.deployments, total: data.total };
488
+ }
489
+
490
+ export interface RollbackResponse {
491
+ deployment: {
492
+ id: string;
493
+ status: string;
494
+ source: string;
495
+ created_at: string;
496
+ updated_at: string;
497
+ };
498
+ }
499
+
500
+ /**
501
+ * Rollback a managed project to a previous deployment.
502
+ * If no deploymentId given, rolls back to the previous successful deployment.
503
+ */
504
+ export async function rollbackDeployment(
505
+ projectId: string,
506
+ deploymentId?: string,
507
+ ): Promise<RollbackResponse> {
508
+ const { authFetch } = await import("./auth/index.ts");
509
+
510
+ const body: Record<string, string> = {};
511
+ if (deploymentId) {
512
+ body.deployment_id = deploymentId;
513
+ }
514
+
515
+ const response = await authFetch(`${getControlApiUrl()}/v1/projects/${projectId}/rollback`, {
516
+ method: "POST",
517
+ headers: { "Content-Type": "application/json" },
518
+ body: JSON.stringify(body),
519
+ });
520
+
521
+ if (!response.ok) {
522
+ const err = (await response.json().catch(() => ({ message: "Unknown error" }))) as {
523
+ message?: string;
524
+ };
525
+ throw new Error(err.message || `Rollback failed: ${response.status}`);
526
+ }
527
+
528
+ return response.json() as Promise<RollbackResponse>;
529
+ }
530
+
454
531
  /**
455
532
  * Create a resource for a managed project.
456
533
  * Uses POST /v1/projects/:id/resources/:type endpoint.
@@ -715,45 +792,6 @@ export async function getCurrentUserProfile(): Promise<UserProfile | null> {
715
792
  }
716
793
  }
717
794
 
718
- export interface SourceSnapshotResponse {
719
- success: boolean;
720
- source_key: string;
721
- }
722
-
723
- /**
724
- * Upload a source snapshot for a project.
725
- * Used to enable project forking.
726
- */
727
- export async function uploadSourceSnapshot(
728
- projectId: string,
729
- sourceZipPath: string,
730
- ): Promise<SourceSnapshotResponse> {
731
- const { authFetch } = await import("./auth/index.ts");
732
-
733
- const formData = new FormData();
734
- const sourceFile = Bun.file(sourceZipPath);
735
- formData.append("source", sourceFile);
736
-
737
- const url = `${getControlApiUrl()}/v1/projects/${projectId}/source`;
738
- debug(`Source snapshot: ${formatSize(sourceFile.size)}`);
739
-
740
- const start = Date.now();
741
- const response = await authFetch(url, {
742
- method: "POST",
743
- body: formData,
744
- });
745
- debug(`Source snapshot: ${response.status} (${((Date.now() - start) / 1000).toFixed(1)}s)`);
746
-
747
- if (!response.ok) {
748
- const error = (await response.json().catch(() => ({ message: "Upload failed" }))) as {
749
- message?: string;
750
- };
751
- throw new Error(error.message || `Source upload failed: ${response.status}`);
752
- }
753
-
754
- return response.json() as Promise<SourceSnapshotResponse>;
755
- }
756
-
757
795
  /**
758
796
  * Publish a project to make it forkable by others.
759
797
  */
package/src/lib/debug.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Debug utilities for timing and verbose logging
2
2
 
3
- let debugEnabled = false;
3
+ let debugEnabled =
4
+ process.env.JACK_DEBUG === "1" || process.env.JACK_DEBUG === "true";
4
5
  const timers: Map<string, number> = new Map();
5
6
  const startTime = Date.now();
6
7
 
@@ -18,6 +18,7 @@ export interface DeployUploadOptions {
18
18
  secretsPath?: string;
19
19
  assetsZipPath?: string;
20
20
  assetManifest?: AssetManifest;
21
+ message?: string;
21
22
  }
22
23
 
23
24
  export interface DeployUploadResult {
@@ -25,6 +26,7 @@ export interface DeployUploadResult {
25
26
  project_id: string;
26
27
  status: "queued" | "building" | "live" | "failed";
27
28
  source: string;
29
+ error_message: string | null;
28
30
  created_at: string;
29
31
  }
30
32
 
@@ -91,6 +93,10 @@ export async function uploadDeployment(options: DeployUploadOptions): Promise<De
91
93
  totalSize += manifestJson.length;
92
94
  }
93
95
 
96
+ if (options.message) {
97
+ formData.append("message", options.message);
98
+ }
99
+
94
100
  const prepareMs = Date.now() - prepareStart;
95
101
  debug(`Payload ready: ${formatSize(totalSize)} (${prepareMs}ms)`);
96
102
 
package/src/lib/hooks.ts CHANGED
@@ -702,7 +702,9 @@ const actionHandlers: {
702
702
  const proc = Bun.spawn(["sh", "-c", command], {
703
703
  cwd,
704
704
  stdin: interactive ? "inherit" : "ignore",
705
- stdout: "inherit",
705
+ // In non-interactive mode (MCP), stdout must NOT inherit because it would
706
+ // write into the JSON-RPC stdio transport and corrupt the protocol.
707
+ stdout: interactive ? "inherit" : "pipe",
706
708
  stderr: "inherit",
707
709
  });
708
710
  await proc.exited;
@@ -7,7 +7,7 @@
7
7
  import { stat } from "node:fs/promises";
8
8
  import { validateBindings } from "./binding-validator.ts";
9
9
  import { buildProject, parseWranglerConfig } from "./build-helper.ts";
10
- import { createManagedProject, syncProjectTags, uploadSourceSnapshot } from "./control-plane.ts";
10
+ import { createManagedProject, syncProjectTags } from "./control-plane.ts";
11
11
  import { debug } from "./debug.ts";
12
12
  import { uploadDeployment } from "./deploy-upload.ts";
13
13
  import { JackError, JackErrorCode } from "./errors.ts";
@@ -31,6 +31,7 @@ export interface ManagedCreateResult {
31
31
  export interface ManagedCreateOptions {
32
32
  template?: string;
33
33
  usePrebuilt?: boolean;
34
+ forkedFrom?: string;
34
35
  }
35
36
 
36
37
  /**
@@ -50,6 +51,7 @@ export async function createManagedProjectRemote(
50
51
  const result = await createManagedProject(projectName, {
51
52
  template: options?.template,
52
53
  usePrebuilt: options?.usePrebuilt ?? true,
54
+ forkedFrom: options?.forkedFrom,
53
55
  });
54
56
 
55
57
  const runjackUrl = result.url || `https://${result.project.slug}.runjack.xyz`;
@@ -79,6 +81,7 @@ export interface ManagedCodeDeployOptions {
79
81
  projectId: string;
80
82
  projectPath: string;
81
83
  reporter?: OperationReporter;
84
+ message?: string;
82
85
  }
83
86
 
84
87
  /**
@@ -88,7 +91,7 @@ export interface ManagedCodeDeployOptions {
88
91
  */
89
92
  export async function deployCodeToManagedProject(
90
93
  options: ManagedCodeDeployOptions,
91
- ): Promise<{ deploymentId: string; status: string }> {
94
+ ): Promise<{ deploymentId: string; status: string; errorMessage: string | null }> {
92
95
  const { projectId, projectPath, reporter } = options;
93
96
 
94
97
  // Track deploy start
@@ -161,6 +164,7 @@ export async function deployCodeToManagedProject(
161
164
  secretsPath: pkg.secretsPath ?? undefined,
162
165
  assetsZipPath: pkg.assetsZipPath ?? undefined,
163
166
  assetManifest: pkg.assetManifest ?? undefined,
167
+ message: options.message,
164
168
  });
165
169
 
166
170
  uploadProgress.complete();
@@ -183,16 +187,13 @@ export async function deployCodeToManagedProject(
183
187
  })
184
188
  .catch(() => {});
185
189
 
186
- // Upload source snapshot for forking (non-fatal, but must await before cleanup)
187
- try {
188
- await uploadSourceSnapshot(projectId, pkg.sourceZipPath);
189
- } catch (err) {
190
- debug("Source snapshot upload failed:", err instanceof Error ? err.message : String(err));
191
- }
190
+ // Source snapshot for forking is now derived from deployment artifacts on the control plane.
191
+ // No separate upload needed — clone/fork reads from the latest live deployment's source.zip.
192
192
 
193
193
  return {
194
194
  deploymentId: result.id,
195
195
  status: result.status,
196
+ errorMessage: result.error_message,
196
197
  };
197
198
  } catch (error) {
198
199
  reporter?.stop();
@@ -218,10 +219,12 @@ export async function deployToManagedProject(
218
219
  projectId: string,
219
220
  projectPath: string,
220
221
  reporter?: OperationReporter,
221
- ): Promise<{ deploymentId: string; status: string }> {
222
+ message?: string,
223
+ ): Promise<{ deploymentId: string; status: string; errorMessage: string | null }> {
222
224
  return deployCodeToManagedProject({
223
225
  projectId,
224
226
  projectPath,
225
227
  reporter,
228
+ message,
226
229
  });
227
230
  }
@@ -116,6 +116,12 @@ export async function linkProject(
116
116
 
117
117
  // Auto-add .jack/ to .gitignore
118
118
  await ensureGitignored(projectDir);
119
+
120
+ // Install Claude Code SessionStart hook to project-level .claude/settings.json
121
+ // Non-blocking, fire-and-forget — never delays project linking
122
+ import("./claude-hooks-installer.ts")
123
+ .then(({ installClaudeCodeHooks }) => installClaudeCodeHooks(projectDir))
124
+ .catch(() => {});
119
125
  }
120
126
 
121
127
  /**