@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 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 { constants, promises as fs } from "node:fs";
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("Installing persistent OpenCore launcher...");
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(`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");
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.0",
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();
@@ -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
- const installed = await this.listInstalledSkillIds();
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) => {