seizu-cli 2.0.0__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.
- seizu_cli/__init__.py +0 -0
- seizu_cli/__main__.py +4 -0
- seizu_cli/auth.py +365 -0
- seizu_cli/client.py +62 -0
- seizu_cli/commands/__init__.py +0 -0
- seizu_cli/commands/auth.py +84 -0
- seizu_cli/commands/reports.py +223 -0
- seizu_cli/commands/scheduled_queries.py +157 -0
- seizu_cli/commands/seed.py +852 -0
- seizu_cli/commands/skillsets.py +371 -0
- seizu_cli/commands/toolsets.py +506 -0
- seizu_cli/config.py +38 -0
- seizu_cli/main.py +181 -0
- seizu_cli/schema.py +31 -0
- seizu_cli/state.py +29 -0
- seizu_cli-2.0.0.dist-info/METADATA +13 -0
- seizu_cli-2.0.0.dist-info/RECORD +22 -0
- seizu_cli-2.0.0.dist-info/WHEEL +5 -0
- seizu_cli-2.0.0.dist-info/entry_points.txt +2 -0
- seizu_cli-2.0.0.dist-info/top_level.txt +2 -0
- seizu_schema/__init__.py +1 -0
- seizu_schema/reporting_config.py +939 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"""CLI commands for managing toolsets and tools."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from seizu_cli import state
|
|
12
|
+
from seizu_cli.client import APIError
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Manage toolsets and tools.", no_args_is_help=True)
|
|
15
|
+
tools_app = typer.Typer(help="Manage tools within a toolset.", no_args_is_help=True)
|
|
16
|
+
app.add_typer(tools_app, name="tools")
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
err_console = Console(stderr=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _die(exc: Exception) -> None:
|
|
23
|
+
if isinstance(exc, APIError):
|
|
24
|
+
err_console.print(f"[red]Error {exc.status_code}[/red]: {exc}")
|
|
25
|
+
else:
|
|
26
|
+
err_console.print(f"[red]Error[/red]: {exc}")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _print_toolset_detail(data: dict[str, Any], as_json: bool) -> None:
|
|
31
|
+
if as_json:
|
|
32
|
+
console.print_json(json.dumps(data))
|
|
33
|
+
return
|
|
34
|
+
console.print(f"[bold]ID[/bold]: {data['toolset_id']}")
|
|
35
|
+
console.print(f"[bold]Name[/bold]: {data['name']}")
|
|
36
|
+
console.print(f"[bold]Description[/bold]: {data.get('description', '')}")
|
|
37
|
+
console.print(f"[bold]Enabled[/bold]: {data.get('enabled', True)}")
|
|
38
|
+
console.print(f"[bold]Version[/bold]: {data.get('current_version', data.get('version'))}")
|
|
39
|
+
console.print(f"[bold]Created By[/bold]: {data['created_by']}")
|
|
40
|
+
console.print(f"[bold]Updated By[/bold]: {data.get('updated_by', '')}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _print_tool_detail(data: dict[str, Any], as_json: bool) -> None:
|
|
44
|
+
if as_json:
|
|
45
|
+
console.print_json(json.dumps(data))
|
|
46
|
+
return
|
|
47
|
+
console.print(f"[bold]ID[/bold]: {data['tool_id']}")
|
|
48
|
+
console.print(f"[bold]Toolset ID[/bold]: {data['toolset_id']}")
|
|
49
|
+
console.print(f"[bold]Name[/bold]: {data['name']}")
|
|
50
|
+
console.print(f"[bold]Description[/bold]: {data.get('description', '')}")
|
|
51
|
+
console.print(f"[bold]Enabled[/bold]: {data.get('enabled', True)}")
|
|
52
|
+
console.print(f"[bold]Version[/bold]: {data.get('current_version', data.get('version'))}")
|
|
53
|
+
console.print(f"[bold]Created By[/bold]: {data['created_by']}")
|
|
54
|
+
console.print(f"[bold]Updated By[/bold]: {data.get('updated_by', '')}")
|
|
55
|
+
if data.get("parameters"):
|
|
56
|
+
console.print(f"[bold]Parameters[/bold]: {json.dumps(data['parameters'])}")
|
|
57
|
+
console.print(f"[bold]Cypher[/bold]\n{data['cypher']}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Toolset commands
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command("list")
|
|
66
|
+
def list_toolsets(
|
|
67
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
68
|
+
) -> None:
|
|
69
|
+
"""List all toolsets."""
|
|
70
|
+
try:
|
|
71
|
+
data = state.get_client().get("/api/v1/toolsets")
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
_die(exc)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
if output == "json":
|
|
77
|
+
console.print_json(json.dumps(data))
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
items = data.get("toolsets", [])
|
|
81
|
+
if not items:
|
|
82
|
+
console.print("[dim]No toolsets found.[/dim]")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
table = Table(show_header=True, header_style="bold")
|
|
86
|
+
table.add_column("ID")
|
|
87
|
+
table.add_column("Name")
|
|
88
|
+
table.add_column("Description")
|
|
89
|
+
table.add_column("Enabled")
|
|
90
|
+
table.add_column("Version", justify="right")
|
|
91
|
+
table.add_column("Updated At")
|
|
92
|
+
|
|
93
|
+
for ts in items:
|
|
94
|
+
table.add_row(
|
|
95
|
+
ts["toolset_id"],
|
|
96
|
+
ts["name"],
|
|
97
|
+
ts.get("description", ""),
|
|
98
|
+
"yes" if ts.get("enabled", True) else "no",
|
|
99
|
+
str(ts.get("current_version", "")),
|
|
100
|
+
ts.get("updated_at", ""),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
console.print(table)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command("get")
|
|
107
|
+
def get_toolset(
|
|
108
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
109
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Get a toolset by ID."""
|
|
112
|
+
try:
|
|
113
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}")
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
_die(exc)
|
|
116
|
+
return
|
|
117
|
+
_print_toolset_detail(data, as_json=(output == "json"))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("create")
|
|
121
|
+
def create_toolset(
|
|
122
|
+
toolset_id: str = typer.Argument(help="Immutable lower_snake_case toolset ID."),
|
|
123
|
+
name: str = typer.Argument(help="Toolset name."),
|
|
124
|
+
description: str = typer.Option("", "--description", "-d", help="Description."),
|
|
125
|
+
disabled: bool = typer.Option(False, "--disabled", help="Create as disabled."),
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Create a new toolset."""
|
|
128
|
+
try:
|
|
129
|
+
data = state.get_client().post(
|
|
130
|
+
"/api/v1/toolsets",
|
|
131
|
+
json={"toolset_id": toolset_id, "name": name, "description": description, "enabled": not disabled},
|
|
132
|
+
)
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
_die(exc)
|
|
135
|
+
return
|
|
136
|
+
console.print(f"[green]Created[/green]: {data['toolset_id']} name={data['name']!r}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command("update")
|
|
140
|
+
def update_toolset(
|
|
141
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
142
|
+
name: str = typer.Option(..., "--name", "-n", help="New name."),
|
|
143
|
+
description: str = typer.Option("", "--description", "-d", help="Description."),
|
|
144
|
+
enabled: bool = typer.Option(True, "--enabled/--disabled", help="Enable or disable."),
|
|
145
|
+
comment: str | None = typer.Option(None, "--comment", "-c", help="Version comment."),
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Update a toolset (creates a new version)."""
|
|
148
|
+
try:
|
|
149
|
+
data = state.get_client().put(
|
|
150
|
+
f"/api/v1/toolsets/{toolset_id}",
|
|
151
|
+
json={
|
|
152
|
+
"name": name,
|
|
153
|
+
"description": description,
|
|
154
|
+
"enabled": enabled,
|
|
155
|
+
"comment": comment,
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
_die(exc)
|
|
160
|
+
return
|
|
161
|
+
_print_toolset_detail(data, as_json=False)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@app.command("delete")
|
|
165
|
+
def delete_toolset(
|
|
166
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
167
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Delete a toolset and all its tools."""
|
|
170
|
+
if not yes:
|
|
171
|
+
typer.confirm(f"Delete toolset {toolset_id!r} and all its tools?", abort=True)
|
|
172
|
+
try:
|
|
173
|
+
state.get_client().delete(f"/api/v1/toolsets/{toolset_id}")
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
_die(exc)
|
|
176
|
+
return
|
|
177
|
+
console.print(f"[red]Deleted[/red]: {toolset_id}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.command("versions")
|
|
181
|
+
def list_toolset_versions(
|
|
182
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
183
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
184
|
+
) -> None:
|
|
185
|
+
"""List all versions of a toolset."""
|
|
186
|
+
try:
|
|
187
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/versions")
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
_die(exc)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
if output == "json":
|
|
193
|
+
console.print_json(json.dumps(data))
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
versions = data.get("versions", [])
|
|
197
|
+
table = Table(show_header=True, header_style="bold")
|
|
198
|
+
table.add_column("Version", justify="right")
|
|
199
|
+
table.add_column("Created By")
|
|
200
|
+
table.add_column("Created At")
|
|
201
|
+
table.add_column("Comment")
|
|
202
|
+
|
|
203
|
+
for v in versions:
|
|
204
|
+
table.add_row(
|
|
205
|
+
str(v["version"]),
|
|
206
|
+
v["created_by"],
|
|
207
|
+
v["created_at"],
|
|
208
|
+
v.get("comment") or "",
|
|
209
|
+
)
|
|
210
|
+
console.print(table)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@app.command("version-get")
|
|
214
|
+
def get_toolset_version(
|
|
215
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
216
|
+
version: int = typer.Argument(help="Version number."),
|
|
217
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Get a specific version of a toolset."""
|
|
220
|
+
try:
|
|
221
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/versions/{version}")
|
|
222
|
+
except Exception as exc:
|
|
223
|
+
_die(exc)
|
|
224
|
+
return
|
|
225
|
+
_print_toolset_detail(data, as_json=(output == "json"))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Tool commands (nested under toolsets tools)
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@tools_app.command("list")
|
|
234
|
+
def list_tools(
|
|
235
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
236
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
237
|
+
) -> None:
|
|
238
|
+
"""List all tools in a toolset."""
|
|
239
|
+
try:
|
|
240
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/tools")
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
_die(exc)
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
if output == "json":
|
|
246
|
+
console.print_json(json.dumps(data))
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
items = data.get("tools", [])
|
|
250
|
+
if not items:
|
|
251
|
+
console.print("[dim]No tools found.[/dim]")
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
table = Table(show_header=True, header_style="bold")
|
|
255
|
+
table.add_column("ID")
|
|
256
|
+
table.add_column("Name")
|
|
257
|
+
table.add_column("Description")
|
|
258
|
+
table.add_column("Enabled")
|
|
259
|
+
table.add_column("Version", justify="right")
|
|
260
|
+
table.add_column("Updated At")
|
|
261
|
+
|
|
262
|
+
for t in items:
|
|
263
|
+
table.add_row(
|
|
264
|
+
t["tool_id"],
|
|
265
|
+
t["name"],
|
|
266
|
+
t.get("description", ""),
|
|
267
|
+
"yes" if t.get("enabled", True) else "no",
|
|
268
|
+
str(t.get("current_version", "")),
|
|
269
|
+
t.get("updated_at", ""),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
console.print(table)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@tools_app.command("get")
|
|
276
|
+
def get_tool(
|
|
277
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
278
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
279
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
280
|
+
) -> None:
|
|
281
|
+
"""Get a tool by ID."""
|
|
282
|
+
try:
|
|
283
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}")
|
|
284
|
+
except Exception as exc:
|
|
285
|
+
_die(exc)
|
|
286
|
+
return
|
|
287
|
+
_print_tool_detail(data, as_json=(output == "json"))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@tools_app.command("create")
|
|
291
|
+
def create_tool(
|
|
292
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
293
|
+
tool_id: str = typer.Argument(help="Immutable lower_snake_case tool ID."),
|
|
294
|
+
name: str = typer.Option(..., "--name", "-n", help="Tool name."),
|
|
295
|
+
cypher: str = typer.Option(..., "--cypher", help="Cypher query."),
|
|
296
|
+
description: str = typer.Option("", "--description", "-d", help="Description."),
|
|
297
|
+
parameters: str | None = typer.Option(
|
|
298
|
+
None,
|
|
299
|
+
"--parameters",
|
|
300
|
+
help="Parameters as a JSON array of ToolParamDef objects.",
|
|
301
|
+
),
|
|
302
|
+
disabled: bool = typer.Option(False, "--disabled", help="Create as disabled."),
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Create a new tool within a toolset."""
|
|
305
|
+
params: list[dict[str, Any]] = []
|
|
306
|
+
if parameters:
|
|
307
|
+
try:
|
|
308
|
+
params = json.loads(parameters)
|
|
309
|
+
except json.JSONDecodeError as exc:
|
|
310
|
+
err_console.print(f"[red]Error[/red]: --parameters is not valid JSON: {exc}")
|
|
311
|
+
sys.exit(1)
|
|
312
|
+
try:
|
|
313
|
+
data = state.get_client().post(
|
|
314
|
+
f"/api/v1/toolsets/{toolset_id}/tools",
|
|
315
|
+
json={
|
|
316
|
+
"tool_id": tool_id,
|
|
317
|
+
"name": name,
|
|
318
|
+
"description": description,
|
|
319
|
+
"cypher": cypher,
|
|
320
|
+
"parameters": params,
|
|
321
|
+
"enabled": not disabled,
|
|
322
|
+
},
|
|
323
|
+
)
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
_die(exc)
|
|
326
|
+
return
|
|
327
|
+
console.print(f"[green]Created[/green]: {data['tool_id']} name={data['name']!r}")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@tools_app.command("update")
|
|
331
|
+
def update_tool(
|
|
332
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
333
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
334
|
+
name: str = typer.Option(..., "--name", "-n", help="New name."),
|
|
335
|
+
cypher: str = typer.Option(..., "--cypher", help="Cypher query."),
|
|
336
|
+
description: str = typer.Option("", "--description", "-d", help="Description."),
|
|
337
|
+
parameters: str | None = typer.Option(
|
|
338
|
+
None,
|
|
339
|
+
"--parameters",
|
|
340
|
+
help="Parameters as a JSON array of ToolParamDef objects.",
|
|
341
|
+
),
|
|
342
|
+
enabled: bool = typer.Option(True, "--enabled/--disabled", help="Enable or disable."),
|
|
343
|
+
comment: str | None = typer.Option(None, "--comment", "-c", help="Version comment."),
|
|
344
|
+
) -> None:
|
|
345
|
+
"""Update a tool (creates a new version)."""
|
|
346
|
+
params: list[dict[str, Any]] = []
|
|
347
|
+
if parameters:
|
|
348
|
+
try:
|
|
349
|
+
params = json.loads(parameters)
|
|
350
|
+
except json.JSONDecodeError as exc:
|
|
351
|
+
err_console.print(f"[red]Error[/red]: --parameters is not valid JSON: {exc}")
|
|
352
|
+
sys.exit(1)
|
|
353
|
+
try:
|
|
354
|
+
data = state.get_client().put(
|
|
355
|
+
f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}",
|
|
356
|
+
json={
|
|
357
|
+
"name": name,
|
|
358
|
+
"description": description,
|
|
359
|
+
"cypher": cypher,
|
|
360
|
+
"parameters": params,
|
|
361
|
+
"enabled": enabled,
|
|
362
|
+
"comment": comment,
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
except Exception as exc:
|
|
366
|
+
_die(exc)
|
|
367
|
+
return
|
|
368
|
+
_print_tool_detail(data, as_json=False)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@tools_app.command("delete")
|
|
372
|
+
def delete_tool(
|
|
373
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
374
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
375
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Delete a tool."""
|
|
378
|
+
if not yes:
|
|
379
|
+
typer.confirm(f"Delete tool {tool_id!r}?", abort=True)
|
|
380
|
+
try:
|
|
381
|
+
state.get_client().delete(f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}")
|
|
382
|
+
except Exception as exc:
|
|
383
|
+
_die(exc)
|
|
384
|
+
return
|
|
385
|
+
console.print(f"[red]Deleted[/red]: {tool_id}")
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@tools_app.command("call")
|
|
389
|
+
def call_tool(
|
|
390
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
391
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
392
|
+
arg: list[str] = typer.Option(
|
|
393
|
+
[],
|
|
394
|
+
"--arg",
|
|
395
|
+
help=(
|
|
396
|
+
"Argument as KEY=VALUE where VALUE is a JSON literal "
|
|
397
|
+
"(e.g. --arg limit=10 --arg name='\"foo\"'). "
|
|
398
|
+
"Ignored if --args-json is provided."
|
|
399
|
+
),
|
|
400
|
+
),
|
|
401
|
+
args_json: str | None = typer.Option(
|
|
402
|
+
None,
|
|
403
|
+
"--args-json",
|
|
404
|
+
help="All arguments as a JSON object (overrides --arg).",
|
|
405
|
+
),
|
|
406
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
407
|
+
) -> None:
|
|
408
|
+
"""Execute a tool's Cypher query with the provided arguments."""
|
|
409
|
+
arguments: dict[str, Any] = {}
|
|
410
|
+
|
|
411
|
+
if args_json is not None:
|
|
412
|
+
try:
|
|
413
|
+
arguments = json.loads(args_json)
|
|
414
|
+
except json.JSONDecodeError as exc:
|
|
415
|
+
err_console.print(f"[red]Error[/red]: --args-json is not valid JSON: {exc}")
|
|
416
|
+
sys.exit(1)
|
|
417
|
+
elif arg:
|
|
418
|
+
for pair in arg:
|
|
419
|
+
if "=" not in pair:
|
|
420
|
+
err_console.print(f"[red]Error[/red]: --arg {pair!r} must be KEY=VALUE")
|
|
421
|
+
sys.exit(1)
|
|
422
|
+
key, _, raw_value = pair.partition("=")
|
|
423
|
+
try:
|
|
424
|
+
arguments[key] = json.loads(raw_value)
|
|
425
|
+
except json.JSONDecodeError:
|
|
426
|
+
# Treat as plain string if not valid JSON
|
|
427
|
+
arguments[key] = raw_value
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
data = state.get_client().post(
|
|
431
|
+
f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}/call",
|
|
432
|
+
json={"arguments": arguments},
|
|
433
|
+
)
|
|
434
|
+
except Exception as exc:
|
|
435
|
+
_die(exc)
|
|
436
|
+
return
|
|
437
|
+
|
|
438
|
+
if output == "json":
|
|
439
|
+
console.print_json(json.dumps(data))
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
results = data.get("results", [])
|
|
443
|
+
if not results:
|
|
444
|
+
console.print("[dim]No results.[/dim]")
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
# Build table from first result's keys
|
|
448
|
+
columns = list(results[0].keys()) if results else []
|
|
449
|
+
table = Table(show_header=True, header_style="bold")
|
|
450
|
+
for col in columns:
|
|
451
|
+
table.add_column(col)
|
|
452
|
+
|
|
453
|
+
for row in results:
|
|
454
|
+
table.add_row(*[str(row.get(c, "")) for c in columns])
|
|
455
|
+
|
|
456
|
+
console.print(table)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@tools_app.command("versions")
|
|
460
|
+
def list_tool_versions(
|
|
461
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
462
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
463
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
464
|
+
) -> None:
|
|
465
|
+
"""List all versions of a tool."""
|
|
466
|
+
try:
|
|
467
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}/versions")
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
_die(exc)
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
if output == "json":
|
|
473
|
+
console.print_json(json.dumps(data))
|
|
474
|
+
return
|
|
475
|
+
|
|
476
|
+
versions = data.get("versions", [])
|
|
477
|
+
table = Table(show_header=True, header_style="bold")
|
|
478
|
+
table.add_column("Version", justify="right")
|
|
479
|
+
table.add_column("Created By")
|
|
480
|
+
table.add_column("Created At")
|
|
481
|
+
table.add_column("Comment")
|
|
482
|
+
|
|
483
|
+
for v in versions:
|
|
484
|
+
table.add_row(
|
|
485
|
+
str(v["version"]),
|
|
486
|
+
v["created_by"],
|
|
487
|
+
v["created_at"],
|
|
488
|
+
v.get("comment") or "",
|
|
489
|
+
)
|
|
490
|
+
console.print(table)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@tools_app.command("version-get")
|
|
494
|
+
def get_tool_version(
|
|
495
|
+
toolset_id: str = typer.Argument(help="Toolset ID."),
|
|
496
|
+
tool_id: str = typer.Argument(help="Tool ID."),
|
|
497
|
+
version: int = typer.Argument(help="Version number."),
|
|
498
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
|
|
499
|
+
) -> None:
|
|
500
|
+
"""Get a specific version of a tool."""
|
|
501
|
+
try:
|
|
502
|
+
data = state.get_client().get(f"/api/v1/toolsets/{toolset_id}/tools/{tool_id}/versions/{version}")
|
|
503
|
+
except Exception as exc:
|
|
504
|
+
_die(exc)
|
|
505
|
+
return
|
|
506
|
+
_print_tool_detail(data, as_json=(output == "json"))
|
seizu_cli/config.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""CLI configuration loaded from ~/.config/seizu/seizu.conf."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
_DEFAULT_CONFIG_DIR = Path.home() / ".config" / "seizu"
|
|
9
|
+
_DEFAULT_CONFIG_FILE = _DEFAULT_CONFIG_DIR / "seizu.conf"
|
|
10
|
+
_DEFAULT_SEED_FILE = _DEFAULT_CONFIG_DIR / "reporting-dashboard.yaml"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SeizuConfig(BaseModel):
|
|
14
|
+
"""Parsed contents of ~/.config/seizu/seizu.conf."""
|
|
15
|
+
|
|
16
|
+
api_url: str | None = None
|
|
17
|
+
seed_file: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_config(config_file: Path | None = None) -> SeizuConfig:
|
|
21
|
+
"""Load the CLI config file.
|
|
22
|
+
|
|
23
|
+
Reads *config_file* if provided, otherwise falls back to
|
|
24
|
+
``~/.config/seizu/seizu.conf``. Returns an empty :class:`SeizuConfig`
|
|
25
|
+
when the file does not exist rather than raising an error.
|
|
26
|
+
"""
|
|
27
|
+
path = config_file or _DEFAULT_CONFIG_FILE
|
|
28
|
+
try:
|
|
29
|
+
with open(path) as f:
|
|
30
|
+
data = yaml.safe_load(f) or {}
|
|
31
|
+
return SeizuConfig.model_validate(data)
|
|
32
|
+
except FileNotFoundError:
|
|
33
|
+
return SeizuConfig()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def default_seed_file() -> str:
|
|
37
|
+
"""Return the default seed-file path as a string."""
|
|
38
|
+
return str(_DEFAULT_SEED_FILE)
|