@startanaicompany/cli 1.4.21 → 1.5.0
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.
- package/CLAUDE.md +67 -7
- package/README.md +453 -18
- package/package.json +1 -1
- package/src/commands/create.js +5 -6
- package/src/commands/delete.js +5 -3
- package/src/commands/deploy.js +6 -4
- package/src/commands/deployments.js +5 -3
- package/src/commands/domain.js +9 -5
- package/src/commands/env.js +9 -5
- package/src/commands/exec.js +9 -5
- package/src/commands/git.js +13 -17
- package/src/commands/init.js +116 -20
- package/src/commands/keys.js +7 -3
- package/src/commands/list.js +5 -6
- package/src/commands/logs.js +5 -3
- package/src/commands/run.js +5 -3
- package/src/commands/sessions.js +4 -5
- package/src/commands/shell.js +5 -3
- package/src/commands/status.js +4 -5
- package/src/commands/update.js +5 -6
- package/src/commands/whoami.js +4 -5
- package/src/lib/config.js +47 -0
package/CLAUDE.md
CHANGED
|
@@ -146,16 +146,28 @@ Uses Commander.js to define:
|
|
|
146
146
|
5. All subsequent API requests include X-Session-Token header
|
|
147
147
|
6. Token expires after 1 year → user must login again
|
|
148
148
|
|
|
149
|
-
**
|
|
150
|
-
1. User
|
|
151
|
-
2.
|
|
152
|
-
3.
|
|
149
|
+
**Auto-Login Flow (CI/CD & Automation):**
|
|
150
|
+
1. User sets `SAAC_USER_API_KEY` and `SAAC_USER_EMAIL` environment variables
|
|
151
|
+
2. Any command checks `ensureAuthenticated()` instead of `isAuthenticated()`
|
|
152
|
+
3. If not logged in, `ensureAuthenticated()` checks for env vars
|
|
153
|
+
4. If both present, automatically calls login API
|
|
154
|
+
5. Session token saved to config, cached for subsequent commands
|
|
155
|
+
6. Subsequent commands use cached session (fast path, no API call)
|
|
153
156
|
|
|
154
157
|
**Authentication Priority:**
|
|
155
158
|
```javascript
|
|
156
|
-
// In
|
|
159
|
+
// In commands (e.g., deploy.js, list.js, etc.)
|
|
160
|
+
if (!(await ensureAuthenticated())) {
|
|
161
|
+
// Not logged in and auto-login failed
|
|
162
|
+
logger.error('Not logged in');
|
|
163
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
164
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// In api.js createClient() - Header priority:
|
|
157
169
|
if (process.env.SAAC_API_KEY) {
|
|
158
|
-
headers['X-API-Key'] = SAAC_API_KEY; // 1st priority
|
|
170
|
+
headers['X-API-Key'] = SAAC_API_KEY; // 1st priority (legacy)
|
|
159
171
|
} else if (user.sessionToken) {
|
|
160
172
|
headers['X-Session-Token'] = sessionToken; // 2nd priority
|
|
161
173
|
} else if (user.apiKey) {
|
|
@@ -163,6 +175,11 @@ if (process.env.SAAC_API_KEY) {
|
|
|
163
175
|
}
|
|
164
176
|
```
|
|
165
177
|
|
|
178
|
+
**Environment Variables:**
|
|
179
|
+
- `SAAC_USER_API_KEY` - API key for auto-login (NEW)
|
|
180
|
+
- `SAAC_USER_EMAIL` - Email for auto-login (NEW)
|
|
181
|
+
- `SAAC_API_KEY` - Legacy API key (used directly in API headers, bypasses login)
|
|
182
|
+
|
|
166
183
|
### Application Lifecycle
|
|
167
184
|
|
|
168
185
|
1. **Create/Init** → applicationUuid saved to `.saac/config.json`
|
|
@@ -205,10 +222,53 @@ The CLI now uses session tokens instead of storing permanent API keys:
|
|
|
205
222
|
```
|
|
206
223
|
|
|
207
224
|
**Token Validation Functions** (in `config.js`):
|
|
208
|
-
- `isAuthenticated()` - Checks if user has valid, non-expired token
|
|
225
|
+
- `isAuthenticated()` - Checks if user has valid, non-expired token (synchronous)
|
|
226
|
+
- `ensureAuthenticated()` - **NEW**: Checks authentication + auto-login via env vars (async)
|
|
209
227
|
- `isTokenExpired()` - Checks if session token has expired
|
|
210
228
|
- `isTokenExpiringSoon()` - Checks if token expires within 7 days
|
|
211
229
|
|
|
230
|
+
**ensureAuthenticated() Function:**
|
|
231
|
+
```javascript
|
|
232
|
+
async function ensureAuthenticated() {
|
|
233
|
+
// Step 1: Check if already authenticated (fast path)
|
|
234
|
+
if (isAuthenticated()) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Step 2: Check for environment variables
|
|
239
|
+
const apiKey = process.env.SAAC_USER_API_KEY;
|
|
240
|
+
const email = process.env.SAAC_USER_EMAIL;
|
|
241
|
+
|
|
242
|
+
if (!apiKey || !email) {
|
|
243
|
+
return false; // No env vars - cannot auto-login
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Step 3: Attempt auto-login via API
|
|
247
|
+
try {
|
|
248
|
+
const api = require('./api');
|
|
249
|
+
const result = await api.login(email, apiKey);
|
|
250
|
+
|
|
251
|
+
// Step 4: Save session token to config
|
|
252
|
+
saveUser({
|
|
253
|
+
email: result.user.email,
|
|
254
|
+
userId: result.user.id,
|
|
255
|
+
sessionToken: result.session_token,
|
|
256
|
+
expiresAt: result.expires_at,
|
|
257
|
+
verified: result.user.verified,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return true; // Auto-login successful
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return false; // Auto-login failed
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**When to use:**
|
|
268
|
+
- Use `ensureAuthenticated()` in ALL non-auth commands (deploy, list, create, etc.)
|
|
269
|
+
- Use `isAuthenticated()` only when you don't want auto-login behavior
|
|
270
|
+
- Commands that should NOT use `ensureAuthenticated()`: login, logout, register, verify
|
|
271
|
+
|
|
212
272
|
**Backend Requirements:**
|
|
213
273
|
- `POST /auth/login` - Accepts `X-API-Key` + email, returns session token
|
|
214
274
|
- Middleware must accept both `X-Session-Token` and `X-API-Key` headers
|
package/README.md
CHANGED
|
@@ -272,6 +272,259 @@ saac whoami
|
|
|
272
272
|
|
|
273
273
|
---
|
|
274
274
|
|
|
275
|
+
## Environment Variables for CI/CD
|
|
276
|
+
|
|
277
|
+
The SAAC CLI supports **automatic authentication** via environment variables, perfect for CI/CD pipelines, Docker containers, and automation scripts.
|
|
278
|
+
|
|
279
|
+
### How It Works
|
|
280
|
+
|
|
281
|
+
When you run any SAAC command:
|
|
282
|
+
1. CLI checks if you're already logged in (session token exists)
|
|
283
|
+
2. If not logged in, checks for `SAAC_USER_API_KEY` and `SAAC_USER_EMAIL` environment variables
|
|
284
|
+
3. If both are present, **automatically logs in** via API
|
|
285
|
+
4. Session token is cached for subsequent commands (same performance as manual login)
|
|
286
|
+
|
|
287
|
+
### Required Environment Variables
|
|
288
|
+
|
|
289
|
+
- `SAAC_USER_API_KEY` - Your API key (format: `cw_...`)
|
|
290
|
+
- `SAAC_USER_EMAIL` - Your email address
|
|
291
|
+
|
|
292
|
+
**Both variables must be set** for auto-login to work.
|
|
293
|
+
|
|
294
|
+
### Usage Examples
|
|
295
|
+
|
|
296
|
+
#### GitHub Actions
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
name: Deploy to SAAC
|
|
300
|
+
on:
|
|
301
|
+
push:
|
|
302
|
+
branches: [main]
|
|
303
|
+
|
|
304
|
+
jobs:
|
|
305
|
+
deploy:
|
|
306
|
+
runs-on: ubuntu-latest
|
|
307
|
+
steps:
|
|
308
|
+
- uses: actions/checkout@v3
|
|
309
|
+
|
|
310
|
+
- name: Install SAAC CLI
|
|
311
|
+
run: npm install -g @startanaicompany/cli
|
|
312
|
+
|
|
313
|
+
- name: Deploy Application
|
|
314
|
+
env:
|
|
315
|
+
SAAC_USER_API_KEY: ${{ secrets.SAAC_API_KEY }}
|
|
316
|
+
SAAC_USER_EMAIL: ${{ secrets.SAAC_EMAIL }}
|
|
317
|
+
run: |
|
|
318
|
+
saac deploy
|
|
319
|
+
saac logs --deployment
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Setup:**
|
|
323
|
+
1. Go to your repo → Settings → Secrets → Actions
|
|
324
|
+
2. Add `SAAC_API_KEY` with your API key
|
|
325
|
+
3. Add `SAAC_EMAIL` with your email address
|
|
326
|
+
|
|
327
|
+
#### GitLab CI/CD
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
deploy:
|
|
331
|
+
stage: deploy
|
|
332
|
+
image: node:18
|
|
333
|
+
variables:
|
|
334
|
+
SAAC_USER_API_KEY: $CI_SAAC_API_KEY
|
|
335
|
+
SAAC_USER_EMAIL: $CI_SAAC_EMAIL
|
|
336
|
+
script:
|
|
337
|
+
- npm install -g @startanaicompany/cli
|
|
338
|
+
- saac deploy
|
|
339
|
+
- saac logs --deployment
|
|
340
|
+
only:
|
|
341
|
+
- main
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Setup:**
|
|
345
|
+
1. Go to your project → Settings → CI/CD → Variables
|
|
346
|
+
2. Add `CI_SAAC_API_KEY` (protected, masked)
|
|
347
|
+
3. Add `CI_SAAC_EMAIL`
|
|
348
|
+
|
|
349
|
+
#### Docker
|
|
350
|
+
|
|
351
|
+
```dockerfile
|
|
352
|
+
FROM node:18
|
|
353
|
+
|
|
354
|
+
# Install SAAC CLI
|
|
355
|
+
RUN npm install -g @startanaicompany/cli
|
|
356
|
+
|
|
357
|
+
# Set environment variables (use build args for security)
|
|
358
|
+
ARG SAAC_API_KEY
|
|
359
|
+
ARG SAAC_EMAIL
|
|
360
|
+
ENV SAAC_USER_API_KEY=$SAAC_API_KEY
|
|
361
|
+
ENV SAAC_USER_EMAIL=$SAAC_EMAIL
|
|
362
|
+
|
|
363
|
+
# Your application code
|
|
364
|
+
WORKDIR /app
|
|
365
|
+
COPY . .
|
|
366
|
+
|
|
367
|
+
# Deploy on container startup
|
|
368
|
+
CMD ["sh", "-c", "saac deploy && npm start"]
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Build with secrets:**
|
|
372
|
+
```bash
|
|
373
|
+
docker build \
|
|
374
|
+
--build-arg SAAC_API_KEY=cw_your_key \
|
|
375
|
+
--build-arg SAAC_EMAIL=your@email.com \
|
|
376
|
+
-t my-app .
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Local Shell Script
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
#!/bin/bash
|
|
383
|
+
set -e
|
|
384
|
+
|
|
385
|
+
# Set environment variables
|
|
386
|
+
export SAAC_USER_API_KEY=cw_your_api_key_here
|
|
387
|
+
export SAAC_USER_EMAIL=your@email.com
|
|
388
|
+
|
|
389
|
+
# Run SAAC commands
|
|
390
|
+
saac deploy
|
|
391
|
+
saac logs --deployment
|
|
392
|
+
saac status
|
|
393
|
+
|
|
394
|
+
echo "Deployment complete!"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### With .env File (Local Development)
|
|
398
|
+
|
|
399
|
+
Create `.env` file (**add to `.gitignore`!**):
|
|
400
|
+
```bash
|
|
401
|
+
SAAC_USER_API_KEY=cw_your_api_key_here
|
|
402
|
+
SAAC_USER_EMAIL=your@email.com
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Use with a script:
|
|
406
|
+
```bash
|
|
407
|
+
#!/bin/bash
|
|
408
|
+
# Load .env file
|
|
409
|
+
set -a
|
|
410
|
+
source .env
|
|
411
|
+
set +a
|
|
412
|
+
|
|
413
|
+
# Run SAAC commands
|
|
414
|
+
saac list
|
|
415
|
+
saac status
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Security Best Practices
|
|
419
|
+
|
|
420
|
+
✅ **DO:**
|
|
421
|
+
- Store API keys in secrets management (GitHub Secrets, GitLab Variables, AWS Secrets Manager, etc.)
|
|
422
|
+
- Use environment variables for automation
|
|
423
|
+
- Add `.env` files to `.gitignore`
|
|
424
|
+
- Rotate API keys periodically with `saac keys regenerate`
|
|
425
|
+
- Use different API keys for different environments (dev, staging, prod)
|
|
426
|
+
- Set secrets as "protected" and "masked" in CI/CD systems
|
|
427
|
+
|
|
428
|
+
❌ **DON'T:**
|
|
429
|
+
- Commit API keys to version control
|
|
430
|
+
- Share API keys in plain text (Slack, email, etc.)
|
|
431
|
+
- Hardcode API keys in scripts or Dockerfiles
|
|
432
|
+
- Use the same API key across multiple teams/projects
|
|
433
|
+
- Log API keys in CI/CD output
|
|
434
|
+
|
|
435
|
+
### How It Differs from Manual Login
|
|
436
|
+
|
|
437
|
+
| Feature | Manual Login | Auto-Login (Env Vars) |
|
|
438
|
+
|---------|--------------|----------------------|
|
|
439
|
+
| Session token created | ✅ Yes | ✅ Yes |
|
|
440
|
+
| Session cached locally | ✅ Yes | ✅ Yes |
|
|
441
|
+
| Session expires | ✅ 1 year | ✅ 1 year |
|
|
442
|
+
| Requires user interaction | ❌ No (after first login) | ✅ Fully automated |
|
|
443
|
+
| Perfect for CI/CD | ⚠️ Requires manual setup | ✅ Native support |
|
|
444
|
+
| Performance | ⚡ Fast (cached) | ⚡ Fast (cached after first auto-login) |
|
|
445
|
+
|
|
446
|
+
### Troubleshooting
|
|
447
|
+
|
|
448
|
+
**"Not logged in" despite setting environment variables:**
|
|
449
|
+
- Verify both `SAAC_USER_API_KEY` and `SAAC_USER_EMAIL` are set:
|
|
450
|
+
```bash
|
|
451
|
+
echo $SAAC_USER_API_KEY
|
|
452
|
+
echo $SAAC_USER_EMAIL
|
|
453
|
+
```
|
|
454
|
+
- Ensure API key is valid (not revoked or expired)
|
|
455
|
+
- Check API key format starts with `cw_`
|
|
456
|
+
- Try logging in manually to verify credentials work:
|
|
457
|
+
```bash
|
|
458
|
+
saac login -e $SAAC_USER_EMAIL -k $SAAC_USER_API_KEY
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**Environment variables not being read:**
|
|
462
|
+
- Make sure variables are exported: `export SAAC_USER_API_KEY=...`
|
|
463
|
+
- Check for typos in variable names (must be exact)
|
|
464
|
+
- In Docker, ensure ENV is set or use build args correctly
|
|
465
|
+
- In CI/CD, verify secrets are configured correctly
|
|
466
|
+
|
|
467
|
+
**Security concerns:**
|
|
468
|
+
- Never print environment variables in logs: `echo $SAAC_USER_API_KEY` ❌
|
|
469
|
+
- Use masked/protected secrets in CI/CD systems
|
|
470
|
+
- Regenerate API key if accidentally exposed: `saac keys regenerate`
|
|
471
|
+
- Monitor active sessions: `saac sessions`
|
|
472
|
+
|
|
473
|
+
### Example: Complete CI/CD Workflow
|
|
474
|
+
|
|
475
|
+
```yaml
|
|
476
|
+
name: Full Deployment Workflow
|
|
477
|
+
on:
|
|
478
|
+
push:
|
|
479
|
+
branches: [main, staging]
|
|
480
|
+
|
|
481
|
+
jobs:
|
|
482
|
+
deploy:
|
|
483
|
+
runs-on: ubuntu-latest
|
|
484
|
+
steps:
|
|
485
|
+
- uses: actions/checkout@v3
|
|
486
|
+
|
|
487
|
+
- name: Setup Node.js
|
|
488
|
+
uses: actions/setup-node@v3
|
|
489
|
+
with:
|
|
490
|
+
node-version: '18'
|
|
491
|
+
|
|
492
|
+
- name: Install Dependencies
|
|
493
|
+
run: npm install
|
|
494
|
+
|
|
495
|
+
- name: Run Tests
|
|
496
|
+
run: npm test
|
|
497
|
+
|
|
498
|
+
- name: Install SAAC CLI
|
|
499
|
+
run: npm install -g @startanaicompany/cli
|
|
500
|
+
|
|
501
|
+
- name: Deploy to SAAC
|
|
502
|
+
env:
|
|
503
|
+
SAAC_USER_API_KEY: ${{ secrets.SAAC_API_KEY }}
|
|
504
|
+
SAAC_USER_EMAIL: ${{ secrets.SAAC_EMAIL }}
|
|
505
|
+
run: |
|
|
506
|
+
# Auto-login happens automatically!
|
|
507
|
+
saac deploy
|
|
508
|
+
|
|
509
|
+
# Wait for deployment to complete (built-in timeout)
|
|
510
|
+
echo "Deployment finished!"
|
|
511
|
+
|
|
512
|
+
- name: Check Application Status
|
|
513
|
+
env:
|
|
514
|
+
SAAC_USER_API_KEY: ${{ secrets.SAAC_API_KEY }}
|
|
515
|
+
SAAC_USER_EMAIL: ${{ secrets.SAAC_EMAIL }}
|
|
516
|
+
run: |
|
|
517
|
+
saac status
|
|
518
|
+
saac logs --deployment | tail -20
|
|
519
|
+
|
|
520
|
+
- name: Notify on Failure
|
|
521
|
+
if: failure()
|
|
522
|
+
run: |
|
|
523
|
+
echo "Deployment failed! Check logs with: saac logs --deployment"
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
275
528
|
## Git OAuth
|
|
276
529
|
|
|
277
530
|
SAAC CLI uses **OAuth-only authentication** for Git access. You must connect your Git account before creating applications.
|
|
@@ -374,6 +627,141 @@ saac git disconnect git.startanaicompany.com
|
|
|
374
627
|
- Existing applications continue to work
|
|
375
628
|
- New applications will require reconnection
|
|
376
629
|
|
|
630
|
+
### `saac git repos <host>`
|
|
631
|
+
|
|
632
|
+
List repositories from a connected Git host.
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
# Basic usage
|
|
636
|
+
saac git repos git.startanaicompany.com
|
|
637
|
+
saac git repos github.com
|
|
638
|
+
|
|
639
|
+
# Include latest commit information
|
|
640
|
+
saac git repos github.com --commits
|
|
641
|
+
|
|
642
|
+
# Filter by visibility
|
|
643
|
+
saac git repos github.com --visibility private
|
|
644
|
+
saac git repos github.com --visibility public
|
|
645
|
+
|
|
646
|
+
# With pagination
|
|
647
|
+
saac git repos github.com --page 2 --per-page 10
|
|
648
|
+
|
|
649
|
+
# Sort repositories
|
|
650
|
+
saac git repos github.com --sort created
|
|
651
|
+
saac git repos github.com --sort name
|
|
652
|
+
|
|
653
|
+
# JSON output for scripting
|
|
654
|
+
saac git repos github.com --json
|
|
655
|
+
|
|
656
|
+
# Combine options
|
|
657
|
+
saac git repos github.com --commits --visibility private --per-page 20
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Options:**
|
|
661
|
+
- `-p, --page <number>` - Page number for pagination (default: 1)
|
|
662
|
+
- `-n, --per-page <number>` - Results per page, max 100 (default: 20)
|
|
663
|
+
- `-s, --sort <type>` - Sort order: `updated`, `created`, `name` (default: `updated`)
|
|
664
|
+
- `-v, --visibility <type>` - Filter: `all`, `public`, `private` (default: `all`)
|
|
665
|
+
- `-c, --commits` - Include latest commit info for each repository
|
|
666
|
+
- `--json` - Output as JSON for scripting
|
|
667
|
+
|
|
668
|
+
**Shows:**
|
|
669
|
+
- Repository name
|
|
670
|
+
- Visibility (public/private)
|
|
671
|
+
- Last updated timestamp
|
|
672
|
+
- Default branch
|
|
673
|
+
- Primary language
|
|
674
|
+
- Latest commit (with `--commits` flag)
|
|
675
|
+
|
|
676
|
+
**Example output (without commits):**
|
|
677
|
+
```
|
|
678
|
+
Repositories on git.startanaicompany.com (ryan.gogo)
|
|
679
|
+
────────────────────────────────────────────────────
|
|
680
|
+
|
|
681
|
+
NAME VISIBILITY UPDATED BRANCH LANGUAGE
|
|
682
|
+
mysimpleflowershop private 1/27/2026, 12:52:18 AM master JavaScript
|
|
683
|
+
api-server private 1/26/2026, 8:30:00 PM main TypeScript
|
|
684
|
+
landing-page public 1/25/2026, 3:15:00 PM master HTML
|
|
685
|
+
|
|
686
|
+
Showing 3 repositories (page 1)
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**Example output (with `--commits`):**
|
|
690
|
+
```
|
|
691
|
+
Repositories on git.startanaicompany.com (ryan.gogo)
|
|
692
|
+
────────────────────────────────────────────────────
|
|
693
|
+
|
|
694
|
+
NAME VISIBILITY LAST COMMIT
|
|
695
|
+
mysimpleflowershop private 4b2c63f Initial commit - Simple Flower (3d ago)
|
|
696
|
+
api-server private a90373d Add user authentication (1d ago)
|
|
697
|
+
landing-page public f1e2d3c Update homepage design (5h ago)
|
|
698
|
+
|
|
699
|
+
Showing 3 repositories (page 1)
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**JSON output:**
|
|
703
|
+
```bash
|
|
704
|
+
$ saac git repos github.com --json
|
|
705
|
+
|
|
706
|
+
{
|
|
707
|
+
"success": true,
|
|
708
|
+
"git_host": "github.com",
|
|
709
|
+
"username": "johndoe",
|
|
710
|
+
"provider_type": "github",
|
|
711
|
+
"repositories": [
|
|
712
|
+
{
|
|
713
|
+
"name": "my-app",
|
|
714
|
+
"fullName": "johndoe/my-app",
|
|
715
|
+
"description": "A cool application",
|
|
716
|
+
"private": false,
|
|
717
|
+
"sshUrl": "git@github.com:johndoe/my-app.git",
|
|
718
|
+
"cloneUrl": "https://github.com/johndoe/my-app.git",
|
|
719
|
+
"htmlUrl": "https://github.com/johndoe/my-app",
|
|
720
|
+
"defaultBranch": "main",
|
|
721
|
+
"updatedAt": "2026-01-30T12:00:00Z",
|
|
722
|
+
"language": "JavaScript",
|
|
723
|
+
"latestCommit": {
|
|
724
|
+
"sha": "abc1234",
|
|
725
|
+
"message": "Fix authentication bug",
|
|
726
|
+
"author": "John Doe",
|
|
727
|
+
"date": "2026-01-30T11:30:00Z"
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
],
|
|
731
|
+
"count": 1,
|
|
732
|
+
"page": 1,
|
|
733
|
+
"per_page": 100
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Use Cases:**
|
|
738
|
+
- Browse available repositories before creating an application
|
|
739
|
+
- Find repository SSH URLs for `saac create` command
|
|
740
|
+
- Check latest commits without visiting Git provider
|
|
741
|
+
- Script repository discovery and automation
|
|
742
|
+
- Filter personal vs organization repositories
|
|
743
|
+
|
|
744
|
+
**Performance Notes:**
|
|
745
|
+
- Without `--commits`: Fast (single API call)
|
|
746
|
+
- With `--commits`: Slower (fetches commit info for each repo in parallel)
|
|
747
|
+
- Use `--commits` only when you need commit information
|
|
748
|
+
|
|
749
|
+
**Example Workflow:**
|
|
750
|
+
```bash
|
|
751
|
+
# 1. Connect to Git host
|
|
752
|
+
saac git connect github.com
|
|
753
|
+
|
|
754
|
+
# 2. List your repositories
|
|
755
|
+
saac git repos github.com
|
|
756
|
+
|
|
757
|
+
# 3. Find the repository you want to deploy
|
|
758
|
+
saac git repos github.com --visibility private
|
|
759
|
+
|
|
760
|
+
# 4. Copy the SSH URL from the output
|
|
761
|
+
# 5. Create application with that repository
|
|
762
|
+
saac create my-app -s myapp -r git@github.com:username/my-app.git
|
|
763
|
+
```
|
|
764
|
+
|
|
377
765
|
---
|
|
378
766
|
|
|
379
767
|
## Application Management
|
|
@@ -392,31 +780,78 @@ saac init
|
|
|
392
780
|
- Have an existing project
|
|
393
781
|
- Want to manage an application from a different directory
|
|
394
782
|
|
|
395
|
-
**
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
3. Saves selected application info to `.saac/config.json`
|
|
399
|
-
4. Now you can use `saac deploy`, `saac logs`, etc.
|
|
783
|
+
**Smart Auto-Detection (NEW!):**
|
|
784
|
+
|
|
785
|
+
If you're in a Git repository, `saac init` automatically detects the remote URL and matches it to your applications:
|
|
400
786
|
|
|
401
|
-
**Example:**
|
|
402
787
|
```bash
|
|
788
|
+
$ cd mysimpleflowershop
|
|
403
789
|
$ saac init
|
|
404
790
|
|
|
405
|
-
|
|
406
|
-
|
|
791
|
+
Initialize SAAC Project
|
|
792
|
+
───────────────────────
|
|
407
793
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
794
|
+
✓ Found 3 application(s)
|
|
795
|
+
|
|
796
|
+
ℹ Auto-detected Git repository: git@git.startanaicompany.com:ryan.gogo/mysimpleflowershop.git
|
|
797
|
+
|
|
798
|
+
Matched Application: mysimpleflowershop
|
|
799
|
+
Domain: mysimpleflowershop.startanaicompany.com
|
|
800
|
+
Status: running:healthy
|
|
801
|
+
|
|
802
|
+
? Link this application to the current directory? (Y/n) Yes
|
|
803
|
+
|
|
804
|
+
✓ Project initialized!
|
|
805
|
+
|
|
806
|
+
Application: mysimpleflowershop
|
|
807
|
+
UUID: abc123-def456...
|
|
808
|
+
Domain: mysimpleflowershop.startanaicompany.com
|
|
809
|
+
Status: running:healthy
|
|
810
|
+
|
|
811
|
+
You can now use:
|
|
812
|
+
saac deploy Deploy your application
|
|
813
|
+
saac logs --follow View deployment logs
|
|
814
|
+
saac status Check application status
|
|
815
|
+
saac update --port 8080 Update configuration
|
|
816
|
+
```
|
|
412
817
|
|
|
413
|
-
|
|
818
|
+
**How Auto-Detection Works:**
|
|
819
|
+
1. Reads Git remote URL from `.git/config`
|
|
820
|
+
2. Normalizes both Git remote and application repositories
|
|
821
|
+
3. Matches against your applications automatically
|
|
822
|
+
4. Asks for confirmation (defaults to Yes)
|
|
823
|
+
5. Falls back to manual selection if no match found or if you decline
|
|
414
824
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
825
|
+
**Supports:**
|
|
826
|
+
- SSH URLs: `git@github.com:user/repo.git`
|
|
827
|
+
- HTTPS URLs: `https://github.com/user/repo.git`
|
|
828
|
+
- With or without `.git` suffix
|
|
829
|
+
- Case-insensitive matching
|
|
830
|
+
|
|
831
|
+
**Manual Selection (fallback):**
|
|
832
|
+
|
|
833
|
+
If not in a Git repository or no match found, shows interactive list:
|
|
834
|
+
|
|
835
|
+
```bash
|
|
836
|
+
$ saac init
|
|
837
|
+
|
|
838
|
+
? Select application to link to this directory:
|
|
839
|
+
❯ mysimpleflowershop - mysimpleflowershop.startanaicompany.com (running:healthy)
|
|
840
|
+
api-server - api.startanaicompany.com (running:healthy)
|
|
841
|
+
landing-page - landing.startanaicompany.com (stopped)
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
**What it saves:**
|
|
845
|
+
|
|
846
|
+
Creates `.saac/config.json` in current directory:
|
|
847
|
+
```json
|
|
848
|
+
{
|
|
849
|
+
"applicationUuid": "abc123-def456...",
|
|
850
|
+
"applicationName": "mysimpleflowershop",
|
|
851
|
+
"subdomain": "mysimpleflowershop",
|
|
852
|
+
"domainSuffix": "startanaicompany.com",
|
|
853
|
+
"gitRepository": "git@git.startanaicompany.com:ryan.gogo/mysimpleflowershop.git"
|
|
854
|
+
}
|
|
420
855
|
```
|
|
421
856
|
|
|
422
857
|
### `saac create <name>`
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const {
|
|
6
|
+
const { ensureAuthenticated, saveProjectConfig, getUser, getProjectConfig } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const oauth = require('../lib/oauth');
|
|
9
9
|
const inquirer = require('inquirer');
|
|
@@ -12,12 +12,11 @@ const errorDisplay = require('../lib/errorDisplay');
|
|
|
12
12
|
|
|
13
13
|
async function create(name, options) {
|
|
14
14
|
try {
|
|
15
|
-
// Check authentication
|
|
16
|
-
if (!
|
|
15
|
+
// Check authentication (with auto-login support)
|
|
16
|
+
if (!(await ensureAuthenticated())) {
|
|
17
17
|
logger.error('Not logged in');
|
|
18
|
-
logger.
|
|
19
|
-
logger.info('
|
|
20
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
18
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
19
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
21
20
|
process.exit(1);
|
|
22
21
|
}
|
|
23
22
|
|
package/src/commands/delete.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const inquirer = require('inquirer');
|
|
9
9
|
const fs = require('fs');
|
|
@@ -16,8 +16,10 @@ const path = require('path');
|
|
|
16
16
|
async function deleteApp(options) {
|
|
17
17
|
try {
|
|
18
18
|
// Check authentication
|
|
19
|
-
if (!
|
|
20
|
-
logger.error('Not logged in
|
|
19
|
+
if (!(await ensureAuthenticated())) {
|
|
20
|
+
logger.error('Not logged in');
|
|
21
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
22
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
21
23
|
process.exit(1);
|
|
22
24
|
}
|
|
23
25
|
|
package/src/commands/deploy.js
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const errorDisplay = require('../lib/errorDisplay');
|
|
9
9
|
|
|
10
10
|
async function deploy(options) {
|
|
11
11
|
try {
|
|
12
|
-
// Check authentication
|
|
13
|
-
if (!
|
|
14
|
-
logger.error('Not logged in
|
|
12
|
+
// Check authentication (with auto-login support)
|
|
13
|
+
if (!(await ensureAuthenticated())) {
|
|
14
|
+
logger.error('Not logged in');
|
|
15
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
16
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
15
17
|
process.exit(1);
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
10
10
|
async function deployments(options) {
|
|
11
11
|
try {
|
|
12
12
|
// Check authentication
|
|
13
|
-
if (!
|
|
14
|
-
logger.error('Not logged in
|
|
13
|
+
if (!(await ensureAuthenticated())) {
|
|
14
|
+
logger.error('Not logged in');
|
|
15
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
16
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
15
17
|
process.exit(1);
|
|
16
18
|
}
|
|
17
19
|
|
package/src/commands/domain.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated, saveProjectConfig } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -14,8 +14,10 @@ const logger = require('../lib/logger');
|
|
|
14
14
|
async function set(subdomain, options) {
|
|
15
15
|
try {
|
|
16
16
|
// Check authentication
|
|
17
|
-
if (!
|
|
18
|
-
logger.error('Not logged in
|
|
17
|
+
if (!(await ensureAuthenticated())) {
|
|
18
|
+
logger.error('Not logged in');
|
|
19
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
20
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -149,8 +151,10 @@ async function set(subdomain, options) {
|
|
|
149
151
|
async function show() {
|
|
150
152
|
try {
|
|
151
153
|
// Check authentication
|
|
152
|
-
if (!
|
|
153
|
-
logger.error('Not logged in
|
|
154
|
+
if (!(await ensureAuthenticated())) {
|
|
155
|
+
logger.error('Not logged in');
|
|
156
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
157
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
154
158
|
process.exit(1);
|
|
155
159
|
}
|
|
156
160
|
|
package/src/commands/env.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
@@ -14,8 +14,10 @@ const { table } = require('table');
|
|
|
14
14
|
async function get(key) {
|
|
15
15
|
try {
|
|
16
16
|
// Check authentication
|
|
17
|
-
if (!
|
|
18
|
-
logger.error('Not logged in
|
|
17
|
+
if (!(await ensureAuthenticated())) {
|
|
18
|
+
logger.error('Not logged in');
|
|
19
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
20
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -108,8 +110,10 @@ async function list() {
|
|
|
108
110
|
async function set(vars) {
|
|
109
111
|
try {
|
|
110
112
|
// Check authentication
|
|
111
|
-
if (!
|
|
112
|
-
logger.error('Not logged in
|
|
113
|
+
if (!(await ensureAuthenticated())) {
|
|
114
|
+
logger.error('Not logged in');
|
|
115
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
116
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
113
117
|
process.exit(1);
|
|
114
118
|
}
|
|
115
119
|
|
package/src/commands/exec.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
@@ -15,8 +15,10 @@ const { table } = require('table');
|
|
|
15
15
|
async function exec(command, options = {}) {
|
|
16
16
|
try {
|
|
17
17
|
// Check authentication
|
|
18
|
-
if (!
|
|
19
|
-
logger.error('Not logged in
|
|
18
|
+
if (!(await ensureAuthenticated())) {
|
|
19
|
+
logger.error('Not logged in');
|
|
20
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
21
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
20
22
|
process.exit(1);
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -156,8 +158,10 @@ async function exec(command, options = {}) {
|
|
|
156
158
|
async function history(options = {}) {
|
|
157
159
|
try {
|
|
158
160
|
// Check authentication
|
|
159
|
-
if (!
|
|
160
|
-
logger.error('Not logged in
|
|
161
|
+
if (!(await ensureAuthenticated())) {
|
|
162
|
+
logger.error('Not logged in');
|
|
163
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
164
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
161
165
|
process.exit(1);
|
|
162
166
|
}
|
|
163
167
|
|
package/src/commands/git.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const oauth = require('../lib/oauth');
|
|
6
6
|
const api = require('../lib/api');
|
|
7
|
-
const {
|
|
7
|
+
const { ensureAuthenticated, getUser } = require('../lib/config');
|
|
8
8
|
const logger = require('../lib/logger');
|
|
9
9
|
const { table } = require('table');
|
|
10
10
|
const inquirer = require('inquirer');
|
|
@@ -15,11 +15,10 @@ const inquirer = require('inquirer');
|
|
|
15
15
|
async function connect(host) {
|
|
16
16
|
try {
|
|
17
17
|
// Check authentication
|
|
18
|
-
if (!
|
|
18
|
+
if (!(await ensureAuthenticated())) {
|
|
19
19
|
logger.error('Not logged in');
|
|
20
|
-
logger.
|
|
21
|
-
logger.info('
|
|
22
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
20
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
21
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
23
22
|
process.exit(1);
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -116,11 +115,10 @@ async function connect(host) {
|
|
|
116
115
|
async function list() {
|
|
117
116
|
try {
|
|
118
117
|
// Check authentication
|
|
119
|
-
if (!
|
|
118
|
+
if (!(await ensureAuthenticated())) {
|
|
120
119
|
logger.error('Not logged in');
|
|
121
|
-
logger.
|
|
122
|
-
logger.info('
|
|
123
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
120
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
121
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
124
122
|
process.exit(1);
|
|
125
123
|
}
|
|
126
124
|
|
|
@@ -193,11 +191,10 @@ async function list() {
|
|
|
193
191
|
async function disconnect(host) {
|
|
194
192
|
try {
|
|
195
193
|
// Check authentication
|
|
196
|
-
if (!
|
|
194
|
+
if (!(await ensureAuthenticated())) {
|
|
197
195
|
logger.error('Not logged in');
|
|
198
|
-
logger.
|
|
199
|
-
logger.info('
|
|
200
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
196
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
197
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
201
198
|
process.exit(1);
|
|
202
199
|
}
|
|
203
200
|
|
|
@@ -254,11 +251,10 @@ async function disconnect(host) {
|
|
|
254
251
|
async function repos(gitHost, options) {
|
|
255
252
|
try {
|
|
256
253
|
// Check authentication
|
|
257
|
-
if (!
|
|
254
|
+
if (!(await ensureAuthenticated())) {
|
|
258
255
|
logger.error('Not logged in');
|
|
259
|
-
logger.
|
|
260
|
-
logger.info('
|
|
261
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
256
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
257
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
262
258
|
process.exit(1);
|
|
263
259
|
}
|
|
264
260
|
|
package/src/commands/init.js
CHANGED
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const api = require('../lib/api');
|
|
10
|
-
const {
|
|
10
|
+
const { ensureAuthenticated, saveProjectConfig, getProjectConfig } = require('../lib/config');
|
|
11
11
|
const logger = require('../lib/logger');
|
|
12
12
|
const inquirer = require('inquirer');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
13
16
|
|
|
14
17
|
async function init(options) {
|
|
15
18
|
try {
|
|
16
|
-
// Check authentication
|
|
17
|
-
if (!
|
|
19
|
+
// Check authentication (with auto-login support)
|
|
20
|
+
if (!(await ensureAuthenticated())) {
|
|
18
21
|
logger.error('Not logged in');
|
|
19
|
-
logger.
|
|
20
|
-
logger.info('
|
|
21
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
22
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
23
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
22
24
|
process.exit(1);
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -86,11 +88,58 @@ async function createAndInitialize(options) {
|
|
|
86
88
|
// This would call the create command functionality, then save the config
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Get Git remote URL from current directory
|
|
93
|
+
* @returns {string|null} - Git remote URL or null if not a git repo
|
|
94
|
+
*/
|
|
95
|
+
function getGitRemoteUrl() {
|
|
96
|
+
try {
|
|
97
|
+
// Check if .git directory exists
|
|
98
|
+
if (!fs.existsSync(path.join(process.cwd(), '.git'))) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Get remote.origin.url
|
|
103
|
+
const remoteUrl = execSync('git config --get remote.origin.url', {
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
|
|
106
|
+
}).trim();
|
|
107
|
+
|
|
108
|
+
return remoteUrl || null;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Not a git repo or no remote configured
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Normalize Git URLs for comparison
|
|
117
|
+
* Converts both SSH and HTTPS URLs to a comparable format
|
|
118
|
+
*/
|
|
119
|
+
function normalizeGitUrl(url) {
|
|
120
|
+
if (!url) return '';
|
|
121
|
+
|
|
122
|
+
// Remove .git suffix
|
|
123
|
+
let normalized = url.replace(/\.git$/, '');
|
|
124
|
+
|
|
125
|
+
// Convert SSH to HTTPS-like format for comparison
|
|
126
|
+
// git@github.com:user/repo -> github.com/user/repo
|
|
127
|
+
normalized = normalized.replace(/^git@([^:]+):/, '$1/');
|
|
128
|
+
|
|
129
|
+
// Remove https:// or http://
|
|
130
|
+
normalized = normalized.replace(/^https?:\/\//, '');
|
|
131
|
+
|
|
132
|
+
return normalized.toLowerCase();
|
|
133
|
+
}
|
|
134
|
+
|
|
89
135
|
/**
|
|
90
136
|
* Link an existing application to current directory (interactive)
|
|
91
137
|
*/
|
|
92
138
|
async function linkExistingApplication() {
|
|
93
139
|
|
|
140
|
+
// Try to auto-detect Git repository
|
|
141
|
+
const gitRemoteUrl = getGitRemoteUrl();
|
|
142
|
+
|
|
94
143
|
// Fetch user's applications
|
|
95
144
|
const spin = logger.spinner('Fetching your applications...').start();
|
|
96
145
|
|
|
@@ -111,20 +160,67 @@ async function linkExistingApplication() {
|
|
|
111
160
|
|
|
112
161
|
logger.newline();
|
|
113
162
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
let selectedApp = null;
|
|
164
|
+
|
|
165
|
+
// Try to auto-match based on Git remote URL
|
|
166
|
+
if (gitRemoteUrl) {
|
|
167
|
+
const normalizedRemote = normalizeGitUrl(gitRemoteUrl);
|
|
168
|
+
|
|
169
|
+
const matchedApp = applications.find(app => {
|
|
170
|
+
if (!app.git_repository) return false;
|
|
171
|
+
const normalizedAppRepo = normalizeGitUrl(app.git_repository);
|
|
172
|
+
return normalizedAppRepo === normalizedRemote;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (matchedApp) {
|
|
176
|
+
// Found matching application!
|
|
177
|
+
logger.info(`Auto-detected Git repository: ${gitRemoteUrl}`);
|
|
178
|
+
logger.newline();
|
|
179
|
+
logger.field('Matched Application', matchedApp.name);
|
|
180
|
+
logger.field('Domain', matchedApp.domain || `${matchedApp.subdomain}.startanaicompany.com`);
|
|
181
|
+
logger.field('Status', matchedApp.status);
|
|
182
|
+
logger.newline();
|
|
183
|
+
|
|
184
|
+
const { confirm } = await inquirer.prompt([
|
|
185
|
+
{
|
|
186
|
+
type: 'confirm',
|
|
187
|
+
name: 'confirm',
|
|
188
|
+
message: 'Link this application to the current directory?',
|
|
189
|
+
default: true,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
if (confirm) {
|
|
194
|
+
selectedApp = matchedApp;
|
|
195
|
+
} else {
|
|
196
|
+
logger.newline();
|
|
197
|
+
logger.info('Please select a different application:');
|
|
198
|
+
logger.newline();
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
logger.warn(`No application found matching Git remote: ${gitRemoteUrl}`);
|
|
202
|
+
logger.newline();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If no auto-match or user declined, show interactive selection
|
|
207
|
+
if (!selectedApp) {
|
|
208
|
+
const choices = applications.map(app => ({
|
|
209
|
+
name: `${app.name} - ${app.domain || `${app.subdomain}.startanaicompany.com`} (${app.status})`,
|
|
210
|
+
value: app,
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
const answer = await inquirer.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: 'list',
|
|
216
|
+
name: 'selectedApp',
|
|
217
|
+
message: 'Select application to link to this directory:',
|
|
218
|
+
choices: choices,
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
selectedApp = answer.selectedApp;
|
|
223
|
+
}
|
|
128
224
|
|
|
129
225
|
logger.newline();
|
|
130
226
|
|
package/src/commands/keys.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getUser,
|
|
6
|
+
const { getUser, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const inquirer = require('inquirer');
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ const inquirer = require('inquirer');
|
|
|
13
13
|
async function regenerate() {
|
|
14
14
|
try {
|
|
15
15
|
// Must be authenticated (via session token)
|
|
16
|
-
if (!
|
|
16
|
+
if (!(await ensureAuthenticated())) {
|
|
17
17
|
logger.error('Not logged in');
|
|
18
18
|
logger.newline();
|
|
19
19
|
logger.info('You must be logged in to regenerate your API key');
|
|
@@ -21,6 +21,10 @@ async function regenerate() {
|
|
|
21
21
|
logger.info('Login using email verification:');
|
|
22
22
|
logger.log(' saac login -e <email> # Request OTP');
|
|
23
23
|
logger.log(' saac login -e <email> --otp <code> # Verify OTP');
|
|
24
|
+
logger.newline();
|
|
25
|
+
logger.info('Or set environment variables:');
|
|
26
|
+
logger.log(' export SAAC_USER_API_KEY=your_api_key');
|
|
27
|
+
logger.log(' export SAAC_USER_EMAIL=your_email');
|
|
24
28
|
process.exit(1);
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -88,7 +92,7 @@ async function regenerate() {
|
|
|
88
92
|
*/
|
|
89
93
|
async function show() {
|
|
90
94
|
try {
|
|
91
|
-
if (!
|
|
95
|
+
if (!(await ensureAuthenticated())) {
|
|
92
96
|
logger.error('Not logged in');
|
|
93
97
|
logger.newline();
|
|
94
98
|
logger.info('Login first:');
|
package/src/commands/list.js
CHANGED
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const {
|
|
6
|
+
const { ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
10
10
|
async function list() {
|
|
11
11
|
try {
|
|
12
|
-
// Check authentication
|
|
13
|
-
if (!
|
|
12
|
+
// Check authentication (with auto-login support)
|
|
13
|
+
if (!(await ensureAuthenticated())) {
|
|
14
14
|
logger.error('Not logged in');
|
|
15
|
-
logger.
|
|
16
|
-
logger.info('
|
|
17
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
15
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
16
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
18
17
|
process.exit(1);
|
|
19
18
|
}
|
|
20
19
|
|
package/src/commands/logs.js
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
|
|
9
9
|
async function logs(deploymentUuidArg, options) {
|
|
10
10
|
try {
|
|
11
11
|
// Check authentication
|
|
12
|
-
if (!
|
|
13
|
-
logger.error('Not logged in
|
|
12
|
+
if (!(await ensureAuthenticated())) {
|
|
13
|
+
logger.error('Not logged in');
|
|
14
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
15
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
14
16
|
process.exit(1);
|
|
15
17
|
}
|
|
16
18
|
|
package/src/commands/run.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getProjectConfig,
|
|
6
|
+
const { getProjectConfig, ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { spawn } = require('child_process');
|
|
9
9
|
const fs = require('fs');
|
|
@@ -55,8 +55,10 @@ async function getEnvironmentVariables(appUuid, forceRefresh = false) {
|
|
|
55
55
|
async function run(command, options = {}) {
|
|
56
56
|
try {
|
|
57
57
|
// Check authentication
|
|
58
|
-
if (!
|
|
59
|
-
logger.error('Not logged in
|
|
58
|
+
if (!(await ensureAuthenticated())) {
|
|
59
|
+
logger.error('Not logged in');
|
|
60
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
61
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
60
62
|
process.exit(1);
|
|
61
63
|
}
|
|
62
64
|
|
package/src/commands/sessions.js
CHANGED
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const {
|
|
6
|
+
const { ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
10
10
|
async function sessions() {
|
|
11
11
|
try {
|
|
12
12
|
// Check authentication
|
|
13
|
-
if (!
|
|
13
|
+
if (!(await ensureAuthenticated())) {
|
|
14
14
|
logger.error('Not logged in');
|
|
15
|
-
logger.
|
|
16
|
-
logger.info('
|
|
17
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
15
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
16
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
18
17
|
process.exit(1);
|
|
19
18
|
}
|
|
20
19
|
|
package/src/commands/shell.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const WebSocket = require('ws');
|
|
9
9
|
const readline = require('readline');
|
|
10
|
-
const { getProjectConfig,
|
|
10
|
+
const { getProjectConfig, ensureAuthenticated, getUser, getApiUrl } = require('../lib/config');
|
|
11
11
|
const logger = require('../lib/logger');
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -320,8 +320,10 @@ class ShellClient {
|
|
|
320
320
|
async function shell(options = {}) {
|
|
321
321
|
try {
|
|
322
322
|
// Check authentication
|
|
323
|
-
if (!
|
|
324
|
-
logger.error('Not logged in
|
|
323
|
+
if (!(await ensureAuthenticated())) {
|
|
324
|
+
logger.error('Not logged in');
|
|
325
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
326
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
325
327
|
process.exit(1);
|
|
326
328
|
}
|
|
327
329
|
|
package/src/commands/status.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const { getUser,
|
|
6
|
+
const { getUser, ensureAuthenticated, isTokenExpiringSoon } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
const { table } = require('table');
|
|
9
9
|
|
|
@@ -13,11 +13,10 @@ async function status() {
|
|
|
13
13
|
logger.newline();
|
|
14
14
|
|
|
15
15
|
// Check if logged in locally (silently)
|
|
16
|
-
if (!
|
|
16
|
+
if (!(await ensureAuthenticated())) {
|
|
17
17
|
logger.error('Not logged in');
|
|
18
|
-
logger.
|
|
19
|
-
logger.info('
|
|
20
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
18
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
19
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
21
20
|
process.exit(1);
|
|
22
21
|
}
|
|
23
22
|
|
package/src/commands/update.js
CHANGED
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const {
|
|
6
|
+
const { ensureAuthenticated, getProjectConfig } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
|
|
9
9
|
async function update(options) {
|
|
10
10
|
try {
|
|
11
|
-
// Check authentication
|
|
12
|
-
if (!
|
|
11
|
+
// Check authentication (with auto-login support)
|
|
12
|
+
if (!(await ensureAuthenticated())) {
|
|
13
13
|
logger.error('Not logged in');
|
|
14
|
-
logger.
|
|
15
|
-
logger.info('
|
|
16
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
14
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
15
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
17
16
|
process.exit(1);
|
|
18
17
|
}
|
|
19
18
|
|
package/src/commands/whoami.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const api = require('../lib/api');
|
|
6
|
-
const {
|
|
6
|
+
const { ensureAuthenticated } = require('../lib/config');
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -12,11 +12,10 @@ const logger = require('../lib/logger');
|
|
|
12
12
|
async function whoami() {
|
|
13
13
|
try {
|
|
14
14
|
// Check authentication
|
|
15
|
-
if (!
|
|
15
|
+
if (!(await ensureAuthenticated())) {
|
|
16
16
|
logger.error('Not logged in');
|
|
17
|
-
logger.
|
|
18
|
-
logger.info('
|
|
19
|
-
logger.log(' saac login -e <email> -k <api-key>');
|
|
17
|
+
logger.info('Run: saac login -e <email> -k <api-key>');
|
|
18
|
+
logger.info('Or set: SAAC_USER_API_KEY and SAAC_USER_EMAIL');
|
|
20
19
|
process.exit(1);
|
|
21
20
|
}
|
|
22
21
|
|
package/src/lib/config.js
CHANGED
|
@@ -106,6 +106,52 @@ function isTokenExpiringSoon() {
|
|
|
106
106
|
return expirationDate <= sevenDaysFromNow && !isTokenExpired();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Ensure user is authenticated, with auto-login support via environment variables
|
|
111
|
+
* Checks for SAAC_USER_API_KEY and SAAC_USER_EMAIL environment variables
|
|
112
|
+
* If present and user is not authenticated, attempts automatic login
|
|
113
|
+
*
|
|
114
|
+
* @returns {Promise<boolean>} - True if authenticated, false otherwise
|
|
115
|
+
*/
|
|
116
|
+
async function ensureAuthenticated() {
|
|
117
|
+
// Step 1: Check if already authenticated (fast path)
|
|
118
|
+
if (isAuthenticated()) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Step 2: Check for environment variables
|
|
123
|
+
const apiKey = process.env.SAAC_USER_API_KEY;
|
|
124
|
+
const email = process.env.SAAC_USER_EMAIL;
|
|
125
|
+
|
|
126
|
+
if (!apiKey || !email) {
|
|
127
|
+
// No environment variables - cannot auto-login
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Step 3: Attempt auto-login via API
|
|
132
|
+
try {
|
|
133
|
+
// Dynamically require to avoid circular dependency
|
|
134
|
+
const api = require('./api');
|
|
135
|
+
const result = await api.login(email, apiKey);
|
|
136
|
+
|
|
137
|
+
// Step 4: Save session token to config
|
|
138
|
+
saveUser({
|
|
139
|
+
email: result.user.email,
|
|
140
|
+
userId: result.user.id,
|
|
141
|
+
sessionToken: result.session_token,
|
|
142
|
+
expiresAt: result.expires_at,
|
|
143
|
+
verified: result.user.verified,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Auto-login successful
|
|
147
|
+
return true;
|
|
148
|
+
|
|
149
|
+
} catch (error) {
|
|
150
|
+
// Auto-login failed (invalid key, network error, etc.)
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
109
155
|
/**
|
|
110
156
|
* Get user info
|
|
111
157
|
*/
|
|
@@ -146,6 +192,7 @@ module.exports = {
|
|
|
146
192
|
getProjectConfig,
|
|
147
193
|
saveProjectConfig,
|
|
148
194
|
isAuthenticated,
|
|
195
|
+
ensureAuthenticated,
|
|
149
196
|
isTokenExpired,
|
|
150
197
|
isTokenExpiringSoon,
|
|
151
198
|
getUser,
|