glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.6a0__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/branding.py +3 -2
- glaip_sdk/cli/commands/__init__.py +1 -1
- glaip_sdk/cli/commands/agents.py +444 -268
- glaip_sdk/cli/commands/configure.py +12 -11
- glaip_sdk/cli/commands/mcps.py +28 -16
- glaip_sdk/cli/commands/models.py +5 -3
- glaip_sdk/cli/commands/tools.py +109 -102
- glaip_sdk/cli/display.py +38 -16
- 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 +376 -157
- glaip_sdk/cli/validators.py +7 -2
- glaip_sdk/client/agents.py +184 -89
- glaip_sdk/client/base.py +24 -13
- glaip_sdk/client/validators.py +154 -94
- glaip_sdk/config/constants.py +0 -2
- glaip_sdk/models.py +4 -4
- glaip_sdk/utils/__init__.py +7 -7
- glaip_sdk/utils/client_utils.py +144 -78
- glaip_sdk/utils/display.py +4 -2
- glaip_sdk/utils/general.py +8 -6
- glaip_sdk/utils/import_export.py +55 -24
- glaip_sdk/utils/rendering/formatting.py +12 -6
- glaip_sdk/utils/rendering/models.py +1 -1
- glaip_sdk/utils/rendering/renderer/base.py +412 -248
- 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 +3 -1
- glaip_sdk/utils/validation.py +2 -2
- glaip_sdk-0.0.6a0.dist-info/METADATA +183 -0
- glaip_sdk-0.0.6a0.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.6a0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.6a0.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
|
|
|
@@ -51,13 +52,13 @@ def save_config(config):
|
|
|
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
|
|
@@ -30,6 +31,7 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
|
30
31
|
from glaip_sdk.cli.utils import (
|
|
31
32
|
coerce_to_row,
|
|
32
33
|
get_client,
|
|
34
|
+
get_ctx_value,
|
|
33
35
|
output_flags,
|
|
34
36
|
output_list,
|
|
35
37
|
output_result,
|
|
@@ -41,12 +43,14 @@ console = Console()
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
@click.group(name="mcps", no_args_is_help=True)
|
|
44
|
-
def mcps_group():
|
|
46
|
+
def mcps_group() -> None:
|
|
45
47
|
"""MCP management operations."""
|
|
46
48
|
pass
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
def _resolve_mcp(
|
|
51
|
+
def _resolve_mcp(
|
|
52
|
+
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
53
|
+
) -> Any | None:
|
|
50
54
|
"""Resolve MCP reference (ID or name) with ambiguity handling."""
|
|
51
55
|
return resolve_resource_reference(
|
|
52
56
|
ctx,
|
|
@@ -63,7 +67,7 @@ def _resolve_mcp(ctx, client, ref, select=None):
|
|
|
63
67
|
@mcps_group.command(name="list")
|
|
64
68
|
@output_flags()
|
|
65
69
|
@click.pass_context
|
|
66
|
-
def list_mcps(ctx):
|
|
70
|
+
def list_mcps(ctx: Any) -> None:
|
|
67
71
|
"""List all MCPs."""
|
|
68
72
|
try:
|
|
69
73
|
client = get_client(ctx)
|
|
@@ -77,7 +81,7 @@ def list_mcps(ctx):
|
|
|
77
81
|
]
|
|
78
82
|
|
|
79
83
|
# Transform function for safe dictionary access
|
|
80
|
-
def transform_mcp(mcp):
|
|
84
|
+
def transform_mcp(mcp: Any) -> dict[str, Any]:
|
|
81
85
|
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
82
86
|
# Ensure id is always a string
|
|
83
87
|
row["id"] = str(row["id"])
|
|
@@ -103,7 +107,9 @@ def list_mcps(ctx):
|
|
|
103
107
|
@click.option("--config", help="JSON configuration string")
|
|
104
108
|
@output_flags()
|
|
105
109
|
@click.pass_context
|
|
106
|
-
def create(
|
|
110
|
+
def create(
|
|
111
|
+
ctx: Any, name: str, transport: str, description: str | None, config: str | None
|
|
112
|
+
) -> None:
|
|
107
113
|
"""Create a new MCP."""
|
|
108
114
|
try:
|
|
109
115
|
client = get_client(ctx)
|
|
@@ -140,7 +146,7 @@ def create(ctx, name, transport, description, config):
|
|
|
140
146
|
|
|
141
147
|
except Exception as e:
|
|
142
148
|
handle_json_output(ctx, error=e)
|
|
143
|
-
if ctx
|
|
149
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
144
150
|
display_api_error(e, "MCP creation")
|
|
145
151
|
raise click.ClickException(str(e))
|
|
146
152
|
|
|
@@ -154,7 +160,7 @@ def create(ctx, name, transport, description, config):
|
|
|
154
160
|
)
|
|
155
161
|
@output_flags()
|
|
156
162
|
@click.pass_context
|
|
157
|
-
def get(ctx, mcp_ref, export):
|
|
163
|
+
def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
158
164
|
"""Get MCP details.
|
|
159
165
|
|
|
160
166
|
Examples:
|
|
@@ -244,7 +250,7 @@ def get(ctx, mcp_ref, export):
|
|
|
244
250
|
@click.argument("mcp_ref")
|
|
245
251
|
@output_flags()
|
|
246
252
|
@click.pass_context
|
|
247
|
-
def list_tools(ctx, mcp_ref):
|
|
253
|
+
def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
248
254
|
"""List tools from MCP."""
|
|
249
255
|
try:
|
|
250
256
|
client = get_client(ctx)
|
|
@@ -263,7 +269,7 @@ def list_tools(ctx, mcp_ref):
|
|
|
263
269
|
]
|
|
264
270
|
|
|
265
271
|
# Transform function for safe dictionary access
|
|
266
|
-
def transform_tool(tool):
|
|
272
|
+
def transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
|
|
267
273
|
return {
|
|
268
274
|
"name": tool.get("name", "N/A"),
|
|
269
275
|
"description": tool.get("description", "N/A")[:47] + "..."
|
|
@@ -289,7 +295,7 @@ def list_tools(ctx, mcp_ref):
|
|
|
289
295
|
)
|
|
290
296
|
@output_flags()
|
|
291
297
|
@click.pass_context
|
|
292
|
-
def connect(ctx, config_file):
|
|
298
|
+
def connect(ctx: Any, config_file: str) -> None:
|
|
293
299
|
"""Connect to MCP using config file."""
|
|
294
300
|
try:
|
|
295
301
|
client = get_client(ctx)
|
|
@@ -298,7 +304,7 @@ def connect(ctx, config_file):
|
|
|
298
304
|
with open(config_file) as f:
|
|
299
305
|
config = json.load(f)
|
|
300
306
|
|
|
301
|
-
view = (ctx
|
|
307
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
302
308
|
if view != "json":
|
|
303
309
|
console.print(
|
|
304
310
|
Text(
|
|
@@ -309,7 +315,7 @@ def connect(ctx, config_file):
|
|
|
309
315
|
# Test connection using config
|
|
310
316
|
result = client.mcps.test_mcp_connection_from_config(config)
|
|
311
317
|
|
|
312
|
-
view = (ctx
|
|
318
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
313
319
|
if view == "json":
|
|
314
320
|
handle_json_output(ctx, result)
|
|
315
321
|
else:
|
|
@@ -332,7 +338,13 @@ def connect(ctx, config_file):
|
|
|
332
338
|
@click.option("--config", help="JSON configuration string")
|
|
333
339
|
@output_flags()
|
|
334
340
|
@click.pass_context
|
|
335
|
-
def update(
|
|
341
|
+
def update(
|
|
342
|
+
ctx: Any,
|
|
343
|
+
mcp_ref: str,
|
|
344
|
+
name: str | None,
|
|
345
|
+
description: str | None,
|
|
346
|
+
config: str | None,
|
|
347
|
+
) -> None:
|
|
336
348
|
"""Update an existing MCP."""
|
|
337
349
|
try:
|
|
338
350
|
client = get_client(ctx)
|
|
@@ -363,7 +375,7 @@ def update(ctx, mcp_ref, name, description, config):
|
|
|
363
375
|
|
|
364
376
|
except Exception as e:
|
|
365
377
|
handle_json_output(ctx, error=e)
|
|
366
|
-
if (ctx
|
|
378
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
367
379
|
display_api_error(e, "MCP update")
|
|
368
380
|
raise click.ClickException(str(e))
|
|
369
381
|
|
|
@@ -373,7 +385,7 @@ def update(ctx, mcp_ref, name, description, config):
|
|
|
373
385
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
374
386
|
@output_flags()
|
|
375
387
|
@click.pass_context
|
|
376
|
-
def delete(ctx, mcp_ref, yes):
|
|
388
|
+
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
377
389
|
"""Delete an MCP."""
|
|
378
390
|
try:
|
|
379
391
|
client = get_client(ctx)
|
|
@@ -398,6 +410,6 @@ def delete(ctx, mcp_ref, yes):
|
|
|
398
410
|
|
|
399
411
|
except Exception as e:
|
|
400
412
|
handle_json_output(ctx, error=e)
|
|
401
|
-
if (ctx
|
|
413
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
402
414
|
display_api_error(e, "MCP deletion")
|
|
403
415
|
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
|
|
@@ -34,6 +35,7 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
|
34
35
|
from glaip_sdk.cli.utils import (
|
|
35
36
|
coerce_to_row,
|
|
36
37
|
get_client,
|
|
38
|
+
get_ctx_value,
|
|
37
39
|
output_flags,
|
|
38
40
|
output_list,
|
|
39
41
|
output_result,
|
|
@@ -45,12 +47,14 @@ console = Console()
|
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
@click.group(name="tools", no_args_is_help=True)
|
|
48
|
-
def tools_group():
|
|
50
|
+
def tools_group() -> None:
|
|
49
51
|
"""Tool management operations."""
|
|
50
52
|
pass
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
def _resolve_tool(
|
|
55
|
+
def _resolve_tool(
|
|
56
|
+
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
57
|
+
) -> Any | None:
|
|
54
58
|
"""Resolve tool reference (ID or name) with ambiguity handling."""
|
|
55
59
|
return resolve_resource_reference(
|
|
56
60
|
ctx,
|
|
@@ -90,7 +94,7 @@ def _validate_name_match(provided: str | None, internal: str) -> str:
|
|
|
90
94
|
return provided or internal
|
|
91
95
|
|
|
92
96
|
|
|
93
|
-
def _check_duplicate_name(client, tool_name: str) -> None:
|
|
97
|
+
def _check_duplicate_name(client: Any, tool_name: str) -> None:
|
|
94
98
|
"""Raise if a tool with the same name already exists."""
|
|
95
99
|
try:
|
|
96
100
|
existing = client.find_tools(name=tool_name)
|
|
@@ -111,6 +115,69 @@ def _parse_tags(tags: str | None) -> list[str]:
|
|
|
111
115
|
return [t.strip() for t in (tags.split(",") if tags else []) if t.strip()]
|
|
112
116
|
|
|
113
117
|
|
|
118
|
+
def _handle_import_file(
|
|
119
|
+
import_file: str | None,
|
|
120
|
+
name: str | None,
|
|
121
|
+
description: str | None,
|
|
122
|
+
tags: tuple[str, ...] | None,
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
"""Handle import file logic and merge with CLI arguments."""
|
|
125
|
+
if import_file:
|
|
126
|
+
import_data = load_resource_from_file(Path(import_file), "tool")
|
|
127
|
+
|
|
128
|
+
# Merge CLI args with imported data
|
|
129
|
+
cli_args = {
|
|
130
|
+
"name": name,
|
|
131
|
+
"description": description,
|
|
132
|
+
"tags": tags,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return merge_import_with_cli_args(import_data, cli_args)
|
|
136
|
+
else:
|
|
137
|
+
# No import file - use CLI args directly
|
|
138
|
+
return {
|
|
139
|
+
"name": name,
|
|
140
|
+
"description": description,
|
|
141
|
+
"tags": tags,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _create_tool_from_file(
|
|
146
|
+
client: Any,
|
|
147
|
+
file_path: str,
|
|
148
|
+
name: str | None,
|
|
149
|
+
description: str | None,
|
|
150
|
+
tags: str | None,
|
|
151
|
+
) -> Any:
|
|
152
|
+
"""Create tool from file upload."""
|
|
153
|
+
with open(file_path, encoding="utf-8") as f:
|
|
154
|
+
code_content = f.read()
|
|
155
|
+
|
|
156
|
+
internal_name = _extract_internal_name(code_content)
|
|
157
|
+
tool_name = _validate_name_match(name, internal_name)
|
|
158
|
+
_check_duplicate_name(client, tool_name)
|
|
159
|
+
|
|
160
|
+
# Upload the plugin code as-is (no rewrite)
|
|
161
|
+
return client.create_tool_from_code(
|
|
162
|
+
name=tool_name,
|
|
163
|
+
code=code_content,
|
|
164
|
+
framework="langchain", # Always langchain
|
|
165
|
+
description=description,
|
|
166
|
+
tags=_parse_tags(tags) if tags else None,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _validate_creation_parameters(
|
|
171
|
+
file: str | None,
|
|
172
|
+
import_file: str | None,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Validate required parameters for tool creation."""
|
|
175
|
+
if not file and not import_file:
|
|
176
|
+
raise click.ClickException(
|
|
177
|
+
"A tool file must be provided. Use --file to specify the tool file to upload."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
114
181
|
@tools_group.command(name="list")
|
|
115
182
|
@output_flags()
|
|
116
183
|
@click.option(
|
|
@@ -121,7 +188,7 @@ def _parse_tags(tags: str | None) -> list[str]:
|
|
|
121
188
|
required=False,
|
|
122
189
|
)
|
|
123
190
|
@click.pass_context
|
|
124
|
-
def list_tools(ctx, tool_type):
|
|
191
|
+
def list_tools(ctx: Any, tool_type: str | None) -> None:
|
|
125
192
|
"""List all tools."""
|
|
126
193
|
try:
|
|
127
194
|
client = get_client(ctx)
|
|
@@ -135,7 +202,7 @@ def list_tools(ctx, tool_type):
|
|
|
135
202
|
]
|
|
136
203
|
|
|
137
204
|
# Transform function for safe dictionary access
|
|
138
|
-
def transform_tool(tool):
|
|
205
|
+
def transform_tool(tool: Any) -> dict[str, Any]:
|
|
139
206
|
row = coerce_to_row(tool, ["id", "name", "framework"])
|
|
140
207
|
# Ensure id is always a string
|
|
141
208
|
row["id"] = str(row["id"])
|
|
@@ -152,15 +219,15 @@ def list_tools(ctx, tool_type):
|
|
|
152
219
|
@click.option(
|
|
153
220
|
"--file",
|
|
154
221
|
type=click.Path(exists=True),
|
|
155
|
-
help="Tool file to upload
|
|
222
|
+
help="Tool file to upload",
|
|
156
223
|
)
|
|
157
224
|
@click.option(
|
|
158
225
|
"--name",
|
|
159
|
-
help="Tool name (
|
|
226
|
+
help="Tool name (extracted from script if file provided)",
|
|
160
227
|
)
|
|
161
228
|
@click.option(
|
|
162
229
|
"--description",
|
|
163
|
-
help="Tool description (
|
|
230
|
+
help="Tool description (extracted from script if file provided)",
|
|
164
231
|
)
|
|
165
232
|
@click.option(
|
|
166
233
|
"--tags",
|
|
@@ -174,113 +241,47 @@ def list_tools(ctx, tool_type):
|
|
|
174
241
|
)
|
|
175
242
|
@output_flags()
|
|
176
243
|
@click.pass_context
|
|
177
|
-
def create(
|
|
244
|
+
def create(
|
|
245
|
+
ctx: Any,
|
|
246
|
+
file_arg: str | None,
|
|
247
|
+
file: str | None,
|
|
248
|
+
name: str | None,
|
|
249
|
+
description: str | None,
|
|
250
|
+
tags: tuple[str, ...] | None,
|
|
251
|
+
import_file: str | None,
|
|
252
|
+
) -> None:
|
|
178
253
|
"""Create a new tool.
|
|
179
254
|
|
|
180
255
|
Examples:
|
|
181
|
-
aip tools create --name "My Tool" --description "A helpful tool"
|
|
182
256
|
aip tools create tool.py # Create from file
|
|
183
257
|
aip tools create --import tool.json # Create from exported configuration
|
|
184
258
|
"""
|
|
185
259
|
try:
|
|
186
260
|
client = get_client(ctx)
|
|
187
261
|
|
|
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
|
-
}
|
|
262
|
+
# Allow positional file argument for better DX (matches examples)
|
|
263
|
+
if not file and file_arg:
|
|
264
|
+
file = file_arg
|
|
201
265
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# No import file - use CLI args directly
|
|
205
|
-
merged_data = {
|
|
206
|
-
"name": name,
|
|
207
|
-
"description": description,
|
|
208
|
-
"tags": tags,
|
|
209
|
-
}
|
|
266
|
+
# Handle import file and merge with CLI arguments
|
|
267
|
+
merged_data = _handle_import_file(import_file, name, description, tags)
|
|
210
268
|
|
|
211
269
|
# Extract merged values
|
|
212
270
|
name = merged_data.get("name")
|
|
213
271
|
description = merged_data.get("description")
|
|
214
272
|
tags = merged_data.get("tags")
|
|
215
273
|
|
|
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
|
-
)
|
|
274
|
+
# Validate required parameters
|
|
275
|
+
_validate_creation_parameters(file, import_file)
|
|
227
276
|
|
|
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)
|
|
277
|
+
# Create tool from file (either direct file or import file)
|
|
278
|
+
tool = _create_tool_from_file(client, file, name, description, tags)
|
|
278
279
|
|
|
279
280
|
# Handle JSON output
|
|
280
281
|
handle_json_output(ctx, tool.model_dump())
|
|
281
282
|
|
|
282
283
|
# Handle Rich output
|
|
283
|
-
creation_method = "file upload (custom)"
|
|
284
|
+
creation_method = "file upload (custom)"
|
|
284
285
|
rich_panel = display_creation_success(
|
|
285
286
|
"Tool",
|
|
286
287
|
tool.name,
|
|
@@ -294,7 +295,7 @@ def create(ctx, file_arg, file, name, description, tags, import_file):
|
|
|
294
295
|
|
|
295
296
|
except Exception as e:
|
|
296
297
|
handle_json_output(ctx, error=e)
|
|
297
|
-
if ctx
|
|
298
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
298
299
|
display_api_error(e, "tool creation")
|
|
299
300
|
raise click.ClickException(str(e))
|
|
300
301
|
|
|
@@ -309,7 +310,7 @@ def create(ctx, file_arg, file, name, description, tags, import_file):
|
|
|
309
310
|
)
|
|
310
311
|
@output_flags()
|
|
311
312
|
@click.pass_context
|
|
312
|
-
def get(ctx, tool_ref, select, export):
|
|
313
|
+
def get(ctx: Any, tool_ref: str, select: int | None, export: str | None) -> None:
|
|
313
314
|
"""Get tool details.
|
|
314
315
|
|
|
315
316
|
Examples:
|
|
@@ -406,7 +407,13 @@ def get(ctx, tool_ref, select, export):
|
|
|
406
407
|
@click.option("--tags", help="Comma-separated tags")
|
|
407
408
|
@output_flags()
|
|
408
409
|
@click.pass_context
|
|
409
|
-
def update(
|
|
410
|
+
def update(
|
|
411
|
+
ctx: Any,
|
|
412
|
+
tool_id: str,
|
|
413
|
+
file: str | None,
|
|
414
|
+
description: str | None,
|
|
415
|
+
tags: tuple[str, ...] | None,
|
|
416
|
+
) -> None:
|
|
410
417
|
"""Update a tool (code or metadata)."""
|
|
411
418
|
try:
|
|
412
419
|
client = get_client(ctx)
|
|
@@ -453,7 +460,7 @@ def update(ctx, tool_id, file, description, tags):
|
|
|
453
460
|
|
|
454
461
|
except Exception as e:
|
|
455
462
|
handle_json_output(ctx, error=e)
|
|
456
|
-
if ctx
|
|
463
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
457
464
|
display_api_error(e, "tool update")
|
|
458
465
|
raise click.ClickException(str(e))
|
|
459
466
|
|
|
@@ -463,7 +470,7 @@ def update(ctx, tool_id, file, description, tags):
|
|
|
463
470
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
464
471
|
@output_flags()
|
|
465
472
|
@click.pass_context
|
|
466
|
-
def delete(ctx, tool_id, yes):
|
|
473
|
+
def delete(ctx: Any, tool_id: str, yes: bool) -> None:
|
|
467
474
|
"""Delete a tool."""
|
|
468
475
|
try:
|
|
469
476
|
client = get_client(ctx)
|
|
@@ -491,7 +498,7 @@ def delete(ctx, tool_id, yes):
|
|
|
491
498
|
|
|
492
499
|
except Exception as e:
|
|
493
500
|
handle_json_output(ctx, error=e)
|
|
494
|
-
if ctx
|
|
501
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
495
502
|
display_api_error(e, "tool deletion")
|
|
496
503
|
raise click.ClickException(str(e))
|
|
497
504
|
|
|
@@ -500,13 +507,13 @@ def delete(ctx, tool_id, yes):
|
|
|
500
507
|
@click.argument("tool_id")
|
|
501
508
|
@output_flags()
|
|
502
509
|
@click.pass_context
|
|
503
|
-
def script(ctx, tool_id):
|
|
510
|
+
def script(ctx: Any, tool_id: str) -> None:
|
|
504
511
|
"""Get tool script content."""
|
|
505
512
|
try:
|
|
506
513
|
client = get_client(ctx)
|
|
507
514
|
script_content = client.get_tool_script(tool_id)
|
|
508
515
|
|
|
509
|
-
if ctx
|
|
516
|
+
if get_ctx_value(ctx, "view") == "json":
|
|
510
517
|
click.echo(json.dumps({"script": script_content}, indent=2))
|
|
511
518
|
else:
|
|
512
519
|
console.print(f"[green]📜 Tool Script for '{tool_id}':[/green]")
|
|
@@ -514,6 +521,6 @@ def script(ctx, tool_id):
|
|
|
514
521
|
|
|
515
522
|
except Exception as e:
|
|
516
523
|
handle_json_output(ctx, error=e)
|
|
517
|
-
if ctx
|
|
524
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
518
525
|
console.print(Text(f"[red]Error getting tool script: {e}[/red]"))
|
|
519
526
|
raise click.ClickException(str(e))
|