colana 1.0.0-beta.4 → 1.0.0-beta.40

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
@@ -187,6 +187,12 @@ async function createKey(flags) {
187
187
  }
188
188
  }
189
189
 
190
+ if (!result || !result.name) {
191
+ console.error(`\n ${icons.fail} ${c.error}Key generation returned an unexpected response.${c.reset}\n`);
192
+ process.exit(1);
193
+ return; // Guard for test environments where process.exit is mocked
194
+ }
195
+
190
196
  console.log('');
191
197
  console.log(` ${icons.pass} Key generated successfully!`);
192
198
  console.log(` ${c.muted}${'─'.repeat(40)}${c.reset}`);
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,22 +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} You can install manually:`);
402
- print(` ${provider.installCmd}`);
556
+ // For npm packages, try auto-installing build tools and retrying
557
+ if (provider.runtime === 'npm') {
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);
564
+ }
565
+
566
+ if (success) {
567
+ print(` ${icons.pass} ${provider.name} installed`);
568
+ return;
569
+ }
570
+
571
+ failureHint = failureHint || 'build tools';
572
+ }
573
+
574
+ // Enterprise-grade: show numbered next-steps
575
+ print(` ${icons.fail} ${c.error}Installation failed.${c.reset}`);
576
+ printNextSteps(provider, failureHint);
403
577
  }
404
578
  }
405
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
+
406
595
  // ---------------------------------------------------------------------------
407
596
  // Start Server
408
597
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colana",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.40",
4
4
  "description": "Agent-First. Multiplied. Multi-agent command center for AI coding agents.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -16,11 +16,9 @@
16
16
  "scripts/postinstall.cjs",
17
17
  "public/*.html",
18
18
  "public/*.css",
19
- "public/app.js",
19
+ "public/*.js",
20
20
  "public/vendor/**",
21
21
  "public/manifest.json",
22
- "public/service-worker.js",
23
- "public/sw-register.js",
24
22
  "public/icons/*",
25
23
  "data/.gitkeep"
26
24
  ],