colana 1.0.0-beta.8 → 1.0.0-beta.80

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/bin/admin.js CHANGED
@@ -156,12 +156,14 @@ async function createKey(flags) {
156
156
  console.error(`\n ${icons.fail} ${c.error}ERROR: --name is required.${c.reset}\n`);
157
157
  console.error(' Usage: colana admin create-key --name "Sarah" --type staff [--email x] [--expires 2026-12-31]\n');
158
158
  process.exit(1);
159
+ return;
159
160
  }
160
161
 
161
162
  const validTypes = ['admin', 'staff', 'beta'];
162
163
  if (!validTypes.includes(type)) {
163
164
  console.error(`\n ${icons.fail} ${c.error}ERROR: Invalid type "${type}". Must be one of: ${validTypes.join(', ')}${c.reset}\n`);
164
165
  process.exit(1);
166
+ return;
165
167
  }
166
168
 
167
169
  // Prefer server API (avoids sql.js in-memory DB conflict)
@@ -174,6 +176,7 @@ async function createKey(flags) {
174
176
  } catch (err) {
175
177
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
176
178
  process.exit(1);
179
+ return;
177
180
  }
178
181
  } else {
179
182
  // Fallback: direct DB access (only safe when server is NOT running)
@@ -184,9 +187,16 @@ async function createKey(flags) {
184
187
  } catch (err) {
185
188
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
186
189
  process.exit(1);
190
+ return;
187
191
  }
188
192
  }
189
193
 
194
+ if (!result || !result.name) {
195
+ console.error(`\n ${icons.fail} ${c.error}Key generation returned an unexpected response.${c.reset}\n`);
196
+ process.exit(1);
197
+ return; // Guard for test environments where process.exit is mocked
198
+ }
199
+
190
200
  console.log('');
191
201
  console.log(` ${icons.pass} Key generated successfully!`);
192
202
  console.log(` ${c.muted}${'─'.repeat(40)}${c.reset}`);
@@ -219,6 +229,7 @@ async function listKeysCmd(flags) {
219
229
  } catch (err) {
220
230
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
221
231
  process.exit(1);
232
+ return;
222
233
  }
223
234
  } else {
224
235
  await ensureDb();
@@ -226,7 +237,7 @@ async function listKeysCmd(flags) {
226
237
  result = listKeys(filters);
227
238
  }
228
239
 
229
- if (!result.keys.length) {
240
+ if (!result || !result.keys || !result.keys.length) {
230
241
  console.log('\n No keys found.\n');
231
242
  return;
232
243
  }
@@ -257,6 +268,7 @@ async function revokeKeyCmd(positional) {
257
268
  console.error(`\n ${icons.fail} ${c.error}ERROR: Key ID is required.${c.reset}\n`);
258
269
  console.error(' Usage: colana admin revoke-key <id>\n');
259
270
  process.exit(1);
271
+ return;
260
272
  }
261
273
 
262
274
  // Prefer server API
@@ -269,6 +281,7 @@ async function revokeKeyCmd(positional) {
269
281
  } catch (err) {
270
282
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
271
283
  process.exit(1);
284
+ return;
272
285
  }
273
286
  } else {
274
287
  await ensureDb();
@@ -279,6 +292,7 @@ async function revokeKeyCmd(positional) {
279
292
  } catch (err) {
280
293
  console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
281
294
  process.exit(1);
295
+ return;
282
296
  }
283
297
  }
284
298
  }
@@ -375,7 +389,11 @@ async function main() {
375
389
  console.error(` ${icons.fail} Unknown command: ${c.value}${command}${c.reset}`);
376
390
  console.error(` ${c.muted}Run "colana admin help" for usage.${c.reset}`);
377
391
  process.exit(1);
392
+ return;
378
393
  }
379
394
  }
380
395
 
381
- main();
396
+ main().catch((err) => {
397
+ console.error(` ${icons.fail} ${c.error}${err.message || err}${c.reset}`);
398
+ process.exit(1);
399
+ });
package/bin/colana.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * Normal run: start server → open browser
8
8
  */
9
9
 
10
- import { execSync, spawn } from 'child_process';
10
+ import { execSync, execFileSync, spawn } from 'child_process';
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import readline from 'readline';
@@ -125,6 +125,79 @@ function detectProvider(provider) {
125
125
  return commandExistsSync(provider.binary);
126
126
  }
127
127
 
128
+ /**
129
+ * Detect the system Python version. Returns { major, minor } or null.
130
+ */
131
+ function detectPythonVersion() {
132
+ for (const bin of ['python3', 'python']) {
133
+ try {
134
+ const out = execFileSync(bin, ['--version'], {
135
+ timeout: 5000, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
136
+ }).trim();
137
+ const match = out.match(/Python\s+(\d+)\.(\d+)/);
138
+ if (match) return { major: parseInt(match[1]), minor: parseInt(match[2]) };
139
+ } catch { /* try next */ }
140
+ }
141
+ return null;
142
+ }
143
+
144
+ /**
145
+ * Return platform-specific numbered next-steps for a failed install.
146
+ * @param {object} provider - Provider object from CLI_PROVIDERS
147
+ * @param {string} [hint] - Optional hint about why it failed
148
+ * @returns {string[]} Steps array
149
+ */
150
+ function getCliNextSteps(provider, hint = '') {
151
+ const h = hint.toLowerCase();
152
+ const platform = process.platform;
153
+
154
+ // Build tools failures (npm providers)
155
+ if (provider.runtime === 'npm' && (h.includes('gyp') || h.includes('msbuild') || h.includes('native') || h.includes('build tools'))) {
156
+ if (platform === 'win32') {
157
+ return [
158
+ 'Open PowerShell as Administrator (right-click > "Run as administrator")',
159
+ `Run: ${c.cmd}winget install Microsoft.VisualStudio.2022.BuildTools --override "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive --norestart" --accept-source-agreements --accept-package-agreements${c.reset}`,
160
+ 'Wait for the install to complete (2-5 minutes)',
161
+ 'Close and reopen your terminal',
162
+ `Run: ${c.cmd}${provider.installCmd}${c.reset}`,
163
+ ];
164
+ }
165
+ if (platform === 'darwin') {
166
+ return [
167
+ `Run: ${c.cmd}xcode-select --install${c.reset}`,
168
+ 'Complete the system dialog that appears',
169
+ `Run: ${c.cmd}${provider.installCmd}${c.reset}`,
170
+ ];
171
+ }
172
+ return [
173
+ `Run: ${c.cmd}sudo apt update && sudo apt install -y build-essential python3${c.reset}`,
174
+ `Run: ${c.cmd}${provider.installCmd}${c.reset}`,
175
+ ];
176
+ }
177
+
178
+ // Python version issues (aider)
179
+ if (provider.runtime === 'pipx' && (h.includes('python') || h.includes('setuptools'))) {
180
+ if (platform === 'win32') {
181
+ return [
182
+ `Run: ${c.cmd}pip install uv${c.reset}`,
183
+ `Run: ${c.cmd}uv tool install --python python3.12 aider-chat${c.reset}`,
184
+ 'Close and reopen your terminal',
185
+ ];
186
+ }
187
+ return [
188
+ `Run: ${c.cmd}pip install uv${c.reset} (or: ${c.cmd}brew install uv${c.reset})`,
189
+ `Run: ${c.cmd}uv tool install --python python3.12 aider-chat${c.reset}`,
190
+ 'Restart your terminal',
191
+ ];
192
+ }
193
+
194
+ // Generic fallback
195
+ return [
196
+ `Run: ${c.cmd}${provider.installCmd}${c.reset}`,
197
+ 'If that fails, check the error output above for details',
198
+ ];
199
+ }
200
+
128
201
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
129
202
  const ask = (q) => new Promise(r => rl.question(q, r));
130
203
 
@@ -136,12 +209,63 @@ function runInstall(cmd) {
136
209
  const proc = spawn(parts[0], parts.slice(1), {
137
210
  stdio: 'inherit',
138
211
  env: { ...process.env },
212
+ ...(process.platform === 'win32' && { shell: true }),
213
+ });
214
+ proc.on('close', (code) => resolve(code === 0));
215
+ proc.on('error', () => resolve(false));
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Spawn a binary with an explicit args array (needed when arguments contain spaces,
221
+ * e.g. winget --override "..." where the value must stay as one argument).
222
+ */
223
+ function runInstallArgs(binary, args) {
224
+ return new Promise((resolve) => {
225
+ // No shell: true here — winget/xcode-select are real executables, not .cmd files.
226
+ // shell: true would cause cmd.exe to split quoted --override values into separate args.
227
+ const proc = spawn(binary, args, {
228
+ stdio: 'inherit',
229
+ env: { ...process.env },
139
230
  });
140
231
  proc.on('close', (code) => resolve(code === 0));
141
232
  proc.on('error', () => resolve(false));
142
233
  });
143
234
  }
144
235
 
236
+ /**
237
+ * Attempt to auto-install C/C++ build tools needed for native npm modules.
238
+ * Returns true if tools were installed (or install was triggered), false otherwise.
239
+ */
240
+ async function tryInstallBuildTools() {
241
+ const platform = process.platform;
242
+
243
+ if (platform === 'win32') {
244
+ // Try winget (available on Windows 10 21H2+ / Windows 11)
245
+ if (commandExistsSync('winget')) {
246
+ print(` ${c.muted}Installing Visual Studio Build Tools via winget...${c.reset}`);
247
+ const ok = await runInstallArgs('winget', [
248
+ 'install', 'Microsoft.VisualStudio.2022.BuildTools',
249
+ '--override', '--add Microsoft.VisualStudio.Workload.VCTools --passive --norestart',
250
+ '--accept-source-agreements', '--accept-package-agreements',
251
+ ]);
252
+ return ok;
253
+ }
254
+ return false;
255
+ }
256
+
257
+ if (platform === 'darwin') {
258
+ print(` ${c.muted}Installing Xcode Command Line Tools...${c.reset}`);
259
+ // xcode-select --install opens a system dialog; returns non-zero if already installed
260
+ const ok = await runInstallArgs('xcode-select', ['--install']);
261
+ // Even if xcode-select exits non-zero (already installed), the tools may be present
262
+ return ok;
263
+ }
264
+
265
+ // Linux — requires sudo, skip auto-install
266
+ return false;
267
+ }
268
+
145
269
  // ---------------------------------------------------------------------------
146
270
  // Doctor Checks (standalone + used by wizard)
147
271
  // ---------------------------------------------------------------------------
@@ -387,37 +511,87 @@ async function installProvider(provider) {
387
511
  }
388
512
  }
389
513
 
514
+ // Aider: detect Python 3.13+ and use uv with Python 3.12 pin
515
+ let installCmd = provider.installCmd;
516
+ let fallbackCmd = provider.fallbackCmd;
517
+ let failureHint = '';
518
+
519
+ if (provider.binary === 'aider') {
520
+ const pyVer = detectPythonVersion();
521
+ if (pyVer && pyVer.major >= 3 && pyVer.minor >= 13) {
522
+ print(` ${c.muted}Python ${pyVer.major}.${pyVer.minor} detected — aider requires Python 3.10-3.12${c.reset}`);
523
+ print(` ${c.muted}Using uv to install with Python 3.12...${c.reset}`);
524
+ failureHint = 'python 3.13';
525
+
526
+ // Try installing uv first if not available
527
+ let hasUv = commandExistsSync('uv');
528
+ if (!hasUv) {
529
+ print(` ${c.muted}Installing uv (universal Python package manager)...${c.reset}`);
530
+ hasUv = await runInstall('pip install uv');
531
+ if (!hasUv) hasUv = await runInstall('pip3 install uv');
532
+ }
533
+
534
+ if (hasUv) {
535
+ installCmd = 'uv tool install --python python3.12 aider-chat';
536
+ fallbackCmd = null;
537
+ } else {
538
+ print(` ${icons.fail} ${c.error}Could not install uv.${c.reset}`);
539
+ printNextSteps(provider, 'python setuptools');
540
+ return;
541
+ }
542
+ }
543
+ }
544
+
390
545
  print(` Installing ${provider.name}...`);
391
- let success = await runInstall(provider.installCmd);
546
+ let success = await runInstall(installCmd);
392
547
 
393
- if (!success && provider.fallbackCmd) {
394
- print(` Trying fallback: ${provider.fallbackCmd}...`);
395
- success = await runInstall(provider.fallbackCmd);
548
+ if (!success && fallbackCmd) {
549
+ print(` Trying fallback: ${fallbackCmd}...`);
550
+ success = await runInstall(fallbackCmd);
396
551
  }
397
552
 
398
553
  if (success) {
399
554
  print(` ${icons.pass} ${provider.name} installed`);
400
555
  } else {
401
- print(` ${icons.fail} ${c.error}Installation failed.${c.reset}`);
402
- // Platform-specific hints for native module failures (npm packages may need C/C++ build tools)
556
+ // For npm packages, try auto-installing build tools and retrying
403
557
  if (provider.runtime === 'npm') {
404
- const platform = process.platform;
405
- if (platform === 'win32') {
406
- print(` ${c.muted}If you see build/compile errors, install Visual Studio Build Tools:${c.reset}`);
407
- print(` ${c.url}https://aka.ms/vs/17/release/vs_BuildTools.exe${c.reset}`);
408
- print(` ${c.muted}Select "Desktop development with C++" workload.${c.reset}`);
409
- } else if (platform === 'darwin') {
410
- print(` ${c.muted}If you see build errors, ensure Xcode CLT is installed:${c.reset}`);
411
- print(` ${c.cmd}xcode-select --install${c.reset}`);
412
- } else {
413
- print(` ${c.muted}If you see build errors, ensure build tools are installed:${c.reset}`);
414
- print(` ${c.cmd}sudo apt install python3 build-essential${c.reset}`);
558
+ print(` ${c.muted}Build tools may be needed. Attempting automatic install...${c.reset}`);
559
+ const toolsInstalled = await tryInstallBuildTools();
560
+
561
+ if (toolsInstalled) {
562
+ print(` ${c.muted}Retrying ${provider.name} install...${c.reset}`);
563
+ success = await runInstall(installCmd);
415
564
  }
565
+
566
+ if (success) {
567
+ print(` ${icons.pass} ${provider.name} installed`);
568
+ return;
569
+ }
570
+
571
+ failureHint = failureHint || 'build tools';
416
572
  }
417
- print(` ${c.muted}Or install manually:${c.reset} ${provider.installCmd}`);
573
+
574
+ // Enterprise-grade: show numbered next-steps
575
+ print(` ${icons.fail} ${c.error}Installation failed.${c.reset}`);
576
+ printNextSteps(provider, failureHint);
418
577
  }
419
578
  }
420
579
 
580
+ /**
581
+ * Print numbered next-steps for a failed install.
582
+ * @param {object} provider - CLI_PROVIDERS entry
583
+ * @param {string} [hint] - Failure hint
584
+ */
585
+ function printNextSteps(provider, hint = '') {
586
+ const steps = getCliNextSteps(provider, hint);
587
+ print('');
588
+ print(` ${c.heading}Next steps to resolve:${c.reset}`);
589
+ steps.forEach((step, i) => {
590
+ print(` ${c.muted}${i + 1}.${c.reset} ${step}`);
591
+ });
592
+ print('');
593
+ }
594
+
421
595
  // ---------------------------------------------------------------------------
422
596
  // Start Server
423
597
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colana",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.80",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -86,8 +86,15 @@
86
86
  ],
87
87
  "license": "SEE LICENSE IN LICENSE",
88
88
  "private": false,
89
- "os": ["linux", "darwin", "win32"],
90
- "cpu": ["x64", "arm64"],
89
+ "os": [
90
+ "linux",
91
+ "darwin",
92
+ "win32"
93
+ ],
94
+ "cpu": [
95
+ "x64",
96
+ "arm64"
97
+ ],
91
98
  "devDependencies": {
92
99
  "@playwright/test": "^1.58.2",
93
100
  "@vitest/coverage-v8": "^4.0.18",