@openmnemo/report 0.2.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/LICENSE +21 -0
- package/dist/chunk-RCUFIYQZ.js +14 -0
- package/dist/github-pages-XPQKYB2E.js +102 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +3235 -0
- package/dist/webhook-6KVPOX3O.js +162 -0
- package/package.json +62 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLogger
|
|
3
|
+
} from "./chunk-RCUFIYQZ.js";
|
|
4
|
+
|
|
5
|
+
// src/deploy/webhook.ts
|
|
6
|
+
function detectPlatform(parsedUrl) {
|
|
7
|
+
const h = parsedUrl.hostname.toLowerCase();
|
|
8
|
+
if (h === "open.feishu.cn" || h === "open.larksuite.com") return "feishu";
|
|
9
|
+
if (h === "api.telegram.org") return "telegram";
|
|
10
|
+
if (h === "discord.com" || h === "discordapp.com") return "discord";
|
|
11
|
+
if (h === "hooks.slack.com") return "slack";
|
|
12
|
+
return "generic";
|
|
13
|
+
}
|
|
14
|
+
function buildFeishuPayload(options) {
|
|
15
|
+
const { sessionCount, newSessionIds = [], reportUrl } = options;
|
|
16
|
+
const newCount = newSessionIds.length;
|
|
17
|
+
const actionBtn = reportUrl ? [{
|
|
18
|
+
tag: "button",
|
|
19
|
+
text: { content: "View Report", tag: "plain_text" },
|
|
20
|
+
url: reportUrl,
|
|
21
|
+
type: "primary"
|
|
22
|
+
}] : [];
|
|
23
|
+
return {
|
|
24
|
+
msg_type: "interactive",
|
|
25
|
+
card: {
|
|
26
|
+
header: {
|
|
27
|
+
title: { content: "MemoryTree Report Updated", tag: "plain_text" },
|
|
28
|
+
template: "blue"
|
|
29
|
+
},
|
|
30
|
+
elements: [
|
|
31
|
+
{
|
|
32
|
+
tag: "div",
|
|
33
|
+
text: {
|
|
34
|
+
tag: "lark_md",
|
|
35
|
+
content: `**Sessions:** ${sessionCount} total${newCount ? `, ${newCount} new` : ""}`
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
...actionBtn.length > 0 ? [{ tag: "action", actions: actionBtn }] : []
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function buildTelegramPayload(options) {
|
|
44
|
+
const { sessionCount, newSessionIds = [], reportUrl } = options;
|
|
45
|
+
const newCount = newSessionIds.length;
|
|
46
|
+
let text = `\u{1F4CA} *MemoryTree Report Updated*
|
|
47
|
+
|
|
48
|
+
Sessions: ${sessionCount} total`;
|
|
49
|
+
if (newCount > 0) text += `, ${newCount} new`;
|
|
50
|
+
if (reportUrl) text += `
|
|
51
|
+
[View Report](${reportUrl})`;
|
|
52
|
+
return { text, parse_mode: "Markdown" };
|
|
53
|
+
}
|
|
54
|
+
function buildDiscordPayload(options) {
|
|
55
|
+
const { sessionCount, newSessionIds = [], reportUrl } = options;
|
|
56
|
+
const newCount = newSessionIds.length;
|
|
57
|
+
let content = `\u{1F4CA} **MemoryTree Report Updated** \u2014 ${sessionCount} sessions total`;
|
|
58
|
+
if (newCount > 0) content += `, ${newCount} new`;
|
|
59
|
+
if (reportUrl) content += ` \xB7 [View Report](${reportUrl})`;
|
|
60
|
+
return { content };
|
|
61
|
+
}
|
|
62
|
+
function buildSlackPayload(options) {
|
|
63
|
+
const { sessionCount, newSessionIds = [], reportUrl } = options;
|
|
64
|
+
const newCount = newSessionIds.length;
|
|
65
|
+
let text = `\u{1F4CA} MemoryTree Report Updated \u2014 ${sessionCount} sessions total`;
|
|
66
|
+
if (newCount > 0) text += `, ${newCount} new`;
|
|
67
|
+
if (reportUrl) text += ` \xB7 <${reportUrl}|View Report>`;
|
|
68
|
+
return { text };
|
|
69
|
+
}
|
|
70
|
+
function buildGenericPayload(options) {
|
|
71
|
+
return {
|
|
72
|
+
event: "memorytree.report.updated",
|
|
73
|
+
session_count: options.sessionCount,
|
|
74
|
+
new_session_count: options.newSessionIds?.length ?? 0,
|
|
75
|
+
new_session_ids: options.newSessionIds ?? [],
|
|
76
|
+
report_url: options.reportUrl ?? ""
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var PRIVATE_IPV4_RE = /^(localhost|127\.\d+\.\d+\.\d+|0\.0\.0\.0|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|169\.254\.\d+\.\d+)$/i;
|
|
80
|
+
function isPrivateIPv6(hostname) {
|
|
81
|
+
const h = hostname.toLowerCase().replace(/^\[|]$/g, "");
|
|
82
|
+
if (h === "::1") return true;
|
|
83
|
+
if (/^fe[89ab]/i.test(h)) return true;
|
|
84
|
+
if (h.startsWith("fc") || h.startsWith("fd")) return true;
|
|
85
|
+
if (h.startsWith("::ffff:")) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function validateWebhookUrl(raw) {
|
|
89
|
+
let url;
|
|
90
|
+
try {
|
|
91
|
+
url = new URL(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error(`Invalid webhook URL: ${raw}`);
|
|
94
|
+
}
|
|
95
|
+
if (url.protocol !== "https:") {
|
|
96
|
+
throw new Error(`Webhook URL must use https: (got ${url.protocol})`);
|
|
97
|
+
}
|
|
98
|
+
if (PRIVATE_IPV4_RE.test(url.hostname) || isPrivateIPv6(url.hostname)) {
|
|
99
|
+
throw new Error(`Webhook URL hostname is private/loopback: ${url.hostname}`);
|
|
100
|
+
}
|
|
101
|
+
return url;
|
|
102
|
+
}
|
|
103
|
+
async function sendWebhook(options) {
|
|
104
|
+
if (!options.url) return;
|
|
105
|
+
const logger = getLogger();
|
|
106
|
+
let parsedUrl;
|
|
107
|
+
try {
|
|
108
|
+
parsedUrl = validateWebhookUrl(options.url);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
logger.warn(`[webhook] Rejected URL: ${String(err)}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const platform = detectPlatform(parsedUrl);
|
|
114
|
+
const payloadMap = {
|
|
115
|
+
feishu: buildFeishuPayload,
|
|
116
|
+
telegram: buildTelegramPayload,
|
|
117
|
+
discord: buildDiscordPayload,
|
|
118
|
+
slack: buildSlackPayload,
|
|
119
|
+
generic: buildGenericPayload
|
|
120
|
+
};
|
|
121
|
+
const payload = payloadMap[platform](options);
|
|
122
|
+
try {
|
|
123
|
+
const { default: https } = await import("https");
|
|
124
|
+
const body = JSON.stringify(payload);
|
|
125
|
+
await new Promise((resolve, reject) => {
|
|
126
|
+
const url = parsedUrl;
|
|
127
|
+
const req = https.request(
|
|
128
|
+
{
|
|
129
|
+
hostname: url.hostname,
|
|
130
|
+
port: url.port || 443,
|
|
131
|
+
path: url.pathname + url.search,
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
"Content-Length": Buffer.byteLength(body)
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
(res) => {
|
|
139
|
+
res.resume();
|
|
140
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
141
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
142
|
+
} else {
|
|
143
|
+
resolve();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
req.on("error", reject);
|
|
148
|
+
req.setTimeout(5e3, () => {
|
|
149
|
+
req.destroy();
|
|
150
|
+
reject(new Error("timeout"));
|
|
151
|
+
});
|
|
152
|
+
req.write(body);
|
|
153
|
+
req.end();
|
|
154
|
+
});
|
|
155
|
+
logger.info(`[webhook] Sent ${platform} notification`);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
logger.warn(`[webhook] Failed to send notification: ${String(err)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
sendWebhook
|
|
162
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openmnemo/report",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenMnemo report generation — builds a static HTML dashboard from Memory/",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/openmnemo/openmnemo.git",
|
|
11
|
+
"directory": "packages/report"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/openmnemo/openmnemo#readme",
|
|
14
|
+
"author": "OpenMnemo Contributors",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openmnemo",
|
|
18
|
+
"memory",
|
|
19
|
+
"report",
|
|
20
|
+
"html",
|
|
21
|
+
"dashboard"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@openmnemo/types": "0.2.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"tsup": "^8.4.0",
|
|
29
|
+
"typescript": "^5.7.3",
|
|
30
|
+
"vitest": "^3.1.1",
|
|
31
|
+
"@anthropic-ai/sdk": "^0.39.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"import": "./dist/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@anthropic-ai/sdk": "^0.39.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"@anthropic-ai/sdk": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"lint": "eslint src tests",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
61
|
+
}
|
|
62
|
+
}
|