@opencoreai/opencore 0.4.0 → 0.4.1
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/opencore.mjs +3 -135
- package/opencore dashboard/app.js +8 -0
- package/package.json +2 -1
- package/scripts/install-migrate.mjs +63 -0
- package/src/dashboard-server.ts +84 -9
package/bin/opencore.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "dotenv/config";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import {
|
|
4
|
+
import { promises as fs } from "node:fs";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import readline from "node:readline/promises";
|
|
@@ -16,10 +16,6 @@ const entry = path.join(rootDir, "src", "index.ts");
|
|
|
16
16
|
const setupScript = path.join(rootDir, "scripts", "postinstall.mjs");
|
|
17
17
|
const openCoreHome = path.join(os.homedir(), ".opencore");
|
|
18
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
19
|
const publishedPackageName = "@opencoreai/opencore";
|
|
24
20
|
|
|
25
21
|
const args = process.argv.slice(2);
|
|
@@ -95,140 +91,12 @@ async function runInitialSetupIfNeeded() {
|
|
|
95
91
|
});
|
|
96
92
|
}
|
|
97
93
|
|
|
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
94
|
async function runSetupCommand() {
|
|
220
95
|
try {
|
|
221
|
-
console.log("
|
|
222
|
-
await installPersistentRuntime();
|
|
223
|
-
const launcher = await installPersistentLauncher();
|
|
96
|
+
console.log("Running OpenCore onboarding...");
|
|
224
97
|
await runInitialSetupIfNeeded();
|
|
225
98
|
console.log("OpenCore setup is complete.");
|
|
226
|
-
console.log(
|
|
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");
|
|
99
|
+
console.log("You can now run: opencore engage");
|
|
232
100
|
process.exit(0);
|
|
233
101
|
} catch (error) {
|
|
234
102
|
console.error(`OpenCore setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -238,6 +238,14 @@
|
|
|
238
238
|
}
|
|
239
239
|
}, [page]);
|
|
240
240
|
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (page !== "skills") return undefined;
|
|
243
|
+
const timer = setInterval(() => {
|
|
244
|
+
loadSkills().catch(() => {});
|
|
245
|
+
}, 3000);
|
|
246
|
+
return () => clearInterval(timer);
|
|
247
|
+
}, [page]);
|
|
248
|
+
|
|
241
249
|
async function sendChat() {
|
|
242
250
|
const text = chatText.trim();
|
|
243
251
|
if (!text || chatBusy) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencoreai/opencore",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "LicenseRef-OpenCore-Personal-Use-1.0",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"opencore": "bin/opencore.mjs"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
+
"postinstall": "node scripts/install-migrate.mjs",
|
|
29
30
|
"start": "tsx src/index.ts",
|
|
30
31
|
"engage": "node bin/opencore.mjs engage"
|
|
31
32
|
},
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
|
|
5
|
+
const homeDir = os.homedir();
|
|
6
|
+
const openCoreBinPath = path.join(homeDir, ".opencore", "bin", "opencore");
|
|
7
|
+
const zshRcPath = path.join(homeDir, ".zshrc");
|
|
8
|
+
const legacyPathLine = 'export PATH="$HOME/.opencore/bin:$PATH"';
|
|
9
|
+
const legacyPathLineExpanded = `export PATH="${path.join(homeDir, ".opencore", "bin")}:$PATH"`;
|
|
10
|
+
|
|
11
|
+
async function removeLegacyLauncher() {
|
|
12
|
+
try {
|
|
13
|
+
const content = await fs.readFile(openCoreBinPath, "utf8");
|
|
14
|
+
const isLegacy =
|
|
15
|
+
content.includes(`${path.join(homeDir, ".opencore", "runtime", "current")}`) ||
|
|
16
|
+
content.includes(".opencore/runtime/current/node_modules/@opencoreai/opencore/bin/opencore.mjs");
|
|
17
|
+
if (!isLegacy) return false;
|
|
18
|
+
await fs.unlink(openCoreBinPath);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function cleanLegacyZshrc() {
|
|
26
|
+
try {
|
|
27
|
+
const existing = await fs.readFile(zshRcPath, "utf8");
|
|
28
|
+
const lines = existing.split("\n");
|
|
29
|
+
const cleaned = [];
|
|
30
|
+
let removed = false;
|
|
31
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
32
|
+
const line = lines[i];
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (trimmed === "# OpenCore CLI") {
|
|
35
|
+
const next = String(lines[i + 1] || "").trim();
|
|
36
|
+
if (next === legacyPathLine || next === legacyPathLineExpanded) {
|
|
37
|
+
removed = true;
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (trimmed === legacyPathLine || trimmed === legacyPathLineExpanded) {
|
|
43
|
+
removed = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
cleaned.push(line);
|
|
47
|
+
}
|
|
48
|
+
if (!removed) return false;
|
|
49
|
+
await fs.writeFile(zshRcPath, `${cleaned.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`, "utf8");
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const [removedLauncher, cleanedZshrc] = await Promise.all([removeLegacyLauncher(), cleanLegacyZshrc()]);
|
|
58
|
+
if (removedLauncher || cleanedZshrc) {
|
|
59
|
+
console.log("[OpenCore] Removed legacy ~/.opencore launcher migration artifacts.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await main();
|
package/src/dashboard-server.ts
CHANGED
|
@@ -164,6 +164,89 @@ export class DashboardServer {
|
|
|
164
164
|
return installed;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
private extractFrontmatterField(content: string, key: string) {
|
|
168
|
+
const text = String(content || "");
|
|
169
|
+
if (!text.startsWith("---")) return "";
|
|
170
|
+
const end = text.indexOf("\n---", 3);
|
|
171
|
+
if (end === -1) return "";
|
|
172
|
+
const frontmatter = text.slice(3, end);
|
|
173
|
+
const match = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
174
|
+
return match ? String(match[1] || "").trim().replace(/^["']|["']$/g, "") : "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private extractFirstHeading(content: string) {
|
|
178
|
+
const match = String(content || "").match(/^#\s+(.+)$/m);
|
|
179
|
+
return match ? String(match[1] || "").trim() : "";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async listSkillsDetailed() {
|
|
183
|
+
const catalogMap = new Map(SKILL_CATALOG.map((skill) => [skill.id, skill]));
|
|
184
|
+
const items = new Map<string, any>();
|
|
185
|
+
|
|
186
|
+
for (const skill of SKILL_CATALOG) {
|
|
187
|
+
items.set(skill.id, {
|
|
188
|
+
id: skill.id,
|
|
189
|
+
name: skill.name,
|
|
190
|
+
description: skill.description,
|
|
191
|
+
installed: false,
|
|
192
|
+
external: false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const entries = await fs.readdir(this.skillsDir, { withFileTypes: true });
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (!entry.isDirectory()) continue;
|
|
200
|
+
const id = entry.name;
|
|
201
|
+
const skillDir = path.join(this.skillsDir, id);
|
|
202
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
203
|
+
const configFile = path.join(skillDir, "config.json");
|
|
204
|
+
try {
|
|
205
|
+
await fs.access(skillFile);
|
|
206
|
+
} catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let config: any = {};
|
|
211
|
+
let markdown = "";
|
|
212
|
+
try {
|
|
213
|
+
const raw = await fs.readFile(configFile, "utf8");
|
|
214
|
+
config = JSON.parse(raw || "{}");
|
|
215
|
+
} catch {}
|
|
216
|
+
try {
|
|
217
|
+
markdown = await fs.readFile(skillFile, "utf8");
|
|
218
|
+
} catch {}
|
|
219
|
+
|
|
220
|
+
const catalogSkill = catalogMap.get(id);
|
|
221
|
+
const resolvedName =
|
|
222
|
+
String(config?.name || "").trim() ||
|
|
223
|
+
this.extractFrontmatterField(markdown, "name") ||
|
|
224
|
+
this.extractFirstHeading(markdown) ||
|
|
225
|
+
catalogSkill?.name ||
|
|
226
|
+
id;
|
|
227
|
+
const resolvedDescription =
|
|
228
|
+
String(config?.description || "").trim() ||
|
|
229
|
+
this.extractFrontmatterField(markdown, "description") ||
|
|
230
|
+
catalogSkill?.description ||
|
|
231
|
+
"External OpenCore skill.";
|
|
232
|
+
|
|
233
|
+
items.set(id, {
|
|
234
|
+
id,
|
|
235
|
+
name: resolvedName,
|
|
236
|
+
description: resolvedDescription,
|
|
237
|
+
installed: true,
|
|
238
|
+
external: !catalogSkill,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
} catch {}
|
|
242
|
+
|
|
243
|
+
return Array.from(items.values()).sort((a, b) => {
|
|
244
|
+
if (a.installed !== b.installed) return a.installed ? -1 : 1;
|
|
245
|
+
if (a.external !== b.external) return a.external ? -1 : 1;
|
|
246
|
+
return String(a.name || a.id).localeCompare(String(b.name || b.id));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
167
250
|
private async installSkill(id: string) {
|
|
168
251
|
const skill = SKILL_CATALOG.find((item) => item.id === id);
|
|
169
252
|
if (!skill) {
|
|
@@ -267,15 +350,7 @@ export class DashboardServer {
|
|
|
267
350
|
});
|
|
268
351
|
|
|
269
352
|
this.app.get("/api/skills", async (_req, res) => {
|
|
270
|
-
|
|
271
|
-
res.json({
|
|
272
|
-
items: SKILL_CATALOG.map((skill) => ({
|
|
273
|
-
id: skill.id,
|
|
274
|
-
name: skill.name,
|
|
275
|
-
description: skill.description,
|
|
276
|
-
installed: installed.has(skill.id),
|
|
277
|
-
})),
|
|
278
|
-
});
|
|
353
|
+
res.json({ items: await this.listSkillsDetailed() });
|
|
279
354
|
});
|
|
280
355
|
|
|
281
356
|
this.app.get("/api/schedules", async (_req, res) => {
|