@stigmer/cli 3.18.1-dev.20260919070736 → 3.19.1-dev.20260920100552

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 (141) hide show
  1. package/commands/apply.d.ts.map +1 -1
  2. package/commands/apply.js +45 -101
  3. package/commands/apply.js.map +1 -1
  4. package/commands/install.js +2 -1
  5. package/commands/install.js.map +1 -1
  6. package/commands/marketplace.js +3 -3
  7. package/commands/marketplace.js.map +1 -1
  8. package/commands/push.js +3 -1
  9. package/commands/push.js.map +1 -1
  10. package/commands/validate.js +3 -4
  11. package/commands/validate.js.map +1 -1
  12. package/local/bootstrap.d.ts +1 -2
  13. package/local/bootstrap.d.ts.map +1 -1
  14. package/local/bootstrap.js +13 -41
  15. package/local/bootstrap.js.map +1 -1
  16. package/local/daemon/launch.d.ts +2 -2
  17. package/local/daemon/launch.d.ts.map +1 -1
  18. package/local/daemon/launch.js +2 -2
  19. package/local/daemon/launch.js.map +1 -1
  20. package/local/plugins/defaults.js +2 -2
  21. package/local/plugins/defaults.js.map +1 -1
  22. package/marketplace/config.d.ts.map +1 -1
  23. package/marketplace/config.js +13 -9
  24. package/marketplace/config.js.map +1 -1
  25. package/marketplace/render.d.ts.map +1 -1
  26. package/marketplace/render.js +1 -7
  27. package/marketplace/render.js.map +1 -1
  28. package/package.json +7 -7
  29. package/registry/index.d.ts +2 -2
  30. package/registry/index.d.ts.map +1 -1
  31. package/registry/index.js +2 -2
  32. package/registry/index.js.map +1 -1
  33. package/registry/metadata.d.ts +9 -0
  34. package/registry/metadata.d.ts.map +1 -1
  35. package/registry/metadata.js +14 -5
  36. package/registry/metadata.js.map +1 -1
  37. package/registry/registry.d.ts +9 -0
  38. package/registry/registry.d.ts.map +1 -1
  39. package/registry/registry.js +16 -1
  40. package/registry/registry.js.map +1 -1
  41. package/registry/verb-support.d.ts.map +1 -1
  42. package/registry/verb-support.js +0 -13
  43. package/registry/verb-support.js.map +1 -1
  44. package/resources/apply/apply.d.ts +11 -12
  45. package/resources/apply/apply.d.ts.map +1 -1
  46. package/resources/apply/apply.js +12 -21
  47. package/resources/apply/apply.js.map +1 -1
  48. package/resources/apply/handlers.js +2 -2
  49. package/resources/apply/handlers.js.map +1 -1
  50. package/resources/connect/oauth.d.ts.map +1 -1
  51. package/resources/connect/oauth.js +4 -1
  52. package/resources/connect/oauth.js.map +1 -1
  53. package/resources/delete.d.ts.map +1 -1
  54. package/resources/delete.js +0 -1
  55. package/resources/delete.js.map +1 -1
  56. package/resources/get-bindings.d.ts.map +1 -1
  57. package/resources/get-bindings.js +0 -5
  58. package/resources/get-bindings.js.map +1 -1
  59. package/resources/list.d.ts.map +1 -1
  60. package/resources/list.js +0 -1
  61. package/resources/list.js.map +1 -1
  62. package/resources/plugin.d.ts +27 -0
  63. package/resources/plugin.d.ts.map +1 -1
  64. package/resources/plugin.js +113 -8
  65. package/resources/plugin.js.map +1 -1
  66. package/resources/validate.d.ts.map +1 -1
  67. package/resources/validate.js +0 -2
  68. package/resources/validate.js.map +1 -1
  69. package/src/commands/apply.ts +40 -150
  70. package/src/commands/install.test.ts +20 -21
  71. package/src/commands/install.ts +2 -1
  72. package/src/commands/marketplace.test.ts +10 -19
  73. package/src/commands/marketplace.ts +3 -3
  74. package/src/commands/push.ts +6 -1
  75. package/src/commands/validate.ts +2 -4
  76. package/src/local/bootstrap.test.ts +15 -59
  77. package/src/local/bootstrap.ts +12 -53
  78. package/src/local/daemon/launch.ts +4 -4
  79. package/src/local/plugins/defaults.test.ts +28 -28
  80. package/src/local/plugins/defaults.ts +2 -2
  81. package/src/marketplace/__fixtures__/cursor-marketplace.ts +16 -12
  82. package/src/marketplace/config.test.ts +12 -28
  83. package/src/marketplace/config.ts +13 -9
  84. package/src/marketplace/install.test.ts +19 -19
  85. package/src/marketplace/read.test.ts +6 -6
  86. package/src/marketplace/render.ts +1 -10
  87. package/src/registry/index.ts +7 -1
  88. package/src/registry/metadata.ts +15 -5
  89. package/src/registry/registry.test.ts +1 -7
  90. package/src/registry/registry.ts +17 -1
  91. package/src/registry/verb-support.ts +0 -13
  92. package/src/resources/apply/apply.integration.test.ts +15 -3
  93. package/src/resources/apply/apply.ts +15 -26
  94. package/src/resources/apply/handlers.ts +2 -2
  95. package/src/resources/connect/oauth.test.ts +2 -2
  96. package/src/resources/connect/oauth.ts +4 -1
  97. package/src/resources/delete.ts +0 -1
  98. package/src/resources/get-bindings.ts +0 -5
  99. package/src/resources/list.ts +0 -1
  100. package/src/resources/plugin.test.ts +119 -2
  101. package/src/resources/plugin.ts +140 -8
  102. package/src/resources/validate.test.ts +0 -1
  103. package/src/resources/validate.ts +0 -2
  104. package/local/seedpack-retire.d.ts +0 -63
  105. package/local/seedpack-retire.d.ts.map +0 -1
  106. package/local/seedpack-retire.js +0 -361
  107. package/local/seedpack-retire.js.map +0 -1
  108. package/resources/apply/declarative.d.ts +0 -106
  109. package/resources/apply/declarative.d.ts.map +0 -1
  110. package/resources/apply/declarative.js +0 -397
  111. package/resources/apply/declarative.js.map +0 -1
  112. package/resources/apply/synth/project-track.d.ts +0 -21
  113. package/resources/apply/synth/project-track.d.ts.map +0 -1
  114. package/resources/apply/synth/project-track.js +0 -102
  115. package/resources/apply/synth/project-track.js.map +0 -1
  116. package/resources/apply/synth/reader.d.ts +0 -18
  117. package/resources/apply/synth/reader.d.ts.map +0 -1
  118. package/resources/apply/synth/reader.js +0 -55
  119. package/resources/apply/synth/reader.js.map +0 -1
  120. package/resources/apply/synth/runtime.d.ts +0 -8
  121. package/resources/apply/synth/runtime.d.ts.map +0 -1
  122. package/resources/apply/synth/runtime.js +0 -32
  123. package/resources/apply/synth/runtime.js.map +0 -1
  124. package/resources/apply/synth/synthesize.d.ts +0 -43
  125. package/resources/apply/synth/synthesize.d.ts.map +0 -1
  126. package/resources/apply/synth/synthesize.js +0 -115
  127. package/resources/apply/synth/synthesize.js.map +0 -1
  128. package/src/local/seedpack-retire.test.ts +0 -499
  129. package/src/local/seedpack-retire.ts +0 -474
  130. package/src/resources/apply/declarative.integration.test.ts +0 -204
  131. package/src/resources/apply/declarative.test.ts +0 -132
  132. package/src/resources/apply/declarative.ts +0 -491
  133. package/src/resources/apply/synth/capstone.integration.test.ts +0 -134
  134. package/src/resources/apply/synth/project-track.integration.test.ts +0 -194
  135. package/src/resources/apply/synth/project-track.ts +0 -126
  136. package/src/resources/apply/synth/reader.test.ts +0 -90
  137. package/src/resources/apply/synth/reader.ts +0 -68
  138. package/src/resources/apply/synth/runtime.test.ts +0 -29
  139. package/src/resources/apply/synth/runtime.ts +0 -39
  140. package/src/resources/apply/synth/synthesize.test.ts +0 -109
  141. package/src/resources/apply/synth/synthesize.ts +0 -161
@@ -1,9 +1,7 @@
1
- // Pins the local bootstrap's shape: prepare, then run, then retire, over one
2
- // client; a preparation failure skips the run and the retire together, and a
3
- // run that cannot start skips the retire (nothing is deleted before its
4
- // replacement is in place); a default that failed to land is one warning
5
- // naming `stigmer bootstrap` and holds the retire back with a line saying so;
6
- // a retire failure is contained by its own warning; nothing throws past the
1
+ // Pins the local bootstrap's shape: prepare, then run, over one client; a
2
+ // preparation failure skips the run with one warning naming the retry; a
3
+ // run that cannot start is one warning; a default that failed to land is one
4
+ // warning per default naming `stigmer bootstrap`; nothing throws past the
7
5
  // bootstrap.
8
6
 
9
7
  import { describe, expect, it } from "vitest";
@@ -24,9 +22,9 @@ function converged(): BootstrapResult {
24
22
  }
25
23
 
26
24
  describe("bootstrapLocalBackend", () => {
27
- it("prepares, runs, then retires, over the same client and home", async () => {
25
+ it("prepares, then runs, over the same client", async () => {
28
26
  const order: string[] = [];
29
- await bootstrapLocalBackend("/home/x", {
27
+ await bootstrapLocalBackend({
30
28
  client,
31
29
  say: () => {},
32
30
  prepare: async () => {
@@ -39,20 +37,14 @@ describe("bootstrapLocalBackend", () => {
39
37
  order.push("run");
40
38
  return converged();
41
39
  },
42
- retire: async (s, home) => {
43
- expect(s).toBe(stigmer);
44
- expect(home).toBe("/home/x");
45
- order.push("retire");
46
- },
47
40
  });
48
- expect(order).toEqual(["prepare", "run", "retire"]);
41
+ expect(order).toEqual(["prepare", "run"]);
49
42
  });
50
43
 
51
- it("skips the run and the retire when preparation fails, with one warning", async () => {
44
+ it("skips the run when preparation fails, with one warning", async () => {
52
45
  const said: string[] = [];
53
- let retired = 0;
54
46
  let ran = 0;
55
- await bootstrapLocalBackend("/home/x", {
47
+ await bootstrapLocalBackend({
56
48
  client,
57
49
  say: (line) => said.push(line),
58
50
  prepare: async () => {
@@ -62,41 +54,31 @@ describe("bootstrapLocalBackend", () => {
62
54
  ran += 1;
63
55
  return converged();
64
56
  },
65
- retire: async () => {
66
- retired += 1;
67
- },
68
57
  });
69
58
  expect(ran).toBe(0);
70
- expect(retired).toBe(0);
71
59
  expect(said).toEqual([
72
- "Warning: could not prepare the default plugins, so the local backend was not bootstrapped and nothing was retired. Run 'stigmer up' again to retry.",
60
+ "Warning: could not prepare the default plugins, so the local backend was not bootstrapped. Run 'stigmer up' again to retry.",
73
61
  ]);
74
62
  });
75
63
 
76
- it("skips the retire when the run cannot even start, with one warning naming the retry", async () => {
64
+ it("contains a run that cannot start in one warning naming the retry", async () => {
77
65
  const said: string[] = [];
78
- let retired = 0;
79
- await bootstrapLocalBackend("/home/x", {
66
+ await bootstrapLocalBackend({
80
67
  client,
81
68
  say: (line) => said.push(line),
82
69
  prepare: async () => prepared,
83
70
  run: async () => {
84
71
  throw new Error("backend unreachable");
85
72
  },
86
- retire: async () => {
87
- retired += 1;
88
- },
89
73
  });
90
- expect(retired).toBe(0);
91
74
  expect(said).toEqual([
92
- "Warning: failed to bootstrap the local backend, so nothing was retired. Run 'stigmer up' again to retry.",
75
+ "Warning: failed to bootstrap the local backend. Run 'stigmer up' again to retry.",
93
76
  ]);
94
77
  });
95
78
 
96
- it("holds the retire back when a default did not land, naming the default and saying why", async () => {
79
+ it("names each default that did not land and the verb that retries it", async () => {
97
80
  const said: string[] = [];
98
- let retired = 0;
99
- await bootstrapLocalBackend("/home/x", {
81
+ await bootstrapLocalBackend({
100
82
  client,
101
83
  say: (line) => said.push(line),
102
84
  prepare: async () => prepared,
@@ -109,35 +91,9 @@ describe("bootstrapLocalBackend", () => {
109
91
  failed: ["assistant"],
110
92
  },
111
93
  }),
112
- retire: async () => {
113
- retired += 1;
114
- },
115
94
  });
116
- expect(retired).toBe(0);
117
95
  expect(said).toEqual([
118
96
  "Warning: failed to install default plugin 'assistant'. Run 'stigmer bootstrap' to retry.",
119
- "Nothing was retired: the resources an older release installed stay until every default plugin is in place.",
120
- ]);
121
- });
122
-
123
- it("contains a retire that threw in one warning, after the run succeeded", async () => {
124
- const said: string[] = [];
125
- let ran = 0;
126
- await bootstrapLocalBackend("/home/x", {
127
- client,
128
- say: (line) => said.push(line),
129
- prepare: async () => prepared,
130
- run: async () => {
131
- ran += 1;
132
- return converged();
133
- },
134
- retire: async () => {
135
- throw new Error("project read failed");
136
- },
137
- });
138
- expect(ran).toBe(1);
139
- expect(said).toEqual([
140
- "Warning: failed to retire the resources an older release installed. Run 'stigmer up' again to retry.",
141
97
  ]);
142
98
  });
143
99
  });
@@ -6,29 +6,20 @@
6
6
  // lane. One definition, so a laptop, a container and the hosted platform
7
7
  // cannot drift in what "ready" means.
8
8
  //
9
- // Two phases, and the order matters (a third, `up`'s retire, follows them):
9
+ // Two phases, and the order matters:
10
10
  //
11
11
  // 1. prepare: resolve the official marketplace (acquiring `@stigmer/plugins`
12
12
  // at the CLI's version on a release's first run) and prepare every
13
13
  // default. No backend is touched.
14
14
  // 2. run: ensure the system org, then push the prepared defaults, each
15
- // skipped when the backend already holds it at the same digest.
15
+ // skipped when the backend already holds it at the same digest. The
16
+ // push adopts a system-content row it replaces in place (a default
17
+ // agent an earlier release seeded keeps its id, its instances and every
18
+ // conversation bound to them), so nothing here ever deletes a row.
16
19
  //
17
- // `stigmer up` adds a third phase after them: retiring the seedpack an
18
- // older release installed. That step deletes rows, so it runs only once
19
- // every default has LANDED, not merely been prepared: the plugin push adopts
20
- // the system-content rows it replaces in place (the seedpack's `assistant`
21
- // keeps its id, its instances and every conversation bound to them), and a
22
- // row that is now a plugin's member is never a candidate for the retire. A
23
- // user who upgrades offline and cannot fetch the new catalogue, or whose
24
- // backend refused a default, keeps the old rows for another day instead of
25
- // losing them and getting nothing back. The retire lives here and not in
26
- // `stigmer bootstrap` on purpose: a verb a scheduled lane runs against the
27
- // platform must never be able to delete rows.
28
- //
29
- // Inside `up` every phase is best-effort with its own warning naming the
30
- // retry, because the stack is already serving by the time any of them runs
31
- // and a seeding failure must never tear it down. Both `up` shapes (detached
20
+ // Inside `up` both phases are best-effort with their own warning naming the
21
+ // retry, because the stack is already serving by the time either runs and a
22
+ // seeding failure must never tear it down. Both `up` shapes (detached
32
23
  // and foreground) call this one function, and both seed the local server
33
24
  // `up` just started, never the active backend context: a CLI pointed at
34
25
  // cloud still gets its local stack seeded, and never has system resources
@@ -44,7 +35,6 @@ import type {
44
35
  InstallingVerb,
45
36
  PreparedDefault,
46
37
  } from "./plugins/defaults.js";
47
- import { dataDir } from "./paths.js";
48
38
  import { type EnsureSystemOrgOutcome, SYSTEM_ORG } from "./system-org.js";
49
39
 
50
40
  export type Say = (line: string) => void;
@@ -116,7 +106,6 @@ export interface BootstrapDeps {
116
106
  readonly client?: BackendClient;
117
107
  readonly say?: Say;
118
108
  readonly prepare?: () => Promise<PreparedBootstrap>;
119
- readonly retire?: (stigmer: Stigmer, home: string, say: Say) => Promise<void>;
120
109
  readonly run?: (
121
110
  stigmer: Stigmer,
122
111
  prepared: PreparedBootstrap,
@@ -130,7 +119,6 @@ const sayToStderr: Say = (line) => {
130
119
 
131
120
  /** What `stigmer up` does once the local server answers. Never throws past its own warnings. */
132
121
  export async function bootstrapLocalBackend(
133
- home: string,
134
122
  deps: BootstrapDeps = {},
135
123
  ): Promise<void> {
136
124
  const client = deps.client ?? (await freshLocalClient());
@@ -142,7 +130,7 @@ export async function bootstrapLocalBackend(
142
130
  } catch (err) {
143
131
  log.warn("bootstrap preparation failed", { error: String(err) });
144
132
  say(
145
- "Warning: could not prepare the default plugins, so the local backend was not bootstrapped and nothing was retired. Run 'stigmer up' again to retry.",
133
+ "Warning: could not prepare the default plugins, so the local backend was not bootstrapped. Run 'stigmer up' again to retry.",
146
134
  );
147
135
  return;
148
136
  }
@@ -157,7 +145,7 @@ export async function bootstrapLocalBackend(
157
145
  } catch (err) {
158
146
  log.warn("bootstrap failed", { error: String(err) });
159
147
  say(
160
- "Warning: failed to bootstrap the local backend, so nothing was retired. Run 'stigmer up' again to retry.",
148
+ "Warning: failed to bootstrap the local backend. Run 'stigmer up' again to retry.",
161
149
  );
162
150
  return;
163
151
  }
@@ -165,24 +153,9 @@ export async function bootstrapLocalBackend(
165
153
  org: result.org,
166
154
  outcomes: result.plugins.outcomes,
167
155
  });
168
- if (result.plugins.failed.length > 0) {
169
- for (const name of result.plugins.failed) {
170
- say(
171
- `Warning: failed to install default plugin '${name}'. Run 'stigmer bootstrap' to retry.`,
172
- );
173
- }
174
- say(
175
- "Nothing was retired: the resources an older release installed stay until every default plugin is in place.",
176
- );
177
- return;
178
- }
179
-
180
- try {
181
- await (deps.retire ?? retireSeedpack)(client.stigmer, home, say);
182
- } catch (err) {
183
- log.warn("seedpack retire failed", { error: String(err) });
156
+ for (const name of result.plugins.failed) {
184
157
  say(
185
- "Warning: failed to retire the resources an older release installed. Run 'stigmer up' again to retry.",
158
+ `Warning: failed to install default plugin '${name}'. Run 'stigmer bootstrap' to retry.`,
186
159
  );
187
160
  }
188
161
  }
@@ -197,20 +170,6 @@ function runLocalBootstrap(
197
170
  return runBootstrap(stigmer, prepared, say, "stigmer up");
198
171
  }
199
172
 
200
- // The retire step in the shape the seam expects; the module is loaded only
201
- // inside `up`, the one place a retire may run.
202
- async function retireSeedpack(
203
- stigmer: Stigmer,
204
- home: string,
205
- say: Say,
206
- ): Promise<void> {
207
- const { retireSeedpack: retire, renderRetireReport } = await import(
208
- "./seedpack-retire.js"
209
- );
210
- const result = await retire(stigmer, { markerDir: dataDir(home) });
211
- renderRetireReport(result, say);
212
- }
213
-
214
173
  async function freshLocalClient(): Promise<BackendClient> {
215
174
  const [{ createBackendClient }, { getDefault }] = await Promise.all([
216
175
  import("../client/index.js"),
@@ -72,7 +72,7 @@ export async function up(options: UpOptions = {}, home: string = homedir()): Pro
72
72
 
73
73
  await waitForTcp({ port: SERVER_PORT, timeoutMs: READY_TIMEOUT_MS, label: "stigmer-server" });
74
74
 
75
- await bootstrapLocalBackend(home);
75
+ await bootstrapLocalBackend();
76
76
 
77
77
  saveStartupConfig(data, buildStartupConfig(data, logs, temporalAddress, daemonPid, options));
78
78
  }
@@ -83,8 +83,8 @@ export interface UpForegroundDeps {
83
83
  runDaemon?: (deps: InternalDaemonDeps) => Promise<number>;
84
84
  /** Resolves when the stack should shut down (default: the first SIGTERM/SIGINT). */
85
85
  waitForShutdown?: () => Promise<void>;
86
- /** Post-readiness bootstrap (default: `bootstrapLocalBackend`: prepare the defaults, retire an older release's seedpack, ensure the org, install). */
87
- bootstrap?: (home: string) => Promise<void>;
86
+ /** Post-readiness bootstrap (default: `bootstrapLocalBackend`: prepare the defaults, ensure the org, install). */
87
+ bootstrap?: () => Promise<void>;
88
88
  /** Receives each line the server and runner write (default: a `[component]`-prefixed stdout mirror). */
89
89
  mirror?: OutputMirror;
90
90
  /** Invoked once the stack is serving and seeded — the moment a detached `up` would have returned. */
@@ -118,7 +118,7 @@ export async function upForeground(
118
118
  host: new NodeProcessHost({ mirror: deps.mirror ?? mirrorToStdout }),
119
119
  waitForShutdown: deps.waitForShutdown ?? waitForShutdownSignal,
120
120
  onStarted: async () => {
121
- await bootstrap(home);
121
+ await bootstrap();
122
122
  saveStartupConfig(data, buildStartupConfig(data, logs, temporalAddress, process.pid, options));
123
123
  await deps.onReady?.();
124
124
  },
@@ -49,8 +49,8 @@ let pushes: PushPluginRequest[];
49
49
  let refusing: Set<string>;
50
50
 
51
51
  let official: string;
52
- let thermosDigest: string;
53
- let githubDigest: string;
52
+ let warmerDigest: string;
53
+ let codeforgeDigest: string;
54
54
 
55
55
  function pluginRow(
56
56
  slug: string,
@@ -75,15 +75,15 @@ beforeAll(async () => {
75
75
  JSON.stringify({
76
76
  name: "stigmer",
77
77
  plugins: [
78
- { name: "thermos", source: "./thermos" },
79
- { name: "github", source: "./third_party/github" },
78
+ { name: "warmer", source: "./warmer" },
79
+ { name: "codeforge", source: "./third_party/codeforge" },
80
80
  ],
81
- defaults: ["thermos", "github"],
81
+ defaults: ["warmer", "codeforge"],
82
82
  }),
83
83
  );
84
- thermosDigest = (await preparePluginPush(join(official, "thermos"))).digest;
85
- githubDigest = (
86
- await preparePluginPush(join(official, "third_party", "github"))
84
+ warmerDigest = (await preparePluginPush(join(official, "warmer"))).digest;
85
+ codeforgeDigest = (
86
+ await preparePluginPush(join(official, "third_party", "codeforge"))
87
87
  ).digest;
88
88
 
89
89
  const routes = (router: ConnectRouter) => {
@@ -92,7 +92,7 @@ beforeAll(async () => {
92
92
  pushes.push(req);
93
93
  // The archive names the plugin: the backend knows the two trees by their digests.
94
94
  const slug =
95
- digestOf(req.artifact) === thermosDigest ? "thermos" : "github";
95
+ digestOf(req.artifact) === warmerDigest ? "warmer" : "codeforge";
96
96
  if (refusing.has(slug))
97
97
  throw new ConnectError(`refused ${slug}`, Code.FailedPrecondition);
98
98
  const row = pluginRow(
@@ -159,8 +159,8 @@ describe("prepareDefaultPlugins", () => {
159
159
  it("prepares every default in the marketplace's order without touching the backend", async () => {
160
160
  const prepared = await prepareDefaultPlugins({ repoDir: () => official });
161
161
  expect(prepared.map((entry) => [entry.name, entry.push.digest])).toEqual([
162
- ["thermos", thermosDigest],
163
- ["github", githubDigest],
162
+ ["warmer", warmerDigest],
163
+ ["codeforge", codeforgeDigest],
164
164
  ]);
165
165
  expect(pushes).toHaveLength(0);
166
166
  });
@@ -189,8 +189,8 @@ describe("installPreparedDefaults", () => {
189
189
  it("installs every default in order, public, with the invoking verb in the message, on a fresh backend", async () => {
190
190
  const result = await run();
191
191
  expect(result.outcomes.map((o) => [o.name, o.action])).toEqual([
192
- ["thermos", "installed"],
193
- ["github", "installed"],
192
+ ["warmer", "installed"],
193
+ ["codeforge", "installed"],
194
194
  ]);
195
195
  expect(result.failed).toEqual([]);
196
196
  expect(pushes.map((p) => p.org)).toEqual(["stigmer", "stigmer"]);
@@ -202,13 +202,13 @@ describe("installPreparedDefaults", () => {
202
202
  expect(pushes[0]?.message).toMatch(
203
203
  /^default plugin, installed by stigmer bootstrap \(/,
204
204
  );
205
- expect(digestOf(pushes[0]!.artifact)).toBe(thermosDigest);
206
- expect(digestOf(pushes[1]!.artifact)).toBe(githubDigest);
205
+ expect(digestOf(pushes[0]!.artifact)).toBe(warmerDigest);
206
+ expect(digestOf(pushes[1]!.artifact)).toBe(codeforgeDigest);
207
207
  });
208
208
 
209
209
  it("pushes nothing when every default is already held at the same digest, READY and public", async () => {
210
- held.set("thermos", pluginRow("thermos", thermosDigest));
211
- held.set("github", pluginRow("github", githubDigest));
210
+ held.set("warmer", pluginRow("warmer", warmerDigest));
211
+ held.set("codeforge", pluginRow("codeforge", codeforgeDigest));
212
212
  const result = await run();
213
213
  expect(result.outcomes.map((o) => o.action)).toEqual([
214
214
  "up-to-date",
@@ -218,8 +218,8 @@ describe("installPreparedDefaults", () => {
218
218
  });
219
219
 
220
220
  it("pushes again on a changed digest, a non-READY state, or a non-public visibility", async () => {
221
- held.set("thermos", pluginRow("thermos", "f".repeat(64)));
222
- held.set("github", pluginRow("github", githubDigest, PluginState.FAILED));
221
+ held.set("warmer", pluginRow("warmer", "f".repeat(64)));
222
+ held.set("codeforge", pluginRow("codeforge", codeforgeDigest, PluginState.FAILED));
223
223
  expect((await run()).outcomes.map((o) => o.action)).toEqual([
224
224
  "installed",
225
225
  "installed",
@@ -228,15 +228,15 @@ describe("installPreparedDefaults", () => {
228
228
 
229
229
  pushes = [];
230
230
  held.set(
231
- "thermos",
231
+ "warmer",
232
232
  pluginRow(
233
- "thermos",
234
- thermosDigest,
233
+ "warmer",
234
+ warmerDigest,
235
235
  PluginState.READY,
236
236
  ApiResourceVisibility.visibility_private,
237
237
  ),
238
238
  );
239
- held.set("github", pluginRow("github", githubDigest));
239
+ held.set("codeforge", pluginRow("codeforge", codeforgeDigest));
240
240
  expect((await run()).outcomes.map((o) => o.action)).toEqual([
241
241
  "installed",
242
242
  "up-to-date",
@@ -256,16 +256,16 @@ describe("installPreparedDefaults", () => {
256
256
  });
257
257
 
258
258
  it("one default failing does not stop the next, and the failure is named", async () => {
259
- refusing.add("thermos");
259
+ refusing.add("warmer");
260
260
  const result = await run();
261
261
  expect(result.outcomes.map((o) => [o.name, o.action])).toEqual([
262
- ["thermos", "failed"],
263
- ["github", "installed"],
262
+ ["warmer", "failed"],
263
+ ["codeforge", "installed"],
264
264
  ]);
265
- expect(result.failed).toEqual(["thermos"]);
265
+ expect(result.failed).toEqual(["warmer"]);
266
266
  const failed = result.outcomes[0];
267
267
  expect(failed?.action === "failed" && failed.error).toMatch(
268
- /refused thermos/,
268
+ /refused warmer/,
269
269
  );
270
270
  });
271
271
  });
@@ -10,8 +10,8 @@
10
10
  // release defect, not a condition to install around. Installing pushes the
11
11
  // prepared archives and is best-effort per entry: one default failing to
12
12
  // land does not stop the next, and the caller decides how to surface the
13
- // failures. The split lets `stigmer up` hold every replacement in hand
14
- // before it retires what the replacement supersedes (local/bootstrap.ts).
13
+ // failures. The split keeps every network read (the catalogue, the
14
+ // archives) ahead of the first write to the backend (local/bootstrap.ts).
15
15
  //
16
16
  // Idempotent by the server's own identity, not a marker file: before each
17
17
  // push the org's plugin of that name is read back; when its digest equals the
@@ -4,7 +4,11 @@
4
4
  // is missing, so every suite sees an offered list and a dropped entry. It
5
5
  // names itself `acme-plugins`: the vendors' own names are built into every
6
6
  // client, so a fixture under one of them would meet the reserved-name
7
- // refusal instead of being added.
7
+ // refusal instead of being added. Its plugins carry names no real catalogue
8
+ // offers (`warmer`, `codeforge`): a bare-name install searches the official
9
+ // catalogue too, which in a checkout is the live `plugins/` tree, and a
10
+ // fixture named after a real plugin would collide with it as the catalogue
11
+ // grows.
8
12
 
9
13
  import { mkdirSync, writeFileSync } from "node:fs";
10
14
  import { dirname, join } from "node:path";
@@ -24,41 +28,41 @@ export function writeCursorMarketplace(root: string): void {
24
28
  metadata: { description: "A test catalogue" },
25
29
  plugins: [
26
30
  {
27
- name: "thermos",
28
- source: "thermos",
31
+ name: "warmer",
32
+ source: "warmer",
29
33
  description: "Keeps things warm.",
30
34
  },
31
- { name: "github", source: "third_party/github" },
35
+ { name: "codeforge", source: "third_party/codeforge" },
32
36
  { name: "ghost", source: "ghost", description: "Listed, not present." },
33
37
  ],
34
38
  }),
35
39
  );
36
40
  write(
37
- "thermos/.cursor-plugin/plugin.json",
41
+ "warmer/.cursor-plugin/plugin.json",
38
42
  JSON.stringify({
39
- name: "thermos",
43
+ name: "warmer",
40
44
  version: "1.0.0",
41
- description: "The thermos plugin.",
45
+ description: "The warmer plugin.",
42
46
  skills: "./skills/",
43
47
  }),
44
48
  );
45
49
  write(
46
- "thermos/skills/warm/SKILL.md",
50
+ "warmer/skills/warm/SKILL.md",
47
51
  "---\nname: warm\ndescription: Warms.\n---\nKeep it warm.\n",
48
52
  );
49
53
  write(
50
- "third_party/github/.cursor-plugin/plugin.json",
54
+ "third_party/codeforge/.cursor-plugin/plugin.json",
51
55
  JSON.stringify({
52
- name: "github",
56
+ name: "codeforge",
53
57
  version: "2.1.0",
54
58
  mcpServers: "./mcp.json",
55
59
  }),
56
60
  );
57
61
  write(
58
- "third_party/github/mcp.json",
62
+ "third_party/codeforge/mcp.json",
59
63
  JSON.stringify({
60
64
  mcpServers: {
61
- github: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
65
+ codeforge: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
62
66
  },
63
67
  }),
64
68
  );
@@ -48,26 +48,18 @@ function writeConfig(yaml: string): void {
48
48
  writeFileSync(configPath(), yaml);
49
49
  }
50
50
 
51
- const BUILT_IN_NAMES = [
52
- OFFICIAL_MARKETPLACE_NAME,
53
- "cursor-plugins",
54
- "claude-code-plugins",
55
- "codex-plugins",
56
- ];
51
+ const BUILT_IN_NAMES = [OFFICIAL_MARKETPLACE_NAME];
57
52
 
58
53
  describe("listMarketplaces", () => {
59
- it("is the built-in sources alone on a fresh machine, the official one first", () => {
54
+ it("is the built-in source alone on a fresh machine, the official catalogue", () => {
60
55
  const listing = listMarketplaces();
61
56
  expect(listing.known.map((m) => m.name)).toEqual(BUILT_IN_NAMES);
62
57
  expect(listing.known[0]?.source).toEqual({ type: "official" });
63
- expect(listing.known[1]?.source).toEqual({
64
- type: "github",
65
- repo: "cursor/plugins",
66
- });
58
+ expect(listing.known).toHaveLength(1);
67
59
  expect(listing.unreadable).toEqual([]);
68
60
  });
69
61
 
70
- it("lists configured entries after the built-ins, in file order, names the unreadable ones, and lets a built-in win over a same-named entry", () => {
62
+ it("lists configured entries after the built-in, in file order, names the unreadable ones, and lets the built-in win over a same-named entry", () => {
71
63
  writeConfig(
72
64
  [
73
65
  "backend:",
@@ -76,8 +68,8 @@ describe("listMarketplaces", () => {
76
68
  " acme:",
77
69
  " type: github",
78
70
  " repo: acme/plugins",
79
- // Written before the vendors were built in: the built-in wins, the entry stays in the file.
80
- " cursor-plugins:",
71
+ // A hand edit under the built-in's name: the built-in wins, the entry stays in the file.
72
+ " stigmer:",
81
73
  " type: github",
82
74
  " repo: someone/fork",
83
75
  " pinned:",
@@ -102,23 +94,20 @@ describe("listMarketplaces", () => {
102
94
  "pinned",
103
95
  "team",
104
96
  ]);
105
- expect(listing.known[4]?.source).toEqual({
97
+ expect(listing.known[0]?.source).toEqual({ type: "official" });
98
+ expect(listing.known[1]?.source).toEqual({
106
99
  type: "github",
107
100
  repo: "acme/plugins",
108
101
  });
109
- expect(listing.known[5]?.source).toEqual({
102
+ expect(listing.known[2]?.source).toEqual({
110
103
  type: "github",
111
104
  repo: "anthropics/claude-code",
112
105
  ref: "v1.2.3",
113
106
  });
114
- expect(listing.known[6]?.source).toEqual({
107
+ expect(listing.known[3]?.source).toEqual({
115
108
  type: "local",
116
109
  path: "/srv/plugins",
117
110
  });
118
- expect(listing.known[1]?.source).toEqual({
119
- type: "github",
120
- repo: "cursor/plugins",
121
- });
122
111
  expect(listing.unreadable).toEqual([
123
112
  {
124
113
  name: "broken",
@@ -173,13 +162,11 @@ describe("addMarketplace and removeMarketplace", () => {
173
162
  });
174
163
  });
175
164
 
176
- it("refuses every built-in name, a duplicate, an unknown removal, and a built-in removal", () => {
165
+ it("refuses the built-in name, a duplicate, an unknown removal, and the built-in's removal; a vendor's name is free", () => {
177
166
  expect(() =>
178
167
  addMarketplace("stigmer", { type: "local", path: "/x" }),
179
168
  ).toThrow(/'stigmer' is a built-in source and cannot be added or replaced/);
180
- expect(() =>
181
- addMarketplace("cursor-plugins", { type: "local", path: "/x" }),
182
- ).toThrow(/'cursor-plugins' is a built-in source/);
169
+ addMarketplace("cursor-plugins", { type: "github", repo: "cursor/plugins" });
183
170
  addMarketplace("team", { type: "local", path: "/srv/plugins" });
184
171
  expect(() =>
185
172
  addMarketplace("team", { type: "local", path: "/other" }),
@@ -190,9 +177,6 @@ describe("addMarketplace and removeMarketplace", () => {
190
177
  expect(() => removeMarketplace("stigmer")).toThrow(
191
178
  /'stigmer' is a built-in source and cannot be removed/,
192
179
  );
193
- expect(() => removeMarketplace("codex-plugins")).toThrow(
194
- /'codex-plugins' is a built-in source and cannot be removed/,
195
- );
196
180
  });
197
181
 
198
182
  it("refuses under --standalone instead of writing a default file over the user's", () => {
@@ -1,14 +1,17 @@
1
- // The sources a CLI knows: the built-in ones (the official catalogue and the
2
- // three vendors' public ones, the same list the console ships), and the ones
3
- // a user added under the `marketplaces` key of ~/.stigmer/config.yaml.
1
+ // The sources a CLI knows: the built-in one (the official catalogue, the same
2
+ // list the console ships) and the ones a user added under the `marketplaces`
3
+ // key of ~/.stigmer/config.yaml.
4
4
  //
5
5
  // A source is client-side configuration, never a server resource: the
6
6
  // server only ever sees the plugin archive a client pushes. That is why the
7
7
  // list lives beside the named backends and the context, and why `stigmer
8
- // marketplace` is a noun group like `config` and `auth`. The built-ins are
9
- // code, not config: they cannot be added over or removed, so `stigmer
10
- // install cursor-plugins/thermos` works on a fresh machine and the CLI's
11
- // list never drifts from the console's.
8
+ // marketplace` is a noun group like `config` and `auth`. The built-in is
9
+ // code, not config: it cannot be added over or removed, so `stigmer install
10
+ // assistant` works on a fresh machine and the CLI's list never drifts from
11
+ // the console's. A vendor's public repository is a source the user adds by
12
+ // name (`stigmer marketplace add cursor/plugins`), never one on offer by
13
+ // default: the official catalogue already carries what Stigmer can offer of
14
+ // it, curated (`plugins/README.md`).
12
15
  //
13
16
  // The config module keeps the on-disk entry loose (`MarketplaceEntryConfig`,
14
17
  // every field optional) so a hand-edited entry this CLI cannot read survives
@@ -82,8 +85,9 @@ export function listMarketplaces(
82
85
  const known: KnownMarketplace[] = [...BUILT_IN_SOURCES];
83
86
  const unreadable: UnreadableMarketplace[] = [];
84
87
  for (const [name, entry] of Object.entries(config.marketplaces ?? {})) {
85
- // A config written before the vendors were built in may name one; the
86
- // built-in wins and the entry is left in the file untouched.
88
+ // A config may name the built-in (a hand edit, or a file written by a
89
+ // CLI whose built-ins differed); the built-in wins and the entry is left
90
+ // in the file untouched.
87
91
  if (isBuiltInMarketplaceName(name)) continue;
88
92
  const narrowed = narrowEntry(entry);
89
93
  if (narrowed.ok) known.push({ name, source: narrowed.source });