@rubytech/create-maxy-code 0.1.126 → 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.
- package/dist/index.js +12 -0
- package/package.json +1 -1
- package/payload/platform/config/brand.json +20 -1
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts +33 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js +119 -0
- package/payload/platform/lib/aeo-llms-txt-writer/dist/index.js.map +1 -0
- package/payload/platform/lib/aeo-llms-txt-writer/src/index.ts +172 -0
- package/payload/platform/lib/aeo-llms-txt-writer/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +73 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts +34 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js +176 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/publish-site.js.map +1 -0
- package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +19 -57
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +5 -38
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -1
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +7 -114
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
- package/payload/server/public/consent.css +97 -0
- package/payload/server/public/consent.js +259 -0
- package/payload/server/public/privacy.html +68 -5
- package/payload/server/public/v.js +53 -0
- package/payload/server/server.js +229 -84
package/dist/index.js
CHANGED
|
@@ -3510,6 +3510,7 @@ else {
|
|
|
3510
3510
|
// Upgrade detection: preserve existing DISPLAY_MODE from .env
|
|
3511
3511
|
const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
3512
3512
|
const envPath = join(persistDir, ".env");
|
|
3513
|
+
let preservedFromEnv = false;
|
|
3513
3514
|
try {
|
|
3514
3515
|
if (existsSync(envPath)) {
|
|
3515
3516
|
const envContent = readFileSync(envPath, "utf-8");
|
|
@@ -3517,10 +3518,21 @@ else {
|
|
|
3517
3518
|
if (envMatch) {
|
|
3518
3519
|
DISPLAY_MODE = envMatch[1];
|
|
3519
3520
|
DISPLAY_MODE_SOURCE = ".env (preserved)";
|
|
3521
|
+
preservedFromEnv = true;
|
|
3520
3522
|
}
|
|
3521
3523
|
}
|
|
3522
3524
|
}
|
|
3523
3525
|
catch { /* non-critical */ }
|
|
3526
|
+
// Darwin has no VNC/X11 stack — `virtual` mode is Pi-only (tigervnc +
|
|
3527
|
+
// websockify + novnc, none of which exist on macOS). Without this guard
|
|
3528
|
+
// a fresh Mac install defaults to virtual, the OAuth step calls the
|
|
3529
|
+
// "Open browser" path that requires a virtual display, and the user
|
|
3530
|
+
// sees "VNC display failed to start" before they ever get to sign in.
|
|
3531
|
+
// Auto-flip to native on darwin when the operator didn't pick a mode.
|
|
3532
|
+
if (!preservedFromEnv && process.platform === "darwin") {
|
|
3533
|
+
DISPLAY_MODE = "native";
|
|
3534
|
+
DISPLAY_MODE_SOURCE = "darwin-auto (no VNC stack)";
|
|
3535
|
+
}
|
|
3524
3536
|
}
|
|
3525
3537
|
// ---------------------------------------------------------------------------
|
|
3526
3538
|
// Embedding model — install-time flag, not a brand attribute.
|
package/package.json
CHANGED
|
@@ -58,5 +58,24 @@
|
|
|
58
58
|
{ "name": "telegram", "marketplace": "claude-plugins-official", "configureSecret": "TELEGRAM_BOT_TOKEN", "channelPlugin": true },
|
|
59
59
|
{ "name": "discord", "marketplace": "claude-plugins-official", "configureSecret": "DISCORD_BOT_TOKEN", "channelPlugin": true },
|
|
60
60
|
{ "name": "imessage", "marketplace": "claude-plugins-official", "channelPlugin": true }
|
|
61
|
-
]
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
"consent": {
|
|
64
|
+
"copy": {
|
|
65
|
+
"title": "Before you browse this listing",
|
|
66
|
+
"body": "Maxy uses a first-party script to count page views, scrolls and clicks. Nothing is shared with third parties. You can change your mind any time via Manage cookies in the footer.",
|
|
67
|
+
"accept": "Accept",
|
|
68
|
+
"reject": "Reject",
|
|
69
|
+
"manage": "Manage cookies",
|
|
70
|
+
"policyLinkText": "Read the cookie policy",
|
|
71
|
+
"policyHref": "/privacy.html#cookies"
|
|
72
|
+
},
|
|
73
|
+
"palette": {
|
|
74
|
+
"bg": "#FAFAF8",
|
|
75
|
+
"fg": "#1F1F1F",
|
|
76
|
+
"accent": "#7C8C72",
|
|
77
|
+
"accentFg": "#FFFFFF",
|
|
78
|
+
"border": "#E8ECF0"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
62
81
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Session } from "neo4j-driver";
|
|
2
|
+
/**
|
|
3
|
+
* Pure llms.txt + llms-full.txt generator for an account's published
|
|
4
|
+
* KnowledgeDocument set. llmstxt.org draft format. Plugin-agnostic:
|
|
5
|
+
* the caller injects its own neo4j session factory so the lib can
|
|
6
|
+
* be consumed from any plugin that already has a configured driver
|
|
7
|
+
* (aeo, admin's publish-site tool, future hook callers).
|
|
8
|
+
*/
|
|
9
|
+
export interface WriteLlmsTxtInput {
|
|
10
|
+
accountId: string;
|
|
11
|
+
siteName: string;
|
|
12
|
+
siteDescription?: string;
|
|
13
|
+
siteDir?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WriteLlmsTxtDeps {
|
|
16
|
+
getSession: () => Session;
|
|
17
|
+
}
|
|
18
|
+
export interface WriteLlmsTxtResult {
|
|
19
|
+
llmsTxt: string;
|
|
20
|
+
llmsFullTxt: string;
|
|
21
|
+
pageCount: number;
|
|
22
|
+
skippedNoUrl: number;
|
|
23
|
+
sizeBytes: {
|
|
24
|
+
index: number;
|
|
25
|
+
full: number;
|
|
26
|
+
};
|
|
27
|
+
writtenPaths?: {
|
|
28
|
+
index: string;
|
|
29
|
+
full: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare function writeLlmsTxt(input: WriteLlmsTxtInput, deps: WriteLlmsTxtDeps): Promise<WriteLlmsTxtResult>;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,OAAO,CAAC;CAC3B;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,EACxB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAqH7B"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeLlmsTxt = writeLlmsTxt;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const HEADER_RX = /^#\s/m;
|
|
7
|
+
function escapeMarkdown(text) {
|
|
8
|
+
return text.replace(/[\[\]\(\)\n\r]/g, " ").trim();
|
|
9
|
+
}
|
|
10
|
+
function summariseBody(body) {
|
|
11
|
+
const flat = body.replace(/\s+/g, " ").trim();
|
|
12
|
+
if (flat.length <= 200)
|
|
13
|
+
return flat;
|
|
14
|
+
return flat.slice(0, 197).trimEnd() + "…";
|
|
15
|
+
}
|
|
16
|
+
async function writeLlmsTxt(input, deps) {
|
|
17
|
+
const session = deps.getSession();
|
|
18
|
+
let rows = [];
|
|
19
|
+
let skippedNoUrl = 0;
|
|
20
|
+
try {
|
|
21
|
+
const r = await session.run(`MATCH (d:KnowledgeDocument {accountId: $accountId})
|
|
22
|
+
RETURN d.title AS title, d.url AS url, d.summary AS summary,
|
|
23
|
+
coalesce(d.body, d.abstract, '') AS body
|
|
24
|
+
ORDER BY d.title ASC`, { accountId: input.accountId });
|
|
25
|
+
for (const rec of r.records) {
|
|
26
|
+
const title = rec.get("title") ?? "";
|
|
27
|
+
const url = rec.get("url") ?? "";
|
|
28
|
+
const summary = rec.get("summary") ?? "";
|
|
29
|
+
const body = rec.get("body") ?? "";
|
|
30
|
+
if (!url || !title) {
|
|
31
|
+
skippedNoUrl += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
rows.push({
|
|
35
|
+
title: escapeMarkdown(title),
|
|
36
|
+
url: url.trim(),
|
|
37
|
+
summary: escapeMarkdown(summary) || summariseBody(body) || "(no summary)",
|
|
38
|
+
body: body.trim(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await session.close();
|
|
44
|
+
}
|
|
45
|
+
const indexLines = [];
|
|
46
|
+
indexLines.push(`# ${escapeMarkdown(input.siteName)}`);
|
|
47
|
+
indexLines.push("");
|
|
48
|
+
if (input.siteDescription) {
|
|
49
|
+
indexLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
|
|
50
|
+
indexLines.push("");
|
|
51
|
+
}
|
|
52
|
+
indexLines.push("## Pages");
|
|
53
|
+
indexLines.push("");
|
|
54
|
+
for (const row of rows) {
|
|
55
|
+
indexLines.push(`- [${row.title}](${row.url}): ${row.summary}`);
|
|
56
|
+
}
|
|
57
|
+
indexLines.push("");
|
|
58
|
+
indexLines.push("<!-- generated by @maxy/aeo-llms-txt-writer; spec: https://llmstxt.org/ -->");
|
|
59
|
+
const llmsTxt = indexLines.join("\n");
|
|
60
|
+
const fullLines = [];
|
|
61
|
+
fullLines.push(`# ${escapeMarkdown(input.siteName)}`);
|
|
62
|
+
fullLines.push("");
|
|
63
|
+
if (input.siteDescription) {
|
|
64
|
+
fullLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
|
|
65
|
+
fullLines.push("");
|
|
66
|
+
}
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
fullLines.push(`# ${row.title}`);
|
|
69
|
+
fullLines.push("");
|
|
70
|
+
fullLines.push(`Source: ${row.url}`);
|
|
71
|
+
fullLines.push("");
|
|
72
|
+
if (row.body) {
|
|
73
|
+
const safeBody = row.body.replace(HEADER_RX, "## ");
|
|
74
|
+
fullLines.push(safeBody);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
fullLines.push(row.summary);
|
|
78
|
+
}
|
|
79
|
+
fullLines.push("");
|
|
80
|
+
fullLines.push("---");
|
|
81
|
+
fullLines.push("");
|
|
82
|
+
}
|
|
83
|
+
const llmsFullTxt = fullLines.join("\n");
|
|
84
|
+
const sizeBytes = {
|
|
85
|
+
index: Buffer.byteLength(llmsTxt, "utf-8"),
|
|
86
|
+
full: Buffer.byteLength(llmsFullTxt, "utf-8"),
|
|
87
|
+
};
|
|
88
|
+
process.stderr.write(`[aeo-llms-txt] site=${input.siteName} pages=${rows.length} skippedNoUrl=${skippedNoUrl} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`);
|
|
89
|
+
let writtenPaths;
|
|
90
|
+
if (input.siteDir) {
|
|
91
|
+
const dir = (0, node_path_1.resolve)(input.siteDir);
|
|
92
|
+
let dirStat;
|
|
93
|
+
try {
|
|
94
|
+
dirStat = await (0, promises_1.stat)(dir);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const code = err?.code;
|
|
98
|
+
throw new Error(`siteDir does not exist (${code ?? "unknown"}): ${dir}`);
|
|
99
|
+
}
|
|
100
|
+
if (!dirStat.isDirectory()) {
|
|
101
|
+
throw new Error(`siteDir is not a directory: ${dir}`);
|
|
102
|
+
}
|
|
103
|
+
const indexPath = (0, node_path_1.resolve)(dir, "llms.txt");
|
|
104
|
+
const fullPath = (0, node_path_1.resolve)(dir, "llms-full.txt");
|
|
105
|
+
await (0, promises_1.writeFile)(indexPath, llmsTxt, "utf-8");
|
|
106
|
+
await (0, promises_1.writeFile)(fullPath, llmsFullTxt, "utf-8");
|
|
107
|
+
writtenPaths = { index: indexPath, full: fullPath };
|
|
108
|
+
process.stderr.write(`[aeo-llms-txt] wrote site=${dir} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
llmsTxt,
|
|
112
|
+
llmsFullTxt,
|
|
113
|
+
pageCount: rows.length,
|
|
114
|
+
skippedNoUrl,
|
|
115
|
+
sizeBytes,
|
|
116
|
+
writtenPaths,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
3
|
+
import type { Session } from "neo4j-driver";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure llms.txt + llms-full.txt generator for an account's published
|
|
7
|
+
* KnowledgeDocument set. llmstxt.org draft format. Plugin-agnostic:
|
|
8
|
+
* the caller injects its own neo4j session factory so the lib can
|
|
9
|
+
* be consumed from any plugin that already has a configured driver
|
|
10
|
+
* (aeo, admin's publish-site tool, future hook callers).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface WriteLlmsTxtInput {
|
|
14
|
+
accountId: string;
|
|
15
|
+
siteName: string;
|
|
16
|
+
siteDescription?: string;
|
|
17
|
+
siteDir?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WriteLlmsTxtDeps {
|
|
21
|
+
getSession: () => Session;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WriteLlmsTxtResult {
|
|
25
|
+
llmsTxt: string;
|
|
26
|
+
llmsFullTxt: string;
|
|
27
|
+
pageCount: number;
|
|
28
|
+
skippedNoUrl: number;
|
|
29
|
+
sizeBytes: { index: number; full: number };
|
|
30
|
+
writtenPaths?: { index: string; full: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PageRow {
|
|
34
|
+
title: string;
|
|
35
|
+
url: string;
|
|
36
|
+
summary: string;
|
|
37
|
+
body: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const HEADER_RX = /^#\s/m;
|
|
41
|
+
|
|
42
|
+
function escapeMarkdown(text: string): string {
|
|
43
|
+
return text.replace(/[\[\]\(\)\n\r]/g, " ").trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function summariseBody(body: string): string {
|
|
47
|
+
const flat = body.replace(/\s+/g, " ").trim();
|
|
48
|
+
if (flat.length <= 200) return flat;
|
|
49
|
+
return flat.slice(0, 197).trimEnd() + "…";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function writeLlmsTxt(
|
|
53
|
+
input: WriteLlmsTxtInput,
|
|
54
|
+
deps: WriteLlmsTxtDeps,
|
|
55
|
+
): Promise<WriteLlmsTxtResult> {
|
|
56
|
+
const session = deps.getSession();
|
|
57
|
+
let rows: PageRow[] = [];
|
|
58
|
+
let skippedNoUrl = 0;
|
|
59
|
+
try {
|
|
60
|
+
const r = await session.run(
|
|
61
|
+
`MATCH (d:KnowledgeDocument {accountId: $accountId})
|
|
62
|
+
RETURN d.title AS title, d.url AS url, d.summary AS summary,
|
|
63
|
+
coalesce(d.body, d.abstract, '') AS body
|
|
64
|
+
ORDER BY d.title ASC`,
|
|
65
|
+
{ accountId: input.accountId },
|
|
66
|
+
);
|
|
67
|
+
for (const rec of r.records) {
|
|
68
|
+
const title = (rec.get("title") as string | null) ?? "";
|
|
69
|
+
const url = (rec.get("url") as string | null) ?? "";
|
|
70
|
+
const summary = (rec.get("summary") as string | null) ?? "";
|
|
71
|
+
const body = (rec.get("body") as string | null) ?? "";
|
|
72
|
+
if (!url || !title) {
|
|
73
|
+
skippedNoUrl += 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
rows.push({
|
|
77
|
+
title: escapeMarkdown(title),
|
|
78
|
+
url: url.trim(),
|
|
79
|
+
summary:
|
|
80
|
+
escapeMarkdown(summary) || summariseBody(body) || "(no summary)",
|
|
81
|
+
body: body.trim(),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
await session.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const indexLines: string[] = [];
|
|
89
|
+
indexLines.push(`# ${escapeMarkdown(input.siteName)}`);
|
|
90
|
+
indexLines.push("");
|
|
91
|
+
if (input.siteDescription) {
|
|
92
|
+
indexLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
|
|
93
|
+
indexLines.push("");
|
|
94
|
+
}
|
|
95
|
+
indexLines.push("## Pages");
|
|
96
|
+
indexLines.push("");
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
indexLines.push(`- [${row.title}](${row.url}): ${row.summary}`);
|
|
99
|
+
}
|
|
100
|
+
indexLines.push("");
|
|
101
|
+
indexLines.push(
|
|
102
|
+
"<!-- generated by @maxy/aeo-llms-txt-writer; spec: https://llmstxt.org/ -->",
|
|
103
|
+
);
|
|
104
|
+
const llmsTxt = indexLines.join("\n");
|
|
105
|
+
|
|
106
|
+
const fullLines: string[] = [];
|
|
107
|
+
fullLines.push(`# ${escapeMarkdown(input.siteName)}`);
|
|
108
|
+
fullLines.push("");
|
|
109
|
+
if (input.siteDescription) {
|
|
110
|
+
fullLines.push(`> ${escapeMarkdown(input.siteDescription)}`);
|
|
111
|
+
fullLines.push("");
|
|
112
|
+
}
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
fullLines.push(`# ${row.title}`);
|
|
115
|
+
fullLines.push("");
|
|
116
|
+
fullLines.push(`Source: ${row.url}`);
|
|
117
|
+
fullLines.push("");
|
|
118
|
+
if (row.body) {
|
|
119
|
+
const safeBody = row.body.replace(HEADER_RX, "## ");
|
|
120
|
+
fullLines.push(safeBody);
|
|
121
|
+
} else {
|
|
122
|
+
fullLines.push(row.summary);
|
|
123
|
+
}
|
|
124
|
+
fullLines.push("");
|
|
125
|
+
fullLines.push("---");
|
|
126
|
+
fullLines.push("");
|
|
127
|
+
}
|
|
128
|
+
const llmsFullTxt = fullLines.join("\n");
|
|
129
|
+
|
|
130
|
+
const sizeBytes = {
|
|
131
|
+
index: Buffer.byteLength(llmsTxt, "utf-8"),
|
|
132
|
+
full: Buffer.byteLength(llmsFullTxt, "utf-8"),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`[aeo-llms-txt] site=${input.siteName} pages=${rows.length} skippedNoUrl=${skippedNoUrl} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
let writtenPaths: { index: string; full: string } | undefined;
|
|
140
|
+
if (input.siteDir) {
|
|
141
|
+
const dir = resolvePath(input.siteDir);
|
|
142
|
+
let dirStat;
|
|
143
|
+
try {
|
|
144
|
+
dirStat = await stat(dir);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
147
|
+
throw new Error(
|
|
148
|
+
`siteDir does not exist (${code ?? "unknown"}): ${dir}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (!dirStat.isDirectory()) {
|
|
152
|
+
throw new Error(`siteDir is not a directory: ${dir}`);
|
|
153
|
+
}
|
|
154
|
+
const indexPath = resolvePath(dir, "llms.txt");
|
|
155
|
+
const fullPath = resolvePath(dir, "llms-full.txt");
|
|
156
|
+
await writeFile(indexPath, llmsTxt, "utf-8");
|
|
157
|
+
await writeFile(fullPath, llmsFullTxt, "utf-8");
|
|
158
|
+
writtenPaths = { index: indexPath, full: fullPath };
|
|
159
|
+
process.stderr.write(
|
|
160
|
+
`[aeo-llms-txt] wrote site=${dir} indexBytes=${sizeBytes.index} fullBytes=${sizeBytes.full}\n`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
llmsTxt,
|
|
166
|
+
llmsFullTxt,
|
|
167
|
+
pageCount: rows.length,
|
|
168
|
+
skippedNoUrl,
|
|
169
|
+
sizeBytes,
|
|
170
|
+
writtenPaths,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"services/*"
|
|
7
7
|
],
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
-
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json",
|
|
9
|
+
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
+
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json",
|
|
11
11
|
"build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
|
|
12
12
|
"build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
|
|
13
13
|
"build:telegram": "tsc -p plugins/telegram/mcp/tsconfig.json",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "admin",
|
|
3
|
-
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, file-attach, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform.",
|
|
3
|
+
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, file-attach, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, file-attach, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
4
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, file-attach, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
5
5
|
tools:
|
|
6
6
|
- name: system-status
|
|
7
7
|
publicAllowlist: false
|
|
@@ -9,6 +9,9 @@ tools:
|
|
|
9
9
|
- name: public-hostname
|
|
10
10
|
publicAllowlist: false
|
|
11
11
|
adminAllowlist: false
|
|
12
|
+
- name: publish-site
|
|
13
|
+
publicAllowlist: false
|
|
14
|
+
adminAllowlist: false
|
|
12
15
|
- name: brand-settings
|
|
13
16
|
publicAllowlist: false
|
|
14
17
|
adminAllowlist: false
|
|
@@ -20,6 +20,21 @@ import QRCode from "qrcode";
|
|
|
20
20
|
import { getSession, closeDriver } from "./lib/neo4j.js";
|
|
21
21
|
import { findSkillOwners, computePluginReadHint, loadSkill, parseRequiredInputs } from "./skill-resolution.js";
|
|
22
22
|
import { resolvePublicHostname } from "./lib/public-hostname.js";
|
|
23
|
+
import { publishSite } from "./tools/publish-site.js";
|
|
24
|
+
function refusalGuidance(kind) {
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case "unsafe-slug":
|
|
27
|
+
return "Operator action: pick a slug whose segments match /^[a-z0-9_][a-z0-9_.-]{0,99}$/i, no leading dot, no `..`.";
|
|
28
|
+
case "destination-occupied":
|
|
29
|
+
return "Operator action: the target directory already exists with contents. Clean it up explicitly or pick a different slug.";
|
|
30
|
+
case "symlink-in-source":
|
|
31
|
+
return "Operator action: the source tree contains a symlink. Resolve or remove the symlink before publishing.";
|
|
32
|
+
case "zero-html":
|
|
33
|
+
return "Operator action: the source has no top-level .html file. Confirm the tree shape.";
|
|
34
|
+
case "ambiguous-html":
|
|
35
|
+
return "Operator action: pick which top-level .html file is the landing page (or add an index.html to the source).";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
23
38
|
const server = new McpServer({
|
|
24
39
|
name: "admin",
|
|
25
40
|
version: "0.1.0",
|
|
@@ -468,6 +483,64 @@ eagerTool(server, "public-hostname", "Resolve this account's canonical public ho
|
|
|
468
483
|
};
|
|
469
484
|
}
|
|
470
485
|
});
|
|
486
|
+
server.tool("publish-site", "Publish an already-extracted static-site tree under <accountDir>/sites/<slug>/. Validates the slug, scans the source for symlinks, refuses on ambiguous landing pages, performs a single mv, then refreshes llms.txt + llms-full.txt at the site root via the AEO writer (best-effort — failure never unwinds the mv). Returns the canonical path slug; pair with public-hostname to build the full URL. Refusals are structured (unsafe-slug | destination-occupied | symlink-in-source | zero-html | ambiguous-html), each carrying a fixed operator message.", {
|
|
487
|
+
source: z
|
|
488
|
+
.string()
|
|
489
|
+
.min(1)
|
|
490
|
+
.describe("Absolute path to the extracted source directory (typically the output of unzip-attachment under <accountDir>/extracted/<id>/). Must be a directory tree of HTML + assets."),
|
|
491
|
+
slug: z
|
|
492
|
+
.string()
|
|
493
|
+
.min(1)
|
|
494
|
+
.describe("Operator-supplied path under <accountDir>/sites/. One or more `/`-separated segments; each segment matches /^[a-z0-9_][a-z0-9_.-]{0,99}$/i, no leading dot, no `..`."),
|
|
495
|
+
siteName: z
|
|
496
|
+
.string()
|
|
497
|
+
.optional()
|
|
498
|
+
.describe("Optional display name used in the generated llms.txt header. Defaults to the last slug segment."),
|
|
499
|
+
siteDescription: z
|
|
500
|
+
.string()
|
|
501
|
+
.optional()
|
|
502
|
+
.describe("Optional one-line description rendered as a blockquote under the llms.txt header."),
|
|
503
|
+
}, async (args) => {
|
|
504
|
+
if (!ACCOUNT_ID)
|
|
505
|
+
return refuseNoAccount("publish-site");
|
|
506
|
+
const accountDir = getAccountDir();
|
|
507
|
+
try {
|
|
508
|
+
const result = await publishSite({
|
|
509
|
+
accountId: ACCOUNT_ID,
|
|
510
|
+
accountDir,
|
|
511
|
+
source: args.source,
|
|
512
|
+
slug: args.slug,
|
|
513
|
+
siteName: args.siteName,
|
|
514
|
+
siteDescription: args.siteDescription,
|
|
515
|
+
}, { getSession });
|
|
516
|
+
if (!result.ok) {
|
|
517
|
+
const body = `refused: ${result.refusal}\n` +
|
|
518
|
+
`detail: ${JSON.stringify(result.detail)}\n` +
|
|
519
|
+
refusalGuidance(result.refusal);
|
|
520
|
+
return { content: [{ type: "text", text: body }] };
|
|
521
|
+
}
|
|
522
|
+
const aeoLine = result.aeo.ok
|
|
523
|
+
? `aeo: ok wrote=${result.aeo.indexPath} indexBytes=${result.aeo.indexBytes}`
|
|
524
|
+
: `aeo: failed reason=${result.aeo.reason}`;
|
|
525
|
+
const body = `pathSlug: ${result.pathSlug}\n` +
|
|
526
|
+
`kind: ${result.kind}\n` +
|
|
527
|
+
`movedFiles: ${result.movedFiles}\n` +
|
|
528
|
+
`destination: ${result.destination}\n` +
|
|
529
|
+
`${aeoLine}\n` +
|
|
530
|
+
`usage: call public-hostname next and concatenate \`https://<hostname>${result.pathSlug}\` to surface the full URL to the operator.`;
|
|
531
|
+
return { content: [{ type: "text", text: body }] };
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
535
|
+
process.stderr.write(`[publish-site] error accountId=${ACCOUNT_ID} message="${errMsg.replace(/"/g, "'")}"\n`);
|
|
536
|
+
return {
|
|
537
|
+
content: [
|
|
538
|
+
{ type: "text", text: `publish-site failed: ${errMsg}` },
|
|
539
|
+
],
|
|
540
|
+
isError: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
});
|
|
471
544
|
eagerTool(server, "remote-auth-status", "Check whether the remote access password is configured. When not configured, emits a device-bound URL affordance (maxy-device-url fenced block) pointing at the password setup page — this URL opens on the device's own screen when the operator clicks it. The agent never constructs the password file path or runs shell commands — this tool is the single authority.", {}, async () => {
|
|
472
545
|
if (!ACCOUNT_ID)
|
|
473
546
|
return refuseNoAccount("remote-auth-status");
|