premanmcp 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -22
- package/bin/api_tools.js +215 -0
- package/bin/cli.js +63 -324
- package/bin/connect.js +715 -0
- package/bin/integrations.js +367 -0
- package/bin/shared.js +351 -0
- package/dist/server.js +121 -1
- package/package.json +3 -2
package/bin/cli.js
CHANGED
|
@@ -11,20 +11,34 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
-
import { existsSync
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import path from "node:path";
|
|
17
|
-
import { createInterface } from "node:readline/promises";
|
|
18
17
|
import { fileURLToPath } from "node:url";
|
|
19
18
|
|
|
19
|
+
import { ENDPOINTS_HELP, TEST_HELP, endpointsCommand, testCommand } from "./api_tools.js";
|
|
20
|
+
import { CONNECT_HELP, connectCommand, writeCursorConfig } from "./connect.js";
|
|
21
|
+
import {
|
|
22
|
+
INTEGRATIONS_HELP,
|
|
23
|
+
awsCommand,
|
|
24
|
+
githubCommand,
|
|
25
|
+
onboardCommand,
|
|
26
|
+
slackCommand,
|
|
27
|
+
} from "./integrations.js";
|
|
20
28
|
import { HOSTED_HELP, linkCommand, runCommand, toolsCommand } from "./hosted.js";
|
|
29
|
+
import {
|
|
30
|
+
CREDENTIALS_FILE,
|
|
31
|
+
DEFAULT_BACKEND,
|
|
32
|
+
DEFAULT_FRONTEND,
|
|
33
|
+
authenticateTerminal,
|
|
34
|
+
buildServerConfig,
|
|
35
|
+
hasKeyAvailable,
|
|
36
|
+
makeArgs,
|
|
37
|
+
readStoredCredentials,
|
|
38
|
+
} from "./shared.js";
|
|
21
39
|
|
|
22
40
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
41
|
const ROOT = path.join(__dirname, "..");
|
|
24
|
-
const DEFAULT_BACKEND = "https://api.preman.live";
|
|
25
|
-
const DEFAULT_FRONTEND = "https://app.preman.live";
|
|
26
|
-
const CREDENTIALS_DIR = path.join(os.homedir(), ".preman");
|
|
27
|
-
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
28
42
|
|
|
29
43
|
const args = process.argv.slice(2);
|
|
30
44
|
const command = args[0] === "--help" || args[0] === "-h"
|
|
@@ -33,31 +47,35 @@ const command = args[0] === "--help" || args[0] === "-h"
|
|
|
33
47
|
? args[0]
|
|
34
48
|
: "start";
|
|
35
49
|
const commandArgs = command === "start" ? args : args.slice(1);
|
|
50
|
+
const cliArgs = makeArgs(commandArgs);
|
|
36
51
|
|
|
37
52
|
function argValue(name, fallback = "") {
|
|
38
|
-
|
|
39
|
-
if (index === -1) return fallback;
|
|
40
|
-
return commandArgs[index + 1] || fallback;
|
|
53
|
+
return cliArgs.value(name, fallback);
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
function hasFlag(name) {
|
|
44
|
-
return
|
|
57
|
+
return cliArgs.has(name);
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
function printHelp() {
|
|
48
61
|
process.stdout.write(`PreMan MCP
|
|
49
62
|
|
|
50
63
|
Usage:
|
|
64
|
+
preman onboard Sign in, then connect agent, GitHub, AWS, Slack
|
|
65
|
+
preman connect [options] Pick a coding agent and connect it
|
|
66
|
+
preman aws | github | slack Connect one integration on its own
|
|
51
67
|
npm exec -y premanmcp@latest -- login Create/login to PreMan from the terminal
|
|
52
68
|
npm exec -y premanmcp@latest -- install [options] Install PreMan into Cursor MCP config
|
|
53
69
|
npm exec -y premanmcp@latest -- Start the PreMan MCP server
|
|
54
70
|
preman link|tools|run ... Drive a published hosted MCP
|
|
55
|
-
|
|
71
|
+
preman endpoints list|discover|setup ... Discover, list, and set up API endpoints
|
|
72
|
+
preman test <id> [--scenario ...] [--stress] Generate + run tests for an endpoint
|
|
73
|
+
${INTEGRATIONS_HELP}${CONNECT_HELP}${ENDPOINTS_HELP}${TEST_HELP}
|
|
56
74
|
Login options:
|
|
57
75
|
--email <email> Pre-fill the email prompt
|
|
58
76
|
--backend <url> PreMan backend URL. Defaults to ${DEFAULT_BACKEND}
|
|
59
77
|
|
|
60
|
-
Install options:
|
|
78
|
+
Install options (Cursor only — prefer 'preman connect'):
|
|
61
79
|
--api-key <key> PreMan API key. If omitted, stored CLI credentials are used
|
|
62
80
|
--backend <url> PreMan backend URL. Defaults to ${DEFAULT_BACKEND}
|
|
63
81
|
--frontend <url> PreMan frontend URL. Defaults to ${DEFAULT_FRONTEND}
|
|
@@ -68,296 +86,15 @@ Install options:
|
|
|
68
86
|
--print Print the config instead of writing it
|
|
69
87
|
|
|
70
88
|
Examples:
|
|
89
|
+
npm exec -y premanmcp@latest -- connect
|
|
90
|
+
preman connect --agent claude-code
|
|
71
91
|
npm exec -y premanmcp@latest -- login
|
|
72
|
-
npm exec -y premanmcp@latest -- install
|
|
73
|
-
npm exec -y premanmcp@latest -- install --api-key pm_live_xxx
|
|
74
92
|
npm exec -y premanmcp@latest -- install --project --backend http://127.0.0.1:8000
|
|
75
93
|
${HOSTED_HELP}`);
|
|
76
94
|
}
|
|
77
95
|
|
|
78
|
-
function readJsonFile(filePath) {
|
|
79
|
-
if (!existsSync(filePath)) return {};
|
|
80
|
-
const raw = readFileSync(filePath, "utf8").trim();
|
|
81
|
-
if (!raw) return {};
|
|
82
|
-
try {
|
|
83
|
-
return JSON.parse(raw);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`Could not parse ${filePath}: ${error.message}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function readStoredCredentials() {
|
|
90
|
-
try {
|
|
91
|
-
const raw = readFileSync(CREDENTIALS_FILE, "utf8").trim();
|
|
92
|
-
if (!raw) return null;
|
|
93
|
-
const creds = JSON.parse(raw);
|
|
94
|
-
if (creds && typeof creds.api_key === "string" && creds.api_key.startsWith("pm_live_")) {
|
|
95
|
-
return creds;
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// No stored credentials yet.
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function saveStoredCredentials(creds) {
|
|
104
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
|
|
105
|
-
writeFileSync(CREDENTIALS_FILE, `${JSON.stringify(creds, null, 2)}\n`, { mode: 0o600 });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function backendUrl() {
|
|
109
|
-
return argValue("--backend", process.env.PREMAN_BACKEND || DEFAULT_BACKEND).replace(/\/+$/, "");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function promptText(question) {
|
|
113
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
114
|
-
try {
|
|
115
|
-
return (await rl.question(question)).trim();
|
|
116
|
-
} finally {
|
|
117
|
-
rl.close();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function promptSecret(question) {
|
|
122
|
-
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
123
|
-
return promptText(question);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return new Promise((resolve) => {
|
|
127
|
-
const stdin = process.stdin;
|
|
128
|
-
const stdout = process.stdout;
|
|
129
|
-
const wasRaw = stdin.isRaw;
|
|
130
|
-
let value = "";
|
|
131
|
-
|
|
132
|
-
function cleanup() {
|
|
133
|
-
stdin.off("data", onData);
|
|
134
|
-
stdin.setRawMode(Boolean(wasRaw));
|
|
135
|
-
stdin.pause();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function onData(chunk) {
|
|
139
|
-
const text = String(chunk);
|
|
140
|
-
if (text === "\u0003") {
|
|
141
|
-
stdout.write("\n");
|
|
142
|
-
cleanup();
|
|
143
|
-
process.exit(130);
|
|
144
|
-
}
|
|
145
|
-
if (text === "\r" || text === "\n" || text === "\u0004") {
|
|
146
|
-
stdout.write("\n");
|
|
147
|
-
cleanup();
|
|
148
|
-
resolve(value);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (text === "\u007f" || text === "\b") {
|
|
152
|
-
if (value.length) {
|
|
153
|
-
value = value.slice(0, -1);
|
|
154
|
-
stdout.write("\b \b");
|
|
155
|
-
}
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
value += text;
|
|
159
|
-
stdout.write("*");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
stdout.write(question);
|
|
163
|
-
stdin.setRawMode(true);
|
|
164
|
-
stdin.resume();
|
|
165
|
-
stdin.setEncoding("utf8");
|
|
166
|
-
stdin.on("data", onData);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function promptPasswordTwice() {
|
|
171
|
-
const password = await promptSecret("Create password: ");
|
|
172
|
-
if (!password || password.length < 6) {
|
|
173
|
-
throw new Error("Password must be at least 6 characters.");
|
|
174
|
-
}
|
|
175
|
-
const confirm = await promptSecret("Confirm password: ");
|
|
176
|
-
if (password !== confirm) {
|
|
177
|
-
throw new Error("Passwords do not match.");
|
|
178
|
-
}
|
|
179
|
-
return password;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function callBackendJson(method, routePath, { json, token, query } = {}) {
|
|
183
|
-
const url = new URL(routePath.replace(/^\/+/, ""), `${backendUrl()}/`);
|
|
184
|
-
if (query) {
|
|
185
|
-
for (const [key, value] of Object.entries(query)) {
|
|
186
|
-
if (value != null && value !== "") url.searchParams.set(key, String(value));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const headers = { Accept: "application/json" };
|
|
191
|
-
const hasBody = json !== undefined && json !== null;
|
|
192
|
-
if (hasBody) headers["Content-Type"] = "application/json";
|
|
193
|
-
if (token) headers.Authorization = `Bearer ${token}`;
|
|
194
|
-
|
|
195
|
-
const resp = await fetch(url, {
|
|
196
|
-
method,
|
|
197
|
-
headers,
|
|
198
|
-
body: hasBody ? JSON.stringify(json) : undefined,
|
|
199
|
-
});
|
|
200
|
-
const text = await resp.text();
|
|
201
|
-
let body = {};
|
|
202
|
-
try {
|
|
203
|
-
body = text ? JSON.parse(text) : {};
|
|
204
|
-
} catch {
|
|
205
|
-
body = { raw: text };
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
status_code: resp.status,
|
|
209
|
-
ok: resp.ok,
|
|
210
|
-
...body,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function assertOk(result, action) {
|
|
215
|
-
if (result.ok) return;
|
|
216
|
-
const detail = result.detail || result.message || result.raw || `${action} failed`;
|
|
217
|
-
throw new Error(`${action} failed: ${result.status_code} ${detail}`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async function verifyUnconfirmedAccount(email) {
|
|
221
|
-
process.stdout.write("This email exists but is not verified. Sending a new OTP.\n");
|
|
222
|
-
const resend = await callBackendJson("POST", "/auth/resend-otp", { json: { email } });
|
|
223
|
-
assertOk(resend, "resend OTP");
|
|
224
|
-
const otp = await promptText("Verification code: ");
|
|
225
|
-
const verified = await callBackendJson("POST", "/auth/verify-otp", { json: { email, otp } });
|
|
226
|
-
assertOk(verified, "verify OTP");
|
|
227
|
-
return verified.access_token;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function createAccountFromTerminal(email) {
|
|
231
|
-
const signup = await callBackendJson("POST", "/auth/start-signup", {
|
|
232
|
-
json: { email },
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
if (signup.ok) {
|
|
236
|
-
process.stdout.write("Verification code sent. Check your email.\n");
|
|
237
|
-
const otp = await promptText("Verification code: ");
|
|
238
|
-
const password = await promptPasswordTwice();
|
|
239
|
-
const setPassword = await callBackendJson("POST", "/auth/set-password", {
|
|
240
|
-
json: { email, otp, new_password: password },
|
|
241
|
-
});
|
|
242
|
-
assertOk(setPassword, "set password");
|
|
243
|
-
return String(setPassword.access_token || "");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (signup.status_code !== 404) {
|
|
247
|
-
assertOk(signup, "start signup");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
process.stdout.write(
|
|
251
|
-
"This PreMan backend uses the password-first signup flow. Create your password now, then enter the email code.\n"
|
|
252
|
-
);
|
|
253
|
-
const password = await promptPasswordTwice();
|
|
254
|
-
const legacySignup = await callBackendJson("POST", "/auth/signup", {
|
|
255
|
-
json: { email, password },
|
|
256
|
-
});
|
|
257
|
-
assertOk(legacySignup, "signup");
|
|
258
|
-
process.stdout.write("Verification code sent. Check your email.\n");
|
|
259
|
-
const otp = await promptText("Verification code: ");
|
|
260
|
-
const verified = await callBackendJson("POST", "/auth/verify-otp", {
|
|
261
|
-
json: { email, otp },
|
|
262
|
-
});
|
|
263
|
-
assertOk(verified, "verify OTP");
|
|
264
|
-
return String(verified.access_token || "");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function authenticateTerminal() {
|
|
268
|
-
const explicitKey = argValue("--api-key", process.env.PREMAN_API_KEY || "");
|
|
269
|
-
if (explicitKey && explicitKey.startsWith("pm_live_")) {
|
|
270
|
-
const creds = {
|
|
271
|
-
api_key: explicitKey,
|
|
272
|
-
backend_url: backendUrl(),
|
|
273
|
-
user_email: argValue("--email", ""),
|
|
274
|
-
device_name: os.hostname(),
|
|
275
|
-
created_at: new Date().toISOString(),
|
|
276
|
-
};
|
|
277
|
-
saveStoredCredentials(creds);
|
|
278
|
-
return creds;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const email = (argValue("--email", "") || await promptText("Email: ")).trim().toLowerCase();
|
|
282
|
-
if (!email) throw new Error("Email is required.");
|
|
283
|
-
|
|
284
|
-
process.stdout.write(`Checking PreMan account for ${email}...\n`);
|
|
285
|
-
const account = await callBackendJson("GET", "/auth/needs-password", { query: { email } });
|
|
286
|
-
assertOk(account, "check account");
|
|
287
|
-
|
|
288
|
-
let accessToken = "";
|
|
289
|
-
|
|
290
|
-
if (!account.exists) {
|
|
291
|
-
process.stdout.write("No PreMan account found. Creating one now.\n");
|
|
292
|
-
accessToken = await createAccountFromTerminal(email);
|
|
293
|
-
} else if (account.needs_password) {
|
|
294
|
-
process.stdout.write("This account needs a password. Sending an OTP first.\n");
|
|
295
|
-
const resend = await callBackendJson("POST", "/auth/resend-otp", { json: { email } });
|
|
296
|
-
assertOk(resend, "resend OTP");
|
|
297
|
-
const otp = await promptText("Verification code: ");
|
|
298
|
-
const password = await promptPasswordTwice();
|
|
299
|
-
const setPassword = await callBackendJson("POST", "/auth/set-password", {
|
|
300
|
-
json: { email, otp, new_password: password },
|
|
301
|
-
});
|
|
302
|
-
assertOk(setPassword, "set password");
|
|
303
|
-
accessToken = String(setPassword.access_token || "");
|
|
304
|
-
} else {
|
|
305
|
-
const password = await promptSecret("Password: ");
|
|
306
|
-
const login = await callBackendJson("POST", "/auth/login", {
|
|
307
|
-
json: { email, password },
|
|
308
|
-
});
|
|
309
|
-
if (!login.ok && login.status_code === 403 && String(login.detail || "").toLowerCase().includes("not verified")) {
|
|
310
|
-
accessToken = await verifyUnconfirmedAccount(email);
|
|
311
|
-
} else {
|
|
312
|
-
assertOk(login, "login");
|
|
313
|
-
accessToken = String(login.access_token || "");
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (!accessToken) throw new Error("PreMan did not return an access token.");
|
|
318
|
-
|
|
319
|
-
const keyName = `PreMan MCP CLI (${os.hostname()})`;
|
|
320
|
-
const key = await callBackendJson("POST", "/api-keys", {
|
|
321
|
-
token: accessToken,
|
|
322
|
-
json: { name: keyName },
|
|
323
|
-
});
|
|
324
|
-
assertOk(key, "create API key");
|
|
325
|
-
if (!key.key || !String(key.key).startsWith("pm_live_")) {
|
|
326
|
-
throw new Error("PreMan did not return a valid API key.");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const creds = {
|
|
330
|
-
api_key: String(key.key),
|
|
331
|
-
backend_url: backendUrl(),
|
|
332
|
-
user_email: email,
|
|
333
|
-
device_name: os.hostname(),
|
|
334
|
-
created_at: new Date().toISOString(),
|
|
335
|
-
};
|
|
336
|
-
saveStoredCredentials(creds);
|
|
337
|
-
return creds;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function buildServerConfig() {
|
|
341
|
-
const apiKey = argValue("--api-key", process.env.PREMAN_API_KEY || "");
|
|
342
|
-
const backend = argValue("--backend", process.env.PREMAN_BACKEND || DEFAULT_BACKEND);
|
|
343
|
-
const frontend = argValue("--frontend", process.env.PREMAN_FRONTEND || DEFAULT_FRONTEND);
|
|
344
|
-
const env = {
|
|
345
|
-
PREMAN_BACKEND: backend,
|
|
346
|
-
PREMAN_FRONTEND: frontend,
|
|
347
|
-
};
|
|
348
|
-
if (apiKey) {
|
|
349
|
-
env.PREMAN_API_KEY = apiKey;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
command: "npm",
|
|
354
|
-
args: ["exec", "-y", "--package", "premanmcp@latest", "--", "premanmcp"],
|
|
355
|
-
env,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
96
|
async function loginCommand() {
|
|
360
|
-
const creds = await authenticateTerminal();
|
|
97
|
+
const creds = await authenticateTerminal(cliArgs);
|
|
361
98
|
process.stdout.write(`PreMan account ready.
|
|
362
99
|
|
|
363
100
|
Email: ${creds.user_email || "unknown"}
|
|
@@ -366,61 +103,47 @@ API key: ${creds.api_key}
|
|
|
366
103
|
Saved to: ${CREDENTIALS_FILE}
|
|
367
104
|
|
|
368
105
|
You can now run:
|
|
369
|
-
|
|
106
|
+
preman connect
|
|
370
107
|
`);
|
|
371
108
|
}
|
|
372
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Cursor-only installer, kept for the documented `install` flow. `connect` is
|
|
112
|
+
* the same write plus agent choice, pairing, and dispatch setup.
|
|
113
|
+
*/
|
|
373
114
|
async function installCursorMcp() {
|
|
374
115
|
const serverName = argValue("--name", "preman");
|
|
375
116
|
const projectInstall = hasFlag("--project");
|
|
376
117
|
|
|
377
|
-
if (
|
|
378
|
-
!hasFlag("--print") &&
|
|
379
|
-
!hasFlag("--skip-login") &&
|
|
380
|
-
!argValue("--api-key", "") &&
|
|
381
|
-
!process.env.PREMAN_API_KEY &&
|
|
382
|
-
!readStoredCredentials()
|
|
383
|
-
) {
|
|
118
|
+
if (!hasFlag("--print") && !hasFlag("--skip-login") && !hasKeyAvailable(cliArgs)) {
|
|
384
119
|
process.stdout.write("First, let's create or connect your PreMan account.\n");
|
|
385
|
-
await authenticateTerminal();
|
|
120
|
+
await authenticateTerminal(cliArgs);
|
|
386
121
|
process.stdout.write("\n");
|
|
387
122
|
}
|
|
388
123
|
|
|
389
|
-
const serverConfig = buildServerConfig();
|
|
124
|
+
const serverConfig = buildServerConfig(cliArgs);
|
|
390
125
|
|
|
391
126
|
if (hasFlag("--print")) {
|
|
392
127
|
process.stdout.write(`${JSON.stringify({ mcpServers: { [serverName]: serverConfig } }, null, 2)}\n`);
|
|
393
128
|
return;
|
|
394
129
|
}
|
|
395
130
|
|
|
396
|
-
const
|
|
397
|
-
? path.join(process.cwd(), ".cursor", "mcp.json")
|
|
398
|
-
: path.join(os.homedir(), ".cursor", "mcp.json");
|
|
399
|
-
|
|
400
|
-
const config = {
|
|
401
|
-
...readJsonFile(configPath),
|
|
402
|
-
};
|
|
403
|
-
config.mcpServers = {
|
|
404
|
-
...(config.mcpServers || {}),
|
|
405
|
-
[serverName]: serverConfig,
|
|
406
|
-
};
|
|
131
|
+
const written = writeCursorConfig({ serverName, serverConfig, projectInstall });
|
|
407
132
|
|
|
408
|
-
const
|
|
409
|
-
mkdirSync(path.dirname(configPath), { recursive: true });
|
|
410
|
-
writeFileSync(configPath, rendered, { mode: 0o600 });
|
|
411
|
-
|
|
412
|
-
const hasInlineKey = Boolean(config.mcpServers[serverName].env.PREMAN_API_KEY);
|
|
133
|
+
const hasInlineKey = Boolean(serverConfig.env.PREMAN_API_KEY);
|
|
413
134
|
const hasStoredKey = Boolean(readStoredCredentials());
|
|
414
|
-
process.stdout.write(`PreMan MCP installed in ${
|
|
135
|
+
process.stdout.write(`PreMan MCP installed in ${written.path}
|
|
415
136
|
|
|
416
137
|
Server name: ${serverName}
|
|
417
|
-
Backend: ${
|
|
138
|
+
Backend: ${serverConfig.env.PREMAN_BACKEND}
|
|
418
139
|
|
|
419
140
|
Next steps:
|
|
420
141
|
1. ${hasInlineKey ? "Your PreMan API key was written to the MCP config." : hasStoredKey ? `Your PreMan API key is saved in ${CREDENTIALS_FILE}; the MCP server will load it automatically.` : "Run npm exec -y premanmcp@latest -- login to create/connect your account and generate an API key."}
|
|
421
142
|
2. Restart Cursor or toggle the PreMan MCP server off/on.
|
|
422
143
|
3. In your API repo, ask your coding agent:
|
|
423
144
|
"Use PreMan to convert the endpoints I choose into a hosted MCP server, then give me the Cursor/Claude install snippet."
|
|
145
|
+
|
|
146
|
+
Tip: 'preman connect' also supports Claude Code and Codex, and links the agent to your account.
|
|
424
147
|
`);
|
|
425
148
|
}
|
|
426
149
|
|
|
@@ -454,6 +177,18 @@ function startServer() {
|
|
|
454
177
|
async function main() {
|
|
455
178
|
if (command === "login") {
|
|
456
179
|
await loginCommand();
|
|
180
|
+
} else if (command === "connect") {
|
|
181
|
+
await connectCommand(commandArgs);
|
|
182
|
+
} else if (command === "onboard" || command === "setup") {
|
|
183
|
+
// makeArgs/authenticateTerminal/connectCommand are injected rather than
|
|
184
|
+
// imported there, so integrations.js stays free of a cycle back into the CLI.
|
|
185
|
+
await onboardCommand(commandArgs, { makeArgs, authenticateTerminal, connectCommand });
|
|
186
|
+
} else if (command === "aws") {
|
|
187
|
+
await awsCommand(makeArgs(commandArgs));
|
|
188
|
+
} else if (command === "github") {
|
|
189
|
+
await githubCommand(makeArgs(commandArgs));
|
|
190
|
+
} else if (command === "slack") {
|
|
191
|
+
await slackCommand(makeArgs(commandArgs));
|
|
457
192
|
} else if (command === "install") {
|
|
458
193
|
await installCursorMcp();
|
|
459
194
|
} else if (command === "link") {
|
|
@@ -462,6 +197,10 @@ async function main() {
|
|
|
462
197
|
await toolsCommand(commandArgs);
|
|
463
198
|
} else if (command === "run") {
|
|
464
199
|
await runCommand(commandArgs);
|
|
200
|
+
} else if (command === "endpoints") {
|
|
201
|
+
await endpointsCommand(commandArgs);
|
|
202
|
+
} else if (command === "test") {
|
|
203
|
+
await testCommand(commandArgs);
|
|
465
204
|
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
466
205
|
printHelp();
|
|
467
206
|
} else if (command === "start") {
|