@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.
@@ -0,0 +1,923 @@
1
+ (function () {
2
+ const e = React.createElement;
3
+ const { useEffect, useMemo, useState } = React;
4
+
5
+ const PAGES = [
6
+ { id: "chat", label: "Mission Chat", eyebrow: "Control", blurb: "Live conversation with OpenCore and real-time task flow." },
7
+ { id: "credentials", label: "Credentials", eyebrow: "Identity Access", blurb: "Store website logins, default email settings, and email-activation policy locally." },
8
+ { id: "telegram", label: "Telegram", eyebrow: "Remote Control", blurb: "Connect Telegram, update bot settings, and monitor link status." },
9
+ { id: "skills", label: "Skills", eyebrow: "Capability", blurb: "Installed and available OpenCore skills." },
10
+ { id: "soul", label: "soul.md", eyebrow: "Identity", blurb: "Core identity, posture, and persona memory." },
11
+ { id: "memory", label: "memory.md", eyebrow: "Recall", blurb: "Durable machine facts, lessons, and task history." },
12
+ { id: "heartbeat", label: "heartbeat.md", eyebrow: "Execution", blurb: "Current plan plus scheduled-task tracking." },
13
+ { id: "guidelines", label: "guidelines.md", eyebrow: "Guardrails", blurb: "Persistent safety and permission boundaries." },
14
+ { id: "instructions", label: "instructions.md", eyebrow: "Operating Mode", blurb: "Workflow preferences and execution style." },
15
+ { id: "screenshots", label: "Screenshots", eyebrow: "Evidence", blurb: "Local screenshot archive captured during tasks." },
16
+ ];
17
+
18
+ function fmtTs(ts) {
19
+ try {
20
+ return new Date(ts).toLocaleString();
21
+ } catch {
22
+ return ts || "";
23
+ }
24
+ }
25
+
26
+ function StatCard({ label, value, tone }) {
27
+ return e("div", { className: `stat-card ${tone || ""}`.trim() }, [
28
+ e("div", { className: "stat-label", key: "l" }, label),
29
+ e("div", { className: "stat-value", key: "v" }, value),
30
+ ]);
31
+ }
32
+
33
+ function App() {
34
+ const initialTheme =
35
+ (() => {
36
+ try {
37
+ return localStorage.getItem("opencore-theme") || "light";
38
+ } catch {
39
+ return "light";
40
+ }
41
+ })();
42
+ const [page, setPage] = useState("chat");
43
+ const [status, setStatus] = useState("Connecting...");
44
+ const [chat, setChat] = useState([]);
45
+ const [chatText, setChatText] = useState("");
46
+ const [chatBusy, setChatBusy] = useState(false);
47
+ const [fileContent, setFileContent] = useState("");
48
+ const [fileBusy, setFileBusy] = useState(false);
49
+ const [shots, setShots] = useState([]);
50
+ const [skills, setSkills] = useState([]);
51
+ const [skillBusy, setSkillBusy] = useState({});
52
+ const [expandedSkills, setExpandedSkills] = useState({});
53
+ const [drawerOpen, setDrawerOpen] = useState(true);
54
+ const [liveEvent, setLiveEvent] = useState("Waiting for activity");
55
+ const [theme, setTheme] = useState(initialTheme === "dark" ? "dark" : "light");
56
+ const [telegramConfig, setTelegramConfig] = useState({
57
+ enabled: false,
58
+ chat_id: "",
59
+ user_id: "",
60
+ bot_token: "",
61
+ pairing_code: "",
62
+ paired: false,
63
+ has_bot_token: false,
64
+ has_pairing_code: false,
65
+ last_update_id: 0,
66
+ });
67
+ const [telegramBusy, setTelegramBusy] = useState(false);
68
+ const [credentialDefaults, setCredentialDefaults] = useState({
69
+ default_email: "",
70
+ default_email_provider: "",
71
+ auto_email_activation_enabled: false,
72
+ });
73
+ const [credentialEntries, setCredentialEntries] = useState([]);
74
+ const [credentialForm, setCredentialForm] = useState({
75
+ id: "",
76
+ website: "",
77
+ email: "",
78
+ password: "",
79
+ email_provider: "",
80
+ notes: "",
81
+ });
82
+ const [credentialsBusy, setCredentialsBusy] = useState(false);
83
+
84
+ async function loadChat() {
85
+ const res = await fetch("/api/chat");
86
+ const json = await res.json();
87
+ setChat(json.messages || []);
88
+ }
89
+
90
+ async function loadFile(name) {
91
+ const res = await fetch(`/api/files/${name}`);
92
+ const json = await res.json();
93
+ setFileContent(json.content || "");
94
+ }
95
+
96
+ async function loadShots() {
97
+ const res = await fetch("/api/screenshots");
98
+ const json = await res.json();
99
+ setShots(json.items || []);
100
+ }
101
+
102
+ async function loadSkills() {
103
+ const res = await fetch("/api/skills");
104
+ const json = await res.json();
105
+ setSkills(json.items || []);
106
+ }
107
+
108
+ async function loadTelegram() {
109
+ const res = await fetch("/api/telegram");
110
+ const json = await res.json();
111
+ setTelegramConfig({
112
+ enabled: Boolean(json.enabled),
113
+ chat_id: json.chat_id || "",
114
+ user_id: json.user_id || "",
115
+ bot_token: "",
116
+ pairing_code: "",
117
+ paired: Boolean(json.paired),
118
+ has_bot_token: Boolean(json.has_bot_token),
119
+ has_pairing_code: Boolean(json.has_pairing_code),
120
+ last_update_id: Number(json.last_update_id || 0),
121
+ });
122
+ }
123
+
124
+ async function loadCredentials() {
125
+ const res = await fetch("/api/credentials");
126
+ const json = await res.json();
127
+ setCredentialDefaults({
128
+ default_email: json.default_email || "",
129
+ default_email_provider: json.default_email_provider || "",
130
+ auto_email_activation_enabled: Boolean(json.auto_email_activation_enabled),
131
+ });
132
+ setCredentialEntries(Array.isArray(json.entries) ? json.entries : []);
133
+ }
134
+
135
+ useEffect(() => {
136
+ try {
137
+ document.documentElement.setAttribute("data-theme", theme);
138
+ localStorage.setItem("opencore-theme", theme);
139
+ } catch {}
140
+ }, [theme]);
141
+
142
+ useEffect(() => {
143
+ Promise.all([loadChat(), loadShots(), loadSkills(), loadTelegram(), loadCredentials()])
144
+ .then(() => setStatus("Online"))
145
+ .catch(() => setStatus("Connection issue"));
146
+
147
+ const proto = location.protocol === "https:" ? "wss" : "ws";
148
+ const ws = new WebSocket(`${proto}://${location.host}/ws`);
149
+ ws.onmessage = (ev) => {
150
+ try {
151
+ const packet = JSON.parse(ev.data);
152
+ if (packet.type === "chat_message" && packet.payload) {
153
+ setChat((prev) => [...prev, packet.payload].slice(-1000));
154
+ }
155
+ if (packet.type === "event" && packet.payload) {
156
+ const evt = packet.payload;
157
+ if (evt.type === "screenshot") {
158
+ setLiveEvent(`Screenshot captured · step ${evt.step || "?"}`);
159
+ loadShots();
160
+ } else if (evt.type === "task_started") {
161
+ setLiveEvent("Task running");
162
+ } else if (evt.type === "task_completed") {
163
+ setLiveEvent("Task completed");
164
+ } else if (evt.type === "task_failed") {
165
+ setLiveEvent("Task failed");
166
+ } else if (evt.type === "file_updated") {
167
+ setLiveEvent(`Updated ${String(evt.target || "").split("/").pop() || "file"}`);
168
+ } else if (evt.type === "action") {
169
+ setLiveEvent(`Action · ${evt.action || "working"}`);
170
+ }
171
+ }
172
+ if (packet.type === "skill_installed") {
173
+ setLiveEvent(`Installed skill · ${packet.payload?.id || "unknown"}`);
174
+ loadSkills();
175
+ }
176
+ if (packet.type === "telegram_config_updated" && packet.payload) {
177
+ setTelegramConfig({
178
+ enabled: Boolean(packet.payload.enabled),
179
+ chat_id: packet.payload.chat_id || "",
180
+ user_id: packet.payload.user_id || "",
181
+ bot_token: "",
182
+ pairing_code: "",
183
+ paired: Boolean(packet.payload.paired),
184
+ has_bot_token: Boolean(packet.payload.has_bot_token),
185
+ has_pairing_code: Boolean(packet.payload.has_pairing_code),
186
+ last_update_id: Number(packet.payload.last_update_id || 0),
187
+ });
188
+ setLiveEvent("Telegram config updated");
189
+ }
190
+ if (packet.type === "credentials_updated" && packet.payload) {
191
+ setCredentialDefaults({
192
+ default_email: packet.payload.default_email || "",
193
+ default_email_provider: packet.payload.default_email_provider || "",
194
+ auto_email_activation_enabled: Boolean(packet.payload.auto_email_activation_enabled),
195
+ });
196
+ setCredentialEntries(Array.isArray(packet.payload.entries) ? packet.payload.entries : []);
197
+ setLiveEvent("Credentials updated");
198
+ }
199
+ } catch {}
200
+ };
201
+ ws.onopen = () => setStatus("Online");
202
+ ws.onclose = () => setStatus("Offline");
203
+ return () => ws.close();
204
+ }, []);
205
+
206
+ useEffect(() => {
207
+ if (["soul", "memory", "guidelines", "instructions", "heartbeat"].includes(page)) {
208
+ loadFile(page);
209
+ }
210
+ if (page === "screenshots") {
211
+ loadShots();
212
+ }
213
+ if (page === "skills") {
214
+ loadSkills();
215
+ }
216
+ if (page === "credentials") {
217
+ loadCredentials();
218
+ }
219
+ if (page === "telegram") {
220
+ loadTelegram();
221
+ }
222
+ }, [page]);
223
+
224
+ async function sendChat() {
225
+ const text = chatText.trim();
226
+ if (!text || chatBusy) return;
227
+ setChatBusy(true);
228
+ try {
229
+ await fetch("/api/chat", {
230
+ method: "POST",
231
+ headers: { "content-type": "application/json" },
232
+ body: JSON.stringify({ text }),
233
+ });
234
+ setChatText("");
235
+ } finally {
236
+ setChatBusy(false);
237
+ }
238
+ }
239
+
240
+ function onComposerKeyDown(ev) {
241
+ if (ev.key === "Enter" && !ev.shiftKey) {
242
+ ev.preventDefault();
243
+ sendChat();
244
+ }
245
+ }
246
+
247
+ async function saveCurrentFile() {
248
+ if (!["soul", "memory", "guidelines", "instructions", "heartbeat"].includes(page)) return;
249
+ setFileBusy(true);
250
+ try {
251
+ await fetch(`/api/files/${page}`, {
252
+ method: "PUT",
253
+ headers: { "content-type": "application/json" },
254
+ body: JSON.stringify({ content: fileContent }),
255
+ });
256
+ setLiveEvent(`Saved ${page}.md`);
257
+ } finally {
258
+ setFileBusy(false);
259
+ }
260
+ }
261
+
262
+ async function installSkill(id) {
263
+ if (!id || skillBusy[id]) return;
264
+ setSkillBusy((prev) => ({ ...prev, [id]: true }));
265
+ try {
266
+ await fetch("/api/skills/install", {
267
+ method: "POST",
268
+ headers: { "content-type": "application/json" },
269
+ body: JSON.stringify({ id }),
270
+ });
271
+ await loadSkills();
272
+ } finally {
273
+ setSkillBusy((prev) => ({ ...prev, [id]: false }));
274
+ }
275
+ }
276
+
277
+ function toggleSkillExpanded(id) {
278
+ setExpandedSkills((prev) => ({ ...prev, [id]: !prev[id] }));
279
+ }
280
+
281
+ async function saveTelegramConfig() {
282
+ setTelegramBusy(true);
283
+ try {
284
+ const res = await fetch("/api/telegram", {
285
+ method: "PUT",
286
+ headers: { "content-type": "application/json" },
287
+ body: JSON.stringify({
288
+ enabled: telegramConfig.enabled,
289
+ chat_id: telegramConfig.chat_id,
290
+ user_id: telegramConfig.user_id,
291
+ bot_token: telegramConfig.bot_token,
292
+ pairing_code: telegramConfig.pairing_code,
293
+ }),
294
+ });
295
+ const json = await res.json();
296
+ setTelegramConfig({
297
+ enabled: Boolean(json.enabled),
298
+ chat_id: json.chat_id || "",
299
+ user_id: json.user_id || "",
300
+ bot_token: "",
301
+ pairing_code: "",
302
+ paired: Boolean(json.paired),
303
+ has_bot_token: Boolean(json.has_bot_token),
304
+ has_pairing_code: Boolean(json.has_pairing_code),
305
+ last_update_id: Number(json.last_update_id || 0),
306
+ });
307
+ setLiveEvent("Saved Telegram settings");
308
+ } finally {
309
+ setTelegramBusy(false);
310
+ }
311
+ }
312
+
313
+ async function saveCredentialDefaults() {
314
+ setCredentialsBusy(true);
315
+ try {
316
+ const res = await fetch("/api/credentials/defaults", {
317
+ method: "PUT",
318
+ headers: { "content-type": "application/json" },
319
+ body: JSON.stringify(credentialDefaults),
320
+ });
321
+ const json = await res.json();
322
+ setCredentialDefaults({
323
+ default_email: json.default_email || "",
324
+ default_email_provider: json.default_email_provider || "",
325
+ auto_email_activation_enabled: Boolean(json.auto_email_activation_enabled),
326
+ });
327
+ setCredentialEntries(Array.isArray(json.entries) ? json.entries : []);
328
+ setLiveEvent("Credential defaults saved");
329
+ } finally {
330
+ setCredentialsBusy(false);
331
+ }
332
+ }
333
+
334
+ async function saveCredentialEntry() {
335
+ if (!credentialForm.website.trim()) return;
336
+ setCredentialsBusy(true);
337
+ try {
338
+ const res = await fetch("/api/credentials/upsert", {
339
+ method: "POST",
340
+ headers: { "content-type": "application/json" },
341
+ body: JSON.stringify(credentialForm),
342
+ });
343
+ const json = await res.json();
344
+ setCredentialEntries(Array.isArray(json.entries) ? json.entries : []);
345
+ setCredentialDefaults({
346
+ default_email: json.default_email || "",
347
+ default_email_provider: json.default_email_provider || "",
348
+ auto_email_activation_enabled: Boolean(json.auto_email_activation_enabled),
349
+ });
350
+ setCredentialForm({
351
+ id: "",
352
+ website: "",
353
+ email: "",
354
+ password: "",
355
+ email_provider: "",
356
+ notes: "",
357
+ });
358
+ setLiveEvent("Credential saved");
359
+ } finally {
360
+ setCredentialsBusy(false);
361
+ }
362
+ }
363
+
364
+ async function deleteCredential(id) {
365
+ if (!id) return;
366
+ setCredentialsBusy(true);
367
+ try {
368
+ const res = await fetch("/api/credentials/delete", {
369
+ method: "POST",
370
+ headers: { "content-type": "application/json" },
371
+ body: JSON.stringify({ id }),
372
+ });
373
+ const json = await res.json();
374
+ setCredentialEntries(Array.isArray(json.entries) ? json.entries : []);
375
+ setCredentialDefaults({
376
+ default_email: json.default_email || "",
377
+ default_email_provider: json.default_email_provider || "",
378
+ auto_email_activation_enabled: Boolean(json.auto_email_activation_enabled),
379
+ });
380
+ if (credentialForm.id === id) {
381
+ setCredentialForm({
382
+ id: "",
383
+ website: "",
384
+ email: "",
385
+ password: "",
386
+ email_provider: "",
387
+ notes: "",
388
+ });
389
+ }
390
+ setLiveEvent("Credential removed");
391
+ } finally {
392
+ setCredentialsBusy(false);
393
+ }
394
+ }
395
+
396
+ function beginCredentialEdit(entry) {
397
+ setCredentialForm({
398
+ id: entry.id || "",
399
+ website: entry.website || "",
400
+ email: entry.email || "",
401
+ password: entry.password || "",
402
+ email_provider: entry.email_provider || "",
403
+ notes: entry.notes || "",
404
+ });
405
+ }
406
+
407
+ async function disconnectTelegram() {
408
+ setTelegramBusy(true);
409
+ try {
410
+ const res = await fetch("/api/telegram/disconnect", { method: "POST" });
411
+ const json = await res.json();
412
+ setTelegramConfig({
413
+ enabled: Boolean(json.enabled),
414
+ chat_id: json.chat_id || "",
415
+ user_id: json.user_id || "",
416
+ bot_token: "",
417
+ pairing_code: "",
418
+ paired: Boolean(json.paired),
419
+ has_bot_token: Boolean(json.has_bot_token),
420
+ has_pairing_code: Boolean(json.has_pairing_code),
421
+ last_update_id: Number(json.last_update_id || 0),
422
+ });
423
+ setLiveEvent("Telegram disconnected");
424
+ } finally {
425
+ setTelegramBusy(false);
426
+ }
427
+ }
428
+
429
+ const currentPage = PAGES.find((p) => p.id === page) || PAGES[0];
430
+ const installedCount = skills.filter((s) => s.installed).length;
431
+ const latestChat = chat.length ? chat[chat.length - 1] : null;
432
+
433
+ const stats = useMemo(
434
+ () => [
435
+ { label: "Connection", value: status, tone: status === "Online" ? "good" : "warn" },
436
+ { label: "Installed Skills", value: String(installedCount), tone: "accent" },
437
+ { label: "Screenshots", value: String(shots.length), tone: "neutral" },
438
+ { label: "Live State", value: liveEvent, tone: "neutral wide" },
439
+ ],
440
+ [installedCount, liveEvent, shots.length, status],
441
+ );
442
+
443
+ function renderChatPage() {
444
+ return e("div", { className: "content-body stacked" }, [
445
+ e("div", { className: "section-head", key: "head" }, [
446
+ e("div", { className: "section-title", key: "title" }, "Conversation Stream"),
447
+ e("div", { className: "section-meta", key: "meta" }, latestChat ? `Last message · ${fmtTs(latestChat.ts)}` : "No messages yet"),
448
+ ]),
449
+ e(
450
+ "div",
451
+ { className: "chat-list", key: "list" },
452
+ chat.map((m) =>
453
+ e("article", { className: `msg ${m.role}`, key: m.id || `${m.ts}-${Math.random()}` }, [
454
+ e("div", { className: "msg-bubble", key: "bubble" }, [
455
+ e("div", { className: "meta", key: "m" }, [
456
+ e("span", { className: "role-badge", key: "r" }, m.role),
457
+ e("span", { key: "s" }, m.source),
458
+ e("span", { key: "t" }, fmtTs(m.ts)),
459
+ ]),
460
+ e("div", { className: "msg-text", key: "t" }, m.text),
461
+ ]),
462
+ ]),
463
+ ),
464
+ ),
465
+ e("div", { className: "composer-shell", key: "c" }, [
466
+ e("div", { className: "composer-label", key: "label" }, "Send a new task"),
467
+ e("div", { className: "composer", key: "inner" }, [
468
+ e("input", {
469
+ key: "ta",
470
+ type: "text",
471
+ value: chatText,
472
+ onChange: (ev) => setChatText(ev.target.value),
473
+ onKeyDown: onComposerKeyDown,
474
+ placeholder: "Tell OpenCore what to do next...",
475
+ disabled: chatBusy,
476
+ }),
477
+ e(
478
+ "button",
479
+ { className: "primary-btn", key: "b", onClick: sendChat, disabled: chatBusy },
480
+ chatBusy ? "Sending..." : "Send",
481
+ ),
482
+ ]),
483
+ ]),
484
+ ]);
485
+ }
486
+
487
+ function renderScreenshotsPage() {
488
+ return e(
489
+ "div",
490
+ { className: "shots-grid content-body" },
491
+ shots.map((s) =>
492
+ e("article", { className: "shot-card", key: s.path }, [
493
+ e("div", { className: "shot-meta", key: "meta" }, [
494
+ e("div", { className: "shot-name", key: "name" }, s.name),
495
+ e("div", { className: "shot-ts", key: "ts" }, fmtTs(s.ts)),
496
+ ]),
497
+ e("img", {
498
+ key: "img",
499
+ src: `/api/screenshots/file?path=${encodeURIComponent(s.path)}`,
500
+ }),
501
+ ]),
502
+ ),
503
+ );
504
+ }
505
+
506
+ function renderSkillsPage() {
507
+ return e(
508
+ "div",
509
+ { className: "skills-grid content-body" },
510
+ skills.map((s) => {
511
+ const expanded = Boolean(expandedSkills[s.id]);
512
+ const titleLong = String(s.name || "").length > 26;
513
+ const descriptionLong = String(s.description || "").length > 120;
514
+ const showExpand = titleLong || descriptionLong;
515
+ return e("article", { className: `skill-card ${expanded ? "expanded" : ""}`.trim(), key: s.id }, [
516
+ e("div", { className: "skill-top", key: "top" }, [
517
+ e("div", { key: "title" }, [
518
+ e("div", { className: "skill-kicker", key: "k" }, s.id),
519
+ e("h3", { className: expanded ? "" : "clamp-title", key: "h3" }, s.name),
520
+ ]),
521
+ e(
522
+ "span",
523
+ { className: `skill-status ${s.installed ? "installed" : "not-installed"}`, key: "status" },
524
+ s.installed ? "Installed" : "Available",
525
+ ),
526
+ ]),
527
+ e("p", { className: expanded ? "" : "clamp-description", key: "p" }, s.description),
528
+ showExpand
529
+ ? e(
530
+ "button",
531
+ {
532
+ className: "text-btn skill-expand-btn",
533
+ key: "expand",
534
+ onClick: () => toggleSkillExpanded(s.id),
535
+ },
536
+ expanded ? "Less" : "More",
537
+ )
538
+ : null,
539
+ s.installed
540
+ ? e("div", { className: "skill-note", key: "note" }, "Ready for use.")
541
+ : e(
542
+ "button",
543
+ {
544
+ className: "secondary-btn",
545
+ key: "install",
546
+ onClick: () => installSkill(s.id),
547
+ disabled: Boolean(skillBusy[s.id]),
548
+ },
549
+ skillBusy[s.id] ? "Installing..." : "Install Skill",
550
+ ),
551
+ ]);
552
+ }),
553
+ );
554
+ }
555
+
556
+ function renderTelegramPage() {
557
+ return e("div", { className: "content-body stacked" }, [
558
+ e("div", { className: "section-head", key: "head" }, [
559
+ e("div", { className: "section-title", key: "title" }, "Telegram Settings"),
560
+ e(
561
+ "div",
562
+ { className: "section-meta", key: "meta" },
563
+ telegramConfig.enabled
564
+ ? telegramConfig.paired
565
+ ? `Connected to user ${telegramConfig.user_id || "unknown"}`
566
+ : "Awaiting Telegram pairing"
567
+ : "Telegram is not connected",
568
+ ),
569
+ ]),
570
+ e("div", { className: "settings-card", key: "card" }, [
571
+ e("label", { className: "settings-row settings-toggle", key: "enabled" }, [
572
+ e("span", { className: "settings-copy", key: "copy" }, [
573
+ e("strong", { key: "title" }, "Enable Telegram"),
574
+ e("span", { key: "sub" }, "Set the bot token first. Then run /start in Telegram to receive both the Telegram user ID and pairing code, and save them here."),
575
+ ]),
576
+ e("input", {
577
+ key: "input",
578
+ type: "checkbox",
579
+ checked: Boolean(telegramConfig.enabled),
580
+ onChange: (ev) => setTelegramConfig((prev) => ({ ...prev, enabled: ev.target.checked })),
581
+ }),
582
+ ]),
583
+ e("label", { className: "settings-field", key: "user" }, [
584
+ e("span", { key: "label" }, "Telegram User ID"),
585
+ e("input", {
586
+ key: "input",
587
+ type: "text",
588
+ value: telegramConfig.user_id,
589
+ onChange: (ev) => setTelegramConfig((prev) => ({ ...prev, user_id: ev.target.value })),
590
+ placeholder: "Returned by /start in Telegram",
591
+ }),
592
+ ]),
593
+ e("label", { className: "settings-field", key: "token" }, [
594
+ e("span", { key: "label" }, `Bot Token ${telegramConfig.has_bot_token ? "(saved)" : ""}`),
595
+ e("input", {
596
+ key: "input",
597
+ type: "password",
598
+ value: telegramConfig.bot_token,
599
+ onChange: (ev) => setTelegramConfig((prev) => ({ ...prev, bot_token: ev.target.value })),
600
+ placeholder: telegramConfig.has_bot_token ? "Leave blank to keep existing token" : "123456:ABC...",
601
+ }),
602
+ ]),
603
+ e("label", { className: "settings-field", key: "pairing" }, [
604
+ e("span", { key: "label" }, `Pairing Code ${telegramConfig.has_pairing_code ? "(saved)" : ""}`),
605
+ e("input", {
606
+ key: "input",
607
+ type: "text",
608
+ value: telegramConfig.pairing_code,
609
+ onChange: (ev) => setTelegramConfig((prev) => ({ ...prev, pairing_code: ev.target.value })),
610
+ placeholder: telegramConfig.has_pairing_code ? "Leave blank to keep existing code" : "Choose a pairing code",
611
+ }),
612
+ ]),
613
+ e("label", { className: "settings-field", key: "chat" }, [
614
+ e("span", { key: "label" }, "Chat ID"),
615
+ e("input", {
616
+ key: "input",
617
+ type: "text",
618
+ value: telegramConfig.chat_id,
619
+ onChange: (ev) => setTelegramConfig((prev) => ({ ...prev, chat_id: ev.target.value })),
620
+ placeholder: "Filled automatically after pairing",
621
+ }),
622
+ ]),
623
+ e(
624
+ "div",
625
+ { className: "settings-inline-meta", key: "meta" },
626
+ `${telegramConfig.paired ? "Paired" : "Not paired"} · Last update id: ${telegramConfig.last_update_id || 0}`,
627
+ ),
628
+ e("div", { className: "settings-actions", key: "actions" }, [
629
+ e(
630
+ "button",
631
+ { className: "primary-btn", key: "save", onClick: saveTelegramConfig, disabled: telegramBusy },
632
+ telegramBusy ? "Saving..." : "Save Telegram Config",
633
+ ),
634
+ e(
635
+ "button",
636
+ { className: "secondary-btn", key: "disconnect", onClick: disconnectTelegram, disabled: telegramBusy },
637
+ "Disconnect Telegram",
638
+ ),
639
+ ]),
640
+ ]),
641
+ ]);
642
+ }
643
+
644
+ function renderCredentialsPage() {
645
+ return e("div", { className: "content-body stacked" }, [
646
+ e("div", { className: "section-head", key: "head" }, [
647
+ e("div", { className: "section-title", key: "title" }, "Credential Vault"),
648
+ e("div", { className: "section-meta", key: "meta" }, `${credentialEntries.length} saved website credential${credentialEntries.length === 1 ? "" : "s"}`),
649
+ ]),
650
+ e("div", { className: "settings-card credentials-settings-card", key: "defaults" }, [
651
+ e("label", { className: "settings-field", key: "de" }, [
652
+ e("span", { key: "label" }, "Default Email"),
653
+ e("input", {
654
+ key: "input",
655
+ type: "email",
656
+ value: credentialDefaults.default_email,
657
+ onChange: (ev) => setCredentialDefaults((prev) => ({ ...prev, default_email: ev.target.value })),
658
+ placeholder: "name@example.com",
659
+ }),
660
+ ]),
661
+ e("label", { className: "settings-field", key: "dep" }, [
662
+ e("span", { key: "label" }, "Default Email Provider"),
663
+ e("input", {
664
+ key: "input",
665
+ type: "text",
666
+ value: credentialDefaults.default_email_provider,
667
+ onChange: (ev) => setCredentialDefaults((prev) => ({ ...prev, default_email_provider: ev.target.value })),
668
+ placeholder: "gmail, outlook, icloud...",
669
+ }),
670
+ ]),
671
+ e("label", { className: "settings-row settings-toggle", key: "auto" }, [
672
+ e("span", { className: "settings-copy", key: "copy" }, [
673
+ e("strong", { key: "title" }, "Automatic Account Email Activation"),
674
+ e("span", { key: "sub" }, "If enabled, OpenCore may open the configured inbox provider and use verification codes or links during sign-up flows."),
675
+ ]),
676
+ e("input", {
677
+ key: "input",
678
+ type: "checkbox",
679
+ checked: Boolean(credentialDefaults.auto_email_activation_enabled),
680
+ onChange: (ev) =>
681
+ setCredentialDefaults((prev) => ({
682
+ ...prev,
683
+ auto_email_activation_enabled: ev.target.checked,
684
+ })),
685
+ }),
686
+ ]),
687
+ credentialDefaults.auto_email_activation_enabled
688
+ ? e(
689
+ "div",
690
+ { className: "credentials-warning", key: "warn" },
691
+ "Warning: automatic activation gives OpenCore permission to inspect incoming verification emails and follow activation links or codes. Only enable this if you trust the current machine and account setup.",
692
+ )
693
+ : null,
694
+ e("div", { className: "settings-actions", key: "actions" }, [
695
+ e(
696
+ "button",
697
+ { className: "primary-btn", key: "save", onClick: saveCredentialDefaults, disabled: credentialsBusy },
698
+ credentialsBusy ? "Saving..." : "Save Defaults",
699
+ ),
700
+ ]),
701
+ ]),
702
+ e("div", { className: "settings-card credentials-entry-card", key: "entry-form" }, [
703
+ e("div", { className: "section-title", key: "title" }, credentialForm.id ? "Edit Website Credential" : "Add Website Credential"),
704
+ e("label", { className: "settings-field", key: "site" }, [
705
+ e("span", { key: "label" }, "Website"),
706
+ e("input", {
707
+ key: "input",
708
+ type: "text",
709
+ value: credentialForm.website,
710
+ onChange: (ev) => setCredentialForm((prev) => ({ ...prev, website: ev.target.value })),
711
+ placeholder: "example.com",
712
+ }),
713
+ ]),
714
+ e("label", { className: "settings-field", key: "email" }, [
715
+ e("span", { key: "label" }, "Email"),
716
+ e("input", {
717
+ key: "input",
718
+ type: "email",
719
+ value: credentialForm.email,
720
+ onChange: (ev) => setCredentialForm((prev) => ({ ...prev, email: ev.target.value })),
721
+ placeholder: "login@example.com",
722
+ }),
723
+ ]),
724
+ e("label", { className: "settings-field", key: "password" }, [
725
+ e("span", { key: "label" }, "Password"),
726
+ e("input", {
727
+ key: "input",
728
+ type: "password",
729
+ value: credentialForm.password,
730
+ onChange: (ev) => setCredentialForm((prev) => ({ ...prev, password: ev.target.value })),
731
+ placeholder: "Saved locally only",
732
+ }),
733
+ ]),
734
+ e("label", { className: "settings-field", key: "provider" }, [
735
+ e("span", { key: "label" }, "Email Provider (optional)"),
736
+ e("input", {
737
+ key: "input",
738
+ type: "text",
739
+ value: credentialForm.email_provider,
740
+ onChange: (ev) => setCredentialForm((prev) => ({ ...prev, email_provider: ev.target.value })),
741
+ placeholder: "gmail, outlook, icloud...",
742
+ }),
743
+ ]),
744
+ e("label", { className: "settings-field settings-field-wide", key: "notes" }, [
745
+ e("span", { key: "label" }, "Notes (optional)"),
746
+ e("input", {
747
+ key: "input",
748
+ type: "text",
749
+ value: credentialForm.notes,
750
+ onChange: (ev) => setCredentialForm((prev) => ({ ...prev, notes: ev.target.value })),
751
+ placeholder: "username variant, MFA note, plan tier...",
752
+ }),
753
+ ]),
754
+ e("div", { className: "settings-actions", key: "entry-actions" }, [
755
+ e(
756
+ "button",
757
+ { className: "primary-btn", key: "save", onClick: saveCredentialEntry, disabled: credentialsBusy },
758
+ credentialsBusy ? "Saving..." : credentialForm.id ? "Update Credential" : "Save Credential",
759
+ ),
760
+ e(
761
+ "button",
762
+ {
763
+ className: "secondary-btn",
764
+ key: "clear",
765
+ onClick: () =>
766
+ setCredentialForm({
767
+ id: "",
768
+ website: "",
769
+ email: "",
770
+ password: "",
771
+ email_provider: "",
772
+ notes: "",
773
+ }),
774
+ disabled: credentialsBusy,
775
+ },
776
+ "Clear",
777
+ ),
778
+ ]),
779
+ ]),
780
+ e(
781
+ "div",
782
+ { className: "credentials-grid", key: "list" },
783
+ credentialEntries.map((entry) =>
784
+ e("article", { className: "credential-card", key: entry.id }, [
785
+ e("div", { className: "skill-top", key: "top" }, [
786
+ e("div", { key: "title" }, [
787
+ e("div", { className: "skill-kicker", key: "k" }, "Stored Login"),
788
+ e("h3", { key: "h3" }, entry.website),
789
+ ]),
790
+ e("span", { className: `skill-status ${entry.generated_by_ai ? "not-installed" : "installed"}`, key: "status" }, entry.generated_by_ai ? "AI Generated" : "User Saved"),
791
+ ]),
792
+ e("p", { key: "email" }, `Email: ${entry.email || "Not set"}`),
793
+ e("p", { key: "password" }, `Password: ${entry.password ? "Saved locally" : "Not set"}`),
794
+ e("p", { key: "provider" }, `Provider: ${entry.email_provider || "Not set"}`),
795
+ entry.notes ? e("div", { className: "skill-note", key: "notes" }, entry.notes) : null,
796
+ e("div", { className: "settings-actions credential-actions", key: "actions" }, [
797
+ e("button", { className: "secondary-btn", key: "edit", onClick: () => beginCredentialEdit(entry), disabled: credentialsBusy }, "Edit"),
798
+ e("button", { className: "secondary-btn danger-btn", key: "delete", onClick: () => deleteCredential(entry.id), disabled: credentialsBusy }, "Delete"),
799
+ ]),
800
+ ]),
801
+ ),
802
+ ),
803
+ ]);
804
+ }
805
+
806
+ function renderEditorPage() {
807
+ return e("div", { className: "editor content-body stacked" }, [
808
+ e("div", { className: "section-head", key: "head" }, [
809
+ e("div", { className: "section-title", key: "title" }, "Editable System File"),
810
+ e("div", { className: "section-meta", key: "meta" }, "Changes are stored locally on this Mac"),
811
+ ]),
812
+ e("textarea", {
813
+ key: "ed",
814
+ value: fileContent,
815
+ onChange: (ev) => setFileContent(ev.target.value),
816
+ }),
817
+ e("div", { className: "editor-footer", key: "ft" }, [
818
+ e("button", { className: "primary-btn", key: "sv", onClick: saveCurrentFile, disabled: fileBusy }, fileBusy ? "Saving..." : "Save Changes"),
819
+ ]),
820
+ ]);
821
+ }
822
+
823
+ function renderPage() {
824
+ if (page === "chat") return renderChatPage();
825
+ if (page === "credentials") return renderCredentialsPage();
826
+ if (page === "telegram") return renderTelegramPage();
827
+ if (page === "screenshots") return renderScreenshotsPage();
828
+ if (page === "skills") return renderSkillsPage();
829
+ return renderEditorPage();
830
+ }
831
+
832
+ return e("div", { className: "shell" }, [
833
+ e("div", { className: "ambient ambient-a", key: "a1" }),
834
+ e("div", { className: "ambient ambient-b", key: "a2" }),
835
+ e("header", { className: "hero", key: "top" }, [
836
+ e("div", { className: "hero-copy", key: "copy" }, [
837
+ e("div", { className: "hero-kicker", key: "k" }, "OpenCore Control Surface"),
838
+ e("h1", { key: "h1" }, "Dashboard"),
839
+ e("p", { key: "p" }, "A local operations console for tasks, memory, skills, and execution state."),
840
+ ]),
841
+ e("div", { className: "hero-status", key: "status" }, [
842
+ e(
843
+ "button",
844
+ {
845
+ className: "theme-switch",
846
+ key: "theme",
847
+ onClick: () => setTheme((prev) => (prev === "dark" ? "light" : "dark")),
848
+ },
849
+ theme === "dark" ? "Light Mode" : "Dark Mode",
850
+ ),
851
+ e("div", { className: "status-chip", key: "chip" }, status),
852
+ e("div", { className: "status-line", key: "line" }, liveEvent),
853
+ ]),
854
+ ]),
855
+ e(
856
+ "div",
857
+ { className: "stats-row", key: "stats" },
858
+ stats.map((item, idx) => e(StatCard, { key: `${item.label}-${idx}`, label: item.label, value: item.value, tone: item.tone })),
859
+ ),
860
+ e("div", { className: `layout ${drawerOpen ? "" : "drawer-closed"}`.trim(), key: "layout" }, [
861
+ e("aside", { className: `drawer ${drawerOpen ? "open" : "closed"}`, key: "drawer" }, [
862
+ e("div", { className: "drawer-head", key: "dh" }, [
863
+ e("div", { className: "drawer-head-main", key: "main" }, [
864
+ e("div", { className: "drawer-kicker", key: "dk" }, "Workspace"),
865
+ e("div", { className: "drawer-title", key: "dt" }, "Navigation"),
866
+ ]),
867
+ e(
868
+ "button",
869
+ {
870
+ className: "drawer-inline-toggle",
871
+ key: "tg",
872
+ onClick: () => setDrawerOpen((v) => !v),
873
+ "aria-label": drawerOpen ? "Close sidebar" : "Open sidebar",
874
+ },
875
+ "◂",
876
+ ),
877
+ ]),
878
+ e(
879
+ "div",
880
+ { className: "drawer-nav", key: "nav" },
881
+ PAGES.map((p) =>
882
+ e(
883
+ "button",
884
+ {
885
+ key: p.id,
886
+ className: `nav-btn ${page === p.id ? "active" : ""}`,
887
+ onClick: () => setPage(p.id),
888
+ },
889
+ [
890
+ e("span", { className: "nav-label", key: "l" }, p.label),
891
+ e("span", { className: "nav-blurb", key: "b" }, p.eyebrow),
892
+ ],
893
+ ),
894
+ ),
895
+ ),
896
+ ]),
897
+ !drawerOpen &&
898
+ e(
899
+ "button",
900
+ {
901
+ className: "drawer-reopen",
902
+ key: "reopen",
903
+ onClick: () => setDrawerOpen(true),
904
+ "aria-label": "Open sidebar",
905
+ },
906
+ "▸",
907
+ ),
908
+ e("main", { className: "page", key: "page" }, [
909
+ e("div", { className: "page-head", key: "ph" }, [
910
+ e("div", { key: "copy" }, [
911
+ e("div", { className: "page-kicker", key: "pk" }, currentPage.eyebrow),
912
+ e("h2", { key: "h2" }, currentPage.label),
913
+ e("p", { key: "p" }, currentPage.blurb),
914
+ ]),
915
+ ]),
916
+ renderPage(),
917
+ ]),
918
+ ]),
919
+ ]);
920
+ }
921
+
922
+ ReactDOM.render(e(App), document.getElementById("root"));
923
+ })();