@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 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.
@@ -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
+ }