@domglyph/runtime 2.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 llcortex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # @domglyph/runtime (formerly [@cortexui/runtime](https://www.npmjs.com/package/@cortexui/runtime))
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@domglyph/runtime?color=0ea5e9)](https://www.npmjs.com/package/@domglyph/runtime)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](../../LICENSE)
5
+
6
+ The browser runtime that makes DOMglyph pages inspectable by AI agents.
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ `@domglyph/runtime` installs `window.__CORTEX_UI__` — a structured inspection API that AI agents use instead of scraping the DOM.
13
+
14
+ Without this runtime, an AI agent visiting your page would need to traverse the DOM, parse CSS, infer intent from visible text, and guess at state. With this runtime, the agent calls a single function and gets back a typed, structured description of exactly what is on screen and what can be done.
15
+
16
+ The runtime works by reading the `data-ai-*` attributes that DOMglyph components emit. It requires no separate data store, no backend, and no framework integration beyond a single `installDOMglyphRuntime()` call.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @domglyph/runtime
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Setup
29
+
30
+ ### With React (recommended)
31
+
32
+ Call `installDOMglyphRuntime` once in your application's entry point, before the app renders:
33
+
34
+ ```tsx
35
+ import { installDOMglyphRuntime } from '@domglyph/runtime';
36
+
37
+ // Install before rendering
38
+ installDOMglyphRuntime(window);
39
+
40
+ // Then render your app
41
+ ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
42
+ ```
43
+
44
+ ### Verifying installation
45
+
46
+ Open the browser console on any DOMglyph page and run:
47
+
48
+ ```js
49
+ console.log(window.__CORTEX_UI__.getScreenContext());
50
+ ```
51
+
52
+ If `__CORTEX_UI__` is defined and returns a context object, the runtime is installed correctly.
53
+
54
+ ---
55
+
56
+ ## API Reference
57
+
58
+ ### getScreenContext()
59
+
60
+ Returns the screen-level context for the current page view. This is the first thing an agent should call to orient itself.
61
+
62
+ ```ts
63
+ getScreenContext(): {
64
+ screen: string | null; // value of data-ai-screen on the root element
65
+ entity: string | null; // value of data-ai-entity on the screen root
66
+ entityId: string | null; // value of data-ai-entity-id on the screen root
67
+ sections: string[]; // all data-ai-section values present on this screen
68
+ }
69
+ ```
70
+
71
+ Example output:
72
+
73
+ ```js
74
+ window.__CORTEX_UI__.getScreenContext();
75
+ // {
76
+ // screen: "order-detail",
77
+ // entity: "order",
78
+ // entityId: "ord-9142",
79
+ // sections: ["summary", "line-items", "shipping", "actions"]
80
+ // }
81
+ ```
82
+
83
+ ---
84
+
85
+ ### getAvailableActions()
86
+
87
+ Returns every element with `data-ai-role="action"` that is currently rendered and not in a `disabled` state. This tells the agent what it can do right now.
88
+
89
+ ```ts
90
+ getAvailableActions(): Array<{
91
+ id: string; // data-ai-id
92
+ action: string; // data-ai-action
93
+ state: string; // data-ai-state
94
+ section: string | null; // data-ai-section (if present)
95
+ }>
96
+ ```
97
+
98
+ Example output:
99
+
100
+ ```js
101
+ window.__CORTEX_UI__.getAvailableActions();
102
+ // [
103
+ // { id: "approve-order", action: "approve-order", state: "idle", section: "actions" },
104
+ // { id: "cancel-order", action: "cancel-order", state: "idle", section: "actions" },
105
+ // { id: "edit-shipping", action: "edit-shipping", state: "idle", section: "shipping" }
106
+ // ]
107
+ ```
108
+
109
+ ---
110
+
111
+ ### getFormSchema(formId)
112
+
113
+ Returns the field schema for a form identified by its `data-ai-id`. Returns `null` if no matching form is found.
114
+
115
+ ```ts
116
+ getFormSchema(formId: string): {
117
+ formId: string;
118
+ fields: Array<{
119
+ id: string; // data-ai-id of the field
120
+ fieldType: string; // data-ai-field-type
121
+ required: boolean; // data-ai-required
122
+ currentValue: string | null; // current input value
123
+ state: string; // data-ai-state of the field
124
+ }>;
125
+ } | null
126
+ ```
127
+
128
+ Example output:
129
+
130
+ ```js
131
+ window.__CORTEX_UI__.getFormSchema('edit-shipping-form');
132
+ // {
133
+ // formId: "edit-shipping-form",
134
+ // fields: [
135
+ // { id: "shipping-name", fieldType: "text", required: true, currentValue: "Jane Smith", state: "idle" },
136
+ // { id: "shipping-address", fieldType: "text", required: true, currentValue: "", state: "error" },
137
+ // { id: "shipping-country", fieldType: "select", required: true, currentValue: "US", state: "idle" }
138
+ // ]
139
+ // }
140
+ ```
141
+
142
+ ---
143
+
144
+ ### getVisibleEntities()
145
+
146
+ Returns all elements carrying `data-ai-entity` that are currently in the viewport. Useful for understanding what data the user can see.
147
+
148
+ ```ts
149
+ getVisibleEntities(): Array<{
150
+ entity: string; // data-ai-entity
151
+ entityId: string | null; // data-ai-entity-id
152
+ section: string | null; // data-ai-section
153
+ }>
154
+ ```
155
+
156
+ Example output:
157
+
158
+ ```js
159
+ window.__CORTEX_UI__.getVisibleEntities();
160
+ // [
161
+ // { entity: "order", entityId: "ord-9142", section: "summary" },
162
+ // { entity: "product", entityId: "prd-001", section: "line-items" },
163
+ // { entity: "product", entityId: "prd-007", section: "line-items" }
164
+ // ]
165
+ ```
166
+
167
+ ---
168
+
169
+ ### getRecentEvents()
170
+
171
+ Returns a log of recent interaction events in chronological order. Use this to verify that an action completed, or to understand what has happened on screen since the last check.
172
+
173
+ ```ts
174
+ getRecentEvents(): Array<{
175
+ type: 'action_triggered' | 'action_completed' | 'action_failed' | 'form_submitted' | 'field_updated';
176
+ actionId?: string;
177
+ formId?: string;
178
+ fieldId?: string;
179
+ result?: 'success' | 'error';
180
+ message?: string;
181
+ timestamp: number;
182
+ }>
183
+ ```
184
+
185
+ Example output:
186
+
187
+ ```js
188
+ window.__CORTEX_UI__.getRecentEvents();
189
+ // [
190
+ // { type: "action_triggered", actionId: "approve-order", timestamp: 1711900000000 },
191
+ // { type: "action_completed", actionId: "approve-order", result: "success", timestamp: 1711900000250 }
192
+ // ]
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Devtools
198
+
199
+ In development, you can install the extended devtools runtime for additional inspection capabilities:
200
+
201
+ ```ts
202
+ import { installDOMglyphDevtools } from '@domglyph/runtime';
203
+
204
+ installDOMglyphDevtools(window);
205
+ // window.__CORTEX_UI_DEVTOOLS__ is now available
206
+ ```
207
+
208
+ The devtools runtime adds:
209
+
210
+ - Full DOM node references in query results
211
+ - Attribute validation warnings in the console
212
+ - A mutation observer log showing when `data-ai-*` attributes change
213
+
214
+ Do not install devtools in production builds.
215
+
216
+ ---
217
+
218
+ ## Using with AI Agents
219
+
220
+ A typical agent interaction loop using the DOMglyph runtime:
221
+
222
+ ```js
223
+ // Step 1: Orient — where am I and what entity am I looking at?
224
+ const ctx = window.__CORTEX_UI__.getScreenContext();
225
+ // { screen: "profile", entity: "user", entityId: "usr-42", sections: [...] }
226
+
227
+ // Step 2: Survey — what actions can I take right now?
228
+ const actions = window.__CORTEX_UI__.getAvailableActions();
229
+ // [{ id: "save-profile", action: "save-profile", state: "idle", section: "contact" }]
230
+
231
+ // Step 3: Act — trigger the desired action deterministically
232
+ document.querySelector(`[data-ai-id="${actions[0].id}"]`).click();
233
+
234
+ // Step 4: Verify — did it work?
235
+ const events = window.__CORTEX_UI__.getRecentEvents();
236
+ // [{ type: "action_completed", actionId: "save-profile", result: "success", ... }]
237
+ ```
238
+
239
+ This pattern is stable across re-renders, redesigns, and component updates. As long as the component exports the same `data-ai-action`, the agent's code does not need to change.
240
+
241
+ ---
242
+
243
+ ## Part of DOMglyph
244
+
245
+ `@domglyph/runtime` is part of the [DOMglyph](../../README.md) design system.
246
+
247
+ - [Main repository](../../README.md)
248
+ - [Documentation](http://localhost:3001/docs/runtime)
249
+ - [Contributing](../../CONTRIBUTING.md)
250
+
251
+ ---
252
+
253
+ ## ☕ Support
254
+
255
+ If you find DOMglyph useful, you can support the project:
256
+
257
+ 👉 https://buymeacoffee.com/nishchya
258
+
259
+ It helps keep the project alive and growing.
@@ -0,0 +1,103 @@
1
+ import { AIAction, AIEvent } from '@domglyph/ai-contract';
2
+
3
+ interface ScreenContext {
4
+ readonly screenId?: string;
5
+ readonly sectionIds: readonly string[];
6
+ readonly url?: string;
7
+ readonly title?: string;
8
+ readonly visibleActionIds: readonly string[];
9
+ readonly visibleEntityTypes: readonly string[];
10
+ }
11
+ interface AvailableAction extends AIAction {
12
+ readonly elementId?: string;
13
+ readonly label?: string;
14
+ readonly role?: string;
15
+ readonly disabled: boolean;
16
+ readonly state: readonly string[];
17
+ }
18
+ interface FormFieldSchema {
19
+ readonly id: string;
20
+ readonly label?: string;
21
+ readonly fieldType?: string;
22
+ readonly required: boolean;
23
+ readonly state: readonly string[];
24
+ readonly placeholder?: string;
25
+ }
26
+ interface FormSchema {
27
+ readonly id: string;
28
+ readonly screenId?: string;
29
+ readonly sectionId?: string;
30
+ readonly fields: readonly FormFieldSchema[];
31
+ readonly submitActions: readonly AvailableAction[];
32
+ }
33
+ interface VisibleEntity {
34
+ readonly entity: string;
35
+ readonly entityId?: string;
36
+ readonly screenId?: string;
37
+ readonly sectionId?: string;
38
+ readonly text?: string;
39
+ }
40
+ interface RuntimeEventLogEntry {
41
+ readonly type: AIEvent | "mutation_observed" | "screen_context_changed";
42
+ readonly timestamp: number;
43
+ readonly targetId?: string;
44
+ readonly detail?: unknown;
45
+ }
46
+ interface RuntimeRegistry {
47
+ readonly prompt: string;
48
+ readonly components: readonly string[];
49
+ }
50
+ interface DOMglyphGlobalAPI {
51
+ getScreenContext(): ScreenContext;
52
+ getAvailableActions(): readonly AvailableAction[];
53
+ getFormSchema(formId: string): FormSchema | null;
54
+ getVisibleEntities(): readonly VisibleEntity[];
55
+ getRecentEvents(): readonly RuntimeEventLogEntry[];
56
+ }
57
+ interface DOMglyphDevtoolsAPI {
58
+ show(): void;
59
+ hide(): void;
60
+ toggle(): boolean;
61
+ destroy(): void;
62
+ isVisible(): boolean;
63
+ }
64
+
65
+ declare function installDOMglyphDevtools(targetWindow?: Window): DOMglyphDevtoolsAPI | null;
66
+
67
+ declare class DOMglyphRuntime implements DOMglyphGlobalAPI {
68
+ private readonly win;
69
+ private readonly doc;
70
+ private readonly actionRegistry;
71
+ private readonly recentEvents;
72
+ private mutationObserver;
73
+ private lastScreenSignature;
74
+ constructor(win: Window, doc: Document);
75
+ getScreenContext(): ScreenContext;
76
+ getAvailableActions(): readonly AvailableAction[];
77
+ getFormSchema(formId: string): FormSchema | null;
78
+ getVisibleEntities(): readonly VisibleEntity[];
79
+ getRecentEvents(): readonly RuntimeEventLogEntry[];
80
+ private attachEventListeners;
81
+ private attachMutationObserver;
82
+ private logMutationMetadata;
83
+ private refreshActionRegistry;
84
+ private buildActionEntry;
85
+ private extractFieldSchema;
86
+ private getVisibleElements;
87
+ private buildScreenSignature;
88
+ private logEvent;
89
+ }
90
+ declare function installDOMglyphRuntime(targetWindow?: Window): DOMglyphGlobalAPI | null;
91
+
92
+ declare const runtimeRegistry: RuntimeRegistry;
93
+ declare const defaultRuntimeEvent: "action_triggered";
94
+ declare const CORTEX_UI: DOMglyphGlobalAPI | null;
95
+ declare const CORTEX_UI_DEVTOOLS: DOMglyphDevtoolsAPI | null;
96
+ declare global {
97
+ interface Window {
98
+ CORTEX_UI?: DOMglyphGlobalAPI;
99
+ CORTEX_UI_DEVTOOLS?: DOMglyphDevtoolsAPI;
100
+ }
101
+ }
102
+
103
+ export { type AvailableAction, CORTEX_UI, CORTEX_UI_DEVTOOLS, type DOMglyphDevtoolsAPI, type DOMglyphGlobalAPI, DOMglyphRuntime, type FormFieldSchema, type FormSchema, type RuntimeEventLogEntry, type RuntimeRegistry, type ScreenContext, type VisibleEntity, defaultRuntimeEvent, installDOMglyphDevtools, installDOMglyphRuntime, runtimeRegistry };
package/dist/index.js ADDED
@@ -0,0 +1,629 @@
1
+ // src/index.ts
2
+ import { AIEvent as AIEvent2 } from "@domglyph/ai-contract";
3
+
4
+ // src/devtools.ts
5
+ import {
6
+ AI_ATTRIBUTE_NAMES,
7
+ DATA_AI_ACTION as DATA_AI_ACTION2,
8
+ DATA_AI_ID as DATA_AI_ID2,
9
+ DATA_AI_ROLE as DATA_AI_ROLE2,
10
+ DATA_AI_STATE as DATA_AI_STATE2,
11
+ extractAIAttributes as extractAIAttributes2
12
+ } from "@domglyph/ai-contract";
13
+
14
+ // src/runtime.ts
15
+ import {
16
+ AIEvent,
17
+ AIRole,
18
+ DATA_AI_ACTION,
19
+ DATA_AI_ENTITY,
20
+ DATA_AI_ENTITY_ID,
21
+ DATA_AI_EVENT,
22
+ DATA_AI_FEEDBACK,
23
+ DATA_AI_FIELD_TYPE,
24
+ DATA_AI_ID,
25
+ DATA_AI_REQUIRED,
26
+ DATA_AI_RESULT,
27
+ DATA_AI_ROLE,
28
+ DATA_AI_SCREEN,
29
+ DATA_AI_SECTION,
30
+ DATA_AI_STATE,
31
+ DATA_AI_STATUS,
32
+ extractAIAttributes
33
+ } from "@domglyph/ai-contract";
34
+ var ACTION_SELECTOR = `[${DATA_AI_ACTION}]`;
35
+ var EVENT_SELECTOR = `[${DATA_AI_EVENT}]`;
36
+ var SCREEN_SELECTOR = `[${DATA_AI_SCREEN}]`;
37
+ var SECTION_SELECTOR = `[${DATA_AI_SECTION}]`;
38
+ var ENTITY_SELECTOR = `[${DATA_AI_ENTITY}]`;
39
+ var FIELD_SELECTOR = `input, textarea, select, [${DATA_AI_ROLE}="${AIRole.FIELD}"]`;
40
+ var FORM_SELECTOR = `form, [${DATA_AI_ROLE}="${AIRole.FORM}"]`;
41
+ var MAX_EVENT_LOG = 100;
42
+ var DOMglyphRuntime = class {
43
+ constructor(win, doc) {
44
+ this.win = win;
45
+ this.doc = doc;
46
+ this.refreshActionRegistry();
47
+ this.lastScreenSignature = this.buildScreenSignature();
48
+ this.attachEventListeners();
49
+ this.attachMutationObserver();
50
+ }
51
+ actionRegistry = /* @__PURE__ */ new Map();
52
+ recentEvents = [];
53
+ mutationObserver = null;
54
+ lastScreenSignature = "";
55
+ getScreenContext() {
56
+ const screenElement = this.getVisibleElements(SCREEN_SELECTOR)[0];
57
+ const sectionIds = unique(
58
+ this.getVisibleElements(SECTION_SELECTOR).map((element) => element.getAttribute(DATA_AI_SECTION) ?? void 0).filter(isDefined)
59
+ );
60
+ const visibleActionIds = this.getAvailableActions().map((action) => action.id);
61
+ const visibleEntityTypes = unique(
62
+ this.getVisibleEntities().map((entity) => entity.entity)
63
+ );
64
+ return {
65
+ screenId: screenElement?.getAttribute(DATA_AI_SCREEN) ?? void 0,
66
+ sectionIds,
67
+ title: this.doc.title || void 0,
68
+ url: this.win.location.href,
69
+ visibleActionIds,
70
+ visibleEntityTypes
71
+ };
72
+ }
73
+ getAvailableActions() {
74
+ this.refreshActionRegistry();
75
+ return Array.from(this.actionRegistry.values()).map(({ attributes, ...action }) => action);
76
+ }
77
+ getFormSchema(formId) {
78
+ const formElement = this.doc.getElementById(formId) ?? this.doc.querySelector(`${FORM_SELECTOR}[${DATA_AI_ID}="${cssEscape(formId)}"]`);
79
+ if (formElement === null) {
80
+ return null;
81
+ }
82
+ const formAttributes = extractAIAttributes(formElement);
83
+ const screenId = formElement.closest(SCREEN_SELECTOR)?.getAttribute(DATA_AI_SCREEN) ?? void 0;
84
+ const sectionId = formElement.closest(SECTION_SELECTOR)?.getAttribute(DATA_AI_SECTION) ?? void 0;
85
+ const fields = Array.from(formElement.querySelectorAll(FIELD_SELECTOR)).filter((element) => isVisible(element)).map((field) => this.extractFieldSchema(field)).filter(isDefined);
86
+ const submitActions = Array.from(formElement.querySelectorAll(ACTION_SELECTOR)).filter((element) => isVisible(element)).map((element) => this.buildActionEntry(element)).filter(isDefined).map(({ attributes, ...action }) => action);
87
+ return {
88
+ fields,
89
+ id: formAttributes[DATA_AI_ID] ?? formElement.id ?? formId,
90
+ screenId,
91
+ sectionId,
92
+ submitActions
93
+ };
94
+ }
95
+ getVisibleEntities() {
96
+ return this.getVisibleElements(ENTITY_SELECTOR).map((element) => {
97
+ const attributes = extractAIAttributes(element);
98
+ return {
99
+ entity: attributes[DATA_AI_ENTITY] ?? "unknown",
100
+ entityId: attributes[DATA_AI_ENTITY_ID],
101
+ screenId: element.closest(SCREEN_SELECTOR)?.getAttribute(DATA_AI_SCREEN) ?? void 0,
102
+ sectionId: element.closest(SECTION_SELECTOR)?.getAttribute(DATA_AI_SECTION) ?? void 0,
103
+ text: textPreview(element)
104
+ };
105
+ });
106
+ }
107
+ getRecentEvents() {
108
+ return [...this.recentEvents];
109
+ }
110
+ attachEventListeners() {
111
+ this.doc.addEventListener(
112
+ "click",
113
+ (event) => {
114
+ const target = event.target instanceof Element ? event.target.closest(ACTION_SELECTOR) : null;
115
+ if (target === null) {
116
+ return;
117
+ }
118
+ const action = this.buildActionEntry(target);
119
+ if (action !== null) {
120
+ this.logEvent(AIEvent.ACTION_TRIGGERED, action.id, {
121
+ action: action.name,
122
+ disabled: action.disabled
123
+ });
124
+ }
125
+ },
126
+ true
127
+ );
128
+ this.doc.addEventListener(
129
+ "input",
130
+ (event) => {
131
+ const target = event.target instanceof HTMLElement ? event.target.closest(FIELD_SELECTOR) : null;
132
+ if (target === null) {
133
+ return;
134
+ }
135
+ const attributes = extractAIAttributes(target);
136
+ this.logEvent(AIEvent.FIELD_UPDATED, attributes[DATA_AI_ID], {
137
+ fieldType: attributes[DATA_AI_FIELD_TYPE],
138
+ required: attributes[DATA_AI_REQUIRED]
139
+ });
140
+ },
141
+ true
142
+ );
143
+ this.doc.addEventListener(
144
+ "submit",
145
+ (event) => {
146
+ const target = event.target instanceof HTMLElement ? event.target.closest(FORM_SELECTOR) : null;
147
+ if (target === null) {
148
+ return;
149
+ }
150
+ const attributes = extractAIAttributes(target);
151
+ const formId = attributes[DATA_AI_ID] ?? (target.id || void 0);
152
+ this.logEvent(AIEvent.FORM_SUBMITTED, formId);
153
+ },
154
+ true
155
+ );
156
+ }
157
+ attachMutationObserver() {
158
+ if (typeof MutationObserver === "undefined") {
159
+ return;
160
+ }
161
+ this.mutationObserver = new MutationObserver((mutations) => {
162
+ let shouldRefreshActions = false;
163
+ let shouldCheckScreen = false;
164
+ for (const mutation of mutations) {
165
+ if (mutation.type === "attributes" && mutation.target instanceof HTMLElement) {
166
+ shouldRefreshActions = shouldRefreshActions || mutation.target.matches(ACTION_SELECTOR);
167
+ shouldCheckScreen = shouldCheckScreen || mutation.target.hasAttribute(DATA_AI_SCREEN) || mutation.target.hasAttribute(DATA_AI_SECTION);
168
+ this.logMutationMetadata(mutation.target);
169
+ }
170
+ if (mutation.type === "childList") {
171
+ shouldRefreshActions = true;
172
+ shouldCheckScreen = true;
173
+ mutation.addedNodes.forEach((node) => {
174
+ if (node instanceof HTMLElement) {
175
+ this.logMutationMetadata(node);
176
+ }
177
+ });
178
+ }
179
+ }
180
+ if (shouldRefreshActions) {
181
+ this.refreshActionRegistry();
182
+ }
183
+ if (shouldCheckScreen) {
184
+ const nextSignature = this.buildScreenSignature();
185
+ if (nextSignature !== this.lastScreenSignature) {
186
+ this.lastScreenSignature = nextSignature;
187
+ this.logEvent("screen_context_changed", void 0, this.getScreenContext());
188
+ }
189
+ }
190
+ });
191
+ const root = this.doc.body ?? this.doc.documentElement;
192
+ this.mutationObserver.observe(root, {
193
+ attributes: true,
194
+ attributeFilter: [
195
+ DATA_AI_ACTION,
196
+ DATA_AI_EVENT,
197
+ DATA_AI_ID,
198
+ DATA_AI_SCREEN,
199
+ DATA_AI_SECTION,
200
+ DATA_AI_STATE,
201
+ DATA_AI_STATUS
202
+ ],
203
+ childList: true,
204
+ subtree: true
205
+ });
206
+ }
207
+ logMutationMetadata(root) {
208
+ const eventNodes = [
209
+ ...root.matches(EVENT_SELECTOR) ? [root] : [],
210
+ ...Array.from(root.querySelectorAll(EVENT_SELECTOR))
211
+ ];
212
+ for (const node of eventNodes) {
213
+ const attributes = extractAIAttributes(node);
214
+ const eventType = attributes[DATA_AI_EVENT];
215
+ if (eventType !== void 0) {
216
+ this.logEvent(eventType, attributes[DATA_AI_ID], {
217
+ feedback: attributes[DATA_AI_FEEDBACK],
218
+ status: attributes[DATA_AI_STATUS]
219
+ });
220
+ }
221
+ }
222
+ if (eventNodes.length > 0) {
223
+ this.logEvent("mutation_observed", void 0, {
224
+ nodes: eventNodes.length
225
+ });
226
+ }
227
+ }
228
+ refreshActionRegistry() {
229
+ this.actionRegistry.clear();
230
+ for (const element of this.getVisibleElements(ACTION_SELECTOR)) {
231
+ const action = this.buildActionEntry(element);
232
+ if (action !== null) {
233
+ this.actionRegistry.set(action.id, action);
234
+ }
235
+ }
236
+ }
237
+ buildActionEntry(element) {
238
+ const attributes = extractAIAttributes(element);
239
+ const actionId = attributes[DATA_AI_ACTION];
240
+ if (actionId === void 0) {
241
+ return null;
242
+ }
243
+ const state = parseStates(attributes[DATA_AI_STATE]);
244
+ const disabled = state.includes("disabled") || element.matches(":disabled") || element.getAttribute("aria-disabled") === "true";
245
+ return {
246
+ attributes,
247
+ disabled,
248
+ elementId: attributes[DATA_AI_ID],
249
+ expectedOutcome: attributes[DATA_AI_RESULT],
250
+ id: actionId,
251
+ label: textPreview(element),
252
+ name: actionId,
253
+ role: attributes[DATA_AI_ROLE],
254
+ state,
255
+ target: describeTarget(element)
256
+ };
257
+ }
258
+ extractFieldSchema(field) {
259
+ const attributes = extractAIAttributes(field);
260
+ const fieldId = attributes[DATA_AI_ID] ?? field.getAttribute("id") ?? void 0;
261
+ if (fieldId === void 0) {
262
+ return null;
263
+ }
264
+ const htmlField = field;
265
+ return {
266
+ fieldType: attributes[DATA_AI_FIELD_TYPE] ?? ("type" in htmlField ? htmlField.type : void 0),
267
+ id: fieldId,
268
+ label: findLabelText(field),
269
+ placeholder: "placeholder" in htmlField ? htmlField.placeholder || void 0 : void 0,
270
+ required: attributes[DATA_AI_REQUIRED] === "true" || "required" in htmlField && Boolean(htmlField.required),
271
+ state: parseStates(attributes[DATA_AI_STATE])
272
+ };
273
+ }
274
+ getVisibleElements(selector) {
275
+ return Array.from(this.doc.querySelectorAll(selector)).filter((element) => isVisible(element));
276
+ }
277
+ buildScreenSignature() {
278
+ const context = this.getScreenContext();
279
+ return JSON.stringify(context);
280
+ }
281
+ logEvent(type, targetId, detail) {
282
+ this.recentEvents.unshift({
283
+ detail,
284
+ targetId,
285
+ timestamp: Date.now(),
286
+ type
287
+ });
288
+ if (this.recentEvents.length > MAX_EVENT_LOG) {
289
+ this.recentEvents.length = MAX_EVENT_LOG;
290
+ }
291
+ }
292
+ };
293
+ function installDOMglyphRuntime(targetWindow = window) {
294
+ if (typeof targetWindow === "undefined" || targetWindow.document === void 0) {
295
+ return null;
296
+ }
297
+ if (targetWindow.CORTEX_UI !== void 0) {
298
+ return targetWindow.CORTEX_UI;
299
+ }
300
+ const runtime = new DOMglyphRuntime(targetWindow, targetWindow.document);
301
+ targetWindow.CORTEX_UI = runtime;
302
+ return runtime;
303
+ }
304
+ function parseStates(value) {
305
+ return value?.split(",").map((state) => state.trim()).filter(Boolean) ?? [];
306
+ }
307
+ function describeTarget(element) {
308
+ const href = element.getAttribute("href");
309
+ if (href) {
310
+ return href;
311
+ }
312
+ const target = element.getAttribute("aria-controls");
313
+ if (target) {
314
+ return target;
315
+ }
316
+ return element.getAttribute(DATA_AI_SECTION) ?? void 0;
317
+ }
318
+ function findLabelText(field) {
319
+ const id = field.getAttribute("id");
320
+ if (id !== null) {
321
+ const label = field.ownerDocument?.querySelector(`label[for="${cssEscape(id)}"]`);
322
+ if (label) {
323
+ return textPreview(label);
324
+ }
325
+ }
326
+ return textPreview(field.closest("label"));
327
+ }
328
+ function textPreview(node) {
329
+ const value = node?.textContent?.replace(/\s+/g, " ").trim();
330
+ return value ? value.slice(0, 160) : void 0;
331
+ }
332
+ function isVisible(element) {
333
+ const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
334
+ if (style && (style.display === "none" || style.visibility === "hidden")) {
335
+ return false;
336
+ }
337
+ if (element.hidden || element.getAttribute("aria-hidden") === "true") {
338
+ return false;
339
+ }
340
+ if (typeof element.getClientRects !== "function") {
341
+ return true;
342
+ }
343
+ return element.getClientRects().length > 0 || element === element.ownerDocument?.body;
344
+ }
345
+ function unique(values) {
346
+ return Array.from(new Set(values));
347
+ }
348
+ function isDefined(value) {
349
+ return value !== void 0 && value !== null;
350
+ }
351
+ function cssEscape(value) {
352
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
353
+ return CSS.escape(value);
354
+ }
355
+ return value.replace(/["\\]/g, "\\$&");
356
+ }
357
+
358
+ // src/devtools.ts
359
+ var DEVTOOLS_ROOT_ID = "domglyph-devtools-root";
360
+ var DEVTOOLS_STYLE_ID = "domglyph-devtools-style";
361
+ var INSPECT_SELECTOR = AI_ATTRIBUTE_NAMES.map((name) => `[${name}]`).join(", ");
362
+ function installDOMglyphDevtools(targetWindow = window) {
363
+ if (typeof targetWindow === "undefined" || targetWindow.document === void 0) {
364
+ return null;
365
+ }
366
+ if (targetWindow.CORTEX_UI_DEVTOOLS !== void 0) {
367
+ return targetWindow.CORTEX_UI_DEVTOOLS;
368
+ }
369
+ const runtime = targetWindow.CORTEX_UI ?? installDOMglyphRuntime(targetWindow);
370
+ if (runtime === null) {
371
+ return null;
372
+ }
373
+ const devtools = new DOMglyphDevtoolsOverlay(targetWindow, runtime);
374
+ targetWindow.CORTEX_UI_DEVTOOLS = devtools;
375
+ return devtools;
376
+ }
377
+ var DOMglyphDevtoolsOverlay = class {
378
+ constructor(win, runtime) {
379
+ this.win = win;
380
+ this.runtime = runtime;
381
+ this.doc = win.document;
382
+ ensureStyle(this.doc);
383
+ this.root = this.doc.createElement("div");
384
+ this.root.id = DEVTOOLS_ROOT_ID;
385
+ this.root.style.display = "none";
386
+ this.root.style.pointerEvents = "none";
387
+ this.panel = this.doc.createElement("div");
388
+ this.panel.style.position = "fixed";
389
+ this.panel.style.top = "16px";
390
+ this.panel.style.right = "16px";
391
+ this.panel.style.width = "360px";
392
+ this.panel.style.maxHeight = "calc(100vh - 32px)";
393
+ this.panel.style.overflow = "auto";
394
+ this.panel.style.background = "rgba(10, 19, 16, 0.94)";
395
+ this.panel.style.color = "#effaf4";
396
+ this.panel.style.border = "1px solid rgba(255,255,255,0.08)";
397
+ this.panel.style.borderRadius = "18px";
398
+ this.panel.style.boxShadow = "0 24px 80px rgba(0,0,0,0.28)";
399
+ this.panel.style.padding = "16px";
400
+ this.panel.style.fontFamily = "ui-sans-serif, system-ui, sans-serif";
401
+ this.panel.style.pointerEvents = "auto";
402
+ this.panel.style.zIndex = "2147483647";
403
+ const title = this.doc.createElement("div");
404
+ title.textContent = "DOMglyph Devtools";
405
+ title.style.fontSize = "16px";
406
+ title.style.fontWeight = "700";
407
+ title.style.marginBottom = "12px";
408
+ const subtitle = this.doc.createElement("div");
409
+ subtitle.textContent = "Hover or click any AI-annotated element to inspect metadata.";
410
+ subtitle.style.color = "rgba(239,250,244,0.72)";
411
+ subtitle.style.fontSize = "12px";
412
+ subtitle.style.marginBottom = "16px";
413
+ const detailBlock = createBlock(this.doc, "Selection");
414
+ const screenBlock = createBlock(this.doc, "Screen Context");
415
+ const eventsBlock = createBlock(this.doc, "Recent Events");
416
+ this.detailPanel = detailBlock.body;
417
+ this.screenPanel = screenBlock.body;
418
+ this.eventsPanel = eventsBlock.body;
419
+ this.panel.append(title, subtitle, detailBlock.wrapper, screenBlock.wrapper, eventsBlock.wrapper);
420
+ this.hoverBox = createHighlightBox(this.doc, "rgba(55, 148, 255, 0.95)");
421
+ this.selectedBox = createHighlightBox(this.doc, "rgba(47, 201, 120, 0.95)");
422
+ this.root.append(this.hoverBox, this.selectedBox, this.panel);
423
+ (this.doc.body ?? this.doc.documentElement).append(this.root);
424
+ this.attachListeners();
425
+ this.render();
426
+ }
427
+ doc;
428
+ root;
429
+ panel;
430
+ detailPanel;
431
+ eventsPanel;
432
+ screenPanel;
433
+ hoverBox;
434
+ selectedBox;
435
+ disposeCallbacks = [];
436
+ hoveredElement = null;
437
+ selectedElement = null;
438
+ visible = false;
439
+ refreshTimer = null;
440
+ show() {
441
+ if (this.visible) {
442
+ return;
443
+ }
444
+ this.visible = true;
445
+ this.root.style.display = "block";
446
+ this.doc.documentElement.setAttribute("data-domglyph-devtools", "open");
447
+ this.refreshTimer = this.win.setInterval(() => this.render(), 600);
448
+ this.render();
449
+ }
450
+ hide() {
451
+ if (!this.visible) {
452
+ return;
453
+ }
454
+ this.visible = false;
455
+ this.root.style.display = "none";
456
+ this.doc.documentElement.removeAttribute("data-domglyph-devtools");
457
+ if (this.refreshTimer !== null) {
458
+ this.win.clearInterval(this.refreshTimer);
459
+ this.refreshTimer = null;
460
+ }
461
+ }
462
+ toggle() {
463
+ if (this.visible) {
464
+ this.hide();
465
+ return false;
466
+ }
467
+ this.show();
468
+ return true;
469
+ }
470
+ destroy() {
471
+ this.hide();
472
+ for (const dispose of this.disposeCallbacks) {
473
+ dispose();
474
+ }
475
+ this.root.remove();
476
+ if (this.win.CORTEX_UI_DEVTOOLS === this) {
477
+ delete this.win.CORTEX_UI_DEVTOOLS;
478
+ }
479
+ }
480
+ isVisible() {
481
+ return this.visible;
482
+ }
483
+ attachListeners() {
484
+ const onMouseMove = (event) => {
485
+ const next = event.target instanceof Element ? event.target.closest(INSPECT_SELECTOR) : null;
486
+ this.hoveredElement = next;
487
+ this.updateBox(this.hoverBox, next);
488
+ if (!this.selectedElement) {
489
+ this.renderSelection(next);
490
+ }
491
+ };
492
+ const onClick = (event) => {
493
+ const next = event.target instanceof Element ? event.target.closest(INSPECT_SELECTOR) : null;
494
+ if (next === null) {
495
+ return;
496
+ }
497
+ this.selectedElement = next;
498
+ this.updateBox(this.selectedBox, next);
499
+ this.renderSelection(next);
500
+ };
501
+ const onScrollOrResize = () => {
502
+ this.updateBox(this.hoverBox, this.hoveredElement);
503
+ this.updateBox(this.selectedBox, this.selectedElement);
504
+ };
505
+ this.doc.addEventListener("mousemove", onMouseMove, true);
506
+ this.doc.addEventListener("click", onClick, true);
507
+ this.win.addEventListener("scroll", onScrollOrResize, true);
508
+ this.win.addEventListener("resize", onScrollOrResize);
509
+ this.disposeCallbacks.push(() => this.doc.removeEventListener("mousemove", onMouseMove, true));
510
+ this.disposeCallbacks.push(() => this.doc.removeEventListener("click", onClick, true));
511
+ this.disposeCallbacks.push(() => this.win.removeEventListener("scroll", onScrollOrResize, true));
512
+ this.disposeCallbacks.push(() => this.win.removeEventListener("resize", onScrollOrResize));
513
+ }
514
+ updateBox(box, element) {
515
+ if (element === null || !this.visible) {
516
+ box.style.display = "none";
517
+ return;
518
+ }
519
+ const rect = element.getBoundingClientRect();
520
+ box.style.display = rect.width > 0 && rect.height > 0 ? "block" : "none";
521
+ box.style.transform = `translate(${rect.left + this.win.scrollX}px, ${rect.top + this.win.scrollY}px)`;
522
+ box.style.width = `${rect.width}px`;
523
+ box.style.height = `${rect.height}px`;
524
+ }
525
+ render() {
526
+ if (!this.visible) {
527
+ return;
528
+ }
529
+ this.screenPanel.textContent = formatJSON(this.runtime.getScreenContext());
530
+ this.eventsPanel.textContent = formatJSON(this.runtime.getRecentEvents().slice(0, 12));
531
+ this.renderSelection(this.selectedElement ?? this.hoveredElement);
532
+ this.updateBox(this.hoverBox, this.hoveredElement);
533
+ this.updateBox(this.selectedBox, this.selectedElement);
534
+ }
535
+ renderSelection(element) {
536
+ if (element === null) {
537
+ this.detailPanel.textContent = "No AI-annotated element selected.";
538
+ return;
539
+ }
540
+ const attrs = extractAIAttributes2(element);
541
+ const payload = {
542
+ id: attrs[DATA_AI_ID2],
543
+ role: attrs[DATA_AI_ROLE2],
544
+ action: attrs[DATA_AI_ACTION2],
545
+ state: attrs[DATA_AI_STATE2],
546
+ tagName: element.tagName.toLowerCase(),
547
+ text: element.textContent?.trim().slice(0, 160) || void 0,
548
+ attributes: attrs
549
+ };
550
+ this.detailPanel.textContent = formatJSON(payload);
551
+ }
552
+ };
553
+ function ensureStyle(doc) {
554
+ if (doc.getElementById(DEVTOOLS_STYLE_ID) !== null) {
555
+ return;
556
+ }
557
+ const style = doc.createElement("style");
558
+ style.id = DEVTOOLS_STYLE_ID;
559
+ style.textContent = `
560
+ html[data-domglyph-devtools="open"] ${INSPECT_SELECTOR} {
561
+ outline: 1px dashed rgba(47, 201, 120, 0.45);
562
+ outline-offset: 2px;
563
+ }
564
+ `;
565
+ (doc.head ?? doc.documentElement).append(style);
566
+ }
567
+ function createBlock(doc, label) {
568
+ const wrapper = doc.createElement("div");
569
+ wrapper.style.marginBottom = "14px";
570
+ const heading = doc.createElement("div");
571
+ heading.textContent = label;
572
+ heading.style.fontSize = "11px";
573
+ heading.style.letterSpacing = "0.14em";
574
+ heading.style.textTransform = "uppercase";
575
+ heading.style.color = "rgba(239,250,244,0.62)";
576
+ heading.style.marginBottom = "8px";
577
+ const body = doc.createElement("pre");
578
+ body.style.margin = "0";
579
+ body.style.padding = "12px";
580
+ body.style.borderRadius = "12px";
581
+ body.style.background = "rgba(255,255,255,0.05)";
582
+ body.style.border = "1px solid rgba(255,255,255,0.08)";
583
+ body.style.font = "12px/1.45 SFMono-Regular, Consolas, monospace";
584
+ body.style.whiteSpace = "pre-wrap";
585
+ body.style.wordBreak = "break-word";
586
+ wrapper.append(heading, body);
587
+ return { body, wrapper };
588
+ }
589
+ function createHighlightBox(doc, color) {
590
+ const box = doc.createElement("div");
591
+ box.style.position = "absolute";
592
+ box.style.top = "0";
593
+ box.style.left = "0";
594
+ box.style.pointerEvents = "none";
595
+ box.style.border = `2px solid ${color}`;
596
+ box.style.background = color.replace("0.95", "0.08");
597
+ box.style.borderRadius = "10px";
598
+ box.style.boxShadow = `0 0 0 1px ${color}`;
599
+ box.style.zIndex = "2147483646";
600
+ box.style.display = "none";
601
+ return box;
602
+ }
603
+ function formatJSON(value) {
604
+ return JSON.stringify(value, null, 2) ?? "null";
605
+ }
606
+
607
+ // src/index.ts
608
+ var runtimeRegistry = {
609
+ prompt: "browser-inspection-runtime",
610
+ components: [
611
+ "getScreenContext",
612
+ "getAvailableActions",
613
+ "getFormSchema",
614
+ "getVisibleEntities",
615
+ "getRecentEvents"
616
+ ]
617
+ };
618
+ var defaultRuntimeEvent = AIEvent2.ACTION_TRIGGERED;
619
+ var CORTEX_UI = typeof window === "undefined" ? null : installDOMglyphRuntime(window);
620
+ var CORTEX_UI_DEVTOOLS = typeof window === "undefined" ? null : installDOMglyphDevtools(window);
621
+ export {
622
+ CORTEX_UI,
623
+ CORTEX_UI_DEVTOOLS,
624
+ DOMglyphRuntime,
625
+ defaultRuntimeEvent,
626
+ installDOMglyphDevtools,
627
+ installDOMglyphRuntime,
628
+ runtimeRegistry
629
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@domglyph/runtime",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@domglyph/ai-contract": "2.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsup": "^8.5.1"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup src/index.ts --format esm --dts --clean",
25
+ "dev": "tsup src/index.ts --format esm --dts --watch",
26
+ "lint": "eslint src --ext .ts",
27
+ "test": "vitest run --passWithNoTests",
28
+ "typecheck": "tsc -b"
29
+ }
30
+ }