premanmcp 0.16.0 → 0.16.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.
Files changed (2) hide show
  1. package/bin/integrations.js +110 -38
  2. package/package.json +1 -1
@@ -273,8 +273,12 @@ export async function githubCommand(args) {
273
273
 
274
274
  const url = started.install_url || started.url;
275
275
  if (!url) throw new Error("Backend returned no GitHub install URL.");
276
- present(url, "install the PreMan GitHub App");
277
- process.stdout.write("Pick the repositories PreMan may read.\n");
276
+ // Installing the App is our plumbing and choosing repositories is the
277
+ // decision, so the decision leads. Naming the save matters more than either:
278
+ // GitHub's picker reads as a confirmation screen, so people recognise their
279
+ // repository on it and close the tab without ever granting anything.
280
+ present(url, "choose the repositories PreMan may read");
281
+ process.stdout.write("Save the selection on GitHub — that is what connects them.\n");
278
282
 
279
283
  // The refresh answer is the diagnosis. A 409 means GitHub never called back;
280
284
  // a success carrying no repositories means the App is installed and sharing
@@ -366,14 +370,6 @@ export async function slackCommand(args) {
366
370
  // The guided run
367
371
  // ---------------------------------------------------------------------------
368
372
 
369
- /**
370
- * "yes" | "no" | "back" -- back only offered once there is somewhere to go.
371
- *
372
- * `--yes` takes each step's own default rather than answering yes to all of
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.
376
- */
377
373
  /**
378
374
  * Ask, once, before PreMan writes into an SDK repository or a docs site.
379
375
  *
@@ -387,8 +383,14 @@ export async function slackCommand(args) {
387
383
  * which is the whole reason this moved out of the delivery run.
388
384
  */
389
385
  async function offerReleaseTargets(args, checkout, { assumeYes }) {
386
+ // SILENT is a step that did not happen rather than one that was skipped: no
387
+ // heading, and no line in the summary either. A repository with no SDK and no
388
+ // docs site has nothing to decide here, and saying so is worse than saying
389
+ // nothing.
390
+ const SILENT = { state: "skipped", silent: true };
391
+
390
392
  const integrationId = checkout?.match?.integration_id;
391
- if (!integrationId) return { state: "skipped" };
393
+ if (!integrationId) return SILENT;
392
394
 
393
395
  const token = resolveApiKey(args);
394
396
  const path = `/sdk/integrations/${integrationId}/distribution`;
@@ -399,18 +401,19 @@ async function offerReleaseTargets(args, checkout, { assumeYes }) {
399
401
  // Detection costs one root listing per repository in the installation. It
400
402
  // is the least important thing in this walk and never worth failing setup
401
403
  // over.
402
- return { state: "skipped" };
404
+ return SILENT;
403
405
  }
404
406
 
407
+ const sdkRepo = detected?.sdk_repo || "";
408
+ const docsRepo = detected?.docs_repo || "";
409
+ if (!sdkRepo && !docsRepo) return SILENT;
410
+
411
+ banner("Releases");
405
412
  if (detected?.confirmed) {
406
413
  process.stdout.write(`${MARK.ok()} Release targets already answered.\n`);
407
414
  return;
408
415
  }
409
416
 
410
- const sdkRepo = detected?.sdk_repo || "";
411
- const docsRepo = detected?.docs_repo || "";
412
- if (!sdkRepo && !docsRepo) return { state: "skipped" };
413
-
414
417
  const body = {};
415
418
  const decided = [];
416
419
  for (const [repo, field, flag, question] of [
@@ -474,6 +477,46 @@ async function askRelease(question, assumeYes) {
474
477
  return answer === "y" || answer === "yes";
475
478
  }
476
479
 
480
+ /**
481
+ * Put PreMan on screen, signed in as the account this walk just used.
482
+ *
483
+ * Returns whether the app is actually up, because that is what decides the last
484
+ * line of the walk and nothing downstream can work it out.
485
+ */
486
+ async function showDesktopSignedIn(openDesktopSignedIn, args, creds, stopOpening) {
487
+ // The same --dest the install honoured, or the launch would look for the app
488
+ // somewhere it was never copied to.
489
+ const destination = args.value("--dest", "/Applications");
490
+ const opened = await openDesktopSignedIn(creds, { destination });
491
+ stopOpening?.();
492
+ if (opened.state === "opened-signed-in") {
493
+ process.stdout.write("Opened PreMan, signed in as this account.\n");
494
+ } else if (opened.state === "not-installed") {
495
+ process.stdout.write(
496
+ `PreMan is not in ${destination} yet \u2014 open it once installed and sign in.\n`
497
+ );
498
+ } else {
499
+ // Either the session could not be handed over or this app is too old to take
500
+ // it up. Say so rather than let the customer wonder why they are looking at
501
+ // a login screen.
502
+ process.stdout.write("Opened PreMan \u2014 sign in with the account you just used.\n");
503
+ }
504
+ return opened.state !== "not-installed";
505
+ }
506
+
507
+ /** One step heading, in the one format the whole walk uses. */
508
+ function banner(name) {
509
+ process.stdout.write(`\n── ${name} ──\n`);
510
+ }
511
+
512
+ /**
513
+ * "yes" | "no" | "back" -- back only offered once there is somewhere to go.
514
+ *
515
+ * `--yes` takes each step's own default rather than answering yes to all of
516
+ * them, so a step that should not ride in on a keypress can say so. Every step
517
+ * that asks defaults to yes today; the desktop app, which used to be the
518
+ * exception, no longer asks at all.
519
+ */
477
520
  async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
478
521
  const fallback = defaultYes ? "yes" : "no";
479
522
  if (assumeYes) return fallback;
@@ -599,6 +642,22 @@ export async function onboardCommand(
599
642
  process.stdout.write(
600
643
  `${checkout.slug} is still not shared. Add it at ${INSTALLATIONS_URL}.\n`
601
644
  );
645
+ // Nothing was connected, so this step did not finish. Returning nothing
646
+ // marks it done, which is how the walk came to print a tick directly
647
+ // under a line saying the repository is not shared — and why somebody
648
+ // would then open the app expecting to find GitHub set up and find
649
+ // "Continue with GitHub" instead. The sibling branch below already
650
+ // reports this correctly; this one simply never did.
651
+ return { state: "skipped" };
652
+ },
653
+ onSkip: () => {
654
+ // The one question in this walk that decides whether a fix from this
655
+ // code can ever land anywhere. Declining it in silence is how the gap
656
+ // stays invisible until a fix task finishes and produces nothing.
657
+ process.stdout.write(
658
+ `${MARK.skip()} ${checkout.slug} stays disconnected — fixes here cannot open pull requests.\n`
659
+ );
660
+ process.stdout.write(` Run '${cliInvocation()} github' here to change that.\n`);
602
661
  },
603
662
  });
604
663
  } else {
@@ -631,6 +690,10 @@ export async function onboardCommand(
631
690
  // inside a delivery run, on a screen most people never opened.
632
691
  steps.push({
633
692
  name: "Releases",
693
+ // Prints its own heading, and only once it knows there is a question. A
694
+ // walk that draws an empty section has told the customer something is
695
+ // missing when nothing is.
696
+ ownBanner: true,
634
697
  run: () => offerReleaseTargets(args, checkout, { assumeYes }),
635
698
  });
636
699
 
@@ -678,8 +741,21 @@ export async function onboardCommand(
678
741
  if (desktop?.state === "installed") {
679
742
  steps.push({
680
743
  name: "Desktop app",
681
- run: () => {
744
+ // Nothing to download, which is the point of this branch. It is not a
745
+ // reason to stop at the sentence: the step exists to leave the customer
746
+ // looking at PreMan, so the one run with nothing to install was also the
747
+ // only run that opened nothing, and it closed by sending someone to a
748
+ // website instead of the app already sitting on their machine.
749
+ run: async () => {
682
750
  process.stdout.write(`${MARK.ok()} PreMan desktop app already installed.\n`);
751
+ const stopOpening = showOpeningDesktop();
752
+ try {
753
+ return {
754
+ appOpened: await showDesktopSignedIn(openDesktopSignedIn, args, creds, stopOpening),
755
+ };
756
+ } finally {
757
+ stopOpening();
758
+ }
683
759
  },
684
760
  });
685
761
  } else if (!desktop) {
@@ -707,26 +783,15 @@ export async function onboardCommand(
707
783
  return installed;
708
784
  }
709
785
  stopOpening ??= showOpeningDesktop();
710
- // The same --dest the install honoured, or the launch would look
711
- // for the app somewhere it was never copied to.
712
- const destination = args.value("--dest", "/Applications");
713
- const opened = await openDesktopSignedIn(creds, { destination });
714
- stopOpening();
715
- if (opened.state === "opened-signed-in") {
716
- process.stdout.write("Opened PreMan, signed in as this account.\n");
717
- } else if (opened.state === "not-installed") {
718
- process.stdout.write(
719
- `PreMan is not in ${destination} yet \u2014 open it once installed and sign in.\n`
720
- );
721
- } else {
722
- // Either the session could not be handed over or this app is too
723
- // old to take it up. Say so rather than let the customer wonder
724
- // why they are looking at a login screen.
725
- process.stdout.write("Opened PreMan \u2014 sign in with the account you just used.\n");
726
- }
727
- // Whether the app is on screen decides what the last line of the walk
728
- // should say, and only this closure knows.
729
- return { ...installed, appOpened: opened.state !== "not-installed" };
786
+ return {
787
+ ...installed,
788
+ appOpened: await showDesktopSignedIn(
789
+ openDesktopSignedIn,
790
+ args,
791
+ creds,
792
+ stopOpening
793
+ ),
794
+ };
730
795
  } finally {
731
796
  stopOpening?.();
732
797
  }
@@ -745,7 +810,7 @@ export async function onboardCommand(
745
810
  let i = 0;
746
811
  while (i < steps.length) {
747
812
  const step = steps[i];
748
- process.stdout.write(`\n── ${step.name} ──\n`);
813
+ if (!step.ownBanner) banner(step.name);
749
814
 
750
815
  if (step.question) {
751
816
  // "back" goes to the last step that asked something, not simply to the
@@ -784,6 +849,13 @@ export async function onboardCommand(
784
849
  // at the end of a successful install: the install reports state "installed",
785
850
  // which is none of the three, so it printed as a failure with no detail.
786
851
  const reported = await step.run();
852
+ // A step that never announced itself does not belong in the summary
853
+ // either: a line reading "(skipped)" for work nobody was offered reads as
854
+ // something having gone wrong.
855
+ if (reported?.silent) {
856
+ i += 1;
857
+ continue;
858
+ }
787
859
  const state =
788
860
  { failed: "failed", skipped: "skipped", unsupported: "skipped" }[reported?.state] ||
789
861
  "done";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {