mushu-cli 0.1.1__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,28 @@
1
+ # Virtual environment
2
+ .venv/
3
+ venv/
4
+
5
+ # Python cache
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.so
10
+
11
+ # Distribution
12
+ dist/
13
+ build/
14
+ *.egg-info/
15
+
16
+ # Ruff cache
17
+ .ruff_cache/
18
+
19
+ # pytest
20
+ .pytest_cache/
21
+
22
+ # IDE
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+
27
+ # macOS
28
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1](https://github.com/cjoelrun/mushu/compare/mushu-cli-v0.1.0...mushu-cli-v0.1.1) (2026-01-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * **cli:** add app and api-key commands ([fa651ff](https://github.com/cjoelrun/mushu/commit/fa651ffa924a5d5d25f9e79453ba327a341368eb))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **cli:** align version with release-please manifest ([ddd628c](https://github.com/cjoelrun/mushu/commit/ddd628cf00cd803270fd4524a67e54b8dbed35b5))
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: mushu-cli
3
+ Version: 0.1.1
4
+ Summary: CLI for Mushu platform (auth + notifications)
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.26.0
7
+ Requires-Dist: pyjwt>=2.8.0
8
+ Requires-Dist: rich>=13.7.0
9
+ Requires-Dist: typer>=0.9.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
@@ -0,0 +1,202 @@
1
+ # mushu-cli
2
+
3
+ Command-line interface for the Mushu platform.
4
+
5
+ ## Features
6
+
7
+ - **Authentication**: Sign in with Apple via web OAuth
8
+ - **Organization Management**: Create and manage organizations
9
+ - **Notification Tenants**: Configure APNs credentials and send push notifications
10
+ - **Payment**: Manage Stripe integration and org wallet
11
+ - **Media**: Upload and manage images/videos
12
+ - **Device Management**: Register and list device tokens
13
+ - **Per-Project Config**: Local `.mushu.json` for project-specific settings
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install mushu-cli
19
+ ```
20
+
21
+ Or install from source:
22
+
23
+ ```bash
24
+ cd mushu-cli
25
+ pip install -e .
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Sign in
32
+ mushu auth login
33
+
34
+ # Create an organization
35
+ mushu org create "My Company"
36
+
37
+ # Initialize project config (creates .mushu.json)
38
+ mushu init --org org_abc123
39
+
40
+ # Commands now use that org automatically
41
+ mushu tenant create --bundle-id com.myapp.ios
42
+ mushu push send --user <user_id> --title "Hello" --body "World"
43
+ mushu media upload photo.jpg
44
+ ```
45
+
46
+ ## Project Configuration
47
+
48
+ ### Per-Project Config (.mushu.json)
49
+
50
+ Create a `.mushu.json` file in your project root to set defaults for that project:
51
+
52
+ ```bash
53
+ # Initialize with your org
54
+ mushu init --org org_abc123
55
+
56
+ # Or with more options
57
+ mushu init --org org_abc123 --tenant tenant_xyz --app app_def456
58
+ ```
59
+
60
+ This creates a `.mushu.json` file:
61
+
62
+ ```json
63
+ {
64
+ "org_id": "org_abc123",
65
+ "tenant_id": "tenant_xyz",
66
+ "app_id": "app_def456"
67
+ }
68
+ ```
69
+
70
+ The CLI automatically finds and uses this config when running commands from that directory (or any subdirectory).
71
+
72
+ ### Config Resolution Order
73
+
74
+ Settings are resolved in this order (highest priority first):
75
+
76
+ 1. **Command-line flags** (`--org`, `--tenant`, etc.)
77
+ 2. **Environment variables** (`MUSHU_ORG_ID`, `MUSHU_TENANT_ID`, etc.)
78
+ 3. **Local `.mushu.json`** (walked up from current directory)
79
+ 4. **Global `~/.mushu/config.json`** defaults
80
+
81
+ ### View Current Config
82
+
83
+ ```bash
84
+ # Show status including which config is active
85
+ mushu status
86
+
87
+ # Show detailed config info
88
+ mushu config --show
89
+ ```
90
+
91
+ ## Commands
92
+
93
+ ### Project Init
94
+
95
+ ```bash
96
+ mushu init # Use current global defaults
97
+ mushu init --org <id> # Set org for this project
98
+ mushu init --org <id> --tenant <id> # Set org and tenant
99
+ mushu init --force # Overwrite existing .mushu.json
100
+ ```
101
+
102
+ ### Authentication
103
+
104
+ ```bash
105
+ mushu auth login # Sign in with Apple
106
+ mushu auth logout # Sign out
107
+ mushu auth whoami # Show current user
108
+ ```
109
+
110
+ ### Organizations
111
+
112
+ ```bash
113
+ mushu org create <name> # Create organization
114
+ mushu org list # List your organizations
115
+ mushu org show <id> # Show organization details
116
+ mushu org delete <id> # Delete organization
117
+ mushu org use <id> # Set as global default
118
+ ```
119
+
120
+ ### Notification Tenants
121
+
122
+ ```bash
123
+ mushu tenant create --bundle-id <bundle> # Create tenant (uses project org)
124
+ mushu tenant list # List tenants
125
+ mushu tenant show <id> # Show tenant
126
+ mushu tenant delete <id> # Delete tenant
127
+ mushu tenant use <id> # Set as default tenant
128
+ ```
129
+
130
+ ### Push Notifications
131
+
132
+ ```bash
133
+ mushu push send --user <id> --title "Title" --body "Body"
134
+ mushu push send --user <id> --silent --data '{"key":"value"}'
135
+ ```
136
+
137
+ ### Devices
138
+
139
+ ```bash
140
+ mushu device register <token> --user <id> --platform ios
141
+ mushu device list
142
+ mushu device delete <id>
143
+ ```
144
+
145
+ ### Media
146
+
147
+ ```bash
148
+ mushu media upload <file> # Upload (uses project org)
149
+ mushu media list # List media
150
+ mushu media show <id>
151
+ mushu media delete <id>
152
+ ```
153
+
154
+ ### Pay
155
+
156
+ ```bash
157
+ mushu pay tenant create --name "My App"
158
+ mushu pay tenant list
159
+ mushu pay product create --name "Starter Pack" --price 999 --amount 10000000
160
+ ```
161
+
162
+ ### Global Configuration
163
+
164
+ ```bash
165
+ mushu config --show # Show current config
166
+ mushu config --auth-url <url> # Set auth API URL
167
+ mushu config --notify-url <url> # Set notify API URL
168
+ mushu status # Show connection status
169
+ ```
170
+
171
+ ## Configuration Files
172
+
173
+ ### Global (~/.mushu/)
174
+
175
+ - `config.json` - API URLs and global defaults
176
+ - `tokens.json` - Authentication tokens (created after login)
177
+
178
+ ### Local (.mushu.json)
179
+
180
+ Project-specific settings. Supports:
181
+
182
+ | Field | Description |
183
+ |-------|-------------|
184
+ | `org_id` | Organization ID |
185
+ | `org_name` | Organization name (display only) |
186
+ | `app_id` | App ID |
187
+ | `app_name` | App name (display only) |
188
+ | `tenant_id` | Notification tenant ID |
189
+ | `pay_tenant_id` | Pay tenant ID |
190
+
191
+ ## Environment Variables
192
+
193
+ | Variable | Description |
194
+ |----------|-------------|
195
+ | `MUSHU_AUTH_URL` | Override auth API URL |
196
+ | `MUSHU_NOTIFY_URL` | Override notify API URL |
197
+ | `MUSHU_PAY_URL` | Override pay API URL |
198
+ | `MUSHU_MEDIA_URL` | Override media API URL |
199
+ | `MUSHU_ORG_ID` | Override org ID |
200
+ | `MUSHU_APP_ID` | Override app ID |
201
+ | `MUSHU_TENANT_ID` | Override tenant ID |
202
+ | `MUSHU_PAY_TENANT_ID` | Override pay tenant ID |
@@ -0,0 +1,83 @@
1
+ # Mushu CLI development justfile
2
+
3
+ # Default recipe - show available commands
4
+ default:
5
+ @just --list
6
+
7
+ # Install dependencies and CLI in development mode
8
+ install:
9
+ uv sync
10
+
11
+ # Run the CLI (pass arguments after --)
12
+ run *args:
13
+ uv run mushu {{args}}
14
+
15
+ # Configure CLI for production
16
+ config-prod:
17
+ uv run mushu config \
18
+ --auth-url https://auth.mushucorp.com \
19
+ --notify-url https://notify.mushucorp.com \
20
+ --pay-url https://pay.mushucorp.com
21
+
22
+ # Configure CLI for local development
23
+ config-local:
24
+ uv run mushu config \
25
+ --auth-url http://localhost:8000 \
26
+ --notify-url http://localhost:8001 \
27
+ --pay-url http://localhost:8002
28
+
29
+ # Show current config
30
+ config:
31
+ uv run mushu config --show
32
+
33
+ # Show current status
34
+ status:
35
+ uv run mushu status
36
+
37
+ # Login
38
+ login:
39
+ uv run mushu auth login
40
+
41
+ # Logout
42
+ logout:
43
+ uv run mushu auth logout
44
+
45
+ # List organizations
46
+ orgs:
47
+ uv run mushu org list
48
+
49
+ # List notification tenants
50
+ tenants:
51
+ uv run mushu tenant list
52
+
53
+ # List pay tenants
54
+ pay-tenants:
55
+ uv run mushu pay list
56
+
57
+ # Create a pay tenant
58
+ pay-create name org_id stripe_key:
59
+ uv run mushu pay create --name "{{name}}" --org "{{org_id}}" --stripe-key "{{stripe_key}}"
60
+
61
+ # List products for default pay tenant
62
+ products:
63
+ uv run mushu pay products
64
+
65
+ # Add a product (credit pack)
66
+ add-product name price credits:
67
+ uv run mushu pay add-product --name "{{name}}" --price {{price}} --credits {{credits}}
68
+
69
+ # Create an API key for default pay tenant
70
+ api-key name="default":
71
+ uv run mushu pay api-key --name "{{name}}"
72
+
73
+ # Run linting
74
+ lint:
75
+ uv run ruff check .
76
+
77
+ # Format code
78
+ fmt:
79
+ uv run ruff format .
80
+
81
+ # Clean up
82
+ clean:
83
+ rm -rf .venv __pycache__ .ruff_cache *.egg-info
@@ -0,0 +1 @@
1
+ """Mushu CLI - Unified CLI for Mushu platform (auth + notifications)."""
@@ -0,0 +1,259 @@
1
+ """Main CLI entry point for Mushu."""
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from mushu.commands import (
10
+ api_key,
11
+ app as app_cmd,
12
+ auth,
13
+ device,
14
+ email,
15
+ media,
16
+ org,
17
+ pay,
18
+ push,
19
+ tenant,
20
+ )
21
+ from mushu.config import (
22
+ LocalConfig,
23
+ StoredTokens,
24
+ get_config,
25
+ get_global_config,
26
+ LOCAL_CONFIG_FILE,
27
+ )
28
+
29
+ app = typer.Typer(
30
+ name="mushu",
31
+ help="Mushu CLI - Authentication and push notifications",
32
+ no_args_is_help=True,
33
+ )
34
+ console = Console()
35
+
36
+ # Add command groups
37
+ app.add_typer(auth.app, name="auth", help="Authentication")
38
+ app.add_typer(org.app, name="org", help="Organization management")
39
+ app.add_typer(app_cmd.app, name="app", help="App management") # app_cmd to avoid name collision
40
+ app.add_typer(api_key.app, name="api-key", help="API key management")
41
+ app.add_typer(tenant.app, name="tenant", help="Notification tenant management")
42
+ app.add_typer(pay.app, name="pay", help="Payment tenant management")
43
+ app.add_typer(media.app, name="media", help="Media management")
44
+ app.add_typer(device.app, name="device", help="Device management")
45
+ app.add_typer(push.app, name="push", help="Push notifications")
46
+ app.add_typer(email.app, name="email", help="Email notifications")
47
+
48
+
49
+ @app.command("init")
50
+ def init_cmd(
51
+ org_id: str = typer.Option(None, "--org", "-o", help="Organization ID"),
52
+ org_name: str = typer.Option(None, "--org-name", help="Organization name (for display)"),
53
+ app_id: str = typer.Option(None, "--app", "-a", help="App ID"),
54
+ app_name: str = typer.Option(None, "--app-name", help="App name (for display)"),
55
+ tenant_id: str = typer.Option(None, "--tenant", "-t", help="Notification tenant ID"),
56
+ pay_tenant_id: str = typer.Option(None, "--pay-tenant", help="Pay tenant ID"),
57
+ force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config"),
58
+ ):
59
+ """Initialize a .mushu.json config file in the current directory.
60
+
61
+ This creates a project-specific configuration that the CLI will automatically
62
+ use when running commands from this directory (or any subdirectory).
63
+
64
+ Examples:
65
+ mushu init --org org_abc123
66
+ mushu init --org org_abc123 --tenant tenant_xyz
67
+ mushu init --org org_abc123 --app app_def456
68
+ """
69
+ config_path = Path.cwd() / LOCAL_CONFIG_FILE
70
+
71
+ # Check if config already exists
72
+ if config_path.exists() and not force:
73
+ console.print(f"[yellow]Config file already exists: {config_path}[/yellow]")
74
+ console.print("Use --force to overwrite")
75
+ raise typer.Exit(1)
76
+
77
+ # If no options provided, try to use current global defaults
78
+ if not any([org_id, app_id, tenant_id, pay_tenant_id]):
79
+ global_config = get_global_config()
80
+ if global_config.default_org:
81
+ org_id = global_config.default_org
82
+ org_name = global_config.default_org_name
83
+ console.print(f"[dim]Using default org from global config: {org_id}[/dim]")
84
+ if global_config.default_tenant:
85
+ tenant_id = global_config.default_tenant
86
+ console.print(f"[dim]Using default tenant from global config: {tenant_id}[/dim]")
87
+ if global_config.default_pay_tenant:
88
+ pay_tenant_id = global_config.default_pay_tenant
89
+
90
+ if not org_id:
91
+ console.print(
92
+ "[yellow]No org specified. Use --org to set one, or run 'mushu org use <id>' first.[/yellow]"
93
+ )
94
+ raise typer.Exit(1)
95
+
96
+ # Create and save local config
97
+ local_config = LocalConfig(
98
+ org_id=org_id,
99
+ org_name=org_name,
100
+ app_id=app_id,
101
+ app_name=app_name,
102
+ tenant_id=tenant_id,
103
+ pay_tenant_id=pay_tenant_id,
104
+ )
105
+ saved_path = local_config.save(config_path)
106
+
107
+ console.print(f"[green]Created {saved_path}[/green]")
108
+ console.print()
109
+
110
+ # Show what was saved
111
+ table = Table(show_header=False, box=None)
112
+ table.add_column("Key", style="dim")
113
+ table.add_column("Value")
114
+
115
+ if org_id:
116
+ display = f"{org_id}" + (f" ({org_name})" if org_name else "")
117
+ table.add_row("org_id", display)
118
+ if app_id:
119
+ display = f"{app_id}" + (f" ({app_name})" if app_name else "")
120
+ table.add_row("app_id", display)
121
+ if tenant_id:
122
+ table.add_row("tenant_id", tenant_id)
123
+ if pay_tenant_id:
124
+ table.add_row("pay_tenant_id", pay_tenant_id)
125
+
126
+ console.print(table)
127
+ console.print()
128
+ console.print("[dim]Commands in this directory will now use these defaults.[/dim]")
129
+
130
+
131
+ @app.command("config")
132
+ def config_cmd(
133
+ auth_url: str = typer.Option(None, "--auth-url", help="Set auth API URL"),
134
+ notify_url: str = typer.Option(None, "--notify-url", help="Set notify API URL"),
135
+ pay_url: str = typer.Option(None, "--pay-url", help="Set pay API URL"),
136
+ media_url: str = typer.Option(None, "--media-url", help="Set media API URL"),
137
+ show: bool = typer.Option(False, "--show", "-s", help="Show current config"),
138
+ ):
139
+ """Configure global CLI settings.
140
+
141
+ For per-project settings, use 'mushu init' to create a .mushu.json file.
142
+ """
143
+ config = get_global_config()
144
+
145
+ if auth_url:
146
+ config.auth_url = auth_url.rstrip("/")
147
+ console.print(f"[green]Auth URL: {auth_url}[/green]")
148
+
149
+ if notify_url:
150
+ config.notify_url = notify_url.rstrip("/")
151
+ console.print(f"[green]Notify URL: {notify_url}[/green]")
152
+
153
+ if pay_url:
154
+ config.pay_url = pay_url.rstrip("/")
155
+ console.print(f"[green]Pay URL: {pay_url}[/green]")
156
+
157
+ if media_url:
158
+ config.media_url = media_url.rstrip("/")
159
+ console.print(f"[green]Media URL: {media_url}[/green]")
160
+
161
+ if auth_url or notify_url or pay_url or media_url:
162
+ config.save()
163
+
164
+ if show or (not auth_url and not notify_url and not pay_url and not media_url):
165
+ # Show effective config (merged)
166
+ effective = get_config()
167
+
168
+ console.print("[bold]Global Config[/bold] (~/.mushu/config.json)")
169
+ console.print(f" Auth URL: {config.auth_url}")
170
+ console.print(f" Notify URL: {config.notify_url}")
171
+ console.print(f" Pay URL: {config.pay_url}")
172
+ console.print(f" Media URL: {config.media_url}")
173
+ console.print(f" Default org: {config.default_org or '[not set]'}")
174
+ console.print(f" Default tenant: {config.default_tenant or '[not set]'}")
175
+ console.print()
176
+
177
+ if effective.local_config_path:
178
+ console.print(f"[bold]Local Config[/bold] ({effective.local_config_path})")
179
+ local = LocalConfig.load()
180
+ if local:
181
+ if local.org_id:
182
+ console.print(f" org_id: {local.org_id}")
183
+ if local.app_id:
184
+ console.print(f" app_id: {local.app_id}")
185
+ if local.tenant_id:
186
+ console.print(f" tenant_id: {local.tenant_id}")
187
+ if local.pay_tenant_id:
188
+ console.print(f" pay_tenant_id: {local.pay_tenant_id}")
189
+ console.print()
190
+
191
+ console.print("[bold]Effective Config[/bold] (merged)")
192
+ console.print(f" org_id: {effective.org_id or '[not set]'}")
193
+ console.print(f" app_id: {effective.app_id or '[not set]'}")
194
+ console.print(f" tenant_id: {effective.tenant_id or '[not set]'}")
195
+
196
+
197
+ @app.command("status")
198
+ def status():
199
+ """Show current status and effective configuration."""
200
+ config = get_config()
201
+ tokens = StoredTokens.load()
202
+
203
+ console.print("[bold]Mushu Status[/bold]")
204
+ console.print()
205
+
206
+ # Auth status
207
+ if tokens:
208
+ console.print("[green]Authenticated[/green]")
209
+ if tokens.email:
210
+ console.print(f" Email: {tokens.email}")
211
+ if tokens.user_id:
212
+ console.print(f" User: {tokens.user_id}")
213
+ else:
214
+ console.print("[yellow]Not authenticated[/yellow]")
215
+ console.print(" Run: mushu auth login")
216
+
217
+ console.print()
218
+
219
+ # Project context
220
+ console.print("[bold]Project Context[/bold]")
221
+ if config.local_config_path:
222
+ console.print(f" [dim]from {config.local_config_path}[/dim]")
223
+ else:
224
+ console.print(" [dim]no .mushu.json found (using global defaults)[/dim]")
225
+
226
+ if config.org_id:
227
+ display = config.org_id + (f" ({config.org_name})" if config.org_name else "")
228
+ console.print(f" [green]Org: {display}[/green]")
229
+ else:
230
+ console.print(" [yellow]Org: not set[/yellow]")
231
+
232
+ if config.app_id:
233
+ display = config.app_id + (f" ({config.app_name})" if config.app_name else "")
234
+ console.print(f" [green]App: {display}[/green]")
235
+
236
+ if config.tenant_id:
237
+ console.print(f" [green]Tenant: {config.tenant_id}[/green]")
238
+
239
+ console.print()
240
+ console.print(f"[dim]Auth: {config.auth_url}[/dim]")
241
+ console.print(f"[dim]Notify: {config.notify_url}[/dim]")
242
+
243
+
244
+ @app.callback()
245
+ def main():
246
+ """
247
+ Mushu CLI - Authentication, push notifications, and media for your apps.
248
+
249
+ Get started:
250
+ mushu auth login # Sign in
251
+ mushu org create ... # Create an organization
252
+ mushu init --org <id> # Set up project config
253
+ mushu push send ... # Send a notification
254
+ """
255
+ pass
256
+
257
+
258
+ if __name__ == "__main__":
259
+ app()
@@ -0,0 +1 @@
1
+ """CLI commands for Mushu."""