@chanl/cli 2.0.3 → 3.0.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__/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/commands/agents.js +103 -13
- 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/mcp.js +1 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/memory.js +137 -4
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/personas.js +3 -3
- package/dist/commands/personas.js.map +1 -1
- package/dist/commands/prompts.js +148 -1
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/scenarios.js +2 -2
- 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/tools.js +5 -5
- package/dist/commands/tools.js.map +1 -1
- 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 -2313
- 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,87 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { configStore } from "../utils/config-store";
|
|
3
|
+
import { ChanlSDK } from "@chanl/sdk";
|
|
4
|
+
import {
|
|
5
|
+
runCli,
|
|
6
|
+
makeMockSdk,
|
|
7
|
+
setupAuthenticatedConfigStore
|
|
8
|
+
} from "./helpers";
|
|
9
|
+
vi.mock("@chanl/sdk", () => ({
|
|
10
|
+
ChanlSDK: vi.fn()
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../utils/config-store", () => ({
|
|
13
|
+
configStore: {
|
|
14
|
+
getApiKey: vi.fn(),
|
|
15
|
+
getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
|
|
16
|
+
getWorkspaceId: vi.fn(() => "ws_123"),
|
|
17
|
+
getJwtToken: vi.fn(() => null),
|
|
18
|
+
getAuthMethod: vi.fn(() => null),
|
|
19
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
20
|
+
getDeployment: vi.fn(() => "cloud"),
|
|
21
|
+
getApiPrefix: vi.fn(() => "/api/v1"),
|
|
22
|
+
setApiKey: vi.fn(),
|
|
23
|
+
setBaseUrl: vi.fn(),
|
|
24
|
+
setWorkspaceId: vi.fn(),
|
|
25
|
+
removeApiKey: vi.fn(),
|
|
26
|
+
clearAuth: vi.fn(),
|
|
27
|
+
delete: vi.fn(),
|
|
28
|
+
isAuthenticated: vi.fn(),
|
|
29
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
describe("CLI", () => {
|
|
33
|
+
let mockSdk;
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
mockSdk = makeMockSdk();
|
|
37
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
38
|
+
setupAuthenticatedConfigStore(configStore);
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
process.exitCode = void 0;
|
|
42
|
+
});
|
|
43
|
+
describe("auth status", () => {
|
|
44
|
+
it("should show authenticated status", async () => {
|
|
45
|
+
configStore.getApiKey.mockReturnValue("chk_test1234567890");
|
|
46
|
+
configStore.getAuthMethod.mockReturnValue("api-key");
|
|
47
|
+
configStore.getWorkspaceId.mockReturnValue("ws_abc123");
|
|
48
|
+
const { logs, exitCode } = await runCli(["auth", "status"]);
|
|
49
|
+
expect(exitCode).toBe(0);
|
|
50
|
+
const output = logs.join("\n");
|
|
51
|
+
expect(output).toContain("Authenticated");
|
|
52
|
+
});
|
|
53
|
+
it("should show not authenticated when no API key", async () => {
|
|
54
|
+
configStore.getApiKey.mockReturnValue(null);
|
|
55
|
+
configStore.getAuthMethod.mockReturnValue(null);
|
|
56
|
+
const { logs } = await runCli(["auth", "status"]);
|
|
57
|
+
const output = logs.join("\n");
|
|
58
|
+
expect(output).toContain("Not authenticated");
|
|
59
|
+
});
|
|
60
|
+
it("should output JSON when --json flag is used", async () => {
|
|
61
|
+
configStore.getApiKey.mockReturnValue("chk_test1234567890");
|
|
62
|
+
configStore.getAuthMethod.mockReturnValue("api-key");
|
|
63
|
+
configStore.getWorkspaceId.mockReturnValue("ws_abc123");
|
|
64
|
+
const { logs } = await runCli(["--json", "auth", "status"]);
|
|
65
|
+
const output = logs.join("\n");
|
|
66
|
+
const parsed = JSON.parse(output);
|
|
67
|
+
expect(parsed.authenticated).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("logout", () => {
|
|
71
|
+
it("should logout successfully", async () => {
|
|
72
|
+
configStore.isAuthenticated.mockReturnValue(true);
|
|
73
|
+
const { logs, exitCode } = await runCli(["logout"]);
|
|
74
|
+
expect(exitCode).toBe(0);
|
|
75
|
+
expect(configStore.clearAuth).toHaveBeenCalled();
|
|
76
|
+
const output = logs.join("\n");
|
|
77
|
+
expect(output).toContain("Logged out");
|
|
78
|
+
});
|
|
79
|
+
it("should handle already logged out", async () => {
|
|
80
|
+
configStore.isAuthenticated.mockReturnValue(false);
|
|
81
|
+
const { logs } = await runCli(["logout"]);
|
|
82
|
+
const output = logs.join("\n");
|
|
83
|
+
expect(output).toContain("Already logged out");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/auth.test.ts"],"sourcesContent":["/**\n * CLI auth tests — auth status, logout\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';\nimport { configStore } from '../utils/config-store';\nimport { ChanlSDK } from '@chanl/sdk';\nimport {\n runCli,\n makeMockSdk,\n setupAuthenticatedConfigStore,\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('auth status', () => {\n it('should show authenticated status', async () => {\n (configStore.getApiKey as Mock).mockReturnValue('chk_test1234567890');\n (configStore.getAuthMethod as Mock).mockReturnValue('api-key');\n (configStore.getWorkspaceId as Mock).mockReturnValue('ws_abc123');\n\n const { logs, exitCode } = await runCli(['auth', 'status']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n expect(output).toContain('Authenticated');\n });\n\n it('should show not authenticated when no API key', async () => {\n (configStore.getApiKey as Mock).mockReturnValue(null);\n (configStore.getAuthMethod as Mock).mockReturnValue(null);\n\n const { logs } = await runCli(['auth', 'status']);\n\n const output = logs.join('\\n');\n expect(output).toContain('Not authenticated');\n });\n\n it('should output JSON when --json flag is used', async () => {\n (configStore.getApiKey as Mock).mockReturnValue('chk_test1234567890');\n (configStore.getAuthMethod as Mock).mockReturnValue('api-key');\n (configStore.getWorkspaceId as Mock).mockReturnValue('ws_abc123');\n\n const { logs } = await runCli(['--json', 'auth', 'status']);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.authenticated).toBe(true);\n });\n });\n\n describe('logout', () => {\n it('should logout successfully', async () => {\n (configStore.isAuthenticated as Mock).mockReturnValue(true);\n\n const { logs, exitCode } = await runCli(['logout']);\n\n expect(exitCode).toBe(0);\n expect(configStore.clearAuth).toHaveBeenCalled();\n\n const output = logs.join('\\n');\n expect(output).toContain('Logged out');\n });\n\n it('should handle already logged out', async () => {\n (configStore.isAuthenticated as Mock).mockReturnValue(false);\n\n const { logs } = await runCli(['logout']);\n\n const output = logs.join('\\n');\n expect(output).toContain('Already logged out');\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { configStore } from "../utils/config-store";
|
|
3
|
+
import { ChanlSDK } from "@chanl/sdk";
|
|
4
|
+
import {
|
|
5
|
+
runCli,
|
|
6
|
+
makeMockSdk,
|
|
7
|
+
setupAuthenticatedConfigStore
|
|
8
|
+
} from "./helpers";
|
|
9
|
+
vi.mock("@chanl/sdk", () => ({
|
|
10
|
+
ChanlSDK: vi.fn()
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../utils/config-store", () => ({
|
|
13
|
+
configStore: {
|
|
14
|
+
getApiKey: vi.fn(),
|
|
15
|
+
getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
|
|
16
|
+
getWorkspaceId: vi.fn(() => "ws_123"),
|
|
17
|
+
getJwtToken: vi.fn(() => null),
|
|
18
|
+
getAuthMethod: vi.fn(() => null),
|
|
19
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
20
|
+
getDeployment: vi.fn(() => "cloud"),
|
|
21
|
+
getApiPrefix: vi.fn(() => "/api/v1"),
|
|
22
|
+
setApiKey: vi.fn(),
|
|
23
|
+
setBaseUrl: vi.fn(),
|
|
24
|
+
setWorkspaceId: vi.fn(),
|
|
25
|
+
removeApiKey: vi.fn(),
|
|
26
|
+
clearAuth: vi.fn(),
|
|
27
|
+
delete: vi.fn(),
|
|
28
|
+
isAuthenticated: vi.fn(),
|
|
29
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
describe("CLI", () => {
|
|
33
|
+
let mockSdk;
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
mockSdk = makeMockSdk();
|
|
37
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
38
|
+
setupAuthenticatedConfigStore(configStore);
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
process.exitCode = void 0;
|
|
42
|
+
});
|
|
43
|
+
describe("calls transcript with tool calls", () => {
|
|
44
|
+
const mockToolCalls = [
|
|
45
|
+
{
|
|
46
|
+
id: "tc-001",
|
|
47
|
+
name: "get_weather",
|
|
48
|
+
parameters: { city: "London" },
|
|
49
|
+
result: { temperature: "12\xB0C" },
|
|
50
|
+
status: "success",
|
|
51
|
+
durationMs: 245,
|
|
52
|
+
startedAt: "2025-01-09T10:30:00.000Z",
|
|
53
|
+
completedAt: "2025-01-09T10:30:00.245Z"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "tc-002",
|
|
57
|
+
name: "send_email",
|
|
58
|
+
parameters: { to: "user@test.com" },
|
|
59
|
+
status: "error",
|
|
60
|
+
durationMs: 1200,
|
|
61
|
+
startedAt: "2025-01-09T10:30:01.000Z",
|
|
62
|
+
error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
it("should display tool calls in transcript output", async () => {
|
|
66
|
+
mockSdk.calls.getTranscript.mockResolvedValue({
|
|
67
|
+
success: true,
|
|
68
|
+
data: {
|
|
69
|
+
text: "Agent: Hello! Customer: What is the weather?",
|
|
70
|
+
wordCount: 8,
|
|
71
|
+
speakerCount: 2,
|
|
72
|
+
language: "en",
|
|
73
|
+
confidence: 0.95,
|
|
74
|
+
segments: [
|
|
75
|
+
{ speaker: "speaker_0", text: "Hello!", start: 0, end: 1.5 },
|
|
76
|
+
{ speaker: "speaker_1", text: "What is the weather?", start: 2, end: 4 }
|
|
77
|
+
],
|
|
78
|
+
toolCalls: mockToolCalls
|
|
79
|
+
},
|
|
80
|
+
message: "Success",
|
|
81
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
82
|
+
});
|
|
83
|
+
const { logs, exitCode } = await runCli(["calls", "transcript", "call_abc123"]);
|
|
84
|
+
expect(exitCode).toBe(0);
|
|
85
|
+
expect(mockSdk.calls.getTranscript).toHaveBeenCalledWith("call_abc123");
|
|
86
|
+
const output = logs.join("\n");
|
|
87
|
+
expect(output).toContain("Tool Calls");
|
|
88
|
+
expect(output).toContain("get_weather");
|
|
89
|
+
expect(output).toContain("send_email");
|
|
90
|
+
expect(output).toContain("245ms");
|
|
91
|
+
expect(output).toContain("SMTP connection failed");
|
|
92
|
+
});
|
|
93
|
+
it("should include tool calls in JSON transcript output", async () => {
|
|
94
|
+
mockSdk.calls.getTranscript.mockResolvedValue({
|
|
95
|
+
success: true,
|
|
96
|
+
data: {
|
|
97
|
+
text: "Agent: Hello!",
|
|
98
|
+
wordCount: 2,
|
|
99
|
+
toolCalls: mockToolCalls
|
|
100
|
+
},
|
|
101
|
+
message: "Success",
|
|
102
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
103
|
+
});
|
|
104
|
+
const { logs, exitCode } = await runCli(["--json", "calls", "transcript", "call_abc123"]);
|
|
105
|
+
expect(exitCode).toBe(0);
|
|
106
|
+
const output = logs.join("\n");
|
|
107
|
+
const parsed = JSON.parse(output);
|
|
108
|
+
expect(parsed.toolCalls).toBeDefined();
|
|
109
|
+
expect(parsed.toolCalls).toHaveLength(2);
|
|
110
|
+
expect(parsed.toolCalls[0].name).toBe("get_weather");
|
|
111
|
+
expect(parsed.toolCalls[1].name).toBe("send_email");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=calls.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/calls.test.ts"],"sourcesContent":["/**\n * CLI calls tests — calls transcript with tool calls\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';\nimport { configStore } from '../utils/config-store';\nimport { ChanlSDK } from '@chanl/sdk';\nimport {\n runCli,\n makeMockSdk,\n setupAuthenticatedConfigStore,\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('calls transcript with tool calls', () => {\n const mockToolCalls = [\n {\n id: 'tc-001',\n name: 'get_weather',\n parameters: { city: 'London' },\n result: { temperature: '12°C' },\n status: 'success',\n durationMs: 245,\n startedAt: '2025-01-09T10:30:00.000Z',\n completedAt: '2025-01-09T10:30:00.245Z',\n },\n {\n id: 'tc-002',\n name: 'send_email',\n parameters: { to: 'user@test.com' },\n status: 'error',\n durationMs: 1200,\n startedAt: '2025-01-09T10:30:01.000Z',\n error: { message: 'SMTP connection failed', code: 'NETWORK_ERROR' },\n },\n ];\n\n it('should display tool calls in transcript output', async () => {\n mockSdk.calls.getTranscript.mockResolvedValue({\n success: true,\n data: {\n text: 'Agent: Hello! Customer: What is the weather?',\n wordCount: 8,\n speakerCount: 2,\n language: 'en',\n confidence: 0.95,\n segments: [\n { speaker: 'speaker_0', text: 'Hello!', start: 0, end: 1.5 },\n { speaker: 'speaker_1', text: 'What is the weather?', start: 2, end: 4 },\n ],\n toolCalls: mockToolCalls,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['calls', 'transcript', 'call_abc123']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.calls.getTranscript).toHaveBeenCalledWith('call_abc123');\n\n const output = logs.join('\\n');\n // Should display Tool Calls header\n expect(output).toContain('Tool Calls');\n // Should display tool names\n expect(output).toContain('get_weather');\n expect(output).toContain('send_email');\n // Should display duration\n expect(output).toContain('245ms');\n // Should display error message for failed tool call\n expect(output).toContain('SMTP connection failed');\n });\n\n it('should include tool calls in JSON transcript output', async () => {\n mockSdk.calls.getTranscript.mockResolvedValue({\n success: true,\n data: {\n text: 'Agent: Hello!',\n wordCount: 2,\n toolCalls: mockToolCalls,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'calls', 'transcript', 'call_abc123']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.toolCalls).toBeDefined();\n expect(parsed.toolCalls).toHaveLength(2);\n expect(parsed.toolCalls[0].name).toBe('get_weather');\n expect(parsed.toolCalls[1].name).toBe('send_email');\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { createProgram } from "../cli";
|
|
3
|
+
import { configStore } from "../utils/config-store";
|
|
4
|
+
import {
|
|
5
|
+
runCli,
|
|
6
|
+
installFetchMock,
|
|
7
|
+
mockFetchResponse,
|
|
8
|
+
setupFetchConfigStore
|
|
9
|
+
} from "./helpers";
|
|
10
|
+
vi.mock("../utils/config-store", () => ({
|
|
11
|
+
configStore: {
|
|
12
|
+
getApiKey: vi.fn(),
|
|
13
|
+
getBaseUrl: vi.fn(),
|
|
14
|
+
getWorkspaceId: vi.fn(),
|
|
15
|
+
getJwtToken: vi.fn(),
|
|
16
|
+
getAuthMethod: vi.fn(() => null),
|
|
17
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
18
|
+
getDeployment: vi.fn(),
|
|
19
|
+
getApiPrefix: vi.fn(),
|
|
20
|
+
setApiKey: vi.fn(),
|
|
21
|
+
setBaseUrl: vi.fn(),
|
|
22
|
+
setWorkspaceId: vi.fn(),
|
|
23
|
+
removeApiKey: vi.fn(),
|
|
24
|
+
clearAuth: vi.fn(),
|
|
25
|
+
delete: vi.fn(),
|
|
26
|
+
isAuthenticated: vi.fn(),
|
|
27
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
function queueChatFlow(mockFetch, opts) {
|
|
31
|
+
if (opts.createResp !== void 0) {
|
|
32
|
+
mockFetchResponse(mockFetch, opts.createResp);
|
|
33
|
+
}
|
|
34
|
+
if (opts.sendResp !== void 0) {
|
|
35
|
+
mockFetchResponse(mockFetch, opts.sendResp);
|
|
36
|
+
}
|
|
37
|
+
if (opts.endResp !== void 0) {
|
|
38
|
+
mockFetchResponse(mockFetch, opts.endResp);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
describe("CLI", () => {
|
|
42
|
+
let mockFetch;
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
mockFetch = installFetchMock();
|
|
46
|
+
setupFetchConfigStore(configStore);
|
|
47
|
+
});
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
process.exitCode = void 0;
|
|
50
|
+
});
|
|
51
|
+
describe("chat command registration", () => {
|
|
52
|
+
it("should have chat command registered", () => {
|
|
53
|
+
const program = createProgram();
|
|
54
|
+
const commandNames = program.commands.map((c) => c.name());
|
|
55
|
+
expect(commandNames).toContain("chat");
|
|
56
|
+
});
|
|
57
|
+
it("should have the correct command name and description", () => {
|
|
58
|
+
const program = createProgram();
|
|
59
|
+
const chatCmd = program.commands.find((c) => c.name() === "chat");
|
|
60
|
+
expect(chatCmd).toBeDefined();
|
|
61
|
+
expect(chatCmd.description()).toContain("chat");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("chat single message", () => {
|
|
65
|
+
it("should send a single message and display response", async () => {
|
|
66
|
+
queueChatFlow(mockFetch, {
|
|
67
|
+
createResp: {
|
|
68
|
+
success: true,
|
|
69
|
+
data: {
|
|
70
|
+
interactionId: "sess_123",
|
|
71
|
+
sessionId: "sess_123",
|
|
72
|
+
agentId: "agent_abc",
|
|
73
|
+
agentName: "Test Agent",
|
|
74
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
75
|
+
},
|
|
76
|
+
message: "OK",
|
|
77
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
78
|
+
},
|
|
79
|
+
sendResp: {
|
|
80
|
+
success: true,
|
|
81
|
+
data: {
|
|
82
|
+
sessionId: "sess_123",
|
|
83
|
+
message: "Hello! How can I help you?",
|
|
84
|
+
latencyMs: 120
|
|
85
|
+
},
|
|
86
|
+
message: "OK",
|
|
87
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
88
|
+
},
|
|
89
|
+
endResp: {
|
|
90
|
+
success: true,
|
|
91
|
+
data: { sessionId: "sess_123", success: true },
|
|
92
|
+
message: "OK",
|
|
93
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const { logs, exitCode } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
|
|
97
|
+
expect(exitCode).toBe(0);
|
|
98
|
+
expect(mockFetch.mock.calls).toHaveLength(3);
|
|
99
|
+
const createCall = mockFetch.mock.calls[0];
|
|
100
|
+
const sendCall = mockFetch.mock.calls[1];
|
|
101
|
+
const endCall = mockFetch.mock.calls[2];
|
|
102
|
+
expect(createCall[0]).toContain("/api/v1/interactions/chat");
|
|
103
|
+
expect(createCall[0]).toContain("agentId=agent_abc");
|
|
104
|
+
expect(sendCall[0]).toContain("/api/v1/interactions/sess_123/message");
|
|
105
|
+
expect(endCall[0]).toContain("/api/v1/interactions/sess_123/end");
|
|
106
|
+
const sendBody = JSON.parse(sendCall[1].body);
|
|
107
|
+
expect(sendBody).toEqual({ message: "Hello" });
|
|
108
|
+
const output = logs.join("\n");
|
|
109
|
+
expect(output).toContain("Hello");
|
|
110
|
+
expect(output).toContain("How can I help you?");
|
|
111
|
+
});
|
|
112
|
+
it("should output JSON when --json flag is used", async () => {
|
|
113
|
+
queueChatFlow(mockFetch, {
|
|
114
|
+
createResp: {
|
|
115
|
+
success: true,
|
|
116
|
+
data: {
|
|
117
|
+
interactionId: "sess_123",
|
|
118
|
+
sessionId: "sess_123",
|
|
119
|
+
agentId: "agent_abc",
|
|
120
|
+
agentName: "Test Agent",
|
|
121
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
122
|
+
},
|
|
123
|
+
message: "OK",
|
|
124
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
125
|
+
},
|
|
126
|
+
sendResp: {
|
|
127
|
+
success: true,
|
|
128
|
+
data: {
|
|
129
|
+
sessionId: "sess_123",
|
|
130
|
+
message: "Hi there!",
|
|
131
|
+
latencyMs: 95
|
|
132
|
+
},
|
|
133
|
+
message: "OK",
|
|
134
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
135
|
+
},
|
|
136
|
+
endResp: {
|
|
137
|
+
success: true,
|
|
138
|
+
data: { sessionId: "sess_123", success: true },
|
|
139
|
+
message: "OK",
|
|
140
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const { logs, exitCode } = await runCli(["--json", "chat", "agent_abc", "--message", "Hi"]);
|
|
144
|
+
expect(exitCode).toBe(0);
|
|
145
|
+
const output = logs.join("\n");
|
|
146
|
+
const parsed = JSON.parse(output);
|
|
147
|
+
expect(parsed.sessionId).toBe("sess_123");
|
|
148
|
+
expect(parsed.response).toBe("Hi there!");
|
|
149
|
+
expect(parsed.latencyMs).toBe(95);
|
|
150
|
+
});
|
|
151
|
+
it("should handle session creation failure", async () => {
|
|
152
|
+
mockFetch.mockResolvedValueOnce(
|
|
153
|
+
new Response(
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
success: false,
|
|
156
|
+
error: { message: "Agent not found" },
|
|
157
|
+
data: null,
|
|
158
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
159
|
+
}),
|
|
160
|
+
{ status: 404, headers: { "content-type": "application/json" } }
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
const { exitCode, errors } = await runCli(["chat", "bad_agent", "--message", "Hello"]);
|
|
164
|
+
expect(exitCode).toBe(1);
|
|
165
|
+
expect(
|
|
166
|
+
errors.some((e) => e.includes("Agent not found") || e.includes("session"))
|
|
167
|
+
).toBe(true);
|
|
168
|
+
expect(mockFetch.mock.calls).toHaveLength(1);
|
|
169
|
+
});
|
|
170
|
+
it("should handle send message failure", async () => {
|
|
171
|
+
mockFetchResponse(mockFetch, {
|
|
172
|
+
success: true,
|
|
173
|
+
data: {
|
|
174
|
+
interactionId: "sess_123",
|
|
175
|
+
sessionId: "sess_123",
|
|
176
|
+
agentId: "agent_abc",
|
|
177
|
+
agentName: "Test Agent",
|
|
178
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
179
|
+
},
|
|
180
|
+
message: "OK",
|
|
181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
182
|
+
});
|
|
183
|
+
mockFetch.mockResolvedValueOnce(
|
|
184
|
+
new Response(
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
success: false,
|
|
187
|
+
error: { message: "No response from agent" },
|
|
188
|
+
data: null,
|
|
189
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
190
|
+
}),
|
|
191
|
+
{ status: 500, headers: { "content-type": "application/json" } }
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
mockFetchResponse(mockFetch, {
|
|
195
|
+
success: true,
|
|
196
|
+
data: { sessionId: "sess_123", success: true },
|
|
197
|
+
message: "OK",
|
|
198
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
199
|
+
});
|
|
200
|
+
const { exitCode, errors } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
|
|
201
|
+
expect(exitCode).toBe(1);
|
|
202
|
+
expect(
|
|
203
|
+
errors.some((e) => e.includes("No response") || e.includes("Failed"))
|
|
204
|
+
).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
it("should handle not authenticated", async () => {
|
|
207
|
+
configStore.getApiKey.mockReturnValue(null);
|
|
208
|
+
configStore.getJwtToken.mockReturnValue(null);
|
|
209
|
+
const { exitCode, errors } = await runCli(["chat", "agent_abc", "--message", "Hello"]);
|
|
210
|
+
expect(exitCode).toBe(1);
|
|
211
|
+
expect(errors.some((e) => e.includes("Not authenticated"))).toBe(true);
|
|
212
|
+
expect(mockFetch.mock.calls).toHaveLength(0);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe("chat single message with tool calls", () => {
|
|
216
|
+
const mockToolCalls = [
|
|
217
|
+
{
|
|
218
|
+
id: "tc-001",
|
|
219
|
+
name: "get_weather",
|
|
220
|
+
parameters: { city: "London" },
|
|
221
|
+
result: { temperature: "12\xB0C" },
|
|
222
|
+
status: "success",
|
|
223
|
+
durationMs: 245,
|
|
224
|
+
startedAt: "2025-01-09T10:30:00.000Z",
|
|
225
|
+
completedAt: "2025-01-09T10:30:00.245Z"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "tc-002",
|
|
229
|
+
name: "send_email",
|
|
230
|
+
parameters: { to: "user@test.com" },
|
|
231
|
+
status: "error",
|
|
232
|
+
durationMs: 1200,
|
|
233
|
+
startedAt: "2025-01-09T10:30:01.000Z",
|
|
234
|
+
error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
|
|
235
|
+
}
|
|
236
|
+
];
|
|
237
|
+
it("should display tool calls in chat response", async () => {
|
|
238
|
+
queueChatFlow(mockFetch, {
|
|
239
|
+
createResp: {
|
|
240
|
+
success: true,
|
|
241
|
+
data: {
|
|
242
|
+
interactionId: "sess_tc1",
|
|
243
|
+
sessionId: "sess_tc1",
|
|
244
|
+
agentId: "agent_abc",
|
|
245
|
+
agentName: "Test Agent",
|
|
246
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
247
|
+
},
|
|
248
|
+
message: "OK",
|
|
249
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
250
|
+
},
|
|
251
|
+
sendResp: {
|
|
252
|
+
success: true,
|
|
253
|
+
data: {
|
|
254
|
+
sessionId: "sess_tc1",
|
|
255
|
+
message: "The weather in London is 12\xB0C.",
|
|
256
|
+
latencyMs: 350,
|
|
257
|
+
toolCalls: mockToolCalls
|
|
258
|
+
},
|
|
259
|
+
message: "OK",
|
|
260
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
261
|
+
},
|
|
262
|
+
endResp: {
|
|
263
|
+
success: true,
|
|
264
|
+
data: { sessionId: "sess_tc1", success: true },
|
|
265
|
+
message: "OK",
|
|
266
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const { logs, exitCode } = await runCli([
|
|
270
|
+
"chat",
|
|
271
|
+
"agent_abc",
|
|
272
|
+
"--message",
|
|
273
|
+
"What is the weather in London?"
|
|
274
|
+
]);
|
|
275
|
+
expect(exitCode).toBe(0);
|
|
276
|
+
const output = logs.join("\n");
|
|
277
|
+
expect(output).toContain("get_weather");
|
|
278
|
+
expect(output).toContain("245ms");
|
|
279
|
+
expect(output).toContain("send_email");
|
|
280
|
+
expect(output).toContain("SMTP connection failed");
|
|
281
|
+
expect(output).toContain("The weather in London is 12\xB0C.");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
describe("chat single message with tool calls JSON output", () => {
|
|
285
|
+
const mockToolCalls = [
|
|
286
|
+
{
|
|
287
|
+
id: "tc-001",
|
|
288
|
+
name: "get_weather",
|
|
289
|
+
parameters: { city: "London" },
|
|
290
|
+
result: { temperature: "12\xB0C" },
|
|
291
|
+
status: "success",
|
|
292
|
+
durationMs: 245,
|
|
293
|
+
startedAt: "2025-01-09T10:30:00.000Z",
|
|
294
|
+
completedAt: "2025-01-09T10:30:00.245Z"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: "tc-002",
|
|
298
|
+
name: "send_email",
|
|
299
|
+
parameters: { to: "user@test.com" },
|
|
300
|
+
status: "error",
|
|
301
|
+
durationMs: 1200,
|
|
302
|
+
startedAt: "2025-01-09T10:30:01.000Z",
|
|
303
|
+
error: { message: "SMTP connection failed", code: "NETWORK_ERROR" }
|
|
304
|
+
}
|
|
305
|
+
];
|
|
306
|
+
it("should include toolCalls in JSON output", async () => {
|
|
307
|
+
queueChatFlow(mockFetch, {
|
|
308
|
+
createResp: {
|
|
309
|
+
success: true,
|
|
310
|
+
data: {
|
|
311
|
+
interactionId: "sess_tc2",
|
|
312
|
+
sessionId: "sess_tc2",
|
|
313
|
+
agentId: "agent_abc",
|
|
314
|
+
agentName: "Test Agent",
|
|
315
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
316
|
+
},
|
|
317
|
+
message: "OK",
|
|
318
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
319
|
+
},
|
|
320
|
+
sendResp: {
|
|
321
|
+
success: true,
|
|
322
|
+
data: {
|
|
323
|
+
sessionId: "sess_tc2",
|
|
324
|
+
message: "The weather in London is 12\xB0C.",
|
|
325
|
+
latencyMs: 350,
|
|
326
|
+
toolCalls: mockToolCalls
|
|
327
|
+
},
|
|
328
|
+
message: "OK",
|
|
329
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
330
|
+
},
|
|
331
|
+
endResp: {
|
|
332
|
+
success: true,
|
|
333
|
+
data: { sessionId: "sess_tc2", success: true },
|
|
334
|
+
message: "OK",
|
|
335
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
const { logs, exitCode } = await runCli([
|
|
339
|
+
"--json",
|
|
340
|
+
"chat",
|
|
341
|
+
"agent_abc",
|
|
342
|
+
"--message",
|
|
343
|
+
"What is the weather?"
|
|
344
|
+
]);
|
|
345
|
+
expect(exitCode).toBe(0);
|
|
346
|
+
const output = logs.join("\n");
|
|
347
|
+
const parsed = JSON.parse(output);
|
|
348
|
+
expect(parsed.toolCalls).toBeDefined();
|
|
349
|
+
expect(parsed.toolCalls).toHaveLength(2);
|
|
350
|
+
expect(parsed.toolCalls[0].name).toBe("get_weather");
|
|
351
|
+
expect(parsed.toolCalls[0].status).toBe("success");
|
|
352
|
+
expect(parsed.toolCalls[1].name).toBe("send_email");
|
|
353
|
+
expect(parsed.toolCalls[1].status).toBe("error");
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
//# sourceMappingURL=chat.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/chat.test.ts"],"sourcesContent":["/**\n * CLI chat tests — fetch-level mocking\n *\n * `chanl chat <agent> -m <text>` kicks off a 3-call flow:\n * 1. POST /api/v1/interactions/chat?agentId=X (createSession)\n * 2. POST /api/v1/interactions/:id/message (sendMessage)\n * 3. POST /api/v1/interactions/:id/end (endSession)\n *\n * Each test queues those three responses in order. This exercises the\n * real LiveChat wrapper + real ChatModule + real SDK request pipeline.\n *\n * Command registration tests stay non-networked — they just introspect\n * `createProgram()`.\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest';\nimport { createProgram } from '../cli';\nimport { configStore } from '../utils/config-store';\nimport {\n runCli,\n installFetchMock,\n mockFetchResponse,\n setupFetchConfigStore,\n type FetchConfigStoreMock,\n} from './helpers';\n\n// Real SDK runs — DO NOT mock '@chanl/sdk'.\nvi.mock('../utils/config-store', () => ({\n configStore: {\n getApiKey: vi.fn(),\n getBaseUrl: vi.fn(),\n getWorkspaceId: vi.fn(),\n getJwtToken: vi.fn(),\n getAuthMethod: vi.fn(() => null),\n getAppUrl: vi.fn(() => 'https://app.chanl.ai'),\n getDeployment: vi.fn(),\n getApiPrefix: vi.fn(),\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\n// Shared helper: queue the full 3-call chat flow.\n// createResp = session creation body (or null to skip queuing)\n// sendResp = sendMessage body\n// endResp = endSession body (or null to skip)\nfunction queueChatFlow(\n mockFetch: Mock,\n opts: {\n createResp?: unknown;\n sendResp?: unknown;\n endResp?: unknown;\n },\n) {\n if (opts.createResp !== undefined) {\n mockFetchResponse(mockFetch, opts.createResp);\n }\n if (opts.sendResp !== undefined) {\n mockFetchResponse(mockFetch, opts.sendResp);\n }\n if (opts.endResp !== undefined) {\n mockFetchResponse(mockFetch, opts.endResp);\n }\n}\n\ndescribe('CLI', () => {\n let mockFetch: Mock;\n\n beforeEach(() => {\n vi.clearAllMocks();\n mockFetch = installFetchMock();\n setupFetchConfigStore(configStore as unknown as FetchConfigStoreMock);\n });\n\n afterEach(() => {\n process.exitCode = undefined;\n });\n\n // ─────────────────────────────────────────────────────────────────\n // Command registration — no network, no real SDK\n // ─────────────────────────────────────────────────────────────────\n\n describe('chat command registration', () => {\n it('should have chat command registered', () => {\n const program = createProgram();\n const commandNames = program.commands.map((c) => c.name());\n\n expect(commandNames).toContain('chat');\n });\n\n it('should have the correct command name and description', () => {\n const program = createProgram();\n const chatCmd = program.commands.find((c) => c.name() === 'chat');\n\n expect(chatCmd).toBeDefined();\n expect(chatCmd!.description()).toContain('chat');\n });\n });\n\n // ─────────────────────────────────────────────────────────────────\n // Single message flow — exercises the full create → send → end path\n // ─────────────────────────────────────────────────────────────────\n\n describe('chat single message', () => {\n it('should send a single message and display response', async () => {\n queueChatFlow(mockFetch, {\n createResp: {\n success: true,\n data: {\n interactionId: 'sess_123',\n sessionId: 'sess_123',\n agentId: 'agent_abc',\n agentName: 'Test Agent',\n createdAt: new Date().toISOString(),\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n sendResp: {\n success: true,\n data: {\n sessionId: 'sess_123',\n message: 'Hello! How can I help you?',\n latencyMs: 120,\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n endResp: {\n success: true,\n data: { sessionId: 'sess_123', success: true },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n });\n\n const { logs, exitCode } = await runCli(['chat', 'agent_abc', '--message', 'Hello']);\n\n expect(exitCode).toBe(0);\n\n // All three endpoints were hit in order\n expect(mockFetch.mock.calls).toHaveLength(3);\n const createCall = mockFetch.mock.calls[0] as [string, RequestInit];\n const sendCall = mockFetch.mock.calls[1] as [string, RequestInit];\n const endCall = mockFetch.mock.calls[2] as [string, RequestInit];\n\n expect(createCall[0]).toContain('/api/v1/interactions/chat');\n expect(createCall[0]).toContain('agentId=agent_abc');\n expect(sendCall[0]).toContain('/api/v1/interactions/sess_123/message');\n expect(endCall[0]).toContain('/api/v1/interactions/sess_123/end');\n\n // The send body was the user's message\n const sendBody = JSON.parse(sendCall[1].body as string);\n expect(sendBody).toEqual({ message: 'Hello' });\n\n const output = logs.join('\\n');\n expect(output).toContain('Hello');\n expect(output).toContain('How can I help you?');\n });\n\n it('should output JSON when --json flag is used', async () => {\n queueChatFlow(mockFetch, {\n createResp: {\n success: true,\n data: {\n interactionId: 'sess_123',\n sessionId: 'sess_123',\n agentId: 'agent_abc',\n agentName: 'Test Agent',\n createdAt: new Date().toISOString(),\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n sendResp: {\n success: true,\n data: {\n sessionId: 'sess_123',\n message: 'Hi there!',\n latencyMs: 95,\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n endResp: {\n success: true,\n data: { sessionId: 'sess_123', success: true },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n });\n\n const { logs, exitCode } = await runCli(['--json', 'chat', 'agent_abc', '--message', 'Hi']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.sessionId).toBe('sess_123');\n expect(parsed.response).toBe('Hi there!');\n expect(parsed.latencyMs).toBe(95);\n });\n\n it('should handle session creation failure', async () => {\n // createSession fails — the CLI exits before send/end are called.\n mockFetch.mockResolvedValueOnce(\n new Response(\n JSON.stringify({\n success: false,\n error: { message: 'Agent not found' },\n data: null,\n timestamp: new Date().toISOString(),\n }),\n { status: 404, headers: { 'content-type': 'application/json' } },\n ),\n );\n\n const { exitCode, errors } = await runCli(['chat', 'bad_agent', '--message', 'Hello']);\n\n expect(exitCode).toBe(1);\n expect(\n errors.some((e) => e.includes('Agent not found') || e.includes('session')),\n ).toBe(true);\n // Send + end should NOT have been called\n expect(mockFetch.mock.calls).toHaveLength(1);\n });\n\n it('should handle send message failure', async () => {\n // Session creates OK, but sendMessage fails.\n mockFetchResponse(mockFetch, {\n success: true,\n data: {\n interactionId: 'sess_123',\n sessionId: 'sess_123',\n agentId: 'agent_abc',\n agentName: 'Test Agent',\n createdAt: new Date().toISOString(),\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n });\n mockFetch.mockResolvedValueOnce(\n new Response(\n JSON.stringify({\n success: false,\n error: { message: 'No response from agent' },\n data: null,\n timestamp: new Date().toISOString(),\n }),\n { status: 500, headers: { 'content-type': 'application/json' } },\n ),\n );\n // The CLI may try to end the session after the send failure — queue\n // a success so cleanup doesn't crash. Even if it doesn't call end,\n // the extra queued response is harmless.\n mockFetchResponse(mockFetch, {\n success: true,\n data: { sessionId: 'sess_123', success: true },\n message: 'OK',\n timestamp: new Date().toISOString(),\n });\n\n const { exitCode, errors } = await runCli(['chat', 'agent_abc', '--message', 'Hello']);\n\n expect(exitCode).toBe(1);\n expect(\n errors.some((e) => e.includes('No response') || e.includes('Failed')),\n ).toBe(true);\n });\n\n it('should handle not authenticated', async () => {\n (configStore.getApiKey as Mock).mockReturnValue(null);\n (configStore.getJwtToken as Mock).mockReturnValue(null);\n\n const { exitCode, errors } = await runCli(['chat', 'agent_abc', '--message', 'Hello']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Not authenticated'))).toBe(true);\n // No HTTP call should have been made\n expect(mockFetch.mock.calls).toHaveLength(0);\n });\n });\n\n // ─────────────────────────────────────────────────────────────────\n // Tool calls in the chat response\n // ─────────────────────────────────────────────────────────────────\n\n describe('chat single message with tool calls', () => {\n const mockToolCalls = [\n {\n id: 'tc-001',\n name: 'get_weather',\n parameters: { city: 'London' },\n result: { temperature: '12°C' },\n status: 'success',\n durationMs: 245,\n startedAt: '2025-01-09T10:30:00.000Z',\n completedAt: '2025-01-09T10:30:00.245Z',\n },\n {\n id: 'tc-002',\n name: 'send_email',\n parameters: { to: 'user@test.com' },\n status: 'error',\n durationMs: 1200,\n startedAt: '2025-01-09T10:30:01.000Z',\n error: { message: 'SMTP connection failed', code: 'NETWORK_ERROR' },\n },\n ];\n\n it('should display tool calls in chat response', async () => {\n queueChatFlow(mockFetch, {\n createResp: {\n success: true,\n data: {\n interactionId: 'sess_tc1',\n sessionId: 'sess_tc1',\n agentId: 'agent_abc',\n agentName: 'Test Agent',\n createdAt: new Date().toISOString(),\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n sendResp: {\n success: true,\n data: {\n sessionId: 'sess_tc1',\n message: 'The weather in London is 12°C.',\n latencyMs: 350,\n toolCalls: mockToolCalls,\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n endResp: {\n success: true,\n data: { sessionId: 'sess_tc1', success: true },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n });\n\n const { logs, exitCode } = await runCli([\n 'chat',\n 'agent_abc',\n '--message',\n 'What is the weather in London?',\n ]);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n expect(output).toContain('get_weather');\n expect(output).toContain('245ms');\n expect(output).toContain('send_email');\n expect(output).toContain('SMTP connection failed');\n expect(output).toContain('The weather in London is 12°C.');\n });\n });\n\n describe('chat single message with tool calls JSON output', () => {\n const mockToolCalls = [\n {\n id: 'tc-001',\n name: 'get_weather',\n parameters: { city: 'London' },\n result: { temperature: '12°C' },\n status: 'success',\n durationMs: 245,\n startedAt: '2025-01-09T10:30:00.000Z',\n completedAt: '2025-01-09T10:30:00.245Z',\n },\n {\n id: 'tc-002',\n name: 'send_email',\n parameters: { to: 'user@test.com' },\n status: 'error',\n durationMs: 1200,\n startedAt: '2025-01-09T10:30:01.000Z',\n error: { message: 'SMTP connection failed', code: 'NETWORK_ERROR' },\n },\n ];\n\n it('should include toolCalls in JSON output', async () => {\n queueChatFlow(mockFetch, {\n createResp: {\n success: true,\n data: {\n interactionId: 'sess_tc2',\n sessionId: 'sess_tc2',\n agentId: 'agent_abc',\n agentName: 'Test Agent',\n createdAt: new Date().toISOString(),\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n sendResp: {\n success: true,\n data: {\n sessionId: 'sess_tc2',\n message: 'The weather in London is 12°C.',\n latencyMs: 350,\n toolCalls: mockToolCalls,\n },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n endResp: {\n success: true,\n data: { sessionId: 'sess_tc2', success: true },\n message: 'OK',\n timestamp: new Date().toISOString(),\n },\n });\n\n const { logs, exitCode } = await runCli([\n '--json',\n 'chat',\n 'agent_abc',\n '--message',\n 'What is the weather?',\n ]);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.toolCalls).toBeDefined();\n expect(parsed.toolCalls).toHaveLength(2);\n expect(parsed.toolCalls[0].name).toBe('get_weather');\n expect(parsed.toolCalls[0].status).toBe('success');\n expect(parsed.toolCalls[1].name).toBe('send_email');\n expect(parsed.toolCalls[1].status).toBe('error');\n });\n });\n});\n"],"mappings":"AAeA,SAAS,UAAU,IAAI,QAAQ,YAAY,WAAW,UAAqB;AAC3E,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGP,GAAG,KAAK,yBAAyB,OAAO;AAAA,EACtC,aAAa;AAAA,IACX,WAAW,GAAG,GAAG;AAAA,IACjB,YAAY,GAAG,GAAG;AAAA,IAClB,gBAAgB,GAAG,GAAG;AAAA,IACtB,aAAa,GAAG,GAAG;AAAA,IACnB,eAAe,GAAG,GAAG,MAAM,IAAI;AAAA,IAC/B,WAAW,GAAG,GAAG,MAAM,sBAAsB;AAAA,IAC7C,eAAe,GAAG,GAAG;AAAA,IACrB,cAAc,GAAG,GAAG;AAAA,IACpB,WAAW,GAAG,GAAG;AAAA,IACjB,YAAY,GAAG,GAAG;AAAA,IAClB,gBAAgB,GAAG,GAAG;AAAA,IACtB,cAAc,GAAG,GAAG;AAAA,IACpB,WAAW,GAAG,GAAG;AAAA,IACjB,QAAQ,GAAG,GAAG;AAAA,IACd,iBAAiB,GAAG,GAAG;AAAA,IACvB,SAAS,GAAG,GAAG,MAAM,+BAA+B;AAAA,EACtD;AACF,EAAE;AAMF,SAAS,cACP,WACA,MAKA;AACA,MAAI,KAAK,eAAe,QAAW;AACjC,sBAAkB,WAAW,KAAK,UAAU;AAAA,EAC9C;AACA,MAAI,KAAK,aAAa,QAAW;AAC/B,sBAAkB,WAAW,KAAK,QAAQ;AAAA,EAC5C;AACA,MAAI,KAAK,YAAY,QAAW;AAC9B,sBAAkB,WAAW,KAAK,OAAO;AAAA,EAC3C;AACF;AAEA,SAAS,OAAO,MAAM;AACpB,MAAI;AAEJ,aAAW,MAAM;AACf,OAAG,cAAc;AACjB,gBAAY,iBAAiB;AAC7B,0BAAsB,WAA8C;AAAA,EACtE,CAAC;AAED,YAAU,MAAM;AACd,YAAQ,WAAW;AAAA,EACrB,CAAC;AAMD,WAAS,6BAA6B,MAAM;AAC1C,OAAG,uCAAuC,MAAM;AAC9C,YAAM,UAAU,cAAc;AAC9B,YAAM,eAAe,QAAQ,SAAS,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAEzD,aAAO,YAAY,EAAE,UAAU,MAAM;AAAA,IACvC,CAAC;AAED,OAAG,wDAAwD,MAAM;AAC/D,YAAM,UAAU,cAAc;AAC9B,YAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,MAAM;AAEhE,aAAO,OAAO,EAAE,YAAY;AAC5B,aAAO,QAAS,YAAY,CAAC,EAAE,UAAU,MAAM;AAAA,IACjD,CAAC;AAAA,EACH,CAAC;AAMD,WAAS,uBAAuB,MAAM;AACpC,OAAG,qDAAqD,YAAY;AAClE,oBAAc,WAAW;AAAA,QACvB,YAAY;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,eAAe;AAAA,YACf,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,UAAU;AAAA,UACR,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,UACb;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,SAAS;AAAA,UACP,SAAS;AAAA,UACT,MAAM,EAAE,WAAW,YAAY,SAAS,KAAK;AAAA,UAC7C,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,MACF,CAAC;AAED,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,OAAO,CAAC,QAAQ,aAAa,aAAa,OAAO,CAAC;AAEnF,aAAO,QAAQ,EAAE,KAAK,CAAC;AAGvB,aAAO,UAAU,KAAK,KAAK,EAAE,aAAa,CAAC;AAC3C,YAAM,aAAa,UAAU,KAAK,MAAM,CAAC;AACzC,YAAM,WAAW,UAAU,KAAK,MAAM,CAAC;AACvC,YAAM,UAAU,UAAU,KAAK,MAAM,CAAC;AAEtC,aAAO,WAAW,CAAC,CAAC,EAAE,UAAU,2BAA2B;AAC3D,aAAO,WAAW,CAAC,CAAC,EAAE,UAAU,mBAAmB;AACnD,aAAO,SAAS,CAAC,CAAC,EAAE,UAAU,uCAAuC;AACrE,aAAO,QAAQ,CAAC,CAAC,EAAE,UAAU,mCAAmC;AAGhE,YAAM,WAAW,KAAK,MAAM,SAAS,CAAC,EAAE,IAAc;AACtD,aAAO,QAAQ,EAAE,QAAQ,EAAE,SAAS,QAAQ,CAAC;AAE7C,YAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,aAAO,MAAM,EAAE,UAAU,OAAO;AAChC,aAAO,MAAM,EAAE,UAAU,qBAAqB;AAAA,IAChD,CAAC;AAED,OAAG,+CAA+C,YAAY;AAC5D,oBAAc,WAAW;AAAA,QACvB,YAAY;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,eAAe;AAAA,YACf,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,UAAU;AAAA,UACR,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,UACb;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,SAAS;AAAA,UACP,SAAS;AAAA,UACT,MAAM,EAAE,WAAW,YAAY,SAAS,KAAK;AAAA,UAC7C,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,MACF,CAAC;AAED,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,OAAO,CAAC,UAAU,QAAQ,aAAa,aAAa,IAAI,CAAC;AAE1F,aAAO,QAAQ,EAAE,KAAK,CAAC;AAEvB,YAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,YAAM,SAAS,KAAK,MAAM,MAAM;AAChC,aAAO,OAAO,SAAS,EAAE,KAAK,UAAU;AACxC,aAAO,OAAO,QAAQ,EAAE,KAAK,WAAW;AACxC,aAAO,OAAO,SAAS,EAAE,KAAK,EAAE;AAAA,IAClC,CAAC;AAED,OAAG,0CAA0C,YAAY;AAEvD,gBAAU;AAAA,QACR,IAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb,SAAS;AAAA,YACT,OAAO,EAAE,SAAS,kBAAkB;AAAA,YACpC,MAAM;AAAA,YACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AAAA,UACD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,QACjE;AAAA,MACF;AAEA,YAAM,EAAE,UAAU,OAAO,IAAI,MAAM,OAAO,CAAC,QAAQ,aAAa,aAAa,OAAO,CAAC;AAErF,aAAO,QAAQ,EAAE,KAAK,CAAC;AACvB;AAAA,QACE,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,iBAAiB,KAAK,EAAE,SAAS,SAAS,CAAC;AAAA,MAC3E,EAAE,KAAK,IAAI;AAEX,aAAO,UAAU,KAAK,KAAK,EAAE,aAAa,CAAC;AAAA,IAC7C,CAAC;AAED,OAAG,sCAAsC,YAAY;AAEnD,wBAAkB,WAAW;AAAA,QAC3B,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,eAAe;AAAA,UACf,WAAW;AAAA,UACX,SAAS;AAAA,UACT,WAAW;AAAA,UACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AACD,gBAAU;AAAA,QACR,IAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb,SAAS;AAAA,YACT,OAAO,EAAE,SAAS,yBAAyB;AAAA,YAC3C,MAAM;AAAA,YACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AAAA,UACD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,QACjE;AAAA,MACF;AAIA,wBAAkB,WAAW;AAAA,QAC3B,SAAS;AAAA,QACT,MAAM,EAAE,WAAW,YAAY,SAAS,KAAK;AAAA,QAC7C,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AAED,YAAM,EAAE,UAAU,OAAO,IAAI,MAAM,OAAO,CAAC,QAAQ,aAAa,aAAa,OAAO,CAAC;AAErF,aAAO,QAAQ,EAAE,KAAK,CAAC;AACvB;AAAA,QACE,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,aAAa,KAAK,EAAE,SAAS,QAAQ,CAAC;AAAA,MACtE,EAAE,KAAK,IAAI;AAAA,IACb,CAAC;AAED,OAAG,mCAAmC,YAAY;AAChD,MAAC,YAAY,UAAmB,gBAAgB,IAAI;AACpD,MAAC,YAAY,YAAqB,gBAAgB,IAAI;AAEtD,YAAM,EAAE,UAAU,OAAO,IAAI,MAAM,OAAO,CAAC,QAAQ,aAAa,aAAa,OAAO,CAAC;AAErF,aAAO,QAAQ,EAAE,KAAK,CAAC;AACvB,aAAO,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,mBAAmB,CAAC,CAAC,EAAE,KAAK,IAAI;AAErE,aAAO,UAAU,KAAK,KAAK,EAAE,aAAa,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH,CAAC;AAMD,WAAS,uCAAuC,MAAM;AACpD,UAAM,gBAAgB;AAAA,MACpB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY,EAAE,MAAM,SAAS;AAAA,QAC7B,QAAQ,EAAE,aAAa,UAAO;AAAA,QAC9B,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY,EAAE,IAAI,gBAAgB;AAAA,QAClC,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,OAAO,EAAE,SAAS,0BAA0B,MAAM,gBAAgB;AAAA,MACpE;AAAA,IACF;AAEA,OAAG,8CAA8C,YAAY;AAC3D,oBAAc,WAAW;AAAA,QACvB,YAAY;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,eAAe;AAAA,YACf,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,UAAU;AAAA,UACR,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,WAAW;AAAA,UACb;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,SAAS;AAAA,UACP,SAAS;AAAA,UACT,MAAM,EAAE,WAAW,YAAY,SAAS,KAAK;AAAA,UAC7C,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,MACF,CAAC;AAED,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,OAAO;AAAA,QACtC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO,QAAQ,EAAE,KAAK,CAAC;AAEvB,YAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,aAAO,MAAM,EAAE,UAAU,aAAa;AACtC,aAAO,MAAM,EAAE,UAAU,OAAO;AAChC,aAAO,MAAM,EAAE,UAAU,YAAY;AACrC,aAAO,MAAM,EAAE,UAAU,wBAAwB;AACjD,aAAO,MAAM,EAAE,UAAU,mCAAgC;AAAA,IAC3D,CAAC;AAAA,EACH,CAAC;AAED,WAAS,mDAAmD,MAAM;AAChE,UAAM,gBAAgB;AAAA,MACpB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY,EAAE,MAAM,SAAS;AAAA,QAC7B,QAAQ,EAAE,aAAa,UAAO;AAAA,QAC9B,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY,EAAE,IAAI,gBAAgB;AAAA,QAClC,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,OAAO,EAAE,SAAS,0BAA0B,MAAM,gBAAgB;AAAA,MACpE;AAAA,IACF;AAEA,OAAG,2CAA2C,YAAY;AACxD,oBAAc,WAAW;AAAA,QACvB,YAAY;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,eAAe;AAAA,YACf,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,UAAU;AAAA,UACR,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,WAAW;AAAA,YACX,SAAS;AAAA,YACT,WAAW;AAAA,YACX,WAAW;AAAA,UACb;AAAA,UACA,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,QACA,SAAS;AAAA,UACP,SAAS;AAAA,UACT,MAAM,EAAE,WAAW,YAAY,SAAS,KAAK;AAAA,UAC7C,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAAA,MACF,CAAC;AAED,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,OAAO;AAAA,QACtC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO,QAAQ,EAAE,KAAK,CAAC;AAEvB,YAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,YAAM,SAAS,KAAK,MAAM,MAAM;AAChC,aAAO,OAAO,SAAS,EAAE,YAAY;AACrC,aAAO,OAAO,SAAS,EAAE,aAAa,CAAC;AACvC,aAAO,OAAO,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,aAAa;AACnD,aAAO,OAAO,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAS;AACjD,aAAO,OAAO,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,YAAY;AAClD,aAAO,OAAO,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,OAAO;AAAA,IACjD,CAAC;AAAA,EACH,CAAC;AACH,CAAC;","names":[]}
|