@decantr/telemetry 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 +21 -0
- package/README.md +82 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.js +166 -0
- package/dist/client.js.map +1 -0
- package/dist/events.d.ts +130 -0
- package/dist/events.js +25 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +248 -0
- package/dist/index.js.map +1 -0
- package/dist/posthog.d.ts +14 -0
- package/dist/posthog.js +63 -0
- package/dist/posthog.js.map +1 -0
- package/dist/privacy.d.ts +16 -0
- package/dist/privacy.js +79 -0
- package/dist/privacy.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Decantr AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @decantr/telemetry
|
|
2
|
+
|
|
3
|
+
Privacy-preserving telemetry contracts, clients, and analytics sinks for Decantr.
|
|
4
|
+
|
|
5
|
+
This package is the first layer of Decantr's usage intelligence system. It defines the event names and payload shapes that the CLI, API, MCP server, registry app, and content pipeline can emit without coupling those surfaces to a single analytics vendor.
|
|
6
|
+
|
|
7
|
+
## Product Stance
|
|
8
|
+
|
|
9
|
+
Decantr telemetry measures Decantr usage, not customer application data.
|
|
10
|
+
|
|
11
|
+
Allowed signals include command names, registry sources, registry content IDs, package versions, workflow modes, success/failure, duration, audit scores, and aggregate counts. Do not send prompts, source code, generated files, raw file paths, environment variables, secrets, API keys, email addresses, IP addresses, or user agents.
|
|
12
|
+
|
|
13
|
+
## MVP Events
|
|
14
|
+
|
|
15
|
+
- `cli.command.completed`
|
|
16
|
+
- `registry.item.resolved`
|
|
17
|
+
- `registry.sync.completed`
|
|
18
|
+
- `execution_pack.compiled`
|
|
19
|
+
- `execution_pack.selected`
|
|
20
|
+
- `audit.completed`
|
|
21
|
+
- `critique.completed`
|
|
22
|
+
- `content.validation.completed`
|
|
23
|
+
- `content.publish.completed`
|
|
24
|
+
- `user.signup.completed`
|
|
25
|
+
- `org.created`
|
|
26
|
+
- `api_key.created`
|
|
27
|
+
|
|
28
|
+
Private registries do not need a separate telemetry surface yet. Use `registry.item.resolved` with `registrySource: "private"` and `visibility: "private"` when that product line lands.
|
|
29
|
+
|
|
30
|
+
## PostHog
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createPostHogTelemetrySink, createTelemetryClient } from '@decantr/telemetry';
|
|
34
|
+
|
|
35
|
+
const telemetry = createTelemetryClient({
|
|
36
|
+
context: {
|
|
37
|
+
source: 'api',
|
|
38
|
+
environment: 'production',
|
|
39
|
+
serviceName: 'decantr-api',
|
|
40
|
+
decantrVersion: '1.7.26',
|
|
41
|
+
},
|
|
42
|
+
sink: createPostHogTelemetrySink({
|
|
43
|
+
apiKey: process.env.POSTHOG_PROJECT_TOKEN!,
|
|
44
|
+
host: process.env.POSTHOG_HOST,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await telemetry.capture({
|
|
49
|
+
name: 'registry.item.resolved',
|
|
50
|
+
context: {
|
|
51
|
+
source: 'api',
|
|
52
|
+
projectId: 'project_opaque_id',
|
|
53
|
+
orgId: 'org_opaque_id',
|
|
54
|
+
registrySource: 'official',
|
|
55
|
+
},
|
|
56
|
+
properties: {
|
|
57
|
+
contentType: 'pattern',
|
|
58
|
+
itemId: 'hero-split',
|
|
59
|
+
namespace: '@official',
|
|
60
|
+
success: true,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The PostHog sink uses opaque Decantr IDs as `distinct_id`, maps `orgId` and `projectId` to PostHog groups, and defaults `$process_person_profile` to `false`.
|
|
66
|
+
|
|
67
|
+
## Future First-Party Dashboard
|
|
68
|
+
|
|
69
|
+
The generic fetch sink is intended for a later Decantr-owned ingestion endpoint:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { createFetchTelemetrySink, createTelemetryClient } from '@decantr/telemetry';
|
|
73
|
+
|
|
74
|
+
const telemetry = createTelemetryClient({
|
|
75
|
+
sink: createFetchTelemetrySink({
|
|
76
|
+
endpoint: 'https://api.decantr.ai/v1/telemetry/events',
|
|
77
|
+
headers: () => ({ Authorization: `Bearer ${process.env.DECANTR_TELEMETRY_TOKEN}` }),
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That endpoint can write raw events and daily rollups into Supabase or an analytics warehouse while PostHog remains the fast product analytics layer.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DecantrTelemetryEvent, TelemetryContext } from './events.js';
|
|
2
|
+
import { TelemetryRedactionOptions } from './privacy.js';
|
|
3
|
+
|
|
4
|
+
interface TelemetrySink {
|
|
5
|
+
capture(event: DecantrTelemetryEvent): Promise<void> | void;
|
|
6
|
+
flush?(): Promise<void> | void;
|
|
7
|
+
}
|
|
8
|
+
interface TelemetryClient {
|
|
9
|
+
capture(event: DecantrTelemetryEvent): Promise<void>;
|
|
10
|
+
flush(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
interface TelemetryClientOptions {
|
|
13
|
+
context?: Partial<TelemetryContext>;
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
onError?: (error: unknown, event: DecantrTelemetryEvent) => void;
|
|
16
|
+
redaction?: TelemetryRedactionOptions;
|
|
17
|
+
sink?: TelemetrySink;
|
|
18
|
+
}
|
|
19
|
+
interface FetchTelemetrySinkOptions {
|
|
20
|
+
endpoint: string;
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
headers?: HeadersInit | (() => HeadersInit);
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
declare function createNoopTelemetrySink(): TelemetrySink;
|
|
26
|
+
declare function createTelemetryClient(options?: TelemetryClientOptions): TelemetryClient;
|
|
27
|
+
declare function createFetchTelemetrySink(options: FetchTelemetrySinkOptions): TelemetrySink;
|
|
28
|
+
|
|
29
|
+
export { type FetchTelemetrySinkOptions, type TelemetryClient, type TelemetryClientOptions, type TelemetrySink, createFetchTelemetrySink, createNoopTelemetrySink, createTelemetryClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var DECANTR_TELEMETRY_SCHEMA_VERSION = "0.1.0";
|
|
3
|
+
|
|
4
|
+
// src/privacy.ts
|
|
5
|
+
var REDACTED_VALUE = "[redacted]";
|
|
6
|
+
var DEFAULT_MAX_STRING_LENGTH = 240;
|
|
7
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 25;
|
|
8
|
+
var DEFAULT_MAX_OBJECT_KEYS = 80;
|
|
9
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
10
|
+
var SENSITIVE_KEY_PATTERNS = [
|
|
11
|
+
/^api[_-]?key$/i,
|
|
12
|
+
/^authorization$/i,
|
|
13
|
+
/^code$/i,
|
|
14
|
+
/^content$/i,
|
|
15
|
+
/^contents$/i,
|
|
16
|
+
/^cookie$/i,
|
|
17
|
+
/^cwd$/i,
|
|
18
|
+
/^email$/i,
|
|
19
|
+
/^env$/i,
|
|
20
|
+
/^file[_-]?contents$/i,
|
|
21
|
+
/^file[_-]?path$/i,
|
|
22
|
+
/^home$/i,
|
|
23
|
+
/^ip[_-]?address$/i,
|
|
24
|
+
/^password$/i,
|
|
25
|
+
/^path$/i,
|
|
26
|
+
/^prompt$/i,
|
|
27
|
+
/^raw[_-]?prompt$/i,
|
|
28
|
+
/^secret$/i,
|
|
29
|
+
/^source$/i,
|
|
30
|
+
/^source[_-]?code$/i,
|
|
31
|
+
/^token$/i,
|
|
32
|
+
/^url$/i,
|
|
33
|
+
/^user[_-]?agent$/i
|
|
34
|
+
];
|
|
35
|
+
function isSensitiveTelemetryKey(key, patterns = SENSITIVE_KEY_PATTERNS) {
|
|
36
|
+
return patterns.some((pattern) => pattern.test(key));
|
|
37
|
+
}
|
|
38
|
+
function sanitizeTelemetryValue(value, options = {}, depth = 0) {
|
|
39
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
40
|
+
if (depth > maxDepth) {
|
|
41
|
+
return options.redactedValue ?? REDACTED_VALUE;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
|
|
45
|
+
return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
if (value === void 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
55
|
+
return value.slice(0, maxArrayLength).map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));
|
|
56
|
+
}
|
|
57
|
+
const redactedValue = options.redactedValue ?? REDACTED_VALUE;
|
|
58
|
+
const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;
|
|
59
|
+
const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;
|
|
60
|
+
const entries = Object.entries(value).slice(0, maxObjectKeys);
|
|
61
|
+
const sanitized = {};
|
|
62
|
+
for (const [key, entryValue] of entries) {
|
|
63
|
+
sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns) ? redactedValue : sanitizeTelemetryValue(entryValue, options, depth + 1);
|
|
64
|
+
}
|
|
65
|
+
return sanitized;
|
|
66
|
+
}
|
|
67
|
+
function sanitizeTelemetryEvent(event, options = {}) {
|
|
68
|
+
return {
|
|
69
|
+
...event,
|
|
70
|
+
properties: sanitizeTelemetryValue(
|
|
71
|
+
event.properties,
|
|
72
|
+
options
|
|
73
|
+
)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/client.ts
|
|
78
|
+
function createNoopTelemetrySink() {
|
|
79
|
+
return {
|
|
80
|
+
capture() {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createTelemetryClient(options = {}) {
|
|
86
|
+
const enabled = options.enabled ?? true;
|
|
87
|
+
const sink = options.sink ?? createNoopTelemetrySink();
|
|
88
|
+
const pending = /* @__PURE__ */ new Set();
|
|
89
|
+
async function capture(event) {
|
|
90
|
+
if (!enabled) return;
|
|
91
|
+
const enriched = sanitizeTelemetryEvent(
|
|
92
|
+
{
|
|
93
|
+
...event,
|
|
94
|
+
context: {
|
|
95
|
+
...options.context,
|
|
96
|
+
...event.context
|
|
97
|
+
},
|
|
98
|
+
timestamp: normalizeTimestamp(event.timestamp)
|
|
99
|
+
},
|
|
100
|
+
options.redaction
|
|
101
|
+
);
|
|
102
|
+
const promise = Promise.resolve().then(() => sink.capture(enriched)).catch((error) => {
|
|
103
|
+
options.onError?.(error, enriched);
|
|
104
|
+
}).then(() => void 0);
|
|
105
|
+
pending.add(promise);
|
|
106
|
+
promise.finally(() => pending.delete(promise));
|
|
107
|
+
await promise;
|
|
108
|
+
}
|
|
109
|
+
async function flush() {
|
|
110
|
+
await Promise.allSettled([...pending]);
|
|
111
|
+
await sink.flush?.();
|
|
112
|
+
}
|
|
113
|
+
return { capture, flush };
|
|
114
|
+
}
|
|
115
|
+
function createFetchTelemetrySink(options) {
|
|
116
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
117
|
+
return {
|
|
118
|
+
async capture(event) {
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetchImpl(options.endpoint, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
...normalizeHeaders(
|
|
127
|
+
typeof options.headers === "function" ? options.headers() : options.headers
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
schemaVersion: DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
132
|
+
event
|
|
133
|
+
}),
|
|
134
|
+
signal: controller.signal
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw new Error(`Telemetry endpoint returned HTTP ${response.status}.`);
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function normalizeTimestamp(timestamp) {
|
|
146
|
+
if (timestamp instanceof Date) {
|
|
147
|
+
return timestamp.toISOString();
|
|
148
|
+
}
|
|
149
|
+
return timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
150
|
+
}
|
|
151
|
+
function normalizeHeaders(headers) {
|
|
152
|
+
if (!headers) return {};
|
|
153
|
+
if (headers instanceof Headers) {
|
|
154
|
+
return Object.fromEntries(headers.entries());
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(headers)) {
|
|
157
|
+
return Object.fromEntries(headers);
|
|
158
|
+
}
|
|
159
|
+
return headers;
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
createFetchTelemetrySink,
|
|
163
|
+
createNoopTelemetrySink,
|
|
164
|
+
createTelemetryClient
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts","../src/privacy.ts","../src/client.ts"],"sourcesContent":["export const DECANTR_TELEMETRY_SCHEMA_VERSION = '0.1.0';\n\nexport type TelemetrySource = 'api' | 'cli' | 'content-ci' | 'mcp' | 'registry-web';\nexport type TelemetryEnvironment = 'development' | 'preview' | 'production' | 'test';\nexport type RegistrySource = 'cache' | 'custom' | 'none' | 'official' | 'private';\nexport type WorkflowMode =\n | 'brownfield-attach'\n | 'greenfield-contract-only'\n | 'greenfield-scaffold'\n | 'hybrid-compose';\nexport type AdoptionMode = 'contract-only' | 'decantr-css' | 'style-bridge';\nexport type ProjectScope = 'single-app' | 'workspace-app';\nexport type TelemetryContentType = 'archetype' | 'blueprint' | 'pattern' | 'shell' | 'theme';\nexport type TelemetryVisibility = 'private' | 'public' | 'team';\nexport type TelemetryAnalysisScope = 'hosted' | 'local';\n\nexport const DECANTR_TELEMETRY_EVENT_NAMES = [\n 'api_key.created',\n 'audit.completed',\n 'cli.command.completed',\n 'content.publish.completed',\n 'content.validation.completed',\n 'critique.completed',\n 'execution_pack.compiled',\n 'execution_pack.selected',\n 'org.created',\n 'registry.item.resolved',\n 'registry.sync.completed',\n 'user.signup.completed',\n] as const;\n\nexport type DecantrTelemetryEventName = (typeof DECANTR_TELEMETRY_EVENT_NAMES)[number];\n\nexport type TelemetryPropertyValue =\n | TelemetryPropertyValue[]\n | boolean\n | null\n | number\n | string\n | undefined\n | { [key: string]: TelemetryPropertyValue };\n\nexport type TelemetryProperties = Record<string, TelemetryPropertyValue>;\n\nexport interface TelemetryContext {\n source: TelemetrySource;\n environment?: TelemetryEnvironment;\n serviceName?: string;\n serviceVersion?: string;\n decantrVersion?: string;\n registrySource?: RegistrySource;\n anonymousId?: string;\n installId?: string;\n projectId?: string;\n sessionId?: string;\n userId?: string;\n orgId?: string;\n}\n\nexport interface TelemetryEventBase<\n Name extends DecantrTelemetryEventName = DecantrTelemetryEventName,\n Properties extends TelemetryProperties = TelemetryProperties,\n> {\n name: Name;\n context: TelemetryContext;\n properties: Properties;\n timestamp?: Date | string;\n}\n\nexport interface CliCommandCompletedProperties extends TelemetryProperties {\n command: string;\n success: boolean;\n durationMs: number;\n adoptionMode?: AdoptionMode;\n errorCode?: string;\n offline?: boolean;\n projectScope?: ProjectScope;\n registrySource?: RegistrySource;\n targetFramework?: string;\n workflowMode?: WorkflowMode;\n}\n\nexport interface RegistryItemResolvedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n cacheHit?: boolean;\n durationMs?: number;\n errorCode?: string;\n itemId?: string;\n namespace?: string;\n registrySource?: RegistrySource;\n version?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface RegistrySyncCompletedProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n registrySource?: RegistrySource;\n totalItems?: number;\n}\n\nexport interface ExecutionPackCompiledProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n pageCount?: number;\n patternCount?: number;\n sectionCount?: number;\n targetFramework?: string;\n}\n\nexport interface ExecutionPackSelectedProperties extends TelemetryProperties {\n packType: 'mutation' | 'page' | 'review' | 'scaffold' | 'section';\n success: boolean;\n durationMs?: number;\n errorCode?: string;\n id?: string;\n}\n\nexport interface AuditCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n pageCount?: number;\n runtimePassed?: boolean;\n score?: number;\n warnCount?: number;\n}\n\nexport interface CritiqueCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n infoCount?: number;\n overall?: number;\n warnCount?: number;\n}\n\nexport interface ContentValidationCompletedProperties extends TelemetryProperties {\n valid: boolean;\n contentType?: TelemetryContentType;\n durationMs?: number;\n errorCount?: number;\n itemCount?: number;\n warningCount?: number;\n}\n\nexport interface ContentPublishCompletedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n namespace?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface ProductEventProperties extends TelemetryProperties {\n success?: boolean;\n channel?: string;\n entrypoint?: string;\n plan?: string;\n}\n\nexport type DecantrTelemetryEvent =\n | TelemetryEventBase<'api_key.created', ProductEventProperties>\n | TelemetryEventBase<'audit.completed', AuditCompletedProperties>\n | TelemetryEventBase<'cli.command.completed', CliCommandCompletedProperties>\n | TelemetryEventBase<'content.publish.completed', ContentPublishCompletedProperties>\n | TelemetryEventBase<'content.validation.completed', ContentValidationCompletedProperties>\n | TelemetryEventBase<'critique.completed', CritiqueCompletedProperties>\n | TelemetryEventBase<'execution_pack.compiled', ExecutionPackCompiledProperties>\n | TelemetryEventBase<'execution_pack.selected', ExecutionPackSelectedProperties>\n | TelemetryEventBase<'org.created', ProductEventProperties>\n | TelemetryEventBase<'registry.item.resolved', RegistryItemResolvedProperties>\n | TelemetryEventBase<'registry.sync.completed', RegistrySyncCompletedProperties>\n | TelemetryEventBase<'user.signup.completed', ProductEventProperties>;\n\nexport function isDecantrTelemetryEventName(value: string): value is DecantrTelemetryEventName {\n return (DECANTR_TELEMETRY_EVENT_NAMES as readonly string[]).includes(value);\n}\n","import type { DecantrTelemetryEvent, TelemetryPropertyValue } from './events.js';\n\nexport const REDACTED_VALUE = '[redacted]';\n\nconst DEFAULT_MAX_STRING_LENGTH = 240;\nconst DEFAULT_MAX_ARRAY_LENGTH = 25;\nconst DEFAULT_MAX_OBJECT_KEYS = 80;\nconst DEFAULT_MAX_DEPTH = 4;\n\nconst SENSITIVE_KEY_PATTERNS = [\n /^api[_-]?key$/i,\n /^authorization$/i,\n /^code$/i,\n /^content$/i,\n /^contents$/i,\n /^cookie$/i,\n /^cwd$/i,\n /^email$/i,\n /^env$/i,\n /^file[_-]?contents$/i,\n /^file[_-]?path$/i,\n /^home$/i,\n /^ip[_-]?address$/i,\n /^password$/i,\n /^path$/i,\n /^prompt$/i,\n /^raw[_-]?prompt$/i,\n /^secret$/i,\n /^source$/i,\n /^source[_-]?code$/i,\n /^token$/i,\n /^url$/i,\n /^user[_-]?agent$/i,\n];\n\nexport interface TelemetryRedactionOptions {\n maxArrayLength?: number;\n maxDepth?: number;\n maxObjectKeys?: number;\n maxStringLength?: number;\n redactedValue?: string;\n sensitiveKeyPatterns?: RegExp[];\n}\n\nexport function isSensitiveTelemetryKey(key: string, patterns = SENSITIVE_KEY_PATTERNS): boolean {\n return patterns.some((pattern) => pattern.test(key));\n}\n\nexport function sanitizeTelemetryValue(\n value: TelemetryPropertyValue,\n options: TelemetryRedactionOptions = {},\n depth = 0,\n): TelemetryPropertyValue {\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n if (depth > maxDepth) {\n return options.redactedValue ?? REDACTED_VALUE;\n }\n\n if (typeof value === 'string') {\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;\n return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n return value;\n }\n\n if (value === undefined) {\n return null;\n }\n\n if (Array.isArray(value)) {\n const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;\n return value\n .slice(0, maxArrayLength)\n .map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));\n }\n\n const redactedValue = options.redactedValue ?? REDACTED_VALUE;\n const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;\n const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;\n const entries = Object.entries(value).slice(0, maxObjectKeys);\n const sanitized: Record<string, TelemetryPropertyValue> = {};\n\n for (const [key, entryValue] of entries) {\n sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns)\n ? redactedValue\n : sanitizeTelemetryValue(entryValue, options, depth + 1);\n }\n\n return sanitized;\n}\n\nexport function sanitizeTelemetryEvent(\n event: DecantrTelemetryEvent,\n options: TelemetryRedactionOptions = {},\n): DecantrTelemetryEvent {\n return {\n ...event,\n properties: sanitizeTelemetryValue(\n event.properties,\n options,\n ) as DecantrTelemetryEvent['properties'],\n } as DecantrTelemetryEvent;\n}\n","import {\n DECANTR_TELEMETRY_SCHEMA_VERSION,\n type DecantrTelemetryEvent,\n type TelemetryContext,\n} from './events.js';\nimport { sanitizeTelemetryEvent, type TelemetryRedactionOptions } from './privacy.js';\n\nexport interface TelemetrySink {\n capture(event: DecantrTelemetryEvent): Promise<void> | void;\n flush?(): Promise<void> | void;\n}\n\nexport interface TelemetryClient {\n capture(event: DecantrTelemetryEvent): Promise<void>;\n flush(): Promise<void>;\n}\n\nexport interface TelemetryClientOptions {\n context?: Partial<TelemetryContext>;\n enabled?: boolean;\n onError?: (error: unknown, event: DecantrTelemetryEvent) => void;\n redaction?: TelemetryRedactionOptions;\n sink?: TelemetrySink;\n}\n\nexport interface FetchTelemetrySinkOptions {\n endpoint: string;\n fetch?: typeof fetch;\n headers?: HeadersInit | (() => HeadersInit);\n timeoutMs?: number;\n}\n\nexport function createNoopTelemetrySink(): TelemetrySink {\n return {\n capture() {\n return undefined;\n },\n };\n}\n\nexport function createTelemetryClient(options: TelemetryClientOptions = {}): TelemetryClient {\n const enabled = options.enabled ?? true;\n const sink = options.sink ?? createNoopTelemetrySink();\n const pending = new Set<Promise<void>>();\n\n async function capture(event: DecantrTelemetryEvent): Promise<void> {\n if (!enabled) return;\n\n const enriched = sanitizeTelemetryEvent(\n {\n ...event,\n context: {\n ...options.context,\n ...event.context,\n },\n timestamp: normalizeTimestamp(event.timestamp),\n } as DecantrTelemetryEvent,\n options.redaction,\n );\n\n const promise = Promise.resolve()\n .then(() => sink.capture(enriched))\n .catch((error) => {\n options.onError?.(error, enriched);\n })\n .then(() => undefined);\n\n pending.add(promise);\n promise.finally(() => pending.delete(promise));\n await promise;\n }\n\n async function flush(): Promise<void> {\n await Promise.allSettled([...pending]);\n await sink.flush?.();\n }\n\n return { capture, flush };\n}\n\nexport function createFetchTelemetrySink(options: FetchTelemetrySinkOptions): TelemetrySink {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n return {\n async capture(event) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n\n try {\n const response = await fetchImpl(options.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...normalizeHeaders(\n typeof options.headers === 'function' ? options.headers() : options.headers,\n ),\n },\n body: JSON.stringify({\n schemaVersion: DECANTR_TELEMETRY_SCHEMA_VERSION,\n event,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Telemetry endpoint returned HTTP ${response.status}.`);\n }\n } finally {\n clearTimeout(timeout);\n }\n },\n };\n}\n\nfunction normalizeTimestamp(timestamp: Date | string | undefined): string {\n if (timestamp instanceof Date) {\n return timestamp.toISOString();\n }\n return timestamp ?? new Date().toISOString();\n}\n\nfunction normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {};\n\n if (headers instanceof Headers) {\n return Object.fromEntries(headers.entries());\n }\n\n if (Array.isArray(headers)) {\n return Object.fromEntries(headers);\n }\n\n return headers as Record<string, string>;\n}\n"],"mappings":"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","names":[]}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
declare const DECANTR_TELEMETRY_SCHEMA_VERSION = "0.1.0";
|
|
2
|
+
type TelemetrySource = 'api' | 'cli' | 'content-ci' | 'mcp' | 'registry-web';
|
|
3
|
+
type TelemetryEnvironment = 'development' | 'preview' | 'production' | 'test';
|
|
4
|
+
type RegistrySource = 'cache' | 'custom' | 'none' | 'official' | 'private';
|
|
5
|
+
type WorkflowMode = 'brownfield-attach' | 'greenfield-contract-only' | 'greenfield-scaffold' | 'hybrid-compose';
|
|
6
|
+
type AdoptionMode = 'contract-only' | 'decantr-css' | 'style-bridge';
|
|
7
|
+
type ProjectScope = 'single-app' | 'workspace-app';
|
|
8
|
+
type TelemetryContentType = 'archetype' | 'blueprint' | 'pattern' | 'shell' | 'theme';
|
|
9
|
+
type TelemetryVisibility = 'private' | 'public' | 'team';
|
|
10
|
+
type TelemetryAnalysisScope = 'hosted' | 'local';
|
|
11
|
+
declare const DECANTR_TELEMETRY_EVENT_NAMES: readonly ["api_key.created", "audit.completed", "cli.command.completed", "content.publish.completed", "content.validation.completed", "critique.completed", "execution_pack.compiled", "execution_pack.selected", "org.created", "registry.item.resolved", "registry.sync.completed", "user.signup.completed"];
|
|
12
|
+
type DecantrTelemetryEventName = (typeof DECANTR_TELEMETRY_EVENT_NAMES)[number];
|
|
13
|
+
type TelemetryPropertyValue = TelemetryPropertyValue[] | boolean | null | number | string | undefined | {
|
|
14
|
+
[key: string]: TelemetryPropertyValue;
|
|
15
|
+
};
|
|
16
|
+
type TelemetryProperties = Record<string, TelemetryPropertyValue>;
|
|
17
|
+
interface TelemetryContext {
|
|
18
|
+
source: TelemetrySource;
|
|
19
|
+
environment?: TelemetryEnvironment;
|
|
20
|
+
serviceName?: string;
|
|
21
|
+
serviceVersion?: string;
|
|
22
|
+
decantrVersion?: string;
|
|
23
|
+
registrySource?: RegistrySource;
|
|
24
|
+
anonymousId?: string;
|
|
25
|
+
installId?: string;
|
|
26
|
+
projectId?: string;
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
userId?: string;
|
|
29
|
+
orgId?: string;
|
|
30
|
+
}
|
|
31
|
+
interface TelemetryEventBase<Name extends DecantrTelemetryEventName = DecantrTelemetryEventName, Properties extends TelemetryProperties = TelemetryProperties> {
|
|
32
|
+
name: Name;
|
|
33
|
+
context: TelemetryContext;
|
|
34
|
+
properties: Properties;
|
|
35
|
+
timestamp?: Date | string;
|
|
36
|
+
}
|
|
37
|
+
interface CliCommandCompletedProperties extends TelemetryProperties {
|
|
38
|
+
command: string;
|
|
39
|
+
success: boolean;
|
|
40
|
+
durationMs: number;
|
|
41
|
+
adoptionMode?: AdoptionMode;
|
|
42
|
+
errorCode?: string;
|
|
43
|
+
offline?: boolean;
|
|
44
|
+
projectScope?: ProjectScope;
|
|
45
|
+
registrySource?: RegistrySource;
|
|
46
|
+
targetFramework?: string;
|
|
47
|
+
workflowMode?: WorkflowMode;
|
|
48
|
+
}
|
|
49
|
+
interface RegistryItemResolvedProperties extends TelemetryProperties {
|
|
50
|
+
contentType: TelemetryContentType;
|
|
51
|
+
success: boolean;
|
|
52
|
+
cacheHit?: boolean;
|
|
53
|
+
durationMs?: number;
|
|
54
|
+
errorCode?: string;
|
|
55
|
+
itemId?: string;
|
|
56
|
+
namespace?: string;
|
|
57
|
+
registrySource?: RegistrySource;
|
|
58
|
+
version?: string;
|
|
59
|
+
visibility?: TelemetryVisibility;
|
|
60
|
+
}
|
|
61
|
+
interface RegistrySyncCompletedProperties extends TelemetryProperties {
|
|
62
|
+
success: boolean;
|
|
63
|
+
durationMs: number;
|
|
64
|
+
errorCode?: string;
|
|
65
|
+
registrySource?: RegistrySource;
|
|
66
|
+
totalItems?: number;
|
|
67
|
+
}
|
|
68
|
+
interface ExecutionPackCompiledProperties extends TelemetryProperties {
|
|
69
|
+
success: boolean;
|
|
70
|
+
durationMs: number;
|
|
71
|
+
errorCode?: string;
|
|
72
|
+
pageCount?: number;
|
|
73
|
+
patternCount?: number;
|
|
74
|
+
sectionCount?: number;
|
|
75
|
+
targetFramework?: string;
|
|
76
|
+
}
|
|
77
|
+
interface ExecutionPackSelectedProperties extends TelemetryProperties {
|
|
78
|
+
packType: 'mutation' | 'page' | 'review' | 'scaffold' | 'section';
|
|
79
|
+
success: boolean;
|
|
80
|
+
durationMs?: number;
|
|
81
|
+
errorCode?: string;
|
|
82
|
+
id?: string;
|
|
83
|
+
}
|
|
84
|
+
interface AuditCompletedProperties extends TelemetryProperties {
|
|
85
|
+
scope: TelemetryAnalysisScope;
|
|
86
|
+
success: boolean;
|
|
87
|
+
durationMs: number;
|
|
88
|
+
errorCode?: string;
|
|
89
|
+
errorCount?: number;
|
|
90
|
+
pageCount?: number;
|
|
91
|
+
runtimePassed?: boolean;
|
|
92
|
+
score?: number;
|
|
93
|
+
warnCount?: number;
|
|
94
|
+
}
|
|
95
|
+
interface CritiqueCompletedProperties extends TelemetryProperties {
|
|
96
|
+
scope: TelemetryAnalysisScope;
|
|
97
|
+
success: boolean;
|
|
98
|
+
durationMs: number;
|
|
99
|
+
errorCode?: string;
|
|
100
|
+
errorCount?: number;
|
|
101
|
+
infoCount?: number;
|
|
102
|
+
overall?: number;
|
|
103
|
+
warnCount?: number;
|
|
104
|
+
}
|
|
105
|
+
interface ContentValidationCompletedProperties extends TelemetryProperties {
|
|
106
|
+
valid: boolean;
|
|
107
|
+
contentType?: TelemetryContentType;
|
|
108
|
+
durationMs?: number;
|
|
109
|
+
errorCount?: number;
|
|
110
|
+
itemCount?: number;
|
|
111
|
+
warningCount?: number;
|
|
112
|
+
}
|
|
113
|
+
interface ContentPublishCompletedProperties extends TelemetryProperties {
|
|
114
|
+
contentType: TelemetryContentType;
|
|
115
|
+
success: boolean;
|
|
116
|
+
durationMs: number;
|
|
117
|
+
errorCode?: string;
|
|
118
|
+
namespace?: string;
|
|
119
|
+
visibility?: TelemetryVisibility;
|
|
120
|
+
}
|
|
121
|
+
interface ProductEventProperties extends TelemetryProperties {
|
|
122
|
+
success?: boolean;
|
|
123
|
+
channel?: string;
|
|
124
|
+
entrypoint?: string;
|
|
125
|
+
plan?: string;
|
|
126
|
+
}
|
|
127
|
+
type DecantrTelemetryEvent = TelemetryEventBase<'api_key.created', ProductEventProperties> | TelemetryEventBase<'audit.completed', AuditCompletedProperties> | TelemetryEventBase<'cli.command.completed', CliCommandCompletedProperties> | TelemetryEventBase<'content.publish.completed', ContentPublishCompletedProperties> | TelemetryEventBase<'content.validation.completed', ContentValidationCompletedProperties> | TelemetryEventBase<'critique.completed', CritiqueCompletedProperties> | TelemetryEventBase<'execution_pack.compiled', ExecutionPackCompiledProperties> | TelemetryEventBase<'execution_pack.selected', ExecutionPackSelectedProperties> | TelemetryEventBase<'org.created', ProductEventProperties> | TelemetryEventBase<'registry.item.resolved', RegistryItemResolvedProperties> | TelemetryEventBase<'registry.sync.completed', RegistrySyncCompletedProperties> | TelemetryEventBase<'user.signup.completed', ProductEventProperties>;
|
|
128
|
+
declare function isDecantrTelemetryEventName(value: string): value is DecantrTelemetryEventName;
|
|
129
|
+
|
|
130
|
+
export { type AdoptionMode, type AuditCompletedProperties, type CliCommandCompletedProperties, type ContentPublishCompletedProperties, type ContentValidationCompletedProperties, type CritiqueCompletedProperties, DECANTR_TELEMETRY_EVENT_NAMES, DECANTR_TELEMETRY_SCHEMA_VERSION, type DecantrTelemetryEvent, type DecantrTelemetryEventName, type ExecutionPackCompiledProperties, type ExecutionPackSelectedProperties, type ProductEventProperties, type ProjectScope, type RegistryItemResolvedProperties, type RegistrySource, type RegistrySyncCompletedProperties, type TelemetryAnalysisScope, type TelemetryContentType, type TelemetryContext, type TelemetryEnvironment, type TelemetryEventBase, type TelemetryProperties, type TelemetryPropertyValue, type TelemetrySource, type TelemetryVisibility, type WorkflowMode, isDecantrTelemetryEventName };
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var DECANTR_TELEMETRY_SCHEMA_VERSION = "0.1.0";
|
|
3
|
+
var DECANTR_TELEMETRY_EVENT_NAMES = [
|
|
4
|
+
"api_key.created",
|
|
5
|
+
"audit.completed",
|
|
6
|
+
"cli.command.completed",
|
|
7
|
+
"content.publish.completed",
|
|
8
|
+
"content.validation.completed",
|
|
9
|
+
"critique.completed",
|
|
10
|
+
"execution_pack.compiled",
|
|
11
|
+
"execution_pack.selected",
|
|
12
|
+
"org.created",
|
|
13
|
+
"registry.item.resolved",
|
|
14
|
+
"registry.sync.completed",
|
|
15
|
+
"user.signup.completed"
|
|
16
|
+
];
|
|
17
|
+
function isDecantrTelemetryEventName(value) {
|
|
18
|
+
return DECANTR_TELEMETRY_EVENT_NAMES.includes(value);
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
DECANTR_TELEMETRY_EVENT_NAMES,
|
|
22
|
+
DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
23
|
+
isDecantrTelemetryEventName
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts"],"sourcesContent":["export const DECANTR_TELEMETRY_SCHEMA_VERSION = '0.1.0';\n\nexport type TelemetrySource = 'api' | 'cli' | 'content-ci' | 'mcp' | 'registry-web';\nexport type TelemetryEnvironment = 'development' | 'preview' | 'production' | 'test';\nexport type RegistrySource = 'cache' | 'custom' | 'none' | 'official' | 'private';\nexport type WorkflowMode =\n | 'brownfield-attach'\n | 'greenfield-contract-only'\n | 'greenfield-scaffold'\n | 'hybrid-compose';\nexport type AdoptionMode = 'contract-only' | 'decantr-css' | 'style-bridge';\nexport type ProjectScope = 'single-app' | 'workspace-app';\nexport type TelemetryContentType = 'archetype' | 'blueprint' | 'pattern' | 'shell' | 'theme';\nexport type TelemetryVisibility = 'private' | 'public' | 'team';\nexport type TelemetryAnalysisScope = 'hosted' | 'local';\n\nexport const DECANTR_TELEMETRY_EVENT_NAMES = [\n 'api_key.created',\n 'audit.completed',\n 'cli.command.completed',\n 'content.publish.completed',\n 'content.validation.completed',\n 'critique.completed',\n 'execution_pack.compiled',\n 'execution_pack.selected',\n 'org.created',\n 'registry.item.resolved',\n 'registry.sync.completed',\n 'user.signup.completed',\n] as const;\n\nexport type DecantrTelemetryEventName = (typeof DECANTR_TELEMETRY_EVENT_NAMES)[number];\n\nexport type TelemetryPropertyValue =\n | TelemetryPropertyValue[]\n | boolean\n | null\n | number\n | string\n | undefined\n | { [key: string]: TelemetryPropertyValue };\n\nexport type TelemetryProperties = Record<string, TelemetryPropertyValue>;\n\nexport interface TelemetryContext {\n source: TelemetrySource;\n environment?: TelemetryEnvironment;\n serviceName?: string;\n serviceVersion?: string;\n decantrVersion?: string;\n registrySource?: RegistrySource;\n anonymousId?: string;\n installId?: string;\n projectId?: string;\n sessionId?: string;\n userId?: string;\n orgId?: string;\n}\n\nexport interface TelemetryEventBase<\n Name extends DecantrTelemetryEventName = DecantrTelemetryEventName,\n Properties extends TelemetryProperties = TelemetryProperties,\n> {\n name: Name;\n context: TelemetryContext;\n properties: Properties;\n timestamp?: Date | string;\n}\n\nexport interface CliCommandCompletedProperties extends TelemetryProperties {\n command: string;\n success: boolean;\n durationMs: number;\n adoptionMode?: AdoptionMode;\n errorCode?: string;\n offline?: boolean;\n projectScope?: ProjectScope;\n registrySource?: RegistrySource;\n targetFramework?: string;\n workflowMode?: WorkflowMode;\n}\n\nexport interface RegistryItemResolvedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n cacheHit?: boolean;\n durationMs?: number;\n errorCode?: string;\n itemId?: string;\n namespace?: string;\n registrySource?: RegistrySource;\n version?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface RegistrySyncCompletedProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n registrySource?: RegistrySource;\n totalItems?: number;\n}\n\nexport interface ExecutionPackCompiledProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n pageCount?: number;\n patternCount?: number;\n sectionCount?: number;\n targetFramework?: string;\n}\n\nexport interface ExecutionPackSelectedProperties extends TelemetryProperties {\n packType: 'mutation' | 'page' | 'review' | 'scaffold' | 'section';\n success: boolean;\n durationMs?: number;\n errorCode?: string;\n id?: string;\n}\n\nexport interface AuditCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n pageCount?: number;\n runtimePassed?: boolean;\n score?: number;\n warnCount?: number;\n}\n\nexport interface CritiqueCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n infoCount?: number;\n overall?: number;\n warnCount?: number;\n}\n\nexport interface ContentValidationCompletedProperties extends TelemetryProperties {\n valid: boolean;\n contentType?: TelemetryContentType;\n durationMs?: number;\n errorCount?: number;\n itemCount?: number;\n warningCount?: number;\n}\n\nexport interface ContentPublishCompletedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n namespace?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface ProductEventProperties extends TelemetryProperties {\n success?: boolean;\n channel?: string;\n entrypoint?: string;\n plan?: string;\n}\n\nexport type DecantrTelemetryEvent =\n | TelemetryEventBase<'api_key.created', ProductEventProperties>\n | TelemetryEventBase<'audit.completed', AuditCompletedProperties>\n | TelemetryEventBase<'cli.command.completed', CliCommandCompletedProperties>\n | TelemetryEventBase<'content.publish.completed', ContentPublishCompletedProperties>\n | TelemetryEventBase<'content.validation.completed', ContentValidationCompletedProperties>\n | TelemetryEventBase<'critique.completed', CritiqueCompletedProperties>\n | TelemetryEventBase<'execution_pack.compiled', ExecutionPackCompiledProperties>\n | TelemetryEventBase<'execution_pack.selected', ExecutionPackSelectedProperties>\n | TelemetryEventBase<'org.created', ProductEventProperties>\n | TelemetryEventBase<'registry.item.resolved', RegistryItemResolvedProperties>\n | TelemetryEventBase<'registry.sync.completed', RegistrySyncCompletedProperties>\n | TelemetryEventBase<'user.signup.completed', ProductEventProperties>;\n\nexport function isDecantrTelemetryEventName(value: string): value is DecantrTelemetryEventName {\n return (DECANTR_TELEMETRY_EVENT_NAMES as readonly string[]).includes(value);\n}\n"],"mappings":";AAAO,IAAM,mCAAmC;AAgBzC,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA0JO,SAAS,4BAA4B,OAAmD;AAC7F,SAAQ,8BAAoD,SAAS,KAAK;AAC5E;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { FetchTelemetrySinkOptions, TelemetryClient, TelemetryClientOptions, TelemetrySink, createFetchTelemetrySink, createNoopTelemetrySink, createTelemetryClient } from './client.js';
|
|
2
|
+
export { AdoptionMode, AuditCompletedProperties, CliCommandCompletedProperties, ContentPublishCompletedProperties, ContentValidationCompletedProperties, CritiqueCompletedProperties, DECANTR_TELEMETRY_EVENT_NAMES, DECANTR_TELEMETRY_SCHEMA_VERSION, DecantrTelemetryEvent, DecantrTelemetryEventName, ExecutionPackCompiledProperties, ExecutionPackSelectedProperties, ProductEventProperties, ProjectScope, RegistryItemResolvedProperties, RegistrySource, RegistrySyncCompletedProperties, TelemetryAnalysisScope, TelemetryContentType, TelemetryContext, TelemetryEnvironment, TelemetryProperties, TelemetryPropertyValue, TelemetrySource, TelemetryVisibility, WorkflowMode, isDecantrTelemetryEventName } from './events.js';
|
|
3
|
+
export { PostHogTelemetrySinkOptions, createPostHogTelemetrySink } from './posthog.js';
|
|
4
|
+
export { REDACTED_VALUE, TelemetryRedactionOptions, isSensitiveTelemetryKey, sanitizeTelemetryEvent, sanitizeTelemetryValue } from './privacy.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var DECANTR_TELEMETRY_SCHEMA_VERSION = "0.1.0";
|
|
3
|
+
var DECANTR_TELEMETRY_EVENT_NAMES = [
|
|
4
|
+
"api_key.created",
|
|
5
|
+
"audit.completed",
|
|
6
|
+
"cli.command.completed",
|
|
7
|
+
"content.publish.completed",
|
|
8
|
+
"content.validation.completed",
|
|
9
|
+
"critique.completed",
|
|
10
|
+
"execution_pack.compiled",
|
|
11
|
+
"execution_pack.selected",
|
|
12
|
+
"org.created",
|
|
13
|
+
"registry.item.resolved",
|
|
14
|
+
"registry.sync.completed",
|
|
15
|
+
"user.signup.completed"
|
|
16
|
+
];
|
|
17
|
+
function isDecantrTelemetryEventName(value) {
|
|
18
|
+
return DECANTR_TELEMETRY_EVENT_NAMES.includes(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/privacy.ts
|
|
22
|
+
var REDACTED_VALUE = "[redacted]";
|
|
23
|
+
var DEFAULT_MAX_STRING_LENGTH = 240;
|
|
24
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 25;
|
|
25
|
+
var DEFAULT_MAX_OBJECT_KEYS = 80;
|
|
26
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
27
|
+
var SENSITIVE_KEY_PATTERNS = [
|
|
28
|
+
/^api[_-]?key$/i,
|
|
29
|
+
/^authorization$/i,
|
|
30
|
+
/^code$/i,
|
|
31
|
+
/^content$/i,
|
|
32
|
+
/^contents$/i,
|
|
33
|
+
/^cookie$/i,
|
|
34
|
+
/^cwd$/i,
|
|
35
|
+
/^email$/i,
|
|
36
|
+
/^env$/i,
|
|
37
|
+
/^file[_-]?contents$/i,
|
|
38
|
+
/^file[_-]?path$/i,
|
|
39
|
+
/^home$/i,
|
|
40
|
+
/^ip[_-]?address$/i,
|
|
41
|
+
/^password$/i,
|
|
42
|
+
/^path$/i,
|
|
43
|
+
/^prompt$/i,
|
|
44
|
+
/^raw[_-]?prompt$/i,
|
|
45
|
+
/^secret$/i,
|
|
46
|
+
/^source$/i,
|
|
47
|
+
/^source[_-]?code$/i,
|
|
48
|
+
/^token$/i,
|
|
49
|
+
/^url$/i,
|
|
50
|
+
/^user[_-]?agent$/i
|
|
51
|
+
];
|
|
52
|
+
function isSensitiveTelemetryKey(key, patterns = SENSITIVE_KEY_PATTERNS) {
|
|
53
|
+
return patterns.some((pattern) => pattern.test(key));
|
|
54
|
+
}
|
|
55
|
+
function sanitizeTelemetryValue(value, options = {}, depth = 0) {
|
|
56
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
57
|
+
if (depth > maxDepth) {
|
|
58
|
+
return options.redactedValue ?? REDACTED_VALUE;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
|
|
62
|
+
return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
if (value === void 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
72
|
+
return value.slice(0, maxArrayLength).map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));
|
|
73
|
+
}
|
|
74
|
+
const redactedValue = options.redactedValue ?? REDACTED_VALUE;
|
|
75
|
+
const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;
|
|
76
|
+
const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;
|
|
77
|
+
const entries = Object.entries(value).slice(0, maxObjectKeys);
|
|
78
|
+
const sanitized = {};
|
|
79
|
+
for (const [key, entryValue] of entries) {
|
|
80
|
+
sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns) ? redactedValue : sanitizeTelemetryValue(entryValue, options, depth + 1);
|
|
81
|
+
}
|
|
82
|
+
return sanitized;
|
|
83
|
+
}
|
|
84
|
+
function sanitizeTelemetryEvent(event, options = {}) {
|
|
85
|
+
return {
|
|
86
|
+
...event,
|
|
87
|
+
properties: sanitizeTelemetryValue(
|
|
88
|
+
event.properties,
|
|
89
|
+
options
|
|
90
|
+
)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/client.ts
|
|
95
|
+
function createNoopTelemetrySink() {
|
|
96
|
+
return {
|
|
97
|
+
capture() {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function createTelemetryClient(options = {}) {
|
|
103
|
+
const enabled = options.enabled ?? true;
|
|
104
|
+
const sink = options.sink ?? createNoopTelemetrySink();
|
|
105
|
+
const pending = /* @__PURE__ */ new Set();
|
|
106
|
+
async function capture(event) {
|
|
107
|
+
if (!enabled) return;
|
|
108
|
+
const enriched = sanitizeTelemetryEvent(
|
|
109
|
+
{
|
|
110
|
+
...event,
|
|
111
|
+
context: {
|
|
112
|
+
...options.context,
|
|
113
|
+
...event.context
|
|
114
|
+
},
|
|
115
|
+
timestamp: normalizeTimestamp(event.timestamp)
|
|
116
|
+
},
|
|
117
|
+
options.redaction
|
|
118
|
+
);
|
|
119
|
+
const promise = Promise.resolve().then(() => sink.capture(enriched)).catch((error) => {
|
|
120
|
+
options.onError?.(error, enriched);
|
|
121
|
+
}).then(() => void 0);
|
|
122
|
+
pending.add(promise);
|
|
123
|
+
promise.finally(() => pending.delete(promise));
|
|
124
|
+
await promise;
|
|
125
|
+
}
|
|
126
|
+
async function flush() {
|
|
127
|
+
await Promise.allSettled([...pending]);
|
|
128
|
+
await sink.flush?.();
|
|
129
|
+
}
|
|
130
|
+
return { capture, flush };
|
|
131
|
+
}
|
|
132
|
+
function createFetchTelemetrySink(options) {
|
|
133
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
134
|
+
return {
|
|
135
|
+
async capture(event) {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetchImpl(options.endpoint, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": "application/json",
|
|
143
|
+
...normalizeHeaders(
|
|
144
|
+
typeof options.headers === "function" ? options.headers() : options.headers
|
|
145
|
+
)
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
schemaVersion: DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
149
|
+
event
|
|
150
|
+
}),
|
|
151
|
+
signal: controller.signal
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new Error(`Telemetry endpoint returned HTTP ${response.status}.`);
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function normalizeTimestamp(timestamp) {
|
|
163
|
+
if (timestamp instanceof Date) {
|
|
164
|
+
return timestamp.toISOString();
|
|
165
|
+
}
|
|
166
|
+
return timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
167
|
+
}
|
|
168
|
+
function normalizeHeaders(headers) {
|
|
169
|
+
if (!headers) return {};
|
|
170
|
+
if (headers instanceof Headers) {
|
|
171
|
+
return Object.fromEntries(headers.entries());
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(headers)) {
|
|
174
|
+
return Object.fromEntries(headers);
|
|
175
|
+
}
|
|
176
|
+
return headers;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/posthog.ts
|
|
180
|
+
function createPostHogTelemetrySink(options) {
|
|
181
|
+
const host = (options.host ?? "https://us.i.posthog.com").replace(/\/+$/, "");
|
|
182
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
183
|
+
return {
|
|
184
|
+
async capture(event) {
|
|
185
|
+
const distinctId = resolveDistinctId(event);
|
|
186
|
+
if (!distinctId) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
"PostHog telemetry requires an opaque user, install, project, or anonymous id."
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetchImpl(`${host}/i/v0/e/`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: { "Content-Type": "application/json" },
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
api_key: options.apiKey,
|
|
199
|
+
event: event.name,
|
|
200
|
+
distinct_id: distinctId,
|
|
201
|
+
properties: toPostHogProperties(event, options),
|
|
202
|
+
timestamp: event.timestamp
|
|
203
|
+
}),
|
|
204
|
+
signal: controller.signal
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(`PostHog capture returned HTTP ${response.status}.`);
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function resolveDistinctId(event) {
|
|
216
|
+
return event.context.userId ?? event.context.installId ?? event.context.projectId ?? event.context.anonymousId;
|
|
217
|
+
}
|
|
218
|
+
function toPostHogProperties(event, options) {
|
|
219
|
+
const groups = {};
|
|
220
|
+
if (event.context.orgId) groups.organization = event.context.orgId;
|
|
221
|
+
if (event.context.projectId) groups.project = event.context.projectId;
|
|
222
|
+
return {
|
|
223
|
+
...event.properties,
|
|
224
|
+
$groups: groups,
|
|
225
|
+
$process_person_profile: options.processPersonProfile ?? false,
|
|
226
|
+
decantr_schema_version: DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
227
|
+
decantr_source: event.context.source,
|
|
228
|
+
decantr_environment: event.context.environment ?? "production",
|
|
229
|
+
decantr_version: event.context.decantrVersion ?? null,
|
|
230
|
+
registry_source: event.context.registrySource ?? null,
|
|
231
|
+
service_name: event.context.serviceName ?? null,
|
|
232
|
+
service_version: event.context.serviceVersion ?? null
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export {
|
|
236
|
+
DECANTR_TELEMETRY_EVENT_NAMES,
|
|
237
|
+
DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
238
|
+
REDACTED_VALUE,
|
|
239
|
+
createFetchTelemetrySink,
|
|
240
|
+
createNoopTelemetrySink,
|
|
241
|
+
createPostHogTelemetrySink,
|
|
242
|
+
createTelemetryClient,
|
|
243
|
+
isDecantrTelemetryEventName,
|
|
244
|
+
isSensitiveTelemetryKey,
|
|
245
|
+
sanitizeTelemetryEvent,
|
|
246
|
+
sanitizeTelemetryValue
|
|
247
|
+
};
|
|
248
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts","../src/privacy.ts","../src/client.ts","../src/posthog.ts"],"sourcesContent":["export const DECANTR_TELEMETRY_SCHEMA_VERSION = '0.1.0';\n\nexport type TelemetrySource = 'api' | 'cli' | 'content-ci' | 'mcp' | 'registry-web';\nexport type TelemetryEnvironment = 'development' | 'preview' | 'production' | 'test';\nexport type RegistrySource = 'cache' | 'custom' | 'none' | 'official' | 'private';\nexport type WorkflowMode =\n | 'brownfield-attach'\n | 'greenfield-contract-only'\n | 'greenfield-scaffold'\n | 'hybrid-compose';\nexport type AdoptionMode = 'contract-only' | 'decantr-css' | 'style-bridge';\nexport type ProjectScope = 'single-app' | 'workspace-app';\nexport type TelemetryContentType = 'archetype' | 'blueprint' | 'pattern' | 'shell' | 'theme';\nexport type TelemetryVisibility = 'private' | 'public' | 'team';\nexport type TelemetryAnalysisScope = 'hosted' | 'local';\n\nexport const DECANTR_TELEMETRY_EVENT_NAMES = [\n 'api_key.created',\n 'audit.completed',\n 'cli.command.completed',\n 'content.publish.completed',\n 'content.validation.completed',\n 'critique.completed',\n 'execution_pack.compiled',\n 'execution_pack.selected',\n 'org.created',\n 'registry.item.resolved',\n 'registry.sync.completed',\n 'user.signup.completed',\n] as const;\n\nexport type DecantrTelemetryEventName = (typeof DECANTR_TELEMETRY_EVENT_NAMES)[number];\n\nexport type TelemetryPropertyValue =\n | TelemetryPropertyValue[]\n | boolean\n | null\n | number\n | string\n | undefined\n | { [key: string]: TelemetryPropertyValue };\n\nexport type TelemetryProperties = Record<string, TelemetryPropertyValue>;\n\nexport interface TelemetryContext {\n source: TelemetrySource;\n environment?: TelemetryEnvironment;\n serviceName?: string;\n serviceVersion?: string;\n decantrVersion?: string;\n registrySource?: RegistrySource;\n anonymousId?: string;\n installId?: string;\n projectId?: string;\n sessionId?: string;\n userId?: string;\n orgId?: string;\n}\n\nexport interface TelemetryEventBase<\n Name extends DecantrTelemetryEventName = DecantrTelemetryEventName,\n Properties extends TelemetryProperties = TelemetryProperties,\n> {\n name: Name;\n context: TelemetryContext;\n properties: Properties;\n timestamp?: Date | string;\n}\n\nexport interface CliCommandCompletedProperties extends TelemetryProperties {\n command: string;\n success: boolean;\n durationMs: number;\n adoptionMode?: AdoptionMode;\n errorCode?: string;\n offline?: boolean;\n projectScope?: ProjectScope;\n registrySource?: RegistrySource;\n targetFramework?: string;\n workflowMode?: WorkflowMode;\n}\n\nexport interface RegistryItemResolvedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n cacheHit?: boolean;\n durationMs?: number;\n errorCode?: string;\n itemId?: string;\n namespace?: string;\n registrySource?: RegistrySource;\n version?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface RegistrySyncCompletedProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n registrySource?: RegistrySource;\n totalItems?: number;\n}\n\nexport interface ExecutionPackCompiledProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n pageCount?: number;\n patternCount?: number;\n sectionCount?: number;\n targetFramework?: string;\n}\n\nexport interface ExecutionPackSelectedProperties extends TelemetryProperties {\n packType: 'mutation' | 'page' | 'review' | 'scaffold' | 'section';\n success: boolean;\n durationMs?: number;\n errorCode?: string;\n id?: string;\n}\n\nexport interface AuditCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n pageCount?: number;\n runtimePassed?: boolean;\n score?: number;\n warnCount?: number;\n}\n\nexport interface CritiqueCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n infoCount?: number;\n overall?: number;\n warnCount?: number;\n}\n\nexport interface ContentValidationCompletedProperties extends TelemetryProperties {\n valid: boolean;\n contentType?: TelemetryContentType;\n durationMs?: number;\n errorCount?: number;\n itemCount?: number;\n warningCount?: number;\n}\n\nexport interface ContentPublishCompletedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n namespace?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface ProductEventProperties extends TelemetryProperties {\n success?: boolean;\n channel?: string;\n entrypoint?: string;\n plan?: string;\n}\n\nexport type DecantrTelemetryEvent =\n | TelemetryEventBase<'api_key.created', ProductEventProperties>\n | TelemetryEventBase<'audit.completed', AuditCompletedProperties>\n | TelemetryEventBase<'cli.command.completed', CliCommandCompletedProperties>\n | TelemetryEventBase<'content.publish.completed', ContentPublishCompletedProperties>\n | TelemetryEventBase<'content.validation.completed', ContentValidationCompletedProperties>\n | TelemetryEventBase<'critique.completed', CritiqueCompletedProperties>\n | TelemetryEventBase<'execution_pack.compiled', ExecutionPackCompiledProperties>\n | TelemetryEventBase<'execution_pack.selected', ExecutionPackSelectedProperties>\n | TelemetryEventBase<'org.created', ProductEventProperties>\n | TelemetryEventBase<'registry.item.resolved', RegistryItemResolvedProperties>\n | TelemetryEventBase<'registry.sync.completed', RegistrySyncCompletedProperties>\n | TelemetryEventBase<'user.signup.completed', ProductEventProperties>;\n\nexport function isDecantrTelemetryEventName(value: string): value is DecantrTelemetryEventName {\n return (DECANTR_TELEMETRY_EVENT_NAMES as readonly string[]).includes(value);\n}\n","import type { DecantrTelemetryEvent, TelemetryPropertyValue } from './events.js';\n\nexport const REDACTED_VALUE = '[redacted]';\n\nconst DEFAULT_MAX_STRING_LENGTH = 240;\nconst DEFAULT_MAX_ARRAY_LENGTH = 25;\nconst DEFAULT_MAX_OBJECT_KEYS = 80;\nconst DEFAULT_MAX_DEPTH = 4;\n\nconst SENSITIVE_KEY_PATTERNS = [\n /^api[_-]?key$/i,\n /^authorization$/i,\n /^code$/i,\n /^content$/i,\n /^contents$/i,\n /^cookie$/i,\n /^cwd$/i,\n /^email$/i,\n /^env$/i,\n /^file[_-]?contents$/i,\n /^file[_-]?path$/i,\n /^home$/i,\n /^ip[_-]?address$/i,\n /^password$/i,\n /^path$/i,\n /^prompt$/i,\n /^raw[_-]?prompt$/i,\n /^secret$/i,\n /^source$/i,\n /^source[_-]?code$/i,\n /^token$/i,\n /^url$/i,\n /^user[_-]?agent$/i,\n];\n\nexport interface TelemetryRedactionOptions {\n maxArrayLength?: number;\n maxDepth?: number;\n maxObjectKeys?: number;\n maxStringLength?: number;\n redactedValue?: string;\n sensitiveKeyPatterns?: RegExp[];\n}\n\nexport function isSensitiveTelemetryKey(key: string, patterns = SENSITIVE_KEY_PATTERNS): boolean {\n return patterns.some((pattern) => pattern.test(key));\n}\n\nexport function sanitizeTelemetryValue(\n value: TelemetryPropertyValue,\n options: TelemetryRedactionOptions = {},\n depth = 0,\n): TelemetryPropertyValue {\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n if (depth > maxDepth) {\n return options.redactedValue ?? REDACTED_VALUE;\n }\n\n if (typeof value === 'string') {\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;\n return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n return value;\n }\n\n if (value === undefined) {\n return null;\n }\n\n if (Array.isArray(value)) {\n const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;\n return value\n .slice(0, maxArrayLength)\n .map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));\n }\n\n const redactedValue = options.redactedValue ?? REDACTED_VALUE;\n const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;\n const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;\n const entries = Object.entries(value).slice(0, maxObjectKeys);\n const sanitized: Record<string, TelemetryPropertyValue> = {};\n\n for (const [key, entryValue] of entries) {\n sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns)\n ? redactedValue\n : sanitizeTelemetryValue(entryValue, options, depth + 1);\n }\n\n return sanitized;\n}\n\nexport function sanitizeTelemetryEvent(\n event: DecantrTelemetryEvent,\n options: TelemetryRedactionOptions = {},\n): DecantrTelemetryEvent {\n return {\n ...event,\n properties: sanitizeTelemetryValue(\n event.properties,\n options,\n ) as DecantrTelemetryEvent['properties'],\n } as DecantrTelemetryEvent;\n}\n","import {\n DECANTR_TELEMETRY_SCHEMA_VERSION,\n type DecantrTelemetryEvent,\n type TelemetryContext,\n} from './events.js';\nimport { sanitizeTelemetryEvent, type TelemetryRedactionOptions } from './privacy.js';\n\nexport interface TelemetrySink {\n capture(event: DecantrTelemetryEvent): Promise<void> | void;\n flush?(): Promise<void> | void;\n}\n\nexport interface TelemetryClient {\n capture(event: DecantrTelemetryEvent): Promise<void>;\n flush(): Promise<void>;\n}\n\nexport interface TelemetryClientOptions {\n context?: Partial<TelemetryContext>;\n enabled?: boolean;\n onError?: (error: unknown, event: DecantrTelemetryEvent) => void;\n redaction?: TelemetryRedactionOptions;\n sink?: TelemetrySink;\n}\n\nexport interface FetchTelemetrySinkOptions {\n endpoint: string;\n fetch?: typeof fetch;\n headers?: HeadersInit | (() => HeadersInit);\n timeoutMs?: number;\n}\n\nexport function createNoopTelemetrySink(): TelemetrySink {\n return {\n capture() {\n return undefined;\n },\n };\n}\n\nexport function createTelemetryClient(options: TelemetryClientOptions = {}): TelemetryClient {\n const enabled = options.enabled ?? true;\n const sink = options.sink ?? createNoopTelemetrySink();\n const pending = new Set<Promise<void>>();\n\n async function capture(event: DecantrTelemetryEvent): Promise<void> {\n if (!enabled) return;\n\n const enriched = sanitizeTelemetryEvent(\n {\n ...event,\n context: {\n ...options.context,\n ...event.context,\n },\n timestamp: normalizeTimestamp(event.timestamp),\n } as DecantrTelemetryEvent,\n options.redaction,\n );\n\n const promise = Promise.resolve()\n .then(() => sink.capture(enriched))\n .catch((error) => {\n options.onError?.(error, enriched);\n })\n .then(() => undefined);\n\n pending.add(promise);\n promise.finally(() => pending.delete(promise));\n await promise;\n }\n\n async function flush(): Promise<void> {\n await Promise.allSettled([...pending]);\n await sink.flush?.();\n }\n\n return { capture, flush };\n}\n\nexport function createFetchTelemetrySink(options: FetchTelemetrySinkOptions): TelemetrySink {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n return {\n async capture(event) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n\n try {\n const response = await fetchImpl(options.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...normalizeHeaders(\n typeof options.headers === 'function' ? options.headers() : options.headers,\n ),\n },\n body: JSON.stringify({\n schemaVersion: DECANTR_TELEMETRY_SCHEMA_VERSION,\n event,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Telemetry endpoint returned HTTP ${response.status}.`);\n }\n } finally {\n clearTimeout(timeout);\n }\n },\n };\n}\n\nfunction normalizeTimestamp(timestamp: Date | string | undefined): string {\n if (timestamp instanceof Date) {\n return timestamp.toISOString();\n }\n return timestamp ?? new Date().toISOString();\n}\n\nfunction normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {};\n\n if (headers instanceof Headers) {\n return Object.fromEntries(headers.entries());\n }\n\n if (Array.isArray(headers)) {\n return Object.fromEntries(headers);\n }\n\n return headers as Record<string, string>;\n}\n","import type { TelemetrySink } from './client.js';\nimport {\n DECANTR_TELEMETRY_SCHEMA_VERSION,\n type DecantrTelemetryEvent,\n type TelemetryProperties,\n} from './events.js';\n\nexport interface PostHogTelemetrySinkOptions {\n apiKey: string;\n fetch?: typeof fetch;\n host?: string;\n processPersonProfile?: boolean;\n timeoutMs?: number;\n}\n\nexport function createPostHogTelemetrySink(options: PostHogTelemetrySinkOptions): TelemetrySink {\n const host = (options.host ?? 'https://us.i.posthog.com').replace(/\\/+$/, '');\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n return {\n async capture(event) {\n const distinctId = resolveDistinctId(event);\n if (!distinctId) {\n throw new Error(\n 'PostHog telemetry requires an opaque user, install, project, or anonymous id.',\n );\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n\n try {\n const response = await fetchImpl(`${host}/i/v0/e/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: options.apiKey,\n event: event.name,\n distinct_id: distinctId,\n properties: toPostHogProperties(event, options),\n timestamp: event.timestamp,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`PostHog capture returned HTTP ${response.status}.`);\n }\n } finally {\n clearTimeout(timeout);\n }\n },\n };\n}\n\nfunction resolveDistinctId(event: DecantrTelemetryEvent): string | undefined {\n return (\n event.context.userId ??\n event.context.installId ??\n event.context.projectId ??\n event.context.anonymousId\n );\n}\n\nfunction toPostHogProperties(\n event: DecantrTelemetryEvent,\n options: PostHogTelemetrySinkOptions,\n): TelemetryProperties {\n const groups: Record<string, string> = {};\n if (event.context.orgId) groups.organization = event.context.orgId;\n if (event.context.projectId) groups.project = event.context.projectId;\n\n return {\n ...event.properties,\n $groups: groups,\n $process_person_profile: options.processPersonProfile ?? false,\n decantr_schema_version: DECANTR_TELEMETRY_SCHEMA_VERSION,\n decantr_source: event.context.source,\n decantr_environment: event.context.environment ?? 'production',\n decantr_version: event.context.decantrVersion ?? null,\n registry_source: event.context.registrySource ?? null,\n service_name: event.context.serviceName ?? null,\n service_version: event.context.serviceVersion ?? null,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TelemetrySink } from './client.js';
|
|
2
|
+
import './events.js';
|
|
3
|
+
import './privacy.js';
|
|
4
|
+
|
|
5
|
+
interface PostHogTelemetrySinkOptions {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
host?: string;
|
|
9
|
+
processPersonProfile?: boolean;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
declare function createPostHogTelemetrySink(options: PostHogTelemetrySinkOptions): TelemetrySink;
|
|
13
|
+
|
|
14
|
+
export { type PostHogTelemetrySinkOptions, createPostHogTelemetrySink };
|
package/dist/posthog.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var DECANTR_TELEMETRY_SCHEMA_VERSION = "0.1.0";
|
|
3
|
+
|
|
4
|
+
// src/posthog.ts
|
|
5
|
+
function createPostHogTelemetrySink(options) {
|
|
6
|
+
const host = (options.host ?? "https://us.i.posthog.com").replace(/\/+$/, "");
|
|
7
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
8
|
+
return {
|
|
9
|
+
async capture(event) {
|
|
10
|
+
const distinctId = resolveDistinctId(event);
|
|
11
|
+
if (!distinctId) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"PostHog telemetry requires an opaque user, install, project, or anonymous id."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetchImpl(`${host}/i/v0/e/`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
api_key: options.apiKey,
|
|
24
|
+
event: event.name,
|
|
25
|
+
distinct_id: distinctId,
|
|
26
|
+
properties: toPostHogProperties(event, options),
|
|
27
|
+
timestamp: event.timestamp
|
|
28
|
+
}),
|
|
29
|
+
signal: controller.signal
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`PostHog capture returned HTTP ${response.status}.`);
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
clearTimeout(timeout);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resolveDistinctId(event) {
|
|
41
|
+
return event.context.userId ?? event.context.installId ?? event.context.projectId ?? event.context.anonymousId;
|
|
42
|
+
}
|
|
43
|
+
function toPostHogProperties(event, options) {
|
|
44
|
+
const groups = {};
|
|
45
|
+
if (event.context.orgId) groups.organization = event.context.orgId;
|
|
46
|
+
if (event.context.projectId) groups.project = event.context.projectId;
|
|
47
|
+
return {
|
|
48
|
+
...event.properties,
|
|
49
|
+
$groups: groups,
|
|
50
|
+
$process_person_profile: options.processPersonProfile ?? false,
|
|
51
|
+
decantr_schema_version: DECANTR_TELEMETRY_SCHEMA_VERSION,
|
|
52
|
+
decantr_source: event.context.source,
|
|
53
|
+
decantr_environment: event.context.environment ?? "production",
|
|
54
|
+
decantr_version: event.context.decantrVersion ?? null,
|
|
55
|
+
registry_source: event.context.registrySource ?? null,
|
|
56
|
+
service_name: event.context.serviceName ?? null,
|
|
57
|
+
service_version: event.context.serviceVersion ?? null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
createPostHogTelemetrySink
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=posthog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts","../src/posthog.ts"],"sourcesContent":["export const DECANTR_TELEMETRY_SCHEMA_VERSION = '0.1.0';\n\nexport type TelemetrySource = 'api' | 'cli' | 'content-ci' | 'mcp' | 'registry-web';\nexport type TelemetryEnvironment = 'development' | 'preview' | 'production' | 'test';\nexport type RegistrySource = 'cache' | 'custom' | 'none' | 'official' | 'private';\nexport type WorkflowMode =\n | 'brownfield-attach'\n | 'greenfield-contract-only'\n | 'greenfield-scaffold'\n | 'hybrid-compose';\nexport type AdoptionMode = 'contract-only' | 'decantr-css' | 'style-bridge';\nexport type ProjectScope = 'single-app' | 'workspace-app';\nexport type TelemetryContentType = 'archetype' | 'blueprint' | 'pattern' | 'shell' | 'theme';\nexport type TelemetryVisibility = 'private' | 'public' | 'team';\nexport type TelemetryAnalysisScope = 'hosted' | 'local';\n\nexport const DECANTR_TELEMETRY_EVENT_NAMES = [\n 'api_key.created',\n 'audit.completed',\n 'cli.command.completed',\n 'content.publish.completed',\n 'content.validation.completed',\n 'critique.completed',\n 'execution_pack.compiled',\n 'execution_pack.selected',\n 'org.created',\n 'registry.item.resolved',\n 'registry.sync.completed',\n 'user.signup.completed',\n] as const;\n\nexport type DecantrTelemetryEventName = (typeof DECANTR_TELEMETRY_EVENT_NAMES)[number];\n\nexport type TelemetryPropertyValue =\n | TelemetryPropertyValue[]\n | boolean\n | null\n | number\n | string\n | undefined\n | { [key: string]: TelemetryPropertyValue };\n\nexport type TelemetryProperties = Record<string, TelemetryPropertyValue>;\n\nexport interface TelemetryContext {\n source: TelemetrySource;\n environment?: TelemetryEnvironment;\n serviceName?: string;\n serviceVersion?: string;\n decantrVersion?: string;\n registrySource?: RegistrySource;\n anonymousId?: string;\n installId?: string;\n projectId?: string;\n sessionId?: string;\n userId?: string;\n orgId?: string;\n}\n\nexport interface TelemetryEventBase<\n Name extends DecantrTelemetryEventName = DecantrTelemetryEventName,\n Properties extends TelemetryProperties = TelemetryProperties,\n> {\n name: Name;\n context: TelemetryContext;\n properties: Properties;\n timestamp?: Date | string;\n}\n\nexport interface CliCommandCompletedProperties extends TelemetryProperties {\n command: string;\n success: boolean;\n durationMs: number;\n adoptionMode?: AdoptionMode;\n errorCode?: string;\n offline?: boolean;\n projectScope?: ProjectScope;\n registrySource?: RegistrySource;\n targetFramework?: string;\n workflowMode?: WorkflowMode;\n}\n\nexport interface RegistryItemResolvedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n cacheHit?: boolean;\n durationMs?: number;\n errorCode?: string;\n itemId?: string;\n namespace?: string;\n registrySource?: RegistrySource;\n version?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface RegistrySyncCompletedProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n registrySource?: RegistrySource;\n totalItems?: number;\n}\n\nexport interface ExecutionPackCompiledProperties extends TelemetryProperties {\n success: boolean;\n durationMs: number;\n errorCode?: string;\n pageCount?: number;\n patternCount?: number;\n sectionCount?: number;\n targetFramework?: string;\n}\n\nexport interface ExecutionPackSelectedProperties extends TelemetryProperties {\n packType: 'mutation' | 'page' | 'review' | 'scaffold' | 'section';\n success: boolean;\n durationMs?: number;\n errorCode?: string;\n id?: string;\n}\n\nexport interface AuditCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n pageCount?: number;\n runtimePassed?: boolean;\n score?: number;\n warnCount?: number;\n}\n\nexport interface CritiqueCompletedProperties extends TelemetryProperties {\n scope: TelemetryAnalysisScope;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n errorCount?: number;\n infoCount?: number;\n overall?: number;\n warnCount?: number;\n}\n\nexport interface ContentValidationCompletedProperties extends TelemetryProperties {\n valid: boolean;\n contentType?: TelemetryContentType;\n durationMs?: number;\n errorCount?: number;\n itemCount?: number;\n warningCount?: number;\n}\n\nexport interface ContentPublishCompletedProperties extends TelemetryProperties {\n contentType: TelemetryContentType;\n success: boolean;\n durationMs: number;\n errorCode?: string;\n namespace?: string;\n visibility?: TelemetryVisibility;\n}\n\nexport interface ProductEventProperties extends TelemetryProperties {\n success?: boolean;\n channel?: string;\n entrypoint?: string;\n plan?: string;\n}\n\nexport type DecantrTelemetryEvent =\n | TelemetryEventBase<'api_key.created', ProductEventProperties>\n | TelemetryEventBase<'audit.completed', AuditCompletedProperties>\n | TelemetryEventBase<'cli.command.completed', CliCommandCompletedProperties>\n | TelemetryEventBase<'content.publish.completed', ContentPublishCompletedProperties>\n | TelemetryEventBase<'content.validation.completed', ContentValidationCompletedProperties>\n | TelemetryEventBase<'critique.completed', CritiqueCompletedProperties>\n | TelemetryEventBase<'execution_pack.compiled', ExecutionPackCompiledProperties>\n | TelemetryEventBase<'execution_pack.selected', ExecutionPackSelectedProperties>\n | TelemetryEventBase<'org.created', ProductEventProperties>\n | TelemetryEventBase<'registry.item.resolved', RegistryItemResolvedProperties>\n | TelemetryEventBase<'registry.sync.completed', RegistrySyncCompletedProperties>\n | TelemetryEventBase<'user.signup.completed', ProductEventProperties>;\n\nexport function isDecantrTelemetryEventName(value: string): value is DecantrTelemetryEventName {\n return (DECANTR_TELEMETRY_EVENT_NAMES as readonly string[]).includes(value);\n}\n","import type { TelemetrySink } from './client.js';\nimport {\n DECANTR_TELEMETRY_SCHEMA_VERSION,\n type DecantrTelemetryEvent,\n type TelemetryProperties,\n} from './events.js';\n\nexport interface PostHogTelemetrySinkOptions {\n apiKey: string;\n fetch?: typeof fetch;\n host?: string;\n processPersonProfile?: boolean;\n timeoutMs?: number;\n}\n\nexport function createPostHogTelemetrySink(options: PostHogTelemetrySinkOptions): TelemetrySink {\n const host = (options.host ?? 'https://us.i.posthog.com').replace(/\\/+$/, '');\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n return {\n async capture(event) {\n const distinctId = resolveDistinctId(event);\n if (!distinctId) {\n throw new Error(\n 'PostHog telemetry requires an opaque user, install, project, or anonymous id.',\n );\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n\n try {\n const response = await fetchImpl(`${host}/i/v0/e/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: options.apiKey,\n event: event.name,\n distinct_id: distinctId,\n properties: toPostHogProperties(event, options),\n timestamp: event.timestamp,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`PostHog capture returned HTTP ${response.status}.`);\n }\n } finally {\n clearTimeout(timeout);\n }\n },\n };\n}\n\nfunction resolveDistinctId(event: DecantrTelemetryEvent): string | undefined {\n return (\n event.context.userId ??\n event.context.installId ??\n event.context.projectId ??\n event.context.anonymousId\n );\n}\n\nfunction toPostHogProperties(\n event: DecantrTelemetryEvent,\n options: PostHogTelemetrySinkOptions,\n): TelemetryProperties {\n const groups: Record<string, string> = {};\n if (event.context.orgId) groups.organization = event.context.orgId;\n if (event.context.projectId) groups.project = event.context.projectId;\n\n return {\n ...event.properties,\n $groups: groups,\n $process_person_profile: options.processPersonProfile ?? false,\n decantr_schema_version: DECANTR_TELEMETRY_SCHEMA_VERSION,\n decantr_source: event.context.source,\n decantr_environment: event.context.environment ?? 'production',\n decantr_version: event.context.decantrVersion ?? null,\n registry_source: event.context.registrySource ?? null,\n service_name: event.context.serviceName ?? null,\n service_version: event.context.serviceVersion ?? null,\n };\n}\n"],"mappings":";AAAO,IAAM,mCAAmC;;;ACezC,SAAS,2BAA2B,SAAqD;AAC9F,QAAM,QAAQ,QAAQ,QAAQ,4BAA4B,QAAQ,QAAQ,EAAE;AAC5E,QAAM,YAAY,QAAQ,SAAS,WAAW;AAE9C,SAAO;AAAA,IACL,MAAM,QAAQ,OAAO;AACnB,YAAM,aAAa,kBAAkB,KAAK;AAC1C,UAAI,CAAC,YAAY;AACf,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,QAAQ,aAAa,GAAI;AAE9E,UAAI;AACF,cAAM,WAAW,MAAM,UAAU,GAAG,IAAI,YAAY;AAAA,UAClD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,SAAS,QAAQ;AAAA,YACjB,OAAO,MAAM;AAAA,YACb,aAAa;AAAA,YACb,YAAY,oBAAoB,OAAO,OAAO;AAAA,YAC9C,WAAW,MAAM;AAAA,UACnB,CAAC;AAAA,UACD,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI,MAAM,iCAAiC,SAAS,MAAM,GAAG;AAAA,QACrE;AAAA,MACF,UAAE;AACA,qBAAa,OAAO;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,OAAkD;AAC3E,SACE,MAAM,QAAQ,UACd,MAAM,QAAQ,aACd,MAAM,QAAQ,aACd,MAAM,QAAQ;AAElB;AAEA,SAAS,oBACP,OACA,SACqB;AACrB,QAAM,SAAiC,CAAC;AACxC,MAAI,MAAM,QAAQ,MAAO,QAAO,eAAe,MAAM,QAAQ;AAC7D,MAAI,MAAM,QAAQ,UAAW,QAAO,UAAU,MAAM,QAAQ;AAE5D,SAAO;AAAA,IACL,GAAG,MAAM;AAAA,IACT,SAAS;AAAA,IACT,yBAAyB,QAAQ,wBAAwB;AAAA,IACzD,wBAAwB;AAAA,IACxB,gBAAgB,MAAM,QAAQ;AAAA,IAC9B,qBAAqB,MAAM,QAAQ,eAAe;AAAA,IAClD,iBAAiB,MAAM,QAAQ,kBAAkB;AAAA,IACjD,iBAAiB,MAAM,QAAQ,kBAAkB;AAAA,IACjD,cAAc,MAAM,QAAQ,eAAe;AAAA,IAC3C,iBAAiB,MAAM,QAAQ,kBAAkB;AAAA,EACnD;AACF;","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DecantrTelemetryEvent, TelemetryPropertyValue } from './events.js';
|
|
2
|
+
|
|
3
|
+
declare const REDACTED_VALUE = "[redacted]";
|
|
4
|
+
interface TelemetryRedactionOptions {
|
|
5
|
+
maxArrayLength?: number;
|
|
6
|
+
maxDepth?: number;
|
|
7
|
+
maxObjectKeys?: number;
|
|
8
|
+
maxStringLength?: number;
|
|
9
|
+
redactedValue?: string;
|
|
10
|
+
sensitiveKeyPatterns?: RegExp[];
|
|
11
|
+
}
|
|
12
|
+
declare function isSensitiveTelemetryKey(key: string, patterns?: RegExp[]): boolean;
|
|
13
|
+
declare function sanitizeTelemetryValue(value: TelemetryPropertyValue, options?: TelemetryRedactionOptions, depth?: number): TelemetryPropertyValue;
|
|
14
|
+
declare function sanitizeTelemetryEvent(event: DecantrTelemetryEvent, options?: TelemetryRedactionOptions): DecantrTelemetryEvent;
|
|
15
|
+
|
|
16
|
+
export { REDACTED_VALUE, type TelemetryRedactionOptions, isSensitiveTelemetryKey, sanitizeTelemetryEvent, sanitizeTelemetryValue };
|
package/dist/privacy.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/privacy.ts
|
|
2
|
+
var REDACTED_VALUE = "[redacted]";
|
|
3
|
+
var DEFAULT_MAX_STRING_LENGTH = 240;
|
|
4
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 25;
|
|
5
|
+
var DEFAULT_MAX_OBJECT_KEYS = 80;
|
|
6
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
7
|
+
var SENSITIVE_KEY_PATTERNS = [
|
|
8
|
+
/^api[_-]?key$/i,
|
|
9
|
+
/^authorization$/i,
|
|
10
|
+
/^code$/i,
|
|
11
|
+
/^content$/i,
|
|
12
|
+
/^contents$/i,
|
|
13
|
+
/^cookie$/i,
|
|
14
|
+
/^cwd$/i,
|
|
15
|
+
/^email$/i,
|
|
16
|
+
/^env$/i,
|
|
17
|
+
/^file[_-]?contents$/i,
|
|
18
|
+
/^file[_-]?path$/i,
|
|
19
|
+
/^home$/i,
|
|
20
|
+
/^ip[_-]?address$/i,
|
|
21
|
+
/^password$/i,
|
|
22
|
+
/^path$/i,
|
|
23
|
+
/^prompt$/i,
|
|
24
|
+
/^raw[_-]?prompt$/i,
|
|
25
|
+
/^secret$/i,
|
|
26
|
+
/^source$/i,
|
|
27
|
+
/^source[_-]?code$/i,
|
|
28
|
+
/^token$/i,
|
|
29
|
+
/^url$/i,
|
|
30
|
+
/^user[_-]?agent$/i
|
|
31
|
+
];
|
|
32
|
+
function isSensitiveTelemetryKey(key, patterns = SENSITIVE_KEY_PATTERNS) {
|
|
33
|
+
return patterns.some((pattern) => pattern.test(key));
|
|
34
|
+
}
|
|
35
|
+
function sanitizeTelemetryValue(value, options = {}, depth = 0) {
|
|
36
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
37
|
+
if (depth > maxDepth) {
|
|
38
|
+
return options.redactedValue ?? REDACTED_VALUE;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
|
|
42
|
+
return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (value === void 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
52
|
+
return value.slice(0, maxArrayLength).map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));
|
|
53
|
+
}
|
|
54
|
+
const redactedValue = options.redactedValue ?? REDACTED_VALUE;
|
|
55
|
+
const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;
|
|
56
|
+
const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;
|
|
57
|
+
const entries = Object.entries(value).slice(0, maxObjectKeys);
|
|
58
|
+
const sanitized = {};
|
|
59
|
+
for (const [key, entryValue] of entries) {
|
|
60
|
+
sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns) ? redactedValue : sanitizeTelemetryValue(entryValue, options, depth + 1);
|
|
61
|
+
}
|
|
62
|
+
return sanitized;
|
|
63
|
+
}
|
|
64
|
+
function sanitizeTelemetryEvent(event, options = {}) {
|
|
65
|
+
return {
|
|
66
|
+
...event,
|
|
67
|
+
properties: sanitizeTelemetryValue(
|
|
68
|
+
event.properties,
|
|
69
|
+
options
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
REDACTED_VALUE,
|
|
75
|
+
isSensitiveTelemetryKey,
|
|
76
|
+
sanitizeTelemetryEvent,
|
|
77
|
+
sanitizeTelemetryValue
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=privacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/privacy.ts"],"sourcesContent":["import type { DecantrTelemetryEvent, TelemetryPropertyValue } from './events.js';\n\nexport const REDACTED_VALUE = '[redacted]';\n\nconst DEFAULT_MAX_STRING_LENGTH = 240;\nconst DEFAULT_MAX_ARRAY_LENGTH = 25;\nconst DEFAULT_MAX_OBJECT_KEYS = 80;\nconst DEFAULT_MAX_DEPTH = 4;\n\nconst SENSITIVE_KEY_PATTERNS = [\n /^api[_-]?key$/i,\n /^authorization$/i,\n /^code$/i,\n /^content$/i,\n /^contents$/i,\n /^cookie$/i,\n /^cwd$/i,\n /^email$/i,\n /^env$/i,\n /^file[_-]?contents$/i,\n /^file[_-]?path$/i,\n /^home$/i,\n /^ip[_-]?address$/i,\n /^password$/i,\n /^path$/i,\n /^prompt$/i,\n /^raw[_-]?prompt$/i,\n /^secret$/i,\n /^source$/i,\n /^source[_-]?code$/i,\n /^token$/i,\n /^url$/i,\n /^user[_-]?agent$/i,\n];\n\nexport interface TelemetryRedactionOptions {\n maxArrayLength?: number;\n maxDepth?: number;\n maxObjectKeys?: number;\n maxStringLength?: number;\n redactedValue?: string;\n sensitiveKeyPatterns?: RegExp[];\n}\n\nexport function isSensitiveTelemetryKey(key: string, patterns = SENSITIVE_KEY_PATTERNS): boolean {\n return patterns.some((pattern) => pattern.test(key));\n}\n\nexport function sanitizeTelemetryValue(\n value: TelemetryPropertyValue,\n options: TelemetryRedactionOptions = {},\n depth = 0,\n): TelemetryPropertyValue {\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n if (depth > maxDepth) {\n return options.redactedValue ?? REDACTED_VALUE;\n }\n\n if (typeof value === 'string') {\n const maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;\n return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}...` : value;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) {\n return value;\n }\n\n if (value === undefined) {\n return null;\n }\n\n if (Array.isArray(value)) {\n const maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;\n return value\n .slice(0, maxArrayLength)\n .map((entry) => sanitizeTelemetryValue(entry, options, depth + 1));\n }\n\n const redactedValue = options.redactedValue ?? REDACTED_VALUE;\n const sensitiveKeyPatterns = options.sensitiveKeyPatterns ?? SENSITIVE_KEY_PATTERNS;\n const maxObjectKeys = options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;\n const entries = Object.entries(value).slice(0, maxObjectKeys);\n const sanitized: Record<string, TelemetryPropertyValue> = {};\n\n for (const [key, entryValue] of entries) {\n sanitized[key] = isSensitiveTelemetryKey(key, sensitiveKeyPatterns)\n ? redactedValue\n : sanitizeTelemetryValue(entryValue, options, depth + 1);\n }\n\n return sanitized;\n}\n\nexport function sanitizeTelemetryEvent(\n event: DecantrTelemetryEvent,\n options: TelemetryRedactionOptions = {},\n): DecantrTelemetryEvent {\n return {\n ...event,\n properties: sanitizeTelemetryValue(\n event.properties,\n options,\n ) as DecantrTelemetryEvent['properties'],\n } as DecantrTelemetryEvent;\n}\n"],"mappings":";AAEO,IAAM,iBAAiB;AAE9B,IAAM,4BAA4B;AAClC,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAE1B,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,wBAAwB,KAAa,WAAW,wBAAiC;AAC/F,SAAO,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,GAAG,CAAC;AACrD;AAEO,SAAS,uBACd,OACA,UAAqC,CAAC,GACtC,QAAQ,GACgB;AACxB,QAAM,WAAW,QAAQ,YAAY;AACrC,MAAI,QAAQ,UAAU;AACpB,WAAO,QAAQ,iBAAiB;AAAA,EAClC;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,WAAO,MAAM,SAAS,kBAAkB,GAAG,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ;AAAA,EACpF;AAEA,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,UAAU,MAAM;AAC7E,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,iBAAiB,QAAQ,kBAAkB;AACjD,WAAO,MACJ,MAAM,GAAG,cAAc,EACvB,IAAI,CAAC,UAAU,uBAAuB,OAAO,SAAS,QAAQ,CAAC,CAAC;AAAA,EACrE;AAEA,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,uBAAuB,QAAQ,wBAAwB;AAC7D,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,MAAM,GAAG,aAAa;AAC5D,QAAM,YAAoD,CAAC;AAE3D,aAAW,CAAC,KAAK,UAAU,KAAK,SAAS;AACvC,cAAU,GAAG,IAAI,wBAAwB,KAAK,oBAAoB,IAC9D,gBACA,uBAAuB,YAAY,SAAS,QAAQ,CAAC;AAAA,EAC3D;AAEA,SAAO;AACT;AAEO,SAAS,uBACd,OACA,UAAqC,CAAC,GACf;AACvB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY;AAAA,MACV,MAAM;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decantr/telemetry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Privacy-preserving telemetry contracts, clients, and analytics sinks for Decantr",
|
|
5
|
+
"author": "Decantr AI",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/decantr-ai/decantr/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/decantr-ai/decantr.git",
|
|
13
|
+
"directory": "packages/telemetry"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://decantr.ai",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"import": "./dist/client.js",
|
|
26
|
+
"types": "./dist/client.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./events": {
|
|
29
|
+
"import": "./dist/events.js",
|
|
30
|
+
"types": "./dist/events.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./posthog": {
|
|
33
|
+
"import": "./dist/posthog.js",
|
|
34
|
+
"types": "./dist/posthog.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./privacy": {
|
|
37
|
+
"import": "./dist/privacy.js",
|
|
38
|
+
"types": "./dist/privacy.d.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"preversion": "pnpm build && pnpm test"
|
|
55
|
+
}
|
|
56
|
+
}
|