muapi-cli 0.2.5__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.
Files changed (62) hide show
  1. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/.github/workflows/release.yml +2 -0
  2. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/PKG-INFO +6 -1
  3. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/README.md +5 -0
  4. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/AGENTS.md +37 -0
  5. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/README.md +81 -0
  6. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/creative_agent.py +203 -0
  7. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/pyproject.toml +15 -0
  8. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/skills/generate-asset/SKILL.md +37 -0
  9. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/skills/run-skill/SKILL.md +30 -0
  10. muapi_cli-0.2.7/integrations/langchain/examples/creative-agent/subagents.yaml +28 -0
  11. muapi_cli-0.2.7/muapi/__init__.py +1 -0
  12. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/auth.py +181 -16
  13. muapi_cli-0.2.7/muapi/commands/init_cmd.py +62 -0
  14. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/mcp_server.py +25 -15
  15. muapi_cli-0.2.7/muapi/commands/open_cmd.py +47 -0
  16. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/config.py +17 -0
  17. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/main.py +13 -1
  18. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/npm/package.json +1 -1
  19. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/npm/scripts/install.js +2 -1
  20. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/pyproject.toml +1 -1
  21. muapi_cli-0.2.5/.claude/settings.local.json +0 -24
  22. muapi_cli-0.2.5/muapi/__init__.py +0 -1
  23. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/.github/workflows/publish-langchain.yml +0 -0
  24. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/.github/workflows/publish-npm.yml +0 -0
  25. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/.github/workflows/publish-pypi.yml +0 -0
  26. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/.gitignore +0 -0
  27. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/cli_entry.py +0 -0
  28. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/README.md +0 -0
  29. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/docs/providers-muapi.mdx +0 -0
  30. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/examples/deep_agents_demo.py +0 -0
  31. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/__init__.py +0 -0
  32. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/_client.py +0 -0
  33. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/_registry.py +0 -0
  34. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/callbacks.py +0 -0
  35. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/data/models.json +0 -0
  36. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/data/skills.json +0 -0
  37. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/loader.py +0 -0
  38. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/muapi_langchain/tools.py +0 -0
  39. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/pyproject.toml +0 -0
  40. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/integrations/langchain/scripts/refresh_registry.py +0 -0
  41. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/client.py +0 -0
  42. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/__init__.py +0 -0
  43. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/account.py +0 -0
  44. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/audio.py +0 -0
  45. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/config_cmd.py +0 -0
  46. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/docs.py +0 -0
  47. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/edit.py +0 -0
  48. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/enhance.py +0 -0
  49. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/image.py +0 -0
  50. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/keys.py +0 -0
  51. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/models.py +0 -0
  52. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/predict.py +0 -0
  53. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/run.py +0 -0
  54. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/upload.py +0 -0
  55. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/video.py +0 -0
  56. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/commands/workflow.py +0 -0
  57. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/dynamic_help.py +0 -0
  58. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/exitcodes.py +0 -0
  59. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/schema_introspect.py +0 -0
  60. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/muapi/utils.py +0 -0
  61. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/npm/README.md +0 -0
  62. {muapi_cli-0.2.5 → muapi_cli-0.2.7}/npm/bin/muapi +0 -0
@@ -153,6 +153,8 @@ jobs:
153
153
  name: Publish to PyPI
154
154
  needs: build
155
155
  runs-on: ubuntu-latest
156
+ permissions:
157
+ id-token: write
156
158
  steps:
157
159
  - uses: actions/checkout@v4
158
160
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: muapi-cli
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Official CLI for muapi.ai — generative media at your fingertips
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -22,6 +22,11 @@ Official command-line interface for [muapi.ai](https://muapi.ai) — generate im
22
22
 
23
23
  **Agent-first design** — every command works for both humans (colored output, tables) and AI agents (`--output-json`, `--jq` filtering, semantic exit codes, MCP server mode).
24
24
 
25
+ ## Related Projects
26
+
27
+ - [Open-Generative-AI](https://github.com/Anil-matcha/Open-Generative-AI) — Browser-based GUI for the same models — no CLI required
28
+ - [Awesome-GPT-Image-2-API-Prompts](https://github.com/Anil-matcha/Awesome-GPT-Image-2-API-Prompts) — Curated prompt library to run via this CLI
29
+
25
30
  ## Install
26
31
 
27
32
  ```bash
@@ -4,6 +4,11 @@ Official command-line interface for [muapi.ai](https://muapi.ai) — generate im
4
4
 
5
5
  **Agent-first design** — every command works for both humans (colored output, tables) and AI agents (`--output-json`, `--jq` filtering, semantic exit codes, MCP server mode).
6
6
 
7
+ ## Related Projects
8
+
9
+ - [Open-Generative-AI](https://github.com/Anil-matcha/Open-Generative-AI) — Browser-based GUI for the same models — no CLI required
10
+ - [Awesome-GPT-Image-2-API-Prompts](https://github.com/Anil-matcha/Awesome-GPT-Image-2-API-Prompts) — Curated prompt library to run via this CLI
11
+
7
12
  ## Install
8
13
 
9
14
  ```bash
@@ -0,0 +1,37 @@
1
+ # MuAPI Creative Agent
2
+
3
+ You are a creative-media AI agent powered by [muapi.ai](https://muapi.ai) — a unified API for 390+ generative media models. You can create images, videos, audio, 3D assets, and apply effects, enhancements, and transformations.
4
+
5
+ ## Your Capabilities
6
+
7
+ | Kind | What you can do |
8
+ |------|----------------|
9
+ | **Image** | Generate from text (Flux, HiDream, Midjourney, GPT-4o, DALL-E, Ideogram…) |
10
+ | **Image edit** | Edit, inpaint, style transfer, background removal, upscale, colorize |
11
+ | **Video** | Text-to-video (Veo 3, Kling, Sora, Seedance, Runway, Pika…) |
12
+ | **Image-to-video** | Animate a still image |
13
+ | **Video edit** | Effects, lipsync, face swap, dance, dress change |
14
+ | **Audio** | Music generation (Suno), sound effects (MMAudio) |
15
+ | **3D** | Image or text to 3D model (Tripo3D, Meshy) |
16
+
17
+ ## Tool Usage Guidelines
18
+
19
+ Always follow this decision tree:
20
+
21
+ 1. **Don't know which model/skill fits?** → `muapi_select` first (free, instant)
22
+ 2. **Single asset, clear prompt?** → `muapi_generate` directly
23
+ 3. **Matches a named multi-step recipe?** → delegate to `creative-specialist` → `muapi_run_skill`
24
+ 4. **Open-ended multi-asset brief?** → delegate to `creative-specialist` → `muapi_creative_agent`
25
+
26
+ ## Quality Rules
27
+
28
+ - Always call `muapi_select` before generating if the user hasn't specified a model — it surfaces the best fit for cost and quality
29
+ - For edits and enhancements, pass `input_asset_url` to `muapi_generate`
30
+ - The `tier` parameter controls quality vs. speed: `"best"` for hero assets, `"balanced"` for iterating, `"fast"` for previews
31
+ - Report asset URLs to the user in every response — they're the deliverable
32
+
33
+ ## What to Avoid
34
+
35
+ - Don't guess model names — use `muapi_select` to discover them
36
+ - Don't call `muapi_creative_agent` without `interrupt_on` approval — it can spend many credits
37
+ - Don't chain multiple `muapi_generate` calls when a skill covers the use case better
@@ -0,0 +1,81 @@
1
+ # MuAPI Creative Agent
2
+
3
+ A generative-media Deep Agent powered by [muapi.ai](https://muapi.ai) — 390+ models for images, video, audio, 3D, and more, exposed as LangChain tools and wired into a Deep Agent with a planner/specialist split.
4
+
5
+ **This example demonstrates how to build a media-generation agent through three filesystem primitives:**
6
+ - **Memory** (`AGENTS.md`) — persistent context: available models, decision tree, quality rules
7
+ - **Skills** (`skills/*/SKILL.md`) — loaded-on-demand workflows for generation and named recipes
8
+ - **Subagents** (`subagents.yaml`) — a `creative-specialist` for heavy multi-step work
9
+
10
+ ## Quick Start
11
+
12
+ ```bash
13
+ # Set API keys
14
+ export MUAPI_API_KEY="..." # Get one at muapi.ai
15
+ export ANTHROPIC_API_KEY="..."
16
+
17
+ # Run (uv installs dependencies automatically)
18
+ cd examples/creative-agent
19
+ uv run python creative_agent.py "Generate a cinematic product photo of sneakers"
20
+
21
+ # With a budget cap
22
+ uv run python creative_agent.py --budget 200 "Animate my product image into a 5s video"
23
+
24
+ # Multi-step recipe
25
+ uv run python creative_agent.py "Make a 3-shot Instagram carousel for SunFizz mango water"
26
+ ```
27
+
28
+ ## How It Works
29
+
30
+ ```
31
+ User brief
32
+
33
+ ├─ AGENTS.md Loaded at startup — tells the agent what muapi can do
34
+ ├─ skills/ Loaded on demand — step-by-step tool usage guides
35
+
36
+
37
+ Planner (Claude Sonnet)
38
+ ├─ muapi_select Discover best model/skill for the brief (free)
39
+ ├─ muapi_generate Single-shot generation: image / video / audio / 3D
40
+
41
+ └─ task ──────────────► creative-specialist subagent
42
+ ├─ muapi_run_skill Named multi-step recipe
43
+ └─ muapi_creative_agent Open-ended brief (HITL-gated)
44
+ ```
45
+
46
+ `MuapiCostCallback` tracks credit spend in real time and aborts the run if the budget cap is hit.
47
+
48
+ ## Tool Capability Gradient
49
+
50
+ | Tool | Tier | What it does | Credits |
51
+ |------|------|-------------|---------|
52
+ | `muapi_select` | Planner | Rank models + skills for an intent | Free |
53
+ | `muapi_generate` | Planner | Generate one asset (any modality) | Per call |
54
+ | `muapi_run_skill` | Specialist | Run a named multi-step recipe | Per recipe |
55
+ | `muapi_creative_agent` | Specialist | Hand a full brief to muapi's planner (HITL-gated) | Variable |
56
+
57
+ ## File Structure
58
+
59
+ ```
60
+ creative-agent/
61
+ ├── AGENTS.md # Persistent agent context
62
+ ├── creative_agent.py # Main entry point
63
+ ├── subagents.yaml # creative-specialist definition
64
+ ├── pyproject.toml
65
+ └── skills/
66
+ ├── generate-asset/SKILL.md # Single-asset generation workflow
67
+ └── run-skill/SKILL.md # Named recipe delegation workflow
68
+ ```
69
+
70
+ ## Environment Variables
71
+
72
+ | Variable | Required | Notes |
73
+ |----------|----------|-------|
74
+ | `MUAPI_API_KEY` | Yes | Get at [muapi.ai](https://muapi.ai) or run `muapi auth configure` |
75
+ | `ANTHROPIC_API_KEY` | Yes | Powers the planner (Claude Sonnet) |
76
+
77
+ ## Related
78
+
79
+ - [muapi-langchain on PyPI](https://pypi.org/project/muapi-langchain/)
80
+ - [muapi.ai](https://muapi.ai) — API docs and model catalog
81
+ - [muapi CLI](https://github.com/SamurAIGPT/muapi-cli) — same auth, same client
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MuAPI Creative Agent
4
+
5
+ A generative-media Deep Agent configured through filesystem primitives:
6
+ - AGENTS.md — persistent context: capabilities, decision tree, quality rules
7
+ - skills/ — loaded-on-demand workflows (generate-asset, run-skill)
8
+ - subagents.yaml — creative-specialist subagent for heavy multi-step work
9
+ - MuapiCostCallback — real-time credit tracking with optional budget cap
10
+
11
+ Usage:
12
+ export MUAPI_API_KEY="..." # or: muapi auth configure
13
+ export ANTHROPIC_API_KEY="..."
14
+ uv run python creative_agent.py "Generate a cinematic product photo of a sneaker"
15
+ uv run python creative_agent.py "Make a 3-shot Instagram carousel for SunFizz mango water"
16
+ uv run python creative_agent.py --budget 200 "Animate my logo"
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ import yaml
26
+ from langchain_anthropic import ChatAnthropic
27
+ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
28
+ from rich.console import Console
29
+ from rich.live import Live
30
+ from rich.markdown import Markdown
31
+ from rich.panel import Panel
32
+ from rich.spinner import Spinner
33
+
34
+ from deepagents import create_deep_agent
35
+ from muapi_langchain import (
36
+ PLANNER_TOOLS,
37
+ SPECIALIST_TOOLS,
38
+ MuapiCostCallback,
39
+ )
40
+
41
+ EXAMPLE_DIR = Path(__file__).parent
42
+ console = Console()
43
+
44
+
45
+ def load_subagents(config_path: Path) -> list[dict]:
46
+ """Load subagent definitions from YAML and wire up muapi specialist tools."""
47
+ tool_map = {t.name: t for t in SPECIALIST_TOOLS}
48
+
49
+ with open(config_path) as f:
50
+ config = yaml.safe_load(f)
51
+
52
+ subagents = []
53
+ for name, spec in config.items():
54
+ subagent: dict[str, Any] = {
55
+ "name": name,
56
+ "description": spec["description"],
57
+ "system_prompt": spec["system_prompt"],
58
+ }
59
+ if "model" in spec:
60
+ subagent["model"] = spec["model"]
61
+ if "tools" in spec:
62
+ subagent["tools"] = [tool_map[t] for t in spec["tools"] if t in tool_map]
63
+ subagents.append(subagent)
64
+ return subagents
65
+
66
+
67
+ def create_creative_agent(budget_credits: int = 500):
68
+ cost_cb = MuapiCostCallback(
69
+ budget_credits=budget_credits,
70
+ on_event=lambda evt, payload: console.print(
71
+ f" [dim][{evt}] credits: {payload.get('credits', 0)} "
72
+ f"(total: {payload.get('running_total', 0)})[/]"
73
+ ),
74
+ )
75
+ agent = create_deep_agent(
76
+ model=ChatAnthropic(model="claude-sonnet-4-6"),
77
+ memory=[str(EXAMPLE_DIR / "AGENTS.md")],
78
+ skills=[str(EXAMPLE_DIR / "skills/")],
79
+ tools=PLANNER_TOOLS,
80
+ subagents=load_subagents(EXAMPLE_DIR / "subagents.yaml"),
81
+ interrupt_on={
82
+ "muapi_creative_agent": {"allowed_decisions": ["approve", "edit", "reject"]},
83
+ },
84
+ )
85
+ return agent, cost_cb
86
+
87
+
88
+ class AgentDisplay:
89
+ def __init__(self):
90
+ self.printed_count = 0
91
+ self._spinner = Spinner("dots", text="Thinking…")
92
+
93
+ def spinner(self, text: str = "Thinking…") -> Spinner:
94
+ self._spinner = Spinner("dots", text=text)
95
+ return self._spinner
96
+
97
+ def render(self, msg: Any) -> None:
98
+ if isinstance(msg, HumanMessage):
99
+ console.print(Panel(str(msg.content), title="You", border_style="blue"))
100
+
101
+ elif isinstance(msg, AIMessage):
102
+ content = msg.content
103
+ if isinstance(content, list):
104
+ content = "\n".join(
105
+ p.get("text", "") for p in content
106
+ if isinstance(p, dict) and p.get("type") == "text"
107
+ )
108
+ if content and content.strip():
109
+ console.print(Panel(Markdown(content), title="Agent", border_style="green"))
110
+
111
+ for tc in (msg.tool_calls or []):
112
+ name = tc.get("name", "")
113
+ args = tc.get("args", {})
114
+ if name == "muapi_select":
115
+ console.print(f" [bold cyan]→ Discovering models for:[/] {args.get('intent', '')[:60]}")
116
+ self.spinner("Discovering models…")
117
+ elif name == "muapi_generate":
118
+ console.print(f" [bold magenta]→ Generating {args.get('kind', 'asset')}:[/] {args.get('prompt', '')[:60]}")
119
+ self.spinner("Generating…")
120
+ elif name == "task":
121
+ desc = args.get("description", "")
122
+ console.print(f" [bold yellow]→ Delegating:[/] {desc[:70]}")
123
+ self.spinner("Delegating to specialist…")
124
+
125
+ elif isinstance(msg, ToolMessage):
126
+ name = getattr(msg, "name", "")
127
+ content = str(msg.content)
128
+ if name == "muapi_generate":
129
+ if '"ok": true' in content or '"url":' in content:
130
+ import json
131
+ try:
132
+ data = json.loads(content)
133
+ url = data.get("url", "")
134
+ console.print(f" [green]✓ Generated:[/] {url}")
135
+ except Exception:
136
+ console.print(f" [green]✓ Generated[/]")
137
+ else:
138
+ console.print(f" [red]✗ Generation failed[/]")
139
+ elif name == "muapi_select":
140
+ console.print(f" [green]✓ Models discovered[/]")
141
+ elif name == "task":
142
+ console.print(f" [green]✓ Specialist done[/]")
143
+
144
+
145
+ async def run(brief: str, budget_credits: int = 500) -> None:
146
+ agent, cost_cb = create_creative_agent(budget_credits)
147
+ display = AgentDisplay()
148
+
149
+ console.print()
150
+ console.print("[bold blue]MuAPI Creative Agent[/]")
151
+ console.print(f"[dim]Brief: {brief}[/]")
152
+ console.print(f"[dim]Budget: {budget_credits} credits[/]")
153
+ console.print()
154
+
155
+ config = {"configurable": {"thread_id": "creative-agent-demo"}, "callbacks": [cost_cb]}
156
+
157
+ with Live(display.spinner(), console=console, refresh_per_second=10, transient=True) as live:
158
+ async for chunk in agent.astream(
159
+ {"messages": [("user", brief)]},
160
+ config=config,
161
+ stream_mode="values",
162
+ ):
163
+ if "messages" not in chunk:
164
+ continue
165
+ messages = chunk["messages"]
166
+ if len(messages) <= display.printed_count:
167
+ continue
168
+ live.stop()
169
+ for msg in messages[display.printed_count:]:
170
+ display.render(msg)
171
+ display.printed_count = len(messages)
172
+ live.start()
173
+ live.update(display.spinner())
174
+
175
+ summary = cost_cb.summary()
176
+ console.print()
177
+ console.print(
178
+ f"[bold green]✓ Done[/] — "
179
+ f"[dim]{summary['total_credits']} credits across {summary['calls']} calls[/]"
180
+ )
181
+
182
+
183
+ def main() -> None:
184
+ import argparse
185
+
186
+ parser = argparse.ArgumentParser(description="MuAPI Creative Agent")
187
+ parser.add_argument("brief", nargs="*", help="Creative brief (or omit for demo)")
188
+ parser.add_argument("--budget", type=int, default=500, help="Credit budget cap (default 500)")
189
+ args = parser.parse_args()
190
+
191
+ brief = " ".join(args.brief) if args.brief else (
192
+ "Generate a cinematic product photo of a pair of minimalist white sneakers "
193
+ "on a wet cobblestone street at night, neon reflections"
194
+ )
195
+
196
+ try:
197
+ asyncio.run(run(brief, budget_credits=args.budget))
198
+ except KeyboardInterrupt:
199
+ console.print("\n[yellow]Interrupted[/]")
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "muapi-creative-agent"
3
+ version = "0.1.0"
4
+ description = "A generative-media Deep Agent powered by muapi.ai"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "deepagents>=0.3.5",
8
+ "muapi-langchain>=0.1.0",
9
+ "langchain-anthropic>=0.3.0",
10
+ "pyyaml>=6.0.0",
11
+ "rich>=13.0.0",
12
+ ]
13
+
14
+ [dependency-groups]
15
+ dev = []
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: generate-asset
3
+ description: Generate a single media asset — image, video, audio, or 3D. Use when the user wants one output from a clear prompt. Calls muapi_select to pick the best model, then muapi_generate.
4
+ ---
5
+
6
+ # Generate Asset
7
+
8
+ Use this skill for single-asset generation tasks.
9
+
10
+ ## Step 1 — Discover
11
+
12
+ Call `muapi_select` with the user's intent and kind:
13
+
14
+ ```
15
+ muapi_select(intent="<user's prompt>", kind="<image|video|audio|3d>", tier="<best|balanced|fast>", limit=3)
16
+ ```
17
+
18
+ Pick the top-ranked model from the result.
19
+
20
+ ## Step 2 — Generate
21
+
22
+ Call `muapi_generate` with the chosen model:
23
+
24
+ ```
25
+ muapi_generate(
26
+ prompt="<refined prompt>",
27
+ kind="<kind>",
28
+ model="<name from muapi_select>",
29
+ tier="<best|balanced|fast>",
30
+ )
31
+ ```
32
+
33
+ For edits, enhancements, image-to-video, or lipsync — pass `input_asset_url` too.
34
+
35
+ ## Step 3 — Return
36
+
37
+ Report the asset URL to the user. Include: model used, kind, and any relevant parameters.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: run-skill
3
+ description: Run a named muapi multi-step recipe (UGC ad, storyboard, brand kit, product video, social carousel…). Use when the brief matches a known workflow. Delegates to the creative-specialist subagent.
4
+ ---
5
+
6
+ # Run Named Skill
7
+
8
+ Use this skill when the user's brief matches a known multi-step recipe.
9
+
10
+ ## Step 1 — Discover skills
11
+
12
+ Call `muapi_select` with the user's intent. The result includes a `skills` list — check if any skill name matches the brief:
13
+
14
+ ```
15
+ muapi_select(intent="<user's brief>", limit=5)
16
+ ```
17
+
18
+ If a skill name matches (e.g., "ugc-ads-workflow", "storyboard", "brand-kit"), use it.
19
+
20
+ ## Step 2 — Delegate to creative-specialist
21
+
22
+ Delegate to the `creative-specialist` subagent with:
23
+ - The matching skill name
24
+ - The user's inputs (from the skill's declared `inputs` schema)
25
+
26
+ The specialist will call `muapi_run_skill(skill_name=..., inputs={...})`.
27
+
28
+ ## Step 3 — Return
29
+
30
+ Collect all asset URLs from the specialist and return them with a brief summary of what was created.
@@ -0,0 +1,28 @@
1
+ # Subagent definitions for the muapi creative agent
2
+
3
+ creative-specialist:
4
+ description: >
5
+ Handles heavy muapi workflows: named multi-step skills (UGC ads, storyboards,
6
+ product videos, brand kits, social carousels) and open-ended multi-asset briefs.
7
+ Delegate here when the brief needs more than one generation call or matches a
8
+ named recipe discovered via muapi_select.
9
+ system_prompt: |
10
+ You are a muapi creative specialist with access to muapi_run_skill and muapi_creative_agent.
11
+
12
+ ## Decision rule
13
+ - If the brief matches a named skill from muapi_select → use muapi_run_skill
14
+ - If it's an open-ended multi-asset brief with no matching skill → use muapi_creative_agent
15
+
16
+ ## muapi_run_skill usage
17
+ muapi_run_skill(skill_name="<name>", inputs={"key": "value", ...})
18
+ Inputs must match the skill's declared schema (get it from muapi_select).
19
+
20
+ ## muapi_creative_agent usage
21
+ muapi_creative_agent(brief="<full description>", budget_credits=<int>)
22
+ Use only when explicitly approved by the user (this can spend significant credits).
23
+
24
+ ## Output
25
+ Always return all asset URLs with a one-line description of each.
26
+ tools:
27
+ - muapi_run_skill
28
+ - muapi_creative_agent
@@ -0,0 +1 @@
1
+ __version__ = "0.2.7"
@@ -1,16 +1,121 @@
1
1
  """muapi auth — configure API key and inspect identity."""
2
- import typer
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import sys
6
+ import webbrowser
7
+
3
8
  import httpx
4
- from rich.prompt import Prompt
9
+ import typer
10
+ from rich.prompt import Confirm, Prompt
5
11
 
6
- from ..config import delete_api_key, get_api_key, save_api_key, BASE_URL
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 (will prompt if omitted)"),
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 to the OS keychain (or config file)."""
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]Enter your muapi API key[/bold]", password=True, console=console)
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
- location = save_api_key(api_key.strip())
176
- console.print(f"[green]API key saved to {location}.[/green]")
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
- """Show the currently configured API key (masked)."""
182
- key = get_api_key()
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()
@@ -15,7 +15,7 @@ from typing import Any
15
15
 
16
16
  import typer
17
17
 
18
- from .. import client as api_client
18
+ from .. import __version__, client as api_client
19
19
  from ..config import get_api_key
20
20
 
21
21
  app = typer.Typer(help="Run muapi as an MCP server for AI agent integration.")
@@ -360,15 +360,20 @@ TOOLS = [
360
360
  "annotations": {"readOnlyHint": True, "idempotentHint": True},
361
361
  "inputSchema": {"type": "object", "properties": {}},
362
362
  "outputSchema": {
363
- "type": "array",
364
- "items": {
365
- "type": "object",
366
- "properties": {
367
- "id": {"type": "integer"},
368
- "name": {"type": "string"},
369
- "is_active": {"type": "boolean"},
370
- "created_at": {"type": "string"},
371
- "last_used_at": {"type": "string"},
363
+ "type": "object",
364
+ "properties": {
365
+ "keys": {
366
+ "type": "array",
367
+ "items": {
368
+ "type": "object",
369
+ "properties": {
370
+ "id": {"type": "integer"},
371
+ "name": {"type": "string"},
372
+ "is_active": {"type": "boolean"},
373
+ "created_at": {"type": "string"},
374
+ "last_used_at": {"type": "string"},
375
+ },
376
+ },
372
377
  },
373
378
  },
374
379
  },
@@ -418,7 +423,12 @@ TOOLS = [
418
423
  "endpoint": None,
419
424
  "annotations": {"readOnlyHint": True, "idempotentHint": True},
420
425
  "inputSchema": {"type": "object", "properties": {}},
421
- "outputSchema": {"type": "array"},
426
+ "outputSchema": {
427
+ "type": "object",
428
+ "properties": {
429
+ "workflows": {"type": "array"},
430
+ },
431
+ },
422
432
  },
423
433
  {
424
434
  "name": "muapi_workflow_create",
@@ -658,7 +668,7 @@ def _dispatch(tool_name: str, args: dict) -> dict:
658
668
  resp = _httpx.get(f"{wf_base}/get-workflow-defs", headers={"x-api-key": key}, timeout=30.0)
659
669
  if resp.status_code >= 400:
660
670
  raise api_client.MuapiError(resp.text, resp.status_code)
661
- return resp.json()
671
+ return {"workflows": resp.json()}
662
672
 
663
673
  if tool_name == "muapi_workflow_create":
664
674
  from ..config import BASE_URL, get_api_key
@@ -732,7 +742,7 @@ def _dispatch(tool_name: str, args: dict) -> dict:
732
742
  resp = _httpx.get(f"{BASE_URL}/keys", headers={"x-api-key": key}, timeout=30.0)
733
743
  if resp.status_code >= 400:
734
744
  raise api_client.MuapiError(resp.text, resp.status_code)
735
- return resp.json()
745
+ return {"keys": resp.json()}
736
746
 
737
747
  if tool_name == "muapi_keys_create":
738
748
  from ..config import BASE_URL, get_api_key
@@ -821,7 +831,7 @@ def _handle_request(request: dict) -> str:
821
831
  return _mcp_response(req_id, {
822
832
  "protocolVersion": "2025-06-18",
823
833
  "capabilities": {"tools": {"listChanged": False}},
824
- "serverInfo": {"name": "muapi", "version": "0.1.0"},
834
+ "serverInfo": {"name": "muapi", "version": __version__},
825
835
  })
826
836
 
827
837
  if method == "tools/list":
@@ -884,7 +894,7 @@ def serve(
884
894
  )
885
895
  sys.exit(3)
886
896
 
887
- sys.stderr.write(json.dumps({"status": "muapi MCP server ready", "tools": len(TOOLS), "version": "0.1.0"}) + "\n")
897
+ sys.stderr.write(json.dumps({"status": "muapi MCP server ready", "tools": len(TOOLS), "version": __version__}) + "\n")
888
898
  sys.stderr.flush()
889
899
 
890
900
  for line in sys.stdin:
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "muapi-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Official CLI for muapi.ai \u2014 generate images, videos, and audio from your terminal",
5
5
  "keywords": [
6
6
  "muapi",
@@ -22,7 +22,8 @@ const NAME_MAP = {
22
22
  };
23
23
 
24
24
  const key = `${process.platform}-${process.arch}`;
25
- const binName = NAME_MAP[key];
25
+ // On Apple Silicon, Node may report x64 when running under Rosetta — fall back to arm64.
26
+ const binName = NAME_MAP[key] || (process.platform === "darwin" ? NAME_MAP["darwin-arm64"] : undefined);
26
27
 
27
28
  if (!binName) {
28
29
  console.warn(`[muapi] Unsupported platform: ${key}. Install via pip: pip install muapi-cli`);
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "muapi-cli"
7
- version = "0.2.5"
7
+ version = "0.2.7"
8
8
  description = "Official CLI for muapi.ai — generative media at your fingertips"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,24 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(gh issue *)",
5
- "Bash(curl -sI \"https://github.com/Anil-matcha/muapiapp/releases/download/v0.2.3/muapi-linux-x86_64\")",
6
- "Bash(curl -sI \"https://github.com/SamurAIGPT/muapi-cli/releases/download/v0.2.3/muapi-linux-x86_64\")",
7
- "Bash(gh release *)",
8
- "Bash(npm view *)",
9
- "Bash(git -C /Users/anilchandranaidumatcha/Downloads/mu_workflow/muapi-cli status)",
10
- "Bash(git -C /Users/anilchandranaidumatcha/Downloads/mu_workflow/muapi-cli remote -v)",
11
- "Bash(gh auth *)",
12
- "Bash(npm whoami *)",
13
- "Bash(git *)",
14
- "Bash(tar -xzf muapi-cli-0.2.5.tgz)",
15
- "Bash(tar -xzf muapi-cli-0.2.3.tgz)",
16
- "Bash(tar -xzf muapi-cli-0.2.4.tgz)",
17
- "Bash(rm -rf /tmp/muapi-022-check)",
18
- "Bash(mkdir /tmp/muapi-022-check)",
19
- "Read(//tmp/**)",
20
- "Bash(npm pack *)",
21
- "Bash(tar -xzf muapi-cli-0.2.2.tgz)"
22
- ]
23
- }
24
- }
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes