premanmcp 0.12.0 → 0.13.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/bin/desktop.js +64 -8
- package/bin/integrations.js +12 -13
- package/package.json +4 -3
package/bin/desktop.js
CHANGED
|
@@ -13,7 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
import { spawn, spawnSync } from "node:child_process";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
chmodSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
mkdtempSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
readdirSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
17
26
|
import os from "node:os";
|
|
18
27
|
import path from "node:path";
|
|
19
28
|
|
|
@@ -76,16 +85,36 @@ export function writeDesktopSession(creds) {
|
|
|
76
85
|
*
|
|
77
86
|
* The session is written before the app is launched so a cold start finds it on
|
|
78
87
|
* first read, rather than racing a window that is already loading.
|
|
88
|
+
*
|
|
89
|
+
* Whether it was taken up is then waited for rather than assumed: the app
|
|
90
|
+
* deletes the file as it reads it, and an app too old to know about the file at
|
|
91
|
+
* all would otherwise be reported as signed in while the customer looks at a
|
|
92
|
+
* login screen. The file is left behind on timeout -- it expires on its own, and
|
|
93
|
+
* a slow first launch can still find it.
|
|
79
94
|
*/
|
|
80
|
-
export function openDesktopSignedIn(
|
|
95
|
+
export async function openDesktopSignedIn(
|
|
96
|
+
creds,
|
|
97
|
+
{ destination = "/Applications", waitMs = 12_000, sleep = defaultSleep } = {}
|
|
98
|
+
) {
|
|
81
99
|
if (!desktopAppInstalled(destination)) {
|
|
82
100
|
return { state: "not-installed" };
|
|
83
101
|
}
|
|
84
102
|
const handoff = writeDesktopSession(creds);
|
|
85
103
|
spawn("open", ["-a", "PreMan"], { stdio: "ignore", detached: true }).unref();
|
|
86
|
-
|
|
104
|
+
if (handoff.state !== "written") return { state: "opened", handoff: handoff.state };
|
|
105
|
+
|
|
106
|
+
const deadline = Date.now() + waitMs;
|
|
107
|
+
while (Date.now() < deadline) {
|
|
108
|
+
await sleep(500);
|
|
109
|
+
if (!existsSync(DESKTOP_SESSION_FILE)) {
|
|
110
|
+
return { state: "opened-signed-in", handoff: handoff.state };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { state: "opened-not-adopted", handoff: handoff.state };
|
|
87
114
|
}
|
|
88
115
|
|
|
116
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
|
+
|
|
89
118
|
/**
|
|
90
119
|
* Open a Playground session URL in PreMan.app on macOS when it is installed.
|
|
91
120
|
* Windows/Linux (and Mac without the app) get the website URL printed instead.
|
|
@@ -212,6 +241,34 @@ function mountedVolume(hdiutilOutput) {
|
|
|
212
241
|
return match[1].trim();
|
|
213
242
|
}
|
|
214
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Attach the disk image somewhere we named, rather than wherever it landed.
|
|
246
|
+
*
|
|
247
|
+
* Asking for the mountpoint is what makes this unambiguous. Reading it back out
|
|
248
|
+
* of `hdiutil info` is not a substitute: that lists every image attached on the
|
|
249
|
+
* machine, so an unrelated volume -- an iOS restore image, someone else's dmg --
|
|
250
|
+
* could be searched for PreMan.app instead of the download.
|
|
251
|
+
*/
|
|
252
|
+
function attachDiskImage(dmgPath, mountPoint) {
|
|
253
|
+
mkdirSync(mountPoint, { recursive: true });
|
|
254
|
+
try {
|
|
255
|
+
run("hdiutil", ["attach", dmgPath, "-nobrowse", "-quiet", "-readonly", "-mountpoint", mountPoint]);
|
|
256
|
+
return mountPoint;
|
|
257
|
+
} catch {
|
|
258
|
+
// Images that refuse an explicit mountpoint still report where they went, so
|
|
259
|
+
// fall back to this attach's own output -- never to the global image list.
|
|
260
|
+
return mountedVolume(run("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly"]));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** The bundle to copy out, found by name and then by extension. */
|
|
265
|
+
function appInsideVolume(volume) {
|
|
266
|
+
const named = path.join(volume, APP_NAME);
|
|
267
|
+
if (existsSync(named)) return named;
|
|
268
|
+
const bundle = readdirSync(volume).find((entry) => entry.endsWith(".app"));
|
|
269
|
+
return bundle ? path.join(volume, bundle) : null;
|
|
270
|
+
}
|
|
271
|
+
|
|
215
272
|
export async function installDesktopCommand(commandArgs = []) {
|
|
216
273
|
const args = makeArgs(commandArgs);
|
|
217
274
|
|
|
@@ -266,12 +323,11 @@ export async function installDesktopCommand(commandArgs = []) {
|
|
|
266
323
|
process.stdout.write(" checksum unavailable for this release; skipping verification\n");
|
|
267
324
|
}
|
|
268
325
|
|
|
269
|
-
|
|
270
|
-
mounted = mountedVolume(attach || run("hdiutil", ["info"]));
|
|
326
|
+
mounted = attachDiskImage(dmgPath, path.join(workDir, "mnt"));
|
|
271
327
|
|
|
272
|
-
const source =
|
|
273
|
-
if (!
|
|
274
|
-
throw new Error(
|
|
328
|
+
const source = appInsideVolume(mounted);
|
|
329
|
+
if (!source) {
|
|
330
|
+
throw new Error(`no application bundle found inside the disk image at ${mounted}`);
|
|
275
331
|
}
|
|
276
332
|
const target = path.join(destination, APP_NAME);
|
|
277
333
|
if (existsSync(target)) rmSync(target, { recursive: true, force: true });
|
package/bin/integrations.js
CHANGED
|
@@ -379,7 +379,8 @@ export async function onboardCommand(
|
|
|
379
379
|
process.stdout.write("Sign in there with the account you just used.\n");
|
|
380
380
|
return;
|
|
381
381
|
}
|
|
382
|
-
|
|
382
|
+
process.stdout.write("Opening PreMan\u2026\n");
|
|
383
|
+
const opened = await openDesktopSignedIn(creds);
|
|
383
384
|
if (opened.state === "opened-signed-in") {
|
|
384
385
|
process.stdout.write("Opened PreMan, signed in as this account.\n");
|
|
385
386
|
} else if (opened.state === "not-installed") {
|
|
@@ -387,25 +388,24 @@ export async function onboardCommand(
|
|
|
387
388
|
"PreMan is not in /Applications yet \u2014 open it once installed and sign in.\n"
|
|
388
389
|
);
|
|
389
390
|
} else {
|
|
390
|
-
//
|
|
391
|
-
// rather than let the customer wonder why they are
|
|
391
|
+
// Either the session could not be handed over or this app is too old to
|
|
392
|
+
// take it up. Say so rather than let the customer wonder why they are
|
|
393
|
+
// looking at a login screen.
|
|
392
394
|
process.stdout.write("Opened PreMan \u2014 sign in with the account you just used.\n");
|
|
393
395
|
}
|
|
394
396
|
},
|
|
395
397
|
},
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{ name: "Slack", question: "Connect Slack?", run: () => slackCommand(args) },
|
|
398
|
+
// GitHub, AWS and Slack are deliberately not here. Starting out is an account
|
|
399
|
+
// and the app; each integration is its own command for whenever it is wanted.
|
|
399
400
|
];
|
|
400
401
|
|
|
401
402
|
// Outcome per step rather than three lists, so revisiting a step replaces its
|
|
402
403
|
// result instead of recording it twice.
|
|
403
404
|
const outcome = new Map();
|
|
404
405
|
|
|
405
|
-
// Indexed rather than for..of so "back" can move the cursor. Re-running a
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
// is detected rather than duplicated.
|
|
406
|
+
// Indexed rather than for..of so "back" can move the cursor. Re-running a step
|
|
407
|
+
// is safe: an install that already happened overwrites the same bundle rather
|
|
408
|
+
// than duplicating it.
|
|
409
409
|
let i = 0;
|
|
410
410
|
while (i < steps.length) {
|
|
411
411
|
const step = steps[i];
|
|
@@ -451,9 +451,8 @@ export async function onboardCommand(
|
|
|
451
451
|
|
|
452
452
|
export const INTEGRATIONS_HELP = `
|
|
453
453
|
Setup options:
|
|
454
|
-
preman onboard Create or sign in to an account, install the
|
|
455
|
-
PreMan app
|
|
456
|
-
one prompt per step
|
|
454
|
+
preman onboard Create or sign in to an account, then install the
|
|
455
|
+
PreMan app and open it signed in
|
|
457
456
|
preman aws Connect an AWS account and stream a log group
|
|
458
457
|
preman github Install the PreMan GitHub App
|
|
459
458
|
preman slack Add PreMan to a Slack workspace
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "premanmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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,10 @@
|
|
|
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 build && npm run test:connect && npm run test:node",
|
|
16
|
+
"test": "npm run build && npm run test:connect && npm run test:node && npm run test:dmg",
|
|
17
17
|
"test:connect": "node scripts/smoke-connect.mjs",
|
|
18
|
-
"test:node": "node --test --test-timeout=90000 scripts/smoke-account.mjs scripts/smoke-cli-entrypoint.mjs 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-runner-heartbeat.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 scripts/smoke-desktop-session.mjs scripts/smoke-api-tools.mjs scripts/smoke-tests-workbench.mjs scripts/smoke-bin-scope.mjs"
|
|
18
|
+
"test:node": "node --test --test-timeout=90000 scripts/smoke-account.mjs scripts/smoke-cli-entrypoint.mjs 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-runner-heartbeat.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 scripts/smoke-desktop-session.mjs scripts/smoke-api-tools.mjs scripts/smoke-tests-workbench.mjs scripts/smoke-bin-scope.mjs",
|
|
19
|
+
"test:dmg": "node --test --test-timeout=300000 scripts/smoke-install-desktop-volume.mjs"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@modelcontextprotocol/ext-apps": "^0.1.0",
|