agentic-fabriq-sdk 0.1.22__tar.gz → 0.1.24__tar.gz
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.
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/PKG-INFO +22 -3
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/README.md +21 -2
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/applications.py +81 -7
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/tools.py +135 -15
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/auth/oauth.py +3 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/connectors/base.py +8 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/pyproject.toml +1 -1
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/auth.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/config.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/mcp_servers.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/commands/secrets.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/client.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/config.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/oauth.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/output.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/core/token_storage.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_cli/main.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/auth/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/auth/applications.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/auth/dpop.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/auth/token_cache.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/connectors/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/connectors/registry.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/dx/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/dx/decorators.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/dx/runtime.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/events.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/exceptions.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/fabriq_client.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/models/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/models/audit.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/models/types.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/py.typed +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/transport/__init__.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/transport/http.py +0 -0
- {agentic_fabriq_sdk-0.1.22 → agentic_fabriq_sdk-0.1.24}/af_sdk/vault.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-fabriq-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: Agentic Fabriq SDK: high-level client, CLI tool, DX helpers, and auth for AI agents
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: fabriq,agentic-fabriq,sdk,ai,agents,agentic,fabric,cli
|
|
@@ -88,15 +88,34 @@ async def main():
|
|
|
88
88
|
tools = await af.list_tools()
|
|
89
89
|
agents = await af.list_agents()
|
|
90
90
|
|
|
91
|
-
# Invoke tools
|
|
91
|
+
# Invoke Slack tools
|
|
92
92
|
result = await af.invoke_connection("my-slack", method="get_channels")
|
|
93
93
|
|
|
94
|
-
#
|
|
94
|
+
# Post a Slack message
|
|
95
95
|
await af.invoke_connection(
|
|
96
96
|
"my-slack",
|
|
97
97
|
method="post_message",
|
|
98
98
|
parameters={"channel": "test", "text": "Hello from SDK!"}
|
|
99
99
|
)
|
|
100
|
+
|
|
101
|
+
# Get Gmail emails
|
|
102
|
+
result = await af.invoke_connection(
|
|
103
|
+
"gmail_work",
|
|
104
|
+
method="get_emails",
|
|
105
|
+
parameters={"max_results": 10, "q": "is:unread"}
|
|
106
|
+
)
|
|
107
|
+
emails = result.get("emails", [])
|
|
108
|
+
|
|
109
|
+
# Send an email
|
|
110
|
+
await af.invoke_connection(
|
|
111
|
+
"gmail_work",
|
|
112
|
+
method="send_email",
|
|
113
|
+
parameters={
|
|
114
|
+
"to": "recipient@example.com",
|
|
115
|
+
"subject": "Hello from SDK",
|
|
116
|
+
"body": "This email was sent via Agentic Fabric SDK!"
|
|
117
|
+
}
|
|
118
|
+
)
|
|
100
119
|
```
|
|
101
120
|
|
|
102
121
|
DX orchestration:
|
|
@@ -52,15 +52,34 @@ async def main():
|
|
|
52
52
|
tools = await af.list_tools()
|
|
53
53
|
agents = await af.list_agents()
|
|
54
54
|
|
|
55
|
-
# Invoke tools
|
|
55
|
+
# Invoke Slack tools
|
|
56
56
|
result = await af.invoke_connection("my-slack", method="get_channels")
|
|
57
57
|
|
|
58
|
-
#
|
|
58
|
+
# Post a Slack message
|
|
59
59
|
await af.invoke_connection(
|
|
60
60
|
"my-slack",
|
|
61
61
|
method="post_message",
|
|
62
62
|
parameters={"channel": "test", "text": "Hello from SDK!"}
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
# Get Gmail emails
|
|
66
|
+
result = await af.invoke_connection(
|
|
67
|
+
"gmail_work",
|
|
68
|
+
method="get_emails",
|
|
69
|
+
parameters={"max_results": 10, "q": "is:unread"}
|
|
70
|
+
)
|
|
71
|
+
emails = result.get("emails", [])
|
|
72
|
+
|
|
73
|
+
# Send an email
|
|
74
|
+
await af.invoke_connection(
|
|
75
|
+
"gmail_work",
|
|
76
|
+
method="send_email",
|
|
77
|
+
parameters={
|
|
78
|
+
"to": "recipient@example.com",
|
|
79
|
+
"subject": "Hello from SDK",
|
|
80
|
+
"body": "This email was sent via Agentic Fabric SDK!"
|
|
81
|
+
}
|
|
82
|
+
)
|
|
64
83
|
```
|
|
65
84
|
|
|
66
85
|
DX orchestration:
|
|
@@ -18,17 +18,21 @@ app = typer.Typer(help="Manage registered applications")
|
|
|
18
18
|
console = Console()
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
@app.command("
|
|
22
|
-
def
|
|
21
|
+
@app.command("register")
|
|
22
|
+
def register_application(
|
|
23
23
|
app_id: str = typer.Option(..., "--app-id", help="Application identifier (no spaces)"),
|
|
24
24
|
connections: str = typer.Option(..., "--connections", help="Tool connections (format: 'tool1:conn-id,tool2:conn-id')"),
|
|
25
25
|
scopes: Optional[str] = typer.Option(None, "--scopes", help="Scopes (format: 'scope1,scope2,scope3')"),
|
|
26
26
|
):
|
|
27
27
|
"""
|
|
28
|
-
Register a new application.
|
|
28
|
+
Step 1: Register a new application (returns activation token).
|
|
29
|
+
|
|
30
|
+
This registers your application and returns a temporary activation token
|
|
31
|
+
that expires in 1 hour. Use this token with 'afctl applications connect'
|
|
32
|
+
to complete the setup and save credentials locally.
|
|
29
33
|
|
|
30
34
|
Example:
|
|
31
|
-
afctl applications
|
|
35
|
+
afctl applications register \\
|
|
32
36
|
--app-id my-slack-bot \\
|
|
33
37
|
--connections slack:my-slack-conn,github:my-github-conn \\
|
|
34
38
|
--scopes slack:read,slack:write,github:repo:read
|
|
@@ -58,10 +62,10 @@ def create_application(
|
|
|
58
62
|
for conn_id in tool_connections:
|
|
59
63
|
tool_connections[conn_id] = scope_list
|
|
60
64
|
|
|
61
|
-
# Make API request
|
|
65
|
+
# Make API request to register (returns activation token)
|
|
62
66
|
try:
|
|
63
67
|
response = httpx.post(
|
|
64
|
-
f"{config.gateway_url}/api/v1/applications",
|
|
68
|
+
f"{config.gateway_url}/api/v1/applications/register",
|
|
65
69
|
headers={"Authorization": f"Bearer {config.access_token}"},
|
|
66
70
|
json={
|
|
67
71
|
"app_id": app_id,
|
|
@@ -83,6 +87,76 @@ def create_application(
|
|
|
83
87
|
|
|
84
88
|
data = response.json()
|
|
85
89
|
|
|
90
|
+
# Display activation token
|
|
91
|
+
console.print("\n✅ Application registered successfully!", style="green bold")
|
|
92
|
+
console.print(f"\n📋 App ID: {data['app_id']}", style="cyan")
|
|
93
|
+
console.print(f"\n🔑 Activation Token:", style="yellow bold")
|
|
94
|
+
console.print(f" {data['activation_token']}", style="yellow")
|
|
95
|
+
console.print(f"\n⏰ Token expires: {data['expires_at'][:19]} UTC", style="white")
|
|
96
|
+
console.print(f" (Valid for 1 hour)", style="dim")
|
|
97
|
+
|
|
98
|
+
console.print("\n📋 Next Steps:", style="cyan bold")
|
|
99
|
+
console.print(f" 1. Navigate to your project directory", style="white")
|
|
100
|
+
console.print(f" 2. Make sure you're authenticated: afctl auth login", style="white")
|
|
101
|
+
console.print(f" 3. Run the connect command:", style="white")
|
|
102
|
+
console.print(f"\n afctl applications connect {app_id} --token <activation-token>", style="green")
|
|
103
|
+
console.print(f"\n⚠️ Save the activation token! It expires in 1 hour and can only be used once.", style="yellow bold")
|
|
104
|
+
|
|
105
|
+
except httpx.HTTPError as e:
|
|
106
|
+
console.print(f"❌ Network error: {e}", style="red")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.command("connect")
|
|
111
|
+
def connect_application(
|
|
112
|
+
app_id: str = typer.Argument(..., help="Application identifier"),
|
|
113
|
+
token: str = typer.Option(..., "--token", help="Activation token from registration"),
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
Step 2: Connect/activate an application (saves credentials locally).
|
|
117
|
+
|
|
118
|
+
Uses the activation token from 'afctl applications register' to activate
|
|
119
|
+
the application and save credentials to the current directory.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
afctl applications connect my-slack-bot --token <activation-token>
|
|
123
|
+
"""
|
|
124
|
+
config = get_config()
|
|
125
|
+
|
|
126
|
+
if not config.is_authenticated():
|
|
127
|
+
console.print("❌ Not authenticated. Run 'afctl auth login' first.", style="red")
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
|
|
130
|
+
# Make API request to activate (returns final credentials)
|
|
131
|
+
try:
|
|
132
|
+
response = httpx.post(
|
|
133
|
+
f"{config.gateway_url}/api/v1/applications/activate",
|
|
134
|
+
headers={"Authorization": f"Bearer {config.access_token}"},
|
|
135
|
+
json={"activation_token": token},
|
|
136
|
+
timeout=30.0
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if response.status_code == 404:
|
|
140
|
+
console.print("❌ Invalid or expired activation token", style="red")
|
|
141
|
+
console.print(" The token may have expired (valid for 1 hour) or was already used.", style="yellow")
|
|
142
|
+
console.print(" Register again with 'afctl applications register'", style="white")
|
|
143
|
+
raise typer.Exit(1)
|
|
144
|
+
elif response.status_code == 403:
|
|
145
|
+
console.print("❌ This activation token does not belong to you", style="red")
|
|
146
|
+
raise typer.Exit(1)
|
|
147
|
+
elif response.status_code != 201:
|
|
148
|
+
error_detail = response.text
|
|
149
|
+
try:
|
|
150
|
+
error_json = response.json()
|
|
151
|
+
error_detail = error_json.get("detail", response.text)
|
|
152
|
+
except:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
console.print(f"❌ Failed to activate application: {error_detail}", style="red")
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
|
|
158
|
+
data = response.json()
|
|
159
|
+
|
|
86
160
|
# Save credentials locally
|
|
87
161
|
from af_sdk import save_application_config
|
|
88
162
|
|
|
@@ -99,7 +173,7 @@ def create_application(
|
|
|
99
173
|
app_file = save_application_config(data["app_id"], app_config)
|
|
100
174
|
|
|
101
175
|
# Display success
|
|
102
|
-
console.print("\n✅ Application
|
|
176
|
+
console.print("\n✅ Application activated successfully!", style="green bold")
|
|
103
177
|
console.print(f"\n📋 App ID: {data['app_id']}", style="cyan")
|
|
104
178
|
console.print(f"🔑 Secret Key: {data['secret_key']}", style="yellow")
|
|
105
179
|
console.print(f"\n💾 Credentials saved to: {app_file}", style="green")
|
|
@@ -14,14 +14,86 @@ app = typer.Typer(help="Tool management commands")
|
|
|
14
14
|
@app.command()
|
|
15
15
|
def list(
|
|
16
16
|
format: str = typer.Option("table", "--format", help="Output format"),
|
|
17
|
+
page: int = typer.Option(1, "--page", help="Page number (starts from 1)", min=1),
|
|
18
|
+
page_size: int = typer.Option(20, "--page-size", help="Number of items per page (1-100)", min=1, max=100),
|
|
19
|
+
search: str = typer.Option(None, "--search", help="Search query (searches tool IDs from registry like 'gmail', 'slack', and user connection names)"),
|
|
20
|
+
tool_filter: str = typer.Option(None, "--tool", help="Filter by tool type from registry (e.g., 'gmail' shows Gmail connections, 'google' shows all Google tools)"),
|
|
17
21
|
):
|
|
18
|
-
"""List your tool connections (configured and connected tools).
|
|
22
|
+
"""List your tool connections (configured and connected tools).
|
|
23
|
+
|
|
24
|
+
The command supports pagination and search to help you navigate large numbers
|
|
25
|
+
of connections efficiently.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
# List all connections (default: page 1, 20 per page)
|
|
29
|
+
afctl tools list
|
|
30
|
+
|
|
31
|
+
# List first page with 10 items per page
|
|
32
|
+
afctl tools list --page 1 --page-size 10
|
|
33
|
+
|
|
34
|
+
# Search for tool types (exact match against registry - shows all connections of that type)
|
|
35
|
+
afctl tools list --search gmail # Shows all Gmail connections
|
|
36
|
+
afctl tools list --search google # Shows all Google Workspace tools
|
|
37
|
+
afctl tools list --search slack # Shows all Slack connections
|
|
38
|
+
afctl tools list --search notion # Shows all Notion connections
|
|
39
|
+
afctl tools list --search github # Shows all GitHub connections
|
|
40
|
+
|
|
41
|
+
# Filter by tool type (shows all connections of that tool)
|
|
42
|
+
afctl tools list --tool gmail # Shows all Gmail connections
|
|
43
|
+
afctl tools list --tool google # Shows all Google Workspace tools
|
|
44
|
+
afctl tools list --tool slack # Shows all Slack connections
|
|
45
|
+
|
|
46
|
+
# Search and paginate together
|
|
47
|
+
afctl tools list --search google --page 1 --page-size 5
|
|
48
|
+
|
|
49
|
+
# Find connections by name (fallback search)
|
|
50
|
+
afctl tools list --search work # Connections with "work" in name
|
|
51
|
+
afctl tools list --search personal # Connections with "personal" in name
|
|
52
|
+
|
|
53
|
+
# Combined search and filtering (AND logic - must match both)
|
|
54
|
+
afctl tools list --tool google --search gmail # Google tools containing "gmail"
|
|
55
|
+
afctl tools list --search drive --tool google # Google tools with "drive"
|
|
56
|
+
afctl tools list --search team --tool slack # Slack connections with "team"
|
|
57
|
+
|
|
58
|
+
Pagination:
|
|
59
|
+
- Pages start from 1
|
|
60
|
+
- Page size range: 1-100 items
|
|
61
|
+
- Shows helpful tips when more results are available
|
|
62
|
+
|
|
63
|
+
Search:
|
|
64
|
+
- **Primary**: Searches available tool IDs from registry (e.g., "gmail" matches "gmail", "google" matches "google_drive")
|
|
65
|
+
- **Secondary**: Searches user connection IDs and display names (e.g., "gmail" matches "gmail_work")
|
|
66
|
+
- Case-insensitive matching
|
|
67
|
+
- Shows all connections matching the search criteria
|
|
68
|
+
|
|
69
|
+
Tool Filtering:
|
|
70
|
+
- Filters by tool type from connector registry
|
|
71
|
+
- Shows all user connections for that tool type
|
|
72
|
+
- "google" shows all Google Workspace tools (Drive, Docs, Sheets, Gmail, etc.)
|
|
73
|
+
- "gmail" shows only Gmail connections
|
|
74
|
+
- "slack" shows only Slack connections
|
|
75
|
+
- Combines with search and pagination
|
|
76
|
+
"""
|
|
19
77
|
try:
|
|
20
78
|
with get_client() as client:
|
|
21
|
-
|
|
22
|
-
|
|
79
|
+
# Build query parameters
|
|
80
|
+
params = {}
|
|
81
|
+
if page != 1:
|
|
82
|
+
params["page"] = page
|
|
83
|
+
if page_size != 20:
|
|
84
|
+
params["page_size"] = page_size
|
|
85
|
+
if search:
|
|
86
|
+
params["search"] = search
|
|
87
|
+
if tool_filter:
|
|
88
|
+
params["tool_filter"] = tool_filter
|
|
89
|
+
|
|
90
|
+
connections = client.get("/api/v1/user-connections", params=params)
|
|
91
|
+
|
|
23
92
|
if not connections:
|
|
24
|
-
|
|
93
|
+
if search or tool_filter:
|
|
94
|
+
warning(f"No tool connections found matching your criteria. Try adjusting your search or filter.")
|
|
95
|
+
else:
|
|
96
|
+
warning("No tool connections found. Add connections in the dashboard UI.")
|
|
25
97
|
return
|
|
26
98
|
|
|
27
99
|
# Format for better display
|
|
@@ -42,11 +114,27 @@ def list(
|
|
|
42
114
|
"Added": conn.get("created_at", "N/A")[:10] if conn.get("created_at") else "N/A",
|
|
43
115
|
})
|
|
44
116
|
|
|
117
|
+
# Show pagination info if not on first page or if filters applied
|
|
118
|
+
total_info = ""
|
|
119
|
+
if page != 1 or page_size != 20 or search or tool_filter:
|
|
120
|
+
total_info = f" (Page {page}, {len(connections)} shown"
|
|
121
|
+
if search:
|
|
122
|
+
total_info += f", Search: '{search}'"
|
|
123
|
+
if tool_filter:
|
|
124
|
+
total_info += f", Tool: '{tool_filter}'"
|
|
125
|
+
total_info += ")"
|
|
126
|
+
|
|
45
127
|
print_output(
|
|
46
128
|
display_data,
|
|
47
129
|
format_type=format,
|
|
48
|
-
title="Your Tool Connections"
|
|
130
|
+
title=f"Your Tool Connections{total_info}"
|
|
49
131
|
)
|
|
132
|
+
|
|
133
|
+
# Show helpful tips for pagination
|
|
134
|
+
if len(connections) == page_size:
|
|
135
|
+
info(f"💡 Tip: Use --page {page + 1} to see more results, or --page-size to adjust items per page")
|
|
136
|
+
if search or tool_filter:
|
|
137
|
+
info(f"💡 Tip: Use --search or --tool filters to find specific connections")
|
|
50
138
|
|
|
51
139
|
except Exception as e:
|
|
52
140
|
error(f"Failed to list tool connections: {e}")
|
|
@@ -197,7 +285,7 @@ def add(
|
|
|
197
285
|
tool: str = typer.Argument(..., help="Tool name (google_drive, google_slides, slack, notion, github, etc.)"),
|
|
198
286
|
connection_id: str = typer.Option(..., "--connection-id", help="Unique connection ID"),
|
|
199
287
|
display_name: str = typer.Option(None, "--display-name", help="Human-readable name"),
|
|
200
|
-
method: str = typer.Option(..., "--method", help="Connection method: 'api_credentials' or 'oauth'"),
|
|
288
|
+
method: str = typer.Option(..., "--method", help="Connection method: 'api_credentials', 'oauth3' (Google platform OAuth), or 'oauth'"),
|
|
201
289
|
|
|
202
290
|
# API credentials method fields (can be either a token OR client_id/secret)
|
|
203
291
|
token: str = typer.Option(None, "--token", help="API token (for simple token-based auth like Notion, Slack bot)"),
|
|
@@ -212,7 +300,16 @@ def add(
|
|
|
212
300
|
# Notion (api_credentials method - single token)
|
|
213
301
|
afctl tools add notion --connection-id notion-work --method api_credentials --token "secret_abc123"
|
|
214
302
|
|
|
215
|
-
# Google (
|
|
303
|
+
# Google (oauth3 method - uses platform OAuth, no credentials needed)
|
|
304
|
+
afctl tools add google_drive --connection-id google-work --method oauth3
|
|
305
|
+
|
|
306
|
+
# Slack (oauth3 method - uses platform OAuth, no credentials needed)
|
|
307
|
+
afctl tools add slack --connection-id slack-work --method oauth3
|
|
308
|
+
|
|
309
|
+
# Notion (oauth3 method - uses platform OAuth, no credentials needed)
|
|
310
|
+
afctl tools add notion --connection-id notion-work --method oauth3
|
|
311
|
+
|
|
312
|
+
# Google (api_credentials method - your own OAuth app)
|
|
216
313
|
afctl tools add google_drive --connection-id google-work --method api_credentials \\
|
|
217
314
|
--client-id "123.apps.googleusercontent.com" \\
|
|
218
315
|
--client-secret "GOCSPX-abc123"
|
|
@@ -247,10 +344,17 @@ def add(
|
|
|
247
344
|
raise typer.Exit(1)
|
|
248
345
|
|
|
249
346
|
# Validate method
|
|
250
|
-
if method not in ["api_credentials", "oauth"]:
|
|
251
|
-
error("Method must be 'api_credentials' or 'oauth'")
|
|
347
|
+
if method not in ["api_credentials", "oauth3", "oauth"]:
|
|
348
|
+
error("Method must be 'api_credentials', 'oauth3', or 'oauth'")
|
|
252
349
|
raise typer.Exit(1)
|
|
253
350
|
|
|
351
|
+
# Validate oauth3 method is only for Google, Slack, and Notion tools
|
|
352
|
+
if method == "oauth3":
|
|
353
|
+
if not (tool.startswith("google_") or tool == "gmail" or tool == "slack" or tool == "notion"):
|
|
354
|
+
error("oauth3 method is only available for Google Workspace tools, Slack, and Notion")
|
|
355
|
+
info("For other tools, use 'api_credentials' method")
|
|
356
|
+
raise typer.Exit(1)
|
|
357
|
+
|
|
254
358
|
# Validate api_credentials method requirements
|
|
255
359
|
if method == "api_credentials":
|
|
256
360
|
# Must have either token OR (client_id + client_secret)
|
|
@@ -282,8 +386,14 @@ def add(
|
|
|
282
386
|
client.post("/api/v1/user-connections", data=connection_data)
|
|
283
387
|
success(f"✅ Connection entry created: {connection_id}")
|
|
284
388
|
|
|
285
|
-
# Step 2: Store credentials based on
|
|
286
|
-
if method == "
|
|
389
|
+
# Step 2: Store credentials based on method
|
|
390
|
+
if method == "oauth3":
|
|
391
|
+
# OAuth3 uses platform credentials - no need to store user credentials
|
|
392
|
+
success("✅ Connection configured with platform OAuth")
|
|
393
|
+
info("")
|
|
394
|
+
info(f"Next: Run 'afctl tools connect {connection_id}' to authenticate")
|
|
395
|
+
|
|
396
|
+
elif method == "api_credentials":
|
|
287
397
|
# Determine the API base tool name (Google tools all use "google")
|
|
288
398
|
api_tool_name = "google" if (tool.startswith("google_") or tool == "gmail") else tool
|
|
289
399
|
|
|
@@ -384,17 +494,27 @@ def connect(
|
|
|
384
494
|
if not confirm:
|
|
385
495
|
return
|
|
386
496
|
|
|
387
|
-
# Determine the API base tool name
|
|
388
|
-
|
|
497
|
+
# Determine the API base tool name
|
|
498
|
+
# - Google tools all use "google" for API credentials, "google_oauth" for oauth3
|
|
499
|
+
# - Notion uses "notion" for API credentials, "notion_oauth" for oauth3
|
|
500
|
+
if tool.startswith("google_") or tool == "gmail":
|
|
501
|
+
api_tool_name = "google_oauth" if method == "oauth3" else "google"
|
|
502
|
+
elif tool == "notion":
|
|
503
|
+
api_tool_name = "notion_oauth" if method == "oauth3" else "notion"
|
|
504
|
+
else:
|
|
505
|
+
api_tool_name = tool
|
|
389
506
|
|
|
390
507
|
# Initiate OAuth flow
|
|
391
508
|
info(f"Initiating OAuth for {tool}...")
|
|
392
509
|
|
|
393
510
|
# For Google tools, pass the specific tool_type parameter
|
|
394
|
-
tool_type_param = f"&tool_type={tool}" if tool
|
|
511
|
+
tool_type_param = f"&tool_type={tool}" if (tool.startswith("google_") or tool == "gmail") else ""
|
|
512
|
+
|
|
513
|
+
# For oauth3 method, pass the method parameter to use platform credentials
|
|
514
|
+
method_param = f"&method={method}" if method == "oauth3" else ""
|
|
395
515
|
|
|
396
516
|
result = client.post(
|
|
397
|
-
f"/api/v1/tools/{api_tool_name}/connect/initiate?connection_id={connection_id}{tool_type_param}",
|
|
517
|
+
f"/api/v1/tools/{api_tool_name}/connect/initiate?connection_id={connection_id}{tool_type_param}{method_param}",
|
|
398
518
|
data={}
|
|
399
519
|
)
|
|
400
520
|
|
|
@@ -47,6 +47,9 @@ def oauth_required(*, scopes: List[str], refresh_if_expired: bool = True):
|
|
|
47
47
|
refresh_if_expired=refresh_if_expired,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# Log token info (first 20 chars for debugging)
|
|
51
|
+
ctx.logger.info(f"OAuth decorator injecting token for {tool_id}: {token[:20]}...")
|
|
52
|
+
|
|
50
53
|
# Inject Authorization header
|
|
51
54
|
headers = kwargs.setdefault("headers", {})
|
|
52
55
|
headers.setdefault("Authorization", f"Bearer {token}")
|
|
@@ -204,10 +204,18 @@ class HTTPConnectorMixin:
|
|
|
204
204
|
url = f"{self.base_url.rstrip('/')}/{path.lstrip('/')}"
|
|
205
205
|
request_headers = {**self.default_headers, **(headers or {})}
|
|
206
206
|
|
|
207
|
+
# Log request details for debugging
|
|
208
|
+
auth_header = request_headers.get("Authorization", "")
|
|
209
|
+
auth_preview = f"{auth_header[:30]}..." if auth_header else "None"
|
|
210
|
+
self.logger.info(f"[HTTP Request] {method} {url}")
|
|
211
|
+
self.logger.info(f"[HTTP Request] Authorization: {auth_preview}")
|
|
212
|
+
self.logger.info(f"[HTTP Request] Headers: {list(request_headers.keys())}")
|
|
213
|
+
|
|
207
214
|
try:
|
|
208
215
|
response = await self.session.request(
|
|
209
216
|
method, url, headers=request_headers, **kwargs
|
|
210
217
|
)
|
|
218
|
+
self.logger.info(f"[HTTP Response] Status: {response.status_code}")
|
|
211
219
|
response.raise_for_status()
|
|
212
220
|
return response
|
|
213
221
|
except httpx.HTTPError as e:
|
|
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
5
5
|
|
|
6
6
|
[tool.poetry]
|
|
7
7
|
name = "agentic-fabriq-sdk"
|
|
8
|
-
version = "0.1.
|
|
8
|
+
version = "0.1.24"
|
|
9
9
|
description = "Agentic Fabriq SDK: high-level client, CLI tool, DX helpers, and auth for AI agents"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = "Apache-2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|