oc2token 0.1.0
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/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/src/accounting/index.d.ts +1 -0
- package/dist/src/accounting/index.js +2 -0
- package/dist/src/accounting/index.js.map +1 -0
- package/dist/src/accounting/reducer.d.ts +21 -0
- package/dist/src/accounting/reducer.js +105 -0
- package/dist/src/accounting/reducer.js.map +1 -0
- package/dist/src/antigravity/discovery.d.ts +18 -0
- package/dist/src/antigravity/discovery.js +95 -0
- package/dist/src/antigravity/discovery.js.map +1 -0
- package/dist/src/antigravity/doctor.d.ts +6 -0
- package/dist/src/antigravity/doctor.js +78 -0
- package/dist/src/antigravity/doctor.js.map +1 -0
- package/dist/src/antigravity/index.d.ts +3 -0
- package/dist/src/antigravity/index.js +4 -0
- package/dist/src/antigravity/index.js.map +1 -0
- package/dist/src/antigravity/proto.d.ts +73 -0
- package/dist/src/antigravity/proto.js +259 -0
- package/dist/src/antigravity/proto.js.map +1 -0
- package/dist/src/antigravity/scanner.d.ts +8 -0
- package/dist/src/antigravity/scanner.js +552 -0
- package/dist/src/antigravity/scanner.js.map +1 -0
- package/dist/src/application.d.ts +73 -0
- package/dist/src/application.js +657 -0
- package/dist/src/application.js.map +1 -0
- package/dist/src/cache/filesystem.d.ts +8 -0
- package/dist/src/cache/filesystem.js +70 -0
- package/dist/src/cache/filesystem.js.map +1 -0
- package/dist/src/cache/index.d.ts +5 -0
- package/dist/src/cache/index.js +6 -0
- package/dist/src/cache/index.js.map +1 -0
- package/dist/src/cache/lock.d.ts +40 -0
- package/dist/src/cache/lock.js +227 -0
- package/dist/src/cache/lock.js.map +1 -0
- package/dist/src/cache/schema.d.ts +45 -0
- package/dist/src/cache/schema.js +308 -0
- package/dist/src/cache/schema.js.map +1 -0
- package/dist/src/cache/store.d.ts +46 -0
- package/dist/src/cache/store.js +396 -0
- package/dist/src/cache/store.js.map +1 -0
- package/dist/src/cache/types.d.ts +130 -0
- package/dist/src/cache/types.js +6 -0
- package/dist/src/cache/types.js.map +1 -0
- package/dist/src/cli.d.ts +8 -0
- package/dist/src/cli.js +874 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/codex/discovery.d.ts +6 -0
- package/dist/src/codex/discovery.js +88 -0
- package/dist/src/codex/discovery.js.map +1 -0
- package/dist/src/codex/doctor.d.ts +6 -0
- package/dist/src/codex/doctor.js +52 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/index.d.ts +3 -0
- package/dist/src/codex/index.js +3 -0
- package/dist/src/codex/index.js.map +1 -0
- package/dist/src/codex/scanner.d.ts +6 -0
- package/dist/src/codex/scanner.js +487 -0
- package/dist/src/codex/scanner.js.map +1 -0
- package/dist/src/codex/types.d.ts +31 -0
- package/dist/src/codex/types.js +5 -0
- package/dist/src/codex/types.js.map +1 -0
- package/dist/src/collector/collector.d.ts +15 -0
- package/dist/src/collector/collector.js +597 -0
- package/dist/src/collector/collector.js.map +1 -0
- package/dist/src/collector/index.d.ts +3 -0
- package/dist/src/collector/index.js +3 -0
- package/dist/src/collector/index.js.map +1 -0
- package/dist/src/collector/types.d.ts +55 -0
- package/dist/src/collector/types.js +18 -0
- package/dist/src/collector/types.js.map +1 -0
- package/dist/src/dashboard/render/ansi.d.ts +53 -0
- package/dist/src/dashboard/render/ansi.js +106 -0
- package/dist/src/dashboard/render/ansi.js.map +1 -0
- package/dist/src/dashboard/render/dashboard.d.ts +13 -0
- package/dist/src/dashboard/render/dashboard.js +750 -0
- package/dist/src/dashboard/render/dashboard.js.map +1 -0
- package/dist/src/dashboard/render/format.d.ts +23 -0
- package/dist/src/dashboard/render/format.js +169 -0
- package/dist/src/dashboard/render/format.js.map +1 -0
- package/dist/src/dashboard/render/redraw.d.ts +13 -0
- package/dist/src/dashboard/render/redraw.js +24 -0
- package/dist/src/dashboard/render/redraw.js.map +1 -0
- package/dist/src/dashboard/render/types.d.ts +83 -0
- package/dist/src/dashboard/render/types.js +528 -0
- package/dist/src/dashboard/render/types.js.map +1 -0
- package/dist/src/dashboard/scheduler/index.d.ts +1 -0
- package/dist/src/dashboard/scheduler/index.js +2 -0
- package/dist/src/dashboard/scheduler/index.js.map +1 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.d.ts +46 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js +137 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js.map +1 -0
- package/dist/src/dashboard/settings/index.d.ts +25 -0
- package/dist/src/dashboard/settings/index.js +162 -0
- package/dist/src/dashboard/settings/index.js.map +1 -0
- package/dist/src/dashboard/state/index.d.ts +2 -0
- package/dist/src/dashboard/state/index.js +3 -0
- package/dist/src/dashboard/state/index.js.map +1 -0
- package/dist/src/dashboard/state/refresh-coordinator.d.ts +72 -0
- package/dist/src/dashboard/state/refresh-coordinator.js +346 -0
- package/dist/src/dashboard/state/refresh-coordinator.js.map +1 -0
- package/dist/src/dashboard/state/types.d.ts +68 -0
- package/dist/src/dashboard/state/types.js +2 -0
- package/dist/src/dashboard/state/types.js.map +1 -0
- package/dist/src/domain/contracts.d.ts +219 -0
- package/dist/src/domain/contracts.js +2 -0
- package/dist/src/domain/contracts.js.map +1 -0
- package/dist/src/domain/errors.d.ts +25 -0
- package/dist/src/domain/errors.js +65 -0
- package/dist/src/domain/errors.js.map +1 -0
- package/dist/src/domain/index.d.ts +5 -0
- package/dist/src/domain/index.js +6 -0
- package/dist/src/domain/index.js.map +1 -0
- package/dist/src/domain/records.d.ts +59 -0
- package/dist/src/domain/records.js +141 -0
- package/dist/src/domain/records.js.map +1 -0
- package/dist/src/domain/tokens.d.ts +51 -0
- package/dist/src/domain/tokens.js +93 -0
- package/dist/src/domain/tokens.js.map +1 -0
- package/dist/src/domain/windows.d.ts +59 -0
- package/dist/src/domain/windows.js +272 -0
- package/dist/src/domain/windows.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/opencode/adapter.d.ts +47 -0
- package/dist/src/opencode/adapter.js +118 -0
- package/dist/src/opencode/adapter.js.map +1 -0
- package/dist/src/opencode/client.d.ts +64 -0
- package/dist/src/opencode/client.js +118 -0
- package/dist/src/opencode/client.js.map +1 -0
- package/dist/src/opencode/doctor.d.ts +28 -0
- package/dist/src/opencode/doctor.js +82 -0
- package/dist/src/opencode/doctor.js.map +1 -0
- package/dist/src/opencode/index.d.ts +5 -0
- package/dist/src/opencode/index.js +6 -0
- package/dist/src/opencode/index.js.map +1 -0
- package/dist/src/opencode/stats.d.ts +8 -0
- package/dist/src/opencode/stats.js +174 -0
- package/dist/src/opencode/stats.js.map +1 -0
- package/dist/src/opencode/transport.d.ts +54 -0
- package/dist/src/opencode/transport.js +621 -0
- package/dist/src/opencode/transport.js.map +1 -0
- package/dist/src/output/index.d.ts +20 -0
- package/dist/src/output/index.js +46 -0
- package/dist/src/output/index.js.map +1 -0
- package/dist/src/output/json.d.ts +72 -0
- package/dist/src/output/json.js +133 -0
- package/dist/src/output/json.js.map +1 -0
- package/dist/src/output/table.d.ts +4 -0
- package/dist/src/output/table.js +81 -0
- package/dist/src/output/table.js.map +1 -0
- package/dist/src/pricing/index.d.ts +1 -0
- package/dist/src/pricing/index.js +2 -0
- package/dist/src/pricing/index.js.map +1 -0
- package/dist/src/pricing/pricing.d.ts +19 -0
- package/dist/src/pricing/pricing.js +441 -0
- package/dist/src/pricing/pricing.js.map +1 -0
- package/package.json +27 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { ANSI, createStableRedraw, getCardHitRegions, renderDashboard, renderJSON, renderTable, } from "./output/index.js";
|
|
5
|
+
import { createApplicationSource, emptyCollectionResult, newCollectionRequest, readCachedSnapshot, } from "./application.js";
|
|
6
|
+
import { RefreshCoordinator } from "./dashboard/state/index.js";
|
|
7
|
+
import { createUsageWindows, isProviderKind, } from "./domain/index.js";
|
|
8
|
+
import { runOpenCodeDoctor } from "./opencode/index.js";
|
|
9
|
+
import { runCodexDoctor } from "./codex/doctor.js";
|
|
10
|
+
import { runAntigravityDoctor } from "./antigravity/doctor.js";
|
|
11
|
+
import { SETTINGS_MAX_REFRESH_SECONDS, SETTINGS_MIN_REFRESH_SECONDS, adjustRefreshIntervalByPreset, clampRefreshIntervalSeconds, loadDashboardSettings, saveDashboardSettings, } from "./dashboard/settings/index.js";
|
|
12
|
+
const VERSION = "0.1.0";
|
|
13
|
+
const DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
14
|
+
function usage() {
|
|
15
|
+
return `oc2token ${VERSION}
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
oc2token [hour|day|week]
|
|
19
|
+
oc2token [options]
|
|
20
|
+
oc2token doctor [--source <provider>] [--json]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--once Collect once and exit
|
|
24
|
+
--json Emit stable JSON and exit
|
|
25
|
+
--format <mode> auto, dashboard, table, or json
|
|
26
|
+
--refresh <seconds> Automatic refresh cadence; 0 is manual-only
|
|
27
|
+
--timezone <IANA> Time zone for local day and ISO week boundaries
|
|
28
|
+
--project <id> Restrict collection to an OpenCode project
|
|
29
|
+
--cache-dir <path> Override the normalized metadata cache directory
|
|
30
|
+
--source <provider> Filter providers: opencode, codex, antigravity, or all (repeatable)
|
|
31
|
+
--no-color Disable ANSI colors
|
|
32
|
+
-h, --help Show this help
|
|
33
|
+
-v, --version Show the version
|
|
34
|
+
|
|
35
|
+
Dashboard keys: r refresh, 1/2/3 / click top card to select period, p projects, s settings, ? help, q quit.
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function failUsage(message) {
|
|
39
|
+
throw new Error(`${message}\n\n${usage()}`);
|
|
40
|
+
}
|
|
41
|
+
function parseSeconds(value) {
|
|
42
|
+
const seconds = Number(value);
|
|
43
|
+
if (!Number.isFinite(seconds) || seconds < 0 || !Number.isSafeInteger(seconds)) {
|
|
44
|
+
failUsage("--refresh must be a non-negative integer number of seconds");
|
|
45
|
+
}
|
|
46
|
+
return seconds;
|
|
47
|
+
}
|
|
48
|
+
function parseArgs(args) {
|
|
49
|
+
let period = "day";
|
|
50
|
+
let once = false;
|
|
51
|
+
let json = false;
|
|
52
|
+
let format = "auto";
|
|
53
|
+
let refreshIntervalSeconds = 300;
|
|
54
|
+
let refreshExplicit = false;
|
|
55
|
+
let filterExplicit = false;
|
|
56
|
+
let timezone = DEFAULT_TIMEZONE;
|
|
57
|
+
let project;
|
|
58
|
+
let cacheDirectory;
|
|
59
|
+
let color = true;
|
|
60
|
+
let command;
|
|
61
|
+
let periodSeen = false;
|
|
62
|
+
const filterProviders = new Set();
|
|
63
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
64
|
+
const arg = args[index];
|
|
65
|
+
if (arg === "-h" || arg === "--help")
|
|
66
|
+
return "help";
|
|
67
|
+
if (arg === "-v" || arg === "--version")
|
|
68
|
+
return "version";
|
|
69
|
+
if (arg === "--once") {
|
|
70
|
+
once = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg === "--json") {
|
|
74
|
+
json = true;
|
|
75
|
+
once = true;
|
|
76
|
+
format = "json";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg === "--no-color") {
|
|
80
|
+
color = false;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === "--source") {
|
|
84
|
+
filterExplicit = true;
|
|
85
|
+
const value = args[++index];
|
|
86
|
+
if (value === undefined || value.startsWith("--"))
|
|
87
|
+
failUsage("--source requires a value");
|
|
88
|
+
if (value === "all") {
|
|
89
|
+
filterProviders.clear();
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!isProviderKind(value)) {
|
|
93
|
+
failUsage(`Unknown source: ${value} (expected opencode, codex, antigravity, or all)`);
|
|
94
|
+
}
|
|
95
|
+
filterProviders.add(value);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg === "--refresh" || arg === "--timezone" || arg === "--project" || arg === "--cache-dir" || arg === "--format") {
|
|
99
|
+
const value = args[++index];
|
|
100
|
+
if (value === undefined || value.startsWith("--"))
|
|
101
|
+
failUsage(`${arg} requires a value`);
|
|
102
|
+
if (arg === "--refresh") {
|
|
103
|
+
refreshIntervalSeconds = parseSeconds(value);
|
|
104
|
+
refreshExplicit = true;
|
|
105
|
+
}
|
|
106
|
+
else if (arg === "--timezone")
|
|
107
|
+
timezone = value;
|
|
108
|
+
else if (arg === "--project")
|
|
109
|
+
project = value;
|
|
110
|
+
else if (arg === "--cache-dir")
|
|
111
|
+
cacheDirectory = value;
|
|
112
|
+
else {
|
|
113
|
+
if (value !== "auto" && value !== "dashboard" && value !== "table" && value !== "json") {
|
|
114
|
+
failUsage(`Unknown output format: ${value}`);
|
|
115
|
+
}
|
|
116
|
+
format = value;
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (arg === "doctor") {
|
|
121
|
+
if (command !== undefined)
|
|
122
|
+
failUsage("Only one command can be selected");
|
|
123
|
+
command = "doctor";
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (arg === "hour" || arg === "day" || arg === "week") {
|
|
127
|
+
if (periodSeen)
|
|
128
|
+
failUsage("Only one period can be selected");
|
|
129
|
+
period = arg;
|
|
130
|
+
periodSeen = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (arg.startsWith("-"))
|
|
134
|
+
failUsage(`Unknown option: ${arg}`);
|
|
135
|
+
failUsage(`Unexpected argument: ${arg}`);
|
|
136
|
+
}
|
|
137
|
+
if (command !== undefined) {
|
|
138
|
+
if (periodSeen || format === "dashboard")
|
|
139
|
+
failUsage("doctor cannot be combined with a period or dashboard output");
|
|
140
|
+
once = true;
|
|
141
|
+
}
|
|
142
|
+
if (format === "json")
|
|
143
|
+
once = true;
|
|
144
|
+
if (refreshExplicit && refreshIntervalSeconds !== 0 && (refreshIntervalSeconds < SETTINGS_MIN_REFRESH_SECONDS || refreshIntervalSeconds > SETTINGS_MAX_REFRESH_SECONDS)) {
|
|
145
|
+
failUsage(`--refresh must be 0 (manual) or between ${SETTINGS_MIN_REFRESH_SECONDS} and ${SETTINGS_MAX_REFRESH_SECONDS} seconds (1 minute to 4 hours)`);
|
|
146
|
+
}
|
|
147
|
+
return { period, once, json, format, refreshIntervalSeconds, refreshExplicit, filterExplicit, timezone, project, cacheDirectory, color, command, filterProviders };
|
|
148
|
+
}
|
|
149
|
+
function emptyTotals() {
|
|
150
|
+
return { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, recorded_total: 0 };
|
|
151
|
+
}
|
|
152
|
+
function dashboardInput(state, fallback, wallNow, monotonicNow) {
|
|
153
|
+
const result = state.snapshot ?? state.lastGoodSnapshot ?? fallback;
|
|
154
|
+
const coverageErrors = [...result.coverage.errors];
|
|
155
|
+
if (state.status === "error" && state.lastError !== undefined) {
|
|
156
|
+
coverageErrors.push({
|
|
157
|
+
code: "unknown",
|
|
158
|
+
message: state.lastError instanceof Error ? state.lastError.message : String(state.lastError),
|
|
159
|
+
retryable: true,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const nextRefreshAt = state.nextRefreshAt === undefined
|
|
163
|
+
? null
|
|
164
|
+
: new Date(wallNow.getTime() + state.nextRefreshAt - monotonicNow);
|
|
165
|
+
return {
|
|
166
|
+
...result,
|
|
167
|
+
stale: state.stale,
|
|
168
|
+
lastUpdated: state.lastUpdated ?? result.capturedAt,
|
|
169
|
+
nextRefreshAt,
|
|
170
|
+
coverage: { ...result.coverage, errors: coverageErrors },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function writeError(io, error, json) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
if (json) {
|
|
176
|
+
io.stdout.write(`${JSON.stringify({ schemaVersion: 1, error: { code: "oc2token", message } })}\n`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
io.stderr.write(`oc2token: ${message}\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function requestedJSON(args) {
|
|
183
|
+
return args.some((arg, index) => arg === "--json" || (arg === "--format" && args[index + 1] === "json"));
|
|
184
|
+
}
|
|
185
|
+
async function runDoctor(options, io) {
|
|
186
|
+
const shouldCheck = (provider) => options.filterProviders.size === 0 || options.filterProviders.has(provider);
|
|
187
|
+
const checks = [];
|
|
188
|
+
let endpoint;
|
|
189
|
+
let version;
|
|
190
|
+
let fingerprint;
|
|
191
|
+
const tasks = [];
|
|
192
|
+
if (shouldCheck("opencode")) {
|
|
193
|
+
tasks.push((async () => {
|
|
194
|
+
const report = await runOpenCodeDoctor({
|
|
195
|
+
ensure: false,
|
|
196
|
+
...(options.project === undefined ? {} : { project: options.project }),
|
|
197
|
+
});
|
|
198
|
+
const ok = report.ok;
|
|
199
|
+
const message = report.checks.map((check) => check.message).join("; ") || (ok ? "OpenCode 2 service OK" : "OpenCode 2 service unavailable");
|
|
200
|
+
checks.push({ name: "opencode", ok, message });
|
|
201
|
+
if (report.endpoint)
|
|
202
|
+
endpoint = report.endpoint;
|
|
203
|
+
if (report.version)
|
|
204
|
+
version = report.version;
|
|
205
|
+
if (report.fingerprint)
|
|
206
|
+
fingerprint = report.fingerprint;
|
|
207
|
+
// If opencode failed, still keep check; overall ok derived later.
|
|
208
|
+
})());
|
|
209
|
+
}
|
|
210
|
+
if (shouldCheck("codex")) {
|
|
211
|
+
tasks.push((async () => {
|
|
212
|
+
const check = await runCodexDoctor();
|
|
213
|
+
checks.push(check);
|
|
214
|
+
})());
|
|
215
|
+
}
|
|
216
|
+
if (shouldCheck("antigravity")) {
|
|
217
|
+
tasks.push((async () => {
|
|
218
|
+
const check = await runAntigravityDoctor();
|
|
219
|
+
checks.push(check);
|
|
220
|
+
})());
|
|
221
|
+
}
|
|
222
|
+
await Promise.all(tasks);
|
|
223
|
+
// Deterministic order: opencode, codex, antigravity
|
|
224
|
+
const order = { opencode: 0, codex: 1, antigravity: 2 };
|
|
225
|
+
checks.sort((a, b) => order[a.name] - order[b.name]);
|
|
226
|
+
const ok = checks.length > 0 ? checks.every((check) => check.ok) : false;
|
|
227
|
+
const report = {
|
|
228
|
+
ok,
|
|
229
|
+
checks,
|
|
230
|
+
...(endpoint === undefined ? {} : { endpoint }),
|
|
231
|
+
...(version === undefined ? {} : { version }),
|
|
232
|
+
...(fingerprint === undefined ? {} : { fingerprint }),
|
|
233
|
+
};
|
|
234
|
+
if (options.json || options.format === "json") {
|
|
235
|
+
io.stdout.write(`${JSON.stringify(report)}\n`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
io.stdout.write(`oc2token doctor: ${report.ok ? "OK" : "FAILED"}\n`);
|
|
239
|
+
for (const check of report.checks)
|
|
240
|
+
io.stdout.write(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.message}\n`);
|
|
241
|
+
if (report.endpoint)
|
|
242
|
+
io.stdout.write(`Endpoint: ${report.endpoint}\n`);
|
|
243
|
+
if (report.version)
|
|
244
|
+
io.stdout.write(`Version: ${report.version}\n`);
|
|
245
|
+
}
|
|
246
|
+
return report.ok ? 0 : 1;
|
|
247
|
+
}
|
|
248
|
+
async function runOnce(options, io) {
|
|
249
|
+
const now = new Date();
|
|
250
|
+
const applicationOptions = {
|
|
251
|
+
...(options.cacheDirectory === undefined ? {} : { cacheDirectory: options.cacheDirectory }),
|
|
252
|
+
...(options.filterProviders.size === 0 ? {} : { filterProviders: options.filterProviders }),
|
|
253
|
+
};
|
|
254
|
+
const { source } = createApplicationSource(applicationOptions);
|
|
255
|
+
const request = newCollectionRequest(now, options.timezone, {
|
|
256
|
+
...(options.project === undefined ? {} : { project: options.project }),
|
|
257
|
+
});
|
|
258
|
+
const result = await source.collect(request);
|
|
259
|
+
const format = options.json ? "json" : options.format === "auto" ? "table" : options.format;
|
|
260
|
+
if (format === "json")
|
|
261
|
+
io.stdout.write(`${renderJSON(result, false)}\n`);
|
|
262
|
+
else if (format === "table")
|
|
263
|
+
io.stdout.write(`${renderTable(result)}\n`);
|
|
264
|
+
else
|
|
265
|
+
io.stdout.write(`${renderDashboard(result, { isTTY: false, color: options.color, selectedWindow: options.period })}\n`);
|
|
266
|
+
return result.coverage.complete ? 0 : 3;
|
|
267
|
+
}
|
|
268
|
+
async function runDashboard(options, io) {
|
|
269
|
+
if (!io.stdout.isTTY || !io.stdin.isTTY) {
|
|
270
|
+
return runOnce({ ...options, once: true }, io);
|
|
271
|
+
}
|
|
272
|
+
// A corrupt/future cache is intentionally ignored; it is an optimization,
|
|
273
|
+
// while the live service remains the only accounting authority.
|
|
274
|
+
const cached = await readCachedSnapshot(options.cacheDirectory);
|
|
275
|
+
// Load persisted settings and merge with CLI options. CLI explicit flags win.
|
|
276
|
+
const persisted = await loadDashboardSettings(options.cacheDirectory);
|
|
277
|
+
const ALL_KINDS = ["opencode", "codex", "antigravity"];
|
|
278
|
+
let initialSettingsEnabled;
|
|
279
|
+
if (options.filterExplicit) {
|
|
280
|
+
initialSettingsEnabled = options.filterProviders.size === 0 ? [...ALL_KINDS] : [...options.filterProviders];
|
|
281
|
+
}
|
|
282
|
+
else if (persisted !== undefined) {
|
|
283
|
+
initialSettingsEnabled = [...persisted.enabledProviders];
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
initialSettingsEnabled = options.filterProviders.size === 0 ? [...ALL_KINDS] : [...options.filterProviders];
|
|
287
|
+
}
|
|
288
|
+
// Normalize to valid kinds and ensure non-empty (at least one)
|
|
289
|
+
initialSettingsEnabled = initialSettingsEnabled.filter((k) => ALL_KINDS.includes(k));
|
|
290
|
+
if (initialSettingsEnabled.length === 0)
|
|
291
|
+
initialSettingsEnabled = [...ALL_KINDS];
|
|
292
|
+
let initialSchedulerInterval;
|
|
293
|
+
let initialSettingsInterval;
|
|
294
|
+
if (options.refreshExplicit) {
|
|
295
|
+
initialSchedulerInterval = options.refreshIntervalSeconds;
|
|
296
|
+
initialSettingsInterval = initialSchedulerInterval === 0
|
|
297
|
+
? SETTINGS_MIN_REFRESH_SECONDS
|
|
298
|
+
: clampRefreshIntervalSeconds(initialSchedulerInterval);
|
|
299
|
+
// Clamp scheduler interval for settings range if explicit but out of bounds? Keep explicit 0 as manual, otherwise clamp
|
|
300
|
+
if (initialSchedulerInterval !== 0) {
|
|
301
|
+
initialSchedulerInterval = clampRefreshIntervalSeconds(initialSchedulerInterval);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else if (persisted !== undefined) {
|
|
305
|
+
initialSchedulerInterval = clampRefreshIntervalSeconds(persisted.refreshIntervalSeconds);
|
|
306
|
+
initialSettingsInterval = initialSchedulerInterval;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
initialSchedulerInterval = options.refreshIntervalSeconds;
|
|
310
|
+
if (initialSchedulerInterval !== 0) {
|
|
311
|
+
initialSchedulerInterval = clampRefreshIntervalSeconds(initialSchedulerInterval);
|
|
312
|
+
}
|
|
313
|
+
initialSettingsInterval = initialSchedulerInterval === 0
|
|
314
|
+
? SETTINGS_MIN_REFRESH_SECONDS
|
|
315
|
+
: clampRefreshIntervalSeconds(initialSchedulerInterval);
|
|
316
|
+
// Also respect the min/max for initial settings: if scheduler is 0 manual, settings defaults to 300
|
|
317
|
+
if (options.refreshIntervalSeconds === 0) {
|
|
318
|
+
initialSettingsInterval = 300;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Ensure settings interval always within 1m..4h
|
|
322
|
+
initialSettingsInterval = clampRefreshIntervalSeconds(initialSettingsInterval);
|
|
323
|
+
const initialFilterForSource = initialSettingsEnabled.length === ALL_KINDS.length
|
|
324
|
+
? undefined
|
|
325
|
+
: new Set(initialSettingsEnabled);
|
|
326
|
+
const adapterSource = createApplicationSource({
|
|
327
|
+
cacheDirectory: options.cacheDirectory,
|
|
328
|
+
...(initialFilterForSource === undefined ? {} : { filterProviders: initialFilterForSource }),
|
|
329
|
+
});
|
|
330
|
+
const clock = {
|
|
331
|
+
wallNow: () => new Date(),
|
|
332
|
+
monotonicNow: () => Number(process.hrtime.bigint()) / 1_000_000,
|
|
333
|
+
};
|
|
334
|
+
const fallback = emptyCollectionResult(clock.wallNow(), options.timezone);
|
|
335
|
+
const coordinator = new RefreshCoordinator({
|
|
336
|
+
source: adapterSource.source,
|
|
337
|
+
clock,
|
|
338
|
+
timezone: options.timezone,
|
|
339
|
+
...(options.project === undefined ? {} : { project: options.project }),
|
|
340
|
+
refreshIntervalSeconds: initialSchedulerInterval,
|
|
341
|
+
initialPeriod: options.period,
|
|
342
|
+
...(cached.snapshot === undefined ? {} : { initialSnapshot: cached.snapshot }),
|
|
343
|
+
});
|
|
344
|
+
const redraw = createStableRedraw();
|
|
345
|
+
let help = false;
|
|
346
|
+
let projectsVisible = false;
|
|
347
|
+
let running = true;
|
|
348
|
+
// Settings UI state
|
|
349
|
+
const settingsState = {
|
|
350
|
+
visible: false,
|
|
351
|
+
enabledProviders: new Set(initialSettingsEnabled),
|
|
352
|
+
refreshIntervalSeconds: initialSettingsInterval,
|
|
353
|
+
focusedIndex: 0,
|
|
354
|
+
};
|
|
355
|
+
let appliedEnabled = new Set(settingsState.enabledProviders);
|
|
356
|
+
let appliedInterval = initialSchedulerInterval;
|
|
357
|
+
const persistCurrentSettings = () => {
|
|
358
|
+
void saveDashboardSettings({
|
|
359
|
+
enabledProviders: [...settingsState.enabledProviders],
|
|
360
|
+
refreshIntervalSeconds: clampRefreshIntervalSeconds(settingsState.refreshIntervalSeconds),
|
|
361
|
+
}, options.cacheDirectory);
|
|
362
|
+
};
|
|
363
|
+
const applySettings = () => {
|
|
364
|
+
// Providers
|
|
365
|
+
const enabledArray = [...settingsState.enabledProviders].sort();
|
|
366
|
+
const appliedArray = [...appliedEnabled].sort();
|
|
367
|
+
const providersChanged = enabledArray.length !== appliedArray.length || enabledArray.some((v, i) => v !== appliedArray[i]);
|
|
368
|
+
if (providersChanged) {
|
|
369
|
+
const newFilter = settingsState.enabledProviders.size === ALL_KINDS.length
|
|
370
|
+
? undefined
|
|
371
|
+
: new Set(settingsState.enabledProviders);
|
|
372
|
+
// Unified source is mutated via CachedUsageSource delegation
|
|
373
|
+
try {
|
|
374
|
+
adapterSource.source.setFilterProviders(newFilter);
|
|
375
|
+
}
|
|
376
|
+
catch { }
|
|
377
|
+
try {
|
|
378
|
+
adapterSource.unified.setFilterProviders(newFilter);
|
|
379
|
+
}
|
|
380
|
+
catch { }
|
|
381
|
+
appliedEnabled = new Set(settingsState.enabledProviders);
|
|
382
|
+
void coordinator.manualRefresh();
|
|
383
|
+
persistCurrentSettings();
|
|
384
|
+
}
|
|
385
|
+
// Interval
|
|
386
|
+
const clamped = clampRefreshIntervalSeconds(settingsState.refreshIntervalSeconds);
|
|
387
|
+
if (clamped !== settingsState.refreshIntervalSeconds) {
|
|
388
|
+
settingsState.refreshIntervalSeconds = clamped;
|
|
389
|
+
}
|
|
390
|
+
if (clamped !== appliedInterval) {
|
|
391
|
+
try {
|
|
392
|
+
coordinator.setRefreshIntervalSeconds(clamped);
|
|
393
|
+
}
|
|
394
|
+
catch { }
|
|
395
|
+
appliedInterval = clamped;
|
|
396
|
+
persistCurrentSettings();
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const draw = () => {
|
|
400
|
+
const state = coordinator.getState();
|
|
401
|
+
const input = dashboardInput(state, fallback, clock.wallNow(), clock.monotonicNow());
|
|
402
|
+
const frame = renderDashboard(input, {
|
|
403
|
+
isTTY: true,
|
|
404
|
+
ansi: true,
|
|
405
|
+
color: options.color,
|
|
406
|
+
width: Math.max(20, io.stdout.columns || 100),
|
|
407
|
+
now: clock.wallNow(),
|
|
408
|
+
selectedWindow: state.period,
|
|
409
|
+
help,
|
|
410
|
+
...(settingsState.visible
|
|
411
|
+
? {
|
|
412
|
+
settings: {
|
|
413
|
+
visible: true,
|
|
414
|
+
enabledProviders: [...settingsState.enabledProviders].sort(),
|
|
415
|
+
refreshIntervalSeconds: settingsState.refreshIntervalSeconds,
|
|
416
|
+
focusedIndex: settingsState.focusedIndex,
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
: {}),
|
|
420
|
+
...(projectsVisible ? { projects: { visible: true } } : {}),
|
|
421
|
+
});
|
|
422
|
+
io.stdout.write(redraw.render(frame, { isTTY: true, ansi: true, color: options.color }));
|
|
423
|
+
};
|
|
424
|
+
const stop = async () => {
|
|
425
|
+
if (!running)
|
|
426
|
+
return;
|
|
427
|
+
running = false;
|
|
428
|
+
await coordinator.quit();
|
|
429
|
+
if (io.stdin.isTTY)
|
|
430
|
+
io.stdin.setRawMode?.(false);
|
|
431
|
+
io.stdin.pause();
|
|
432
|
+
// Disable mouse tracking before restoring cursor
|
|
433
|
+
if (io.stdout.isTTY)
|
|
434
|
+
io.stdout.write(ANSI.mouseDisable);
|
|
435
|
+
io.stdout.write(`\n${redraw.cleanup({ isTTY: true, ansi: true })}\n`);
|
|
436
|
+
};
|
|
437
|
+
coordinator.subscribe(() => draw());
|
|
438
|
+
const onSignal = () => { void stop(); };
|
|
439
|
+
process.once("SIGINT", onSignal);
|
|
440
|
+
process.once("SIGTERM", onSignal);
|
|
441
|
+
io.stdout.on("resize", draw);
|
|
442
|
+
io.stdin.setRawMode?.(true);
|
|
443
|
+
io.stdin.resume();
|
|
444
|
+
io.stdout.write(ANSI.cursorHide);
|
|
445
|
+
if (io.stdout.isTTY)
|
|
446
|
+
io.stdout.write(ANSI.mouseEnable);
|
|
447
|
+
draw();
|
|
448
|
+
void coordinator.start().catch(() => undefined);
|
|
449
|
+
let inputBuffer = "";
|
|
450
|
+
const onInput = (chunk) => {
|
|
451
|
+
inputBuffer += chunk.toString();
|
|
452
|
+
const value = inputBuffer;
|
|
453
|
+
inputBuffer = "";
|
|
454
|
+
const periods = ["hour", "day", "week"];
|
|
455
|
+
const nextPeriod = () => {
|
|
456
|
+
const current = coordinator.getState().period;
|
|
457
|
+
const idx = periods.indexOf(current);
|
|
458
|
+
return periods[(idx + 1) % periods.length];
|
|
459
|
+
};
|
|
460
|
+
const prevPeriod = () => {
|
|
461
|
+
const current = coordinator.getState().period;
|
|
462
|
+
const idx = periods.indexOf(current);
|
|
463
|
+
return periods[(idx - 1 + periods.length) % periods.length];
|
|
464
|
+
};
|
|
465
|
+
let i = 0;
|
|
466
|
+
while (i < value.length) {
|
|
467
|
+
// SGR mouse click: \x1b[<Cb;Cx;CyM (press) / m (release)
|
|
468
|
+
if (value.startsWith("\u001b[<", i)) {
|
|
469
|
+
const termM = value.indexOf("M", i + 3);
|
|
470
|
+
const termLower = value.indexOf("m", i + 3);
|
|
471
|
+
let end = -1;
|
|
472
|
+
let isPress = true;
|
|
473
|
+
if (termM !== -1 && (termLower === -1 || termM < termLower)) {
|
|
474
|
+
end = termM;
|
|
475
|
+
isPress = true;
|
|
476
|
+
}
|
|
477
|
+
else if (termLower !== -1) {
|
|
478
|
+
end = termLower;
|
|
479
|
+
isPress = false;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
// Incomplete SGR sequence — wait for next chunk
|
|
483
|
+
inputBuffer = value.slice(i);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
const body = value.slice(i + 3, end);
|
|
487
|
+
const parts = body.split(";");
|
|
488
|
+
if (parts.length === 3 && isPress) {
|
|
489
|
+
const cb = Number(parts[0]);
|
|
490
|
+
const cx = Number(parts[1]);
|
|
491
|
+
const cy = Number(parts[2]);
|
|
492
|
+
if (Number.isFinite(cb) && Number.isFinite(cx) && Number.isFinite(cy) && cb === 0) {
|
|
493
|
+
if (!settingsState.visible && !projectsVisible) {
|
|
494
|
+
const w = Math.max(20, io.stdout.columns || 100);
|
|
495
|
+
const regions = getCardHitRegions(w);
|
|
496
|
+
for (const r of regions) {
|
|
497
|
+
if (cx >= r.x1 && cx <= r.x2 && cy >= r.y1 && cy <= r.y2) {
|
|
498
|
+
coordinator.setPeriod(r.kind);
|
|
499
|
+
draw();
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
i = end + 1;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (value.startsWith("\u001b[", i)) {
|
|
510
|
+
if (value.startsWith("\u001b[Z", i)) {
|
|
511
|
+
if (settingsState.visible) {
|
|
512
|
+
settingsState.focusedIndex = (settingsState.focusedIndex - 1 + 4) % 4;
|
|
513
|
+
draw();
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
coordinator.setPeriod(prevPeriod());
|
|
517
|
+
}
|
|
518
|
+
i += 3;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (i + 2 < value.length) {
|
|
522
|
+
const code = value[i + 2];
|
|
523
|
+
if (settingsState.visible) {
|
|
524
|
+
if (code === "A") {
|
|
525
|
+
settingsState.focusedIndex = (settingsState.focusedIndex - 1 + 4) % 4;
|
|
526
|
+
draw();
|
|
527
|
+
i += 3;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (code === "B") {
|
|
531
|
+
settingsState.focusedIndex = (settingsState.focusedIndex + 1) % 4;
|
|
532
|
+
draw();
|
|
533
|
+
i += 3;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (code === "C") {
|
|
537
|
+
if (settingsState.focusedIndex === 3) {
|
|
538
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, 1);
|
|
539
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
540
|
+
settingsState.refreshIntervalSeconds = next;
|
|
541
|
+
applySettings();
|
|
542
|
+
draw();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
i += 3;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (code === "D") {
|
|
549
|
+
if (settingsState.focusedIndex === 3) {
|
|
550
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, -1);
|
|
551
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
552
|
+
settingsState.refreshIntervalSeconds = next;
|
|
553
|
+
applySettings();
|
|
554
|
+
draw();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
i += 3;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
if (code === "A" || code === "D") {
|
|
563
|
+
coordinator.setPeriod(prevPeriod());
|
|
564
|
+
i += 3;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (code === "B" || code === "C") {
|
|
568
|
+
coordinator.setPeriod(nextPeriod());
|
|
569
|
+
i += 3;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
i += 1;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const key = value[i];
|
|
578
|
+
if (key === "\u0003") {
|
|
579
|
+
void stop();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (projectsVisible) {
|
|
583
|
+
if (key === "p" || key === "P" || key === "\u001b") {
|
|
584
|
+
projectsVisible = false;
|
|
585
|
+
draw();
|
|
586
|
+
i += 1;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (key === "q") {
|
|
590
|
+
projectsVisible = false;
|
|
591
|
+
draw();
|
|
592
|
+
i += 1;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (key === "s" || key === "S") {
|
|
596
|
+
projectsVisible = false;
|
|
597
|
+
settingsState.visible = true;
|
|
598
|
+
help = false;
|
|
599
|
+
draw();
|
|
600
|
+
i += 1;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (key === "r" || key === "R") {
|
|
604
|
+
void coordinator.manualRefresh();
|
|
605
|
+
i += 1;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (key === "?") {
|
|
609
|
+
projectsVisible = false;
|
|
610
|
+
help = !help;
|
|
611
|
+
draw();
|
|
612
|
+
i += 1;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (key === "1" || key === "2" || key === "3") {
|
|
616
|
+
coordinator.setPeriod(key === "1" ? "hour" : key === "2" ? "day" : "week");
|
|
617
|
+
draw();
|
|
618
|
+
i += 1;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (key === "\t" || key === "\u0009") {
|
|
622
|
+
coordinator.setPeriod(nextPeriod());
|
|
623
|
+
draw();
|
|
624
|
+
i += 1;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (key === "\r" || key === "\n") {
|
|
628
|
+
i += 1;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
i += 1;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (settingsState.visible) {
|
|
635
|
+
if (key === "q" || key === "\u001b") {
|
|
636
|
+
settingsState.visible = false;
|
|
637
|
+
draw();
|
|
638
|
+
i += 1;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (key === "p" || key === "P") {
|
|
642
|
+
settingsState.visible = false;
|
|
643
|
+
projectsVisible = true;
|
|
644
|
+
help = false;
|
|
645
|
+
draw();
|
|
646
|
+
i += 1;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (key === "s" || key === "S") {
|
|
650
|
+
settingsState.visible = false;
|
|
651
|
+
draw();
|
|
652
|
+
i += 1;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (key === "r" || key === "R") {
|
|
656
|
+
void coordinator.manualRefresh();
|
|
657
|
+
i += 1;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (key === "?") {
|
|
661
|
+
// ignore ? inside settings; could close settings and toggle help
|
|
662
|
+
i += 1;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (key === " ") {
|
|
666
|
+
if (settingsState.focusedIndex >= 0 && settingsState.focusedIndex <= 2) {
|
|
667
|
+
const provs = ["opencode", "codex", "antigravity"];
|
|
668
|
+
const prov = provs[settingsState.focusedIndex];
|
|
669
|
+
if (settingsState.enabledProviders.has(prov)) {
|
|
670
|
+
if (settingsState.enabledProviders.size > 1) {
|
|
671
|
+
settingsState.enabledProviders.delete(prov);
|
|
672
|
+
applySettings();
|
|
673
|
+
draw();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
settingsState.enabledProviders.add(prov);
|
|
678
|
+
applySettings();
|
|
679
|
+
draw();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
i += 1;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (key === "\t" || key === "\u0009") {
|
|
686
|
+
settingsState.focusedIndex = (settingsState.focusedIndex + 1) % 4;
|
|
687
|
+
draw();
|
|
688
|
+
i += 1;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (key === "1" || key === "2" || key === "3") {
|
|
692
|
+
i += 1;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (key === "h" || key === "H") {
|
|
696
|
+
if (settingsState.focusedIndex === 3) {
|
|
697
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, -1);
|
|
698
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
699
|
+
settingsState.refreshIntervalSeconds = next;
|
|
700
|
+
applySettings();
|
|
701
|
+
draw();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
i += 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (key === "l" || key === "L") {
|
|
708
|
+
if (settingsState.focusedIndex === 3) {
|
|
709
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, 1);
|
|
710
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
711
|
+
settingsState.refreshIntervalSeconds = next;
|
|
712
|
+
applySettings();
|
|
713
|
+
draw();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
i += 1;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (key === "+" || key === "=") {
|
|
720
|
+
if (settingsState.focusedIndex === 3) {
|
|
721
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, 1);
|
|
722
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
723
|
+
settingsState.refreshIntervalSeconds = next;
|
|
724
|
+
applySettings();
|
|
725
|
+
draw();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
i += 1;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (key === "-" || key === "_") {
|
|
732
|
+
if (settingsState.focusedIndex === 3) {
|
|
733
|
+
const next = adjustRefreshIntervalByPreset(settingsState.refreshIntervalSeconds, -1);
|
|
734
|
+
if (next !== settingsState.refreshIntervalSeconds) {
|
|
735
|
+
settingsState.refreshIntervalSeconds = next;
|
|
736
|
+
applySettings();
|
|
737
|
+
draw();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
i += 1;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (key === "\r" || key === "\n") {
|
|
744
|
+
if (settingsState.focusedIndex >= 0 && settingsState.focusedIndex <= 2) {
|
|
745
|
+
const provs = ["opencode", "codex", "antigravity"];
|
|
746
|
+
const prov = provs[settingsState.focusedIndex];
|
|
747
|
+
if (settingsState.enabledProviders.has(prov)) {
|
|
748
|
+
if (settingsState.enabledProviders.size > 1) {
|
|
749
|
+
settingsState.enabledProviders.delete(prov);
|
|
750
|
+
applySettings();
|
|
751
|
+
draw();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
settingsState.enabledProviders.add(prov);
|
|
756
|
+
applySettings();
|
|
757
|
+
draw();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
i += 1;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (key === "\u001b") {
|
|
764
|
+
settingsState.visible = false;
|
|
765
|
+
draw();
|
|
766
|
+
i += 1;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
i += 1;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
if (key === "q") {
|
|
774
|
+
void stop();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (key === "r" || key === "R") {
|
|
778
|
+
void coordinator.manualRefresh();
|
|
779
|
+
i += 1;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (key === "?") {
|
|
783
|
+
help = !help;
|
|
784
|
+
draw();
|
|
785
|
+
i += 1;
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (key === "\t" || key === "\u0009") {
|
|
789
|
+
coordinator.setPeriod(nextPeriod());
|
|
790
|
+
i += 1;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (key === "1" || key === "2" || key === "3") {
|
|
794
|
+
coordinator.setPeriod(key === "1" ? "hour" : key === "2" ? "day" : "week");
|
|
795
|
+
}
|
|
796
|
+
if (key === "p" || key === "P") {
|
|
797
|
+
projectsVisible = true;
|
|
798
|
+
help = false;
|
|
799
|
+
settingsState.visible = false;
|
|
800
|
+
draw();
|
|
801
|
+
i += 1;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (key === "s" || key === "S") {
|
|
805
|
+
settingsState.visible = true;
|
|
806
|
+
help = false;
|
|
807
|
+
projectsVisible = false;
|
|
808
|
+
draw();
|
|
809
|
+
i += 1;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (key === "\u001b") {
|
|
813
|
+
if (help) {
|
|
814
|
+
help = false;
|
|
815
|
+
draw();
|
|
816
|
+
}
|
|
817
|
+
if (projectsVisible) {
|
|
818
|
+
projectsVisible = false;
|
|
819
|
+
draw();
|
|
820
|
+
}
|
|
821
|
+
i += 1;
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
i += 1;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
io.stdin.on("data", onInput);
|
|
829
|
+
const heartbeat = setInterval(draw, 1_000);
|
|
830
|
+
await new Promise((resolve) => {
|
|
831
|
+
const check = () => running ? setTimeout(check, 25) : resolve();
|
|
832
|
+
check();
|
|
833
|
+
});
|
|
834
|
+
clearInterval(heartbeat);
|
|
835
|
+
io.stdin.off("data", onInput);
|
|
836
|
+
io.stdout.off("resize", draw);
|
|
837
|
+
process.off("SIGINT", onSignal);
|
|
838
|
+
process.off("SIGTERM", onSignal);
|
|
839
|
+
return 0;
|
|
840
|
+
}
|
|
841
|
+
export async function main(args = process.argv.slice(2), io = {
|
|
842
|
+
stdin: process.stdin,
|
|
843
|
+
stdout: process.stdout,
|
|
844
|
+
stderr: process.stderr,
|
|
845
|
+
}) {
|
|
846
|
+
let options;
|
|
847
|
+
try {
|
|
848
|
+
options = parseArgs(args);
|
|
849
|
+
if (options === "help") {
|
|
850
|
+
io.stdout.write(usage());
|
|
851
|
+
return 0;
|
|
852
|
+
}
|
|
853
|
+
if (options === "version") {
|
|
854
|
+
io.stdout.write(`${VERSION}\n`);
|
|
855
|
+
return 0;
|
|
856
|
+
}
|
|
857
|
+
// Validate the IANA timezone before connecting to OpenCode.
|
|
858
|
+
createUsageWindows(new Date(), options.timezone);
|
|
859
|
+
if (options.command === "doctor")
|
|
860
|
+
return runDoctor(options, io);
|
|
861
|
+
if (options.once)
|
|
862
|
+
return await runOnce(options, io);
|
|
863
|
+
return await runDashboard(options, io);
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
writeError(io, error, requestedJSON(args));
|
|
867
|
+
return 1;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
const invokedName = process.argv[1] === undefined ? "" : basename(process.argv[1]);
|
|
871
|
+
if ((invokedName === "oc2token" || invokedName === "cli.js") && !invokedName.endsWith(".test.js")) {
|
|
872
|
+
void main().then((code) => { process.exitCode = code; });
|
|
873
|
+
}
|
|
874
|
+
//# sourceMappingURL=cli.js.map
|