@getjack/jack 0.1.31 → 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 +100 -20
- package/src/commands/ship.ts +30 -3
- package/src/commands/tokens.ts +134 -0
- package/src/commands/whoami.ts +51 -12
- package/src/index.ts +33 -0
- package/src/lib/agent-files.ts +54 -4
- package/src/lib/agent-integration.ts +4 -166
- package/src/lib/auth/client.ts +11 -1
- package/src/lib/auth/guard.ts +1 -1
- package/src/lib/auth/store.ts +3 -0
- 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/services/token-operations.ts +84 -0
- package/src/lib/telemetry.ts +6 -0
- package/src/mcp/README.md +1 -1
- package/src/mcp/resources/index.ts +174 -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
package/src/lib/auth/store.ts
CHANGED
|
@@ -51,6 +51,9 @@ export type AuthState = "logged-in" | "not-logged-in" | "session-expired";
|
|
|
51
51
|
* - "session-expired": had credentials but refresh failed
|
|
52
52
|
*/
|
|
53
53
|
export async function getAuthState(): Promise<AuthState> {
|
|
54
|
+
// API token always counts as logged in
|
|
55
|
+
if (process.env.JACK_API_TOKEN) return "logged-in";
|
|
56
|
+
|
|
54
57
|
const creds = await getCredentials();
|
|
55
58
|
if (!creds) return "not-logged-in";
|
|
56
59
|
|
|
@@ -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
|
/**
|
|
@@ -102,6 +102,7 @@ export interface DeployOptions {
|
|
|
102
102
|
managed?: boolean; // Force managed deploy mode
|
|
103
103
|
byo?: boolean; // Force BYO deploy mode
|
|
104
104
|
dryRun?: boolean; // Stop before actual deployment
|
|
105
|
+
message?: string; // Deploy message describing what changed
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
export interface DeployResult {
|
|
@@ -109,6 +110,9 @@ export interface DeployResult {
|
|
|
109
110
|
projectName: string;
|
|
110
111
|
deployOutput?: string;
|
|
111
112
|
deployMode: DeployMode; // The deploy mode used
|
|
113
|
+
deploymentId?: string;
|
|
114
|
+
deployStatus?: string;
|
|
115
|
+
errorMessage?: string | null;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
export interface ProjectStatus {
|
|
@@ -126,6 +130,12 @@ export interface ProjectStatus {
|
|
|
126
130
|
missing: boolean;
|
|
127
131
|
backupFiles: number | null;
|
|
128
132
|
backupLastSync: string | null;
|
|
133
|
+
// Deploy tracking (managed projects only)
|
|
134
|
+
lastDeployAt: string | null;
|
|
135
|
+
deployCount: number;
|
|
136
|
+
lastDeployStatus: string | null;
|
|
137
|
+
lastDeploySource: string | null;
|
|
138
|
+
lastDeployMessage: string | null;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
export interface StaleProject {
|
|
@@ -361,6 +371,7 @@ async function runParallelSetup(
|
|
|
361
371
|
options: {
|
|
362
372
|
template?: string;
|
|
363
373
|
usePrebuilt?: boolean;
|
|
374
|
+
forkedFrom?: string;
|
|
364
375
|
onRemoteReady?: (result: ManagedCreateResult) => void;
|
|
365
376
|
},
|
|
366
377
|
): Promise<{
|
|
@@ -391,6 +402,7 @@ async function runParallelSetup(
|
|
|
391
402
|
const remotePromise = createManagedProjectRemote(projectName, undefined, {
|
|
392
403
|
template: options.template || "hello",
|
|
393
404
|
usePrebuilt: options.usePrebuilt ?? true,
|
|
405
|
+
forkedFrom: options.forkedFrom,
|
|
394
406
|
}).then((result) => {
|
|
395
407
|
const duration = ((Date.now() - remoteStart) / 1000).toFixed(1);
|
|
396
408
|
debug(`Remote project created in ${duration}s (status: ${result.status})`);
|
|
@@ -793,11 +805,25 @@ export async function createProject(
|
|
|
793
805
|
}
|
|
794
806
|
}
|
|
795
807
|
|
|
796
|
-
//
|
|
808
|
+
// Auto-initialize on first run (replaces "Run: jack init" error)
|
|
797
809
|
const { isInitialized } = await import("../commands/init.ts");
|
|
798
810
|
const initialized = await isInitialized();
|
|
799
811
|
if (!initialized) {
|
|
800
|
-
|
|
812
|
+
const { writeConfig, readConfig } = await import("./config.ts");
|
|
813
|
+
const existingConfig = await readConfig();
|
|
814
|
+
await writeConfig({
|
|
815
|
+
version: 1,
|
|
816
|
+
initialized: true,
|
|
817
|
+
initializedAt: existingConfig?.initializedAt || new Date().toISOString(),
|
|
818
|
+
...existingConfig,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// Best-effort MCP config install (non-blocking)
|
|
822
|
+
try {
|
|
823
|
+
const { installMcpConfigsToAllApps, saveMcpConfig } = await import("./mcp-config.ts");
|
|
824
|
+
await installMcpConfigsToAllApps();
|
|
825
|
+
await saveMcpConfig();
|
|
826
|
+
} catch {}
|
|
801
827
|
}
|
|
802
828
|
|
|
803
829
|
// Auth gate - check/prompt for authentication before any work
|
|
@@ -1199,9 +1225,11 @@ export async function createProject(
|
|
|
1199
1225
|
reporter.start("Setting up project...");
|
|
1200
1226
|
|
|
1201
1227
|
try {
|
|
1228
|
+
const forkedFrom = templateOrigin.type !== "builtin" ? templateOrigin.name : undefined;
|
|
1202
1229
|
const result = await runParallelSetup(targetDir, projectName, {
|
|
1203
1230
|
template: resolvedTemplate || "hello",
|
|
1204
1231
|
usePrebuilt: templateOrigin.type === "builtin", // Only builtin templates have prebuilt bundles
|
|
1232
|
+
forkedFrom,
|
|
1205
1233
|
onRemoteReady: (remote) => {
|
|
1206
1234
|
// Show URL immediately when prebuilt succeeds
|
|
1207
1235
|
reporter.stop();
|
|
@@ -1355,22 +1383,8 @@ export async function createProject(
|
|
|
1355
1383
|
reporter.success(`Deployed: ${workerUrl}`);
|
|
1356
1384
|
}
|
|
1357
1385
|
|
|
1358
|
-
//
|
|
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
|
-
}
|
|
1386
|
+
// source.zip is now stored in deployment artifacts by the control plane
|
|
1387
|
+
// during prebuilt deploy — no client-side upload needed
|
|
1374
1388
|
} else {
|
|
1375
1389
|
// Prebuilt not available - fall back to fresh build
|
|
1376
1390
|
if (remoteResult.prebuiltFailed) {
|
|
@@ -1688,11 +1702,10 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1688
1702
|
deployMode = link?.deploy_mode ?? "byo";
|
|
1689
1703
|
}
|
|
1690
1704
|
|
|
1691
|
-
// Ensure agent integration is set up (
|
|
1705
|
+
// Ensure agent integration is set up (MCP config)
|
|
1692
1706
|
// This is idempotent and runs silently
|
|
1693
1707
|
try {
|
|
1694
1708
|
await ensureAgentIntegration(projectPath, {
|
|
1695
|
-
projectName,
|
|
1696
1709
|
silent: true,
|
|
1697
1710
|
});
|
|
1698
1711
|
} catch (err) {
|
|
@@ -1723,6 +1736,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1723
1736
|
|
|
1724
1737
|
let workerUrl: string | null = null;
|
|
1725
1738
|
let deployOutput: string | undefined;
|
|
1739
|
+
let managedDeployResult: { deploymentId: string; status: string; errorMessage: string | null } | undefined;
|
|
1726
1740
|
|
|
1727
1741
|
// Deploy based on mode
|
|
1728
1742
|
if (deployMode === "managed") {
|
|
@@ -1781,7 +1795,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1781
1795
|
}
|
|
1782
1796
|
|
|
1783
1797
|
// deployToManagedProject now handles both template and code deploy
|
|
1784
|
-
await deployToManagedProject(managedProjectId as string, projectPath, reporter);
|
|
1798
|
+
managedDeployResult = await deployToManagedProject(managedProjectId as string, projectPath, reporter, options.message);
|
|
1785
1799
|
|
|
1786
1800
|
// Construct URL with username if available
|
|
1787
1801
|
workerUrl = link?.owner_username
|
|
@@ -1936,6 +1950,9 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1936
1950
|
projectName,
|
|
1937
1951
|
deployOutput: workerUrl ? undefined : deployOutput,
|
|
1938
1952
|
deployMode,
|
|
1953
|
+
deploymentId: managedDeployResult?.deploymentId,
|
|
1954
|
+
deployStatus: managedDeployResult?.status,
|
|
1955
|
+
errorMessage: managedDeployResult?.errorMessage,
|
|
1939
1956
|
};
|
|
1940
1957
|
}
|
|
1941
1958
|
|
|
@@ -2025,11 +2042,35 @@ export async function getProjectStatus(
|
|
|
2025
2042
|
}
|
|
2026
2043
|
}
|
|
2027
2044
|
|
|
2045
|
+
// Fetch real deployment data for managed projects
|
|
2046
|
+
let lastDeployAt: string | null = null;
|
|
2047
|
+
let deployCount = 0;
|
|
2048
|
+
let lastDeployStatus: string | null = null;
|
|
2049
|
+
let lastDeploySource: string | null = null;
|
|
2050
|
+
let lastDeployMessage: string | null = null;
|
|
2051
|
+
|
|
2052
|
+
if (link?.deploy_mode === "managed") {
|
|
2053
|
+
try {
|
|
2054
|
+
const { fetchDeployments } = await import("./control-plane.ts");
|
|
2055
|
+
const result = await fetchDeployments(link.project_id);
|
|
2056
|
+
deployCount = result.total;
|
|
2057
|
+
const latest = result.deployments[0];
|
|
2058
|
+
if (latest) {
|
|
2059
|
+
lastDeployAt = latest.created_at;
|
|
2060
|
+
lastDeployStatus = latest.status;
|
|
2061
|
+
lastDeploySource = latest.source;
|
|
2062
|
+
lastDeployMessage = latest.message;
|
|
2063
|
+
}
|
|
2064
|
+
} catch {
|
|
2065
|
+
// Silent fail — deploy tracking is supplementary
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2028
2069
|
return {
|
|
2029
2070
|
name: projectName,
|
|
2030
2071
|
localPath,
|
|
2031
2072
|
workerUrl,
|
|
2032
|
-
lastDeployed: link?.linked_at ?? null,
|
|
2073
|
+
lastDeployed: lastDeployAt ?? link?.linked_at ?? null,
|
|
2033
2074
|
createdAt: link?.linked_at ?? null,
|
|
2034
2075
|
accountId: null, // No longer stored in registry
|
|
2035
2076
|
workerId: projectName,
|
|
@@ -2040,6 +2081,11 @@ export async function getProjectStatus(
|
|
|
2040
2081
|
missing: false,
|
|
2041
2082
|
backupFiles,
|
|
2042
2083
|
backupLastSync,
|
|
2084
|
+
lastDeployAt,
|
|
2085
|
+
deployCount,
|
|
2086
|
+
lastDeployStatus,
|
|
2087
|
+
lastDeploySource,
|
|
2088
|
+
lastDeployMessage,
|
|
2043
2089
|
};
|
|
2044
2090
|
}
|
|
2045
2091
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token operations service layer for jack cloud
|
|
3
|
+
*
|
|
4
|
+
* Provides shared API token management functions for both CLI and MCP.
|
|
5
|
+
* Returns pure data - no console.log or process.exit.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { authFetch } from "../auth/index.ts";
|
|
9
|
+
import { getControlApiUrl } from "../control-plane.ts";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export interface CreateTokenResult {
|
|
16
|
+
token: string;
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
created_at: string;
|
|
20
|
+
expires_at: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TokenInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
id_prefix: string;
|
|
27
|
+
created_at: string;
|
|
28
|
+
last_used_at: string | null;
|
|
29
|
+
expires_at: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Service Functions
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new API token for headless authentication.
|
|
38
|
+
*/
|
|
39
|
+
export async function createApiToken(
|
|
40
|
+
name: string,
|
|
41
|
+
expiresInDays?: number,
|
|
42
|
+
): Promise<CreateTokenResult> {
|
|
43
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ name, expires_in_days: expiresInDays }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
51
|
+
throw new Error(err.message || `Failed to create token: ${response.status}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return response.json() as Promise<CreateTokenResult>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List all active API tokens for the current user.
|
|
59
|
+
*/
|
|
60
|
+
export async function listApiTokens(): Promise<TokenInfo[]> {
|
|
61
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens`);
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
65
|
+
throw new Error(err.message || `Failed to list tokens: ${response.status}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = (await response.json()) as { tokens: TokenInfo[] };
|
|
69
|
+
return data.tokens;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Revoke an API token by ID.
|
|
74
|
+
*/
|
|
75
|
+
export async function revokeApiToken(tokenId: string): Promise<void> {
|
|
76
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens/${tokenId}`, {
|
|
77
|
+
method: "DELETE",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
82
|
+
throw new Error(err.message || `Failed to revoke token: ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/lib/telemetry.ts
CHANGED
|
@@ -44,6 +44,11 @@ export const Events = {
|
|
|
44
44
|
BYO_DEPLOY_STARTED: "byo_deploy_started",
|
|
45
45
|
BYO_DEPLOY_COMPLETED: "byo_deploy_completed",
|
|
46
46
|
BYO_DEPLOY_FAILED: "byo_deploy_failed",
|
|
47
|
+
// Token management events
|
|
48
|
+
TOKEN_CREATED: "token_created",
|
|
49
|
+
TOKEN_REVOKED: "token_revoked",
|
|
50
|
+
// Hook events
|
|
51
|
+
HOOK_SESSION_CONTEXT: "hook_session_context",
|
|
47
52
|
} as const;
|
|
48
53
|
|
|
49
54
|
type EventName = (typeof Events)[keyof typeof Events];
|
|
@@ -127,6 +132,7 @@ export interface UserProperties {
|
|
|
127
132
|
is_tty?: boolean;
|
|
128
133
|
locale?: string;
|
|
129
134
|
config_style?: "byoc" | "jack-cloud";
|
|
135
|
+
auth_method?: "oauth" | "token";
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
// Detect environment properties (for user profile - stable properties)
|
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
|