@stigmer/cli 3.18.0-dev.20260918103812 → 3.18.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.
- package/commands/bootstrap.d.ts +3 -0
- package/commands/bootstrap.d.ts.map +1 -0
- package/commands/bootstrap.js +75 -0
- package/commands/bootstrap.js.map +1 -0
- package/commands/marketplace.d.ts.map +1 -1
- package/commands/marketplace.js +7 -7
- package/commands/marketplace.js.map +1 -1
- package/config/resolve.d.ts.map +1 -1
- package/config/resolve.js +8 -7
- package/config/resolve.js.map +1 -1
- package/local/bootstrap.d.ts +32 -13
- package/local/bootstrap.d.ts.map +1 -1
- package/local/bootstrap.js +105 -64
- package/local/bootstrap.js.map +1 -1
- package/local/daemon/launch.d.ts +1 -1
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/paths.d.ts +1 -1
- package/local/paths.js +1 -1
- package/local/plugins/defaults.d.ts +27 -7
- package/local/plugins/defaults.d.ts.map +1 -1
- package/local/plugins/defaults.js +55 -28
- package/local/plugins/defaults.js.map +1 -1
- package/local/runtime/runtimes-install.d.ts.map +1 -1
- package/local/runtime/runtimes-install.js +7 -4
- package/local/runtime/runtimes-install.js.map +1 -1
- package/local/seedpack-retire.d.ts +63 -0
- package/local/seedpack-retire.d.ts.map +1 -0
- package/local/seedpack-retire.js +361 -0
- package/local/seedpack-retire.js.map +1 -0
- package/local/system-org.d.ts +15 -0
- package/local/system-org.d.ts.map +1 -0
- package/local/system-org.js +57 -0
- package/local/system-org.js.map +1 -0
- package/marketplace/config.d.ts +3 -1
- package/marketplace/config.d.ts.map +1 -1
- package/marketplace/config.js +24 -11
- package/marketplace/config.js.map +1 -1
- package/marketplace/github.d.ts +37 -0
- package/marketplace/github.d.ts.map +1 -1
- package/marketplace/github.js +70 -1
- package/marketplace/github.js.map +1 -1
- package/marketplace/install.d.ts +13 -6
- package/marketplace/install.d.ts.map +1 -1
- package/marketplace/install.js +63 -16
- package/marketplace/install.js.map +1 -1
- package/marketplace/official.js +4 -4
- package/marketplace/official.js.map +1 -1
- package/marketplace/render.d.ts.map +1 -1
- package/marketplace/render.js +10 -4
- package/marketplace/render.js.map +1 -1
- package/package.json +7 -7
- package/program.js +2 -2
- package/program.js.map +1 -1
- package/resources/apply/declarative.js +2 -2
- package/resources/apply/declarative.js.map +1 -1
- package/resources/plugin.d.ts.map +1 -1
- package/resources/plugin.js +12 -9
- package/resources/plugin.js.map +1 -1
- package/src/commands/bootstrap.test.ts +253 -0
- package/src/commands/bootstrap.ts +91 -0
- package/src/commands/install.test.ts +28 -9
- package/src/commands/marketplace.test.ts +28 -14
- package/src/commands/marketplace.ts +9 -7
- package/src/config/resolve.test.ts +2 -2
- package/src/config/resolve.ts +8 -7
- package/src/local/bootstrap.test.ts +110 -38
- package/src/local/bootstrap.ts +179 -89
- package/src/local/daemon/launch.ts +1 -1
- package/src/local/paths.ts +1 -1
- package/src/local/plugins/defaults.test.ts +46 -13
- package/src/local/plugins/defaults.ts +77 -34
- package/src/local/runtime/runtimes-install.test.ts +2 -0
- package/src/local/runtime/runtimes-install.ts +7 -4
- package/src/local/seedpack-retire.test.ts +499 -0
- package/src/local/seedpack-retire.ts +474 -0
- package/src/local/system-org.test.ts +124 -0
- package/src/local/system-org.ts +63 -0
- package/src/marketplace/__fixtures__/cursor-marketplace.ts +5 -2
- package/src/marketplace/config.test.ts +47 -22
- package/src/marketplace/config.ts +27 -12
- package/src/marketplace/github.test.ts +77 -1
- package/src/marketplace/github.ts +115 -1
- package/src/marketplace/install.test.ts +84 -17
- package/src/marketplace/install.ts +73 -15
- package/src/marketplace/official.ts +4 -4
- package/src/marketplace/read.test.ts +1 -1
- package/src/marketplace/render.ts +13 -4
- package/src/program.ts +2 -2
- package/src/resources/apply/declarative.integration.test.ts +2 -2
- package/src/resources/apply/declarative.ts +2 -2
- package/src/resources/connect/discover.test.ts +1 -1
- package/src/resources/plugin.ts +11 -10
- package/commands/seedpack.d.ts +0 -3
- package/commands/seedpack.d.ts.map +0 -1
- package/commands/seedpack.js +0 -98
- package/commands/seedpack.js.map +0 -1
- package/local/seedpack/apply.d.ts +0 -72
- package/local/seedpack/apply.d.ts.map +0 -1
- package/local/seedpack/apply.js +0 -145
- package/local/seedpack/apply.js.map +0 -1
- package/local/seedpack/content.d.ts +0 -61
- package/local/seedpack/content.d.ts.map +0 -1
- package/local/seedpack/content.js +0 -178
- package/local/seedpack/content.js.map +0 -1
- package/src/commands/seedpack.ts +0 -116
- package/src/local/seedpack/apply.test.ts +0 -287
- package/src/local/seedpack/apply.ts +0 -199
- package/src/local/seedpack/content.test.ts +0 -169
- package/src/local/seedpack/content.ts +0 -222
package/src/local/bootstrap.ts
CHANGED
|
@@ -1,133 +1,223 @@
|
|
|
1
|
-
// What
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// What makes a backend a ready Stigmer: the system org exists and the
|
|
2
|
+
// official marketplace's default plugins are installed into it, public.
|
|
3
|
+
// `stigmer up` runs this against the local server it just started;
|
|
4
|
+
// `stigmer bootstrap` runs the same two phases against whatever backend the
|
|
5
|
+
// CLI is pointed at, for a raw self-hosted server or the platform's daily
|
|
6
|
+
// lane. One definition, so a laptop, a container and the hosted platform
|
|
7
|
+
// cannot drift in what "ready" means.
|
|
5
8
|
//
|
|
6
|
-
//
|
|
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.
|
|
9
|
+
// Two phases, and the order matters (a third, `up`'s retire, follows them):
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// 1. prepare: resolve the official marketplace (acquiring `@stigmer/plugins`
|
|
12
|
+
// at the CLI's version on a release's first run) and prepare every
|
|
13
|
+
// default. No backend is touched.
|
|
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.
|
|
14
16
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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
|
|
32
|
+
// and foreground) call this one function, and both seed the local server
|
|
33
|
+
// `up` just started, never the active backend context: a CLI pointed at
|
|
34
|
+
// cloud still gets its local stack seeded, and never has system resources
|
|
35
|
+
// applied to its cloud org. The client is pinned to a fresh local config
|
|
21
36
|
// (localhost:SERVER_PORT) with no auth, the trusted-local identity.
|
|
22
37
|
|
|
38
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
23
39
|
import type { BackendClient } from "../client/index.js";
|
|
24
40
|
import { log } from "../logger.js";
|
|
41
|
+
import type { ResolveOfficialOptions } from "../marketplace/official.js";
|
|
42
|
+
import type {
|
|
43
|
+
DefaultPluginsResult,
|
|
44
|
+
InstallingVerb,
|
|
45
|
+
PreparedDefault,
|
|
46
|
+
} from "./plugins/defaults.js";
|
|
25
47
|
import { dataDir } from "./paths.js";
|
|
48
|
+
import { type EnsureSystemOrgOutcome, SYSTEM_ORG } from "./system-org.js";
|
|
26
49
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
export type Say = (line: string) => void;
|
|
51
|
+
|
|
52
|
+
/** Everything phase 2 needs, gathered without a backend. */
|
|
53
|
+
export interface PreparedBootstrap {
|
|
54
|
+
readonly defaults: readonly PreparedDefault[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BootstrapResult {
|
|
58
|
+
readonly org: EnsureSystemOrgOutcome;
|
|
59
|
+
readonly plugins: DefaultPluginsResult;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PrepareBootstrapOptions {
|
|
63
|
+
/** How the official tree is found (injectable for tests). */
|
|
64
|
+
readonly official?: ResolveOfficialOptions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Phase 1. Throws when the catalogue cannot be resolved or a default cannot be prepared. */
|
|
68
|
+
export async function prepareBootstrap(
|
|
69
|
+
options: PrepareBootstrapOptions = {},
|
|
70
|
+
): Promise<PreparedBootstrap> {
|
|
71
|
+
const { prepareDefaultPlugins } = await import("./plugins/defaults.js");
|
|
72
|
+
return { defaults: await prepareDefaultPlugins(options.official) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Phase 2. Throws when the system org cannot be ensured; a default that
|
|
77
|
+
* fails to land is reported in the result, not thrown, so the caller can
|
|
78
|
+
* name each one. `verb` is the command running this, stamped into each
|
|
79
|
+
* installed plugin's version message.
|
|
80
|
+
*/
|
|
81
|
+
export async function runBootstrap(
|
|
82
|
+
stigmer: Stigmer,
|
|
83
|
+
prepared: PreparedBootstrap,
|
|
31
84
|
say: Say,
|
|
32
|
-
|
|
85
|
+
verb: InstallingVerb,
|
|
86
|
+
): Promise<BootstrapResult> {
|
|
87
|
+
const [{ ensureSystemOrg }, { installPreparedDefaults }] = await Promise.all(
|
|
88
|
+
[import("./system-org.js"), import("./plugins/defaults.js")],
|
|
89
|
+
);
|
|
90
|
+
const org = await ensureSystemOrg(stigmer);
|
|
91
|
+
if (org === "created") say(`Created the '${SYSTEM_ORG}' organization`);
|
|
92
|
+
const plugins = await installPreparedDefaults(
|
|
93
|
+
{ stigmer, info: say, verb },
|
|
94
|
+
prepared.defaults,
|
|
95
|
+
SYSTEM_ORG,
|
|
96
|
+
);
|
|
97
|
+
return { org, plugins };
|
|
98
|
+
}
|
|
33
99
|
|
|
34
|
-
|
|
100
|
+
/** Both phases, against the backend `stigmer` is bound to: what `stigmer bootstrap` runs. */
|
|
101
|
+
export async function bootstrapBackend(
|
|
102
|
+
stigmer: Stigmer,
|
|
103
|
+
say: Say,
|
|
104
|
+
options: PrepareBootstrapOptions = {},
|
|
105
|
+
): Promise<BootstrapResult> {
|
|
106
|
+
return runBootstrap(
|
|
107
|
+
stigmer,
|
|
108
|
+
await prepareBootstrap(options),
|
|
109
|
+
say,
|
|
110
|
+
"stigmer bootstrap",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
35
113
|
|
|
36
|
-
/** Seams of the bootstrap, injectable for tests; the defaults are the real
|
|
114
|
+
/** Seams of the local bootstrap, injectable for tests; the defaults are the real phases over the real local client. */
|
|
37
115
|
export interface BootstrapDeps {
|
|
38
116
|
readonly client?: BackendClient;
|
|
39
|
-
readonly seedpack?: BootstrapStep;
|
|
40
|
-
readonly plugins?: BootstrapStep;
|
|
41
117
|
readonly say?: Say;
|
|
118
|
+
readonly prepare?: () => Promise<PreparedBootstrap>;
|
|
119
|
+
readonly retire?: (stigmer: Stigmer, home: string, say: Say) => Promise<void>;
|
|
120
|
+
readonly run?: (
|
|
121
|
+
stigmer: Stigmer,
|
|
122
|
+
prepared: PreparedBootstrap,
|
|
123
|
+
say: Say,
|
|
124
|
+
) => Promise<BootstrapResult>;
|
|
42
125
|
}
|
|
43
126
|
|
|
44
127
|
const sayToStderr: Say = (line) => {
|
|
45
128
|
process.stderr.write(`${line}\n`);
|
|
46
129
|
};
|
|
47
130
|
|
|
131
|
+
/** What `stigmer up` does once the local server answers. Never throws past its own warnings. */
|
|
48
132
|
export async function bootstrapLocalBackend(
|
|
49
133
|
home: string,
|
|
50
134
|
deps: BootstrapDeps = {},
|
|
51
135
|
): Promise<void> {
|
|
52
136
|
const client = deps.client ?? (await freshLocalClient());
|
|
53
137
|
const say = deps.say ?? sayToStderr;
|
|
54
|
-
await (deps.seedpack ?? applySeedpackBestEffort)(client, home, say);
|
|
55
|
-
await (deps.plugins ?? installDefaultPluginsBestEffort)(client, home, say);
|
|
56
|
-
}
|
|
57
138
|
|
|
58
|
-
|
|
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> {
|
|
139
|
+
let prepared: PreparedBootstrap;
|
|
75
140
|
try {
|
|
76
|
-
|
|
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
|
-
});
|
|
141
|
+
prepared = await (deps.prepare ?? prepareBootstrap)();
|
|
91
142
|
} catch (err) {
|
|
92
|
-
log.warn("
|
|
143
|
+
log.warn("bootstrap preparation failed", { error: String(err) });
|
|
93
144
|
say(
|
|
94
|
-
"Warning:
|
|
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.",
|
|
95
146
|
);
|
|
147
|
+
return;
|
|
96
148
|
}
|
|
97
|
-
}
|
|
98
149
|
|
|
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> {
|
|
150
|
+
let result: BootstrapResult;
|
|
109
151
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
]);
|
|
115
|
-
const result = await installDefaultPlugins(
|
|
116
|
-
{ stigmer: client.stigmer, info: say },
|
|
117
|
-
{ org: resolveSeedpackOrg() },
|
|
152
|
+
result = await (deps.run ?? runLocalBootstrap)(
|
|
153
|
+
client.stigmer,
|
|
154
|
+
prepared,
|
|
155
|
+
say,
|
|
118
156
|
);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
157
|
+
} catch (err) {
|
|
158
|
+
log.warn("bootstrap failed", { error: String(err) });
|
|
159
|
+
say(
|
|
160
|
+
"Warning: failed to bootstrap the local backend, so nothing was retired. Run 'stigmer up' again to retry.",
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
log.debug("bootstrap complete", {
|
|
165
|
+
org: result.org,
|
|
166
|
+
outcomes: result.plugins.outcomes,
|
|
167
|
+
});
|
|
168
|
+
if (result.plugins.failed.length > 0) {
|
|
169
|
+
for (const name of result.plugins.failed) {
|
|
123
170
|
say(
|
|
124
|
-
`Warning: failed to install default plugin '${name}'. Run 'stigmer
|
|
171
|
+
`Warning: failed to install default plugin '${name}'. Run 'stigmer bootstrap' to retry.`,
|
|
125
172
|
);
|
|
126
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);
|
|
127
182
|
} catch (err) {
|
|
128
|
-
log.warn("
|
|
183
|
+
log.warn("seedpack retire failed", { error: String(err) });
|
|
129
184
|
say(
|
|
130
|
-
"Warning: failed to
|
|
185
|
+
"Warning: failed to retire the resources an older release installed. Run 'stigmer up' again to retry.",
|
|
131
186
|
);
|
|
132
187
|
}
|
|
133
188
|
}
|
|
189
|
+
|
|
190
|
+
// Phase 2 in the seam's shape, naming the verb: everything this file's
|
|
191
|
+
// local bootstrap installs was installed by `stigmer up`.
|
|
192
|
+
function runLocalBootstrap(
|
|
193
|
+
stigmer: Stigmer,
|
|
194
|
+
prepared: PreparedBootstrap,
|
|
195
|
+
say: Say,
|
|
196
|
+
): Promise<BootstrapResult> {
|
|
197
|
+
return runBootstrap(stigmer, prepared, say, "stigmer up");
|
|
198
|
+
}
|
|
199
|
+
|
|
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
|
+
async function freshLocalClient(): Promise<BackendClient> {
|
|
215
|
+
const [{ createBackendClient }, { getDefault }] = await Promise.all([
|
|
216
|
+
import("../client/index.js"),
|
|
217
|
+
import("../config/config.js"),
|
|
218
|
+
]);
|
|
219
|
+
return createBackendClient({
|
|
220
|
+
config: getDefault(),
|
|
221
|
+
getAccessToken: () => null,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -83,7 +83,7 @@ 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
|
|
86
|
+
/** Post-readiness bootstrap (default: `bootstrapLocalBackend`: prepare the defaults, retire an older release's seedpack, ensure the org, install). */
|
|
87
87
|
bootstrap?: (home: string) => Promise<void>;
|
|
88
88
|
/** Receives each line the server and runner write (default: a `[component]`-prefixed stdout mirror). */
|
|
89
89
|
mirror?: OutputMirror;
|
package/src/local/paths.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function binDir(home: string = homedir()): string {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Env var that relocates the acquired-runtimes root. The acquirers (server,
|
|
51
|
-
* runner,
|
|
51
|
+
* runner, the official plugin catalogue) keep their `<root>/<version>/node_modules/@stigmer/<pkg>`
|
|
52
52
|
* layout under it, so a root that already holds the packages is used as-is
|
|
53
53
|
* and nothing is downloaded. The all-in-one image bakes exactly that layout
|
|
54
54
|
* read-only off its data volume — a `stigmer up` whose first-run acquisitions
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// Pins the default-set step over a real Connect backend:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Pins the default-set step over a real Connect backend: preparation is
|
|
2
|
+
// all-or-nothing and touches no backend; a default absent from the org is
|
|
3
|
+
// pushed public with the CLI's message; one present at the same digest,
|
|
4
|
+
// READY and public is skipped without a push; a changed digest, a non-READY
|
|
5
|
+
// state or a non-public visibility pushes again; the defaults go in the
|
|
6
|
+
// marketplace's order; one push failure does not stop the next; and a
|
|
6
7
|
// second run over a converged backend pushes nothing.
|
|
7
8
|
|
|
8
9
|
import { createHash } from "node:crypto";
|
|
@@ -35,7 +36,7 @@ import type { Stigmer } from "@stigmer/sdk";
|
|
|
35
36
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
36
37
|
import { writeCursorMarketplace } from "../../marketplace/__fixtures__/cursor-marketplace.js";
|
|
37
38
|
import { preparePluginPush } from "../../resources/plugin.js";
|
|
38
|
-
import {
|
|
39
|
+
import { installPreparedDefaults, prepareDefaultPlugins } from "./defaults.js";
|
|
39
40
|
|
|
40
41
|
let backend: Http2Server;
|
|
41
42
|
let stigmer: Stigmer;
|
|
@@ -145,15 +146,47 @@ function digestOf(bytes: Uint8Array): string {
|
|
|
145
146
|
|
|
146
147
|
const say = (): void => {};
|
|
147
148
|
|
|
148
|
-
function run() {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{
|
|
149
|
+
async function run() {
|
|
150
|
+
const prepared = await prepareDefaultPlugins({ repoDir: () => official });
|
|
151
|
+
return installPreparedDefaults(
|
|
152
|
+
{ stigmer, info: say, verb: "stigmer bootstrap" },
|
|
153
|
+
prepared,
|
|
154
|
+
"stigmer",
|
|
152
155
|
);
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
describe("
|
|
156
|
-
it("
|
|
158
|
+
describe("prepareDefaultPlugins", () => {
|
|
159
|
+
it("prepares every default in the marketplace's order without touching the backend", async () => {
|
|
160
|
+
const prepared = await prepareDefaultPlugins({ repoDir: () => official });
|
|
161
|
+
expect(prepared.map((entry) => [entry.name, entry.push.digest])).toEqual([
|
|
162
|
+
["thermos", thermosDigest],
|
|
163
|
+
["github", githubDigest],
|
|
164
|
+
]);
|
|
165
|
+
expect(pushes).toHaveLength(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("refuses the whole set when one default cannot be prepared", async () => {
|
|
169
|
+
const broken = mkdtempSync(join(tmpdir(), "stigmer-defaults-broken-"));
|
|
170
|
+
try {
|
|
171
|
+
writeFileSync(
|
|
172
|
+
join(broken, "marketplace.json"),
|
|
173
|
+
JSON.stringify({
|
|
174
|
+
name: "stigmer",
|
|
175
|
+
plugins: [{ name: "missing", source: "./missing" }],
|
|
176
|
+
defaults: ["missing"],
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
await expect(
|
|
180
|
+
prepareDefaultPlugins({ repoDir: () => broken }),
|
|
181
|
+
).rejects.toThrow(/1 plugin this CLI cannot install/);
|
|
182
|
+
} finally {
|
|
183
|
+
rmSync(broken, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("installPreparedDefaults", () => {
|
|
189
|
+
it("installs every default in order, public, with the invoking verb in the message, on a fresh backend", async () => {
|
|
157
190
|
const result = await run();
|
|
158
191
|
expect(result.outcomes.map((o) => [o.name, o.action])).toEqual([
|
|
159
192
|
["thermos", "installed"],
|
|
@@ -167,7 +200,7 @@ describe("installDefaultPlugins", () => {
|
|
|
167
200
|
),
|
|
168
201
|
).toBe(true);
|
|
169
202
|
expect(pushes[0]?.message).toMatch(
|
|
170
|
-
/^default plugin, installed by stigmer
|
|
203
|
+
/^default plugin, installed by stigmer bootstrap \(/,
|
|
171
204
|
);
|
|
172
205
|
expect(digestOf(pushes[0]!.artifact)).toBe(thermosDigest);
|
|
173
206
|
expect(digestOf(pushes[1]!.artifact)).toBe(githubDigest);
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
// The default plugins a
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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, so the global default-agent lookup and cross-org references find
|
|
4
|
+
// them.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
6
|
+
// Two phases, deliberately separate. Preparing (resolve the official tree,
|
|
7
|
+
// which may acquire `@stigmer/plugins` at the CLI's version; walk, zip and
|
|
8
|
+
// hash every default) needs no backend and is all-or-nothing: the catalogue
|
|
9
|
+
// is ours and validated on CI, so a default that cannot be prepared is a
|
|
10
|
+
// release defect, not a condition to install around. Installing pushes the
|
|
11
|
+
// prepared archives and is best-effort per entry: one default failing to
|
|
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).
|
|
14
15
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// Idempotent by the server's own identity, not a marker file: before each
|
|
17
|
+
// push the org's plugin of that name is read back; when its digest equals the
|
|
18
|
+
// prepared archive's, it is READY and it is public, there is nothing to do
|
|
19
|
+
// and nothing is sent. A second `stigmer up` therefore pushes nothing, and a
|
|
20
|
+
// `stigmer up` after an upgrade pushes exactly the entries whose content
|
|
21
|
+
// changed. The record is the server, so any machine that reaches the same
|
|
22
|
+
// backend sees the same answer.
|
|
18
23
|
|
|
19
24
|
import { type Stigmer, isNotFound } from "@stigmer/sdk";
|
|
20
25
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
21
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";
|
|
22
29
|
import { log } from "../../logger.js";
|
|
23
30
|
import { VERSION } from "../../version.js";
|
|
24
31
|
import {
|
|
@@ -29,21 +36,30 @@ import { readMarketplaceTree } from "../../marketplace/read.js";
|
|
|
29
36
|
import { prepareEntry } from "../../marketplace/install.js";
|
|
30
37
|
import {
|
|
31
38
|
type PreparedPluginPush,
|
|
39
|
+
count,
|
|
32
40
|
pushPrepared,
|
|
33
41
|
} from "../../resources/plugin.js";
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The command that ran the install, as the plugin's stored version message
|
|
45
|
+
* names it. Two verbs share the one bootstrap function, and a platform
|
|
46
|
+
* operator reading the row a year later must see which one wrote it.
|
|
47
|
+
*/
|
|
48
|
+
export type InstallingVerb = "stigmer up" | "stigmer bootstrap";
|
|
49
|
+
|
|
35
50
|
export interface DefaultPluginsDeps {
|
|
36
51
|
/** The client bound to the backend being bootstrapped. */
|
|
37
52
|
readonly stigmer: Stigmer;
|
|
38
53
|
/** Human progress lines (stderr). */
|
|
39
54
|
readonly info: (line: string) => void;
|
|
55
|
+
/** Stamped into each installed plugin's version message. */
|
|
56
|
+
readonly verb: InstallingVerb;
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
readonly
|
|
45
|
-
|
|
46
|
-
readonly official?: ResolveOfficialOptions;
|
|
59
|
+
/** One default, ready to push: its marketplace name and the prepared archive. */
|
|
60
|
+
export interface PreparedDefault {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly push: PreparedPluginPush;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
export type DefaultPluginOutcome =
|
|
@@ -69,29 +85,56 @@ export interface DefaultPluginsResult {
|
|
|
69
85
|
readonly failed: readonly string[];
|
|
70
86
|
}
|
|
71
87
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Phase 1: every default the official marketplace names, prepared in the
|
|
90
|
+
* marketplace's order. Throws when the tree cannot be resolved, offers less
|
|
91
|
+
* than it lists (the library drops an entry it cannot install with a
|
|
92
|
+
* warning, which for a user's marketplace is `show`'s business and for ours
|
|
93
|
+
* is a broken release), or any default cannot be prepared; nothing is
|
|
94
|
+
* returned half-ready.
|
|
95
|
+
*/
|
|
96
|
+
export async function prepareDefaultPlugins(
|
|
97
|
+
official?: ResolveOfficialOptions,
|
|
98
|
+
): Promise<readonly PreparedDefault[]> {
|
|
99
|
+
const resolved = resolveOfficialMarketplace(official);
|
|
100
|
+
const tree = readMarketplaceTree(resolved.dir, "the official marketplace");
|
|
101
|
+
if (tree.warnings.length > 0) {
|
|
102
|
+
throw new CliExitError(
|
|
103
|
+
`the official marketplace at ${resolved.dir} lists ${count(tree.warnings.length, "plugin")} this CLI cannot install`,
|
|
104
|
+
ExitCode.General,
|
|
105
|
+
tree.warnings.map((finding) => finding.message),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const prepared: PreparedDefault[] = [];
|
|
109
|
+
for (const name of tree.marketplace.defaults) {
|
|
110
|
+
prepared.push({ name, push: await prepareEntry(tree, name) });
|
|
111
|
+
}
|
|
112
|
+
return prepared;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Phase 2: install the prepared defaults into `org`, skipping what the
|
|
117
|
+
* backend already holds. Best-effort per entry.
|
|
118
|
+
*/
|
|
119
|
+
export async function installPreparedDefaults(
|
|
74
120
|
deps: DefaultPluginsDeps,
|
|
75
|
-
|
|
121
|
+
prepared: readonly PreparedDefault[],
|
|
122
|
+
org: string,
|
|
76
123
|
): Promise<DefaultPluginsResult> {
|
|
77
|
-
const official = resolveOfficialMarketplace(options.official);
|
|
78
|
-
const tree = readMarketplaceTree(official.dir, "the official marketplace");
|
|
79
|
-
|
|
80
124
|
const outcomes: DefaultPluginOutcome[] = [];
|
|
81
|
-
for (const name of
|
|
125
|
+
for (const { name, push } of prepared) {
|
|
82
126
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
outcomes.push({ name, action: "up-to-date", digest: prepared.digest });
|
|
127
|
+
if (await isAlreadyInstalled(deps.stigmer, org, push)) {
|
|
128
|
+
outcomes.push({ name, action: "up-to-date", digest: push.digest });
|
|
86
129
|
continue;
|
|
87
130
|
}
|
|
88
131
|
deps.info(`Installing default plugin '${name}'…`);
|
|
89
|
-
await pushPrepared(deps.stigmer,
|
|
90
|
-
org
|
|
132
|
+
await pushPrepared(deps.stigmer, push, {
|
|
133
|
+
org,
|
|
91
134
|
visibility: ApiResourceVisibility.visibility_public,
|
|
92
|
-
message: `default plugin, installed by
|
|
135
|
+
message: `default plugin, installed by ${deps.verb} (${VERSION})`,
|
|
93
136
|
});
|
|
94
|
-
outcomes.push({ name, action: "installed", digest:
|
|
137
|
+
outcomes.push({ name, action: "installed", digest: push.digest });
|
|
95
138
|
} catch (error) {
|
|
96
139
|
const message = error instanceof Error ? error.message : String(error);
|
|
97
140
|
log.warn("default plugin install failed", { name, error: message });
|
|
@@ -36,6 +36,8 @@ describe("isAcquirableRelease", () => {
|
|
|
36
36
|
expect(isAcquirableRelease("0.5.0-rc.1")).toBe(true);
|
|
37
37
|
expect(isAcquirableRelease("0.0.0-dev")).toBe(false);
|
|
38
38
|
expect(isAcquirableRelease("0.5.0-dev.20260825")).toBe(false);
|
|
39
|
+
// The bare stamp an unbundled server reports; the substring test this once was let it through.
|
|
40
|
+
expect(isAcquirableRelease("dev")).toBe(false);
|
|
39
41
|
});
|
|
40
42
|
});
|
|
41
43
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import { isReleaseVersion } from "@stigmer/plugin-package/client";
|
|
10
11
|
import { CliExitError } from "../../errors/cli-exit-error.js";
|
|
11
12
|
import { ExitCode } from "../../errors/exit-codes.js";
|
|
12
13
|
|
|
@@ -64,9 +65,11 @@ export function npmInstallIntoRuntimes(installDir: string, spec: string): void {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
// A source build reports "0.0.0-dev"
|
|
68
|
-
// "<v>-dev.<stamp>"
|
|
69
|
-
// so
|
|
68
|
+
// A source build reports "0.0.0-dev", an unbundled server the bare "dev",
|
|
69
|
+
// and the dev npm channel stamps "<v>-dev.<stamp>"; none publishes matching
|
|
70
|
+
// runtime packages, so none is acquirable. Release and rc/next versions are.
|
|
71
|
+
// The rule is the shared client predicate, so the console's read of the
|
|
72
|
+
// official catalogue and this acquisition agree on every version.
|
|
70
73
|
export function isAcquirableRelease(version: string): boolean {
|
|
71
|
-
return
|
|
74
|
+
return isReleaseVersion(version);
|
|
72
75
|
}
|