@startanaicompany/cli 1.4.21 → 1.6.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.
@@ -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
 
@@ -90,6 +89,7 @@ async function create(name, options) {
90
89
  logger.info('Required options:');
91
90
  logger.log(' -s, --subdomain <subdomain> Subdomain for your app');
92
91
  logger.log(' -r, --repository <url> Git repository URL (SSH format)');
92
+ logger.log(' --org <organization_id> Organization ID');
93
93
  logger.newline();
94
94
  logger.info('Optional options:');
95
95
  logger.log(' -b, --branch <branch> Git branch (default: master)');
@@ -111,17 +111,17 @@ async function create(name, options) {
111
111
  logger.log(' --env <KEY=VALUE> Environment variable (can be used multiple times)');
112
112
  logger.newline();
113
113
  logger.info('Example:');
114
- logger.log(' saac create my-app -s myapp -r git@git.startanaicompany.com:user/repo.git');
115
- logger.log(' saac create api -s api -r git@git... --build-pack nixpacks --port 8080');
116
- logger.log(' saac create web -s web -r git@git... --health-check --pre-deploy-cmd "npm run migrate"');
114
+ logger.log(' saac create my-app -s myapp -r git@git.startanaicompany.com:user/repo.git --org <org_id>');
115
+ logger.log(' saac create api -s api -r git@git... --org <org_id> --build-pack nixpacks --port 8080');
116
+ logger.log(' saac create web -s web -r git@git... --org <org_id> --health-check --pre-deploy-cmd "npm run migrate"');
117
117
  process.exit(1);
118
118
  }
119
119
 
120
- if (!options.subdomain || !options.repository) {
121
- logger.error('Missing required options: subdomain and repository are required');
120
+ if (!options.subdomain || !options.repository || !options.org) {
121
+ logger.error('Missing required options: subdomain, repository, and organization ID are required');
122
122
  logger.newline();
123
123
  logger.info('Example:');
124
- logger.log(` saac create ${name} -s myapp -r git@git.startanaicompany.com:user/repo.git`);
124
+ logger.log(` saac create ${name} -s myapp -r git@git.startanaicompany.com:user/repo.git --org <org_id>`);
125
125
  logger.newline();
126
126
  logger.info('Note: Git OAuth connection required. Connect with: saac git connect');
127
127
  process.exit(1);
@@ -157,6 +157,7 @@ async function create(name, options) {
157
157
  domain_suffix: options.domainSuffix || 'startanaicompany.com',
158
158
  git_repository: options.repository,
159
159
  git_branch: options.branch || 'master',
160
+ organization_id: options.org,
160
161
  };
161
162
 
162
163
  // OAuth tokens are retrieved from database by wrapper
@@ -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
 
@@ -111,7 +113,7 @@ async function deleteApp(options) {
111
113
 
112
114
  logger.newline();
113
115
 
114
- logger.success(`Application '${result.application_name}' has been permanently deleted.`);
116
+ logger.success(`Application '${app.name}' has been permanently deleted.`);
115
117
 
116
118
  logger.newline();
117
119
 
@@ -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
 
@@ -83,61 +87,6 @@ async function regenerate() {
83
87
  }
84
88
  }
85
89
 
86
- /**
87
- * Show API key info (without revealing full key)
88
- */
89
- async function show() {
90
- try {
91
- if (!isAuthenticated()) {
92
- logger.error('Not logged in');
93
- logger.newline();
94
- logger.info('Login first:');
95
- logger.log(' saac login -e <email>');
96
- process.exit(1);
97
- }
98
-
99
- logger.section('API Key Information');
100
- logger.newline();
101
-
102
- const spin = logger.spinner('Fetching API key info...').start();
103
-
104
- try {
105
- const result = await api.getApiKeyInfo();
106
-
107
- spin.succeed('API key info retrieved');
108
-
109
- logger.newline();
110
- logger.field('Key Prefix', result.key_prefix); // e.g., "cw_RJ1gH8..."
111
- logger.field('Created', new Date(result.created_at).toLocaleDateString());
112
- logger.field('Last Used', result.last_used_at
113
- ? new Date(result.last_used_at).toLocaleString()
114
- : 'Never');
115
-
116
- logger.newline();
117
- logger.info('Commands:');
118
- logger.log(' saac keys regenerate Generate new API key');
119
- logger.log(' saac sessions View active sessions');
120
-
121
- } catch (error) {
122
- spin.fail('Failed to fetch API key info');
123
-
124
- // If endpoint doesn't exist yet, show helpful message
125
- if (error.response?.status === 404) {
126
- logger.newline();
127
- logger.warn('API key info endpoint not available yet');
128
- logger.info('You can still regenerate your key with:');
129
- logger.log(' saac keys regenerate');
130
- } else {
131
- throw error;
132
- }
133
- }
134
- } catch (error) {
135
- logger.error(error.response?.data?.message || error.message);
136
- process.exit(1);
137
- }
138
- }
139
-
140
90
  module.exports = {
141
91
  regenerate,
142
- show,
143
92
  };
@@ -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