@token-dashboard/codex-usage-uploader 0.1.8 → 0.1.9
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/CHANGELOG.zh-CN.md +8 -0
- package/README.md +5 -125
- package/dist/bin/codex-usage-uploader.js +4 -4
- package/package.json +3 -12
- package/dist/cli.mjs +0 -1
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本文档记录 `@token-dashboard/codex-usage-uploader` 的重要变更。
|
|
4
4
|
|
|
5
|
+
## 0.1.9 - 2026-06-30
|
|
6
|
+
|
|
7
|
+
### 移除
|
|
8
|
+
|
|
9
|
+
- 独立 `codex-usage-uploader` 命令已移除。
|
|
10
|
+
- 请改用统一 CLI:`@token-dashboard/usage-uploader` / `token-usage-uploader codex ...`。
|
|
11
|
+
- 此版本只保留 tombstone 提示,防止用户继续安装旧后台服务。
|
|
12
|
+
|
|
5
13
|
## 0.1.8 - 2026-05-20
|
|
6
14
|
|
|
7
15
|
### 修复
|
package/README.md
CHANGED
|
@@ -1,130 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# codex-usage-uploader removed
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package is deprecated and no longer provides a standalone uploader.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- 首次安装自动补采全量历史
|
|
7
|
-
- macOS launchd 托管,登录自启动可选
|
|
8
|
-
|
|
9
|
-
> 仅支持 macOS,需要 Node.js >= 22.13.0。
|
|
10
|
-
|
|
11
|
-
版本变更记录见 [CHANGELOG.zh-CN.md](./CHANGELOG.zh-CN.md)。
|
|
12
|
-
|
|
13
|
-
## 快速开始
|
|
14
|
-
|
|
15
|
-
一行命令完成安装、身份绑定、历史补采和后台服务启动:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx @token-dashboard/codex-usage-uploader init
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
安装过程中会自动读取 Codex 登录态(`~/.codex/auth.json`)获取身份信息。如果读取不到邮箱,会在终端交互式提示你手动填写;同时会询问是否在这台 Mac 上启用登录自启动。
|
|
22
|
-
|
|
23
|
-
安装完成后,`codex-usage-uploader` 命令会被写入 `~/bin/`,后续可直接使用。
|
|
24
|
-
|
|
25
|
-
## 命令一览
|
|
26
|
-
|
|
27
|
-
### 初始化 / 升级
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
codex-usage-uploader init
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
首次运行时完成安装与配置;重复运行会升级本地版本并重启后台服务,同时可重新选择是否启用登录自启动。后端地址默认为 `http://101.126.66.51:8086`,如需自定义可通过 `--backend-url` 指定。
|
|
34
|
-
|
|
35
|
-
### 绑定身份
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
codex-usage-uploader bind
|
|
39
|
-
codex-usage-uploader bind --email you@company.com --yes
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
更新上报身份。如果后台服务正在运行,会自动重启以应用新身份。
|
|
43
|
-
|
|
44
|
-
### 后台服务管理
|
|
45
|
-
|
|
46
|
-
| 命令 | 说明 |
|
|
47
|
-
| --------------------------------------- | ------------------------------- |
|
|
48
|
-
| `codex-usage-uploader start` | 启动后台服务 |
|
|
49
|
-
| `codex-usage-uploader stop` | 停止后台服务 |
|
|
50
|
-
| `codex-usage-uploader restart` | 重启后台服务 |
|
|
51
|
-
| `codex-usage-uploader status` | 查看服务状态与本地队列 |
|
|
52
|
-
| `codex-usage-uploader logs` | 查看服务日志(默认最近 100 行) |
|
|
53
|
-
| `codex-usage-uploader logs --lines 500` | 查看更多日志 |
|
|
54
|
-
|
|
55
|
-
### 查看本机 token 用量
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
codex-usage-uploader usage
|
|
59
|
-
codex-usage-uploader usage --period today
|
|
60
|
-
codex-usage-uploader usage --period 7d
|
|
61
|
-
codex-usage-uploader usage --from 2026-04-01 --to 2026-04-17
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`usage` 是一个纯本地只读命令,不依赖后端查询,也不要求先执行 `init`。它会重新扫描当前机器上的 Codex rollout 文件,默认同时包含:
|
|
65
|
-
|
|
66
|
-
- `~/.codex/sessions`
|
|
67
|
-
- 同级的 `~/.codex/archived_sessions`
|
|
68
|
-
|
|
69
|
-
默认输出是一张纯文本表格,列为 `Date`、`Models`、`Input`、`Output`、`Reasoning`、`Cache Read`、`Total Tokens`,最后一行固定为 `Total`。默认 `--period=all` 时,表格覆盖全历史所有有 token 使用的日期。
|
|
70
|
-
|
|
71
|
-
统计口径与现有 uploader/后端保持一致,使用 `token_count` 事件中的 `last_*` token 字段,而不是累计 `total_*` 字段。切天时区使用当前机器时区。
|
|
72
|
-
|
|
73
|
-
### 清除本地状态
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
codex-usage-uploader clear --yes
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
清空本地补采进度和待上传队列,不影响已绑定的身份。服务端清库后需要重新补历史时使用:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
codex-usage-uploader stop
|
|
83
|
-
codex-usage-uploader clear --yes
|
|
84
|
-
codex-usage-uploader init
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 卸载
|
|
5
|
+
Use the unified uploader instead:
|
|
88
6
|
|
|
89
7
|
```bash
|
|
90
|
-
|
|
8
|
+
npx -y --registry=https://artifacts-cn-beijing.volces.com/repository/nodeskai-pub/ @token-dashboard/usage-uploader@latest init
|
|
9
|
+
token-usage-uploader codex status
|
|
91
10
|
```
|
|
92
|
-
|
|
93
|
-
停止后台服务并删除本地安装目录。
|
|
94
|
-
|
|
95
|
-
## 常用选项
|
|
96
|
-
|
|
97
|
-
| 选项 | 说明 |
|
|
98
|
-
| ------------------------ | ------------------------------------------------------ |
|
|
99
|
-
| `--backend-url <url>` | Dashboard 后端地址(默认 `http://101.126.66.51:8086`) |
|
|
100
|
-
| `--email <email>` | 绑定邮箱 |
|
|
101
|
-
| `--employee-name <name>` | 绑定姓名 |
|
|
102
|
-
| `--employee-id <id>` | 绑定工号 |
|
|
103
|
-
| `--interval <seconds>` | 扫描间隔秒数(默认 30) |
|
|
104
|
-
| `--period <value>` | `usage` 时间范围:`today`、`7d`、`30d`、`all` |
|
|
105
|
-
| `--from <date>` | `usage` 开始日期(`YYYY-MM-DD`) |
|
|
106
|
-
| `--to <date>` | `usage` 结束日期(`YYYY-MM-DD`) |
|
|
107
|
-
| `--yes` | 跳过所有交互确认 |
|
|
108
|
-
| `--lines <n>` | `logs` 命令输出行数(默认 100) |
|
|
109
|
-
| `-h, --help` | 显示帮助 |
|
|
110
|
-
|
|
111
|
-
## 工作原理
|
|
112
|
-
|
|
113
|
-
1. **init** 将包安装到 `~/.codex-usage-uploader/`,绑定身份后前台执行一次全量历史补采,完成后启动 macOS launchd 后台服务,并按交互选择决定是否启用登录自启动。
|
|
114
|
-
2. 后台服务每 30 秒(可通过 `--interval` 调整)增量扫描 `~/.codex/sessions/` 下的 rollout 文件。
|
|
115
|
-
3. 扫描到的 token 用量数据暂存在本地 SQLite 队列中,满足条件后自动封批上传至后端。
|
|
116
|
-
4. `usage` 命令会按需重扫本地 `sessions + archived_sessions`,直接在终端计算并展示本机 token 用量,不写入本地状态库,也不访问后端。
|
|
117
|
-
5. 所有数据在上报前已做去敏处理,仅包含 session 元信息、turn 上下文摘要和 token 计数。
|
|
118
|
-
|
|
119
|
-
## 本地文件
|
|
120
|
-
|
|
121
|
-
| 路径 | 说明 |
|
|
122
|
-
| -------------------------------------- | -------------- |
|
|
123
|
-
| `~/.codex-usage-uploader/config.json` | 运行配置 |
|
|
124
|
-
| `~/.codex-usage-uploader/state.sqlite` | 本地状态数据库 |
|
|
125
|
-
| `~/.codex-usage-uploader/logs/` | 服务日志 |
|
|
126
|
-
| `~/.codex-usage-uploader/launchd/` | launchd service plist |
|
|
127
|
-
|
|
128
|
-
## License
|
|
129
|
-
|
|
130
|
-
MIT
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
process.exit(
|
|
2
|
+
console.error('codex-usage-uploader has been removed.');
|
|
3
|
+
console.error('Please use: npx -y --registry=https://artifacts-cn-beijing.volces.com/repository/nodeskai-pub/ @token-dashboard/usage-uploader@latest init');
|
|
4
|
+
console.error('Then run: token-usage-uploader codex status');
|
|
5
|
+
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@token-dashboard/codex-usage-uploader",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Codex
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Deprecated Codex usage uploader CLI tombstone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"codex-usage-uploader": "dist/bin/codex-usage-uploader.js"
|
|
@@ -14,16 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "node scripts/build.mjs",
|
|
16
16
|
"prepublishOnly": "npm run build",
|
|
17
|
-
"
|
|
18
|
-
"init": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json init --backend-url http://localhost:8086",
|
|
19
|
-
"start": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json start",
|
|
20
|
-
"stop": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json stop",
|
|
21
|
-
"restart": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json restart",
|
|
22
|
-
"status": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json status",
|
|
23
|
-
"usage": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json usage",
|
|
24
|
-
"logs": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json logs",
|
|
25
|
-
"clear": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json clear",
|
|
26
|
-
"uninstall": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json uninstall"
|
|
17
|
+
"test": "npm run build"
|
|
27
18
|
},
|
|
28
19
|
"engines": {
|
|
29
20
|
"node": ">=22.13.0"
|
package/dist/cli.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:path";import n from"node:os";import{createInterface as o}from"node:readline";import s from"node:readline/promises";import{stdout as i,stdin as r}from"node:process";import{Buffer as a}from"node:buffer";import{createHash as l}from"node:crypto";import{DatabaseSync as c}from"node:sqlite";import{spawnSync as u}from"node:child_process";import{fileURLToPath as d}from"node:url";function h(t){try{const n=JSON.parse(e.readFileSync(t,"utf8")),o=n?.tokens;if(!o||"object"!=typeof o)return{};const s=function(e){if(!e)return{};const t=String(e).split(".");if(t.length<2||!t[1])return{};try{const e=Buffer.from(t[1],"base64url").toString("utf8"),n=JSON.parse(e);return n&&"object"==typeof n?n:{}}catch{return{}}}(o.id_token),i={};return"string"==typeof s.email&&s.email.trim()&&(i.employeeEmail=s.email.trim()),"string"==typeof s.name&&s.name.trim()&&(i.employeeName=s.name.trim()),i}catch{return{}}}function p(e){return Boolean(e.employeeId||e.employeeEmail||e.employeeName)}async function m(e,t){const n=s.createInterface({input:r,output:i});try{const o=t?` [${t}]`:"";return(await n.question(`${e}${o}: `)).trim()||t||null}finally{n.close()}}function g(){return Date.now()/1e3}function f(e){return new Promise(t=>setTimeout(t,e))}function y(e){return JSON.stringify(E(e))}function b(e){return null==e?null:"string"==typeof e?e:"number"==typeof e||"boolean"==typeof e?String(e):y(e)}function S(e){const n=String(e??"").split(t.sep).join("/"),o=t.posix.basename(n),s=/^rollout-(\d{4})-(\d{2})-(\d{2})T.*\.jsonl$/.exec(o);if(!s)return n;const i=`${s[1]}/${s[2]}/${s[3]}/`;return n.startsWith(i)?n:`${i}${o}`}function E(e){return Array.isArray(e)?e.map(E):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,E(e[t])])):e}function k(e){if(!e)return null;const t=String(e).replace("Z","+00:00"),n=Date.parse(t);return Number.isNaN(n)?null:n}function _(e,n){if(e){let t=e.replace(/\/+$/,"").split("/").pop()??"";if(t.endsWith(".git")&&(t=t.slice(0,-4)),t)return t}return n&&t.basename(n)||null}function v(e,t,n){const o=a.from(`${e}|${t}|${n}`,"utf8");return l("sha1").update(o).digest("hex")}class T{constructor(e,t,n={}){this.collectorIdentity=e,this.relpath=t,this.currentSession=n.current_session??null,this.currentTurn=n.current_turn??null}exportState(){return{current_session:this.currentSession,current_turn:this.currentTurn}}normalizeSession(e){if(!e?.id)return null;const t=e.git&&"object"==typeof e.git?e.git:{},n=e.cwd??null,o=t.repository_url??null;return{sessionId:String(e.id),sessionTimestamp:k(e.timestamp),cwd:n,originator:e.originator??null,source:e.source??null,cliVersion:e.cli_version??null,modelProvider:e.model_provider??null,repoName:_(o,n),gitBranch:t.branch??null,gitCommitHash:t.commit_hash??null,repositoryUrl:o}}normalizeTurn(e,t){const n=e?.turn_id,o=this.currentSession?.sessionId;if(!n||!o)return null;const s=e.sandbox_policy&&"object"==typeof e.sandbox_policy?e.sandbox_policy:{},i=Array.isArray(s.writable_roots)?s.writable_roots:null,r=e.collaboration_mode&&"object"==typeof e.collaboration_mode?e.collaboration_mode.mode:null;return{turnId:String(n),sessionId:String(o),eventTimestamp:t,cwd:e.cwd??null,currentDate:e.current_date??null,timezone:e.timezone??null,approvalPolicy:b(e.approval_policy),sandboxPolicyType:s.type??null,sandboxNetworkAccess:s.network_access??null,sandboxWritableRootsJson:i?y(i):null,sandboxPolicyJson:Object.keys(s).length?y(s):null,model:e.model??null,personality:e.personality??null,collaborationMode:r??null,effort:e.effort??null,summary:e.summary??null,truncationPolicyJson:null!=e.truncation_policy?y(e.truncation_policy):null}}normalizeTokenEvent(e,t,n){if(!this.currentSession||null==t)return null;const o=e?.info;if(!o||"object"!=typeof o)return null;const s=o.total_token_usage&&"object"==typeof o.total_token_usage?o.total_token_usage:{},i=o.last_token_usage&&"object"==typeof o.last_token_usage?o.last_token_usage:{},r=e.rate_limits&&"object"==typeof e.rate_limits?e.rate_limits:{},a=r.primary&&"object"==typeof r.primary?r.primary:{},l=r.secondary&&"object"==typeof r.secondary?r.secondary:{},c=this.currentSession,u=this.currentTurn??{};return{eventUid:v(this.collectorIdentity.collectorId,this.relpath,n),sessionId:c.sessionId,turnId:u.turnId??null,sourceFileRelpath:this.relpath,lineNo:n,timestamp:t,model:u.model??null,cwd:u.cwd??c.cwd??null,timezone:u.timezone??null,approvalPolicy:u.approvalPolicy??null,sandboxPolicyType:u.sandboxPolicyType??null,source:c.source??null,originator:c.originator??null,cliVersion:c.cliVersion??null,repositoryUrl:c.repositoryUrl??null,repoName:c.repoName??null,gitBranch:c.gitBranch??null,gitCommitHash:c.gitCommitHash??null,totalInputTokens:s.input_tokens??null,totalCachedInputTokens:s.cached_input_tokens??null,totalOutputTokens:s.output_tokens??null,totalReasoningOutputTokens:s.reasoning_output_tokens??null,totalTokens:s.total_tokens??null,lastInputTokens:i.input_tokens??null,lastCachedInputTokens:i.cached_input_tokens??null,lastOutputTokens:i.output_tokens??null,lastReasoningOutputTokens:i.reasoning_output_tokens??null,lastTotalTokens:i.total_tokens??null,modelContextWindow:o.model_context_window??null,rateLimitPlanType:r.plan_type??null,primaryUsedPercent:a.used_percent??null,primaryWindowMinutes:a.window_minutes??null,primaryResetsAt:a.resets_at??null,secondaryUsedPercent:l.used_percent??null,secondaryWindowMinutes:l.window_minutes??null,secondaryResetsAt:l.resets_at??null,credits:P(r.credits),rawRateLimitsJson:Object.keys(r).length?y(r):null}}processLine(e,t){let n;try{n=JSON.parse(t)}catch{return{sessions:[],turns:[],events:[]}}const o=n.type,s=n.payload??{},i=k(n.timestamp),r=[],a=[],l=[];if("session_meta"===o){const e=this.normalizeSession(s);e&&(this.currentSession=e,r.push(e))}else if("turn_context"===o){const e=this.normalizeTurn(s,i);e&&(this.currentTurn=e,a.push(e))}else if("event_msg"===o&&"token_count"===s.type){const t=this.normalizeTokenEvent(s,i,e);t&&l.push(t)}return{sessions:r,turns:a,events:l}}}function P(e){if("number"==typeof e&&Number.isFinite(e))return e;if(e&&"object"==typeof e){const t=e.balance;if("number"==typeof t&&Number.isFinite(t))return t}return null}const w="Codex 用量上报",L="codex-usage-uploader",N="sessions",I="archived_sessions",$=t.join(n.homedir(),".codex","auth.json"),D=t.join(n.homedir(),".codex","sessions"),B=t.join(n.homedir(),".codex-usage-uploader"),A=t.join(B,"config.json"),C=t.join(B,"state.sqlite"),R=t.join(B,"logs"),F=t.join(R,"stdout.log"),O=t.join(R,"stderr.log"),x=t.join(B,"app");t.join(x,"current");const U=t.join(B,"bin");t.join(U,L),t.join(n.homedir(),"bin",L);const j="com.token-dashboard.codex-usage-uploader";t.join(B,"launchd",`${j}.plist`);const M=t.join(n.homedir(),"Library","LaunchAgents",`${j}.plist`),z=t.join(n.homedir(),".codex-usage-uploader-collector-id"),H="http://101.126.66.51:8086",q=1e6;class W{constructor(n){this.dbPath=n,e.mkdirSync(t.dirname(n),{recursive:!0}),this.db=new c(n,{readBigInts:!0}),this.initSchema()}close(){this.db.close()}initSchema(){this.db.exec("\n CREATE TABLE IF NOT EXISTS identity_config (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at REAL NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS pending_batches (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n batch_key TEXT NOT NULL UNIQUE,\n status TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n payload_bytes INTEGER NOT NULL DEFAULT 0,\n session_count INTEGER NOT NULL DEFAULT 0,\n turn_count INTEGER NOT NULL DEFAULT 0,\n event_count INTEGER NOT NULL DEFAULT 0,\n attempt_count INTEGER NOT NULL DEFAULT 0,\n next_retry_at REAL,\n last_error TEXT,\n created_at REAL NOT NULL,\n updated_at REAL NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS upload_checkpoint (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at REAL NOT NULL\n );\n "),this.ensureIngestionFilesTable()}ensureIngestionFilesTable(){if(this.db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'ingestion_files'").get()){if(!this.db.prepare("PRAGMA table_info(ingestion_files)").all().some(e=>"source_root"===e.name)){this.db.exec("BEGIN");try{this.db.exec("ALTER TABLE ingestion_files RENAME TO ingestion_files_legacy"),this.createIngestionFilesTable(),this.db.prepare("\n INSERT INTO ingestion_files(\n source_root, relpath, file_size, file_mtime_ns, last_line_no, state_json, updated_at\n )\n SELECT 'sessions', relpath, file_size, file_mtime_ns, last_line_no, state_json, updated_at\n FROM ingestion_files_legacy\n ").run(),this.db.exec("DROP TABLE ingestion_files_legacy"),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}}else this.createIngestionFilesTable()}createIngestionFilesTable(){this.db.exec("\n CREATE TABLE IF NOT EXISTS ingestion_files (\n source_root TEXT NOT NULL,\n relpath TEXT NOT NULL,\n file_size INTEGER NOT NULL,\n file_mtime_ns INTEGER NOT NULL,\n last_line_no INTEGER NOT NULL,\n state_json TEXT,\n updated_at REAL NOT NULL,\n PRIMARY KEY (source_root, relpath)\n );\n ")}getIdentity(){const e=this.db.prepare("SELECT key, value FROM identity_config").all();return Object.fromEntries(e.map(e=>[e.key,e.value]))}setIdentity(e){const t=g(),n=this.db.prepare("\n INSERT INTO identity_config(key, value, updated_at)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at\n "),o=this.db.prepare("DELETE FROM identity_config WHERE key = ?");this.db.exec("BEGIN");try{for(const[s,i]of Object.entries(e))null==i?o.run(s):n.run(s,i,t);this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getCheckpoint(e){const t=this.db.prepare("SELECT value FROM upload_checkpoint WHERE key = ?").get(e);return t?.value??null}setCheckpoint(e,t){const n=g();this.db.prepare("\n INSERT INTO upload_checkpoint(key, value, updated_at)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at\n ").run(e,t,n)}getFileState(e,t){const n=this.db.prepare("SELECT * FROM ingestion_files WHERE source_root = ? AND relpath = ?");return n.setReadBigInts(!0),n.get(e,t)??null}upsertFileState(e,t,n,o,s,i){this.db.prepare("\n INSERT INTO ingestion_files(\n source_root, relpath, file_size, file_mtime_ns, last_line_no, state_json, updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(source_root, relpath) DO UPDATE SET\n file_size = excluded.file_size,\n file_mtime_ns = excluded.file_mtime_ns,\n last_line_no = excluded.last_line_no,\n state_json = excluded.state_json,\n updated_at = excluded.updated_at\n ").run(e,t,n,o,s,y(i),g())}getBufferingBatch(){const e=this.db.prepare("\n SELECT * FROM pending_batches\n WHERE status = 'buffering'\n ORDER BY id ASC\n LIMIT 1\n ");return e.setReadBigInts(!0),e.get()??null}saveBufferingPayload(e){const t=this.getBufferingBatch(),n=y(e),o=Buffer.byteLength(n,"utf8"),s=e.sessions?.length??0,i=e.turns?.length??0,r=e.events?.length??0,a=g();return t?this.db.prepare("\n UPDATE pending_batches\n SET payload_json = ?, payload_bytes = ?, session_count = ?, turn_count = ?, event_count = ?, updated_at = ?\n WHERE id = ?\n ").run(n,o,s,i,r,a,t.id):this.db.prepare("\n INSERT INTO pending_batches(\n batch_key, status, payload_json, payload_bytes, session_count, turn_count, event_count,\n attempt_count, created_at, updated_at\n ) VALUES (?, 'buffering', ?, ?, ?, ?, ?, 0, ?, ?)\n ").run(`${Date.now().toString(16)}${Math.random().toString(16).slice(2,14)}`,n,o,s,i,r,a,a),this.getBufferingBatch()}sealStaleBatches(e=!1){const t=this.db.prepare("SELECT id, created_at FROM pending_batches WHERE status = 'buffering'");t.setReadBigInts(!0);const n=t.all();let o=0;const s=g(),i=this.db.prepare("\n UPDATE pending_batches\n SET status = 'pending', updated_at = ?\n WHERE id = ?\n ");for(const t of n)(e||s-Number(t.created_at)>=60)&&(i.run(s,t.id),o+=1);return o}sealBufferIfThresholdHit(){const e=this.getBufferingBatch();if(!e)return!1;const t=Number(e.event_count)>=200||Number(e.payload_bytes)>=q||g()-Number(e.created_at)>=60;if(t){const t=g();this.db.prepare("\n UPDATE pending_batches\n SET status = 'pending', updated_at = ?\n WHERE id = ?\n ").run(t,e.id)}return t}iterPendingBatches(){const e=this.db.prepare("\n SELECT * FROM pending_batches\n WHERE status = 'pending'\n ORDER BY created_at ASC, id ASC\n ");return e.setReadBigInts(!0),e.all()}markBatchUploaded(e){this.db.prepare("DELETE FROM pending_batches WHERE id = ?").run(e)}markBatchFailed(e,t,n){this.db.prepare("\n UPDATE pending_batches\n SET attempt_count = ?, last_error = ?, updated_at = ?\n WHERE id = ?\n ").run(t,String(n).slice(0,1e3),g(),e)}resetBackfillState(){this.db.exec("BEGIN");try{this.db.prepare("DELETE FROM ingestion_files").run(),this.db.prepare("DELETE FROM pending_batches").run(),this.db.prepare("DELETE FROM upload_checkpoint").run(),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getQueueStats(){const e=this.db.prepare("\n SELECT\n COALESCE(SUM(CASE WHEN status = 'buffering' THEN 1 ELSE 0 END), 0) AS buffering_batch_count,\n COALESCE(SUM(CASE WHEN status = 'pending' AND attempt_count = 0 THEN 1 ELSE 0 END), 0) AS pending_batch_count,\n COALESCE(SUM(CASE WHEN status = 'pending' AND attempt_count > 0 THEN 1 ELSE 0 END), 0) AS retrying_batch_count,\n COALESCE(SUM(session_count), 0) AS queued_sessions,\n COALESCE(SUM(turn_count), 0) AS queued_turns,\n COALESCE(SUM(event_count), 0) AS queued_events,\n MIN(created_at) AS oldest_created_at\n FROM pending_batches\n ");e.setReadBigInts(!0);const t=e.get()??{},n=null==t.oldest_created_at?null:Number(t.oldest_created_at);return{bufferingBatchCount:Number(t.buffering_batch_count??0),pendingBatchCount:Number(t.pending_batch_count??0),retryingBatchCount:Number(t.retrying_batch_count??0),queuedSessions:Number(t.queued_sessions??0),queuedTurns:Number(t.queued_turns??0),queuedEvents:Number(t.queued_events??0),oldestPendingAgeSeconds:null==n?null:Math.max(0,Math.floor(g()-n))}}}class Q{constructor({sessionsDir:e,stateDbPath:t,backendUrl:n,intervalSeconds:o,codexAuthPath:s,persistentCollectorIdPath:i=z,scanChunkMaxEvents:r=50,scanChunkMaxBytes:a=262144}){this.sessionsDir=e,this.stateDb=new W(t),this.backendUrl=n?.replace(/\/+$/,"")||null,this.intervalSeconds=o,this.codexAuthPath=s,this.persistentCollectorIdPath=i,this.scanChunkMaxEvents=r,this.scanChunkMaxBytes=a,this.identity=this.ensureIdentity()}close(){this.stateDb.close()}ensureIdentity(){const t=this.stateDb.getIdentity();if(!t.collectorId){const n=function(t){try{return e.readFileSync(t,"utf8").trim()||null}catch{return null}}(this.persistentCollectorIdPath);t.collectorId=n??`${Date.now().toString(16)}${Math.random().toString(16).slice(2,14)}`}!function(t,n){const o=`${t}.${process.pid}.tmp`;e.writeFileSync(o,n,"utf8"),e.renameSync(o,t)}(this.persistentCollectorIdPath,t.collectorId),t.deviceId||(t.deviceId=`${n.hostname()}-${Math.random().toString(16).slice(2,14)}`),t.hostname||(t.hostname=n.hostname());const o=h(this.codexAuthPath);for(const e of["employeeEmail","employeeName"])!t[e]&&o[e]&&(t[e]=o[e]);return this.stateDb.setIdentity(t),this.stateDb.getIdentity()}resolveIdentityValues({employeeId:e,employeeEmail:t,employeeName:n,deviceId:o,hostname:s}={}){const i=this.stateDb.getIdentity(),r=h(this.codexAuthPath);return{employeeId:void 0!==e?e:i.employeeId??null,employeeEmail:void 0!==t?t:i.employeeEmail??r.employeeEmail??null,employeeName:void 0!==n?n:i.employeeName??r.employeeName??null,deviceId:void 0!==o?o:i.deviceId??null,hostname:void 0!==s?s:i.hostname??null}}configureIdentity({employeeId:e=null,employeeEmail:t=null,employeeName:n=null,deviceId:o,hostname:s}={}){const i={employeeId:e,employeeEmail:t,employeeName:n};if(!p(i))throw new Error("At least one of employeeId, employeeEmail, or employeeName must be provided.");return void 0!==o&&(i.deviceId=o),void 0!==s&&(i.hostname=s),this.stateDb.setIdentity(i),this.identity=this.ensureIdentity(),this.identity}configureIdentityWithDefaults(e={}){const t=this.resolveIdentityValues(e);return this.configureIdentity(t)}async configureIdentityInteractive(e={}){const t=this.resolveIdentityValues(e),n=h(this.codexAuthPath);return(n.employeeEmail||n.employeeName)&&console.log("Detected identity defaults from ~/.codex/auth.json. Press Enter to accept."),void 0===e.employeeEmail&&(t.employeeEmail=await m("Employee email",t.employeeEmail)),p(t)||(void 0===e.employeeName&&(t.employeeName=await m("Employee name",t.employeeName)),p(t)||void 0===e.employeeId&&(t.employeeId=await m("Employee ID",t.employeeId))),this.configureIdentity(t)}resetBackfillState(){this.stateDb.resetBackfillState()}getQueueStats(){return this.stateDb.getQueueStats()}getArchivedSessionsDir(){return t.join(t.dirname(this.sessionsDir),I)}getScanRoots({includeArchived:e=!1}={}){const t=[{sourceRoot:N,dir:this.sessionsDir}];return e&&t.push({sourceRoot:I,dir:this.getArchivedSessionsDir()}),t}iterRolloutFiles(n){if(!e.existsSync(n))return[];const o=[],s=n=>{for(const i of e.readdirSync(n,{withFileTypes:!0})){const e=t.join(n,i.name);i.isDirectory()?s(e):i.isFile()&&/^rollout-.*\.jsonl$/.test(i.name)&&o.push(e)}};return s(n),o.sort(),o}buildSnapshot({includeArchived:n=!1}={}){const o=[];for(const s of this.getScanRoots({includeArchived:n}))for(const n of this.iterRolloutFiles(s.dir)){const i=t.relative(s.dir,n).split(t.sep).join("/"),r=S(i),a=e.statSync(n,{bigint:!0}),l=this.stateDb.getFileState(s.sourceRoot,r),c=l||s.sourceRoot!==I||r===i?null:this.stateDb.getFileState(s.sourceRoot,i),u=l||s.sourceRoot!==I?null:this.stateDb.getFileState(N,r)??c;l&&l.file_size===a.size&&l.file_mtime_ns===a.mtimeNs||o.push({filePath:n,relpath:r,actualRelpath:i,sourceRoot:s.sourceRoot,progressPath:`${s.sourceRoot}/${i}`,snapshotSize:a.size,snapshotMtimeNs:a.mtimeNs,fallbackState:u})}return o}buildBackfillSnapshot(){return this.buildSnapshot({includeArchived:!0})}emptyPayload(){return{sessions:[],turns:[],events:[]}}payloadHasData(e){return Boolean(e.sessions.length||e.turns.length||e.events.length)}payloadBytes(e){return Buffer.byteLength(y(e),"utf8")}mergePayload(e,t){const n=new Map((e.sessions??[]).map(e=>[e.sessionId,e])),o=new Map((e.turns??[]).map(e=>[e.turnId,e])),s=new Map((e.events??[]).map(e=>[e.eventUid,e]));for(const e of t.sessions??[])n.set(e.sessionId,e);for(const e of t.turns??[])o.set(e.turnId,e);for(const e of t.events??[])s.set(e.eventUid,e);return{sessions:[...n.values()],turns:[...o.values()],events:[...s.values()]}}shouldFlushChunk(e){return!!this.payloadHasData(e)&&(e.events.length>=this.scanChunkMaxEvents||this.payloadBytes(e)>=this.scanChunkMaxBytes)}wouldExceedUploadThreshold(e){return!!this.payloadHasData(e)&&(e.events.length>200||this.payloadBytes(e)>q)}appendPayloadToBuffer(e){if(!this.payloadHasData(e))return 0;const t=this.stateDb.getBufferingBatch();let n=0,o=e;if(t){const s=JSON.parse(t.payload_json),i=this.mergePayload(s,e);this.wouldExceedUploadThreshold(i)?n+=this.stateDb.sealStaleBatches(!0):o=i}return this.stateDb.saveBufferingPayload(o),n+(this.stateDb.sealBufferIfThresholdHit()?1:0)}async scanSnapshotEntries(t,{onFileProcessed:n}={}){const s={filesScanned:0,sessions:0,turns:0,events:0,batchesQueued:0};for(const i of t){const{filePath:t,relpath:r,sourceRoot:a,snapshotSize:l,snapshotMtimeNs:c,fallbackState:u}=i,d=this.stateDb.getFileState(a,r),h=d??u??null;if(s.filesScanned+=1,!d&&u&&u.file_size===l&&u.file_mtime_ns===c){this.stateDb.upsertFileState(a,r,l,c,Number(u.last_line_no),JSON.parse(u.state_json||"{}")),n?.({entry:i,filesProcessed:s.filesScanned,totals:{filesScanned:s.filesScanned,sessions:s.sessions,turns:s.turns,events:s.events,batchesQueued:s.batchesQueued}});continue}let p={},m=0;h&&l>h.file_size&&(m=Number(h.last_line_no),p=JSON.parse(h.state_json||"{}"));const g=new T(this.identity,r,p);let f=this.emptyPayload(),y=0,b=0;if(Number(l)>0){const n=e.createReadStream(t,{encoding:"utf8",start:0,end:Number(l)-1}),i=o({input:n,crlfDelay:1/0});try{for await(const e of i){if(b+=1,!e)continue;if(y=b,b<=m)continue;const t=g.processLine(b,e);s.sessions+=t.sessions.length,s.turns+=t.turns.length,s.events+=t.events.length,f=this.mergePayload(f,t),this.shouldFlushChunk(f)&&(s.batchesQueued+=this.appendPayloadToBuffer(f),f=this.emptyPayload())}}finally{i.close()}}this.payloadHasData(f)&&(s.batchesQueued+=this.appendPayloadToBuffer(f)),this.stateDb.upsertFileState(a,r,l,c,y,g.exportState()),n?.({entry:i,filesProcessed:s.filesScanned,totals:{filesScanned:s.filesScanned,sessions:s.sessions,turns:s.turns,events:s.events,batchesQueued:s.batchesQueued}})}return s}async scanRollouts(){return this.scanSnapshotEntries(this.buildSnapshot({includeArchived:!0}))}collectorRequestBody(){return{collectorId:this.identity.collectorId,deviceId:this.identity.deviceId,hostname:this.identity.hostname??null,employeeId:this.identity.employeeId??null,employeeEmail:this.identity.employeeEmail??null,employeeName:this.identity.employeeName??null}}sanitizeUploadPayload(e){return{sessions:Array.isArray(e.sessions)?e.sessions:[],turns:Array.isArray(e.turns)?e.turns.map(e=>({...e,approvalPolicy:b(e?.approvalPolicy)})):[],events:Array.isArray(e.events)?e.events.map(e=>({...e,approvalPolicy:b(e?.approvalPolicy),credits:"number"==typeof e?.credits&&Number.isFinite(e.credits)?e.credits:e?.credits&&"object"==typeof e.credits&&"number"==typeof e.credits.balance&&Number.isFinite(e.credits.balance)?e.credits.balance:null})):[]}}async postJson(e,t){if(!this.backendUrl)throw new Error("backendUrl is not configured");const n=await fetch(`${this.backendUrl}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok){const e=await n.text(),t=e?`: ${e.slice(0,500)}`:"";throw new Error(`HTTP ${n.status} ${n.statusText}${t}`)}const o=await n.text();return o?JSON.parse(o):{}}async ensureRemoteRegistration(){if(!this.backendUrl)return;const e=this.stateDb.getCheckpoint("last_register_at");e&&g()-Number(e)<600||(await this.postJson("/codex-usage/collectors/register",this.collectorRequestBody()),this.stateDb.setCheckpoint("last_register_at",String(g())))}async flushPendingBatches({failFast:e=!1,concurrency:t=5,onBatchUploaded:n}={}){if(!this.backendUrl)return{uploadedBatches:0,failedBatches:0,lastError:null};try{await this.ensureRemoteRegistration()}catch(t){if(e)throw new Error(`Collector registration failed: ${t instanceof Error?t.message:String(t)}`);return{uploadedBatches:0,failedBatches:1,lastError:t}}let o=0,s=0,i=null;const r=this.collectorRequestBody(),a=this.stateDb.iterPendingBatches(),l=async e=>{const t=this.sanitizeUploadPayload(JSON.parse(e.payload_json)),n={idempotencyKey:e.batch_key,collector:r,payloadSizeBytes:Number(e.payload_bytes),sessions:t.sessions??[],turns:t.turns??[],events:t.events??[]};await this.postJson("/codex-usage/upload",n)},c=new Set;let u=0;const d=()=>{for(;c.size<t&&u<a.length;){const t=a[u++],r=l(t).then(()=>{this.stateDb.markBatchUploaded(t.id),o+=1,n?.({row:t,uploadedBatches:o})}).catch(n=>{if(this.stateDb.markBatchFailed(t.id,Number(t.attempt_count)+1,n instanceof Error?n.message:String(n)),s+=1,i=n,e)throw n}).finally(()=>{c.delete(r)});c.add(r)}};for(d();c.size>0;){try{await Promise.race(c)}catch{if(e)break}d()}if(e&&i)throw new Error(`Upload batch failed: ${i instanceof Error?i.message:String(i)}`);return{uploadedBatches:o,failedBatches:s,lastError:i}}async runCycle({forceSeal:e=!1}={}){const t=await this.scanRollouts();t.batchesQueued+=this.stateDb.sealStaleBatches(e);const n=await this.flushPendingBatches();t.uploadedBatches=n.uploadedBatches,t.failedBatches=n.failedBatches;const o=this.getQueueStats();return{...t,...o,remainingQueuedBatches:o.bufferingBatchCount+o.pendingBatchCount+o.retryingBatchCount,remainingQueuedEvents:o.queuedEvents}}async runForegroundCatchUp({snapshot:e,onProgress:t}={}){const n=Date.now(),o=e??this.buildBackfillSnapshot(),s=o.length;let i;t?.({phase:"start",totalFiles:s,filesProcessed:0,eventsParsed:0,batchesQueued:0});try{i=await this.scanSnapshotEntries(o,{onFileProcessed:({entry:e,filesProcessed:n,totals:o})=>{t?.({phase:"file",file:e.progressPath??e.relpath,totalFiles:s,filesProcessed:n,eventsParsed:o.events,batchesQueued:o.batchesQueued})}})}catch(e){throw t?.({phase:"error",stage:"scan",message:e instanceof Error?e.message:String(e)}),e}i.batchesQueued+=this.stateDb.sealStaleBatches(!0),await this.ensureRemoteRegistration();const r=this.getQueueStats(),a={totalFiles:s,filesProcessed:s,eventsParsed:i.events,sessionsParsed:i.sessions,turnsParsed:i.turns,batchesQueued:i.batchesQueued,pendingBatches:r.pendingBatchCount+r.retryingBatchCount,pendingEvents:r.queuedEvents,durationMs:Date.now()-n};return t?.({phase:"done",...a}),a}async watch(){let e=!0;for(;;){const t=await this.runCycle({forceSeal:e});console.log(`[uploader] scanned_files=${t.filesScanned} sessions=${t.sessions} turns=${t.turns} events=${t.events} queued_batches=${t.batchesQueued} uploaded_batches=${t.uploadedBatches} remaining_batches=${t.remainingQueuedBatches}`),e=!1,await f(1e3*this.intervalSeconds)}}}const Y=t.basename(B);function J(e){const n=t.basename(e);if(!n.startsWith(`${Y}-`))return"";const o=n.slice(Y.length+1);return o?`-${o}`:""}function G(o=A){const s=function(e=A){const o=t.dirname(e),s=J(o),i=t.join(o,"app"),r=t.join(i,"current"),a=`${j}${s}`;return{configFile:e,installRoot:o,appRoot:i,currentAppDir:r,backendUrl:H,intervalSeconds:30,codexAuthPath:$,sessionsDir:D,stateDbPath:t.join(o,t.basename(C)),nodePath:process.execPath,entryFile:"",packageSpec:"",localBinDir:t.join(o,"bin"),localBinPath:t.join(o,"bin",L),homeBinLink:t.join(n.homedir(),"bin",`${L}${s}`),stdoutLogPath:t.join(o,"logs",t.basename(F)),stderrLogPath:t.join(o,"logs",t.basename(O)),launchdLabel:a,plistPath:t.join(o,"launchd",`${a}.plist`),launchAgentPath:t.join(t.dirname(M),`${a}.plist`),autoStartOnLogin:!0}}(o);try{const t=JSON.parse(e.readFileSync(o,"utf8"));return t&&"object"==typeof t?X({...s,...t,configFile:o}):s}catch{return s}}function X(e){const o=e.installRoot||B,s=J(o),i=e.appRoot||t.join(o,"app"),r=e.currentAppDir||t.join(i,"current"),a=e.localBinDir||t.join(o,"bin"),l=e.launchdLabel||`${j}${s}`,c=t.join(o,"launchd",`${l}.plist`),u=t.join(t.dirname(M),`${l}.plist`),d=t.join(t.dirname(M),`${l}.plist`),h=e.plistPath||c;return{...e,configFile:e.configFile||A,installRoot:o,appRoot:i,currentAppDir:r,backendUrl:e.backendUrl?.trim()?e.backendUrl.replace(/\/+$/,""):null,intervalSeconds:Number(e.intervalSeconds)||30,codexAuthPath:e.codexAuthPath||$,sessionsDir:e.sessionsDir||D,stateDbPath:e.stateDbPath||t.join(o,"state.sqlite"),nodePath:e.nodePath||process.execPath,entryFile:e.entryFile||"",packageSpec:e.packageSpec||"",localBinDir:a,localBinPath:e.localBinPath||t.join(a,L),homeBinLink:e.homeBinLink||t.join(n.homedir(),"bin",`${L}${s}`),stdoutLogPath:e.stdoutLogPath||t.join(o,"logs","stdout.log"),stderrLogPath:e.stderrLogPath||t.join(o,"logs","stderr.log"),launchdLabel:l,plistPath:h===d?c:h,launchAgentPath:e.launchAgentPath||u,autoStartOnLogin:void 0===e.autoStartOnLogin||Boolean(e.autoStartOnLogin)}}function V(n){const o=X(n);return e.mkdirSync(t.dirname(o.configFile),{recursive:!0}),e.mkdirSync(t.dirname(o.stdoutLogPath),{recursive:!0}),e.writeFileSync(o.configFile,`${y({backendUrl:o.backendUrl,intervalSeconds:o.intervalSeconds,codexAuthPath:o.codexAuthPath,sessionsDir:o.sessionsDir,stateDbPath:o.stateDbPath,installRoot:o.installRoot,appRoot:o.appRoot,currentAppDir:o.currentAppDir,nodePath:o.nodePath,entryFile:o.entryFile,packageSpec:o.packageSpec,localBinDir:o.localBinDir,localBinPath:o.localBinPath,homeBinLink:o.homeBinLink,stdoutLogPath:o.stdoutLogPath,stderrLogPath:o.stderrLogPath,launchdLabel:o.launchdLabel,plistPath:o.plistPath,launchAgentPath:o.launchAgentPath,autoStartOnLogin:o.autoStartOnLogin})}\n`),o}function K(e,t={}){return X({...G(e),...t,configFile:e})}function Z(t,n){return{configExists:e.existsSync(t.configFile),loaded:n.loaded,running:n.running,pid:n.pid,state:n.state,lastExitCode:n.lastExitCode,backendUrl:t.backendUrl,intervalSeconds:t.intervalSeconds,configFile:t.configFile,stateDbPath:t.stateDbPath,stdoutLogPath:t.stdoutLogPath,stderrLogPath:t.stderrLogPath,plistPath:t.plistPath,launchAgentPath:t.launchAgentPath,label:t.launchdLabel,autoStartOnLogin:t.autoStartOnLogin,onlineThresholdSeconds:600}}const ee="# Added by codex-usage-uploader",te=[t.join(n.homedir(),".zshrc"),t.join(n.homedir(),".bash_profile")];function ne(n=import.meta.url){let o=t.dirname(d(n));for(;;){const n=t.join(o,"package.json");if(e.existsSync(n))return o;const s=t.dirname(o);if(s===o)throw new Error("package.json not found from current runtime");o=s}}function oe(e){return String(e).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}class se{constructor(e){this.runtime=e}ensureMacos(){if("darwin"!==process.platform)throw new Error("launchd management is only supported on macOS.")}domainTarget(){return`gui/${process.getuid()}`}serviceTarget(){return`${this.domainTarget()}/${this.runtime.launchdLabel}`}run(e,{check:t=!0}={}){const n=u(e[0],e.slice(1),{encoding:"utf8"});if(t&&0!==n.status)throw new Error(n.stderr?.trim()||n.stdout?.trim()||`command failed: ${e.join(" ")}`);return n}ensurePlist(){if(this.ensureMacos(),!this.runtime.entryFile||!e.existsSync(this.runtime.entryFile))throw new Error(`installed runtime entry not found: ${this.runtime.entryFile||"(empty)"}`);const n=function(e){const t=[e.nodePath,e.entryFile,"--config-file",e.configFile,"run"];return`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n <dict>\n <key>Label</key>\n <string>${oe(e.launchdLabel)}</string>\n <key>ProgramArguments</key>\n <array>\n${t.map(e=>` <string>${oe(e)}</string>`).join("\n")}\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>ProcessType</key>\n <string>Background</string>\n <key>WorkingDirectory</key>\n <string>${oe(e.installRoot)}</string>\n <key>StandardOutPath</key>\n <string>${oe(e.stdoutLogPath)}</string>\n <key>StandardErrorPath</key>\n <string>${oe(e.stderrLogPath)}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>NODE_NO_WARNINGS</key>\n <string>1</string>\n </dict>\n </dict>\n</plist>\n`}(this.runtime);e.mkdirSync(t.dirname(this.runtime.plistPath),{recursive:!0}),e.mkdirSync(t.dirname(this.runtime.stdoutLogPath),{recursive:!0}),e.writeFileSync(this.runtime.plistPath,n),this.syncLaunchAgent(n)}syncLaunchAgent(n){const o=this.runtime.launchAgentPath;if(o)return this.runtime.autoStartOnLogin?(e.mkdirSync(t.dirname(o),{recursive:!0}),void e.writeFileSync(o,n)):void(e.existsSync(o)&&e.rmSync(o,{force:!0}))}start(){this.ensurePlist(),this.stop(),this.run(["launchctl","bootstrap",this.domainTarget(),this.runtime.plistPath])}stop(){this.ensureMacos(),this.run(["launchctl","bootout",this.serviceTarget()],{check:!1})}restart(){this.start()}status(){this.ensureMacos();const e=this.run(["launchctl","print",this.serviceTarget()],{check:!1});return 0!==e.status?{loaded:!1,running:!1,pid:null,state:null,lastExitCode:null}:function(e){const t=e.match(/^\s*state = ([^\n]+)$/m)?.[1]?.trim()??null,n=e.match(/^\s*pid = (\d+)$/m)?.[1]??null,o=e.match(/^\s*last exit code = (-?\d+)$/m)?.[1]??null;return{loaded:!0,running:"running"===t||null!=n,pid:n?Number(n):null,state:t,lastExitCode:o?Number(o):null}}(e.stdout)}uninstall(){if(this.stop(),e.existsSync(this.runtime.plistPath)&&e.unlinkSync(this.runtime.plistPath),this.runtime.launchAgentPath&&e.existsSync(this.runtime.launchAgentPath)&&e.unlinkSync(this.runtime.launchAgentPath),e.existsSync(this.runtime.homeBinLink)&&e.lstatSync(this.runtime.homeBinLink).isSymbolicLink())try{e.realpathSync.native(this.runtime.homeBinLink)===this.runtime.localBinPath&&e.unlinkSync(this.runtime.homeBinLink)}catch{e.unlinkSync(this.runtime.homeBinLink)}}}const ie=Object.freeze({collectorId:"local-usage"}),re=new Set(["today","7d","30d","all"]),ae=/^\d{4}-\d{2}-\d{2}$/;function le(){return Intl.DateTimeFormat().resolvedOptions().timeZone||"UTC"}function ce(){return{inputTokens:0,cachedInputTokens:0,outputTokens:0,reasoningOutputTokens:0,totalTokens:0}}function ue(e){return{model:e.model??"(unknown)",inputTokens:he(e.lastInputTokens),cachedInputTokens:he(e.lastCachedInputTokens),outputTokens:he(e.lastOutputTokens),reasoningOutputTokens:he(e.lastReasoningOutputTokens),totalTokens:he(e.lastTotalTokens)}}function de(e,t){e.inputTokens+=t.inputTokens,e.cachedInputTokens+=t.cachedInputTokens,e.outputTokens+=t.outputTokens,e.reasoningOutputTokens+=t.reasoningOutputTokens,e.totalTokens+=t.totalTokens}function he(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function pe(e,t){const n=new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(new Date(e)),o=Object.fromEntries(n.map(e=>[e.type,e.value]));return`${o.year}-${o.month}-${o.day}`}function me(e,t){if(!ae.test(String(e)))throw new Error(`${t} must be in YYYY-MM-DD format`);const[n,o,s]=String(e).split("-"),i=Number(n),r=Number(o),a=Number(s),l=new Date(Date.UTC(i,r-1,a));if(Number.isNaN(l.getTime())||l.getUTCFullYear()!==i||l.getUTCMonth()!==r-1||l.getUTCDate()!==a)throw new Error(`${t} must be a valid calendar date`);return`${n}-${o}-${s}`}function ge(e,t){const[n,o,s]=e.split("-").map(Number),i=new Date(Date.UTC(n,o-1,s+t));return`${i.getUTCFullYear()}-${String(i.getUTCMonth()+1).padStart(2,"0")}-${String(i.getUTCDate()).padStart(2,"0")}`}function fe(e,t,n){return!(t&&e<t||n&&e>n)}const ye=`${w}\n\nUsage:\n ${L} init [--backend-url <url>] [--interval 30] [--yes] [--package-spec <spec>]\n ${L} bind [--email <email>] [--employee-name <name>] [--employee-id <id>] [--yes]\n ${L} clear [--yes]\n ${L} start\n ${L} stop\n ${L} restart\n ${L} status\n ${L} usage [--period <today|7d|30d|all>] [--from <date>] [--to <date>]\n ${L} logs [--lines 100]\n ${L} uninstall\n\nCommon options:\n --config-file <path> Runtime config path. Default: ${A}\n --backend-url <url> Dashboard backend URL\n --interval <seconds> Scan interval in seconds\n --codex-auth <path> Codex auth.json path\n --sessions-dir <path> Codex rollout root directory\n --state-db <path> Local SQLite state DB path\n --employee-id <id> Bind employee ID\n --email <email> Bind employee email\n --employee-name <name> Bind employee name\n --device-id <id> Override device ID\n --hostname <name> Override hostname\n --period <value> Usage range: today, 7d, 30d, or all\n --from <date> Usage range start date (YYYY-MM-DD)\n --to <date> Usage range end date (YYYY-MM-DD)\n --yes Skip interactive prompts\n --package-spec <spec> npm install spec used by init\n --lines <n> Number of log lines for service logs\n -v, --version Show version\n -h, --help Show help\n`;class be extends Error{constructor(e){super(e),this.name="CliOperationalError",this.showUsage=!1}}const Se={findPackageRoot:ne,installCurrentPackage:function(n,{packageRoot:o,packageSpec:s,nodePath:i=process.execPath}={}){const r=function(e,t){return t||`file:${e}`}(o,s),a=n.appRoot,l=t.join(a,`.staging-${Date.now()}`);e.mkdirSync(l,{recursive:!0}),e.writeFileSync(t.join(l,"package.json"),`${JSON.stringify({private:!0,name:"codex-usage-uploader-runtime"},null,2)}\n`);const c="win32"===process.platform?"npm.cmd":"npm",d=u(c,["install","--no-save","--omit=dev","--no-package-lock",r],{cwd:l,stdio:"inherit",env:{...process.env,npm_config_fund:"false",npm_config_audit:"false"}});if(d.error)throw new Error(`failed to execute ${c}: ${d.error.message}`);if(0!==d.status)throw new Error(`npm install failed for ${r}`);const h=JSON.parse(e.readFileSync(t.join(o,"package.json"),"utf8")),p=t.join(l,"node_modules",...String(h.name).split("/")),m=JSON.parse(e.readFileSync(t.join(p,"package.json"),"utf8")).bin,g="string"==typeof m?m:m?.[L]??Object.values(m??{})[0];if(!g)throw new Error("installed package does not expose the expected CLI binary");const f=n.currentAppDir;return e.rmSync(f,{recursive:!0,force:!0}),e.mkdirSync(t.dirname(f),{recursive:!0}),e.renameSync(l,f),n.nodePath=i,n.packageSpec=r,n.entryFile=t.join(f,"node_modules",...String(h.name).split("/"),g),function(t){e.mkdirSync(t.localBinDir,{recursive:!0});const n=`#!/bin/sh\nset -eu\nCONFIG_FILE="\${CODEX_USAGE_UPLOADER_CONFIG:-${t.configFile}}"\nexec "${t.nodePath}" "${t.entryFile}" --config-file "$CONFIG_FILE" "$@"\n`;e.writeFileSync(t.localBinPath,n,{mode:493}),e.chmodSync(t.localBinPath,493)}(n),function(n){e.mkdirSync(t.dirname(n.homeBinLink),{recursive:!0});try{(e.existsSync(n.homeBinLink)||e.lstatSync(n.homeBinLink).isSymbolicLink())&&e.rmSync(n.homeBinLink,{force:!0})}catch{}e.symlinkSync(n.localBinPath,n.homeBinLink)}(n),V(n),n},ensurePathInShellConfigs:function(t){const n=`export PATH="${t}:$PATH"`;for(const t of te)try{if(!e.existsSync(t))continue;const o=e.readFileSync(t,"utf8");if(o.includes(ee))continue;const s=o.length>0&&!o.endsWith("\n")?"\n":"";e.appendFileSync(t,`${s}\n${ee}\n${n}\n`)}catch{}},removePathFromShellConfigs:function(){for(const t of te)try{if(!e.existsSync(t))continue;const n=e.readFileSync(t,"utf8");if(!n.includes(ee))continue;const o=n.split("\n"),s=[];for(let e=0;e<o.length;e++)o[e]!==ee?s.push(o[e]):(e+1<o.length&&o[e+1].startsWith("export PATH=")&&(e+=1),s.length>0&&""===s[s.length-1]&&s.pop());e.writeFileSync(t,s.join("\n"))}catch{}},promptConfirm:async function(e,t=!1){const n=t?"y":"n",o=await m(`${e} (${t?"Y/n":"y/N"})`,n);return/^(y|yes)$/i.test(String(o??"").trim())},createUploader:e=>new Q({sessionsDir:e.sessionsDir,stateDbPath:e.stateDbPath,backendUrl:e.backendUrl,intervalSeconds:e.intervalSeconds,codexAuthPath:e.codexAuthPath,persistentCollectorIdPath:e.persistentCollectorIdPath}),createServiceManager:e=>new se(e)};function Ee(e){const t={configFile:A,interval:void 0,yes:!1,lines:100},n=[];for(let o=0;o<e.length;o+=1){const s=e[o];if(!s.startsWith("-")){n.push(s);continue}if("-h"===s||"--help"===s){t.help=!0;continue}if("-v"===s||"--version"===s){t.version=!0;continue}if("--yes"===s){t.yes=!0;continue}const[i,r]=s.split("=",2);if(!new Set(["--config-file","--backend-url","--interval","--codex-auth","--sessions-dir","--state-db","--employee-id","--email","--employee-name","--device-id","--hostname","--period","--from","--to","--package-spec","--lines"]).has(i))throw new Error(`Unknown argument: ${s}`);const a=r??e[++o];if(null==a||a.startsWith("-"))throw new Error(`Missing value for ${i}`);ke(t,i,a)}return{command:n[0]??null,subcommand:n[1]??null,extraPositionals:n.slice(2),options:t}}function ke(e,t,n){switch(t){case"--config-file":e.configFile=n;break;case"--backend-url":e.backendUrl=n;break;case"--interval":e.interval=_e(n,"--interval");break;case"--codex-auth":e.codexAuthPath=n;break;case"--sessions-dir":e.sessionsDir=n;break;case"--state-db":e.stateDbPath=n;break;case"--employee-id":e.employeeId=n;break;case"--email":e.employeeEmail=n;break;case"--employee-name":e.employeeName=n;break;case"--device-id":e.deviceId=n;break;case"--hostname":e.hostname=n;break;case"--period":e.period=n;break;case"--from":e.from=n;break;case"--to":e.to=n;break;case"--package-spec":e.packageSpec=n;break;case"--lines":e.lines=_e(n,"--lines");break;default:throw new Error(`Unknown argument: ${t}`)}}function _e(e,t){const n=Number.parseInt(String(e),10);if(!Number.isFinite(n)||n<=0)throw new Error(`${t} must be a positive integer`);return n}function ve(e){const t={};return void 0!==e.backendUrl&&(t.backendUrl=e.backendUrl),void 0!==e.interval&&(t.intervalSeconds=e.interval),void 0!==e.codexAuthPath&&(t.codexAuthPath=e.codexAuthPath),void 0!==e.sessionsDir&&(t.sessionsDir=e.sessionsDir),void 0!==e.stateDbPath&&(t.stateDbPath=e.stateDbPath),t}function Te(e){const t={};return void 0!==e.employeeId&&(t.employeeId=e.employeeId),void 0!==e.employeeEmail&&(t.employeeEmail=e.employeeEmail),void 0!==e.employeeName&&(t.employeeName=e.employeeName),void 0!==e.deviceId&&(t.deviceId=e.deviceId),void 0!==e.hostname&&(t.hostname=e.hostname),t}function Pe(e){if(!p(e.identity))throw new be("No employee identity is bound yet. Run `codex-usage-uploader bind` or rerun `init` with --email.")}function we(e){console.log("Identity binding:"),console.log(` collectorId: ${e.collectorId}`),console.log(` employeeEmail: ${e.employeeEmail??"-"}`),console.log(` employeeName: ${e.employeeName??"-"}`),console.log(` employeeId: ${e.employeeId??"-"}`),console.log(` deviceId: ${e.deviceId??"-"}`),console.log(` hostname: ${e.hostname??"-"}`)}function Le(t,n){return e.existsSync(t)?e.readFileSync(t,"utf8").split(/\r?\n/).filter((e,t,n)=>!(t===n.length-1&&""===e)).slice(-n):[]}async function Ne(e,t){const n=Te(t);if(t.yes){const t=e.configureIdentityWithDefaults(n);return Pe(e),t}const o=await e.configureIdentityInteractive(n);return Pe(e),o}function Ie(e){switch(e.phase){case"start":return void console.log(`[scan] start files=${e.totalFiles}`);case"file":return void console.log(`[scan] file ${e.filesProcessed}/${e.totalFiles} current=${e.file} events=${e.eventsParsed}`);case"done":return void console.log(`[scan] complete files=${e.filesProcessed}/${e.totalFiles} events=${e.eventsParsed} pending_batches=${e.pendingBatches} duration=${t=e.durationMs,`${Math.max(0,Math.round(t/1e3))}s`}`);case"error":return void console.error(`[scan] failed stage=${e.stage} message=${e.message}`)}var t}function $e(e){return Number(e??0).toLocaleString("en-US")}function De(e){const[t,n,o]=e.split("-").map(Number);return new Intl.DateTimeFormat("en-US",{timeZone:"UTC",month:"short",day:"numeric",year:"numeric"}).format(new Date(Date.UTC(t,n-1,o)))}async function Be(n){const s=K(n.configFile,ve(n));let i;console.log("Scanning local Codex usage...");try{i=await async function({sessionsDir:n,period:s,from:i,to:r,now:a=Date.now(),timeZone:l=le(),includeArchived:c=!0}={}){const u=function({period:e,from:t,to:n,now:o=Date.now(),timeZone:s=le()}={}){if(e&&(t||n))throw new Error("--period cannot be combined with --from or --to");if(e&&!re.has(e))throw new Error("--period must be one of: today, 7d, 30d, all");const i=pe(o,s);if("today"===e)return{mode:"today",from:i,to:i,trendFrom:i,trendTo:i};if("7d"===e)return{mode:"7d",from:ge(i,-6),to:i,trendFrom:ge(i,-6),trendTo:i};if("30d"===e)return{mode:"30d",from:ge(i,-29),to:i,trendFrom:ge(i,-29),trendTo:i};if("all"===e||!e&&!t&&!n)return{mode:"all",from:null,to:null,trendFrom:ge(i,-29),trendTo:i};const r=null==t?null:me(t,"--from"),a=null==n?null:me(n,"--to");if(r&&a&&r>a)throw new Error("--from cannot be later than --to");return{mode:"custom",from:r,to:a,trendFrom:r,trendTo:a}}({period:s,from:i,to:r,now:a,timeZone:l}),d=t.join(t.dirname(n),I),h=[{sourceRoot:N,dir:n}];c&&h.push({sourceRoot:I,dir:d});const p=h.flatMap(n=>function(n){if(!e.existsSync(n))return[];const o=[],s=n=>{for(const i of e.readdirSync(n,{withFileTypes:!0})){const e=t.join(n,i.name);i.isDirectory()?s(e):i.isFile()&&/^rollout-.*\.jsonl$/.test(i.name)&&o.push(e)}};return s(n),o}(n.dir).map(e=>({...n,filePath:e,relpath:t.relative(n.dir,e).split(t.sep).join("/")})));p.sort((e,t)=>e.filePath.localeCompare(t.filePath));const m={inputTokens:0,cachedInputTokens:0,outputTokens:0,reasoningOutputTokens:0,totalTokens:0},g=new Map,f=new Map,y=new Map;let b=0;for(const t of p){const n=new T(ie,`${t.sourceRoot}/${t.relpath}`),s=e.createReadStream(t.filePath,{encoding:"utf8"}),i=o({input:s,crlfDelay:1/0});let r=0;try{for await(const e of i){if(r+=1,!e)continue;const t=n.processLine(r,e);for(const e of t.events){const t=pe(e.timestamp,l);if(!fe(t,u.from,u.to))continue;const n=ue(e);b+=1,de(m,n);const o=g.get(t)??{date:t,eventCount:0,models:new Set,...ce()};o.eventCount+=1,o.models.add(n.model),de(o,n),g.set(t,o);const s=y.get(n.model)??{model:n.model,eventCount:0,...ce()};if(s.eventCount+=1,de(s,n),y.set(n.model,s),fe(t,u.trendFrom,u.trendTo)){const e=f.get(t)??{date:t,eventCount:0,...ce()};e.eventCount+=1,de(e,n),f.set(t,e)}}}}finally{i.close()}}const S=[...g.values()].sort((e,t)=>e.date.localeCompare(t.date)).map(e=>({...e,models:[...e.models].sort((e,t)=>e.localeCompare(t))})),E=function(e,t){const n=[...t.values()].sort((e,t)=>e.date.localeCompare(t.date));if(0===n.length)return[];if(!e.trendFrom||!e.trendTo||e.trendFrom>e.trendTo)return n;const o=[];for(let n=e.trendFrom;n<=e.trendTo;n=ge(n,1))o.push(t.get(n)??{date:n,eventCount:0,...ce()});return o}(u,f),k=[...y.values()].sort((e,t)=>t.totalTokens!==e.totalTokens?t.totalTokens-e.totalTokens:e.model.localeCompare(t.model));return{scope:"local-machine",timezone:l,range:u,sources:{sessionsDir:n,archivedSessionsDir:d,includeArchived:c},filesScanned:p.length,tokenEventCount:b,summary:m,days:S,trend:E,byModel:k}}({sessionsDir:s.sessionsDir,period:n.period,from:n.from,to:n.to,timeZone:le()})}catch(e){throw function(e){return new be(`Usage query failed: ${e instanceof Error?e.message:String(e)}`)}(e)}!function(e){if(0===e.tokenEventCount)return void console.log("No local Codex usage found for selected range.");const t=e.days.map(e=>[De(e.date),e.models.join(", "),$e(e.inputTokens),$e(e.outputTokens),$e(e.reasoningOutputTokens),$e(e.cachedInputTokens),$e(e.totalTokens)]);t.push(["Total","",$e(e.summary.inputTokens),$e(e.summary.outputTokens),$e(e.summary.reasoningOutputTokens),$e(e.summary.cachedInputTokens),$e(e.summary.totalTokens)]),console.log(function(e,t,n=[]){const o=e.map((e,n)=>Math.max(e.length,...t.map(e=>String(e[n]??"").length))),s=e=>e.map((e,t)=>((e,t)=>{const s=String(e??"");return"right"===n[t]?s.padStart(o[t]):s.padEnd(o[t])})(e,t)).join(" ").trimEnd();return[s(e),...t.map(e=>s(e))].join("\n")}(["Date","Models","Input","Output","Reasoning","Cache Read","Total Tokens"],t,["left","left","right","right","right","right","right"]))}(i)}function Ae(t){if(!e.existsSync(t))return{collectorId:null,bufferingBatchCount:0,pendingBatchCount:0,retryingBatchCount:0,queuedSessions:0,queuedTurns:0,queuedEvents:0,oldestPendingAgeSeconds:null};const n=new W(t);try{return{collectorId:n.getIdentity().collectorId??null,...n.getQueueStats()}}finally{n.close()}}function Ce(t){if(!e.existsSync(t.configFile)||!t.entryFile)throw new be(`Uploader is not initialized yet. Run \`${L} init\` first.`)}async function Re(n=process.argv.slice(2)){try{const{command:o,subcommand:s,extraPositionals:i,options:r}=Ee(n);if(r.version)return console.log(function(n=import.meta.url){const o=ne(n);return JSON.parse(e.readFileSync(t.join(o,"package.json"),"utf8")).version??"unknown"}()),0;if(!o||r.help)return console.log(ye),0;if(!new Set(["init","bind","clear","start","stop","restart","status","usage","logs","uninstall","run"]).has(o))throw new Error(`Unknown command: ${o}`);switch(function(e,t,n){if(t)throw new Error(`Unexpected subcommand for ${e}: ${t}`);if(n.length>0)throw new Error(`Unexpected extra arguments: ${n.join(" ")}`)}(o,s,i),o){case"init":return await async function(e){const n=ve(e);n.backendUrl||(n.backendUrl=H);let o=K(e.configFile,n);if(!o.backendUrl)throw new Error("--backend-url is required for init unless already configured in config.json");o=Se.installCurrentPackage(o,{packageRoot:Se.findPackageRoot(),packageSpec:e.packageSpec,nodePath:process.execPath});let s=Se.createServiceManager(o);s.stop();const i=Se.createUploader(o);try{let n,r;n=!Object.values(Te(e)).some(e=>void 0!==e)&&p(i.identity)?i.identity:await Ne(i,e),console.log(`${w} install is ready.`),console.log(`Backend: ${o.backendUrl}`),console.log(`Install root: ${o.installRoot}`),console.log(`Config file: ${o.configFile}`),we(n),console.log("Scanning local Codex sessions...");try{r=await i.runForegroundCatchUp({onProgress:Ie})}catch(e){throw function(e){return new be(`Foreground catch-up failed: ${e instanceof Error?e.message:String(e)}`)}(e)}o.autoStartOnLogin=await async function(e,t){return t.yes?e.autoStartOnLogin:Se.promptConfirm("Start automatically when you log in on this Mac?",e.autoStartOnLogin)}(o,e),o=V(o),s=Se.createServiceManager(o),s.start(),console.log(`${w} initialized and started.`),console.log(o.autoStartOnLogin?"Login auto-start is enabled.":"Login auto-start is disabled for future logins."),r.pendingBatches>0&&console.log(`Background service is uploading ${r.pendingBatches} batch(es) with ${r.pendingEvents} event(s).`),console.log(`Use \`${L} status\` to check the local service.`);const a=t.dirname(o.homeBinLink);(process.env.PATH||"").split(t.delimiter).includes(a)||(Se.ensurePathInShellConfigs(a),console.log(`Open a new terminal to use \`${L}\` directly.`))}finally{i.close()}}(r),0;case"bind":return await async function(t){const n=K(t.configFile,ve(t)),o=Se.createUploader(n);try{we(await Ne(o,t)),await async function(t){if("darwin"!==process.platform)return!1;if(!e.existsSync(t.configFile)||!t.entryFile)return!1;const n=Se.createServiceManager(t);let o;try{o=n.status()}catch{return!1}return!!o.running&&(n.restart(),!0)}(n)&&console.log("Background service restarted to apply the updated identity.")}finally{o.close()}}(r),0;case"clear":return await async function(e){const t=K(e.configFile,ve(e));if(Ce(t),Se.createServiceManager(t).status().running)throw new be(`Background service is still running. Stop it first with \`${L} stop\`.`);if(!e.yes&&!await Se.promptConfirm("Clear local backfill state and queued uploads?",!1))return void console.log("Clear cancelled.");const n=Se.createUploader(t);try{n.resetBackfillState()}finally{n.close()}console.log("Local backfill state has been cleared."),console.log(`Run \`${L} init\` when you want to backfill history again.`)}(r),0;case"usage":return await Be(r),0;case"run":return await async function(e){const t=K(e.configFile,ve(e)),n=Se.createUploader(t);try{Pe(n),console.log(`[run] backend=${t.backendUrl??"-"} interval=${t.intervalSeconds}s sessions_dir=${t.sessionsDir}`),await n.watch()}finally{n.close()}}(r),0;case"start":case"stop":case"restart":case"status":case"logs":case"uninstall":return function(t,n){const o=K(n.configFile,ve(n)),s=Se.createServiceManager(o);switch(t){case"start":return Ce(o),s.start(),void console.log(`Service started: ${o.launchdLabel}`);case"stop":return s.stop(),void console.log(`Service stopped: ${o.launchdLabel}`);case"restart":return Ce(o),s.restart(),void console.log(`Service restarted: ${o.launchdLabel}`);case"status":return void function(e,t){const n={...Z(e,t),...Ae(e.stateDbPath)},o=[["Config exists",n.configExists?"yes":"no"],["Loaded",n.loaded?"yes":"no"],["Running",n.running?"yes":"no"],["Auto start on login",n.autoStartOnLogin?"yes":"no"],["PID",n.pid??"-"],["State",n.state??"-"],["Last exit code",n.lastExitCode??"-"],["Collector ID",n.collectorId??"-"],["Upload URL",n.backendUrl?`${n.backendUrl}/codex-usage/upload`:"-"],["Register URL",n.backendUrl?`${n.backendUrl}/codex-usage/collectors/register`:"-"],["Interval",`${n.intervalSeconds}s`],["Buffering batches",n.bufferingBatchCount],["Pending batches",n.pendingBatchCount],["Retrying batches",n.retryingBatchCount],["Queued sessions",n.queuedSessions],["Queued turns",n.queuedTurns],["Queued events",n.queuedEvents],["Oldest pending age",null==n.oldestPendingAgeSeconds?"-":`${n.oldestPendingAgeSeconds}s`],["Config file",n.configFile],["State DB",n.stateDbPath],["Stdout log",n.stdoutLogPath],["Stderr log",n.stderrLogPath],["Service plist",n.plistPath],["LaunchAgent plist",n.launchAgentPath],["Label",n.label]];for(const[e,t]of o)console.log(`${e}: ${t}`)}(o,s.status());case"logs":{const e=Le(o.stdoutLogPath,n.lines),t=Le(o.stderrLogPath,n.lines);return console.log(`== stdout (${o.stdoutLogPath}) ==`),console.log(e.length?e.join("\n"):"(empty)"),console.log(`== stderr (${o.stderrLogPath}) ==`),void console.log(t.length?t.join("\n"):"(empty)")}case"uninstall":return s.uninstall(),Se.removePathFromShellConfigs(),e.rmSync(o.installRoot,{recursive:!0,force:!0}),void console.log(`${w} uninstalled from ${o.installRoot}`);default:throw new Error(`Unknown lifecycle command: ${t??"(empty)"}`)}}(o,r),0}}catch(e){return console.error(e instanceof Error?e.message:String(e)),!1!==e?.showUsage&&console.error(`Run \`${L} --help\` for usage.`),1}}export{Se as cliDeps,Re as main,Ee as parseCliArgs};
|