@digital-gravy/etch-connector 0.1.2 → 0.1.4
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/README.md +18 -30
- package/dist/cli.js +43 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Claude Code / Cowork
|
|
|
23
23
|
│ shell: etch-connector eval -t homepage "return ..."
|
|
24
24
|
▼
|
|
25
25
|
etch-connector (the `serve` daemon)
|
|
26
|
-
│ ▲ WebSocket (tab dials in via etch.
|
|
26
|
+
│ ▲ WebSocket (tab dials in via etch.connectAs)
|
|
27
27
|
▼ │
|
|
28
28
|
Etch builder tab ──► window.etch.*
|
|
29
29
|
```
|
|
@@ -32,8 +32,7 @@ Claude Code / Cowork
|
|
|
32
32
|
|
|
33
33
|
This is an **eval relay**: the harness sends a JavaScript snippet, the page runs
|
|
34
34
|
it as an async function, and the return value + console output come back. It is
|
|
35
|
-
|
|
36
|
-
thin add — see [Going to MCP](#going-to-mcp-later).
|
|
35
|
+
not an MCP server yet — see [Going to MCP](#going-to-mcp-later).
|
|
37
36
|
|
|
38
37
|
Scripts run in a **page-enforced safe mode**: the script sees only `etch` and
|
|
39
38
|
the standard JS built-ins. `window`, `document`, `fetch`, storage, `eval`, … are
|
|
@@ -75,13 +74,19 @@ etch-connector serve
|
|
|
75
74
|
|
|
76
75
|
### 2. Connect a tab
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
**Primary way — the AI button.** Enable the **AI Connector** in the Etch
|
|
78
|
+
Settings, then open an Etch builder page and click the **AI button** in the left
|
|
79
|
+
sidebar. It connects this tab for you, registering it under your site name (it
|
|
80
|
+
just calls `connectAs` under the hood). Click it again to disconnect.
|
|
81
|
+
|
|
82
|
+
**Secondary way — the console.** You can also connect manually from the browser
|
|
83
|
+
console:
|
|
79
84
|
|
|
80
85
|
```js
|
|
81
|
-
etch.
|
|
86
|
+
etch.connectAs('homepage');
|
|
82
87
|
```
|
|
83
88
|
|
|
84
|
-
(`
|
|
89
|
+
(`connectAs` is part of the public API; it opens the WebSocket back to the
|
|
85
90
|
daemon and registers this tab under the given name. Pass `{ port: <n> }` if you
|
|
86
91
|
started the daemon on a non-default WebSocket port.)
|
|
87
92
|
|
|
@@ -144,7 +149,7 @@ Usage:
|
|
|
144
149
|
etch-connector computed <selector> [-t name] [--props a,b,c] (CDP)
|
|
145
150
|
|
|
146
151
|
Two backends:
|
|
147
|
-
bridge (default) the tab dials in via etch.
|
|
152
|
+
bridge (default) the tab dials in via etch.connectAs(); page-sandboxed.
|
|
148
153
|
Scripts see only "etch" + the standard JS built-ins.
|
|
149
154
|
cdp connect OUT to a Chrome started with --remote-debugging-port=9222.
|
|
150
155
|
No daemon. Runs at DevTools privilege: full "window", plus screenshots,
|
|
@@ -191,22 +196,10 @@ the same one a future MCP server would call):
|
|
|
191
196
|
|
|
192
197
|
## Going to MCP later
|
|
193
198
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
import { BridgeServer } from 'etch-connector';
|
|
199
|
-
|
|
200
|
-
const bridge = new BridgeServer();
|
|
201
|
-
await bridge.listen();
|
|
202
|
-
|
|
203
|
-
// list_etch_tabs -> bridge.listTabs()
|
|
204
|
-
// eval_etch_script -> bridge.evalInTab(code, { tab })
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
When you want first-class per-tool MCP tools (rather than one eval tool), have
|
|
208
|
-
the Etch tab advertise a tool manifest in its `hello` and register those
|
|
209
|
-
dynamically — keeping a single source of truth in the browser registry.
|
|
199
|
+
Today this is a CLI eval relay. A future version will expose the same bridge as
|
|
200
|
+
a proper MCP server, so an AI harness can call first-class tools (list tabs, run
|
|
201
|
+
a script) directly instead of shelling out — without changing how you connect a
|
|
202
|
+
tab.
|
|
210
203
|
|
|
211
204
|
## Security
|
|
212
205
|
|
|
@@ -223,10 +216,5 @@ hardened sandbox**: because the script shares the page's JS realm, a _determined
|
|
|
223
216
|
author can still reach globals through prototype / constructor chains (e.g.
|
|
224
217
|
`({}).constructor.constructor`). It reliably keeps casual and AI-authored
|
|
225
218
|
scripts on the `etch` surface and closes the obvious escape hatches; it does
|
|
226
|
-
**not** contain hostile code.
|
|
227
|
-
|
|
228
|
-
For a true boundary against adversarial scripts you would need to harden the
|
|
229
|
-
realm (SES `lockdown()`), which freezes all intrinsics app-wide and is out of
|
|
230
|
-
scope for this relay. The enforcement lives in the Etch page (`bridge.ts`), not
|
|
231
|
-
this connector — the connector only carries the requested mode; a string filter
|
|
232
|
-
here would be theater.
|
|
219
|
+
**not** contain hostile code. For a true boundary against adversarial scripts,
|
|
220
|
+
run the daemon only on a trusted machine.
|
package/dist/cli.js
CHANGED
|
@@ -133,7 +133,7 @@ async function runServe(flags) {
|
|
|
133
133
|
console.error(`[etch-connector] control ${controlBaseUrl({ host: controlHost, port: controlPort })}`);
|
|
134
134
|
console.error(`[etch-connector] default mode: ${defaultMode}${defaultMode === 'unsafe' ? ' ⚠ scripts get full access to the page' : ' (scripts see only window.etch + JS built-ins)'}`);
|
|
135
135
|
console.error('[etch-connector] In an Etch builder tab, run:');
|
|
136
|
-
console.error(`[etch-connector] etch.
|
|
136
|
+
console.error(`[etch-connector] etch.connectAs('my-tab'${boundWsPort === DEFAULT_WS_PORT ? '' : `, { port: ${boundWsPort} }`})`);
|
|
137
137
|
console.error('[etch-connector] then drive it with: etch-connector eval -t my-tab "return window.etch.apiVersion"');
|
|
138
138
|
const shutdown = () => {
|
|
139
139
|
clearInterval(heartbeat);
|
|
@@ -403,7 +403,7 @@ Usage:
|
|
|
403
403
|
etch-connector computed <selector> [-t name] [--props a,b,c] (CDP)
|
|
404
404
|
|
|
405
405
|
Two backends:
|
|
406
|
-
bridge (default) the tab dials in via etch.
|
|
406
|
+
bridge (default) the tab dials in via etch.connectAs(); page-sandboxed.
|
|
407
407
|
Scripts see only "etch" + the standard JS built-ins.
|
|
408
408
|
cdp connect OUT to a Chrome started with --remote-debugging-port=${DEFAULT_CDP_PORT}.
|
|
409
409
|
No daemon. Runs at DevTools privilege: full "window", plus screenshots,
|
|
@@ -414,6 +414,17 @@ AGENT: before running anything, ask the user how they want to connect — bridge
|
|
|
414
414
|
(API only) or cdp (DevTools privilege: full page access). cdp grants more reach;
|
|
415
415
|
don't assume — let the user choose.
|
|
416
416
|
|
|
417
|
+
Connecting a bridge tab (AGENT: walk the user through activating it — the AI
|
|
418
|
+
button is the primary path; don't just wait for a tab to appear):
|
|
419
|
+
1. Tell them to open Etch Settings and enable the AI Connector.
|
|
420
|
+
2. Tell them to open a builder page and click the AI button in the left
|
|
421
|
+
sidebar — it connects the tab and registers it under the site name (calls
|
|
422
|
+
connectAs under the hood); clicking it again disconnects.
|
|
423
|
+
Fallback — they can connect from the browser console:
|
|
424
|
+
etch.connectAs('homepage') (add { port: <n> } for a non-default WS port)
|
|
425
|
+
cdp needs no connect step — start Chrome with --remote-debugging-port and
|
|
426
|
+
target a tab with -t.
|
|
427
|
+
|
|
417
428
|
Examples:
|
|
418
429
|
etch-connector serve
|
|
419
430
|
etch-connector tabs
|
|
@@ -437,7 +448,23 @@ changes and training data may be stale):
|
|
|
437
448
|
Manual fallback https://docs.etchwp.com/public-api (window.etch.*)
|
|
438
449
|
https://docs.etchwp.com (everything else)
|
|
439
450
|
|
|
440
|
-
Building Etch pages (AGENT: conventions
|
|
451
|
+
Building Etch pages (AGENT: conventions — follow these or the builder won't
|
|
452
|
+
render what you author):
|
|
453
|
+
Images. ALWAYS use the Dynamic Image element (etch/dynamic-image, <etch:img>),
|
|
454
|
+
NEVER a plain etch/element with tag "img". Bind it by WP media ID, not a URL:
|
|
455
|
+
attributes: { mediaId: "{props.image}", useSrcSet: "true", alt: "{props.name}" }
|
|
456
|
+
The backing prop must be a Media prop, not a text prop:
|
|
457
|
+
type: { primitive: "string", specialized: "wpMediaId" }
|
|
458
|
+
(specialized "image" is the URL-based picker — NOT for mediaId.) Instances
|
|
459
|
+
then pass the numeric media ID, e.g. image: "10" — not the file URL.
|
|
460
|
+
|
|
461
|
+
Classes. A block's classes ARE its read-only \`styles\` array, not an HTML
|
|
462
|
+
attribute. Setting attributes.class in authored JSON is silently dropped, and
|
|
463
|
+
\`styles\` is rejected on authoring. Assign classes with etch.blocks.addClass()
|
|
464
|
+
— for component blocks, inside enterComponentEditMode() →
|
|
465
|
+
saveComponentEditModeAsync() → exitComponentEditMode(). An existing class
|
|
466
|
+
name re-links to its style record automatically (styling is preserved).
|
|
467
|
+
|
|
441
468
|
Sections. When you create a top-level section, always tag it
|
|
442
469
|
data-etch-element="section" and nest ONE container inside it as its inner —
|
|
443
470
|
a div block with data-etch-element="container" — and put the section's
|
|
@@ -453,6 +480,19 @@ Building Etch pages (AGENT: conventions for structuring content):
|
|
|
453
480
|
side padding, or inter-section margins — the pair already handles layout
|
|
454
481
|
structure, spacing, and content width.
|
|
455
482
|
|
|
483
|
+
Gotchas.
|
|
484
|
+
• NEVER call etch.*.saveAsync (or otherwise persist) unless the user
|
|
485
|
+
explicitly asks to save — authored changes stay in the builder until then.
|
|
486
|
+
• Component instance props are attributes: set them at create()/replace()
|
|
487
|
+
time, or update an existing instance in place with
|
|
488
|
+
etch.blocks.update(instanceId, { attributes: { image: "10" } })
|
|
489
|
+
• updateAsync({ blocks }) requires styles:[] on every element block
|
|
490
|
+
(validation differs from blocks.create(), which omits styles entirely).
|
|
491
|
+
• No etch.media API. Resolve a URL → media ID via the WP REST API:
|
|
492
|
+
curl 'http://<site>/?rest_route=/wp/v2/media&per_page=100&_fields=id,source_url'
|
|
493
|
+
(rest_route form works on plain permalinks; /wp-json/wp/v2/media on pretty
|
|
494
|
+
ones.)
|
|
495
|
+
|
|
456
496
|
See AGENTS.md for project conventions.`);
|
|
457
497
|
}
|
|
458
498
|
async function main() {
|