@getjack/jack 0.1.28 → 0.1.30
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/cd.ts +163 -0
- package/src/commands/clone.ts +112 -68
- package/src/commands/domain.ts +506 -0
- package/src/commands/domains.ts +215 -0
- package/src/commands/down.ts +18 -12
- package/src/commands/hack.ts +185 -8
- package/src/commands/init.ts +52 -1
- package/src/commands/link.ts +25 -43
- package/src/commands/logs.ts +2 -2
- package/src/commands/mcp.ts +74 -3
- package/src/commands/new.ts +48 -54
- package/src/commands/projects.ts +53 -10
- package/src/commands/secrets.ts +5 -1
- package/src/commands/services.ts +16 -4
- package/src/commands/shell-init.ts +43 -0
- package/src/commands/ship.ts +2 -11
- package/src/commands/skills.ts +335 -0
- package/src/commands/update.ts +31 -0
- package/src/commands/upgrade.ts +14 -0
- package/src/index.ts +116 -24
- package/src/lib/agent-integration.ts +1 -2
- package/src/lib/agents.ts +2 -2
- package/src/lib/auth/login-flow.ts +1 -1
- package/src/lib/clone-core.ts +252 -0
- package/src/lib/config.ts +22 -0
- package/src/lib/control-plane.ts +31 -5
- package/src/lib/fuzzy.ts +93 -0
- package/src/lib/managed-deploy.ts +4 -1
- package/src/lib/managed-down.ts +20 -5
- package/src/lib/output.ts +90 -9
- package/src/lib/picker.ts +406 -0
- package/src/lib/project-detection.ts +5 -2
- package/src/lib/project-list.ts +66 -5
- package/src/lib/project-operations.ts +68 -6
- package/src/lib/prompts.ts +1 -1
- package/src/lib/services/db-execute.ts +8 -1
- package/src/lib/services/db-list.ts +4 -1
- package/src/lib/services/domain-operations.ts +379 -0
- package/src/lib/services/storage-config.ts +1 -5
- package/src/lib/services/storage-delete.ts +1 -1
- package/src/lib/services/storage-info.ts +2 -4
- package/src/lib/services/vectorize-config.ts +1 -5
- package/src/lib/services/vectorize-create.ts +3 -1
- package/src/lib/shell-integration.ts +202 -0
- package/src/lib/telemetry-config.ts +50 -4
- package/src/lib/telemetry.ts +71 -2
- package/src/lib/version-check.ts +1 -3
- package/src/lib/wrangler-config.test.ts +2 -2
- package/src/lib/wrangler-config.ts +1 -1
- package/src/lib/zip-packager.ts +1 -3
- package/src/mcp/tools/index.ts +261 -7
- package/src/templates/index.ts +10 -1
- package/templates/ai-chat/.jack.json +1 -5
- package/templates/ai-chat/public/chat.js +130 -130
- package/templates/ai-chat/src/index.ts +9 -13
- package/templates/ai-chat/src/jack-ai.ts +6 -2
- package/templates/saas/.jack.json +6 -1
- package/templates/saas/src/auth.ts +8 -4
- package/templates/saas/src/client/App.tsx +22 -7
- package/templates/saas/src/client/components/ProtectedRoute.tsx +9 -2
- package/templates/saas/src/client/components/ThemeToggle.tsx +1 -6
- package/templates/saas/src/client/components/ui/accordion.tsx +1 -1
- package/templates/saas/src/client/components/ui/alert-dialog.tsx +2 -2
- package/templates/saas/src/client/components/ui/alert.tsx +2 -2
- package/templates/saas/src/client/components/ui/avatar.tsx +1 -1
- package/templates/saas/src/client/components/ui/badge.tsx +2 -2
- package/templates/saas/src/client/components/ui/breadcrumb.tsx +1 -1
- package/templates/saas/src/client/components/ui/button-group.tsx +2 -2
- package/templates/saas/src/client/components/ui/button.tsx +2 -2
- package/templates/saas/src/client/components/ui/card.tsx +1 -1
- package/templates/saas/src/client/components/ui/carousel.tsx +2 -2
- package/templates/saas/src/client/components/ui/checkbox.tsx +1 -1
- package/templates/saas/src/client/components/ui/command.tsx +2 -2
- package/templates/saas/src/client/components/ui/context-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/dialog.tsx +1 -1
- package/templates/saas/src/client/components/ui/drawer.tsx +1 -1
- package/templates/saas/src/client/components/ui/dropdown-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/empty.tsx +1 -1
- package/templates/saas/src/client/components/ui/field.tsx +2 -2
- package/templates/saas/src/client/components/ui/form.tsx +5 -5
- package/templates/saas/src/client/components/ui/hover-card.tsx +1 -1
- package/templates/saas/src/client/components/ui/input-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/input-otp.tsx +1 -1
- package/templates/saas/src/client/components/ui/input.tsx +1 -1
- package/templates/saas/src/client/components/ui/item.tsx +3 -3
- package/templates/saas/src/client/components/ui/label.tsx +1 -1
- package/templates/saas/src/client/components/ui/menubar.tsx +1 -1
- package/templates/saas/src/client/components/ui/navigation-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/pagination.tsx +2 -2
- package/templates/saas/src/client/components/ui/popover.tsx +1 -1
- package/templates/saas/src/client/components/ui/progress.tsx +1 -1
- package/templates/saas/src/client/components/ui/radio-group.tsx +1 -1
- package/templates/saas/src/client/components/ui/resizable.tsx +1 -1
- package/templates/saas/src/client/components/ui/scroll-area.tsx +1 -1
- package/templates/saas/src/client/components/ui/select.tsx +1 -1
- package/templates/saas/src/client/components/ui/separator.tsx +1 -1
- package/templates/saas/src/client/components/ui/sheet.tsx +1 -1
- package/templates/saas/src/client/components/ui/sidebar.tsx +4 -4
- package/templates/saas/src/client/components/ui/slider.tsx +1 -1
- package/templates/saas/src/client/components/ui/switch.tsx +1 -1
- package/templates/saas/src/client/components/ui/table.tsx +1 -1
- package/templates/saas/src/client/components/ui/tabs.tsx +1 -1
- package/templates/saas/src/client/components/ui/textarea.tsx +1 -1
- package/templates/saas/src/client/components/ui/toggle-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/toggle.tsx +2 -2
- package/templates/saas/src/client/components/ui/tooltip.tsx +1 -1
- package/templates/saas/src/client/hooks/useSubscription.ts +5 -4
- package/templates/saas/src/client/lib/auth-client.ts +1 -1
- package/templates/saas/src/client/lib/plans.ts +1 -6
- package/templates/saas/src/client/lib/utils.ts +1 -1
- package/templates/saas/src/client/main.tsx +1 -1
- package/templates/saas/src/client/pages/DashboardPage.tsx +41 -9
- package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +11 -2
- package/templates/saas/src/client/pages/HomePage.tsx +11 -2
- package/templates/saas/src/client/pages/LoginPage.tsx +11 -2
- package/templates/saas/src/client/pages/PricingPage.tsx +20 -10
- package/templates/saas/src/client/pages/ResetPasswordPage.tsx +14 -11
- package/templates/saas/src/client/pages/SignupPage.tsx +11 -2
- package/templates/saas/src/index.ts +28 -19
- package/templates/saas/vite.config.ts +1 -1
- package/templates/semantic-search/.jack.json +1 -5
- package/templates/semantic-search/src/index.ts +8 -4
- package/templates/semantic-search/src/jack-ai.ts +6 -2
- package/templates/semantic-search/src/jack-vectorize.ts +5 -1
|
@@ -4,12 +4,16 @@ import { join } from "node:path";
|
|
|
4
4
|
import { CONFIG_DIR } from "./config.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Telemetry configuration structure
|
|
7
|
+
* Telemetry configuration structure (v2)
|
|
8
8
|
*/
|
|
9
9
|
export interface TelemetryConfig {
|
|
10
10
|
anonymousId: string; // UUID v4, generated once
|
|
11
11
|
enabled: boolean; // false if user opted out
|
|
12
|
-
version: number; // config schema version (
|
|
12
|
+
version: number; // config schema version (2 for new configs)
|
|
13
|
+
// AARRR tracking fields
|
|
14
|
+
firstSeenAt: string; // ISO date when config was created
|
|
15
|
+
firstDeployAt?: string; // ISO date when first deploy succeeded
|
|
16
|
+
lastIdentifyDate?: string; // "YYYY-MM-DD" for dedupe
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export const TELEMETRY_CONFIG_DIR = CONFIG_DIR;
|
|
@@ -37,13 +41,25 @@ async function ensureTelemetryConfigDir(): Promise<void> {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
|
-
* Create a new telemetry config with generated anonymous ID
|
|
44
|
+
* Create a new telemetry config with generated anonymous ID (v2)
|
|
41
45
|
*/
|
|
42
46
|
function createNewTelemetryConfig(): TelemetryConfig {
|
|
43
47
|
return {
|
|
44
48
|
anonymousId: crypto.randomUUID(),
|
|
45
49
|
enabled: true,
|
|
46
|
-
version:
|
|
50
|
+
version: 2,
|
|
51
|
+
firstSeenAt: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Migrate v1 config to v2 by adding AARRR tracking fields
|
|
57
|
+
*/
|
|
58
|
+
function migrateV1ToV2(config: TelemetryConfig): TelemetryConfig {
|
|
59
|
+
return {
|
|
60
|
+
...config,
|
|
61
|
+
version: 2,
|
|
62
|
+
firstSeenAt: new Date().toISOString(), // Best approximation for existing users
|
|
47
63
|
};
|
|
48
64
|
}
|
|
49
65
|
|
|
@@ -55,6 +71,19 @@ export async function loadOrCreateTelemetryConfig(): Promise<TelemetryConfig> {
|
|
|
55
71
|
const newConfig = createNewTelemetryConfig();
|
|
56
72
|
await ensureTelemetryConfigDir();
|
|
57
73
|
await Bun.write(TELEMETRY_CONFIG_PATH, JSON.stringify(newConfig, null, 2));
|
|
74
|
+
|
|
75
|
+
// Track install event for new users (fire-and-forget)
|
|
76
|
+
queueMicrotask(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const { track, Events } = await import("./telemetry.ts");
|
|
79
|
+
track(Events.USER_INSTALLED, {
|
|
80
|
+
install_date: newConfig.firstSeenAt,
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
// Ignore - telemetry should not break CLI
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
58
87
|
return newConfig;
|
|
59
88
|
}
|
|
60
89
|
|
|
@@ -68,6 +97,12 @@ export async function loadOrCreateTelemetryConfig(): Promise<TelemetryConfig> {
|
|
|
68
97
|
typeof config.enabled === "boolean" &&
|
|
69
98
|
typeof config.version === "number"
|
|
70
99
|
) {
|
|
100
|
+
// Migrate v1 configs to v2
|
|
101
|
+
if (config.version === 1) {
|
|
102
|
+
const migratedConfig = migrateV1ToV2(config as TelemetryConfig);
|
|
103
|
+
await Bun.write(TELEMETRY_CONFIG_PATH, JSON.stringify(migratedConfig, null, 2));
|
|
104
|
+
return migratedConfig;
|
|
105
|
+
}
|
|
71
106
|
return config as TelemetryConfig;
|
|
72
107
|
}
|
|
73
108
|
// Invalid config, regenerate
|
|
@@ -108,3 +143,14 @@ export async function setTelemetryEnabled(enabled: boolean): Promise<void> {
|
|
|
108
143
|
// Update cache
|
|
109
144
|
cachedTelemetryConfig = config;
|
|
110
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Save telemetry config to disk
|
|
149
|
+
*/
|
|
150
|
+
export async function saveTelemetryConfig(config: TelemetryConfig): Promise<void> {
|
|
151
|
+
await ensureTelemetryConfigDir();
|
|
152
|
+
await Bun.write(TELEMETRY_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
153
|
+
|
|
154
|
+
// Update cache
|
|
155
|
+
cachedTelemetryConfig = config;
|
|
156
|
+
}
|
package/src/lib/telemetry.ts
CHANGED
|
@@ -35,7 +35,15 @@ export const Events = {
|
|
|
35
35
|
AUTO_DETECT_REJECTED: "auto_detect_rejected",
|
|
36
36
|
// Service events
|
|
37
37
|
SERVICE_CREATED: "service_created",
|
|
38
|
+
SERVICE_DELETED: "service_deleted",
|
|
38
39
|
SQL_EXECUTED: "sql_executed",
|
|
40
|
+
// AARRR lifecycle events
|
|
41
|
+
USER_INSTALLED: "user_installed",
|
|
42
|
+
USER_ACTIVATED: "user_activated",
|
|
43
|
+
// BYO deploy events (parity with managed)
|
|
44
|
+
BYO_DEPLOY_STARTED: "byo_deploy_started",
|
|
45
|
+
BYO_DEPLOY_COMPLETED: "byo_deploy_completed",
|
|
46
|
+
BYO_DEPLOY_FAILED: "byo_deploy_failed",
|
|
39
47
|
} as const;
|
|
40
48
|
|
|
41
49
|
type EventName = (typeof Events)[keyof typeof Events];
|
|
@@ -121,7 +129,7 @@ export interface UserProperties {
|
|
|
121
129
|
config_style?: "byoc" | "jack-cloud";
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
// Detect environment properties
|
|
132
|
+
// Detect environment properties (for user profile - stable properties)
|
|
125
133
|
export function getEnvironmentProps(): Pick<
|
|
126
134
|
UserProperties,
|
|
127
135
|
"shell" | "terminal" | "terminal_width" | "is_tty" | "locale"
|
|
@@ -135,6 +143,25 @@ export function getEnvironmentProps(): Pick<
|
|
|
135
143
|
};
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
// ============================================
|
|
147
|
+
// INVOCATION CONTEXT (per-event, not per-user)
|
|
148
|
+
// ============================================
|
|
149
|
+
export interface InvocationContext {
|
|
150
|
+
is_tty: boolean;
|
|
151
|
+
is_ci: boolean;
|
|
152
|
+
terminal?: string;
|
|
153
|
+
shell?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getInvocationContext(): InvocationContext {
|
|
157
|
+
return {
|
|
158
|
+
is_tty: process.stdout.isTTY ?? false,
|
|
159
|
+
is_ci: !!process.env.CI,
|
|
160
|
+
terminal: process.env.TERM_PROGRAM,
|
|
161
|
+
shell: process.env.SHELL?.split("/").pop(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
138
165
|
export async function identify(properties: Partial<UserProperties>): Promise<void> {
|
|
139
166
|
userProps = { ...userProps, ...properties };
|
|
140
167
|
if (!(await isEnabled())) return;
|
|
@@ -203,11 +230,42 @@ export async function track(event: EventName, properties?: Record<string, unknow
|
|
|
203
230
|
}
|
|
204
231
|
}
|
|
205
232
|
|
|
233
|
+
export async function trackActivationIfFirst(deployMode: "managed" | "byo"): Promise<void> {
|
|
234
|
+
try {
|
|
235
|
+
const { getTelemetryConfig, saveTelemetryConfig } = await import("./telemetry-config.ts");
|
|
236
|
+
const config = await getTelemetryConfig();
|
|
237
|
+
|
|
238
|
+
// Already activated? Skip
|
|
239
|
+
if (config.firstDeployAt) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const now = new Date();
|
|
244
|
+
const firstSeen = config.firstSeenAt ? new Date(config.firstSeenAt) : now;
|
|
245
|
+
const daysToActivate = Math.floor(
|
|
246
|
+
(now.getTime() - firstSeen.getTime()) / (1000 * 60 * 60 * 24),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Fire activation event
|
|
250
|
+
track(Events.USER_ACTIVATED, {
|
|
251
|
+
deploy_mode: deployMode,
|
|
252
|
+
days_to_activate: daysToActivate,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Save activation timestamp
|
|
256
|
+
config.firstDeployAt = now.toISOString();
|
|
257
|
+
await saveTelemetryConfig(config);
|
|
258
|
+
} catch {
|
|
259
|
+
// Ignore - telemetry should not break CLI
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
206
263
|
// ============================================
|
|
207
264
|
// WRAPPER
|
|
208
265
|
// ============================================
|
|
209
266
|
export interface TelemetryOptions {
|
|
210
267
|
platform?: "cli" | "mcp";
|
|
268
|
+
subcommand?: string;
|
|
211
269
|
}
|
|
212
270
|
|
|
213
271
|
// biome-ignore lint/suspicious/noExplicitAny: Required for flexible command wrapping
|
|
@@ -217,9 +275,16 @@ export function withTelemetry<T extends (...args: any[]) => Promise<any>>(
|
|
|
217
275
|
options?: TelemetryOptions,
|
|
218
276
|
): T {
|
|
219
277
|
const platform = options?.platform ?? "cli";
|
|
278
|
+
const subcommand = options?.subcommand;
|
|
220
279
|
|
|
221
280
|
return (async (...args: Parameters<T>) => {
|
|
222
|
-
|
|
281
|
+
const context = getInvocationContext();
|
|
282
|
+
track(Events.COMMAND_INVOKED, {
|
|
283
|
+
command: commandName,
|
|
284
|
+
platform,
|
|
285
|
+
...(subcommand && { subcommand }),
|
|
286
|
+
...context,
|
|
287
|
+
});
|
|
223
288
|
const start = Date.now();
|
|
224
289
|
|
|
225
290
|
try {
|
|
@@ -227,15 +292,19 @@ export function withTelemetry<T extends (...args: any[]) => Promise<any>>(
|
|
|
227
292
|
track(Events.COMMAND_COMPLETED, {
|
|
228
293
|
command: commandName,
|
|
229
294
|
platform,
|
|
295
|
+
...(subcommand && { subcommand }),
|
|
230
296
|
duration_ms: Date.now() - start,
|
|
297
|
+
...context,
|
|
231
298
|
});
|
|
232
299
|
return result;
|
|
233
300
|
} catch (error) {
|
|
234
301
|
track(Events.COMMAND_FAILED, {
|
|
235
302
|
command: commandName,
|
|
236
303
|
platform,
|
|
304
|
+
...(subcommand && { subcommand }),
|
|
237
305
|
error_type: classifyError(error),
|
|
238
306
|
duration_ms: Date.now() - start,
|
|
307
|
+
...context,
|
|
239
308
|
});
|
|
240
309
|
throw error;
|
|
241
310
|
}
|
package/src/lib/version-check.ts
CHANGED
|
@@ -87,9 +87,7 @@ function isNewerVersion(latest: string, current: string): boolean {
|
|
|
87
87
|
* Returns the latest version if newer, null otherwise
|
|
88
88
|
* @param skipCache - If true, bypass the cache and always fetch from npm
|
|
89
89
|
*/
|
|
90
|
-
export async function checkForUpdate(
|
|
91
|
-
skipCache = false,
|
|
92
|
-
): Promise<string | null> {
|
|
90
|
+
export async function checkForUpdate(skipCache = false): Promise<string | null> {
|
|
93
91
|
const currentVersion = getCurrentVersion();
|
|
94
92
|
const now = Date.now();
|
|
95
93
|
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
8
8
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
9
|
import { tmpdir } from "node:os";
|
|
11
|
-
import {
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { type D1BindingConfig, addD1Binding, getExistingD1Bindings } from "./wrangler-config.ts";
|
|
12
12
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// Test Helpers
|
|
@@ -387,7 +387,7 @@ function findLastObjectEndInArray(content: string, startIndex: number, endIndex:
|
|
|
387
387
|
function shouldAddCommaBefore(content: string): boolean {
|
|
388
388
|
// Strip trailing comments and whitespace to find last meaningful char
|
|
389
389
|
let i = content.length - 1;
|
|
390
|
-
|
|
390
|
+
const inLineComment = false;
|
|
391
391
|
|
|
392
392
|
// First pass: find where any trailing line comment starts
|
|
393
393
|
for (let j = content.length - 1; j >= 0; j--) {
|
package/src/lib/zip-packager.ts
CHANGED
|
@@ -54,9 +54,7 @@ export interface ManifestData {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export
|
|
58
|
-
(current: number, total: number): void;
|
|
59
|
-
}
|
|
57
|
+
export type ZipProgressCallback = (current: number, total: number) => void;
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* Creates a ZIP archive from source directory
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Server as McpServer } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { JackError, JackErrorCode } from "../../lib/errors.ts";
|
|
5
4
|
import { authFetch } from "../../lib/auth/index.ts";
|
|
6
5
|
import { getControlApiUrl, startLogSession } from "../../lib/control-plane.ts";
|
|
6
|
+
import { JackError, JackErrorCode } from "../../lib/errors.ts";
|
|
7
7
|
import { getDeployMode, getProjectId } from "../../lib/project-link.ts";
|
|
8
8
|
import { createProject, deployProject, getProjectStatus } from "../../lib/project-operations.ts";
|
|
9
9
|
import { listAllProjects } from "../../lib/project-resolver.ts";
|
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
wrapResultsForMcp,
|
|
16
16
|
} from "../../lib/services/db-execute.ts";
|
|
17
17
|
import { listDatabases } from "../../lib/services/db-list.ts";
|
|
18
|
+
import {
|
|
19
|
+
assignDomain,
|
|
20
|
+
connectDomain,
|
|
21
|
+
disconnectDomain,
|
|
22
|
+
listDomains,
|
|
23
|
+
unassignDomain,
|
|
24
|
+
} from "../../lib/services/domain-operations.ts";
|
|
18
25
|
import { createStorageBucket } from "../../lib/services/storage-create.ts";
|
|
19
26
|
import { deleteStorageBucket } from "../../lib/services/storage-delete.ts";
|
|
20
27
|
import { getStorageBucketInfo } from "../../lib/services/storage-info.ts";
|
|
@@ -189,6 +196,25 @@ const TailLogsSchema = z.object({
|
|
|
189
196
|
.describe("How long to listen before returning (default: 2000ms, max: 10000ms)"),
|
|
190
197
|
});
|
|
191
198
|
|
|
199
|
+
const ListDomainsSchema = z.object({});
|
|
200
|
+
|
|
201
|
+
const ConnectDomainSchema = z.object({
|
|
202
|
+
hostname: z.string().describe("The domain hostname to connect (e.g., 'app.example.com')"),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const AssignDomainSchema = z.object({
|
|
206
|
+
hostname: z.string().describe("The domain hostname to assign"),
|
|
207
|
+
project_slug: z.string().describe("The project slug to assign the domain to"),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const UnassignDomainSchema = z.object({
|
|
211
|
+
hostname: z.string().describe("The domain hostname to unassign from its project"),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const DisconnectDomainSchema = z.object({
|
|
215
|
+
hostname: z.string().describe("The domain hostname to disconnect (fully remove)"),
|
|
216
|
+
});
|
|
217
|
+
|
|
192
218
|
export function registerTools(server: McpServer, _options: McpServerOptions, debug: DebugLogger) {
|
|
193
219
|
// Register tool list handler
|
|
194
220
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -512,6 +538,79 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
512
538
|
required: ["name"],
|
|
513
539
|
},
|
|
514
540
|
},
|
|
541
|
+
{
|
|
542
|
+
name: "list_domains",
|
|
543
|
+
description:
|
|
544
|
+
"List all custom domains for the current user, including their status and assigned projects.",
|
|
545
|
+
inputSchema: {
|
|
546
|
+
type: "object",
|
|
547
|
+
properties: {},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "connect_domain",
|
|
552
|
+
description:
|
|
553
|
+
"Reserve a custom domain slot. This is the first step before assigning the domain to a project.",
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: "object",
|
|
556
|
+
properties: {
|
|
557
|
+
hostname: {
|
|
558
|
+
type: "string",
|
|
559
|
+
description: "The domain hostname to connect (e.g., 'app.example.com')",
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
required: ["hostname"],
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "assign_domain",
|
|
567
|
+
description:
|
|
568
|
+
"Assign a reserved domain to a project. The domain must be connected first. Returns DNS verification instructions.",
|
|
569
|
+
inputSchema: {
|
|
570
|
+
type: "object",
|
|
571
|
+
properties: {
|
|
572
|
+
hostname: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "The domain hostname to assign",
|
|
575
|
+
},
|
|
576
|
+
project_slug: {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "The project slug to assign the domain to",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
required: ["hostname", "project_slug"],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "unassign_domain",
|
|
586
|
+
description:
|
|
587
|
+
"Unassign a domain from its project, keeping the domain slot reserved for future use.",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: "object",
|
|
590
|
+
properties: {
|
|
591
|
+
hostname: {
|
|
592
|
+
type: "string",
|
|
593
|
+
description: "The domain hostname to unassign from its project",
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
required: ["hostname"],
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: "disconnect_domain",
|
|
601
|
+
description:
|
|
602
|
+
"Fully remove a domain, releasing the slot. Use this when you no longer need the domain.",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
hostname: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "The domain hostname to disconnect (fully remove)",
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
required: ["hostname"],
|
|
612
|
+
},
|
|
613
|
+
},
|
|
515
614
|
],
|
|
516
615
|
};
|
|
517
616
|
});
|
|
@@ -736,7 +835,12 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
736
835
|
|
|
737
836
|
const wrappedTailLogs = withTelemetry(
|
|
738
837
|
"tail_logs",
|
|
739
|
-
async (
|
|
838
|
+
async (
|
|
839
|
+
id: string,
|
|
840
|
+
label: string | undefined,
|
|
841
|
+
maxEvents: number,
|
|
842
|
+
durationMs: number,
|
|
843
|
+
) => {
|
|
740
844
|
const session = await startLogSession(id, label);
|
|
741
845
|
const streamUrl = `${getControlApiUrl()}${session.stream.url}`;
|
|
742
846
|
|
|
@@ -949,10 +1053,7 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
949
1053
|
return {
|
|
950
1054
|
success: false,
|
|
951
1055
|
error: err.message,
|
|
952
|
-
suggestion:
|
|
953
|
-
"Destructive operations (DROP, TRUNCATE, ALTER, DELETE without WHERE) " +
|
|
954
|
-
"must be run via CLI with explicit confirmation: " +
|
|
955
|
-
`jack services db execute "${sql.slice(0, 50)}..." --write`,
|
|
1056
|
+
suggestion: `Destructive operations (DROP, TRUNCATE, ALTER, DELETE without WHERE) must be run via CLI with explicit confirmation: jack services db execute "${sql.slice(0, 50)}..." --write`,
|
|
956
1057
|
risk_level: "destructive",
|
|
957
1058
|
};
|
|
958
1059
|
}
|
|
@@ -1250,7 +1351,9 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
1250
1351
|
if (!info) {
|
|
1251
1352
|
throw new JackError(
|
|
1252
1353
|
JackErrorCode.RESOURCE_NOT_FOUND,
|
|
1253
|
-
bucketName
|
|
1354
|
+
bucketName
|
|
1355
|
+
? `Storage bucket '${bucketName}' not found`
|
|
1356
|
+
: "No storage buckets found",
|
|
1254
1357
|
"Use list_storage_buckets to see available buckets or create_storage_bucket to create one",
|
|
1255
1358
|
);
|
|
1256
1359
|
}
|
|
@@ -1312,6 +1415,157 @@ export function registerTools(server: McpServer, _options: McpServerOptions, deb
|
|
|
1312
1415
|
};
|
|
1313
1416
|
}
|
|
1314
1417
|
|
|
1418
|
+
case "list_domains": {
|
|
1419
|
+
ListDomainsSchema.parse(request.params.arguments ?? {});
|
|
1420
|
+
|
|
1421
|
+
const wrappedListDomains = withTelemetry(
|
|
1422
|
+
"list_domains",
|
|
1423
|
+
async () => {
|
|
1424
|
+
const result = await listDomains();
|
|
1425
|
+
return {
|
|
1426
|
+
domains: result.domains.map((d) => ({
|
|
1427
|
+
id: d.id,
|
|
1428
|
+
hostname: d.hostname,
|
|
1429
|
+
status: d.status,
|
|
1430
|
+
ssl_status: d.ssl_status,
|
|
1431
|
+
project_id: d.project_id,
|
|
1432
|
+
project_slug: d.project_slug,
|
|
1433
|
+
created_at: d.created_at,
|
|
1434
|
+
})),
|
|
1435
|
+
slots: result.slots,
|
|
1436
|
+
};
|
|
1437
|
+
},
|
|
1438
|
+
{ platform: "mcp" },
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1441
|
+
const result = await wrappedListDomains();
|
|
1442
|
+
|
|
1443
|
+
return {
|
|
1444
|
+
content: [
|
|
1445
|
+
{
|
|
1446
|
+
type: "text",
|
|
1447
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1448
|
+
},
|
|
1449
|
+
],
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
case "connect_domain": {
|
|
1454
|
+
const args = ConnectDomainSchema.parse(request.params.arguments ?? {});
|
|
1455
|
+
|
|
1456
|
+
const wrappedConnectDomain = withTelemetry(
|
|
1457
|
+
"connect_domain",
|
|
1458
|
+
async (hostname: string) => {
|
|
1459
|
+
const result = await connectDomain(hostname);
|
|
1460
|
+
return {
|
|
1461
|
+
id: result.id,
|
|
1462
|
+
hostname: result.hostname,
|
|
1463
|
+
status: result.status,
|
|
1464
|
+
};
|
|
1465
|
+
},
|
|
1466
|
+
{ platform: "mcp" },
|
|
1467
|
+
);
|
|
1468
|
+
|
|
1469
|
+
const result = await wrappedConnectDomain(args.hostname);
|
|
1470
|
+
|
|
1471
|
+
return {
|
|
1472
|
+
content: [
|
|
1473
|
+
{
|
|
1474
|
+
type: "text",
|
|
1475
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1476
|
+
},
|
|
1477
|
+
],
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
case "assign_domain": {
|
|
1482
|
+
const args = AssignDomainSchema.parse(request.params.arguments ?? {});
|
|
1483
|
+
|
|
1484
|
+
const wrappedAssignDomain = withTelemetry(
|
|
1485
|
+
"assign_domain",
|
|
1486
|
+
async (hostname: string, projectSlug: string) => {
|
|
1487
|
+
const result = await assignDomain(hostname, projectSlug);
|
|
1488
|
+
return {
|
|
1489
|
+
id: result.id,
|
|
1490
|
+
hostname: result.hostname,
|
|
1491
|
+
status: result.status,
|
|
1492
|
+
ssl_status: result.ssl_status,
|
|
1493
|
+
project_id: result.project_id,
|
|
1494
|
+
project_slug: result.project_slug,
|
|
1495
|
+
verification: result.verification,
|
|
1496
|
+
ownership_verification: result.ownership_verification,
|
|
1497
|
+
};
|
|
1498
|
+
},
|
|
1499
|
+
{ platform: "mcp" },
|
|
1500
|
+
);
|
|
1501
|
+
|
|
1502
|
+
const result = await wrappedAssignDomain(args.hostname, args.project_slug);
|
|
1503
|
+
|
|
1504
|
+
return {
|
|
1505
|
+
content: [
|
|
1506
|
+
{
|
|
1507
|
+
type: "text",
|
|
1508
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1509
|
+
},
|
|
1510
|
+
],
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
case "unassign_domain": {
|
|
1515
|
+
const args = UnassignDomainSchema.parse(request.params.arguments ?? {});
|
|
1516
|
+
|
|
1517
|
+
const wrappedUnassignDomain = withTelemetry(
|
|
1518
|
+
"unassign_domain",
|
|
1519
|
+
async (hostname: string) => {
|
|
1520
|
+
const result = await unassignDomain(hostname);
|
|
1521
|
+
return {
|
|
1522
|
+
id: result.id,
|
|
1523
|
+
hostname: result.hostname,
|
|
1524
|
+
status: result.status,
|
|
1525
|
+
};
|
|
1526
|
+
},
|
|
1527
|
+
{ platform: "mcp" },
|
|
1528
|
+
);
|
|
1529
|
+
|
|
1530
|
+
const result = await wrappedUnassignDomain(args.hostname);
|
|
1531
|
+
|
|
1532
|
+
return {
|
|
1533
|
+
content: [
|
|
1534
|
+
{
|
|
1535
|
+
type: "text",
|
|
1536
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1537
|
+
},
|
|
1538
|
+
],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
case "disconnect_domain": {
|
|
1543
|
+
const args = DisconnectDomainSchema.parse(request.params.arguments ?? {});
|
|
1544
|
+
|
|
1545
|
+
const wrappedDisconnectDomain = withTelemetry(
|
|
1546
|
+
"disconnect_domain",
|
|
1547
|
+
async (hostname: string) => {
|
|
1548
|
+
const result = await disconnectDomain(hostname);
|
|
1549
|
+
return {
|
|
1550
|
+
success: result.success,
|
|
1551
|
+
hostname: result.hostname,
|
|
1552
|
+
};
|
|
1553
|
+
},
|
|
1554
|
+
{ platform: "mcp" },
|
|
1555
|
+
);
|
|
1556
|
+
|
|
1557
|
+
const result = await wrappedDisconnectDomain(args.hostname);
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
content: [
|
|
1561
|
+
{
|
|
1562
|
+
type: "text",
|
|
1563
|
+
text: JSON.stringify(formatSuccessResponse(result, startTime), null, 2),
|
|
1564
|
+
},
|
|
1565
|
+
],
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1315
1569
|
default:
|
|
1316
1570
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
1317
1571
|
}
|
package/src/templates/index.ts
CHANGED
|
@@ -10,7 +10,16 @@ import type { Template } from "./types";
|
|
|
10
10
|
// Resolve templates directory relative to this file (src/templates -> templates)
|
|
11
11
|
const TEMPLATES_DIR = join(dirname(dirname(import.meta.dir)), "templates");
|
|
12
12
|
|
|
13
|
-
export const BUILTIN_TEMPLATES = [
|
|
13
|
+
export const BUILTIN_TEMPLATES = [
|
|
14
|
+
"hello",
|
|
15
|
+
"miniapp",
|
|
16
|
+
"api",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"saas",
|
|
19
|
+
"simple-api-starter",
|
|
20
|
+
"ai-chat",
|
|
21
|
+
"semantic-search",
|
|
22
|
+
];
|
|
14
23
|
|
|
15
24
|
/**
|
|
16
25
|
* Resolved template with origin tracking for lineage
|