@stigmer/cli 3.23.0 → 3.24.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 (84) hide show
  1. package/commands/bootstrap.d.ts.map +1 -1
  2. package/commands/bootstrap.js +16 -57
  3. package/commands/bootstrap.js.map +1 -1
  4. package/commands/config/context.d.ts.map +1 -1
  5. package/commands/config/context.js +41 -5
  6. package/commands/config/context.js.map +1 -1
  7. package/commands/install.js +1 -1
  8. package/commands/install.js.map +1 -1
  9. package/commands/push.js +1 -1
  10. package/commands/push.js.map +1 -1
  11. package/commands/search.d.ts.map +1 -1
  12. package/commands/search.js +0 -2
  13. package/commands/search.js.map +1 -1
  14. package/commands/up.js +1 -1
  15. package/commands/up.js.map +1 -1
  16. package/config/index.d.ts +1 -1
  17. package/config/index.d.ts.map +1 -1
  18. package/config/index.js +1 -1
  19. package/config/index.js.map +1 -1
  20. package/config/resolve.d.ts +8 -0
  21. package/config/resolve.d.ts.map +1 -1
  22. package/config/resolve.js +8 -8
  23. package/config/resolve.js.map +1 -1
  24. package/local/bootstrap.d.ts +9 -29
  25. package/local/bootstrap.d.ts.map +1 -1
  26. package/local/bootstrap.js +61 -69
  27. package/local/bootstrap.js.map +1 -1
  28. package/local/daemon/launch.d.ts +2 -2
  29. package/local/daemon/launch.d.ts.map +1 -1
  30. package/marketplace/official.d.ts.map +1 -1
  31. package/marketplace/official.js +6 -5
  32. package/marketplace/official.js.map +1 -1
  33. package/marketplace/render.d.ts.map +1 -1
  34. package/marketplace/render.js +0 -3
  35. package/marketplace/render.js.map +1 -1
  36. package/package.json +7 -7
  37. package/resources/search.d.ts +1 -3
  38. package/resources/search.d.ts.map +1 -1
  39. package/resources/search.js +0 -1
  40. package/resources/search.js.map +1 -1
  41. package/resources/share.d.ts.map +1 -1
  42. package/resources/share.js +20 -24
  43. package/resources/share.js.map +1 -1
  44. package/resources/skill.d.ts +2 -2
  45. package/resources/skill.d.ts.map +1 -1
  46. package/resources/skill.js +19 -10
  47. package/resources/skill.js.map +1 -1
  48. package/src/commands/bootstrap.test.ts +19 -75
  49. package/src/commands/bootstrap.ts +19 -68
  50. package/src/commands/config/context.test.ts +171 -0
  51. package/src/commands/config/context.ts +50 -5
  52. package/src/commands/install.ts +1 -1
  53. package/src/commands/marketplace.test.ts +2 -2
  54. package/src/commands/push.ts +1 -1
  55. package/src/commands/search.ts +0 -3
  56. package/src/commands/up.ts +1 -1
  57. package/src/config/index.ts +1 -0
  58. package/src/config/resolve.test.ts +2 -2
  59. package/src/config/resolve.ts +8 -8
  60. package/src/local/bootstrap.test.ts +132 -65
  61. package/src/local/bootstrap.ts +64 -139
  62. package/src/local/daemon/launch.ts +2 -2
  63. package/src/marketplace/official.ts +6 -5
  64. package/src/marketplace/read.test.ts +2 -3
  65. package/src/marketplace/render.ts +0 -3
  66. package/src/resources/apply/apply.test.ts +10 -10
  67. package/src/resources/read-completion.integration.test.ts +0 -2
  68. package/src/resources/search.ts +1 -4
  69. package/src/resources/share.test.ts +7 -60
  70. package/src/resources/share.ts +22 -27
  71. package/src/resources/skill.test.ts +20 -13
  72. package/src/resources/skill.ts +25 -14
  73. package/local/plugins/defaults.d.ts +0 -55
  74. package/local/plugins/defaults.d.ts.map +0 -1
  75. package/local/plugins/defaults.js +0 -111
  76. package/local/plugins/defaults.js.map +0 -1
  77. package/local/system-org.d.ts +0 -15
  78. package/local/system-org.d.ts.map +0 -1
  79. package/local/system-org.js +0 -57
  80. package/local/system-org.js.map +0 -1
  81. package/src/local/plugins/defaults.test.ts +0 -271
  82. package/src/local/plugins/defaults.ts +0 -179
  83. package/src/local/system-org.test.ts +0 -124
  84. package/src/local/system-org.ts +0 -63
@@ -51,20 +51,20 @@ function handlerWith(opts: HandlerOptions): { handler: ApplyHandler; calls: Upda
51
51
 
52
52
  describe("applyMessage declared-visibility follow-up", () => {
53
53
  it("lands a declared level the update preserved away, and reflects it on the outcome", async () => {
54
- // Server preserved stored org; manifest declares public.
54
+ // Server preserved stored org; manifest declares platform.
55
55
  const { handler, calls } = handlerWith({
56
56
  applyReturns: agent(ApiResourceVisibility.visibility_org),
57
- updateVisibility: () => Promise.resolve(agent(ApiResourceVisibility.visibility_public)),
57
+ updateVisibility: () => Promise.resolve(agent(ApiResourceVisibility.visibility_platform)),
58
58
  });
59
59
 
60
- const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_public), "acme", false);
60
+ const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_platform), "acme", false);
61
61
 
62
62
  expect(calls).toHaveLength(1);
63
63
  expect(calls[0].resourceId).toBe("agent-1");
64
- expect(calls[0].visibility).toBe(ApiResourceVisibility.visibility_public);
64
+ expect(calls[0].visibility).toBe(ApiResourceVisibility.visibility_platform);
65
65
  expect(outcome.warning).toBeUndefined();
66
66
  const appliedMeta = (outcome.applied as { metadata?: { visibility?: ApiResourceVisibility } })?.metadata;
67
- expect(appliedMeta?.visibility).toBe(ApiResourceVisibility.visibility_public);
67
+ expect(appliedMeta?.visibility).toBe(ApiResourceVisibility.visibility_platform);
68
68
  });
69
69
 
70
70
  it("skips the follow-up when the manifest omits visibility", async () => {
@@ -87,11 +87,11 @@ describe("applyMessage declared-visibility follow-up", () => {
87
87
 
88
88
  it("skips the follow-up when the server already matches (idempotent re-apply)", async () => {
89
89
  const { handler, calls } = handlerWith({
90
- applyReturns: agent(ApiResourceVisibility.visibility_public),
90
+ applyReturns: agent(ApiResourceVisibility.visibility_platform),
91
91
  updateVisibility: () => Promise.reject(new Error("must not be called")),
92
92
  });
93
93
 
94
- const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_public), "acme", false);
94
+ const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_platform), "acme", false);
95
95
 
96
96
  expect(calls).toHaveLength(0);
97
97
  expect(outcome.warning).toBeUndefined();
@@ -102,7 +102,7 @@ describe("applyMessage declared-visibility follow-up", () => {
102
102
  applyReturns: agent(ApiResourceVisibility.visibility_private),
103
103
  });
104
104
 
105
- const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_public), "acme", false);
105
+ const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_platform), "acme", false);
106
106
 
107
107
  expect(outcome.warning).toMatch(/visibility cannot be changed declaratively/);
108
108
  expect(outcome.warning).toMatch(/stored value is kept/);
@@ -116,7 +116,7 @@ describe("applyMessage declared-visibility follow-up", () => {
116
116
  });
117
117
 
118
118
  await expect(
119
- applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_public), "acme", false),
119
+ applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_platform), "acme", false),
120
120
  ).rejects.toThrow(/spec applied, but the manifest's visibility change was rejected/);
121
121
  });
122
122
 
@@ -126,7 +126,7 @@ describe("applyMessage declared-visibility follow-up", () => {
126
126
  updateVisibility: () => Promise.reject(new Error("must not be called")),
127
127
  });
128
128
 
129
- const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_public), "acme", true);
129
+ const outcome = await applyMessage(controller, handler, agent(ApiResourceVisibility.visibility_platform), "acme", true);
130
130
 
131
131
  expect(calls).toHaveLength(0);
132
132
  expect(outcome.applied).toBeUndefined();
@@ -100,7 +100,6 @@ describe("search integration", () => {
100
100
  it("queries the SearchService and renders entries as protojson", async () => {
101
101
  const outcome = await searchResources(client, ApiResourceKind.workflow, "deploy", {
102
102
  org: "acme",
103
- excludePublic: false,
104
103
  page: 1,
105
104
  pageSize: 20,
106
105
  }, "json");
@@ -113,7 +112,6 @@ describe("search integration", () => {
113
112
  it("renders a human table for search results", async () => {
114
113
  const outcome = await searchResources(client, ApiResourceKind.workflow, "deploy", {
115
114
  org: "acme",
116
- excludePublic: false,
117
115
  page: 1,
118
116
  pageSize: 20,
119
117
  }, "table");
@@ -11,10 +11,8 @@ import { SEARCH_TABLE } from "./list.js";
11
11
  import { renderCollection } from "./render.js";
12
12
 
13
13
  export interface SearchParams {
14
- /** Organization scope. Empty searches across all accessible orgs (incl. public). */
14
+ /** Organization scope. Empty searches across every org the caller can access. */
15
15
  readonly org: string;
16
- /** Exclude public/platform resources, restricting to the caller's own. */
17
- readonly excludePublic: boolean;
18
16
  /** 1-indexed page number. */
19
17
  readonly page: number;
20
18
  /** Results per page. */
@@ -40,7 +38,6 @@ export async function searchResources(
40
38
  kinds: [kind],
41
39
  query,
42
40
  org: params.org,
43
- excludePublic: params.excludePublic,
44
41
  page: { num: params.page, size: params.pageSize },
45
42
  });
46
43
 
@@ -472,77 +472,24 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
472
472
  });
473
473
  });
474
474
 
475
- describe("shareAgent cross-org (decision 013)", () => {
476
- it("lands the share in the CALLER's org, agent_ref keeps the provider's", async () => {
477
- const { client, applies } = fakeClient(makeAgent(), null);
478
-
479
- const result = await shareAgent(client, "acme/support-agent", "consumer-org", {
480
- enabled: true,
481
- ...CLOUD,
482
- });
483
-
484
- expect(applies).toHaveLength(1);
485
- // The share is the caller org's channel: its URL, billing, and
486
- // credentials — while the blueprint reference stays the provider's.
487
- expect(applies[0].org).toBe("consumer-org");
488
- expect(applies[0].slug).toBe("support-agent");
489
- expect(applies[0].agentRef).toEqual({ org: "acme", slug: "support-agent" });
490
-
491
- const link = result.sections.find((s) => s.title === "Public chat link");
492
- expect(link?.items).toEqual(["https://app.stigmer.ai/chat/consumer-org/support-agent"]);
493
- expect(result.hints.some((h) => h.includes("consumer-org's credits"))).toBe(true);
494
- });
495
-
496
- it("never edits the provider org's own share (canonical resolution is org-scoped)", async () => {
497
- // The provider already shares this agent with its own config; the
498
- // caller's toggle must create the CALLER org's share, not flip (or
499
- // inherit the config of) the provider's row.
500
- const providerShare = makeShare({
501
- enabled: true,
502
- allowedOrigins: ["https://provider.example.com"],
503
- });
504
- const { client, applies } = fakeClient(makeAgent(), providerShare);
505
-
506
- await shareAgent(client, "acme/support-agent", "consumer-org", {
507
- enabled: true,
508
- ...CLOUD,
509
- });
510
-
511
- expect(applies).toHaveLength(1);
512
- expect(applies[0].org).toBe("consumer-org");
513
- // Fresh channel, fresh config: the provider's origins must not leak
514
- // into the caller org's share.
515
- expect(applies[0].allowedOrigins).toEqual([]);
516
- });
517
-
518
- it("refuses --audience org with guidance (cross-org shares are public only)", async () => {
475
+ describe("shareAgent from another organization's context", () => {
476
+ it("refuses before any write, naming where the share lives and the plugin remedy", async () => {
477
+ // The server would refuse the agent_ref too; the CLI refuses first so
478
+ // the caller reads the remedy rather than a bare FAILED_PRECONDITION.
519
479
  const { client, applies } = fakeClient(makeAgent(), null);
520
480
 
521
481
  const err = await shareAgent(client, "acme/support-agent", "consumer-org", {
522
482
  enabled: true,
523
- audience: "org",
524
483
  ...CLOUD,
525
484
  }).catch((e: unknown) => e);
526
485
 
527
486
  expect(err).toBeInstanceOf(UsageError);
528
- expect((err as Error).message).toContain("agent's own organization");
487
+ expect((err as Error).message).toContain("lives in acme, not consumer-org");
488
+ expect((err as Error).message).toContain("install the plugin that carries it");
489
+ expect((err as Error).message).toContain("--org acme");
529
490
  expect(classify(err)?.exitCode).toBe(ExitCode.Usage);
530
491
  expect(applies).toHaveLength(0);
531
492
  });
532
-
533
- it("passes the server's fail-loud dependency refusal through unchanged", async () => {
534
- // The D5 sweep names every non-public dependency; the CLI must
535
- // surface that message verbatim — it tells the caller exactly what
536
- // to ask the provider org to publish.
537
- const refusal = new Error(
538
- "cannot share acme/support-agent across organizations: it references resources that are not public: skill acme/private-skill",
539
- );
540
- const { client } = fakeClient(makeAgent(), null, { failWith: refusal });
541
-
542
- await expect(
543
- shareAgent(client, "acme/support-agent", "consumer-org", { enabled: true, ...CLOUD }),
544
- ).rejects.toThrow(/skill acme\/private-skill/);
545
- });
546
493
  });
547
494
 
548
495
  describe("shareAgent errors", () => {
@@ -94,32 +94,28 @@ export async function shareAgent(
94
94
 
95
95
  const agent = await resolveAgentRef(client, ref, org);
96
96
  const agentOrg = agent.metadata?.org ?? "";
97
- // The share lands in the CALLER's resolved org (decision 013 D8): when
98
- // it differs from the agent's, this is a cross-org share — the caller
99
- // org's own channel (URL, billing, credentials) of another org's
100
- // marketplace-public agent. An empty org (ID ref without context)
101
- // falls back to the agent's own — the Phase A same-org form.
102
- const shareOrg = org || agentOrg;
103
- const isCrossOrg = shareOrg !== agentOrg;
97
+ // A share lives in its agent's organization: its URL, billing and
98
+ // credential bindings belong to the organization that owns the agent.
99
+ // The server refuses an agent_ref in any other organization; refusing
100
+ // here names the remedy instead of surfacing a bare FAILED_PRECONDITION,
101
+ // and mirrors the share dialog, which never offers another org's agent.
102
+ // An empty org (an ID ref without context) is the agent's own.
103
+ if (org !== "" && org !== agentOrg) {
104
+ throw new UsageError(
105
+ `a share of ${agentOrg}/${agent.metadata?.slug ?? ""} lives in ${agentOrg}, not ${org}\n\n` +
106
+ "A share is the agent's own organization's channel. To share another\n" +
107
+ "organization's agent from yours, install the plugin that carries it and\n" +
108
+ "share the installed copy. To manage this agent's own share:\n" +
109
+ ` stigmer share agent ${ref} --org ${agentOrg}`,
110
+ );
111
+ }
112
+ const shareOrg = agentOrg;
104
113
  let share = await resolveCanonicalShare(client, agent, shareOrg);
105
114
  const current = share?.spec;
106
115
 
107
116
  const currentAudience = audienceFromProto(current?.audience);
108
117
  const targetAudience = options.audience ?? currentAudience;
109
118
 
110
- // Cross-org shares are public-audience only (decision 013 D3). The
111
- // server refuses this too; refusing here names the remedy instead of
112
- // surfacing a bare FAILED_PRECONDITION — and mirrors the share dialog,
113
- // which hides the audience selector in cross-org mode.
114
- if (isCrossOrg && targetAudience === "org") {
115
- throw new UsageError(
116
- "--audience org is not available for another organization's agent\n\n" +
117
- `A share of ${agentOrg}/${agent.metadata?.slug ?? ""} in your organization\n` +
118
- "(" + shareOrg + ") serves anyone with the link. Org-members-only sharing\n" +
119
- "is limited to the agent's own organization.",
120
- );
121
- }
122
-
123
119
  // Resetting the link is meaningless for org-members-only shares: their
124
120
  // access is gated by live membership, and the member link never carries
125
121
  // the token. Refuse rather than silently rotating an unused secret —
@@ -179,10 +175,10 @@ export async function shareAgent(
179
175
  * slug equals the agent's (the server's default on create), else the
180
176
  * first entry in that org, else null when the agent has never been shared
181
177
  * there. Mirrors the web dialog's selection so both surfaces manage the
182
- * same row. The org scope is what keeps each org on its own channel
183
- * (decision 013): an agent may be shared in N orgs, and toggling here
184
- * must never edit another org's share. The server applies the scope via
185
- * the request's org field.
178
+ * same row. The org scope is the agent's organization, where every share
179
+ * of it lives; the server applies the scope via the request's org field,
180
+ * so a share written in another organization before sharing across
181
+ * organizations was retired is never the row this command edits.
186
182
  */
187
183
  async function resolveCanonicalShare(
188
184
  client: Stigmer,
@@ -214,9 +210,8 @@ function audienceFromProto(audience: AgentShareAudience | undefined): ShareAudie
214
210
  // was never shared before). Identity comes from the existing share when one
215
211
  // exists — a manifest-created share may carry a non-default slug, and
216
212
  // applying with the agent's slug would create a SECOND share — and from the
217
- // sharing org + the agent's slug otherwise (the server's own D2 default,
218
- // made explicit; for a cross-org share the org is the CALLER's, not the
219
- // agent's — decision 013). The audience is written explicitly — never left
213
+ // agent's org + the agent's slug otherwise (the server's own D2 default,
214
+ // made explicit). The audience is written explicitly — never left
220
215
  // unspecified — so a console-managed org share can't drift back to public.
221
216
  function preservingShareInput(
222
217
  agent: Agent,
@@ -71,10 +71,10 @@ describe("parseSkillMetadata", () => {
71
71
  });
72
72
 
73
73
  it("parses a declared visibility from frontmatter", () => {
74
- writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: public\n---\n");
74
+ writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: org\n---\n");
75
75
  expect(parseSkillMetadata(dir)).toEqual({
76
76
  name: "my-skill",
77
- visibility: ApiResourceVisibility.visibility_public,
77
+ visibility: ApiResourceVisibility.visibility_org,
78
78
  });
79
79
  });
80
80
 
@@ -85,16 +85,23 @@ describe("parseSkillMetadata", () => {
85
85
  });
86
86
 
87
87
  describe("parseVisibility", () => {
88
- it("maps the four short forms", () => {
88
+ it("maps the three short forms", () => {
89
89
  expect(parseVisibility("private")).toBe(ApiResourceVisibility.visibility_private);
90
- expect(parseVisibility("public")).toBe(ApiResourceVisibility.visibility_public);
91
90
  expect(parseVisibility("org")).toBe(ApiResourceVisibility.visibility_org);
92
91
  expect(parseVisibility("platform")).toBe(ApiResourceVisibility.visibility_platform);
93
92
  });
94
93
 
95
94
  it("accepts canonical enum names and is case/whitespace insensitive", () => {
96
- expect(parseVisibility(" Visibility_Public ")).toBe(ApiResourceVisibility.visibility_public);
97
- expect(parseVisibility("PUBLIC")).toBe(ApiResourceVisibility.visibility_public);
95
+ expect(parseVisibility(" Visibility_Platform ")).toBe(ApiResourceVisibility.visibility_platform);
96
+ expect(parseVisibility("ORG")).toBe(ApiResourceVisibility.visibility_org);
97
+ });
98
+
99
+ it("refuses the retired public level by name, with the remedy and the valid values", () => {
100
+ for (const spelling of ["public", "visibility_public", " PUBLIC "]) {
101
+ expect(() => parseVisibility(spelling, "--visibility")).toThrow(
102
+ /invalid 'visibility' value '.*' in --visibility: the public level is retired[\s\S]*plugins[\s\S]*Valid values: private, org, platform\./,
103
+ );
104
+ }
98
105
  });
99
106
 
100
107
  it("treats omitted/unspecified as not declared (undefined)", () => {
@@ -141,16 +148,16 @@ const NO_IGNORE = { respectGitignore: true, extraIgnore: [], extraInclude: [] };
141
148
 
142
149
  describe("pushSkill visibility propagation", () => {
143
150
  it("issues an UpdateVisibility RPC when the SKILL.md declares a non-matching visibility", async () => {
144
- writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: public\n---\n");
151
+ writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: org\n---\n");
145
152
  const { client, calls } = fakeClient({ id: "skill-123", visibility: ApiResourceVisibility.visibility_private });
146
153
 
147
154
  const result = await pushSkill(client, dir, "stigmer", "latest", "", NO_IGNORE);
148
155
 
149
156
  expect(calls.push).toBe(1);
150
157
  expect(calls.updateVisibility).toEqual([
151
- { resourceId: "skill-123", visibility: ApiResourceVisibility.visibility_public },
158
+ { resourceId: "skill-123", visibility: ApiResourceVisibility.visibility_org },
152
159
  ]);
153
- expect(result.visibility).toBe(ApiResourceVisibility.visibility_public);
160
+ expect(result.visibility).toBe(ApiResourceVisibility.visibility_org);
154
161
  });
155
162
 
156
163
  it("does not touch visibility when the SKILL.md omits it", async () => {
@@ -163,8 +170,8 @@ describe("pushSkill visibility propagation", () => {
163
170
  });
164
171
 
165
172
  it("skips the RPC when the server already matches the declared visibility", async () => {
166
- writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: public\n---\n");
167
- const { client, calls } = fakeClient({ id: "skill-123", visibility: ApiResourceVisibility.visibility_public });
173
+ writeFileSync(join(dir, "SKILL.md"), "---\nname: my-skill\nvisibility: org\n---\n");
174
+ const { client, calls } = fakeClient({ id: "skill-123", visibility: ApiResourceVisibility.visibility_org });
168
175
 
169
176
  await pushSkill(client, dir, "stigmer", "latest", "", NO_IGNORE);
170
177
 
@@ -296,7 +303,7 @@ describe("readSkillArchive", () => {
296
303
  describe("pushSkillFromArchive", () => {
297
304
  it("uploads the archive bytes untouched (checksum parity) and applies declared visibility", async () => {
298
305
  const zipped = zipSync({
299
- "SKILL.md": new TextEncoder().encode("---\nname: my-skill\nvisibility: public\n---\n# S\n"),
306
+ "SKILL.md": new TextEncoder().encode("---\nname: my-skill\nvisibility: org\n---\n# S\n"),
300
307
  });
301
308
  const archivePath = join(dir, "skill.zip");
302
309
  writeFileSync(archivePath, zipped);
@@ -307,7 +314,7 @@ describe("pushSkillFromArchive", () => {
307
314
  expect(calls.push).toBe(1);
308
315
  expect(Buffer.from(calls.pushedArtifacts[0]).equals(Buffer.from(zipped))).toBe(true);
309
316
  expect(calls.updateVisibility).toEqual([
310
- { resourceId: "skill-123", visibility: ApiResourceVisibility.visibility_public },
317
+ { resourceId: "skill-123", visibility: ApiResourceVisibility.visibility_org },
311
318
  ]);
312
319
  expect(result.skillName).toBe("my-skill");
313
320
  });
@@ -95,8 +95,8 @@ interface SkillMetadata {
95
95
  /**
96
96
  * Declared access level, parsed from the optional `visibility:` frontmatter
97
97
  * key. `undefined` when omitted — the skill keeps whatever the server already
98
- * has (default PRIVATE for new skills). This is the declarative replacement
99
- * for the Go CLI's `--public-skills` flag.
98
+ * has (the kind's default for new skills). The level is declared with the
99
+ * skill so a push carries it, rather than a second command after.
100
100
  */
101
101
  readonly visibility?: ApiResourceVisibility;
102
102
  }
@@ -143,21 +143,29 @@ export function parseSkillMetadataContent(content: string): SkillMetadata {
143
143
  return { name, visibility: parseVisibility(parsed.visibility, SKILL_FILE) };
144
144
  }
145
145
 
146
- // Friendly frontmatter spellings → proto enum. Both the short form (`public`)
147
- // and the canonical enum name (`visibility_public`) are accepted so authors can
146
+ // Friendly frontmatter spellings → proto enum. Both the short form (`org`)
147
+ // and the canonical enum name (`visibility_org`) are accepted so authors can
148
148
  // copy either from docs or from generated YAML. `unspecified`/empty is treated
149
149
  // as "not declared" (returns undefined) so we never emit a no-op update.
150
150
  const VISIBILITY_ALIASES: ReadonlyMap<string, ApiResourceVisibility> = new Map([
151
151
  ["private", ApiResourceVisibility.visibility_private],
152
152
  ["visibility_private", ApiResourceVisibility.visibility_private],
153
- ["public", ApiResourceVisibility.visibility_public],
154
- ["visibility_public", ApiResourceVisibility.visibility_public],
155
153
  ["org", ApiResourceVisibility.visibility_org],
156
154
  ["visibility_org", ApiResourceVisibility.visibility_org],
157
155
  ["platform", ApiResourceVisibility.visibility_platform],
158
156
  ["visibility_platform", ApiResourceVisibility.visibility_platform],
159
157
  ]);
160
158
 
159
+ /**
160
+ * The spellings of the retired public level. They are refused like any
161
+ * unknown value, with one extra clause: an author who wrote `public` meant
162
+ * something the platform no longer offers, and the error should say so
163
+ * rather than read as a typo.
164
+ */
165
+ const RETIRED_VISIBILITY_SPELLINGS: ReadonlySet<string> = new Set(["public", "visibility_public"]);
166
+
167
+ const VALID_VISIBILITY_VALUES = "Valid values: private, org, platform.";
168
+
161
169
  /**
162
170
  * Map an optional `visibility` frontmatter value to the proto enum. Returns
163
171
  * undefined when omitted or explicitly unspecified; throws on an unknown value
@@ -170,13 +178,17 @@ export function parseVisibility(value: unknown, source = SKILL_FILE): ApiResourc
170
178
  }
171
179
  const key = value.trim().toLowerCase();
172
180
  if (key === "" || key === "unspecified" || key === "api_resource_visibility_unspecified") return undefined;
173
- const mapped = VISIBILITY_ALIASES.get(key);
174
- if (mapped === undefined) {
181
+ if (RETIRED_VISIBILITY_SPELLINGS.has(key)) {
175
182
  throw new UsageError(
176
- `invalid 'visibility' value '${value}' in ${source}\n\n` +
177
- "Valid values: private, public, org, platform.",
183
+ `invalid 'visibility' value '${value}' in ${source}: the public level is retired\n\n` +
184
+ "Resources are shared across organizations through plugins, or through the\n" +
185
+ `platform level between organizations linked by one identity provider. ${VALID_VISIBILITY_VALUES}`,
178
186
  );
179
187
  }
188
+ const mapped = VISIBILITY_ALIASES.get(key);
189
+ if (mapped === undefined) {
190
+ throw new UsageError(`invalid 'visibility' value '${value}' in ${source}\n\n${VALID_VISIBILITY_VALUES}`);
191
+ }
180
192
  return mapped;
181
193
  }
182
194
 
@@ -485,10 +497,9 @@ export async function pushSkillFromArchive(
485
497
  /**
486
498
  * Apply the SKILL.md-declared visibility after a push. The push RPC carries
487
499
  * artifact + provenance but not access level (visibility is metadata, not part
488
- * of the versioned content), so we follow it with a single UpdateVisibility RPC
489
- * — the declarative equivalent of the Go CLI's post-push `--public-skills`
490
- * sweep. No-ops (undefined visibility, or the server already matching) are
491
- * skipped so an unchanged skill costs nothing extra.
500
+ * of the versioned content), so we follow it with a single UpdateVisibility RPC.
501
+ * No-ops (undefined visibility, or the server already matching) are skipped
502
+ * so an unchanged skill costs nothing extra.
492
503
  */
493
504
  async function applyDeclaredVisibility(
494
505
  client: Stigmer,
@@ -1,55 +0,0 @@
1
- import { type Stigmer } from "@stigmer/sdk";
2
- import { type ResolveOfficialOptions } from "../../marketplace/official.js";
3
- import { type PreparedPluginPush } from "../../resources/plugin.js";
4
- /**
5
- * The command that ran the install, as the plugin's stored version message
6
- * names it. Two verbs share the one bootstrap function, and a platform
7
- * operator reading the row a year later must see which one wrote it.
8
- */
9
- export type InstallingVerb = "stigmer up" | "stigmer bootstrap";
10
- export interface DefaultPluginsDeps {
11
- /** The client bound to the backend being bootstrapped. */
12
- readonly stigmer: Stigmer;
13
- /** Human progress lines (stderr). */
14
- readonly info: (line: string) => void;
15
- /** Stamped into each installed plugin's version message. */
16
- readonly verb: InstallingVerb;
17
- }
18
- /** One default, ready to push: its marketplace name and the prepared archive. */
19
- export interface PreparedDefault {
20
- readonly name: string;
21
- readonly push: PreparedPluginPush;
22
- }
23
- export type DefaultPluginOutcome = {
24
- readonly name: string;
25
- readonly action: "installed";
26
- readonly digest: string;
27
- } | {
28
- readonly name: string;
29
- readonly action: "up-to-date";
30
- readonly digest: string;
31
- } | {
32
- readonly name: string;
33
- readonly action: "failed";
34
- readonly error: string;
35
- };
36
- export interface DefaultPluginsResult {
37
- readonly outcomes: readonly DefaultPluginOutcome[];
38
- /** The names that did not land, for the caller's warning. */
39
- readonly failed: readonly string[];
40
- }
41
- /**
42
- * Phase 1: every default the official marketplace names, prepared in the
43
- * marketplace's order. Throws when the tree cannot be resolved, offers less
44
- * than it lists (the library drops an entry it cannot install with a
45
- * warning, which for a user's marketplace is `show`'s business and for ours
46
- * is a broken release), or any default cannot be prepared; nothing is
47
- * returned half-ready.
48
- */
49
- export declare function prepareDefaultPlugins(official?: ResolveOfficialOptions): Promise<readonly PreparedDefault[]>;
50
- /**
51
- * Phase 2: install the prepared defaults into `org`, skipping what the
52
- * backend already holds. Best-effort per entry.
53
- */
54
- export declare function installPreparedDefaults(deps: DefaultPluginsDeps, prepared: readonly PreparedDefault[], org: string): Promise<DefaultPluginsResult>;
55
- //# sourceMappingURL=defaults.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../../src/local/plugins/defaults.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,KAAK,OAAO,EAAc,MAAM,cAAc,CAAC;AAOxD,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EACL,KAAK,kBAAkB,EAGxB,MAAM,2BAA2B,CAAC;AAEnC;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,mBAAmB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;CACnC;AAED,MAAM,MAAM,oBAAoB,GAC5B;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACnD,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,CAAC,EAAE,sBAAsB,GAChC,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAerC;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,CAAC,CA2B/B"}
@@ -1,111 +0,0 @@
1
- // The default plugins a backend is bootstrapped with: the `defaults` list of
2
- // the official marketplace, installed in order into the system org as
3
- // public. The official list is empty today (a session with no agent runs
4
- // the built-in assistant, so a fresh install needs no default content);
5
- // the mechanism stays until the bootstrap is re-scoped.
6
- //
7
- // Two phases, deliberately separate. Preparing (resolve the official tree,
8
- // which may acquire `@stigmer/plugins` at the CLI's version; walk, zip and
9
- // hash every default) needs no backend and is all-or-nothing: the catalogue
10
- // is ours and validated on CI, so a default that cannot be prepared is a
11
- // release defect, not a condition to install around. Installing pushes the
12
- // prepared archives and is best-effort per entry: one default failing to
13
- // land does not stop the next, and the caller decides how to surface the
14
- // failures. The split keeps every network read (the catalogue, the
15
- // archives) ahead of the first write to the backend (local/bootstrap.ts).
16
- //
17
- // Idempotent by the server's own identity, not a marker file: before each
18
- // push the org's plugin of that name is read back; when its digest equals the
19
- // prepared archive's, it is READY and it is public, there is nothing to do
20
- // and nothing is sent. A second `stigmer up` therefore pushes nothing, and a
21
- // `stigmer up` after an upgrade pushes exactly the entries whose content
22
- // changed. The record is the server, so any machine that reaches the same
23
- // backend sees the same answer.
24
- import { isNotFound } from "@stigmer/sdk";
25
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
26
- import { PluginState } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/status_pb";
27
- import { CliExitError } from "../../errors/cli-exit-error.js";
28
- import { ExitCode } from "../../errors/exit-codes.js";
29
- import { log } from "../../logger.js";
30
- import { VERSION } from "../../version.js";
31
- import { resolveOfficialMarketplace, } from "../../marketplace/official.js";
32
- import { readMarketplaceTree } from "../../marketplace/read.js";
33
- import { prepareEntry } from "../../marketplace/install.js";
34
- import { count, pushPrepared, } from "../../resources/plugin.js";
35
- /**
36
- * Phase 1: every default the official marketplace names, prepared in the
37
- * marketplace's order. Throws when the tree cannot be resolved, offers less
38
- * than it lists (the library drops an entry it cannot install with a
39
- * warning, which for a user's marketplace is `show`'s business and for ours
40
- * is a broken release), or any default cannot be prepared; nothing is
41
- * returned half-ready.
42
- */
43
- export async function prepareDefaultPlugins(official) {
44
- const resolved = resolveOfficialMarketplace(official);
45
- const tree = readMarketplaceTree(resolved.dir, "the official marketplace");
46
- if (tree.warnings.length > 0) {
47
- throw new CliExitError(`the official marketplace at ${resolved.dir} lists ${count(tree.warnings.length, "plugin")} this CLI cannot install`, ExitCode.General, tree.warnings.map((finding) => finding.message));
48
- }
49
- const prepared = [];
50
- for (const name of tree.marketplace.defaults) {
51
- prepared.push({ name, push: await prepareEntry(tree, name) });
52
- }
53
- return prepared;
54
- }
55
- /**
56
- * Phase 2: install the prepared defaults into `org`, skipping what the
57
- * backend already holds. Best-effort per entry.
58
- */
59
- export async function installPreparedDefaults(deps, prepared, org) {
60
- const outcomes = [];
61
- for (const { name, push } of prepared) {
62
- try {
63
- if (await isAlreadyInstalled(deps.stigmer, org, push)) {
64
- outcomes.push({ name, action: "up-to-date", digest: push.digest });
65
- continue;
66
- }
67
- deps.info(`Installing default plugin '${name}'…`);
68
- await pushPrepared(deps.stigmer, push, {
69
- org,
70
- visibility: ApiResourceVisibility.visibility_public,
71
- message: `default plugin, installed by ${deps.verb} (${VERSION})`,
72
- });
73
- outcomes.push({ name, action: "installed", digest: push.digest });
74
- }
75
- catch (error) {
76
- const message = error instanceof Error ? error.message : String(error);
77
- log.warn("default plugin install failed", { name, error: message });
78
- outcomes.push({ name, action: "failed", error: message });
79
- }
80
- }
81
- return {
82
- outcomes,
83
- failed: outcomes
84
- .filter((outcome) => outcome.action === "failed")
85
- .map((outcome) => outcome.name),
86
- };
87
- }
88
- /**
89
- * True when the org already holds this exact archive, materialised and
90
- * public. A plugin that is absent (NOT_FOUND) installs; one that is present
91
- * with another digest, still installing, failed, or not public is pushed
92
- * again, which the server treats as an upgrade or a convergence.
93
- */
94
- async function isAlreadyInstalled(stigmer, org, prepared) {
95
- let existing;
96
- try {
97
- existing = await stigmer.plugin.getByReference({
98
- org,
99
- slug: prepared.plugin.name,
100
- });
101
- }
102
- catch (error) {
103
- if (isNotFound(error))
104
- return false;
105
- throw error;
106
- }
107
- return (existing.status?.digest === prepared.digest &&
108
- existing.status.state === PluginState.READY &&
109
- existing.metadata?.visibility === ApiResourceVisibility.visibility_public);
110
- }
111
- //# sourceMappingURL=defaults.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../../src/local/plugins/defaults.ts"],"names":[],"mappings":"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"}
@@ -1,15 +0,0 @@
1
- import { type Stigmer } from "@stigmer/sdk";
2
- /** The system organization's slug, identical across editions. */
3
- export declare const SYSTEM_ORG = "stigmer";
4
- /**
5
- * Marks the system org so an operator can tell it from a user's own org in
6
- * any listing. Informational: no server or client code path reads it.
7
- */
8
- export declare const SYSTEM_ORG_LABEL = "stigmer.ai/system";
9
- export type EnsureSystemOrgOutcome = "present" | "created";
10
- /**
11
- * Make sure the system organization exists on the backend `stigmer` is bound
12
- * to. Idempotent; a lost create race counts as `present`.
13
- */
14
- export declare function ensureSystemOrg(stigmer: Stigmer): Promise<EnsureSystemOrgOutcome>;
15
- //# sourceMappingURL=system-org.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"system-org.d.ts","sourceRoot":"","sources":["../../src/local/system-org.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,cAAc,CAAC;AAE1D,iEAAiE;AACjE,eAAO,MAAM,UAAU,YAAY,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE3D;;;GAGG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,sBAAsB,CAAC,CAuBjC"}