@opencoreai/opencore 0.3.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.3.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) => {
@@ -9,12 +9,17 @@ export const SKILL_CATALOG = [
9
9
  triggers: ["create skill", "make a skill", "new skill", "update skill", "improve skill"],
10
10
  builtin: true,
11
11
  },
12
- markdown: `# OpenCore Skill: Skill Creator
12
+ markdown: `---
13
+ name: skill-creator
14
+ description: Create or update OpenCore skills with focused triggers, reusable instructions, and local config.
15
+ ---
16
+
17
+ # Skill Creator
13
18
 
14
19
  ## Purpose
15
20
  Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
16
21
 
17
- ## When To Use
22
+ ## Use This Skill When
18
23
  - User asks for a new OpenCore skill.
19
24
  - User asks to improve an installed skill.
20
25
  - Manager notices repeated work that should become a reusable skill.
@@ -43,12 +48,17 @@ Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
43
48
  triggers: ["run for days", "run for weeks", "continuous task", "long-running", "keep working", "autonomous for days"],
44
49
  builtin: true,
45
50
  },
46
- markdown: `# OpenCore Skill: Continuous Operations
51
+ markdown: `---
52
+ name: continuous-operations
53
+ description: Run multi-day or multi-week tasks safely by turning them into heartbeat steps, checkpoints, memory updates, and recoverable scheduled work.
54
+ ---
55
+
56
+ # Continuous Operations
47
57
 
48
58
  ## Purpose
49
59
  Handle long-running work that lasts for days or weeks without losing state, repeating completed work, or forgetting learned facts.
50
60
 
51
- ## When To Use
61
+ ## Use This Skill When
52
62
  - User asks OpenCore to keep working over a long period.
53
63
  - Task needs repeated checkpoints, follow-ups, monitoring, or staged execution.
54
64
  - Task must survive gaps between CLI sessions or scheduled runs.
@@ -88,12 +98,17 @@ Handle long-running work that lasts for days or weeks without losing state, repe
88
98
  triggers: ["build a dashboard", "build a tool", "add integration", "create UI", "build for yourself", "improve opencore"],
89
99
  builtin: true,
90
100
  },
91
- markdown: `# OpenCore Skill: Builder
101
+ markdown: `---
102
+ name: builder
103
+ description: Design and build new tools, dashboards, automations, integrations, and operator surfaces for OpenCore itself.
104
+ ---
105
+
106
+ # Builder
92
107
 
93
108
  ## Purpose
94
109
  Build or improve OpenCore-owned tools such as dashboards, helpers, integrations, automations, or local operator utilities.
95
110
 
96
- ## When To Use
111
+ ## Use This Skill When
97
112
  - User asks OpenCore to create a dashboard, agent utility, local service, or integration.
98
113
  - OpenCore needs a durable internal tool to reduce repeated manual work.
99
114
  - Existing OpenCore tooling is too weak for the requested workflow.
@@ -123,7 +138,12 @@ Build or improve OpenCore-owned tools such as dashboards, helpers, integrations,
123
138
  triggers: ["log in", "login", "sign up", "signup", "create account", "credentials", "password", "verification code"],
124
139
  builtin: true,
125
140
  },
126
- markdown: `# OpenCore Skill: Credential Operator
141
+ markdown: `---
142
+ name: credential-operator
143
+ description: Manage saved website credentials, default email settings, verification flows, and safe account creation/login behavior.
144
+ ---
145
+
146
+ # Credential Operator
127
147
 
128
148
  ## Purpose
129
149
  Use saved credentials safely, create new website accounts with the user's email when appropriate, handle email verification only when enabled, and save resulting credentials locally for reuse.
@@ -133,6 +153,11 @@ Use saved credentials safely, create new website accounts with the user's email
133
153
  - A default email and default email provider may also be available.
134
154
  - All credentials are local-only and should be reused instead of re-asking when already available.
135
155
 
156
+ ## Use This Skill When
157
+ - A task involves sign-in, sign-up, credentials, passwords, or verification codes.
158
+ - A site account needs to be created with the user's saved email context.
159
+ - OpenCore needs to reuse or store local credentials safely.
160
+
136
161
  ## Login Workflow
137
162
  1. Identify the website and determine whether a saved account already exists.
138
163
  2. Inspect the page to see which fields are required: email, username, password, one-time code, or extra profile fields.
@@ -181,11 +206,21 @@ CREDENTIAL_SAVE {"website":"example.com","email":"user@example.com","password":"
181
206
  category: "research",
182
207
  triggers: ["find", "research", "latest", "compare", "look up"],
183
208
  },
184
- markdown: `# OpenCore Skill: Web Research
209
+ markdown: `---
210
+ name: web-research
211
+ description: Plan targeted searches, verify sources, and return concise evidence-backed summaries.
212
+ ---
213
+
214
+ # Web Research
185
215
 
186
216
  ## Purpose
187
217
  Run structured web research tasks quickly and reliably.
188
218
 
219
+ ## Use This Skill When
220
+ - The task needs current web information.
221
+ - The task needs source-backed comparison or verification.
222
+ - The task needs concise findings with links and dates.
223
+
189
224
  ## Workflow
190
225
  1. Clarify the exact research goal and required output format.
191
226
  2. Check multiple reputable sources, prioritizing primary sources where possible.
@@ -207,11 +242,21 @@ Run structured web research tasks quickly and reliably.
207
242
  category: "productivity",
208
243
  triggers: ["schedule", "calendar", "plan", "reminder", "timeline"],
209
244
  },
210
- markdown: `# OpenCore Skill: Calendar Planner
245
+ markdown: `---
246
+ name: calendar-planner
247
+ description: Create and maintain realistic plans, reminders, checkpoints, and schedule-aware task breakdowns.
248
+ ---
249
+
250
+ # Calendar Planner
211
251
 
212
252
  ## Purpose
213
253
  Convert goals into realistic scheduled actions with explicit checkpoints.
214
254
 
255
+ ## Use This Skill When
256
+ - The task involves schedules, reminders, calendars, plans, or timelines.
257
+ - A large request needs realistic sequencing and timing.
258
+ - Future work should be turned into explicit checkpoints or scheduled actions.
259
+
215
260
  ## Workflow
216
261
  1. Extract deadlines, timing constraints, and fixed commitments.
217
262
  2. Break work into executable blocks with durations and dependencies.
@@ -233,11 +278,21 @@ Convert goals into realistic scheduled actions with explicit checkpoints.
233
278
  category: "filesystem",
234
279
  triggers: ["organize files", "cleanup", "rename", "folder structure", "sort downloads"],
235
280
  },
236
- markdown: `# OpenCore Skill: File Organizer
281
+ markdown: `---
282
+ name: file-organizer
283
+ description: Organize files and folders, normalize naming, and create clean local structures without unsafe churn.
284
+ ---
285
+
286
+ # File Organizer
237
287
 
238
288
  ## Purpose
239
289
  Create clean, predictable file layouts and naming standards.
240
290
 
291
+ ## Use This Skill When
292
+ - The user asks to organize files or folders.
293
+ - Downloads, workspaces, or local directories need cleanup.
294
+ - A naming convention or folder structure needs to be applied safely.
295
+
241
296
  ## Workflow
242
297
  1. Inspect the current folder structure and identify patterns.
243
298
  2. Define the target structure and naming convention.
@@ -259,11 +314,21 @@ Create clean, predictable file layouts and naming standards.
259
314
  category: "engineering",
260
315
  triggers: ["bug", "feature", "refactor", "test", "build", "fix code"],
261
316
  },
262
- markdown: `# OpenCore Skill: Code Assistant
317
+ markdown: `---
318
+ name: code-assistant
319
+ description: Handle coding tasks with implementation, validation, and concise change summaries.
320
+ ---
321
+
322
+ # Code Assistant
263
323
 
264
324
  ## Purpose
265
325
  Deliver production-quality code changes with minimal regressions.
266
326
 
327
+ ## Use This Skill When
328
+ - The task is a bug fix, feature, refactor, test, or build issue.
329
+ - The user expects code changes, validation, and a concise summary.
330
+ - OpenCore needs to modify local code rather than only describe a solution.
331
+
267
332
  ## Workflow
268
333
  1. Reproduce or isolate the issue.
269
334
  2. Edit the smallest correct surface area.
@@ -285,11 +350,21 @@ Deliver production-quality code changes with minimal regressions.
285
350
  category: "communication",
286
351
  triggers: ["email", "reply", "message", "draft", "write a note"],
287
352
  },
288
- markdown: `# OpenCore Skill: Communication Drafts
353
+ markdown: `---
354
+ name: communication-drafts
355
+ description: Draft polished emails and messages with clear tone, intent, and next-step asks.
356
+ ---
357
+
358
+ # Communication Drafts
289
359
 
290
360
  ## Purpose
291
361
  Write concise, audience-appropriate communication drafts.
292
362
 
363
+ ## Use This Skill When
364
+ - The task is to draft an email, message, note, or reply.
365
+ - Tone, clarity, and next steps matter.
366
+ - The user needs a polished communication artifact, not raw bullets.
367
+
293
368
  ## Workflow
294
369
  1. Identify the audience, objective, and preferred tone.
295
370
  2. Draft the message with a clear opening, body, and call to action.