birdclaw 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +10 -8
- package/bin/birdclaw.mjs +4 -66
- package/dist/cli/birdclaw.js +28164 -0
- package/dist/client/assets/AvatarChip-DLoqM5xD.js +1 -0
- package/dist/client/assets/ProfilePreview-DQX6_xc3.js +1 -0
- package/dist/client/assets/SavedTimelineView-BhSiVyR1.js +1 -0
- package/dist/client/assets/SmartTimestamp-BR0-9se2.js +1 -0
- package/dist/client/assets/TimelineRouteFrame-9NDLnXzH.js +1 -0
- package/dist/client/assets/blocks-CFp_njvG.js +1 -0
- package/dist/client/assets/bookmarks-LbYOsoKf.js +1 -0
- package/dist/client/assets/chunk-C0phanGV.js +1 -0
- package/dist/client/assets/circle-check-C_Vq3ElD.js +1 -0
- package/dist/client/assets/client-stream-contracts-sR4xMQxY.js +5 -0
- package/dist/client/assets/createLucideIcon-JV8isFFR.js +1 -0
- package/dist/client/assets/data-sources-euTr6T_L.js +1 -0
- package/dist/client/assets/discuss-BWKu_p7B.js +1 -0
- package/dist/client/assets/dms-SLXMtC4v.js +1 -0
- package/dist/client/assets/inbox-BybSI5Vf.js +1 -0
- package/dist/client/assets/index-DZVisyna.js +31 -0
- package/dist/client/assets/index-bpFrQ5yG.css +1 -0
- package/dist/client/assets/likes-dyh9p8y_.js +1 -0
- package/dist/client/assets/links-CIwqK_dX.js +1 -0
- package/dist/client/assets/mapbox-B4zqX7r0.js +2 -0
- package/dist/client/assets/mapbox-gl-DYNcLcll.js +3215 -0
- package/dist/client/assets/mentions-BoVVAIT7.js +1 -0
- package/dist/client/assets/network-map-zRysIY_b.js +2 -0
- package/dist/client/assets/profile-analyze-C_yDeX6-.js +1 -0
- package/dist/client/assets/profile-hydration-client-YEtJsNTv.js +1 -0
- package/dist/client/assets/profiles._handle-DdIVPqU-.js +1 -0
- package/dist/client/assets/query-client-fYu2rzRC.js +17 -0
- package/dist/client/assets/rate-limits-BWPmPOSh.js +1 -0
- package/dist/client/assets/react-dom-DHl6Ug7F.js +1 -0
- package/dist/client/assets/react-nBylq1U5.js +1 -0
- package/dist/client/assets/repeat-2-xCwHM9hD.js +1 -0
- package/dist/client/assets/routes-CiLuYpZr.js +1 -0
- package/dist/client/assets/schemas-I9r6F6ya.js +64 -0
- package/dist/client/assets/search-Dq5kYsxA.js +1 -0
- package/dist/client/assets/sparkles-CQVlvHsZ.js +1 -0
- package/dist/client/assets/styles-B8s3tt3r.css +2 -0
- package/dist/client/assets/today-BoO64YoY.js +1 -0
- package/dist/client/assets/triangle-alert-CBL7s2pf.js +1 -0
- package/dist/client/assets/useTimelineRouteData-BaO3Rna1.js +1 -0
- package/dist/client/assets/users-CHbKlqUr.js +1 -0
- package/dist/server/assets/AvatarChip-zUDAEmjL.js +105 -0
- package/dist/server/assets/FeedState-ntW0Q2GB.js +214 -0
- package/dist/server/assets/ProfileAnalysisStream-L7bs6IHH.js +198 -0
- package/dist/server/assets/ProfilePreview-Cg-AOHBE.js +751 -0
- package/dist/server/assets/SavedTimelineView-BtCq1dHk.js +64 -0
- package/dist/server/assets/SmartTimestamp-DfwE1NGf.js +45 -0
- package/dist/server/assets/SyncNowButton-BOn_IBXS.js +76 -0
- package/dist/server/assets/TimelineRouteFrame-CjMIh63G.js +85 -0
- package/dist/server/assets/TweetRichText-DDywESRF.js +74 -0
- package/dist/server/assets/_tanstack-start-manifest_v-aLWUdbAc.js +148 -0
- package/dist/server/assets/account-selection-CTdFGWRk.js +44 -0
- package/dist/server/assets/blocks-C1mgGTB3.js +299 -0
- package/dist/server/assets/blocks-DSgQghqP.js +17 -0
- package/dist/server/assets/bookmarks-D_YkmCK3.js +14 -0
- package/dist/server/assets/client-stream-contracts-Bq6kMmhh.js +769 -0
- package/dist/server/assets/data-sources-BnA1Q94p.js +180 -0
- package/dist/server/assets/discuss-C1QMBBSk.js +338 -0
- package/dist/server/assets/discuss-DAq6ehL9.js +15 -0
- package/dist/server/assets/dms-BuLj4bp8.js +368 -0
- package/dist/server/assets/dms-DAyQrVM2.js +240 -0
- package/dist/server/assets/empty-plugin-adapters-DzrEkdBF.js +5 -0
- package/dist/server/assets/inbox-CkGOaW1q.js +214 -0
- package/dist/server/assets/inbox-DpWd1EzC.js +146 -0
- package/dist/server/assets/likes-BMm2JWYI.js +14 -0
- package/dist/server/assets/links-Da4tjWtI.js +468 -0
- package/dist/server/assets/links-dqXb35YM.js +240 -0
- package/dist/server/assets/mentions-CEyEcOFw.js +26 -0
- package/dist/server/assets/network-map-D3CzRcC4.js +224 -0
- package/dist/server/assets/network-map-D5DoLOwR.js +582 -0
- package/dist/server/assets/profile-analyze-CGCj4VFd.js +90 -0
- package/dist/server/assets/profile-analyze-DH1TSBkT.js +9 -0
- package/dist/server/assets/profile-hydration-client-GJ4kwJS8.js +133 -0
- package/dist/server/assets/profiles._handle-B5V-huoo.js +12 -0
- package/dist/server/assets/profiles._handle-CJ6rgzt8.js +184 -0
- package/dist/server/assets/query-client-BdauHBgd.js +923 -0
- package/dist/server/assets/rate-limits-VAr2qzYD.js +262 -0
- package/dist/server/assets/route-search-BUlQ5lGM.js +117 -0
- package/dist/server/assets/router-C4cz9ypd.js +17026 -0
- package/dist/server/assets/routes-B9aikOdB.js +26 -0
- package/dist/server/assets/start-FaDOTmto.js +4 -0
- package/dist/server/assets/today-B_TA16QG.js +303 -0
- package/dist/server/assets/today-JkxZXdBt.js +17 -0
- package/dist/server/assets/ui-qbmcMSSX.js +137 -0
- package/{src/components/useDebouncedValue.ts → dist/server/assets/useDebouncedValue-DOnA6Xcy.js} +4 -4
- package/dist/server/assets/useTimelineRouteData-BLXvMGb2.js +1101 -0
- package/dist/server/server.js +5520 -0
- package/package.json +26 -34
- package/playwright.config.ts +0 -35
- package/scripts/browser-perf.mjs +0 -427
- package/scripts/build-docs-site.mjs +0 -967
- package/scripts/docs-site-assets.mjs +0 -311
- package/scripts/run-vitest.mjs +0 -21
- package/scripts/sanitize-node-options.mjs +0 -23
- package/scripts/start-test-server.mjs +0 -42
- package/src/cli/command-context.ts +0 -17
- package/src/cli/register-analysis.ts +0 -500
- package/src/cli/register-compose.ts +0 -40
- package/src/cli/register-graph.ts +0 -132
- package/src/cli/register-inbox.ts +0 -41
- package/src/cli/register-storage.ts +0 -106
- package/src/cli-moderation.ts +0 -210
- package/src/cli.ts +0 -1616
- package/src/components/AccountSwitcher.tsx +0 -178
- package/src/components/AppNav.tsx +0 -118
- package/src/components/AvatarChip.tsx +0 -58
- package/src/components/AvatarPreload.ts +0 -149
- package/src/components/BrandMark.tsx +0 -67
- package/src/components/ConversationThread.tsx +0 -131
- package/src/components/DmWorkspace.tsx +0 -286
- package/src/components/EmbeddedTweetCard.tsx +0 -55
- package/src/components/FeedState.tsx +0 -147
- package/src/components/FloatingPreview.tsx +0 -382
- package/src/components/InboxCard.tsx +0 -152
- package/src/components/LinkPreviewCard.tsx +0 -292
- package/src/components/MarkdownCitations.tsx +0 -680
- package/src/components/MarkdownViewer.tsx +0 -111
- package/src/components/ProfileAnalysisClient.ts +0 -191
- package/src/components/ProfileAnalysisStream.tsx +0 -259
- package/src/components/ProfilePreview.tsx +0 -131
- package/src/components/SavedTimelineView.tsx +0 -155
- package/src/components/SmartTimestamp.tsx +0 -72
- package/src/components/SyncNowButton.tsx +0 -140
- package/src/components/ThemeSlider.tsx +0 -80
- package/src/components/TimelineCard.tsx +0 -533
- package/src/components/TimelineRouteFrame.tsx +0 -186
- package/src/components/TweetArticleCard.tsx +0 -66
- package/src/components/TweetMediaGrid.tsx +0 -185
- package/src/components/TweetRichText.tsx +0 -159
- package/src/components/account-selection.ts +0 -64
- package/src/components/links-controller.ts +0 -162
- package/src/components/links-model.ts +0 -198
- package/src/components/network-map-controller.ts +0 -84
- package/src/components/network-map-model.ts +0 -255
- package/src/components/useTimelineRouteData.ts +0 -193
- package/src/lib/account-sync-job.ts +0 -666
- package/src/lib/actions-transport.ts +0 -300
- package/src/lib/analysis-runtime.ts +0 -238
- package/src/lib/api-client.ts +0 -220
- package/src/lib/api-contracts.ts +0 -328
- package/src/lib/archive-finder.ts +0 -185
- package/src/lib/archive-import-plan.ts +0 -102
- package/src/lib/archive-import.ts +0 -2695
- package/src/lib/authored-live.ts +0 -1027
- package/src/lib/avatar-cache.ts +0 -349
- package/src/lib/backup.ts +0 -2652
- package/src/lib/bird-actions.ts +0 -294
- package/src/lib/bird-command.ts +0 -130
- package/src/lib/bird.ts +0 -1140
- package/src/lib/blocklist.ts +0 -136
- package/src/lib/blocks-write.ts +0 -171
- package/src/lib/blocks.ts +0 -387
- package/src/lib/bookmark-sync-job.ts +0 -536
- package/src/lib/config.ts +0 -224
- package/src/lib/conversation-surface.ts +0 -205
- package/src/lib/data-sources.ts +0 -219
- package/src/lib/database-metrics.ts +0 -88
- package/src/lib/database-migrations.ts +0 -34
- package/src/lib/database-schema.ts +0 -312
- package/src/lib/database-writer.ts +0 -69
- package/src/lib/db.ts +0 -889
- package/src/lib/dm-read-model.ts +0 -533
- package/src/lib/dms-live.ts +0 -870
- package/src/lib/effect-runtime.ts +0 -45
- package/src/lib/follow-graph.ts +0 -1087
- package/src/lib/geocoding.ts +0 -296
- package/src/lib/http-effect.ts +0 -222
- package/src/lib/identity-search-index.ts +0 -369
- package/src/lib/import-repository.ts +0 -138
- package/src/lib/inbox.ts +0 -242
- package/src/lib/link-index.ts +0 -641
- package/src/lib/link-insights.ts +0 -860
- package/src/lib/link-preview-metadata.ts +0 -754
- package/src/lib/live-sync-engine.ts +0 -209
- package/src/lib/live-transport-gateway.ts +0 -128
- package/src/lib/location.ts +0 -137
- package/src/lib/media-fetch.ts +0 -860
- package/src/lib/media-includes.ts +0 -165
- package/src/lib/mention-threads-live.ts +0 -830
- package/src/lib/mentions-export.ts +0 -147
- package/src/lib/mentions-live.ts +0 -1114
- package/src/lib/moderation-target.ts +0 -227
- package/src/lib/moderation-write.ts +0 -131
- package/src/lib/mutes-write.ts +0 -171
- package/src/lib/mutes.ts +0 -84
- package/src/lib/ndjson-stream.ts +0 -106
- package/src/lib/network-map.ts +0 -382
- package/src/lib/openai-response-runtime.ts +0 -251
- package/src/lib/openai.ts +0 -117
- package/src/lib/paginated-sync.ts +0 -93
- package/src/lib/period-digest.ts +0 -1364
- package/src/lib/present.ts +0 -118
- package/src/lib/profile-affiliation-hydration.ts +0 -228
- package/src/lib/profile-affiliations.ts +0 -262
- package/src/lib/profile-analysis.ts +0 -1198
- package/src/lib/profile-bio-entities.ts +0 -318
- package/src/lib/profile-history.ts +0 -164
- package/src/lib/profile-hydration.ts +0 -180
- package/src/lib/profile-replies.ts +0 -77
- package/src/lib/profile-resolver.ts +0 -650
- package/src/lib/queries.ts +0 -6
- package/src/lib/query-actions.ts +0 -437
- package/src/lib/query-client.tsx +0 -47
- package/src/lib/query-read-model-shared.ts +0 -52
- package/src/lib/query-read-models.ts +0 -5
- package/src/lib/query-resource.ts +0 -41
- package/src/lib/query-status.ts +0 -164
- package/src/lib/research.ts +0 -652
- package/src/lib/runtime-services.ts +0 -20
- package/src/lib/search-discussion.ts +0 -812
- package/src/lib/seed.ts +0 -613
- package/src/lib/server-runtime-services.ts +0 -30
- package/src/lib/sqlite.ts +0 -183
- package/src/lib/streaming-ingestion.ts +0 -240
- package/src/lib/sync-cache.ts +0 -70
- package/src/lib/sync-plan.ts +0 -175
- package/src/lib/theme-transition.ts +0 -113
- package/src/lib/theme.tsx +0 -165
- package/src/lib/timeline-collections-live.ts +0 -407
- package/src/lib/timeline-live.ts +0 -302
- package/src/lib/timeline-read-model.ts +0 -1191
- package/src/lib/tweet-account-edges.ts +0 -48
- package/src/lib/tweet-lookup.ts +0 -46
- package/src/lib/tweet-render.ts +0 -430
- package/src/lib/tweet-repository.ts +0 -156
- package/src/lib/tweet-search-live.ts +0 -462
- package/src/lib/types.ts +0 -723
- package/src/lib/ui.ts +0 -529
- package/src/lib/url-expansion-store.ts +0 -110
- package/src/lib/url-expansion.ts +0 -343
- package/src/lib/url-safety.ts +0 -220
- package/src/lib/web-sync.ts +0 -533
- package/src/lib/whois.ts +0 -1080
- package/src/lib/x-profile.ts +0 -365
- package/src/lib/x-web.ts +0 -199
- package/src/lib/xurl-rate-limits.ts +0 -267
- package/src/lib/xurl.ts +0 -1677
- package/src/routeTree.gen.ts +0 -723
- package/src/router.tsx +0 -20
- package/src/routes/__root.tsx +0 -82
- package/src/routes/api/action.tsx +0 -147
- package/src/routes/api/avatar.tsx +0 -50
- package/src/routes/api/blocks.tsx +0 -35
- package/src/routes/api/conversation.tsx +0 -45
- package/src/routes/api/data-sources.tsx +0 -24
- package/src/routes/api/inbox.tsx +0 -43
- package/src/routes/api/link-insights.tsx +0 -85
- package/src/routes/api/link-preview.tsx +0 -47
- package/src/routes/api/network-map.tsx +0 -55
- package/src/routes/api/period-digest.tsx +0 -101
- package/src/routes/api/profile-analysis.tsx +0 -97
- package/src/routes/api/profile-hydrate.tsx +0 -54
- package/src/routes/api/query.tsx +0 -121
- package/src/routes/api/search-discussion.tsx +0 -113
- package/src/routes/api/status.tsx +0 -30
- package/src/routes/api/sync.tsx +0 -108
- package/src/routes/api/xurl-rate-limits.tsx +0 -24
- package/src/routes/blocks.tsx +0 -343
- package/src/routes/bookmarks.tsx +0 -18
- package/src/routes/data-sources.tsx +0 -249
- package/src/routes/discuss.tsx +0 -419
- package/src/routes/dms.tsx +0 -415
- package/src/routes/inbox.tsx +0 -232
- package/src/routes/index.tsx +0 -32
- package/src/routes/likes.tsx +0 -18
- package/src/routes/links.tsx +0 -631
- package/src/routes/mentions.tsx +0 -32
- package/src/routes/network-map.tsx +0 -732
- package/src/routes/profile-analyze.tsx +0 -112
- package/src/routes/profiles.$handle.tsx +0 -228
- package/src/routes/rate-limits.tsx +0 -305
- package/src/routes/today.tsx +0 -511
- package/src/styles.css +0 -257
- package/tsconfig.json +0 -29
- package/vite.config.ts +0 -29
- /package/{public → dist/client}/birdclaw-mark.png +0 -0
- /package/{public → dist/client}/favicon.ico +0 -0
- /package/{public → dist/client}/logo192.png +0 -0
- /package/{public → dist/client}/logo512.png +0 -0
- /package/{public → dist/client}/manifest.json +0 -0
- /package/{public → dist/client}/robots.txt +0 -0
|
@@ -1,967 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
css,
|
|
7
|
-
js,
|
|
8
|
-
preThemeScript,
|
|
9
|
-
themeToggleHtml,
|
|
10
|
-
} from "./docs-site-assets.mjs";
|
|
11
|
-
|
|
12
|
-
const root = process.cwd();
|
|
13
|
-
const docsDir = path.join(root, "docs");
|
|
14
|
-
const outDir = path.join(root, "dist", "docs-site");
|
|
15
|
-
const repoBase = "https://github.com/steipete/birdclaw";
|
|
16
|
-
const repoEditBase = `${repoBase}/edit/main/docs`;
|
|
17
|
-
const cname = readCname();
|
|
18
|
-
const siteBase = cname ? `https://${cname}` : "";
|
|
19
|
-
|
|
20
|
-
const productName = "birdclaw";
|
|
21
|
-
const productTagline = "Local Twitter memory in SQLite";
|
|
22
|
-
const productDescription =
|
|
23
|
-
"birdclaw is a local-first Twitter workspace: archive import, cached live reads, focused triage, and reply flows in one local web app + CLI.";
|
|
24
|
-
const brewInstall = "brew install steipete/tap/birdclaw";
|
|
25
|
-
|
|
26
|
-
const sections = [
|
|
27
|
-
[
|
|
28
|
-
"Start",
|
|
29
|
-
["index.md", "install.md", "auth.md", "quickstart.md", "configuration.md"],
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
"Archive & Sync",
|
|
33
|
-
["archive.md", "sync.md", "media.md", "backup.md", "jobs.md"],
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
"Reading & Triage",
|
|
37
|
-
["search.md", "mentions.md", "dms.md", "inbox.md", "research.md"],
|
|
38
|
-
],
|
|
39
|
-
["Acting", ["compose.md", "moderation.md"]],
|
|
40
|
-
["Reference", ["cli.md", "data-architecture.md", "spec.md"]],
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const buildExcludes = [];
|
|
44
|
-
|
|
45
|
-
fs.rmSync(outDir, { recursive: true, force: true });
|
|
46
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
const allPages = allMarkdown(docsDir).map((file) => {
|
|
49
|
-
const rel = path.relative(docsDir, file).replaceAll(path.sep, "/");
|
|
50
|
-
const raw = fs.readFileSync(file, "utf8");
|
|
51
|
-
const { frontmatter, body } = parseFrontmatter(raw);
|
|
52
|
-
const cleaned = stripStrayDirectives(body);
|
|
53
|
-
const title =
|
|
54
|
-
frontmatter.title ||
|
|
55
|
-
firstHeading(cleaned) ||
|
|
56
|
-
titleize(path.basename(rel, ".md"));
|
|
57
|
-
return {
|
|
58
|
-
file,
|
|
59
|
-
rel,
|
|
60
|
-
title,
|
|
61
|
-
outRel: outPath(rel, frontmatter),
|
|
62
|
-
markdown: cleaned,
|
|
63
|
-
frontmatter,
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const pages = allPages.filter(
|
|
68
|
-
(page) => !buildExcludes.some((re) => re.test(page.rel)),
|
|
69
|
-
);
|
|
70
|
-
const pageMap = new Map(pages.map((page) => [page.rel, page]));
|
|
71
|
-
const permalinkMap = new Map();
|
|
72
|
-
for (const page of pages) {
|
|
73
|
-
if (page.frontmatter.permalink) {
|
|
74
|
-
permalinkMap.set(normalizePermalink(page.frontmatter.permalink), page);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const nav = sections
|
|
79
|
-
.map(([name, rels]) => ({
|
|
80
|
-
name,
|
|
81
|
-
pages: rels.map((rel) => pageMap.get(rel)).filter(Boolean),
|
|
82
|
-
}))
|
|
83
|
-
.filter((section) => section.pages.length);
|
|
84
|
-
|
|
85
|
-
const sectionByRel = new Map();
|
|
86
|
-
for (const section of nav)
|
|
87
|
-
for (const page of section.pages) sectionByRel.set(page.rel, section.name);
|
|
88
|
-
const orderedPages = nav.flatMap((s) => s.pages);
|
|
89
|
-
|
|
90
|
-
for (const page of pages) {
|
|
91
|
-
const html = markdownToHtml(page.markdown, page.rel);
|
|
92
|
-
const toc = tocFromHtml(html);
|
|
93
|
-
const idx = orderedPages.findIndex((p) => p.rel === page.rel);
|
|
94
|
-
const prev = idx > 0 ? orderedPages[idx - 1] : null;
|
|
95
|
-
const next =
|
|
96
|
-
idx >= 0 && idx < orderedPages.length - 1 ? orderedPages[idx + 1] : null;
|
|
97
|
-
const sectionName = sectionByRel.get(page.rel) || "Reference";
|
|
98
|
-
const pageOut = path.join(outDir, page.outRel);
|
|
99
|
-
fs.mkdirSync(path.dirname(pageOut), { recursive: true });
|
|
100
|
-
fs.writeFileSync(
|
|
101
|
-
pageOut,
|
|
102
|
-
layout({ page, html, toc, prev, next, sectionName }),
|
|
103
|
-
"utf8",
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
copyStaticAsset("birdclaw-mark.png");
|
|
108
|
-
copyStaticAsset("favicon.ico");
|
|
109
|
-
copyStaticAsset("social-card.svg");
|
|
110
|
-
copyStaticAsset("social-card.png");
|
|
111
|
-
fs.writeFileSync(path.join(outDir, ".nojekyll"), "", "utf8");
|
|
112
|
-
if (cname) fs.writeFileSync(path.join(outDir, "CNAME"), cname, "utf8");
|
|
113
|
-
validateLinks(outDir);
|
|
114
|
-
fs.writeFileSync(path.join(outDir, "llms.txt"), llmsTxt(), "utf8");
|
|
115
|
-
console.log(`built docs site: ${path.relative(root, outDir)}`);
|
|
116
|
-
|
|
117
|
-
function llmsTxt() {
|
|
118
|
-
const origin = docsOrigin();
|
|
119
|
-
const source = docsSourceUrl();
|
|
120
|
-
const name =
|
|
121
|
-
typeof productName !== "undefined" ? productName : path.basename(root);
|
|
122
|
-
const description =
|
|
123
|
-
typeof productDescription !== "undefined"
|
|
124
|
-
? productDescription
|
|
125
|
-
: `${name} documentation index.`;
|
|
126
|
-
const install = docsInstallHint();
|
|
127
|
-
const docPages = docsLlmsPages().map(
|
|
128
|
-
(page) => `- ${page.title}: ${pageUrl(origin, page.outRel)}`,
|
|
129
|
-
);
|
|
130
|
-
const lines = [
|
|
131
|
-
`# ${name}`,
|
|
132
|
-
"",
|
|
133
|
-
description,
|
|
134
|
-
"",
|
|
135
|
-
"Canonical documentation:",
|
|
136
|
-
...docPages,
|
|
137
|
-
];
|
|
138
|
-
if (install) {
|
|
139
|
-
lines.push("", "Install:", `- ${install}`);
|
|
140
|
-
}
|
|
141
|
-
if (source) {
|
|
142
|
-
lines.push("", `Source: ${source}`);
|
|
143
|
-
}
|
|
144
|
-
lines.push(
|
|
145
|
-
"",
|
|
146
|
-
"Guidance for agents:",
|
|
147
|
-
"- Prefer the canonical documentation URLs above over README excerpts or package metadata.",
|
|
148
|
-
"- Fetch only the pages needed for the current task; this is an index, not a full-site corpus.",
|
|
149
|
-
);
|
|
150
|
-
return `${lines.join("\n")}\n`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function docsLlmsPages() {
|
|
154
|
-
const seen = new Set();
|
|
155
|
-
const ordered = typeof orderedPages !== "undefined" ? orderedPages : [];
|
|
156
|
-
return [...ordered, ...pages].filter(
|
|
157
|
-
(page) => page.outRel && !seen.has(page.outRel) && seen.add(page.outRel),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function docsOrigin() {
|
|
162
|
-
const value =
|
|
163
|
-
(typeof siteBase !== "undefined" && siteBase) ||
|
|
164
|
-
(typeof siteUrl !== "undefined" && siteUrl) ||
|
|
165
|
-
(typeof customDomain !== "undefined" && customDomain
|
|
166
|
-
? `https://${customDomain}`
|
|
167
|
-
: "");
|
|
168
|
-
return value.replace(/\/$/, "");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function docsSourceUrl() {
|
|
172
|
-
if (typeof repoBase !== "undefined") return repoBase;
|
|
173
|
-
if (typeof repoUrl !== "undefined") return repoUrl;
|
|
174
|
-
if (typeof repoEditBase !== "undefined")
|
|
175
|
-
return repoEditBase.replace(/\/edit\/main\/docs\/?$/, "");
|
|
176
|
-
return "";
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function docsInstallHint() {
|
|
180
|
-
if (typeof installCommand !== "undefined") return installCommand;
|
|
181
|
-
if (typeof installLine !== "undefined") return installLine;
|
|
182
|
-
if (typeof installCmd !== "undefined") return installCmd;
|
|
183
|
-
if (typeof installSnippet !== "undefined") return installSnippet;
|
|
184
|
-
if (typeof brewInstall !== "undefined") return brewInstall;
|
|
185
|
-
return "";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function pageUrl(origin, outRel) {
|
|
189
|
-
const normalized =
|
|
190
|
-
outRel === "index.html"
|
|
191
|
-
? ""
|
|
192
|
-
: outRel.replace(/(?:^|\/)index\.html$/, (match) =>
|
|
193
|
-
match === "index.html" ? "" : "/",
|
|
194
|
-
);
|
|
195
|
-
if (!origin) return normalized || "index.html";
|
|
196
|
-
return normalized ? `${origin}/${normalized}` : `${origin}/`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function copyStaticAsset(name) {
|
|
200
|
-
const source = path.join(docsDir, name);
|
|
201
|
-
if (fs.existsSync(source)) fs.copyFileSync(source, path.join(outDir, name));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function readCname() {
|
|
205
|
-
for (const candidate of [
|
|
206
|
-
path.join(docsDir, "CNAME"),
|
|
207
|
-
path.join(root, "CNAME"),
|
|
208
|
-
]) {
|
|
209
|
-
if (fs.existsSync(candidate))
|
|
210
|
-
return fs.readFileSync(candidate, "utf8").trim();
|
|
211
|
-
}
|
|
212
|
-
return "";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function parseFrontmatter(raw) {
|
|
216
|
-
const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
217
|
-
if (!match) return { frontmatter: {}, body: raw };
|
|
218
|
-
const fm = {};
|
|
219
|
-
for (const line of match[1].split("\n")) {
|
|
220
|
-
const m = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
221
|
-
if (!m) continue;
|
|
222
|
-
let value = m[2];
|
|
223
|
-
if (
|
|
224
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
225
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
226
|
-
) {
|
|
227
|
-
value = value.slice(1, -1);
|
|
228
|
-
}
|
|
229
|
-
fm[m[1]] = value;
|
|
230
|
-
}
|
|
231
|
-
return { frontmatter: fm, body: raw.slice(match[0].length) };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function stripStrayDirectives(body) {
|
|
235
|
-
return body
|
|
236
|
-
.replace(/\r\n/g, "\n")
|
|
237
|
-
.split("\n")
|
|
238
|
-
.filter((line) => !/^\s*\{:\s*[^}]*\}\s*$/.test(line))
|
|
239
|
-
.map((line) => line.replace(/\s*\{:\s*[^}]*\}\s*$/, ""))
|
|
240
|
-
.join("\n");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function normalizePermalink(value) {
|
|
244
|
-
let v = value.trim();
|
|
245
|
-
if (!v) return "/";
|
|
246
|
-
if (!v.startsWith("/")) v = `/${v}`;
|
|
247
|
-
if (v.length > 1 && v.endsWith("/")) v = v.slice(0, -1);
|
|
248
|
-
return v;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function allMarkdown(dir) {
|
|
252
|
-
return fs
|
|
253
|
-
.readdirSync(dir, { withFileTypes: true })
|
|
254
|
-
.flatMap((entry) => {
|
|
255
|
-
const full = path.join(dir, entry.name);
|
|
256
|
-
if (entry.isDirectory()) return allMarkdown(full);
|
|
257
|
-
return entry.name.endsWith(".md") ? [full] : [];
|
|
258
|
-
})
|
|
259
|
-
.sort();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function outPath(rel, frontmatter = {}) {
|
|
263
|
-
if (frontmatter.permalink) {
|
|
264
|
-
const permalink = normalizePermalink(frontmatter.permalink);
|
|
265
|
-
if (permalink === "/") return "index.html";
|
|
266
|
-
return `${permalink.slice(1)}/index.html`;
|
|
267
|
-
}
|
|
268
|
-
if (rel === "index.md") return "index.html";
|
|
269
|
-
if (rel === "README.md") return "index.html";
|
|
270
|
-
if (rel.endsWith("/README.md"))
|
|
271
|
-
return rel.replace(/README\.md$/, "index.html");
|
|
272
|
-
return rel.replace(/\.md$/, ".html");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function firstHeading(markdown) {
|
|
276
|
-
return markdown.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function titleize(input) {
|
|
280
|
-
return input.replaceAll("-", " ").replace(/\b\w/g, (m) => m.toUpperCase());
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function markdownToHtml(markdown, currentRel) {
|
|
284
|
-
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
285
|
-
const html = [];
|
|
286
|
-
let paragraph = [];
|
|
287
|
-
let list = null;
|
|
288
|
-
let fence = null;
|
|
289
|
-
let blockquote = [];
|
|
290
|
-
|
|
291
|
-
const flushParagraph = () => {
|
|
292
|
-
if (!paragraph.length) return;
|
|
293
|
-
html.push(`<p>${inline(paragraph.join(" "), currentRel)}</p>`);
|
|
294
|
-
paragraph = [];
|
|
295
|
-
};
|
|
296
|
-
const closeList = () => {
|
|
297
|
-
if (!list) return;
|
|
298
|
-
html.push(`</${list}>`);
|
|
299
|
-
list = null;
|
|
300
|
-
};
|
|
301
|
-
const flushBlockquote = () => {
|
|
302
|
-
if (!blockquote.length) return;
|
|
303
|
-
const inner = markdownToHtml(blockquote.join("\n"), currentRel);
|
|
304
|
-
html.push(`<blockquote>${inner}</blockquote>`);
|
|
305
|
-
blockquote = [];
|
|
306
|
-
};
|
|
307
|
-
const splitRow = (line) => {
|
|
308
|
-
let trimmed = line.trim();
|
|
309
|
-
if (trimmed.startsWith("|")) trimmed = trimmed.slice(1);
|
|
310
|
-
if (trimmed.endsWith("|") && !trimmed.endsWith("\\|"))
|
|
311
|
-
trimmed = trimmed.slice(0, -1);
|
|
312
|
-
const cells = [];
|
|
313
|
-
let current = "";
|
|
314
|
-
for (let idx = 0; idx < trimmed.length; idx++) {
|
|
315
|
-
const char = trimmed[idx];
|
|
316
|
-
if (char === "\\" && trimmed[idx + 1] === "|") {
|
|
317
|
-
current += "\\|";
|
|
318
|
-
idx += 1;
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
if (char === "|") {
|
|
322
|
-
cells.push(current.trim().replace(/\\\|/g, "|"));
|
|
323
|
-
current = "";
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
current += char;
|
|
327
|
-
}
|
|
328
|
-
cells.push(current.trim().replace(/\\\|/g, "|"));
|
|
329
|
-
return cells;
|
|
330
|
-
};
|
|
331
|
-
const isDivider = (line) =>
|
|
332
|
-
/^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(line);
|
|
333
|
-
|
|
334
|
-
for (let i = 0; i < lines.length; i++) {
|
|
335
|
-
const line = lines[i];
|
|
336
|
-
const fenceMatch = line.match(/^```([\w+-]+)?\s*$/);
|
|
337
|
-
if (fenceMatch) {
|
|
338
|
-
flushParagraph();
|
|
339
|
-
closeList();
|
|
340
|
-
flushBlockquote();
|
|
341
|
-
if (fence) {
|
|
342
|
-
const lang = fence.lang;
|
|
343
|
-
const body = highlightCode(fence.lines.join("\n"), lang);
|
|
344
|
-
html.push(
|
|
345
|
-
`<pre><code class="language-${escapeAttr(lang)}">${body}</code></pre>`,
|
|
346
|
-
);
|
|
347
|
-
fence = null;
|
|
348
|
-
} else {
|
|
349
|
-
fence = { lang: fenceMatch[1] || "text", lines: [] };
|
|
350
|
-
}
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
if (fence) {
|
|
354
|
-
fence.lines.push(line);
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
if (/^>\s?/.test(line)) {
|
|
358
|
-
flushParagraph();
|
|
359
|
-
closeList();
|
|
360
|
-
blockquote.push(line.replace(/^>\s?/, ""));
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
flushBlockquote();
|
|
364
|
-
if (!line.trim()) {
|
|
365
|
-
flushParagraph();
|
|
366
|
-
closeList();
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
if (/^\s*---+\s*$/.test(line)) {
|
|
370
|
-
flushParagraph();
|
|
371
|
-
closeList();
|
|
372
|
-
html.push("<hr>");
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
const heading = line.match(/^(#{1,4})\s+(.+)$/);
|
|
376
|
-
if (heading) {
|
|
377
|
-
flushParagraph();
|
|
378
|
-
closeList();
|
|
379
|
-
const level = heading[1].length;
|
|
380
|
-
const text = heading[2].trim();
|
|
381
|
-
const id = slug(text);
|
|
382
|
-
const inner = inline(text, currentRel);
|
|
383
|
-
if (level === 1) {
|
|
384
|
-
html.push(`<h1 id="${id}">${inner}</h1>`);
|
|
385
|
-
} else {
|
|
386
|
-
html.push(
|
|
387
|
-
`<h${level} id="${id}"><a class="anchor" href="#${id}" aria-label="Anchor link">#</a>${inner}</h${level}>`,
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (
|
|
393
|
-
line.trimStart().startsWith("|") &&
|
|
394
|
-
line.includes("|", line.indexOf("|") + 1) &&
|
|
395
|
-
isDivider(lines[i + 1] || "")
|
|
396
|
-
) {
|
|
397
|
-
flushParagraph();
|
|
398
|
-
closeList();
|
|
399
|
-
const header = splitRow(line);
|
|
400
|
-
const aligns = splitRow(lines[i + 1]).map((cell) => {
|
|
401
|
-
const left = cell.startsWith(":");
|
|
402
|
-
const right = cell.endsWith(":");
|
|
403
|
-
return right && left ? "center" : right ? "right" : left ? "left" : "";
|
|
404
|
-
});
|
|
405
|
-
i += 1;
|
|
406
|
-
const rows = [];
|
|
407
|
-
while (i + 1 < lines.length && lines[i + 1].trimStart().startsWith("|")) {
|
|
408
|
-
i += 1;
|
|
409
|
-
rows.push(splitRow(lines[i]));
|
|
410
|
-
}
|
|
411
|
-
const th = header
|
|
412
|
-
.map(
|
|
413
|
-
(c, idx) =>
|
|
414
|
-
`<th${aligns[idx] ? ` style="text-align:${aligns[idx]}"` : ""}>${inline(c, currentRel)}</th>`,
|
|
415
|
-
)
|
|
416
|
-
.join("");
|
|
417
|
-
const tb = rows
|
|
418
|
-
.map(
|
|
419
|
-
(r) =>
|
|
420
|
-
`<tr>${r.map((c, idx) => `<td${aligns[idx] ? ` style="text-align:${aligns[idx]}"` : ""}>${inline(c, currentRel)}</td>`).join("")}</tr>`,
|
|
421
|
-
)
|
|
422
|
-
.join("");
|
|
423
|
-
html.push(
|
|
424
|
-
`<table><thead><tr>${th}</tr></thead><tbody>${tb}</tbody></table>`,
|
|
425
|
-
);
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const bullet = line.match(/^\s*-\s+(.+)$/);
|
|
429
|
-
const numbered = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
430
|
-
if (bullet || numbered) {
|
|
431
|
-
flushParagraph();
|
|
432
|
-
const tag = bullet ? "ul" : "ol";
|
|
433
|
-
if (list && list !== tag) closeList();
|
|
434
|
-
if (!list) {
|
|
435
|
-
list = tag;
|
|
436
|
-
html.push(`<${tag}>`);
|
|
437
|
-
}
|
|
438
|
-
html.push(`<li>${inline((bullet || numbered)[1], currentRel)}</li>`);
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
paragraph.push(line.trim());
|
|
442
|
-
}
|
|
443
|
-
flushParagraph();
|
|
444
|
-
closeList();
|
|
445
|
-
flushBlockquote();
|
|
446
|
-
return html.join("\n");
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function inline(text, currentRel) {
|
|
450
|
-
const stash = [];
|
|
451
|
-
let out = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
452
|
-
stash.push(`<code>${escapeHtml(code)}</code>`);
|
|
453
|
-
return `\u0000${stash.length - 1}\u0000`;
|
|
454
|
-
});
|
|
455
|
-
const stashLink = (label, href) => {
|
|
456
|
-
stash.push(
|
|
457
|
-
`<a href="${escapeAttr(rewriteHref(href, currentRel))}">${renderInlineText(label, stash)}</a>`,
|
|
458
|
-
);
|
|
459
|
-
return `\u0000${stash.length - 1}\u0000`;
|
|
460
|
-
};
|
|
461
|
-
out = out.replace(/\[([^\]]+)\]\(<([^<>]+)>\)/g, (_, label, href) =>
|
|
462
|
-
stashLink(label, href),
|
|
463
|
-
);
|
|
464
|
-
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) =>
|
|
465
|
-
stashLink(label, href),
|
|
466
|
-
);
|
|
467
|
-
out = out.replace(/<(https?:\/\/[^\s<>]+)>/g, (_, url) => {
|
|
468
|
-
stash.push(`<a href="${escapeAttr(url)}">${escapeHtml(url)}</a>`);
|
|
469
|
-
return `\u0000${stash.length - 1}\u0000`;
|
|
470
|
-
});
|
|
471
|
-
return renderInlineText(out, stash);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function renderInlineText(text, stash) {
|
|
475
|
-
let out = formatInlineText(text);
|
|
476
|
-
out = out.replace(/\\\|/g, "|");
|
|
477
|
-
out = out.replace(/<br>/g, "<br>");
|
|
478
|
-
return restoreInlineStash(out, stash);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function formatInlineText(text) {
|
|
482
|
-
return escapeHtml(text)
|
|
483
|
-
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
484
|
-
.replace(/(^|[^*])\*([^*\s][^*]*?)\*(?!\*)/g, "$1<em>$2</em>")
|
|
485
|
-
.replace(/(^|[^_])_([^_\s][^_]*?)_(?!_)/g, "$1<em>$2</em>");
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function restoreInlineStash(text, stash) {
|
|
489
|
-
return text.replace(/\u0000(\d+)\u0000/g, (_, i) => stash[Number(i)]);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function rewriteHref(href, currentRel) {
|
|
493
|
-
if (/^(https?:|mailto:|tel:|#)/.test(href)) return href;
|
|
494
|
-
const [raw, hash = ""] = href.split("#");
|
|
495
|
-
if (!raw) return hash ? `#${hash}` : "";
|
|
496
|
-
if (raw.startsWith("/")) {
|
|
497
|
-
const target = permalinkMap.get(normalizePermalink(raw));
|
|
498
|
-
if (target) {
|
|
499
|
-
const currentOut = pageMap.get(currentRel)?.outRel || outPath(currentRel);
|
|
500
|
-
const out = hrefToOutRel(target.outRel, currentOut);
|
|
501
|
-
return hash ? `${out}#${hash}` : out;
|
|
502
|
-
}
|
|
503
|
-
return href;
|
|
504
|
-
}
|
|
505
|
-
if (!raw.endsWith(".md")) return href;
|
|
506
|
-
const from = path.posix.dirname(currentRel);
|
|
507
|
-
const target = path.posix.normalize(path.posix.join(from, raw));
|
|
508
|
-
let rewritten = pageMap.get(target)?.outRel || outPath(target);
|
|
509
|
-
const currentOut = pageMap.get(currentRel)?.outRel || outPath(currentRel);
|
|
510
|
-
rewritten = hrefToOutRel(rewritten, currentOut);
|
|
511
|
-
return `${rewritten}${hash ? `#${hash}` : ""}`;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function tocFromHtml(html) {
|
|
515
|
-
const items = [];
|
|
516
|
-
const re = /<h([23]) id="([^"]+)">([\s\S]*?)<\/h[23]>/g;
|
|
517
|
-
let m;
|
|
518
|
-
while ((m = re.exec(html))) {
|
|
519
|
-
const text = m[3]
|
|
520
|
-
.replace(/<a class="anchor"[^>]*>.*?<\/a>/, "")
|
|
521
|
-
.replace(/<[^>]+>/g, "")
|
|
522
|
-
.trim();
|
|
523
|
-
items.push({ level: Number(m[1]), id: m[2], text });
|
|
524
|
-
}
|
|
525
|
-
if (items.length < 2) return "";
|
|
526
|
-
return `<nav class="toc" aria-label="On this page"><h2>On this page</h2>${items
|
|
527
|
-
.map(
|
|
528
|
-
(i) =>
|
|
529
|
-
`<a class="toc-l${i.level}" href="#${i.id}">${escapeHtml(i.text)}</a>`,
|
|
530
|
-
)
|
|
531
|
-
.join("")}</nav>`;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function isHomePage(page) {
|
|
535
|
-
if (
|
|
536
|
-
page.frontmatter.permalink &&
|
|
537
|
-
normalizePermalink(page.frontmatter.permalink) === "/"
|
|
538
|
-
)
|
|
539
|
-
return true;
|
|
540
|
-
return page.rel === "index.md" || page.rel === "README.md";
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function homeHero(page) {
|
|
544
|
-
const description = page.frontmatter.description || productDescription;
|
|
545
|
-
const installRel = pageMap.get("install.md")?.outRel
|
|
546
|
-
? hrefToOutRel(pageMap.get("install.md").outRel, page.outRel)
|
|
547
|
-
: "install.html";
|
|
548
|
-
const quickstartRel = pageMap.get("quickstart.md")?.outRel
|
|
549
|
-
? hrefToOutRel(pageMap.get("quickstart.md").outRel, page.outRel)
|
|
550
|
-
: "quickstart.html";
|
|
551
|
-
const surfaces = [
|
|
552
|
-
"Tweets",
|
|
553
|
-
"DMs",
|
|
554
|
-
"Likes",
|
|
555
|
-
"Bookmarks",
|
|
556
|
-
"Mentions",
|
|
557
|
-
"Inbox",
|
|
558
|
-
"Blocks",
|
|
559
|
-
"Mutes",
|
|
560
|
-
"Graph",
|
|
561
|
-
];
|
|
562
|
-
return `<header class="home-hero">
|
|
563
|
-
<img class="home-mark" src="birdclaw-mark.png" alt="" aria-hidden="true">
|
|
564
|
-
<p class="eyebrow">Local-first · CLI + Web · SQLite</p>
|
|
565
|
-
<h1>Your Twitter, <span class="accent">your bird</span>.</h1>
|
|
566
|
-
<p class="lede">${escapeHtml(description)}</p>
|
|
567
|
-
<div class="home-cta">
|
|
568
|
-
<a class="btn btn-primary" href="${quickstartRel}">Quickstart</a>
|
|
569
|
-
<a class="btn btn-ghost" href="${repoBase}" rel="noopener">GitHub</a>
|
|
570
|
-
<div class="home-install" aria-label="Install with Homebrew">
|
|
571
|
-
<span class="prompt" aria-hidden="true">$</span>
|
|
572
|
-
<code>${escapeHtml(brewInstall)}</code>
|
|
573
|
-
</div>
|
|
574
|
-
</div>
|
|
575
|
-
<div class="home-services" aria-label="Things birdclaw stores locally">
|
|
576
|
-
${surfaces.map((s) => `<span>${escapeHtml(s)}</span>`).join("")}
|
|
577
|
-
</div>
|
|
578
|
-
<p><a href="${installRel}">Other install options →</a></p>
|
|
579
|
-
</header>`;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function standardHero(page, sectionName, editUrl) {
|
|
583
|
-
return `<header class="hero">
|
|
584
|
-
<div class="hero-text">
|
|
585
|
-
<p class="eyebrow">${escapeHtml(sectionName)}</p>
|
|
586
|
-
<h1>${escapeHtml(page.title)}</h1>
|
|
587
|
-
</div>
|
|
588
|
-
<div class="hero-meta">
|
|
589
|
-
<a class="repo" href="${repoBase}" rel="noopener">GitHub</a>
|
|
590
|
-
<a class="edit" href="${escapeAttr(editUrl)}" rel="noopener">Edit page</a>
|
|
591
|
-
</div>
|
|
592
|
-
</header>`;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function layout({ page, html, toc, prev, next, sectionName }) {
|
|
596
|
-
const depth = page.outRel.split("/").length - 1;
|
|
597
|
-
const rootPrefix = depth ? "../".repeat(depth) : "";
|
|
598
|
-
const editUrl = `${repoEditBase}/${page.rel}`;
|
|
599
|
-
const home = isHomePage(page);
|
|
600
|
-
const prevNext =
|
|
601
|
-
!home && (prev || next) ? pageNavHtml(prev, next, page.outRel) : "";
|
|
602
|
-
const heroBlock = home
|
|
603
|
-
? homeHero(page)
|
|
604
|
-
: standardHero(page, sectionName, editUrl);
|
|
605
|
-
const articleClass = home ? "doc doc-home" : "doc";
|
|
606
|
-
const tocBlock = home ? "" : toc;
|
|
607
|
-
const titleSuffix = home
|
|
608
|
-
? `${productName} — ${productTagline}`
|
|
609
|
-
: `${page.title} — ${productName}`;
|
|
610
|
-
const description =
|
|
611
|
-
page.frontmatter.description ||
|
|
612
|
-
(home
|
|
613
|
-
? productDescription
|
|
614
|
-
: `${page.title} — ${productName} CLI documentation.`);
|
|
615
|
-
const canonicalUrl = pageCanonicalUrl(page);
|
|
616
|
-
const socialImage = siteBase
|
|
617
|
-
? `${siteBase}/social-card.png`
|
|
618
|
-
: `${rootPrefix}social-card.png`;
|
|
619
|
-
const socialMeta = [
|
|
620
|
-
["link", "rel", "canonical", "href", canonicalUrl],
|
|
621
|
-
["meta", "property", "og:type", "content", "website"],
|
|
622
|
-
["meta", "property", "og:site_name", "content", productName],
|
|
623
|
-
["meta", "property", "og:title", "content", titleSuffix],
|
|
624
|
-
["meta", "property", "og:description", "content", description],
|
|
625
|
-
["meta", "property", "og:url", "content", canonicalUrl],
|
|
626
|
-
["meta", "property", "og:image", "content", socialImage],
|
|
627
|
-
["meta", "property", "og:image:width", "content", "1200"],
|
|
628
|
-
["meta", "property", "og:image:height", "content", "630"],
|
|
629
|
-
["meta", "name", "twitter:card", "content", "summary_large_image"],
|
|
630
|
-
["meta", "name", "twitter:title", "content", titleSuffix],
|
|
631
|
-
["meta", "name", "twitter:description", "content", description],
|
|
632
|
-
["meta", "name", "twitter:image", "content", socialImage],
|
|
633
|
-
]
|
|
634
|
-
.map(tagHtml)
|
|
635
|
-
.join("\n ");
|
|
636
|
-
return `<!doctype html>
|
|
637
|
-
<html lang="en">
|
|
638
|
-
<head>
|
|
639
|
-
<meta charset="utf-8">
|
|
640
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
641
|
-
<title>${escapeHtml(titleSuffix)}</title>
|
|
642
|
-
<meta name="description" content="${escapeAttr(description)}">
|
|
643
|
-
${socialMeta}
|
|
644
|
-
<link rel="icon" href="${rootPrefix}favicon.ico" sizes="any">
|
|
645
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
646
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
647
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
648
|
-
<script>${preThemeScript()}</script>
|
|
649
|
-
<style>${css()}</style>
|
|
650
|
-
</head>
|
|
651
|
-
<body${home ? ' class="home"' : ""}>
|
|
652
|
-
<button class="nav-toggle" type="button" aria-label="Toggle navigation" aria-expanded="false">
|
|
653
|
-
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
|
|
654
|
-
</button>
|
|
655
|
-
<div class="shell">
|
|
656
|
-
<aside class="sidebar">
|
|
657
|
-
<div class="sidebar-head">
|
|
658
|
-
<a class="brand" href="${hrefToOutRel("index.html", page.outRel)}" aria-label="${productName} docs home">
|
|
659
|
-
<span class="mark" aria-hidden="true"><img src="${rootPrefix}birdclaw-mark.png" alt=""></span>
|
|
660
|
-
<span><strong>${escapeHtml(productName)}</strong><small>Local Twitter memory</small></span>
|
|
661
|
-
</a>
|
|
662
|
-
${themeToggleHtml()}
|
|
663
|
-
</div>
|
|
664
|
-
<label class="search"><span>Search</span><input id="doc-search" type="search" placeholder="archive, sync, dms"></label>
|
|
665
|
-
<nav>${navHtml(page)}</nav>
|
|
666
|
-
</aside>
|
|
667
|
-
<main>
|
|
668
|
-
${heroBlock}
|
|
669
|
-
<div class="doc-grid${home ? " doc-grid-home" : ""}">
|
|
670
|
-
<article class="${articleClass}">${html}${prevNext}</article>
|
|
671
|
-
${tocBlock}
|
|
672
|
-
</div>
|
|
673
|
-
</main>
|
|
674
|
-
</div>
|
|
675
|
-
<script>${js()}</script>
|
|
676
|
-
</body>
|
|
677
|
-
</html>`;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function pageCanonicalUrl(page) {
|
|
681
|
-
if (!siteBase) return page.outRel;
|
|
682
|
-
if (page.outRel === "index.html") return `${siteBase}/`;
|
|
683
|
-
const rel = page.outRel.endsWith("/index.html")
|
|
684
|
-
? page.outRel.slice(0, -"index.html".length)
|
|
685
|
-
: page.outRel;
|
|
686
|
-
return `${siteBase}/${rel}`;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
function tagHtml([tag, k1, v1, k2, v2]) {
|
|
690
|
-
return tag === "link"
|
|
691
|
-
? `<link ${k1}="${v1}" ${k2}="${escapeAttr(v2)}">`
|
|
692
|
-
: `<meta ${k1}="${v1}" ${k2}="${escapeAttr(v2)}">`;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function pageNavHtml(prev, next, currentOutRel) {
|
|
696
|
-
const cell = (page, dir) => {
|
|
697
|
-
if (!page) return "";
|
|
698
|
-
return `<a class="page-nav-${dir}" href="${hrefToOutRel(page.outRel, currentOutRel)}"><small>${dir === "prev" ? "Previous" : "Next"}</small><span>${escapeHtml(page.title)}</span></a>`;
|
|
699
|
-
};
|
|
700
|
-
return `<nav class="page-nav" aria-label="Pager">${cell(prev, "prev")}${cell(next, "next")}</nav>`;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function navHtml(currentPage) {
|
|
704
|
-
return nav
|
|
705
|
-
.map(
|
|
706
|
-
(section) =>
|
|
707
|
-
`<section><h2>${escapeHtml(section.name)}</h2>${section.pages
|
|
708
|
-
.map((page) => {
|
|
709
|
-
const href = hrefToOutRel(page.outRel, currentPage.outRel);
|
|
710
|
-
const active = page.rel === currentPage.rel ? " active" : "";
|
|
711
|
-
return `<a class="nav-link${active}" href="${href}">${escapeHtml(navTitle(page))}</a>`;
|
|
712
|
-
})
|
|
713
|
-
.join("")}</section>`,
|
|
714
|
-
)
|
|
715
|
-
.join("");
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function navTitle(page) {
|
|
719
|
-
if (page.rel === "index.md") return "Overview";
|
|
720
|
-
return page.title.replace(/^`birdclaw\s*/, "").replace(/`$/, "");
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function hrefToOutRel(targetOutRel, currentOutRel) {
|
|
724
|
-
const currentDir = path.posix.dirname(currentOutRel);
|
|
725
|
-
if (targetOutRel.endsWith("/index.html")) {
|
|
726
|
-
const targetDir = targetOutRel.slice(0, -"index.html".length);
|
|
727
|
-
const rel = path.posix.relative(currentDir, targetDir || ".") || ".";
|
|
728
|
-
return rel.endsWith("/") ? rel : `${rel}/`;
|
|
729
|
-
}
|
|
730
|
-
if (targetOutRel === "index.html") {
|
|
731
|
-
const rel = path.posix.relative(currentDir, ".") || ".";
|
|
732
|
-
return rel.endsWith("/") ? rel : `${rel}/`;
|
|
733
|
-
}
|
|
734
|
-
return (
|
|
735
|
-
path.posix.relative(currentDir, targetOutRel) ||
|
|
736
|
-
path.posix.basename(targetOutRel)
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function slug(text) {
|
|
741
|
-
return text
|
|
742
|
-
.toLowerCase()
|
|
743
|
-
.replace(/`/g, "")
|
|
744
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
745
|
-
.replace(/^-|-$/g, "");
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function escapeHtml(value) {
|
|
749
|
-
return String(value ?? "").replace(
|
|
750
|
-
/[&<>"']/g,
|
|
751
|
-
(char) =>
|
|
752
|
-
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
|
753
|
-
char
|
|
754
|
-
],
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function escapeAttr(value) {
|
|
759
|
-
return escapeHtml(value);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function highlightCode(code, lang) {
|
|
763
|
-
const language = (lang || "text").toLowerCase();
|
|
764
|
-
if (
|
|
765
|
-
language === "bash" ||
|
|
766
|
-
language === "sh" ||
|
|
767
|
-
language === "shell" ||
|
|
768
|
-
language === "zsh" ||
|
|
769
|
-
language === "console"
|
|
770
|
-
) {
|
|
771
|
-
return highlightShell(code);
|
|
772
|
-
}
|
|
773
|
-
if (language === "json" || language === "json5") return highlightJson(code);
|
|
774
|
-
if (
|
|
775
|
-
language === "ts" ||
|
|
776
|
-
language === "typescript" ||
|
|
777
|
-
language === "js" ||
|
|
778
|
-
language === "javascript" ||
|
|
779
|
-
language === "tsx" ||
|
|
780
|
-
language === "jsx"
|
|
781
|
-
) {
|
|
782
|
-
return highlightJs(code);
|
|
783
|
-
}
|
|
784
|
-
if (language === "yaml" || language === "yml") return highlightYaml(code);
|
|
785
|
-
return escapeHtml(code);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function withStash(code, patterns) {
|
|
789
|
-
const stash = [];
|
|
790
|
-
let working = code;
|
|
791
|
-
for (const [re, cls] of patterns) {
|
|
792
|
-
working = working.replace(re, (match) => {
|
|
793
|
-
const idx = stash.length;
|
|
794
|
-
stash.push(`<span class="${cls}">${escapeHtml(match)}</span>`);
|
|
795
|
-
return stashToken(idx);
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
let escaped = escapeHtml(working);
|
|
799
|
-
escaped = restoreStashTokens(escaped, stash);
|
|
800
|
-
return escaped;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function highlightShell(code) {
|
|
804
|
-
return code
|
|
805
|
-
.split("\n")
|
|
806
|
-
.map((line) => {
|
|
807
|
-
const promptMatch = line.match(/^(\s*)([$#>])(\s+)(.*)$/);
|
|
808
|
-
if (promptMatch) {
|
|
809
|
-
const [, lead, sym, gap, rest] = promptMatch;
|
|
810
|
-
return `${escapeHtml(lead)}<span class="hl-p">${escapeHtml(sym)}</span>${escapeHtml(gap)}${highlightShellLine(rest)}`;
|
|
811
|
-
}
|
|
812
|
-
if (/^\s*#/.test(line))
|
|
813
|
-
return `<span class="hl-c">${escapeHtml(line)}</span>`;
|
|
814
|
-
return highlightShellLine(line);
|
|
815
|
-
})
|
|
816
|
-
.join("\n");
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function highlightShellLine(line) {
|
|
820
|
-
const stash = [];
|
|
821
|
-
let working = line;
|
|
822
|
-
const stashAdd = (match, cls) => {
|
|
823
|
-
const idx = stash.length;
|
|
824
|
-
stash.push(`<span class="${cls}">${escapeHtml(match)}</span>`);
|
|
825
|
-
return stashToken(idx);
|
|
826
|
-
};
|
|
827
|
-
working = working.replace(/(?:'[^']*'|"[^"]*")/g, (m) => stashAdd(m, "hl-s"));
|
|
828
|
-
working = working.replace(/\s#.*$/g, (m) => stashAdd(m, "hl-c"));
|
|
829
|
-
working = working.replace(
|
|
830
|
-
/(^|\s)(--?[A-Za-z][A-Za-z0-9-]*)/g,
|
|
831
|
-
(_, lead, flag) => `${escapeHtml(lead)}${stashAdd(flag, "hl-f")}`,
|
|
832
|
-
);
|
|
833
|
-
working = working.replace(
|
|
834
|
-
/\b(birdclaw|pnpm|npm|brew|bird|xurl|git|node|tsx|sudo|launchctl|tail|jq|cd|export|cat|curl|cargo|ls|mv|cp|rm|mkdir|cron)\b/g,
|
|
835
|
-
(m) => stashAdd(m, "hl-cmd"),
|
|
836
|
-
);
|
|
837
|
-
working = working.replace(/\b(\d+(?:\.\d+)?)\b/g, (m) => stashAdd(m, "hl-n"));
|
|
838
|
-
let escaped = escapeHtml(working);
|
|
839
|
-
return restoreStashTokens(escaped, stash);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function stashToken(idx) {
|
|
843
|
-
return String.fromCharCode(0xe000 + idx);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function restoreStashTokens(value, stash) {
|
|
847
|
-
return value.replace(/[\ue000-\uf8ff]/g, (token) => {
|
|
848
|
-
const idx = token.charCodeAt(0) - 0xe000;
|
|
849
|
-
return stash[idx] ?? "";
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function highlightJson(code) {
|
|
854
|
-
return withStash(code, [
|
|
855
|
-
[/"(?:\\.|[^"\\])*"\s*:/g, "hl-k"],
|
|
856
|
-
[/"(?:\\.|[^"\\])*"/g, "hl-s"],
|
|
857
|
-
[/\b(true|false|null)\b/g, "hl-m"],
|
|
858
|
-
[/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/gi, "hl-n"],
|
|
859
|
-
]);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
function highlightJs(code) {
|
|
863
|
-
return withStash(code, [
|
|
864
|
-
[/\/\/[^\n]*/g, "hl-c"],
|
|
865
|
-
[/\/\*[\s\S]*?\*\//g, "hl-c"],
|
|
866
|
-
[/`(?:\\.|[^`\\])*`/g, "hl-s"],
|
|
867
|
-
[/"(?:\\.|[^"\\])*"/g, "hl-s"],
|
|
868
|
-
[/'(?:\\.|[^'\\])*'/g, "hl-s"],
|
|
869
|
-
[
|
|
870
|
-
/\b(const|let|var|function|return|if|else|for|while|switch|case|break|continue|class|extends|new|import|from|export|default|async|await|try|catch|finally|throw|typeof|instanceof|interface|type|enum|as|of|in|null|undefined|true|false|this)\b/g,
|
|
871
|
-
"hl-k",
|
|
872
|
-
],
|
|
873
|
-
[/\b(\d+(?:\.\d+)?)\b/g, "hl-n"],
|
|
874
|
-
]);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function highlightYaml(code) {
|
|
878
|
-
return code
|
|
879
|
-
.split("\n")
|
|
880
|
-
.map((line) => {
|
|
881
|
-
if (/^\s*#/.test(line))
|
|
882
|
-
return `<span class="hl-c">${escapeHtml(line)}</span>`;
|
|
883
|
-
const m = line.match(/^(\s*-?\s*)([A-Za-z0-9_.-]+)(\s*:)(.*)$/);
|
|
884
|
-
if (m) {
|
|
885
|
-
const [, lead, key, colon, rest] = m;
|
|
886
|
-
return `${escapeHtml(lead)}<span class="hl-k">${escapeHtml(key)}</span>${escapeHtml(colon)}${highlightYamlValue(rest)}`;
|
|
887
|
-
}
|
|
888
|
-
return escapeHtml(line);
|
|
889
|
-
})
|
|
890
|
-
.join("\n");
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
function highlightYamlValue(rest) {
|
|
894
|
-
if (!rest.trim()) return escapeHtml(rest);
|
|
895
|
-
const trimmed = rest.trim();
|
|
896
|
-
if (/^["'].*["']$/.test(trimmed))
|
|
897
|
-
return (
|
|
898
|
-
escapeHtml(rest.replace(trimmed, "")) +
|
|
899
|
-
`<span class="hl-s">${escapeHtml(trimmed)}</span>`
|
|
900
|
-
);
|
|
901
|
-
if (/^(true|false|null|~)$/i.test(trimmed))
|
|
902
|
-
return (
|
|
903
|
-
escapeHtml(rest.replace(trimmed, "")) +
|
|
904
|
-
`<span class="hl-m">${escapeHtml(trimmed)}</span>`
|
|
905
|
-
);
|
|
906
|
-
if (/^-?\d+(\.\d+)?$/.test(trimmed))
|
|
907
|
-
return (
|
|
908
|
-
escapeHtml(rest.replace(trimmed, "")) +
|
|
909
|
-
`<span class="hl-n">${escapeHtml(trimmed)}</span>`
|
|
910
|
-
);
|
|
911
|
-
return escapeHtml(rest);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export const __test__ = { inline };
|
|
915
|
-
|
|
916
|
-
function validateLinks(outputDir) {
|
|
917
|
-
const failures = [];
|
|
918
|
-
const placeholderHrefs =
|
|
919
|
-
/^(url|path|file|dir|name|host|port|kind|tweet-id|conversation-id|handle-or-id|account-id|n|seconds|date|cursor-or-id|query|text)$/i;
|
|
920
|
-
for (const file of allHtml(outputDir)) {
|
|
921
|
-
const html = fs.readFileSync(file, "utf8");
|
|
922
|
-
for (const match of html.matchAll(/href="([^"]+)"/g)) {
|
|
923
|
-
const href = match[1];
|
|
924
|
-
if (/^(#|https?:|mailto:|tel:|javascript:)/.test(href)) continue;
|
|
925
|
-
if (placeholderHrefs.test(href)) continue;
|
|
926
|
-
const [rawPath, anchor = ""] = href.split("#");
|
|
927
|
-
const targetPath = rawPath
|
|
928
|
-
? path.resolve(path.dirname(file), rawPath)
|
|
929
|
-
: file;
|
|
930
|
-
const target =
|
|
931
|
-
fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()
|
|
932
|
-
? path.join(targetPath, "index.html")
|
|
933
|
-
: targetPath;
|
|
934
|
-
if (!fs.existsSync(target)) {
|
|
935
|
-
failures.push(
|
|
936
|
-
`${path.relative(outputDir, file)}: ${href} -> missing ${path.relative(outputDir, target)}`,
|
|
937
|
-
);
|
|
938
|
-
continue;
|
|
939
|
-
}
|
|
940
|
-
if (anchor) {
|
|
941
|
-
const targetHtml = fs.readFileSync(target, "utf8");
|
|
942
|
-
if (
|
|
943
|
-
!targetHtml.includes(`id="${anchor}"`) &&
|
|
944
|
-
!targetHtml.includes(`name="${anchor}"`)
|
|
945
|
-
) {
|
|
946
|
-
failures.push(
|
|
947
|
-
`${path.relative(outputDir, file)}: ${href} -> missing anchor`,
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
if (failures.length) {
|
|
954
|
-
throw new Error(`broken docs links:\n${failures.join("\n")}`);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
function allHtml(dir) {
|
|
959
|
-
return fs
|
|
960
|
-
.readdirSync(dir, { withFileTypes: true })
|
|
961
|
-
.flatMap((entry) => {
|
|
962
|
-
const full = path.join(dir, entry.name);
|
|
963
|
-
if (entry.isDirectory()) return allHtml(full);
|
|
964
|
-
return entry.name.endsWith(".html") ? [full] : [];
|
|
965
|
-
})
|
|
966
|
-
.sort();
|
|
967
|
-
}
|