@token-dashboard/codex-usage-uploader 0.1.3 → 0.1.5
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/package.json +10 -10
- package/src/cli.js +10 -1
- package/src/collector.js +1 -1
- package/src/install.js +6 -0
- package/src/runtime-config.js +17 -5
- package/src/state-db.js +11 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@token-dashboard/codex-usage-uploader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Codex 用量上报 CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"init": "node bin/codex-usage-uploader.js init --backend-url http://localhost:8086",
|
|
16
|
-
"start": "node bin/codex-usage-uploader.js start",
|
|
17
|
-
"stop": "node bin/codex-usage-uploader.js stop",
|
|
18
|
-
"restart": "node bin/codex-usage-uploader.js restart",
|
|
19
|
-
"status": "node bin/codex-usage-uploader.js status",
|
|
20
|
-
"usage": "node bin/codex-usage-uploader.js usage",
|
|
21
|
-
"logs": "node bin/codex-usage-uploader.js logs",
|
|
22
|
-
"clear": "node bin/codex-usage-uploader.js clear",
|
|
23
|
-
"uninstall": "node bin/codex-usage-uploader.js uninstall"
|
|
15
|
+
"init": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json init --backend-url http://localhost:8086",
|
|
16
|
+
"start": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json start",
|
|
17
|
+
"stop": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json stop",
|
|
18
|
+
"restart": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json restart",
|
|
19
|
+
"status": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json status",
|
|
20
|
+
"usage": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json usage",
|
|
21
|
+
"logs": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json logs",
|
|
22
|
+
"clear": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json clear",
|
|
23
|
+
"uninstall": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json uninstall"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=22.13.0"
|
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { CodexUsageUploader } from './collector.js';
|
|
4
4
|
import { identityIsBound, promptConfirm } from './auth.js';
|
|
5
5
|
import { CLI_NAME, DEFAULT_BACKEND_URL, DEFAULT_CONFIG_FILE, PRODUCT_NAME } from './constants.js';
|
|
6
|
-
import { ensurePathInShellConfigs, findPackageRoot, installCurrentPackage, removePathFromShellConfigs } from './install.js';
|
|
6
|
+
import { ensurePathInShellConfigs, findPackageRoot, getPackageVersion, installCurrentPackage, removePathFromShellConfigs } from './install.js';
|
|
7
7
|
import { LaunchdServiceManager } from './launchd.js';
|
|
8
8
|
import { collectLocalUsage, getLocalTimeZone } from './local-usage.js';
|
|
9
9
|
import { formatStatusOutput, mergeRuntimeConfig, saveRuntimeConfig } from './runtime-config.js';
|
|
@@ -41,6 +41,7 @@ Common options:
|
|
|
41
41
|
--yes Skip interactive prompts
|
|
42
42
|
--package-spec <spec> npm install spec used by init
|
|
43
43
|
--lines <n> Number of log lines for service logs
|
|
44
|
+
-v, --version Show version
|
|
44
45
|
-h, --help Show help
|
|
45
46
|
`;
|
|
46
47
|
|
|
@@ -93,6 +94,10 @@ export function parseCliArgs(argv) {
|
|
|
93
94
|
options.help = true;
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
97
|
+
if (token === '-v' || token === '--version') {
|
|
98
|
+
options.version = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
96
101
|
if (token === '--yes') {
|
|
97
102
|
options.yes = true;
|
|
98
103
|
continue;
|
|
@@ -685,6 +690,10 @@ function runLifecycleCommand(action, options) {
|
|
|
685
690
|
export async function main(argv = process.argv.slice(2)) {
|
|
686
691
|
try {
|
|
687
692
|
const { command, subcommand, extraPositionals, options } = parseCliArgs(argv);
|
|
693
|
+
if (options.version) {
|
|
694
|
+
console.log(getPackageVersion());
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
688
697
|
if (!command || options.help) {
|
|
689
698
|
printUsage();
|
|
690
699
|
return 0;
|
package/src/collector.js
CHANGED
|
@@ -516,7 +516,7 @@ export class CodexUsageUploader {
|
|
|
516
516
|
let lastError = null;
|
|
517
517
|
const collectorBody = this.collectorRequestBody();
|
|
518
518
|
|
|
519
|
-
const rows = this.stateDb.
|
|
519
|
+
const rows = this.stateDb.iterPendingBatches();
|
|
520
520
|
|
|
521
521
|
const uploadOne = async (row) => {
|
|
522
522
|
const payload = this.sanitizeUploadPayload(JSON.parse(row.payload_json));
|
package/src/install.js
CHANGED
|
@@ -12,6 +12,12 @@ const SHELL_CONFIG_FILES = [
|
|
|
12
12
|
path.join(os.homedir(), '.bash_profile'),
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
+
export function getPackageVersion(startUrl = import.meta.url) {
|
|
16
|
+
const root = findPackageRoot(startUrl);
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
18
|
+
return pkg.version ?? 'unknown';
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function findPackageRoot(startUrl = import.meta.url) {
|
|
16
22
|
let current = path.dirname(fileURLToPath(startUrl));
|
|
17
23
|
while (true) {
|
package/src/runtime-config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {
|
|
4
5
|
CLI_NAME,
|
|
@@ -23,11 +24,21 @@ import {
|
|
|
23
24
|
} from './constants.js';
|
|
24
25
|
import { stableStringify } from './utils.js';
|
|
25
26
|
|
|
27
|
+
const DEFAULT_INSTALL_ROOT_BASE = path.basename(DEFAULT_INSTALL_ROOT);
|
|
28
|
+
|
|
29
|
+
export function deriveEnvSuffix(installRoot) {
|
|
30
|
+
const base = path.basename(installRoot);
|
|
31
|
+
if (!base.startsWith(`${DEFAULT_INSTALL_ROOT_BASE}-`)) return '';
|
|
32
|
+
const envName = base.slice(DEFAULT_INSTALL_ROOT_BASE.length + 1);
|
|
33
|
+
return envName ? `-${envName}` : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
export function defaultRuntimeConfig(configFile = DEFAULT_CONFIG_FILE) {
|
|
27
37
|
const installRoot = path.dirname(configFile);
|
|
38
|
+
const envSuffix = deriveEnvSuffix(installRoot);
|
|
28
39
|
const appRoot = path.join(installRoot, 'app');
|
|
29
40
|
const currentAppDir = path.join(appRoot, 'current');
|
|
30
|
-
const launchdLabel = DEFAULT_LAUNCHD_LABEL
|
|
41
|
+
const launchdLabel = `${DEFAULT_LAUNCHD_LABEL}${envSuffix}`;
|
|
31
42
|
return {
|
|
32
43
|
configFile,
|
|
33
44
|
installRoot,
|
|
@@ -43,7 +54,7 @@ export function defaultRuntimeConfig(configFile = DEFAULT_CONFIG_FILE) {
|
|
|
43
54
|
packageSpec: '',
|
|
44
55
|
localBinDir: path.join(installRoot, 'bin'),
|
|
45
56
|
localBinPath: path.join(installRoot, 'bin', CLI_NAME),
|
|
46
|
-
homeBinLink:
|
|
57
|
+
homeBinLink: path.join(os.homedir(), 'bin', `${CLI_NAME}${envSuffix}`),
|
|
47
58
|
stdoutLogPath: path.join(installRoot, 'logs', path.basename(DEFAULT_STDOUT_LOG_PATH)),
|
|
48
59
|
stderrLogPath: path.join(installRoot, 'logs', path.basename(DEFAULT_STDERR_LOG_PATH)),
|
|
49
60
|
launchdLabel,
|
|
@@ -69,10 +80,11 @@ export function loadRuntimeConfig(configFile = DEFAULT_CONFIG_FILE) {
|
|
|
69
80
|
|
|
70
81
|
export function normalizeRuntimeConfig(runtime) {
|
|
71
82
|
const installRoot = runtime.installRoot || DEFAULT_INSTALL_ROOT;
|
|
83
|
+
const envSuffix = deriveEnvSuffix(installRoot);
|
|
72
84
|
const appRoot = runtime.appRoot || path.join(installRoot, 'app');
|
|
73
85
|
const currentAppDir = runtime.currentAppDir || path.join(appRoot, 'current');
|
|
74
|
-
const localBinDir = runtime.localBinDir ||
|
|
75
|
-
const launchdLabel = runtime.launchdLabel || DEFAULT_LAUNCHD_LABEL
|
|
86
|
+
const localBinDir = runtime.localBinDir || path.join(installRoot, 'bin');
|
|
87
|
+
const launchdLabel = runtime.launchdLabel || `${DEFAULT_LAUNCHD_LABEL}${envSuffix}`;
|
|
76
88
|
const defaultPlistPath = path.join(installRoot, 'launchd', `${launchdLabel}.plist`);
|
|
77
89
|
const defaultLaunchAgentPath = path.join(
|
|
78
90
|
path.dirname(DEFAULT_LAUNCH_AGENT_PATH),
|
|
@@ -99,7 +111,7 @@ export function normalizeRuntimeConfig(runtime) {
|
|
|
99
111
|
packageSpec: runtime.packageSpec || '',
|
|
100
112
|
localBinDir,
|
|
101
113
|
localBinPath: runtime.localBinPath || path.join(localBinDir, CLI_NAME),
|
|
102
|
-
homeBinLink: runtime.homeBinLink ||
|
|
114
|
+
homeBinLink: runtime.homeBinLink || path.join(os.homedir(), 'bin', `${CLI_NAME}${envSuffix}`),
|
|
103
115
|
stdoutLogPath: runtime.stdoutLogPath || path.join(installRoot, 'logs', 'stdout.log'),
|
|
104
116
|
stderrLogPath: runtime.stderrLogPath || path.join(installRoot, 'logs', 'stderr.log'),
|
|
105
117
|
launchdLabel,
|
package/src/state-db.js
CHANGED
|
@@ -207,8 +207,8 @@ export class StateDb {
|
|
|
207
207
|
this.db.prepare(`
|
|
208
208
|
INSERT INTO pending_batches(
|
|
209
209
|
batch_key, status, payload_json, payload_bytes, session_count, turn_count, event_count,
|
|
210
|
-
attempt_count,
|
|
211
|
-
) VALUES (?, 'buffering', ?, ?, ?, ?, ?, 0,
|
|
210
|
+
attempt_count, created_at, updated_at
|
|
211
|
+
) VALUES (?, 'buffering', ?, ?, ?, ?, ?, 0, ?, ?)
|
|
212
212
|
`).run(randomBatchKey(), payloadJson, payloadBytes, sessionCount, turnCount, eventCount, ts, ts);
|
|
213
213
|
}
|
|
214
214
|
return this.getBufferingBatch();
|
|
@@ -224,12 +224,12 @@ export class StateDb {
|
|
|
224
224
|
const ts = nowTs();
|
|
225
225
|
const updateStmt = this.db.prepare(`
|
|
226
226
|
UPDATE pending_batches
|
|
227
|
-
SET status = 'pending',
|
|
227
|
+
SET status = 'pending', updated_at = ?
|
|
228
228
|
WHERE id = ?
|
|
229
229
|
`);
|
|
230
230
|
for (const row of rows) {
|
|
231
231
|
if (force || ts - Number(row.created_at) >= MAX_BUFFER_AGE_SECONDS) {
|
|
232
|
-
updateStmt.run(ts,
|
|
232
|
+
updateStmt.run(ts, row.id);
|
|
233
233
|
sealed += 1;
|
|
234
234
|
}
|
|
235
235
|
}
|
|
@@ -247,29 +247,21 @@ export class StateDb {
|
|
|
247
247
|
const ts = nowTs();
|
|
248
248
|
this.db.prepare(`
|
|
249
249
|
UPDATE pending_batches
|
|
250
|
-
SET status = 'pending',
|
|
250
|
+
SET status = 'pending', updated_at = ?
|
|
251
251
|
WHERE id = ?
|
|
252
|
-
`).run(ts,
|
|
252
|
+
`).run(ts, row.id);
|
|
253
253
|
}
|
|
254
254
|
return shouldSeal;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
iterPendingBatches() {
|
|
258
258
|
const stmt = this.db.prepare(`
|
|
259
259
|
SELECT * FROM pending_batches
|
|
260
|
-
WHERE status = 'pending'
|
|
260
|
+
WHERE status = 'pending'
|
|
261
261
|
ORDER BY created_at ASC, id ASC
|
|
262
262
|
`);
|
|
263
263
|
stmt.setReadBigInts(true);
|
|
264
|
-
return stmt.all(
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
markAllPendingDue() {
|
|
268
|
-
this.db.prepare(`
|
|
269
|
-
UPDATE pending_batches
|
|
270
|
-
SET next_retry_at = 0, updated_at = ?
|
|
271
|
-
WHERE status = 'pending'
|
|
272
|
-
`).run(nowTs());
|
|
264
|
+
return stmt.all();
|
|
273
265
|
}
|
|
274
266
|
|
|
275
267
|
markBatchUploaded(batchId) {
|
|
@@ -277,12 +269,11 @@ export class StateDb {
|
|
|
277
269
|
}
|
|
278
270
|
|
|
279
271
|
markBatchFailed(batchId, attemptCount, errorMessage) {
|
|
280
|
-
const nextRetryAt = nowTs() + Math.min(300, 2 ** Math.min(attemptCount, 8));
|
|
281
272
|
this.db.prepare(`
|
|
282
273
|
UPDATE pending_batches
|
|
283
|
-
SET attempt_count = ?,
|
|
274
|
+
SET attempt_count = ?, last_error = ?, updated_at = ?
|
|
284
275
|
WHERE id = ?
|
|
285
|
-
`).run(attemptCount,
|
|
276
|
+
`).run(attemptCount, String(errorMessage).slice(0, 1000), nowTs(), batchId);
|
|
286
277
|
}
|
|
287
278
|
|
|
288
279
|
resetBackfillState() {
|