@startanaicompany/cli 1.0.0 → 1.3.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 +358 -0
- package/README.md +98 -14
- package/auth_session_update.md +785 -0
- package/bin/saac.js +73 -3
- package/create-application-update.md +759 -0
- package/package.json +2 -2
- package/src/commands/create.js +278 -4
- package/src/commands/login.js +38 -44
- package/src/commands/logout.js +41 -3
- package/src/commands/logoutAll.js +74 -0
- package/src/commands/register.js +46 -34
- package/src/commands/sessions.js +75 -0
- package/src/commands/status.js +164 -4
- package/src/commands/update.js +284 -0
- package/src/commands/verify.js +32 -4
- package/src/lib/api.js +47 -1
- package/src/lib/config.js +60 -11
- package/test-session-token.js +117 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startanaicompany/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Official CLI for StartAnAiCompany.com - Deploy AI recruitment sites with ease",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"saac": "
|
|
7
|
+
"saac": "bin/saac.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1",
|
package/src/commands/create.js
CHANGED
|
@@ -1,4 +1,278 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Create command - Create a new application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { isAuthenticated, saveProjectConfig } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
|
|
9
|
+
async function create(name, options) {
|
|
10
|
+
try {
|
|
11
|
+
// Check authentication
|
|
12
|
+
if (!isAuthenticated()) {
|
|
13
|
+
logger.error('Not logged in');
|
|
14
|
+
logger.newline();
|
|
15
|
+
logger.info('Run:');
|
|
16
|
+
logger.log(' saac login -e <email> -k <api-key>');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Validate required fields
|
|
21
|
+
if (!name) {
|
|
22
|
+
logger.error('Application name is required');
|
|
23
|
+
logger.newline();
|
|
24
|
+
logger.info('Usage:');
|
|
25
|
+
logger.log(' saac create <name> [options]');
|
|
26
|
+
logger.newline();
|
|
27
|
+
logger.info('Required options:');
|
|
28
|
+
logger.log(' -s, --subdomain <subdomain> Subdomain for your app');
|
|
29
|
+
logger.log(' -r, --repository <url> Git repository URL (SSH format)');
|
|
30
|
+
logger.log(' -t, --git-token <token> Git API token');
|
|
31
|
+
logger.newline();
|
|
32
|
+
logger.info('Optional options:');
|
|
33
|
+
logger.log(' -b, --branch <branch> Git branch (default: master)');
|
|
34
|
+
logger.log(' -d, --domain-suffix <suffix> Domain suffix (default: startanaicompany.com)');
|
|
35
|
+
logger.log(' -p, --port <port> Port to expose (default: 3000)');
|
|
36
|
+
logger.log(' --build-pack <pack> Build pack: dockercompose, nixpacks, dockerfile, static');
|
|
37
|
+
logger.log(' --install-cmd <command> Install command (e.g., "pnpm install")');
|
|
38
|
+
logger.log(' --build-cmd <command> Build command (e.g., "npm run build")');
|
|
39
|
+
logger.log(' --start-cmd <command> Start command (e.g., "node server.js")');
|
|
40
|
+
logger.log(' --pre-deploy-cmd <command> Pre-deployment command (e.g., "npm run migrate")');
|
|
41
|
+
logger.log(' --post-deploy-cmd <command> Post-deployment command (e.g., "npm run seed")');
|
|
42
|
+
logger.log(' --health-check Enable health checks');
|
|
43
|
+
logger.log(' --health-path <path> Health check path (default: /health)');
|
|
44
|
+
logger.log(' --health-interval <seconds> Health check interval in seconds');
|
|
45
|
+
logger.log(' --health-timeout <seconds> Health check timeout in seconds');
|
|
46
|
+
logger.log(' --health-retries <count> Health check retries (1-10)');
|
|
47
|
+
logger.log(' --cpu-limit <limit> CPU limit (e.g., "1", "2.5")');
|
|
48
|
+
logger.log(' --memory-limit <limit> Memory limit (e.g., "512M", "2G")');
|
|
49
|
+
logger.log(' --env <KEY=VALUE> Environment variable (can be used multiple times)');
|
|
50
|
+
logger.newline();
|
|
51
|
+
logger.info('Example:');
|
|
52
|
+
logger.log(' saac create my-app -s myapp -r git@git.startanaicompany.com:user/repo.git -t abc123');
|
|
53
|
+
logger.log(' saac create api -s api -r git@git... -t abc123 --build-pack nixpacks --port 8080');
|
|
54
|
+
logger.log(' saac create web -s web -r git@git... -t abc123 --health-check --pre-deploy-cmd "npm run migrate"');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!options.subdomain || !options.repository || !options.gitToken) {
|
|
59
|
+
logger.error('Missing required options: subdomain, repository, and git-token are required');
|
|
60
|
+
logger.newline();
|
|
61
|
+
logger.info('Example:');
|
|
62
|
+
logger.log(` saac create ${name} -s myapp -r git@git.startanaicompany.com:user/repo.git -t your_token`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
logger.section(`Creating Application: ${name}`);
|
|
67
|
+
logger.newline();
|
|
68
|
+
|
|
69
|
+
// Build application payload
|
|
70
|
+
const appData = {
|
|
71
|
+
name: name,
|
|
72
|
+
subdomain: options.subdomain,
|
|
73
|
+
domain_suffix: options.domainSuffix || 'startanaicompany.com',
|
|
74
|
+
git_repository: options.repository,
|
|
75
|
+
git_branch: options.branch || 'master',
|
|
76
|
+
git_api_token: options.gitToken,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Optional: Port configuration
|
|
80
|
+
if (options.port) {
|
|
81
|
+
appData.ports_exposes = options.port;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Optional: Build pack
|
|
85
|
+
if (options.buildPack) {
|
|
86
|
+
const validBuildPacks = ['dockercompose', 'nixpacks', 'dockerfile', 'static'];
|
|
87
|
+
if (!validBuildPacks.includes(options.buildPack)) {
|
|
88
|
+
logger.error(`Invalid build pack: ${options.buildPack}`);
|
|
89
|
+
logger.info(`Must be one of: ${validBuildPacks.join(', ')}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
appData.build_pack = options.buildPack;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Optional: Custom commands
|
|
96
|
+
if (options.installCmd) {
|
|
97
|
+
appData.install_command = options.installCmd;
|
|
98
|
+
}
|
|
99
|
+
if (options.buildCmd) {
|
|
100
|
+
appData.build_command = options.buildCmd;
|
|
101
|
+
}
|
|
102
|
+
if (options.startCmd) {
|
|
103
|
+
appData.start_command = options.startCmd;
|
|
104
|
+
}
|
|
105
|
+
if (options.preDeployCmd) {
|
|
106
|
+
appData.pre_deployment_command = options.preDeployCmd;
|
|
107
|
+
}
|
|
108
|
+
if (options.postDeployCmd) {
|
|
109
|
+
appData.post_deployment_command = options.postDeployCmd;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Optional: Resource limits
|
|
113
|
+
if (options.cpuLimit) {
|
|
114
|
+
appData.cpu_limit = options.cpuLimit;
|
|
115
|
+
}
|
|
116
|
+
if (options.memoryLimit) {
|
|
117
|
+
appData.memory_limit = options.memoryLimit;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Optional: Health check configuration
|
|
121
|
+
if (options.healthCheck) {
|
|
122
|
+
appData.health_check_enabled = true;
|
|
123
|
+
if (options.healthPath) {
|
|
124
|
+
appData.health_check_path = options.healthPath;
|
|
125
|
+
}
|
|
126
|
+
if (options.healthInterval) {
|
|
127
|
+
appData.health_check_interval = parseInt(options.healthInterval, 10);
|
|
128
|
+
}
|
|
129
|
+
if (options.healthTimeout) {
|
|
130
|
+
appData.health_check_timeout = parseInt(options.healthTimeout, 10);
|
|
131
|
+
}
|
|
132
|
+
if (options.healthRetries) {
|
|
133
|
+
const retries = parseInt(options.healthRetries, 10);
|
|
134
|
+
if (retries < 1 || retries > 10) {
|
|
135
|
+
logger.error('Health check retries must be between 1 and 10');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
appData.health_check_retries = retries;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Optional: Environment variables
|
|
143
|
+
if (options.env) {
|
|
144
|
+
const envVars = {};
|
|
145
|
+
const envArray = Array.isArray(options.env) ? options.env : [options.env];
|
|
146
|
+
|
|
147
|
+
for (const envStr of envArray) {
|
|
148
|
+
const [key, ...valueParts] = envStr.split('=');
|
|
149
|
+
const value = valueParts.join('='); // Handle values with '=' in them
|
|
150
|
+
|
|
151
|
+
if (!key || value === undefined) {
|
|
152
|
+
logger.error(`Invalid environment variable format: ${envStr}`);
|
|
153
|
+
logger.info('Use format: KEY=VALUE');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
envVars[key] = value;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (Object.keys(envVars).length > 50) {
|
|
161
|
+
logger.error('Maximum 50 environment variables allowed');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
appData.environment_variables = envVars;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Show configuration summary
|
|
169
|
+
logger.info('Configuration:');
|
|
170
|
+
logger.field('Name', appData.name);
|
|
171
|
+
logger.field('Subdomain', `${appData.subdomain}.${appData.domain_suffix}`);
|
|
172
|
+
logger.field('Repository', appData.git_repository);
|
|
173
|
+
logger.field('Branch', appData.git_branch);
|
|
174
|
+
if (appData.ports_exposes) {
|
|
175
|
+
logger.field('Port', appData.ports_exposes);
|
|
176
|
+
}
|
|
177
|
+
if (appData.build_pack) {
|
|
178
|
+
logger.field('Build Pack', appData.build_pack);
|
|
179
|
+
}
|
|
180
|
+
if (appData.cpu_limit || appData.memory_limit) {
|
|
181
|
+
const limits = [];
|
|
182
|
+
if (appData.cpu_limit) limits.push(`CPU: ${appData.cpu_limit}`);
|
|
183
|
+
if (appData.memory_limit) limits.push(`Memory: ${appData.memory_limit}`);
|
|
184
|
+
logger.field('Resource Limits', limits.join(', '));
|
|
185
|
+
logger.warn('Note: Free tier limited to 1 vCPU, 1024M RAM');
|
|
186
|
+
}
|
|
187
|
+
if (appData.health_check_enabled) {
|
|
188
|
+
logger.field('Health Check', `Enabled on ${appData.health_check_path || '/health'}`);
|
|
189
|
+
}
|
|
190
|
+
if (appData.pre_deployment_command) {
|
|
191
|
+
logger.field('Pre-Deploy Hook', appData.pre_deployment_command);
|
|
192
|
+
}
|
|
193
|
+
if (appData.environment_variables) {
|
|
194
|
+
logger.field('Environment Vars', `${Object.keys(appData.environment_variables).length} variable(s)`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logger.newline();
|
|
198
|
+
|
|
199
|
+
const spin = logger.spinner('Creating application...').start();
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const result = await api.createApplication(appData);
|
|
203
|
+
|
|
204
|
+
spin.succeed('Application created successfully!');
|
|
205
|
+
|
|
206
|
+
// Save project configuration
|
|
207
|
+
saveProjectConfig({
|
|
208
|
+
applicationUuid: result.coolify_app_uuid,
|
|
209
|
+
applicationName: result.app_name,
|
|
210
|
+
subdomain: result.subdomain,
|
|
211
|
+
domainSuffix: appData.domain_suffix,
|
|
212
|
+
gitRepository: appData.git_repository,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
logger.newline();
|
|
216
|
+
logger.success('Application created!');
|
|
217
|
+
logger.newline();
|
|
218
|
+
logger.field('Name', result.app_name);
|
|
219
|
+
logger.field('Domain', result.domain);
|
|
220
|
+
logger.field('UUID', result.coolify_app_uuid);
|
|
221
|
+
logger.field('Status', result.deployment_status);
|
|
222
|
+
logger.newline();
|
|
223
|
+
|
|
224
|
+
// Show next steps
|
|
225
|
+
if (result.next_steps && result.next_steps.length > 0) {
|
|
226
|
+
logger.info('Next Steps:');
|
|
227
|
+
result.next_steps.forEach((step, index) => {
|
|
228
|
+
logger.log(` ${index + 1}. ${step}`);
|
|
229
|
+
});
|
|
230
|
+
logger.newline();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
logger.info('Useful commands:');
|
|
234
|
+
logger.log(` saac deploy Deploy your application`);
|
|
235
|
+
logger.log(` saac logs --follow View deployment logs`);
|
|
236
|
+
logger.log(` saac status Check application status`);
|
|
237
|
+
logger.log(` saac env set KEY=VALUE Set environment variables`);
|
|
238
|
+
|
|
239
|
+
} catch (error) {
|
|
240
|
+
spin.fail('Application creation failed');
|
|
241
|
+
|
|
242
|
+
if (error.response?.status === 403) {
|
|
243
|
+
const data = error.response.data;
|
|
244
|
+
logger.newline();
|
|
245
|
+
logger.error('Quota exceeded');
|
|
246
|
+
if (data.current_tier) {
|
|
247
|
+
logger.field('Current Tier', data.current_tier);
|
|
248
|
+
}
|
|
249
|
+
logger.newline();
|
|
250
|
+
logger.warn(data.error || data.message);
|
|
251
|
+
if (data.upgrade_info) {
|
|
252
|
+
logger.info(data.upgrade_info);
|
|
253
|
+
}
|
|
254
|
+
} else if (error.response?.status === 400) {
|
|
255
|
+
const data = error.response.data;
|
|
256
|
+
logger.newline();
|
|
257
|
+
logger.error('Validation failed');
|
|
258
|
+
if (data.details) {
|
|
259
|
+
logger.newline();
|
|
260
|
+
Object.entries(data.details).forEach(([field, message]) => {
|
|
261
|
+
logger.log(` ${logger.chalk.yellow(field)}: ${message}`);
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
logger.log(` ${data.message || data.error}`);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} catch (error) {
|
|
273
|
+
logger.error(error.response?.data?.message || error.message);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = create;
|
package/src/commands/login.js
CHANGED
|
@@ -10,65 +10,59 @@ const logger = require('../lib/logger');
|
|
|
10
10
|
|
|
11
11
|
async function login(options) {
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!validator.isEmail(input)) {
|
|
25
|
-
return 'Please enter a valid email address';
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
]);
|
|
31
|
-
email = answers.email;
|
|
13
|
+
// Require both email and API key flags (no interactive prompts)
|
|
14
|
+
if (!options.email || !options.apiKey) {
|
|
15
|
+
logger.error('Email and API key are required');
|
|
16
|
+
logger.newline();
|
|
17
|
+
logger.info('Usage:');
|
|
18
|
+
logger.log(' saac login -e <email> -k <api-key>');
|
|
19
|
+
logger.log(' saac login --email <email> --api-key <api-key>');
|
|
20
|
+
logger.newline();
|
|
21
|
+
logger.info('Example:');
|
|
22
|
+
logger.log(' saac login -e user@example.com -k cw_your_api_key');
|
|
23
|
+
process.exit(1);
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
message: 'API Key:',
|
|
42
|
-
mask: '*',
|
|
43
|
-
},
|
|
44
|
-
]);
|
|
45
|
-
apiKey = answers.apiKey;
|
|
26
|
+
const email = options.email;
|
|
27
|
+
const apiKey = options.apiKey;
|
|
28
|
+
|
|
29
|
+
// Validate email format
|
|
30
|
+
if (!validator.isEmail(email)) {
|
|
31
|
+
logger.error('Invalid email address');
|
|
32
|
+
process.exit(1);
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
const spin = logger.spinner('Verifying credentials...').start();
|
|
35
|
+
logger.section('Login to StartAnAiCompany');
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
saveUser({ email, apiKey });
|
|
37
|
+
// Login and get session token
|
|
38
|
+
const spin = logger.spinner('Logging in...').start();
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
try {
|
|
41
|
+
// Call /auth/login endpoint to get session token
|
|
42
|
+
const result = await api.login(email, apiKey);
|
|
56
43
|
|
|
57
44
|
spin.succeed('Login successful!');
|
|
58
45
|
|
|
59
|
-
//
|
|
46
|
+
// Save session token and expiration
|
|
60
47
|
saveUser({
|
|
61
|
-
email:
|
|
62
|
-
userId:
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
email: result.user.email || email,
|
|
49
|
+
userId: result.user.id,
|
|
50
|
+
sessionToken: result.session_token,
|
|
51
|
+
expiresAt: result.expires_at,
|
|
52
|
+
verified: result.user.verified,
|
|
65
53
|
});
|
|
66
54
|
|
|
67
55
|
logger.newline();
|
|
68
56
|
logger.success('You are now logged in!');
|
|
69
57
|
logger.newline();
|
|
70
|
-
logger.field('Email',
|
|
71
|
-
logger.field('Verified',
|
|
58
|
+
logger.field('Email', result.user.email || email);
|
|
59
|
+
logger.field('Verified', result.user.verified ? 'Yes' : 'No');
|
|
60
|
+
|
|
61
|
+
// Show expiration date
|
|
62
|
+
if (result.expires_at) {
|
|
63
|
+
const expirationDate = new Date(result.expires_at);
|
|
64
|
+
logger.field('Session expires', expirationDate.toLocaleDateString());
|
|
65
|
+
}
|
|
72
66
|
|
|
73
67
|
} catch (error) {
|
|
74
68
|
spin.fail('Login failed');
|
package/src/commands/logout.js
CHANGED
|
@@ -1,9 +1,47 @@
|
|
|
1
|
-
const
|
|
1
|
+
const api = require('../lib/api');
|
|
2
|
+
const { clearUser, getUser } = require('../lib/config');
|
|
2
3
|
const logger = require('../lib/logger');
|
|
3
4
|
|
|
4
5
|
async function logout() {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
try {
|
|
7
|
+
const user = getUser();
|
|
8
|
+
|
|
9
|
+
if (!user || (!user.sessionToken && !user.apiKey)) {
|
|
10
|
+
logger.warn('You are not logged in');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
logger.section('Logout from StartAnAiCompany');
|
|
15
|
+
|
|
16
|
+
const spin = logger.spinner('Logging out...').start();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Try to revoke session on server
|
|
20
|
+
const client = api.createClient();
|
|
21
|
+
await client.post('/auth/logout');
|
|
22
|
+
|
|
23
|
+
// Clear local config
|
|
24
|
+
clearUser();
|
|
25
|
+
|
|
26
|
+
spin.succeed('Logout successful!');
|
|
27
|
+
logger.success('You have been logged out from this device');
|
|
28
|
+
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// Even if server call fails, clear local config
|
|
31
|
+
clearUser();
|
|
32
|
+
|
|
33
|
+
if (error.response?.status === 401) {
|
|
34
|
+
// Session already invalid
|
|
35
|
+
spin.succeed('Logged out locally (session was expired)');
|
|
36
|
+
} else {
|
|
37
|
+
spin.warn('Logged out locally (server error)');
|
|
38
|
+
logger.warn('Session may still be active on server');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error(error.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
7
45
|
}
|
|
8
46
|
|
|
9
47
|
module.exports = logout;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout All command - Revoke all sessions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const api = require('../lib/api');
|
|
7
|
+
const { clearUser, isAuthenticated } = require('../lib/config');
|
|
8
|
+
const logger = require('../lib/logger');
|
|
9
|
+
|
|
10
|
+
async function logoutAll(options) {
|
|
11
|
+
try {
|
|
12
|
+
// Check authentication
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
logger.error('Not logged in');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
logger.section('Logout from All Devices');
|
|
19
|
+
|
|
20
|
+
// Confirm unless --yes flag is provided
|
|
21
|
+
if (!options.yes) {
|
|
22
|
+
logger.warn('This will logout from ALL devices where you are logged in');
|
|
23
|
+
logger.newline();
|
|
24
|
+
|
|
25
|
+
const answers = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'confirm',
|
|
29
|
+
message: 'Are you sure you want to continue?',
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
if (!answers.confirm) {
|
|
35
|
+
logger.info('Cancelled');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spin = logger.spinner('Revoking all sessions...').start();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const client = api.createClient();
|
|
44
|
+
const response = await client.post('/auth/logout-all');
|
|
45
|
+
|
|
46
|
+
// Clear local config
|
|
47
|
+
clearUser();
|
|
48
|
+
|
|
49
|
+
const count = response.data.sessions_revoked || 0;
|
|
50
|
+
spin.succeed(`Logged out from ${count} device(s)!`);
|
|
51
|
+
|
|
52
|
+
logger.newline();
|
|
53
|
+
logger.success('All sessions have been revoked');
|
|
54
|
+
logger.info('You will need to login again on all devices');
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Even if server call fails, clear local config
|
|
58
|
+
clearUser();
|
|
59
|
+
|
|
60
|
+
if (error.response?.status === 401) {
|
|
61
|
+
spin.succeed('Logged out locally (session was expired)');
|
|
62
|
+
} else {
|
|
63
|
+
spin.fail('Failed to revoke sessions on server');
|
|
64
|
+
logger.warn('Logged out locally, but server sessions may still be active');
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error(error.response?.data?.message || error.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = logoutAll;
|
package/src/commands/register.js
CHANGED
|
@@ -10,40 +10,32 @@ const logger = require('../lib/logger');
|
|
|
10
10
|
|
|
11
11
|
async function register(options) {
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return 'Please enter a valid email address';
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
]);
|
|
31
|
-
email = answers.email;
|
|
13
|
+
// Require email flag (no interactive prompt)
|
|
14
|
+
if (!options.email) {
|
|
15
|
+
logger.error('Email is required');
|
|
16
|
+
logger.newline();
|
|
17
|
+
logger.info('Usage:');
|
|
18
|
+
logger.log(' saac register -e <email>');
|
|
19
|
+
logger.log(' saac register --email <email> [--gitea-username <username>]');
|
|
20
|
+
logger.newline();
|
|
21
|
+
logger.info('Example:');
|
|
22
|
+
logger.log(' saac register -e user@example.com');
|
|
23
|
+
logger.log(' saac register -e user@example.com --gitea-username myuser');
|
|
24
|
+
process.exit(1);
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
name: 'giteaUsername',
|
|
41
|
-
message: 'Gitea username (optional, press Enter to skip):',
|
|
42
|
-
},
|
|
43
|
-
]);
|
|
44
|
-
giteaUsername = answers.giteaUsername || undefined;
|
|
27
|
+
const email = options.email;
|
|
28
|
+
|
|
29
|
+
// Validate email format
|
|
30
|
+
if (!validator.isEmail(email)) {
|
|
31
|
+
logger.error('Invalid email address');
|
|
32
|
+
process.exit(1);
|
|
45
33
|
}
|
|
46
34
|
|
|
35
|
+
const giteaUsername = options.giteaUsername || undefined;
|
|
36
|
+
|
|
37
|
+
logger.section('Register for StartAnAiCompany');
|
|
38
|
+
|
|
47
39
|
// Register via API
|
|
48
40
|
const spin = logger.spinner('Creating account...').start();
|
|
49
41
|
|
|
@@ -53,12 +45,22 @@ async function register(options) {
|
|
|
53
45
|
spin.succeed('Account created successfully!');
|
|
54
46
|
|
|
55
47
|
// Save user info
|
|
56
|
-
|
|
48
|
+
// If backend returns session_token, use it; otherwise fall back to api_key
|
|
49
|
+
const userData = {
|
|
57
50
|
email: result.email || email,
|
|
58
51
|
userId: result.user_id,
|
|
59
|
-
apiKey: result.api_key,
|
|
60
52
|
verified: result.verified || false,
|
|
61
|
-
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (result.session_token) {
|
|
56
|
+
userData.sessionToken = result.session_token;
|
|
57
|
+
userData.expiresAt = result.expires_at;
|
|
58
|
+
} else {
|
|
59
|
+
// Backward compatibility: use API key if session token not provided
|
|
60
|
+
userData.apiKey = result.api_key;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
saveUser(userData);
|
|
62
64
|
|
|
63
65
|
logger.newline();
|
|
64
66
|
logger.success('Registration complete!');
|
|
@@ -81,7 +83,17 @@ async function register(options) {
|
|
|
81
83
|
|
|
82
84
|
logger.newline();
|
|
83
85
|
logger.field('Email', email);
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
// Show session token or API key info
|
|
88
|
+
if (result.session_token) {
|
|
89
|
+
logger.field('Session Token', result.session_token.substring(0, 20) + '...');
|
|
90
|
+
if (result.expires_at) {
|
|
91
|
+
const expirationDate = new Date(result.expires_at);
|
|
92
|
+
logger.field('Expires', expirationDate.toLocaleDateString());
|
|
93
|
+
}
|
|
94
|
+
} else if (result.api_key) {
|
|
95
|
+
logger.field('API Key', result.api_key.substring(0, 20) + '...');
|
|
96
|
+
}
|
|
85
97
|
|
|
86
98
|
} catch (error) {
|
|
87
99
|
spin.fail('Registration failed');
|