premanmcp 0.15.0 → 0.15.2

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 CHANGED
@@ -114,9 +114,14 @@ push testing alone, and `--no-guide` connects and nothing else. With `--guide`,
114
114
  takes every step's default without asking and `--no-runner` / `--no-desktop` /
115
115
  `--no-integrations` skip one each.
116
116
 
117
- `--yes` deliberately does *not* install the desktop app: that step's default is no, because
118
- it downloads a hundred-odd megabytes and writes to `/Applications`. Run `install-desktop`
119
- when you want it.
117
+ The desktop app is installed rather than offered. It was a question for a while, defaulted
118
+ to no on the grounds that nobody should get a 150MB download by pressing Enter, and what
119
+ that produced was a setup whose last act was to describe the app and decline to install
120
+ it. The build is Developer ID signed and notarized, it is checked against the sha512
121
+ published with the release before anything is copied into `/Applications`, and nothing
122
+ arrives through a browser so there is no quarantine flag and no Gatekeeper warning on
123
+ first launch. `--no-desktop`, or `PREMAN_NO_DESKTOP=1` where no flag can be passed, skips
124
+ it; `install-desktop` does it on its own.
120
125
 
121
126
  In CI or any non-interactive shell, run `connect --agent <name> --api-key pm_live_…`.
122
127
  Without `--agent` there is nothing to prompt on, so `connect` prints ready-to-paste
@@ -74,11 +74,11 @@ export function whatIsLeftBlock(args) {
74
74
  /**
75
75
  * Ask, or take the step's own default when nobody can answer.
76
76
  *
77
- * `--yes` returns the default rather than a blanket yes, which matters for
78
- * exactly one step: the desktop app defaults to no because it downloads a
79
- * hundred-odd megabytes and writes to /Applications. A blanket yes made
80
- * `preman connect --yes` do that unattended, which is not what anyone means by
81
- * "do not ask me questions" -- they get `preman install-desktop` for that.
77
+ * `--yes` returns each step's own default rather than a blanket yes, so a step
78
+ * that should not ride in on a keypress can say so. The desktop app used to be
79
+ * the only one that did, on the grounds that `--yes` should not mean 150MB into
80
+ * /Applications unattended; it does not ask at all now, and `PREMAN_NO_DESKTOP`
81
+ * or `--no-desktop` is how a run that does not want it says so.
82
82
  */
83
83
  export async function confirm(question, { assumeYes = false, defaultYes = true } = {}) {
84
84
  if (assumeYes) return defaultYes;
@@ -149,7 +149,13 @@ export async function discoverEndpoints(
149
149
  args,
150
150
  agent,
151
151
  serverName,
152
- { blockedHere = "", force = false, basePath = ".", frameworkHint = undefined } = {}
152
+ {
153
+ blockedHere = "",
154
+ force = false,
155
+ basePath = ".",
156
+ frameworkHint = undefined,
157
+ handOffBrief = true,
158
+ } = {}
153
159
  ) {
154
160
  try {
155
161
  const before = await endpointCounts(args);
@@ -183,6 +189,20 @@ export async function discoverEndpoints(
183
189
  // Say why before printing homework: "here is a brief" reads as PreMan not
184
190
  // working, when the actual answer is one login away.
185
191
  if (blocker) process.stdout.write(`${MARK.skip()} ${blocker}\n`);
192
+ // The brief is written for an agent to execute, not for a person to read.
193
+ // Twenty lines of it in the middle of a setup walk reads as PreMan having
194
+ // failed, and the walk still has questions to ask after this. `endpoints
195
+ // discover` prints the same brief whenever it is actually wanted, so the
196
+ // pointer loses nothing but the wall of text.
197
+ if (!handOffBrief) {
198
+ if (!blocker) {
199
+ process.stdout.write(`${MARK.skip()} no coding agent here to read this repo\n`);
200
+ }
201
+ process.stdout.write(
202
+ ` Map it from inside one: ${cliInvocation()} endpoints discover\n`
203
+ );
204
+ return before;
205
+ }
186
206
  for (const line of brief.instructions || []) process.stdout.write(`${line}\n`);
187
207
  const who = agent ? agent.label : "your coding agent";
188
208
  process.stdout.write(`\nHand this brief to ${who}, then run:\n${manualSteps()}`);
@@ -367,38 +387,38 @@ async function setUpRunner(args, agent, { assumeYes }) {
367
387
  }
368
388
  }
369
389
 
370
- /**
371
- * The question, word for word, wherever the desktop app is offered.
372
- *
373
- * Shared because both walks ask it and they must not drift: "install the
374
- * desktop app?" got yes from people who thought they were agreeing to a menu
375
- * bar icon, then spent two minutes watching an unexplained download. The size
376
- * and the destination are the two facts that change the answer, and the last
377
- * clause is there because the honest answer to "do I need this?" is no.
378
- */
379
- export function desktopQuestion(args) {
380
- const dest = args.value("--dest", "/Applications");
381
- return `Download the PreMan desktop app? ~150MB, installs to ${dest} — optional, everything works without it`;
382
- }
383
-
384
390
  /** Where this run would put the app, honouring the same --dest the install does. */
385
391
  function desktopAppPath(args) {
386
392
  return path.join(args.value("--dest", "/Applications"), "PreMan.app");
387
393
  }
388
394
 
389
395
  /**
390
- * Why the desktop app should not be asked about here, or null to go ahead.
396
+ * An opt-out for runs that cannot pass a flag.
391
397
  *
392
- * Split out from the offer so a caller that runs its own prompt loop can decide
393
- * whether the question is worth asking *before* asking it. Both of these were
394
- * bugs when the check lived after the prompt: a Linux user got a macOS-only
395
- * question, and someone who already had the app got asked to download it again.
398
+ * A bare `preman` with no arguments is a real entry point and now installs the
399
+ * app, which is right for a customer and wrong for a CI image or a suite that
400
+ * drives the walk end to end. `--no-desktop` cannot reach either of those, so
401
+ * they get the environment variable instead. Same spelling as PREMAN_NO_BROWSER
402
+ * and PREMAN_NO_AGENT, including that 0/false/no mean the opt-out is off.
403
+ */
404
+ function noDesktopEnv() {
405
+ const optOut = (process.env.PREMAN_NO_DESKTOP || "").trim().toLowerCase();
406
+ return Boolean(optOut) && !["0", "false", "no"].includes(optOut);
407
+ }
408
+
409
+ /**
410
+ * Why the desktop app should not be installed here, or null to go ahead.
411
+ *
412
+ * Split out from the step so a caller running its own loop can decide whether
413
+ * the step is worth listing *before* listing it. Both of these were bugs when
414
+ * the check lived after: a Linux user got a macOS-only step, and someone who
415
+ * already had the app watched it download again.
396
416
  */
397
417
  export function desktopPrecheck(args) {
398
- if (args.has("--no-desktop")) return { state: "skipped" };
418
+ if (args.has("--no-desktop") || noDesktopEnv()) return { state: "skipped" };
399
419
  // An explicit --desktop is honoured whatever the platform: someone who asked
400
420
  // for the app by name is owed the macOS-only notice and the download link
401
- // rather than silence. Only the unasked offer is withheld where there is no
421
+ // rather than silence. Only the unasked install is withheld where there is no
402
422
  // build, because a step that announces itself to say "not for you" reads as
403
423
  // something being broken.
404
424
  if (process.platform !== "darwin" && !args.has("--desktop")) return { state: "unsupported" };
@@ -411,7 +431,7 @@ export function desktopPrecheck(args) {
411
431
  *
412
432
  * `install` is injectable because onboarding does more than this one does — it
413
433
  * hands the fresh session to the app so it opens signed in — and the part worth
414
- * sharing is the guards, the default and the wording, not the install itself.
434
+ * sharing is the guards and the wording, not the install itself.
415
435
  */
416
436
  export async function runDesktopInstall(args, { install = null } = {}) {
417
437
  try {
@@ -422,14 +442,22 @@ export async function runDesktopInstall(args, { install = null } = {}) {
422
442
  }
423
443
  }
424
444
 
425
- export function desktopSkipped() {
426
- process.stdout.write(`${MARK.skip()} Skipped. Install later: ${cliInvocation()} install-desktop\n`);
427
- }
428
-
429
- /** Offer the desktop app. Optional by design the terminal flow is complete without it. */
430
- async function offerDesktop(args, { assumeYes } = {}) {
445
+ /**
446
+ * Install the desktop app. Not a question.
447
+ *
448
+ * It used to be one, defaulted to no, on the grounds that the download is large
449
+ * and nobody should get it by pressing Enter. What that produced was a setup
450
+ * whose last act was to describe a thing and decline to do it — the customer
451
+ * still had to go install the app, only now by hand and later. The download is
452
+ * the whole reason a terminal is better placed to do this than a download page:
453
+ * the build is Developer ID signed and notarized, it lands in /Applications
454
+ * verified against the checksum published with the release, and because nothing
455
+ * here came through a browser there is no quarantine flag and so no Gatekeeper
456
+ * warning on first launch. Opting out is `--no-desktop`.
457
+ */
458
+ async function offerDesktop(args) {
431
459
  const already = desktopPrecheck(args);
432
- // Silent on Linux and Windows: there is no build to offer, and a step that
460
+ // Silent on Linux and Windows: there is no build to install, and a step that
433
461
  // announces itself only to say "not for you" reads as something being wrong.
434
462
  if (already && already.state !== "installed") return already;
435
463
  step("Desktop app");
@@ -437,15 +465,6 @@ async function offerDesktop(args, { assumeYes } = {}) {
437
465
  process.stdout.write(`${MARK.ok()} PreMan desktop app already installed.\n`);
438
466
  return already;
439
467
  }
440
- // Default no, unlike every other step here: this one downloads a hundred-odd
441
- // megabytes and writes to /Applications, which nobody should get by pressing
442
- // Enter to move past a prompt. `--desktop` is how an unattended run opts in,
443
- // since `--yes` deliberately no longer does.
444
- const wanted = args.has("--desktop");
445
- if (!wanted && !(await confirm(desktopQuestion(args), { assumeYes, defaultYes: false }))) {
446
- desktopSkipped();
447
- return { state: "skipped" };
448
- }
449
468
  return runDesktopInstall(args);
450
469
  }
451
470
 
@@ -585,7 +604,7 @@ export async function guidedFirstRun(args, agent, apiKey, serverName, { blockedH
585
604
  step("Runner");
586
605
  await setUpRunner(args, agent, { assumeYes });
587
606
 
588
- await offerDesktop(args, { assumeYes });
607
+ await offerDesktop(args);
589
608
 
590
609
  step("Integrations");
591
610
  await connectIntegrations(args, apiKey, { assumeYes });
package/bin/connect.js CHANGED
@@ -144,11 +144,11 @@ Connect options:
144
144
  and the integration prompts after connecting
145
145
  --no-guide Connect only: no push hook, no closing summary
146
146
  --no-runner With --guide, do not pair this machine as a runner
147
- --no-desktop With --guide, do not offer the desktop app
147
+ --no-desktop With --guide, skip the desktop app, which otherwise
148
+ installs. PREMAN_NO_DESKTOP=1 does the same
148
149
  --no-integrations With --guide, do not offer GitHub / AWS / Slack
149
150
  --no-hook Do not install the git pre-push hook
150
151
  --yes Take every step's default without prompting
151
- (the desktop app defaults to no; install-desktop)
152
152
  --print Print the config instead of writing it
153
153
  `;
154
154
 
package/bin/desktop.js CHANGED
@@ -243,13 +243,62 @@ async function expectedDigest(arch) {
243
243
  }
244
244
  }
245
245
 
246
- async function download(url, destination) {
246
+ const BYTES_PER_MB = 1024 * 1024;
247
+
248
+ /**
249
+ * A single line that rewrites itself, or nothing at all when not on a terminal.
250
+ *
251
+ * Onboarding no longer asks before installing the app, and a step nobody agreed
252
+ * to cannot also go quiet for ninety seconds: a still cursor during a 150 MB
253
+ * fetch is indistinguishable from a hang, and the reflex is Ctrl-C halfway
254
+ * through. Redirected output gets the summary line that already existed rather
255
+ * than a few hundred carriage returns, which is why this is TTY-only.
256
+ */
257
+ function progressLine() {
258
+ if (!process.stdout.isTTY) return { tick: () => {}, clear: () => {} };
259
+ let lastDrawn = 0;
260
+ let width = 0;
261
+ const tick = (received, total) => {
262
+ const now = Date.now();
263
+ // One redraw per chunk is thousands of writes for an effect the eye cannot
264
+ // follow. The final chunk always draws, so the line never stops short.
265
+ if (now - lastDrawn < 150 && received !== total) return;
266
+ lastDrawn = now;
267
+ const got = (received / BYTES_PER_MB).toFixed(1);
268
+ const text = total
269
+ ? ` ${got} of ${(total / BYTES_PER_MB).toFixed(1)} MB ${Math.floor((received / total) * 100)}%`
270
+ : ` ${got} MB`;
271
+ // Padded to the widest line drawn so far, or a shorter one leaves the tail
272
+ // of its predecessor on screen.
273
+ width = Math.max(width, text.length);
274
+ process.stdout.write(`\r${text.padEnd(width)}`);
275
+ };
276
+ const clear = () => {
277
+ if (width) process.stdout.write(`\r${" ".repeat(width)}\r`);
278
+ };
279
+ return { tick, clear };
280
+ }
281
+
282
+ async function download(url, destination, onProgress) {
247
283
  const controller = new AbortController();
248
284
  const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
249
285
  try {
250
286
  const resp = await fetch(url, { signal: controller.signal, redirect: "follow" });
251
287
  if (!resp.ok) throw new Error(`download failed: ${resp.status} ${url}`);
252
- const buffer = Buffer.from(await resp.arrayBuffer());
288
+ // Read the body in chunks rather than as one arrayBuffer: the buffering is
289
+ // the same either way, but only this version can say how far along it is.
290
+ const total = Number(resp.headers.get("content-length")) || 0;
291
+ const reader = resp.body.getReader();
292
+ const chunks = [];
293
+ let received = 0;
294
+ for (;;) {
295
+ const { done, value } = await reader.read();
296
+ if (done) break;
297
+ chunks.push(value);
298
+ received += value.length;
299
+ onProgress?.(received, total);
300
+ }
301
+ const buffer = Buffer.concat(chunks);
253
302
  writeFileSync(destination, buffer);
254
303
  return buffer.length;
255
304
  } finally {
@@ -335,8 +384,16 @@ export async function installDesktopCommand(commandArgs = [], { onInstalled } =
335
384
  let mounted = null;
336
385
 
337
386
  try {
338
- const bytes = await download(url, dmgPath);
339
- process.stdout.write(` ${(bytes / 1024 / 1024).toFixed(1)} MB\n`);
387
+ const progress = progressLine();
388
+ let bytes;
389
+ try {
390
+ bytes = await download(url, dmgPath, progress.tick);
391
+ } finally {
392
+ // Cleared even when the download throws, or the error prints onto the
393
+ // half-drawn progress line.
394
+ progress.clear();
395
+ }
396
+ process.stdout.write(` ${(bytes / BYTES_PER_MB).toFixed(1)} MB\n`);
340
397
 
341
398
  const expected = await expectedDigest(arch);
342
399
  if (expected) {
@@ -16,7 +16,9 @@
16
16
 
17
17
  import { describeCheckout, explainNoCheckout, resolveCheckout } from "./repo.js";
18
18
  import {
19
+ DEFAULT_BACKEND,
19
20
  assertOk,
21
+ backendUrl,
20
22
  callBackendJson,
21
23
  cliInvocation,
22
24
  frontendUrl,
@@ -86,10 +88,14 @@ function showOpeningDesktop() {
86
88
  };
87
89
  }
88
90
 
91
+ const INSTALLATIONS_URL = "https://github.com/settings/installations";
92
+
89
93
  function present(url, what) {
90
94
  process.stdout.write(`\n ${url}\n\n`);
91
- if (openUrl(url)) process.stdout.write(`Opened ${what} in your browser.\n`);
92
- else process.stdout.write(`Open the link above to ${what}.\n`);
95
+ // A tab appearing is its own confirmation; saying so as well spends a line
96
+ // on what the customer is already looking at. It earns its place only when
97
+ // no tab opened and the link is the way through.
98
+ if (!openUrl(url)) process.stdout.write(`Open the link above to ${what}.\n`);
93
99
  }
94
100
 
95
101
  /**
@@ -293,9 +299,7 @@ export async function githubCommand(args) {
293
299
  {
294
300
  timeoutMs: Number(process.env.PREMAN_GITHUB_POLL_MS) || GITHUB_POLL_TIMEOUT_MS,
295
301
  hintAfterMs: 20000,
296
- hint:
297
- "Still nothing from GitHub. Picking at least one repository and confirming\n" +
298
- "is what sends you back here.",
302
+ hint: "Still nothing from GitHub — confirm a repository to come back here.",
299
303
  }
300
304
  );
301
305
 
@@ -319,15 +323,12 @@ function githubHandOff(args, refresh) {
319
323
  if (installed) {
320
324
  return (
321
325
  `The App is installed, but no repositories are shared with it.\n` +
322
- ` - Add some: https://github.com/settings/installations\n` +
323
- ` - Then re-run '${cliInvocation()} github'.\n`
326
+ ` Add some at ${INSTALLATIONS_URL}, then re-run '${cliInvocation()} github'.\n`
324
327
  );
325
328
  }
326
329
  return (
327
- `Nothing from GitHub yet no need to wait here.\n` +
328
- ` - Finish the install in the browser; it records itself when you confirm.\n` +
329
- ` - Check it: ${frontendUrl(args)} or https://github.com/settings/installations\n` +
330
- ` - Then re-run '${cliInvocation()} github'.\n`
330
+ `Nothing from GitHub yet. Finish the install in the browser — it records itself.\n` +
331
+ ` Then re-run '${cliInvocation()} github'.\n`
331
332
  );
332
333
  }
333
334
 
@@ -369,11 +370,9 @@ export async function slackCommand(args) {
369
370
  * "yes" | "no" | "back" -- back only offered once there is somewhere to go.
370
371
  *
371
372
  * `--yes` takes each step's own default rather than answering yes to all of
372
- * them. One step needs that today and it is the expensive one: the desktop app
373
- * defaults to no because it downloads a hundred-odd megabytes and writes to
374
- * /Applications, and `onboard --yes` used to do exactly that, unattended, to
375
- * someone who only meant "stop asking me questions". They have
376
- * `preman install-desktop` when they want it.
373
+ * them, so a step that should not ride in on a keypress can say so. Every step
374
+ * that asks defaults to yes today; the desktop app, which used to be the
375
+ * exception, no longer asks at all.
377
376
  */
378
377
  async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
379
378
  const fallback = defaultYes ? "yes" : "no";
@@ -400,7 +399,8 @@ async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
400
399
  * halfway had a desktop app pointed at an empty account. Endpoints come first
401
400
  * now because they are the only step that shows what PreMan is for, and every
402
401
  * ask after it can name what it just found. The app goes last because it is the
403
- * one thing here nobody needs.
402
+ * only step that asks nothing: by then every decision is made, and a download is
403
+ * something to leave running, not something to make someone watch first.
404
404
  *
405
405
  * The steps themselves live in connect/guide.js, which is also what
406
406
  * `preman connect --guide` runs. Two walks that asked the same questions in
@@ -416,12 +416,24 @@ export async function onboardCommand(
416
416
  // Imported at call time, not at the top: guide.js imports this module for MARK
417
417
  // and the three integration commands, so a static edge back would close the
418
418
  // cycle. By the time anyone runs onboard, this module is fully evaluated.
419
- const { desktopPrecheck, desktopQuestion, desktopSkipped, discoverEndpoints, runDesktopInstall } =
420
- await import("./connect/guide.js");
419
+ const { desktopPrecheck, discoverEndpoints, runDesktopInstall } = await import(
420
+ "./connect/guide.js"
421
+ );
421
422
  const { resolveAgentToDrive } = await import("./connect/agents.js");
422
423
 
423
424
  process.stdout.write("PreMan setup\n\n");
424
425
 
426
+ // Which PreMan this is talking to is invisible until the very last line,
427
+ // and a stored login from a local run silently redirects the whole walk:
428
+ // the account is created in a database nobody else can see, and the GitHub
429
+ // App redirects to the real callback, so the install never arrives and the
430
+ // wait looks like GitHub being slow. Production says nothing, because there
431
+ // is nothing to warn about; anything else is worth one line up front.
432
+ const target = backendUrl(args);
433
+ if (target !== DEFAULT_BACKEND) {
434
+ process.stdout.write(`${MARK.skip()} Using ${target}, not ${DEFAULT_BACKEND}.\n\n`);
435
+ }
436
+
425
437
  const creds = await authenticateTerminal(args);
426
438
  connected(`Signed in as ${creds.user_email || "your account"}.`);
427
439
 
@@ -449,7 +461,9 @@ export async function onboardCommand(
449
461
  agent: args.value("--agent", ""),
450
462
  interactive: false,
451
463
  });
452
- const counts = await discoverEndpoints(args, agent, args.value("--name", "preman"));
464
+ const counts = await discoverEndpoints(args, agent, args.value("--name", "preman"), {
465
+ handOffBrief: false,
466
+ });
453
467
  return { state: counts.registered ? "done" : "skipped", counts };
454
468
  },
455
469
  },
@@ -483,9 +497,7 @@ export async function onboardCommand(
483
497
  return;
484
498
  }
485
499
  process.stdout.write(
486
- `${checkout.slug} is still not shared with PreMan.\n` +
487
- ` Add it at https://github.com/settings/installations, then re-run ` +
488
- `'${cliInvocation()} github'.\n`
500
+ `${checkout.slug} is still not shared. Add it at ${INSTALLATIONS_URL}.\n`
489
501
  );
490
502
  },
491
503
  });
@@ -549,10 +561,10 @@ export async function onboardCommand(
549
561
  },
550
562
  });
551
563
 
552
- // Last, defaulted to no, and only where there is something to install. The
553
- // guards run out here rather than inside the step so a Linux user is not
554
- // asked a macOS question and someone who already has the app is not asked to
555
- // download it again — both of which happened when the check came after.
564
+ // Last, and only where there is something to install. The guards run out here
565
+ // rather than inside the step so a Linux user does not get a macOS-only step
566
+ // and someone who already has the app does not watch it download again — both
567
+ // of which happened when the check came after.
556
568
  const desktop = desktopPrecheck(args);
557
569
  if (desktop?.state === "installed") {
558
570
  steps.push({
@@ -564,11 +576,8 @@ export async function onboardCommand(
564
576
  } else if (!desktop) {
565
577
  steps.push({
566
578
  name: "Desktop app",
567
- question: desktopQuestion(args),
568
- // `--desktop` is how an unattended run opts in, since `--yes` gives every
569
- // step its own default and this step's default is no.
570
- defaultYes: args.has("--desktop"),
571
- onSkip: desktopSkipped,
579
+ // No question. See offerDesktop in connect/guide.js for why the ask went
580
+ // away; `--no-desktop` is the way past it.
572
581
  run: () =>
573
582
  runDesktopInstall(args, {
574
583
  // Onboarding installs *and* hands the fresh session over, so the app
@@ -685,16 +694,17 @@ export const INTEGRATIONS_HELP = `
685
694
  Setup options:
686
695
  preman onboard Sign in, map this repo's endpoints, connect it for
687
696
  pull requests, and check the endpoints you touch on
688
- every git push. Offers the desktop app at the end
697
+ every git push. Installs the desktop app at the end
689
698
  preman aws Connect an AWS account and stream a log group
690
699
  preman github Install the PreMan GitHub App
691
700
  preman slack Add PreMan to a Slack workspace
692
701
 
693
- --yes Take each step's default without prompting. The
694
- desktop app defaults to no, so --yes never
695
- downloads it (onboard)
696
- --desktop Install the desktop app without asking (onboard)
697
- --no-desktop Do not offer the desktop app at all (onboard)
702
+ --yes Take each step's default without prompting (onboard)
703
+ --desktop Install the desktop app even where there is no macOS
704
+ build, to print the download link (onboard)
705
+ --no-desktop Skip the desktop app, which otherwise installs
706
+ (onboard)
707
+ PREMAN_NO_DESKTOP=1 The same, for runs that cannot pass a flag
698
708
  b at any onboard prompt Go back to the previous step
699
709
  --account <id> AWS account id, skips the prompt
700
710
  --region <region> AWS region for log groups. Defaults to us-east-1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {