@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.
- 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/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/debug.ts +2 -1
- package/src/lib/deploy-upload.ts +6 -0
- package/src/lib/hooks.ts +3 -1
- package/src/lib/managed-deploy.ts +12 -9
- package/src/lib/project-link.ts +6 -0
- package/src/lib/project-operations.ts +68 -22
- 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 +41 -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 +26 -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
|
@@ -1,165 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent integration module
|
|
3
3
|
*
|
|
4
|
-
* Ensures AI agents have
|
|
4
|
+
* Ensures AI agents have MCP configured for jack projects.
|
|
5
5
|
* Called during both project creation and first BYO deploy.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import type { Template } from "../templates/types.ts";
|
|
11
8
|
import { installMcpConfigsToAllApps, isAppInstalled } from "./mcp-config.ts";
|
|
12
9
|
|
|
13
10
|
export interface EnsureAgentResult {
|
|
14
11
|
mcpInstalled: string[];
|
|
15
|
-
jackMdCreated: boolean;
|
|
16
|
-
referencesAdded: string[];
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
export interface EnsureAgentOptions {
|
|
20
|
-
template?: Template;
|
|
21
15
|
silent?: boolean;
|
|
22
|
-
projectName?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Generate JACK.md content
|
|
27
|
-
* Uses template agentContext if available, otherwise generic jack instructions
|
|
28
|
-
*/
|
|
29
|
-
export function generateJackMd(projectName?: string, template?: Template): string {
|
|
30
|
-
const header = projectName ? `# ${projectName}\n\n` : "# Jack\n\n";
|
|
31
|
-
|
|
32
|
-
const templateSummary = template?.agentContext?.summary;
|
|
33
|
-
const templateFullText = template?.agentContext?.full_text;
|
|
34
|
-
|
|
35
|
-
const summarySection = templateSummary ? `> ${templateSummary}\n\n` : "";
|
|
36
|
-
|
|
37
|
-
const templateSection = templateFullText ? `${templateFullText}\n\n` : "";
|
|
38
|
-
|
|
39
|
-
return `${header}${summarySection}This project is deployed and managed via jack.
|
|
40
|
-
|
|
41
|
-
## Quick Commands
|
|
42
|
-
|
|
43
|
-
| Command | What it does |
|
|
44
|
-
|---------|--------------|
|
|
45
|
-
| \`jack ship\` | Deploy to production |
|
|
46
|
-
| \`jack logs\` | Stream live logs |
|
|
47
|
-
| \`jack services\` | Manage databases, KV, and other bindings |
|
|
48
|
-
| \`jack secrets\` | Manage environment secrets |
|
|
49
|
-
|
|
50
|
-
## Important
|
|
51
|
-
|
|
52
|
-
- **Never run \`wrangler\` commands directly** - jack handles all infrastructure
|
|
53
|
-
- Use \`jack services db\` to create and query databases
|
|
54
|
-
- Secrets sync automatically across deploys
|
|
55
|
-
|
|
56
|
-
## Services & Bindings
|
|
57
|
-
|
|
58
|
-
Jack manages your project's services. To add a database:
|
|
59
|
-
|
|
60
|
-
\`\`\`bash
|
|
61
|
-
jack services db create
|
|
62
|
-
\`\`\`
|
|
63
|
-
|
|
64
|
-
To query it:
|
|
65
|
-
|
|
66
|
-
\`\`\`bash
|
|
67
|
-
jack services db query "SELECT * FROM users"
|
|
68
|
-
\`\`\`
|
|
69
|
-
|
|
70
|
-
More bindings (KV, R2, queues) coming soon.
|
|
71
|
-
|
|
72
|
-
${templateSection}## For AI Agents
|
|
73
|
-
|
|
74
|
-
### MCP Tools
|
|
75
|
-
|
|
76
|
-
If jack MCP is connected, prefer these tools over CLI commands:
|
|
77
|
-
|
|
78
|
-
| Tool | Use for |
|
|
79
|
-
|------|---------|
|
|
80
|
-
| \`mcp__jack__deploy_project\` | Deploy changes |
|
|
81
|
-
| \`mcp__jack__create_database\` | Create a new database |
|
|
82
|
-
| \`mcp__jack__execute_sql\` | Query the database |
|
|
83
|
-
| \`mcp__jack__list_projects\` | List all projects |
|
|
84
|
-
| \`mcp__jack__get_project_status\` | Check deployment status |
|
|
85
|
-
|
|
86
|
-
### Documentation
|
|
87
|
-
|
|
88
|
-
Full jack documentation: https://docs.getjack.org/llms-full.txt
|
|
89
|
-
`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Create JACK.md if it doesn't exist
|
|
94
|
-
*/
|
|
95
|
-
async function ensureJackMd(
|
|
96
|
-
projectPath: string,
|
|
97
|
-
projectName?: string,
|
|
98
|
-
template?: Template,
|
|
99
|
-
): Promise<boolean> {
|
|
100
|
-
const jackMdPath = join(projectPath, "JACK.md");
|
|
101
|
-
|
|
102
|
-
if (existsSync(jackMdPath)) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const content = generateJackMd(projectName, template);
|
|
107
|
-
await Bun.write(jackMdPath, content);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Append JACK.md reference to existing agent files (CLAUDE.md, AGENTS.md)
|
|
113
|
-
*/
|
|
114
|
-
async function appendJackMdReferences(projectPath: string): Promise<string[]> {
|
|
115
|
-
const filesToCheck = ["CLAUDE.md", "AGENTS.md"];
|
|
116
|
-
const referencesAdded: string[] = [];
|
|
117
|
-
const jackMdPath = join(projectPath, "JACK.md");
|
|
118
|
-
|
|
119
|
-
// Only add references if JACK.md exists
|
|
120
|
-
if (!existsSync(jackMdPath)) {
|
|
121
|
-
return referencesAdded;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const referenceBlock = `<!-- Added by jack -->
|
|
125
|
-
> **Jack project** - See [JACK.md](./JACK.md) for deployment, services, and bindings.
|
|
126
|
-
|
|
127
|
-
`;
|
|
128
|
-
|
|
129
|
-
for (const filename of filesToCheck) {
|
|
130
|
-
const filePath = join(projectPath, filename);
|
|
131
|
-
|
|
132
|
-
if (!existsSync(filePath)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const content = await Bun.file(filePath).text();
|
|
138
|
-
|
|
139
|
-
// Skip if reference already exists
|
|
140
|
-
if (content.includes("JACK.md") || content.includes("<!-- Added by jack -->")) {
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Find position after first heading, or prepend if no heading
|
|
145
|
-
const headingMatch = content.match(/^#[^\n]*\n/m);
|
|
146
|
-
let newContent: string;
|
|
147
|
-
|
|
148
|
-
if (headingMatch && headingMatch.index !== undefined) {
|
|
149
|
-
const insertPos = headingMatch.index + headingMatch[0].length;
|
|
150
|
-
newContent = content.slice(0, insertPos) + "\n" + referenceBlock + content.slice(insertPos);
|
|
151
|
-
} else {
|
|
152
|
-
newContent = referenceBlock + content;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
await Bun.write(filePath, newContent);
|
|
156
|
-
referencesAdded.push(filename);
|
|
157
|
-
} catch {
|
|
158
|
-
// Ignore errors reading/writing individual files
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return referencesAdded;
|
|
163
16
|
}
|
|
164
17
|
|
|
165
18
|
/**
|
|
@@ -186,31 +39,16 @@ async function ensureMcpConfigured(): Promise<string[]> {
|
|
|
186
39
|
/**
|
|
187
40
|
* Ensure agent integration is set up for a project
|
|
188
41
|
*
|
|
189
|
-
*
|
|
190
|
-
* 1. Creates JACK.md if not exists (with template context if available)
|
|
191
|
-
* 2. Appends JACK.md reference to existing CLAUDE.md/AGENTS.md
|
|
192
|
-
* 3. Installs MCP config to detected AI apps
|
|
193
|
-
*
|
|
42
|
+
* Installs MCP config to detected AI apps.
|
|
194
43
|
* Safe to call multiple times - all operations are idempotent.
|
|
195
44
|
*/
|
|
196
45
|
export async function ensureAgentIntegration(
|
|
197
|
-
|
|
198
|
-
|
|
46
|
+
_projectPath: string,
|
|
47
|
+
_options: EnsureAgentOptions = {},
|
|
199
48
|
): Promise<EnsureAgentResult> {
|
|
200
|
-
const { template, projectName } = options;
|
|
201
|
-
|
|
202
|
-
// 1. Create JACK.md if not exists
|
|
203
|
-
const jackMdCreated = await ensureJackMd(projectPath, projectName, template);
|
|
204
|
-
|
|
205
|
-
// 2. Append references to existing agent files
|
|
206
|
-
const referencesAdded = await appendJackMdReferences(projectPath);
|
|
207
|
-
|
|
208
|
-
// 3. Ensure MCP is configured
|
|
209
49
|
const mcpInstalled = await ensureMcpConfigured();
|
|
210
50
|
|
|
211
51
|
return {
|
|
212
52
|
mcpInstalled,
|
|
213
|
-
jackMdCreated,
|
|
214
|
-
referencesAdded,
|
|
215
53
|
};
|
|
216
54
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const HOOK_COMMAND = "jack mcp context 2>/dev/null || true";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Install a Claude Code SessionStart hook to the project's .claude/settings.json.
|
|
9
|
+
* Only fires when Claude Code is opened in this project directory.
|
|
10
|
+
* Non-destructive: preserves existing hooks and deduplicates.
|
|
11
|
+
*/
|
|
12
|
+
export async function installClaudeCodeHooks(projectPath: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
const claudeDir = join(projectPath, ".claude");
|
|
15
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
16
|
+
|
|
17
|
+
// Ensure .claude directory exists
|
|
18
|
+
if (!existsSync(claudeDir)) {
|
|
19
|
+
await mkdir(claudeDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let settings: Record<string, unknown> = {};
|
|
23
|
+
if (existsSync(settingsPath)) {
|
|
24
|
+
try {
|
|
25
|
+
settings = await Bun.file(settingsPath).json();
|
|
26
|
+
} catch {
|
|
27
|
+
settings = {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hooks = (settings.hooks as Record<string, unknown[]>) ?? {};
|
|
32
|
+
const sessionStart = (hooks.SessionStart as Array<Record<string, unknown>>) ?? [];
|
|
33
|
+
|
|
34
|
+
// Check if jack hook is already installed
|
|
35
|
+
for (const entry of sessionStart) {
|
|
36
|
+
const entryHooks = entry.hooks as Array<Record<string, string>> | undefined;
|
|
37
|
+
if (entryHooks?.some((h) => h.command?.includes("jack mcp context"))) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sessionStart.push({
|
|
43
|
+
matcher: "",
|
|
44
|
+
hooks: [{ type: "command", command: HOOK_COMMAND }],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
hooks.SessionStart = sessionStart;
|
|
48
|
+
settings.hooks = hooks;
|
|
49
|
+
|
|
50
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/lib/control-plane.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { debug } from "./debug.ts";
|
|
6
|
-
import { formatSize } from "./format.ts";
|
|
7
6
|
|
|
8
7
|
const DEFAULT_CONTROL_API_URL = "https://control.getjack.org";
|
|
9
8
|
|
|
@@ -16,12 +15,14 @@ export interface CreateProjectRequest {
|
|
|
16
15
|
slug?: string;
|
|
17
16
|
template?: string;
|
|
18
17
|
use_prebuilt?: boolean;
|
|
18
|
+
forked_from?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface CreateManagedProjectOptions {
|
|
22
22
|
slug?: string;
|
|
23
23
|
template?: string;
|
|
24
24
|
usePrebuilt?: boolean;
|
|
25
|
+
forkedFrom?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface CreateProjectResponse {
|
|
@@ -111,6 +112,9 @@ export async function createManagedProject(
|
|
|
111
112
|
if (options?.usePrebuilt !== undefined) {
|
|
112
113
|
requestBody.use_prebuilt = options.usePrebuilt;
|
|
113
114
|
}
|
|
115
|
+
if (options?.forkedFrom) {
|
|
116
|
+
requestBody.forked_from = options.forkedFrom;
|
|
117
|
+
}
|
|
114
118
|
|
|
115
119
|
debug("Creating managed project", {
|
|
116
120
|
name,
|
|
@@ -451,6 +455,79 @@ export async function fetchProjectResources(projectId: string): Promise<ProjectR
|
|
|
451
455
|
return data.resources;
|
|
452
456
|
}
|
|
453
457
|
|
|
458
|
+
export interface DeploymentInfo {
|
|
459
|
+
id: string;
|
|
460
|
+
status: "queued" | "building" | "live" | "failed";
|
|
461
|
+
source: string;
|
|
462
|
+
error_message: string | null;
|
|
463
|
+
message: string | null;
|
|
464
|
+
created_at: string;
|
|
465
|
+
updated_at: string;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface DeploymentListResult {
|
|
469
|
+
deployments: DeploymentInfo[];
|
|
470
|
+
total: number;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Fetch recent deployments for a managed project.
|
|
475
|
+
* Returns deployments (up to 10) and total count.
|
|
476
|
+
*/
|
|
477
|
+
export async function fetchDeployments(projectId: string): Promise<DeploymentListResult> {
|
|
478
|
+
const { authFetch } = await import("./auth/index.ts");
|
|
479
|
+
|
|
480
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/projects/${projectId}/deployments`);
|
|
481
|
+
|
|
482
|
+
if (!response.ok) {
|
|
483
|
+
return { deployments: [], total: 0 }; // Silent fail — deployment history is supplementary
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const data = (await response.json()) as { deployments: DeploymentInfo[]; total: number };
|
|
487
|
+
return { deployments: data.deployments, total: data.total };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export interface RollbackResponse {
|
|
491
|
+
deployment: {
|
|
492
|
+
id: string;
|
|
493
|
+
status: string;
|
|
494
|
+
source: string;
|
|
495
|
+
created_at: string;
|
|
496
|
+
updated_at: string;
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Rollback a managed project to a previous deployment.
|
|
502
|
+
* If no deploymentId given, rolls back to the previous successful deployment.
|
|
503
|
+
*/
|
|
504
|
+
export async function rollbackDeployment(
|
|
505
|
+
projectId: string,
|
|
506
|
+
deploymentId?: string,
|
|
507
|
+
): Promise<RollbackResponse> {
|
|
508
|
+
const { authFetch } = await import("./auth/index.ts");
|
|
509
|
+
|
|
510
|
+
const body: Record<string, string> = {};
|
|
511
|
+
if (deploymentId) {
|
|
512
|
+
body.deployment_id = deploymentId;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/projects/${projectId}/rollback`, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: { "Content-Type": "application/json" },
|
|
518
|
+
body: JSON.stringify(body),
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
if (!response.ok) {
|
|
522
|
+
const err = (await response.json().catch(() => ({ message: "Unknown error" }))) as {
|
|
523
|
+
message?: string;
|
|
524
|
+
};
|
|
525
|
+
throw new Error(err.message || `Rollback failed: ${response.status}`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return response.json() as Promise<RollbackResponse>;
|
|
529
|
+
}
|
|
530
|
+
|
|
454
531
|
/**
|
|
455
532
|
* Create a resource for a managed project.
|
|
456
533
|
* Uses POST /v1/projects/:id/resources/:type endpoint.
|
|
@@ -715,45 +792,6 @@ export async function getCurrentUserProfile(): Promise<UserProfile | null> {
|
|
|
715
792
|
}
|
|
716
793
|
}
|
|
717
794
|
|
|
718
|
-
export interface SourceSnapshotResponse {
|
|
719
|
-
success: boolean;
|
|
720
|
-
source_key: string;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Upload a source snapshot for a project.
|
|
725
|
-
* Used to enable project forking.
|
|
726
|
-
*/
|
|
727
|
-
export async function uploadSourceSnapshot(
|
|
728
|
-
projectId: string,
|
|
729
|
-
sourceZipPath: string,
|
|
730
|
-
): Promise<SourceSnapshotResponse> {
|
|
731
|
-
const { authFetch } = await import("./auth/index.ts");
|
|
732
|
-
|
|
733
|
-
const formData = new FormData();
|
|
734
|
-
const sourceFile = Bun.file(sourceZipPath);
|
|
735
|
-
formData.append("source", sourceFile);
|
|
736
|
-
|
|
737
|
-
const url = `${getControlApiUrl()}/v1/projects/${projectId}/source`;
|
|
738
|
-
debug(`Source snapshot: ${formatSize(sourceFile.size)}`);
|
|
739
|
-
|
|
740
|
-
const start = Date.now();
|
|
741
|
-
const response = await authFetch(url, {
|
|
742
|
-
method: "POST",
|
|
743
|
-
body: formData,
|
|
744
|
-
});
|
|
745
|
-
debug(`Source snapshot: ${response.status} (${((Date.now() - start) / 1000).toFixed(1)}s)`);
|
|
746
|
-
|
|
747
|
-
if (!response.ok) {
|
|
748
|
-
const error = (await response.json().catch(() => ({ message: "Upload failed" }))) as {
|
|
749
|
-
message?: string;
|
|
750
|
-
};
|
|
751
|
-
throw new Error(error.message || `Source upload failed: ${response.status}`);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return response.json() as Promise<SourceSnapshotResponse>;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
795
|
/**
|
|
758
796
|
* Publish a project to make it forkable by others.
|
|
759
797
|
*/
|
package/src/lib/debug.ts
CHANGED
package/src/lib/deploy-upload.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface DeployUploadOptions {
|
|
|
18
18
|
secretsPath?: string;
|
|
19
19
|
assetsZipPath?: string;
|
|
20
20
|
assetManifest?: AssetManifest;
|
|
21
|
+
message?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface DeployUploadResult {
|
|
@@ -25,6 +26,7 @@ export interface DeployUploadResult {
|
|
|
25
26
|
project_id: string;
|
|
26
27
|
status: "queued" | "building" | "live" | "failed";
|
|
27
28
|
source: string;
|
|
29
|
+
error_message: string | null;
|
|
28
30
|
created_at: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -91,6 +93,10 @@ export async function uploadDeployment(options: DeployUploadOptions): Promise<De
|
|
|
91
93
|
totalSize += manifestJson.length;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
if (options.message) {
|
|
97
|
+
formData.append("message", options.message);
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
const prepareMs = Date.now() - prepareStart;
|
|
95
101
|
debug(`Payload ready: ${formatSize(totalSize)} (${prepareMs}ms)`);
|
|
96
102
|
|
package/src/lib/hooks.ts
CHANGED
|
@@ -702,7 +702,9 @@ const actionHandlers: {
|
|
|
702
702
|
const proc = Bun.spawn(["sh", "-c", command], {
|
|
703
703
|
cwd,
|
|
704
704
|
stdin: interactive ? "inherit" : "ignore",
|
|
705
|
-
stdout
|
|
705
|
+
// In non-interactive mode (MCP), stdout must NOT inherit because it would
|
|
706
|
+
// write into the JSON-RPC stdio transport and corrupt the protocol.
|
|
707
|
+
stdout: interactive ? "inherit" : "pipe",
|
|
706
708
|
stderr: "inherit",
|
|
707
709
|
});
|
|
708
710
|
await proc.exited;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { stat } from "node:fs/promises";
|
|
8
8
|
import { validateBindings } from "./binding-validator.ts";
|
|
9
9
|
import { buildProject, parseWranglerConfig } from "./build-helper.ts";
|
|
10
|
-
import { createManagedProject, syncProjectTags
|
|
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
|
/**
|