@skill-map/cli 0.43.0 → 0.45.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.
Files changed (46) hide show
  1. package/dist/cli/tutorial/sm-master/SKILL.md +7 -9
  2. package/dist/cli/tutorial/sm-master/references/fixture-templates.md +1 -1
  3. package/dist/cli/tutorial/sm-master/references/tour-plugins.md +10 -9
  4. package/dist/cli/tutorial/sm-master/references/tour-settings.md +4 -4
  5. package/dist/cli/tutorial/sm-tutorial/SKILL.md +263 -369
  6. package/dist/cli.js +945 -256
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.js +21 -15
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel/index.d.ts +43 -6
  11. package/dist/kernel/index.js +21 -15
  12. package/dist/kernel/index.js.map +1 -1
  13. package/dist/ui/chunk-27WQPOXP.js +1 -0
  14. package/dist/ui/{chunk-DZBSELHN.js → chunk-43S72FTV.js} +1 -1
  15. package/dist/ui/chunk-555ST76V.js +1 -0
  16. package/dist/ui/{chunk-JPYAASHN.js → chunk-5AD5ZV4I.js} +3 -3
  17. package/dist/ui/{chunk-CFJBTDAA.js → chunk-5ITZXW3A.js} +1 -1
  18. package/dist/ui/chunk-A7PRWMQD.js +1021 -0
  19. package/dist/ui/chunk-CBI77N5U.js +123 -0
  20. package/dist/ui/chunk-F7I6KMHX.js +1 -0
  21. package/dist/ui/{chunk-77J7XU3Y.js → chunk-I5AX4U2N.js} +28 -28
  22. package/dist/ui/chunk-IUZRAD7S.js +1 -0
  23. package/dist/ui/{chunk-XOHD5XWA.js → chunk-IYM26L3O.js} +1 -1
  24. package/dist/ui/{chunk-SBCO7ZSP.js → chunk-LGFABCIA.js} +1 -1
  25. package/dist/ui/{chunk-IUDL3NDH.js → chunk-MFLFIA7C.js} +1 -1
  26. package/dist/ui/chunk-MS6B7344.js +315 -0
  27. package/dist/ui/chunk-P3SNMV4X.js +2 -0
  28. package/dist/ui/chunk-PZQHB7GS.js +4 -0
  29. package/dist/ui/chunk-QDUSFOBE.js +1 -0
  30. package/dist/ui/{chunk-YCR3XCIW.js → chunk-QNTAOR2L.js} +5 -5
  31. package/dist/ui/{chunk-CR3AANNX.js → chunk-S4S5ZMXJ.js} +1 -1
  32. package/dist/ui/{chunk-5WJRN3LD.js → chunk-T3IVIRRJ.js} +1 -1
  33. package/dist/ui/{chunk-HP375T2O.js → chunk-VGPYYAVI.js} +1 -1
  34. package/dist/ui/chunk-X227ITGS.js +499 -0
  35. package/dist/ui/index.html +1 -1
  36. package/dist/ui/main-ERCTR2PR.js +3 -0
  37. package/package.json +10 -9
  38. package/dist/ui/chunk-2IF446BS.js +0 -1
  39. package/dist/ui/chunk-DIKPNZUZ.js +0 -315
  40. package/dist/ui/chunk-KGCJINI6.js +0 -1
  41. package/dist/ui/chunk-PPE3P2JD.js +0 -1
  42. package/dist/ui/chunk-UOCACZLI.js +0 -123
  43. package/dist/ui/chunk-YL6SWAFJ.js +0 -1024
  44. package/dist/ui/main-VHFB7Q2D.js +0 -3
  45. /package/dist/ui/{chunk-C2YUQODZ.js → chunk-4SG4352Z.js} +0 -0
  46. /package/dist/ui/{chunk-VB56BUGO.js → chunk-WCABR6TI.js} +0 -0
package/dist/cli.js CHANGED
@@ -462,6 +462,11 @@ var claudeProvider = {
462
462
  // a Claude Code project. Provider-owned (replaces the old central
463
463
  // detection table in `src/core/config/active-provider.ts`).
464
464
  detect: { markers: [".claude"] },
465
+ // Authoring target for `sm tutorial`: Claude Code discovers skills under
466
+ // `.claude/skills/<name>/SKILL.md`, so a materialised tutorial folder
467
+ // lands there. This is the WRITE side of the territory the `classify`
468
+ // below READS.
469
+ scaffold: { skillDir: ".claude/skills" },
465
470
  // Vendor provider: Claude Code only reads its own `.claude/` territory
466
471
  // and ignores `.codex/` / Antigravity layouts at runtime. Gating the
467
472
  // classifier behind the active lens prevents the walker from inventing
@@ -912,75 +917,77 @@ var antigravityProvider = {
912
917
  classify() {
913
918
  return null;
914
919
  },
915
- // Seed catalog, PROVISIONAL, derived from the Gemini CLI slash-command
916
- // surface. The Google Developers Blog post that announced the Antigravity
917
- // CLI rollout on 2026-05-19 states verbatim: "The Antigravity CLI fully
918
- // replaces the Gemini CLI ... preserves the most critical Gemini CLI
919
- // features: Agent Skills, Hooks, Subagents, and Extensions (now rebranded
920
- // as Antigravity plugins) ... shares the same agent harness as Antigravity
921
- // 2.0, the new Antigravity desktop application." Since the four feature
922
- // pillars carry over 1:1 and the agent harness is shared, the operator's
923
- // built-in slash-command vocabulary is overwhelmingly likely to be
924
- // Gemini CLI's. We mirror the full 38-verb Gemini CLI catalog (plus its
925
- // four documented aliases: `dir`, `?`, `exit`, `bashes`) so a user file
926
- // that names a skill / command `help`, `clear`, `mcp`, etc. is flagged
927
- // immediately by `core/name-reserved` once the lens activates the catalog.
920
+ // Built-in slash-command catalog, captured verbatim from `agy /help`
921
+ // (Antigravity CLI v1.0.3). This REPLACES the earlier provisional list
922
+ // that mirrored Gemini CLI's verbs: `agy` ships its own surface. It
923
+ // DROPPED Gemini-only verbs (`vim`, `theme`, `terminal-setup`,
924
+ // `setup-github`, `bashes`, `shells`, `policies`, `extensions`, `about`,
925
+ // `auth`, `bug`, `chat`, `compress`, `docs`, `editor`, `ide`, `init`,
926
+ // `memory`, `restore`, `stats`, `tools`, `upgrade`, `?`, `dir`) and
927
+ // ADDED agent-first ones (`goal`, `grill-me`, `schedule`, `fast`, `btw`,
928
+ // `artifact`, `context`, `diff`, `fork`, `tasks`, `add-dir`, `credits`,
929
+ // `feedback`, `logout`, `open`, `planning`, `rename`, `statusline`,
930
+ // `title`, `usage`). Both the 35 primary verbs and the 8 documented
931
+ // aliases (`new`, `settings`, `quit`, `branch`, `switch`, `conversation`,
932
+ // `undo`, `quota`) are reserved: a user skill named after either is
933
+ // silently shadowed by the built-in once the catalog activates.
928
934
  //
929
- // The catalog is INACTIVE today: the analyzer keys on `node.provider`
930
- // and this Provider's `classify()` returns `null` for every path, so
931
- // no node carries `provider: 'antigravity'`. The seed lives here so
932
- // the day Antigravity grows its own on-disk kind (e.g. a vendor-specific
933
- // `.antigravity/commands/` directory beyond the open-standard
934
- // `.agents/skills/`) the catalog is already in place with no migration.
935
+ // Declared under the `skill` kind (NOT `command`): Antigravity has no
936
+ // vendor-specific command directory, its user slash-commands are skills
937
+ // (`.agents/skills/<name>/SKILL.md`, owned by the universal `agent-skills`
938
+ // Provider). The catalog is ACTIVE via the LENS SCOPE in
939
+ // `buildReservedNodePaths` (spec/architecture.md §Provider ·
940
+ // reservedNames): when `activeProvider === 'antigravity'` the orchestrator
941
+ // lends this `skill` catalog to `agent-skills` skill nodes, so a user
942
+ // `.agents/skills/goal/SKILL.md` is flagged because `/goal` is built-in.
935
943
  //
936
- // **Reconciliation marker**: the day Google's docs at
937
- // antigravity.google/docs publishes the authoritative slash-command
938
- // reference, replace this comment + array with the official list (and
939
- // bump the file's leading docblock to cite the new source URL).
944
+ // **Reconciliation marker**: re-capture from `agy /help` on each major
945
+ // Antigravity CLI release and bump the cited version above.
940
946
  reservedNames: {
941
- command: [
942
- "?",
943
- "about",
947
+ skill: [
948
+ "add-dir",
944
949
  "agents",
945
- "auth",
946
- "bashes",
947
- "bug",
948
- "chat",
950
+ "artifact",
951
+ "branch",
952
+ "btw",
953
+ "changelog",
949
954
  "clear",
950
- "commands",
951
- "compress",
955
+ "config",
956
+ "context",
957
+ "conversation",
952
958
  "copy",
953
- "dir",
954
- "directory",
955
- "docs",
956
- "editor",
959
+ "credits",
960
+ "diff",
957
961
  "exit",
958
- "extensions",
962
+ "fast",
963
+ "feedback",
964
+ "fork",
965
+ "goal",
966
+ "grill-me",
959
967
  "help",
960
968
  "hooks",
961
- "ide",
962
- "init",
969
+ "keybindings",
970
+ "logout",
963
971
  "mcp",
964
- "memory",
965
972
  "model",
973
+ "new",
974
+ "open",
966
975
  "permissions",
967
- "plan",
968
- "policies",
969
- "privacy",
976
+ "planning",
970
977
  "quit",
971
- "restore",
978
+ "quota",
979
+ "rename",
972
980
  "resume",
973
981
  "rewind",
982
+ "schedule",
974
983
  "settings",
975
- "setup-github",
976
- "shells",
977
984
  "skills",
978
- "stats",
979
- "terminal-setup",
980
- "theme",
981
- "tools",
982
- "upgrade",
983
- "vim"
985
+ "statusline",
986
+ "switch",
987
+ "tasks",
988
+ "title",
989
+ "undo",
990
+ "usage"
984
991
  ]
985
992
  }
986
993
  };
@@ -1145,6 +1152,13 @@ var agentSkillsProvider = {
1145
1152
  // (Antigravity adopted the open standard), so such projects auto-detect
1146
1153
  // as this universal lens. Provider-owned.
1147
1154
  detect: { markers: [".agents"] },
1155
+ // Authoring target for `sm tutorial`: the open standard discovers skills
1156
+ // under `.agents/skills/<name>/SKILL.md`. The same path is consumed by
1157
+ // Antigravity (adopted the standard rather than a `.gemini/` layout) and
1158
+ // OpenAI Codex (skills mirror the open standard), so `aka` surfaces both
1159
+ // names in the destination prompt to orient testers on those agents.
1160
+ // `aka` is display-only, `--for` still matches the `agent-skills` id.
1161
+ scaffold: { skillDir: ".agents/skills", aka: ["Antigravity", "OpenAI Codex"] },
1148
1162
  read: { extensions: [".md"], parser: "frontmatter-yaml" },
1149
1163
  kinds: {
1150
1164
  skill: {
@@ -3946,7 +3960,7 @@ var UPDATE_CHECK_TEXTS = {
3946
3960
  // package.json
3947
3961
  var package_default = {
3948
3962
  name: "@skill-map/cli",
3949
- version: "0.43.0",
3963
+ version: "0.45.0",
3950
3964
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
3951
3965
  license: "MIT",
3952
3966
  type: "module",
@@ -3997,25 +4011,25 @@ var package_default = {
3997
4011
  typecheck: "tsc --noEmit",
3998
4012
  lint: "eslint .",
3999
4013
  "lint:fix": "eslint . --fix",
4000
- reference: "node scripts/build-reference.js",
4001
- "reference:check": "node scripts/build-reference.js --check",
4002
4014
  "build-built-ins": "node ../scripts/generate-built-ins.js",
4003
4015
  "built-ins:check": "node ../scripts/generate-built-ins.js --check",
4004
4016
  prebuild: "pnpm build-built-ins",
4005
4017
  validate: "pnpm validate:compile && pnpm validate:test",
4006
- "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm reference:check && pnpm built-ins:check",
4018
+ "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm built-ins:check",
4007
4019
  "validate:test": "pnpm test:ci",
4008
4020
  pretest: "tsup",
4009
4021
  "pretest:coverage": "tsup",
4010
4022
  "pretest:coverage:html": "tsup",
4011
- test: "tsc --noEmit && node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4012
- "test:ci": "node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4013
- "test:coverage": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4014
- "test:coverage:html": "tsc --noEmit && SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4023
+ test: "tsc --noEmit && SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4024
+ "test:ci": "FORCE_COLOR=1 SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4025
+ "test:spec": "SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4026
+ "test:coverage": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4027
+ "test:coverage:html": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
4015
4028
  clean: "rm -rf dist coverage"
4016
4029
  },
4017
4030
  dependencies: {
4018
4031
  "@hono/node-server": "2.0.1",
4032
+ "@sentry/node": "10.55.0",
4019
4033
  "@skill-map/spec": "workspace:*",
4020
4034
  ajv: "8.18.0",
4021
4035
  "ajv-formats": "3.0.1",
@@ -4026,6 +4040,7 @@ var package_default = {
4026
4040
  "js-tiktoken": "1.0.21",
4027
4041
  "js-yaml": "4.1.1",
4028
4042
  kysely: "0.28.17",
4043
+ "posthog-node": "5.35.6",
4029
4044
  semver: "7.7.4",
4030
4045
  "smol-toml": "1.6.1",
4031
4046
  typanion: "3.14.0",
@@ -4096,6 +4111,7 @@ function ansiFor(opts) {
4096
4111
  }
4097
4112
 
4098
4113
  // cli/util/user-settings-store.ts
4114
+ import { randomUUID } from "crypto";
4099
4115
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4100
4116
  import { homedir } from "os";
4101
4117
  import { join as join2 } from "path";
@@ -4204,13 +4220,13 @@ function userSettingsFilePath() {
4204
4220
  return join2(homedir(), SKILL_MAP_DIR, FILENAME);
4205
4221
  }
4206
4222
  function defaultSettings() {
4207
- return { schemaVersion: SCHEMA_VERSION, updateCheck: {} };
4223
+ return { schemaVersion: SCHEMA_VERSION, updateCheck: {}, telemetry: {} };
4208
4224
  }
4209
4225
  function readUserSettings() {
4210
4226
  const parsed = readParsedFile();
4211
4227
  if (parsed === null) return defaultSettings();
4212
4228
  const validated = validateOrDefault(parsed);
4213
- return backfillUpdateCheck(validated);
4229
+ return backfillSubObjects(validated);
4214
4230
  }
4215
4231
  function readParsedFile() {
4216
4232
  const path = userSettingsFilePath();
@@ -4232,11 +4248,12 @@ function validateOrDefault(parsed) {
4232
4248
  if (!result.ok) return defaultSettings();
4233
4249
  return result.data;
4234
4250
  }
4235
- function backfillUpdateCheck(settings) {
4236
- if (settings.updateCheck === void 0) {
4237
- return { ...settings, updateCheck: {} };
4238
- }
4239
- return settings;
4251
+ function backfillSubObjects(settings) {
4252
+ return {
4253
+ ...settings,
4254
+ updateCheck: settings.updateCheck ?? {},
4255
+ telemetry: settings.telemetry ?? {}
4256
+ };
4240
4257
  }
4241
4258
  function writeUserSettings(patch) {
4242
4259
  const dir = join2(homedir(), SKILL_MAP_DIR);
@@ -4258,14 +4275,49 @@ function isUpdateCheckEnabled() {
4258
4275
  const settings = readUserSettings();
4259
4276
  return settings.updateCheck?.enabled !== false;
4260
4277
  }
4278
+ function isErrorTelemetryEnabled() {
4279
+ const settings = readUserSettings();
4280
+ return settings.telemetry?.errorsEnabled === true;
4281
+ }
4282
+ function isUsageCliTelemetryEnabled() {
4283
+ const settings = readUserSettings();
4284
+ return settings.telemetry?.usageCliEnabled === true;
4285
+ }
4286
+ function isUsageUiTelemetryEnabled() {
4287
+ const settings = readUserSettings();
4288
+ return settings.telemetry?.usageUiEnabled === true;
4289
+ }
4290
+ function readAnonymousId() {
4291
+ const settings = readUserSettings();
4292
+ return settings.telemetry?.anonymousId ?? null;
4293
+ }
4294
+ function ensureAnonymousId(generate = () => randomUUID()) {
4295
+ const existing = readAnonymousId();
4296
+ if (existing !== null && existing !== "") return existing;
4297
+ const id = generate();
4298
+ writeUserSettings({ telemetry: { anonymousId: id } });
4299
+ return id;
4300
+ }
4301
+ function hasTelemetryPromptBeenShown() {
4302
+ const settings = readUserSettings();
4303
+ return typeof settings.telemetry?.promptedAt === "number";
4304
+ }
4305
+ function hasSeenFirstRun() {
4306
+ const settings = readUserSettings();
4307
+ return typeof settings.telemetry?.firstRunAt === "number";
4308
+ }
4261
4309
  function mergeSettings(current, patch) {
4262
4310
  const merged = {
4263
4311
  schemaVersion: SCHEMA_VERSION,
4264
- updateCheck: { ...current.updateCheck ?? {} }
4312
+ updateCheck: { ...current.updateCheck ?? {} },
4313
+ telemetry: { ...current.telemetry ?? {} }
4265
4314
  };
4266
4315
  if (patch.updateCheck) {
4267
4316
  merged.updateCheck = { ...merged.updateCheck, ...patch.updateCheck };
4268
4317
  }
4318
+ if (patch.telemetry) {
4319
+ merged.telemetry = { ...merged.telemetry, ...patch.telemetry };
4320
+ }
4269
4321
  return merged;
4270
4322
  }
4271
4323
  function tryLoadValidators() {
@@ -4378,40 +4430,40 @@ var updateCheckHook = {
4378
4430
  };
4379
4431
 
4380
4432
  // plugins/built-ins.ts
4381
- var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.43.0" };
4382
- var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.43.0" };
4383
- var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.43.0" };
4384
- var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.43.0" };
4385
- var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.43.0" };
4386
- var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.43.0" };
4387
- var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.43.0" };
4388
- var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.43.0" };
4389
- var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.43.0" };
4390
- var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.43.0" };
4391
- var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.43.0" };
4392
- var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.43.0" };
4393
- var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.43.0" };
4394
- var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.43.0" };
4395
- var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.43.0" };
4396
- var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.43.0" };
4397
- var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.43.0" };
4398
- var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.43.0" };
4399
- var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.43.0" };
4400
- var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.43.0" };
4401
- var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.43.0" };
4402
- var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.43.0" };
4403
- var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.43.0" };
4404
- var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.43.0" };
4405
- var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.43.0" };
4406
- var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.43.0" };
4407
- var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.43.0" };
4408
- var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.43.0" };
4409
- var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.43.0" };
4410
- var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.43.0" };
4411
- var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.43.0" };
4412
- var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.43.0" };
4413
- var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.43.0" };
4414
- var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.43.0" };
4433
+ var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.45.0" };
4434
+ var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.45.0" };
4435
+ var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.45.0" };
4436
+ var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.45.0" };
4437
+ var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.45.0" };
4438
+ var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.45.0" };
4439
+ var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.45.0" };
4440
+ var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.45.0" };
4441
+ var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.45.0" };
4442
+ var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.45.0" };
4443
+ var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.45.0" };
4444
+ var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.45.0" };
4445
+ var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.45.0" };
4446
+ var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4447
+ var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.45.0" };
4448
+ var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4449
+ var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.45.0" };
4450
+ var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.45.0" };
4451
+ var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.45.0" };
4452
+ var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.45.0" };
4453
+ var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.45.0" };
4454
+ var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.45.0" };
4455
+ var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.45.0" };
4456
+ var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.45.0" };
4457
+ var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.45.0" };
4458
+ var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.45.0" };
4459
+ var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.45.0" };
4460
+ var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.45.0" };
4461
+ var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.45.0" };
4462
+ var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.45.0" };
4463
+ var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.45.0" };
4464
+ var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.45.0" };
4465
+ var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.45.0" };
4466
+ var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.45.0" };
4415
4467
  var builtInBundles = [
4416
4468
  {
4417
4469
  id: "claude",
@@ -4943,6 +4995,341 @@ function defaultRuntimeContext() {
4943
4995
  return { cwd: process.cwd() };
4944
4996
  }
4945
4997
 
4998
+ // cli/telemetry/first-run-prompt.ts
4999
+ import { createInterface } from "readline/promises";
5000
+
5001
+ // cli/i18n/telemetry.texts.ts
5002
+ var TELEMETRY_PROMPT_TEXTS = {
5003
+ // Header + body of the one-time question (glyph `ℹ` added by the renderer).
5004
+ title: "Anonymous error and usage reporting",
5005
+ intro: [
5006
+ "skill-map can send anonymous reports to help fix bugs and decide what to",
5007
+ "build next. No personal information is ever sent: not your files or their",
5008
+ "contents, not your folder or home paths, not your settings."
5009
+ ],
5010
+ question: "Enable anonymous error and usage reporting?",
5011
+ answerYes: "[Y]es",
5012
+ answerNo: "[n]o",
5013
+ answerDetails: "[d]etails",
5014
+ // Disclosure shown on `[d]etails`, then the question is re-asked.
5015
+ detailsSentTitle: "Sent, only if you turn this on",
5016
+ detailsSent: [
5017
+ "crashes: error name, code, message, and a path-stripped stack trace",
5018
+ "usage: the command you ran and its flag names (never their values)",
5019
+ "usage: which built-in extractors ran, and which UI views you opened",
5020
+ "cli version, node major, os, arch, and a random anonymous id"
5021
+ ],
5022
+ detailsNeverTitle: "Never sent",
5023
+ detailsNever: [
5024
+ "your files, their contents, frontmatter, annotations",
5025
+ "absolute paths, hostname, your username, ip address",
5026
+ "your settings values or any flag values"
5027
+ ],
5028
+ detailsHint: "Turn error reports, CLI usage, and UI usage on or off independently in Settings, or force everything off with SKILL_MAP_TELEMETRY=0.",
5029
+ // Confirmation lines (glyph added by the renderer).
5030
+ enabled: "Telemetry on. Thanks. Turn error reports, CLI usage, and UI usage off independently in Settings.",
5031
+ disabled: "Telemetry off. You can turn any of it on later in Settings."
5032
+ };
5033
+
5034
+ // public-config.ts
5035
+ var SENTRY_DSN_NODE = "https://8b73dbb2563da4b77def12ce5ee46e75@o4511475590037504.ingest.de.sentry.io/4511475708002384";
5036
+ var POSTHOG_KEY_NODE = "phc_vMX3PcNeDsacWNg2hYEbKVXDijSWcjKFzabCkzU7RNEr";
5037
+ var POSTHOG_HOST = "https://eu.i.posthog.com";
5038
+
5039
+ // core/telemetry/scrub.ts
5040
+ var HOME_PLACEHOLDER = "<HOME>";
5041
+ var STRIPPED_ENVELOPE_KEYS = ["server_name", "user"];
5042
+ var HOME_PATTERNS = [
5043
+ // Windows: drive + Users + one user segment (back- or forward-slash).
5044
+ /[A-Za-z]:[\\/]Users[\\/][^\\/\s:*?"<>|]+/g,
5045
+ // POSIX user homes: /home/<user> or macOS /Users/<user>.
5046
+ /\/(?:home|Users)\/[^/\s:]+/g,
5047
+ // Root account home.
5048
+ /\/root(?=\/|\b)/g
5049
+ ];
5050
+ function scrubString(value) {
5051
+ let out = value;
5052
+ for (const pattern of HOME_PATTERNS) {
5053
+ out = out.replace(pattern, HOME_PLACEHOLDER);
5054
+ }
5055
+ return out;
5056
+ }
5057
+ function scrubEvent(event) {
5058
+ const walked = walk(event);
5059
+ if (walked !== null && typeof walked === "object" && !Array.isArray(walked)) {
5060
+ const record = walked;
5061
+ for (const key of STRIPPED_ENVELOPE_KEYS) {
5062
+ if (key in record) delete record[key];
5063
+ }
5064
+ }
5065
+ return walked;
5066
+ }
5067
+ function walk(value) {
5068
+ if (typeof value === "string") return scrubString(value);
5069
+ if (Array.isArray(value)) return value.map((item) => walk(item));
5070
+ if (value !== null && typeof value === "object") {
5071
+ const out = {};
5072
+ for (const [key, child] of Object.entries(value)) {
5073
+ out[key] = walk(child);
5074
+ }
5075
+ return out;
5076
+ }
5077
+ return value;
5078
+ }
5079
+
5080
+ // cli/telemetry/telemetry-env.ts
5081
+ var TELEMETRY_ENV_VAR = "SKILL_MAP_TELEMETRY_ENV";
5082
+ function resolveTelemetryEnv() {
5083
+ const raw = process.env[TELEMETRY_ENV_VAR];
5084
+ if (raw === void 0 || raw.trim() === "" || raw === "prod" || raw === "production") {
5085
+ return "prod";
5086
+ }
5087
+ return "dev";
5088
+ }
5089
+
5090
+ // cli/telemetry/sentry-init.ts
5091
+ var KILL_SWITCH_ENV = "SKILL_MAP_TELEMETRY";
5092
+ var sdk = null;
5093
+ function isCliDsnConfigured() {
5094
+ return SENTRY_DSN_NODE !== "";
5095
+ }
5096
+ function isTelemetryForcedOff() {
5097
+ return process.env[KILL_SWITCH_ENV] === "0";
5098
+ }
5099
+ function isTelemetryActive(dsn) {
5100
+ if (isTelemetryForcedOff()) return false;
5101
+ if (dsn === "") return false;
5102
+ return isErrorTelemetryEnabled();
5103
+ }
5104
+ async function initSentryCli(version, loadSdk = () => import("@sentry/node")) {
5105
+ if (sdk) return;
5106
+ if (!isTelemetryActive(SENTRY_DSN_NODE)) return;
5107
+ const Sentry = await loadSdk();
5108
+ Sentry.init({
5109
+ dsn: SENTRY_DSN_NODE,
5110
+ release: `@skill-map/cli@${version}`,
5111
+ environment: resolveTelemetryEnv(),
5112
+ // CLI and BFF share one Sentry project; the `surface` tag tells their
5113
+ // events apart in the shared issue stream.
5114
+ initialScope: { tags: { surface: "cli" } },
5115
+ // Errors only: do NOT register the OpenTelemetry ESM loader hooks. We
5116
+ // run no tracing / auto-instrumentation, and the hook calls the
5117
+ // deprecated `module.register()` (a `DEP0205` warning on Node >= 26 that
5118
+ // would print on every telemetry-on invocation). Disabling it keeps
5119
+ // stderr clean and skips the loader's startup cost.
5120
+ registerEsmLoaderHooks: false,
5121
+ defaultIntegrations: false,
5122
+ integrations: [
5123
+ Sentry.onUncaughtExceptionIntegration(),
5124
+ Sentry.onUnhandledRejectionIntegration()
5125
+ ],
5126
+ tracesSampleRate: 0,
5127
+ sendDefaultPii: false,
5128
+ beforeSend: (event) => scrubEvent(event)
5129
+ });
5130
+ sdk = Sentry;
5131
+ }
5132
+ function setTelemetryVerbTag(verb) {
5133
+ if (!sdk || verb === void 0 || verb === "") return;
5134
+ sdk.setTag("verb", verb);
5135
+ }
5136
+ async function closeSentryCli(timeoutMs = 2e3) {
5137
+ if (!sdk) return;
5138
+ try {
5139
+ await sdk.close(timeoutMs);
5140
+ } catch {
5141
+ }
5142
+ }
5143
+
5144
+ // cli/telemetry/usage-collector.ts
5145
+ var BUILT_IN_PLUGIN_IDS = /* @__PURE__ */ new Set([
5146
+ "claude",
5147
+ "antigravity",
5148
+ "openai",
5149
+ "agent-skills",
5150
+ "core"
5151
+ ]);
5152
+ var EXTERNAL_PLUGIN_PLACEHOLDER = "external_plugin";
5153
+ function qualifyExtensionForUsage(qualifiedId2) {
5154
+ const slash = qualifiedId2.indexOf("/");
5155
+ if (slash <= 0) return EXTERNAL_PLUGIN_PLACEHOLDER;
5156
+ const pluginId = qualifiedId2.slice(0, slash);
5157
+ return BUILT_IN_PLUGIN_IDS.has(pluginId) ? qualifiedId2 : EXTERNAL_PLUGIN_PLACEHOLDER;
5158
+ }
5159
+ function buildScanExtensionSet(executedExtensionIds) {
5160
+ const out = /* @__PURE__ */ new Set();
5161
+ for (const id of executedExtensionIds) {
5162
+ out.add(qualifyExtensionForUsage(id));
5163
+ }
5164
+ return [...out].sort();
5165
+ }
5166
+ function extractFlagNames(args2) {
5167
+ const out = /* @__PURE__ */ new Set();
5168
+ for (const arg of args2) {
5169
+ if (!arg.startsWith("-")) continue;
5170
+ const name = arg.replace(/^-+/, "").split("=")[0];
5171
+ if (name !== void 0 && name !== "") out.add(name);
5172
+ }
5173
+ return [...out].sort();
5174
+ }
5175
+ function cliVerbEventName(verb, knownVerbs) {
5176
+ return `cli.${knownVerbs.has(verb) ? verb : "unknown"}`;
5177
+ }
5178
+ function buildCliVerbProperties(flagNames, extensions) {
5179
+ const flags = [...new Set(flagNames)].sort();
5180
+ return extensions ? { flags, extensions } : { flags };
5181
+ }
5182
+ function envUsageProps(cliVersion) {
5183
+ return {
5184
+ cli_version: cliVersion,
5185
+ node_major: Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10),
5186
+ os: process.platform,
5187
+ arch: process.arch,
5188
+ environment: resolveTelemetryEnv()
5189
+ };
5190
+ }
5191
+
5192
+ // cli/telemetry/posthog-init.ts
5193
+ var client = null;
5194
+ function isUsageKeyConfigured() {
5195
+ return POSTHOG_KEY_NODE !== "";
5196
+ }
5197
+ function isUsageCliTelemetryActive(key) {
5198
+ if (isTelemetryForcedOff()) return false;
5199
+ if (key === "") return false;
5200
+ return isUsageCliTelemetryEnabled();
5201
+ }
5202
+ function scrubUsageEvent(event) {
5203
+ return event === null ? null : scrubEvent(event);
5204
+ }
5205
+ async function initUsageCli(loadSdk = () => import("posthog-node")) {
5206
+ if (client) return;
5207
+ if (!isUsageCliTelemetryActive(POSTHOG_KEY_NODE)) return;
5208
+ const { PostHog } = await loadSdk();
5209
+ client = new PostHog(POSTHOG_KEY_NODE, {
5210
+ host: POSTHOG_HOST,
5211
+ // Second line of defense behind the project-level IP drop: the client
5212
+ // never attaches an IP or geo, and never autocaptures.
5213
+ disableGeoip: true,
5214
+ before_send: scrubUsageEvent
5215
+ });
5216
+ }
5217
+ function captureUsage(event, properties) {
5218
+ if (client === null) return;
5219
+ const distinctId = readAnonymousId();
5220
+ if (distinctId === null) return;
5221
+ client.capture({
5222
+ distinctId,
5223
+ event,
5224
+ properties: { ...envUsageProps(VERSION), ...properties }
5225
+ });
5226
+ }
5227
+ var pendingScanExtensions = null;
5228
+ function setScanExtensions(extensions) {
5229
+ pendingScanExtensions = extensions;
5230
+ }
5231
+ function captureCliInvocation(verb, flagNames, knownVerbs) {
5232
+ const extensions = pendingScanExtensions;
5233
+ pendingScanExtensions = null;
5234
+ captureUsage(cliVerbEventName(verb, knownVerbs), buildCliVerbProperties(flagNames, extensions));
5235
+ }
5236
+ async function flushUsageCli(timeoutMs = 2e3) {
5237
+ if (client === null) return;
5238
+ try {
5239
+ await client.shutdown(timeoutMs);
5240
+ } catch {
5241
+ }
5242
+ }
5243
+
5244
+ // cli/telemetry/first-run-prompt.ts
5245
+ function interpretConsentAnswer(raw) {
5246
+ const value = raw.trim().toLowerCase();
5247
+ if (value === "n" || value === "no") return "no";
5248
+ if (value === "d" || value === "details") return "details";
5249
+ return "yes";
5250
+ }
5251
+ function isPromptEligible(opts) {
5252
+ return opts.dsnConfigured && opts.isTTY && !opts.isCI && !opts.forcedOff && !opts.alreadyPrompted;
5253
+ }
5254
+ function liveGateInputs(stdout) {
5255
+ return {
5256
+ dsnConfigured: isCliDsnConfigured() || isUsageKeyConfigured(),
5257
+ isTTY: stdout.isTTY === true,
5258
+ isCI: Boolean(process.env["CI"]),
5259
+ forcedOff: isTelemetryForcedOff(),
5260
+ alreadyPrompted: hasTelemetryPromptBeenShown()
5261
+ };
5262
+ }
5263
+ function renderConsent(ansi) {
5264
+ const t = TELEMETRY_PROMPT_TEXTS;
5265
+ const answerLine = ` ${t.question} ${ansi.bold(t.answerYes)} ${t.answerNo} ${ansi.dim(t.answerDetails)} `;
5266
+ return {
5267
+ question: [
5268
+ ` ${ansi.cyan("\u2139")} ${ansi.bold(t.title)}`,
5269
+ ...t.intro.map((line) => ` ${line}`),
5270
+ "",
5271
+ answerLine
5272
+ ].join("\n"),
5273
+ reprompt: answerLine,
5274
+ details: [
5275
+ "",
5276
+ ` ${t.detailsSentTitle}`,
5277
+ ...t.detailsSent.map((line) => ` ${ansi.dim("\u2192")} ${line}`),
5278
+ ` ${t.detailsNeverTitle}`,
5279
+ ...t.detailsNever.map((line) => ` ${ansi.red("\u2715")} ${line}`),
5280
+ "",
5281
+ ` ${ansi.dim(t.detailsHint)}`,
5282
+ ""
5283
+ ].join("\n"),
5284
+ enabled: ` ${ansi.green("\u2713")} ${t.enabled}
5285
+ `,
5286
+ disabled: ` ${ansi.cyan("\u2139")} ${t.disabled}
5287
+ `
5288
+ };
5289
+ }
5290
+ async function readConsentDecision(rl, stdout, rendered) {
5291
+ let answer = interpretConsentAnswer(await rl.question(rendered.question));
5292
+ while (answer === "details") {
5293
+ stdout.write(rendered.details);
5294
+ answer = interpretConsentAnswer(await rl.question(rendered.reprompt));
5295
+ }
5296
+ return answer === "yes";
5297
+ }
5298
+ async function runConsentPrompt(stdin, stdout, nowMs) {
5299
+ const rendered = renderConsent(
5300
+ ansiFor({ isTTY: stdout.isTTY === true, noColorFlag: false })
5301
+ );
5302
+ const rl = createInterface({ input: stdin, output: stdout });
5303
+ try {
5304
+ const consented = await readConsentDecision(rl, stdout, rendered);
5305
+ writeUserSettings({
5306
+ telemetry: {
5307
+ errorsEnabled: consented,
5308
+ usageCliEnabled: consented,
5309
+ usageUiEnabled: consented,
5310
+ promptedAt: nowMs
5311
+ }
5312
+ });
5313
+ if (consented) ensureAnonymousId();
5314
+ stdout.write(consented ? rendered.enabled : rendered.disabled);
5315
+ } catch {
5316
+ } finally {
5317
+ rl.close();
5318
+ }
5319
+ }
5320
+ async function maybeRunFirstRunPrompt({
5321
+ stdin = process.stdin,
5322
+ stdout = process.stdout,
5323
+ nowMs = Date.now()
5324
+ } = {}) {
5325
+ if (!isPromptEligible(liveGateInputs(stdout))) return;
5326
+ if (!hasSeenFirstRun()) {
5327
+ writeUserSettings({ telemetry: { firstRunAt: nowMs } });
5328
+ return;
5329
+ }
5330
+ await runConsentPrompt(stdin, stdout, nowMs);
5331
+ }
5332
+
4946
5333
  // cli/commands/bump.ts
4947
5334
  import { Command as Command2, Option as Option2 } from "clipanion";
4948
5335
 
@@ -5625,13 +6012,13 @@ var CONSENT_TEXTS = {
5625
6012
  };
5626
6013
 
5627
6014
  // cli/util/confirm.ts
5628
- import { createInterface } from "readline";
6015
+ import { createInterface as createInterface2 } from "readline";
5629
6016
  var YES_PATTERN = new RegExp(UTIL_TEXTS.confirmYesPatternSource, "i");
5630
6017
  var NO_PATTERN = new RegExp(UTIL_TEXTS.confirmNoPatternSource, "i");
5631
6018
  async function confirm(question, streams, opts) {
5632
6019
  const defaultAnswer = opts?.defaultAnswer ?? "no";
5633
6020
  const suffix = defaultAnswer === "yes" ? UTIL_TEXTS.confirmPromptSuffixDefaultYes : UTIL_TEXTS.confirmPromptSuffix;
5634
- const rl = createInterface({ input: streams.stdin, output: streams.stderr });
6021
+ const rl = createInterface2({ input: streams.stdin, output: streams.stderr });
5635
6022
  try {
5636
6023
  const answer = await new Promise(
5637
6024
  (resolveP) => rl.question(`${question}${suffix}`, resolveP)
@@ -7904,7 +8291,7 @@ var SqliteStorageAdapter = class {
7904
8291
  /**
7905
8292
  * Access the underlying Kysely instance.
7906
8293
  *
7907
- * Test-only escape hatch (per AGENTS.md § Kernel boundaries, tests
8294
+ * Test-only escape hatch (per context/kernel.md §Kernel boundaries, tests
7908
8295
  * are the documented exception). CLI commands MUST consume the
7909
8296
  * adapter through the namespaced port surfaces (`port.<namespace>.*`
7910
8297
  * or `port.transaction(...)`); reaching for this getter from a
@@ -10243,8 +10630,8 @@ var DEFAULT_READ_CONFIG = Object.freeze({
10243
10630
  });
10244
10631
  function resolveProviderWalk(provider) {
10245
10632
  if (provider.walk) {
10246
- const walk2 = provider.walk.bind(provider);
10247
- return walk2;
10633
+ const walk3 = provider.walk.bind(provider);
10634
+ return walk3;
10248
10635
  }
10249
10636
  const read = provider.read ?? DEFAULT_READ_CONFIG;
10250
10637
  return (roots, options) => {
@@ -13870,8 +14257,7 @@ var HelpCommand = class extends Command15 {
13870
14257
 
13871
14258
  Formats:
13872
14259
  human (default): pretty terminal output.
13873
- md : canonical markdown. context/cli-reference.md is
13874
- regenerated from this and CI fails on drift.
14260
+ md : canonical markdown for documentation.
13875
14261
  json : structured surface dump per spec/cli-contract.md.
13876
14262
  `
13877
14263
  });
@@ -15508,11 +15894,11 @@ import { join as join13, relative as relative4, sep as sep4 } from "path";
15508
15894
  function discoverOrphanSidecars(roots, shouldSkip) {
15509
15895
  const out = [];
15510
15896
  for (const root of roots) {
15511
- walk(root, root, shouldSkip ?? (() => false), out);
15897
+ walk2(root, root, shouldSkip ?? (() => false), out);
15512
15898
  }
15513
15899
  return out;
15514
15900
  }
15515
- function walk(root, current, shouldSkip, out) {
15901
+ function walk2(root, current, shouldSkip, out) {
15516
15902
  let entries;
15517
15903
  try {
15518
15904
  entries = readdirSync7(current, { withFileTypes: true, encoding: "utf8" });
@@ -15525,7 +15911,7 @@ function walk(root, current, shouldSkip, out) {
15525
15911
  if (shouldSkip(rel)) continue;
15526
15912
  if (entry.isSymbolicLink()) continue;
15527
15913
  if (entry.isDirectory()) {
15528
- walk(root, full, shouldSkip, out);
15914
+ walk2(root, full, shouldSkip, out);
15529
15915
  continue;
15530
15916
  }
15531
15917
  if (!entry.isFile()) continue;
@@ -16149,7 +16535,8 @@ function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
16149
16535
  const reservedNodePaths = buildReservedNodePaths(
16150
16536
  nodes,
16151
16537
  kindRegistry,
16152
- reservedNamesByProviderKind
16538
+ reservedNamesByProviderKind,
16539
+ activeProvider
16153
16540
  );
16154
16541
  return { kindRegistry, providerResolution, activeProvider, reservedNodePaths };
16155
16542
  }
@@ -16180,19 +16567,23 @@ function indexReservedNames(provider, out) {
16180
16567
  }
16181
16568
  }
16182
16569
  }
16183
- function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind) {
16570
+ function buildReservedNodePaths(nodes, kindRegistry, reservedNamesByProviderKind, activeProvider) {
16184
16571
  const out = /* @__PURE__ */ new Set();
16185
16572
  for (const node of nodes) {
16186
- const key = `${node.provider}/${node.kind}`;
16187
- const reservedSet = reservedNamesByProviderKind.get(key);
16188
- if (!reservedSet || reservedSet.size === 0) continue;
16189
- const ids = deriveNodeIdentifiers(node, kindRegistry.get(key));
16190
- if (ids.some((id) => reservedSet.has(id))) {
16573
+ const selfKey = `${node.provider}/${node.kind}`;
16574
+ const selfReserved = reservedNamesByProviderKind.get(selfKey);
16575
+ const lensReserved = activeProvider && activeProvider !== node.provider ? reservedNamesByProviderKind.get(`${activeProvider}/${node.kind}`) : void 0;
16576
+ if (!hasEntries(selfReserved) && !hasEntries(lensReserved)) continue;
16577
+ const ids = deriveNodeIdentifiers(node, kindRegistry.get(selfKey));
16578
+ if (ids.some((id) => selfReserved?.has(id) === true || lensReserved?.has(id) === true)) {
16191
16579
  out.add(node.path);
16192
16580
  }
16193
16581
  }
16194
16582
  return out;
16195
16583
  }
16584
+ function hasEntries(set) {
16585
+ return set !== void 0 && set.size > 0;
16586
+ }
16196
16587
  function buildScanSetup(options) {
16197
16588
  const start = Date.now();
16198
16589
  const emitter = options.emitter ?? new InMemoryProgressEmitter();
@@ -16621,13 +17012,13 @@ var SCAN_RUNNER_TEXTS = {
16621
17012
  * Active-provider bootstrap: filesystem auto-detect found exactly
16622
17013
  * one marker and persisted the detected id to project settings.
16623
17014
  */
16624
- activeProviderAutodetected: "Auto-detected activeProvider = {{id}} from filesystem markers; persisted to .skill-map/settings.json.",
17015
+ activeProviderAutodetected: "Auto-detected activeProvider = {{id}} from filesystem markers; persisted to .skill-map/settings.json.\n",
16625
17016
  /**
16626
17017
  * Active-provider bootstrap: persistence of the auto-detected id
16627
17018
  * failed (permission, disk full, etc). Non-fatal; the scan
16628
17019
  * continues with the value in memory for this run.
16629
17020
  */
16630
- activeProviderPersistFailed: "Auto-detected activeProvider = {{id}}, but persisting to .skill-map/settings.json failed: {{message}}. Run `sm config set activeProvider {{id}}` manually to make the choice sticky.",
17021
+ activeProviderPersistFailed: "Auto-detected activeProvider = {{id}}, but persisting to .skill-map/settings.json failed: {{message}}. Run `sm config set activeProvider {{id}}` manually to make the choice sticky.\n",
16631
17022
  /**
16632
17023
  * Active-provider bootstrap: ambiguous detection (2+ markers
16633
17024
  * present), interactive prompt header. Follows
@@ -16754,7 +17145,7 @@ function safeStat(path) {
16754
17145
  }
16755
17146
 
16756
17147
  // core/runtime/active-provider-bootstrap.ts
16757
- import { createInterface as createInterface2 } from "readline";
17148
+ import { createInterface as createInterface3 } from "readline";
16758
17149
  import { isAbsolute as isAbsolute9, join as join16 } from "path";
16759
17150
  async function bootstrapActiveProvider(opts) {
16760
17151
  const fromCwd = resolveActiveProvider(opts.cwd, opts.providers);
@@ -16910,7 +17301,7 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
16910
17301
  );
16911
17302
  }
16912
17303
  stderr.write(lines.join("\n") + "\n");
16913
- const rl = createInterface2({ input: stdin, output: stderr });
17304
+ const rl = createInterface3({ input: stdin, output: stderr });
16914
17305
  try {
16915
17306
  const answer = await new Promise(
16916
17307
  (resolveP) => rl.question(SCAN_RUNNER_TEXTS.activeProviderPromptInput, resolveP)
@@ -16929,7 +17320,7 @@ async function promptForLens(detected, stdin, stderr, warnGlyph) {
16929
17320
 
16930
17321
  // core/sqlite/db-drift-reset.ts
16931
17322
  import { existsSync as existsSync23 } from "fs";
16932
- import { createInterface as createInterface3 } from "readline";
17323
+ import { createInterface as createInterface4 } from "readline";
16933
17324
  import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
16934
17325
 
16935
17326
  // core/sqlite/i18n/db-drift.texts.ts
@@ -16996,7 +17387,7 @@ async function askDriftReset(dbVersion, policy) {
16996
17387
  hint: dim(DB_DRIFT_TEXTS.driftPromptHint)
16997
17388
  })
16998
17389
  );
16999
- const rl = createInterface3({ input: policy.stdin, output: policy.stderr });
17390
+ const rl = createInterface4({ input: policy.stdin, output: policy.stderr });
17000
17391
  try {
17001
17392
  const answer = await new Promise(
17002
17393
  (resolveP) => rl.question(DB_DRIFT_TEXTS.driftPromptQuestion, resolveP)
@@ -17032,9 +17423,9 @@ async function runScanForCommand(opts) {
17032
17423
  const { cfg, ignoreFilter, strict, effectiveRoots } = scanInputs;
17033
17424
  let referenceablePaths;
17034
17425
  if (cfg.scan.referencePaths.length > 0) {
17035
- const walk2 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
17036
- referenceablePaths = walk2.paths;
17037
- emitReferenceWalkAdvisory(walk2, opts);
17426
+ const walk3 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
17427
+ referenceablePaths = walk3.paths;
17428
+ emitReferenceWalkAdvisory(walk3, opts);
17038
17429
  }
17039
17430
  const loadPrior = makePriorLoader(opts.noBuiltIns, strict);
17040
17431
  const jobsDir = defaultProjectJobsDir(ctx);
@@ -17096,11 +17487,11 @@ async function resolveActiveLens(opts, ctx, effectiveRoots, pluginRuntime, provi
17096
17487
  });
17097
17488
  return { kind: "ok", activeProvider: bootstrap.activeProvider };
17098
17489
  }
17099
- function emitReferenceWalkAdvisory(walk2, opts) {
17100
- if (walk2.truncated) {
17490
+ function emitReferenceWalkAdvisory(walk3, opts) {
17491
+ if (walk3.truncated) {
17101
17492
  opts.printer.warn(SCAN_RUNNER_TEXTS.referenceWalkTruncated);
17102
17493
  }
17103
- for (const missing of walk2.missingRoots) {
17494
+ for (const missing of walk3.missingRoots) {
17104
17495
  opts.printer.warn(
17105
17496
  tx(SCAN_RUNNER_TEXTS.referenceWalkMissingRoot, { path: missing })
17106
17497
  );
@@ -17279,7 +17670,8 @@ async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanW
17279
17670
  renameOps: outcome.renameOps,
17280
17671
  persistedTo: dbPath,
17281
17672
  dbPath,
17282
- strict
17673
+ strict,
17674
+ executedExtensionIds: outcome.extractorRuns.map((run) => run.extractorId)
17283
17675
  };
17284
17676
  }
17285
17677
  async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
@@ -17297,7 +17689,8 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
17297
17689
  renameOps: scanned.renameOps,
17298
17690
  persistedTo: null,
17299
17691
  dbPath,
17300
- strict
17692
+ strict,
17693
+ executedExtensionIds: scanned.extractorRuns.map((run) => run.extractorId)
17301
17694
  };
17302
17695
  } catch (err) {
17303
17696
  return { kind: "scan-error", message: formatErrorMessage(err) };
@@ -17318,7 +17711,7 @@ var INIT_TEXTS = {
17318
17711
  * scan never inherits stale rows from a pre-current schema.
17319
17712
  */
17320
17713
  removedPriorDb: "{{glyph}} Removed prior DB at {{path}} (--force reset)\n",
17321
- runningFirstScan: "Running first scan...\n",
17714
+ runningFirstScan: "\nRunning first scan...\n",
17322
17715
  configLoadFailure: "{{glyph}} sm init: {{message}}\n",
17323
17716
  scanFailed: "{{glyph}} sm init: scan failed: {{message}}\n",
17324
17717
  firstScanSummary: "{{glyph}} First scan: {{nodes}} node{{nodesPlural}}, {{links}} link{{linksPlural}}, {{issues}} issue{{issuesPlural}}.\n",
@@ -20221,6 +20614,8 @@ var TogglePluginsBase = class extends SmCommand {
20221
20614
  targets = lockError;
20222
20615
  const keys = expandToKeys(targets);
20223
20616
  await this.#persistKeys(keys, enabled);
20617
+ const set = buildScanExtensionSet(keys);
20618
+ captureUsage("plugin.apply", enabled ? { enabled: set, disabled: [] } : { enabled: [], disabled: set });
20224
20619
  this.#renderSuccess(keys, enabled);
20225
20620
  return ExitCode.Ok;
20226
20621
  }
@@ -21183,6 +21578,29 @@ function stripFrontmatterFence(text) {
21183
21578
  }
21184
21579
  var REFRESH_COMMANDS = [RefreshCommand];
21185
21580
 
21581
+ // cli/i18n/intentional-fail.texts.ts
21582
+ var INTENTIONAL_FAIL_TEXTS = {
21583
+ triggering: "Triggering an intentional uncaught error to exercise Sentry error reporting...",
21584
+ errorMessage: "skill-map intentional failure (Sentry self-test)"
21585
+ };
21586
+
21587
+ // cli/commands/intentional-fail.ts
21588
+ var IntentionalFailCommand = class extends SmCommand {
21589
+ static paths = [["intentional-fail"]];
21590
+ // No `static usage` on purpose: that is what keeps the verb out of every
21591
+ // help / reference surface Clipanion drives from command definitions.
21592
+ // A self-test crash has no meaningful "done in <...>" line.
21593
+ emitElapsed = false;
21594
+ async run() {
21595
+ this.printer.warn(INTENTIONAL_FAIL_TEXTS.triggering);
21596
+ setTimeout(() => {
21597
+ throw new Error(INTENTIONAL_FAIL_TEXTS.errorMessage);
21598
+ }, 0);
21599
+ await new Promise((resolve40) => setTimeout(resolve40, 5e3));
21600
+ return 1;
21601
+ }
21602
+ };
21603
+
21186
21604
  // cli/commands/scan.ts
21187
21605
  import { Command as Command31, Option as Option29 } from "clipanion";
21188
21606
 
@@ -21414,9 +21832,9 @@ function createWatcherRuntime(opts) {
21414
21832
  overrideMaxNodes: opts.maxNodesOverride ?? null
21415
21833
  };
21416
21834
  if (cfg.scan.referencePaths.length > 0) {
21417
- const walk2 = walkReferencePaths(cfg.scan.referencePaths, cwd);
21418
- if (walk2.paths.size > 0) {
21419
- runOptions.referenceablePaths = walk2.paths;
21835
+ const walk3 = walkReferencePaths(cfg.scan.referencePaths, cwd);
21836
+ if (walk3.paths.size > 0) {
21837
+ runOptions.referenceablePaths = walk3.paths;
21420
21838
  runOptions.cwd = cwd;
21421
21839
  }
21422
21840
  }
@@ -21994,7 +22412,11 @@ var ScanCommand = class extends SmCommand {
21994
22412
  style,
21995
22413
  ...parsedMaxNodes.value !== void 0 ? { maxNodes: parsedMaxNodes.value } : {}
21996
22414
  });
21997
- return outcome.kind === "ok" ? this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict) : this.renderFailure(outcome);
22415
+ if (outcome.kind === "ok") {
22416
+ setScanExtensions(buildScanExtensionSet(outcome.executedExtensionIds));
22417
+ return this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict);
22418
+ }
22419
+ return this.renderFailure(outcome);
21998
22420
  }
21999
22421
  /**
22000
22422
  * Parse `--max-nodes <N>`. Returns either the integer value (or
@@ -22512,7 +22934,7 @@ import { WebSocketServer } from "ws";
22512
22934
  // server/app.ts
22513
22935
  import { Hono } from "hono";
22514
22936
  import { bodyLimit } from "hono/body-limit";
22515
- import { HTTPException as HTTPException15 } from "hono/http-exception";
22937
+ import { HTTPException as HTTPException16 } from "hono/http-exception";
22516
22938
 
22517
22939
  // core/config/service.ts
22518
22940
  var ConfigService = class {
@@ -22749,6 +23171,10 @@ var SERVER_TEXTS = {
22749
23171
  preferencesBodyEmpty: "Request body must contain at least one known preference (e.g. `updateCheck.enabled`).",
22750
23172
  preferencesUpdateCheckNotObject: '`updateCheck` must be an object (e.g. `{"updateCheck": {"enabled": false}}`).',
22751
23173
  preferencesUpdateCheckEnabledNotBoolean: "`updateCheck.enabled` must be a boolean.",
23174
+ preferencesTelemetryNotObject: '`telemetry` must be an object (e.g. `{"telemetry": {"errorsEnabled": true}}`).',
23175
+ preferencesTelemetryErrorsEnabledNotBoolean: "`telemetry.errorsEnabled` must be a boolean.",
23176
+ preferencesTelemetryUsageCliEnabledNotBoolean: "`telemetry.usageCliEnabled` must be a boolean.",
23177
+ preferencesTelemetryUsageUiEnabledNotBoolean: "`telemetry.usageUiEnabled` must be a boolean.",
22752
23178
  preferencesPersistFailed: "Could not persist preferences: {{message}}",
22753
23179
  // ---- project-preferences route (routes/project-preferences.ts) ----------
22754
23180
  //
@@ -22908,6 +23334,53 @@ function createSecurityHeaders() {
22908
23334
  };
22909
23335
  }
22910
23336
 
23337
+ // server/telemetry/sentry.ts
23338
+ import { HTTPException } from "hono/http-exception";
23339
+ var sdk2 = null;
23340
+ async function initSentryBff(version, loadSdk = () => import("@sentry/node")) {
23341
+ if (sdk2) return;
23342
+ if (!isTelemetryActive(SENTRY_DSN_NODE)) return;
23343
+ const Sentry = await loadSdk();
23344
+ Sentry.init({
23345
+ dsn: SENTRY_DSN_NODE,
23346
+ release: `@skill-map/cli@${version}`,
23347
+ environment: resolveTelemetryEnv(),
23348
+ // Shared project with the CLI; the `surface` tag separates the two.
23349
+ initialScope: { tags: { surface: "bff" } },
23350
+ // Errors only: skip the OpenTelemetry ESM loader hooks (we run no
23351
+ // tracing). They call the deprecated `module.register()` (a Node >= 26
23352
+ // DEP0205 warning) and add startup cost for nothing here.
23353
+ registerEsmLoaderHooks: false,
23354
+ defaultIntegrations: false,
23355
+ integrations: [],
23356
+ tracesSampleRate: 0,
23357
+ sendDefaultPii: false,
23358
+ beforeSend: (event) => scrubEvent(event)
23359
+ });
23360
+ sdk2 = Sentry;
23361
+ }
23362
+ function shouldCaptureError(err) {
23363
+ if (err instanceof HTTPException) return err.status >= 500;
23364
+ return true;
23365
+ }
23366
+ function createSentryRequestCapture() {
23367
+ return async function sentryRequestCapture(c, next) {
23368
+ try {
23369
+ await next();
23370
+ } catch (err) {
23371
+ const client2 = sdk2;
23372
+ if (client2 && shouldCaptureError(err)) {
23373
+ client2.withScope((scope) => {
23374
+ scope.setTag("route", c.req.routePath ?? c.req.path);
23375
+ scope.setTag("method", c.req.method);
23376
+ client2.captureException(err);
23377
+ });
23378
+ }
23379
+ throw err;
23380
+ }
23381
+ };
23382
+ }
23383
+
22911
23384
  // server/routes/annotations.ts
22912
23385
  var ENVELOPE_KIND = "annotations.registered";
22913
23386
  function registerAnnotationsRoute(app, deps) {
@@ -22924,10 +23397,10 @@ function registerAnnotationsRoute(app, deps) {
22924
23397
  }
22925
23398
 
22926
23399
  // server/routes/contributions.ts
22927
- import { HTTPException as HTTPException2 } from "hono/http-exception";
23400
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
22928
23401
 
22929
23402
  // server/util/parse-query.ts
22930
- import { HTTPException } from "hono/http-exception";
23403
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
22931
23404
  function parseCsv(value) {
22932
23405
  if (value === void 0) return [];
22933
23406
  return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -22936,7 +23409,7 @@ function parsePagination(query, defaults) {
22936
23409
  const offset = parseNonNegativeInt(query.offset, "offset", 0);
22937
23410
  const limit = parseNonNegativeInt(query.limit, "limit", defaults.limit);
22938
23411
  if (limit > defaults.max) {
22939
- throw new HTTPException(400, {
23412
+ throw new HTTPException2(400, {
22940
23413
  message: tx(SERVER_TEXTS.paginationLimitTooLarge, {
22941
23414
  value: limit,
22942
23415
  max: defaults.max
@@ -22950,7 +23423,7 @@ function parseBooleanFlag(value) {
22950
23423
  }
22951
23424
  function parseRequiredString(value, name) {
22952
23425
  if (typeof value !== "string" || value.length === 0) {
22953
- throw new HTTPException(400, {
23426
+ throw new HTTPException2(400, {
22954
23427
  message: tx(SERVER_TEXTS.queryRequiredString, { name })
22955
23428
  });
22956
23429
  }
@@ -22961,7 +23434,7 @@ function parseNonNegativeInt(raw, name, fallback) {
22961
23434
  const trimmed = raw.trim();
22962
23435
  const parsed = Number.parseInt(trimmed, 10);
22963
23436
  if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== trimmed) {
22964
- throw new HTTPException(400, {
23437
+ throw new HTTPException2(400, {
22965
23438
  message: tx(SERVER_TEXTS.paginationInvalidInteger, { name, value: raw })
22966
23439
  });
22967
23440
  }
@@ -22972,7 +23445,7 @@ function parseNonNegativeInt(raw, name, fallback) {
22972
23445
  var QUALIFIED_ID_SEGMENT = /^[A-Za-z0-9._-]+$/;
22973
23446
  function parseQualifiedIdSegment(value, name) {
22974
23447
  if (!QUALIFIED_ID_SEGMENT.test(value)) {
22975
- throw new HTTPException2(400, {
23448
+ throw new HTTPException3(400, {
22976
23449
  message: tx(SERVER_TEXTS.qualifiedIdMalformed, {
22977
23450
  name,
22978
23451
  value: sanitizeForTerminal(value)
@@ -23002,7 +23475,7 @@ function registerContributionsRoutes(app, deps) {
23002
23475
  (e) => e.pluginId === pluginId && e.extensionId === extensionId && e.contributionId === contributionId
23003
23476
  );
23004
23477
  if (!catalogEntry) {
23005
- throw new HTTPException2(404, {
23478
+ throw new HTTPException3(404, {
23006
23479
  message: tx(SERVER_TEXTS.contributionUnknown, {
23007
23480
  pluginId: sanitizeForTerminal(pluginId),
23008
23481
  extensionId: sanitizeForTerminal(extensionId),
@@ -23033,7 +23506,7 @@ function registerContributionsRoutes(app, deps) {
23033
23506
  }
23034
23507
 
23035
23508
  // server/routes/config.ts
23036
- import { HTTPException as HTTPException3 } from "hono/http-exception";
23509
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
23037
23510
 
23038
23511
  // server/envelope.ts
23039
23512
  var REST_ENVELOPE_SCHEMA_VERSION = "1";
@@ -23072,7 +23545,7 @@ function registerConfigRoute(app, deps) {
23072
23545
  try {
23073
23546
  loaded = deps.configService.get();
23074
23547
  } catch (err) {
23075
- throw new HTTPException3(500, { message: formatErrorMessage(err) });
23548
+ throw new HTTPException4(500, { message: formatErrorMessage(err) });
23076
23549
  }
23077
23550
  for (const warn of loaded.warnings) {
23078
23551
  log.warn(sanitizeForTerminal(warn));
@@ -23090,7 +23563,7 @@ function registerConfigRoute(app, deps) {
23090
23563
  }
23091
23564
 
23092
23565
  // server/routes/favorites.ts
23093
- import { HTTPException as HTTPException4 } from "hono/http-exception";
23566
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
23094
23567
 
23095
23568
  // server/path-codec.ts
23096
23569
  var PathCodecError = class extends Error {
@@ -23130,7 +23603,7 @@ function registerFavoritesRoutes(app, deps) {
23130
23603
  }
23131
23604
  );
23132
23605
  if (!result || !result.found) {
23133
- throw new HTTPException4(404, {
23606
+ throw new HTTPException5(404, {
23134
23607
  message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
23135
23608
  });
23136
23609
  }
@@ -23150,14 +23623,14 @@ function decodePath(pathB64) {
23150
23623
  return decodeNodePath(pathB64);
23151
23624
  } catch (err) {
23152
23625
  if (err instanceof PathCodecError) {
23153
- throw new HTTPException4(404, { message: SERVER_TEXTS.pathB64Malformed });
23626
+ throw new HTTPException5(404, { message: SERVER_TEXTS.pathB64Malformed });
23154
23627
  }
23155
23628
  throw err;
23156
23629
  }
23157
23630
  }
23158
23631
 
23159
23632
  // server/routes/graph.ts
23160
- import { HTTPException as HTTPException5 } from "hono/http-exception";
23633
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
23161
23634
  var DEFAULT_FORMAT2 = "ascii";
23162
23635
  var FORMAT_ID_PATTERN = /^[a-z0-9-]+$/;
23163
23636
  var FORMAT_ID_MAX = 32;
@@ -23165,7 +23638,7 @@ function registerGraphRoute(app, deps) {
23165
23638
  app.get("/api/graph", async (c) => {
23166
23639
  const format = c.req.query("format") ?? DEFAULT_FORMAT2;
23167
23640
  if (format.length > FORMAT_ID_MAX || !FORMAT_ID_PATTERN.test(format)) {
23168
- throw new HTTPException5(400, {
23641
+ throw new HTTPException6(400, {
23169
23642
  // Sanitize defensively, the regex above already rejects ANSI
23170
23643
  // and control bytes, but the message interpolates user input
23171
23644
  // and the BFF mirrors error envelopes into the server log.
@@ -23181,7 +23654,7 @@ function registerGraphRoute(app, deps) {
23181
23654
  const formatter = formatters.find((f) => f.formatId === format);
23182
23655
  if (!formatter) {
23183
23656
  const available = formatters.map((f) => f.formatId).sort().join(", ");
23184
- throw new HTTPException5(400, {
23657
+ throw new HTTPException6(400, {
23185
23658
  message: tx(SERVER_TEXTS.graphUnknownFormat, {
23186
23659
  format,
23187
23660
  available: available || "(none)"
@@ -23354,7 +23827,7 @@ function registerLinksRoute(app, deps) {
23354
23827
  }
23355
23828
 
23356
23829
  // server/routes/nodes.ts
23357
- import { HTTPException as HTTPException6 } from "hono/http-exception";
23830
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
23358
23831
 
23359
23832
  // server/node-body.ts
23360
23833
  import { constants as fsConstants2 } from "fs";
@@ -23465,7 +23938,7 @@ function registerNodesRoutes(app, deps) {
23465
23938
  nodePath = decodeNodePath(pathB64);
23466
23939
  } catch (err) {
23467
23940
  if (err instanceof PathCodecError) {
23468
- throw new HTTPException6(404, { message: SERVER_TEXTS.pathB64Malformed });
23941
+ throw new HTTPException7(404, { message: SERVER_TEXTS.pathB64Malformed });
23469
23942
  }
23470
23943
  throw err;
23471
23944
  }
@@ -23497,7 +23970,7 @@ function registerNodesRoutes(app, deps) {
23497
23970
  const contributions = result?.contributions ?? [];
23498
23971
  const tags = result?.tags ?? [];
23499
23972
  if (!bundle) {
23500
- throw new HTTPException6(404, {
23973
+ throw new HTTPException7(404, {
23501
23974
  message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
23502
23975
  });
23503
23976
  }
@@ -23607,11 +24080,11 @@ async function groupContributionsByPath(rows) {
23607
24080
  }
23608
24081
 
23609
24082
  // server/routes/plugins.ts
23610
- import { HTTPException as HTTPException8 } from "hono/http-exception";
24083
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
23611
24084
 
23612
24085
  // server/util/parse-body.ts
23613
24086
  import { Ajv2020 as Ajv20207 } from "ajv/dist/2020.js";
23614
- import { HTTPException as HTTPException7 } from "hono/http-exception";
24087
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
23615
24088
  function makeBodyValidator(schema, messages) {
23616
24089
  const ajv = new Ajv20207({ strict: false, allErrors: false });
23617
24090
  const validate = ajv.compile(schema);
@@ -23620,16 +24093,16 @@ function makeBodyValidator(schema, messages) {
23620
24093
  try {
23621
24094
  raw = await req.json();
23622
24095
  } catch {
23623
- throw new HTTPException7(400, { message: messages.notJson });
24096
+ throw new HTTPException8(400, { message: messages.notJson });
23624
24097
  }
23625
24098
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
23626
- throw new HTTPException7(400, { message: messages.notObject });
24099
+ throw new HTTPException8(400, { message: messages.notObject });
23627
24100
  }
23628
24101
  if (validate(raw)) {
23629
24102
  return raw;
23630
24103
  }
23631
24104
  const message = resolveErrorMessage(validate.errors, messages);
23632
- throw new HTTPException7(400, { message });
24105
+ throw new HTTPException8(400, { message });
23633
24106
  };
23634
24107
  }
23635
24108
  function resolveErrorMessage(errors, messages) {
@@ -23730,18 +24203,18 @@ function registerPluginsRoute(app, deps) {
23730
24203
  app.patch("/api/plugins/:id", async (c) => {
23731
24204
  const id = c.req.param("id");
23732
24205
  if (id.includes("/")) {
23733
- throw new HTTPException8(400, {
24206
+ throw new HTTPException9(400, {
23734
24207
  message: tx(SERVER_TEXTS.pluginsCascadeRouteQualifiedRejected, { id })
23735
24208
  });
23736
24209
  }
23737
24210
  const handle = findHandle(id, deps);
23738
24211
  if (!handle) {
23739
- throw new HTTPException8(404, {
24212
+ throw new HTTPException9(404, {
23740
24213
  message: tx(SERVER_TEXTS.pluginsUnknown, { id })
23741
24214
  });
23742
24215
  }
23743
24216
  if (isPluginLocked(id)) {
23744
- throw new HTTPException8(403, {
24217
+ throw new HTTPException9(403, {
23745
24218
  message: tx(SERVER_TEXTS.pluginsLocked, { id })
23746
24219
  });
23747
24220
  }
@@ -23755,18 +24228,18 @@ function registerPluginsRoute(app, deps) {
23755
24228
  const extensionId = c.req.param("extensionId");
23756
24229
  const handle = findHandle(bundleId, deps);
23757
24230
  if (!handle) {
23758
- throw new HTTPException8(404, {
24231
+ throw new HTTPException9(404, {
23759
24232
  message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
23760
24233
  });
23761
24234
  }
23762
24235
  if (!hasExtension(handle, extensionId)) {
23763
- throw new HTTPException8(404, {
24236
+ throw new HTTPException9(404, {
23764
24237
  message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
23765
24238
  });
23766
24239
  }
23767
24240
  const qualified = qualifiedExtensionId(bundleId, extensionId);
23768
24241
  if (isPluginLocked(qualified) || isPluginLocked(bundleId)) {
23769
- throw new HTTPException8(403, {
24242
+ throw new HTTPException9(403, {
23770
24243
  message: tx(SERVER_TEXTS.pluginsExtensionLocked, { bundleId, extensionId })
23771
24244
  });
23772
24245
  }
@@ -24042,7 +24515,7 @@ function hasExtension(handle, extensionId) {
24042
24515
  }
24043
24516
 
24044
24517
  // server/routes/preferences.ts
24045
- import { HTTPException as HTTPException9 } from "hono/http-exception";
24518
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
24046
24519
  function registerPreferencesRoute(app, _deps) {
24047
24520
  app.get("/api/preferences", (c) => {
24048
24521
  return c.json(buildEnvelope());
@@ -24055,20 +24528,43 @@ function registerPreferencesRoute(app, _deps) {
24055
24528
  }
24056
24529
  function buildEnvelope() {
24057
24530
  return {
24058
- updateCheck: { enabled: isUpdateCheckEnabled() }
24531
+ updateCheck: { enabled: isUpdateCheckEnabled() },
24532
+ telemetry: {
24533
+ errorsEnabled: isErrorTelemetryEnabled(),
24534
+ usageCliEnabled: isUsageCliTelemetryEnabled(),
24535
+ usageUiEnabled: isUsageUiTelemetryEnabled(),
24536
+ anonymousId: readAnonymousId(),
24537
+ environment: resolveTelemetryEnv()
24538
+ }
24059
24539
  };
24060
24540
  }
24061
24541
  function applyPatch(body) {
24062
- if (body.updateCheck && typeof body.updateCheck.enabled === "boolean") {
24063
- try {
24542
+ try {
24543
+ if (body.updateCheck && typeof body.updateCheck.enabled === "boolean") {
24064
24544
  writeUserSettings({ updateCheck: { enabled: body.updateCheck.enabled } });
24065
- } catch (err) {
24066
- throw new HTTPException9(400, {
24067
- message: tx(SERVER_TEXTS.preferencesPersistFailed, {
24068
- message: formatErrorMessage(err)
24069
- })
24070
- });
24071
24545
  }
24546
+ if (body.telemetry) {
24547
+ applyTelemetryPatch(body.telemetry);
24548
+ }
24549
+ } catch (err) {
24550
+ throw new HTTPException10(400, {
24551
+ message: tx(SERVER_TEXTS.preferencesPersistFailed, {
24552
+ message: formatErrorMessage(err)
24553
+ })
24554
+ });
24555
+ }
24556
+ }
24557
+ function applyTelemetryPatch(t) {
24558
+ if (typeof t.errorsEnabled === "boolean") {
24559
+ writeUserSettings({ telemetry: { errorsEnabled: t.errorsEnabled } });
24560
+ }
24561
+ if (typeof t.usageCliEnabled === "boolean") {
24562
+ writeUserSettings({ telemetry: { usageCliEnabled: t.usageCliEnabled } });
24563
+ if (t.usageCliEnabled) ensureAnonymousId();
24564
+ }
24565
+ if (typeof t.usageUiEnabled === "boolean") {
24566
+ writeUserSettings({ telemetry: { usageUiEnabled: t.usageUiEnabled } });
24567
+ if (t.usageUiEnabled) ensureAnonymousId();
24072
24568
  }
24073
24569
  }
24074
24570
  var PATCH_BODY_SCHEMA = {
@@ -24082,6 +24578,15 @@ var PATCH_BODY_SCHEMA = {
24082
24578
  properties: {
24083
24579
  enabled: { type: "boolean" }
24084
24580
  }
24581
+ },
24582
+ telemetry: {
24583
+ type: "object",
24584
+ additionalProperties: false,
24585
+ properties: {
24586
+ errorsEnabled: { type: "boolean" },
24587
+ usageCliEnabled: { type: "boolean" },
24588
+ usageUiEnabled: { type: "boolean" }
24589
+ }
24085
24590
  }
24086
24591
  }
24087
24592
  };
@@ -24092,12 +24597,16 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
24092
24597
  mapping: {
24093
24598
  ":minProperties": SERVER_TEXTS.preferencesBodyEmpty,
24094
24599
  "/updateCheck:type:object": SERVER_TEXTS.preferencesUpdateCheckNotObject,
24095
- "/updateCheck/enabled:type:boolean": SERVER_TEXTS.preferencesUpdateCheckEnabledNotBoolean
24600
+ "/updateCheck/enabled:type:boolean": SERVER_TEXTS.preferencesUpdateCheckEnabledNotBoolean,
24601
+ "/telemetry:type:object": SERVER_TEXTS.preferencesTelemetryNotObject,
24602
+ "/telemetry/errorsEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryErrorsEnabledNotBoolean,
24603
+ "/telemetry/usageCliEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryUsageCliEnabledNotBoolean,
24604
+ "/telemetry/usageUiEnabled:type:boolean": SERVER_TEXTS.preferencesTelemetryUsageUiEnabledNotBoolean
24096
24605
  }
24097
24606
  });
24098
24607
 
24099
24608
  // server/routes/project-ignore.ts
24100
- import { HTTPException as HTTPException10 } from "hono/http-exception";
24609
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
24101
24610
 
24102
24611
  // server/util/skillmapignore-io.ts
24103
24612
  import { existsSync as existsSync26, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
@@ -24184,12 +24693,12 @@ async function applyPatch2(deps, body) {
24184
24693
  for (const raw of body.patterns) {
24185
24694
  const t = raw.trim();
24186
24695
  if (t.length === 0) {
24187
- throw new HTTPException10(400, {
24696
+ throw new HTTPException11(400, {
24188
24697
  message: SERVER_TEXTS.projectIgnorePatternEmpty
24189
24698
  });
24190
24699
  }
24191
24700
  if (seen.has(t)) {
24192
- throw new HTTPException10(400, {
24701
+ throw new HTTPException11(400, {
24193
24702
  message: tx(SERVER_TEXTS.projectIgnorePatternDuplicate, { pattern: t })
24194
24703
  });
24195
24704
  }
@@ -24200,7 +24709,7 @@ async function applyPatch2(deps, body) {
24200
24709
  try {
24201
24710
  writePatterns(cwd, trimmed);
24202
24711
  } catch (err) {
24203
- throw new HTTPException10(400, {
24712
+ throw new HTTPException11(400, {
24204
24713
  message: tx(SERVER_TEXTS.projectIgnorePersistFailed, {
24205
24714
  message: formatErrorMessage(err)
24206
24715
  })
@@ -24278,7 +24787,7 @@ var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
24278
24787
 
24279
24788
  // server/routes/project-preferences.ts
24280
24789
  import { statSync as statSync9 } from "fs";
24281
- import { HTTPException as HTTPException11 } from "hono/http-exception";
24790
+ import { HTTPException as HTTPException12 } from "hono/http-exception";
24282
24791
  function registerProjectPreferencesRoute(app, deps) {
24283
24792
  app.get("/api/project-preferences", (c) => {
24284
24793
  return c.json(buildEnvelope3(deps));
@@ -24306,7 +24815,7 @@ async function applyPatch3(deps, body) {
24306
24815
  const cwd = deps.runtimeContext.cwd;
24307
24816
  const missingPaths = collectMissingPaths(writes, cwd);
24308
24817
  if (missingPaths.length > 0) {
24309
- throw new HTTPException11(400, {
24818
+ throw new HTTPException12(400, {
24310
24819
  message: tx(SERVER_TEXTS.projectPrefsPathNotFound, {
24311
24820
  paths: missingPaths.join(", ")
24312
24821
  })
@@ -24315,7 +24824,7 @@ async function applyPatch3(deps, body) {
24315
24824
  const exposures = writes.map((w) => projectPathExposure({ key: w.key, value: w.value, cwd })).filter((e) => e.expandsSurface);
24316
24825
  if (exposures.length > 0 && body.confirm !== true) {
24317
24826
  const exposed = exposures.flatMap((e) => e.exposedPaths);
24318
- throw new HTTPException11(412, {
24827
+ throw new HTTPException12(412, {
24319
24828
  message: tx(SERVER_TEXTS.projectPrefsConfirmRequired, {
24320
24829
  paths: exposed.join(", ")
24321
24830
  })
@@ -24367,7 +24876,7 @@ function runWrite(w, cwd) {
24367
24876
  try {
24368
24877
  writeConfigValue(w.key, w.value, { target: "project-local", cwd });
24369
24878
  } catch (err) {
24370
- throw new HTTPException11(400, {
24879
+ throw new HTTPException12(400, {
24371
24880
  message: tx(SERVER_TEXTS.projectPrefsPersistFailed, {
24372
24881
  key: w.key,
24373
24882
  message: formatErrorMessage(err)
@@ -24467,7 +24976,7 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
24467
24976
 
24468
24977
  // server/routes/active-provider.ts
24469
24978
  import { existsSync as existsSync27 } from "fs";
24470
- import { HTTPException as HTTPException12 } from "hono/http-exception";
24979
+ import { HTTPException as HTTPException13 } from "hono/http-exception";
24471
24980
  function registerActiveProviderRoute(app, deps) {
24472
24981
  app.get("/api/active-provider", (c) => {
24473
24982
  return c.json(buildEnvelope4(deps));
@@ -24492,7 +25001,7 @@ function applyLensSwitch(deps, newValue) {
24492
25001
  try {
24493
25002
  writeConfigValue("activeProvider", newValue, { target: "project", cwd });
24494
25003
  } catch (err) {
24495
- throw new HTTPException12(400, {
25004
+ throw new HTTPException13(400, {
24496
25005
  message: tx(SERVER_TEXTS.activeProviderPersistFailed, {
24497
25006
  message: formatErrorMessage(err)
24498
25007
  })
@@ -24528,7 +25037,7 @@ var parsePatchBody5 = makeBodyValidator(PATCH_BODY_SCHEMA4, {
24528
25037
  });
24529
25038
 
24530
25039
  // server/routes/scan.ts
24531
- import { HTTPException as HTTPException13 } from "hono/http-exception";
25040
+ import { HTTPException as HTTPException14 } from "hono/http-exception";
24532
25041
 
24533
25042
  // server/scan-mutex.ts
24534
25043
  var inFlight = null;
@@ -24694,7 +25203,7 @@ function registerScanRoute(app, deps) {
24694
25203
  }
24695
25204
  async function runPersistedScan(c, deps) {
24696
25205
  if (deps.options.noBuiltIns || deps.options.noPlugins) {
24697
- throw new HTTPException13(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
25206
+ throw new HTTPException14(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
24698
25207
  }
24699
25208
  const dbExists = await tryWithSqlite(
24700
25209
  { databasePath: deps.options.dbPath, autoBackup: false },
@@ -24731,7 +25240,7 @@ async function runPersistedScan(c, deps) {
24731
25240
  ...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
24732
25241
  });
24733
25242
  if (outcome.kind !== "ok") {
24734
- throw new HTTPException13(500, {
25243
+ throw new HTTPException14(500, {
24735
25244
  message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.scanGuardTrip, { existing: outcome.existing }) : outcome.message
24736
25245
  });
24737
25246
  }
@@ -24739,7 +25248,7 @@ async function runPersistedScan(c, deps) {
24739
25248
  });
24740
25249
  } catch (err) {
24741
25250
  if (err instanceof ScanBusyError) {
24742
- throw new HTTPException13(409, { message: SERVER_TEXTS.scanPostBusy });
25251
+ throw new HTTPException14(409, { message: SERVER_TEXTS.scanPostBusy });
24743
25252
  }
24744
25253
  throw err;
24745
25254
  }
@@ -24800,7 +25309,7 @@ function groupTagsByPath2(rows) {
24800
25309
  }
24801
25310
  async function runFreshScan(deps) {
24802
25311
  if (deps.options.noBuiltIns || deps.options.noPlugins) {
24803
- throw new HTTPException13(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
25312
+ throw new HTTPException14(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
24804
25313
  }
24805
25314
  const resolveEnabledOverride = await buildBffResolverOverride(deps);
24806
25315
  const outcome = await runScanForCommand({
@@ -24835,7 +25344,7 @@ async function runFreshScan(deps) {
24835
25344
  ...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
24836
25345
  });
24837
25346
  if (outcome.kind !== "ok") {
24838
- throw new HTTPException13(500, {
25347
+ throw new HTTPException14(500, {
24839
25348
  message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.freshScanGuardTrip, { existing: outcome.existing }) : outcome.message
24840
25349
  });
24841
25350
  }
@@ -24875,7 +25384,7 @@ function emptyScanResult() {
24875
25384
  }
24876
25385
 
24877
25386
  // server/routes/sidecar.ts
24878
- import { HTTPException as HTTPException14 } from "hono/http-exception";
25387
+ import { HTTPException as HTTPException15 } from "hono/http-exception";
24879
25388
  import { resolve as resolve36 } from "path";
24880
25389
  var STATUS_FRESH = "fresh";
24881
25390
  var ENVELOPE_KIND2 = "sidecar.bumped";
@@ -24912,11 +25421,11 @@ function registerSidecarRoutes(app, deps) {
24912
25421
  assertContained(deps.runtimeContext.cwd, node.path);
24913
25422
  absPath = resolve36(deps.runtimeContext.cwd, node.path);
24914
25423
  } catch (err) {
24915
- throw new HTTPException14(400, { message: formatErrorMessage(err) });
25424
+ throw new HTTPException15(400, { message: formatErrorMessage(err) });
24916
25425
  }
24917
25426
  const result = invokeBump2(node, absPath, body);
24918
25427
  if (result.report.ok === false && result.report.reason === "fresh") {
24919
- throw new HTTPException14(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
25428
+ throw new HTTPException15(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
24920
25429
  }
24921
25430
  if (result.report.ok === true && result.report.noop === true) {
24922
25431
  const envelope2 = {
@@ -24943,7 +25452,7 @@ function registerSidecarRoutes(app, deps) {
24943
25452
  }
24944
25453
  } catch (err) {
24945
25454
  if (err instanceof EConsentRequiredError) throw err;
24946
- throw new HTTPException14(500, { message: formatErrorMessage(err) });
25455
+ throw new HTTPException15(500, { message: formatErrorMessage(err) });
24947
25456
  }
24948
25457
  if (body.confirm === true) {
24949
25458
  deps.configService.reload();
@@ -24980,7 +25489,7 @@ async function loadNode(deps, nodePath) {
24980
25489
  );
24981
25490
  const node = persisted?.nodes.find((n) => n.path === nodePath);
24982
25491
  if (!node) {
24983
- throw new HTTPException14(404, {
25492
+ throw new HTTPException15(404, {
24984
25493
  message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
24985
25494
  });
24986
25495
  }
@@ -24988,7 +25497,7 @@ async function loadNode(deps, nodePath) {
24988
25497
  }
24989
25498
  function invokeBump2(node, absPath, body) {
24990
25499
  if (!nodeBumpAction.invoke) {
24991
- throw new HTTPException14(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
25500
+ throw new HTTPException15(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
24992
25501
  }
24993
25502
  const input = {};
24994
25503
  if (body.force === true) input.force = true;
@@ -25175,13 +25684,13 @@ function attachBroadcasterRoute(app, broadcaster) {
25175
25684
 
25176
25685
  // server/app.ts
25177
25686
  var BODY_LIMIT_BYTES = 1024 * 1024;
25178
- var DbMissingError = class extends HTTPException15 {
25687
+ var DbMissingError = class extends HTTPException16 {
25179
25688
  constructor(message) {
25180
25689
  super(500, { message });
25181
25690
  this.name = "DbMissingError";
25182
25691
  }
25183
25692
  };
25184
- var BulkValidationError = class extends HTTPException15 {
25693
+ var BulkValidationError = class extends HTTPException16 {
25185
25694
  id;
25186
25695
  code;
25187
25696
  constructor(init) {
@@ -25191,7 +25700,7 @@ var BulkValidationError = class extends HTTPException15 {
25191
25700
  this.code = init.code;
25192
25701
  }
25193
25702
  };
25194
- var LoopbackGateError = class extends HTTPException15 {
25703
+ var LoopbackGateError = class extends HTTPException16 {
25195
25704
  code;
25196
25705
  constructor(init) {
25197
25706
  super(403, { message: init.message });
@@ -25204,6 +25713,7 @@ function createApp(deps) {
25204
25713
  const configService = new ConfigService({
25205
25714
  cwd: deps.runtimeContext.cwd
25206
25715
  });
25716
+ app.use("*", createSentryRequestCapture());
25207
25717
  app.use("*", createLoopbackGate({ port: deps.options.port }));
25208
25718
  app.use("*", createSecurityHeaders());
25209
25719
  app.use(
@@ -25211,7 +25721,7 @@ function createApp(deps) {
25211
25721
  bodyLimit({
25212
25722
  maxSize: BODY_LIMIT_BYTES,
25213
25723
  onError: () => {
25214
- throw new HTTPException15(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
25724
+ throw new HTTPException16(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
25215
25725
  }
25216
25726
  })
25217
25727
  );
@@ -25257,7 +25767,7 @@ function createApp(deps) {
25257
25767
  registerActiveProviderRoute(app, routeDeps);
25258
25768
  registerProjectIgnoreRoute(app, routeDeps);
25259
25769
  app.all("/api/*", (c) => {
25260
- throw new HTTPException15(404, {
25770
+ throw new HTTPException16(404, {
25261
25771
  message: tx(SERVER_TEXTS.unknownApiEndpoint, { path: sanitizeForTerminal(c.req.path) })
25262
25772
  });
25263
25773
  });
@@ -25265,7 +25775,7 @@ function createApp(deps) {
25265
25775
  app.use("*", createStaticHandler({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
25266
25776
  app.get("*", createSpaFallback({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
25267
25777
  app.notFound((c) => {
25268
- throw new HTTPException15(404, {
25778
+ throw new HTTPException16(404, {
25269
25779
  message: tx(SERVER_TEXTS.unknownPath, { path: sanitizeForTerminal(c.req.path) })
25270
25780
  });
25271
25781
  });
@@ -25320,7 +25830,7 @@ function formatError2(err, c) {
25320
25830
  };
25321
25831
  return c.json(envelope, 403);
25322
25832
  }
25323
- if (err instanceof HTTPException15) {
25833
+ if (err instanceof HTTPException16) {
25324
25834
  const status = err.status;
25325
25835
  const envelope = {
25326
25836
  ok: false,
@@ -25419,7 +25929,7 @@ var WsBroadcaster = class {
25419
25929
  * stop the rest from receiving the event. A failing socket is closed
25420
25930
  * + unregistered so the next broadcast doesn't waste cycles on it.
25421
25931
  *
25422
- * Backpressure check (per AGENTS.md §Watcher integration): if a
25932
+ * Backpressure check (per context/kernel.md §Kernel boundaries): if a
25423
25933
  * client's `bufferedAmount` exceeds `MAX_BUFFERED_BYTES`, it's evicted
25424
25934
  * with close code 1009. The check runs BEFORE `send` so the threshold
25425
25935
  * acts as an admission gate, not a post-mortem.
@@ -25439,8 +25949,8 @@ var WsBroadcaster = class {
25439
25949
  return;
25440
25950
  }
25441
25951
  const snapshot = Array.from(this.#clients);
25442
- for (const client of snapshot) {
25443
- this.#deliver(client, payload);
25952
+ for (const client2 of snapshot) {
25953
+ this.#deliver(client2, payload);
25444
25954
  }
25445
25955
  }
25446
25956
  /**
@@ -25454,9 +25964,9 @@ var WsBroadcaster = class {
25454
25964
  this.#shutDown = true;
25455
25965
  const snapshot = Array.from(this.#clients);
25456
25966
  this.#clients.clear();
25457
- for (const client of snapshot) {
25967
+ for (const client2 of snapshot) {
25458
25968
  try {
25459
- client.close(CLOSE_CODE_GOING_AWAY, "server shutdown");
25969
+ client2.close(CLOSE_CODE_GOING_AWAY, "server shutdown");
25460
25970
  } catch {
25461
25971
  }
25462
25972
  }
@@ -25465,31 +25975,31 @@ var WsBroadcaster = class {
25465
25975
  * Per-client delivery: backpressure check, then `send()`. Eviction +
25466
25976
  * unregistration on either failure mode.
25467
25977
  */
25468
- #deliver(client, payload) {
25469
- if (client.readyState !== READY_STATE_OPEN) {
25470
- this.#clients.delete(client);
25978
+ #deliver(client2, payload) {
25979
+ if (client2.readyState !== READY_STATE_OPEN) {
25980
+ this.#clients.delete(client2);
25471
25981
  return;
25472
25982
  }
25473
- if (client.bufferedAmount > MAX_BUFFERED_BYTES) {
25474
- this.#clients.delete(client);
25983
+ if (client2.bufferedAmount > MAX_BUFFERED_BYTES) {
25984
+ this.#clients.delete(client2);
25475
25985
  try {
25476
- client.close(CLOSE_CODE_MESSAGE_TOO_BIG, "backpressure exceeded");
25986
+ client2.close(CLOSE_CODE_MESSAGE_TOO_BIG, "backpressure exceeded");
25477
25987
  } catch {
25478
25988
  }
25479
25989
  log.warn(
25480
25990
  tx(SERVER_TEXTS.wsBackpressureEvicted, {
25481
- buffered: String(client.bufferedAmount),
25991
+ buffered: String(client2.bufferedAmount),
25482
25992
  threshold: String(MAX_BUFFERED_BYTES)
25483
25993
  })
25484
25994
  );
25485
25995
  return;
25486
25996
  }
25487
25997
  try {
25488
- client.send(payload);
25998
+ client2.send(payload);
25489
25999
  } catch (err) {
25490
- this.#clients.delete(client);
26000
+ this.#clients.delete(client2);
25491
26001
  try {
25492
- client.close();
26002
+ client2.close();
25493
26003
  } catch {
25494
26004
  }
25495
26005
  const message = formatErrorMessage(err);
@@ -26250,7 +26760,7 @@ var ServeCommand = class extends SmCommand {
26250
26760
  emitElapsed = false;
26251
26761
  // CLI orchestrator with multi-flag handling, each `if (this.flag)`
26252
26762
  // branch is one cyclomatic point. Splitting per branch scatters the
26253
- // validation away from the flag it gates. Per AGENTS.md §Linting
26763
+ // validation away from the flag it gates. Per context/lint.md
26254
26764
  // category 1 ("CLI orchestrators with multi-flag handling").
26255
26765
  // eslint-disable-next-line complexity
26256
26766
  async run() {
@@ -26353,6 +26863,7 @@ var ServeCommand = class extends SmCommand {
26353
26863
  this.printer.info(formatValidationError(validation.error, stderrAnsi));
26354
26864
  return ExitCode.Error;
26355
26865
  }
26866
+ await initSentryBff(VERSION);
26356
26867
  let handle;
26357
26868
  try {
26358
26869
  handle = await createServer(validation.options);
@@ -27536,31 +28047,54 @@ var STUB_COMMANDS = [
27536
28047
  ];
27537
28048
 
27538
28049
  // cli/commands/tutorial.ts
27539
- import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
28050
+ import { cpSync as cpSync2, existsSync as existsSync31, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync11 } from "fs";
27540
28051
  import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
28052
+ import { createInterface as createInterface5 } from "readline";
27541
28053
  import { fileURLToPath as fileURLToPath7 } from "url";
27542
28054
  import { Command as Command37, Option as Option35 } from "clipanion";
27543
28055
 
27544
28056
  // cli/i18n/tutorial.texts.ts
27545
28057
  var TUTORIAL_TEXTS = {
27546
28058
  // Success, written to stdout after `<cwd>/{{target}}` is created.
27547
- // The skill now lives at `.claude/skills/<slug>/`; Claude Code
28059
+ // The skill lives at `<skillDir>/<slug>/`; the tester's agent
27548
28060
  // auto-discovers it on the next boot, so the tester invokes by
27549
28061
  // speaking a trigger phrase rather than referencing the file path.
27550
- // English / Spanish triggers are surfaced side by side and the
27551
- // first phrase the tester types sets the tutorial language for the
27552
- // rest of the session.
27553
- written: " {{glyph}} Skill `{{slug}}` materialized at {{target}} (under {{cwd}})\n\n Open Claude Code in this directory. The skill is auto-\n discovered; invoke it with one of its trigger phrases. The\n first message you type sets the tutorial language for the\n rest of the session:\n\n {{enLabel}} {{enTrigger}}\n {{esLabel}} {{esTrigger}}\n",
28062
+ // Message stays host-agnostic ("your coding agent") because the
28063
+ // destination Provider varies. English / Spanish triggers are
28064
+ // surfaced side by side and the first phrase the tester types sets
28065
+ // the tutorial language for the rest of the session.
28066
+ written: " {{glyph}} Skill `{{slug}}` materialized at {{target}} (under {{cwd}}, for {{provider}})\n\n Open your coding agent in this directory. The skill is auto-\n discovered; invoke it with one of its trigger phrases. The\n first message you type sets the tutorial language for the\n rest of the session:\n\n {{enLabel}} {{enTrigger}}\n {{esLabel}} {{esTrigger}}\n",
27554
28067
  writtenLabelEn: "English",
27555
28068
  writtenLabelEs: "Espa\xF1ol",
27556
- // Refusal, `{{target}}` already exists and `--force` was not set.
27557
- // Goes to stderr, exit code 2 (operational error per spec § Exit codes).
27558
- // Mirrors the success body shape: glyph + headline, then a dim hint
27559
- // line spelling the fix.
27560
- alreadyExists: "{{glyph}} {{target}} already exists under {{cwd}}\n {{hint}}\n",
27561
- alreadyExistsHint: "Pass `--force` to overwrite (deletes the existing folder first).",
28069
+ // Destination-provider prompt (interactive stdin, no `--for`). Header
28070
+ // uses a yellow `?` glyph; options are a numbered list of provider
28071
+ // label (with any `aka` agents in parentheses) + skill directory, with
28072
+ // a `(default)` marker on the first option (Claude). The input line
28073
+ // accepts a number, a provider id, or an empty answer (which takes the
28074
+ // default).
28075
+ promptHeader: "{{glyph}} Which agent should host the tutorial skill?",
28076
+ promptOption: " {{index}}) {{label}}: {{skillDir}}{{marker}}",
28077
+ promptDefaultMarker: " (default)",
28078
+ promptInput: " Enter the number or provider id [default {{index}}]: ",
28079
+ // Prompt answer matched neither an index nor an id. Goes to stderr,
28080
+ // exit code 2. Mirrors the error shape: glyph + headline + dim hint.
28081
+ promptInvalid: "{{glyph}} sm tutorial: that is not one of the listed providers\n {{hint}}\n",
28082
+ // `--for` named a provider that does not exist or declares no
28083
+ // `scaffold.skillDir`. Goes to stderr, exit code 2.
28084
+ forUnknown: "{{glyph}} sm tutorial: unknown provider '{{provider}}' for --for\n {{hint}}\n",
28085
+ forUnknownHint: "Valid providers: {{ids}}.",
28086
+ // Defensive: no built-in provider declares a scaffold target. Should
28087
+ // never happen (claude always does). Goes to stderr, exit code 2.
28088
+ noTargets: "{{glyph}} sm tutorial: no provider declares a skill scaffold target.\n",
28089
+ // Refusal, the cwd is not empty and `--force` was not set. Goes to
28090
+ // stderr, exit code 2 (operational error per spec § Exit codes). The
28091
+ // tutorial seeds a self-contained scenario into the cwd, so it needs
28092
+ // an empty directory; the hint spells the two ways forward. Mirrors
28093
+ // the error shape: glyph + headline + dim hint.
28094
+ notEmpty: "{{glyph}} sm tutorial: the current directory is not empty (found {{entries}})\n {{hint}}\n",
28095
+ notEmptyHint: "sm tutorial seeds a self-contained scenario; run it in a fresh empty directory, or pass `--force` to use this one anyway.",
27562
28096
  // Invalid `variant` positional argument. Goes to stderr, exit code 2.
27563
- // Mirrors `alreadyExists`: glyph + headline + dim hint enumerating the
28097
+ // Mirrors the error shape: glyph + headline + dim hint enumerating the
27564
28098
  // valid values.
27565
28099
  invalidVariant: "{{glyph}} sm tutorial: unknown variant '{{variant}}'\n {{hint}}\n",
27566
28100
  invalidVariantHint: "Valid values: tutorial (default), master.",
@@ -27612,6 +28146,13 @@ var TutorialCommand = class extends SmCommand {
27612
28146
  ]
27613
28147
  });
27614
28148
  variant = Option35.String({ required: false });
28149
+ // Named `forProvider`, NOT `for` (reserved word). The CLI surface stays
28150
+ // `--for`; selects the destination Provider whose `scaffold.skillDir`
28151
+ // the skill is materialised under, skipping the interactive prompt.
28152
+ forProvider = Option35.String("--for", {
28153
+ required: false,
28154
+ description: "Destination provider id (e.g. claude, agent-skills). Skips the prompt."
28155
+ });
27615
28156
  force = Option35.Boolean("--force", false, {
27616
28157
  description: "Overwrite an existing target directory without prompting."
27617
28158
  });
@@ -27620,32 +28161,24 @@ var TutorialCommand = class extends SmCommand {
27620
28161
  const stderr = this.context.stderr;
27621
28162
  const stderrAnsi = this.ansiFor("stderr");
27622
28163
  const errGlyph = stderrAnsi.red("\u2715");
27623
- const rawVariant = this.variant;
27624
- if (rawVariant !== void 0 && !isTutorialVariant(rawVariant)) {
27625
- this.printer.error(
27626
- tx(TUTORIAL_TEXTS.invalidVariant, {
27627
- glyph: errGlyph,
27628
- variant: rawVariant,
27629
- hint: stderrAnsi.dim(TUTORIAL_TEXTS.invalidVariantHint)
27630
- })
27631
- );
27632
- return ExitCode.Error;
27633
- }
27634
- const variant = rawVariant ?? DEFAULT_VARIANT;
28164
+ const variant = this.resolveVariantArg(errGlyph, stderrAnsi);
28165
+ if (variant === null) return ExitCode.Error;
27635
28166
  const spec = VARIANT_SPECS[variant];
27636
- const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
27637
- const targetDisplay = `.claude/skills/${spec.slug}/`;
27638
- if (existsSync31(targetDir) && !this.force) {
28167
+ if (!this.force && !isDirEmpty(ctx.cwd)) {
27639
28168
  this.printer.error(
27640
- tx(TUTORIAL_TEXTS.alreadyExists, {
28169
+ tx(TUTORIAL_TEXTS.notEmpty, {
27641
28170
  glyph: errGlyph,
27642
- target: targetDisplay,
27643
- cwd: stderrAnsi.dim(displayCwd(ctx.cwd)),
27644
- hint: stderrAnsi.dim(TUTORIAL_TEXTS.alreadyExistsHint)
28171
+ entries: listCwdEntries(ctx.cwd),
28172
+ hint: stderrAnsi.dim(TUTORIAL_TEXTS.notEmptyHint)
27645
28173
  })
27646
28174
  );
27647
28175
  return ExitCode.Error;
27648
28176
  }
28177
+ const targets = listScaffoldTargets();
28178
+ const target = await this.resolveScaffoldTarget(targets, stderrAnsi, errGlyph);
28179
+ if (target === null) return ExitCode.Error;
28180
+ const targetDir = join21(ctx.cwd, target.skillDir, spec.slug);
28181
+ const targetDisplay = `${target.skillDir}/${spec.slug}/`;
27649
28182
  let sourceDir;
27650
28183
  try {
27651
28184
  sourceDir = resolveSkillSourceDir(variant);
@@ -27685,6 +28218,7 @@ var TutorialCommand = class extends SmCommand {
27685
28218
  glyph: ansi.green("\u2713"),
27686
28219
  slug: spec.slug,
27687
28220
  target: targetDisplay,
28221
+ provider: ansi.dim(target.label),
27688
28222
  cwd: ansi.dim(displayCwd(ctx.cwd)),
27689
28223
  enLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEn),
27690
28224
  esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs),
@@ -27694,15 +28228,154 @@ var TutorialCommand = class extends SmCommand {
27694
28228
  );
27695
28229
  return ExitCode.Ok;
27696
28230
  }
28231
+ /**
28232
+ * Validate the positional `variant` arg against the closed catalog.
28233
+ * Returns the resolved variant, or `null` after printing the
28234
+ * `invalidVariant` error (caller exits non-zero). Extracted from
28235
+ * `run()` to keep its cyclomatic complexity within the lint budget.
28236
+ */
28237
+ resolveVariantArg(errGlyph, stderrAnsi) {
28238
+ const rawVariant = this.variant;
28239
+ if (rawVariant !== void 0 && !isTutorialVariant(rawVariant)) {
28240
+ this.printer.error(
28241
+ tx(TUTORIAL_TEXTS.invalidVariant, {
28242
+ glyph: errGlyph,
28243
+ variant: rawVariant,
28244
+ hint: stderrAnsi.dim(TUTORIAL_TEXTS.invalidVariantHint)
28245
+ })
28246
+ );
28247
+ return null;
28248
+ }
28249
+ return rawVariant ?? DEFAULT_VARIANT;
28250
+ }
28251
+ /**
28252
+ * Resolve the destination Provider. Precedence:
28253
+ * 1. `--for <id>` (validated against the scaffold-capable catalog).
28254
+ * 2. Interactive stdin → numbered prompt defaulting to Claude (the
28255
+ * first entry); an empty answer accepts it.
28256
+ * 3. Non-interactive stdin → Claude (the first entry), so the verb
28257
+ * stays scriptable.
28258
+ * The verb requires an empty cwd, so there is no marker to detect: the
28259
+ * default is always the first scaffold-capable Provider (Claude).
28260
+ * Returns `null` after printing an error (caller exits non-zero).
28261
+ */
28262
+ async resolveScaffoldTarget(targets, stderrAnsi, errGlyph) {
28263
+ if (targets.length === 0) {
28264
+ this.printer.error(tx(TUTORIAL_TEXTS.noTargets, { glyph: errGlyph }));
28265
+ return null;
28266
+ }
28267
+ const requested = this.forProvider;
28268
+ if (requested !== void 0) {
28269
+ const found = targets.find((t) => t.id === requested);
28270
+ if (found === void 0) {
28271
+ this.printer.error(
28272
+ tx(TUTORIAL_TEXTS.forUnknown, {
28273
+ glyph: errGlyph,
28274
+ provider: requested,
28275
+ hint: stderrAnsi.dim(
28276
+ tx(TUTORIAL_TEXTS.forUnknownHint, { ids: targets.map((t) => t.id).join(", ") })
28277
+ )
28278
+ })
28279
+ );
28280
+ return null;
28281
+ }
28282
+ return found;
28283
+ }
28284
+ const defaultIndex = 0;
28285
+ const def = targets[defaultIndex];
28286
+ const stdin = this.context.stdin;
28287
+ if (targets.length === 1 || stdin.isTTY !== true) return def;
28288
+ const stderr = this.context.stderr;
28289
+ const picked = await promptForTarget(
28290
+ targets,
28291
+ defaultIndex,
28292
+ stdin,
28293
+ stderr,
28294
+ stderrAnsi.yellow("?")
28295
+ );
28296
+ if (picked === null) {
28297
+ this.printer.error(
28298
+ tx(TUTORIAL_TEXTS.promptInvalid, {
28299
+ glyph: errGlyph,
28300
+ hint: stderrAnsi.dim(
28301
+ tx(TUTORIAL_TEXTS.forUnknownHint, { ids: targets.map((t) => t.id).join(", ") })
28302
+ )
28303
+ })
28304
+ );
28305
+ return null;
28306
+ }
28307
+ return picked;
28308
+ }
27697
28309
  };
27698
28310
  function isTutorialVariant(value) {
27699
28311
  return VALID_VARIANTS.includes(value);
27700
28312
  }
28313
+ function toScaffoldTarget(provider) {
28314
+ const scaffold = provider.scaffold;
28315
+ if (!scaffold || !scaffold.skillDir) return null;
28316
+ return {
28317
+ id: provider.id,
28318
+ label: provider.presentation.label,
28319
+ skillDir: scaffold.skillDir,
28320
+ aka: scaffold.aka ?? []
28321
+ };
28322
+ }
28323
+ function listScaffoldTargets() {
28324
+ const out = [];
28325
+ for (const provider of builtIns().providers) {
28326
+ const target = toScaffoldTarget(provider);
28327
+ if (target !== null) out.push(target);
28328
+ }
28329
+ return out;
28330
+ }
28331
+ function labelWithAka(target) {
28332
+ return target.aka.length > 0 ? `${target.label} (${target.aka.join(", ")})` : target.label;
28333
+ }
28334
+ async function promptForTarget(targets, defaultIndex, stdin, stderr, glyph) {
28335
+ const lines = [tx(TUTORIAL_TEXTS.promptHeader, { glyph })];
28336
+ for (let i = 0; i < targets.length; i += 1) {
28337
+ const t = targets[i];
28338
+ lines.push(
28339
+ tx(TUTORIAL_TEXTS.promptOption, {
28340
+ index: i + 1,
28341
+ label: labelWithAka(t),
28342
+ skillDir: `${t.skillDir}/`,
28343
+ marker: i === defaultIndex ? TUTORIAL_TEXTS.promptDefaultMarker : ""
28344
+ })
28345
+ );
28346
+ }
28347
+ stderr.write(lines.join("\n") + "\n");
28348
+ const rl = createInterface5({ input: stdin, output: stderr });
28349
+ try {
28350
+ const answer = await new Promise(
28351
+ (resolveP) => rl.question(tx(TUTORIAL_TEXTS.promptInput, { index: defaultIndex + 1 }), resolveP)
28352
+ );
28353
+ const trimmed = answer.trim();
28354
+ if (trimmed === "") return targets[defaultIndex];
28355
+ const asNumber = Number.parseInt(trimmed, 10);
28356
+ if (!Number.isNaN(asNumber) && asNumber >= 1 && asNumber <= targets.length) {
28357
+ return targets[asNumber - 1];
28358
+ }
28359
+ const byId = targets.find((t) => t.id.toLowerCase() === trimmed.toLowerCase());
28360
+ return byId ?? null;
28361
+ } finally {
28362
+ rl.close();
28363
+ }
28364
+ }
27701
28365
  function displayCwd(cwd) {
27702
28366
  const segments = cwd.split("/").filter((s) => s.length > 0);
27703
28367
  if (segments.length === 0) return "./";
27704
28368
  return `./${segments[segments.length - 1]}/`;
27705
28369
  }
28370
+ function isDirEmpty(dir) {
28371
+ return readdirSync10(dir).length === 0;
28372
+ }
28373
+ function listCwdEntries(dir) {
28374
+ const entries = readdirSync10(dir).sort();
28375
+ const shown = entries.slice(0, 5);
28376
+ const more = entries.length > shown.length ? ", ..." : "";
28377
+ return shown.join(", ") + more;
28378
+ }
27706
28379
  var cachedSourceDirs = /* @__PURE__ */ new Map();
27707
28380
  function resolveSkillSourceDir(variant) {
27708
28381
  const cached = cachedSourceDirs.get(variant);
@@ -27822,6 +28495,7 @@ cli.register(RootHelpCommand);
27822
28495
  cli.register(HelpCommand);
27823
28496
  cli.register(InitCommand);
27824
28497
  cli.register(TutorialCommand);
28498
+ cli.register(IntentionalFailCommand);
27825
28499
  cli.register(ScanCommand);
27826
28500
  cli.register(ScanCompareCommand);
27827
28501
  cli.register(ServeCommand);
@@ -27855,6 +28529,13 @@ var logLevel = resolveLogLevel({
27855
28529
  configureLogger(new Logger({ level: logLevel, stream: process.stderr }));
27856
28530
  var bareArgs = resolveBareInvocation(args);
27857
28531
  var routedArgs = routeHelpArgs(bareArgs ?? args, cli);
28532
+ var telemetryVerb = routedArgs[0];
28533
+ await maybeRunFirstRunPrompt();
28534
+ if (telemetryVerb !== "serve") {
28535
+ await initSentryCli(VERSION);
28536
+ setTelemetryVerbTag(telemetryVerb);
28537
+ await initUsageCli();
28538
+ }
27858
28539
  var lifecycleDispatcher = makeHookDispatcher(
27859
28540
  builtIns().hooks ?? [],
27860
28541
  new InMemoryProgressEmitter()
@@ -27891,10 +28572,18 @@ var exitCode = await cli.run(routedArgs, {
27891
28572
  stdout: process.stdout,
27892
28573
  stderr: process.stderr
27893
28574
  });
28575
+ if (telemetryVerb !== void 0 && telemetryVerb !== "") {
28576
+ const knownVerbs = new Set(
28577
+ registeredVerbPaths(cli).map((path) => path[0]).filter((token) => token !== void 0)
28578
+ );
28579
+ captureCliInvocation(telemetryVerb, extractFlagNames(routedArgs.slice(1)), knownVerbs);
28580
+ }
27894
28581
  await lifecycleDispatcher.dispatch(
27895
28582
  "shutdown",
27896
28583
  makeEvent("shutdown", { exitCode })
27897
28584
  );
28585
+ await closeSentryCli();
28586
+ await flushUsageCli();
27898
28587
  process.exit(exitCode);
27899
28588
  function resolveBareInvocation(rawArgs) {
27900
28589
  if (rawArgs.length === 0) return resolveBareDefault();