chatatp-cli 1.0.0__py3-none-any.whl

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.
File without changes
@@ -0,0 +1,183 @@
1
+ import requests
2
+ from typing import Dict, List, Optional, Any
3
+ import json
4
+ from config import Config
5
+
6
+ class ChatATPAPI:
7
+ def __init__(self, config: Config):
8
+ self.config = config
9
+ self.session = requests.Session()
10
+ self.session.headers.update(self.config.get_headers())
11
+
12
+ def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
13
+ """Make GET request"""
14
+ url = f"{self.config.api_base_url}{endpoint}"
15
+ response = self.session.get(url, params=params)
16
+ response.raise_for_status()
17
+ return response.json()
18
+
19
+ def _post(self, endpoint: str, data: Optional[Dict] = None) -> Dict:
20
+ """Make POST request"""
21
+ url = f"{self.config.api_base_url}{endpoint}"
22
+ response = self.session.post(url, json=data)
23
+ response.raise_for_status()
24
+ return response.json()
25
+
26
+ def _patch(self, endpoint: str, data: Optional[Dict] = None) -> Dict:
27
+ """Make PATCH request"""
28
+ url = f"{self.config.api_base_url}{endpoint}"
29
+ response = self.session.patch(url, json=data)
30
+ response.raise_for_status()
31
+ return response.json()
32
+
33
+ def _delete(self, endpoint: str) -> Dict:
34
+ """Make DELETE request"""
35
+ url = f"{self.config.api_base_url}{endpoint}"
36
+ response = self.session.delete(url)
37
+ response.raise_for_status()
38
+ return response.json() if response.content else {}
39
+
40
+ # Account endpoints
41
+ def get_account(self) -> Dict:
42
+ """Get user account information"""
43
+ return self._get('/api/v1/account/')
44
+
45
+ # Models endpoints
46
+ def list_models(self) -> List[Dict]:
47
+ """List available models"""
48
+ return self._get('/api/v1/list-models/')
49
+
50
+ # Collections/Toolkits endpoints
51
+ def list_collections(self) -> Dict:
52
+ """List toolkits collections"""
53
+ return self._get('/api/v1/collections/toolkits/')
54
+
55
+ # MCP endpoints
56
+ def list_mcp_connections(self) -> Dict:
57
+ """List MCP connections"""
58
+ return self._get('/api/v1/mcp/connections/')
59
+
60
+ def list_mcp_servers(self) -> Dict:
61
+ """List MCP servers"""
62
+ return self._get('/api/v1/mcp/servers/')
63
+
64
+ # Chat endpoints
65
+ def list_chatrooms(self) -> Dict:
66
+ """List chatrooms"""
67
+ return self._get('/api/v1/chatrooms/')
68
+
69
+ def get_chatroom(self, room_id: str) -> Dict:
70
+ """Get chatroom details"""
71
+ return self._get(f'/api/v1/chats/{room_id}/')
72
+
73
+ def create_chatroom(self, message: str, model: str = None, toolkit_ids: List[str] = None,
74
+ mcp_server_connection_ids: List[str] = None) -> Dict:
75
+ """Create new chatroom"""
76
+ data = {'message': message}
77
+ if model:
78
+ data['model'] = model
79
+ if toolkit_ids:
80
+ data['toolkit_ids'] = toolkit_ids
81
+ if mcp_server_connection_ids:
82
+ data['mcp_server_connection_ids'] = mcp_server_connection_ids
83
+ return self._post('/api/v1/chats/new/', data)
84
+
85
+ def send_chat_message_stream(self, room_id: str, message: str, model: str = None,
86
+ toolkit_ids: List[str] = None, mcp_server_connection_ids: List[str] = None,
87
+ attachments: List = None):
88
+ """Send chat message and yield streaming response chunks"""
89
+ url = f"{self.config.api_base_url}/api/v1/chats/{room_id}/completions/stream/"
90
+ data = {
91
+ 'message_type': 'chat_message',
92
+ 'message': message,
93
+ 'model': model or self.config.default_model,
94
+ 'toolkit_ids': toolkit_ids or [],
95
+ 'mcp_server_connection_ids': mcp_server_connection_ids or [],
96
+ 'attachments': attachments or []
97
+ }
98
+
99
+ with self.session.post(url, json=data, stream=True) as response:
100
+ response.raise_for_status()
101
+ buffer = ""
102
+
103
+ for line in response.iter_lines():
104
+ if not line:
105
+ continue
106
+
107
+ line = line.decode('utf-8')
108
+
109
+ # Handle SSE format: "data: {...}"
110
+ if line.startswith('data: '):
111
+ line = line[6:]
112
+
113
+ # Skip SSE comments or keep-alive
114
+ if line.startswith(':'):
115
+ continue
116
+
117
+ buffer += line
118
+
119
+ # Try to parse whatever is in the buffer
120
+ while buffer:
121
+ buffer = buffer.strip()
122
+ if not buffer:
123
+ break
124
+ try:
125
+ chunk, idx = json.JSONDecoder().raw_decode(buffer)
126
+ yield chunk
127
+ buffer = buffer[idx:].strip()
128
+ except json.JSONDecodeError:
129
+ # Incomplete JSON, wait for more data
130
+ break
131
+
132
+ # Media endpoints
133
+ def list_media(self, page: int = 1, page_size: int = 12, media_type: str = None, search: str = None) -> Dict:
134
+ """List user media"""
135
+ params = {'page': page, 'page_size': page_size}
136
+ if media_type:
137
+ params['type'] = media_type
138
+ if search:
139
+ params['search'] = search
140
+ return self._get('/api/v1/media/', params)
141
+
142
+ # Integrations endpoints
143
+ def list_integrations(self) -> Dict:
144
+ """List integrated accounts"""
145
+ return self._get('/api/v1/integrations/')
146
+
147
+ def list_custom_integrations(self) -> Dict:
148
+ """List custom integrated accounts"""
149
+ return self._get('/api/v1/integrations/custom/')
150
+
151
+ def list_ai_configs(self) -> List[Dict]:
152
+ """List AI provider configurations"""
153
+ return self._get('/api/v1/integrations/ai/providers/configs/')
154
+
155
+ def list_ai_providers(self) -> List[Dict]:
156
+ """List AI providers"""
157
+ return self._get('/api/v1/integrations/ai/providers/')
158
+
159
+ def list_provider_models(self, provider_id: str) -> List[Dict]:
160
+ """List models for a provider"""
161
+ return self._get('/api/v1/list-provider-models/', {'provider': provider_id})
162
+
163
+ def get_ai_settings(self) -> Dict:
164
+ """Get AI settings"""
165
+ return self._get('/api/v1/integrations/ai/settings/')
166
+
167
+ # Store endpoints
168
+ def list_featured_toolkits(self) -> Dict:
169
+ """List featured toolkits"""
170
+ return self._get('/api/v1/store/toolkits/featured/')
171
+
172
+ def list_popular_toolkits(self) -> Dict:
173
+ """List popular toolkits"""
174
+ return self._get('/api/v1/store/toolkits/popular/')
175
+
176
+ def list_recommended_toolkits(self) -> Dict:
177
+ """List recommended toolkits"""
178
+ return self._get('/api/v1/store/toolkits/for_you/')
179
+
180
+ # Pricing endpoints
181
+ def get_pricing(self) -> List[Dict]:
182
+ """Get pricing information"""
183
+ return self._get('/api/v1/payments/pricing/')
chatatp_cli/config.py ADDED
@@ -0,0 +1,68 @@
1
+ import os
2
+ from typing import Optional
3
+ import yaml
4
+ from pathlib import Path
5
+
6
+ class Config:
7
+ def __init__(self, config_file: str = None):
8
+ self.config_file = config_file or os.path.join(Path.home(), '.chatatp', 'config.yaml')
9
+ self._config = {}
10
+ self.load_config()
11
+
12
+ def load_config(self):
13
+ """Load configuration from file"""
14
+ try:
15
+ if os.path.exists(self.config_file):
16
+ with open(self.config_file, 'r') as f:
17
+ self._config = yaml.safe_load(f) or {}
18
+ else:
19
+ self._config = {}
20
+ except Exception as e:
21
+ print(f"Warning: Could not load config file: {e}")
22
+ self._config = {}
23
+
24
+ def save_config(self):
25
+ """Save configuration to file"""
26
+ try:
27
+ os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
28
+ with open(self.config_file, 'w') as f:
29
+ yaml.dump(self._config, f, default_flow_style=False)
30
+ except Exception as e:
31
+ print(f"Error saving config: {e}")
32
+
33
+ @property
34
+ def api_base_url(self) -> str:
35
+ return self._config.get('api_base_url', 'https://api.chat-atp.com')
36
+
37
+ @api_base_url.setter
38
+ def api_base_url(self, value: str):
39
+ self._config['api_base_url'] = value
40
+ self.save_config()
41
+
42
+ @property
43
+ def api_token(self) -> Optional[str]:
44
+ return self._config.get('api_token')
45
+
46
+ @api_token.setter
47
+ def api_token(self, value: str):
48
+ self._config['api_token'] = value
49
+ self.save_config()
50
+
51
+ @property
52
+ def default_model(self) -> str:
53
+ return self._config.get('default_model', 'gpt-oss-120b')
54
+
55
+ @default_model.setter
56
+ def default_model(self, value: str):
57
+ self._config['default_model'] = value
58
+ self.save_config()
59
+
60
+ def get_headers(self) -> dict:
61
+ """Get headers for API requests"""
62
+ headers = {
63
+ 'Content-Type': 'application/json',
64
+ 'Accept': 'application/json'
65
+ }
66
+ if self.api_token:
67
+ headers['Authorization'] = f'Token {self.api_token}'
68
+ return headers
chatatp_cli/main.py ADDED
@@ -0,0 +1,830 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ChatATP CLI - Terminal Interface for ChatATP API
4
+ """
5
+
6
+ import click
7
+ import json
8
+ import sys
9
+ from typing import Optional, List
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+ from rich.panel import Panel
13
+ from rich.text import Text
14
+ from rich.live import Live
15
+ from rich.spinner import Spinner
16
+ from rich.markdown import Markdown
17
+ from .config import Config
18
+ from .api_client import ChatATPAPI
19
+
20
+ console = Console()
21
+ config_manager = Config()
22
+ api = ChatATPAPI(config_manager)
23
+
24
+ def format_json(data) -> str:
25
+ """Format JSON data for display"""
26
+ return json.dumps(data, indent=2, ensure_ascii=False)
27
+
28
+ def check_auth():
29
+ """Check if user is authenticated"""
30
+ if not config_manager.api_token:
31
+ console.print("[red]Error: No API token configured. Use 'chatatp config set-token <token>' to set it.[/red]")
32
+ sys.exit(1)
33
+
34
+ @click.group()
35
+ @click.version_option(version="1.0.0")
36
+ def cli():
37
+ """ChatATP CLI - Terminal Interface for ChatATP API"""
38
+ pass
39
+
40
+ # Configuration commands
41
+ @cli.group()
42
+ def config():
43
+ """Configuration management"""
44
+ pass
45
+
46
+ @config.command()
47
+ @click.argument('token')
48
+ def set_token(token):
49
+ """Set API token"""
50
+ config_manager.api_token = token
51
+ console.print("[green]API token set successfully![/green]")
52
+
53
+ @config.command()
54
+ @click.argument('url')
55
+ def set_base_url(url):
56
+ """Set API base URL"""
57
+ config_manager.api_base_url = url
58
+ console.print("[green]API base URL set successfully![/green]")
59
+
60
+ @config.command()
61
+ @click.argument('model')
62
+ def set_default_model(model):
63
+ """Set default model"""
64
+ config_manager.default_model = model
65
+ console.print("[green]Default model set successfully![/green]")
66
+
67
+ @config.command()
68
+ def show():
69
+ """Show current configuration"""
70
+ table = Table(title="Configuration")
71
+ table.add_column("Setting", style="cyan")
72
+ table.add_column("Value", style="magenta")
73
+
74
+ table.add_row("API Base URL", config_manager.api_base_url)
75
+ table.add_row("API Token", config_manager.api_token[:20] + "..." if config_manager.api_token else "Not set")
76
+ table.add_row("Default Model", config_manager.default_model)
77
+
78
+ console.print(table)
79
+
80
+ # Account commands
81
+ @cli.command()
82
+ def account():
83
+ """Get account information"""
84
+ check_auth()
85
+ try:
86
+ with console.status("[bold green]Fetching account info..."):
87
+ data = api.get_account()
88
+
89
+ user = data['user_account']['user']
90
+ console.print(Panel.fit(
91
+ f"[bold blue]Name:[/bold blue] {user['first_name']} {user['last_name']}\n"
92
+ f"[bold blue]Username:[/bold blue] {user['username']}\n"
93
+ f"[bold blue]Email:[/bold blue] {user['email']}\n"
94
+ f"[bold blue]Bio:[/bold blue] {data['user_account']['bio']}\n"
95
+ f"[bold blue]Company:[/bold blue] {data['user_account']['company_name']}\n"
96
+ f"[bold blue]Credits:[/bold blue] {data['user_account']['credits']}",
97
+ title="Account Information"
98
+ ))
99
+ except Exception as e:
100
+ console.print(f"[red]Error: {e}[/red]")
101
+
102
+ # Models commands
103
+ @cli.command()
104
+ def models():
105
+ """List available models"""
106
+ check_auth()
107
+ try:
108
+ with console.status("[bold green]Fetching models..."):
109
+ data = api.list_models()
110
+
111
+ table = Table(title="Available Models")
112
+ table.add_column("ID", style="cyan")
113
+ table.add_column("Name", style="magenta")
114
+ table.add_column("Description", style="white")
115
+ table.add_column("Default", style="green")
116
+
117
+ for model in data:
118
+ table.add_row(
119
+ model['id'],
120
+ model['name'],
121
+ model['description'],
122
+ "✓" if model.get('default', False) else ""
123
+ )
124
+
125
+ console.print(table)
126
+ except Exception as e:
127
+ console.print(f"[red]Error: {e}[/red]")
128
+
129
+ # Toolkits commands
130
+ @cli.command()
131
+ def toolkits():
132
+ """List user's toolkits"""
133
+ check_auth()
134
+ try:
135
+ with console.status("[bold green]Fetching toolkits..."):
136
+ data = api.list_collections()
137
+
138
+ table = Table(title="Your Toolkits")
139
+ table.add_column("Name", style="cyan")
140
+ table.add_column("Display Name", style="magenta")
141
+ table.add_column("Category", style="white")
142
+ table.add_column("Installs", style="green")
143
+
144
+ for toolkit in data['toolkits']:
145
+ table.add_row(
146
+ toolkit['tool_kit']['name'],
147
+ toolkit['tool_kit']['display_name'],
148
+ toolkit['tool_kit']['category'],
149
+ str(toolkit['tool_kit']['installs'])
150
+ )
151
+
152
+ console.print(table)
153
+ except Exception as e:
154
+ console.print(f"[red]Error: {e}[/red]")
155
+
156
+ # Chat commands
157
+ @cli.group()
158
+ def chat():
159
+ """Chat management"""
160
+ pass
161
+
162
+ @chat.command()
163
+ def rooms():
164
+ """List chatrooms"""
165
+ check_auth()
166
+ try:
167
+ with console.status("[bold green]Fetching chatrooms..."):
168
+ data = api.list_chatrooms()
169
+
170
+ table = Table(title="Chat Rooms")
171
+ table.add_column("Room ID", style="cyan")
172
+ table.add_column("Name", style="magenta")
173
+ table.add_column("Created", style="white")
174
+
175
+ for room in data['chatrooms']:
176
+ table.add_row(
177
+ room['room_id'],
178
+ room['group_name'],
179
+ room['created'][:19]
180
+ )
181
+
182
+ console.print(table)
183
+ except Exception as e:
184
+ console.print(f"[red]Error: {e}[/red]")
185
+
186
+ @chat.command()
187
+ @click.argument('room_id')
188
+ def show(room_id):
189
+ """Show chatroom details"""
190
+ check_auth()
191
+ try:
192
+ with console.status("[bold green]Fetching chat details..."):
193
+ data = api.get_chatroom(room_id)
194
+
195
+ room = data['chatroom']
196
+ console.print(Panel.fit(
197
+ f"[bold blue]Name:[/bold blue] {room['group_name']}\n"
198
+ f"[bold blue]Description:[/bold blue] {room['group_description']}\n"
199
+ f"[bold blue]Created:[/bold blue] {room['created'][:19]}\n"
200
+ f"[bold blue]Members:[/bold blue] {len(room['members'])}",
201
+ title=f"Chat Room: {room['room_id']}"
202
+ ))
203
+
204
+ if data['chats']:
205
+ console.print("\n[bold]Recent Messages:[/bold]")
206
+ for chat in data['chats'][-5:]: # Show last 5 messages
207
+ sender = chat['sender_account']['user']['first_name']
208
+ time = chat['created'][:19]
209
+ message = chat['text'][:100] + "..." if len(chat['text']) > 100 else chat['text']
210
+ console.print(f"[cyan]{time}[/cyan] [magenta]{sender}:[/magenta] {message}")
211
+ except Exception as e:
212
+ console.print(f"[red]Error: {e}[/red]")
213
+
214
+ def chat_loop(room_id, initial_message=None, model=None, toolkits=None, mcp_connections=None):
215
+ """Interactive chat loop for back-and-forth conversation"""
216
+ console.print(f"\n[bold cyan]Entered chatroom: {room_id}[/bold cyan]")
217
+ console.print("[dim]Type your message or '/exit' to quit, '/help' for commands[/dim]\n")
218
+
219
+ # Send initial message if provided
220
+ if initial_message:
221
+ console.print(f"[bold green]You:[/bold green] {initial_message}")
222
+ send_single_message(room_id, initial_message, model, toolkits, mcp_connections)
223
+
224
+ while True:
225
+ try:
226
+ # Get user input
227
+ user_input = console.input("[bold green]You:[/bold green] ").strip()
228
+
229
+ if not user_input:
230
+ continue
231
+
232
+ # Handle commands
233
+ if user_input.lower() in ['/exit', '/quit', '/q']:
234
+ console.print("[yellow]Exiting chat...[/yellow]")
235
+ break
236
+ elif user_input.lower() in ['/help', '/h']:
237
+ console.print("\n[bold]Available commands:[/bold]")
238
+ console.print(" /exit, /quit, /q - Exit the chat")
239
+ console.print(" /help, /h - Show this help")
240
+ console.print(" /clear - Clear the screen")
241
+ console.print(" /history - Show chat history")
242
+ console.print()
243
+ continue
244
+ elif user_input.lower() == '/clear':
245
+ console.clear()
246
+ console.print(f"[bold cyan]Chatroom: {room_id}[/bold cyan]")
247
+ console.print("[dim]Type your message or '/exit' to quit[/dim]\n")
248
+ continue
249
+ elif user_input.lower() == '/history':
250
+ try:
251
+ with console.status("[bold cyan]Loading history...[/bold cyan]"):
252
+ data = api.get_chatroom(room_id)
253
+ if data.get('chats'):
254
+ console.print("\n[bold]Recent messages:[/bold]")
255
+ for chat in data['chats'][-10:]: # Last 10 messages
256
+ sender = chat['sender_account']['user']['first_name']
257
+ time = chat['created'][:19]
258
+ message = chat['text'][:100] + "..." if len(chat['text']) > 100 else chat['text']
259
+ if sender == "ChatATP":
260
+ console.print(f"[cyan]{time}[/cyan] [magenta]{sender}:[/magenta] {message}")
261
+ else:
262
+ console.print(f"[cyan]{time}[/cyan] [green]{sender}:[/green] {message}")
263
+ console.print()
264
+ else:
265
+ console.print("[yellow]No chat history found.[/yellow]\n")
266
+ except Exception as e:
267
+ console.print(f"[red]Error loading history: {e}[/red]\n")
268
+ continue
269
+
270
+ # Send the message
271
+ send_single_message(room_id, user_input, model, toolkits, mcp_connections)
272
+
273
+ except KeyboardInterrupt:
274
+ console.print("\n[yellow]Interrupted. Use '/exit' to quit properly.[/yellow]")
275
+ except EOFError:
276
+ console.print("\n[yellow]EOF received. Exiting...[/yellow]")
277
+ break
278
+
279
+ def send_single_message(room_id, message, model=None, toolkits=None, mcp_connections=None, debug=False):
280
+ """Send a single message and handle the streaming response"""
281
+ try:
282
+ full_response = ""
283
+ in_think_block = False
284
+ chunk_count = 0
285
+ response_started = False
286
+ tool_lines = []
287
+
288
+ with console.status("[bold cyan]Sending...[/bold cyan]", spinner="dots") as status:
289
+
290
+ for chunk in api.send_chat_message_stream(
291
+ room_id=room_id,
292
+ message=message,
293
+ model=model,
294
+ toolkit_ids=list(toolkits) if toolkits else None,
295
+ mcp_server_connection_ids=list(mcp_connections) if mcp_connections else None
296
+ ):
297
+ chunk_count += 1
298
+
299
+ if debug:
300
+ console.print(f"[dim]CHUNK #{chunk_count}: {chunk}[/dim]")
301
+
302
+ msg_type = chunk.get('message_type', '')
303
+
304
+ # ── tool_call ──────────────────────────────────────────────
305
+ if msg_type == 'tool_call':
306
+ tool = chunk.get('tool', {})
307
+ tool_name = tool.get('name') or chunk.get('toolkit_name') or 'tool'
308
+ toolkit = chunk.get('toolkit_name', '')
309
+ args = tool.get('arguments', {})
310
+
311
+ arg_hint = ''
312
+ if args:
313
+ first_val = next(iter(args.values()), None)
314
+ if first_val and isinstance(first_val, str) and len(first_val) < 60:
315
+ arg_hint = f' [dim]"{first_val}"[/dim]'
316
+
317
+ # Print a running line — no emoji, clean mono style
318
+ console.print(
319
+ f"\n [dim]┌[/dim] [bold white]{tool_name}[/bold white]"
320
+ f"[dim] · {toolkit}{arg_hint}[/dim]"
321
+ )
322
+ status.update(
323
+ f"[cyan]Running [bold]{tool_name}[/bold]...[/cyan]"
324
+ )
325
+
326
+ # ── tool_result ────────────────────────────────────────────
327
+ elif msg_type == 'tool_result':
328
+ executions = chunk.get('timing_metrics', {}).get('tool_executions', [])
329
+ if executions:
330
+ last = executions[-1]
331
+ tool_name = last.get('tool_name', 'tool')
332
+ duration = last.get('execution_duration', 0)
333
+ ok = last.get('status', 'success') == 'success'
334
+ mark = '[bold green]done[/bold green]' if ok else '[bold red]failed[/bold red]'
335
+ console.print(
336
+ f" [dim]└[/dim] {mark} [dim]{duration:.2f}s[/dim]"
337
+ )
338
+ else:
339
+ console.print(f" [dim]└[/dim] [bold green]done[/bold green]")
340
+
341
+ status.update("[cyan]Processing...[/cyan]")
342
+
343
+ # ── chat_message ───────────────────────────────────────────
344
+ elif msg_type == 'chat_message':
345
+ message_chunk = chunk.get('message', '')
346
+
347
+ if message_chunk:
348
+ if '<think>' in message_chunk:
349
+ in_think_block = True
350
+
351
+ if in_think_block:
352
+ if '</think>' in message_chunk:
353
+ in_think_block = False
354
+ status.update("[cyan]Processing...[/cyan]")
355
+ else:
356
+ status.update("[yellow]Thinking...[/yellow]")
357
+ continue
358
+
359
+ if not response_started:
360
+ response_started = True
361
+ status.stop()
362
+ console.print(
363
+ f"\n[bold white]ChatATP[/bold white] [dim]·[/dim]\n"
364
+ )
365
+
366
+ full_response += message_chunk
367
+
368
+ if chunk.get('is_typing') == False:
369
+ break
370
+
371
+ if response_started:
372
+ console.print(Markdown(full_response))
373
+ console.print("\n[dim]─────────────────────────────────[/dim]")
374
+ elif chunk_count == 0:
375
+ console.print("[red]No data received.[/red]")
376
+ else:
377
+ console.print(f"[yellow]No message content in {chunk_count} chunks.[/yellow]")
378
+ if not debug:
379
+ console.print("[dim]Run with --debug to inspect.[/dim]")
380
+
381
+ except Exception as e:
382
+ console.print(f"[red]Error: {e}[/red]")
383
+ if debug:
384
+ import traceback
385
+ traceback.print_exc()
386
+
387
+ @chat.command()
388
+ @click.argument('message')
389
+ @click.option('--model', default=None, help='Model to use')
390
+ @click.option('--toolkits', multiple=True, help='Toolkit IDs to use')
391
+ @click.option('--mcp-connections', multiple=True, help='MCP connection IDs to use')
392
+ def new(message, model, toolkits, mcp_connections):
393
+ """Create new chatroom and start interactive chat"""
394
+ check_auth()
395
+ try:
396
+ with console.status("[bold green]Creating chatroom..."):
397
+ data = api.create_chatroom(
398
+ message=message,
399
+ model=model,
400
+ toolkit_ids=list(toolkits) if toolkits else None,
401
+ mcp_server_connection_ids=list(mcp_connections) if mcp_connections else None
402
+ )
403
+
404
+ room_id = data['room_id']
405
+ console.print(f"[green]Chatroom created: {room_id}[/green]")
406
+
407
+ # Start the interactive chat loop
408
+ chat_loop(room_id, initial_message=message, model=model, toolkits=toolkits, mcp_connections=mcp_connections)
409
+
410
+ except Exception as e:
411
+ console.print(f"[red]Error: {e}[/red]")
412
+
413
+ @chat.command()
414
+ @click.argument('room_id')
415
+ @click.option('--model', default=None, help='Model to use')
416
+ @click.option('--toolkits', multiple=True, help='Toolkit IDs to use')
417
+ @click.option('--mcp-connections', multiple=True, help='MCP connection IDs to use')
418
+ def converse(room_id, model, toolkits, mcp_connections):
419
+ """Enter existing chatroom for interactive chat"""
420
+ check_auth()
421
+ try:
422
+ # Verify the room exists
423
+ with console.status("[bold cyan]Entering chatroom...[/bold cyan]"):
424
+ data = api.get_chatroom(room_id)
425
+
426
+ # Start the interactive chat loop
427
+ chat_loop(room_id, initial_message=None, model=model, toolkits=toolkits, mcp_connections=mcp_connections)
428
+
429
+ except Exception as e:
430
+ console.print(f"[red]Error entering chatroom: {e}[/red]")
431
+
432
+ @chat.command()
433
+ @click.argument('room_id')
434
+ @click.argument('message')
435
+ @click.option('--model', default=None, help='Model to use')
436
+ @click.option('--toolkits', multiple=True, help='Toolkit IDs to use')
437
+ @click.option('--mcp-connections', multiple=True, help='MCP connection IDs to use')
438
+ @click.option('--debug', is_flag=True, help='Show raw stream chunks')
439
+ def send(room_id, message, model, toolkits, mcp_connections, debug):
440
+ """Send message to chatroom"""
441
+ check_auth()
442
+ try:
443
+ full_response = ""
444
+ in_think_block = False
445
+ chunk_count = 0
446
+ response_started = False
447
+ tool_lines = [] # track printed tool lines to update them
448
+
449
+ with console.status("[bold cyan]Sending...[/bold cyan]", spinner="dots") as status:
450
+
451
+ for chunk in api.send_chat_message_stream(
452
+ room_id=room_id,
453
+ message=message,
454
+ model=model,
455
+ toolkit_ids=list(toolkits) if toolkits else None,
456
+ mcp_server_connection_ids=list(mcp_connections) if mcp_connections else None
457
+ ):
458
+ chunk_count += 1
459
+
460
+ if debug:
461
+ console.print(f"[dim]CHUNK #{chunk_count}: {chunk}[/dim]")
462
+
463
+ msg_type = chunk.get('message_type', '')
464
+
465
+ # ── tool_call ──────────────────────────────────────────────
466
+ if msg_type == 'tool_call':
467
+ tool = chunk.get('tool', {})
468
+ tool_name = tool.get('name') or chunk.get('toolkit_name') or 'tool'
469
+ toolkit = chunk.get('toolkit_name', '')
470
+ args = tool.get('arguments', {})
471
+
472
+ arg_hint = ''
473
+ if args:
474
+ first_val = next(iter(args.values()), None)
475
+ if first_val and isinstance(first_val, str) and len(first_val) < 60:
476
+ arg_hint = f' [dim]"{first_val}"[/dim]'
477
+
478
+ # Print a running line — no emoji, clean mono style
479
+ console.print(
480
+ f"\n [dim]┌[/dim] [bold white]{tool_name}[/bold white]"
481
+ f"[dim] · {toolkit}{arg_hint}[/dim]"
482
+ )
483
+ status.update(
484
+ f"[cyan]Running [bold]{tool_name}[/bold]...[/cyan]"
485
+ )
486
+
487
+ # ── tool_result ────────────────────────────────────────────
488
+ elif msg_type == 'tool_result':
489
+ executions = chunk.get('timing_metrics', {}).get('tool_executions', [])
490
+ if executions:
491
+ last = executions[-1]
492
+ tool_name = last.get('tool_name', 'tool')
493
+ duration = last.get('execution_duration', 0)
494
+ ok = last.get('status', 'success') == 'success'
495
+ mark = '[bold green]done[/bold green]' if ok else '[bold red]failed[/bold red]'
496
+ console.print(
497
+ f" [dim]└[/dim] {mark} [dim]{duration:.2f}s[/dim]"
498
+ )
499
+ else:
500
+ console.print(f" [dim]└[/dim] [bold green]done[/bold green]")
501
+
502
+ status.update("[cyan]Processing...[/cyan]")
503
+
504
+ # ── chat_message ───────────────────────────────────────────
505
+ elif msg_type == 'chat_message':
506
+ message_chunk = chunk.get('message', '')
507
+
508
+ if message_chunk:
509
+ if '<think>' in message_chunk:
510
+ in_think_block = True
511
+
512
+ if in_think_block:
513
+ if '</think>' in message_chunk:
514
+ in_think_block = False
515
+ status.update("[cyan]Processing...[/cyan]")
516
+ else:
517
+ status.update("[yellow]Thinking...[/yellow]")
518
+ continue
519
+
520
+ if not response_started:
521
+ response_started = True
522
+ status.stop()
523
+ console.print(
524
+ f"\n[bold white]ChatATP[/bold white] [dim]·[/dim]\n"
525
+ )
526
+
527
+ full_response += message_chunk
528
+
529
+ if chunk.get('is_typing') == False:
530
+ break
531
+
532
+ if response_started:
533
+ console.print(Markdown(full_response))
534
+ console.print("\n[dim]─────────────────────────────────[/dim]")
535
+ elif chunk_count == 0:
536
+ console.print("[red]No data received.[/red]")
537
+ else:
538
+ console.print(f"[yellow]No message content in {chunk_count} chunks.[/yellow]")
539
+ if not debug:
540
+ console.print("[dim]Run with --debug to inspect.[/dim]")
541
+
542
+ except Exception as e:
543
+ console.print(f"[red]Error: {e}[/red]")
544
+ if debug:
545
+ import traceback
546
+ traceback.print_exc()
547
+
548
+ # Integrations commands
549
+ @cli.group()
550
+ def integrations():
551
+ """Integration management"""
552
+ pass
553
+
554
+ @integrations.command()
555
+ def list():
556
+ """List integrations"""
557
+ check_auth()
558
+ try:
559
+ with console.status("[bold green]Fetching integrations..."):
560
+ data = api.list_integrations()
561
+
562
+ table = Table(title="OAuth Integrations")
563
+ table.add_column("Platform", style="cyan")
564
+ table.add_column("Display Name", style="magenta")
565
+ table.add_column("Status", style="green")
566
+
567
+ for integration in data['integrations']:
568
+ platform = integration['platform']['display_name'] if integration.get('platform') else 'N/A'
569
+ table.add_row(
570
+ integration['platform']['name'] if integration.get('platform') else 'Unknown',
571
+ platform,
572
+ "Connected" if integration.get('access_token') else "Not Connected"
573
+ )
574
+
575
+ console.print(table)
576
+ except Exception as e:
577
+ console.print(f"[red]Error: {e}[/red]")
578
+
579
+ @integrations.command()
580
+ def custom():
581
+ """List custom integrations"""
582
+ check_auth()
583
+ try:
584
+ with console.status("[bold green]Fetching custom integrations..."):
585
+ data = api.list_custom_integrations()
586
+
587
+ table = Table(title="Custom Integrations")
588
+ table.add_column("Name", style="cyan")
589
+ table.add_column("Unique Name", style="magenta")
590
+ table.add_column("API Key", style="green")
591
+
592
+ for integration in data['integrations']:
593
+ table.add_row(
594
+ integration['name'],
595
+ integration['unique_name'],
596
+ "Set" if integration['api_key'] else "Not Set"
597
+ )
598
+
599
+ console.print(table)
600
+ except Exception as e:
601
+ console.print(f"[red]Error: {e}[/red]")
602
+
603
+ # AI commands
604
+ @cli.group()
605
+ def ai():
606
+ """AI management"""
607
+ pass
608
+
609
+ @ai.command()
610
+ def providers():
611
+ """List AI providers"""
612
+ check_auth()
613
+ try:
614
+ with console.status("[bold green]Fetching providers..."):
615
+ data = api.list_ai_providers()
616
+
617
+ table = Table(title="AI Providers")
618
+ table.add_column("Name", style="cyan")
619
+ table.add_column("Unique Name", style="magenta")
620
+ table.add_column("Category", style="white")
621
+ table.add_column("Connected", style="green")
622
+
623
+ for provider in data:
624
+ table.add_row(
625
+ provider['name'],
626
+ provider['unique_name'],
627
+ provider['category'],
628
+ "✓" if provider.get('connected', False) else ""
629
+ )
630
+
631
+ console.print(table)
632
+ except Exception as e:
633
+ console.print(f"[red]Error: {e}[/red]")
634
+
635
+ @ai.command()
636
+ def configs():
637
+ """List AI configurations"""
638
+ check_auth()
639
+ try:
640
+ with console.status("[bold green]Fetching configs..."):
641
+ data = api.list_ai_configs()
642
+
643
+ table = Table(title="AI Configurations")
644
+ table.add_column("Provider", style="cyan")
645
+ table.add_column("Config ID", style="magenta")
646
+ table.add_column("Default", style="green")
647
+ table.add_column("API Key", style="white")
648
+
649
+ for config_item in data:
650
+ provider_name = config_item['provider']['name']
651
+ table.add_row(
652
+ provider_name,
653
+ config_item['config_id'],
654
+ "✓" if config_item.get('default', False) else "",
655
+ "Set" if config_item.get('api_key') else "Not Set"
656
+ )
657
+
658
+ console.print(table)
659
+ except Exception as e:
660
+ console.print(f"[red]Error: {e}[/red]")
661
+
662
+ @ai.command()
663
+ @click.argument('provider_id')
664
+ def provider_models(provider_id):
665
+ """List models for a provider"""
666
+ check_auth()
667
+ try:
668
+ with console.status("[bold green]Fetching provider models..."):
669
+ data = api.list_provider_models(provider_id)
670
+
671
+ table = Table(title=f"Models for Provider {provider_id}")
672
+ table.add_column("ID", style="cyan")
673
+ table.add_column("Name", style="magenta")
674
+ table.add_column("Description", style="white")
675
+
676
+ for model in data:
677
+ table.add_row(
678
+ model['id'],
679
+ model['name'],
680
+ model['description']
681
+ )
682
+
683
+ console.print(table)
684
+ except Exception as e:
685
+ console.print(f"[red]Error: {e}[/red]")
686
+
687
+ @ai.command()
688
+ def settings():
689
+ """Show AI settings"""
690
+ check_auth()
691
+ try:
692
+ with console.status("[bold green]Fetching settings..."):
693
+ data = api.get_ai_settings()
694
+
695
+ console.print(Panel.fit(
696
+ f"[bold blue]Default Chat Model:[/bold blue] {data['default_chat_completion_model_id']}\n"
697
+ f"[bold blue]Default Image Model:[/bold blue] {data['default_image_gen_model_id']}\n"
698
+ f"[bold blue]Default Speech Model:[/bold blue] {data['default_speech_gen_model_id']}\n"
699
+ f"[bold blue]Temperature:[/bold blue] {data['temperature']}\n"
700
+ f"[bold blue]Max Tokens:[/bold blue] {data['max_tokens']}\n"
701
+ f"[bold blue]System Instruction:[/bold blue] {data['system_instruction'][:100]}...",
702
+ title="AI Settings"
703
+ ))
704
+ except Exception as e:
705
+ console.print(f"[red]Error: {e}[/red]")
706
+
707
+ # Store commands
708
+ @cli.group()
709
+ def store():
710
+ """Store management"""
711
+ pass
712
+
713
+ @store.command()
714
+ def featured():
715
+ """List featured toolkits"""
716
+ check_auth()
717
+ try:
718
+ with console.status("[bold green]Fetching featured toolkits..."):
719
+ data = api.list_featured_toolkits()
720
+
721
+ table = Table(title="Featured Toolkits")
722
+ table.add_column("Name", style="cyan")
723
+ table.add_column("Display Name", style="magenta")
724
+ table.add_column("Category", style="white")
725
+ table.add_column("Installs", style="green")
726
+
727
+ for toolkit in data['featured']:
728
+ table.add_row(
729
+ toolkit['name'],
730
+ toolkit['display_name'],
731
+ toolkit['category'],
732
+ str(toolkit['installs'])
733
+ )
734
+
735
+ console.print(table)
736
+ except Exception as e:
737
+ console.print(f"[red]Error: {e}[/red]")
738
+
739
+ @store.command()
740
+ def popular():
741
+ """List popular toolkits"""
742
+ check_auth()
743
+ try:
744
+ with console.status("[bold green]Fetching popular toolkits..."):
745
+ data = api.list_popular_toolkits()
746
+
747
+ table = Table(title="Popular Toolkits")
748
+ table.add_column("Name", style="cyan")
749
+ table.add_column("Display Name", style="magenta")
750
+ table.add_column("Category", style="white")
751
+ table.add_column("Installs", style="green")
752
+
753
+ for toolkit in data['popular_toolkits']:
754
+ table.add_row(
755
+ toolkit['name'],
756
+ toolkit['display_name'],
757
+ toolkit['category'],
758
+ str(toolkit['installs'])
759
+ )
760
+
761
+ console.print(table)
762
+ except Exception as e:
763
+ console.print(f"[red]Error: {e}[/red]")
764
+
765
+ # Media commands
766
+ @cli.command()
767
+ @click.option('--page', default=1, help='Page number')
768
+ @click.option('--page-size', default=12, help='Items per page')
769
+ @click.option('--type', 'media_type', help='Media type (image, video, audio, document)')
770
+ @click.option('--search', help='Search query')
771
+ def media(page, page_size, media_type, search):
772
+ """List user media"""
773
+ check_auth()
774
+ try:
775
+ with console.status("[bold green]Fetching media..."):
776
+ data = api.list_media(page=page, page_size=page_size, media_type=media_type, search=search)
777
+
778
+ table = Table(title="Media Files")
779
+ table.add_column("Type", style="cyan")
780
+ table.add_column("File Name", style="magenta")
781
+ table.add_column("Created", style="white")
782
+ table.add_column("URL", style="green")
783
+
784
+ for item in data['results']:
785
+ media_type_display = "Document" if item['media_is_doc'] else "Image" if item['media_is_img'] else "Video" if item['media_is_vid'] else "Audio" if item['media_is_aud'] else "Other"
786
+ filename = item['extra_data'].get('original_name', 'Unknown')
787
+ table.add_row(
788
+ media_type_display,
789
+ filename,
790
+ item['created'][:19],
791
+ item['media_url'][:50] + "..."
792
+ )
793
+
794
+ console.print(table)
795
+ console.print(f"\nPage {data['next']} of {len(data['results'])} items")
796
+ except Exception as e:
797
+ console.print(f"[red]Error: {e}[/red]")
798
+
799
+ # Pricing command
800
+ @cli.command()
801
+ def pricing():
802
+ """Show pricing information"""
803
+ check_auth()
804
+ try:
805
+ with console.status("[bold green]Fetching pricing..."):
806
+ data = api.get_pricing()
807
+
808
+ for plan in data:
809
+ console.print(Panel.fit(
810
+ f"[bold blue]Name:[/bold blue] {plan['name']}\n"
811
+ f"[bold blue]Description:[/bold blue] {plan['description']}\n"
812
+ f"[bold blue]Features:[/bold blue]\n" +
813
+ "\n".join(f" • {feature}" for feature in plan['features']),
814
+ title=f"Plan: {plan['name']}"
815
+ ))
816
+
817
+ if plan.get('prices'):
818
+ console.print("[bold]Pricing:[/bold]")
819
+ for price in plan['prices']:
820
+ interval = price['interval']
821
+ amount = price['amount']
822
+ currency = price['currency'].upper()
823
+ console.print(f" {interval.capitalize()}: ${amount} {currency}")
824
+
825
+ console.print() # Spacing between plans
826
+ except Exception as e:
827
+ console.print(f"[red]Error: {e}[/red]")
828
+
829
+ if __name__ == '__main__':
830
+ cli()
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatatp-cli
3
+ Version: 1.0.0
4
+ Summary: ChatATP CLI - Terminal Interface for ChatATP API
5
+ Author-email: ChatATP Team <support@chatatp.com>
6
+ Project-URL: Homepage, https://chat-atp.com
7
+ Project-URL: Documentation, https://docs.chat-atp.com
8
+ Project-URL: Repository, https://github.com/sam-14uel/ChatATP-Python-Cli
9
+ Project-URL: Issues, https://github.com/sam-14uel/ChatATP-Python-Cli/issues
10
+ Requires-Dist: requests
11
+ Requires-Dist: click
12
+ Requires-Dist: rich
13
+ Requires-Dist: pyyaml
14
+ Requires-Dist: python-dotenv
@@ -0,0 +1,9 @@
1
+ chatatp_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ chatatp_cli/api_client.py,sha256=VF6zeUWFmHKySvSTbOBwjJg4jsGQPdgH8H0K7IfY-us,6758
3
+ chatatp_cli/config.py,sha256=MKOBTJNTCAGBkiten_CwVIdnevggYqn4_5UfdAPUDpU,2129
4
+ chatatp_cli/main.py,sha256=pv9j8n40HbrA7rORVESxI4Dygft_RwL13imA-FkFcqM,32707
5
+ chatatp_cli-1.0.0.dist-info/METADATA,sha256=l-iFwsombzRka25jYDq0EEoQS2mXr7bBScogbOt83OU,539
6
+ chatatp_cli-1.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
7
+ chatatp_cli-1.0.0.dist-info/entry_points.txt,sha256=YdQ_m7TbOrgPga1fnM4LbMm_4R5HZOm81_y44bnGSZ0,49
8
+ chatatp_cli-1.0.0.dist-info/top_level.txt,sha256=zs71L0-w-kyZboyFlu5ah49qJgT-H-wB_T9Ae0kTWoQ,12
9
+ chatatp_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ chatatp = chatatp_cli.main:cli
@@ -0,0 +1 @@
1
+ chatatp_cli