@stigmer/cli 3.15.3-dev.20260914100347 → 3.15.3-dev.20260916211208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +3 -4
  2. package/commands/apply.d.ts.map +1 -1
  3. package/commands/apply.js +56 -4
  4. package/commands/apply.js.map +1 -1
  5. package/commands/push.d.ts.map +1 -1
  6. package/commands/push.js +122 -16
  7. package/commands/push.js.map +1 -1
  8. package/commands/validate.d.ts.map +1 -1
  9. package/commands/validate.js +39 -7
  10. package/commands/validate.js.map +1 -1
  11. package/output/command-result.d.ts +8 -0
  12. package/output/command-result.d.ts.map +1 -1
  13. package/output/command-result.js +13 -0
  14. package/output/command-result.js.map +1 -1
  15. package/package.json +6 -5
  16. package/registry/aliases.d.ts.map +1 -1
  17. package/registry/aliases.js.map +1 -1
  18. package/registry/index.d.ts +3 -3
  19. package/registry/index.d.ts.map +1 -1
  20. package/registry/index.js +3 -3
  21. package/registry/index.js.map +1 -1
  22. package/registry/metadata.d.ts.map +1 -1
  23. package/registry/metadata.js +85 -18
  24. package/registry/metadata.js.map +1 -1
  25. package/registry/registry.d.ts.map +1 -1
  26. package/registry/registry.js.map +1 -1
  27. package/registry/verb-support.d.ts.map +1 -1
  28. package/registry/verb-support.js +81 -13
  29. package/registry/verb-support.js.map +1 -1
  30. package/resources/delete.d.ts.map +1 -1
  31. package/resources/delete.js +35 -9
  32. package/resources/delete.js.map +1 -1
  33. package/resources/get-bindings.d.ts.map +1 -1
  34. package/resources/get-bindings.js +56 -13
  35. package/resources/get-bindings.js.map +1 -1
  36. package/resources/list.d.ts.map +1 -1
  37. package/resources/list.js +62 -14
  38. package/resources/list.js.map +1 -1
  39. package/resources/plugin.d.ts +80 -0
  40. package/resources/plugin.d.ts.map +1 -0
  41. package/resources/plugin.js +305 -0
  42. package/resources/plugin.js.map +1 -0
  43. package/resources/skill.d.ts +1 -0
  44. package/resources/skill.d.ts.map +1 -1
  45. package/resources/skill.js +1 -1
  46. package/resources/skill.js.map +1 -1
  47. package/src/commands/apply.ts +133 -21
  48. package/src/commands/push.ts +270 -41
  49. package/src/commands/validate.test.ts +111 -22
  50. package/src/commands/validate.ts +68 -12
  51. package/src/output/command-result.test.ts +9 -0
  52. package/src/output/command-result.ts +14 -0
  53. package/src/registry/aliases.test.ts +24 -5
  54. package/src/registry/aliases.ts +6 -1
  55. package/src/registry/index.ts +19 -3
  56. package/src/registry/metadata.ts +85 -18
  57. package/src/registry/registry.test.ts +86 -37
  58. package/src/registry/registry.ts +8 -2
  59. package/src/registry/verb-support.ts +85 -14
  60. package/src/resources/delete.ts +106 -26
  61. package/src/resources/get-bindings.ts +95 -17
  62. package/src/resources/list.ts +102 -28
  63. package/src/resources/plugin.test.ts +233 -0
  64. package/src/resources/plugin.ts +432 -0
  65. package/src/resources/skill.ts +1 -1
@@ -49,7 +49,9 @@ describe("registry — alias resolution", () => {
49
49
  });
50
50
 
51
51
  it("resolution is case-insensitive", () => {
52
- expect(registry.getByAlias("MCP-SERVER")?.kind).toBe(ApiResourceKind.mcp_server);
52
+ expect(registry.getByAlias("MCP-SERVER")?.kind).toBe(
53
+ ApiResourceKind.mcp_server,
54
+ );
53
55
  });
54
56
 
55
57
  it("returns undefined for an unknown alias", () => {
@@ -61,7 +63,10 @@ describe("registry — alias resolution", () => {
61
63
  // row in `list types` that the pre-gate route then shadows — the
62
64
  // stigmer/stigmer#469 class (see the alias-shadowing suite below).
63
65
  it("does not register the runtime execution kinds as addressable types", () => {
64
- for (const kind of [ApiResourceKind.agent_execution, ApiResourceKind.workflow_execution]) {
66
+ for (const kind of [
67
+ ApiResourceKind.agent_execution,
68
+ ApiResourceKind.workflow_execution,
69
+ ]) {
65
70
  expect(registry.getByKind(kind)).toBeUndefined();
66
71
  }
67
72
  });
@@ -69,14 +74,26 @@ describe("registry — alias resolution", () => {
69
74
 
70
75
  describe("registry — YAML kind resolution", () => {
71
76
  it("resolves the exact YAML kind", () => {
72
- expect(registry.getByYamlKind("McpServer")?.kind).toBe(ApiResourceKind.mcp_server);
73
- expect(registry.getByYamlKind("Workflow")?.kind).toBe(ApiResourceKind.workflow);
77
+ expect(registry.getByYamlKind("McpServer")?.kind).toBe(
78
+ ApiResourceKind.mcp_server,
79
+ );
80
+ expect(registry.getByYamlKind("Workflow")?.kind).toBe(
81
+ ApiResourceKind.workflow,
82
+ );
74
83
  });
75
84
  });
76
85
 
77
86
  describe("registry — verb support matrix", () => {
78
87
  it("agent supports read + run + search verbs", () => {
79
- for (const v of [Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete, Verb.Run, Verb.Search]) {
88
+ for (const v of [
89
+ Verb.Apply,
90
+ Verb.Validate,
91
+ Verb.Get,
92
+ Verb.List,
93
+ Verb.Delete,
94
+ Verb.Run,
95
+ Verb.Search,
96
+ ]) {
80
97
  expect(registry.supportsVerb(ApiResourceKind.agent, v)).toBe(true);
81
98
  }
82
99
  expect(registry.supportsVerb(ApiResourceKind.agent, Verb.Push)).toBe(false);
@@ -84,21 +101,33 @@ describe("registry — verb support matrix", () => {
84
101
 
85
102
  it("skill supports push but not apply", () => {
86
103
  expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Push)).toBe(true);
87
- expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Apply)).toBe(false);
104
+ expect(registry.supportsVerb(ApiResourceKind.skill, Verb.Apply)).toBe(
105
+ false,
106
+ );
88
107
  });
89
108
 
90
109
  it("workflow_instance has no list verb", () => {
91
- expect(registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.Get)).toBe(true);
92
- expect(registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.List)).toBe(false);
110
+ expect(
111
+ registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.Get),
112
+ ).toBe(true);
113
+ expect(
114
+ registry.supportsVerb(ApiResourceKind.workflow_instance, Verb.List),
115
+ ).toBe(false);
93
116
  });
94
117
 
95
118
  it("agent_channel supports the full declarative verb set", () => {
96
119
  for (const v of [Verb.Apply, Verb.Get, Verb.List, Verb.Delete]) {
97
- expect(registry.supportsVerb(ApiResourceKind.agent_channel, v)).toBe(true);
120
+ expect(registry.supportsVerb(ApiResourceKind.agent_channel, v)).toBe(
121
+ true,
122
+ );
98
123
  }
99
124
  // The install flow is console-driven and cloud-only — never a CLI verb.
100
- expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Run)).toBe(false);
101
- expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Search)).toBe(false);
125
+ expect(registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Run)).toBe(
126
+ false,
127
+ );
128
+ expect(
129
+ registry.supportsVerb(ApiResourceKind.agent_channel, Verb.Search),
130
+ ).toBe(false);
102
131
  });
103
132
 
104
133
  it("schedule supports the full declarative verb set", () => {
@@ -106,19 +135,27 @@ describe("registry — verb support matrix", () => {
106
135
  expect(registry.supportsVerb(ApiResourceKind.schedule, v)).toBe(true);
107
136
  }
108
137
  // Firing rides `stigmer schedule trigger`, never the generic run verb.
109
- expect(registry.supportsVerb(ApiResourceKind.schedule, Verb.Run)).toBe(false);
138
+ expect(registry.supportsVerb(ApiResourceKind.schedule, Verb.Run)).toBe(
139
+ false,
140
+ );
110
141
  });
111
142
 
112
143
  it("agent_share promises only apply (narrowed — stigmer/stigmer#354)", () => {
113
- expect(registry.supportsVerb(ApiResourceKind.agent_share, Verb.Apply)).toBe(true);
144
+ expect(registry.supportsVerb(ApiResourceKind.agent_share, Verb.Apply)).toBe(
145
+ true,
146
+ );
114
147
  for (const v of [Verb.Get, Verb.List, Verb.Delete]) {
115
148
  expect(registry.supportsVerb(ApiResourceKind.agent_share, v)).toBe(false);
116
149
  }
117
150
  });
118
151
 
119
152
  it("session promises apply + list; get/delete stay narrowed (stigmer/stigmer#354, #469)", () => {
120
- expect(registry.supportsVerb(ApiResourceKind.session, Verb.Apply)).toBe(true);
121
- expect(registry.supportsVerb(ApiResourceKind.session, Verb.List)).toBe(true);
153
+ expect(registry.supportsVerb(ApiResourceKind.session, Verb.Apply)).toBe(
154
+ true,
155
+ );
156
+ expect(registry.supportsVerb(ApiResourceKind.session, Verb.List)).toBe(
157
+ true,
158
+ );
122
159
  for (const v of [Verb.Get, Verb.Delete]) {
123
160
  expect(registry.supportsVerb(ApiResourceKind.session, v)).toBe(false);
124
161
  }
@@ -248,27 +285,33 @@ describe("registry — verb/dispatch conformance", () => {
248
285
  },
249
286
  ];
250
287
 
251
- it.each(DISPATCH)("every kind promising '$label' has a dispatch entry", ({ verb, wired, specialCases }) => {
252
- for (const info of registry.all()) {
253
- if (!info.supportedVerbs.has(verb)) continue;
254
- if (specialCases.has(info.kind)) continue;
255
- expect(
256
- wired.has(info.kind),
257
- `${info.name} promises '${verb}' in the verb matrix but has no dispatch entry — ` +
258
- "wire it or narrow the matrix (stigmer/stigmer#353, #354)",
259
- ).toBe(true);
260
- }
261
- });
288
+ it.each(DISPATCH)(
289
+ "every kind promising '$label' has a dispatch entry",
290
+ ({ verb, wired, specialCases }) => {
291
+ for (const info of registry.all()) {
292
+ if (!info.supportedVerbs.has(verb)) continue;
293
+ if (specialCases.has(info.kind)) continue;
294
+ expect(
295
+ wired.has(info.kind),
296
+ `${info.name} promises '${verb}' in the verb matrix but has no dispatch entry — ` +
297
+ "wire it or narrow the matrix (stigmer/stigmer#353, #354)",
298
+ ).toBe(true);
299
+ }
300
+ },
301
+ );
262
302
 
263
- it.each(DISPATCH)("every '$label' dispatch entry is promised in the matrix", ({ verb, wired }) => {
264
- for (const kind of wired) {
265
- expect(
266
- registry.supportsVerb(kind, verb),
267
- `${ApiResourceKind[kind]} is wired for '${verb}' but the verb matrix does not promise it — ` +
268
- "dead dispatch the command-layer gate blocks",
269
- ).toBe(true);
270
- }
271
- });
303
+ it.each(DISPATCH)(
304
+ "every '$label' dispatch entry is promised in the matrix",
305
+ ({ verb, wired }) => {
306
+ for (const kind of wired) {
307
+ expect(
308
+ registry.supportsVerb(kind, verb),
309
+ `${ApiResourceKind[kind]} is wired for '${verb}' but the verb matrix does not promise it — ` +
310
+ "dead dispatch the command-layer gate blocks",
311
+ ).toBe(true);
312
+ }
313
+ },
314
+ );
272
315
 
273
316
  it("special cases do not shadow a real dispatch entry", () => {
274
317
  for (const { label, wired, specialCases } of DISPATCH) {
@@ -292,8 +335,14 @@ describe("registry — verb/dispatch conformance", () => {
292
335
  // alias set, so a future bypass for an addressable kind is a red test, not
293
336
  // a silently lying `list types` row.
294
337
  describe("registry — pre-gate list aliases cannot shadow registered kinds", () => {
295
- const PRE_GATE_PREDICATES: ReadonlyArray<{ name: string; matches: (type: string) => boolean }> = [
296
- { name: "executions (resources/execution.ts isExecutionAlias)", matches: isExecutionAlias },
338
+ const PRE_GATE_PREDICATES: ReadonlyArray<{
339
+ name: string;
340
+ matches: (type: string) => boolean;
341
+ }> = [
342
+ {
343
+ name: "executions (resources/execution.ts isExecutionAlias)",
344
+ matches: isExecutionAlias,
345
+ },
297
346
  ];
298
347
 
299
348
  it("no registered kind's alias is intercepted by a pre-gate predicate", () => {
@@ -64,7 +64,12 @@ function buildTypeInfo(kind: ApiResourceKind): TypeInfo | undefined {
64
64
  // ApiResourceKind[kind] reverse-maps to the proto enum value name (e.g.
65
65
  // "oauth_app") — the canonical spelling, taken from the enum itself so it
66
66
  // can never drift from the proto.
67
- aliases: generateAliases(meta.name, meta.displayName, meta.idPrefix, ApiResourceKind[kind]),
67
+ aliases: generateAliases(
68
+ meta.name,
69
+ meta.displayName,
70
+ meta.idPrefix,
71
+ ApiResourceKind[kind],
72
+ ),
68
73
  supportedVerbs: verbsForKind(kind),
69
74
  };
70
75
  }
@@ -91,7 +96,8 @@ function buildRegistry(): Registry {
91
96
  getByAlias: (input) => byAlias.get(normalizeAlias(input)),
92
97
  getByYamlKind: (yamlKind) => byYamlKind.get(yamlKind),
93
98
  all: () => all,
94
- supportsVerb: (kind, verb) => byKind.get(kind)?.supportedVerbs.has(verb) ?? false,
99
+ supportsVerb: (kind, verb) =>
100
+ byKind.get(kind)?.supportedVerbs.has(verb) ?? false,
95
101
  typesForVerb: (verb) => all.filter((info) => info.supportedVerbs.has(verb)),
96
102
  };
97
103
  }
@@ -13,50 +13,121 @@
13
13
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
14
14
  import { Verb } from "./verbs.js";
15
15
 
16
- export const VERB_SUPPORT: ReadonlyMap<ApiResourceKind, ReadonlySet<Verb>> = new Map([
17
- [ApiResourceKind.organization, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
16
+ export const VERB_SUPPORT: ReadonlyMap<
17
+ ApiResourceKind,
18
+ ReadonlySet<Verb>
19
+ > = new Map([
20
+ [
21
+ ApiResourceKind.organization,
22
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
23
+ ],
18
24
  [
19
25
  ApiResourceKind.agent,
20
- new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete, Verb.Run, Verb.Search]),
26
+ new Set<Verb>([
27
+ Verb.Apply,
28
+ Verb.Validate,
29
+ Verb.Get,
30
+ Verb.List,
31
+ Verb.Delete,
32
+ Verb.Run,
33
+ Verb.Search,
34
+ ]),
21
35
  ],
22
36
  [
23
37
  ApiResourceKind.workflow,
24
- new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete, Verb.Run, Verb.Search]),
38
+ new Set<Verb>([
39
+ Verb.Apply,
40
+ Verb.Validate,
41
+ Verb.Get,
42
+ Verb.List,
43
+ Verb.Delete,
44
+ Verb.Run,
45
+ Verb.Search,
46
+ ]),
47
+ ],
48
+ [
49
+ ApiResourceKind.skill,
50
+ new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push]),
51
+ ],
52
+ // A plugin is installed from its folder (`push plugin <dir>`, or `apply`
53
+ // pointed at one); it is never authored as YAML, so no Validate/Apply of a
54
+ // document — `validate -f <dir>` reads the folder through the plugin route.
55
+ [
56
+ ApiResourceKind.plugin,
57
+ new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push]),
58
+ ],
59
+ [
60
+ ApiResourceKind.mcp_server,
61
+ new Set<Verb>([
62
+ Verb.Apply,
63
+ Verb.Validate,
64
+ Verb.Get,
65
+ Verb.List,
66
+ Verb.Delete,
67
+ ]),
25
68
  ],
26
- [ApiResourceKind.skill, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Push])],
27
- [ApiResourceKind.mcp_server, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
28
69
  // Project apply is the stigmer.yaml declarative/synthesis track, not
29
70
  // `apply -f project.yaml` — resolveHandlerForKind carries the teaching
30
71
  // refusal, and the conformance test documents it as a special case.
31
- [ApiResourceKind.project, new Set<Verb>([Verb.Apply, Verb.Validate, Verb.Get, Verb.List, Verb.Delete])],
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
+ ],
32
82
  [ApiResourceKind.api_key, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete])],
33
83
  // agent_execution is special — uses dedicated AgentExecutionQueryController
34
84
  // RPCs, not the unified SearchService. delete maps to cancel.
35
- [ApiResourceKind.agent_execution, new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Download])],
85
+ [
86
+ ApiResourceKind.agent_execution,
87
+ new Set<Verb>([Verb.Get, Verb.List, Verb.Delete, Verb.Download]),
88
+ ],
36
89
  // IAM apps are configured declaratively; read/ops verbs are deliberately
37
90
  // not promised (never wired, no demand — stigmer/stigmer#354). To add one:
38
91
  // dispatch entry + this line, in the same change.
39
92
  [ApiResourceKind.identity_provider, new Set<Verb>([Verb.Apply])],
40
93
  [ApiResourceKind.oauth_app, new Set<Verb>([Verb.Apply])],
41
- [ApiResourceKind.environment, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
94
+ [
95
+ ApiResourceKind.environment,
96
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
97
+ ],
42
98
  // Enable/disable flows through `stigmer share agent`, which is the whole
43
99
  // read/ops surface today; only the declarative apply path is promised
44
100
  // (stigmer/stigmer#354 records the narrowing).
45
101
  [ApiResourceKind.agent_share, new Set<Verb>([Verb.Apply])],
46
102
  // The provider install flow is console-driven and cloud-only; the generic
47
103
  // verbs cover the declarative path (apply a manifest, inspect, tear down).
48
- [ApiResourceKind.agent_channel, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
104
+ [
105
+ ApiResourceKind.agent_channel,
106
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
107
+ ],
49
108
  // Secret fields round-trip as ***REDACTED***; applying a fetched manifest
50
109
  // preserves the stored secrets (the OAuthApp marker convention).
51
- [ApiResourceKind.channel_app, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
110
+ [
111
+ ApiResourceKind.channel_app,
112
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
113
+ ],
52
114
  // Firing is the platform's job (Temporal Schedules); the generic verbs
53
115
  // cover the declarative path, and get/list surface the state the dedicated
54
116
  // `stigmer schedule` commands act on — spec.enabled (the owner's switch)
55
117
  // versus status.paused_reason (the platform's failure latch).
56
- [ApiResourceKind.schedule, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
57
- [ApiResourceKind.agent_instance, new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete])],
118
+ [
119
+ ApiResourceKind.schedule,
120
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
121
+ ],
122
+ [
123
+ ApiResourceKind.agent_instance,
124
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.List, Verb.Delete]),
125
+ ],
58
126
  // No list: proto exposes getByWorkflow (requires workflow_id), not a generic list.
59
- [ApiResourceKind.workflow_instance, new Set<Verb>([Verb.Apply, Verb.Get, Verb.Delete])],
127
+ [
128
+ ApiResourceKind.workflow_instance,
129
+ new Set<Verb>([Verb.Apply, Verb.Get, Verb.Delete]),
130
+ ],
60
131
  // Session list is served by its dedicated query RPC (a LIST_HANDLERS
61
132
  // entry); stigmer/stigmer#469 promoted it into the matrix after it shipped
62
133
  // working-but-unadvertised through a pre-gate bypass. get/delete remain
@@ -23,7 +23,12 @@ import type { Stigmer } from "@stigmer/sdk";
23
23
  import { CliExitError, ExitCode, UsageError } from "../errors/index.js";
24
24
  import { CommandResult } from "../output/index.js";
25
25
  import { defaultRegistry, type TypeInfo, Verb } from "../registry/index.js";
26
- import { cancelAgentExecution, formatAgentPhase, isAgentExecutionId, isExecutionAlias } from "./execution.js";
26
+ import {
27
+ cancelAgentExecution,
28
+ formatAgentPhase,
29
+ isAgentExecutionId,
30
+ isExecutionAlias,
31
+ } from "./execution.js";
27
32
  import { fetchResource } from "./get.js";
28
33
  import { getterFor } from "./get-bindings.js";
29
34
  import { parseReference } from "./reference.js";
@@ -42,13 +47,22 @@ export interface DeletePlan {
42
47
  // the structural slice the delete UI needs. Reading it generically keeps the
43
48
  // dispatch kind-agnostic without a per-kind accessor.
44
49
  interface HasMetadata {
45
- readonly metadata?: { readonly id?: string; readonly name?: string; readonly slug?: string; readonly org?: string };
50
+ readonly metadata?: {
51
+ readonly id?: string;
52
+ readonly name?: string;
53
+ readonly slug?: string;
54
+ readonly org?: string;
55
+ };
46
56
  }
47
57
 
48
58
  // `force` is the RPC-level acknowledgment of destructive side effects —
49
59
  // distinct from, but carried by, the CLI's `-f/--force` flag. Kinds whose delete RPC takes a plain typed ID have no
50
60
  // force field and simply ignore the argument.
51
- type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMetadata>;
61
+ type DeleteFn = (
62
+ client: Stigmer,
63
+ id: string,
64
+ force: boolean,
65
+ ) => Promise<HasMetadata>;
52
66
 
53
67
  // Kinds the unified delete handles directly, each bound to its SDK delete call.
54
68
  // Every kind declaring Delete in the verb matrix must have an entry here (or a
@@ -56,20 +70,33 @@ type DeleteFn = (client: Stigmer, id: string, force: boolean) => Promise<HasMeta
56
70
  // enforces it, so the two cannot drift.
57
71
  // Kinds whose delete takes a DeleteResourceInput (not an ID) thread the force
58
72
  // acknowledgment through — force is the contract's generic ack carrier.
59
- export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<ApiResourceKind, DeleteFn>([
73
+ export const DELETE_HANDLERS: ReadonlyMap<ApiResourceKind, DeleteFn> = new Map<
74
+ ApiResourceKind,
75
+ DeleteFn
76
+ >([
60
77
  [ApiResourceKind.agent, (c, id) => c.agent.delete(id)],
61
78
  [ApiResourceKind.agent_instance, (c, id) => c.agentInstance.delete(id)],
62
79
  [ApiResourceKind.workflow, (c, id) => c.workflow.delete(id)],
63
80
  [ApiResourceKind.workflow_instance, (c, id) => c.workflowInstance.delete(id)],
64
- [ApiResourceKind.mcp_server, (c, id, force) => c.mcpServer.delete({ resourceId: id, force })],
81
+ [
82
+ ApiResourceKind.mcp_server,
83
+ (c, id, force) => c.mcpServer.delete({ resourceId: id, force }),
84
+ ],
65
85
  [ApiResourceKind.project, (c, id) => c.project.delete(id)],
66
- [ApiResourceKind.environment, (c, id, force) => c.environment.delete({ resourceId: id, force })],
86
+ [
87
+ ApiResourceKind.environment,
88
+ (c, id, force) => c.environment.delete({ resourceId: id, force }),
89
+ ],
67
90
  [ApiResourceKind.agent_channel, (c, id) => c.agentChannel.delete(id)],
68
- [ApiResourceKind.channel_app, (c, id, force) => c.channelapp.delete({ resourceId: id, force })],
91
+ [
92
+ ApiResourceKind.channel_app,
93
+ (c, id, force) => c.channelapp.delete({ resourceId: id, force }),
94
+ ],
69
95
  // A schedule delete also tears down its Temporal Schedule artifact
70
96
  // server-side; the RPC takes a bare ScheduleId, so force is ignored.
71
97
  [ApiResourceKind.schedule, (c, id) => c.schedule.delete(id)],
72
98
  [ApiResourceKind.skill, (c, id) => c.skill.delete(id)],
99
+ [ApiResourceKind.plugin, (c, id) => c.plugin.delete(id)],
73
100
  [ApiResourceKind.api_key, (c, id) => c.apiKey.delete(id)],
74
101
  ]);
75
102
 
@@ -107,7 +134,9 @@ export async function planDelete(
107
134
 
108
135
  const info = defaultRegistry().getByAlias(typeArg);
109
136
  if (info === undefined) {
110
- throw new UsageError(`unknown resource type: ${typeArg}\n\nAvailable types: ${availableDeleteTypes()}`);
137
+ throw new UsageError(
138
+ `unknown resource type: ${typeArg}\n\nAvailable types: ${availableDeleteTypes()}`,
139
+ );
111
140
  }
112
141
 
113
142
  if (info.kind === ApiResourceKind.organization) {
@@ -143,25 +172,38 @@ async function planStandardDelete(
143
172
  const parsed = parseReference(reference, org, info.idPrefix);
144
173
  // ResourceResult.message is the opaque proto Message; every resource carries
145
174
  // the ApiResourceMetadata envelope, so read it through the structural view.
146
- const resource = (await getter(client, parsed)).message as unknown as HasMetadata;
175
+ const resource = (await getter(client, parsed))
176
+ .message as unknown as HasMetadata;
147
177
  const id = metaOf(resource).id;
148
178
 
149
179
  return {
150
180
  warning: buildDeleteWarning(info, resource),
151
181
  confirmPrompt: "Proceed with deletion? [y/N]",
152
- perform: async () => buildDeleteSuccess(info, await deleteFn(client, id, force)),
182
+ perform: async () =>
183
+ buildDeleteSuccess(info, await deleteFn(client, id, force)),
153
184
  };
154
185
  }
155
186
 
156
- async function planOrganizationDelete(client: Stigmer, reference: string): Promise<DeletePlan> {
187
+ async function planOrganizationDelete(
188
+ client: Stigmer,
189
+ reference: string,
190
+ ): Promise<DeletePlan> {
157
191
  // Organizations are not org-scoped: a bare token is a slug resolved against
158
192
  // the caller's memberships (fetchResource handles the id-vs-slug split).
159
193
  const parsed = parseReference(reference, "", "org");
160
- const resource = (await fetchResource(client, ApiResourceKind.organization, parsed)).message as unknown as HasMetadata;
194
+ const resource = (
195
+ await fetchResource(client, ApiResourceKind.organization, parsed)
196
+ ).message as unknown as HasMetadata;
161
197
  const meta = metaOf(resource);
162
198
 
163
- const warning = CommandResult.warning("You are about to delete the following organization:");
164
- warning.addSection("").field("ID", meta.id).field("Name", meta.name).field("Slug", meta.slug);
199
+ const warning = CommandResult.warning(
200
+ "You are about to delete the following organization:",
201
+ );
202
+ warning
203
+ .addSection("")
204
+ .field("ID", meta.id)
205
+ .field("Name", meta.name)
206
+ .field("Slug", meta.slug);
165
207
  warning.hint("This will delete the organization and all its resources.");
166
208
  warning.hint("This action cannot be undone.");
167
209
 
@@ -171,7 +213,11 @@ async function planOrganizationDelete(client: Stigmer, reference: string): Promi
171
213
  perform: async () => {
172
214
  const deleted = metaOf(await client.organization.delete(meta.id));
173
215
  const out = CommandResult.success("Organization deleted successfully");
174
- out.addSection("Deleted Organization").field("ID", deleted.id).field("Name", deleted.name).field("Slug", deleted.slug);
216
+ out
217
+ .addSection("Deleted Organization")
218
+ .field("ID", deleted.id)
219
+ .field("Name", deleted.name)
220
+ .field("Slug", deleted.slug);
175
221
  return out;
176
222
  },
177
223
  };
@@ -185,16 +231,23 @@ function planExecutionCancel(client: Stigmer, reference: string): DeletePlan {
185
231
  );
186
232
  }
187
233
 
188
- const warning = CommandResult.warning(`You are about to cancel execution: ${reference}`);
234
+ const warning = CommandResult.warning(
235
+ `You are about to cancel execution: ${reference}`,
236
+ );
189
237
  warning.hint("This will gracefully stop the running agent.");
190
238
 
191
239
  return {
192
240
  warning,
193
241
  confirmPrompt: "Proceed with cancellation? [y/N]",
194
242
  perform: async () => {
195
- const { execution, wasAlreadyTerminal } = await cancelAgentExecution(client, reference);
243
+ const { execution, wasAlreadyTerminal } = await cancelAgentExecution(
244
+ client,
245
+ reference,
246
+ );
196
247
  const id = execution.metadata?.id ?? "";
197
- const status = formatAgentPhase(execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED);
248
+ const status = formatAgentPhase(
249
+ execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED,
250
+ );
198
251
 
199
252
  const out = wasAlreadyTerminal
200
253
  ? CommandResult.warning("Execution was already in terminal state")
@@ -207,13 +260,18 @@ function planExecutionCancel(client: Stigmer, reference: string): DeletePlan {
207
260
 
208
261
  // --- Warning + success rendering (mirrors Go's per-type delete handlers) ---
209
262
 
210
- function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult {
263
+ function buildDeleteWarning(
264
+ info: TypeInfo,
265
+ message: HasMetadata,
266
+ ): CommandResult {
211
267
  if (info.kind === ApiResourceKind.api_key) {
212
268
  return buildApiKeyWarning(message);
213
269
  }
214
270
 
215
271
  const meta = metaOf(message);
216
- const warning = CommandResult.warning(`You are about to delete the following ${info.displayName.toLowerCase()}:`);
272
+ const warning = CommandResult.warning(
273
+ `You are about to delete the following ${info.displayName.toLowerCase()}:`,
274
+ );
217
275
  const section = warning
218
276
  .addSection("")
219
277
  .field("ID", meta.id)
@@ -227,10 +285,19 @@ function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult
227
285
  warning.hint("This will delete the skill and all its versions.");
228
286
  }
229
287
 
288
+ if (info.kind === ApiResourceKind.plugin) {
289
+ warning.hint(
290
+ "This removes the plugin and every skill, MCP server, agent and workflow it installed. " +
291
+ "It is refused while another agent or workflow of yours still references one of them.",
292
+ );
293
+ }
294
+
230
295
  if (info.kind === ApiResourceKind.agent_channel) {
231
296
  // The server's documented distinction: delete is the connection's full
232
297
  // teardown; disabling is the config-preserving pause.
233
- warning.hint("Delete is the connection's full teardown; to pause instead, apply with spec.enabled: false.");
298
+ warning.hint(
299
+ "Delete is the connection's full teardown; to pause instead, apply with spec.enabled: false.",
300
+ );
234
301
  }
235
302
 
236
303
  warning.hint("This action cannot be undone.");
@@ -239,20 +306,28 @@ function buildDeleteWarning(info: TypeInfo, message: HasMetadata): CommandResult
239
306
 
240
307
  function buildApiKeyWarning(message: HasMetadata): CommandResult {
241
308
  const meta = metaOf(message);
242
- const warning = CommandResult.warning("You are about to delete the following API key:");
309
+ const warning = CommandResult.warning(
310
+ "You are about to delete the following API key:",
311
+ );
243
312
  const section = warning.addSection("").field("ID", meta.id);
244
313
  if (meta.name) section.field("Name", meta.name);
245
- const fingerprint = (message as { spec?: { fingerprint?: string } }).spec?.fingerprint;
314
+ const fingerprint = (message as { spec?: { fingerprint?: string } }).spec
315
+ ?.fingerprint;
246
316
  if (fingerprint) section.field("Fingerprint", `***${fingerprint}`);
247
317
  warning.hint("This will permanently revoke the API key.");
248
318
  warning.hint("This action cannot be undone.");
249
319
  return warning;
250
320
  }
251
321
 
252
- function buildDeleteSuccess(info: TypeInfo, deleted: HasMetadata): CommandResult {
322
+ function buildDeleteSuccess(
323
+ info: TypeInfo,
324
+ deleted: HasMetadata,
325
+ ): CommandResult {
253
326
  const meta = metaOf(deleted);
254
327
  const out = CommandResult.success(`${info.displayName} deleted successfully`);
255
- const section = out.addSection(`Deleted ${info.displayName}`).field("ID", meta.id);
328
+ const section = out
329
+ .addSection(`Deleted ${info.displayName}`)
330
+ .field("ID", meta.id);
256
331
 
257
332
  if (info.kind === ApiResourceKind.api_key) {
258
333
  if (meta.name) section.field("Name", meta.name);
@@ -263,7 +338,12 @@ function buildDeleteSuccess(info: TypeInfo, deleted: HasMetadata): CommandResult
263
338
  return out;
264
339
  }
265
340
 
266
- function metaOf(message: HasMetadata): { id: string; name: string; slug: string; org: string } {
341
+ function metaOf(message: HasMetadata): {
342
+ id: string;
343
+ name: string;
344
+ slug: string;
345
+ org: string;
346
+ } {
267
347
  return {
268
348
  id: message.metadata?.id ?? "",
269
349
  name: message.metadata?.name ?? "",