@lorekit/cli 1.24.1 → 1.25.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorekit/cli",
3
- "version": "1.24.1",
3
+ "version": "1.25.0",
4
4
  "description": "Install the LoreKit shared-memory skill and run health checks for the LoreKit MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/mcp.mjs CHANGED
@@ -124,16 +124,36 @@ export function mcpToRestBase(mcpEndpointUrl) {
124
124
  * @param {number} [opts.timeoutMs=10000]
125
125
  * @param {string} [opts.traceparent] - W3C traceparent header value
126
126
  */
127
+ /**
128
+ * Normalise a client-supplied usage correlation id (a PR ref, session id, or CI
129
+ * job id). Bounded + charset-restricted to match the server's `parseCorrelationId`
130
+ * (supabase/functions/_shared/usage-stats.ts); returns null for empty/over-long/
131
+ * out-of-charset input so a bad value is simply not sent. Zero-dep (the CLI does
132
+ * not import mcp-core), so the small regex is duplicated intentionally.
133
+ */
134
+ export function normalizeCorrelationId(raw) {
135
+ if (typeof raw !== 'string') return null;
136
+ const t = raw.trim();
137
+ if (!t || t.length > 200) return null;
138
+ return /^[A-Za-z0-9_\-./:#@]+$/.test(t) ? t : null;
139
+ }
140
+
127
141
  export async function restFetch(baseUrl, token, path, { method = 'GET', body, timeoutMs = 10000, traceparent } = {}) {
128
142
  const controller = new AbortController();
129
143
  const timer = setTimeout(() => controller.abort(), timeoutMs);
130
144
  try {
131
145
  const url = `${baseUrl}${path}`;
146
+ // Opt-in usage correlation: when LOREKIT_CORRELATION_ID is set (e.g. by a CI
147
+ // job or a hook to a PR/session id), tag every REST call so GET
148
+ // /memories/usage?correlation_id=… can report "usage for this PR". Absent env
149
+ // ⇒ no header ⇒ existing behaviour unchanged.
150
+ const correlationId = normalizeCorrelationId(process.env.LOREKIT_CORRELATION_ID);
132
151
  const headers = {
133
152
  accept: 'application/json',
134
153
  ...(body !== undefined ? { 'content-type': 'application/json' } : {}),
135
154
  ...(token ? { authorization: `Bearer ${token}` } : {}),
136
155
  ...(traceparent ? { traceparent } : {}),
156
+ ...(correlationId ? { 'x-lorekit-correlation-id': correlationId } : {}),
137
157
  };
138
158
  const res = await fetch(url, {
139
159
  method,
package/src/telemetry.mjs CHANGED
@@ -165,16 +165,58 @@ function toOtlpAttributes(attributes) {
165
165
  return Object.entries(attributes).map(([key, value]) => ({ key, value: toOtlpValue(value) }));
166
166
  }
167
167
 
168
- function resourceAttributes(version) {
169
- return [
168
+ // OTel `os.type` / `host.arch` use their own bounded enum vocabularies, which
169
+ // are NOT identical to Node's `process.platform` / `process.arch` spellings.
170
+ // Node's `win32` / `sunos` and `x64` / `ia32` / `arm` are the ones that differ;
171
+ // emitting them verbatim produces off-registry attribute values that a Dash0 /
172
+ // OTel-native backend can't group with telemetry from other SDKs. Map the known
173
+ // divergences and pass anything already-canonical (or unknown) through.
174
+ // os.type: https://opentelemetry.io/docs/specs/semconv/registry/attributes/os/
175
+ // host.arch: https://opentelemetry.io/docs/specs/semconv/registry/attributes/host/
176
+ const OS_TYPE_BY_PLATFORM = { win32: 'windows', sunos: 'solaris' };
177
+ // Node's `process.arch` reports `ppc` for 32-bit PowerPC; the OTel `host.arch`
178
+ // registry value for it is `ppc32` (its `ppc64` spelling already matches Node).
179
+ const HOST_ARCH_BY_PROCESS_ARCH = { x64: 'amd64', ia32: 'x86', arm: 'arm32', ppc: 'ppc32' };
180
+
181
+ /** Map a Node `process.platform` value to an OTel `os.type` registry value. */
182
+ export function normalizeOsType(platform) {
183
+ return OS_TYPE_BY_PLATFORM[platform] ?? platform;
184
+ }
185
+
186
+ /** Map a Node `process.arch` value to an OTel `host.arch` registry value. */
187
+ export function normalizeHostArch(arch) {
188
+ return HOST_ARCH_BY_PROCESS_ARCH[arch] ?? arch;
189
+ }
190
+
191
+ /**
192
+ * Resolve the `deployment.environment.name` resource value, or `undefined` when
193
+ * none is set. The CLI runs on end-users' machines, so — unlike the edge/web/
194
+ * mcp-node deployments — it has no ambient environment and deliberately OMITS
195
+ * the attribute by default. It is emitted ONLY when explicitly overridden via
196
+ * `DEPLOYMENT_ENVIRONMENT` (falling back to `OTEL_DEPLOYMENT_ENVIRONMENT`) — the
197
+ * same single, env-driven knob the edge honours, which the correlated-trace
198
+ * harness (`scripts/emit-correlated-trace.mts`) uses to stamp `test`.
199
+ * @param {object} [env] defaults to process.env
200
+ */
201
+ export function resolveDeploymentEnvironment(env = process.env) {
202
+ const raw = env.DEPLOYMENT_ENVIRONMENT ?? env.OTEL_DEPLOYMENT_ENVIRONMENT;
203
+ const value = raw !== undefined ? String(raw).trim() : '';
204
+ return value || undefined;
205
+ }
206
+
207
+ function resourceAttributes(version, env = process.env) {
208
+ const attrs = [
170
209
  { key: 'service.name', value: { stringValue: 'cli' } },
171
210
  { key: 'service.namespace', value: { stringValue: 'lorekit' } },
172
211
  { key: 'service.version', value: { stringValue: String(version) } },
173
212
  { key: 'process.runtime.name', value: { stringValue: 'nodejs' } },
174
213
  { key: 'process.runtime.version', value: { stringValue: process.versions.node } },
175
- { key: 'os.type', value: { stringValue: process.platform } },
176
- { key: 'host.arch', value: { stringValue: process.arch } },
214
+ { key: 'os.type', value: { stringValue: normalizeOsType(process.platform) } },
215
+ { key: 'host.arch', value: { stringValue: normalizeHostArch(process.arch) } },
177
216
  ];
217
+ const deploymentEnv = resolveDeploymentEnvironment(env);
218
+ if (deploymentEnv) attrs.push({ key: 'deployment.environment.name', value: { stringValue: deploymentEnv } });
219
+ return attrs;
178
220
  }
179
221
 
180
222
  // ── Payload builders (pure — unit-tested) ─────────────────────────────────────