diff --git a/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/dynamic-skills.js b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/dynamic-skills.js new file mode 100644 index 00000000..6b6de66d --- /dev/null +++ b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/dynamic-skills.js @@ -0,0 +1,157 @@ +"use strict"; + +// Dynamically load skills after BE startup. +// This avoids needing to rebuild the big BE bundle when adding new skills on disk. + +const path = require("path"); +const jibo = require("jibo"); + +let rlog = null; +try { + rlog = require("./robot-logger"); +} catch (e) { + rlog = null; +} + +function log(msg, data) { + const line = "[DYN-SKILLS] " + msg; + try { + console.log(line, data || ""); + } catch (e) {} + try { + if (rlog && typeof rlog.raw === "function") rlog.raw(line + (data ? " " + JSON.stringify(data) : "")); + } catch (e) {} +} + +function normalizeSkillExport(SkillExport, id) { + if (typeof SkillExport === "function") return SkillExport; + if (SkillExport && typeof SkillExport.Skill === "function") return SkillExport.Skill; + throw new Error("Error loading skill: " + id + ". Incorrect exports"); +} + +function attachLifecycleHooks(be, id, skill) { + // Mirror the hooks the Be constructor normally installs. + try { + skill.on("exit", function () { + be.exit.call(be, skill, ...arguments); + }); + skill.on("redirect", function () { + be.skillRedirect.call(be, skill, ...arguments); + }); + skill.on("refresh", function () { + be.skillRedirect.call(be, skill, skill.assetPack, ...arguments); + }); + } catch (e) { + log("failed to attach lifecycle hooks", { id: id, err: String(e && (e.stack || e.message || e)) }); + } + + const empty = (done) => { + try { + done(); + } catch (e) {} + }; + + try { + if (!skill.postInit) skill.postInit = empty; + if (!skill.preload) skill.preload = empty; + } catch (e) {} +} + +function tryLoadSkill(be, id) { + if (!be || !be.skills) throw new Error("be.skills missing"); + if (!id || typeof id !== "string") return false; + + if (be.skills[id]) return true; + + // eslint-disable-next-line global-require, import/no-dynamic-require + const SkillExport = require(id); + const Skill = normalizeSkillExport(SkillExport, id); + + const rootPath = path.dirname(jibo.utils.PathUtils.resolve(id)); + const skill = new Skill({ assetPack: id, rootPath: rootPath }); + + if (typeof be._validateSkill === "function" && !be._validateSkill(skill)) { + throw new Error("not a valid BeSkill"); + } + + be.skills[id] = skill; + attachLifecycleHooks(be, id, skill); + + log("loaded", { id: id }); + return true; +} + +function collectSkillIdsFromMenuEntries(entries) { + const ids = new Set(); + + (entries || []).forEach(function (e) { + if (!e) return; + + if (e.type === "skill" && e.skillId) ids.add(e.skillId); + + if (e.type === "submenu" && Array.isArray(e.children)) { + e.children.forEach(function (c) { + if (c && c.type === "skill" && c.skillId) ids.add(c.skillId); + }); + } + }); + + return Array.from(ids); +} + +function loadMissingSkillsFromMenuEntries(be, opts) { + opts = opts || {}; + + let menuEntries = null; + try { + // Note: this is the modular path (includes provider entries). + menuEntries = require("../menu/menu-entries"); + } catch (e) { + log("menu-entries module missing", { err: String(e && (e.stack || e.message || e)) }); + return { loaded: [], failed: [] }; + } + + let res; + try { + res = menuEntries.getMenuEntries({ + skillsRoot: opts.skillsRoot, + providersDir: opts.providersDir, + log: function () { + try { + log(Array.prototype.join.call(arguments, " ")); + } catch (e) {} + } + }); + } catch (e) { + log("getMenuEntries failed", { err: String(e && (e.stack || e.message || e)) }); + return { loaded: [], failed: [] }; + } + + const ids = collectSkillIdsFromMenuEntries(res && res.entries); + log("menu referenced skillIds", { count: ids.length }); + + const loaded = []; + const failed = []; + + ids.forEach(function (id) { + try { + // Only attempt ids that look like NPM packages (reduces noise). + if (typeof id !== "string") return; + if (id.indexOf("/") === -1) return; + + if (!be.skills[id]) { + tryLoadSkill(be, id); + loaded.push(id); + } + } catch (e) { + failed.push({ id: id, err: String(e && (e.stack || e.message || e)) }); + log("load failed", { id: id, err: String(e && (e.message || e)) }); + } + }); + + return { loaded: loaded, failed: failed, providersDir: res && res.providersDir, skillsRoot: res && res.skillsRoot }; +} + +module.exports = { + loadMissingSkillsFromMenuEntries: loadMissingSkillsFromMenuEntries +}; diff --git a/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/postinit.js b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/postinit.js index c9641c1b..5532e3a4 100644 --- a/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/postinit.js +++ b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/postinit.js @@ -54,6 +54,26 @@ exports.postInit = function (err) { this.log.warn('Dynamic skills patch failed (non-fatal):', e.message || e); } + // Optional: dynamically load skills referenced by modular menu entries. + // This is the missing piece for “menu entry exists” => “skill is launchable”. + try { + const dynSkills = require('./dynamic-skills'); + const res = dynSkills.loadMissingSkillsFromMenuEntries(this); + try { + this.log.info('Dynamic skill load complete', { + loaded: res && res.loaded ? res.loaded.length : 0, + failed: res && res.failed ? res.failed.length : 0, + skillsRoot: res && res.skillsRoot, + providersDir: res && res.providersDir + }); + } catch (e2) { + // ignore + } + } + catch (e) { + this.log.warn('Dynamic skill loader failed (non-fatal):', e.message || e); + } + // Optional: AI Bridge (modular; can run models off-robot for now) try { if (rlog && typeof rlog.raw === 'function') { diff --git a/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/TASKS.md b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/TASKS.md new file mode 100644 index 00000000..aa33483c --- /dev/null +++ b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/TASKS.md @@ -0,0 +1,44 @@ +# Plex Music (Jibo) — task list + +Goal: add a Jibo menu button that opens a Plex music browser UI. Next phase will be playback. + +## Phase 0 — Get a basic GUI up (now) +- [x] Create launchable skill module `@be/plex-music` +- [x] Add modular menu entry in `@be/menu-entries.d/` +- [x] Show a simple `MenuView` with placeholder actions + +## Phase 1 — Connect to Plex (discovery + auth) +- [ ] Decide config source for server + token + - Option A: JSON file on-robot (e.g. `/opt/jibo/Jibo/Skills/@be/plex-config.json`) + - Option B: env vars (`PLEX_BASE_URL`, `PLEX_TOKEN`) + - Option C: run a tiny local helper service and talk to it +- [ ] Implement a minimal Plex client (HTTP GET) + - [ ] Fetch `/:/resources` or `/?X-Plex-Token=...` health check + - [ ] Handle HTTPS vs HTTP and timeouts +- [ ] Add a “connection status” indicator in the UI +- [ ] Log failures to robot logd (UDP) for easy debugging + +## Phase 2 — Browse music library (read-only) +- [ ] List music libraries (Plex sections) +- [ ] Pick a library (default to first music section) +- [ ] Browse hierarchy (at minimum) + - [ ] Artists list + - [ ] Albums for artist + - [ ] Tracks for album +- [ ] Add simple paging/scroll behavior (Jibo screen limits) +- [ ] Cache results in-memory for snappy navigation + +## Phase 3 — Playback (next) +- [ ] Decide playback strategy + - Option A: stream directly and use Jibo audio APIs + - Option B: ask Plex to transcode to a simple stream + - Option C: route through an on-robot helper (Python) that downloads/streams +- [ ] Add “Play / Pause / Next” UI +- [ ] Maintain a queue (album or playlist) +- [ ] Show “Now Playing” (track title / artist / album) + +## Phase 4 — Polish +- [ ] Better icons + labels +- [ ] Remember last-used library and last selection +- [ ] Handle token expiration gracefully +- [ ] Add a tiny test harness (run on dev box against Plex) diff --git a/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.html b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.html new file mode 100644 index 00000000..9d5ddff8 --- /dev/null +++ b/V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.html @@ -0,0 +1,12 @@ + + +
+ + +