refactor: move setup scripts out of src/ to reduce build token count

Setup scripts are standalone CLI tools run via tsx with no runtime
imports from the main app. Moving them out of src/ excludes them from
the tsc build output and reduces the compiled bundle size.

- git mv src/setup/ setup/
- Fix imports to use ../src/logger.js and ../src/config.js
- Update package.json, vitest.config.ts, SKILL.md references
- Fix platform tests to be cross-platform (macOS + Linux)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-22 18:43:22 +02:00
parent c1a2491e77
commit 92d14405c5
18 changed files with 39 additions and 39 deletions

130
setup/platform.ts Normal file
View File

@@ -0,0 +1,130 @@
/**
* Cross-platform detection utilities for NanoClaw setup.
*/
import { execSync } from 'child_process';
import fs from 'fs';
import os from 'os';
export type Platform = 'macos' | 'linux' | 'unknown';
export type ServiceManager = 'launchd' | 'systemd' | 'none';
export function getPlatform(): Platform {
const platform = os.platform();
if (platform === 'darwin') return 'macos';
if (platform === 'linux') return 'linux';
return 'unknown';
}
export function isWSL(): boolean {
if (os.platform() !== 'linux') return false;
try {
const release = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
return release.includes('microsoft') || release.includes('wsl');
} catch {
return false;
}
}
export function isRoot(): boolean {
return process.getuid?.() === 0;
}
export function isHeadless(): boolean {
// No display server available
if (getPlatform() === 'linux') {
return !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
}
// macOS is never headless in practice (even SSH sessions can open URLs)
return false;
}
export function hasSystemd(): boolean {
if (getPlatform() !== 'linux') return false;
try {
// Check if systemd is PID 1
const init = fs.readFileSync('/proc/1/comm', 'utf-8').trim();
return init === 'systemd';
} catch {
return false;
}
}
/**
* Open a URL in the default browser, cross-platform.
* Returns true if the command was attempted, false if no method available.
*/
export function openBrowser(url: string): boolean {
try {
const platform = getPlatform();
if (platform === 'macos') {
execSync(`open ${JSON.stringify(url)}`, { stdio: 'ignore' });
return true;
}
if (platform === 'linux') {
// Try xdg-open first, then wslview for WSL
if (commandExists('xdg-open')) {
execSync(`xdg-open ${JSON.stringify(url)}`, { stdio: 'ignore' });
return true;
}
if (isWSL() && commandExists('wslview')) {
execSync(`wslview ${JSON.stringify(url)}`, { stdio: 'ignore' });
return true;
}
// WSL without wslview: try cmd.exe
if (isWSL()) {
try {
execSync(`cmd.exe /c start "" ${JSON.stringify(url)}`, { stdio: 'ignore' });
return true;
} catch {
// cmd.exe not available
}
}
}
} catch {
// Command failed
}
return false;
}
export function getServiceManager(): ServiceManager {
const platform = getPlatform();
if (platform === 'macos') return 'launchd';
if (platform === 'linux') {
if (hasSystemd()) return 'systemd';
return 'none';
}
return 'none';
}
export function getNodePath(): string {
try {
return execSync('command -v node', { encoding: 'utf-8' }).trim();
} catch {
return process.execPath;
}
}
export function commandExists(name: string): boolean {
try {
execSync(`command -v ${name}`, { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
export function getNodeVersion(): string | null {
try {
const version = execSync('node --version', { encoding: 'utf-8' }).trim();
return version.replace(/^v/, '');
} catch {
return null;
}
}
export function getNodeMajorVersion(): number | null {
const version = getNodeVersion();
if (!version) return null;
const major = parseInt(version.split('.')[0], 10);
return isNaN(major) ? null : major;
}