@stigmer/cli 3.18.1-dev.20260919070736 → 3.19.1-dev.20260920100552
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/apply.d.ts.map +1 -1
- package/commands/apply.js +45 -101
- package/commands/apply.js.map +1 -1
- package/commands/install.js +2 -1
- package/commands/install.js.map +1 -1
- package/commands/marketplace.js +3 -3
- package/commands/marketplace.js.map +1 -1
- package/commands/push.js +3 -1
- package/commands/push.js.map +1 -1
- package/commands/validate.js +3 -4
- package/commands/validate.js.map +1 -1
- package/local/bootstrap.d.ts +1 -2
- package/local/bootstrap.d.ts.map +1 -1
- package/local/bootstrap.js +13 -41
- package/local/bootstrap.js.map +1 -1
- package/local/daemon/launch.d.ts +2 -2
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/daemon/launch.js +2 -2
- package/local/daemon/launch.js.map +1 -1
- package/local/plugins/defaults.js +2 -2
- package/local/plugins/defaults.js.map +1 -1
- package/marketplace/config.d.ts.map +1 -1
- package/marketplace/config.js +13 -9
- package/marketplace/config.js.map +1 -1
- package/marketplace/render.d.ts.map +1 -1
- package/marketplace/render.js +1 -7
- package/marketplace/render.js.map +1 -1
- package/package.json +7 -7
- package/registry/index.d.ts +2 -2
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +2 -2
- package/registry/index.js.map +1 -1
- package/registry/metadata.d.ts +9 -0
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +14 -5
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts +9 -0
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js +16 -1
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +0 -13
- package/registry/verb-support.js.map +1 -1
- package/resources/apply/apply.d.ts +11 -12
- package/resources/apply/apply.d.ts.map +1 -1
- package/resources/apply/apply.js +12 -21
- package/resources/apply/apply.js.map +1 -1
- package/resources/apply/handlers.js +2 -2
- package/resources/apply/handlers.js.map +1 -1
- package/resources/connect/oauth.d.ts.map +1 -1
- package/resources/connect/oauth.js +4 -1
- package/resources/connect/oauth.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +0 -1
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +0 -5
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +0 -1
- package/resources/list.js.map +1 -1
- package/resources/plugin.d.ts +27 -0
- package/resources/plugin.d.ts.map +1 -1
- package/resources/plugin.js +113 -8
- package/resources/plugin.js.map +1 -1
- package/resources/validate.d.ts.map +1 -1
- package/resources/validate.js +0 -2
- package/resources/validate.js.map +1 -1
- package/src/commands/apply.ts +40 -150
- package/src/commands/install.test.ts +20 -21
- package/src/commands/install.ts +2 -1
- package/src/commands/marketplace.test.ts +10 -19
- package/src/commands/marketplace.ts +3 -3
- package/src/commands/push.ts +6 -1
- package/src/commands/validate.ts +2 -4
- package/src/local/bootstrap.test.ts +15 -59
- package/src/local/bootstrap.ts +12 -53
- package/src/local/daemon/launch.ts +4 -4
- package/src/local/plugins/defaults.test.ts +28 -28
- package/src/local/plugins/defaults.ts +2 -2
- package/src/marketplace/__fixtures__/cursor-marketplace.ts +16 -12
- package/src/marketplace/config.test.ts +12 -28
- package/src/marketplace/config.ts +13 -9
- package/src/marketplace/install.test.ts +19 -19
- package/src/marketplace/read.test.ts +6 -6
- package/src/marketplace/render.ts +1 -10
- package/src/registry/index.ts +7 -1
- package/src/registry/metadata.ts +15 -5
- package/src/registry/registry.test.ts +1 -7
- package/src/registry/registry.ts +17 -1
- package/src/registry/verb-support.ts +0 -13
- package/src/resources/apply/apply.integration.test.ts +15 -3
- package/src/resources/apply/apply.ts +15 -26
- package/src/resources/apply/handlers.ts +2 -2
- package/src/resources/connect/oauth.test.ts +2 -2
- package/src/resources/connect/oauth.ts +4 -1
- package/src/resources/delete.ts +0 -1
- package/src/resources/get-bindings.ts +0 -5
- package/src/resources/list.ts +0 -1
- package/src/resources/plugin.test.ts +119 -2
- package/src/resources/plugin.ts +140 -8
- package/src/resources/validate.test.ts +0 -1
- package/src/resources/validate.ts +0 -2
- package/local/seedpack-retire.d.ts +0 -63
- package/local/seedpack-retire.d.ts.map +0 -1
- package/local/seedpack-retire.js +0 -361
- package/local/seedpack-retire.js.map +0 -1
- package/resources/apply/declarative.d.ts +0 -106
- package/resources/apply/declarative.d.ts.map +0 -1
- package/resources/apply/declarative.js +0 -397
- package/resources/apply/declarative.js.map +0 -1
- package/resources/apply/synth/project-track.d.ts +0 -21
- package/resources/apply/synth/project-track.d.ts.map +0 -1
- package/resources/apply/synth/project-track.js +0 -102
- package/resources/apply/synth/project-track.js.map +0 -1
- package/resources/apply/synth/reader.d.ts +0 -18
- package/resources/apply/synth/reader.d.ts.map +0 -1
- package/resources/apply/synth/reader.js +0 -55
- package/resources/apply/synth/reader.js.map +0 -1
- package/resources/apply/synth/runtime.d.ts +0 -8
- package/resources/apply/synth/runtime.d.ts.map +0 -1
- package/resources/apply/synth/runtime.js +0 -32
- package/resources/apply/synth/runtime.js.map +0 -1
- package/resources/apply/synth/synthesize.d.ts +0 -43
- package/resources/apply/synth/synthesize.d.ts.map +0 -1
- package/resources/apply/synth/synthesize.js +0 -115
- package/resources/apply/synth/synthesize.js.map +0 -1
- package/src/local/seedpack-retire.test.ts +0 -499
- package/src/local/seedpack-retire.ts +0 -474
- package/src/resources/apply/declarative.integration.test.ts +0 -204
- package/src/resources/apply/declarative.test.ts +0 -132
- package/src/resources/apply/declarative.ts +0 -491
- package/src/resources/apply/synth/capstone.integration.test.ts +0 -134
- package/src/resources/apply/synth/project-track.integration.test.ts +0 -194
- package/src/resources/apply/synth/project-track.ts +0 -126
- package/src/resources/apply/synth/reader.test.ts +0 -90
- package/src/resources/apply/synth/reader.ts +0 -68
- package/src/resources/apply/synth/runtime.test.ts +0 -29
- package/src/resources/apply/synth/runtime.ts +0 -39
- package/src/resources/apply/synth/synthesize.test.ts +0 -109
- package/src/resources/apply/synth/synthesize.ts +0 -161
|
@@ -33,9 +33,9 @@ afterAll(() => {
|
|
|
33
33
|
describe("prepareEntry", () => {
|
|
34
34
|
it("prepares an offered entry exactly as push plugin would prepare its directory", async () => {
|
|
35
35
|
const tree = readMarketplaceTree(root);
|
|
36
|
-
const viaMarketplace = await prepareEntry(tree, "
|
|
37
|
-
const viaFolder = await preparePluginPush(join(root, "third_party", "
|
|
38
|
-
expect(viaMarketplace.plugin.name).toBe("
|
|
36
|
+
const viaMarketplace = await prepareEntry(tree, "codeforge");
|
|
37
|
+
const viaFolder = await preparePluginPush(join(root, "third_party", "codeforge"));
|
|
38
|
+
expect(viaMarketplace.plugin.name).toBe("codeforge");
|
|
39
39
|
expect(viaMarketplace.digest).toBe(viaFolder.digest);
|
|
40
40
|
expect(
|
|
41
41
|
Buffer.from(viaMarketplace.archive).equals(
|
|
@@ -57,15 +57,15 @@ describe("prepareEntry", () => {
|
|
|
57
57
|
|
|
58
58
|
describe("assertVersion", () => {
|
|
59
59
|
it("passes without a pin or with the offered version, refuses another naming the offered one", async () => {
|
|
60
|
-
const prepared = await prepareEntry(readMarketplaceTree(root), "
|
|
61
|
-
expect(() => assertVersion(prepared, undefined, "
|
|
60
|
+
const prepared = await prepareEntry(readMarketplaceTree(root), "warmer");
|
|
61
|
+
expect(() => assertVersion(prepared, undefined, "warmer")).not.toThrow();
|
|
62
62
|
expect(() =>
|
|
63
|
-
assertVersion(prepared, "1.0.0", "
|
|
63
|
+
assertVersion(prepared, "1.0.0", "warmer@1.0.0"),
|
|
64
64
|
).not.toThrow();
|
|
65
65
|
expect(() =>
|
|
66
|
-
assertVersion(prepared, "9.9.9", "acme-plugins/
|
|
66
|
+
assertVersion(prepared, "9.9.9", "acme-plugins/warmer@9.9.9"),
|
|
67
67
|
).toThrow(
|
|
68
|
-
/pins version 9\.9\.9, but the marketplace offers '
|
|
68
|
+
/pins version 9\.9\.9, but the marketplace offers 'warmer' at 1\.0\.0\n\nInstall it as 'acme-plugins\/warmer@1\.0\.0'/,
|
|
69
69
|
);
|
|
70
70
|
});
|
|
71
71
|
});
|
|
@@ -104,10 +104,10 @@ describe("locateEntry", () => {
|
|
|
104
104
|
const remoteFiles = {
|
|
105
105
|
"marketplace.json": JSON.stringify({
|
|
106
106
|
name: "remote",
|
|
107
|
-
plugins: [{ name: "
|
|
107
|
+
plugins: [{ name: "warmer", source: "./warmer" }],
|
|
108
108
|
}),
|
|
109
|
-
"
|
|
110
|
-
name: "
|
|
109
|
+
"warmer/plugin.json": JSON.stringify({
|
|
110
|
+
name: "warmer",
|
|
111
111
|
version: "3.0.0",
|
|
112
112
|
}),
|
|
113
113
|
};
|
|
@@ -122,7 +122,7 @@ describe("locateEntry", () => {
|
|
|
122
122
|
source: { type: "local", path: root } as const,
|
|
123
123
|
};
|
|
124
124
|
const located = await locateEntry(
|
|
125
|
-
parseInstallRef("acme-plugins/
|
|
125
|
+
parseInstallRef("acme-plugins/warmer"),
|
|
126
126
|
listing(local),
|
|
127
127
|
);
|
|
128
128
|
try {
|
|
@@ -132,7 +132,7 @@ describe("locateEntry", () => {
|
|
|
132
132
|
located.open.dispose();
|
|
133
133
|
}
|
|
134
134
|
await expect(
|
|
135
|
-
locateEntry(parseInstallRef("nowhere/
|
|
135
|
+
locateEntry(parseInstallRef("nowhere/warmer"), listing(local)),
|
|
136
136
|
).rejects.toThrow(/no marketplace named 'nowhere' is configured/);
|
|
137
137
|
});
|
|
138
138
|
|
|
@@ -142,7 +142,7 @@ describe("locateEntry", () => {
|
|
|
142
142
|
source: { type: "local", path: root } as const,
|
|
143
143
|
};
|
|
144
144
|
const located = await locateEntry(
|
|
145
|
-
parseInstallRef("
|
|
145
|
+
parseInstallRef("codeforge"),
|
|
146
146
|
listing(local),
|
|
147
147
|
);
|
|
148
148
|
try {
|
|
@@ -178,11 +178,11 @@ describe("locateEntry", () => {
|
|
|
178
178
|
};
|
|
179
179
|
const before = countTempTrees();
|
|
180
180
|
await expect(
|
|
181
|
-
locateEntry(parseInstallRef("
|
|
181
|
+
locateEntry(parseInstallRef("warmer"), listing(local, remote), {
|
|
182
182
|
fetchImpl: servingZipOf(remoteFiles),
|
|
183
183
|
}),
|
|
184
184
|
).rejects.toThrow(
|
|
185
|
-
/offered by more than one marketplace: acme-plugins, remote\n\nName the one you mean: acme-plugins\/
|
|
185
|
+
/offered by more than one marketplace: acme-plugins, remote\n\nName the one you mean: acme-plugins\/warmer or remote\/warmer/,
|
|
186
186
|
);
|
|
187
187
|
expect(countTempTrees()).toBe(before);
|
|
188
188
|
});
|
|
@@ -197,8 +197,8 @@ describe("locateEntry", () => {
|
|
|
197
197
|
source: { type: "github", repo: "a/b" } as const,
|
|
198
198
|
};
|
|
199
199
|
const requests: string[] = [];
|
|
200
|
-
// `
|
|
201
|
-
const located = await locateEntry(parseInstallRef("
|
|
200
|
+
// `codeforge` is offered by the local fixture alone: the remote is peeked, not downloaded.
|
|
201
|
+
const located = await locateEntry(parseInstallRef("codeforge"), listing(local, remote), {
|
|
202
202
|
fetchImpl: servingZipOf(remoteFiles, "r-HEAD", requests),
|
|
203
203
|
});
|
|
204
204
|
located.open.dispose();
|
|
@@ -241,7 +241,7 @@ describe("locateEntry", () => {
|
|
|
241
241
|
source: { type: "github", repo: "a/b" } as const,
|
|
242
242
|
};
|
|
243
243
|
const located = await locateEntry(
|
|
244
|
-
parseInstallRef("remote/
|
|
244
|
+
parseInstallRef("remote/warmer"),
|
|
245
245
|
listing(remote),
|
|
246
246
|
{
|
|
247
247
|
fetchImpl: servingZipOf(remoteFiles),
|
|
@@ -27,8 +27,8 @@ describe("readMarketplaceTree", () => {
|
|
|
27
27
|
expect(tree.marketplace.name).toBe("acme-plugins");
|
|
28
28
|
expect(tree.marketplace.dialect).toBe("cursor");
|
|
29
29
|
expect(tree.marketplace.plugins.map((entry) => entry.name)).toEqual([
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"warmer",
|
|
31
|
+
"codeforge",
|
|
32
32
|
]);
|
|
33
33
|
expect(tree.warnings).toHaveLength(1);
|
|
34
34
|
expect(tree.warnings[0]?.kind).toBe("entry-directory-missing");
|
|
@@ -71,10 +71,10 @@ describe("readMarketplaceTree", () => {
|
|
|
71
71
|
describe("findEntry and entryDirectory", () => {
|
|
72
72
|
it("resolves an offered entry to its absolute directory and an unknown name to undefined", () => {
|
|
73
73
|
const tree = readMarketplaceTree(root);
|
|
74
|
-
const
|
|
75
|
-
expect(
|
|
76
|
-
expect(entryDirectory(tree,
|
|
77
|
-
join(root, "third_party", "
|
|
74
|
+
const codeforge = findEntry(tree.marketplace, "codeforge");
|
|
75
|
+
expect(codeforge?.dir).toBe("third_party/codeforge");
|
|
76
|
+
expect(entryDirectory(tree, codeforge!)).toBe(
|
|
77
|
+
join(root, "third_party", "codeforge"),
|
|
78
78
|
);
|
|
79
79
|
expect(findEntry(tree.marketplace, "ghost")).toBeUndefined();
|
|
80
80
|
});
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// same kind of thing: client-side configuration, not a server collection.
|
|
6
6
|
|
|
7
7
|
import { readPluginPackage } from "@stigmer/plugin-package";
|
|
8
|
-
import { isBuiltInMarketplaceName } from "@stigmer/plugin-package/client";
|
|
9
8
|
import { CommandResult } from "../output/index.js";
|
|
10
9
|
import { count, readPluginDirectory } from "../resources/plugin.js";
|
|
11
10
|
import {
|
|
@@ -27,15 +26,7 @@ export function renderMarketplaceList(
|
|
|
27
26
|
);
|
|
28
27
|
const section = result.addSection("Sources");
|
|
29
28
|
for (const marketplace of listing.known) {
|
|
30
|
-
|
|
31
|
-
// knows which names `remove` will refuse.
|
|
32
|
-
section.field(
|
|
33
|
-
marketplace.name,
|
|
34
|
-
isBuiltInMarketplaceName(marketplace.name) &&
|
|
35
|
-
marketplace.source.type !== "official"
|
|
36
|
-
? `${describeSource(marketplace.source)} (built in)`
|
|
37
|
-
: describeSource(marketplace.source),
|
|
38
|
-
);
|
|
29
|
+
section.field(marketplace.name, describeSource(marketplace.source));
|
|
39
30
|
}
|
|
40
31
|
for (const marketplace of listing.unreadable) {
|
|
41
32
|
section.field(marketplace.name, `cannot be read: ${marketplace.reason}`);
|
package/src/registry/index.ts
CHANGED
|
@@ -7,12 +7,18 @@ export {
|
|
|
7
7
|
toKebabCase,
|
|
8
8
|
toSnakeCase,
|
|
9
9
|
} from "./aliases.js";
|
|
10
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
CLI_RELEVANT_KINDS,
|
|
12
|
+
type KindMeta,
|
|
13
|
+
KIND_META,
|
|
14
|
+
RETIRED_KINDS,
|
|
15
|
+
} from "./metadata.js";
|
|
11
16
|
export {
|
|
12
17
|
defaultRegistry,
|
|
13
18
|
type Registry,
|
|
14
19
|
supportsVerb,
|
|
15
20
|
type TypeInfo,
|
|
21
|
+
unknownKindError,
|
|
16
22
|
} from "./registry.js";
|
|
17
23
|
export {
|
|
18
24
|
ALL_VERBS,
|
package/src/registry/metadata.ts
CHANGED
|
@@ -46,10 +46,6 @@ export const KIND_META: ReadonlyMap<ApiResourceKind, KindMeta> = new Map([
|
|
|
46
46
|
ApiResourceKind.mcp_server,
|
|
47
47
|
{ name: "McpServer", displayName: "MCP Server", idPrefix: "mcp" },
|
|
48
48
|
],
|
|
49
|
-
[
|
|
50
|
-
ApiResourceKind.project,
|
|
51
|
-
{ name: "Project", displayName: "Project", idPrefix: "prj" },
|
|
52
|
-
],
|
|
53
49
|
[
|
|
54
50
|
ApiResourceKind.api_key,
|
|
55
51
|
{ name: "ApiKey", displayName: "API Key", idPrefix: "key" },
|
|
@@ -122,7 +118,6 @@ export const CLI_RELEVANT_KINDS: readonly ApiResourceKind[] = [
|
|
|
122
118
|
ApiResourceKind.skill,
|
|
123
119
|
ApiResourceKind.plugin,
|
|
124
120
|
ApiResourceKind.mcp_server,
|
|
125
|
-
ApiResourceKind.project,
|
|
126
121
|
ApiResourceKind.api_key,
|
|
127
122
|
ApiResourceKind.identity_provider,
|
|
128
123
|
ApiResourceKind.oauth_app,
|
|
@@ -135,3 +130,18 @@ export const CLI_RELEVANT_KINDS: readonly ApiResourceKind[] = [
|
|
|
135
130
|
ApiResourceKind.workflow_instance,
|
|
136
131
|
ApiResourceKind.session,
|
|
137
132
|
];
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* YAML kinds the platform once served and no longer does. A manifest that
|
|
136
|
+
* still carries one is an old file, not a typo, so the refusal it meets
|
|
137
|
+
* says what happened and what to do instead of "unknown kind"; the
|
|
138
|
+
* registry's `unknownKindError` reads this table before falling back to the
|
|
139
|
+
* generic sentence. The key is the YAML `kind` value as the retired proto
|
|
140
|
+
* declared it; the value is the sentence that follows the kind's name.
|
|
141
|
+
*/
|
|
142
|
+
export const RETIRED_KINDS: ReadonlyMap<string, string> = new Map([
|
|
143
|
+
[
|
|
144
|
+
"Project",
|
|
145
|
+
"is no longer a Stigmer resource. A folder of resources that belong together is a plugin: run `stigmer push plugin <dir>` to install it as one, or apply each resource file with `stigmer apply -f <file>`.",
|
|
146
|
+
],
|
|
147
|
+
]);
|
|
@@ -241,13 +241,7 @@ describe("registry — verb/dispatch conformance", () => {
|
|
|
241
241
|
label: "apply",
|
|
242
242
|
verb: Verb.Apply,
|
|
243
243
|
wired: new Set(APPLY_HANDLERS.keys()),
|
|
244
|
-
specialCases: new Map(
|
|
245
|
-
[
|
|
246
|
-
ApiResourceKind.project,
|
|
247
|
-
"the stigmer.yaml declarative/synthesis track (resources/apply/declarative.ts); " +
|
|
248
|
-
"file mode refuses with a pointer there (resolveHandlerForKind)",
|
|
249
|
-
],
|
|
250
|
-
]),
|
|
244
|
+
specialCases: new Map(),
|
|
251
245
|
},
|
|
252
246
|
{
|
|
253
247
|
label: "get",
|
package/src/registry/registry.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// removed in the TypeScript migration — stigmer/stigmer#203.)
|
|
6
6
|
|
|
7
7
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
8
|
+
import { UsageError } from "../errors/index.js";
|
|
8
9
|
import { generateAliases, normalizeAlias } from "./aliases.js";
|
|
9
|
-
import { CLI_RELEVANT_KINDS, KIND_META } from "./metadata.js";
|
|
10
|
+
import { CLI_RELEVANT_KINDS, KIND_META, RETIRED_KINDS } from "./metadata.js";
|
|
10
11
|
import { Verb } from "./verbs.js";
|
|
11
12
|
import { verbsForKind } from "./verb-support.js";
|
|
12
13
|
|
|
@@ -34,6 +35,21 @@ export function supportsVerb(info: TypeInfo, verb: Verb): boolean {
|
|
|
34
35
|
return info.supportedVerbs.has(verb);
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The one refusal for a YAML `kind` the registry does not know, shared by
|
|
40
|
+
* every path that reads a kind from a file (`apply -f`, `validate -f`) so
|
|
41
|
+
* the wording lives in one place. A kind the platform retired gets its own
|
|
42
|
+
* sentence (RETIRED_KINDS): the file is old, not wrong, and the user
|
|
43
|
+
* deserves the reason and the way. `where` names the file for the message.
|
|
44
|
+
*/
|
|
45
|
+
export function unknownKindError(yamlKind: string, where: string): UsageError {
|
|
46
|
+
const retired = RETIRED_KINDS.get(yamlKind);
|
|
47
|
+
if (retired !== undefined) {
|
|
48
|
+
return new UsageError(`kind '${yamlKind}' in ${where} ${retired}`);
|
|
49
|
+
}
|
|
50
|
+
return new UsageError(`unknown resource kind '${yamlKind}' in ${where}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
export interface Registry {
|
|
38
54
|
/** Look up a type by its proto kind. */
|
|
39
55
|
getByKind(kind: ApiResourceKind): TypeInfo | undefined;
|
|
@@ -66,19 +66,6 @@ export const VERB_SUPPORT: ReadonlyMap<
|
|
|
66
66
|
Verb.Delete,
|
|
67
67
|
]),
|
|
68
68
|
],
|
|
69
|
-
// Project apply is the stigmer.yaml declarative/synthesis track, not
|
|
70
|
-
// `apply -f project.yaml` — resolveHandlerForKind carries the teaching
|
|
71
|
-
// refusal, and the conformance test documents it as a special case.
|
|
72
|
-
[
|
|
73
|
-
ApiResourceKind.project,
|
|
74
|
-
new Set<Verb>([
|
|
75
|
-
Verb.Apply,
|
|
76
|
-
Verb.Validate,
|
|
77
|
-
Verb.Get,
|
|
78
|
-
Verb.List,
|
|
79
|
-
Verb.Delete,
|
|
80
|
-
]),
|
|
81
|
-
],
|
|
82
69
|
[ApiResourceKind.api_key, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete])],
|
|
83
70
|
// agent_execution is special — uses dedicated AgentExecutionQueryController
|
|
84
71
|
// RPCs, not the unified SearchService. delete maps to cancel.
|
|
@@ -268,12 +268,24 @@ describe("file-mode apply — channel app (#353)", () => {
|
|
|
268
268
|
});
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
-
describe("file-mode apply —
|
|
272
|
-
it("refuses a
|
|
271
|
+
describe("file-mode apply — kinds the registry does not know", () => {
|
|
272
|
+
it("refuses a retired kind with what happened and what to run instead", () => {
|
|
273
273
|
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
274
274
|
try {
|
|
275
275
|
writeYaml(dir, "project.yaml", ["kind: Project", "metadata:", " name: Demo", " slug: demo", ""].join("\n"));
|
|
276
|
-
expect(() => resolveApplyItems(dir)).toThrow(
|
|
276
|
+
expect(() => resolveApplyItems(dir)).toThrow(
|
|
277
|
+
/kind 'Project' in .*project\.yaml is no longer a Stigmer resource.*stigmer push plugin <dir>/,
|
|
278
|
+
);
|
|
279
|
+
} finally {
|
|
280
|
+
rmSync(dir, { recursive: true, force: true });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("refuses a kind that never existed with the plain unknown-kind sentence", () => {
|
|
285
|
+
const dir = mkdtempSync(join(tmpdir(), "apply-it-"));
|
|
286
|
+
try {
|
|
287
|
+
writeYaml(dir, "thing.yaml", ["kind: Widget", "metadata:", " name: Demo", ""].join("\n"));
|
|
288
|
+
expect(() => resolveApplyItems(dir)).toThrow(/^unknown resource kind 'Widget' in .*thing\.yaml$/);
|
|
277
289
|
} finally {
|
|
278
290
|
rmSync(dir, { recursive: true, force: true });
|
|
279
291
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
19
19
|
import { UsageError } from "../../errors/index.js";
|
|
20
20
|
import { CommandResult } from "../../output/index.js";
|
|
21
|
-
import { defaultRegistry, Verb } from "../../registry/index.js";
|
|
21
|
+
import { defaultRegistry, unknownKindError, Verb } from "../../registry/index.js";
|
|
22
22
|
import { loadDocuments, resolveYamlFiles } from "../documents.js";
|
|
23
23
|
import { decodeWorkflowTaskConfigs } from "../task-configs.js";
|
|
24
24
|
import { type ApplyHandler, APPLY_HANDLERS, type ControllerFn } from "./handlers.js";
|
|
@@ -36,9 +36,9 @@ export interface ApplyOutcome {
|
|
|
36
36
|
/** Optional org-mismatch warning to surface on stderr. */
|
|
37
37
|
readonly warning?: string;
|
|
38
38
|
/**
|
|
39
|
-
* The applied resource message (absent in dry-run)
|
|
40
|
-
* authoritative metadata (id
|
|
41
|
-
*
|
|
39
|
+
* The applied resource message (absent in dry-run), as the server stored
|
|
40
|
+
* it: authoritative metadata (id, slug, visibility) for anything that
|
|
41
|
+
* runs after the apply.
|
|
42
42
|
*/
|
|
43
43
|
readonly applied?: Message;
|
|
44
44
|
}
|
|
@@ -60,22 +60,14 @@ export function resolveApplyItems(path: string): ApplyItem[] {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Resolve a YAML `kind` to its apply handler, enforcing the verb-support gate.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* The unknown-kind wording is the registry's (`unknownKindError`), shared
|
|
64
|
+
* with `validate -f`, so a retired kind is refused with the same sentence on
|
|
65
|
+
* both paths. `where` is woven into errors for a precise location.
|
|
65
66
|
*/
|
|
66
67
|
export function resolveHandlerForKind(kind: string, where: string): ApplyHandler {
|
|
67
68
|
const info = defaultRegistry().getByYamlKind(kind);
|
|
68
|
-
if (info === undefined) throw
|
|
69
|
+
if (info === undefined) throw unknownKindError(kind, where);
|
|
69
70
|
if (!info.supportedVerbs.has(Verb.Apply)) throw new UsageError(`${info.displayName} does not support 'apply'`);
|
|
70
|
-
if (info.kind === ApiResourceKind.project) {
|
|
71
|
-
// Project's Apply verb is the stigmer.yaml track, deliberately not a
|
|
72
|
-
// manifest handler: a file-mode apply would create a second, member-less
|
|
73
|
-
// way to apply a project, bypassing the membership reconciler.
|
|
74
|
-
throw new UsageError(
|
|
75
|
-
`a Project is not applied from a resource manifest (${where}): ` +
|
|
76
|
-
"run `stigmer apply` in the directory containing stigmer.yaml and the project is reconciled from its members",
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
71
|
const handler = APPLY_HANDLERS.get(info.kind);
|
|
80
72
|
if (handler === undefined) throw new UsageError(`apply not implemented for ${info.displayName}`);
|
|
81
73
|
return handler;
|
|
@@ -109,16 +101,15 @@ export async function applyItem(
|
|
|
109
101
|
|
|
110
102
|
/**
|
|
111
103
|
* Strict YAML→proto marshal for one item, with a precise location in the error.
|
|
112
|
-
* Split out from {@link applyItem} so
|
|
113
|
-
*
|
|
104
|
+
* Split out from {@link applyItem} so marshalling can be exercised and reused
|
|
105
|
+
* apart from the apply it feeds.
|
|
114
106
|
*/
|
|
115
107
|
export function marshalItem(item: ApplyItem): Message {
|
|
116
108
|
try {
|
|
117
109
|
const message = fromJson(item.handler.schema, item.document, { ignoreUnknownFields: false });
|
|
118
110
|
// Workflow task_config blocks live inside an open Struct the top-level
|
|
119
|
-
// decode cannot see into; decode them per kind so a dry-run
|
|
120
|
-
//
|
|
121
|
-
// (stigmer/stigmer#778).
|
|
111
|
+
// decode cannot see into; decode them per kind so a dry-run rejects
|
|
112
|
+
// exactly what a real apply rejects (stigmer/stigmer#778).
|
|
122
113
|
if (item.handler.kind === ApiResourceKind.workflow) {
|
|
123
114
|
decodeWorkflowTaskConfigs(message as Workflow);
|
|
124
115
|
}
|
|
@@ -130,11 +121,9 @@ export function marshalItem(item: ApplyItem): Message {
|
|
|
130
121
|
|
|
131
122
|
/**
|
|
132
123
|
* Apply a fully-marshalled resource message: inject org, then dry-run preview or
|
|
133
|
-
* drive the controller's `apply` RPC.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* (`fromBinary`). One core means one place where org injection, create/update
|
|
137
|
-
* detection, and result shaping live (DD-009 §6).
|
|
124
|
+
* drive the controller's `apply` RPC. The single apply core behind file mode,
|
|
125
|
+
* taking the message rather than the YAML so org injection, create/update
|
|
126
|
+
* detection and result shaping live in one place whatever produced the message.
|
|
138
127
|
*/
|
|
139
128
|
export async function applyMessage(
|
|
140
129
|
controller: ControllerFn,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Apply-handler registry for `apply -f` (file mode)
|
|
1
|
+
// Apply-handler registry for `apply -f` (file mode).
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
3
|
+
// The manifest kinds come from the SDK's manifest registry
|
|
4
4
|
// (`manifestKinds()` in @stigmer/sdk) — the one table binding a kind to its
|
|
5
5
|
// proto schema, its raw command-controller `apply` RPC, and its dependency
|
|
6
6
|
// apply order. The CLI consumes that table instead of keeping a copy: the
|
|
@@ -112,7 +112,7 @@ describe("runOAuthFlow", () => {
|
|
|
112
112
|
log: () => {},
|
|
113
113
|
});
|
|
114
114
|
expect(openBrowser).toHaveBeenCalledWith(
|
|
115
|
-
"https://app.stigmer.ai/
|
|
115
|
+
"https://app.stigmer.ai/library/mcp-servers/acme/github",
|
|
116
116
|
);
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -152,7 +152,7 @@ describe("runOAuthFlow", () => {
|
|
|
152
152
|
log: () => {},
|
|
153
153
|
});
|
|
154
154
|
expect(openBrowser).toHaveBeenCalledWith(
|
|
155
|
-
"http://localhost:7234/
|
|
155
|
+
"http://localhost:7234/library/mcp-servers/acme/github",
|
|
156
156
|
);
|
|
157
157
|
});
|
|
158
158
|
});
|
|
@@ -60,7 +60,10 @@ export async function runOAuthFlow(deps: OAuthFlowDeps): Promise<void> {
|
|
|
60
60
|
|
|
61
61
|
const slug = deps.server.metadata?.slug ?? "";
|
|
62
62
|
const name = deps.server.metadata?.name ?? slug;
|
|
63
|
-
|
|
63
|
+
// The route both consoles serve (`/library/mcp-servers/<org>/<slug>`);
|
|
64
|
+
// the Go-era `/<org>/mcp-servers/<slug>` answered 404 in the TypeScript
|
|
65
|
+
// console and sent the user to a dead page.
|
|
66
|
+
const pageURL = `${consoleURL}/library/mcp-servers/${deps.org}/${slug}`;
|
|
64
67
|
|
|
65
68
|
log(`OAuth authentication required for '${name}'.`);
|
|
66
69
|
log("Opening web console to complete authentication...");
|
package/src/resources/delete.ts
CHANGED
|
@@ -82,7 +82,6 @@ export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<
|
|
|
82
82
|
ApiResourceKind.mcp_server,
|
|
83
83
|
(c, id, force) => c.mcpServer.delete({ resourceId: id, force }),
|
|
84
84
|
],
|
|
85
|
-
[ApiResourceKind.project, (c, id) => c.project.delete(id)],
|
|
86
85
|
[
|
|
87
86
|
ApiResourceKind.environment,
|
|
88
87
|
(c, id, force) => c.environment.delete({ resourceId: id, force }),
|
|
@@ -17,7 +17,6 @@ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/a
|
|
|
17
17
|
import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
|
|
18
18
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
19
19
|
import { ApiKeySchema } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/api_pb";
|
|
20
|
-
import { ProjectSchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
|
|
21
20
|
import type { Stigmer } from "@stigmer/sdk";
|
|
22
21
|
import { UsageError } from "../errors/index.js";
|
|
23
22
|
import type { ParsedReference } from "./reference.js";
|
|
@@ -64,10 +63,6 @@ export const GET_BINDINGS: ReadonlyMap<ApiResourceKind, Getter> = new Map([
|
|
|
64
63
|
ApiResourceKind.mcp_server,
|
|
65
64
|
refGetter(ApiResourceKind.mcp_server, McpServerSchema, (c) => c.mcpServer),
|
|
66
65
|
],
|
|
67
|
-
[
|
|
68
|
-
ApiResourceKind.project,
|
|
69
|
-
refGetter(ApiResourceKind.project, ProjectSchema, (c) => c.project),
|
|
70
|
-
],
|
|
71
66
|
[
|
|
72
67
|
ApiResourceKind.environment,
|
|
73
68
|
refGetter(
|
package/src/resources/list.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
// sorted sized entries, the ignore matcher applied (defaults, .gitignore,
|
|
3
3
|
// .stigmerignore), symlinks never followed, and the JSON projection that
|
|
4
4
|
// reduces overlay documents to their paths; then the prepared push (the
|
|
5
|
-
// deterministic archive and its SHA-256, the server's own digest)
|
|
6
|
-
// install summary renderer
|
|
5
|
+
// deterministic archive and its SHA-256, the server's own digest), the
|
|
6
|
+
// install summary renderer (only the kinds installed are counted; the Next
|
|
7
|
+
// section says what each server still needs), and `readNextSteps` against an
|
|
8
|
+
// in-memory backend: a sign-in for an OAuth server without a grant, "signed
|
|
9
|
+
// in" with one, the variables an API-key server wants and where they are
|
|
10
|
+
// asked, the agent an MCP-only plugin still needs, and no line for a server
|
|
11
|
+
// the CLI cannot read.
|
|
7
12
|
|
|
8
13
|
import {
|
|
9
14
|
mkdirSync,
|
|
@@ -260,3 +265,115 @@ describe("renderPushOutcome", () => {
|
|
|
260
265
|
expect(text).toContain("runs on the session's model");
|
|
261
266
|
});
|
|
262
267
|
});
|
|
268
|
+
|
|
269
|
+
describe("readNextSteps", () => {
|
|
270
|
+
it("reads what each server still needs, best-effort, and names the agent an MCP-only plugin lacks", async () => {
|
|
271
|
+
const { readNextSteps, renderPushOutcome } = await import("./plugin.js");
|
|
272
|
+
const { create } = await import("@bufbuild/protobuf");
|
|
273
|
+
const { Code, ConnectError, createRouterTransport } =
|
|
274
|
+
await import("@connectrpc/connect");
|
|
275
|
+
const { Stigmer } = await import("@stigmer/sdk");
|
|
276
|
+
const { McpServerQueryController } =
|
|
277
|
+
await import("@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb");
|
|
278
|
+
const { McpServerSchema } =
|
|
279
|
+
await import("@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb");
|
|
280
|
+
const { GetOAuthGrantStatusOutputSchema, OAuthConnectionHealth } =
|
|
281
|
+
await import("@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb");
|
|
282
|
+
const { McpServerAuthSchema, McpServerSpecSchema } =
|
|
283
|
+
await import("@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb");
|
|
284
|
+
const { EnvVarDeclarationSchema } =
|
|
285
|
+
await import("@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb");
|
|
286
|
+
const { PluginSchema } =
|
|
287
|
+
await import("@stigmer/protos/ai/stigmer/agentic/plugin/v1/api_pb");
|
|
288
|
+
const { PluginMemberSchema } =
|
|
289
|
+
await import("@stigmer/protos/ai/stigmer/agentic/plugin/v1/io_pb");
|
|
290
|
+
const { ApiResourceKind } =
|
|
291
|
+
await import("@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb");
|
|
292
|
+
const { renderResult } = await import("../output/command-result.js");
|
|
293
|
+
|
|
294
|
+
const shapes: Record<string, "oauth" | "oauth-granted" | "api-key" | "open" | "missing"> = {
|
|
295
|
+
linear: "oauth",
|
|
296
|
+
notion: "oauth-granted",
|
|
297
|
+
warmth: "api-key",
|
|
298
|
+
weather: "open",
|
|
299
|
+
ghost: "missing",
|
|
300
|
+
};
|
|
301
|
+
const transport = createRouterTransport(({ service }) => {
|
|
302
|
+
service(McpServerQueryController, {
|
|
303
|
+
getByReference: (ref) => {
|
|
304
|
+
const shape = shapes[ref.slug];
|
|
305
|
+
if (shape === undefined || shape === "missing")
|
|
306
|
+
throw new ConnectError("no server", Code.NotFound);
|
|
307
|
+
const spec = create(McpServerSpecSchema);
|
|
308
|
+
if (shape === "oauth" || shape === "oauth-granted") {
|
|
309
|
+
spec.auth = create(McpServerAuthSchema, { targetEnvVar: "TOKEN", oauthOnly: true });
|
|
310
|
+
spec.env = { TOKEN: create(EnvVarDeclarationSchema, { isSecret: true }) };
|
|
311
|
+
}
|
|
312
|
+
if (shape === "api-key") spec.env = { API_TOKEN: create(EnvVarDeclarationSchema, { isSecret: true }) };
|
|
313
|
+
return create(McpServerSchema, { metadata: { id: `mcp_${ref.slug}`, org: "acme", slug: ref.slug }, spec });
|
|
314
|
+
},
|
|
315
|
+
getOAuthGrantStatus: (input) => {
|
|
316
|
+
const granted = input.resourceId === "mcp_notion";
|
|
317
|
+
return create(GetOAuthGrantStatusOutputSchema, {
|
|
318
|
+
connected: granted,
|
|
319
|
+
connectionHealth: granted
|
|
320
|
+
? OAuthConnectionHealth.OAUTH_CONNECTION_HEALTH_HEALTHY
|
|
321
|
+
: OAuthConnectionHealth.OAUTH_CONNECTION_HEALTH_NO_GRANT,
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
const client = new Stigmer({ baseUrl: "/", getAccessToken: () => "t", customTransport: transport });
|
|
327
|
+
const member = (slug: string) =>
|
|
328
|
+
create(PluginMemberSchema, { kind: ApiResourceKind.mcp_server, id: `mcp_${slug}`, slug, name: slug });
|
|
329
|
+
|
|
330
|
+
const next = await readNextSteps(client, "acme", Object.keys(shapes).map(member));
|
|
331
|
+
expect(next).toEqual([
|
|
332
|
+
{ kind: "sign-in", server: "linear", command: "stigmer connect mcp-server linear" },
|
|
333
|
+
{ kind: "signed-in", server: "notion" },
|
|
334
|
+
{
|
|
335
|
+
kind: "api-key",
|
|
336
|
+
server: "warmth",
|
|
337
|
+
variables: ["API_TOKEN"],
|
|
338
|
+
askedAt: "connect",
|
|
339
|
+
command: "stigmer connect mcp-server warmth --env API_TOKEN=...",
|
|
340
|
+
},
|
|
341
|
+
{ kind: "add-to-agent", servers: ["linear", "notion", "warmth", "weather", "ghost"] },
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
// With an agent, the variables are the agent's to ask and no agent is missing.
|
|
345
|
+
const withAgent = await readNextSteps(client, "acme", [
|
|
346
|
+
member("warmth"),
|
|
347
|
+
create(PluginMemberSchema, { kind: ApiResourceKind.agent, id: "agt_1", slug: "thermos", name: "thermos" }),
|
|
348
|
+
]);
|
|
349
|
+
expect(withAgent).toEqual([
|
|
350
|
+
{ kind: "api-key", server: "warmth", variables: ["API_TOKEN"], askedAt: "agent" },
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
// The renderer names only the kinds installed and prints the Next section.
|
|
354
|
+
const plugin = create(PluginSchema, {
|
|
355
|
+
metadata: { id: "plg_1", slug: "linear" },
|
|
356
|
+
status: { materialized: { skills: 0, mcpServers: 1, agents: 0, workflows: 0 } },
|
|
357
|
+
});
|
|
358
|
+
const result = renderPushOutcome(
|
|
359
|
+
{ plugin, members: [member("linear")], archiveBytes: 10 },
|
|
360
|
+
{ next: next.slice(0, 1) },
|
|
361
|
+
);
|
|
362
|
+
const lines: string[] = [];
|
|
363
|
+
const original = process.stderr.write.bind(process.stderr);
|
|
364
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
365
|
+
lines.push(String(chunk));
|
|
366
|
+
return true;
|
|
367
|
+
}) as typeof process.stderr.write;
|
|
368
|
+
try {
|
|
369
|
+
renderResult(result, "human");
|
|
370
|
+
} finally {
|
|
371
|
+
process.stderr.write = original;
|
|
372
|
+
}
|
|
373
|
+
const text = lines.join("");
|
|
374
|
+
expect(text).toContain("Installed plugin 'linear' (1 MCP server)");
|
|
375
|
+
expect(text).not.toContain("0 skills");
|
|
376
|
+
expect(text).toContain("Next");
|
|
377
|
+
expect(text).toContain("Sign in to linear: stigmer connect mcp-server linear");
|
|
378
|
+
});
|
|
379
|
+
});
|