loktar-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: loktar-cli
3
+ Version: 0.1.0
4
+ Summary: CLI and AI agent client for LOKTAR — query 2100+ curated AI tools across 86+ tablets
5
+ Author-email: Loktar Team <info@loktar.cc>
6
+ License: MIT
7
+ Project-URL: Homepage, https://loktar.cc
8
+ Keywords: cli,ai-tools,agents,mcp,loktar
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: click>=8.1.0
20
+ Requires-Dist: requests>=2.28.0
21
+ Requires-Dist: rich>=13.0.0
22
+
23
+ # LOKTAR CLI (`loktar`)
24
+
25
+ A super efficient, scriptable command-line interface and AI agent client for **LOKTAR** (https://loktar.cc).
26
+
27
+ Allows both human developers and autonomous AI agents (Claude Code, Antigravity, Aider, Cursor, etc.) to query, search, and inspect 86+ tablets and 2100+ AI tools with minimum token usage and zero prompt overhead.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install --upgrade lockthard-cli
33
+ ```
34
+
35
+ Or run without installing:
36
+
37
+ ```bash
38
+ pipx run lockthard-cli --help
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Authentication
44
+ Get your API key from [loktar.cc](https://loktar.cc).
45
+
46
+ - **For AI Agents & CI/CD:**
47
+ ```bash
48
+ export LOKTAR_API_KEY="lk_your_token_here"
49
+ ```
50
+ - **Interactive Browser Login (Humans):**
51
+ ```bash
52
+ loktar login
53
+ ```
54
+ - **Manual Login:**
55
+ ```bash
56
+ loktar login --token "lk_your_token_here"
57
+ ```
58
+
59
+ ### 2. Commands
60
+
61
+ ```bash
62
+ # Ping service
63
+ loktar ping
64
+
65
+ # Search tools (returns concise table)
66
+ loktar search "ocr"
67
+
68
+ # Search tools in raw JSON (for agents / jq)
69
+ loktar -j search "ocr"
70
+
71
+ # List all tablets
72
+ loktar tablets list
73
+
74
+ # Inspect a specific tablet
75
+ loktar tablets get ocr
76
+
77
+ # Inspect tool details
78
+ loktar tools get "Chandra OCR"
79
+
80
+ # Display CLI agent guide
81
+ loktar guide
82
+
83
+ # Display full agent skill
84
+ loktar skill
85
+ ```
86
+
87
+ ## Options
88
+
89
+ - `--json` / `-j`: Output raw JSON without Rich styling.
90
+ - `--url` / `-u`: Override base server URL (default: `https://loktar.cc`).
91
+ - `--token` / `-t`: Override API token.
92
+
93
+ ## License
94
+
95
+ MIT License
@@ -0,0 +1,73 @@
1
+ # LOKTAR CLI (`loktar`)
2
+
3
+ A super efficient, scriptable command-line interface and AI agent client for **LOKTAR** (https://loktar.cc).
4
+
5
+ Allows both human developers and autonomous AI agents (Claude Code, Antigravity, Aider, Cursor, etc.) to query, search, and inspect 86+ tablets and 2100+ AI tools with minimum token usage and zero prompt overhead.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install --upgrade lockthard-cli
11
+ ```
12
+
13
+ Or run without installing:
14
+
15
+ ```bash
16
+ pipx run lockthard-cli --help
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Authentication
22
+ Get your API key from [loktar.cc](https://loktar.cc).
23
+
24
+ - **For AI Agents & CI/CD:**
25
+ ```bash
26
+ export LOKTAR_API_KEY="lk_your_token_here"
27
+ ```
28
+ - **Interactive Browser Login (Humans):**
29
+ ```bash
30
+ loktar login
31
+ ```
32
+ - **Manual Login:**
33
+ ```bash
34
+ loktar login --token "lk_your_token_here"
35
+ ```
36
+
37
+ ### 2. Commands
38
+
39
+ ```bash
40
+ # Ping service
41
+ loktar ping
42
+
43
+ # Search tools (returns concise table)
44
+ loktar search "ocr"
45
+
46
+ # Search tools in raw JSON (for agents / jq)
47
+ loktar -j search "ocr"
48
+
49
+ # List all tablets
50
+ loktar tablets list
51
+
52
+ # Inspect a specific tablet
53
+ loktar tablets get ocr
54
+
55
+ # Inspect tool details
56
+ loktar tools get "Chandra OCR"
57
+
58
+ # Display CLI agent guide
59
+ loktar guide
60
+
61
+ # Display full agent skill
62
+ loktar skill
63
+ ```
64
+
65
+ ## Options
66
+
67
+ - `--json` / `-j`: Output raw JSON without Rich styling.
68
+ - `--url` / `-u`: Override base server URL (default: `https://loktar.cc`).
69
+ - `--token` / `-t`: Override API token.
70
+
71
+ ## License
72
+
73
+ MIT License
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: loktar
3
+ description: "LOKTAR CLI — query 2100+ curated AI tools across 86+ tablets. Search, browse tablets, get tool details, and investigate repos. Designed for both humans and AI agents."
4
+ version: 0.2.8
5
+ metadata:
6
+ hermes:
7
+ tags: [loktar, tools, ai, search, tablets, mcp]
8
+ homepage: https://loktar.cc
9
+ ---
10
+
11
+ # LOKTAR CLI
12
+
13
+ CLI para consultar el directorio de herramientas IA de LOKTAR (2100+ herramientas, 86+ tablets). Diseñada para humanos (tablas Rich) y agentes IA (JSON limpio).
14
+
15
+ ## Autenticación
16
+
17
+ ```bash
18
+ # Opción 1: Variable de entorno (recomendado para agentes)
19
+ export LOKTAR_API_KEY="lk_tu_token"
20
+
21
+ # Opción 2: Login con token
22
+ loktar login --token lk_tu_token
23
+
24
+ # Opción 3: Login interactivo (abre navegador)
25
+ loktar login
26
+ ```
27
+
28
+ Token guardado en `~/.loktar/credentials.json` con permisos 0600.
29
+
30
+ ## Flag JSON (`-j` / `--json`)
31
+
32
+ Para agentes IA: **siempre usa `-j`** para recibir JSON limpio sin formato ANSI.
33
+
34
+ El flag funciona tanto a nivel global como en cualquier subcommand:
35
+
36
+ ```bash
37
+ loktar -j tablets list # ✅ global
38
+ loktar tablets list -j # ✅ en subcommand
39
+ loktar -j tablets get vector-knowledge-base # ✅
40
+ loktar tablets get vector-knowledge-base -j # ✅
41
+ ```
42
+
43
+ ## Comandos
44
+
45
+ | Comando | Descripción |
46
+ |---|---|
47
+ | `loktar ping` | Verificar conexión y autenticación |
48
+ | `loktar -j search "<query>"` | Buscar herramientas por palabra clave |
49
+ | `loktar -j tablets list` | Listar todos los tablets disponibles |
50
+ | `loktar -j tablets get <tablet_id>` | Ver herramientas de un tablet |
51
+ | `loktar -j tools get "<tool_id>"` | Detalles de una herramienta específica |
52
+ | `loktar -j cli-tools` | Listar herramientas CLI dedicadas |
53
+ | `loktar guide` | Guía rápida para agentes |
54
+ | `loktar skill` | Muestra esta SKILL.md completa |
55
+
56
+ ## Estructura de un Tablet
57
+
58
+ Cada tablet es una colección curada de herramientas agrupadas por temática. Al obtener un tablet (`loktar -j tablets get <id>`), recibes:
59
+
60
+ ```json
61
+ {
62
+ "id": "vector-knowledge-base",
63
+ "title": "Vector Knowledge Base",
64
+ "toolCount": 41,
65
+ "tools": [
66
+ {
67
+ "name": "Graphify",
68
+ "description": "Transforma un proyecto en un knowledge graph...",
69
+ "url": "https://github.com/safishamsi/graphify",
70
+ "section": "General",
71
+ "snippetsCount": 1
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ ### Cómo interpretar las herramientas
78
+
79
+ - **`section`**: Categoría que agrupa la herramienta. **La sección YA es contexto** — si una herramienta está en "Memoria Y Contexto Para Agentes", eso ya indica su dominio.
80
+ - **`description`**: Descripción textual (puede estar vacía si el nombre + sección + URL son suficientes).
81
+ - **`url`**: Enlace al repositorio o sitio web. **El agente debe investigar esta URL** para obtener más detalles (leer README, verificar instalación, etc.).
82
+ - **`snippetsCount`**: Número de snippets de código o instrucciones adicionales disponibles vía `tools get`.
83
+
84
+ ### Ejemplo: interpretación correcta
85
+
86
+ ```
87
+ Herramienta: "LightRAG"
88
+ Sección: "RAG" (o "General")
89
+ URL: https://github.com/HKUDS/LightRAG
90
+ Descripción: (vacía)
91
+ ```
92
+
93
+ **Interpretación correcta:** Es un sistema RAG. La sección indica que es de la categoría RAG. La URL apunta a GitHub donde el agente puede leer el README para entender qué hace.
94
+
95
+ **Interpretación incorrecta:** ❌ "Herramienta sin descripción, está incompleta."
96
+
97
+ ## Flujo de trabajo recomendado para agentes
98
+
99
+ 1. `loktar ping` — verificar auth
100
+ 2. `loktar -j tablets list` — ver tablets disponibles
101
+ 3. `loktar -j tablets get <id>` — explorar herramientas de un tablet relevante
102
+ 4. Para cada herramienta interesante: **ir al repo/URL y leer documentación**
103
+ 5. `loktar -j tools get "<nombre>"` — detalles específicos con snippets si se necesitan
104
+
105
+ ## Ejemplos de uso
106
+
107
+ ```bash
108
+ # Buscar herramientas de OCR
109
+ loktar -j search "ocr"
110
+
111
+ # Ver todo el tablet de cybersecurity
112
+ loktar -j tablets get cybersecurity
113
+
114
+ # Ver detalles de una herramienta específica
115
+ loktar -j tools get "Graphify"
116
+
117
+ # Listar herramientas CLI
118
+ loktar -j cli-tools
119
+
120
+ # Verificar que todo funciona
121
+ loktar ping
122
+ ```
@@ -0,0 +1,5 @@
1
+ """
2
+ LOKTAR CLI - Command Line Interface and Agent Client for LOKTAR.
3
+ """
4
+
5
+ __version__ = "0.2.9"
@@ -0,0 +1,159 @@
1
+ import http.server
2
+ import socket
3
+ import socketserver
4
+ import urllib.parse
5
+ import webbrowser
6
+ from typing import Tuple, Optional, Dict, Any
7
+ from .config import save_credentials, clear_credentials
8
+
9
+
10
+ class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
11
+ """Temporary HTTP handler to capture the token callback from browser authentication."""
12
+
13
+ received_token: Optional[str] = None
14
+ received_email: Optional[str] = None
15
+ error_message: Optional[str] = None
16
+
17
+ def log_message(self, format, *args):
18
+ # Silence default HTTP server logging
19
+ pass
20
+
21
+ def do_GET(self):
22
+ parsed = urllib.parse.urlparse(self.path)
23
+ if parsed.path in ("/callback", "/"):
24
+ query = urllib.parse.parse_qs(parsed.query)
25
+ token = query.get("token", [None])[0]
26
+ email = query.get("email", [None])[0]
27
+ error = query.get("error", [None])[0]
28
+
29
+ if error:
30
+ OAuthCallbackHandler.error_message = error
31
+ self._send_response_html(
32
+ status=400,
33
+ title="Error de Autenticación",
34
+ body=f"<p style='color: red;'>Error durante el inicio de sesión: {error}</p>"
35
+ )
36
+ elif token:
37
+ OAuthCallbackHandler.received_token = token
38
+ OAuthCallbackHandler.received_email = email
39
+ self._send_response_html(
40
+ status=200,
41
+ title="¡Autenticación Exitosa!",
42
+ body="""
43
+ <h2 style='color: #10B981;'>¡Autenticación Exitosa!</h2>
44
+ <p>La CLI de <strong>LOKTAR</strong> ha sido conectada correctamente a tu cuenta.</p>
45
+ <p>Ya puedes cerrar esta ventana y regresar a tu terminal.</p>
46
+ """
47
+ )
48
+ else:
49
+ self._send_response_html(
50
+ status=400,
51
+ title="Falta Token",
52
+ body="<p style='color: red;'>No se recibió ningún token de autenticación.</p>"
53
+ )
54
+ else:
55
+ self.send_error(404, "Not Found")
56
+
57
+ def _send_response_html(self, status: int, title: str, body: str):
58
+ self.send_response(status)
59
+ self.send_header("Content-Type", "text/html; charset=utf-8")
60
+ self.end_headers()
61
+
62
+ html = f"""<!DOCTYPE html>
63
+ <html>
64
+ <head>
65
+ <meta charset="utf-8">
66
+ <title>{title}</title>
67
+ <style>
68
+ body {{
69
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
70
+ background: #0d1117;
71
+ color: #c9d1d9;
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ height: 100vh;
76
+ margin: 0;
77
+ }}
78
+ .card {{
79
+ background: #161b22;
80
+ border: 1px solid #30363d;
81
+ border-radius: 12px;
82
+ padding: 32px;
83
+ max-width: 420px;
84
+ text-align: center;
85
+ box-shadow: 0 10px 25px rgba(0,0,0,0.5);
86
+ }}
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class="card">
91
+ {body}
92
+ </div>
93
+ </body>
94
+ </html>
95
+ """
96
+ self.wfile.write(html.encode("utf-8"))
97
+
98
+
99
+ def find_free_port(start_port: int = 8989) -> int:
100
+ """Find an available port starting from start_port."""
101
+ for port in range(start_port, start_port + 20):
102
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
103
+ try:
104
+ s.bind(("127.0.0.1", port))
105
+ return port
106
+ except OSError:
107
+ continue
108
+ return start_port
109
+
110
+
111
+ def perform_browser_login(api_url: str, timeout_seconds: int = 120) -> Tuple[bool, str]:
112
+ """
113
+ Launch local server, open browser to loktar.cc/cli-auth, and capture token.
114
+ """
115
+ port = find_free_port(8989)
116
+ callback_url = f"http://127.0.0.1:{port}/callback"
117
+ auth_url = f"{api_url.rstrip('/')}/cli-auth?callback={urllib.parse.quote(callback_url)}"
118
+
119
+ OAuthCallbackHandler.received_token = None
120
+ OAuthCallbackHandler.received_email = None
121
+ OAuthCallbackHandler.error_message = None
122
+
123
+ class ReusableTCPServer(socketserver.TCPServer):
124
+ allow_reuse_address = True
125
+
126
+ try:
127
+ httpd = ReusableTCPServer(("127.0.0.1", port), OAuthCallbackHandler)
128
+ httpd.timeout = 1.0
129
+ except Exception as e:
130
+ return False, f"No se pudo iniciar el servidor local en el puerto {port}: {e}"
131
+
132
+ print(f"Abriendo el navegador para autenticar en LOKTAR...")
133
+ print(f"URL: {auth_url}\n")
134
+ try:
135
+ webbrowser.open(auth_url)
136
+ except Exception:
137
+ print("No se pudo abrir el navegador automáticamente. Por favor abre la URL manualmente.")
138
+
139
+ import time
140
+ start_time = time.time()
141
+
142
+ while time.time() - start_time < timeout_seconds:
143
+ httpd.handle_request()
144
+ if OAuthCallbackHandler.received_token:
145
+ token = OAuthCallbackHandler.received_token
146
+ email = OAuthCallbackHandler.received_email
147
+ httpd.server_close()
148
+
149
+ # Save credentials
150
+ save_credentials(api_key=token, email=email, api_url=api_url)
151
+ user_str = f" como {email}" if email else ""
152
+ return True, f"¡Autenticado correctamente{user_str}!"
153
+
154
+ if OAuthCallbackHandler.error_message:
155
+ httpd.server_close()
156
+ return False, f"Error devuelto por la autenticación: {OAuthCallbackHandler.error_message}"
157
+
158
+ httpd.server_close()
159
+ return False, "Tiempo de espera agotado (timeout) esperando la autorización en el navegador."
@@ -0,0 +1,242 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Optional
5
+ import click
6
+
7
+ from . import __version__
8
+ from .config import resolve_token, resolve_api_url, save_credentials, clear_credentials
9
+ from .auth import perform_browser_login
10
+ from .client import LoktarClient, LoktarError
11
+ from .formatters import (
12
+ output_data,
13
+ print_ping_result,
14
+ print_tablets_list,
15
+ print_search_results,
16
+ print_tablet_content,
17
+ print_tool_details,
18
+ print_cli_tools,
19
+ print_agent_guide,
20
+ HAS_RICH,
21
+ console,
22
+ )
23
+ from rich.markdown import Markdown
24
+
25
+
26
+ def _handle_error(err: Exception):
27
+ """Print clean error message without traceback."""
28
+ click.secho(f"Error: {err}", fg="red", err=True)
29
+ sys.exit(1)
30
+
31
+
32
+ def _get_as_json(ctx: click.Context, local_flag: bool) -> bool:
33
+ """Merge global --json with local subcommand --json flag."""
34
+ return ctx.obj.get("as_json", False) or local_flag
35
+
36
+
37
+ @click.group(invoke_without_command=False,
38
+ epilog="Primer uso: loktar skill | Para agentes: loktar -j skill")
39
+ @click.option("--url", "-u", default=None, help="Base URL del servidor LOKTAR (default: https://loktar.cc).")
40
+ @click.option("--token", "-t", default=None, help="API Key / Token de autenticación (lk_...).")
41
+ @click.option("--json", "-j", "as_json", is_flag=True, help="Salida en JSON bruto (ideal para scripts y agentes).")
42
+ @click.version_option(version=__version__, prog_name="loktar")
43
+ @click.pass_context
44
+ def cli(ctx: click.Context, url: Optional[str], token: Optional[str], as_json: bool):
45
+ """LOKTAR CLI - Cliente super eficiente de herramientas IA para humanos y agentes.
46
+
47
+ Primer uso: loktar skill | Para agentes: loktar -j skill
48
+ """
49
+ resolved_url = resolve_api_url(url)
50
+ resolved_token = resolve_token(token)
51
+
52
+ ctx.ensure_object(dict)
53
+ ctx.obj["url"] = resolved_url
54
+ ctx.obj["token"] = resolved_token
55
+ ctx.obj["as_json"] = as_json
56
+ ctx.obj["client"] = LoktarClient(base_url=resolved_url, token=resolved_token)
57
+
58
+
59
+ @cli.command()
60
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
61
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
62
+ @click.pass_context
63
+ def ping(ctx: click.Context, as_json_local: bool):
64
+ """Verifica la conectividad con el servidor de LOKTAR y el estado del token."""
65
+ client: LoktarClient = ctx.obj["client"]
66
+ as_json = _get_as_json(ctx, as_json_local)
67
+ try:
68
+ res = client.ping()
69
+ print_ping_result(res, as_json=as_json)
70
+ except LoktarError as e:
71
+ _handle_error(e)
72
+
73
+
74
+ @cli.command()
75
+ @click.option("--token", "-t", help="API Key directa (lk_...) para guardar sin interacción.")
76
+ @click.option("--manual", "-m", is_flag=True, help="Modo interactivo en consola (sin abrir navegador).")
77
+ @click.pass_context
78
+ def login(ctx: click.Context, token: Optional[str], manual: bool):
79
+ """Inicia sesión en LOKTAR (Browser OAuth, Token o Interactivo)."""
80
+ api_url = ctx.obj["url"]
81
+
82
+ # 1. Direct token via flag
83
+ if token and token.strip():
84
+ save_credentials(api_key=token.strip(), api_url=api_url)
85
+ click.secho("✓ API Key guardada correctamente en ~/.loktar/credentials.json", fg="green")
86
+ return
87
+
88
+ # 2. Manual prompt via flag
89
+ if manual:
90
+ token_input = click.prompt("Pega tu API Key de LOKTAR (lk_...)", hide_input=True)
91
+ if not token_input or not token_input.strip():
92
+ _handle_error(LoktarError("Token vacío."))
93
+ save_credentials(api_key=token_input.strip(), api_url=api_url)
94
+ click.secho("✓ API Key guardada correctamente en ~/.loktar/credentials.json", fg="green")
95
+ return
96
+
97
+ # 3. Interactive Browser Login
98
+ click.echo(f"Iniciando flujo de autenticación para {api_url}...")
99
+ success, msg = perform_browser_login(api_url)
100
+ if success:
101
+ click.secho(f"✓ {msg}", fg="green")
102
+ else:
103
+ click.secho(f"✗ {msg}", fg="yellow")
104
+ click.echo("Puedes autenticarte manualmente con: loktar login --token lk_tu_token")
105
+
106
+
107
+ @cli.command()
108
+ def logout():
109
+ """Cierra la sesión y elimina las credenciales locales."""
110
+ if clear_credentials():
111
+ click.secho("✓ Sesión cerrada y credenciales eliminadas (~/.loktar/credentials.json).", fg="green")
112
+ else:
113
+ click.echo("No había ninguna sesión activa guardada.")
114
+
115
+
116
+ @cli.command()
117
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
118
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
119
+ @click.argument("query")
120
+ @click.option("--tablet", "-t", help="Filtrar por ID de tablet específica.")
121
+ @click.option("--limit", "-l", default=20, type=int, help="Límite de resultados (default: 20).")
122
+ @click.pass_context
123
+ def search(ctx: click.Context, as_json_local: bool, query: str, tablet: Optional[str], limit: int):
124
+ """Busca herramientas por palabra clave en LOKTAR."""
125
+ client: LoktarClient = ctx.obj["client"]
126
+ as_json = _get_as_json(ctx, as_json_local)
127
+ try:
128
+ results = client.search_tools(query=query, tablet_id=tablet, limit=limit)
129
+ print_search_results(query, results, as_json=as_json)
130
+ except LoktarError as e:
131
+ _handle_error(e)
132
+
133
+
134
+ @cli.group()
135
+ def tablets():
136
+ """Comandos para explorar las tablets de LOKTAR."""
137
+ pass
138
+
139
+
140
+ @tablets.command(name="list")
141
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
142
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
143
+ @click.option("--query", "-q", help="Filtrar tablets por título o categoría.")
144
+ @click.pass_context
145
+ def list_tablets_cmd(ctx: click.Context, as_json_local: bool, query: Optional[str]):
146
+ """Lista los 61+ tablets y categorías disponibles."""
147
+ client: LoktarClient = ctx.obj["client"]
148
+ as_json = _get_as_json(ctx, as_json_local)
149
+ try:
150
+ res = client.list_tablets(query=query)
151
+ print_tablets_list(res, as_json=as_json)
152
+ except LoktarError as e:
153
+ _handle_error(e)
154
+
155
+
156
+ @tablets.command(name="get")
157
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
158
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
159
+ @click.argument("tablet_id")
160
+ @click.option("--limit", "-l", type=int, help="Límite de herramientas a devolver.")
161
+ @click.pass_context
162
+ def get_tablet_cmd(ctx: click.Context, as_json_local: bool, tablet_id: str, limit: Optional[int]):
163
+ """Obtiene el contenido completo de un tablet por su ID (ej. ocr, open-source)."""
164
+ client: LoktarClient = ctx.obj["client"]
165
+ as_json = _get_as_json(ctx, as_json_local)
166
+ try:
167
+ res = client.get_tablet(tablet_id=tablet_id, limit=limit)
168
+ print_tablet_content(res, as_json=as_json)
169
+ except LoktarError as e:
170
+ _handle_error(e)
171
+
172
+
173
+ @cli.group()
174
+ def tools():
175
+ """Comandos para inspeccionar herramientas individuales."""
176
+ pass
177
+
178
+
179
+ @tools.command(name="get")
180
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
181
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
182
+ @click.argument("tool_id")
183
+ @click.pass_context
184
+ def get_tool_cmd(ctx: click.Context, as_json_local: bool, tool_id: str):
185
+ """Obtiene los detalles y comandos recomendados de una herramienta específica."""
186
+ client: LoktarClient = ctx.obj["client"]
187
+ as_json = _get_as_json(ctx, as_json_local)
188
+ try:
189
+ res = client.get_tool_details(tool_id=tool_id)
190
+ print_tool_details(res, as_json=as_json)
191
+ except LoktarError as e:
192
+ _handle_error(e)
193
+
194
+
195
+ @cli.command(name="cli-tools")
196
+ @click.option("--json", "-j", "as_json_local", is_flag=True, default=False,
197
+ help="Salida en JSON bruto (ideal para scripts y agentes).")
198
+ @click.pass_context
199
+ def cli_tools_cmd(ctx: click.Context, as_json_local: bool):
200
+ """Lista las herramientas especializadas de línea de comandos en LOKTAR."""
201
+ client: LoktarClient = ctx.obj["client"]
202
+ as_json = _get_as_json(ctx, as_json_local)
203
+ try:
204
+ res = client.get_cli_tools()
205
+ print_cli_tools(res, as_json=as_json)
206
+ except LoktarError as e:
207
+ _handle_error(e)
208
+
209
+
210
+ @cli.command()
211
+ def guide():
212
+ """Muestra la guía rápida de la CLI optimizada para Agentes de IA."""
213
+ print_agent_guide()
214
+
215
+
216
+ @cli.command()
217
+ @click.pass_context
218
+ def skill(ctx: click.Context):
219
+ """Muestra la SKILL.md integrada del paquete para que un Agente de IA la importe."""
220
+ try:
221
+ skill_path = Path(__file__).parent / "SKILL.md"
222
+ if not skill_path.exists():
223
+ raise FileNotFoundError(f"SKILL.md no encontrada en {skill_path}")
224
+ content = skill_path.read_text(encoding="utf-8")
225
+
226
+ if ctx.obj.get("as_json"):
227
+ print(json.dumps({"skill": content}, ensure_ascii=False))
228
+ else:
229
+ if HAS_RICH and console:
230
+ console.print(Markdown(content))
231
+ else:
232
+ print(content)
233
+ except Exception as e:
234
+ _handle_error(LoktarError(f"No se pudo leer SKILL.md: {e}"))
235
+
236
+
237
+ def main():
238
+ cli(obj={})
239
+
240
+
241
+ if __name__ == "__main__":
242
+ main()