@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 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
- **API Key Flow (CI/CD & Scripts):**
150
- 1. User logs in with email + API key → API returns session token
151
- 2. Environment variable `SAAC_API_KEY` can override stored credentials
152
- 3. Useful for automation, scripts, and CI/CD pipelines
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 api.js createClient()
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
- **What it does:**
396
- 1. Fetches all your SAAC applications
397
- 2. Shows interactive list to select from
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
- Select Application
406
- ──────────────────
791
+ Initialize SAAC Project
792
+ ───────────────────────
407
793
 
408
- ? Which application do you want to link to this directory?
409
- ❯ mysimpleflowershop (mysimpleflowershop.startanaicompany.com)
410
- api-server (api.startanaicompany.com)
411
- landing-page (landing.startanaicompany.com)
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
- Application linked successfully!
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
- ℹ Configuration saved to .saac/config.json
416
- You can now use:
417
- saac deploy - Deploy application
418
- saac logs - View logs
419
- saac status - Check status
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/cli",
3
- "version": "1.4.21",
3
+ "version": "1.5.0",
4
4
  "description": "Official CLI for StartAnAiCompany.com - Deploy AI recruitment sites with ease",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { isAuthenticated, saveProjectConfig, getUser, getProjectConfig } = require('../lib/config');
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 (!isAuthenticated()) {
15
+ // Check authentication (with auto-login support)
16
+ if (!(await ensureAuthenticated())) {
17
17
  logger.error('Not logged in');
18
- logger.newline();
19
- logger.info('Run:');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
20
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,15 +3,17 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
14
- logger.error('Not logged in. Run: saac login');
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, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
14
- logger.error('Not logged in. Run: saac login');
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,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated, saveProjectConfig } = require('../lib/config');
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 (!isAuthenticated()) {
18
- logger.error('Not logged in. Run: saac login');
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 (!isAuthenticated()) {
153
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
18
- logger.error('Not logged in. Run: saac login');
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 (!isAuthenticated()) {
112
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
19
- logger.error('Not logged in. Run: saac login');
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 (!isAuthenticated()) {
160
- logger.error('Not logged in. Run: saac login');
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
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  const oauth = require('../lib/oauth');
6
6
  const api = require('../lib/api');
7
- const { isAuthenticated, getUser } = require('../lib/config');
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 (!isAuthenticated()) {
18
+ if (!(await ensureAuthenticated())) {
19
19
  logger.error('Not logged in');
20
- logger.newline();
21
- logger.info('Run:');
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 (!isAuthenticated()) {
118
+ if (!(await ensureAuthenticated())) {
120
119
  logger.error('Not logged in');
121
- logger.newline();
122
- logger.info('Run:');
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 (!isAuthenticated()) {
194
+ if (!(await ensureAuthenticated())) {
197
195
  logger.error('Not logged in');
198
- logger.newline();
199
- logger.info('Run:');
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 (!isAuthenticated()) {
254
+ if (!(await ensureAuthenticated())) {
258
255
  logger.error('Not logged in');
259
- logger.newline();
260
- logger.info('Run:');
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
 
@@ -7,18 +7,20 @@
7
7
  */
8
8
 
9
9
  const api = require('../lib/api');
10
- const { isAuthenticated, saveProjectConfig, getProjectConfig } = require('../lib/config');
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 (!isAuthenticated()) {
19
+ // Check authentication (with auto-login support)
20
+ if (!(await ensureAuthenticated())) {
18
21
  logger.error('Not logged in');
19
- logger.newline();
20
- logger.info('Run:');
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
- // Interactive: Let user select application
115
- const choices = applications.map(app => ({
116
- name: `${app.name} - ${app.domain || `${app.subdomain}.startanaicompany.com`} (${app.status})`,
117
- value: app,
118
- }));
119
-
120
- const { selectedApp } = await inquirer.prompt([
121
- {
122
- type: 'list',
123
- name: 'selectedApp',
124
- message: 'Select application to link to this directory:',
125
- choices: choices,
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getUser, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
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 (!isAuthenticated()) {
95
+ if (!(await ensureAuthenticated())) {
92
96
  logger.error('Not logged in');
93
97
  logger.newline();
94
98
  logger.info('Login first:');
@@ -3,18 +3,17 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
12
+ // Check authentication (with auto-login support)
13
+ if (!(await ensureAuthenticated())) {
14
14
  logger.error('Not logged in');
15
- logger.newline();
16
- logger.info('Run:');
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
 
@@ -3,14 +3,16 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
13
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getProjectConfig, isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
59
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,18 +3,17 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
13
+ if (!(await ensureAuthenticated())) {
14
14
  logger.error('Not logged in');
15
- logger.newline();
16
- logger.info('Run:');
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
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  const WebSocket = require('ws');
9
9
  const readline = require('readline');
10
- const { getProjectConfig, isAuthenticated, getUser, getApiUrl } = require('../lib/config');
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 (!isAuthenticated()) {
324
- logger.error('Not logged in. Run: saac login');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { getUser, isAuthenticated, isTokenExpiringSoon } = require('../lib/config');
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 (!isAuthenticated()) {
16
+ if (!(await ensureAuthenticated())) {
17
17
  logger.error('Not logged in');
18
- logger.newline();
19
- logger.info('Run:');
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
 
@@ -3,17 +3,16 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { isAuthenticated, getProjectConfig } = require('../lib/config');
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 (!isAuthenticated()) {
11
+ // Check authentication (with auto-login support)
12
+ if (!(await ensureAuthenticated())) {
13
13
  logger.error('Not logged in');
14
- logger.newline();
15
- logger.info('Run:');
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
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  const api = require('../lib/api');
6
- const { isAuthenticated } = require('../lib/config');
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 (!isAuthenticated()) {
15
+ if (!(await ensureAuthenticated())) {
16
16
  logger.error('Not logged in');
17
- logger.newline();
18
- logger.info('Run:');
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,