scalemax 0.1.0
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/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/clients.d.ts +2 -0
- package/dist/clients.js +29 -0
- package/dist/commands/configure.d.ts +7 -0
- package/dist/commands/configure.js +78 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +25 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +4 -0
- package/dist/commands/models.d.ts +1 -0
- package/dist/commands/models.js +19 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +20 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +2 -0
- package/dist/configure/claude.d.ts +3 -0
- package/dist/configure/claude.js +14 -0
- package/dist/configure/codex.d.ts +16 -0
- package/dist/configure/codex.js +210 -0
- package/dist/configure/common.d.ts +4 -0
- package/dist/configure/common.js +39 -0
- package/dist/configure/continue.d.ts +3 -0
- package/dist/configure/continue.js +12 -0
- package/dist/configure/cursor.d.ts +3 -0
- package/dist/configure/cursor.js +13 -0
- package/dist/configure/hermes.d.ts +3 -0
- package/dist/configure/hermes.js +14 -0
- package/dist/configure/openclaw.d.ts +3 -0
- package/dist/configure/openclaw.js +14 -0
- package/dist/configure/opencode.d.ts +3 -0
- package/dist/configure/opencode.js +14 -0
- package/dist/configure/paths.d.ts +6 -0
- package/dist/configure/paths.js +35 -0
- package/dist/configure/templates.d.ts +3 -0
- package/dist/configure/templates.js +31 -0
- package/dist/configure/vscode.d.ts +3 -0
- package/dist/configure/vscode.js +13 -0
- package/dist/configure/windsurf.d.ts +3 -0
- package/dist/configure/windsurf.js +13 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/detect/claude.d.ts +2 -0
- package/dist/detect/claude.js +2 -0
- package/dist/detect/codex.d.ts +2 -0
- package/dist/detect/codex.js +2 -0
- package/dist/detect/common.d.ts +2 -0
- package/dist/detect/common.js +19 -0
- package/dist/detect/continue.d.ts +2 -0
- package/dist/detect/continue.js +2 -0
- package/dist/detect/cursor.d.ts +2 -0
- package/dist/detect/cursor.js +2 -0
- package/dist/detect/hermes.d.ts +2 -0
- package/dist/detect/hermes.js +2 -0
- package/dist/detect/openclaw.d.ts +2 -0
- package/dist/detect/openclaw.js +2 -0
- package/dist/detect/opencode.d.ts +2 -0
- package/dist/detect/opencode.js +2 -0
- package/dist/detect/vscode.d.ts +2 -0
- package/dist/detect/vscode.js +2 -0
- package/dist/detect/windsurf.d.ts +2 -0
- package/dist/detect/windsurf.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +90 -0
- package/dist/types/client.d.ts +32 -0
- package/dist/types/client.js +1 -0
- package/dist/utils/api.d.ts +15 -0
- package/dist/utils/api.js +37 -0
- package/dist/utils/exec.d.ts +1 -0
- package/dist/utils/exec.js +12 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.js +41 -0
- package/dist/utils/installerConfig.d.ts +5 -0
- package/dist/utils/installerConfig.js +84 -0
- package/dist/utils/keyStore.d.ts +3 -0
- package/dist/utils/keyStore.js +17 -0
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.js +15 -0
- package/package.json +78 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { outro } from '@clack/prompts';
|
|
7
|
+
import { loginCommand } from './commands/login.js';
|
|
8
|
+
import { configureCommand } from './commands/configure.js';
|
|
9
|
+
import { logoutCommand } from './commands/logout.js';
|
|
10
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
11
|
+
import { modelsCommand } from './commands/models.js';
|
|
12
|
+
import { updateCommand } from './commands/update.js';
|
|
13
|
+
import { removeCommand } from './commands/remove.js';
|
|
14
|
+
import { ensureInstallerApiKey } from './utils/installerConfig.js';
|
|
15
|
+
function packageVersion() {
|
|
16
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const candidates = [path.join(here, '..', 'package.json'), path.join(here, 'package.json')];
|
|
18
|
+
for (const candidate of candidates) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(candidate, 'utf8')).version || '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
}
|
|
24
|
+
return '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
function welcome() {
|
|
27
|
+
console.log(chalk.cyan('╔══════════════════════════════╗'));
|
|
28
|
+
console.log(chalk.cyan('║ Welcome to ScaleMax ║'));
|
|
29
|
+
console.log(chalk.cyan('╚══════════════════════════════╝'));
|
|
30
|
+
console.log('\nThe fastest way to connect every AI coding client.\n');
|
|
31
|
+
}
|
|
32
|
+
async function main() {
|
|
33
|
+
const [, , command] = process.argv;
|
|
34
|
+
try {
|
|
35
|
+
if (!command) {
|
|
36
|
+
welcome();
|
|
37
|
+
const apiKey = await ensureInstallerApiKey(process.env.HOME || process.env.USERPROFILE || process.cwd());
|
|
38
|
+
if (!apiKey)
|
|
39
|
+
return;
|
|
40
|
+
await configureCommand();
|
|
41
|
+
outro(chalk.green('ScaleMax is ready.'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
switch (command) {
|
|
45
|
+
case 'login':
|
|
46
|
+
await loginCommand();
|
|
47
|
+
break;
|
|
48
|
+
case 'logout':
|
|
49
|
+
await logoutCommand();
|
|
50
|
+
break;
|
|
51
|
+
case 'doctor':
|
|
52
|
+
await doctorCommand();
|
|
53
|
+
break;
|
|
54
|
+
case 'models':
|
|
55
|
+
await modelsCommand();
|
|
56
|
+
break;
|
|
57
|
+
case 'update':
|
|
58
|
+
await updateCommand();
|
|
59
|
+
break;
|
|
60
|
+
case 'remove':
|
|
61
|
+
await removeCommand();
|
|
62
|
+
break;
|
|
63
|
+
case '--version':
|
|
64
|
+
case '-v':
|
|
65
|
+
console.log(packageVersion());
|
|
66
|
+
break;
|
|
67
|
+
case '--help':
|
|
68
|
+
case '-h':
|
|
69
|
+
console.log(`ScaleMax CLI
|
|
70
|
+
|
|
71
|
+
Commands:
|
|
72
|
+
scalemax Interactive installer
|
|
73
|
+
scalemax login Store or replace API key
|
|
74
|
+
scalemax logout Remove API key
|
|
75
|
+
scalemax doctor Run diagnostics
|
|
76
|
+
scalemax models List available models
|
|
77
|
+
scalemax update Reconfigure detected clients
|
|
78
|
+
scalemax remove Remove ScaleMax-generated configs
|
|
79
|
+
scalemax --version Show installed version
|
|
80
|
+
`);
|
|
81
|
+
break;
|
|
82
|
+
default: throw new Error(`Unknown command: ${command}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
void main();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ClientId = 'codex' | 'cursor' | 'claude' | 'opencode' | 'openclaw' | 'hermes' | 'continue' | 'vscode' | 'windsurf';
|
|
2
|
+
export interface ClientDefinition {
|
|
3
|
+
id: ClientId;
|
|
4
|
+
name: string;
|
|
5
|
+
detect: (ctx: DetectContext) => Promise<DetectionResult>;
|
|
6
|
+
configure: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
7
|
+
remove: (ctx: ConfigureContext) => Promise<ConfigureResult>;
|
|
8
|
+
}
|
|
9
|
+
export interface DetectContext {
|
|
10
|
+
home: string;
|
|
11
|
+
platform: NodeJS.Platform;
|
|
12
|
+
env: NodeJS.ProcessEnv;
|
|
13
|
+
}
|
|
14
|
+
export interface DetectionResult {
|
|
15
|
+
id: ClientId;
|
|
16
|
+
name: string;
|
|
17
|
+
installed: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
paths: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface ConfigureContext extends DetectContext {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
baseUrl: string;
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ConfigureResult {
|
|
27
|
+
id: ClientId;
|
|
28
|
+
name: string;
|
|
29
|
+
changed: boolean;
|
|
30
|
+
path?: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ModelInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
object?: string;
|
|
4
|
+
owned_by?: string;
|
|
5
|
+
display_name?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateApiKey(apiKey: string): Promise<{
|
|
8
|
+
ok: boolean;
|
|
9
|
+
models: ModelInfo[];
|
|
10
|
+
error?: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function testStreaming(apiKey: string, model?: string): Promise<{
|
|
13
|
+
ok: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
2
|
+
async function request(path, apiKey, init = {}) {
|
|
3
|
+
const headers = new Headers(init.headers);
|
|
4
|
+
if (apiKey)
|
|
5
|
+
headers.set('Authorization', `Bearer ${apiKey}`);
|
|
6
|
+
if (!headers.has('Content-Type') && init.body)
|
|
7
|
+
headers.set('Content-Type', 'application/json');
|
|
8
|
+
return fetch(`${SCALEMAX_BASE_URL}${path}`, { ...init, headers });
|
|
9
|
+
}
|
|
10
|
+
export async function validateApiKey(apiKey) {
|
|
11
|
+
try {
|
|
12
|
+
const res = await request('/models', apiKey);
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
return { ok: false, models: [], error: `HTTP ${res.status}` };
|
|
15
|
+
const data = await res.json();
|
|
16
|
+
return { ok: Array.isArray(data.data), models: data.data || [] };
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
return { ok: false, models: [], error: err instanceof Error ? err.message : String(err) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function testStreaming(apiKey, model) {
|
|
23
|
+
const selected = model || 'gpt-5.5';
|
|
24
|
+
try {
|
|
25
|
+
const res = await request('/chat/completions', apiKey, { method: 'POST', body: JSON.stringify({ model: selected, stream: true, max_tokens: 8, messages: [{ role: 'user', content: 'Say OK' }] }) });
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
return { ok: false, error: `HTTP ${res.status}` };
|
|
28
|
+
const ct = res.headers.get('content-type') || '';
|
|
29
|
+
if (!ct.includes('text/event-stream'))
|
|
30
|
+
return { ok: false, error: `Expected event stream, got ${ct}` };
|
|
31
|
+
await res.body?.cancel().catch(() => undefined);
|
|
32
|
+
return { ok: true };
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function commandExists(command: string): Promise<boolean>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
export async function commandExists(command) {
|
|
3
|
+
const checker = process.platform === 'win32' ? 'where' : 'command';
|
|
4
|
+
const args = process.platform === 'win32' ? [command] : ['-v', command];
|
|
5
|
+
try {
|
|
6
|
+
await execa(checker, args, { shell: process.platform !== 'win32' });
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function exists(p: string): Promise<boolean>;
|
|
2
|
+
export declare function atomicWrite(file: string, content: string, mode?: number): Promise<void>;
|
|
3
|
+
export declare function backupNameFor(file: string): string;
|
|
4
|
+
export declare function backupFile(file: string, home: string): Promise<string | undefined>;
|
|
5
|
+
export declare function listBackups(file: string, home: string): Promise<string[]>;
|
|
6
|
+
export declare function readJson<T>(file: string, fallback: T): Promise<T>;
|
|
7
|
+
export declare function writeJsonAtomic(file: string, value: unknown, mode?: number): Promise<void>;
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { backupDir, timestamp } from './paths.js';
|
|
4
|
+
export async function exists(p) { return fs.pathExists(p); }
|
|
5
|
+
export async function atomicWrite(file, content, mode = 0o600) {
|
|
6
|
+
await fs.ensureDir(path.dirname(file));
|
|
7
|
+
const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
|
|
8
|
+
await fs.writeFile(tmp, content, { mode });
|
|
9
|
+
await fs.rename(tmp, file);
|
|
10
|
+
await fs.chmod(file, mode).catch(() => undefined);
|
|
11
|
+
}
|
|
12
|
+
export function backupNameFor(file) {
|
|
13
|
+
return file.replace(/^([A-Za-z]:)?[\/]/, '').replace(/[\/]/g, '__');
|
|
14
|
+
}
|
|
15
|
+
export async function backupFile(file, home) {
|
|
16
|
+
if (!(await fs.pathExists(file)))
|
|
17
|
+
return undefined;
|
|
18
|
+
const dest = path.join(backupDir(home), `${timestamp()}__${backupNameFor(file)}`);
|
|
19
|
+
await fs.ensureDir(path.dirname(dest));
|
|
20
|
+
await fs.copy(file, dest, { overwrite: false, errorOnExist: true });
|
|
21
|
+
return dest;
|
|
22
|
+
}
|
|
23
|
+
export async function listBackups(file, home) {
|
|
24
|
+
const dir = backupDir(home);
|
|
25
|
+
if (!(await fs.pathExists(dir)))
|
|
26
|
+
return [];
|
|
27
|
+
const suffix = `__${backupNameFor(file)}`;
|
|
28
|
+
const entries = await fs.readdir(dir);
|
|
29
|
+
return entries.filter(e => e.endsWith(suffix)).sort().reverse().map(e => path.join(dir, e));
|
|
30
|
+
}
|
|
31
|
+
export async function readJson(file, fallback) {
|
|
32
|
+
try {
|
|
33
|
+
return await fs.readJson(file);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function writeJsonAtomic(file, value, mode = 0o600) {
|
|
40
|
+
await atomicWrite(file, JSON.stringify(value, null, 2) + '\n', mode);
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type ExistingConfigChoice = 'keep' | 'replace' | 'remove' | 'exit';
|
|
2
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
3
|
+
export declare function promptForValidatedKey(home: string, message?: string): Promise<string | undefined>;
|
|
4
|
+
export declare function applyExistingConfigChoice(choice: ExistingConfigChoice, home: string, existing: string, getReplacement?: () => Promise<string | undefined>): Promise<string | undefined>;
|
|
5
|
+
export declare function ensureInstallerApiKey(home?: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { intro, outro, password, select, confirm, isCancel, cancel } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { SCALEMAX_BASE_URL } from '../constants.js';
|
|
5
|
+
import { validateApiKey } from './api.js';
|
|
6
|
+
import { loadApiKey, removeApiKey, saveApiKey } from './keyStore.js';
|
|
7
|
+
import { homeDir } from './paths.js';
|
|
8
|
+
export function maskApiKey(apiKey) {
|
|
9
|
+
const trimmed = apiKey.trim();
|
|
10
|
+
if (trimmed.length <= 4)
|
|
11
|
+
return '••••';
|
|
12
|
+
return `${'•'.repeat(16)}${trimmed.slice(-4)}`;
|
|
13
|
+
}
|
|
14
|
+
export async function promptForValidatedKey(home, message = 'Enter your ScaleMax API key:') {
|
|
15
|
+
const key = await password({ message, mask: '•', validate(value) { return value.trim().length < 12 ? 'Enter a valid ScaleMax API key.' : undefined; } });
|
|
16
|
+
if (isCancel(key)) {
|
|
17
|
+
cancel('Installer cancelled.');
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const candidate = String(key).trim();
|
|
21
|
+
const spinner = ora('Validating API key').start();
|
|
22
|
+
const result = await validateApiKey(candidate);
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
spinner.fail('Invalid API key');
|
|
25
|
+
throw new Error(result.error || 'Invalid API key');
|
|
26
|
+
}
|
|
27
|
+
await saveApiKey(candidate, SCALEMAX_BASE_URL, home);
|
|
28
|
+
spinner.succeed('API key verified and saved');
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
export async function applyExistingConfigChoice(choice, home, existing, getReplacement = () => promptForValidatedKey(home, 'Enter your new ScaleMax API key:')) {
|
|
32
|
+
if (choice === 'keep')
|
|
33
|
+
return existing;
|
|
34
|
+
if (choice === 'replace')
|
|
35
|
+
return getReplacement();
|
|
36
|
+
if (choice === 'remove') {
|
|
37
|
+
await removeApiKey(home);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
export async function ensureInstallerApiKey(home = homeDir()) {
|
|
43
|
+
intro(chalk.cyan('ScaleMax Installer'));
|
|
44
|
+
const existing = await loadApiKey(home);
|
|
45
|
+
if (!existing) {
|
|
46
|
+
const key = await promptForValidatedKey(home, 'Enter your ScaleMax API key:');
|
|
47
|
+
if (key)
|
|
48
|
+
outro(chalk.green('ScaleMax configuration saved.'));
|
|
49
|
+
return key;
|
|
50
|
+
}
|
|
51
|
+
console.log(chalk.green('✓ Existing ScaleMax configuration detected.'));
|
|
52
|
+
console.log('\nAPI key:');
|
|
53
|
+
console.log(maskApiKey(existing));
|
|
54
|
+
const choice = await select({
|
|
55
|
+
message: 'What would you like to do?',
|
|
56
|
+
options: [
|
|
57
|
+
{ value: 'keep', label: '1. Keep existing key' },
|
|
58
|
+
{ value: 'replace', label: '2. Replace with a new key' },
|
|
59
|
+
{ value: 'remove', label: '3. Remove configuration' },
|
|
60
|
+
{ value: 'exit', label: '4. Exit' }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
if (isCancel(choice)) {
|
|
64
|
+
cancel('Installer cancelled.');
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (choice === 'remove') {
|
|
68
|
+
const ok = await confirm({ message: 'Remove stored ScaleMax configuration and API key?', initialValue: false });
|
|
69
|
+
if (isCancel(ok) || ok === false) {
|
|
70
|
+
console.log(chalk.yellow('Removal cancelled.'));
|
|
71
|
+
return existing;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const result = await applyExistingConfigChoice(choice, home, existing);
|
|
75
|
+
if (choice === 'keep')
|
|
76
|
+
console.log(chalk.green('✓ Keeping existing ScaleMax key.'));
|
|
77
|
+
if (choice === 'replace' && result)
|
|
78
|
+
outro(chalk.green('ScaleMax API key replaced.'));
|
|
79
|
+
if (choice === 'remove')
|
|
80
|
+
outro(chalk.green('ScaleMax configuration removed.'));
|
|
81
|
+
if (choice === 'exit')
|
|
82
|
+
console.log(chalk.yellow('Exiting without changes.'));
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { KEY_ENV } from '../constants.js';
|
|
3
|
+
import { scaleMaxConfigPath, scaleMaxDir } from './paths.js';
|
|
4
|
+
import { readJson, writeJsonAtomic } from './fs.js';
|
|
5
|
+
export async function saveApiKey(apiKey, baseUrl, home) {
|
|
6
|
+
await fs.ensureDir(scaleMaxDir(home), 0o700);
|
|
7
|
+
await writeJsonAtomic(scaleMaxConfigPath(home), { apiKey, baseUrl, updatedAt: new Date().toISOString() }, 0o600);
|
|
8
|
+
}
|
|
9
|
+
export async function loadApiKey(home) {
|
|
10
|
+
if (process.env[KEY_ENV])
|
|
11
|
+
return process.env[KEY_ENV];
|
|
12
|
+
const cfg = await readJson(scaleMaxConfigPath(home), {});
|
|
13
|
+
return cfg.apiKey;
|
|
14
|
+
}
|
|
15
|
+
export async function removeApiKey(home) {
|
|
16
|
+
await fs.remove(scaleMaxConfigPath(home));
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function homeDir(env?: NodeJS.ProcessEnv): string;
|
|
2
|
+
export declare function configRoot(home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function scaleMaxDir(home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
export declare function scaleMaxConfigPath(home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function backupDir(home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string;
|
|
6
|
+
export declare function timestamp(): string;
|
|
7
|
+
export declare function expandHome(p: string, home?: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function homeDir(env = process.env) { return env.HOME || env.USERPROFILE || os.homedir(); }
|
|
4
|
+
export function configRoot(home = homeDir(), platform = process.platform, env = process.env) {
|
|
5
|
+
if (platform === 'win32')
|
|
6
|
+
return env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
7
|
+
if (platform === 'darwin')
|
|
8
|
+
return path.join(home, 'Library', 'Application Support');
|
|
9
|
+
return env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
10
|
+
}
|
|
11
|
+
export function scaleMaxDir(home = homeDir(), platform = process.platform, env = process.env) { return path.join(configRoot(home, platform, env), 'scalemax'); }
|
|
12
|
+
export function scaleMaxConfigPath(home = homeDir(), platform = process.platform, env = process.env) { return path.join(scaleMaxDir(home, platform, env), 'config.json'); }
|
|
13
|
+
export function backupDir(home = homeDir(), platform = process.platform, env = process.env) { return path.join(scaleMaxDir(home, platform, env), 'backups'); }
|
|
14
|
+
export function timestamp() { return new Date().toISOString().replace(/[:.]/g, '-'); }
|
|
15
|
+
export function expandHome(p, home = homeDir()) { return p.startsWith('~/') ? path.join(home, p.slice(2)) : p; }
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scalemax",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal ScaleMax installer for AI coding clients",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"scalemax": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"clean": "node scripts/clean.mjs",
|
|
16
|
+
"build": "tsc -p tsconfig.json && node scripts/postbuild.mjs",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"lint": "node scripts/lint.mjs",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run clean && npm run typecheck && npm test && npm run lint && npm run build && npm run validate:package",
|
|
21
|
+
"validate:package": "node scripts/validate-package.mjs",
|
|
22
|
+
"pack:check": "npm run clean && npm run build && npm run validate:package && npm pack --dry-run",
|
|
23
|
+
"publish:dry-run": "npm publish --dry-run",
|
|
24
|
+
"version:patch": "npm version patch --no-git-tag-version",
|
|
25
|
+
"version:minor": "npm version minor --no-git-tag-version",
|
|
26
|
+
"version:major": "npm version major --no-git-tag-version",
|
|
27
|
+
"dev": "tsx src/index.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^0.8.2",
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"execa": "^9.5.1",
|
|
33
|
+
"fs-extra": "^11.2.0",
|
|
34
|
+
"ora": "^8.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/fs-extra": "^11.0.4",
|
|
38
|
+
"@types/node": "^22.10.2",
|
|
39
|
+
"tsx": "^4.19.2",
|
|
40
|
+
"typescript": "^5.7.2",
|
|
41
|
+
"vitest": "^2.1.8"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.17"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"scalemax",
|
|
48
|
+
"ai",
|
|
49
|
+
"cli",
|
|
50
|
+
"npx",
|
|
51
|
+
"installer",
|
|
52
|
+
"codex",
|
|
53
|
+
"cursor",
|
|
54
|
+
"claude",
|
|
55
|
+
"opencode",
|
|
56
|
+
"continue",
|
|
57
|
+
"vscode",
|
|
58
|
+
"windsurf",
|
|
59
|
+
"openai-compatible"
|
|
60
|
+
],
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"author": "ScaleMax <support@scalemax.pro>",
|
|
63
|
+
"homepage": "https://scalemax.pro",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/scalemax-pro/scalemax.git",
|
|
67
|
+
"directory": "packages/cli"
|
|
68
|
+
},
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://scalemax.pro",
|
|
71
|
+
"email": "support@scalemax.pro"
|
|
72
|
+
},
|
|
73
|
+
"os": [
|
|
74
|
+
"darwin",
|
|
75
|
+
"linux",
|
|
76
|
+
"win32"
|
|
77
|
+
]
|
|
78
|
+
}
|