@opencoreai/opencore 0.4.0 → 0.4.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/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/scripts/postinstall.mjs +27 -3
- package/src/dashboard-server.ts +85 -10
- package/src/index.ts +25 -1
- package/src/opencore-indicator.m +4 -4
- package/src/skill-catalog.mjs +2 -2
- package/templates/default-guidelines.md +3 -1
- package/templates/default-instructions.md +3 -2
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.2",
|
|
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/scripts/postinstall.mjs
CHANGED
|
@@ -16,6 +16,9 @@ const __dirname = path.dirname(__filename);
|
|
|
16
16
|
const ROOT_DIR = path.resolve(__dirname, "..");
|
|
17
17
|
const TEMPLATE_DIR = path.join(ROOT_DIR, "templates");
|
|
18
18
|
const OPENCORE_HOME = path.join(os.homedir(), ".opencore");
|
|
19
|
+
const AGENTS_DIR = path.join(OPENCORE_HOME, ".agents");
|
|
20
|
+
const SKILLS_DIR = path.join(AGENTS_DIR, "skills");
|
|
21
|
+
const LEGACY_SKILLS_DIR = path.join(OPENCORE_HOME, "skills");
|
|
19
22
|
const SETTINGS_PATH = path.join(OPENCORE_HOME, "configs", "settings.json");
|
|
20
23
|
const GUIDELINES_PATH = path.join(OPENCORE_HOME, "guidelines.md");
|
|
21
24
|
const INSTRUCTIONS_PATH = path.join(OPENCORE_HOME, "instructions.md");
|
|
@@ -26,10 +29,11 @@ const PROFILE_SECTION_START = "<!-- OPENCORE_INSTALL_PROFILE_START -->";
|
|
|
26
29
|
const PROFILE_SECTION_END = "<!-- OPENCORE_INSTALL_PROFILE_END -->";
|
|
27
30
|
const DIRECTORIES = [
|
|
28
31
|
OPENCORE_HOME,
|
|
32
|
+
AGENTS_DIR,
|
|
29
33
|
path.join(OPENCORE_HOME, "configs"),
|
|
30
34
|
path.join(OPENCORE_HOME, "logs"),
|
|
31
35
|
path.join(OPENCORE_HOME, "cache"),
|
|
32
|
-
|
|
36
|
+
SKILLS_DIR,
|
|
33
37
|
];
|
|
34
38
|
const execFileAsync = promisify(execFile);
|
|
35
39
|
|
|
@@ -213,13 +217,13 @@ async function configureTelegram(currentSettings) {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
async function installSelectedSkills(skillIds) {
|
|
216
|
-
await ensureDir(
|
|
220
|
+
await ensureDir(SKILLS_DIR);
|
|
217
221
|
const installed = [];
|
|
218
222
|
const skipped = [];
|
|
219
223
|
for (const id of skillIds) {
|
|
220
224
|
const skill = SKILL_CATALOG.find((item) => item.id === id);
|
|
221
225
|
if (!skill) continue;
|
|
222
|
-
const dir = path.join(
|
|
226
|
+
const dir = path.join(SKILLS_DIR, skill.id);
|
|
223
227
|
const skillFile = path.join(dir, "SKILL.md");
|
|
224
228
|
const configFile = path.join(dir, "config.json");
|
|
225
229
|
try {
|
|
@@ -248,6 +252,25 @@ async function installSelectedSkills(skillIds) {
|
|
|
248
252
|
return { installed, skipped };
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
async function migrateLegacySkillsDir() {
|
|
256
|
+
try {
|
|
257
|
+
const entries = await fs.readdir(LEGACY_SKILLS_DIR, { withFileTypes: true });
|
|
258
|
+
await ensureDir(SKILLS_DIR);
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
if (!entry.isDirectory()) continue;
|
|
261
|
+
const fromDir = path.join(LEGACY_SKILLS_DIR, entry.name);
|
|
262
|
+
const toDir = path.join(SKILLS_DIR, entry.name);
|
|
263
|
+
try {
|
|
264
|
+
await fs.access(toDir);
|
|
265
|
+
continue;
|
|
266
|
+
} catch {}
|
|
267
|
+
await fs.rename(fromDir, toDir).catch(async () => {
|
|
268
|
+
await fs.cp(fromDir, toDir, { recursive: true, force: false });
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
} catch {}
|
|
272
|
+
}
|
|
273
|
+
|
|
251
274
|
async function promptSkillInstallation() {
|
|
252
275
|
if (!(input.isTTY && output.isTTY)) return;
|
|
253
276
|
const choices = [
|
|
@@ -407,6 +430,7 @@ async function run() {
|
|
|
407
430
|
await ensureTemplateApplied(GUIDELINES_PATH, defaultGuidelines, ["# OpenCore Guidelines\n"]);
|
|
408
431
|
await ensureTemplateApplied(INSTRUCTIONS_PATH, defaultInstructions, ["# OpenCore Instructions\n"]);
|
|
409
432
|
await ensureFile(SETTINGS_PATH, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`);
|
|
433
|
+
await migrateLegacySkillsDir();
|
|
410
434
|
await ensureCredentialStore();
|
|
411
435
|
await ensureDefaultSkillsInstalled();
|
|
412
436
|
|
package/src/dashboard-server.ts
CHANGED
|
@@ -61,7 +61,7 @@ export class DashboardServer {
|
|
|
61
61
|
this.options = options;
|
|
62
62
|
this.app.disable("x-powered-by");
|
|
63
63
|
this.dashboardDir = path.join(options.rootDir, "opencore dashboard");
|
|
64
|
-
this.skillsDir = path.join(options.openCoreHome, "skills");
|
|
64
|
+
this.skillsDir = path.join(options.openCoreHome, ".agents", "skills");
|
|
65
65
|
this.schedulesPath = path.join(options.openCoreHome, "configs", "schedules.json");
|
|
66
66
|
this.chatLogPath = path.join(options.openCoreHome, "chat-history.json");
|
|
67
67
|
this.vendorMap = {
|
|
@@ -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) => {
|
package/src/index.ts
CHANGED
|
@@ -43,7 +43,9 @@ const INSTRUCTIONS_PATH = path.join(OPENCORE_HOME, "instructions.md");
|
|
|
43
43
|
const SETTINGS_PATH = path.join(OPENCORE_HOME, "configs", "settings.json");
|
|
44
44
|
const SCHEDULES_PATH = path.join(OPENCORE_HOME, "configs", "schedules.json");
|
|
45
45
|
const SCREENSHOT_DIR = path.join(OPENCORE_HOME, "screenshots");
|
|
46
|
-
const
|
|
46
|
+
const AGENTS_DIR = path.join(OPENCORE_HOME, ".agents");
|
|
47
|
+
const SKILLS_DIR = path.join(AGENTS_DIR, "skills");
|
|
48
|
+
const LEGACY_SKILLS_DIR = path.join(OPENCORE_HOME, "skills");
|
|
47
49
|
const INDICATOR_STATE_PATH = path.join(OPENCORE_HOME, "indicator-state.json");
|
|
48
50
|
const SCHEDULER_LOG_PATH = path.join(OPENCORE_HOME, "logs", "scheduler.log");
|
|
49
51
|
const DASHBOARD_PORT = Number(process.env.OPENCORE_DASHBOARD_PORT || 4111);
|
|
@@ -200,6 +202,7 @@ async function chmodIfPossible(targetPath: string, mode: number) {
|
|
|
200
202
|
async function enforceOpenCorePermissions() {
|
|
201
203
|
const dirs = [
|
|
202
204
|
OPENCORE_HOME,
|
|
205
|
+
AGENTS_DIR,
|
|
203
206
|
path.join(OPENCORE_HOME, "configs"),
|
|
204
207
|
path.join(OPENCORE_HOME, "logs"),
|
|
205
208
|
path.join(OPENCORE_HOME, "cache"),
|
|
@@ -344,6 +347,7 @@ async function ensureOpenCoreHome() {
|
|
|
344
347
|
await fs.mkdir(path.join(OPENCORE_HOME, "configs"), { recursive: true });
|
|
345
348
|
await fs.mkdir(path.join(OPENCORE_HOME, "logs"), { recursive: true });
|
|
346
349
|
await fs.mkdir(path.join(OPENCORE_HOME, "cache"), { recursive: true });
|
|
350
|
+
await fs.mkdir(AGENTS_DIR, { recursive: true });
|
|
347
351
|
await fs.mkdir(SCREENSHOT_DIR, { recursive: true });
|
|
348
352
|
await fs.mkdir(SKILLS_DIR, { recursive: true });
|
|
349
353
|
|
|
@@ -394,10 +398,30 @@ async function ensureOpenCoreHome() {
|
|
|
394
398
|
await fs.writeFile(SCHEDULES_PATH, "[]\n", "utf8");
|
|
395
399
|
}
|
|
396
400
|
await ensureDefaultOpenCoreSkills();
|
|
401
|
+
await migrateLegacySkillsDir();
|
|
397
402
|
await ensureCredentialStore();
|
|
398
403
|
await enforceOpenCorePermissions();
|
|
399
404
|
}
|
|
400
405
|
|
|
406
|
+
async function migrateLegacySkillsDir() {
|
|
407
|
+
try {
|
|
408
|
+
const entries = await fs.readdir(LEGACY_SKILLS_DIR, { withFileTypes: true });
|
|
409
|
+
await fs.mkdir(SKILLS_DIR, { recursive: true });
|
|
410
|
+
for (const entry of entries) {
|
|
411
|
+
if (!entry.isDirectory()) continue;
|
|
412
|
+
const fromDir = path.join(LEGACY_SKILLS_DIR, entry.name);
|
|
413
|
+
const toDir = path.join(SKILLS_DIR, entry.name);
|
|
414
|
+
try {
|
|
415
|
+
await fs.access(toDir);
|
|
416
|
+
continue;
|
|
417
|
+
} catch {}
|
|
418
|
+
await fs.rename(fromDir, toDir).catch(async () => {
|
|
419
|
+
await fs.cp(fromDir, toDir, { recursive: true, force: false });
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
} catch {}
|
|
423
|
+
}
|
|
424
|
+
|
|
401
425
|
async function ensureDefaultOpenCoreSkills() {
|
|
402
426
|
for (const skill of DEFAULT_SYSTEM_SKILLS) {
|
|
403
427
|
const dir = path.join(SKILLS_DIR, skill.id);
|
package/src/opencore-indicator.m
CHANGED
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
|
|
126
126
|
if (self.statusGlowLayer == nil) {
|
|
127
127
|
self.statusGlowLayer = [CALayer layer];
|
|
128
|
-
self.statusGlowLayer.cornerRadius =
|
|
128
|
+
self.statusGlowLayer.cornerRadius = 10.0;
|
|
129
129
|
self.statusGlowLayer.shadowOpacity = 0.34f;
|
|
130
130
|
self.statusGlowLayer.shadowRadius = 10.0f;
|
|
131
131
|
self.statusGlowLayer.shadowOffset = CGSizeMake(0, 0);
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
|
|
136
136
|
if (self.statusPillLayer == nil) {
|
|
137
137
|
self.statusPillLayer = [CAGradientLayer layer];
|
|
138
|
-
self.statusPillLayer.cornerRadius =
|
|
138
|
+
self.statusPillLayer.cornerRadius = 10.0;
|
|
139
139
|
self.statusPillLayer.borderWidth = 1.0;
|
|
140
140
|
self.statusPillLayer.startPoint = CGPointMake(0.0, 0.5);
|
|
141
141
|
self.statusPillLayer.endPoint = CGPointMake(1.0, 0.5);
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
};
|
|
211
211
|
CGFloat textWidth = ceil([title sizeWithAttributes:attributes].width);
|
|
212
212
|
CGFloat iconWidth = button.image != nil ? 18.0 : 0.0;
|
|
213
|
-
CGFloat totalWidth = textWidth + iconWidth +
|
|
213
|
+
CGFloat totalWidth = textWidth + iconWidth + 24.0;
|
|
214
214
|
CGFloat width = MAX(122.0, totalWidth);
|
|
215
215
|
CGFloat height = 24.0;
|
|
216
216
|
CGFloat x = floor((NSWidth(button.bounds) - width) / 2.0);
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
self.statusPillLayer.borderColor = [[self.orangeBadgeBorderColor colorWithAlphaComponent:(active ? (self.hoverActive ? 1.0 : 0.85) : (self.hoverActive ? 0.86 : 0.60))] CGColor];
|
|
232
232
|
|
|
233
233
|
self.statusShineLayer.frame = CGRectMake(8.0, 2.0, width - 16.0, height * 0.48);
|
|
234
|
-
self.statusShineLayer.cornerRadius =
|
|
234
|
+
self.statusShineLayer.cornerRadius = 8.0;
|
|
235
235
|
self.statusShineLayer.opacity = active ? (self.hoverActive ? 1.0f : 0.95f) : (self.hoverActive ? 0.84f : 0.62f);
|
|
236
236
|
}
|
|
237
237
|
|
package/src/skill-catalog.mjs
CHANGED
|
@@ -17,7 +17,7 @@ description: Create or update OpenCore skills with focused triggers, reusable in
|
|
|
17
17
|
# Skill Creator
|
|
18
18
|
|
|
19
19
|
## Purpose
|
|
20
|
-
Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
|
|
20
|
+
Create, repair, or improve reusable OpenCore skills under ~/.opencore/.agents/skills.
|
|
21
21
|
|
|
22
22
|
## Use This Skill When
|
|
23
23
|
- User asks for a new OpenCore skill.
|
|
@@ -26,7 +26,7 @@ Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
|
|
|
26
26
|
|
|
27
27
|
## Workflow
|
|
28
28
|
1. Define the skill purpose, trigger phrases, and expected outcome.
|
|
29
|
-
2. Create or update ~/.opencore/skills/<skill-id>/SKILL.md with concise operating instructions.
|
|
29
|
+
2. Create or update ~/.opencore/.agents/skills/<skill-id>/SKILL.md with concise operating instructions.
|
|
30
30
|
3. Create or update config.json with id, name, description, version, category, and triggers.
|
|
31
31
|
4. Keep the skill narrow enough to be reliable, but complete enough to be reusable.
|
|
32
32
|
5. Summarize how the skill should be triggered and what it changes.
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
- Computer Agent must not make independent policy decisions outside delegated scope.
|
|
16
16
|
|
|
17
17
|
## Skills Rules
|
|
18
|
-
- OpenCore
|
|
18
|
+
- OpenCore agent assets are stored under `~/.opencore/.agents`.
|
|
19
|
+
- OpenCore skills are stored under `~/.opencore/.agents/skills`.
|
|
19
20
|
- `skill-creator` is a built-in OpenCore skill and should exist by default.
|
|
20
21
|
- `continuous-operations` is a built-in OpenCore skill and should exist by default.
|
|
21
22
|
- `builder` is a built-in OpenCore skill and should exist by default.
|
|
22
23
|
- `credential-operator` is a built-in OpenCore skill and should exist by default.
|
|
23
24
|
- Manager Agent must check installed skills before planning and execution.
|
|
25
|
+
- Dashboard must also read installed skills from `~/.opencore/.agents/skills`.
|
|
24
26
|
- If a relevant skill exists, Manager Agent should follow it.
|
|
25
27
|
- If no relevant skill exists, proceed with default behavior.
|
|
26
28
|
- Skills do not override core safety rules in this file.
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
05. Computer Agent is only for macOS UI control tasks.
|
|
9
9
|
06. If no UI control is needed, Manager should answer directly.
|
|
10
10
|
07. If user asks to edit soul/memory/guidelines/instructions, Manager should edit locally.
|
|
11
|
-
07.1 Manager should also handle heartbeat.md and skills content under ~/.opencore/skills.
|
|
11
|
+
07.1 Manager should also handle heartbeat.md and skills content under ~/.opencore/.agents/skills.
|
|
12
12
|
08. Manager should delegate to Computer Agent only when interaction with apps/windows is required.
|
|
13
13
|
08.1 For future-time or recurring requests, Manager should prefer cron-backed scheduling when possible.
|
|
14
14
|
09. Manager should keep responses concise and operational.
|
|
15
15
|
10. Manager should follow soul.md, guidelines.md, instructions.md, memory.md, and config context.
|
|
16
16
|
10.05 Manager should also use computer-profile.md for durable machine facts such as installed apps, default browser, workspace, hardware profile, and permission-related environment facts.
|
|
17
|
-
10.1 Manager should inspect and follow installed OpenCore skills in ~/.opencore/skills when relevant.
|
|
17
|
+
10.1 Manager should inspect and follow installed OpenCore skills in ~/.opencore/.agents/skills when relevant.
|
|
18
|
+
10.15 Dashboard should read and display installed skills from ~/.opencore/.agents/skills.
|
|
18
19
|
10.2 Built-in skills for skill creation, continuous operations, and internal tool building should be treated as always available if installed.
|
|
19
20
|
10.3 The credential-operator skill should be treated as a built-in subsystem for login, sign-up, password reuse, verification handling, and saving credentials locally.
|
|
20
21
|
11. Computer Agent should follow the same policy context while executing UI actions.
|