@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.
Files changed (56) hide show
  1. package/dist/server/builtin-plugins/claude-usage-source/index.server.ts +2 -3
  2. package/dist/server/builtin-plugins/claude-usage-source/server/stores.ts +131 -0
  3. package/dist/server/builtin-plugins/claude-usage-source/server/usage.ts +102 -50
  4. package/dist/server/builtin-plugins/claude-usage-source/shared/input.ts +13 -1
  5. package/dist/server/builtin-plugins/codex-usage-source/index.server.ts +2 -3
  6. package/dist/server/builtin-plugins/codex-usage-source/server/stores.ts +131 -0
  7. package/dist/server/builtin-plugins/codex-usage-source/server/usage.ts +70 -30
  8. package/dist/server/builtin-plugins/codex-usage-source/shared/input.ts +11 -1
  9. package/dist/server/builtin-plugins/copilot-usage-source/index.server.ts +2 -3
  10. package/dist/server/builtin-plugins/copilot-usage-source/server/usage.ts +28 -39
  11. package/dist/server/builtin-plugins/copilot-usage-source/shared/input.ts +6 -2
  12. package/dist/server/builtin-plugins/cursor-usage-source/index.server.ts +2 -3
  13. package/dist/server/builtin-plugins/cursor-usage-source/server/usage.ts +53 -54
  14. package/dist/server/builtin-plugins/cursor-usage-source/shared/input.ts +6 -2
  15. package/dist/server/builtin-plugins/grok-usage-source/index.server.ts +2 -3
  16. package/dist/server/builtin-plugins/grok-usage-source/server/usage.ts +23 -27
  17. package/dist/server/builtin-plugins/grok-usage-source/shared/input.ts +6 -2
  18. package/dist/server/builtin-plugins/kimi-usage-source/index.server.ts +2 -3
  19. package/dist/server/builtin-plugins/kimi-usage-source/server/usage.ts +44 -67
  20. package/dist/server/builtin-plugins/kimi-usage-source/shared/input.ts +6 -2
  21. package/dist/server/builtin-plugins/minimax-usage-source/index.server.ts +2 -3
  22. package/dist/server/builtin-plugins/minimax-usage-source/server/usage.ts +64 -100
  23. package/dist/server/builtin-plugins/minimax-usage-source/shared/input.ts +6 -2
  24. package/dist/server/builtin-plugins/muse-provider/server/usage.ts +9 -9
  25. package/dist/server/builtin-plugins/opencode-go-usage-source/index.server.ts +1 -2
  26. package/dist/server/builtin-plugins/opencode-go-usage-source/server/usage.ts +8 -12
  27. package/dist/server/builtin-plugins/opencode-go-usage-source/shared/input.ts +1 -2
  28. package/dist/server/builtin-plugins/package.json +1 -1
  29. package/dist/server/builtin-plugins/zai-usage-source/index.server.ts +2 -3
  30. package/dist/server/builtin-plugins/zai-usage-source/server/usage.ts +13 -11
  31. package/dist/server/builtin-plugins/zai-usage-source/shared/input.ts +6 -2
  32. package/dist/server/server/orchestration-skills/internal/operations.js +19 -5
  33. package/dist/server/server/orchestration-skills/internal/sync.d.ts +12 -0
  34. package/dist/server/server/orchestration-skills/internal/sync.js +48 -2
  35. package/dist/server/server/plugins/builtin/index.js +6 -0
  36. package/dist/server/server/plugins/compiler.js +4 -0
  37. package/dist/server/server/plugins/index.d.ts +18 -3
  38. package/dist/server/server/plugins/index.js +1 -2
  39. package/dist/server/server/plugins/plugin-process-protocol.d.ts +0 -5
  40. package/dist/server/server/plugins/plugin-process-protocol.js +0 -8
  41. package/dist/server/server/plugins/plugin-process.js +2 -7
  42. package/dist/server/server/plugins/runtime.d.ts +0 -1
  43. package/dist/server/server/plugins/runtime.js +0 -11
  44. package/dist/server/server/plugins/test-fixtures/usage-source/index.server.js +11 -2
  45. package/dist/server/server/plugins/test-fixtures/usage-source-directory/index.server.js +11 -2
  46. package/dist/server/server/plugins/usage-sources/index.d.ts +7 -9
  47. package/dist/server/server/plugins/usage-sources/index.js +98 -71
  48. package/dist/server/web-ui/_expo/static/js/web/{index-87a07caf197cc320e593332ed2d5e20f.js → index-1a3199cb049d45e47bfd4c216ce98838.js} +13 -13
  49. package/dist/server/web-ui/_expo/static/js/web/index-1a3199cb049d45e47bfd4c216ce98838.js.br +0 -0
  50. package/dist/server/web-ui/_expo/static/js/web/index-1a3199cb049d45e47bfd4c216ce98838.js.gz +0 -0
  51. package/dist/server/web-ui/index.html +1 -1
  52. package/dist/server/web-ui/index.html.br +0 -0
  53. package/dist/server/web-ui/index.html.gz +0 -0
  54. package/package.json +6 -6
  55. package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.br +0 -0
  56. package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.gz +0 -0
@@ -1,11 +1,12 @@
1
1
  import type { UsageInput } from "../shared/input.js";
2
- import { existsSync, promises as fs } from "node:fs";
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
- unavailableUsage,
8
+ unavailable,
9
+ type UsageAccount,
9
10
  windowFromUsedPct,
10
11
  type UsageReport,
11
12
  type UsageWindow,
@@ -251,9 +252,6 @@ export async function fetchUsage(
251
252
  input: UsageInput,
252
253
  fetchApi: typeof fetch = fetch,
253
254
  ): Promise<UsageReport> {
254
- void input;
255
- const homeDir = homedir();
256
-
257
255
  async function callUsageApi(token: string): Promise<Response> {
258
256
  return fetchApi(KIMI_USAGE_URL, {
259
257
  signal: AbortSignal.timeout(15_000),
@@ -264,52 +262,19 @@ export async function fetchUsage(
264
262
  });
265
263
  }
266
264
 
267
- async function readCredentials(): Promise<KimiCredentials | null> {
268
- const environmentToken = process.env["KIMI_TOKEN"] || process.env["KIMI_API_KEY"];
269
- if (environmentToken) {
270
- return { access_token: environmentToken };
271
- }
272
-
273
- for (const path of credentialPaths()) {
274
- const credentials = await readCredentialFile(path);
275
- if (credentials?.access_token) {
276
- return { ...credentials, access_token: credentials.access_token };
277
- }
278
- }
279
- return null;
280
- }
281
-
282
- function credentialPaths(): string[] {
283
- const home = homeDir;
284
- return [
285
- join(
286
- process.env["KIMI_CODE_HOME"] || join(home, ".kimi-code"),
287
- "credentials",
288
- "kimi-code.json",
289
- ),
290
- join(home, ".kimi", "credentials", "kimi-code.json"),
291
- ];
292
- }
293
-
294
- async function readCredentialFile(path: string): Promise<KimiAuth | null> {
295
- if (!existsSync(path)) return null;
296
- try {
297
- return KimiAuthSchema.parse(JSON.parse(await fs.readFile(path, "utf8")));
298
- } catch {
299
- return null;
300
- }
301
- }
302
-
303
- const credentials = await readCredentials();
304
- if (!credentials) return unavailableUsage();
265
+ const credentials = await readCredentials(input);
266
+ if (!credentials) throw new Error("Kimi login store no longer exists");
267
+ if (credentials.expires_at != null && credentials.expires_at * 1000 <= Date.now())
268
+ return unavailable({
269
+ kind: "expired",
270
+ expiresAt: new Date(credentials.expires_at * 1000).toISOString(),
271
+ });
305
272
 
306
273
  const res = await callUsageApi(credentials.access_token);
307
274
 
308
- if (!res.ok) {
309
- // Read-only on credentials; the Kimi CLI owns refresh. See docs/providers.md.
310
-
311
- return unavailableUsage();
312
- }
275
+ if (res.status === 401 || res.status === 403)
276
+ return unavailable({ kind: "rejected", status: res.status });
277
+ if (!res.ok) throw new Error(`Kimi usage API returned ${res.status}`);
313
278
 
314
279
  const windows = kimiUsageWindowsFromPayload(await res.json());
315
280
 
@@ -322,24 +287,36 @@ export async function fetchUsage(
322
287
  };
323
288
  }
324
289
 
325
- export async function identify() {
326
- if (process.env["KIMI_TOKEN"] || process.env["KIMI_API_KEY"]) return { key: "default" };
327
- const home = homedir();
328
- const paths = [
329
- join(
330
- process.env["KIMI_CODE_HOME"] || join(home, ".kimi-code"),
331
- "credentials",
332
- "kimi-code.json",
333
- ),
334
- join(home, ".kimi", "credentials", "kimi-code.json"),
335
- ];
336
- for (const path of paths) {
337
- try {
338
- const auth = KimiAuthSchema.parse(JSON.parse(await fs.readFile(path, "utf8")));
339
- if (auth.access_token) return { key: "default" };
340
- } catch {
341
- continue;
342
- }
290
+ async function readCredentials(input: UsageInput): Promise<KimiCredentials | undefined> {
291
+ if (input.store === "env") {
292
+ const token = process.env[input.locator];
293
+ return token ? { access_token: token } : undefined;
343
294
  }
344
- return null;
295
+ try {
296
+ const credentials = KimiAuthSchema.parse(JSON.parse(await fs.readFile(input.locator, "utf8")));
297
+ return credentials.access_token
298
+ ? { ...credentials, access_token: credentials.access_token }
299
+ : undefined;
300
+ } catch {
301
+ return undefined;
302
+ }
303
+ }
304
+ export async function discover(): Promise<UsageAccount[]> {
305
+ const candidates: UsageInput[] = ["KIMI_TOKEN", "KIMI_API_KEY"].map((locator) => ({
306
+ store: "env",
307
+ locator,
308
+ }));
309
+ candidates.push(
310
+ ...[
311
+ join(
312
+ process.env.KIMI_CODE_HOME || join(homedir(), ".kimi-code"),
313
+ "credentials",
314
+ "kimi-code.json",
315
+ ),
316
+ join(homedir(), ".kimi", "credentials", "kimi-code.json"),
317
+ ].map((locator) => ({ store: "file" as const, locator })),
318
+ );
319
+ for (const input of candidates)
320
+ if (await readCredentials(input)) return [{ key: "default", input }];
321
+ return [];
345
322
  }
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
- export const inputSchema = z.object({}).strict();
3
-
2
+ export const inputSchema = z
3
+ .object({
4
+ store: z.enum(["env", "file"]),
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, identify } from "./server/usage.js";
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: "MiniMax",
9
9
  icon: "icon.svg",
10
10
  input: inputSchema,
11
- discover: async () => [{}],
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 { existsSync, promises as fs } from "node:fs";
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
- unavailableUsage,
7
+ unavailable,
8
+ type UsageAccount,
8
9
  windowFromUsedPct,
9
10
  type UsageReport,
10
11
  type UsageWindow,
@@ -148,83 +149,10 @@ export async function fetchUsage(
148
149
  input: UsageInput,
149
150
  fetchApi: typeof fetch = fetch,
150
151
  ): Promise<UsageReport> {
151
- void input;
152
- const configPath = join(homedir(), ".mmx", "config.json");
153
- const credentialsPath = join(homedir(), ".mmx", "credentials.json");
154
- const env = process.env;
155
- const now = Date.now;
156
-
157
- async function resolveAuth(): Promise<MiniMaxResolvedAuth | null> {
158
- const envToken = env["MINIMAX_API_KEY"];
159
- if (envToken) {
160
- const envBase = env["MINIMAX_BASE_URL"];
161
- return {
162
- token: envToken,
163
- baseUrl: resolveBaseUrl({ baseUrl: envBase }),
164
- };
165
- }
166
-
167
- const credentials = await readCredentials();
168
- if (credentials?.access_token && !isExpired(credentials.expires_at)) {
169
- return {
170
- token: credentials.access_token,
171
- baseUrl: resolveBaseUrl({ baseUrl: credentials.resource_url }),
172
- };
173
- }
174
-
175
- const config = await readConfig();
176
- if (config?.api_key) {
177
- return {
178
- token: config.api_key,
179
- baseUrl: resolveBaseUrl({
180
- baseUrl: config.base_url,
181
- region: config.region,
182
- }),
183
- };
184
- }
185
-
186
- if (config?.oauth?.access_token && !isExpired(config.oauth.expires_at)) {
187
- return {
188
- token: config.oauth.access_token,
189
- baseUrl: resolveBaseUrl({
190
- baseUrl: config.oauth.resource_url ?? config.base_url,
191
- region: config.region,
192
- }),
193
- };
194
- }
195
-
196
- return null;
197
- }
198
-
199
- function isExpired(expiresAt: string | null | undefined): boolean {
200
- if (!expiresAt) return false;
201
- const parsed = Date.parse(expiresAt);
202
- if (!Number.isFinite(parsed)) return false;
203
- return parsed <= now();
204
- }
205
-
206
- async function readCredentials(): Promise<z.infer<typeof MiniMaxCredentialsSchema> | null> {
207
- if (!existsSync(credentialsPath)) return null;
208
- try {
209
- const raw = JSON.parse(await fs.readFile(credentialsPath, "utf8"));
210
- return MiniMaxCredentialsSchema.parse(raw);
211
- } catch {
212
- return null;
213
- }
214
- }
215
-
216
- async function readConfig(): Promise<z.infer<typeof MiniMaxConfigSchema> | null> {
217
- if (!existsSync(configPath)) return null;
218
- try {
219
- const raw = JSON.parse(await fs.readFile(configPath, "utf8"));
220
- return MiniMaxConfigSchema.parse(raw);
221
- } catch {
222
- return null;
223
- }
224
- }
225
-
226
- const auth = await resolveAuth();
227
- if (!auth) return unavailableUsage();
152
+ const auth = await readAuth(input);
153
+ if (!auth) throw new Error("MiniMax login store no longer exists");
154
+ if (auth.expiresAt && Date.parse(auth.expiresAt) <= Date.now())
155
+ return unavailable({ kind: "expired", expiresAt: new Date(auth.expiresAt).toISOString() });
228
156
 
229
157
  const res = await fetchApi(`${auth.baseUrl}/v1/token_plan/remains`, {
230
158
  signal: AbortSignal.timeout(15_000),
@@ -234,15 +162,18 @@ export async function fetchUsage(
234
162
  },
235
163
  });
236
164
 
237
- if (!res.ok) {
238
- return unavailableUsage();
239
- }
165
+ if (res.status === 401 || res.status === 403)
166
+ return unavailable({ kind: "rejected", status: res.status });
167
+ if (!res.ok) throw new Error(`MiniMax usage API returned ${res.status}`);
240
168
 
241
169
  const resp = MiniMaxQuotaResponseSchema.parse(await res.json());
242
170
 
243
171
  const statusCode = resp.base_resp?.status_code;
244
172
  if (typeof statusCode === "number" && statusCode !== 0) {
245
- return unavailableUsage();
173
+ return unavailable({
174
+ kind: "no_quota",
175
+ detail: resp.base_resp?.status_msg ?? "No active coding plan",
176
+ });
246
177
  }
247
178
 
248
179
  const models = resp.model_remains ?? [];
@@ -256,8 +187,10 @@ export async function fetchUsage(
256
187
  if (weeklyWindow) windows.push(weeklyWindow);
257
188
  }
258
189
 
190
+ if (windows.length === 0)
191
+ return unavailable({ kind: "no_quota", detail: "No active coding plan" });
259
192
  return {
260
- status: windows.length > 0 ? "available" : "unavailable",
193
+ status: "available",
261
194
  planLabel: undefined,
262
195
  windows,
263
196
  balances: [],
@@ -265,24 +198,55 @@ export async function fetchUsage(
265
198
  };
266
199
  }
267
200
 
268
- export async function identify() {
269
- if (process.env["MINIMAX_API_KEY"]) return { key: "default" };
270
- const home = join(homedir(), ".mmx");
271
- try {
272
- const credentials = MiniMaxCredentialsSchema.parse(
273
- JSON.parse(await fs.readFile(join(home, "credentials.json"), "utf8")),
274
- );
275
- if (credentials.access_token) return { key: "default" };
276
- } catch {
277
- /* Check config next. */
201
+ async function readAuth(
202
+ input: UsageInput,
203
+ ): Promise<(MiniMaxResolvedAuth & { expiresAt?: string }) | undefined> {
204
+ if (input.store === "env") {
205
+ const token = process.env[input.locator];
206
+ return token
207
+ ? { token, baseUrl: resolveBaseUrl({ baseUrl: process.env.MINIMAX_BASE_URL }) }
208
+ : undefined;
278
209
  }
279
210
  try {
280
- const config = MiniMaxConfigSchema.parse(
281
- JSON.parse(await fs.readFile(join(home, "config.json"), "utf8")),
282
- );
283
- if (config.api_key || config.oauth?.access_token) return { key: "default" };
211
+ const raw: unknown = JSON.parse(await fs.readFile(input.locator, "utf8"));
212
+ if (input.store === "credentials") {
213
+ const credentials = MiniMaxCredentialsSchema.parse(raw);
214
+ return credentials.access_token
215
+ ? {
216
+ token: credentials.access_token,
217
+ baseUrl: resolveBaseUrl({ baseUrl: credentials.resource_url }),
218
+ expiresAt: credentials.expires_at ?? undefined,
219
+ }
220
+ : undefined;
221
+ }
222
+ const config = MiniMaxConfigSchema.parse(raw);
223
+ if (config.api_key)
224
+ return {
225
+ token: config.api_key,
226
+ baseUrl: resolveBaseUrl({ baseUrl: config.base_url, region: config.region }),
227
+ };
228
+ return config.oauth?.access_token
229
+ ? {
230
+ token: config.oauth.access_token,
231
+ baseUrl: resolveBaseUrl({
232
+ baseUrl: config.oauth.resource_url ?? config.base_url,
233
+ region: config.region,
234
+ }),
235
+ expiresAt: config.oauth.expires_at ?? undefined,
236
+ }
237
+ : undefined;
284
238
  } catch {
285
- /* No configured account. */
239
+ return undefined;
286
240
  }
287
- return null;
241
+ }
242
+ export async function discover(): Promise<UsageAccount[]> {
243
+ const candidates: UsageInput[] = [
244
+ { store: "env", locator: "MINIMAX_API_KEY" },
245
+ { store: "credentials", locator: join(homedir(), ".mmx", "credentials.json") },
246
+ { store: "config", locator: join(homedir(), ".mmx", "config.json") },
247
+ ];
248
+ const accounts: UsageAccount[] = [];
249
+ for (const input of candidates)
250
+ if (await readAuth(input)) accounts.push({ key: "default", input });
251
+ return accounts;
288
252
  }
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
- export const inputSchema = z.object({}).strict();
3
-
2
+ export const inputSchema = z
3
+ .object({
4
+ store: z.enum(["env", "credentials", "config"]),
5
+ locator: z.string().min(1),
6
+ })
7
+ .strict();
4
8
  export type UsageInput = z.infer<typeof inputSchema>;
@@ -5,7 +5,7 @@ import type { UsageSourceRegistration } from "@getpaseo/plugin/server";
5
5
  import type { ProviderLaunch } from "@getpaseo/plugin/server/provider";
6
6
  import {
7
7
  hashAccountKey,
8
- unavailableUsage,
8
+ unavailable,
9
9
  windowFromUsedPct,
10
10
  toneFromUsedPct,
11
11
  } from "@getpaseo/plugin/server/usage";
@@ -59,18 +59,19 @@ export class Usage {
59
59
  label: "Muse Code",
60
60
  icon: "icon.svg",
61
61
  input: inputSchema,
62
- discover: async () => [...this.launches.keys()].map((account) => ({ account })),
63
- identify: async (input) => {
64
- const { account } = inputSchema.parse(input);
65
- return this.launches.has(account) ? { key: account, label: "Muse Code" } : null;
66
- },
62
+ discover: async () =>
63
+ [...this.launches.keys()].map((account) => ({
64
+ key: account,
65
+ label: "Muse Code",
66
+ input: { account },
67
+ })),
67
68
  fetch: async (input) => {
68
69
  const { account } = inputSchema.parse(input);
69
70
  const launch = this.launches.get(account);
70
- if (!launch) return unavailableUsage();
71
+ if (!launch) throw new Error("Muse login no longer exists");
71
72
  try {
72
73
  const { usage } = await this.read(account, launch);
73
- if (!usage) return unavailableUsage();
74
+ if (!usage) return unavailable({ kind: "no_quota", detail: "No usage quota reported" });
74
75
  return {
75
76
  status: "available",
76
77
  planLabel: usage.tier,
@@ -105,7 +106,6 @@ export class Usage {
105
106
  } catch (error) {
106
107
  return {
107
108
  status: "error",
108
- windows: [],
109
109
  error: error instanceof Error ? error.message : String(error),
110
110
  };
111
111
  }
@@ -1,6 +1,6 @@
1
1
  import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { inputSchema } from "./shared/input.js";
3
- import { discover, fetchUsage, identify } from "./server/usage.js";
3
+ import { discover, fetchUsage } from "./server/usage.js";
4
4
 
5
5
  export default function contribute(server: PluginServerContext) {
6
6
  server.registerUsageSource({
@@ -9,7 +9,6 @@ export default function contribute(server: PluginServerContext) {
9
9
  icon: "icon.svg",
10
10
  input: inputSchema,
11
11
  discover,
12
- identify,
13
12
  fetch: fetchUsage,
14
13
  });
15
14
  return () => {};
@@ -4,6 +4,8 @@ import { join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import {
6
6
  hashAccountKey,
7
+ unavailable,
8
+ type UsageAccount,
7
9
  toneFromUsedPct,
8
10
  windowFromUsedPct,
9
11
  type UsageReport,
@@ -44,23 +46,22 @@ export async function readDefaultKey(path = authPath()): Promise<string | null>
44
46
  }
45
47
  }
46
48
 
47
- export async function discover(path = authPath()): Promise<Array<{}>> {
48
- return (await readDefaultKey(path)) ? [{}] : [];
49
+ export async function discover(path = authPath()): Promise<UsageAccount[]> {
50
+ return (await readDefaultKey(path)) ? [{ key: hashAccountKey(path), input: { path } }] : [];
49
51
  }
50
52
 
51
53
  export async function fetchUsage(
52
- _input: Input,
54
+ input: Input,
53
55
  fetchApi: typeof fetch = fetch,
54
- path = authPath(),
55
56
  ): Promise<UsageReport> {
56
- const apiKey = await readDefaultKey(path);
57
- if (!apiKey) return { status: "unavailable", windows: [] };
57
+ const apiKey = await readDefaultKey(input.path);
58
+ if (!apiKey) throw new Error("OpenCode Go login store no longer exists");
58
59
  const response = await fetchApi("https://opencode.ai/zen/go/v1/usage", {
59
60
  headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
60
61
  signal: AbortSignal.timeout(15_000),
61
62
  });
62
63
  if (response.status === 401 || response.status === 403)
63
- return { status: "unavailable", windows: [] };
64
+ return unavailable({ kind: "rejected", status: response.status, refreshedBy: "opencode" });
64
65
  if (!response.ok) throw new Error(`OpenCode Go usage API returned ${response.status}`);
65
66
  const data = responseSchema.parse(await response.json());
66
67
  const windows = (
@@ -82,8 +83,3 @@ export async function fetchUsage(
82
83
  );
83
84
  return { status: "available", planLabel: "Go", windows };
84
85
  }
85
-
86
- export async function identify(_input: Input, path = authPath()) {
87
- const key = await readDefaultKey(path);
88
- return key ? { key: hashAccountKey(key) } : null;
89
- }
@@ -1,4 +1,3 @@
1
1
  import { z } from "zod";
2
-
3
- export const inputSchema = z.object({}).strict();
2
+ export const inputSchema = z.object({ path: z.string().min(1) }).strict();
4
3
  export type Input = z.infer<typeof inputSchema>;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/builtin-plugins",
3
- "version": "0.11.0-beta.1",
3
+ "version": "0.11.0-beta.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { inputSchema } from "./shared/input.js";
3
- import { fetchUsage, identify } from "./server/usage.js";
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: "Z.ai",
9
9
  icon: "icon.svg",
10
10
  input: inputSchema,
11
- discover: async () => [{}],
12
- identify,
11
+ discover: () => discover(),
13
12
  fetch: fetchUsage,
14
13
  });
15
14
  return () => {};
@@ -1,7 +1,8 @@
1
1
  import type { UsageInput } from "../shared/input.js";
2
2
  import { z } from "zod";
3
3
  import {
4
- unavailableUsage,
4
+ unavailable,
5
+ type UsageAccount,
5
6
  type UsageReport,
6
7
  type UsageDetail,
7
8
  } from "@getpaseo/plugin/server/usage";
@@ -28,10 +29,8 @@ export async function fetchUsage(
28
29
  input: UsageInput,
29
30
  fetchApi: typeof fetch = fetch,
30
31
  ): Promise<UsageReport> {
31
- void input;
32
-
33
- const token = process.env["ZAI_API_KEY"] || process.env["GLM_API_KEY"];
34
- if (!token) return unavailableUsage();
32
+ const token = process.env[input.locator];
33
+ if (!token) throw new Error("Z.ai login store no longer exists");
35
34
 
36
35
  const res = await fetchApi("https://api.z.ai/api/biz/subscription/list", {
37
36
  signal: AbortSignal.timeout(15_000),
@@ -41,13 +40,13 @@ export async function fetchUsage(
41
40
  },
42
41
  });
43
42
 
44
- if (!res.ok) {
45
- return unavailableUsage();
46
- }
43
+ if (res.status === 401 || res.status === 403)
44
+ return unavailable({ kind: "rejected", status: res.status });
45
+ if (!res.ok) throw new Error(`Z.ai usage API returned ${res.status}`);
47
46
 
48
47
  const resp = ZaiUsageResponseSchema.parse(await res.json());
49
48
  const sub = resp.data?.[0];
50
- if (!sub) return unavailableUsage();
49
+ if (!sub) return unavailable({ kind: "no_quota", detail: "No active coding plan" });
51
50
 
52
51
  const details: UsageDetail[] = [];
53
52
  if (sub.status) details.push({ id: "status", label: "Status", value: sub.status });
@@ -65,6 +64,9 @@ export async function fetchUsage(
65
64
  };
66
65
  }
67
66
 
68
- export async function identify() {
69
- return process.env["ZAI_API_KEY"] || process.env["GLM_API_KEY"] ? { key: "default" } : null;
67
+ export async function discover(): Promise<UsageAccount[]> {
68
+ for (const locator of ["ZAI_API_KEY", "GLM_API_KEY"]) {
69
+ if (process.env[locator]) return [{ key: "default", input: { store: "env", locator } }];
70
+ }
71
+ return [];
70
72
  }
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
- export const inputSchema = z.object({}).strict();
3
-
2
+ export const inputSchema = z
3
+ .object({
4
+ store: z.literal("env"),
5
+ locator: z.string().min(1),
6
+ })
7
+ .strict();
4
8
  export type UsageInput = z.infer<typeof inputSchema>;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { promises as fs } from "node:fs";
3
3
  import path from "node:path";
4
- import { listFilesRecursive, removeSkill, syncSkills } from "./sync.js";
4
+ import { isLegacyCodexCopy, listFilesRecursive, readManagedFilesManifest, removeSkill, syncSkills, } from "./sync.js";
5
5
  // Names the bundle used to ship. They are never selectable, but every scan still
6
6
  // covers them so an older install's copies get cleaned up.
7
7
  export const LEGACY_SKILL_NAMES = [
@@ -110,10 +110,24 @@ export async function getSkillsStatus(targets, selection) {
110
110
  hashSkills(targets.claudeDir, names),
111
111
  hashSkills(targets.codexDir, names),
112
112
  ]);
113
- const disks = [agentsDisk, claudeDisk, codexDisk];
114
- const ops = diff(bundle, disks, names, resolveDesiredSkills(selection, available));
115
- const installed = installedSkillNames(disks, names);
116
- if (!hasInstalledPaseoSkill(disks))
113
+ const disks = [agentsDisk, claudeDisk];
114
+ const desired = resolveDesiredSkills(selection, available);
115
+ const ops = diff(bundle, disks, names, desired);
116
+ for (const name of codexDisk.keys()) {
117
+ if (ops.some((op) => op.name === name))
118
+ continue;
119
+ if (!(await isLegacyCodexCopy(name, targets)))
120
+ continue;
121
+ if (!desired.has(name))
122
+ ops.push({ kind: "delete", name });
123
+ else if (await readManagedFilesManifest(path.join(targets.codexDir, name))) {
124
+ ops.push({ kind: "update", name });
125
+ }
126
+ }
127
+ ops.sort((a, b) => compareStrings(a.name, b.name));
128
+ const allDisks = [...disks, codexDisk];
129
+ const installed = installedSkillNames(allDisks, names);
130
+ if (!hasInstalledPaseoSkill(allDisks))
117
131
  return { state: "not-installed", ops, available, installed };
118
132
  if (ops.length === 0)
119
133
  return { state: "up-to-date", ops, available, installed };
@@ -10,12 +10,24 @@ export interface SkillSyncResult {
10
10
  changedFiles: number;
11
11
  processedSkills: number;
12
12
  }
13
+ interface ManagedFilesManifest {
14
+ version: 1;
15
+ files: Record<string, string>;
16
+ }
13
17
  export declare function listFilesRecursive(rootDir: string): Promise<string[]>;
18
+ export declare function readManagedFilesManifest(dstDir: string): Promise<ManagedFilesManifest | null>;
14
19
  export interface RemoveSkillTargets {
15
20
  agentsDir: string;
16
21
  claudeDir: string;
17
22
  codexDir: string;
18
23
  }
19
24
  export declare function removeSkill(skillName: string, targets: RemoveSkillTargets): Promise<void>;
25
+ /**
26
+ * Codex also discovers .agents/skills, so its dedicated copy is a leftover. When
27
+ * the Codex skill directory resolves to the shared or Claude copy, it is that
28
+ * copy, and retiring it would delete the install.
29
+ */
30
+ export declare function isLegacyCodexCopy(skillName: string, targets: RemoveSkillTargets): Promise<boolean>;
20
31
  export declare function syncSkills(options: SkillSyncOptions): Promise<SkillSyncResult>;
32
+ export {};
21
33
  //# sourceMappingURL=sync.d.ts.map