@diffohq/diffo 0.8.0 → 0.9.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 (98) hide show
  1. package/README.md +8 -6
  2. package/dist/cli.mjs +462 -4
  3. package/dist/client/assets/{abnfDiagram-O67JEVCF-BUIFF3Lv.js → abnfDiagram-O67JEVCF-Dh4is2Nj.js} +1 -1
  4. package/dist/client/assets/{arc-x6Ulf5MI.js → arc-CMN0Gb1g.js} +1 -1
  5. package/dist/client/assets/architecture-7GRP2DOG-DaIFNiR2.js +1 -0
  6. package/dist/client/assets/{architectureDiagram-NJMV4G6O-D25O1A2B.js → architectureDiagram-NJMV4G6O-DxVGDSE1.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-BEXU5L5S-Dy7P3IZw.js → blockDiagram-BEXU5L5S-T1F9hSwZ.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-YGBWAQC7-BnE5HTw4.js → c4Diagram-YGBWAQC7-DC6zwYuX.js} +1 -1
  9. package/dist/client/assets/channel-Ci5ZF5xD.js +1 -0
  10. package/dist/client/assets/{chunk-3FUC2YCW-6C72_i5n.js → chunk-3FUC2YCW-D0PAoCRl.js} +1 -1
  11. package/dist/client/assets/{chunk-5DYCD2WN-B0IgEc5I.js → chunk-5DYCD2WN-NqHzOtt0.js} +1 -1
  12. package/dist/client/assets/{chunk-742MDFTN-BNLZzbxp.js → chunk-742MDFTN-B9Y5HHB7.js} +1 -1
  13. package/dist/client/assets/{chunk-7INBJB4K-K1DkKka3.js → chunk-7INBJB4K-CBOWlj91.js} +1 -1
  14. package/dist/client/assets/{chunk-7M6MHVWA-B-KIWC1T.js → chunk-7M6MHVWA-D2dZ_rQI.js} +1 -1
  15. package/dist/client/assets/{chunk-7PRAP22T-DR6CzDYx.js → chunk-7PRAP22T-7dilu8x-.js} +1 -1
  16. package/dist/client/assets/{chunk-GTNCS2PH-BWkvnYRd.js → chunk-GTNCS2PH-at0ENjhB.js} +1 -1
  17. package/dist/client/assets/{chunk-GWA4HPMP-l8NLPoZc.js → chunk-GWA4HPMP-C06n_37U.js} +1 -1
  18. package/dist/client/assets/{chunk-MBY4JIJT-k1mwME9y.js → chunk-MBY4JIJT-DuowBunG.js} +1 -1
  19. package/dist/client/assets/{chunk-NETBCI7D-DV1F8c_R.js → chunk-NETBCI7D-ClNA7QFH.js} +1 -1
  20. package/dist/client/assets/{chunk-O7XYJQB3-Bb8gIxaB.js → chunk-O7XYJQB3-BjcjInBL.js} +1 -1
  21. package/dist/client/assets/{chunk-UA2S7LBM-BL7m6fd_.js → chunk-UA2S7LBM-B-PAn1hd.js} +1 -1
  22. package/dist/client/assets/{chunk-WEXAMYUT-CAnQv1qY.js → chunk-WEXAMYUT-Br84uK4B.js} +1 -1
  23. package/dist/client/assets/{chunk-XXDRQBXY-DooI1miH.js → chunk-XXDRQBXY-BbBZykgX.js} +1 -1
  24. package/dist/client/assets/{chunk-Z7XXMR3K-DPVaCcdM.js → chunk-Z7XXMR3K-CQ-grwfe.js} +1 -1
  25. package/dist/client/assets/{chunk-ZIGJFQKS-Bz4IWpFy.js → chunk-ZIGJFQKS-CLwfE-hs.js} +1 -1
  26. package/dist/client/assets/{classDiagram-v2-NBCMYWYE-Dso7ogb5.js → classDiagram-v2-NBCMYWYE-DD-bcOHO.js} +1 -1
  27. package/dist/client/assets/{core-Dy-hJrbg.js → core-BADyutHL.js} +1 -1
  28. package/dist/client/assets/{cose-bilkent-JH36ORCC-Baj8S6bg.js → cose-bilkent-JH36ORCC-fuMg3J_l.js} +1 -1
  29. package/dist/client/assets/{cynefin-OW5HDTMX-BkA1ccYX.js → cynefin-OW5HDTMX-DwFXJ_x8.js} +1 -1
  30. package/dist/client/assets/{cynefinDiagram-VND7K2PF-nyK9OZGN.js → cynefinDiagram-VND7K2PF-cGL_k8gj.js} +1 -1
  31. package/dist/client/assets/{dagre-6A5THRUB-BP_dyQLS.js → dagre-6A5THRUB-BetgCBn9.js} +1 -1
  32. package/dist/client/assets/{diagram-22UHCM2B-BIt_aIJx.js → diagram-22UHCM2B-C7qT2Ix2.js} +1 -1
  33. package/dist/client/assets/{diagram-3UASUU5V-UX2eUwyi.js → diagram-3UASUU5V-DGUO9DWV.js} +1 -1
  34. package/dist/client/assets/{diagram-ATOU4E4O-CEYK5oZO.js → diagram-ATOU4E4O-f520gjx5.js} +1 -1
  35. package/dist/client/assets/{diagram-CDSNMT55-4uIYrmGp.js → diagram-CDSNMT55-Dhrm4GzB.js} +1 -1
  36. package/dist/client/assets/{diagram-MLGK6HIB-5BGrc92k.js → diagram-MLGK6HIB-Di0aRiuU.js} +1 -1
  37. package/dist/client/assets/{diagram-MPIPVDR6-0BG375Nf.js → diagram-MPIPVDR6-BHhVZUxI.js} +1 -1
  38. package/dist/client/assets/{dist-hfzhbdYS.js → dist-CbdYocyN.js} +1 -1
  39. package/dist/client/assets/{dist-uIEEXDIm.js → dist-Cezvykkn.js} +1 -1
  40. package/dist/client/assets/{ebnfDiagram-ZINNZB2B-CnAveRvy.js → ebnfDiagram-ZINNZB2B-CQA0eib_.js} +1 -1
  41. package/dist/client/assets/{elk-276RUBZZ-CejJ0q5w.js → elk-276RUBZZ-CZKKVSu0.js} +1 -1
  42. package/dist/client/assets/{engine-oniguruma-BpOI5GVJ.js → engine-oniguruma-B68c_YAs.js} +1 -1
  43. package/dist/client/assets/{erDiagram-OPXOYQCR-Lg3_iVkX.js → erDiagram-OPXOYQCR-DcNnfq_S.js} +1 -1
  44. package/dist/client/assets/eventmodeling-NTZA5JFV-C61pKQjq.js +1 -0
  45. package/dist/client/assets/flowDiagram-KWPJA3E3-BolWtw3C.js +1 -0
  46. package/dist/client/assets/{ganttDiagram-FUAMR5RP-D5nJpDg8.js → ganttDiagram-FUAMR5RP-Dteh33ci.js} +1 -1
  47. package/dist/client/assets/{gitGraph-4MIJSDKK-BLNIFBUu.js → gitGraph-4MIJSDKK-DMTE1jTV.js} +1 -1
  48. package/dist/client/assets/{gitGraphDiagram-X574FWY7-YHwgA3VE.js → gitGraphDiagram-X574FWY7-EV_0IPok.js} +1 -1
  49. package/dist/client/assets/index-B__poZ2b.js +93 -0
  50. package/dist/client/assets/{index-CE0LasmM.css → index-CL4K6Y07.css} +1 -1
  51. package/dist/client/assets/{info-A6RAGUB7-Bfa8ErwC.js → info-A6RAGUB7-WkHzxEDS.js} +1 -1
  52. package/dist/client/assets/{infoDiagram-VRGFBTTK-Dzp_xQpV.js → infoDiagram-VRGFBTTK-BcH5U5G2.js} +1 -1
  53. package/dist/client/assets/{ishikawaDiagram-OU5B5YK6-Hun-xe-t.js → ishikawaDiagram-OU5B5YK6-Z8vZnj1l.js} +1 -1
  54. package/dist/client/assets/{journeyDiagram-ZHPQQLJL-CT5fXqj_.js → journeyDiagram-ZHPQQLJL-Cn-m-UYg.js} +1 -1
  55. package/dist/client/assets/{kanban-definition-PNTS6WVX-kFYcpsMx.js → kanban-definition-PNTS6WVX-DLOrUOML.js} +1 -1
  56. package/dist/client/assets/{line-Cda_OIfO.js → line-DhKwT3E3.js} +1 -1
  57. package/dist/client/assets/{linear-CDQGk4q-.js → linear-DvwuMCFu.js} +1 -1
  58. package/dist/client/assets/{mermaid-parser.core-CCoRJPAh.js → mermaid-parser.core-CwcOI9nG.js} +3 -3
  59. package/dist/client/assets/{mermaid.core-SnjyC0ZV.js → mermaid.core-BabKb2iQ.js} +4 -4
  60. package/dist/client/assets/{mindmap-definition-NLK3R4M7-C3oLUr5b.js → mindmap-definition-NLK3R4M7-D7wpNtz6.js} +1 -1
  61. package/dist/client/assets/{packet-AYTQ26CC-BT6BgV6a.js → packet-AYTQ26CC-BJablqte.js} +1 -1
  62. package/dist/client/assets/{pegDiagram-GJSIUBJH-OT5BsfLg.js → pegDiagram-GJSIUBJH-EAlXAkUz.js} +1 -1
  63. package/dist/client/assets/{pie-WAS4IAKB-Bou89QXR.js → pie-WAS4IAKB-BAAbtIiO.js} +1 -1
  64. package/dist/client/assets/{pieDiagram-5QR66LMP-AzEtJfJu.js → pieDiagram-5QR66LMP-b1pYP4ES.js} +1 -1
  65. package/dist/client/assets/{quadrantDiagram-O4NWA36T-BUKCkyd7.js → quadrantDiagram-O4NWA36T-B_LJNNdE.js} +1 -1
  66. package/dist/client/assets/{radar-RG4KPBEZ-D77V4y6h.js → radar-RG4KPBEZ-BKldNRRC.js} +1 -1
  67. package/dist/client/assets/{railroad-74A4TZTK-CIYb8bnG.js → railroad-74A4TZTK-LvULpHpQ.js} +1 -1
  68. package/dist/client/assets/railroad-abnf-HS5TGJTU-CORGw4Le.js +1 -0
  69. package/dist/client/assets/railroad-ebnf-LZEXJU2U-BXCX4hkl.js +1 -0
  70. package/dist/client/assets/railroad-peg-WCYAUIDC-C6BQUpjn.js +1 -0
  71. package/dist/client/assets/{railroadDiagram-XR7U4H2S-DhUQyIW9.js → railroadDiagram-XR7U4H2S-kp6LeaeM.js} +1 -1
  72. package/dist/client/assets/{requirementDiagram-PLB6GJNP-5HRstoAa.js → requirementDiagram-PLB6GJNP-Br-OgP6b.js} +1 -1
  73. package/dist/client/assets/{sankeyDiagram-IPEJSGJF-sNbgZbEH.js → sankeyDiagram-IPEJSGJF-DSIbAxcK.js} +1 -1
  74. package/dist/client/assets/{sequenceDiagram-PO4LG4MO-B_vIrQ3k.js → sequenceDiagram-PO4LG4MO-uVv8u8VI.js} +1 -1
  75. package/dist/client/assets/{src-BWz11yEV.js → src-Bmfs-TFn.js} +1 -1
  76. package/dist/client/assets/{stateDiagram-v2-GCMORJYK-BnRt6hQF.js → stateDiagram-v2-GCMORJYK-Crql1z4d.js} +1 -1
  77. package/dist/client/assets/{swimlanes-2SLR337P-CLlJdLDU.js → swimlanes-2SLR337P-CnpU7AM-.js} +1 -1
  78. package/dist/client/assets/swimlanesDiagram-TC7HE7FX-Crca2YKi.js +8 -0
  79. package/dist/client/assets/{timeline-definition-EJHVYXUP-sXwAwUmH.js → timeline-definition-EJHVYXUP-CqzGi-Sd.js} +1 -1
  80. package/dist/client/assets/{treeView-Q6P3EWNA-BmhLEMPr.js → treeView-Q6P3EWNA-N_EzEUvg.js} +1 -1
  81. package/dist/client/assets/{treemap-WGGIJYW6-D_PGkPFQ.js → treemap-WGGIJYW6-BYeVgP-v.js} +1 -1
  82. package/dist/client/assets/{usecaseDiagram-POWQR4AR-GARihMJS.js → usecaseDiagram-POWQR4AR-Dyeq2Zcy.js} +1 -1
  83. package/dist/client/assets/{vennDiagram-UO4OBE2U-DXSYoTtG.js → vennDiagram-UO4OBE2U-CuCjJaOo.js} +1 -1
  84. package/dist/client/assets/{wardley-WFR3VGLG-Caebv6bl.js → wardley-WFR3VGLG-AV0-dO_Z.js} +1 -1
  85. package/dist/client/assets/{wardleyDiagram-VNRHLVJA-BnFa4EKC.js → wardleyDiagram-VNRHLVJA-f0dVm2LA.js} +1 -1
  86. package/dist/client/assets/{xychartDiagram-PMCCYNJV-XS2BbOyB.js → xychartDiagram-PMCCYNJV-BNjSW7P1.js} +1 -1
  87. package/dist/client/index.html +2 -2
  88. package/package.json +1 -1
  89. package/plugin.json +1 -1
  90. package/dist/client/assets/architecture-7GRP2DOG-Cx2ezWq8.js +0 -1
  91. package/dist/client/assets/channel-IK-3bv-L.js +0 -1
  92. package/dist/client/assets/eventmodeling-NTZA5JFV-Dh34QIVY.js +0 -1
  93. package/dist/client/assets/flowDiagram-KWPJA3E3-Bcos9jHd.js +0 -1
  94. package/dist/client/assets/index-DYt1d3uP.js +0 -93
  95. package/dist/client/assets/railroad-abnf-HS5TGJTU-OVikttJY.js +0 -1
  96. package/dist/client/assets/railroad-ebnf-LZEXJU2U-DPAI1fGw.js +0 -1
  97. package/dist/client/assets/railroad-peg-WCYAUIDC-CcsZpvwt.js +0 -1
  98. package/dist/client/assets/swimlanesDiagram-TC7HE7FX-D6iEiuTl.js +0 -8
package/README.md CHANGED
@@ -60,8 +60,9 @@ That's the whole install. From then on, one command in any session:
60
60
 
61
61
  Every time, the agent opens the review and hands you the URL. It all runs on your
62
62
  machine, and nothing needs to be committed or pushed first. A pull request needs the
63
- [GitHub CLI](https://cli.github.com) signed in, and that is the only time Diffo touches
64
- the network.
63
+ [GitHub CLI](https://cli.github.com) signed in. Otherwise the only thing that leaves your
64
+ machine is two small anonymous usage events per review, [listed in full](https://diffohq.github.io/diffo/telemetry)
65
+ and off with `diffo telemetry off`.
65
66
 
66
67
  New here? [**Your first review, end to end**](https://diffohq.github.io/diffo/tutorial) takes about five minutes.
67
68
 
@@ -135,10 +136,11 @@ read, and the agent is there to answer, explain, and fix.
135
136
  <img alt="Diffo's architecture: your agent writes the code and opens the review; a local Diffo server watches the changeset and serves it to your browser; your comments and Finish review return to the agent through diffo poll, and its answers and fixes land back in the review live." src="assets/how-it-works-light.svg" width="100%">
136
137
  </picture>
137
138
 
138
- One process on your machine, bound to loopback: no account, no cloud, no telemetry, and
139
- no model API. Diffo spawns no agents of its own; the one you're already talking to stays
140
- attached through the `diffo` CLI, so your comments land in the session that holds the
141
- context. The viewer itself is a real diff viewer, unified and split, keyboard-first, with
139
+ One process on your machine, bound to loopback: no account, no cloud, and no model API.
140
+ The one thing it reports is [anonymous usage data](https://diffohq.github.io/diffo/telemetry),
141
+ two events per review, never code or paths, off with one command. Diffo spawns no agents
142
+ of its own; the one you're already talking to stays attached through the `diffo` CLI, so
143
+ your comments land in the session that holds the context. The viewer itself is a real diff viewer, unified and split, keyboard-first, with
142
144
  GitHub's conventions. The CLI, the flags, and everything under the hood are in the docs.
143
145
 
144
146
  ## Docs
package/dist/cli.mjs CHANGED
@@ -17,9 +17,24 @@ import { stream, streamSSE } from "hono/streaming";
17
17
  * token only: under Claude Code the transient `zsh -c` carries
18
18
  * `~/.claude/shell-snapshots/…` in its arguments, which would match HARNESS. */
19
19
  const SHELL = /^-?(?:\/[^ ]*\/)?(?:sh|bash|zsh|fish|dash|ksh|csh|tcsh)(?:\s|$)/;
20
+ /** The coding agents whose processes the ancestor walk recognises. Also the
21
+ * vocabulary of the `agent` field in usage data, so a name never travels unless
22
+ * it is on this list. */
23
+ const HARNESS_NAMES = [
24
+ "claude",
25
+ "cursor",
26
+ "codex",
27
+ "copilot",
28
+ "windsurf",
29
+ "zed",
30
+ "aider",
31
+ "goose",
32
+ "cline",
33
+ "amp"
34
+ ];
20
35
  /** A harness name at the start of a path segment or token, never inside a word:
21
36
  * `/Users/sam/example/` must not read as `amp`. */
22
- const HARNESS = /(?:^|[/\s@_.-])(?:claude|cursor|codex|copilot|windsurf|zed|aider|goose|cline|amp)/i;
37
+ const HARNESS = new RegExp(`(?:^|[/\\s@_.-])(${HARNESS_NAMES.join("|")})`, "i");
23
38
  function ownInvocation() {
24
39
  return {
25
40
  script: process.argv[1] ?? "",
@@ -34,15 +49,26 @@ function isOwnLauncher(command, own) {
34
49
  const tail = own.args.length > 0 ? `${base} ${own.args.join(" ")}` : own.script;
35
50
  return command === tail || command.endsWith(` ${tail}`) || command.endsWith(`/${tail}`);
36
51
  }
37
- function pickSessionAncestor(ancestors, own = ownInvocation()) {
52
+ function pickSession(ancestors, own) {
38
53
  for (const row of ancestors) {
39
54
  const command = row.command.trim();
40
55
  if (SHELL.test(command)) continue;
41
56
  if (isOwnLauncher(command, own)) continue;
42
- if (HARNESS.test(command)) return row.pid;
57
+ const match = HARNESS.exec(command);
58
+ if (match) return {
59
+ pid: row.pid,
60
+ harness: match[1].toLowerCase()
61
+ };
43
62
  }
44
63
  return null;
45
64
  }
65
+ function pickSessionAncestor(ancestors, own = ownInvocation()) {
66
+ return pickSession(ancestors, own)?.pid ?? null;
67
+ }
68
+ /** Which agent the session is, by the same walk that finds it. */
69
+ function pickSessionHarness(ancestors, own = ownInvocation()) {
70
+ return pickSession(ancestors, own)?.harness ?? null;
71
+ }
46
72
  function readRow(pid) {
47
73
  try {
48
74
  const ppid = Number.parseInt(execFileSync("ps", [
@@ -87,6 +113,16 @@ function detectSessionPid() {
87
113
  return null;
88
114
  }
89
115
  }
116
+ /** The agent this CLI runs under, or null from a plain terminal (and on Windows,
117
+ * where the walk is not available). Only ever a name from HARNESS_NAMES. */
118
+ function detectHarness() {
119
+ if (process.platform === "win32") return null;
120
+ try {
121
+ return pickSessionHarness(readAncestors(process.ppid));
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
90
126
  //#endregion
91
127
  //#region src/shared/review.ts
92
128
  const THREAD_INTENTS = ["fix", "question"];
@@ -1019,6 +1055,8 @@ For the reviewer:
1019
1055
  setup Register diffo with the coding agents on this machine
1020
1056
  (Claude Code, Cursor, VS Code, Copilot, and the shared
1021
1057
  ~/.agents skills dir read by Codex, Gemini, Amp, Goose, …)
1058
+ telemetry Show whether anonymous usage data is on, or turn it
1059
+ off / on (telemetry off · telemetry on)
1022
1060
 
1023
1061
  For the agent (the AI that wrote the change):
1024
1062
  poll Wait for the reviewer's feedback (blocking long-poll;
@@ -1358,7 +1396,33 @@ are left as they are, and opening a review keeps installed skills fresh
1358
1396
  automatically after upgrades.
1359
1397
 
1360
1398
  Example:
1361
- diffo setup`
1399
+ diffo setup`,
1400
+ telemetry: `diffo telemetry: anonymous usage data — see it, turn it off, turn it on
1401
+
1402
+ Usage: diffo telemetry [status|on|off]
1403
+
1404
+ Diffo reports two small anonymous events per review, "opened" and "finished":
1405
+ its version, your OS and Node major, the kind of review (working tree, branch,
1406
+ or pull request), which agent opened it, counts (threads, comments, whether
1407
+ layers were used, a coarse duration), and whether it runs from a source
1408
+ checkout (dev: true, so the maintainers' own use is filtered out). Never code,
1409
+ paths, repository or branch names, comment text, or anything about the pull
1410
+ request. One random id per machine tells returning machines from new ones;
1411
+ turning reporting off deletes it. Nothing is sent until the review page has
1412
+ shown you the notice once, and nothing from the review that showed it.
1413
+
1414
+ status what is on, why it is off, and where the data goes (the default)
1415
+ off stop reporting on this machine, for good (a machine that has
1416
+ reported sends one last event saying so, then nothing)
1417
+ on start again (a fresh id, not the old history)
1418
+
1419
+ DIFFO_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1 turns it off for one process or
1420
+ for a fleet; CI does the same. DIFFO_TELEMETRY_DEBUG=1 writes every payload to
1421
+ the server log before it is sent. The full list, and where it goes:
1422
+ https://diffohq.github.io/diffo/telemetry
1423
+
1424
+ Example:
1425
+ diffo telemetry off`
1362
1426
  };
1363
1427
  function helpFor(topic) {
1364
1428
  return topic && VERB_HELP[topic] || HELP_TEXT;
@@ -1370,6 +1434,7 @@ const VERBS = /* @__PURE__ */ new Set([
1370
1434
  "layers",
1371
1435
  "end",
1372
1436
  "setup",
1437
+ "telemetry",
1373
1438
  "status",
1374
1439
  "stop",
1375
1440
  "clean",
@@ -1653,9 +1718,38 @@ function parseCleanVerb(rest) {
1653
1718
  all: parsed.value.values.all === true
1654
1719
  };
1655
1720
  }
1721
+ function parseTelemetryVerb(rest) {
1722
+ const parsed = tryParse(() => parseArgs({
1723
+ args: rest,
1724
+ allowPositionals: true,
1725
+ options: { help: {
1726
+ type: "boolean",
1727
+ short: "h"
1728
+ } }
1729
+ }));
1730
+ if (!parsed.ok) return {
1731
+ kind: "error",
1732
+ message: parsed.message
1733
+ };
1734
+ const { values, positionals } = parsed.value;
1735
+ if (values.help) return {
1736
+ kind: "help",
1737
+ topic: "telemetry"
1738
+ };
1739
+ const action = positionals[0] ?? "status";
1740
+ if (positionals.length > 1 || action !== "status" && action !== "on" && action !== "off") return {
1741
+ kind: "error",
1742
+ message: "telemetry takes one of: status, on, off"
1743
+ };
1744
+ return {
1745
+ kind: "telemetry",
1746
+ action
1747
+ };
1748
+ }
1656
1749
  function parseVerb(verb, rest) {
1657
1750
  if (verb === "layers") return parseLayersVerb(rest);
1658
1751
  if (verb === "clean") return parseCleanVerb(rest);
1752
+ if (verb === "telemetry") return parseTelemetryVerb(rest);
1659
1753
  const parsed = tryParse(() => parseArgs({
1660
1754
  args: rest,
1661
1755
  allowPositionals: true,
@@ -5993,6 +6087,286 @@ var ChangesetStore = class {
5993
6087
  }
5994
6088
  };
5995
6089
  //#endregion
6090
+ //#region src/server/telemetry.ts
6091
+ /**
6092
+ * Anonymous usage data, Homebrew-style: on by default, announced in the review
6093
+ * page before the first event leaves, one command or variable to turn off. Two
6094
+ * events per review, `review_opened` and `review_finished`, and one last
6095
+ * `telemetry_disabled` when a machine that has reported turns it off; `send`
6096
+ * below is the authoritative list of what they carry, and the docs page links here. Never
6097
+ * paths, repo or branch names, code, comment text, or anything IP-derived. The
6098
+ * one identifier is a random UUID per machine, in a file of its own
6099
+ * (`~/.diffo/telemetry-id`) rather than the database: `DIFFO_DB` moves the
6100
+ * database, and one machine must count as one machine wherever it keeps state.
6101
+ */
6102
+ const TELEMETRY_DOCS_URL = "https://diffohq.github.io/diffo/telemetry";
6103
+ /** PostHog Cloud, EU region. */
6104
+ const TELEMETRY_ENDPOINT = "https://eu.i.posthog.com/capture/";
6105
+ /** Public and write-only by design: adds events, reads nothing. */
6106
+ const TELEMETRY_KEY = "phc_nZqoJQbyccraPZwxsHaaAUPjf5z8k2QqdnWYedyhQzCx";
6107
+ /** A blocked host must never be felt. */
6108
+ const SEND_TIMEOUT_MS = 2e3;
6109
+ function defaultMachineIdPath() {
6110
+ return join(homedir(), ".diffo", "telemetry-id");
6111
+ }
6112
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6113
+ /** One UUID and a newline, owner-readable like the database next to it. */
6114
+ function fileMachineIdStore(path = defaultMachineIdPath()) {
6115
+ const read = () => {
6116
+ try {
6117
+ const id = readFileSync(path, "utf8").trim();
6118
+ return UUID.test(id) ? id : null;
6119
+ } catch {
6120
+ return null;
6121
+ }
6122
+ };
6123
+ const write = (id, flag) => {
6124
+ try {
6125
+ mkdirSync(dirname(path), {
6126
+ recursive: true,
6127
+ mode: 448
6128
+ });
6129
+ writeFileSync(path, `${id}\n`, {
6130
+ flag,
6131
+ mode: 384
6132
+ });
6133
+ return id;
6134
+ } catch (err) {
6135
+ if (flag === "wx" && err.code === "EEXIST") return read() ?? write(id, "w");
6136
+ return null;
6137
+ }
6138
+ };
6139
+ return {
6140
+ read,
6141
+ claim: (id) => write(id, "wx"),
6142
+ clear() {
6143
+ try {
6144
+ rmSync(path, { force: true });
6145
+ } catch {}
6146
+ }
6147
+ };
6148
+ }
6149
+ const KEY = {
6150
+ enabled: "telemetry.enabled",
6151
+ notice: "telemetry.notice",
6152
+ noticeAt: "telemetry.notice_at"
6153
+ };
6154
+ const DEBUG_VAR = "DIFFO_TELEMETRY_DEBUG";
6155
+ /** `0`, `false` and the empty string mean unset, as most tools read them. */
6156
+ function isSet(value) {
6157
+ if (value === void 0) return false;
6158
+ const v = value.trim().toLowerCase();
6159
+ return v !== "" && v !== "0" && v !== "false";
6160
+ }
6161
+ /** The variable that turns usage data off, in precedence order, or null. */
6162
+ function envOptOut(env) {
6163
+ if (isSet(env.DO_NOT_TRACK)) return "DO_NOT_TRACK";
6164
+ if (isSet(env.DIFFO_TELEMETRY_DISABLED)) return "DIFFO_TELEMETRY_DISABLED";
6165
+ if (isSet(env.CI)) return "CI";
6166
+ return null;
6167
+ }
6168
+ function reviewKindOf(spec, pr) {
6169
+ if (pr) return "pr";
6170
+ return spec.kind === "branch" ? "branch" : "tree";
6171
+ }
6172
+ const AGENT_NAMES = /* @__PURE__ */ new Set([...HARNESS_NAMES, "unknown"]);
6173
+ /** Whatever the CLI reported, narrowed to a name this build knows. */
6174
+ function agentNameOf(raw) {
6175
+ return typeof raw === "string" && AGENT_NAMES.has(raw) ? raw : "other";
6176
+ }
6177
+ /** Coarse on purpose: low cardinality, and nobody's afternoon timed. */
6178
+ function durationBucket(ms) {
6179
+ const min = ms / 6e4;
6180
+ if (min < 5) return "<5m";
6181
+ if (min < 15) return "5-15m";
6182
+ if (min < 30) return "15-30m";
6183
+ if (min < 60) return "30-60m";
6184
+ return "1h+";
6185
+ }
6186
+ var Telemetry = class {
6187
+ store;
6188
+ ids;
6189
+ env;
6190
+ fetchImpl;
6191
+ now;
6192
+ log;
6193
+ dev;
6194
+ base;
6195
+ /** Decided at open and kept for the finish, so the review that first shows the
6196
+ * notice reports nothing at all. Undefined until the first open. */
6197
+ armedReview;
6198
+ openedAt;
6199
+ inFlight = [];
6200
+ /** The id this process settled on when the disk refused to keep one. */
6201
+ fallbackId;
6202
+ constructor(deps) {
6203
+ this.store = deps.store;
6204
+ this.ids = deps.ids;
6205
+ this.env = deps.env ?? process.env;
6206
+ this.fetchImpl = deps.fetch ?? globalThis.fetch;
6207
+ this.now = deps.now ?? Date.now;
6208
+ this.log = deps.log ?? (() => {});
6209
+ this.dev = deps.dev ?? false;
6210
+ this.base = {
6211
+ version: deps.version ?? VERSION,
6212
+ platform: deps.platform ?? process.platform,
6213
+ arch: deps.arch ?? process.arch,
6214
+ node: deps.nodeMajor ?? Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10),
6215
+ dev: this.dev
6216
+ };
6217
+ }
6218
+ status() {
6219
+ const shared = {
6220
+ notice: this.notice(),
6221
+ machineId: this.readMachineId(),
6222
+ dev: this.dev,
6223
+ docs: TELEMETRY_DOCS_URL
6224
+ };
6225
+ const variable = envOptOut(this.env);
6226
+ if (variable !== null) return {
6227
+ enabled: false,
6228
+ source: "env",
6229
+ variable,
6230
+ ...shared
6231
+ };
6232
+ if (this.store.get(KEY.enabled) === "false") return {
6233
+ enabled: false,
6234
+ source: "setting",
6235
+ ...shared
6236
+ };
6237
+ return {
6238
+ enabled: true,
6239
+ source: "default",
6240
+ ...shared
6241
+ };
6242
+ }
6243
+ /**
6244
+ * Either way the machine id goes: back on means a new history, not the old one.
6245
+ * Turning off a machine that has reported sends one last `telemetry_disabled`,
6246
+ * so opt-outs can be counted; a machine that never reported, or one an
6247
+ * environment variable already silenced, sends nothing. Returns whether it did.
6248
+ */
6249
+ setEnabled(on, source) {
6250
+ const farewell = !on && this.status().enabled && this.readMachineId() !== null;
6251
+ if (farewell) this.send("telemetry_disabled", { source });
6252
+ this.store.set(KEY.enabled, on ? "true" : "false");
6253
+ this.ids.clear();
6254
+ this.fallbackId = void 0;
6255
+ if (on) this.store.set(KEY.notice, "acknowledged");
6256
+ return farewell;
6257
+ }
6258
+ /** The review page rendered the notice; nothing is sent before this. */
6259
+ noticeShown() {
6260
+ if (this.notice() !== "pending") return;
6261
+ this.store.set(KEY.notice, "shown");
6262
+ this.store.set(KEY.noticeAt, new Date(this.now()).toISOString());
6263
+ }
6264
+ /** The reviewer dismissed the notice; it stays gone. */
6265
+ acknowledge() {
6266
+ if (this.notice() === "pending") this.noticeShown();
6267
+ this.store.set(KEY.notice, "acknowledged");
6268
+ }
6269
+ /** A `diffo` open landed. Decides whether this review reports, and says so. */
6270
+ reviewOpened(props) {
6271
+ const armed = this.armed();
6272
+ this.armedReview = armed;
6273
+ this.openedAt = this.now();
6274
+ if (armed) this.send("review_opened", {
6275
+ kind: props.kind,
6276
+ agent: props.agent
6277
+ });
6278
+ return armed;
6279
+ }
6280
+ /** Only for a review that was opened reporting, and is still on. */
6281
+ reviewFinished(props) {
6282
+ if (this.armedReview !== true || !this.armed()) return;
6283
+ this.send("review_finished", {
6284
+ kind: props.kind,
6285
+ threads: props.threads,
6286
+ comments: props.comments,
6287
+ layers: props.layers,
6288
+ ...props.event ? { event: props.event } : {},
6289
+ ...this.openedAt !== void 0 ? { duration: durationBucket(this.now() - this.openedAt) } : {}
6290
+ });
6291
+ }
6292
+ /** Every send so far, settled. Tests, and an orderly shutdown. */
6293
+ async flush() {
6294
+ await Promise.all(this.inFlight.splice(0));
6295
+ }
6296
+ notice() {
6297
+ const raw = this.store.get(KEY.notice);
6298
+ return raw === "shown" || raw === "acknowledged" ? raw : "pending";
6299
+ }
6300
+ /** On, and the notice has been on a screen at least once. */
6301
+ armed() {
6302
+ return this.status().enabled && this.notice() !== "pending";
6303
+ }
6304
+ readMachineId() {
6305
+ return this.ids.read() ?? this.fallbackId ?? null;
6306
+ }
6307
+ /** The id on disk, minted on the first send; a disk that refuses it gets one
6308
+ * id per process rather than one per event. */
6309
+ machineId() {
6310
+ const existing = this.readMachineId();
6311
+ if (existing) return existing;
6312
+ const id = randomUUID();
6313
+ const claimed = this.ids.claim(id);
6314
+ if (claimed === null) this.fallbackId = id;
6315
+ return claimed ?? id;
6316
+ }
6317
+ send(event, properties) {
6318
+ const payload = {
6319
+ api_key: TELEMETRY_KEY,
6320
+ event,
6321
+ distinct_id: this.machineId(),
6322
+ timestamp: new Date(this.now()).toISOString(),
6323
+ properties: {
6324
+ $lib: "diffo",
6325
+ $lib_version: this.base.version,
6326
+ $process_person_profile: false,
6327
+ $geoip_disable: true,
6328
+ ...this.base,
6329
+ ...properties
6330
+ }
6331
+ };
6332
+ if (isSet(this.env[DEBUG_VAR])) this.log(`telemetry: ${JSON.stringify(payload)}`);
6333
+ let request;
6334
+ try {
6335
+ request = this.fetchImpl(TELEMETRY_ENDPOINT, {
6336
+ method: "POST",
6337
+ headers: { "Content-Type": "application/json" },
6338
+ body: JSON.stringify(payload),
6339
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
6340
+ });
6341
+ } catch {
6342
+ request = Promise.resolve();
6343
+ }
6344
+ const debug = isSet(this.env[DEBUG_VAR]);
6345
+ this.inFlight.push(request.then((res) => {
6346
+ if (debug) this.log(`telemetry: ${event} → ${res?.status ?? "sent"}`);
6347
+ }, (err) => {
6348
+ if (debug) this.log(`telemetry: ${event} failed: ${err?.message ?? String(err)}`);
6349
+ }));
6350
+ }
6351
+ };
6352
+ /** `diffo telemetry [status|on|off]`, for a human's terminal. */
6353
+ function describeTelemetry(status, asked, farewell = false) {
6354
+ const lines = [];
6355
+ if (status.enabled) {
6356
+ lines.push(asked === "on" ? "usage data: on (turned on)" : "usage data: on", " what: two anonymous events per review (opened, finished) — version, OS,", " kind of review, agent, counts. Never code, paths, or comment text", " off: diffo telemetry off, or DIFFO_TELEMETRY_DISABLED=1 (DO_NOT_TRACK=1 counts too)");
6357
+ if (status.notice === "pending") lines.push(" note: nothing has been sent yet; the review page shows a notice first");
6358
+ if (status.dev) lines.push(" dev: running from a source checkout; every event carries dev: true");
6359
+ } else {
6360
+ const why = status.source === "env" ? `${status.variable} is set` : asked === "off" ? "turned off" : "turned off with `diffo telemetry off`";
6361
+ lines.push(`usage data: off (${why})`);
6362
+ if (asked === "off" && farewell) lines.push(" sent: one last event saying this machine turned it off; nothing follows");
6363
+ if (asked === "on" && status.source === "env") lines.push(` note: it stays off while ${status.variable} is set`);
6364
+ if (status.source === "setting") lines.push(" on: diffo telemetry on");
6365
+ }
6366
+ lines.push(` more: ${status.docs}`);
6367
+ return lines.join("\n");
6368
+ }
6369
+ //#endregion
5996
6370
  //#region src/server/watcher.ts
5997
6371
  const DEBOUNCE_MS = 300;
5998
6372
  const MAX_COOLDOWN_MS = 5e3;
@@ -6185,6 +6559,31 @@ function createApp(ctx, store, review, queue) {
6185
6559
  ctx.uiSettings.set("theme", theme);
6186
6560
  return c.json({ ok: true });
6187
6561
  });
6562
+ const reviewKind = () => reviewKindOf(ctx.spec, ctx.pr !== void 0);
6563
+ app.get("/api/telemetry", (c) => {
6564
+ if (!ctx.telemetry) return c.json({ error: "telemetry unavailable" }, 503);
6565
+ return c.json(ctx.telemetry.status());
6566
+ });
6567
+ app.put("/api/telemetry", async (c) => {
6568
+ if (!ctx.telemetry) return c.json({ error: "telemetry unavailable" }, 503);
6569
+ const body = await c.req.json().catch(() => null);
6570
+ if (typeof body?.enabled === "boolean") ctx.telemetry.setEnabled(body.enabled, "ui");
6571
+ else if (body?.notice === "shown") ctx.telemetry.noticeShown();
6572
+ else if (body?.notice === "acknowledged") ctx.telemetry.acknowledge();
6573
+ else return c.json({ error: "need {enabled: boolean} or {notice: \"shown\" | \"acknowledged\"}" }, 400);
6574
+ return c.json(ctx.telemetry.status());
6575
+ });
6576
+ app.post("/api/telemetry/opened", async (c) => {
6577
+ const body = await c.req.json().catch(() => null);
6578
+ const armed = ctx.telemetry?.reviewOpened({
6579
+ kind: reviewKind(),
6580
+ agent: agentNameOf(body?.agent)
6581
+ }) ?? false;
6582
+ return c.json({
6583
+ ok: true,
6584
+ armed
6585
+ });
6586
+ });
6188
6587
  const repoInfo = () => store?.get().repo ?? {
6189
6588
  path: resolve(ctx.root),
6190
6589
  name: "",
@@ -6543,6 +6942,13 @@ function createApp(ctx, store, review, queue) {
6543
6942
  queue.enqueueFinish(coverage);
6544
6943
  }
6545
6944
  review.clearWithheld(batch.map((t) => t.id));
6945
+ ctx.telemetry?.reviewFinished({
6946
+ kind: reviewKind(),
6947
+ threads: batch.length,
6948
+ comments: batch.reduce((n, t) => n + t.messages.filter((m) => m.author === "reviewer").length, 0),
6949
+ layers: (review.get().layers?.items.length ?? 0) > 0,
6950
+ ...event !== null ? { event } : {}
6951
+ });
6546
6952
  return c.json({
6547
6953
  threads: review.get().threads,
6548
6954
  prompt,
@@ -6870,6 +7276,15 @@ function startServer(options) {
6870
7276
  queue.rescope(store.get().repo.branch);
6871
7277
  rehydrateQueue(review, queue);
6872
7278
  const log = (message) => console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false })}] ${message}`);
7279
+ const telemetry = options.telemetry ?? new Telemetry({
7280
+ store: {
7281
+ get: (key) => db.getUiSetting(key),
7282
+ set: (key, value) => db.setUiSetting(key, value)
7283
+ },
7284
+ ids: fileMachineIdStore(),
7285
+ log,
7286
+ dev: SRC_STAMP !== null || IS_DEV
7287
+ });
6873
7288
  queue.subscribe((presence) => log(`agent ${presence}`));
6874
7289
  const unsubscribeBatch = queue.onBatchClosed((closed) => {
6875
7290
  const took = Math.round((closed.closedAt - closed.deliveredAt) / 1e3);
@@ -6931,6 +7346,7 @@ function startServer(options) {
6931
7346
  ...options,
6932
7347
  ...prContext ? { pr: prContext } : {},
6933
7348
  idle,
7349
+ telemetry,
6934
7350
  onShutdownRequest: options.onShutdownRequest ?? (() => process.exit(0)),
6935
7351
  uiSettings: {
6936
7352
  get: (key) => db.getUiSetting(key),
@@ -6963,6 +7379,7 @@ function startServer(options) {
6963
7379
  store,
6964
7380
  review,
6965
7381
  queue,
7382
+ telemetry,
6966
7383
  stopWatching: async () => {
6967
7384
  idle?.stop();
6968
7385
  puller?.stop();
@@ -7686,6 +8103,21 @@ function printBaseHint(spec, files) {
7686
8103
  if (!hint) return;
7687
8104
  console.log(`this branch has ${hint.commits} ${hint.commits === 1 ? "commit" : "commits"} since ${hint.base}. Review them with \`diffo --base ${hint.base}\``);
7688
8105
  }
8106
+ /**
8107
+ * Tells the server a review was opened, and by which agent — the server decides
8108
+ * whether that is reported (see server/telemetry.ts). Best effort: an open never
8109
+ * waits on, or fails over, usage data.
8110
+ */
8111
+ async function announceOpen(port) {
8112
+ try {
8113
+ await fetch(apiUrl(port, "/api/telemetry/opened"), {
8114
+ method: "POST",
8115
+ headers: { "Content-Type": "application/json" },
8116
+ body: JSON.stringify({ agent: detectHarness() ?? "unknown" }),
8117
+ signal: AbortSignal.timeout(2e3)
8118
+ });
8119
+ } catch {}
8120
+ }
7689
8121
  async function fetchChangesetInfo(port) {
7690
8122
  try {
7691
8123
  return await (await fetch(apiUrl(port, "/api/changeset"), { signal: AbortSignal.timeout(2e3) })).json();
@@ -7829,6 +8261,26 @@ if (command.kind === "stop") {
7829
8261
  }
7830
8262
  process.exit(0);
7831
8263
  }
8264
+ if (command.kind === "telemetry") {
8265
+ const db = new DiffoDb();
8266
+ try {
8267
+ const telemetry = new Telemetry({
8268
+ store: {
8269
+ get: (key) => db.getUiSetting(key),
8270
+ set: (key, value) => db.setUiSetting(key, value)
8271
+ },
8272
+ ids: fileMachineIdStore(),
8273
+ log: (line) => process.stderr.write(`diffo: ${line}\n`),
8274
+ dev: SRC_STAMP !== null || process.env.ENV === "development"
8275
+ });
8276
+ const farewell = command.action === "status" ? false : telemetry.setEnabled(command.action === "on", "cli");
8277
+ console.log(describeTelemetry(telemetry.status(), command.action, farewell));
8278
+ await telemetry.flush();
8279
+ } finally {
8280
+ db.close();
8281
+ }
8282
+ process.exit(0);
8283
+ }
7832
8284
  if (command.kind === "poll") {
7833
8285
  const port = await requireServer();
7834
8286
  process.stderr.write("diffo: waiting for the reviewer. Keep this process attended: a tracked\nbackground task or the foreground, never detached. If it dies, just\nre-run it; feedback is held in the review and survives.\n");
@@ -8047,6 +8499,7 @@ if (existing !== null && takeOverPort === null) {
8047
8499
  const url = reviewUrl(existing.port);
8048
8500
  console.log(prOpen ? "diffo is already watching this pull request" : "diffo is already watching this repo");
8049
8501
  await warnSpecMismatch(existing.port, spec);
8502
+ await announceOpen(existing.port);
8050
8503
  console.log(`→ ${url}`);
8051
8504
  await printAgentNextStep(existing.port, prOpen?.pr);
8052
8505
  process.exit(0);
@@ -8066,6 +8519,7 @@ if (existing !== null && takeOverPort !== null) {
8066
8519
  }
8067
8520
  if (!command.foreground) {
8068
8521
  const daemonPort = await requireServer(command.port, spec.kind === "branch" ? spec.base : void 0, SRC_STAMP, prOpen ? command.target : void 0);
8522
+ await announceOpen(daemonPort);
8069
8523
  const url = reviewUrl(daemonPort);
8070
8524
  await printChangesetSummary(daemonPort);
8071
8525
  console.log(`→ ${url}`);
@@ -8107,6 +8561,10 @@ try {
8107
8561
  }
8108
8562
  throw err;
8109
8563
  }
8564
+ started.telemetry.reviewOpened({
8565
+ kind: reviewKindOf(spec, prOpen !== null),
8566
+ agent: detectHarness() ?? "unknown"
8567
+ });
8110
8568
  const { stats, repo } = started.store.get();
8111
8569
  const url = reviewUrl(port);
8112
8570
  const label = prOpen ? `pull request #${prOpen.pr.number} "${prOpen.pr.title}"` : specLabel(spec);
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-CCoRJPAh.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BWz11yEV.js";import"./chunk-O7XYJQB3-Bb8gIxaB.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-BWkvnYRd.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-SnjyC0ZV.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
1
+ import{m as e,t}from"./mermaid-parser.core-CwcOI9nG.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-Bmfs-TFn.js";import"./chunk-O7XYJQB3-BjcjInBL.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-at0ENjhB.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-BabKb2iQ.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
@@ -1 +1 @@
1
- import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-uIEEXDIm.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-Cezvykkn.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -0,0 +1 @@
1
+ import"./chunk-FOHPRMQF-DzxwRta7.js";import{M as e}from"./mermaid-parser.core-CwcOI9nG.js";export{e as createArchitectureServices};