@stigmer/cli 3.18.1-dev.20260919070736 → 3.19.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 (39) hide show
  1. package/commands/apply.js +3 -1
  2. package/commands/apply.js.map +1 -1
  3. package/commands/install.js +2 -1
  4. package/commands/install.js.map +1 -1
  5. package/commands/marketplace.js +3 -3
  6. package/commands/marketplace.js.map +1 -1
  7. package/commands/push.js +3 -1
  8. package/commands/push.js.map +1 -1
  9. package/marketplace/config.d.ts.map +1 -1
  10. package/marketplace/config.js +13 -9
  11. package/marketplace/config.js.map +1 -1
  12. package/marketplace/render.d.ts.map +1 -1
  13. package/marketplace/render.js +1 -7
  14. package/marketplace/render.js.map +1 -1
  15. package/package.json +7 -7
  16. package/resources/connect/oauth.d.ts.map +1 -1
  17. package/resources/connect/oauth.js +4 -1
  18. package/resources/connect/oauth.js.map +1 -1
  19. package/resources/plugin.d.ts +27 -0
  20. package/resources/plugin.d.ts.map +1 -1
  21. package/resources/plugin.js +113 -8
  22. package/resources/plugin.js.map +1 -1
  23. package/src/commands/apply.ts +6 -1
  24. package/src/commands/install.test.ts +20 -21
  25. package/src/commands/install.ts +2 -1
  26. package/src/commands/marketplace.test.ts +10 -19
  27. package/src/commands/marketplace.ts +3 -3
  28. package/src/commands/push.ts +6 -1
  29. package/src/local/plugins/defaults.test.ts +28 -28
  30. package/src/marketplace/__fixtures__/cursor-marketplace.ts +16 -12
  31. package/src/marketplace/config.test.ts +12 -28
  32. package/src/marketplace/config.ts +13 -9
  33. package/src/marketplace/install.test.ts +19 -19
  34. package/src/marketplace/read.test.ts +6 -6
  35. package/src/marketplace/render.ts +1 -10
  36. package/src/resources/connect/oauth.test.ts +2 -2
  37. package/src/resources/connect/oauth.ts +4 -1
  38. package/src/resources/plugin.test.ts +119 -2
  39. package/src/resources/plugin.ts +140 -8
@@ -23,6 +23,15 @@
23
23
  // and what `stigmer up` compares with the server's digest before deciding
24
24
  // to push. `validate -f`, `push plugin --dry-run` and `push plugin` share one
25
25
  // description renderer so the author reads one vocabulary at every step.
26
+ //
27
+ // `readNextSteps` is what the install leaves the user to do, read from the
28
+ // servers the push produced: a sign-in an OAuth server needs (`stigmer
29
+ // connect mcp-server` runs it), the variables an API-key server wants and
30
+ // where they are asked, and, for a plugin that installed tools and no
31
+ // agent, the agent the tools still need. Best-effort by design: a server
32
+ // or a grant the CLI cannot read yields no line rather than a failed
33
+ // install, because the install itself succeeded. `stigmer up` does not
34
+ // read them: its plugin is the built-in assistant, not a user's act.
26
35
 
27
36
  import { readdirSync, readFileSync, statSync } from "node:fs";
28
37
  import { join } from "node:path";
@@ -48,6 +57,10 @@ import {
48
57
  PushPluginRequestSchema,
49
58
  type PluginMember,
50
59
  } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/io_pb";
60
+ import {
61
+ GetOAuthGrantStatusInputSchema,
62
+ OAuthConnectionHealth,
63
+ } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
51
64
  import { PluginDialect as PluginDialectProto } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/spec_pb";
52
65
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
53
66
  import type { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
@@ -390,6 +403,113 @@ export async function pushPlugin(
390
403
  export interface RenderPushOptions {
391
404
  /** Where the archive came from when it was not a folder: the marketplace's name and source. */
392
405
  readonly installedFrom?: string;
406
+ /** What the install leaves the user to do, from `readNextSteps`. */
407
+ readonly next?: readonly NextStep[];
408
+ }
409
+
410
+ /** One thing standing between an installed MCP server and its first tool call, or the agent the tools still need. */
411
+ export type NextStep =
412
+ | { readonly kind: "sign-in"; readonly server: string; readonly command: string }
413
+ | { readonly kind: "signed-in"; readonly server: string }
414
+ | {
415
+ readonly kind: "api-key";
416
+ readonly server: string;
417
+ readonly variables: readonly string[];
418
+ /** Where the variables are asked: the agent's first session, or `stigmer connect mcp-server --env`. */
419
+ readonly askedAt: "agent" | "connect";
420
+ readonly command?: string;
421
+ }
422
+ | { readonly kind: "add-to-agent"; readonly servers: readonly string[] };
423
+
424
+ /**
425
+ * What the install leaves the user to do, read from each MCP server the
426
+ * push produced. Best-effort: a server or grant this CLI cannot read yields
427
+ * no step, never a failure, because the install itself succeeded.
428
+ */
429
+ export async function readNextSteps(
430
+ client: Stigmer,
431
+ org: string,
432
+ members: readonly PluginMember[],
433
+ ): Promise<NextStep[]> {
434
+ const servers = members.filter((m) => m.kind === ApiResourceKind.mcp_server);
435
+ const hasAgent = members.some((m) => m.kind === ApiResourceKind.agent);
436
+ const steps: NextStep[] = [];
437
+ for (const member of servers) {
438
+ try {
439
+ const server = await client.mcpServer.getByReference({
440
+ org,
441
+ slug: member.slug,
442
+ });
443
+ const auth = server.spec?.auth;
444
+ const variables = Object.keys(server.spec?.env ?? {});
445
+ if (auth?.targetEnvVar) {
446
+ let connected = false;
447
+ try {
448
+ const grant = await client.mcpServer.getOAuthGrantStatus(
449
+ create(GetOAuthGrantStatusInputSchema, {
450
+ resourceId: server.metadata?.id ?? "",
451
+ org,
452
+ }),
453
+ );
454
+ connected =
455
+ grant.connected &&
456
+ grant.connectionHealth !==
457
+ OAuthConnectionHealth.OAUTH_CONNECTION_HEALTH_TOKEN_EXPIRED;
458
+ } catch {
459
+ // Fail closed: a grant the CLI cannot read is a sign-in still owed.
460
+ }
461
+ steps.push(
462
+ connected
463
+ ? { kind: "signed-in", server: member.slug }
464
+ : {
465
+ kind: "sign-in",
466
+ server: member.slug,
467
+ command: `stigmer connect mcp-server ${member.slug}`,
468
+ },
469
+ );
470
+ } else if (variables.length > 0) {
471
+ steps.push({
472
+ kind: "api-key",
473
+ server: member.slug,
474
+ variables,
475
+ askedAt: hasAgent ? "agent" : "connect",
476
+ ...(hasAgent
477
+ ? {}
478
+ : {
479
+ command: `stigmer connect mcp-server ${member.slug} --env ${variables.map((v) => `${v}=...`).join(" --env ")}`,
480
+ }),
481
+ });
482
+ }
483
+ } catch {
484
+ // A server the CLI cannot read has no line; the Installed section still names it.
485
+ }
486
+ }
487
+ if (!hasAgent && servers.length > 0) {
488
+ steps.push({
489
+ kind: "add-to-agent",
490
+ servers: servers.map((m) => m.slug),
491
+ });
492
+ }
493
+ return steps;
494
+ }
495
+
496
+ function describeNextStep(step: NextStep): string {
497
+ switch (step.kind) {
498
+ case "sign-in":
499
+ return `Sign in to ${step.server}: ${step.command}`;
500
+ case "signed-in":
501
+ return `${step.server}: signed in`;
502
+ case "api-key":
503
+ return step.askedAt === "agent"
504
+ ? `${step.server} needs ${step.variables.join(", ")}; the agent asks at its first session`
505
+ : `${step.server} needs ${step.variables.join(", ")}; set them when you connect: ${step.command ?? ""}`;
506
+ case "add-to-agent":
507
+ return `Add these tools to an agent: list ${step.servers.map((s) => `'${s}'`).join(", ")} under mcp_server_usages in an agent's YAML, or from the plugin's page in the console`;
508
+ default: {
509
+ const exhaustive: never = step;
510
+ return exhaustive;
511
+ }
512
+ }
393
513
  }
394
514
 
395
515
  /** The install as the user reads it: what landed, what was skipped, what to know. */
@@ -400,14 +520,20 @@ export function renderPushOutcome(
400
520
  const { plugin, members } = outcome;
401
521
  const warnings = plugin.status?.warnings ?? [];
402
522
  const counts = plugin.status?.materialized;
403
- const summary = [
404
- count(counts?.skills ?? 0, "skill"),
405
- count(counts?.mcpServers ?? 0, "MCP server"),
406
- count(counts?.agents ?? 0, "agent"),
407
- ...(counts !== undefined && counts.workflows > 0
408
- ? [count(counts.workflows, "workflow")]
409
- : []),
410
- ].join(", ");
523
+ // Only the kinds the plugin installed are named, as the console's
524
+ // `summariseInstall` names them: most catalogue plugins install tools
525
+ // alone, and "0 skills, 1 MCP server, 0 agents" reads as three facts.
526
+ const named = (
527
+ [
528
+ [counts?.skills ?? 0, "skill"],
529
+ [counts?.mcpServers ?? 0, "MCP server"],
530
+ [counts?.agents ?? 0, "agent"],
531
+ [counts?.workflows ?? 0, "workflow"],
532
+ ] as const
533
+ )
534
+ .filter(([n]) => n > 0)
535
+ .map(([n, noun]) => count(n, noun));
536
+ const summary = named.length === 0 ? "nothing installed" : named.join(", ");
411
537
  const headline = `Installed plugin '${plugin.metadata?.slug ?? plugin.spec?.name ?? ""}' (${summary})`;
412
538
  const result =
413
539
  warnings.length === 0
@@ -447,9 +573,15 @@ export function renderPushOutcome(
447
573
  const section = result.addSection("Warnings");
448
574
  for (const warning of warnings) section.item(warning.message);
449
575
  }
576
+ const next = options.next ?? [];
577
+ if (next.length > 0) {
578
+ const section = result.addSection("Next");
579
+ for (const step of next) section.item(describeNextStep(step));
580
+ }
450
581
  return result.withData({
451
582
  plugin: toJson(PluginSchema, plugin),
452
583
  members: members.map((m) => toJson(PluginMemberSchema, m)),
584
+ ...(options.next !== undefined && { next: options.next }),
453
585
  });
454
586
  }
455
587