scalemax 0.1.3 → 0.1.5
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/dist/bootstrap.cjs +15 -0
- package/dist/clients.js +8 -5
- package/dist/commands/configure.js +30 -18
- package/dist/configure/cline.d.ts +3 -0
- package/dist/configure/cline.js +13 -0
- package/dist/configure/codex.d.ts +4 -1
- package/dist/configure/codex.js +20 -0
- package/dist/configure/paths.js +4 -0
- package/dist/configure/roo.d.ts +3 -0
- package/dist/configure/roo.js +13 -0
- package/dist/configure/templates.js +9 -2
- package/dist/configure/vscode.js +6 -6
- package/dist/detect/cline.d.ts +2 -0
- package/dist/detect/cline.js +2 -0
- package/dist/detect/common.js +29 -2
- package/dist/detect/roo.d.ts +2 -0
- package/dist/detect/roo.js +2 -0
- package/dist/index.js +4 -3
- package/dist/types/client.d.ts +7 -1
- package/dist/utils/runtime.d.ts +19 -1
- package/dist/utils/runtime.js +58 -2
- package/dist/utils/systemAudit.d.ts +1 -0
- package/dist/utils/systemAudit.js +32 -0
- package/package.json +4 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
function cmp(a,b){a=String(a).replace(/^v/,'').split('.').map(Number);b=String(b).split('.').map(Number);for(var i=0;i<3;i++){var d=(a[i]||0)-(b[i]||0);if(d)return d;}return 0;}
|
|
4
|
+
var min='18.17.0';
|
|
5
|
+
if (cmp(process.versions.node,min)<0) {
|
|
6
|
+
console.error('ScaleMax requires Node.js >= '+min+'. Current: '+process.versions.node);
|
|
7
|
+
console.error('Install Node.js LTS, then rerun: npx scalemax@latest');
|
|
8
|
+
console.error('macOS: brew install node@22 && brew link --overwrite node@22');
|
|
9
|
+
console.error('Ubuntu/Debian/WSL: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs');
|
|
10
|
+
console.error('Fedora: sudo dnf install -y nodejs npm');
|
|
11
|
+
console.error('Arch: sudo pacman -S --needed nodejs npm');
|
|
12
|
+
console.error('Windows PowerShell: winget install OpenJS.NodeJS.LTS');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
import('./index.js').catch(function(err){ console.error(err && err.stack || err); process.exit(1); });
|
package/dist/clients.js
CHANGED
|
@@ -2,8 +2,6 @@ import * as codexDetect from './detect/codex.js';
|
|
|
2
2
|
import * as codexConfig from './configure/codex.js';
|
|
3
3
|
import * as cursorDetect from './detect/cursor.js';
|
|
4
4
|
import * as cursorConfig from './configure/cursor.js';
|
|
5
|
-
import * as claudeDetect from './detect/claude.js';
|
|
6
|
-
import * as claudeConfig from './configure/claude.js';
|
|
7
5
|
import * as opencodeDetect from './detect/opencode.js';
|
|
8
6
|
import * as opencodeConfig from './configure/opencode.js';
|
|
9
7
|
import * as openclawDetect from './detect/openclaw.js';
|
|
@@ -14,16 +12,21 @@ import * as continueDetect from './detect/continue.js';
|
|
|
14
12
|
import * as continueConfig from './configure/continue.js';
|
|
15
13
|
import * as vscodeDetect from './detect/vscode.js';
|
|
16
14
|
import * as vscodeConfig from './configure/vscode.js';
|
|
15
|
+
import * as rooDetect from './detect/roo.js';
|
|
16
|
+
import * as rooConfig from './configure/roo.js';
|
|
17
|
+
import * as clineDetect from './detect/cline.js';
|
|
18
|
+
import * as clineConfig from './configure/cline.js';
|
|
17
19
|
import * as windsurfDetect from './detect/windsurf.js';
|
|
18
20
|
import * as windsurfConfig from './configure/windsurf.js';
|
|
19
21
|
export const clients = [
|
|
20
|
-
{ id: 'codex', name: 'Codex', detect: codexDetect.detect, configure: codexConfig.configure, remove: codexConfig.remove },
|
|
22
|
+
{ id: 'codex', name: 'Codex', detect: codexDetect.detect, install: codexConfig.install, configure: codexConfig.configure, verify: codexConfig.verify, launch: codexConfig.launch, remove: codexConfig.remove },
|
|
21
23
|
{ id: 'cursor', name: 'Cursor', detect: cursorDetect.detect, configure: cursorConfig.configure, remove: cursorConfig.remove },
|
|
22
|
-
{ id: 'claude', name: 'Claude Code', detect: claudeDetect.detect, configure: claudeConfig.configure, remove: claudeConfig.remove },
|
|
23
24
|
{ id: 'opencode', name: 'OpenCode', detect: opencodeDetect.detect, configure: opencodeConfig.configure, remove: opencodeConfig.remove },
|
|
24
25
|
{ id: 'openclaw', name: 'OpenClaw', detect: openclawDetect.detect, configure: openclawConfig.configure, remove: openclawConfig.remove },
|
|
25
26
|
{ id: 'hermes', name: 'Hermes', detect: hermesDetect.detect, configure: hermesConfig.configure, remove: hermesConfig.remove },
|
|
26
|
-
{ id: 'vscode', name: 'VS Code', detect: vscodeDetect.detect, configure: vscodeConfig.configure, remove: vscodeConfig.remove },
|
|
27
|
+
{ id: 'vscode', name: 'Continue Extension (VS Code)', detect: vscodeDetect.detect, configure: vscodeConfig.configure, remove: vscodeConfig.remove },
|
|
28
|
+
{ id: 'roo', name: 'Roo Code (VS Code)', detect: rooDetect.detect, configure: rooConfig.configure, remove: rooConfig.remove },
|
|
29
|
+
{ id: 'cline', name: 'Cline (VS Code)', detect: clineDetect.detect, configure: clineConfig.configure, remove: clineConfig.remove },
|
|
27
30
|
{ id: 'continue', name: 'Continue', detect: continueDetect.detect, configure: continueConfig.configure, remove: continueConfig.remove },
|
|
28
31
|
{ id: 'windsurf', name: 'Windsurf', detect: windsurfDetect.detect, configure: windsurfConfig.configure, remove: windsurfConfig.remove }
|
|
29
32
|
];
|
|
@@ -5,8 +5,6 @@ import { clients } from '../clients.js';
|
|
|
5
5
|
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
6
6
|
import { loadApiKey } from '../utils/keyStore.js';
|
|
7
7
|
import { homeDir } from '../utils/paths.js';
|
|
8
|
-
import { launchCodex } from '../configure/codex.js';
|
|
9
|
-
import { ensureCodexInstalled } from '../install/codex.js';
|
|
10
8
|
export async function detectAll(home = homeDir()) {
|
|
11
9
|
const ctx = { home, platform: process.platform, env: process.env };
|
|
12
10
|
return Promise.all(clients.map(c => c.detect(ctx)));
|
|
@@ -27,13 +25,16 @@ async function maybeLaunchCodex(chosen, successful) {
|
|
|
27
25
|
if (isCancel(answer) || answer === false)
|
|
28
26
|
return;
|
|
29
27
|
console.log('\nLaunching Codex...\n');
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
const codex = clients.find(c => c.id === 'codex');
|
|
29
|
+
try {
|
|
30
|
+
const apiKey = await loadApiKey(homeDir());
|
|
31
|
+
if (!apiKey)
|
|
32
|
+
throw new Error('No API key found.');
|
|
33
|
+
const result = await codex?.launch?.({ home: homeDir(), platform: process.platform, env: process.env, apiKey, baseUrl: SCALEMAX_BASE_URL });
|
|
34
|
+
console.log(chalk.green(`✓ ${result?.message || 'Codex launched.'}`));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.log(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
37
38
|
}
|
|
38
39
|
console.log('\nIf Codex asks for authentication,\nit should already be configured to use ScaleMax.\n\nIf you still see the official login screen,\nthe configuration is incomplete or Codex rejected the generated auth/config files.\n');
|
|
39
40
|
}
|
|
@@ -62,20 +63,31 @@ export async function configureCommand(opts = {}) {
|
|
|
62
63
|
console.log(chalk.yellow('No detected clients to configure.'));
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
|
-
if (chosen.includes('codex')) {
|
|
66
|
-
const install = await ensureCodexInstalled(process.stdin.isTTY);
|
|
67
|
-
if (!install.ok)
|
|
68
|
-
console.log(chalk.yellow(install.message));
|
|
69
|
-
else
|
|
70
|
-
console.log(chalk.green(`✓ ${install.message}`));
|
|
71
|
-
}
|
|
72
66
|
const successful = new Set();
|
|
73
67
|
for (const client of clients.filter(c => chosen.includes(c.id))) {
|
|
68
|
+
const baseCtx = { home, platform: process.platform, env: process.env };
|
|
69
|
+
if (client.install) {
|
|
70
|
+
const spinner = ora(`Checking ${client.name}`).start();
|
|
71
|
+
try {
|
|
72
|
+
const res = await client.install({ ...baseCtx, interactive: process.stdin.isTTY });
|
|
73
|
+
spinner.succeed(`${client.name} ${res.message}`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
spinner.fail(`${client.name} install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
74
80
|
const spinner = ora(`Configuring ${client.name}`).start();
|
|
75
81
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
82
|
+
const ctx = { ...baseCtx, apiKey, baseUrl: SCALEMAX_BASE_URL };
|
|
83
|
+
const res = await client.configure(ctx);
|
|
78
84
|
spinner.succeed(`${client.name} ${res.message}`);
|
|
85
|
+
if (client.verify) {
|
|
86
|
+
const verify = ora(`Verifying ${client.name}`).start();
|
|
87
|
+
const v = await client.verify(ctx);
|
|
88
|
+
verify.succeed(`${client.name} ${v.message}`);
|
|
89
|
+
}
|
|
90
|
+
successful.add(client.id);
|
|
79
91
|
}
|
|
80
92
|
catch (err) {
|
|
81
93
|
spinner.fail(`${client.name} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const id = 'cline';
|
|
2
|
+
const name = 'Cline (VS Code)';
|
|
3
|
+
export async function configure(ctx) {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
name,
|
|
7
|
+
changed: false,
|
|
8
|
+
message: `detected. Open the extension settings and select API Provider: OpenAI Compatible; Base URL: ${ctx.baseUrl}; API Key: your ScaleMax key; Model ID: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, or codex-auto-review. Official docs: https://docs.cline.bot/provider-config/openai-compatible`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export async function remove(_ctx) {
|
|
12
|
+
return { id, name, changed: false, message: 'Roo/Cline settings are stored inside VS Code extension state. Remove or edit them from the extension settings UI.' };
|
|
13
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigureContext, ConfigureResult } from '../types/client.js';
|
|
1
|
+
import { ConfigureContext, ConfigureResult, InstallContext } from '../types/client.js';
|
|
2
2
|
export interface CodexVerificationResult {
|
|
3
3
|
ok: boolean;
|
|
4
4
|
warnings: string[];
|
|
@@ -7,8 +7,11 @@ export interface CodexVerificationResult {
|
|
|
7
7
|
authPath: string;
|
|
8
8
|
}
|
|
9
9
|
export declare function verifyCodexConfiguration(ctx: ConfigureContext): Promise<CodexVerificationResult>;
|
|
10
|
+
export declare function install(ctx: InstallContext): Promise<ConfigureResult>;
|
|
11
|
+
export declare function verify(ctx: ConfigureContext): Promise<ConfigureResult>;
|
|
10
12
|
export declare function configure(ctx: ConfigureContext): Promise<ConfigureResult>;
|
|
11
13
|
export declare function remove(ctx: ConfigureContext): Promise<ConfigureResult>;
|
|
14
|
+
export declare function launch(ctx?: ConfigureContext): Promise<ConfigureResult>;
|
|
12
15
|
export declare function launchCodex(): Promise<{
|
|
13
16
|
ok: boolean;
|
|
14
17
|
message: string;
|
package/dist/configure/codex.js
CHANGED
|
@@ -164,6 +164,20 @@ export async function verifyCodexConfiguration(ctx) {
|
|
|
164
164
|
}
|
|
165
165
|
return { ok: errors.length === 0, warnings, errors, configPath, authPath };
|
|
166
166
|
}
|
|
167
|
+
export async function install(ctx) {
|
|
168
|
+
const mod = await import('../install/codex.js');
|
|
169
|
+
const res = await mod.ensureCodexInstalled(ctx.interactive ?? process.stdin.isTTY);
|
|
170
|
+
if (!res.ok)
|
|
171
|
+
throw new Error(res.message);
|
|
172
|
+
return { id, name, changed: res.installed, message: res.message };
|
|
173
|
+
}
|
|
174
|
+
export async function verify(ctx) {
|
|
175
|
+
const verification = await verifyCodexConfiguration(ctx);
|
|
176
|
+
if (!verification.ok)
|
|
177
|
+
throw new Error(verification.errors.join('\n'));
|
|
178
|
+
const warnings = verification.warnings.length ? ` Warnings: ${verification.warnings.join(' ')}` : '';
|
|
179
|
+
return { id, name, changed: false, path: verification.configPath, message: `verified.${warnings}` };
|
|
180
|
+
}
|
|
167
181
|
export async function configure(ctx) {
|
|
168
182
|
const configPath = codexConfigPath(ctx.home);
|
|
169
183
|
const authPath = codexAuthPath(ctx.home);
|
|
@@ -195,6 +209,12 @@ export async function remove(ctx) {
|
|
|
195
209
|
messages.push(`legacy config.json: ${await restoreOrRemove(legacyConfig, ctx.home)}`);
|
|
196
210
|
return { id, name, changed: true, path: codexConfigPath(ctx.home), message: messages.join('; ') };
|
|
197
211
|
}
|
|
212
|
+
export async function launch(ctx) {
|
|
213
|
+
const result = await launchCodex();
|
|
214
|
+
if (!result.ok)
|
|
215
|
+
throw new Error(result.output ? `${result.message} ${result.output}` : result.message);
|
|
216
|
+
return { id, name, changed: false, message: result.message };
|
|
217
|
+
}
|
|
198
218
|
export async function launchCodex() {
|
|
199
219
|
if (!(await commandExists('codex')))
|
|
200
220
|
return { ok: false, message: 'Codex executable was not found on PATH.' };
|
package/dist/configure/paths.js
CHANGED
|
@@ -14,6 +14,8 @@ export function clientConfigPath(id, home, platform, env = process.env) {
|
|
|
14
14
|
case 'openclaw': return path.join(home, '.openclaw', 'config.json');
|
|
15
15
|
case 'hermes': return path.join(home, '.hermes', 'config', 'scalemax.json');
|
|
16
16
|
case 'continue': return path.join(home, '.continue', 'config.json');
|
|
17
|
+
case 'roo': return path.join(home, '.scalemax', 'roo-code-vscode.manual.json');
|
|
18
|
+
case 'cline': return path.join(home, '.scalemax', 'cline-vscode.manual.json');
|
|
17
19
|
case 'vscode': return platform === 'win32' ? path.join(app, 'Code', 'User', 'settings.json') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json') : path.join(linux, 'Code', 'User', 'settings.json');
|
|
18
20
|
case 'windsurf': return platform === 'win32' ? path.join(app, 'Windsurf', 'User', 'settings.json') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'Windsurf', 'User', 'settings.json') : path.join(linux, 'Windsurf', 'User', 'settings.json');
|
|
19
21
|
}
|
|
@@ -29,6 +31,8 @@ export function installHints(id, home, platform, env = process.env) {
|
|
|
29
31
|
case 'openclaw': return [path.join(home, '.openclaw'), path.join(linux, 'openclaw')];
|
|
30
32
|
case 'hermes': return [path.join(home, '.hermes')];
|
|
31
33
|
case 'continue': return [path.join(home, '.continue')];
|
|
34
|
+
case 'roo': return [];
|
|
35
|
+
case 'cline': return [];
|
|
32
36
|
case 'vscode': return [platform === 'darwin' ? '/Applications/Visual Studio Code.app' : path.join(app, 'Code'), path.join(linux, 'Code')];
|
|
33
37
|
case 'windsurf': return [platform === 'darwin' ? '/Applications/Windsurf.app' : path.join(app, 'Windsurf'), path.join(linux, 'Windsurf')];
|
|
34
38
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const id = 'roo';
|
|
2
|
+
const name = 'Roo Code (VS Code)';
|
|
3
|
+
export async function configure(ctx) {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
name,
|
|
7
|
+
changed: false,
|
|
8
|
+
message: `detected. Open the extension settings and select API Provider: OpenAI Compatible; Base URL: ${ctx.baseUrl}; API Key: your ScaleMax key; Model ID: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, or codex-auto-review. Official docs: https://docs.roocode.com/providers/openai-compatible`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export async function remove(_ctx) {
|
|
12
|
+
return { id, name, changed: false, message: 'Roo/Cline settings are stored inside VS Code extension state. Remove or edit them from the extension settings UI.' };
|
|
13
|
+
}
|
|
@@ -11,9 +11,16 @@ export function jsonConfig(client, apiKey, baseUrl, extra = {}) {
|
|
|
11
11
|
}, null, 2) + '\n';
|
|
12
12
|
}
|
|
13
13
|
export function continueConfig(apiKey, baseUrl) {
|
|
14
|
+
const models = ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'codex-auto-review'];
|
|
14
15
|
return JSON.stringify({
|
|
15
|
-
models:
|
|
16
|
-
|
|
16
|
+
models: models.map(model => ({
|
|
17
|
+
title: `ScaleMax ${model}`,
|
|
18
|
+
provider: 'openai',
|
|
19
|
+
model,
|
|
20
|
+
apiBase: baseUrl,
|
|
21
|
+
apiKey
|
|
22
|
+
})),
|
|
23
|
+
tabAutocompleteModel: { title: 'ScaleMax gpt-5.4-mini', provider: 'openai', model: 'gpt-5.4-mini', apiBase: baseUrl, apiKey },
|
|
17
24
|
contextProviders: [{ name: 'code' }, { name: 'docs' }, { name: 'diff' }, { name: 'terminal' }]
|
|
18
25
|
}, null, 2) + '\n';
|
|
19
26
|
}
|
package/dist/configure/vscode.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { clientConfigPath } from './paths.js';
|
|
2
|
-
import { removeManagedConfig,
|
|
3
|
-
import {
|
|
2
|
+
import { removeManagedConfig, writeManagedConfig } from './common.js';
|
|
3
|
+
import { continueConfig } from './templates.js';
|
|
4
4
|
const id = 'vscode';
|
|
5
|
-
const name = 'VS Code';
|
|
5
|
+
const name = 'Continue Extension (VS Code)';
|
|
6
6
|
export async function configure(ctx) {
|
|
7
|
-
const file = clientConfigPath(
|
|
8
|
-
return
|
|
7
|
+
const file = clientConfigPath('continue', ctx.home, ctx.platform);
|
|
8
|
+
return writeManagedConfig(ctx, id, name, file, continueConfig(ctx.apiKey, ctx.baseUrl));
|
|
9
9
|
}
|
|
10
10
|
export async function remove(ctx) {
|
|
11
|
-
const file = clientConfigPath(
|
|
11
|
+
const file = clientConfigPath('continue', ctx.home, ctx.platform);
|
|
12
12
|
return removeManagedConfig(ctx, id, name, file);
|
|
13
13
|
}
|
package/dist/detect/common.js
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { commandExists } from '../utils/exec.js';
|
|
3
3
|
import { clientConfigPath, installHints } from '../configure/paths.js';
|
|
4
|
+
function vscodeExtensionRoots(ctx) {
|
|
5
|
+
const app = ctx.env.APPDATA || '';
|
|
6
|
+
const xdg = ctx.env.XDG_DATA_HOME || pathJoin(ctx.home, '.local', 'share');
|
|
7
|
+
if (ctx.platform === 'win32')
|
|
8
|
+
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(app, 'Code', 'User', 'globalStorage')];
|
|
9
|
+
if (ctx.platform === 'darwin')
|
|
10
|
+
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(ctx.home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage')];
|
|
11
|
+
return [pathJoin(ctx.home, '.vscode', 'extensions'), pathJoin(xdg, 'Code', 'User', 'globalStorage')];
|
|
12
|
+
}
|
|
13
|
+
function pathJoin(...parts) { return parts.filter(Boolean).join('/'); }
|
|
14
|
+
async function detectVsCodeExtension(ctx, patterns) {
|
|
15
|
+
const hits = [];
|
|
16
|
+
for (const root of vscodeExtensionRoots(ctx)) {
|
|
17
|
+
const entries = await fs.readdir(root).catch(() => []);
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const lower = entry.toLowerCase();
|
|
20
|
+
if (patterns.some(pattern => lower.includes(pattern)))
|
|
21
|
+
hits.push(`${root}/${entry}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return hits;
|
|
25
|
+
}
|
|
4
26
|
const commands = {
|
|
5
|
-
codex: ['codex'], cursor: ['cursor'], claude: ['claude'], opencode: ['opencode'], openclaw: ['openclaw'], hermes: ['hermes'], continue: ['continue'], vscode: ['code'], windsurf: ['windsurf']
|
|
27
|
+
codex: ['codex'], cursor: ['cursor'], claude: ['claude'], opencode: ['opencode'], openclaw: ['openclaw'], hermes: ['hermes'], continue: ['continue'], vscode: ['code'], roo: [], cline: [], windsurf: ['windsurf']
|
|
6
28
|
};
|
|
7
29
|
export async function detectClient(ctx, id, name) {
|
|
8
30
|
const paths = [clientConfigPath(id, ctx.home, ctx.platform, ctx.env), ...installHints(id, ctx.home, ctx.platform, ctx.env)];
|
|
@@ -14,6 +36,11 @@ export async function detectClient(ctx, id, name) {
|
|
|
14
36
|
for (const cmd of commands[id])
|
|
15
37
|
if (await commandExists(cmd))
|
|
16
38
|
cmdHits.push(`command:${cmd}`);
|
|
17
|
-
const
|
|
39
|
+
const extensionHits = id === 'roo'
|
|
40
|
+
? await detectVsCodeExtension(ctx, ['rooveterinaryinc.roo-cline', 'roo-cline', 'roo-code'])
|
|
41
|
+
: id === 'cline'
|
|
42
|
+
? await detectVsCodeExtension(ctx, ['saoudrizwan.claude-dev', 'cline.cline'])
|
|
43
|
+
: [];
|
|
44
|
+
const hits = [...cmdHits, ...pathHits, ...extensionHits];
|
|
18
45
|
return { id, name, installed: hits.length > 0, reason: hits.length ? undefined : 'not detected', paths: hits };
|
|
19
46
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,8 @@ import { modelsCommand } from './commands/models.js';
|
|
|
12
12
|
import { updateCommand } from './commands/update.js';
|
|
13
13
|
import { removeCommand } from './commands/remove.js';
|
|
14
14
|
import { ensureInstallerApiKey } from './utils/installerConfig.js';
|
|
15
|
-
import { isNodeSupported, nodeUpgradeHint
|
|
15
|
+
import { isNodeSupported, nodeUpgradeHint } from './utils/runtime.js';
|
|
16
|
+
import { runSystemAudit } from './utils/systemAudit.js';
|
|
16
17
|
function packageVersion() {
|
|
17
18
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const candidates = [path.join(here, '..', 'package.json'), path.join(here, 'package.json')];
|
|
@@ -41,11 +42,11 @@ async function main() {
|
|
|
41
42
|
try {
|
|
42
43
|
if (!command) {
|
|
43
44
|
welcome();
|
|
44
|
-
|
|
45
|
+
await runSystemAudit();
|
|
45
46
|
const apiKey = await ensureInstallerApiKey(process.env.HOME || process.env.USERPROFILE || process.cwd());
|
|
46
47
|
if (!apiKey)
|
|
47
48
|
return;
|
|
48
|
-
await configureCommand();
|
|
49
|
+
await configureCommand({ promptLaunch: true });
|
|
49
50
|
outro(chalk.green('ScaleMax is ready.'));
|
|
50
51
|
return;
|
|
51
52
|
}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
export type ClientId = 'codex' | 'cursor' | 'claude' | 'opencode' | 'openclaw' | 'hermes' | 'continue' | 'vscode' | 'windsurf';
|
|
1
|
+
export type ClientId = 'codex' | 'cursor' | 'claude' | 'opencode' | 'openclaw' | 'hermes' | 'continue' | 'vscode' | 'roo' | 'cline' | 'windsurf';
|
|
2
2
|
export interface ClientDefinition {
|
|
3
3
|
id: ClientId;
|
|
4
4
|
name: string;
|
|
5
5
|
detect: (ctx: DetectContext) => Promise<DetectionResult>;
|
|
6
|
+
install?: (ctx: InstallContext) => Promise<ConfigureResult>;
|
|
6
7
|
configure: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
8
|
+
verify?: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
9
|
+
launch?: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
7
10
|
remove: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
8
11
|
}
|
|
9
12
|
export interface DetectContext {
|
|
@@ -21,6 +24,9 @@ export interface DetectionResult {
|
|
|
21
24
|
configured?: boolean;
|
|
22
25
|
installable?: boolean;
|
|
23
26
|
}
|
|
27
|
+
export interface InstallContext extends DetectContext {
|
|
28
|
+
interactive?: boolean;
|
|
29
|
+
}
|
|
24
30
|
export interface ConfigureContext extends DetectContext {
|
|
25
31
|
apiKey: string;
|
|
26
32
|
baseUrl: string;
|
package/dist/utils/runtime.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
export declare const MIN_NODE = "18.17.0";
|
|
2
|
+
export declare const RECOMMENDED_NODE = "22";
|
|
3
|
+
export type OsFamily = 'macos' | 'ubuntu' | 'debian' | 'fedora' | 'arch' | 'windows' | 'wsl' | 'linux' | 'unknown';
|
|
4
|
+
export type RuntimeAudit = {
|
|
5
|
+
os: OsFamily;
|
|
6
|
+
platform: NodeJS.Platform;
|
|
7
|
+
arch: string;
|
|
8
|
+
isWsl: boolean;
|
|
9
|
+
nodeVersion: string;
|
|
10
|
+
nodeOk: boolean;
|
|
11
|
+
npmVersion?: string;
|
|
12
|
+
npmOk: boolean;
|
|
13
|
+
pathEntries: string[];
|
|
14
|
+
packageManager?: string;
|
|
15
|
+
installCommand?: string;
|
|
16
|
+
};
|
|
2
17
|
export declare function compareSemver(a: string, b: string): number;
|
|
3
18
|
export declare function isNodeSupported(version?: string): boolean;
|
|
4
19
|
export declare function npmVersion(): Promise<string | undefined>;
|
|
5
|
-
export declare function
|
|
20
|
+
export declare function detectOsFamily(): Promise<OsFamily>;
|
|
21
|
+
export declare function runtimeAudit(): Promise<RuntimeAudit>;
|
|
22
|
+
export declare function nodeInstallCommand(osFamily: OsFamily, pm?: string): string | undefined;
|
|
6
23
|
export declare function nodeUpgradeHint(): string;
|
|
24
|
+
export declare function runtimeReport(): Promise<string>;
|
package/dist/utils/runtime.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
1
2
|
import { execa } from 'execa';
|
|
3
|
+
import { commandExists } from './exec.js';
|
|
2
4
|
export const MIN_NODE = '18.17.0';
|
|
5
|
+
export const RECOMMENDED_NODE = '22';
|
|
3
6
|
export function compareSemver(a, b) { const pa = a.replace(/^v/, '').split('.').map(Number), pb = b.replace(/^v/, '').split('.').map(Number); for (let i = 0; i < 3; i++) {
|
|
4
7
|
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
5
8
|
if (d)
|
|
@@ -12,5 +15,58 @@ export async function npmVersion() { try {
|
|
|
12
15
|
catch {
|
|
13
16
|
return undefined;
|
|
14
17
|
} }
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
async function detectLinuxId() {
|
|
19
|
+
try {
|
|
20
|
+
const txt = await import('node:fs/promises').then(fs => fs.readFile('/etc/os-release', 'utf8'));
|
|
21
|
+
return (txt.match(/^ID=(.+)$/m)?.[1] || '').replace(/['"]/g, '').toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function detectOsFamily() {
|
|
28
|
+
const release = os.release().toLowerCase();
|
|
29
|
+
const isWsl = process.platform === 'linux' && (release.includes('microsoft') || release.includes('wsl'));
|
|
30
|
+
if (isWsl)
|
|
31
|
+
return 'wsl';
|
|
32
|
+
if (process.platform === 'darwin')
|
|
33
|
+
return 'macos';
|
|
34
|
+
if (process.platform === 'win32')
|
|
35
|
+
return 'windows';
|
|
36
|
+
if (process.platform === 'linux') {
|
|
37
|
+
const id = await detectLinuxId();
|
|
38
|
+
if (['ubuntu', 'debian', 'fedora', 'arch'].includes(id))
|
|
39
|
+
return id;
|
|
40
|
+
return 'linux';
|
|
41
|
+
}
|
|
42
|
+
return 'unknown';
|
|
43
|
+
}
|
|
44
|
+
async function firstCommand(commands) { for (const c of commands)
|
|
45
|
+
if (await commandExists(c))
|
|
46
|
+
return c; return undefined; }
|
|
47
|
+
export async function runtimeAudit() {
|
|
48
|
+
const osFamily = await detectOsFamily();
|
|
49
|
+
const npm = await npmVersion();
|
|
50
|
+
const pathEntries = (process.env.PATH || '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
51
|
+
const packageManager = await firstCommand(process.platform === 'win32' ? ['winget', 'choco', 'scoop'] : ['brew', 'apt-get', 'dnf', 'pacman', 'npm']);
|
|
52
|
+
return { os: osFamily, platform: process.platform, arch: process.arch, isWsl: osFamily === 'wsl', nodeVersion: process.versions.node, nodeOk: isNodeSupported(), npmVersion: npm, npmOk: Boolean(npm), pathEntries, packageManager, installCommand: nodeInstallCommand(osFamily, packageManager) };
|
|
53
|
+
}
|
|
54
|
+
export function nodeInstallCommand(osFamily, pm) {
|
|
55
|
+
if (osFamily === 'macos' && pm === 'brew')
|
|
56
|
+
return 'brew install node@22 && brew link --overwrite node@22';
|
|
57
|
+
if ((osFamily === 'ubuntu' || osFamily === 'debian' || osFamily === 'wsl') && pm === 'apt-get')
|
|
58
|
+
return 'curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs';
|
|
59
|
+
if (osFamily === 'fedora' && pm === 'dnf')
|
|
60
|
+
return 'sudo dnf install -y nodejs npm';
|
|
61
|
+
if (osFamily === 'arch' && pm === 'pacman')
|
|
62
|
+
return 'sudo pacman -S --needed nodejs npm';
|
|
63
|
+
if (osFamily === 'windows' && pm === 'winget')
|
|
64
|
+
return 'winget install OpenJS.NodeJS.LTS';
|
|
65
|
+
if (osFamily === 'windows' && pm === 'choco')
|
|
66
|
+
return 'choco install nodejs-lts -y';
|
|
67
|
+
if (osFamily === 'windows' && pm === 'scoop')
|
|
68
|
+
return 'scoop install nodejs-lts';
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
export function nodeUpgradeHint() { return ['Install Node.js LTS, then rerun: npx scalemax@latest', 'macOS Homebrew: brew install node@22 && brew link --overwrite node@22', 'Ubuntu/Debian/WSL: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs', 'Fedora: sudo dnf install -y nodejs npm', 'Arch: sudo pacman -S --needed nodejs npm', 'Windows PowerShell: winget install OpenJS.NodeJS.LTS'].join('\n'); }
|
|
72
|
+
export async function runtimeReport() { const a = await runtimeAudit(); return `OS ${a.os} ${a.arch} · Node ${a.nodeVersion} ${a.nodeOk ? '✓' : '✗'} (required >=${MIN_NODE}) · npm ${a.npmVersion || 'not found'} ${a.npmOk ? '✓' : '✗'} · PATH entries ${a.pathEntries.length}`; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSystemAudit(): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { confirm, isCancel } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { runtimeAudit, nodeUpgradeHint } from './runtime.js';
|
|
6
|
+
function check(label, ok, value) { console.log(`${ok ? chalk.green('✓') : chalk.red('✗')} ${label} ${value}`); }
|
|
7
|
+
async function runCommand(command) { const res = await execa(command, { shell: true, stdio: 'inherit', reject: false }); return res.exitCode === 0; }
|
|
8
|
+
export async function runSystemAudit() {
|
|
9
|
+
const spin = ora('Checking system').start();
|
|
10
|
+
const audit = await runtimeAudit();
|
|
11
|
+
spin.stop();
|
|
12
|
+
check('Operating system', true, `${audit.os}${audit.isWsl ? ' (WSL)' : ''}`);
|
|
13
|
+
check('Architecture', true, audit.arch);
|
|
14
|
+
check('Node.js', audit.nodeOk, `v${audit.nodeVersion}`);
|
|
15
|
+
check('npm', audit.npmOk, audit.npmVersion ? `v${audit.npmVersion}` : 'not found');
|
|
16
|
+
check('PATH', audit.pathEntries.length > 0, `${audit.pathEntries.length} entries`);
|
|
17
|
+
if (audit.nodeOk && audit.npmOk)
|
|
18
|
+
return;
|
|
19
|
+
console.log(chalk.yellow('\nScaleMax needs a supported Node.js/npm runtime before continuing.'));
|
|
20
|
+
if (audit.installCommand) {
|
|
21
|
+
console.log(`\nRecommended command:\n${chalk.cyan(audit.installCommand)}\n`);
|
|
22
|
+
const ans = await confirm({ message: 'Run this command now?', initialValue: false });
|
|
23
|
+
if (!isCancel(ans) && ans === true) {
|
|
24
|
+
const ok = await runCommand(audit.installCommand);
|
|
25
|
+
if (!ok)
|
|
26
|
+
throw new Error('Automatic Node/npm installation failed. Run the command above manually, then rerun npx scalemax.');
|
|
27
|
+
console.log(chalk.green('✓ Node/npm installation command completed. Re-run npx scalemax if this process does not resume automatically.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Node/npm setup is incomplete.\n${nodeUpgradeHint()}`);
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scalemax",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Universal ScaleMax installer for AI coding clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"scalemax": "dist/
|
|
7
|
+
"scalemax": "dist/bootstrap.cjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/",
|
|
@@ -51,10 +51,11 @@
|
|
|
51
51
|
"installer",
|
|
52
52
|
"codex",
|
|
53
53
|
"cursor",
|
|
54
|
-
"claude",
|
|
55
54
|
"opencode",
|
|
56
55
|
"continue",
|
|
57
56
|
"vscode",
|
|
57
|
+
"roo-code",
|
|
58
|
+
"cline",
|
|
58
59
|
"windsurf",
|
|
59
60
|
"openai-compatible"
|
|
60
61
|
],
|