@chanl-ai/cli 2.0.1

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 (77) hide show
  1. package/bin/chanl.js +10 -0
  2. package/dist/__tests__/cli.test.d.ts +2 -0
  3. package/dist/__tests__/cli.test.js +2313 -0
  4. package/dist/__tests__/cli.test.js.map +1 -0
  5. package/dist/cli.d.ts +12 -0
  6. package/dist/cli.js +72 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/agents.d.ts +8 -0
  9. package/dist/commands/agents.js +671 -0
  10. package/dist/commands/agents.js.map +1 -0
  11. package/dist/commands/auth.d.ts +16 -0
  12. package/dist/commands/auth.js +294 -0
  13. package/dist/commands/auth.js.map +1 -0
  14. package/dist/commands/call.d.ts +8 -0
  15. package/dist/commands/call.js +166 -0
  16. package/dist/commands/call.js.map +1 -0
  17. package/dist/commands/calls.d.ts +8 -0
  18. package/dist/commands/calls.js +719 -0
  19. package/dist/commands/calls.js.map +1 -0
  20. package/dist/commands/chat.d.ts +8 -0
  21. package/dist/commands/chat.js +203 -0
  22. package/dist/commands/chat.js.map +1 -0
  23. package/dist/commands/config.d.ts +8 -0
  24. package/dist/commands/config.js +231 -0
  25. package/dist/commands/config.js.map +1 -0
  26. package/dist/commands/health.d.ts +8 -0
  27. package/dist/commands/health.js +55 -0
  28. package/dist/commands/health.js.map +1 -0
  29. package/dist/commands/index.d.ts +18 -0
  30. package/dist/commands/index.js +39 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/knowledge.d.ts +8 -0
  33. package/dist/commands/knowledge.js +539 -0
  34. package/dist/commands/knowledge.js.map +1 -0
  35. package/dist/commands/mcp.d.ts +8 -0
  36. package/dist/commands/mcp.js +589 -0
  37. package/dist/commands/mcp.js.map +1 -0
  38. package/dist/commands/memory.d.ts +8 -0
  39. package/dist/commands/memory.js +408 -0
  40. package/dist/commands/memory.js.map +1 -0
  41. package/dist/commands/personas.d.ts +8 -0
  42. package/dist/commands/personas.js +356 -0
  43. package/dist/commands/personas.js.map +1 -0
  44. package/dist/commands/prompts.d.ts +8 -0
  45. package/dist/commands/prompts.js +295 -0
  46. package/dist/commands/prompts.js.map +1 -0
  47. package/dist/commands/scenarios.d.ts +8 -0
  48. package/dist/commands/scenarios.js +591 -0
  49. package/dist/commands/scenarios.js.map +1 -0
  50. package/dist/commands/scorecards.d.ts +8 -0
  51. package/dist/commands/scorecards.js +570 -0
  52. package/dist/commands/scorecards.js.map +1 -0
  53. package/dist/commands/tools.d.ts +8 -0
  54. package/dist/commands/tools.js +632 -0
  55. package/dist/commands/tools.js.map +1 -0
  56. package/dist/commands/toolsets.d.ts +8 -0
  57. package/dist/commands/toolsets.js +464 -0
  58. package/dist/commands/toolsets.js.map +1 -0
  59. package/dist/commands/workspaces.d.ts +8 -0
  60. package/dist/commands/workspaces.js +170 -0
  61. package/dist/commands/workspaces.js.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +6 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/utils/config-store.d.ts +117 -0
  66. package/dist/utils/config-store.js +191 -0
  67. package/dist/utils/config-store.js.map +1 -0
  68. package/dist/utils/interactive.d.ts +41 -0
  69. package/dist/utils/interactive.js +83 -0
  70. package/dist/utils/interactive.js.map +1 -0
  71. package/dist/utils/output.d.ts +100 -0
  72. package/dist/utils/output.js +221 -0
  73. package/dist/utils/output.js.map +1 -0
  74. package/dist/utils/sdk-factory.d.ts +15 -0
  75. package/dist/utils/sdk-factory.js +34 -0
  76. package/dist/utils/sdk-factory.js.map +1 -0
  77. package/package.json +67 -0
@@ -0,0 +1,2313 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { createProgram } from "../cli";
3
+ import { configStore } from "../utils/config-store";
4
+ import { ChanlSDK } from "@chanl-ai/sdk";
5
+ vi.mock("@chanl-ai/sdk", () => ({
6
+ ChanlSDK: vi.fn()
7
+ }));
8
+ vi.mock("../utils/config-store", () => ({
9
+ configStore: {
10
+ getApiKey: vi.fn(),
11
+ getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
12
+ getWorkspaceId: vi.fn(() => "ws_123"),
13
+ getJwtToken: vi.fn(() => null),
14
+ getAuthMethod: vi.fn(() => null),
15
+ getAppUrl: vi.fn(() => "https://app.chanl.ai"),
16
+ setApiKey: vi.fn(),
17
+ setBaseUrl: vi.fn(),
18
+ setWorkspaceId: vi.fn(),
19
+ removeApiKey: vi.fn(),
20
+ clearAuth: vi.fn(),
21
+ delete: vi.fn(),
22
+ isAuthenticated: vi.fn(),
23
+ getPath: vi.fn(() => "/home/user/.chanl/config.json")
24
+ }
25
+ }));
26
+ function captureOutput() {
27
+ const logs = [];
28
+ const errors = [];
29
+ const originalLog = console.log;
30
+ const originalError = console.error;
31
+ console.log = (...args) => {
32
+ logs.push(args.map(String).join(" "));
33
+ };
34
+ console.error = (...args) => {
35
+ errors.push(args.map(String).join(" "));
36
+ };
37
+ return {
38
+ logs,
39
+ errors,
40
+ restore: () => {
41
+ console.log = originalLog;
42
+ console.error = originalError;
43
+ }
44
+ };
45
+ }
46
+ async function runCli(args) {
47
+ const output = captureOutput();
48
+ const program = createProgram();
49
+ process.exitCode = void 0;
50
+ try {
51
+ await program.parseAsync(["node", "chanl", ...args]);
52
+ } catch {
53
+ }
54
+ output.restore();
55
+ return {
56
+ exitCode: process.exitCode ?? 0,
57
+ logs: output.logs,
58
+ errors: output.errors
59
+ };
60
+ }
61
+ const mockTool = {
62
+ id: "tool_weather123",
63
+ workspaceId: "ws_123",
64
+ name: "get_weather",
65
+ description: "Get weather for a city",
66
+ type: "http",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ city: { type: "string", description: "City name" }
71
+ },
72
+ required: ["city"]
73
+ },
74
+ configuration: {
75
+ http: { method: "GET", url: "https://wttr.in/{{city}}" }
76
+ },
77
+ isEnabled: true,
78
+ tags: ["weather", "utility"],
79
+ createdBy: "user_123",
80
+ stats: {
81
+ totalCalls: 100,
82
+ successfulCalls: 95,
83
+ failedCalls: 5,
84
+ averageLatencyMs: 250,
85
+ lastCalledAt: "2025-01-15T10:30:00Z"
86
+ },
87
+ createdAt: "2025-01-01T00:00:00Z",
88
+ updatedAt: "2025-01-15T10:30:00Z"
89
+ };
90
+ const mockExecution = {
91
+ id: "exec_abc123",
92
+ toolId: "tool_weather123",
93
+ workspaceId: "ws_123",
94
+ source: "api",
95
+ status: "completed",
96
+ input: { city: "London" },
97
+ output: { temperature: "12\xB0C", condition: "Cloudy" },
98
+ executionTimeMs: 245,
99
+ createdAt: "2025-01-15T10:30:00Z",
100
+ updatedAt: "2025-01-15T10:30:00Z"
101
+ };
102
+ const mockMcpConfig = {
103
+ serverUrl: "https://my-workspace.chanl.app/mcp",
104
+ environment: "production",
105
+ status: "active",
106
+ toolCount: 15,
107
+ resourceCount: 8,
108
+ promptCount: 5,
109
+ agentConfigs: {
110
+ claude: {
111
+ mcpServers: {
112
+ chanl: {
113
+ url: "https://my-workspace.chanl.app/mcp",
114
+ headers: { "X-API-Key": "${CHANL_MCP_API_KEY}" },
115
+ transport: "streamable-http"
116
+ }
117
+ }
118
+ },
119
+ cursor: {
120
+ mcpServers: {
121
+ chanl: {
122
+ url: "https://my-workspace.chanl.app/mcp",
123
+ headers: { "X-API-Key": "${CHANL_MCP_API_KEY}" },
124
+ transport: "streamable-http"
125
+ }
126
+ }
127
+ },
128
+ generic: {
129
+ url: "https://my-workspace.chanl.app/mcp",
130
+ headers: { "X-API-Key": "${CHANL_MCP_API_KEY}" },
131
+ transport: "streamable-http"
132
+ }
133
+ }
134
+ };
135
+ const mockMcpStatusHealthy = {
136
+ healthy: true,
137
+ message: "MCP server is running",
138
+ responseTimeMs: 45
139
+ };
140
+ const mockMcpStatusUnhealthy = {
141
+ healthy: false,
142
+ message: "Failed to connect to MCP server",
143
+ error: "Connection refused"
144
+ };
145
+ const mockToolset = {
146
+ id: "toolset_abc123",
147
+ workspaceId: "ws_123",
148
+ name: "customer_support_tools",
149
+ description: "Tools for customer support agents",
150
+ tools: ["tool_weather123", "tool_lookup456"],
151
+ toolCount: 2,
152
+ enabled: true,
153
+ isPublic: false,
154
+ version: "1.0.0",
155
+ tags: ["support", "customer-facing"],
156
+ createdBy: "user_123",
157
+ createdAt: "2025-01-01T00:00:00Z",
158
+ updatedAt: "2025-01-15T10:30:00Z"
159
+ };
160
+ describe("CLI", () => {
161
+ let mockSdk;
162
+ beforeEach(() => {
163
+ vi.clearAllMocks();
164
+ mockSdk = {
165
+ tools: {
166
+ list: vi.fn(),
167
+ get: vi.fn(),
168
+ create: vi.fn(),
169
+ update: vi.fn(),
170
+ delete: vi.fn(),
171
+ execute: vi.fn(),
172
+ test: vi.fn(),
173
+ getExecutions: vi.fn(),
174
+ enable: vi.fn(),
175
+ disable: vi.fn(),
176
+ getCategories: vi.fn()
177
+ },
178
+ toolsets: {
179
+ list: vi.fn(),
180
+ get: vi.fn(),
181
+ create: vi.fn(),
182
+ update: vi.fn(),
183
+ delete: vi.fn(),
184
+ enable: vi.fn(),
185
+ disable: vi.fn(),
186
+ manageTools: vi.fn()
187
+ },
188
+ workspace: {
189
+ getAll: vi.fn(),
190
+ getById: vi.fn()
191
+ },
192
+ mcp: {
193
+ getConfig: vi.fn(),
194
+ getStatus: vi.fn(),
195
+ listTools: vi.fn(),
196
+ callTool: vi.fn(),
197
+ getTool: vi.fn()
198
+ },
199
+ chat: {
200
+ createSession: vi.fn(),
201
+ sendMessage: vi.fn(),
202
+ getMessages: vi.fn(),
203
+ endSession: vi.fn(),
204
+ deleteSession: vi.fn(),
205
+ session: vi.fn()
206
+ },
207
+ calls: {
208
+ list: vi.fn(),
209
+ get: vi.fn(),
210
+ getTranscript: vi.fn(),
211
+ getMetrics: vi.fn(),
212
+ getAnalysis: vi.fn(),
213
+ analyze: vi.fn(),
214
+ import: vi.fn(),
215
+ delete: vi.fn(),
216
+ bulkDelete: vi.fn()
217
+ },
218
+ scenarios: {
219
+ list: vi.fn(),
220
+ get: vi.fn(),
221
+ run: vi.fn(),
222
+ runAll: vi.fn(),
223
+ getExecution: vi.fn(),
224
+ getExecutions: vi.fn(),
225
+ getSimulations: vi.fn()
226
+ },
227
+ agents: {
228
+ list: vi.fn(),
229
+ get: vi.fn(),
230
+ create: vi.fn(),
231
+ update: vi.fn(),
232
+ delete: vi.fn(),
233
+ sync: vi.fn(),
234
+ syncAll: vi.fn(),
235
+ getStats: vi.fn(),
236
+ importFromPlatform: vi.fn(),
237
+ publish: vi.fn(),
238
+ pushMcp: vi.fn()
239
+ },
240
+ prompts: {
241
+ list: vi.fn(),
242
+ get: vi.fn(),
243
+ create: vi.fn(),
244
+ update: vi.fn(),
245
+ delete: vi.fn()
246
+ }
247
+ };
248
+ ChanlSDK.mockImplementation(() => mockSdk);
249
+ configStore.getApiKey.mockReturnValue("test-api-key");
250
+ configStore.getWorkspaceId.mockReturnValue("ws_123");
251
+ configStore.isAuthenticated.mockReturnValue(true);
252
+ });
253
+ afterEach(() => {
254
+ process.exitCode = void 0;
255
+ });
256
+ describe("--help", () => {
257
+ it("should have all expected commands in program", () => {
258
+ const program = createProgram();
259
+ const commandNames = program.commands.map((c) => c.name());
260
+ expect(commandNames).toContain("login");
261
+ expect(commandNames).toContain("logout");
262
+ expect(commandNames).toContain("auth");
263
+ expect(commandNames).toContain("config");
264
+ expect(commandNames).toContain("tools");
265
+ });
266
+ });
267
+ describe("--version", () => {
268
+ it("should have correct version in program", () => {
269
+ const program = createProgram();
270
+ expect(program.version()).toBe("1.0.0");
271
+ });
272
+ });
273
+ describe("tools list", () => {
274
+ it("should list tools with table output", async () => {
275
+ mockSdk.tools.list.mockResolvedValue({
276
+ success: true,
277
+ data: {
278
+ tools: [mockTool],
279
+ total: 1,
280
+ pagination: {
281
+ page: 1,
282
+ limit: 20,
283
+ totalPages: 1,
284
+ hasNext: false,
285
+ hasPrev: false
286
+ }
287
+ },
288
+ message: "Success",
289
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
290
+ });
291
+ const { logs, exitCode } = await runCli(["tools", "list"]);
292
+ expect(exitCode).toBe(0);
293
+ expect(mockSdk.tools.list).toHaveBeenCalledWith(expect.any(Object));
294
+ const output = logs.join("\n");
295
+ expect(output).toContain("get_weather");
296
+ expect(output).toContain("http");
297
+ expect(output).toContain("Total: 1 tools");
298
+ });
299
+ it("should list tools with JSON output", async () => {
300
+ mockSdk.tools.list.mockResolvedValue({
301
+ success: true,
302
+ data: {
303
+ tools: [mockTool],
304
+ total: 1,
305
+ pagination: {
306
+ page: 1,
307
+ limit: 20,
308
+ totalPages: 1,
309
+ hasNext: false,
310
+ hasPrev: false
311
+ }
312
+ },
313
+ message: "Success",
314
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
315
+ });
316
+ const { logs, exitCode } = await runCli(["--json", "tools", "list"]);
317
+ expect(exitCode).toBe(0);
318
+ const output = logs.join("\n");
319
+ const parsed = JSON.parse(output);
320
+ expect(parsed.tools).toHaveLength(1);
321
+ expect(parsed.tools[0].name).toBe("get_weather");
322
+ });
323
+ it("should filter by type", async () => {
324
+ mockSdk.tools.list.mockResolvedValue({
325
+ success: true,
326
+ data: {
327
+ tools: [mockTool],
328
+ total: 1,
329
+ pagination: { page: 1, limit: 20, totalPages: 1, hasNext: false, hasPrev: false }
330
+ },
331
+ message: "Success",
332
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
333
+ });
334
+ await runCli(["tools", "list", "--type", "http"]);
335
+ expect(mockSdk.tools.list).toHaveBeenCalledWith(
336
+ expect.objectContaining({ type: "http" })
337
+ );
338
+ });
339
+ it("should filter by enabled status", async () => {
340
+ mockSdk.tools.list.mockResolvedValue({
341
+ success: true,
342
+ data: {
343
+ tools: [mockTool],
344
+ total: 1,
345
+ pagination: { page: 1, limit: 20, totalPages: 1, hasNext: false, hasPrev: false }
346
+ },
347
+ message: "Success",
348
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
349
+ });
350
+ await runCli(["tools", "list", "--enabled"]);
351
+ expect(mockSdk.tools.list).toHaveBeenCalledWith(
352
+ expect.objectContaining({ isEnabled: true })
353
+ );
354
+ });
355
+ });
356
+ describe("tools get", () => {
357
+ it("should get tool by ID", async () => {
358
+ mockSdk.tools.get.mockResolvedValue({
359
+ success: true,
360
+ data: { tool: mockTool },
361
+ message: "Success",
362
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
363
+ });
364
+ const { logs, exitCode } = await runCli(["tools", "get", "tool_weather123"]);
365
+ expect(exitCode).toBe(0);
366
+ expect(mockSdk.tools.get).toHaveBeenCalledWith("tool_weather123");
367
+ const output = logs.join("\n");
368
+ expect(output).toContain("get_weather");
369
+ expect(output).toContain("http");
370
+ });
371
+ it("should output JSON when --json flag is used", async () => {
372
+ mockSdk.tools.get.mockResolvedValue({
373
+ success: true,
374
+ data: { tool: mockTool },
375
+ message: "Success",
376
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
377
+ });
378
+ const { logs, exitCode } = await runCli(["--json", "tools", "get", "tool_weather123"]);
379
+ expect(exitCode).toBe(0);
380
+ const output = logs.join("\n");
381
+ const parsed = JSON.parse(output);
382
+ expect(parsed.name).toBe("get_weather");
383
+ });
384
+ });
385
+ describe("tools execute", () => {
386
+ it("should execute tool with input", async () => {
387
+ mockSdk.tools.execute.mockResolvedValue({
388
+ success: true,
389
+ data: {
390
+ success: true,
391
+ data: { temperature: "12\xB0C", condition: "Cloudy" },
392
+ latencyMs: 245,
393
+ traceId: "trace_abc123",
394
+ executionId: "exec_abc123"
395
+ },
396
+ message: "Success",
397
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
398
+ });
399
+ const { logs, exitCode } = await runCli([
400
+ "tools",
401
+ "execute",
402
+ "tool_weather123",
403
+ "--input",
404
+ '{"city":"London"}'
405
+ ]);
406
+ expect(exitCode).toBe(0);
407
+ expect(mockSdk.tools.execute).toHaveBeenCalledWith("tool_weather123", { city: "London" });
408
+ const output = logs.join("\n");
409
+ expect(output).toContain("Success");
410
+ expect(output).toContain("245ms");
411
+ });
412
+ it("should handle invalid JSON input", async () => {
413
+ const { exitCode, errors } = await runCli([
414
+ "tools",
415
+ "execute",
416
+ "tool_weather123",
417
+ "--input",
418
+ "not-valid-json"
419
+ ]);
420
+ expect(exitCode).toBe(1);
421
+ expect(errors.some((e) => e.includes("Invalid JSON"))).toBe(true);
422
+ });
423
+ });
424
+ describe("tools delete", () => {
425
+ it("should delete tool with --yes flag", async () => {
426
+ mockSdk.tools.delete.mockResolvedValue({
427
+ success: true,
428
+ data: { deleted: true },
429
+ message: "Success",
430
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
431
+ });
432
+ const { logs, exitCode } = await runCli(["tools", "delete", "tool_weather123", "--yes"]);
433
+ expect(exitCode).toBe(0);
434
+ expect(mockSdk.tools.delete).toHaveBeenCalledWith("tool_weather123");
435
+ const output = logs.join("\n");
436
+ expect(output).toContain("Deleted");
437
+ });
438
+ });
439
+ describe("tools executions", () => {
440
+ it("should list executions for a tool", async () => {
441
+ mockSdk.tools.getExecutions.mockResolvedValue({
442
+ success: true,
443
+ data: {
444
+ data: [mockExecution],
445
+ total: 1,
446
+ page: 1,
447
+ limit: 10,
448
+ pagination: {
449
+ page: 1,
450
+ limit: 10,
451
+ totalPages: 1,
452
+ hasNext: false,
453
+ hasPrev: false
454
+ }
455
+ },
456
+ message: "Success",
457
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
458
+ });
459
+ const { logs, exitCode } = await runCli(["tools", "executions", "tool_weather123"]);
460
+ expect(exitCode).toBe(0);
461
+ expect(mockSdk.tools.getExecutions).toHaveBeenCalledWith("tool_weather123", expect.any(Object));
462
+ const output = logs.join("\n");
463
+ expect(output).toContain("exec_abc123");
464
+ expect(output).toContain("completed");
465
+ });
466
+ });
467
+ describe("auth status", () => {
468
+ it("should show authenticated status", async () => {
469
+ configStore.getApiKey.mockReturnValue("chk_test1234567890");
470
+ configStore.getAuthMethod.mockReturnValue("api-key");
471
+ configStore.getWorkspaceId.mockReturnValue("ws_abc123");
472
+ const { logs, exitCode } = await runCli(["auth", "status"]);
473
+ expect(exitCode).toBe(0);
474
+ const output = logs.join("\n");
475
+ expect(output).toContain("Authenticated");
476
+ });
477
+ it("should show not authenticated when no API key", async () => {
478
+ configStore.getApiKey.mockReturnValue(null);
479
+ configStore.getAuthMethod.mockReturnValue(null);
480
+ const { logs } = await runCli(["auth", "status"]);
481
+ const output = logs.join("\n");
482
+ expect(output).toContain("Not authenticated");
483
+ });
484
+ it("should output JSON when --json flag is used", async () => {
485
+ configStore.getApiKey.mockReturnValue("chk_test1234567890");
486
+ configStore.getAuthMethod.mockReturnValue("api-key");
487
+ configStore.getWorkspaceId.mockReturnValue("ws_abc123");
488
+ const { logs } = await runCli(["--json", "auth", "status"]);
489
+ const output = logs.join("\n");
490
+ const parsed = JSON.parse(output);
491
+ expect(parsed.authenticated).toBe(true);
492
+ });
493
+ });
494
+ describe("logout", () => {
495
+ it("should logout successfully", async () => {
496
+ configStore.isAuthenticated.mockReturnValue(true);
497
+ const { logs, exitCode } = await runCli(["logout"]);
498
+ expect(exitCode).toBe(0);
499
+ expect(configStore.clearAuth).toHaveBeenCalled();
500
+ const output = logs.join("\n");
501
+ expect(output).toContain("Logged out");
502
+ });
503
+ it("should handle already logged out", async () => {
504
+ configStore.isAuthenticated.mockReturnValue(false);
505
+ const { logs } = await runCli(["logout"]);
506
+ const output = logs.join("\n");
507
+ expect(output).toContain("Already logged out");
508
+ });
509
+ });
510
+ describe("error handling", () => {
511
+ it("should show error when not authenticated for tools commands", async () => {
512
+ configStore.getApiKey.mockReturnValue(null);
513
+ const { exitCode, errors } = await runCli(["tools", "list"]);
514
+ expect(exitCode).toBe(1);
515
+ expect(errors.some((e) => e.includes("Not authenticated"))).toBe(true);
516
+ });
517
+ it("should handle API errors gracefully", async () => {
518
+ mockSdk.tools.list.mockRejectedValue(new Error("Network error"));
519
+ const { exitCode, errors } = await runCli(["tools", "list"]);
520
+ expect(exitCode).toBe(1);
521
+ expect(errors.some((e) => e.includes("Network error"))).toBe(true);
522
+ });
523
+ it("should handle unsuccessful API responses", async () => {
524
+ mockSdk.tools.list.mockResolvedValue({
525
+ success: false,
526
+ data: null,
527
+ message: "Unauthorized",
528
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
529
+ });
530
+ const { exitCode, errors } = await runCli(["tools", "list"]);
531
+ expect(exitCode).toBe(1);
532
+ expect(errors.some((e) => e.includes("Unauthorized") || e.includes("Failed"))).toBe(true);
533
+ });
534
+ });
535
+ describe("mcp info", () => {
536
+ it("should display MCP info with table output", async () => {
537
+ mockSdk.mcp.getConfig.mockResolvedValue({
538
+ success: true,
539
+ data: mockMcpConfig,
540
+ message: "Success",
541
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
542
+ });
543
+ const { logs, exitCode } = await runCli(["mcp", "info"]);
544
+ expect(exitCode).toBe(0);
545
+ expect(mockSdk.mcp.getConfig).toHaveBeenCalled();
546
+ const output = logs.join("\n");
547
+ expect(output).toContain("https://my-workspace.chanl.app/mcp");
548
+ expect(output).toContain("production");
549
+ expect(output).toContain("15");
550
+ });
551
+ it("should display MCP info with JSON output", async () => {
552
+ mockSdk.mcp.getConfig.mockResolvedValue({
553
+ success: true,
554
+ data: mockMcpConfig,
555
+ message: "Success",
556
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
557
+ });
558
+ const { logs, exitCode } = await runCli(["--json", "mcp", "info"]);
559
+ expect(exitCode).toBe(0);
560
+ const output = logs.join("\n");
561
+ const parsed = JSON.parse(output);
562
+ expect(parsed.serverUrl).toBe("https://my-workspace.chanl.app/mcp");
563
+ expect(parsed.environment).toBe("production");
564
+ expect(parsed.toolCount).toBe(15);
565
+ });
566
+ it("should handle API errors", async () => {
567
+ mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
568
+ const { exitCode, errors } = await runCli(["mcp", "info"]);
569
+ expect(exitCode).toBe(1);
570
+ expect(errors.some((e) => e.includes("Network error"))).toBe(true);
571
+ });
572
+ });
573
+ describe("mcp status", () => {
574
+ it("should show healthy status", async () => {
575
+ mockSdk.mcp.getStatus.mockResolvedValue({
576
+ success: true,
577
+ data: mockMcpStatusHealthy,
578
+ message: "Success",
579
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
580
+ });
581
+ const { logs, exitCode } = await runCli(["mcp", "status"]);
582
+ expect(exitCode).toBe(0);
583
+ expect(mockSdk.mcp.getStatus).toHaveBeenCalled();
584
+ const output = logs.join("\n");
585
+ expect(output).toContain("healthy");
586
+ expect(output).toContain("MCP server is running");
587
+ });
588
+ it("should show unhealthy status with exit code 1", async () => {
589
+ mockSdk.mcp.getStatus.mockResolvedValue({
590
+ success: true,
591
+ data: mockMcpStatusUnhealthy,
592
+ message: "Success",
593
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
594
+ });
595
+ const { errors, exitCode } = await runCli(["mcp", "status"]);
596
+ expect(exitCode).toBe(1);
597
+ const output = errors.join("\n");
598
+ expect(output).toContain("unhealthy");
599
+ });
600
+ it("should output JSON when --json flag is used", async () => {
601
+ mockSdk.mcp.getStatus.mockResolvedValue({
602
+ success: true,
603
+ data: mockMcpStatusHealthy,
604
+ message: "Success",
605
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
606
+ });
607
+ const { logs, exitCode } = await runCli(["--json", "mcp", "status"]);
608
+ expect(exitCode).toBe(0);
609
+ const output = logs.join("\n");
610
+ const parsed = JSON.parse(output);
611
+ expect(parsed.healthy).toBe(true);
612
+ expect(parsed.message).toBe("MCP server is running");
613
+ });
614
+ });
615
+ describe("mcp config", () => {
616
+ it("should output generic config by default", async () => {
617
+ mockSdk.mcp.getConfig.mockResolvedValue({
618
+ success: true,
619
+ data: mockMcpConfig,
620
+ message: "Success",
621
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
622
+ });
623
+ const { logs, exitCode } = await runCli(["--json", "mcp", "config"]);
624
+ expect(exitCode).toBe(0);
625
+ const output = logs.join("\n");
626
+ const parsed = JSON.parse(output);
627
+ expect(parsed.url).toBe("https://my-workspace.chanl.app/mcp");
628
+ expect(parsed.transport).toBe("streamable-http");
629
+ });
630
+ it("should output claude config with --format claude", async () => {
631
+ mockSdk.mcp.getConfig.mockResolvedValue({
632
+ success: true,
633
+ data: mockMcpConfig,
634
+ message: "Success",
635
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
636
+ });
637
+ const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "claude"]);
638
+ expect(exitCode).toBe(0);
639
+ const output = logs.join("\n");
640
+ const parsed = JSON.parse(output);
641
+ expect(parsed.mcpServers).toBeDefined();
642
+ expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
643
+ });
644
+ it("should output cursor config with --format cursor", async () => {
645
+ mockSdk.mcp.getConfig.mockResolvedValue({
646
+ success: true,
647
+ data: mockMcpConfig,
648
+ message: "Success",
649
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
650
+ });
651
+ const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "cursor"]);
652
+ expect(exitCode).toBe(0);
653
+ const output = logs.join("\n");
654
+ const parsed = JSON.parse(output);
655
+ expect(parsed.mcpServers).toBeDefined();
656
+ expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
657
+ });
658
+ it("should handle API errors", async () => {
659
+ mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
660
+ const { exitCode, errors } = await runCli(["mcp", "config"]);
661
+ expect(exitCode).toBe(1);
662
+ expect(errors.some((e) => e.includes("Network error"))).toBe(true);
663
+ });
664
+ });
665
+ describe("mcp command registration", () => {
666
+ it("should have mcp command registered", () => {
667
+ const program = createProgram();
668
+ const commandNames = program.commands.map((c) => c.name());
669
+ expect(commandNames).toContain("mcp");
670
+ });
671
+ });
672
+ describe("mcp tools", () => {
673
+ const mockMcpTools = [
674
+ {
675
+ name: "get_weather",
676
+ description: "Get current weather for a location",
677
+ inputSchema: {
678
+ type: "object",
679
+ properties: {
680
+ city: { type: "string", description: "City name" }
681
+ },
682
+ required: ["city"]
683
+ }
684
+ },
685
+ {
686
+ name: "search_kb",
687
+ description: "Search the knowledge base",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ query: { type: "string", description: "Search query" }
692
+ },
693
+ required: ["query"]
694
+ }
695
+ }
696
+ ];
697
+ it("should list MCP tools for an agent with table output", async () => {
698
+ mockSdk.mcp.listTools.mockResolvedValue({
699
+ success: true,
700
+ data: { tools: mockMcpTools },
701
+ message: "Success",
702
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
703
+ });
704
+ const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
705
+ expect(exitCode).toBe(0);
706
+ expect(mockSdk.mcp.listTools).toHaveBeenCalledWith("agent_123");
707
+ const output = logs.join("\n");
708
+ expect(output).toContain("get_weather");
709
+ expect(output).toContain("search_kb");
710
+ });
711
+ it("should list MCP tools with JSON output", async () => {
712
+ mockSdk.mcp.listTools.mockResolvedValue({
713
+ success: true,
714
+ data: { tools: mockMcpTools },
715
+ message: "Success",
716
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
717
+ });
718
+ const { logs, exitCode } = await runCli(["--json", "mcp", "tools", "agent_123"]);
719
+ expect(exitCode).toBe(0);
720
+ const output = logs.join("\n");
721
+ const parsed = JSON.parse(output);
722
+ expect(parsed.tools).toHaveLength(2);
723
+ expect(parsed.tools[0].name).toBe("get_weather");
724
+ });
725
+ it("should show message when agent has no tools", async () => {
726
+ mockSdk.mcp.listTools.mockResolvedValue({
727
+ success: true,
728
+ data: { tools: [] },
729
+ message: "Success",
730
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
731
+ });
732
+ const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
733
+ expect(exitCode).toBe(0);
734
+ const output = logs.join("\n");
735
+ expect(output).toContain("No MCP tools");
736
+ });
737
+ it("should handle API errors", async () => {
738
+ mockSdk.mcp.listTools.mockRejectedValue(new Error("Agent not found"));
739
+ const { exitCode, errors } = await runCli(["mcp", "tools", "agent_123"]);
740
+ expect(exitCode).toBe(1);
741
+ expect(errors.some((e) => e.includes("Agent not found"))).toBe(true);
742
+ });
743
+ });
744
+ describe("mcp call", () => {
745
+ it("should call a tool and display result", async () => {
746
+ mockSdk.mcp.callTool.mockResolvedValue({
747
+ success: true,
748
+ data: {
749
+ content: [{ type: "text", text: "Temperature in London: 15C" }],
750
+ isError: false
751
+ },
752
+ message: "Success",
753
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
754
+ });
755
+ const { logs, exitCode } = await runCli([
756
+ "mcp",
757
+ "call",
758
+ "agent_123",
759
+ "get_weather",
760
+ '{"city":"London"}'
761
+ ]);
762
+ expect(exitCode).toBe(0);
763
+ expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
764
+ "agent_123",
765
+ "get_weather",
766
+ { city: "London" }
767
+ );
768
+ const output = logs.join("\n");
769
+ expect(output).toContain("Temperature in London: 15C");
770
+ });
771
+ it("should call a tool without arguments", async () => {
772
+ mockSdk.mcp.callTool.mockResolvedValue({
773
+ success: true,
774
+ data: {
775
+ content: [{ type: "text", text: "pong" }],
776
+ isError: false
777
+ },
778
+ message: "Success",
779
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
780
+ });
781
+ const { logs, exitCode } = await runCli(["mcp", "call", "agent_123", "ping"]);
782
+ expect(exitCode).toBe(0);
783
+ expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
784
+ "agent_123",
785
+ "ping",
786
+ void 0
787
+ );
788
+ const output = logs.join("\n");
789
+ expect(output).toContain("pong");
790
+ });
791
+ it("should display error result from tool", async () => {
792
+ mockSdk.mcp.callTool.mockResolvedValue({
793
+ success: true,
794
+ data: {
795
+ content: [{ type: "text", text: "Error: city not found" }],
796
+ isError: true
797
+ },
798
+ message: "Success",
799
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
800
+ });
801
+ const { errors, exitCode } = await runCli([
802
+ "mcp",
803
+ "call",
804
+ "agent_123",
805
+ "get_weather",
806
+ '{"city":"xyz"}'
807
+ ]);
808
+ expect(exitCode).toBe(1);
809
+ const output = errors.join("\n");
810
+ expect(output).toContain("Error");
811
+ });
812
+ it("should output JSON when --json flag is used", async () => {
813
+ mockSdk.mcp.callTool.mockResolvedValue({
814
+ success: true,
815
+ data: {
816
+ content: [{ type: "text", text: "result text" }],
817
+ isError: false
818
+ },
819
+ message: "Success",
820
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
821
+ });
822
+ const { logs, exitCode } = await runCli([
823
+ "--json",
824
+ "mcp",
825
+ "call",
826
+ "agent_123",
827
+ "get_weather",
828
+ '{"city":"London"}'
829
+ ]);
830
+ expect(exitCode).toBe(0);
831
+ const output = logs.join("\n");
832
+ const parsed = JSON.parse(output);
833
+ expect(parsed.content).toBeDefined();
834
+ expect(parsed.content[0].text).toBe("result text");
835
+ });
836
+ it("should handle invalid JSON arguments gracefully", async () => {
837
+ const { exitCode, errors } = await runCli([
838
+ "mcp",
839
+ "call",
840
+ "agent_123",
841
+ "get_weather",
842
+ "not-json"
843
+ ]);
844
+ expect(exitCode).toBe(1);
845
+ expect(errors.some((e) => e.includes("Invalid JSON"))).toBe(true);
846
+ });
847
+ it("should handle API errors", async () => {
848
+ mockSdk.mcp.callTool.mockRejectedValue(new Error("Tool not found"));
849
+ const { exitCode, errors } = await runCli([
850
+ "mcp",
851
+ "call",
852
+ "agent_123",
853
+ "nonexistent",
854
+ "{}"
855
+ ]);
856
+ expect(exitCode).toBe(1);
857
+ expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
858
+ });
859
+ });
860
+ describe("mcp inspect", () => {
861
+ const mockToolDetail = {
862
+ name: "get_weather",
863
+ description: "Get current weather for a location",
864
+ inputSchema: {
865
+ type: "object",
866
+ properties: {
867
+ city: { type: "string", description: "City name" },
868
+ units: { type: "string", enum: ["celsius", "fahrenheit"] }
869
+ },
870
+ required: ["city"]
871
+ }
872
+ };
873
+ it("should display tool details with table output", async () => {
874
+ mockSdk.mcp.getTool.mockResolvedValue({
875
+ success: true,
876
+ data: { tool: mockToolDetail },
877
+ message: "Success",
878
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
879
+ });
880
+ const { logs, exitCode } = await runCli(["mcp", "inspect", "agent_123", "get_weather"]);
881
+ expect(exitCode).toBe(0);
882
+ expect(mockSdk.mcp.getTool).toHaveBeenCalledWith("agent_123", "get_weather");
883
+ const output = logs.join("\n");
884
+ expect(output).toContain("get_weather");
885
+ expect(output).toContain("Get current weather");
886
+ expect(output).toContain("city");
887
+ });
888
+ it("should display tool details with JSON output", async () => {
889
+ mockSdk.mcp.getTool.mockResolvedValue({
890
+ success: true,
891
+ data: { tool: mockToolDetail },
892
+ message: "Success",
893
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
894
+ });
895
+ const { logs, exitCode } = await runCli(["--json", "mcp", "inspect", "agent_123", "get_weather"]);
896
+ expect(exitCode).toBe(0);
897
+ const output = logs.join("\n");
898
+ const parsed = JSON.parse(output);
899
+ expect(parsed.name).toBe("get_weather");
900
+ expect(parsed.inputSchema).toBeDefined();
901
+ });
902
+ it("should handle tool not found", async () => {
903
+ mockSdk.mcp.getTool.mockRejectedValue(new Error("Tool not found"));
904
+ const { exitCode, errors } = await runCli(["mcp", "inspect", "agent_123", "nonexistent"]);
905
+ expect(exitCode).toBe(1);
906
+ expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
907
+ });
908
+ });
909
+ describe("mcp install", () => {
910
+ it("should generate config snippet for default (generic) format", async () => {
911
+ const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
912
+ expect(exitCode).toBe(0);
913
+ const output = logs.join("\n");
914
+ const parsed = JSON.parse(output);
915
+ expect(parsed.mcpServers).toBeDefined();
916
+ expect(parsed.mcpServers.chanl).toBeDefined();
917
+ expect(parsed.mcpServers.chanl.transport).toBe("streamable-http");
918
+ });
919
+ it("should include API key from config", async () => {
920
+ const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
921
+ expect(exitCode).toBe(0);
922
+ const output = logs.join("\n");
923
+ const parsed = JSON.parse(output);
924
+ expect(parsed.mcpServers.chanl.headers["X-API-Key"]).toBe("test-api-key");
925
+ });
926
+ it("should generate config for claude format", async () => {
927
+ const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "claude"]);
928
+ expect(exitCode).toBe(0);
929
+ const output = logs.join("\n");
930
+ const parsed = JSON.parse(output);
931
+ expect(parsed.mcpServers).toBeDefined();
932
+ expect(parsed.mcpServers.chanl).toBeDefined();
933
+ });
934
+ it("should generate config for cursor format", async () => {
935
+ const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "cursor"]);
936
+ expect(exitCode).toBe(0);
937
+ const output = logs.join("\n");
938
+ const parsed = JSON.parse(output);
939
+ expect(parsed.mcpServers).toBeDefined();
940
+ expect(parsed.mcpServers.chanl).toBeDefined();
941
+ });
942
+ it("should generate config for vscode format", async () => {
943
+ const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "vscode"]);
944
+ expect(exitCode).toBe(0);
945
+ const output = logs.join("\n");
946
+ const parsed = JSON.parse(output);
947
+ expect(parsed.mcpServers).toBeDefined();
948
+ expect(parsed.mcpServers.chanl).toBeDefined();
949
+ });
950
+ it("should error when not authenticated", async () => {
951
+ configStore.getApiKey.mockReturnValue(null);
952
+ const { exitCode, errors } = await runCli(["mcp", "install"]);
953
+ expect(exitCode).toBe(1);
954
+ expect(errors.some((e) => e.includes("Not authenticated") || e.includes("login"))).toBe(true);
955
+ });
956
+ });
957
+ describe("toolsets command registration", () => {
958
+ it("should have toolsets command registered", () => {
959
+ const program = createProgram();
960
+ const commandNames = program.commands.map((c) => c.name());
961
+ expect(commandNames).toContain("toolsets");
962
+ });
963
+ });
964
+ describe("toolsets list", () => {
965
+ it("should list toolsets with table output", async () => {
966
+ mockSdk.toolsets.list.mockResolvedValue({
967
+ success: true,
968
+ data: {
969
+ data: [mockToolset],
970
+ total: 1,
971
+ pagination: {
972
+ page: 1,
973
+ limit: 20,
974
+ totalPages: 1,
975
+ hasNext: false,
976
+ hasPrev: false
977
+ }
978
+ },
979
+ message: "Success",
980
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
981
+ });
982
+ const { logs, exitCode } = await runCli(["toolsets", "list"]);
983
+ expect(exitCode).toBe(0);
984
+ expect(mockSdk.toolsets.list).toHaveBeenCalledWith(expect.any(Object));
985
+ const output = logs.join("\n");
986
+ expect(output).toContain("customer_support_tools");
987
+ expect(output).toContain("1.0.0");
988
+ expect(output).toContain("Total: 1 toolsets");
989
+ });
990
+ it("should list toolsets with JSON output", async () => {
991
+ mockSdk.toolsets.list.mockResolvedValue({
992
+ success: true,
993
+ data: {
994
+ data: [mockToolset],
995
+ total: 1,
996
+ pagination: {
997
+ page: 1,
998
+ limit: 20,
999
+ totalPages: 1,
1000
+ hasNext: false,
1001
+ hasPrev: false
1002
+ }
1003
+ },
1004
+ message: "Success",
1005
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1006
+ });
1007
+ const { logs, exitCode } = await runCli(["--json", "toolsets", "list"]);
1008
+ expect(exitCode).toBe(0);
1009
+ const output = logs.join("\n");
1010
+ const parsed = JSON.parse(output);
1011
+ expect(parsed.toolsets).toHaveLength(1);
1012
+ expect(parsed.toolsets[0].name).toBe("customer_support_tools");
1013
+ });
1014
+ it("should filter by enabled status", async () => {
1015
+ mockSdk.toolsets.list.mockResolvedValue({
1016
+ success: true,
1017
+ data: {
1018
+ data: [mockToolset],
1019
+ total: 1,
1020
+ pagination: { page: 1, limit: 20, totalPages: 1, hasNext: false, hasPrev: false }
1021
+ },
1022
+ message: "Success",
1023
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1024
+ });
1025
+ await runCli(["toolsets", "list", "--enabled"]);
1026
+ expect(mockSdk.toolsets.list).toHaveBeenCalledWith(
1027
+ expect.objectContaining({ enabled: true })
1028
+ );
1029
+ });
1030
+ });
1031
+ describe("toolsets get", () => {
1032
+ it("should get toolset by ID", async () => {
1033
+ mockSdk.toolsets.get.mockResolvedValue({
1034
+ success: true,
1035
+ data: mockToolset,
1036
+ message: "Success",
1037
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1038
+ });
1039
+ const { logs, exitCode } = await runCli(["toolsets", "get", "toolset_abc123"]);
1040
+ expect(exitCode).toBe(0);
1041
+ expect(mockSdk.toolsets.get).toHaveBeenCalledWith("toolset_abc123");
1042
+ const output = logs.join("\n");
1043
+ expect(output).toContain("customer_support_tools");
1044
+ expect(output).toContain("1.0.0");
1045
+ });
1046
+ it("should output JSON when --json flag is used", async () => {
1047
+ mockSdk.toolsets.get.mockResolvedValue({
1048
+ success: true,
1049
+ data: mockToolset,
1050
+ message: "Success",
1051
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1052
+ });
1053
+ const { logs, exitCode } = await runCli(["--json", "toolsets", "get", "toolset_abc123"]);
1054
+ expect(exitCode).toBe(0);
1055
+ const output = logs.join("\n");
1056
+ const parsed = JSON.parse(output);
1057
+ expect(parsed.name).toBe("customer_support_tools");
1058
+ });
1059
+ });
1060
+ describe("toolsets enable", () => {
1061
+ it("should enable a toolset", async () => {
1062
+ mockSdk.toolsets.enable.mockResolvedValue({
1063
+ success: true,
1064
+ data: { ...mockToolset, enabled: true },
1065
+ message: "Success",
1066
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1067
+ });
1068
+ const { logs, exitCode } = await runCli(["toolsets", "enable", "toolset_abc123"]);
1069
+ expect(exitCode).toBe(0);
1070
+ expect(mockSdk.toolsets.enable).toHaveBeenCalledWith("toolset_abc123");
1071
+ const output = logs.join("\n");
1072
+ expect(output).toContain("Enabled");
1073
+ expect(output).toContain("customer_support_tools");
1074
+ });
1075
+ });
1076
+ describe("toolsets disable", () => {
1077
+ it("should disable a toolset", async () => {
1078
+ mockSdk.toolsets.disable.mockResolvedValue({
1079
+ success: true,
1080
+ data: { ...mockToolset, enabled: false },
1081
+ message: "Success",
1082
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1083
+ });
1084
+ const { logs, exitCode } = await runCli(["toolsets", "disable", "toolset_abc123"]);
1085
+ expect(exitCode).toBe(0);
1086
+ expect(mockSdk.toolsets.disable).toHaveBeenCalledWith("toolset_abc123");
1087
+ const output = logs.join("\n");
1088
+ expect(output).toContain("Disabled");
1089
+ expect(output).toContain("customer_support_tools");
1090
+ });
1091
+ });
1092
+ describe("toolsets delete", () => {
1093
+ it("should delete toolset with --yes flag", async () => {
1094
+ mockSdk.toolsets.delete.mockResolvedValue({
1095
+ success: true,
1096
+ data: { deleted: true },
1097
+ message: "Success",
1098
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1099
+ });
1100
+ const { logs, exitCode } = await runCli(["toolsets", "delete", "toolset_abc123", "--yes"]);
1101
+ expect(exitCode).toBe(0);
1102
+ expect(mockSdk.toolsets.delete).toHaveBeenCalledWith("toolset_abc123");
1103
+ const output = logs.join("\n");
1104
+ expect(output).toContain("Deleted");
1105
+ });
1106
+ });
1107
+ describe("tools enable/disable", () => {
1108
+ it("should enable a tool", async () => {
1109
+ mockSdk.tools.enable.mockResolvedValue({
1110
+ success: true,
1111
+ data: { tool: { ...mockTool, isEnabled: true } },
1112
+ message: "Success",
1113
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1114
+ });
1115
+ const { logs, exitCode } = await runCli(["tools", "enable", "tool_weather123"]);
1116
+ expect(exitCode).toBe(0);
1117
+ expect(mockSdk.tools.enable).toHaveBeenCalledWith("tool_weather123");
1118
+ const output = logs.join("\n");
1119
+ expect(output).toContain("Enabled");
1120
+ expect(output).toContain("get_weather");
1121
+ });
1122
+ it("should disable a tool", async () => {
1123
+ mockSdk.tools.disable.mockResolvedValue({
1124
+ success: true,
1125
+ data: { tool: { ...mockTool, isEnabled: false } },
1126
+ message: "Success",
1127
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1128
+ });
1129
+ const { logs, exitCode } = await runCli(["tools", "disable", "tool_weather123"]);
1130
+ expect(exitCode).toBe(0);
1131
+ expect(mockSdk.tools.disable).toHaveBeenCalledWith("tool_weather123");
1132
+ const output = logs.join("\n");
1133
+ expect(output).toContain("Disabled");
1134
+ expect(output).toContain("get_weather");
1135
+ });
1136
+ });
1137
+ describe("chat command registration", () => {
1138
+ it("should have chat command registered", () => {
1139
+ const program = createProgram();
1140
+ const commandNames = program.commands.map((c) => c.name());
1141
+ expect(commandNames).toContain("chat");
1142
+ });
1143
+ it("should have the correct command name and description", () => {
1144
+ const program = createProgram();
1145
+ const chatCmd = program.commands.find((c) => c.name() === "chat");
1146
+ expect(chatCmd).toBeDefined();
1147
+ expect(chatCmd.description()).toContain("chat");
1148
+ });
1149
+ });
1150
+ describe("chat single message", () => {
1151
+ it("should send a single message and display response", async () => {
1152
+ const mockSend = vi.fn().mockResolvedValue({
1153
+ message: "Hello! How can I help you?",
1154
+ sessionId: "sess_123",
1155
+ latencyMs: 120
1156
+ });
1157
+ const mockEnd = vi.fn().mockResolvedValue(void 0);
1158
+ mockSdk.chat.session.mockResolvedValue({
1159
+ sessionId: "sess_123",
1160
+ agentName: "Test Agent",
1161
+ send: mockSend,
1162
+ end: mockEnd
1163
+ });
1164
+ const { logs, exitCode } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
1165
+ expect(exitCode).toBe(0);
1166
+ expect(mockSdk.chat.session).toHaveBeenCalledWith("agent_abc");
1167
+ expect(mockSend).toHaveBeenCalledWith("Hello");
1168
+ expect(mockEnd).toHaveBeenCalled();
1169
+ const output = logs.join("\n");
1170
+ expect(output).toContain("Hello");
1171
+ expect(output).toContain("How can I help you?");
1172
+ });
1173
+ it("should output JSON when --json flag is used", async () => {
1174
+ const mockSend = vi.fn().mockResolvedValue({
1175
+ message: "Hi there!",
1176
+ sessionId: "sess_123",
1177
+ latencyMs: 95
1178
+ });
1179
+ const mockEnd = vi.fn().mockResolvedValue(void 0);
1180
+ mockSdk.chat.session.mockResolvedValue({
1181
+ sessionId: "sess_123",
1182
+ agentName: "Test Agent",
1183
+ send: mockSend,
1184
+ end: mockEnd
1185
+ });
1186
+ const { logs, exitCode } = await runCli(["--json", "chat", "agent_abc", "--message", "Hi"]);
1187
+ expect(exitCode).toBe(0);
1188
+ const output = logs.join("\n");
1189
+ const parsed = JSON.parse(output);
1190
+ expect(parsed.sessionId).toBe("sess_123");
1191
+ expect(parsed.response).toBe("Hi there!");
1192
+ expect(parsed.latencyMs).toBe(95);
1193
+ });
1194
+ it("should handle session creation failure", async () => {
1195
+ mockSdk.chat.session.mockRejectedValue(new Error("Agent not found"));
1196
+ const { exitCode, errors } = await runCli(["chat", "bad_agent", "--message", "Hello"]);
1197
+ expect(exitCode).toBe(1);
1198
+ expect(errors.some((e) => e.includes("Agent not found") || e.includes("session"))).toBe(true);
1199
+ });
1200
+ it("should handle send message failure", async () => {
1201
+ const mockSend = vi.fn().mockRejectedValue(new Error("No response from agent"));
1202
+ const mockEnd = vi.fn().mockResolvedValue(void 0);
1203
+ mockSdk.chat.session.mockResolvedValue({
1204
+ sessionId: "sess_123",
1205
+ agentName: "Test Agent",
1206
+ send: mockSend,
1207
+ end: mockEnd
1208
+ });
1209
+ const { exitCode, errors } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
1210
+ expect(exitCode).toBe(1);
1211
+ expect(errors.some((e) => e.includes("No response") || e.includes("Failed"))).toBe(true);
1212
+ });
1213
+ it("should handle not authenticated", async () => {
1214
+ configStore.getApiKey.mockReturnValue(null);
1215
+ const { exitCode, errors } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
1216
+ expect(exitCode).toBe(1);
1217
+ expect(errors.some((e) => e.includes("Not authenticated"))).toBe(true);
1218
+ });
1219
+ });
1220
+ describe("tools categories", () => {
1221
+ it("should show tool categories", async () => {
1222
+ mockSdk.tools.getCategories.mockResolvedValue({
1223
+ success: true,
1224
+ data: {
1225
+ categories: [
1226
+ { tag: "weather", count: 5, enabled: 4 },
1227
+ { tag: "utility", count: 10, enabled: 8 }
1228
+ ],
1229
+ uncategorized: { count: 3, enabled: 2 }
1230
+ },
1231
+ message: "Success",
1232
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1233
+ });
1234
+ const { logs, exitCode } = await runCli(["tools", "categories"]);
1235
+ expect(exitCode).toBe(0);
1236
+ expect(mockSdk.tools.getCategories).toHaveBeenCalled();
1237
+ const output = logs.join("\n");
1238
+ expect(output).toContain("weather");
1239
+ expect(output).toContain("utility");
1240
+ expect(output).toContain("uncategorized");
1241
+ });
1242
+ it("should output JSON when --json flag is used", async () => {
1243
+ mockSdk.tools.getCategories.mockResolvedValue({
1244
+ success: true,
1245
+ data: {
1246
+ categories: [{ tag: "weather", count: 5, enabled: 4 }],
1247
+ uncategorized: { count: 3, enabled: 2 }
1248
+ },
1249
+ message: "Success",
1250
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1251
+ });
1252
+ const { logs, exitCode } = await runCli(["--json", "tools", "categories"]);
1253
+ expect(exitCode).toBe(0);
1254
+ const output = logs.join("\n");
1255
+ const parsed = JSON.parse(output);
1256
+ expect(parsed.categories).toHaveLength(1);
1257
+ expect(parsed.categories[0].tag).toBe("weather");
1258
+ expect(parsed.uncategorized.count).toBe(3);
1259
+ });
1260
+ });
1261
+ describe("chat single message with tool calls", () => {
1262
+ const mockToolCalls = [
1263
+ {
1264
+ id: "tc-001",
1265
+ name: "get_weather",
1266
+ parameters: { city: "London" },
1267
+ result: { temperature: "12\xB0C" },
1268
+ status: "success",
1269
+ durationMs: 245,
1270
+ startedAt: "2025-01-09T10:30:00.000Z",
1271
+ completedAt: "2025-01-09T10:30:00.245Z"
1272
+ },
1273
+ {
1274
+ id: "tc-002",
1275
+ name: "send_email",
1276
+ parameters: { to: "user@test.com" },
1277
+ status: "error",
1278
+ durationMs: 1200,
1279
+ startedAt: "2025-01-09T10:30:01.000Z",
1280
+ error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
1281
+ }
1282
+ ];
1283
+ it("should display tool calls in chat response", async () => {
1284
+ const mockSend = vi.fn().mockResolvedValue({
1285
+ message: "The weather in London is 12\xB0C.",
1286
+ sessionId: "sess_tc1",
1287
+ latencyMs: 350,
1288
+ toolCalls: mockToolCalls
1289
+ });
1290
+ const mockEnd = vi.fn().mockResolvedValue(void 0);
1291
+ mockSdk.chat.session.mockResolvedValue({
1292
+ sessionId: "sess_tc1",
1293
+ agentName: "Test Agent",
1294
+ send: mockSend,
1295
+ end: mockEnd
1296
+ });
1297
+ const { logs, exitCode } = await runCli(["chat", "agent_abc", "--message", "What is the weather in London?"]);
1298
+ expect(exitCode).toBe(0);
1299
+ const output = logs.join("\n");
1300
+ expect(output).toContain("get_weather");
1301
+ expect(output).toContain("245ms");
1302
+ expect(output).toContain("send_email");
1303
+ expect(output).toContain("SMTP connection failed");
1304
+ expect(output).toContain("The weather in London is 12\xB0C.");
1305
+ });
1306
+ });
1307
+ describe("chat single message with tool calls JSON output", () => {
1308
+ const mockToolCalls = [
1309
+ {
1310
+ id: "tc-001",
1311
+ name: "get_weather",
1312
+ parameters: { city: "London" },
1313
+ result: { temperature: "12\xB0C" },
1314
+ status: "success",
1315
+ durationMs: 245,
1316
+ startedAt: "2025-01-09T10:30:00.000Z",
1317
+ completedAt: "2025-01-09T10:30:00.245Z"
1318
+ },
1319
+ {
1320
+ id: "tc-002",
1321
+ name: "send_email",
1322
+ parameters: { to: "user@test.com" },
1323
+ status: "error",
1324
+ durationMs: 1200,
1325
+ startedAt: "2025-01-09T10:30:01.000Z",
1326
+ error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
1327
+ }
1328
+ ];
1329
+ it("should include toolCalls in JSON output", async () => {
1330
+ const mockSend = vi.fn().mockResolvedValue({
1331
+ message: "The weather in London is 12\xB0C.",
1332
+ sessionId: "sess_tc2",
1333
+ latencyMs: 350,
1334
+ toolCalls: mockToolCalls
1335
+ });
1336
+ const mockEnd = vi.fn().mockResolvedValue(void 0);
1337
+ mockSdk.chat.session.mockResolvedValue({
1338
+ sessionId: "sess_tc2",
1339
+ agentName: "Test Agent",
1340
+ send: mockSend,
1341
+ end: mockEnd
1342
+ });
1343
+ const { logs, exitCode } = await runCli(["--json", "chat", "agent_abc", "--message", "What is the weather?"]);
1344
+ expect(exitCode).toBe(0);
1345
+ const output = logs.join("\n");
1346
+ const parsed = JSON.parse(output);
1347
+ expect(parsed.toolCalls).toBeDefined();
1348
+ expect(parsed.toolCalls).toHaveLength(2);
1349
+ expect(parsed.toolCalls[0].name).toBe("get_weather");
1350
+ expect(parsed.toolCalls[0].status).toBe("success");
1351
+ expect(parsed.toolCalls[1].name).toBe("send_email");
1352
+ expect(parsed.toolCalls[1].status).toBe("error");
1353
+ });
1354
+ });
1355
+ describe("calls transcript with tool calls", () => {
1356
+ const mockToolCalls = [
1357
+ {
1358
+ id: "tc-001",
1359
+ name: "get_weather",
1360
+ parameters: { city: "London" },
1361
+ result: { temperature: "12\xB0C" },
1362
+ status: "success",
1363
+ durationMs: 245,
1364
+ startedAt: "2025-01-09T10:30:00.000Z",
1365
+ completedAt: "2025-01-09T10:30:00.245Z"
1366
+ },
1367
+ {
1368
+ id: "tc-002",
1369
+ name: "send_email",
1370
+ parameters: { to: "user@test.com" },
1371
+ status: "error",
1372
+ durationMs: 1200,
1373
+ startedAt: "2025-01-09T10:30:01.000Z",
1374
+ error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
1375
+ }
1376
+ ];
1377
+ it("should display tool calls in transcript output", async () => {
1378
+ mockSdk.calls.getTranscript.mockResolvedValue({
1379
+ success: true,
1380
+ data: {
1381
+ text: "Agent: Hello! Customer: What is the weather?",
1382
+ wordCount: 8,
1383
+ speakerCount: 2,
1384
+ language: "en",
1385
+ confidence: 0.95,
1386
+ segments: [
1387
+ { speaker: "speaker_0", text: "Hello!", start: 0, end: 1.5 },
1388
+ { speaker: "speaker_1", text: "What is the weather?", start: 2, end: 4 }
1389
+ ],
1390
+ toolCalls: mockToolCalls
1391
+ },
1392
+ message: "Success",
1393
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1394
+ });
1395
+ const { logs, exitCode } = await runCli(["calls", "transcript", "call_abc123"]);
1396
+ expect(exitCode).toBe(0);
1397
+ expect(mockSdk.calls.getTranscript).toHaveBeenCalledWith("call_abc123");
1398
+ const output = logs.join("\n");
1399
+ expect(output).toContain("Tool Calls");
1400
+ expect(output).toContain("get_weather");
1401
+ expect(output).toContain("send_email");
1402
+ expect(output).toContain("245ms");
1403
+ expect(output).toContain("SMTP connection failed");
1404
+ });
1405
+ it("should include tool calls in JSON transcript output", async () => {
1406
+ mockSdk.calls.getTranscript.mockResolvedValue({
1407
+ success: true,
1408
+ data: {
1409
+ text: "Agent: Hello!",
1410
+ wordCount: 2,
1411
+ toolCalls: mockToolCalls
1412
+ },
1413
+ message: "Success",
1414
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1415
+ });
1416
+ const { logs, exitCode } = await runCli(["--json", "calls", "transcript", "call_abc123"]);
1417
+ expect(exitCode).toBe(0);
1418
+ const output = logs.join("\n");
1419
+ const parsed = JSON.parse(output);
1420
+ expect(parsed.toolCalls).toBeDefined();
1421
+ expect(parsed.toolCalls).toHaveLength(2);
1422
+ expect(parsed.toolCalls[0].name).toBe("get_weather");
1423
+ expect(parsed.toolCalls[1].name).toBe("send_email");
1424
+ });
1425
+ });
1426
+ describe("scenarios execution with tool calls", () => {
1427
+ const mockToolCalls = [
1428
+ {
1429
+ id: "tc-001",
1430
+ name: "get_weather",
1431
+ parameters: { city: "London" },
1432
+ result: { temperature: "12\xB0C" },
1433
+ status: "success",
1434
+ durationMs: 245,
1435
+ startedAt: "2025-01-09T10:30:00.000Z",
1436
+ completedAt: "2025-01-09T10:30:00.245Z"
1437
+ },
1438
+ {
1439
+ id: "tc-002",
1440
+ name: "send_email",
1441
+ parameters: { to: "user@test.com" },
1442
+ status: "error",
1443
+ durationMs: 1200,
1444
+ startedAt: "2025-01-09T10:30:01.000Z",
1445
+ error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
1446
+ }
1447
+ ];
1448
+ it("should display tool calls in execution details", async () => {
1449
+ mockSdk.scenarios.getExecution.mockResolvedValue({
1450
+ success: true,
1451
+ data: {
1452
+ execution: {
1453
+ id: "exec_tc1",
1454
+ executionId: "exec_tc1",
1455
+ scenarioId: "scen_abc123",
1456
+ status: "completed",
1457
+ overallScore: 85,
1458
+ duration: 12.5,
1459
+ stepResults: [
1460
+ {
1461
+ stepId: "step_1",
1462
+ status: "completed",
1463
+ actualResponse: "The weather is 12\xB0C in London.",
1464
+ toolCalls: mockToolCalls
1465
+ }
1466
+ ]
1467
+ }
1468
+ },
1469
+ message: "Success",
1470
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1471
+ });
1472
+ const { logs, exitCode } = await runCli(["scenarios", "execution", "exec_tc1"]);
1473
+ expect(exitCode).toBe(0);
1474
+ expect(mockSdk.scenarios.getExecution).toHaveBeenCalledWith("exec_tc1");
1475
+ const output = logs.join("\n");
1476
+ expect(output).toContain("Tool Calls");
1477
+ expect(output).toContain("get_weather");
1478
+ expect(output).toContain("send_email");
1479
+ expect(output).toContain("245ms");
1480
+ expect(output).toContain("SMTP connection failed");
1481
+ });
1482
+ it("should include tool calls in JSON execution output", async () => {
1483
+ mockSdk.scenarios.getExecution.mockResolvedValue({
1484
+ success: true,
1485
+ data: {
1486
+ execution: {
1487
+ id: "exec_tc2",
1488
+ executionId: "exec_tc2",
1489
+ scenarioId: "scen_abc123",
1490
+ status: "completed",
1491
+ overallScore: 85,
1492
+ duration: 12.5,
1493
+ stepResults: [
1494
+ {
1495
+ stepId: "step_1",
1496
+ status: "completed",
1497
+ actualResponse: "The weather is 12\xB0C in London.",
1498
+ toolCalls: mockToolCalls
1499
+ }
1500
+ ]
1501
+ }
1502
+ },
1503
+ message: "Success",
1504
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1505
+ });
1506
+ const { logs, exitCode } = await runCli(["--json", "scenarios", "execution", "exec_tc2"]);
1507
+ expect(exitCode).toBe(0);
1508
+ const output = logs.join("\n");
1509
+ const parsed = JSON.parse(output);
1510
+ expect(parsed.execution).toBeDefined();
1511
+ expect(parsed.execution.stepResults[0].toolCalls).toHaveLength(2);
1512
+ expect(parsed.execution.stepResults[0].toolCalls[0].name).toBe("get_weather");
1513
+ expect(parsed.execution.stepResults[0].toolCalls[1].name).toBe("send_email");
1514
+ });
1515
+ });
1516
+ describe("agents import", () => {
1517
+ const mockImportedAgent = {
1518
+ id: "agent_imported_123",
1519
+ name: "My VAPI Agent",
1520
+ description: "Imported from VAPI",
1521
+ platform: "vapi",
1522
+ platformAgentId: "asst_abc",
1523
+ status: "active",
1524
+ deployments: [
1525
+ {
1526
+ orchestrator: "vapi",
1527
+ externalId: "asst_abc",
1528
+ status: "synced"
1529
+ }
1530
+ ],
1531
+ createdAt: "2026-03-09T10:00:00.000Z",
1532
+ updatedAt: "2026-03-09T10:00:00.000Z"
1533
+ };
1534
+ it("should import an agent from VAPI and display results", async () => {
1535
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1536
+ success: true,
1537
+ data: { agent: mockImportedAgent },
1538
+ message: "Success",
1539
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1540
+ });
1541
+ const { logs, exitCode } = await runCli([
1542
+ "agents",
1543
+ "import",
1544
+ "--from",
1545
+ "vapi",
1546
+ "--agent-id",
1547
+ "asst_abc"
1548
+ ]);
1549
+ expect(exitCode).toBe(0);
1550
+ const output = logs.join("\n");
1551
+ expect(output).toContain("My VAPI Agent");
1552
+ expect(output).toContain("agent_imported_123");
1553
+ expect(output).toContain("vapi");
1554
+ expect(mockSdk.agents.importFromPlatform).toHaveBeenCalledWith(
1555
+ expect.objectContaining({
1556
+ platform: "vapi",
1557
+ externalAgentId: "asst_abc"
1558
+ })
1559
+ );
1560
+ });
1561
+ it("should display external ID and deployment info", async () => {
1562
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1563
+ success: true,
1564
+ data: { agent: mockImportedAgent },
1565
+ message: "Success",
1566
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1567
+ });
1568
+ const { logs, exitCode } = await runCli([
1569
+ "agents",
1570
+ "import",
1571
+ "--from",
1572
+ "vapi",
1573
+ "--agent-id",
1574
+ "asst_abc"
1575
+ ]);
1576
+ expect(exitCode).toBe(0);
1577
+ const output = logs.join("\n");
1578
+ expect(output).toContain("asst_abc");
1579
+ });
1580
+ it("should output JSON with --json flag", async () => {
1581
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1582
+ success: true,
1583
+ data: { agent: mockImportedAgent },
1584
+ message: "Success",
1585
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1586
+ });
1587
+ const { logs, exitCode } = await runCli([
1588
+ "--json",
1589
+ "agents",
1590
+ "import",
1591
+ "--from",
1592
+ "vapi",
1593
+ "--agent-id",
1594
+ "asst_abc"
1595
+ ]);
1596
+ expect(exitCode).toBe(0);
1597
+ const output = logs.join("\n");
1598
+ const parsed = JSON.parse(output);
1599
+ expect(parsed.agent).toBeDefined();
1600
+ expect(parsed.agent.id).toBe("agent_imported_123");
1601
+ expect(parsed.agent.name).toBe("My VAPI Agent");
1602
+ expect(parsed.agent.platform).toBe("vapi");
1603
+ expect(parsed.agent.platformAgentId).toBe("asst_abc");
1604
+ expect(parsed.agent.deployments).toHaveLength(1);
1605
+ expect(parsed.agent.deployments[0].orchestrator).toBe("vapi");
1606
+ });
1607
+ it("should import from Retell platform", async () => {
1608
+ const retellAgent = {
1609
+ ...mockImportedAgent,
1610
+ id: "agent_imported_456",
1611
+ name: "My Retell Agent",
1612
+ platform: "retell",
1613
+ platformAgentId: "ret_xyz",
1614
+ deployments: [
1615
+ { orchestrator: "retell", externalId: "ret_xyz", status: "synced" }
1616
+ ]
1617
+ };
1618
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1619
+ success: true,
1620
+ data: { agent: retellAgent },
1621
+ message: "Success",
1622
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1623
+ });
1624
+ const { logs, exitCode } = await runCli([
1625
+ "agents",
1626
+ "import",
1627
+ "--from",
1628
+ "retell",
1629
+ "--agent-id",
1630
+ "ret_xyz"
1631
+ ]);
1632
+ expect(exitCode).toBe(0);
1633
+ const output = logs.join("\n");
1634
+ expect(output).toContain("My Retell Agent");
1635
+ expect(output).toContain("retell");
1636
+ expect(mockSdk.agents.importFromPlatform).toHaveBeenCalledWith(
1637
+ expect.objectContaining({
1638
+ platform: "retell",
1639
+ externalAgentId: "ret_xyz"
1640
+ })
1641
+ );
1642
+ });
1643
+ it("should handle import failure with error message", async () => {
1644
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1645
+ success: false,
1646
+ data: null,
1647
+ message: "Agent 'asst_nonexistent' not found on platform 'vapi'",
1648
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1649
+ });
1650
+ const { errors, exitCode } = await runCli([
1651
+ "agents",
1652
+ "import",
1653
+ "--from",
1654
+ "vapi",
1655
+ "--agent-id",
1656
+ "asst_nonexistent"
1657
+ ]);
1658
+ expect(exitCode).toBe(1);
1659
+ const errorOutput = errors.join("\n");
1660
+ expect(errorOutput).toContain("not found");
1661
+ });
1662
+ it("should accept optional --name flag for name override", async () => {
1663
+ mockSdk.agents.importFromPlatform.mockResolvedValue({
1664
+ success: true,
1665
+ data: {
1666
+ agent: { ...mockImportedAgent, name: "Custom Agent Name" }
1667
+ },
1668
+ message: "Success",
1669
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1670
+ });
1671
+ const { logs, exitCode } = await runCli([
1672
+ "agents",
1673
+ "import",
1674
+ "--from",
1675
+ "vapi",
1676
+ "--agent-id",
1677
+ "asst_abc",
1678
+ "--name",
1679
+ "Custom Agent Name"
1680
+ ]);
1681
+ expect(exitCode).toBe(0);
1682
+ const output = logs.join("\n");
1683
+ expect(output).toContain("Custom Agent Name");
1684
+ expect(mockSdk.agents.importFromPlatform).toHaveBeenCalledWith(
1685
+ expect.objectContaining({
1686
+ platform: "vapi",
1687
+ externalAgentId: "asst_abc",
1688
+ name: "Custom Agent Name"
1689
+ })
1690
+ );
1691
+ });
1692
+ });
1693
+ describe("agents publish", () => {
1694
+ const mockPublishResult = {
1695
+ externalId: "ext_vapi_789",
1696
+ platform: "vapi",
1697
+ status: "active",
1698
+ mcpUrl: "https://my-workspace.chanl.dev/mcp",
1699
+ action: "created"
1700
+ };
1701
+ it("should publish an agent and display results", async () => {
1702
+ mockSdk.agents.publish.mockResolvedValue({
1703
+ success: true,
1704
+ data: mockPublishResult,
1705
+ message: "Success",
1706
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1707
+ });
1708
+ const { logs, exitCode } = await runCli([
1709
+ "agents",
1710
+ "publish",
1711
+ "agent_123",
1712
+ "--to",
1713
+ "vapi"
1714
+ ]);
1715
+ expect(exitCode).toBe(0);
1716
+ const output = logs.join("\n");
1717
+ expect(output).toContain("created");
1718
+ expect(output).toContain("vapi");
1719
+ expect(output).toContain("ext_vapi_789");
1720
+ expect(mockSdk.agents.publish).toHaveBeenCalledWith(
1721
+ "agent_123",
1722
+ expect.objectContaining({
1723
+ platform: "vapi"
1724
+ })
1725
+ );
1726
+ });
1727
+ it("should show MCP URL when included in result", async () => {
1728
+ mockSdk.agents.publish.mockResolvedValue({
1729
+ success: true,
1730
+ data: mockPublishResult,
1731
+ message: "Success",
1732
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1733
+ });
1734
+ const { logs, exitCode } = await runCli([
1735
+ "agents",
1736
+ "publish",
1737
+ "agent_123",
1738
+ "--to",
1739
+ "vapi",
1740
+ "--with-mcp"
1741
+ ]);
1742
+ expect(exitCode).toBe(0);
1743
+ const output = logs.join("\n");
1744
+ expect(output).toContain("https://my-workspace.chanl.dev/mcp");
1745
+ expect(mockSdk.agents.publish).toHaveBeenCalledWith(
1746
+ "agent_123",
1747
+ expect.objectContaining({
1748
+ platform: "vapi",
1749
+ includeMcp: true
1750
+ })
1751
+ );
1752
+ });
1753
+ it("should pass --target option as targetExternalId", async () => {
1754
+ mockSdk.agents.publish.mockResolvedValue({
1755
+ success: true,
1756
+ data: { ...mockPublishResult, action: "updated" },
1757
+ message: "Success",
1758
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1759
+ });
1760
+ const { logs, exitCode } = await runCli([
1761
+ "agents",
1762
+ "publish",
1763
+ "agent_123",
1764
+ "--to",
1765
+ "vapi",
1766
+ "--target",
1767
+ "existing_ext_456"
1768
+ ]);
1769
+ expect(exitCode).toBe(0);
1770
+ const output = logs.join("\n");
1771
+ expect(output).toContain("updated");
1772
+ expect(mockSdk.agents.publish).toHaveBeenCalledWith(
1773
+ "agent_123",
1774
+ expect.objectContaining({
1775
+ platform: "vapi",
1776
+ targetExternalId: "existing_ext_456"
1777
+ })
1778
+ );
1779
+ });
1780
+ it("should output JSON with --json flag", async () => {
1781
+ mockSdk.agents.publish.mockResolvedValue({
1782
+ success: true,
1783
+ data: mockPublishResult,
1784
+ message: "Success",
1785
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1786
+ });
1787
+ const { logs, exitCode } = await runCli([
1788
+ "--json",
1789
+ "agents",
1790
+ "publish",
1791
+ "agent_123",
1792
+ "--to",
1793
+ "vapi"
1794
+ ]);
1795
+ expect(exitCode).toBe(0);
1796
+ const output = logs.join("\n");
1797
+ const parsed = JSON.parse(output);
1798
+ expect(parsed.externalId).toBe("ext_vapi_789");
1799
+ expect(parsed.platform).toBe("vapi");
1800
+ expect(parsed.action).toBe("created");
1801
+ expect(parsed.mcpUrl).toBe("https://my-workspace.chanl.dev/mcp");
1802
+ });
1803
+ it("should handle publish failure with error message", async () => {
1804
+ mockSdk.agents.publish.mockResolvedValue({
1805
+ success: false,
1806
+ data: null,
1807
+ message: "Agent 'agent_123' not found",
1808
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1809
+ });
1810
+ const { errors, exitCode } = await runCli([
1811
+ "agents",
1812
+ "publish",
1813
+ "agent_123",
1814
+ "--to",
1815
+ "vapi"
1816
+ ]);
1817
+ expect(exitCode).toBe(1);
1818
+ const errorOutput = errors.join("\n");
1819
+ expect(errorOutput).toContain("not found");
1820
+ });
1821
+ });
1822
+ describe("agents push-mcp", () => {
1823
+ const mockPushMcpResult = {
1824
+ mcpUrl: "https://my-workspace.chanl.dev/mcp",
1825
+ platform: "vapi",
1826
+ externalId: "ext_vapi_789",
1827
+ status: "configured"
1828
+ };
1829
+ it("should push MCP URL and display results", async () => {
1830
+ mockSdk.agents.pushMcp.mockResolvedValue({
1831
+ success: true,
1832
+ data: mockPushMcpResult,
1833
+ message: "Success",
1834
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1835
+ });
1836
+ const { logs, exitCode } = await runCli([
1837
+ "agents",
1838
+ "push-mcp",
1839
+ "agent_123",
1840
+ "--to",
1841
+ "vapi"
1842
+ ]);
1843
+ expect(exitCode).toBe(0);
1844
+ const output = logs.join("\n");
1845
+ expect(output).toContain("https://my-workspace.chanl.dev/mcp");
1846
+ expect(output).toContain("vapi");
1847
+ expect(output).toContain("ext_vapi_789");
1848
+ expect(mockSdk.agents.pushMcp).toHaveBeenCalledWith(
1849
+ "agent_123",
1850
+ expect.objectContaining({
1851
+ platform: "vapi"
1852
+ })
1853
+ );
1854
+ });
1855
+ it("should pass --target option as targetExternalId", async () => {
1856
+ mockSdk.agents.pushMcp.mockResolvedValue({
1857
+ success: true,
1858
+ data: mockPushMcpResult,
1859
+ message: "Success",
1860
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1861
+ });
1862
+ const { exitCode } = await runCli([
1863
+ "agents",
1864
+ "push-mcp",
1865
+ "agent_123",
1866
+ "--to",
1867
+ "vapi",
1868
+ "--target",
1869
+ "existing_ext_456"
1870
+ ]);
1871
+ expect(exitCode).toBe(0);
1872
+ expect(mockSdk.agents.pushMcp).toHaveBeenCalledWith(
1873
+ "agent_123",
1874
+ expect.objectContaining({
1875
+ platform: "vapi",
1876
+ targetExternalId: "existing_ext_456"
1877
+ })
1878
+ );
1879
+ });
1880
+ it("should output JSON with --json flag", async () => {
1881
+ mockSdk.agents.pushMcp.mockResolvedValue({
1882
+ success: true,
1883
+ data: mockPushMcpResult,
1884
+ message: "Success",
1885
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1886
+ });
1887
+ const { logs, exitCode } = await runCli([
1888
+ "--json",
1889
+ "agents",
1890
+ "push-mcp",
1891
+ "agent_123",
1892
+ "--to",
1893
+ "vapi"
1894
+ ]);
1895
+ expect(exitCode).toBe(0);
1896
+ const output = logs.join("\n");
1897
+ const parsed = JSON.parse(output);
1898
+ expect(parsed.mcpUrl).toBe("https://my-workspace.chanl.dev/mcp");
1899
+ expect(parsed.platform).toBe("vapi");
1900
+ expect(parsed.externalId).toBe("ext_vapi_789");
1901
+ expect(parsed.status).toBe("configured");
1902
+ });
1903
+ it("should handle push-mcp failure with error message", async () => {
1904
+ mockSdk.agents.pushMcp.mockResolvedValue({
1905
+ success: false,
1906
+ data: null,
1907
+ message: "Agent 'agent_123' not found",
1908
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1909
+ });
1910
+ const { errors, exitCode } = await runCli([
1911
+ "agents",
1912
+ "push-mcp",
1913
+ "agent_123",
1914
+ "--to",
1915
+ "vapi"
1916
+ ]);
1917
+ expect(exitCode).toBe(1);
1918
+ const errorOutput = errors.join("\n");
1919
+ expect(errorOutput).toContain("not found");
1920
+ });
1921
+ });
1922
+ describe("scenarios run-all", () => {
1923
+ const mockBatchResult = {
1924
+ agentId: "agent_abc",
1925
+ totalScenarios: 3,
1926
+ passed: 3,
1927
+ failed: 0,
1928
+ averageScore: 90,
1929
+ allPassed: true,
1930
+ minScore: 80,
1931
+ results: [
1932
+ {
1933
+ scenarioId: "scen_001",
1934
+ scenarioName: "Greeting Flow",
1935
+ executionId: "exec_001",
1936
+ status: "completed",
1937
+ score: 90,
1938
+ duration: 15.5,
1939
+ toolCallCount: 2,
1940
+ passed: true
1941
+ },
1942
+ {
1943
+ scenarioId: "scen_002",
1944
+ scenarioName: "Refund Flow",
1945
+ executionId: "exec_002",
1946
+ status: "completed",
1947
+ score: 85,
1948
+ duration: 22.3,
1949
+ toolCallCount: 3,
1950
+ passed: true
1951
+ },
1952
+ {
1953
+ scenarioId: "scen_003",
1954
+ scenarioName: "Escalation Flow",
1955
+ executionId: "exec_003",
1956
+ status: "completed",
1957
+ score: 95,
1958
+ duration: 10.1,
1959
+ toolCallCount: 1,
1960
+ passed: true
1961
+ }
1962
+ ]
1963
+ };
1964
+ it("should display results when all scenarios pass", async () => {
1965
+ mockSdk.scenarios.runAll.mockResolvedValue(mockBatchResult);
1966
+ const { logs, exitCode } = await runCli([
1967
+ "scenarios",
1968
+ "run-all",
1969
+ "--agent",
1970
+ "agent_abc",
1971
+ "--min-score",
1972
+ "80"
1973
+ ]);
1974
+ expect(exitCode).toBe(0);
1975
+ expect(mockSdk.scenarios.runAll).toHaveBeenCalledWith(
1976
+ expect.objectContaining({
1977
+ agentId: "agent_abc",
1978
+ minScore: 80
1979
+ })
1980
+ );
1981
+ const output = logs.join("\n");
1982
+ expect(output).toContain("Greeting Flow");
1983
+ expect(output).toContain("Refund Flow");
1984
+ expect(output).toContain("Escalation Flow");
1985
+ expect(output).toMatch(/3.*passed/i);
1986
+ });
1987
+ it("should exit code 1 when any scenario fails", async () => {
1988
+ const failedResult = {
1989
+ ...mockBatchResult,
1990
+ allPassed: false,
1991
+ passed: 2,
1992
+ failed: 1,
1993
+ averageScore: 78,
1994
+ results: [
1995
+ { ...mockBatchResult.results[0] },
1996
+ { ...mockBatchResult.results[1], score: 60, passed: false },
1997
+ { ...mockBatchResult.results[2] }
1998
+ ]
1999
+ };
2000
+ mockSdk.scenarios.runAll.mockResolvedValue(failedResult);
2001
+ const { exitCode } = await runCli([
2002
+ "scenarios",
2003
+ "run-all",
2004
+ "--agent",
2005
+ "agent_abc",
2006
+ "--min-score",
2007
+ "80"
2008
+ ]);
2009
+ expect(exitCode).toBe(1);
2010
+ });
2011
+ it("should output JSON with --json flag", async () => {
2012
+ mockSdk.scenarios.runAll.mockResolvedValue(mockBatchResult);
2013
+ const { logs, exitCode } = await runCli([
2014
+ "--json",
2015
+ "scenarios",
2016
+ "run-all",
2017
+ "--agent",
2018
+ "agent_abc"
2019
+ ]);
2020
+ expect(exitCode).toBe(0);
2021
+ const output = logs.join("\n");
2022
+ const parsed = JSON.parse(output);
2023
+ expect(parsed.allPassed).toBe(true);
2024
+ expect(parsed.totalScenarios).toBe(3);
2025
+ expect(parsed.results).toHaveLength(3);
2026
+ });
2027
+ it("should pass parallel option to SDK", async () => {
2028
+ mockSdk.scenarios.runAll.mockResolvedValue(mockBatchResult);
2029
+ await runCli([
2030
+ "scenarios",
2031
+ "run-all",
2032
+ "--agent",
2033
+ "agent_abc",
2034
+ "--parallel",
2035
+ "5"
2036
+ ]);
2037
+ expect(mockSdk.scenarios.runAll).toHaveBeenCalledWith(
2038
+ expect.objectContaining({
2039
+ agentId: "agent_abc",
2040
+ parallel: 5
2041
+ })
2042
+ );
2043
+ });
2044
+ it("should display summary with average score", async () => {
2045
+ mockSdk.scenarios.runAll.mockResolvedValue(mockBatchResult);
2046
+ const { logs } = await runCli([
2047
+ "scenarios",
2048
+ "run-all",
2049
+ "--agent",
2050
+ "agent_abc"
2051
+ ]);
2052
+ const output = logs.join("\n");
2053
+ expect(output).toContain("90");
2054
+ });
2055
+ it("should not call SDK when --agent option is missing", async () => {
2056
+ await runCli(["scenarios", "run-all"]);
2057
+ expect(mockSdk.scenarios.runAll).not.toHaveBeenCalled();
2058
+ });
2059
+ });
2060
+ describe("prompts list", () => {
2061
+ it("should list prompts with table output", async () => {
2062
+ mockSdk.prompts.list.mockResolvedValue({
2063
+ success: true,
2064
+ data: [
2065
+ {
2066
+ id: "prompt_abc123",
2067
+ name: "Support Greeting",
2068
+ category: "support",
2069
+ status: "active",
2070
+ version: 3,
2071
+ createdAt: "2025-01-01T00:00:00Z",
2072
+ updatedAt: "2025-01-15T10:30:00Z"
2073
+ },
2074
+ {
2075
+ id: "prompt_def456",
2076
+ name: "Sales Pitch",
2077
+ category: "sales",
2078
+ status: "draft",
2079
+ version: 1,
2080
+ createdAt: "2025-02-01T00:00:00Z",
2081
+ updatedAt: "2025-02-05T10:30:00Z"
2082
+ }
2083
+ ],
2084
+ message: "Success",
2085
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2086
+ });
2087
+ const { logs, exitCode } = await runCli(["prompts", "list"]);
2088
+ expect(exitCode).toBe(0);
2089
+ expect(mockSdk.prompts.list).toHaveBeenCalled();
2090
+ const output = logs.join("\n");
2091
+ expect(output).toContain("Support Greeting");
2092
+ expect(output).toContain("Sales Pitch");
2093
+ });
2094
+ it("should list prompts with JSON output", async () => {
2095
+ const mockPrompts = [
2096
+ {
2097
+ id: "prompt_abc123",
2098
+ name: "Support Greeting",
2099
+ category: "support",
2100
+ status: "active"
2101
+ }
2102
+ ];
2103
+ mockSdk.prompts.list.mockResolvedValue({
2104
+ success: true,
2105
+ data: mockPrompts,
2106
+ message: "Success",
2107
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2108
+ });
2109
+ const { logs, exitCode } = await runCli(["--json", "prompts", "list"]);
2110
+ expect(exitCode).toBe(0);
2111
+ const output = logs.join("\n");
2112
+ const parsed = JSON.parse(output);
2113
+ expect(parsed).toHaveLength(1);
2114
+ expect(parsed[0].name).toBe("Support Greeting");
2115
+ });
2116
+ it("should show message when no prompts found", async () => {
2117
+ mockSdk.prompts.list.mockResolvedValue({
2118
+ success: true,
2119
+ data: [],
2120
+ message: "Success",
2121
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2122
+ });
2123
+ const { logs, exitCode } = await runCli(["prompts", "list"]);
2124
+ expect(exitCode).toBe(0);
2125
+ const output = logs.join("\n");
2126
+ expect(output).toContain("No prompts found");
2127
+ });
2128
+ });
2129
+ describe("prompts get", () => {
2130
+ it("should get prompt details by ID", async () => {
2131
+ mockSdk.prompts.get.mockResolvedValue({
2132
+ success: true,
2133
+ data: {
2134
+ id: "prompt_abc123",
2135
+ name: "Support Greeting",
2136
+ content: "Hello! How can I help you today?",
2137
+ category: "support",
2138
+ status: "active",
2139
+ version: 3,
2140
+ tags: ["greeting", "support"],
2141
+ createdAt: "2025-01-01T00:00:00Z",
2142
+ updatedAt: "2025-01-15T10:30:00Z"
2143
+ },
2144
+ message: "Success",
2145
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2146
+ });
2147
+ const { logs, exitCode } = await runCli(["prompts", "get", "prompt_abc123"]);
2148
+ expect(exitCode).toBe(0);
2149
+ expect(mockSdk.prompts.get).toHaveBeenCalledWith("prompt_abc123");
2150
+ const output = logs.join("\n");
2151
+ expect(output).toContain("Support Greeting");
2152
+ expect(output).toContain("prompt_abc123");
2153
+ });
2154
+ it("should output JSON when --json flag is used", async () => {
2155
+ mockSdk.prompts.get.mockResolvedValue({
2156
+ success: true,
2157
+ data: {
2158
+ id: "prompt_abc123",
2159
+ name: "Support Greeting",
2160
+ content: "Hello! How can I help you today?",
2161
+ category: "support",
2162
+ status: "active"
2163
+ },
2164
+ message: "Success",
2165
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2166
+ });
2167
+ const { logs, exitCode } = await runCli(["--json", "prompts", "get", "prompt_abc123"]);
2168
+ expect(exitCode).toBe(0);
2169
+ const output = logs.join("\n");
2170
+ const parsed = JSON.parse(output);
2171
+ expect(parsed.name).toBe("Support Greeting");
2172
+ expect(parsed.content).toBe("Hello! How can I help you today?");
2173
+ });
2174
+ it("should handle not found error", async () => {
2175
+ mockSdk.prompts.get.mockResolvedValue({
2176
+ success: false,
2177
+ data: null,
2178
+ message: "Prompt not found",
2179
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2180
+ });
2181
+ const { exitCode, errors } = await runCli(["prompts", "get", "nonexistent"]);
2182
+ expect(exitCode).toBe(1);
2183
+ expect(errors.some((e) => e.includes("Failed") || e.includes("not found"))).toBe(true);
2184
+ });
2185
+ });
2186
+ describe("prompts delete", () => {
2187
+ it("should delete a prompt", async () => {
2188
+ mockSdk.prompts.delete.mockResolvedValue({
2189
+ success: true,
2190
+ data: null,
2191
+ message: "Success",
2192
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2193
+ });
2194
+ const { logs, exitCode } = await runCli(["prompts", "delete", "prompt_abc123", "--yes"]);
2195
+ expect(exitCode).toBe(0);
2196
+ expect(mockSdk.prompts.delete).toHaveBeenCalledWith("prompt_abc123");
2197
+ const output = logs.join("\n");
2198
+ expect(output).toContain("deleted");
2199
+ });
2200
+ });
2201
+ describe("workspaces list", () => {
2202
+ it("should list workspaces with table output", async () => {
2203
+ mockSdk.workspace.getAll.mockResolvedValue({
2204
+ success: true,
2205
+ data: [
2206
+ {
2207
+ id: "ws_abc123",
2208
+ name: "My Workspace",
2209
+ description: "Main workspace",
2210
+ createdAt: "2025-01-01T00:00:00Z",
2211
+ updatedAt: "2025-01-15T10:30:00Z"
2212
+ },
2213
+ {
2214
+ id: "ws_def456",
2215
+ name: "Test Workspace",
2216
+ description: "Testing environment",
2217
+ createdAt: "2025-02-01T00:00:00Z",
2218
+ updatedAt: "2025-02-05T10:30:00Z"
2219
+ }
2220
+ ],
2221
+ message: "Success",
2222
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2223
+ });
2224
+ const { logs, exitCode } = await runCli(["workspaces", "list"]);
2225
+ expect(exitCode).toBe(0);
2226
+ expect(mockSdk.workspace.getAll).toHaveBeenCalled();
2227
+ const output = logs.join("\n");
2228
+ expect(output).toContain("My Workspace");
2229
+ expect(output).toContain("Test Workspace");
2230
+ });
2231
+ it("should list workspaces with JSON output", async () => {
2232
+ const mockWorkspaces = [
2233
+ {
2234
+ id: "ws_abc123",
2235
+ name: "My Workspace",
2236
+ description: "Main workspace",
2237
+ createdAt: "2025-01-01T00:00:00Z",
2238
+ updatedAt: "2025-01-15T10:30:00Z"
2239
+ }
2240
+ ];
2241
+ mockSdk.workspace.getAll.mockResolvedValue({
2242
+ success: true,
2243
+ data: mockWorkspaces,
2244
+ message: "Success",
2245
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2246
+ });
2247
+ const { logs, exitCode } = await runCli(["--json", "workspaces", "list"]);
2248
+ expect(exitCode).toBe(0);
2249
+ const output = logs.join("\n");
2250
+ const parsed = JSON.parse(output);
2251
+ expect(parsed).toHaveLength(1);
2252
+ expect(parsed[0].name).toBe("My Workspace");
2253
+ });
2254
+ });
2255
+ describe("workspaces get", () => {
2256
+ it("should get workspace details by ID", async () => {
2257
+ mockSdk.workspace.getById.mockResolvedValue({
2258
+ success: true,
2259
+ data: {
2260
+ id: "ws_abc123",
2261
+ name: "My Workspace",
2262
+ description: "Main workspace",
2263
+ settings: { timezone: "UTC" },
2264
+ createdAt: "2025-01-01T00:00:00Z",
2265
+ updatedAt: "2025-01-15T10:30:00Z"
2266
+ },
2267
+ message: "Success",
2268
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2269
+ });
2270
+ const { logs, exitCode } = await runCli(["workspaces", "get", "ws_abc123"]);
2271
+ expect(exitCode).toBe(0);
2272
+ expect(mockSdk.workspace.getById).toHaveBeenCalledWith("ws_abc123");
2273
+ const output = logs.join("\n");
2274
+ expect(output).toContain("My Workspace");
2275
+ expect(output).toContain("ws_abc123");
2276
+ });
2277
+ });
2278
+ describe("workspaces use", () => {
2279
+ it("should set active workspace", async () => {
2280
+ mockSdk.workspace.getById.mockResolvedValue({
2281
+ success: true,
2282
+ data: {
2283
+ id: "ws_abc123",
2284
+ name: "My Workspace",
2285
+ description: "Main workspace",
2286
+ settings: {},
2287
+ createdAt: "2025-01-01T00:00:00Z",
2288
+ updatedAt: "2025-01-15T10:30:00Z"
2289
+ },
2290
+ message: "Success",
2291
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2292
+ });
2293
+ const { logs, exitCode } = await runCli(["workspaces", "use", "ws_abc123"]);
2294
+ expect(exitCode).toBe(0);
2295
+ expect(configStore.setWorkspaceId).toHaveBeenCalledWith("ws_abc123");
2296
+ const output = logs.join("\n");
2297
+ expect(output).toContain("ws_abc123");
2298
+ });
2299
+ it("should handle invalid workspace ID", async () => {
2300
+ mockSdk.workspace.getById.mockResolvedValue({
2301
+ success: false,
2302
+ data: null,
2303
+ message: "Workspace not found",
2304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2305
+ });
2306
+ const { exitCode, errors } = await runCli(["workspaces", "use", "ws_invalid"]);
2307
+ expect(exitCode).toBe(1);
2308
+ expect(errors.some((e) => e.includes("not found") || e.includes("Failed"))).toBe(true);
2309
+ expect(configStore.setWorkspaceId).not.toHaveBeenCalled();
2310
+ });
2311
+ });
2312
+ });
2313
+ //# sourceMappingURL=cli.test.js.map