Add getRegisteredPlugins, plugin manager panel in test app

This commit is contained in:
2026-04-18 20:24:54 +10:00
parent 93dbef6a59
commit c6d3d4cbe5
7 changed files with 247 additions and 5 deletions

View File

@@ -105,7 +105,121 @@
document.getElementById('status-badge').classList.add('loaded');
addLog('example-plugin loaded successfully', 'success');
// ---------------------------------------------------------------------------
// Plugin Manager panel
// ---------------------------------------------------------------------------
/** Renders the plugin directory card list. */
function refreshPluginList() {
const listEl = document.getElementById('plugin-list');
const plugins = registry.getRegisteredPlugins();
if (!plugins.length) {
listEl.innerHTML = '<div id="no-plugins-msg" style="color:#555;font-size:12px;text-align:center;padding:20px 0;">No plugins loaded</div>';
return;
}
listEl.innerHTML = plugins.map(p => `
<div class="plugin-card" data-id="${p.id}">
<div class="plugin-card-header">
<span class="plugin-name">${p.name}</span>
<span class="plugin-version">v${p.version}</span>
</div>
<div style="font-size:10px;color:#7986cb;font-family:monospace;">${p.id}</div>
<div class="plugin-badges">
<span class="badge green">loaded</span>
${p.commandCount ? `<span class="badge">${p.commandCount} cmd${p.commandCount !== 1 ? 's' : ''}</span>` : ''}
${p.interceptorCount ? `<span class="badge">${p.interceptorCount} interceptor${p.interceptorCount !== 1 ? 's' : ''}</span>` : ''}
${p.themeCount ? `<span class="badge amber">${p.themeCount} theme${p.themeCount !== 1 ? 's' : ''}</span>` : ''}
${p.hasSettings ? `<span class="badge">has settings</span>` : ''}
</div>
<button class="plugin-unload-btn" data-id="${p.id}">Unload</button>
</div>
`).join('');
listEl.querySelectorAll('.plugin-unload-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const id = btn.dataset.id;
await registry.unregisterPlugin(id);
addLog(`Plugin unloaded: ${id}`, 'warn');
refreshPluginList();
refreshCommandList();
renderSettings();
});
});
}
/**
* Loads a plugin from an arbitrary file path entered in the Load input.
* Supports CJS modules (same eval pattern as cinny-desktop).
* @param {string} filePath
*/
async function loadPluginFromPath(filePath) {
try {
const code = fs.readFileSync(filePath, 'utf-8');
const m = { exports: {} };
// eslint-disable-next-line no-new-func
new Function('module', 'exports', code)(m, m.exports);
const p = m.exports;
if (!p || typeof p.onLoad !== 'function') {
throw new Error('File does not export a valid plugin (needs an onLoad function)');
}
const id = p.id ?? path.basename(path.dirname(filePath));
if (registry.getRegisteredPlugins().some(r => r.id === id)) {
throw new Error(`Plugin with id "${id}" is already loaded`);
}
const ctx = createPluginContext(
{
pluginId: id,
eventClient: eventEmitter,
onNotify: (opts) =>
addLog(`[${opts.type?.toUpperCase() ?? 'NOTIFY'}] ${opts.title}: ${opts.body}`, 'info'),
},
registry
);
ctx.log = (...args) => {
console.log(`[Plugin ${id}]`, ...args);
registry.addLog(id, 'log', args);
addLog(args.map(String).join(' '), 'log');
};
registry.registerPlugin(id, p, ctx);
await p.onLoad(ctx);
addLog(`Plugin loaded from path: ${id}`, 'success');
refreshPluginList();
refreshCommandList();
renderSettings();
} catch (err) {
addLog(`Failed to load plugin: ${err.message}`, 'error');
}
}
document.getElementById('load-plugin-btn').addEventListener('click', () => {
const filePath = document.getElementById('load-plugin-path').value.trim();
if (!filePath) { addLog('Enter a path to a plugin index.js', 'error'); return; }
loadPluginFromPath(filePath);
document.getElementById('load-plugin-path').value = '';
});
document.getElementById('load-plugin-path').addEventListener('keydown', (e) => {
if (e.key === 'Enter') document.getElementById('load-plugin-btn').click();
});
refreshPluginList();
// Populate registered command list
function refreshCommandList() {
const cmdListEl = document.getElementById('cmd-list');
cmdListEl.innerHTML = registry
.getCommands()
.map(c => `<li><code>/${c.name}</code> — ${c.command.description ?? ''}</li>`)
.join('') || '<li style="color:#555">No commands registered</li>';
}
const cmdListEl = document.getElementById('cmd-list');
cmdListEl.innerHTML = registry
.getCommands()