bloby-bot 0.66.1 → 0.68.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/README.md +11 -5
- package/bin/cli.js +1944 -1463
- package/package.json +4 -5
- package/scripts/install.sh +1 -0
- package/supervisor/backend.ts +11 -0
- package/supervisor/channels/manager.ts +2 -0
- package/supervisor/channels/whatsapp-auth.ts +216 -0
- package/supervisor/channels/whatsapp.ts +106 -11
- package/supervisor/index.ts +91 -2
- package/tsconfig.json +1 -1
- package/cli/commands/daemon.ts +0 -31
- package/cli/commands/init.ts +0 -40
- package/cli/commands/start.ts +0 -91
- package/cli/commands/tunnel.ts +0 -175
- package/cli/commands/update.ts +0 -174
- package/cli/core/base-adapter.ts +0 -99
- package/cli/core/cloudflared.ts +0 -71
- package/cli/core/config.ts +0 -58
- package/cli/core/os-detector.ts +0 -31
- package/cli/core/server.ts +0 -87
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -110
- package/cli/platforms/index.ts +0 -20
- package/cli/platforms/linux.ts +0 -116
- package/cli/platforms/win32.ts +0 -20
- package/cli/utils/ui.ts +0 -38
package/cli/core/config.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import url from 'node:url';
|
|
5
|
-
|
|
6
|
-
import type { BotConfig } from '../../shared/config.js';
|
|
7
|
-
|
|
8
|
-
export const PLATFORM = os.platform();
|
|
9
|
-
|
|
10
|
-
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
11
|
-
export const REPO_ROOT = path.resolve(__dirname, '../..');
|
|
12
|
-
|
|
13
|
-
export const DATA_DIR = path.join(
|
|
14
|
-
process.env.BLOBY_REAL_HOME || os.homedir(),
|
|
15
|
-
'.bloby',
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
export const IS_DEV = fs.existsSync(path.join(REPO_ROOT, '.git'));
|
|
19
|
-
export const ROOT = IS_DEV ? REPO_ROOT : DATA_DIR;
|
|
20
|
-
export const CONFIG_PATH = path.join(DATA_DIR, 'config.json');
|
|
21
|
-
export const BIN_DIR = path.join(DATA_DIR, 'bin');
|
|
22
|
-
export const CF_PATH = path.join(
|
|
23
|
-
BIN_DIR,
|
|
24
|
-
'cloudflared' + (PLATFORM === 'win32' ? '.exe' : ''),
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
export const SERVICE_NAME = 'bloby';
|
|
28
|
-
|
|
29
|
-
export const pkg = JSON.parse(
|
|
30
|
-
fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'),
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
export function loadConfig(): BotConfig {
|
|
34
|
-
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function createConfig() {
|
|
38
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
39
|
-
if (fs.existsSync(CONFIG_PATH)) return;
|
|
40
|
-
|
|
41
|
-
const config: Partial<BotConfig> = {
|
|
42
|
-
port: 7400,
|
|
43
|
-
username: '',
|
|
44
|
-
ai: { provider: '', model: '', apiKey: '' },
|
|
45
|
-
tunnel: { mode: 'quick' },
|
|
46
|
-
relay: { token: '', tier: '', url: '' },
|
|
47
|
-
};
|
|
48
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function safeLoadConfig(fallback: Partial<BotConfig> = {}): BotConfig {
|
|
52
|
-
if (!fs.existsSync(CONFIG_PATH)) return fallback as BotConfig;
|
|
53
|
-
try {
|
|
54
|
-
return loadConfig();
|
|
55
|
-
} catch {
|
|
56
|
-
return fallback as BotConfig;
|
|
57
|
-
}
|
|
58
|
-
}
|
package/cli/core/os-detector.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
|
|
3
|
-
export function getPlatform(): string {
|
|
4
|
-
return os.platform();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isWindows(): boolean {
|
|
8
|
-
return getPlatform() === 'win32';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function isUnixLike(): boolean {
|
|
12
|
-
return getPlatform() !== 'win32';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getRawArchitecture(): string {
|
|
16
|
-
return (process.env.PROCESSOR_ARCHITECTURE || os.arch()).toLowerCase();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalizes the architecture specifically for cloudflared download links.
|
|
21
|
-
*/
|
|
22
|
-
export function getNormalizedArchitecture(): string {
|
|
23
|
-
const rawArch = getRawArchitecture();
|
|
24
|
-
|
|
25
|
-
if (['arm64', 'aarch64'].includes(rawArch)) return 'arm64';
|
|
26
|
-
if (['arm', 'armv7l'].includes(rawArch)) return 'arm';
|
|
27
|
-
if (rawArch === 'x64' || rawArch === 'amd64' || rawArch === 'x86_64') return 'amd64';
|
|
28
|
-
if (rawArch === '386' || rawArch === 'ia32' || rawArch === 'x86') return '386';
|
|
29
|
-
|
|
30
|
-
return 'amd64';
|
|
31
|
-
}
|
package/cli/core/server.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import { ROOT, CONFIG_PATH } from './config.js';
|
|
5
|
-
import type { BotConfig } from '../../shared/config.js';
|
|
6
|
-
|
|
7
|
-
export interface BootResult {
|
|
8
|
-
child: ChildProcess;
|
|
9
|
-
tunnelUrl: string;
|
|
10
|
-
relayUrl: string | null;
|
|
11
|
-
tunnelFailed: boolean;
|
|
12
|
-
viteWarm: Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function bootServer({
|
|
16
|
-
onTunnelUp,
|
|
17
|
-
onReady,
|
|
18
|
-
}: {
|
|
19
|
-
onTunnelUp?: (url?: string) => void;
|
|
20
|
-
onReady?: () => void;
|
|
21
|
-
} = {}): Promise<BootResult> {
|
|
22
|
-
return new Promise(resolve => {
|
|
23
|
-
const child = spawn(
|
|
24
|
-
process.execPath,
|
|
25
|
-
['--import', 'tsx/esm', path.join(ROOT, 'supervisor/index.ts')],
|
|
26
|
-
{ cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
let tunnelUrl: string | undefined;
|
|
30
|
-
let relayUrl: string | undefined;
|
|
31
|
-
let resolved = false;
|
|
32
|
-
let tunnelFired = false;
|
|
33
|
-
let tunnelFailed = false;
|
|
34
|
-
|
|
35
|
-
let viteWarmResolve: (value: void | PromiseLike<void>) => void;
|
|
36
|
-
const viteWarm = new Promise<void>(r => { viteWarmResolve = r as any; });
|
|
37
|
-
|
|
38
|
-
const doResolve = () => {
|
|
39
|
-
if (resolved) return;
|
|
40
|
-
resolved = true;
|
|
41
|
-
|
|
42
|
-
if (!tunnelFired && onTunnelUp) onTunnelUp();
|
|
43
|
-
if (onReady) onReady();
|
|
44
|
-
|
|
45
|
-
let config: Partial<BotConfig> = {};
|
|
46
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
47
|
-
config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
resolve({
|
|
51
|
-
child,
|
|
52
|
-
tunnelUrl: tunnelUrl || `http://localhost:${config.port || 7400}`,
|
|
53
|
-
relayUrl: relayUrl || config.relay?.url || null,
|
|
54
|
-
tunnelFailed,
|
|
55
|
-
viteWarm,
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const handleData = (data: any) => {
|
|
60
|
-
const text = data.toString();
|
|
61
|
-
|
|
62
|
-
const tunnelMatch = text.match(/__TUNNEL_URL__=(\S+)/);
|
|
63
|
-
if (tunnelMatch) {
|
|
64
|
-
tunnelUrl = tunnelMatch[1];
|
|
65
|
-
if (!tunnelFired && onTunnelUp) {
|
|
66
|
-
tunnelFired = true;
|
|
67
|
-
onTunnelUp(tunnelUrl);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const relayMatch = text.match(/__RELAY_URL__=(\S+)/);
|
|
72
|
-
if (relayMatch) relayUrl = relayMatch[1];
|
|
73
|
-
|
|
74
|
-
if (text.includes('__VITE_WARM__')) viteWarmResolve();
|
|
75
|
-
if (text.includes('__READY__')) { doResolve(); return; }
|
|
76
|
-
if (text.includes('__TUNNEL_FAILED__')) { tunnelFailed = true; doResolve(); }
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
setTimeout(doResolve, 45_000);
|
|
80
|
-
|
|
81
|
-
child.stdout?.on('data', handleData);
|
|
82
|
-
child.stderr?.on('data', handleData);
|
|
83
|
-
|
|
84
|
-
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
85
|
-
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
86
|
-
});
|
|
87
|
-
}
|
package/cli/core/types.ts
DELETED
package/cli/index.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
|
|
5
|
-
import { getAdapter } from './platforms/index.js';
|
|
6
|
-
import { DATA_DIR, CONFIG_PATH, pkg } from './core/config.js';
|
|
7
|
-
import { registerDaemonCommand } from './commands/daemon.js';
|
|
8
|
-
import { registerStartCommand } from './commands/start.js';
|
|
9
|
-
import { registerTunnelCommand } from './commands/tunnel.js';
|
|
10
|
-
import { registerUpdateCommand } from './commands/update.js';
|
|
11
|
-
import { registerInitCommand } from './commands/init.js';
|
|
12
|
-
|
|
13
|
-
const program = new Command();
|
|
14
|
-
|
|
15
|
-
program
|
|
16
|
-
.name('bloby')
|
|
17
|
-
.description('Bloby Local AI and Tunneling Supervisor')
|
|
18
|
-
.version(pkg.version);
|
|
19
|
-
|
|
20
|
-
registerStartCommand(program);
|
|
21
|
-
registerDaemonCommand(program);
|
|
22
|
-
registerTunnelCommand(program);
|
|
23
|
-
registerUpdateCommand(program);
|
|
24
|
-
registerInitCommand(program);
|
|
25
|
-
|
|
26
|
-
// Aliases for convenience matching the old CLI
|
|
27
|
-
program
|
|
28
|
-
.command('stop')
|
|
29
|
-
.description('Stop the Bloby daemon')
|
|
30
|
-
.action(() => {
|
|
31
|
-
getAdapter().handleDaemonAction('stop', {
|
|
32
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
33
|
-
home: process.env.BLOBY_REAL_HOME || os.homedir(),
|
|
34
|
-
nodePath: process.env.BLOBY_NODE_PATH || process.execPath,
|
|
35
|
-
dataDir: DATA_DIR,
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
program
|
|
40
|
-
.command('status')
|
|
41
|
-
.description('Check the Bloby daemon status')
|
|
42
|
-
.action(() => {
|
|
43
|
-
getAdapter().handleDaemonAction('status', {
|
|
44
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
45
|
-
home: process.env.BLOBY_REAL_HOME || os.homedir(),
|
|
46
|
-
nodePath: process.env.BLOBY_NODE_PATH || process.execPath,
|
|
47
|
-
dataDir: DATA_DIR,
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
program
|
|
52
|
-
.command('logs')
|
|
53
|
-
.description('View the Bloby daemon logs')
|
|
54
|
-
.action(() => {
|
|
55
|
-
getAdapter().handleDaemonAction('logs', {
|
|
56
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
57
|
-
home: process.env.BLOBY_REAL_HOME || os.homedir(),
|
|
58
|
-
nodePath: process.env.BLOBY_NODE_PATH || process.execPath,
|
|
59
|
-
dataDir: DATA_DIR,
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Default: if no command given, start or init
|
|
64
|
-
if (process.argv.length === 2) {
|
|
65
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
66
|
-
process.argv.push('start');
|
|
67
|
-
} else {
|
|
68
|
-
process.argv.push('init');
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
program.parse(process.argv);
|
package/cli/platforms/darwin.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
|
|
6
|
-
import { BaseAdapter } from '../core/base-adapter.js';
|
|
7
|
-
import type { DaemonConfig } from '../core/types.js';
|
|
8
|
-
|
|
9
|
-
const LAUNCHD_LABEL = 'com.bloby.bot';
|
|
10
|
-
const LAUNCHD_PLIST_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);
|
|
11
|
-
const LAUNCHD_LOG_DIR = path.join(os.homedir(), 'Library', 'Logs', 'bloby');
|
|
12
|
-
|
|
13
|
-
function generateLaunchdPlist({ nodePath, dataDir }: DaemonConfig) {
|
|
14
|
-
const nodeBinDir = path.dirname(nodePath);
|
|
15
|
-
fs.mkdirSync(LAUNCHD_LOG_DIR, { recursive: true });
|
|
16
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
17
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
18
|
-
<plist version="1.0">
|
|
19
|
-
<dict>
|
|
20
|
-
<key>Label</key>
|
|
21
|
-
<string>${LAUNCHD_LABEL}</string>
|
|
22
|
-
<key>ProgramArguments</key>
|
|
23
|
-
<array>
|
|
24
|
-
<string>${nodePath}</string>
|
|
25
|
-
<string>--import</string>
|
|
26
|
-
<string>tsx/esm</string>
|
|
27
|
-
<string>${dataDir}/supervisor/index.ts</string>
|
|
28
|
-
</array>
|
|
29
|
-
<key>WorkingDirectory</key>
|
|
30
|
-
<string>${dataDir}</string>
|
|
31
|
-
<key>EnvironmentVariables</key>
|
|
32
|
-
<dict>
|
|
33
|
-
<key>HOME</key>
|
|
34
|
-
<string>${os.homedir()}</string>
|
|
35
|
-
<key>NODE_ENV</key>
|
|
36
|
-
<string>development</string>
|
|
37
|
-
<key>NODE_PATH</key>
|
|
38
|
-
<string>${dataDir}/node_modules</string>
|
|
39
|
-
<key>PATH</key>
|
|
40
|
-
<string>${nodeBinDir}:${dataDir}/node_modules/.bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
41
|
-
</dict>
|
|
42
|
-
<key>RunAtLoad</key>
|
|
43
|
-
<true/>
|
|
44
|
-
<key>KeepAlive</key>
|
|
45
|
-
<dict>
|
|
46
|
-
<key>SuccessfulExit</key>
|
|
47
|
-
<false/>
|
|
48
|
-
</dict>
|
|
49
|
-
<key>ThrottleInterval</key>
|
|
50
|
-
<integer>5</integer>
|
|
51
|
-
<key>StandardOutPath</key>
|
|
52
|
-
<string>${LAUNCHD_LOG_DIR}/bloby.log</string>
|
|
53
|
-
<key>StandardErrorPath</key>
|
|
54
|
-
<string>${LAUNCHD_LOG_DIR}/bloby.error.log</string>
|
|
55
|
-
</dict>
|
|
56
|
-
</plist>`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class DarwinAdapter extends BaseAdapter {
|
|
60
|
-
get hasDaemonSupport() { return true; }
|
|
61
|
-
|
|
62
|
-
get isInstalled() { return fs.existsSync(LAUNCHD_PLIST_PATH); }
|
|
63
|
-
|
|
64
|
-
get isActive() {
|
|
65
|
-
try {
|
|
66
|
-
const out = execSync(`launchctl list ${LAUNCHD_LABEL} 2>/dev/null`, { encoding: 'utf-8' });
|
|
67
|
-
const pidLine = out.split('\n').find(l => l.includes('PID'));
|
|
68
|
-
if (pidLine) {
|
|
69
|
-
const pid = pidLine.split('=')[1]?.trim();
|
|
70
|
-
return Boolean(pid && pid !== '0' && pid !== '-');
|
|
71
|
-
}
|
|
72
|
-
return !out.includes('"PID" = 0;');
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
protected installService(config: DaemonConfig): void {
|
|
79
|
-
const plist = generateLaunchdPlist(config);
|
|
80
|
-
fs.mkdirSync(path.dirname(LAUNCHD_PLIST_PATH), { recursive: true });
|
|
81
|
-
fs.writeFileSync(LAUNCHD_PLIST_PATH, plist);
|
|
82
|
-
execSync(`launchctl load "${LAUNCHD_PLIST_PATH}"`, { stdio: 'inherit' });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
protected startService(): void {
|
|
86
|
-
execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}" 2>/dev/null; launchctl load "${LAUNCHD_PLIST_PATH}"`, { stdio: 'inherit' });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
protected stopService(): void {
|
|
90
|
-
execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}"`, { stdio: 'inherit' });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
protected uninstallService(): void {
|
|
94
|
-
try { execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' }); } catch {}
|
|
95
|
-
if (fs.existsSync(LAUNCHD_PLIST_PATH)) fs.unlinkSync(LAUNCHD_PLIST_PATH);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
protected showLogs(): void {
|
|
99
|
-
const logPath = `${LAUNCHD_LOG_DIR}/bloby.log`;
|
|
100
|
-
if (fs.existsSync(logPath)) {
|
|
101
|
-
spawnSync('tail', ['-f', logPath], { stdio: 'inherit' });
|
|
102
|
-
} else {
|
|
103
|
-
console.log('Log file not found yet.');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
protected checkStatus(): void {
|
|
108
|
-
try { execSync(`launchctl list ${LAUNCHD_LABEL}`, { stdio: 'inherit' }); } catch {}
|
|
109
|
-
}
|
|
110
|
-
}
|
package/cli/platforms/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { getPlatform } from '../core/os-detector.js';
|
|
2
|
-
import { BaseAdapter } from '../core/base-adapter.js';
|
|
3
|
-
|
|
4
|
-
import { DarwinAdapter } from './darwin.js';
|
|
5
|
-
import { LinuxAdapter } from './linux.js';
|
|
6
|
-
import { WindowsAdapter } from './win32.js';
|
|
7
|
-
|
|
8
|
-
export function getAdapter(): BaseAdapter {
|
|
9
|
-
const platform = getPlatform();
|
|
10
|
-
|
|
11
|
-
switch (platform) {
|
|
12
|
-
case 'darwin':
|
|
13
|
-
return new DarwinAdapter();
|
|
14
|
-
case 'win32':
|
|
15
|
-
return new WindowsAdapter();
|
|
16
|
-
case 'linux':
|
|
17
|
-
default:
|
|
18
|
-
return new LinuxAdapter();
|
|
19
|
-
}
|
|
20
|
-
}
|
package/cli/platforms/linux.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import pc from 'picocolors';
|
|
6
|
-
|
|
7
|
-
import { BaseAdapter } from '../core/base-adapter.js';
|
|
8
|
-
import { SERVICE_NAME } from '../core/config.js';
|
|
9
|
-
import type { DaemonAction, DaemonConfig } from '../core/types.js';
|
|
10
|
-
|
|
11
|
-
const SERVICE_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
12
|
-
|
|
13
|
-
function generateUnitFile({ user, home, nodePath, dataDir }: DaemonConfig) {
|
|
14
|
-
const nodeBinDir = path.dirname(nodePath);
|
|
15
|
-
return `[Unit]
|
|
16
|
-
Description=Bloby Bot
|
|
17
|
-
After=network-online.target
|
|
18
|
-
Wants=network-online.target
|
|
19
|
-
|
|
20
|
-
[Service]
|
|
21
|
-
Type=simple
|
|
22
|
-
User=${user}
|
|
23
|
-
WorkingDirectory=${dataDir}
|
|
24
|
-
ExecStart=${nodePath} --import tsx/esm ${dataDir}/supervisor/index.ts
|
|
25
|
-
Restart=on-failure
|
|
26
|
-
RestartSec=5
|
|
27
|
-
Environment=HOME=${home}
|
|
28
|
-
Environment=NODE_ENV=development
|
|
29
|
-
Environment=NODE_PATH=${dataDir}/node_modules
|
|
30
|
-
Environment=PATH=${nodeBinDir}:${dataDir}/node_modules/.bin:/usr/local/bin:/usr/bin:/bin
|
|
31
|
-
StandardOutput=journal
|
|
32
|
-
StandardError=journal
|
|
33
|
-
SyslogIdentifier=bloby
|
|
34
|
-
|
|
35
|
-
[Install]
|
|
36
|
-
WantedBy=multi-user.target
|
|
37
|
-
`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class LinuxAdapter extends BaseAdapter {
|
|
41
|
-
get hasDaemonSupport(): boolean {
|
|
42
|
-
try { execSync('systemctl --version', { stdio: 'ignore' }); return true; } catch { return false; }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get isInstalled(): boolean { return fs.existsSync(SERVICE_PATH); }
|
|
46
|
-
|
|
47
|
-
get isActive(): boolean {
|
|
48
|
-
try { execSync(`systemctl is-active ${SERVICE_NAME}`, { stdio: 'ignore' }); return true; } catch { return false; }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
protected requiresPrivilegeEscalation(action: DaemonAction): boolean {
|
|
52
|
-
if (action === 'status' || action === 'logs') return false;
|
|
53
|
-
return process.getuid?.() !== 0;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
protected escalatePrivileges(): void {
|
|
57
|
-
const nodePath = process.env.BLOBY_NODE_PATH || process.execPath;
|
|
58
|
-
const realHome = process.env.BLOBY_REAL_HOME || this.getRealHome();
|
|
59
|
-
const args = process.argv.slice(1);
|
|
60
|
-
|
|
61
|
-
console.log(pc.yellow('Sudo is required for this action.'));
|
|
62
|
-
const result = spawnSync('sudo', [
|
|
63
|
-
`BLOBY_NODE_PATH=${nodePath}`,
|
|
64
|
-
`BLOBY_REAL_HOME=${realHome}`,
|
|
65
|
-
nodePath, ...args,
|
|
66
|
-
], { stdio: 'inherit' });
|
|
67
|
-
process.exit(result.status ?? 1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private getRealUser() {
|
|
71
|
-
return process.env.SUDO_USER || os.userInfo().username;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private getRealHome() {
|
|
75
|
-
try {
|
|
76
|
-
return execSync(`getent passwd ${this.getRealUser()}`, { encoding: 'utf-8' }).split(':')[5].trim();
|
|
77
|
-
} catch {
|
|
78
|
-
return os.homedir();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
protected installService(config: DaemonConfig): void {
|
|
83
|
-
const unit = generateUnitFile(config);
|
|
84
|
-
fs.writeFileSync(SERVICE_PATH, unit);
|
|
85
|
-
execSync([
|
|
86
|
-
`systemctl daemon-reload`,
|
|
87
|
-
`systemctl enable ${SERVICE_NAME}`,
|
|
88
|
-
`systemctl start ${SERVICE_NAME}`,
|
|
89
|
-
].join('; '), { stdio: 'inherit' });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
protected startService(): void {
|
|
93
|
-
execSync(`systemctl start ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
protected stopService(): void {
|
|
97
|
-
execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
protected uninstallService(): void {
|
|
101
|
-
execSync([
|
|
102
|
-
`systemctl disable ${SERVICE_NAME}`,
|
|
103
|
-
`systemctl stop ${SERVICE_NAME}`,
|
|
104
|
-
].join('; '), { stdio: 'ignore' });
|
|
105
|
-
if (fs.existsSync(SERVICE_PATH)) fs.unlinkSync(SERVICE_PATH);
|
|
106
|
-
execSync(`systemctl daemon-reload`, { stdio: 'ignore' });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
protected showLogs(): void {
|
|
110
|
-
spawnSync('journalctl', ['-u', SERVICE_NAME, '-f', '-n', '50'], { stdio: 'inherit' });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
protected checkStatus(): void {
|
|
114
|
-
try { execSync(`systemctl status ${SERVICE_NAME}`, { stdio: 'inherit' }); } catch {}
|
|
115
|
-
}
|
|
116
|
-
}
|
package/cli/platforms/win32.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import pc from 'picocolors';
|
|
2
|
-
|
|
3
|
-
import { BaseAdapter } from '../core/base-adapter.js';
|
|
4
|
-
import type { DaemonConfig } from '../core/types.js';
|
|
5
|
-
|
|
6
|
-
export class WindowsAdapter extends BaseAdapter {
|
|
7
|
-
get hasDaemonSupport() { return false; }
|
|
8
|
-
get isInstalled() { return false; }
|
|
9
|
-
get isActive() { return false; }
|
|
10
|
-
|
|
11
|
-
protected installService(_config: DaemonConfig): void {
|
|
12
|
-
console.log(pc.yellow('Daemon mode is not supported on Windows natively yet. Use Task Scheduler.'));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
protected startService(): void {}
|
|
16
|
-
protected stopService(): void {}
|
|
17
|
-
protected uninstallService(): void {}
|
|
18
|
-
protected showLogs(): void {}
|
|
19
|
-
protected checkStatus(): void {}
|
|
20
|
-
}
|
package/cli/utils/ui.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import pc from 'picocolors';
|
|
2
|
-
import { pkg } from '../core/config.js';
|
|
3
|
-
|
|
4
|
-
// 24-bit truecolor wrapper (picocolors only ships the 16-color set). Respects
|
|
5
|
-
// picocolors' own support detection so NO_COLOR / piped output stays clean.
|
|
6
|
-
const tc = (r: number, g: number, b: number) => (s: string) =>
|
|
7
|
-
pc.isColorSupported ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m` : s;
|
|
8
|
-
|
|
9
|
-
// Morphy logo gradient: #00ADFE (top) -> #0158FB (bottom)
|
|
10
|
-
const grad = [
|
|
11
|
-
tc(0, 173, 254),
|
|
12
|
-
tc(0, 159, 254),
|
|
13
|
-
tc(0, 145, 253),
|
|
14
|
-
tc(1, 131, 253),
|
|
15
|
-
tc(1, 116, 252),
|
|
16
|
-
tc(1, 102, 251),
|
|
17
|
-
tc(1, 88, 251),
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const logo = [
|
|
21
|
-
' █▄ ',
|
|
22
|
-
' ▄ ▄ ██ ',
|
|
23
|
-
' ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██',
|
|
24
|
-
' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██',
|
|
25
|
-
' ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀',
|
|
26
|
-
' ██ ██ ',
|
|
27
|
-
' ▀ ▀▀▀ ',
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
export function banner() {
|
|
31
|
-
console.log('');
|
|
32
|
-
logo.forEach((row, i) => console.log(grad[i](pc.bold(row))));
|
|
33
|
-
console.log(pc.dim(`v${pkg.version || '1.0.0'} · Self-hosted AI agent`));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function commandExample(name: string, cmd: string) {
|
|
37
|
-
return ` ${pc.dim(name)} ${tc(0, 173, 254)(cmd)}`;
|
|
38
|
-
}
|