@aaroncql/pim-agent 0.0.1
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/README.md +212 -0
- package/bin/pim.ts +109 -0
- package/package.json +49 -0
- package/src/extensions/_init/index.ts +109 -0
- package/src/extensions/bash/capture.test.ts +126 -0
- package/src/extensions/bash/capture.ts +80 -0
- package/src/extensions/bash/format.test.ts +240 -0
- package/src/extensions/bash/format.ts +76 -0
- package/src/extensions/bash/index.ts +86 -0
- package/src/extensions/bash/run.test.ts +262 -0
- package/src/extensions/bash/run.ts +207 -0
- package/src/extensions/bash/schema.ts +54 -0
- package/src/extensions/command-picker/index.ts +52 -0
- package/src/extensions/command-picker/ranker.test.ts +46 -0
- package/src/extensions/command-picker/ranker.ts +17 -0
- package/src/extensions/edit/edit.test.ts +285 -0
- package/src/extensions/edit/edit.ts +382 -0
- package/src/extensions/edit/index.ts +54 -0
- package/src/extensions/edit/schema.ts +37 -0
- package/src/extensions/file-picker/catalog.test.ts +263 -0
- package/src/extensions/file-picker/catalog.ts +219 -0
- package/src/extensions/file-picker/index.test.ts +168 -0
- package/src/extensions/file-picker/index.ts +119 -0
- package/src/extensions/file-picker/ranker.test.ts +94 -0
- package/src/extensions/file-picker/ranker.ts +76 -0
- package/src/extensions/footer/git.test.ts +76 -0
- package/src/extensions/footer/git.ts +87 -0
- package/src/extensions/footer/index.test.ts +161 -0
- package/src/extensions/footer/index.ts +148 -0
- package/src/extensions/footer/powerline.ts +87 -0
- package/src/extensions/footer/segments.test.ts +164 -0
- package/src/extensions/footer/segments.ts +234 -0
- package/src/extensions/glob/glob.test.ts +171 -0
- package/src/extensions/glob/glob.ts +34 -0
- package/src/extensions/glob/index.test.ts +68 -0
- package/src/extensions/glob/index.ts +136 -0
- package/src/extensions/glob/render.test.ts +126 -0
- package/src/extensions/glob/render.ts +74 -0
- package/src/extensions/glob/schema.ts +52 -0
- package/src/extensions/grep/grep.test.ts +387 -0
- package/src/extensions/grep/grep.ts +215 -0
- package/src/extensions/grep/index.test.ts +68 -0
- package/src/extensions/grep/index.ts +158 -0
- package/src/extensions/grep/render.test.ts +269 -0
- package/src/extensions/grep/render.ts +243 -0
- package/src/extensions/grep/schema.ts +92 -0
- package/src/extensions/read/index.ts +84 -0
- package/src/extensions/read/read.test.ts +177 -0
- package/src/extensions/read/read.ts +206 -0
- package/src/extensions/read/render.test.ts +61 -0
- package/src/extensions/read/render.ts +33 -0
- package/src/extensions/read/schema.ts +27 -0
- package/src/extensions/subagent/index.test.ts +44 -0
- package/src/extensions/subagent/index.ts +30 -0
- package/src/extensions/subagent/render.test.ts +292 -0
- package/src/extensions/subagent/render.ts +359 -0
- package/src/extensions/subagent/schema.ts +9 -0
- package/src/extensions/subagent/subagent.test.ts +315 -0
- package/src/extensions/subagent/subagent.ts +418 -0
- package/src/extensions/system-prompt/index.ts +28 -0
- package/src/extensions/system-prompt/prompt.test.ts +64 -0
- package/src/extensions/system-prompt/prompt.ts +213 -0
- package/src/extensions/todo/index.test.ts +244 -0
- package/src/extensions/todo/index.ts +122 -0
- package/src/extensions/todo/render.test.ts +180 -0
- package/src/extensions/todo/render.ts +172 -0
- package/src/extensions/todo/schema.ts +24 -0
- package/src/extensions/todo/todo.test.ts +222 -0
- package/src/extensions/todo/todo.ts +188 -0
- package/src/extensions/tps/index.test.ts +254 -0
- package/src/extensions/tps/index.ts +136 -0
- package/src/extensions/web-fetch/JinaReaderClient.ts +230 -0
- package/src/extensions/web-fetch/WebViewFetchClient.ts +186 -0
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +119 -0
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +511 -0
- package/src/extensions/web-fetch/fetch.test.ts +244 -0
- package/src/extensions/web-fetch/fetch.ts +249 -0
- package/src/extensions/web-fetch/index.ts +107 -0
- package/src/extensions/web-fetch/render.test.ts +56 -0
- package/src/extensions/web-fetch/render.ts +39 -0
- package/src/extensions/web-fetch/schema.ts +23 -0
- package/src/extensions/web-search/ExaMcpClient.test.ts +143 -0
- package/src/extensions/web-search/ExaMcpClient.ts +258 -0
- package/src/extensions/web-search/index.ts +118 -0
- package/src/extensions/web-search/render.test.ts +21 -0
- package/src/extensions/web-search/render.ts +9 -0
- package/src/extensions/web-search/schema.ts +21 -0
- package/src/extensions/web-search/search.test.ts +53 -0
- package/src/extensions/web-search/search.ts +23 -0
- package/src/extensions/working-indicator/index.test.ts +21 -0
- package/src/extensions/working-indicator/index.ts +77 -0
- package/src/extensions/write/index.ts +76 -0
- package/src/extensions/write/render.test.ts +64 -0
- package/src/extensions/write/schema.ts +14 -0
- package/src/extensions/write/write.test.ts +108 -0
- package/src/extensions/write/write.ts +104 -0
- package/src/shared/DiffLines.test.ts +193 -0
- package/src/shared/DiffLines.ts +307 -0
- package/src/shared/DiffRenderer.test.ts +206 -0
- package/src/shared/DiffRenderer.ts +396 -0
- package/src/shared/DiffView.ts +199 -0
- package/src/shared/EditMatcher.test.ts +123 -0
- package/src/shared/EditMatcher.ts +826 -0
- package/src/shared/FileScanner.test.ts +158 -0
- package/src/shared/FileScanner.ts +41 -0
- package/src/shared/Fs.ts +46 -0
- package/src/shared/FsErrors.ts +72 -0
- package/src/shared/FuzzyMatcher.test.ts +114 -0
- package/src/shared/FuzzyMatcher.ts +73 -0
- package/src/shared/GitignoreFilter.test.ts +64 -0
- package/src/shared/GitignoreFilter.ts +142 -0
- package/src/shared/GlobExclusions.ts +23 -0
- package/src/shared/Levenshtein.ts +33 -0
- package/src/shared/Lines.test.ts +25 -0
- package/src/shared/Lines.ts +77 -0
- package/src/shared/McpClient.test.ts +235 -0
- package/src/shared/McpClient.ts +406 -0
- package/src/shared/OutputBudget.test.ts +99 -0
- package/src/shared/OutputBudget.ts +79 -0
- package/src/shared/Paths.test.ts +51 -0
- package/src/shared/Paths.ts +52 -0
- package/src/shared/PimSettings.test.ts +90 -0
- package/src/shared/PimSettings.ts +124 -0
- package/src/shared/Renderer.test.ts +190 -0
- package/src/shared/Renderer.ts +256 -0
- package/src/shared/SpillCache.test.ts +94 -0
- package/src/shared/SpillCache.ts +89 -0
- package/src/shared/Tools.test.ts +392 -0
- package/src/shared/Tools.ts +636 -0
- package/src/telegram/Bot.ts +198 -0
- package/src/telegram/Commands.ts +721 -0
- package/src/telegram/Config.test.ts +275 -0
- package/src/telegram/Config.ts +162 -0
- package/src/telegram/Markdown.test.ts +143 -0
- package/src/telegram/Markdown.ts +177 -0
- package/src/telegram/Message.ts +211 -0
- package/src/telegram/Renderer.test.ts +216 -0
- package/src/telegram/Renderer.ts +713 -0
- package/src/telegram/SendFileSchema.ts +19 -0
- package/src/telegram/SendFileTool.ts +94 -0
- package/src/telegram/Session.ts +579 -0
- package/src/telegram/SessionRegistry.test.ts +89 -0
- package/src/telegram/SessionRegistry.ts +170 -0
- package/src/telegram/Supervisor.ts +357 -0
- package/src/telegram/TaskScheduler.test.ts +278 -0
- package/src/telegram/TaskScheduler.ts +293 -0
- package/src/telegram/TaskSchema.ts +88 -0
- package/src/telegram/TaskStore.ts +73 -0
- package/src/telegram/TaskTool.test.ts +179 -0
- package/src/telegram/TaskTool.ts +159 -0
- package/src/telegram/TypingIndicator.ts +43 -0
- package/src/telegram/index.ts +32 -0
- package/src/themes/pim-dark.json +84 -0
- package/src/themes/pim-light.json +84 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
export type MarkdownSnapshotTextNode = {
|
|
2
|
+
readonly type: "text";
|
|
3
|
+
readonly text: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type MarkdownSnapshotElementNode = {
|
|
7
|
+
readonly type: "element";
|
|
8
|
+
readonly tagName: string;
|
|
9
|
+
readonly hidden?: boolean;
|
|
10
|
+
readonly ariaHidden?: string | null;
|
|
11
|
+
readonly attributes?: readonly {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly value: string;
|
|
14
|
+
}[];
|
|
15
|
+
readonly children?: readonly MarkdownSnapshotNode[];
|
|
16
|
+
readonly textContent?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type MarkdownSnapshotNode =
|
|
20
|
+
| MarkdownSnapshotTextNode
|
|
21
|
+
| MarkdownSnapshotElementNode;
|
|
22
|
+
|
|
23
|
+
type BrowserMarkdownSnapshotRenderer = (
|
|
24
|
+
root: MarkdownSnapshotNode,
|
|
25
|
+
baseUrl: string
|
|
26
|
+
) => string;
|
|
27
|
+
|
|
28
|
+
type BrowserMarkdownSnapshot = {
|
|
29
|
+
readonly title: string;
|
|
30
|
+
readonly url: string;
|
|
31
|
+
readonly content: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function createMarkdownSnapshotScript(): string {
|
|
35
|
+
return `(${captureMarkdownSnapshot.toString()})(${renderMarkdownSnapshotTree.toString()})`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function captureMarkdownSnapshot(
|
|
39
|
+
renderMarkdownSnapshotTree: BrowserMarkdownSnapshotRenderer
|
|
40
|
+
): BrowserMarkdownSnapshot {
|
|
41
|
+
function textFallback(): BrowserMarkdownSnapshot {
|
|
42
|
+
const content = (document.body?.innerText || "")
|
|
43
|
+
.replace(/[ \t]+/g, " ")
|
|
44
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
45
|
+
.trim();
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
title: document.title,
|
|
49
|
+
url: location.href,
|
|
50
|
+
content,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function serializeNode(node: Node): MarkdownSnapshotNode | undefined {
|
|
55
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
56
|
+
return { type: "text", text: node.textContent || "" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!(node instanceof HTMLElement)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tagName = node.tagName.toLowerCase();
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
[
|
|
67
|
+
"script",
|
|
68
|
+
"style",
|
|
69
|
+
"noscript",
|
|
70
|
+
"svg",
|
|
71
|
+
"canvas",
|
|
72
|
+
"template",
|
|
73
|
+
"iframe",
|
|
74
|
+
].includes(tagName)
|
|
75
|
+
) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
type: "element",
|
|
81
|
+
tagName,
|
|
82
|
+
hidden: Boolean(node.hidden) || node.hasAttribute("hidden"),
|
|
83
|
+
ariaHidden: node.getAttribute("aria-hidden"),
|
|
84
|
+
attributes: ["href", "alt"]
|
|
85
|
+
.map((name) => ({ name, value: node.getAttribute(name) }))
|
|
86
|
+
.filter(
|
|
87
|
+
(
|
|
88
|
+
attribute
|
|
89
|
+
): attribute is { readonly name: string; readonly value: string } =>
|
|
90
|
+
attribute.value !== null
|
|
91
|
+
),
|
|
92
|
+
children: Array.from(node.childNodes)
|
|
93
|
+
.map(serializeNode)
|
|
94
|
+
.filter((child): child is MarkdownSnapshotNode => child !== undefined),
|
|
95
|
+
textContent: node.textContent || "",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const sourceRoot = document.body;
|
|
101
|
+
|
|
102
|
+
if (!(sourceRoot instanceof HTMLElement)) {
|
|
103
|
+
return textFallback();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const root = serializeNode(sourceRoot);
|
|
107
|
+
|
|
108
|
+
if (root === undefined) {
|
|
109
|
+
return textFallback();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
title: document.title,
|
|
114
|
+
url: location.href,
|
|
115
|
+
content: renderMarkdownSnapshotTree(root, location.href),
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
return textFallback();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function renderMarkdownSnapshotTree(
|
|
123
|
+
root: MarkdownSnapshotNode,
|
|
124
|
+
baseUrl: string
|
|
125
|
+
): string {
|
|
126
|
+
const blockTags = new Set([
|
|
127
|
+
"address",
|
|
128
|
+
"article",
|
|
129
|
+
"aside",
|
|
130
|
+
"blockquote",
|
|
131
|
+
"dd",
|
|
132
|
+
"details",
|
|
133
|
+
"dialog",
|
|
134
|
+
"div",
|
|
135
|
+
"dl",
|
|
136
|
+
"dt",
|
|
137
|
+
"fieldset",
|
|
138
|
+
"figcaption",
|
|
139
|
+
"figure",
|
|
140
|
+
"footer",
|
|
141
|
+
"form",
|
|
142
|
+
"h1",
|
|
143
|
+
"h2",
|
|
144
|
+
"h3",
|
|
145
|
+
"h4",
|
|
146
|
+
"h5",
|
|
147
|
+
"h6",
|
|
148
|
+
"header",
|
|
149
|
+
"hr",
|
|
150
|
+
"li",
|
|
151
|
+
"main",
|
|
152
|
+
"nav",
|
|
153
|
+
"ol",
|
|
154
|
+
"p",
|
|
155
|
+
"pre",
|
|
156
|
+
"section",
|
|
157
|
+
"table",
|
|
158
|
+
"ul",
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
function renderNode(node: MarkdownSnapshotNode, depth: number): string {
|
|
162
|
+
if (node.type === "text") {
|
|
163
|
+
return normalizeInline(node.text || "");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (node.type !== "element") {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const tag = String(node.tagName || "").toLowerCase();
|
|
171
|
+
|
|
172
|
+
if (node.hidden === true || node.ariaHidden === "true") {
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
switch (tag) {
|
|
177
|
+
case "h1":
|
|
178
|
+
return block("# " + renderInlineChildren(node));
|
|
179
|
+
case "h2":
|
|
180
|
+
return block("## " + renderInlineChildren(node));
|
|
181
|
+
case "h3":
|
|
182
|
+
return block("### " + renderInlineChildren(node));
|
|
183
|
+
case "h4":
|
|
184
|
+
return block("#### " + renderInlineChildren(node));
|
|
185
|
+
case "h5":
|
|
186
|
+
return block("##### " + renderInlineChildren(node));
|
|
187
|
+
case "h6":
|
|
188
|
+
return block("###### " + renderInlineChildren(node));
|
|
189
|
+
case "p":
|
|
190
|
+
return block(renderInlineChildren(node));
|
|
191
|
+
case "br":
|
|
192
|
+
return "\n";
|
|
193
|
+
case "hr":
|
|
194
|
+
return "\n---\n\n";
|
|
195
|
+
case "strong":
|
|
196
|
+
case "b":
|
|
197
|
+
return wrapInline("**", renderInlineChildren(node));
|
|
198
|
+
case "em":
|
|
199
|
+
case "i":
|
|
200
|
+
return wrapInline("*", renderInlineChildren(node));
|
|
201
|
+
case "code":
|
|
202
|
+
return renderCode(node);
|
|
203
|
+
case "pre":
|
|
204
|
+
return renderPre(node);
|
|
205
|
+
case "a":
|
|
206
|
+
return renderLink(node);
|
|
207
|
+
case "img":
|
|
208
|
+
return renderImage(node);
|
|
209
|
+
case "ul":
|
|
210
|
+
return renderList(node, false, depth);
|
|
211
|
+
case "ol":
|
|
212
|
+
return renderList(node, true, depth);
|
|
213
|
+
case "blockquote":
|
|
214
|
+
return renderBlockquote(node, depth);
|
|
215
|
+
case "table":
|
|
216
|
+
return renderTable(node);
|
|
217
|
+
case "thead":
|
|
218
|
+
case "tbody":
|
|
219
|
+
case "tfoot":
|
|
220
|
+
case "tr":
|
|
221
|
+
case "th":
|
|
222
|
+
case "td":
|
|
223
|
+
return "";
|
|
224
|
+
default: {
|
|
225
|
+
const rendered = renderChildren(node, depth);
|
|
226
|
+
return blockTags.has(tag) ? block(rendered) : rendered;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderChildren(
|
|
232
|
+
node: MarkdownSnapshotElementNode,
|
|
233
|
+
depth: number
|
|
234
|
+
): string {
|
|
235
|
+
return joinMarkdown(
|
|
236
|
+
getChildren(node).map((child) => renderNode(child, depth))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderInlineChildren(node: MarkdownSnapshotElementNode): string {
|
|
241
|
+
return normalizeInline(
|
|
242
|
+
getChildren(node)
|
|
243
|
+
.map((child) => renderInlineNode(child))
|
|
244
|
+
.filter((part) => part.trim().length > 0)
|
|
245
|
+
.join(" ")
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function renderInlineNode(node: MarkdownSnapshotNode): string {
|
|
250
|
+
if (node.type === "text") {
|
|
251
|
+
return normalizeInline(node.text || "");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (node.type !== "element") {
|
|
255
|
+
return "";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const tag = String(node.tagName || "").toLowerCase();
|
|
259
|
+
|
|
260
|
+
if (node.hidden === true || node.ariaHidden === "true") {
|
|
261
|
+
return "";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
switch (tag) {
|
|
265
|
+
case "br":
|
|
266
|
+
return "\n";
|
|
267
|
+
case "strong":
|
|
268
|
+
case "b":
|
|
269
|
+
return wrapInline("**", renderInlineChildren(node));
|
|
270
|
+
case "em":
|
|
271
|
+
case "i":
|
|
272
|
+
return wrapInline("*", renderInlineChildren(node));
|
|
273
|
+
case "code":
|
|
274
|
+
return renderCode(node);
|
|
275
|
+
case "a":
|
|
276
|
+
return renderLink(node);
|
|
277
|
+
case "img":
|
|
278
|
+
return renderImage(node);
|
|
279
|
+
default:
|
|
280
|
+
return renderInlineChildren(node);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function renderList(
|
|
285
|
+
list: MarkdownSnapshotElementNode,
|
|
286
|
+
ordered: boolean,
|
|
287
|
+
depth: number
|
|
288
|
+
): string {
|
|
289
|
+
const items = getChildren(list).filter(
|
|
290
|
+
(child): child is MarkdownSnapshotElementNode =>
|
|
291
|
+
child.type === "element" && child.tagName.toLowerCase() === "li"
|
|
292
|
+
);
|
|
293
|
+
const indent = " ".repeat(depth);
|
|
294
|
+
const lines: string[] = [];
|
|
295
|
+
|
|
296
|
+
items.forEach((item, index) => {
|
|
297
|
+
const marker = ordered ? String(index + 1) + ". " : "- ";
|
|
298
|
+
const body = renderChildren(item, depth + 1).trim();
|
|
299
|
+
|
|
300
|
+
if (body.length === 0) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const bodyLines = body.split("\n");
|
|
305
|
+
lines.push(indent + marker + bodyLines[0]);
|
|
306
|
+
|
|
307
|
+
for (const line of bodyLines.slice(1)) {
|
|
308
|
+
lines.push(line.trim().length === 0 ? "" : indent + " " + line);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return lines.length === 0 ? "" : "\n" + lines.join("\n") + "\n\n";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function renderBlockquote(
|
|
316
|
+
node: MarkdownSnapshotElementNode,
|
|
317
|
+
depth: number
|
|
318
|
+
): string {
|
|
319
|
+
const rendered = renderChildren(node, depth).trim();
|
|
320
|
+
|
|
321
|
+
if (rendered.length === 0) {
|
|
322
|
+
return "";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
rendered
|
|
327
|
+
.split("\n")
|
|
328
|
+
.map((line) => (line.trim().length === 0 ? ">" : "> " + line))
|
|
329
|
+
.join("\n") + "\n\n"
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function renderTable(table: MarkdownSnapshotElementNode): string {
|
|
334
|
+
const rows = descendants(table, ["tr"])
|
|
335
|
+
.map((row) =>
|
|
336
|
+
descendants(row, ["th", "td"]).map((cell) =>
|
|
337
|
+
renderInlineChildren(cell).replace(/\|/g, "\\|")
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
.filter((row) => row.length > 0);
|
|
341
|
+
|
|
342
|
+
if (rows.length === 0) {
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
347
|
+
const normalizedRows = rows.map((row) => padRow(row, width));
|
|
348
|
+
const header = normalizedRows[0] ?? [];
|
|
349
|
+
const body = normalizedRows.slice(1);
|
|
350
|
+
const lines = [
|
|
351
|
+
"| " + header.join(" | ") + " |",
|
|
352
|
+
"| " + header.map(() => "---").join(" | ") + " |",
|
|
353
|
+
...body.map((row) => "| " + row.join(" | ") + " |"),
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
return lines.join("\n") + "\n\n";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function padRow(row: readonly string[], width: number): string[] {
|
|
360
|
+
return Array.from({ length: width }, (_, index) => row[index] || "");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function renderPre(node: MarkdownSnapshotElementNode): string {
|
|
364
|
+
const text = (node.textContent || "").replace(/^\n+|\n+$/g, "");
|
|
365
|
+
|
|
366
|
+
if (text.length === 0) {
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const fence = String.fromCharCode(96).repeat(3);
|
|
371
|
+
return fence + "\n" + text + "\n" + fence + "\n\n";
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function renderCode(node: MarkdownSnapshotElementNode): string {
|
|
375
|
+
const backtick = String.fromCharCode(96);
|
|
376
|
+
const text = normalizeInline(node.textContent || textContent(node)).replace(
|
|
377
|
+
new RegExp(backtick, "g"),
|
|
378
|
+
"\\" + backtick
|
|
379
|
+
);
|
|
380
|
+
return text.length === 0 ? "" : backtick + text + backtick;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function renderLink(node: MarkdownSnapshotElementNode): string {
|
|
384
|
+
const text = renderInlineChildren(node);
|
|
385
|
+
const href = getAttribute(node, "href");
|
|
386
|
+
|
|
387
|
+
if (text.length === 0) {
|
|
388
|
+
return "";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (
|
|
392
|
+
href === undefined ||
|
|
393
|
+
href.trim().length === 0 ||
|
|
394
|
+
href.trim().toLowerCase().startsWith("javascript:")
|
|
395
|
+
) {
|
|
396
|
+
return text;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
return (
|
|
401
|
+
"[" +
|
|
402
|
+
text.replace(/[[\]]/g, "") +
|
|
403
|
+
"](" +
|
|
404
|
+
new URL(href, baseUrl).href +
|
|
405
|
+
")"
|
|
406
|
+
);
|
|
407
|
+
} catch {
|
|
408
|
+
return text;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function renderImage(node: MarkdownSnapshotElementNode): string {
|
|
413
|
+
const alt = normalizeInline(getAttribute(node, "alt") || "");
|
|
414
|
+
|
|
415
|
+
if (alt.length === 0) {
|
|
416
|
+
return "";
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return "![" + alt.replace(/[[\]]/g, "") + "]";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function block(text: string): string {
|
|
423
|
+
const trimmed = text.trim();
|
|
424
|
+
return trimmed.length === 0 ? "" : trimmed + "\n\n";
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function wrapInline(marker: string, text: string): string {
|
|
428
|
+
return text.length === 0 ? "" : marker + text + marker;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function normalizeInline(text: string): string {
|
|
432
|
+
return String(text).replace(/\s+/g, " ");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function joinMarkdown(parts: readonly string[]): string {
|
|
436
|
+
let output = "";
|
|
437
|
+
|
|
438
|
+
for (const part of parts) {
|
|
439
|
+
if (part.length === 0) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
output.length > 0 &&
|
|
445
|
+
!output.endsWith("\n") &&
|
|
446
|
+
!part.startsWith("\n")
|
|
447
|
+
) {
|
|
448
|
+
output += " ";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
output += part;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return output;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getChildren(
|
|
458
|
+
node: MarkdownSnapshotElementNode
|
|
459
|
+
): readonly MarkdownSnapshotNode[] {
|
|
460
|
+
return Array.isArray(node.children) ? node.children : [];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function getAttribute(
|
|
464
|
+
node: MarkdownSnapshotElementNode,
|
|
465
|
+
name: string
|
|
466
|
+
): string | undefined {
|
|
467
|
+
const attributes = Array.isArray(node.attributes) ? node.attributes : [];
|
|
468
|
+
const attribute = attributes.find((item) => item.name === name);
|
|
469
|
+
return typeof attribute?.value === "string" ? attribute.value : undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function textContent(node: MarkdownSnapshotNode): string {
|
|
473
|
+
if (node.type === "text") {
|
|
474
|
+
return node.text || "";
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return getChildren(node)
|
|
478
|
+
.map((child) => textContent(child))
|
|
479
|
+
.join("");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function descendants(
|
|
483
|
+
node: MarkdownSnapshotElementNode,
|
|
484
|
+
tags: readonly string[]
|
|
485
|
+
): MarkdownSnapshotElementNode[] {
|
|
486
|
+
const matches: MarkdownSnapshotElementNode[] = [];
|
|
487
|
+
const tagSet = new Set(tags);
|
|
488
|
+
|
|
489
|
+
function visit(current: MarkdownSnapshotElementNode): void {
|
|
490
|
+
for (const child of getChildren(current)) {
|
|
491
|
+
if (child.type !== "element") {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (tagSet.has(String(child.tagName || "").toLowerCase())) {
|
|
496
|
+
matches.push(child);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
visit(child);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
visit(node);
|
|
504
|
+
return matches;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return renderNode(root, 0)
|
|
508
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
509
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
510
|
+
.trim();
|
|
511
|
+
}
|