crumbtrail-install-shared 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crumbtrail
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/dist/index.cjs ADDED
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BACKEND_JS_STACKS: () => BACKEND_JS_STACKS,
24
+ INFRA_STACKS: () => INFRA_STACKS,
25
+ JS_STACKS: () => JS_STACKS,
26
+ OTLP_CAPABILITY_FACTS: () => OTLP_CAPABILITY_FACTS,
27
+ OTLP_STACKS: () => OTLP_STACKS,
28
+ allStackInstalls: () => allStackInstalls,
29
+ buildAgentPrompt: () => buildAgentPrompt,
30
+ buildBackendJsNote: () => buildBackendJsNote,
31
+ buildOtlpSnippets: () => buildOtlpSnippets,
32
+ getInstallVariant: () => getInstallVariant,
33
+ otlpAuthHeaderValue: () => otlpAuthHeaderValue,
34
+ otlpBearerHeaderValue: () => otlpBearerHeaderValue
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+ var import_crumbtrail_core = require("crumbtrail-core");
38
+ var JS_STACKS = [
39
+ "nextjs",
40
+ "react",
41
+ "vue",
42
+ "svelte",
43
+ "vite",
44
+ "express",
45
+ "hono",
46
+ "node"
47
+ ];
48
+ var BACKEND_JS_STACKS = ["express", "hono", "node"];
49
+ var OTLP_STACKS = [
50
+ "django",
51
+ "flask",
52
+ "fastapi",
53
+ "dotnet",
54
+ "go",
55
+ "rails"
56
+ ];
57
+ var INFRA_STACKS = [
58
+ "postgres",
59
+ "grafana",
60
+ "loki",
61
+ "docker"
62
+ ];
63
+ function getInstallVariant(stack) {
64
+ const backendJs = BACKEND_JS_STACKS.includes(stack);
65
+ if (JS_STACKS.includes(stack)) {
66
+ return { stack, kind: "js", backendJs, comingSoon: false };
67
+ }
68
+ if (OTLP_STACKS.includes(stack)) {
69
+ return { stack, kind: "otlp", backendJs: false, comingSoon: false };
70
+ }
71
+ return { stack, kind: "infra", backendJs: false, comingSoon: true };
72
+ }
73
+ function allStackInstalls() {
74
+ return import_crumbtrail_core.STACK_IDS.map(getInstallVariant);
75
+ }
76
+ function buildBackendJsNote(stack) {
77
+ if (stack === "express") {
78
+ return [
79
+ "// Backend (Express) \u2014 also capture server-side errors and requests.",
80
+ "// Reuse the same ingest endpoint + key you set on the SDK above.",
81
+ "import {",
82
+ " createCrumbtrailExpressMiddleware,",
83
+ " createCrumbtrailExpressErrorMiddleware,",
84
+ '} from "crumbtrail-node";',
85
+ "",
86
+ "const crumbtrailOptions = { endpoint: CRUMBTRAIL_ENDPOINT, authToken: CRUMBTRAIL_KEY };",
87
+ "app.use(createCrumbtrailExpressMiddleware(crumbtrailOptions)); // before your routes",
88
+ "app.use(createCrumbtrailExpressErrorMiddleware(crumbtrailOptions)); // after your routes",
89
+ "",
90
+ "// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
91
+ ].join("\n");
92
+ }
93
+ return [
94
+ "// Backend (Hono / Node) \u2014 crumbtrail-node ships no framework middleware here.",
95
+ "// Open a headless session and record server-side events against it:",
96
+ 'import { startHeadlessSession } from "crumbtrail-node";',
97
+ "",
98
+ "const session = await startHeadlessSession({",
99
+ " endpoint: CRUMBTRAIL_ENDPOINT,",
100
+ " authToken: CRUMBTRAIL_KEY,",
101
+ ' sessionId: "<your-session-id>",',
102
+ "});",
103
+ "// session.record(event) to attach server events; session.end() when done.",
104
+ "",
105
+ "// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
106
+ ].join("\n");
107
+ }
108
+ var OTLP_CAPABILITY_FACTS = {
109
+ paths: ["/v1/traces", "/v1/logs"],
110
+ protocols: ["http/protobuf", "http/json"],
111
+ authHeaders: ["X-Crumbtrail-Auth", "Authorization: Bearer"],
112
+ compression: { recommended: "none", accepted: ["none", "gzip"] },
113
+ sessionAttribute: "crumbtrail.session.id"
114
+ };
115
+ function otlpAuthHeaderValue(apiKey) {
116
+ return `X-Crumbtrail-Auth=${apiKey}`;
117
+ }
118
+ function otlpBearerHeaderValue(apiKey) {
119
+ return `Authorization=Bearer%20${apiKey}`;
120
+ }
121
+ function buildOtlpSnippets({
122
+ endpoint,
123
+ apiKey
124
+ }) {
125
+ const [protobuf, jsonProtocol] = OTLP_CAPABILITY_FACTS.protocols;
126
+ return {
127
+ env: [
128
+ `OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint}`,
129
+ `OTEL_EXPORTER_OTLP_PROTOCOL=${protobuf} # or ${jsonProtocol}`,
130
+ `OTEL_EXPORTER_OTLP_COMPRESSION=${OTLP_CAPABILITY_FACTS.compression.recommended} # recommended; gzip is also accepted`
131
+ ].join("\n"),
132
+ authHeader: [
133
+ `OTEL_EXPORTER_OTLP_HEADERS=${otlpAuthHeaderValue(apiKey)}`,
134
+ `# Or, if your exporter sends a Bearer token (note the %20-escaped space):`,
135
+ `# OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeaderValue(apiKey)}`
136
+ ].join("\n"),
137
+ sessionAttr: `OTEL_RESOURCE_ATTRIBUTES=${OTLP_CAPABILITY_FACTS.sessionAttribute}=<your-session-id>`,
138
+ note: `Crumbtrail's OTLP receiver appends ${OTLP_CAPABILITY_FACTS.paths.join(" and ")} to the endpoint above \u2014 don't include those paths yourself. Set the ${OTLP_CAPABILITY_FACTS.sessionAttribute} resource attribute to join backend spans to the frontend session.`
139
+ };
140
+ }
141
+ function buildAgentPrompt(stack, keys) {
142
+ const { kind, backendJs } = getInstallVariant(stack);
143
+ const { endpoint, apiKey } = keys;
144
+ const header = [
145
+ "You are setting up Crumbtrail in this project. Make ONLY the changes below,",
146
+ "do not refactor or touch anything else, then verify the build still passes.",
147
+ "",
148
+ `Ingest endpoint: ${endpoint}`,
149
+ `Ingest key: ${apiKey}`,
150
+ ""
151
+ ];
152
+ if (kind === "otlp") {
153
+ return [
154
+ ...header,
155
+ "This is a non-JS backend that already uses OpenTelemetry. Do NOT install a",
156
+ "second SDK. Instead, add Crumbtrail as an additional OTLP/HTTP exporter:",
157
+ ` 1. Set OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint} (the exporter appends`,
158
+ " /v1/traces and /v1/logs \u2014 do not add those paths).",
159
+ ` 2. Send the auth header X-Crumbtrail-Auth: ${apiKey} on every export`,
160
+ " (or Authorization: Bearer <key> if your exporter prefers that).",
161
+ " 3. Stamp the resource attribute crumbtrail.session.id so spans/logs join",
162
+ " the right session.",
163
+ " 4. Keep your existing exporter \u2014 add Crumbtrail alongside it.",
164
+ " 5. Verify the app still builds and starts."
165
+ ].join("\n");
166
+ }
167
+ const jsLines = [
168
+ ...header,
169
+ "This is a JavaScript/TypeScript project. Do the following:",
170
+ " 1. Install the SDK: npm install crumbtrail-core",
171
+ ' 2. Import the SDK: import { Crumbtrail, PRESET_PASSIVE } from "crumbtrail-core";',
172
+ " 3. Initialize once at the app entry point with PRESET_PASSIVE:",
173
+ " Crumbtrail.init({",
174
+ " ...PRESET_PASSIVE,",
175
+ ` httpEndpoint: "${endpoint}",`,
176
+ ` httpAuthToken: "${apiKey}",`,
177
+ " });"
178
+ ];
179
+ if (backendJs && stack === "express") {
180
+ jsLines.push(
181
+ " 4. This is an Express backend \u2014 also install crumbtrail-node and capture",
182
+ " server errors and requests with its Express middleware:",
183
+ " import {",
184
+ " createCrumbtrailExpressMiddleware,",
185
+ " createCrumbtrailExpressErrorMiddleware,",
186
+ ' } from "crumbtrail-node";',
187
+ ` const opts = { endpoint: "${endpoint}", authToken: "${apiKey}" };`,
188
+ " app.use(createCrumbtrailExpressMiddleware(opts)); // before your routes",
189
+ " app.use(createCrumbtrailExpressErrorMiddleware(opts)); // after your routes",
190
+ " 5. Change nothing else, then verify the build still passes."
191
+ );
192
+ } else if (backendJs) {
193
+ jsLines.push(
194
+ " 4. This is also a backend. Install crumbtrail-node; it ships no framework",
195
+ " middleware for this stack, so open a headless session and record",
196
+ " server-side events against it:",
197
+ ' import { startHeadlessSession } from "crumbtrail-node";',
198
+ " const session = await startHeadlessSession({",
199
+ ` endpoint: "${endpoint}",`,
200
+ ` authToken: "${apiKey}",`,
201
+ ' sessionId: "<your-session-id>",',
202
+ " });",
203
+ " // session.record(event) for server events; session.end() when the request ends.",
204
+ " 5. Change nothing else, then verify the build still passes."
205
+ );
206
+ } else {
207
+ jsLines.push(
208
+ " 4. Change nothing else, then verify the build still passes."
209
+ );
210
+ }
211
+ return jsLines.join("\n");
212
+ }
213
+ // Annotate the CommonJS export names for ESM import in node:
214
+ 0 && (module.exports = {
215
+ BACKEND_JS_STACKS,
216
+ INFRA_STACKS,
217
+ JS_STACKS,
218
+ OTLP_CAPABILITY_FACTS,
219
+ OTLP_STACKS,
220
+ allStackInstalls,
221
+ buildAgentPrompt,
222
+ buildBackendJsNote,
223
+ buildOtlpSnippets,
224
+ getInstallVariant,
225
+ otlpAuthHeaderValue,
226
+ otlpBearerHeaderValue
227
+ });
@@ -0,0 +1,109 @@
1
+ import { Stack } from 'crumbtrail-core';
2
+
3
+ type InstallVariantKind = "js" | "otlp" | "infra";
4
+ /** JS/TS stacks that install crumbtrail-core directly. */
5
+ declare const JS_STACKS: readonly Stack[];
6
+ /** JS backends that additionally wire the crumbtrail-node middleware. */
7
+ declare const BACKEND_JS_STACKS: readonly Stack[];
8
+ /** Non-JS backends wired via their existing OpenTelemetry exporter. */
9
+ declare const OTLP_STACKS: readonly Stack[];
10
+ /** Evidence sources that are not yet a first-class ingest target. */
11
+ declare const INFRA_STACKS: readonly Stack[];
12
+ interface StackInstall {
13
+ stack: Stack;
14
+ kind: InstallVariantKind;
15
+ /** True for express/hono/node — they also need the backend middleware note. */
16
+ backendJs: boolean;
17
+ /** True for infra evidence sources — surfaced as "coming soon". */
18
+ comingSoon: boolean;
19
+ }
20
+ /** Classify a single stack into its install variant. Total over all 18 stacks. */
21
+ declare function getInstallVariant(stack: Stack): StackInstall;
22
+ /** The classification table for every supported stack (handy for tests/UI). */
23
+ declare function allStackInstalls(): StackInstall[];
24
+ interface EndpointKey {
25
+ /** Live ingest endpoint (the cloud origin / dashboard origin). */
26
+ endpoint: string;
27
+ /** Live ingest key used as the auth token. */
28
+ apiKey: string;
29
+ }
30
+ /**
31
+ * The crumbtrail-node backend note shown under <InstallSteps/> for the backend-JS
32
+ * stacks. Uses ONLY the real crumbtrail-node exports — no invented names:
33
+ * • Express is the only stack with framework middleware
34
+ * (createCrumbtrailExpressMiddleware / createCrumbtrailExpressErrorMiddleware).
35
+ * • Hono / Node ship no framework middleware, so they open a headless session
36
+ * (startHeadlessSession) and record server-side events against it.
37
+ */
38
+ declare function buildBackendJsNote(stack: Stack): string;
39
+ /**
40
+ * Single source of truth for what Crumbtrail's OTLP/HTTP receiver accepts. Both
41
+ * `buildOtlpSnippets` (the wizard guidance) and the collector recipes in
42
+ * `packages/node/src/provider-recipes.json` must agree with these facts — a
43
+ * consistency test asserts the two never drift (compression, endpoint path
44
+ * suffix, auth header names).
45
+ *
46
+ * Nothing here is invented: the paths, protocols, auth headers, and session
47
+ * attribute all match the live ingest routes served by packages/node/src/server.ts
48
+ * and by the hosted Crumbtrail cloud.
49
+ */
50
+ interface OtlpCapabilityFacts {
51
+ /** Signal paths the receiver serves; exporters append these to the endpoint. */
52
+ readonly paths: readonly ["/v1/traces", "/v1/logs"];
53
+ /** OTLP/HTTP wire protocols accepted (both, as of the protobuf+gzip parity CP). */
54
+ readonly protocols: readonly ["http/protobuf", "http/json"];
55
+ /** Auth header names honored equivalently by the receiver. */
56
+ readonly authHeaders: readonly ["X-Crumbtrail-Auth", "Authorization: Bearer"];
57
+ /** Content-Encoding posture: "none" recommended for collectors; gzip accepted. */
58
+ readonly compression: {
59
+ readonly recommended: "none";
60
+ readonly accepted: readonly ["none", "gzip"];
61
+ };
62
+ /** Resource/span attribute that files spans/logs into a Crumbtrail session. */
63
+ readonly sessionAttribute: "crumbtrail.session.id";
64
+ }
65
+ declare const OTLP_CAPABILITY_FACTS: OtlpCapabilityFacts;
66
+ /**
67
+ * The `X-Crumbtrail-Auth=<key>` value for `OTEL_EXPORTER_OTLP_HEADERS`.
68
+ * OTEL parses that env var as comma-separated `name=value` pairs; the value is
69
+ * used verbatim, so a plain key needs no escaping here.
70
+ */
71
+ declare function otlpAuthHeaderValue(apiKey: string): string;
72
+ /**
73
+ * The Bearer form for `OTEL_EXPORTER_OTLP_HEADERS`. The space between `Bearer`
74
+ * and the token MUST be percent-encoded (`%20`) — an unescaped space breaks
75
+ * OTEL's `name=value` header parsing and silently drops auth. This is the fix
76
+ * for the previously wrong `Authorization=Bearer <key>` guidance.
77
+ */
78
+ declare function otlpBearerHeaderValue(apiKey: string): string;
79
+ interface OtlpSnippets {
80
+ /** OTLP endpoint + protocol + compression env vars pointed at the cloud origin. */
81
+ env: string;
82
+ /** Auth header carried by the exporter (X-Crumbtrail-Auth or Bearer). */
83
+ authHeader: string;
84
+ /** Resource attribute that files spans/logs into a Crumbtrail session. */
85
+ sessionAttr: string;
86
+ /** Human note about the appended /v1/traces + /v1/logs paths. */
87
+ note: string;
88
+ }
89
+ /**
90
+ * Build the OTLP setup snippets for a non-JS backend. Uses ONLY the real,
91
+ * documented names — no invented env vars or headers, and everything is derived
92
+ * from OTLP_CAPABILITY_FACTS so it can never drift from the collector recipes:
93
+ * • OTEL_EXPORTER_OTLP_ENDPOINT → the cloud origin (exporter appends paths)
94
+ * • OTEL_EXPORTER_OTLP_PROTOCOL → http/protobuf or http/json
95
+ * • OTEL_EXPORTER_OTLP_HEADERS → X-Crumbtrail-Auth=<key> (or Bearer%20<key>)
96
+ * • OTEL_EXPORTER_OTLP_COMPRESSION→ none (recommended) — gzip is accepted too
97
+ * • OTEL_RESOURCE_ATTRIBUTES → crumbtrail.session.id=<id>
98
+ * Verified against docs/integrations/* and the ingest routes.
99
+ */
100
+ declare function buildOtlpSnippets({ endpoint, apiKey, }: EndpointKey): OtlpSnippets;
101
+ /**
102
+ * Build the "Install via AI" agent prompt — a copyable block with the real
103
+ * endpoint + key baked in. It instructs a coding agent to run the correct setup
104
+ * for the stack, initialize with PRESET_PASSIVE (JS), wire backend middleware
105
+ * when applicable, change nothing else, and verify the build.
106
+ */
107
+ declare function buildAgentPrompt(stack: Stack, keys: EndpointKey): string;
108
+
109
+ export { BACKEND_JS_STACKS, type EndpointKey, INFRA_STACKS, type InstallVariantKind, JS_STACKS, OTLP_CAPABILITY_FACTS, OTLP_STACKS, type OtlpCapabilityFacts, type OtlpSnippets, type StackInstall, allStackInstalls, buildAgentPrompt, buildBackendJsNote, buildOtlpSnippets, getInstallVariant, otlpAuthHeaderValue, otlpBearerHeaderValue };
@@ -0,0 +1,109 @@
1
+ import { Stack } from 'crumbtrail-core';
2
+
3
+ type InstallVariantKind = "js" | "otlp" | "infra";
4
+ /** JS/TS stacks that install crumbtrail-core directly. */
5
+ declare const JS_STACKS: readonly Stack[];
6
+ /** JS backends that additionally wire the crumbtrail-node middleware. */
7
+ declare const BACKEND_JS_STACKS: readonly Stack[];
8
+ /** Non-JS backends wired via their existing OpenTelemetry exporter. */
9
+ declare const OTLP_STACKS: readonly Stack[];
10
+ /** Evidence sources that are not yet a first-class ingest target. */
11
+ declare const INFRA_STACKS: readonly Stack[];
12
+ interface StackInstall {
13
+ stack: Stack;
14
+ kind: InstallVariantKind;
15
+ /** True for express/hono/node — they also need the backend middleware note. */
16
+ backendJs: boolean;
17
+ /** True for infra evidence sources — surfaced as "coming soon". */
18
+ comingSoon: boolean;
19
+ }
20
+ /** Classify a single stack into its install variant. Total over all 18 stacks. */
21
+ declare function getInstallVariant(stack: Stack): StackInstall;
22
+ /** The classification table for every supported stack (handy for tests/UI). */
23
+ declare function allStackInstalls(): StackInstall[];
24
+ interface EndpointKey {
25
+ /** Live ingest endpoint (the cloud origin / dashboard origin). */
26
+ endpoint: string;
27
+ /** Live ingest key used as the auth token. */
28
+ apiKey: string;
29
+ }
30
+ /**
31
+ * The crumbtrail-node backend note shown under <InstallSteps/> for the backend-JS
32
+ * stacks. Uses ONLY the real crumbtrail-node exports — no invented names:
33
+ * • Express is the only stack with framework middleware
34
+ * (createCrumbtrailExpressMiddleware / createCrumbtrailExpressErrorMiddleware).
35
+ * • Hono / Node ship no framework middleware, so they open a headless session
36
+ * (startHeadlessSession) and record server-side events against it.
37
+ */
38
+ declare function buildBackendJsNote(stack: Stack): string;
39
+ /**
40
+ * Single source of truth for what Crumbtrail's OTLP/HTTP receiver accepts. Both
41
+ * `buildOtlpSnippets` (the wizard guidance) and the collector recipes in
42
+ * `packages/node/src/provider-recipes.json` must agree with these facts — a
43
+ * consistency test asserts the two never drift (compression, endpoint path
44
+ * suffix, auth header names).
45
+ *
46
+ * Nothing here is invented: the paths, protocols, auth headers, and session
47
+ * attribute all match the live ingest routes served by packages/node/src/server.ts
48
+ * and by the hosted Crumbtrail cloud.
49
+ */
50
+ interface OtlpCapabilityFacts {
51
+ /** Signal paths the receiver serves; exporters append these to the endpoint. */
52
+ readonly paths: readonly ["/v1/traces", "/v1/logs"];
53
+ /** OTLP/HTTP wire protocols accepted (both, as of the protobuf+gzip parity CP). */
54
+ readonly protocols: readonly ["http/protobuf", "http/json"];
55
+ /** Auth header names honored equivalently by the receiver. */
56
+ readonly authHeaders: readonly ["X-Crumbtrail-Auth", "Authorization: Bearer"];
57
+ /** Content-Encoding posture: "none" recommended for collectors; gzip accepted. */
58
+ readonly compression: {
59
+ readonly recommended: "none";
60
+ readonly accepted: readonly ["none", "gzip"];
61
+ };
62
+ /** Resource/span attribute that files spans/logs into a Crumbtrail session. */
63
+ readonly sessionAttribute: "crumbtrail.session.id";
64
+ }
65
+ declare const OTLP_CAPABILITY_FACTS: OtlpCapabilityFacts;
66
+ /**
67
+ * The `X-Crumbtrail-Auth=<key>` value for `OTEL_EXPORTER_OTLP_HEADERS`.
68
+ * OTEL parses that env var as comma-separated `name=value` pairs; the value is
69
+ * used verbatim, so a plain key needs no escaping here.
70
+ */
71
+ declare function otlpAuthHeaderValue(apiKey: string): string;
72
+ /**
73
+ * The Bearer form for `OTEL_EXPORTER_OTLP_HEADERS`. The space between `Bearer`
74
+ * and the token MUST be percent-encoded (`%20`) — an unescaped space breaks
75
+ * OTEL's `name=value` header parsing and silently drops auth. This is the fix
76
+ * for the previously wrong `Authorization=Bearer <key>` guidance.
77
+ */
78
+ declare function otlpBearerHeaderValue(apiKey: string): string;
79
+ interface OtlpSnippets {
80
+ /** OTLP endpoint + protocol + compression env vars pointed at the cloud origin. */
81
+ env: string;
82
+ /** Auth header carried by the exporter (X-Crumbtrail-Auth or Bearer). */
83
+ authHeader: string;
84
+ /** Resource attribute that files spans/logs into a Crumbtrail session. */
85
+ sessionAttr: string;
86
+ /** Human note about the appended /v1/traces + /v1/logs paths. */
87
+ note: string;
88
+ }
89
+ /**
90
+ * Build the OTLP setup snippets for a non-JS backend. Uses ONLY the real,
91
+ * documented names — no invented env vars or headers, and everything is derived
92
+ * from OTLP_CAPABILITY_FACTS so it can never drift from the collector recipes:
93
+ * • OTEL_EXPORTER_OTLP_ENDPOINT → the cloud origin (exporter appends paths)
94
+ * • OTEL_EXPORTER_OTLP_PROTOCOL → http/protobuf or http/json
95
+ * • OTEL_EXPORTER_OTLP_HEADERS → X-Crumbtrail-Auth=<key> (or Bearer%20<key>)
96
+ * • OTEL_EXPORTER_OTLP_COMPRESSION→ none (recommended) — gzip is accepted too
97
+ * • OTEL_RESOURCE_ATTRIBUTES → crumbtrail.session.id=<id>
98
+ * Verified against docs/integrations/* and the ingest routes.
99
+ */
100
+ declare function buildOtlpSnippets({ endpoint, apiKey, }: EndpointKey): OtlpSnippets;
101
+ /**
102
+ * Build the "Install via AI" agent prompt — a copyable block with the real
103
+ * endpoint + key baked in. It instructs a coding agent to run the correct setup
104
+ * for the stack, initialize with PRESET_PASSIVE (JS), wire backend middleware
105
+ * when applicable, change nothing else, and verify the build.
106
+ */
107
+ declare function buildAgentPrompt(stack: Stack, keys: EndpointKey): string;
108
+
109
+ export { BACKEND_JS_STACKS, type EndpointKey, INFRA_STACKS, type InstallVariantKind, JS_STACKS, OTLP_CAPABILITY_FACTS, OTLP_STACKS, type OtlpCapabilityFacts, type OtlpSnippets, type StackInstall, allStackInstalls, buildAgentPrompt, buildBackendJsNote, buildOtlpSnippets, getInstallVariant, otlpAuthHeaderValue, otlpBearerHeaderValue };
package/dist/index.js ADDED
@@ -0,0 +1,191 @@
1
+ // src/index.ts
2
+ import { STACK_IDS } from "crumbtrail-core";
3
+ var JS_STACKS = [
4
+ "nextjs",
5
+ "react",
6
+ "vue",
7
+ "svelte",
8
+ "vite",
9
+ "express",
10
+ "hono",
11
+ "node"
12
+ ];
13
+ var BACKEND_JS_STACKS = ["express", "hono", "node"];
14
+ var OTLP_STACKS = [
15
+ "django",
16
+ "flask",
17
+ "fastapi",
18
+ "dotnet",
19
+ "go",
20
+ "rails"
21
+ ];
22
+ var INFRA_STACKS = [
23
+ "postgres",
24
+ "grafana",
25
+ "loki",
26
+ "docker"
27
+ ];
28
+ function getInstallVariant(stack) {
29
+ const backendJs = BACKEND_JS_STACKS.includes(stack);
30
+ if (JS_STACKS.includes(stack)) {
31
+ return { stack, kind: "js", backendJs, comingSoon: false };
32
+ }
33
+ if (OTLP_STACKS.includes(stack)) {
34
+ return { stack, kind: "otlp", backendJs: false, comingSoon: false };
35
+ }
36
+ return { stack, kind: "infra", backendJs: false, comingSoon: true };
37
+ }
38
+ function allStackInstalls() {
39
+ return STACK_IDS.map(getInstallVariant);
40
+ }
41
+ function buildBackendJsNote(stack) {
42
+ if (stack === "express") {
43
+ return [
44
+ "// Backend (Express) \u2014 also capture server-side errors and requests.",
45
+ "// Reuse the same ingest endpoint + key you set on the SDK above.",
46
+ "import {",
47
+ " createCrumbtrailExpressMiddleware,",
48
+ " createCrumbtrailExpressErrorMiddleware,",
49
+ '} from "crumbtrail-node";',
50
+ "",
51
+ "const crumbtrailOptions = { endpoint: CRUMBTRAIL_ENDPOINT, authToken: CRUMBTRAIL_KEY };",
52
+ "app.use(createCrumbtrailExpressMiddleware(crumbtrailOptions)); // before your routes",
53
+ "app.use(createCrumbtrailExpressErrorMiddleware(crumbtrailOptions)); // after your routes",
54
+ "",
55
+ "// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
56
+ ].join("\n");
57
+ }
58
+ return [
59
+ "// Backend (Hono / Node) \u2014 crumbtrail-node ships no framework middleware here.",
60
+ "// Open a headless session and record server-side events against it:",
61
+ 'import { startHeadlessSession } from "crumbtrail-node";',
62
+ "",
63
+ "const session = await startHeadlessSession({",
64
+ " endpoint: CRUMBTRAIL_ENDPOINT,",
65
+ " authToken: CRUMBTRAIL_KEY,",
66
+ ' sessionId: "<your-session-id>",',
67
+ "});",
68
+ "// session.record(event) to attach server events; session.end() when done.",
69
+ "",
70
+ "// Prefer OpenTelemetry? Point your OTLP exporter at the receiver instead."
71
+ ].join("\n");
72
+ }
73
+ var OTLP_CAPABILITY_FACTS = {
74
+ paths: ["/v1/traces", "/v1/logs"],
75
+ protocols: ["http/protobuf", "http/json"],
76
+ authHeaders: ["X-Crumbtrail-Auth", "Authorization: Bearer"],
77
+ compression: { recommended: "none", accepted: ["none", "gzip"] },
78
+ sessionAttribute: "crumbtrail.session.id"
79
+ };
80
+ function otlpAuthHeaderValue(apiKey) {
81
+ return `X-Crumbtrail-Auth=${apiKey}`;
82
+ }
83
+ function otlpBearerHeaderValue(apiKey) {
84
+ return `Authorization=Bearer%20${apiKey}`;
85
+ }
86
+ function buildOtlpSnippets({
87
+ endpoint,
88
+ apiKey
89
+ }) {
90
+ const [protobuf, jsonProtocol] = OTLP_CAPABILITY_FACTS.protocols;
91
+ return {
92
+ env: [
93
+ `OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint}`,
94
+ `OTEL_EXPORTER_OTLP_PROTOCOL=${protobuf} # or ${jsonProtocol}`,
95
+ `OTEL_EXPORTER_OTLP_COMPRESSION=${OTLP_CAPABILITY_FACTS.compression.recommended} # recommended; gzip is also accepted`
96
+ ].join("\n"),
97
+ authHeader: [
98
+ `OTEL_EXPORTER_OTLP_HEADERS=${otlpAuthHeaderValue(apiKey)}`,
99
+ `# Or, if your exporter sends a Bearer token (note the %20-escaped space):`,
100
+ `# OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeaderValue(apiKey)}`
101
+ ].join("\n"),
102
+ sessionAttr: `OTEL_RESOURCE_ATTRIBUTES=${OTLP_CAPABILITY_FACTS.sessionAttribute}=<your-session-id>`,
103
+ note: `Crumbtrail's OTLP receiver appends ${OTLP_CAPABILITY_FACTS.paths.join(" and ")} to the endpoint above \u2014 don't include those paths yourself. Set the ${OTLP_CAPABILITY_FACTS.sessionAttribute} resource attribute to join backend spans to the frontend session.`
104
+ };
105
+ }
106
+ function buildAgentPrompt(stack, keys) {
107
+ const { kind, backendJs } = getInstallVariant(stack);
108
+ const { endpoint, apiKey } = keys;
109
+ const header = [
110
+ "You are setting up Crumbtrail in this project. Make ONLY the changes below,",
111
+ "do not refactor or touch anything else, then verify the build still passes.",
112
+ "",
113
+ `Ingest endpoint: ${endpoint}`,
114
+ `Ingest key: ${apiKey}`,
115
+ ""
116
+ ];
117
+ if (kind === "otlp") {
118
+ return [
119
+ ...header,
120
+ "This is a non-JS backend that already uses OpenTelemetry. Do NOT install a",
121
+ "second SDK. Instead, add Crumbtrail as an additional OTLP/HTTP exporter:",
122
+ ` 1. Set OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint} (the exporter appends`,
123
+ " /v1/traces and /v1/logs \u2014 do not add those paths).",
124
+ ` 2. Send the auth header X-Crumbtrail-Auth: ${apiKey} on every export`,
125
+ " (or Authorization: Bearer <key> if your exporter prefers that).",
126
+ " 3. Stamp the resource attribute crumbtrail.session.id so spans/logs join",
127
+ " the right session.",
128
+ " 4. Keep your existing exporter \u2014 add Crumbtrail alongside it.",
129
+ " 5. Verify the app still builds and starts."
130
+ ].join("\n");
131
+ }
132
+ const jsLines = [
133
+ ...header,
134
+ "This is a JavaScript/TypeScript project. Do the following:",
135
+ " 1. Install the SDK: npm install crumbtrail-core",
136
+ ' 2. Import the SDK: import { Crumbtrail, PRESET_PASSIVE } from "crumbtrail-core";',
137
+ " 3. Initialize once at the app entry point with PRESET_PASSIVE:",
138
+ " Crumbtrail.init({",
139
+ " ...PRESET_PASSIVE,",
140
+ ` httpEndpoint: "${endpoint}",`,
141
+ ` httpAuthToken: "${apiKey}",`,
142
+ " });"
143
+ ];
144
+ if (backendJs && stack === "express") {
145
+ jsLines.push(
146
+ " 4. This is an Express backend \u2014 also install crumbtrail-node and capture",
147
+ " server errors and requests with its Express middleware:",
148
+ " import {",
149
+ " createCrumbtrailExpressMiddleware,",
150
+ " createCrumbtrailExpressErrorMiddleware,",
151
+ ' } from "crumbtrail-node";',
152
+ ` const opts = { endpoint: "${endpoint}", authToken: "${apiKey}" };`,
153
+ " app.use(createCrumbtrailExpressMiddleware(opts)); // before your routes",
154
+ " app.use(createCrumbtrailExpressErrorMiddleware(opts)); // after your routes",
155
+ " 5. Change nothing else, then verify the build still passes."
156
+ );
157
+ } else if (backendJs) {
158
+ jsLines.push(
159
+ " 4. This is also a backend. Install crumbtrail-node; it ships no framework",
160
+ " middleware for this stack, so open a headless session and record",
161
+ " server-side events against it:",
162
+ ' import { startHeadlessSession } from "crumbtrail-node";',
163
+ " const session = await startHeadlessSession({",
164
+ ` endpoint: "${endpoint}",`,
165
+ ` authToken: "${apiKey}",`,
166
+ ' sessionId: "<your-session-id>",',
167
+ " });",
168
+ " // session.record(event) for server events; session.end() when the request ends.",
169
+ " 5. Change nothing else, then verify the build still passes."
170
+ );
171
+ } else {
172
+ jsLines.push(
173
+ " 4. Change nothing else, then verify the build still passes."
174
+ );
175
+ }
176
+ return jsLines.join("\n");
177
+ }
178
+ export {
179
+ BACKEND_JS_STACKS,
180
+ INFRA_STACKS,
181
+ JS_STACKS,
182
+ OTLP_CAPABILITY_FACTS,
183
+ OTLP_STACKS,
184
+ allStackInstalls,
185
+ buildAgentPrompt,
186
+ buildBackendJsNote,
187
+ buildOtlpSnippets,
188
+ getInstallVariant,
189
+ otlpAuthHeaderValue,
190
+ otlpBearerHeaderValue
191
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "crumbtrail-install-shared",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "crumbtrail-core": "^0.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/react": "^19.0.0",
23
+ "react": "^19.0.0"
24
+ },
25
+ "license": "MIT",
26
+ "homepage": "https://github.com/crumbtrail-dev/crumbtrail-js/tree/main/packages/install-shared#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/crumbtrail-dev/crumbtrail-js/issues"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/crumbtrail-dev/crumbtrail-js.git",
33
+ "directory": "packages/install-shared"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "test": "vitest run",
41
+ "test:coverage": "vitest run --coverage",
42
+ "test:watch": "vitest",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json",
44
+ "clean": "rm -rf dist"
45
+ }
46
+ }