lockthard-cli 0.2.8__tar.gz → 0.2.9__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.
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/PKG-INFO +2 -5
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/README.md +0 -3
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/lockthard_cli.egg-info/PKG-INFO +2 -5
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/lockthard_cli.egg-info/SOURCES.txt +7 -7
- lockthard_cli-0.2.9/lockthard_cli.egg-info/entry_points.txt +2 -0
- lockthard_cli-0.2.9/lockthard_cli.egg-info/top_level.txt +3 -0
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/SKILL.md +2 -4
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/__init__.py +1 -1
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/cli.py +16 -16
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/client.py +19 -19
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/config.py +13 -18
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/pyproject.toml +3 -4
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/tests/test_cli.py +3 -3
- lockthard_cli-0.2.8/lockthard_cli.egg-info/entry_points.txt +0 -3
- lockthard_cli-0.2.8/lockthard_cli.egg-info/top_level.txt +0 -3
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/lockthard_cli.egg-info/dependency_links.txt +0 -0
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/lockthard_cli.egg-info/requires.txt +0 -0
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/auth.py +0 -0
- {lockthard_cli-0.2.8/lockthard_cli → lockthard_cli-0.2.9/loktar_cli}/formatters.py +0 -0
- {lockthard_cli-0.2.8 → lockthard_cli-0.2.9}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockthard-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: CLI and AI agent client for LOKTAR — query 2100+ curated AI tools across 86+ tablets
|
|
5
5
|
Author-email: Loktar Team <info@loktar.cc>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://loktar.cc
|
|
8
|
-
Keywords: cli,ai-tools,agents,mcp,loktar
|
|
8
|
+
Keywords: cli,ai-tools,agents,mcp,loktar
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Environment :: Console
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -26,8 +26,6 @@ A super efficient, scriptable command-line interface and AI agent client for **L
|
|
|
26
26
|
|
|
27
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
28
|
|
|
29
|
-
*(Note: The command `lockthard` is also provided as a fully compatible alias)*
|
|
30
|
-
|
|
31
29
|
## Installation
|
|
32
30
|
|
|
33
31
|
```bash
|
|
@@ -48,7 +46,6 @@ Get your API key from [loktar.cc](https://loktar.cc).
|
|
|
48
46
|
- **For AI Agents & CI/CD:**
|
|
49
47
|
```bash
|
|
50
48
|
export LOKTAR_API_KEY="lk_your_token_here"
|
|
51
|
-
# (LOCKTHARD_API_KEY is also supported as fallback)
|
|
52
49
|
```
|
|
53
50
|
- **Interactive Browser Login (Humans):**
|
|
54
51
|
```bash
|
|
@@ -4,8 +4,6 @@ A super efficient, scriptable command-line interface and AI agent client for **L
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
-
*(Note: The command `lockthard` is also provided as a fully compatible alias)*
|
|
8
|
-
|
|
9
7
|
## Installation
|
|
10
8
|
|
|
11
9
|
```bash
|
|
@@ -26,7 +24,6 @@ Get your API key from [loktar.cc](https://loktar.cc).
|
|
|
26
24
|
- **For AI Agents & CI/CD:**
|
|
27
25
|
```bash
|
|
28
26
|
export LOKTAR_API_KEY="lk_your_token_here"
|
|
29
|
-
# (LOCKTHARD_API_KEY is also supported as fallback)
|
|
30
27
|
```
|
|
31
28
|
- **Interactive Browser Login (Humans):**
|
|
32
29
|
```bash
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockthard-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: CLI and AI agent client for LOKTAR — query 2100+ curated AI tools across 86+ tablets
|
|
5
5
|
Author-email: Loktar Team <info@loktar.cc>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://loktar.cc
|
|
8
|
-
Keywords: cli,ai-tools,agents,mcp,loktar
|
|
8
|
+
Keywords: cli,ai-tools,agents,mcp,loktar
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Environment :: Console
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -26,8 +26,6 @@ A super efficient, scriptable command-line interface and AI agent client for **L
|
|
|
26
26
|
|
|
27
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
28
|
|
|
29
|
-
*(Note: The command `lockthard` is also provided as a fully compatible alias)*
|
|
30
|
-
|
|
31
29
|
## Installation
|
|
32
30
|
|
|
33
31
|
```bash
|
|
@@ -48,7 +46,6 @@ Get your API key from [loktar.cc](https://loktar.cc).
|
|
|
48
46
|
- **For AI Agents & CI/CD:**
|
|
49
47
|
```bash
|
|
50
48
|
export LOKTAR_API_KEY="lk_your_token_here"
|
|
51
|
-
# (LOCKTHARD_API_KEY is also supported as fallback)
|
|
52
49
|
```
|
|
53
50
|
- **Interactive Browser Login (Humans):**
|
|
54
51
|
```bash
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
|
-
lockthard_cli/SKILL.md
|
|
4
|
-
lockthard_cli/__init__.py
|
|
5
|
-
lockthard_cli/auth.py
|
|
6
|
-
lockthard_cli/cli.py
|
|
7
|
-
lockthard_cli/client.py
|
|
8
|
-
lockthard_cli/config.py
|
|
9
|
-
lockthard_cli/formatters.py
|
|
10
3
|
lockthard_cli.egg-info/PKG-INFO
|
|
11
4
|
lockthard_cli.egg-info/SOURCES.txt
|
|
12
5
|
lockthard_cli.egg-info/dependency_links.txt
|
|
13
6
|
lockthard_cli.egg-info/entry_points.txt
|
|
14
7
|
lockthard_cli.egg-info/requires.txt
|
|
15
8
|
lockthard_cli.egg-info/top_level.txt
|
|
9
|
+
loktar_cli/SKILL.md
|
|
10
|
+
loktar_cli/__init__.py
|
|
11
|
+
loktar_cli/auth.py
|
|
12
|
+
loktar_cli/cli.py
|
|
13
|
+
loktar_cli/client.py
|
|
14
|
+
loktar_cli/config.py
|
|
15
|
+
loktar_cli/formatters.py
|
|
16
16
|
tests/test_cli.py
|
|
@@ -4,21 +4,19 @@ description: "LOKTAR CLI — query 2100+ curated AI tools across 86+ tablets. Se
|
|
|
4
4
|
version: 0.2.8
|
|
5
5
|
metadata:
|
|
6
6
|
hermes:
|
|
7
|
-
tags: [loktar,
|
|
7
|
+
tags: [loktar, tools, ai, search, tablets, mcp]
|
|
8
8
|
homepage: https://loktar.cc
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# LOKTAR CLI
|
|
12
12
|
|
|
13
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
|
-
*Nota: El comando `lockthard` se mantiene disponible como alias compatible.*
|
|
15
14
|
|
|
16
15
|
## Autenticación
|
|
17
16
|
|
|
18
17
|
```bash
|
|
19
18
|
# Opción 1: Variable de entorno (recomendado para agentes)
|
|
20
19
|
export LOKTAR_API_KEY="lk_tu_token"
|
|
21
|
-
# (También soporta LOCKTHARD_API_KEY como fallback)
|
|
22
20
|
|
|
23
21
|
# Opción 2: Login con token
|
|
24
22
|
loktar login --token lk_tu_token
|
|
@@ -27,7 +25,7 @@ loktar login --token lk_tu_token
|
|
|
27
25
|
loktar login
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
Token guardado en `~/.loktar/credentials.json`
|
|
28
|
+
Token guardado en `~/.loktar/credentials.json` con permisos 0600.
|
|
31
29
|
|
|
32
30
|
## Flag JSON (`-j` / `--json`)
|
|
33
31
|
|
|
@@ -7,7 +7,7 @@ import click
|
|
|
7
7
|
from . import __version__
|
|
8
8
|
from .config import resolve_token, resolve_api_url, save_credentials, clear_credentials
|
|
9
9
|
from .auth import perform_browser_login
|
|
10
|
-
from .client import
|
|
10
|
+
from .client import LoktarClient, LoktarError
|
|
11
11
|
from .formatters import (
|
|
12
12
|
output_data,
|
|
13
13
|
print_ping_result,
|
|
@@ -53,7 +53,7 @@ Primer uso: loktar skill | Para agentes: loktar -j skill
|
|
|
53
53
|
ctx.obj["url"] = resolved_url
|
|
54
54
|
ctx.obj["token"] = resolved_token
|
|
55
55
|
ctx.obj["as_json"] = as_json
|
|
56
|
-
ctx.obj["client"] =
|
|
56
|
+
ctx.obj["client"] = LoktarClient(base_url=resolved_url, token=resolved_token)
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
@cli.command()
|
|
@@ -62,12 +62,12 @@ Primer uso: loktar skill | Para agentes: loktar -j skill
|
|
|
62
62
|
@click.pass_context
|
|
63
63
|
def ping(ctx: click.Context, as_json_local: bool):
|
|
64
64
|
"""Verifica la conectividad con el servidor de LOKTAR y el estado del token."""
|
|
65
|
-
client:
|
|
65
|
+
client: LoktarClient = ctx.obj["client"]
|
|
66
66
|
as_json = _get_as_json(ctx, as_json_local)
|
|
67
67
|
try:
|
|
68
68
|
res = client.ping()
|
|
69
69
|
print_ping_result(res, as_json=as_json)
|
|
70
|
-
except
|
|
70
|
+
except LoktarError as e:
|
|
71
71
|
_handle_error(e)
|
|
72
72
|
|
|
73
73
|
|
|
@@ -89,7 +89,7 @@ def login(ctx: click.Context, token: Optional[str], manual: bool):
|
|
|
89
89
|
if manual:
|
|
90
90
|
token_input = click.prompt("Pega tu API Key de LOKTAR (lk_...)", hide_input=True)
|
|
91
91
|
if not token_input or not token_input.strip():
|
|
92
|
-
_handle_error(
|
|
92
|
+
_handle_error(LoktarError("Token vacío."))
|
|
93
93
|
save_credentials(api_key=token_input.strip(), api_url=api_url)
|
|
94
94
|
click.secho("✓ API Key guardada correctamente en ~/.loktar/credentials.json", fg="green")
|
|
95
95
|
return
|
|
@@ -122,12 +122,12 @@ def logout():
|
|
|
122
122
|
@click.pass_context
|
|
123
123
|
def search(ctx: click.Context, as_json_local: bool, query: str, tablet: Optional[str], limit: int):
|
|
124
124
|
"""Busca herramientas por palabra clave en LOKTAR."""
|
|
125
|
-
client:
|
|
125
|
+
client: LoktarClient = ctx.obj["client"]
|
|
126
126
|
as_json = _get_as_json(ctx, as_json_local)
|
|
127
127
|
try:
|
|
128
128
|
results = client.search_tools(query=query, tablet_id=tablet, limit=limit)
|
|
129
129
|
print_search_results(query, results, as_json=as_json)
|
|
130
|
-
except
|
|
130
|
+
except LoktarError as e:
|
|
131
131
|
_handle_error(e)
|
|
132
132
|
|
|
133
133
|
|
|
@@ -144,12 +144,12 @@ def tablets():
|
|
|
144
144
|
@click.pass_context
|
|
145
145
|
def list_tablets_cmd(ctx: click.Context, as_json_local: bool, query: Optional[str]):
|
|
146
146
|
"""Lista los 61+ tablets y categorías disponibles."""
|
|
147
|
-
client:
|
|
147
|
+
client: LoktarClient = ctx.obj["client"]
|
|
148
148
|
as_json = _get_as_json(ctx, as_json_local)
|
|
149
149
|
try:
|
|
150
150
|
res = client.list_tablets(query=query)
|
|
151
151
|
print_tablets_list(res, as_json=as_json)
|
|
152
|
-
except
|
|
152
|
+
except LoktarError as e:
|
|
153
153
|
_handle_error(e)
|
|
154
154
|
|
|
155
155
|
|
|
@@ -161,12 +161,12 @@ def list_tablets_cmd(ctx: click.Context, as_json_local: bool, query: Optional[st
|
|
|
161
161
|
@click.pass_context
|
|
162
162
|
def get_tablet_cmd(ctx: click.Context, as_json_local: bool, tablet_id: str, limit: Optional[int]):
|
|
163
163
|
"""Obtiene el contenido completo de un tablet por su ID (ej. ocr, open-source)."""
|
|
164
|
-
client:
|
|
164
|
+
client: LoktarClient = ctx.obj["client"]
|
|
165
165
|
as_json = _get_as_json(ctx, as_json_local)
|
|
166
166
|
try:
|
|
167
167
|
res = client.get_tablet(tablet_id=tablet_id, limit=limit)
|
|
168
168
|
print_tablet_content(res, as_json=as_json)
|
|
169
|
-
except
|
|
169
|
+
except LoktarError as e:
|
|
170
170
|
_handle_error(e)
|
|
171
171
|
|
|
172
172
|
|
|
@@ -183,12 +183,12 @@ def tools():
|
|
|
183
183
|
@click.pass_context
|
|
184
184
|
def get_tool_cmd(ctx: click.Context, as_json_local: bool, tool_id: str):
|
|
185
185
|
"""Obtiene los detalles y comandos recomendados de una herramienta específica."""
|
|
186
|
-
client:
|
|
186
|
+
client: LoktarClient = ctx.obj["client"]
|
|
187
187
|
as_json = _get_as_json(ctx, as_json_local)
|
|
188
188
|
try:
|
|
189
189
|
res = client.get_tool_details(tool_id=tool_id)
|
|
190
190
|
print_tool_details(res, as_json=as_json)
|
|
191
|
-
except
|
|
191
|
+
except LoktarError as e:
|
|
192
192
|
_handle_error(e)
|
|
193
193
|
|
|
194
194
|
|
|
@@ -198,12 +198,12 @@ def get_tool_cmd(ctx: click.Context, as_json_local: bool, tool_id: str):
|
|
|
198
198
|
@click.pass_context
|
|
199
199
|
def cli_tools_cmd(ctx: click.Context, as_json_local: bool):
|
|
200
200
|
"""Lista las herramientas especializadas de línea de comandos en LOKTAR."""
|
|
201
|
-
client:
|
|
201
|
+
client: LoktarClient = ctx.obj["client"]
|
|
202
202
|
as_json = _get_as_json(ctx, as_json_local)
|
|
203
203
|
try:
|
|
204
204
|
res = client.get_cli_tools()
|
|
205
205
|
print_cli_tools(res, as_json=as_json)
|
|
206
|
-
except
|
|
206
|
+
except LoktarError as e:
|
|
207
207
|
_handle_error(e)
|
|
208
208
|
|
|
209
209
|
|
|
@@ -231,7 +231,7 @@ def skill(ctx: click.Context):
|
|
|
231
231
|
else:
|
|
232
232
|
print(content)
|
|
233
233
|
except Exception as e:
|
|
234
|
-
_handle_error(
|
|
234
|
+
_handle_error(LoktarError(f"No se pudo leer SKILL.md: {e}"))
|
|
235
235
|
|
|
236
236
|
|
|
237
237
|
def main():
|
|
@@ -3,13 +3,13 @@ from typing import Any, Dict, List, Optional
|
|
|
3
3
|
import requests
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
7
|
-
"""Base exception for
|
|
6
|
+
class LoktarError(Exception):
|
|
7
|
+
"""Base exception for LOKTAR CLI client errors."""
|
|
8
8
|
pass
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
12
|
-
"""Pure data layer for communicating with the
|
|
11
|
+
class LoktarClient:
|
|
12
|
+
"""Pure data layer for communicating with the LOKTAR API backend."""
|
|
13
13
|
|
|
14
14
|
def __init__(self, base_url: str = "https://loktar.cc", token: Optional[str] = None, timeout: int = 15):
|
|
15
15
|
self.base_url = base_url.rstrip("/")
|
|
@@ -26,22 +26,22 @@ class LockthardClient:
|
|
|
26
26
|
url = f"{self.base_url}{path}"
|
|
27
27
|
resp = self._session.get(url, params=params, timeout=self.timeout)
|
|
28
28
|
if resp.status_code == 401:
|
|
29
|
-
raise
|
|
29
|
+
raise LoktarError("No autorizado: Token inválido o no proporcionado.")
|
|
30
30
|
resp.raise_for_status()
|
|
31
31
|
if not resp.content:
|
|
32
32
|
return {}
|
|
33
33
|
return resp.json()
|
|
34
34
|
except requests.ConnectionError:
|
|
35
|
-
raise
|
|
35
|
+
raise LoktarError(f"No se pudo conectar con el servidor en {self.base_url}")
|
|
36
36
|
except requests.Timeout:
|
|
37
|
-
raise
|
|
37
|
+
raise LoktarError(f"Tiempo de espera agotado ({self.timeout}s) conectando a {self.base_url}")
|
|
38
38
|
except requests.HTTPError as e:
|
|
39
|
-
raise
|
|
39
|
+
raise LoktarError(f"Error HTTP del servidor: {e}")
|
|
40
40
|
except ValueError:
|
|
41
|
-
raise
|
|
41
|
+
raise LoktarError("Respuesta de servidor no válida (JSON inválido).")
|
|
42
42
|
|
|
43
43
|
def _mcp_call(self, method: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
44
|
-
"""Call
|
|
44
|
+
"""Call LOKTAR JSON-RPC MCP endpoint at /api/mcp."""
|
|
45
45
|
self._jsonrpc_id += 1
|
|
46
46
|
payload = {
|
|
47
47
|
"jsonrpc": "2.0",
|
|
@@ -53,12 +53,12 @@ class LockthardClient:
|
|
|
53
53
|
url = f"{self.base_url}/api/mcp"
|
|
54
54
|
resp = self._session.post(url, json=payload, timeout=self.timeout)
|
|
55
55
|
if resp.status_code == 401:
|
|
56
|
-
raise
|
|
56
|
+
raise LoktarError("No autorizado: Token inválido. Inicia sesión con 'loktar login'.")
|
|
57
57
|
resp.raise_for_status()
|
|
58
58
|
data = resp.json()
|
|
59
59
|
if "error" in data:
|
|
60
60
|
err = data["error"]
|
|
61
|
-
raise
|
|
61
|
+
raise LoktarError(f"Error MCP ({err.get('code', 'unknown')}): {err.get('message', '')}")
|
|
62
62
|
|
|
63
63
|
result = data.get("result", {})
|
|
64
64
|
# Decode MCP text content if wrapped
|
|
@@ -72,13 +72,13 @@ class LockthardClient:
|
|
|
72
72
|
return text_val
|
|
73
73
|
return result
|
|
74
74
|
except requests.ConnectionError:
|
|
75
|
-
raise
|
|
75
|
+
raise LoktarError(f"No se pudo conectar con el servidor en {self.base_url}")
|
|
76
76
|
except requests.Timeout:
|
|
77
|
-
raise
|
|
77
|
+
raise LoktarError(f"Tiempo de espera agotado ({self.timeout}s) conectando a {self.base_url}")
|
|
78
78
|
except requests.HTTPError as e:
|
|
79
|
-
raise
|
|
79
|
+
raise LoktarError(f"Error HTTP del servidor: {e}")
|
|
80
80
|
except ValueError:
|
|
81
|
-
raise
|
|
81
|
+
raise LoktarError("Respuesta de servidor no válida (JSON inválido).")
|
|
82
82
|
|
|
83
83
|
def ping(self) -> Dict[str, Any]:
|
|
84
84
|
"""Check API connectivity and auth status."""
|
|
@@ -86,7 +86,7 @@ class LockthardClient:
|
|
|
86
86
|
try:
|
|
87
87
|
data = self._get("/api/mcp/ping")
|
|
88
88
|
return {"status": "ok", "authenticated": True, "details": data}
|
|
89
|
-
except
|
|
89
|
+
except LoktarError as e:
|
|
90
90
|
if "No autorizado" in str(e):
|
|
91
91
|
return {"status": "unauthorized", "authenticated": False, "error": str(e)}
|
|
92
92
|
raise
|
|
@@ -119,7 +119,7 @@ class LockthardClient:
|
|
|
119
119
|
return {"tools": res if isinstance(res, list) else []}
|
|
120
120
|
|
|
121
121
|
def search_tools(self, query: str, tablet_id: Optional[str] = None, limit: int = 20) -> List[Dict[str, Any]]:
|
|
122
|
-
"""Search across all tools in
|
|
122
|
+
"""Search across all tools in LOKTAR directory."""
|
|
123
123
|
args: Dict[str, Any] = {"query": query, "limit": limit}
|
|
124
124
|
if tablet_id:
|
|
125
125
|
args["tablet_id"] = tablet_id
|
|
@@ -158,7 +158,7 @@ class LockthardClient:
|
|
|
158
158
|
return {}
|
|
159
159
|
|
|
160
160
|
def get_cli_tools(self) -> List[Dict[str, Any]]:
|
|
161
|
-
"""Fetch all CLI specific tools from
|
|
161
|
+
"""Fetch all CLI specific tools from LOKTAR."""
|
|
162
162
|
try:
|
|
163
163
|
res = self._mcp_call("tools/call", {
|
|
164
164
|
"name": "get_cli_tools_data",
|
|
@@ -6,17 +6,13 @@ from typing import Dict, Any, Optional
|
|
|
6
6
|
|
|
7
7
|
DEFAULT_API_URL = "https://loktar.cc"
|
|
8
8
|
CONFIG_DIR_NAME = ".loktar"
|
|
9
|
-
FALLBACK_CONFIG_DIR_NAME = ".lockthard"
|
|
10
9
|
CREDENTIALS_FILE_NAME = "credentials.json"
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def get_config_dir() -> Path:
|
|
14
|
-
"""Return the configuration directory path (~/.loktar
|
|
13
|
+
"""Return the configuration directory path (~/.loktar)."""
|
|
15
14
|
home = Path.home()
|
|
16
15
|
primary = home / CONFIG_DIR_NAME
|
|
17
|
-
fallback = home / FALLBACK_CONFIG_DIR_NAME
|
|
18
|
-
if not primary.exists() and fallback.exists():
|
|
19
|
-
return fallback
|
|
20
16
|
primary.mkdir(parents=True, exist_ok=True)
|
|
21
17
|
return primary
|
|
22
18
|
|
|
@@ -69,14 +65,13 @@ def clear_credentials() -> bool:
|
|
|
69
65
|
"""Remove the credentials file on logout."""
|
|
70
66
|
removed = False
|
|
71
67
|
home = Path.home()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pass
|
|
68
|
+
path = home / CONFIG_DIR_NAME / CREDENTIALS_FILE_NAME
|
|
69
|
+
if path.exists():
|
|
70
|
+
try:
|
|
71
|
+
path.unlink()
|
|
72
|
+
removed = True
|
|
73
|
+
except OSError:
|
|
74
|
+
pass
|
|
80
75
|
return removed
|
|
81
76
|
|
|
82
77
|
|
|
@@ -84,13 +79,13 @@ def resolve_token(token_flag: Optional[str] = None) -> Optional[str]:
|
|
|
84
79
|
"""
|
|
85
80
|
Resolve API key token in precedence order:
|
|
86
81
|
1. Direct CLI argument / flag (--token)
|
|
87
|
-
2. Environment variable LOKTAR_API_KEY
|
|
88
|
-
3. Saved credentials (~/.loktar/credentials.json
|
|
82
|
+
2. Environment variable LOKTAR_API_KEY
|
|
83
|
+
3. Saved credentials (~/.loktar/credentials.json)
|
|
89
84
|
"""
|
|
90
85
|
if token_flag and token_flag.strip():
|
|
91
86
|
return token_flag.strip()
|
|
92
87
|
|
|
93
|
-
env_token = os.environ.get("LOKTAR_API_KEY")
|
|
88
|
+
env_token = os.environ.get("LOKTAR_API_KEY")
|
|
94
89
|
if env_token and env_token.strip():
|
|
95
90
|
return env_token.strip()
|
|
96
91
|
|
|
@@ -106,14 +101,14 @@ def resolve_api_url(url_flag: Optional[str] = None) -> str:
|
|
|
106
101
|
"""
|
|
107
102
|
Resolve API Base URL in precedence order:
|
|
108
103
|
1. Direct CLI argument / flag (--url)
|
|
109
|
-
2. Environment variable LOKTAR_API_URL
|
|
104
|
+
2. Environment variable LOKTAR_API_URL
|
|
110
105
|
3. Saved credentials api_url
|
|
111
106
|
4. Default (https://loktar.cc)
|
|
112
107
|
"""
|
|
113
108
|
if url_flag and url_flag.strip():
|
|
114
109
|
return url_flag.strip().rstrip("/")
|
|
115
110
|
|
|
116
|
-
env_url = os.environ.get("LOKTAR_API_URL")
|
|
111
|
+
env_url = os.environ.get("LOKTAR_API_URL")
|
|
117
112
|
if env_url and env_url.strip():
|
|
118
113
|
return env_url.strip().rstrip("/")
|
|
119
114
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lockthard-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.9"
|
|
8
8
|
description = "CLI and AI agent client for LOKTAR — query 2100+ curated AI tools across 86+ tablets"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -12,7 +12,7 @@ requires-python = ">=3.10"
|
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Loktar Team", email = "info@loktar.cc" }
|
|
14
14
|
]
|
|
15
|
-
keywords = ["cli", "ai-tools", "agents", "mcp", "loktar"
|
|
15
|
+
keywords = ["cli", "ai-tools", "agents", "mcp", "loktar"]
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Development Status :: 4 - Beta",
|
|
18
18
|
"Environment :: Console",
|
|
@@ -33,8 +33,7 @@ dependencies = [
|
|
|
33
33
|
Homepage = "https://loktar.cc"
|
|
34
34
|
|
|
35
35
|
[project.scripts]
|
|
36
|
-
loktar = "
|
|
37
|
-
lockthard = "lockthard_cli.cli:main"
|
|
36
|
+
loktar = "loktar_cli.cli:main"
|
|
38
37
|
|
|
39
38
|
[tool.setuptools.packages.find]
|
|
40
39
|
where = ["."]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from click.testing import CliRunner
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
from loktar_cli.cli import cli
|
|
4
|
+
from loktar_cli import __version__
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_cli_version():
|
|
@@ -15,4 +15,4 @@ def test_cli_guide():
|
|
|
15
15
|
runner = CliRunner()
|
|
16
16
|
result = runner.invoke(cli, ["guide"])
|
|
17
17
|
assert result.exit_code == 0
|
|
18
|
-
assert "Guía para Agentes de IA" in result.output or "
|
|
18
|
+
assert "Guía para Agentes de IA" in result.output or "LOKTAR CLI" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|