@stigmer/cli 3.15.3-dev.20260916211208 → 3.16.1-dev.20260917224426

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 (171) hide show
  1. package/commands/agent-exec-flags.d.ts +2 -5
  2. package/commands/agent-exec-flags.d.ts.map +1 -1
  3. package/commands/agent-exec-flags.js +6 -13
  4. package/commands/agent-exec-flags.js.map +1 -1
  5. package/commands/connect.js +1 -1
  6. package/commands/connect.js.map +1 -1
  7. package/commands/install.d.ts +3 -0
  8. package/commands/install.d.ts.map +1 -0
  9. package/commands/install.js +68 -0
  10. package/commands/install.js.map +1 -0
  11. package/commands/marketplace.d.ts +3 -0
  12. package/commands/marketplace.d.ts.map +1 -0
  13. package/commands/marketplace.js +94 -0
  14. package/commands/marketplace.js.map +1 -0
  15. package/commands/push.js +2 -7
  16. package/commands/push.js.map +1 -1
  17. package/commands/run.js +0 -3
  18. package/commands/run.js.map +1 -1
  19. package/config/config.d.ts +19 -0
  20. package/config/config.d.ts.map +1 -1
  21. package/config/config.js +15 -0
  22. package/config/config.js.map +1 -1
  23. package/config/index.d.ts +1 -1
  24. package/config/index.d.ts.map +1 -1
  25. package/config/index.js.map +1 -1
  26. package/local/bootstrap.d.ts +21 -0
  27. package/local/bootstrap.d.ts.map +1 -0
  28. package/local/bootstrap.js +88 -0
  29. package/local/bootstrap.js.map +1 -0
  30. package/local/daemon/launch.d.ts +2 -2
  31. package/local/daemon/launch.d.ts.map +1 -1
  32. package/local/daemon/launch.js +4 -34
  33. package/local/daemon/launch.js.map +1 -1
  34. package/local/daemon/process.d.ts +1 -1
  35. package/local/plugins/defaults.d.ts +35 -0
  36. package/local/plugins/defaults.d.ts.map +1 -0
  37. package/local/plugins/defaults.js +83 -0
  38. package/local/plugins/defaults.js.map +1 -0
  39. package/local/seedpack/content.d.ts +2 -1
  40. package/local/seedpack/content.d.ts.map +1 -1
  41. package/local/seedpack/content.js +5 -34
  42. package/local/seedpack/content.js.map +1 -1
  43. package/marketplace/config.d.ts +59 -0
  44. package/marketplace/config.d.ts.map +1 -0
  45. package/marketplace/config.js +162 -0
  46. package/marketplace/config.js.map +1 -0
  47. package/marketplace/github.d.ts +31 -0
  48. package/marketplace/github.d.ts.map +1 -0
  49. package/marketplace/github.js +203 -0
  50. package/marketplace/github.js.map +1 -0
  51. package/marketplace/index.d.ts +8 -0
  52. package/marketplace/index.d.ts.map +1 -0
  53. package/marketplace/index.js +12 -0
  54. package/marketplace/index.js.map +1 -0
  55. package/marketplace/install.d.ts +28 -0
  56. package/marketplace/install.d.ts.map +1 -0
  57. package/marketplace/install.js +109 -0
  58. package/marketplace/install.js.map +1 -0
  59. package/marketplace/materialize.d.ts +19 -0
  60. package/marketplace/materialize.d.ts.map +1 -0
  61. package/marketplace/materialize.js +53 -0
  62. package/marketplace/materialize.js.map +1 -0
  63. package/marketplace/official.d.ts +28 -0
  64. package/marketplace/official.d.ts.map +1 -0
  65. package/marketplace/official.js +86 -0
  66. package/marketplace/official.js.map +1 -0
  67. package/marketplace/read.d.ts +17 -0
  68. package/marketplace/read.d.ts.map +1 -0
  69. package/marketplace/read.js +44 -0
  70. package/marketplace/read.js.map +1 -0
  71. package/marketplace/ref.d.ts +13 -0
  72. package/marketplace/ref.d.ts.map +1 -0
  73. package/marketplace/ref.js +54 -0
  74. package/marketplace/ref.js.map +1 -0
  75. package/marketplace/render.d.ts +22 -0
  76. package/marketplace/render.d.ts.map +1 -0
  77. package/marketplace/render.js +118 -0
  78. package/marketplace/render.js.map +1 -0
  79. package/output/ndjson.js +1 -1
  80. package/output/ndjson.js.map +1 -1
  81. package/package.json +7 -6
  82. package/program.d.ts.map +1 -1
  83. package/program.js +4 -2
  84. package/program.js.map +1 -1
  85. package/resources/ignore/index.d.ts +2 -4
  86. package/resources/ignore/index.d.ts.map +1 -1
  87. package/resources/ignore/index.js +5 -5
  88. package/resources/ignore/index.js.map +1 -1
  89. package/resources/ignore/matcher.d.ts +2 -35
  90. package/resources/ignore/matcher.d.ts.map +1 -1
  91. package/resources/ignore/matcher.js +25 -109
  92. package/resources/ignore/matcher.js.map +1 -1
  93. package/resources/plugin.d.ts +50 -18
  94. package/resources/plugin.d.ts.map +1 -1
  95. package/resources/plugin.js +73 -74
  96. package/resources/plugin.js.map +1 -1
  97. package/resources/run/agent-exec.js +1 -1
  98. package/resources/run/agent-exec.js.map +1 -1
  99. package/resources/run/prepare.d.ts +8 -19
  100. package/resources/run/prepare.d.ts.map +1 -1
  101. package/resources/run/prepare.js +4 -4
  102. package/resources/run/prepare.js.map +1 -1
  103. package/resources/skill.d.ts +2 -2
  104. package/resources/skill.d.ts.map +1 -1
  105. package/resources/skill.js +6 -10
  106. package/resources/skill.js.map +1 -1
  107. package/src/commands/agent-exec-flags.ts +6 -12
  108. package/src/commands/connect.ts +1 -1
  109. package/src/commands/install.test.ts +289 -0
  110. package/src/commands/install.ts +108 -0
  111. package/src/commands/marketplace.test.ts +200 -0
  112. package/src/commands/marketplace.ts +131 -0
  113. package/src/commands/push.ts +5 -10
  114. package/src/commands/run.ts +0 -3
  115. package/src/config/config.ts +37 -0
  116. package/src/config/index.ts +1 -0
  117. package/src/local/bootstrap.test.ts +71 -0
  118. package/src/local/bootstrap.ts +133 -0
  119. package/src/local/daemon/launch.ts +5 -40
  120. package/src/local/daemon/process.ts +1 -1
  121. package/src/local/plugins/defaults.test.ts +238 -0
  122. package/src/local/plugins/defaults.ts +135 -0
  123. package/src/local/seedpack/content.ts +11 -42
  124. package/src/marketplace/__fixtures__/cursor-marketplace.ts +62 -0
  125. package/src/marketplace/config.test.ts +245 -0
  126. package/src/marketplace/config.ts +236 -0
  127. package/src/marketplace/github.test.ts +318 -0
  128. package/src/marketplace/github.ts +296 -0
  129. package/src/marketplace/index.ts +54 -0
  130. package/src/marketplace/install.test.ts +194 -0
  131. package/src/marketplace/install.ts +159 -0
  132. package/src/marketplace/materialize.ts +80 -0
  133. package/src/marketplace/official.test.ts +107 -0
  134. package/src/marketplace/official.ts +125 -0
  135. package/src/marketplace/read.test.ts +94 -0
  136. package/src/marketplace/read.ts +76 -0
  137. package/src/marketplace/ref.test.ts +179 -0
  138. package/src/marketplace/ref.ts +74 -0
  139. package/src/marketplace/render.ts +154 -0
  140. package/src/output/ndjson.ts +1 -1
  141. package/src/program.test.ts +4 -13
  142. package/src/program.ts +4 -2
  143. package/src/resources/ignore/ignore.test.ts +23 -100
  144. package/src/resources/ignore/index.ts +11 -8
  145. package/src/resources/ignore/matcher.ts +33 -136
  146. package/src/resources/plugin.test.ts +35 -6
  147. package/src/resources/plugin.ts +132 -85
  148. package/src/resources/run/agent-exec.ts +1 -1
  149. package/src/resources/run/prepare.test.ts +4 -18
  150. package/src/resources/run/prepare.ts +12 -24
  151. package/src/resources/skill.ts +6 -10
  152. package/commands/draft.d.ts +0 -3
  153. package/commands/draft.d.ts.map +0 -1
  154. package/commands/draft.js +0 -115
  155. package/commands/draft.js.map +0 -1
  156. package/resources/ignore/defaults.d.ts +0 -2
  157. package/resources/ignore/defaults.d.ts.map +0 -1
  158. package/resources/ignore/defaults.js +0 -140
  159. package/resources/ignore/defaults.js.map +0 -1
  160. package/resources/ignore/match.d.ts +0 -3
  161. package/resources/ignore/match.d.ts.map +0 -1
  162. package/resources/ignore/match.js +0 -166
  163. package/resources/ignore/match.js.map +0 -1
  164. package/resources/ignore/pattern.d.ts +0 -13
  165. package/resources/ignore/pattern.d.ts.map +0 -1
  166. package/resources/ignore/pattern.js +0 -118
  167. package/resources/ignore/pattern.js.map +0 -1
  168. package/src/commands/draft.ts +0 -189
  169. package/src/resources/ignore/defaults.ts +0 -155
  170. package/src/resources/ignore/match.ts +0 -158
  171. package/src/resources/ignore/pattern.ts +0 -131
@@ -0,0 +1,131 @@
1
+ // `stigmer marketplace add|list|show|remove` — the plugin marketplaces this
2
+ // CLI installs from. A noun group, like `config` and `auth`, because a
3
+ // marketplace is client-side configuration rather than a resource kind: the
4
+ // server never sees one, only the plugin archive a client pushes from it.
5
+ //
6
+ // `list` is offline (the config file alone). `add` and `show` fetch the tree
7
+ // once, so `add` also proves the source is a readable marketplace before it
8
+ // is recorded, and the recorded name is the one the marketplace file gives
9
+ // itself unless `--name` says otherwise. Nothing is cached; `show` reads the
10
+ // source every time and says so in its docs.
11
+ //
12
+ // Heavy modules load lazily inside the action so `--help` stays fast.
13
+
14
+ import type { Command } from "commander";
15
+ import { type OutputFlags, renderResult } from "../output/index.js";
16
+ import { addResultFlags, resultFormat } from "./shared.js";
17
+
18
+ interface AddFlags extends OutputFlags {
19
+ name?: string;
20
+ }
21
+
22
+ export function registerMarketplace(program: Command): void {
23
+ const marketplace = program
24
+ .command("marketplace")
25
+ .description("manage the plugin marketplaces you install from");
26
+
27
+ const add = marketplace
28
+ .command("add <source>")
29
+ .description(
30
+ "add a marketplace: a directory, a GitHub 'owner/repo[@ref]', or that repository's URL",
31
+ )
32
+ .option(
33
+ "--name <name>",
34
+ "the name to install from (defaults to the name the marketplace file gives itself)",
35
+ )
36
+ .action((source: string, options: AddFlags) => runAdd(source, options));
37
+ addResultFlags(add);
38
+
39
+ const list = marketplace
40
+ .command("list")
41
+ .description("list the configured marketplaces (offline)")
42
+ .action((options: OutputFlags) => runList(options));
43
+ addResultFlags(list);
44
+
45
+ const show = marketplace
46
+ .command("show <name>")
47
+ .description("show the plugins a marketplace offers (reads its source)")
48
+ .action((name: string, options: OutputFlags) => runShow(name, options));
49
+ addResultFlags(show);
50
+
51
+ const remove = marketplace
52
+ .command("remove <name>")
53
+ .description("remove a configured marketplace (installed plugins stay)")
54
+ .action((name: string, options: OutputFlags) => runRemove(name, options));
55
+ addResultFlags(remove);
56
+ }
57
+
58
+ async function runAdd(source: string, flags: AddFlags): Promise<void> {
59
+ const format = resultFormat(flags);
60
+ const m = await import("../marketplace/index.js");
61
+ const { CommandResult } = await import("../output/index.js");
62
+ const { UsageError } = await import("../errors/index.js");
63
+ const { isValidPluginName } = await import("@stigmer/plugin-package");
64
+
65
+ const parsed = m.parseAddSource(source);
66
+ if (flags.name !== undefined && !isValidPluginName(flags.name)) {
67
+ throw new UsageError(
68
+ `'${flags.name}' is not a marketplace name\n\nA name is lowercase letters, digits, '.' and '-', like 'cursor-plugins'.`,
69
+ );
70
+ }
71
+
72
+ // Read before recording: an entry that cannot be read is a mistake caught
73
+ // now, not on the first install, and the file's own name is the default.
74
+ const { name, offered } = await m.withMarketplace(parsed, (open) => {
75
+ const tree = m.readMarketplaceTree(open.root, m.describeSource(parsed));
76
+ return {
77
+ name: flags.name ?? tree.marketplace.name,
78
+ offered: tree.marketplace.plugins.length,
79
+ };
80
+ });
81
+ m.addMarketplace(name, parsed);
82
+
83
+ const result = CommandResult.success(
84
+ `Added marketplace '${name}' (${m.describeSource(parsed)}), offering ${offered} plugin${offered === 1 ? "" : "s"}`,
85
+ );
86
+ result.hint(`See what it offers with: stigmer marketplace show ${name}`);
87
+ result.hint(`Install from it with: stigmer install ${name}/<plugin>`);
88
+ renderResult(
89
+ result.withData({ name, source: parsed, plugins: offered }),
90
+ format,
91
+ );
92
+ }
93
+
94
+ async function runList(flags: OutputFlags): Promise<void> {
95
+ const format = resultFormat(flags);
96
+ const m = await import("../marketplace/index.js");
97
+ renderResult(m.renderMarketplaceList(m.listMarketplaces()), format);
98
+ }
99
+
100
+ async function runShow(name: string, flags: OutputFlags): Promise<void> {
101
+ const format = resultFormat(flags);
102
+ const m = await import("../marketplace/index.js");
103
+ const { UsageError } = await import("../errors/index.js");
104
+
105
+ const marketplace = m.findMarketplace(name);
106
+ if (marketplace === undefined) {
107
+ throw new UsageError(
108
+ `no marketplace named '${name}' is configured\n\nRun 'stigmer marketplace list' to see the configured names.`,
109
+ );
110
+ }
111
+ const result = await m.withMarketplace(marketplace.source, (open) => {
112
+ const tree = m.readMarketplaceTree(
113
+ open.root,
114
+ m.describeSource(marketplace.source),
115
+ );
116
+ return m.renderMarketplaceShow(marketplace, tree, m.inspectEntries(tree));
117
+ });
118
+ renderResult(result, format);
119
+ }
120
+
121
+ async function runRemove(name: string, flags: OutputFlags): Promise<void> {
122
+ const format = resultFormat(flags);
123
+ const m = await import("../marketplace/index.js");
124
+ const { CommandResult } = await import("../output/index.js");
125
+ m.removeMarketplace(name);
126
+ const result = CommandResult.success(`Removed marketplace '${name}'`);
127
+ result.hint(
128
+ "Plugins installed from it stay installed; remove one with: stigmer delete plugin <name>",
129
+ );
130
+ renderResult(result, format);
131
+ }
@@ -237,20 +237,15 @@ async function runPushPlugin(
237
237
  const visibility = parseVisibility(options.visibility, "--visibility");
238
238
 
239
239
  if (options.dryRun === true) {
240
- const { readPluginPackage } = await import("@stigmer/plugin-package");
241
- const read = plugin.readPluginDirectory(directory, ignoreOptions);
242
- const outcome = readPluginPackage(read.files);
243
- if (!outcome.ok) {
244
- throw plugin.pluginRefusal(directory, outcome.errors, outcome.warnings);
245
- }
240
+ const prepared = await plugin.preparePluginPush(directory, ignoreOptions);
246
241
  const { CommandResult } = await import("../output/index.js");
247
242
  const result = plugin.describePackageOn(
248
243
  CommandResult.success(
249
- `Dry run: plugin '${outcome.plugin.name}' would install`,
244
+ `Dry run: plugin '${prepared.plugin.name}' would install`,
250
245
  ),
251
- outcome.plugin,
252
- outcome.warnings,
253
- read.stats,
246
+ prepared.plugin,
247
+ prepared.warnings,
248
+ prepared.stats,
254
249
  );
255
250
  result.hint("Run without --dry-run to install it.");
256
251
  renderCommandResult(result, format);
@@ -194,9 +194,6 @@ async function runResolvedAgent(
194
194
  accountPreferencesAvailable: await client.isResourceAvailable(
195
195
  ApiResourceKind.identity_account,
196
196
  ),
197
- // run opts into the account default_harness fill; draft deliberately
198
- // does not (see the option's doc comment for the D5 rationale).
199
- applyAccountHarnessDefault: true,
200
197
  },
201
198
  );
202
199
  await executeResolvedAgent({
@@ -83,11 +83,31 @@ export interface ContextConfig {
83
83
  organization?: string;
84
84
  }
85
85
 
86
+ /**
87
+ * One added plugin marketplace as the file spells it. The `marketplace`
88
+ * commands are the only writer; the shape is kept loose here and narrowed by
89
+ * `src/marketplace/config.ts`, so a hand-edited entry this CLI cannot read is
90
+ * reported by `stigmer marketplace list` instead of being dropped on the next
91
+ * save. The official marketplace is built in and never stored. `type` is the
92
+ * discriminator, the word this file already uses for backends.
93
+ */
94
+ export interface MarketplaceEntryConfig {
95
+ type?: string;
96
+ /** GitHub `owner/repo` for `type: github`. */
97
+ repo?: string;
98
+ /** Branch, tag or commit for `type: github`; the default branch when absent. */
99
+ ref?: string;
100
+ /** Absolute directory for `type: local`. */
101
+ path?: string;
102
+ }
103
+
86
104
  export interface Config {
87
105
  backend: BackendConfig;
88
106
  backends?: Record<string, NamedBackendConfig>;
89
107
  current_backend?: string;
90
108
  context?: ContextConfig;
109
+ /** Added plugin marketplaces, keyed by the name a user installs from. */
110
+ marketplaces?: Record<string, MarketplaceEntryConfig>;
91
111
  }
92
112
 
93
113
  const SAVE_HEADER = `# Stigmer CLI Configuration
@@ -207,9 +227,25 @@ function normalize(parsed: Partial<Config> | null): Config {
207
227
  backends,
208
228
  current_backend: current,
209
229
  context: parsed.context,
230
+ marketplaces: normalizeMarketplaces(parsed.marketplaces),
210
231
  };
211
232
  }
212
233
 
234
+ /** Object-valued entries verbatim; anything else is not a marketplace and is dropped. */
235
+ function normalizeMarketplaces(
236
+ parsed: Record<string, MarketplaceEntryConfig> | undefined,
237
+ ): Record<string, MarketplaceEntryConfig> | undefined {
238
+ if (parsed === undefined || parsed === null || typeof parsed !== "object") {
239
+ return undefined;
240
+ }
241
+ const marketplaces: Record<string, MarketplaceEntryConfig> = {};
242
+ for (const [name, entry] of Object.entries(parsed)) {
243
+ if (entry === null || typeof entry !== "object") continue;
244
+ marketplaces[name] = { ...entry };
245
+ }
246
+ return Object.keys(marketplaces).length === 0 ? undefined : marketplaces;
247
+ }
248
+
213
249
  function normalizeBackends(
214
250
  parsed: Record<string, NamedBackendConfig> | undefined,
215
251
  ): Record<string, NamedBackendConfig> {
@@ -243,6 +279,7 @@ function serializable(config: Config): Config {
243
279
  },
244
280
  ...(pristineLocal ? {} : { backends, current_backend: current }),
245
281
  context: config.context,
282
+ marketplaces: config.marketplaces,
246
283
  };
247
284
  }
248
285
 
@@ -6,6 +6,7 @@ export {
6
6
  type CloudBackendConfig,
7
7
  type Config,
8
8
  type ContextConfig,
9
+ type MarketplaceEntryConfig,
9
10
  type NamedBackendConfig,
10
11
  type NamedBackendType,
11
12
  CLOUD_BACKEND_NAME,
@@ -0,0 +1,71 @@
1
+ // Pins the bootstrap's shape: the seedpack step then the default-plugins step,
2
+ // over one client; a seedpack failure is contained by its own step and the
3
+ // plugins step still runs; a plugins failure is a warning that names the
4
+ // retry command, never a throw past the bootstrap.
5
+
6
+ import { describe, expect, it } from "vitest";
7
+ import type { BackendClient } from "../client/index.js";
8
+ import {
9
+ bootstrapLocalBackend,
10
+ installDefaultPluginsBestEffort,
11
+ } from "./bootstrap.js";
12
+
13
+ const client = { stigmer: {} } as unknown as BackendClient;
14
+
15
+ describe("bootstrapLocalBackend", () => {
16
+ it("runs the seedpack step then the plugins step, over the same client and home", async () => {
17
+ const order: string[] = [];
18
+ await bootstrapLocalBackend("/home/x", {
19
+ client,
20
+ say: () => {},
21
+ seedpack: async (c, home) => {
22
+ expect(c).toBe(client);
23
+ expect(home).toBe("/home/x");
24
+ order.push("seedpack");
25
+ },
26
+ plugins: async (c, home) => {
27
+ expect(c).toBe(client);
28
+ expect(home).toBe("/home/x");
29
+ order.push("plugins");
30
+ },
31
+ });
32
+ expect(order).toEqual(["seedpack", "plugins"]);
33
+ });
34
+
35
+ it("still runs the plugins step when the seedpack step has warned", async () => {
36
+ const said: string[] = [];
37
+ let plugins = 0;
38
+ await bootstrapLocalBackend("/home/x", {
39
+ client,
40
+ say: (line) => said.push(line),
41
+ seedpack: async (_c, _h, say) => say("Warning: seedpack failed"),
42
+ plugins: async () => {
43
+ plugins += 1;
44
+ },
45
+ });
46
+ expect(plugins).toBe(1);
47
+ expect(said).toEqual(["Warning: seedpack failed"]);
48
+ });
49
+ });
50
+
51
+ describe("installDefaultPluginsBestEffort", () => {
52
+ it("turns a step that cannot even start into one warning naming the retry commands", async () => {
53
+ const said: string[] = [];
54
+ // A client whose plugin lookups throw: the step's own failure path, not a default's.
55
+ const broken = {
56
+ stigmer: {
57
+ plugin: {
58
+ getByReference: async () => {
59
+ throw new Error("backend unreachable");
60
+ },
61
+ },
62
+ },
63
+ } as unknown as BackendClient;
64
+ await installDefaultPluginsBestEffort(broken, "/home/x", (line) =>
65
+ said.push(line),
66
+ );
67
+ expect(said.filter((line) => line.startsWith("Warning:"))).toEqual([
68
+ "Warning: failed to install default plugin 'assistant'. Run 'stigmer install assistant' to retry.",
69
+ ]);
70
+ });
71
+ });
@@ -0,0 +1,133 @@
1
+ // What `stigmer up` does once the local server answers: seed it. Two steps,
2
+ // in this order, each best-effort with its own warning, because the stack is
3
+ // already serving by the time either runs and a seeding failure must never
4
+ // tear it down:
5
+ //
6
+ // 1. the seedpack (the system org, the `stigmer` MCP server, the remaining
7
+ // system agents, skills and workflows), content-hash idempotent;
8
+ // 2. the default plugins the official marketplace names, installed into the
9
+ // same org as public, idempotent by the server's own digest.
10
+ //
11
+ // The seedpack goes first because the plugins install INTO the org it
12
+ // creates and reference the server it holds. When the seedpack retires, this
13
+ // file keeps step 2 and loses step 1, and nothing else moves.
14
+ //
15
+ // Both `up` shapes (detached and foreground) call this one function, so what
16
+ // a detached `up` seeds and what a container's `up --foreground` seeds cannot
17
+ // drift. Both always seed the local server `up` just started, never the
18
+ // active backend context: a user whose CLI is pointed at cloud must still get
19
+ // their local stack seeded, and must never have system resources applied to
20
+ // their cloud org. The client is pinned to a fresh local config
21
+ // (localhost:SERVER_PORT) with no auth, the trusted-local identity.
22
+
23
+ import type { BackendClient } from "../client/index.js";
24
+ import { log } from "../logger.js";
25
+ import { dataDir } from "./paths.js";
26
+
27
+ /** One seeding step: talks to the user through `say`, never throws past its own warning. */
28
+ export type BootstrapStep = (
29
+ client: BackendClient,
30
+ home: string,
31
+ say: Say,
32
+ ) => Promise<void>;
33
+
34
+ export type Say = (line: string) => void;
35
+
36
+ /** Seams of the bootstrap, injectable for tests; the defaults are the real steps over the real local client. */
37
+ export interface BootstrapDeps {
38
+ readonly client?: BackendClient;
39
+ readonly seedpack?: BootstrapStep;
40
+ readonly plugins?: BootstrapStep;
41
+ readonly say?: Say;
42
+ }
43
+
44
+ const sayToStderr: Say = (line) => {
45
+ process.stderr.write(`${line}\n`);
46
+ };
47
+
48
+ export async function bootstrapLocalBackend(
49
+ home: string,
50
+ deps: BootstrapDeps = {},
51
+ ): Promise<void> {
52
+ const client = deps.client ?? (await freshLocalClient());
53
+ const say = deps.say ?? sayToStderr;
54
+ await (deps.seedpack ?? applySeedpackBestEffort)(client, home, say);
55
+ await (deps.plugins ?? installDefaultPluginsBestEffort)(client, home, say);
56
+ }
57
+
58
+ async function freshLocalClient(): Promise<BackendClient> {
59
+ const [{ createBackendClient }, { getDefault }] = await Promise.all([
60
+ import("../client/index.js"),
61
+ import("../config/config.js"),
62
+ ]);
63
+ return createBackendClient({
64
+ config: getDefault(),
65
+ getAccessToken: () => null,
66
+ });
67
+ }
68
+
69
+ /** Step 1. Idempotent via a content-hash marker, so repeated `up`s are cheap. */
70
+ export async function applySeedpackBestEffort(
71
+ client: BackendClient,
72
+ home: string,
73
+ say: Say,
74
+ ): Promise<void> {
75
+ try {
76
+ const { applySeedpack } = await import("./seedpack/apply.js");
77
+ const result = await applySeedpack(
78
+ {
79
+ controller: client.controller,
80
+ stigmer: client.stigmer,
81
+ info: say,
82
+ warn: say,
83
+ },
84
+ { markerDir: dataDir(home), home },
85
+ );
86
+ log.debug("seedpack bootstrap complete", {
87
+ applied: result.applied,
88
+ hash: result.hash,
89
+ org: result.org,
90
+ });
91
+ } catch (err) {
92
+ log.warn("seedpack bootstrap failed", { error: String(err) });
93
+ say(
94
+ "Warning: failed to apply system resources (seedpack). Run 'stigmer seedpack apply' to retry.",
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Step 2. Runs even when step 1 warned: the plugins can only fail on their
101
+ * own terms (an org that does not exist fails the push, and the warning
102
+ * names the command that retries it).
103
+ */
104
+ export async function installDefaultPluginsBestEffort(
105
+ client: BackendClient,
106
+ _home: string,
107
+ say: Say,
108
+ ): Promise<void> {
109
+ try {
110
+ const [{ installDefaultPlugins }, { resolveSeedpackOrg }] =
111
+ await Promise.all([
112
+ import("./plugins/defaults.js"),
113
+ import("./seedpack/apply.js"),
114
+ ]);
115
+ const result = await installDefaultPlugins(
116
+ { stigmer: client.stigmer, info: say },
117
+ { org: resolveSeedpackOrg() },
118
+ );
119
+ log.debug("default plugins bootstrap complete", {
120
+ outcomes: result.outcomes,
121
+ });
122
+ for (const name of result.failed) {
123
+ say(
124
+ `Warning: failed to install default plugin '${name}'. Run 'stigmer install ${name}' to retry.`,
125
+ );
126
+ }
127
+ } catch (err) {
128
+ log.warn("default plugins bootstrap failed", { error: String(err) });
129
+ say(
130
+ "Warning: failed to install the default plugins. Run 'stigmer marketplace show stigmer' to see them and 'stigmer install <name>' to retry.",
131
+ );
132
+ }
133
+ }
@@ -18,6 +18,7 @@ import { type Config, load as loadConfig } from "../../config/config.js";
18
18
  import { log } from "../../logger.js";
19
19
  import { CliExitError } from "../../errors/cli-exit-error.js";
20
20
  import { ExitCode } from "../../errors/exit-codes.js";
21
+ import { bootstrapLocalBackend } from "../bootstrap.js";
21
22
  import { DAEMON_PID_FILE, SERVER_PORT } from "../constants.js";
22
23
  import { tcpConnects, waitForTcp } from "../net/tcp.js";
23
24
  import { dataDir, logDir } from "../paths.js";
@@ -71,7 +72,7 @@ export async function up(options: UpOptions = {}, home: string = homedir()): Pro
71
72
 
72
73
  await waitForTcp({ port: SERVER_PORT, timeoutMs: READY_TIMEOUT_MS, label: "stigmer-server" });
73
74
 
74
- await applySeedpackBestEffort(home);
75
+ await bootstrapLocalBackend(home);
75
76
 
76
77
  saveStartupConfig(data, buildStartupConfig(data, logs, temporalAddress, daemonPid, options));
77
78
  }
@@ -82,7 +83,7 @@ export interface UpForegroundDeps {
82
83
  runDaemon?: (deps: InternalDaemonDeps) => Promise<number>;
83
84
  /** Resolves when the stack should shut down (default: the first SIGTERM/SIGINT). */
84
85
  waitForShutdown?: () => Promise<void>;
85
- /** Post-readiness bootstrap (default: the best-effort seedpack apply). */
86
+ /** Post-readiness bootstrap (default: `bootstrapLocalBackend`, the seedpack then the default plugins). */
86
87
  bootstrap?: (home: string) => Promise<void>;
87
88
  /** Receives each line the server and runner write (default: a `[component]`-prefixed stdout mirror). */
88
89
  mirror?: OutputMirror;
@@ -99,7 +100,7 @@ export interface UpForegroundDeps {
99
100
  * `up`, so `stigmer status` and `stigmer down` from another shell see an
100
101
  * ordinary daemon whose PID happens to be ours. The children receive the very
101
102
  * environment a detached daemon would have inherited, and what a detached `up`
102
- * does once the server answers — the seedpack, the startup record — happens in
103
+ * does once the server answers — the bootstrap, the startup record — happens in
103
104
  * `onStarted`, at the same point in the stack's life.
104
105
  */
105
106
  export async function upForeground(
@@ -109,7 +110,7 @@ export async function upForeground(
109
110
  ): Promise<number> {
110
111
  const { data, logs, temporalAddress, env } = await prepareLaunch(options, home);
111
112
  const runDaemon = deps.runDaemon ?? runInternalDaemon;
112
- const bootstrap = deps.bootstrap ?? applySeedpackBestEffort;
113
+ const bootstrap = deps.bootstrap ?? bootstrapLocalBackend;
113
114
 
114
115
  return runDaemon({
115
116
  config: readDaemonConfig(env),
@@ -179,42 +180,6 @@ async function prepareLaunch(options: UpOptions, home: string): Promise<Prepared
179
180
  return { data, logs, temporalAddress: temporal.address, env };
180
181
  }
181
182
 
182
- /**
183
- * Bootstrap the system seedpack into the freshly-started local backend.
184
- * Idempotent via a content-hash marker, so repeated `up`s are cheap. Best-effort: the stack is
185
- * already serving by this point, so a seedpack failure is surfaced as a warning
186
- * with a retry hint rather than tearing the stack down.
187
- */
188
- async function applySeedpackBestEffort(home: string): Promise<void> {
189
- try {
190
- const [{ createBackendClient }, { getDefault }, { applySeedpack }] = await Promise.all([
191
- import("../../client/index.js"),
192
- import("../../config/config.js"),
193
- import("../seedpack/apply.js"),
194
- ]);
195
- // Always seed the local server `up` just started — never the active backend
196
- // context. A user whose CLI is pointed at cloud must still get their local
197
- // stack seeded, and must never have system resources applied to their cloud
198
- // org. Pin to a fresh local config (localhost:SERVER_PORT) with no auth.
199
- const client = createBackendClient({ config: getDefault(), getAccessToken: () => null });
200
- const result = await applySeedpack(
201
- {
202
- controller: client.controller,
203
- stigmer: client.stigmer,
204
- info: (line) => process.stderr.write(`${line}\n`),
205
- warn: (line) => process.stderr.write(`${line}\n`),
206
- },
207
- { markerDir: dataDir(home), home },
208
- );
209
- log.debug("seedpack bootstrap complete", { applied: result.applied, hash: result.hash, org: result.org });
210
- } catch (err) {
211
- log.warn("seedpack bootstrap failed", { error: String(err) });
212
- process.stderr.write(
213
- "Warning: failed to apply system resources (seedpack). Run 'stigmer seedpack apply' to retry.\n",
214
- );
215
- }
216
- }
217
-
218
183
  /** Stop the local stack. Returns false if nothing was running. */
219
184
  export async function down(home: string = homedir()): Promise<boolean> {
220
185
  const data = dataDir(home);
@@ -52,7 +52,7 @@ export interface InternalDaemonDeps {
52
52
  /**
53
53
  * Invoked once the stack is up and the monitor is running, and awaited
54
54
  * before the daemon settles into waiting for shutdown. The foreground
55
- * launcher does its post-readiness work here (seedpack, the success card);
55
+ * launcher does its post-readiness work here (the bootstrap, the success card);
56
56
  * tests use it to observe "started".
57
57
  */
58
58
  onStarted?: () => void | Promise<void>;