mcphosting-cli 0.1.2 → 0.3.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/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command7 } from "commander";
4
+ import { Command as Command17 } from "commander";
5
+ import chalk16 from "chalk";
5
6
 
6
- // src/commands/auth.ts
7
+ // src/commands/login.ts
7
8
  import { Command } from "commander";
8
9
  import { createServer } from "http";
9
10
  import open from "open";
@@ -24,8 +25,11 @@ var Config = class {
24
25
  defaults: { connections: [] }
25
26
  });
26
27
  }
28
+ /**
29
+ * Token resolution: env var MCPHOSTING_TOKEN takes priority over config file.
30
+ */
27
31
  get token() {
28
- return this.conf.get("token");
32
+ return process.env.MCPHOSTING_TOKEN || this.conf.get("token");
29
33
  }
30
34
  set token(value) {
31
35
  if (value) {
@@ -34,6 +38,16 @@ var Config = class {
34
38
  this.conf.delete("token");
35
39
  }
36
40
  }
41
+ get refreshToken() {
42
+ return this.conf.get("refresh_token");
43
+ }
44
+ set refreshToken(value) {
45
+ if (value) {
46
+ this.conf.set("refresh_token", value);
47
+ } else {
48
+ this.conf.delete("refresh_token");
49
+ }
50
+ }
37
51
  get user() {
38
52
  return this.conf.get("user");
39
53
  }
@@ -44,6 +58,12 @@ var Config = class {
44
58
  this.conf.delete("user");
45
59
  }
46
60
  }
61
+ /**
62
+ * Check if token came from environment variable.
63
+ */
64
+ get isEnvToken() {
65
+ return !!process.env.MCPHOSTING_TOKEN;
66
+ }
47
67
  get connections() {
48
68
  return this.connectionsConf.get("connections", []);
49
69
  }
@@ -84,20 +104,220 @@ var Config = class {
84
104
  };
85
105
 
86
106
  // src/lib/api.ts
107
+ import axios, { AxiosError } from "axios";
108
+ var API_BASE = process.env.MCPHOSTING_API_URL || "https://mcphosting.com";
87
109
  var MCPHostingAPI = class {
88
- baseUrl = "https://mcphosting.com/api";
89
- token;
110
+ token = null;
90
111
  constructor(token) {
112
+ this.token = token || null;
113
+ }
114
+ setToken(token) {
91
115
  this.token = token;
92
116
  }
93
- async request(endpoint, options = {}) {
94
- throw new Error(`API not available - using static fallback`);
117
+ get headers() {
118
+ const h = {
119
+ "Content-Type": "application/json",
120
+ "User-Agent": "mcphosting-cli/1.0.0"
121
+ };
122
+ if (this.token) {
123
+ h["Authorization"] = `Bearer ${this.token}`;
124
+ }
125
+ return h;
95
126
  }
96
- async searchMCPs(query) {
127
+ handleError(error) {
128
+ if (error instanceof AxiosError) {
129
+ const status = error.response?.status;
130
+ const message = error.response?.data?.error || error.response?.data?.message || error.message;
131
+ if (status === 401) {
132
+ throw new Error("Authentication required. Run `mcphosting login` first.");
133
+ }
134
+ if (status === 403) {
135
+ throw new Error("Access denied. Check your permissions.");
136
+ }
137
+ if (status === 404) {
138
+ throw new Error("Not found. Check the server slug or ID.");
139
+ }
140
+ if (status === 429) {
141
+ throw new Error("Rate limited. Please wait a moment and try again.");
142
+ }
143
+ throw new Error(`API error (${status}): ${message}`);
144
+ }
145
+ throw error;
146
+ }
147
+ // --- Auth ---
148
+ async login(email, password) {
149
+ try {
150
+ const { data } = await axios.post(`${API_BASE}/api/auth/cli-login`, { email, password });
151
+ return data;
152
+ } catch (error) {
153
+ this.handleError(error);
154
+ }
155
+ }
156
+ async refreshToken(refreshToken) {
157
+ try {
158
+ const { data } = await axios.post(`${API_BASE}/api/auth/cli-login/refresh`, { refresh_token: refreshToken });
159
+ return data;
160
+ } catch (error) {
161
+ this.handleError(error);
162
+ }
163
+ }
164
+ async githubDeviceStart() {
165
+ try {
166
+ const { data } = await axios.get(`${API_BASE}/api/auth/github/device`);
167
+ return data;
168
+ } catch (error) {
169
+ this.handleError(error);
170
+ }
171
+ }
172
+ async githubDevicePoll(deviceCode) {
173
+ try {
174
+ const { data } = await axios.post(
175
+ `${API_BASE}/api/auth/github/device`,
176
+ { device_code: deviceCode },
177
+ { validateStatus: (status) => status < 500 }
178
+ // Don't throw on 202/400
179
+ );
180
+ return data;
181
+ } catch (error) {
182
+ this.handleError(error);
183
+ }
184
+ }
185
+ async whoami() {
186
+ if (!this.token) return null;
187
+ try {
188
+ const { data } = await axios.get(`${API_BASE}/api/auth/whoami`, { headers: this.headers });
189
+ return data.user || null;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ // --- Deploy ---
195
+ async deploy(params) {
196
+ try {
197
+ if (params.githubUrl) {
198
+ const { data: data2 } = await axios.post(`${API_BASE}/api/cli/github/deploy`, {
199
+ repo_url: params.githubUrl,
200
+ auto_detect: true
201
+ }, { headers: this.headers });
202
+ return data2;
203
+ }
204
+ const { data } = await axios.post(`${API_BASE}/api/cli/deploy`, params, { headers: this.headers });
205
+ return data;
206
+ } catch (error) {
207
+ this.handleError(error);
208
+ }
209
+ }
210
+ // --- Server Management ---
211
+ async listServers() {
212
+ try {
213
+ const { data } = await axios.get(`${API_BASE}/api/mcp/projects`, { headers: this.headers });
214
+ return data.projects || [];
215
+ } catch (error) {
216
+ this.handleError(error);
217
+ }
218
+ }
219
+ async getServer(idOrSlug) {
220
+ try {
221
+ const { data } = await axios.get(`${API_BASE}/api/mcp/projects/${idOrSlug}`, { headers: this.headers });
222
+ return data.project || data;
223
+ } catch (error) {
224
+ this.handleError(error);
225
+ }
226
+ }
227
+ async getServerLogs(idOrSlug, lines = 50) {
228
+ try {
229
+ const { data } = await axios.get(`${API_BASE}/api/mcp/projects/${idOrSlug}/logs?lines=${lines}`, { headers: this.headers });
230
+ return data.logs || [];
231
+ } catch (error) {
232
+ this.handleError(error);
233
+ }
234
+ }
235
+ // --- Connection Info ---
236
+ async connect(slug) {
237
+ try {
238
+ const { data } = await axios.post(`${API_BASE}/api/mcp/connect`, { slug }, { headers: this.headers });
239
+ return data;
240
+ } catch (error) {
241
+ this.handleError(error);
242
+ }
243
+ }
244
+ async getConnectionInfo(slug) {
245
+ try {
246
+ const { data } = await axios.get(`${API_BASE}/api/cli/info/${slug}`, { headers: this.headers });
247
+ return data;
248
+ } catch (error) {
249
+ this.handleError(error);
250
+ }
251
+ }
252
+ // --- Env Vars ---
253
+ async listEnvVars(projectId) {
254
+ try {
255
+ const { data } = await axios.get(`${API_BASE}/api/mcp/projects/${projectId}/env-vars`, { headers: this.headers });
256
+ return data.envVars || [];
257
+ } catch (error) {
258
+ this.handleError(error);
259
+ }
260
+ }
261
+ async addEnvVar(projectId, key, value) {
262
+ try {
263
+ await axios.post(
264
+ `${API_BASE}/api/mcp/projects/${projectId}/env-vars`,
265
+ { key, value },
266
+ { headers: this.headers }
267
+ );
268
+ } catch (error) {
269
+ this.handleError(error);
270
+ }
271
+ }
272
+ async removeEnvVar(projectId, key) {
273
+ try {
274
+ await axios.delete(
275
+ `${API_BASE}/api/mcp/projects/${projectId}/env-vars/${key}`,
276
+ { headers: this.headers }
277
+ );
278
+ } catch (error) {
279
+ this.handleError(error);
280
+ }
281
+ }
282
+ // --- One-Click Deploy ---
283
+ async oneClickDeploy(params) {
97
284
  try {
98
- const results = await this.request(`/marketplace/search?q=${encodeURIComponent(query)}`);
99
- return results.mcps || [];
285
+ const { data } = await axios.post(`${API_BASE}/api/cli/deploy/one-click`, params, { headers: this.headers });
286
+ return data;
100
287
  } catch (error) {
288
+ this.handleError(error);
289
+ }
290
+ }
291
+ // --- API Keys ---
292
+ async getAPIKeys() {
293
+ try {
294
+ const { data } = await axios.get(`${API_BASE}/api/keys`, { headers: this.headers });
295
+ return data.keys || [];
296
+ } catch (error) {
297
+ this.handleError(error);
298
+ }
299
+ }
300
+ async createAPIKey(name) {
301
+ try {
302
+ const { data } = await axios.post(`${API_BASE}/api/keys`, { name }, { headers: this.headers });
303
+ return data;
304
+ } catch (error) {
305
+ this.handleError(error);
306
+ }
307
+ }
308
+ async deleteAPIKey(id) {
309
+ try {
310
+ await axios.delete(`${API_BASE}/api/keys/${id}`, { headers: this.headers });
311
+ } catch (error) {
312
+ this.handleError(error);
313
+ }
314
+ }
315
+ // --- Marketplace (static fallback for MVP) ---
316
+ async searchMCPs(query) {
317
+ try {
318
+ const { data } = await axios.get(`${API_BASE}/api/marketplace/search?q=${encodeURIComponent(query)}`, { headers: this.headers });
319
+ return data.mcps || [];
320
+ } catch {
101
321
  const staticMCPs = this.getStaticMCPs();
102
322
  return staticMCPs.filter(
103
323
  (mcp) => mcp.name.toLowerCase().includes(query.toLowerCase()) || mcp.description.toLowerCase().includes(query.toLowerCase()) || mcp.tools.some((tool) => tool.toLowerCase().includes(query.toLowerCase()))
@@ -106,22 +326,13 @@ var MCPHostingAPI = class {
106
326
  }
107
327
  async getMCPInfo(slug) {
108
328
  try {
109
- const result = await this.request(`/marketplace/mcp/${slug}`);
110
- return result.mcp || null;
329
+ const { data } = await axios.get(`${API_BASE}/api/marketplace/mcp/${slug}`, { headers: this.headers });
330
+ return data.mcp || null;
111
331
  } catch {
112
332
  const staticMCPs = this.getStaticMCPs();
113
333
  return staticMCPs.find((mcp) => mcp.slug === slug) || null;
114
334
  }
115
335
  }
116
- async whoami() {
117
- if (!this.token) return null;
118
- try {
119
- const result = await this.request("/auth/whoami");
120
- return result.user;
121
- } catch {
122
- return null;
123
- }
124
- }
125
336
  getStaticMCPs() {
126
337
  return [
127
338
  {
@@ -185,26 +396,66 @@ var MCPHostingAPI = class {
185
396
  // src/lib/logger.ts
186
397
  import chalk from "chalk";
187
398
  import ora from "ora";
399
+ var _silent = false;
400
+ var _jsonMode = false;
188
401
  var Logger = class _Logger {
402
+ static get isSilent() {
403
+ return _silent;
404
+ }
405
+ static set silent(value) {
406
+ _silent = value;
407
+ }
408
+ static get isJsonMode() {
409
+ return _jsonMode;
410
+ }
411
+ static set jsonMode(value) {
412
+ _jsonMode = value;
413
+ }
189
414
  static info(message) {
415
+ if (_silent || _jsonMode) return;
190
416
  console.log(chalk.blue("\u2139"), message);
191
417
  }
192
418
  static success(message) {
419
+ if (_silent || _jsonMode) return;
193
420
  console.log(chalk.green("\u2713"), message);
194
421
  }
195
422
  static warning(message) {
423
+ if (_silent || _jsonMode) return;
196
424
  console.log(chalk.yellow("\u26A0"), message);
197
425
  }
198
426
  static error(message) {
427
+ if (_jsonMode) {
428
+ console.error(JSON.stringify({ success: false, error: message }));
429
+ return;
430
+ }
431
+ if (_silent) return;
199
432
  console.error(chalk.red("\u2717"), message);
200
433
  }
201
434
  static dim(message) {
435
+ if (_silent || _jsonMode) return;
202
436
  console.log(chalk.dim(message));
203
437
  }
204
438
  static bold(message) {
439
+ if (_silent || _jsonMode) return;
205
440
  console.log(chalk.bold(message));
206
441
  }
207
442
  static spinner(text) {
443
+ if (_silent || _jsonMode || !process.stderr.isTTY) {
444
+ return {
445
+ start: () => ora(text),
446
+ stop: () => {
447
+ },
448
+ succeed: () => {
449
+ },
450
+ fail: () => {
451
+ },
452
+ warn: () => {
453
+ },
454
+ info: () => {
455
+ },
456
+ text: ""
457
+ };
458
+ }
208
459
  return ora(text).start();
209
460
  }
210
461
  static table(data, headers) {
@@ -233,26 +484,196 @@ var Logger = class _Logger {
233
484
  }
234
485
  };
235
486
 
236
- // src/commands/auth.ts
237
- function createAuthCommands() {
238
- const auth = new Command("auth");
239
- auth.command("login").description("Authenticate with MCPHosting").option("--token <token>", "Provide API token directly").action(async (options) => {
487
+ // src/commands/login.ts
488
+ import chalk2 from "chalk";
489
+ import * as readline from "readline";
490
+ function prompt(question, hidden = false) {
491
+ return new Promise((resolve) => {
492
+ const rl = readline.createInterface({
493
+ input: process.stdin,
494
+ output: process.stdout
495
+ });
496
+ if (hidden) {
497
+ const originalWrite = process.stdout.write.bind(process.stdout);
498
+ process.stdout.write = ((str) => {
499
+ if (typeof str === "string" && str !== question && str !== "\n" && str !== "\r\n") {
500
+ return originalWrite("*");
501
+ }
502
+ return originalWrite(str);
503
+ });
504
+ rl.question(question, (answer) => {
505
+ process.stdout.write = originalWrite;
506
+ console.log();
507
+ rl.close();
508
+ resolve(answer);
509
+ });
510
+ } else {
511
+ rl.question(question, (answer) => {
512
+ rl.close();
513
+ resolve(answer);
514
+ });
515
+ }
516
+ });
517
+ }
518
+ function sleep(ms) {
519
+ return new Promise((resolve) => setTimeout(resolve, ms));
520
+ }
521
+ async function loginWithGitHub(config) {
522
+ const api = new MCPHostingAPI();
523
+ const spinner = Logger.spinner("Starting GitHub login...");
524
+ try {
525
+ const deviceFlow = await api.githubDeviceStart();
526
+ spinner.stop();
527
+ console.log("");
528
+ console.log(chalk2.bold(" \u{1F510} GitHub Login"));
529
+ console.log("");
530
+ console.log(` ${chalk2.dim("1.")} Open ${chalk2.cyan.underline(deviceFlow.verification_uri)}`);
531
+ console.log(` ${chalk2.dim("2.")} Enter code: ${chalk2.bold.yellow(deviceFlow.user_code)}`);
532
+ console.log("");
533
+ try {
534
+ await open(deviceFlow.verification_uri);
535
+ Logger.dim(" Browser opened automatically.");
536
+ } catch {
537
+ Logger.dim(" Open the URL above in your browser.");
538
+ }
539
+ console.log("");
540
+ const pollSpinner = Logger.spinner("Waiting for GitHub authorization...");
541
+ const pollInterval = (deviceFlow.interval || 5) * 1e3;
542
+ const maxAttempts = Math.ceil((deviceFlow.expires_in || 900) / (deviceFlow.interval || 5));
543
+ let currentInterval = pollInterval;
544
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
545
+ await sleep(currentInterval);
546
+ const result = await api.githubDevicePoll(deviceFlow.device_code);
547
+ if (result.token && result.user) {
548
+ config.token = result.token;
549
+ config.refreshToken = result.refresh_token;
550
+ config.user = { email: result.user.email, org: result.user.github_username };
551
+ pollSpinner.succeed(
552
+ result.is_new_user ? `Account created and logged in as ${chalk2.bold(result.user.github_username || result.user.email)}` : `Logged in as ${chalk2.bold(result.user.github_username || result.user.email)}`
553
+ );
554
+ console.log("");
555
+ Logger.info(`Token stored in ${chalk2.dim("~/.config/mcphosting/")}`);
556
+ Logger.info(`Run ${chalk2.cyan("mcphosting deploy")} to deploy your first MCP server!`);
557
+ return;
558
+ }
559
+ if (result.error === "authorization_pending") {
560
+ continue;
561
+ }
562
+ if (result.error === "slow_down") {
563
+ currentInterval = (result.interval || 10) * 1e3;
564
+ continue;
565
+ }
566
+ pollSpinner.fail("GitHub login failed");
567
+ Logger.error(result.error || "Unknown error during GitHub authorization");
568
+ process.exit(1);
569
+ }
570
+ pollSpinner.fail("GitHub login timed out");
571
+ Logger.error("Authorization expired. Please try again.");
572
+ process.exit(1);
573
+ } catch (error) {
574
+ spinner.fail("GitHub login failed");
575
+ Logger.error(error.message || "Failed to start GitHub device flow");
576
+ process.exit(1);
577
+ }
578
+ }
579
+ function createLoginCommand() {
580
+ return new Command("login").description("Authenticate with MCPHosting").option("--email <email>", "Email address").option("--token <token>", "Provide API token directly").option("--github", "Login with GitHub (recommended)").option("--browser", "Use browser-based login").option("--json", "Output as JSON").action(async (options) => {
240
581
  const config = new Config();
582
+ const isJson = options.json || Logger.isJsonMode;
583
+ if (process.env.MCPHOSTING_TOKEN && !options.token && !options.github && !options.email) {
584
+ const api = new MCPHostingAPI(process.env.MCPHOSTING_TOKEN);
585
+ const user = await api.whoami();
586
+ if (isJson) {
587
+ console.log(JSON.stringify({
588
+ success: true,
589
+ email: user?.email || "unknown",
590
+ token_source: "environment"
591
+ }));
592
+ } else {
593
+ Logger.success(`Authenticated via MCPHOSTING_TOKEN env var`);
594
+ if (user) Logger.info(` User: ${chalk2.bold(user.email)}`);
595
+ }
596
+ return;
597
+ }
598
+ if (options.github) {
599
+ await loginWithGitHub(config);
600
+ return;
601
+ }
241
602
  if (options.token) {
242
603
  config.token = options.token;
243
604
  const api = new MCPHostingAPI(options.token);
244
605
  const user = await api.whoami();
245
606
  if (user) {
246
607
  config.user = user;
247
- Logger.success(`Logged in as ${user.email}${user.org ? ` (${user.org})` : ""}`);
608
+ if (isJson) {
609
+ console.log(JSON.stringify({ success: true, email: user.email }));
610
+ } else {
611
+ Logger.success(`Logged in as ${chalk2.bold(user.email)}`);
612
+ }
248
613
  } else {
249
- Logger.warning("Token saved, but could not verify user info");
614
+ if (isJson) {
615
+ console.log(JSON.stringify({ success: true, warning: "Token saved but could not verify identity" }));
616
+ } else {
617
+ Logger.warning("Token saved, but could not verify identity.");
618
+ }
619
+ }
620
+ return;
621
+ }
622
+ if (!process.stdin.isTTY && !options.email) {
623
+ if (isJson) {
624
+ console.log(JSON.stringify({
625
+ success: false,
626
+ error: "Interactive login requires a TTY. Use --token or MCPHOSTING_TOKEN env var for scripted usage."
627
+ }));
628
+ } else {
629
+ Logger.error("Interactive login requires a TTY.");
630
+ Logger.info("Use one of:");
631
+ Logger.dim(` ${chalk2.cyan("mcphosting login --token <token>")}`);
632
+ Logger.dim(` ${chalk2.cyan("export MCPHOSTING_TOKEN=<token>")}`);
633
+ }
634
+ process.exit(1);
635
+ }
636
+ if (options.email || !process.stdout.isTTY) {
637
+ const email = options.email || await prompt(chalk2.cyan("Email: "));
638
+ const password = await prompt(chalk2.cyan("Password: "), true);
639
+ if (!email || !password) {
640
+ Logger.error("Email and password are required.");
641
+ process.exit(1);
642
+ }
643
+ const spinner2 = Logger.spinner("Logging in...");
644
+ try {
645
+ const api = new MCPHostingAPI();
646
+ const result = await api.login(email, password);
647
+ config.token = result.token;
648
+ config.refreshToken = result.refresh_token;
649
+ config.user = result.user;
650
+ spinner2.succeed(`Logged in as ${chalk2.bold(result.user.email)}`);
651
+ console.log("");
652
+ Logger.info(`Token stored in ${chalk2.dim("~/.config/mcphosting/")}`);
653
+ Logger.info(`Run ${chalk2.cyan("mcphosting deploy")} to deploy your first MCP server!`);
654
+ } catch (error) {
655
+ spinner2.fail("Login failed");
656
+ Logger.error(error.message || "Invalid email or password.");
657
+ process.exit(1);
250
658
  }
251
659
  return;
252
660
  }
253
- const spinner = Logger.spinner("Starting login flow...");
661
+ if (process.stdout.isTTY) {
662
+ console.log("");
663
+ console.log(chalk2.bold(" \u{1F680} MCPHosting Login"));
664
+ console.log("");
665
+ console.log(` ${chalk2.cyan("mcphosting login --github")} ${chalk2.dim("Login with GitHub (recommended)")}`);
666
+ console.log(` ${chalk2.cyan("mcphosting login --email")} ${chalk2.dim("Login with email/password")}`);
667
+ console.log(` ${chalk2.cyan("mcphosting login --browser")} ${chalk2.dim("Login via browser")}`);
668
+ console.log(` ${chalk2.cyan("mcphosting login --token")} ${chalk2.dim("Use existing API token")}`);
669
+ console.log("");
670
+ Logger.info("Starting GitHub login (use --email for email/password)...");
671
+ console.log("");
672
+ await loginWithGitHub(config);
673
+ return;
674
+ }
675
+ const spinner = Logger.spinner("Opening browser for login...");
254
676
  let server;
255
- let resolved = false;
256
677
  try {
257
678
  const authPromise = new Promise((resolve, reject) => {
258
679
  server = createServer((req, res) => {
@@ -263,9 +684,9 @@ function createAuthCommands() {
263
684
  res.writeHead(200, { "Content-Type": "text/html" });
264
685
  res.end(`
265
686
  <html>
266
- <body style="font-family: system-ui; text-align: center; padding: 50px;">
687
+ <body style="font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff;">
267
688
  <h2>\u2705 Login Successful!</h2>
268
- <p>You can now close this window and return to your terminal.</p>
689
+ <p style="color: #888;">You can close this window and return to your terminal.</p>
269
690
  </body>
270
691
  </html>
271
692
  `);
@@ -274,9 +695,9 @@ function createAuthCommands() {
274
695
  res.writeHead(400, { "Content-Type": "text/html" });
275
696
  res.end(`
276
697
  <html>
277
- <body style="font-family: system-ui; text-align: center; padding: 50px;">
698
+ <body style="font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff;">
278
699
  <h2>\u274C Login Failed</h2>
279
- <p>No token received. Please try again.</p>
700
+ <p style="color: #888;">No token received. Please try again.</p>
280
701
  </body>
281
702
  </html>
282
703
  `);
@@ -291,29 +712,32 @@ function createAuthCommands() {
291
712
  const port = server.address().port;
292
713
  const callbackUrl = `http://localhost:${port}/callback`;
293
714
  const authUrl = `https://mcphosting.com/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
715
+ spinner.text = "Waiting for browser login...";
294
716
  setTimeout(() => {
295
- if (!resolved) {
296
- reject(new Error("Login timeout - please try again"));
297
- }
717
+ reject(new Error("Login timed out after 2 minutes. Try again."));
298
718
  }, 12e4);
299
719
  open(authUrl).catch(() => {
300
- Logger.warning(`Could not open browser automatically. Please visit: ${authUrl}`);
720
+ spinner.info("Could not open browser automatically.");
721
+ Logger.info(`Open this URL manually: ${chalk2.blue(authUrl)}`);
301
722
  });
302
723
  });
303
724
  });
304
725
  const token = await authPromise;
305
- resolved = true;
306
726
  spinner.succeed("Login successful!");
307
727
  config.token = token;
308
728
  const api = new MCPHostingAPI(token);
309
729
  const user = await api.whoami();
310
730
  if (user) {
311
731
  config.user = user;
312
- Logger.success(`Logged in as ${user.email}${user.org ? ` (${user.org})` : ""}`);
732
+ Logger.success(`Logged in as ${chalk2.bold(user.email)}`);
313
733
  }
734
+ console.log("");
735
+ Logger.info(`Run ${chalk2.cyan("mcphosting deploy")} to deploy your first MCP server!`);
314
736
  } catch (error) {
315
737
  spinner.fail("Login failed");
316
- Logger.error(`Login error: ${error}`);
738
+ Logger.error(error.message);
739
+ console.log("");
740
+ Logger.info(`Alternative: ${chalk2.cyan("mcphosting login --github")}`);
317
741
  process.exit(1);
318
742
  } finally {
319
743
  if (server) {
@@ -321,58 +745,173 @@ function createAuthCommands() {
321
745
  }
322
746
  }
323
747
  });
324
- auth.command("logout").description("Remove stored authentication").action(() => {
325
- const config = new Config();
326
- config.token = void 0;
327
- config.user = void 0;
328
- Logger.success("Logged out successfully");
329
- });
330
- auth.command("whoami").description("Show current user information").action(async () => {
748
+ }
749
+
750
+ // src/commands/logout.ts
751
+ import { Command as Command2 } from "commander";
752
+ function createLogoutCommand() {
753
+ return new Command2("logout").description("Log out and remove stored credentials").option("--json", "Output as JSON").action((options) => {
331
754
  const config = new Config();
332
- const token = config.token;
333
- if (!token) {
334
- Logger.warning("Not logged in. Use `mcphost auth login` to authenticate.");
755
+ const isJson = options.json || Logger.isJsonMode;
756
+ if (!config.token) {
757
+ if (isJson) {
758
+ console.log(JSON.stringify({ success: true, message: "Already logged out" }));
759
+ } else {
760
+ Logger.info("Already logged out.");
761
+ }
335
762
  return;
336
763
  }
337
- const user = config.user;
338
- if (user) {
339
- Logger.info(`Logged in as: ${user.email}${user.org ? ` (${user.org})` : ""}`);
764
+ const email = config.user?.email;
765
+ config.token = void 0;
766
+ config.user = void 0;
767
+ if (isJson) {
768
+ console.log(JSON.stringify({ success: true, email: email || void 0 }));
769
+ } else if (email) {
770
+ Logger.success(`Logged out from ${email}`);
340
771
  } else {
341
- Logger.warning("User info not available. Try logging in again.");
772
+ Logger.success("Logged out successfully.");
342
773
  }
343
774
  });
344
- return auth;
345
775
  }
346
- function createLegacyAuthCommands() {
776
+
777
+ // src/commands/deploy.ts
778
+ import { Command as Command3 } from "commander";
779
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
780
+ import { join as join3 } from "path";
781
+
782
+ // src/lib/auth.ts
783
+ function decodeJwtPayload(token) {
784
+ try {
785
+ const parts = token.split(".");
786
+ if (parts.length !== 3) return null;
787
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
788
+ const json = Buffer.from(base64, "base64").toString("utf-8");
789
+ return JSON.parse(json);
790
+ } catch {
791
+ return null;
792
+ }
793
+ }
794
+ function isTokenExpiringSoon(token, thresholdSeconds = 300) {
795
+ const payload = decodeJwtPayload(token);
796
+ if (!payload || !payload.exp) return false;
797
+ const nowSeconds = Math.floor(Date.now() / 1e3);
798
+ return payload.exp - nowSeconds < thresholdSeconds;
799
+ }
800
+ function getAuthenticatedAPI() {
347
801
  const config = new Config();
348
- const login2 = new Command("login").description("Authenticate with MCPHosting").option("--token <token>", "Provide API token directly").action(async (options) => {
349
- const authCmd = createAuthCommands();
350
- const loginCmd = authCmd.commands.find((cmd) => cmd.name() === "login");
351
- if (loginCmd) {
352
- await loginCmd.action(options);
353
- }
354
- });
355
- const logout2 = new Command("logout").description("Remove stored authentication").action(() => {
356
- config.token = void 0;
357
- config.user = void 0;
358
- Logger.success("Logged out successfully");
359
- });
360
- const whoami2 = new Command("whoami").description("Show current user information").action(async () => {
361
- const authCmd = createAuthCommands();
362
- const whoamiCmd = authCmd.commands.find((cmd) => cmd.name() === "whoami");
363
- if (whoamiCmd) {
364
- await whoamiCmd.action();
802
+ let token = config.token;
803
+ if (!token) {
804
+ Logger.error("Not logged in.");
805
+ Logger.info("Run `mcphosting login` to authenticate.");
806
+ process.exit(1);
807
+ }
808
+ if (isTokenExpiringSoon(token) && !config.isEnvToken) {
809
+ if (config.refreshToken) {
810
+ Logger.warning("Token expiring soon. Use async auth for auto-refresh, or run `mcphosting login`.");
811
+ } else {
812
+ Logger.warning("Token is expiring soon. Run `mcphosting login` to re-authenticate.");
365
813
  }
366
- });
367
- return [login2, logout2, whoami2];
814
+ }
815
+ const api = new MCPHostingAPI(token);
816
+ return { api, config };
817
+ }
818
+ function isAuthenticated() {
819
+ const config = new Config();
820
+ return !!config.token;
368
821
  }
369
822
 
370
- // src/commands/connect.ts
371
- import { Command as Command2 } from "commander";
823
+ // src/lib/detector.ts
824
+ import { readFile, readdir } from "fs/promises";
825
+ import { join, basename } from "path";
826
+ async function detectMCP(dir) {
827
+ try {
828
+ const files = await readdir(dir);
829
+ const configFiles = files.filter(
830
+ (f) => ["mcp.json", "mcp.config.json", "mcp.yaml", ".mcprc", "mcp.config.ts", "mcp.config.js"].includes(f)
831
+ );
832
+ let packageJson = null;
833
+ let hasMcpSdk = false;
834
+ let runtime = "unknown";
835
+ let entryPoint;
836
+ if (files.includes("package.json")) {
837
+ try {
838
+ const content = await readFile(join(dir, "package.json"), "utf-8");
839
+ packageJson = JSON.parse(content);
840
+ hasMcpSdk = !!(packageJson?.dependencies?.["@modelcontextprotocol/sdk"] || packageJson?.devDependencies?.["@modelcontextprotocol/sdk"]);
841
+ if (hasMcpSdk || packageJson?.dependencies?.["@modelcontextprotocol/sdk"]) {
842
+ runtime = "node";
843
+ }
844
+ if (packageJson?.main) {
845
+ entryPoint = packageJson.main;
846
+ } else if (packageJson?.bin) {
847
+ const bins = typeof packageJson.bin === "string" ? packageJson.bin : Object.values(packageJson.bin)[0];
848
+ entryPoint = bins;
849
+ }
850
+ } catch {
851
+ }
852
+ }
853
+ if (files.includes("pyproject.toml") || files.includes("requirements.txt") || files.includes("setup.py")) {
854
+ try {
855
+ let pythonDeps = "";
856
+ if (files.includes("requirements.txt")) {
857
+ pythonDeps = await readFile(join(dir, "requirements.txt"), "utf-8");
858
+ } else if (files.includes("pyproject.toml")) {
859
+ pythonDeps = await readFile(join(dir, "pyproject.toml"), "utf-8");
860
+ }
861
+ if (pythonDeps.includes("mcp") || pythonDeps.includes("modelcontextprotocol")) {
862
+ hasMcpSdk = true;
863
+ runtime = "python";
864
+ }
865
+ } catch {
866
+ }
867
+ if (files.includes("server.py")) entryPoint = "server.py";
868
+ else if (files.includes("main.py")) entryPoint = "main.py";
869
+ else if (files.includes("app.py")) entryPoint = "app.py";
870
+ }
871
+ if (!hasMcpSdk && configFiles.length === 0) {
872
+ const sourceFiles = files.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
873
+ for (const sf of sourceFiles.slice(0, 5)) {
874
+ try {
875
+ const content = await readFile(join(dir, sf), "utf-8");
876
+ if (content.includes("@modelcontextprotocol/sdk") || content.includes("McpServer") || content.includes("mcp.server")) {
877
+ hasMcpSdk = true;
878
+ runtime = sf.endsWith(".py") ? "python" : "node";
879
+ if (!entryPoint) entryPoint = sf;
880
+ break;
881
+ }
882
+ } catch {
883
+ }
884
+ }
885
+ }
886
+ if (!hasMcpSdk && configFiles.length === 0) {
887
+ return null;
888
+ }
889
+ let mcpConfig = null;
890
+ if (configFiles.length > 0) {
891
+ try {
892
+ const configContent = await readFile(join(dir, configFiles[0]), "utf-8");
893
+ mcpConfig = JSON.parse(configContent);
894
+ } catch {
895
+ }
896
+ }
897
+ const name = packageJson?.name || basename(dir);
898
+ return {
899
+ name,
900
+ hasMcpSdk,
901
+ configFiles,
902
+ packageJson,
903
+ mcpConfig,
904
+ runtime,
905
+ entryPoint
906
+ };
907
+ } catch {
908
+ return null;
909
+ }
910
+ }
372
911
 
373
912
  // src/lib/clients.ts
374
- import { readFile, writeFile, access } from "fs/promises";
375
- import { join, dirname } from "path";
913
+ import { readFile as readFile2, writeFile, access } from "fs/promises";
914
+ import { join as join2, dirname } from "path";
376
915
  import { homedir } from "os";
377
916
  import { mkdir } from "fs/promises";
378
917
  var ClientManager = class {
@@ -380,10 +919,10 @@ var ClientManager = class {
380
919
  const home = homedir();
381
920
  const platform = process.platform;
382
921
  const paths = {
383
- claude: platform === "win32" ? join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
384
- cursor: join(home, ".cursor", "mcp.json"),
385
- vscode: join(process.cwd(), ".vscode", "mcp.json"),
386
- openclaw: join(home, ".openclaw", "mcp.json"),
922
+ claude: platform === "win32" ? join2(process.env.APPDATA || "", "Claude", "claude_desktop_config.json") : join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
923
+ cursor: join2(home, ".cursor", "mcp.json"),
924
+ vscode: join2(process.cwd(), ".vscode", "mcp.json"),
925
+ openclaw: join2(home, ".openclaw", "mcp.json"),
387
926
  chatgpt: ""
388
927
  // Web-based, no local config
389
928
  };
@@ -428,7 +967,7 @@ var ClientManager = class {
428
967
  await mkdir(dirname(configPath), { recursive: true });
429
968
  let config = { mcpServers: {} };
430
969
  try {
431
- const configContent = await readFile(configPath, "utf-8");
970
+ const configContent = await readFile2(configPath, "utf-8");
432
971
  config = JSON.parse(configContent);
433
972
  if (!config.mcpServers) {
434
973
  config.mcpServers = {};
@@ -459,7 +998,7 @@ var ClientManager = class {
459
998
  throw new Error(`Unsupported client: ${client}`);
460
999
  }
461
1000
  try {
462
- const configContent = await readFile(configPath, "utf-8");
1001
+ const configContent = await readFile2(configPath, "utf-8");
463
1002
  const config = JSON.parse(configContent);
464
1003
  if (config.mcpServers && config.mcpServers[slug]) {
465
1004
  delete config.mcpServers[slug];
@@ -510,29 +1049,368 @@ var ClientManager = class {
510
1049
  }
511
1050
  };
512
1051
 
513
- // src/commands/connect.ts
514
- import chalk2 from "chalk";
515
- function createConnectCommands() {
516
- const connect = new Command2("connect").description("Connect an MCP server to AI clients").argument("<url-or-slug>", "MCP server URL or slug").option("--client <client>", "Target specific client (claude, cursor, vscode, openclaw, chatgpt)").option("--name <name>", "Custom name for the connection").action(async (urlOrSlug, options) => {
517
- const config = new Config();
518
- const spinner = Logger.spinner("Setting up MCP connection...");
519
- try {
520
- const url = ClientManager.resolveUrl(urlOrSlug);
521
- const slug = ClientManager.extractSlug(url);
522
- const name = options.name || slug;
523
- const existing = config.findConnection(slug);
524
- if (existing) {
525
- spinner.warn(`Already connected to ${slug}`);
526
- Logger.info(`Use \`mcphost disconnect ${slug}\` to remove first`);
527
- return;
1052
+ // src/commands/deploy.ts
1053
+ import chalk3 from "chalk";
1054
+ var TEMPLATES = ["weather", "crypto", "notion", "postgres", "blank"];
1055
+ function createDeployCommand() {
1056
+ return new Command3("deploy").description("Deploy an MCP server to MCPHosting (one command)").option("--github <url>", "Deploy from a GitHub repository URL").option("--name <name>", "Server name (auto-detected if not provided)").option("--template <name>", "Deploy from a template (weather, crypto, notion, postgres, blank)").option("--api-url <url>", "Your MCP server's API URL (for external servers)").option("--auth <type>", "Auth type: none, api_key, or oauth", "none").option("--auto-key", "Automatically create an API key", true).option("--no-auto-key", "Skip automatic API key creation").option("--configure", "Automatically configure detected AI clients", false).option("--json", "Output result as JSON").option("--silent", "Suppress interactive output").action(async (options) => {
1057
+ const isJson = options.json || Logger.isJsonMode;
1058
+ const isSilent = options.silent || Logger.isSilent;
1059
+ const { api, config } = getAuthenticatedAPI();
1060
+ if (!isJson && !isSilent) {
1061
+ console.log("");
1062
+ console.log(chalk3.bold("\u{1F680} MCPHosting Deploy"));
1063
+ console.log(chalk3.dim(" One command. Your MCP server goes live."));
1064
+ console.log("");
1065
+ }
1066
+ if (options.template) {
1067
+ const template = options.template.toLowerCase();
1068
+ if (!TEMPLATES.includes(template)) {
1069
+ Logger.error(`Unknown template: ${template}`);
1070
+ console.log("");
1071
+ Logger.info("Available templates:");
1072
+ for (const t of TEMPLATES) {
1073
+ console.log(` ${chalk3.cyan(t)}`);
1074
+ }
1075
+ process.exit(1);
528
1076
  }
529
- const clients = [];
530
- if (options.client) {
1077
+ const name2 = options.name || `mcp-${template}`;
1078
+ const spinner2 = Logger.spinner(`Deploying template ${chalk3.cyan(template)}...`);
1079
+ try {
1080
+ const result = await api.oneClickDeploy({
1081
+ name: name2,
1082
+ template,
1083
+ authType: options.auth,
1084
+ autoKey: options.autoKey
1085
+ });
1086
+ spinner2.succeed(`Template ${chalk3.cyan(template)} deployed!`);
1087
+ if (isJson) {
1088
+ Logger.json(result);
1089
+ } else {
1090
+ printOneClickResult(result, options.configure);
1091
+ }
1092
+ if (options.configure && result.connectionUrl) {
1093
+ await autoConfigureClients(result.slug, result.connectionUrl);
1094
+ }
1095
+ } catch (error) {
1096
+ spinner2.fail("Deploy failed");
1097
+ Logger.error(error.message);
1098
+ process.exit(1);
1099
+ }
1100
+ return;
1101
+ }
1102
+ if (options.github) {
1103
+ const githubUrl2 = options.github;
1104
+ const name2 = options.name || extractRepoName(githubUrl2);
1105
+ const spinner2 = Logger.spinner(`Deploying from ${chalk3.cyan(githubUrl2)}...`);
1106
+ try {
1107
+ const result = await api.oneClickDeploy({
1108
+ name: name2,
1109
+ githubUrl: githubUrl2,
1110
+ authType: options.auth,
1111
+ autoKey: options.autoKey
1112
+ });
1113
+ spinner2.succeed("Deployed from GitHub!");
1114
+ if (isJson) {
1115
+ Logger.json(result);
1116
+ } else {
1117
+ printOneClickResult(result, options.configure);
1118
+ }
1119
+ if (options.configure && result.connectionUrl) {
1120
+ await autoConfigureClients(result.slug, result.connectionUrl);
1121
+ }
1122
+ } catch (error) {
1123
+ spinner2.fail("Deploy failed");
1124
+ Logger.error(error.message);
1125
+ process.exit(1);
1126
+ }
1127
+ return;
1128
+ }
1129
+ if (options.apiUrl) {
1130
+ const name2 = options.name || new URL(options.apiUrl).hostname;
1131
+ const spinner2 = Logger.spinner(`Registering ${chalk3.cyan(name2)}...`);
1132
+ try {
1133
+ const result = await api.oneClickDeploy({
1134
+ name: name2,
1135
+ baseApiUrl: options.apiUrl,
1136
+ authType: options.auth,
1137
+ autoKey: options.autoKey
1138
+ });
1139
+ spinner2.succeed("Registered successfully!");
1140
+ if (isJson) {
1141
+ Logger.json(result);
1142
+ } else {
1143
+ printOneClickResult(result, options.configure);
1144
+ }
1145
+ if (options.configure && result.connectionUrl) {
1146
+ await autoConfigureClients(result.slug, result.connectionUrl);
1147
+ }
1148
+ } catch (error) {
1149
+ spinner2.fail("Registration failed");
1150
+ Logger.error(error.message);
1151
+ process.exit(1);
1152
+ }
1153
+ return;
1154
+ }
1155
+ const dir = process.cwd();
1156
+ let mcphostingConfig = null;
1157
+ try {
1158
+ const configContent = await readFile3(join3(dir, "mcphosting.json"), "utf-8");
1159
+ mcphostingConfig = JSON.parse(configContent);
1160
+ } catch {
1161
+ }
1162
+ const spinner = Logger.spinner("Detecting MCP server...");
1163
+ const detected = await detectMCP(dir);
1164
+ if (!detected && !mcphostingConfig) {
1165
+ spinner.fail("No MCP server detected");
1166
+ console.log("");
1167
+ Logger.info("This directory doesn't look like an MCP server project.");
1168
+ console.log("");
1169
+ Logger.info(chalk3.bold("Quick options:"));
1170
+ console.log(` ${chalk3.cyan("mcphosting deploy --template weather")} Deploy a template`);
1171
+ console.log(` ${chalk3.cyan("mcphosting deploy --github <url>")} Deploy from GitHub`);
1172
+ console.log(` ${chalk3.cyan("mcphosting deploy --api-url <url>")} Register external server`);
1173
+ console.log(` ${chalk3.cyan("mcphosting init")} Create mcphosting.json`);
1174
+ console.log("");
1175
+ process.exit(1);
1176
+ }
1177
+ const name = options.name || mcphostingConfig?.name || detected?.name || "my-mcp-server";
1178
+ spinner.succeed(`Detected: ${chalk3.bold(name)}`);
1179
+ if (detected) {
1180
+ Logger.info(` SDK: ${detected.hasMcpSdk ? chalk3.green("\u2713") : chalk3.yellow("\u25CB")} @modelcontextprotocol/sdk`);
1181
+ if (detected.runtime !== "unknown") {
1182
+ Logger.info(` Runtime: ${detected.runtime}`);
1183
+ }
1184
+ }
1185
+ let githubUrl = mcphostingConfig?.github || null;
1186
+ if (!githubUrl) {
1187
+ try {
1188
+ const { execSync } = await import("child_process");
1189
+ const remoteUrl = execSync("git remote get-url origin", { cwd: dir, encoding: "utf-8" }).trim();
1190
+ if (remoteUrl.includes("github.com")) {
1191
+ githubUrl = remoteUrl.replace(/^git@github\.com:/, "https://github.com/").replace(/\.git$/, "");
1192
+ }
1193
+ } catch {
1194
+ }
1195
+ }
1196
+ if (githubUrl) {
1197
+ Logger.info(` GitHub: ${chalk3.dim(githubUrl)}`);
1198
+ }
1199
+ console.log("");
1200
+ const deploySpinner = Logger.spinner(`Deploying ${chalk3.bold(name)}...`);
1201
+ try {
1202
+ const result = await api.oneClickDeploy({
1203
+ name,
1204
+ slug: mcphostingConfig?.slug,
1205
+ githubUrl: githubUrl || void 0,
1206
+ authType: options.auth,
1207
+ autoKey: options.autoKey,
1208
+ vercelProjectUrl: mcphostingConfig?.vercelUrl
1209
+ });
1210
+ deploySpinner.succeed("Deployed successfully!");
1211
+ if (mcphostingConfig) {
1212
+ try {
1213
+ mcphostingConfig.connectionUrl = result.connectionUrl;
1214
+ mcphostingConfig.projectId = result.projectId;
1215
+ mcphostingConfig.slug = result.slug;
1216
+ await writeFile2(join3(dir, "mcphosting.json"), JSON.stringify(mcphostingConfig, null, 2) + "\n");
1217
+ } catch {
1218
+ }
1219
+ }
1220
+ if (isJson) {
1221
+ Logger.json(result);
1222
+ } else {
1223
+ printOneClickResult(result, options.configure);
1224
+ }
1225
+ if (options.configure && result.connectionUrl) {
1226
+ await autoConfigureClients(result.slug, result.connectionUrl);
1227
+ }
1228
+ } catch (error) {
1229
+ deploySpinner.fail("Deploy failed");
1230
+ Logger.error(error.message);
1231
+ if (error.message.includes("Authentication") || error.message.includes("Unauthorized")) {
1232
+ Logger.info(`Run ${chalk3.cyan("mcphosting login")} first.`);
1233
+ }
1234
+ process.exit(1);
1235
+ }
1236
+ });
1237
+ }
1238
+ function extractRepoName(url) {
1239
+ const parts = url.replace(/\.git$/, "").split("/");
1240
+ return parts[parts.length - 1] || "mcp-server";
1241
+ }
1242
+ function printOneClickResult(result, showConfigureHint = false) {
1243
+ console.log("");
1244
+ console.log(chalk3.green.bold(" \u2705 Your MCP server is live!"));
1245
+ console.log("");
1246
+ Logger.info(` ${chalk3.dim("Endpoint:")} ${chalk3.blue(result.connectionUrl)}`);
1247
+ if (result.slug) {
1248
+ Logger.info(` ${chalk3.dim("Slug:")} ${chalk3.cyan(result.slug)}`);
1249
+ }
1250
+ if (result.status) {
1251
+ const statusColor = result.status === "deployed" ? chalk3.green : chalk3.yellow;
1252
+ Logger.info(` ${chalk3.dim("Status:")} ${statusColor(result.status)}`);
1253
+ }
1254
+ if (result.apiKey?.key) {
1255
+ console.log("");
1256
+ console.log(chalk3.bold(" \u{1F511} API Key (save this \u2014 shown only once):"));
1257
+ console.log(` ${chalk3.green(result.apiKey.key)}`);
1258
+ }
1259
+ if (result.vercelDeployUrl) {
1260
+ console.log("");
1261
+ console.log(chalk3.bold(" \u{1F310} Hosted on mcphosting.com infrastructure"));
1262
+ console.log(chalk3.dim(" Your server is live and managed by MCPHosting."));
1263
+ }
1264
+ if (result.clientConfig) {
1265
+ console.log("");
1266
+ console.log(chalk3.bold(" \u{1F4CB} Add to your AI client:"));
1267
+ console.log("");
1268
+ console.log(chalk3.dim(" Claude Desktop (claude_desktop_config.json):"));
1269
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1270
+ console.log(chalk3.white(` ${JSON.stringify(result.clientConfig.claude, null, 2).split("\n").join("\n ")}`));
1271
+ console.log("");
1272
+ console.log(chalk3.dim(" Cursor / VS Code (.cursor/mcp.json):"));
1273
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1274
+ console.log(chalk3.white(` ${JSON.stringify(result.clientConfig.cursor, null, 2).split("\n").join("\n ")}`));
1275
+ }
1276
+ console.log("");
1277
+ console.log(chalk3.dim(" Quick connect:"));
1278
+ console.log(chalk3.cyan(` mcphosting connect ${result.slug}`));
1279
+ if (showConfigureHint) {
1280
+ console.log("");
1281
+ console.log(chalk3.dim(" Auto-configure AI clients:"));
1282
+ console.log(chalk3.cyan(` mcphosting deploy --configure`));
1283
+ }
1284
+ console.log("");
1285
+ Logger.info(`Manage at ${chalk3.blue(`https://mcphosting.com/dashboard/servers/${result.projectId || result.slug}`)}`);
1286
+ console.log("");
1287
+ }
1288
+ async function autoConfigureClients(slug, url) {
1289
+ const clients = await ClientManager.detectInstalledClients();
1290
+ const installed = clients.filter((c) => c.exists);
1291
+ if (installed.length === 0) {
1292
+ Logger.info("No AI clients detected. Add the config manually (shown above).");
1293
+ return;
1294
+ }
1295
+ for (const client of installed) {
1296
+ try {
1297
+ const success = await ClientManager.addToClient(client.name, slug, url);
1298
+ if (success) {
1299
+ Logger.success(`Configured ${chalk3.bold(client.name)}`);
1300
+ }
1301
+ } catch (error) {
1302
+ Logger.warning(`Failed to configure ${client.name}: ${error.message}`);
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ // src/commands/init.ts
1308
+ import { Command as Command4 } from "commander";
1309
+ import { writeFile as writeFile3, access as access3 } from "fs/promises";
1310
+ import { join as join4, basename as basename2 } from "path";
1311
+ import chalk4 from "chalk";
1312
+ var TEMPLATES2 = [
1313
+ { name: "weather", description: "Weather data MCP server", repo: "gorlomi-enzo/mcp-template-weather" },
1314
+ { name: "crypto", description: "Crypto prices & portfolio MCP", repo: "gorlomi-enzo/mcp-template-crypto" },
1315
+ { name: "notion", description: "Notion integration MCP", repo: "gorlomi-enzo/mcp-template-notion" },
1316
+ { name: "postgres", description: "PostgreSQL query MCP", repo: "gorlomi-enzo/mcp-template-postgres" },
1317
+ { name: "blank", description: "Minimal MCP server scaffold", repo: "gorlomi-enzo/mcp-template-blank" }
1318
+ ];
1319
+ function createInitCommand() {
1320
+ return new Command4("init").description("Initialize mcphosting.json in the current directory").option("--name <name>", "Project name").option("--template <template>", "Initialize from a template").option("--list-templates", "List available templates").action(async (options) => {
1321
+ console.log("");
1322
+ console.log(chalk4.bold("\u{1F4E6} MCPHosting Init"));
1323
+ console.log("");
1324
+ if (options.listTemplates) {
1325
+ console.log(chalk4.bold("Available templates:"));
1326
+ console.log("");
1327
+ for (const t of TEMPLATES2) {
1328
+ console.log(` ${chalk4.cyan(t.name.padEnd(12))} ${chalk4.dim(t.description)}`);
1329
+ console.log(` ${chalk4.dim(` github.com/${t.repo}`)}`);
1330
+ console.log("");
1331
+ }
1332
+ console.log(chalk4.dim(`Usage: mcphosting init --template <name>`));
1333
+ return;
1334
+ }
1335
+ const dir = process.cwd();
1336
+ const configPath = join4(dir, "mcphosting.json");
1337
+ try {
1338
+ await access3(configPath);
1339
+ Logger.warning("mcphosting.json already exists in this directory.");
1340
+ Logger.info(`Edit it manually or delete it and run ${chalk4.cyan("mcphosting init")} again.`);
1341
+ return;
1342
+ } catch {
1343
+ }
1344
+ const detected = await detectMCP(dir);
1345
+ const projectName = options.name || detected?.name || basename2(dir);
1346
+ const slug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
1347
+ let githubUrl = null;
1348
+ try {
1349
+ const { execSync } = await import("child_process");
1350
+ const remoteUrl = execSync("git remote get-url origin", { cwd: dir, encoding: "utf-8" }).trim();
1351
+ if (remoteUrl.includes("github.com")) {
1352
+ githubUrl = remoteUrl.replace(/^git@github\.com:/, "https://github.com/").replace(/\.git$/, "");
1353
+ }
1354
+ } catch {
1355
+ }
1356
+ const config = {
1357
+ $schema: "https://mcphosting.com/schema/mcphosting.json",
1358
+ name: projectName,
1359
+ slug,
1360
+ version: "1.0.0",
1361
+ server: {
1362
+ transport: "streamable-http",
1363
+ port: 3e3,
1364
+ path: "/mcp"
1365
+ }
1366
+ };
1367
+ if (githubUrl) {
1368
+ config.github = githubUrl;
1369
+ }
1370
+ if (options.template) {
1371
+ config.template = options.template;
1372
+ }
1373
+ await writeFile3(configPath, JSON.stringify(config, null, 2) + "\n");
1374
+ Logger.success(`Created ${chalk4.bold("mcphosting.json")}`);
1375
+ console.log("");
1376
+ console.log(chalk4.dim(JSON.stringify(config, null, 2)));
1377
+ console.log("");
1378
+ if (detected?.hasMcpSdk) {
1379
+ Logger.info(`Detected MCP SDK (${detected.runtime})`);
1380
+ }
1381
+ if (githubUrl) {
1382
+ Logger.info(`GitHub: ${chalk4.blue(githubUrl)}`);
1383
+ }
1384
+ console.log("");
1385
+ Logger.info(`Next: ${chalk4.cyan("mcphosting deploy")} to deploy your MCP server`);
1386
+ console.log("");
1387
+ });
1388
+ }
1389
+
1390
+ // src/commands/connect.ts
1391
+ import { Command as Command5 } from "commander";
1392
+ import chalk5 from "chalk";
1393
+ function createConnectCommand() {
1394
+ return new Command5("connect").description("Connect an MCP server to your AI clients").argument("<url-or-slug>", "MCP server URL or slug (e.g. github, notion, https://my-mcp.com)").option("--client <client>", "Target specific client: claude, cursor, vscode, openclaw, chatgpt").option("--name <name>", "Custom name for the connection").action(async (urlOrSlug, options) => {
1395
+ const config = new Config();
1396
+ const spinner = Logger.spinner("Setting up MCP connection...");
1397
+ try {
1398
+ const url = ClientManager.resolveUrl(urlOrSlug);
1399
+ const slug = ClientManager.extractSlug(url);
1400
+ const name = options.name || slug;
1401
+ const existing = config.findConnection(slug);
1402
+ if (existing) {
1403
+ spinner.warn(`Already connected to ${chalk5.cyan(slug)}`);
1404
+ Logger.info(`Use ${chalk5.cyan(`mcphosting disconnect ${slug}`)} to remove first`);
1405
+ return;
1406
+ }
1407
+ const clients = [];
1408
+ if (options.client) {
531
1409
  const clientName = options.client;
532
1410
  const success = await ClientManager.addToClient(clientName, slug, url);
533
1411
  if (success) {
534
1412
  clients.push(clientName);
535
- spinner.succeed(`Connected to ${clientName}`);
1413
+ spinner.succeed(`Connected ${chalk5.bold(name)} to ${chalk5.green(clientName)}`);
536
1414
  } else {
537
1415
  spinner.fail(`Failed to connect to ${clientName}`);
538
1416
  process.exit(1);
@@ -541,9 +1419,9 @@ function createConnectCommands() {
541
1419
  const detectedClients = await ClientManager.detectInstalledClients();
542
1420
  const availableClients = detectedClients.filter((c) => c.exists);
543
1421
  if (availableClients.length === 0) {
544
- spinner.warn("No supported clients found");
545
- Logger.info("Supported clients: Claude Desktop, Cursor, VS Code, OpenClaw");
546
- Logger.info("Install a client and try again, or use --client chatgpt for web setup");
1422
+ spinner.warn("No supported AI clients detected");
1423
+ Logger.info("Supported: Claude Desktop, Cursor, VS Code, OpenClaw");
1424
+ Logger.info(`Use ${chalk5.cyan("--client chatgpt")} for web-based setup`);
547
1425
  return;
548
1426
  }
549
1427
  spinner.text = `Configuring ${availableClients.length} client${availableClients.length > 1 ? "s" : ""}...`;
@@ -561,86 +1439,470 @@ function createConnectCommands() {
561
1439
  Logger.warning(`Failed to configure ${client.name}: ${error}`);
562
1440
  }
563
1441
  }
564
- spinner.succeed(`Connected to ${clients.length} client${clients.length > 1 ? "s" : ""}`);
1442
+ spinner.succeed(`Connected ${chalk5.bold(name)} to ${clients.length} client${clients.length > 1 ? "s" : ""}`);
565
1443
  }
566
- config.addConnection({
567
- slug,
568
- url,
569
- clients
570
- });
571
- Logger.success(`\u{1F517} ${name} connected successfully!`);
572
- Logger.dim(` URL: ${url}`);
573
- Logger.dim(` Clients: ${clients.join(", ")}`);
574
- console.log("\n" + chalk2.green("\u{1F389} Connected! Share with your team:"));
575
- console.log(chalk2.cyan(`npx mcphosting-cli connect ${urlOrSlug}`));
576
- console.log("\n" + chalk2.yellow("\u2B50 Star us: ") + chalk2.blue("https://github.com/gorlomi-enzo/mcphosting-cli"));
1444
+ config.addConnection({ slug, url, clients });
1445
+ console.log("");
1446
+ Logger.info(` URL: ${chalk5.dim(url)}`);
1447
+ Logger.info(` Clients: ${chalk5.dim(clients.join(", "))}`);
1448
+ console.log("");
1449
+ console.log(chalk5.green("\u{1F389} Share with your team:"));
1450
+ console.log(chalk5.cyan(` npx mcphosting-cli connect ${urlOrSlug}`));
577
1451
  console.log("");
578
1452
  } catch (error) {
579
1453
  spinner.fail("Connection failed");
580
- Logger.error(`Error: ${error}`);
1454
+ Logger.error(error.message);
581
1455
  process.exit(1);
582
1456
  }
583
1457
  });
584
- return connect;
585
1458
  }
586
1459
  function createDisconnectCommand() {
587
- return new Command2("disconnect").description("Disconnect an MCP server").argument("<slug-or-id>", "MCP server slug or connection ID").action(async (slugOrId) => {
1460
+ return new Command5("disconnect").description("Disconnect an MCP server").argument("<slug-or-id>", "MCP server slug or connection ID").action(async (slugOrId) => {
588
1461
  const config = new Config();
589
1462
  const connection = config.findConnection(slugOrId);
590
1463
  if (!connection) {
591
1464
  Logger.error(`Connection not found: ${slugOrId}`);
592
- Logger.info("Use `mcphost list` to see active connections");
1465
+ Logger.info(`Use ${chalk5.cyan("mcphosting list")} to see active connections`);
593
1466
  return;
594
1467
  }
595
1468
  const spinner = Logger.spinner(`Disconnecting ${connection.slug}...`);
596
1469
  try {
597
1470
  const removedFrom = await ClientManager.removeFromAllClients(connection.slug);
598
1471
  config.removeConnection(connection.id);
599
- spinner.succeed(`Disconnected ${connection.slug}`);
1472
+ spinner.succeed(`Disconnected ${chalk5.bold(connection.slug)}`);
600
1473
  if (removedFrom.length > 0) {
601
1474
  Logger.info(`Removed from: ${removedFrom.join(", ")}`);
602
1475
  }
603
1476
  } catch (error) {
604
1477
  spinner.fail("Disconnect failed");
605
- Logger.error(`Error: ${error}`);
1478
+ Logger.error(error.message);
606
1479
  }
607
1480
  });
608
1481
  }
1482
+
1483
+ // src/commands/list.ts
1484
+ import { Command as Command6 } from "commander";
1485
+ import chalk6 from "chalk";
609
1486
  function createListCommand() {
610
- return new Command2("list").description("List connected MCP servers").option("--json", "Output as JSON").action(async (options) => {
1487
+ return new Command6("list").description("List your MCP servers").option("--local", "Show locally connected servers only").option("--remote", "Show deployed servers only (requires login)").option("--json", "Output as JSON").action(async (options) => {
1488
+ const isJson = options.json || Logger.isJsonMode;
1489
+ const showLocal = options.local || !options.local && !options.remote;
1490
+ const showRemote = options.remote || !options.local && !options.remote;
1491
+ if (showLocal) {
1492
+ const config = new Config();
1493
+ const connections = config.connections;
1494
+ if (connections.length > 0) {
1495
+ if (isJson && !showRemote) {
1496
+ Logger.json(connections);
1497
+ return;
1498
+ }
1499
+ console.log("");
1500
+ Logger.bold(`\u{1F4CB} Connected MCP Servers (${connections.length})`);
1501
+ console.log("");
1502
+ const tableData = connections.map((conn) => ({
1503
+ Slug: conn.slug,
1504
+ URL: conn.url.length > 50 ? conn.url.slice(0, 47) + "..." : conn.url,
1505
+ Clients: conn.clients.join(", "),
1506
+ Added: new Date(conn.addedAt).toLocaleDateString()
1507
+ }));
1508
+ Logger.table(tableData);
1509
+ console.log("");
1510
+ } else if (!showRemote) {
1511
+ Logger.info("No local MCP connections.");
1512
+ Logger.dim(`Use ${chalk6.cyan("mcphosting connect <slug>")} to connect one.`);
1513
+ return;
1514
+ }
1515
+ }
1516
+ if (showRemote && isAuthenticated()) {
1517
+ const { api } = getAuthenticatedAPI();
1518
+ const spinner = Logger.spinner("Fetching deployed servers...");
1519
+ try {
1520
+ const servers = await api.listServers();
1521
+ spinner.stop();
1522
+ if (servers.length === 0) {
1523
+ if (!showLocal) {
1524
+ Logger.info("No deployed servers yet.");
1525
+ Logger.dim(`Use ${chalk6.cyan("mcphosting deploy")} to deploy your first MCP server.`);
1526
+ }
1527
+ return;
1528
+ }
1529
+ if (isJson) {
1530
+ Logger.json(servers);
1531
+ return;
1532
+ }
1533
+ Logger.bold(`\u{1F5A5}\uFE0F Deployed Servers (${servers.length})`);
1534
+ console.log("");
1535
+ const tableData = servers.map((s) => ({
1536
+ Name: s.name || s.slug,
1537
+ Status: s.status === "active" ? chalk6.green("\u25CF active") : s.status === "deploying" ? chalk6.yellow("\u25D0 deploying") : s.status === "stopped" ? chalk6.dim("\u25CB stopped") : chalk6.red("\u2717 " + s.status),
1538
+ Slug: s.slug,
1539
+ URL: (s.url || "").length > 45 ? (s.url || "").slice(0, 42) + "..." : s.url || ""
1540
+ }));
1541
+ Logger.table(tableData);
1542
+ console.log("");
1543
+ } catch (error) {
1544
+ spinner.fail("Failed to fetch servers");
1545
+ Logger.error(error.message);
1546
+ }
1547
+ } else if (showRemote && !isAuthenticated()) {
1548
+ Logger.dim(`Log in to see deployed servers: ${chalk6.cyan("mcphosting login")}`);
1549
+ console.log("");
1550
+ }
1551
+ });
1552
+ }
1553
+
1554
+ // src/commands/status.ts
1555
+ import { Command as Command7 } from "commander";
1556
+ import chalk7 from "chalk";
1557
+ function createStatusCommand() {
1558
+ return new Command7("status").description("Check the status of a deployed MCP server").argument("<slug-or-id>", "Server slug or ID").option("--json", "Output as JSON").action(async (slugOrId, options) => {
1559
+ const isJson = options.json || Logger.isJsonMode;
1560
+ const { api } = getAuthenticatedAPI();
1561
+ const spinner = Logger.spinner(`Checking status of ${chalk7.cyan(slugOrId)}...`);
1562
+ try {
1563
+ const server = await api.getServer(slugOrId);
1564
+ spinner.stop();
1565
+ if (isJson) {
1566
+ Logger.json(server);
1567
+ return;
1568
+ }
1569
+ console.log("");
1570
+ console.log(chalk7.bold(`\u{1F4E1} ${server.name || server.slug}`));
1571
+ console.log("");
1572
+ const statusIcon = server.status === "active" ? chalk7.green("\u25CF Active") : server.status === "deploying" ? chalk7.yellow("\u25D0 Deploying") : server.status === "stopped" ? chalk7.dim("\u25CB Stopped") : chalk7.red("\u2717 " + server.status);
1573
+ const info = [
1574
+ { Property: "Status", Value: statusIcon },
1575
+ { Property: "Slug", Value: server.slug },
1576
+ { Property: "URL", Value: server.url || "\u2014" }
1577
+ ];
1578
+ if (server.sseUrl) {
1579
+ info.push({ Property: "SSE URL", Value: server.sseUrl });
1580
+ }
1581
+ if (server.streamableUrl) {
1582
+ info.push({ Property: "Streamable", Value: server.streamableUrl });
1583
+ }
1584
+ if (server.githubUrl) {
1585
+ info.push({ Property: "GitHub", Value: server.githubUrl });
1586
+ }
1587
+ info.push(
1588
+ { Property: "Created", Value: server.createdAt ? new Date(server.createdAt).toLocaleString() : "\u2014" },
1589
+ { Property: "Updated", Value: server.updatedAt ? new Date(server.updatedAt).toLocaleString() : "\u2014" }
1590
+ );
1591
+ Logger.table(info);
1592
+ if (server.tools && server.tools.length > 0) {
1593
+ console.log("");
1594
+ console.log(chalk7.yellow("\u{1F527} Tools:"));
1595
+ server.tools.forEach((tool) => {
1596
+ console.log(` \u2022 ${tool}`);
1597
+ });
1598
+ }
1599
+ if (server.envVars && server.envVars.length > 0) {
1600
+ console.log("");
1601
+ console.log(chalk7.yellow("\u{1F511} Environment Variables:"));
1602
+ server.envVars.forEach((env) => {
1603
+ console.log(` ${env.key} = ${env.masked ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "(set)"}`);
1604
+ });
1605
+ }
1606
+ console.log("");
1607
+ } catch (error) {
1608
+ spinner.fail("Failed to get status");
1609
+ Logger.error(error.message);
1610
+ process.exit(1);
1611
+ }
1612
+ });
1613
+ }
1614
+
1615
+ // src/commands/logs.ts
1616
+ import { Command as Command8 } from "commander";
1617
+ import chalk8 from "chalk";
1618
+ function createLogsCommand() {
1619
+ return new Command8("logs").description("View logs for a deployed MCP server").argument("<slug-or-id>", "Server slug or ID").option("-n, --lines <number>", "Number of log lines to show", "50").option("--json", "Output as JSON").action(async (slugOrId, options) => {
1620
+ const isJson = options.json || Logger.isJsonMode;
1621
+ const { api } = getAuthenticatedAPI();
1622
+ const lines = parseInt(options.lines);
1623
+ const spinner = Logger.spinner(`Fetching logs for ${chalk8.cyan(slugOrId)}...`);
1624
+ try {
1625
+ const logs = await api.getServerLogs(slugOrId, lines);
1626
+ spinner.stop();
1627
+ if (logs.length === 0) {
1628
+ if (isJson) {
1629
+ console.log(JSON.stringify({ success: true, logs: [] }));
1630
+ } else {
1631
+ Logger.info("No logs available yet.");
1632
+ }
1633
+ return;
1634
+ }
1635
+ if (isJson) {
1636
+ Logger.json(logs);
1637
+ return;
1638
+ }
1639
+ console.log("");
1640
+ console.log(chalk8.bold(`\u{1F4DC} Logs: ${slugOrId}`));
1641
+ console.log(chalk8.dim("\u2500".repeat(60)));
1642
+ console.log("");
1643
+ for (const entry of logs) {
1644
+ const time = chalk8.dim(new Date(entry.timestamp).toLocaleTimeString());
1645
+ const level = entry.level === "error" ? chalk8.red("ERR") : entry.level === "warn" ? chalk8.yellow("WRN") : entry.level === "debug" ? chalk8.dim("DBG") : chalk8.blue("INF");
1646
+ console.log(`${time} ${level} ${entry.message}`);
1647
+ }
1648
+ console.log("");
1649
+ console.log(chalk8.dim(`Showing last ${logs.length} entries`));
1650
+ console.log("");
1651
+ } catch (error) {
1652
+ spinner.fail("Failed to fetch logs");
1653
+ Logger.error(error.message);
1654
+ process.exit(1);
1655
+ }
1656
+ });
1657
+ }
1658
+
1659
+ // src/commands/env.ts
1660
+ import { Command as Command9 } from "commander";
1661
+ import chalk9 from "chalk";
1662
+ function createEnvCommand() {
1663
+ const env = new Command9("env").description("Manage environment variables for a deployed MCP server");
1664
+ env.command("list").description("List environment variables").argument("<slug-or-id>", "Server slug or ID").option("--json", "Output as JSON").action(async (slugOrId, options) => {
1665
+ const { api } = getAuthenticatedAPI();
1666
+ const spinner = Logger.spinner("Fetching environment variables...");
1667
+ try {
1668
+ const envVars = await api.listEnvVars(slugOrId);
1669
+ spinner.stop();
1670
+ if (envVars.length === 0) {
1671
+ Logger.info("No environment variables set.");
1672
+ Logger.dim(`Add one: ${chalk9.cyan(`mcphosting env set ${slugOrId} KEY=value`)}`);
1673
+ return;
1674
+ }
1675
+ if (options.json) {
1676
+ Logger.json(envVars);
1677
+ return;
1678
+ }
1679
+ console.log("");
1680
+ Logger.bold(`\u{1F511} Environment Variables: ${slugOrId}`);
1681
+ console.log("");
1682
+ envVars.forEach((env2) => {
1683
+ console.log(` ${chalk9.cyan(env2.key)} = ${env2.masked ? chalk9.dim("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022") : chalk9.dim("(set)")}`);
1684
+ });
1685
+ console.log("");
1686
+ } catch (error) {
1687
+ spinner.fail("Failed to fetch environment variables");
1688
+ Logger.error(error.message);
1689
+ process.exit(1);
1690
+ }
1691
+ });
1692
+ env.command("set").description("Set an environment variable (KEY=value)").argument("<slug-or-id>", "Server slug or ID").argument("<key-value>", "Environment variable in KEY=value format").action(async (slugOrId, keyValue) => {
1693
+ const eqIndex = keyValue.indexOf("=");
1694
+ if (eqIndex === -1) {
1695
+ Logger.error("Invalid format. Use KEY=value");
1696
+ Logger.dim(`Example: ${chalk9.cyan(`mcphosting env set ${slugOrId} API_KEY=sk-abc123`)}`);
1697
+ process.exit(1);
1698
+ }
1699
+ const key = keyValue.substring(0, eqIndex);
1700
+ const value = keyValue.substring(eqIndex + 1);
1701
+ if (!key) {
1702
+ Logger.error("Key cannot be empty.");
1703
+ process.exit(1);
1704
+ }
1705
+ const { api } = getAuthenticatedAPI();
1706
+ const spinner = Logger.spinner(`Setting ${chalk9.cyan(key)}...`);
1707
+ try {
1708
+ await api.addEnvVar(slugOrId, key, value);
1709
+ spinner.succeed(`Set ${chalk9.bold(key)} on ${chalk9.cyan(slugOrId)}`);
1710
+ } catch (error) {
1711
+ spinner.fail(`Failed to set ${key}`);
1712
+ Logger.error(error.message);
1713
+ process.exit(1);
1714
+ }
1715
+ });
1716
+ env.command("remove").description("Remove an environment variable").argument("<slug-or-id>", "Server slug or ID").argument("<key>", "Environment variable key to remove").action(async (slugOrId, key) => {
1717
+ const { api } = getAuthenticatedAPI();
1718
+ const spinner = Logger.spinner(`Removing ${chalk9.cyan(key)}...`);
1719
+ try {
1720
+ await api.removeEnvVar(slugOrId, key);
1721
+ spinner.succeed(`Removed ${chalk9.bold(key)} from ${chalk9.cyan(slugOrId)}`);
1722
+ } catch (error) {
1723
+ spinner.fail(`Failed to remove ${key}`);
1724
+ Logger.error(error.message);
1725
+ process.exit(1);
1726
+ }
1727
+ });
1728
+ return env;
1729
+ }
1730
+
1731
+ // src/commands/keys.ts
1732
+ import { Command as Command10 } from "commander";
1733
+ import chalk10 from "chalk";
1734
+ function createKeysCommand() {
1735
+ const keys = new Command10("keys").description("Manage API keys");
1736
+ keys.command("list").description("List your API keys").option("--json", "Output as JSON").action(async (options) => {
1737
+ const { api } = getAuthenticatedAPI();
1738
+ const spinner = Logger.spinner("Fetching API keys...");
1739
+ try {
1740
+ const apiKeys = await api.getAPIKeys();
1741
+ spinner.stop();
1742
+ if (apiKeys.length === 0) {
1743
+ Logger.info("No API keys found.");
1744
+ Logger.dim(`Create one: ${chalk10.cyan('mcphosting keys create "My Key"')}`);
1745
+ return;
1746
+ }
1747
+ if (options.json) {
1748
+ Logger.json(apiKeys);
1749
+ return;
1750
+ }
1751
+ console.log("");
1752
+ Logger.bold(`\u{1F511} API Keys (${apiKeys.length})`);
1753
+ console.log("");
1754
+ const tableData = apiKeys.map((key) => ({
1755
+ Name: key.name,
1756
+ Prefix: key.prefix + "...",
1757
+ Created: new Date(key.createdAt).toLocaleDateString(),
1758
+ "Last Used": key.lastUsedAt ? new Date(key.lastUsedAt).toLocaleDateString() : "Never"
1759
+ }));
1760
+ Logger.table(tableData);
1761
+ console.log("");
1762
+ } catch (error) {
1763
+ spinner.fail("Failed to fetch API keys");
1764
+ Logger.error(error.message);
1765
+ process.exit(1);
1766
+ }
1767
+ });
1768
+ keys.command("create").description("Create a new API key").argument("<name>", "Name for the API key").action(async (name) => {
1769
+ const { api } = getAuthenticatedAPI();
1770
+ const spinner = Logger.spinner("Creating API key...");
1771
+ try {
1772
+ const key = await api.createAPIKey(name);
1773
+ spinner.succeed("API key created!");
1774
+ console.log("");
1775
+ Logger.bold("\u{1F511} New API Key");
1776
+ console.log("");
1777
+ Logger.info(` Name: ${chalk10.bold(key.name)}`);
1778
+ Logger.info(` Key: ${chalk10.green(key.key || key.prefix + "...")}`);
1779
+ Logger.info(` ID: ${chalk10.dim(key.id)}`);
1780
+ console.log("");
1781
+ Logger.warning("Save this key now \u2014 it won't be shown again!");
1782
+ console.log("");
1783
+ } catch (error) {
1784
+ spinner.fail("Failed to create API key");
1785
+ Logger.error(error.message);
1786
+ process.exit(1);
1787
+ }
1788
+ });
1789
+ keys.command("delete").description("Delete an API key").argument("<id>", "API key ID").action(async (id) => {
1790
+ const { api } = getAuthenticatedAPI();
1791
+ const spinner = Logger.spinner("Deleting API key...");
1792
+ try {
1793
+ await api.deleteAPIKey(id);
1794
+ spinner.succeed(`API key ${chalk10.dim(id)} deleted.`);
1795
+ } catch (error) {
1796
+ spinner.fail("Failed to delete API key");
1797
+ Logger.error(error.message);
1798
+ process.exit(1);
1799
+ }
1800
+ });
1801
+ return keys;
1802
+ }
1803
+
1804
+ // src/commands/search.ts
1805
+ import { Command as Command11 } from "commander";
1806
+ import chalk11 from "chalk";
1807
+ function createSearchCommand() {
1808
+ return new Command11("search").description("Search MCP servers in the marketplace").argument("<query>", "Search query").option("--limit <number>", "Maximum number of results", "10").option("--json", "Output as JSON").action(async (query, options) => {
611
1809
  const config = new Config();
612
- const connections = config.connections;
613
- if (connections.length === 0) {
614
- Logger.info("No MCP connections found");
615
- Logger.dim("Use `mcphost connect <url>` to add one");
616
- return;
1810
+ const api = new MCPHostingAPI(config.token);
1811
+ const spinner = Logger.spinner(`Searching for "${query}"...`);
1812
+ try {
1813
+ const results = await api.searchMCPs(query);
1814
+ spinner.succeed(`Found ${results.length} MCP server(s)`);
1815
+ if (results.length === 0) {
1816
+ Logger.info("No MCP servers found.");
1817
+ Logger.dim("Try: github, slack, notion, stripe, postgres, filesystem");
1818
+ return;
1819
+ }
1820
+ const limit = parseInt(options.limit);
1821
+ const limitedResults = results.slice(0, limit);
1822
+ if (options.json) {
1823
+ Logger.json(limitedResults);
1824
+ return;
1825
+ }
1826
+ console.log("");
1827
+ Logger.bold("\u{1F50D} Marketplace Results");
1828
+ console.log("");
1829
+ limitedResults.forEach((server, index) => {
1830
+ console.log(chalk11.cyan(`${index + 1}. ${server.name}`));
1831
+ console.log(` ${chalk11.dim(server.description)}`);
1832
+ console.log(` ${chalk11.yellow("Slug:")} ${server.slug} | ${chalk11.yellow("Installs:")} ${server.installs.toLocaleString()} | ${chalk11.yellow("By:")} ${server.author}`);
1833
+ console.log(` ${chalk11.green("Tools:")} ${server.tools.join(", ")}`);
1834
+ console.log(` ${chalk11.blue("Connect:")} ${chalk11.dim(`mcphosting connect ${server.slug}`)}`);
1835
+ console.log("");
1836
+ });
1837
+ if (results.length > limit) {
1838
+ Logger.dim(`Showing ${limit} of ${results.length}. Use --limit for more.`);
1839
+ }
1840
+ } catch (error) {
1841
+ spinner.fail("Search failed");
1842
+ Logger.error(error.message);
1843
+ process.exit(1);
617
1844
  }
618
- if (options.json) {
619
- Logger.json(connections);
620
- return;
1845
+ });
1846
+ }
1847
+ function createInfoCommand() {
1848
+ return new Command11("info").description("Show detailed information about an MCP server").argument("<slug>", "MCP server slug").option("--json", "Output as JSON").action(async (slug, options) => {
1849
+ const config = new Config();
1850
+ const api = new MCPHostingAPI(config.token);
1851
+ const spinner = Logger.spinner(`Getting info for ${slug}...`);
1852
+ try {
1853
+ const server = await api.getMCPInfo(slug);
1854
+ if (!server) {
1855
+ spinner.fail(`Not found: ${slug}`);
1856
+ Logger.info(`Use ${chalk11.cyan("mcphosting search <query>")} to find servers`);
1857
+ return;
1858
+ }
1859
+ spinner.stop();
1860
+ if (options.json) {
1861
+ Logger.json(server);
1862
+ return;
1863
+ }
1864
+ console.log("");
1865
+ console.log(chalk11.bold.cyan(`\u{1F4E6} ${server.name}`));
1866
+ console.log(chalk11.dim(server.description));
1867
+ console.log("");
1868
+ const info = [
1869
+ { Property: "Slug", Value: server.slug },
1870
+ { Property: "Author", Value: server.author },
1871
+ { Property: "Installs", Value: server.installs.toLocaleString() },
1872
+ { Property: "Tools", Value: server.tools.length.toString() }
1873
+ ];
1874
+ if (server.url) {
1875
+ info.push({ Property: "URL", Value: server.url });
1876
+ }
1877
+ Logger.table(info);
1878
+ console.log("");
1879
+ console.log(chalk11.yellow("\u{1F527} Available Tools:"));
1880
+ server.tools.forEach((tool) => console.log(` \u2022 ${tool}`));
1881
+ console.log("");
1882
+ console.log(chalk11.green("\u{1F4A1} Quick Start:"));
1883
+ console.log(chalk11.dim(` mcphosting connect ${server.slug}`));
1884
+ const connection = config.findConnection(slug);
1885
+ if (connection) {
1886
+ console.log("");
1887
+ console.log(chalk11.blue("\u2139\uFE0F Already connected to:"), connection.clients.join(", "));
1888
+ }
1889
+ console.log("");
1890
+ } catch (error) {
1891
+ spinner.fail("Failed to get info");
1892
+ Logger.error(error.message);
1893
+ process.exit(1);
621
1894
  }
622
- Logger.bold(`\u{1F4CB} Connected MCP Servers (${connections.length})`);
623
- console.log("");
624
- const tableData = connections.map((conn) => ({
625
- Slug: conn.slug,
626
- URL: conn.url.length > 50 ? conn.url.slice(0, 47) + "..." : conn.url,
627
- Clients: conn.clients.join(", "),
628
- Added: new Date(conn.addedAt).toLocaleDateString()
629
- }));
630
- Logger.table(tableData);
631
- console.log("");
632
- Logger.dim("Use `mcphost disconnect <slug>` to remove a connection");
633
1895
  });
634
1896
  }
635
1897
 
636
1898
  // src/commands/import.ts
637
- import { Command as Command3 } from "commander";
638
- import { readFile as readFile2, access as access2 } from "fs/promises";
639
- import { join as join2 } from "path";
1899
+ import { Command as Command12 } from "commander";
1900
+ import { readFile as readFile5, access as access4 } from "fs/promises";
1901
+ import { join as join5 } from "path";
640
1902
  import { homedir as homedir2 } from "os";
641
- import chalk3 from "chalk";
1903
+ import chalk12 from "chalk";
642
1904
  function createImportCommand() {
643
- return new Command3("import").description("Import MCP connections from other tools").option("--from <tool>", "Import from: smithery", "smithery").option("--dry-run", "Show what would be imported without actually importing").option("--config-path <path>", "Custom config file path").action(async (options) => {
1905
+ return new Command12("import").description("Import MCP connections from other tools").option("--from <tool>", "Import from: smithery", "smithery").option("--dry-run", "Show what would be imported without actually importing").option("--config-path <path>", "Custom config file path").action(async (options) => {
644
1906
  if (options.from !== "smithery") {
645
1907
  Logger.error("Only Smithery import is currently supported");
646
1908
  Logger.info("Usage: mcphost import --from smithery");
@@ -655,16 +1917,16 @@ async function importFromSmitery(options) {
655
1917
  try {
656
1918
  const smitheryPaths = [
657
1919
  options.configPath,
658
- join2(homedir2(), ".smithery", "config.json"),
659
- join2(homedir2(), ".config", "smithery", "config.json"),
660
- join2(homedir2(), "Library", "Application Support", "smithery", "config.json"),
661
- join2(process.cwd(), "smithery.json"),
662
- join2(process.cwd(), ".smithery.json")
1920
+ join5(homedir2(), ".smithery", "config.json"),
1921
+ join5(homedir2(), ".config", "smithery", "config.json"),
1922
+ join5(homedir2(), "Library", "Application Support", "smithery", "config.json"),
1923
+ join5(process.cwd(), "smithery.json"),
1924
+ join5(process.cwd(), ".smithery.json")
663
1925
  ].filter(Boolean);
664
1926
  let smitheryConfigPath = null;
665
1927
  for (const path of smitheryPaths) {
666
1928
  try {
667
- await access2(path);
1929
+ await access4(path);
668
1930
  smitheryConfigPath = path;
669
1931
  break;
670
1932
  } catch {
@@ -680,7 +1942,7 @@ async function importFromSmitery(options) {
680
1942
  }
681
1943
  spinner.succeed(`Found Smithery config: ${smitheryConfigPath}`);
682
1944
  const loadSpinner = Logger.spinner("Reading Smithery config...");
683
- const configContent = await readFile2(smitheryConfigPath, "utf-8");
1945
+ const configContent = await readFile5(smitheryConfigPath, "utf-8");
684
1946
  const smitheryConfig = JSON.parse(configContent);
685
1947
  const servers = {
686
1948
  ...smitheryConfig.servers,
@@ -693,14 +1955,14 @@ async function importFromSmitery(options) {
693
1955
  const serverEntries = Object.entries(servers);
694
1956
  loadSpinner.succeed(`Found ${serverEntries.length} MCP server(s) in Smithery config`);
695
1957
  if (options.dryRun) {
696
- console.log("\n" + chalk3.bold("\u{1F50D} Preview: Would import the following MCP servers:"));
1958
+ console.log("\n" + chalk12.bold("\u{1F50D} Preview: Would import the following MCP servers:"));
697
1959
  console.log("");
698
1960
  serverEntries.forEach(([slug, serverConfig]) => {
699
1961
  const url = serverConfig.url || `https://${slug}.mcphost.dev`;
700
- console.log(`\u2022 ${chalk3.cyan(slug)}`);
701
- console.log(` URL: ${chalk3.dim(url)}`);
1962
+ console.log(`\u2022 ${chalk12.cyan(slug)}`);
1963
+ console.log(` URL: ${chalk12.dim(url)}`);
702
1964
  if (serverConfig.command) {
703
- console.log(` Command: ${chalk3.dim(serverConfig.command + " " + (serverConfig.args?.join(" ") || ""))}`);
1965
+ console.log(` Command: ${chalk12.dim(serverConfig.command + " " + (serverConfig.args?.join(" ") || ""))}`);
704
1966
  }
705
1967
  console.log("");
706
1968
  });
@@ -765,9 +2027,9 @@ async function importFromSmitery(options) {
765
2027
  }
766
2028
  console.log("");
767
2029
  Logger.info("Use `mcphost list` to see your imported connections");
768
- console.log("\n" + chalk3.green("\u{1F389} Welcome to MCPHosting! Share with your team:"));
769
- console.log(chalk3.cyan("npx mcphosting-cli import --from smithery"));
770
- console.log("\n" + chalk3.yellow("\u2B50 Star us: ") + chalk3.blue("https://github.com/gorlomi-enzo/mcphosting-cli"));
2030
+ console.log("\n" + chalk12.green("\u{1F389} Welcome to MCPHosting! Share with your team:"));
2031
+ console.log(chalk12.cyan("npx mcphosting-cli import --from smithery"));
2032
+ console.log("\n" + chalk12.yellow("\u2B50 Star us: ") + chalk12.blue("https://github.com/gorlomi-enzo/mcphosting-cli"));
771
2033
  console.log("");
772
2034
  } catch (error) {
773
2035
  spinner.fail("Import failed");
@@ -780,9 +2042,9 @@ async function importFromSmitery(options) {
780
2042
  }
781
2043
 
782
2044
  // src/commands/proxy.ts
783
- import { Command as Command4 } from "commander";
2045
+ import { Command as Command13 } from "commander";
784
2046
  function createProxyCommand() {
785
- return new Command4("proxy").description("Start a local STDIO MCP proxy to a remote server").argument("<url>", "Remote MCP server URL").option("--quiet", "Suppress startup messages").action(async (url, options) => {
2047
+ return new Command13("proxy").description("Start a local STDIO MCP proxy to a remote server").argument("<url>", "Remote MCP server URL").option("--quiet", "Suppress startup messages").action(async (url, options) => {
786
2048
  if (!options.quiet) {
787
2049
  console.error("\u{1F517} Proxying via mcphosting.com");
788
2050
  console.error(`\u{1F4E1} Remote server: ${url}`);
@@ -879,279 +2141,478 @@ async function forwardMessage(url, message, quiet) {
879
2141
  }
880
2142
  }
881
2143
 
882
- // src/commands/search.ts
883
- import { Command as Command5 } from "commander";
884
- import chalk4 from "chalk";
885
- function createSearchCommand() {
886
- return new Command5("search").description("Search MCP servers in the marketplace").argument("<query>", "Search query").option("--limit <number>", "Maximum number of results", "10").option("--json", "Output as JSON").action(async (query, options) => {
2144
+ // src/commands/whoami.ts
2145
+ import { Command as Command14 } from "commander";
2146
+ import chalk13 from "chalk";
2147
+ function createWhoamiCommand() {
2148
+ return new Command14("whoami").description("Show current logged-in user").option("--json", "Output as JSON").action(async (options) => {
887
2149
  const config = new Config();
888
- const api = new MCPHostingAPI(config.token);
889
- const spinner = Logger.spinner(`Searching for "${query}"...`);
890
- try {
891
- const results = await api.searchMCPs(query);
892
- spinner.succeed(`Found ${results.length} MCP server(s)`);
893
- if (results.length === 0) {
894
- Logger.info("No MCP servers found matching your query");
895
- Logger.dim("Try searching for: github, slack, notion, stripe, postgres, filesystem");
896
- return;
2150
+ const token = config.token;
2151
+ if (!token) {
2152
+ if (options.json || Logger.isJsonMode) {
2153
+ console.log(JSON.stringify({ success: false, logged_in: false, error: "Not logged in" }));
2154
+ } else {
2155
+ Logger.info("Not logged in.");
2156
+ Logger.dim(`Run ${chalk13.cyan("mcphosting login")} to authenticate.`);
897
2157
  }
898
- const limit = parseInt(options.limit);
899
- const limitedResults = results.slice(0, limit);
900
- if (options.json) {
901
- Logger.json(limitedResults);
902
- return;
2158
+ return;
2159
+ }
2160
+ const api = new MCPHostingAPI(token);
2161
+ const user = await api.whoami();
2162
+ if (!user && token) {
2163
+ if (options.json || Logger.isJsonMode) {
2164
+ console.log(JSON.stringify({
2165
+ success: false,
2166
+ logged_in: false,
2167
+ error: "Token expired",
2168
+ cached_email: config.user?.email
2169
+ }));
2170
+ } else {
2171
+ Logger.warning("Token expired. Run `mcphosting login` to re-authenticate.");
2172
+ if (config.user?.email) {
2173
+ Logger.dim(` Cached email: ${config.user.email}`);
2174
+ }
903
2175
  }
2176
+ return;
2177
+ }
2178
+ const result = {
2179
+ success: true,
2180
+ logged_in: true,
2181
+ email: user?.email || config.user?.email || "unknown",
2182
+ org: user?.org || config.user?.org || void 0,
2183
+ token_source: config.isEnvToken ? "environment" : "config"
2184
+ };
2185
+ if (options.json || Logger.isJsonMode) {
2186
+ console.log(JSON.stringify(result));
2187
+ } else {
904
2188
  console.log("");
905
- Logger.bold(`\u{1F50D} MCP Marketplace Search Results`);
906
- console.log("");
907
- limitedResults.forEach((server, index) => {
908
- console.log(chalk4.cyan(`${index + 1}. ${server.name}`));
909
- console.log(` ${chalk4.dim(server.description)}`);
910
- console.log(` ${chalk4.yellow("Slug:")} ${server.slug} | ${chalk4.yellow("Installs:")} ${server.installs.toLocaleString()} | ${chalk4.yellow("Author:")} ${server.author}`);
911
- console.log(` ${chalk4.green("Tools:")} ${server.tools.join(", ")}`);
912
- if (server.url) {
913
- console.log(` ${chalk4.blue("Connect:")} ${chalk4.dim(`mcphost connect ${server.slug}`)}`);
914
- }
915
- console.log("");
916
- });
917
- if (results.length > limit) {
918
- Logger.dim(`Showing ${limit} of ${results.length} results. Use --limit to see more.`);
2189
+ Logger.success(`Logged in as ${chalk13.bold(result.email)}${result.org ? ` (${result.org})` : ""}`);
2190
+ if (config.isEnvToken) {
2191
+ Logger.dim(" Token source: MCPHOSTING_TOKEN environment variable");
919
2192
  }
920
- Logger.info("Use `mcphost info <slug>` for detailed information");
921
- Logger.info("Use `mcphost connect <slug>` to install");
922
- } catch (error) {
923
- spinner.fail("Search failed");
924
- Logger.error(`Error: ${error}`);
925
- process.exit(1);
2193
+ console.log("");
926
2194
  }
927
2195
  });
928
2196
  }
929
- function createInfoCommand() {
930
- return new Command5("info").description("Show detailed information about an MCP server").argument("<slug>", "MCP server slug").option("--json", "Output as JSON").action(async (slug, options) => {
2197
+
2198
+ // src/commands/account-status.ts
2199
+ import { Command as Command15 } from "commander";
2200
+ import chalk14 from "chalk";
2201
+ function createAccountCommand() {
2202
+ return new Command15("account").description("Show MCPHosting account status, plan, and servers").option("--json", "Output as JSON").action(async (options) => {
931
2203
  const config = new Config();
932
- const api = new MCPHostingAPI(config.token);
933
- const spinner = Logger.spinner(`Getting info for ${slug}...`);
934
- try {
935
- const server = await api.getMCPInfo(slug);
936
- if (!server) {
937
- spinner.fail(`MCP server not found: ${slug}`);
938
- Logger.info("Use `mcphost search <query>` to find servers");
939
- return;
2204
+ if (!isAuthenticated()) {
2205
+ if (options.json || Logger.isJsonMode) {
2206
+ console.log(JSON.stringify({
2207
+ success: false,
2208
+ logged_in: false,
2209
+ error: "Not logged in"
2210
+ }));
2211
+ } else {
2212
+ Logger.error("Not logged in.");
2213
+ Logger.info(`Run ${chalk14.cyan("mcphosting login")} to authenticate.`);
940
2214
  }
941
- spinner.succeed("Found server info");
942
- if (options.json) {
943
- Logger.json(server);
2215
+ return;
2216
+ }
2217
+ const { api } = getAuthenticatedAPI();
2218
+ try {
2219
+ const [user, servers] = await Promise.all([
2220
+ api.whoami(),
2221
+ api.listServers().catch(() => [])
2222
+ ]);
2223
+ const result = {
2224
+ success: true,
2225
+ logged_in: true,
2226
+ email: user?.email || config.user?.email || "unknown",
2227
+ org: user?.org || config.user?.org || void 0,
2228
+ plan: user?.plan || "free",
2229
+ token_source: config.isEnvToken ? "environment" : "config",
2230
+ servers: servers.map((s) => ({
2231
+ name: s.name || s.slug,
2232
+ slug: s.slug,
2233
+ url: s.url || "",
2234
+ status: s.status || "unknown"
2235
+ }))
2236
+ };
2237
+ if (options.json || Logger.isJsonMode) {
2238
+ console.log(JSON.stringify(result));
944
2239
  return;
945
2240
  }
946
2241
  console.log("");
947
- console.log(chalk4.bold.cyan(`\u{1F4E6} ${server.name}`));
948
- console.log(chalk4.dim(server.description));
2242
+ Logger.bold("\u{1F4CA} MCPHosting Account");
949
2243
  console.log("");
950
- const infoTable = [
951
- { Property: "Slug", Value: server.slug },
952
- { Property: "Author", Value: server.author },
953
- { Property: "Installs", Value: server.installs.toLocaleString() },
954
- { Property: "Tools", Value: server.tools.length.toString() }
955
- ];
956
- if (server.url) {
957
- infoTable.push({ Property: "URL", Value: server.url });
2244
+ Logger.info(` ${chalk14.dim("Email:")} ${chalk14.bold(result.email)}`);
2245
+ if (result.org) {
2246
+ Logger.info(` ${chalk14.dim("Org:")} ${result.org}`);
958
2247
  }
959
- Logger.table(infoTable);
960
- console.log("");
961
- console.log(chalk4.yellow("\u{1F527} Available Tools:"));
962
- server.tools.forEach((tool) => {
963
- console.log(` \u2022 ${tool}`);
964
- });
2248
+ Logger.info(` ${chalk14.dim("Plan:")} ${chalk14.cyan(result.plan)}`);
2249
+ Logger.info(` ${chalk14.dim("Servers:")} ${result.servers.length}`);
965
2250
  console.log("");
966
- console.log(chalk4.green("\u{1F4A1} Quick Start:"));
967
- console.log(chalk4.dim(` mcphost connect ${server.slug}`));
968
- const connection = config.findConnection(slug);
969
- if (connection) {
2251
+ if (result.servers.length > 0) {
2252
+ Logger.bold("\u{1F5A5}\uFE0F Your MCP Servers");
970
2253
  console.log("");
971
- console.log(chalk4.blue("\u2139\uFE0F Already connected to:"), connection.clients.join(", "));
2254
+ const tableData = result.servers.map((s) => ({
2255
+ Name: s.name,
2256
+ Status: s.status === "active" ? chalk14.green("\u25CF active") : s.status === "deploying" ? chalk14.yellow("\u25D0 deploying") : s.status === "stopped" ? chalk14.dim("\u25CB stopped") : chalk14.red("\u2717 " + s.status),
2257
+ URL: s.url.length > 40 ? s.url.slice(0, 37) + "..." : s.url
2258
+ }));
2259
+ Logger.table(tableData);
2260
+ } else {
2261
+ Logger.dim(" No servers deployed yet.");
2262
+ Logger.dim(` Run ${chalk14.cyan("mcphosting deploy --template weather")} to get started.`);
972
2263
  }
2264
+ console.log("");
973
2265
  } catch (error) {
974
- spinner.fail("Failed to get server info");
975
- Logger.error(`Error: ${error}`);
976
- process.exit(1);
2266
+ if (options.json || Logger.isJsonMode) {
2267
+ console.log(JSON.stringify({ success: false, error: error.message }));
2268
+ } else {
2269
+ Logger.error(error.message);
2270
+ }
977
2271
  }
978
2272
  });
979
2273
  }
980
2274
 
981
- // src/commands/servers.ts
982
- import { Command as Command6 } from "commander";
983
- function createServersCommand() {
984
- const servers = new Command6("servers");
985
- servers.description("Manage your hosted MCP servers");
986
- servers.command("list").description("List your MCP servers").action(async () => {
987
- const config = new Config();
988
- if (!config.token) {
989
- Logger.warning("Authentication required");
990
- Logger.info("Use `mcphost login` to authenticate with MCPHosting");
991
- return;
992
- }
993
- Logger.info("\u{1F6A7} Server management coming soon!");
994
- console.log("");
995
- console.log("This feature will let you:");
996
- console.log("\u2022 List your hosted MCP servers");
997
- console.log("\u2022 View usage analytics");
998
- console.log("\u2022 Manage API keys");
999
- console.log("\u2022 Publish to marketplace");
1000
- console.log("");
1001
- Logger.info("Visit https://mcphosting.com to manage servers in the web dashboard");
1002
- });
1003
- return servers;
2275
+ // src/commands/completions.ts
2276
+ import { Command as Command16 } from "commander";
2277
+ import chalk15 from "chalk";
2278
+ var BASH_COMPLETIONS = `
2279
+ # mcphosting bash completions
2280
+ _mcphosting_completions() {
2281
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
2282
+ local prev="\${COMP_WORDS[COMP_CWORD-1]}"
2283
+ local commands="login logout deploy init connect disconnect list status logs env keys search info import proxy whoami account completions"
2284
+ local templates="weather crypto notion postgres blank runescape reminder search"
2285
+ local global_opts="--help --version --json --silent"
2286
+
2287
+ case "\${prev}" in
2288
+ mcphosting|mcphost)
2289
+ COMPREPLY=( $(compgen -W "\${commands} \${global_opts}" -- "\${cur}") )
2290
+ return 0
2291
+ ;;
2292
+ deploy)
2293
+ COMPREPLY=( $(compgen -W "--template --github --api-url --name --auth --json --silent --configure --no-auto-key" -- "\${cur}") )
2294
+ return 0
2295
+ ;;
2296
+ --template)
2297
+ COMPREPLY=( $(compgen -W "\${templates}" -- "\${cur}") )
2298
+ return 0
2299
+ ;;
2300
+ --auth)
2301
+ COMPREPLY=( $(compgen -W "none api_key oauth" -- "\${cur}") )
2302
+ return 0
2303
+ ;;
2304
+ --shell)
2305
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
2306
+ return 0
2307
+ ;;
2308
+ env)
2309
+ COMPREPLY=( $(compgen -W "list set remove" -- "\${cur}") )
2310
+ return 0
2311
+ ;;
2312
+ keys)
2313
+ COMPREPLY=( $(compgen -W "list create delete" -- "\${cur}") )
2314
+ return 0
2315
+ ;;
2316
+ login)
2317
+ COMPREPLY=( $(compgen -W "--github --email --token --browser --json" -- "\${cur}") )
2318
+ return 0
2319
+ ;;
2320
+ list)
2321
+ COMPREPLY=( $(compgen -W "--local --remote --json" -- "\${cur}") )
2322
+ return 0
2323
+ ;;
2324
+ status|logs)
2325
+ COMPREPLY=( $(compgen -W "--json" -- "\${cur}") )
2326
+ return 0
2327
+ ;;
2328
+ *)
2329
+ COMPREPLY=( $(compgen -W "\${global_opts}" -- "\${cur}") )
2330
+ return 0
2331
+ ;;
2332
+ esac
1004
2333
  }
1005
- function createKeysCommand() {
1006
- const keys = new Command6("keys");
1007
- keys.description("Manage API keys for your MCP servers");
1008
- keys.command("list").argument("[server-id]", "Server ID").description("List API keys").action(async (serverId) => {
1009
- const config = new Config();
1010
- if (!config.token) {
1011
- Logger.warning("Authentication required");
1012
- Logger.info("Use `mcphost login` to authenticate");
1013
- return;
1014
- }
1015
- Logger.info("\u{1F6A7} API key management coming soon!");
1016
- Logger.info("Visit https://mcphosting.com to manage keys in the web dashboard");
1017
- });
1018
- keys.command("create").argument("<server-id>", "Server ID").option("--name <name>", "Key name", "CLI Key").description("Create a new API key").action(async (serverId, options) => {
1019
- const config = new Config();
1020
- if (!config.token) {
1021
- Logger.warning("Authentication required");
1022
- Logger.info("Use `mcphost login` to authenticate");
1023
- return;
1024
- }
1025
- Logger.info("\u{1F6A7} API key creation coming soon!");
1026
- Logger.info("Visit https://mcphosting.com to create keys in the web dashboard");
1027
- });
1028
- keys.command("revoke").argument("<key-id>", "API key ID").description("Revoke an API key").action(async (keyId) => {
1029
- const config = new Config();
1030
- if (!config.token) {
1031
- Logger.warning("Authentication required");
1032
- Logger.info("Use `mcphost login` to authenticate");
1033
- return;
1034
- }
1035
- Logger.info("\u{1F6A7} API key revocation coming soon!");
1036
- Logger.info("Visit https://mcphosting.com to revoke keys in the web dashboard");
1037
- });
1038
- return keys;
2334
+ complete -F _mcphosting_completions mcphosting
2335
+ complete -F _mcphosting_completions mcphost
2336
+ `.trim();
2337
+ var ZSH_COMPLETIONS = `
2338
+ #compdef mcphosting mcphost
2339
+
2340
+ _mcphosting() {
2341
+ local -a commands templates global_opts
2342
+
2343
+ commands=(
2344
+ 'login:Authenticate with MCPHosting'
2345
+ 'logout:Log out and remove stored credentials'
2346
+ 'deploy:Deploy an MCP server to MCPHosting'
2347
+ 'init:Initialize mcphosting.json in the current directory'
2348
+ 'connect:Connect an MCP server to your AI clients'
2349
+ 'disconnect:Disconnect an MCP server'
2350
+ 'list:List your MCP servers'
2351
+ 'status:Check the status of a deployed MCP server'
2352
+ 'logs:View logs for a deployed MCP server'
2353
+ 'env:Manage environment variables'
2354
+ 'keys:Manage API keys'
2355
+ 'search:Search MCP servers in the marketplace'
2356
+ 'info:Show detailed information about an MCP server'
2357
+ 'import:Import MCP connections from other tools'
2358
+ 'proxy:Start a local STDIO MCP proxy'
2359
+ 'whoami:Show current logged-in user'
2360
+ 'account:Show MCPHosting account status'
2361
+ 'completions:Generate shell completions'
2362
+ )
2363
+
2364
+ templates=(weather crypto notion postgres blank runescape reminder search)
2365
+ global_opts=(--help --version --json --silent)
2366
+
2367
+ _arguments -C \\
2368
+ '1:command:->cmd' \\
2369
+ '*::arg:->args'
2370
+
2371
+ case "$state" in
2372
+ cmd)
2373
+ _describe -t commands 'mcphosting commands' commands
2374
+ _values 'global options' $global_opts
2375
+ ;;
2376
+ args)
2377
+ case "$words[1]" in
2378
+ deploy)
2379
+ _arguments \\
2380
+ '--template[Deploy from template]:template:(weather crypto notion postgres blank runescape reminder search)' \\
2381
+ '--github[Deploy from GitHub URL]:url:' \\
2382
+ '--api-url[External server URL]:url:' \\
2383
+ '--name[Server name]:name:' \\
2384
+ '--auth[Auth type]:auth:(none api_key oauth)' \\
2385
+ '--json[Output as JSON]' \\
2386
+ '--silent[Suppress interactive output]' \\
2387
+ '--configure[Auto-configure AI clients]' \\
2388
+ '--no-auto-key[Skip API key creation]'
2389
+ ;;
2390
+ completions)
2391
+ _arguments '--shell[Shell type]:shell:(bash zsh fish)'
2392
+ ;;
2393
+ login)
2394
+ _arguments '--github' '--email:email:' '--token:token:' '--browser' '--json'
2395
+ ;;
2396
+ *)
2397
+ _arguments '--json[Output as JSON]'
2398
+ ;;
2399
+ esac
2400
+ ;;
2401
+ esac
1039
2402
  }
1040
- function createPublishCommand() {
1041
- return new Command6("publish").description("Publish MCP server to marketplace").argument("[server-id]", "Server ID to publish").option("--private", "Keep server private").option("--description <desc>", "Server description").option("--tags <tags>", "Comma-separated tags").action(async (serverId, options = {}) => {
1042
- const config = new Config();
1043
- if (!config.token) {
1044
- Logger.warning("Authentication required");
1045
- Logger.info("Use `mcphost login` to authenticate");
1046
- return;
1047
- }
1048
- Logger.info("\u{1F6A7} Marketplace publishing coming soon!");
1049
- console.log("");
1050
- console.log("This feature will let you:");
1051
- console.log("\u2022 Publish your MCP servers to the marketplace");
1052
- console.log("\u2022 Set descriptions and tags");
1053
- console.log("\u2022 Configure public/private visibility");
1054
- console.log("\u2022 Track usage analytics");
1055
- console.log("");
1056
- Logger.info("Visit https://mcphosting.com to publish servers via the web dashboard");
1057
- if (serverId) {
1058
- Logger.dim(`Would publish server: ${serverId}`);
1059
- }
1060
- if (options.description) {
1061
- Logger.dim(`Description: ${options.description}`);
1062
- }
1063
- if (options.tags) {
1064
- Logger.dim(`Tags: ${options.tags}`);
2403
+
2404
+ _mcphosting "$@"
2405
+ `.trim();
2406
+ var FISH_COMPLETIONS = `
2407
+ # mcphosting fish completions
2408
+ set -l commands login logout deploy init connect disconnect list status logs env keys search info import proxy whoami account completions
2409
+ set -l templates weather crypto notion postgres blank runescape reminder search
2410
+
2411
+ complete -c mcphosting -f
2412
+ complete -c mcphost -f
2413
+
2414
+ # Commands
2415
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a login -d "Authenticate with MCPHosting"
2416
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a logout -d "Log out"
2417
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a deploy -d "Deploy an MCP server"
2418
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize mcphosting.json"
2419
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a connect -d "Connect MCP to AI clients"
2420
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a disconnect -d "Disconnect MCP server"
2421
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a list -d "List servers"
2422
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a status -d "Check server status"
2423
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a logs -d "View logs"
2424
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a env -d "Manage env vars"
2425
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a keys -d "Manage API keys"
2426
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a search -d "Search marketplace"
2427
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a info -d "MCP server info"
2428
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a import -d "Import connections"
2429
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a proxy -d "STDIO proxy"
2430
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a whoami -d "Show current user"
2431
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a account -d "Account status"
2432
+ complete -c mcphosting -n "not __fish_seen_subcommand_from $commands" -a completions -d "Generate completions"
2433
+
2434
+ # Global
2435
+ complete -c mcphosting -l json -d "Output as JSON"
2436
+ complete -c mcphosting -l silent -d "Suppress interactive output"
2437
+ complete -c mcphosting -l help -d "Show help"
2438
+ complete -c mcphosting -l version -d "Show version"
2439
+
2440
+ # Deploy options
2441
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l template -xa "$templates" -d "Template name"
2442
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l github -d "GitHub URL"
2443
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l api-url -d "API URL"
2444
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l name -d "Server name"
2445
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l auth -xa "none api_key oauth" -d "Auth type"
2446
+ complete -c mcphosting -n "__fish_seen_subcommand_from deploy" -l configure -d "Auto-configure clients"
2447
+
2448
+ # Completions options
2449
+ complete -c mcphosting -n "__fish_seen_subcommand_from completions" -l shell -xa "bash zsh fish" -d "Shell type"
2450
+ `.trim();
2451
+ function createCompletionsCommand() {
2452
+ return new Command16("completions").description("Generate shell completions (bash, zsh, fish)").option("--shell <shell>", "Shell type: bash, zsh, or fish").action((options) => {
2453
+ const shell = options.shell?.toLowerCase() || detectShell();
2454
+ switch (shell) {
2455
+ case "bash":
2456
+ console.log(BASH_COMPLETIONS);
2457
+ if (!options.shell) {
2458
+ console.error("");
2459
+ console.error(chalk15.dim("# Add to ~/.bashrc:"));
2460
+ console.error(chalk15.cyan("# mcphosting completions --shell=bash >> ~/.bashrc"));
2461
+ }
2462
+ break;
2463
+ case "zsh":
2464
+ console.log(ZSH_COMPLETIONS);
2465
+ if (!options.shell) {
2466
+ console.error("");
2467
+ console.error(chalk15.dim("# Save to a file in your fpath:"));
2468
+ console.error(chalk15.cyan("# mcphosting completions --shell=zsh > ~/.zfunc/_mcphosting"));
2469
+ }
2470
+ break;
2471
+ case "fish":
2472
+ console.log(FISH_COMPLETIONS);
2473
+ if (!options.shell) {
2474
+ console.error("");
2475
+ console.error(chalk15.dim("# Save to completions dir:"));
2476
+ console.error(chalk15.cyan("# mcphosting completions --shell=fish > ~/.config/fish/completions/mcphosting.fish"));
2477
+ }
2478
+ break;
2479
+ default:
2480
+ Logger.error(`Unknown shell: ${shell}`);
2481
+ Logger.info("Supported shells: bash, zsh, fish");
2482
+ Logger.info("Usage: mcphosting completions --shell=bash");
2483
+ process.exit(1);
1065
2484
  }
1066
2485
  });
1067
2486
  }
2487
+ function detectShell() {
2488
+ const shell = process.env.SHELL || "";
2489
+ if (shell.includes("zsh")) return "zsh";
2490
+ if (shell.includes("fish")) return "fish";
2491
+ return "bash";
2492
+ }
1068
2493
 
1069
2494
  // src/index.ts
1070
- import chalk5 from "chalk";
1071
- var program = new Command7();
1072
- program.name("mcphost").description("Connect AI agents to MCP servers. Browse, install, and manage Model Context Protocol servers.").version("0.1.0").configureOutput({
2495
+ var program = new Command17();
2496
+ program.name("mcphosting").description("Deploy and manage MCP servers from the terminal").version("1.0.0").option("--json", "Output all results as JSON (agent-friendly)").option("--silent", "Suppress interactive output and prompts").configureOutput({
1073
2497
  outputError: (str, write) => {
1074
- write(chalk5.red(str));
2498
+ if (Logger.isJsonMode) {
2499
+ write(JSON.stringify({ success: false, error: str.trim() }));
2500
+ } else {
2501
+ write(chalk16.red(str));
2502
+ }
2503
+ }
2504
+ }).hook("preAction", (thisCommand) => {
2505
+ const rootOpts = program.opts();
2506
+ if (rootOpts.json) {
2507
+ Logger.jsonMode = true;
2508
+ }
2509
+ if (rootOpts.silent) {
2510
+ Logger.silent = true;
1075
2511
  }
1076
2512
  });
1077
- program.addCommand(createConnectCommands());
2513
+ program.addCommand(createLoginCommand());
2514
+ program.addCommand(createLogoutCommand());
2515
+ program.addCommand(createDeployCommand());
2516
+ program.addCommand(createInitCommand());
2517
+ program.addCommand(createConnectCommand());
1078
2518
  program.addCommand(createDisconnectCommand());
1079
2519
  program.addCommand(createListCommand());
1080
- program.addCommand(createImportCommand());
1081
- program.addCommand(createProxyCommand());
2520
+ program.addCommand(createStatusCommand());
2521
+ program.addCommand(createLogsCommand());
2522
+ program.addCommand(createEnvCommand());
2523
+ program.addCommand(createKeysCommand());
1082
2524
  program.addCommand(createSearchCommand());
1083
2525
  program.addCommand(createInfoCommand());
1084
- program.addCommand(createServersCommand());
1085
- program.addCommand(createKeysCommand());
1086
- program.addCommand(createPublishCommand());
1087
- program.addCommand(createAuthCommands());
1088
- var [login, logout, whoami] = createLegacyAuthCommands();
1089
- program.addCommand(login);
1090
- program.addCommand(logout);
1091
- program.addCommand(whoami);
2526
+ program.addCommand(createWhoamiCommand());
2527
+ program.addCommand(createAccountCommand());
2528
+ program.addCommand(createImportCommand());
2529
+ program.addCommand(createProxyCommand());
2530
+ program.addCommand(createCompletionsCommand());
1092
2531
  program.configureHelp({
1093
- subcommandTerm: (cmd) => chalk5.cyan(cmd.name()),
1094
- commandUsage: (cmd) => {
1095
- const usage = cmd.usage();
1096
- return chalk5.yellow(usage);
1097
- },
1098
- commandDescription: (cmd) => {
1099
- return chalk5.dim(cmd.description());
1100
- }
2532
+ subcommandTerm: (cmd) => chalk16.cyan(cmd.name()),
2533
+ commandUsage: (cmd) => chalk16.yellow(cmd.usage()),
2534
+ commandDescription: (cmd) => chalk16.dim(cmd.description())
1101
2535
  });
1102
2536
  program.addHelpText("after", `
1103
- ${chalk5.bold("Examples:")}
1104
- ${chalk5.cyan("mcphost connect github")} ${chalk5.dim("Connect to GitHub MCP server")}
1105
- ${chalk5.cyan("mcphost connect https://my-mcp.example.com")} ${chalk5.dim("Connect to custom MCP server")}
1106
- ${chalk5.cyan("mcphost connect slack --client claude")} ${chalk5.dim("Connect Slack MCP only to Claude")}
1107
- ${chalk5.cyan("mcphost list")} ${chalk5.dim("List all connected MCP servers")}
1108
- ${chalk5.cyan("mcphost search github")} ${chalk5.dim("Search marketplace for MCP servers")}
1109
- ${chalk5.cyan("mcphost info notion")} ${chalk5.dim("Get details about Notion MCP")}
1110
- ${chalk5.cyan("mcphost import --from smithery")} ${chalk5.dim("Import connections from Smithery")}
1111
- ${chalk5.cyan("mcphost disconnect github")} ${chalk5.dim("Remove GitHub MCP connection")}
2537
+ ${chalk16.bold("Quick Start (one command!):")}
2538
+ ${chalk16.dim("1.")} ${chalk16.cyan("mcphosting login")} ${chalk16.dim("Authenticate")}
2539
+ ${chalk16.dim("2.")} ${chalk16.cyan("mcphosting deploy --template weather")} ${chalk16.dim("Deploy a template")}
2540
+ ${chalk16.dim(" ")} ${chalk16.dim("Done! URL returned. API key created. Config ready.")}
1112
2541
 
1113
- ${chalk5.bold("Supported AI Clients:")}
1114
- \u2022 ${chalk5.green("Claude Desktop")} - Auto-configured via claude_desktop_config.json
1115
- \u2022 ${chalk5.green("Cursor")} - Auto-configured via .cursor/mcp.json
1116
- \u2022 ${chalk5.green("VS Code")} - Configured via .vscode/mcp.json
1117
- \u2022 ${chalk5.green("OpenClaw")} - Auto-configured via ~/.openclaw/mcp.json
1118
- \u2022 ${chalk5.green("ChatGPT")} - Manual setup with web instructions
2542
+ ${chalk16.bold("Global Flags:")}
2543
+ ${chalk16.cyan("--json")} ${chalk16.dim("Output as JSON (agent-friendly)")}
2544
+ ${chalk16.cyan("--silent")} ${chalk16.dim("Suppress interactive output")}
1119
2545
 
1120
- ${chalk5.bold("Get Started:")}
1121
- ${chalk5.dim("1.")} ${chalk5.cyan("mcphost search <topic>")} ${chalk5.dim("Find MCP servers")}
1122
- ${chalk5.dim("2.")} ${chalk5.cyan("mcphost connect <slug>")} ${chalk5.dim("Connect to your AI clients")}
1123
- ${chalk5.dim("3.")} ${chalk5.cyan("mcphost list")} ${chalk5.dim("View your connections")}
2546
+ ${chalk16.bold("Environment Variables:")}
2547
+ ${chalk16.cyan("MCPHOSTING_TOKEN")} ${chalk16.dim("Auth token (overrides config)")}
2548
+ ${chalk16.cyan("MCPHOSTING_API_URL")} ${chalk16.dim("API base URL (default: mcphosting.com)")}
1124
2549
 
1125
- ${chalk5.yellow("\u2B50 Star us:")} ${chalk5.blue("https://github.com/gorlomi-enzo/mcphosting-cli")}
1126
- ${chalk5.yellow("\u{1F4DA} Docs:")} ${chalk5.blue("https://mcphosting.com/docs")}
2550
+ ${chalk16.bold("Deploy Options:")}
2551
+ ${chalk16.cyan("mcphosting deploy")} ${chalk16.dim("Deploy from current directory")}
2552
+ ${chalk16.cyan("mcphosting deploy --template crypto")} ${chalk16.dim("Deploy from template")}
2553
+ ${chalk16.cyan("mcphosting deploy --github <url>")} ${chalk16.dim("Deploy from GitHub repo")}
2554
+ ${chalk16.cyan("mcphosting deploy --api-url <url>")} ${chalk16.dim("Register external server")}
2555
+ ${chalk16.cyan("mcphosting deploy --configure")} ${chalk16.dim("Auto-configure AI clients")}
2556
+
2557
+ ${chalk16.bold("Templates:")}
2558
+ ${chalk16.cyan("weather")} ${chalk16.cyan("crypto")} ${chalk16.cyan("notion")} ${chalk16.cyan("postgres")} ${chalk16.cyan("blank")}
2559
+
2560
+ ${chalk16.bold("Agent Usage:")}
2561
+ ${chalk16.cyan("mcphosting deploy --template crypto --json")} ${chalk16.dim("Deploy + JSON output")}
2562
+ ${chalk16.cyan("mcphosting whoami --json")} ${chalk16.dim("Check auth status")}
2563
+ ${chalk16.cyan("mcphosting account --json")} ${chalk16.dim("Account + servers")}
2564
+ ${chalk16.cyan("mcphosting list --json")} ${chalk16.dim("List all servers")}
2565
+
2566
+ ${chalk16.bold("More Commands:")}
2567
+ ${chalk16.cyan("mcphosting init")} ${chalk16.dim("Create mcphosting.json")}
2568
+ ${chalk16.cyan("mcphosting connect <slug>")} ${chalk16.dim("Connect MCP to AI clients")}
2569
+ ${chalk16.cyan("mcphosting list")} ${chalk16.dim("List all servers")}
2570
+ ${chalk16.cyan("mcphosting status <server>")} ${chalk16.dim("Check server status")}
2571
+ ${chalk16.cyan('mcphosting keys create "Key Name"')} ${chalk16.dim("Create API key")}
2572
+ ${chalk16.cyan("mcphosting search <query>")} ${chalk16.dim("Search marketplace")}
2573
+ ${chalk16.cyan("mcphosting completions --shell=zsh")} ${chalk16.dim("Shell completions")}
2574
+
2575
+ ${chalk16.bold("Supported AI Clients:")}
2576
+ \u2022 ${chalk16.green("Claude Desktop")} \u2022 ${chalk16.green("Cursor")} \u2022 ${chalk16.green("VS Code")} \u2022 ${chalk16.green("OpenClaw")} \u2022 ${chalk16.green("ChatGPT")}
2577
+
2578
+ ${chalk16.yellow("\u{1F4DA} Docs:")} ${chalk16.blue("https://mcphosting.com/docs")}
2579
+ ${chalk16.yellow("\u2B50 GitHub:")} ${chalk16.blue("https://github.com/gorlomi-enzo/mcphosting-cli")}
1127
2580
  `);
1128
2581
  process.on("uncaughtException", (error) => {
1129
- Logger.error(`Uncaught error: ${error.message}`);
1130
- if (process.env.DEBUG) {
1131
- console.error(error.stack);
2582
+ if (Logger.isJsonMode) {
2583
+ console.error(JSON.stringify({ success: false, error: error.message }));
2584
+ } else {
2585
+ Logger.error(`Unexpected error: ${error.message}`);
1132
2586
  }
2587
+ if (process.env.DEBUG) console.error(error.stack);
1133
2588
  process.exit(1);
1134
2589
  });
1135
2590
  process.on("unhandledRejection", (reason) => {
1136
- Logger.error(`Unhandled rejection: ${reason}`);
1137
- if (process.env.DEBUG) {
1138
- console.error(reason);
2591
+ if (Logger.isJsonMode) {
2592
+ console.error(JSON.stringify({ success: false, error: String(reason) }));
2593
+ } else {
2594
+ Logger.error(`Unhandled rejection: ${reason}`);
1139
2595
  }
2596
+ if (process.env.DEBUG) console.error(reason);
1140
2597
  process.exit(1);
1141
2598
  });
1142
2599
  process.on("SIGINT", () => {
1143
- console.log("\n");
1144
- Logger.info("Goodbye! \u{1F44B}");
2600
+ if (!Logger.isSilent && !Logger.isJsonMode) {
2601
+ console.log("\n");
2602
+ Logger.info("Goodbye! \u{1F44B}");
2603
+ }
1145
2604
  process.exit(0);
1146
2605
  });
1147
2606
  async function main() {
1148
2607
  try {
1149
2608
  await program.parseAsync();
1150
2609
  } catch (error) {
1151
- Logger.error(`Command failed: ${error}`);
1152
- if (process.env.DEBUG) {
1153
- console.error(error);
2610
+ if (Logger.isJsonMode) {
2611
+ console.error(JSON.stringify({ success: false, error: error.message }));
2612
+ } else {
2613
+ Logger.error(`Command failed: ${error.message}`);
1154
2614
  }
2615
+ if (process.env.DEBUG) console.error(error);
1155
2616
  process.exit(1);
1156
2617
  }
1157
2618
  }