@link-assistant/hive-mind 2.1.3 → 2.1.4
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/CHANGELOG.md +6 -0
- package/README.hi.md +3 -1
- package/README.md +3 -1
- package/README.ru.md +3 -1
- package/README.zh.md +3 -1
- package/package.json +1 -1
- package/src/limits-i18n.lib.mjs +8 -0
- package/src/limits-subscription.lib.mjs +40 -1
- package/src/limits.lib.mjs +109 -150
- package/src/locales/en.lino +16 -0
- package/src/locales/hi.lino +16 -0
- package/src/locales/ru.lino +16 -0
- package/src/locales/zh.lino +16 -0
package/CHANGELOG.md
CHANGED
package/README.hi.md
CHANGED
|
@@ -521,7 +521,9 @@ Shows:
|
|
|
521
521
|
- RAM usage (used vs total)
|
|
522
522
|
- Disk space usage
|
|
523
523
|
- GitHub API rate limits
|
|
524
|
-
- Claude
|
|
524
|
+
- Claude and ChatGPT subscription usage windows
|
|
525
|
+
- Non-zero Codex weekly limits and credits
|
|
526
|
+
- Solve queue status
|
|
525
527
|
```
|
|
526
528
|
|
|
527
529
|
#### `/terminal_watch` - Live Session Log
|
package/README.md
CHANGED
|
@@ -542,7 +542,9 @@ Shows:
|
|
|
542
542
|
- RAM usage (used vs total)
|
|
543
543
|
- Disk space usage
|
|
544
544
|
- GitHub API rate limits
|
|
545
|
-
- Claude
|
|
545
|
+
- Claude and ChatGPT subscription usage windows
|
|
546
|
+
- Non-zero Codex weekly limits and credits
|
|
547
|
+
- Solve queue status
|
|
546
548
|
```
|
|
547
549
|
|
|
548
550
|
#### `/terminal_watch` - Live Session Log
|
package/README.ru.md
CHANGED
|
@@ -522,7 +522,9 @@ Shows:
|
|
|
522
522
|
- RAM usage (used vs total)
|
|
523
523
|
- Disk space usage
|
|
524
524
|
- GitHub API rate limits
|
|
525
|
-
- Claude
|
|
525
|
+
- Claude and ChatGPT subscription usage windows
|
|
526
|
+
- Non-zero Codex weekly limits and credits
|
|
527
|
+
- Solve queue status
|
|
526
528
|
```
|
|
527
529
|
|
|
528
530
|
#### `/terminal_watch` — Live Session Log
|
package/README.zh.md
CHANGED
|
@@ -518,7 +518,9 @@ Shows:
|
|
|
518
518
|
- RAM usage (used vs total)
|
|
519
519
|
- Disk space usage
|
|
520
520
|
- GitHub API rate limits
|
|
521
|
-
- Claude
|
|
521
|
+
- Claude and ChatGPT subscription usage windows
|
|
522
|
+
- Non-zero Codex weekly limits and credits
|
|
523
|
+
- Solve queue status
|
|
522
524
|
```
|
|
523
525
|
|
|
524
526
|
#### `/terminal_watch` - Live Session Log
|
package/package.json
CHANGED
package/src/limits-i18n.lib.mjs
CHANGED
|
@@ -9,12 +9,15 @@ const ENGLISH_LIMITS = {
|
|
|
9
9
|
balance: 'balance',
|
|
10
10
|
claude_5_hour_session: 'Claude 5 hour session',
|
|
11
11
|
claude_limits: 'Claude limits',
|
|
12
|
+
claude_subscription_title: 'Claude{{plan}} subscription',
|
|
13
|
+
chatgpt_subscription_title: 'ChatGPT{{plan}} subscription',
|
|
12
14
|
codex_5_hour_session: 'Codex 5 hour session',
|
|
13
15
|
codex_credits: 'Codex credits',
|
|
14
16
|
codex_limits: 'Codex limits',
|
|
15
17
|
cpu: 'CPU',
|
|
16
18
|
cpu_cores_used: 'CPU cores used',
|
|
17
19
|
current_time: 'Current time',
|
|
20
|
+
current_week: 'Current week',
|
|
18
21
|
current_week_all_models: 'Current week (all models)',
|
|
19
22
|
current_week_sonnet_only: 'Current week (Sonnet only)',
|
|
20
23
|
disabled_by_admin: '`--show-limits` is disabled by the bot administrator.',
|
|
@@ -25,6 +28,7 @@ const ENGLISH_LIMITS = {
|
|
|
25
28
|
duration_second_short: 's',
|
|
26
29
|
end: 'End',
|
|
27
30
|
five_hour_session: '5h session',
|
|
31
|
+
five_hour_limit_session: '5 hour session',
|
|
28
32
|
five_min_load_avg: '5m load avg',
|
|
29
33
|
github_api: 'GitHub API',
|
|
30
34
|
limits_at_end: 'Limits at end',
|
|
@@ -76,6 +80,10 @@ const ENGLISH_LIMITS = {
|
|
|
76
80
|
start: 'Start',
|
|
77
81
|
subscription_ends: 'Subscription ends {{time}}',
|
|
78
82
|
subscription_ends_in: 'Subscription ends in {{duration}} ({{time}})',
|
|
83
|
+
subscription_detail_ends: 'ends {{time}}',
|
|
84
|
+
subscription_detail_ends_in: 'ends in {{duration}}; {{time}}',
|
|
85
|
+
subscription_detail_trial_ends: 'trial ends {{time}}',
|
|
86
|
+
subscription_detail_trial_ends_in: 'trial ends in {{duration}}; {{time}}',
|
|
79
87
|
subscription_status: 'Subscription: {{status}}',
|
|
80
88
|
trial_ends: 'Trial ends {{time}}',
|
|
81
89
|
trial_ends_in: 'Trial ends in {{duration}} ({{time}})',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { CACHE_TTL, DEFAULT_CODEX_AUTH_PATH, DEFAULT_CREDENTIALS_PATH, decodeJwtPayload, getLimitCache, readCodexAuth, readCredentials } from './limits.lib.mjs';
|
|
13
|
-
import { formatLocalizedRelativeTime, formatLocalizedResetTime, formatSubscriptionEnds, formatSubscriptionStatus, formatTrialEnds, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
13
|
+
import { formatLocalizedRelativeTime, formatLocalizedResetTime, formatSubscriptionEnds, formatSubscriptionStatus, formatTrialEnds, lt, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
14
14
|
|
|
15
15
|
const PROFILE_API_ENDPOINT = 'https://api.anthropic.com/api/oauth/profile';
|
|
16
16
|
|
|
@@ -32,6 +32,45 @@ export function formatSubscriptionLines(subscription, options = {}) {
|
|
|
32
32
|
return line ? `${line}\n` : '';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function humanizePlanType(planType, provider) {
|
|
36
|
+
if (!planType) return '';
|
|
37
|
+
const providerPrefix = provider === 'claude' ? /^claude[\s_-]*/i : /^(chatgpt|codex|openai)[\s_-]*/i;
|
|
38
|
+
const normalized = String(planType).trim().replace(providerPrefix, '').replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
39
|
+
if (!normalized) return '';
|
|
40
|
+
return normalized
|
|
41
|
+
.split(' ')
|
|
42
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
43
|
+
.join(' ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatSubscriptionDetail(subscription, options = {}) {
|
|
47
|
+
if (!subscription) return '';
|
|
48
|
+
const locale = resolveLimitLocale(options);
|
|
49
|
+
const buildDetail = (iso, inKey, atKey) => {
|
|
50
|
+
const resetTime = formatLocalizedResetTime(iso, true, { locale });
|
|
51
|
+
if (!resetTime) return '';
|
|
52
|
+
const duration = formatLocalizedRelativeTime(iso, { locale });
|
|
53
|
+
return duration ? lt(inKey, { duration, time: resetTime }, { locale }) : lt(atKey, { time: resetTime }, { locale });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (subscription.endsAt) return buildDetail(subscription.endsAt, 'subscription_detail_ends_in', 'subscription_detail_ends');
|
|
57
|
+
if (subscription.trialEndsAt) return buildDetail(subscription.trialEndsAt, 'subscription_detail_trial_ends_in', 'subscription_detail_trial_ends');
|
|
58
|
+
return subscription.status ? String(subscription.status).trim() : '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatSubscriptionHeading(provider, subscription, options = {}) {
|
|
62
|
+
const locale = resolveLimitLocale(options);
|
|
63
|
+
const normalizedProvider = provider === 'claude' ? 'claude' : 'chatgpt';
|
|
64
|
+
const planType = subscription?.planType || options?.planType || null;
|
|
65
|
+
if (!subscription && !planType) return '';
|
|
66
|
+
|
|
67
|
+
const plan = humanizePlanType(planType, normalizedProvider);
|
|
68
|
+
const titleKey = normalizedProvider === 'claude' ? 'claude_subscription_title' : 'chatgpt_subscription_title';
|
|
69
|
+
const title = lt(titleKey, { plan: plan ? ` ${plan}` : '' }, { locale });
|
|
70
|
+
const detail = formatSubscriptionDetail(subscription, { locale });
|
|
71
|
+
return detail ? `${title} (${detail})` : title;
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
/**
|
|
36
75
|
* Get Claude subscription metadata.
|
|
37
76
|
*
|
package/src/limits.lib.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import utc from 'dayjs/plugin/utc.js';
|
|
|
14
14
|
|
|
15
15
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry, execGhWithRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller. execGhWithRetry adds transient-network retry (#1756).
|
|
16
16
|
import { formatLimitResetsAt, formatLimitResetsIn, formatLocalizedCurrentTime, formatLocalizedRelativeTime, formatLocalizedResetTime, localizeCompactDuration, lt, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
17
|
-
import { formatSubscriptionLines, getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo } from './limits-subscription.lib.mjs';
|
|
17
|
+
import { formatSubscriptionHeading, formatSubscriptionLines, getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo } from './limits-subscription.lib.mjs';
|
|
18
18
|
export { getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo };
|
|
19
19
|
// Initialize dayjs plugins
|
|
20
20
|
dayjs.extend(utc);
|
|
@@ -280,6 +280,61 @@ function getLocalizedRelativeReset(window, options = {}, fallbackRelative = null
|
|
|
280
280
|
return formatRelativeTime(window?.resetsAt, options) || localizeCompactDuration(fallbackRelative, options);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
function formatCodeBlock(content) {
|
|
284
|
+
const text = Array.isArray(content) ? content.join('\n') : String(content ?? '');
|
|
285
|
+
return '```\n' + (text.endsWith('\n') ? text : `${text}\n`) + '```';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function formatPlainTitledCodeSection(section) {
|
|
289
|
+
const text = String(section ?? '').trimEnd();
|
|
290
|
+
if (!text) return '';
|
|
291
|
+
if (text.includes('```')) return text;
|
|
292
|
+
|
|
293
|
+
const lines = text.split('\n');
|
|
294
|
+
const title = lines.shift();
|
|
295
|
+
const body = lines.join('\n');
|
|
296
|
+
if (!body) return formatCodeBlock(title);
|
|
297
|
+
return `${title}\n\n${formatCodeBlock(body)}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function formatLimitWindowSection(label, window, periodHours, threshold, options = {}) {
|
|
301
|
+
const locale = resolveLimitLocale(options);
|
|
302
|
+
let section = `${label}\n`;
|
|
303
|
+
if (hasLimitPercentage(window)) {
|
|
304
|
+
const timePassed = calculateTimePassedPercentage(window.resetsAt, periodHours);
|
|
305
|
+
if (timePassed !== null) {
|
|
306
|
+
section += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const pct = Math.floor(window.percentage);
|
|
310
|
+
const bar = getProgressBar(pct, threshold);
|
|
311
|
+
const suffix = pct >= threshold ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
312
|
+
section += `${bar} ${pct}%${suffix}\n`;
|
|
313
|
+
|
|
314
|
+
const resetTime = getLocalizedResetTime(window, { locale });
|
|
315
|
+
if (resetTime) {
|
|
316
|
+
const relativeTime = getLocalizedRelativeReset(window, { locale });
|
|
317
|
+
section += relativeTime ? `${formatLimitResetsIn(relativeTime, resetTime, { locale })}\n` : `${formatLimitResetsAt(resetTime, { locale })}\n`;
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
section += `${lt('na', {}, { locale })}\n`;
|
|
321
|
+
}
|
|
322
|
+
return section;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function hasPositivePercentage(value) {
|
|
326
|
+
if (value === null || value === undefined) return false;
|
|
327
|
+
const numeric = Number(value);
|
|
328
|
+
return Number.isFinite(numeric) && numeric > 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function hasPositiveCreditBalance(credits) {
|
|
332
|
+
if (!credits) return false;
|
|
333
|
+
if (credits.unlimited) return true;
|
|
334
|
+
const numeric = Number.parseFloat(String(credits.balance ?? '0'));
|
|
335
|
+
return Number.isFinite(numeric) && numeric > 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
283
338
|
/**
|
|
284
339
|
* Get GitHub API rate limits by calling gh api rate_limit
|
|
285
340
|
* Returns rate limit info for core, search, graphql, and other resources
|
|
@@ -1061,118 +1116,51 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1061
1116
|
sections.push(section);
|
|
1062
1117
|
}
|
|
1063
1118
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1119
|
+
const claudeHeading = formatSubscriptionHeading('claude', subscription, { locale });
|
|
1120
|
+
const useShortClaudeLabels = Boolean(claudeHeading);
|
|
1121
|
+
const claudeSections = [];
|
|
1122
|
+
|
|
1123
|
+
// Claude limits section. When there's an error (e.g., auth expired), show it once and skip empty subsections.
|
|
1066
1124
|
if (claudeError) {
|
|
1067
|
-
|
|
1125
|
+
claudeSections.push(useShortClaudeLabels ? `${claudeError}\n` : `${lt('claude_limits', {}, { locale })}\n${claudeError}\n`);
|
|
1068
1126
|
} else {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
if (timePassed !== null) {
|
|
1075
|
-
const timeBar = getProgressBar(timePassed);
|
|
1076
|
-
sessionSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Use Math.floor so 100% only appears when usage is exactly 100%
|
|
1080
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1133
|
|
1081
|
-
const pct = Math.floor(usage.currentSession.percentage);
|
|
1082
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION);
|
|
1083
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1084
|
-
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1085
|
-
|
|
1086
|
-
const sessionResetTime = getLocalizedResetTime(usage.currentSession, { locale });
|
|
1087
|
-
if (sessionResetTime) {
|
|
1088
|
-
const relativeTime = getLocalizedRelativeReset(usage.currentSession, { locale });
|
|
1089
|
-
if (relativeTime) {
|
|
1090
|
-
sessionSection += `${formatLimitResetsIn(relativeTime, sessionResetTime, { locale })}\n`;
|
|
1091
|
-
} else {
|
|
1092
|
-
sessionSection += `${formatLimitResetsAt(sessionResetTime, { locale })}\n`;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
} else {
|
|
1096
|
-
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1127
|
+
const hasSonnetOnly = hasLimitPercentage(usage?.sonnetOnly);
|
|
1128
|
+
claudeSections.push(formatLimitWindowSection(useShortClaudeLabels ? lt('five_hour_limit_session', {}, { locale }) : lt('claude_5_hour_session', {}, { locale }), usage?.currentSession, 5, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION, { locale }));
|
|
1129
|
+
claudeSections.push(formatLimitWindowSection(useShortClaudeLabels && !hasSonnetOnly ? lt('current_week', {}, { locale }) : lt('current_week_all_models', {}, { locale }), usage?.allModels, 168, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY, { locale }));
|
|
1130
|
+
if (hasSonnetOnly || !useShortClaudeLabels) {
|
|
1131
|
+
claudeSections.push(formatLimitWindowSection(lt('current_week_sonnet_only', {}, { locale }), usage?.sonnetOnly, 168, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY, { locale }));
|
|
1097
1132
|
}
|
|
1098
|
-
sections.push(sessionSection);
|
|
1099
|
-
|
|
1100
|
-
// Current week (all models / seven_day)
|
|
1101
|
-
// Threshold: One-at-a-time mode when usage >= 97%
|
|
1102
|
-
let allModelsSection = `${lt('current_week_all_models', {}, { locale })}\n`;
|
|
1103
|
-
if (hasLimitPercentage(usage?.allModels)) {
|
|
1104
|
-
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1105
|
-
if (timePassed !== null) {
|
|
1106
|
-
const timeBar = getProgressBar(timePassed);
|
|
1107
|
-
allModelsSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1108
|
-
}
|
|
1109
1133
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
|
|
1114
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1115
|
-
allModelsSection += `${bar} ${pct}%${suffix}\n`;
|
|
1116
|
-
|
|
1117
|
-
const allModelsResetTime = getLocalizedResetTime(usage.allModels, { locale });
|
|
1118
|
-
if (allModelsResetTime) {
|
|
1119
|
-
const relativeTime = getLocalizedRelativeReset(usage.allModels, { locale });
|
|
1120
|
-
if (relativeTime) {
|
|
1121
|
-
allModelsSection += `${formatLimitResetsIn(relativeTime, allModelsResetTime, { locale })}\n`;
|
|
1122
|
-
} else {
|
|
1123
|
-
allModelsSection += `${formatLimitResetsAt(allModelsResetTime, { locale })}\n`;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
} else {
|
|
1127
|
-
allModelsSection += `${lt('na', {}, { locale })}\n`;
|
|
1134
|
+
if (!useShortClaudeLabels) {
|
|
1135
|
+
const subscriptionLines = formatSubscriptionLines(subscription, { locale });
|
|
1136
|
+
if (subscriptionLines) claudeSections.push(subscriptionLines);
|
|
1128
1137
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
// Current week (Sonnet only / seven_day_sonnet)
|
|
1132
|
-
// Threshold: One-at-a-time mode when usage >= 97% (same as all models)
|
|
1133
|
-
let sonnetSection = `${lt('current_week_sonnet_only', {}, { locale })}\n`;
|
|
1134
|
-
if (hasLimitPercentage(usage?.sonnetOnly)) {
|
|
1135
|
-
// Add time passed progress bar first (no threshold marker for time)
|
|
1136
|
-
const timePassed = calculateTimePassedPercentage(usage.sonnetOnly.resetsAt, 168);
|
|
1137
|
-
if (timePassed !== null) {
|
|
1138
|
-
const timeBar = getProgressBar(timePassed);
|
|
1139
|
-
sonnetSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1140
|
-
}
|
|
1138
|
+
}
|
|
1141
1139
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const sonnetResetTime = getLocalizedResetTime(usage.sonnetOnly, { locale });
|
|
1151
|
-
if (sonnetResetTime) {
|
|
1152
|
-
const relativeTime = getLocalizedRelativeReset(usage.sonnetOnly, { locale });
|
|
1153
|
-
if (relativeTime) {
|
|
1154
|
-
sonnetSection += `${formatLimitResetsIn(relativeTime, sonnetResetTime, { locale })}\n`;
|
|
1155
|
-
} else {
|
|
1156
|
-
sonnetSection += `${formatLimitResetsAt(sonnetResetTime, { locale })}\n`;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
} else {
|
|
1160
|
-
sonnetSection += `${lt('na', {}, { locale })}\n`;
|
|
1140
|
+
const hasFencedExtraSection = extraSections.some(extra => String(extra ?? '').includes('```'));
|
|
1141
|
+
const useSplitLayout = Boolean(claudeHeading) || hasFencedExtraSection;
|
|
1142
|
+
|
|
1143
|
+
if (!useSplitLayout) {
|
|
1144
|
+
sections.push(...claudeSections);
|
|
1145
|
+
for (const extra of extraSections) {
|
|
1146
|
+
sections.push(extra);
|
|
1161
1147
|
}
|
|
1162
|
-
sections.
|
|
1148
|
+
return formatCodeBlock(sections.join('\n'));
|
|
1149
|
+
}
|
|
1163
1150
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1151
|
+
const markdownSections = [];
|
|
1152
|
+
markdownSections.push(formatCodeBlock(claudeHeading ? sections.join('\n') : [...sections, ...claudeSections].join('\n')));
|
|
1153
|
+
if (claudeHeading) {
|
|
1154
|
+
markdownSections.push(claudeHeading);
|
|
1155
|
+
markdownSections.push(formatCodeBlock(claudeSections.join('\n')));
|
|
1166
1156
|
}
|
|
1167
1157
|
|
|
1168
|
-
// Append any caller-provided extra sections (e.g. queue status) inside the code block
|
|
1169
1158
|
for (const extra of extraSections) {
|
|
1170
|
-
|
|
1159
|
+
const formatted = formatPlainTitledCodeSection(extra);
|
|
1160
|
+
if (formatted) markdownSections.push(formatted);
|
|
1171
1161
|
}
|
|
1172
1162
|
|
|
1173
|
-
|
|
1174
|
-
// Sections are separated by blank lines; the trailing newline on each section provides spacing.
|
|
1175
|
-
return '```\n' + sections.join('\n') + '```';
|
|
1163
|
+
return markdownSections.join('\n\n');
|
|
1176
1164
|
}
|
|
1177
1165
|
|
|
1178
1166
|
/**
|
|
@@ -1185,64 +1173,33 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1185
1173
|
*/
|
|
1186
1174
|
export function formatCodexLimitsSection(codexLimits, codexError = null, options = {}) {
|
|
1187
1175
|
const locale = resolveLimitLocale(options);
|
|
1188
|
-
|
|
1189
|
-
return `${lt('codex_limits', {}, { locale })}\n${codexError}\n`;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1176
|
+
const subscription = options?.subscription || null;
|
|
1192
1177
|
const usage = codexLimits?.usage || null;
|
|
1193
1178
|
const additionalRateLimits = codexLimits?.additionalRateLimits || [];
|
|
1194
1179
|
const credits = codexLimits?.credits || null;
|
|
1195
|
-
const planType = codexLimits?.planType || null;
|
|
1196
|
-
const
|
|
1180
|
+
const planType = subscription?.planType || codexLimits?.planType || null;
|
|
1181
|
+
const heading = formatSubscriptionHeading('codex', subscription, { locale, planType });
|
|
1182
|
+
const useTitledLayout = Boolean(heading);
|
|
1197
1183
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1184
|
+
if (codexError) {
|
|
1185
|
+
const errorSection = useTitledLayout ? `${codexError}\n` : `${lt('codex_limits', {}, { locale })}\n${codexError}\n`;
|
|
1186
|
+
return useTitledLayout ? `${heading}\n\n${formatCodeBlock(errorSection)}` : errorSection;
|
|
1201
1187
|
}
|
|
1202
1188
|
|
|
1203
|
-
let
|
|
1204
|
-
if (
|
|
1205
|
-
|
|
1206
|
-
if (timePassed !== null) {
|
|
1207
|
-
sessionSection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1208
|
-
}
|
|
1209
|
-
const pct = Math.floor(usage.currentSession.percentage);
|
|
1210
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION);
|
|
1211
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1212
|
-
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1213
|
-
const sessionResetTime = getLocalizedResetTime(usage.currentSession, { locale });
|
|
1214
|
-
if (sessionResetTime) {
|
|
1215
|
-
const relativeTime = getLocalizedRelativeReset(usage.currentSession, { locale });
|
|
1216
|
-
sessionSection += relativeTime ? `${formatLimitResetsIn(relativeTime, sessionResetTime, { locale })}\n` : `${formatLimitResetsAt(sessionResetTime, { locale })}\n`;
|
|
1217
|
-
}
|
|
1218
|
-
} else {
|
|
1219
|
-
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1189
|
+
let section = useTitledLayout ? '' : `${lt('codex_limits', {}, { locale })}\n`;
|
|
1190
|
+
if (planType && !useTitledLayout) {
|
|
1191
|
+
section += `${lt('plan', {}, { locale })}: ${planType}\n`;
|
|
1220
1192
|
}
|
|
1221
1193
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1225
|
-
if (timePassed !== null) {
|
|
1226
|
-
weeklySection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1227
|
-
}
|
|
1228
|
-
const pct = Math.floor(usage.allModels.percentage);
|
|
1229
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_WEEKLY);
|
|
1230
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1231
|
-
weeklySection += `${bar} ${pct}%${suffix}\n`;
|
|
1232
|
-
const weeklyResetTime = getLocalizedResetTime(usage.allModels, { locale });
|
|
1233
|
-
if (weeklyResetTime) {
|
|
1234
|
-
const relativeTime = getLocalizedRelativeReset(usage.allModels, { locale });
|
|
1235
|
-
weeklySection += relativeTime ? `${formatLimitResetsIn(relativeTime, weeklyResetTime, { locale })}\n` : `${formatLimitResetsAt(weeklyResetTime, { locale })}\n`;
|
|
1236
|
-
}
|
|
1237
|
-
} else {
|
|
1238
|
-
weeklySection += `${lt('na', {}, { locale })}\n`;
|
|
1239
|
-
}
|
|
1194
|
+
const sessionSection = formatLimitWindowSection(useTitledLayout ? lt('five_hour_limit_session', {}, { locale }) : lt('codex_5_hour_session', {}, { locale }), usage?.currentSession, 5, DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION, { locale });
|
|
1195
|
+
const weeklySection = formatLimitWindowSection(useTitledLayout ? lt('current_week', {}, { locale }) : lt('current_week_all_models', {}, { locale }), usage?.allModels, 168, DISPLAY_THRESHOLDS.CODEX_WEEKLY, { locale });
|
|
1240
1196
|
|
|
1241
1197
|
section += `${sessionSection}\n${weeklySection}`;
|
|
1242
1198
|
|
|
1243
|
-
|
|
1199
|
+
const visibleAdditionalRateLimits = additionalRateLimits.filter(limit => hasPositivePercentage(limit.allModels?.percentage));
|
|
1200
|
+
if (visibleAdditionalRateLimits.length > 0) {
|
|
1244
1201
|
section += `\n${lt('additional_codex_limits', {}, { locale })}\n`;
|
|
1245
|
-
for (const limit of
|
|
1202
|
+
for (const limit of visibleAdditionalRateLimits) {
|
|
1246
1203
|
const sessionPct = limit.currentSession?.percentage;
|
|
1247
1204
|
const weeklyPct = limit.allModels?.percentage;
|
|
1248
1205
|
const sessionText = sessionPct === null || sessionPct === undefined ? `${lt('session', {}, { locale })} ${lt('na', {}, { locale })}` : `${lt('session', {}, { locale })} ${Math.floor(sessionPct)}%`;
|
|
@@ -1251,15 +1208,17 @@ export function formatCodexLimitsSection(codexLimits, codexError = null, options
|
|
|
1251
1208
|
}
|
|
1252
1209
|
}
|
|
1253
1210
|
|
|
1254
|
-
if (credits) {
|
|
1211
|
+
if (hasPositiveCreditBalance(credits)) {
|
|
1255
1212
|
const creditSummary = credits.unlimited ? lt('unlimited', {}, { locale }) : `${credits.balance ?? '0'} ${lt('balance', {}, { locale })}`;
|
|
1256
1213
|
section += `\n${lt('codex_credits', {}, { locale })}\n${creditSummary}\n`;
|
|
1257
1214
|
}
|
|
1258
1215
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1216
|
+
if (!useTitledLayout) {
|
|
1217
|
+
const subscriptionLines = formatSubscriptionLines(subscription, { locale });
|
|
1218
|
+
if (subscriptionLines) section += subscriptionLines;
|
|
1219
|
+
}
|
|
1261
1220
|
|
|
1262
|
-
return section;
|
|
1221
|
+
return useTitledLayout ? `${heading}\n\n${formatCodeBlock(section)}` : section;
|
|
1263
1222
|
}
|
|
1264
1223
|
|
|
1265
1224
|
// ============================================================================
|
package/src/locales/en.lino
CHANGED
|
@@ -192,6 +192,11 @@ en
|
|
|
192
192
|
hour
|
|
193
193
|
session "Claude 5 hour session"
|
|
194
194
|
limits "Claude limits"
|
|
195
|
+
subscription
|
|
196
|
+
title "Claude{{plan}} subscription"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "ChatGPT{{plan}} subscription"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ en
|
|
|
205
210
|
current
|
|
206
211
|
time "Current time"
|
|
207
212
|
week
|
|
213
|
+
label "Current week"
|
|
208
214
|
all
|
|
209
215
|
models "Current week (all models)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ en
|
|
|
226
232
|
end "End"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5 hour session"
|
|
229
237
|
session "5h session"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ en
|
|
|
314
322
|
session "session"
|
|
315
323
|
start "Start"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "ends {{time}}"
|
|
328
|
+
in "ends in {{duration}}; {{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "trial ends {{time}}"
|
|
332
|
+
in "trial ends in {{duration}}; {{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "Subscription ends {{time}}"
|
|
319
335
|
in "Subscription ends in {{duration}} ({{time}})"
|
package/src/locales/hi.lino
CHANGED
|
@@ -192,6 +192,11 @@ hi
|
|
|
192
192
|
hour
|
|
193
193
|
session "Claude 5 घंटे का सत्र"
|
|
194
194
|
limits "Claude सीमाएँ"
|
|
195
|
+
subscription
|
|
196
|
+
title "Claude{{plan}} सदस्यता"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "ChatGPT{{plan}} सदस्यता"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ hi
|
|
|
205
210
|
current
|
|
206
211
|
time "वर्तमान समय"
|
|
207
212
|
week
|
|
213
|
+
label "वर्तमान सप्ताह"
|
|
208
214
|
all
|
|
209
215
|
models "वर्तमान सप्ताह (सभी मॉडल)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ hi
|
|
|
226
232
|
end "समाप्ति"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5 घंटे का सत्र"
|
|
229
237
|
session "5 घंटे का सत्र"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ hi
|
|
|
314
322
|
session "सत्र"
|
|
315
323
|
start "शुरुआत"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "{{time}} को समाप्त होगी"
|
|
328
|
+
in "{{duration}} में समाप्त होगी; {{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "ट्रायल {{time}} को समाप्त होगा"
|
|
332
|
+
in "ट्रायल {{duration}} में समाप्त होगा; {{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "सदस्यता समाप्त होगी {{time}}"
|
|
319
335
|
in "सदस्यता {{duration}} में समाप्त होगी ({{time}})"
|
package/src/locales/ru.lino
CHANGED
|
@@ -192,6 +192,11 @@ ru
|
|
|
192
192
|
hour
|
|
193
193
|
session "5-часовой сеанс Claude"
|
|
194
194
|
limits "Лимиты Claude"
|
|
195
|
+
subscription
|
|
196
|
+
title "Подписка Claude{{plan}}"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "Подписка ChatGPT{{plan}}"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ ru
|
|
|
205
210
|
current
|
|
206
211
|
time "Текущее время"
|
|
207
212
|
week
|
|
213
|
+
label "Текущая неделя"
|
|
208
214
|
all
|
|
209
215
|
models "Текущая неделя (все модели)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ ru
|
|
|
226
232
|
end "Конец"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5-часовой сеанс"
|
|
229
237
|
session "5-часовой сеанс"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ ru
|
|
|
314
322
|
session "сеанс"
|
|
315
323
|
start "Начало"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "заканчивается {{time}}"
|
|
328
|
+
in "заканчивается через {{duration}}; {{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "пробный период заканчивается {{time}}"
|
|
332
|
+
in "пробный период заканчивается через {{duration}}; {{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "Подписка заканчивается {{time}}"
|
|
319
335
|
in "Подписка заканчивается через {{duration}} ({{time}})"
|
package/src/locales/zh.lino
CHANGED
|
@@ -192,6 +192,11 @@ zh
|
|
|
192
192
|
hour
|
|
193
193
|
session "Claude 5 小时会话"
|
|
194
194
|
limits "Claude 限额"
|
|
195
|
+
subscription
|
|
196
|
+
title "Claude{{plan}} 订阅"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "ChatGPT{{plan}} 订阅"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ zh
|
|
|
205
210
|
current
|
|
206
211
|
time "当前时间"
|
|
207
212
|
week
|
|
213
|
+
label "本周"
|
|
208
214
|
all
|
|
209
215
|
models "本周(所有模型)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ zh
|
|
|
226
232
|
end "结束"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5 小时会话"
|
|
229
237
|
session "5 小时会话"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ zh
|
|
|
314
322
|
session "会话"
|
|
315
323
|
start "开始"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "结束于 {{time}}"
|
|
328
|
+
in "将在 {{duration}} 后结束;{{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "试用结束于 {{time}}"
|
|
332
|
+
in "试用将在 {{duration}} 后结束;{{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "订阅结束于 {{time}}"
|
|
319
335
|
in "订阅将在 {{duration}} 后结束 ({{time}})"
|