@michaelmagan/dnumb 0.1.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 (133) hide show
  1. package/AGENTS.md +180 -0
  2. package/LICENSE +21 -0
  3. package/README.md +297 -0
  4. package/dist/analysis/cluster.cjs +114 -0
  5. package/dist/analysis/cluster.d.cts +68 -0
  6. package/dist/analysis/cluster.d.ts +68 -0
  7. package/dist/analysis/cluster.js +109 -0
  8. package/dist/analysis/distances.cjs +124 -0
  9. package/dist/analysis/distances.d.cts +58 -0
  10. package/dist/analysis/distances.d.ts +58 -0
  11. package/dist/analysis/distances.js +119 -0
  12. package/dist/analysis/geometry.cjs +60 -0
  13. package/dist/analysis/geometry.d.cts +34 -0
  14. package/dist/analysis/geometry.d.ts +34 -0
  15. package/dist/analysis/geometry.js +53 -0
  16. package/dist/analysis/layout-tree.cjs +245 -0
  17. package/dist/analysis/layout-tree.d.cts +45 -0
  18. package/dist/analysis/layout-tree.d.ts +45 -0
  19. package/dist/analysis/layout-tree.js +242 -0
  20. package/dist/analysis/region-query.cjs +71 -0
  21. package/dist/analysis/region-query.d.cts +25 -0
  22. package/dist/analysis/region-query.d.ts +25 -0
  23. package/dist/analysis/region-query.js +67 -0
  24. package/dist/analysis/types.cjs +7 -0
  25. package/dist/analysis/types.d.cts +143 -0
  26. package/dist/analysis/types.d.ts +143 -0
  27. package/dist/analysis/types.js +6 -0
  28. package/dist/bundle.cjs +182 -0
  29. package/dist/bundle.d.cts +91 -0
  30. package/dist/bundle.d.ts +91 -0
  31. package/dist/bundle.js +175 -0
  32. package/dist/cli.cjs +755 -0
  33. package/dist/cli.d.cts +2 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +719 -0
  36. package/dist/diff/changes.cjs +656 -0
  37. package/dist/diff/changes.d.cts +2 -0
  38. package/dist/diff/changes.d.ts +2 -0
  39. package/dist/diff/changes.js +653 -0
  40. package/dist/diff/identity.cjs +167 -0
  41. package/dist/diff/identity.d.cts +62 -0
  42. package/dist/diff/identity.d.ts +62 -0
  43. package/dist/diff/identity.js +159 -0
  44. package/dist/diff/index.cjs +84 -0
  45. package/dist/diff/index.d.cts +76 -0
  46. package/dist/diff/index.d.ts +76 -0
  47. package/dist/diff/index.js +68 -0
  48. package/dist/diff/match.cjs +156 -0
  49. package/dist/diff/match.d.cts +17 -0
  50. package/dist/diff/match.d.ts +17 -0
  51. package/dist/diff/match.js +152 -0
  52. package/dist/diff/render.cjs +540 -0
  53. package/dist/diff/render.d.cts +2 -0
  54. package/dist/diff/render.d.ts +2 -0
  55. package/dist/diff/render.js +537 -0
  56. package/dist/diff/types.cjs +2 -0
  57. package/dist/diff/types.d.cts +211 -0
  58. package/dist/diff/types.d.ts +211 -0
  59. package/dist/diff/types.js +1 -0
  60. package/dist/digest/index.cjs +8 -0
  61. package/dist/digest/index.d.cts +4 -0
  62. package/dist/digest/index.d.ts +4 -0
  63. package/dist/digest/index.js +2 -0
  64. package/dist/digest/parse.cjs +163 -0
  65. package/dist/digest/parse.d.cts +80 -0
  66. package/dist/digest/parse.d.ts +80 -0
  67. package/dist/digest/parse.js +160 -0
  68. package/dist/digest/render.cjs +1575 -0
  69. package/dist/digest/render.d.cts +41 -0
  70. package/dist/digest/render.d.ts +41 -0
  71. package/dist/digest/render.js +1571 -0
  72. package/dist/digest/types.cjs +10 -0
  73. package/dist/digest/types.d.cts +86 -0
  74. package/dist/digest/types.d.ts +86 -0
  75. package/dist/digest/types.js +9 -0
  76. package/dist/index.cjs +61 -0
  77. package/dist/index.d.cts +27 -0
  78. package/dist/index.d.ts +27 -0
  79. package/dist/index.js +19 -0
  80. package/dist/page.cjs +35 -0
  81. package/dist/page.d.cts +40 -0
  82. package/dist/page.d.ts +40 -0
  83. package/dist/page.js +31 -0
  84. package/dist/playwright.cjs +138 -0
  85. package/dist/playwright.d.cts +69 -0
  86. package/dist/playwright.d.ts +69 -0
  87. package/dist/playwright.js +131 -0
  88. package/dist/query.cjs +1152 -0
  89. package/dist/query.d.cts +47 -0
  90. package/dist/query.d.ts +47 -0
  91. package/dist/query.js +1146 -0
  92. package/dist/snapshot/capture.cjs +80 -0
  93. package/dist/snapshot/capture.d.cts +34 -0
  94. package/dist/snapshot/capture.d.ts +34 -0
  95. package/dist/snapshot/capture.js +77 -0
  96. package/dist/snapshot/cdp.cjs +450 -0
  97. package/dist/snapshot/cdp.d.cts +36 -0
  98. package/dist/snapshot/cdp.d.ts +36 -0
  99. package/dist/snapshot/cdp.js +447 -0
  100. package/dist/snapshot/clip.cjs +100 -0
  101. package/dist/snapshot/clip.d.cts +26 -0
  102. package/dist/snapshot/clip.d.ts +26 -0
  103. package/dist/snapshot/clip.js +96 -0
  104. package/dist/snapshot/collect.cjs +358 -0
  105. package/dist/snapshot/collect.d.cts +18 -0
  106. package/dist/snapshot/collect.d.ts +18 -0
  107. package/dist/snapshot/collect.js +355 -0
  108. package/dist/snapshot/color.cjs +1039 -0
  109. package/dist/snapshot/color.d.cts +316 -0
  110. package/dist/snapshot/color.d.ts +316 -0
  111. package/dist/snapshot/color.js +1026 -0
  112. package/dist/snapshot/facts.cjs +167 -0
  113. package/dist/snapshot/facts.d.cts +48 -0
  114. package/dist/snapshot/facts.d.ts +48 -0
  115. package/dist/snapshot/facts.js +161 -0
  116. package/dist/snapshot/limits.cjs +69 -0
  117. package/dist/snapshot/limits.d.cts +59 -0
  118. package/dist/snapshot/limits.d.ts +59 -0
  119. package/dist/snapshot/limits.js +65 -0
  120. package/dist/snapshot/not-measured.cjs +44 -0
  121. package/dist/snapshot/not-measured.d.cts +14 -0
  122. package/dist/snapshot/not-measured.d.ts +14 -0
  123. package/dist/snapshot/not-measured.js +41 -0
  124. package/dist/snapshot/types.cjs +2 -0
  125. package/dist/snapshot/types.d.cts +238 -0
  126. package/dist/snapshot/types.d.ts +238 -0
  127. package/dist/snapshot/types.js +1 -0
  128. package/dist/spec/index.cjs +9 -0
  129. package/dist/spec/index.d.cts +15 -0
  130. package/dist/spec/index.d.ts +15 -0
  131. package/dist/spec/index.js +6 -0
  132. package/package.json +141 -0
  133. package/skills/dnumb/SKILL.md +136 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Parses one line of a rendered digest back into structured data. This is what
3
+ * makes the format machine-readable as well as agent-readable: a consumer that
4
+ * wants numbers rather than text does not have to re-implement the grammar.
5
+ *
6
+ * Ported from the prototype, where it round-tripped every golden digest, plus
7
+ * the v2 addition: facts.
8
+ */
9
+ export interface ParsedGap {
10
+ direction: 'down' | 'right';
11
+ value: number;
12
+ overlap: boolean;
13
+ }
14
+ export interface ParsedElementLine {
15
+ kind: 'element';
16
+ id: string;
17
+ tag: string;
18
+ /** From `.role` or `#testid`, whichever was rendered; bare, no sigil. */
19
+ annotation: string | null;
20
+ text: string | null;
21
+ rect: {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+ flow: 'row' | 'column' | 'grid' | 'none';
28
+ gap: ParsedGap | null;
29
+ /** Fact bodies, without their brackets, in the order rendered. */
30
+ facts: string[];
31
+ belowFold: boolean;
32
+ /** Tree depth, from the line's indentation. */
33
+ depth: number;
34
+ }
35
+ export interface ParsedFoldEntry {
36
+ id: string;
37
+ x: number;
38
+ y: number;
39
+ gap: ParsedGap | null;
40
+ }
41
+ export interface ParsedFoldLine {
42
+ kind: 'fold';
43
+ count: number;
44
+ likeId: string;
45
+ /** Size of the repeating unit the fold covers. `1` for a plain run of
46
+ * identical siblings; `>1` for a periodic run (story/subtext/spacer, `dt`/
47
+ * `dd`, ...) where only the first whole period was printed in full. */
48
+ period: number;
49
+ /** The `(...)` size-spread token, verbatim and unparsed, or `null` when the
50
+ * folded members were uniform enough that none was emitted. A `period > 1`
51
+ * fold's spread is per phase (`phase0 h 2-90`) rather than pooled. */
52
+ variance: string | null;
53
+ entries: ParsedFoldEntry[];
54
+ }
55
+ export interface ParsedCollapsedLine {
56
+ kind: 'collapsed';
57
+ nodeCount: number;
58
+ /** Elements carrying a fact inside the collapsed subtree. `0` when the
59
+ * renderer omitted the clause -- a real, counted zero, not "unknown" --
60
+ * see `digest/render.ts#countFacts`. */
61
+ factCount: number;
62
+ bbox: {
63
+ x: number;
64
+ y: number;
65
+ width: number;
66
+ height: number;
67
+ };
68
+ /**
69
+ * The id `--focus` reaches this collapsed subtree through -- always
70
+ * present; `digest/render.ts#renderChildren` prints it on every
71
+ * `[folded: …]` line unconditionally (it is the collapsed container's own
72
+ * id, already printed on the line directly above), unlike the fold line's
73
+ * `…+N not listed (--focus <id>)` hint, which is conditional on there
74
+ * being a hidden tail at all.
75
+ */
76
+ focusHint: string;
77
+ }
78
+ export type ParsedLine = ParsedElementLine | ParsedFoldLine | ParsedCollapsedLine;
79
+ /** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
80
+ export declare function parseDigestLine(line: string): ParsedLine | null;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Parses one line of a rendered digest back into structured data. This is what
3
+ * makes the format machine-readable as well as agent-readable: a consumer that
4
+ * wants numbers rather than text does not have to re-implement the grammar.
5
+ *
6
+ * Ported from the prototype, where it round-tripped every golden digest, plus
7
+ * the v2 addition: facts.
8
+ */
9
+ export interface ParsedGap {
10
+ direction: 'down' | 'right';
11
+ value: number;
12
+ overlap: boolean;
13
+ }
14
+ export interface ParsedElementLine {
15
+ kind: 'element';
16
+ id: string;
17
+ tag: string;
18
+ /** From `.role` or `#testid`, whichever was rendered; bare, no sigil. */
19
+ annotation: string | null;
20
+ text: string | null;
21
+ rect: {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+ flow: 'row' | 'column' | 'grid' | 'none';
28
+ gap: ParsedGap | null;
29
+ /** Fact bodies, without their brackets, in the order rendered. */
30
+ facts: string[];
31
+ belowFold: boolean;
32
+ /** Tree depth, from the line's indentation. */
33
+ depth: number;
34
+ }
35
+ export interface ParsedFoldEntry {
36
+ id: string;
37
+ x: number;
38
+ y: number;
39
+ gap: ParsedGap | null;
40
+ }
41
+ export interface ParsedFoldLine {
42
+ kind: 'fold';
43
+ count: number;
44
+ likeId: string;
45
+ /** Size of the repeating unit the fold covers. `1` for a plain run of
46
+ * identical siblings; `>1` for a periodic run (story/subtext/spacer, `dt`/
47
+ * `dd`, ...) where only the first whole period was printed in full. */
48
+ period: number;
49
+ /** The `(...)` size-spread token, verbatim and unparsed, or `null` when the
50
+ * folded members were uniform enough that none was emitted. A `period > 1`
51
+ * fold's spread is per phase (`phase0 h 2-90`) rather than pooled. */
52
+ variance: string | null;
53
+ entries: ParsedFoldEntry[];
54
+ }
55
+ export interface ParsedCollapsedLine {
56
+ kind: 'collapsed';
57
+ nodeCount: number;
58
+ /** Elements carrying a fact inside the collapsed subtree. `0` when the
59
+ * renderer omitted the clause -- a real, counted zero, not "unknown" --
60
+ * see `digest/render.ts#countFacts`. */
61
+ factCount: number;
62
+ bbox: {
63
+ x: number;
64
+ y: number;
65
+ width: number;
66
+ height: number;
67
+ };
68
+ /**
69
+ * The id `--focus` reaches this collapsed subtree through -- always
70
+ * present; `digest/render.ts#renderChildren` prints it on every
71
+ * `[folded: …]` line unconditionally (it is the collapsed container's own
72
+ * id, already printed on the line directly above), unlike the fold line's
73
+ * `…+N not listed (--focus <id>)` hint, which is conditional on there
74
+ * being a hidden tail at all.
75
+ */
76
+ focusHint: string;
77
+ }
78
+ export type ParsedLine = ParsedElementLine | ParsedFoldLine | ParsedCollapsedLine;
79
+ /** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
80
+ export declare function parseDigestLine(line: string): ParsedLine | null;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Parses one line of a rendered digest back into structured data. This is what
3
+ * makes the format machine-readable as well as agent-readable: a consumer that
4
+ * wants numbers rather than text does not have to re-implement the grammar.
5
+ *
6
+ * Ported from the prototype, where it round-tripped every golden digest, plus
7
+ * the v2 addition: facts.
8
+ */
9
+ const GAP_RE = /([↓→])(-?\d+)( OVERLAP)?/;
10
+ const POS_SIZE_RE = /@(-?\d+),(-?\d+)\s+(\d+)x(\d+)/;
11
+ function parseGapToken(token) {
12
+ if (!token)
13
+ return null;
14
+ const match = GAP_RE.exec(token);
15
+ if (!match)
16
+ return null;
17
+ const [, arrow, valueStr, overlapStr] = match;
18
+ return {
19
+ direction: arrow === '↓' ? 'down' : 'right',
20
+ value: Number(valueStr),
21
+ overlap: Boolean(overlapStr),
22
+ };
23
+ }
24
+ /** A fold line's per-entry list: `id @x,y [gap], id @x,y [gap], ...`. */
25
+ function parseFoldEntries(list) {
26
+ const entries = [];
27
+ // Entries never contain a literal ", " -- ids, positions and gaps cannot --
28
+ // so a plain split is safe.
29
+ for (const part of list.split(', ')) {
30
+ const match = /^(\S+)\s+@(-?\d+),(-?\d+)(.*)$/.exec(part.trim());
31
+ if (!match)
32
+ continue;
33
+ const [, id, xStr, yStr, rest] = match;
34
+ if (!id || xStr === undefined || yStr === undefined)
35
+ continue;
36
+ entries.push({ id, x: Number(xStr), y: Number(yStr), gap: parseGapToken(rest ?? null) });
37
+ }
38
+ return entries;
39
+ }
40
+ /**
41
+ * Every `[...]` group after the geometry that is not a known structural token.
42
+ * Facts are open-ended by design — the renderer must be able to add one without
43
+ * the parser needing to know its name first.
44
+ */
45
+ function parseFacts(after) {
46
+ const structural = /^(row|column|grid|below fold|wraps \d+)$/;
47
+ const facts = [];
48
+ for (const match of after.matchAll(/\[([^\]]+)\]/g)) {
49
+ const body = match[1];
50
+ if (body && !structural.test(body))
51
+ facts.push(body);
52
+ }
53
+ return facts;
54
+ }
55
+ /** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
56
+ export function parseDigestLine(line) {
57
+ const trimmed = line.trim();
58
+ if (trimmed === '' || trimmed.startsWith('#'))
59
+ return null;
60
+ const depth = Math.floor((line.length - line.trimStart().length) / 2);
61
+ // The `, <K> facts` clause is optional -- present only when the collapsed
62
+ // subtree hides at least one fact-carrying element (`digest/render.ts`'s
63
+ // `renderChildren`) -- so it is its own optional group rather than folded
64
+ // into a catch-all, the same discipline the fold line's variance token
65
+ // uses. `(--focus <id>)` is not optional in what the renderer emits (every
66
+ // `[folded: …]` line carries it -- P1-1: the collapse line used to be the
67
+ // one place in the whole format that disclosed the most while pointing
68
+ // nowhere), but the group here is still `?` so a hand-built or
69
+ // differently-versioned line missing it still parses rather than going
70
+ // `null`.
71
+ const collapsed = /^\[folded:\s*(\d+)\s*nodes(?:,\s*(\d+)\s*facts)?,\s*bbox\s*@(-?\d+),(-?\d+)\s+(\d+)x(\d+)(?:\s*\(--focus\s+(\S+)\))?\]$/.exec(trimmed);
72
+ if (collapsed) {
73
+ const [, count, facts, x, y, w, h, focusHint] = collapsed;
74
+ return {
75
+ kind: 'collapsed',
76
+ nodeCount: Number(count),
77
+ factCount: facts ? Number(facts) : 0,
78
+ bbox: { x: Number(x), y: Number(y), width: Number(w), height: Number(h) },
79
+ focusHint: focusHint ?? '',
80
+ };
81
+ }
82
+ // `×N[/period] more like <id>[ (variance)] — entries`. The variance and
83
+ // period groups are both explicit and optional rather than folded into a
84
+ // catch-all `\S+` for `likeId` -- a variance token has a space in it
85
+ // (`r0 (h 30-90)`), and `(\S+)\s*—` alone cannot span that, which used to
86
+ // make every folded id on a page with a size spread unparseable.
87
+ const fold = /^\[fold:\s*×(\d+)(?:\/(\d+))?\s*more like (\S+)(?:\s*(\([^()]*\)))?\s*—\s*(.+)\]$/.exec(trimmed);
88
+ if (fold) {
89
+ const [, count, period, likeId, variance, list] = fold;
90
+ if (!likeId || !list)
91
+ return null;
92
+ return {
93
+ kind: 'fold',
94
+ count: Number(count),
95
+ likeId,
96
+ period: period ? Number(period) : 1,
97
+ variance: variance ?? null,
98
+ entries: parseFoldEntries(list),
99
+ };
100
+ }
101
+ const posSize = POS_SIZE_RE.exec(trimmed);
102
+ if (!posSize)
103
+ return null;
104
+ const [, xStr, yStr, wStr, hStr] = posSize;
105
+ if (xStr === undefined || yStr === undefined || wStr === undefined || hStr === undefined) {
106
+ return null;
107
+ }
108
+ const before = trimmed.slice(0, posSize.index).trim();
109
+ const after = trimmed.slice(posSize.index + posSize[0].length);
110
+ // Text is split off first, then id/tag/annotation are parsed from what's
111
+ // left -- not one combined regex -- because the renderer does not escape
112
+ // the text it quotes, and a real accessible name can contain a literal
113
+ // `"` (ar.wikipedia.org link titles quote a word inline: `"الملحوظة"`
114
+ // inside the name). `(.*?)` is lazy, so it finds the *first* `\s+"` in
115
+ // `before` -- right after tag/annotation, since neither ever contains
116
+ // `\s+"` itself -- and the greedy `(.*)"$` then takes everything from
117
+ // there to the final `"` at the very end of the line as the text,
118
+ // embedded quotes included. Without this split, a naive `"([^"]*)"$`
119
+ // stops at the first embedded quote and the anchored `$` never matches,
120
+ // so the whole line -- id, tag, position, every fact on it -- was
121
+ // discarded.
122
+ const textMatch = /^(.*?)\s+"(.*)"$/.exec(before);
123
+ const head = textMatch ? textMatch[1] : before;
124
+ const text = textMatch ? textMatch[2] : undefined;
125
+ if (head === undefined)
126
+ return null;
127
+ // Tag: an optional `::` pseudo-element prefix (`::before`, `::after`,
128
+ // `::marker` -- real tokens on a real capture; the CDP snapshot path
129
+ // reports a pseudo-element's node name verbatim) followed by a plain tag
130
+ // name, which on a modern page is routinely a hyphenated custom-element
131
+ // name (`sl-button`, or any other web component). Annotation, when
132
+ // present, is greedy (`.*`) rather than `[^\s"]+` from its `.`/`#` sigil to
133
+ // the end of what's left: a role (`.heading`) never has whitespace in it,
134
+ // but a `data-testid` value is raw attribute text, and real pages put
135
+ // whitespace in it -- e.g. grafana.com renders
136
+ // `data-testid="data-testid navigation mega-menu"` verbatim, so the
137
+ // annotation itself is `data-testid navigation mega-menu`. The text (if
138
+ // any) is already stripped off above, so there is nothing after the
139
+ // annotation left to over-consume.
140
+ const idTagMatch = /^(\S+)\s+((?:::)?[a-zA-Z][a-zA-Z0-9-]*)([.#].*)?$/.exec(head);
141
+ if (!idTagMatch)
142
+ return null;
143
+ const [, id, tag, annotation] = idTagMatch;
144
+ if (!id || !tag)
145
+ return null;
146
+ const flow = /\[(row|column|grid)\]/.exec(after);
147
+ return {
148
+ kind: 'element',
149
+ id,
150
+ tag,
151
+ annotation: annotation ? annotation.slice(1) : null,
152
+ text: text ?? null,
153
+ rect: { x: Number(xStr), y: Number(yStr), width: Number(wStr), height: Number(hStr) },
154
+ flow: flow?.[1] ?? 'none',
155
+ gap: parseGapToken(GAP_RE.exec(after)?.[0] ?? null),
156
+ facts: parseFacts(after),
157
+ belowFold: /\[below fold\]/.test(after),
158
+ depth,
159
+ };
160
+ }