@startanaicompany/cli 1.9.1 → 1.9.3

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
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code when working with the SAAC CLI codeba
6
6
 
7
7
  SAAC CLI is the official command-line interface for StartAnAiCompany.com. Built with Node.js and Commander.js, it interfaces with a wrapper API that manages Coolify deployments.
8
8
 
9
- **Current Version:** 1.6.0
9
+ **Current Version:** 1.9.3
10
10
 
11
11
  ## Development Commands
12
12
 
@@ -63,6 +63,55 @@ try {
63
63
  }
64
64
  ```
65
65
 
66
+ ### Polling Mechanism Pattern (v1.9.0)
67
+
68
+ Used for async operations (exec, db commands):
69
+
70
+ ```javascript
71
+ async function pollForResult(applicationUuid, commandId, commandType, maxWaitSeconds = 120) {
72
+ const startTime = Date.now();
73
+ const pollInterval = 1000; // 1 second
74
+
75
+ while (true) {
76
+ const elapsed = (Date.now() - startTime) / 1000;
77
+
78
+ if (elapsed > maxWaitSeconds) {
79
+ throw new Error(`Command timed out after ${maxWaitSeconds} seconds`);
80
+ }
81
+
82
+ try {
83
+ const result = await api.getDbCommandResult(applicationUuid, commandType, commandId);
84
+
85
+ if (result.status === 'completed') {
86
+ return result;
87
+ }
88
+
89
+ if (result.status === 'failed') {
90
+ const errorMsg = result.result?.error || result.error || 'Command failed';
91
+ throw new Error(errorMsg);
92
+ }
93
+
94
+ // Still pending, wait and retry
95
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
96
+ } catch (error) {
97
+ // If error is not a 404 (command not found yet), rethrow
98
+ if (error.response?.status !== 404) {
99
+ throw error;
100
+ }
101
+ // 404 means command not processed yet, keep polling
102
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ **Key points:**
109
+ - Universal polling endpoint: `/db/result/:commandId`
110
+ - 1-second poll interval
111
+ - 120-second default timeout
112
+ - Handles 404 (command not ready yet) vs other errors
113
+ - Returns result object with status, result, timestamps
114
+
66
115
  ## Authentication Flow
67
116
 
68
117
  ### Session Tokens (Primary Method)
@@ -110,14 +159,29 @@ SSE streaming implementation:
110
159
  **Commands:** `git connect/list/disconnect`
111
160
  **Flow:** Browser OAuth → CLI polls every 2s → Connection saved (AES-256-GCM encrypted)
112
161
 
113
- ### Remote Execution (v1.6.0)
162
+ ### Remote Execution (v1.9.1)
114
163
  ```bash
115
164
  saac exec "npm run migrate" # Execute command
116
165
  saac exec --history # View history
117
166
  ```
167
+ **Implementation:** SSE command channel with polling mechanism
118
168
  **Security:** Command allowlist, dangerous pattern detection, rate limiting (30/5min)
169
+ **Performance:** ~500ms average response time
119
170
 
120
- ## API Endpoints (v1.6.0)
171
+ ### Database Management (v1.9.0)
172
+ ```bash
173
+ saac db list # List database containers
174
+ saac db sql "SELECT * FROM users" # Execute SQL query (read-only)
175
+ saac db sql "INSERT INTO..." --write # Write operation (requires flag)
176
+ saac db sql "SELECT..." --db mydb # Target specific database
177
+ saac db redis GET mykey # Execute Redis command
178
+ saac db info # Show database connection info
179
+ ```
180
+ **Implementation:** SSE command channel with universal polling endpoint
181
+ **Security:** Read-only by default, --write flag for modifications, rate limiting (60/5min)
182
+ **Performance:** ~1s average response time
183
+
184
+ ## API Endpoints (v1.9.0)
121
185
 
122
186
  ```
123
187
  POST /api/v1/users/register
@@ -128,7 +192,16 @@ PATCH /api/v1/applications/:uuid
128
192
  POST /api/v1/applications/:uuid/deploy
129
193
  GET /api/v1/applications/:uuid/logs # ?follow=true for SSE
130
194
  GET /api/v1/applications/:uuid/env/export
131
- POST /api/v1/applications/:uuid/exec
195
+
196
+ # Remote Execution (v1.9.1)
197
+ POST /api/v1/applications/:uuid/exec # Queue command
198
+ GET /api/v1/applications/:uuid/db/result/:commandId # Universal polling
199
+
200
+ # Database Management (v1.9.0)
201
+ GET /api/v1/applications/:uuid/db/containers
202
+ GET /api/v1/applications/:uuid/db/info # Instant response
203
+ POST /api/v1/applications/:uuid/db/sql # Queue SQL query
204
+ POST /api/v1/applications/:uuid/db/redis # Queue Redis command
132
205
 
133
206
  # OAuth (no /api/v1 prefix)
134
207
  GET /oauth/authorize
@@ -142,7 +215,7 @@ GET /oauth/poll/:session_id
142
215
  3. **delete message:** Use `app.name` not `result.application_name`
143
216
  4. **organization_id:** Added required `--org` flag to create command
144
217
 
145
- ## Testing Methodology (v1.6.0)
218
+ ## Testing Methodology
146
219
 
147
220
  **Process:**
148
221
  1. Test ONE feature at a time
@@ -151,12 +224,29 @@ GET /oauth/poll/:session_id
151
224
  4. Fix bugs immediately
152
225
  5. Move to next feature
153
226
 
154
- **35+ tests completed:**
227
+ **Comprehensive tests completed:**
155
228
  - Authentication, auto-login, API keys
156
229
  - Git OAuth, application management
157
230
  - Environment/domain, logs, streaming
231
+ - Database management (SQL queries, Redis commands)
232
+ - Remote execution (allowed/blocked commands)
233
+ - Error handling and user experience
158
234
  - Real app testing (golden-68-rekrytering-9jsq1e)
159
235
 
236
+ **v1.9.0 Database Testing:**
237
+ - Created test_users table with INSERT/SELECT operations
238
+ - Tested PostgreSQL meta-queries (list databases, tables, schemas)
239
+ - Tested Redis commands (PING, SET, GET, HGETALL)
240
+ - Created new database (test_cli_db) with products table
241
+ - Validated --write flag enforcement
242
+ - Validated --db flag for targeting specific databases
243
+
244
+ **v1.9.1/v1.9.2 Exec Testing:**
245
+ - Tested allowed commands (npm, node, ls, cat, pwd)
246
+ - Tested blocked commands (whoami, ps, kill)
247
+ - Validated error messages and UX improvements
248
+ - Confirmed ~500ms average response time
249
+
160
250
  ## Publishing Checklist
161
251
 
162
252
  1. Test all new features with live backend
@@ -166,8 +256,56 @@ GET /oauth/poll/:session_id
166
256
  5. Git commit and push
167
257
  6. `npm publish --access public`
168
258
 
169
- ## Version 1.6.0 Release Notes
259
+ ## Release Notes
170
260
 
261
+ ### Version 1.9.3 (Current)
262
+ **Bug Fixes:**
263
+ - Fixed SQL query crash when querying tables with text fields containing special characters
264
+ - Backend now returns structured JSON format (`columns` and `rows` arrays) instead of raw CSV
265
+ - Added fallback to CSV parsing for backward compatibility
266
+ - Handles commas, newlines, and Swedish characters in text fields correctly
267
+
268
+ ### Version 1.9.2
269
+ **Bug Fixes:**
270
+ - Fixed exec error handling to use correct backend field (`data.error` not `data.message`)
271
+ - Improved error messages for blocked commands
272
+ - Updated allowed commands list to match backend implementation
273
+
274
+ ### Version 1.9.1
275
+ **Improvements:**
276
+ - Fixed `saac exec` to use SSE command channel with polling
277
+ - Migrated from direct docker exec to daemon-based execution
278
+ - Average response time improved to ~500ms (from 30s timeout)
279
+
280
+ ### Version 1.9.0
281
+ **New Features:**
282
+ - **Database Management Commands:**
283
+ - `saac db list` - List database containers (postgres, redis, etc.)
284
+ - `saac db sql <query>` - Execute SQL queries (read-only by default)
285
+ - `saac db redis <command>` - Execute Redis commands
286
+ - `saac db info` - Show database connection information
287
+ - Read-only by default with `--write` flag for modifications
288
+ - `--db` flag to target specific databases
289
+ - Rate limiting: 60 queries per 5 minutes
290
+ - Universal polling endpoint for all async operations
291
+
292
+ **Implementation:**
293
+ - Created `src/commands/db.js` (416 lines)
294
+ - Added 5 new API client methods in `src/lib/api.js`
295
+ - Polling mechanism with 1s interval, 120s timeout
296
+ - Formatted table output for query results
297
+
298
+ ### Version 1.8.0
299
+ **Improvements:**
300
+ - Made deploy streaming the default behavior
301
+ - Improved deploy command performance
302
+
303
+ ### Version 1.7.0
304
+ **New Features:**
305
+ - Deploy streaming support
306
+ - No-cache deployment option
307
+
308
+ ### Version 1.6.0
171
309
  **New Features:**
172
310
  - SSE streaming for real-time logs (`--follow`)
173
311
  - Log type filtering (`--type access/runtime/build`)
@@ -184,6 +322,40 @@ GET /oauth/poll/:session_id
184
322
  - `saac create` requires `--org` flag
185
323
  - `saac keys show` removed
186
324
 
325
+ ## Key Files & Structure
326
+
327
+ ```
328
+ saac-cli/
329
+ ├── bin/
330
+ │ └── saac.js # CLI entry point, all command definitions
331
+ ├── src/
332
+ │ ├── commands/
333
+ │ │ ├── auth.js # login, logout, register, verify
334
+ │ │ ├── create.js # create applications
335
+ │ │ ├── db.js # database management (v1.9.0)
336
+ │ │ ├── deploy.js # deploy command
337
+ │ │ ├── exec.js # remote execution (v1.9.1)
338
+ │ │ ├── git.js # Git OAuth
339
+ │ │ ├── keys.js # API key management
340
+ │ │ ├── logs.js # log streaming (v1.6.0)
341
+ │ │ └── ... # other commands
342
+ │ └── lib/
343
+ │ ├── api.js # Axios client, all API methods
344
+ │ ├── config.js # Config management, auth checking
345
+ │ └── logger.js # Formatted output utilities
346
+ ├── package.json # Version, dependencies, bin script
347
+ ├── README.md # Public documentation
348
+ └── CLAUDE.md # AI agent instructions (this file)
349
+ ```
350
+
351
+ **Critical files for new features:**
352
+ 1. `bin/saac.js` - Add new command definitions
353
+ 2. `src/commands/<feature>.js` - Implement command logic
354
+ 3. `src/lib/api.js` - Add API client methods
355
+ 4. `package.json` - Update version number
356
+ 5. `README.md` - Document public-facing features
357
+ 6. `CLAUDE.md` - Update AI agent instructions
358
+
187
359
  ## Dependencies
188
360
 
189
361
  - axios, chalk, commander, conf, inquirer, ora, boxen, table, validator, dotenv, open, ws
@@ -215,8 +387,45 @@ GET /oauth/poll/:session_id
215
387
  - Show FULL API keys when displayed (they're only shown once)
216
388
  - Same for session tokens during verification
217
389
 
390
+ **Database command patterns:**
391
+ - Always use `--write` flag for INSERT, UPDATE, DELETE, DROP, TRUNCATE
392
+ - Use `--db` flag to target specific databases (defaults to env vars)
393
+ - Format query results as tables using `table` package
394
+ - Handle CSV output from backend (comma-separated rows)
395
+
396
+ **Exec error handling (v1.9.2):**
397
+ - 400 errors = command not allowed (show clear message with allowlist)
398
+ - 408 errors = timeout (suggest increasing --timeout)
399
+ - 429 errors = rate limit exceeded (30 commands per 5 minutes)
400
+ - 503 errors = container not running
401
+ - Backend error field is `data.error` (not `data.message`)
402
+ - Show full backend error message with context
403
+
404
+ ## HIVE Collaboration
405
+
406
+ **Agent Names:**
407
+ - CLI Developer: `saac-saac-clitool-developer`
408
+ - Backend Developer: `saac-owndockermachine-backend-developer`
409
+ - Workspace Backend: `saac-workspace-backend-developer`
410
+ - Orchestrator: `saac-orchestrator-backend-developer`
411
+
412
+ **Collaboration Workflow:**
413
+ 1. Poll HIVE regularly when implementing new features
414
+ 2. Coordinate with backend developers for API changes
415
+ 3. Validate implementations with backend team
416
+ 4. Share knowledge and best practices with other agents
417
+ 5. Document all architectural decisions
418
+
419
+ **Example Communication:**
420
+ - Request API endpoint specifications
421
+ - Confirm error response formats
422
+ - Validate security patterns
423
+ - Share implementation guides for other teams
424
+ - Debug issues collaboratively
425
+
218
426
  ## MailHog & Testing
219
427
 
220
428
  - MailHog URL: https://mailhog.goryan.io
221
429
  - Default domain: `{subdomain}.startanaicompany.com`
222
430
  - Git server: https://git.startanaicompany.com
431
+ - Test app: golden-68-rekrytering-9jsq1e
package/README.md CHANGED
@@ -1472,17 +1472,19 @@ Run one-off commands inside your container without opening an interactive shell.
1472
1472
  Execute a single command in the remote container.
1473
1473
 
1474
1474
  ```bash
1475
- # Run command
1475
+ # Run npm commands
1476
1476
  saac exec "npm run db:migrate"
1477
+ saac exec "npm test"
1478
+ saac exec "npm run build"
1477
1479
 
1478
- # Check Node.js version
1480
+ # Check versions
1479
1481
  saac exec "node --version"
1482
+ saac exec "npm --version"
1480
1483
 
1481
- # View environment variables
1482
- saac exec "printenv | grep NODE"
1483
-
1484
- # Check running processes
1485
- saac exec "ps aux"
1484
+ # File operations
1485
+ saac exec "ls -la"
1486
+ saac exec "cat package.json"
1487
+ saac exec "pwd"
1486
1488
 
1487
1489
  # Custom working directory
1488
1490
  saac exec "npm test" --workdir /app/src
@@ -1495,6 +1497,34 @@ saac exec "npm run build" --timeout 300
1495
1497
  - `--workdir <path>` - Working directory (default: `/app`)
1496
1498
  - `--timeout <seconds>` - Timeout in seconds (default: 30, max: 300)
1497
1499
 
1500
+ **Security & Command Allowlist:**
1501
+
1502
+ For security, only specific commands are allowed. Commands are executed via the SSE command channel with ~500ms response time.
1503
+
1504
+ **Allowed Commands:**
1505
+ - **Node.js:** npm, node, npx, yarn, pnpm
1506
+ - **Python:** python, python3, pip, pip3, poetry
1507
+ - **Ruby:** bundle, rake, rails, ruby
1508
+ - **Shell:** sh, bash, echo, cat, ls, pwd, env
1509
+ - **Database:** psql, mysql, mongosh
1510
+ - **Build:** go, cargo, make, cmake
1511
+
1512
+ **Blocked for Security:**
1513
+ - System commands: `whoami`, `ps`, `top`, `kill`
1514
+ - Destructive operations: `rm`, `chmod`, `chown`
1515
+ - Advanced shell features: pipes (`|`), redirects (`>`), command substitution
1516
+
1517
+ **Rate Limit:** 60 commands per 5 minutes per user
1518
+
1519
+ If you try a blocked command, you'll see a clear error message:
1520
+ ```
1521
+ ✖ Command not allowed
1522
+
1523
+ ✗ This command is blocked for security reasons
1524
+
1525
+ ⚠ Command 'whoami' is not in allowlist. Allowed commands: ...
1526
+ ```
1527
+
1498
1528
  **Example output:**
1499
1529
  ```bash
1500
1530
  $ saac exec "npm run db:migrate"
@@ -2451,7 +2481,8 @@ saac shell
2451
2481
  **Workaround:** Use `saac exec` for one-off commands:
2452
2482
  ```bash
2453
2483
  saac exec "npm run migrate"
2454
- saac exec "ps aux"
2484
+ saac exec "node --version"
2485
+ saac exec "ls -la"
2455
2486
  ```
2456
2487
 
2457
2488
  ### General Debugging
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startanaicompany/cli",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "Official CLI for StartAnAiCompany.com - Deploy AI recruitment sites with ease",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -166,8 +166,21 @@ async function sql(query, options) {
166
166
  spin.succeed('Query executed');
167
167
  logger.newline();
168
168
 
169
- if (result.result && result.result.output) {
170
- // Display CSV output as table
169
+ if (result.result && result.result.format === 'json') {
170
+ // New format: structured JSON with columns and rows
171
+ const { columns, rows } = result.result;
172
+
173
+ if (columns && rows && rows.length > 0) {
174
+ // Build table data with header row
175
+ const tableData = [columns, ...rows];
176
+ console.log(table(tableData));
177
+ logger.newline();
178
+ logger.info(`Rows returned: ${rows.length}`);
179
+ } else {
180
+ logger.info('Query returned no results');
181
+ }
182
+ } else if (result.result && result.result.output) {
183
+ // Fallback: old CSV format (if daemon parsing failed)
171
184
  const csvData = result.result.output;
172
185
  const rows = csvData.trim().split('\n').map(row => row.split(','));
173
186
 
@@ -111,27 +111,36 @@ async function exec(command, options = {}) {
111
111
 
112
112
  spin.succeed('Command executed');
113
113
  } catch (error) {
114
- spin.fail('Command execution failed');
114
+ spin.fail('Command not allowed');
115
115
 
116
116
  if (error.response?.status === 400) {
117
- const data = error.response.data;
117
+ const data = error.response.data || {};
118
118
  logger.newline();
119
119
 
120
- if (data.error === 'VALIDATION_ERROR') {
121
- logger.error('Command validation failed');
120
+ // Show clear "command not allowed" message for all 400 errors
121
+ logger.error('This command is blocked for security reasons');
122
+
123
+ // Show backend error message if available (note: field is 'error', not 'message')
124
+ if (data.error) {
122
125
  logger.newline();
123
- logger.warn(data.message);
124
-
125
- if (data.message.includes('not in allowlist')) {
126
- logger.newline();
127
- logger.info('Allowed commands include:');
128
- logger.log(' Node.js: npm, node, npx, yarn, pnpm');
129
- logger.log(' Python: python, python3, pip, poetry');
130
- logger.log(' Ruby: bundle, rake, rails');
131
- logger.log(' Shell: sh, bash, echo, cat, ls, pwd');
132
- logger.log(' Database: psql, mysql, mongosh');
133
- }
126
+ logger.warn(data.error);
134
127
  }
128
+
129
+ logger.newline();
130
+ logger.info('Allowed commands include:');
131
+ logger.log(' Node.js: npm, node, npx, yarn, pnpm');
132
+ logger.log(' Python: python, python3, pip, poetry');
133
+ logger.log(' Ruby: bundle, rake, rails, ruby');
134
+ logger.log(' Shell: sh, bash, echo, cat, ls, pwd, env');
135
+ logger.log(' Database: psql, mysql, mongosh');
136
+ logger.log(' Build: go, cargo, make, cmake');
137
+ logger.newline();
138
+ logger.info('Blocked for security:');
139
+ logger.log(' System commands: whoami, ps, top, kill');
140
+ logger.log(' Destructive operations: rm, chmod, chown');
141
+ logger.log(' Advanced shell features: pipes (|), redirects (>), command substitution');
142
+
143
+ process.exit(1);
135
144
  } else if (error.response?.status === 408) {
136
145
  logger.newline();
137
146
  logger.error('Command execution timed out');