Files
fusion/plugins/fusion-plugin-even-realities-glasses/src/index.ts
Fusion 007efcab0b feat(FN-3970): consolidate Even plugin APIs into unified package with webho
Collapsed the Even plugin architecture into a single unified plugin by merging board card routes and replacing the transport stub with a real webhook implementation, removing the separate cards plugin from the workspace. The new unified plugin (`fusion-plugin-even-realities-glasses`) now exports con

Fusion-Task-Id: FN-3970
2026-05-11 06:47:42 -07:00

161 lines
5.3 KiB
TypeScript

import { definePlugin } from "@fusion/plugin-sdk";
import type { FusionPlugin, PluginContext, PluginRouteDefinition, PluginRouteResponse } from "@fusion/plugin-sdk";
import { createNotifier } from "./notifier.js";
import { requestReview, startWork } from "./agent-actions.js";
import { runQuickCapture } from "./quick-capture.js";
import { quickCaptureRoutes } from "./routes/quick-capture-routes.js";
import { createNotificationRoutes } from "./routes/notification-routes.js";
import { agentActionRoutes } from "./routes/agent-action-routes.js";
import { boardRoutes } from "./routes/board-routes.js";
import {
getCompanionWebhookUrl,
getFusionToken,
getNotifyColumns,
getQuickCaptureColumn,
settingsSchema,
} from "./settings.js";
import { WebhookGlassesTransport } from "./transport.js";
import { createTransportRoutes } from "./routes/transport-routes.js";
export type PluginDb = {
exec(sql: string): void;
prepare(sql: string): {
get(...args: unknown[]): unknown;
all(...args: unknown[]): unknown;
run(...args: unknown[]): unknown;
};
};
type PluginInstance = {
transport: WebhookGlassesTransport;
notifier: ReturnType<typeof createNotifier>;
};const instances = new Map<string, PluginInstance>();
function getDbFromTaskStore(ctx: PluginContext): PluginDb {
const pluginStore = ctx.taskStore.getPluginStore();
const db = (pluginStore as unknown as { db?: PluginDb }).db;
if (!db) throw new Error("Plugin database unavailable");
return db;
}
function getInstanceOrResponse(ctx: PluginContext): { instance?: PluginInstance; error?: PluginRouteResponse } {
const instance = instances.get(ctx.pluginId);
if (!instance) return { error: { status: 503, body: { error: "Plugin instance not initialized" } } };
return { instance };
}
const coreRoutes: PluginRouteDefinition[] = [
{
method: "GET",
path: "/status",
handler: async (_req, ctx) => {
const { instance, error } = getInstanceOrResponse(ctx);
if (!instance) return error as PluginRouteResponse;
return {
status: 200,
body: {
connected: instance.transport.connected,
transport: instance.transport.status,
lastPollTime: instance.notifier.lastPolledAt() ?? null,
notifyOnColumns: getNotifyColumns(ctx.settings),
},
};
},
},
{
method: "POST",
path: "/reconnect",
handler: async (_req, ctx) => {
const { instance, error } = getInstanceOrResponse(ctx);
if (!instance) return error as PluginRouteResponse;
await instance.transport.disconnect();
await instance.transport.connect();
return { status: 200, body: { ok: true } };
},
},
];
const notificationRoutes = createNotificationRoutes((ctx) => instances.get(ctx.pluginId)?.notifier);
const transportRoutes = createTransportRoutes((ctx) => instances.get(ctx.pluginId)?.transport);
const plugin: FusionPlugin = definePlugin({
manifest: {
id: "fusion-plugin-even-realities-glasses",
name: "Even Realities Glasses",
version: "0.1.0",
description: "Task-focused card bridge between Fusion and Even Realities glasses.",
author: "Fusion Team",
fusionVersion: ">=0.1.0",
settingsSchema,
},
state: "installed",
routes: [...coreRoutes, ...boardRoutes, ...quickCaptureRoutes, ...agentActionRoutes, ...notificationRoutes, ...transportRoutes],
hooks: {
onSchemaInit: (db) => {
(db as PluginDb).exec(`
CREATE TABLE IF NOT EXISTS even_realities_seen_tasks (
taskId TEXT PRIMARY KEY,
lastColumn TEXT NOT NULL,
updatedAt TEXT NOT NULL
)
`);
},
onLoad: async (ctx) => {
const token = getFusionToken(ctx.settings);
if (!token) {
ctx.logger.warn("fusionApiToken is missing; even-realities plugin not initialized");
return;
}
const db = getDbFromTaskStore(ctx);
const transport = new WebhookGlassesTransport({
companionWebhookUrl: getCompanionWebhookUrl(ctx.settings),
});
await transport.connect();
transport.onAction(async (action) => {
if (action.type === "quick-capture") {
await runQuickCapture(
{ text: action.text, column: undefined },
{
taskStore: ctx.taskStore,
pluginId: ctx.pluginId,
defaultColumn: getQuickCaptureColumn(ctx.settings),
},
);
return;
}
if (!action.taskId) return;
if (action.type === "start-work") {
await startWork({ taskId: action.taskId }, { taskStore: ctx.taskStore, pluginId: ctx.pluginId });
return;
}
if (action.type === "request-review") {
await requestReview({ taskId: action.taskId }, { taskStore: ctx.taskStore, pluginId: ctx.pluginId });
}
});
const notifier = createNotifier({
taskStore: ctx.taskStore,
db,
transport,
settings: ctx.settings,
logger: ctx.logger,
pluginId: ctx.pluginId,
});
notifier.start();
instances.set(ctx.pluginId, { transport, notifier });
},
onUnload: async () => {
for (const [pluginId, instance] of instances.entries()) {
await instance.notifier.stop();
await instance.transport.disconnect();
instances.delete(pluginId);
}
},
},
});
export default plugin;