@digital-gravy/etch-connector 0.1.1 → 0.1.2

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 +51 -72
  2. package/dist/cli.js +45 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # etch-connector
2
2
 
3
+ > **AGENT: read this first.** Before interacting with Etch in any way, run
4
+ > `etch-connector --help` (or `npx @digital-gravy/etch-connector --help`) and
5
+ > read the **entire** output. It documents the backends, commands, flags, the
6
+ > stdout/stderr/exit-code contract, and how to pull the current Etch docs —
7
+ > everything you need to drive a tab correctly. Do not call any other command
8
+ > until you have read it.
9
+
3
10
  A tiny local bridge that lets an AI harness — Claude Code, Cowork, or any tool
4
11
  that can run a shell command — **drive a live Etch builder tab** by evaluating
5
12
  scripts against the [`etch-public-api`](https://www.npmjs.com/package/@digital-gravy/etch-public-api)
@@ -28,25 +35,40 @@ it as an async function, and the return value + console output come back. It is
28
35
  intentionally _not_ an MCP server yet, but it is built so that becoming one is a
29
36
  thin add — see [Going to MCP](#going-to-mcp-later).
30
37
 
31
- Scripts run in one of two modes, enforced by the page:
38
+ Scripts run in a **page-enforced safe mode**: the script sees only `etch` and
39
+ the standard JS built-ins. `window`, `document`, `fetch`, storage, `eval`, … are
40
+ withheld.
41
+
42
+ ## Install & run
43
+
44
+ No install needed — run it on demand with `npx` (npm) or `bunx` (Bun):
32
45
 
33
- - **`safe`** (default) — the script sees only `etch` and the standard JS
34
- built-ins. `window`, `document`, `fetch`, storage, `eval`, … are withheld.
35
- - **`unsafe`** — the script runs with full access to the page. Opt in
36
- explicitly.
46
+ ```bash
47
+ npx @digital-gravy/etch-connector serve
48
+ # or
49
+ bunx @digital-gravy/etch-connector serve
50
+ ```
37
51
 
38
- ## Install
52
+ To get the short `etch-connector` command on your `PATH`, install it globally:
39
53
 
40
54
  ```bash
41
- bun install # or npm install
55
+ npm install -g @digital-gravy/etch-connector # or: bun install -g …
56
+ etch-connector serve
42
57
  ```
43
58
 
59
+ > Using the connector requires a valid, active Etch license — see
60
+ > [`LICENSE`](LICENSE).
61
+
62
+ The examples below use the bare `etch-connector` command; if you're running via
63
+ `npx`/`bunx` instead, prefix them with `npx @digital-gravy/etch-connector` (or
64
+ `bunx …`).
65
+
44
66
  ## Use
45
67
 
46
68
  ### 1. Start the daemon (once)
47
69
 
48
70
  ```bash
49
- bun run serve
71
+ etch-connector serve
50
72
  # bridge ws://127.0.0.1:7331
51
73
  # control http://127.0.0.1:7332
52
74
  ```
@@ -70,20 +92,17 @@ started the daemon on a non-default WebSocket port.)
70
92
  etch-connector tabs
71
93
 
72
94
  # run a script (value → stdout, console output → stderr).
73
- # Default is safe mode: reference the API as `etch.*`.
95
+ # reference the API as `etch.*`.
74
96
  etch-connector eval -t homepage "return etch.apiVersion"
75
97
 
76
98
  # multi-line / from a file / from stdin
77
99
  etch-connector eval -t homepage -f ./script.js
78
100
  echo "return await etch.blocks.getTree()" | etch-connector eval -t homepage
79
-
80
- # full page access (opt in)
81
- etch-connector eval -t homepage --unsafe "return document.title"
82
101
  ```
83
102
 
84
103
  The script body runs as an **async function**, so it can `return` a value and
85
- use top-level `await`. In safe mode write scripts against the `etch-public-api`
86
- surface via the `etch` binding (`window` is not available). Examples:
104
+ use top-level `await`. Write scripts against the `etch-public-api` surface via
105
+ the `etch` binding (`window` is not available). Examples:
87
106
 
88
107
  ```js
89
108
  return etch.apiVersion;
@@ -93,16 +112,6 @@ const groups = await etch.fields.listGroupsAsync();
93
112
  return groups.length;
94
113
  ```
95
114
 
96
- ### Modes
97
-
98
- | Mode | In scope | Use for |
99
- | ---------------- | ------------------------------------ | ------------------------------- |
100
- | `safe` (default) | `etch` + standard JS built-ins | Driving the builder via the API |
101
- | `unsafe` | everything (`window`, `document`, …) | Debugging, page internals |
102
-
103
- Pick per call with `--safe` / `--unsafe`, or change the daemon default with
104
- `serve --unsafe`. A per-call flag overrides the daemon default.
105
-
106
115
  ### Output contract (for agents)
107
116
 
108
117
  `eval` keeps stdout clean so a harness can capture it as the result:
@@ -112,40 +121,14 @@ Pick per call with `--safe` / `--unsafe`, or change the daemon default with
112
121
  - **exit code** — `0` success · `2` the script threw · `1` operational failure
113
122
  (no such tab, ambiguous target, timeout, daemon unreachable).
114
123
 
115
- ## Documentation (for AI agents)
116
-
117
- Before writing scripts against `etch.*`, **always pull the current Etch
118
- documentation first** — the `etch-public-api` surface changes and training data
119
- may be stale. Fetch the docs, _then_ write scripts.
120
-
121
- ### Preferred: Context7 MCP
122
-
123
- If a Context7 MCP server is available, fetch the Etch docs through it:
124
-
125
- 1. Call `resolve-library-id` with the library name **`Etch`** (pass the user's
126
- question alongside it).
127
- 2. From the matches, pick the entry for Etch / the Etch public API — prefer an
128
- exact name match, and a version-specific id if the user named a version.
129
- 3. Call `query-docs` with the selected library id and the specific question
130
- (e.g. _"how do I create a block with etch.blocks.create"_).
131
- 4. Write the script using the fetched docs, referencing `etch.*` exactly as the
132
- docs show.
133
-
134
- ### Fallback: pull the docs manually
135
-
136
- If no Context7 server is available, fetch the documentation directly from the
137
- Etch docs site:
138
-
139
- - **Public API reference:** <https://docs.etchwp.com/public-api> — the
140
- authoritative surface for `window.etch.*` (`etch.blocks`, `etch.styles`,
141
- `etch.components`, `etch.fields`, `etch.stylesheets`, …).
142
- - **Main docs:** <https://docs.etchwp.com> — for everything else about Etch
143
- (concepts, blocks, components, ACSS, units, …). Follow the links from there to
144
- the specific page that answers the question.
124
+ ## For AI agents
145
125
 
146
- Read the relevant page(s), then write scripts against the documented API. On top
147
- of the API docs, follow the project-specific conventions in
148
- [`AGENTS.md`](AGENTS.md).
126
+ The authoritative agent instructions live in **`etch-connector --help`** — read
127
+ the entire output before interacting with Etch. It covers the backends, commands
128
+ and flags, the stdout/stderr/exit-code contract, how to pull the current Etch
129
+ docs (Context7 MCP or <https://docs.etchwp.com/public-api>), and the conventions
130
+ for structuring pages (e.g. the section/container layout pattern). Project
131
+ conventions for working on this repo live in [`AGENTS.md`](AGENTS.md).
149
132
 
150
133
  ## CLI
151
134
 
@@ -153,17 +136,16 @@ of the API docs, follow the project-specific conventions in
153
136
  etch-connector — drive a live Etch tab from the command line
154
137
 
155
138
  Usage:
156
- etch-connector serve [--ws-port 7331] [--control-port 7332] [--ws-host 127.0.0.1] [--unsafe]
139
+ etch-connector serve [--ws-port 7331] [--control-port 7332] [--ws-host 127.0.0.1]
157
140
  etch-connector tabs [--json] [--control-port 7332] [--cdp]
158
- etch-connector eval [code] [-t|--tab name] [-f|--file path] [--timeout ms] [--safe|--unsafe|--cdp]
141
+ etch-connector eval [code] [-t|--tab name] [-f|--file path] [--timeout ms] [--cdp]
159
142
  etch-connector shot [-t name] [-s|--selector css] [--full] [--jpeg] [-o|--out file] [--freeze=false] (CDP)
160
143
  etch-connector html <selector> [-t name] (CDP)
161
144
  etch-connector computed <selector> [-t name] [--props a,b,c] (CDP)
162
145
 
163
146
  Two backends:
164
147
  bridge (default) the tab dials in via etch.connectTo(); page-sandboxed.
165
- safe = script sees only "etch" + JS built-ins (default)
166
- unsafe = full page access (--unsafe per eval, or serve --unsafe)
148
+ Scripts see only "etch" + the standard JS built-ins.
167
149
  cdp connect OUT to a Chrome started with --remote-debugging-port=9222.
168
150
  No daemon. Runs at DevTools privilege: full "window", plus screenshots,
169
151
  rendered HTML, and computed styles. Opt in with --cdp (or --cdp-port /
@@ -234,17 +216,14 @@ Two layers, neither a substitute for the other:
234
216
  the daemon. Run it only on a trusted machine and do not expose the ports beyond
235
217
  loopback.
236
218
 
237
- **Execution.** `safe` mode (the default) runs the script inside a `with`-bound
238
- Proxy that exposes only `etch` plus the standard JS built-ins and throws on
239
- `window`, `document`, `fetch`, storage, `eval`, `Function`, … This is a
240
- **guardrail, not a hardened sandbox**: because the script shares the page's JS
241
- realm, a _determined_ author can still reach globals through prototype /
242
- constructor chains (e.g. `({}).constructor.constructor`). Safe mode reliably
243
- keeps casual and AI-authored scripts on the `etch` surface and closes the
244
- obvious escape hatches; it does **not** contain hostile code.
245
-
246
- `unsafe` mode runs the script with full, unconstrained access to the page — only
247
- use it for trusted debugging.
219
+ **Execution.** Bridge mode runs the script inside a `with`-bound Proxy that
220
+ exposes only `etch` plus the standard JS built-ins and throws on `window`,
221
+ `document`, `fetch`, storage, `eval`, `Function`, … This is a **guardrail, not a
222
+ hardened sandbox**: because the script shares the page's JS realm, a _determined_
223
+ author can still reach globals through prototype / constructor chains (e.g.
224
+ `({}).constructor.constructor`). It reliably keeps casual and AI-authored
225
+ scripts on the `etch` surface and closes the obvious escape hatches; it does
226
+ **not** contain hostile code.
248
227
 
249
228
  For a true boundary against adversarial scripts you would need to harden the
250
229
  realm (SES `lockdown()`), which freezes all intrinsics app-wide and is out of
package/dist/cli.js CHANGED
@@ -17,6 +17,14 @@ import { createControlServer, controlBaseUrl } from './control-server.js';
17
17
  import { CdpClient, CdpError, CdpEvalError } from './cdp-client.js';
18
18
  import { DEFAULT_CONTROL_PORT, DEFAULT_HOST, DEFAULT_WS_PORT } from './protocol.js';
19
19
  const DEFAULT_CDP_PORT = 9222;
20
+ /**
21
+ * Unsafe mode (scripts get full, unconstrained access to the page) is
22
+ * intentionally DISABLED. The `--unsafe` flag and the branches that honor it
23
+ * are kept below as a deliberate dead path behind this constant — never
24
+ * reachable in shipped builds, and undocumented in --help and the README — so
25
+ * the machinery can be revived later without rebuilding it. Leave it `false`.
26
+ */
27
+ const ALLOW_UNSAFE = false;
20
28
  /** Long flags that never take a value, so they don't swallow the next token. */
21
29
  const BOOLEAN_FLAGS = new Set([
22
30
  'cdp',
@@ -110,7 +118,8 @@ async function runServe(flags) {
110
118
  const wsPort = num(flags['ws-port'], DEFAULT_WS_PORT);
111
119
  const controlHost = str(flags['control-host']) ?? DEFAULT_HOST;
112
120
  const controlPort = num(flags['control-port'], DEFAULT_CONTROL_PORT);
113
- const defaultMode = flags['unsafe'] ? 'unsafe' : 'safe';
121
+ // --unsafe is a dead path (see ALLOW_UNSAFE); defaultMode is always 'safe'.
122
+ const defaultMode = ALLOW_UNSAFE && flags['unsafe'] ? 'unsafe' : 'safe';
114
123
  const bridge = new BridgeServer({ host: wsHost, port: wsPort });
115
124
  bridge.on('connect', (tab) => console.error(`[etch-connector] + tab "${tab.name}" (${tab.url})`));
116
125
  bridge.on('disconnect', (tab) => console.error(`[etch-connector] - tab "${tab.name}"`));
@@ -271,9 +280,10 @@ async function runEval(positional, flags) {
271
280
  }
272
281
  return;
273
282
  }
274
- // --unsafe / --safe override the daemon default; omitting both defers to it.
283
+ // --safe overrides the daemon default; omitting it defers to the daemon.
284
+ // --unsafe is a dead path (see ALLOW_UNSAFE): it never sets unsafe mode.
275
285
  let mode;
276
- if (flags['unsafe'])
286
+ if (ALLOW_UNSAFE && flags['unsafe'])
277
287
  mode = 'unsafe';
278
288
  else if (flags['safe'])
279
289
  mode = 'safe';
@@ -376,26 +386,33 @@ async function runComputed(positional, flags) {
376
386
  function printHelp() {
377
387
  console.error(`etch-connector — drive a live Etch tab from the command line
378
388
 
389
+ Run without installing (requires a valid, active Etch license):
390
+ npx @digital-gravy/etch-connector serve
391
+ bunx @digital-gravy/etch-connector serve
392
+ Or install globally for the bare \`etch-connector\` command:
393
+ npm install -g @digital-gravy/etch-connector # or: bun install -g …
394
+ (When running via npx/bunx, prefix the commands below with
395
+ \`npx @digital-gravy/etch-connector\` / \`bunx …\`.)
396
+
379
397
  Usage:
380
- etch-connector serve [--ws-port ${DEFAULT_WS_PORT}] [--control-port ${DEFAULT_CONTROL_PORT}] [--ws-host ${DEFAULT_HOST}] [--unsafe]
398
+ etch-connector serve [--ws-port ${DEFAULT_WS_PORT}] [--control-port ${DEFAULT_CONTROL_PORT}] [--ws-host ${DEFAULT_HOST}]
381
399
  etch-connector tabs [--json] [--control-port ${DEFAULT_CONTROL_PORT}] [--cdp]
382
- etch-connector eval [code] [-t|--tab name] [-f|--file path] [--timeout ms] [--safe|--unsafe|--cdp]
400
+ etch-connector eval [code] [-t|--tab name] [-f|--file path] [--timeout ms] [--cdp]
383
401
  etch-connector shot [-t name] [-s|--selector css] [--full] [--jpeg] [-o|--out file] [--freeze=false] (CDP)
384
402
  etch-connector html <selector> [-t name] (CDP)
385
403
  etch-connector computed <selector> [-t name] [--props a,b,c] (CDP)
386
404
 
387
405
  Two backends:
388
406
  bridge (default) the tab dials in via etch.connectTo(); page-sandboxed.
389
- safe = script sees only "etch" + JS built-ins (default)
390
- unsafe = full page access (--unsafe per eval, or serve --unsafe)
407
+ Scripts see only "etch" + the standard JS built-ins.
391
408
  cdp connect OUT to a Chrome started with --remote-debugging-port=${DEFAULT_CDP_PORT}.
392
409
  No daemon. Runs at DevTools privilege: full "window", plus screenshots,
393
410
  rendered HTML, and computed styles. Opt in with --cdp (or --cdp-port /
394
411
  --remote-debugging-port / --cdp-host). Target a tab with -t <url|title substring>.
395
412
 
396
413
  AGENT: before running anything, ask the user how they want to connect — bridge
397
- safe (API only), bridge unsafe (full page access), or cdp (DevTools privilege).
398
- Each grants more reach than the last; don't assume — let the user choose.
414
+ (API only) or cdp (DevTools privilege: full page access). cdp grants more reach;
415
+ don't assume — let the user choose.
399
416
 
400
417
  Examples:
401
418
  etch-connector serve
@@ -410,8 +427,8 @@ Examples:
410
427
  etch-connector computed --cdp -t etch -s .orbit__chip --props transform,animation-duration
411
428
 
412
429
  The eval body runs as an async function, so it may 'return' a value and use
413
- top-level 'await'. In bridge safe mode reference the API as "etch.*" ("window"
414
- is withheld); in cdp mode the full page is available.
430
+ top-level 'await'. In bridge mode reference the API as "etch.*" ("window" is
431
+ withheld); in cdp mode the full page is available.
415
432
 
416
433
  Docs (AGENT: pull these BEFORE writing scripts against etch.* — the API surface
417
434
  changes and training data may be stale):
@@ -419,6 +436,23 @@ changes and training data may be stale):
419
436
  specific call (e.g. etch.blocks.create).
420
437
  Manual fallback https://docs.etchwp.com/public-api (window.etch.*)
421
438
  https://docs.etchwp.com (everything else)
439
+
440
+ Building Etch pages (AGENT: conventions for structuring content):
441
+ Sections. When you create a top-level section, always tag it
442
+ data-etch-element="section" and nest ONE container inside it as its inner —
443
+ a div block with data-etch-element="container" — and put the section's
444
+ content inside that container, not directly in the section:
445
+
446
+ section data-etch-element="section"
447
+ └─ div data-etch-element="container" ← content goes here
448
+ └─ … your blocks …
449
+
450
+ section + container carry Etch's default styles, so you get vertical spacing
451
+ / gutter between sections, horizontal edge padding, and a constrained,
452
+ centered content width for free. Do NOT hand-roll wrapper widths, max-widths,
453
+ side padding, or inter-section margins — the pair already handles layout
454
+ structure, spacing, and content width.
455
+
422
456
  See AGENTS.md for project conventions.`);
423
457
  }
424
458
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-gravy/etch-connector",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },