glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.7__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.
- glaip_sdk/__init__.py +1 -1
- glaip_sdk/_version.py +42 -19
- glaip_sdk/branding.py +3 -2
- glaip_sdk/cli/commands/__init__.py +1 -1
- glaip_sdk/cli/commands/agents.py +452 -285
- glaip_sdk/cli/commands/configure.py +14 -13
- glaip_sdk/cli/commands/mcps.py +30 -20
- glaip_sdk/cli/commands/models.py +5 -3
- glaip_sdk/cli/commands/tools.py +111 -106
- glaip_sdk/cli/display.py +48 -27
- glaip_sdk/cli/io.py +1 -1
- glaip_sdk/cli/main.py +26 -5
- glaip_sdk/cli/resolution.py +5 -4
- glaip_sdk/cli/utils.py +437 -188
- glaip_sdk/cli/validators.py +7 -2
- glaip_sdk/client/agents.py +276 -153
- glaip_sdk/client/base.py +69 -27
- glaip_sdk/client/tools.py +44 -26
- glaip_sdk/client/validators.py +154 -94
- glaip_sdk/config/constants.py +0 -2
- glaip_sdk/models.py +5 -4
- glaip_sdk/utils/__init__.py +7 -7
- glaip_sdk/utils/client_utils.py +191 -101
- glaip_sdk/utils/display.py +4 -2
- glaip_sdk/utils/general.py +8 -6
- glaip_sdk/utils/import_export.py +58 -25
- glaip_sdk/utils/rendering/formatting.py +12 -6
- glaip_sdk/utils/rendering/models.py +1 -1
- glaip_sdk/utils/rendering/renderer/base.py +523 -332
- glaip_sdk/utils/rendering/renderer/console.py +6 -5
- glaip_sdk/utils/rendering/renderer/debug.py +94 -52
- glaip_sdk/utils/rendering/renderer/stream.py +93 -48
- glaip_sdk/utils/rendering/steps.py +103 -39
- glaip_sdk/utils/rich_utils.py +1 -1
- glaip_sdk/utils/run_renderer.py +1 -1
- glaip_sdk/utils/serialization.py +9 -3
- glaip_sdk/utils/validation.py +2 -2
- glaip_sdk-0.0.7.dist-info/METADATA +183 -0
- glaip_sdk-0.0.7.dist-info/RECORD +55 -0
- glaip_sdk-0.0.5b1.dist-info/METADATA +0 -645
- glaip_sdk-0.0.5b1.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/entry_points.txt +0 -0
|
@@ -7,6 +7,7 @@ Authors:
|
|
|
7
7
|
import getpass
|
|
8
8
|
import os
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
13
|
import yaml
|
|
@@ -24,7 +25,7 @@ CONFIG_DIR = Path.home() / ".aip"
|
|
|
24
25
|
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
def load_config():
|
|
28
|
+
def load_config() -> dict[str, Any]:
|
|
28
29
|
"""Load configuration from file."""
|
|
29
30
|
if not CONFIG_FILE.exists():
|
|
30
31
|
return {}
|
|
@@ -36,7 +37,7 @@ def load_config():
|
|
|
36
37
|
return {}
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def save_config(config):
|
|
40
|
+
def save_config(config: dict[str, Any]) -> None:
|
|
40
41
|
"""Save configuration to file."""
|
|
41
42
|
CONFIG_DIR.mkdir(exist_ok=True)
|
|
42
43
|
|
|
@@ -46,18 +47,18 @@ def save_config(config):
|
|
|
46
47
|
# Set secure file permissions
|
|
47
48
|
try:
|
|
48
49
|
os.chmod(CONFIG_FILE, 0o600)
|
|
49
|
-
except Exception:
|
|
50
|
-
pass
|
|
50
|
+
except Exception: # pragma: no cover - platform dependent best effort
|
|
51
|
+
pass
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
@click.group()
|
|
54
|
-
def config_group():
|
|
55
|
+
def config_group() -> None:
|
|
55
56
|
"""Configuration management operations."""
|
|
56
57
|
pass
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
@config_group.command("list")
|
|
60
|
-
def list_config():
|
|
61
|
+
def list_config() -> None:
|
|
61
62
|
"""List current configuration."""
|
|
62
63
|
|
|
63
64
|
config = load_config()
|
|
@@ -87,7 +88,7 @@ def list_config():
|
|
|
87
88
|
@config_group.command("set")
|
|
88
89
|
@click.argument("key")
|
|
89
90
|
@click.argument("value")
|
|
90
|
-
def set_config(key, value):
|
|
91
|
+
def set_config(key: str, value: str) -> None:
|
|
91
92
|
"""Set a configuration value."""
|
|
92
93
|
|
|
93
94
|
valid_keys = ["api_url", "api_key"]
|
|
@@ -111,7 +112,7 @@ def set_config(key, value):
|
|
|
111
112
|
|
|
112
113
|
@config_group.command("get")
|
|
113
114
|
@click.argument("key")
|
|
114
|
-
def get_config(key):
|
|
115
|
+
def get_config(key: str) -> None:
|
|
115
116
|
"""Get a configuration value."""
|
|
116
117
|
|
|
117
118
|
config = load_config()
|
|
@@ -132,7 +133,7 @@ def get_config(key):
|
|
|
132
133
|
|
|
133
134
|
@config_group.command("unset")
|
|
134
135
|
@click.argument("key")
|
|
135
|
-
def unset_config(key):
|
|
136
|
+
def unset_config(key: str) -> None:
|
|
136
137
|
"""Remove a configuration value."""
|
|
137
138
|
|
|
138
139
|
config = load_config()
|
|
@@ -149,7 +150,7 @@ def unset_config(key):
|
|
|
149
150
|
|
|
150
151
|
@config_group.command("reset")
|
|
151
152
|
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
|
|
152
|
-
def reset_config(force):
|
|
153
|
+
def reset_config(force: bool) -> None:
|
|
153
154
|
"""Reset all configuration to defaults."""
|
|
154
155
|
|
|
155
156
|
if not force:
|
|
@@ -168,7 +169,7 @@ def reset_config(force):
|
|
|
168
169
|
console.print("[yellow]No configuration found to reset.[/yellow]")
|
|
169
170
|
|
|
170
171
|
|
|
171
|
-
def _configure_interactive():
|
|
172
|
+
def _configure_interactive() -> None:
|
|
172
173
|
"""Shared configuration logic for both configure commands."""
|
|
173
174
|
# Display AIP welcome banner
|
|
174
175
|
branding = AIPBranding.create_from_sdk(
|
|
@@ -239,14 +240,14 @@ def _configure_interactive():
|
|
|
239
240
|
|
|
240
241
|
|
|
241
242
|
@config_group.command()
|
|
242
|
-
def configure():
|
|
243
|
+
def configure() -> None:
|
|
243
244
|
"""Configure AIP CLI credentials and settings interactively."""
|
|
244
245
|
_configure_interactive()
|
|
245
246
|
|
|
246
247
|
|
|
247
248
|
# Alias command for backward compatibility
|
|
248
249
|
@click.command()
|
|
249
|
-
def configure_command():
|
|
250
|
+
def configure_command() -> None:
|
|
250
251
|
"""Configure AIP CLI credentials and settings interactively.
|
|
251
252
|
|
|
252
253
|
This is an alias for 'aip config configure' for backward compatibility.
|
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -6,6 +6,7 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
import click
|
|
11
12
|
from rich.console import Console
|
|
@@ -29,7 +30,9 @@ from glaip_sdk.cli.io import (
|
|
|
29
30
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
30
31
|
from glaip_sdk.cli.utils import (
|
|
31
32
|
coerce_to_row,
|
|
33
|
+
detect_export_format,
|
|
32
34
|
get_client,
|
|
35
|
+
get_ctx_value,
|
|
33
36
|
output_flags,
|
|
34
37
|
output_list,
|
|
35
38
|
output_result,
|
|
@@ -41,12 +44,14 @@ console = Console()
|
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
@click.group(name="mcps", no_args_is_help=True)
|
|
44
|
-
def mcps_group():
|
|
47
|
+
def mcps_group() -> None:
|
|
45
48
|
"""MCP management operations."""
|
|
46
49
|
pass
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
def _resolve_mcp(
|
|
52
|
+
def _resolve_mcp(
|
|
53
|
+
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
54
|
+
) -> Any | None:
|
|
50
55
|
"""Resolve MCP reference (ID or name) with ambiguity handling."""
|
|
51
56
|
return resolve_resource_reference(
|
|
52
57
|
ctx,
|
|
@@ -63,7 +68,7 @@ def _resolve_mcp(ctx, client, ref, select=None):
|
|
|
63
68
|
@mcps_group.command(name="list")
|
|
64
69
|
@output_flags()
|
|
65
70
|
@click.pass_context
|
|
66
|
-
def list_mcps(ctx):
|
|
71
|
+
def list_mcps(ctx: Any) -> None:
|
|
67
72
|
"""List all MCPs."""
|
|
68
73
|
try:
|
|
69
74
|
client = get_client(ctx)
|
|
@@ -77,7 +82,7 @@ def list_mcps(ctx):
|
|
|
77
82
|
]
|
|
78
83
|
|
|
79
84
|
# Transform function for safe dictionary access
|
|
80
|
-
def transform_mcp(mcp):
|
|
85
|
+
def transform_mcp(mcp: Any) -> dict[str, Any]:
|
|
81
86
|
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
82
87
|
# Ensure id is always a string
|
|
83
88
|
row["id"] = str(row["id"])
|
|
@@ -103,7 +108,9 @@ def list_mcps(ctx):
|
|
|
103
108
|
@click.option("--config", help="JSON configuration string")
|
|
104
109
|
@output_flags()
|
|
105
110
|
@click.pass_context
|
|
106
|
-
def create(
|
|
111
|
+
def create(
|
|
112
|
+
ctx: Any, name: str, transport: str, description: str | None, config: str | None
|
|
113
|
+
) -> None:
|
|
107
114
|
"""Create a new MCP."""
|
|
108
115
|
try:
|
|
109
116
|
client = get_client(ctx)
|
|
@@ -140,7 +147,7 @@ def create(ctx, name, transport, description, config):
|
|
|
140
147
|
|
|
141
148
|
except Exception as e:
|
|
142
149
|
handle_json_output(ctx, error=e)
|
|
143
|
-
if ctx
|
|
150
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
144
151
|
display_api_error(e, "MCP creation")
|
|
145
152
|
raise click.ClickException(str(e))
|
|
146
153
|
|
|
@@ -154,7 +161,7 @@ def create(ctx, name, transport, description, config):
|
|
|
154
161
|
)
|
|
155
162
|
@output_flags()
|
|
156
163
|
@click.pass_context
|
|
157
|
-
def get(ctx, mcp_ref, export):
|
|
164
|
+
def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
158
165
|
"""Get MCP details.
|
|
159
166
|
|
|
160
167
|
Examples:
|
|
@@ -172,10 +179,7 @@ def get(ctx, mcp_ref, export):
|
|
|
172
179
|
if export:
|
|
173
180
|
export_path = Path(export)
|
|
174
181
|
# Auto-detect format from file extension
|
|
175
|
-
|
|
176
|
-
detected_format = "yaml"
|
|
177
|
-
else:
|
|
178
|
-
detected_format = "json"
|
|
182
|
+
detected_format = detect_export_format(export_path)
|
|
179
183
|
|
|
180
184
|
# Always export comprehensive data - re-fetch MCP with full details if needed
|
|
181
185
|
try:
|
|
@@ -244,7 +248,7 @@ def get(ctx, mcp_ref, export):
|
|
|
244
248
|
@click.argument("mcp_ref")
|
|
245
249
|
@output_flags()
|
|
246
250
|
@click.pass_context
|
|
247
|
-
def list_tools(ctx, mcp_ref):
|
|
251
|
+
def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
248
252
|
"""List tools from MCP."""
|
|
249
253
|
try:
|
|
250
254
|
client = get_client(ctx)
|
|
@@ -263,7 +267,7 @@ def list_tools(ctx, mcp_ref):
|
|
|
263
267
|
]
|
|
264
268
|
|
|
265
269
|
# Transform function for safe dictionary access
|
|
266
|
-
def transform_tool(tool):
|
|
270
|
+
def transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
|
|
267
271
|
return {
|
|
268
272
|
"name": tool.get("name", "N/A"),
|
|
269
273
|
"description": tool.get("description", "N/A")[:47] + "..."
|
|
@@ -289,7 +293,7 @@ def list_tools(ctx, mcp_ref):
|
|
|
289
293
|
)
|
|
290
294
|
@output_flags()
|
|
291
295
|
@click.pass_context
|
|
292
|
-
def connect(ctx, config_file):
|
|
296
|
+
def connect(ctx: Any, config_file: str) -> None:
|
|
293
297
|
"""Connect to MCP using config file."""
|
|
294
298
|
try:
|
|
295
299
|
client = get_client(ctx)
|
|
@@ -298,7 +302,7 @@ def connect(ctx, config_file):
|
|
|
298
302
|
with open(config_file) as f:
|
|
299
303
|
config = json.load(f)
|
|
300
304
|
|
|
301
|
-
view = (ctx
|
|
305
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
302
306
|
if view != "json":
|
|
303
307
|
console.print(
|
|
304
308
|
Text(
|
|
@@ -309,7 +313,7 @@ def connect(ctx, config_file):
|
|
|
309
313
|
# Test connection using config
|
|
310
314
|
result = client.mcps.test_mcp_connection_from_config(config)
|
|
311
315
|
|
|
312
|
-
view = (ctx
|
|
316
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
313
317
|
if view == "json":
|
|
314
318
|
handle_json_output(ctx, result)
|
|
315
319
|
else:
|
|
@@ -332,7 +336,13 @@ def connect(ctx, config_file):
|
|
|
332
336
|
@click.option("--config", help="JSON configuration string")
|
|
333
337
|
@output_flags()
|
|
334
338
|
@click.pass_context
|
|
335
|
-
def update(
|
|
339
|
+
def update(
|
|
340
|
+
ctx: Any,
|
|
341
|
+
mcp_ref: str,
|
|
342
|
+
name: str | None,
|
|
343
|
+
description: str | None,
|
|
344
|
+
config: str | None,
|
|
345
|
+
) -> None:
|
|
336
346
|
"""Update an existing MCP."""
|
|
337
347
|
try:
|
|
338
348
|
client = get_client(ctx)
|
|
@@ -363,7 +373,7 @@ def update(ctx, mcp_ref, name, description, config):
|
|
|
363
373
|
|
|
364
374
|
except Exception as e:
|
|
365
375
|
handle_json_output(ctx, error=e)
|
|
366
|
-
if (ctx
|
|
376
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
367
377
|
display_api_error(e, "MCP update")
|
|
368
378
|
raise click.ClickException(str(e))
|
|
369
379
|
|
|
@@ -373,7 +383,7 @@ def update(ctx, mcp_ref, name, description, config):
|
|
|
373
383
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
374
384
|
@output_flags()
|
|
375
385
|
@click.pass_context
|
|
376
|
-
def delete(ctx, mcp_ref, yes):
|
|
386
|
+
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
377
387
|
"""Delete an MCP."""
|
|
378
388
|
try:
|
|
379
389
|
client = get_client(ctx)
|
|
@@ -398,6 +408,6 @@ def delete(ctx, mcp_ref, yes):
|
|
|
398
408
|
|
|
399
409
|
except Exception as e:
|
|
400
410
|
handle_json_output(ctx, error=e)
|
|
401
|
-
if (ctx
|
|
411
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
402
412
|
display_api_error(e, "MCP deletion")
|
|
403
413
|
raise click.ClickException(str(e))
|
glaip_sdk/cli/commands/models.py
CHANGED
|
@@ -4,6 +4,8 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
7
9
|
import click
|
|
8
10
|
from rich.console import Console
|
|
9
11
|
|
|
@@ -13,7 +15,7 @@ console = Console()
|
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
@click.group(name="models", no_args_is_help=True)
|
|
16
|
-
def models_group():
|
|
18
|
+
def models_group() -> None:
|
|
17
19
|
"""Language model operations."""
|
|
18
20
|
pass
|
|
19
21
|
|
|
@@ -21,7 +23,7 @@ def models_group():
|
|
|
21
23
|
@models_group.command(name="list")
|
|
22
24
|
@output_flags()
|
|
23
25
|
@click.pass_context
|
|
24
|
-
def list_models(ctx):
|
|
26
|
+
def list_models(ctx: Any) -> None:
|
|
25
27
|
"""List available language models."""
|
|
26
28
|
try:
|
|
27
29
|
client = get_client(ctx)
|
|
@@ -36,7 +38,7 @@ def list_models(ctx):
|
|
|
36
38
|
]
|
|
37
39
|
|
|
38
40
|
# Transform function for safe dictionary access
|
|
39
|
-
def transform_model(model):
|
|
41
|
+
def transform_model(model: dict[str, Any]) -> dict[str, Any]:
|
|
40
42
|
return {
|
|
41
43
|
"id": str(model.get("id", "N/A")),
|
|
42
44
|
"provider": model.get("provider", "N/A"),
|
glaip_sdk/cli/commands/tools.py
CHANGED
|
@@ -7,6 +7,7 @@ Authors:
|
|
|
7
7
|
import json
|
|
8
8
|
import re
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
13
|
from rich.console import Console
|
|
@@ -33,7 +34,9 @@ from glaip_sdk.cli.io import (
|
|
|
33
34
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
34
35
|
from glaip_sdk.cli.utils import (
|
|
35
36
|
coerce_to_row,
|
|
37
|
+
detect_export_format,
|
|
36
38
|
get_client,
|
|
39
|
+
get_ctx_value,
|
|
37
40
|
output_flags,
|
|
38
41
|
output_list,
|
|
39
42
|
output_result,
|
|
@@ -45,12 +48,14 @@ console = Console()
|
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
@click.group(name="tools", no_args_is_help=True)
|
|
48
|
-
def tools_group():
|
|
51
|
+
def tools_group() -> None:
|
|
49
52
|
"""Tool management operations."""
|
|
50
53
|
pass
|
|
51
54
|
|
|
52
55
|
|
|
53
|
-
def _resolve_tool(
|
|
56
|
+
def _resolve_tool(
|
|
57
|
+
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
58
|
+
) -> Any | None:
|
|
54
59
|
"""Resolve tool reference (ID or name) with ambiguity handling."""
|
|
55
60
|
return resolve_resource_reference(
|
|
56
61
|
ctx,
|
|
@@ -90,7 +95,7 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
|
|
|
90
95
|
return provided or internal
|
|
91
96
|
|
|
92
97
|
|
|
93
|
-
def _check_duplicate_name(client, tool_name: str) -> None:
|
|
98
|
+
def _check_duplicate_name(client: Any, tool_name: str) -> None:
|
|
94
99
|
"""Raise if a tool with the same name already exists."""
|
|
95
100
|
try:
|
|
96
101
|
existing = client.find_tools(name=tool_name)
|
|
@@ -111,6 +116,69 @@ def _parse_tags(tags: str | None) -> list[str]:
|
|
|
111
116
|
return [t.strip() for t in (tags.split(",") if tags else []) if t.strip()]
|
|
112
117
|
|
|
113
118
|
|
|
119
|
+
def _handle_import_file(
|
|
120
|
+
import_file: str | None,
|
|
121
|
+
name: str | None,
|
|
122
|
+
description: str | None,
|
|
123
|
+
tags: tuple[str, ...] | None,
|
|
124
|
+
) -> dict[str, Any]:
|
|
125
|
+
"""Handle import file logic and merge with CLI arguments."""
|
|
126
|
+
if import_file:
|
|
127
|
+
import_data = load_resource_from_file(Path(import_file), "tool")
|
|
128
|
+
|
|
129
|
+
# Merge CLI args with imported data
|
|
130
|
+
cli_args = {
|
|
131
|
+
"name": name,
|
|
132
|
+
"description": description,
|
|
133
|
+
"tags": tags,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return merge_import_with_cli_args(import_data, cli_args)
|
|
137
|
+
else:
|
|
138
|
+
# No import file - use CLI args directly
|
|
139
|
+
return {
|
|
140
|
+
"name": name,
|
|
141
|
+
"description": description,
|
|
142
|
+
"tags": tags,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _create_tool_from_file(
|
|
147
|
+
client: Any,
|
|
148
|
+
file_path: str,
|
|
149
|
+
name: str | None,
|
|
150
|
+
description: str | None,
|
|
151
|
+
tags: str | None,
|
|
152
|
+
) -> Any:
|
|
153
|
+
"""Create tool from file upload."""
|
|
154
|
+
with open(file_path, encoding="utf-8") as f:
|
|
155
|
+
code_content = f.read()
|
|
156
|
+
|
|
157
|
+
internal_name = _extract_internal_name(code_content)
|
|
158
|
+
tool_name = _validate_name_match(name, internal_name)
|
|
159
|
+
_check_duplicate_name(client, tool_name)
|
|
160
|
+
|
|
161
|
+
# Upload the plugin code as-is (no rewrite)
|
|
162
|
+
return client.create_tool_from_code(
|
|
163
|
+
name=tool_name,
|
|
164
|
+
code=code_content,
|
|
165
|
+
framework="langchain", # Always langchain
|
|
166
|
+
description=description,
|
|
167
|
+
tags=_parse_tags(tags) if tags else None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _validate_creation_parameters(
|
|
172
|
+
file: str | None,
|
|
173
|
+
import_file: str | None,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Validate required parameters for tool creation."""
|
|
176
|
+
if not file and not import_file:
|
|
177
|
+
raise click.ClickException(
|
|
178
|
+
"A tool file must be provided. Use --file to specify the tool file to upload."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
114
182
|
@tools_group.command(name="list")
|
|
115
183
|
@output_flags()
|
|
116
184
|
@click.option(
|
|
@@ -121,7 +189,7 @@ def _parse_tags(tags: str | None) -> list[str]:
|
|
|
121
189
|
required=False,
|
|
122
190
|
)
|
|
123
191
|
@click.pass_context
|
|
124
|
-
def list_tools(ctx, tool_type):
|
|
192
|
+
def list_tools(ctx: Any, tool_type: str | None) -> None:
|
|
125
193
|
"""List all tools."""
|
|
126
194
|
try:
|
|
127
195
|
client = get_client(ctx)
|
|
@@ -135,7 +203,7 @@ def list_tools(ctx, tool_type):
|
|
|
135
203
|
]
|
|
136
204
|
|
|
137
205
|
# Transform function for safe dictionary access
|
|
138
|
-
def transform_tool(tool):
|
|
206
|
+
def transform_tool(tool: Any) -> dict[str, Any]:
|
|
139
207
|
row = coerce_to_row(tool, ["id", "name", "framework"])
|
|
140
208
|
# Ensure id is always a string
|
|
141
209
|
row["id"] = str(row["id"])
|
|
@@ -152,15 +220,15 @@ def list_tools(ctx, tool_type):
|
|
|
152
220
|
@click.option(
|
|
153
221
|
"--file",
|
|
154
222
|
type=click.Path(exists=True),
|
|
155
|
-
help="Tool file to upload
|
|
223
|
+
help="Tool file to upload",
|
|
156
224
|
)
|
|
157
225
|
@click.option(
|
|
158
226
|
"--name",
|
|
159
|
-
help="Tool name (
|
|
227
|
+
help="Tool name (extracted from script if file provided)",
|
|
160
228
|
)
|
|
161
229
|
@click.option(
|
|
162
230
|
"--description",
|
|
163
|
-
help="Tool description (
|
|
231
|
+
help="Tool description (extracted from script if file provided)",
|
|
164
232
|
)
|
|
165
233
|
@click.option(
|
|
166
234
|
"--tags",
|
|
@@ -174,113 +242,47 @@ def list_tools(ctx, tool_type):
|
|
|
174
242
|
)
|
|
175
243
|
@output_flags()
|
|
176
244
|
@click.pass_context
|
|
177
|
-
def create(
|
|
245
|
+
def create(
|
|
246
|
+
ctx: Any,
|
|
247
|
+
file_arg: str | None,
|
|
248
|
+
file: str | None,
|
|
249
|
+
name: str | None,
|
|
250
|
+
description: str | None,
|
|
251
|
+
tags: tuple[str, ...] | None,
|
|
252
|
+
import_file: str | None,
|
|
253
|
+
) -> None:
|
|
178
254
|
"""Create a new tool.
|
|
179
255
|
|
|
180
256
|
Examples:
|
|
181
|
-
aip tools create --name "My Tool" --description "A helpful tool"
|
|
182
257
|
aip tools create tool.py # Create from file
|
|
183
258
|
aip tools create --import tool.json # Create from exported configuration
|
|
184
259
|
"""
|
|
185
260
|
try:
|
|
186
261
|
client = get_client(ctx)
|
|
187
262
|
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# Handle import from file
|
|
192
|
-
if import_file:
|
|
193
|
-
import_data = load_resource_from_file(Path(import_file), "tool")
|
|
194
|
-
|
|
195
|
-
# Merge CLI args with imported data
|
|
196
|
-
cli_args = {
|
|
197
|
-
"name": name,
|
|
198
|
-
"description": description,
|
|
199
|
-
"tags": tags,
|
|
200
|
-
}
|
|
263
|
+
# Allow positional file argument for better DX (matches examples)
|
|
264
|
+
if not file and file_arg:
|
|
265
|
+
file = file_arg
|
|
201
266
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# No import file - use CLI args directly
|
|
205
|
-
merged_data = {
|
|
206
|
-
"name": name,
|
|
207
|
-
"description": description,
|
|
208
|
-
"tags": tags,
|
|
209
|
-
}
|
|
267
|
+
# Handle import file and merge with CLI arguments
|
|
268
|
+
merged_data = _handle_import_file(import_file, name, description, tags)
|
|
210
269
|
|
|
211
270
|
# Extract merged values
|
|
212
271
|
name = merged_data.get("name")
|
|
213
272
|
description = merged_data.get("description")
|
|
214
273
|
tags = merged_data.get("tags")
|
|
215
274
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
file = file_arg
|
|
219
|
-
|
|
220
|
-
# Validate required parameters based on creation method
|
|
221
|
-
if not file and not import_file:
|
|
222
|
-
# Metadata-only tool creation
|
|
223
|
-
if not name:
|
|
224
|
-
raise click.ClickException(
|
|
225
|
-
"--name is required when creating metadata-only tools"
|
|
226
|
-
)
|
|
275
|
+
# Validate required parameters
|
|
276
|
+
_validate_creation_parameters(file, import_file)
|
|
227
277
|
|
|
228
|
-
# Create tool
|
|
229
|
-
|
|
230
|
-
# File-based tool creation — validate internal plugin name, no rewriting
|
|
231
|
-
with open(file, encoding="utf-8") as f:
|
|
232
|
-
code_content = f.read()
|
|
233
|
-
|
|
234
|
-
internal_name = _extract_internal_name(code_content)
|
|
235
|
-
tool_name = _validate_name_match(name, internal_name)
|
|
236
|
-
_check_duplicate_name(client, tool_name)
|
|
237
|
-
|
|
238
|
-
# Upload the plugin code as-is (no rewrite)
|
|
239
|
-
tool = client.create_tool_from_code(
|
|
240
|
-
name=tool_name,
|
|
241
|
-
code=code_content,
|
|
242
|
-
framework="langchain", # Always langchain
|
|
243
|
-
description=description,
|
|
244
|
-
tags=_parse_tags(tags) if tags else None,
|
|
245
|
-
)
|
|
246
|
-
else:
|
|
247
|
-
# Metadata-only tool creation or import from file
|
|
248
|
-
tool_kwargs = {}
|
|
249
|
-
if name:
|
|
250
|
-
tool_kwargs["name"] = name
|
|
251
|
-
tool_kwargs["tool_type"] = "custom" # Always custom
|
|
252
|
-
tool_kwargs["framework"] = "langchain" # Always langchain
|
|
253
|
-
if description:
|
|
254
|
-
tool_kwargs["description"] = description
|
|
255
|
-
if tags:
|
|
256
|
-
tool_kwargs["tags"] = _parse_tags(tags)
|
|
257
|
-
|
|
258
|
-
# If importing from file, include all other detected attributes
|
|
259
|
-
if import_file:
|
|
260
|
-
# Add all other attributes from import data (excluding already handled ones)
|
|
261
|
-
excluded_fields = {
|
|
262
|
-
"name",
|
|
263
|
-
"description",
|
|
264
|
-
"tags",
|
|
265
|
-
# System-only fields that shouldn't be passed to create_tool
|
|
266
|
-
"id",
|
|
267
|
-
"created_at",
|
|
268
|
-
"updated_at",
|
|
269
|
-
"tool_type",
|
|
270
|
-
"framework",
|
|
271
|
-
"version",
|
|
272
|
-
}
|
|
273
|
-
for key, value in merged_data.items():
|
|
274
|
-
if key not in excluded_fields and value is not None:
|
|
275
|
-
tool_kwargs[key] = value
|
|
276
|
-
|
|
277
|
-
tool = client.create_tool(**tool_kwargs)
|
|
278
|
+
# Create tool from file (either direct file or import file)
|
|
279
|
+
tool = _create_tool_from_file(client, file, name, description, tags)
|
|
278
280
|
|
|
279
281
|
# Handle JSON output
|
|
280
282
|
handle_json_output(ctx, tool.model_dump())
|
|
281
283
|
|
|
282
284
|
# Handle Rich output
|
|
283
|
-
creation_method = "file upload (custom)"
|
|
285
|
+
creation_method = "file upload (custom)"
|
|
284
286
|
rich_panel = display_creation_success(
|
|
285
287
|
"Tool",
|
|
286
288
|
tool.name,
|
|
@@ -294,7 +296,7 @@ def create(ctx, file_arg, file, name, description, tags, import_file):
|
|
|
294
296
|
|
|
295
297
|
except Exception as e:
|
|
296
298
|
handle_json_output(ctx, error=e)
|
|
297
|
-
if ctx
|
|
299
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
298
300
|
display_api_error(e, "tool creation")
|
|
299
301
|
raise click.ClickException(str(e))
|
|
300
302
|
|
|
@@ -309,7 +311,7 @@ def create(ctx, file_arg, file, name, description, tags, import_file):
|
|
|
309
311
|
)
|
|
310
312
|
@output_flags()
|
|
311
313
|
@click.pass_context
|
|
312
|
-
def get(ctx, tool_ref, select, export):
|
|
314
|
+
def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None:
|
|
313
315
|
"""Get tool details.
|
|
314
316
|
|
|
315
317
|
Examples:
|
|
@@ -327,10 +329,7 @@ def get(ctx, tool_ref, select, export):
|
|
|
327
329
|
if export:
|
|
328
330
|
export_path = Path(export)
|
|
329
331
|
# Auto-detect format from file extension
|
|
330
|
-
|
|
331
|
-
detected_format = "yaml"
|
|
332
|
-
else:
|
|
333
|
-
detected_format = "json"
|
|
332
|
+
detected_format = detect_export_format(export_path)
|
|
334
333
|
|
|
335
334
|
# Always export comprehensive data - re-fetch tool with full details if needed
|
|
336
335
|
try:
|
|
@@ -406,7 +405,13 @@ def get(ctx, tool_ref, select, export):
|
|
|
406
405
|
@click.option("--tags", help="Comma-separated tags")
|
|
407
406
|
@output_flags()
|
|
408
407
|
@click.pass_context
|
|
409
|
-
def update(
|
|
408
|
+
def update(
|
|
409
|
+
ctx: Any,
|
|
410
|
+
tool_id: str,
|
|
411
|
+
file: str | None,
|
|
412
|
+
description: str | None,
|
|
413
|
+
tags: tuple[str, ...] | None,
|
|
414
|
+
) -> None:
|
|
410
415
|
"""Update a tool (code or metadata)."""
|
|
411
416
|
try:
|
|
412
417
|
client = get_client(ctx)
|
|
@@ -453,7 +458,7 @@ def update(ctx, tool_id, file, description, tags):
|
|
|
453
458
|
|
|
454
459
|
except Exception as e:
|
|
455
460
|
handle_json_output(ctx, error=e)
|
|
456
|
-
if ctx
|
|
461
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
457
462
|
display_api_error(e, "tool update")
|
|
458
463
|
raise click.ClickException(str(e))
|
|
459
464
|
|
|
@@ -463,7 +468,7 @@ def update(ctx, tool_id, file, description, tags):
|
|
|
463
468
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
464
469
|
@output_flags()
|
|
465
470
|
@click.pass_context
|
|
466
|
-
def delete(ctx, tool_id, yes):
|
|
471
|
+
def delete(ctx: Any, tool_id: str, yes: bool) -> None:
|
|
467
472
|
"""Delete a tool."""
|
|
468
473
|
try:
|
|
469
474
|
client = get_client(ctx)
|
|
@@ -491,7 +496,7 @@ def delete(ctx, tool_id, yes):
|
|
|
491
496
|
|
|
492
497
|
except Exception as e:
|
|
493
498
|
handle_json_output(ctx, error=e)
|
|
494
|
-
if ctx
|
|
499
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
495
500
|
display_api_error(e, "tool deletion")
|
|
496
501
|
raise click.ClickException(str(e))
|
|
497
502
|
|
|
@@ -500,13 +505,13 @@ def delete(ctx, tool_id, yes):
|
|
|
500
505
|
@click.argument("tool_id")
|
|
501
506
|
@output_flags()
|
|
502
507
|
@click.pass_context
|
|
503
|
-
def script(ctx, tool_id):
|
|
508
|
+
def script(ctx: Any, tool_id: str) -> None:
|
|
504
509
|
"""Get tool script content."""
|
|
505
510
|
try:
|
|
506
511
|
client = get_client(ctx)
|
|
507
512
|
script_content = client.get_tool_script(tool_id)
|
|
508
513
|
|
|
509
|
-
if ctx
|
|
514
|
+
if get_ctx_value(ctx, "view") == "json":
|
|
510
515
|
click.echo(json.dumps({"script": script_content}, indent=2))
|
|
511
516
|
else:
|
|
512
517
|
console.print(f"[green]📜 Tool Script for '{tool_id}':[/green]")
|
|
@@ -514,6 +519,6 @@ def script(ctx, tool_id):
|
|
|
514
519
|
|
|
515
520
|
except Exception as e:
|
|
516
521
|
handle_json_output(ctx, error=e)
|
|
517
|
-
if ctx
|
|
522
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
518
523
|
console.print(Text(f"[red]Error getting tool script: {e}[/red]"))
|
|
519
524
|
raise click.ClickException(str(e))
|