@stigmer/cli 3.17.0 → 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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// `stigmer bootstrap` — make the configured backend a ready Stigmer: the
|
|
2
|
+
// system organization exists and the official marketplace's default plugins
|
|
3
|
+
// are installed into it, public. `stigmer up` does this for the local stack
|
|
4
|
+
// on its own; this verb is for every other shape (a raw self-hosted server
|
|
5
|
+
// behind docker-compose or Helm, the hosted platform's scheduled lane), and
|
|
6
|
+
// runs the very same two phases (local/bootstrap.ts), so "ready" has one
|
|
7
|
+
// meaning everywhere.
|
|
8
|
+
//
|
|
9
|
+
// Flagless on purpose. The org is the system org in every edition, and the
|
|
10
|
+
// defaults are idempotent by the server's digest, so there is nothing to
|
|
11
|
+
// force: a second run over a converged backend changes nothing and says so.
|
|
12
|
+
// The verb never deletes anything; a scheduled lane must be able to run it
|
|
13
|
+
// without that power.
|
|
14
|
+
//
|
|
15
|
+
// Exit is non-zero when any default failed to land, after the outcomes are
|
|
16
|
+
// rendered, so a lane's run is red for the right reason and a human reads
|
|
17
|
+
// what did and did not install. Heavy modules load lazily inside the action
|
|
18
|
+
// so `--help` stays fast.
|
|
19
|
+
|
|
20
|
+
import type { Command } from "commander";
|
|
21
|
+
import { ensureAuthenticated } from "../config/index.js";
|
|
22
|
+
import { CliExitError } from "../errors/cli-exit-error.js";
|
|
23
|
+
import { ExitCode } from "../errors/exit-codes.js";
|
|
24
|
+
import { CommandResult, type OutputFlags, renderResult } from "../output/index.js";
|
|
25
|
+
import { addResultFlags, resultFormat } from "./shared.js";
|
|
26
|
+
|
|
27
|
+
export function registerBootstrap(program: Command): void {
|
|
28
|
+
const bootstrap = program
|
|
29
|
+
.command("bootstrap")
|
|
30
|
+
.description(
|
|
31
|
+
"make the configured backend ready: ensure the system organization and install the default plugins",
|
|
32
|
+
)
|
|
33
|
+
.action((options: OutputFlags) => runBootstrapCommand(options));
|
|
34
|
+
addResultFlags(bootstrap);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function runBootstrapCommand(flags: OutputFlags): Promise<void> {
|
|
38
|
+
const [{ connectBackend }, { bootstrapBackend }, { SYSTEM_ORG }] =
|
|
39
|
+
await Promise.all([
|
|
40
|
+
import("../backend.js"),
|
|
41
|
+
import("../local/bootstrap.js"),
|
|
42
|
+
import("../local/system-org.js"),
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const client = connectBackend();
|
|
46
|
+
ensureAuthenticated(client.config);
|
|
47
|
+
|
|
48
|
+
const say = (line: string): void => {
|
|
49
|
+
process.stderr.write(`${line}\n`);
|
|
50
|
+
};
|
|
51
|
+
const result = await bootstrapBackend(client.stigmer, say);
|
|
52
|
+
|
|
53
|
+
const failed = result.plugins.failed;
|
|
54
|
+
const out =
|
|
55
|
+
failed.length === 0
|
|
56
|
+
? CommandResult.success("Backend is ready")
|
|
57
|
+
: CommandResult.warning(
|
|
58
|
+
`Backend bootstrapped with ${failed.length} default plugin${failed.length === 1 ? "" : "s"} not installed`,
|
|
59
|
+
);
|
|
60
|
+
out
|
|
61
|
+
.addSection("System organization")
|
|
62
|
+
.field("Slug", SYSTEM_ORG)
|
|
63
|
+
.field("State", result.org);
|
|
64
|
+
const plugins = out.addSection("Default plugins");
|
|
65
|
+
for (const outcome of result.plugins.outcomes) {
|
|
66
|
+
switch (outcome.action) {
|
|
67
|
+
case "installed":
|
|
68
|
+
case "up-to-date":
|
|
69
|
+
plugins.field(outcome.name, outcome.action);
|
|
70
|
+
break;
|
|
71
|
+
case "failed":
|
|
72
|
+
plugins.field(outcome.name, `failed: ${outcome.error}`);
|
|
73
|
+
break;
|
|
74
|
+
default: {
|
|
75
|
+
const exhaustive: never = outcome;
|
|
76
|
+
throw new Error(`unhandled outcome ${String(exhaustive)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (failed.length > 0) {
|
|
81
|
+
out.hint("Fix the cause named above and run 'stigmer bootstrap' again.");
|
|
82
|
+
}
|
|
83
|
+
renderResult(out, resultFormat(flags));
|
|
84
|
+
|
|
85
|
+
if (failed.length > 0) {
|
|
86
|
+
throw new CliExitError(
|
|
87
|
+
`default plugin${failed.length === 1 ? "" : "s"} not installed: ${failed.join(", ")}`,
|
|
88
|
+
ExitCode.General,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// the folder prepares to); --dry-run pushes nothing; a version pin, a path,
|
|
5
5
|
// an unknown name and an ambiguous name refuse with their sentences. A real
|
|
6
6
|
// Connect backend over h2c serves the plugin controllers; the config file
|
|
7
|
-
// (HOME redirected) points a selfhost backend at it.
|
|
7
|
+
// (HOME redirected) points a selfhost backend at it. The global fetch is
|
|
8
|
+
// stubbed: the built-in vendor sources are asked through their marketplace
|
|
9
|
+
// files (answered 404, so they declare nothing) and any download would fail
|
|
10
|
+
// loudly, which pins that a bare install never fetches a vendor's zipball.
|
|
8
11
|
|
|
9
12
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
10
13
|
import {
|
|
@@ -101,8 +104,20 @@ afterAll(async () => {
|
|
|
101
104
|
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
102
105
|
});
|
|
103
106
|
|
|
107
|
+
/** Every URL the stubbed fetch was asked; raw files answer 404, anything else refuses. */
|
|
108
|
+
let fetched: string[] = [];
|
|
109
|
+
|
|
104
110
|
beforeEach(() => {
|
|
105
111
|
pushes = [];
|
|
112
|
+
fetched = [];
|
|
113
|
+
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
|
|
114
|
+
const url = String(input);
|
|
115
|
+
fetched.push(url);
|
|
116
|
+
if (url.startsWith("https://raw.githubusercontent.com/")) {
|
|
117
|
+
return new Response("Not Found", { status: 404 });
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`unexpected network request in test: ${url}`);
|
|
120
|
+
});
|
|
106
121
|
originalHome = process.env.HOME;
|
|
107
122
|
home = mkdtempSync(join(tmpdir(), "stigmer-install-cmd-"));
|
|
108
123
|
fixture = mkdtempSync(join(tmpdir(), "stigmer-install-cmd-fixture-"));
|
|
@@ -122,7 +137,7 @@ beforeEach(() => {
|
|
|
122
137
|
"context:",
|
|
123
138
|
" organization: acme",
|
|
124
139
|
"marketplaces:",
|
|
125
|
-
"
|
|
140
|
+
" acme-plugins:",
|
|
126
141
|
" type: local",
|
|
127
142
|
` path: ${fixture}`,
|
|
128
143
|
"",
|
|
@@ -131,6 +146,7 @@ beforeEach(() => {
|
|
|
131
146
|
});
|
|
132
147
|
|
|
133
148
|
afterEach(() => {
|
|
149
|
+
vi.unstubAllGlobals();
|
|
134
150
|
if (originalHome === undefined) delete process.env.HOME;
|
|
135
151
|
else process.env.HOME = originalHome;
|
|
136
152
|
rmSync(home, { recursive: true, force: true });
|
|
@@ -184,7 +200,7 @@ async function run(
|
|
|
184
200
|
|
|
185
201
|
describe("install", () => {
|
|
186
202
|
it("pushes the entry's folder as push plugin would, into the context org, and renders the install", async () => {
|
|
187
|
-
const outcome = await run("
|
|
203
|
+
const outcome = await run("acme-plugins/thermos", "--json");
|
|
188
204
|
expect(outcome.exitCode).toBe(ExitCode.Success);
|
|
189
205
|
expect(pushes).toHaveLength(1);
|
|
190
206
|
const expected = await preparePluginPush(join(fixture, "thermos"));
|
|
@@ -195,7 +211,7 @@ describe("install", () => {
|
|
|
195
211
|
),
|
|
196
212
|
).toBe(true);
|
|
197
213
|
expect(pushes[0]?.message).toBe(
|
|
198
|
-
"installed from marketplace '
|
|
214
|
+
"installed from marketplace 'acme-plugins'",
|
|
199
215
|
);
|
|
200
216
|
|
|
201
217
|
const payload = JSON.parse(outcome.stdout);
|
|
@@ -207,7 +223,7 @@ describe("install", () => {
|
|
|
207
223
|
);
|
|
208
224
|
expect(about.fields).toContainEqual({
|
|
209
225
|
key: "Marketplace",
|
|
210
|
-
value: `
|
|
226
|
+
value: `acme-plugins (${fixture})`,
|
|
211
227
|
});
|
|
212
228
|
expect(payload.data.members).toHaveLength(2);
|
|
213
229
|
});
|
|
@@ -229,7 +245,7 @@ describe("install", () => {
|
|
|
229
245
|
|
|
230
246
|
it("--dry-run reads the marketplace and describes the plugin without pushing", async () => {
|
|
231
247
|
const outcome = await run(
|
|
232
|
-
"
|
|
248
|
+
"acme-plugins/thermos@1.0.0",
|
|
233
249
|
"--dry-run",
|
|
234
250
|
"--json",
|
|
235
251
|
);
|
|
@@ -237,13 +253,13 @@ describe("install", () => {
|
|
|
237
253
|
expect(pushes).toHaveLength(0);
|
|
238
254
|
const payload = JSON.parse(outcome.stdout);
|
|
239
255
|
expect(payload.message).toBe(
|
|
240
|
-
"Dry run: '
|
|
256
|
+
"Dry run: 'acme-plugins/thermos' would install plugin 'thermos'",
|
|
241
257
|
);
|
|
242
258
|
expect(payload.data.plugin.name).toBe("thermos");
|
|
243
259
|
});
|
|
244
260
|
|
|
245
261
|
it("refuses a version the marketplace does not offer, naming the one it does", async () => {
|
|
246
|
-
const outcome = await run("
|
|
262
|
+
const outcome = await run("acme-plugins/thermos@9.9.9");
|
|
247
263
|
expect(outcome.exitCode).toBe(ExitCode.Usage);
|
|
248
264
|
expect(outcome.message).toMatch(
|
|
249
265
|
/pins version 9\.9\.9, but the marketplace offers 'thermos' at 1\.0\.0/,
|
|
@@ -280,8 +296,11 @@ describe("install", () => {
|
|
|
280
296
|
const outcome = await run("thermos");
|
|
281
297
|
expect(outcome.exitCode).toBe(ExitCode.Usage);
|
|
282
298
|
expect(outcome.message).toMatch(
|
|
283
|
-
/offered by more than one marketplace:
|
|
299
|
+
/offered by more than one marketplace: acme-plugins, second\n\nName the one you mean: acme-plugins\/thermos or second\/thermos/,
|
|
284
300
|
);
|
|
301
|
+
// The three built-in vendor sources were asked through their files and nothing was downloaded.
|
|
302
|
+
expect(fetched.filter((url) => url.startsWith("https://raw.githubusercontent.com/cursor/plugins/")).length).toBeGreaterThan(0);
|
|
303
|
+
expect(fetched.some((url) => url.startsWith("https://codeload.github.com/"))).toBe(false);
|
|
285
304
|
} finally {
|
|
286
305
|
rmSync(second, { recursive: true, force: true });
|
|
287
306
|
}
|
|
@@ -78,14 +78,14 @@ describe("marketplace add", () => {
|
|
|
78
78
|
expect(outcome.exitCode).toBe(ExitCode.Success);
|
|
79
79
|
const payload = JSON.parse(outcome.stdout);
|
|
80
80
|
expect(payload.message).toBe(
|
|
81
|
-
`Added
|
|
81
|
+
`Added source 'acme-plugins' (${fixture}), offering 2 plugins`,
|
|
82
82
|
);
|
|
83
83
|
expect(payload.data).toEqual({
|
|
84
|
-
name: "
|
|
84
|
+
name: "acme-plugins",
|
|
85
85
|
source: { type: "local", path: fixture },
|
|
86
86
|
plugins: 2,
|
|
87
87
|
});
|
|
88
|
-
expect(load().marketplaces?.["
|
|
88
|
+
expect(load().marketplaces?.["acme-plugins"]).toEqual({
|
|
89
89
|
type: "local",
|
|
90
90
|
path: fixture,
|
|
91
91
|
});
|
|
@@ -108,9 +108,14 @@ describe("marketplace add", () => {
|
|
|
108
108
|
expect(load().marketplaces).toBeUndefined();
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
it("refuses
|
|
111
|
+
it("refuses every built-in name toward --name, and a duplicate", async () => {
|
|
112
112
|
expect((await run("add", fixture, "--name", "stigmer")).message).toMatch(
|
|
113
|
-
/built-in
|
|
113
|
+
/'stigmer' is a built-in source and cannot be added or replaced[\s\S]*--name/,
|
|
114
|
+
);
|
|
115
|
+
// A local clone of a vendor's catalogue names itself after the vendor; the
|
|
116
|
+
// built-in wins the name and the user is told how to add the clone anyway.
|
|
117
|
+
expect((await run("add", fixture, "--name", "cursor-plugins")).message).toMatch(
|
|
118
|
+
/'cursor-plugins' is a built-in source/,
|
|
114
119
|
);
|
|
115
120
|
await run("add", fixture);
|
|
116
121
|
expect((await run("add", fixture)).message).toMatch(/already configured/);
|
|
@@ -118,7 +123,7 @@ describe("marketplace add", () => {
|
|
|
118
123
|
});
|
|
119
124
|
|
|
120
125
|
describe("marketplace list", () => {
|
|
121
|
-
it("is offline,
|
|
126
|
+
it("is offline, the built-ins first, and names an entry it cannot read", async () => {
|
|
122
127
|
await run("add", fixture);
|
|
123
128
|
const { save } = await import("../config/index.js");
|
|
124
129
|
const config = load();
|
|
@@ -133,7 +138,13 @@ describe("marketplace list", () => {
|
|
|
133
138
|
expect(payload.status).toBe("warning");
|
|
134
139
|
expect(payload.data.marketplaces).toEqual([
|
|
135
140
|
{ name: "stigmer", source: { type: "official" } },
|
|
136
|
-
{ name: "cursor-plugins", source: { type: "
|
|
141
|
+
{ name: "cursor-plugins", source: { type: "github", repo: "cursor/plugins" } },
|
|
142
|
+
{
|
|
143
|
+
name: "claude-code-plugins",
|
|
144
|
+
source: { type: "github", repo: "anthropics/claude-code" },
|
|
145
|
+
},
|
|
146
|
+
{ name: "codex-plugins", source: { type: "github", repo: "openai/plugins" } },
|
|
147
|
+
{ name: "acme-plugins", source: { type: "local", path: fixture } },
|
|
137
148
|
{
|
|
138
149
|
name: "broken",
|
|
139
150
|
unreadable: "unknown type 'gitlab' (expected 'github' or 'local')",
|
|
@@ -145,11 +156,11 @@ describe("marketplace list", () => {
|
|
|
145
156
|
describe("marketplace show", () => {
|
|
146
157
|
it("lists the offered plugins with their manifest versions and the entries it could not offer", async () => {
|
|
147
158
|
await run("add", fixture);
|
|
148
|
-
const outcome = await run("show", "
|
|
159
|
+
const outcome = await run("show", "acme-plugins", "--json");
|
|
149
160
|
expect(outcome.exitCode).toBe(ExitCode.Success);
|
|
150
161
|
const payload = JSON.parse(outcome.stdout);
|
|
151
162
|
expect(payload.message).toBe(
|
|
152
|
-
"Marketplace '
|
|
163
|
+
"Marketplace 'acme-plugins' offers 2 plugins, with 1 warning",
|
|
153
164
|
);
|
|
154
165
|
expect(payload.data.plugins).toEqual([
|
|
155
166
|
{
|
|
@@ -184,17 +195,20 @@ describe("marketplace show", () => {
|
|
|
184
195
|
});
|
|
185
196
|
|
|
186
197
|
describe("marketplace remove", () => {
|
|
187
|
-
it("removes
|
|
198
|
+
it("removes an added source and refuses a built-in and an unknown one", async () => {
|
|
188
199
|
await run("add", fixture);
|
|
189
|
-
expect((await run("remove", "
|
|
200
|
+
expect((await run("remove", "acme-plugins")).exitCode).toBe(
|
|
190
201
|
ExitCode.Success,
|
|
191
202
|
);
|
|
192
203
|
expect(load().marketplaces).toBeUndefined();
|
|
193
204
|
expect((await run("remove", "stigmer")).message).toMatch(
|
|
194
|
-
/cannot be removed/,
|
|
205
|
+
/'stigmer' is a built-in source and cannot be removed/,
|
|
206
|
+
);
|
|
207
|
+
expect((await run("remove", "codex-plugins")).message).toMatch(
|
|
208
|
+
/'codex-plugins' is a built-in source and cannot be removed/,
|
|
195
209
|
);
|
|
196
|
-
expect((await run("remove", "
|
|
197
|
-
/no marketplace named '
|
|
210
|
+
expect((await run("remove", "acme-plugins")).message).toMatch(
|
|
211
|
+
/no marketplace named 'acme-plugins'/,
|
|
198
212
|
);
|
|
199
213
|
});
|
|
200
214
|
});
|
|
@@ -22,12 +22,14 @@ interface AddFlags extends OutputFlags {
|
|
|
22
22
|
export function registerMarketplace(program: Command): void {
|
|
23
23
|
const marketplace = program
|
|
24
24
|
.command("marketplace")
|
|
25
|
-
.description(
|
|
25
|
+
.description(
|
|
26
|
+
"manage the sources the Marketplace installs from: the built-in catalogues and the ones you add",
|
|
27
|
+
);
|
|
26
28
|
|
|
27
29
|
const add = marketplace
|
|
28
30
|
.command("add <source>")
|
|
29
31
|
.description(
|
|
30
|
-
"add a
|
|
32
|
+
"add a source: a directory, a GitHub 'owner/repo[@ref]', or that repository's URL",
|
|
31
33
|
)
|
|
32
34
|
.option(
|
|
33
35
|
"--name <name>",
|
|
@@ -38,19 +40,19 @@ export function registerMarketplace(program: Command): void {
|
|
|
38
40
|
|
|
39
41
|
const list = marketplace
|
|
40
42
|
.command("list")
|
|
41
|
-
.description("list the
|
|
43
|
+
.description("list the known sources, built in and added (offline)")
|
|
42
44
|
.action((options: OutputFlags) => runList(options));
|
|
43
45
|
addResultFlags(list);
|
|
44
46
|
|
|
45
47
|
const show = marketplace
|
|
46
48
|
.command("show <name>")
|
|
47
|
-
.description("show the plugins a
|
|
49
|
+
.description("show the plugins a source offers (reads it)")
|
|
48
50
|
.action((name: string, options: OutputFlags) => runShow(name, options));
|
|
49
51
|
addResultFlags(show);
|
|
50
52
|
|
|
51
53
|
const remove = marketplace
|
|
52
54
|
.command("remove <name>")
|
|
53
|
-
.description("remove
|
|
55
|
+
.description("remove an added source (installed plugins stay; a built-in cannot be removed)")
|
|
54
56
|
.action((name: string, options: OutputFlags) => runRemove(name, options));
|
|
55
57
|
addResultFlags(remove);
|
|
56
58
|
}
|
|
@@ -81,7 +83,7 @@ async function runAdd(source: string, flags: AddFlags): Promise<void> {
|
|
|
81
83
|
m.addMarketplace(name, parsed);
|
|
82
84
|
|
|
83
85
|
const result = CommandResult.success(
|
|
84
|
-
`Added
|
|
86
|
+
`Added source '${name}' (${m.describeSource(parsed)}), offering ${offered} plugin${offered === 1 ? "" : "s"}`,
|
|
85
87
|
);
|
|
86
88
|
result.hint(`See what it offers with: stigmer marketplace show ${name}`);
|
|
87
89
|
result.hint(`Install from it with: stigmer install ${name}/<plugin>`);
|
|
@@ -123,7 +125,7 @@ async function runRemove(name: string, flags: OutputFlags): Promise<void> {
|
|
|
123
125
|
const m = await import("../marketplace/index.js");
|
|
124
126
|
const { CommandResult } = await import("../output/index.js");
|
|
125
127
|
m.removeMarketplace(name);
|
|
126
|
-
const result = CommandResult.success(`Removed
|
|
128
|
+
const result = CommandResult.success(`Removed source '${name}'`);
|
|
127
129
|
result.hint(
|
|
128
130
|
"Plugins installed from it stay installed; remove one with: stigmer delete plugin <name>",
|
|
129
131
|
);
|
|
@@ -150,11 +150,11 @@ describe("resolveOrganization", () => {
|
|
|
150
150
|
);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("defaults to the
|
|
153
|
+
it("defaults to the system org in local mode when nothing is configured", () => {
|
|
154
154
|
expect(resolveOrganization(localConfig())).toBe("stigmer");
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it("defaults to the
|
|
157
|
+
it("defaults to the system org on a selfhost backend (it IS the OSS server)", () => {
|
|
158
158
|
expect(resolveOrganization(selfhostConfig())).toBe("stigmer");
|
|
159
159
|
});
|
|
160
160
|
|
package/src/config/resolve.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { CliExitError, ExitCode } from "../errors/index.js";
|
|
10
10
|
import { SERVER_PORT } from "../local/constants.js";
|
|
11
|
+
import { SYSTEM_ORG } from "../local/system-org.js";
|
|
11
12
|
import { type Config, activeBackend, isCloudMode } from "./config.js";
|
|
12
13
|
|
|
13
14
|
const DEFAULT_CLOUD_ENDPOINT = "api.stigmer.ai:443";
|
|
@@ -16,13 +17,13 @@ const DEFAULT_LOCAL_ENDPOINT = "localhost:7234";
|
|
|
16
17
|
/** Well-known URL for the Stigmer Cloud web console. */
|
|
17
18
|
export const DEFAULT_CLOUD_CONSOLE_URL = "https://app.stigmer.ai";
|
|
18
19
|
|
|
19
|
-
// Local mode is single-tenant: the
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
const DEFAULT_LOCAL_ORG =
|
|
20
|
+
// Local mode is single-tenant: the bootstrap `stigmer up` runs creates the
|
|
21
|
+
// system org and installs the default plugins into it, so bare-slug
|
|
22
|
+
// `get`/`run`/`apply` in local mode fall back to that org when nothing else
|
|
23
|
+
// is configured. The same holds for selfhost backends — they ARE the OSS
|
|
24
|
+
// server. Cloud mode has no implicit org — the caller must select one
|
|
25
|
+
// (flag/env/context/login).
|
|
26
|
+
const DEFAULT_LOCAL_ORG = SYSTEM_ORG;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Resolve the server endpoint.
|
|
@@ -1,71 +1,143 @@
|
|
|
1
|
-
// Pins the bootstrap's shape:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
|
7
|
+
// bootstrap.
|
|
5
8
|
|
|
6
9
|
import { describe, expect, it } from "vitest";
|
|
10
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
7
11
|
import type { BackendClient } from "../client/index.js";
|
|
8
12
|
import {
|
|
13
|
+
type BootstrapResult,
|
|
14
|
+
type PreparedBootstrap,
|
|
9
15
|
bootstrapLocalBackend,
|
|
10
|
-
installDefaultPluginsBestEffort,
|
|
11
16
|
} from "./bootstrap.js";
|
|
12
17
|
|
|
13
|
-
const
|
|
18
|
+
const stigmer = {} as unknown as Stigmer;
|
|
19
|
+
const client = { stigmer } as unknown as BackendClient;
|
|
20
|
+
const prepared: PreparedBootstrap = { defaults: [] };
|
|
21
|
+
|
|
22
|
+
function converged(): BootstrapResult {
|
|
23
|
+
return { org: "present", plugins: { outcomes: [], failed: [] } };
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
describe("bootstrapLocalBackend", () => {
|
|
16
|
-
it("runs
|
|
27
|
+
it("prepares, runs, then retires, over the same client and home", async () => {
|
|
17
28
|
const order: string[] = [];
|
|
18
29
|
await bootstrapLocalBackend("/home/x", {
|
|
19
30
|
client,
|
|
20
31
|
say: () => {},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
order.push("seedpack");
|
|
32
|
+
prepare: async () => {
|
|
33
|
+
order.push("prepare");
|
|
34
|
+
return prepared;
|
|
25
35
|
},
|
|
26
|
-
|
|
27
|
-
expect(
|
|
36
|
+
run: async (s, p) => {
|
|
37
|
+
expect(s).toBe(stigmer);
|
|
38
|
+
expect(p).toBe(prepared);
|
|
39
|
+
order.push("run");
|
|
40
|
+
return converged();
|
|
41
|
+
},
|
|
42
|
+
retire: async (s, home) => {
|
|
43
|
+
expect(s).toBe(stigmer);
|
|
28
44
|
expect(home).toBe("/home/x");
|
|
29
|
-
order.push("
|
|
45
|
+
order.push("retire");
|
|
30
46
|
},
|
|
31
47
|
});
|
|
32
|
-
expect(order).toEqual(["
|
|
48
|
+
expect(order).toEqual(["prepare", "run", "retire"]);
|
|
33
49
|
});
|
|
34
50
|
|
|
35
|
-
it("
|
|
51
|
+
it("skips the run and the retire when preparation fails, with one warning", async () => {
|
|
36
52
|
const said: string[] = [];
|
|
37
|
-
let
|
|
53
|
+
let retired = 0;
|
|
54
|
+
let ran = 0;
|
|
38
55
|
await bootstrapLocalBackend("/home/x", {
|
|
39
56
|
client,
|
|
40
57
|
say: (line) => said.push(line),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
prepare: async () => {
|
|
59
|
+
throw new Error("offline");
|
|
60
|
+
},
|
|
61
|
+
run: async () => {
|
|
62
|
+
ran += 1;
|
|
63
|
+
return converged();
|
|
64
|
+
},
|
|
65
|
+
retire: async () => {
|
|
66
|
+
retired += 1;
|
|
44
67
|
},
|
|
45
68
|
});
|
|
46
|
-
expect(
|
|
47
|
-
expect(
|
|
69
|
+
expect(ran).toBe(0);
|
|
70
|
+
expect(retired).toBe(0);
|
|
71
|
+
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.",
|
|
73
|
+
]);
|
|
48
74
|
});
|
|
49
|
-
});
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
it("turns a step that cannot even start into one warning naming the retry commands", async () => {
|
|
76
|
+
it("skips the retire when the run cannot even start, with one warning naming the retry", async () => {
|
|
53
77
|
const said: string[] = [];
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
let retired = 0;
|
|
79
|
+
await bootstrapLocalBackend("/home/x", {
|
|
80
|
+
client,
|
|
81
|
+
say: (line) => said.push(line),
|
|
82
|
+
prepare: async () => prepared,
|
|
83
|
+
run: async () => {
|
|
84
|
+
throw new Error("backend unreachable");
|
|
85
|
+
},
|
|
86
|
+
retire: async () => {
|
|
87
|
+
retired += 1;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
expect(retired).toBe(0);
|
|
91
|
+
expect(said).toEqual([
|
|
92
|
+
"Warning: failed to bootstrap the local backend, so nothing was retired. Run 'stigmer up' again to retry.",
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("holds the retire back when a default did not land, naming the default and saying why", async () => {
|
|
97
|
+
const said: string[] = [];
|
|
98
|
+
let retired = 0;
|
|
99
|
+
await bootstrapLocalBackend("/home/x", {
|
|
100
|
+
client,
|
|
101
|
+
say: (line) => said.push(line),
|
|
102
|
+
prepare: async () => prepared,
|
|
103
|
+
run: async () => ({
|
|
104
|
+
org: "created",
|
|
105
|
+
plugins: {
|
|
106
|
+
outcomes: [
|
|
107
|
+
{ name: "assistant", action: "failed", error: "refused" },
|
|
108
|
+
],
|
|
109
|
+
failed: ["assistant"],
|
|
61
110
|
},
|
|
111
|
+
}),
|
|
112
|
+
retire: async () => {
|
|
113
|
+
retired += 1;
|
|
62
114
|
},
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
115
|
+
});
|
|
116
|
+
expect(retired).toBe(0);
|
|
117
|
+
expect(said).toEqual([
|
|
118
|
+
"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.",
|
|
69
141
|
]);
|
|
70
142
|
});
|
|
71
143
|
});
|