@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.
- package/package.json +1 -1
- package/src/commands/deploys.ts +95 -0
- package/src/commands/link.ts +8 -0
- package/src/commands/mcp.ts +179 -4
- package/src/commands/rollback.ts +53 -0
- package/src/commands/secrets.ts +3 -1
- package/src/commands/services.ts +11 -1
- package/src/commands/ship.ts +3 -1
- package/src/commands/tokens.ts +16 -1
- package/src/commands/whoami.ts +43 -8
- package/src/index.ts +16 -0
- package/src/lib/agent-files.ts +54 -4
- package/src/lib/agent-integration.ts +4 -166
- package/src/lib/claude-hooks-installer.ts +55 -0
- package/src/lib/control-plane.ts +78 -40
- package/src/lib/crypto.ts +84 -0
- package/src/lib/debug.ts +2 -1
- package/src/lib/deploy-upload.ts +13 -3
- package/src/lib/hooks.ts +4 -3
- package/src/lib/managed-deploy.ts +12 -9
- package/src/lib/project-link.ts +6 -0
- package/src/lib/project-operations.ts +92 -30
- package/src/lib/prompts.ts +2 -2
- package/src/lib/telemetry.ts +2 -0
- package/src/mcp/README.md +1 -1
- package/src/mcp/resources/index.ts +1 -16
- package/src/mcp/server.ts +23 -0
- package/src/mcp/tools/index.ts +133 -17
- package/src/mcp/types.ts +1 -0
- package/src/mcp/utils.ts +2 -1
- package/src/templates/index.ts +25 -73
- package/templates/CLAUDE.md +62 -0
- package/templates/ai-chat/.jack.json +10 -5
- package/templates/ai-chat/bun.lock +50 -1
- package/templates/ai-chat/package.json +5 -0
- package/templates/ai-chat/public/app.js +73 -0
- package/templates/ai-chat/public/index.html +14 -197
- package/templates/ai-chat/schema.sql +14 -0
- package/templates/ai-chat/src/index.ts +86 -102
- package/templates/ai-chat/wrangler.jsonc +8 -1
- package/templates/cron/.jack.json +66 -0
- package/templates/cron/bun.lock +23 -0
- package/templates/cron/package.json +16 -0
- package/templates/cron/schema.sql +24 -0
- package/templates/cron/src/index.ts +117 -0
- package/templates/cron/src/jobs.ts +139 -0
- package/templates/cron/src/webhooks.ts +95 -0
- package/templates/cron/tsconfig.json +17 -0
- package/templates/cron/wrangler.jsonc +11 -0
- package/templates/miniapp/.jack.json +1 -1
- package/templates/nextjs/.jack.json +1 -1
- package/templates/nextjs-auth/.jack.json +44 -0
- package/templates/nextjs-auth/app/api/auth/[...all]/route.ts +11 -0
- package/templates/nextjs-auth/app/dashboard/loading.tsx +53 -0
- package/templates/nextjs-auth/app/dashboard/page.tsx +73 -0
- package/templates/nextjs-auth/app/error.tsx +44 -0
- package/templates/nextjs-auth/app/globals.css +1 -0
- package/templates/nextjs-auth/app/health/route.ts +3 -0
- package/templates/nextjs-auth/app/layout.tsx +24 -0
- package/templates/nextjs-auth/app/login/page.tsx +10 -0
- package/templates/nextjs-auth/app/page.tsx +86 -0
- package/templates/nextjs-auth/app/signup/page.tsx +10 -0
- package/templates/nextjs-auth/bun.lock +1065 -0
- package/templates/nextjs-auth/cloudflare-env.d.ts +8 -0
- package/templates/nextjs-auth/components/auth-form.tsx +191 -0
- package/templates/nextjs-auth/components/header.tsx +50 -0
- package/templates/nextjs-auth/components/user-menu.tsx +23 -0
- package/templates/nextjs-auth/lib/auth-client.ts +3 -0
- package/templates/nextjs-auth/lib/auth.ts +43 -0
- package/templates/nextjs-auth/lib/utils.ts +6 -0
- package/templates/nextjs-auth/middleware.ts +33 -0
- package/templates/nextjs-auth/next.config.ts +8 -0
- package/templates/nextjs-auth/open-next.config.ts +6 -0
- package/templates/nextjs-auth/package.json +33 -0
- package/templates/nextjs-auth/postcss.config.mjs +8 -0
- package/templates/nextjs-auth/schema.sql +49 -0
- package/templates/nextjs-auth/tsconfig.json +28 -0
- package/templates/nextjs-auth/wrangler.jsonc +23 -0
- package/templates/nextjs-clerk/.jack.json +54 -0
- package/templates/nextjs-clerk/app/dashboard/page.tsx +69 -0
- package/templates/nextjs-clerk/app/globals.css +1 -0
- package/templates/nextjs-clerk/app/health/route.ts +3 -0
- package/templates/nextjs-clerk/app/layout.tsx +28 -0
- package/templates/nextjs-clerk/app/page.tsx +86 -0
- package/templates/nextjs-clerk/app/sign-in/[[...sign-in]]/page.tsx +9 -0
- package/templates/nextjs-clerk/app/sign-up/[[...sign-up]]/page.tsx +9 -0
- package/templates/nextjs-clerk/bun.lock +1055 -0
- package/templates/nextjs-clerk/cloudflare-env.d.ts +3 -0
- package/templates/nextjs-clerk/components/header.tsx +40 -0
- package/templates/nextjs-clerk/lib/utils.ts +6 -0
- package/templates/nextjs-clerk/middleware.ts +18 -0
- package/templates/nextjs-clerk/next.config.ts +8 -0
- package/templates/nextjs-clerk/open-next.config.ts +6 -0
- package/templates/nextjs-clerk/package.json +31 -0
- package/templates/nextjs-clerk/postcss.config.mjs +8 -0
- package/templates/nextjs-clerk/tsconfig.json +28 -0
- package/templates/nextjs-clerk/wrangler.jsonc +17 -0
- package/templates/nextjs-shadcn/.jack.json +34 -0
- package/templates/nextjs-shadcn/app/dashboard/data.json +614 -0
- package/templates/nextjs-shadcn/app/dashboard/page.tsx +55 -0
- package/templates/nextjs-shadcn/app/globals.css +126 -0
- package/templates/nextjs-shadcn/app/health/route.ts +3 -0
- package/templates/nextjs-shadcn/app/layout.tsx +24 -0
- package/templates/nextjs-shadcn/app/login/page.tsx +19 -0
- package/templates/nextjs-shadcn/app/page.tsx +180 -0
- package/templates/nextjs-shadcn/app/showcase.tsx +1262 -0
- package/templates/nextjs-shadcn/bun.lock +1789 -0
- package/templates/nextjs-shadcn/cloudflare-env.d.ts +4 -0
- package/templates/nextjs-shadcn/components/app-sidebar.tsx +175 -0
- package/templates/nextjs-shadcn/components/chart-area-interactive.tsx +291 -0
- package/templates/nextjs-shadcn/components/data-table.tsx +807 -0
- package/templates/nextjs-shadcn/components/login-form.tsx +95 -0
- package/templates/nextjs-shadcn/components/nav-documents.tsx +92 -0
- package/templates/nextjs-shadcn/components/nav-main.tsx +73 -0
- package/templates/nextjs-shadcn/components/nav-projects.tsx +89 -0
- package/templates/nextjs-shadcn/components/nav-secondary.tsx +42 -0
- package/templates/nextjs-shadcn/components/nav-user.tsx +114 -0
- package/templates/nextjs-shadcn/components/section-cards.tsx +102 -0
- package/templates/nextjs-shadcn/components/site-header.tsx +30 -0
- package/templates/nextjs-shadcn/components/team-switcher.tsx +91 -0
- package/templates/nextjs-shadcn/components/ui/accordion.tsx +66 -0
- package/templates/nextjs-shadcn/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-shadcn/components/ui/alert.tsx +66 -0
- package/templates/nextjs-shadcn/components/ui/aspect-ratio.tsx +11 -0
- package/templates/nextjs-shadcn/components/ui/avatar.tsx +109 -0
- package/templates/nextjs-shadcn/components/ui/badge.tsx +48 -0
- package/templates/nextjs-shadcn/components/ui/breadcrumb.tsx +109 -0
- package/templates/nextjs-shadcn/components/ui/button-group.tsx +83 -0
- package/templates/nextjs-shadcn/components/ui/button.tsx +64 -0
- package/templates/nextjs-shadcn/components/ui/calendar.tsx +220 -0
- package/templates/nextjs-shadcn/components/ui/card.tsx +92 -0
- package/templates/nextjs-shadcn/components/ui/carousel.tsx +241 -0
- package/templates/nextjs-shadcn/components/ui/chart.tsx +357 -0
- package/templates/nextjs-shadcn/components/ui/checkbox.tsx +32 -0
- package/templates/nextjs-shadcn/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-shadcn/components/ui/combobox.tsx +310 -0
- package/templates/nextjs-shadcn/components/ui/command.tsx +184 -0
- package/templates/nextjs-shadcn/components/ui/context-menu.tsx +252 -0
- package/templates/nextjs-shadcn/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-shadcn/components/ui/direction.tsx +22 -0
- package/templates/nextjs-shadcn/components/ui/drawer.tsx +135 -0
- package/templates/nextjs-shadcn/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-shadcn/components/ui/empty.tsx +104 -0
- package/templates/nextjs-shadcn/components/ui/field.tsx +248 -0
- package/templates/nextjs-shadcn/components/ui/form.tsx +167 -0
- package/templates/nextjs-shadcn/components/ui/hover-card.tsx +44 -0
- package/templates/nextjs-shadcn/components/ui/input-group.tsx +170 -0
- package/templates/nextjs-shadcn/components/ui/input-otp.tsx +77 -0
- package/templates/nextjs-shadcn/components/ui/input.tsx +21 -0
- package/templates/nextjs-shadcn/components/ui/item.tsx +193 -0
- package/templates/nextjs-shadcn/components/ui/kbd.tsx +28 -0
- package/templates/nextjs-shadcn/components/ui/label.tsx +24 -0
- package/templates/nextjs-shadcn/components/ui/menubar.tsx +276 -0
- package/templates/nextjs-shadcn/components/ui/native-select.tsx +53 -0
- package/templates/nextjs-shadcn/components/ui/navigation-menu.tsx +168 -0
- package/templates/nextjs-shadcn/components/ui/pagination.tsx +127 -0
- package/templates/nextjs-shadcn/components/ui/popover.tsx +89 -0
- package/templates/nextjs-shadcn/components/ui/progress.tsx +31 -0
- package/templates/nextjs-shadcn/components/ui/radio-group.tsx +45 -0
- package/templates/nextjs-shadcn/components/ui/resizable.tsx +53 -0
- package/templates/nextjs-shadcn/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-shadcn/components/ui/select.tsx +190 -0
- package/templates/nextjs-shadcn/components/ui/separator.tsx +28 -0
- package/templates/nextjs-shadcn/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-shadcn/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-shadcn/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-shadcn/components/ui/slider.tsx +63 -0
- package/templates/nextjs-shadcn/components/ui/sonner.tsx +40 -0
- package/templates/nextjs-shadcn/components/ui/spinner.tsx +16 -0
- package/templates/nextjs-shadcn/components/ui/switch.tsx +35 -0
- package/templates/nextjs-shadcn/components/ui/table.tsx +116 -0
- package/templates/nextjs-shadcn/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-shadcn/components/ui/textarea.tsx +18 -0
- package/templates/nextjs-shadcn/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-shadcn/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-shadcn/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-shadcn/components.json +23 -0
- package/templates/nextjs-shadcn/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-shadcn/lib/utils.ts +6 -0
- package/templates/nextjs-shadcn/next-env.d.ts +6 -0
- package/templates/nextjs-shadcn/next.config.ts +8 -0
- package/templates/nextjs-shadcn/open-next.config.ts +6 -0
- package/templates/nextjs-shadcn/package.json +55 -0
- package/templates/nextjs-shadcn/postcss.config.mjs +8 -0
- package/templates/nextjs-shadcn/tsconfig.json +28 -0
- package/templates/nextjs-shadcn/wrangler.jsonc +23 -0
- package/templates/resend/.jack.json +64 -0
- package/templates/resend/bun.lock +23 -0
- package/templates/resend/package.json +16 -0
- package/templates/resend/schema.sql +13 -0
- package/templates/resend/src/email.ts +165 -0
- package/templates/resend/src/index.ts +108 -0
- package/templates/resend/tsconfig.json +17 -0
- package/templates/resend/wrangler.jsonc +11 -0
- package/templates/saas/.jack.json +1 -1
- 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
|
|
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
|
-
//
|
|
187
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/project-link.ts
CHANGED
|
@@ -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
|
|
810
|
+
`Folder already exists at ${effectiveTargetDir}/`,
|
|
811
|
+
"Remove it or pick a different name.",
|
|
792
812
|
);
|
|
793
813
|
}
|
|
794
814
|
}
|
|
795
815
|
|
|
796
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
1023
|
-
|
|
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
|
-
//
|
|
1359
|
-
|
|
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 (
|
|
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
|
|
package/src/lib/prompts.ts
CHANGED
|
@@ -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
|
|
package/src/lib/telemetry.ts
CHANGED
|
@@ -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
|
|
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
|
|
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");
|