@chanl/cli 2.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/agents.test.js +574 -0
- package/dist/__tests__/agents.test.js.map +1 -0
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.js +87 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/calls.test.d.ts +2 -0
- package/dist/__tests__/calls.test.js +115 -0
- package/dist/__tests__/calls.test.js.map +1 -0
- package/dist/__tests__/chat.test.d.ts +2 -0
- package/dist/__tests__/chat.test.js +357 -0
- package/dist/__tests__/chat.test.js.map +1 -0
- package/dist/__tests__/cli-base.test.d.ts +2 -0
- package/dist/__tests__/cli-base.test.js +87 -0
- package/dist/__tests__/cli-base.test.js.map +1 -0
- package/dist/__tests__/cli-missing.test.d.ts +2 -0
- package/dist/__tests__/cli-missing.test.js +546 -0
- package/dist/__tests__/cli-missing.test.js.map +1 -0
- package/dist/__tests__/cli-p0-scenarios.test.d.ts +2 -0
- package/dist/__tests__/cli-p0-scenarios.test.js +123 -0
- package/dist/__tests__/cli-p0-scenarios.test.js.map +1 -0
- package/dist/__tests__/helpers.d.ts +234 -0
- package/dist/__tests__/helpers.js +314 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/__tests__/mcp.test.d.ts +2 -0
- package/dist/__tests__/mcp.test.js +470 -0
- package/dist/__tests__/mcp.test.js.map +1 -0
- package/dist/__tests__/prompts.test.d.ts +2 -0
- package/dist/__tests__/prompts.test.js +227 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/scenarios.test.d.ts +2 -0
- package/dist/__tests__/scenarios.test.js +270 -0
- package/dist/__tests__/scenarios.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +2 -0
- package/dist/__tests__/tools.test.js +331 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/__tests__/toolsets.test.d.ts +2 -0
- package/dist/__tests__/toolsets.test.js +196 -0
- package/dist/__tests__/toolsets.test.js.map +1 -0
- package/dist/__tests__/workspaces.test.d.ts +2 -0
- package/dist/__tests__/workspaces.test.js +155 -0
- package/dist/__tests__/workspaces.test.js.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/agents.js +194 -6
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/chat.js +2 -2
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/config.js +24 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/customers.d.ts +8 -0
- package/dist/commands/customers.js +289 -0
- package/dist/commands/customers.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/memory.js +134 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/messaging.d.ts +12 -0
- package/dist/commands/messaging.js +208 -0
- package/dist/commands/messaging.js.map +1 -0
- package/dist/commands/prompts.js +148 -1
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/scenarios.js +121 -0
- package/dist/commands/scenarios.js.map +1 -1
- package/dist/commands/scorecards.js +3 -3
- package/dist/commands/scorecards.js.map +1 -1
- package/dist/commands/whatsapp.d.ts +8 -0
- package/dist/commands/whatsapp.js +332 -0
- package/dist/commands/whatsapp.js.map +1 -0
- package/dist/utils/config-store.d.ts +8 -0
- package/dist/utils/config-store.js +18 -1
- package/dist/utils/config-store.js.map +1 -1
- package/dist/utils/sdk-factory.js +15 -3
- package/dist/utils/sdk-factory.js.map +1 -1
- package/package.json +4 -4
- package/dist/__tests__/cli.test.js +0 -2308
- package/dist/__tests__/cli.test.js.map +0 -1
- /package/dist/__tests__/{cli.test.d.ts → agents.test.d.ts} +0 -0
|
@@ -0,0 +1,470 @@
|
|
|
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/sdk";
|
|
5
|
+
import {
|
|
6
|
+
runCli,
|
|
7
|
+
makeMockSdk,
|
|
8
|
+
setupAuthenticatedConfigStore,
|
|
9
|
+
mockMcpConfig,
|
|
10
|
+
mockMcpStatusHealthy,
|
|
11
|
+
mockMcpStatusUnhealthy
|
|
12
|
+
} from "./helpers";
|
|
13
|
+
vi.mock("@chanl/sdk", () => ({
|
|
14
|
+
ChanlSDK: vi.fn()
|
|
15
|
+
}));
|
|
16
|
+
vi.mock("../utils/config-store", () => ({
|
|
17
|
+
configStore: {
|
|
18
|
+
getApiKey: vi.fn(),
|
|
19
|
+
getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
|
|
20
|
+
getWorkspaceId: vi.fn(() => "ws_123"),
|
|
21
|
+
getJwtToken: vi.fn(() => null),
|
|
22
|
+
getAuthMethod: vi.fn(() => null),
|
|
23
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
24
|
+
getDeployment: vi.fn(() => "cloud"),
|
|
25
|
+
getApiPrefix: vi.fn(() => "/api/v1"),
|
|
26
|
+
setApiKey: vi.fn(),
|
|
27
|
+
setBaseUrl: vi.fn(),
|
|
28
|
+
setWorkspaceId: vi.fn(),
|
|
29
|
+
removeApiKey: vi.fn(),
|
|
30
|
+
clearAuth: vi.fn(),
|
|
31
|
+
delete: vi.fn(),
|
|
32
|
+
isAuthenticated: vi.fn(),
|
|
33
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
describe("CLI", () => {
|
|
37
|
+
let mockSdk;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
mockSdk = makeMockSdk();
|
|
41
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
42
|
+
setupAuthenticatedConfigStore(configStore);
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
process.exitCode = void 0;
|
|
46
|
+
});
|
|
47
|
+
describe("mcp info", () => {
|
|
48
|
+
it("should display MCP info with table output", async () => {
|
|
49
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
50
|
+
success: true,
|
|
51
|
+
data: mockMcpConfig,
|
|
52
|
+
message: "Success",
|
|
53
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
+
});
|
|
55
|
+
const { logs, exitCode } = await runCli(["mcp", "info"]);
|
|
56
|
+
expect(exitCode).toBe(0);
|
|
57
|
+
expect(mockSdk.mcp.getConfig).toHaveBeenCalled();
|
|
58
|
+
const output = logs.join("\n");
|
|
59
|
+
expect(output).toContain("https://my-workspace.chanl.app/mcp");
|
|
60
|
+
expect(output).toContain("production");
|
|
61
|
+
expect(output).toContain("15");
|
|
62
|
+
});
|
|
63
|
+
it("should display MCP info with JSON output", async () => {
|
|
64
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
65
|
+
success: true,
|
|
66
|
+
data: mockMcpConfig,
|
|
67
|
+
message: "Success",
|
|
68
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
69
|
+
});
|
|
70
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "info"]);
|
|
71
|
+
expect(exitCode).toBe(0);
|
|
72
|
+
const output = logs.join("\n");
|
|
73
|
+
const parsed = JSON.parse(output);
|
|
74
|
+
expect(parsed.serverUrl).toBe("https://my-workspace.chanl.app/mcp");
|
|
75
|
+
expect(parsed.environment).toBe("production");
|
|
76
|
+
expect(parsed.toolCount).toBe(15);
|
|
77
|
+
});
|
|
78
|
+
it("should handle API errors", async () => {
|
|
79
|
+
mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
|
|
80
|
+
const { exitCode, errors } = await runCli(["mcp", "info"]);
|
|
81
|
+
expect(exitCode).toBe(1);
|
|
82
|
+
expect(errors.some((e) => e.includes("Network error"))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("mcp status", () => {
|
|
86
|
+
it("should show healthy status", async () => {
|
|
87
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
88
|
+
success: true,
|
|
89
|
+
data: mockMcpStatusHealthy,
|
|
90
|
+
message: "Success",
|
|
91
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
92
|
+
});
|
|
93
|
+
const { logs, exitCode } = await runCli(["mcp", "status"]);
|
|
94
|
+
expect(exitCode).toBe(0);
|
|
95
|
+
expect(mockSdk.mcp.getStatus).toHaveBeenCalled();
|
|
96
|
+
const output = logs.join("\n");
|
|
97
|
+
expect(output).toContain("healthy");
|
|
98
|
+
expect(output).toContain("MCP server is running");
|
|
99
|
+
});
|
|
100
|
+
it("should show unhealthy status with exit code 1", async () => {
|
|
101
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
102
|
+
success: true,
|
|
103
|
+
data: mockMcpStatusUnhealthy,
|
|
104
|
+
message: "Success",
|
|
105
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
});
|
|
107
|
+
const { errors, exitCode } = await runCli(["mcp", "status"]);
|
|
108
|
+
expect(exitCode).toBe(1);
|
|
109
|
+
const output = errors.join("\n");
|
|
110
|
+
expect(output).toContain("unhealthy");
|
|
111
|
+
});
|
|
112
|
+
it("should output JSON when --json flag is used", async () => {
|
|
113
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
114
|
+
success: true,
|
|
115
|
+
data: mockMcpStatusHealthy,
|
|
116
|
+
message: "Success",
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
118
|
+
});
|
|
119
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "status"]);
|
|
120
|
+
expect(exitCode).toBe(0);
|
|
121
|
+
const output = logs.join("\n");
|
|
122
|
+
const parsed = JSON.parse(output);
|
|
123
|
+
expect(parsed.healthy).toBe(true);
|
|
124
|
+
expect(parsed.message).toBe("MCP server is running");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("mcp config", () => {
|
|
128
|
+
it("should output generic config by default", async () => {
|
|
129
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
130
|
+
success: true,
|
|
131
|
+
data: mockMcpConfig,
|
|
132
|
+
message: "Success",
|
|
133
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
|
+
});
|
|
135
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config"]);
|
|
136
|
+
expect(exitCode).toBe(0);
|
|
137
|
+
const output = logs.join("\n");
|
|
138
|
+
const parsed = JSON.parse(output);
|
|
139
|
+
expect(parsed.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
140
|
+
expect(parsed.transport).toBe("streamable-http");
|
|
141
|
+
});
|
|
142
|
+
it("should output claude config with --format claude", async () => {
|
|
143
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
144
|
+
success: true,
|
|
145
|
+
data: mockMcpConfig,
|
|
146
|
+
message: "Success",
|
|
147
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
148
|
+
});
|
|
149
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "claude"]);
|
|
150
|
+
expect(exitCode).toBe(0);
|
|
151
|
+
const output = logs.join("\n");
|
|
152
|
+
const parsed = JSON.parse(output);
|
|
153
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
154
|
+
expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
155
|
+
});
|
|
156
|
+
it("should output cursor config with --format cursor", async () => {
|
|
157
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
158
|
+
success: true,
|
|
159
|
+
data: mockMcpConfig,
|
|
160
|
+
message: "Success",
|
|
161
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
162
|
+
});
|
|
163
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "cursor"]);
|
|
164
|
+
expect(exitCode).toBe(0);
|
|
165
|
+
const output = logs.join("\n");
|
|
166
|
+
const parsed = JSON.parse(output);
|
|
167
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
168
|
+
expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
169
|
+
});
|
|
170
|
+
it("should handle API errors", async () => {
|
|
171
|
+
mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
|
|
172
|
+
const { exitCode, errors } = await runCli(["mcp", "config"]);
|
|
173
|
+
expect(exitCode).toBe(1);
|
|
174
|
+
expect(errors.some((e) => e.includes("Network error"))).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe("mcp command registration", () => {
|
|
178
|
+
it("should have mcp command registered", () => {
|
|
179
|
+
const program = createProgram();
|
|
180
|
+
const commandNames = program.commands.map((c) => c.name());
|
|
181
|
+
expect(commandNames).toContain("mcp");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe("mcp tools", () => {
|
|
185
|
+
const mockMcpTools = [
|
|
186
|
+
{
|
|
187
|
+
name: "get_weather",
|
|
188
|
+
description: "Get current weather for a location",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
city: { type: "string", description: "City name" }
|
|
193
|
+
},
|
|
194
|
+
required: ["city"]
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "search_kb",
|
|
199
|
+
description: "Search the knowledge base",
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
query: { type: "string", description: "Search query" }
|
|
204
|
+
},
|
|
205
|
+
required: ["query"]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
it("should list MCP tools for an agent with table output", async () => {
|
|
210
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
211
|
+
success: true,
|
|
212
|
+
data: { tools: mockMcpTools },
|
|
213
|
+
message: "Success",
|
|
214
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
|
+
});
|
|
216
|
+
const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
|
|
217
|
+
expect(exitCode).toBe(0);
|
|
218
|
+
expect(mockSdk.mcp.listTools).toHaveBeenCalledWith("agent_123");
|
|
219
|
+
const output = logs.join("\n");
|
|
220
|
+
expect(output).toContain("get_weather");
|
|
221
|
+
expect(output).toContain("search_kb");
|
|
222
|
+
});
|
|
223
|
+
it("should list MCP tools with JSON output", async () => {
|
|
224
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
225
|
+
success: true,
|
|
226
|
+
data: { tools: mockMcpTools },
|
|
227
|
+
message: "Success",
|
|
228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
229
|
+
});
|
|
230
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "tools", "agent_123"]);
|
|
231
|
+
expect(exitCode).toBe(0);
|
|
232
|
+
const output = logs.join("\n");
|
|
233
|
+
const parsed = JSON.parse(output);
|
|
234
|
+
expect(parsed.tools).toHaveLength(2);
|
|
235
|
+
expect(parsed.tools[0].name).toBe("get_weather");
|
|
236
|
+
});
|
|
237
|
+
it("should show message when agent has no tools", async () => {
|
|
238
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
239
|
+
success: true,
|
|
240
|
+
data: { tools: [] },
|
|
241
|
+
message: "Success",
|
|
242
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
243
|
+
});
|
|
244
|
+
const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
|
|
245
|
+
expect(exitCode).toBe(0);
|
|
246
|
+
const output = logs.join("\n");
|
|
247
|
+
expect(output).toContain("No MCP tools");
|
|
248
|
+
});
|
|
249
|
+
it("should handle API errors", async () => {
|
|
250
|
+
mockSdk.mcp.listTools.mockRejectedValue(new Error("Agent not found"));
|
|
251
|
+
const { exitCode, errors } = await runCli(["mcp", "tools", "agent_123"]);
|
|
252
|
+
expect(exitCode).toBe(1);
|
|
253
|
+
expect(errors.some((e) => e.includes("Agent not found"))).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe("mcp call", () => {
|
|
257
|
+
it("should call a tool and display result", async () => {
|
|
258
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
259
|
+
success: true,
|
|
260
|
+
data: {
|
|
261
|
+
content: [{ type: "text", text: "Temperature in London: 15C" }],
|
|
262
|
+
isError: false
|
|
263
|
+
},
|
|
264
|
+
message: "Success",
|
|
265
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
266
|
+
});
|
|
267
|
+
const { logs, exitCode } = await runCli([
|
|
268
|
+
"mcp",
|
|
269
|
+
"call",
|
|
270
|
+
"agent_123",
|
|
271
|
+
"get_weather",
|
|
272
|
+
'{"city":"London"}'
|
|
273
|
+
]);
|
|
274
|
+
expect(exitCode).toBe(0);
|
|
275
|
+
expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
|
|
276
|
+
"agent_123",
|
|
277
|
+
"get_weather",
|
|
278
|
+
{ city: "London" }
|
|
279
|
+
);
|
|
280
|
+
const output = logs.join("\n");
|
|
281
|
+
expect(output).toContain("Temperature in London: 15C");
|
|
282
|
+
});
|
|
283
|
+
it("should call a tool without arguments", async () => {
|
|
284
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
285
|
+
success: true,
|
|
286
|
+
data: {
|
|
287
|
+
content: [{ type: "text", text: "pong" }],
|
|
288
|
+
isError: false
|
|
289
|
+
},
|
|
290
|
+
message: "Success",
|
|
291
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
292
|
+
});
|
|
293
|
+
const { logs, exitCode } = await runCli(["mcp", "call", "agent_123", "ping"]);
|
|
294
|
+
expect(exitCode).toBe(0);
|
|
295
|
+
expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
|
|
296
|
+
"agent_123",
|
|
297
|
+
"ping",
|
|
298
|
+
void 0
|
|
299
|
+
);
|
|
300
|
+
const output = logs.join("\n");
|
|
301
|
+
expect(output).toContain("pong");
|
|
302
|
+
});
|
|
303
|
+
it("should display error result from tool", async () => {
|
|
304
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
305
|
+
success: true,
|
|
306
|
+
data: {
|
|
307
|
+
content: [{ type: "text", text: "Error: city not found" }],
|
|
308
|
+
isError: true
|
|
309
|
+
},
|
|
310
|
+
message: "Success",
|
|
311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
312
|
+
});
|
|
313
|
+
const { errors, exitCode } = await runCli([
|
|
314
|
+
"mcp",
|
|
315
|
+
"call",
|
|
316
|
+
"agent_123",
|
|
317
|
+
"get_weather",
|
|
318
|
+
'{"city":"xyz"}'
|
|
319
|
+
]);
|
|
320
|
+
expect(exitCode).toBe(1);
|
|
321
|
+
const output = errors.join("\n");
|
|
322
|
+
expect(output).toContain("Error");
|
|
323
|
+
});
|
|
324
|
+
it("should output JSON when --json flag is used", async () => {
|
|
325
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
326
|
+
success: true,
|
|
327
|
+
data: {
|
|
328
|
+
content: [{ type: "text", text: "result text" }],
|
|
329
|
+
isError: false
|
|
330
|
+
},
|
|
331
|
+
message: "Success",
|
|
332
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
333
|
+
});
|
|
334
|
+
const { logs, exitCode } = await runCli([
|
|
335
|
+
"--json",
|
|
336
|
+
"mcp",
|
|
337
|
+
"call",
|
|
338
|
+
"agent_123",
|
|
339
|
+
"get_weather",
|
|
340
|
+
'{"city":"London"}'
|
|
341
|
+
]);
|
|
342
|
+
expect(exitCode).toBe(0);
|
|
343
|
+
const output = logs.join("\n");
|
|
344
|
+
const parsed = JSON.parse(output);
|
|
345
|
+
expect(parsed.content).toBeDefined();
|
|
346
|
+
expect(parsed.content[0].text).toBe("result text");
|
|
347
|
+
});
|
|
348
|
+
it("should handle invalid JSON arguments gracefully", async () => {
|
|
349
|
+
const { exitCode, errors } = await runCli([
|
|
350
|
+
"mcp",
|
|
351
|
+
"call",
|
|
352
|
+
"agent_123",
|
|
353
|
+
"get_weather",
|
|
354
|
+
"not-json"
|
|
355
|
+
]);
|
|
356
|
+
expect(exitCode).toBe(1);
|
|
357
|
+
expect(errors.some((e) => e.includes("Invalid JSON"))).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
it("should handle API errors", async () => {
|
|
360
|
+
mockSdk.mcp.callTool.mockRejectedValue(new Error("Tool not found"));
|
|
361
|
+
const { exitCode, errors } = await runCli([
|
|
362
|
+
"mcp",
|
|
363
|
+
"call",
|
|
364
|
+
"agent_123",
|
|
365
|
+
"nonexistent",
|
|
366
|
+
"{}"
|
|
367
|
+
]);
|
|
368
|
+
expect(exitCode).toBe(1);
|
|
369
|
+
expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
describe("mcp inspect", () => {
|
|
373
|
+
const mockToolDetail = {
|
|
374
|
+
name: "get_weather",
|
|
375
|
+
description: "Get current weather for a location",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
city: { type: "string", description: "City name" },
|
|
380
|
+
units: { type: "string", enum: ["celsius", "fahrenheit"] }
|
|
381
|
+
},
|
|
382
|
+
required: ["city"]
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
it("should display tool details with table output", async () => {
|
|
386
|
+
mockSdk.mcp.getTool.mockResolvedValue({
|
|
387
|
+
success: true,
|
|
388
|
+
data: mockToolDetail,
|
|
389
|
+
message: "Success",
|
|
390
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
391
|
+
});
|
|
392
|
+
const { logs, exitCode } = await runCli(["mcp", "inspect", "agent_123", "get_weather"]);
|
|
393
|
+
expect(exitCode).toBe(0);
|
|
394
|
+
expect(mockSdk.mcp.getTool).toHaveBeenCalledWith("agent_123", "get_weather");
|
|
395
|
+
const output = logs.join("\n");
|
|
396
|
+
expect(output).toContain("get_weather");
|
|
397
|
+
expect(output).toContain("Get current weather");
|
|
398
|
+
expect(output).toContain("city");
|
|
399
|
+
});
|
|
400
|
+
it("should display tool details with JSON output", async () => {
|
|
401
|
+
mockSdk.mcp.getTool.mockResolvedValue({
|
|
402
|
+
success: true,
|
|
403
|
+
data: mockToolDetail,
|
|
404
|
+
message: "Success",
|
|
405
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
406
|
+
});
|
|
407
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "inspect", "agent_123", "get_weather"]);
|
|
408
|
+
expect(exitCode).toBe(0);
|
|
409
|
+
const output = logs.join("\n");
|
|
410
|
+
const parsed = JSON.parse(output);
|
|
411
|
+
expect(parsed.name).toBe("get_weather");
|
|
412
|
+
expect(parsed.inputSchema).toBeDefined();
|
|
413
|
+
});
|
|
414
|
+
it("should handle tool not found", async () => {
|
|
415
|
+
mockSdk.mcp.getTool.mockRejectedValue(new Error("Tool not found"));
|
|
416
|
+
const { exitCode, errors } = await runCli(["mcp", "inspect", "agent_123", "nonexistent"]);
|
|
417
|
+
expect(exitCode).toBe(1);
|
|
418
|
+
expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
describe("mcp install", () => {
|
|
422
|
+
it("should generate config snippet for default (generic) format", async () => {
|
|
423
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
|
|
424
|
+
expect(exitCode).toBe(0);
|
|
425
|
+
const output = logs.join("\n");
|
|
426
|
+
const parsed = JSON.parse(output);
|
|
427
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
428
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
429
|
+
expect(parsed.mcpServers.chanl.transport).toBe("streamable-http");
|
|
430
|
+
});
|
|
431
|
+
it("should include API key from config", async () => {
|
|
432
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
|
|
433
|
+
expect(exitCode).toBe(0);
|
|
434
|
+
const output = logs.join("\n");
|
|
435
|
+
const parsed = JSON.parse(output);
|
|
436
|
+
expect(parsed.mcpServers.chanl.headers["X-API-Key"]).toBe("test-api-key");
|
|
437
|
+
});
|
|
438
|
+
it("should generate config for claude format", async () => {
|
|
439
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "claude"]);
|
|
440
|
+
expect(exitCode).toBe(0);
|
|
441
|
+
const output = logs.join("\n");
|
|
442
|
+
const parsed = JSON.parse(output);
|
|
443
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
444
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
445
|
+
});
|
|
446
|
+
it("should generate config for cursor format", async () => {
|
|
447
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "cursor"]);
|
|
448
|
+
expect(exitCode).toBe(0);
|
|
449
|
+
const output = logs.join("\n");
|
|
450
|
+
const parsed = JSON.parse(output);
|
|
451
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
452
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
453
|
+
});
|
|
454
|
+
it("should generate config for vscode format", async () => {
|
|
455
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "vscode"]);
|
|
456
|
+
expect(exitCode).toBe(0);
|
|
457
|
+
const output = logs.join("\n");
|
|
458
|
+
const parsed = JSON.parse(output);
|
|
459
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
460
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
461
|
+
});
|
|
462
|
+
it("should error when not authenticated", async () => {
|
|
463
|
+
configStore.getApiKey.mockReturnValue(null);
|
|
464
|
+
const { exitCode, errors } = await runCli(["mcp", "install"]);
|
|
465
|
+
expect(exitCode).toBe(1);
|
|
466
|
+
expect(errors.some((e) => e.includes("Not authenticated") || e.includes("login"))).toBe(true);
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
//# sourceMappingURL=mcp.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/mcp.test.ts"],"sourcesContent":["/**\n * CLI mcp tests — mcp info/status/config/tools/call/inspect/install + command registration\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';\nimport { createProgram } from '../cli';\nimport { configStore } from '../utils/config-store';\nimport { ChanlSDK } from '@chanl/sdk';\nimport {\n runCli,\n makeMockSdk,\n setupAuthenticatedConfigStore,\n mockMcpConfig,\n mockMcpStatusHealthy,\n mockMcpStatusUnhealthy,\n type MockSdk,\n} from './helpers';\n\n// Mock the SDK\nvi.mock('@chanl/sdk', () => ({\n ChanlSDK: vi.fn(),\n}));\n\n// Mock config store\nvi.mock('../utils/config-store', () => ({\n configStore: {\n getApiKey: vi.fn(),\n getBaseUrl: vi.fn(() => 'https://api.chanl.ai'),\n getWorkspaceId: vi.fn(() => 'ws_123'),\n getJwtToken: vi.fn(() => null),\n getAuthMethod: vi.fn(() => null),\n getAppUrl: vi.fn(() => 'https://app.chanl.ai'),\n getDeployment: vi.fn(() => 'cloud'),\n getApiPrefix: vi.fn(() => '/api/v1'),\n setApiKey: vi.fn(),\n setBaseUrl: vi.fn(),\n setWorkspaceId: vi.fn(),\n removeApiKey: vi.fn(),\n clearAuth: vi.fn(),\n delete: vi.fn(),\n isAuthenticated: vi.fn(),\n getPath: vi.fn(() => '/home/user/.chanl/config.json'),\n },\n}));\n\ndescribe('CLI', () => {\n let mockSdk: MockSdk;\n\n beforeEach(() => {\n vi.clearAllMocks();\n mockSdk = makeMockSdk();\n (ChanlSDK as Mock).mockImplementation(() => mockSdk);\n setupAuthenticatedConfigStore(configStore as unknown as {\n getApiKey: Mock;\n getWorkspaceId: Mock;\n isAuthenticated: Mock;\n });\n });\n\n afterEach(() => {\n process.exitCode = undefined;\n });\n\n describe('mcp info', () => {\n it('should display MCP info with table output', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'info']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getConfig).toHaveBeenCalled();\n\n const output = logs.join('\\n');\n expect(output).toContain('https://my-workspace.chanl.app/mcp');\n expect(output).toContain('production');\n expect(output).toContain('15'); // toolCount\n });\n\n it('should display MCP info with JSON output', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'info']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.serverUrl).toBe('https://my-workspace.chanl.app/mcp');\n expect(parsed.environment).toBe('production');\n expect(parsed.toolCount).toBe(15);\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.getConfig.mockRejectedValue(new Error('Network error'));\n\n const { exitCode, errors } = await runCli(['mcp', 'info']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Network error'))).toBe(true);\n });\n });\n\n describe('mcp status', () => {\n it('should show healthy status', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusHealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'status']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getStatus).toHaveBeenCalled();\n\n const output = logs.join('\\n');\n expect(output).toContain('healthy');\n expect(output).toContain('MCP server is running');\n });\n\n it('should show unhealthy status with exit code 1', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusUnhealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { errors, exitCode } = await runCli(['mcp', 'status']);\n\n expect(exitCode).toBe(1);\n\n const output = errors.join('\\n');\n expect(output).toContain('unhealthy');\n });\n\n it('should output JSON when --json flag is used', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusHealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'status']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.healthy).toBe(true);\n expect(parsed.message).toBe('MCP server is running');\n });\n });\n\n describe('mcp config', () => {\n it('should output generic config by default', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.url).toBe('https://my-workspace.chanl.app/mcp');\n expect(parsed.transport).toBe('streamable-http');\n });\n\n it('should output claude config with --format claude', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config', '--format', 'claude']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl.url).toBe('https://my-workspace.chanl.app/mcp');\n });\n\n it('should output cursor config with --format cursor', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config', '--format', 'cursor']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl.url).toBe('https://my-workspace.chanl.app/mcp');\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.getConfig.mockRejectedValue(new Error('Network error'));\n\n const { exitCode, errors } = await runCli(['mcp', 'config']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Network error'))).toBe(true);\n });\n });\n\n describe('mcp command registration', () => {\n it('should have mcp command registered', () => {\n const program = createProgram();\n const commandNames = program.commands.map((c) => c.name());\n\n expect(commandNames).toContain('mcp');\n });\n });\n\n // ============================================\n // MCP Tools Commands (Story 10)\n // ============================================\n\n describe('mcp tools', () => {\n const mockMcpTools = [\n {\n name: 'get_weather',\n description: 'Get current weather for a location',\n inputSchema: {\n type: 'object',\n properties: {\n city: { type: 'string', description: 'City name' },\n },\n required: ['city'],\n },\n },\n {\n name: 'search_kb',\n description: 'Search the knowledge base',\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query' },\n },\n required: ['query'],\n },\n },\n ];\n\n it('should list MCP tools for an agent with table output', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: mockMcpTools },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.listTools).toHaveBeenCalledWith('agent_123');\n\n const output = logs.join('\\n');\n expect(output).toContain('get_weather');\n expect(output).toContain('search_kb');\n });\n\n it('should list MCP tools with JSON output', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: mockMcpTools },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.tools).toHaveLength(2);\n expect(parsed.tools[0].name).toBe('get_weather');\n });\n\n it('should show message when agent has no tools', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: [] },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n expect(output).toContain('No MCP tools');\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.listTools.mockRejectedValue(new Error('Agent not found'));\n\n const { exitCode, errors } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Agent not found'))).toBe(true);\n });\n });\n\n describe('mcp call', () => {\n it('should call a tool and display result', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'Temperature in London: 15C' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"London\"}',\n ]);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(\n 'agent_123', 'get_weather', { city: 'London' }\n );\n\n const output = logs.join('\\n');\n expect(output).toContain('Temperature in London: 15C');\n });\n\n it('should call a tool without arguments', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'pong' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'call', 'agent_123', 'ping']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(\n 'agent_123', 'ping', undefined\n );\n\n const output = logs.join('\\n');\n expect(output).toContain('pong');\n });\n\n it('should display error result from tool', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'Error: city not found' }],\n isError: true,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { errors, exitCode } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"xyz\"}',\n ]);\n\n expect(exitCode).toBe(1);\n const output = errors.join('\\n');\n expect(output).toContain('Error');\n });\n\n it('should output JSON when --json flag is used', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'result text' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli([\n '--json', 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"London\"}',\n ]);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.content).toBeDefined();\n expect(parsed.content[0].text).toBe('result text');\n });\n\n it('should handle invalid JSON arguments gracefully', async () => {\n const { exitCode, errors } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', 'not-json',\n ]);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Invalid JSON'))).toBe(true);\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.callTool.mockRejectedValue(new Error('Tool not found'));\n\n const { exitCode, errors } = await runCli([\n 'mcp', 'call', 'agent_123', 'nonexistent', '{}',\n ]);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Tool not found'))).toBe(true);\n });\n });\n\n describe('mcp inspect', () => {\n const mockToolDetail = {\n name: 'get_weather',\n description: 'Get current weather for a location',\n inputSchema: {\n type: 'object',\n properties: {\n city: { type: 'string', description: 'City name' },\n units: { type: 'string', enum: ['celsius', 'fahrenheit'] },\n },\n required: ['city'],\n },\n };\n\n it('should display tool details with table output', async () => {\n mockSdk.mcp.getTool.mockResolvedValue({\n success: true,\n data: mockToolDetail,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'inspect', 'agent_123', 'get_weather']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getTool).toHaveBeenCalledWith('agent_123', 'get_weather');\n\n const output = logs.join('\\n');\n expect(output).toContain('get_weather');\n expect(output).toContain('Get current weather');\n expect(output).toContain('city');\n });\n\n it('should display tool details with JSON output', async () => {\n mockSdk.mcp.getTool.mockResolvedValue({\n success: true,\n data: mockToolDetail,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'inspect', 'agent_123', 'get_weather']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.name).toBe('get_weather');\n expect(parsed.inputSchema).toBeDefined();\n });\n\n it('should handle tool not found', async () => {\n mockSdk.mcp.getTool.mockRejectedValue(new Error('Tool not found'));\n\n const { exitCode, errors } = await runCli(['mcp', 'inspect', 'agent_123', 'nonexistent']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Tool not found'))).toBe(true);\n });\n });\n\n describe('mcp install', () => {\n it('should generate config snippet for default (generic) format', async () => {\n // Use --json to suppress info messages in output (config is always JSON)\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n expect(parsed.mcpServers.chanl.transport).toBe('streamable-http');\n });\n\n it('should include API key from config', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers.chanl.headers['X-API-Key']).toBe('test-api-key');\n });\n\n it('should generate config for claude format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'claude']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should generate config for cursor format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'cursor']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should generate config for vscode format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'vscode']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should error when not authenticated', async () => {\n (configStore.getApiKey as Mock).mockReturnValue(null);\n\n const { exitCode, errors } = await runCli(['mcp', 'install']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Not authenticated') || e.includes('login'))).toBe(true);\n });\n });\n});\n"],"mappings":"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","names":[]}
|