@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/chat.ts"],"sourcesContent":["import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { createInterface } from 'node:readline';\nimport type { ChanlSDK, ToolCall } from '@chanl/sdk';\nimport { createSdk } from '../utils/sdk-factory.js';\nimport {\n printError,\n printSuccess,\n printInfo,\n printBlank,\n isJsonOutput,\n printJson,\n} from '../utils/output.js';\n\n/**\n * Create the chat command\n */\nexport function createChatCommand(): Command {\n const chat = new Command('chat')\n .description('Start an interactive chat session with an agent')\n .argument('<agent-id>', 'ID of the agent to chat with')\n .option('-m, --message <text>', 'Send a single message and exit')\n .option('-s, --stream', 'Enable streaming responses (real-time text)')\n .option('-w, --workspace <id>', 'Workspace ID override')\n .addHelpText(\n 'after',\n `\nWhat is Chat?\n Chat lets you test an AI agent via text in your terminal. It creates a session\n through interactions-service which manages the full lifecycle including\n transcription, billing, and evaluation.\n\nModes:\n Interactive (default): Opens a prompt loop — type messages, see agent responses.\n Type 'exit', 'quit', or press Ctrl+C to end.\n\n Single message: Send one message with --message, print response, exit.\n\nStreaming:\n Use --stream for real-time text output. Text appears as the agent generates it.\n Note: streaming responses do not include tool call metadata.\n\nExamples:\n $ chanl chat abc123 # Interactive chat\n $ chanl chat abc123 --stream # Interactive with streaming\n $ chanl chat abc123 --message \"Hello\" # Single message mode\n $ chanl chat abc123 --message \"Hi\" --stream # Single message, streamed\n $ chanl chat abc123 --message \"Hi\" --json # Single message, JSON output\n $ chanl chat abc123 --workspace ws_456 # Override workspace`\n )\n .action(handleChat);\n\n return chat;\n}\n\n/**\n * Handle the chat command\n */\nasync function handleChat(\n agentId: string,\n options: { message?: string; stream?: boolean; workspace?: string },\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n if (options.message) {\n await handleSingleMessage(sdk, agentId, options.message, options.stream);\n } else {\n await handleInteractiveChat(sdk, agentId, options.stream);\n }\n}\n\n/**\n * Single message mode: create session, send message, print response, end session\n * Uses SDK LiveChat for session management\n */\nasync function handleSingleMessage(\n sdk: ChanlSDK,\n agentId: string,\n message: string,\n stream?: boolean,\n): Promise<void> {\n const spinner = ora('Connecting to agent...').start();\n\n try {\n const chat = await sdk.chat.session(agentId);\n spinner.text = 'Sending message...';\n\n try {\n if (stream && !isJsonOutput()) {\n // Streaming mode: show text as it arrives\n spinner.stop();\n printBlank();\n console.log(chalk.dim('You >'), message);\n process.stdout.write(chalk.cyan('Agent > '));\n\n const reply = await chat.send(message, {\n stream: true,\n onChunk: (delta) => process.stdout.write(delta),\n });\n\n // Newline after streamed text\n console.log();\n if (reply.latencyMs) {\n printBlank();\n printInfo(`Response time: ${reply.latencyMs}ms`);\n }\n printBlank();\n } else {\n // Non-streaming or JSON mode\n const reply = await chat.send(message);\n spinner.stop();\n\n if (isJsonOutput()) {\n printJson({\n sessionId: chat.sessionId,\n agentId,\n message,\n response: reply.message,\n toolCalls: reply.toolCalls,\n latencyMs: reply.latencyMs,\n });\n } else {\n printBlank();\n console.log(chalk.dim('You >'), message);\n if (reply.toolCalls?.length) {\n console.log(chalk.dim(' Tools:'));\n printToolCalls(reply.toolCalls);\n }\n console.log(chalk.cyan('Agent >'), reply.message);\n if (reply.latencyMs) {\n printBlank();\n printInfo(`Response time: ${reply.latencyMs}ms`);\n }\n printBlank();\n }\n }\n } finally {\n await chat.end();\n }\n } catch (error) {\n spinner.fail('Chat failed');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Interactive mode: readline prompt loop with agent\n * Uses SDK LiveChat for session management\n */\nasync function handleInteractiveChat(\n sdk: ChanlSDK,\n agentId: string,\n stream?: boolean,\n): Promise<void> {\n const spinner = ora('Connecting to agent...').start();\n\n try {\n const chat = await sdk.chat.session(agentId);\n const agentName = chat.agentName || 'Agent';\n spinner.succeed(`Connected to ${agentName}${stream ? ' (streaming)' : ''}`);\n printInfo(\"Type 'exit' or 'quit' to end the session. Ctrl+C also works.\");\n printBlank();\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const prompt = (): void => {\n rl.question(chalk.bold('You > '), async (input) => {\n const trimmed = input.trim();\n\n if (!trimmed) {\n prompt();\n return;\n }\n\n if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {\n await endSessionCleanly(chat);\n rl.close();\n return;\n }\n\n try {\n if (stream) {\n // Streaming: show text character-by-character\n process.stdout.write(chalk.cyan('Agent > '));\n const reply = await chat.send(trimmed, {\n stream: true,\n onChunk: (delta) => process.stdout.write(delta),\n });\n console.log(); // newline after stream\n if (reply.latencyMs) {\n printInfo(`${reply.latencyMs}ms`);\n }\n } else {\n const reply = await chat.send(trimmed);\n if (reply.toolCalls?.length) {\n console.log(chalk.dim(' Tools:'));\n printToolCalls(reply.toolCalls);\n }\n console.log(chalk.cyan('Agent >'), reply.message);\n }\n printBlank();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Send failed', errorMessage);\n printBlank();\n }\n\n prompt();\n });\n };\n\n // Handle Ctrl+C gracefully\n const handleSigint = async (): Promise<void> => {\n printBlank();\n await endSessionCleanly(chat);\n rl.close();\n process.exit(0);\n };\n\n rl.on('SIGINT', () => {\n void handleSigint();\n });\n\n rl.on('close', () => {\n // Readline closed (e.g., piped input ended)\n });\n\n prompt();\n } catch (error) {\n spinner.fail('Failed to connect');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Display tool calls in a readable format\n */\nfunction printToolCalls(toolCalls: ToolCall[]): void {\n for (const tc of toolCalls) {\n const statusIcon =\n tc.status === 'success'\n ? chalk.green('✓')\n : tc.status === 'error'\n ? chalk.red('✗')\n : tc.status === 'timeout'\n ? chalk.yellow('⏱')\n : chalk.dim('…');\n const duration = tc.durationMs ? chalk.dim(` (${tc.durationMs}ms)`) : '';\n const params = Object.keys(tc.parameters).length\n ? chalk.dim(` ${JSON.stringify(tc.parameters)}`)\n : '';\n\n console.log(` ${statusIcon} ${chalk.yellow(tc.name)}${params}${duration}`);\n\n if (tc.status === 'error' && tc.error) {\n console.log(` ${chalk.red(tc.error.message)}`);\n }\n }\n}\n\n/**\n * End a chat session with user feedback\n */\nasync function endSessionCleanly(\n chat: { end: () => Promise<void> },\n): Promise<void> {\n try {\n await chat.end();\n printSuccess('Session ended');\n } catch {\n printInfo('Session closed');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/chat.ts"],"sourcesContent":["import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { createInterface } from 'node:readline';\nimport type { ChanlSDK, ToolCall } from '@chanl/sdk';\nimport { createSdk } from '../utils/sdk-factory.js';\nimport {\n printError,\n printSuccess,\n printInfo,\n printBlank,\n isJsonOutput,\n printJson,\n} from '../utils/output.js';\n\n/**\n * Create the chat command\n */\nexport function createChatCommand(): Command {\n const chat = new Command('chat')\n .description('Start an interactive chat session with an agent')\n .argument('<agent-id>', 'ID of the agent to chat with')\n .option('-m, --message <text>', 'Send a single message and exit')\n .option('-s, --stream', 'Enable streaming responses (real-time text)')\n .option('-w, --workspace <id>', 'Workspace ID override')\n .addHelpText(\n 'after',\n `\nWhat is Chat?\n Chat lets you test an AI agent via text in your terminal. It creates a session\n on the Chanl platform, which manages the full lifecycle including\n transcription, billing, and post-chat evaluation.\n\nModes:\n Interactive (default): Opens a prompt loop — type messages, see agent responses.\n Type 'exit', 'quit', or press Ctrl+C to end.\n\n Single message: Send one message with --message, print response, exit.\n\nStreaming:\n Use --stream for real-time text output. Text appears as the agent generates it.\n Note: streaming responses do not include tool call metadata.\n\nExamples:\n $ chanl chat abc123 # Interactive chat\n $ chanl chat abc123 --stream # Interactive with streaming\n $ chanl chat abc123 --message \"Hello\" # Single message mode\n $ chanl chat abc123 --message \"Hi\" --stream # Single message, streamed\n $ chanl chat abc123 --message \"Hi\" --json # Single message, JSON output\n $ chanl chat abc123 --workspace ws_456 # Override workspace`\n )\n .action(handleChat);\n\n return chat;\n}\n\n/**\n * Handle the chat command\n */\nasync function handleChat(\n agentId: string,\n options: { message?: string; stream?: boolean; workspace?: string },\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n if (options.message) {\n await handleSingleMessage(sdk, agentId, options.message, options.stream);\n } else {\n await handleInteractiveChat(sdk, agentId, options.stream);\n }\n}\n\n/**\n * Single message mode: create session, send message, print response, end session\n * Uses SDK LiveChat for session management\n */\nasync function handleSingleMessage(\n sdk: ChanlSDK,\n agentId: string,\n message: string,\n stream?: boolean,\n): Promise<void> {\n const spinner = ora('Connecting to agent...').start();\n\n try {\n const chat = await sdk.chat.session(agentId);\n spinner.text = 'Sending message...';\n\n try {\n if (stream && !isJsonOutput()) {\n // Streaming mode: show text as it arrives\n spinner.stop();\n printBlank();\n console.log(chalk.dim('You >'), message);\n process.stdout.write(chalk.cyan('Agent > '));\n\n const reply = await chat.send(message, {\n stream: true,\n onChunk: (delta) => process.stdout.write(delta),\n });\n\n // Newline after streamed text\n console.log();\n if (reply.latencyMs) {\n printBlank();\n printInfo(`Response time: ${reply.latencyMs}ms`);\n }\n printBlank();\n } else {\n // Non-streaming or JSON mode\n const reply = await chat.send(message);\n spinner.stop();\n\n if (isJsonOutput()) {\n printJson({\n sessionId: chat.sessionId,\n agentId,\n message,\n response: reply.message,\n toolCalls: reply.toolCalls,\n latencyMs: reply.latencyMs,\n });\n } else {\n printBlank();\n console.log(chalk.dim('You >'), message);\n if (reply.toolCalls?.length) {\n console.log(chalk.dim(' Tools:'));\n printToolCalls(reply.toolCalls);\n }\n console.log(chalk.cyan('Agent >'), reply.message);\n if (reply.latencyMs) {\n printBlank();\n printInfo(`Response time: ${reply.latencyMs}ms`);\n }\n printBlank();\n }\n }\n } finally {\n await chat.end();\n }\n } catch (error) {\n spinner.fail('Chat failed');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Interactive mode: readline prompt loop with agent\n * Uses SDK LiveChat for session management\n */\nasync function handleInteractiveChat(\n sdk: ChanlSDK,\n agentId: string,\n stream?: boolean,\n): Promise<void> {\n const spinner = ora('Connecting to agent...').start();\n\n try {\n const chat = await sdk.chat.session(agentId);\n const agentName = chat.agentName || 'Agent';\n spinner.succeed(`Connected to ${agentName}${stream ? ' (streaming)' : ''}`);\n printInfo(\"Type 'exit' or 'quit' to end the session. Ctrl+C also works.\");\n printBlank();\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const prompt = (): void => {\n rl.question(chalk.bold('You > '), async (input) => {\n const trimmed = input.trim();\n\n if (!trimmed) {\n prompt();\n return;\n }\n\n if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {\n await endSessionCleanly(chat);\n rl.close();\n return;\n }\n\n try {\n if (stream) {\n // Streaming: show text character-by-character\n process.stdout.write(chalk.cyan('Agent > '));\n const reply = await chat.send(trimmed, {\n stream: true,\n onChunk: (delta) => process.stdout.write(delta),\n });\n console.log(); // newline after stream\n if (reply.latencyMs) {\n printInfo(`${reply.latencyMs}ms`);\n }\n } else {\n const reply = await chat.send(trimmed);\n if (reply.toolCalls?.length) {\n console.log(chalk.dim(' Tools:'));\n printToolCalls(reply.toolCalls);\n }\n console.log(chalk.cyan('Agent >'), reply.message);\n }\n printBlank();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Send failed', errorMessage);\n printBlank();\n }\n\n prompt();\n });\n };\n\n // Handle Ctrl+C gracefully\n const handleSigint = async (): Promise<void> => {\n printBlank();\n await endSessionCleanly(chat);\n rl.close();\n process.exit(0);\n };\n\n rl.on('SIGINT', () => {\n void handleSigint();\n });\n\n rl.on('close', () => {\n // Readline closed (e.g., piped input ended)\n });\n\n prompt();\n } catch (error) {\n spinner.fail('Failed to connect');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Display tool calls in a readable format\n */\nfunction printToolCalls(toolCalls: ToolCall[]): void {\n for (const tc of toolCalls) {\n const statusIcon =\n tc.status === 'success'\n ? chalk.green('✓')\n : tc.status === 'error'\n ? chalk.red('✗')\n : tc.status === 'timeout'\n ? chalk.yellow('⏱')\n : chalk.dim('…');\n const duration = tc.durationMs ? chalk.dim(` (${tc.durationMs}ms)`) : '';\n const params = Object.keys(tc.parameters).length\n ? chalk.dim(` ${JSON.stringify(tc.parameters)}`)\n : '';\n\n console.log(` ${statusIcon} ${chalk.yellow(tc.name)}${params}${duration}`);\n\n if (tc.status === 'error' && tc.error) {\n console.log(` ${chalk.red(tc.error.message)}`);\n }\n }\n}\n\n/**\n * End a chat session with user feedback\n */\nasync function endSessionCleanly(\n chat: { end: () => Promise<void> },\n): Promise<void> {\n try {\n await chat.end();\n printSuccess('Session ended');\n } catch {\n printInfo('Session closed');\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/commands/config.js
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
const VALID_KEYS = [
|
|
15
15
|
"apiKey",
|
|
16
16
|
"baseUrl",
|
|
17
|
+
"server",
|
|
18
|
+
"deployment",
|
|
19
|
+
"apiPrefix",
|
|
17
20
|
"workspaceId",
|
|
18
21
|
"defaultFormat"
|
|
19
22
|
];
|
|
@@ -25,6 +28,9 @@ const KEY_DESCRIPTIONS = {
|
|
|
25
28
|
jwtToken: "JWT access token (from browser login)",
|
|
26
29
|
refreshToken: "JWT refresh token (from browser login)",
|
|
27
30
|
baseUrl: "Base URL for the Chanl API",
|
|
31
|
+
server: "Alias for baseUrl (e.g. http://localhost:18005)",
|
|
32
|
+
deployment: "local | cloud \u2014 local rewrites /api/v1 paths for OSS scenario servers",
|
|
33
|
+
apiPrefix: "Override API prefix; empty string for flat /scenarios routes",
|
|
28
34
|
workspaceId: "Default workspace ID",
|
|
29
35
|
appUrl: "App URL for browser-based flows",
|
|
30
36
|
defaultFormat: "Default output format (table or json)"
|
|
@@ -143,16 +149,24 @@ function handleConfigSet(key, value) {
|
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
configStore.set(typedKey, value);
|
|
152
|
+
const clearedServer = typedKey === "baseUrl" && configStore.has("server");
|
|
153
|
+
if (clearedServer) {
|
|
154
|
+
configStore.delete("server");
|
|
155
|
+
}
|
|
146
156
|
if (isJsonOutput()) {
|
|
147
157
|
printJson({
|
|
148
158
|
success: true,
|
|
149
159
|
key,
|
|
150
|
-
value: key === "apiKey" ? maskString(value) : value
|
|
160
|
+
value: key === "apiKey" ? maskString(value) : value,
|
|
161
|
+
...clearedServer && { clearedServer: true }
|
|
151
162
|
});
|
|
152
163
|
return;
|
|
153
164
|
}
|
|
154
165
|
const displayValue = key === "apiKey" ? maskString(value) : value;
|
|
155
166
|
printSuccess(`Set ${key} = ${displayValue}`);
|
|
167
|
+
if (clearedServer) {
|
|
168
|
+
printLabel("Note", "cleared 'server' (alias for baseUrl) so this change takes effect");
|
|
169
|
+
}
|
|
156
170
|
}
|
|
157
171
|
function handleConfigDelete(key) {
|
|
158
172
|
if (!isValidKey(key)) {
|
|
@@ -215,15 +229,23 @@ function handleConfigUse(environment) {
|
|
|
215
229
|
return;
|
|
216
230
|
}
|
|
217
231
|
configStore.set("baseUrl", url);
|
|
232
|
+
const clearedServer = configStore.has("server");
|
|
233
|
+
if (clearedServer) {
|
|
234
|
+
configStore.delete("server");
|
|
235
|
+
}
|
|
218
236
|
if (isJsonOutput()) {
|
|
219
237
|
printJson({
|
|
220
238
|
success: true,
|
|
221
239
|
environment: env,
|
|
222
|
-
baseUrl: url
|
|
240
|
+
baseUrl: url,
|
|
241
|
+
...clearedServer && { clearedServer: true }
|
|
223
242
|
});
|
|
224
243
|
return;
|
|
225
244
|
}
|
|
226
245
|
printSuccess(`Switched to ${env}: ${url}`);
|
|
246
|
+
if (clearedServer) {
|
|
247
|
+
printLabel("Note", "cleared 'server' (alias for baseUrl) so this change takes effect");
|
|
248
|
+
}
|
|
227
249
|
}
|
|
228
250
|
export {
|
|
229
251
|
createConfigCommand
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/config.ts"],"sourcesContent":["import { Command } from 'commander';\nimport chalk from 'chalk';\nimport { configStore, type ChanlCliConfig } from '../utils/config-store.js';\nimport {\n printSuccess,\n printError,\n printLabel,\n printBlank,\n printTable,\n isJsonOutput,\n printJson,\n maskString,\n} from '../utils/output.js';\n\n/**\n * Valid configuration keys\n */\nconst VALID_KEYS: (keyof ChanlCliConfig)[] = [\n 'apiKey',\n 'baseUrl',\n 'workspaceId',\n 'defaultFormat',\n];\n\n/**\n * Environment presets for quick switching\n * Only production is exposed publicly. Use `chanl config set baseUrl <url>` for custom endpoints.\n */\nconst ENVIRONMENT_PRESETS: Record<string, string> = {\n production: 'https://platform.chanl.ai',\n};\n\n/**\n * Configuration key descriptions\n */\nconst KEY_DESCRIPTIONS: Record<keyof ChanlCliConfig, string> = {\n apiKey: 'Chanl API key for authentication',\n jwtToken: 'JWT access token (from browser login)',\n refreshToken: 'JWT refresh token (from browser login)',\n baseUrl: 'Base URL for the Chanl API',\n workspaceId: 'Default workspace ID',\n appUrl: 'App URL for browser-based flows',\n defaultFormat: 'Default output format (table or json)',\n};\n\n/**\n * Create the config command group\n */\nexport function createConfigCommand(): Command {\n const config = new Command('config').description('Manage CLI configuration');\n\n // `chanl config list` subcommand\n config\n .command('list')\n .description('List all configuration values')\n .action(handleConfigList);\n\n // `chanl config get <key>` subcommand\n config\n .command('get <key>')\n .description('Get a configuration value')\n .action(handleConfigGet);\n\n // `chanl config set <key> <value>` subcommand\n config\n .command('set <key> <value>')\n .description('Set a configuration value')\n .action(handleConfigSet);\n\n // `chanl config delete <key>` subcommand\n config\n .command('delete <key>')\n .alias('unset')\n .description('Delete a configuration value')\n .action(handleConfigDelete);\n\n // `chanl config path` subcommand\n config\n .command('path')\n .description('Show path to configuration file')\n .action(handleConfigPath);\n\n // `chanl config use <environment>` subcommand\n config\n .command('use <environment>')\n .description('Reset to production API (or set a custom base URL with `config set baseUrl`)')\n .action(handleConfigUse);\n\n return config;\n}\n\n/**\n * Handle config list command\n */\nfunction handleConfigList(): void {\n const allConfig = configStore.getAll();\n\n if (isJsonOutput()) {\n // Mask sensitive values in JSON output\n const safeConfig = {\n ...allConfig,\n apiKey: allConfig.apiKey ? maskString(allConfig.apiKey) : undefined,\n };\n printJson(safeConfig);\n return;\n }\n\n printBlank();\n\n const rows = VALID_KEYS.map((key) => {\n let value = allConfig[key];\n const source = getConfigSource(key);\n\n // Mask API key\n if (key === 'apiKey' && typeof value === 'string') {\n value = maskString(value);\n }\n\n return {\n key,\n value: value ?? chalk.dim('(not set)'),\n source,\n description: KEY_DESCRIPTIONS[key],\n };\n });\n\n printTable(\n [\n { header: 'Key', key: 'key', width: 15 },\n { header: 'Value', key: 'value', width: 30 },\n { header: 'Source', key: 'source', width: 10 },\n ],\n rows\n );\n\n printBlank();\n printLabel('Config file', configStore.getPath());\n printBlank();\n}\n\n/**\n * Handle config get command\n */\nfunction handleConfigGet(key: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n let value: string | undefined;\n\n // Get value with env var fallback\n switch (typedKey) {\n case 'apiKey':\n value = configStore.getApiKey();\n break;\n case 'baseUrl':\n value = configStore.getBaseUrl();\n break;\n case 'workspaceId':\n value = configStore.getWorkspaceId();\n break;\n default:\n value = configStore.get(typedKey) as string | undefined;\n }\n\n if (isJsonOutput()) {\n printJson({\n key,\n value: key === 'apiKey' && value ? maskString(value) : value,\n source: getConfigSource(typedKey),\n });\n return;\n }\n\n if (value === undefined) {\n console.log(chalk.dim('(not set)'));\n return;\n }\n\n // Mask API key even in regular output\n if (key === 'apiKey') {\n console.log(maskString(value));\n } else {\n console.log(value);\n }\n}\n\n/**\n * Handle config set command\n */\nfunction handleConfigSet(key: string, value: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n\n // Validate specific keys\n if (typedKey === 'defaultFormat') {\n if (value !== 'table' && value !== 'json') {\n printError(`Invalid format: ${value}`);\n printLabel('Valid values', 'table, json');\n process.exitCode = 1;\n return;\n }\n }\n\n if (typedKey === 'baseUrl') {\n try {\n new URL(value);\n } catch {\n printError(`Invalid URL: ${value}`);\n process.exitCode = 1;\n return;\n }\n }\n\n // Set the value\n configStore.set(typedKey, value as never);\n\n if (isJsonOutput()) {\n printJson({\n success: true,\n key,\n value: key === 'apiKey' ? maskString(value) : value,\n });\n return;\n }\n\n const displayValue = key === 'apiKey' ? maskString(value) : value;\n printSuccess(`Set ${key} = ${displayValue}`);\n}\n\n/**\n * Handle config delete command\n */\nfunction handleConfigDelete(key: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n\n // Check if key exists\n if (!configStore.has(typedKey)) {\n if (isJsonOutput()) {\n printJson({ success: true, key, message: 'Key was not set' });\n } else {\n printSuccess(`${key} was not set`);\n }\n return;\n }\n\n configStore.delete(typedKey);\n\n if (isJsonOutput()) {\n printJson({ success: true, key, message: 'Deleted' });\n return;\n }\n\n printSuccess(`Deleted ${key}`);\n}\n\n/**\n * Handle config path command\n */\nfunction handleConfigPath(): void {\n const path = configStore.getPath();\n\n if (isJsonOutput()) {\n printJson({ path });\n return;\n }\n\n console.log(path);\n}\n\n/**\n * Check if a key is a valid configuration key\n */\nfunction isValidKey(key: string): key is keyof ChanlCliConfig {\n return VALID_KEYS.includes(key as keyof ChanlCliConfig);\n}\n\n/**\n * Get the source of a configuration value\n */\nfunction getConfigSource(key: keyof ChanlCliConfig): string {\n const envVarMap: Partial<Record<keyof ChanlCliConfig, string>> = {\n apiKey: 'CHANL_API_KEY',\n baseUrl: 'CHANL_BASE_URL',\n workspaceId: 'CHANL_WORKSPACE_ID',\n };\n\n const envVar = envVarMap[key];\n if (envVar && process.env[envVar]) {\n return chalk.cyan('env');\n }\n\n if (configStore.has(key)) {\n return chalk.green('config');\n }\n\n return chalk.dim('default');\n}\n\n/**\n * Handle config use command - switch to a preset environment\n */\nfunction handleConfigUse(environment: string): void {\n const env = environment.toLowerCase();\n const url = ENVIRONMENT_PRESETS[env];\n\n if (!url) {\n printError(`Unknown environment: ${environment}`);\n printBlank();\n printLabel('Available environments', Object.keys(ENVIRONMENT_PRESETS).join(', '));\n process.exitCode = 1;\n return;\n }\n\n configStore.set('baseUrl', url);\n\n if (isJsonOutput()) {\n printJson({\n success: true,\n environment: env,\n baseUrl: url,\n });\n return;\n }\n\n printSuccess(`Switched to ${env}: ${url}`);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/config.ts"],"sourcesContent":["import { Command } from 'commander';\nimport chalk from 'chalk';\nimport { configStore, type ChanlCliConfig } from '../utils/config-store.js';\nimport {\n printSuccess,\n printError,\n printLabel,\n printBlank,\n printTable,\n isJsonOutput,\n printJson,\n maskString,\n} from '../utils/output.js';\n\n/**\n * Valid configuration keys\n */\nconst VALID_KEYS: (keyof ChanlCliConfig)[] = [\n 'apiKey',\n 'baseUrl',\n 'server',\n 'deployment',\n 'apiPrefix',\n 'workspaceId',\n 'defaultFormat',\n];\n\n/**\n * Environment presets for quick switching\n * Only production is exposed publicly. Use `chanl config set baseUrl <url>` for custom endpoints.\n */\nconst ENVIRONMENT_PRESETS: Record<string, string> = {\n production: 'https://platform.chanl.ai',\n};\n\n/**\n * Configuration key descriptions\n */\nconst KEY_DESCRIPTIONS: Record<keyof ChanlCliConfig, string> = {\n apiKey: 'Chanl API key for authentication',\n jwtToken: 'JWT access token (from browser login)',\n refreshToken: 'JWT refresh token (from browser login)',\n baseUrl: 'Base URL for the Chanl API',\n server: 'Alias for baseUrl (e.g. http://localhost:18005)',\n deployment: 'local | cloud — local rewrites /api/v1 paths for OSS scenario servers',\n apiPrefix: 'Override API prefix; empty string for flat /scenarios routes',\n workspaceId: 'Default workspace ID',\n appUrl: 'App URL for browser-based flows',\n defaultFormat: 'Default output format (table or json)',\n};\n\n/**\n * Create the config command group\n */\nexport function createConfigCommand(): Command {\n const config = new Command('config').description('Manage CLI configuration');\n\n // `chanl config list` subcommand\n config\n .command('list')\n .description('List all configuration values')\n .action(handleConfigList);\n\n // `chanl config get <key>` subcommand\n config\n .command('get <key>')\n .description('Get a configuration value')\n .action(handleConfigGet);\n\n // `chanl config set <key> <value>` subcommand\n config\n .command('set <key> <value>')\n .description('Set a configuration value')\n .action(handleConfigSet);\n\n // `chanl config delete <key>` subcommand\n config\n .command('delete <key>')\n .alias('unset')\n .description('Delete a configuration value')\n .action(handleConfigDelete);\n\n // `chanl config path` subcommand\n config\n .command('path')\n .description('Show path to configuration file')\n .action(handleConfigPath);\n\n // `chanl config use <environment>` subcommand\n config\n .command('use <environment>')\n .description('Reset to production API (or set a custom base URL with `config set baseUrl`)')\n .action(handleConfigUse);\n\n return config;\n}\n\n/**\n * Handle config list command\n */\nfunction handleConfigList(): void {\n const allConfig = configStore.getAll();\n\n if (isJsonOutput()) {\n // Mask sensitive values in JSON output\n const safeConfig = {\n ...allConfig,\n apiKey: allConfig.apiKey ? maskString(allConfig.apiKey) : undefined,\n };\n printJson(safeConfig);\n return;\n }\n\n printBlank();\n\n const rows = VALID_KEYS.map((key) => {\n let value = allConfig[key];\n const source = getConfigSource(key);\n\n // Mask API key\n if (key === 'apiKey' && typeof value === 'string') {\n value = maskString(value);\n }\n\n return {\n key,\n value: value ?? chalk.dim('(not set)'),\n source,\n description: KEY_DESCRIPTIONS[key],\n };\n });\n\n printTable(\n [\n { header: 'Key', key: 'key', width: 15 },\n { header: 'Value', key: 'value', width: 30 },\n { header: 'Source', key: 'source', width: 10 },\n ],\n rows\n );\n\n printBlank();\n printLabel('Config file', configStore.getPath());\n printBlank();\n}\n\n/**\n * Handle config get command\n */\nfunction handleConfigGet(key: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n let value: string | undefined;\n\n // Get value with env var fallback\n switch (typedKey) {\n case 'apiKey':\n value = configStore.getApiKey();\n break;\n case 'baseUrl':\n value = configStore.getBaseUrl();\n break;\n case 'workspaceId':\n value = configStore.getWorkspaceId();\n break;\n default:\n value = configStore.get(typedKey) as string | undefined;\n }\n\n if (isJsonOutput()) {\n printJson({\n key,\n value: key === 'apiKey' && value ? maskString(value) : value,\n source: getConfigSource(typedKey),\n });\n return;\n }\n\n if (value === undefined) {\n console.log(chalk.dim('(not set)'));\n return;\n }\n\n // Mask API key even in regular output\n if (key === 'apiKey') {\n console.log(maskString(value));\n } else {\n console.log(value);\n }\n}\n\n/**\n * Handle config set command\n */\nfunction handleConfigSet(key: string, value: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n\n // Validate specific keys\n if (typedKey === 'defaultFormat') {\n if (value !== 'table' && value !== 'json') {\n printError(`Invalid format: ${value}`);\n printLabel('Valid values', 'table, json');\n process.exitCode = 1;\n return;\n }\n }\n\n if (typedKey === 'baseUrl') {\n try {\n new URL(value);\n } catch {\n printError(`Invalid URL: ${value}`);\n process.exitCode = 1;\n return;\n }\n }\n\n // Set the value\n configStore.set(typedKey, value as never);\n\n // `server` is an alias for `baseUrl` that takes priority in getBaseUrl().\n // Setting baseUrl while a stale `server` is still around would silently\n // have no effect, so clear it here to keep the two in sync.\n const clearedServer = typedKey === 'baseUrl' && configStore.has('server');\n if (clearedServer) {\n configStore.delete('server');\n }\n\n if (isJsonOutput()) {\n printJson({\n success: true,\n key,\n value: key === 'apiKey' ? maskString(value) : value,\n ...(clearedServer && { clearedServer: true }),\n });\n return;\n }\n\n const displayValue = key === 'apiKey' ? maskString(value) : value;\n printSuccess(`Set ${key} = ${displayValue}`);\n if (clearedServer) {\n printLabel('Note', \"cleared 'server' (alias for baseUrl) so this change takes effect\");\n }\n}\n\n/**\n * Handle config delete command\n */\nfunction handleConfigDelete(key: string): void {\n if (!isValidKey(key)) {\n printError(`Unknown configuration key: ${key}`);\n printBlank();\n printLabel('Valid keys', VALID_KEYS.join(', '));\n process.exitCode = 1;\n return;\n }\n\n const typedKey = key as keyof ChanlCliConfig;\n\n // Check if key exists\n if (!configStore.has(typedKey)) {\n if (isJsonOutput()) {\n printJson({ success: true, key, message: 'Key was not set' });\n } else {\n printSuccess(`${key} was not set`);\n }\n return;\n }\n\n configStore.delete(typedKey);\n\n if (isJsonOutput()) {\n printJson({ success: true, key, message: 'Deleted' });\n return;\n }\n\n printSuccess(`Deleted ${key}`);\n}\n\n/**\n * Handle config path command\n */\nfunction handleConfigPath(): void {\n const path = configStore.getPath();\n\n if (isJsonOutput()) {\n printJson({ path });\n return;\n }\n\n console.log(path);\n}\n\n/**\n * Check if a key is a valid configuration key\n */\nfunction isValidKey(key: string): key is keyof ChanlCliConfig {\n return VALID_KEYS.includes(key as keyof ChanlCliConfig);\n}\n\n/**\n * Get the source of a configuration value\n */\nfunction getConfigSource(key: keyof ChanlCliConfig): string {\n const envVarMap: Partial<Record<keyof ChanlCliConfig, string>> = {\n apiKey: 'CHANL_API_KEY',\n baseUrl: 'CHANL_BASE_URL',\n workspaceId: 'CHANL_WORKSPACE_ID',\n };\n\n const envVar = envVarMap[key];\n if (envVar && process.env[envVar]) {\n return chalk.cyan('env');\n }\n\n if (configStore.has(key)) {\n return chalk.green('config');\n }\n\n return chalk.dim('default');\n}\n\n/**\n * Handle config use command - switch to a preset environment\n */\nfunction handleConfigUse(environment: string): void {\n const env = environment.toLowerCase();\n const url = ENVIRONMENT_PRESETS[env];\n\n if (!url) {\n printError(`Unknown environment: ${environment}`);\n printBlank();\n printLabel('Available environments', Object.keys(ENVIRONMENT_PRESETS).join(', '));\n process.exitCode = 1;\n return;\n }\n\n configStore.set('baseUrl', url);\n\n // Same reasoning as `config set baseUrl`: `server` overrides baseUrl in\n // getBaseUrl(), so leaving a stale `server` in place would make this\n // command a no-op from the caller's perspective.\n const clearedServer = configStore.has('server');\n if (clearedServer) {\n configStore.delete('server');\n }\n\n if (isJsonOutput()) {\n printJson({\n success: true,\n environment: env,\n baseUrl: url,\n ...(clearedServer && { clearedServer: true }),\n });\n return;\n }\n\n printSuccess(`Switched to ${env}: ${url}`);\n if (clearedServer) {\n printLabel('Note', \"cleared 'server' (alias for baseUrl) so this change takes effect\");\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/commands/mcp.js
CHANGED
package/dist/commands/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/mcp.ts"],"sourcesContent":["import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport type { McpConfigResponse, McpStatusResponse, McpTool, McpToolCallResult } from '@chanl/sdk';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport { configStore } from '../utils/config-store.js';\nimport { createSdk } from '../utils/sdk-factory.js';\nimport {\n printSuccess,\n printError,\n printInfo,\n printLabel,\n printBlank,\n printSimpleTable,\n isJsonOutput,\n printJson,\n} from '../utils/output.js';\n\n/**\n * Create the MCP command group\n */\nexport function createMcpCommand(): Command {\n const mcp = new Command('mcp')\n .description('MCP server configuration and status')\n .addHelpText(\n 'after',\n `\nWhat is MCP?\n Model Context Protocol (MCP) lets AI agents (Claude, Cursor, etc.) use your\n Chanl tools, knowledge base, and prompts. The MCP server bridges your workspace\n with AI clients.\n\nQuick Start:\n $ chanl mcp # Check server status\n $ chanl mcp test # Run protocol test\n $ chanl mcp config # Get config for your AI client\n\nConnecting AI Clients:\n 1. Run 'chanl mcp config --format claude' to get Claude Desktop config\n 2. Add the JSON to Claude Desktop's settings\n 3. Your Chanl tools will appear in Claude!`\n )\n .action(handleMcpDefault); // Default action when no subcommand\n\n // mcp info\n mcp\n .command('info')\n .description('Show MCP server URL, status, and resource counts')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp info # Show server details\n $ chanl mcp info --json # Output as JSON`\n )\n .action(handleMcpInfo);\n\n // mcp status\n mcp\n .command('status')\n .description('Health check the MCP server')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp status # Quick health check`\n )\n .action(handleMcpStatus);\n\n // mcp test\n mcp\n .command('test')\n .description('Run MCP protocol test with real MCP client')\n .addHelpText(\n 'after',\n `\nTests the full MCP protocol handshake:\n 1. Connect to MCP server\n 2. List available tools\n 3. List resources (knowledge base)\n 4. List prompts\n\nExample:\n $ chanl mcp test # Run all protocol tests\n $ chanl mcp test --json # Output results as JSON`\n )\n .action(handleMcpTest);\n\n // mcp config\n mcp\n .command('config')\n .description('Output MCP configuration for AI clients')\n .option('-f, --format <format>', 'Format: claude, cursor, generic', 'generic')\n .addHelpText(\n 'after',\n `\nGenerates configuration JSON for connecting AI clients to your MCP server.\n\nSupported formats:\n claude - Claude Desktop (claude_desktop_config.json)\n cursor - Cursor IDE settings\n generic - Generic MCP client format\n\nExamples:\n $ chanl mcp config # Generic format\n $ chanl mcp config --format claude # Claude Desktop format\n $ chanl mcp config --format cursor # Cursor IDE format\n $ chanl mcp config --format claude > config.json # Save to file\n\nSetup Claude Desktop:\n 1. Run: chanl mcp config --format claude\n 2. Copy the JSON output\n 3. Paste into ~/Library/Application Support/Claude/claude_desktop_config.json`\n )\n .action(handleMcpConfig);\n\n // mcp tools <agent-id>\n mcp\n .command('tools <agent-id>')\n .description('List MCP tools available for an agent')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp tools agent_123 # List tools for agent\n $ chanl mcp tools agent_123 --json # Output as JSON`\n )\n .action(handleMcpTools);\n\n // mcp call <agent-id> <tool-name> [args]\n mcp\n .command('call <agent-id> <tool-name> [args]')\n .description('Call an MCP tool by name')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp call agent_123 get_weather '{\"city\":\"London\"}'\n $ chanl mcp call agent_123 ping\n $ chanl mcp call agent_123 get_weather '{\"city\":\"London\"}' --json`\n )\n .action(handleMcpCall);\n\n // mcp inspect <agent-id> <tool-name>\n mcp\n .command('inspect <agent-id> <tool-name>')\n .description('Show schema and details for a specific MCP tool')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp inspect agent_123 get_weather\n $ chanl mcp inspect agent_123 get_weather --json`\n )\n .action(handleMcpInspect);\n\n // mcp install\n mcp\n .command('install')\n .description('Generate MCP config snippet for editors (Cursor, VSCode, Claude)')\n .option('-f, --format <format>', 'Format: claude, cursor, vscode, generic', 'generic')\n .addHelpText(\n 'after',\n `\nGenerates a ready-to-use MCP configuration using your stored API key and workspace.\nNo server call needed — works offline.\n\nSupported formats:\n claude - Claude Desktop / Claude Code\n cursor - Cursor IDE\n vscode - Visual Studio Code\n generic - Generic MCP client (default)\n\nExamples:\n $ chanl mcp install # Generic format\n $ chanl mcp install --format claude # Claude Desktop format\n $ chanl mcp install --format cursor # Cursor IDE format\n $ chanl mcp install --format vscode # VSCode format\n $ chanl mcp install --format claude > mcp.json # Save to file`\n )\n .action(handleMcpInstall);\n\n return mcp;\n}\n\n/**\n * Handle mcp info command\n */\nasync function handleMcpInfo(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP configuration...').start();\n\n try {\n const response = await sdk.mcp.getConfig();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP configuration', response.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = response.data;\n\n if (isJsonOutput()) {\n printJson(config);\n return;\n }\n\n printBlank();\n printSimpleTable(\n ['Property', 'Value'],\n [\n ['Server URL', config.serverUrl],\n ['Environment', config.environment],\n ['Status', config.status === 'active' ? chalk.green('active') : chalk.red('disabled')],\n ['Tools', config.toolCount.toString()],\n ['Resources', config.resourceCount.toString()],\n ['Prompts', config.promptCount.toString()],\n ...(config.toolsetName ? [['Toolset', config.toolsetName]] : []),\n ]\n );\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp status command\n */\nasync function handleMcpStatus(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Checking MCP server health...').start();\n\n try {\n const response = await sdk.mcp.getStatus();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to check MCP status', response.message);\n process.exitCode = 1;\n return;\n }\n\n const status: McpStatusResponse = response.data;\n\n if (isJsonOutput()) {\n printJson(status);\n return;\n }\n\n printBlank();\n if (status.healthy) {\n printSuccess('MCP server is healthy');\n printLabel('Message', status.message);\n if (status.responseTimeMs) {\n printLabel('Response Time', status.responseTimeMs + 'ms');\n }\n } else {\n printError('MCP server is unhealthy', status.message);\n if (status.error) {\n printLabel('Error', status.error);\n }\n process.exitCode = 1;\n }\n printBlank();\n } catch (error) {\n spinner.fail('Failed to check MCP status');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp config command\n */\nasync function handleMcpConfig(options: { format: string }): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP configuration...').start();\n\n try {\n const response = await sdk.mcp.getConfig();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP configuration', response.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = response.data;\n const format = options.format.toLowerCase();\n\n let output: unknown;\n\n switch (format) {\n case 'claude':\n output = config.agentConfigs.claude;\n break;\n case 'cursor':\n output = config.agentConfigs.cursor;\n break;\n case 'generic':\n default:\n output = config.agentConfigs.generic;\n break;\n }\n\n // Always output JSON for config command (it's meant to be piped/copied)\n printJson(output);\n\n if (!isJsonOutput()) {\n printBlank();\n printInfo(`Configuration for ${format} format. Copy and paste into your ${format === 'claude' ? 'Claude Desktop' : format === 'cursor' ? 'Cursor IDE' : 'MCP client'} configuration.`);\n printInfo(`Replace \\${CHANL_MCP_API_KEY} with your actual MCP API key.`);\n }\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle bare 'chanl mcp' command (default)\n * Shows combined info + status\n */\nasync function handleMcpDefault(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Checking MCP server...').start();\n\n try {\n // Fetch config and status in parallel\n const [configResponse, statusResponse] = await Promise.all([\n sdk.mcp.getConfig(),\n sdk.mcp.getStatus(),\n ]);\n\n spinner.stop();\n\n if (!configResponse.success || !configResponse.data) {\n printError('Failed to fetch MCP configuration', configResponse.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = configResponse.data;\n const status: McpStatusResponse | undefined = statusResponse.data;\n\n if (isJsonOutput()) {\n printJson({ config, status });\n return;\n }\n\n // Format status indicator\n const statusIcon = status?.healthy ? chalk.green('✓') : chalk.red('✗');\n const statusText = status?.healthy ? 'Connected' : 'Unhealthy';\n const latency = status?.responseTimeMs ? ` (${status.responseTimeMs}ms)` : '';\n\n printBlank();\n console.log(chalk.bold('MCP Server'));\n console.log(chalk.dim('─'.repeat(40)));\n printLabel('Server URL', config.serverUrl ?? 'Not configured');\n printLabel('Status', `${statusIcon} ${statusText}${latency}`);\n printLabel('Tools', `${config.toolCount ?? 0} enabled`);\n printLabel('Resources', (config.resourceCount ?? 0).toString());\n printLabel('Prompts', (config.promptCount ?? 0).toString());\n console.log(chalk.dim('─'.repeat(40)));\n printBlank();\n\n if (!status?.healthy) {\n printInfo(\"Run 'chanl mcp test' for detailed diagnostics\");\n process.exitCode = 1;\n }\n } catch (error) {\n spinner.fail('Failed to check MCP server');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp tools command\n * Lists MCP tools available for an agent\n */\nasync function handleMcpTools(agentId: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP tools...').start();\n\n try {\n const response = await sdk.mcp.listTools(agentId);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP tools', response.message);\n process.exitCode = 1;\n return;\n }\n\n const tools: McpTool[] = response.data.tools;\n\n if (isJsonOutput()) {\n printJson({ tools });\n return;\n }\n\n if (tools.length === 0) {\n printBlank();\n printInfo('No MCP tools available for this agent');\n printBlank();\n return;\n }\n\n printBlank();\n printSimpleTable(\n ['Name', 'Description'],\n tools.map((t) => [\n t.name,\n t.description || chalk.dim('No description'),\n ])\n );\n printBlank();\n printInfo(`Total: ${tools.length} tool${tools.length === 1 ? '' : 's'}`);\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch MCP tools');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp call command\n * Calls an MCP tool by name with optional JSON arguments\n */\nasync function handleMcpCall(agentId: string, toolName: string, argsJson?: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n // Parse args if provided\n let args: Record<string, unknown> | undefined;\n if (argsJson) {\n try {\n args = JSON.parse(argsJson) as Record<string, unknown>;\n } catch {\n printError('Invalid JSON arguments', `Could not parse: ${argsJson}`);\n process.exitCode = 1;\n return;\n }\n }\n\n const spinner = ora(`Calling tool '${toolName}'...`).start();\n\n try {\n const response = await sdk.mcp.callTool(agentId, toolName, args);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to call tool', response.message);\n process.exitCode = 1;\n return;\n }\n\n const result: McpToolCallResult = response.data;\n\n if (isJsonOutput()) {\n printJson(result);\n return;\n }\n\n // Check if tool returned an error\n if (result.isError) {\n for (const item of result.content) {\n if (item.text) {\n printError('Tool returned an error', item.text);\n }\n }\n process.exitCode = 1;\n return;\n }\n\n // Display result content\n printBlank();\n for (const item of result.content) {\n if (item.text) {\n console.log(item.text);\n } else {\n // Non-text content: display as JSON\n printJson(item);\n }\n }\n printBlank();\n } catch (error) {\n spinner.fail('Failed to call tool');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp inspect command\n * Shows schema and details for a specific MCP tool\n */\nasync function handleMcpInspect(agentId: string, toolName: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora(`Fetching tool '${toolName}'...`).start();\n\n try {\n const response = await sdk.mcp.getTool(agentId, toolName);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch tool details', response.message);\n process.exitCode = 1;\n return;\n }\n\n const tool: McpTool = response.data.tool;\n\n if (isJsonOutput()) {\n printJson(tool);\n return;\n }\n\n printBlank();\n console.log(chalk.bold(tool.name));\n console.log(chalk.dim('─'.repeat(40)));\n if (tool.description) {\n printLabel('Description', tool.description);\n }\n printBlank();\n\n // Display input schema\n console.log(chalk.bold('Input Schema'));\n console.log(chalk.dim('─'.repeat(40)));\n\n const schema = tool.inputSchema;\n const properties = schema['properties'] as Record<string, Record<string, unknown>> | undefined;\n const required = (schema['required'] as string[]) || [];\n\n if (properties) {\n const rows: string[][] = [];\n for (const [name, prop] of Object.entries(properties)) {\n const isRequired = required.includes(name);\n const typeStr = String(prop['type'] || 'any');\n const desc = String(prop['description'] || '');\n const reqStr = isRequired ? chalk.red('*') : ' ';\n rows.push([`${reqStr} ${name}`, typeStr, desc]);\n }\n printSimpleTable(['Parameter', 'Type', 'Description'], rows);\n } else {\n printInfo('No parameters defined');\n }\n\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch tool details');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp install command\n * Generates MCP config snippets for various editors, using stored API key\n */\nasync function handleMcpInstall(options: { format: string }): Promise<void> {\n const apiKey = configStore.getApiKey();\n if (!apiKey) {\n printError('Not authenticated', \"Run 'chanl login' to authenticate first\");\n process.exitCode = 1;\n return;\n }\n\n const baseUrl = configStore.getBaseUrl() || 'https://api.chanl.ai';\n // Derive MCP server URL from base URL\n // Production: https://api.chanl.ai -> workspace.chanl.app/mcp (generic)\n // For install, use the base URL with /mcp path\n const mcpUrl = baseUrl.replace(/\\/$/, '') + '/mcp';\n\n const format = options.format.toLowerCase();\n\n const serverConfig = {\n url: mcpUrl,\n headers: { 'X-API-Key': apiKey },\n transport: 'streamable-http' as const,\n };\n\n let output: unknown;\n\n switch (format) {\n case 'claude':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'cursor':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'vscode':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'generic':\n default:\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n }\n\n // Always output JSON (config is meant to be copied)\n printJson(output);\n\n if (!isJsonOutput()) {\n printBlank();\n const targetName =\n format === 'claude' ? 'Claude Desktop / Claude Code' :\n format === 'cursor' ? 'Cursor IDE' :\n format === 'vscode' ? 'Visual Studio Code' :\n 'your MCP client';\n printInfo(`Copy the JSON above into your ${targetName} configuration.`);\n printBlank();\n }\n}\n\n/**\n * Handle mcp test command\n * Runs real MCP protocol test using @modelcontextprotocol/sdk\n */\nasync function handleMcpTest(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n // First get the MCP config to know the server URL\n const spinner = ora('Fetching MCP configuration...').start();\n\n let config: McpConfigResponse;\n try {\n const configResponse = await sdk.mcp.getConfig();\n if (!configResponse.success || !configResponse.data) {\n spinner.fail('Failed to fetch MCP configuration');\n printError('Error', configResponse.message || 'Unknown error');\n process.exitCode = 1;\n return;\n }\n config = configResponse.data;\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n return;\n }\n\n spinner.text = 'Running MCP protocol test...';\n\n // Get API key for MCP authentication\n // Use MCP_API_KEY env var if set (for local dev), otherwise fall back to stored key\n const apiKey = process.env.MCP_API_KEY || configStore.getApiKey();\n if (!apiKey) {\n spinner.fail('No API key available');\n printError('Error', 'API key required for MCP test. Set MCP_API_KEY env var for local dev.');\n process.exitCode = 1;\n return;\n }\n\n const results: { test: string; passed: boolean; detail: string }[] = [];\n\n try {\n // Create MCP client with Streamable HTTP transport\n const transport = new StreamableHTTPClientTransport(new URL(config.serverUrl), {\n requestInit: {\n headers: { 'X-API-Key': apiKey },\n },\n });\n\n const client = new Client(\n { name: 'chanl-cli', version: '1.0.0' },\n { capabilities: {} }\n );\n\n // Test 1: Connect\n spinner.text = 'Connecting to MCP server...';\n const connectStart = Date.now();\n await client.connect(transport);\n const connectTime = Date.now() - connectStart;\n results.push({ test: 'Connection', passed: true, detail: `${connectTime}ms` });\n\n // Test 2: List tools\n spinner.text = 'Testing tools/list...';\n try {\n const tools = await client.listTools();\n results.push({ test: 'tools/list', passed: true, detail: `${tools.tools.length} tools` });\n } catch (err) {\n results.push({ test: 'tools/list', passed: false, detail: err instanceof Error ? err.message : 'Failed' });\n }\n\n // Test 3: List resources\n spinner.text = 'Testing resources/list...';\n try {\n const resources = await client.listResources();\n results.push({ test: 'resources/list', passed: true, detail: `${resources.resources.length} resources` });\n } catch (err) {\n // Resources may not be available (no knowledge base)\n results.push({ test: 'resources/list', passed: true, detail: 'N/A (no knowledge base)' });\n }\n\n // Test 4: List prompts\n spinner.text = 'Testing prompts/list...';\n try {\n const prompts = await client.listPrompts();\n results.push({ test: 'prompts/list', passed: true, detail: `${prompts.prompts.length} prompts` });\n } catch (err) {\n results.push({ test: 'prompts/list', passed: true, detail: 'N/A' });\n }\n\n await client.close();\n spinner.stop();\n\n // Output results\n if (isJsonOutput()) {\n printJson({ serverUrl: config.serverUrl, results });\n return;\n }\n\n printBlank();\n console.log(chalk.bold('🧪 MCP Protocol Test'));\n console.log(chalk.dim('─'.repeat(50)));\n printLabel('Server', config.serverUrl);\n console.log(chalk.dim('─'.repeat(50)));\n\n let allPassed = true;\n for (const result of results) {\n const icon = result.passed ? chalk.green('✓') : chalk.red('✗');\n console.log(`${icon} ${result.test}: ${result.detail}`);\n if (!result.passed) allPassed = false;\n }\n\n console.log(chalk.dim('─'.repeat(50)));\n\n if (allPassed) {\n printSuccess('All tests passed');\n } else {\n printError('Some tests failed', 'Check the results above');\n process.exitCode = 1;\n }\n printBlank();\n } catch (error) {\n spinner.fail('MCP protocol test failed');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Connection failed', message);\n printInfo('Make sure the MCP server is running and accessible');\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/mcp.ts"],"sourcesContent":["import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport type { McpConfigResponse, McpStatusResponse, McpTool, McpToolCallResult } from '@chanl/sdk';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport { configStore } from '../utils/config-store.js';\nimport { createSdk } from '../utils/sdk-factory.js';\nimport {\n printSuccess,\n printError,\n printInfo,\n printLabel,\n printBlank,\n printSimpleTable,\n isJsonOutput,\n printJson,\n} from '../utils/output.js';\n\n/**\n * Create the MCP command group\n */\nexport function createMcpCommand(): Command {\n const mcp = new Command('mcp')\n .description('MCP server configuration and status')\n .addHelpText(\n 'after',\n `\nWhat is MCP?\n Model Context Protocol (MCP) lets AI agents (Claude, Cursor, etc.) use your\n Chanl tools, knowledge base, and prompts. The MCP server bridges your workspace\n with AI clients.\n\nQuick Start:\n $ chanl mcp # Check server status\n $ chanl mcp test # Run protocol test\n $ chanl mcp config # Get config for your AI client\n\nConnecting AI Clients:\n 1. Run 'chanl mcp config --format claude' to get Claude Desktop config\n 2. Add the JSON to Claude Desktop's settings\n 3. Your Chanl tools will appear in Claude!`\n )\n .action(handleMcpDefault); // Default action when no subcommand\n\n // mcp info\n mcp\n .command('info')\n .description('Show MCP server URL, status, and resource counts')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp info # Show server details\n $ chanl mcp info --json # Output as JSON`\n )\n .action(handleMcpInfo);\n\n // mcp status\n mcp\n .command('status')\n .description('Health check the MCP server')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp status # Quick health check`\n )\n .action(handleMcpStatus);\n\n // mcp test\n mcp\n .command('test')\n .description('Run MCP protocol test with real MCP client')\n .addHelpText(\n 'after',\n `\nTests the full MCP protocol handshake:\n 1. Connect to MCP server\n 2. List available tools\n 3. List resources (knowledge base)\n 4. List prompts\n\nExample:\n $ chanl mcp test # Run all protocol tests\n $ chanl mcp test --json # Output results as JSON`\n )\n .action(handleMcpTest);\n\n // mcp config\n mcp\n .command('config')\n .description('Output MCP configuration for AI clients')\n .option('-f, --format <format>', 'Format: claude, cursor, generic', 'generic')\n .addHelpText(\n 'after',\n `\nGenerates configuration JSON for connecting AI clients to your MCP server.\n\nSupported formats:\n claude - Claude Desktop (claude_desktop_config.json)\n cursor - Cursor IDE settings\n generic - Generic MCP client format\n\nExamples:\n $ chanl mcp config # Generic format\n $ chanl mcp config --format claude # Claude Desktop format\n $ chanl mcp config --format cursor # Cursor IDE format\n $ chanl mcp config --format claude > config.json # Save to file\n\nSetup Claude Desktop:\n 1. Run: chanl mcp config --format claude\n 2. Copy the JSON output\n 3. Paste into ~/Library/Application Support/Claude/claude_desktop_config.json`\n )\n .action(handleMcpConfig);\n\n // mcp tools <agent-id>\n mcp\n .command('tools <agent-id>')\n .description('List MCP tools available for an agent')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp tools agent_123 # List tools for agent\n $ chanl mcp tools agent_123 --json # Output as JSON`\n )\n .action(handleMcpTools);\n\n // mcp call <agent-id> <tool-name> [args]\n mcp\n .command('call <agent-id> <tool-name> [args]')\n .description('Call an MCP tool by name')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp call agent_123 get_weather '{\"city\":\"London\"}'\n $ chanl mcp call agent_123 ping\n $ chanl mcp call agent_123 get_weather '{\"city\":\"London\"}' --json`\n )\n .action(handleMcpCall);\n\n // mcp inspect <agent-id> <tool-name>\n mcp\n .command('inspect <agent-id> <tool-name>')\n .description('Show schema and details for a specific MCP tool')\n .addHelpText(\n 'after',\n `\nExample:\n $ chanl mcp inspect agent_123 get_weather\n $ chanl mcp inspect agent_123 get_weather --json`\n )\n .action(handleMcpInspect);\n\n // mcp install\n mcp\n .command('install')\n .description('Generate MCP config snippet for editors (Cursor, VSCode, Claude)')\n .option('-f, --format <format>', 'Format: claude, cursor, vscode, generic', 'generic')\n .addHelpText(\n 'after',\n `\nGenerates a ready-to-use MCP configuration using your stored API key and workspace.\nNo server call needed — works offline.\n\nSupported formats:\n claude - Claude Desktop / Claude Code\n cursor - Cursor IDE\n vscode - Visual Studio Code\n generic - Generic MCP client (default)\n\nExamples:\n $ chanl mcp install # Generic format\n $ chanl mcp install --format claude # Claude Desktop format\n $ chanl mcp install --format cursor # Cursor IDE format\n $ chanl mcp install --format vscode # VSCode format\n $ chanl mcp install --format claude > mcp.json # Save to file`\n )\n .action(handleMcpInstall);\n\n return mcp;\n}\n\n/**\n * Handle mcp info command\n */\nasync function handleMcpInfo(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP configuration...').start();\n\n try {\n const response = await sdk.mcp.getConfig();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP configuration', response.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = response.data;\n\n if (isJsonOutput()) {\n printJson(config);\n return;\n }\n\n printBlank();\n printSimpleTable(\n ['Property', 'Value'],\n [\n ['Server URL', config.serverUrl],\n ['Environment', config.environment],\n ['Status', config.status === 'active' ? chalk.green('active') : chalk.red('disabled')],\n ['Tools', config.toolCount.toString()],\n ['Resources', config.resourceCount.toString()],\n ['Prompts', config.promptCount.toString()],\n ...(config.toolsetName ? [['Toolset', config.toolsetName]] : []),\n ]\n );\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp status command\n */\nasync function handleMcpStatus(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Checking MCP server health...').start();\n\n try {\n const response = await sdk.mcp.getStatus();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to check MCP status', response.message);\n process.exitCode = 1;\n return;\n }\n\n const status: McpStatusResponse = response.data;\n\n if (isJsonOutput()) {\n printJson(status);\n return;\n }\n\n printBlank();\n if (status.healthy) {\n printSuccess('MCP server is healthy');\n printLabel('Message', status.message);\n if (status.responseTimeMs) {\n printLabel('Response Time', status.responseTimeMs + 'ms');\n }\n } else {\n printError('MCP server is unhealthy', status.message);\n if (status.error) {\n printLabel('Error', status.error);\n }\n process.exitCode = 1;\n }\n printBlank();\n } catch (error) {\n spinner.fail('Failed to check MCP status');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp config command\n */\nasync function handleMcpConfig(options: { format: string }): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP configuration...').start();\n\n try {\n const response = await sdk.mcp.getConfig();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP configuration', response.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = response.data;\n const format = options.format.toLowerCase();\n\n let output: unknown;\n\n switch (format) {\n case 'claude':\n output = config.agentConfigs.claude;\n break;\n case 'cursor':\n output = config.agentConfigs.cursor;\n break;\n case 'generic':\n default:\n output = config.agentConfigs.generic;\n break;\n }\n\n // Always output JSON for config command (it's meant to be piped/copied)\n printJson(output);\n\n if (!isJsonOutput()) {\n printBlank();\n printInfo(`Configuration for ${format} format. Copy and paste into your ${format === 'claude' ? 'Claude Desktop' : format === 'cursor' ? 'Cursor IDE' : 'MCP client'} configuration.`);\n printInfo(`Replace \\${CHANL_MCP_API_KEY} with your actual MCP API key.`);\n }\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle bare 'chanl mcp' command (default)\n * Shows combined info + status\n */\nasync function handleMcpDefault(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Checking MCP server...').start();\n\n try {\n // Fetch config and status in parallel\n const [configResponse, statusResponse] = await Promise.all([\n sdk.mcp.getConfig(),\n sdk.mcp.getStatus(),\n ]);\n\n spinner.stop();\n\n if (!configResponse.success || !configResponse.data) {\n printError('Failed to fetch MCP configuration', configResponse.message);\n process.exitCode = 1;\n return;\n }\n\n const config: McpConfigResponse = configResponse.data;\n const status: McpStatusResponse | undefined = statusResponse.data;\n\n if (isJsonOutput()) {\n printJson({ config, status });\n return;\n }\n\n // Format status indicator\n const statusIcon = status?.healthy ? chalk.green('✓') : chalk.red('✗');\n const statusText = status?.healthy ? 'Connected' : 'Unhealthy';\n const latency = status?.responseTimeMs ? ` (${status.responseTimeMs}ms)` : '';\n\n printBlank();\n console.log(chalk.bold('MCP Server'));\n console.log(chalk.dim('─'.repeat(40)));\n printLabel('Server URL', config.serverUrl ?? 'Not configured');\n printLabel('Status', `${statusIcon} ${statusText}${latency}`);\n printLabel('Tools', `${config.toolCount ?? 0} enabled`);\n printLabel('Resources', (config.resourceCount ?? 0).toString());\n printLabel('Prompts', (config.promptCount ?? 0).toString());\n console.log(chalk.dim('─'.repeat(40)));\n printBlank();\n\n if (!status?.healthy) {\n printInfo(\"Run 'chanl mcp test' for detailed diagnostics\");\n process.exitCode = 1;\n }\n } catch (error) {\n spinner.fail('Failed to check MCP server');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp tools command\n * Lists MCP tools available for an agent\n */\nasync function handleMcpTools(agentId: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching MCP tools...').start();\n\n try {\n const response = await sdk.mcp.listTools(agentId);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch MCP tools', response.message);\n process.exitCode = 1;\n return;\n }\n\n const tools: McpTool[] = response.data.tools;\n\n if (isJsonOutput()) {\n printJson({ tools });\n return;\n }\n\n if (tools.length === 0) {\n printBlank();\n printInfo('No MCP tools available for this agent');\n printBlank();\n return;\n }\n\n printBlank();\n printSimpleTable(\n ['Name', 'Description'],\n tools.map((t) => [\n t.name,\n t.description || chalk.dim('No description'),\n ])\n );\n printBlank();\n printInfo(`Total: ${tools.length} tool${tools.length === 1 ? '' : 's'}`);\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch MCP tools');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp call command\n * Calls an MCP tool by name with optional JSON arguments\n */\nasync function handleMcpCall(agentId: string, toolName: string, argsJson?: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n // Parse args if provided\n let args: Record<string, unknown> | undefined;\n if (argsJson) {\n try {\n args = JSON.parse(argsJson) as Record<string, unknown>;\n } catch {\n printError('Invalid JSON arguments', `Could not parse: ${argsJson}`);\n process.exitCode = 1;\n return;\n }\n }\n\n const spinner = ora(`Calling tool '${toolName}'...`).start();\n\n try {\n const response = await sdk.mcp.callTool(agentId, toolName, args);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to call tool', response.message);\n process.exitCode = 1;\n return;\n }\n\n const result: McpToolCallResult = response.data;\n\n if (isJsonOutput()) {\n printJson(result);\n return;\n }\n\n // Check if tool returned an error\n if (result.isError) {\n for (const item of result.content) {\n if (item.text) {\n printError('Tool returned an error', item.text);\n }\n }\n process.exitCode = 1;\n return;\n }\n\n // Display result content\n printBlank();\n for (const item of result.content) {\n if (item.text) {\n console.log(item.text);\n } else {\n // Non-text content: display as JSON\n printJson(item);\n }\n }\n printBlank();\n } catch (error) {\n spinner.fail('Failed to call tool');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp inspect command\n * Shows schema and details for a specific MCP tool\n */\nasync function handleMcpInspect(agentId: string, toolName: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora(`Fetching tool '${toolName}'...`).start();\n\n try {\n const response = await sdk.mcp.getTool(agentId, toolName);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch tool details', response.message);\n process.exitCode = 1;\n return;\n }\n\n const tool: McpTool = response.data as McpTool;\n\n if (isJsonOutput()) {\n printJson(tool);\n return;\n }\n\n printBlank();\n console.log(chalk.bold(tool.name));\n console.log(chalk.dim('─'.repeat(40)));\n if (tool.description) {\n printLabel('Description', tool.description);\n }\n printBlank();\n\n // Display input schema\n console.log(chalk.bold('Input Schema'));\n console.log(chalk.dim('─'.repeat(40)));\n\n const schema = tool.inputSchema;\n const properties = schema['properties'] as Record<string, Record<string, unknown>> | undefined;\n const required = (schema['required'] as string[]) || [];\n\n if (properties) {\n const rows: string[][] = [];\n for (const [name, prop] of Object.entries(properties)) {\n const isRequired = required.includes(name);\n const typeStr = String(prop['type'] || 'any');\n const desc = String(prop['description'] || '');\n const reqStr = isRequired ? chalk.red('*') : ' ';\n rows.push([`${reqStr} ${name}`, typeStr, desc]);\n }\n printSimpleTable(['Parameter', 'Type', 'Description'], rows);\n } else {\n printInfo('No parameters defined');\n }\n\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch tool details');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle mcp install command\n * Generates MCP config snippets for various editors, using stored API key\n */\nasync function handleMcpInstall(options: { format: string }): Promise<void> {\n const apiKey = configStore.getApiKey();\n if (!apiKey) {\n printError('Not authenticated', \"Run 'chanl login' to authenticate first\");\n process.exitCode = 1;\n return;\n }\n\n const baseUrl = configStore.getBaseUrl() || 'https://api.chanl.ai';\n // Derive MCP server URL from base URL\n // Production: https://api.chanl.ai -> workspace.chanl.app/mcp (generic)\n // For install, use the base URL with /mcp path\n const mcpUrl = baseUrl.replace(/\\/$/, '') + '/mcp';\n\n const format = options.format.toLowerCase();\n\n const serverConfig = {\n url: mcpUrl,\n headers: { 'X-API-Key': apiKey },\n transport: 'streamable-http' as const,\n };\n\n let output: unknown;\n\n switch (format) {\n case 'claude':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'cursor':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'vscode':\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n case 'generic':\n default:\n output = {\n mcpServers: {\n chanl: serverConfig,\n },\n };\n break;\n }\n\n // Always output JSON (config is meant to be copied)\n printJson(output);\n\n if (!isJsonOutput()) {\n printBlank();\n const targetName =\n format === 'claude' ? 'Claude Desktop / Claude Code' :\n format === 'cursor' ? 'Cursor IDE' :\n format === 'vscode' ? 'Visual Studio Code' :\n 'your MCP client';\n printInfo(`Copy the JSON above into your ${targetName} configuration.`);\n printBlank();\n }\n}\n\n/**\n * Handle mcp test command\n * Runs real MCP protocol test using @modelcontextprotocol/sdk\n */\nasync function handleMcpTest(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n // First get the MCP config to know the server URL\n const spinner = ora('Fetching MCP configuration...').start();\n\n let config: McpConfigResponse;\n try {\n const configResponse = await sdk.mcp.getConfig();\n if (!configResponse.success || !configResponse.data) {\n spinner.fail('Failed to fetch MCP configuration');\n printError('Error', configResponse.message || 'Unknown error');\n process.exitCode = 1;\n return;\n }\n config = configResponse.data;\n } catch (error) {\n spinner.fail('Failed to fetch MCP configuration');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n return;\n }\n\n spinner.text = 'Running MCP protocol test...';\n\n // Get API key for MCP authentication\n // Use MCP_API_KEY env var if set (for local dev), otherwise fall back to stored key\n const apiKey = process.env.MCP_API_KEY || configStore.getApiKey();\n if (!apiKey) {\n spinner.fail('No API key available');\n printError('Error', 'API key required for MCP test. Set MCP_API_KEY env var for local dev.');\n process.exitCode = 1;\n return;\n }\n\n const results: { test: string; passed: boolean; detail: string }[] = [];\n\n try {\n // Create MCP client with Streamable HTTP transport\n const transport = new StreamableHTTPClientTransport(new URL(config.serverUrl), {\n requestInit: {\n headers: { 'X-API-Key': apiKey },\n },\n });\n\n const client = new Client(\n { name: 'chanl-cli', version: '1.0.0' },\n { capabilities: {} }\n );\n\n // Test 1: Connect\n spinner.text = 'Connecting to MCP server...';\n const connectStart = Date.now();\n await client.connect(transport);\n const connectTime = Date.now() - connectStart;\n results.push({ test: 'Connection', passed: true, detail: `${connectTime}ms` });\n\n // Test 2: List tools\n spinner.text = 'Testing tools/list...';\n try {\n const tools = await client.listTools();\n results.push({ test: 'tools/list', passed: true, detail: `${tools.tools.length} tools` });\n } catch (err) {\n results.push({ test: 'tools/list', passed: false, detail: err instanceof Error ? err.message : 'Failed' });\n }\n\n // Test 3: List resources\n spinner.text = 'Testing resources/list...';\n try {\n const resources = await client.listResources();\n results.push({ test: 'resources/list', passed: true, detail: `${resources.resources.length} resources` });\n } catch (err) {\n // Resources may not be available (no knowledge base)\n results.push({ test: 'resources/list', passed: true, detail: 'N/A (no knowledge base)' });\n }\n\n // Test 4: List prompts\n spinner.text = 'Testing prompts/list...';\n try {\n const prompts = await client.listPrompts();\n results.push({ test: 'prompts/list', passed: true, detail: `${prompts.prompts.length} prompts` });\n } catch (err) {\n results.push({ test: 'prompts/list', passed: true, detail: 'N/A' });\n }\n\n await client.close();\n spinner.stop();\n\n // Output results\n if (isJsonOutput()) {\n printJson({ serverUrl: config.serverUrl, results });\n return;\n }\n\n printBlank();\n console.log(chalk.bold('🧪 MCP Protocol Test'));\n console.log(chalk.dim('─'.repeat(50)));\n printLabel('Server', config.serverUrl);\n console.log(chalk.dim('─'.repeat(50)));\n\n let allPassed = true;\n for (const result of results) {\n const icon = result.passed ? chalk.green('✓') : chalk.red('✗');\n console.log(`${icon} ${result.test}: ${result.detail}`);\n if (!result.passed) allPassed = false;\n }\n\n console.log(chalk.dim('─'.repeat(50)));\n\n if (allPassed) {\n printSuccess('All tests passed');\n } else {\n printError('Some tests failed', 'Check the results above');\n process.exitCode = 1;\n }\n printBlank();\n } catch (error) {\n spinner.fail('MCP protocol test failed');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Connection failed', message);\n printInfo('Make sure the MCP server is running and accessible');\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/commands/memory.js
CHANGED
|
@@ -71,15 +71,32 @@ Examples:
|
|
|
71
71
|
$ chanl memory delete 680a1b2c3d4e5f6789012345 # With confirmation
|
|
72
72
|
$ chanl memory delete 680a1b2c3d4e5f6789012345 -y # Skip confirmation`
|
|
73
73
|
).action(handleMemoryDelete);
|
|
74
|
-
memory.command("search").description("Search memories using semantic similarity").option("--entity-type <type>", "Entity type (required): customer, session, agent, conversation").option("--entity-id <id>", "Entity ID (required)").option("--query <text>", "Search query text (required)").option("--agent-id <id>", "Optional agent scope filter").option("-l, --limit <number>", "Maximum results").option("--min-score <number>", "Minimum relevance score (0-1)").addHelpText(
|
|
74
|
+
memory.command("search").description("Search memories using semantic similarity").option("--entity-type <type>", "Entity type (required): customer, session, agent, conversation").option("--entity-id <id>", "Entity ID (required)").option("--query <text>", "Search query text (required)").option("--agent-id <id>", "Optional agent scope filter").option("-l, --limit <number>", "Maximum results").option("--min-score <number>", "Minimum relevance score (0-1)").option("--min-confidence <number>", "Minimum extraction confidence (0-1)").option("--created-after <iso>", "Only memories created at/after this ISO-8601 timestamp").option("--created-before <iso>", "Only memories created at/before this ISO-8601 timestamp").addHelpText(
|
|
75
75
|
"after",
|
|
76
76
|
`
|
|
77
77
|
Examples:
|
|
78
78
|
$ chanl memory search --entity-type customer --entity-id cust_123 --query "email preferences"
|
|
79
79
|
$ chanl memory search --entity-type customer --entity-id cust_123 --query "contact" --limit 5
|
|
80
80
|
$ chanl memory search --entity-type customer --entity-id cust_123 --query "VIP" --min-score 0.8
|
|
81
|
+
$ chanl memory search --entity-type customer --entity-id cust_123 --query "recent" --created-after 2026-05-01T00:00:00Z
|
|
81
82
|
$ chanl memory search --entity-type customer --entity-id cust_123 --query "test" --json`
|
|
82
83
|
).action(handleMemorySearch);
|
|
84
|
+
memory.command("search-global").description("Search ALL memories in the workspace (no entity scope)").option("--query <text>", "Search query text (required)").option("--entity-type <type>", "Optional: narrow to an entity type").option("--agent-id <id>", "Optional agent scope filter").option("--source <source>", "Filter by source: conversation, manual, extraction").option("-l, --limit <number>", "Maximum results").option("--min-score <number>", "Minimum relevance score (0-1)").addHelpText(
|
|
85
|
+
"after",
|
|
86
|
+
`
|
|
87
|
+
Examples:
|
|
88
|
+
$ chanl memory search-global --query "billing preferences"
|
|
89
|
+
$ chanl memory search-global --query "VIP" --entity-type customer --limit 10
|
|
90
|
+
$ chanl memory search-global --query "test" --json`
|
|
91
|
+
).action(handleMemorySearchGlobal);
|
|
92
|
+
memory.command("stats").description("Aggregate memory statistics (totals, by type/source, top-accessed)").option("--entity-type <type>", "Optionally scope stats to an entity type").option("--entity-id <id>", "Optionally scope stats to an entity ID").addHelpText(
|
|
93
|
+
"after",
|
|
94
|
+
`
|
|
95
|
+
Examples:
|
|
96
|
+
$ chanl memory stats # Workspace-wide
|
|
97
|
+
$ chanl memory stats --entity-type customer --entity-id cust_123
|
|
98
|
+
$ chanl memory stats --json`
|
|
99
|
+
).action(handleMemoryStats);
|
|
83
100
|
return memory;
|
|
84
101
|
}
|
|
85
102
|
function truncate(str, maxLen) {
|
|
@@ -152,7 +169,7 @@ async function handleMemoryGet(id) {
|
|
|
152
169
|
process.exitCode = 1;
|
|
153
170
|
return;
|
|
154
171
|
}
|
|
155
|
-
const
|
|
172
|
+
const memory = response.data;
|
|
156
173
|
if (isJsonOutput()) {
|
|
157
174
|
printJson(memory);
|
|
158
175
|
return;
|
|
@@ -220,7 +237,7 @@ async function handleMemoryCreate(options) {
|
|
|
220
237
|
process.exitCode = 1;
|
|
221
238
|
return;
|
|
222
239
|
}
|
|
223
|
-
const
|
|
240
|
+
const memory = response.data;
|
|
224
241
|
if (isJsonOutput()) {
|
|
225
242
|
printJson({ success: true, memory });
|
|
226
243
|
return;
|
|
@@ -260,7 +277,7 @@ async function handleMemoryUpdate(id, options) {
|
|
|
260
277
|
process.exitCode = 1;
|
|
261
278
|
return;
|
|
262
279
|
}
|
|
263
|
-
const
|
|
280
|
+
const memory = response.data;
|
|
264
281
|
if (isJsonOutput()) {
|
|
265
282
|
printJson({ success: true, memory });
|
|
266
283
|
return;
|
|
@@ -336,6 +353,15 @@ async function handleMemorySearch(options) {
|
|
|
336
353
|
if (options.minScore) {
|
|
337
354
|
input.minScore = parseFloat(options.minScore);
|
|
338
355
|
}
|
|
356
|
+
if (options.minConfidence) {
|
|
357
|
+
input.minConfidence = parseFloat(options.minConfidence);
|
|
358
|
+
}
|
|
359
|
+
if (options.createdAfter) {
|
|
360
|
+
input.createdAfter = options.createdAfter;
|
|
361
|
+
}
|
|
362
|
+
if (options.createdBefore) {
|
|
363
|
+
input.createdBefore = options.createdBefore;
|
|
364
|
+
}
|
|
339
365
|
const spinner = ora("Searching memories...").start();
|
|
340
366
|
try {
|
|
341
367
|
const response = await sdk.memory.search(input);
|
|
@@ -374,6 +400,113 @@ async function handleMemorySearch(options) {
|
|
|
374
400
|
process.exitCode = 1;
|
|
375
401
|
}
|
|
376
402
|
}
|
|
403
|
+
async function handleMemorySearchGlobal(options) {
|
|
404
|
+
if (!options.query) {
|
|
405
|
+
printError("Missing required option", "Provide --query");
|
|
406
|
+
printInfo("Run 'chanl memory search-global --help' for usage");
|
|
407
|
+
process.exitCode = 1;
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const sdk = createSdk();
|
|
411
|
+
if (!sdk) return;
|
|
412
|
+
const input = { query: options.query };
|
|
413
|
+
if (options.entityType) input.entityType = options.entityType;
|
|
414
|
+
if (options.agentId) input.agentId = options.agentId;
|
|
415
|
+
if (options.source) input.source = options.source;
|
|
416
|
+
if (options.limit) input.limit = parseInt(options.limit, 10);
|
|
417
|
+
if (options.minScore) input.minScore = parseFloat(options.minScore);
|
|
418
|
+
const spinner = ora("Searching all memories...").start();
|
|
419
|
+
try {
|
|
420
|
+
const response = await sdk.memory.searchGlobal(input);
|
|
421
|
+
spinner.stop();
|
|
422
|
+
if (!response.success || !response.data) {
|
|
423
|
+
printError("Failed to search memories", response.message);
|
|
424
|
+
process.exitCode = 1;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const { memories } = response.data;
|
|
428
|
+
if (isJsonOutput()) {
|
|
429
|
+
printJson({ memories });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (memories.length === 0) {
|
|
433
|
+
printInfo("No matching memories found");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
printBlank();
|
|
437
|
+
printSimpleTable(
|
|
438
|
+
["ID", "Score", "Content", "Entity Type", "Entity ID"],
|
|
439
|
+
memories.map((m) => [
|
|
440
|
+
truncate(m.id, 12),
|
|
441
|
+
(m.score ?? 0).toFixed(2),
|
|
442
|
+
truncate(m.content, 50),
|
|
443
|
+
m.entityType,
|
|
444
|
+
m.entityId
|
|
445
|
+
])
|
|
446
|
+
);
|
|
447
|
+
printBlank();
|
|
448
|
+
printInfo(`Found ${memories.length} matching memories`);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
spinner.fail("Failed to search memories");
|
|
451
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
452
|
+
printError("Error", message);
|
|
453
|
+
process.exitCode = 1;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function handleMemoryStats(options) {
|
|
457
|
+
const sdk = createSdk();
|
|
458
|
+
if (!sdk) return;
|
|
459
|
+
const spinner = ora("Aggregating memory stats...").start();
|
|
460
|
+
try {
|
|
461
|
+
const response = await sdk.memory.stats({
|
|
462
|
+
entityType: options.entityType,
|
|
463
|
+
entityId: options.entityId
|
|
464
|
+
});
|
|
465
|
+
spinner.stop();
|
|
466
|
+
if (!response.success || !response.data) {
|
|
467
|
+
printError("Failed to fetch memory stats", response.message);
|
|
468
|
+
process.exitCode = 1;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const stats = response.data;
|
|
472
|
+
if (isJsonOutput()) {
|
|
473
|
+
printJson(stats);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
printBlank();
|
|
477
|
+
printLabel("Total", String(stats.total));
|
|
478
|
+
printLabel("Active", chalk.green(String(stats.active)));
|
|
479
|
+
printLabel("Inactive", String(stats.inactive));
|
|
480
|
+
printLabel("Expired", stats.expired > 0 ? chalk.yellow(String(stats.expired)) : "0");
|
|
481
|
+
printLabel("Avg confidence", stats.avgConfidence.toFixed(3));
|
|
482
|
+
printBlank();
|
|
483
|
+
printInfo("By type:");
|
|
484
|
+
printSimpleTable(
|
|
485
|
+
["Entity Type", "Count"],
|
|
486
|
+
Object.entries(stats.byType).map(([k, v]) => [k, String(v)])
|
|
487
|
+
);
|
|
488
|
+
printBlank();
|
|
489
|
+
printInfo("By source:");
|
|
490
|
+
printSimpleTable(
|
|
491
|
+
["Source", "Count"],
|
|
492
|
+
Object.entries(stats.bySource).map(([k, v]) => [k, String(v)])
|
|
493
|
+
);
|
|
494
|
+
if (stats.topAccessed.length > 0) {
|
|
495
|
+
printBlank();
|
|
496
|
+
printInfo("Top accessed:");
|
|
497
|
+
printSimpleTable(
|
|
498
|
+
["ID", "Access Count", "Content"],
|
|
499
|
+
stats.topAccessed.map((t) => [truncate(t.id, 12), String(t.accessCount), truncate(t.content, 50)])
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
printBlank();
|
|
503
|
+
} catch (error) {
|
|
504
|
+
spinner.fail("Failed to fetch memory stats");
|
|
505
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
506
|
+
printError("Error", message);
|
|
507
|
+
process.exitCode = 1;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
377
510
|
function printMemoryDetails(memory) {
|
|
378
511
|
printLabel("ID", memory.id);
|
|
379
512
|
printLabel("Entity Type", memory.entityType);
|