@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.
Files changed (3) hide show
  1. package/README.md +18 -30
  2. package/dist/cli.js +43 -3
  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.connectTo)
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
- intentionally _not_ an MCP server yet, but it is built so that becoming one is a
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
- Open an Etch builder page and, in the browser console:
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.connectTo('homepage');
86
+ etch.connectAs('homepage');
82
87
  ```
83
88
 
84
- (`connectTo` is part of the public API; it opens the WebSocket back to the
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.connectTo(); page-sandboxed.
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
- Nothing in the transport changes. An MCP server is `new BridgeServer()` plus two
195
- tool definitions over the same core:
196
-
197
- ```ts
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.connectTo('my-tab'${boundWsPort === DEFAULT_WS_PORT ? '' : `, { port: ${boundWsPort} }`})`);
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.connectTo(); page-sandboxed.
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 for structuring content):
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-gravy/etch-connector",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },