@nicotinetool/o7-cli 1.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/bin/o7-setup ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, execSync } from 'child_process';
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { homedir, platform, hostname, release } from 'os';
7
+
8
+ const VERSION = '1.1.0';
9
+ const O7_ADMIN_URL = 'https://o7-os-admin-production.up.railway.app';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ if (platform() === 'win32') {
14
+ console.error('Windows is not supported. Use macOS or Linux.');
15
+ process.exit(1);
16
+ }
17
+
18
+ // Parse flags
19
+ let profile = 'o7';
20
+ let useWizard = false;
21
+ const args = process.argv.slice(2);
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i] === '--profile' && args[i + 1]) { profile = args[++i]; }
24
+ if (args[i] === '--wizard') { useWizard = true; }
25
+ }
26
+
27
+ // Sanitize profile
28
+ if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
29
+ console.error(`Invalid profile name: "${profile}". Use only letters, numbers, hyphens, underscores.`);
30
+ process.exit(1);
31
+ }
32
+
33
+ // Find installer: bundled first, then local repo
34
+ const bundledInstaller = join(__dirname, '..', 'installer', 'install.sh');
35
+ const localLocations = [
36
+ join(homedir(), 'Projects/unified-mc/installer/install.sh'),
37
+ join(process.cwd(), 'installer/install.sh'),
38
+ ];
39
+
40
+ let installerPath = existsSync(bundledInstaller) ? bundledInstaller : localLocations.find(p => existsSync(p));
41
+
42
+ if (!installerPath) {
43
+ console.error('Installer not found. This package may be corrupted.');
44
+ console.error('Try reinstalling: npm i -g @nicotinetool/o7-cli@latest');
45
+ process.exit(1);
46
+ }
47
+
48
+ console.log(`\n\x1b[1mO7 OpenClaw Setup\x1b[0m v${VERSION} (profile: ${profile})\n`);
49
+
50
+ // Run the wizard (onboard.mjs) or the shell installer
51
+ if (useWizard) {
52
+ const wizardPath = join(dirname(installerPath), 'onboard.mjs');
53
+ if (existsSync(wizardPath)) {
54
+ try {
55
+ execFileSync('node', [wizardPath], { stdio: 'inherit', env: { ...process.env }, timeout: 600000 });
56
+ } catch {
57
+ console.error('\nWizard failed. Falling back to shell installer.');
58
+ useWizard = false;
59
+ }
60
+ } else {
61
+ console.log('Wizard not available, using shell installer.');
62
+ }
63
+ }
64
+
65
+ if (!useWizard) {
66
+ try {
67
+ execFileSync('bash', [installerPath, '--profile', profile], {
68
+ stdio: 'inherit',
69
+ env: { ...process.env },
70
+ timeout: 600000,
71
+ });
72
+ } catch {
73
+ console.error('\nInstallation failed. Check the output above for details.');
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ // Register device with O7 OS admin
79
+ async function registerDevice() {
80
+ const o7osDir = join(homedir(), '.openclaw', 'o7os');
81
+ const deviceFile = join(o7osDir, 'device.json');
82
+
83
+ if (existsSync(deviceFile)) {
84
+ try {
85
+ const existing = JSON.parse(readFileSync(deviceFile, 'utf8'));
86
+ if (existing.device_id && existing.api_key) {
87
+ console.log(`\x1b[32m✓\x1b[0m Device already registered (${existing.device_id.slice(0, 8)}...)`);
88
+ return;
89
+ }
90
+ } catch {}
91
+ }
92
+
93
+ const deviceName = hostname();
94
+ const osVersion = `${platform()} ${release()}`;
95
+ let ocVersion = 'unknown';
96
+ try { ocVersion = execSync('openclaw --version', { encoding: 'utf8', timeout: 5000 }).trim(); } catch {}
97
+
98
+ const body = JSON.stringify({ device_name: deviceName, os: osVersion, openclaw_version: ocVersion });
99
+
100
+ try {
101
+ const { default: https } = await import('https');
102
+ const result = await new Promise((resolve, reject) => {
103
+ const url = new URL(`${O7_ADMIN_URL}/api/devices`);
104
+ const req = https.request(url, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
107
+ timeout: 10000,
108
+ }, (res) => {
109
+ let data = '';
110
+ res.on('data', chunk => data += chunk);
111
+ res.on('end', () => {
112
+ if (res.statusCode >= 200 && res.statusCode < 300) resolve(JSON.parse(data));
113
+ else reject(new Error(`HTTP ${res.statusCode}`));
114
+ });
115
+ });
116
+ req.on('error', reject);
117
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
118
+ req.write(body);
119
+ req.end();
120
+ });
121
+
122
+ mkdirSync(o7osDir, { recursive: true });
123
+ writeFileSync(deviceFile, JSON.stringify({
124
+ device_id: result.id, device_name: deviceName, os: osVersion,
125
+ openclaw_version: ocVersion, o7os_version: VERSION,
126
+ installed_at: new Date().toISOString(), api_key: result.api_key,
127
+ }, null, 2) + '\n');
128
+ console.log(`\x1b[32m✓\x1b[0m Registered with O7 OS admin`);
129
+ } catch (err) {
130
+ console.log(`\x1b[33m⚠\x1b[0m Could not register with admin (${err.message}). Non-blocking.`);
131
+ }
132
+ }
133
+
134
+ await registerDevice();
135
+
136
+ console.log(`
137
+ \x1b[32m✓ Installation complete!\x1b[0m
138
+
139
+ Get started:
140
+ o7 start Start Gateway + Mission Control
141
+ o7 status Check running status
142
+ o7 doctor Run health check
143
+ `);
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, execSync } from 'child_process';
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { homedir, platform, hostname, release } from 'os';
7
+
8
+ const VERSION = '1.1.0';
9
+ const O7_ADMIN_URL = 'https://o7-os-admin-production.up.railway.app';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ if (platform() === 'win32') {
14
+ console.error('Windows is not supported. Use macOS or Linux.');
15
+ process.exit(1);
16
+ }
17
+
18
+ // Parse flags
19
+ let profile = 'o7';
20
+ let useWizard = false;
21
+ const args = process.argv.slice(2);
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i] === '--profile' && args[i + 1]) { profile = args[++i]; }
24
+ if (args[i] === '--wizard') { useWizard = true; }
25
+ }
26
+
27
+ // Sanitize profile
28
+ if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
29
+ console.error(`Invalid profile name: "${profile}". Use only letters, numbers, hyphens, underscores.`);
30
+ process.exit(1);
31
+ }
32
+
33
+ // Find installer: bundled first, then local repo
34
+ const bundledInstaller = join(__dirname, '..', 'installer', 'install.sh');
35
+ const localLocations = [
36
+ join(homedir(), 'Projects/unified-mc/installer/install.sh'),
37
+ join(process.cwd(), 'installer/install.sh'),
38
+ ];
39
+
40
+ let installerPath = existsSync(bundledInstaller) ? bundledInstaller : localLocations.find(p => existsSync(p));
41
+
42
+ if (!installerPath) {
43
+ console.error('Installer not found. This package may be corrupted.');
44
+ console.error('Try reinstalling: npm i -g @nicotinetool/o7-cli@latest');
45
+ process.exit(1);
46
+ }
47
+
48
+ console.log(`\n\x1b[1mO7 OpenClaw Setup\x1b[0m v${VERSION} (profile: ${profile})\n`);
49
+
50
+ // Run the wizard (onboard.mjs) or the shell installer
51
+ if (useWizard) {
52
+ const wizardPath = join(dirname(installerPath), 'onboard.mjs');
53
+ if (existsSync(wizardPath)) {
54
+ try {
55
+ execFileSync('node', [wizardPath], { stdio: 'inherit', env: { ...process.env }, timeout: 600000 });
56
+ } catch {
57
+ console.error('\nWizard failed. Falling back to shell installer.');
58
+ useWizard = false;
59
+ }
60
+ } else {
61
+ console.log('Wizard not available, using shell installer.');
62
+ }
63
+ }
64
+
65
+ if (!useWizard) {
66
+ try {
67
+ execFileSync('bash', [installerPath, '--profile', profile], {
68
+ stdio: 'inherit',
69
+ env: { ...process.env },
70
+ timeout: 600000,
71
+ });
72
+ } catch {
73
+ console.error('\nInstallation failed. Check the output above for details.');
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ // Register device with O7 OS admin
79
+ async function registerDevice() {
80
+ const o7osDir = join(homedir(), '.openclaw', 'o7os');
81
+ const deviceFile = join(o7osDir, 'device.json');
82
+
83
+ if (existsSync(deviceFile)) {
84
+ try {
85
+ const existing = JSON.parse(readFileSync(deviceFile, 'utf8'));
86
+ if (existing.device_id && existing.api_key) {
87
+ console.log(`\x1b[32m✓\x1b[0m Device already registered (${existing.device_id.slice(0, 8)}...)`);
88
+ return;
89
+ }
90
+ } catch {}
91
+ }
92
+
93
+ const deviceName = hostname();
94
+ const osVersion = `${platform()} ${release()}`;
95
+ let ocVersion = 'unknown';
96
+ try { ocVersion = execSync('openclaw --version', { encoding: 'utf8', timeout: 5000 }).trim(); } catch {}
97
+
98
+ const body = JSON.stringify({ device_name: deviceName, os: osVersion, openclaw_version: ocVersion });
99
+
100
+ try {
101
+ const { default: https } = await import('https');
102
+ const result = await new Promise((resolve, reject) => {
103
+ const url = new URL(`${O7_ADMIN_URL}/api/devices`);
104
+ const req = https.request(url, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
107
+ timeout: 10000,
108
+ }, (res) => {
109
+ let data = '';
110
+ res.on('data', chunk => data += chunk);
111
+ res.on('end', () => {
112
+ if (res.statusCode >= 200 && res.statusCode < 300) resolve(JSON.parse(data));
113
+ else reject(new Error(`HTTP ${res.statusCode}`));
114
+ });
115
+ });
116
+ req.on('error', reject);
117
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
118
+ req.write(body);
119
+ req.end();
120
+ });
121
+
122
+ mkdirSync(o7osDir, { recursive: true });
123
+ writeFileSync(deviceFile, JSON.stringify({
124
+ device_id: result.id, device_name: deviceName, os: osVersion,
125
+ openclaw_version: ocVersion, o7os_version: VERSION,
126
+ installed_at: new Date().toISOString(), api_key: result.api_key,
127
+ }, null, 2) + '\n');
128
+ console.log(`\x1b[32m✓\x1b[0m Registered with O7 OS admin`);
129
+ } catch (err) {
130
+ console.log(`\x1b[33m⚠\x1b[0m Could not register with admin (${err.message}). Non-blocking.`);
131
+ }
132
+ }
133
+
134
+ await registerDevice();
135
+
136
+ console.log(`
137
+ \x1b[32m✓ Installation complete!\x1b[0m
138
+
139
+ Get started:
140
+ o7 start Start Gateway + Mission Control
141
+ o7 status Check running status
142
+ o7 doctor Run health check
143
+ `);
package/bin/o7.js ADDED
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env node
2
+ import { execSync, spawn, execFileSync } from 'child_process';
3
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { homedir, hostname, platform, arch, release } from 'os';
6
+
7
+ const VERSION = '1.1.0';
8
+ const O7_ADMIN_URL = 'https://o7-os-admin-production.up.railway.app';
9
+ const MC_DIR = process.env.O7_MC_DIR || join(homedir(), 'Projects/unified-mc');
10
+ const STATE_DIR = join(homedir(), '.openclaw');
11
+ const O7OS_DIR = join(STATE_DIR, 'o7os');
12
+ const PID_FILE = join(STATE_DIR, '.mc.pid');
13
+ const DEVICE_FILE = join(O7OS_DIR, 'device.json');
14
+
15
+ mkdirSync(STATE_DIR, { recursive: true });
16
+
17
+ const cmd = process.argv[2];
18
+
19
+ // Handle flags before command dispatch
20
+ if (cmd === '--help' || cmd === '-h') { showHelp(); process.exit(0); }
21
+ if (cmd === '--version' || cmd === '-v') { console.log(VERSION); process.exit(0); }
22
+
23
+ // ── Helpers ──────────────────────────────────────────────────────────────────
24
+
25
+ function run(c, opts = {}) {
26
+ try {
27
+ return execSync(c, { stdio: 'inherit', timeout: 30000, ...opts });
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ function runSilent(c, opts = {}) {
34
+ try {
35
+ return execSync(c, { encoding: 'utf8', timeout: 15000, ...opts }).trim();
36
+ } catch {
37
+ return '';
38
+ }
39
+ }
40
+
41
+ function getMcPid() {
42
+ if (!existsSync(PID_FILE)) return null;
43
+ const raw = readFileSync(PID_FILE, 'utf8').trim();
44
+ const pid = Number(raw);
45
+ if (!pid || isNaN(pid)) {
46
+ unlinkSync(PID_FILE);
47
+ return null;
48
+ }
49
+ try {
50
+ process.kill(pid, 0);
51
+ return pid;
52
+ } catch {
53
+ unlinkSync(PID_FILE);
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function getDeviceInfo() {
59
+ try {
60
+ if (existsSync(DEVICE_FILE)) return JSON.parse(readFileSync(DEVICE_FILE, 'utf8'));
61
+ } catch {}
62
+ return null;
63
+ }
64
+
65
+ // ── Commands ─────────────────────────────────────────────────────────────────
66
+
67
+ function status() {
68
+ console.log('\x1b[1m--- Gateway ---\x1b[0m');
69
+ run('openclaw gateway status');
70
+
71
+ console.log('\n\x1b[1m--- Mission Control ---\x1b[0m');
72
+ const pid = getMcPid();
73
+ if (pid) {
74
+ console.log(`Running (PID ${pid}) at http://localhost:3005`);
75
+ } else {
76
+ console.log('Not running');
77
+ }
78
+
79
+ console.log('\n\x1b[1m--- Device ---\x1b[0m');
80
+ const device = getDeviceInfo();
81
+ if (device) {
82
+ console.log(`ID: ${device.device_id || 'unregistered'}`);
83
+ console.log(`Name: ${device.device_name || hostname()}`);
84
+ console.log(`OS: ${device.os || platform()}`);
85
+ console.log(`Registered: ${device.installed_at || 'unknown'}`);
86
+ } else {
87
+ console.log('Not registered with O7 OS');
88
+ }
89
+ }
90
+
91
+ function start() {
92
+ console.log('Starting OpenClaw gateway...');
93
+ run('openclaw gateway start');
94
+
95
+ const existingPid = getMcPid();
96
+ if (existingPid) {
97
+ console.log(`Mission Control already running (PID ${existingPid})`);
98
+ return;
99
+ }
100
+
101
+ if (!existsSync(MC_DIR)) {
102
+ console.error(`Mission Control directory not found: ${MC_DIR}`);
103
+ console.error('Set O7_MC_DIR env var or install first with: npx @erenes1667/o7-cli o7-setup');
104
+ process.exit(1);
105
+ }
106
+
107
+ console.log('Starting Mission Control...');
108
+ const envPath = `${dirname(process.execPath)}:/opt/homebrew/bin:/usr/local/bin:${process.env.PATH || ''}`;
109
+ const mc = spawn('pnpm', ['start'], {
110
+ cwd: MC_DIR,
111
+ detached: true,
112
+ stdio: 'ignore',
113
+ env: { ...process.env, PATH: envPath },
114
+ });
115
+ if (!mc.pid) {
116
+ console.error('Failed to start Mission Control. Is pnpm installed?');
117
+ process.exit(1);
118
+ }
119
+ mc.unref();
120
+ writeFileSync(PID_FILE, String(mc.pid));
121
+ console.log(`\n\x1b[32m✓ Mission Control started\x1b[0m (PID ${mc.pid})`);
122
+ console.log(' http://localhost:3005');
123
+
124
+ // Phone home (best-effort, non-blocking)
125
+ phoneHome().catch(() => {});
126
+ }
127
+
128
+ function stop() {
129
+ const pid = getMcPid();
130
+ if (pid) {
131
+ try {
132
+ // Kill the process group to catch child processes
133
+ process.kill(-pid, 'SIGTERM');
134
+ } catch {
135
+ try { process.kill(pid, 'SIGTERM'); } catch {}
136
+ }
137
+ console.log('Mission Control stopped');
138
+ if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
139
+ } else {
140
+ console.log('Mission Control was not running');
141
+ }
142
+ run('openclaw gateway stop');
143
+ }
144
+
145
+ function restart() {
146
+ stop();
147
+ setTimeout(() => start(), 1000);
148
+ }
149
+
150
+ function update() {
151
+ if (!existsSync(MC_DIR)) {
152
+ console.error(`Mission Control directory not found: ${MC_DIR}`);
153
+ process.exit(1);
154
+ }
155
+ console.log('Pulling latest changes...');
156
+ run('git pull', { cwd: MC_DIR });
157
+ console.log('Installing dependencies...');
158
+ run('pnpm install', { cwd: MC_DIR });
159
+ console.log('Rebuilding...');
160
+ run('pnpm build', { cwd: MC_DIR });
161
+ restart();
162
+ }
163
+
164
+ // ── Phone Home ───────────────────────────────────────────────────────────────
165
+
166
+ async function phoneHome() {
167
+ const device = getDeviceInfo();
168
+ if (!device || !device.device_id || !device.api_key) return;
169
+
170
+ const url = `${O7_ADMIN_URL}/api/devices/${device.device_id}/heartbeat`;
171
+ const ocVer = runSilent('openclaw --version');
172
+ const body = JSON.stringify({
173
+ openclaw_version: ocVer || 'unknown',
174
+ o7_cli_version: VERSION,
175
+ });
176
+
177
+ try {
178
+ const { default: https } = await import('https');
179
+ const reqUrl = new URL(url);
180
+ const req = https.request(reqUrl, {
181
+ method: 'POST',
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ 'Authorization': `Bearer ${device.api_key}`,
185
+ 'Content-Length': Buffer.byteLength(body),
186
+ },
187
+ timeout: 5000,
188
+ });
189
+ req.on('error', () => {});
190
+ req.write(body);
191
+ req.end();
192
+ } catch {}
193
+ }
194
+
195
+ // ── Help ─────────────────────────────────────────────────────────────────────
196
+
197
+ function showHelp() {
198
+ console.log(`
199
+ \x1b[1mO7 - Optimum7 OpenClaw Manager\x1b[0m v${VERSION}
200
+
201
+ Usage: o7 <command>
202
+
203
+ Commands:
204
+ start Start Gateway + Mission Control
205
+ stop Stop both
206
+ restart Restart both
207
+ update Pull latest, install deps, rebuild, restart
208
+ status Show running status
209
+ doctor Config-level health check + auto-fix
210
+
211
+ Options:
212
+ --help, -h Show this help
213
+ --version, -v Show version
214
+
215
+ Environment:
216
+ O7_MC_DIR Path to Mission Control (default: ~/Projects/unified-mc)
217
+ `);
218
+ }
219
+
220
+ // ── Dispatch ─────────────────────────────────────────────────────────────────
221
+
222
+ function doctor() {
223
+ const doctorScript = join(dirname(new URL(import.meta.url).pathname), 'o7-doctor');
224
+ try {
225
+ execSync(`node "${doctorScript}" ${process.argv.slice(3).join(' ')}`, { stdio: 'inherit', timeout: 60000 });
226
+ } catch (err) {
227
+ // doctor exits 1 if issues found, that's expected
228
+ if (err.status > 1) {
229
+ console.error('Doctor script failed to run.');
230
+ process.exit(1);
231
+ }
232
+ }
233
+ }
234
+
235
+ const commands = { status, start, stop, restart, update, doctor };
236
+
237
+ if (!cmd) { showHelp(); process.exit(0); }
238
+ if (!commands[cmd]) {
239
+ console.error(`Unknown command: ${cmd}`);
240
+ showHelp();
241
+ process.exit(1);
242
+ }
243
+
244
+ commands[cmd]();
@@ -0,0 +1,35 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>{{PLIST_LABEL}}</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>{{NODE_PATH}}</string>
11
+ <string>{{SERVER_JS}}</string>
12
+ </array>
13
+
14
+ <key>WorkingDirectory</key>
15
+ <string>{{APP_DIR}}</string>
16
+
17
+ <key>EnvironmentVariables</key>
18
+ <dict>
19
+ <key>PORT</key>
20
+ <string>{{PORT}}</string>
21
+ </dict>
22
+
23
+ <key>RunAtLoad</key>
24
+ <true/>
25
+
26
+ <key>KeepAlive</key>
27
+ <true/>
28
+
29
+ <key>StandardOutPath</key>
30
+ <string>{{LOG_DIR}}/stdout.log</string>
31
+
32
+ <key>StandardErrorPath</key>
33
+ <string>{{LOG_DIR}}/stderr.log</string>
34
+ </dict>
35
+ </plist>