muapi-cli 0.2.6__tar.gz → 0.2.7__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.
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/.github/workflows/release.yml +2 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/PKG-INFO +1 -1
- muapi_cli-0.2.7/muapi/__init__.py +1 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/auth.py +181 -16
- muapi_cli-0.2.7/muapi/commands/init_cmd.py +62 -0
- muapi_cli-0.2.7/muapi/commands/open_cmd.py +47 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/config.py +17 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/main.py +13 -1
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/npm/package.json +1 -1
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/pyproject.toml +1 -1
- muapi_cli-0.2.6/muapi/__init__.py +0 -1
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/.github/workflows/publish-langchain.yml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/.github/workflows/publish-npm.yml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/.github/workflows/publish-pypi.yml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/.gitignore +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/README.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/cli_entry.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/README.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/docs/providers-muapi.mdx +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/AGENTS.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/README.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/creative_agent.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/pyproject.toml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/skills/generate-asset/SKILL.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/skills/run-skill/SKILL.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/subagents.yaml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/deep_agents_demo.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/__init__.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/_client.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/_registry.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/callbacks.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/data/models.json +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/data/skills.json +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/loader.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/tools.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/pyproject.toml +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/scripts/refresh_registry.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/client.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/__init__.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/account.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/audio.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/config_cmd.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/docs.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/edit.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/enhance.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/image.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/keys.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/mcp_server.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/models.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/predict.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/run.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/upload.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/video.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/commands/workflow.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/dynamic_help.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/exitcodes.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/schema_introspect.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/muapi/utils.py +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/npm/README.md +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/npm/bin/muapi +0 -0
- {muapi_cli-0.2.6 → muapi_cli-0.2.7}/npm/scripts/install.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.7"
|
|
@@ -1,16 +1,121 @@
|
|
|
1
1
|
"""muapi auth — configure API key and inspect identity."""
|
|
2
|
-
import
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import webbrowser
|
|
7
|
+
|
|
3
8
|
import httpx
|
|
4
|
-
|
|
9
|
+
import typer
|
|
10
|
+
from rich.prompt import Confirm, Prompt
|
|
5
11
|
|
|
6
|
-
from ..config import delete_api_key, get_api_key,
|
|
12
|
+
from ..config import BASE_URL, _CONFIG_FILE, delete_api_key, get_api_key, get_key_info, save_api_key
|
|
7
13
|
from .. import exitcodes
|
|
8
14
|
from ..utils import console, error_exit, out
|
|
9
15
|
|
|
10
16
|
app = typer.Typer(help="Manage authentication and API key.")
|
|
11
17
|
|
|
12
|
-
# Auth endpoints live at the root host, not under /api/v1
|
|
13
18
|
_AUTH_BASE = BASE_URL.replace("/api/v1", "")
|
|
19
|
+
_ACCESS_KEYS_URL = "https://muapi.ai/access-keys"
|
|
20
|
+
|
|
21
|
+
LINKS = {
|
|
22
|
+
"dashboard": "https://muapi.ai/dashboard",
|
|
23
|
+
"access-keys": _ACCESS_KEYS_URL,
|
|
24
|
+
"models": "https://muapi.ai/models",
|
|
25
|
+
"docs": "https://muapi.ai/docs",
|
|
26
|
+
"pricing": "https://muapi.ai/pricing",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _mask(key: str) -> str:
|
|
31
|
+
if len(key) < 12:
|
|
32
|
+
return "••••"
|
|
33
|
+
return key[:8] + "…" + key[-4:]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _looks_like_key(s: str) -> bool:
|
|
37
|
+
s = s.strip()
|
|
38
|
+
return bool(re.match(r'^[A-Za-z0-9_\-]{20,}$', s) and '\n' not in s)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _read_clipboard() -> str | None:
|
|
42
|
+
try:
|
|
43
|
+
if sys.platform == "darwin":
|
|
44
|
+
r = subprocess.run(["pbpaste"], capture_output=True, text=True, timeout=2)
|
|
45
|
+
return r.stdout.strip() or None
|
|
46
|
+
if sys.platform.startswith("linux"):
|
|
47
|
+
for cmd in (["xclip", "-o"], ["xsel", "--clipboard", "--output"]):
|
|
48
|
+
try:
|
|
49
|
+
r = subprocess.run(cmd, capture_output=True, text=True, timeout=2)
|
|
50
|
+
if r.returncode == 0:
|
|
51
|
+
return r.stdout.strip() or None
|
|
52
|
+
except FileNotFoundError:
|
|
53
|
+
continue
|
|
54
|
+
if sys.platform == "win32":
|
|
55
|
+
r = subprocess.run(
|
|
56
|
+
["powershell", "-command", "Get-Clipboard"],
|
|
57
|
+
capture_output=True, text=True, timeout=2,
|
|
58
|
+
)
|
|
59
|
+
return r.stdout.strip() or None
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _validate_key(api_key: str) -> tuple[bool, str]:
|
|
66
|
+
"""Validate key against the live API. Returns (ok, error_msg)."""
|
|
67
|
+
try:
|
|
68
|
+
resp = httpx.get(
|
|
69
|
+
f"{BASE_URL}/account/balance",
|
|
70
|
+
headers={"x-api-key": api_key},
|
|
71
|
+
timeout=15.0,
|
|
72
|
+
)
|
|
73
|
+
if resp.status_code in (401, 403):
|
|
74
|
+
return False, "API rejected the key (401/403)."
|
|
75
|
+
if resp.status_code >= 400:
|
|
76
|
+
return False, f"API returned {resp.status_code}."
|
|
77
|
+
return True, ""
|
|
78
|
+
except httpx.RequestError as e:
|
|
79
|
+
return False, f"Could not reach {BASE_URL}: {e}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _find_project_config() -> str | None:
|
|
83
|
+
"""Walk up from CWD looking for muapi.json."""
|
|
84
|
+
from pathlib import Path
|
|
85
|
+
d = Path.cwd()
|
|
86
|
+
while True:
|
|
87
|
+
candidate = d / "muapi.json"
|
|
88
|
+
if candidate.exists():
|
|
89
|
+
return str(candidate)
|
|
90
|
+
parent = d.parent
|
|
91
|
+
if parent == d:
|
|
92
|
+
return None
|
|
93
|
+
d = parent
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _do_save(api_key: str) -> None:
|
|
97
|
+
with console.status("[dim]Validating with api.muapi.ai…[/dim]"):
|
|
98
|
+
ok, reason = _validate_key(api_key)
|
|
99
|
+
|
|
100
|
+
if not ok:
|
|
101
|
+
console.print(f"[bold red]✖[/bold red] {reason}")
|
|
102
|
+
console.print()
|
|
103
|
+
console.print(f"[dim] Double-check at [/dim][cyan]{_ACCESS_KEYS_URL}[/cyan]")
|
|
104
|
+
console.print("[dim] Or set [/dim][cyan]MUAPI_API_KEY[/cyan][dim] in your shell and skip this step.[/dim]\n")
|
|
105
|
+
raise typer.Exit(exitcodes.AUTH_ERROR)
|
|
106
|
+
|
|
107
|
+
location = save_api_key(api_key)
|
|
108
|
+
location_display = "OS keychain" if location == "keychain" else str(_CONFIG_FILE)
|
|
109
|
+
console.print("[bold green]✔[/bold green] Signed in.")
|
|
110
|
+
console.print()
|
|
111
|
+
console.print(f" [dim]Key: [/dim][green]{_mask(api_key)}[/green]")
|
|
112
|
+
console.print(f" [dim]Stored: [/dim][cyan]{location_display}[/cyan]")
|
|
113
|
+
console.print()
|
|
114
|
+
console.print("[bold]Try it:[/bold]")
|
|
115
|
+
console.print(" [cyan]muapi account balance[/cyan]")
|
|
116
|
+
console.print(" [cyan]muapi image generate -p \"a cyberpunk skyline at golden hour\"[/cyan]")
|
|
117
|
+
console.print(" [cyan]muapi video generate -p \"drone shot over snowy peaks\" --model kling-master[/cyan]")
|
|
118
|
+
console.print()
|
|
14
119
|
|
|
15
120
|
|
|
16
121
|
@app.command("login")
|
|
@@ -165,25 +270,85 @@ def reset_password(
|
|
|
165
270
|
|
|
166
271
|
@app.command("configure")
|
|
167
272
|
def configure(
|
|
168
|
-
api_key: str = typer.Option(None, "--api-key", "-k", help="API key (
|
|
273
|
+
api_key: str = typer.Option(None, "--api-key", "-k", help="API key (skips all prompts)"),
|
|
274
|
+
no_browser: bool = typer.Option(False, "--no-browser", help="Skip opening the access-keys page"),
|
|
169
275
|
):
|
|
170
|
-
"""Save your muapi API key
|
|
276
|
+
"""Save your muapi API key — opens browser, detects clipboard, validates before saving."""
|
|
277
|
+
if api_key:
|
|
278
|
+
_do_save(api_key.strip())
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
console.print()
|
|
282
|
+
console.print("[bold magenta] Welcome to muapi.[/bold magenta]")
|
|
283
|
+
console.print("[dim] Sign in once and you're set on this machine.[/dim]\n")
|
|
284
|
+
|
|
285
|
+
if not no_browser:
|
|
286
|
+
console.print(f"[bold] 1.[/bold] Opening [cyan]{_ACCESS_KEYS_URL}[/cyan]")
|
|
287
|
+
try:
|
|
288
|
+
webbrowser.open(_ACCESS_KEYS_URL)
|
|
289
|
+
except Exception:
|
|
290
|
+
console.print("[dim] (browser launch failed — open the link manually)[/dim]")
|
|
291
|
+
console.print("[bold] 2.[/bold] Copy your API key")
|
|
292
|
+
console.print("[bold] 3.[/bold] Paste below — we'll validate it automatically\n")
|
|
293
|
+
|
|
294
|
+
# Clipboard detection
|
|
295
|
+
detected: str | None = None
|
|
296
|
+
clip = _read_clipboard()
|
|
297
|
+
if clip and _looks_like_key(clip):
|
|
298
|
+
detected = clip
|
|
299
|
+
|
|
300
|
+
if detected:
|
|
301
|
+
use_it = Confirm.ask(
|
|
302
|
+
f" Detected a key on your clipboard ({_mask(detected)}). Use it?",
|
|
303
|
+
default=True,
|
|
304
|
+
console=console,
|
|
305
|
+
)
|
|
306
|
+
if use_it:
|
|
307
|
+
api_key = detected
|
|
308
|
+
|
|
171
309
|
if not api_key:
|
|
172
|
-
api_key = Prompt.ask("[bold]
|
|
310
|
+
api_key = Prompt.ask("[bold] Paste your API key[/bold]", password=True, console=console)
|
|
311
|
+
api_key = api_key.strip()
|
|
312
|
+
|
|
173
313
|
if not api_key:
|
|
174
|
-
error_exit("No API key provided.")
|
|
175
|
-
|
|
176
|
-
|
|
314
|
+
error_exit("No API key provided.", exitcodes.AUTH_ERROR)
|
|
315
|
+
|
|
316
|
+
_do_save(api_key)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@app.command("status")
|
|
320
|
+
def status():
|
|
321
|
+
"""Show the active API key, config location, base URL, and quick links."""
|
|
322
|
+
key, source = get_key_info()
|
|
323
|
+
|
|
324
|
+
console.print()
|
|
325
|
+
console.print("[bold]muapi CLI status[/bold]")
|
|
326
|
+
if key:
|
|
327
|
+
console.print(f" [dim]API key: [/dim][green]{_mask(key)}[/green]")
|
|
328
|
+
else:
|
|
329
|
+
console.print(f" [dim]API key: [/dim][red]not set — run [bold]muapi auth configure[/bold][/red]")
|
|
330
|
+
console.print(f" [dim]Source: [/dim][cyan]{source}[/cyan]")
|
|
331
|
+
console.print(f" [dim]Base URL: [/dim][cyan]{BASE_URL}[/cyan]")
|
|
332
|
+
console.print(f" [dim]Config: [/dim][cyan]{_CONFIG_FILE}[/cyan]")
|
|
333
|
+
|
|
334
|
+
project_file = _find_project_config()
|
|
335
|
+
if project_file:
|
|
336
|
+
console.print(f" [dim]Project: [/dim][cyan]{project_file}[/cyan] [dim](muapi.json detected)[/dim]")
|
|
337
|
+
|
|
338
|
+
console.print()
|
|
339
|
+
console.print("[bold]Useful links[/bold]")
|
|
340
|
+
width = max(len(k) for k in LINKS)
|
|
341
|
+
for name, url in LINKS.items():
|
|
342
|
+
console.print(f" [dim]{name.ljust(width)}[/dim] [cyan]{url}[/cyan]")
|
|
343
|
+
console.print()
|
|
344
|
+
console.print("[dim]Jump in your browser: [/dim][cyan]muapi open <target>[/cyan]")
|
|
345
|
+
console.print()
|
|
177
346
|
|
|
178
347
|
|
|
179
348
|
@app.command("whoami")
|
|
180
349
|
def whoami():
|
|
181
|
-
"""
|
|
182
|
-
|
|
183
|
-
if not key:
|
|
184
|
-
error_exit("No API key configured. Run: muapi auth configure", exitcodes.AUTH_ERROR)
|
|
185
|
-
masked = key[:8] + "..." + key[-4:]
|
|
186
|
-
out.print(f"API key: [bold]{masked}[/bold]")
|
|
350
|
+
"""Alias for [bold]muapi auth status[/bold]."""
|
|
351
|
+
status()
|
|
187
352
|
|
|
188
353
|
|
|
189
354
|
@app.command("logout")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""muapi init — create a muapi.json project config in the current directory."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..utils import console, error_exit
|
|
8
|
+
|
|
9
|
+
_PROJECT_FILE = "muapi.json"
|
|
10
|
+
_SCHEMA_URL = "https://muapi.ai/schema/cli.json"
|
|
11
|
+
|
|
12
|
+
_DEFAULT_CONFIG = {
|
|
13
|
+
"$schema": _SCHEMA_URL,
|
|
14
|
+
"defaultModel": "flux-dev-image",
|
|
15
|
+
"outputDir": "muapi-output",
|
|
16
|
+
"aliases": {},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def init(
|
|
21
|
+
yes: bool = typer.Option(False, "-y", "--yes", help="Skip prompts and write defaults"),
|
|
22
|
+
force: bool = typer.Option(False, "-f", "--force", help="Overwrite an existing muapi.json"),
|
|
23
|
+
):
|
|
24
|
+
"""Create a muapi.json project config with a defaultModel and alias stubs.
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
Examples:
|
|
28
|
+
muapi init # interactive
|
|
29
|
+
muapi init -y # write defaults silently
|
|
30
|
+
muapi init -y -f # overwrite existing
|
|
31
|
+
"""
|
|
32
|
+
target = Path.cwd() / _PROJECT_FILE
|
|
33
|
+
|
|
34
|
+
if target.exists() and not force:
|
|
35
|
+
error_exit(
|
|
36
|
+
f"{_PROJECT_FILE} already exists. Use --force to overwrite.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
config = dict(_DEFAULT_CONFIG)
|
|
40
|
+
|
|
41
|
+
if not yes:
|
|
42
|
+
from rich.prompt import Prompt
|
|
43
|
+
default_model = Prompt.ask(
|
|
44
|
+
"Default model",
|
|
45
|
+
default=config["defaultModel"],
|
|
46
|
+
console=console,
|
|
47
|
+
)
|
|
48
|
+
output_dir = Prompt.ask(
|
|
49
|
+
"Output directory",
|
|
50
|
+
default=config["outputDir"],
|
|
51
|
+
console=console,
|
|
52
|
+
)
|
|
53
|
+
config["defaultModel"] = default_model
|
|
54
|
+
config["outputDir"] = output_dir
|
|
55
|
+
|
|
56
|
+
target.write_text(json.dumps(config, indent=2) + "\n")
|
|
57
|
+
console.print(f"[green]Wrote {_PROJECT_FILE}[/green]")
|
|
58
|
+
console.print()
|
|
59
|
+
console.print("Now try:")
|
|
60
|
+
console.print(f" [cyan]muapi run -p \"a serene mountain lake at sunrise\"[/cyan]")
|
|
61
|
+
console.print(f" Add aliases by editing [bold]{_PROJECT_FILE}[/bold]")
|
|
62
|
+
console.print()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""muapi open — open muapi.ai pages in your browser."""
|
|
2
|
+
import webbrowser
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..utils import console, error_exit
|
|
8
|
+
|
|
9
|
+
_TARGETS: dict[str, str] = {
|
|
10
|
+
"dashboard": "https://muapi.ai/dashboard",
|
|
11
|
+
"access-keys": "https://muapi.ai/access-keys",
|
|
12
|
+
"models": "https://muapi.ai/models",
|
|
13
|
+
"docs": "https://muapi.ai/docs",
|
|
14
|
+
"pricing": "https://muapi.ai/pricing",
|
|
15
|
+
"api": "https://api.muapi.ai/docs",
|
|
16
|
+
"discord": "https://discord.gg/muapi",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def open_page(
|
|
21
|
+
target: Optional[str] = typer.Argument(
|
|
22
|
+
None,
|
|
23
|
+
help="Page to open: " + ", ".join(_TARGETS.keys()),
|
|
24
|
+
),
|
|
25
|
+
):
|
|
26
|
+
"""Open a muapi.ai page in your browser.
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
Examples:
|
|
30
|
+
muapi open # opens dashboard
|
|
31
|
+
muapi open access-keys # key management
|
|
32
|
+
muapi open models # model catalog
|
|
33
|
+
muapi open docs # API docs
|
|
34
|
+
"""
|
|
35
|
+
if not target:
|
|
36
|
+
target = "dashboard"
|
|
37
|
+
|
|
38
|
+
url = _TARGETS.get(target.lower())
|
|
39
|
+
if not url:
|
|
40
|
+
valid = ", ".join(_TARGETS.keys())
|
|
41
|
+
error_exit(f"Unknown target '{target}'. Valid: {valid}")
|
|
42
|
+
|
|
43
|
+
console.print(f"Opening [cyan]{url}[/cyan]")
|
|
44
|
+
try:
|
|
45
|
+
webbrowser.open(url)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
error_exit(f"Could not open browser: {e}")
|
|
@@ -96,6 +96,23 @@ def get_all_settings() -> dict:
|
|
|
96
96
|
return {}
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
def get_key_info() -> tuple[Optional[str], str]:
|
|
100
|
+
"""Return (api_key, source_description) for display in status/whoami."""
|
|
101
|
+
if key := os.environ.get("MUAPI_API_KEY"):
|
|
102
|
+
return key, "env:MUAPI_API_KEY"
|
|
103
|
+
ok, val = _try_keyring()
|
|
104
|
+
if ok and val:
|
|
105
|
+
return val, "keychain"
|
|
106
|
+
if _CONFIG_FILE.exists():
|
|
107
|
+
try:
|
|
108
|
+
data = json.loads(_CONFIG_FILE.read_text())
|
|
109
|
+
if key := data.get("api_key"):
|
|
110
|
+
return key, f"file:{_CONFIG_FILE}"
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return None, "not set"
|
|
114
|
+
|
|
115
|
+
|
|
99
116
|
def delete_api_key() -> None:
|
|
100
117
|
ok, _ = _try_keyring()
|
|
101
118
|
if ok:
|
|
@@ -5,7 +5,7 @@ import typer
|
|
|
5
5
|
from rich import print as rprint
|
|
6
6
|
|
|
7
7
|
from . import __version__
|
|
8
|
-
from .commands import auth, account, audio, config_cmd, docs, edit, enhance, image, keys, models, predict, run, upload, video, workflow
|
|
8
|
+
from .commands import auth, account, audio, config_cmd, docs, edit, enhance, image, init_cmd, keys, models, open_cmd, predict, run, upload, video, workflow
|
|
9
9
|
from .commands import mcp_server
|
|
10
10
|
from .dynamic_help import maybe_handle_run_help
|
|
11
11
|
|
|
@@ -44,6 +44,18 @@ app.add_typer(config_cmd.app, name="config", help="Get and set persistent CLI
|
|
|
44
44
|
app.add_typer(docs.app, name="docs", help="Access the muapi.ai API documentation.")
|
|
45
45
|
app.add_typer(mcp_server.app, name="mcp", help="Run as an MCP server for AI agent integration.")
|
|
46
46
|
|
|
47
|
+
app.command(
|
|
48
|
+
"init",
|
|
49
|
+
help="Create a muapi.json project config with defaultModel and alias stubs.",
|
|
50
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
51
|
+
)(init_cmd.init)
|
|
52
|
+
|
|
53
|
+
app.command(
|
|
54
|
+
"open",
|
|
55
|
+
help="Open a muapi.ai page in your browser (dashboard, models, docs, access-keys…).",
|
|
56
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
57
|
+
)(open_cmd.open_page)
|
|
58
|
+
|
|
47
59
|
|
|
48
60
|
@app.command("version")
|
|
49
61
|
def version(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/AGENTS.md
RENAMED
|
File without changes
|
{muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/README.md
RENAMED
|
File without changes
|
{muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/creative_agent.py
RENAMED
|
File without changes
|
{muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{muapi_cli-0.2.6 → muapi_cli-0.2.7}/integrations/langchain/examples/creative-agent/subagents.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|