@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.
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +348 -0
- package/dist/amp-QSFH2V7Y.js +217 -0
- package/dist/amp-QSFH2V7Y.js.map +1 -0
- package/dist/amp-TULUPXXL.js +237 -0
- package/dist/amp-TULUPXXL.js.map +1 -0
- package/dist/antigravity-CX2YLSY7.js +17 -0
- package/dist/antigravity-CX2YLSY7.js.map +1 -0
- package/dist/antigravity-STHZWMP3.js +171 -0
- package/dist/antigravity-STHZWMP3.js.map +1 -0
- package/dist/antigravity-cli-P6HADOAO.js +121 -0
- package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
- package/dist/antigravity-cli-YQDFVV4U.js +86 -0
- package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
- package/dist/chunk-54EMKTTX.js +191 -0
- package/dist/chunk-54EMKTTX.js.map +1 -0
- package/dist/chunk-7L6ITC4T.js +54 -0
- package/dist/chunk-7L6ITC4T.js.map +1 -0
- package/dist/chunk-7MSDGHJW.js +316 -0
- package/dist/chunk-7MSDGHJW.js.map +1 -0
- package/dist/chunk-BCHWFTV4.js +196 -0
- package/dist/chunk-BCHWFTV4.js.map +1 -0
- package/dist/chunk-DBQMX75U.js +36 -0
- package/dist/chunk-DBQMX75U.js.map +1 -0
- package/dist/chunk-EXLS7BSI.js +29 -0
- package/dist/chunk-EXLS7BSI.js.map +1 -0
- package/dist/chunk-JNM3TPLX.js +593 -0
- package/dist/chunk-JNM3TPLX.js.map +1 -0
- package/dist/chunk-JWNT5KW7.js +197 -0
- package/dist/chunk-JWNT5KW7.js.map +1 -0
- package/dist/chunk-KHO5WNTP.js +30 -0
- package/dist/chunk-KHO5WNTP.js.map +1 -0
- package/dist/chunk-LTT56GI3.js +155 -0
- package/dist/chunk-LTT56GI3.js.map +1 -0
- package/dist/chunk-MEH7GC7R.js +111 -0
- package/dist/chunk-MEH7GC7R.js.map +1 -0
- package/dist/chunk-MQPNU2CY.js +64 -0
- package/dist/chunk-MQPNU2CY.js.map +1 -0
- package/dist/chunk-OPXAP3AC.js +12 -0
- package/dist/chunk-OPXAP3AC.js.map +1 -0
- package/dist/chunk-RK6VBLXP.js +50 -0
- package/dist/chunk-RK6VBLXP.js.map +1 -0
- package/dist/chunk-TIIP3RG2.js +420 -0
- package/dist/chunk-TIIP3RG2.js.map +1 -0
- package/dist/chunk-TRKWUNO4.js +702 -0
- package/dist/chunk-TRKWUNO4.js.map +1 -0
- package/dist/chunk-TRQKHP7D.js +93 -0
- package/dist/chunk-TRQKHP7D.js.map +1 -0
- package/dist/chunk-WCETQWWK.js +59 -0
- package/dist/chunk-WCETQWWK.js.map +1 -0
- package/dist/chunk-WV63X7IK.js +357 -0
- package/dist/chunk-WV63X7IK.js.map +1 -0
- package/dist/chunk-XD7MY6HB.js +343 -0
- package/dist/chunk-XD7MY6HB.js.map +1 -0
- package/dist/chunk-XNAM7F35.js +276 -0
- package/dist/chunk-XNAM7F35.js.map +1 -0
- package/dist/chunk-XUCWLJY5.js +238 -0
- package/dist/chunk-XUCWLJY5.js.map +1 -0
- package/dist/chunk-YNZU6PAV.js +800 -0
- package/dist/chunk-YNZU6PAV.js.map +1 -0
- package/dist/chunk-YT43MUSJ.js +146 -0
- package/dist/chunk-YT43MUSJ.js.map +1 -0
- package/dist/claude-code-HKDV7T32.js +637 -0
- package/dist/claude-code-HKDV7T32.js.map +1 -0
- package/dist/claude-code-W74MNTAC.js +652 -0
- package/dist/claude-code-W74MNTAC.js.map +1 -0
- package/dist/cli/sdk.d.ts +54 -0
- package/dist/cli/sdk.js +82 -0
- package/dist/cli/sdk.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/codebuff-3PMZJJHP.js +307 -0
- package/dist/codebuff-3PMZJJHP.js.map +1 -0
- package/dist/codebuff-QX57FJDN.js +220 -0
- package/dist/codebuff-QX57FJDN.js.map +1 -0
- package/dist/codex-JFQTS6LU.js +593 -0
- package/dist/codex-JFQTS6LU.js.map +1 -0
- package/dist/codex-Q3ATRRX3.js +364 -0
- package/dist/codex-Q3ATRRX3.js.map +1 -0
- package/dist/copilot-cli-MTCGHZ2T.js +622 -0
- package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
- package/dist/copilot-cli-SJGIXVSD.js +369 -0
- package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
- package/dist/crush-I7PK3FGI.js +360 -0
- package/dist/crush-I7PK3FGI.js.map +1 -0
- package/dist/crush-LN5QFFR2.js +183 -0
- package/dist/crush-LN5QFFR2.js.map +1 -0
- package/dist/cursor-4TMXKJOZ.js +603 -0
- package/dist/cursor-4TMXKJOZ.js.map +1 -0
- package/dist/cursor-QZ6MTO74.js +194 -0
- package/dist/cursor-QZ6MTO74.js.map +1 -0
- package/dist/detect-WIAGVYS4.js +68 -0
- package/dist/detect-WIAGVYS4.js.map +1 -0
- package/dist/doctor-EZW2K4NI.js +372 -0
- package/dist/doctor-EZW2K4NI.js.map +1 -0
- package/dist/droid-KN6VRMMT.js +424 -0
- package/dist/droid-KN6VRMMT.js.map +1 -0
- package/dist/droid-W5WF5XEX.js +224 -0
- package/dist/droid-W5WF5XEX.js.map +1 -0
- package/dist/gemini-cli-CIRW37TU.js +369 -0
- package/dist/gemini-cli-CIRW37TU.js.map +1 -0
- package/dist/gemini-cli-Y35FFK4M.js +705 -0
- package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
- package/dist/goose-A3M5GODH.js +212 -0
- package/dist/goose-A3M5GODH.js.map +1 -0
- package/dist/goose-ZTGTSUFJ.js +480 -0
- package/dist/goose-ZTGTSUFJ.js.map +1 -0
- package/dist/hermes-H2KV44GK.js +470 -0
- package/dist/hermes-H2KV44GK.js.map +1 -0
- package/dist/hermes-ZYLMOFLZ.js +196 -0
- package/dist/hermes-ZYLMOFLZ.js.map +1 -0
- package/dist/hook-YVDILAMZ.js +75 -0
- package/dist/hook-YVDILAMZ.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/install-HQRYZOQ2.js +60 -0
- package/dist/install-HQRYZOQ2.js.map +1 -0
- package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
- package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
- package/dist/kilo-NSWAVP6P.js +235 -0
- package/dist/kilo-NSWAVP6P.js.map +1 -0
- package/dist/kilo-QXJRCSLT.js +384 -0
- package/dist/kilo-QXJRCSLT.js.map +1 -0
- package/dist/kilo-cli-EAQTHXUW.js +587 -0
- package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
- package/dist/kilo-cli-EVBKUODA.js +160 -0
- package/dist/kilo-cli-EVBKUODA.js.map +1 -0
- package/dist/kimi-EI3UM3ZV.js +155 -0
- package/dist/kimi-EI3UM3ZV.js.map +1 -0
- package/dist/kimi-GQ7VXNYT.js +423 -0
- package/dist/kimi-GQ7VXNYT.js.map +1 -0
- package/dist/kiro-UCLSLBW2.js +217 -0
- package/dist/kiro-UCLSLBW2.js.map +1 -0
- package/dist/kiro-VQTT6LO2.js +451 -0
- package/dist/kiro-VQTT6LO2.js.map +1 -0
- package/dist/leaderboard-K6ON3SWW.js +137 -0
- package/dist/leaderboard-K6ON3SWW.js.map +1 -0
- package/dist/mux-T2TV4CIQ.js +211 -0
- package/dist/mux-T2TV4CIQ.js.map +1 -0
- package/dist/mux-ZMLVAXOO.js +96 -0
- package/dist/mux-ZMLVAXOO.js.map +1 -0
- package/dist/omp-PB4VDKS6.js +586 -0
- package/dist/omp-PB4VDKS6.js.map +1 -0
- package/dist/openclaw-ETN2M63O.js +751 -0
- package/dist/openclaw-ETN2M63O.js.map +1 -0
- package/dist/openclaw-IYA7ASL6.js +150 -0
- package/dist/openclaw-IYA7ASL6.js.map +1 -0
- package/dist/opencode-FZ33JKUZ.js +734 -0
- package/dist/opencode-FZ33JKUZ.js.map +1 -0
- package/dist/opencode-HWT3RSMN.js +216 -0
- package/dist/opencode-HWT3RSMN.js.map +1 -0
- package/dist/package-DLOVE2E4.js +1132 -0
- package/dist/package-DLOVE2E4.js.map +1 -0
- package/dist/pi-CIU2QHT4.js +208 -0
- package/dist/pi-CIU2QHT4.js.map +1 -0
- package/dist/pi-UFX2OYFV.js +139 -0
- package/dist/pi-UFX2OYFV.js.map +1 -0
- package/dist/qwen-code-5POCNU6P.js +573 -0
- package/dist/qwen-code-5POCNU6P.js.map +1 -0
- package/dist/qwen-code-W7NUCDEN.js +134 -0
- package/dist/qwen-code-W7NUCDEN.js.map +1 -0
- package/dist/roo-code-IOIUXRDE.js +242 -0
- package/dist/roo-code-IOIUXRDE.js.map +1 -0
- package/dist/roo-code-ZLKH4A7N.js +229 -0
- package/dist/roo-code-ZLKH4A7N.js.map +1 -0
- package/dist/runtime/index.d.ts +157 -0
- package/dist/runtime/index.js +20 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/serve-HVWWY4B6.js +65 -0
- package/dist/serve-HVWWY4B6.js.map +1 -0
- package/dist/status-FE7TECZD.js +128 -0
- package/dist/status-FE7TECZD.js.map +1 -0
- package/dist/synthetic-UKR6SMGS.js +178 -0
- package/dist/synthetic-UKR6SMGS.js.map +1 -0
- package/dist/telemetry-36GESKQQ.js +349 -0
- package/dist/telemetry-36GESKQQ.js.map +1 -0
- package/dist/trae-JSNDZFKL.js +204 -0
- package/dist/trae-JSNDZFKL.js.map +1 -0
- package/dist/trae-PA26DPBW.js +157 -0
- package/dist/trae-PA26DPBW.js.map +1 -0
- package/dist/types-CYUB1OTr.d.ts +427 -0
- package/dist/uninstall-IGCKNOSF.js +72 -0
- package/dist/uninstall-IGCKNOSF.js.map +1 -0
- package/dist/upgrade-JV2VK242.js +104 -0
- package/dist/upgrade-JV2VK242.js.map +1 -0
- package/dist/usage-6ER6EMEY.js +324 -0
- package/dist/usage-6ER6EMEY.js.map +1 -0
- package/dist/usage-event-JKBI6XCF.js +48 -0
- package/dist/usage-event-JKBI6XCF.js.map +1 -0
- package/dist/vscode-copilot-CK3NO54M.js +715 -0
- package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
- package/dist/warp-JNQA7PBU.js +126 -0
- package/dist/warp-JNQA7PBU.js.map +1 -0
- package/dist/warp-QNVDXOG4.js +199 -0
- package/dist/warp-QNVDXOG4.js.map +1 -0
- package/dist/zed-NNNX5MNT.js +272 -0
- package/dist/zed-NNNX5MNT.js.map +1 -0
- package/dist/zed-VRJKWYUR.js +209 -0
- package/dist/zed-VRJKWYUR.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/telemetry/surface-footprint.ts","../src/telemetry/leaderboard.ts"],"sourcesContent":["/**\n * telemetry/surface-footprint — the STATIC developer-axis surfaces.\n *\n * Commands, skills, and subagents are NOT runtime-intercepted: the host loads\n * them as context (slash-command bodies, SKILL.md + resources, subagent system\n * prompts) and we never sit in the byte path the way the serve-proxy does for a\n * server or the hook runtime does for a hook. So we do NOT (and must not) write\n * fake usage rows for them. Instead we compute a deterministic STATIC FOOTPRINT:\n * the token cost the surface IMPOSES on a host that loads it, tokenized with the\n * SAME shared tokenizer the proxy + hook runtime use.\n *\n * This is pure: the only input is the passed {@link ResolvedConnector}; there is\n * no IO beyond reading that object's already-loaded fields.\n */\n\nimport type { ResolvedConnector } from \"../core/types.js\";\nimport { getTokenizer, inferModelFamily } from \"./tokenizer.js\";\nimport type { ModelFamily, SurfaceKind, Tokenizer } from \"./types.js\";\n\n/**\n * One static surface footprint: a content surface (command/skill/subagent) and\n * the token cost it imposes on a host that loads it. `kind` is the surface kind\n * (never `server`/`hook` — those are runtime-measured), `name` is the surface's\n * declared name, and `tokens` is the tokenized whole-surface footprint.\n */\nexport interface SurfaceFootprint {\n surfaceKind: Extract<SurfaceKind, \"command\" | \"skill\" | \"subagent\">;\n name: string;\n tokens: number;\n}\n\n/** Tokenize a list of text fragments (joined with \"\\n\") under `family`. */\nfunction countParts(\n parts: readonly string[],\n family: ModelFamily,\n tok: Tokenizer,\n): number {\n // A surface's footprint is its concatenated content — joining with a newline\n // mirrors how the host renders the fields together as one context blob.\n return tok.count(parts.join(\"\\n\"), family).tokens;\n}\n\n/**\n * Compute the STATIC footprints of every content surface declared on a\n * connector, deterministically (same input → same output) and without IO.\n *\n * • command → description + prompt + argumentHint\n * • skill → description + body + every resource value (sorted by path for\n * deterministic ordering, so the joined text is stable)\n * • subagent → description + prompt\n *\n * The shared {@link Tokenizer} is used so a command/skill/subagent footprint is\n * directly comparable with the runtime `server`/`hook` token counts. The model\n * family is inferred from the connector's telemetry hint (no host context here).\n */\nexport function computeSurfaceFootprints(\n connector: ResolvedConnector,\n tok: Tokenizer = getTokenizer(),\n): SurfaceFootprint[] {\n const family = inferModelFamily(\"\", connector.telemetry.modelFamilyHint);\n const out: SurfaceFootprint[] = [];\n\n for (const cmd of connector.commands) {\n const parts: string[] = [];\n if (cmd.description !== undefined) parts.push(cmd.description);\n parts.push(cmd.prompt);\n if (cmd.argumentHint !== undefined) parts.push(cmd.argumentHint);\n out.push({\n surfaceKind: \"command\",\n name: cmd.name,\n tokens: countParts(parts, family, tok),\n });\n }\n\n for (const skill of connector.skills) {\n const parts: string[] = [skill.description, skill.body];\n if (skill.resources !== undefined) {\n // Sort by resource path so the joined text (and thus the count) is stable\n // regardless of object key insertion order.\n const paths = Object.keys(skill.resources).sort();\n for (const p of paths) {\n const value = skill.resources[p];\n if (value !== undefined) parts.push(value);\n }\n }\n out.push({\n surfaceKind: \"skill\",\n name: skill.name,\n tokens: countParts(parts, family, tok),\n });\n }\n\n for (const agent of connector.subagents) {\n out.push({\n surfaceKind: \"subagent\",\n name: agent.name,\n tokens: countParts([agent.description, agent.prompt], family, tok),\n });\n }\n\n return out;\n}\n","/**\n * telemetry/leaderboard — the PLUGIN / MCP leaderboard, derived from the per-MCP\n * serve-proxy telemetry store (src/telemetry/*).\n *\n * This is the signature MCP-dev metric: of the MCP servers WE wrap, which one\n * costs the most tokens — platform-independent, because it measures the server's\n * OWN bytes (tool args in, tool result out, tool-defs schemas) tokenized locally,\n * the only signal identical across every host. Origin is always `mcp-self`; it is\n * NEVER summed with host-native usage (src/usage/*), which measures a different\n * thing (whole-conversation tokens).\n *\n * Pure aggregation + pure formatting over {@link TelemetryStore.query}. The scope\n * dimension (installScope/launchMethod) is OPTIONAL on each row — rows written\n * before those fields existed are read as \"unknown\" and are honestly labeled.\n */\n\nimport type { ResolvedConnector } from \"../core/types.js\";\nimport { openStore } from \"./store.js\";\nimport { computeSurfaceFootprints } from \"./surface-footprint.js\";\nimport { worstConfidence } from \"./types.js\";\nimport type {\n ConfidenceSource,\n LaunchMethod,\n QueryFilter,\n SurfaceKind,\n TelemetryInstallScope,\n TelemetryStore,\n ToolEventRecord,\n} from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// Options + scope filter\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * A scope filter value the leaderboards accept. The two install buckets\n * (`user`/`project`) and the launch methods (`npx`/`bunx`/`uvx`/`node`/`binary`/\n * `http`) plus the honest `unknown` fallback. A record matches a `user`/`project`\n * value by its `installScope`, and a launch-method value by its `launchMethod`.\n */\nexport type ScopeFilter =\n | TelemetryInstallScope\n | LaunchMethod;\n\n/** Every accepted {@link ScopeFilter} string, for CLI validation. */\nexport const SCOPE_FILTER_VALUES: readonly ScopeFilter[] = [\n \"user\",\n \"project\",\n \"npx\",\n \"bunx\",\n \"uvx\",\n \"node\",\n \"binary\",\n \"http\",\n \"unknown\",\n] as const;\n\n/** Options shared by every leaderboard query. */\nexport interface LeaderboardOptions {\n /** An open store to read from. Defaults to {@link openStore} on the data-root. */\n store?: TelemetryStore;\n /** Lower-bound epoch ms (inclusive) — forwarded as the store's sinceMs filter. */\n sinceMs?: number;\n /** Restrict to one connector. */\n connectorId?: string;\n /** Slice by install scope (user|project) or launch method (npx|binary|http|…). */\n scope?: ScopeFilter;\n}\n\n/** Is `value` one of the accepted scope-filter strings? */\nexport function isScopeFilter(value: string): value is ScopeFilter {\n return (SCOPE_FILTER_VALUES as readonly string[]).includes(value);\n}\n\n/** The install/launch values a `user`/`project` scope filter matches against. */\nconst INSTALL_SCOPES: ReadonlySet<string> = new Set<string>([\"user\", \"project\"]);\n\n/**\n * Does a record satisfy the requested scope slice? `user`/`project` match the\n * record's `installScope`; any concrete launch method matches the record's\n * `launchMethod`. The honest `unknown` filter is special: it matches ONLY a\n * record that lacks BOTH dimensions (a truly pre-scope row) — a record carrying\n * a known installScope but no launchMethod (or vice-versa) is NOT \"unknown\" and\n * must not be swept into that bucket.\n */\nfunction matchesScope(record: ToolEventRecord, scope: ScopeFilter): boolean {\n if (scope === \"unknown\") {\n return record.installScope === undefined && record.launchMethod === undefined;\n }\n if (INSTALL_SCOPES.has(scope)) {\n return record.installScope === scope;\n }\n return record.launchMethod === scope;\n}\n\n/** Build the store {@link QueryFilter} from the shared options. */\nfunction toFilter(opts: LeaderboardOptions): QueryFilter {\n const filter: QueryFilter = {};\n if (opts.sinceMs !== undefined) filter.sinceMs = opts.sinceMs;\n if (opts.connectorId !== undefined && opts.connectorId !== \"\") {\n filter.connectorId = opts.connectorId;\n }\n return filter;\n}\n\n/**\n * Query the store and apply the (in-memory) scope slice the store cannot. By\n * default EXCLUDES the host-native `model_turn` rows: this selector feeds the\n * per-MCP/plugin views (mcp/tool/scope), which measure per-MCP `call` + the\n * `tool_defs` overhead and must NEVER sum the whole-conversation host-native\n * turns. Pass `includeModelTurn: true` to read those rows instead (used by\n * {@link hostNativeTurns}).\n */\nfunction selectRecords(\n opts: LeaderboardOptions,\n includeModelTurn = false,\n): ToolEventRecord[] {\n const store = opts.store ?? openStore({});\n const owned = opts.store === undefined;\n try {\n let rows = store.query(toFilter(opts));\n rows = includeModelTurn\n ? rows.filter((r) => r.scope === \"model_turn\")\n : rows.filter((r) => r.scope !== \"model_turn\");\n if (opts.scope === undefined) return rows;\n const scope = opts.scope;\n return rows.filter((r) => matchesScope(r, scope));\n } finally {\n if (owned) store.close();\n }\n}\n\n// Confidence ranking + worst-of comparison live in ./types (the single source\n// of truth, imported as worstConfidence above) so a new ConfidenceSource value\n// orders correctly here as well.\n\n// ─────────────────────────────────────────────────────────────────────────\n// Leaderboard rows\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * One row of the MCP/plugin leaderboard — a connector ranked by total measured\n * tokens. `tools` is the count of distinct tool names seen (excluding the\n * tool-defs `*` pseudo-tool); `hostPlatforms` is the distinct set of hosts that\n * ran this server, evidence that the count aggregates across machines.\n */\nexport interface McpLeaderboardRow {\n connectorId: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n /** Count of distinct real tool names (the tool-defs `*` row is excluded). */\n tools: number;\n /** Distinct host platforms that produced records, sorted for stable output. */\n hostPlatforms: string[];\n /** Worst (least-confident) source in the group, for honest labeling. */\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * One row of the per-tool leaderboard — a (connector, tool) pair ranked by total\n * measured tokens. The tool-defs pseudo-tool (`*`, scope `tool_defs`) is kept as\n * its own row so the fixed schema overhead is visible rather than hidden.\n */\nexport interface ToolLeaderboardRow {\n connectorId: string;\n toolName: string;\n /** \"call\" round-trips vs the one-time \"tool_defs\" overhead measurement. */\n scope: ToolEventRecord[\"scope\"];\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * One row of the scope breakdown — usage grouped by (installScope, launchMethod),\n * both honestly reported as \"unknown\" when the underlying record lacks the field.\n */\nexport interface ScopeBreakdownRow {\n installScope: TelemetryInstallScope | \"unknown\";\n launchMethod: LaunchMethod;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * One row of the host-native turns aggregation — a (host, session) pair ranked by\n * total host-reported tokens for that whole-conversation turn stream. This is the\n * THIRD origin (`host-native-live`): it comes from the opt-in AfterModel /\n * PostInvocation usage hook (scope `model_turn`, confidence `host-native`) and is\n * NEVER summed with the per-MCP `call` rows or the usage-reader host-scan numbers.\n */\nexport interface HostNativeTurnsRow {\n hostPlatform: string;\n sessionId: string;\n /** Number of recorded model turns (scope `model_turn` rows) in this group. */\n turns: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n /** Distinct connector ids that produced turns under this host/session. */\n connectors: string[];\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/** Mutable accumulator for the host-native turns fold (set finalized to count). */\ninterface HostNativeAcc {\n hostPlatform: string;\n sessionId: string;\n turns: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n connectors: Set<string>;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/** Mutable accumulator for the MCP leaderboard fold (sets finalized to counts). */\ninterface McpAcc {\n connectorId: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n tools: Set<string>;\n hostPlatforms: Set<string>;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * The PLUGIN / MCP leaderboard: rank connectors by total measured tokens desc\n * (ties broken by recency). This is the headline \"which MCP server costs the\n * most\" metric, platform-independent by construction.\n */\nexport function mcpLeaderboard(opts: LeaderboardOptions = {}): McpLeaderboardRow[] {\n const groups = new Map<string, McpAcc>();\n\n for (const r of selectRecords(opts)) {\n let g = groups.get(r.connectorId);\n if (g === undefined) {\n g = {\n connectorId: r.connectorId,\n calls: 0,\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n tools: new Set<string>(),\n hostPlatforms: new Set<string>(),\n confidence: r.confidenceSource,\n lastTs: r.ts,\n };\n groups.set(r.connectorId, g);\n }\n // A tool_defs row is a one-time schema overhead, not a call: count its\n // tokens but do not inflate the call count or the distinct-tool count.\n if (r.scope === \"call\") {\n g.calls += 1;\n g.tools.add(r.toolName);\n }\n g.inputTokens += r.inputTokens;\n g.outputTokens += r.outputTokens;\n g.totalTokens += r.inputTokens + r.outputTokens;\n g.hostPlatforms.add(r.hostPlatform);\n g.confidence = worstConfidence(g.confidence, r.confidenceSource);\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n }\n\n const rows: McpLeaderboardRow[] = [];\n for (const g of groups.values()) {\n rows.push({\n connectorId: g.connectorId,\n calls: g.calls,\n inputTokens: g.inputTokens,\n outputTokens: g.outputTokens,\n totalTokens: g.totalTokens,\n tools: g.tools.size,\n hostPlatforms: [...g.hostPlatforms].sort(),\n confidence: g.confidence,\n lastTs: g.lastTs,\n });\n }\n rows.sort((a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs);\n return rows;\n}\n\n/**\n * The per-tool leaderboard: rank (connector, tool) pairs by total tokens desc\n * (ties broken by recency). Drills the MCP leaderboard down to which individual\n * tool is the expensive one.\n */\nexport function toolLeaderboard(opts: LeaderboardOptions = {}): ToolLeaderboardRow[] {\n const groups = new Map<string, ToolLeaderboardRow>();\n\n for (const r of selectRecords(opts)) {\n const key = `${r.connectorId}\\u0000${r.toolName}\\u0000${r.scope}`;\n const g = groups.get(key);\n if (g === undefined) {\n groups.set(key, {\n connectorId: r.connectorId,\n toolName: r.toolName,\n scope: r.scope,\n calls: r.scope === \"call\" ? 1 : 0,\n inputTokens: r.inputTokens,\n outputTokens: r.outputTokens,\n totalTokens: r.inputTokens + r.outputTokens,\n confidence: r.confidenceSource,\n lastTs: r.ts,\n });\n } else {\n if (r.scope === \"call\") g.calls += 1;\n g.inputTokens += r.inputTokens;\n g.outputTokens += r.outputTokens;\n g.totalTokens += r.inputTokens + r.outputTokens;\n g.confidence = worstConfidence(g.confidence, r.confidenceSource);\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n }\n }\n\n const rows = [...groups.values()];\n rows.sort((a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs);\n return rows;\n}\n\n/**\n * The scope breakdown: group usage by (installScope, launchMethod). Both\n * dimensions read \"unknown\" when the source row lacks the field, so a pre-scope\n * row is honestly bucketed rather than dropped or mis-attributed.\n */\nexport function scopeBreakdown(opts: LeaderboardOptions = {}): ScopeBreakdownRow[] {\n const groups = new Map<string, ScopeBreakdownRow>();\n\n for (const r of selectRecords(opts)) {\n const installScope: TelemetryInstallScope | \"unknown\" = r.installScope ?? \"unknown\";\n const launchMethod: LaunchMethod = r.launchMethod ?? \"unknown\";\n const key = `${installScope}\\u0000${launchMethod}`;\n const g = groups.get(key);\n if (g === undefined) {\n groups.set(key, {\n installScope,\n launchMethod,\n calls: r.scope === \"call\" ? 1 : 0,\n inputTokens: r.inputTokens,\n outputTokens: r.outputTokens,\n totalTokens: r.inputTokens + r.outputTokens,\n confidence: r.confidenceSource,\n lastTs: r.ts,\n });\n } else {\n if (r.scope === \"call\") g.calls += 1;\n g.inputTokens += r.inputTokens;\n g.outputTokens += r.outputTokens;\n g.totalTokens += r.inputTokens + r.outputTokens;\n g.confidence = worstConfidence(g.confidence, r.confidenceSource);\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n }\n }\n\n const rows = [...groups.values()];\n rows.sort((a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs);\n return rows;\n}\n\n/**\n * The THIRD origin: host-native turns. Aggregates ONLY the `model_turn` rows (the\n * opt-in AfterModel / PostInvocation host-native usage hook) by (host, session),\n * ranked by total host-reported tokens desc. This is whole-conversation usage the\n * host itself reported — exact, but a DIFFERENT thing than the per-MCP `call`\n * rows; it is surfaced separately and NEVER summed with the other two origins.\n */\nexport function hostNativeTurns(opts: LeaderboardOptions = {}): HostNativeTurnsRow[] {\n const groups = new Map<string, HostNativeAcc>();\n\n for (const r of selectRecords(opts, /* includeModelTurn */ true)) {\n const key = `${r.hostPlatform}\\u0000${r.sessionId}`;\n let g = groups.get(key);\n if (g === undefined) {\n g = {\n hostPlatform: r.hostPlatform,\n sessionId: r.sessionId,\n turns: 0,\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n connectors: new Set<string>(),\n confidence: r.confidenceSource,\n lastTs: r.ts,\n };\n groups.set(key, g);\n }\n g.turns += 1;\n g.inputTokens += r.inputTokens;\n g.outputTokens += r.outputTokens;\n g.totalTokens += r.inputTokens + r.outputTokens;\n g.connectors.add(r.connectorId);\n g.confidence = worstConfidence(g.confidence, r.confidenceSource);\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n }\n\n const rows: HostNativeTurnsRow[] = [];\n for (const g of groups.values()) {\n rows.push({\n hostPlatform: g.hostPlatform,\n sessionId: g.sessionId,\n turns: g.turns,\n inputTokens: g.inputTokens,\n outputTokens: g.outputTokens,\n totalTokens: g.totalTokens,\n connectors: [...g.connectors].sort(),\n confidence: g.confidence,\n lastTs: g.lastTs,\n });\n }\n rows.sort((a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs);\n return rows;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Per-surface leaderboard (the FIVE developer-axis surfaces)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * One row of the per-surface developer breakdown — a (surfaceKind, name) pair.\n * Combines the RUNTIME-measured surfaces (server `call`+`tool_defs` rows and the\n * new `hook` rows from the telemetry store) with the STATIC content footprints\n * (command/skill/subagent) from {@link computeSurfaceFootprints}.\n *\n * • `kind` distinguishes RUNTIME (measured live) from STATIC (footprint of\n * context the host loads — never an intercepted usage row).\n * • For static rows `calls` is 0, `outputTokens` is 0, the cost sits in\n * `inputTokens`/`totalTokens`, and `confidence` is the tokenizer source for\n * the connector's family. For runtime rows everything aggregates the store.\n */\nexport interface SurfaceLeaderboardRow {\n surfaceKind: SurfaceKind;\n /** Per-item name: the tool name (server), the event name (hook), or the\n * command/skill/subagent name (static surfaces). */\n name: string;\n /** RUNTIME (server/hook store rows) vs STATIC (command/skill/subagent footprint). */\n kind: \"runtime\" | \"static\";\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/** Mutable accumulator for the runtime side of the per-surface fold. */\ninterface SurfaceAcc {\n surfaceKind: SurfaceKind;\n name: string;\n calls: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n confidence: ConfidenceSource;\n lastTs: number;\n}\n\n/**\n * A record's developer-axis surface kind, backward-compatibly. Rows written\n * before `surfaceKind` existed (every legacy serve-proxy `call`/`tool_defs` row)\n * lack it and are read as `server`; the `hook` runtime stamps it explicitly.\n */\nfunction recordSurfaceKind(r: ToolEventRecord): SurfaceKind {\n if (r.surfaceKind !== undefined) return r.surfaceKind;\n // No explicit kind → it predates this field → it is a serve-proxy server row.\n return \"server\";\n}\n\n/** Options for {@link surfaceLeaderboard}: the shared query opts + the connectors\n * whose STATIC footprints are folded in. */\nexport interface SurfaceLeaderboardOptions extends LeaderboardOptions {\n /** Registered connectors to compute static command/skill/subagent footprints\n * over. Omit/empty → only the runtime (server/hook) rows are reported. */\n connectors?: readonly ResolvedConnector[];\n}\n\n/**\n * The PER-SURFACE developer leaderboard across all FIVE surfaces.\n *\n * RUNTIME side: the store's `call`/`tool_defs` rows (surface `server`) and the\n * `hook` rows (surface `hook`), grouped by (surfaceKind, name) with IN/OUT/TOTAL\n * and a call count. (`model_turn` rows are excluded by `selectRecords`, exactly\n * as the per-MCP views exclude them.)\n *\n * STATIC side: one row per command/skill/subagent from\n * {@link computeSurfaceFootprints} over each passed connector, deduped by\n * (surfaceKind, name) and summed when the same surface is declared by more than\n * one connector. Static rows carry the footprint in `inputTokens` (the cost the\n * host pays to load that context); `outputTokens`/`calls` are 0.\n *\n * Sorted by total tokens desc, ties broken by recency (static rows have lastTs 0\n * so they sort after equal-token runtime rows).\n */\nexport function surfaceLeaderboard(\n opts: SurfaceLeaderboardOptions = {},\n): SurfaceLeaderboardRow[] {\n // ── RUNTIME: aggregate server + hook store rows by (surfaceKind, name) ──\n const runtime = new Map<string, SurfaceAcc>();\n for (const r of selectRecords(opts)) {\n const surfaceKind = recordSurfaceKind(r);\n const key = `${surfaceKind} ${r.toolName}`;\n let g = runtime.get(key);\n if (g === undefined) {\n g = {\n surfaceKind,\n name: r.toolName,\n calls: 0,\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n confidence: r.confidenceSource,\n lastTs: r.ts,\n };\n runtime.set(key, g);\n }\n // tool_defs is a one-time schema overhead, not a call — count tokens only.\n if (r.scope === \"call\" || r.scope === \"hook\") g.calls += 1;\n g.inputTokens += r.inputTokens;\n g.outputTokens += r.outputTokens;\n g.totalTokens += r.inputTokens + r.outputTokens;\n g.confidence = worstConfidence(g.confidence, r.confidenceSource);\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n }\n\n const rows: SurfaceLeaderboardRow[] = [];\n for (const g of runtime.values()) {\n rows.push({\n surfaceKind: g.surfaceKind,\n name: g.name,\n kind: \"runtime\",\n calls: g.calls,\n inputTokens: g.inputTokens,\n outputTokens: g.outputTokens,\n totalTokens: g.totalTokens,\n confidence: g.confidence,\n lastTs: g.lastTs,\n });\n }\n\n // ── STATIC: fold in command/skill/subagent footprints per connector ──────\n const staticRows = new Map<\n string,\n { surfaceKind: SurfaceKind; name: string; tokens: number }\n >();\n for (const connector of opts.connectors ?? []) {\n for (const fp of computeSurfaceFootprints(connector)) {\n const key = `${fp.surfaceKind} ${fp.name}`;\n const existing = staticRows.get(key);\n if (existing === undefined) {\n staticRows.set(key, { surfaceKind: fp.surfaceKind, name: fp.name, tokens: fp.tokens });\n } else {\n existing.tokens += fp.tokens;\n }\n }\n }\n // A pure tokenizer footprint carries no host-reported truth — label it with the\n // tokenizer source the connector's family would yield. We derive that once via\n // a throwaway count so the static confidence is honest (approx vs exact).\n const staticConfidence: ConfidenceSource = staticFootprintConfidence(opts.connectors ?? []);\n for (const s of staticRows.values()) {\n rows.push({\n surfaceKind: s.surfaceKind,\n name: s.name,\n kind: \"static\",\n calls: 0,\n inputTokens: s.tokens,\n outputTokens: 0,\n totalTokens: s.tokens,\n confidence: staticConfidence,\n lastTs: 0,\n });\n }\n\n rows.sort((a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs);\n return rows;\n}\n\n/**\n * The confidence label for the static footprints: the tokenizer source for the\n * first connector's family (exact for openai, approx otherwise). Defaults to\n * `tokenizer-approx` when no connector is supplied — an honest estimate label.\n */\nfunction staticFootprintConfidence(\n connectors: readonly ResolvedConnector[],\n): ConfidenceSource {\n const first = connectors[0];\n if (first === undefined) return \"tokenizer-approx\";\n // openai family → exact BPE; everything else → approx. Mirrors tokenizer.bpeSource.\n return first.telemetry.modelFamilyHint === \"openai\"\n ? \"tokenizer-exact\"\n : \"tokenizer-approx\";\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Formatting (mirrors telemetry/report.ts: aligned table + honesty legend)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Sources whose counts are estimates (vs real / host-reported numbers).\n * `tokenizer-calibrated` is still an estimate — an approximation nudged toward\n * truth by a sampled Anthropic count_tokens factor — so it belongs here too.\n */\nconst ESTIMATE_SOURCES: ReadonlySet<ConfidenceSource> = new Set<ConfidenceSource>([\n \"heuristic\",\n \"tokenizer-approx\",\n \"tokenizer-calibrated\",\n]);\n\n/** Compact integer formatting with thousands separators. */\nfunction fmtInt(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n/** Right-pad (left-aligned columns). */\nfunction padRight(s: string, width: number): string {\n return s.length >= width ? s : s + \" \".repeat(width - s.length);\n}\n\n/** Left-pad (right-aligned numeric columns). */\nfunction padLeft(s: string, width: number): string {\n return s.length >= width ? s : \" \".repeat(width - s.length) + s;\n}\n\n/**\n * Render an aligned table given headers, the data cells, and which columns are\n * left-aligned (text) vs right-aligned (numbers). Adds rule lines and a TOTAL\n * footer. Shared by every leaderboard formatter for one consistent style.\n */\nfunction renderTable(\n headers: string[],\n dataRows: string[][],\n leftAlignedCols: ReadonlySet<number>,\n totalRow: string[],\n emptyMessage: string,\n): string[] {\n const widths = headers.map((h, col) => {\n let w = h.length;\n for (const row of dataRows) w = Math.max(w, (row[col] ?? \"\").length);\n w = Math.max(w, (totalRow[col] ?? \"\").length);\n return w;\n });\n\n const renderRow = (cells: string[]): string =>\n cells\n .map((c, col) =>\n leftAlignedCols.has(col)\n ? padRight(c, widths[col] ?? 0)\n : padLeft(c, widths[col] ?? 0),\n )\n .join(\" \")\n .trimEnd();\n\n const ruleWidth = widths.reduce((a, b) => a + b, 0) + 2 * (widths.length - 1);\n const rule = \"-\".repeat(ruleWidth);\n\n const lines: string[] = [];\n lines.push(renderRow(headers));\n lines.push(rule);\n if (dataRows.length === 0) {\n lines.push(emptyMessage);\n } else {\n for (const row of dataRows) lines.push(renderRow(row));\n }\n lines.push(rule);\n lines.push(renderRow(totalRow));\n return lines;\n}\n\n/** Append the estimate legend when any row carries an estimate confidence. */\nfunction appendEstimateLegend(\n lines: string[],\n confidences: ConfidenceSource[],\n): void {\n if (confidences.some((c) => ESTIMATE_SOURCES.has(c))) {\n lines.push(\"\");\n lines.push(\n \"note: heuristic and tokenizer-approx token counts are estimates, \" +\n \"not exact host-reported usage.\",\n );\n if (confidences.some((c) => c === \"tokenizer-calibrated\")) {\n lines.push(\n \"note: tokenizer-calibrated = local approx adjusted by a sampled \" +\n \"Anthropic count_tokens factor (opt-in; content sampled off-box only \" +\n \"when AGENT_CONNECTOR_CALIBRATE=anthropic).\",\n );\n }\n }\n}\n\n/**\n * Format the MCP/plugin leaderboard as an aligned table:\n * RANK | CONNECTOR | CALLS | TOOLS | IN | OUT | TOTAL | HOSTS | CONFIDENCE\n * sorted by total tokens desc, with a TOTAL footer and the honesty legend.\n */\nexport function formatMcpLeaderboard(rows: McpLeaderboardRow[]): string {\n const sorted = [...rows].sort(\n (a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs,\n );\n const headers = [\n \"RANK\",\n \"CONNECTOR\",\n \"CALLS\",\n \"TOOLS\",\n \"IN\",\n \"OUT\",\n \"TOTAL\",\n \"HOSTS\",\n \"CONFIDENCE\",\n ];\n const dataRows = sorted.map((r, i) => [\n `${i + 1}`,\n r.connectorId,\n fmtInt(r.calls),\n fmtInt(r.tools),\n fmtInt(r.inputTokens),\n fmtInt(r.outputTokens),\n fmtInt(r.totalTokens),\n r.hostPlatforms.length === 0 ? \"-\" : r.hostPlatforms.join(\",\"),\n r.confidence,\n ]);\n\n const totals = sorted.reduce(\n (acc, r) => {\n acc.calls += r.calls;\n acc.inputTokens += r.inputTokens;\n acc.outputTokens += r.outputTokens;\n acc.totalTokens += r.totalTokens;\n return acc;\n },\n { calls: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n );\n const totalRow = [\n \"\",\n \"TOTAL\",\n fmtInt(totals.calls),\n \"\",\n fmtInt(totals.inputTokens),\n fmtInt(totals.outputTokens),\n fmtInt(totals.totalTokens),\n \"\",\n \"\",\n ];\n\n // Left-align RANK (0), CONNECTOR (1), HOSTS (7), CONFIDENCE (8); numbers right.\n const leftCols = new Set<number>([0, 1, 7, 8]);\n const lines = renderTable(\n headers,\n dataRows,\n leftCols,\n totalRow,\n \"(no MCP telemetry recorded)\",\n );\n appendEstimateLegend(\n lines,\n sorted.map((r) => r.confidence),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * Format the per-tool leaderboard:\n * RANK | CONNECTOR | TOOL | SCOPE | CALLS | IN | OUT | TOTAL | CONFIDENCE\n * sorted by total tokens desc, with a TOTAL footer and the honesty legend.\n */\nexport function formatToolLeaderboard(rows: ToolLeaderboardRow[]): string {\n const sorted = [...rows].sort(\n (a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs,\n );\n const headers = [\n \"RANK\",\n \"CONNECTOR\",\n \"TOOL\",\n \"SCOPE\",\n \"CALLS\",\n \"IN\",\n \"OUT\",\n \"TOTAL\",\n \"CONFIDENCE\",\n ];\n const dataRows = sorted.map((r, i) => [\n `${i + 1}`,\n r.connectorId,\n r.toolName,\n r.scope,\n fmtInt(r.calls),\n fmtInt(r.inputTokens),\n fmtInt(r.outputTokens),\n fmtInt(r.totalTokens),\n r.confidence,\n ]);\n\n const totals = sorted.reduce(\n (acc, r) => {\n acc.calls += r.calls;\n acc.inputTokens += r.inputTokens;\n acc.outputTokens += r.outputTokens;\n acc.totalTokens += r.totalTokens;\n return acc;\n },\n { calls: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n );\n const totalRow = [\n \"\",\n \"TOTAL\",\n \"\",\n \"\",\n fmtInt(totals.calls),\n fmtInt(totals.inputTokens),\n fmtInt(totals.outputTokens),\n fmtInt(totals.totalTokens),\n \"\",\n ];\n\n // Left-align RANK (0), CONNECTOR (1), TOOL (2), SCOPE (3), CONFIDENCE (8).\n const leftCols = new Set<number>([0, 1, 2, 3, 8]);\n const lines = renderTable(\n headers,\n dataRows,\n leftCols,\n totalRow,\n \"(no MCP telemetry recorded)\",\n );\n appendEstimateLegend(\n lines,\n sorted.map((r) => r.confidence),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * Format the scope breakdown:\n * INSTALL | LAUNCH | CALLS | IN | OUT | TOTAL | CONFIDENCE\n * with a TOTAL footer and the honesty legend.\n */\nexport function formatScopeBreakdown(rows: ScopeBreakdownRow[]): string {\n const sorted = [...rows].sort(\n (a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs,\n );\n const headers = [\"INSTALL\", \"LAUNCH\", \"CALLS\", \"IN\", \"OUT\", \"TOTAL\", \"CONFIDENCE\"];\n const dataRows = sorted.map((r) => [\n r.installScope,\n r.launchMethod,\n fmtInt(r.calls),\n fmtInt(r.inputTokens),\n fmtInt(r.outputTokens),\n fmtInt(r.totalTokens),\n r.confidence,\n ]);\n\n const totals = sorted.reduce(\n (acc, r) => {\n acc.calls += r.calls;\n acc.inputTokens += r.inputTokens;\n acc.outputTokens += r.outputTokens;\n acc.totalTokens += r.totalTokens;\n return acc;\n },\n { calls: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n );\n const totalRow = [\n \"TOTAL\",\n \"\",\n fmtInt(totals.calls),\n fmtInt(totals.inputTokens),\n fmtInt(totals.outputTokens),\n fmtInt(totals.totalTokens),\n \"\",\n ];\n\n // Left-align INSTALL (0), LAUNCH (1), CONFIDENCE (6); numbers right-aligned.\n const leftCols = new Set<number>([0, 1, 6]);\n const lines = renderTable(\n headers,\n dataRows,\n leftCols,\n totalRow,\n \"(no MCP telemetry recorded)\",\n );\n appendEstimateLegend(\n lines,\n sorted.map((r) => r.confidence),\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * Format the host-native turns aggregation (the THIRD origin):\n * RANK | HOST | SESSION | TURNS | IN | OUT | TOTAL | CONNECTORS | CONFIDENCE\n * sorted by total tokens desc, with a TOTAL footer. These are whole-conversation,\n * host-reported (exact) counts — a DIFFERENT thing than the per-MCP rows, so the\n * footer total is for THIS section alone and is never added to the others.\n */\nexport function formatHostNativeTurns(rows: HostNativeTurnsRow[]): string {\n const sorted = [...rows].sort(\n (a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs,\n );\n const headers = [\n \"RANK\",\n \"HOST\",\n \"SESSION\",\n \"TURNS\",\n \"IN\",\n \"OUT\",\n \"TOTAL\",\n \"CONNECTORS\",\n \"CONFIDENCE\",\n ];\n const dataRows = sorted.map((r, i) => [\n `${i + 1}`,\n r.hostPlatform,\n r.sessionId === \"\" ? \"-\" : r.sessionId,\n fmtInt(r.turns),\n fmtInt(r.inputTokens),\n fmtInt(r.outputTokens),\n fmtInt(r.totalTokens),\n r.connectors.length === 0 ? \"-\" : r.connectors.join(\",\"),\n r.confidence,\n ]);\n\n const totals = sorted.reduce(\n (acc, r) => {\n acc.turns += r.turns;\n acc.inputTokens += r.inputTokens;\n acc.outputTokens += r.outputTokens;\n acc.totalTokens += r.totalTokens;\n return acc;\n },\n { turns: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n );\n const totalRow = [\n \"\",\n \"TOTAL\",\n \"\",\n fmtInt(totals.turns),\n fmtInt(totals.inputTokens),\n fmtInt(totals.outputTokens),\n fmtInt(totals.totalTokens),\n \"\",\n \"\",\n ];\n\n // Left-align RANK (0), HOST (1), SESSION (2), CONNECTORS (7), CONFIDENCE (8).\n const leftCols = new Set<number>([0, 1, 2, 7, 8]);\n const lines = renderTable(\n headers,\n dataRows,\n leftCols,\n totalRow,\n \"(no host-native turns recorded — enable opt-in host-native usage)\",\n );\n return lines.join(\"\\n\");\n}\n\n/**\n * Format the per-surface developer breakdown:\n * SURFACE | NAME | IN | OUT | TOTAL | KIND\n * one row per (surfaceKind, name) across all FIVE surfaces — server + hook\n * (runtime, measured from the store) and command/skill/subagent (static\n * footprints). Sorted by total tokens desc, with a TOTAL footer and the honesty\n * legend (static footprints + any estimate-confidence runtime rows).\n *\n * NOTE: the TOTAL footer sums BOTH runtime and static tokens for at-a-glance\n * scale; the KIND column keeps the distinction explicit so the two are never\n * silently conflated (runtime = live usage; static = context-load footprint).\n */\nexport function formatSurfaceLeaderboard(rows: SurfaceLeaderboardRow[]): string {\n const sorted = [...rows].sort(\n (a, b) => b.totalTokens - a.totalTokens || b.lastTs - a.lastTs,\n );\n const headers = [\"SURFACE\", \"NAME\", \"IN\", \"OUT\", \"TOTAL\", \"KIND\"];\n const dataRows = sorted.map((r) => [\n r.surfaceKind,\n r.name,\n fmtInt(r.inputTokens),\n fmtInt(r.outputTokens),\n fmtInt(r.totalTokens),\n r.kind,\n ]);\n\n const totals = sorted.reduce(\n (acc, r) => {\n acc.inputTokens += r.inputTokens;\n acc.outputTokens += r.outputTokens;\n acc.totalTokens += r.totalTokens;\n return acc;\n },\n { inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n );\n const totalRow = [\n \"TOTAL\",\n \"\",\n fmtInt(totals.inputTokens),\n fmtInt(totals.outputTokens),\n fmtInt(totals.totalTokens),\n \"\",\n ];\n\n // Left-align SURFACE (0), NAME (1), KIND (5); numbers right-aligned.\n const leftCols = new Set<number>([0, 1, 5]);\n const lines = renderTable(\n headers,\n dataRows,\n leftCols,\n totalRow,\n \"(no developer surfaces recorded)\",\n );\n if (sorted.some((r) => r.kind === \"static\")) {\n lines.push(\"\");\n lines.push(\n \"note: KIND=static rows are the tokenized FOOTPRINT a command/skill/subagent \" +\n \"imposes on a host that loads it as context — not intercepted usage rows.\",\n );\n }\n appendEstimateLegend(\n lines,\n sorted.map((r) => r.confidence),\n );\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/usage/paths.ts
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
statSync
|
|
8
|
+
} from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { isAbsolute, join, resolve } from "path";
|
|
11
|
+
function expandHome(p) {
|
|
12
|
+
if (p === "~") return homedir();
|
|
13
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
14
|
+
return join(homedir(), p.slice(2));
|
|
15
|
+
}
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
function envOverride(name) {
|
|
19
|
+
const raw = process.env[name];
|
|
20
|
+
if (raw == null || raw.trim() === "") return void 0;
|
|
21
|
+
const expanded = expandHome(raw.trim());
|
|
22
|
+
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
23
|
+
}
|
|
24
|
+
function xdgDataHome() {
|
|
25
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
26
|
+
if (xdg && xdg.trim() !== "") return resolve(expandHome(xdg.trim()));
|
|
27
|
+
return join(homedir(), ".local", "share");
|
|
28
|
+
}
|
|
29
|
+
function xdgConfigHome() {
|
|
30
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
31
|
+
if (xdg && xdg.trim() !== "") return resolve(expandHome(xdg.trim()));
|
|
32
|
+
return join(homedir(), ".config");
|
|
33
|
+
}
|
|
34
|
+
function localAppData() {
|
|
35
|
+
const v = process.env.LOCALAPPDATA;
|
|
36
|
+
if (v && v.trim() !== "") return resolve(v.trim());
|
|
37
|
+
return join(homedir(), "AppData", "Local");
|
|
38
|
+
}
|
|
39
|
+
var isMac = process.platform === "darwin";
|
|
40
|
+
var isWin = process.platform === "win32";
|
|
41
|
+
function tokscaleConfigDir() {
|
|
42
|
+
const override = envOverride("AGENT_CONNECTOR_TOKSCALE_DIR");
|
|
43
|
+
if (override) return override;
|
|
44
|
+
if (isMac) return join(homedir(), ".config", "tokscale");
|
|
45
|
+
if (isWin) {
|
|
46
|
+
const appData = process.env.APPDATA;
|
|
47
|
+
if (appData && appData.trim() !== "") return join(resolve(appData.trim()), "tokscale");
|
|
48
|
+
return join(homedir(), ".config", "tokscale");
|
|
49
|
+
}
|
|
50
|
+
return join(xdgConfigHome(), "tokscale");
|
|
51
|
+
}
|
|
52
|
+
function tokscaleCacheDir(name) {
|
|
53
|
+
return join(tokscaleConfigDir(), name);
|
|
54
|
+
}
|
|
55
|
+
function hostRoots(platformId) {
|
|
56
|
+
const out = [];
|
|
57
|
+
const override = envOverride(`AGENT_CONNECTOR_${platformId.toUpperCase().replace(/-/g, "_")}_DIR`);
|
|
58
|
+
if (override) out.push(override);
|
|
59
|
+
switch (platformId) {
|
|
60
|
+
case "qwen-code":
|
|
61
|
+
out.push(join(homedir(), ".qwen", "projects"));
|
|
62
|
+
break;
|
|
63
|
+
case "claude-code":
|
|
64
|
+
out.push(join(homedir(), ".claude", "projects"));
|
|
65
|
+
break;
|
|
66
|
+
case "codex":
|
|
67
|
+
out.push(join(homedir(), ".codex", "sessions"));
|
|
68
|
+
break;
|
|
69
|
+
case "gemini-cli":
|
|
70
|
+
out.push(join(homedir(), ".gemini", "tmp"));
|
|
71
|
+
break;
|
|
72
|
+
case "pi":
|
|
73
|
+
out.push(join(homedir(), ".pi", "agent", "sessions"));
|
|
74
|
+
break;
|
|
75
|
+
case "kimi":
|
|
76
|
+
out.push(join(homedir(), ".kimi", "sessions"));
|
|
77
|
+
break;
|
|
78
|
+
case "copilot-cli":
|
|
79
|
+
out.push(join(xdgDataHome(), "Copilot", "telemetry"));
|
|
80
|
+
out.push(join(xdgConfigHome(), "copilot", "telemetry"));
|
|
81
|
+
break;
|
|
82
|
+
case "amp":
|
|
83
|
+
out.push(join(xdgDataHome(), "amp", "threads"));
|
|
84
|
+
break;
|
|
85
|
+
case "droid":
|
|
86
|
+
out.push(join(homedir(), ".factory", "sessions"));
|
|
87
|
+
break;
|
|
88
|
+
case "mux":
|
|
89
|
+
out.push(join(homedir(), ".mux", "sessions"));
|
|
90
|
+
break;
|
|
91
|
+
case "kiro":
|
|
92
|
+
out.push(join(homedir(), ".kiro", "sessions", "cli"));
|
|
93
|
+
break;
|
|
94
|
+
case "opencode":
|
|
95
|
+
out.push(join(xdgDataHome(), "opencode", "opencode.db"));
|
|
96
|
+
break;
|
|
97
|
+
case "goose":
|
|
98
|
+
if (isMac) out.push(join(homedir(), "Library", "Application Support", "goose", "sessions", "sessions.db"));
|
|
99
|
+
out.push(join(xdgDataHome(), "goose", "sessions", "sessions.db"));
|
|
100
|
+
out.push(join(xdgDataHome(), "Block", "goose", "sessions", "sessions.db"));
|
|
101
|
+
break;
|
|
102
|
+
case "hermes":
|
|
103
|
+
out.push(join(homedir(), ".hermes", "state.db"));
|
|
104
|
+
break;
|
|
105
|
+
case "kilo-cli":
|
|
106
|
+
out.push(join(xdgDataHome(), "kilo", "kilo.db"));
|
|
107
|
+
break;
|
|
108
|
+
case "crush":
|
|
109
|
+
out.push(join(homedir(), ".cache", "crush", "crush.db"));
|
|
110
|
+
break;
|
|
111
|
+
case "zed":
|
|
112
|
+
if (isMac) out.push(join(homedir(), "Library", "Application Support", "Zed", "threads", "threads.db"));
|
|
113
|
+
else if (isWin) out.push(join(localAppData(), "Zed", "threads", "threads.db"));
|
|
114
|
+
else out.push(join(xdgDataHome(), "zed", "threads", "threads.db"));
|
|
115
|
+
break;
|
|
116
|
+
// Synced platforms: local cache under the tokscale config dir.
|
|
117
|
+
case "cursor":
|
|
118
|
+
out.push(tokscaleCacheDir("cursor-cache"));
|
|
119
|
+
break;
|
|
120
|
+
case "antigravity":
|
|
121
|
+
out.push(tokscaleCacheDir("antigravity-cache"));
|
|
122
|
+
break;
|
|
123
|
+
case "antigravity-cli":
|
|
124
|
+
out.push(tokscaleCacheDir("antigravity-cache"));
|
|
125
|
+
break;
|
|
126
|
+
case "trae":
|
|
127
|
+
out.push(tokscaleCacheDir("trae-cache"));
|
|
128
|
+
break;
|
|
129
|
+
case "warp":
|
|
130
|
+
out.push(tokscaleCacheDir("warp-cache"));
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
function firstExistingRoot(platformId) {
|
|
138
|
+
for (const root of hostRoots(platformId)) {
|
|
139
|
+
if (existsSync(root)) return root;
|
|
140
|
+
}
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
function isDir(p) {
|
|
144
|
+
try {
|
|
145
|
+
return statSync(p).isDirectory();
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function isFile(p) {
|
|
151
|
+
try {
|
|
152
|
+
return statSync(p).isFile();
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function walkFiles(root, predicate, maxDepth = 12) {
|
|
158
|
+
const out = [];
|
|
159
|
+
if (!isDir(root)) return out;
|
|
160
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
161
|
+
while (stack.length > 0) {
|
|
162
|
+
const { dir, depth } = stack.pop();
|
|
163
|
+
let entries;
|
|
164
|
+
try {
|
|
165
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
166
|
+
} catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
const abs = join(dir, entry.name);
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
if (depth < maxDepth) stack.push({ dir: abs, depth: depth + 1 });
|
|
173
|
+
} else if (entry.isFile() && predicate(entry.name, abs)) {
|
|
174
|
+
out.push(abs);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function listSubdirs(dir) {
|
|
181
|
+
if (!isDir(dir)) return [];
|
|
182
|
+
try {
|
|
183
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(dir, e.name));
|
|
184
|
+
} catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
expandHome,
|
|
191
|
+
firstExistingRoot,
|
|
192
|
+
isDir,
|
|
193
|
+
isFile,
|
|
194
|
+
walkFiles,
|
|
195
|
+
listSubdirs
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=chunk-JWNT5KW7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/usage/paths.ts"],"sourcesContent":["/**\n * usage/paths — per-platform HOST storage-root resolution + small fs walkers.\n *\n * This is the read-only complement to core/paths.ts: where that module resolves\n * the FRAMEWORK data-root (~/.agent-connector), this one resolves each agent\n * CLI's OWN native storage roots (~/.qwen, ~/.claude, …) so the usage readers\n * can parse them. It NEVER writes; it only enumerates files.\n *\n * Ported from tokscale paths.rs (the tokscale cache dir) and from each reader's\n * storage-path spec (docs/research/usage-readers.json). Resolution order is\n * uniform across platforms:\n * 1. an explicit AGENT_CONNECTOR_<PLATFORM>_DIR env override (verbatim, when\n * non-empty — an empty string is treated as unset so we never resolve to \"\");\n * 2. the platform-specific default for the current OS.\n *\n * The \"tokscale cache dir\" (~/.config/tokscale, or AGENT_CONNECTOR_TOKSCALE_DIR)\n * is where synced readers (cursor/antigravity/antigravity-cli/trae/warp) look for\n * a local cache that a separate tokscale run may have produced — we read it if\n * present, but we never create or sync it. (Antigravity's own native store is\n * protobuf .pb with no public schema, so it too is synced-cache-or-skip.)\n */\n\nimport {\n existsSync,\n readdirSync,\n statSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// Home / env helpers\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Expand a leading \"~\" (and \"~/\") to the user's home directory. */\nexport function expandHome(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return join(homedir(), p.slice(2));\n }\n return p;\n}\n\n/**\n * Read an env override, treating empty/blank as unset (mirrors tokscale's\n * is_config_dir_overridden contract: an empty override must NOT resolve to \"\").\n * Relative paths are resolved against the process CWD; \"~\" is expanded.\n */\nfunction envOverride(name: string): string | undefined {\n const raw = process.env[name];\n if (raw == null || raw.trim() === \"\") return undefined;\n const expanded = expandHome(raw.trim());\n return isAbsolute(expanded) ? expanded : resolve(expanded);\n}\n\n/** $XDG_DATA_HOME (when set & non-empty) else ~/.local/share. */\nfunction xdgDataHome(): string {\n const xdg = process.env.XDG_DATA_HOME;\n if (xdg && xdg.trim() !== \"\") return resolve(expandHome(xdg.trim()));\n return join(homedir(), \".local\", \"share\");\n}\n\n/** $XDG_CONFIG_HOME (when set & non-empty) else ~/.config. */\nfunction xdgConfigHome(): string {\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg && xdg.trim() !== \"\") return resolve(expandHome(xdg.trim()));\n return join(homedir(), \".config\");\n}\n\n/** Windows %LOCALAPPDATA% else ~/AppData/Local. */\nfunction localAppData(): string {\n const v = process.env.LOCALAPPDATA;\n if (v && v.trim() !== \"\") return resolve(v.trim());\n return join(homedir(), \"AppData\", \"Local\");\n}\n\nconst isMac = process.platform === \"darwin\";\nconst isWin = process.platform === \"win32\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// tokscale cache dir (for synced readers)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the tokscale config dir (where synced caches live). Ported from\n * tokscale paths.rs get_config_dir():\n * 1. AGENT_CONNECTOR_TOKSCALE_DIR override (verbatim, non-empty);\n * 2. macOS: $HOME/.config/tokscale (NOT ~/Library/Application Support);\n * 3. Linux: $XDG_CONFIG_HOME/tokscale or ~/.config/tokscale;\n * 4. Windows: %APPDATA%/tokscale (config_dir equivalent) else ~/.config/tokscale.\n *\n * We use AGENT_CONNECTOR_TOKSCALE_DIR (not TOKSCALE_CONFIG_DIR) so the framework\n * never accidentally couples to a tokscale install's hermetic test env.\n */\nexport function tokscaleConfigDir(): string {\n const override = envOverride(\"AGENT_CONNECTOR_TOKSCALE_DIR\");\n if (override) return override;\n if (isMac) return join(homedir(), \".config\", \"tokscale\");\n if (isWin) {\n const appData = process.env.APPDATA;\n if (appData && appData.trim() !== \"\") return join(resolve(appData.trim()), \"tokscale\");\n return join(homedir(), \".config\", \"tokscale\");\n }\n return join(xdgConfigHome(), \"tokscale\");\n}\n\n/** The directory a synced platform's local cache lives under, e.g. `<tokscale>/cursor-cache`. */\nexport function tokscaleCacheDir(name: string): string {\n return join(tokscaleConfigDir(), name);\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Antigravity native store roots (DETECTION ONLY — not parsed for usage)\n//\n// CONFIRMED-BY-INSTALL (2026-06-03, docs/research/antigravity-paths-confirmed.md):\n// the native Antigravity store is `~/.gemini/antigravity/conversations/<uuid>.pb`\n// — PROTOBUF with no public schema — and `brain/<uuid>/` holds only media +\n// `*.metadata.json`. There are NO `transcript*.jsonl` files. The `agy` CLI shares\n// this SAME `~/.gemini/antigravity/` dir (no separate config/storage dir). So the\n// native dir is used only for platform DETECTION (in the adapters); the usage\n// readers do NOT parse it (protobuf, unreadable) — they are SYNCED readers that\n// read the tokscale synced-cache instead (see hostRoots → antigravity-cache).\n//\n// These helpers are retained for any detection caller and honor the standard\n// AGENT_CONNECTOR_<PLATFORM>_DIR override (prepended, preferred when set).\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * The native Antigravity store root for DETECTION: `~/.gemini/antigravity/`\n * (CONFIRMED). Its `conversations/<uuid>.pb` payloads are protobuf with no public\n * schema, so this dir is NOT parsed for usage — only its presence is a signal.\n *\n * Honors AGENT_CONNECTOR_ANTIGRAVITY_DIR (verbatim, when set) as the override.\n */\nexport function antigravityNativeRoots(): string[] {\n const out: string[] = [];\n const override = envOverride(\"AGENT_CONNECTOR_ANTIGRAVITY_DIR\");\n if (override) out.push(override);\n out.push(join(homedir(), \".gemini\", \"antigravity\"));\n return out;\n}\n\n/**\n * The native store root for the Antigravity CLI (`agy`) for DETECTION. CONFIRMED:\n * `agy` has NO separate dir — it SHARES the IDE's `~/.gemini/antigravity/` (also\n * protobuf, not parsed for usage).\n *\n * Honors AGENT_CONNECTOR_ANTIGRAVITY_CLI_DIR (verbatim, when set) as the override.\n */\nexport function antigravityCliNativeRoots(): string[] {\n const out: string[] = [];\n const override = envOverride(\"AGENT_CONNECTOR_ANTIGRAVITY_CLI_DIR\");\n if (override) out.push(override);\n out.push(join(homedir(), \".gemini\", \"antigravity\"));\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Per-platform host roots\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Candidate host storage roots for a platform, most-preferred first. Readers\n * iterate the returned list and use the first that exists (each variant is the\n * OS-correct default; the env override, when present, is prepended).\n *\n * Only the platforms this subsystem currently ships a reader for are listed in\n * detail; the table is structured so a new platform is one new case. Returning\n * `[]` (no candidate root) makes a reader fail-open to zero records.\n */\nexport function hostRoots(platformId: string): string[] {\n const out: string[] = [];\n const override = envOverride(`AGENT_CONNECTOR_${platformId.toUpperCase().replace(/-/g, \"_\")}_DIR`);\n if (override) out.push(override);\n\n switch (platformId) {\n case \"qwen-code\":\n // ~/.qwen/projects/*/chats/*.jsonl\n out.push(join(homedir(), \".qwen\", \"projects\"));\n break;\n case \"claude-code\":\n out.push(join(homedir(), \".claude\", \"projects\"));\n break;\n case \"codex\":\n out.push(join(homedir(), \".codex\", \"sessions\"));\n break;\n case \"gemini-cli\":\n out.push(join(homedir(), \".gemini\", \"tmp\"));\n break;\n case \"pi\":\n out.push(join(homedir(), \".pi\", \"agent\", \"sessions\"));\n break;\n case \"kimi\":\n out.push(join(homedir(), \".kimi\", \"sessions\"));\n break;\n case \"copilot-cli\":\n out.push(join(xdgDataHome(), \"Copilot\", \"telemetry\"));\n out.push(join(xdgConfigHome(), \"copilot\", \"telemetry\"));\n break;\n case \"amp\":\n out.push(join(xdgDataHome(), \"amp\", \"threads\"));\n break;\n case \"droid\":\n out.push(join(homedir(), \".factory\", \"sessions\"));\n break;\n case \"mux\":\n out.push(join(homedir(), \".mux\", \"sessions\"));\n break;\n case \"kiro\":\n out.push(join(homedir(), \".kiro\", \"sessions\", \"cli\"));\n break;\n case \"opencode\":\n out.push(join(xdgDataHome(), \"opencode\", \"opencode.db\"));\n break;\n case \"goose\":\n if (isMac) out.push(join(homedir(), \"Library\", \"Application Support\", \"goose\", \"sessions\", \"sessions.db\"));\n out.push(join(xdgDataHome(), \"goose\", \"sessions\", \"sessions.db\"));\n out.push(join(xdgDataHome(), \"Block\", \"goose\", \"sessions\", \"sessions.db\"));\n break;\n case \"hermes\":\n out.push(join(homedir(), \".hermes\", \"state.db\"));\n break;\n case \"kilo-cli\":\n out.push(join(xdgDataHome(), \"kilo\", \"kilo.db\"));\n break;\n case \"crush\":\n out.push(join(homedir(), \".cache\", \"crush\", \"crush.db\"));\n break;\n case \"zed\":\n if (isMac) out.push(join(homedir(), \"Library\", \"Application Support\", \"Zed\", \"threads\", \"threads.db\"));\n else if (isWin) out.push(join(localAppData(), \"Zed\", \"threads\", \"threads.db\"));\n else out.push(join(xdgDataHome(), \"zed\", \"threads\", \"threads.db\"));\n break;\n // Synced platforms: local cache under the tokscale config dir.\n case \"cursor\":\n out.push(tokscaleCacheDir(\"cursor-cache\"));\n break;\n case \"antigravity\":\n // SYNCED: the native store is protobuf (.pb, no public schema) and NOT\n // parseable, so the reader reads only the tokscale synced-cache mirror if a\n // separate tokscale run produced one (else [] → \"requires sync\"). The\n // native `~/.gemini/antigravity/` dir is used only for detection.\n out.push(tokscaleCacheDir(\"antigravity-cache\"));\n break;\n case \"antigravity-cli\":\n // SYNCED: `agy` shares the IDE's protobuf (.pb) store (no schema), so the\n // reader reads only the tokscale synced-cache mirror if present (else [] →\n // \"requires sync\"). Shares the IDE's antigravity-cache name (same data).\n out.push(tokscaleCacheDir(\"antigravity-cache\"));\n break;\n case \"trae\":\n out.push(tokscaleCacheDir(\"trae-cache\"));\n break;\n case \"warp\":\n out.push(tokscaleCacheDir(\"warp-cache\"));\n break;\n default:\n break;\n }\n return out;\n}\n\n/** First existing host root for a platform, or undefined when none is present. */\nexport function firstExistingRoot(platformId: string): string | undefined {\n for (const root of hostRoots(platformId)) {\n if (existsSync(root)) return root;\n }\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Directory / glob walkers (tolerant — never throw)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Is the path an existing directory? Tolerant (false on any stat error). */\nexport function isDir(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Is the path an existing regular file? Tolerant (false on any stat error). */\nexport function isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively list every file under `root` whose name matches `predicate`.\n * Tolerant: an unreadable directory is skipped, never thrown. Returns absolute\n * paths in directory-walk order. A non-existent root yields [].\n */\nexport function walkFiles(\n root: string,\n predicate: (name: string, absPath: string) => boolean,\n maxDepth = 12,\n): string[] {\n const out: string[] = [];\n if (!isDir(root)) return out;\n\n const stack: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }];\n while (stack.length > 0) {\n const { dir, depth } = stack.pop() as { dir: string; depth: number };\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue; // unreadable dir → skip\n }\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (depth < maxDepth) stack.push({ dir: abs, depth: depth + 1 });\n } else if (entry.isFile() && predicate(entry.name, abs)) {\n out.push(abs);\n }\n }\n }\n return out;\n}\n\n/**\n * List immediate subdirectory names of `dir` (not recursive). Tolerant: returns\n * [] for a missing/unreadable directory.\n */\nexport function listSubdirs(dir: string): string[] {\n if (!isDir(dir)) return [];\n try {\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(dir, e.name));\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/logger.ts
|
|
4
|
+
var ORDER = {
|
|
5
|
+
silent: 0,
|
|
6
|
+
error: 1,
|
|
7
|
+
warn: 2,
|
|
8
|
+
info: 3,
|
|
9
|
+
debug: 4
|
|
10
|
+
};
|
|
11
|
+
function currentLevel() {
|
|
12
|
+
const env = (process.env.AGENT_CONNECTOR_LOG ?? "info").toLowerCase();
|
|
13
|
+
return env in ORDER ? env : "info";
|
|
14
|
+
}
|
|
15
|
+
function emit(level, args) {
|
|
16
|
+
if (ORDER[currentLevel()] < ORDER[level]) return;
|
|
17
|
+
const prefix = { error: "\u2717", warn: "!", info: "\u203A", debug: "\xB7" }[level];
|
|
18
|
+
console.error(prefix, ...args);
|
|
19
|
+
}
|
|
20
|
+
var log = {
|
|
21
|
+
error: (...args) => emit("error", args),
|
|
22
|
+
warn: (...args) => emit("warn", args),
|
|
23
|
+
info: (...args) => emit("info", args),
|
|
24
|
+
debug: (...args) => emit("debug", args)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
log
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-KHO5WNTP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/logger.ts"],"sourcesContent":["/**\n * core/logger — minimal leveled logger. Writes human output to stderr so stdout\n * stays clean for machine-readable payloads (hook replies, --json output).\n */\n\nexport type LogLevel = \"silent\" | \"error\" | \"warn\" | \"info\" | \"debug\";\n\nconst ORDER: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4,\n};\n\nfunction currentLevel(): LogLevel {\n const env = (process.env.AGENT_CONNECTOR_LOG ?? \"info\").toLowerCase();\n return (env in ORDER ? env : \"info\") as LogLevel;\n}\n\nfunction emit(level: Exclude<LogLevel, \"silent\">, args: unknown[]): void {\n if (ORDER[currentLevel()] < ORDER[level]) return;\n const prefix = { error: \"✗\", warn: \"!\", info: \"›\", debug: \"·\" }[level];\n // eslint-disable-next-line no-console\n console.error(prefix, ...args);\n}\n\nexport const log = {\n error: (...args: unknown[]) => emit(\"error\", args),\n warn: (...args: unknown[]) => emit(\"warn\", args),\n info: (...args: unknown[]) => emit(\"info\", args),\n debug: (...args: unknown[]) => emit(\"debug\", args),\n};\n"],"mappings":";;;AAOA,IAAM,QAAkC;AAAA,EACtC,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,eAAyB;AAChC,QAAM,OAAO,QAAQ,IAAI,uBAAuB,QAAQ,YAAY;AACpE,SAAQ,OAAO,QAAQ,MAAM;AAC/B;AAEA,SAAS,KAAK,OAAoC,MAAuB;AACvE,MAAI,MAAM,aAAa,CAAC,IAAI,MAAM,KAAK,EAAG;AAC1C,QAAM,SAAS,EAAE,OAAO,UAAK,MAAM,KAAK,MAAM,UAAK,OAAO,OAAI,EAAE,KAAK;AAErE,UAAQ,MAAM,QAAQ,GAAG,IAAI;AAC/B;AAEO,IAAM,MAAM;AAAA,EACjB,OAAO,IAAI,SAAoB,KAAK,SAAS,IAAI;AAAA,EACjD,MAAM,IAAI,SAAoB,KAAK,QAAQ,IAAI;AAAA,EAC/C,MAAM,IAAI,SAAoB,KAAK,QAAQ,IAAI;AAAA,EAC/C,OAAO,IAAI,SAAoB,KAAK,SAAS,IAAI;AACnD;","names":[]}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
defineConnector
|
|
4
|
+
} from "./chunk-XNAM7F35.js";
|
|
5
|
+
import {
|
|
6
|
+
connectorDir,
|
|
7
|
+
connectorsDir,
|
|
8
|
+
ensureDir
|
|
9
|
+
} from "./chunk-TRQKHP7D.js";
|
|
10
|
+
|
|
11
|
+
// src/core/load-connector.ts
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
13
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
14
|
+
import { pathToFileURL } from "url";
|
|
15
|
+
var CONFIG_FILENAMES = [
|
|
16
|
+
"agent-connector.config.mjs",
|
|
17
|
+
"agent-connector.config.js",
|
|
18
|
+
"agent-connector.config.json"
|
|
19
|
+
];
|
|
20
|
+
function findConnectorConfig(startDir) {
|
|
21
|
+
let dir = resolve(startDir);
|
|
22
|
+
for (; ; ) {
|
|
23
|
+
for (const name of CONFIG_FILENAMES) {
|
|
24
|
+
const candidate = join(dir, name);
|
|
25
|
+
if (existsSync(candidate)) return candidate;
|
|
26
|
+
}
|
|
27
|
+
const parent = dirname(dir);
|
|
28
|
+
if (parent === dir) break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function isResolvedConnector(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && Array.isArray(value.hookEvents);
|
|
35
|
+
}
|
|
36
|
+
async function loadConnectorFromPath(path) {
|
|
37
|
+
const modulePath = isAbsolute(path) ? path : resolve(path);
|
|
38
|
+
if (modulePath.endsWith(".json")) {
|
|
39
|
+
const text = readFileSync(modulePath, "utf8");
|
|
40
|
+
const config = JSON.parse(text);
|
|
41
|
+
const connector2 = defineConnector(config);
|
|
42
|
+
return { connector: connector2, modulePath };
|
|
43
|
+
}
|
|
44
|
+
const mod = await import(pathToFileURL(modulePath).href);
|
|
45
|
+
const exported = mod.default;
|
|
46
|
+
if (exported == null) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Connector module has no default export: ${modulePath}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const connector = isResolvedConnector(exported) ? exported : defineConnector(exported);
|
|
52
|
+
return { connector, modulePath };
|
|
53
|
+
}
|
|
54
|
+
function serializablePlatforms(platforms) {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [id, override] of Object.entries(platforms)) {
|
|
57
|
+
if (!override) continue;
|
|
58
|
+
const { hooks, ...rest } = override;
|
|
59
|
+
const clean = { ...rest };
|
|
60
|
+
if (typeof hooks === "boolean") clean.hooks = hooks;
|
|
61
|
+
out[id] = clean;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function registerConnector(connector, modulePath) {
|
|
66
|
+
const meta = {
|
|
67
|
+
id: connector.id,
|
|
68
|
+
displayName: connector.displayName,
|
|
69
|
+
version: connector.version,
|
|
70
|
+
modulePath: resolve(modulePath),
|
|
71
|
+
telemetry: connector.telemetry,
|
|
72
|
+
hookEvents: connector.hookEvents,
|
|
73
|
+
hasServer: !!connector.server,
|
|
74
|
+
server: connector.server ?? null,
|
|
75
|
+
commands: connector.commands,
|
|
76
|
+
skills: connector.skills,
|
|
77
|
+
subagents: connector.subagents,
|
|
78
|
+
targets: connector.targets,
|
|
79
|
+
platforms: serializablePlatforms(connector.platforms)
|
|
80
|
+
};
|
|
81
|
+
const dir = connectorDir(connector.id);
|
|
82
|
+
ensureDir(dir);
|
|
83
|
+
const outPath = join(dir, "connector.json");
|
|
84
|
+
writeFileSync(outPath, `${JSON.stringify(meta, null, 2)}
|
|
85
|
+
`, "utf8");
|
|
86
|
+
return outPath;
|
|
87
|
+
}
|
|
88
|
+
function deregisterConnector(id) {
|
|
89
|
+
const dir = connectorDir(id);
|
|
90
|
+
const existed = existsSync(dir);
|
|
91
|
+
if (existed) rmSync(dir, { recursive: true, force: true });
|
|
92
|
+
return { removed: existed, path: dir };
|
|
93
|
+
}
|
|
94
|
+
function readRegisteredMeta(id) {
|
|
95
|
+
const recordPath = join(connectorDir(id), "connector.json");
|
|
96
|
+
if (!existsSync(recordPath)) return null;
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(readFileSync(recordPath, "utf8"));
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function loadRegisteredConnector(id) {
|
|
104
|
+
const meta = readRegisteredMeta(id);
|
|
105
|
+
if (!meta) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Connector "${id}" is not registered (no record at ${join(connectorDir(id), "connector.json")}). Run an install/register step first.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!meta.modulePath || !existsSync(meta.modulePath)) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Connector "${id}" record points at a missing module: ${meta.modulePath}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const { connector } = await loadConnectorFromPath(meta.modulePath);
|
|
116
|
+
return connector;
|
|
117
|
+
}
|
|
118
|
+
function connectorFromMeta(meta) {
|
|
119
|
+
return {
|
|
120
|
+
id: meta.id,
|
|
121
|
+
displayName: meta.displayName,
|
|
122
|
+
version: meta.version,
|
|
123
|
+
...meta.server ? { server: meta.server } : {},
|
|
124
|
+
hooks: {},
|
|
125
|
+
hookEvents: meta.hookEvents ?? [],
|
|
126
|
+
telemetry: meta.telemetry,
|
|
127
|
+
commands: meta.commands ?? [],
|
|
128
|
+
skills: meta.skills ?? [],
|
|
129
|
+
subagents: meta.subagents ?? [],
|
|
130
|
+
platforms: meta.platforms ?? {},
|
|
131
|
+
targets: meta.targets
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function listRegisteredConnectors() {
|
|
135
|
+
const dir = connectorsDir();
|
|
136
|
+
if (!existsSync(dir)) return [];
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const entry of readdirSync(dir)) {
|
|
139
|
+
const meta = readRegisteredMeta(entry);
|
|
140
|
+
if (meta) out.push(connectorFromMeta(meta));
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
findConnectorConfig,
|
|
147
|
+
loadConnectorFromPath,
|
|
148
|
+
registerConnector,
|
|
149
|
+
deregisterConnector,
|
|
150
|
+
readRegisteredMeta,
|
|
151
|
+
loadRegisteredConnector,
|
|
152
|
+
connectorFromMeta,
|
|
153
|
+
listRegisteredConnectors
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=chunk-LTT56GI3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/load-connector.ts"],"sourcesContent":["/**\n * core/load-connector — discover, load, and register connector definitions.\n *\n * Responsibilities:\n * 1. Locate the developer's `agent-connector.config.{mjs,js,json}` by walking\n * upward from a start directory (the project root convention).\n * 2. Load a config from a path into a LIVE ResolvedConnector (with handlers).\n * 3. Register a connector by persisting SERIALIZABLE metadata (no functions)\n * to `connectorDir(id)/connector.json`, pointing at the source module so\n * live handlers can be re-imported at runtime from a stable record.\n *\n * Why split live vs. serialized? Hook handlers are functions and cannot survive\n * JSON. The registry stores only what JSON can hold plus an absolute\n * `modulePath`; the runtime re-imports that module to recover the live handlers.\n */\n\nimport { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport type {\n CommandDef,\n HookEventName,\n PlatformId,\n PlatformOverride,\n ResolvedConnector,\n ServerDef,\n SkillDef,\n SubagentDef,\n} from \"./types.js\";\nimport { defineConnector } from \"./define-connector.js\";\nimport { connectorDir, connectorsDir, ensureDir } from \"./paths.js\";\n\n/** Candidate config filenames, in resolution-precedence order. */\nconst CONFIG_FILENAMES = [\n \"agent-connector.config.mjs\",\n \"agent-connector.config.js\",\n \"agent-connector.config.json\",\n] as const;\n\n/**\n * The serializable subset of a connector persisted to `connector.json`.\n * Hook handler functions are intentionally absent — only the absolute\n * `modulePath` is stored so the live ResolvedConnector can be re-imported.\n */\nexport interface RegisteredMeta {\n id: string;\n displayName: string;\n version: string;\n /** Absolute path to the source module that produced this connector. */\n modulePath: string;\n telemetry: ResolvedConnector[\"telemetry\"];\n /** Canonical event names the connector handles (handlers themselves stripped). */\n hookEvents: HookEventName[];\n hasServer: boolean;\n server: ServerDef | null;\n /**\n * Content surfaces (commands/skills/subagents). These are JSON-serializable\n * (no functions) so they persist cleanly and let uninstall locate the files\n * to remove even when the source module is gone.\n */\n commands: CommandDef[];\n skills: SkillDef[];\n subagents: SubagentDef[];\n targets: ResolvedConnector[\"targets\"];\n /** Per-platform overrides with any hook handler functions stripped. */\n platforms: Partial<Record<PlatformId, PlatformOverride>>;\n}\n\n/**\n * Search `startDir` and each ancestor for a connector config file. Returns the\n * absolute path to the first match (filesystem-root-stopping), or null.\n */\nexport function findConnectorConfig(startDir: string): string | null {\n let dir = resolve(startDir);\n // Walk upward until `dirname(dir) === dir` (filesystem root).\n for (;;) {\n for (const name of CONFIG_FILENAMES) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/** True if a value already looks like a ResolvedConnector (vs. a raw config). */\nfunction isResolvedConnector(value: unknown): value is ResolvedConnector {\n return (\n typeof value === \"object\" &&\n value !== null &&\n Array.isArray((value as { hookEvents?: unknown }).hookEvents)\n );\n}\n\n/**\n * Load a connector from an absolute or relative path.\n * - `.json` → JSON.parse, then defineConnector().\n * - `.mjs` / `.js` → dynamic import (via pathToFileURL, Windows-safe),\n * take the default export. If it already looks like a\n * ResolvedConnector use it as-is; otherwise treat it as\n * a ConnectorConfig and run it through defineConnector().\n * Returns the live connector plus the absolute module path it came from.\n */\nexport async function loadConnectorFromPath(\n path: string,\n): Promise<{ connector: ResolvedConnector; modulePath: string }> {\n const modulePath = isAbsolute(path) ? path : resolve(path);\n\n if (modulePath.endsWith(\".json\")) {\n const text = readFileSync(modulePath, \"utf8\");\n const config = JSON.parse(text) as unknown;\n const connector = defineConnector(config as Parameters<typeof defineConnector>[0]);\n return { connector, modulePath };\n }\n\n // .mjs / .js — dynamic ESM import. pathToFileURL keeps Windows paths valid.\n const mod = (await import(pathToFileURL(modulePath).href)) as {\n default?: unknown;\n };\n const exported = mod.default;\n if (exported == null) {\n throw new Error(\n `Connector module has no default export: ${modulePath}`,\n );\n }\n const connector = isResolvedConnector(exported)\n ? exported\n : defineConnector(exported as Parameters<typeof defineConnector>[0]);\n return { connector, modulePath };\n}\n\n/**\n * Strip any function-valued `hooks` from per-platform overrides so the result\n * is JSON-serializable. A boolean `hooks` override is preserved; an object\n * override is dropped (it can only carry handler functions, which are not\n * serializable). Everything else on the override is kept verbatim.\n */\nfunction serializablePlatforms(\n platforms: Partial<Record<PlatformId, PlatformOverride>>,\n): Partial<Record<PlatformId, PlatformOverride>> {\n const out: Partial<Record<PlatformId, PlatformOverride>> = {};\n for (const [id, override] of Object.entries(platforms) as [\n PlatformId,\n PlatformOverride | undefined,\n ][]) {\n if (!override) continue;\n const { hooks, ...rest } = override;\n const clean: PlatformOverride = { ...rest };\n // Keep only a boolean hooks flag; object hooks carry handler functions.\n if (typeof hooks === \"boolean\") clean.hooks = hooks;\n out[id] = clean;\n }\n return out;\n}\n\n/**\n * Persist a connector's serializable metadata to `connectorDir(id)/connector.json`.\n * Handlers are NOT serialized — they are re-imported from `modulePath` at runtime.\n * Returns the absolute path of the written record.\n */\nexport function registerConnector(\n connector: ResolvedConnector,\n modulePath: string,\n): string {\n const meta: RegisteredMeta = {\n id: connector.id,\n displayName: connector.displayName,\n version: connector.version,\n modulePath: resolve(modulePath),\n telemetry: connector.telemetry,\n hookEvents: connector.hookEvents,\n hasServer: !!connector.server,\n server: connector.server ?? null,\n commands: connector.commands,\n skills: connector.skills,\n subagents: connector.subagents,\n targets: connector.targets,\n platforms: serializablePlatforms(connector.platforms),\n };\n\n const dir = connectorDir(connector.id);\n ensureDir(dir);\n const outPath = join(dir, \"connector.json\");\n writeFileSync(outPath, `${JSON.stringify(meta, null, 2)}\\n`, \"utf8\");\n return outPath;\n}\n\n/**\n * Deregister a connector by removing its DATA-dir record directory\n * (`connectorDir(id)`) recursively. This is the inverse of {@link registerConnector}:\n * registerConnector writes `connectorDir(id)/connector.json`, but per-target\n * uninstall only strips host-native registrations — it never removes this record,\n * leaving an orphan behind. Best-effort + guarded: returns the directory path and\n * whether it existed before removal.\n */\nexport function deregisterConnector(id: string): {\n removed: boolean;\n path: string;\n} {\n const dir = connectorDir(id);\n const existed = existsSync(dir);\n if (existed) rmSync(dir, { recursive: true, force: true });\n return { removed: existed, path: dir };\n}\n\n/**\n * Read the registered metadata record for `id`, or null if not registered or\n * the record is unreadable/corrupt.\n */\nexport function readRegisteredMeta(id: string): RegisteredMeta | null {\n const recordPath = join(connectorDir(id), \"connector.json\");\n if (!existsSync(recordPath)) return null;\n try {\n return JSON.parse(readFileSync(recordPath, \"utf8\")) as RegisteredMeta;\n } catch {\n return null;\n }\n}\n\n/**\n * Load the LIVE ResolvedConnector (with hook handlers) for a registered id by\n * re-importing its source module. Throws a clear error when the connector is\n * not registered or its source module no longer resolves to it.\n */\nexport async function loadRegisteredConnector(\n id: string,\n): Promise<ResolvedConnector> {\n const meta = readRegisteredMeta(id);\n if (!meta) {\n throw new Error(\n `Connector \"${id}\" is not registered (no record at ${join(connectorDir(id), \"connector.json\")}). ` +\n `Run an install/register step first.`,\n );\n }\n if (!meta.modulePath || !existsSync(meta.modulePath)) {\n throw new Error(\n `Connector \"${id}\" record points at a missing module: ${meta.modulePath}`,\n );\n }\n const { connector } = await loadConnectorFromPath(meta.modulePath);\n return connector;\n}\n\n/**\n * Build a handler-less ResolvedConnector from stored metadata. Suitable for\n * inspection/doctor where live hook handlers are not needed (and where the\n * source module may even be gone). `hooks` is empty; `hookEvents` is preserved.\n */\nexport function connectorFromMeta(meta: RegisteredMeta): ResolvedConnector {\n return {\n id: meta.id,\n displayName: meta.displayName,\n version: meta.version,\n ...(meta.server ? { server: meta.server } : {}),\n hooks: {},\n hookEvents: meta.hookEvents ?? [],\n telemetry: meta.telemetry,\n commands: meta.commands ?? [],\n skills: meta.skills ?? [],\n subagents: meta.subagents ?? [],\n platforms: meta.platforms ?? {},\n targets: meta.targets,\n };\n}\n\n/**\n * List every registered connector as a handler-less ResolvedConnector (read\n * from `connectorsDir()`). Used by doctor to health-check what is actually\n * installed, rather than guessing from the local working directory.\n */\nexport function listRegisteredConnectors(): ResolvedConnector[] {\n const dir = connectorsDir();\n if (!existsSync(dir)) return [];\n const out: ResolvedConnector[] = [];\n for (const entry of readdirSync(dir)) {\n const meta = readRegisteredMeta(entry);\n if (meta) out.push(connectorFromMeta(meta));\n }\n return out;\n}\n"],"mappings":"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","names":["connector"]}
|