@leo000001/opencode-quota-sidebar 4.0.9 → 4.0.12
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 +504 -446
- package/README.zh-CN.md +516 -458
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +123 -46
- package/dist/cli_render.d.ts +4 -4
- package/dist/cli_render.js +74 -74
- package/dist/cost.d.ts +21 -4
- package/dist/cost.js +493 -264
- package/dist/format.d.ts +5 -5
- package/dist/format.js +288 -287
- package/dist/history_usage.d.ts +15 -9
- package/dist/history_usage.js +28 -22
- package/dist/index.js +15 -1
- package/dist/models_dev_pricing.d.ts +6 -0
- package/dist/models_dev_pricing.js +226 -0
- package/dist/opencode_pricing.d.ts +14 -0
- package/dist/opencode_pricing.js +273 -0
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +27 -28
- package/dist/storage_parse.d.ts +1 -1
- package/dist/storage_parse.js +51 -45
- package/dist/storage_paths.d.ts +1 -0
- package/dist/storage_paths.js +26 -11
- package/dist/title_apply.d.ts +1 -17
- package/dist/title_apply.js +17 -48
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +29 -15
- package/dist/tui.d.ts +1 -1
- package/dist/tui.tsx +481 -471
- package/dist/tui_helpers.d.ts +5 -3
- package/dist/tui_helpers.js +62 -34
- package/dist/types.d.ts +8 -10
- package/dist/usage.d.ts +9 -6
- package/dist/usage.js +27 -21
- package/dist/usage_service.d.ts +8 -7
- package/dist/usage_service.js +261 -150
- package/package.json +1 -1
package/dist/storage_paths.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import os from
|
|
2
|
-
import path from
|
|
3
|
-
import { isDateKey } from
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isDateKey } from "./storage_dates.js";
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the OpenCode data directory.
|
|
6
6
|
*
|
|
@@ -17,8 +17,8 @@ export function resolveOpencodeDataDir() {
|
|
|
17
17
|
return path.resolve(override);
|
|
18
18
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
19
19
|
if (xdg)
|
|
20
|
-
return path.join(path.resolve(xdg),
|
|
21
|
-
return path.join(os.homedir(),
|
|
20
|
+
return path.join(path.resolve(xdg), "opencode");
|
|
21
|
+
return path.join(os.homedir(), ".local", "share", "opencode");
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Resolve OpenCode config directory.
|
|
@@ -31,23 +31,38 @@ export function resolveOpencodeConfigDir() {
|
|
|
31
31
|
return path.resolve(override);
|
|
32
32
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
33
33
|
if (xdg)
|
|
34
|
-
return path.join(path.resolve(xdg),
|
|
35
|
-
return path.join(os.homedir(),
|
|
34
|
+
return path.join(path.resolve(xdg), "opencode");
|
|
35
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
36
|
+
}
|
|
37
|
+
export function opencodeConfigPaths(worktree, directory) {
|
|
38
|
+
const configDir = resolveOpencodeConfigDir();
|
|
39
|
+
return [
|
|
40
|
+
path.join(configDir, "opencode.jsonc"),
|
|
41
|
+
path.join(configDir, "opencode.json"),
|
|
42
|
+
path.join(worktree, "opencode.jsonc"),
|
|
43
|
+
path.join(worktree, "opencode.json"),
|
|
44
|
+
path.join(directory, "opencode.jsonc"),
|
|
45
|
+
path.join(directory, "opencode.json"),
|
|
46
|
+
path.join(worktree, ".opencode", "opencode.jsonc"),
|
|
47
|
+
path.join(worktree, ".opencode", "opencode.json"),
|
|
48
|
+
path.join(directory, ".opencode", "opencode.jsonc"),
|
|
49
|
+
path.join(directory, ".opencode", "opencode.json"),
|
|
50
|
+
];
|
|
36
51
|
}
|
|
37
52
|
export function stateFilePath(dataDir) {
|
|
38
|
-
return path.join(dataDir,
|
|
53
|
+
return path.join(dataDir, "quota-sidebar.state.json");
|
|
39
54
|
}
|
|
40
55
|
export function authFilePath(dataDir) {
|
|
41
|
-
return path.join(dataDir,
|
|
56
|
+
return path.join(dataDir, "auth.json");
|
|
42
57
|
}
|
|
43
58
|
export function chunkRootPathFromStateFile(statePath) {
|
|
44
|
-
return path.join(path.dirname(statePath),
|
|
59
|
+
return path.join(path.dirname(statePath), "quota-sidebar-sessions");
|
|
45
60
|
}
|
|
46
61
|
export function chunkFilePath(rootPath, dateKey) {
|
|
47
62
|
// Defense-in-depth: ensure we never build paths from untrusted inputs.
|
|
48
63
|
if (!isDateKey(dateKey)) {
|
|
49
64
|
throw new Error(`invalid dateKey: ${dateKey}`);
|
|
50
65
|
}
|
|
51
|
-
const [year, month, day] = dateKey.split(
|
|
66
|
+
const [year, month, day] = dateKey.split("-");
|
|
52
67
|
return path.join(rootPath, year, month, `${day}.json`);
|
|
53
68
|
}
|
package/dist/title_apply.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function createTitleApplicator(deps: {
|
|
|
16
16
|
allowDefault?: boolean;
|
|
17
17
|
}) => Promise<QuotaSnapshot[]>;
|
|
18
18
|
summarizeSessionUsageForDisplay: (sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
19
|
+
listCurrentProviderIDs?: () => Promise<Set<string> | undefined>;
|
|
19
20
|
scheduleParentRefreshIfSafe: (sessionID: string, parentID?: string) => void;
|
|
20
21
|
isSessionActive?: (sessionID: string) => boolean;
|
|
21
22
|
restoreConcurrency: number;
|
|
@@ -30,22 +31,5 @@ export declare function createTitleApplicator(deps: {
|
|
|
30
31
|
restoreSessionTitle: (sessionID: string, options?: {
|
|
31
32
|
abortIfEnabled?: boolean;
|
|
32
33
|
}) => Promise<boolean>;
|
|
33
|
-
restoreAllVisibleTitles: (options?: {
|
|
34
|
-
abortIfEnabled?: boolean;
|
|
35
|
-
}) => Promise<{
|
|
36
|
-
attempted: number;
|
|
37
|
-
restored: number;
|
|
38
|
-
listFailed: boolean;
|
|
39
|
-
}>;
|
|
40
|
-
refreshAllTouchedTitles: () => Promise<{
|
|
41
|
-
attempted: number;
|
|
42
|
-
refreshed: number;
|
|
43
|
-
listFailed: boolean;
|
|
44
|
-
}>;
|
|
45
|
-
refreshAllVisibleTitles: () => Promise<{
|
|
46
|
-
attempted: number;
|
|
47
|
-
refreshed: number;
|
|
48
|
-
listFailed: boolean;
|
|
49
|
-
}>;
|
|
50
34
|
forgetSession: (sessionID: string) => void;
|
|
51
35
|
};
|
package/dist/title_apply.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalizeTitle, canonicalizeTitleForCompare, looksDecorated, normalizeBaseTitle, } from './title.js';
|
|
2
2
|
import { toCachedSessionUsage } from './usage.js';
|
|
3
|
-
import { swallow, debug
|
|
3
|
+
import { swallow, debug } from './helpers.js';
|
|
4
4
|
import { resolveTitleView, selectDesktopCompactProviderIDs, } from './format.js';
|
|
5
5
|
import { collapseQuotaSnapshots } from './quota_render.js';
|
|
6
6
|
export function createTitleApplicator(deps) {
|
|
@@ -25,6 +25,11 @@ export function createTitleApplicator(deps) {
|
|
|
25
25
|
}
|
|
26
26
|
return true;
|
|
27
27
|
};
|
|
28
|
+
const filterAllowedProviderIDs = (providerIDs, allowedProviderIDs) => {
|
|
29
|
+
if (!allowedProviderIDs)
|
|
30
|
+
return [];
|
|
31
|
+
return providerIDs.filter((providerID) => allowedProviderIDs.has(providerID));
|
|
32
|
+
};
|
|
28
33
|
const applyTitle = async (sessionID) => {
|
|
29
34
|
if (!deps.config.sidebar.enabled)
|
|
30
35
|
return false;
|
|
@@ -65,7 +70,8 @@ export function createTitleApplicator(deps) {
|
|
|
65
70
|
// Ignore decorated echoes as base-title source.
|
|
66
71
|
// If we previously applied a decorated title, treat this as an
|
|
67
72
|
// equivalent echo (OpenCode may normalize whitespace) and keep
|
|
68
|
-
// lastAppliedTitle in sync so
|
|
73
|
+
// Keep lastAppliedTitle in sync with server echoes so restore logic can
|
|
74
|
+
// still tell whether the current title is ours.
|
|
69
75
|
if (sessionState.lastAppliedTitle &&
|
|
70
76
|
looksDecorated(sessionState.lastAppliedTitle)) {
|
|
71
77
|
if (sessionState.lastAppliedTitle !== currentTitle) {
|
|
@@ -96,10 +102,16 @@ export function createTitleApplicator(deps) {
|
|
|
96
102
|
const usage = await deps.summarizeSessionUsageForDisplay(sessionID, deps.config.sidebar.includeChildren);
|
|
97
103
|
const view = deps.getTitleView?.(sessionID) ??
|
|
98
104
|
resolveTitleView({ config: deps.config });
|
|
99
|
-
const
|
|
100
|
-
const
|
|
105
|
+
const rawPanelQuotaProviders = Array.from(new Set(Object.keys(usage.providers)));
|
|
106
|
+
const rawQuotaProviders = Array.from(new Set(view === 'compact'
|
|
101
107
|
? selectDesktopCompactProviderIDs(usage, deps.config)
|
|
102
|
-
:
|
|
108
|
+
: rawPanelQuotaProviders));
|
|
109
|
+
const allowedProviderIDs = deps.config.sidebar.showQuota &&
|
|
110
|
+
(rawPanelQuotaProviders.length > 0 || rawQuotaProviders.length > 0)
|
|
111
|
+
? await deps.listCurrentProviderIDs?.()
|
|
112
|
+
: undefined;
|
|
113
|
+
const panelQuotaProviders = filterAllowedProviderIDs(rawPanelQuotaProviders, allowedProviderIDs);
|
|
114
|
+
const quotaProviders = filterAllowedProviderIDs(rawQuotaProviders, allowedProviderIDs);
|
|
103
115
|
const quotas = deps.config.sidebar.showQuota && quotaProviders.length > 0
|
|
104
116
|
? await deps.getQuotaSnapshots(quotaProviders)
|
|
105
117
|
: [];
|
|
@@ -278,53 +290,10 @@ export function createTitleApplicator(deps) {
|
|
|
278
290
|
deps.scheduleSave();
|
|
279
291
|
return true;
|
|
280
292
|
};
|
|
281
|
-
const restoreAllVisibleTitles = async (options) => {
|
|
282
|
-
const touched = Object.entries(deps.state.sessions)
|
|
283
|
-
.filter(([, sessionState]) => Boolean(sessionState.lastAppliedTitle))
|
|
284
|
-
.map(([sessionID]) => sessionID);
|
|
285
|
-
const results = await mapConcurrent(touched, deps.restoreConcurrency, async (sessionID) => restoreSessionTitle(sessionID, options));
|
|
286
|
-
return {
|
|
287
|
-
attempted: touched.length,
|
|
288
|
-
restored: results.filter(Boolean).length,
|
|
289
|
-
listFailed: false,
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
|
-
const refreshAllTouchedTitles = async () => {
|
|
293
|
-
const touched = Object.entries(deps.state.sessions)
|
|
294
|
-
.filter(([, sessionState]) => Boolean(sessionState.lastAppliedTitle))
|
|
295
|
-
.map(([sessionID]) => sessionID);
|
|
296
|
-
const results = await mapConcurrent(touched, deps.restoreConcurrency, async (sessionID) => applyTitle(sessionID));
|
|
297
|
-
return {
|
|
298
|
-
attempted: touched.length,
|
|
299
|
-
refreshed: results.filter(Boolean).length,
|
|
300
|
-
listFailed: false,
|
|
301
|
-
};
|
|
302
|
-
};
|
|
303
|
-
const refreshAllVisibleTitles = async () => {
|
|
304
|
-
const list = await deps.client.session
|
|
305
|
-
.list({
|
|
306
|
-
query: { directory: deps.directory },
|
|
307
|
-
throwOnError: true,
|
|
308
|
-
})
|
|
309
|
-
.catch(swallow('refreshAllVisibleTitles:list'));
|
|
310
|
-
if (!list?.data || !Array.isArray(list.data)) {
|
|
311
|
-
return { attempted: 0, refreshed: 0, listFailed: true };
|
|
312
|
-
}
|
|
313
|
-
const sessions = list.data.filter((session) => Boolean(session && typeof session.id === 'string'));
|
|
314
|
-
const results = await mapConcurrent(sessions, deps.restoreConcurrency, async (session) => applyTitle(session.id));
|
|
315
|
-
return {
|
|
316
|
-
attempted: sessions.length,
|
|
317
|
-
refreshed: results.filter(Boolean).length,
|
|
318
|
-
listFailed: false,
|
|
319
|
-
};
|
|
320
|
-
};
|
|
321
293
|
return {
|
|
322
294
|
applyTitle,
|
|
323
295
|
handleSessionUpdatedTitle,
|
|
324
296
|
restoreSessionTitle,
|
|
325
|
-
restoreAllVisibleTitles,
|
|
326
|
-
refreshAllTouchedTitles,
|
|
327
|
-
refreshAllVisibleTitles,
|
|
328
297
|
forgetSession,
|
|
329
298
|
};
|
|
330
299
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export declare function createQuotaSidebarTools(deps: {
|
|
|
22
22
|
showToast: (period: 'session' | 'day' | 'week' | 'month' | 'toggle', message: string) => Promise<void>;
|
|
23
23
|
summarizeForTool: (period: 'session' | 'day' | 'week' | 'month', sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
24
24
|
summarizeHistoryForTool: (period: HistoryPeriod, since: string) => Promise<HistoryUsageResult>;
|
|
25
|
-
listCurrentProviderIDs
|
|
25
|
+
listCurrentProviderIDs: () => Promise<Set<string>>;
|
|
26
26
|
getQuotaSnapshots: (providerIDs: string[], options?: {
|
|
27
27
|
allowDefault?: boolean;
|
|
28
28
|
}) => Promise<QuotaSnapshot[]>;
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
import { sinceFromLast } from './period.js';
|
|
3
3
|
import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, } from './provider_catalog.js';
|
|
4
|
+
function emptyProviderUsage(usage) {
|
|
5
|
+
return {
|
|
6
|
+
...usage,
|
|
7
|
+
providers: {},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function strictFilterUsageProviders(usage, allowedProviderIDs) {
|
|
11
|
+
if (allowedProviderIDs.size === 0)
|
|
12
|
+
return emptyProviderUsage(usage);
|
|
13
|
+
return filterUsageProvidersForDisplay(usage, allowedProviderIDs);
|
|
14
|
+
}
|
|
15
|
+
function strictFilterHistoryProviders(history, allowedProviderIDs) {
|
|
16
|
+
if (allowedProviderIDs.size === 0) {
|
|
17
|
+
return {
|
|
18
|
+
...history,
|
|
19
|
+
rows: history.rows.map((row) => ({
|
|
20
|
+
...row,
|
|
21
|
+
usage: emptyProviderUsage(row.usage),
|
|
22
|
+
})),
|
|
23
|
+
total: emptyProviderUsage(history.total),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return filterHistoryProvidersForDisplay(history, allowedProviderIDs);
|
|
27
|
+
}
|
|
4
28
|
function tool(input) {
|
|
5
29
|
return input;
|
|
6
30
|
}
|
|
@@ -51,17 +75,11 @@ export function createQuotaSidebarTools(deps) {
|
|
|
51
75
|
(period !== 'session' && last !== undefined
|
|
52
76
|
? sinceFromLast(period, last)
|
|
53
77
|
: undefined);
|
|
54
|
-
const allowedProviderIDs = await deps
|
|
55
|
-
.listCurrentProviderIDs?.()
|
|
56
|
-
.catch(() => new Set());
|
|
78
|
+
const allowedProviderIDs = await deps.listCurrentProviderIDs();
|
|
57
79
|
if (period !== 'session' && resolvedSince) {
|
|
58
80
|
const historyRaw = await deps.summarizeHistoryForTool(period, resolvedSince);
|
|
59
|
-
const history = allowedProviderIDs
|
|
60
|
-
|
|
61
|
-
: historyRaw;
|
|
62
|
-
const quotas = await deps.getQuotaSnapshots([], {
|
|
63
|
-
allowDefault: true,
|
|
64
|
-
});
|
|
81
|
+
const history = strictFilterHistoryProviders(historyRaw, allowedProviderIDs);
|
|
82
|
+
const quotas = await deps.getQuotaSnapshots([...allowedProviderIDs]);
|
|
65
83
|
const markdown = deps.renderHistoryMarkdownReport(history, quotas, {
|
|
66
84
|
showCost: deps.config.sidebar.showCost,
|
|
67
85
|
});
|
|
@@ -77,12 +95,8 @@ export function createQuotaSidebarTools(deps) {
|
|
|
77
95
|
? (args.includeChildren ?? deps.config.sidebar.includeChildren)
|
|
78
96
|
: false;
|
|
79
97
|
const usageRaw = await deps.summarizeForTool(period, context.sessionID, includeChildren);
|
|
80
|
-
const usage = allowedProviderIDs
|
|
81
|
-
|
|
82
|
-
: usageRaw;
|
|
83
|
-
// For quota_summary, always show all subscription quota balances,
|
|
84
|
-
// regardless of which providers were used in the session.
|
|
85
|
-
const quotas = await deps.getQuotaSnapshots([], { allowDefault: true });
|
|
98
|
+
const usage = strictFilterUsageProviders(usageRaw, allowedProviderIDs);
|
|
99
|
+
const quotas = await deps.getQuotaSnapshots([...allowedProviderIDs]);
|
|
86
100
|
const markdown = deps.renderMarkdownReport(period, usage, quotas, {
|
|
87
101
|
showCost: deps.config.sidebar.showCost,
|
|
88
102
|
});
|
package/dist/tui.d.ts
CHANGED