premanmcp 0.16.2 → 1.0.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 +40 -371
- package/bin/api_tools.js +46 -7
- package/bin/cli.js +1 -1
- package/bin/desktop.js +155 -22
- package/bin/integrations.js +32 -5
- package/bin/link.js +3 -18
- package/bin/shared.js +20 -1
- package/bin/tests.js +1 -1
- package/bin/verify.js +2 -5
- package/dist/server.d.ts +7 -2
- package/dist/server.js +109 -1843
- package/package.json +10 -15
package/bin/desktop.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `preman install-desktop` — download and install the PreMan desktop app.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* ## One release, resolved once
|
|
5
|
+
*
|
|
6
|
+
* An install reads three things from the release: the version to report, the
|
|
7
|
+
* disk image, and the checksum to verify it against. Each used to be a separate
|
|
8
|
+
* request to `/releases/latest/…`, which is three independent answers to "what
|
|
9
|
+
* is the newest release" taken minutes apart, across a 100 MB download. Desktop
|
|
10
|
+
* ships several times a day, so those answers disagreed regularly, and every way
|
|
11
|
+
* they could disagree was bad:
|
|
12
|
+
*
|
|
13
|
+
* * The version was read *before* the download, so a release landing mid-fetch
|
|
14
|
+
* meant reporting `Installed PreMan 0.3.170` over a copy of 0.3.172 — the
|
|
15
|
+
* install genuinely was not the version it claimed.
|
|
16
|
+
* * The manifest was read *after*, so the same race made the checksum belong to
|
|
17
|
+
* a different build than the image. That is a hard `size mismatch`, and the
|
|
18
|
+
* 100 MB the user just waited for is discarded and fetched again.
|
|
19
|
+
* * `electron-updater` then found a newer build on first launch and downloaded
|
|
20
|
+
* a third copy, this time a 97 MB ZIP.
|
|
21
|
+
*
|
|
22
|
+
* So the tag is resolved once, up front, and every later request is pinned to
|
|
23
|
+
* it. A pinned install is reproducible, its reported version is the one on disk,
|
|
24
|
+
* and its checksum describes the bytes it actually fetched. Version-less aliases
|
|
25
|
+
* remain the fallback for when the tag cannot be resolved, which is the only
|
|
26
|
+
* reason they still exist here: they are what keeps a URL valid across releases,
|
|
27
|
+
* at the cost of not saying which release it is.
|
|
8
28
|
*
|
|
9
29
|
* Arch detection is trivial here in a way it is not on the website: `uname -m`
|
|
10
30
|
* is authoritative, so there is no need for the WebGL guessing the download page
|
|
@@ -32,6 +52,7 @@ export const DESKTOP_HELP = `
|
|
|
32
52
|
Install-desktop options:
|
|
33
53
|
--arch <arm64|x64> Override architecture detection
|
|
34
54
|
--dest <dir> Install directory. Defaults to /Applications
|
|
55
|
+
--force Reinstall even when the latest release is already installed
|
|
35
56
|
--keep-dmg Leave the downloaded disk image in place
|
|
36
57
|
--print-url Print the resolved download URL and exit
|
|
37
58
|
`;
|
|
@@ -57,6 +78,27 @@ export function installedAppPath(destination = "/Applications") {
|
|
|
57
78
|
return path.join(destination, APP_NAME);
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
/**
|
|
82
|
+
* `CFBundleShortVersionString` of the installed app, or `""`.
|
|
83
|
+
*
|
|
84
|
+
* Lives here rather than in link.js, which is where it started, because the
|
|
85
|
+
* installer needs it too -- to answer "is this already the release I am about to
|
|
86
|
+
* download" -- and link.js already imports this module, so the dependency only
|
|
87
|
+
* points one way from here.
|
|
88
|
+
*/
|
|
89
|
+
export function installedDesktopVersion(destination = "/Applications") {
|
|
90
|
+
try {
|
|
91
|
+
const plist = path.join(installedAppPath(destination), "Contents", "Info.plist");
|
|
92
|
+
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
93
|
+
readFileSync(plist, "utf8")
|
|
94
|
+
);
|
|
95
|
+
return match ? match[1].trim() : "";
|
|
96
|
+
} catch {
|
|
97
|
+
// Not installed, or a bundle we cannot read.
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
export function desktopAppInstalled(destination = "/Applications") {
|
|
61
103
|
return process.platform === "darwin" && existsSync(installedAppPath(destination));
|
|
62
104
|
}
|
|
@@ -174,10 +216,29 @@ export function printPlayground(url) {
|
|
|
174
216
|
}
|
|
175
217
|
}
|
|
176
218
|
|
|
177
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Where to fetch the disk image for `arch`.
|
|
221
|
+
*
|
|
222
|
+
* With a tag, the release's own versioned asset -- one exact build, which cannot
|
|
223
|
+
* become a different one between resolving it and finishing the download.
|
|
224
|
+
* Without one, the version-less alias, which always resolves to something but
|
|
225
|
+
* will not say what.
|
|
226
|
+
*/
|
|
227
|
+
export function dmgUrl(arch, tag = null) {
|
|
228
|
+
if (tag) return `${RELEASES_BASE}/download/${tag}/PreMan-${versionFromTag(tag)}-${arch}.dmg`;
|
|
178
229
|
return `${RELEASES_BASE}/latest/download/PreMan-mac-${arch}.dmg`;
|
|
179
230
|
}
|
|
180
231
|
|
|
232
|
+
/** Where to fetch the update manifest, pinned the same way. */
|
|
233
|
+
export function manifestUrl(tag = null) {
|
|
234
|
+
if (tag) return `${RELEASES_BASE}/download/${tag}/latest-mac.yml`;
|
|
235
|
+
return `${RELEASES_BASE}/latest/download/latest-mac.yml`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function versionFromTag(tag) {
|
|
239
|
+
return String(tag || "").replace(/^v/i, "");
|
|
240
|
+
}
|
|
241
|
+
|
|
181
242
|
export function detectArch() {
|
|
182
243
|
const machine = os.machine ? os.machine() : os.arch();
|
|
183
244
|
if (machine === "arm64" || machine === "aarch64") return "arm64";
|
|
@@ -201,16 +262,46 @@ async function fetchLatestRelease() {
|
|
|
201
262
|
}
|
|
202
263
|
}
|
|
203
264
|
|
|
265
|
+
/**
|
|
266
|
+
* The tag `/releases/latest` currently points at, read out of its redirect.
|
|
267
|
+
*
|
|
268
|
+
* A second way to ask, because the first one is rate limited: the GitHub API
|
|
269
|
+
* allows 60 unauthenticated calls an hour per address, which a shared office or
|
|
270
|
+
* a CI runner can exhaust without doing anything unusual. Downloads are not
|
|
271
|
+
* limited, and the alias redirects to the release it resolved -- so asking for
|
|
272
|
+
* the alias and declining to follow the redirect yields the same tag the
|
|
273
|
+
* download itself would have used, which is exactly the one worth pinning.
|
|
274
|
+
*/
|
|
275
|
+
export function tagFromRedirect(location) {
|
|
276
|
+
return String(location || "").match(/\/releases\/download\/([^/]+)\//)?.[1] ?? null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function resolveReleaseTag(arch) {
|
|
280
|
+
const release = await fetchLatestRelease();
|
|
281
|
+
if (release?.tag_name) return String(release.tag_name);
|
|
282
|
+
try {
|
|
283
|
+
const resp = await fetch(dmgUrl(arch), {
|
|
284
|
+
method: "HEAD",
|
|
285
|
+
redirect: "manual",
|
|
286
|
+
headers: { "User-Agent": "premanmcp-cli" },
|
|
287
|
+
});
|
|
288
|
+
return tagFromRedirect(resp.headers.get("location"));
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
204
294
|
/**
|
|
205
295
|
* The sha512 and size electron-builder published for this architecture's disk
|
|
206
296
|
* image, or null if the manifest could not be read.
|
|
207
297
|
*
|
|
208
298
|
* `latest-mac.yml` is the only integrity signal available without our own signing
|
|
209
299
|
* infrastructure. It keys entries by the *versioned* filename
|
|
210
|
-
* (`PreMan-0.3.79-arm64.dmg`),
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
300
|
+
* (`PreMan-0.3.79-arm64.dmg`), so the version is read out of the manifest to
|
|
301
|
+
* find the right entry. On a pinned install that version is the tag's, and the
|
|
302
|
+
* asset named in the manifest is the one that was downloaded; on a fallback
|
|
303
|
+
* install it describes the alias's twin, which the size check confirms before
|
|
304
|
+
* the digest is trusted.
|
|
214
305
|
*/
|
|
215
306
|
export function parseMacManifest(text, arch) {
|
|
216
307
|
const version = text.match(/^version:\s*(\S+)/m)?.[1];
|
|
@@ -231,9 +322,9 @@ export function parseMacManifest(text, arch) {
|
|
|
231
322
|
return null;
|
|
232
323
|
}
|
|
233
324
|
|
|
234
|
-
async function expectedDigest(arch) {
|
|
325
|
+
async function expectedDigest(arch, tag = null) {
|
|
235
326
|
try {
|
|
236
|
-
const resp = await fetch(
|
|
327
|
+
const resp = await fetch(manifestUrl(tag), {
|
|
237
328
|
headers: { "User-Agent": "premanmcp-cli" },
|
|
238
329
|
});
|
|
239
330
|
if (!resp.ok) return null;
|
|
@@ -367,18 +458,41 @@ export async function installDesktopCommand(commandArgs = [], { onInstalled } =
|
|
|
367
458
|
if (!["arm64", "x64"].includes(arch)) {
|
|
368
459
|
throw new Error(`unsupported --arch ${arch}; expected arm64 or x64`);
|
|
369
460
|
}
|
|
370
|
-
const url = dmgUrl(arch);
|
|
371
|
-
|
|
372
461
|
if (args.has("--print-url")) {
|
|
373
|
-
|
|
374
|
-
|
|
462
|
+
// The alias, and no network call: this is asked for by scripts and by people
|
|
463
|
+
// who want the URL that keeps working, not the one for today's build.
|
|
464
|
+
const alias = dmgUrl(arch);
|
|
465
|
+
process.stdout.write(`${alias}\n`);
|
|
466
|
+
return { state: "printed", url: alias, arch };
|
|
375
467
|
}
|
|
376
468
|
|
|
377
|
-
|
|
378
|
-
|
|
469
|
+
// Resolved once. Everything below is pinned to this tag, so the version
|
|
470
|
+
// reported, the image downloaded and the checksum verified against it all
|
|
471
|
+
// describe the same release even if another ships while this runs.
|
|
472
|
+
const tag = await resolveReleaseTag(arch);
|
|
473
|
+
const url = dmgUrl(arch, tag);
|
|
474
|
+
const version = tag ? versionFromTag(tag) : "latest";
|
|
379
475
|
const destination = args.value("--dest", "/Applications");
|
|
380
476
|
|
|
381
|
-
|
|
477
|
+
const installed = installedDesktopVersion(destination);
|
|
478
|
+
if (tag && installed && installed === version && !args.has("--force")) {
|
|
479
|
+
// Re-fetching 100 MB to arrive at the bytes already on disk is the most
|
|
480
|
+
// literal form of the "it downloads the update again" complaint, and it is
|
|
481
|
+
// what this command did every time it was run.
|
|
482
|
+
process.stdout.write(
|
|
483
|
+
`PreMan ${installed} is already installed and is the latest release.\n` +
|
|
484
|
+
`Nothing to do. Use --force to reinstall.\n`
|
|
485
|
+
);
|
|
486
|
+
return { state: "current", version: installed, arch, path: installedAppPath(destination) };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (installed === version) {
|
|
490
|
+
process.stdout.write(`Reinstalling PreMan ${version} (${arch})…\n`);
|
|
491
|
+
} else if (installed) {
|
|
492
|
+
process.stdout.write(`Replacing PreMan ${installed} with ${version} (${arch})…\n`);
|
|
493
|
+
} else {
|
|
494
|
+
process.stdout.write(`Downloading PreMan ${version} (${arch})…\n`);
|
|
495
|
+
}
|
|
382
496
|
const workDir = mkdtempSync(path.join(os.tmpdir(), "preman-desktop-"));
|
|
383
497
|
const dmgPath = path.join(workDir, `PreMan-mac-${arch}.dmg`);
|
|
384
498
|
let mounted = null;
|
|
@@ -386,8 +500,21 @@ export async function installDesktopCommand(commandArgs = [], { onInstalled } =
|
|
|
386
500
|
try {
|
|
387
501
|
const progress = progressLine();
|
|
388
502
|
let bytes;
|
|
503
|
+
let pinned = tag;
|
|
389
504
|
try {
|
|
390
|
-
|
|
505
|
+
try {
|
|
506
|
+
bytes = await download(url, dmgPath, progress.tick);
|
|
507
|
+
} catch (err) {
|
|
508
|
+
// Pinning names an asset instead of an alias, so it is the one thing
|
|
509
|
+
// here that can be wrong about a release rather than merely unlucky --
|
|
510
|
+
// an older build that predates the versioned artifact, or a rename.
|
|
511
|
+
// Falling back to the alias costs the guarantees above and is still an
|
|
512
|
+
// install; failing outright would make this worse than what it replaced.
|
|
513
|
+
if (!pinned || !/download failed: 404/.test(String(err?.message))) throw err;
|
|
514
|
+
process.stdout.write(` ${tag} has no versioned image; using the latest alias\n`);
|
|
515
|
+
pinned = null;
|
|
516
|
+
bytes = await download(dmgUrl(arch), dmgPath, progress.tick);
|
|
517
|
+
}
|
|
391
518
|
} finally {
|
|
392
519
|
// Cleared even when the download throws, or the error prints onto the
|
|
393
520
|
// half-drawn progress line.
|
|
@@ -395,7 +522,9 @@ export async function installDesktopCommand(commandArgs = [], { onInstalled } =
|
|
|
395
522
|
}
|
|
396
523
|
process.stdout.write(` ${(bytes / BYTES_PER_MB).toFixed(1)} MB\n`);
|
|
397
524
|
|
|
398
|
-
|
|
525
|
+
// Read from the same place the image came from, pinned or not, so the two
|
|
526
|
+
// can never describe different releases.
|
|
527
|
+
const expected = await expectedDigest(arch, pinned);
|
|
399
528
|
if (expected) {
|
|
400
529
|
if (expected.size && expected.size !== bytes) {
|
|
401
530
|
throw new Error(
|
|
@@ -425,8 +554,12 @@ export async function installDesktopCommand(commandArgs = [], { onInstalled } =
|
|
|
425
554
|
run("cp", ["-R", source, target]);
|
|
426
555
|
chmodSync(target, 0o755);
|
|
427
556
|
|
|
428
|
-
|
|
429
|
-
|
|
557
|
+
// The manifest's version describes bytes whose size and digest were just
|
|
558
|
+
// checked against it, which makes it a better answer than the tag resolved
|
|
559
|
+
// before any of this ran -- and the only honest one on the alias fallback.
|
|
560
|
+
const installedVersion = expected?.version || installedDesktopVersion(destination) || version;
|
|
561
|
+
const result = { state: "installed", version: installedVersion, arch, path: target };
|
|
562
|
+
process.stdout.write(`\nInstalled PreMan ${installedVersion} to ${target}\n\n`);
|
|
430
563
|
onInstalled?.(result);
|
|
431
564
|
return result;
|
|
432
565
|
} finally {
|
package/bin/integrations.js
CHANGED
|
@@ -265,9 +265,15 @@ export async function githubCommand(args) {
|
|
|
265
265
|
|
|
266
266
|
const seen = new Set((await listRepos()).map((r) => r.id));
|
|
267
267
|
|
|
268
|
+
// `return_to: "desktop"` because the person who ran this is in a terminal and
|
|
269
|
+
// the CLI is already polling for the installation. The web return 302s the
|
|
270
|
+
// tab into the hosted dashboard � an app they did not ask for, on whichever
|
|
271
|
+
// environment owns the App's setup URL. The desktop return finishes on a page
|
|
272
|
+
// that offers PreMan and says the tab may be closed, which is all the browser
|
|
273
|
+
// still has to do here.
|
|
268
274
|
const started = await callBackendJson(args, "POST", "/integrations/github/app/install", {
|
|
269
275
|
token,
|
|
270
|
-
json: {},
|
|
276
|
+
json: { return_to: "desktop" },
|
|
271
277
|
});
|
|
272
278
|
assertOk(started, "start GitHub install");
|
|
273
279
|
|
|
@@ -522,10 +528,31 @@ async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
|
|
|
522
528
|
if (assumeYes) return fallback;
|
|
523
529
|
const shown = defaultYes ? "Y/n" : "y/N";
|
|
524
530
|
const hint = canGoBack ? `[${shown}/b]` : `[${shown}]`;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
531
|
+
// Anything that was not yes, no or a usable back used to fall through to no.
|
|
532
|
+
// On the repository question that is the most expensive misreading in the
|
|
533
|
+
// walk: "b" typed at the one step with nothing behind it, or a plain "sure",
|
|
534
|
+
// disconnected the repo and explained itself in a line that had already
|
|
535
|
+
// scrolled past by the time the next question drew.
|
|
536
|
+
//
|
|
537
|
+
// Bounded, and empty still takes the default, so a closed stdin ends the
|
|
538
|
+
// question rather than starting a spin.
|
|
539
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
540
|
+
const answer = (await promptText(`${question} ${hint}: `)).trim().toLowerCase();
|
|
541
|
+
if (answer === "") return fallback;
|
|
542
|
+
if (answer === "y" || answer === "yes") return "yes";
|
|
543
|
+
if (answer === "n" || answer === "no") return "no";
|
|
544
|
+
if (answer === "b" || answer === "back") {
|
|
545
|
+
if (canGoBack) return "back";
|
|
546
|
+
// Offered nowhere, so this is somebody guessing. Say why it did nothing
|
|
547
|
+
// rather than silently spending their answer on the opposite of it.
|
|
548
|
+
process.stdout.write(" Nothing to go back to yet.\n");
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
process.stdout.write(
|
|
552
|
+
` Please answer y or n${canGoBack ? ", or b to go back" : ""}.\n`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
return fallback;
|
|
529
556
|
}
|
|
530
557
|
|
|
531
558
|
/**
|
package/bin/link.js
CHANGED
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
* a `git push` is exactly the behaviour that gets a tool uninstalled.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
|
|
20
|
-
import { desktopAppInstalled, installedAppPath } from "./desktop.js";
|
|
17
|
+
import { desktopAppInstalled, installedDesktopVersion } from "./desktop.js";
|
|
21
18
|
import { DEFAULT_FRONTEND, frontendUrl } from "./shared.js";
|
|
22
19
|
|
|
20
|
+
export { installedDesktopVersion };
|
|
21
|
+
|
|
23
22
|
const DIM = "\u001b[2m";
|
|
24
23
|
const RESET = "\u001b[0m";
|
|
25
24
|
|
|
@@ -82,20 +81,6 @@ export function compareVersions(a, b) {
|
|
|
82
81
|
return 0;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
/** CFBundleShortVersionString of the installed app, or "". */
|
|
86
|
-
export function installedDesktopVersion(destination = "/Applications") {
|
|
87
|
-
try {
|
|
88
|
-
const plist = path.join(installedAppPath(destination), "Contents", "Info.plist");
|
|
89
|
-
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
90
|
-
readFileSync(plist, "utf8")
|
|
91
|
-
);
|
|
92
|
-
return match ? match[1].trim() : "";
|
|
93
|
-
} catch {
|
|
94
|
-
// Not installed, or a bundle we cannot read. Either way: not routable.
|
|
95
|
-
return "";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
84
|
export function desktopSupportsRouting(destination = "/Applications") {
|
|
100
85
|
const version = installedDesktopVersion(destination);
|
|
101
86
|
return Boolean(version) && compareVersions(version, MIN_ROUTE_VERSION) >= 0;
|
package/bin/shared.js
CHANGED
|
@@ -294,9 +294,28 @@ export function openUrl(url) {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
export async function promptText(question) {
|
|
297
|
+
// A stream that has already ended will never deliver a line, and readline does
|
|
298
|
+
// not emit 'close' for an 'end' that happened before the interface existed --
|
|
299
|
+
// so the second prompt after stdin ran out resolved nothing, kept nothing
|
|
300
|
+
// alive, and the process simply exited mid-walk with a zero status.
|
|
301
|
+
if (process.stdin.readableEnded || process.stdin.destroyed) {
|
|
302
|
+
// Still shown, and still terminated, so the transcript reads the way it
|
|
303
|
+
// would have if somebody had pressed Enter.
|
|
304
|
+
process.stdout.write(`${question}\n`);
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
297
307
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
298
308
|
try {
|
|
299
|
-
|
|
309
|
+
// An exhausted or closed stdin never answers, and rl.question waits for an
|
|
310
|
+
// answer that cannot come. That is how a run given fewer answers than it had
|
|
311
|
+
// questions -- `onboard < /dev/null`, a script whose here-doc ran out, a
|
|
312
|
+
// pipe whose writer exited -- stopped dead on a prompt instead of finishing
|
|
313
|
+
// on its defaults. Empty is what every caller already treats as "no answer".
|
|
314
|
+
const answer = await Promise.race([
|
|
315
|
+
rl.question(question),
|
|
316
|
+
new Promise((resolve) => rl.once("close", () => resolve(""))),
|
|
317
|
+
]);
|
|
318
|
+
return answer.trim();
|
|
300
319
|
} finally {
|
|
301
320
|
rl.close();
|
|
302
321
|
}
|
package/bin/tests.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin HTTP wrappers over the same `/workbench/generate-tests` routes the
|
|
5
5
|
* dashboard uses. Distinct from `preman test <id>`, which generates + runs
|
|
6
|
-
* scenarios for one endpoint via `POST /
|
|
6
|
+
* scenarios for one endpoint via `POST /cli/tests/generate`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { callBackendJson, cliInvocation, makeArgs, resolveApiKey } from "./shared.js";
|
package/bin/verify.js
CHANGED
|
@@ -243,12 +243,9 @@ async function safeCall(args, method, routePath, options) {
|
|
|
243
243
|
async function fetchInventory(args, token) {
|
|
244
244
|
const endpoints = [];
|
|
245
245
|
for (let offset = 0; offset < INVENTORY_MAX_ENDPOINTS; offset += INVENTORY_PAGE_SIZE) {
|
|
246
|
-
const result = await safeCall(args, "
|
|
246
|
+
const result = await safeCall(args, "GET", "/cli/endpoints", {
|
|
247
247
|
token,
|
|
248
|
-
|
|
249
|
-
tool: "get_endpoints",
|
|
250
|
-
arguments: { format: "json", limit: INVENTORY_PAGE_SIZE, offset },
|
|
251
|
-
},
|
|
248
|
+
query: { limit: INVENTORY_PAGE_SIZE, offset },
|
|
252
249
|
});
|
|
253
250
|
if (!result.ok) {
|
|
254
251
|
return {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Transparent stdio -> hosted Streamable HTTP MCP bridge.
|
|
2
|
+
*
|
|
3
|
+
* This process deliberately defines no tools or schemas. Initialization,
|
|
4
|
+
* discovery, notifications, and calls are forwarded byte-for-byte at the JSON-
|
|
5
|
+
* RPC layer so the Python service remains the sole public contract authority.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|