Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14,075 changes: 11,496 additions & 2,579 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions packages/atxp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
"create"
],
"dependencies": {
"@atxp/client": "^0.10.5",
"chalk": "^5.3.0",
"inquirer": "^9.2.12",
"ora": "^7.0.1",
"fs-extra": "^11.2.0",
"open": "^9.1.0"
"inquirer": "^9.2.12",
"open": "^9.1.0",
"ora": "^7.0.1"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
Expand Down
90 changes: 90 additions & 0 deletions packages/atxp/src/call-tool.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest';

describe('Call Tool', () => {
describe('server configuration', () => {
it('should map commands to correct servers', () => {
const serverMap: Record<string, string> = {
search: 'search.mcp.atxp.ai',
image: 'image.mcp.atxp.ai',
music: 'music.mcp.atxp.ai',
video: 'video.mcp.atxp.ai',
x: 'x-live-search.mcp.atxp.ai',
};

expect(serverMap.search).toBe('search.mcp.atxp.ai');
expect(serverMap.image).toBe('image.mcp.atxp.ai');
expect(serverMap.music).toBe('music.mcp.atxp.ai');
expect(serverMap.video).toBe('video.mcp.atxp.ai');
expect(serverMap.x).toBe('x-live-search.mcp.atxp.ai');
});

it('should map commands to correct tool names', () => {
const toolMap: Record<string, string> = {
search: 'search',
image: 'generate_image',
music: 'generate_music',
video: 'generate_video',
x: 'x_live_search',
};

expect(toolMap.search).toBe('search');
expect(toolMap.image).toBe('generate_image');
expect(toolMap.music).toBe('generate_music');
expect(toolMap.video).toBe('generate_video');
expect(toolMap.x).toBe('x_live_search');
});
});

describe('connection string validation', () => {
it('should detect missing connection string', () => {
const checkConnection = (connection?: string) => {
return !!connection;
};

expect(checkConnection(undefined)).toBe(false);
expect(checkConnection('')).toBe(false);
expect(checkConnection('valid-connection')).toBe(true);
});
});

describe('tool result parsing', () => {
it('should extract text content from result', () => {
const extractText = (
result: { content: Array<{ type: string; text?: string }> } | null
) => {
if (result?.content?.[0]?.text) {
return result.content[0].text;
}
return null;
};

const textResult = { content: [{ type: 'text', text: 'Hello world' }] };
expect(extractText(textResult)).toBe('Hello world');

const emptyResult = { content: [] };
expect(extractText(emptyResult)).toBe(null);

expect(extractText(null)).toBe(null);
});

it('should detect binary content', () => {
const isBinaryContent = (content: { data?: string; mimeType?: string }) => {
return !!(content.data && content.mimeType);
};

expect(isBinaryContent({ data: 'base64data', mimeType: 'image/png' })).toBe(true);
expect(isBinaryContent({ data: 'base64data' })).toBe(false);
expect(isBinaryContent({ mimeType: 'image/png' })).toBe(false);
expect(isBinaryContent({})).toBe(false);
});
});

describe('server URL construction', () => {
it('should construct correct server URL', () => {
const constructUrl = (server: string) => `https://${server}`;

expect(constructUrl('search.mcp.atxp.ai')).toBe('https://search.mcp.atxp.ai');
expect(constructUrl('image.mcp.atxp.ai')).toBe('https://image.mcp.atxp.ai');
});
});
});
49 changes: 49 additions & 0 deletions packages/atxp/src/call-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { atxpClient, ATXPAccount } from '@atxp/client';
import chalk from 'chalk';

export interface ToolResult {
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
}

export async function callTool(
server: string,
tool: string,
args: Record<string, unknown>
): Promise<string> {
const connection = process.env.ATXP_CONNECTION;

if (!connection) {
console.error(chalk.red('Not logged in.'));
console.error(`Run: ${chalk.cyan('npx atxp login')}`);
process.exit(1);
}

try {
const client = await atxpClient({
mcpServer: `https://${server}`,
account: new ATXPAccount(connection),
});

const result = (await client.callTool({
name: tool,
arguments: args,
})) as ToolResult;

// Handle different content types
if (result.content && result.content.length > 0) {
const content = result.content[0];
if (content.text) {
return content.text;
} else if (content.data && content.mimeType) {
// For binary content (images, audio, video), return info about it
return `[${content.mimeType} data received - ${content.data.length} bytes base64]`;
}
}

return JSON.stringify(result, null, 2);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(chalk.red(`Error calling ${tool}: ${errorMessage}`));
process.exit(1);
}
}
140 changes: 140 additions & 0 deletions packages/atxp/src/commands/commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { describe, it, expect } from 'vitest';

describe('Tool Commands', () => {
describe('search command', () => {
const SERVER = 'search.mcp.atxp.ai';
const TOOL = 'search';

it('should have correct server', () => {
expect(SERVER).toBe('search.mcp.atxp.ai');
});

it('should have correct tool name', () => {
expect(TOOL).toBe('search');
});

it('should validate query is required', () => {
const validateQuery = (query: string) => {
return query && query.trim().length > 0;
};

expect(validateQuery('test query')).toBeTruthy();
expect(validateQuery('')).toBeFalsy();
expect(validateQuery(' ')).toBeFalsy();
});

it('should trim query whitespace', () => {
const prepareQuery = (query: string) => query.trim();

expect(prepareQuery(' hello world ')).toBe('hello world');
expect(prepareQuery('test')).toBe('test');
});
});

describe('image command', () => {
const SERVER = 'image.mcp.atxp.ai';
const TOOL = 'generate_image';

it('should have correct server', () => {
expect(SERVER).toBe('image.mcp.atxp.ai');
});

it('should have correct tool name', () => {
expect(TOOL).toBe('generate_image');
});

it('should validate prompt is required', () => {
const validatePrompt = (prompt: string) => {
return prompt && prompt.trim().length > 0;
};

expect(validatePrompt('sunset over mountains')).toBeTruthy();
expect(validatePrompt('')).toBeFalsy();
});
});

describe('music command', () => {
const SERVER = 'music.mcp.atxp.ai';
const TOOL = 'generate_music';

it('should have correct server', () => {
expect(SERVER).toBe('music.mcp.atxp.ai');
});

it('should have correct tool name', () => {
expect(TOOL).toBe('generate_music');
});

it('should validate prompt is required', () => {
const validatePrompt = (prompt: string) => {
return prompt && prompt.trim().length > 0;
};

expect(validatePrompt('relaxing piano')).toBeTruthy();
expect(validatePrompt('')).toBeFalsy();
});
});

describe('video command', () => {
const SERVER = 'video.mcp.atxp.ai';
const TOOL = 'generate_video';

it('should have correct server', () => {
expect(SERVER).toBe('video.mcp.atxp.ai');
});

it('should have correct tool name', () => {
expect(TOOL).toBe('generate_video');
});

it('should validate prompt is required', () => {
const validatePrompt = (prompt: string) => {
return prompt && prompt.trim().length > 0;
};

expect(validatePrompt('ocean waves')).toBeTruthy();
expect(validatePrompt('')).toBeFalsy();
});
});

describe('x command', () => {
const SERVER = 'x-live-search.mcp.atxp.ai';
const TOOL = 'x_live_search';

it('should have correct server', () => {
expect(SERVER).toBe('x-live-search.mcp.atxp.ai');
});

it('should have correct tool name', () => {
expect(TOOL).toBe('x_live_search');
});

it('should validate query is required', () => {
const validateQuery = (query: string) => {
return query && query.trim().length > 0;
};

expect(validateQuery('trending topics')).toBeTruthy();
expect(validateQuery('')).toBeFalsy();
});
});

describe('common command behavior', () => {
it('should construct tool arguments correctly', () => {
const buildArgs = (key: string, value: string) => {
return { [key]: value.trim() };
};

expect(buildArgs('query', 'test query')).toEqual({ query: 'test query' });
expect(buildArgs('prompt', ' image prompt ')).toEqual({ prompt: 'image prompt' });
});

it('should handle multi-word inputs', () => {
const parseInput = (args: string[]) => args.join(' ');

expect(parseInput(['hello', 'world'])).toBe('hello world');
expect(parseInput(['a', 'beautiful', 'sunset'])).toBe('a beautiful sunset');
expect(parseInput([])).toBe('');
});
});
});
16 changes: 16 additions & 0 deletions packages/atxp/src/commands/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { callTool } from '../call-tool.js';
import chalk from 'chalk';

const SERVER = 'image.mcp.atxp.ai';
const TOOL = 'generate_image';

export async function imageCommand(prompt: string): Promise<void> {
if (!prompt || prompt.trim().length === 0) {
console.error(chalk.red('Error: Image prompt is required'));
console.log(`Usage: ${chalk.cyan('npx atxp image <prompt>')}`);
process.exit(1);
}

const result = await callTool(SERVER, TOOL, { prompt: prompt.trim() });
console.log(result);
}
16 changes: 16 additions & 0 deletions packages/atxp/src/commands/music.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { callTool } from '../call-tool.js';
import chalk from 'chalk';

const SERVER = 'music.mcp.atxp.ai';
const TOOL = 'generate_music';

export async function musicCommand(prompt: string): Promise<void> {
if (!prompt || prompt.trim().length === 0) {
console.error(chalk.red('Error: Music prompt is required'));
console.log(`Usage: ${chalk.cyan('npx atxp music <prompt>')}`);
process.exit(1);
}

const result = await callTool(SERVER, TOOL, { prompt: prompt.trim() });
console.log(result);
}
16 changes: 16 additions & 0 deletions packages/atxp/src/commands/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { callTool } from '../call-tool.js';
import chalk from 'chalk';

const SERVER = 'search.mcp.atxp.ai';
const TOOL = 'search';

export async function searchCommand(query: string): Promise<void> {
if (!query || query.trim().length === 0) {
console.error(chalk.red('Error: Search query is required'));
console.log(`Usage: ${chalk.cyan('npx atxp search <query>')}`);
process.exit(1);
}

const result = await callTool(SERVER, TOOL, { query: query.trim() });
console.log(result);
}
16 changes: 16 additions & 0 deletions packages/atxp/src/commands/video.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { callTool } from '../call-tool.js';
import chalk from 'chalk';

const SERVER = 'video.mcp.atxp.ai';
const TOOL = 'generate_video';

export async function videoCommand(prompt: string): Promise<void> {
if (!prompt || prompt.trim().length === 0) {
console.error(chalk.red('Error: Video prompt is required'));
console.log(`Usage: ${chalk.cyan('npx atxp video <prompt>')}`);
process.exit(1);
}

const result = await callTool(SERVER, TOOL, { prompt: prompt.trim() });
console.log(result);
}
16 changes: 16 additions & 0 deletions packages/atxp/src/commands/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { callTool } from '../call-tool.js';
import chalk from 'chalk';

const SERVER = 'x-live-search.mcp.atxp.ai';
const TOOL = 'x_live_search';

export async function xCommand(query: string): Promise<void> {
if (!query || query.trim().length === 0) {
console.error(chalk.red('Error: Search query is required'));
console.log(`Usage: ${chalk.cyan('npx atxp x <query>')}`);
process.exit(1);
}

const result = await callTool(SERVER, TOOL, { query: query.trim() });
console.log(result);
}
Loading