agentic-fabriq-sdk 0.1.14__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 +18 -18
- af_cli/commands/applications.py +323 -0
- af_cli/commands/auth.py +0 -6
- af_cli/commands/config.py +1 -1
- af_cli/commands/mcp_servers.py +5 -5
- af_cli/commands/secrets.py +6 -6
- af_cli/commands/tools.py +85 -11
- af_cli/main.py +2 -7
- af_sdk/__init__.py +15 -0
- af_sdk/auth/__init__.py +16 -0
- af_sdk/auth/application.py +264 -0
- af_sdk/auth/applications.py +264 -0
- af_sdk/fabriq_client.py +54 -1
- {agentic_fabriq_sdk-0.1.14.dist-info → agentic_fabriq_sdk-0.1.15.dist-info}/METADATA +14 -3
- {agentic_fabriq_sdk-0.1.14.dist-info → agentic_fabriq_sdk-0.1.15.dist-info}/RECORD +17 -14
- {agentic_fabriq_sdk-0.1.14.dist-info → agentic_fabriq_sdk-0.1.15.dist-info}/WHEEL +0 -0
- {agentic_fabriq_sdk-0.1.14.dist-info → agentic_fabriq_sdk-0.1.15.dist-info}/entry_points.txt +0 -0
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",
|
|
18
|
-
page_size: int = typer.Option(20, "--page-size",
|
|
19
|
-
search: Optional[str] = typer.Option(None, "--search",
|
|
20
|
-
format: str = typer.Option("table", "--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",
|
|
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",
|
|
106
|
-
description: Optional[str] = typer.Option(None, "--description",
|
|
107
|
-
version: str = typer.Option("1.0.0", "--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",
|
|
110
|
-
auth_method: str = typer.Option("OAUTH2", "--auth-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",
|
|
139
|
-
description: Optional[str] = typer.Option(None, "--description",
|
|
140
|
-
version: Optional[str] = typer.Option(None, "--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",
|
|
143
|
-
auth_method: Optional[str] = typer.Option(None, "--auth-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",
|
|
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",
|
|
204
|
-
format: str = typer.Option("table", "--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",
|
|
15
|
+
format: str = typer.Option("table", "--format", help="Output format"),
|
|
16
16
|
):
|
|
17
17
|
"""Show current configuration."""
|
|
18
18
|
import os
|
af_cli/commands/mcp_servers.py
CHANGED
|
@@ -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",
|
|
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",
|
|
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",
|
|
62
|
-
base_url: str = typer.Option(..., "--base-url",
|
|
63
|
-
auth_type: str = typer.Option("API_KEY", "--auth-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:
|
af_cli/commands/secrets.py
CHANGED
|
@@ -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",
|
|
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",
|
|
46
|
-
description: str = typer.Option("", "--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",
|
|
70
|
-
description: str = typer.Option("", "--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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
125
|
-
method: str = typer.Option(..., "--method",
|
|
126
|
-
|
|
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 {
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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:
|