@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
@@ -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
  /**
@@ -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})`);
@@ -785,19 +797,41 @@ export async function createProject(
785
797
  ? resolve(targetDirOption, name)
786
798
  : join(getJackHome(), name);
787
799
  if (existsSync(effectiveTargetDir)) {
800
+ const existingLink = await readProjectLink(effectiveTargetDir);
801
+ if (existingLink) {
802
+ throw new JackError(
803
+ JackErrorCode.VALIDATION_ERROR,
804
+ `'${name}' already exists`,
805
+ `cd ${effectiveTargetDir} && jack ship to deploy it, or rm -rf ${effectiveTargetDir} to start fresh.`,
806
+ );
807
+ }
788
808
  throw new JackError(
789
809
  JackErrorCode.VALIDATION_ERROR,
790
- `Folder exists at ${effectiveTargetDir}/`,
791
- "Remove it first, or use 'jack ship' if it's a project.",
810
+ `Folder already exists at ${effectiveTargetDir}/`,
811
+ "Remove it or pick a different name.",
792
812
  );
793
813
  }
794
814
  }
795
815
 
796
- // Check if jack init was run (throws if not)
816
+ // Auto-initialize on first run (replaces "Run: jack init" error)
797
817
  const { isInitialized } = await import("../commands/init.ts");
798
818
  const initialized = await isInitialized();
799
819
  if (!initialized) {
800
- throw new JackError(JackErrorCode.VALIDATION_ERROR, "jack is not set up yet", "Run: jack init");
820
+ const { writeConfig, readConfig } = await import("./config.ts");
821
+ const existingConfig = await readConfig();
822
+ await writeConfig({
823
+ version: 1,
824
+ initialized: true,
825
+ initializedAt: existingConfig?.initializedAt || new Date().toISOString(),
826
+ ...existingConfig,
827
+ });
828
+
829
+ // Best-effort MCP config install (non-blocking)
830
+ try {
831
+ const { installMcpConfigsToAllApps, saveMcpConfig } = await import("./mcp-config.ts");
832
+ await installMcpConfigsToAllApps();
833
+ await saveMcpConfig();
834
+ } catch {}
801
835
  }
802
836
 
803
837
  // Auth gate - check/prompt for authentication before any work
@@ -847,10 +881,18 @@ export async function createProject(
847
881
 
848
882
  // Check directory doesn't exist (only needed for auto-generated names now)
849
883
  if (!nameWasProvided && existsSync(targetDir)) {
884
+ const existingLink = await readProjectLink(targetDir);
885
+ if (existingLink) {
886
+ throw new JackError(
887
+ JackErrorCode.VALIDATION_ERROR,
888
+ `'${projectName}' already exists`,
889
+ `cd ${targetDir} && jack ship to deploy it, or rm -rf ${targetDir} to start fresh.`,
890
+ );
891
+ }
850
892
  throw new JackError(
851
893
  JackErrorCode.VALIDATION_ERROR,
852
- `Folder exists at ${targetDir}/`,
853
- "Remove it first, or use 'jack ship' if it's a project.",
894
+ `Folder already exists at ${targetDir}/`,
895
+ "Remove it first or pick a different name.",
854
896
  );
855
897
  }
856
898
 
@@ -1019,8 +1061,8 @@ export async function createProject(
1019
1061
  if (!hookResult.success) {
1020
1062
  throw new JackError(
1021
1063
  JackErrorCode.VALIDATION_ERROR,
1022
- "Project setup incomplete",
1023
- "Missing required configuration",
1064
+ "Project setup cancelled",
1065
+ `Run the same command again when you're ready — jack new ${projectName} -t ${resolvedTemplate}`,
1024
1066
  );
1025
1067
  }
1026
1068
  }
@@ -1085,7 +1127,7 @@ export async function createProject(
1085
1127
  placeholder: "paste value or press Esc to skip",
1086
1128
  });
1087
1129
 
1088
- if (!isCancel(value) && value.trim()) {
1130
+ if (!isCancel(value) && value && value.trim()) {
1089
1131
  secretsToUse[optionalSecret.name] = value.trim();
1090
1132
  // Save to global secrets for reuse
1091
1133
  await saveSecrets([
@@ -1097,7 +1139,7 @@ export async function createProject(
1097
1139
  ]);
1098
1140
  reporter.success(`Saved ${optionalSecret.name}`);
1099
1141
  } else {
1100
- reporter.info(`Skipped ${optionalSecret.name}`);
1142
+ reporter.info(`Skipped ${optionalSecret.name} — add it later with: jack secrets add ${optionalSecret.name}`);
1101
1143
  }
1102
1144
 
1103
1145
  reporter.start("Creating project...");
@@ -1199,9 +1241,11 @@ export async function createProject(
1199
1241
  reporter.start("Setting up project...");
1200
1242
 
1201
1243
  try {
1244
+ const forkedFrom = templateOrigin.type !== "builtin" ? templateOrigin.name : undefined;
1202
1245
  const result = await runParallelSetup(targetDir, projectName, {
1203
1246
  template: resolvedTemplate || "hello",
1204
1247
  usePrebuilt: templateOrigin.type === "builtin", // Only builtin templates have prebuilt bundles
1248
+ forkedFrom,
1205
1249
  onRemoteReady: (remote) => {
1206
1250
  // Show URL immediately when prebuilt succeeds
1207
1251
  reporter.stop();
@@ -1355,22 +1399,8 @@ export async function createProject(
1355
1399
  reporter.success(`Deployed: ${workerUrl}`);
1356
1400
  }
1357
1401
 
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
- }
1402
+ // source.zip is now stored in deployment artifacts by the control plane
1403
+ // during prebuilt deploy — no client-side upload needed
1374
1404
  } else {
1375
1405
  // Prebuilt not available - fall back to fresh build
1376
1406
  if (remoteResult.prebuiltFailed) {
@@ -1688,11 +1718,10 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1688
1718
  deployMode = link?.deploy_mode ?? "byo";
1689
1719
  }
1690
1720
 
1691
- // Ensure agent integration is set up (JACK.md, MCP config)
1721
+ // Ensure agent integration is set up (MCP config)
1692
1722
  // This is idempotent and runs silently
1693
1723
  try {
1694
1724
  await ensureAgentIntegration(projectPath, {
1695
- projectName,
1696
1725
  silent: true,
1697
1726
  });
1698
1727
  } catch (err) {
@@ -1723,6 +1752,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1723
1752
 
1724
1753
  let workerUrl: string | null = null;
1725
1754
  let deployOutput: string | undefined;
1755
+ let managedDeployResult: { deploymentId: string; status: string; errorMessage: string | null } | undefined;
1726
1756
 
1727
1757
  // Deploy based on mode
1728
1758
  if (deployMode === "managed") {
@@ -1781,7 +1811,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1781
1811
  }
1782
1812
 
1783
1813
  // deployToManagedProject now handles both template and code deploy
1784
- await deployToManagedProject(managedProjectId as string, projectPath, reporter);
1814
+ managedDeployResult = await deployToManagedProject(managedProjectId as string, projectPath, reporter, options.message);
1785
1815
 
1786
1816
  // Construct URL with username if available
1787
1817
  workerUrl = link?.owner_username
@@ -1936,6 +1966,9 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
1936
1966
  projectName,
1937
1967
  deployOutput: workerUrl ? undefined : deployOutput,
1938
1968
  deployMode,
1969
+ deploymentId: managedDeployResult?.deploymentId,
1970
+ deployStatus: managedDeployResult?.status,
1971
+ errorMessage: managedDeployResult?.errorMessage,
1939
1972
  };
1940
1973
  }
1941
1974
 
@@ -2025,11 +2058,35 @@ export async function getProjectStatus(
2025
2058
  }
2026
2059
  }
2027
2060
 
2061
+ // Fetch real deployment data for managed projects
2062
+ let lastDeployAt: string | null = null;
2063
+ let deployCount = 0;
2064
+ let lastDeployStatus: string | null = null;
2065
+ let lastDeploySource: string | null = null;
2066
+ let lastDeployMessage: string | null = null;
2067
+
2068
+ if (link?.deploy_mode === "managed") {
2069
+ try {
2070
+ const { fetchDeployments } = await import("./control-plane.ts");
2071
+ const result = await fetchDeployments(link.project_id);
2072
+ deployCount = result.total;
2073
+ const latest = result.deployments[0];
2074
+ if (latest) {
2075
+ lastDeployAt = latest.created_at;
2076
+ lastDeployStatus = latest.status;
2077
+ lastDeploySource = latest.source;
2078
+ lastDeployMessage = latest.message;
2079
+ }
2080
+ } catch {
2081
+ // Silent fail — deploy tracking is supplementary
2082
+ }
2083
+ }
2084
+
2028
2085
  return {
2029
2086
  name: projectName,
2030
2087
  localPath,
2031
2088
  workerUrl,
2032
- lastDeployed: link?.linked_at ?? null,
2089
+ lastDeployed: lastDeployAt ?? link?.linked_at ?? null,
2033
2090
  createdAt: link?.linked_at ?? null,
2034
2091
  accountId: null, // No longer stored in registry
2035
2092
  workerId: projectName,
@@ -2040,6 +2097,11 @@ export async function getProjectStatus(
2040
2097
  missing: false,
2041
2098
  backupFiles,
2042
2099
  backupLastSync,
2100
+ lastDeployAt,
2101
+ deployCount,
2102
+ lastDeployStatus,
2103
+ lastDeploySource,
2104
+ lastDeployMessage,
2043
2105
  };
2044
2106
  }
2045
2107
 
@@ -68,7 +68,7 @@ async function promptAdditionalSecrets(): Promise<
68
68
  message: "Enter secret name (or press enter to finish):",
69
69
  });
70
70
 
71
- if (isCancel(key) || !key.trim()) {
71
+ if (isCancel(key) || !key || !key.trim()) {
72
72
  break;
73
73
  }
74
74
 
@@ -76,7 +76,7 @@ async function promptAdditionalSecrets(): Promise<
76
76
  message: `Enter value for ${key}:`,
77
77
  });
78
78
 
79
- if (isCancel(value)) {
79
+ if (isCancel(value) || !value) {
80
80
  break;
81
81
  }
82
82
 
@@ -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");