@byfungsi/funforge 0.2.3 → 0.4.0

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.
@@ -124,19 +124,31 @@ describe("MCP Integration", () => {
124
124
  "funforge_whoami",
125
125
  "funforge_apps_list",
126
126
  "funforge_apps_create",
127
+ "funforge_apps_delete",
127
128
  "funforge_status",
128
129
  "funforge_deploy",
130
+ "funforge_stop",
131
+ "funforge_start",
132
+ "funforge_cancel",
129
133
  "funforge_env_list",
130
134
  "funforge_env_set",
131
135
  "funforge_env_unset",
132
136
  "funforge_domains_list",
133
137
  "funforge_domains_add",
138
+ "funforge_domains_remove",
139
+ "funforge_domains_verify",
134
140
  ];
135
141
  it("should define all expected tools", () => {
136
142
  // This is a documentation test to ensure we have all tools
137
- expect(expectedTools).toHaveLength(10);
143
+ expect(expectedTools).toHaveLength(16);
138
144
  expect(expectedTools).toContain("funforge_deploy");
139
145
  expect(expectedTools).toContain("funforge_whoami");
146
+ expect(expectedTools).toContain("funforge_stop");
147
+ expect(expectedTools).toContain("funforge_start");
148
+ expect(expectedTools).toContain("funforge_cancel");
149
+ expect(expectedTools).toContain("funforge_apps_delete");
150
+ expect(expectedTools).toContain("funforge_domains_remove");
151
+ expect(expectedTools).toContain("funforge_domains_verify");
140
152
  });
141
153
  });
142
154
  });
package/dist/api.d.ts CHANGED
@@ -154,5 +154,43 @@ export declare function getDeployment(deploymentId: string): Promise<{
154
154
  * Stream deployment logs (returns EventSource URL)
155
155
  */
156
156
  export declare function getDeploymentLogsUrl(deploymentId: string): string;
157
+ /**
158
+ * Stop the current deployment for an app
159
+ */
160
+ export declare function stopDeployment(appId: string): Promise<{
161
+ success: boolean;
162
+ }>;
163
+ /**
164
+ * Start a stopped deployment for an app
165
+ */
166
+ export declare function startDeployment(appId: string): Promise<{
167
+ success: boolean;
168
+ }>;
169
+ /**
170
+ * Cancel an in-progress deployment
171
+ */
172
+ export declare function cancelDeployment(appId: string): Promise<{
173
+ success: boolean;
174
+ }>;
175
+ /**
176
+ * Delete an app
177
+ */
178
+ export declare function deleteApp(appId: string): Promise<{
179
+ success: boolean;
180
+ }>;
181
+ /**
182
+ * Remove a custom domain from an app
183
+ */
184
+ export declare function removeDomain(appId: string, domainId: string): Promise<{
185
+ success: boolean;
186
+ }>;
187
+ /**
188
+ * Verify DNS and provision SSL for a domain
189
+ */
190
+ export declare function verifyDomain(appId: string, domainId: string): Promise<{
191
+ verified: boolean;
192
+ sslStatus?: string;
193
+ message?: string;
194
+ }>;
157
195
  export {};
158
196
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -208,3 +208,57 @@ export function getDeploymentLogsUrl(deploymentId) {
208
208
  const apiKey = getApiKey();
209
209
  return `${config.apiUrl}/api/cli/deployments/${deploymentId}/logs?token=${apiKey}`;
210
210
  }
211
+ // ============================================
212
+ // DEPLOYMENT CONTROL ENDPOINTS
213
+ // ============================================
214
+ /**
215
+ * Stop the current deployment for an app
216
+ */
217
+ export async function stopDeployment(appId) {
218
+ return apiRequest(`/api/cli/apps/${appId}/stop`, {
219
+ method: "POST",
220
+ });
221
+ }
222
+ /**
223
+ * Start a stopped deployment for an app
224
+ */
225
+ export async function startDeployment(appId) {
226
+ return apiRequest(`/api/cli/apps/${appId}/start`, {
227
+ method: "POST",
228
+ });
229
+ }
230
+ /**
231
+ * Cancel an in-progress deployment
232
+ */
233
+ export async function cancelDeployment(appId) {
234
+ return apiRequest(`/api/cli/apps/${appId}/cancel`, {
235
+ method: "POST",
236
+ });
237
+ }
238
+ /**
239
+ * Delete an app
240
+ */
241
+ export async function deleteApp(appId) {
242
+ return apiRequest(`/api/cli/apps/${appId}`, {
243
+ method: "DELETE",
244
+ });
245
+ }
246
+ // ============================================
247
+ // DOMAIN ENDPOINTS
248
+ // ============================================
249
+ /**
250
+ * Remove a custom domain from an app
251
+ */
252
+ export async function removeDomain(appId, domainId) {
253
+ return apiRequest(`/api/cli/apps/${appId}/domains/${domainId}`, {
254
+ method: "DELETE",
255
+ });
256
+ }
257
+ /**
258
+ * Verify DNS and provision SSL for a domain
259
+ */
260
+ export async function verifyDomain(appId, domainId) {
261
+ return apiRequest(`/api/cli/apps/${appId}/domains/${domainId}/verify`, {
262
+ method: "POST",
263
+ });
264
+ }
package/dist/cli.js CHANGED
@@ -10,10 +10,11 @@ import { Command } from "commander";
10
10
  // Load version from package.json
11
11
  const require = createRequire(import.meta.url);
12
12
  const pkg = require("../package.json");
13
- import { appsCreateCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
13
+ import { appsCreateCommand, appsDeleteCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
14
14
  // Import commands
15
15
  import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
16
16
  import { configPullCommand, configPushCommand, configShowCommand, } from "./commands/config.js";
17
+ import { cancelCommand, startCommand, stopCommand, } from "./commands/control.js";
17
18
  import { deployCommand } from "./commands/deploy.js";
18
19
  import { domainsAddCommand, domainsListCommand, domainsRemoveCommand, domainsVerifyCommand, } from "./commands/domains.js";
19
20
  import { envListCommand, envSetCommand, envUnsetCommand, } from "./commands/env.js";
@@ -49,6 +50,11 @@ appsCmd
49
50
  .option("-s, --slug <slug>", "Custom slug (subdomain)")
50
51
  .option("-t, --tier <tier>", "Tier key (e.g., cost-optimized.small)")
51
52
  .action(appsCreateCommand);
53
+ appsCmd
54
+ .command("delete <appId>")
55
+ .description("Delete an app")
56
+ .option("-y, --yes", "Skip confirmation prompt")
57
+ .action(appsDeleteCommand);
52
58
  program
53
59
  .command("link [appId]")
54
60
  .description("Link current directory to an app")
@@ -63,6 +69,21 @@ program
63
69
  .option("--no-watch", "Don't watch deployment logs")
64
70
  .action(deployCommand);
65
71
  // ============================================
72
+ // DEPLOYMENT CONTROL COMMANDS
73
+ // ============================================
74
+ program
75
+ .command("stop")
76
+ .description("Stop the current deployment")
77
+ .action(stopCommand);
78
+ program
79
+ .command("start")
80
+ .description("Start a stopped deployment")
81
+ .action(startCommand);
82
+ program
83
+ .command("cancel")
84
+ .description("Cancel an in-progress deployment")
85
+ .action(cancelCommand);
86
+ // ============================================
66
87
  // ENVIRONMENT VARIABLES
67
88
  // ============================================
68
89
  const envCmd = program
@@ -27,4 +27,12 @@ export declare function appsCreateCommand(name: string, options: {
27
27
  * If no appId provided, shows interactive selector.
28
28
  */
29
29
  export declare function linkCommand(appId?: string): Promise<void>;
30
+ /**
31
+ * funforge apps delete <appId>
32
+ *
33
+ * Delete an app (requires confirmation).
34
+ */
35
+ export declare function appsDeleteCommand(appId: string, options: {
36
+ yes?: boolean;
37
+ }): Promise<void>;
30
38
  //# sourceMappingURL=apps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAgCrD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/D"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAgCrD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/D;AAaD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
@@ -5,9 +5,10 @@
5
5
  * - apps create: Create a new app (with interactive tier selection)
6
6
  * - link: Link current directory to an app
7
7
  */
8
+ import { confirm } from "@inquirer/prompts";
8
9
  import chalk from "chalk";
9
10
  import ora from "ora";
10
- import { createApp, getApp, listApps } from "../api.js";
11
+ import { createApp, deleteApp, getApp, listApps } from "../api.js";
11
12
  import { isAuthenticated } from "../credentials.js";
12
13
  import { handleError } from "../errors.js";
13
14
  import { readConfig, updateConfig } from "../project-config.js";
@@ -151,3 +152,50 @@ function requireAuth() {
151
152
  process.exit(1);
152
153
  }
153
154
  }
155
+ /**
156
+ * funforge apps delete <appId>
157
+ *
158
+ * Delete an app (requires confirmation).
159
+ */
160
+ export async function appsDeleteCommand(appId, options) {
161
+ requireAuth();
162
+ // Get app info first
163
+ const infoSpinner = ora("Fetching app info...").start();
164
+ let app;
165
+ try {
166
+ const result = await getApp(appId);
167
+ app = result.app;
168
+ infoSpinner.stop();
169
+ }
170
+ catch (error) {
171
+ infoSpinner.fail("Failed to fetch app");
172
+ handleError(error);
173
+ return;
174
+ }
175
+ console.log();
176
+ console.log(chalk.bold("App to delete:"));
177
+ console.log(` Name: ${app.name}`);
178
+ console.log(` Slug: ${chalk.cyan(app.slug)}`);
179
+ console.log(` ID: ${app.id}`);
180
+ console.log();
181
+ // Confirm deletion
182
+ if (!options.yes) {
183
+ const confirmed = await confirm({
184
+ message: `Are you sure you want to delete "${app.name}"? This cannot be undone.`,
185
+ default: false,
186
+ });
187
+ if (!confirmed) {
188
+ console.log(chalk.gray("Deletion canceled."));
189
+ return;
190
+ }
191
+ }
192
+ const deleteSpinner = ora(`Deleting ${app.name}...`).start();
193
+ try {
194
+ await deleteApp(appId);
195
+ deleteSpinner.succeed(`App "${app.name}" deleted`);
196
+ }
197
+ catch (error) {
198
+ deleteSpinner.fail("Failed to delete app");
199
+ handleError(error);
200
+ }
201
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Deployment Control Commands
3
+ *
4
+ * - stop: Stop the current deployment
5
+ * - start: Start a stopped deployment
6
+ * - cancel: Cancel an in-progress deployment
7
+ */
8
+ /**
9
+ * funforge stop
10
+ *
11
+ * Stop the current deployment for the linked app.
12
+ */
13
+ export declare function stopCommand(): Promise<void>;
14
+ /**
15
+ * funforge start
16
+ *
17
+ * Start a stopped deployment for the linked app.
18
+ */
19
+ export declare function startCommand(): Promise<void>;
20
+ /**
21
+ * funforge cancel
22
+ *
23
+ * Cancel an in-progress deployment for the linked app.
24
+ */
25
+ export declare function cancelCommand(): Promise<void>;
26
+ //# sourceMappingURL=control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/commands/control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAejD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAalD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAanD"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Deployment Control Commands
3
+ *
4
+ * - stop: Stop the current deployment
5
+ * - start: Start a stopped deployment
6
+ * - cancel: Cancel an in-progress deployment
7
+ */
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ import { cancelDeployment, startDeployment, stopDeployment } from "../api.js";
11
+ import { isAuthenticated } from "../credentials.js";
12
+ import { handleError } from "../errors.js";
13
+ import { readConfig } from "../project-config.js";
14
+ /**
15
+ * funforge stop
16
+ *
17
+ * Stop the current deployment for the linked app.
18
+ */
19
+ export async function stopCommand() {
20
+ requireAuth();
21
+ const appId = await getLinkedAppId();
22
+ const spinner = ora("Stopping deployment...").start();
23
+ try {
24
+ await stopDeployment(appId);
25
+ spinner.succeed("Deployment stopped");
26
+ console.log();
27
+ console.log(chalk.gray("Use `funforge start` to restart the deployment"));
28
+ }
29
+ catch (error) {
30
+ spinner.fail("Failed to stop deployment");
31
+ handleError(error);
32
+ }
33
+ }
34
+ /**
35
+ * funforge start
36
+ *
37
+ * Start a stopped deployment for the linked app.
38
+ */
39
+ export async function startCommand() {
40
+ requireAuth();
41
+ const appId = await getLinkedAppId();
42
+ const spinner = ora("Starting deployment...").start();
43
+ try {
44
+ await startDeployment(appId);
45
+ spinner.succeed("Deployment started");
46
+ }
47
+ catch (error) {
48
+ spinner.fail("Failed to start deployment");
49
+ handleError(error);
50
+ }
51
+ }
52
+ /**
53
+ * funforge cancel
54
+ *
55
+ * Cancel an in-progress deployment for the linked app.
56
+ */
57
+ export async function cancelCommand() {
58
+ requireAuth();
59
+ const appId = await getLinkedAppId();
60
+ const spinner = ora("Canceling deployment...").start();
61
+ try {
62
+ await cancelDeployment(appId);
63
+ spinner.succeed("Deployment canceled");
64
+ }
65
+ catch (error) {
66
+ spinner.fail("Failed to cancel deployment");
67
+ handleError(error);
68
+ }
69
+ }
70
+ /**
71
+ * Get the linked app ID or exit
72
+ */
73
+ async function getLinkedAppId() {
74
+ const config = await readConfig();
75
+ if (!config?.appId) {
76
+ console.log(chalk.red("Not linked to an app."));
77
+ console.log(chalk.gray("Run `funforge link <app-id>` first."));
78
+ process.exit(1);
79
+ }
80
+ return config.appId;
81
+ }
82
+ /**
83
+ * Check if user is authenticated, exit if not
84
+ */
85
+ function requireAuth() {
86
+ if (!isAuthenticated()) {
87
+ console.log(chalk.red("Not authenticated."));
88
+ console.log(chalk.gray("Run `funforge login` first."));
89
+ process.exit(1);
90
+ }
91
+ }
@@ -99,7 +99,7 @@ export async function deployCommand(options = {}) {
99
99
  console.log();
100
100
  // 6. Watch logs if requested
101
101
  if (options.watch !== false) {
102
- await watchDeployment(deployResult.deployment.id);
102
+ await watchDeployment(deployResult.deployment.id, config.appSlug);
103
103
  }
104
104
  else {
105
105
  console.log(chalk.gray("Run with --watch to follow logs"));
@@ -108,7 +108,7 @@ export async function deployCommand(options = {}) {
108
108
  /**
109
109
  * Watch deployment status until completion
110
110
  */
111
- async function watchDeployment(deploymentId) {
111
+ async function watchDeployment(deploymentId, appSlug) {
112
112
  console.log(chalk.gray("Watching deployment..."));
113
113
  console.log();
114
114
  const spinner = ora("Building...").start();
@@ -123,7 +123,12 @@ async function watchDeployment(deploymentId) {
123
123
  if (deployment.status === "live") {
124
124
  spinner.succeed(chalk.green("Deployment live!"));
125
125
  console.log();
126
- // TODO: Show deployment URL
126
+ // Show deployment URL
127
+ if (appSlug) {
128
+ console.log(chalk.bold("Your app is live at:"));
129
+ console.log(` ${chalk.cyan(`https://sarana-${appSlug}.funforge.app`)}`);
130
+ console.log();
131
+ }
127
132
  return;
128
133
  }
129
134
  if (deployment.status === "failed" || deployment.status === "canceled") {
package/dist/mcp.js CHANGED
@@ -195,6 +195,98 @@ const TOOLS = [
195
195
  required: ["domain"],
196
196
  },
197
197
  },
198
+ {
199
+ name: "funforge_domains_remove",
200
+ description: "Remove a custom domain from the linked app.",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ directory: {
205
+ type: "string",
206
+ description: "Directory with funforge.json (defaults to cwd)",
207
+ },
208
+ domain: {
209
+ type: "string",
210
+ description: "Domain name or domain ID to remove",
211
+ },
212
+ },
213
+ required: ["domain"],
214
+ },
215
+ },
216
+ {
217
+ name: "funforge_domains_verify",
218
+ description: "Verify DNS configuration and provision SSL for a custom domain.",
219
+ inputSchema: {
220
+ type: "object",
221
+ properties: {
222
+ directory: {
223
+ type: "string",
224
+ description: "Directory with funforge.json (defaults to cwd)",
225
+ },
226
+ domain: {
227
+ type: "string",
228
+ description: "Domain name or domain ID to verify",
229
+ },
230
+ },
231
+ required: ["domain"],
232
+ },
233
+ },
234
+ {
235
+ name: "funforge_stop",
236
+ description: "Stop the current deployment for the linked app. The app will be scaled down to 0 replicas.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ directory: {
241
+ type: "string",
242
+ description: "Directory with funforge.json (defaults to cwd)",
243
+ },
244
+ },
245
+ required: [],
246
+ },
247
+ },
248
+ {
249
+ name: "funforge_start",
250
+ description: "Start a stopped deployment for the linked app. Scales the app back up.",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ directory: {
255
+ type: "string",
256
+ description: "Directory with funforge.json (defaults to cwd)",
257
+ },
258
+ },
259
+ required: [],
260
+ },
261
+ },
262
+ {
263
+ name: "funforge_cancel",
264
+ description: "Cancel an in-progress deployment for the linked app. Use this to abort a build or deployment that is stuck.",
265
+ inputSchema: {
266
+ type: "object",
267
+ properties: {
268
+ directory: {
269
+ type: "string",
270
+ description: "Directory with funforge.json (defaults to cwd)",
271
+ },
272
+ },
273
+ required: [],
274
+ },
275
+ },
276
+ {
277
+ name: "funforge_apps_delete",
278
+ description: "Delete an app permanently. This will stop all deployments and remove all associated resources.",
279
+ inputSchema: {
280
+ type: "object",
281
+ properties: {
282
+ appId: {
283
+ type: "string",
284
+ description: "The app ID to delete",
285
+ },
286
+ },
287
+ required: ["appId"],
288
+ },
289
+ },
198
290
  ];
199
291
  async function handleToolCall(name, args) {
200
292
  try {
@@ -219,6 +311,18 @@ async function handleToolCall(name, args) {
219
311
  return await handleDomainsList(args.directory);
220
312
  case "funforge_domains_add":
221
313
  return await handleDomainsAdd(args.directory, args.domain, args.verificationMethod);
314
+ case "funforge_domains_remove":
315
+ return await handleDomainsRemove(args.directory, args.domain);
316
+ case "funforge_domains_verify":
317
+ return await handleDomainsVerify(args.directory, args.domain);
318
+ case "funforge_stop":
319
+ return await handleStop(args.directory);
320
+ case "funforge_start":
321
+ return await handleStart(args.directory);
322
+ case "funforge_cancel":
323
+ return await handleCancel(args.directory);
324
+ case "funforge_apps_delete":
325
+ return await handleAppsDelete(args.appId);
222
326
  default:
223
327
  return errorResult(`Unknown tool: ${name}`);
224
328
  }
@@ -427,6 +531,126 @@ async function handleDomainsAdd(directory, domain, verificationMethod) {
427
531
  nextStep: "Add the DNS record above, then verify with funforge domains verify command",
428
532
  });
429
533
  }
534
+ async function handleDomainsRemove(directory, domain) {
535
+ requireAuth();
536
+ const dir = directory || process.cwd();
537
+ const appId = await getLinkedAppId(dir);
538
+ if (!appId) {
539
+ return errorResult(`No app linked in ${dir}.`);
540
+ }
541
+ const { domains } = await apiRequest(`/api/cli/apps/${appId}/domains`);
542
+ const cleanDomain = domain.toLowerCase();
543
+ const targetDomain = domains.find((d) => d.domain.toLowerCase() === cleanDomain || d.id === domain);
544
+ if (!targetDomain) {
545
+ return errorResult(`Domain not found: ${domain}. Use funforge_domains_list to see available domains.`);
546
+ }
547
+ await apiRequest(`/api/cli/apps/${appId}/domains/${targetDomain.id}`, {
548
+ method: "DELETE",
549
+ });
550
+ return successResult({
551
+ message: `Domain ${targetDomain.domain} removed`,
552
+ domainId: targetDomain.id,
553
+ });
554
+ }
555
+ async function handleDomainsVerify(directory, domain) {
556
+ requireAuth();
557
+ const dir = directory || process.cwd();
558
+ const appId = await getLinkedAppId(dir);
559
+ if (!appId) {
560
+ return errorResult(`No app linked in ${dir}.`);
561
+ }
562
+ const { domains } = await apiRequest(`/api/cli/apps/${appId}/domains`);
563
+ const cleanDomain = domain.toLowerCase();
564
+ const targetDomain = domains.find((d) => d.domain.toLowerCase() === cleanDomain || d.id === domain);
565
+ if (!targetDomain) {
566
+ return errorResult(`Domain not found: ${domain}. Use funforge_domains_list to see available domains.`);
567
+ }
568
+ if (targetDomain.verified) {
569
+ return successResult({
570
+ message: `Domain ${targetDomain.domain} is already verified`,
571
+ domain: targetDomain.domain,
572
+ verified: true,
573
+ });
574
+ }
575
+ const result = await apiRequest(`/api/cli/apps/${appId}/domains/${targetDomain.id}/verify`, { method: "POST" });
576
+ if (result.verified) {
577
+ return successResult({
578
+ message: `Domain ${targetDomain.domain} verified successfully`,
579
+ domain: targetDomain.domain,
580
+ verified: true,
581
+ sslStatus: result.sslStatus,
582
+ });
583
+ }
584
+ return successResult({
585
+ message: result.message || "Domain verification failed",
586
+ domain: targetDomain.domain,
587
+ verified: false,
588
+ hint: "Make sure your DNS record is correctly configured. DNS changes can take up to 48 hours to propagate.",
589
+ });
590
+ }
591
+ async function handleStop(directory) {
592
+ requireAuth();
593
+ const dir = directory || process.cwd();
594
+ const appId = await getLinkedAppId(dir);
595
+ if (!appId) {
596
+ return errorResult(`No app linked in ${dir}.`);
597
+ }
598
+ await apiRequest(`/api/cli/apps/${appId}/stop`, {
599
+ method: "POST",
600
+ });
601
+ return successResult({
602
+ message: "Deployment stopped",
603
+ appId,
604
+ note: "Use funforge_start to restart the deployment",
605
+ });
606
+ }
607
+ async function handleStart(directory) {
608
+ requireAuth();
609
+ const dir = directory || process.cwd();
610
+ const appId = await getLinkedAppId(dir);
611
+ if (!appId) {
612
+ return errorResult(`No app linked in ${dir}.`);
613
+ }
614
+ await apiRequest(`/api/cli/apps/${appId}/start`, {
615
+ method: "POST",
616
+ });
617
+ return successResult({
618
+ message: "Deployment started",
619
+ appId,
620
+ });
621
+ }
622
+ async function handleCancel(directory) {
623
+ requireAuth();
624
+ const dir = directory || process.cwd();
625
+ const appId = await getLinkedAppId(dir);
626
+ if (!appId) {
627
+ return errorResult(`No app linked in ${dir}.`);
628
+ }
629
+ await apiRequest(`/api/cli/apps/${appId}/cancel`, {
630
+ method: "POST",
631
+ });
632
+ return successResult({
633
+ message: "Deployment canceled",
634
+ appId,
635
+ note: "The in-progress build or deployment has been canceled",
636
+ });
637
+ }
638
+ async function handleAppsDelete(appId) {
639
+ requireAuth();
640
+ const { app } = await apiRequest(`/api/cli/apps/${appId}`);
641
+ await apiRequest(`/api/cli/apps/${appId}`, {
642
+ method: "DELETE",
643
+ });
644
+ return successResult({
645
+ message: `App "${app.name}" deleted successfully`,
646
+ app: {
647
+ id: app.id,
648
+ name: app.name,
649
+ slug: app.slug,
650
+ },
651
+ note: "All deployments and resources have been removed",
652
+ });
653
+ }
430
654
  // ============================================
431
655
  // HELPERS
432
656
  // ============================================
@@ -470,7 +694,7 @@ function formatError(error) {
470
694
  async function main() {
471
695
  const server = new Server({
472
696
  name: "funforge",
473
- version: "0.1.0",
697
+ version: "0.3.0",
474
698
  }, {
475
699
  capabilities: {
476
700
  tools: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byfungsi/funforge",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "description": "Deploy without git, without leaving your editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,10 +9,7 @@
9
9
  "funforge": "./dist/cli.js",
10
10
  "funforge-mcp": "./dist/mcp.js"
11
11
  },
12
- "files": [
13
- "dist",
14
- "README.md"
15
- ],
12
+ "files": ["dist", "README.md"],
16
13
  "scripts": {
17
14
  "build": "tsc",
18
15
  "dev": "tsc --watch",
@@ -26,13 +23,7 @@
26
23
  "release:publish": "pnpm run build && pnpm run test && npm publish --access public",
27
24
  "release:dry": "pnpm run build && pnpm run test && npm publish --access public --dry-run"
28
25
  },
29
- "keywords": [
30
- "cli",
31
- "deploy",
32
- "funforge",
33
- "fungsi",
34
- "mcp"
35
- ],
26
+ "keywords": ["cli", "deploy", "funforge", "fungsi", "mcp"],
36
27
  "author": "Fungsi",
37
28
  "license": "MIT",
38
29
  "engines": {