@aeptus/aexeo-emdash 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@aeptus/aexeo-emdash` are listed here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/),
5
+ and the project follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-04-28
10
+
11
+ First public release.
12
+
13
+ ### Added
14
+
15
+ - `seogeoPlugin()` factory for **configured mode** — the recommended
16
+ install path for first-party emdash sites. Plugin runs in-process
17
+ inside the host Worker. No sidecar deploy, no auth token, no admin
18
+ Setup page.
19
+ - `seogeoPluginSandboxed({ evaluatorHost })` factory for **sandboxed
20
+ mode** — preserved for future third-party-deploy scenarios where
21
+ the plugin code shouldn't have full host access. Requires a
22
+ separately-deployed sidecar Worker (template at
23
+ `seogeo-crawl-worker` in the source repo).
24
+ - Block Kit admin pages: findings table, per-document panel,
25
+ intelligence-score dashboard widget. Refresh button on the
26
+ findings page sweeps every document in the configured collections.
27
+ - `content:afterSave` hook (configured mode only): saves trigger an
28
+ automatic re-evaluation of the changed document and update the
29
+ findings table without manual intervention.
30
+ - WASM-backed evaluator powered by the `aexeo-emdash-bridge` Rust
31
+ crate (same engine as the seogeo CLI). Compiles via the consumer's
32
+ Vite/Wrangler chain — `vite-plugin-wasm` is a peer-side runtime
33
+ dependency for Vite-driven Cloudflare deploys.
34
+
35
+ ### Compatibility
36
+
37
+ - emdash `>= 0.7.0`
38
+ - `@astrojs/cloudflare` adapter (Cloudflare Workers target). The
39
+ plugin will silently noop on the Node adapter.
40
+ - `vite-plugin-wasm` required in the consumer's `astro.config.mjs`
41
+ vite plugins for the WASM import to resolve as a precompiled
42
+ `WebAssembly.Module`.
43
+
44
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/seogeo-plugin-v0.1.0...HEAD
45
+ [0.1.0]: https://github.com/schiste/Aexeo/releases/tag/seogeo-plugin-v0.1.0
package/INSTALL.md ADDED
@@ -0,0 +1,289 @@
1
+ # Installing `@aeptus/aexeo-emdash`
2
+
3
+ This package adds Aexeo's seogeo SEO/GEO content evaluator to an emdash
4
+ site. Findings show up as a Block Kit admin page; a sidebar widget
5
+ shows the site's current intelligence score; saves auto-evaluate the
6
+ changed document.
7
+
8
+ ## Two install paths
9
+
10
+ The package exports two factories:
11
+
12
+ - **`seogeoPlugin()`** — **configured mode** (recommended). Plugin
13
+ runs in-process inside the host emdash Worker. No separate
14
+ deployment, no auth token, no admin Setup page. Use this for
15
+ first-party emdash sites.
16
+ - **`seogeoPluginSandboxed({ evaluatorHost })`** — **sandboxed
17
+ mode**. Plugin runs inside emdash's Worker Loader isolate; WASM
18
+ evaluation runs in a separate sidecar Worker the operator
19
+ deploys. Use this only when a third party hosts emdash and
20
+ doesn't trust the plugin code with full host access.
21
+
22
+ Configured mode is what most installs should pick. The instructions
23
+ below cover that path. Sandboxed mode is documented at the bottom for
24
+ when it's actually needed.
25
+
26
+ ## Configured mode (recommended)
27
+
28
+ ### Prerequisites
29
+
30
+ - An emdash site running on the **Cloudflare** adapter
31
+ (`@astrojs/cloudflare` + `@emdash-cms/cloudflare`'s `sandbox()`
32
+ runner is fine — sandboxed plugins use Worker Loader, configured
33
+ plugins don't, but both adapters import cleanly).
34
+ - emdash 0.7.0 or later as a peer dependency.
35
+
36
+ ### Install
37
+
38
+ ```bash
39
+ npm install @aeptus/aexeo-emdash vite-plugin-wasm
40
+ ```
41
+
42
+ `vite-plugin-wasm` is required because Vite's defaults treat `.wasm`
43
+ imports as static-asset URLs, but Cloudflare Workers can only run
44
+ WASM as precompiled `WebAssembly.Module` instances. The plugin
45
+ bridges the two; without it, the seogeo plugin's first call throws
46
+ "WASM module did not resolve to a WebAssembly.Module."
47
+
48
+ > **If installing from a private git remote or as a vendored
49
+ > directory** — see the "Alternative install sources" section at the
50
+ > end. The result is the same, just the dependency line differs.
51
+
52
+ ### Wire into `astro.config.mjs`
53
+
54
+ ```js
55
+ import cloudflare from "@astrojs/cloudflare";
56
+ import { d1, r2 } from "@emdash-cms/cloudflare";
57
+ import { seogeoPlugin } from "@aeptus/aexeo-emdash";
58
+ import { defineConfig } from "astro/config";
59
+ import emdash from "emdash/astro";
60
+ import wasm from "vite-plugin-wasm";
61
+
62
+ export default defineConfig({
63
+ output: "server",
64
+ adapter: cloudflare(),
65
+ vite: {
66
+ // Required: makes `import x from "./foo.wasm"` resolve to a
67
+ // precompiled WebAssembly.Module. Cloudflare Workers /
68
+ // workerd disallow runtime WebAssembly.instantiate from raw
69
+ // bytes, so the bundler has to do the compilation.
70
+ plugins: [wasm()],
71
+ // The seogeo plugin's WASM import confuses Vite's dep
72
+ // optimizer when it tries to pre-bundle the package.
73
+ optimizeDeps: { exclude: ["@aeptus/aexeo-emdash"] },
74
+ },
75
+ integrations: [
76
+ emdash({
77
+ database: d1({ binding: "DB" }),
78
+ storage: r2({ binding: "MEDIA" }),
79
+ plugins: [seogeoPlugin()],
80
+ }),
81
+ ],
82
+ });
83
+ ```
84
+
85
+ That's it for code. No environment variables, no secrets to manage,
86
+ no sidecar to deploy.
87
+
88
+ ### Verify
89
+
90
+ 1. Start the dev server: `npm run dev`.
91
+ 2. The emdash startup log doesn't print a special line for configured
92
+ plugins (only sandboxed ones get a "Loaded sandboxed plugin..."
93
+ line). Instead, navigate to:
94
+
95
+ ```
96
+ http://localhost:4321/_emdash/admin/plugins
97
+ ```
98
+
99
+ The seogeo plugin will appear in the list. Click it.
100
+
101
+ 3. The findings page renders with a Refresh button. Click **Refresh**
102
+ — toast says "Refreshed N routes (M findings across K documents)"
103
+ and the table populates.
104
+
105
+ 4. Save a document: the `content:afterSave` hook auto-evaluates that
106
+ one document; refresh the findings page and that route's findings
107
+ are updated. (The dashboard widget also picks up the new score.)
108
+
109
+ ### Production deploy
110
+
111
+ Run your usual Astro/Cloudflare deploy: `npm run build && wrangler
112
+ deploy` (or whatever your CI does). The same `vite-plugin-wasm` and
113
+ `optimizeDeps.exclude` lines work for production builds — the plugin
114
+ ships its WASM as a separate `.wasm` file under
115
+ `node_modules/@aeptus/aexeo-emdash/wasm/` which Wrangler
116
+ compiles into the deployed Worker artifact.
117
+
118
+ ## Updating
119
+
120
+ ```bash
121
+ npm update @aeptus/aexeo-emdash
122
+ npm run build && <your deploy command>
123
+ ```
124
+
125
+ The plugin's WASM is bundled with each version of the package, so
126
+ `npm update` brings new rules along automatically. There's no
127
+ separate sidecar to keep in sync (configured mode), no token to
128
+ rotate, no Setup page to revisit.
129
+
130
+ ## Removing
131
+
132
+ Delete the dependency, the import, and the `seogeoPlugin()` line in
133
+ `integrations:[emdash({plugins:[...]})]`. The KV keys the plugin
134
+ wrote remain in your KV namespace — purge with
135
+ `wrangler kv:key delete --prefix=findings:` and
136
+ `wrangler kv:key delete --prefix=document:` if you want a clean
137
+ removal.
138
+
139
+ ## Sandboxed mode (future external deploys)
140
+
141
+ If you're shipping this plugin to a third-party emdash site whose
142
+ operator should NOT have the plugin code running in their host
143
+ Worker process, switch to sandboxed mode. This requires deploying a
144
+ separate sidecar Worker that runs the WASM evaluator.
145
+
146
+ Trade-offs vs. configured mode:
147
+
148
+ - **Pro**: V8 isolate boundary; capability + allowedHosts enforcement
149
+ prevents the plugin from ever reading host state directly.
150
+ - **Con**: separate sidecar Worker to deploy and maintain; an
151
+ EVAL_TOKEN to rotate; a Setup admin page to configure. The
152
+ `content:afterSave` hook is also non-functional in emdash 0.7.x's
153
+ sandbox (post-response bridge invalidation — known upstream issue);
154
+ evaluation is manual-Refresh-only.
155
+
156
+ ```js
157
+ // astro.config.mjs (sandboxed mode)
158
+ import { sandbox } from "@emdash-cms/cloudflare";
159
+ import { seogeoPluginSandboxed } from "@aeptus/aexeo-emdash";
160
+
161
+ emdash({
162
+ database: d1({ binding: "DB" }),
163
+ storage: r2({ binding: "MEDIA" }),
164
+ sandboxed: [
165
+ seogeoPluginSandboxed({
166
+ // Public host of the deployed sidecar Worker.
167
+ evaluatorHost: "seogeo-crawl-worker.<subdomain>.workers.dev",
168
+ }),
169
+ ],
170
+ sandboxRunner: sandbox(),
171
+ });
172
+ ```
173
+
174
+ The sidecar Worker template lives at `packages/seogeo-crawl-worker/`
175
+ in this repo. Per-site deploy:
176
+
177
+ ```bash
178
+ cd seogeo-crawl-worker
179
+ # Edit wrangler.toml: name, R2 bucket name, SITE_URL
180
+ npx wrangler login
181
+ npx wrangler r2 bucket create <bucket-name>
182
+ npx wrangler deploy
183
+ echo "$(openssl rand -hex 32)" | npx wrangler secret put EVAL_TOKEN
184
+ ```
185
+
186
+ Then in the admin UI, visit
187
+ `/admin/plugins/aexeo-seogeo/setup` and paste the deployed URL +
188
+ the same token.
189
+
190
+ ## Alternative install sources
191
+
192
+ Public npm is the recommended path. If your install context can't
193
+ reach the npm registry, two fallbacks:
194
+
195
+ **Git URL** (works without npm registry access, requires GitHub
196
+ SSH access to the source repo):
197
+
198
+ ```jsonc
199
+ "dependencies": {
200
+ "@aeptus/aexeo-emdash":
201
+ "git+ssh://git@github.com/schiste/Aexeo.git#<commit-sha>:packages/aexeo-emdash"
202
+ }
203
+ ```
204
+
205
+ Pin to a commit SHA, not a branch. Update by changing the SHA.
206
+
207
+ **Vendored copy** (no git access required):
208
+
209
+ Copy `dist/`, `wasm/`, and `package.json` from this repo into your
210
+ project at e.g. `vendor/aexeo-emdash/`, then:
211
+
212
+ ```jsonc
213
+ "dependencies": {
214
+ "@aeptus/aexeo-emdash": "file:./vendor/aexeo-emdash"
215
+ }
216
+ ```
217
+
218
+ Update by re-copying the directory. Vendored installs lose npm's
219
+ update tracking, so this is the heaviest of the three options
220
+ operationally.
221
+
222
+ ## Troubleshooting
223
+
224
+ **`Wasm code generation disallowed by embedder` at first Refresh.**
225
+ The bundle is trying to instantiate WASM from raw bytes at runtime,
226
+ which Cloudflare Workers reject. Confirm `vite-plugin-wasm` is
227
+ installed and in the `vite.plugins` array of `astro.config.mjs`. If
228
+ present, double-check the package version is recent (≥ the version
229
+ that switched to direct .wasm imports — see git log for `Switch
230
+ configured-mode WASM from inlined bytes to direct .wasm import`).
231
+
232
+ **`seogeo: WASM module did not resolve to a WebAssembly.Module`**
233
+ The bundler resolved the .wasm import but produced something other
234
+ than a Module (URL string, Uint8Array). Same fix as above: ensure
235
+ `vite-plugin-wasm` is loaded.
236
+
237
+ **`Cannot read properties of undefined (reading 'kv')` on Refresh.**
238
+ Older plugin version where the configured-mode handler used the
239
+ sandboxed two-arg ctx shape. Update; it's fixed in the version with
240
+ `Switch configured-mode WASM from inlined bytes to direct .wasm
241
+ import` in the commit log.
242
+
243
+ **`/admin/plugins` doesn't list seogeo, but `/findings` works.** The
244
+ emdash version's plugins meta page may filter out plugins without a
245
+ `/` adminPage entry. The package declares one explicitly; if you see
246
+ this on a recent version, it's an emdash regression — file upstream.
247
+
248
+ **`Refresh issues: posts: TypeError: ...`** during a sweep. The
249
+ adapter hit a content item shape it didn't expect (a custom
250
+ collection field, an unusual schema). Open an issue with the
251
+ collection's slug + the failing field; we'll add defensive handling.
252
+
253
+ **Plugin doesn't appear in admin sidebar at all.** Confirm the
254
+ emdash adapter is `@astrojs/cloudflare`'s `cloudflare()`. The plugin
255
+ relies on Cloudflare-specific APIs and won't load (silently) on the
256
+ Node adapter.
257
+
258
+ ## What the plugin actually does
259
+
260
+ - **Findings page** (`/_emdash/admin/plugins/aexeo-seogeo/findings`):
261
+ Block Kit table of every rule violation across the site. Filter by
262
+ severity. Click a route in the picker to drill into per-document
263
+ findings.
264
+ - **Document panel** (`/document`): the same findings, scoped to one
265
+ document.
266
+ - **Score widget** (dashboard): top-line intelligence score across
267
+ citation, truth, answer-pack, external-trust dimensions. Banner
268
+ appears below 60.
269
+ - **Refresh button** on the findings page: re-evaluate every
270
+ document in the configured collections (`posts`, `pages` by
271
+ default). Writes findings to KV under `findings:<route>` and the
272
+ evaluated document under `document:<route>`.
273
+ - **Auto-evaluate on save**: emdash's `content:afterSave` hook fires
274
+ for each save, re-evaluates that one document, and updates its
275
+ KV findings entry. The dashboard widget reflects the new score on
276
+ the next page load.
277
+
278
+ ## Architecture in one paragraph
279
+
280
+ The plugin runs in-process inside the host emdash Worker. Saves
281
+ trigger emdash's `content:afterSave` hook, which adapts the saved
282
+ content to the bridge's wire format and runs the WASM evaluator
283
+ inline. Findings are stored in the host's plugin KV (the
284
+ `PluginContext.kv` accessor). The Refresh button does the same thing
285
+ but for every document in every configured collection. The WASM
286
+ itself is the same `aexeo-emdash-bridge` Rust crate that powers the
287
+ seogeo CLI; it's compiled to WebAssembly via wasm-pack and imported
288
+ by the host's bundler (Vite + `vite-plugin-wasm`). No separate
289
+ service, no sidecar, no auth token.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aexeo
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,78 @@
1
+ # @aeptus/aexeo-emdash
2
+
3
+ Aexeo's seogeo SEO/GEO content evaluator as an emdash plugin.
4
+
5
+ Adds a Findings admin page, per-document panel, and dashboard
6
+ intelligence-score widget to an emdash site. Saves auto-evaluate the
7
+ changed document; the Refresh button re-evaluates the whole site.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @aeptus/aexeo-emdash vite-plugin-wasm
13
+ ```
14
+
15
+ ```js
16
+ // astro.config.mjs
17
+ import { seogeoPlugin } from "@aeptus/aexeo-emdash";
18
+ import wasm from "vite-plugin-wasm";
19
+
20
+ export default defineConfig({
21
+ vite: {
22
+ plugins: [wasm()],
23
+ optimizeDeps: { exclude: ["@aeptus/aexeo-emdash"] },
24
+ },
25
+ integrations: [
26
+ emdash({
27
+ database: d1({ binding: "DB" }),
28
+ storage: r2({ binding: "MEDIA" }),
29
+ plugins: [seogeoPlugin()],
30
+ }),
31
+ ],
32
+ });
33
+ ```
34
+
35
+ That's it. No environment variables, no auth tokens, no admin Setup
36
+ page, no separate sidecar to deploy.
37
+
38
+ For the full install runbook, sandboxed-mode alternative, and
39
+ troubleshooting list, see [INSTALL.md](./INSTALL.md).
40
+
41
+ ## What it does
42
+
43
+ - **Findings page** at `/_emdash/admin/plugins/aexeo-seogeo/findings`:
44
+ rule violations across the site, filterable by severity.
45
+ - **Document panel** at `/document`: findings scoped to one document.
46
+ - **Dashboard widget**: top-line intelligence score (citation, truth,
47
+ answer-pack, external-trust dimensions). Banner appears below 60.
48
+ - **Refresh button**: re-evaluate every document in the configured
49
+ collections (`posts`, `pages` by default).
50
+ - **Auto-evaluate on save**: emdash's `content:afterSave` hook fires,
51
+ re-evaluates the saved document, updates the findings table.
52
+
53
+ ## Compatibility
54
+
55
+ - emdash `>= 0.7.0` (peer dep)
56
+ - Cloudflare Workers via `@astrojs/cloudflare`. The plugin uses
57
+ Cloudflare-specific APIs and silently no-ops on the Node adapter.
58
+ - WASM eval ships with the package; the consumer's Vite/Wrangler
59
+ bundler handles compilation at build time. `vite-plugin-wasm` is a
60
+ hard requirement on the consumer side.
61
+
62
+ ## Two factories
63
+
64
+ The package exports two factories for different trust contexts:
65
+
66
+ - `seogeoPlugin()` — **configured mode** (recommended). In-process
67
+ plugin. Use for first-party emdash sites.
68
+ - `seogeoPluginSandboxed({ evaluatorHost })` — **sandboxed mode**.
69
+ Requires a separately-deployed sidecar Worker. Use only when
70
+ shipping to third-party emdash sites that shouldn't trust the
71
+ plugin code with full host access.
72
+
73
+ See [INSTALL.md](./INSTALL.md#sandboxed-mode-future-external-deploys)
74
+ for the sandboxed setup runbook.
75
+
76
+ ## License
77
+
78
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,16 @@
1
+ import type { EmdashDocument } from "./types.js";
2
+ export interface EmdashContentItem {
3
+ id: string;
4
+ type: string;
5
+ slug: string | null;
6
+ status: string;
7
+ locale: string | null;
8
+ data: Record<string, unknown>;
9
+ seo?: {
10
+ title?: string | null;
11
+ description?: string | null;
12
+ canonical?: string | null;
13
+ };
14
+ }
15
+ export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
16
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAyBhB"}
@@ -0,0 +1,5 @@
1
+ export interface ConfiguredPluginOptions {
2
+ }
3
+ export declare function createPlugin(_options?: ConfiguredPluginOptions): unknown;
4
+ export default createPlugin;
5
+ //# sourceMappingURL=configured.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA4EA,MAAM,WAAW,uBAAuB;CAIvC;AAED,wBAAgB,YAAY,CAAC,QAAQ,GAAE,uBAA4B,GAAG,OAAO,CAiC5E;AAID,eAAe,YAAY,CAAC"}