@orcapt/cli 1.0.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.
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Orca Login Command
3
+ * Authenticates users before using kickstart commands
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const chalk = require('chalk');
8
+ const ora = require('ora');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+ const https = require('https');
13
+ const http = require('http');
14
+ const { API_BASE_URL, API_ENDPOINTS } = require('../config');
15
+
16
+ // Config file location in user's home directory
17
+ const CONFIG_DIR = path.join(os.homedir(), '.orca');
18
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
19
+
20
+ /**
21
+ * Make API request to authenticate
22
+ */
23
+ function authenticate(mode, workspace, token) {
24
+ return new Promise((resolve, reject) => {
25
+ // Parse API URL from config
26
+ const apiUrl = new URL(API_ENDPOINTS.AUTH, API_BASE_URL);
27
+ const hostname = apiUrl.hostname;
28
+ const pathName = apiUrl.pathname;
29
+ const isHttps = apiUrl.protocol === 'https:';
30
+ const httpModule = isHttps ? https : http;
31
+ // Map legacy 'dev' selection to 'pro' for the API
32
+ const modeValue = mode === 'team' ? 'team' : 'dev';
33
+
34
+ const postData = JSON.stringify({
35
+ workspace,
36
+ token,
37
+ mode: modeValue
38
+ });
39
+
40
+ const options = {
41
+ hostname,
42
+ port: apiUrl.port || (isHttps ? 443 : 80),
43
+ path: pathName,
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ 'Content-Length': Buffer.byteLength(postData)
48
+ }
49
+ };
50
+
51
+ const req = httpModule.request(options, (res) => {
52
+ let data = '';
53
+
54
+ res.on('data', (chunk) => {
55
+ data += chunk;
56
+ });
57
+
58
+ res.on('end', () => {
59
+ try {
60
+ const response = JSON.parse(data);
61
+ if (response.pass === true) {
62
+ resolve(true);
63
+ } else {
64
+ resolve(false);
65
+ }
66
+ } catch (error) {
67
+ reject(new Error('Invalid response from server'));
68
+ }
69
+ });
70
+ });
71
+
72
+ req.on('error', (error) => {
73
+ reject(error);
74
+ });
75
+
76
+ req.write(postData);
77
+ req.end();
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Save credentials to config file
83
+ */
84
+ function saveCredentials(mode, workspace, token) {
85
+ try {
86
+ // Create .orca directory if it doesn't exist
87
+ if (!fs.existsSync(CONFIG_DIR)) {
88
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
89
+ }
90
+
91
+ const config = {
92
+ mode,
93
+ workspace,
94
+ token,
95
+ authenticated: true,
96
+ timestamp: new Date().toISOString()
97
+ };
98
+
99
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
100
+ return true;
101
+ } catch (error) {
102
+ console.error(chalk.red('Failed to save credentials:', error.message));
103
+ return false;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Login command handler
109
+ */
110
+ async function login() {
111
+ console.log(chalk.cyan('\n============================================================'));
112
+ console.log(chalk.cyan('🔐 Orca Login'));
113
+ console.log(chalk.cyan('============================================================\n'));
114
+
115
+ let authenticated = false;
116
+ let retryCount = 0;
117
+ const MAX_RETRIES = 3;
118
+
119
+ while (!authenticated && retryCount < MAX_RETRIES) {
120
+ try {
121
+ // Ask for mode
122
+ const modeAnswer = await inquirer.prompt([
123
+ {
124
+ type: 'list',
125
+ name: 'mode',
126
+ message: 'Select your Orca mode:',
127
+ choices: [
128
+ { name: 'Sandbox/Pro mode', value: 'dev' },
129
+ { name: 'Team mode', value: 'team' }
130
+ ]
131
+ }
132
+ ]);
133
+
134
+ // Ask for workspace name
135
+ const workspaceAnswer = await inquirer.prompt([
136
+ {
137
+ type: 'input',
138
+ name: 'workspace',
139
+ message: 'Enter your workspace name:',
140
+ validate: (input) => {
141
+ if (input.trim().length === 0) {
142
+ return 'Workspace name cannot be empty';
143
+ }
144
+ return true;
145
+ }
146
+ }
147
+ ]);
148
+
149
+ // Ask for token
150
+ const tokenAnswer = await inquirer.prompt([
151
+ {
152
+ type: 'password',
153
+ name: 'token',
154
+ message: 'Enter your authentication token:',
155
+ mask: '*',
156
+ validate: (input) => {
157
+ if (input.trim().length === 0) {
158
+ return 'Token cannot be empty';
159
+ }
160
+ return true;
161
+ }
162
+ }
163
+ ]);
164
+
165
+ const spinner = ora('Authenticating...').start();
166
+
167
+ try {
168
+ const isAuthenticated = await authenticate(
169
+ modeAnswer.mode,
170
+ workspaceAnswer.workspace.trim(),
171
+ tokenAnswer.token.trim()
172
+ );
173
+
174
+ if (isAuthenticated) {
175
+ spinner.succeed(chalk.green('Authentication successful!'));
176
+
177
+ // Save credentials
178
+ const saved = saveCredentials(
179
+ modeAnswer.mode,
180
+ workspaceAnswer.workspace.trim(),
181
+ tokenAnswer.token.trim()
182
+ );
183
+
184
+ if (saved) {
185
+ console.log(chalk.green('\n✓ Credentials saved successfully'));
186
+ console.log(chalk.cyan('\nYou can now use:'), chalk.white('orca kickstart <language>'));
187
+ console.log(chalk.cyan('============================================================\n'));
188
+ }
189
+
190
+ authenticated = true;
191
+ } else {
192
+ spinner.fail(chalk.red('Authentication failed'));
193
+ retryCount++;
194
+
195
+ if (retryCount < MAX_RETRIES) {
196
+ console.log(chalk.yellow(`\n⚠ Invalid credentials. Please try again (${retryCount}/${MAX_RETRIES})\n`));
197
+ } else {
198
+ console.log(chalk.red('\n✗ Maximum retry attempts reached. Please try again later.\n'));
199
+ }
200
+ }
201
+ } catch (error) {
202
+ spinner.fail(chalk.red('Authentication error'));
203
+ console.log(chalk.red(`Error: ${error.message}`));
204
+ retryCount++;
205
+
206
+ if (retryCount < MAX_RETRIES) {
207
+ console.log(chalk.yellow(`\n⚠ Please try again (${retryCount}/${MAX_RETRIES})\n`));
208
+ }
209
+ }
210
+ } catch (error) {
211
+ if (error.isTtyError) {
212
+ console.error(chalk.red('Prompt couldn\'t be rendered in the current environment'));
213
+ } else {
214
+ console.error(chalk.red('An error occurred:', error.message));
215
+ }
216
+ break;
217
+ }
218
+ }
219
+
220
+ if (!authenticated) {
221
+ process.exit(1);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Check if user is logged in
227
+ */
228
+ function isLoggedIn() {
229
+ try {
230
+ if (!fs.existsSync(CONFIG_FILE)) {
231
+ return false;
232
+ }
233
+
234
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
235
+ return config.authenticated === true;
236
+ } catch (error) {
237
+ return false;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Get current credentials
243
+ */
244
+ function getCredentials() {
245
+ try {
246
+ if (!fs.existsSync(CONFIG_FILE)) {
247
+ return null;
248
+ }
249
+
250
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
251
+ } catch (error) {
252
+ return null;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Clear credentials (logout)
258
+ */
259
+ function clearCredentials() {
260
+ try {
261
+ if (fs.existsSync(CONFIG_FILE)) {
262
+ fs.unlinkSync(CONFIG_FILE);
263
+ }
264
+ return true;
265
+ } catch (error) {
266
+ return false;
267
+ }
268
+ }
269
+
270
+ module.exports = {
271
+ login,
272
+ isLoggedIn,
273
+ getCredentials,
274
+ clearCredentials,
275
+ CONFIG_FILE
276
+ };
277
+