premanmcp 0.15.3 → 0.16.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.
Files changed (2) hide show
  1. package/bin/integrations.js +109 -0
  2. package/package.json +1 -1
@@ -374,6 +374,106 @@ export async function slackCommand(args) {
374
374
  * that asks defaults to yes today; the desktop app, which used to be the
375
375
  * exception, no longer asks at all.
376
376
  */
377
+ /**
378
+ * Ask, once, before PreMan writes into an SDK repository or a docs site.
379
+ *
380
+ * Two questions rather than one. Agreeing to a generated client library is not
381
+ * agreeing to have your documentation site rewritten (a docs release replaces
382
+ * mkdocs.yml), and a single answer used to buy both.
383
+ *
384
+ * Says nothing when there is nothing to ask: no connected repository, no
385
+ * detected target, or an answer already on file. A walk that re-asks a question
386
+ * it has already had answered teaches people that answering buys them nothing,
387
+ * which is the whole reason this moved out of the delivery run.
388
+ */
389
+ async function offerReleaseTargets(args, checkout, { assumeYes }) {
390
+ const integrationId = checkout?.match?.integration_id;
391
+ if (!integrationId) return { state: "skipped" };
392
+
393
+ const token = resolveApiKey(args);
394
+ const path = `/sdk/integrations/${integrationId}/distribution`;
395
+ let detected = null;
396
+ try {
397
+ detected = await callBackendJson(args, "GET", path, { token });
398
+ } catch {
399
+ // Detection costs one root listing per repository in the installation. It
400
+ // is the least important thing in this walk and never worth failing setup
401
+ // over.
402
+ return { state: "skipped" };
403
+ }
404
+
405
+ if (detected?.confirmed) {
406
+ process.stdout.write(`${MARK.ok()} Release targets already answered.\n`);
407
+ return;
408
+ }
409
+
410
+ const sdkRepo = detected?.sdk_repo || "";
411
+ const docsRepo = detected?.docs_repo || "";
412
+ if (!sdkRepo && !docsRepo) return { state: "skipped" };
413
+
414
+ const body = {};
415
+ const decided = [];
416
+ for (const [repo, field, flag, question] of [
417
+ [
418
+ sdkRepo,
419
+ "sdk_repo",
420
+ "publish_sdk",
421
+ `${sdkRepo} looks like your client library. Open pull requests there with generated SDKs?`,
422
+ ],
423
+ [
424
+ docsRepo,
425
+ "docs_repo",
426
+ "publish_docs",
427
+ // The wider blast radius is named in the question, because it is the part
428
+ // somebody would resent discovering afterwards.
429
+ `${docsRepo} looks like your docs site. Open pull requests there? Releases rewrite mkdocs.yml.`,
430
+ ],
431
+ ]) {
432
+ if (!repo) continue;
433
+ const answer = await askRelease(question, assumeYes);
434
+ if (answer === null) continue;
435
+ body[field] = repo;
436
+ body[flag] = answer;
437
+ decided.push([repo, answer]);
438
+ }
439
+ if (!decided.length) return { state: "skipped" };
440
+
441
+ await callBackendJson(args, "PUT", path, { token, json: body });
442
+
443
+ for (const [repo, allowed] of decided) {
444
+ // A no is reported as recorded, not as a step that failed. It is an answer,
445
+ // and PreMan will not come back to it.
446
+ if (allowed) process.stdout.write(`${MARK.ok()} Publishing to ${repo}.\n`);
447
+ else
448
+ process.stdout.write(
449
+ `${MARK.skip()} Not publishing to ${repo}. PreMan will not ask again.\n`
450
+ );
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Yes, no, or nobody answered.
456
+ *
457
+ * Three outcomes rather than two, and the third is the whole reason this does
458
+ * not use `confirm`. That helper reads a closed stdin as a no, which is right
459
+ * for a step that then simply does less work. A no here is *recorded*, and a
460
+ * recorded no is never asked again, so reading silence as refusal would retire
461
+ * a question the customer never saw.
462
+ *
463
+ * The same argument applies to --yes: accepting defaults on someone's behalf is
464
+ * fine for a hook or a download, and is not a way to acquire write access to a
465
+ * repository nobody mentioned. So an unattended run leaves both questions open.
466
+ *
467
+ * The default is no, for the reason silence means no everywhere else PreMan
468
+ * writes to somebody's repository.
469
+ */
470
+ async function askRelease(question, assumeYes) {
471
+ if (assumeYes) return null;
472
+ const answer = (await promptText(`${question} [y/N]: `)).trim().toLowerCase();
473
+ if (answer === "") return null;
474
+ return answer === "y" || answer === "yes";
475
+ }
476
+
377
477
  async function askStep(question, { assumeYes, canGoBack, defaultYes = true }) {
378
478
  const fallback = defaultYes ? "yes" : "no";
379
479
  if (assumeYes) return fallback;
@@ -525,6 +625,15 @@ export async function onboardCommand(
525
625
  });
526
626
  }
527
627
 
628
+ // The only questions in this walk about repositories *other* than the one
629
+ // being connected. They are asked here, while PreMan has just read the
630
+ // installation and can name what it found, rather than weeks later from
631
+ // inside a delivery run, on a screen most people never opened.
632
+ steps.push({
633
+ name: "Releases",
634
+ run: () => offerReleaseTargets(args, checkout, { assumeYes }),
635
+ });
636
+
528
637
  steps.push({
529
638
  name: "Push checks",
530
639
  question: "Check your endpoints on every git push in this repo?",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.15.3",
3
+ "version": "0.16.0",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {