@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
@@ -102,6 +102,7 @@ export interface DeployOptions {
102
102
  managed?: boolean; // Force managed deploy mode
103
103
  byo?: boolean; // Force BYO deploy mode
104
104
  dryRun?: boolean; // Stop before actual deployment
105
+ message?: string; // Deploy message describing what changed
105
106
  }
106
107
 
107
108
  export interface DeployResult {
@@ -109,6 +110,9 @@ export interface DeployResult {
109
110
  projectName: string;
110
111
  deployOutput?: string;
111
112
  deployMode: DeployMode; // The deploy mode used
113
+ deploymentId?: string;
114
+ deployStatus?: string;
115
+ errorMessage?: string | null;
112
116
  }
113
117
 
114
118
  export interface ProjectStatus {
@@ -126,6 +130,12 @@ export interface ProjectStatus {
126
130
  missing: boolean;
127
131
  backupFiles: number | null;
128
132
  backupLastSync: string | null;
133
+ // Deploy tracking (managed projects only)
134
+ lastDeployAt: string | null;
135
+ deployCount: number;
136
+ lastDeployStatus: string | null;
137
+ lastDeploySource: string | null;
138
+ lastDeployMessage: string | null;
129
139
  }
130
140
 
131
141
  export interface StaleProject {
@@ -361,6 +371,7 @@ async function runParallelSetup(
361
371
  options: {
362
372
  template?: string;
363
373
  usePrebuilt?: boolean;
374
+ forkedFrom?: string;
364
375
  onRemoteReady?: (result: ManagedCreateResult) => void;
365
376
  },
366
377
  ): Promise<{
@@ -391,6 +402,7 @@ async function runParallelSetup(
391
402
  const remotePromise = createManagedProjectRemote(projectName, undefined, {
392
403
  template: options.template || "hello",
393
404
  usePrebuilt: options.usePrebuilt ?? true,
405
+ forkedFrom: options.forkedFrom,
394
406
  }).then((result) => {
395
407
  const duration = ((Date.now() - remoteStart) / 1000).toFixed(1);
396
408
  debug(`Remote project created in ${duration}s (status: ${result.status})`);
@@ -793,11 +805,25 @@ export async function createProject(
793
805
  }
794
806
  }
795
807
 
796
- // Check if jack init was run (throws if not)
808
+ // Auto-initialize on first run (replaces "Run: jack init" error)
797
809
  const { isInitialized } = await import("../commands/init.ts");
798
810
  const initialized = await isInitialized();
799
811
  if (!initialized) {
800
- throw new JackError(JackErrorCode.VALIDATION_ERROR, "jack is not set up yet", "Run: jack init");
812
+ const { writeConfig, readConfig } = await import("./config.ts");
813
+ const existingConfig = await readConfig();
814
+ await writeConfig({
815
+ version: 1,
816
+ initialized: true,
817
+ initializedAt: existingConfig?.initializedAt || new Date().toISOString(),
818
+ ...existingConfig,
819
+ });
820
+
821
+ // Best-effort MCP config install (non-blocking)
822
+ try {
823
+ const { installMcpConfigsToAllApps, saveMcpConfig } = await import("./mcp-config.ts");
824
+ await installMcpConfigsToAllApps();
825
+ await saveMcpConfig();
826
+ } catch {}
801
827
  }
802
828
 
803
829
  // Auth gate - check/prompt for authentication before any work
@@ -1199,9 +1225,11 @@ export async function createProject(
1199
1225
  reporter.start("Setting up project...");
1200
1226
 
1201
1227
  try {
1228
+ const forkedFrom = templateOrigin.type !== "builtin" ? templateOrigin.name : undefined;
1202
1229
  const result = await runParallelSetup(targetDir, projectName, {
1203
1230
  template: resolvedTemplate || "hello",
1204
1231
  usePrebuilt: templateOrigin.type === "builtin", // Only builtin templates have prebuilt bundles
1232
+ forkedFrom,
1205
1233
  onRemoteReady: (remote) => {
1206
1234
  // Show URL immediately when prebuilt succeeds
1207
1235
  reporter.stop();
@@ -1355,22 +1383,8 @@ export async function createProject(
1355
1383
  reporter.success(`Deployed: ${workerUrl}`);
1356
1384
  }
1357
1385
 
1358
- // Upload source snapshot for forking (prebuilt path needs this too)
1359
- try {
1360
- const { createSourceZip } = await import("./zip-packager.ts");
1361
- const { uploadSourceSnapshot } = await import("./control-plane.ts");
1362
- const { rm } = await import("node:fs/promises");
1363
-
1364
- const sourceZipPath = await createSourceZip(targetDir);
1365
- await uploadSourceSnapshot(remoteResult.projectId, sourceZipPath);
1366
- await rm(sourceZipPath, { force: true });
1367
- debug("Source snapshot uploaded for prebuilt project");
1368
- } catch (err) {
1369
- debug(
1370
- "Source snapshot upload failed (prebuilt):",
1371
- err instanceof Error ? err.message : String(err),
1372
- );
1373
- }
1386
+ // source.zip is now stored in deployment artifacts by the control plane
1387
+ // during prebuilt deploy — no client-side upload needed
1374
1388
  } else {
1375
1389
  // Prebuilt not available - fall back to fresh build
1376
1390
  if (remoteResult.prebuiltFailed) {
@@ -1688,11 +1702,10 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1688
1702
  deployMode = link?.deploy_mode ?? "byo";
1689
1703
  }
1690
1704
 
1691
- // Ensure agent integration is set up (JACK.md, MCP config)
1705
+ // Ensure agent integration is set up (MCP config)
1692
1706
  // This is idempotent and runs silently
1693
1707
  try {
1694
1708
  await ensureAgentIntegration(projectPath, {
1695
- projectName,
1696
1709
  silent: true,
1697
1710
  });
1698
1711
  } catch (err) {
@@ -1723,6 +1736,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1723
1736
 
1724
1737
  let workerUrl: string | null = null;
1725
1738
  let deployOutput: string | undefined;
1739
+ let managedDeployResult: { deploymentId: string; status: string; errorMessage: string | null } | undefined;
1726
1740
 
1727
1741
  // Deploy based on mode
1728
1742
  if (deployMode === "managed") {
@@ -1781,7 +1795,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1781
1795
  }
1782
1796
 
1783
1797
  // deployToManagedProject now handles both template and code deploy
1784
- await deployToManagedProject(managedProjectId as string, projectPath, reporter);
1798
+ managedDeployResult = await deployToManagedProject(managedProjectId as string, projectPath, reporter, options.message);
1785
1799
 
1786
1800
  // Construct URL with username if available
1787
1801
  workerUrl = link?.owner_username
@@ -1936,6 +1950,9 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1936
1950
  projectName,
1937
1951
  deployOutput: workerUrl ? undefined : deployOutput,
1938
1952
  deployMode,
1953
+ deploymentId: managedDeployResult?.deploymentId,
1954
+ deployStatus: managedDeployResult?.status,
1955
+ errorMessage: managedDeployResult?.errorMessage,
1939
1956
  };
1940
1957
  }
1941
1958
 
@@ -2025,11 +2042,35 @@ export async function getProjectStatus(
2025
2042
  }
2026
2043
  }
2027
2044
 
2045
+ // Fetch real deployment data for managed projects
2046
+ let lastDeployAt: string | null = null;
2047
+ let deployCount = 0;
2048
+ let lastDeployStatus: string | null = null;
2049
+ let lastDeploySource: string | null = null;
2050
+ let lastDeployMessage: string | null = null;
2051
+
2052
+ if (link?.deploy_mode === "managed") {
2053
+ try {
2054
+ const { fetchDeployments } = await import("./control-plane.ts");
2055
+ const result = await fetchDeployments(link.project_id);
2056
+ deployCount = result.total;
2057
+ const latest = result.deployments[0];
2058
+ if (latest) {
2059
+ lastDeployAt = latest.created_at;
2060
+ lastDeployStatus = latest.status;
2061
+ lastDeploySource = latest.source;
2062
+ lastDeployMessage = latest.message;
2063
+ }
2064
+ } catch {
2065
+ // Silent fail — deploy tracking is supplementary
2066
+ }
2067
+ }
2068
+
2028
2069
  return {
2029
2070
  name: projectName,
2030
2071
  localPath,
2031
2072
  workerUrl,
2032
- lastDeployed: link?.linked_at ?? null,
2073
+ lastDeployed: lastDeployAt ?? link?.linked_at ?? null,
2033
2074
  createdAt: link?.linked_at ?? null,
2034
2075
  accountId: null, // No longer stored in registry
2035
2076
  workerId: projectName,
@@ -2040,6 +2081,11 @@ export async function getProjectStatus(
2040
2081
  missing: false,
2041
2082
  backupFiles,
2042
2083
  backupLastSync,
2084
+ lastDeployAt,
2085
+ deployCount,
2086
+ lastDeployStatus,
2087
+ lastDeploySource,
2088
+ lastDeployMessage,
2043
2089
  };
2044
2090
  }
2045
2091
 
@@ -47,6 +47,8 @@ export const Events = {
47
47
  // Token management events
48
48
  TOKEN_CREATED: "token_created",
49
49
  TOKEN_REVOKED: "token_revoked",
50
+ // Hook events
51
+ HOOK_SESSION_CONTEXT: "hook_session_context",
50
52
  } as const;
51
53
 
52
54
  type EventName = (typeof Events)[keyof typeof Events];
package/src/mcp/README.md CHANGED
@@ -25,7 +25,7 @@ src/mcp/
25
25
  ### `tools/index.ts`
26
26
  - Centralized tool registration and dispatch
27
27
  - Implements 4 core tools:
28
- - `create_project` - Create new Cloudflare Workers project
28
+ - `create_project` - Create new project (supports forking via template)
29
29
  - `deploy_project` - Deploy existing project
30
30
  - `get_project_status` - Get project status info
31
31
  - `list_projects` - List all projects with filters
@@ -74,30 +74,15 @@ export function registerResources(
74
74
 
75
75
  if (uri === "agents://context") {
76
76
  const projectPath = options.projectPath ?? process.cwd();
77
- const jackPath = join(projectPath, "JACK.md");
78
77
  const agentsPath = join(projectPath, "AGENTS.md");
79
78
  const claudePath = join(projectPath, "CLAUDE.md");
80
79
 
81
80
  const contents: string[] = [];
82
81
 
83
- // Try to read JACK.md first (jack-specific context)
84
- if (existsSync(jackPath)) {
85
- try {
86
- const jackContent = await Bun.file(jackPath).text();
87
- contents.push("# JACK.md\n\n");
88
- contents.push(jackContent);
89
- } catch {
90
- // Ignore read errors
91
- }
92
- }
93
-
94
82
  // Try to read AGENTS.md
95
83
  if (existsSync(agentsPath)) {
96
84
  try {
97
85
  const agentsContent = await Bun.file(agentsPath).text();
98
- if (contents.length > 0) {
99
- contents.push("\n\n---\n\n");
100
- }
101
86
  contents.push("# AGENTS.md\n\n");
102
87
  contents.push(agentsContent);
103
88
  } catch {
@@ -143,7 +128,7 @@ If connected, prefer \`mcp__jack__*\` tools over CLI:
143
128
 
144
129
  Full docs: https://docs.getjack.org/llms-full.txt
145
130
 
146
- Check for JACK.md in the project root for project-specific instructions.
131
+ Check AGENTS.md in the project root for project-specific instructions.
147
132
  `;
148
133
  return {
149
134
  contents: [
package/src/mcp/server.ts CHANGED
@@ -43,8 +43,31 @@ export async function createMcpServer(options: McpServerOptions = {}) {
43
43
  return { server, debug };
44
44
  }
45
45
 
46
+ /**
47
+ * Install runtime guards that prevent accidental stdout writes from corrupting
48
+ * the MCP JSON-RPC stdio transport. This catches console.log() and
49
+ * process.stdout.write() but NOT Bun.spawn({ stdout: "inherit" }) which writes
50
+ * directly to fd 1 — that case is handled by the `interactive` flag in hooks.
51
+ */
52
+ function installStdoutGuards() {
53
+ // Redirect console.log to stderr so accidental calls don't corrupt the stream.
54
+ // We don't wrap process.stdout.write because the MCP SDK writes JSON-RPC
55
+ // messages through it — intercepting those risks breaking the protocol if
56
+ // messages are chunked or newlines are written separately.
57
+ console.log = (...args: unknown[]) => {
58
+ console.error(
59
+ "[jack-mcp] WARNING: console.log intercepted (would corrupt MCP protocol):",
60
+ ...args,
61
+ );
62
+ };
63
+ }
64
+
46
65
  export async function startMcpServer(options: McpServerOptions = {}) {
47
66
  const { server, debug } = await createMcpServer(options);
67
+
68
+ // Install stdout guards BEFORE connecting transport to prevent corruption
69
+ installStdoutGuards();
70
+
48
71
  const transport = new StdioServerTransport();
49
72
 
50
73
  debug("Starting MCP server on stdio transport");
@@ -7,6 +7,10 @@ import { JackError, JackErrorCode } from "../../lib/errors.ts";
7
7
  import { getDeployMode, getProjectId } from "../../lib/project-link.ts";
8
8
  import { createProject, deployProject, getProjectStatus } from "../../lib/project-operations.ts";
9
9
  import { listAllProjects } from "../../lib/project-resolver.ts";
10
+ import { createCronSchedule } from "../../lib/services/cron-create.ts";
11
+ import { deleteCronSchedule } from "../../lib/services/cron-delete.ts";
12
+ import { listCronSchedules } from "../../lib/services/cron-list.ts";
13
+ import { testCronExpression } from "../../lib/services/cron-test.ts";
10
14
  import { createDatabase } from "../../lib/services/db-create.ts";
11
15
  import {
12
16
  DestructiveOperationError,
@@ -15,10 +19,6 @@ import {
15
19
  wrapResultsForMcp,
16
20
  } from "../../lib/services/db-execute.ts";
17
21
  import { listDatabases } from "../../lib/services/db-list.ts";
18
- import { createCronSchedule } from "../../lib/services/cron-create.ts";
19
- import { deleteCronSchedule } from "../../lib/services/cron-delete.ts";
20
- import { listCronSchedules } from "../../lib/services/cron-list.ts";
21
- import { testCronExpression } from "../../lib/services/cron-test.ts";
22
22
  import {
23
23
  assignDomain,
24
24
  connectDomain,
@@ -41,7 +41,12 @@ import { formatErrorResponse, formatSuccessResponse } from "../utils.ts";
41
41
  // Tool schemas
42
42
  const CreateProjectSchema = z.object({
43
43
  name: z.string().optional().describe("Project name (auto-generated if not provided)"),
44
- template: z.string().optional().describe("Template to use (e.g., 'miniapp', 'api')"),
44
+ template: z
45
+ .string()
46
+ .optional()
47
+ .describe(
48
+ "Template to use (e.g., 'miniapp', 'api'). Also supports forking: use 'username/slug' for published projects or 'my-project' to fork your own.",
49
+ ),
45
50
  });
46
51
 
47
52
  const DeployProjectSchema = z.object({
@@ -49,6 +54,12 @@ const DeployProjectSchema = z.object({
49
54
  .string()
50
55
  .optional()
51
56
  .describe("Path to project directory (defaults to current directory)"),
57
+ message: z
58
+ .string()
59
+ .optional()
60
+ .describe(
61
+ "Deploy message describing what changed and why (e.g., 'Add user auth', 'Fix CORS bug')",
62
+ ),
52
63
  });
53
64
 
54
65
  const GetProjectStatusSchema = z.object({
@@ -200,6 +211,19 @@ const TailLogsSchema = z.object({
200
211
  .describe("How long to listen before returning (default: 2000ms, max: 10000ms)"),
201
212
  });
202
213
 
214
+ const RollbackProjectSchema = z.object({
215
+ deployment_id: z
216
+ .string()
217
+ .optional()
218
+ .describe(
219
+ "Specific deployment ID to roll back to (defaults to previous successful deployment)",
220
+ ),
221
+ project_path: z
222
+ .string()
223
+ .optional()
224
+ .describe("Path to project directory (defaults to current directory)"),
225
+ });
226
+
203
227
  const ListDomainsSchema = z.object({});
204
228
 
205
229
  const ConnectDomainSchema = z.object({
@@ -220,7 +244,9 @@ const DisconnectDomainSchema = z.object({
220
244
  });
221
245
 
222
246
  const CreateCronSchema = z.object({
223
- expression: z.string().describe("Cron expression (e.g., '0 * * * *' for hourly, '*/15 * * * *' for every 15 min)"),
247
+ expression: z
248
+ .string()
249
+ .describe("Cron expression (e.g., '0 * * * *' for hourly, '*/15 * * * *' for every 15 min)"),
224
250
  project_path: z
225
251
  .string()
226
252
  .optional()
@@ -264,7 +290,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
264
290
  {
265
291
  name: "create_project",
266
292
  description:
267
- "Create a new Cloudflare Workers project from a template. Automatically installs dependencies, deploys to Cloudflare, and registers the project.",
293
+ "Create a new project from a template. Automatically installs dependencies, deploys, and registers the project. Also supports forking: pass a 'username/slug' template to fork a published project, or a project slug to fork your own.",
268
294
  inputSchema: {
269
295
  type: "object",
270
296
  properties: {
@@ -274,7 +300,8 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
274
300
  },
275
301
  template: {
276
302
  type: "string",
277
- description: "Template to use (e.g., 'miniapp', 'api')",
303
+ description:
304
+ "Template to use (e.g., 'miniapp', 'api'). Also supports forking: use 'username/slug' for published projects or 'my-project' to fork your own.",
278
305
  },
279
306
  },
280
307
  },
@@ -282,7 +309,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
282
309
  {
283
310
  name: "deploy_project",
284
311
  description:
285
- "Deploy an existing project to Cloudflare Workers. Builds the project if needed and pushes to production.",
312
+ "Deploy an existing project. Builds if needed and pushes to production. Always provide a 'message' describing what changed and why (e.g., 'Add user auth', 'Fix CORS bug').",
286
313
  inputSchema: {
287
314
  type: "object",
288
315
  properties: {
@@ -290,13 +317,18 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
290
317
  type: "string",
291
318
  description: "Path to project directory (defaults to current directory)",
292
319
  },
320
+ message: {
321
+ type: "string",
322
+ description:
323
+ "Deploy message describing what changed and why (e.g., 'Add user auth', 'Fix CORS bug')",
324
+ },
293
325
  },
294
326
  },
295
327
  },
296
328
  {
297
329
  name: "get_project_status",
298
330
  description:
299
- "Get detailed status information for a specific project, including deployment status, local path, and backup status.",
331
+ "Get live deployment state: URL, last deploy time, deploy count, status (live/failed), and deploy source. Call this first to understand what's currently deployed before making changes.",
300
332
  inputSchema: {
301
333
  type: "object",
302
334
  properties: {
@@ -347,7 +379,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
347
379
  {
348
380
  name: "tail_logs",
349
381
  description:
350
- "Collect a short sample of live log events (JSON) from a managed (jack cloud) project. Useful for agentic debugging.",
382
+ "Collect live log events from production. Use after deploying to verify changes work, or to debug errors. Returns JSON log entries with timestamps and messages.",
351
383
  inputSchema: {
352
384
  type: "object",
353
385
  properties: {
@@ -370,6 +402,27 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
370
402
  },
371
403
  },
372
404
  },
405
+ {
406
+ name: "rollback_project",
407
+ description:
408
+ "Roll back a managed (jack cloud) project to a previous deployment. " +
409
+ "Defaults to the previous successful deployment if no deployment_id is specified. " +
410
+ "Only rolls back code — database state and secrets are unchanged.",
411
+ inputSchema: {
412
+ type: "object",
413
+ properties: {
414
+ deployment_id: {
415
+ type: "string",
416
+ description:
417
+ "Specific deployment ID to roll back to (defaults to previous successful deployment)",
418
+ },
419
+ project_path: {
420
+ type: "string",
421
+ description: "Path to project directory (defaults to current directory)",
422
+ },
423
+ },
424
+ },
425
+ },
373
426
  {
374
427
  name: "create_database",
375
428
  description:
@@ -654,13 +707,14 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
654
707
  {
655
708
  name: "create_cron",
656
709
  description:
657
- "Create a cron schedule for a managed (Jack Cloud) project. Minimum interval is 15 minutes. The worker must have a scheduled() handler or POST /__scheduled route.",
710
+ "Create a cron schedule for a managed (Jack Cloud) project. Minimum interval is 15 minutes. The worker must handle POST /__scheduled requests — Cloudflare's native scheduled() export does not work with Jack Cloud crons.",
658
711
  inputSchema: {
659
712
  type: "object",
660
713
  properties: {
661
714
  expression: {
662
715
  type: "string",
663
- description: "Cron expression (e.g., '0 * * * *' for hourly, '*/15 * * * *' for every 15 min)",
716
+ description:
717
+ "Cron expression (e.g., '0 * * * *' for hourly, '*/15 * * * *' for every 15 min)",
664
718
  },
665
719
  project_path: {
666
720
  type: "string",
@@ -719,7 +773,8 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
719
773
  trigger_production: {
720
774
  type: "boolean",
721
775
  default: false,
722
- description: "Whether to trigger the cron handler on production (requires managed project)",
776
+ description:
777
+ "Whether to trigger the cron handler on production (requires managed project)",
723
778
  },
724
779
  },
725
780
  required: ["expression"],
@@ -777,12 +832,13 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
777
832
 
778
833
  const wrappedDeployProject = withTelemetry(
779
834
  "deploy_project",
780
- async (projectPath?: string) => {
835
+ async (projectPath?: string, message?: string) => {
781
836
  const result = await deployProject({
782
837
  projectPath,
783
838
  interactive: false,
784
839
  includeSecrets: false,
785
840
  includeSync: false,
841
+ message,
786
842
  });
787
843
 
788
844
  // Track business event
@@ -795,7 +851,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
795
851
  { platform: "mcp" },
796
852
  );
797
853
 
798
- const result = await wrappedDeployProject(args.project_path);
854
+ const result = await wrappedDeployProject(args.project_path, args.message);
799
855
 
800
856
  return {
801
857
  content: [
@@ -1046,6 +1102,60 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
1046
1102
  };
1047
1103
  }
1048
1104
 
1105
+ case "rollback_project": {
1106
+ const args = RollbackProjectSchema.parse(request.params.arguments ?? {});
1107
+ const projectPath = args.project_path ?? process.cwd();
1108
+
1109
+ const deployMode = await getDeployMode(projectPath);
1110
+ if (deployMode !== "managed") {
1111
+ throw new JackError(
1112
+ JackErrorCode.VALIDATION_ERROR,
1113
+ "Rollback is only available for managed (jack cloud) projects",
1114
+ "For BYOC projects, use wrangler to manage deployments.",
1115
+ );
1116
+ }
1117
+
1118
+ const projectId = await getProjectId(projectPath);
1119
+ if (!projectId) {
1120
+ throw new JackError(
1121
+ JackErrorCode.PROJECT_NOT_FOUND,
1122
+ "Project not found",
1123
+ "Run this from a linked jack cloud project directory (has .jack/project.json).",
1124
+ );
1125
+ }
1126
+
1127
+ // Import rollbackDeployment from control-plane
1128
+ const { rollbackDeployment } = await import("../../lib/control-plane.ts");
1129
+
1130
+ const wrappedRollback = withTelemetry(
1131
+ "rollback_project",
1132
+ async (id: string, deploymentId?: string) => rollbackDeployment(id, deploymentId),
1133
+ { platform: "mcp" },
1134
+ );
1135
+
1136
+ const result = await wrappedRollback(projectId, args.deployment_id);
1137
+
1138
+ return {
1139
+ content: [
1140
+ {
1141
+ type: "text",
1142
+ text: JSON.stringify(
1143
+ formatSuccessResponse(
1144
+ {
1145
+ ...result.deployment,
1146
+ message:
1147
+ "Code rolled back successfully. Database state and secrets are unchanged.",
1148
+ },
1149
+ startTime,
1150
+ ),
1151
+ null,
1152
+ 2,
1153
+ ),
1154
+ },
1155
+ ],
1156
+ };
1157
+ }
1158
+
1049
1159
  case "create_database": {
1050
1160
  const args = CreateDatabaseSchema.parse(request.params.arguments ?? {});
1051
1161
  const projectPath = args.project_path ?? process.cwd();
@@ -1268,7 +1378,13 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
1268
1378
  content: [
1269
1379
  {
1270
1380
  type: "text",
1271
- text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
1381
+ text: JSON.stringify(
1382
+ formatSuccessResponse(result, startTime, [
1383
+ "Vectorize indexes have eventual consistency. Newly inserted vectors typically take 2-3 minutes to become queryable.",
1384
+ ]),
1385
+ null,
1386
+ 2,
1387
+ ),
1272
1388
  },
1273
1389
  ],
1274
1390
  };
package/src/mcp/types.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  export interface McpToolResponse<T = unknown> {
6
6
  success: boolean;
7
7
  data?: T;
8
+ notes?: string[]; // Situational context for AI agents (e.g. eventual consistency caveats)
8
9
  error?: {
9
10
  code: string; // Machine-readable: 'AUTH_FAILED', 'PROJECT_NOT_FOUND'
10
11
  message: string; // Human-readable description
package/src/mcp/utils.ts CHANGED
@@ -5,10 +5,11 @@ import { McpErrorCode, type McpToolResponse } from "./types.ts";
5
5
  /**
6
6
  * Format a successful MCP tool response
7
7
  */
8
- export function formatSuccessResponse<T>(data: T, startTime: number): McpToolResponse<T> {
8
+ export function formatSuccessResponse<T>(data: T, startTime: number, notes?: string[]): McpToolResponse<T> {
9
9
  return {
10
10
  success: true,
11
11
  data,
12
+ ...(notes?.length && { notes }),
12
13
  meta: {
13
14
  duration_ms: Date.now() - startTime,
14
15
  jack_version: packageJson.version,