@ticktockbent/charlotte 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 (136) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +254 -0
  4. package/dist/browser/browser-manager.d.ts +14 -0
  5. package/dist/browser/browser-manager.d.ts.map +1 -0
  6. package/dist/browser/browser-manager.js +72 -0
  7. package/dist/browser/browser-manager.js.map +1 -0
  8. package/dist/browser/cdp-session.d.ts +7 -0
  9. package/dist/browser/cdp-session.d.ts.map +1 -0
  10. package/dist/browser/cdp-session.js +35 -0
  11. package/dist/browser/cdp-session.js.map +1 -0
  12. package/dist/browser/page-manager.d.ts +30 -0
  13. package/dist/browser/page-manager.d.ts.map +1 -0
  14. package/dist/browser/page-manager.js +123 -0
  15. package/dist/browser/page-manager.js.map +1 -0
  16. package/dist/dev/auditor.d.ts +39 -0
  17. package/dist/dev/auditor.d.ts.map +1 -0
  18. package/dist/dev/auditor.js +474 -0
  19. package/dist/dev/auditor.js.map +1 -0
  20. package/dist/dev/dev-mode-state.d.ts +24 -0
  21. package/dist/dev/dev-mode-state.d.ts.map +1 -0
  22. package/dist/dev/dev-mode-state.js +93 -0
  23. package/dist/dev/dev-mode-state.js.map +1 -0
  24. package/dist/dev/file-watcher.d.ts +20 -0
  25. package/dist/dev/file-watcher.d.ts.map +1 -0
  26. package/dist/dev/file-watcher.js +78 -0
  27. package/dist/dev/file-watcher.js.map +1 -0
  28. package/dist/dev/static-server.d.ts +18 -0
  29. package/dist/dev/static-server.d.ts.map +1 -0
  30. package/dist/dev/static-server.js +73 -0
  31. package/dist/dev/static-server.js.map +1 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +60 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/renderer/accessibility-extractor.d.ts +19 -0
  37. package/dist/renderer/accessibility-extractor.d.ts.map +1 -0
  38. package/dist/renderer/accessibility-extractor.js +138 -0
  39. package/dist/renderer/accessibility-extractor.js.map +1 -0
  40. package/dist/renderer/content-extractor.d.ts +6 -0
  41. package/dist/renderer/content-extractor.d.ts.map +1 -0
  42. package/dist/renderer/content-extractor.js +150 -0
  43. package/dist/renderer/content-extractor.js.map +1 -0
  44. package/dist/renderer/dom-path.d.ts +4 -0
  45. package/dist/renderer/dom-path.d.ts.map +1 -0
  46. package/dist/renderer/dom-path.js +34 -0
  47. package/dist/renderer/dom-path.js.map +1 -0
  48. package/dist/renderer/element-id-generator.d.ts +19 -0
  49. package/dist/renderer/element-id-generator.d.ts.map +1 -0
  50. package/dist/renderer/element-id-generator.js +73 -0
  51. package/dist/renderer/element-id-generator.js.map +1 -0
  52. package/dist/renderer/interactive-extractor.d.ts +13 -0
  53. package/dist/renderer/interactive-extractor.d.ts.map +1 -0
  54. package/dist/renderer/interactive-extractor.js +161 -0
  55. package/dist/renderer/interactive-extractor.js.map +1 -0
  56. package/dist/renderer/layout-extractor.d.ts +8 -0
  57. package/dist/renderer/layout-extractor.d.ts.map +1 -0
  58. package/dist/renderer/layout-extractor.js +48 -0
  59. package/dist/renderer/layout-extractor.js.map +1 -0
  60. package/dist/renderer/renderer-pipeline.d.ts +26 -0
  61. package/dist/renderer/renderer-pipeline.d.ts.map +1 -0
  62. package/dist/renderer/renderer-pipeline.js +163 -0
  63. package/dist/renderer/renderer-pipeline.js.map +1 -0
  64. package/dist/server.d.ts +19 -0
  65. package/dist/server.d.ts.map +1 -0
  66. package/dist/server.js +39 -0
  67. package/dist/server.js.map +1 -0
  68. package/dist/state/differ.d.ts +9 -0
  69. package/dist/state/differ.d.ts.map +1 -0
  70. package/dist/state/differ.js +295 -0
  71. package/dist/state/differ.js.map +1 -0
  72. package/dist/state/snapshot-store.d.ts +52 -0
  73. package/dist/state/snapshot-store.d.ts.map +1 -0
  74. package/dist/state/snapshot-store.js +98 -0
  75. package/dist/state/snapshot-store.js.map +1 -0
  76. package/dist/tools/dev-mode.d.ts +4 -0
  77. package/dist/tools/dev-mode.d.ts.map +1 -0
  78. package/dist/tools/dev-mode.js +160 -0
  79. package/dist/tools/dev-mode.js.map +1 -0
  80. package/dist/tools/evaluate.d.ts +10 -0
  81. package/dist/tools/evaluate.d.ts.map +1 -0
  82. package/dist/tools/evaluate.js +109 -0
  83. package/dist/tools/evaluate.js.map +1 -0
  84. package/dist/tools/interaction.d.ts +4 -0
  85. package/dist/tools/interaction.d.ts.map +1 -0
  86. package/dist/tools/interaction.js +680 -0
  87. package/dist/tools/interaction.js.map +1 -0
  88. package/dist/tools/navigation.d.ts +4 -0
  89. package/dist/tools/navigation.d.ts.map +1 -0
  90. package/dist/tools/navigation.js +136 -0
  91. package/dist/tools/navigation.js.map +1 -0
  92. package/dist/tools/observation.d.ts +4 -0
  93. package/dist/tools/observation.d.ts.map +1 -0
  94. package/dist/tools/observation.js +278 -0
  95. package/dist/tools/observation.js.map +1 -0
  96. package/dist/tools/session.d.ts +4 -0
  97. package/dist/tools/session.d.ts.map +1 -0
  98. package/dist/tools/session.js +372 -0
  99. package/dist/tools/session.js.map +1 -0
  100. package/dist/tools/tool-helpers.d.ts +89 -0
  101. package/dist/tools/tool-helpers.d.ts.map +1 -0
  102. package/dist/tools/tool-helpers.js +127 -0
  103. package/dist/tools/tool-helpers.js.map +1 -0
  104. package/dist/types/config.d.ts +7 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +7 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/element-id.d.ts +8 -0
  109. package/dist/types/element-id.d.ts.map +1 -0
  110. package/dist/types/element-id.js +19 -0
  111. package/dist/types/element-id.js.map +1 -0
  112. package/dist/types/errors.d.ts +22 -0
  113. package/dist/types/errors.d.ts.map +1 -0
  114. package/dist/types/errors.js +30 -0
  115. package/dist/types/errors.js.map +1 -0
  116. package/dist/types/page-representation.d.ts +84 -0
  117. package/dist/types/page-representation.d.ts.map +1 -0
  118. package/dist/types/page-representation.js +2 -0
  119. package/dist/types/page-representation.js.map +1 -0
  120. package/dist/types/snapshot.d.ts +22 -0
  121. package/dist/types/snapshot.d.ts.map +1 -0
  122. package/dist/types/snapshot.js +2 -0
  123. package/dist/types/snapshot.js.map +1 -0
  124. package/dist/utils/hash.d.ts +2 -0
  125. package/dist/utils/hash.d.ts.map +1 -0
  126. package/dist/utils/hash.js +6 -0
  127. package/dist/utils/hash.js.map +1 -0
  128. package/dist/utils/logger.d.ts +9 -0
  129. package/dist/utils/logger.d.ts.map +1 -0
  130. package/dist/utils/logger.js +31 -0
  131. package/dist/utils/logger.js.map +1 -0
  132. package/dist/utils/wait.d.ts +21 -0
  133. package/dist/utils/wait.d.ts.map +1 -0
  134. package/dist/utils/wait.js +55 -0
  135. package/dist/utils/wait.js.map +1 -0
  136. package/package.json +67 -0
@@ -0,0 +1,9 @@
1
+ import type { PageRepresentation } from "../types/page-representation.js";
2
+ import type { SnapshotDiff } from "../types/snapshot.js";
3
+ export type DiffScope = "all" | "structure" | "interactive" | "content";
4
+ /**
5
+ * Compare two PageRepresentations and produce a SnapshotDiff describing
6
+ * what changed between them.
7
+ */
8
+ export declare function diffRepresentations(fromRepresentation: PageRepresentation, toRepresentation: PageRepresentation, fromSnapshotId: number, toSnapshotId: number, scope?: DiffScope): SnapshotDiff;
9
+ //# sourceMappingURL=differ.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/state/differ.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAOnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAErE,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;AAExE;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,kBAAkB,EACpC,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,KAAK,GAAE,SAAiB,GACvB,YAAY,CAyBd"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Compare two PageRepresentations and produce a SnapshotDiff describing
3
+ * what changed between them.
4
+ */
5
+ export function diffRepresentations(fromRepresentation, toRepresentation, fromSnapshotId, toSnapshotId, scope = "all") {
6
+ const changes = [];
7
+ if (scope === "all" || scope === "structure") {
8
+ changes.push(...diffLandmarks(fromRepresentation.structure.landmarks, toRepresentation.structure.landmarks));
9
+ changes.push(...diffHeadings(fromRepresentation.structure.headings, toRepresentation.structure.headings));
10
+ }
11
+ if (scope === "all" || scope === "interactive") {
12
+ changes.push(...diffInteractiveElements(fromRepresentation.interactive, toRepresentation.interactive));
13
+ changes.push(...diffForms(fromRepresentation.forms, toRepresentation.forms));
14
+ }
15
+ if (scope === "all" || scope === "content") {
16
+ changes.push(...diffContent(fromRepresentation, toRepresentation));
17
+ }
18
+ const summary = buildSummary(changes);
19
+ return {
20
+ from_snapshot: fromSnapshotId,
21
+ to_snapshot: toSnapshotId,
22
+ changes,
23
+ summary,
24
+ };
25
+ }
26
+ // ─── Landmark diffing ────────────────────────────────────────────────
27
+ function landmarkKey(landmark) {
28
+ return `${landmark.role}:${landmark.label}`;
29
+ }
30
+ function diffLandmarks(fromLandmarks, toLandmarks) {
31
+ const changes = [];
32
+ const fromByKey = new Map(fromLandmarks.map((landmark) => [landmarkKey(landmark), landmark]));
33
+ const toByKey = new Map(toLandmarks.map((landmark) => [landmarkKey(landmark), landmark]));
34
+ // Removed landmarks
35
+ for (const [key, landmark] of fromByKey) {
36
+ if (!toByKey.has(key)) {
37
+ changes.push({
38
+ type: "removed",
39
+ detail: `Landmark removed: ${landmark.role} "${landmark.label}"`,
40
+ });
41
+ }
42
+ }
43
+ // Added landmarks
44
+ for (const [key, landmark] of toByKey) {
45
+ if (!fromByKey.has(key)) {
46
+ changes.push({
47
+ type: "added",
48
+ detail: `Landmark added: ${landmark.role} "${landmark.label}"`,
49
+ });
50
+ }
51
+ }
52
+ // Moved landmarks (same key, different bounds)
53
+ for (const [key, toLandmark] of toByKey) {
54
+ const fromLandmark = fromByKey.get(key);
55
+ if (fromLandmark && !boundsEqual(fromLandmark.bounds, toLandmark.bounds)) {
56
+ changes.push({
57
+ type: "moved",
58
+ detail: `Landmark moved: ${toLandmark.role} "${toLandmark.label}"`,
59
+ from: fromLandmark.bounds,
60
+ to: toLandmark.bounds,
61
+ });
62
+ }
63
+ }
64
+ return changes;
65
+ }
66
+ // ─── Heading diffing ─────────────────────────────────────────────────
67
+ function diffHeadings(fromHeadings, toHeadings) {
68
+ const changes = [];
69
+ const fromById = new Map(fromHeadings.map((heading) => [heading.id, heading]));
70
+ const toById = new Map(toHeadings.map((heading) => [heading.id, heading]));
71
+ for (const [headingId, heading] of fromById) {
72
+ if (!toById.has(headingId)) {
73
+ changes.push({
74
+ type: "removed",
75
+ element: headingId,
76
+ detail: `Heading removed: h${heading.level} "${heading.text}"`,
77
+ });
78
+ }
79
+ }
80
+ for (const [headingId, heading] of toById) {
81
+ if (!fromById.has(headingId)) {
82
+ changes.push({
83
+ type: "added",
84
+ element: headingId,
85
+ detail: `Heading added: h${heading.level} "${heading.text}"`,
86
+ });
87
+ }
88
+ else {
89
+ const fromHeading = fromById.get(headingId);
90
+ if (fromHeading.text !== heading.text) {
91
+ changes.push({
92
+ type: "changed",
93
+ element: headingId,
94
+ property: "text",
95
+ from: fromHeading.text,
96
+ to: heading.text,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ return changes;
102
+ }
103
+ // ─── Interactive element diffing ─────────────────────────────────────
104
+ function diffInteractiveElements(fromElements, toElements) {
105
+ const changes = [];
106
+ const fromById = new Map(fromElements.map((element) => [element.id, element]));
107
+ const toById = new Map(toElements.map((element) => [element.id, element]));
108
+ // Removed elements
109
+ for (const [elementId, element] of fromById) {
110
+ if (!toById.has(elementId)) {
111
+ changes.push({
112
+ type: "removed",
113
+ element: elementId,
114
+ detail: `${element.type} "${element.label}" removed`,
115
+ });
116
+ }
117
+ }
118
+ // Added elements
119
+ for (const [elementId, element] of toById) {
120
+ if (!fromById.has(elementId)) {
121
+ changes.push({
122
+ type: "added",
123
+ element: elementId,
124
+ detail: `${element.type} "${element.label}" added`,
125
+ });
126
+ }
127
+ }
128
+ // Changed/moved elements
129
+ for (const [elementId, toElement] of toById) {
130
+ const fromElement = fromById.get(elementId);
131
+ if (!fromElement)
132
+ continue;
133
+ // Check bounds change (moved)
134
+ if (fromElement.bounds &&
135
+ toElement.bounds &&
136
+ !boundsEqual(fromElement.bounds, toElement.bounds)) {
137
+ changes.push({
138
+ type: "moved",
139
+ element: elementId,
140
+ from: fromElement.bounds,
141
+ to: toElement.bounds,
142
+ });
143
+ }
144
+ // Check state changes
145
+ const stateChanges = diffElementState(fromElement.state, toElement.state);
146
+ for (const stateChange of stateChanges) {
147
+ changes.push({
148
+ type: "changed",
149
+ element: elementId,
150
+ property: `state.${stateChange.property}`,
151
+ from: stateChange.from,
152
+ to: stateChange.to,
153
+ });
154
+ }
155
+ // Check value change
156
+ if (fromElement.value !== toElement.value) {
157
+ changes.push({
158
+ type: "changed",
159
+ element: elementId,
160
+ property: "value",
161
+ from: fromElement.value,
162
+ to: toElement.value,
163
+ });
164
+ }
165
+ // Check label change
166
+ if (fromElement.label !== toElement.label) {
167
+ changes.push({
168
+ type: "changed",
169
+ element: elementId,
170
+ property: "label",
171
+ from: fromElement.label,
172
+ to: toElement.label,
173
+ });
174
+ }
175
+ }
176
+ return changes;
177
+ }
178
+ function diffElementState(fromState, toState) {
179
+ const statePropertyChanges = [];
180
+ const stateProperties = [
181
+ "enabled",
182
+ "visible",
183
+ "focused",
184
+ "checked",
185
+ "expanded",
186
+ "selected",
187
+ "required",
188
+ "invalid",
189
+ ];
190
+ for (const property of stateProperties) {
191
+ if (fromState[property] !== toState[property]) {
192
+ statePropertyChanges.push({
193
+ property,
194
+ from: fromState[property],
195
+ to: toState[property],
196
+ });
197
+ }
198
+ }
199
+ return statePropertyChanges;
200
+ }
201
+ // ─── Form diffing ────────────────────────────────────────────────────
202
+ function diffForms(fromForms, toForms) {
203
+ const changes = [];
204
+ const fromById = new Map(fromForms.map((form) => [form.id, form]));
205
+ const toById = new Map(toForms.map((form) => [form.id, form]));
206
+ for (const [formId, form] of fromById) {
207
+ if (!toById.has(formId)) {
208
+ changes.push({
209
+ type: "removed",
210
+ element: formId,
211
+ detail: `Form removed (${form.fields.length} fields)`,
212
+ });
213
+ }
214
+ }
215
+ for (const [formId, form] of toById) {
216
+ if (!fromById.has(formId)) {
217
+ changes.push({
218
+ type: "added",
219
+ element: formId,
220
+ detail: `Form added (${form.fields.length} fields)`,
221
+ });
222
+ }
223
+ else {
224
+ const fromForm = fromById.get(formId);
225
+ // Check field list changes
226
+ const addedFields = form.fields.filter((field) => !fromForm.fields.includes(field));
227
+ const removedFields = fromForm.fields.filter((field) => !form.fields.includes(field));
228
+ if (addedFields.length > 0 || removedFields.length > 0) {
229
+ changes.push({
230
+ type: "changed",
231
+ element: formId,
232
+ property: "fields",
233
+ from: fromForm.fields,
234
+ to: form.fields,
235
+ });
236
+ }
237
+ }
238
+ }
239
+ return changes;
240
+ }
241
+ // ─── Content diffing ─────────────────────────────────────────────────
242
+ function diffContent(fromRepresentation, toRepresentation) {
243
+ const changes = [];
244
+ if (fromRepresentation.structure.content_summary !== toRepresentation.structure.content_summary) {
245
+ changes.push({
246
+ type: "changed",
247
+ property: "content_summary",
248
+ from: fromRepresentation.structure.content_summary,
249
+ to: toRepresentation.structure.content_summary,
250
+ });
251
+ }
252
+ if (fromRepresentation.url !== toRepresentation.url) {
253
+ changes.push({
254
+ type: "changed",
255
+ property: "url",
256
+ from: fromRepresentation.url,
257
+ to: toRepresentation.url,
258
+ });
259
+ }
260
+ if (fromRepresentation.title !== toRepresentation.title) {
261
+ changes.push({
262
+ type: "changed",
263
+ property: "title",
264
+ from: fromRepresentation.title,
265
+ to: toRepresentation.title,
266
+ });
267
+ }
268
+ return changes;
269
+ }
270
+ // ─── Helpers ─────────────────────────────────────────────────────────
271
+ function boundsEqual(boundsA, boundsB) {
272
+ return (boundsA.x === boundsB.x &&
273
+ boundsA.y === boundsB.y &&
274
+ boundsA.w === boundsB.w &&
275
+ boundsA.h === boundsB.h);
276
+ }
277
+ function buildSummary(changes) {
278
+ if (changes.length === 0)
279
+ return "No changes detected.";
280
+ const addedCount = changes.filter((change) => change.type === "added").length;
281
+ const removedCount = changes.filter((change) => change.type === "removed").length;
282
+ const movedCount = changes.filter((change) => change.type === "moved").length;
283
+ const changedCount = changes.filter((change) => change.type === "changed").length;
284
+ const parts = [];
285
+ if (addedCount > 0)
286
+ parts.push(`${addedCount} added`);
287
+ if (removedCount > 0)
288
+ parts.push(`${removedCount} removed`);
289
+ if (movedCount > 0)
290
+ parts.push(`${movedCount} moved`);
291
+ if (changedCount > 0)
292
+ parts.push(`${changedCount} changed`);
293
+ return `${changes.length} changes: ${parts.join(", ")}.`;
294
+ }
295
+ //# sourceMappingURL=differ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"differ.js","sourceRoot":"","sources":["../../src/state/differ.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { PageRepresentation } from "../types/page-representation.js";
2
+ import type { Snapshot } from "../types/snapshot.js";
3
+ /**
4
+ * Ring-buffer store for page snapshots. Snapshots have monotonically
5
+ * increasing integer IDs and are evicted oldest-first when the buffer
6
+ * reaches its configured depth.
7
+ */
8
+ export declare class SnapshotStore {
9
+ private buffer;
10
+ private nextSnapshotId;
11
+ private maxDepth;
12
+ constructor(depth?: number);
13
+ /**
14
+ * Push a new snapshot from a PageRepresentation. Returns the assigned
15
+ * snapshot ID (also stamped onto the representation).
16
+ */
17
+ push(representation: PageRepresentation): number;
18
+ /**
19
+ * Retrieve a snapshot by its ID. Returns null if evicted or never existed.
20
+ */
21
+ get(snapshotId: number): Snapshot | null;
22
+ /**
23
+ * Get the most recent snapshot, or null if the store is empty.
24
+ */
25
+ getLatest(): Snapshot | null;
26
+ /**
27
+ * Get the second-most-recent snapshot (the one before latest), or null.
28
+ */
29
+ getPrevious(): Snapshot | null;
30
+ /**
31
+ * Get the oldest snapshot ID still in the buffer, or null if empty.
32
+ */
33
+ getOldestId(): number | null;
34
+ /**
35
+ * Get the latest snapshot ID that has been assigned (even if evicted).
36
+ */
37
+ getLatestId(): number;
38
+ /**
39
+ * Current number of snapshots in the buffer.
40
+ */
41
+ get size(): number;
42
+ /**
43
+ * Resize the ring buffer. Evicts oldest snapshots if the new depth
44
+ * is smaller than the current size.
45
+ */
46
+ setDepth(newDepth: number): void;
47
+ /**
48
+ * Clear all snapshots and reset the ID counter.
49
+ */
50
+ clear(): void;
51
+ }
52
+ //# sourceMappingURL=snapshot-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-store.d.ts","sourceRoot":"","sources":["../../src/state/snapshot-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAMrD;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAS;gBAEb,KAAK,GAAE,MAAsB;IAIzC;;;OAGG;IACH,IAAI,CAAC,cAAc,EAAE,kBAAkB,GAAG,MAAM;IAwBhD;;OAEG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC;;OAEG;IACH,SAAS,IAAI,QAAQ,GAAG,IAAI;IAK5B;;OAEG;IACH,WAAW,IAAI,QAAQ,GAAG,IAAI;IAK9B;;OAEG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI;IAK5B;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOhC;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,98 @@
1
+ const DEFAULT_DEPTH = 50;
2
+ const MIN_DEPTH = 5;
3
+ const MAX_DEPTH = 500;
4
+ /**
5
+ * Ring-buffer store for page snapshots. Snapshots have monotonically
6
+ * increasing integer IDs and are evicted oldest-first when the buffer
7
+ * reaches its configured depth.
8
+ */
9
+ export class SnapshotStore {
10
+ buffer = [];
11
+ nextSnapshotId = 1;
12
+ maxDepth;
13
+ constructor(depth = DEFAULT_DEPTH) {
14
+ this.maxDepth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
15
+ }
16
+ /**
17
+ * Push a new snapshot from a PageRepresentation. Returns the assigned
18
+ * snapshot ID (also stamped onto the representation).
19
+ */
20
+ push(representation) {
21
+ const snapshotId = this.nextSnapshotId++;
22
+ const timestamp = new Date().toISOString();
23
+ // Stamp the snapshot_id onto the representation so callers see it
24
+ representation.snapshot_id = snapshotId;
25
+ representation.timestamp = timestamp;
26
+ const snapshot = {
27
+ id: snapshotId,
28
+ timestamp,
29
+ representation,
30
+ };
31
+ this.buffer.push(snapshot);
32
+ // Evict oldest if over capacity
33
+ while (this.buffer.length > this.maxDepth) {
34
+ this.buffer.shift();
35
+ }
36
+ return snapshotId;
37
+ }
38
+ /**
39
+ * Retrieve a snapshot by its ID. Returns null if evicted or never existed.
40
+ */
41
+ get(snapshotId) {
42
+ return this.buffer.find((snapshot) => snapshot.id === snapshotId) ?? null;
43
+ }
44
+ /**
45
+ * Get the most recent snapshot, or null if the store is empty.
46
+ */
47
+ getLatest() {
48
+ if (this.buffer.length === 0)
49
+ return null;
50
+ return this.buffer[this.buffer.length - 1];
51
+ }
52
+ /**
53
+ * Get the second-most-recent snapshot (the one before latest), or null.
54
+ */
55
+ getPrevious() {
56
+ if (this.buffer.length < 2)
57
+ return null;
58
+ return this.buffer[this.buffer.length - 2];
59
+ }
60
+ /**
61
+ * Get the oldest snapshot ID still in the buffer, or null if empty.
62
+ */
63
+ getOldestId() {
64
+ if (this.buffer.length === 0)
65
+ return null;
66
+ return this.buffer[0].id;
67
+ }
68
+ /**
69
+ * Get the latest snapshot ID that has been assigned (even if evicted).
70
+ */
71
+ getLatestId() {
72
+ return this.nextSnapshotId - 1;
73
+ }
74
+ /**
75
+ * Current number of snapshots in the buffer.
76
+ */
77
+ get size() {
78
+ return this.buffer.length;
79
+ }
80
+ /**
81
+ * Resize the ring buffer. Evicts oldest snapshots if the new depth
82
+ * is smaller than the current size.
83
+ */
84
+ setDepth(newDepth) {
85
+ this.maxDepth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, newDepth));
86
+ while (this.buffer.length > this.maxDepth) {
87
+ this.buffer.shift();
88
+ }
89
+ }
90
+ /**
91
+ * Clear all snapshots and reset the ID counter.
92
+ */
93
+ clear() {
94
+ this.buffer = [];
95
+ this.nextSnapshotId = 1;
96
+ }
97
+ }
98
+ //# sourceMappingURL=snapshot-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-store.js","sourceRoot":"","sources":["../../src/state/snapshot-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolDependencies } from "./tool-helpers.js";
3
+ export declare function registerDevModeTools(server: McpServer, deps: ToolDependencies): void;
4
+ //# sourceMappingURL=dev-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-mode.d.ts","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAiNN"}
@@ -0,0 +1,160 @@
1
+ import { z } from "zod";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
5
+ import { logger } from "../utils/logger.js";
6
+ import { Auditor } from "../dev/auditor.js";
7
+ import { renderActivePage, renderAfterAction, formatPageResponse, handleToolError, } from "./tool-helpers.js";
8
+ export function registerDevModeTools(server, deps) {
9
+ // ─── charlotte:dev_serve ───
10
+ server.registerTool("charlotte:dev_serve", {
11
+ description: "Serve a local directory as a static website and optionally watch for file changes. Navigates to the served URL and returns the page representation. File changes trigger automatic reloads and surface as reload_event on the next tool response.",
12
+ inputSchema: {
13
+ path: z.string().describe("Local directory to serve"),
14
+ port: z
15
+ .number()
16
+ .optional()
17
+ .describe("Port to serve on (default: auto-assign)"),
18
+ watch: z
19
+ .boolean()
20
+ .optional()
21
+ .describe("Auto-reload on file changes (default: true)"),
22
+ },
23
+ }, async ({ path: directoryPath, port, watch }) => {
24
+ try {
25
+ await deps.browserManager.ensureConnected();
26
+ if (!deps.devModeState) {
27
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Dev mode is not available.", "DevModeState was not initialized.");
28
+ }
29
+ // Resolve and validate the directory path
30
+ const absoluteDirectoryPath = path.resolve(directoryPath);
31
+ let directoryStats;
32
+ try {
33
+ directoryStats = fs.statSync(absoluteDirectoryPath);
34
+ }
35
+ catch {
36
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Path does not exist: ${absoluteDirectoryPath}`, "Provide a valid directory path.");
37
+ }
38
+ if (!directoryStats.isDirectory()) {
39
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Path is not a directory: ${absoluteDirectoryPath}`, "Provide a path to a directory, not a file.");
40
+ }
41
+ const shouldWatch = watch ?? true;
42
+ logger.info("Starting dev serve", {
43
+ directory: absoluteDirectoryPath,
44
+ port,
45
+ watch: shouldWatch,
46
+ });
47
+ const serverInfo = await deps.devModeState.startServing({
48
+ directoryPath: absoluteDirectoryPath,
49
+ port,
50
+ watch: shouldWatch,
51
+ pageManager: deps.pageManager,
52
+ });
53
+ // Navigate the active page to the served URL
54
+ const page = deps.pageManager.getActivePage();
55
+ await page.goto(serverInfo.url, { waitUntil: "load" });
56
+ const representation = await renderActivePage(deps, {
57
+ source: "action",
58
+ });
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: JSON.stringify({
64
+ ...representation,
65
+ dev_server: {
66
+ url: serverInfo.url,
67
+ port: serverInfo.port,
68
+ directory: serverInfo.directoryPath,
69
+ watching: shouldWatch,
70
+ },
71
+ }, null, 2),
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ catch (error) {
77
+ return handleToolError(error);
78
+ }
79
+ });
80
+ // ─── charlotte:dev_inject ───
81
+ server.registerTool("charlotte:dev_inject", {
82
+ description: "Inject CSS or JavaScript into the current page for testing modifications without editing files. Returns the page representation with a delta showing changes.",
83
+ inputSchema: {
84
+ css: z
85
+ .string()
86
+ .optional()
87
+ .describe("CSS to inject into the page"),
88
+ js: z
89
+ .string()
90
+ .optional()
91
+ .describe("JavaScript to execute in the page context"),
92
+ },
93
+ }, async ({ css, js }) => {
94
+ try {
95
+ await deps.browserManager.ensureConnected();
96
+ if (!css && !js) {
97
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "At least one of 'css' or 'js' must be provided.", "Provide CSS to inject, JS to execute, or both.");
98
+ }
99
+ const page = deps.pageManager.getActivePage();
100
+ if (css) {
101
+ await page.addStyleTag({ content: css });
102
+ logger.info("Injected CSS", {
103
+ length: css.length,
104
+ });
105
+ }
106
+ if (js) {
107
+ await page.evaluate(js);
108
+ logger.info("Executed injected JS", {
109
+ length: js.length,
110
+ });
111
+ }
112
+ // Brief pause to allow DOM updates to settle
113
+ await new Promise((resolve) => setTimeout(resolve, 50));
114
+ const representation = await renderAfterAction(deps);
115
+ return formatPageResponse(representation);
116
+ }
117
+ catch (error) {
118
+ return handleToolError(error);
119
+ }
120
+ });
121
+ // ─── charlotte:dev_audit ───
122
+ server.registerTool("charlotte:dev_audit", {
123
+ description: "Run accessibility and quality audits on the current page. Returns findings with severity levels and actionable recommendations.",
124
+ inputSchema: {
125
+ checks: z
126
+ .array(z.enum(["a11y", "performance", "seo", "contrast", "links"]))
127
+ .optional()
128
+ .describe("Audit categories to run. Options: a11y, performance, seo, contrast, links. Default: all categories."),
129
+ },
130
+ }, async ({ checks }) => {
131
+ try {
132
+ await deps.browserManager.ensureConnected();
133
+ const page = deps.pageManager.getActivePage();
134
+ const session = await page.createCDPSession();
135
+ const auditor = new Auditor();
136
+ const categories = checks;
137
+ logger.info("Running dev audit", {
138
+ categories: categories ?? "all",
139
+ });
140
+ try {
141
+ const auditResult = await auditor.audit(page, session, categories);
142
+ return {
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: JSON.stringify(auditResult, null, 2),
147
+ },
148
+ ],
149
+ };
150
+ }
151
+ finally {
152
+ await session.detach();
153
+ }
154
+ }
155
+ catch (error) {
156
+ return handleToolError(error);
157
+ }
158
+ });
159
+ }
160
+ //# sourceMappingURL=dev-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-mode.js","sourceRoot":"","sources":["../../src/tools/dev-mode.ts"],"names":[],"mappings":"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"}