@stigmer/cli 3.4.0 → 3.5.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.
Files changed (42) hide show
  1. package/commands/setup.d.ts.map +1 -1
  2. package/commands/setup.js +11 -16
  3. package/commands/setup.js.map +1 -1
  4. package/local/daemon/components.d.ts.map +1 -1
  5. package/local/daemon/components.js +3 -2
  6. package/local/daemon/components.js.map +1 -1
  7. package/local/daemon/env.d.ts +0 -3
  8. package/local/daemon/env.d.ts.map +1 -1
  9. package/local/daemon/env.js +4 -8
  10. package/local/daemon/env.js.map +1 -1
  11. package/local/daemon/launch.d.ts.map +1 -1
  12. package/local/daemon/launch.js +9 -17
  13. package/local/daemon/launch.js.map +1 -1
  14. package/local/llm-config.d.ts +7 -7
  15. package/local/llm-config.d.ts.map +1 -1
  16. package/local/llm-config.js +29 -42
  17. package/local/llm-config.js.map +1 -1
  18. package/local/setup/wizard.d.ts +16 -7
  19. package/local/setup/wizard.d.ts.map +1 -1
  20. package/local/setup/wizard.js +41 -44
  21. package/local/setup/wizard.js.map +1 -1
  22. package/local/status.d.ts.map +1 -1
  23. package/local/status.js +16 -15
  24. package/local/status.js.map +1 -1
  25. package/package.json +5 -5
  26. package/resources/list.d.ts.map +1 -1
  27. package/resources/list.js +70 -4
  28. package/resources/list.js.map +1 -1
  29. package/src/commands/setup.ts +13 -20
  30. package/src/local/daemon/components.test.ts +8 -2
  31. package/src/local/daemon/components.ts +3 -1
  32. package/src/local/daemon/env.test.ts +13 -6
  33. package/src/local/daemon/env.ts +10 -13
  34. package/src/local/daemon/launch.ts +9 -17
  35. package/src/local/llm-config.test.ts +16 -27
  36. package/src/local/llm-config.ts +27 -41
  37. package/src/local/setup/wizard.test.ts +19 -11
  38. package/src/local/setup/wizard.ts +42 -47
  39. package/src/local/status.test.ts +20 -0
  40. package/src/local/status.ts +17 -15
  41. package/src/resources/list.ts +76 -4
  42. package/src/resources/read-verbs.integration.test.ts +127 -3
@@ -7,12 +7,14 @@
7
7
  // to the right CLI exit code via classify().
8
8
 
9
9
  import { create, toJson } from "@bufbuild/protobuf";
10
+ import { timestampFromDate } from "@bufbuild/protobuf/wkt";
10
11
  import { Code, ConnectError, type ConnectRouter } from "@connectrpc/connect";
11
12
  import { connectNodeAdapter } from "@connectrpc/connect-node";
12
13
  import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
13
14
  import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
14
15
  import { AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
15
16
  import { AgentChannelQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/query_pb";
17
+ import { AgentChannelInstallState } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/status_pb";
16
18
  import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
17
19
  import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/query_pb";
18
20
  import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
@@ -73,24 +75,71 @@ const knownSearchResult = create(SearchResultSchema, {
73
75
  description: "reviews code",
74
76
  });
75
77
 
76
- // The three T07 cutover kinds — wired into get-bindings alongside this test.
78
+ // The three T07 cutover kinds — wired into get-bindings and list alongside
79
+ // this test. environment lists via the SearchService (it is search-indexed);
80
+ // the two channel kinds list via their dedicated query RPCs.
77
81
  const knownEnvironment = create(EnvironmentSchema, {
78
82
  apiVersion: "agentic.stigmer.ai/v1",
79
83
  kind: "Environment",
80
84
  metadata: { name: "clinic-patient-db", slug: "clinic-patient-db", org: "acme", id: "env_1" },
81
85
  });
82
86
 
87
+ const knownEnvironmentSearchResult = create(SearchResultSchema, {
88
+ kind: ApiResourceKind.environment,
89
+ id: "env_1",
90
+ name: "clinic-patient-db",
91
+ slug: "clinic-patient-db",
92
+ qualifiedSlug: "acme/clinic-patient-db",
93
+ org: "acme",
94
+ description: "clinic patient database credentials",
95
+ });
96
+
97
+ // Spec/status populated to exercise every AGENT_CHANNEL_TABLE column:
98
+ // agent_ref, the provider oneof, install_state, and enabled.
83
99
  const knownChannel = create(AgentChannelSchema, {
84
100
  apiVersion: "agentic.stigmer.ai/v1",
85
101
  kind: "AgentChannel",
86
102
  metadata: { name: "clinic-patient-whatsapp", slug: "clinic-patient-whatsapp", org: "acme", id: "ach_1" },
87
- spec: { enabled: true },
103
+ spec: {
104
+ enabled: true,
105
+ agentRef: { kind: ApiResourceKind.agent, org: "acme", slug: "clinic-assistant" },
106
+ providerConfig: { case: "whatsapp", value: { phoneNumberId: "106540352242922" } },
107
+ },
108
+ status: { installState: AgentChannelInstallState.installed },
88
109
  });
89
110
 
111
+ // Secret fields carry the redaction marker, matching what the server's
112
+ // RedactChannelApp pipeline returns on every list/get response.
90
113
  const knownChannelApp = create(ChannelAppSchema, {
91
114
  apiVersion: "agentic.stigmer.ai/v1",
92
115
  kind: "ChannelApp",
93
116
  metadata: { name: "clinic-meta-app", slug: "clinic-meta-app", org: "acme", id: "chapp_1" },
117
+ spec: {
118
+ providerConfig: {
119
+ case: "whatsapp",
120
+ value: {
121
+ appId: "108954",
122
+ appSecret: "***REDACTED***",
123
+ accessToken: "***REDACTED***",
124
+ verifyToken: "***REDACTED***",
125
+ },
126
+ },
127
+ },
128
+ status: { audit: { specAudit: { createdAt: timestampFromDate(new Date("2026-07-20T10:00:00Z")) } } },
129
+ });
130
+
131
+ // Second entry (slack arm) so the list tests cover both provider derivations
132
+ // and the client-side --limit slice (listByOrg has no pagination).
133
+ const secondChannelApp = create(ChannelAppSchema, {
134
+ apiVersion: "agentic.stigmer.ai/v1",
135
+ kind: "ChannelApp",
136
+ metadata: { name: "clinic-slack-app", slug: "clinic-slack-app", org: "acme", id: "chapp_2" },
137
+ spec: {
138
+ providerConfig: {
139
+ case: "slack",
140
+ value: { clientId: "12.34", clientSecret: "***REDACTED***", signingSecret: "***REDACTED***" },
141
+ },
142
+ },
94
143
  });
95
144
 
96
145
  let backend: Http2Server;
@@ -124,7 +173,14 @@ beforeAll(async () => {
124
173
  },
125
174
  });
126
175
  router.service(SearchService, {
127
- search: () => ({ entries: [knownSearchResult], totalCount: 1, totalPages: 1 }),
176
+ // Kind-aware: search-backed list sends a single-kind query, so the
177
+ // environment arm proves the kind actually rode the request.
178
+ search: (req) => {
179
+ if (req.kinds.length === 1 && req.kinds[0] === ApiResourceKind.environment) {
180
+ return { entries: [knownEnvironmentSearchResult], totalCount: 1, totalPages: 1 };
181
+ }
182
+ return { entries: [knownSearchResult], totalCount: 1, totalPages: 1 };
183
+ },
128
184
  });
129
185
  router.service(OrganizationQueryController, {
130
186
  findMyOrganizations: () => ({ entries: [knownOrg] }),
@@ -151,6 +207,10 @@ beforeAll(async () => {
151
207
  if (req.slug !== "clinic-patient-whatsapp") throw new ConnectError("agent channel not found", Code.NotFound);
152
208
  return knownChannel;
153
209
  },
210
+ list: (req) => {
211
+ if (req.org !== "acme") throw new ConnectError("org is required", Code.InvalidArgument);
212
+ return { totalCount: 1, items: [knownChannel] };
213
+ },
154
214
  });
155
215
  router.service(ChannelAppQueryController, {
156
216
  get: (req) => {
@@ -161,6 +221,10 @@ beforeAll(async () => {
161
221
  if (req.slug !== "clinic-meta-app") throw new ConnectError("channel app not found", Code.NotFound);
162
222
  return knownChannelApp;
163
223
  },
224
+ listByOrg: (req) => {
225
+ if (req.org !== "acme") throw new ConnectError("org is required", Code.InvalidArgument);
226
+ return { entries: [knownChannelApp, secondChannelApp] };
227
+ },
164
228
  });
165
229
  };
166
230
  backend = createHttp2Server(connectNodeAdapter({ routes }));
@@ -324,4 +388,64 @@ describe("list integration", () => {
324
388
  expect(out).toContain("reviewer-default");
325
389
  expect(out).toContain("agt_1");
326
390
  });
391
+
392
+ it("lists environments via the search service as JSON", async () => {
393
+ const out = await listResources(client, ApiResourceKind.environment, "acme", 50, "json");
394
+ expect(JSON.parse(out)).toEqual([
395
+ toJson(SearchResultSchema, knownEnvironmentSearchResult, { useProtoFieldName: true }),
396
+ ]);
397
+ });
398
+
399
+ it("renders a human table for environment lists", async () => {
400
+ const out = await listResources(client, ApiResourceKind.environment, "acme", 50, "table");
401
+ expect(out).toContain("NAME");
402
+ expect(out).toContain("acme/clinic-patient-db");
403
+ });
404
+
405
+ it("lists agent channels via the dedicated list RPC as JSON", async () => {
406
+ const out = await listResources(client, ApiResourceKind.agent_channel, "acme", 50, "json");
407
+ expect(JSON.parse(out)).toEqual([toJson(AgentChannelSchema, knownChannel, { useProtoFieldName: true })]);
408
+ });
409
+
410
+ it("renders a human table for agent channels with the serving signals", async () => {
411
+ const out = await listResources(client, ApiResourceKind.agent_channel, "acme", 50, "table");
412
+ expect(out).toContain("PROVIDER");
413
+ expect(out).toContain("STATE");
414
+ expect(out).toContain("ENABLED");
415
+ expect(out).toContain("acme/clinic-assistant"); // agent_ref as org/slug
416
+ expect(out).toContain("whatsapp"); // derived from the provider oneof
417
+ expect(out).toContain("installed"); // status.install_state
418
+ expect(out).toContain("true"); // spec.enabled
419
+ });
420
+
421
+ it("lists channel apps via listByOrg as JSON", async () => {
422
+ const out = await listResources(client, ApiResourceKind.channel_app, "acme", 50, "json");
423
+ expect(JSON.parse(out)).toEqual([
424
+ toJson(ChannelAppSchema, knownChannelApp, { useProtoFieldName: true }),
425
+ toJson(ChannelAppSchema, secondChannelApp, { useProtoFieldName: true }),
426
+ ]);
427
+ });
428
+
429
+ it("renders a human table for channel apps with provider and created date", async () => {
430
+ const out = await listResources(client, ApiResourceKind.channel_app, "acme", 50, "table");
431
+ expect(out).toContain("PROVIDER");
432
+ expect(out).toContain("whatsapp");
433
+ expect(out).toContain("slack");
434
+ expect(out).toContain("2026-07-20"); // status.audit.spec_audit.created_at
435
+ // Secrets never reach the table (redaction markers stay in json/yaml only).
436
+ expect(out).not.toContain("***REDACTED***");
437
+ });
438
+
439
+ it("applies --limit client-side for channel apps (listByOrg has no pagination)", async () => {
440
+ const out = await listResources(client, ApiResourceKind.channel_app, "acme", 1, "json");
441
+ expect(JSON.parse(out)).toEqual([toJson(ChannelAppSchema, knownChannelApp, { useProtoFieldName: true })]);
442
+ });
443
+
444
+ it("rejects a kind whose list is unwired with a usage error", async () => {
445
+ // agent_share declares List in the registry but has no list branch —
446
+ // the canonical still-unwired list kind (the three cutover kinds and
447
+ // environment gained branches).
448
+ const err = await listResources(client, ApiResourceKind.agent_share, "acme", 50, "json").catch((e) => e);
449
+ expect(classify(err)?.exitCode).toBe(ExitCode.Usage);
450
+ });
327
451
  });