agentic-fabriq-sdk 0.1.13__py3-none-any.whl → 0.1.15__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.

Potentially problematic release.


This version of agentic-fabriq-sdk might be problematic. Click here for more details.

af_cli/commands/agents.py CHANGED
@@ -14,10 +14,10 @@ app = typer.Typer(help="Agent management commands")
14
14
 
15
15
  @app.command()
16
16
  def list(
17
- page: int = typer.Option(1, "--page", "-p", help="Page number"),
18
- page_size: int = typer.Option(20, "--page-size", "-s", help="Page size"),
19
- search: Optional[str] = typer.Option(None, "--search", "-q", help="Search query"),
20
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
17
+ page: int = typer.Option(1, "--page", help="Page number"),
18
+ page_size: int = typer.Option(20, "--page-size", help="Page size"),
19
+ search: Optional[str] = typer.Option(None, "--search", help="Search query"),
20
+ format: str = typer.Option("table", "--format", help="Output format"),
21
21
  ):
22
22
  """List agents."""
23
23
  try:
@@ -82,7 +82,7 @@ def list(
82
82
  @app.command()
83
83
  def get(
84
84
  agent_id: str = typer.Argument(..., help="Agent ID"),
85
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
85
+ format: str = typer.Option("table", "--format", help="Output format"),
86
86
  ):
87
87
  """Get agent details."""
88
88
  try:
@@ -102,12 +102,12 @@ def get(
102
102
 
103
103
  @app.command()
104
104
  def create(
105
- name: str = typer.Option(..., "--name", "-n", help="Agent name"),
106
- description: Optional[str] = typer.Option(None, "--description", "-d", help="Agent description"),
107
- version: str = typer.Option("1.0.0", "--version", "-v", help="Agent version"),
105
+ name: str = typer.Option(..., "--name", help="Agent name"),
106
+ description: Optional[str] = typer.Option(None, "--description", help="Agent description"),
107
+ version: str = typer.Option("1.0.0", "--version", help="Agent version"),
108
108
  protocol: str = typer.Option("HTTP", "--protocol", help="Agent protocol"),
109
- endpoint_url: str = typer.Option(..., "--endpoint-url", "-u", help="Agent endpoint URL"),
110
- auth_method: str = typer.Option("OAUTH2", "--auth-method", "-a", help="Authentication method"),
109
+ endpoint_url: str = typer.Option(..., "--endpoint-url", help="Agent endpoint URL"),
110
+ auth_method: str = typer.Option("OAUTH2", "--auth-method", help="Authentication method"),
111
111
  ):
112
112
  """Create a new agent."""
113
113
  try:
@@ -135,12 +135,12 @@ def create(
135
135
  @app.command()
136
136
  def update(
137
137
  agent_id: str = typer.Argument(..., help="Agent ID"),
138
- name: Optional[str] = typer.Option(None, "--name", "-n", help="Agent name"),
139
- description: Optional[str] = typer.Option(None, "--description", "-d", help="Agent description"),
140
- version: Optional[str] = typer.Option(None, "--version", "-v", help="Agent version"),
138
+ name: Optional[str] = typer.Option(None, "--name", help="Agent name"),
139
+ description: Optional[str] = typer.Option(None, "--description", help="Agent description"),
140
+ version: Optional[str] = typer.Option(None, "--version", help="Agent version"),
141
141
  protocol: Optional[str] = typer.Option(None, "--protocol", help="Agent protocol"),
142
- endpoint_url: Optional[str] = typer.Option(None, "--endpoint-url", "-u", help="Agent endpoint URL"),
143
- auth_method: Optional[str] = typer.Option(None, "--auth-method", "-a", help="Authentication method"),
142
+ endpoint_url: Optional[str] = typer.Option(None, "--endpoint-url", help="Agent endpoint URL"),
143
+ auth_method: Optional[str] = typer.Option(None, "--auth-method", help="Authentication method"),
144
144
  ):
145
145
  """Update an agent."""
146
146
  try:
@@ -178,7 +178,7 @@ def update(
178
178
  @app.command()
179
179
  def delete(
180
180
  agent_id: str = typer.Argument(..., help="Agent ID"),
181
- force: bool = typer.Option(False, "--force", "-f", help="Force deletion without confirmation"),
181
+ force: bool = typer.Option(False, "--force", help="Force deletion without confirmation"),
182
182
  ):
183
183
  """Delete an agent."""
184
184
  try:
@@ -200,8 +200,8 @@ def delete(
200
200
  @app.command()
201
201
  def invoke(
202
202
  agent_id: str = typer.Argument(..., help="Agent ID"),
203
- input_text: str = typer.Option(..., "--input", "-i", help="Input message for the agent"),
204
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
203
+ input_text: str = typer.Option(..., "--input", help="Input message for the agent"),
204
+ format: str = typer.Option("table", "--format", help="Output format"),
205
205
  ):
206
206
  """Invoke an agent."""
207
207
  try:
@@ -0,0 +1,323 @@
1
+ """
2
+ CLI commands for managing registered applications.
3
+ """
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import httpx
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from af_cli.core.config import get_config
15
+ from af_cli.core.output import print_output
16
+
17
+ app = typer.Typer(help="Manage registered applications")
18
+ console = Console()
19
+
20
+
21
+ @app.command("create")
22
+ def create_application(
23
+ app_id: str = typer.Option(..., "--app-id", help="Application identifier (no spaces)"),
24
+ connections: str = typer.Option(..., "--connections", help="Tool connections (format: 'tool1:conn-id,tool2:conn-id')"),
25
+ scopes: Optional[str] = typer.Option(None, "--scopes", help="Scopes (format: 'scope1,scope2,scope3')"),
26
+ ):
27
+ """
28
+ Register a new application.
29
+
30
+ Example:
31
+ afctl applications create \\
32
+ --app-id my-slack-bot \\
33
+ --connections slack:my-slack-conn,github:my-github-conn \\
34
+ --scopes slack:read,slack:write,github:repo:read
35
+ """
36
+ config = get_config()
37
+
38
+ if not config.is_authenticated():
39
+ console.print("❌ Not authenticated. Run 'afctl auth login' first.", style="red")
40
+ raise typer.Exit(1)
41
+
42
+ # Parse connections
43
+ tool_connections = {}
44
+ if connections:
45
+ for conn_pair in connections.split(","):
46
+ try:
47
+ tool, conn_id = conn_pair.split(":")
48
+ tool_connections[conn_id] = [] # Will add scopes below
49
+ except ValueError:
50
+ console.print(f"❌ Invalid connection format: '{conn_pair}'. Use 'tool:conn-id'", style="red")
51
+ raise typer.Exit(1)
52
+
53
+ # Parse scopes and assign to connections
54
+ if scopes:
55
+ scope_list = [s.strip() for s in scopes.split(",")]
56
+ # For simplicity, assign all scopes to all connections
57
+ # In production, you might want per-connection scopes
58
+ for conn_id in tool_connections:
59
+ tool_connections[conn_id] = scope_list
60
+
61
+ # Make API request
62
+ try:
63
+ response = httpx.post(
64
+ f"{config.gateway_url}/api/v1/applications",
65
+ headers={"Authorization": f"Bearer {config.access_token}"},
66
+ json={
67
+ "app_id": app_id,
68
+ "tool_connections": tool_connections
69
+ },
70
+ timeout=30.0
71
+ )
72
+
73
+ if response.status_code != 201:
74
+ error_detail = response.text
75
+ try:
76
+ error_json = response.json()
77
+ error_detail = error_json.get("detail", response.text)
78
+ except:
79
+ pass
80
+
81
+ console.print(f"❌ Failed to register application: {error_detail}", style="red")
82
+ raise typer.Exit(1)
83
+
84
+ data = response.json()
85
+
86
+ # Save credentials locally
87
+ from af_sdk import save_application_config
88
+
89
+ app_config = {
90
+ "app_id": data["app_id"],
91
+ "secret_key": data["secret_key"],
92
+ "user_id": data["user_id"],
93
+ "tenant_id": data["tenant_id"],
94
+ "tool_connections": data["tool_connections"],
95
+ "created_at": data["created_at"],
96
+ "gateway_url": config.gateway_url
97
+ }
98
+
99
+ app_file = save_application_config(data["app_id"], app_config)
100
+
101
+ # Display success
102
+ console.print("\n✅ Application registered successfully!", style="green bold")
103
+ console.print(f"\n📋 App ID: {data['app_id']}", style="cyan")
104
+ console.print(f"🔑 Secret Key: {data['secret_key']}", style="yellow")
105
+ console.print(f"\n💾 Credentials saved to: {app_file}", style="green")
106
+ console.print("\n⚠️ Save the secret key securely! It won't be shown again.", style="yellow bold")
107
+ console.print("\n🚀 Your agent can now authenticate with:", style="cyan")
108
+ console.print(f" from af_sdk import get_application_client", style="white")
109
+ console.print(f" client = await get_application_client('{data['app_id']}')", style="white")
110
+
111
+ except httpx.HTTPError as e:
112
+ console.print(f"❌ Network error: {e}", style="red")
113
+ raise typer.Exit(1)
114
+
115
+
116
+ @app.command("list")
117
+ def list_applications(
118
+ format: str = typer.Option("table", "--format", help="Output format (table, json, yaml)"),
119
+ ):
120
+ """
121
+ List all registered applications.
122
+
123
+ Shows applications from both:
124
+ - Local config files (~/.af/applications/)
125
+ - Server (via API)
126
+ """
127
+ config = get_config()
128
+
129
+ # Load from local config first
130
+ from af_sdk import list_applications as list_local_apps
131
+
132
+ local_apps = list_local_apps()
133
+
134
+ if format == "table":
135
+ if not local_apps:
136
+ console.print("No applications registered locally.", style="yellow")
137
+ return
138
+
139
+ table = Table(title="Registered Applications")
140
+ table.add_column("App ID", style="cyan")
141
+ table.add_column("Created", style="green")
142
+ table.add_column("Tool Connections", style="magenta")
143
+ table.add_column("Config File", style="white")
144
+
145
+ for app in local_apps:
146
+ conn_count = len(app.get("tool_connections", {}))
147
+ conn_str = f"{conn_count} connection(s)"
148
+ config_file = f"~/.af/applications/{app['app_id']}.json"
149
+
150
+ table.add_row(
151
+ app["app_id"],
152
+ app.get("created_at", "N/A")[:10], # Just date
153
+ conn_str,
154
+ config_file
155
+ )
156
+
157
+ console.print(table)
158
+ console.print(f"\n📊 Total: {len(local_apps)} application(s)")
159
+ else:
160
+ print_output(
161
+ {"applications": local_apps, "total": len(local_apps)},
162
+ format_type=format,
163
+ title="Registered Applications"
164
+ )
165
+
166
+
167
+ @app.command("show")
168
+ def show_application(
169
+ app_id: str = typer.Argument(..., help="Application identifier"),
170
+ reveal_secret: bool = typer.Option(False, "--reveal-secret", help="Reveal the secret key"),
171
+ ):
172
+ """
173
+ Show details of a registered application.
174
+
175
+ Example:
176
+ afctl applications show my-slack-bot
177
+ afctl applications show my-slack-bot --reveal-secret
178
+ """
179
+ from af_sdk import load_application_config, ApplicationNotFoundError
180
+
181
+ try:
182
+ app_config = load_application_config(app_id)
183
+ except ApplicationNotFoundError as e:
184
+ console.print(f"❌ {e}", style="red")
185
+ raise typer.Exit(1)
186
+
187
+ console.print(f"\n📋 Application: {app_config['app_id']}", style="cyan bold")
188
+ console.print(f"👤 User ID: {app_config.get('user_id', 'N/A')}", style="white")
189
+ console.print(f"🏢 Tenant ID: {app_config.get('tenant_id', 'N/A')}", style="white")
190
+ console.print(f"📅 Created: {app_config.get('created_at', 'N/A')}", style="white")
191
+ console.print(f"🌐 Gateway: {app_config.get('gateway_url', 'N/A')}", style="white")
192
+
193
+ if reveal_secret:
194
+ console.print(f"\n🔑 Secret Key: {app_config['secret_key']}", style="yellow bold")
195
+ console.print("⚠️ Keep this secret secure!", style="yellow")
196
+ else:
197
+ console.print(f"\n🔑 Secret Key: ••••••••", style="white")
198
+ console.print(" Use --reveal-secret to show", style="dim")
199
+
200
+ console.print("\n🔌 Tool Connections:", style="cyan bold")
201
+ tool_conns = app_config.get("tool_connections", {})
202
+ if tool_conns:
203
+ for conn_id, scopes in tool_conns.items():
204
+ console.print(f" • {conn_id}", style="white")
205
+ if scopes:
206
+ console.print(f" Scopes: {', '.join(scopes)}", style="dim")
207
+ else:
208
+ console.print(" (none)", style="dim")
209
+
210
+ config_file = Path.home() / ".af" / "applications" / f"{app_id}.json"
211
+ console.print(f"\n💾 Config file: {config_file}", style="green")
212
+
213
+
214
+ @app.command("delete")
215
+ def delete_application(
216
+ app_id: str = typer.Argument(..., help="Application identifier"),
217
+ yes: bool = typer.Option(False, "--yes", help="Skip confirmation"),
218
+ ):
219
+ """
220
+ Delete a registered application.
221
+
222
+ This will:
223
+ - Delete the application registration on the server
224
+ - Remove local credentials
225
+ - Invalidate all active tokens
226
+
227
+ Example:
228
+ afctl applications delete my-slack-bot
229
+ afctl applications delete my-slack-bot --yes
230
+ """
231
+ config = get_config()
232
+
233
+ if not config.is_authenticated():
234
+ console.print("❌ Not authenticated. Run 'afctl auth login' first.", style="red")
235
+ raise typer.Exit(1)
236
+
237
+ # Confirm deletion
238
+ if not yes:
239
+ console.print(f"\n⚠️ This will:", style="yellow bold")
240
+ console.print(f" • Delete the application registration on the server", style="white")
241
+ console.print(f" • Remove local credentials from ~/.af/applications/{app_id}.json", style="white")
242
+ console.print(f" • Invalidate all active tokens for this application", style="white")
243
+
244
+ confirm = typer.confirm(f"\nAre you sure you want to delete '{app_id}'?", default=False)
245
+ if not confirm:
246
+ console.print("❌ Cancelled", style="yellow")
247
+ raise typer.Exit(0)
248
+
249
+ # Delete from server
250
+ try:
251
+ response = httpx.delete(
252
+ f"{config.gateway_url}/api/v1/applications/{app_id}",
253
+ headers={"Authorization": f"Bearer {config.access_token}"},
254
+ timeout=30.0
255
+ )
256
+
257
+ if response.status_code == 404:
258
+ console.print(f"⚠️ Application '{app_id}' not found on server", style="yellow")
259
+ elif response.status_code != 204:
260
+ error_detail = response.text
261
+ try:
262
+ error_json = response.json()
263
+ error_detail = error_json.get("detail", response.text)
264
+ except:
265
+ pass
266
+
267
+ console.print(f"❌ Failed to delete from server: {error_detail}", style="red")
268
+ raise typer.Exit(1)
269
+ else:
270
+ console.print(f"✅ Deleted from server", style="green")
271
+
272
+ except httpx.HTTPError as e:
273
+ console.print(f"❌ Network error: {e}", style="red")
274
+ raise typer.Exit(1)
275
+
276
+ # Delete local config
277
+ from af_sdk import delete_application_config
278
+
279
+ deleted = delete_application_config(app_id)
280
+ if deleted:
281
+ console.print(f"✅ Deleted local credentials", style="green")
282
+ else:
283
+ console.print(f"⚠️ Local credentials not found", style="yellow")
284
+
285
+ console.print(f"\n🎉 Application '{app_id}' deleted successfully", style="green bold")
286
+
287
+
288
+ @app.command("test")
289
+ def test_application(
290
+ app_id: str = typer.Argument(..., help="Application identifier"),
291
+ ):
292
+ """
293
+ Test application authentication.
294
+
295
+ Attempts to exchange credentials for a token to verify the application
296
+ is properly registered and can authenticate.
297
+
298
+ Example:
299
+ afctl applications test my-slack-bot
300
+ """
301
+ import asyncio
302
+ from af_sdk import get_application_client, ApplicationNotFoundError, AuthenticationError
303
+
304
+ async def _test():
305
+ try:
306
+ console.print(f"🔄 Testing authentication for '{app_id}'...", style="cyan")
307
+
308
+ client = await get_application_client(app_id)
309
+
310
+ console.print(f"✅ Authentication successful!", style="green bold")
311
+ console.print(f"\n📋 Application: {client._app_id}", style="cyan")
312
+ console.print(f"⏱️ Token expires in: {client._expires_in} seconds", style="white")
313
+ console.print(f"\n🎉 Your application can authenticate and make API calls!", style="green")
314
+
315
+ except ApplicationNotFoundError as e:
316
+ console.print(f"❌ {e}", style="red")
317
+ raise typer.Exit(1)
318
+ except AuthenticationError as e:
319
+ console.print(f"❌ {e}", style="red")
320
+ raise typer.Exit(1)
321
+
322
+ asyncio.run(_test())
323
+
af_cli/commands/auth.py CHANGED
@@ -51,18 +51,15 @@ def login(
51
51
  tenant_id: Optional[str] = typer.Option(
52
52
  None,
53
53
  "--tenant-id",
54
- "-t",
55
54
  help="Tenant ID (optional, can be extracted from JWT)"
56
55
  ),
57
56
  browser: bool = typer.Option(
58
57
  True,
59
- "--browser/--no-browser",
60
58
  help="Open browser for authentication"
61
59
  ),
62
60
  keycloak_url: Optional[str] = typer.Option(
63
61
  None,
64
62
  "--keycloak-url",
65
- "-k",
66
63
  help="Keycloak URL (default: https://auth.agenticfabriq.com or from config)"
67
64
  ),
68
65
  ):
@@ -142,7 +139,6 @@ def logout(
142
139
  keycloak_url: Optional[str] = typer.Option(
143
140
  None,
144
141
  "--keycloak-url",
145
- "-k",
146
142
  help="Keycloak URL (default: https://auth.agenticfabriq.com or from config)"
147
143
  ),
148
144
  ):
@@ -254,7 +250,6 @@ def refresh(
254
250
  keycloak_url: Optional[str] = typer.Option(
255
251
  None,
256
252
  "--keycloak-url",
257
- "-k",
258
253
  help="Keycloak URL (default: https://auth.agenticfabriq.com or from config)"
259
254
  ),
260
255
  ):
@@ -320,7 +315,6 @@ def token(
320
315
  show_full: bool = typer.Option(
321
316
  False,
322
317
  "--full",
323
- "-f",
324
318
  help="Show full token (warning: sensitive information)"
325
319
  ),
326
320
  ):
af_cli/commands/config.py CHANGED
@@ -12,7 +12,7 @@ app = typer.Typer(help="Configuration commands")
12
12
 
13
13
  @app.command()
14
14
  def show(
15
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
15
+ format: str = typer.Option("table", "--format", help="Output format"),
16
16
  ):
17
17
  """Show current configuration."""
18
18
  import os
@@ -12,7 +12,7 @@ app = typer.Typer(help="MCP server management commands")
12
12
 
13
13
  @app.command()
14
14
  def list(
15
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
15
+ format: str = typer.Option("table", "--format", help="Output format"),
16
16
  ):
17
17
  """List MCP servers."""
18
18
  try:
@@ -38,7 +38,7 @@ def list(
38
38
  @app.command()
39
39
  def get(
40
40
  server_id: str = typer.Argument(..., help="MCP server ID"),
41
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
41
+ format: str = typer.Option("table", "--format", help="Output format"),
42
42
  ):
43
43
  """Get MCP server details."""
44
44
  try:
@@ -58,9 +58,9 @@ def get(
58
58
 
59
59
  @app.command()
60
60
  def create(
61
- name: str = typer.Option(..., "--name", "-n", help="MCP server name"),
62
- base_url: str = typer.Option(..., "--base-url", "-u", help="Base URL"),
63
- auth_type: str = typer.Option("API_KEY", "--auth-type", "-a", help="Authentication type"),
61
+ name: str = typer.Option(..., "--name", help="MCP server name"),
62
+ base_url: str = typer.Option(..., "--base-url", help="Base URL"),
63
+ auth_type: str = typer.Option("API_KEY", "--auth-type", help="Authentication type"),
64
64
  ):
65
65
  """Create a new MCP server."""
66
66
  try:
@@ -13,7 +13,7 @@ app = typer.Typer(help="Secret management commands")
13
13
  @app.command()
14
14
  def get(
15
15
  path: str = typer.Argument(..., help="Secret path"),
16
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
16
+ format: str = typer.Option("table", "--format", help="Output format"),
17
17
  ):
18
18
  """Get a secret."""
19
19
  try:
@@ -42,8 +42,8 @@ def get(
42
42
  @app.command()
43
43
  def create(
44
44
  path: str = typer.Argument(..., help="Secret path"),
45
- value: str = typer.Option(..., "--value", "-v", help="Secret value"),
46
- description: str = typer.Option("", "--description", "-d", help="Secret description"),
45
+ value: str = typer.Option(..., "--value", help="Secret value"),
46
+ description: str = typer.Option("", "--description", help="Secret description"),
47
47
  ):
48
48
  """Create a new secret."""
49
49
  try:
@@ -66,8 +66,8 @@ def create(
66
66
  @app.command()
67
67
  def update(
68
68
  path: str = typer.Argument(..., help="Secret path"),
69
- value: str = typer.Option(..., "--value", "-v", help="Secret value"),
70
- description: str = typer.Option("", "--description", "-d", help="Secret description"),
69
+ value: str = typer.Option(..., "--value", help="Secret value"),
70
+ description: str = typer.Option("", "--description", help="Secret description"),
71
71
  ):
72
72
  """Update a secret."""
73
73
  try:
@@ -90,7 +90,7 @@ def update(
90
90
  @app.command()
91
91
  def delete(
92
92
  path: str = typer.Argument(..., help="Secret path"),
93
- force: bool = typer.Option(False, "--force", "-f", help="Force deletion"),
93
+ force: bool = typer.Option(False, "--force", help="Force deletion"),
94
94
  ):
95
95
  """Delete a secret."""
96
96
  try:
af_cli/commands/tools.py CHANGED
@@ -13,7 +13,7 @@ app = typer.Typer(help="Tool management commands")
13
13
 
14
14
  @app.command()
15
15
  def list(
16
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
16
+ format: str = typer.Option("table", "--format", help="Output format"),
17
17
  ):
18
18
  """List your tool connections (configured and connected tools)."""
19
19
  try:
@@ -56,7 +56,7 @@ def list(
56
56
  @app.command()
57
57
  def get(
58
58
  connection_id: str = typer.Argument(..., help="Connection ID (e.g., 'google', 'slack')"),
59
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
59
+ format: str = typer.Option("table", "--format", help="Output format"),
60
60
  ):
61
61
  """Get tool connection details."""
62
62
  try:
@@ -121,24 +121,98 @@ def get(
121
121
 
122
122
  @app.command()
123
123
  def invoke(
124
- tool_id: str = typer.Argument(..., help="Tool ID"),
125
- method: str = typer.Option(..., "--method", "-m", help="Tool method to invoke"),
126
- format: str = typer.Option("table", "--format", "-f", help="Output format"),
124
+ tool_identifier: str = typer.Argument(..., help="Tool name (e.g., 'slack') or UUID"),
125
+ method: str = typer.Option(..., "--method", help="Tool method to invoke"),
126
+ params: str = typer.Option(None, "--params", help="JSON string of method parameters (e.g., '{\"channel\": \"test\", \"text\": \"Hello\"}')"),
127
+ format: str = typer.Option("json", "--format", help="Output format (json, table, yaml)"),
127
128
  ):
128
- """Invoke a tool."""
129
+ """Invoke a tool by name or UUID.
130
+
131
+ Examples:
132
+ afctl tools invoke slack --method get_channels
133
+ afctl tools invoke slack --method post_message --params '{"channel": "test", "text": "Hello!"}'
134
+ afctl tools invoke c17b55af-18f8-49be-b9e9-44b1d1be429e --method get_channels
135
+ """
129
136
  try:
137
+ # Parse parameters if provided
138
+ parameters = {}
139
+ if params:
140
+ import json as json_lib
141
+ try:
142
+ parameters = json_lib.loads(params)
143
+ except json_lib.JSONDecodeError as e:
144
+ error(f"Invalid JSON in --params: {e}")
145
+ raise typer.Exit(1)
146
+
130
147
  with get_client() as client:
148
+ # Try to resolve tool name to UUID if not already a UUID
149
+ tool_id = tool_identifier
150
+ try:
151
+ from uuid import UUID
152
+ UUID(tool_identifier)
153
+ # It's already a UUID, use it directly
154
+ except ValueError:
155
+ # Not a UUID, try to look up by name
156
+ info(f"Looking up tool '{tool_identifier}'...")
157
+ try:
158
+ tools_response = client.get("/api/v1/tools")
159
+
160
+ # Handle different response formats
161
+ if isinstance(tools_response, dict) and "tools" in tools_response:
162
+ tools = tools_response["tools"]
163
+ elif isinstance(tools_response, dict) and "items" in tools_response:
164
+ tools = tools_response["items"]
165
+ elif hasattr(tools_response, '__iter__') and not isinstance(tools_response, (str, dict)):
166
+ tools = list(tools_response)
167
+ else:
168
+ tools = []
169
+
170
+ # Find tool by name (case-insensitive)
171
+ matching_tools = [t for t in tools if isinstance(t, dict) and t.get("name", "").lower() == tool_identifier.lower()]
172
+
173
+ if not matching_tools:
174
+ error(f"Tool '{tool_identifier}' not found. Available tools:")
175
+ for t in tools:
176
+ if isinstance(t, dict):
177
+ print(f" - {t.get('name')} (ID: {t.get('id')})")
178
+ raise typer.Exit(1)
179
+
180
+ if len(matching_tools) > 1:
181
+ error(f"Multiple tools found with name '{tool_identifier}':")
182
+ for t in matching_tools:
183
+ print(f" - {t.get('name')} (ID: {t.get('id')})")
184
+ error("Please use the UUID instead.")
185
+ raise typer.Exit(1)
186
+
187
+ tool_id = matching_tools[0].get("id")
188
+ info(f"Resolved '{tool_identifier}' to tool ID: {tool_id}")
189
+ except Exception as lookup_error:
190
+ error(f"Failed to look up tool: {lookup_error}")
191
+ raise typer.Exit(1)
192
+
131
193
  data = {
132
194
  "method": method,
133
- "parameters": {},
195
+ "parameters": parameters,
134
196
  "context": {},
135
197
  }
136
198
 
137
- info(f"Invoking tool {tool_id} method {method}...")
199
+ info(f"Invoking tool {tool_identifier} method {method}...")
138
200
  response = client.post(f"/api/v1/tools/{tool_id}/invoke", data)
139
201
 
140
202
  success("Tool invoked successfully")
141
- print_output(response, format_type=format)
203
+
204
+ # For tool invocations, show the result in a more readable format
205
+ if format == "json":
206
+ import json as json_lib
207
+ print(json_lib.dumps(response, indent=2))
208
+ elif format == "yaml":
209
+ import yaml
210
+ print(yaml.dump(response, default_flow_style=False))
211
+ else:
212
+ # For table format, show just the result field nicely
213
+ result = response.get("result", {})
214
+ print("\n📊 Result:")
215
+ print_output(result, format_type="json")
142
216
 
143
217
  except Exception as e:
144
218
  error(f"Failed to invoke tool: {e}")
@@ -413,7 +487,7 @@ def connect(
413
487
  @app.command()
414
488
  def disconnect(
415
489
  connection_id: str = typer.Argument(..., help="Connection ID to disconnect"),
416
- force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
490
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
417
491
  ):
418
492
  """Disconnect a tool (remove credentials but keep connection entry)."""
419
493
  try:
@@ -466,7 +540,7 @@ def disconnect(
466
540
  @app.command()
467
541
  def remove(
468
542
  connection_id: str = typer.Argument(..., help="Connection ID to remove"),
469
- force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
543
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
470
544
  ):
471
545
  """Remove a tool connection completely (delete entry and credentials)."""
472
546
  try: