@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,196 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureDir,
4
+ telemetryPath
5
+ } from "./chunk-TRQKHP7D.js";
6
+
7
+ // src/telemetry/store.ts
8
+ import { appendFileSync, existsSync, readFileSync } from "fs";
9
+ import { dirname } from "path";
10
+
11
+ // src/telemetry/types.ts
12
+ var CONFIDENCE_RANK = {
13
+ heuristic: 0,
14
+ "tokenizer-approx": 1,
15
+ "tokenizer-calibrated": 2,
16
+ "tokenizer-exact": 3,
17
+ "host-native": 4
18
+ };
19
+ function worstConfidence(a, b) {
20
+ return CONFIDENCE_RANK[b] < CONFIDENCE_RANK[a] ? b : a;
21
+ }
22
+
23
+ // src/telemetry/store.ts
24
+ function telemetryDisabled() {
25
+ return process.env.AGENT_CONNECTOR_TELEMETRY === "0";
26
+ }
27
+ function newRecordId(seq) {
28
+ return `${Date.now()}-${seq}`;
29
+ }
30
+ var NdjsonStore = class {
31
+ path;
32
+ constructor(path) {
33
+ this.path = path;
34
+ }
35
+ append(record) {
36
+ if (telemetryDisabled()) return;
37
+ ensureDir(dirname(this.path));
38
+ appendFileSync(this.path, `${JSON.stringify(record)}
39
+ `, "utf8");
40
+ }
41
+ query(filter) {
42
+ if (!existsSync(this.path)) return [];
43
+ let text;
44
+ try {
45
+ text = readFileSync(this.path, "utf8");
46
+ } catch {
47
+ return [];
48
+ }
49
+ const out = [];
50
+ for (const line of text.split("\n")) {
51
+ const trimmed = line.trim();
52
+ if (trimmed === "") continue;
53
+ let record;
54
+ try {
55
+ record = JSON.parse(trimmed);
56
+ } catch {
57
+ continue;
58
+ }
59
+ if (matches(record, filter)) out.push(record);
60
+ }
61
+ return out;
62
+ }
63
+ rollup(by, filter) {
64
+ const groups = /* @__PURE__ */ new Map();
65
+ for (const record of this.query(filter)) {
66
+ const key = groupKey(record, by);
67
+ const existing = groups.get(key);
68
+ if (existing === void 0) {
69
+ groups.set(key, {
70
+ key,
71
+ calls: 1,
72
+ inputTokens: record.inputTokens,
73
+ outputTokens: record.outputTokens,
74
+ totalTokens: record.inputTokens + record.outputTokens,
75
+ confidence: record.confidenceSource,
76
+ lastTs: record.ts
77
+ });
78
+ } else {
79
+ existing.calls += 1;
80
+ existing.inputTokens += record.inputTokens;
81
+ existing.outputTokens += record.outputTokens;
82
+ existing.totalTokens += record.inputTokens + record.outputTokens;
83
+ existing.confidence = worstConfidence(
84
+ existing.confidence,
85
+ record.confidenceSource
86
+ );
87
+ if (record.ts > existing.lastTs) existing.lastTs = record.ts;
88
+ }
89
+ }
90
+ return [...groups.values()];
91
+ }
92
+ close() {
93
+ }
94
+ };
95
+ function matches(record, filter) {
96
+ if (filter.connectorId !== void 0 && record.connectorId !== filter.connectorId) {
97
+ return false;
98
+ }
99
+ if (filter.projectKey !== void 0 && record.projectKey !== filter.projectKey) {
100
+ return false;
101
+ }
102
+ if (filter.sessionId !== void 0 && record.sessionId !== filter.sessionId) {
103
+ return false;
104
+ }
105
+ if (filter.toolName !== void 0 && record.toolName !== filter.toolName) {
106
+ return false;
107
+ }
108
+ if (filter.sinceMs !== void 0 && record.ts < filter.sinceMs) {
109
+ return false;
110
+ }
111
+ return true;
112
+ }
113
+ function groupKey(record, by) {
114
+ switch (by) {
115
+ case "tool":
116
+ return record.toolName;
117
+ case "session":
118
+ return record.sessionId;
119
+ case "project":
120
+ return record.projectDir;
121
+ }
122
+ }
123
+ function openStore(opts) {
124
+ const path = opts?.path ?? telemetryPath("ndjson");
125
+ return new NdjsonStore(path);
126
+ }
127
+
128
+ // src/telemetry/tokenizer.ts
129
+ import { createRequire } from "module";
130
+ var requireCjs = createRequire(import.meta.url);
131
+ var encoderState;
132
+ var HEURISTIC_CHARS_PER_TOKEN = 4;
133
+ function loadEncoder() {
134
+ if (encoderState !== void 0) return encoderState;
135
+ try {
136
+ const mod = requireCjs("gpt-tokenizer/encoding/o200k_base");
137
+ if (mod && typeof mod.encode === "function") {
138
+ encoderState = { encode: mod.encode.bind(mod) };
139
+ } else {
140
+ encoderState = null;
141
+ }
142
+ } catch {
143
+ encoderState = null;
144
+ }
145
+ return encoderState;
146
+ }
147
+ function heuristicCount(text) {
148
+ return {
149
+ tokens: Math.ceil(text.length / HEURISTIC_CHARS_PER_TOKEN),
150
+ source: "heuristic"
151
+ };
152
+ }
153
+ function bpeSource(family) {
154
+ return family === "openai" ? "tokenizer-exact" : "tokenizer-approx";
155
+ }
156
+ var DefaultTokenizer = class {
157
+ count(text, family) {
158
+ const encoder = loadEncoder();
159
+ if (!encoder) return heuristicCount(text);
160
+ try {
161
+ return { tokens: encoder.encode(text).length, source: bpeSource(family) };
162
+ } catch {
163
+ return heuristicCount(text);
164
+ }
165
+ }
166
+ countValue(value, family) {
167
+ return this.count(canonicalString(value), family);
168
+ }
169
+ };
170
+ function canonicalString(value) {
171
+ if (typeof value === "string") return value;
172
+ const json = JSON.stringify(value);
173
+ return json ?? "";
174
+ }
175
+ var SHARED_TOKENIZER = new DefaultTokenizer();
176
+ function getTokenizer() {
177
+ return SHARED_TOKENIZER;
178
+ }
179
+ function inferModelFamily(clientName, hint) {
180
+ if (hint !== "auto") return hint;
181
+ const name = clientName.toLowerCase();
182
+ if (name.includes("claude") || name.includes("anthropic")) return "anthropic";
183
+ if (name.includes("codex") || name.includes("gpt") || name.includes("openai")) {
184
+ return "openai";
185
+ }
186
+ return "generic";
187
+ }
188
+
189
+ export {
190
+ worstConfidence,
191
+ newRecordId,
192
+ openStore,
193
+ getTokenizer,
194
+ inferModelFamily
195
+ };
196
+ //# sourceMappingURL=chunk-BCHWFTV4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/telemetry/store.ts","../src/telemetry/types.ts","../src/telemetry/tokenizer.ts"],"sourcesContent":["/**\n * telemetry/store — the default NDJSON {@link TelemetryStore} implementation.\n *\n * Local-first, dependency-free persistence. One JSON object per line; appends\n * are atomic enough for concurrent multi-host writers (a single `appendFileSync`\n * of a `<line>\\n` chunk is written in one `write(2)` for small payloads, so\n * interleaving across processes does not corrupt individual records). A\n * SQLite/WAL backend is a drop-in upgrade behind the same interface.\n *\n * Telemetry stores AGGREGATE COUNTS ONLY — raw tool arguments/results are never\n * persisted (enforced by the {@link ToolEventRecord} shape, which carries no\n * content fields).\n *\n * Global kill switch: AGENT_CONNECTOR_TELEMETRY=0 makes `append` a no-op so a\n * disabled telemetry layer can never touch disk or break a host's tool call.\n */\n\nimport { appendFileSync, existsSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nimport { ensureDir, telemetryPath } from \"../core/paths.js\";\nimport { worstConfidence } from \"./types.js\";\nimport type {\n QueryFilter,\n RollupRow,\n TelemetryStore,\n ToolEventRecord,\n} from \"./types.js\";\n\n/** Options for {@link openStore}. */\nexport interface OpenStoreOptions {\n /** Override the NDJSON file path. Defaults to telemetryPath(\"ndjson\"). */\n path?: string;\n}\n\n// Confidence ranking + worst-of comparison live in ./types (the single source\n// of truth) so a new ConfidenceSource value orders correctly everywhere.\n\n/** Is `process.env.AGENT_CONNECTOR_TELEMETRY` an explicit off switch? */\nfunction telemetryDisabled(): boolean {\n return process.env.AGENT_CONNECTOR_TELEMETRY === \"0\";\n}\n\n/**\n * A unique-enough record id. Combines the wall clock with a caller-supplied\n * sequence so two records written in the same millisecond still differ.\n */\nexport function newRecordId(seq: number): string {\n return `${Date.now()}-${seq}`;\n}\n\nclass NdjsonStore implements TelemetryStore {\n private readonly path: string;\n\n constructor(path: string) {\n this.path = path;\n }\n\n append(record: ToolEventRecord): void {\n if (telemetryDisabled()) return;\n ensureDir(dirname(this.path));\n appendFileSync(this.path, `${JSON.stringify(record)}\\n`, \"utf8\");\n }\n\n query(filter: QueryFilter): ToolEventRecord[] {\n if (!existsSync(this.path)) return [];\n\n let text: string;\n try {\n text = readFileSync(this.path, \"utf8\");\n } catch {\n return [];\n }\n\n const out: ToolEventRecord[] = [];\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed === \"\") continue;\n\n let record: ToolEventRecord;\n try {\n record = JSON.parse(trimmed) as ToolEventRecord;\n } catch {\n continue; // skip malformed lines\n }\n\n if (matches(record, filter)) out.push(record);\n }\n return out;\n }\n\n rollup(by: \"tool\" | \"session\" | \"project\", filter: QueryFilter): RollupRow[] {\n const groups = new Map<string, RollupRow>();\n\n for (const record of this.query(filter)) {\n const key = groupKey(record, by);\n const existing = groups.get(key);\n if (existing === undefined) {\n groups.set(key, {\n key,\n calls: 1,\n inputTokens: record.inputTokens,\n outputTokens: record.outputTokens,\n totalTokens: record.inputTokens + record.outputTokens,\n confidence: record.confidenceSource,\n lastTs: record.ts,\n });\n } else {\n existing.calls += 1;\n existing.inputTokens += record.inputTokens;\n existing.outputTokens += record.outputTokens;\n existing.totalTokens += record.inputTokens + record.outputTokens;\n existing.confidence = worstConfidence(\n existing.confidence,\n record.confidenceSource,\n );\n if (record.ts > existing.lastTs) existing.lastTs = record.ts;\n }\n }\n\n return [...groups.values()];\n }\n\n close(): void {\n /* no-op for the NDJSON backend */\n }\n}\n\n/** Does a record satisfy every set field of the filter? */\nfunction matches(record: ToolEventRecord, filter: QueryFilter): boolean {\n if (\n filter.connectorId !== undefined &&\n record.connectorId !== filter.connectorId\n ) {\n return false;\n }\n if (\n filter.projectKey !== undefined &&\n record.projectKey !== filter.projectKey\n ) {\n return false;\n }\n if (filter.sessionId !== undefined && record.sessionId !== filter.sessionId) {\n return false;\n }\n if (filter.toolName !== undefined && record.toolName !== filter.toolName) {\n return false;\n }\n if (filter.sinceMs !== undefined && record.ts < filter.sinceMs) {\n return false;\n }\n return true;\n}\n\n/** Resolve the grouping key value for a record under the requested dimension. */\nfunction groupKey(\n record: ToolEventRecord,\n by: \"tool\" | \"session\" | \"project\",\n): string {\n switch (by) {\n case \"tool\":\n return record.toolName;\n case \"session\":\n return record.sessionId;\n case \"project\":\n return record.projectDir;\n }\n}\n\n/**\n * Open the default local telemetry store. NDJSON backend; no native deps. The\n * returned store is safe to use even when telemetry is disabled — `append`\n * becomes a no-op and reads of a missing file return empty results.\n */\nexport function openStore(opts?: OpenStoreOptions): TelemetryStore {\n const path = opts?.path ?? telemetryPath(\"ndjson\");\n return new NdjsonStore(path);\n}\n","/**\n * telemetry/types — runtime telemetry contracts.\n *\n * The framework measures the MCP server's OWN bytes (tool args in, tool result\n * out, tool-definition schemas) and tokenizes them locally — the only signal\n * identical across every host (no host reports per-tool usage to a server).\n * Records are aggregate counts only; raw arguments/results are NEVER stored.\n */\n\nimport type { PlatformId } from \"../core/types.js\";\n\n/** Provenance of a token count — surfaced so \"estimate\" is never read as \"exact\". */\nexport type ConfidenceSource =\n | \"tokenizer-exact\" // real BPE for a matching model family\n | \"tokenizer-calibrated\" // approx adjusted by a sampled Anthropic count_tokens factor\n | \"tokenizer-approx\" // real BPE used as a documented approximation (e.g. Anthropic)\n | \"heuristic\" // chars/4 fallback\n | \"host-native\"; // a host actually reported usage (e.g. Gemini AfterModel)\n\n/**\n * Confidence ranking, least-trustworthy (0) → most-trustworthy. The single\n * source of truth for ordering {@link ConfidenceSource} values; every\n * worst-of comparison across the telemetry subsystem (store rollup, both\n * leaderboards, report, measure) reads through {@link rankOf} /\n * {@link worstConfidence} so a new value orders correctly everywhere.\n *\n * heuristic < tokenizer-approx < tokenizer-calibrated < tokenizer-exact < host-native\n *\n * `tokenizer-calibrated` sits between the raw approximation and an exact\n * family-matched BPE count: it is an approximation nudged toward truth by a\n * sampled real count_tokens factor, so it is more trustworthy than bare approx\n * but still not the exact encoding for the target family.\n */\nexport const CONFIDENCE_RANK: Record<ConfidenceSource, number> = {\n heuristic: 0,\n \"tokenizer-approx\": 1,\n \"tokenizer-calibrated\": 2,\n \"tokenizer-exact\": 3,\n \"host-native\": 4,\n};\n\n/** Trust rank of a single {@link ConfidenceSource} (higher = more trustworthy). */\nexport function rankOf(c: ConfidenceSource): number {\n return CONFIDENCE_RANK[c];\n}\n\n/** Return whichever source is the worse (least-confident) of the two. */\nexport function worstConfidence(\n a: ConfidenceSource,\n b: ConfidenceSource,\n): ConfidenceSource {\n return CONFIDENCE_RANK[b] < CONFIDENCE_RANK[a] ? b : a;\n}\n\nexport type ModelFamily = \"openai\" | \"anthropic\" | \"generic\";\n\nexport interface TokenCount {\n tokens: number;\n source: ConfidenceSource;\n}\n\n/** Pluggable tokenizer. Default impl uses gpt-tokenizer; fallback is chars/4. */\nexport interface Tokenizer {\n /** Count tokens for a string under the given model family. */\n count(text: string, family: ModelFamily): TokenCount;\n /** Count tokens for an arbitrary JSON value (serialized canonically first). */\n countValue(value: unknown, family: ModelFamily): TokenCount;\n}\n\n/**\n * What a record measures. DISTINCT origins that must never be summed:\n * • `call` — one per-MCP `tools/call` round-trip (serve-proxy bytes).\n * • `tool_defs` — the one-time `tools/list` schema overhead (serve-proxy).\n * • `model_turn` — a WHOLE-CONVERSATION host-native turn reported by the host\n * (e.g. Gemini/Antigravity AfterModel `usageMetadata`). This is NOT per-MCP:\n * it covers the entire model turn, so the MCP leaderboard EXCLUDES it (just\n * as it treats `tool_defs` specially) and it is surfaced as its own labeled\n * section rather than added to the per-MCP `call` totals or to the\n * usage-reader host-scan numbers.\n * • `hook` — one RUNTIME hook dispatch through the home-bin `hook`\n * entrypoint (src/runtime/hook-entrypoint). The framework tokenizes the\n * inbound normalized event payload the handler reads (input) and what the\n * handler returns that becomes context/decision (output). The per-item\n * `toolName` is the hook EVENT name (e.g. \"SessionStart\"). This is the\n * developer-axis \"hook\" surface — measured live, like `call`.\n */\nexport type EventScope = \"call\" | \"tool_defs\" | \"model_turn\" | \"hook\";\n\n/**\n * Which of the FIVE developer-axis surfaces a record (or footprint) belongs to.\n * `server`/`hook` are RUNTIME-measured surfaces that produce {@link ToolEventRecord}\n * store rows; `command`/`skill`/`subagent` are STATIC footprints computed from the\n * registered connector (the host loads them as context — we never intercept them,\n * so they are NEVER written as usage rows). OPTIONAL on the record so rows written\n * before this field existed (every legacy serve-proxy row) read as `server`.\n */\nexport type SurfaceKind = \"server\" | \"hook\" | \"command\" | \"skill\" | \"subagent\";\n\n/**\n * The install scope a wrapped server was deployed under, narrowed to the two\n * dimensions that matter for slicing telemetry: a `user`-global install vs a\n * `project`-local one. The framework's broader {@link InstallScope}\n * (`system|user|project|profile|managed`) is mapped down to this at wrap time —\n * everything that is not project-local reads as `user`. Optional on the record\n * so rows written before this field existed are treated as \"unknown\".\n */\nexport type TelemetryInstallScope = \"user\" | \"project\";\n\n/**\n * How the real MCP server underneath the proxy was launched. `npx`/`bunx`/`uvx`\n * are ephemeral package runners; `node`/`bun`/`deno` are interpreters running a\n * local script; `binary` is a resolved executable on PATH; `http` marks a remote\n * server reached over the network (no local launch); `unknown` is the honest\n * fallback. Optional on the record so older rows read as \"unknown\".\n */\nexport type LaunchMethod =\n | \"npx\"\n | \"bunx\"\n | \"uvx\"\n | \"node\"\n | \"binary\"\n | \"http\"\n | \"unknown\";\n\n/**\n * One telemetry row. Aggregate counts only — no content. `projectKey` is the\n * hashed stable project identity (git remote || normalized abs path); `projectDir`\n * is kept human-readable for reports but is the same partition.\n */\nexport interface ToolEventRecord {\n /** Monotonic-ish unique id (e.g. `${ts}-${seq}`). */\n id: string;\n /** Epoch milliseconds. */\n ts: number;\n connectorId: string;\n toolName: string;\n scope: EventScope;\n hostPlatform: PlatformId;\n sessionId: string;\n projectKey: string;\n projectDir: string;\n inputTokens: number;\n outputTokens: number;\n confidenceSource: ConfidenceSource;\n isError: boolean;\n /**\n * The (narrowed) install scope the wrapped server was deployed under — a\n * slicing dimension for global(user) vs project usage. OPTIONAL: rows written\n * before this field existed lack it and must be read as \"unknown\".\n */\n installScope?: TelemetryInstallScope;\n /**\n * How the real server was launched (npx/bunx/uvx/node/binary/http) — the\n * \"launch-method\" slicing dimension. OPTIONAL: older rows lack it → \"unknown\".\n */\n launchMethod?: LaunchMethod;\n /**\n * Which developer-axis surface produced this row. OPTIONAL and\n * backward-compatible: rows written before this field existed (every legacy\n * serve-proxy `call`/`tool_defs` row) lack it and MUST be read as `server`.\n * Runtime rows stamp it explicitly (`server` for the proxy, `hook` for the\n * hook runtime); the static command/skill/subagent surfaces never produce\n * store rows (they are reported as footprints — see surface-footprint.ts).\n */\n surfaceKind?: SurfaceKind;\n}\n\nexport interface QueryFilter {\n connectorId?: string;\n projectKey?: string;\n sessionId?: string;\n toolName?: string;\n /** Lower bound epoch ms (inclusive). */\n sinceMs?: number;\n}\n\n/** A grouped rollup row for reports. */\nexport interface RollupRow {\n /** The grouping key value (tool name, session id, or project dir). */\n key: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n /** Worst (least-confident) source seen in the group, for honest labeling. */\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * Local-first telemetry store. The default NDJSON implementation appends one\n * line per record (append is atomic enough for concurrent multi-host writers);\n * a SQLite/WAL implementation is a drop-in upgrade behind this same interface.\n */\nexport interface TelemetryStore {\n append(record: ToolEventRecord): void;\n query(filter: QueryFilter): ToolEventRecord[];\n /** Group + aggregate by tool | session | project. */\n rollup(by: \"tool\" | \"session\" | \"project\", filter: QueryFilter): RollupRow[];\n close(): void;\n}\n","/**\n * telemetry/tokenizer — the default {@link Tokenizer} implementation.\n *\n * Token counts are the only signal that is identical across every host: no host\n * reports a server's per-tool usage back to the server, so the framework\n * tokenizes its OWN bytes locally. We use gpt-tokenizer's `o200k_base` encoding\n * (the GPT-4o / o1 BPE) for all families:\n * • openai → exact BPE for the target family → \"tokenizer-exact\"\n * • anthropic → o200k_base used as a documented stand-in → \"tokenizer-approx\"\n * • generic → o200k_base used as a generic approximation → \"tokenizer-approx\"\n *\n * gpt-tokenizer is loaded LAZILY on first use, inside a try/catch. If the module\n * (or its BPE data) fails to load we degrade to a chars/4 heuristic that never\n * throws, so telemetry can never break a host's tool call.\n *\n * Pure + side-effect-free apart from the one-time lazy encoder load. No IO.\n */\n\nimport { createRequire } from \"node:module\";\n\nimport type {\n ConfidenceSource,\n ModelFamily,\n TokenCount,\n Tokenizer,\n} from \"./types.js\";\n\n/**\n * A CommonJS `require` bound to this module's URL. gpt-tokenizer ships its\n * encoding entry points as CJS (the package `exports` `require` condition maps\n * `./encoding/*` to `./cjs/encoding/*.js`), so we resolve them synchronously via\n * createRequire — this keeps the {@link Tokenizer} surface synchronous and works\n * under ESM / NodeNext without baking an absolute path.\n */\nconst requireCjs = createRequire(import.meta.url);\n\n/** Minimal shape we use from a gpt-tokenizer encoding module. */\ninterface EncodingModule {\n encode(text: string): number[];\n}\n\n/**\n * Lazy-load state for the shared o200k_base encoder.\n * - undefined → not attempted yet\n * - null → load attempted and failed (stick to the heuristic)\n * - object → loaded encoder\n */\nlet encoderState: EncodingModule | null | undefined;\n\n/** Average characters-per-token for the chars/4 fallback heuristic. */\nconst HEURISTIC_CHARS_PER_TOKEN = 4;\n\n/**\n * Lazily import the o200k_base encoding. Returns the encoder, or null if the\n * module/data could not be loaded (in which case callers use the heuristic).\n *\n * The dynamic import resolves to `gpt-tokenizer/esm/encoding/o200k_base.js` via\n * the package `exports` map and exposes a named `encode(text) => number[]`.\n */\nfunction loadEncoder(): EncodingModule | null {\n if (encoderState !== undefined) return encoderState;\n try {\n // Synchronous require keeps count() a sync function (per the Tokenizer\n // interface). The package's `require` export condition resolves this to\n // gpt-tokenizer/cjs/encoding/o200k_base.js, which exports a named `encode`.\n const mod = requireCjs(\"gpt-tokenizer/encoding/o200k_base\") as {\n encode?: (text: string) => number[];\n };\n if (mod && typeof mod.encode === \"function\") {\n encoderState = { encode: mod.encode.bind(mod) };\n } else {\n encoderState = null;\n }\n } catch {\n encoderState = null;\n }\n return encoderState;\n}\n\n/** Heuristic chars/4 count — used when no encoder is available. Never throws. */\nfunction heuristicCount(text: string): TokenCount {\n return {\n tokens: Math.ceil(text.length / HEURISTIC_CHARS_PER_TOKEN),\n source: \"heuristic\",\n };\n}\n\n/**\n * Confidence label for a successful BPE count: exact only for the OpenAI family\n * (the encoding actually matches), approximate for everything else.\n */\nfunction bpeSource(family: ModelFamily): ConfidenceSource {\n return family === \"openai\" ? \"tokenizer-exact\" : \"tokenizer-approx\";\n}\n\n/**\n * The default tokenizer. A single shared instance is fine: the encoder is\n * stateless across calls and lazy-loaded once.\n */\nclass DefaultTokenizer implements Tokenizer {\n count(text: string, family: ModelFamily): TokenCount {\n const encoder = loadEncoder();\n if (!encoder) return heuristicCount(text);\n try {\n return { tokens: encoder.encode(text).length, source: bpeSource(family) };\n } catch {\n // A per-call encode failure (e.g. a pathological input) still degrades\n // gracefully rather than breaking the host's tool call.\n return heuristicCount(text);\n }\n }\n\n countValue(value: unknown, family: ModelFamily): TokenCount {\n return this.count(canonicalString(value), family);\n }\n}\n\n/**\n * Canonical string form of an arbitrary JSON value for counting.\n * `undefined` (and anything that JSON.stringify drops to undefined, e.g. a bare\n * function or symbol) serializes to the empty string.\n */\nfunction canonicalString(value: unknown): string {\n if (typeof value === \"string\") return value;\n const json = JSON.stringify(value);\n return json ?? \"\";\n}\n\nconst SHARED_TOKENIZER: Tokenizer = new DefaultTokenizer();\n\n/** Return the default {@link Tokenizer} (shared singleton). */\nexport function getTokenizer(): Tokenizer {\n return SHARED_TOKENIZER;\n}\n\n/**\n * Infer the {@link ModelFamily} for token counting.\n *\n * An explicit hint other than \"auto\" wins outright. Otherwise we sniff the\n * client/host name: Anthropic/Claude → anthropic, OpenAI/GPT/Codex → openai,\n * Gemini and everything unrecognized → generic.\n */\nexport function inferModelFamily(\n clientName: string,\n hint: \"auto\" | ModelFamily,\n): ModelFamily {\n if (hint !== \"auto\") return hint;\n const name = clientName.toLowerCase();\n if (name.includes(\"claude\") || name.includes(\"anthropic\")) return \"anthropic\";\n if (\n name.includes(\"codex\") ||\n name.includes(\"gpt\") ||\n name.includes(\"openai\")\n ) {\n return \"openai\";\n }\n // gemini and all others fall through to the generic approximation.\n return \"generic\";\n}\n"],"mappings":";;;;;;;AAiBA,SAAS,gBAAgB,YAAY,oBAAoB;AACzD,SAAS,eAAe;;;ACejB,IAAM,kBAAoD;AAAA,EAC/D,WAAW;AAAA,EACX,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,mBAAmB;AAAA,EACnB,eAAe;AACjB;AAQO,SAAS,gBACd,GACA,GACkB;AAClB,SAAO,gBAAgB,CAAC,IAAI,gBAAgB,CAAC,IAAI,IAAI;AACvD;;;ADbA,SAAS,oBAA6B;AACpC,SAAO,QAAQ,IAAI,8BAA8B;AACnD;AAMO,SAAS,YAAY,KAAqB;AAC/C,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,GAAG;AAC7B;AAEA,IAAM,cAAN,MAA4C;AAAA,EACzB;AAAA,EAEjB,YAAY,MAAc;AACxB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAO,QAA+B;AACpC,QAAI,kBAAkB,EAAG;AACzB,cAAU,QAAQ,KAAK,IAAI,CAAC;AAC5B,mBAAe,KAAK,MAAM,GAAG,KAAK,UAAU,MAAM,CAAC;AAAA,GAAM,MAAM;AAAA,EACjE;AAAA,EAEA,MAAM,QAAwC;AAC5C,QAAI,CAAC,WAAW,KAAK,IAAI,EAAG,QAAO,CAAC;AAEpC,QAAI;AACJ,QAAI;AACF,aAAO,aAAa,KAAK,MAAM,MAAM;AAAA,IACvC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,MAAyB,CAAC;AAChC,eAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,YAAY,GAAI;AAEpB,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,OAAO;AAAA,MAC7B,QAAQ;AACN;AAAA,MACF;AAEA,UAAI,QAAQ,QAAQ,MAAM,EAAG,KAAI,KAAK,MAAM;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,IAAoC,QAAkC;AAC3E,UAAM,SAAS,oBAAI,IAAuB;AAE1C,eAAW,UAAU,KAAK,MAAM,MAAM,GAAG;AACvC,YAAM,MAAM,SAAS,QAAQ,EAAE;AAC/B,YAAM,WAAW,OAAO,IAAI,GAAG;AAC/B,UAAI,aAAa,QAAW;AAC1B,eAAO,IAAI,KAAK;AAAA,UACd;AAAA,UACA,OAAO;AAAA,UACP,aAAa,OAAO;AAAA,UACpB,cAAc,OAAO;AAAA,UACrB,aAAa,OAAO,cAAc,OAAO;AAAA,UACzC,YAAY,OAAO;AAAA,UACnB,QAAQ,OAAO;AAAA,QACjB,CAAC;AAAA,MACH,OAAO;AACL,iBAAS,SAAS;AAClB,iBAAS,eAAe,OAAO;AAC/B,iBAAS,gBAAgB,OAAO;AAChC,iBAAS,eAAe,OAAO,cAAc,OAAO;AACpD,iBAAS,aAAa;AAAA,UACpB,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AACA,YAAI,OAAO,KAAK,SAAS,OAAQ,UAAS,SAAS,OAAO;AAAA,MAC5D;AAAA,IACF;AAEA,WAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAAA,EAC5B;AAAA,EAEA,QAAc;AAAA,EAEd;AACF;AAGA,SAAS,QAAQ,QAAyB,QAA8B;AACtE,MACE,OAAO,gBAAgB,UACvB,OAAO,gBAAgB,OAAO,aAC9B;AACA,WAAO;AAAA,EACT;AACA,MACE,OAAO,eAAe,UACtB,OAAO,eAAe,OAAO,YAC7B;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,cAAc,UAAa,OAAO,cAAc,OAAO,WAAW;AAC3E,WAAO;AAAA,EACT;AACA,MAAI,OAAO,aAAa,UAAa,OAAO,aAAa,OAAO,UAAU;AACxE,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,UAAa,OAAO,KAAK,OAAO,SAAS;AAC9D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,SACP,QACA,IACQ;AACR,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,EAClB;AACF;AAOO,SAAS,UAAU,MAAyC;AACjE,QAAM,OAAO,MAAM,QAAQ,cAAc,QAAQ;AACjD,SAAO,IAAI,YAAY,IAAI;AAC7B;;;AE/JA,SAAS,qBAAqB;AAgB9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAahD,IAAI;AAGJ,IAAM,4BAA4B;AASlC,SAAS,cAAqC;AAC5C,MAAI,iBAAiB,OAAW,QAAO;AACvC,MAAI;AAIF,UAAM,MAAM,WAAW,mCAAmC;AAG1D,QAAI,OAAO,OAAO,IAAI,WAAW,YAAY;AAC3C,qBAAe,EAAE,QAAQ,IAAI,OAAO,KAAK,GAAG,EAAE;AAAA,IAChD,OAAO;AACL,qBAAe;AAAA,IACjB;AAAA,EACF,QAAQ;AACN,mBAAe;AAAA,EACjB;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAA0B;AAChD,SAAO;AAAA,IACL,QAAQ,KAAK,KAAK,KAAK,SAAS,yBAAyB;AAAA,IACzD,QAAQ;AAAA,EACV;AACF;AAMA,SAAS,UAAU,QAAuC;AACxD,SAAO,WAAW,WAAW,oBAAoB;AACnD;AAMA,IAAM,mBAAN,MAA4C;AAAA,EAC1C,MAAM,MAAc,QAAiC;AACnD,UAAM,UAAU,YAAY;AAC5B,QAAI,CAAC,QAAS,QAAO,eAAe,IAAI;AACxC,QAAI;AACF,aAAO,EAAE,QAAQ,QAAQ,OAAO,IAAI,EAAE,QAAQ,QAAQ,UAAU,MAAM,EAAE;AAAA,IAC1E,QAAQ;AAGN,aAAO,eAAe,IAAI;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,WAAW,OAAgB,QAAiC;AAC1D,WAAO,KAAK,MAAM,gBAAgB,KAAK,GAAG,MAAM;AAAA,EAClD;AACF;AAOA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,OAAO,KAAK,UAAU,KAAK;AACjC,SAAO,QAAQ;AACjB;AAEA,IAAM,mBAA8B,IAAI,iBAAiB;AAGlD,SAAS,eAA0B;AACxC,SAAO;AACT;AASO,SAAS,iBACd,YACA,MACa;AACb,MAAI,SAAS,OAAQ,QAAO;AAC5B,QAAM,OAAO,WAAW,YAAY;AACpC,MAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,WAAW,EAAG,QAAO;AAClE,MACE,KAAK,SAAS,OAAO,KACrB,KAAK,SAAS,KAAK,KACnB,KAAK,SAAS,QAAQ,GACtB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/interpolate.ts
4
+ var ENV_REF_RE = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
5
+ function resolveEnvRefs(input, env = process.env) {
6
+ return input.replace(ENV_REF_RE, (_m, name, def) => {
7
+ const v = env[name];
8
+ if (v != null && v !== "") return v;
9
+ return def ?? "";
10
+ });
11
+ }
12
+ function resolveEnvRefsDeep(value, env = process.env) {
13
+ if (typeof value === "string") return resolveEnvRefs(value, env);
14
+ if (Array.isArray(value)) {
15
+ return value.map((v) => resolveEnvRefsDeep(v, env));
16
+ }
17
+ if (value && typeof value === "object") {
18
+ const out = {};
19
+ for (const [k, v] of Object.entries(value)) out[k] = resolveEnvRefsDeep(v, env);
20
+ return out;
21
+ }
22
+ return value;
23
+ }
24
+ function rewriteEnvRefs(input, render) {
25
+ return input.replace(
26
+ ENV_REF_RE,
27
+ (_m, name, def) => render(name, def)
28
+ );
29
+ }
30
+
31
+ export {
32
+ resolveEnvRefs,
33
+ resolveEnvRefsDeep,
34
+ rewriteEnvRefs
35
+ };
36
+ //# sourceMappingURL=chunk-DBQMX75U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/interpolate.ts"],"sourcesContent":["/**\n * core/interpolate — universal ${env:VAR} interpolation.\n *\n * The developer writes one portable syntax in their connector config:\n * \"${env:ACME_KEY}\" → value of process.env.ACME_KEY\n * \"${env:ACME_URL:-https://x}\" → value, or the default after \":-\" if unset/empty\n *\n * Two consumers:\n * • resolveEnvRefs / resolveEnvRefsDeep — resolve to literals at INSTALL time,\n * for hosts with NO native interpolation (e.g. Codex TOML).\n * • rewriteEnvRefs — translate to a host's NATIVE interpolation token (e.g.\n * Cursor/VS Code \"${env:VAR}\", Claude \"${VAR}\") so secrets are never baked\n * into config files. Adapters choose which to use.\n */\n\n/** Matches ${env:NAME} and ${env:NAME:-default}. NAME is [A-Za-z_][A-Za-z0-9_]*. */\nexport const ENV_REF_RE = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g;\n\n/** Resolve every ${env:VAR} in `input` to a literal from `env`. */\nexport function resolveEnvRefs(\n input: string,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n return input.replace(ENV_REF_RE, (_m, name: string, def?: string) => {\n const v = env[name];\n if (v != null && v !== \"\") return v;\n return def ?? \"\";\n });\n}\n\n/** Recursively resolve ${env:VAR} in all strings of a JSON-ish value. */\nexport function resolveEnvRefsDeep<T>(value: T, env: NodeJS.ProcessEnv = process.env): T {\n if (typeof value === \"string\") return resolveEnvRefs(value, env) as unknown as T;\n if (Array.isArray(value)) {\n return value.map((v) => resolveEnvRefsDeep(v, env)) as unknown as T;\n }\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = resolveEnvRefsDeep(v, env);\n return out as T;\n }\n return value;\n}\n\n/**\n * Translate every ${env:VAR(:-default)} via `render` into a host-native token.\n * The renderer receives the var name and optional default and returns the\n * replacement string (e.g. `(name) => \"${\" + name + \"}\"` for Claude).\n */\nexport function rewriteEnvRefs(\n input: string,\n render: (name: string, def?: string) => string,\n): string {\n return input.replace(ENV_REF_RE, (_m, name: string, def?: string) =>\n render(name, def),\n );\n}\n\n/** True if the string contains at least one ${env:...} reference. */\nexport function hasEnvRef(input: string): boolean {\n ENV_REF_RE.lastIndex = 0;\n return ENV_REF_RE.test(input);\n}\n"],"mappings":";;;AAgBO,IAAM,aAAa;AAGnB,SAAS,eACd,OACA,MAAyB,QAAQ,KACzB;AACR,SAAO,MAAM,QAAQ,YAAY,CAAC,IAAI,MAAc,QAAiB;AACnE,UAAM,IAAI,IAAI,IAAI;AAClB,QAAI,KAAK,QAAQ,MAAM,GAAI,QAAO;AAClC,WAAO,OAAO;AAAA,EAChB,CAAC;AACH;AAGO,SAAS,mBAAsB,OAAU,MAAyB,QAAQ,KAAQ;AACvF,MAAI,OAAO,UAAU,SAAU,QAAO,eAAe,OAAO,GAAG;AAC/D,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAAA,EACpD;AACA,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,CAAC,IAAI,mBAAmB,GAAG,GAAG;AAC9E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,eACd,OACA,QACQ;AACR,SAAO,MAAM;AAAA,IAAQ;AAAA,IAAY,CAAC,IAAI,MAAc,QAClD,OAAO,MAAM,GAAG;AAAA,EAClB;AACF;","names":[]}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureDir
4
+ } from "./chunk-TRQKHP7D.js";
5
+
6
+ // src/core/yaml.ts
7
+ import { existsSync, readFileSync, writeFileSync } from "fs";
8
+ import { dirname } from "path";
9
+ import { parse, stringify } from "yaml";
10
+ function readYaml(path) {
11
+ if (!existsSync(path)) return null;
12
+ try {
13
+ const parsed = parse(readFileSync(path, "utf8"));
14
+ return parsed == null ? null : parsed;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function writeYaml(path, data, dryRun = false) {
20
+ if (dryRun) return;
21
+ ensureDir(dirname(path));
22
+ writeFileSync(path, stringify(data), "utf8");
23
+ }
24
+
25
+ export {
26
+ readYaml,
27
+ writeYaml
28
+ };
29
+ //# sourceMappingURL=chunk-EXLS7BSI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/yaml.ts"],"sourcesContent":["/**\n * core/yaml — minimal YAML config IO.\n *\n * The YAML analog of BaseAdapter's JSON helpers (readJson/writeJson). Adapters\n * whose native config is YAML (Goose's config.yaml, Hermes's config.yaml) cannot\n * use the JSON helpers, so they reach for these two functions instead — same\n * existsSync-guard / parse-failure-tolerant / dryRun-respecting contract, just\n * over the `yaml` package's parse()/stringify() rather than JSON.parse/stringify.\n *\n * Deliberately tiny: read-or-null and ensureDir+write. All merge/upsert/idempotency\n * logic lives in each adapter (the shapes differ per platform), exactly as the\n * JSON adapters layer their own upsert on top of writeJson.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nimport { parse, stringify } from \"yaml\";\n\nimport { ensureDir } from \"./paths.js\";\n\n/**\n * Read and parse a YAML file. Returns `null` when the file is missing OR when it\n * fails to parse (mirrors BaseAdapter.readJson's fail-soft behavior so a corrupt\n * file degrades to \"treat as absent\" rather than throwing mid-install).\n */\nexport function readYaml<T = Record<string, unknown>>(path: string): T | null {\n if (!existsSync(path)) return null;\n try {\n const parsed = parse(readFileSync(path, \"utf8\")) as T | null | undefined;\n // An empty YAML document parses to null/undefined — normalize to null.\n return parsed == null ? null : parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Serialize `data` to YAML and write it to `path`, creating parent dirs as\n * needed. Honors `dryRun` (no write). Mirrors BaseAdapter.writeJson's signature.\n */\nexport function writeYaml(path: string, data: unknown, dryRun = false): void {\n if (dryRun) return;\n ensureDir(dirname(path));\n writeFileSync(path, stringify(data), \"utf8\");\n}\n"],"mappings":";;;;;;AAcA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,eAAe;AAExB,SAAS,OAAO,iBAAiB;AAS1B,SAAS,SAAsC,MAAwB;AAC5E,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,SAAS,MAAM,aAAa,MAAM,MAAM,CAAC;AAE/C,WAAO,UAAU,OAAO,OAAO;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,UAAU,MAAc,MAAe,SAAS,OAAa;AAC3E,MAAI,OAAQ;AACZ,YAAU,QAAQ,IAAI,CAAC;AACvB,gBAAc,MAAM,UAAU,IAAI,GAAG,MAAM;AAC7C;","names":[]}