@ken-jo/agent-connector 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +348 -0
  4. package/dist/amp-QSFH2V7Y.js +217 -0
  5. package/dist/amp-QSFH2V7Y.js.map +1 -0
  6. package/dist/amp-TULUPXXL.js +237 -0
  7. package/dist/amp-TULUPXXL.js.map +1 -0
  8. package/dist/antigravity-CX2YLSY7.js +17 -0
  9. package/dist/antigravity-CX2YLSY7.js.map +1 -0
  10. package/dist/antigravity-STHZWMP3.js +171 -0
  11. package/dist/antigravity-STHZWMP3.js.map +1 -0
  12. package/dist/antigravity-cli-P6HADOAO.js +121 -0
  13. package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
  14. package/dist/antigravity-cli-YQDFVV4U.js +86 -0
  15. package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
  16. package/dist/chunk-54EMKTTX.js +191 -0
  17. package/dist/chunk-54EMKTTX.js.map +1 -0
  18. package/dist/chunk-7L6ITC4T.js +54 -0
  19. package/dist/chunk-7L6ITC4T.js.map +1 -0
  20. package/dist/chunk-7MSDGHJW.js +316 -0
  21. package/dist/chunk-7MSDGHJW.js.map +1 -0
  22. package/dist/chunk-BCHWFTV4.js +196 -0
  23. package/dist/chunk-BCHWFTV4.js.map +1 -0
  24. package/dist/chunk-DBQMX75U.js +36 -0
  25. package/dist/chunk-DBQMX75U.js.map +1 -0
  26. package/dist/chunk-EXLS7BSI.js +29 -0
  27. package/dist/chunk-EXLS7BSI.js.map +1 -0
  28. package/dist/chunk-JNM3TPLX.js +593 -0
  29. package/dist/chunk-JNM3TPLX.js.map +1 -0
  30. package/dist/chunk-JWNT5KW7.js +197 -0
  31. package/dist/chunk-JWNT5KW7.js.map +1 -0
  32. package/dist/chunk-KHO5WNTP.js +30 -0
  33. package/dist/chunk-KHO5WNTP.js.map +1 -0
  34. package/dist/chunk-LTT56GI3.js +155 -0
  35. package/dist/chunk-LTT56GI3.js.map +1 -0
  36. package/dist/chunk-MEH7GC7R.js +111 -0
  37. package/dist/chunk-MEH7GC7R.js.map +1 -0
  38. package/dist/chunk-MQPNU2CY.js +64 -0
  39. package/dist/chunk-MQPNU2CY.js.map +1 -0
  40. package/dist/chunk-OPXAP3AC.js +12 -0
  41. package/dist/chunk-OPXAP3AC.js.map +1 -0
  42. package/dist/chunk-RK6VBLXP.js +50 -0
  43. package/dist/chunk-RK6VBLXP.js.map +1 -0
  44. package/dist/chunk-TIIP3RG2.js +420 -0
  45. package/dist/chunk-TIIP3RG2.js.map +1 -0
  46. package/dist/chunk-TRKWUNO4.js +702 -0
  47. package/dist/chunk-TRKWUNO4.js.map +1 -0
  48. package/dist/chunk-TRQKHP7D.js +93 -0
  49. package/dist/chunk-TRQKHP7D.js.map +1 -0
  50. package/dist/chunk-WCETQWWK.js +59 -0
  51. package/dist/chunk-WCETQWWK.js.map +1 -0
  52. package/dist/chunk-WV63X7IK.js +357 -0
  53. package/dist/chunk-WV63X7IK.js.map +1 -0
  54. package/dist/chunk-XD7MY6HB.js +343 -0
  55. package/dist/chunk-XD7MY6HB.js.map +1 -0
  56. package/dist/chunk-XNAM7F35.js +276 -0
  57. package/dist/chunk-XNAM7F35.js.map +1 -0
  58. package/dist/chunk-XUCWLJY5.js +238 -0
  59. package/dist/chunk-XUCWLJY5.js.map +1 -0
  60. package/dist/chunk-YNZU6PAV.js +800 -0
  61. package/dist/chunk-YNZU6PAV.js.map +1 -0
  62. package/dist/chunk-YT43MUSJ.js +146 -0
  63. package/dist/chunk-YT43MUSJ.js.map +1 -0
  64. package/dist/claude-code-HKDV7T32.js +637 -0
  65. package/dist/claude-code-HKDV7T32.js.map +1 -0
  66. package/dist/claude-code-W74MNTAC.js +652 -0
  67. package/dist/claude-code-W74MNTAC.js.map +1 -0
  68. package/dist/cli/sdk.d.ts +54 -0
  69. package/dist/cli/sdk.js +82 -0
  70. package/dist/cli/sdk.js.map +1 -0
  71. package/dist/cli.d.ts +2 -0
  72. package/dist/cli.js +15 -0
  73. package/dist/cli.js.map +1 -0
  74. package/dist/codebuff-3PMZJJHP.js +307 -0
  75. package/dist/codebuff-3PMZJJHP.js.map +1 -0
  76. package/dist/codebuff-QX57FJDN.js +220 -0
  77. package/dist/codebuff-QX57FJDN.js.map +1 -0
  78. package/dist/codex-JFQTS6LU.js +593 -0
  79. package/dist/codex-JFQTS6LU.js.map +1 -0
  80. package/dist/codex-Q3ATRRX3.js +364 -0
  81. package/dist/codex-Q3ATRRX3.js.map +1 -0
  82. package/dist/copilot-cli-MTCGHZ2T.js +622 -0
  83. package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
  84. package/dist/copilot-cli-SJGIXVSD.js +369 -0
  85. package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
  86. package/dist/crush-I7PK3FGI.js +360 -0
  87. package/dist/crush-I7PK3FGI.js.map +1 -0
  88. package/dist/crush-LN5QFFR2.js +183 -0
  89. package/dist/crush-LN5QFFR2.js.map +1 -0
  90. package/dist/cursor-4TMXKJOZ.js +603 -0
  91. package/dist/cursor-4TMXKJOZ.js.map +1 -0
  92. package/dist/cursor-QZ6MTO74.js +194 -0
  93. package/dist/cursor-QZ6MTO74.js.map +1 -0
  94. package/dist/detect-WIAGVYS4.js +68 -0
  95. package/dist/detect-WIAGVYS4.js.map +1 -0
  96. package/dist/doctor-EZW2K4NI.js +372 -0
  97. package/dist/doctor-EZW2K4NI.js.map +1 -0
  98. package/dist/droid-KN6VRMMT.js +424 -0
  99. package/dist/droid-KN6VRMMT.js.map +1 -0
  100. package/dist/droid-W5WF5XEX.js +224 -0
  101. package/dist/droid-W5WF5XEX.js.map +1 -0
  102. package/dist/gemini-cli-CIRW37TU.js +369 -0
  103. package/dist/gemini-cli-CIRW37TU.js.map +1 -0
  104. package/dist/gemini-cli-Y35FFK4M.js +705 -0
  105. package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
  106. package/dist/goose-A3M5GODH.js +212 -0
  107. package/dist/goose-A3M5GODH.js.map +1 -0
  108. package/dist/goose-ZTGTSUFJ.js +480 -0
  109. package/dist/goose-ZTGTSUFJ.js.map +1 -0
  110. package/dist/hermes-H2KV44GK.js +470 -0
  111. package/dist/hermes-H2KV44GK.js.map +1 -0
  112. package/dist/hermes-ZYLMOFLZ.js +196 -0
  113. package/dist/hermes-ZYLMOFLZ.js.map +1 -0
  114. package/dist/hook-YVDILAMZ.js +75 -0
  115. package/dist/hook-YVDILAMZ.js.map +1 -0
  116. package/dist/index.d.ts +22 -0
  117. package/dist/index.js +10 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/install-HQRYZOQ2.js +60 -0
  120. package/dist/install-HQRYZOQ2.js.map +1 -0
  121. package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
  122. package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
  123. package/dist/kilo-NSWAVP6P.js +235 -0
  124. package/dist/kilo-NSWAVP6P.js.map +1 -0
  125. package/dist/kilo-QXJRCSLT.js +384 -0
  126. package/dist/kilo-QXJRCSLT.js.map +1 -0
  127. package/dist/kilo-cli-EAQTHXUW.js +587 -0
  128. package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
  129. package/dist/kilo-cli-EVBKUODA.js +160 -0
  130. package/dist/kilo-cli-EVBKUODA.js.map +1 -0
  131. package/dist/kimi-EI3UM3ZV.js +155 -0
  132. package/dist/kimi-EI3UM3ZV.js.map +1 -0
  133. package/dist/kimi-GQ7VXNYT.js +423 -0
  134. package/dist/kimi-GQ7VXNYT.js.map +1 -0
  135. package/dist/kiro-UCLSLBW2.js +217 -0
  136. package/dist/kiro-UCLSLBW2.js.map +1 -0
  137. package/dist/kiro-VQTT6LO2.js +451 -0
  138. package/dist/kiro-VQTT6LO2.js.map +1 -0
  139. package/dist/leaderboard-K6ON3SWW.js +137 -0
  140. package/dist/leaderboard-K6ON3SWW.js.map +1 -0
  141. package/dist/mux-T2TV4CIQ.js +211 -0
  142. package/dist/mux-T2TV4CIQ.js.map +1 -0
  143. package/dist/mux-ZMLVAXOO.js +96 -0
  144. package/dist/mux-ZMLVAXOO.js.map +1 -0
  145. package/dist/omp-PB4VDKS6.js +586 -0
  146. package/dist/omp-PB4VDKS6.js.map +1 -0
  147. package/dist/openclaw-ETN2M63O.js +751 -0
  148. package/dist/openclaw-ETN2M63O.js.map +1 -0
  149. package/dist/openclaw-IYA7ASL6.js +150 -0
  150. package/dist/openclaw-IYA7ASL6.js.map +1 -0
  151. package/dist/opencode-FZ33JKUZ.js +734 -0
  152. package/dist/opencode-FZ33JKUZ.js.map +1 -0
  153. package/dist/opencode-HWT3RSMN.js +216 -0
  154. package/dist/opencode-HWT3RSMN.js.map +1 -0
  155. package/dist/package-DLOVE2E4.js +1132 -0
  156. package/dist/package-DLOVE2E4.js.map +1 -0
  157. package/dist/pi-CIU2QHT4.js +208 -0
  158. package/dist/pi-CIU2QHT4.js.map +1 -0
  159. package/dist/pi-UFX2OYFV.js +139 -0
  160. package/dist/pi-UFX2OYFV.js.map +1 -0
  161. package/dist/qwen-code-5POCNU6P.js +573 -0
  162. package/dist/qwen-code-5POCNU6P.js.map +1 -0
  163. package/dist/qwen-code-W7NUCDEN.js +134 -0
  164. package/dist/qwen-code-W7NUCDEN.js.map +1 -0
  165. package/dist/roo-code-IOIUXRDE.js +242 -0
  166. package/dist/roo-code-IOIUXRDE.js.map +1 -0
  167. package/dist/roo-code-ZLKH4A7N.js +229 -0
  168. package/dist/roo-code-ZLKH4A7N.js.map +1 -0
  169. package/dist/runtime/index.d.ts +157 -0
  170. package/dist/runtime/index.js +20 -0
  171. package/dist/runtime/index.js.map +1 -0
  172. package/dist/serve-HVWWY4B6.js +65 -0
  173. package/dist/serve-HVWWY4B6.js.map +1 -0
  174. package/dist/status-FE7TECZD.js +128 -0
  175. package/dist/status-FE7TECZD.js.map +1 -0
  176. package/dist/synthetic-UKR6SMGS.js +178 -0
  177. package/dist/synthetic-UKR6SMGS.js.map +1 -0
  178. package/dist/telemetry-36GESKQQ.js +349 -0
  179. package/dist/telemetry-36GESKQQ.js.map +1 -0
  180. package/dist/trae-JSNDZFKL.js +204 -0
  181. package/dist/trae-JSNDZFKL.js.map +1 -0
  182. package/dist/trae-PA26DPBW.js +157 -0
  183. package/dist/trae-PA26DPBW.js.map +1 -0
  184. package/dist/types-CYUB1OTr.d.ts +427 -0
  185. package/dist/uninstall-IGCKNOSF.js +72 -0
  186. package/dist/uninstall-IGCKNOSF.js.map +1 -0
  187. package/dist/upgrade-JV2VK242.js +104 -0
  188. package/dist/upgrade-JV2VK242.js.map +1 -0
  189. package/dist/usage-6ER6EMEY.js +324 -0
  190. package/dist/usage-6ER6EMEY.js.map +1 -0
  191. package/dist/usage-event-JKBI6XCF.js +48 -0
  192. package/dist/usage-event-JKBI6XCF.js.map +1 -0
  193. package/dist/vscode-copilot-CK3NO54M.js +715 -0
  194. package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
  195. package/dist/warp-JNQA7PBU.js +126 -0
  196. package/dist/warp-JNQA7PBU.js.map +1 -0
  197. package/dist/warp-QNVDXOG4.js +199 -0
  198. package/dist/warp-QNVDXOG4.js.map +1 -0
  199. package/dist/zed-NNNX5MNT.js +272 -0
  200. package/dist/zed-NNNX5MNT.js.map +1 -0
  201. package/dist/zed-VRJKWYUR.js +209 -0
  202. package/dist/zed-VRJKWYUR.js.map +1 -0
  203. package/package.json +79 -0
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveEnvRefsDeep
4
+ } from "./chunk-DBQMX75U.js";
5
+ import {
6
+ BaseAdapter
7
+ } from "./chunk-TIIP3RG2.js";
8
+ import "./chunk-7L6ITC4T.js";
9
+ import {
10
+ buildServeWrapperCommand,
11
+ isHomeBinHookCommand,
12
+ shouldWrapForTelemetry
13
+ } from "./chunk-MEH7GC7R.js";
14
+ import "./chunk-TRQKHP7D.js";
15
+
16
+ // src/adapters/trae/index.ts
17
+ import { existsSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { join } from "path";
20
+ var HOST = "trae";
21
+ var MCP_ROOT_KEY = "mcpServers";
22
+ var TraeAdapter = class extends BaseAdapter {
23
+ id = HOST;
24
+ name = "Trae";
25
+ paradigm = "mcp-only";
26
+ capabilities = {
27
+ // Trae has no lifecycle hook system we can target — every hook capability
28
+ // is false.
29
+ preToolUse: false,
30
+ postToolUse: false,
31
+ preCompact: false,
32
+ sessionStart: false,
33
+ sessionEnd: false,
34
+ userPromptSubmit: false,
35
+ stop: false,
36
+ notification: false,
37
+ canModifyArgs: false,
38
+ canModifyOutput: false,
39
+ canInjectSessionContext: false,
40
+ // Trae registers stdio, SSE, and Streamable HTTP MCP servers.
41
+ transports: ["stdio", "sse", "http"]
42
+ };
43
+ // ── Detection ────────────────────────────────────────────────────────────
44
+ detectInstalled(projectDir) {
45
+ const userDir = join(homedir(), ".trae");
46
+ const userMcp = join(userDir, "mcp.json");
47
+ const projDir = join(projectDir, ".trae");
48
+ const projMcp = join(projDir, "mcp.json");
49
+ const userInstalled = existsSync(userDir) || existsSync(userMcp);
50
+ const projInstalled = existsSync(projDir) || existsSync(projMcp);
51
+ const installed = userInstalled || projInstalled;
52
+ const scope = projInstalled && !userInstalled ? "project" : "user";
53
+ const configPath = scope === "project" ? projMcp : userMcp;
54
+ return {
55
+ id: this.id,
56
+ name: this.name,
57
+ installed,
58
+ paradigm: this.paradigm,
59
+ capabilities: this.capabilities,
60
+ configPath,
61
+ scope,
62
+ reason: installed ? `found Trae config (${scope})` : `no Trae config at ${userDir} or ${projDir}`,
63
+ confidence: installed ? "high" : "low"
64
+ };
65
+ }
66
+ // ── Native paths ─────────────────────────────────────────────────────────
67
+ getConfigDir(ctx) {
68
+ return ctx.scope === "project" ? join(ctx.projectDir, ".trae") : join(homedir(), ".trae");
69
+ }
70
+ getServerConfigPath(ctx) {
71
+ return join(this.getConfigDir(ctx), "mcp.json");
72
+ }
73
+ /**
74
+ * Trae has no hook file — hooks are not a thing here. The hook "config path"
75
+ * is the same mcp.json so the generic doctor/backup behave sensibly.
76
+ */
77
+ getHookConfigPath(ctx) {
78
+ return this.getServerConfigPath(ctx);
79
+ }
80
+ // ── MCP server install / uninstall ───────────────────────────────────────
81
+ installServer(ctx) {
82
+ const { connector } = ctx;
83
+ const override = connector.platforms[HOST]?.server;
84
+ if (!connector.server || override === false) {
85
+ return [
86
+ {
87
+ platform: this.id,
88
+ action: "skip",
89
+ detail: connector.server ? "server registration disabled for trae" : "connector declares no MCP server"
90
+ }
91
+ ];
92
+ }
93
+ const server = override && typeof override === "object" ? { ...connector.server, ...override } : connector.server;
94
+ const serverPath = this.getServerConfigPath(ctx);
95
+ const entry = this.renderServerEntry(ctx, server);
96
+ return [
97
+ this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun)
98
+ ];
99
+ }
100
+ uninstallServer(ctx) {
101
+ const serverPath = this.getServerConfigPath(ctx);
102
+ return [
103
+ this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun)
104
+ ];
105
+ }
106
+ /** Render a normalized ServerDef into Trae's native mcpServers entry. */
107
+ renderServerEntry(ctx, server) {
108
+ const transport = server.transport;
109
+ if (transport === "stdio") {
110
+ let command = server.command ?? "";
111
+ let args = [...server.args ?? []];
112
+ if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
113
+ const wrapped = buildServeWrapperCommand(
114
+ ctx.homeBinPath,
115
+ ctx.connector.id,
116
+ command,
117
+ args,
118
+ ctx.scope,
119
+ this.id
120
+ );
121
+ command = wrapped.command;
122
+ args = wrapped.args;
123
+ }
124
+ const entry2 = { command: resolveEnvRefsDeep(command) };
125
+ if (args.length > 0) entry2.args = resolveEnvRefsDeep(args);
126
+ const env = this.renderEnv(server.env);
127
+ if (env) entry2.env = env;
128
+ return entry2;
129
+ }
130
+ const entry = { url: resolveEnvRefsDeep(server.url ?? "") };
131
+ const headers = this.renderEnv(server.headers);
132
+ if (headers) entry.headers = headers;
133
+ return entry;
134
+ }
135
+ /**
136
+ * Render env/header values. Trae documents no native interpolation token, so
137
+ * resolve `${env:VAR}` references to literals at install time.
138
+ */
139
+ renderEnv(env) {
140
+ if (!env || Object.keys(env).length === 0) return void 0;
141
+ return resolveEnvRefsDeep({ ...env });
142
+ }
143
+ // ── Hooks (unavailable — Trae is mcp-only) ───────────────────────────────
144
+ installHooks(_ctx) {
145
+ return [
146
+ {
147
+ platform: this.id,
148
+ action: "skip",
149
+ detail: "hooks unavailable (Trae is mcp-only)"
150
+ }
151
+ ];
152
+ }
153
+ uninstallHooks(_ctx) {
154
+ return [
155
+ {
156
+ platform: this.id,
157
+ action: "skip",
158
+ detail: "hooks unavailable (Trae is mcp-only)"
159
+ }
160
+ ];
161
+ }
162
+ /**
163
+ * True when a hook command references our home binary AND this connector id
164
+ * (anchored so a shared-prefix id can't collide — see isHomeBinHookCommand).
165
+ * Trae installs no hooks, but this guard keeps any future shared-file edit
166
+ * from removing another connector's entries.
167
+ */
168
+ isOurCommand(command, ctx) {
169
+ return isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id);
170
+ }
171
+ // ── Diagnostics ──────────────────────────────────────────────────────────
172
+ getHealthChecks(ctx) {
173
+ const mcpPath = this.getServerConfigPath(ctx);
174
+ const connectorId = ctx.connector.id;
175
+ return [
176
+ {
177
+ name: `${this.name}: mcp.json present`,
178
+ check: () => existsSync(mcpPath) ? { status: "OK", detail: mcpPath } : { status: "FAIL", detail: `not found: ${mcpPath}` }
179
+ },
180
+ {
181
+ name: `${this.name}: server entry registered`,
182
+ check: () => {
183
+ const cfg = this.readJson(mcpPath);
184
+ const bucket = cfg?.[MCP_ROOT_KEY];
185
+ if (!cfg || !bucket) {
186
+ return { status: "FAIL", detail: `no ${MCP_ROOT_KEY} in ${mcpPath}` };
187
+ }
188
+ return connectorId in bucket ? { status: "OK", detail: `${MCP_ROOT_KEY}.${connectorId} present` } : {
189
+ status: "FAIL",
190
+ detail: `no ${MCP_ROOT_KEY}.${connectorId} in ${mcpPath}`
191
+ };
192
+ }
193
+ }
194
+ ];
195
+ }
196
+ };
197
+ var adapter = new TraeAdapter();
198
+ var trae_default = adapter;
199
+ export {
200
+ TraeAdapter,
201
+ adapter,
202
+ trae_default as default
203
+ };
204
+ //# sourceMappingURL=trae-JSNDZFKL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/trae/index.ts"],"sourcesContent":["/**\n * adapters/trae — Trae (ByteDance) platform adapter for agent-connector.\n *\n * Trae is an **mcp-only** host for our purposes: it exposes MCP servers as its\n * extensibility mechanism but has no lifecycle hook system we can register\n * against. This adapter therefore installs only the MCP server and reports hooks\n * as unavailable, mirroring the Warp reference path that validates the\n * `mcp-only` paradigm end-to-end.\n *\n * MCP config (medium-confidence path — still implemented; doctor reports presence):\n * - user scope → ~/.trae/mcp.json\n * - project scope → <projectDir>/.trae/mcp.json\n * Both are JSON, root key \"mcpServers\", and are the SAME file used for the\n * (non-existent) hook registration — there is no separate hook file here.\n *\n * A stdio server entry uses the standard Claude/Cursor shape:\n * { command, args, env } (object-keyed by connector id).\n *\n * Trae documents no native `${env:VAR}` interpolation token, so env/header/url\n * refs are resolved to literals at install time (the no-native-token path).\n */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { BaseAdapter } from \"../base.js\";\nimport type { Adapter, InstallContext } from \"../spi.js\";\nimport type {\n ChangeRecord,\n DetectedPlatform,\n HealthCheck,\n HookParadigm,\n PlatformCapabilities,\n PlatformId,\n ServerDef,\n Transport,\n} from \"../../core/types.js\";\nimport { resolveEnvRefsDeep } from \"../../core/interpolate.js\";\nimport {\n buildServeWrapperCommand,\n isHomeBinHookCommand,\n shouldWrapForTelemetry,\n} from \"../../core/spawn.js\";\n\nconst HOST: PlatformId = \"trae\";\nconst MCP_ROOT_KEY = \"mcpServers\";\n\n/** Native MCP server entry shapes Trae accepts under `mcpServers`. */\ninterface TraeStdioServer {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\ninterface TraeHttpServer {\n url: string;\n headers?: Record<string, string>;\n}\n\nexport class TraeAdapter extends BaseAdapter implements Adapter {\n readonly id: PlatformId = HOST;\n readonly name = \"Trae\";\n readonly paradigm: HookParadigm = \"mcp-only\";\n\n readonly capabilities: PlatformCapabilities = {\n // Trae has no lifecycle hook system we can target — every hook capability\n // is false.\n preToolUse: false,\n postToolUse: false,\n preCompact: false,\n sessionStart: false,\n sessionEnd: false,\n userPromptSubmit: false,\n stop: false,\n notification: false,\n canModifyArgs: false,\n canModifyOutput: false,\n canInjectSessionContext: false,\n // Trae registers stdio, SSE, and Streamable HTTP MCP servers.\n transports: [\"stdio\", \"sse\", \"http\"],\n };\n\n // ── Detection ────────────────────────────────────────────────────────────\n\n detectInstalled(projectDir: string): DetectedPlatform {\n const userDir = join(homedir(), \".trae\");\n const userMcp = join(userDir, \"mcp.json\");\n const projDir = join(projectDir, \".trae\");\n const projMcp = join(projDir, \"mcp.json\");\n\n const userInstalled = existsSync(userDir) || existsSync(userMcp);\n const projInstalled = existsSync(projDir) || existsSync(projMcp);\n const installed = userInstalled || projInstalled;\n const scope = projInstalled && !userInstalled ? \"project\" : \"user\";\n const configPath = scope === \"project\" ? projMcp : userMcp;\n\n return {\n id: this.id,\n name: this.name,\n installed,\n paradigm: this.paradigm,\n capabilities: this.capabilities,\n configPath,\n scope,\n reason: installed\n ? `found Trae config (${scope})`\n : `no Trae config at ${userDir} or ${projDir}`,\n confidence: installed ? \"high\" : \"low\",\n };\n }\n\n // ── Native paths ─────────────────────────────────────────────────────────\n\n getConfigDir(ctx: InstallContext): string {\n return ctx.scope === \"project\"\n ? join(ctx.projectDir, \".trae\")\n : join(homedir(), \".trae\");\n }\n\n getServerConfigPath(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"mcp.json\");\n }\n\n /**\n * Trae has no hook file — hooks are not a thing here. The hook \"config path\"\n * is the same mcp.json so the generic doctor/backup behave sensibly.\n */\n getHookConfigPath(ctx: InstallContext): string {\n return this.getServerConfigPath(ctx);\n }\n\n // ── MCP server install / uninstall ───────────────────────────────────────\n\n installServer(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n const override = connector.platforms[HOST]?.server;\n if (!connector.server || override === false) {\n return [\n {\n platform: this.id,\n action: \"skip\",\n detail: connector.server\n ? \"server registration disabled for trae\"\n : \"connector declares no MCP server\",\n },\n ];\n }\n\n // Shallow-merge any per-platform server override into the base ServerDef.\n const server: ServerDef =\n override && typeof override === \"object\"\n ? { ...connector.server, ...override }\n : connector.server;\n\n const serverPath = this.getServerConfigPath(ctx);\n const entry = this.renderServerEntry(ctx, server);\n\n return [\n this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun),\n ];\n }\n\n uninstallServer(ctx: InstallContext): ChangeRecord[] {\n const serverPath = this.getServerConfigPath(ctx);\n return [\n this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun),\n ];\n }\n\n /** Render a normalized ServerDef into Trae's native mcpServers entry. */\n private renderServerEntry(\n ctx: InstallContext,\n server: ServerDef,\n ): TraeStdioServer | TraeHttpServer {\n const transport: Transport = server.transport;\n\n if (transport === \"stdio\") {\n let command = server.command ?? \"\";\n let args = [...(server.args ?? [])];\n\n // Transparent telemetry wrapping: route the real command through\n // `<homeBin> serve --connector <id> -- <command> <args...>`.\n if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {\n const wrapped = buildServeWrapperCommand(\n ctx.homeBinPath,\n ctx.connector.id,\n command,\n args,\n ctx.scope,\n this.id,\n );\n command = wrapped.command;\n args = wrapped.args;\n }\n\n // Trae has no documented native interpolation token, so resolve every\n // ${env:VAR} to a literal at install time.\n const entry: TraeStdioServer = { command: resolveEnvRefsDeep(command) };\n if (args.length > 0) entry.args = resolveEnvRefsDeep(args);\n const env = this.renderEnv(server.env);\n if (env) entry.env = env;\n return entry;\n }\n\n // sse / http (and any other remote transport) — Trae registers a URL.\n const entry: TraeHttpServer = { url: resolveEnvRefsDeep(server.url ?? \"\") };\n const headers = this.renderEnv(server.headers);\n if (headers) entry.headers = headers;\n return entry;\n }\n\n /**\n * Render env/header values. Trae documents no native interpolation token, so\n * resolve `${env:VAR}` references to literals at install time.\n */\n private renderEnv(\n env: Record<string, string> | undefined,\n ): Record<string, string> | undefined {\n if (!env || Object.keys(env).length === 0) return undefined;\n return resolveEnvRefsDeep({ ...env });\n }\n\n // ── Hooks (unavailable — Trae is mcp-only) ───────────────────────────────\n\n installHooks(_ctx: InstallContext): ChangeRecord[] {\n return [\n {\n platform: this.id,\n action: \"skip\",\n detail: \"hooks unavailable (Trae is mcp-only)\",\n },\n ];\n }\n\n uninstallHooks(_ctx: InstallContext): ChangeRecord[] {\n return [\n {\n platform: this.id,\n action: \"skip\",\n detail: \"hooks unavailable (Trae is mcp-only)\",\n },\n ];\n }\n\n /**\n * True when a hook command references our home binary AND this connector id\n * (anchored so a shared-prefix id can't collide — see isHomeBinHookCommand).\n * Trae installs no hooks, but this guard keeps any future shared-file edit\n * from removing another connector's entries.\n */\n private isOurCommand(command: string | undefined, ctx: InstallContext): boolean {\n return isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id);\n }\n\n // ── Diagnostics ──────────────────────────────────────────────────────────\n\n override getHealthChecks(ctx: InstallContext): readonly HealthCheck[] {\n const mcpPath = this.getServerConfigPath(ctx);\n const connectorId = ctx.connector.id;\n return [\n {\n name: `${this.name}: mcp.json present`,\n check: () =>\n existsSync(mcpPath)\n ? { status: \"OK\", detail: mcpPath }\n : { status: \"FAIL\", detail: `not found: ${mcpPath}` },\n },\n {\n name: `${this.name}: server entry registered`,\n check: () => {\n const cfg = this.readJson<{ [k: string]: Record<string, unknown> }>(mcpPath);\n const bucket = cfg?.[MCP_ROOT_KEY];\n if (!cfg || !bucket) {\n return { status: \"FAIL\", detail: `no ${MCP_ROOT_KEY} in ${mcpPath}` };\n }\n return connectorId in bucket\n ? { status: \"OK\", detail: `${MCP_ROOT_KEY}.${connectorId} present` }\n : {\n status: \"FAIL\",\n detail: `no ${MCP_ROOT_KEY}.${connectorId} in ${mcpPath}`,\n };\n },\n },\n ];\n }\n}\n\nexport const adapter = new TraeAdapter();\nexport default adapter;\n"],"mappings":"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","names":["entry"]}
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readJsonFile
4
+ } from "./chunk-RK6VBLXP.js";
5
+ import {
6
+ firstExistingRoot,
7
+ walkFiles
8
+ } from "./chunk-JWNT5KW7.js";
9
+ import {
10
+ emptyTokens
11
+ } from "./chunk-XD7MY6HB.js";
12
+
13
+ // src/usage/readers/trae.ts
14
+ var PLATFORM_ID = "trae";
15
+ var DEFAULT_PROVIDER = "trae";
16
+ var MAX_SAFE_USAGE_TIME_S = Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
17
+ function normalizeTraeModel(name) {
18
+ switch (name) {
19
+ case "GPT-5.4":
20
+ return "gpt-5.4";
21
+ case "GPT-5.3-Codex":
22
+ case "GPT-5.3 Codex":
23
+ return "gpt-5.3-codex";
24
+ case "GPT-5.3":
25
+ return "gpt-5.3";
26
+ case "GPT-5.2-Codex":
27
+ case "GPT-5.2 Codex":
28
+ return "gpt-5.2-codex";
29
+ case "GPT-5.2":
30
+ return "gpt-5.2";
31
+ case "GPT-5.1-Codex":
32
+ case "GPT-5.1 Codex":
33
+ return "gpt-5.1-codex";
34
+ case "GPT-5.1":
35
+ return "gpt-5.1";
36
+ case "Gemini 3.1 Pro":
37
+ return "gemini-3.1-pro";
38
+ case "Gemini 3.1":
39
+ return "gemini-3.1";
40
+ case "GLM 5.1":
41
+ case "GLM-5.1":
42
+ return "glm-5.1";
43
+ case "Claude Sonnet 4.6":
44
+ case "Claude-Sonnet-4.6":
45
+ return "claude-sonnet-4.6";
46
+ case "Claude Sonnet 4.5":
47
+ case "Claude-Sonnet-4.5":
48
+ return "claude-sonnet-4.5";
49
+ default:
50
+ return name;
51
+ }
52
+ }
53
+ function providerForModel(name) {
54
+ if (name.includes("GPT") || name.includes("gpt")) return "openai";
55
+ if (name.includes("Claude") || name.includes("claude")) return "anthropic";
56
+ if (name.includes("Gemini") || name.includes("gemini")) return "google";
57
+ if (name.includes("GLM") || name.includes("glm")) return "zhipu";
58
+ return DEFAULT_PROVIDER;
59
+ }
60
+ function toNonNegInt(v) {
61
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
62
+ if (!Number.isFinite(n)) return 0;
63
+ return Math.max(0, Math.trunc(n));
64
+ }
65
+ function toNumber(v) {
66
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
67
+ return Number.isFinite(n) ? n : 0;
68
+ }
69
+ function toUsageTimeSeconds(v) {
70
+ let n;
71
+ if (typeof v === "number") n = v;
72
+ else if (typeof v === "string") n = Number(v);
73
+ else return null;
74
+ if (!Number.isFinite(n) || !Number.isInteger(n)) return null;
75
+ if (n <= 0) return null;
76
+ return n;
77
+ }
78
+ function parseSession(session) {
79
+ const modelRaw = typeof session.model_name === "string" ? session.model_name : "";
80
+ const mode = typeof session.mode === "string" ? session.mode : "";
81
+ let modelId;
82
+ if (modelRaw !== "") modelId = normalizeTraeModel(modelRaw);
83
+ else if (mode !== "") modelId = `trae-${mode.toLowerCase()}`;
84
+ else modelId = "trae-unknown";
85
+ const providerId = providerForModel(modelRaw !== "" ? modelRaw : modelId);
86
+ const sessionId = typeof session.session_id === "string" && session.session_id !== "" ? session.session_id : null;
87
+ if (sessionId === null) return null;
88
+ const usageTime = toUsageTimeSeconds(session.usage_time);
89
+ if (usageTime === null) return null;
90
+ if (usageTime > MAX_SAFE_USAGE_TIME_S) return null;
91
+ const ts = usageTime * 1e3;
92
+ const cost = toNumber(session.dollar_float);
93
+ const extra = session.extra_info ?? {};
94
+ const input = toNonNegInt(extra.input_token);
95
+ const output = toNonNegInt(extra.output_token);
96
+ const cacheRead = toNonNegInt(extra.cache_read_token);
97
+ const cacheWrite = toNonNegInt(extra.cache_write_token);
98
+ if (input + output + cacheRead + cacheWrite === 0) return null;
99
+ const tokens = emptyTokens();
100
+ tokens.input = input;
101
+ tokens.output = output;
102
+ tokens.cacheRead = cacheRead;
103
+ tokens.cacheWrite = cacheWrite;
104
+ return {
105
+ platformId: PLATFORM_ID,
106
+ modelId,
107
+ providerId,
108
+ sessionId,
109
+ tokens,
110
+ cost,
111
+ ts,
112
+ messageCount: 1,
113
+ dedupKey: `trae:${sessionId}:${usageTime}`,
114
+ confidence: "host-reported"
115
+ };
116
+ }
117
+ function parseTraeFile(path) {
118
+ const value = readJsonFile(path);
119
+ if (!Array.isArray(value)) return [];
120
+ const out = [];
121
+ for (const raw of value) {
122
+ if (typeof raw !== "object" || raw === null) continue;
123
+ const record = parseSession(raw);
124
+ if (record !== null) out.push(record);
125
+ }
126
+ return out;
127
+ }
128
+ var traeReader = {
129
+ platformId: PLATFORM_ID,
130
+ kind: "synced",
131
+ async read({ sinceMs }) {
132
+ const root = firstExistingRoot(PLATFORM_ID);
133
+ if (root === void 0) return [];
134
+ const files = walkFiles(root, (name) => {
135
+ const lower = name.toLowerCase();
136
+ return lower.startsWith("usage-") && lower.endsWith(".json");
137
+ });
138
+ const byKey = /* @__PURE__ */ new Map();
139
+ for (const file of files) {
140
+ for (const row of parseTraeFile(file)) {
141
+ const key = row.dedupKey ?? `${row.sessionId}:${row.ts}`;
142
+ byKey.set(key, row);
143
+ }
144
+ }
145
+ const records = [];
146
+ for (const row of byKey.values()) {
147
+ if (sinceMs !== void 0 && row.ts < sinceMs) continue;
148
+ records.push(row);
149
+ }
150
+ return records;
151
+ }
152
+ };
153
+ var trae_default = traeReader;
154
+ export {
155
+ trae_default as default
156
+ };
157
+ //# sourceMappingURL=trae-PA26DPBW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usage/readers/trae.ts"],"sourcesContent":["/**\n * usage/readers/trae — Trae / ByteDance AI IDE usage reader (SYNCED platform).\n *\n * Faithful port of tokscale sessions/trae.rs. Trae's per-session token usage\n * lives behind the Trae usage REST API; tokscale authenticates, paginates, and\n * caches the JSON responses under ~/.config/tokscale/trae-cache/. THIS reader\n * never authenticates and never calls that API — it only parses the cached\n * artifacts when a tokscale run has already produced them. Hence kind:\"synced\":\n * - cache present → parse the JSON array(s) and emit one record per session;\n * - cache absent → return [] (the scan layer notes \"requires sync, skipped\").\n *\n * Cache layout (per docs/research/usage-readers.json + design §3d):\n * ~/.config/tokscale/trae-cache/sessions/usage-*.json (cached API artifacts)\n * ~/.config/tokscale/trae-cache/manifest.json (sync bookkeeping — ignored here)\n * paths.ts resolves the cache dir (env override AGENT_CONNECTOR_TOKSCALE_DIR\n * first, the tokscale ~/.config/tokscale default second). We walk for the\n * `usage-*.json` artifacts directly rather than reading the manifest: the\n * artifacts are self-describing and a manifest mismatch must never drop real data.\n *\n * Artifact format (mirrors parse_trae_file exactly): each file is a JSON array of\n * session objects. Per-session fields:\n * model_name — display name (may be \"\" for auto-mode);\n * mode — interaction mode (e.g. \"Auto\");\n * session_id — required; records without it cannot dedup correctly → dropped;\n * usage_time — epoch SECONDS, required & > 0 → ms = usage_time × 1000;\n * dollar_float — cost in USD;\n * extra_info — { input_token, output_token, cache_read_token, cache_write_token }.\n *\n * Token math (port of parse_session): the API returns exact counts, so each\n * dimension maps straight through; reasoning is always 0 (Trae does not report\n * it). A row whose four token dimensions sum to 0 is skipped.\n *\n * Model / provider (port of normalize_trae_model + provider_for_model):\n * - a known display name maps to a tiktoken-style id (e.g. \"GPT-5.4\" →\n * \"gpt-5.4\", \"Claude Sonnet 4.6\" → \"claude-sonnet-4.6\"); an unknown name\n * passes through verbatim;\n * - an empty model_name (auto-mode) buckets under \"trae-<mode>\" (e.g.\n * \"trae-auto\"); empty model AND empty mode → \"trae-unknown\" so the cost is\n * still attributed instead of vanishing into an empty Model cell;\n * - provider is inferred by substring on the display name: GPT→openai,\n * Claude→anthropic, Gemini→google, GLM→zhipu, else \"trae\".\n *\n * Dedup (design §6 / §220): Trae's API returns cumulative deltas and overlapping\n * syncs can write the same (session_id, usage_time) into multiple artifacts —\n * latest-per-(session_id, usage_time) wins. The dedupKey is\n * `trae:{session_id}:{usage_time}`. Because the global aggregate backstop keeps\n * the FIRST occurrence per key, this reader collapses duplicates itself, keeping\n * the LAST artifact row seen per key (walk order), so the emitted set is already\n * unique and the global filter is a no-op. Confidence is \"host-reported\" — the\n * cached data carries real, exact host token counts (spec lists it as medium for\n * the source overall; the per-row provenance is host-reported).\n *\n * Overflow guard (port of the checked_mul in Rust): a crafted usage_time near\n * the safe-integer ceiling would overflow when multiplied by 1000 — reject the\n * record rather than emit a garbage timestamp.\n *\n * Fail-open: no cache dir → []; unreadable/malformed file → skipped; a session\n * missing session_id / usage_time, with non-positive or overflowing usage_time,\n * or with all-zero tokens → skipped. No project scope (account-level usage).\n */\n\nimport type { TokenBreakdown, UsageReader, UsageRecord } from \"../types.js\";\nimport { emptyTokens } from \"../aggregate.js\";\nimport { readJsonFile } from \"../jsonl.js\";\nimport { firstExistingRoot, walkFiles } from \"../paths.js\";\n\nconst PLATFORM_ID = \"trae\" as const;\nconst DEFAULT_PROVIDER = \"trae\";\n\n/** Multiplying usage_time (s) by 1000 must stay a safe integer (port of checked_mul). */\nconst MAX_SAFE_USAGE_TIME_S = Math.floor(Number.MAX_SAFE_INTEGER / 1000);\n\n/** The fields we read off a cached Trae session object (everything optional / unknown). */\ninterface TraeSession {\n model_name?: unknown;\n mode?: unknown;\n session_id?: unknown;\n usage_time?: unknown;\n dollar_float?: unknown;\n extra_info?: {\n input_token?: unknown;\n output_token?: unknown;\n cache_read_token?: unknown;\n cache_write_token?: unknown;\n };\n}\n\n/**\n * Known mapping from Trae display names to tiktoken-style model ids (port of\n * normalize_trae_model). Unknown names fall through to the raw `model_name`\n * (mixed-case, space-separated) so a future model is still attributed.\n */\nfunction normalizeTraeModel(name: string): string {\n switch (name) {\n case \"GPT-5.4\":\n return \"gpt-5.4\";\n case \"GPT-5.3-Codex\":\n case \"GPT-5.3 Codex\":\n return \"gpt-5.3-codex\";\n case \"GPT-5.3\":\n return \"gpt-5.3\";\n case \"GPT-5.2-Codex\":\n case \"GPT-5.2 Codex\":\n return \"gpt-5.2-codex\";\n case \"GPT-5.2\":\n return \"gpt-5.2\";\n case \"GPT-5.1-Codex\":\n case \"GPT-5.1 Codex\":\n return \"gpt-5.1-codex\";\n case \"GPT-5.1\":\n return \"gpt-5.1\";\n case \"Gemini 3.1 Pro\":\n return \"gemini-3.1-pro\";\n case \"Gemini 3.1\":\n return \"gemini-3.1\";\n case \"GLM 5.1\":\n case \"GLM-5.1\":\n return \"glm-5.1\";\n case \"Claude Sonnet 4.6\":\n case \"Claude-Sonnet-4.6\":\n return \"claude-sonnet-4.6\";\n case \"Claude Sonnet 4.5\":\n case \"Claude-Sonnet-4.5\":\n return \"claude-sonnet-4.5\";\n default:\n return name;\n }\n}\n\n/**\n * Infer the provider from the display name (port of provider_for_model).\n * Substring matching, first match wins; falls back to \"trae\". Trae-specific so it\n * does not route through normalize.ts inferProvider (which knows nothing of GLM).\n */\nfunction providerForModel(name: string): string {\n if (name.includes(\"GPT\") || name.includes(\"gpt\")) return \"openai\";\n if (name.includes(\"Claude\") || name.includes(\"claude\")) return \"anthropic\";\n if (name.includes(\"Gemini\") || name.includes(\"gemini\")) return \"google\";\n if (name.includes(\"GLM\") || name.includes(\"glm\")) return \"zhipu\";\n return DEFAULT_PROVIDER;\n}\n\n/** Coerce an unknown to a non-negative integer (0 on absence/garbage). */\nfunction toNonNegInt(v: unknown): number {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n if (!Number.isFinite(n)) return 0;\n return Math.max(0, Math.trunc(n));\n}\n\n/** Coerce an unknown to a finite number (0 on absence/garbage) — for the cost field. */\nfunction toNumber(v: unknown): number {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n return Number.isFinite(n) ? n : 0;\n}\n\n/**\n * Coerce an unknown to a positive integer epoch-seconds value, or null when it is\n * absent / non-positive / not an integer (port of the as_i64()? + ≤0 checks).\n */\nfunction toUsageTimeSeconds(v: unknown): number | null {\n let n: number;\n if (typeof v === \"number\") n = v;\n else if (typeof v === \"string\") n = Number(v);\n else return null;\n if (!Number.isFinite(n) || !Number.isInteger(n)) return null;\n if (n <= 0) return null;\n return n;\n}\n\n/** Parse one cached Trae session object into a usage record (port of parse_session). */\nfunction parseSession(session: TraeSession): UsageRecord | null {\n const modelRaw = typeof session.model_name === \"string\" ? session.model_name : \"\";\n const mode = typeof session.mode === \"string\" ? session.mode : \"\";\n\n // Auto-mode sessions return model_name \"\" (the system picks a model per turn).\n // Bucket them under \"trae-<mode>\" so the cost is still attributed.\n let modelId: string;\n if (modelRaw !== \"\") modelId = normalizeTraeModel(modelRaw);\n else if (mode !== \"\") modelId = `trae-${mode.toLowerCase()}`;\n else modelId = \"trae-unknown\";\n\n // Provider is inferred from the raw display name (the model_id may already be a\n // bucket like \"trae-auto\"); fall back to the model_id when no raw name exists.\n const providerId = providerForModel(modelRaw !== \"\" ? modelRaw : modelId);\n\n // Records without a real session_id cannot be deduplicated correctly (every\n // \"missing-id\" record would collide on the same key); records without a\n // positive usage_time would land at epoch 0. Drop them rather than fabricating.\n const sessionId =\n typeof session.session_id === \"string\" && session.session_id !== \"\" ? session.session_id : null;\n if (sessionId === null) return null;\n\n const usageTime = toUsageTimeSeconds(session.usage_time);\n if (usageTime === null) return null;\n\n // Overflow guard: a crafted usage_time would overflow × 1000 → reject it.\n if (usageTime > MAX_SAFE_USAGE_TIME_S) return null;\n const ts = usageTime * 1000;\n\n const cost = toNumber(session.dollar_float);\n\n const extra = session.extra_info ?? {};\n const input = toNonNegInt(extra.input_token);\n const output = toNonNegInt(extra.output_token);\n const cacheRead = toNonNegInt(extra.cache_read_token);\n const cacheWrite = toNonNegInt(extra.cache_write_token);\n\n if (input + output + cacheRead + cacheWrite === 0) return null;\n\n const tokens: TokenBreakdown = emptyTokens();\n tokens.input = input;\n tokens.output = output;\n tokens.cacheRead = cacheRead;\n tokens.cacheWrite = cacheWrite;\n // reasoning stays 0 (Trae does not report it).\n\n return {\n platformId: PLATFORM_ID,\n modelId,\n providerId,\n sessionId,\n tokens,\n cost,\n ts,\n messageCount: 1,\n dedupKey: `trae:${sessionId}:${usageTime}`,\n confidence: \"host-reported\",\n };\n}\n\n/**\n * Parse one cached artifact file (a JSON array of sessions) into usage records\n * (port of parse_trae_file). A non-array / unreadable / malformed file yields [].\n */\nfunction parseTraeFile(path: string): UsageRecord[] {\n const value = readJsonFile(path);\n if (!Array.isArray(value)) return [];\n\n const out: UsageRecord[] = [];\n for (const raw of value) {\n if (typeof raw !== \"object\" || raw === null) continue;\n const record = parseSession(raw as TraeSession);\n if (record !== null) out.push(record);\n }\n return out;\n}\n\n/** The Trae synced usage reader singleton. */\nconst traeReader: UsageReader = {\n platformId: PLATFORM_ID,\n kind: \"synced\",\n async read({ sinceMs }: { sinceMs?: number }): Promise<UsageRecord[]> {\n // The cache dir is ~/.config/tokscale/trae-cache (env override first).\n // Absent → [] so the scan layer reports \"requires sync, skipped\".\n const root = firstExistingRoot(PLATFORM_ID);\n if (root === undefined) return [];\n\n // Enumerate cached artifacts: sessions/usage-*.json (walkFiles recurses, so\n // a flat or nested layout both work). Match the artifact name shape.\n const files = walkFiles(root, (name) => {\n const lower = name.toLowerCase();\n return lower.startsWith(\"usage-\") && lower.endsWith(\".json\");\n });\n\n // Trae returns cumulative deltas and overlapping syncs can repeat a\n // (session_id, usage_time) across artifacts — latest wins. Collapse to one\n // record per dedupKey, keeping the LAST seen (walk order), so the emitted set\n // is already unique (the global aggregate backstop keeps FIRST, not last).\n const byKey = new Map<string, UsageRecord>();\n for (const file of files) {\n for (const row of parseTraeFile(file)) {\n const key = row.dedupKey ?? `${row.sessionId}:${row.ts}`;\n byKey.set(key, row); // later artifact overwrites earlier → latest wins\n }\n }\n\n const records: UsageRecord[] = [];\n for (const row of byKey.values()) {\n if (sinceMs !== undefined && row.ts < sinceMs) continue;\n records.push(row);\n }\n return records;\n },\n};\n\nexport default traeReader;\n"],"mappings":"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","names":[]}