@leo000001/opencode-quota-sidebar 4.0.5 → 4.0.11
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 +38 -0
- package/dist/cli.js +153 -42
- 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.d.ts +3 -3
- package/dist/index.js +35 -34
- 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 +5 -22
- package/dist/title_apply.js +19 -61
- 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/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
2
3
|
import { type HistoryPeriod } from './period.js';
|
|
3
4
|
type CliCommand = {
|
|
4
5
|
period: HistoryPeriod;
|
|
@@ -10,9 +11,46 @@ type CliServerCommand = {
|
|
|
10
11
|
args: string[];
|
|
11
12
|
shell?: boolean;
|
|
12
13
|
};
|
|
14
|
+
type SpawnedCliServerProcess = ReturnType<typeof spawn>;
|
|
15
|
+
export declare function releaseCliServerProcess(proc: {
|
|
16
|
+
stdin?: {
|
|
17
|
+
destroy: () => void;
|
|
18
|
+
} | null;
|
|
19
|
+
stdout?: {
|
|
20
|
+
destroy: () => void;
|
|
21
|
+
} | null;
|
|
22
|
+
stderr?: {
|
|
23
|
+
destroy: () => void;
|
|
24
|
+
} | null;
|
|
25
|
+
unref: () => void;
|
|
26
|
+
}): void;
|
|
27
|
+
export declare function terminateCliServerProcess(proc: {
|
|
28
|
+
pid?: number;
|
|
29
|
+
killed?: boolean;
|
|
30
|
+
kill: (signal?: NodeJS.Signals) => boolean;
|
|
31
|
+
stdin?: {
|
|
32
|
+
destroy: () => void;
|
|
33
|
+
} | null;
|
|
34
|
+
stdout?: {
|
|
35
|
+
destroy: () => void;
|
|
36
|
+
} | null;
|
|
37
|
+
stderr?: {
|
|
38
|
+
destroy: () => void;
|
|
39
|
+
} | null;
|
|
40
|
+
unref: () => void;
|
|
41
|
+
}, options?: {
|
|
42
|
+
platform?: NodeJS.Platform;
|
|
43
|
+
killProcess?: typeof process.kill;
|
|
44
|
+
}): void;
|
|
45
|
+
export declare function extractCliServerUrl(output: string): string | undefined;
|
|
13
46
|
export declare function parseCliArgs(argv: string[]): CliCommand;
|
|
14
47
|
export declare function cliBaseUrl(): string;
|
|
15
48
|
export declare function cliServerCommandCandidates(platform?: NodeJS.Platform): CliServerCommand[];
|
|
49
|
+
export declare function closeCliServerProcess(proc: SpawnedCliServerProcess, platform?: NodeJS.Platform, killProcess?: typeof process.kill, spawnProcess?: typeof spawn): void;
|
|
50
|
+
export declare function tryStartCliOpencodeServer(candidate: CliServerCommand, spawnProcess?: typeof spawn, closeProcess?: typeof closeCliServerProcess): Promise<{
|
|
51
|
+
url: string;
|
|
52
|
+
close: () => void;
|
|
53
|
+
}>;
|
|
16
54
|
export declare function runCli(argv: string[]): Promise<string>;
|
|
17
55
|
export declare function cliExitCodeForError(message: string): 0 | 1;
|
|
18
56
|
export declare function cliShouldRunMain(argv1?: string, modulePath?: string, resolvePath?: (filePath: string) => string): boolean;
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,42 @@ import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, listC
|
|
|
15
15
|
import { createUsageService } from './usage_service.js';
|
|
16
16
|
const DEFAULT_OPENCODE_BASE_URL = 'http://localhost:4096';
|
|
17
17
|
const CLI_SERVER_TIMEOUT_MS = 10_000;
|
|
18
|
+
const CLI_FORCE_EXIT_DELAY_MS = 100;
|
|
19
|
+
export function releaseCliServerProcess(proc) {
|
|
20
|
+
// The CLI only needs the child pipes until the server prints its listen URL.
|
|
21
|
+
// After that, unref/destroy them so the parent process can exit cleanly.
|
|
22
|
+
proc.stdin?.destroy();
|
|
23
|
+
proc.stdout?.destroy();
|
|
24
|
+
proc.stderr?.destroy();
|
|
25
|
+
proc.unref();
|
|
26
|
+
}
|
|
27
|
+
export function terminateCliServerProcess(proc, options) {
|
|
28
|
+
releaseCliServerProcess(proc);
|
|
29
|
+
if (proc.killed)
|
|
30
|
+
return;
|
|
31
|
+
const platform = options?.platform ?? process.platform;
|
|
32
|
+
const killProcess = options?.killProcess ?? process.kill;
|
|
33
|
+
if (platform !== 'win32' && typeof proc.pid === 'number' && proc.pid > 0) {
|
|
34
|
+
try {
|
|
35
|
+
killProcess(-proc.pid, 'SIGTERM');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Fall back to the direct child pid when group termination is unavailable.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
proc.kill('SIGTERM');
|
|
43
|
+
}
|
|
44
|
+
export function extractCliServerUrl(output) {
|
|
45
|
+
for (const line of output.split('\n')) {
|
|
46
|
+
if (!line.startsWith('opencode server listening'))
|
|
47
|
+
continue;
|
|
48
|
+
const match = line.match(/on\s+(https?:\/\/[^\s]+)/);
|
|
49
|
+
if (match)
|
|
50
|
+
return match[1];
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
18
54
|
const HELP_TEXT = `opencode-quota
|
|
19
55
|
|
|
20
56
|
Usage:
|
|
@@ -132,12 +168,49 @@ export function cliServerCommandCandidates(platform = process.platform) {
|
|
|
132
168
|
}
|
|
133
169
|
return [{ command: 'opencode', args: directArgs }];
|
|
134
170
|
}
|
|
135
|
-
|
|
171
|
+
function releaseCliServerPipes(proc, inspect, onError, onExit) {
|
|
172
|
+
if (inspect) {
|
|
173
|
+
proc.stdout?.removeListener('data', inspect);
|
|
174
|
+
proc.stderr?.removeListener('data', inspect);
|
|
175
|
+
}
|
|
176
|
+
if (onError)
|
|
177
|
+
proc.removeListener('error', onError);
|
|
178
|
+
if (onExit)
|
|
179
|
+
proc.removeListener('exit', onExit);
|
|
180
|
+
proc.stdout?.unpipe();
|
|
181
|
+
proc.stderr?.unpipe();
|
|
182
|
+
proc.stdout?.destroy();
|
|
183
|
+
proc.stderr?.destroy();
|
|
184
|
+
}
|
|
185
|
+
export function closeCliServerProcess(proc, platform = process.platform, killProcess = process.kill, spawnProcess = spawn) {
|
|
186
|
+
const pid = proc.pid;
|
|
187
|
+
if (typeof pid !== 'number' || pid <= 0)
|
|
188
|
+
return;
|
|
189
|
+
if (platform === 'win32') {
|
|
190
|
+
const killer = spawnProcess('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
191
|
+
stdio: 'ignore',
|
|
192
|
+
windowsHide: true,
|
|
193
|
+
});
|
|
194
|
+
killer.unref();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
killProcess(-pid, 'SIGTERM');
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const code = error.code;
|
|
202
|
+
if (code !== 'ESRCH')
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export async function tryStartCliOpencodeServer(candidate, spawnProcess = spawn, closeProcess = closeCliServerProcess) {
|
|
136
207
|
let proc;
|
|
137
208
|
try {
|
|
138
|
-
proc =
|
|
209
|
+
proc = spawnProcess(candidate.command, candidate.args, {
|
|
139
210
|
env: process.env,
|
|
211
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
140
212
|
shell: candidate.shell ?? false,
|
|
213
|
+
detached: true,
|
|
141
214
|
windowsHide: true,
|
|
142
215
|
});
|
|
143
216
|
}
|
|
@@ -150,52 +223,67 @@ async function tryStartCliOpencodeServer(candidate) {
|
|
|
150
223
|
};
|
|
151
224
|
}
|
|
152
225
|
const url = await new Promise((resolve, reject) => {
|
|
226
|
+
let inspect;
|
|
227
|
+
let onError;
|
|
228
|
+
let onExit;
|
|
229
|
+
let output = '';
|
|
230
|
+
let settled = false;
|
|
153
231
|
const id = setTimeout(() => {
|
|
232
|
+
if (settled)
|
|
233
|
+
return;
|
|
234
|
+
settled = true;
|
|
235
|
+
releaseCliServerPipes(proc, inspect, onError, onExit);
|
|
236
|
+
closeProcess(proc);
|
|
154
237
|
reject(new Error(`Timeout waiting for OpenCode server to start after ${CLI_SERVER_TIMEOUT_MS}ms`));
|
|
155
238
|
}, CLI_SERVER_TIMEOUT_MS);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
239
|
+
id.unref?.();
|
|
240
|
+
const fail = (failure) => {
|
|
241
|
+
if (settled)
|
|
242
|
+
return;
|
|
243
|
+
settled = true;
|
|
244
|
+
clearTimeout(id);
|
|
245
|
+
releaseCliServerPipes(proc, inspect, onError, onExit);
|
|
246
|
+
reject(failure);
|
|
247
|
+
};
|
|
248
|
+
inspect = (chunk) => {
|
|
159
249
|
output += chunk.toString();
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
if (!line.startsWith('opencode server listening'))
|
|
163
|
-
continue;
|
|
164
|
-
const match = line.match(/on\s+(https?:\/\/[^\s]+)/);
|
|
165
|
-
if (!match)
|
|
166
|
-
continue;
|
|
250
|
+
const url = extractCliServerUrl(output);
|
|
251
|
+
if (url) {
|
|
167
252
|
clearTimeout(id);
|
|
168
253
|
settled = true;
|
|
169
|
-
|
|
254
|
+
releaseCliServerPipes(proc, inspect, onError, onExit);
|
|
255
|
+
// The CLI only needs the startup line; after that the detached server
|
|
256
|
+
// must not keep the parent process alive.
|
|
257
|
+
proc.stdin?.destroy();
|
|
258
|
+
proc.unref();
|
|
259
|
+
resolve(url);
|
|
170
260
|
return;
|
|
171
261
|
}
|
|
172
262
|
};
|
|
173
263
|
proc.stdout?.on('data', inspect);
|
|
174
264
|
proc.stderr?.on('data', inspect);
|
|
175
|
-
|
|
176
|
-
clearTimeout(id);
|
|
265
|
+
onError = (error) => {
|
|
177
266
|
const code = error.code;
|
|
178
|
-
|
|
267
|
+
fail({
|
|
179
268
|
error,
|
|
180
269
|
output,
|
|
181
270
|
recoverable: code === 'ENOENT' || code === 'EINVAL',
|
|
182
271
|
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (settled)
|
|
186
|
-
return;
|
|
187
|
-
clearTimeout(id);
|
|
272
|
+
};
|
|
273
|
+
onExit = (code) => {
|
|
188
274
|
let message = `OpenCode server exited with code ${code}`;
|
|
189
275
|
if (output.trim())
|
|
190
276
|
message += `\n${output}`;
|
|
191
277
|
const recoverable = /not recognized as an internal or external command/i.test(output) ||
|
|
192
278
|
/command not found/i.test(output);
|
|
193
|
-
|
|
194
|
-
}
|
|
279
|
+
fail({ error: new Error(message), output, recoverable });
|
|
280
|
+
};
|
|
281
|
+
proc.on('error', onError);
|
|
282
|
+
proc.on('exit', onExit);
|
|
195
283
|
});
|
|
196
284
|
return {
|
|
197
285
|
url,
|
|
198
|
-
close: () => proc
|
|
286
|
+
close: () => closeProcess(proc),
|
|
199
287
|
};
|
|
200
288
|
}
|
|
201
289
|
async function startCliOpencodeServer() {
|
|
@@ -249,23 +337,40 @@ async function resolvePathInfo(directory) {
|
|
|
249
337
|
throw new Error(`Failed to connect to OpenCode API at ${cliBaseUrl()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
338
|
}
|
|
251
339
|
const server = await startCliOpencodeServer();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
340
|
+
try {
|
|
341
|
+
const client = createOpencodeClient({
|
|
342
|
+
directory,
|
|
343
|
+
baseUrl: server.url,
|
|
344
|
+
});
|
|
345
|
+
const response = await client.path.get({
|
|
346
|
+
query: { directory },
|
|
347
|
+
throwOnError: true,
|
|
348
|
+
});
|
|
349
|
+
const data = response.data;
|
|
350
|
+
return {
|
|
351
|
+
client,
|
|
352
|
+
worktree: data.worktree || directory,
|
|
353
|
+
directory: data.directory || directory,
|
|
354
|
+
close: () => server.close(),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (innerError) {
|
|
358
|
+
server.close();
|
|
359
|
+
throw innerError;
|
|
360
|
+
}
|
|
267
361
|
}
|
|
268
362
|
}
|
|
363
|
+
function writeCliLine(stream, value) {
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
stream.write(`${value}\n`, (error) => {
|
|
366
|
+
if (error) {
|
|
367
|
+
reject(error);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
resolve();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
269
374
|
export async function runCli(argv) {
|
|
270
375
|
const command = parseCliArgs(argv);
|
|
271
376
|
const cwd = process.cwd();
|
|
@@ -292,6 +397,7 @@ export async function runCli(argv) {
|
|
|
292
397
|
statePath,
|
|
293
398
|
client: client,
|
|
294
399
|
directory,
|
|
400
|
+
worktree,
|
|
295
401
|
persistence: {
|
|
296
402
|
markDirty: () => { },
|
|
297
403
|
scheduleSave: () => { },
|
|
@@ -350,16 +456,21 @@ export function cliShouldRunMain(argv1 = process.argv[1], modulePath = fileURLTo
|
|
|
350
456
|
return resolvePath(modulePath) === resolvePath(argv1);
|
|
351
457
|
}
|
|
352
458
|
async function main() {
|
|
459
|
+
let exitCode = 0;
|
|
353
460
|
try {
|
|
354
461
|
const output = await runCli(process.argv.slice(2));
|
|
355
|
-
process.stdout
|
|
462
|
+
await writeCliLine(process.stdout, output);
|
|
356
463
|
}
|
|
357
464
|
catch (error) {
|
|
358
465
|
const message = error instanceof Error ? error.message : String(error);
|
|
359
|
-
|
|
466
|
+
exitCode = cliExitCodeForError(message);
|
|
360
467
|
const stream = exitCode === 0 ? process.stdout : process.stderr;
|
|
361
|
-
stream
|
|
468
|
+
await writeCliLine(stream, message);
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
362
471
|
process.exitCode = exitCode;
|
|
472
|
+
const forceExit = setTimeout(() => process.exit(exitCode), CLI_FORCE_EXIT_DELAY_MS);
|
|
473
|
+
forceExit.unref?.();
|
|
363
474
|
}
|
|
364
475
|
}
|
|
365
476
|
if (cliShouldRunMain()) {
|
package/dist/cli_render.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { QuotaSnapshot } from
|
|
2
|
-
import { type UsageSummary } from
|
|
3
|
-
import type { HistoryUsageResult } from
|
|
1
|
+
import type { QuotaSnapshot } from "./types.js";
|
|
2
|
+
import { type UsageSummary } from "./usage.js";
|
|
3
|
+
import type { HistoryUsageResult } from "./usage_service.js";
|
|
4
4
|
export declare function renderCliDashboard(input: {
|
|
5
5
|
label: string;
|
|
6
6
|
usage: UsageSummary;
|
|
@@ -14,4 +14,4 @@ export declare function renderCliHistoryDashboard(input: {
|
|
|
14
14
|
width?: number;
|
|
15
15
|
showCost?: boolean;
|
|
16
16
|
}): string;
|
|
17
|
-
export declare function cliCurrentLabel(period:
|
|
17
|
+
export declare function cliCurrentLabel(period: "day" | "week" | "month"): "Today" | "This Week" | "This Month";
|