premanmcp 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -11
- package/bin/account.js +1 -1
- package/bin/api_tools.js +2 -2
- package/bin/cli.js +5 -1
- package/bin/connect.js +732 -68
- package/bin/hook.js +16 -4
- package/bin/integrations.js +46 -25
- package/bin/runner.js +946 -0
- package/bin/shared.js +65 -6
- package/dist/server.js +30 -9
- package/package.json +3 -3
package/bin/shared.js
CHANGED
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
chmodSync,
|
|
13
|
+
existsSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
realpathSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
} from "node:fs";
|
|
12
20
|
import os from "node:os";
|
|
13
21
|
import path from "node:path";
|
|
14
22
|
import { createInterface } from "node:readline/promises";
|
|
@@ -29,16 +37,55 @@ export const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
|
29
37
|
*/
|
|
30
38
|
let _invocation = null;
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The package that owns the first `preman` on PATH, or "".
|
|
42
|
+
*
|
|
43
|
+
* `preman` is not ours by name alone: `preman-sdk` publishes a bin called
|
|
44
|
+
* exactly that, and whichever package lost the race still resolves. Assuming it
|
|
45
|
+
* is us writes hints and a git hook that call the other CLI — which answers
|
|
46
|
+
* "Unknown command: verify" and turns every push into a silent
|
|
47
|
+
* "[preman] checks skipped", the worst possible failure for a tool whose whole
|
|
48
|
+
* job is running checks on push.
|
|
49
|
+
*/
|
|
50
|
+
export function pathPremanOwner() {
|
|
34
51
|
const probe = process.platform === "win32" ? "where" : "which";
|
|
52
|
+
let resolved = "";
|
|
35
53
|
try {
|
|
36
54
|
const found = spawnSync(probe, ["preman"], { stdio: "pipe", encoding: "utf8" });
|
|
37
|
-
|
|
55
|
+
if (found.status !== 0) return "";
|
|
56
|
+
resolved = String(found.stdout || "").split("\n")[0].trim();
|
|
57
|
+
} catch {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
if (!resolved) return "";
|
|
61
|
+
|
|
62
|
+
// Walk up from the real file, not the symlink: a bin is a link into the
|
|
63
|
+
// package directory, and only that directory's manifest names the owner.
|
|
64
|
+
try {
|
|
65
|
+
let dir = path.dirname(realpathSync(resolved));
|
|
66
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
67
|
+
const manifest = path.join(dir, "package.json");
|
|
68
|
+
if (existsSync(manifest)) {
|
|
69
|
+
return String(JSON.parse(readFileSync(manifest, "utf8")).name || "");
|
|
70
|
+
}
|
|
71
|
+
const parent = path.dirname(dir);
|
|
72
|
+
if (parent === dir) break;
|
|
73
|
+
dir = parent;
|
|
74
|
+
}
|
|
38
75
|
} catch {
|
|
39
|
-
|
|
76
|
+
return "";
|
|
40
77
|
}
|
|
41
|
-
return
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function cliInvocation() {
|
|
82
|
+
return (_invocation ||=
|
|
83
|
+
pathPremanOwner() === "premanmcp" ? "preman" : "npm exec -y premanmcp@latest --");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Test seam: `cliInvocation` memoizes a PATH probe for the life of the process. */
|
|
87
|
+
export function resetCliInvocation() {
|
|
88
|
+
_invocation = null;
|
|
42
89
|
}
|
|
43
90
|
|
|
44
91
|
export function makeArgs(commandArgs = []) {
|
|
@@ -93,6 +140,18 @@ export function saveStoredCredentials(creds) {
|
|
|
93
140
|
writeFileSync(CREDENTIALS_FILE, `${JSON.stringify(creds, null, 2)}\n`, { mode: 0o600 });
|
|
94
141
|
}
|
|
95
142
|
|
|
143
|
+
/** Forget the stored key. Returns whether there was one. */
|
|
144
|
+
export function clearStoredCredentials() {
|
|
145
|
+
if (!existsSync(CREDENTIALS_FILE)) return false;
|
|
146
|
+
rmSync(CREDENTIALS_FILE, { force: true });
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** True when the key in play came from a flag or the environment, not our store. */
|
|
151
|
+
export function apiKeyIsExplicit(args) {
|
|
152
|
+
return Boolean(args.value("--api-key", "") || process.env.PREMAN_API_KEY);
|
|
153
|
+
}
|
|
154
|
+
|
|
96
155
|
export function backendUrl(args) {
|
|
97
156
|
return args.value("--backend", process.env.PREMAN_BACKEND || DEFAULT_BACKEND).replace(/\/+$/, "");
|
|
98
157
|
}
|
package/dist/server.js
CHANGED
|
@@ -9,11 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { MCP_PREVIEW_RESOURCE_URI, RESOURCE_URI_META_KEY, buildConversionPanelHtml, writeMcpPreviewFile, } from "./mcp-preview-panel.js";
|
|
12
|
-
|
|
13
|
-
* Load ``PREMAN_*`` from repo JSON so project URLs win over stale **global** Cursor MCP env
|
|
14
|
-
* (which often sets the wrong ``PREMAN_BACKEND``, e.g. app port instead of API port).
|
|
15
|
-
* First match wins: ``.cursor/preman-mcp.config.json``, then ``preman-mcp.config.json`` (cwd).
|
|
16
|
-
*/
|
|
12
|
+
let REPO_CONFIG = null;
|
|
17
13
|
function applyRepoPremanConfig() {
|
|
18
14
|
const candidates = [
|
|
19
15
|
path.join(process.cwd(), ".cursor", "preman-mcp.config.json"),
|
|
@@ -24,13 +20,19 @@ function applyRepoPremanConfig() {
|
|
|
24
20
|
if (!existsSync(p))
|
|
25
21
|
continue;
|
|
26
22
|
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
23
|
+
const override = j.PREMAN_CONFIG_OVERRIDE === true || j.PREMAN_CONFIG_OVERRIDE === "true";
|
|
24
|
+
const applied = [];
|
|
27
25
|
for (const [key, val] of Object.entries(j)) {
|
|
28
|
-
if (!key.startsWith("PREMAN_"))
|
|
26
|
+
if (!key.startsWith("PREMAN_") || key === "PREMAN_CONFIG_OVERRIDE")
|
|
29
27
|
continue;
|
|
30
|
-
if (typeof val
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
if (typeof val !== "string" || !val.trim())
|
|
29
|
+
continue;
|
|
30
|
+
if (!override && process.env[key])
|
|
31
|
+
continue;
|
|
32
|
+
process.env[key] = val.trim();
|
|
33
|
+
applied.push(key);
|
|
33
34
|
}
|
|
35
|
+
REPO_CONFIG = { path: p, override, applied };
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
36
38
|
catch {
|
|
@@ -42,6 +44,22 @@ applyRepoPremanConfig();
|
|
|
42
44
|
const BACKEND_URL = process.env.PREMAN_BACKEND || "https://api.preman.live";
|
|
43
45
|
const FRONTEND_URL = process.env.PREMAN_FRONTEND || "https://app.preman.live";
|
|
44
46
|
let API_KEY = process.env.PREMAN_API_KEY || "";
|
|
47
|
+
/**
|
|
48
|
+
* Where the URLs this process is using came from.
|
|
49
|
+
*
|
|
50
|
+
* Reported by `preman_status` because the most confusing failure in setup is a
|
|
51
|
+
* server that works perfectly against the wrong backend: the key is fine, the
|
|
52
|
+
* config on disk is fine, and every call still 401s. Naming the file that
|
|
53
|
+
* redirected it turns that into a one-line diagnosis.
|
|
54
|
+
*/
|
|
55
|
+
function configSource() {
|
|
56
|
+
return {
|
|
57
|
+
backend_url: BACKEND_URL,
|
|
58
|
+
repo_config: REPO_CONFIG
|
|
59
|
+
? { path: REPO_CONFIG.path, override: REPO_CONFIG.override, applied: REPO_CONFIG.applied }
|
|
60
|
+
: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
45
63
|
function detectCodingAgent() {
|
|
46
64
|
const forced = (process.env.PREMAN_CODING_AGENT || "").trim().toLowerCase();
|
|
47
65
|
if (forced)
|
|
@@ -1025,6 +1043,7 @@ export function createServer() {
|
|
|
1025
1043
|
backend_url: BACKEND_URL,
|
|
1026
1044
|
frontend_base_url: FRONTEND_BASE,
|
|
1027
1045
|
endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
|
|
1046
|
+
config: configSource(),
|
|
1028
1047
|
message: "Not authenticated. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.",
|
|
1029
1048
|
}),
|
|
1030
1049
|
}],
|
|
@@ -1040,6 +1059,7 @@ export function createServer() {
|
|
|
1040
1059
|
backend_url: BACKEND_URL,
|
|
1041
1060
|
frontend_base_url: FRONTEND_BASE,
|
|
1042
1061
|
endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
|
|
1062
|
+
config: configSource(),
|
|
1043
1063
|
message: "Stored API key is no longer valid. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.",
|
|
1044
1064
|
}),
|
|
1045
1065
|
}],
|
|
@@ -1056,6 +1076,7 @@ export function createServer() {
|
|
|
1056
1076
|
backend_url: BACKEND_URL,
|
|
1057
1077
|
frontend_base_url: FRONTEND_BASE,
|
|
1058
1078
|
endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
|
|
1079
|
+
config: configSource(),
|
|
1059
1080
|
coding_agent: workbench,
|
|
1060
1081
|
_agent_hints: {
|
|
1061
1082
|
next_actions: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premanmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"dev": "tsx src/server.ts",
|
|
14
14
|
"open-mcp-preview": "node scripts/emit-mcp-preview.mjs",
|
|
15
15
|
"open-mcp-preview-in-cursor": "node scripts/open-cursor-preview.mjs",
|
|
16
|
-
"test": "npm run test:connect && npm run test:node",
|
|
16
|
+
"test": "npm run build && npm run test:connect && npm run test:node",
|
|
17
17
|
"test:connect": "node scripts/smoke-connect.mjs",
|
|
18
|
-
"test:node": "node --test --test-timeout=30000 scripts/smoke-launcher-config.mjs scripts/smoke-onboard.mjs scripts/smoke-local-detect.mjs scripts/smoke-prepush-hook.mjs scripts/smoke-verify-prepush.mjs scripts/smoke-push-diff.mjs scripts/smoke-progress-reporter.mjs scripts/smoke-verify-plan.mjs scripts/smoke-install-desktop.mjs"
|
|
18
|
+
"test:node": "node --test --test-timeout=30000 scripts/smoke-launcher-config.mjs scripts/smoke-runner.mjs scripts/smoke-repo-config.mjs scripts/smoke-onboard.mjs scripts/smoke-local-detect.mjs scripts/smoke-prepush-hook.mjs scripts/smoke-cli-identity.mjs scripts/smoke-verify-prepush.mjs scripts/smoke-push-diff.mjs scripts/smoke-progress-reporter.mjs scripts/smoke-verify-plan.mjs scripts/smoke-install-desktop.mjs"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@modelcontextprotocol/ext-apps": "^0.1.0",
|