Skip to content

Commit e7f5010

Browse files
Add CLI commands for tenant management
12 new Artisan commands for provisioning tenants/teams, managing domains, members, auth/SSO configuration, and team lifecycle. Includes shared ResolvesTenantContext trait, model-aware SlugHelper, and full documentation in docs/cli-commands.md.
1 parent def553a commit e7f5010

18 files changed

Lines changed: 2327 additions & 157 deletions

docs/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Start here to set up and use Neev in your application.
1717
| [Teams](./teams.md) | Team creation, invitations, roles, domain federation |
1818
| [Multi-Tenancy](./multi-tenancy.md) | Identity strategy, tenant isolation, subdomain/custom domain, enterprise SSO |
1919
| [Security](./security.md) | Brute force protection, password policies, login tracking, session management |
20+
| [CLI Commands](./cli-commands.md) | All Artisan commands: tenant provisioning, domains, members, auth/SSO, team lifecycle |
2021

2122
## Reference
2223

@@ -155,12 +156,26 @@ class LogSuccessfulLogin
155156

156157
## Console Commands
157158

159+
See [CLI Commands](./cli-commands.md) for full reference with options and examples.
160+
158161
| Command | Description |
159162
|---------|-------------|
160163
| `neev:install` | Interactive setup wizard |
161164
| `neev:download-geoip` | Download MaxMind GeoLite2 database |
162165
| `neev:clean-login-attempts` | Remove old login attempt records |
163166
| `neev:clean-passwords` | Remove old password history |
167+
| `neev:tenant:create` | Create a tenant (isolated) or team (shared) |
168+
| `neev:tenant:list` | List tenants or teams |
169+
| `neev:tenant:show` | Show tenant/team details by ID, slug, or domain |
170+
| `neev:domain:add` | Add a domain to a tenant or team |
171+
| `neev:domain:verify` | Verify a domain via DNS TXT record |
172+
| `neev:domain:list` | List domains |
173+
| `neev:member:add` | Add a user to a team (bypasses invitation) |
174+
| `neev:member:remove` | Remove a user from a team |
175+
| `neev:member:list` | List members of a team or tenant |
176+
| `neev:auth:configure` | Configure auth method (password/SSO) for a tenant or team |
177+
| `neev:auth:show` | Display auth configuration |
178+
| `neev:team:activate` | Activate or deactivate a team |
164179

165180
---
166181

docs/cli-commands.md

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
# CLI Commands
2+
3+
Complete reference for all Neev Artisan commands. Commands adapt to your [identity strategy](./architecture.md) — in **shared** mode they operate on teams, in **isolated** mode they operate on tenants.
4+
5+
> Run `php artisan list neev` to see all available commands.
6+
7+
---
8+
9+
## Setup & Maintenance
10+
11+
These commands handle initial setup and scheduled maintenance.
12+
13+
### `neev:install`
14+
15+
Interactive setup wizard for new installations.
16+
17+
```bash
18+
php artisan neev:install
19+
```
20+
21+
Prompts for: team support, email verification, domain federation, tenant isolation. Publishes config and migrations.
22+
23+
### `neev:download-geoip`
24+
25+
Download the MaxMind GeoLite2 database for IP geolocation.
26+
27+
```bash
28+
php artisan neev:download-geoip
29+
```
30+
31+
Requires `MAXMIND_LICENSE_KEY` in your `.env`.
32+
33+
### `neev:clean-login-attempts`
34+
35+
Remove login attempt records older than the configured retention period.
36+
37+
```bash
38+
php artisan neev:clean-login-attempts
39+
```
40+
41+
Retention is controlled by `config('neev.last_login_attempts_in_days')`.
42+
43+
### `neev:clean-passwords`
44+
45+
Remove expired password history records.
46+
47+
```bash
48+
php artisan neev:clean-passwords
49+
```
50+
51+
---
52+
53+
## Tenant / Team Provisioning
54+
55+
These commands create and inspect tenants (isolated mode) or teams (shared mode).
56+
57+
### `neev:tenant:create`
58+
59+
Create a new tenant or team.
60+
61+
```bash
62+
# Shared mode — creates a team
63+
php artisan neev:tenant:create "Acme Corp" --owner=admin@acme.com --activate
64+
65+
# Isolated mode — creates a tenant with platform team
66+
php artisan neev:tenant:create "Acme Corp" --owner=admin@acme.com --domain=acme.yourapp.com
67+
68+
# With custom slug and parent tenant (isolated)
69+
php artisan neev:tenant:create "Sub Tenant" --slug=sub-tenant --managed-by=acme-corp
70+
```
71+
72+
| Argument / Option | Description |
73+
|-------------------|-------------|
74+
| `name` | Name of the tenant or team (prompted if omitted) |
75+
| `--slug=` | Custom slug (auto-generated from name if omitted) |
76+
| `--owner=` | Owner by user ID or email address |
77+
| `--domain=` | Attach a domain (auto-verifies subdomains, shows DNS instructions for custom) |
78+
| `--activate` | Activate the team immediately |
79+
| `--managed-by=` | Parent tenant ID or slug (isolated mode only) |
80+
81+
**Shared mode**: Creates a `Team` with the given owner. If `--activate` is passed, sets `activated_at`.
82+
83+
**Isolated mode**: Creates a `Tenant`. If `--owner` is provided, also creates a platform team with the owner attached. If `--domain` is provided, attaches it to the tenant.
84+
85+
### `neev:tenant:list`
86+
87+
List all tenants or teams.
88+
89+
```bash
90+
# Default table output
91+
php artisan neev:tenant:list
92+
93+
# Filter and format
94+
php artisan neev:tenant:list --search=acme --limit=10
95+
php artisan neev:tenant:list --inactive --json
96+
```
97+
98+
| Option | Description |
99+
|--------|-------------|
100+
| `--search=` | Filter by name or slug |
101+
| `--inactive` | Show only inactive entries (shared mode) |
102+
| `--json` | Output as JSON |
103+
| `--limit=25` | Maximum number of results |
104+
105+
**Shared mode columns**: ID, Name, Slug, Members, Status, Created.
106+
**Isolated mode columns**: ID, Name, Slug, Teams, Created.
107+
108+
### `neev:tenant:show`
109+
110+
Show details for a tenant or team. Resolves by ID, slug, or domain.
111+
112+
```bash
113+
php artisan neev:tenant:show acme-corp
114+
php artisan neev:tenant:show 42
115+
php artisan neev:tenant:show app.acme.com
116+
php artisan neev:tenant:show acme-corp --json
117+
```
118+
119+
| Argument / Option | Description |
120+
|-------------------|-------------|
121+
| `identifier` | ID, slug, or domain (prompted if omitted) |
122+
| `--json` | Output as JSON |
123+
124+
Displays: name, ID, slug, status, owner, member/team count, domains, auth config summary.
125+
126+
---
127+
128+
## Domain Management
129+
130+
Manage domains attached to tenants or teams.
131+
132+
### `neev:domain:add`
133+
134+
Add a domain to a tenant or team.
135+
136+
```bash
137+
# Add subdomain (auto-verified if it matches your subdomain suffix)
138+
php artisan neev:domain:add acme.yourapp.com --team=acme-corp --primary
139+
140+
# Add custom domain (requires DNS verification)
141+
php artisan neev:domain:add app.acme.com --tenant=acme-corp
142+
143+
# Skip verification (local dev)
144+
php artisan neev:domain:add custom.local --team=1 --skip-verification
145+
```
146+
147+
| Argument / Option | Description |
148+
|-------------------|-------------|
149+
| `domain` | The domain to add (prompted if omitted) |
150+
| `--tenant=` | Tenant ID or slug |
151+
| `--team=` | Team ID or slug |
152+
| `--primary` | Set as primary domain |
153+
| `--enforce` | Enforce domain-based federation |
154+
| `--skip-verification` | Mark as verified immediately |
155+
156+
Subdomains matching `config('neev.tenant_isolation_options.subdomain_suffix')` are automatically verified. Custom domains display DNS TXT record instructions.
157+
158+
### `neev:domain:verify`
159+
160+
Verify a domain via DNS TXT record lookup.
161+
162+
```bash
163+
# Check DNS and verify
164+
php artisan neev:domain:verify app.acme.com
165+
166+
# Force-verify without DNS check (local dev)
167+
php artisan neev:domain:verify app.acme.com --force
168+
```
169+
170+
| Argument / Option | Description |
171+
|-------------------|-------------|
172+
| `domain` | The domain to verify (prompted if omitted) |
173+
| `--force` | Mark verified without DNS check |
174+
175+
Performs a `dns_get_record()` lookup on `_neev-verification.{domain}` and matches the TXT value against the stored verification token.
176+
177+
### `neev:domain:list`
178+
179+
List domains with optional filters.
180+
181+
```bash
182+
php artisan neev:domain:list
183+
php artisan neev:domain:list --tenant=acme-corp --unverified
184+
php artisan neev:domain:list --team=1 --json
185+
```
186+
187+
| Option | Description |
188+
|--------|-------------|
189+
| `--tenant=` | Filter by tenant ID or slug |
190+
| `--team=` | Filter by team ID or slug |
191+
| `--unverified` | Show only unverified domains |
192+
| `--json` | Output as JSON |
193+
194+
Columns: ID, Domain, Team ID, Tenant ID, Primary, Enforce, Status.
195+
196+
---
197+
198+
## Member Management
199+
200+
Add, remove, and list team members. These commands operate on the team membership pivot table directly, bypassing the invitation flow.
201+
202+
### `neev:member:add`
203+
204+
Add a user to a team directly.
205+
206+
```bash
207+
# Add by team
208+
php artisan neev:member:add user@example.com --team=acme-corp --role=editor
209+
210+
# Add to tenant's platform team
211+
php artisan neev:member:add user@example.com --tenant=acme-corp --role=member
212+
```
213+
214+
| Argument / Option | Description |
215+
|-------------------|-------------|
216+
| `email` | Email of the user to add (prompted if omitted) |
217+
| `--team=` | Team ID or slug |
218+
| `--tenant=` | Tenant ID or slug (adds to platform team) |
219+
| `--role=` | Role to assign |
220+
221+
Looks up the user via the `emails` table and attaches them with `joined=true`.
222+
223+
### `neev:member:remove`
224+
225+
Remove a user from a team.
226+
227+
```bash
228+
php artisan neev:member:remove user@example.com --team=acme-corp
229+
php artisan neev:member:remove user@example.com --team=acme-corp --force
230+
```
231+
232+
| Argument / Option | Description |
233+
|-------------------|-------------|
234+
| `email` | Email of the user to remove (prompted if omitted) |
235+
| `--team=` | Team ID or slug (required) |
236+
| `--force` | Skip confirmation prompt |
237+
238+
Refuses to remove the team owner.
239+
240+
### `neev:member:list`
241+
242+
List members of a team or tenant.
243+
244+
```bash
245+
php artisan neev:member:list --team=acme-corp
246+
php artisan neev:member:list --tenant=acme-corp --json
247+
```
248+
249+
| Option | Description |
250+
|--------|-------------|
251+
| `--team=` | Team ID or slug |
252+
| `--tenant=` | Tenant ID or slug (lists platform team members) |
253+
| `--json` | Output as JSON |
254+
255+
Columns: ID, Name, Email, Role, Joined, Since.
256+
257+
---
258+
259+
## Auth / SSO Configuration
260+
261+
Configure per-tenant or per-team authentication methods.
262+
263+
### `neev:auth:configure`
264+
265+
Configure the authentication method for a tenant or team. Interactive when options are omitted.
266+
267+
```bash
268+
# Set password auth
269+
php artisan neev:auth:configure --team=acme-corp --method=password
270+
271+
# Configure SSO interactively
272+
php artisan neev:auth:configure --tenant=acme-corp --method=sso
273+
274+
# Full non-interactive SSO setup
275+
php artisan neev:auth:configure --tenant=acme-corp \
276+
--method=sso \
277+
--sso-provider=entra \
278+
--sso-client-id=your-client-id \
279+
--sso-client-secret=your-secret \
280+
--sso-tenant-id=your-azure-tenant-id \
281+
--auto-provision \
282+
--auto-provision-role=member
283+
```
284+
285+
| Option | Description |
286+
|--------|-------------|
287+
| `--tenant=` | Tenant ID or slug |
288+
| `--team=` | Team ID or slug |
289+
| `--method=` | Auth method: `password` or `sso` |
290+
| `--sso-provider=` | SSO provider: `entra`, `google`, or `okta` |
291+
| `--sso-client-id=` | SSO client ID |
292+
| `--sso-client-secret=` | SSO client secret |
293+
| `--sso-tenant-id=` | SSO tenant/directory ID (required for Entra) |
294+
| `--auto-provision` | Enable auto-provisioning of SSO users |
295+
| `--auto-provision-role=` | Role for auto-provisioned users |
296+
297+
Creates or updates `TenantAuthSettings` (isolated) or `TeamAuthSettings` (shared). Validates the provider against `config('neev.tenant_auth_options.sso_providers')`.
298+
299+
### `neev:auth:show`
300+
301+
Display the authentication configuration.
302+
303+
```bash
304+
php artisan neev:auth:show --tenant=acme-corp
305+
php artisan neev:auth:show --team=1 --reveal
306+
php artisan neev:auth:show --team=1 --json
307+
```
308+
309+
| Option | Description |
310+
|--------|-------------|
311+
| `--tenant=` | Tenant ID or slug |
312+
| `--team=` | Team ID or slug |
313+
| `--reveal` | Show client ID (client secret is **never** shown) |
314+
| `--json` | Output as JSON |
315+
316+
---
317+
318+
## Team Lifecycle
319+
320+
### `neev:team:activate`
321+
322+
Activate or deactivate a team.
323+
324+
```bash
325+
# Activate
326+
php artisan neev:team:activate acme-corp
327+
328+
# Deactivate with reason
329+
php artisan neev:team:activate acme-corp --deactivate --reason="Non-payment"
330+
```
331+
332+
| Argument / Option | Description |
333+
|-------------------|-------------|
334+
| `team` | Team ID or slug (prompted if omitted) |
335+
| `--deactivate` | Deactivate instead of activate |
336+
| `--reason=` | Reason for deactivation |
337+
338+
Calls the existing `$team->activate()` / `$team->deactivate($reason)` methods.
339+
340+
---
341+
342+
## Scripting & Automation
343+
344+
All list and show commands support `--json` for machine-readable output:
345+
346+
```bash
347+
# Pipe to jq
348+
php artisan neev:tenant:list --json | jq '.[].slug'
349+
350+
# Use in shell scripts
351+
TENANT_ID=$(php artisan neev:tenant:show acme-corp --json | jq -r '.id')
352+
```
353+
354+
All commands that accept required arguments implement `PromptsForMissingInput` — they work interactively when arguments are omitted and non-interactively when all arguments are supplied.

0 commit comments

Comments
 (0)