codesift-mcp 0.9.10 → 0.10.1

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 (75) hide show
  1. package/README.md +23 -0
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +4 -0
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/telemetry-commands.d.ts +2 -0
  6. package/dist/cli/telemetry-commands.d.ts.map +1 -0
  7. package/dist/cli/telemetry-commands.js +50 -0
  8. package/dist/cli/telemetry-commands.js.map +1 -0
  9. package/dist/register-tool-groups/meta.d.ts.map +1 -1
  10. package/dist/register-tool-groups/meta.js +6 -1
  11. package/dist/register-tool-groups/meta.js.map +1 -1
  12. package/dist/server-helpers/response-hints.d.ts.map +1 -1
  13. package/dist/server-helpers/response-hints.js +14 -3
  14. package/dist/server-helpers/response-hints.js.map +1 -1
  15. package/dist/server-helpers.d.ts.map +1 -1
  16. package/dist/server-helpers.js +9 -2
  17. package/dist/server-helpers.js.map +1 -1
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +6 -0
  20. package/dist/server.js.map +1 -1
  21. package/dist/storage/_shared.d.ts +9 -0
  22. package/dist/storage/_shared.d.ts.map +1 -1
  23. package/dist/storage/_shared.js +24 -0
  24. package/dist/storage/_shared.js.map +1 -1
  25. package/dist/storage/telemetry/aggregator.d.ts +52 -0
  26. package/dist/storage/telemetry/aggregator.d.ts.map +1 -0
  27. package/dist/storage/telemetry/aggregator.js +188 -0
  28. package/dist/storage/telemetry/aggregator.js.map +1 -0
  29. package/dist/storage/telemetry/anon-id.d.ts +11 -0
  30. package/dist/storage/telemetry/anon-id.d.ts.map +1 -0
  31. package/dist/storage/telemetry/anon-id.js +62 -0
  32. package/dist/storage/telemetry/anon-id.js.map +1 -0
  33. package/dist/storage/telemetry/config.d.ts +37 -0
  34. package/dist/storage/telemetry/config.d.ts.map +1 -0
  35. package/dist/storage/telemetry/config.js +130 -0
  36. package/dist/storage/telemetry/config.js.map +1 -0
  37. package/dist/storage/telemetry/env-profile.d.ts +19 -0
  38. package/dist/storage/telemetry/env-profile.d.ts.map +1 -0
  39. package/dist/storage/telemetry/env-profile.js +67 -0
  40. package/dist/storage/telemetry/env-profile.js.map +1 -0
  41. package/dist/storage/telemetry/payload.d.ts +8 -0
  42. package/dist/storage/telemetry/payload.d.ts.map +1 -0
  43. package/dist/storage/telemetry/payload.js +22 -0
  44. package/dist/storage/telemetry/payload.js.map +1 -0
  45. package/dist/storage/telemetry/sanitizer.d.ts +48 -0
  46. package/dist/storage/telemetry/sanitizer.d.ts.map +1 -0
  47. package/dist/storage/telemetry/sanitizer.js +82 -0
  48. package/dist/storage/telemetry/sanitizer.js.map +1 -0
  49. package/dist/storage/telemetry/uploader.d.ts +11 -0
  50. package/dist/storage/telemetry/uploader.d.ts.map +1 -0
  51. package/dist/storage/telemetry/uploader.js +140 -0
  52. package/dist/storage/telemetry/uploader.js.map +1 -0
  53. package/dist/storage/usage-tracker.d.ts +14 -0
  54. package/dist/storage/usage-tracker.d.ts.map +1 -1
  55. package/dist/storage/usage-tracker.js +45 -0
  56. package/dist/storage/usage-tracker.js.map +1 -1
  57. package/dist/tools/nest-audit-tools.d.ts.map +1 -1
  58. package/dist/tools/nest-audit-tools.js +21 -1
  59. package/dist/tools/nest-audit-tools.js.map +1 -1
  60. package/dist/tools/project-tools.d.ts +3 -0
  61. package/dist/tools/project-tools.d.ts.map +1 -1
  62. package/dist/tools/project-tools.js +2 -0
  63. package/dist/tools/project-tools.js.map +1 -1
  64. package/dist/tools/symbol-tools.d.ts.map +1 -1
  65. package/dist/tools/symbol-tools.js +26 -11
  66. package/dist/tools/symbol-tools.js.map +1 -1
  67. package/dist/utils/edge-provenance.d.ts +55 -0
  68. package/dist/utils/edge-provenance.d.ts.map +1 -0
  69. package/dist/utils/edge-provenance.js +53 -0
  70. package/dist/utils/edge-provenance.js.map +1 -0
  71. package/dist/utils/overlay-cache.d.ts +38 -0
  72. package/dist/utils/overlay-cache.d.ts.map +1 -0
  73. package/dist/utils/overlay-cache.js +73 -0
  74. package/dist/utils/overlay-cache.js.map +1 -0
  75. package/package.json +1 -1
@@ -0,0 +1,130 @@
1
+ // Telemetry level resolution + opt-out. No network here — this module only
2
+ // decides WHETHER and at what level telemetry runs. See spec
3
+ // docs/specs/2026-07-19-telemetry-spec.md §4.
4
+ import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ /** Bump when the L1 payload shape changes. Collector tolerates unknown versions. */
8
+ export const TELEMETRY_SCHEMA_VERSION = 1;
9
+ function dataDir() {
10
+ return process.env["CODESIFT_DATA_DIR"] ?? join(homedir(), ".codesift");
11
+ }
12
+ export function getConfigPath() {
13
+ return join(dataDir(), "config.json");
14
+ }
15
+ /** Best-effort read of ~/.codesift/config.json — never throws. */
16
+ export function readStoredConfig() {
17
+ try {
18
+ const raw = readFileSync(getConfigPath(), "utf-8");
19
+ const parsed = JSON.parse(raw);
20
+ if (parsed && typeof parsed === "object")
21
+ return parsed;
22
+ }
23
+ catch {
24
+ /* absent or malformed → defaults */
25
+ }
26
+ return {};
27
+ }
28
+ function normalizeLevel(v) {
29
+ if (typeof v !== "string")
30
+ return null;
31
+ const s = v.trim().toLowerCase();
32
+ if (s === "off" || s === "0" || s === "false" || s === "no")
33
+ return "off";
34
+ if (s === "full")
35
+ return "full";
36
+ if (s === "anon" || s === "on" || s === "1" || s === "true" || s === "anonymous")
37
+ return "anon";
38
+ return null;
39
+ }
40
+ function isTruthyEnv(v) {
41
+ if (!v)
42
+ return false;
43
+ const s = v.trim().toLowerCase();
44
+ return s !== "" && s !== "0" && s !== "false" && s !== "no";
45
+ }
46
+ /**
47
+ * Resolve the effective telemetry level. Precedence (first match wins):
48
+ * 1. DO_NOT_TRACK truthy → off (respect the cross-tool standard)
49
+ * 2. CODESIFT_TELEMETRY env → off | anon | full
50
+ * 3. config.json `telemetry` → off | anon | full (level string or {level})
51
+ * 4. default → anon (opt-out model — spec decision)
52
+ */
53
+ export function resolveTelemetryLevel() {
54
+ if (isTruthyEnv(process.env["DO_NOT_TRACK"]))
55
+ return "off";
56
+ const fromEnv = normalizeLevel(process.env["CODESIFT_TELEMETRY"]);
57
+ if (fromEnv)
58
+ return fromEnv;
59
+ const stored = readStoredConfig().telemetry;
60
+ const fromStore = normalizeLevel(typeof stored === "object" && stored !== null ? stored.level : stored);
61
+ if (fromStore)
62
+ return fromStore;
63
+ return "anon";
64
+ }
65
+ /**
66
+ * Persist the telemetry level into ~/.codesift/config.json (merging with any
67
+ * existing config). Atomic tmp+rename. Used by `codesift telemetry on|off|full`.
68
+ */
69
+ export function writeStoredTelemetryLevel(level) {
70
+ const path = getConfigPath();
71
+ let cfg = {};
72
+ try {
73
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
74
+ if (parsed && typeof parsed === "object")
75
+ cfg = parsed;
76
+ }
77
+ catch {
78
+ /* absent/malformed → start fresh */
79
+ }
80
+ cfg["telemetry"] = { level };
81
+ mkdirSync(dataDir(), { recursive: true });
82
+ const tmp = path + ".tmp";
83
+ writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
84
+ renameSync(tmp, path);
85
+ }
86
+ function noticePath() {
87
+ return join(dataDir(), "telemetry-notice-shown");
88
+ }
89
+ /**
90
+ * Print the one-time consent notice to STDERR (spec §4). Shown once per machine
91
+ * when telemetry is anon, then a marker file suppresses it. STDERR so it never
92
+ * corrupts the MCP stdio protocol. Best-effort — never throws.
93
+ */
94
+ export function maybePrintFirstRunNotice() {
95
+ try {
96
+ if (resolveTelemetryLevel() !== "anon")
97
+ return;
98
+ const marker = noticePath();
99
+ try {
100
+ readFileSync(marker, "utf-8");
101
+ return; // already shown
102
+ }
103
+ catch {
104
+ /* not shown yet */
105
+ }
106
+ process.stderr.write("[codesift] Anonymous usage stats are ON (tool names, latencies, error/empty rates,\n" +
107
+ " bucketed env — NO queries, paths, repo names or code). See exactly what is sent:\n" +
108
+ " `codesift telemetry show`. Opt out: CODESIFT_TELEMETRY=off (or DO_NOT_TRACK=1).\n");
109
+ mkdirSync(dataDir(), { recursive: true });
110
+ writeFileSync(marker, String(Date.now()) + "\n", "utf-8");
111
+ }
112
+ catch {
113
+ /* ignore */
114
+ }
115
+ }
116
+ /** Human-readable reason for the current level — powers `codesift telemetry show`. */
117
+ export function telemetrySource() {
118
+ if (isTruthyEnv(process.env["DO_NOT_TRACK"])) {
119
+ return { level: "off", reason: "DO_NOT_TRACK env is set" };
120
+ }
121
+ const fromEnv = normalizeLevel(process.env["CODESIFT_TELEMETRY"]);
122
+ if (fromEnv)
123
+ return { level: fromEnv, reason: `CODESIFT_TELEMETRY=${process.env["CODESIFT_TELEMETRY"]}` };
124
+ const stored = readStoredConfig().telemetry;
125
+ const fromStore = normalizeLevel(typeof stored === "object" && stored !== null ? stored.level : stored);
126
+ if (fromStore)
127
+ return { level: fromStore, reason: `config.json telemetry=${fromStore}` };
128
+ return { level: "anon", reason: "default (anonymous, opt-out)" };
129
+ }
130
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/storage/telemetry/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface EnvProfile {
2
+ platform: string;
3
+ arch: string;
4
+ ram_bucket: string;
5
+ cores: number;
6
+ node_ver: string;
7
+ codesift_ver: string;
8
+ repo_size_bucket?: string;
9
+ top3_ext?: string[];
10
+ }
11
+ export declare function repoSizeBucket(fileCount: number): string;
12
+ export declare function getCodesiftVersion(): string;
13
+ /** Build the environment profile. Repo dimensions are optional — omitted when
14
+ * no repo is in scope (e.g. `telemetry show` outside a project). */
15
+ export declare function buildEnvProfile(repo?: {
16
+ fileCount: number;
17
+ topExts: string[];
18
+ }): EnvProfile;
19
+ //# sourceMappingURL=env-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-profile.d.ts","sourceRoot":"","sources":["../../../src/storage/telemetry/env-profile.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAWD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD;AAID,wBAAgB,kBAAkB,IAAI,MAAM,CAiB3C;AAED;qEACqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,UAAU,CAe3F"}
@@ -0,0 +1,67 @@
1
+ // Bucketed environment profile — coarse enough that it can never identify a
2
+ // machine, precise enough to slice behaviour by config (spec §1). No hostnames,
3
+ // no paths, no exact counts that could fingerprint.
4
+ import { readFileSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import * as os from "node:os";
7
+ function ramBucket(bytes) {
8
+ const gb = bytes / 1024 ** 3;
9
+ if (gb < 8)
10
+ return "<8gb";
11
+ if (gb < 16)
12
+ return "8-16gb";
13
+ if (gb < 32)
14
+ return "16-32gb";
15
+ if (gb < 64)
16
+ return "32-64gb";
17
+ return ">=64gb";
18
+ }
19
+ export function repoSizeBucket(fileCount) {
20
+ if (fileCount < 1000)
21
+ return "<1k";
22
+ if (fileCount < 10_000)
23
+ return "1-10k";
24
+ if (fileCount < 50_000)
25
+ return "10-50k";
26
+ return ">50k";
27
+ }
28
+ let versionCache = null;
29
+ export function getCodesiftVersion() {
30
+ if (versionCache)
31
+ return versionCache;
32
+ // package.json sits at the repo root; this module is src|dist/storage/telemetry/.
33
+ for (const rel of ["../../../package.json", "../../../../package.json"]) {
34
+ try {
35
+ const p = fileURLToPath(new URL(rel, import.meta.url));
36
+ const pkg = JSON.parse(readFileSync(p, "utf-8"));
37
+ if (pkg.name === "codesift-mcp" && typeof pkg.version === "string") {
38
+ versionCache = pkg.version;
39
+ return versionCache;
40
+ }
41
+ }
42
+ catch {
43
+ /* try next */
44
+ }
45
+ }
46
+ versionCache = "unknown";
47
+ return versionCache;
48
+ }
49
+ /** Build the environment profile. Repo dimensions are optional — omitted when
50
+ * no repo is in scope (e.g. `telemetry show` outside a project). */
51
+ export function buildEnvProfile(repo) {
52
+ const nodeMajor = process.versions.node.split(".")[0] ?? process.versions.node;
53
+ const profile = {
54
+ platform: os.platform(),
55
+ arch: os.arch(),
56
+ ram_bucket: ramBucket(os.totalmem()),
57
+ cores: os.cpus().length,
58
+ node_ver: nodeMajor,
59
+ codesift_ver: getCodesiftVersion(),
60
+ };
61
+ if (repo) {
62
+ profile.repo_size_bucket = repoSizeBucket(repo.fileCount);
63
+ profile.top3_ext = repo.topExts.slice(0, 3);
64
+ }
65
+ return profile;
66
+ }
67
+ //# sourceMappingURL=env-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-profile.js","sourceRoot":"","sources":["../../../src/storage/telemetry/env-profile.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAa9B,SAAS,SAAS,CAAC,KAAa;IAC9B,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC;IAC7B,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,EAAE,GAAG,EAAE;QAAE,OAAO,QAAQ,CAAC;IAC7B,IAAI,EAAE,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,EAAE,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,IAAI,SAAS,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,SAAS,GAAG,MAAM;QAAE,OAAO,OAAO,CAAC;IACvC,IAAI,SAAS,GAAG,MAAM;QAAE,OAAO,QAAQ,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEvC,MAAM,UAAU,kBAAkB;IAChC,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,kFAAkF;IAClF,KAAK,MAAM,GAAG,IAAI,CAAC,uBAAuB,EAAE,0BAA0B,CAAC,EAAE,CAAC;QACxE,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACvD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAwC,CAAC;YACxF,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACnE,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC;gBAC3B,OAAO,YAAY,CAAC;YACtB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;IACD,YAAY,GAAG,SAAS,CAAC;IACzB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;qEACqE;AACrE,MAAM,UAAU,eAAe,CAAC,IAA+C;IAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC/E,MAAM,OAAO,GAAe;QAC1B,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;QACvB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE;QACf,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;QACpC,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM;QACvB,QAAQ,EAAE,SAAS;QACnB,YAAY,EAAE,kBAAkB,EAAE;KACnC,CAAC;IACF,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1D,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type Level1Payload } from "./sanitizer.js";
2
+ /**
3
+ * Build the exact Level-1 payload that would be sent right now, from the local
4
+ * usage.jsonl. Repo dimensions are omitted here (added per-context at push
5
+ * time). Runs the sanitizer guard so `telemetry show` proves the allowlist.
6
+ */
7
+ export declare function buildCurrentLevel1Payload(now: number, sinceTs?: number): Promise<Level1Payload>;
8
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/storage/telemetry/payload.ts"],"names":[],"mappings":"AAKA,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEzF;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAShG"}
@@ -0,0 +1,22 @@
1
+ // Convenience assembly used by `codesift telemetry show` and (Phase 3) the
2
+ // uploader: local usage → aggregates → sanitized Level-1 payload.
3
+ import { readLocalUsageEntries, aggregateToolMetrics, aggregateHintFunnel, aggregatePlanTurnFunnel } from "./aggregator.js";
4
+ import { buildEnvProfile } from "./env-profile.js";
5
+ import { getAnonId } from "./anon-id.js";
6
+ import { buildLevel1Payload, assertSanitized } from "./sanitizer.js";
7
+ /**
8
+ * Build the exact Level-1 payload that would be sent right now, from the local
9
+ * usage.jsonl. Repo dimensions are omitted here (added per-context at push
10
+ * time). Runs the sanitizer guard so `telemetry show` proves the allowlist.
11
+ */
12
+ export async function buildCurrentLevel1Payload(now, sinceTs = 0) {
13
+ const entries = await readLocalUsageEntries(sinceTs);
14
+ const tools = aggregateToolMetrics(entries);
15
+ const hints = aggregateHintFunnel(entries);
16
+ const planTurn = aggregatePlanTurnFunnel(entries);
17
+ const env = buildEnvProfile();
18
+ const payload = buildLevel1Payload({ anonId: getAnonId(), env, tools, hints, planTurn, now });
19
+ assertSanitized(payload); // throws if the allowlist was ever violated
20
+ return payload;
21
+ }
22
+ //# sourceMappingURL=payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../../src/storage/telemetry/payload.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,kEAAkE;AAClE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC5H,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAsB,MAAM,gBAAgB,CAAC;AAEzF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAW,EAAE,OAAO,GAAG,CAAC;IACtE,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9F,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,4CAA4C;IACtE,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { EnvProfile } from "./env-profile.js";
2
+ import type { ToolAggregate, HintEmission, PlanTurnFunnel } from "./aggregator.js";
3
+ export interface Level1Payload {
4
+ schema_version: number;
5
+ ts: number;
6
+ anon_id: string;
7
+ env: EnvProfile;
8
+ tools: Level1ToolMetric[];
9
+ hints: Level1HintEmission[];
10
+ plan_turn: Level1PlanTurn[];
11
+ }
12
+ export interface Level1ToolMetric {
13
+ tool: string;
14
+ day: string;
15
+ count: number;
16
+ p50_ms: number;
17
+ p95_ms: number;
18
+ max_ms: number;
19
+ error_rate: number;
20
+ empty_result_rate: number;
21
+ cache_hit_rate: number;
22
+ }
23
+ export interface Level1HintEmission {
24
+ day: string;
25
+ hint_code: string;
26
+ emitted: number;
27
+ applied: number;
28
+ }
29
+ export interface Level1PlanTurn {
30
+ day: string;
31
+ recommended: number;
32
+ used: number;
33
+ }
34
+ export declare function buildLevel1Payload(input: {
35
+ anonId: string;
36
+ env: EnvProfile;
37
+ tools: ToolAggregate[];
38
+ hints?: HintEmission[];
39
+ planTurn?: PlanTurnFunnel[];
40
+ now: number;
41
+ }): Level1Payload;
42
+ /**
43
+ * Deep-scan a payload's KEYS for anything forbidden. Throws on violation.
44
+ * Called from tests and (dev builds) before send — cheap insurance that the
45
+ * allowlist is actually honoured end-to-end.
46
+ */
47
+ export declare function assertSanitized(payload: unknown, pathTrace?: string): void;
48
+ //# sourceMappingURL=sanitizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../../src/storage/telemetry/sanitizer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAwCD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,aAAa,CAUhB;AAYD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,SAAM,GAAG,IAAI,CAavE"}
@@ -0,0 +1,82 @@
1
+ // The ONLY place a Level-1 (anonymous) payload is constructed. Allowlist, not
2
+ // blocklist (spec §1): every field is assembled explicitly here, so adding a
3
+ // new field to the wire format is a conscious edit to this file — never an
4
+ // accidental spread of a raw entry. `assertSanitized` is a defense-in-depth
5
+ // guard that fails loudly (in tests / dev) if a forbidden key ever leaks in.
6
+ import { TELEMETRY_SCHEMA_VERSION } from "./config.js";
7
+ /** Explicitly pick ONLY allowlisted fields from an aggregate. */
8
+ function pickToolMetric(a) {
9
+ return {
10
+ tool: a.tool,
11
+ day: a.day,
12
+ count: a.count,
13
+ p50_ms: a.p50_ms,
14
+ p95_ms: a.p95_ms,
15
+ max_ms: a.max_ms,
16
+ error_rate: a.error_rate,
17
+ empty_result_rate: a.empty_result_rate,
18
+ cache_hit_rate: a.cache_hit_rate,
19
+ };
20
+ }
21
+ function pickHint(h) {
22
+ return { day: h.day, hint_code: h.hint_code, emitted: h.emitted, applied: h.applied };
23
+ }
24
+ function pickPlanTurn(p) {
25
+ return { day: p.day, recommended: p.recommended, used: p.used };
26
+ }
27
+ /** Explicitly pick ONLY allowlisted env fields (no hostname/paths). */
28
+ function pickEnv(env) {
29
+ const picked = {
30
+ platform: env.platform,
31
+ arch: env.arch,
32
+ ram_bucket: env.ram_bucket,
33
+ cores: env.cores,
34
+ node_ver: env.node_ver,
35
+ codesift_ver: env.codesift_ver,
36
+ };
37
+ if (env.repo_size_bucket !== undefined)
38
+ picked.repo_size_bucket = env.repo_size_bucket;
39
+ if (env.top3_ext !== undefined)
40
+ picked.top3_ext = env.top3_ext.slice(0, 3);
41
+ return picked;
42
+ }
43
+ export function buildLevel1Payload(input) {
44
+ return {
45
+ schema_version: TELEMETRY_SCHEMA_VERSION,
46
+ ts: input.now,
47
+ anon_id: input.anonId,
48
+ env: pickEnv(input.env),
49
+ tools: input.tools.map(pickToolMetric),
50
+ hints: (input.hints ?? []).map(pickHint),
51
+ plan_turn: (input.planTurn ?? []).map(pickPlanTurn),
52
+ };
53
+ }
54
+ /**
55
+ * Keys that must NEVER appear anywhere in an L1 payload. Extension identifiers
56
+ * like ".ts" in top3_ext are fine (they're values, not keys); this checks KEYS.
57
+ */
58
+ const FORBIDDEN_KEYS = new Set([
59
+ "query", "repo", "path", "file", "files", "args", "args_summary",
60
+ "symbol", "symbols", "hostname", "host", "username", "user", "ip",
61
+ "source", "content", "code", "name", "session_id",
62
+ ]);
63
+ /**
64
+ * Deep-scan a payload's KEYS for anything forbidden. Throws on violation.
65
+ * Called from tests and (dev builds) before send — cheap insurance that the
66
+ * allowlist is actually honoured end-to-end.
67
+ */
68
+ export function assertSanitized(payload, pathTrace = "$") {
69
+ if (Array.isArray(payload)) {
70
+ payload.forEach((v, i) => assertSanitized(v, `${pathTrace}[${i}]`));
71
+ return;
72
+ }
73
+ if (payload && typeof payload === "object") {
74
+ for (const [k, v] of Object.entries(payload)) {
75
+ if (FORBIDDEN_KEYS.has(k.toLowerCase())) {
76
+ throw new Error(`telemetry sanitizer: forbidden key "${k}" at ${pathTrace}`);
77
+ }
78
+ assertSanitized(v, `${pathTrace}.${k}`);
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=sanitizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["../../../src/storage/telemetry/sanitizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * One flush cycle. Reads usage since the watermark, builds the level-appropriate
3
+ * payload, POSTs once (with a single retry), and advances the watermark ONLY on
4
+ * success. Best-effort; never throws.
5
+ */
6
+ export declare function flushTelemetry(now: number): Promise<"off" | "empty" | "sent" | "failed">;
7
+ /** Start the background flush timer. Called once at server startup. No-op when
8
+ * telemetry is off or no endpoint is configured. */
9
+ export declare function startTelemetryTimer(): void;
10
+ export declare function stopTelemetryTimer(): void;
11
+ //# sourceMappingURL=uploader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../../src/storage/telemetry/uploader.ts"],"names":[],"mappings":"AA+EA;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAqC9F;AAID;qDACqD;AACrD,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,140 @@
1
+ // Telemetry uploader (spec §3). Runs on a timer, NEVER on the tool hot path.
2
+ // Source of truth is the local usage.jsonl + a watermark (last-uploaded ts) —
3
+ // no separate spool to keep in sync. Push happens ONLY when an endpoint is
4
+ // configured (CODESIFT_TELEMETRY_URL); with no endpoint nothing leaves the
5
+ // machine, which is the safe default until the public collector is exposed
6
+ // (staged rollout: notice first, push later).
7
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
8
+ import { gzipSync } from "node:zlib";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { resolveTelemetryLevel } from "./config.js";
12
+ import { readLocalUsageEntries, aggregateToolMetrics, aggregateHintFunnel, aggregatePlanTurnFunnel } from "./aggregator.js";
13
+ import { buildEnvProfile } from "./env-profile.js";
14
+ import { getAnonId } from "./anon-id.js";
15
+ import { buildLevel1Payload, assertSanitized } from "./sanitizer.js";
16
+ const FLUSH_TIMEOUT_MS = 2000; // hard cap per spec §3
17
+ const INITIAL_FLUSH_DELAY_MS = 10_000;
18
+ const FLUSH_INTERVAL_MS = 24 * 60 * 60 * 1000; // daily
19
+ function dataDir() {
20
+ return process.env["CODESIFT_DATA_DIR"] ?? join(homedir(), ".codesift");
21
+ }
22
+ function watermarkPath() {
23
+ return join(dataDir(), "telemetry-watermark");
24
+ }
25
+ function readWatermark() {
26
+ try {
27
+ const n = Number(readFileSync(watermarkPath(), "utf-8").trim());
28
+ return Number.isFinite(n) ? n : 0;
29
+ }
30
+ catch {
31
+ return 0;
32
+ }
33
+ }
34
+ function writeWatermark(ts) {
35
+ try {
36
+ mkdirSync(dataDir(), { recursive: true });
37
+ writeFileSync(watermarkPath(), String(ts), "utf-8");
38
+ }
39
+ catch {
40
+ /* ignore */
41
+ }
42
+ }
43
+ /** Baked default collector — anonymous ingest needs NO token (the endpoint is
44
+ * open + validated + rate-limited). Full/fleet sets CODESIFT_TELEMETRY_TOKEN.
45
+ * Both are env-overridable. */
46
+ const DEFAULT_TELEMETRY_URL = "https://coding.tgmedit.com";
47
+ function endpoint() {
48
+ const url = (process.env["CODESIFT_TELEMETRY_URL"] ?? DEFAULT_TELEMETRY_URL).replace(/\/$/, "");
49
+ const token = process.env["CODESIFT_TELEMETRY_TOKEN"] ?? "";
50
+ return { url, token };
51
+ }
52
+ async function postGzip(url, token, body) {
53
+ const controller = new AbortController();
54
+ const timer = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
55
+ try {
56
+ const gz = gzipSync(Buffer.from(JSON.stringify(body), "utf-8"));
57
+ const res = await fetch(url, {
58
+ method: "POST",
59
+ headers: {
60
+ "content-type": "application/json",
61
+ "content-encoding": "gzip",
62
+ "x-api-key": token,
63
+ "x-telemetry-client": "codesift-mcp",
64
+ },
65
+ body: gz,
66
+ signal: controller.signal,
67
+ });
68
+ return res.ok;
69
+ }
70
+ catch {
71
+ return false; // timeout / network / abort — fail-silent
72
+ }
73
+ finally {
74
+ clearTimeout(timer);
75
+ }
76
+ }
77
+ /**
78
+ * One flush cycle. Reads usage since the watermark, builds the level-appropriate
79
+ * payload, POSTs once (with a single retry), and advances the watermark ONLY on
80
+ * success. Best-effort; never throws.
81
+ */
82
+ export async function flushTelemetry(now) {
83
+ const level = resolveTelemetryLevel();
84
+ if (level === "off")
85
+ return "off";
86
+ const ep = endpoint();
87
+ const since = readWatermark();
88
+ const entries = await readLocalUsageEntries(since);
89
+ if (entries.length === 0)
90
+ return "empty";
91
+ const maxTs = entries.reduce((m, e) => (e.ts > m ? e.ts : m), since);
92
+ let body;
93
+ let path;
94
+ if (level === "full") {
95
+ // Level 2 (opt-in): raw entries, batched. Full detail — query/paths included.
96
+ path = "/ingest/codesift";
97
+ body = { schema_version: 1, level: "full", anon_id: getAnonId(), entries };
98
+ }
99
+ else {
100
+ // Level 1 (anon): aggregate-only, allowlisted, guarded.
101
+ path = "/ingest/codesift";
102
+ const payload = buildLevel1Payload({
103
+ anonId: getAnonId(),
104
+ env: buildEnvProfile(),
105
+ tools: aggregateToolMetrics(entries),
106
+ hints: aggregateHintFunnel(entries),
107
+ planTurn: aggregatePlanTurnFunnel(entries),
108
+ now,
109
+ });
110
+ assertSanitized(payload); // never send an unsanitized L1 payload
111
+ body = payload;
112
+ }
113
+ let ok = await postGzip(ep.url + path, ep.token, body);
114
+ if (!ok)
115
+ ok = await postGzip(ep.url + path, ep.token, body); // single retry
116
+ if (!ok)
117
+ return "failed"; // leave watermark — retry next flush
118
+ writeWatermark(maxTs);
119
+ return "sent";
120
+ }
121
+ let timer = null;
122
+ /** Start the background flush timer. Called once at server startup. No-op when
123
+ * telemetry is off or no endpoint is configured. */
124
+ export function startTelemetryTimer() {
125
+ if (timer)
126
+ return;
127
+ if (resolveTelemetryLevel() === "off")
128
+ return;
129
+ const tick = () => { void flushTelemetry(Date.now()); };
130
+ setTimeout(tick, INITIAL_FLUSH_DELAY_MS).unref();
131
+ timer = setInterval(tick, FLUSH_INTERVAL_MS);
132
+ timer.unref?.();
133
+ }
134
+ export function stopTelemetryTimer() {
135
+ if (timer) {
136
+ clearInterval(timer);
137
+ timer = null;
138
+ }
139
+ }
140
+ //# sourceMappingURL=uploader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader.js","sourceRoot":"","sources":["../../../src/storage/telemetry/uploader.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,15 @@ export interface UsageEntry {
18
18
  result_tokens_sent?: number;
19
19
  /** True when the handler threw — the logged result is the error message. */
20
20
  error?: boolean;
21
+ /** True when served from the response cache — excluded from latency/error/empty
22
+ * aggregation, counted only toward cache_hit_rate. */
23
+ cache_hit?: boolean;
24
+ /** Response-hint codes emitted on this call (e.g. ["H1","H12"]) — powers the
25
+ * hint-efficacy funnel. Codes only, never the hint text. */
26
+ hints_emitted?: string[];
27
+ /** Tool names plan_turn recommended on this call — powers the discovery funnel
28
+ * (was a recommended tool actually used next?). Names only. */
29
+ recommended_tools?: string[];
21
30
  }
22
31
  export declare function getUsagePath(): string;
23
32
  /**
@@ -45,6 +54,7 @@ export declare function addSavings(tokens: number): void;
45
54
  * Non-blocking: errors are silently caught and logged to stderr.
46
55
  */
47
56
  export declare function trackUsage(entry: UsageEntry): Promise<void>;
57
+ export declare function setPlanTurnRecommendations(names: string[]): void;
48
58
  /**
49
59
  * High-level helper: track a completed tool call.
50
60
  * Called at the end of each tool handler after the result is computed.
@@ -54,6 +64,10 @@ export declare function trackToolCall(tool: string, args: Record<string, unknown
54
64
  sentChars?: number;
55
65
  /** The handler threw — resultText is the error message. */
56
66
  error?: boolean;
67
+ /** Served from the response cache (excluded from latency/error/empty stats). */
68
+ cacheHit?: boolean;
69
+ /** Response-hint codes emitted on this call, e.g. ["H1","H12"]. */
70
+ hintsEmitted?: string[];
57
71
  }): void;
58
72
  /**
59
73
  * Get the current session ID (for testing or display).
@@ -1 +1 @@
1
- {"version":3,"file":"usage-tracker.d.ts","sourceRoot":"","sources":["../../src/storage/usage-tracker.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB;;yEAEqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;iEAE6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAMD;uFACuF;AACvF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,CAqB5F,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyCzB;AAaD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CA+BzD;AAQD,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE/C;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,OAAO,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE;IACN,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACA,IAAI,CAmBN;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
1
+ {"version":3,"file":"usage-tracker.d.ts","sourceRoot":"","sources":["../../src/storage/usage-tracker.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB;;yEAEqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;iEAE6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;2DACuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;iEAC6D;IAC7D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;oEACgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAMD,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAMD;uFACuF;AACvF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,CAqB5F,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyCzB;AAaD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CA+BzD;AAQD,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE/C;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;AAYD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAEhE;AAuBD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,OAAO,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE;IACN,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,GACA,IAAI,CA+BN;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC"}