@getpaseo/server 0.11.0-beta.1 → 0.11.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/builtin-plugins/claude-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/claude-usage-source/server/stores.ts +131 -0
- package/dist/server/builtin-plugins/claude-usage-source/server/usage.ts +102 -50
- package/dist/server/builtin-plugins/claude-usage-source/shared/input.ts +13 -1
- package/dist/server/builtin-plugins/codex-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/codex-usage-source/server/stores.ts +131 -0
- package/dist/server/builtin-plugins/codex-usage-source/server/usage.ts +70 -30
- package/dist/server/builtin-plugins/codex-usage-source/shared/input.ts +11 -1
- package/dist/server/builtin-plugins/copilot-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/copilot-usage-source/server/usage.ts +28 -39
- package/dist/server/builtin-plugins/copilot-usage-source/shared/input.ts +6 -2
- package/dist/server/builtin-plugins/cursor-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/cursor-usage-source/server/usage.ts +53 -54
- package/dist/server/builtin-plugins/cursor-usage-source/shared/input.ts +6 -2
- package/dist/server/builtin-plugins/grok-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/grok-usage-source/server/usage.ts +23 -27
- package/dist/server/builtin-plugins/grok-usage-source/shared/input.ts +6 -2
- package/dist/server/builtin-plugins/kimi-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/kimi-usage-source/server/usage.ts +44 -67
- package/dist/server/builtin-plugins/kimi-usage-source/shared/input.ts +6 -2
- package/dist/server/builtin-plugins/minimax-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/minimax-usage-source/server/usage.ts +64 -100
- package/dist/server/builtin-plugins/minimax-usage-source/shared/input.ts +6 -2
- package/dist/server/builtin-plugins/muse-provider/server/usage.ts +9 -9
- package/dist/server/builtin-plugins/opencode-go-usage-source/index.server.ts +1 -2
- package/dist/server/builtin-plugins/opencode-go-usage-source/server/usage.ts +8 -12
- package/dist/server/builtin-plugins/opencode-go-usage-source/shared/input.ts +1 -2
- package/dist/server/builtin-plugins/package.json +1 -1
- package/dist/server/builtin-plugins/zai-usage-source/index.server.ts +2 -3
- package/dist/server/builtin-plugins/zai-usage-source/server/usage.ts +13 -11
- package/dist/server/builtin-plugins/zai-usage-source/shared/input.ts +6 -2
- package/dist/server/server/orchestration-skills/internal/operations.js +19 -5
- package/dist/server/server/orchestration-skills/internal/sync.d.ts +12 -0
- package/dist/server/server/orchestration-skills/internal/sync.js +48 -2
- package/dist/server/server/plugins/builtin/index.js +6 -0
- package/dist/server/server/plugins/compiler.js +4 -0
- package/dist/server/server/plugins/index.d.ts +18 -3
- package/dist/server/server/plugins/index.js +1 -2
- package/dist/server/server/plugins/plugin-process-protocol.d.ts +0 -5
- package/dist/server/server/plugins/plugin-process-protocol.js +0 -8
- package/dist/server/server/plugins/plugin-process.js +2 -7
- package/dist/server/server/plugins/runtime.d.ts +0 -1
- package/dist/server/server/plugins/runtime.js +0 -11
- package/dist/server/server/plugins/test-fixtures/usage-source/index.server.js +11 -2
- package/dist/server/server/plugins/test-fixtures/usage-source-directory/index.server.js +11 -2
- package/dist/server/server/plugins/usage-sources/index.d.ts +7 -9
- package/dist/server/server/plugins/usage-sources/index.js +98 -71
- package/dist/server/web-ui/_expo/static/js/web/{index-87a07caf197cc320e593332ed2d5e20f.js → index-1a3199cb049d45e47bfd4c216ce98838.js} +13 -13
- package/dist/server/web-ui/_expo/static/js/web/index-1a3199cb049d45e47bfd4c216ce98838.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-1a3199cb049d45e47bfd4c216ce98838.js.gz +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.gz +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { discoverOmp, piAuthPath, readHarness, readJson, type StoreLookup } from "./stores.js";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
balanceToneFromRemaining,
|
|
6
|
+
hashAccountKey,
|
|
7
|
+
unavailable,
|
|
8
|
+
type UsageAccount,
|
|
6
9
|
toneFromUsedPct,
|
|
7
10
|
windowFromUsedPct,
|
|
8
11
|
type UsageReport,
|
|
@@ -32,28 +35,59 @@ const responseSchema = z.object({
|
|
|
32
35
|
credits: z.object({ balance: number.optional() }).nullish(),
|
|
33
36
|
});
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
interface Auth {
|
|
39
|
+
token: string;
|
|
40
|
+
accountId?: string;
|
|
41
|
+
idToken?: string;
|
|
42
|
+
expires?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function discover(lookup: StoreLookup = {}): Promise<UsageAccount[]> {
|
|
46
|
+
const env = lookup.env ?? process.env;
|
|
47
|
+
const home = lookup.home ?? homedir();
|
|
48
|
+
const paths = [
|
|
49
|
+
...(env.CODEX_HOME ? [join(env.CODEX_HOME, "auth.json")] : []),
|
|
50
|
+
join(home, ".codex", "auth.json"),
|
|
42
51
|
];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
}
|
|
52
|
+
const candidates: CodexUsageInput[] = [...new Set(paths)].map((path) => ({
|
|
53
|
+
route: { store: "codex", path },
|
|
54
|
+
}));
|
|
55
|
+
candidates.push(
|
|
56
|
+
{
|
|
57
|
+
route: {
|
|
58
|
+
store: "opencode",
|
|
59
|
+
path: join(env.XDG_DATA_HOME || join(home, ".local", "share"), "opencode", "auth.json"),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{ route: { store: "pi", path: piAuthPath(lookup) } },
|
|
63
|
+
...discoverOmp(lookup).map((route) => ({ route })),
|
|
64
|
+
);
|
|
65
|
+
const present: UsageAccount[] = [];
|
|
66
|
+
for (const input of candidates) {
|
|
67
|
+
const auth = await readAuth(input, lookup);
|
|
68
|
+
if (auth) present.push({ ...accountIdentity(auth, input), input });
|
|
69
|
+
}
|
|
70
|
+
return present;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function readAuth(
|
|
74
|
+
input: CodexUsageInput,
|
|
75
|
+
lookup: StoreLookup = {},
|
|
76
|
+
): Promise<Auth | null> {
|
|
77
|
+
const route = input.route;
|
|
78
|
+
if (route.store !== "codex") {
|
|
79
|
+
const oauth = await readHarness(route, lookup);
|
|
80
|
+
return oauth
|
|
81
|
+
? { token: oauth.access, accountId: oauth.accountId, expires: oauth.expires }
|
|
82
|
+
: null;
|
|
55
83
|
}
|
|
56
|
-
|
|
84
|
+
const auth = authSchema.safeParse(await readJson(route.path));
|
|
85
|
+
if (!auth.success || !auth.data.tokens?.access_token) return null;
|
|
86
|
+
return {
|
|
87
|
+
token: auth.data.tokens.access_token,
|
|
88
|
+
accountId: auth.data.tokens.account_id,
|
|
89
|
+
idToken: auth.data.tokens.id_token,
|
|
90
|
+
};
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
function usageWindow(
|
|
@@ -73,9 +107,17 @@ function usageWindow(
|
|
|
73
107
|
export async function fetchUsage(
|
|
74
108
|
input: CodexUsageInput,
|
|
75
109
|
fetchApi: typeof fetch = fetch,
|
|
110
|
+
lookup: StoreLookup = {},
|
|
76
111
|
): Promise<UsageReport> {
|
|
77
|
-
const auth = await readAuth(input);
|
|
78
|
-
if (!auth)
|
|
112
|
+
const auth = await readAuth(input, lookup);
|
|
113
|
+
if (!auth) throw new Error("Codex login store no longer exists");
|
|
114
|
+
const refreshedBy = input.route.store;
|
|
115
|
+
if (auth.expires !== undefined && auth.expires <= (lookup.now ?? Date.now)())
|
|
116
|
+
return unavailable({
|
|
117
|
+
kind: "expired",
|
|
118
|
+
expiresAt: new Date(auth.expires).toISOString(),
|
|
119
|
+
refreshedBy,
|
|
120
|
+
});
|
|
79
121
|
const headers: Record<string, string> = {
|
|
80
122
|
Authorization: `Bearer ${auth.token}`,
|
|
81
123
|
Accept: "application/json",
|
|
@@ -87,10 +129,10 @@ export async function fetchUsage(
|
|
|
87
129
|
signal: AbortSignal.timeout(15_000),
|
|
88
130
|
});
|
|
89
131
|
if (response.status === 401 || response.status === 403)
|
|
90
|
-
return {
|
|
132
|
+
return unavailable({ kind: "rejected", status: response.status, refreshedBy });
|
|
91
133
|
if (!response.ok) throw new Error(`Codex usage API returned ${response.status}`);
|
|
92
134
|
const text = await response.text();
|
|
93
|
-
if (text.trim().startsWith("<"))
|
|
135
|
+
if (text.trim().startsWith("<")) throw new Error("Codex usage API returned HTML");
|
|
94
136
|
const usage = responseSchema.parse(JSON.parse(text));
|
|
95
137
|
const windows = [
|
|
96
138
|
usageWindow(
|
|
@@ -151,9 +193,7 @@ function claimString(value: unknown): string | null {
|
|
|
151
193
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
152
194
|
}
|
|
153
195
|
|
|
154
|
-
|
|
155
|
-
const auth = await readAuth(input);
|
|
156
|
-
if (!auth) return null;
|
|
196
|
+
function accountIdentity(auth: Auth, input: CodexUsageInput): { key: string; label?: string } {
|
|
157
197
|
const access = jwtClaims(auth.token);
|
|
158
198
|
const id = jwtClaims(auth.idToken);
|
|
159
199
|
const accessAuth = claimObject(access, "https://api.openai.com/auth");
|
|
@@ -163,11 +203,11 @@ export async function identify(input: CodexUsageInput) {
|
|
|
163
203
|
claimString(accessAuth?.["chatgpt_account_id"]) ??
|
|
164
204
|
claimString(access?.["chatgpt_account_id"]) ??
|
|
165
205
|
claimString(idAuth?.["chatgpt_account_id"]);
|
|
166
|
-
|
|
206
|
+
|
|
167
207
|
const label =
|
|
168
208
|
claimString(claimObject(access, "https://api.openai.com/profile")?.["email"]) ??
|
|
169
209
|
claimString(access?.["email"]) ??
|
|
170
210
|
claimString(claimObject(id, "https://api.openai.com/profile")?.["email"]) ??
|
|
171
211
|
claimString(id?.["email"]);
|
|
172
|
-
return { key, ...(label ? { label } : {}) };
|
|
212
|
+
return { key: key ?? hashAccountKey(JSON.stringify(input.route)), ...(label ? { label } : {}) };
|
|
173
213
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const routeSchema = z.discriminatedUnion("store", [
|
|
4
|
+
z.object({ store: z.enum(["codex", "opencode", "pi"]), path: z.string().min(1) }).strict(),
|
|
5
|
+
z
|
|
6
|
+
.object({
|
|
7
|
+
store: z.literal("omp"),
|
|
8
|
+
path: z.string().min(1),
|
|
9
|
+
credentialId: z.number().int().positive(),
|
|
10
|
+
})
|
|
11
|
+
.strict(),
|
|
12
|
+
]);
|
|
13
|
+
export const inputSchema = z.object({ route: routeSchema }).strict();
|
|
4
14
|
export type CodexUsageInput = z.infer<typeof inputSchema>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
2
|
import { inputSchema } from "./shared/input.js";
|
|
3
|
-
import { fetchUsage,
|
|
3
|
+
import { fetchUsage, discover } from "./server/usage.js";
|
|
4
4
|
|
|
5
5
|
export default function contribute(server: PluginServerContext) {
|
|
6
6
|
server.registerUsageSource({
|
|
@@ -8,8 +8,7 @@ export default function contribute(server: PluginServerContext) {
|
|
|
8
8
|
label: "GitHub Copilot",
|
|
9
9
|
icon: "icon.svg",
|
|
10
10
|
input: inputSchema,
|
|
11
|
-
discover:
|
|
12
|
-
identify,
|
|
11
|
+
discover: () => discover(),
|
|
13
12
|
fetch: fetchUsage,
|
|
14
13
|
});
|
|
15
14
|
return () => {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { UsageInput } from "../shared/input.js";
|
|
2
|
-
import {
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
unavailable,
|
|
8
|
+
type UsageAccount,
|
|
8
9
|
type UsageReport,
|
|
9
10
|
type UsageDetail,
|
|
10
11
|
} from "@getpaseo/plugin/server/usage";
|
|
@@ -19,39 +20,36 @@ const CopilotUsageResponseSchema = z.object({
|
|
|
19
20
|
quota_reset_date: ApiOptionalStringSchema,
|
|
20
21
|
});
|
|
21
22
|
|
|
22
|
-
async function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
async function readToken(input: UsageInput): Promise<string | undefined> {
|
|
24
|
+
if (input.store === "env") return process.env[input.locator];
|
|
25
|
+
try {
|
|
26
|
+
const raw = await fs.readFile(input.locator, "utf8");
|
|
27
|
+
return raw.match(/oauth_token:\s*["']?([a-zA-Z0-9_-]+)["']?/)?.[1];
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return
|
|
33
|
+
export async function discover(): Promise<UsageAccount[]> {
|
|
34
|
+
const candidates: UsageInput[] = ["COPILOT_TOKEN", "GITHUB_TOKEN", "GITHUB_PAT"].map(
|
|
35
|
+
(locator) => ({ store: "env", locator }),
|
|
36
|
+
);
|
|
37
|
+
if (process.env.APPDATA)
|
|
38
|
+
candidates.push({
|
|
39
|
+
store: "file",
|
|
40
|
+
locator: join(process.env.APPDATA, "GitHub CLI", "hosts.yml"),
|
|
41
|
+
});
|
|
42
|
+
candidates.push({ store: "file", locator: join(homedir(), ".config", "gh", "hosts.yml") });
|
|
43
|
+
for (const input of candidates) if (await readToken(input)) return [{ key: "default", input }];
|
|
44
|
+
return [];
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
export async function fetchUsage(
|
|
43
48
|
input: UsageInput,
|
|
44
49
|
fetchApi: typeof fetch = fetch,
|
|
45
50
|
): Promise<UsageReport> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const token =
|
|
49
|
-
process.env["COPILOT_TOKEN"] ||
|
|
50
|
-
process.env["GITHUB_TOKEN"] ||
|
|
51
|
-
process.env["GITHUB_PAT"] ||
|
|
52
|
-
(await readGithubCliToken());
|
|
53
|
-
|
|
54
|
-
if (!token) return unavailableUsage();
|
|
51
|
+
const token = await readToken(input);
|
|
52
|
+
if (!token) throw new Error("Copilot login store no longer exists");
|
|
55
53
|
|
|
56
54
|
const res = await fetchApi("https://api.github.com/copilot_internal/user", {
|
|
57
55
|
signal: AbortSignal.timeout(15_000),
|
|
@@ -65,9 +63,9 @@ export async function fetchUsage(
|
|
|
65
63
|
},
|
|
66
64
|
});
|
|
67
65
|
|
|
68
|
-
if (
|
|
69
|
-
return
|
|
70
|
-
}
|
|
66
|
+
if (res.status === 401 || res.status === 403)
|
|
67
|
+
return unavailable({ kind: "rejected", status: res.status });
|
|
68
|
+
if (!res.ok) throw new Error(`Copilot usage API returned ${res.status}`);
|
|
71
69
|
|
|
72
70
|
const resp = CopilotUsageResponseSchema.parse(await res.json());
|
|
73
71
|
const details: UsageDetail[] = resp.quota_reset_date
|
|
@@ -82,12 +80,3 @@ export async function fetchUsage(
|
|
|
82
80
|
details,
|
|
83
81
|
};
|
|
84
82
|
}
|
|
85
|
-
|
|
86
|
-
export async function identify() {
|
|
87
|
-
const token =
|
|
88
|
-
process.env["COPILOT_TOKEN"] ||
|
|
89
|
-
process.env["GITHUB_TOKEN"] ||
|
|
90
|
-
process.env["GITHUB_PAT"] ||
|
|
91
|
-
(await readGithubCliToken());
|
|
92
|
-
return token ? { key: "default" } : null;
|
|
93
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
2
|
import { inputSchema } from "./shared/input.js";
|
|
3
|
-
import { fetchUsage,
|
|
3
|
+
import { fetchUsage, discover } from "./server/usage.js";
|
|
4
4
|
|
|
5
5
|
export default function contribute(server: PluginServerContext) {
|
|
6
6
|
server.registerUsageSource({
|
|
@@ -8,8 +8,7 @@ export default function contribute(server: PluginServerContext) {
|
|
|
8
8
|
label: "Cursor",
|
|
9
9
|
icon: "icon.svg",
|
|
10
10
|
input: inputSchema,
|
|
11
|
-
discover:
|
|
12
|
-
identify,
|
|
11
|
+
discover: () => discover(),
|
|
13
12
|
fetch: fetchUsage,
|
|
14
13
|
});
|
|
15
14
|
return () => {};
|
|
@@ -7,7 +7,8 @@ import { z } from "zod";
|
|
|
7
7
|
import {
|
|
8
8
|
toneFromUsedPct,
|
|
9
9
|
usedPctOf,
|
|
10
|
-
|
|
10
|
+
unavailable,
|
|
11
|
+
type UsageAccount,
|
|
11
12
|
type UsageReport,
|
|
12
13
|
type UsageBalance,
|
|
13
14
|
} from "@getpaseo/plugin/server/usage";
|
|
@@ -114,24 +115,7 @@ function cursorTokenFromDb(db: CursorStateDatabase): string | null {
|
|
|
114
115
|
return null;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
async function readCursorTokenFromSqlite(
|
|
118
|
-
const dbPaths: string[] = [];
|
|
119
|
-
if (process.env["APPDATA"]) {
|
|
120
|
-
dbPaths.push(join(process.env["APPDATA"], "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
121
|
-
}
|
|
122
|
-
dbPaths.push(
|
|
123
|
-
join(
|
|
124
|
-
homeDir,
|
|
125
|
-
"Library",
|
|
126
|
-
"Application Support",
|
|
127
|
-
"Cursor",
|
|
128
|
-
"User",
|
|
129
|
-
"globalStorage",
|
|
130
|
-
"state.vscdb",
|
|
131
|
-
),
|
|
132
|
-
);
|
|
133
|
-
dbPaths.push(join(homeDir, ".config", "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
134
|
-
|
|
118
|
+
async function readCursorTokenFromSqlite(path: string): Promise<string | null> {
|
|
135
119
|
// Held in a variable so TypeScript skips module resolution: @types/node@20 has no
|
|
136
120
|
// node:sqlite typings yet, while the runtime (Node 22+ / Electron) provides it.
|
|
137
121
|
const sqliteSpecifier: string = "node:sqlite";
|
|
@@ -142,24 +126,19 @@ async function readCursorTokenFromSqlite(homeDir: string): Promise<string | null
|
|
|
142
126
|
return null; // runtime without node:sqlite
|
|
143
127
|
}
|
|
144
128
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
} finally {
|
|
155
|
-
db?.close();
|
|
156
|
-
}
|
|
129
|
+
if (!existsSync(path)) return null;
|
|
130
|
+
let db: CursorStateDatabase | undefined;
|
|
131
|
+
try {
|
|
132
|
+
db = new sqlite.DatabaseSync(path, { readOnly: true });
|
|
133
|
+
return cursorTokenFromDb(db);
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
} finally {
|
|
137
|
+
db?.close();
|
|
157
138
|
}
|
|
158
|
-
return null;
|
|
159
139
|
}
|
|
160
140
|
|
|
161
|
-
async function readCursorTokenFromAuthJson(
|
|
162
|
-
const path = join(homeDir, ".config", "cursor", "auth.json");
|
|
141
|
+
async function readCursorTokenFromAuthJson(path: string): Promise<string | null> {
|
|
163
142
|
if (!existsSync(path)) return null;
|
|
164
143
|
try {
|
|
165
144
|
const parsed = CursorAuthStatusSchema.parse(JSON.parse(await readFile(path, "utf8")));
|
|
@@ -173,16 +152,8 @@ export async function fetchUsage(
|
|
|
173
152
|
input: UsageInput,
|
|
174
153
|
fetchApi: typeof fetch = fetch,
|
|
175
154
|
): Promise<UsageReport> {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const token =
|
|
180
|
-
process.env["CURSOR_ACCESS_TOKEN"] ||
|
|
181
|
-
process.env["CURSOR_TOKEN"] ||
|
|
182
|
-
(await readCursorTokenFromSqlite(homeDir)) ||
|
|
183
|
-
(await readCursorTokenFromAuthJson(homeDir));
|
|
184
|
-
|
|
185
|
-
if (!token) return unavailableUsage();
|
|
155
|
+
const token = await readToken(input);
|
|
156
|
+
if (!token) throw new Error("Cursor login store no longer exists");
|
|
186
157
|
|
|
187
158
|
const res = await fetchApi(
|
|
188
159
|
"https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage",
|
|
@@ -198,9 +169,9 @@ export async function fetchUsage(
|
|
|
198
169
|
},
|
|
199
170
|
);
|
|
200
171
|
|
|
201
|
-
if (
|
|
202
|
-
return
|
|
203
|
-
}
|
|
172
|
+
if (res.status === 401 || res.status === 403)
|
|
173
|
+
return unavailable({ kind: "rejected", status: res.status });
|
|
174
|
+
if (!res.ok) throw new Error(`Cursor usage API returned ${res.status}`);
|
|
204
175
|
|
|
205
176
|
const resp = CursorUsageResponseSchema.parse(await res.json());
|
|
206
177
|
const billingCycleEnd = parseCursorBillingCycleTimestamp(resp.billingCycleEnd);
|
|
@@ -230,12 +201,40 @@ export async function fetchUsage(
|
|
|
230
201
|
};
|
|
231
202
|
}
|
|
232
203
|
|
|
233
|
-
|
|
234
|
-
|
|
204
|
+
async function readToken(input: UsageInput): Promise<string | undefined> {
|
|
205
|
+
if (input.store === "env") return process.env[input.locator];
|
|
235
206
|
const token =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
207
|
+
input.store === "sqlite"
|
|
208
|
+
? await readCursorTokenFromSqlite(input.locator)
|
|
209
|
+
: await readCursorTokenFromAuthJson(input.locator);
|
|
210
|
+
return token ?? undefined;
|
|
211
|
+
}
|
|
212
|
+
export async function discover(): Promise<UsageAccount[]> {
|
|
213
|
+
const home = homedir();
|
|
214
|
+
const candidates: UsageInput[] = ["CURSOR_ACCESS_TOKEN", "CURSOR_TOKEN"].map((locator) => ({
|
|
215
|
+
store: "env",
|
|
216
|
+
locator,
|
|
217
|
+
}));
|
|
218
|
+
if (process.env.APPDATA)
|
|
219
|
+
candidates.push({
|
|
220
|
+
store: "sqlite",
|
|
221
|
+
locator: join(process.env.APPDATA, "Cursor", "User", "globalStorage", "state.vscdb"),
|
|
222
|
+
});
|
|
223
|
+
candidates.push(
|
|
224
|
+
...[
|
|
225
|
+
join(
|
|
226
|
+
home,
|
|
227
|
+
"Library",
|
|
228
|
+
"Application Support",
|
|
229
|
+
"Cursor",
|
|
230
|
+
"User",
|
|
231
|
+
"globalStorage",
|
|
232
|
+
"state.vscdb",
|
|
233
|
+
),
|
|
234
|
+
join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb"),
|
|
235
|
+
].map((locator) => ({ store: "sqlite" as const, locator })),
|
|
236
|
+
{ store: "file", locator: join(home, ".config", "cursor", "auth.json") },
|
|
237
|
+
);
|
|
238
|
+
for (const input of candidates) if (await readToken(input)) return [{ key: "default", input }];
|
|
239
|
+
return [];
|
|
241
240
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const inputSchema = z
|
|
3
|
-
|
|
2
|
+
export const inputSchema = z
|
|
3
|
+
.object({
|
|
4
|
+
store: z.enum(["env", "file", "sqlite"]),
|
|
5
|
+
locator: z.string().min(1),
|
|
6
|
+
})
|
|
7
|
+
.strict();
|
|
4
8
|
export type UsageInput = z.infer<typeof inputSchema>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
2
|
import { inputSchema } from "./shared/input.js";
|
|
3
|
-
import { fetchUsage,
|
|
3
|
+
import { fetchUsage, discover } from "./server/usage.js";
|
|
4
4
|
|
|
5
5
|
export default function contribute(server: PluginServerContext) {
|
|
6
6
|
server.registerUsageSource({
|
|
@@ -8,8 +8,7 @@ export default function contribute(server: PluginServerContext) {
|
|
|
8
8
|
label: "Grok",
|
|
9
9
|
icon: "icon.svg",
|
|
10
10
|
input: inputSchema,
|
|
11
|
-
discover:
|
|
12
|
-
identify,
|
|
11
|
+
discover: () => discover(),
|
|
13
12
|
fetch: fetchUsage,
|
|
14
13
|
});
|
|
15
14
|
return () => {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { UsageInput } from "../shared/input.js";
|
|
2
|
-
import {
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import {
|
|
7
7
|
toneFromUsedPct,
|
|
8
8
|
usedPctOf,
|
|
9
|
-
|
|
9
|
+
unavailable,
|
|
10
|
+
type UsageAccount,
|
|
10
11
|
windowFromUsedPct,
|
|
11
12
|
type UsageReport,
|
|
12
13
|
type UsageWindow,
|
|
@@ -109,23 +110,8 @@ export async function fetchUsage(
|
|
|
109
110
|
input: UsageInput,
|
|
110
111
|
fetchApi: typeof fetch = fetch,
|
|
111
112
|
): Promise<UsageReport> {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
async function readGrokToken(): Promise<string | null> {
|
|
116
|
-
// homeDir override is for tests: Windows os.homedir() ignores $HOME (uses USERPROFILE).
|
|
117
|
-
const path = join(homeDir ?? homedir(), ".grok", "auth.json");
|
|
118
|
-
if (!existsSync(path)) return null;
|
|
119
|
-
try {
|
|
120
|
-
return extractGrokTokenFromAuth(JSON.parse(await fs.readFile(path, "utf8")));
|
|
121
|
-
} catch {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const token = process.env["GROK_API_KEY"] || process.env["GROK_TOKEN"] || (await readGrokToken());
|
|
127
|
-
|
|
128
|
-
if (!token) return unavailableUsage();
|
|
113
|
+
const token = await readToken(input);
|
|
114
|
+
if (!token) throw new Error("Grok login store no longer exists");
|
|
129
115
|
|
|
130
116
|
// The Grok CLI's /usage uses ?format=credits; without it, unified-billing accounts
|
|
131
117
|
// get a zeroed legacy monthly shape (monthlyLimit.val 0) instead of real usage.
|
|
@@ -138,9 +124,9 @@ export async function fetchUsage(
|
|
|
138
124
|
},
|
|
139
125
|
});
|
|
140
126
|
|
|
141
|
-
if (
|
|
142
|
-
return
|
|
143
|
-
}
|
|
127
|
+
if (res.status === 401 || res.status === 403)
|
|
128
|
+
return unavailable({ kind: "rejected", status: res.status });
|
|
129
|
+
if (!res.ok) throw new Error(`Grok usage API returned ${res.status}`);
|
|
144
130
|
|
|
145
131
|
const resp = GrokUsageResponseSchema.parse(await res.json());
|
|
146
132
|
const balance = grokMonthlyCreditBalance(resp);
|
|
@@ -155,12 +141,22 @@ export async function fetchUsage(
|
|
|
155
141
|
};
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
if (
|
|
144
|
+
async function readToken(input: UsageInput): Promise<string | undefined> {
|
|
145
|
+
if (input.store === "env") return process.env[input.locator];
|
|
160
146
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
147
|
+
return (
|
|
148
|
+
extractGrokTokenFromAuth(JSON.parse(await fs.readFile(input.locator, "utf8"))) ?? undefined
|
|
149
|
+
);
|
|
163
150
|
} catch {
|
|
164
|
-
return
|
|
151
|
+
return undefined;
|
|
165
152
|
}
|
|
166
153
|
}
|
|
154
|
+
export async function discover(): Promise<UsageAccount[]> {
|
|
155
|
+
const candidates: UsageInput[] = ["GROK_API_KEY", "GROK_TOKEN"].map((locator) => ({
|
|
156
|
+
store: "env",
|
|
157
|
+
locator,
|
|
158
|
+
}));
|
|
159
|
+
candidates.push({ store: "file", locator: join(homedir(), ".grok", "auth.json") });
|
|
160
|
+
for (const input of candidates) if (await readToken(input)) return [{ key: "default", input }];
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
2
|
import { inputSchema } from "./shared/input.js";
|
|
3
|
-
import { fetchUsage,
|
|
3
|
+
import { fetchUsage, discover } from "./server/usage.js";
|
|
4
4
|
|
|
5
5
|
export default function contribute(server: PluginServerContext) {
|
|
6
6
|
server.registerUsageSource({
|
|
@@ -8,8 +8,7 @@ export default function contribute(server: PluginServerContext) {
|
|
|
8
8
|
label: "Kimi",
|
|
9
9
|
icon: "icon.svg",
|
|
10
10
|
input: inputSchema,
|
|
11
|
-
discover:
|
|
12
|
-
identify,
|
|
11
|
+
discover: () => discover(),
|
|
13
12
|
fetch: fetchUsage,
|
|
14
13
|
});
|
|
15
14
|
return () => {};
|