mono-pilot 0.2.10 → 0.2.13

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 (155) hide show
  1. package/README.md +260 -2
  2. package/dist/src/agents-paths.js +36 -0
  3. package/dist/src/brief/blocks.js +83 -0
  4. package/dist/src/brief/defaults.js +60 -0
  5. package/dist/src/brief/frontmatter.js +53 -0
  6. package/dist/src/brief/paths.js +10 -0
  7. package/dist/src/brief/reflection.js +27 -0
  8. package/dist/src/cli.js +62 -5
  9. package/dist/src/cluster/bus.js +102 -0
  10. package/dist/src/cluster/follower.js +137 -0
  11. package/dist/src/cluster/init.js +182 -0
  12. package/dist/src/cluster/leader.js +97 -0
  13. package/dist/src/cluster/log.js +49 -0
  14. package/dist/src/cluster/protocol.js +34 -0
  15. package/dist/src/cluster/services/bus.js +243 -0
  16. package/dist/src/cluster/services/embedding.js +12 -0
  17. package/dist/src/cluster/socket.js +86 -0
  18. package/dist/src/cluster/test-bus.js +175 -0
  19. package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
  20. package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
  21. package/dist/src/cluster_v2/connection.js +159 -0
  22. package/dist/src/cluster_v2/connection.test.js +55 -0
  23. package/dist/src/cluster_v2/events.js +102 -0
  24. package/dist/src/cluster_v2/index.js +2 -0
  25. package/dist/src/cluster_v2/observability.js +99 -0
  26. package/dist/src/cluster_v2/observability.test.js +46 -0
  27. package/dist/src/cluster_v2/rpc.js +389 -0
  28. package/dist/src/cluster_v2/rpc.test.js +110 -0
  29. package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
  30. package/dist/src/cluster_v2/runtime.js +531 -0
  31. package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
  32. package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
  33. package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
  34. package/dist/src/cluster_v2/services/bus.js +450 -0
  35. package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
  36. package/dist/src/cluster_v2/services/discord/collector.js +569 -0
  37. package/dist/src/cluster_v2/services/discord/index.js +1 -0
  38. package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
  39. package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
  40. package/dist/src/cluster_v2/services/embedding.js +66 -0
  41. package/dist/src/cluster_v2/services/registry-cache.js +107 -0
  42. package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
  43. package/dist/src/cluster_v2/services/registry.js +36 -0
  44. package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
  45. package/dist/src/cluster_v2/services/twitter/index.js +1 -0
  46. package/dist/src/config/digest.js +78 -0
  47. package/dist/src/config/discord.js +143 -0
  48. package/dist/src/config/image-gen.js +48 -0
  49. package/dist/src/config/mono-pilot.js +31 -0
  50. package/dist/src/config/twitter.js +100 -0
  51. package/dist/src/extensions/cluster.js +311 -0
  52. package/dist/src/extensions/commands/build-memory.js +76 -0
  53. package/dist/src/extensions/commands/digest/backfill.js +779 -0
  54. package/dist/src/extensions/commands/digest/index.js +1133 -0
  55. package/dist/src/extensions/commands/image-model.js +214 -0
  56. package/dist/src/extensions/game/bus-injection.js +47 -0
  57. package/dist/src/extensions/game/identity.js +83 -0
  58. package/dist/src/extensions/game/mailbox.js +61 -0
  59. package/dist/src/extensions/game/system-prompt.js +134 -0
  60. package/dist/src/extensions/game/tools.js +28 -0
  61. package/dist/src/extensions/lifecycle.js +337 -0
  62. package/dist/src/extensions/mode-runtime.js +26 -2
  63. package/dist/src/extensions/mono-game.js +66 -0
  64. package/dist/src/extensions/mono-pilot.js +100 -18
  65. package/dist/src/extensions/nvim.js +47 -0
  66. package/dist/src/extensions/session-hints.js +1 -2
  67. package/dist/src/extensions/sftp.js +897 -0
  68. package/dist/src/extensions/status.js +676 -0
  69. package/dist/src/extensions/system-events.js +478 -0
  70. package/dist/src/extensions/system-prompt.js +24 -14
  71. package/dist/src/extensions/user-message.js +70 -1
  72. package/dist/src/lsp/client.js +235 -0
  73. package/dist/src/lsp/index.js +165 -0
  74. package/dist/src/lsp/runtime.js +67 -0
  75. package/dist/src/lsp/server.js +242 -0
  76. package/dist/src/memory/build-memory.js +103 -0
  77. package/dist/src/memory/config/defaults.js +55 -0
  78. package/dist/src/memory/config/loader.js +29 -0
  79. package/dist/src/memory/config/paths.js +9 -0
  80. package/dist/src/memory/config/resolve.js +90 -0
  81. package/dist/src/memory/config/types.js +1 -0
  82. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  83. package/dist/src/memory/embeddings/cache.js +47 -0
  84. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  85. package/dist/src/memory/embeddings/input-limits.js +48 -0
  86. package/dist/src/memory/embeddings/local.js +108 -0
  87. package/dist/src/memory/embeddings/types.js +1 -0
  88. package/dist/src/memory/index-manager.js +552 -0
  89. package/dist/src/memory/indexing/embeddings.js +67 -0
  90. package/dist/src/memory/indexing/files.js +180 -0
  91. package/dist/src/memory/indexing/index-file.js +105 -0
  92. package/dist/src/memory/log.js +38 -0
  93. package/dist/src/memory/paths.js +15 -0
  94. package/dist/src/memory/runtime/index.js +299 -0
  95. package/dist/src/memory/runtime/thread.js +116 -0
  96. package/dist/src/memory/search/fts.js +57 -0
  97. package/dist/src/memory/search/hybrid.js +50 -0
  98. package/dist/src/memory/search/text.js +30 -0
  99. package/dist/src/memory/search/vector.js +43 -0
  100. package/dist/src/memory/session/content-hash.js +7 -0
  101. package/dist/src/memory/session/entry.js +33 -0
  102. package/dist/src/memory/session/flush-policy.js +34 -0
  103. package/dist/src/memory/session/hook.js +191 -0
  104. package/dist/src/memory/session/paths.js +15 -0
  105. package/dist/src/memory/session/session-reader.js +88 -0
  106. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  107. package/dist/src/memory/session/transcript/entry.js +28 -0
  108. package/dist/src/memory/session/transcript/flush.js +56 -0
  109. package/dist/src/memory/session/transcript/paths.js +28 -0
  110. package/dist/src/memory/session/transcript/reader.js +112 -0
  111. package/dist/src/memory/session/transcript/state.js +31 -0
  112. package/dist/src/memory/store/schema.js +89 -0
  113. package/dist/src/memory/store/sqlite.js +89 -0
  114. package/dist/src/memory/types.js +1 -0
  115. package/dist/src/memory/warm.js +25 -0
  116. package/dist/{tools → src/tools}/README.md +28 -2
  117. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  118. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  119. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  120. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  121. package/dist/src/tools/ast-grep.js +357 -0
  122. package/dist/src/tools/brief-write.js +122 -0
  123. package/dist/src/tools/bus-send.js +100 -0
  124. package/dist/{tools → src/tools}/call-mcp-tool.js +20 -24
  125. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  126. package/dist/src/tools/codex-apply-patch.js +540 -0
  127. package/dist/{tools → src/tools}/delete.js +24 -0
  128. package/dist/src/tools/exit-plan-mode.js +83 -0
  129. package/dist/{tools → src/tools}/fetch-mcp-resource.js +31 -3
  130. package/dist/src/tools/generate-image.js +567 -0
  131. package/dist/{tools → src/tools}/glob.js +55 -1
  132. package/dist/{tools → src/tools}/list-mcp-resources.js +32 -3
  133. package/dist/{tools → src/tools}/list-mcp-tools.js +38 -3
  134. package/dist/src/tools/ls.js +48 -0
  135. package/dist/src/tools/lsp-diagnostics.js +67 -0
  136. package/dist/src/tools/lsp-symbols.js +54 -0
  137. package/dist/src/tools/mailbox.js +85 -0
  138. package/dist/src/tools/memory-get.js +90 -0
  139. package/dist/src/tools/memory-search.js +180 -0
  140. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  141. package/dist/{tools → src/tools}/read-file.js +8 -19
  142. package/dist/{tools → src/tools}/rg.js +10 -20
  143. package/dist/{tools → src/tools}/shell.js +19 -42
  144. package/dist/{tools → src/tools}/subagent.js +255 -6
  145. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  146. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  147. package/dist/{tools → src/tools}/web-search.js +29 -1
  148. package/package.json +21 -9
  149. package/dist/src/utils/mcp-client.js +0 -282
  150. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  151. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  152. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  153. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  154. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  155. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -0,0 +1,676 @@
1
+ const DEFAULT_TIMEOUT_MS = 12000;
2
+ const USAGE = "Usage: /status [--timeout-ms <milliseconds>]";
3
+ const PROVIDER_LABELS = {
4
+ anthropic: "Claude",
5
+ "openai-codex": "Codex",
6
+ "github-copilot": "Copilot",
7
+ "google-gemini-cli": "Gemini CLI",
8
+ minimax: "MiniMax",
9
+ zai: "z.ai",
10
+ };
11
+ const PROVIDER_ALIASES = {
12
+ anthropic: "anthropic",
13
+ "openai-codex": "openai-codex",
14
+ codex: "openai-codex",
15
+ "github-copilot": "github-copilot",
16
+ "google-gemini-cli": "google-gemini-cli",
17
+ minimax: "minimax",
18
+ "minimax-cn": "minimax",
19
+ zai: "zai",
20
+ "z-ai": "zai",
21
+ "z.ai": "zai",
22
+ };
23
+ const MINIMAX_PERCENT_KEYS = ["used_percent", "usedPercent", "usage_percent", "usagePercent"];
24
+ const MINIMAX_TOTAL_KEYS = ["total", "total_tokens", "totalTokens", "limit", "quota", "max"];
25
+ const MINIMAX_USED_KEYS = ["used", "usage", "used_tokens", "usedTokens", "consumed"];
26
+ const MINIMAX_REMAINING_KEYS = ["remaining", "remain", "remaining_tokens", "remainingTokens", "left"];
27
+ const MINIMAX_RESET_KEYS = ["reset_at", "resetAt", "next_reset_time", "nextResetTime"];
28
+ export function registerStatusCommand(pi) {
29
+ pi.registerCommand("status", {
30
+ description: "Show current model and provider usage windows",
31
+ handler: async (args, ctx) => {
32
+ const parsed = parseStatusArgs(args);
33
+ if (parsed.error) {
34
+ notify(ctx, `${parsed.error}\n${USAGE}`, "warning");
35
+ return;
36
+ }
37
+ const model = ctx.model;
38
+ if (!model) {
39
+ notify(ctx, "No active model in this session.", "warning");
40
+ return;
41
+ }
42
+ const providerId = resolveUsageProviderId(model.provider);
43
+ const modelLine = `model: ${model.provider}/${model.id}`;
44
+ const contextLine = formatContextLine(ctx);
45
+ if (!providerId) {
46
+ notify(ctx, [modelLine, contextLine, `usage: provider ${model.provider} is not supported yet`].join("\n"), "info");
47
+ return;
48
+ }
49
+ const apiKey = await ctx.modelRegistry.getApiKeyForProvider(model.provider);
50
+ if (!apiKey) {
51
+ notify(ctx, [modelLine, contextLine, "usage: no credentials available for current provider"].join("\n"), "warning");
52
+ return;
53
+ }
54
+ const token = normalizeProviderToken(providerId, apiKey);
55
+ if (!token) {
56
+ notify(ctx, [modelLine, contextLine, "usage: credentials format is not usable for usage lookup"].join("\n"), "warning");
57
+ return;
58
+ }
59
+ const snapshot = await loadProviderUsageSnapshot({
60
+ provider: providerId,
61
+ token,
62
+ timeoutMs: parsed.timeoutMs,
63
+ });
64
+ if (snapshot.error) {
65
+ notify(ctx, [modelLine, contextLine, `usage: ${snapshot.displayName}: ${snapshot.error}`].join("\n"), "warning");
66
+ return;
67
+ }
68
+ if (snapshot.windows.length === 0) {
69
+ notify(ctx, [modelLine, contextLine, `usage: ${snapshot.displayName}: no usage window data`].join("\n"), "info");
70
+ return;
71
+ }
72
+ const summary = formatUsageWindowSummary(snapshot, {
73
+ now: Date.now(),
74
+ maxWindows: 2,
75
+ includeResets: true,
76
+ });
77
+ const planLine = snapshot.plan ? `plan: ${snapshot.plan}` : null;
78
+ const usageLine = summary
79
+ ? `usage: ${snapshot.displayName} ${summary}`
80
+ : `usage: ${snapshot.displayName}: no usage window data`;
81
+ notify(ctx, [modelLine, contextLine, usageLine, planLine].filter(Boolean).join("\n"), "info");
82
+ },
83
+ });
84
+ }
85
+ function parseStatusArgs(raw) {
86
+ const trimmed = raw.trim();
87
+ if (!trimmed) {
88
+ return { timeoutMs: DEFAULT_TIMEOUT_MS };
89
+ }
90
+ const tokens = trimmed.split(/\s+/);
91
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
92
+ for (let i = 0; i < tokens.length; i += 1) {
93
+ const token = tokens[i];
94
+ if (token === "--timeout-ms") {
95
+ const value = tokens[i + 1];
96
+ if (!value) {
97
+ return { timeoutMs, error: "--timeout-ms requires a value" };
98
+ }
99
+ const parsed = Number.parseInt(value, 10);
100
+ if (!Number.isFinite(parsed) || parsed <= 0) {
101
+ return { timeoutMs, error: `invalid --timeout-ms: ${value}` };
102
+ }
103
+ timeoutMs = parsed;
104
+ i += 1;
105
+ continue;
106
+ }
107
+ if (token.startsWith("--timeout-ms=")) {
108
+ const value = token.slice("--timeout-ms=".length);
109
+ const parsed = Number.parseInt(value, 10);
110
+ if (!Number.isFinite(parsed) || parsed <= 0) {
111
+ return { timeoutMs, error: `invalid --timeout-ms: ${value}` };
112
+ }
113
+ timeoutMs = parsed;
114
+ continue;
115
+ }
116
+ return { timeoutMs, error: `unknown argument: ${token}` };
117
+ }
118
+ return { timeoutMs };
119
+ }
120
+ function resolveUsageProviderId(provider) {
121
+ if (!provider) {
122
+ return undefined;
123
+ }
124
+ const normalized = provider.trim().toLowerCase();
125
+ return PROVIDER_ALIASES[normalized];
126
+ }
127
+ function normalizeProviderToken(provider, raw) {
128
+ const trimmed = raw.trim();
129
+ if (!trimmed || trimmed === "<authenticated>") {
130
+ return null;
131
+ }
132
+ if (provider !== "google-gemini-cli") {
133
+ return trimmed;
134
+ }
135
+ try {
136
+ const parsed = JSON.parse(trimmed);
137
+ if (isRecord(parsed) && typeof parsed.token === "string" && parsed.token.trim()) {
138
+ return parsed.token.trim();
139
+ }
140
+ }
141
+ catch {
142
+ // Keep fallback below.
143
+ }
144
+ return trimmed;
145
+ }
146
+ async function loadProviderUsageSnapshot(params) {
147
+ try {
148
+ switch (params.provider) {
149
+ case "anthropic":
150
+ return await fetchClaudeUsage(params.token, params.timeoutMs);
151
+ case "openai-codex":
152
+ return await fetchCodexUsage(params.token, params.timeoutMs);
153
+ case "github-copilot":
154
+ return await fetchCopilotUsage(params.token, params.timeoutMs);
155
+ case "google-gemini-cli":
156
+ return await fetchGeminiUsage(params.token, params.timeoutMs);
157
+ case "zai":
158
+ return await fetchZaiUsage(params.token, params.timeoutMs);
159
+ case "minimax":
160
+ return await fetchMiniMaxUsage(params.token, params.timeoutMs);
161
+ default:
162
+ return {
163
+ provider: params.provider,
164
+ displayName: PROVIDER_LABELS[params.provider],
165
+ windows: [],
166
+ error: "Unsupported provider",
167
+ };
168
+ }
169
+ }
170
+ catch (error) {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ return {
173
+ provider: params.provider,
174
+ displayName: PROVIDER_LABELS[params.provider],
175
+ windows: [],
176
+ error: message,
177
+ };
178
+ }
179
+ }
180
+ async function fetchClaudeUsage(token, timeoutMs) {
181
+ const response = await fetchJson("https://api.anthropic.com/api/oauth/usage", {
182
+ headers: {
183
+ Authorization: `Bearer ${token}`,
184
+ "User-Agent": "mono-pilot",
185
+ Accept: "application/json",
186
+ "anthropic-version": "2023-06-01",
187
+ "anthropic-beta": "oauth-2025-04-20",
188
+ },
189
+ }, timeoutMs);
190
+ if (!response.ok) {
191
+ return buildHttpErrorSnapshot("anthropic", response.status);
192
+ }
193
+ const payload = await readJsonObject(response);
194
+ if (!payload) {
195
+ return buildErrorSnapshot("anthropic", "Invalid JSON response");
196
+ }
197
+ const windows = [];
198
+ const fiveHour = getRecord(payload, "five_hour");
199
+ const week = getRecord(payload, "seven_day");
200
+ const fiveHourUsed = parseFiniteNumber(fiveHour?.utilization);
201
+ if (fiveHourUsed !== undefined) {
202
+ windows.push({
203
+ label: "5h",
204
+ usedPercent: normalizePercent(fiveHourUsed),
205
+ resetAt: parseEpoch(fiveHour?.resets_at),
206
+ });
207
+ }
208
+ const weekUsed = parseFiniteNumber(week?.utilization);
209
+ if (weekUsed !== undefined) {
210
+ windows.push({
211
+ label: "Week",
212
+ usedPercent: normalizePercent(weekUsed),
213
+ resetAt: parseEpoch(week?.resets_at),
214
+ });
215
+ }
216
+ return {
217
+ provider: "anthropic",
218
+ displayName: PROVIDER_LABELS.anthropic,
219
+ windows,
220
+ };
221
+ }
222
+ async function fetchCodexUsage(token, timeoutMs) {
223
+ const accountId = extractCodexAccountId(token);
224
+ const headers = {
225
+ Authorization: `Bearer ${token}`,
226
+ "User-Agent": "CodexBar",
227
+ Accept: "application/json",
228
+ };
229
+ if (accountId) {
230
+ headers["ChatGPT-Account-Id"] = accountId;
231
+ }
232
+ const response = await fetchJson("https://chatgpt.com/backend-api/wham/usage", { method: "GET", headers }, timeoutMs);
233
+ if (!response.ok) {
234
+ return buildHttpErrorSnapshot("openai-codex", response.status, [401, 403]);
235
+ }
236
+ const payload = await readJsonObject(response);
237
+ if (!payload) {
238
+ return buildErrorSnapshot("openai-codex", "Invalid JSON response");
239
+ }
240
+ const rateLimit = getRecord(payload, "rate_limit");
241
+ const primary = getRecord(rateLimit, "primary_window");
242
+ const secondary = getRecord(rateLimit, "secondary_window");
243
+ const windows = [];
244
+ const primaryUsed = parseFiniteNumber(primary?.used_percent);
245
+ if (primaryUsed !== undefined) {
246
+ const seconds = parseFiniteNumber(primary?.limit_window_seconds) ?? 10800;
247
+ const hours = Math.max(1, Math.round(seconds / 3600));
248
+ windows.push({
249
+ label: `${hours}h`,
250
+ usedPercent: normalizePercent(primaryUsed),
251
+ resetAt: parseEpoch(primary?.reset_at),
252
+ });
253
+ }
254
+ const secondaryUsed = parseFiniteNumber(secondary?.used_percent);
255
+ if (secondaryUsed !== undefined) {
256
+ const seconds = parseFiniteNumber(secondary?.limit_window_seconds) ?? 86400;
257
+ const hours = Math.max(1, Math.round(seconds / 3600));
258
+ const label = hours >= 168 ? "Week" : hours >= 24 ? "Day" : `${hours}h`;
259
+ windows.push({
260
+ label,
261
+ usedPercent: normalizePercent(secondaryUsed),
262
+ resetAt: parseEpoch(secondary?.reset_at),
263
+ });
264
+ }
265
+ const credits = getRecord(payload, "credits");
266
+ const planType = typeof payload.plan_type === "string" ? payload.plan_type.trim() : undefined;
267
+ const balance = parseFiniteNumber(credits?.balance);
268
+ const plan = balance !== undefined ? `${planType ?? "plan"} ($${balance.toFixed(2)})` : planType;
269
+ return {
270
+ provider: "openai-codex",
271
+ displayName: PROVIDER_LABELS["openai-codex"],
272
+ windows,
273
+ plan,
274
+ };
275
+ }
276
+ async function fetchCopilotUsage(token, timeoutMs) {
277
+ const response = await fetchJson("https://api.github.com/copilot_internal/user", {
278
+ headers: {
279
+ Authorization: `token ${token}`,
280
+ "Editor-Version": "vscode/1.96.2",
281
+ "User-Agent": "GitHubCopilotChat/0.26.7",
282
+ "X-Github-Api-Version": "2025-04-01",
283
+ },
284
+ }, timeoutMs);
285
+ if (!response.ok) {
286
+ return buildHttpErrorSnapshot("github-copilot", response.status, [401, 403]);
287
+ }
288
+ const payload = await readJsonObject(response);
289
+ if (!payload) {
290
+ return buildErrorSnapshot("github-copilot", "Invalid JSON response");
291
+ }
292
+ const windows = [];
293
+ const snapshots = getRecord(payload, "quota_snapshots");
294
+ const premium = getRecord(snapshots, "premium_interactions");
295
+ const chat = getRecord(snapshots, "chat");
296
+ const premiumRemaining = parseFiniteNumber(premium?.percent_remaining);
297
+ if (premiumRemaining !== undefined) {
298
+ windows.push({
299
+ label: "Premium",
300
+ usedPercent: clampPercent(100 - normalizePercent(premiumRemaining)),
301
+ });
302
+ }
303
+ const chatRemaining = parseFiniteNumber(chat?.percent_remaining);
304
+ if (chatRemaining !== undefined) {
305
+ windows.push({
306
+ label: "Chat",
307
+ usedPercent: clampPercent(100 - normalizePercent(chatRemaining)),
308
+ });
309
+ }
310
+ const plan = typeof payload.copilot_plan === "string" ? payload.copilot_plan.trim() : undefined;
311
+ return {
312
+ provider: "github-copilot",
313
+ displayName: PROVIDER_LABELS["github-copilot"],
314
+ windows,
315
+ plan,
316
+ };
317
+ }
318
+ async function fetchGeminiUsage(token, timeoutMs) {
319
+ const response = await fetchJson("https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota", {
320
+ method: "POST",
321
+ headers: {
322
+ Authorization: `Bearer ${token}`,
323
+ "Content-Type": "application/json",
324
+ },
325
+ body: "{}",
326
+ }, timeoutMs);
327
+ if (!response.ok) {
328
+ return buildHttpErrorSnapshot("google-gemini-cli", response.status, [401, 403]);
329
+ }
330
+ const payload = await readJsonObject(response);
331
+ if (!payload) {
332
+ return buildErrorSnapshot("google-gemini-cli", "Invalid JSON response");
333
+ }
334
+ const buckets = Array.isArray(payload.buckets) ? payload.buckets : [];
335
+ let proRemaining;
336
+ let flashRemaining;
337
+ for (const entry of buckets) {
338
+ if (!isRecord(entry)) {
339
+ continue;
340
+ }
341
+ const modelId = typeof entry.modelId === "string" ? entry.modelId.toLowerCase() : "";
342
+ const rawRemaining = parseFiniteNumber(entry.remainingFraction);
343
+ if (rawRemaining === undefined) {
344
+ continue;
345
+ }
346
+ const remaining = clamp01(rawRemaining <= 1 ? rawRemaining : rawRemaining / 100);
347
+ if (modelId.includes("pro")) {
348
+ proRemaining = proRemaining === undefined ? remaining : Math.min(proRemaining, remaining);
349
+ }
350
+ if (modelId.includes("flash")) {
351
+ flashRemaining = flashRemaining === undefined ? remaining : Math.min(flashRemaining, remaining);
352
+ }
353
+ }
354
+ const windows = [];
355
+ if (proRemaining !== undefined) {
356
+ windows.push({ label: "Pro", usedPercent: clampPercent((1 - proRemaining) * 100) });
357
+ }
358
+ if (flashRemaining !== undefined) {
359
+ windows.push({ label: "Flash", usedPercent: clampPercent((1 - flashRemaining) * 100) });
360
+ }
361
+ return {
362
+ provider: "google-gemini-cli",
363
+ displayName: PROVIDER_LABELS["google-gemini-cli"],
364
+ windows,
365
+ };
366
+ }
367
+ async function fetchZaiUsage(token, timeoutMs) {
368
+ const response = await fetchJson("https://api.z.ai/api/monitor/usage/quota/limit", {
369
+ method: "GET",
370
+ headers: {
371
+ Authorization: `Bearer ${token}`,
372
+ Accept: "application/json",
373
+ },
374
+ }, timeoutMs);
375
+ if (!response.ok) {
376
+ return buildHttpErrorSnapshot("zai", response.status, [401, 403]);
377
+ }
378
+ const payload = await readJsonObject(response);
379
+ if (!payload) {
380
+ return buildErrorSnapshot("zai", "Invalid JSON response");
381
+ }
382
+ if (payload.success !== true || parseFiniteNumber(payload.code) !== 200) {
383
+ const message = typeof payload.msg === "string" && payload.msg.trim() ? payload.msg.trim() : "API error";
384
+ return buildErrorSnapshot("zai", message);
385
+ }
386
+ const data = getRecord(payload, "data");
387
+ const limits = Array.isArray(data?.limits) ? data.limits : [];
388
+ const windows = [];
389
+ for (const entry of limits) {
390
+ if (!isRecord(entry)) {
391
+ continue;
392
+ }
393
+ const type = typeof entry.type === "string" ? entry.type : "";
394
+ const percentage = parseFiniteNumber(entry.percentage);
395
+ if (percentage === undefined) {
396
+ continue;
397
+ }
398
+ const unit = parseFiniteNumber(entry.unit);
399
+ const count = parseFiniteNumber(entry.number);
400
+ const windowLabel = unit === 1 ? `${count ?? 1}d` : unit === 3 ? `${count ?? 1}h` : unit === 5 ? `${count ?? 1}m` : "window";
401
+ if (type === "TOKENS_LIMIT") {
402
+ windows.push({
403
+ label: `Tokens (${windowLabel})`,
404
+ usedPercent: normalizePercent(percentage),
405
+ resetAt: parseEpoch(entry.nextResetTime),
406
+ });
407
+ }
408
+ else if (type === "TIME_LIMIT") {
409
+ windows.push({
410
+ label: "Monthly",
411
+ usedPercent: normalizePercent(percentage),
412
+ resetAt: parseEpoch(entry.nextResetTime),
413
+ });
414
+ }
415
+ }
416
+ const planName = typeof data?.planName === "string"
417
+ ? data.planName
418
+ : typeof data?.plan === "string"
419
+ ? data.plan
420
+ : undefined;
421
+ return {
422
+ provider: "zai",
423
+ displayName: PROVIDER_LABELS.zai,
424
+ windows,
425
+ plan: planName,
426
+ };
427
+ }
428
+ async function fetchMiniMaxUsage(token, timeoutMs) {
429
+ const response = await fetchJson("https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains", {
430
+ method: "GET",
431
+ headers: {
432
+ Authorization: `Bearer ${token}`,
433
+ "Content-Type": "application/json",
434
+ "MM-API-Source": "mono-pilot",
435
+ },
436
+ }, timeoutMs);
437
+ if (!response.ok) {
438
+ return buildHttpErrorSnapshot("minimax", response.status, [401, 403]);
439
+ }
440
+ const payload = await readJsonObject(response);
441
+ if (!payload) {
442
+ return buildErrorSnapshot("minimax", "Invalid JSON response");
443
+ }
444
+ const baseResp = getRecord(payload, "base_resp");
445
+ const statusCode = parseFiniteNumber(baseResp?.status_code);
446
+ if (statusCode !== undefined && statusCode !== 0) {
447
+ const message = typeof baseResp?.status_msg === "string" ? baseResp.status_msg : "API error";
448
+ return buildErrorSnapshot("minimax", message);
449
+ }
450
+ const usagePayload = getRecord(payload, "data") ?? payload;
451
+ const directPercent = pickFirstNumber(usagePayload, MINIMAX_PERCENT_KEYS);
452
+ let usedPercent = directPercent !== undefined ? normalizePercent(directPercent) : undefined;
453
+ if (usedPercent === undefined) {
454
+ const total = pickFirstNumber(usagePayload, MINIMAX_TOTAL_KEYS);
455
+ let used = pickFirstNumber(usagePayload, MINIMAX_USED_KEYS);
456
+ const remaining = pickFirstNumber(usagePayload, MINIMAX_REMAINING_KEYS);
457
+ if (used === undefined && remaining !== undefined && total !== undefined) {
458
+ used = total - remaining;
459
+ }
460
+ if (total !== undefined && total > 0 && used !== undefined) {
461
+ usedPercent = clampPercent((used / total) * 100);
462
+ }
463
+ }
464
+ if (usedPercent === undefined) {
465
+ return buildErrorSnapshot("minimax", "Unsupported response shape");
466
+ }
467
+ const windowHours = pickFirstNumber(usagePayload, ["window_hours", "windowHours"]);
468
+ const windowMinutes = pickFirstNumber(usagePayload, ["window_minutes", "windowMinutes"]);
469
+ const windowLabel = windowHours !== undefined
470
+ ? `${windowHours}h`
471
+ : windowMinutes !== undefined
472
+ ? `${windowMinutes}m`
473
+ : "5h";
474
+ const plan = pickFirstString(usagePayload, ["plan", "plan_name", "planName", "tier"]);
475
+ const resetAt = parseEpoch(pickFirstString(usagePayload, MINIMAX_RESET_KEYS));
476
+ return {
477
+ provider: "minimax",
478
+ displayName: PROVIDER_LABELS.minimax,
479
+ windows: [
480
+ {
481
+ label: windowLabel,
482
+ usedPercent,
483
+ resetAt,
484
+ },
485
+ ],
486
+ plan,
487
+ };
488
+ }
489
+ async function fetchJson(url, init, timeoutMs) {
490
+ const controller = new AbortController();
491
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
492
+ try {
493
+ return await fetch(url, { ...init, signal: controller.signal });
494
+ }
495
+ catch (error) {
496
+ if (error instanceof Error && error.name === "AbortError") {
497
+ throw new Error("request timed out");
498
+ }
499
+ throw error;
500
+ }
501
+ finally {
502
+ clearTimeout(timer);
503
+ }
504
+ }
505
+ async function readJsonObject(response) {
506
+ try {
507
+ const parsed = (await response.json());
508
+ return isRecord(parsed) ? parsed : null;
509
+ }
510
+ catch {
511
+ return null;
512
+ }
513
+ }
514
+ function getRecord(value, key) {
515
+ if (!isRecord(value)) {
516
+ return undefined;
517
+ }
518
+ const child = value[key];
519
+ return isRecord(child) ? child : undefined;
520
+ }
521
+ function parseFiniteNumber(value) {
522
+ if (typeof value === "number" && Number.isFinite(value)) {
523
+ return value;
524
+ }
525
+ if (typeof value === "string") {
526
+ const parsed = Number.parseFloat(value);
527
+ if (Number.isFinite(parsed)) {
528
+ return parsed;
529
+ }
530
+ }
531
+ return undefined;
532
+ }
533
+ function parseEpoch(value) {
534
+ if (typeof value === "number" && Number.isFinite(value)) {
535
+ return value < 1e12 ? Math.floor(value * 1000) : Math.floor(value);
536
+ }
537
+ if (typeof value === "string") {
538
+ const directNumber = Number.parseFloat(value);
539
+ if (Number.isFinite(directNumber)) {
540
+ return directNumber < 1e12 ? Math.floor(directNumber * 1000) : Math.floor(directNumber);
541
+ }
542
+ const parsedDate = Date.parse(value);
543
+ if (Number.isFinite(parsedDate)) {
544
+ return parsedDate;
545
+ }
546
+ }
547
+ return undefined;
548
+ }
549
+ function normalizePercent(value) {
550
+ const scaled = value >= 0 && value <= 1 ? value * 100 : value;
551
+ return clampPercent(scaled);
552
+ }
553
+ function clampPercent(value) {
554
+ if (!Number.isFinite(value)) {
555
+ return 0;
556
+ }
557
+ return Math.max(0, Math.min(100, value));
558
+ }
559
+ function clamp01(value) {
560
+ if (!Number.isFinite(value)) {
561
+ return 0;
562
+ }
563
+ return Math.max(0, Math.min(1, value));
564
+ }
565
+ function pickFirstNumber(record, keys) {
566
+ for (const key of keys) {
567
+ const parsed = parseFiniteNumber(record[key]);
568
+ if (parsed !== undefined) {
569
+ return parsed;
570
+ }
571
+ }
572
+ return undefined;
573
+ }
574
+ function pickFirstString(record, keys) {
575
+ for (const key of keys) {
576
+ const value = record[key];
577
+ if (typeof value === "string" && value.trim()) {
578
+ return value.trim();
579
+ }
580
+ }
581
+ return undefined;
582
+ }
583
+ function isRecord(value) {
584
+ return typeof value === "object" && value !== null;
585
+ }
586
+ function buildErrorSnapshot(provider, error) {
587
+ return {
588
+ provider,
589
+ displayName: PROVIDER_LABELS[provider],
590
+ windows: [],
591
+ error,
592
+ };
593
+ }
594
+ function buildHttpErrorSnapshot(provider, status, tokenExpiredStatuses = []) {
595
+ if (tokenExpiredStatuses.includes(status)) {
596
+ return buildErrorSnapshot(provider, "Token expired");
597
+ }
598
+ return buildErrorSnapshot(provider, `HTTP ${status}`);
599
+ }
600
+ function extractCodexAccountId(token) {
601
+ const parts = token.split(".");
602
+ if (parts.length !== 3) {
603
+ return undefined;
604
+ }
605
+ try {
606
+ const payloadText = Buffer.from(parts[1], "base64url").toString("utf8");
607
+ const payload = JSON.parse(payloadText);
608
+ if (!isRecord(payload)) {
609
+ return undefined;
610
+ }
611
+ const authClaim = getRecord(payload, "https://api.openai.com/auth");
612
+ const accountId = authClaim?.chatgpt_account_id;
613
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
614
+ }
615
+ catch {
616
+ return undefined;
617
+ }
618
+ }
619
+ function formatUsageWindowSummary(snapshot, opts) {
620
+ if (snapshot.windows.length === 0) {
621
+ return null;
622
+ }
623
+ const now = opts?.now ?? Date.now();
624
+ const maxWindows = typeof opts?.maxWindows === "number" && opts.maxWindows > 0
625
+ ? Math.min(opts.maxWindows, snapshot.windows.length)
626
+ : snapshot.windows.length;
627
+ const includeResets = opts?.includeResets ?? false;
628
+ const parts = snapshot.windows.slice(0, maxWindows).map((window) => {
629
+ const remaining = clampPercent(100 - window.usedPercent);
630
+ const reset = includeResets ? formatResetRemaining(window.resetAt, now) : null;
631
+ const resetSuffix = reset ? ` \u23f1${reset}` : "";
632
+ return `${window.label} ${remaining.toFixed(0)}% left${resetSuffix}`;
633
+ });
634
+ return parts.join(" \u00b7 ");
635
+ }
636
+ function formatResetRemaining(targetMs, now = Date.now()) {
637
+ if (!targetMs || !Number.isFinite(targetMs)) {
638
+ return null;
639
+ }
640
+ const diffMs = targetMs - now;
641
+ if (diffMs <= 0) {
642
+ return "now";
643
+ }
644
+ const diffMins = Math.floor(diffMs / 60000);
645
+ if (diffMins < 60) {
646
+ return `${diffMins}m`;
647
+ }
648
+ const hours = Math.floor(diffMins / 60);
649
+ const mins = diffMins % 60;
650
+ if (hours < 24) {
651
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
652
+ }
653
+ const days = Math.floor(hours / 24);
654
+ if (days < 7) {
655
+ return `${days}d ${hours % 24}h`;
656
+ }
657
+ return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" }).format(new Date(targetMs));
658
+ }
659
+ function formatContextLine(ctx) {
660
+ const usage = ctx.getContextUsage();
661
+ if (!usage) {
662
+ return "context: unavailable";
663
+ }
664
+ const tokenLabel = usage.tokens === null ? "?" : String(usage.tokens);
665
+ const percentLabel = usage.percent === null ? "?" : `${Math.round(usage.percent)}%`;
666
+ return `context: ${tokenLabel}/${usage.contextWindow} (${percentLabel})`;
667
+ }
668
+ function notify(ctx, message, level) {
669
+ if (ctx.hasUI && ctx.ui?.notify) {
670
+ ctx.ui.notify(message, level);
671
+ }
672
+ else {
673
+ const prefix = level === "error" ? "[error]" : level === "warning" ? "[warn]" : "[info]";
674
+ console.log(`${prefix} ${message}`);
675
+ }
676
+ }