@token-dashboard/codex-usage-uploader 0.1.7 → 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.
@@ -0,0 +1,35 @@
1
+ # 版本变更记录
2
+
3
+ 本文档记录 `@token-dashboard/codex-usage-uploader` 的重要变更。
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
+
13
+ ## 0.1.8 - 2026-05-20
14
+
15
+ ### 修复
16
+
17
+ - 后台服务新增扫描 `~/.codex/archived_sessions`,兼容新版 Codex App 将 rollout 文件移动到归档目录后导致的漏采问题。
18
+ - 将 rollout 相对路径统一规范为 `YYYY/MM/DD/rollout-...jsonl`,同时兼容嵌套目录和平铺归档目录两种布局。
19
+ - 归档文件被发现时,会复用已有的 `sessions` checkpoint 和旧版平铺 `archived_sessions` checkpoint,避免升级或文件移动后重复采集。
20
+ - `approval_policy` 现在支持对象格式,会在上传前稳定序列化为 JSON 字符串,兼容新版 Codex App 的字段结构。
21
+ - 后端也增加了 `approvalPolicy` 归一化,确保旧的待上传队列里如果已经存在对象格式 payload,也能被安全接收。
22
+
23
+ ### 行为说明
24
+
25
+ - 升级后不需要手动补数据。collector 会自动发现之前未上报的归档 rollout 文件,并且只上传缺失的新增行。
26
+ - 补报数据会按 rollout 事件自身的时间戳归属到原始发生日期,并按 Asia/Shanghai 计算 `event_date`;不会全部计入补报当天。
27
+ - 上传批次的 `received_at` 和数据库行的 `created_at` 仍然表示后端实际收到数据的时间,这是接收时间,不影响用量日期归属。
28
+
29
+ ### 测试
30
+
31
+ - 增加后台扫描 `archived_sessions` 的回归测试。
32
+ - 增加从 `sessions` 移动到 `archived_sessions` 后复用 checkpoint 的测试。
33
+ - 增加旧版平铺归档 checkpoint 升级兼容测试。
34
+ - 增加 parser、待上传队列、后端 DTO 和 service 对对象格式 `approval_policy` 的测试覆盖。
35
+ - 发布前已验证 collector 测试、collector 构建、后端构建、后端单元测试和后端 e2e smoke 测试。
package/README.md CHANGED
@@ -1,128 +1,10 @@
1
- # @token-dashboard/codex-usage-uploader
1
+ # codex-usage-uploader removed
2
2
 
3
- 自动采集本机 [Codex CLI](https://github.com/openai/codex) token 用量数据,并上报至 Token Data Dashboard 后端,用于团队用量统计与成本分析。
3
+ This package is deprecated and no longer provides a standalone uploader.
4
4
 
5
- - 后台常驻运行,增量扫描本地 Codex session 数据
6
- - 首次安装自动补采全量历史
7
- - macOS launchd 托管,登录自启动可选
8
-
9
- > 仅支持 macOS,需要 Node.js >= 22.13.0。
10
-
11
- ## 快速开始
12
-
13
- 一行命令完成安装、身份绑定、历史补采和后台服务启动:
14
-
15
- ```bash
16
- npx @token-dashboard/codex-usage-uploader init
17
- ```
18
-
19
- 安装过程中会自动读取 Codex 登录态(`~/.codex/auth.json`)获取身份信息。如果读取不到邮箱,会在终端交互式提示你手动填写;同时会询问是否在这台 Mac 上启用登录自启动。
20
-
21
- 安装完成后,`codex-usage-uploader` 命令会被写入 `~/bin/`,后续可直接使用。
22
-
23
- ## 命令一览
24
-
25
- ### 初始化 / 升级
26
-
27
- ```bash
28
- codex-usage-uploader init
29
- ```
30
-
31
- 首次运行时完成安装与配置;重复运行会升级本地版本并重启后台服务,同时可重新选择是否启用登录自启动。后端地址默认为 `http://101.126.66.51:8086`,如需自定义可通过 `--backend-url` 指定。
32
-
33
- ### 绑定身份
34
-
35
- ```bash
36
- codex-usage-uploader bind
37
- codex-usage-uploader bind --email you@company.com --yes
38
- ```
39
-
40
- 更新上报身份。如果后台服务正在运行,会自动重启以应用新身份。
41
-
42
- ### 后台服务管理
43
-
44
- | 命令 | 说明 |
45
- | --------------------------------------- | ------------------------------- |
46
- | `codex-usage-uploader start` | 启动后台服务 |
47
- | `codex-usage-uploader stop` | 停止后台服务 |
48
- | `codex-usage-uploader restart` | 重启后台服务 |
49
- | `codex-usage-uploader status` | 查看服务状态与本地队列 |
50
- | `codex-usage-uploader logs` | 查看服务日志(默认最近 100 行) |
51
- | `codex-usage-uploader logs --lines 500` | 查看更多日志 |
52
-
53
- ### 查看本机 token 用量
5
+ Use the unified uploader instead:
54
6
 
55
7
  ```bash
56
- codex-usage-uploader usage
57
- codex-usage-uploader usage --period today
58
- codex-usage-uploader usage --period 7d
59
- codex-usage-uploader usage --from 2026-04-01 --to 2026-04-17
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
60
10
  ```
61
-
62
- `usage` 是一个纯本地只读命令,不依赖后端查询,也不要求先执行 `init`。它会重新扫描当前机器上的 Codex rollout 文件,默认同时包含:
63
-
64
- - `~/.codex/sessions`
65
- - 同级的 `~/.codex/archived_sessions`
66
-
67
- 默认输出是一张纯文本表格,列为 `Date`、`Models`、`Input`、`Output`、`Reasoning`、`Cache Read`、`Total Tokens`,最后一行固定为 `Total`。默认 `--period=all` 时,表格覆盖全历史所有有 token 使用的日期。
68
-
69
- 统计口径与现有 uploader/后端保持一致,使用 `token_count` 事件中的 `last_*` token 字段,而不是累计 `total_*` 字段。切天时区使用当前机器时区。
70
-
71
- ### 清除本地状态
72
-
73
- ```bash
74
- codex-usage-uploader clear --yes
75
- ```
76
-
77
- 清空本地补采进度和待上传队列,不影响已绑定的身份。服务端清库后需要重新补历史时使用:
78
-
79
- ```bash
80
- codex-usage-uploader stop
81
- codex-usage-uploader clear --yes
82
- codex-usage-uploader init
83
- ```
84
-
85
- ### 卸载
86
-
87
- ```bash
88
- codex-usage-uploader uninstall
89
- ```
90
-
91
- 停止后台服务并删除本地安装目录。
92
-
93
- ## 常用选项
94
-
95
- | 选项 | 说明 |
96
- | ------------------------ | ------------------------------------------------------ |
97
- | `--backend-url <url>` | Dashboard 后端地址(默认 `http://101.126.66.51:8086`) |
98
- | `--email <email>` | 绑定邮箱 |
99
- | `--employee-name <name>` | 绑定姓名 |
100
- | `--employee-id <id>` | 绑定工号 |
101
- | `--interval <seconds>` | 扫描间隔秒数(默认 30) |
102
- | `--period <value>` | `usage` 时间范围:`today`、`7d`、`30d`、`all` |
103
- | `--from <date>` | `usage` 开始日期(`YYYY-MM-DD`) |
104
- | `--to <date>` | `usage` 结束日期(`YYYY-MM-DD`) |
105
- | `--yes` | 跳过所有交互确认 |
106
- | `--lines <n>` | `logs` 命令输出行数(默认 100) |
107
- | `-h, --help` | 显示帮助 |
108
-
109
- ## 工作原理
110
-
111
- 1. **init** 将包安装到 `~/.codex-usage-uploader/`,绑定身份后前台执行一次全量历史补采,完成后启动 macOS launchd 后台服务,并按交互选择决定是否启用登录自启动。
112
- 2. 后台服务每 30 秒(可通过 `--interval` 调整)增量扫描 `~/.codex/sessions/` 下的 rollout 文件。
113
- 3. 扫描到的 token 用量数据暂存在本地 SQLite 队列中,满足条件后自动封批上传至后端。
114
- 4. `usage` 命令会按需重扫本地 `sessions + archived_sessions`,直接在终端计算并展示本机 token 用量,不写入本地状态库,也不访问后端。
115
- 5. 所有数据在上报前已做去敏处理,仅包含 session 元信息、turn 上下文摘要和 token 计数。
116
-
117
- ## 本地文件
118
-
119
- | 路径 | 说明 |
120
- | -------------------------------------- | -------------- |
121
- | `~/.codex-usage-uploader/config.json` | 运行配置 |
122
- | `~/.codex-usage-uploader/state.sqlite` | 本地状态数据库 |
123
- | `~/.codex-usage-uploader/logs/` | 服务日志 |
124
- | `~/.codex-usage-uploader/launchd/` | launchd service plist |
125
-
126
- ## License
127
-
128
- MIT
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- if(!process.env.NODE_NO_WARNINGS){process.env.NODE_NO_WARNINGS='1'}
3
- process.emitWarning=()=>{};
4
- const{main}=await import('../cli.mjs');
5
- process.exit(await main(process.argv.slice(2)));
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,27 +1,20 @@
1
1
  {
2
2
  "name": "@token-dashboard/codex-usage-uploader",
3
- "version": "0.1.7",
4
- "description": "Codex 用量上报 CLI",
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"
8
8
  },
9
9
  "files": [
10
10
  "dist/",
11
- "README.md"
11
+ "README.md",
12
+ "CHANGELOG.zh-CN.md"
12
13
  ],
13
14
  "scripts": {
14
15
  "build": "node scripts/build.mjs",
15
16
  "prepublishOnly": "npm run build",
16
- "init": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json init --backend-url http://localhost:8086",
17
- "start": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json start",
18
- "stop": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json stop",
19
- "restart": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json restart",
20
- "status": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json status",
21
- "usage": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json usage",
22
- "logs": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json logs",
23
- "clear": "node ./bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json clear",
24
- "uninstall": "node bin/codex-usage-uploader.js --config-file $HOME/.codex-usage-uploader-dev/config.json uninstall"
17
+ "test": "npm run build"
25
18
  },
26
19
  "engines": {
27
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(b(e))}function b(e){return Array.isArray(e)?e.map(b):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,b(e[t])])):e}function S(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(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 k(e,t,n){const o=a.from(`${e}|${t}|${n}`,"utf8");return l("sha1").update(o).digest("hex")}class _{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:S(e.timestamp),cwd:n,originator:e.originator??null,source:e.source??null,cliVersion:e.cli_version??null,modelProvider:e.model_provider??null,repoName:E(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:e.approval_policy??null,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:k(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:T(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=S(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 T(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 v="Codex 用量上报",P="codex-usage-uploader",w="sessions",L="archived_sessions",N=t.join(n.homedir(),".codex","auth.json"),I=t.join(n.homedir(),".codex","sessions"),$=t.join(n.homedir(),".codex-usage-uploader"),D=t.join($,"config.json"),B=t.join($,"state.sqlite"),C=t.join($,"logs"),A=t.join(C,"stdout.log"),R=t.join(C,"stderr.log"),F=t.join($,"app");t.join(F,"current");const O=t.join($,"bin");t.join(O,P),t.join(n.homedir(),"bin",P);const x="com.token-dashboard.codex-usage-uploader";t.join($,"launchd",`${x}.plist`);const U=t.join(n.homedir(),"Library","LaunchAgents",`${x}.plist`),j=t.join(n.homedir(),".codex-usage-uploader-collector-id"),M="http://101.126.66.51:8086",z=1e6;class H{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)>=z||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=j,scanChunkMaxEvents:r=50,scanChunkMaxBytes:a=262144}){this.sessionsDir=e,this.stateDb=new H(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),L)}getScanRoots({includeArchived:e=!1}={}){const t=[{sourceRoot:w,dir:this.sessionsDir}];return e&&t.push({sourceRoot:L,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=e.statSync(n,{bigint:!0}),a=this.stateDb.getFileState(s.sourceRoot,i),l=a||s.sourceRoot!==L?null:this.stateDb.getFileState(w,i);a&&a.file_size===r.size&&a.file_mtime_ns===r.mtimeNs||o.push({filePath:n,relpath:i,sourceRoot:s.sourceRoot,progressPath:`${s.sourceRoot}/${i}`,snapshotSize:r.size,snapshotMtimeNs:r.mtimeNs,fallbackState:l})}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)>z)}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 _(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())}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:[],events:Array.isArray(e.events)?e.events.map(e=>({...e,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 Q=t.basename($);function W(e){const n=t.basename(e);if(!n.startsWith(`${Q}-`))return"";const o=n.slice(Q.length+1);return o?`-${o}`:""}function Y(o=D){const s=function(e=D){const o=t.dirname(e),s=W(o),i=t.join(o,"app"),r=t.join(i,"current"),a=`${x}${s}`;return{configFile:e,installRoot:o,appRoot:i,currentAppDir:r,backendUrl:M,intervalSeconds:30,codexAuthPath:N,sessionsDir:I,stateDbPath:t.join(o,t.basename(B)),nodePath:process.execPath,entryFile:"",packageSpec:"",localBinDir:t.join(o,"bin"),localBinPath:t.join(o,"bin",P),homeBinLink:t.join(n.homedir(),"bin",`${P}${s}`),stdoutLogPath:t.join(o,"logs",t.basename(A)),stderrLogPath:t.join(o,"logs",t.basename(R)),launchdLabel:a,plistPath:t.join(o,"launchd",`${a}.plist`),launchAgentPath:t.join(t.dirname(U),`${a}.plist`),autoStartOnLogin:!0}}(o);try{const t=JSON.parse(e.readFileSync(o,"utf8"));return t&&"object"==typeof t?J({...s,...t,configFile:o}):s}catch{return s}}function J(e){const o=e.installRoot||$,s=W(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||`${x}${s}`,c=t.join(o,"launchd",`${l}.plist`),u=t.join(t.dirname(U),`${l}.plist`),d=t.join(t.dirname(U),`${l}.plist`),h=e.plistPath||c;return{...e,configFile:e.configFile||D,installRoot:o,appRoot:i,currentAppDir:r,backendUrl:e.backendUrl?.trim()?e.backendUrl.replace(/\/+$/,""):null,intervalSeconds:Number(e.intervalSeconds)||30,codexAuthPath:e.codexAuthPath||N,sessionsDir:e.sessionsDir||I,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,P),homeBinLink:e.homeBinLink||t.join(n.homedir(),"bin",`${P}${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 G(n){const o=J(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 X(e,t={}){return J({...Y(e),...t,configFile:e})}function V(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 K="# Added by codex-usage-uploader",Z=[t.join(n.homedir(),".zshrc"),t.join(n.homedir(),".bash_profile")];function ee(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 te(e){return String(e).replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}class ne{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>${te(e.launchdLabel)}</string>\n <key>ProgramArguments</key>\n <array>\n${t.map(e=>` <string>${te(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>${te(e.installRoot)}</string>\n <key>StandardOutPath</key>\n <string>${te(e.stdoutLogPath)}</string>\n <key>StandardErrorPath</key>\n <string>${te(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 oe=Object.freeze({collectorId:"local-usage"}),se=new Set(["today","7d","30d","all"]),ie=/^\d{4}-\d{2}-\d{2}$/;function re(){return Intl.DateTimeFormat().resolvedOptions().timeZone||"UTC"}function ae(){return{inputTokens:0,cachedInputTokens:0,outputTokens:0,reasoningOutputTokens:0,totalTokens:0}}function le(e){return{model:e.model??"(unknown)",inputTokens:ue(e.lastInputTokens),cachedInputTokens:ue(e.lastCachedInputTokens),outputTokens:ue(e.lastOutputTokens),reasoningOutputTokens:ue(e.lastReasoningOutputTokens),totalTokens:ue(e.lastTotalTokens)}}function ce(e,t){e.inputTokens+=t.inputTokens,e.cachedInputTokens+=t.cachedInputTokens,e.outputTokens+=t.outputTokens,e.reasoningOutputTokens+=t.reasoningOutputTokens,e.totalTokens+=t.totalTokens}function ue(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function de(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 he(e,t){if(!ie.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 pe(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 me(e,t,n){return!(t&&e<t||n&&e>n)}const ge=`${v}\n\nUsage:\n ${P} init [--backend-url <url>] [--interval 30] [--yes] [--package-spec <spec>]\n ${P} bind [--email <email>] [--employee-name <name>] [--employee-id <id>] [--yes]\n ${P} clear [--yes]\n ${P} start\n ${P} stop\n ${P} restart\n ${P} status\n ${P} usage [--period <today|7d|30d|all>] [--from <date>] [--to <date>]\n ${P} logs [--lines 100]\n ${P} uninstall\n\nCommon options:\n --config-file <path> Runtime config path. Default: ${D}\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 fe extends Error{constructor(e){super(e),this.name="CliOperationalError",this.showUsage=!1}}const ye={findPackageRoot:ee,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?.[P]??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),G(n),n},ensurePathInShellConfigs:function(t){const n=`export PATH="${t}:$PATH"`;for(const t of Z)try{if(!e.existsSync(t))continue;const o=e.readFileSync(t,"utf8");if(o.includes(K))continue;const s=o.length>0&&!o.endsWith("\n")?"\n":"";e.appendFileSync(t,`${s}\n${K}\n${n}\n`)}catch{}},removePathFromShellConfigs:function(){for(const t of Z)try{if(!e.existsSync(t))continue;const n=e.readFileSync(t,"utf8");if(!n.includes(K))continue;const o=n.split("\n"),s=[];for(let e=0;e<o.length;e++)o[e]!==K?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 ne(e)};function be(e){const t={configFile:D,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}`);Se(t,i,a)}return{command:n[0]??null,subcommand:n[1]??null,extraPositionals:n.slice(2),options:t}}function Se(e,t,n){switch(t){case"--config-file":e.configFile=n;break;case"--backend-url":e.backendUrl=n;break;case"--interval":e.interval=Ee(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=Ee(n,"--lines");break;default:throw new Error(`Unknown argument: ${t}`)}}function Ee(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 ke(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 _e(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 Te(e){if(!p(e.identity))throw new fe("No employee identity is bound yet. Run `codex-usage-uploader bind` or rerun `init` with --email.")}function ve(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 Pe(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 we(e,t){const n=_e(t);if(t.yes){const t=e.configureIdentityWithDefaults(n);return Te(e),t}const o=await e.configureIdentityInteractive(n);return Te(e),o}function Le(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 Ne(e){return Number(e??0).toLocaleString("en-US")}function Ie(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 $e(n){const s=X(n.configFile,ke(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=re(),includeArchived:c=!0}={}){const u=function({period:e,from:t,to:n,now:o=Date.now(),timeZone:s=re()}={}){if(e&&(t||n))throw new Error("--period cannot be combined with --from or --to");if(e&&!se.has(e))throw new Error("--period must be one of: today, 7d, 30d, all");const i=de(o,s);if("today"===e)return{mode:"today",from:i,to:i,trendFrom:i,trendTo:i};if("7d"===e)return{mode:"7d",from:pe(i,-6),to:i,trendFrom:pe(i,-6),trendTo:i};if("30d"===e)return{mode:"30d",from:pe(i,-29),to:i,trendFrom:pe(i,-29),trendTo:i};if("all"===e||!e&&!t&&!n)return{mode:"all",from:null,to:null,trendFrom:pe(i,-29),trendTo:i};const r=null==t?null:he(t,"--from"),a=null==n?null:he(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),L),h=[{sourceRoot:w,dir:n}];c&&h.push({sourceRoot:L,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 _(oe,`${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=de(e.timestamp,l);if(!me(t,u.from,u.to))continue;const n=le(e);b+=1,ce(m,n);const o=g.get(t)??{date:t,eventCount:0,models:new Set,...ae()};o.eventCount+=1,o.models.add(n.model),ce(o,n),g.set(t,o);const s=y.get(n.model)??{model:n.model,eventCount:0,...ae()};if(s.eventCount+=1,ce(s,n),y.set(n.model,s),me(t,u.trendFrom,u.trendTo)){const e=f.get(t)??{date:t,eventCount:0,...ae()};e.eventCount+=1,ce(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=pe(n,1))o.push(t.get(n)??{date:n,eventCount:0,...ae()});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:re()})}catch(e){throw function(e){return new fe(`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=>[Ie(e.date),e.models.join(", "),Ne(e.inputTokens),Ne(e.outputTokens),Ne(e.reasoningOutputTokens),Ne(e.cachedInputTokens),Ne(e.totalTokens)]);t.push(["Total","",Ne(e.summary.inputTokens),Ne(e.summary.outputTokens),Ne(e.summary.reasoningOutputTokens),Ne(e.summary.cachedInputTokens),Ne(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 De(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 H(t);try{return{collectorId:n.getIdentity().collectorId??null,...n.getQueueStats()}}finally{n.close()}}function Be(t){if(!e.existsSync(t.configFile)||!t.entryFile)throw new fe(`Uploader is not initialized yet. Run \`${P} init\` first.`)}async function Ce(n=process.argv.slice(2)){try{const{command:o,subcommand:s,extraPositionals:i,options:r}=be(n);if(r.version)return console.log(function(n=import.meta.url){const o=ee(n);return JSON.parse(e.readFileSync(t.join(o,"package.json"),"utf8")).version??"unknown"}()),0;if(!o||r.help)return console.log(ge),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=ke(e);n.backendUrl||(n.backendUrl=M);let o=X(e.configFile,n);if(!o.backendUrl)throw new Error("--backend-url is required for init unless already configured in config.json");o=ye.installCurrentPackage(o,{packageRoot:ye.findPackageRoot(),packageSpec:e.packageSpec,nodePath:process.execPath});let s=ye.createServiceManager(o);s.stop();const i=ye.createUploader(o);try{let n,r;n=!Object.values(_e(e)).some(e=>void 0!==e)&&p(i.identity)?i.identity:await we(i,e),console.log(`${v} install is ready.`),console.log(`Backend: ${o.backendUrl}`),console.log(`Install root: ${o.installRoot}`),console.log(`Config file: ${o.configFile}`),ve(n),console.log("Scanning local Codex sessions...");try{r=await i.runForegroundCatchUp({onProgress:Le})}catch(e){throw function(e){return new fe(`Foreground catch-up failed: ${e instanceof Error?e.message:String(e)}`)}(e)}o.autoStartOnLogin=await async function(e,t){return t.yes?e.autoStartOnLogin:ye.promptConfirm("Start automatically when you log in on this Mac?",e.autoStartOnLogin)}(o,e),o=G(o),s=ye.createServiceManager(o),s.start(),console.log(`${v} 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 \`${P} status\` to check the local service.`);const a=t.dirname(o.homeBinLink);(process.env.PATH||"").split(t.delimiter).includes(a)||(ye.ensurePathInShellConfigs(a),console.log(`Open a new terminal to use \`${P}\` directly.`))}finally{i.close()}}(r),0;case"bind":return await async function(t){const n=X(t.configFile,ke(t)),o=ye.createUploader(n);try{ve(await we(o,t)),await async function(t){if("darwin"!==process.platform)return!1;if(!e.existsSync(t.configFile)||!t.entryFile)return!1;const n=ye.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=X(e.configFile,ke(e));if(Be(t),ye.createServiceManager(t).status().running)throw new fe(`Background service is still running. Stop it first with \`${P} stop\`.`);if(!e.yes&&!await ye.promptConfirm("Clear local backfill state and queued uploads?",!1))return void console.log("Clear cancelled.");const n=ye.createUploader(t);try{n.resetBackfillState()}finally{n.close()}console.log("Local backfill state has been cleared."),console.log(`Run \`${P} init\` when you want to backfill history again.`)}(r),0;case"usage":return await $e(r),0;case"run":return await async function(e){const t=X(e.configFile,ke(e)),n=ye.createUploader(t);try{Te(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=X(n.configFile,ke(n)),s=ye.createServiceManager(o);switch(t){case"start":return Be(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 Be(o),s.restart(),void console.log(`Service restarted: ${o.launchdLabel}`);case"status":return void function(e,t){const n={...V(e,t),...De(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=Pe(o.stdoutLogPath,n.lines),t=Pe(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(),ye.removePathFromShellConfigs(),e.rmSync(o.installRoot,{recursive:!0,force:!0}),void console.log(`${v} 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 \`${P} --help\` for usage.`),1}}export{ye as cliDeps,Ce as main,be as parseCliArgs};