@startanaicompany/cli 1.4.17 → 1.4.19
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 +428 -29
- package/bin/saac.js +56 -4
- package/package.json +3 -2
- package/src/commands/delete.js +191 -4
- package/src/commands/deployments.js +130 -0
- package/src/commands/domain.js +204 -3
- package/src/commands/env.js +264 -3
- package/src/commands/exec.js +277 -0
- package/src/commands/logs.js +232 -4
- package/src/commands/run.js +170 -0
- package/src/commands/shell.js +403 -0
- package/src/commands/whoami.js +90 -4
- package/src/lib/api.js +54 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Command - Interactive remote shell via WebSocket (Project Aurora)
|
|
3
|
+
*
|
|
4
|
+
* This command provides a TRUE remote shell experience - you're actually inside
|
|
5
|
+
* the container, not just a local shell with env vars loaded.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const WebSocket = require('ws');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
const { getProjectConfig, isAuthenticated, getUser, getApiUrl } = require('../lib/config');
|
|
11
|
+
const logger = require('../lib/logger');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* WebSocket Shell Client
|
|
15
|
+
* Connects to backend WebSocket server and provides interactive shell
|
|
16
|
+
*/
|
|
17
|
+
class ShellClient {
|
|
18
|
+
constructor(serverUrl, token, applicationUuid) {
|
|
19
|
+
this.serverUrl = serverUrl;
|
|
20
|
+
this.token = token;
|
|
21
|
+
this.applicationUuid = applicationUuid;
|
|
22
|
+
this.ws = null;
|
|
23
|
+
this.sessionId = null;
|
|
24
|
+
this.connected = false;
|
|
25
|
+
this.rl = null;
|
|
26
|
+
this.lastScreen = '';
|
|
27
|
+
this.reconnectAttempts = 0;
|
|
28
|
+
this.maxReconnectAttempts = 3;
|
|
29
|
+
this.shouldReconnect = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async connect() {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Build WebSocket URL
|
|
35
|
+
const wsUrl = this.buildWebSocketUrl();
|
|
36
|
+
|
|
37
|
+
logger.info('Connecting to remote shell...');
|
|
38
|
+
|
|
39
|
+
this.ws = new WebSocket(wsUrl, {
|
|
40
|
+
headers: {
|
|
41
|
+
'X-Session-Token': this.token,
|
|
42
|
+
},
|
|
43
|
+
// Handle HTTPS/WSS
|
|
44
|
+
rejectUnauthorized: process.env.NODE_ENV === 'production'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Connection timeout
|
|
48
|
+
const timeout = setTimeout(() => {
|
|
49
|
+
reject(new Error('Connection timeout'));
|
|
50
|
+
if (this.ws) {
|
|
51
|
+
this.ws.close();
|
|
52
|
+
}
|
|
53
|
+
}, 30000); // 30 second timeout for container creation
|
|
54
|
+
|
|
55
|
+
this.ws.on('open', () => {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
this.connected = true;
|
|
58
|
+
this.reconnectAttempts = 0;
|
|
59
|
+
logger.success('Connected to remote container');
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.ws.on('message', (data) => {
|
|
64
|
+
try {
|
|
65
|
+
const message = JSON.parse(data.toString());
|
|
66
|
+
this.handleMessage(message);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger.error('Error parsing message:', err.message);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.ws.on('close', (code, reason) => {
|
|
73
|
+
this.connected = false;
|
|
74
|
+
const reasonText = reason ? reason.toString() : '';
|
|
75
|
+
|
|
76
|
+
if (code === 1000) {
|
|
77
|
+
// Normal closure
|
|
78
|
+
logger.info('Disconnected from remote shell');
|
|
79
|
+
} else {
|
|
80
|
+
logger.warn(`Disconnected: ${code}${reasonText ? ' - ' + reasonText : ''}`);
|
|
81
|
+
|
|
82
|
+
if (this.shouldReconnect && code !== 4001 && code !== 4003) {
|
|
83
|
+
this.handleReconnect();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.ws.on('error', (error) => {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
|
|
91
|
+
// Check for specific error codes
|
|
92
|
+
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
93
|
+
logger.error('Authentication failed');
|
|
94
|
+
reject(new Error('Authentication failed'));
|
|
95
|
+
} else if (error.message.includes('403') || error.message.includes('Forbidden')) {
|
|
96
|
+
logger.error('Access denied - you do not own this application');
|
|
97
|
+
reject(new Error('Access denied'));
|
|
98
|
+
} else {
|
|
99
|
+
logger.error('WebSocket error:', error.message);
|
|
100
|
+
reject(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
buildWebSocketUrl() {
|
|
107
|
+
// Convert HTTP(S) URL to WS(S) URL
|
|
108
|
+
const apiUrl = this.serverUrl;
|
|
109
|
+
let wsUrl = apiUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://');
|
|
110
|
+
|
|
111
|
+
// Remove /api/v1 suffix if present
|
|
112
|
+
wsUrl = wsUrl.replace(/\/api\/v1$/, '');
|
|
113
|
+
|
|
114
|
+
// Build shell connect endpoint
|
|
115
|
+
wsUrl = `${wsUrl}/api/v1/shell/connect?app=${this.applicationUuid}&token=${this.token}`;
|
|
116
|
+
|
|
117
|
+
return wsUrl;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleMessage(message) {
|
|
121
|
+
switch (message.type) {
|
|
122
|
+
case 'control':
|
|
123
|
+
this.handleControlMessage(message);
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'output':
|
|
127
|
+
this.handleOutputMessage(message);
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'pong':
|
|
131
|
+
// Heartbeat response
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
logger.warn('Unknown message type:', message.type);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
handleControlMessage(message) {
|
|
140
|
+
if (message.action === 'session_ready') {
|
|
141
|
+
this.sessionId = message.data.session_id;
|
|
142
|
+
const status = message.data.status;
|
|
143
|
+
|
|
144
|
+
if (status === 'creating') {
|
|
145
|
+
logger.info('Container is being created, please wait...');
|
|
146
|
+
} else if (status === 'active') {
|
|
147
|
+
logger.success('Shell session ready!');
|
|
148
|
+
logger.info('Type commands below. Press Ctrl+D or type "exit" to quit.');
|
|
149
|
+
logger.newline();
|
|
150
|
+
|
|
151
|
+
// Start terminal interface
|
|
152
|
+
this.startTerminal();
|
|
153
|
+
}
|
|
154
|
+
} else if (message.action === 'session_active') {
|
|
155
|
+
logger.success('Container is ready!');
|
|
156
|
+
logger.info('Type commands below. Press Ctrl+D or type "exit" to quit.');
|
|
157
|
+
logger.newline();
|
|
158
|
+
|
|
159
|
+
// Start terminal interface
|
|
160
|
+
if (!this.rl) {
|
|
161
|
+
this.startTerminal();
|
|
162
|
+
}
|
|
163
|
+
} else if (message.action === 'error') {
|
|
164
|
+
logger.error('Server error:', message.data.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
handleOutputMessage(message) {
|
|
169
|
+
const screen = message.data.screen;
|
|
170
|
+
|
|
171
|
+
// Only update if screen changed
|
|
172
|
+
if (screen !== this.lastScreen) {
|
|
173
|
+
// Clear current readline prompt
|
|
174
|
+
if (this.rl) {
|
|
175
|
+
readline.clearLine(process.stdout, 0);
|
|
176
|
+
readline.cursorTo(process.stdout, 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Display full screen content
|
|
180
|
+
// For better UX, only show last 40 lines
|
|
181
|
+
const lines = screen.split('\n');
|
|
182
|
+
const displayLines = lines.slice(-40);
|
|
183
|
+
|
|
184
|
+
console.log(displayLines.join('\n'));
|
|
185
|
+
|
|
186
|
+
this.lastScreen = screen;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Show prompt again
|
|
190
|
+
if (this.rl) {
|
|
191
|
+
this.rl.prompt(true);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
startTerminal() {
|
|
196
|
+
// Create readline interface
|
|
197
|
+
this.rl = readline.createInterface({
|
|
198
|
+
input: process.stdin,
|
|
199
|
+
output: process.stdout,
|
|
200
|
+
prompt: '', // No prompt - we get it from the container
|
|
201
|
+
terminal: true,
|
|
202
|
+
historySize: 1000
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Handle user input
|
|
206
|
+
this.rl.on('line', (input) => {
|
|
207
|
+
const command = input.trim();
|
|
208
|
+
|
|
209
|
+
// Local exit command
|
|
210
|
+
if (command === 'exit' || command === 'quit') {
|
|
211
|
+
this.cleanup();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Send command to server
|
|
216
|
+
if (this.connected && this.ws.readyState === WebSocket.OPEN) {
|
|
217
|
+
this.sendCommand(command);
|
|
218
|
+
} else {
|
|
219
|
+
logger.error('Not connected to server');
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Handle Ctrl+C (SIGINT)
|
|
224
|
+
this.rl.on('SIGINT', () => {
|
|
225
|
+
// Send Ctrl+C to remote shell
|
|
226
|
+
if (this.connected) {
|
|
227
|
+
this.sendCommand('\x03'); // ASCII ETX (Ctrl+C)
|
|
228
|
+
}
|
|
229
|
+
this.rl.prompt();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Handle Ctrl+D (EOF) - exit
|
|
233
|
+
process.stdin.on('keypress', (str, key) => {
|
|
234
|
+
if (key && key.ctrl && key.name === 'd') {
|
|
235
|
+
this.cleanup();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Enable keypress events
|
|
240
|
+
if (process.stdin.isTTY) {
|
|
241
|
+
process.stdin.setRawMode(false); // Keep cooked mode for line editing
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sendCommand(command) {
|
|
246
|
+
if (!this.connected || this.ws.readyState !== WebSocket.OPEN) {
|
|
247
|
+
logger.error('Not connected to server');
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const message = {
|
|
252
|
+
type: 'command',
|
|
253
|
+
data: {
|
|
254
|
+
command: command
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
this.ws.send(JSON.stringify(message));
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async handleReconnect() {
|
|
263
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
264
|
+
logger.error('Max reconnection attempts reached. Exiting.');
|
|
265
|
+
this.cleanup();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.reconnectAttempts++;
|
|
270
|
+
const delay = 2000 * this.reconnectAttempts; // Exponential backoff
|
|
271
|
+
|
|
272
|
+
logger.info(`Reconnecting in ${delay/1000}s... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
273
|
+
|
|
274
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
await this.connect();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
logger.error('Reconnection failed:', err.message);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
cleanup() {
|
|
284
|
+
logger.newline();
|
|
285
|
+
logger.info('Disconnecting from remote shell...');
|
|
286
|
+
|
|
287
|
+
this.shouldReconnect = false;
|
|
288
|
+
|
|
289
|
+
if (this.rl) {
|
|
290
|
+
this.rl.close();
|
|
291
|
+
this.rl = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (this.ws) {
|
|
295
|
+
this.ws.close(1000, 'Client closing');
|
|
296
|
+
this.ws = null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Restore terminal
|
|
300
|
+
if (process.stdin.isTTY) {
|
|
301
|
+
process.stdin.setRawMode(false);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
startHeartbeat() {
|
|
308
|
+
// Send ping every 30 seconds to keep connection alive
|
|
309
|
+
setInterval(() => {
|
|
310
|
+
if (this.connected && this.ws.readyState === WebSocket.OPEN) {
|
|
311
|
+
this.ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
|
|
312
|
+
}
|
|
313
|
+
}, 30000);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Shell command main function
|
|
319
|
+
*/
|
|
320
|
+
async function shell(options = {}) {
|
|
321
|
+
try {
|
|
322
|
+
// Check authentication
|
|
323
|
+
if (!isAuthenticated()) {
|
|
324
|
+
logger.error('Not logged in. Run: saac login');
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check for project config
|
|
329
|
+
const projectConfig = getProjectConfig();
|
|
330
|
+
if (!projectConfig || !projectConfig.applicationUuid) {
|
|
331
|
+
logger.error('No application found in current directory');
|
|
332
|
+
logger.info('Run this command from a project directory (must have .saac/config.json)');
|
|
333
|
+
logger.newline();
|
|
334
|
+
logger.info('Or initialize with:');
|
|
335
|
+
logger.log(' saac init');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const { applicationUuid, applicationName } = projectConfig;
|
|
340
|
+
|
|
341
|
+
// Get user and token
|
|
342
|
+
const user = getUser();
|
|
343
|
+
const token = user.sessionToken || user.apiKey;
|
|
344
|
+
|
|
345
|
+
if (!token) {
|
|
346
|
+
logger.error('No authentication token found. Please login again.');
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Get server URL
|
|
351
|
+
const serverUrl = getApiUrl();
|
|
352
|
+
|
|
353
|
+
// Show banner
|
|
354
|
+
logger.newline();
|
|
355
|
+
logger.section(`Remote Shell: ${applicationName}`);
|
|
356
|
+
logger.newline();
|
|
357
|
+
logger.info('Connecting to container...');
|
|
358
|
+
logger.info('This may take up to 30 seconds for container creation.');
|
|
359
|
+
logger.newline();
|
|
360
|
+
|
|
361
|
+
// Create shell client
|
|
362
|
+
const client = new ShellClient(serverUrl, token, applicationUuid);
|
|
363
|
+
|
|
364
|
+
// Handle process termination
|
|
365
|
+
process.on('SIGTERM', () => {
|
|
366
|
+
client.cleanup();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
process.on('SIGINT', () => {
|
|
370
|
+
// Let readline handle SIGINT
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Connect to server
|
|
374
|
+
try {
|
|
375
|
+
await client.connect();
|
|
376
|
+
|
|
377
|
+
// Start heartbeat
|
|
378
|
+
client.startHeartbeat();
|
|
379
|
+
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err.message === 'Authentication failed') {
|
|
382
|
+
logger.error('Authentication failed. Please login again:');
|
|
383
|
+
logger.log(' saac login');
|
|
384
|
+
} else if (err.message === 'Access denied') {
|
|
385
|
+
logger.error('Access denied. You do not own this application.');
|
|
386
|
+
} else if (err.message === 'Connection timeout') {
|
|
387
|
+
logger.error('Connection timeout. The server may be unavailable or the container failed to start.');
|
|
388
|
+
logger.info('Please try again later or check application status:');
|
|
389
|
+
logger.log(' saac status');
|
|
390
|
+
} else {
|
|
391
|
+
logger.error('Failed to connect:', err.message);
|
|
392
|
+
logger.info('Please check your network connection and try again.');
|
|
393
|
+
}
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
} catch (error) {
|
|
398
|
+
logger.error(error.message);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = shell;
|
package/src/commands/whoami.js
CHANGED
|
@@ -1,4 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Whoami Command - Show current user information
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const api = require('../lib/api');
|
|
6
|
+
const { isAuthenticated } = require('../lib/config');
|
|
7
|
+
const logger = require('../lib/logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display current authenticated user information
|
|
11
|
+
*/
|
|
12
|
+
async function whoami() {
|
|
13
|
+
try {
|
|
14
|
+
// Check authentication
|
|
15
|
+
if (!isAuthenticated()) {
|
|
16
|
+
logger.error('Not logged in');
|
|
17
|
+
logger.newline();
|
|
18
|
+
logger.info('Run:');
|
|
19
|
+
logger.log(' saac login -e <email> -k <api-key>');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const spin = logger.spinner('Fetching user information...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const user = await api.getUserInfo();
|
|
27
|
+
|
|
28
|
+
spin.succeed('User information retrieved');
|
|
29
|
+
|
|
30
|
+
logger.newline();
|
|
31
|
+
|
|
32
|
+
logger.section('Current User');
|
|
33
|
+
logger.newline();
|
|
34
|
+
|
|
35
|
+
logger.field('Email', user.email);
|
|
36
|
+
logger.field('User ID', user.id);
|
|
37
|
+
logger.field('Verified', user.verified ? logger.chalk.green('Yes ✓') : logger.chalk.red('No ✗'));
|
|
38
|
+
logger.field('Member Since', formatDate(user.created_at));
|
|
39
|
+
|
|
40
|
+
logger.newline();
|
|
41
|
+
|
|
42
|
+
// Git Connections
|
|
43
|
+
if (user.git_connections && user.git_connections.length > 0) {
|
|
44
|
+
logger.info('Git Connections:');
|
|
45
|
+
for (const conn of user.git_connections) {
|
|
46
|
+
logger.log(` • ${conn.gitUsername} @ ${conn.gitHost} (${conn.providerType})`);
|
|
47
|
+
logger.log(` Connected: ${formatDate(conn.connectedAt)}`);
|
|
48
|
+
}
|
|
49
|
+
logger.newline();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Quotas
|
|
53
|
+
logger.info('Quotas:');
|
|
54
|
+
logger.field(' Applications', `${user.application_count} / ${user.max_applications}`);
|
|
55
|
+
|
|
56
|
+
logger.newline();
|
|
57
|
+
|
|
58
|
+
logger.info('Commands:');
|
|
59
|
+
logger.log(' View applications: ' + logger.chalk.cyan('saac list'));
|
|
60
|
+
logger.log(' View status: ' + logger.chalk.cyan('saac status'));
|
|
61
|
+
logger.log(' Logout: ' + logger.chalk.cyan('saac logout'));
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
spin.fail('Failed to fetch user information');
|
|
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
|
+
/**
|
|
75
|
+
* Format ISO date string to readable format
|
|
76
|
+
* @param {string} isoString - ISO 8601 date string
|
|
77
|
+
* @returns {string} Formatted date
|
|
78
|
+
*/
|
|
79
|
+
function formatDate(isoString) {
|
|
80
|
+
if (!isoString) return 'N/A';
|
|
81
|
+
|
|
82
|
+
const date = new Date(isoString);
|
|
83
|
+
return date.toLocaleDateString('en-US', {
|
|
84
|
+
year: 'numeric',
|
|
85
|
+
month: 'short',
|
|
86
|
+
day: 'numeric'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = whoami;
|
package/src/lib/api.js
CHANGED
|
@@ -186,6 +186,24 @@ async function healthCheck() {
|
|
|
186
186
|
return response.data;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Get deployment history for an application
|
|
191
|
+
*/
|
|
192
|
+
async function getDeployments(uuid, params = {}) {
|
|
193
|
+
const client = createClient();
|
|
194
|
+
const response = await client.get(`/applications/${uuid}/deployments`, { params });
|
|
195
|
+
return response.data;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get deployment logs (build logs, not runtime logs)
|
|
200
|
+
*/
|
|
201
|
+
async function getDeploymentLogs(uuid, params = {}) {
|
|
202
|
+
const client = createClient();
|
|
203
|
+
const response = await client.get(`/applications/${uuid}/deployment-logs`, { params });
|
|
204
|
+
return response.data;
|
|
205
|
+
}
|
|
206
|
+
|
|
189
207
|
/**
|
|
190
208
|
* Request login OTP (no API key required)
|
|
191
209
|
*/
|
|
@@ -241,6 +259,37 @@ async function getApiKeyInfo() {
|
|
|
241
259
|
return response.data;
|
|
242
260
|
}
|
|
243
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Get environment variables for an application
|
|
264
|
+
*/
|
|
265
|
+
async function getEnvironmentVariables(uuid) {
|
|
266
|
+
const client = createClient();
|
|
267
|
+
const response = await client.get(`/applications/${uuid}/env`);
|
|
268
|
+
return response.data;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Execute a command in the application container
|
|
273
|
+
* @param {string} uuid - Application UUID
|
|
274
|
+
* @param {object} execRequest - { command, workdir, timeout }
|
|
275
|
+
*/
|
|
276
|
+
async function executeCommand(uuid, execRequest) {
|
|
277
|
+
const client = createClient();
|
|
278
|
+
const response = await client.post(`/applications/${uuid}/exec`, execRequest);
|
|
279
|
+
return response.data;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get execution history for an application
|
|
284
|
+
* @param {string} uuid - Application UUID
|
|
285
|
+
* @param {object} params - { limit, offset }
|
|
286
|
+
*/
|
|
287
|
+
async function getExecutionHistory(uuid, params = {}) {
|
|
288
|
+
const client = createClient();
|
|
289
|
+
const response = await client.get(`/applications/${uuid}/exec/history`, { params });
|
|
290
|
+
return response.data;
|
|
291
|
+
}
|
|
292
|
+
|
|
244
293
|
module.exports = {
|
|
245
294
|
createClient,
|
|
246
295
|
login,
|
|
@@ -254,6 +303,7 @@ module.exports = {
|
|
|
254
303
|
deployApplication,
|
|
255
304
|
getApplicationLogs,
|
|
256
305
|
updateEnvironmentVariables,
|
|
306
|
+
getEnvironmentVariables,
|
|
257
307
|
updateDomain,
|
|
258
308
|
deleteApplication,
|
|
259
309
|
healthCheck,
|
|
@@ -261,4 +311,8 @@ module.exports = {
|
|
|
261
311
|
verifyLoginOtp,
|
|
262
312
|
regenerateApiKey,
|
|
263
313
|
getApiKeyInfo,
|
|
314
|
+
getDeployments,
|
|
315
|
+
getDeploymentLogs,
|
|
316
|
+
executeCommand,
|
|
317
|
+
getExecutionHistory,
|
|
264
318
|
};
|