@protoboxai/cli 1.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/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @chanl/cli
2
+
3
+ Command-line interface for the [Chanl AI](https://chanl.ai) platform.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @chanl/cli
9
+ ```
10
+
11
+ Or run directly:
12
+
13
+ ```bash
14
+ npx @chanl/cli
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Authenticate
21
+ chanl login
22
+
23
+ # Check health
24
+ chanl health
25
+
26
+ # List agents
27
+ chanl agents list
28
+
29
+ # Chat with an agent
30
+ chanl chat <agent-id>
31
+
32
+ # Run test scenarios
33
+ chanl scenarios list
34
+ chanl scenarios run <id>
35
+
36
+ # Manage tools
37
+ chanl tools list
38
+ chanl toolsets list
39
+ ```
40
+
41
+ ## Commands
42
+
43
+ | Command | Description |
44
+ |---------|-------------|
45
+ | `chanl login` | Authenticate with API key or browser |
46
+ | `chanl health` | Check API health |
47
+ | `chanl agents` | Manage AI agents |
48
+ | `chanl tools` | Manage MCP tools |
49
+ | `chanl toolsets` | Manage tool collections |
50
+ | `chanl scenarios` | Run test scenarios |
51
+ | `chanl calls` | Import and analyze calls |
52
+ | `chanl call <phone>` | Make an outbound AI call |
53
+ | `chanl chat <agent>` | Interactive chat session |
54
+ | `chanl kb` | Manage knowledge base |
55
+ | `chanl memory` | Manage agent memory |
56
+ | `chanl scorecards` | Manage evaluation scorecards |
57
+ | `chanl prompts` | Manage prompt templates |
58
+ | `chanl config` | CLI configuration |
59
+ | `chanl mcp` | MCP server status |
60
+
61
+ ## Configuration
62
+
63
+ ```bash
64
+ chanl config show # Show current config
65
+ chanl config set baseUrl <url> # Custom API endpoint
66
+ chanl config reset # Reset to production
67
+ ```
68
+
69
+ Default API: `https://platform.chanl.ai`
70
+
71
+ ## Documentation
72
+
73
+ - [Chanl Platform](https://chanl.ai)
74
+ - [GitHub](https://github.com/chanl-ai/chanl-sdk)
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Entry point for the Protobox CLI (local dev via workspace link)
4
+
5
+ import('../dist/index.js').catch((err) => {
6
+ console.error('Failed to load CLI:', err.message);
7
+ process.exit(1);
8
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ import { Command } from "commander";
2
+ import { createRequire } from "module";
3
+ import { setOutputOptions } from "./utils/output.js";
4
+ import {
5
+ createAuthCommand,
6
+ createLoginCommand,
7
+ createLogoutCommand,
8
+ createConfigCommand,
9
+ createToolsCommand,
10
+ createToolsetsCommand,
11
+ createHealthCommand,
12
+ createMcpCommand,
13
+ createKnowledgeCommand,
14
+ createChatCommand,
15
+ createPromptsCommand,
16
+ createWorkspacesCommand
17
+ } from "./commands/index.js";
18
+ const require2 = createRequire(import.meta.url);
19
+ const { version: CLI_VERSION } = require2("../package.json");
20
+ function createProgram() {
21
+ const program = new Command();
22
+ program.name("protobox").description("Command-line interface for Protobox \u2014 MCP servers, tools, and resources").version(CLI_VERSION, "-v, --version", "Show CLI version").option("--json", "Output results as JSON").option("--no-color", "Disable colored output").hook("preAction", (thisCommand) => {
23
+ const opts = thisCommand.opts();
24
+ setOutputOptions({
25
+ json: opts["json"] === true,
26
+ noColor: opts["color"] === false
27
+ });
28
+ });
29
+ program.addCommand(createLoginCommand());
30
+ program.addCommand(createLogoutCommand());
31
+ program.addCommand(createAuthCommand());
32
+ program.addCommand(createConfigCommand());
33
+ program.addCommand(createToolsCommand());
34
+ program.addCommand(createToolsetsCommand());
35
+ program.addCommand(createHealthCommand());
36
+ program.addCommand(createMcpCommand());
37
+ program.addCommand(createKnowledgeCommand());
38
+ program.addCommand(createChatCommand());
39
+ program.addCommand(createPromptsCommand());
40
+ program.addCommand(createWorkspacesCommand());
41
+ program.showHelpAfterError(true);
42
+ program.showSuggestionAfterError(true);
43
+ return program;
44
+ }
45
+ async function run(argv = process.argv) {
46
+ const program = createProgram();
47
+ try {
48
+ await program.parseAsync(argv);
49
+ } catch (error) {
50
+ if (error instanceof Error) {
51
+ console.error("Error:", error.message);
52
+ }
53
+ process.exitCode = 1;
54
+ }
55
+ }
56
+ export {
57
+ createProgram,
58
+ run
59
+ };
60
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,294 @@
1
+ import { Command } from "commander";
2
+ import { select } from "@inquirer/prompts";
3
+ import ora from "ora";
4
+ import chalk from "chalk";
5
+ import http from "node:http";
6
+ import { ChanlSDK } from "@protoboxai/sdk";
7
+ import { configStore } from "../utils/config-store.js";
8
+ import {
9
+ printSuccess,
10
+ printError,
11
+ printInfo,
12
+ printLabel,
13
+ printBlank,
14
+ isJsonOutput,
15
+ printJson,
16
+ maskString
17
+ } from "../utils/output.js";
18
+ function createAuthCommand() {
19
+ const auth = new Command("auth").description("Authentication commands");
20
+ auth.command("status").description("Show current authentication status").action(handleAuthStatus);
21
+ return auth;
22
+ }
23
+ function createLoginCommand() {
24
+ return new Command("login").description("Authenticate with Chanl API").option("-k, --api-key <key>", "API key to use for authentication").option("--browser", "Force browser-based login (email/password or Google/Microsoft)").option("--base-url <url>", "Override the API base URL").addHelpText(
25
+ "after",
26
+ `
27
+ Login Methods:
28
+ Default Opens browser for sign-in (Google, Microsoft, or email)
29
+ --api-key <key> Use an API key directly (skips browser)
30
+
31
+ Examples:
32
+ $ chanl login # Browser login (recommended)
33
+ $ chanl login --api-key ak_xxx # API key login
34
+ $ chanl login --base-url http://localhost:3100 # Custom API URL`
35
+ ).action(handleLogin);
36
+ }
37
+ function createLogoutCommand() {
38
+ return new Command("logout").description("Remove stored credentials").action(handleLogout);
39
+ }
40
+ async function handleLogin(options) {
41
+ if (options.baseUrl) {
42
+ configStore.setBaseUrl(options.baseUrl);
43
+ }
44
+ if (options.apiKey) {
45
+ await handleApiKeyLogin(options.apiKey);
46
+ } else {
47
+ await handleBrowserLogin();
48
+ }
49
+ }
50
+ async function handleApiKeyLogin(apiKey) {
51
+ const baseUrl = configStore.getBaseUrl();
52
+ const spinner = ora("Validating API key...").start();
53
+ try {
54
+ const sdk = new ChanlSDK({ apiKey, baseUrl });
55
+ const healthResponse = await sdk.health.getHealth();
56
+ if (!healthResponse.success) {
57
+ spinner.fail("Cannot connect to API");
58
+ printError("API is not reachable", `Check that the server is running at ${baseUrl}`);
59
+ process.exitCode = 1;
60
+ return;
61
+ }
62
+ const authTestResponse = await sdk.tools.list();
63
+ if (!authTestResponse.success) {
64
+ spinner.fail("Authentication failed");
65
+ printError("Invalid API key or unauthorized access");
66
+ process.exitCode = 1;
67
+ return;
68
+ }
69
+ configStore.setApiKey(apiKey);
70
+ spinner.succeed("Authentication successful");
71
+ if (isJsonOutput()) {
72
+ printJson({ success: true, method: "api-key", apiKey: maskString(apiKey), baseUrl });
73
+ } else {
74
+ printSuccess(`API key saved to ${configStore.getPath()}`);
75
+ printInfo("Workspace context is determined by your API key");
76
+ }
77
+ } catch (error) {
78
+ spinner.fail("Authentication failed");
79
+ const message = error instanceof Error ? error.message : "Unknown error";
80
+ printError("Failed to validate API key", message);
81
+ process.exitCode = 1;
82
+ }
83
+ }
84
+ async function handleBrowserLogin() {
85
+ const baseUrl = configStore.getBaseUrl();
86
+ const appUrl = configStore.getAppUrl();
87
+ printBlank();
88
+ printInfo("Opening browser for authentication...");
89
+ try {
90
+ const { firebaseToken, port } = await startCallbackServer();
91
+ const authUrl = `${appUrl}/cli-auth?port=${port}`;
92
+ const open = (await import("open")).default;
93
+ await open(authUrl);
94
+ printInfo(`If the browser didn't open, visit:`);
95
+ console.log(chalk.cyan(` ${authUrl}`));
96
+ printBlank();
97
+ const spinner = ora("Waiting for authentication...").start();
98
+ const token = await firebaseToken;
99
+ spinner.text = "Exchanging token...";
100
+ const sdk = new ChanlSDK({ baseUrl });
101
+ const response = await sdk.auth.exchangeFirebaseToken(token);
102
+ if (!response.success || !response.data) {
103
+ spinner.fail("Token exchange failed");
104
+ printError("Failed to exchange token", response.message);
105
+ process.exitCode = 1;
106
+ return;
107
+ }
108
+ const { accessToken, refreshToken, user, defaultWorkspaceId, workspaces } = response.data;
109
+ configStore.setJwtAuth(accessToken, refreshToken);
110
+ let selectedWorkspaceId = defaultWorkspaceId;
111
+ if (workspaces && workspaces.length > 1 && !isJsonOutput()) {
112
+ spinner.stop();
113
+ printBlank();
114
+ selectedWorkspaceId = await select({
115
+ message: "Select a workspace:",
116
+ choices: workspaces.map((ws) => ({
117
+ name: ws.name || ws.workspaceId,
118
+ value: ws.workspaceId,
119
+ description: ws.role
120
+ })),
121
+ default: defaultWorkspaceId
122
+ });
123
+ } else if (workspaces && workspaces.length === 1) {
124
+ selectedWorkspaceId = workspaces[0].workspaceId;
125
+ }
126
+ if (selectedWorkspaceId) {
127
+ configStore.setWorkspaceId(selectedWorkspaceId);
128
+ }
129
+ spinner.succeed("Authentication successful");
130
+ if (isJsonOutput()) {
131
+ printJson({
132
+ success: true,
133
+ method: "browser",
134
+ user: { email: user.email, displayName: user.displayName },
135
+ workspaceId: selectedWorkspaceId
136
+ });
137
+ } else {
138
+ printBlank();
139
+ printLabel("User", user.email);
140
+ if (user.displayName) printLabel("Name", user.displayName);
141
+ if (selectedWorkspaceId) printLabel("Workspace", selectedWorkspaceId);
142
+ printBlank();
143
+ printSuccess(`Credentials saved to ${configStore.getPath()}`);
144
+ }
145
+ } catch (error) {
146
+ const message = error instanceof Error ? error.message : "Unknown error";
147
+ if (message === "LOGIN_TIMEOUT") {
148
+ printError("Login timed out", "No response from browser within 2 minutes");
149
+ } else if (message === "LOGIN_CANCELLED") {
150
+ printInfo("Login cancelled");
151
+ } else {
152
+ printError("Browser login failed", message);
153
+ }
154
+ process.exitCode = 1;
155
+ }
156
+ }
157
+ function startCallbackServer() {
158
+ return new Promise((resolveSetup) => {
159
+ let resolveToken;
160
+ let rejectToken;
161
+ const firebaseToken = new Promise((resolve, reject) => {
162
+ resolveToken = resolve;
163
+ rejectToken = reject;
164
+ });
165
+ const server = http.createServer((req, res) => {
166
+ res.setHeader("Access-Control-Allow-Origin", "*");
167
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
168
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
169
+ if (req.method === "OPTIONS") {
170
+ res.writeHead(204);
171
+ res.end();
172
+ return;
173
+ }
174
+ if (req.method === "POST" && req.url === "/callback") {
175
+ let body = "";
176
+ req.on("data", (chunk) => {
177
+ body += chunk.toString();
178
+ });
179
+ req.on("end", () => {
180
+ try {
181
+ const data = JSON.parse(body);
182
+ if (data.error) {
183
+ res.writeHead(200, { "Content-Type": "application/json" });
184
+ res.end(JSON.stringify({ success: false }));
185
+ rejectToken(new Error(data.error));
186
+ return;
187
+ }
188
+ if (!data.token) {
189
+ res.writeHead(400, { "Content-Type": "application/json" });
190
+ res.end(JSON.stringify({ error: "Missing token" }));
191
+ return;
192
+ }
193
+ res.writeHead(200, { "Content-Type": "application/json" });
194
+ res.end(JSON.stringify({ success: true }));
195
+ resolveToken(data.token);
196
+ } catch {
197
+ res.writeHead(400, { "Content-Type": "application/json" });
198
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
199
+ }
200
+ });
201
+ return;
202
+ }
203
+ res.writeHead(404);
204
+ res.end();
205
+ });
206
+ server.listen(0, "127.0.0.1", () => {
207
+ const addr = server.address();
208
+ const port = typeof addr === "object" && addr ? addr.port : 0;
209
+ const timeout = setTimeout(() => {
210
+ server.close();
211
+ rejectToken(new Error("LOGIN_TIMEOUT"));
212
+ }, 12e4);
213
+ firebaseToken.then(() => {
214
+ clearTimeout(timeout);
215
+ setTimeout(() => server.close(), 500);
216
+ }).catch(() => {
217
+ clearTimeout(timeout);
218
+ server.close();
219
+ });
220
+ resolveSetup({ firebaseToken, port, server });
221
+ });
222
+ });
223
+ }
224
+ async function handleLogout() {
225
+ if (!configStore.isAuthenticated()) {
226
+ if (isJsonOutput()) {
227
+ printJson({ success: true, message: "Already logged out" });
228
+ } else {
229
+ printInfo("Already logged out");
230
+ }
231
+ return;
232
+ }
233
+ configStore.clearAuth();
234
+ configStore.delete("workspaceId");
235
+ if (isJsonOutput()) {
236
+ printJson({ success: true, message: "Logged out successfully" });
237
+ } else {
238
+ printSuccess("Logged out successfully");
239
+ }
240
+ }
241
+ async function handleAuthStatus() {
242
+ const authMethod = configStore.getAuthMethod();
243
+ const baseUrl = configStore.getBaseUrl();
244
+ const workspaceId = configStore.getWorkspaceId();
245
+ if (isJsonOutput()) {
246
+ printJson({
247
+ authenticated: authMethod !== null,
248
+ method: authMethod,
249
+ apiKey: authMethod === "api-key" ? maskString(configStore.getApiKey()) : null,
250
+ jwtToken: authMethod === "jwt" ? "(set)" : null,
251
+ workspaceId: workspaceId ?? null,
252
+ baseUrl
253
+ });
254
+ return;
255
+ }
256
+ printBlank();
257
+ if (!authMethod) {
258
+ printLabel("Status", chalk.yellow("Not authenticated"));
259
+ printBlank();
260
+ printInfo("Run 'chanl login' to authenticate via browser");
261
+ printInfo("Run 'chanl login --api-key <key>' to use an API key");
262
+ return;
263
+ }
264
+ printLabel("Status", chalk.green("Authenticated"));
265
+ printLabel("Method", authMethod === "jwt" ? "Browser (JWT)" : "API Key");
266
+ if (authMethod === "api-key") {
267
+ printLabel("API Key", maskString(configStore.getApiKey()));
268
+ }
269
+ printLabel("Base URL", baseUrl);
270
+ if (workspaceId) {
271
+ printLabel("Workspace ID", workspaceId);
272
+ try {
273
+ const sdkConfig = { baseUrl };
274
+ if (authMethod === "api-key") {
275
+ sdkConfig.apiKey = configStore.getApiKey();
276
+ } else {
277
+ sdkConfig.jwtToken = configStore.getJwtToken();
278
+ }
279
+ const sdk = new ChanlSDK(sdkConfig);
280
+ const response = await sdk.workspace.getById(workspaceId);
281
+ if (response.success && response.data?.name) {
282
+ printLabel("Workspace Name", response.data.name);
283
+ }
284
+ } catch {
285
+ }
286
+ }
287
+ printBlank();
288
+ }
289
+ export {
290
+ createAuthCommand,
291
+ createLoginCommand,
292
+ createLogoutCommand
293
+ };
294
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,203 @@
1
+ import { Command } from "commander";
2
+ import ora from "ora";
3
+ import chalk from "chalk";
4
+ import { createInterface } from "node:readline";
5
+ import { createSdk } from "../utils/sdk-factory.js";
6
+ import {
7
+ printError,
8
+ printSuccess,
9
+ printInfo,
10
+ printBlank,
11
+ isJsonOutput,
12
+ printJson
13
+ } from "../utils/output.js";
14
+ function createChatCommand() {
15
+ const chat = new Command("chat").description("Start an interactive chat session with an agent").argument("<agent-id>", "ID of the agent to chat with").option("-m, --message <text>", "Send a single message and exit").option("-s, --stream", "Enable streaming responses (real-time text)").option("-w, --workspace <id>", "Workspace ID override").addHelpText(
16
+ "after",
17
+ `
18
+ What is Chat?
19
+ Chat lets you test an AI agent via text in your terminal. It creates a session
20
+ on the Chanl platform, which manages the full lifecycle including
21
+ transcription, billing, and post-chat evaluation.
22
+
23
+ Modes:
24
+ Interactive (default): Opens a prompt loop \u2014 type messages, see agent responses.
25
+ Type 'exit', 'quit', or press Ctrl+C to end.
26
+
27
+ Single message: Send one message with --message, print response, exit.
28
+
29
+ Streaming:
30
+ Use --stream for real-time text output. Text appears as the agent generates it.
31
+ Note: streaming responses do not include tool call metadata.
32
+
33
+ Examples:
34
+ $ chanl chat abc123 # Interactive chat
35
+ $ chanl chat abc123 --stream # Interactive with streaming
36
+ $ chanl chat abc123 --message "Hello" # Single message mode
37
+ $ chanl chat abc123 --message "Hi" --stream # Single message, streamed
38
+ $ chanl chat abc123 --message "Hi" --json # Single message, JSON output
39
+ $ chanl chat abc123 --workspace ws_456 # Override workspace`
40
+ ).action(handleChat);
41
+ return chat;
42
+ }
43
+ async function handleChat(agentId, options) {
44
+ const sdk = createSdk();
45
+ if (!sdk) return;
46
+ if (options.message) {
47
+ await handleSingleMessage(sdk, agentId, options.message, options.stream);
48
+ } else {
49
+ await handleInteractiveChat(sdk, agentId, options.stream);
50
+ }
51
+ }
52
+ async function handleSingleMessage(sdk, agentId, message, stream) {
53
+ const spinner = ora("Connecting to agent...").start();
54
+ try {
55
+ const chat = await sdk.chat.session(agentId);
56
+ spinner.text = "Sending message...";
57
+ try {
58
+ if (stream && !isJsonOutput()) {
59
+ spinner.stop();
60
+ printBlank();
61
+ console.log(chalk.dim("You >"), message);
62
+ process.stdout.write(chalk.cyan("Agent > "));
63
+ const reply = await chat.send(message, {
64
+ stream: true,
65
+ onChunk: (delta) => process.stdout.write(delta)
66
+ });
67
+ console.log();
68
+ if (reply.latencyMs) {
69
+ printBlank();
70
+ printInfo(`Response time: ${reply.latencyMs}ms`);
71
+ }
72
+ printBlank();
73
+ } else {
74
+ const reply = await chat.send(message);
75
+ spinner.stop();
76
+ if (isJsonOutput()) {
77
+ printJson({
78
+ sessionId: chat.sessionId,
79
+ agentId,
80
+ message,
81
+ response: reply.message,
82
+ toolCalls: reply.toolCalls,
83
+ latencyMs: reply.latencyMs
84
+ });
85
+ } else {
86
+ printBlank();
87
+ console.log(chalk.dim("You >"), message);
88
+ if (reply.toolCalls?.length) {
89
+ console.log(chalk.dim(" Tools:"));
90
+ printToolCalls(reply.toolCalls);
91
+ }
92
+ console.log(chalk.cyan("Agent >"), reply.message);
93
+ if (reply.latencyMs) {
94
+ printBlank();
95
+ printInfo(`Response time: ${reply.latencyMs}ms`);
96
+ }
97
+ printBlank();
98
+ }
99
+ }
100
+ } finally {
101
+ await chat.end();
102
+ }
103
+ } catch (error) {
104
+ spinner.fail("Chat failed");
105
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
106
+ printError("Error", errorMessage);
107
+ process.exitCode = 1;
108
+ }
109
+ }
110
+ async function handleInteractiveChat(sdk, agentId, stream) {
111
+ const spinner = ora("Connecting to agent...").start();
112
+ try {
113
+ const chat = await sdk.chat.session(agentId);
114
+ const agentName = chat.agentName || "Agent";
115
+ spinner.succeed(`Connected to ${agentName}${stream ? " (streaming)" : ""}`);
116
+ printInfo("Type 'exit' or 'quit' to end the session. Ctrl+C also works.");
117
+ printBlank();
118
+ const rl = createInterface({
119
+ input: process.stdin,
120
+ output: process.stdout
121
+ });
122
+ const prompt = () => {
123
+ rl.question(chalk.bold("You > "), async (input) => {
124
+ const trimmed = input.trim();
125
+ if (!trimmed) {
126
+ prompt();
127
+ return;
128
+ }
129
+ if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
130
+ await endSessionCleanly(chat);
131
+ rl.close();
132
+ return;
133
+ }
134
+ try {
135
+ if (stream) {
136
+ process.stdout.write(chalk.cyan("Agent > "));
137
+ const reply = await chat.send(trimmed, {
138
+ stream: true,
139
+ onChunk: (delta) => process.stdout.write(delta)
140
+ });
141
+ console.log();
142
+ if (reply.latencyMs) {
143
+ printInfo(`${reply.latencyMs}ms`);
144
+ }
145
+ } else {
146
+ const reply = await chat.send(trimmed);
147
+ if (reply.toolCalls?.length) {
148
+ console.log(chalk.dim(" Tools:"));
149
+ printToolCalls(reply.toolCalls);
150
+ }
151
+ console.log(chalk.cyan("Agent >"), reply.message);
152
+ }
153
+ printBlank();
154
+ } catch (error) {
155
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
156
+ printError("Send failed", errorMessage);
157
+ printBlank();
158
+ }
159
+ prompt();
160
+ });
161
+ };
162
+ const handleSigint = async () => {
163
+ printBlank();
164
+ await endSessionCleanly(chat);
165
+ rl.close();
166
+ process.exit(0);
167
+ };
168
+ rl.on("SIGINT", () => {
169
+ void handleSigint();
170
+ });
171
+ rl.on("close", () => {
172
+ });
173
+ prompt();
174
+ } catch (error) {
175
+ spinner.fail("Failed to connect");
176
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
177
+ printError("Error", errorMessage);
178
+ process.exitCode = 1;
179
+ }
180
+ }
181
+ function printToolCalls(toolCalls) {
182
+ for (const tc of toolCalls) {
183
+ const statusIcon = tc.status === "success" ? chalk.green("\u2713") : tc.status === "error" ? chalk.red("\u2717") : tc.status === "timeout" ? chalk.yellow("\u23F1") : chalk.dim("\u2026");
184
+ const duration = tc.durationMs ? chalk.dim(` (${tc.durationMs}ms)`) : "";
185
+ const params = Object.keys(tc.parameters).length ? chalk.dim(` ${JSON.stringify(tc.parameters)}`) : "";
186
+ console.log(` ${statusIcon} ${chalk.yellow(tc.name)}${params}${duration}`);
187
+ if (tc.status === "error" && tc.error) {
188
+ console.log(` ${chalk.red(tc.error.message)}`);
189
+ }
190
+ }
191
+ }
192
+ async function endSessionCleanly(chat) {
193
+ try {
194
+ await chat.end();
195
+ printSuccess("Session ended");
196
+ } catch {
197
+ printInfo("Session closed");
198
+ }
199
+ }
200
+ export {
201
+ createChatCommand
202
+ };
203
+ //# sourceMappingURL=chat.js.map