@getjack/jack 0.1.0 → 0.1.1
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/README.md +16 -0
- package/package.json +47 -39
- package/src/commands/agents.ts +40 -9
- package/src/commands/cloud.ts +8 -4
- package/src/commands/down.ts +120 -69
- package/src/commands/init.ts +41 -3
- package/src/commands/mcp.ts +18 -0
- package/src/commands/new.ts +64 -334
- package/src/commands/projects.ts +139 -143
- package/src/commands/services.ts +315 -0
- package/src/commands/ship.ts +33 -139
- package/src/index.ts +27 -3
- package/src/lib/agent-files.ts +0 -41
- package/src/lib/agents.ts +238 -64
- package/src/lib/cloudflare-api.ts +3 -2
- package/src/lib/config.ts +8 -0
- package/src/lib/errors.ts +53 -0
- package/src/lib/hooks.ts +93 -41
- package/src/lib/mcp-config.ts +175 -0
- package/src/lib/project-operations.ts +793 -0
- package/src/lib/prompts.ts +15 -7
- package/src/lib/registry.ts +29 -1
- package/src/lib/services/db.ts +81 -0
- package/src/lib/services/index.ts +27 -0
- package/src/lib/telemetry.ts +10 -1
- package/src/mcp/README.md +142 -0
- package/src/mcp/resources/index.ts +87 -0
- package/src/mcp/server.ts +32 -0
- package/src/mcp/tools/index.ts +261 -0
- package/src/mcp/types.ts +29 -0
- package/src/mcp/utils.ts +147 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/types.ts +16 -8
- package/templates/CLAUDE.md +105 -4
- package/templates/api/.jack.json +20 -1
- package/templates/api/src/index.ts +1 -1
- package/templates/miniapp/.jack.json +7 -5
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { select } from "@inquirer/prompts";
|
|
4
|
+
import { formatSize } from "../lib/format.ts";
|
|
5
|
+
import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
|
|
6
|
+
import {
|
|
7
|
+
type Project,
|
|
8
|
+
getProject,
|
|
9
|
+
getProjectDatabaseName,
|
|
10
|
+
updateProjectDatabase,
|
|
11
|
+
} from "../lib/registry.ts";
|
|
12
|
+
import {
|
|
13
|
+
deleteDatabase,
|
|
14
|
+
exportDatabase,
|
|
15
|
+
generateExportFilename,
|
|
16
|
+
getDatabaseInfo,
|
|
17
|
+
} from "../lib/services/db.ts";
|
|
18
|
+
import { getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get database name from wrangler.jsonc/toml file
|
|
22
|
+
* Fallback when registry doesn't have the info
|
|
23
|
+
*/
|
|
24
|
+
async function getDatabaseFromWranglerConfig(projectPath: string): Promise<string | null> {
|
|
25
|
+
// Try wrangler.jsonc first
|
|
26
|
+
const jsoncPath = join(projectPath, "wrangler.jsonc");
|
|
27
|
+
if (existsSync(jsoncPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await Bun.file(jsoncPath).text();
|
|
30
|
+
// Remove comments for JSON parsing (simple approach)
|
|
31
|
+
const jsonContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
32
|
+
const config = JSON.parse(jsonContent);
|
|
33
|
+
if (config.d1_databases?.[0]?.database_name) {
|
|
34
|
+
return config.d1_databases[0].database_name;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Ignore parse errors
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Try wrangler.toml
|
|
42
|
+
const tomlPath = join(projectPath, "wrangler.toml");
|
|
43
|
+
if (existsSync(tomlPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const content = await Bun.file(tomlPath).text();
|
|
46
|
+
// Simple regex to extract database_name from [[d1_databases]] section
|
|
47
|
+
const match = content.match(/database_name\s*=\s*"([^"]+)"/);
|
|
48
|
+
if (match?.[1]) {
|
|
49
|
+
return match[1];
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore read errors
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get database name for a project, with fallback to wrangler config
|
|
61
|
+
*/
|
|
62
|
+
async function resolveDbName(project: Project): Promise<string | null> {
|
|
63
|
+
// First check registry
|
|
64
|
+
const dbFromRegistry = getProjectDatabaseName(project);
|
|
65
|
+
if (dbFromRegistry) {
|
|
66
|
+
return dbFromRegistry;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Fallback: read from wrangler config file
|
|
70
|
+
if (project.localPath && existsSync(project.localPath)) {
|
|
71
|
+
return await getDatabaseFromWranglerConfig(project.localPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface ServiceOptions {
|
|
78
|
+
project?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default async function services(
|
|
82
|
+
subcommand?: string,
|
|
83
|
+
args: string[] = [],
|
|
84
|
+
options: ServiceOptions = {},
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
if (!subcommand) {
|
|
87
|
+
return showHelp();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
switch (subcommand) {
|
|
91
|
+
case "db":
|
|
92
|
+
return await dbCommand(args, options);
|
|
93
|
+
default:
|
|
94
|
+
error(`Unknown service: ${subcommand}`);
|
|
95
|
+
info("Available: db");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function showHelp(): void {
|
|
101
|
+
console.error("");
|
|
102
|
+
info("jack services - Manage project services");
|
|
103
|
+
console.error("");
|
|
104
|
+
console.error("Commands:");
|
|
105
|
+
console.error(" db Manage database");
|
|
106
|
+
console.error("");
|
|
107
|
+
console.error("Run 'jack services <command>' for more information.");
|
|
108
|
+
console.error("");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function dbCommand(args: string[], options: ServiceOptions): Promise<void> {
|
|
112
|
+
const action = args[0] || "info"; // Default to info
|
|
113
|
+
|
|
114
|
+
switch (action) {
|
|
115
|
+
case "info":
|
|
116
|
+
return await dbInfo(options);
|
|
117
|
+
case "export":
|
|
118
|
+
return await dbExport(options);
|
|
119
|
+
case "delete":
|
|
120
|
+
return await dbDelete(options);
|
|
121
|
+
default:
|
|
122
|
+
error(`Unknown action: ${action}`);
|
|
123
|
+
info("Available: info, export, delete");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve project name from options or current directory
|
|
130
|
+
*/
|
|
131
|
+
async function resolveProjectName(options: ServiceOptions): Promise<string> {
|
|
132
|
+
if (options.project) {
|
|
133
|
+
return options.project;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
return await getProjectNameFromDir(process.cwd());
|
|
138
|
+
} catch {
|
|
139
|
+
error("Could not determine project");
|
|
140
|
+
info("Run from a project directory, or use --project <name>");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Show database information
|
|
147
|
+
*/
|
|
148
|
+
async function dbInfo(options: ServiceOptions): Promise<void> {
|
|
149
|
+
const projectName = await resolveProjectName(options);
|
|
150
|
+
const project = await getProject(projectName);
|
|
151
|
+
|
|
152
|
+
if (!project) {
|
|
153
|
+
error(`Project "${projectName}" not found in registry`);
|
|
154
|
+
info("List projects with: jack projects list");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const dbName = await resolveDbName(project);
|
|
159
|
+
|
|
160
|
+
if (!dbName) {
|
|
161
|
+
console.error("");
|
|
162
|
+
info("No database configured for this project");
|
|
163
|
+
console.error("");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fetch database info
|
|
168
|
+
outputSpinner.start("Fetching database info...");
|
|
169
|
+
const dbInfo = await getDatabaseInfo(dbName);
|
|
170
|
+
outputSpinner.stop();
|
|
171
|
+
|
|
172
|
+
if (!dbInfo) {
|
|
173
|
+
console.error("");
|
|
174
|
+
error("Database not found");
|
|
175
|
+
info("It may have been deleted");
|
|
176
|
+
console.error("");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Display info
|
|
181
|
+
console.error("");
|
|
182
|
+
success(`Database: ${dbInfo.name}`);
|
|
183
|
+
console.error("");
|
|
184
|
+
item(`Size: ${formatSize(dbInfo.sizeBytes)}`);
|
|
185
|
+
item(`Tables: ${dbInfo.numTables}`);
|
|
186
|
+
item(`ID: ${dbInfo.id}`);
|
|
187
|
+
console.error("");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Export database to SQL file
|
|
192
|
+
*/
|
|
193
|
+
async function dbExport(options: ServiceOptions): Promise<void> {
|
|
194
|
+
const projectName = await resolveProjectName(options);
|
|
195
|
+
const project = await getProject(projectName);
|
|
196
|
+
|
|
197
|
+
if (!project) {
|
|
198
|
+
error(`Project "${projectName}" not found in registry`);
|
|
199
|
+
info("List projects with: jack projects list");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const dbName = await resolveDbName(project);
|
|
204
|
+
|
|
205
|
+
if (!dbName) {
|
|
206
|
+
console.error("");
|
|
207
|
+
info("No database configured for this project");
|
|
208
|
+
console.error("");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Generate filename
|
|
213
|
+
const filename = generateExportFilename(dbName);
|
|
214
|
+
|
|
215
|
+
// Determine output directory (project dir if in it, cwd otherwise)
|
|
216
|
+
let outputDir = process.cwd();
|
|
217
|
+
if (project.localPath && existsSync(project.localPath)) {
|
|
218
|
+
// Check if we're in the project directory or subdirectory
|
|
219
|
+
const cwd = process.cwd();
|
|
220
|
+
if (cwd === project.localPath || cwd.startsWith(`${project.localPath}/`)) {
|
|
221
|
+
outputDir = project.localPath;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const outputPath = join(outputDir, filename);
|
|
226
|
+
|
|
227
|
+
// Export
|
|
228
|
+
outputSpinner.start("Exporting database...");
|
|
229
|
+
try {
|
|
230
|
+
await exportDatabase(dbName, outputPath);
|
|
231
|
+
outputSpinner.stop();
|
|
232
|
+
|
|
233
|
+
console.error("");
|
|
234
|
+
success(`Exported to ./${filename}`);
|
|
235
|
+
console.error("");
|
|
236
|
+
} catch (err) {
|
|
237
|
+
outputSpinner.stop();
|
|
238
|
+
console.error("");
|
|
239
|
+
error(`Failed to export: ${err instanceof Error ? err.message : String(err)}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Delete database with full cleanup
|
|
246
|
+
*/
|
|
247
|
+
async function dbDelete(options: ServiceOptions): Promise<void> {
|
|
248
|
+
const projectName = await resolveProjectName(options);
|
|
249
|
+
const project = await getProject(projectName);
|
|
250
|
+
|
|
251
|
+
if (!project) {
|
|
252
|
+
error(`Project "${projectName}" not found in registry`);
|
|
253
|
+
info("List projects with: jack projects list");
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const dbName = await resolveDbName(project);
|
|
258
|
+
|
|
259
|
+
if (!dbName) {
|
|
260
|
+
console.error("");
|
|
261
|
+
info("No database configured for this project");
|
|
262
|
+
console.error("");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Get database info to show what will be deleted
|
|
267
|
+
outputSpinner.start("Fetching database info...");
|
|
268
|
+
const dbInfo = await getDatabaseInfo(dbName);
|
|
269
|
+
outputSpinner.stop();
|
|
270
|
+
|
|
271
|
+
// Show what will be deleted
|
|
272
|
+
console.error("");
|
|
273
|
+
info(`Database: ${dbName}`);
|
|
274
|
+
if (dbInfo) {
|
|
275
|
+
item(`Size: ${formatSize(dbInfo.sizeBytes)}`);
|
|
276
|
+
item(`Tables: ${dbInfo.numTables}`);
|
|
277
|
+
}
|
|
278
|
+
console.error("");
|
|
279
|
+
warn("This will permanently delete the database and all its data");
|
|
280
|
+
console.error("");
|
|
281
|
+
|
|
282
|
+
// Confirm deletion
|
|
283
|
+
console.error(" Esc to skip\n");
|
|
284
|
+
const action = await select({
|
|
285
|
+
message: `Delete database '${dbName}'?`,
|
|
286
|
+
choices: [
|
|
287
|
+
{ name: "1. Yes", value: "yes" },
|
|
288
|
+
{ name: "2. No", value: "no" },
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (action === "no") {
|
|
293
|
+
info("Cancelled");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Delete database
|
|
298
|
+
outputSpinner.start("Deleting database...");
|
|
299
|
+
try {
|
|
300
|
+
await deleteDatabase(dbName);
|
|
301
|
+
outputSpinner.stop();
|
|
302
|
+
|
|
303
|
+
// Update registry (set db to null in services structure)
|
|
304
|
+
await updateProjectDatabase(projectName, null);
|
|
305
|
+
|
|
306
|
+
console.error("");
|
|
307
|
+
success("Database deleted");
|
|
308
|
+
console.error("");
|
|
309
|
+
} catch (err) {
|
|
310
|
+
outputSpinner.stop();
|
|
311
|
+
console.error("");
|
|
312
|
+
error(`Failed to delete: ${err instanceof Error ? err.message : String(err)}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
}
|
package/src/commands/ship.ts
CHANGED
|
@@ -1,150 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getSyncConfig } from "../lib/config.ts";
|
|
5
|
-
import { detectSecrets } from "../lib/env-parser.ts";
|
|
6
|
-
import { error, info, spinner, success, warn } from "../lib/output.ts";
|
|
7
|
-
import { filterNewSecrets, promptSaveSecrets } from "../lib/prompts.ts";
|
|
8
|
-
import { applySchema, getD1DatabaseName, hasD1Config } from "../lib/schema.ts";
|
|
9
|
-
import { getProjectNameFromDir, syncToCloud } from "../lib/storage/index.ts";
|
|
10
|
-
|
|
11
|
-
function hasWranglerConfig(): boolean {
|
|
12
|
-
return (
|
|
13
|
-
existsSync("./wrangler.toml") || existsSync("./wrangler.jsonc") || existsSync("./wrangler.json")
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function isViteProject(): boolean {
|
|
18
|
-
return (
|
|
19
|
-
existsSync("./vite.config.ts") ||
|
|
20
|
-
existsSync("./vite.config.js") ||
|
|
21
|
-
existsSync("./vite.config.mjs")
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function getProjectName(): Promise<string> {
|
|
26
|
-
const configPaths = ["./wrangler.jsonc", "./wrangler.json", "./wrangler.toml"];
|
|
27
|
-
|
|
28
|
-
for (const configPath of configPaths) {
|
|
29
|
-
if (existsSync(configPath)) {
|
|
30
|
-
try {
|
|
31
|
-
const content = await Bun.file(configPath).text();
|
|
32
|
-
// For JSON/JSONC - strip comments and parse
|
|
33
|
-
if (configPath.endsWith(".json") || configPath.endsWith(".jsonc")) {
|
|
34
|
-
const cleaned = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
35
|
-
const config = JSON.parse(cleaned);
|
|
36
|
-
if (config.name) return config.name;
|
|
37
|
-
}
|
|
38
|
-
// For TOML
|
|
39
|
-
if (configPath.endsWith(".toml")) {
|
|
40
|
-
const match = content.match(/name\s*=\s*"([^"]+)"/);
|
|
41
|
-
if (match?.[1]) return match[1];
|
|
42
|
-
}
|
|
43
|
-
} catch {}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Fallback to directory name
|
|
48
|
-
return basename(process.cwd());
|
|
49
|
-
}
|
|
1
|
+
import { getErrorDetails } from "../lib/errors.ts";
|
|
2
|
+
import { output, spinner } from "../lib/output.ts";
|
|
3
|
+
import { deployProject } from "../lib/project-operations.ts";
|
|
50
4
|
|
|
51
5
|
export default async function ship(): Promise<void> {
|
|
52
|
-
|
|
53
|
-
error("No wrangler config found in current directory");
|
|
54
|
-
error("Run: jack new <project-name>");
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// For Vite projects, build first
|
|
59
|
-
if (isViteProject()) {
|
|
60
|
-
const buildSpin = spinner("Building...");
|
|
61
|
-
const buildResult = await $`npx vite build`.nothrow().quiet();
|
|
62
|
-
|
|
63
|
-
if (buildResult.exitCode !== 0) {
|
|
64
|
-
buildSpin.error("Build failed");
|
|
65
|
-
console.error(buildResult.stderr.toString());
|
|
66
|
-
process.exit(buildResult.exitCode);
|
|
67
|
-
}
|
|
68
|
-
buildSpin.success("Built");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const spin = spinner("Deploying...");
|
|
72
|
-
|
|
73
|
-
const result = await $`wrangler deploy`.nothrow().quiet();
|
|
74
|
-
|
|
75
|
-
if (result.exitCode !== 0) {
|
|
76
|
-
spin.error("Deploy failed");
|
|
77
|
-
console.error(result.stderr.toString());
|
|
78
|
-
process.exit(result.exitCode);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Parse URL from output
|
|
82
|
-
const output = result.stdout.toString();
|
|
83
|
-
const urlMatch = output.match(/https:\/\/[\w-]+\.[\w-]+\.workers\.dev/);
|
|
84
|
-
|
|
85
|
-
if (urlMatch) {
|
|
86
|
-
spin.success(`Live: ${urlMatch[0]}`);
|
|
87
|
-
} else {
|
|
88
|
-
spin.success("Deployed");
|
|
89
|
-
console.error(output);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Update registry after successful deploy
|
|
6
|
+
const isCi = process.env.CI === "true" || process.env.CI === "1";
|
|
93
7
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
8
|
+
const result = await deployProject({
|
|
9
|
+
projectPath: process.cwd(),
|
|
10
|
+
reporter: {
|
|
11
|
+
start: output.start,
|
|
12
|
+
stop: output.stop,
|
|
13
|
+
spinner,
|
|
14
|
+
info: output.info,
|
|
15
|
+
warn: output.warn,
|
|
16
|
+
error: output.error,
|
|
17
|
+
success: output.success,
|
|
18
|
+
box: output.box,
|
|
19
|
+
},
|
|
20
|
+
interactive: !isCi,
|
|
21
|
+
includeSecrets: true,
|
|
22
|
+
includeSync: true,
|
|
101
23
|
});
|
|
102
|
-
} catch {
|
|
103
|
-
// Don't fail the deploy if registry update fails
|
|
104
|
-
}
|
|
105
24
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
} catch (err) {
|
|
114
|
-
warn(`Schema application failed: ${err}`);
|
|
115
|
-
info("Run manually: bun run db:migrate");
|
|
116
|
-
}
|
|
25
|
+
if (!result.workerUrl && result.deployOutput) {
|
|
26
|
+
console.error(result.deployOutput);
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const details = getErrorDetails(error);
|
|
30
|
+
if (!details.meta?.reported) {
|
|
31
|
+
output.error(details.message);
|
|
117
32
|
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Detect and offer to save secrets
|
|
121
|
-
const detected = await detectSecrets();
|
|
122
|
-
const newSecrets = await filterNewSecrets(detected);
|
|
123
33
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
34
|
+
if (details.meta?.stderr) {
|
|
35
|
+
console.error(details.meta.stderr);
|
|
36
|
+
}
|
|
127
37
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (syncConfig.enabled && syncConfig.autoSync) {
|
|
131
|
-
const syncSpin = spinner("Syncing source to cloud...");
|
|
132
|
-
try {
|
|
133
|
-
const projectName = await getProjectNameFromDir(process.cwd());
|
|
134
|
-
const result = await syncToCloud(process.cwd());
|
|
135
|
-
if (result.success) {
|
|
136
|
-
if (result.filesUploaded > 0 || result.filesDeleted > 0) {
|
|
137
|
-
syncSpin.success(
|
|
138
|
-
`Backed up ${result.filesUploaded} files to jack-storage/${projectName}/`,
|
|
139
|
-
);
|
|
140
|
-
} else {
|
|
141
|
-
syncSpin.success("Source already synced");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
} catch (err) {
|
|
145
|
-
syncSpin.stop();
|
|
146
|
-
warn("Cloud sync failed (deploy succeeded)");
|
|
147
|
-
info("Run: jack sync");
|
|
38
|
+
if (details.suggestion && !details.meta?.reported) {
|
|
39
|
+
output.info(details.suggestion);
|
|
148
40
|
}
|
|
41
|
+
|
|
42
|
+
process.exit(details.meta?.exitCode ?? 1);
|
|
149
43
|
}
|
|
150
44
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,9 +20,11 @@ const cli = meow(
|
|
|
20
20
|
sync Sync to cloud storage
|
|
21
21
|
clone <project> Pull project from cloud
|
|
22
22
|
cloud Manage cloud storage
|
|
23
|
-
down [name]
|
|
23
|
+
down [name] Undeploy from cloud
|
|
24
24
|
open [name] Open project in browser
|
|
25
25
|
projects Manage project registry
|
|
26
|
+
services Manage project services
|
|
27
|
+
mcp serve Start MCP server for AI agents
|
|
26
28
|
telemetry Manage anonymous usage data
|
|
27
29
|
about The story behind jack
|
|
28
30
|
|
|
@@ -37,12 +39,13 @@ const cli = meow(
|
|
|
37
39
|
--dry-run Preview changes without applying
|
|
38
40
|
--force Force operation
|
|
39
41
|
--as <name> Clone to different directory name
|
|
40
|
-
--dash Open
|
|
42
|
+
--dash Open cloud dashboard
|
|
41
43
|
--logs Open logs page
|
|
42
44
|
--yes Skip confirmation prompts
|
|
43
45
|
--local Filter by local projects
|
|
44
46
|
--deployed Filter by deployed projects
|
|
45
47
|
--cloud Filter by cloud-backed projects
|
|
48
|
+
--skip-mcp Skip MCP config installation during init
|
|
46
49
|
|
|
47
50
|
Examples
|
|
48
51
|
$ jack init Set up once
|
|
@@ -100,6 +103,14 @@ const cli = meow(
|
|
|
100
103
|
type: "boolean",
|
|
101
104
|
default: false,
|
|
102
105
|
},
|
|
106
|
+
project: {
|
|
107
|
+
type: "string",
|
|
108
|
+
shortFlag: "p",
|
|
109
|
+
},
|
|
110
|
+
skipMcp: {
|
|
111
|
+
type: "boolean",
|
|
112
|
+
default: false,
|
|
113
|
+
},
|
|
103
114
|
},
|
|
104
115
|
},
|
|
105
116
|
);
|
|
@@ -123,7 +134,7 @@ identify({
|
|
|
123
134
|
switch (command) {
|
|
124
135
|
case "init": {
|
|
125
136
|
const { default: init } = await import("./commands/init.ts");
|
|
126
|
-
await withTelemetry("init", init)();
|
|
137
|
+
await withTelemetry("init", init)({ skipMcp: cli.flags.skipMcp });
|
|
127
138
|
break;
|
|
128
139
|
}
|
|
129
140
|
case "new":
|
|
@@ -207,6 +218,19 @@ switch (command) {
|
|
|
207
218
|
await withTelemetry("projects", projects)(args[0], projectArgs);
|
|
208
219
|
break;
|
|
209
220
|
}
|
|
221
|
+
case "services": {
|
|
222
|
+
const { default: services } = await import("./commands/services.ts");
|
|
223
|
+
await withTelemetry("services", services)(args[0], args.slice(1), {
|
|
224
|
+
project: cli.flags.project,
|
|
225
|
+
});
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "mcp": {
|
|
229
|
+
const { default: mcp } = await import("./commands/mcp.ts");
|
|
230
|
+
// Note: Don't use withTelemetry wrapper for MCP serve - it runs indefinitely
|
|
231
|
+
await mcp(args[0], { project: cli.flags.project });
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
210
234
|
case "ls": {
|
|
211
235
|
const { default: projects } = await import("./commands/projects.ts");
|
|
212
236
|
const lsArgs: string[] = [];
|
package/src/lib/agent-files.ts
CHANGED
|
@@ -50,43 +50,6 @@ Never run \`wrangler\` commands directly. All deployment is handled by jack.
|
|
|
50
50
|
`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
* Template for .cursorrules
|
|
55
|
-
*/
|
|
56
|
-
function generateCursorrules(): string {
|
|
57
|
-
return `# Cursor Rules
|
|
58
|
-
|
|
59
|
-
See AGENTS.md for project context and deployment instructions.
|
|
60
|
-
|
|
61
|
-
## Deployment Commands
|
|
62
|
-
|
|
63
|
-
- Deploy: \`jack ship\`
|
|
64
|
-
- Logs: \`jack logs\`
|
|
65
|
-
- Dev server: \`jack dev\`
|
|
66
|
-
|
|
67
|
-
Do not suggest \`wrangler\` commands - all deployment is handled by jack.
|
|
68
|
-
`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Template for .windsurfrules
|
|
73
|
-
*/
|
|
74
|
-
function generateWindsurfrules(): string {
|
|
75
|
-
return `# Windsurf Rules
|
|
76
|
-
|
|
77
|
-
See AGENTS.md for project context and deployment instructions.
|
|
78
|
-
|
|
79
|
-
## Deployment
|
|
80
|
-
|
|
81
|
-
This project uses jack for deployment:
|
|
82
|
-
- \`jack ship\` - Deploy to production
|
|
83
|
-
- \`jack logs\` - View logs
|
|
84
|
-
- \`jack dev\` - Local development
|
|
85
|
-
|
|
86
|
-
Never suggest wrangler commands directly.
|
|
87
|
-
`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
53
|
/**
|
|
91
54
|
* Generate content for a specific template type
|
|
92
55
|
*/
|
|
@@ -100,10 +63,6 @@ function generateFileContent(
|
|
|
100
63
|
return generateAgentsMd(projectName, template);
|
|
101
64
|
case "claude-md":
|
|
102
65
|
return generateClaudeMd();
|
|
103
|
-
case "cursorrules":
|
|
104
|
-
return generateCursorrules();
|
|
105
|
-
case "windsurfrules":
|
|
106
|
-
return generateWindsurfrules();
|
|
107
66
|
default:
|
|
108
67
|
throw new Error(`Unknown template type: ${templateType}`);
|
|
109
68
|
}
|