mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 02:15:02 +00:00
Restore desktop usage and subscription discovery across harnesses (#5844)
* fix(app): hide the sidebar Usage item while it has no summary Without a summary window with data, the item fell back to a plain "Usage" row that duplicated the footer's Usage icon. It now renders nothing, and the footer rows leave it out so an otherwise empty rows block and its separator disappear with it. * fix(desktop): restore built-in usage in packaged apps The external plugin compiler cannot traverse app.asar, and dependency packaging drops declarations needed by import validation. Ship complete built-in resources beside the archive and verify registration in the packaged desktop smoke check. * fix(usage): restore login discovery across harnesses Discover subscription accounts independently of agent sessions and retain each account’s logins in preference order so an unusable credential can fall back to another login for the same account.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { discoverOmp, piAuthPath, readHarness, readJson, type StoreLookup } from "./stores.js";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
@@ -32,28 +32,56 @@ const responseSchema = z.object({
|
||||
credits: z.object({ balance: number.optional() }).nullish(),
|
||||
});
|
||||
|
||||
export async function readAuth(
|
||||
_input: CodexUsageInput,
|
||||
): Promise<{ token: string; accountId?: string; idToken?: string } | null> {
|
||||
const candidates = [
|
||||
...(process.env["CODEX_HOME"] ? [join(process.env["CODEX_HOME"], "auth.json")] : []),
|
||||
join(homedir(), ".config", "codex", "auth.json"),
|
||||
join(homedir(), ".codex", "auth.json"),
|
||||
interface Auth {
|
||||
token: string;
|
||||
accountId?: string;
|
||||
idToken?: string;
|
||||
expires?: number;
|
||||
}
|
||||
|
||||
export async function discover(lookup: StoreLookup = {}): Promise<CodexUsageInput[]> {
|
||||
const env = lookup.env ?? process.env;
|
||||
const home = lookup.home ?? homedir();
|
||||
const paths = [
|
||||
...(env.CODEX_HOME ? [join(env.CODEX_HOME, "auth.json")] : []),
|
||||
join(home, ".codex", "auth.json"),
|
||||
];
|
||||
for (const path of candidates) {
|
||||
try {
|
||||
const auth = authSchema.parse(JSON.parse(await readFile(path, "utf8")));
|
||||
if (auth.tokens?.access_token)
|
||||
return {
|
||||
token: auth.tokens.access_token,
|
||||
accountId: auth.tokens.account_id,
|
||||
idToken: auth.tokens.id_token,
|
||||
};
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const candidates: CodexUsageInput[] = [...new Set(paths)].map((path) => ({
|
||||
route: { store: "codex", path },
|
||||
}));
|
||||
candidates.push(
|
||||
{
|
||||
route: {
|
||||
store: "opencode",
|
||||
path: join(env.XDG_DATA_HOME || join(home, ".local", "share"), "opencode", "auth.json"),
|
||||
},
|
||||
},
|
||||
{ route: { store: "pi", path: piAuthPath(lookup) } },
|
||||
...discoverOmp(lookup).map((route) => ({ route })),
|
||||
);
|
||||
const present: CodexUsageInput[] = [];
|
||||
for (const input of candidates) if (await readAuth(input, lookup)) present.push(input);
|
||||
return present;
|
||||
}
|
||||
|
||||
export async function readAuth(
|
||||
input: CodexUsageInput,
|
||||
lookup: StoreLookup = {},
|
||||
): Promise<Auth | null> {
|
||||
const route = input.route;
|
||||
if (route.store !== "codex") {
|
||||
const oauth = await readHarness(route, lookup);
|
||||
return oauth
|
||||
? { token: oauth.access, accountId: oauth.accountId, expires: oauth.expires }
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
const auth = authSchema.safeParse(await readJson(route.path));
|
||||
if (!auth.success || !auth.data.tokens?.access_token) return null;
|
||||
return {
|
||||
token: auth.data.tokens.access_token,
|
||||
accountId: auth.data.tokens.account_id,
|
||||
idToken: auth.data.tokens.id_token,
|
||||
};
|
||||
}
|
||||
|
||||
function usageWindow(
|
||||
@@ -73,9 +101,11 @@ function usageWindow(
|
||||
export async function fetchUsage(
|
||||
input: CodexUsageInput,
|
||||
fetchApi: typeof fetch = fetch,
|
||||
lookup: StoreLookup = {},
|
||||
): Promise<UsageReport> {
|
||||
const auth = await readAuth(input);
|
||||
if (!auth) return { status: "unavailable", windows: [] };
|
||||
const auth = await readAuth(input, lookup);
|
||||
if (!auth || (auth.expires !== undefined && auth.expires <= (lookup.now ?? Date.now)()))
|
||||
return { status: "unavailable", windows: [] };
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${auth.token}`,
|
||||
Accept: "application/json",
|
||||
@@ -151,8 +181,8 @@ function claimString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
export async function identify(input: CodexUsageInput) {
|
||||
const auth = await readAuth(input);
|
||||
export async function identify(input: CodexUsageInput, lookup: StoreLookup = {}) {
|
||||
const auth = await readAuth(input, lookup);
|
||||
if (!auth) return null;
|
||||
const access = jwtClaims(auth.token);
|
||||
const id = jwtClaims(auth.idToken);
|
||||
|
||||
Reference in New Issue
Block a user