Files
paseo/plugins/muse-provider/server/usage.ts
T
Mohamed Boudraandmillerben95 8332f4d1ff feat(muse): add Muse Code as a built-in provider plugin (#5719)
* test(muse): record real host protocol fixtures

* feat(muse): add built-in MSP provider

Co-authored-by: millerben95 <millerben95@users.noreply.github.com>

* fix(muse): expose provider settings in the app

The daemon registered host settings without a client contribution, leaving sandbox, network, and workspace trust unreachable. Share the definition with a settings screen and expose it through Command Center even when installed plugins are disabled.

* docs(muse): publish setup and provider support

* test: correct runtime fixtures and restart readiness

* fix(muse): hide reminder housekeeping from the timeline

* fix(muse): use the official Muse Code mark

Source: https://muse.ai/images/favicon/app-squiggle.svg

Preserve the official silhouette path verbatim. Remove its gradient definition and use currentColor, scaling the original 100-unit coordinates into the 24-unit viewBox. Black silhouette renders match pixel-for-pixel.

* fix(muse): adapt to current plugin APIs

Use the screen API introduced with sidebar plugin items. Usage reports now come from source account discovery; remove the retired agent usage-reference request and headline marker, and exercise discovery through the existing provider fixtures.

---------

Co-authored-by: millerben95 <millerben95@users.noreply.github.com>
2026-09-30 14:56:41 +01:00

112 lines
4.0 KiB
TypeScript

import { homedir } from "node:os";
import path from "node:path";
import { z } from "zod";
import type { UsageSourceRegistration } from "@getpaseo/plugin/server";
import type { ProviderLaunch } from "@getpaseo/plugin/server/provider";
import {
hashAccountKey,
unavailableUsage,
windowFromUsedPct,
toneFromUsedPct,
} from "@getpaseo/plugin/server/usage";
import { MspConnection } from "./connection.js";
import { usageSchema } from "./wire.js";
const inputSchema = z.object({ account: z.string() }).strict();
type Observation = z.infer<typeof usageSchema>;
export class Usage {
private readonly launches = new Map<string, ProviderLaunch>();
private readonly sessions = new Map<
string,
{ account: string; read: () => Promise<Observation> }
>();
remember(launch: ProviderLaunch): { account: string } {
// MSP 1.4.1 has no stable account ID; swapping accounts in one config directory shares a report ID.
const configHome =
launch.env.XDG_CONFIG_HOME ?? path.join(launch.env.HOME ?? homedir(), ".config");
const key = hashAccountKey(path.resolve(configHome, "muse"));
this.launches.set(key, launch);
return { account: key };
}
attach(id: string, launch: ProviderLaunch, read: () => Promise<Observation>): void {
this.sessions.set(id, { ...this.remember(launch), read });
}
detach(id: string): void {
this.sessions.delete(id);
}
private async read(account: string, launch: ProviderLaunch): Promise<Observation> {
const readers = [...this.sessions.values()].filter((session) => session.account === account);
if (readers.length > 0) {
const observations = await Promise.all(readers.map((session) => session.read()));
const usages = observations.flatMap((observation) =>
observation.usage ? [observation.usage] : [],
);
usages.sort((a, b) => b.observedAtMs - a.observedAtMs);
return usages.length > 0 ? { usage: usages[0] } : {};
}
const host = new MspConnection({ launch });
try {
await host.initialize();
return await host.request("usage/read", {}, usageSchema);
} finally {
await host.close();
}
}
registration(): UsageSourceRegistration {
return {
id: "muse",
label: "Muse Code",
icon: "icon.svg",
input: inputSchema,
discover: async () => [...this.launches.keys()].map((account) => ({ account })),
identify: async (input) => {
const { account } = inputSchema.parse(input);
return this.launches.has(account) ? { key: account, label: "Muse Code" } : null;
},
fetch: async (input) => {
const { account } = inputSchema.parse(input);
const launch = this.launches.get(account);
if (!launch) return unavailableUsage();
try {
const { usage } = await this.read(account, launch);
if (!usage) return unavailableUsage();
return {
status: "available",
planLabel: usage.tier,
windows: [
windowFromUsedPct({
id: "five_hour",
label: `${usage.window.windowDurationMins / 60} hours`,
utilizationPct: usage.window.usedPercent,
resetsAt: new Date(usage.window.resetsAtMs).toISOString(),
tone: toneFromUsedPct(usage.window.usedPercent),
}),
windowFromUsedPct({
id: "weekly",
label: "Weekly",
utilizationPct: usage.weekly.usedPercent,
resetsAt: new Date(usage.weekly.resetsAtMs).toISOString(),
tone: toneFromUsedPct(usage.weekly.usedPercent),
}),
],
details: [
{
id: "observed",
label: "Observed",
value: new Date(usage.observedAtMs).toISOString(),
},
],
};
} catch (error) {
return {
status: "error",
windows: [],
error: error instanceof Error ? error.message : String(error),
};
}
},
};
}
}