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.
@@ -0,0 +1,371 @@
1
+ """CLI commands for managing skillsets and skills."""
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 skillsets and skills.", no_args_is_help=True)
15
+ skills_app = typer.Typer(help="Manage skills within a skillset.", no_args_is_help=True)
16
+ app.add_typer(skills_app, name="skills")
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 _json_arg(value: str | None, label: str) -> Any:
31
+ if value is None:
32
+ return [] if label in ("parameters", "triggers", "tools-required") else {}
33
+ try:
34
+ return json.loads(value)
35
+ except json.JSONDecodeError as exc:
36
+ err_console.print(f"[red]Error[/red]: --{label} is not valid JSON: {exc}")
37
+ sys.exit(1)
38
+
39
+
40
+ def _print_table(items: list[dict[str, Any]], id_key: str) -> None:
41
+ table = Table(show_header=True, header_style="bold")
42
+ table.add_column("ID")
43
+ table.add_column("Name")
44
+ table.add_column("Description")
45
+ table.add_column("Enabled")
46
+ table.add_column("Version", justify="right")
47
+ table.add_column("Updated At")
48
+ for item in items:
49
+ table.add_row(
50
+ item[id_key],
51
+ item["name"],
52
+ item.get("description", ""),
53
+ "yes" if item.get("enabled", True) else "no",
54
+ str(item.get("current_version", "")),
55
+ item.get("updated_at", ""),
56
+ )
57
+ console.print(table)
58
+
59
+
60
+ def _print_detail(data: dict[str, Any], id_key: str, as_json: bool) -> None:
61
+ if as_json:
62
+ console.print_json(json.dumps(data))
63
+ return
64
+ console.print(f"[bold]ID[/bold]: {data[id_key]}")
65
+ if "skillset_id" in data and id_key == "skill_id":
66
+ console.print(f"[bold]Skillset ID[/bold]: {data['skillset_id']}")
67
+ console.print(f"[bold]Name[/bold]: {data['name']}")
68
+ console.print(f"[bold]Description[/bold]: {data.get('description', '')}")
69
+ console.print(f"[bold]Enabled[/bold]: {data.get('enabled', True)}")
70
+ console.print(f"[bold]Version[/bold]: {data.get('current_version', data.get('version'))}")
71
+ console.print(f"[bold]Created By[/bold]: {data['created_by']}")
72
+ console.print(f"[bold]Updated By[/bold]: {data.get('updated_by', '')}")
73
+ if data.get("parameters"):
74
+ console.print(f"[bold]Parameters[/bold]: {json.dumps(data['parameters'])}")
75
+ if data.get("triggers"):
76
+ console.print(f"[bold]Triggers[/bold]: {json.dumps(data['triggers'])}")
77
+ if data.get("tools_required"):
78
+ console.print(f"[bold]Tools Required[/bold]: {json.dumps(data['tools_required'])}")
79
+ if data.get("template"):
80
+ console.print(f"[bold]Template[/bold]\n{data['template']}")
81
+
82
+
83
+ @app.command("list")
84
+ def list_skillsets(output: str = typer.Option("table", "--output", "-o", help="Output format: table or json.")) -> None:
85
+ try:
86
+ data = state.get_client().get("/api/v1/skillsets")
87
+ except Exception as exc:
88
+ _die(exc)
89
+ return
90
+ if output == "json":
91
+ console.print_json(json.dumps(data))
92
+ return
93
+ _print_table(data.get("skillsets", []), "skillset_id")
94
+
95
+
96
+ @app.command("get")
97
+ def get_skillset(
98
+ skillset_id: str = typer.Argument(help="Skillset ID."),
99
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
100
+ ) -> None:
101
+ try:
102
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}")
103
+ except Exception as exc:
104
+ _die(exc)
105
+ return
106
+ _print_detail(data, "skillset_id", output == "json")
107
+
108
+
109
+ @app.command("create")
110
+ def create_skillset(
111
+ skillset_id: str = typer.Argument(help="Immutable lower_snake_case skillset ID."),
112
+ name: str = typer.Argument(help="Skillset name."),
113
+ description: str = typer.Option("", "--description", "-d", help="Description."),
114
+ disabled: bool = typer.Option(False, "--disabled", help="Create as disabled."),
115
+ ) -> None:
116
+ try:
117
+ data = state.get_client().post(
118
+ "/api/v1/skillsets",
119
+ json={"skillset_id": skillset_id, "name": name, "description": description, "enabled": not disabled},
120
+ )
121
+ except Exception as exc:
122
+ _die(exc)
123
+ return
124
+ console.print(f"[green]Created[/green]: {data['skillset_id']} name={data['name']!r}")
125
+
126
+
127
+ @app.command("update")
128
+ def update_skillset(
129
+ skillset_id: str = typer.Argument(help="Skillset ID."),
130
+ name: str = typer.Option(..., "--name", "-n", help="New name."),
131
+ description: str = typer.Option("", "--description", "-d", help="Description."),
132
+ enabled: bool = typer.Option(True, "--enabled/--disabled", help="Enable or disable."),
133
+ comment: str | None = typer.Option(None, "--comment", "-c", help="Version comment."),
134
+ ) -> None:
135
+ try:
136
+ data = state.get_client().put(
137
+ f"/api/v1/skillsets/{skillset_id}",
138
+ json={"name": name, "description": description, "enabled": enabled, "comment": comment},
139
+ )
140
+ except Exception as exc:
141
+ _die(exc)
142
+ return
143
+ _print_detail(data, "skillset_id", False)
144
+
145
+
146
+ @app.command("delete")
147
+ def delete_skillset(
148
+ skillset_id: str = typer.Argument(help="Skillset ID."),
149
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
150
+ ) -> None:
151
+ if not yes:
152
+ typer.confirm(f"Delete skillset {skillset_id!r} and all its skills?", abort=True)
153
+ try:
154
+ state.get_client().delete(f"/api/v1/skillsets/{skillset_id}")
155
+ except Exception as exc:
156
+ _die(exc)
157
+ return
158
+ console.print(f"[red]Deleted[/red]: {skillset_id}")
159
+
160
+
161
+ @app.command("versions")
162
+ def list_skillset_versions(
163
+ skillset_id: str = typer.Argument(help="Skillset ID."),
164
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
165
+ ) -> None:
166
+ try:
167
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/versions")
168
+ except Exception as exc:
169
+ _die(exc)
170
+ return
171
+ if output == "json":
172
+ console.print_json(json.dumps(data))
173
+ return
174
+ _print_versions(data.get("versions", []))
175
+
176
+
177
+ @app.command("version-get")
178
+ def get_skillset_version(
179
+ skillset_id: str = typer.Argument(help="Skillset ID."),
180
+ version: int = typer.Argument(help="Version number."),
181
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
182
+ ) -> None:
183
+ try:
184
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/versions/{version}")
185
+ except Exception as exc:
186
+ _die(exc)
187
+ return
188
+ _print_detail(data, "skillset_id", output == "json")
189
+
190
+
191
+ def _print_versions(versions: list[dict[str, Any]]) -> None:
192
+ table = Table(show_header=True, header_style="bold")
193
+ table.add_column("Version", justify="right")
194
+ table.add_column("Created By")
195
+ table.add_column("Created At")
196
+ table.add_column("Comment")
197
+ for v in versions:
198
+ table.add_row(str(v["version"]), v["created_by"], v["created_at"], v.get("comment") or "")
199
+ console.print(table)
200
+
201
+
202
+ @skills_app.command("list")
203
+ def list_skills(
204
+ skillset_id: str = typer.Argument(help="Skillset ID."),
205
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
206
+ ) -> None:
207
+ try:
208
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/skills")
209
+ except Exception as exc:
210
+ _die(exc)
211
+ return
212
+ if output == "json":
213
+ console.print_json(json.dumps(data))
214
+ return
215
+ _print_table(data.get("skills", []), "skill_id")
216
+
217
+
218
+ @skills_app.command("get")
219
+ def get_skill(
220
+ skillset_id: str = typer.Argument(help="Skillset ID."),
221
+ skill_id: str = typer.Argument(help="Skill ID."),
222
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
223
+ ) -> None:
224
+ try:
225
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}")
226
+ except Exception as exc:
227
+ _die(exc)
228
+ return
229
+ _print_detail(data, "skill_id", output == "json")
230
+
231
+
232
+ @skills_app.command("create")
233
+ def create_skill(
234
+ skillset_id: str = typer.Argument(help="Skillset ID."),
235
+ skill_id: str = typer.Argument(help="Immutable lower_snake_case skill ID."),
236
+ name: str = typer.Option(..., "--name", "-n", help="Skill name."),
237
+ template: str = typer.Option(..., "--template", help="Prompt template."),
238
+ description: str = typer.Option("", "--description", "-d", help="Description."),
239
+ parameters: str | None = typer.Option(None, "--parameters", help="Parameters as a JSON array."),
240
+ triggers: str | None = typer.Option(None, "--triggers", help="Triggers as a JSON array of strings."),
241
+ tools_required: str | None = typer.Option(
242
+ None,
243
+ "--tools-required",
244
+ help="Required MCP tool names as a JSON array of strings.",
245
+ ),
246
+ disabled: bool = typer.Option(False, "--disabled", help="Create as disabled."),
247
+ ) -> None:
248
+ try:
249
+ data = state.get_client().post(
250
+ f"/api/v1/skillsets/{skillset_id}/skills",
251
+ json={
252
+ "skill_id": skill_id,
253
+ "name": name,
254
+ "description": description,
255
+ "template": template,
256
+ "parameters": _json_arg(parameters, "parameters"),
257
+ "triggers": _json_arg(triggers, "triggers"),
258
+ "tools_required": _json_arg(tools_required, "tools-required"),
259
+ "enabled": not disabled,
260
+ },
261
+ )
262
+ except Exception as exc:
263
+ _die(exc)
264
+ return
265
+ console.print(f"[green]Created[/green]: {data['skill_id']} name={data['name']!r}")
266
+
267
+
268
+ @skills_app.command("update")
269
+ def update_skill(
270
+ skillset_id: str = typer.Argument(help="Skillset ID."),
271
+ skill_id: str = typer.Argument(help="Skill ID."),
272
+ name: str = typer.Option(..., "--name", "-n", help="New name."),
273
+ template: str = typer.Option(..., "--template", help="Prompt template."),
274
+ description: str = typer.Option("", "--description", "-d", help="Description."),
275
+ parameters: str | None = typer.Option(None, "--parameters", help="Parameters as a JSON array."),
276
+ triggers: str | None = typer.Option(None, "--triggers", help="Triggers as a JSON array of strings."),
277
+ tools_required: str | None = typer.Option(
278
+ None,
279
+ "--tools-required",
280
+ help="Required MCP tool names as a JSON array of strings.",
281
+ ),
282
+ enabled: bool = typer.Option(True, "--enabled/--disabled", help="Enable or disable."),
283
+ comment: str | None = typer.Option(None, "--comment", "-c", help="Version comment."),
284
+ ) -> None:
285
+ try:
286
+ data = state.get_client().put(
287
+ f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}",
288
+ json={
289
+ "name": name,
290
+ "description": description,
291
+ "template": template,
292
+ "parameters": _json_arg(parameters, "parameters"),
293
+ "triggers": _json_arg(triggers, "triggers"),
294
+ "tools_required": _json_arg(tools_required, "tools-required"),
295
+ "enabled": enabled,
296
+ "comment": comment,
297
+ },
298
+ )
299
+ except Exception as exc:
300
+ _die(exc)
301
+ return
302
+ _print_detail(data, "skill_id", False)
303
+
304
+
305
+ @skills_app.command("delete")
306
+ def delete_skill(
307
+ skillset_id: str = typer.Argument(help="Skillset ID."),
308
+ skill_id: str = typer.Argument(help="Skill ID."),
309
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
310
+ ) -> None:
311
+ if not yes:
312
+ typer.confirm(f"Delete skill {skill_id!r}?", abort=True)
313
+ try:
314
+ state.get_client().delete(f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}")
315
+ except Exception as exc:
316
+ _die(exc)
317
+ return
318
+ console.print(f"[red]Deleted[/red]: {skill_id}")
319
+
320
+
321
+ @skills_app.command("render")
322
+ def render_skill(
323
+ skillset_id: str = typer.Argument(help="Skillset ID."),
324
+ skill_id: str = typer.Argument(help="Skill ID."),
325
+ args_json: str | None = typer.Option(None, "--args-json", help="Arguments as a JSON object."),
326
+ output: str = typer.Option("text", "--output", "-o", help="Output format: text or json."),
327
+ ) -> None:
328
+ try:
329
+ data = state.get_client().post(
330
+ f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}/render",
331
+ json={"arguments": _json_arg(args_json, "args-json")},
332
+ )
333
+ except Exception as exc:
334
+ _die(exc)
335
+ return
336
+ if output == "json":
337
+ console.print_json(json.dumps(data))
338
+ return
339
+ console.print(data.get("text", ""))
340
+
341
+
342
+ @skills_app.command("versions")
343
+ def list_skill_versions(
344
+ skillset_id: str = typer.Argument(help="Skillset ID."),
345
+ skill_id: str = typer.Argument(help="Skill ID."),
346
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
347
+ ) -> None:
348
+ try:
349
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}/versions")
350
+ except Exception as exc:
351
+ _die(exc)
352
+ return
353
+ if output == "json":
354
+ console.print_json(json.dumps(data))
355
+ return
356
+ _print_versions(data.get("versions", []))
357
+
358
+
359
+ @skills_app.command("version-get")
360
+ def get_skill_version(
361
+ skillset_id: str = typer.Argument(help="Skillset ID."),
362
+ skill_id: str = typer.Argument(help="Skill ID."),
363
+ version: int = typer.Argument(help="Version number."),
364
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json."),
365
+ ) -> None:
366
+ try:
367
+ data = state.get_client().get(f"/api/v1/skillsets/{skillset_id}/skills/{skill_id}/versions/{version}")
368
+ except Exception as exc:
369
+ _die(exc)
370
+ return
371
+ _print_detail(data, "skill_id", output == "json")