@rubytech/create-maxy-code 0.1.125 → 0.1.127

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 (78) hide show
  1. package/dist/index.js +23 -1
  2. package/package.json +2 -2
  3. package/payload/platform/config/brand.json +20 -1
  4. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts +33 -0
  5. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts.map +1 -0
  6. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js +119 -0
  7. package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js.map +1 -0
  8. package/payload/platform/lib/aeo-llms-txt-writer/src/index.ts +172 -0
  9. package/payload/platform/lib/aeo-llms-txt-writer/tsconfig.json +8 -0
  10. package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
  11. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
  12. package/payload/platform/lib/graph-style/dist/index.js +296 -0
  13. package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
  14. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
  15. package/payload/platform/lib/graph-style/src/index.ts +307 -0
  16. package/payload/platform/lib/graph-style/tsconfig.json +9 -0
  17. package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
  18. package/payload/platform/package-lock.json +318 -0
  19. package/payload/platform/package.json +3 -2
  20. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  21. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  22. package/payload/platform/plugins/admin/PLUGIN.md +4 -1
  23. package/payload/platform/plugins/admin/mcp/dist/index.js +73 -0
  24. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  25. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts +34 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts.map +1 -0
  27. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js +176 -0
  28. package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js.map +1 -0
  29. package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +19 -57
  30. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +5 -38
  31. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -1
  32. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +7 -114
  33. package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -1
  34. package/payload/platform/plugins/docs/references/visitor-graph.md +4 -3
  35. package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
  36. package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
  37. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
  38. package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
  39. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
  40. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
  41. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
  42. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
  43. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
  44. package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
  45. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
  46. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
  47. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
  48. package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
  49. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
  50. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
  51. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
  52. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
  53. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
  54. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
  55. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
  56. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
  57. package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
  58. package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
  59. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
  60. package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
  61. package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
  62. package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
  63. package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
  64. package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
  65. package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
  66. package/payload/server/public/assets/page-BOtNny_4.js +51 -0
  67. package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
  68. package/payload/server/public/consent.css +97 -0
  69. package/payload/server/public/consent.js +259 -0
  70. package/payload/server/public/data.html +3 -3
  71. package/payload/server/public/graph.html +3 -3
  72. package/payload/server/public/index.html +4 -4
  73. package/payload/server/public/privacy.html +68 -5
  74. package/payload/server/public/v.js +53 -0
  75. package/payload/server/server.js +465 -176
  76. package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
  77. package/payload/server/public/assets/page-B4oirCvn.js +0 -1
  78. package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
@@ -0,0 +1,176 @@
1
+ import { mkdir, readdir, rename, stat } from "node:fs/promises";
2
+ import { resolve as resolvePath, join, dirname } from "node:path";
3
+ import { writeLlmsTxt } from "../../../../../lib/aeo-llms-txt-writer/dist/index.js";
4
+ /**
5
+ * Publish an already-extracted static-site tree into the per-account
6
+ * static-publish surface (<accountDir>/sites/<slug>/) and emit one
7
+ * canonical path slug. Pure function — the MCP registration in index.ts
8
+ * adapts the discriminated-union result onto the MCP transport response.
9
+ *
10
+ * Invariants (each maps to one Refusal kind):
11
+ * - slug segments match SAFE_SEG_RE, no leading dot, no `..`
12
+ * - destination must be absent or empty
13
+ * - source tree must contain no symlinks at any depth
14
+ * - source must have at least one .html at the top level
15
+ * - if no index.html at top level, exactly one top-level .html
16
+ *
17
+ * Post-move:
18
+ * - canonical path is `/sites/<slug>/` if index.html at top level,
19
+ * otherwise `/sites/<slug>/<the-one-html>.html`
20
+ * - llms.txt + llms-full.txt are refreshed via writeLlmsTxt
21
+ * (best-effort: failure does not unwind the mv)
22
+ *
23
+ * All operator-visible log lines preserved verbatim from the prior
24
+ * skill-prose contract so existing grep targets keep working.
25
+ */
26
+ const SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
27
+ const TAG = "[publish-site]";
28
+ function refusal(kind, detail, logLine) {
29
+ process.stderr.write(`${TAG} refused reason=${kind} ${logLine}\n`);
30
+ return { ok: false, refusal: kind, detail };
31
+ }
32
+ function validateSlug(slug) {
33
+ const segments = slug.split("/").filter((s) => s.length > 0);
34
+ if (segments.length === 0) {
35
+ return {
36
+ ok: false,
37
+ refusal: refusal("unsafe-slug", { slug }, `slug=${slug}`),
38
+ };
39
+ }
40
+ for (const seg of segments) {
41
+ if (seg === "..") {
42
+ return {
43
+ ok: false,
44
+ refusal: refusal("unsafe-slug", { slug, segment: seg }, `slug=${slug}`),
45
+ };
46
+ }
47
+ if (seg.startsWith(".")) {
48
+ return {
49
+ ok: false,
50
+ refusal: refusal("unsafe-slug", { slug, segment: seg }, `slug=${slug}`),
51
+ };
52
+ }
53
+ if (!SAFE_SEG_RE.test(seg)) {
54
+ return {
55
+ ok: false,
56
+ refusal: refusal("unsafe-slug", { slug, segment: seg }, `slug=${slug}`),
57
+ };
58
+ }
59
+ }
60
+ return { ok: true, segments };
61
+ }
62
+ async function findSymlinkAnywhere(dir) {
63
+ const entries = await readdir(dir, { withFileTypes: true });
64
+ for (const ent of entries) {
65
+ if (ent.isSymbolicLink())
66
+ return join(dir, ent.name);
67
+ if (ent.isDirectory()) {
68
+ const found = await findSymlinkAnywhere(join(dir, ent.name));
69
+ if (found)
70
+ return found;
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ async function countFiles(dir) {
76
+ let n = 0;
77
+ const entries = await readdir(dir, { withFileTypes: true });
78
+ for (const ent of entries) {
79
+ if (ent.isFile())
80
+ n += 1;
81
+ else if (ent.isDirectory())
82
+ n += await countFiles(join(dir, ent.name));
83
+ }
84
+ return n;
85
+ }
86
+ export async function publishSite(input, deps) {
87
+ const { accountId, accountDir, source, slug } = input;
88
+ const slugCheck = validateSlug(slug);
89
+ if (!slugCheck.ok)
90
+ return slugCheck.refusal;
91
+ const destination = resolvePath(accountDir, "sites", ...slugCheck.segments);
92
+ // Pre-flight: dest must be absent or empty.
93
+ try {
94
+ const destStat = await stat(destination);
95
+ if (destStat.isDirectory()) {
96
+ const entries = await readdir(destination);
97
+ if (entries.length > 0) {
98
+ return refusal("destination-occupied", { destination, entryCount: entries.length }, `dest=${destination}`);
99
+ }
100
+ }
101
+ else {
102
+ return refusal("destination-occupied", { destination, kind: "non-directory" }, `dest=${destination}`);
103
+ }
104
+ }
105
+ catch (err) {
106
+ const code = err?.code;
107
+ if (code !== "ENOENT")
108
+ throw err;
109
+ // ENOENT is the happy path — destination doesn't exist yet.
110
+ }
111
+ // Pre-flight: source symlink scan (any depth).
112
+ const symlink = await findSymlinkAnywhere(source);
113
+ if (symlink) {
114
+ return refusal("symlink-in-source", { source, entry: symlink }, `entry=${symlink}`);
115
+ }
116
+ // Pre-flight: top-level html inventory.
117
+ const topLevel = await readdir(source, { withFileTypes: true });
118
+ const topHtml = topLevel
119
+ .filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".html"))
120
+ .map((e) => e.name);
121
+ if (topHtml.length === 0) {
122
+ return refusal("zero-html", { source }, `source=${source}`);
123
+ }
124
+ const hasIndex = topHtml.some((n) => n.toLowerCase() === "index.html");
125
+ if (!hasIndex && topHtml.length > 1) {
126
+ return refusal("ambiguous-html", { source, candidates: topHtml }, `candidates=${topHtml.join(",")}`);
127
+ }
128
+ const movedFiles = await countFiles(source);
129
+ // Move. Single rename — same filesystem assumed (accountDir-local).
130
+ // Ensure the parent (`<accountDir>/sites/...` up to the leaf) exists so
131
+ // the first publish on a fresh accountDir doesn't ENOENT. mkdir is
132
+ // idempotent on the parent; the leaf itself was confirmed absent above.
133
+ await mkdir(dirname(destination), { recursive: true });
134
+ // EXDEV or EACCES propagates as an exception → MCP isError surfacing
135
+ // via the registration layer in index.ts.
136
+ await rename(source, destination);
137
+ process.stderr.write(`${TAG} move from=${source} to=${destination}/ files=${movedFiles}\n`);
138
+ const kind = hasIndex ? "index" : "file";
139
+ const pathSlug = hasIndex
140
+ ? `/sites/${slugCheck.segments.join("/")}/`
141
+ : `/sites/${slugCheck.segments.join("/")}/${topHtml[0]}`;
142
+ process.stderr.write(`${TAG} url emitted=${pathSlug} kind=${kind}\n`);
143
+ // Post-move hook: refresh llms.txt at the site root. Best-effort.
144
+ // Never unwinds the mv — a stale llms.txt is strictly less bad than
145
+ // a failed publish.
146
+ let aeo;
147
+ try {
148
+ const llms = await writeLlmsTxt({
149
+ accountId,
150
+ siteName: slugCheck.segments[slugCheck.segments.length - 1],
151
+ siteDir: destination,
152
+ siteDescription: input.siteDescription,
153
+ }, { getSession: deps.getSession });
154
+ const indexPath = llms.writtenPaths?.index ?? "";
155
+ aeo = {
156
+ ok: true,
157
+ indexPath,
158
+ indexBytes: llms.sizeBytes.index,
159
+ };
160
+ process.stderr.write(`${TAG} aeo-llms-txt-ok wrote=${indexPath} indexBytes=${llms.sizeBytes.index}\n`);
161
+ }
162
+ catch (err) {
163
+ const reason = err instanceof Error ? err.message : String(err);
164
+ aeo = { ok: false, reason };
165
+ process.stderr.write(`${TAG} aeo-llms-txt-failed reason=${reason}\n`);
166
+ }
167
+ return {
168
+ ok: true,
169
+ pathSlug,
170
+ kind,
171
+ movedFiles,
172
+ destination,
173
+ aeo,
174
+ };
175
+ }
176
+ //# sourceMappingURL=publish-site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-site.js","sourceRoot":"","sources":["../../src/tools/publish-site.ts"],"names":[],"mappings":"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"}
@@ -1,75 +1,37 @@
1
1
  # Publish Site
2
2
 
3
- Move an already-extracted static-site tree into the per-account static-publish surface (`<accountDir>/sites/<slug>/`) and emit exactly one canonical path slug. Pair the slug with the deterministic public hostname returned by `mcp__admin__public-hostname` to surface the full URL in a single turn.
3
+ Call `mcp__admin__publish-site` to move an already-extracted static-site tree into the per-account static-publish surface (`<accountDir>/sites/<slug>/`) and emit one canonical path slug. Pair the slug with `mcp__admin__public-hostname` to surface the full URL in the same turn.
4
4
 
5
- **Invoked from `specialists:content-producer`** when the brief carries a host-website / publish-site / put-online intent. Admin's IDENTITY.md routes those intents to that specialist on turn 1; running this skill inline on the main admin runner exhausts the 10-turn budget on per-turn ToolSearch + plugin-read discovery before publish-site executes.
5
+ **Invoked from `specialists:content-producer`** when the brief carries a host-website / publish-site / put-online intent. The slug validation, symlink scan, refusal taxonomy, `mv`, canonical-path selection, and llms.txt refresh all run inside the typed tool this skill is the intent router, not the implementer.
6
6
 
7
- ## When to Use
7
+ ## When to use
8
8
 
9
9
  Activate when **both** are true:
10
10
 
11
11
  - The current turn carries a "host this website" / "publish this site" / "put this online" / equivalent intent.
12
- - A directory tree of HTML + assets is already on disk under `<accountDir>/extracted/<attachmentId>/` (the typical output of [unzip-attachment](../unzip-attachment/SKILL.md)), or the conversation already names a source directory under `<accountDir>/`.
12
+ - A directory tree of HTML + assets is already on disk under `<accountDir>/extracted/<attachmentId>/` (typical output of [unzip-attachment](../unzip-attachment/SKILL.md)), or the conversation already names a source directory under `<accountDir>/`.
13
13
 
14
- Do **not** activate for `.pdf`, `.docx`, slide decks, single-image attachments, or zips whose extracted output is not a directory tree of HTML + assets — those have their own skills (`a4-print-documents`, `deck-pages`, etc.). The intent + tree shape is the gate.
14
+ Do **not** activate for `.pdf`, `.docx`, slide decks, single-image attachments, or zips whose extracted output is not a directory tree of HTML + assets — those have their own skills (`a4-print-documents`, `deck-pages`, etc.).
15
15
 
16
- ## Inputs
16
+ ## Call
17
17
 
18
- - `source` — absolute path to the extracted directory tree (already produced by `unzip-attachment`).
19
- - `slug` — operator-supplied or operator-confirmed path under `<accountDir>/sites/`. One or more `/`-separated segments; each segment matches the platform's `SAFE_SEG_RE` (`/^[a-z0-9_][a-z0-9_.-]{0,99}$/i`); no segment starts with `.`; no segment equals `..`. Confirm with the operator before proceeding when the slug is not already explicit.
18
+ ```
19
+ mcp__admin__publish-site
20
+ source: <absolute path to the extracted source directory>
21
+ slug: <operator-supplied or operator-confirmed path under sites/>
22
+ siteName?: <optional llms.txt header; defaults to the last slug segment>
23
+ siteDescription?: <optional one-line blockquote under the llms.txt header>
24
+ ```
20
25
 
21
- ## Invariants
26
+ Confirm the slug with the operator before calling if it isn't already explicit in the conversation.
22
27
 
23
- These are non-negotiable. Each is mechanically check-able and each maps to a fixed refusal log line.
28
+ ## Surface the result
24
29
 
25
- 1. **Move, never copy.** The source tree is `mv`'d into `<accountDir>/sites/<slug>/`. No re-extraction, no `cp`. After success the source path no longer exists.
26
- 2. **No filename invention.** Never `cp <any>.html index.html` to satisfy a URL form. If the moved tree's top level has no `index.html`, the canonical path slug points at the actual landing HTML by name.
27
- 3. **One emitted path slug.** The skill emits exactly one path string starting with `/sites/`:
28
- - `/sites/<slug>/` — when `<accountDir>/sites/<slug>/index.html` exists after the move.
29
- - `/sites/<slug>/<file>.html` — when no `index.html` and exactly one HTML file sits at the top level.
30
- 4. **Path only — no scheme, no host.** The emitted string is a path. Never emit a URL with a scheme or a host. The operator pairs the slug with their own public-host root.
31
- 5. **No fallback servers, no Playwright probes, no service restarts.** If the move or URL form is wrong, refuse. Reaching for `python -m http.server`, `npx http-server`, browser-automation probes, or platform restarts in place of fixing the URL contract is the prior-session failure pattern this skill exists to close.
32
- 6. **Source must be a real directory tree, no symlinks.** If `find <source> -type l` returns any entry, refuse.
33
-
34
- ## Refusals
35
-
36
- Each refusal is a `[publish-site] refused` log line plus a fixed operator message. No retry, no silent substitution.
37
-
38
- | Reason | Trigger | Log line |
39
- |---|---|---|
40
- | `unsafe-slug` | Any segment fails `SAFE_SEG_RE`, has a leading `.`, or equals `..` | `[publish-site] refused reason=unsafe-slug slug=<value>` |
41
- | `destination-occupied` | `<accountDir>/sites/<slug>/` already exists with non-empty contents | `[publish-site] refused reason=destination-occupied dest=<path>` |
42
- | `symlink-in-source` | `find <source> -type l` returns at least one entry | `[publish-site] refused reason=symlink-in-source entry=<path>` |
43
- | `zero-html` | The top level of `<source>` has no `*.html` files | `[publish-site] refused reason=zero-html source=<path>` |
44
- | `ambiguous-html` | Multiple top-level `*.html` files and no `index.html` | `[publish-site] refused reason=ambiguous-html candidates=<f1>,<f2>,...` |
45
-
46
- The operator message for `ambiguous-html` names the candidate files and asks the operator to pick which file is the landing page (or to add an `index.html` to the source). No move happens until the choice is explicit.
47
-
48
- ## Flow
49
-
50
- 1. **Resolve and validate slug.** Split on `/`; reject any segment that violates the rules above.
51
- 2. **Pre-flight checks.** Inspect the source tree's top level: count `*.html` files; check for `index.html`; run the symlink scan; check the destination is absent or empty. Apply refusals before any disk mutation.
52
- 3. **Move.** Single `mv <source> <accountDir>/sites/<slug>/`. Emit one log line:
53
- `[publish-site] move from=<source> to=<accountDir>/sites/<slug>/ files=<n>`
54
- 4. **Choose the canonical path.** `index.html` present at top level → path is `/sites/<slug>/`. Otherwise the single top-level HTML file → path is `/sites/<slug>/<file>.html`.
55
- 5. **Emit.** One log line:
56
- `[publish-site] url emitted=<path-slug> kind=<index|file>`
57
- 6. **Refresh `llms.txt` at the site root.** Call `mcp__aeo__aeo-write-llms-txt` with `{ siteName: "<slug>", siteDir: "<accountDir>/sites/<slug>" }`. The AEO tool reads the account's `KnowledgeDocument` set and writes `llms.txt` + `llms-full.txt` into `<siteDir>/`, so answer-engine crawlers always see the current page set at the moment of publish. `accountId` is implicit — the AEO plugin reads it from its own env. On success, emit `[publish-site] aeo-llms-txt-ok wrote=<indexPath> indexBytes=<n>`. **Best-effort invariant:** if the call fails (tool returns `isError`, throws, or the `aeo` plugin is not loaded for this account), emit `[publish-site] aeo-llms-txt-failed reason=<msg>` and continue. **Never unwind the `mv` on AEO failure** — the publish already succeeded, the URL is live, and a stale or missing `llms.txt` is strictly less bad than a failed publish.
58
- 7. **Resolve the public hostname and emit the full URL.** Call `mcp__admin__public-hostname` to fetch this account's hostname; the tool reads `cloudflared/config.yml` + `alias-domains.json` — the same files the platform server trusts to route. Concatenate `https://<hostname><path-slug>` and surface that one URL to the operator. If the tool returns `reason: no-tunnel`, relay the tool's remediation message verbatim — do not improvise the URL. The route at `/sites/*` (see [server/routes/sites.ts](../../../../ui/server/routes/sites.ts)) handles the trailing-slash redirect on the dir form, so the slug is correct as-emitted whether the operator's HTML uses an `index.html` entry-point or a publisher-named landing file.
59
-
60
- ## Log lines (grep targets)
61
-
62
- | When | Line |
63
- |---|---|
64
- | After move | `[publish-site] move from=<src> to=<dest> files=<n>` |
65
- | Path emission | `[publish-site] url emitted=<path-slug> kind=<index\|file>` |
66
- | AEO llms.txt success | `[publish-site] aeo-llms-txt-ok wrote=<indexPath> indexBytes=<n>` |
67
- | AEO llms.txt failure (best-effort, publish continues) | `[publish-site] aeo-llms-txt-failed reason=<msg>` |
68
- | Refusal (any reason) | `[publish-site] refused reason=<unsafe-slug\|destination-occupied\|symlink-in-source\|zero-html\|ambiguous-html> <key=value pairs>` |
30
+ - **On success** the tool returns `pathSlug` (e.g. `/sites/demo/` or `/sites/demo/brochure.html`). Call `mcp__admin__public-hostname` next and surface `https://<hostname><pathSlug>` to the operator. The `aeo:` line on the response tells you whether `llms.txt` refresh succeeded; if it failed, the publish still succeeded — relay the URL anyway.
31
+ - **On refusal** the tool returns a `refused: <kind>` body with a fixed `Operator action:` guidance line. Relay the guidance verbatim. The five refusal kinds are `unsafe-slug`, `destination-occupied`, `symlink-in-source`, `zero-html`, and `ambiguous-html`. No retry, no improvisation, no fallback server.
69
32
 
70
33
  ## Out of scope
71
34
 
72
- - Cleanup of pre-existing synthetic files left by earlier sessions (e.g. a stray `index.html` produced by an earlier `cp brochure.html index.html` workaround). Refuse with `destination-occupied` and let the operator clean up explicitly.
73
35
  - Extraction. `unzip-attachment` already owns the extract step; the source path is its output.
74
- - DNS, tunnels, certificates, or any public-host configuration. The route at `/sites/*` is the wire contract; this skill is a placement + URL-shape skill only.
75
- - Per-directory default-filename config (e.g. a `.publish.json` that names a default landing file). The publisher names the landing file; no inference layer.
36
+ - DNS, tunnels, certificates, or any public-host configuration. The route at `/sites/*` is the wire contract.
37
+ - Cleanup of pre-existing synthetic files left by earlier sessions. A non-empty destination produces `destination-occupied` and the operator cleans up explicitly.
@@ -1,42 +1,9 @@
1
+ import { type WriteLlmsTxtInput, type WriteLlmsTxtResult } from "../../../../../lib/aeo-llms-txt-writer/dist/index.js";
2
+ export type { WriteLlmsTxtInput, WriteLlmsTxtResult };
1
3
  /**
2
- * Generates the `llms.txt` and `llms-full.txt` pair for an account's
3
- * published content. Format follows the current llmstxt.org draft:
4
- *
5
- * # <Site name>
6
- * > <Short description>
7
- *
8
- * ## <Section name>
9
- * - [<Title>](<URL>): <Summary>
10
- *
11
- * `llms-full.txt` concatenates the full text of every listed page in
12
- * the same order, separated by a blank line and the page's title as an
13
- * H1.
14
- *
15
- * Source: `KnowledgeDocument` nodes for the account that have a `url`
16
- * property. `summary` and `body` properties feed the index and the
17
- * concatenated dump respectively. Pages without a `url` are skipped
18
- * (they aren't reachable from a public site), and the count of skipped
19
- * pages is returned so the operator can audit completeness.
4
+ * Thin aeo-side wrapper around the plugin-agnostic writeLlmsTxt lib.
5
+ * Injects this plugin's neo4j session factory so the shared lib stays
6
+ * decoupled from any one plugin's driver wiring.
20
7
  */
21
- export interface WriteLlmsTxtInput {
22
- accountId: string;
23
- siteName: string;
24
- siteDescription?: string;
25
- siteDir?: string;
26
- }
27
- export interface WriteLlmsTxtResult {
28
- llmsTxt: string;
29
- llmsFullTxt: string;
30
- pageCount: number;
31
- skippedNoUrl: number;
32
- sizeBytes: {
33
- index: number;
34
- full: number;
35
- };
36
- writtenPaths?: {
37
- index: string;
38
- full: string;
39
- };
40
- }
41
8
  export declare function writeLlmsTxt(input: WriteLlmsTxtInput): Promise<WriteLlmsTxtResult>;
42
9
  //# sourceMappingURL=aeo-write-llms-txt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aeo-write-llms-txt.d.ts","sourceRoot":"","sources":["../../src/tools/aeo-write-llms-txt.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IAMzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAqBD,wBAAsB,YAAY,CAChC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAsH7B"}
1
+ {"version":3,"file":"aeo-write-llms-txt.d.ts","sourceRoot":"","sources":["../../src/tools/aeo-write-llms-txt.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,sDAAsD,CAAC;AAG9D,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC;AAEtD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAE7B"}
@@ -1,118 +1,11 @@
1
- import { stat, writeFile } from "node:fs/promises";
2
- import { resolve as resolvePath } from "node:path";
1
+ import { writeLlmsTxt as writeLlmsTxtLib, } from "../../../../../lib/aeo-llms-txt-writer/dist/index.js";
3
2
  import { getSession } from "../lib/neo4j.js";
4
- const HEADER_RX = /^#\s/m;
5
- function escapeMarkdown(text) {
6
- return text.replace(/[\[\]\(\)\n\r]/g, " ").trim();
7
- }
8
- function summariseBody(body) {
9
- const flat = body.replace(/\s+/g, " ").trim();
10
- if (flat.length <= 200)
11
- return flat;
12
- return flat.slice(0, 197).trimEnd() + "…";
13
- }
3
+ /**
4
+ * Thin aeo-side wrapper around the plugin-agnostic writeLlmsTxt lib.
5
+ * Injects this plugin's neo4j session factory so the shared lib stays
6
+ * decoupled from any one plugin's driver wiring.
7
+ */
14
8
  export async function writeLlmsTxt(input) {
15
- const session = getSession();
16
- let rows = [];
17
- let skippedNoUrl = 0;
18
- try {
19
- const r = await session.run(`MATCH (d:KnowledgeDocument {accountId: $accountId})
20
- RETURN d.title AS title, d.url AS url, d.summary AS summary,
21
- coalesce(d.body, d.abstract, '') AS body
22
- ORDER BY d.title ASC`, { accountId: input.accountId });
23
- for (const rec of r.records) {
24
- const title = rec.get("title") ?? "";
25
- const url = rec.get("url") ?? "";
26
- const summary = rec.get("summary") ?? "";
27
- const body = rec.get("body") ?? "";
28
- if (!url || !title) {
29
- skippedNoUrl += 1;
30
- continue;
31
- }
32
- rows.push({
33
- title: escapeMarkdown(title),
34
- url: url.trim(),
35
- summary: escapeMarkdown(summary) || summariseBody(body) || "(no summary)",
36
- body: body.trim(),
37
- });
38
- }
39
- }
40
- finally {
41
- await session.close();
42
- }
43
- const indexLines = [];
44
- indexLines.push(`# ${escapeMarkdown(input.siteName)}`);
45
- indexLines.push("");
46
- if (input.siteDescription) {
47
- indexLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
48
- indexLines.push("");
49
- }
50
- indexLines.push("## Pages");
51
- indexLines.push("");
52
- for (const row of rows) {
53
- indexLines.push(`- [${row.title}](${row.url}): ${row.summary}`);
54
- }
55
- indexLines.push("");
56
- indexLines.push("<!-- generated by @maxy/aeo aeo-write-llms-txt; spec: https://llmstxt.org/ -->");
57
- const llmsTxt = indexLines.join("\n");
58
- const fullLines = [];
59
- fullLines.push(`# ${escapeMarkdown(input.siteName)}`);
60
- fullLines.push("");
61
- if (input.siteDescription) {
62
- fullLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
63
- fullLines.push("");
64
- }
65
- for (const row of rows) {
66
- fullLines.push(`# ${row.title}`);
67
- fullLines.push("");
68
- fullLines.push(`Source: ${row.url}`);
69
- fullLines.push("");
70
- if (row.body) {
71
- // Demote any in-body H1s to H2 so a single H1 hierarchy holds.
72
- const safeBody = row.body.replace(HEADER_RX, "## ");
73
- fullLines.push(safeBody);
74
- }
75
- else {
76
- fullLines.push(row.summary);
77
- }
78
- fullLines.push("");
79
- fullLines.push("---");
80
- fullLines.push("");
81
- }
82
- const llmsFullTxt = fullLines.join("\n");
83
- const sizeBytes = {
84
- index: Buffer.byteLength(llmsTxt, "utf-8"),
85
- full: Buffer.byteLength(llmsFullTxt, "utf-8"),
86
- };
87
- process.stderr.write(`[aeo-llms-txt] site=${input.siteName} pages=${rows.length} skippedNoUrl=${skippedNoUrl} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`);
88
- let writtenPaths;
89
- if (input.siteDir) {
90
- const dir = resolvePath(input.siteDir);
91
- let dirStat;
92
- try {
93
- dirStat = await stat(dir);
94
- }
95
- catch (err) {
96
- const code = err?.code;
97
- throw new Error(`siteDir does not exist (${code ?? "unknown"}): ${dir}`);
98
- }
99
- if (!dirStat.isDirectory()) {
100
- throw new Error(`siteDir is not a directory: ${dir}`);
101
- }
102
- const indexPath = resolvePath(dir, "llms.txt");
103
- const fullPath = resolvePath(dir, "llms-full.txt");
104
- await writeFile(indexPath, llmsTxt, "utf-8");
105
- await writeFile(fullPath, llmsFullTxt, "utf-8");
106
- writtenPaths = { index: indexPath, full: fullPath };
107
- process.stderr.write(`[aeo-llms-txt] wrote site=${dir} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`);
108
- }
109
- return {
110
- llmsTxt,
111
- llmsFullTxt,
112
- pageCount: rows.length,
113
- skippedNoUrl,
114
- sizeBytes,
115
- writtenPaths,
116
- };
9
+ return writeLlmsTxtLib(input, { getSession });
117
10
  }
118
11
  //# sourceMappingURL=aeo-write-llms-txt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aeo-write-llms-txt.js","sourceRoot":"","sources":["../../src/tools/aeo-write-llms-txt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aeo-write-llms-txt.js","sourceRoot":"","sources":["../../src/tools/aeo-write-llms-txt.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,IAAI,eAAe,GAGhC,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAwB;IAExB,OAAO,eAAe,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;AAChD,CAAC"}
@@ -50,10 +50,11 @@ All under the `real-agent-buyers` plugin, admin-side only:
50
50
  | `visitor-recent-by-person` | Recent sessions for a known `:Person` (morning round, 1:1 prep). |
51
51
  | `visitor-recent-by-page` | Recent visitors of a given listing slug or URL. |
52
52
  | `visitor-engagement-score` | Engagement-ranked `:Person` list (nurture queue). |
53
- | `visitor-recommendation-ctr` | Graph-backed CTR over a window. Replaces the Task 336 log-scan after the 14-day parity cutover. |
53
+ | `visitor-recommendation-ctr` | Graph-backed CTR over a window, joined from `:Recommendation` and `:Click` nodes. |
54
54
  | `visitor-session-detail` | Full event timeline for one `:Session`. |
55
55
  | `visitor-event-ingest` | Admin companion to `POST /v/event` for test harness work. |
56
- | `visitor-backfill-from-logs` | One-shot importer: parses `[property-recommended]` and `[property-card-click]` log lines, computes a parity check, populates the graph. |
56
+ | `visitor-backfill-from-logs` | One-shot importer: parses `[property-recommended]` and `[property-card-click]` log lines into the graph; used to recover late-arriving sessions and for ad-hoc forensics. |
57
+ | `mint-visitor-token` | Mints a signed token bound to a `:Person` for outbound URLs in `morning-round`, `lead-nurturing`, `vendor-updates`. Returns `{ token, expiryMs }`; the agent appends `&v=<token>` to `/listings/<slug>/click?session=<sk>`. Same secret file as the UI server; both processes share it via the wx-create pattern. (Task 362) |
57
58
 
58
59
  ## Privacy
59
60
 
@@ -70,7 +71,7 @@ Quick checks the operator can run after deployment:
70
71
  1. Load a published listing page; grep `[visitor-event] type=pageview` in `server.log` within 1s.
71
72
  2. Scroll past 50%; grep `[visitor-event] type=scroll depth=50`.
72
73
  3. Click an element marked `data-track="floorplan"`; grep `[visitor-event] type=click label=floorplan`.
73
- 4. Run `visitor-backfill-from-logs` over the existing Task 336 log window; the response includes `parityCheck=ok` when graph CTR matches log-scan CTR exactly.
74
+ 4. Run `visitor-backfill-from-logs` over a log window where live writes were lost (process restart, etc.); the response reports `recWritten` and `clickWritten` counts. Subsequent runs over the same window are idempotent for `:Recommendation` and append-only for `:Click`.
74
75
 
75
76
  ## Failure signals
76
77
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "graph-viewer",
3
+ "description": "Inline Neo4j subgraph renderer. Provides graph-render — a Node-side PNG renderer that walks N hops from a starting node, applies the same palette and display strings the live /graph page uses, and returns the image as an MCP image content block. Removes the device-switch otherwise required for the agent to show the operator what a subgraph looks like. Account-scoped: the tool refuses to render nodes outside the caller's accountId. Layout is d3-force with a deterministic seed; drawing is @napi-rs/canvas with a system-font fallback for labels. The fidelity contract is palette and display-string parity — node positions will differ from the live page's vis-network layout. Ships one skill: render-graph.",
4
+ "version": "0.1.0",
5
+ "author": {
6
+ "name": "Rubytech LLC"
7
+ }
8
+ }
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: graph-viewer
3
+ mcp:
4
+ command: node
5
+ args:
6
+ - ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
7
+ - ${PLATFORM_ROOT}/plugins/graph-viewer/mcp/dist/index.js
8
+ env:
9
+ MCP_SPAWN_TEE_NAME: graph-viewer
10
+ LOG_DIR: ${LOG_DIR}
11
+ PLATFORM_ROOT: ${PLATFORM_ROOT}
12
+ ACCOUNT_ID: ${ACCOUNT_ID}
13
+ SESSION_ID: ${SESSION_ID}
14
+ mcp-manifest: skip
15
+ description: "Inline Neo4j subgraph renderer. Provides graph-render — a Node-side PNG renderer that walks N hops from a starting node, applies the same palette and display strings the live /graph page uses, and returns the image as an MCP image content block. Removes the device-switch otherwise required for the agent to show the operator what a subgraph looks like. Account-scoped: the tool refuses to render nodes outside the caller's accountId. Layout is d3-force with a deterministic seed; drawing is @napi-rs/canvas with a system-font fallback for labels. The fidelity contract is palette and display-string parity — node positions will differ from the live page's vis-network layout. Ships one skill: render-graph."
16
+ tools:
17
+ - name: graph-render
18
+ publicAllowlist: false
19
+ adminAllowlist: true
20
+ ---
21
+
22
+ # graph-viewer
23
+
24
+ ## What it does
25
+
26
+ A single MCP tool, `graph-render`, takes a Neo4j node ID and a hop depth and
27
+ returns an inline PNG of the subgraph rooted at that node. The agent
28
+ attaches the PNG to its reply so the operator can see the graph without
29
+ opening the admin UI on a different device.
30
+
31
+ ## Why it exists
32
+
33
+ Before this plugin, the agent had no way to ship the operator a picture
34
+ of a subgraph from inside chat. Mermaid and ASCII substitutes were
35
+ illegible and bore no resemblance to the live `/graph` page. Operators
36
+ otherwise had to leave the chat surface, open the admin UI on whatever
37
+ device hosts it, and re-find the same subgraph by hand.
38
+
39
+ ## Surface boundaries
40
+
41
+ - `graph-render` only accepts a `nodeId + hopDepth` query shape. Arbitrary
42
+ Cypher is not supported and is filed as a follow-up task.
43
+ - Pixel-equivalence with `/graph` is not a goal. The layout engine is
44
+ d3-force, not the live page's vis-network port; node positions will
45
+ differ. The colour palette, label precedence, and short-label strings
46
+ are shared with `/graph` via `platform/lib/graph-style/`.
47
+ - Subgraphs above 200 nodes return a truncation envelope alongside the
48
+ capped render.
49
+ - `:Trashed` nodes are filtered out, matching `memory-search`'s contract.
50
+
51
+ ## Enabling
52
+
53
+ The plugin is auto-discovered from `platform/plugins/`. Each operator
54
+ account must list `graph-viewer` in its `enabledPlugins` (use the admin
55
+ `plugin-toggle-enabled` MCP tool) before `graph-render` appears in the
56
+ agent's tool surface.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP server entrypoint for the graph-viewer plugin. Registers one tool,
3
+ * `graph-render`, on the standard stdio transport. Account-scoping is
4
+ * enforced inside the tool handler against the caller-supplied accountId.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}