@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
package/README.md
CHANGED
|
@@ -34,6 +34,22 @@ jack list
|
|
|
34
34
|
- [Bun](https://bun.sh) >= 1.0.0
|
|
35
35
|
- Cloudflare account (for deployments)
|
|
36
36
|
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
This project uses [Semantic Versioning](https://semver.org/).
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Bump version, commit, and tag
|
|
43
|
+
npm version patch # 0.1.1 → 0.1.2 (bug fixes)
|
|
44
|
+
npm version minor # 0.1.1 → 0.2.0 (new features)
|
|
45
|
+
npm version major # 0.1.1 → 1.0.0 (breaking changes)
|
|
46
|
+
|
|
47
|
+
# Push to trigger publish
|
|
48
|
+
git push && git push --tags
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
GitHub Actions automatically publishes to npm when a version tag is pushed.
|
|
52
|
+
|
|
37
53
|
## License
|
|
38
54
|
|
|
39
55
|
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
2
|
+
"name": "@getjack/jack",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI for vibecoders to rapidly deploy to Cloudflare Workers",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"jack": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"bun": ">=1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/getjack-org/jack"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cli",
|
|
26
|
+
"cloudflare",
|
|
27
|
+
"workers",
|
|
28
|
+
"deploy",
|
|
29
|
+
"vibecode"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "bun run src/index.ts",
|
|
33
|
+
"build": "bun build --compile src/index.ts --outfile dist/jack",
|
|
34
|
+
"lint": "biome check .",
|
|
35
|
+
"format": "biome format --write ."
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@biomejs/biome": "^1.9.0",
|
|
39
|
+
"bun-types": "latest"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@inquirer/prompts": "^7.0.0",
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
44
|
+
"meow": "^14.0.0",
|
|
45
|
+
"posthog-node": "^5.17.4",
|
|
46
|
+
"yocto-spinner": "^1.0.0",
|
|
47
|
+
"zod": "^4.2.1"
|
|
48
|
+
}
|
|
41
49
|
}
|
package/src/commands/agents.ts
CHANGED
|
@@ -103,7 +103,7 @@ async function scanAndPrompt(): Promise<void> {
|
|
|
103
103
|
|
|
104
104
|
console.error("");
|
|
105
105
|
success(`Found ${newAgents.length} new agent(s):`);
|
|
106
|
-
for (const { id, path } of newAgents) {
|
|
106
|
+
for (const { id, path, launch } of newAgents) {
|
|
107
107
|
const definition = getAgentDefinition(id);
|
|
108
108
|
item(`${definition?.name}: ${path}`);
|
|
109
109
|
|
|
@@ -112,6 +112,7 @@ async function scanAndPrompt(): Promise<void> {
|
|
|
112
112
|
active: true,
|
|
113
113
|
path,
|
|
114
114
|
detectedAt: new Date().toISOString(),
|
|
115
|
+
launch,
|
|
115
116
|
});
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -120,6 +121,17 @@ async function scanAndPrompt(): Promise<void> {
|
|
|
120
121
|
info("Future projects will include context files for these agents");
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
function getFlagValues(args: string[], flag: string): string[] {
|
|
125
|
+
const values: string[] = [];
|
|
126
|
+
for (let i = 0; i < args.length; i++) {
|
|
127
|
+
if (args[i] === flag && args[i + 1]) {
|
|
128
|
+
values.push(args[i + 1]);
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return values;
|
|
133
|
+
}
|
|
134
|
+
|
|
123
135
|
/**
|
|
124
136
|
* Manually add an agent
|
|
125
137
|
*/
|
|
@@ -128,7 +140,7 @@ async function addAgentCommand(args: string[]): Promise<void> {
|
|
|
128
140
|
|
|
129
141
|
if (!agentId) {
|
|
130
142
|
error("Agent ID required");
|
|
131
|
-
info("Usage: jack agents add <id> [--
|
|
143
|
+
info("Usage: jack agents add <id> [--command cmd] [--arg value]");
|
|
132
144
|
info(`Available IDs: ${AGENT_REGISTRY.map((a) => a.id).join(", ")}`);
|
|
133
145
|
process.exit(1);
|
|
134
146
|
}
|
|
@@ -140,15 +152,32 @@ async function addAgentCommand(args: string[]): Promise<void> {
|
|
|
140
152
|
process.exit(1);
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
// Parse
|
|
144
|
-
|
|
145
|
-
let
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
// Parse flags
|
|
156
|
+
let customCommand: string | undefined;
|
|
157
|
+
for (let i = 0; i < rest.length; i++) {
|
|
158
|
+
if (rest[i] === "--command" && rest[i + 1]) {
|
|
159
|
+
customCommand = rest[i + 1];
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const customArgs = getFlagValues(rest, "--arg");
|
|
164
|
+
|
|
165
|
+
if (customArgs.length > 0 && !customCommand) {
|
|
166
|
+
error("Use --command with --arg");
|
|
167
|
+
info("Usage: jack agents add <id> [--command cmd] [--arg value]");
|
|
168
|
+
process.exit(1);
|
|
148
169
|
}
|
|
149
170
|
|
|
150
171
|
try {
|
|
151
|
-
|
|
172
|
+
const launchOverride = customCommand
|
|
173
|
+
? {
|
|
174
|
+
type: "cli" as const,
|
|
175
|
+
command: customCommand,
|
|
176
|
+
args: customArgs.length ? customArgs : undefined,
|
|
177
|
+
}
|
|
178
|
+
: undefined;
|
|
179
|
+
|
|
180
|
+
await addAgent(agentId, { launch: launchOverride });
|
|
152
181
|
|
|
153
182
|
success(`Added ${definition.name}`);
|
|
154
183
|
const config = await readConfig();
|
|
@@ -161,7 +190,9 @@ async function addAgentCommand(args: string[]): Promise<void> {
|
|
|
161
190
|
if (err instanceof Error) {
|
|
162
191
|
error(err.message);
|
|
163
192
|
if (err.message.includes("Could not detect")) {
|
|
164
|
-
info(
|
|
193
|
+
info(
|
|
194
|
+
`Specify launch manually: jack agents add ${agentId} --command /path/to/command`,
|
|
195
|
+
);
|
|
165
196
|
}
|
|
166
197
|
}
|
|
167
198
|
process.exit(1);
|
package/src/commands/cloud.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { select } from "@inquirer/prompts";
|
|
4
4
|
import { formatRelativeTime, formatSize } from "../lib/format.ts";
|
|
5
5
|
import { error, info, item, output as outputSpinner, success } from "../lib/output.ts";
|
|
6
6
|
import { scanProjectFiles } from "../lib/storage/file-filter.ts";
|
|
@@ -200,12 +200,16 @@ async function deleteCommand(args: string[]): Promise<void> {
|
|
|
200
200
|
console.error("");
|
|
201
201
|
|
|
202
202
|
// Confirm deletion
|
|
203
|
-
|
|
203
|
+
console.error(" Esc to skip\n");
|
|
204
|
+
const action = await select({
|
|
204
205
|
message: `Delete project '${projectName}' from cloud storage?`,
|
|
205
|
-
|
|
206
|
+
choices: [
|
|
207
|
+
{ name: "1. Yes", value: "yes" },
|
|
208
|
+
{ name: "2. No", value: "no" },
|
|
209
|
+
],
|
|
206
210
|
});
|
|
207
211
|
|
|
208
|
-
if (
|
|
212
|
+
if (action === "no") {
|
|
209
213
|
info("Cancelled");
|
|
210
214
|
return;
|
|
211
215
|
}
|
package/src/commands/down.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
3
|
+
import { select } from "@inquirer/prompts";
|
|
3
4
|
import {
|
|
4
5
|
checkWorkerExists,
|
|
5
6
|
deleteDatabase,
|
|
@@ -7,9 +8,58 @@ import {
|
|
|
7
8
|
exportDatabase,
|
|
8
9
|
} from "../lib/cloudflare-api.ts";
|
|
9
10
|
import { error, info, item, output, success, warn } from "../lib/output.ts";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type Project,
|
|
13
|
+
getProject,
|
|
14
|
+
getProjectDatabaseName,
|
|
15
|
+
updateProject,
|
|
16
|
+
updateProjectDatabase,
|
|
17
|
+
} from "../lib/registry.ts";
|
|
11
18
|
import { deleteCloudProject, getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
12
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Get database name for a project, with fallback to wrangler config
|
|
22
|
+
*/
|
|
23
|
+
async function resolveDbName(project: Project): Promise<string | null> {
|
|
24
|
+
// First check registry
|
|
25
|
+
const dbFromRegistry = getProjectDatabaseName(project);
|
|
26
|
+
if (dbFromRegistry) {
|
|
27
|
+
return dbFromRegistry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fallback: read from wrangler config file
|
|
31
|
+
if (project.localPath && existsSync(project.localPath)) {
|
|
32
|
+
const jsoncPath = join(project.localPath, "wrangler.jsonc");
|
|
33
|
+
if (existsSync(jsoncPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const content = await Bun.file(jsoncPath).text();
|
|
36
|
+
const jsonContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
37
|
+
const config = JSON.parse(jsonContent);
|
|
38
|
+
if (config.d1_databases?.[0]?.database_name) {
|
|
39
|
+
return config.d1_databases[0].database_name;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Ignore parse errors
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tomlPath = join(project.localPath, "wrangler.toml");
|
|
47
|
+
if (existsSync(tomlPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const content = await Bun.file(tomlPath).text();
|
|
50
|
+
const match = content.match(/database_name\s*=\s*"([^"]+)"/);
|
|
51
|
+
if (match?.[1]) {
|
|
52
|
+
return match[1];
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Ignore read errors
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
13
63
|
export interface DownFlags {
|
|
14
64
|
force?: boolean;
|
|
15
65
|
}
|
|
@@ -32,17 +82,17 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
32
82
|
const project = await getProject(name);
|
|
33
83
|
if (!project) {
|
|
34
84
|
warn(`Project '${name}' not found in registry`);
|
|
35
|
-
info("Will attempt to undeploy
|
|
85
|
+
info("Will attempt to undeploy if deployed");
|
|
36
86
|
}
|
|
37
87
|
|
|
38
88
|
// Check if worker exists
|
|
39
|
-
output.start("Checking
|
|
89
|
+
output.start("Checking deployment...");
|
|
40
90
|
const workerExists = await checkWorkerExists(name);
|
|
41
91
|
output.stop();
|
|
42
92
|
|
|
43
93
|
if (!workerExists) {
|
|
44
94
|
console.error("");
|
|
45
|
-
warn(`
|
|
95
|
+
warn(`'${name}' is not deployed`);
|
|
46
96
|
info("Nothing to undeploy");
|
|
47
97
|
return;
|
|
48
98
|
}
|
|
@@ -50,10 +100,10 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
50
100
|
// Force mode - quick deletion without prompts
|
|
51
101
|
if (flags.force) {
|
|
52
102
|
console.error("");
|
|
53
|
-
info(`Undeploying
|
|
103
|
+
info(`Undeploying '${name}'`);
|
|
54
104
|
console.error("");
|
|
55
105
|
|
|
56
|
-
output.start("
|
|
106
|
+
output.start("Undeploying...");
|
|
57
107
|
await deleteWorker(name);
|
|
58
108
|
output.stop();
|
|
59
109
|
|
|
@@ -66,8 +116,8 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
66
116
|
}
|
|
67
117
|
|
|
68
118
|
console.error("");
|
|
69
|
-
success(`
|
|
70
|
-
info("
|
|
119
|
+
success(`'${name}' undeployed`);
|
|
120
|
+
info("Databases and cloud storage were not affected");
|
|
71
121
|
console.error("");
|
|
72
122
|
return;
|
|
73
123
|
}
|
|
@@ -76,46 +126,47 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
76
126
|
console.error("");
|
|
77
127
|
info(`Project: ${name}`);
|
|
78
128
|
if (project?.workerUrl) {
|
|
79
|
-
item(`
|
|
129
|
+
item(`URL: ${project.workerUrl}`);
|
|
80
130
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
item(` - ${db}`);
|
|
85
|
-
}
|
|
131
|
+
const dbName = project ? await resolveDbName(project) : null;
|
|
132
|
+
if (dbName) {
|
|
133
|
+
item(`Database: ${dbName}`);
|
|
86
134
|
}
|
|
87
135
|
console.error("");
|
|
88
136
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
137
|
+
// Confirm undeploy
|
|
138
|
+
console.error(" Esc to skip\n");
|
|
139
|
+
const action = await select({
|
|
140
|
+
message: "Undeploy this project?",
|
|
92
141
|
choices: [
|
|
93
|
-
{ name: "
|
|
94
|
-
{ name: "
|
|
142
|
+
{ name: "1. Yes", value: "yes" },
|
|
143
|
+
{ name: "2. No", value: "no" },
|
|
95
144
|
],
|
|
96
|
-
default: "delete",
|
|
97
145
|
});
|
|
98
146
|
|
|
99
|
-
if (
|
|
147
|
+
if (action === "no") {
|
|
100
148
|
info("Cancelled");
|
|
101
149
|
return;
|
|
102
150
|
}
|
|
103
151
|
|
|
104
|
-
// Handle
|
|
105
|
-
|
|
106
|
-
const databasesToDelete: string[] = [];
|
|
152
|
+
// Handle database if it exists
|
|
153
|
+
let shouldDeleteDb = false;
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
if (dbName) {
|
|
109
156
|
console.error("");
|
|
110
|
-
info(`Found
|
|
157
|
+
info(`Found database: ${dbName}`);
|
|
111
158
|
|
|
112
159
|
// Ask if they want to export first
|
|
113
|
-
|
|
160
|
+
console.error(" Esc to skip\n");
|
|
161
|
+
const exportAction = await select({
|
|
114
162
|
message: `Export database '${dbName}' before deleting?`,
|
|
115
|
-
|
|
163
|
+
choices: [
|
|
164
|
+
{ name: "1. Yes", value: "yes" },
|
|
165
|
+
{ name: "2. No", value: "no" },
|
|
166
|
+
],
|
|
116
167
|
});
|
|
117
168
|
|
|
118
|
-
if (
|
|
169
|
+
if (exportAction === "yes") {
|
|
119
170
|
const exportPath = join(process.cwd(), `${dbName}-backup.sql`);
|
|
120
171
|
output.start(`Exporting database to ${exportPath}...`);
|
|
121
172
|
try {
|
|
@@ -125,11 +176,15 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
125
176
|
} catch (err) {
|
|
126
177
|
output.stop();
|
|
127
178
|
error(`Failed to export database: ${err instanceof Error ? err.message : String(err)}`);
|
|
128
|
-
|
|
179
|
+
console.error(" Esc to skip\n");
|
|
180
|
+
const continueAction = await select({
|
|
129
181
|
message: "Continue without exporting?",
|
|
130
|
-
|
|
182
|
+
choices: [
|
|
183
|
+
{ name: "1. Yes", value: "yes" },
|
|
184
|
+
{ name: "2. No", value: "no" },
|
|
185
|
+
],
|
|
131
186
|
});
|
|
132
|
-
if (
|
|
187
|
+
if (continueAction === "no") {
|
|
133
188
|
info("Cancelled");
|
|
134
189
|
return;
|
|
135
190
|
}
|
|
@@ -137,25 +192,31 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
137
192
|
}
|
|
138
193
|
|
|
139
194
|
// Ask if they want to delete the database
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
195
|
+
console.error(" Esc to skip\n");
|
|
196
|
+
const deleteAction = await select({
|
|
197
|
+
message: `Delete database '${dbName}'?`,
|
|
198
|
+
choices: [
|
|
199
|
+
{ name: "1. Yes", value: "yes" },
|
|
200
|
+
{ name: "2. No", value: "no" },
|
|
201
|
+
],
|
|
143
202
|
});
|
|
144
203
|
|
|
145
|
-
|
|
146
|
-
databasesToDelete.push(dbName);
|
|
147
|
-
}
|
|
204
|
+
shouldDeleteDb = deleteAction === "yes";
|
|
148
205
|
}
|
|
149
206
|
|
|
150
207
|
// Handle R2 backup deletion
|
|
151
208
|
let shouldDeleteR2 = false;
|
|
152
209
|
if (project) {
|
|
153
210
|
console.error("");
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
211
|
+
console.error(" Esc to skip\n");
|
|
212
|
+
const deleteR2Action = await select({
|
|
213
|
+
message: "Delete cloud backup for this project?",
|
|
214
|
+
choices: [
|
|
215
|
+
{ name: "1. Yes", value: "yes" },
|
|
216
|
+
{ name: "2. No", value: "no" },
|
|
217
|
+
],
|
|
157
218
|
});
|
|
158
|
-
shouldDeleteR2 =
|
|
219
|
+
shouldDeleteR2 = deleteR2Action === "yes";
|
|
159
220
|
}
|
|
160
221
|
|
|
161
222
|
// Execute deletions
|
|
@@ -163,25 +224,28 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
163
224
|
info("Executing cleanup...");
|
|
164
225
|
console.error("");
|
|
165
226
|
|
|
166
|
-
//
|
|
167
|
-
output.start("
|
|
227
|
+
// Undeploy service
|
|
228
|
+
output.start("Undeploying...");
|
|
168
229
|
try {
|
|
169
230
|
await deleteWorker(name);
|
|
170
231
|
output.stop();
|
|
171
|
-
success(`
|
|
232
|
+
success(`'${name}' undeployed`);
|
|
172
233
|
} catch (err) {
|
|
173
234
|
output.stop();
|
|
174
|
-
error(`Failed to
|
|
235
|
+
error(`Failed to undeploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
175
236
|
process.exit(1);
|
|
176
237
|
}
|
|
177
238
|
|
|
178
|
-
// Delete
|
|
179
|
-
|
|
239
|
+
// Delete database if requested
|
|
240
|
+
if (shouldDeleteDb && dbName) {
|
|
180
241
|
output.start(`Deleting database '${dbName}'...`);
|
|
181
242
|
try {
|
|
182
243
|
await deleteDatabase(dbName);
|
|
183
244
|
output.stop();
|
|
184
245
|
success(`Database '${dbName}' deleted`);
|
|
246
|
+
|
|
247
|
+
// Update registry
|
|
248
|
+
await updateProjectDatabase(name, null);
|
|
185
249
|
} catch (err) {
|
|
186
250
|
output.stop();
|
|
187
251
|
warn(
|
|
@@ -190,42 +254,29 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
190
254
|
}
|
|
191
255
|
}
|
|
192
256
|
|
|
193
|
-
// Delete
|
|
257
|
+
// Delete cloud backup if requested
|
|
194
258
|
if (shouldDeleteR2) {
|
|
195
|
-
output.start("Deleting
|
|
259
|
+
output.start("Deleting cloud backup...");
|
|
196
260
|
try {
|
|
197
261
|
const deleted = await deleteCloudProject(name);
|
|
198
262
|
output.stop();
|
|
199
263
|
if (deleted) {
|
|
200
|
-
success("
|
|
264
|
+
success("Cloud backup deleted");
|
|
201
265
|
} else {
|
|
202
|
-
warn("No
|
|
266
|
+
warn("No cloud backup found or already deleted");
|
|
203
267
|
}
|
|
204
268
|
} catch (err) {
|
|
205
269
|
output.stop();
|
|
206
|
-
warn(`Failed to delete
|
|
270
|
+
warn(`Failed to delete cloud backup: ${err instanceof Error ? err.message : String(err)}`);
|
|
207
271
|
}
|
|
208
272
|
}
|
|
209
273
|
|
|
210
274
|
// Update registry - keep entry but clear worker URL
|
|
211
275
|
if (project) {
|
|
212
|
-
|
|
213
|
-
workerUrl: null;
|
|
214
|
-
lastDeployed: null;
|
|
215
|
-
resources?: { d1Databases: string[] };
|
|
216
|
-
} = {
|
|
276
|
+
await updateProject(name, {
|
|
217
277
|
workerUrl: null,
|
|
218
278
|
lastDeployed: null,
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// Remove deleted databases from registry
|
|
222
|
-
if (databasesToDelete.length > 0) {
|
|
223
|
-
updates.resources = {
|
|
224
|
-
d1Databases: databases.filter((db) => !databasesToDelete.includes(db)),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
await updateProject(name, updates);
|
|
279
|
+
});
|
|
229
280
|
}
|
|
230
281
|
|
|
231
282
|
console.error("");
|
package/src/commands/init.ts
CHANGED
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
updateAgent,
|
|
6
6
|
} from "../lib/agents.ts";
|
|
7
7
|
import { readConfig, writeConfig } from "../lib/config.ts";
|
|
8
|
+
import {
|
|
9
|
+
getIdeDisplayName,
|
|
10
|
+
installMcpConfigsToAllIdes,
|
|
11
|
+
saveMcpConfig,
|
|
12
|
+
} from "../lib/mcp-config.ts";
|
|
8
13
|
import { info, item, spinner, success } from "../lib/output.ts";
|
|
9
14
|
import { ensureAuth, ensureWrangler, isAuthenticated } from "../lib/wrangler.ts";
|
|
10
15
|
|
|
@@ -14,7 +19,11 @@ export async function isInitialized(): Promise<boolean> {
|
|
|
14
19
|
return await isAuthenticated();
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
interface InitOptions {
|
|
23
|
+
skipMcp?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function init(options: InitOptions = {}): Promise<void> {
|
|
18
27
|
// Immediate feedback
|
|
19
28
|
const spin = spinner("Checking setup...");
|
|
20
29
|
|
|
@@ -47,7 +56,7 @@ export default async function init(): Promise<void> {
|
|
|
47
56
|
let preferredAgent: string | undefined;
|
|
48
57
|
if (detectionResult.detected.length > 0) {
|
|
49
58
|
success(`Found ${detectionResult.detected.length} agent(s)`);
|
|
50
|
-
for (const { id, path } of detectionResult.detected) {
|
|
59
|
+
for (const { id, path, launch } of detectionResult.detected) {
|
|
51
60
|
const definition = getAgentDefinition(id);
|
|
52
61
|
item(`${definition?.name}: ${path}`);
|
|
53
62
|
|
|
@@ -56,6 +65,7 @@ export default async function init(): Promise<void> {
|
|
|
56
65
|
active: true,
|
|
57
66
|
path: path,
|
|
58
67
|
detectedAt: new Date().toISOString(),
|
|
68
|
+
launch,
|
|
59
69
|
});
|
|
60
70
|
}
|
|
61
71
|
|
|
@@ -69,7 +79,35 @@ export default async function init(): Promise<void> {
|
|
|
69
79
|
info("No agents detected (you can add them later with: jack agents add)");
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
// Step 4:
|
|
82
|
+
// Step 4: Install MCP configs to detected IDEs (unless --skip-mcp)
|
|
83
|
+
if (!options.skipMcp) {
|
|
84
|
+
const mcpSpinner = spinner("Installing MCP server configs...");
|
|
85
|
+
try {
|
|
86
|
+
const installedIdes = await installMcpConfigsToAllIdes();
|
|
87
|
+
mcpSpinner.stop();
|
|
88
|
+
|
|
89
|
+
if (installedIdes.length > 0) {
|
|
90
|
+
success(`MCP server installed to ${installedIdes.length} IDE(s)`);
|
|
91
|
+
for (const ideId of installedIdes) {
|
|
92
|
+
item(` ${getIdeDisplayName(ideId)}`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
info("No supported IDEs detected for MCP installation");
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
mcpSpinner.stop();
|
|
99
|
+
// Don't fail init if MCP install fails - just warn
|
|
100
|
+
info("Could not install MCP configs (non-critical)");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await saveMcpConfig();
|
|
105
|
+
} catch {
|
|
106
|
+
// Non-critical; config persistence shouldn't block init
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Step 5: Save config (preserve existing agents, just update init status)
|
|
73
111
|
const existingConfig = await readConfig();
|
|
74
112
|
await writeConfig({
|
|
75
113
|
version: 1,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { error, info } from "../lib/output.ts";
|
|
2
|
+
import { startMcpServer } from "../mcp/server.ts";
|
|
3
|
+
|
|
4
|
+
interface McpOptions {
|
|
5
|
+
project?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default async function mcp(subcommand?: string, options: McpOptions = {}): Promise<void> {
|
|
9
|
+
if (subcommand !== "serve") {
|
|
10
|
+
error("Unknown subcommand. Use: jack mcp serve");
|
|
11
|
+
info("Usage: jack mcp serve [--project /path/to/project]");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await startMcpServer({
|
|
16
|
+
projectPath: options.project,
|
|
17
|
+
});
|
|
18
|
+
}
|