@howaboua/opencode-usage-plugin 0.0.2 → 0.0.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/README.md CHANGED
@@ -9,6 +9,8 @@ Track AI provider rate limits and quotas in real-time.
9
9
  - **Inline status** – Results appear directly in your chat, no context switching
10
10
  - **Zero setup** – Auto-detects providers from your existing config
11
11
 
12
+ <img width="1300" height="900" alt="image" src="https://github.com/user-attachments/assets/cd49e450-f4b6-4314-b236-b3a92bffdb88" />
13
+
12
14
  ## Installation
13
15
 
14
16
  Add to your `opencode.json`:
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAI1C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAkD5D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAI1C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAwD5D"}
@@ -38,7 +38,10 @@ export async function loadProxyConfig() {
38
38
  }
39
39
  try {
40
40
  const content = await file.text();
41
- const cleanJson = content.replace(/(\".*?\"|\'.*?\')|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g1) => g1 ?? "");
41
+ // Remove comments first (both // and /* */)
42
+ const withoutComments = content.replace(/(\".*?\"|\'.*?\')|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g1) => g1 ?? "");
43
+ // Remove trailing commas before closing brackets/braces
44
+ const cleanJson = withoutComments.replace(/,(\s*[}\]])/g, "$1");
42
45
  const config = JSON.parse(cleanJson);
43
46
  if (!config.endpoint) {
44
47
  throw new Error('Config must contain "endpoint" field');
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,SAAS,CAAA;AAiFpE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAiC7D"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA8B,MAAM,SAAS,CAAA;AAwHxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoC7D"}
@@ -4,10 +4,29 @@
4
4
  const GROUP_MAPPING = {
5
5
  "claude": "claude",
6
6
  "g3-pro": "g3-pro",
7
- "g3-flash": "g3-fla",
7
+ "g3-flash": "g3-flash",
8
+ "g25-flash": "25-flash",
9
+ "g25-lite": "25-lite",
8
10
  "pro": "g3-pro",
9
- "3-flash": "g3-fla"
11
+ "3-flash": "g3-flash",
12
+ "25-flash": "25-flash",
13
+ "25-lite": "25-lite"
10
14
  };
15
+ // Display name ordering priority
16
+ const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
17
+ function sortGroupNames(groups) {
18
+ return Array.from(groups.keys()).sort((a, b) => {
19
+ const aIndex = GROUP_ORDER.indexOf(a);
20
+ const bIndex = GROUP_ORDER.indexOf(b);
21
+ if (aIndex !== -1 && bIndex !== -1)
22
+ return aIndex - bIndex;
23
+ if (aIndex !== -1)
24
+ return -1;
25
+ if (bIndex !== -1)
26
+ return 1;
27
+ return a.localeCompare(b);
28
+ });
29
+ }
11
30
  function formatBar(remainingPercent) {
12
31
  const clamped = Math.max(0, Math.min(100, remainingPercent));
13
32
  const size = 20;
@@ -48,26 +67,44 @@ function aggregateCredentialsByTier(credentials) {
48
67
  };
49
68
  for (const cred of credentials) {
50
69
  const tier = normalizeTier(cred.tier);
51
- const groups = cred.model_groups ?? {};
52
- for (const [name, group] of Object.entries(groups)) {
70
+ const groupUsage = cred.group_usage ?? {};
71
+ for (const [name, groupData] of Object.entries(groupUsage)) {
53
72
  if (!(name in GROUP_MAPPING))
54
73
  continue;
55
74
  const mappedName = GROUP_MAPPING[name];
75
+ // Find the best window from group_usage
76
+ const windows = groupData.windows || {};
77
+ let bestWindow = null;
78
+ // Priority order for windows
79
+ const windowPriority = ["daily", "5h", "1h", "15m"];
80
+ for (const windowName of windowPriority) {
81
+ if (windows[windowName]) {
82
+ bestWindow = windows[windowName];
83
+ break;
84
+ }
85
+ }
86
+ // Fallback to any available window
87
+ if (!bestWindow && Object.keys(windows).length > 0) {
88
+ bestWindow = Object.values(windows)[0];
89
+ }
90
+ if (!bestWindow)
91
+ continue;
56
92
  const existing = result[tier].get(mappedName);
57
93
  if (existing) {
58
- existing.remaining += group.requests_remaining;
59
- existing.max += group.requests_max;
60
- if (group.reset_time_iso) {
61
- if (!existing.resetTime || new Date(group.reset_time_iso) > new Date(existing.resetTime)) {
62
- existing.resetTime = group.reset_time_iso;
94
+ existing.remaining += bestWindow.remaining;
95
+ existing.max += bestWindow.limit || 0;
96
+ if (bestWindow.reset_at) {
97
+ const newResetTime = new Date(bestWindow.reset_at * 1000).toISOString();
98
+ if (!existing.resetTime || new Date(newResetTime) > new Date(existing.resetTime)) {
99
+ existing.resetTime = newResetTime;
63
100
  }
64
101
  }
65
102
  }
66
103
  else {
67
104
  result[tier].set(mappedName, {
68
- remaining: group.requests_remaining,
69
- max: group.requests_max,
70
- resetTime: group.reset_time_iso,
105
+ remaining: bestWindow.remaining,
106
+ max: bestWindow.limit || bestWindow.remaining,
107
+ resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
71
108
  });
72
109
  }
73
110
  }
@@ -84,13 +121,16 @@ export function formatProxyLimits(data) {
84
121
  }
85
122
  for (const [providerName, provider] of Object.entries(data.providers)) {
86
123
  lines.push(`${providerName}:`);
87
- const tierData = aggregateCredentialsByTier(provider.credentials ?? []);
124
+ const credentialsArray = Object.values(provider.credentials ?? {});
125
+ const tierData = aggregateCredentialsByTier(credentialsArray);
88
126
  for (const [tierName, quotas] of Object.entries(tierData)) {
89
127
  if (quotas.size === 0)
90
128
  continue;
91
129
  const tierLabel = tierName === "paid" ? "Paid" : "Free";
92
130
  lines.push(` ${tierLabel}:`);
93
- for (const [groupName, quota] of quotas) {
131
+ const sortedNames = sortGroupNames(quotas);
132
+ for (const groupName of sortedNames) {
133
+ const quota = quotas.get(groupName);
94
134
  const remainingPct = quota.max > 0 ? (quota.remaining / quota.max) * 100 : 0;
95
135
  const resetSuffix = quota.remaining === 0 ? formatResetTime(quota.resetTime) : "";
96
136
  lines.push(` ${groupName}: ${formatBar(remainingPct)} ${quota.remaining}/${quota.max}${resetSuffix}`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AA8F5C,eAAO,MAAM,aAAa,EAAE,aAwB3B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAiK5C,eAAO,MAAM,aAAa,EAAE,aAwB3B,CAAA"}
@@ -10,32 +10,90 @@ export { formatProxyLimits } from "./format";
10
10
  const GROUP_MAPPING = {
11
11
  "claude": "claude",
12
12
  "g3-pro": "g3-pro",
13
- "g3-flash": "g3-fla",
13
+ "g3-flash": "g3-flash",
14
+ "g25-flash": "25-flash",
15
+ "g25-lite": "25-lite",
14
16
  "pro": "g3-pro",
15
- "3-flash": "g3-fla"
17
+ "3-flash": "g3-flash",
18
+ "25-flash": "25-flash",
19
+ "25-lite": "25-lite"
16
20
  };
21
+ // Display name ordering priority
22
+ const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
23
+ function sortQuotaGroups(groups) {
24
+ return groups.sort((a, b) => {
25
+ const aIndex = GROUP_ORDER.indexOf(a.name);
26
+ const bIndex = GROUP_ORDER.indexOf(b.name);
27
+ // Both in priority list: sort by index
28
+ if (aIndex !== -1 && bIndex !== -1)
29
+ return aIndex - bIndex;
30
+ // Only a in priority list: a comes first
31
+ if (aIndex !== -1)
32
+ return -1;
33
+ // Only b in priority list: b comes first
34
+ if (bIndex !== -1)
35
+ return 1;
36
+ // Neither in priority list: alphabetical
37
+ return a.name.localeCompare(b.name);
38
+ });
39
+ }
17
40
  function normalizeTier(tier) {
18
41
  if (!tier)
19
42
  return "free";
20
43
  return tier.includes("free") ? "free" : "paid";
21
44
  }
22
- function parseQuotaGroupsFromCredential(modelGroups) {
23
- if (!modelGroups)
45
+ /**
46
+ * Extract quota groups from group_usage data
47
+ * New API structure: group_usage[groupName].windows[windowName]
48
+ */
49
+ function parseQuotaGroupsFromCredential(groupUsage) {
50
+ if (!groupUsage)
24
51
  return [];
25
- return Object.entries(modelGroups)
26
- .filter(([name]) => name in GROUP_MAPPING)
27
- .map(([name, group]) => {
28
- const realRemainingPct = group.requests_max > 0
29
- ? Math.round((group.requests_remaining / group.requests_max) * 100)
30
- : 0;
31
- return {
32
- name: GROUP_MAPPING[name],
33
- remaining: group.requests_remaining,
34
- max: group.requests_max,
35
- remainingPct: realRemainingPct,
36
- resetTime: group.reset_time_iso,
37
- };
38
- });
52
+ const result = new Map();
53
+ for (const [groupName, groupData] of Object.entries(groupUsage)) {
54
+ const mappedName = GROUP_MAPPING[groupName];
55
+ if (!mappedName)
56
+ continue;
57
+ // Find the window with the best data (prefer daily, then 5h, then any)
58
+ const windows = groupData.windows || {};
59
+ let bestWindow = null;
60
+ // Priority order for windows
61
+ const windowPriority = ["daily", "5h", "1h", "15m"];
62
+ for (const windowName of windowPriority) {
63
+ if (windows[windowName]) {
64
+ bestWindow = windows[windowName];
65
+ break;
66
+ }
67
+ }
68
+ // Fallback to any available window
69
+ if (!bestWindow && Object.keys(windows).length > 0) {
70
+ bestWindow = Object.values(windows)[0];
71
+ }
72
+ if (!bestWindow)
73
+ continue;
74
+ const existing = result.get(mappedName);
75
+ if (existing) {
76
+ existing.remaining += bestWindow.remaining;
77
+ existing.max += bestWindow.limit || 0;
78
+ // Use the latest reset time
79
+ if (bestWindow.reset_at) {
80
+ const newResetTime = new Date(bestWindow.reset_at * 1000).toISOString();
81
+ if (!existing.resetTime || new Date(newResetTime) > new Date(existing.resetTime)) {
82
+ existing.resetTime = newResetTime;
83
+ }
84
+ }
85
+ }
86
+ else {
87
+ result.set(mappedName, {
88
+ name: mappedName,
89
+ remaining: bestWindow.remaining,
90
+ max: bestWindow.limit || bestWindow.remaining,
91
+ remainingPct: bestWindow.limit ? Math.round((bestWindow.remaining / bestWindow.limit) * 100) : 0,
92
+ resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
93
+ });
94
+ }
95
+ }
96
+ return Array.from(result.values());
39
97
  }
40
98
  function aggregateByTier(credentials) {
41
99
  const tiers = {
@@ -44,7 +102,7 @@ function aggregateByTier(credentials) {
44
102
  };
45
103
  for (const cred of credentials) {
46
104
  const tier = normalizeTier(cred.tier);
47
- const groups = parseQuotaGroupsFromCredential(cred.model_groups);
105
+ const groups = parseQuotaGroupsFromCredential(cred.group_usage);
48
106
  for (const group of groups) {
49
107
  const existing = tiers[tier].get(group.name);
50
108
  if (existing) {
@@ -66,23 +124,27 @@ function aggregateByTier(credentials) {
66
124
  }
67
125
  const result = [];
68
126
  if (tiers.paid.size > 0) {
69
- result.push({ tier: "paid", quotaGroups: Array.from(tiers.paid.values()) });
127
+ result.push({ tier: "paid", quotaGroups: sortQuotaGroups(Array.from(tiers.paid.values())) });
70
128
  }
71
129
  if (tiers.free.size > 0) {
72
- result.push({ tier: "free", quotaGroups: Array.from(tiers.free.values()) });
130
+ result.push({ tier: "free", quotaGroups: sortQuotaGroups(Array.from(tiers.free.values())) });
73
131
  }
74
132
  return result;
75
133
  }
76
134
  function parseProviders(data) {
77
135
  if (!data.providers)
78
136
  return [];
79
- return Object.entries(data.providers).map(([name, provider]) => ({
80
- name,
81
- tiers: aggregateByTier(provider.credentials ?? []),
82
- }));
137
+ return Object.entries(data.providers).map(([name, provider]) => {
138
+ // Convert credentials object to array
139
+ const credentialsArray = Object.values(provider.credentials || {});
140
+ return {
141
+ name,
142
+ tiers: aggregateByTier(credentialsArray),
143
+ };
144
+ });
83
145
  }
84
146
  function parseProxyQuota(data) {
85
- const summary = data.global_summary ?? data.summary;
147
+ const summary = data.summary;
86
148
  return {
87
149
  providers: parseProviders(data),
88
150
  totalCredentials: summary?.total_credentials ?? 0,
@@ -13,96 +13,116 @@ export type ProxyConfig = {
13
13
  };
14
14
  /** Token statistics from the proxy */
15
15
  export type TokenStats = {
16
- input_cached: number;
17
- input_uncached: number;
18
- input_cache_pct: number;
19
- output: number;
16
+ input_cached?: number;
17
+ input_uncached?: number;
18
+ input_cache_pct?: number;
19
+ output?: number;
20
+ prompt_tokens?: number;
21
+ completion_tokens?: number;
22
+ thinking_tokens?: number;
23
+ output_tokens?: number;
24
+ prompt_tokens_cache_read?: number;
25
+ prompt_tokens_cache_write?: number;
26
+ total_tokens?: number;
27
+ request_count?: number;
28
+ success_count?: number;
29
+ failure_count?: number;
30
+ approx_cost?: number;
20
31
  };
21
- /** Quota group aggregation */
22
- export type QuotaGroup = {
23
- avg_remaining_pct: number;
24
- credentials_exhausted: number;
25
- credentials_total: number;
26
- models: string[];
27
- tiers: Record<string, {
28
- active: number;
32
+ /** Window-based quota information */
33
+ export type WindowQuota = {
34
+ limit?: number;
35
+ remaining: number;
36
+ reset_at?: number | null;
37
+ request_count?: number;
38
+ success_count?: number;
39
+ failure_count?: number;
40
+ total_used?: number;
41
+ total_remaining?: number;
42
+ total_max?: number;
43
+ remaining_pct?: number;
44
+ };
45
+ /** Model group usage information */
46
+ export type GroupUsageWindow = {
47
+ [windowName: string]: WindowQuota;
48
+ };
49
+ export type GroupUsage = {
50
+ windows: GroupUsageWindow;
51
+ totals: TokenStats;
52
+ fair_cycle_exhausted?: boolean;
53
+ fair_cycle_reason?: string | null;
54
+ cooldown_remaining?: number | null;
55
+ cooldown_source?: string | null;
56
+ custom_cap?: number | null;
57
+ };
58
+ /** Model usage information */
59
+ export type ModelUsage = {
60
+ windows: GroupUsageWindow;
61
+ totals: TokenStats;
62
+ };
63
+ /** Tier availability info */
64
+ export type TierAvailability = {
65
+ total: number;
66
+ available: number;
67
+ };
68
+ /** Tier window info */
69
+ export type TierWindow = {
70
+ total_used: number;
71
+ total_remaining: number;
72
+ total_max: number;
73
+ remaining_pct: number;
74
+ tier_availability: Record<string, TierAvailability>;
75
+ };
76
+ /** Model group tiers */
77
+ export type GroupTiers = {
78
+ [tierName: string]: {
29
79
  priority: number;
30
80
  total: number;
31
- }>;
32
- total_remaining_pct: number;
33
- total_requests_max: number;
34
- total_requests_remaining: number;
35
- total_requests_used: number;
81
+ };
36
82
  };
37
- /** Model quota information */
38
- export type ModelQuota = {
39
- requests: number;
40
- request_count: number;
41
- success_count: number;
42
- failure_count: number;
43
- prompt_tokens: number;
44
- prompt_tokens_cached: number;
45
- completion_tokens: number;
46
- approx_cost: number;
47
- window_start_ts: number | null;
48
- quota_reset_ts: number | null;
49
- baseline_remaining_fraction: number | null;
50
- baseline_fetched_at: number | null;
51
- quota_max_requests: number;
52
- quota_display: string;
83
+ /** Fair cycle summary */
84
+ export type FairCycleSummary = {
85
+ exhausted_count: number;
86
+ total_count: number;
53
87
  };
54
- /** Model group information */
55
- export type ModelGroup = {
56
- confidence: string;
57
- display: string;
58
- is_exhausted: boolean;
59
- models: string[];
60
- remaining_pct: number;
61
- requests_max: number;
62
- requests_remaining: number;
63
- requests_used: number;
64
- reset_time_iso: string | null;
88
+ /** Model group aggregation */
89
+ export type ModelGroupAggregation = {
90
+ tiers: GroupTiers;
91
+ windows: {
92
+ [windowName: string]: TierWindow;
93
+ };
94
+ fair_cycle_summary: FairCycleSummary;
65
95
  };
66
- /** Credential information */
67
- export type Credential = {
68
- identifier: string;
96
+ /** Credential information from new API */
97
+ export type CredentialData = {
98
+ stable_id: string;
99
+ accessor_masked?: string;
69
100
  full_path: string;
70
- status: string;
71
- last_used_ts: number;
101
+ identifier: string;
102
+ email?: string | null;
72
103
  tier?: string;
73
- requests: number;
74
- tokens: TokenStats;
75
- approx_cost: number | null;
76
- global: {
77
- requests: number;
78
- tokens: TokenStats;
79
- approx_cost: number | null;
80
- };
81
- models: Record<string, ModelQuota>;
82
- model_groups?: Record<string, ModelGroup>;
104
+ priority?: number;
105
+ active_requests?: number;
106
+ status: string;
107
+ totals: TokenStats;
108
+ model_usage?: Record<string, ModelUsage>;
109
+ group_usage?: Record<string, GroupUsage>;
110
+ last_used_at?: number;
111
+ first_used_at?: number;
83
112
  };
84
- /** Provider information */
113
+ /** Provider information from new API */
85
114
  export type Provider = {
115
+ provider: string;
86
116
  credential_count: number;
87
- active_count: number;
88
- on_cooldown_count: number;
89
- exhausted_count: number;
90
- total_requests: number;
91
- tokens: TokenStats;
92
- approx_cost: number | null;
93
- credentials: Credential[];
94
- quota_groups?: Record<string, QuotaGroup>;
95
- global?: {
96
- approx_cost: number | null;
97
- tokens: TokenStats;
98
- total_requests: number;
99
- };
117
+ rotation_mode?: string;
118
+ credentials: Record<string, CredentialData>;
119
+ quota_groups?: Record<string, ModelGroupAggregation>;
100
120
  };
101
- /** Summary statistics */
121
+ /** Summary statistics from new API */
102
122
  export type Summary = {
103
- total_providers: number;
123
+ total_providers?: number;
104
124
  total_credentials: number;
105
- active_credentials?: number;
125
+ active_credentials: number;
106
126
  exhausted_credentials?: number;
107
127
  total_requests: number;
108
128
  tokens: TokenStats;
@@ -112,7 +132,6 @@ export type Summary = {
112
132
  export type ProxyResponse = {
113
133
  providers: Record<string, Provider>;
114
134
  summary: Summary;
115
- global_summary?: Summary;
116
135
  data_source: string;
117
136
  timestamp: number;
118
137
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhB;AA0GD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAwChB;AA0GD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
package/dist/ui/status.js CHANGED
@@ -2,25 +2,45 @@
2
2
  * Renders usage snapshots into readable status text.
3
3
  */
4
4
  export async function sendStatusMessage(options) {
5
- const sent = await options.client.session
5
+ // 1. Send to Companion via Bus
6
+ // @ts-ignore
7
+ const bus = options.client.bus;
8
+ if (bus) {
9
+ try {
10
+ await bus.publish({
11
+ topic: "companion.projection",
12
+ body: {
13
+ key: "usage",
14
+ kind: "markdown",
15
+ content: options.text,
16
+ },
17
+ });
18
+ }
19
+ catch { }
20
+ }
21
+ // 2. Send plain message to TUI
22
+ await options.client.session
6
23
  .prompt({
7
24
  path: { id: options.sessionID },
8
25
  body: {
9
26
  noReply: true,
10
- agent: options.state.agent,
11
- model: options.state.model,
12
- parts: [{ type: "text", text: options.text, ignored: true }],
27
+ parts: [
28
+ {
29
+ type: "text",
30
+ text: options.text,
31
+ ignored: true,
32
+ },
33
+ ],
13
34
  },
14
35
  })
15
- .then(() => true)
16
- .catch(() => false);
17
- if (sent)
18
- return;
19
- await options.client.tui
20
- .showToast({
21
- body: { title: "Usage Status", message: options.text, variant: "info" },
22
- })
23
- .catch(() => { });
36
+ .catch(async () => {
37
+ // 3. Fallback: Toast
38
+ await options.client.tui
39
+ .showToast({
40
+ body: { title: "Usage Status", message: options.text, variant: "info" },
41
+ })
42
+ .catch(() => { });
43
+ });
24
44
  }
25
45
  function formatBar(remainingPercent) {
26
46
  const clamped = Math.max(0, Math.min(100, remainingPercent));
package/package.json CHANGED
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "@howaboua/opencode-usage-plugin",
3
- "version": "0.0.2",
4
- "description": "OpenCode plugin for tracking AI provider usage, rate limits, and quotas",
3
+ "version": "0.0.4",
4
+ "description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
- "files": ["dist", "README.md", "LICENSE"],
9
- "keywords": ["opencode", "opencode-plugin", "plugin", "usage", "tracking", "rate-limit"],
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "keywords": [
14
+ "opencode",
15
+ "opencode-plugin",
16
+ "plugin",
17
+ "usage",
18
+ "tracking",
19
+ "rate-limit"
20
+ ],
10
21
  "license": "MIT",
11
22
  "peerDependencies": {
12
23
  "@opencode-ai/plugin": "^1.0.0"