@chanl/cli 2.0.4 → 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 +96 -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/memory.js +134 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/prompts.js +148 -1
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/scorecards.js +3 -3
- package/dist/commands/scorecards.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 -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,87 @@
|
|
|
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
|
+
} from "./helpers";
|
|
10
|
+
vi.mock("@chanl/sdk", () => ({
|
|
11
|
+
ChanlSDK: vi.fn()
|
|
12
|
+
}));
|
|
13
|
+
vi.mock("../utils/config-store", () => ({
|
|
14
|
+
configStore: {
|
|
15
|
+
getApiKey: vi.fn(),
|
|
16
|
+
getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
|
|
17
|
+
getWorkspaceId: vi.fn(() => "ws_123"),
|
|
18
|
+
getJwtToken: vi.fn(() => null),
|
|
19
|
+
getAuthMethod: vi.fn(() => null),
|
|
20
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
21
|
+
getDeployment: vi.fn(() => "cloud"),
|
|
22
|
+
getApiPrefix: vi.fn(() => "/api/v1"),
|
|
23
|
+
setApiKey: vi.fn(),
|
|
24
|
+
setBaseUrl: vi.fn(),
|
|
25
|
+
setWorkspaceId: vi.fn(),
|
|
26
|
+
removeApiKey: vi.fn(),
|
|
27
|
+
clearAuth: vi.fn(),
|
|
28
|
+
delete: vi.fn(),
|
|
29
|
+
isAuthenticated: vi.fn(),
|
|
30
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
describe("CLI", () => {
|
|
34
|
+
let mockSdk;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
mockSdk = makeMockSdk();
|
|
38
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
39
|
+
setupAuthenticatedConfigStore(configStore);
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
process.exitCode = void 0;
|
|
43
|
+
});
|
|
44
|
+
describe("--help", () => {
|
|
45
|
+
it("should have all expected commands in program", () => {
|
|
46
|
+
const program = createProgram();
|
|
47
|
+
const commandNames = program.commands.map((c) => c.name());
|
|
48
|
+
expect(commandNames).toContain("login");
|
|
49
|
+
expect(commandNames).toContain("logout");
|
|
50
|
+
expect(commandNames).toContain("auth");
|
|
51
|
+
expect(commandNames).toContain("config");
|
|
52
|
+
expect(commandNames).toContain("tools");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("--version", () => {
|
|
56
|
+
it("should have correct version in program", () => {
|
|
57
|
+
const program = createProgram();
|
|
58
|
+
expect(program.version()).toMatch(/^\d+\.\d+\.\d+$/);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("error handling", () => {
|
|
62
|
+
it("should show error when not authenticated for tools commands", async () => {
|
|
63
|
+
configStore.getApiKey.mockReturnValue(null);
|
|
64
|
+
const { exitCode, errors } = await runCli(["tools", "list"]);
|
|
65
|
+
expect(exitCode).toBe(1);
|
|
66
|
+
expect(errors.some((e) => e.includes("Not authenticated"))).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("should handle API errors gracefully", async () => {
|
|
69
|
+
mockSdk.tools.list.mockRejectedValue(new Error("Network error"));
|
|
70
|
+
const { exitCode, errors } = await runCli(["tools", "list"]);
|
|
71
|
+
expect(exitCode).toBe(1);
|
|
72
|
+
expect(errors.some((e) => e.includes("Network error"))).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("should handle unsuccessful API responses", async () => {
|
|
75
|
+
mockSdk.tools.list.mockResolvedValue({
|
|
76
|
+
success: false,
|
|
77
|
+
data: null,
|
|
78
|
+
message: "Unauthorized",
|
|
79
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
80
|
+
});
|
|
81
|
+
const { exitCode, errors } = await runCli(["tools", "list"]);
|
|
82
|
+
expect(exitCode).toBe(1);
|
|
83
|
+
expect(errors.some((e) => e.includes("Unauthorized") || e.includes("Failed"))).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=cli-base.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/cli-base.test.ts"],"sourcesContent":["/**\n * CLI base tests — --help, --version, error handling\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 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 // Reset mocks\n vi.clearAllMocks();\n\n // Setup mock SDK\n mockSdk = makeMockSdk();\n\n (ChanlSDK as Mock).mockImplementation(() => mockSdk);\n\n // Default: authenticated with workspace\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('--help', () => {\n it('should have all expected commands in program', () => {\n // Test program structure instead of output since Commander uses stdout.write\n const program = createProgram();\n const commandNames = program.commands.map((c) => c.name());\n\n expect(commandNames).toContain('login');\n expect(commandNames).toContain('logout');\n expect(commandNames).toContain('auth');\n expect(commandNames).toContain('config');\n expect(commandNames).toContain('tools');\n });\n });\n\n describe('--version', () => {\n it('should have correct version in program', () => {\n // Test program version instead of output since Commander uses stdout.write\n const program = createProgram();\n // Version is dynamically read from package.json\n expect(program.version()).toMatch(/^\\d+\\.\\d+\\.\\d+$/);\n });\n });\n\n describe('error handling', () => {\n it('should show error when not authenticated for tools commands', async () => {\n (configStore.getApiKey as Mock).mockReturnValue(null);\n\n const { exitCode, errors } = await runCli(['tools', 'list']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Not authenticated'))).toBe(true);\n });\n\n it('should handle API errors gracefully', async () => {\n mockSdk.tools.list.mockRejectedValue(new Error('Network error'));\n\n const { exitCode, errors } = await runCli(['tools', 'list']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Network error'))).toBe(true);\n });\n\n it('should handle unsuccessful API responses', async () => {\n mockSdk.tools.list.mockResolvedValue({\n success: false,\n data: null,\n message: 'Unauthorized',\n timestamp: new Date().toISOString(),\n });\n\n const { exitCode, errors } = await runCli(['tools', 'list']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Unauthorized') || e.includes('Failed'))).toBe(true);\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
it,
|
|
4
|
+
expect,
|
|
5
|
+
beforeEach,
|
|
6
|
+
afterEach,
|
|
7
|
+
vi
|
|
8
|
+
} from "vitest";
|
|
9
|
+
import { createProgram } from "../cli";
|
|
10
|
+
import { configStore } from "../utils/config-store";
|
|
11
|
+
import { ChanlSDK } from "@chanl/sdk";
|
|
12
|
+
vi.mock("@chanl/sdk", () => ({
|
|
13
|
+
ChanlSDK: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
vi.mock("../utils/config-store", () => ({
|
|
16
|
+
configStore: {
|
|
17
|
+
// Read accessors (used by sdk-factory + individual commands)
|
|
18
|
+
getApiKey: vi.fn(() => "test-api-key"),
|
|
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
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json"),
|
|
27
|
+
// Mutators
|
|
28
|
+
set: vi.fn(),
|
|
29
|
+
get: vi.fn(),
|
|
30
|
+
has: vi.fn(() => false),
|
|
31
|
+
delete: vi.fn(),
|
|
32
|
+
getAll: vi.fn(() => ({
|
|
33
|
+
apiKey: "test-api-key",
|
|
34
|
+
baseUrl: "https://api.chanl.ai",
|
|
35
|
+
workspaceId: "ws_123"
|
|
36
|
+
})),
|
|
37
|
+
setApiKey: vi.fn(),
|
|
38
|
+
setBaseUrl: vi.fn(),
|
|
39
|
+
setWorkspaceId: vi.fn(),
|
|
40
|
+
removeApiKey: vi.fn(),
|
|
41
|
+
clearAuth: vi.fn(),
|
|
42
|
+
isAuthenticated: vi.fn(() => true)
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
function captureOutput() {
|
|
46
|
+
const logs = [];
|
|
47
|
+
const errors = [];
|
|
48
|
+
const origLog = console.log;
|
|
49
|
+
const origErr = console.error;
|
|
50
|
+
console.log = (...args) => {
|
|
51
|
+
logs.push(args.map(String).join(" "));
|
|
52
|
+
};
|
|
53
|
+
console.error = (...args) => {
|
|
54
|
+
errors.push(args.map(String).join(" "));
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
logs,
|
|
58
|
+
errors,
|
|
59
|
+
restore: () => {
|
|
60
|
+
console.log = origLog;
|
|
61
|
+
console.error = origErr;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function runCli(args) {
|
|
66
|
+
const out = captureOutput();
|
|
67
|
+
const program = createProgram();
|
|
68
|
+
process.exitCode = void 0;
|
|
69
|
+
try {
|
|
70
|
+
await program.parseAsync(["node", "chanl", ...args]);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
out.restore();
|
|
74
|
+
return {
|
|
75
|
+
exitCode: process.exitCode ?? 0,
|
|
76
|
+
logs: out.logs,
|
|
77
|
+
errors: out.errors
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function makeMockSdk() {
|
|
81
|
+
return {
|
|
82
|
+
memory: {
|
|
83
|
+
list: vi.fn(),
|
|
84
|
+
get: vi.fn(),
|
|
85
|
+
create: vi.fn(),
|
|
86
|
+
update: vi.fn(),
|
|
87
|
+
delete: vi.fn(),
|
|
88
|
+
search: vi.fn(),
|
|
89
|
+
extract: vi.fn(),
|
|
90
|
+
bulkDelete: vi.fn()
|
|
91
|
+
},
|
|
92
|
+
knowledge: {
|
|
93
|
+
list: vi.fn(),
|
|
94
|
+
get: vi.fn(),
|
|
95
|
+
create: vi.fn(),
|
|
96
|
+
update: vi.fn(),
|
|
97
|
+
delete: vi.fn(),
|
|
98
|
+
search: vi.fn(),
|
|
99
|
+
getTaskStatus: vi.fn(),
|
|
100
|
+
getChunks: vi.fn()
|
|
101
|
+
},
|
|
102
|
+
personas: {
|
|
103
|
+
list: vi.fn(),
|
|
104
|
+
get: vi.fn(),
|
|
105
|
+
create: vi.fn(),
|
|
106
|
+
update: vi.fn(),
|
|
107
|
+
delete: vi.fn()
|
|
108
|
+
},
|
|
109
|
+
scorecard: {
|
|
110
|
+
list: vi.fn(),
|
|
111
|
+
get: vi.fn(),
|
|
112
|
+
create: vi.fn(),
|
|
113
|
+
update: vi.fn(),
|
|
114
|
+
delete: vi.fn()
|
|
115
|
+
},
|
|
116
|
+
health: {
|
|
117
|
+
getHealth: vi.fn(),
|
|
118
|
+
getDetailedHealth: vi.fn()
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
describe("CLI (missing commands)", () => {
|
|
123
|
+
let mockSdk;
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
vi.clearAllMocks();
|
|
126
|
+
mockSdk = makeMockSdk();
|
|
127
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
128
|
+
configStore.getApiKey.mockReturnValue("test-api-key");
|
|
129
|
+
configStore.getWorkspaceId.mockReturnValue("ws_123");
|
|
130
|
+
configStore.isAuthenticated.mockReturnValue(true);
|
|
131
|
+
});
|
|
132
|
+
afterEach(() => {
|
|
133
|
+
process.exitCode = void 0;
|
|
134
|
+
});
|
|
135
|
+
describe("config list", () => {
|
|
136
|
+
it("reads all values from configStore.getAll", async () => {
|
|
137
|
+
configStore.getAll.mockReturnValueOnce({
|
|
138
|
+
apiKey: "ak_secret",
|
|
139
|
+
baseUrl: "https://api.chanl.ai",
|
|
140
|
+
workspaceId: "ws_1"
|
|
141
|
+
});
|
|
142
|
+
const { exitCode } = await runCli(["config", "list"]);
|
|
143
|
+
expect(exitCode).toBe(0);
|
|
144
|
+
expect(configStore.getAll).toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe("config get", () => {
|
|
148
|
+
it("reads a single key via configStore.getApiKey", async () => {
|
|
149
|
+
await runCli(["config", "get", "apiKey"]);
|
|
150
|
+
expect(configStore.getApiKey).toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
it("exits 1 on an unknown key", async () => {
|
|
153
|
+
const { exitCode, errors } = await runCli([
|
|
154
|
+
"config",
|
|
155
|
+
"get",
|
|
156
|
+
"nonexistentKey"
|
|
157
|
+
]);
|
|
158
|
+
expect(exitCode).toBe(1);
|
|
159
|
+
expect(errors.some((e) => e.includes("Unknown configuration key"))).toBe(
|
|
160
|
+
true
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe("config set", () => {
|
|
165
|
+
it("writes via configStore.set with the provided key + value", async () => {
|
|
166
|
+
const { exitCode } = await runCli([
|
|
167
|
+
"config",
|
|
168
|
+
"set",
|
|
169
|
+
"workspaceId",
|
|
170
|
+
"ws_new"
|
|
171
|
+
]);
|
|
172
|
+
expect(exitCode).toBe(0);
|
|
173
|
+
expect(configStore.set).toHaveBeenCalledWith("workspaceId", "ws_new");
|
|
174
|
+
});
|
|
175
|
+
it("rejects invalid baseUrl values", async () => {
|
|
176
|
+
const { exitCode, errors } = await runCli([
|
|
177
|
+
"config",
|
|
178
|
+
"set",
|
|
179
|
+
"baseUrl",
|
|
180
|
+
"not-a-url"
|
|
181
|
+
]);
|
|
182
|
+
expect(exitCode).toBe(1);
|
|
183
|
+
expect(errors.some((e) => e.includes("Invalid URL"))).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
it("rejects invalid defaultFormat", async () => {
|
|
186
|
+
const { exitCode, errors } = await runCli([
|
|
187
|
+
"config",
|
|
188
|
+
"set",
|
|
189
|
+
"defaultFormat",
|
|
190
|
+
"xml"
|
|
191
|
+
]);
|
|
192
|
+
expect(exitCode).toBe(1);
|
|
193
|
+
expect(errors.some((e) => e.includes("Invalid format"))).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
it("rejects unknown config keys", async () => {
|
|
196
|
+
const { exitCode, errors } = await runCli([
|
|
197
|
+
"config",
|
|
198
|
+
"set",
|
|
199
|
+
"bogus",
|
|
200
|
+
"anything"
|
|
201
|
+
]);
|
|
202
|
+
expect(exitCode).toBe(1);
|
|
203
|
+
expect(errors.some((e) => e.includes("Unknown configuration key"))).toBe(
|
|
204
|
+
true
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
it("clears a stale `server` alias when setting baseUrl, so the change actually takes effect", async () => {
|
|
208
|
+
configStore.has.mockReturnValueOnce(true);
|
|
209
|
+
const { exitCode, logs } = await runCli([
|
|
210
|
+
"config",
|
|
211
|
+
"set",
|
|
212
|
+
"baseUrl",
|
|
213
|
+
"http://localhost:3100"
|
|
214
|
+
]);
|
|
215
|
+
expect(exitCode).toBe(0);
|
|
216
|
+
expect(configStore.set).toHaveBeenCalledWith("baseUrl", "http://localhost:3100");
|
|
217
|
+
expect(configStore.delete).toHaveBeenCalledWith("server");
|
|
218
|
+
expect(logs.join("\n")).toContain("cleared 'server'");
|
|
219
|
+
});
|
|
220
|
+
it("does not touch `server` when it was not set", async () => {
|
|
221
|
+
configStore.has.mockReturnValueOnce(false);
|
|
222
|
+
const { exitCode } = await runCli([
|
|
223
|
+
"config",
|
|
224
|
+
"set",
|
|
225
|
+
"baseUrl",
|
|
226
|
+
"http://localhost:3100"
|
|
227
|
+
]);
|
|
228
|
+
expect(exitCode).toBe(0);
|
|
229
|
+
expect(configStore.delete).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
it("does not touch `server` when setting an unrelated key", async () => {
|
|
232
|
+
const { exitCode } = await runCli(["config", "set", "workspaceId", "ws_new"]);
|
|
233
|
+
expect(exitCode).toBe(0);
|
|
234
|
+
expect(configStore.delete).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe("config use", () => {
|
|
238
|
+
it("switches baseUrl to the preset for a known environment", async () => {
|
|
239
|
+
const { exitCode, logs } = await runCli(["config", "use", "production"]);
|
|
240
|
+
expect(exitCode).toBe(0);
|
|
241
|
+
expect(configStore.set).toHaveBeenCalledWith("baseUrl", "https://platform.chanl.ai");
|
|
242
|
+
expect(logs.join("\n")).toContain("platform.chanl.ai");
|
|
243
|
+
});
|
|
244
|
+
it("clears a stale `server` alias so the switch actually takes effect", async () => {
|
|
245
|
+
configStore.has.mockReturnValueOnce(true);
|
|
246
|
+
const { exitCode, logs } = await runCli(["config", "use", "production"]);
|
|
247
|
+
expect(exitCode).toBe(0);
|
|
248
|
+
expect(configStore.delete).toHaveBeenCalledWith("server");
|
|
249
|
+
expect(logs.join("\n")).toContain("cleared 'server'");
|
|
250
|
+
});
|
|
251
|
+
it("rejects an unknown environment", async () => {
|
|
252
|
+
const { exitCode, errors } = await runCli(["config", "use", "bogus-env"]);
|
|
253
|
+
expect(exitCode).toBe(1);
|
|
254
|
+
expect(errors.some((e) => e.includes("Unknown environment"))).toBe(true);
|
|
255
|
+
expect(configStore.set).not.toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
describe("config delete", () => {
|
|
259
|
+
it("returns success silently when key was never set", async () => {
|
|
260
|
+
configStore.has.mockReturnValueOnce(false);
|
|
261
|
+
const { exitCode } = await runCli(["config", "delete", "workspaceId"]);
|
|
262
|
+
expect(exitCode).toBe(0);
|
|
263
|
+
expect(configStore.delete).not.toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
it("calls configStore.delete when key exists", async () => {
|
|
266
|
+
configStore.has.mockReturnValueOnce(true);
|
|
267
|
+
const { exitCode } = await runCli(["config", "delete", "workspaceId"]);
|
|
268
|
+
expect(exitCode).toBe(0);
|
|
269
|
+
expect(configStore.delete).toHaveBeenCalledWith("workspaceId");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe("health", () => {
|
|
273
|
+
it("calls sdk.health.getHealth() and prints status", async () => {
|
|
274
|
+
mockSdk.health.getHealth.mockResolvedValueOnce({
|
|
275
|
+
success: true,
|
|
276
|
+
data: {
|
|
277
|
+
status: "ok",
|
|
278
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
version: "2.0.4",
|
|
280
|
+
uptime: 3600
|
|
281
|
+
},
|
|
282
|
+
message: "OK",
|
|
283
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
284
|
+
});
|
|
285
|
+
const { exitCode, logs } = await runCli(["health"]);
|
|
286
|
+
expect(exitCode).toBe(0);
|
|
287
|
+
expect(mockSdk.health.getHealth).toHaveBeenCalled();
|
|
288
|
+
const output = logs.join("\n");
|
|
289
|
+
expect(output).toContain("healthy");
|
|
290
|
+
});
|
|
291
|
+
it("exits 1 on network error", async () => {
|
|
292
|
+
mockSdk.health.getHealth.mockRejectedValueOnce(new Error("Connection refused"));
|
|
293
|
+
const { exitCode, errors } = await runCli(["health"]);
|
|
294
|
+
expect(exitCode).toBe(1);
|
|
295
|
+
expect(errors.join("\n")).toContain("Connection refused");
|
|
296
|
+
});
|
|
297
|
+
it("outputs JSON with --json flag", async () => {
|
|
298
|
+
mockSdk.health.getHealth.mockResolvedValueOnce({
|
|
299
|
+
success: true,
|
|
300
|
+
data: {
|
|
301
|
+
status: "ok",
|
|
302
|
+
timestamp: "2026-04-09T00:00:00Z",
|
|
303
|
+
version: "2.0.4",
|
|
304
|
+
uptime: 100
|
|
305
|
+
},
|
|
306
|
+
message: "OK",
|
|
307
|
+
timestamp: "2026-04-09T00:00:00Z"
|
|
308
|
+
});
|
|
309
|
+
const { exitCode, logs } = await runCli(["health", "--json"]);
|
|
310
|
+
expect(exitCode).toBe(0);
|
|
311
|
+
const parsed = JSON.parse(logs.join(""));
|
|
312
|
+
expect(parsed.status).toBe("ok");
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
describe("memory list", () => {
|
|
316
|
+
it("calls sdk.memory.list with default pagination", async () => {
|
|
317
|
+
mockSdk.memory.list.mockResolvedValueOnce({
|
|
318
|
+
success: true,
|
|
319
|
+
data: { memories: [], total: 0 },
|
|
320
|
+
message: "OK",
|
|
321
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
322
|
+
});
|
|
323
|
+
const { exitCode } = await runCli(["memory", "list"]);
|
|
324
|
+
expect(exitCode).toBe(0);
|
|
325
|
+
expect(mockSdk.memory.list).toHaveBeenCalledWith(
|
|
326
|
+
expect.objectContaining({ page: 1, limit: 20 })
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
it("forwards filters from flags", async () => {
|
|
330
|
+
mockSdk.memory.list.mockResolvedValueOnce({
|
|
331
|
+
success: true,
|
|
332
|
+
data: { memories: [], total: 0 },
|
|
333
|
+
message: "OK",
|
|
334
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
335
|
+
});
|
|
336
|
+
await runCli([
|
|
337
|
+
"memory",
|
|
338
|
+
"list",
|
|
339
|
+
"--entity-type",
|
|
340
|
+
"customer",
|
|
341
|
+
"--entity-id",
|
|
342
|
+
"cust_1",
|
|
343
|
+
"--agent-id",
|
|
344
|
+
"ag_1",
|
|
345
|
+
"--source",
|
|
346
|
+
"manual",
|
|
347
|
+
"--limit",
|
|
348
|
+
"50",
|
|
349
|
+
"--page",
|
|
350
|
+
"2"
|
|
351
|
+
]);
|
|
352
|
+
expect(mockSdk.memory.list).toHaveBeenCalledWith(
|
|
353
|
+
expect.objectContaining({
|
|
354
|
+
entityType: "customer",
|
|
355
|
+
entityId: "cust_1",
|
|
356
|
+
agentId: "ag_1",
|
|
357
|
+
source: "manual",
|
|
358
|
+
limit: 50,
|
|
359
|
+
page: 2
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
it('prints "No memories found" on empty result', async () => {
|
|
364
|
+
mockSdk.memory.list.mockResolvedValueOnce({
|
|
365
|
+
success: true,
|
|
366
|
+
data: { memories: [], total: 0 },
|
|
367
|
+
message: "OK",
|
|
368
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
369
|
+
});
|
|
370
|
+
const { logs } = await runCli(["memory", "list"]);
|
|
371
|
+
expect(logs.join("\n")).toContain("No memories found");
|
|
372
|
+
});
|
|
373
|
+
it("exits 1 on SDK error", async () => {
|
|
374
|
+
mockSdk.memory.list.mockRejectedValueOnce(new Error("Forbidden"));
|
|
375
|
+
const { exitCode, errors } = await runCli(["memory", "list"]);
|
|
376
|
+
expect(exitCode).toBe(1);
|
|
377
|
+
expect(errors.join("\n")).toContain("Forbidden");
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
describe("memory get", () => {
|
|
381
|
+
it("fetches by ID", async () => {
|
|
382
|
+
mockSdk.memory.get.mockResolvedValueOnce({
|
|
383
|
+
success: true,
|
|
384
|
+
data: {
|
|
385
|
+
id: "mem_1",
|
|
386
|
+
entityType: "customer",
|
|
387
|
+
entityId: "cust_1",
|
|
388
|
+
content: "Prefers email",
|
|
389
|
+
isActive: true,
|
|
390
|
+
createdAt: "2026-04-09T00:00:00Z"
|
|
391
|
+
},
|
|
392
|
+
message: "OK",
|
|
393
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
394
|
+
});
|
|
395
|
+
const { exitCode } = await runCli(["memory", "get", "mem_1"]);
|
|
396
|
+
expect(exitCode).toBe(0);
|
|
397
|
+
expect(mockSdk.memory.get).toHaveBeenCalledWith("mem_1");
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
describe("memory search", () => {
|
|
401
|
+
it("requires entity-type, entity-id, and query", async () => {
|
|
402
|
+
mockSdk.memory.search.mockResolvedValueOnce({
|
|
403
|
+
success: true,
|
|
404
|
+
data: {
|
|
405
|
+
memories: [
|
|
406
|
+
{
|
|
407
|
+
id: "mem_1",
|
|
408
|
+
content: "prefers email",
|
|
409
|
+
score: 0.92,
|
|
410
|
+
entityType: "customer",
|
|
411
|
+
entityId: "cust_1",
|
|
412
|
+
createdAt: "2026-04-09T00:00:00Z"
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
message: "OK",
|
|
417
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
418
|
+
});
|
|
419
|
+
const { exitCode } = await runCli([
|
|
420
|
+
"memory",
|
|
421
|
+
"search",
|
|
422
|
+
"--entity-type",
|
|
423
|
+
"customer",
|
|
424
|
+
"--entity-id",
|
|
425
|
+
"cust_1",
|
|
426
|
+
"--query",
|
|
427
|
+
"email preferences",
|
|
428
|
+
"--limit",
|
|
429
|
+
"5"
|
|
430
|
+
]);
|
|
431
|
+
expect(exitCode).toBe(0);
|
|
432
|
+
expect(mockSdk.memory.search).toHaveBeenCalledWith(
|
|
433
|
+
expect.objectContaining({
|
|
434
|
+
entityType: "customer",
|
|
435
|
+
entityId: "cust_1",
|
|
436
|
+
query: "email preferences",
|
|
437
|
+
limit: 5
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
describe("knowledge list", () => {
|
|
443
|
+
it("calls sdk.knowledge.list", async () => {
|
|
444
|
+
mockSdk.knowledge.list.mockResolvedValueOnce({
|
|
445
|
+
success: true,
|
|
446
|
+
data: { items: [], total: 0 },
|
|
447
|
+
message: "OK",
|
|
448
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
449
|
+
});
|
|
450
|
+
const { exitCode } = await runCli(["knowledge", "list"]);
|
|
451
|
+
expect(exitCode).toBe(0);
|
|
452
|
+
expect(mockSdk.knowledge.list).toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
describe("knowledge search", () => {
|
|
456
|
+
it("calls sdk.knowledge.search with query body", async () => {
|
|
457
|
+
mockSdk.knowledge.search.mockResolvedValueOnce({
|
|
458
|
+
success: true,
|
|
459
|
+
data: { results: [] },
|
|
460
|
+
message: "OK",
|
|
461
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
462
|
+
});
|
|
463
|
+
const { exitCode } = await runCli([
|
|
464
|
+
"knowledge",
|
|
465
|
+
"search",
|
|
466
|
+
"how to return"
|
|
467
|
+
]);
|
|
468
|
+
expect(exitCode).toBe(0);
|
|
469
|
+
expect(mockSdk.knowledge.search).toHaveBeenCalledWith(
|
|
470
|
+
expect.objectContaining({ query: "how to return" })
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
describe("personas list", () => {
|
|
475
|
+
it("calls sdk.personas.list", async () => {
|
|
476
|
+
mockSdk.personas.list.mockResolvedValueOnce({
|
|
477
|
+
success: true,
|
|
478
|
+
data: { personas: [], total: 0 },
|
|
479
|
+
message: "OK",
|
|
480
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
481
|
+
});
|
|
482
|
+
const { exitCode } = await runCli(["personas", "list"]);
|
|
483
|
+
expect(exitCode).toBe(0);
|
|
484
|
+
expect(mockSdk.personas.list).toHaveBeenCalled();
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
describe("personas get", () => {
|
|
488
|
+
it("calls sdk.personas.get with id", async () => {
|
|
489
|
+
mockSdk.personas.get.mockResolvedValueOnce({
|
|
490
|
+
success: true,
|
|
491
|
+
data: {
|
|
492
|
+
id: "p_1",
|
|
493
|
+
name: "Frustrated Customer",
|
|
494
|
+
gender: "female",
|
|
495
|
+
emotion: "frustrated",
|
|
496
|
+
language: "english",
|
|
497
|
+
accent: "american",
|
|
498
|
+
intentClarity: "very clear",
|
|
499
|
+
speechStyle: "fast",
|
|
500
|
+
backgroundNoise: false,
|
|
501
|
+
allowInterruptions: true,
|
|
502
|
+
backstory: "Bad shipping",
|
|
503
|
+
isActive: true,
|
|
504
|
+
createdAt: "2026-04-09T00:00:00Z",
|
|
505
|
+
updatedAt: "2026-04-09T00:00:00Z"
|
|
506
|
+
},
|
|
507
|
+
message: "OK",
|
|
508
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
509
|
+
});
|
|
510
|
+
const { exitCode } = await runCli(["personas", "get", "p_1"]);
|
|
511
|
+
expect(exitCode).toBe(0);
|
|
512
|
+
expect(mockSdk.personas.get).toHaveBeenCalledWith("p_1");
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
describe("scorecards list", () => {
|
|
516
|
+
it("calls sdk.scorecard.list", async () => {
|
|
517
|
+
mockSdk.scorecard.list.mockResolvedValueOnce({
|
|
518
|
+
success: true,
|
|
519
|
+
data: { scorecards: [], total: 0 },
|
|
520
|
+
message: "OK",
|
|
521
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
522
|
+
});
|
|
523
|
+
const { exitCode } = await runCli(["scorecards", "list"]);
|
|
524
|
+
expect(exitCode).toBe(0);
|
|
525
|
+
expect(mockSdk.scorecard.list).toHaveBeenCalled();
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
describe("scorecards get", () => {
|
|
529
|
+
it("calls sdk.scorecard.get with id", async () => {
|
|
530
|
+
mockSdk.scorecard.get.mockResolvedValueOnce({
|
|
531
|
+
success: true,
|
|
532
|
+
data: {
|
|
533
|
+
id: "sc_1",
|
|
534
|
+
name: "Customer Quality",
|
|
535
|
+
status: "active"
|
|
536
|
+
},
|
|
537
|
+
message: "OK",
|
|
538
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
539
|
+
});
|
|
540
|
+
const { exitCode } = await runCli(["scorecards", "get", "sc_1"]);
|
|
541
|
+
expect(exitCode).toBe(0);
|
|
542
|
+
expect(mockSdk.scorecard.get).toHaveBeenCalledWith("sc_1");
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
//# sourceMappingURL=cli-missing.test.js.map
|