@opencoreai/opencore 0.2.2
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 +62 -0
- package/README.md +205 -0
- package/bin/opencore.mjs +343 -0
- package/opencore dashboard/app.js +923 -0
- package/opencore dashboard/index.html +15 -0
- package/opencore dashboard/styles.css +965 -0
- package/package.json +46 -0
- package/scripts/postinstall.mjs +448 -0
- package/src/credential-store.mjs +209 -0
- package/src/dashboard-server.ts +403 -0
- package/src/index.ts +2523 -0
- package/src/mac-controller.mjs +614 -0
- package/src/opencore-indicator.js +140 -0
- package/src/skill-catalog.mjs +305 -0
- package/templates/default-guidelines.md +142 -0
- package/templates/default-heartbeat.md +20 -0
- package/templates/default-instructions.md +72 -0
- package/templates/default-memory.md +7 -0
- package/templates/default-soul.md +130 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
OpenCore Personal Use License 1.0
|
|
2
|
+
Copyright (c) 2026 Eli Abdeen. All rights reserved.
|
|
3
|
+
|
|
4
|
+
1. Grant of License
|
|
5
|
+
|
|
6
|
+
Subject to the terms of this License, you are granted a limited, personal,
|
|
7
|
+
non-exclusive, non-transferable, non-sublicensable, revocable license to
|
|
8
|
+
install and use this software solely for your own personal, internal use.
|
|
9
|
+
|
|
10
|
+
2. No Commercial Use
|
|
11
|
+
|
|
12
|
+
You may not sell, rent, lease, sublicense, distribute for a fee, offer as a
|
|
13
|
+
service, bundle for sale, or otherwise commercially exploit the software or any
|
|
14
|
+
copy of it.
|
|
15
|
+
|
|
16
|
+
3. No Modification or Derivative Works
|
|
17
|
+
|
|
18
|
+
You may not modify, adapt, translate, reverse engineer, decompile, disassemble,
|
|
19
|
+
create derivative works from, merge, repackage, alter branding of, or otherwise
|
|
20
|
+
prepare modified versions of the software, except to the extent such a
|
|
21
|
+
restriction is prohibited by applicable law and cannot be contractually waived.
|
|
22
|
+
|
|
23
|
+
4. No Redistribution
|
|
24
|
+
|
|
25
|
+
You may not copy, publish, distribute, share, transfer, assign, or make the
|
|
26
|
+
software available to any third party, except that you may download and install
|
|
27
|
+
an authorized copy for your own personal use.
|
|
28
|
+
|
|
29
|
+
5. No Resale
|
|
30
|
+
|
|
31
|
+
You may not resell the software, any copy of the software, any access to the
|
|
32
|
+
software, or any service built from or around the software.
|
|
33
|
+
|
|
34
|
+
6. Ownership
|
|
35
|
+
|
|
36
|
+
The software is licensed, not sold. All rights, title, and interest in and to
|
|
37
|
+
the software, including all intellectual property rights, remain exclusively
|
|
38
|
+
with the copyright holder.
|
|
39
|
+
|
|
40
|
+
7. Termination
|
|
41
|
+
|
|
42
|
+
This License terminates automatically if you violate any term of this License.
|
|
43
|
+
Upon termination, you must stop using the software and destroy all copies in
|
|
44
|
+
your possession or control.
|
|
45
|
+
|
|
46
|
+
8. No Warranty
|
|
47
|
+
|
|
48
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
49
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
50
|
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.
|
|
51
|
+
|
|
52
|
+
9. Limitation of Liability
|
|
53
|
+
|
|
54
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER SHALL NOT BE
|
|
55
|
+
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
56
|
+
CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
57
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
58
|
+
|
|
59
|
+
10. Acceptance
|
|
60
|
+
|
|
61
|
+
By installing, copying, or using the software, you agree to be bound by this
|
|
62
|
+
License.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# OpenCore
|
|
2
|
+
|
|
3
|
+
OpenCore is a macOS assistant that can carry out tasks on your computer through a local app experience with a CLI and dashboard.
|
|
4
|
+
|
|
5
|
+
OpenCore can help with:
|
|
6
|
+
- using apps and websites on your Mac
|
|
7
|
+
- handling repetitive workflows
|
|
8
|
+
- sending tasks from the terminal, dashboard, or Telegram
|
|
9
|
+
- creating scheduled tasks
|
|
10
|
+
- storing local website login details for reuse
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- macOS
|
|
15
|
+
- Node.js and npm
|
|
16
|
+
- a ChatGPT API key
|
|
17
|
+
- Terminal access
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Install OpenCore globally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i -g @opencoreai/opencore
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run first-time onboarding:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
opencore onboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Start OpenCore:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
opencore engage
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Onboarding
|
|
40
|
+
|
|
41
|
+
On first use, run `opencore onboard`. If onboarding has not been completed yet, `opencore engage` will start onboarding automatically.
|
|
42
|
+
|
|
43
|
+
OpenCore will ask for:
|
|
44
|
+
- your ChatGPT API key
|
|
45
|
+
- the name OpenCore should use for you
|
|
46
|
+
- the tone OpenCore should use
|
|
47
|
+
- optional Telegram connection
|
|
48
|
+
- optional skill selection
|
|
49
|
+
|
|
50
|
+
## Permissions
|
|
51
|
+
|
|
52
|
+
To let OpenCore control your Mac, allow your terminal app in:
|
|
53
|
+
|
|
54
|
+
1. `System Settings -> Privacy & Security -> Accessibility`
|
|
55
|
+
2. `System Settings -> Privacy & Security -> Screen Recording`
|
|
56
|
+
|
|
57
|
+
## Use
|
|
58
|
+
|
|
59
|
+
Run OpenCore:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
opencore engage
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Stop OpenCore:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
/exit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
While OpenCore is running, you can:
|
|
72
|
+
- send tasks in the terminal
|
|
73
|
+
- use the dashboard in your browser
|
|
74
|
+
- send tasks from Telegram if connected
|
|
75
|
+
|
|
76
|
+
Dashboard address:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
http://127.0.0.1:4111/dashboard/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Dashboard
|
|
83
|
+
|
|
84
|
+
The dashboard lets you:
|
|
85
|
+
- send tasks
|
|
86
|
+
- view activity
|
|
87
|
+
- manage Telegram
|
|
88
|
+
- manage saved login details
|
|
89
|
+
- manage skills
|
|
90
|
+
- view screenshots
|
|
91
|
+
|
|
92
|
+
All dashboard data stays local on the user’s Mac.
|
|
93
|
+
|
|
94
|
+
## Saved Logins
|
|
95
|
+
|
|
96
|
+
OpenCore can save website login details locally so they can be reused later.
|
|
97
|
+
|
|
98
|
+
You can:
|
|
99
|
+
- save an email and password for a website
|
|
100
|
+
- set a default email for new account creation
|
|
101
|
+
- set an email provider
|
|
102
|
+
- choose whether automatic email activation is allowed
|
|
103
|
+
|
|
104
|
+
Automatic email activation is off by default.
|
|
105
|
+
|
|
106
|
+
If you turn it on, OpenCore may open your email provider to read verification codes or links needed to complete sign-up flows.
|
|
107
|
+
|
|
108
|
+
Only enable that if you trust the current machine and session.
|
|
109
|
+
|
|
110
|
+
## Telegram
|
|
111
|
+
|
|
112
|
+
OpenCore can connect to Telegram so you can send tasks from your phone while the CLI is running.
|
|
113
|
+
|
|
114
|
+
## Scheduled Tasks
|
|
115
|
+
|
|
116
|
+
OpenCore can create scheduled tasks and check whether missed work needs to be completed.
|
|
117
|
+
|
|
118
|
+
Useful commands:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
/schedules
|
|
122
|
+
/check-heartbeat
|
|
123
|
+
/unschedule <schedule-id>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Commands
|
|
127
|
+
|
|
128
|
+
Start OpenCore:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
opencore engage
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Run onboarding again:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
opencore onboard
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Run setup manually:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
opencore setup
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Update OpenCore:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
opencore update
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Set ChatGPT API key:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
opencore config set-key chatgpt
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Show current config:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
opencore config show
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Reconnect Telegram:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
/telegram reconnect
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Disconnect Telegram:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
/telegram disconnect
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Update
|
|
177
|
+
|
|
178
|
+
To update OpenCore:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
opencore update
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Or install the newest published version directly:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npm install -g @opencoreai/opencore@latest
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
OpenCore does not require the GitHub repository to be public in order for the npm package to work. Only the published npm package needs to be available to the installing user.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
OpenCore is distributed under the `OpenCore Personal Use License 1.0`.
|
|
195
|
+
|
|
196
|
+
It is not open source.
|
|
197
|
+
|
|
198
|
+
The license prohibits:
|
|
199
|
+
- modification
|
|
200
|
+
- redistribution
|
|
201
|
+
- resale
|
|
202
|
+
- creating derivative works
|
|
203
|
+
- commercial use
|
|
204
|
+
|
|
205
|
+
See [`LICENSE`](/Users/eli/dev/computer-use-mac-agent/LICENSE) for full terms.
|
package/bin/opencore.mjs
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { constants, promises as fs } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const rootDir = path.resolve(__dirname, "..");
|
|
14
|
+
const tsxCli = path.join(rootDir, "node_modules", "tsx", "dist", "cli.mjs");
|
|
15
|
+
const entry = path.join(rootDir, "src", "index.ts");
|
|
16
|
+
const setupScript = path.join(rootDir, "scripts", "postinstall.mjs");
|
|
17
|
+
const openCoreHome = path.join(os.homedir(), ".opencore");
|
|
18
|
+
const settingsPath = path.join(openCoreHome, "configs", "settings.json");
|
|
19
|
+
const runtimeRoot = path.join(openCoreHome, "runtime");
|
|
20
|
+
const runtimeInstallRoot = path.join(runtimeRoot, "current");
|
|
21
|
+
const localBinDir = path.join(openCoreHome, "bin");
|
|
22
|
+
const zshRcPath = path.join(os.homedir(), ".zshrc");
|
|
23
|
+
const publishedPackageName = "@opencoreai/opencore";
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const command = args[0];
|
|
27
|
+
const subcommand = args[1];
|
|
28
|
+
const thirdArg = args[2];
|
|
29
|
+
|
|
30
|
+
async function ensureSettingsFile() {
|
|
31
|
+
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(settingsPath);
|
|
34
|
+
} catch {
|
|
35
|
+
const defaults = {
|
|
36
|
+
name: "OpenCore",
|
|
37
|
+
platform: "macOS",
|
|
38
|
+
provider: "chatgpt",
|
|
39
|
+
openai_api_key: "",
|
|
40
|
+
schema_version: 1,
|
|
41
|
+
};
|
|
42
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(defaults, null, 2)}\n`, "utf8");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readSettings() {
|
|
47
|
+
await ensureSettingsFile();
|
|
48
|
+
const raw = await fs.readFile(settingsPath, "utf8");
|
|
49
|
+
return JSON.parse(raw || "{}");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function writeSettings(settings) {
|
|
53
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function needsInitialSetup(settings) {
|
|
57
|
+
const apiKey = String(settings?.openai_api_key || "").trim();
|
|
58
|
+
const displayName = String(settings?.user_display_name || "").trim();
|
|
59
|
+
const tone = String(settings?.assistant_tone || "").trim();
|
|
60
|
+
return !apiKey || !displayName || !tone;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runInitialSetupIfNeeded() {
|
|
64
|
+
const settings = await readSettings();
|
|
65
|
+
if (!needsInitialSetup(settings)) return;
|
|
66
|
+
|
|
67
|
+
if (!(input.isTTY && output.isTTY)) {
|
|
68
|
+
console.error("Initial OpenCore setup requires an interactive terminal.");
|
|
69
|
+
console.error("Run `opencore engage` in a normal terminal window to finish setup.");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log("Running first-time OpenCore setup...");
|
|
74
|
+
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
const setup = spawn(process.execPath, [setupScript], {
|
|
77
|
+
cwd: rootDir,
|
|
78
|
+
stdio: "inherit",
|
|
79
|
+
env: process.env,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
setup.on("exit", (code, signal) => {
|
|
83
|
+
if (signal) {
|
|
84
|
+
reject(new Error(`Setup exited with signal ${signal}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if ((code ?? 1) !== 0) {
|
|
88
|
+
reject(new Error(`Setup exited with code ${code ?? 1}`));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
setup.on("error", reject);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function fileExists(targetPath) {
|
|
99
|
+
try {
|
|
100
|
+
await fs.access(targetPath);
|
|
101
|
+
return true;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function ensureDir(targetPath) {
|
|
108
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function isWritableDirectory(targetPath) {
|
|
112
|
+
try {
|
|
113
|
+
await ensureDir(targetPath);
|
|
114
|
+
await fs.access(targetPath, constants.W_OK);
|
|
115
|
+
return true;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function installPersistentRuntime() {
|
|
122
|
+
await ensureDir(runtimeInstallRoot);
|
|
123
|
+
|
|
124
|
+
await new Promise((resolve, reject) => {
|
|
125
|
+
const installer = spawn(
|
|
126
|
+
"npm",
|
|
127
|
+
["install", "--no-fund", "--no-audit", "--prefix", runtimeInstallRoot, rootDir],
|
|
128
|
+
{
|
|
129
|
+
cwd: rootDir,
|
|
130
|
+
stdio: "inherit",
|
|
131
|
+
env: process.env,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
installer.on("exit", (code, signal) => {
|
|
136
|
+
if (signal) {
|
|
137
|
+
reject(new Error(`Local install exited with signal ${signal}`));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if ((code ?? 1) !== 0) {
|
|
141
|
+
reject(new Error(`Local install exited with code ${code ?? 1}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
installer.on("error", reject);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getPersistentEntryPath() {
|
|
152
|
+
return path.join(runtimeInstallRoot, "node_modules", publishedPackageName, "bin", "opencore.mjs");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function renderLauncherScript(entryPath) {
|
|
156
|
+
return `#!/bin/sh
|
|
157
|
+
exec node "${entryPath}" "$@"
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function findPreferredLauncherDir() {
|
|
162
|
+
const pathEntries = String(process.env.PATH || "")
|
|
163
|
+
.split(path.delimiter)
|
|
164
|
+
.map((item) => item.trim())
|
|
165
|
+
.filter(Boolean);
|
|
166
|
+
|
|
167
|
+
const preferred = [
|
|
168
|
+
...pathEntries,
|
|
169
|
+
"/opt/homebrew/bin",
|
|
170
|
+
"/usr/local/bin",
|
|
171
|
+
path.join(os.homedir(), "bin"),
|
|
172
|
+
path.join(os.homedir(), ".local", "bin"),
|
|
173
|
+
localBinDir,
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
for (const dir of preferred) {
|
|
178
|
+
if (seen.has(dir)) continue;
|
|
179
|
+
seen.add(dir);
|
|
180
|
+
if (await isWritableDirectory(dir)) {
|
|
181
|
+
return { dir, alreadyOnPath: pathEntries.includes(dir) };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { dir: localBinDir, alreadyOnPath: pathEntries.includes(localBinDir) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function ensureZshPathEntry(binDir) {
|
|
189
|
+
const exportLine = `export PATH="${binDir}:$PATH"`;
|
|
190
|
+
const existing = (await fs.readFile(zshRcPath, "utf8").catch(() => "")).toString();
|
|
191
|
+
if (existing.includes(exportLine)) return false;
|
|
192
|
+
const next = existing.trimEnd();
|
|
193
|
+
const content = `${next ? `${next}\n\n` : ""}# OpenCore CLI\n${exportLine}\n`;
|
|
194
|
+
await fs.writeFile(zshRcPath, content, "utf8");
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function installPersistentLauncher() {
|
|
199
|
+
const entryPath = getPersistentEntryPath();
|
|
200
|
+
if (!(await fileExists(entryPath))) {
|
|
201
|
+
throw new Error("Persistent OpenCore runtime was not installed correctly.");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { dir, alreadyOnPath } = await findPreferredLauncherDir();
|
|
205
|
+
await ensureDir(dir);
|
|
206
|
+
|
|
207
|
+
const launcherPath = path.join(dir, "opencore");
|
|
208
|
+
await fs.writeFile(launcherPath, renderLauncherScript(entryPath), "utf8");
|
|
209
|
+
await fs.chmod(launcherPath, 0o755);
|
|
210
|
+
|
|
211
|
+
let zshUpdated = false;
|
|
212
|
+
if (!alreadyOnPath) {
|
|
213
|
+
zshUpdated = await ensureZshPathEntry(dir);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { launcherPath, binDir: dir, alreadyOnPath, zshUpdated };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function runSetupCommand() {
|
|
220
|
+
try {
|
|
221
|
+
console.log("Installing persistent OpenCore launcher...");
|
|
222
|
+
await installPersistentRuntime();
|
|
223
|
+
const launcher = await installPersistentLauncher();
|
|
224
|
+
await runInitialSetupIfNeeded();
|
|
225
|
+
console.log("OpenCore setup is complete.");
|
|
226
|
+
console.log(`OpenCore launcher: ${launcher.launcherPath}`);
|
|
227
|
+
if (!launcher.alreadyOnPath) {
|
|
228
|
+
console.log(`Added ${launcher.binDir} to ~/.zshrc`);
|
|
229
|
+
console.log("Open a new terminal window or run: source ~/.zshrc");
|
|
230
|
+
}
|
|
231
|
+
console.log("You can then run: opencore engage");
|
|
232
|
+
process.exit(0);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(`OpenCore setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function maskKey(key) {
|
|
240
|
+
const value = String(key || "");
|
|
241
|
+
if (!value) return "(not set)";
|
|
242
|
+
if (value.length <= 8) return "********";
|
|
243
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (command === "config" && subcommand === "show") {
|
|
247
|
+
const settings = await readSettings();
|
|
248
|
+
const outputConfig = {
|
|
249
|
+
...settings,
|
|
250
|
+
openai_api_key: maskKey(process.env.OPENAI_API_KEY || settings.openai_api_key),
|
|
251
|
+
settings_path: settingsPath,
|
|
252
|
+
env_path: path.join(rootDir, ".env"),
|
|
253
|
+
};
|
|
254
|
+
console.log(JSON.stringify(outputConfig, null, 2));
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (command === "config" && subcommand === "set-key") {
|
|
259
|
+
const settings = await readSettings();
|
|
260
|
+
const providerArg = String(thirdArg || "").trim().toLowerCase();
|
|
261
|
+
const fourthArg = String(args[3] || "").trim();
|
|
262
|
+
let key = providerArg === "chatgpt" ? fourthArg : String(thirdArg || "").trim();
|
|
263
|
+
if (!key) {
|
|
264
|
+
if (!(input.isTTY && output.isTTY)) {
|
|
265
|
+
console.error("No key provided. Use: opencore config set-key [chatgpt] <your-key>");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
const rl = readline.createInterface({ input, output });
|
|
269
|
+
try {
|
|
270
|
+
while (!key) {
|
|
271
|
+
key = (await rl.question("Enter OpenAI API key: ")).trim();
|
|
272
|
+
}
|
|
273
|
+
} finally {
|
|
274
|
+
rl.close();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
settings.provider = "chatgpt";
|
|
278
|
+
settings.openai_api_key = key;
|
|
279
|
+
await writeSettings(settings);
|
|
280
|
+
console.log(`Saved chatgpt API key to ${settingsPath}`);
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (command === "update") {
|
|
285
|
+
console.log(`Updating OpenCore from ${publishedPackageName}@latest ...`);
|
|
286
|
+
console.log("This command updates the published OpenCore CLI package.");
|
|
287
|
+
console.log("If you are running a local repo checkout, use git/pull or reinstall dependencies there instead.");
|
|
288
|
+
const updater = spawn("npm", ["install", "-g", `${publishedPackageName}@latest`], {
|
|
289
|
+
cwd: process.cwd(),
|
|
290
|
+
stdio: "inherit",
|
|
291
|
+
env: process.env,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
updater.on("exit", (code, signal) => {
|
|
295
|
+
if (signal) process.kill(process.pid, signal);
|
|
296
|
+
if ((code ?? 1) !== 0) {
|
|
297
|
+
console.error("OpenCore update failed.");
|
|
298
|
+
process.exit(code ?? 1);
|
|
299
|
+
}
|
|
300
|
+
console.log("OpenCore updated successfully.");
|
|
301
|
+
console.log("Run `opencore engage` again to start the newest version.");
|
|
302
|
+
process.exit(0);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
process.on("SIGINT", () => updater.kill("SIGINT"));
|
|
306
|
+
process.on("SIGTERM", () => updater.kill("SIGTERM"));
|
|
307
|
+
} else if (command === "setup" || command === "onboard") {
|
|
308
|
+
await runSetupCommand();
|
|
309
|
+
} else if (!["engage", "run-scheduled"].includes(String(command || ""))) {
|
|
310
|
+
console.log("OpenCore CLI");
|
|
311
|
+
console.log("Usage:");
|
|
312
|
+
console.log(" opencore engage");
|
|
313
|
+
console.log(" opencore setup");
|
|
314
|
+
console.log(" opencore onboard");
|
|
315
|
+
console.log(" opencore run-scheduled <schedule-id>");
|
|
316
|
+
console.log(" opencore update");
|
|
317
|
+
console.log(" opencore config set-key [chatgpt] <api-key>");
|
|
318
|
+
console.log(" opencore config show");
|
|
319
|
+
process.exit(command ? 1 : 0);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (command === "engage" || command === "run-scheduled") {
|
|
323
|
+
if (command === "engage") {
|
|
324
|
+
try {
|
|
325
|
+
await runInitialSetupIfNeeded();
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error(`OpenCore setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const childArgs = command === "run-scheduled" ? [tsxCli, entry, "--scheduled-id", subcommand || ""] : [tsxCli, entry];
|
|
333
|
+
const child = spawn(process.execPath, childArgs, {
|
|
334
|
+
cwd: rootDir,
|
|
335
|
+
stdio: "inherit",
|
|
336
|
+
env: process.env,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
child.on("exit", (code, signal) => {
|
|
340
|
+
if (signal) process.kill(process.pid, signal);
|
|
341
|
+
else process.exit(code ?? 0);
|
|
342
|
+
});
|
|
343
|
+
}
|