@aixle/insights 0.2.7-staging → 0.2.8-staging

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/README.md CHANGED
@@ -13,7 +13,7 @@ Two channels are published. Pick one deliberately — they are **not** interchan
13
13
  | | Production | Staging (QA) |
14
14
  |---|---|---|
15
15
  | Install | `npm i -g @aixle/insights` | `npm i -g @aixle/insights@staging` |
16
- | Version looks like | `0.2.0` | `0.2.1-staging` |
16
+ | Version looks like | `0.2.1` | `0.2.6-staging` |
17
17
  | Points at | the production API | the staging API |
18
18
  | Who should use it | **everyone** | QA validating unreleased work |
19
19
  | Stability | released, supported | may change or break without notice |
@@ -28,7 +28,7 @@ npx -y @aixle/insights init \
28
28
 
29
29
  # Or global install:
30
30
  npm i -g @aixle/insights
31
- aixle-insights --version # e.g. 0.2.0 (no suffix)
31
+ npm ls -g @aixle/insights # e.g. @aixle/insights@0.2.1 (no suffix)
32
32
  ```
33
33
 
34
34
  ### Staging — QA only
@@ -44,7 +44,7 @@ npx -y @aixle/insights@staging init \
44
44
 
45
45
  # Or global install:
46
46
  npm i -g @aixle/insights@staging
47
- aixle-insights --version # e.g. 0.2.1-staging (note the suffix)
47
+ npm ls -g @aixle/insights # e.g. @aixle/insights@0.2.6-staging (note the suffix)
48
48
  ```
49
49
 
50
50
  Point a staging build at the **staging** API host. Sending staging telemetry to production
@@ -53,14 +53,14 @@ pollutes production analytics.
53
53
  ### Which one do I have?
54
54
 
55
55
  ```bash
56
- aixle-insights --version # a -staging suffix means a QA build
56
+ npm ls -g @aixle/insights # a -staging suffix means a QA build
57
57
  npm view @aixle/insights dist-tags # what each channel currently resolves to
58
58
  ```
59
59
 
60
60
  Expected output — `latest` and `staging` move independently:
61
61
 
62
62
  ```
63
- { latest: '0.2.0', staging: '0.2.1-staging' }
63
+ { latest: '0.2.1', staging: '0.2.6-staging' }
64
64
  ```
65
65
 
66
66
  ### Switching back to production
@@ -73,8 +73,8 @@ Then re-run `init` against the production host, since credentials and the MCP en
73
73
  per-host.
74
74
 
75
75
  > **Why `npm install` never surprises you with a staging build:** `-staging` versions are semver
76
- > prereleases, and no ordinary version range resolves to a prerelease. `*`, `^0.2.0`, `~0.2.0`
77
- > and `>=0.1.0` all select `0.2.0` even when `0.2.1-staging` exists. Staging builds are
76
+ > prereleases, and no ordinary version range resolves to a prerelease. `*`, `^0.2.1`, `~0.2.1`
77
+ > and `>=0.1.0` all select `0.2.1` even when `0.2.6-staging` exists. Staging builds are
78
78
  > reachable only by exact version or the `staging` dist-tag.
79
79
 
80
80
  Maintainers: see [`../RELEASING.md`](../RELEASING.md) for how each channel is cut.
@@ -137,6 +137,45 @@ Once `init` succeeds it reports the bound org (`Credentials saved (organization
137
137
 
138
138
  > **Preferences caveat — this is non-obvious.** The web app's "current org" (the one you appear to be viewing) is normally the **last-used** org, remembered in your browser's `localStorage`. That is **not** the same as your **Default Organization** preference, which is what `init` reads. A multi-org user who has switched orgs in the UI but never explicitly set **Default Organization** in web Preferences has no server-side preference for `init` to use — so `init` will hit the org-selection error above until you either set the Default Organization preference or pass `--organization-id`.
139
139
 
140
+ ## Cursor hooks (optional)
141
+
142
+ **Claude Code needs nothing beyond `init`** — the MCP transcript reader already captures model, token counts (including cache tokens), prompt and assistant text, risk scan, and tool uses.
143
+
144
+ Cursor is different: its local store does not reliably record *which model* answered a turn, so events can land with `model: "unknown"`. Two things address it — the automatic `state.vscdb` fallback (DB90DV-540), and this opt-in hook forwarder for per-turn attribution captured as it happens:
145
+
146
+ ```bash
147
+ npx -y @aixle/insights init --hooks --tool-name cursor --host <host> --keycloak-url <realm>
148
+
149
+ aixle-insights verify-hooks # JSON: installed? queue depth?
150
+ aixle-insights uninstall-hooks # remove, restoring the ~/.cursor/hooks.json backup
151
+ ```
152
+
153
+ **Restart Cursor afterwards** — hooks are read at launch. The forwarder appends redacted payloads to `~/.aixle-insights/hooks-queue.ndjson`, which the next sync drains, so events arrive on the normal cycle rather than instantly.
154
+
155
+ > Editor-level hooks (`~/.claude/settings.json` `PostToolUse` / `Stop`) are a **fallback for environments that cannot run an MCP server**, not an upgrade. They carry no model, token, or cost data, so the MCP path is strictly better wherever it is available.
156
+
157
+ ## Ingest tokens and rotation
158
+
159
+ `init` mints an ingest token per tool — distinct from your Keycloak login — and stores it in the **OS keychain** (service `aixle-insights`), falling back to `~/.aixle-insights/credentials.json` at mode 0600 where no keychain is available. Tokens are `aixle_<64 hex>` and the server retains only a SHA-256 hash, so a token cannot be recovered after `init`; losing it means re-running `init`.
160
+
161
+ You never need to paste a token for the MCP path — `init` obtains its own.
162
+
163
+ > **Rotation policy: TBD.** There is no self-service rotation command and no documented expiry or cadence. Placeholders until an owner defines them:
164
+ >
165
+ > | | Placeholder |
166
+ > |---|---|
167
+ > | Token lifetime | _undefined — tokens do not self-expire today_ |
168
+ > | Rotation cadence | _TBD_ |
169
+ > | Who can revoke | _TBD — no CLI path; server-side only_ |
170
+
171
+ **If a token is revoked, rotated server-side, or invalidated by a redeploy**, every sync fails with `HTTP 401` while `health` still reports `authenticated: true` — that flag only covers the OIDC login. Clear the stored credential and re-run `init`; state files are preserved, so already-sent sessions stay deduped:
172
+
173
+ ```bash
174
+ security delete-generic-password -s "aixle-insights" -a "aixle-insights-ingest-credential" # macOS
175
+ rm -f ~/.aixle-insights/credentials.json
176
+ # then re-run init
177
+ ```
178
+
140
179
  ## First run and backfill
141
180
 
142
181
  Before you run `init`, the MCP server is a deliberate no-op: it does not read, buffer, or send anything, and it does not create any state files. Nothing is lost during this window — Claude Code's transcripts and Cursor's local telemetry stores are unaffected by whether `@aixle/insights` is watching them.
@@ -145,6 +184,49 @@ The moment `init` succeeds, this package's per-credential state starts from empt
145
184
 
146
185
  If the MCP has been installed but never connected, `aixle-insights health` (or the `status` MCP tool) reports `needs_init: true` with a human-readable `onboarding_message` explaining exactly this — install-but-uninitialized is not a state you need to worry about losing data in.
147
186
 
187
+ Once `init` succeeds it reports the bound org (`Credentials saved (organization <uuid>).`), and `aixle-insights health` (or the `aixle_insights_status` MCP tool) shows the bound `organization_id`.
188
+
189
+ > **Preferences caveat — this is non-obvious.** The web app's "current org" (the one you appear to be viewing) is normally the **last-used** org, remembered in your browser's `localStorage`. That is **not** the same as your **Default Organization** preference, which is what `init` reads. A multi-org user who has switched orgs in the UI but never explicitly set **Default Organization** in web Preferences has no server-side preference for `init` to use — so `init` will hit the org-selection error above until you either set the Default Organization preference or pass `--organization-id`.
190
+
191
+ ## Cursor hooks (optional)
192
+
193
+ **Claude Code needs nothing beyond `init`** — the MCP transcript reader already captures model, token counts (including cache tokens), prompt and assistant text, risk scan, and tool uses.
194
+
195
+ Cursor is different: its local store does not reliably record *which model* answered a turn, so events can land with `model: "unknown"`. Two things address it — the automatic `state.vscdb` fallback (DB90DV-540), and this opt-in hook forwarder for per-turn attribution captured as it happens:
196
+
197
+ ```bash
198
+ npx -y @aixle/insights init --hooks --tool-name cursor --host <host> --keycloak-url <realm>
199
+
200
+ aixle-insights verify-hooks # JSON: installed? queue depth?
201
+ aixle-insights uninstall-hooks # remove, restoring the ~/.cursor/hooks.json backup
202
+ ```
203
+
204
+ **Restart Cursor afterwards** — hooks are read at launch. The forwarder appends redacted payloads to `~/.aixle-insights/hooks-queue.ndjson`, which the next sync drains, so events arrive on the normal cycle rather than instantly.
205
+
206
+ > Editor-level hooks (`~/.claude/settings.json` `PostToolUse` / `Stop`) are a **fallback for environments that cannot run an MCP server**, not an upgrade. They carry no model, token, or cost data, so the MCP path is strictly better wherever it is available.
207
+
208
+ ## Ingest tokens and rotation
209
+
210
+ `init` mints an ingest token per tool — distinct from your Keycloak login — and stores it in the **OS keychain** (service `aixle-insights`), falling back to `~/.aixle-insights/credentials.json` at mode 0600 where no keychain is available. Tokens are `aixle_<64 hex>` and the server retains only a SHA-256 hash, so a token cannot be recovered after `init`; losing it means re-running `init`.
211
+
212
+ You never need to paste a token for the MCP path — `init` obtains its own.
213
+
214
+ > **Rotation policy: TBD.** There is no self-service rotation command and no documented expiry or cadence. Placeholders until an owner defines them:
215
+ >
216
+ > | | Placeholder |
217
+ > |---|---|
218
+ > | Token lifetime | _undefined — tokens do not self-expire today_ |
219
+ > | Rotation cadence | _TBD_ |
220
+ > | Who can revoke | _TBD — no CLI path; server-side only_ |
221
+
222
+ **If a token is revoked, rotated server-side, or invalidated by a redeploy**, every sync fails with `HTTP 401` while `health` still reports `authenticated: true` — that flag only covers the OIDC login. Clear the stored credential and re-run `init`; state files are preserved, so already-sent sessions stay deduped:
223
+
224
+ ```bash
225
+ security delete-generic-password -s "aixle-insights" -a "aixle-insights-ingest-credential" # macOS
226
+ rm -f ~/.aixle-insights/credentials.json
227
+ # then re-run init
228
+ ```
229
+
148
230
  ## Commands
149
231
 
150
232
  | Command | What it does |
@@ -156,7 +238,10 @@ If the MCP has been installed but never connected, `aixle-insights health` (or t
156
238
  | `aixle-insights init --host <url>` | Use an Aixle Insights API origin for token exchange. Remote hosts must use HTTPS; `http://localhost` and loopback addresses are allowed for local development. |
157
239
  | `aixle-insights init --insecure --host http://...` | Allow a remote plaintext HTTP host for a trusted non-production test endpoint. Prints a warning because tokens and telemetry can be exposed. |
158
240
  | `aixle-insights init --hooks --tool-name cursor` | Also install the Cursor-side hook forwarder (opt-in; requires Cursor restart). |
241
+ | `aixle-insights init --force` | Re-run `init` even when credentials already exist — re-mints ingest tokens and re-writes the MCP entry. Use after changing host, org, or realm. |
242
+ | `aixle-insights init --tool-name <tool>` | Scope `init` to a single connector. **Omit it** so one login covers both Claude Code and Cursor. |
159
243
  | `aixle-insights uninstall-hooks` | Remove the hook forwarder + restore `~/.cursor/hooks.json` backup. |
244
+ | `aixle-insights uninstall-cursor-mcp` | Remove (or restore from backup) only the aixle-insights entry in `~/.cursor/mcp.json`, leaving sibling MCP servers untouched. |
160
245
  | `aixle-insights verify-hooks` | Print hooks install status + queue depth as JSON. |
161
246
  | `aixle-insights health` | Multi-line diagnostic (credentials, sync, log path, state files). |
162
247
 
@@ -166,9 +251,14 @@ If the MCP has been installed but never connected, `aixle-insights health` (or t
166
251
  |---|---|
167
252
  | `AIXLE_INSIGHTS_API_URL` (deprecated: `DB90_API_URL`) | API origin for ingestion + MCP exchange (defaults to `http://localhost:3000`; `init --host` overrides). |
168
253
  | `KEYCLOAK_ISSUER` / `AIXLE_INSIGHTS_KEYCLOAK_ISSUER` (deprecated: `DB90_KEYCLOAK_ISSUER`) | Realm issuer URLs (`KEYCLOAK_ISSUER` is preferred on servers + CI). |
169
- | `AIXLE_INSIGHTS_KEYCLOAK_CLIENT_ID` (deprecated: `DB90_KEYCLOAK_CLIENT_ID`) | Defaults to `db90-web`; must allow device authorization in Keycloak. |
254
+ | `KEYCLOAK_CLIENT_ID` / `AIXLE_INSIGHTS_KEYCLOAK_CLIENT_ID` (deprecated: `DB90_KEYCLOAK_CLIENT_ID`) | Defaults to `aixle-insights-web`; must allow device authorization in Keycloak. |
170
255
  | `AIXLE_INSIGHTS_ORGANIZATION_ID` (deprecated: `DB90_ORGANIZATION_ID`) | Optional UUID scoping `init` to that org membership (header `X-Organization-ID`). |
171
256
  | `AIXLE_INSIGHTS_HOME` | Override the local state directory (defaults to `~/.aixle-insights/`). |
257
+ | `AIXLE_INSIGHTS_MCP_DISABLE_KEYTAR` (deprecated: `DB90_MCP_DISABLE_KEYTAR`) | Set to skip the OS keychain entirely and use the `credentials.json` fallback. Useful on headless Linux/CI/Docker where Secret Service is absent. |
258
+ | `AIXLE_INSIGHTS_MCP_USE_LOCAL_KEYCLOAK_DEFAULT` (deprecated: `DB90_MCP_USE_LOCAL_KEYCLOAK_DEFAULT`) | Default the Keycloak issuer to the local stack (`http://localhost:8080/realms/db90`) so `--keycloak-url` can be omitted during local development. |
259
+ | `AIXLE_INSIGHTS_CLAUDE_USER_CONFIG_PATH` (deprecated: `DB90_CLAUDE_USER_CONFIG_PATH`) | Override the path to `~/.claude.json` that `init` merges the MCP entry into. Primarily a test seam; also lets you target a non-default Claude Code profile. |
260
+
261
+ Also read, but not intended as user-facing configuration: `APPDATA` and `XDG_CONFIG_HOME` (platform config-directory discovery) and `NODE_ENV`.
172
262
 
173
263
  Remote API and ingest hosts must use `https://`. Plaintext `http://` is local-dev-only for `localhost`, `127.0.0.0/8`, and `[::1]`; those loopback URLs work without warnings. A remote `http://` host is rejected during `init` unless you pass `--insecure`, which should only be used for trusted non-production test endpoints and will print a warning because ingest tokens and telemetry can cross the network unencrypted.
174
264
 
@@ -186,11 +276,81 @@ Optional `~/.aixle-insights/config.json` accepts Cursor line-cost overrides (per
186
276
  }
187
277
  ```
188
278
 
189
- The file is optional — an absent `config.json` is the normal case and is silent. A file that is
190
- present but unusable is ignored entirely (every override falls back to its default) and a
191
- `config_parse_failed` line is written to `mcp.log`. That covers malformed JSON and valid JSON that
192
- isn't an object, including a **top-level array** — a common mistake when writing per-model rates.
193
- Nothing is printed to the terminal, so check the log if an override appears to have no effect.
279
+ ### Configuration reference
280
+
281
+ `~/.aixle-insights/config.json` is optional `init` does not create it. Every key is optional too.
282
+
283
+ | Key | Type | Purpose |
284
+ |---|---|---|
285
+ | `host` | string | Ingest API origin. Same value as `--host`. |
286
+ | `token` | string | Ingest token. Rarely needed — `init` stores tokens in the keychain instead, and this is the plaintext alternative. |
287
+ | `project_id` | string | Pin every event to one project instead of resolving from the git remote. |
288
+ | `cursor.line_costs.<model>` | `{ input_per_line, output_per_line }` | Per-model cost rates for Cursor line-based accounting. |
289
+
290
+ **Precedence:** CLI flag → environment variable → `config.json`. A flag always wins; the config file is the fallback of last resort. An absent `config.json` is the normal case and is silent. A file that is present but unusable — malformed JSON, or valid JSON that isn't an object, including a **top-level array** (a common mistake when writing per-model rates) — is ignored entirely: every override falls back to its default, and a `config_parse_failed` line is written to `mcp.log` rather than a crash. Nothing is printed to the terminal, so check the log if an override appears to have no effect.
291
+
292
+ Worked example — pin a project and override Cursor rates for two models:
293
+
294
+ ```json
295
+ {
296
+ "host": "https://staging.insights.aixle.com",
297
+ "project_id": "3f6c1e28-9b4a-4c7f-8d21-5ac0e7b91f04",
298
+ "cursor": {
299
+ "line_costs": {
300
+ "claude-sonnet-4-5": { "input_per_line": 0.0002, "output_per_line": 0.0008 },
301
+ "gpt-4o": { "input_per_line": 0.0001, "output_per_line": 0.0004 }
302
+ }
303
+ }
304
+ }
305
+ ```
306
+
307
+ ## Clean slate — full uninstall / reset
308
+
309
+ Use this when a breaking change lands, when switching between the production and staging channels, or when you want to prove a problem is not stale local state. Steps are ordered least to most destructive; stop wherever your problem clears.
310
+
311
+ **1. Remove editor integration**
312
+
313
+ ```bash
314
+ aixle-insights uninstall-hooks # Cursor hook forwarder + restore ~/.cursor/hooks.json backup
315
+ aixle-insights uninstall-cursor-mcp # remove only our entry from ~/.cursor/mcp.json
316
+ ```
317
+
318
+ Then delete the `mcpServers.aixle-insights` entry from `~/.claude.json` by hand — `init` writes it and there is no command to remove it.
319
+
320
+ **2. Clear credentials** — forces a fresh `init` and re-mints ingest tokens:
321
+
322
+ ```bash
323
+ security delete-generic-password -s "aixle-insights" -a "aixle-insights-ingest-credential" # macOS
324
+ rm -f ~/.aixle-insights/credentials.json
325
+ ```
326
+
327
+ **3. Reset sync state** — ⚠️ **this is the destructive one.** State files hold the watermarks and dedupe checkpoints. Deleting them makes the next sync treat all local history as new, so you get a **full re-backfill**. Ingest upserts by `metadata.session_id`, so you should get updates rather than duplicates, but volume will spike:
328
+
329
+ ```bash
330
+ rm -f ~/.aixle-insights/state-*.json
331
+ rm -f ~/.aixle-insights/state.lock # only if no `aixle-insights run` is alive — check with: pgrep -fa aixle-insights
332
+ rm -f ~/.aixle-insights/hooks-queue.ndjson # discards hook events not yet drained
333
+ ```
334
+
335
+ **4. Nuclear** — remove everything the package created locally:
336
+
337
+ ```bash
338
+ rm -rf ~/.aixle-insights/ # credentials, state, logs, hook queue
339
+ npm uninstall -g @aixle/insights
340
+ ```
341
+
342
+ Nothing here touches your Claude Code transcripts or Cursor's own store — those belong to the editors and are only ever read. Events already delivered to the server are unaffected; this is purely local state.
343
+
344
+ | Artifact | Created by | Removed in step |
345
+ |---|---|---|
346
+ | `~/.aixle-insights/credentials.json` / keychain entry | `init` | 2 |
347
+ | `~/.aixle-insights/state-<host>-<hash>.json` | first sync | 3 |
348
+ | `~/.aixle-insights/state.lock` | `run` | 3 |
349
+ | `~/.aixle-insights/hooks-queue.ndjson` | hook forwarder | 3 |
350
+ | `~/.aixle-insights/mcp.log`, `mcp.log.1` | any run | 4 |
351
+ | `~/.cursor/hooks.json` entry | `init --hooks` | 1 |
352
+ | `~/.cursor/mcp.json` entry | `init` | 1 |
353
+ | `mcpServers.aixle-insights` in `~/.claude.json` | `init` | 1 (manual) |
194
354
 
195
355
  ## Security
196
356
 
@@ -362,6 +522,10 @@ cd packages/tools
362
522
  npm rebuild better-sqlite3
363
523
  ```
364
524
 
525
+ ## Changelog
526
+
527
+ See [CHANGELOG.md](https://github.com/dualboot-partners/db90-rails/blob/develop/packages/tools/aixle-insights/CHANGELOG.md) — npm's registry page doesn't render this file directly, so it's linked here instead of duplicated.
528
+
365
529
  ## License
366
530
 
367
531
  MIT.
@@ -193,9 +193,11 @@ export function defaultKeycloakIssuer(ingestHost) {
193
193
  return "";
194
194
  }
195
195
  export function defaultKeycloakClientId() {
196
- return (readEnvWithDeprecatedAlias({
197
- current: "AIXLE_INSIGHTS_KEYCLOAK_CLIENT_ID",
198
- deprecated: "DB90_KEYCLOAK_CLIENT_ID",
199
- onDeprecatedUse: warnDeprecatedEnvVar,
200
- }) || "db90-web");
196
+ return (process.env["KEYCLOAK_CLIENT_ID"]?.trim() ||
197
+ readEnvWithDeprecatedAlias({
198
+ current: "AIXLE_INSIGHTS_KEYCLOAK_CLIENT_ID",
199
+ deprecated: "DB90_KEYCLOAK_CLIENT_ID",
200
+ onDeprecatedUse: warnDeprecatedEnvVar,
201
+ }) ||
202
+ "aixle-insights-web");
201
203
  }
package/dist/cli.js CHANGED
@@ -238,7 +238,7 @@ export async function runInit(cliArgs, deps) {
238
238
  }
239
239
  const kcIssuer = (cliArgs.keycloakUrl ?? runtime.defaultKeycloakIssuer(apiHost)).trim();
240
240
  if (!kcIssuer) {
241
- runtime.error("Error: Keycloak issuer is not configured. Pass --keycloak-url or set KEYCLOAK_ISSUER / AIXLE_INSIGHTS_KEYCLOAK_ISSUER (or deprecated DB90_KEYCLOAK_ISSUER).");
241
+ runtime.error("Error: Keycloak issuer is not configured. Pass --keycloak-url or set KEYCLOAK_ISSUER / AIXLE_INSIGHTS_KEYCLOAK_ISSUER.");
242
242
  return 1;
243
243
  }
244
244
  if (cliArgs.toolName !== undefined && !["claude_code", "cursor"].includes(cliArgs.toolName)) {
@@ -258,7 +258,7 @@ export async function runInit(cliArgs, deps) {
258
258
  });
259
259
  const exchangeOrganizationId = fromFlag || fromEnv;
260
260
  if (exchangeOrganizationId && !isValidOrganizationUuid(exchangeOrganizationId)) {
261
- runtime.error("Error: --organization-id / AIXLE_INSIGHTS_ORGANIZATION_ID (or deprecated DB90_ORGANIZATION_ID) must be a valid UUID (RFC 4122, version 1–5, variant per Aixle Insights API).");
261
+ runtime.error("Error: --organization-id / AIXLE_INSIGHTS_ORGANIZATION_ID must be a valid UUID (RFC 4122, version 1–5, variant per Aixle Insights API).");
262
262
  return 1;
263
263
  }
264
264
  const result = await runtime.loginAndPersistCredentials({
@@ -33,7 +33,7 @@ export declare function canonicalizeGitRemote(remote: string, verbose: boolean):
33
33
  */
34
34
  export declare function repoNameToGitRemoteCandidates(repoName: string): string[];
35
35
  export declare function lookupProjectByRepoName(repoName: string, host: string, token: string, verbose: boolean, allowInsecureHttp?: boolean): Promise<LookupResult | "not-found" | null>;
36
- /** Payload shape shared by db90-cursor and telemetry-mcp commit mappers. */
36
+ /** Payload shape shared by the Cursor and Claude commit mappers. */
37
37
  export interface CommitAttributionPayload {
38
38
  event_type?: string;
39
39
  project_id?: string;
package/dist/server.js CHANGED
@@ -160,7 +160,7 @@ async function authenticateHandler(args) {
160
160
  if (!kc) {
161
161
  return jsonContent({
162
162
  ok: false,
163
- error: "keycloakUrl or KEYCLOAK_ISSUER / AIXLE_INSIGHTS_KEYCLOAK_ISSUER (or deprecated DB90_KEYCLOAK_ISSUER) is required",
163
+ error: "keycloakUrl or KEYCLOAK_ISSUER / AIXLE_INSIGHTS_KEYCLOAK_ISSUER is required",
164
164
  });
165
165
  }
166
166
  const clientId = args.clientId?.trim() || defaultKeycloakClientId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aixle/insights",
3
- "version": "0.2.7-staging",
3
+ "version": "0.2.8-staging",
4
4
  "description": "stdio MCP server for AI coding-assistant telemetry — Claude transcript sync + Cursor SQLite ingest.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,7 @@
46
46
  "lint": "eslint . --max-warnings 0",
47
47
  "verify:cursor-dry-run": "tsx scripts/verify-cursor-dry-run.ts",
48
48
  "audit:local-stores": "tsx scripts/audit-local-stores.ts",
49
+ "nightly:resolve": "tsx scripts/nightly-release-resolve.ts",
49
50
  "dev": "tsx src/cli.ts",
50
51
  "prepublishOnly": "npm run build"
51
52
  },