grok-digest 0.3.1 → 0.3.2
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/BOT.md +8 -3
- package/dist/components/BookmarkCard.astro +39 -35
- package/dist/lib/bookmarks.d.ts +6 -0
- package/dist/lib/bookmarks.js +12 -1
- package/dist/lib/feed.js +6 -6
- package/dist/lib/load-bookmarks.d.ts +1 -0
- package/dist/lib/media.d.ts +11 -2
- package/dist/lib/media.js +17 -2
- package/package.json +1 -1
package/BOT.md
CHANGED
|
@@ -54,7 +54,7 @@ bun run validate
|
|
|
54
54
|
"shelf_life": "evergreen",
|
|
55
55
|
"tldr": "Stale workaround comments spread because coding agents copy them",
|
|
56
56
|
"summary": "Argues you should delete most code comments, because coding agents copy existing workaround comments and spread hacks through the codebase.",
|
|
57
|
-
"text": "ban and delete most if not all code comments!\n\nagents love copying what already exists.",
|
|
57
|
+
"text": "ban and delete most if not all code comments!\n\nagents love copying what already exists. https://t.co/Xq7mPLe0Ab",
|
|
58
58
|
"key_points": [
|
|
59
59
|
"Agents imitate existing code, so stale workaround comments spread like weeds.",
|
|
60
60
|
"Keep only comments that must exist, such as browser-quirk explanations."
|
|
@@ -62,7 +62,11 @@ bun run validate
|
|
|
62
62
|
"action": "Have an agent run /no-comments on one repo and sort each comment into refactor or delete.",
|
|
63
63
|
"links": [{ "title": "pstack: the /no-comments skill", "url": "https://github.com/example/pstack" }],
|
|
64
64
|
"media": [
|
|
65
|
-
{
|
|
65
|
+
{
|
|
66
|
+
"url": "https://pbs.twimg.com/media/G3xAmPLeAAAbCdE.jpg",
|
|
67
|
+
"alt": "A diff that deletes a workaround comment",
|
|
68
|
+
"link": "https://t.co/Xq7mPLe0Ab"
|
|
69
|
+
}
|
|
66
70
|
]
|
|
67
71
|
}
|
|
68
72
|
```
|
|
@@ -156,10 +160,11 @@ Every link in the post and its thread, followed to where it really lands. Expand
|
|
|
156
160
|
|
|
157
161
|
### media
|
|
158
162
|
|
|
159
|
-
The images in the post and its thread, in thread order, so the card can show
|
|
163
|
+
The images in the post and its thread, in thread order, so the card can show each one with the post it came from. One object per photo:
|
|
160
164
|
|
|
161
165
|
- `url`: the image's `https://pbs.twimg.com/` address, such as `https://pbs.twimg.com/media/G3xAmPLeAAAbCdE.jpg`. A `?format=jpg&name=large` query is fine. Never a `t.co` link or an `x.com/…/photo/1` page; the build rejects any other host.
|
|
162
166
|
- `alt`: the author's alt text for the image when it has one, otherwise a few words on what it shows. Leave `alt` out rather than writing `""`.
|
|
167
|
+
- `link`: the short link that stands for the image in `text`, copied exactly as it appears there, such as `https://t.co/Xq7mPLe0Ab` or `pic.x.com/Xq7mPLe0Ab`. X gives all the photos of one post a single link, so photos from the same post share it. The card shows the images where that link is and hides the link. The build rejects a `link` that is not in `text`. Leave `link` out only when `text` has no link for the image; the card then shows it after the post.
|
|
163
168
|
- For a video or GIF, add its poster image (the `pbs.twimg.com` thumbnail X shows before it plays) when you can find it. Skip it otherwise.
|
|
164
169
|
- Don't add link-preview cards or the author's profile picture.
|
|
165
170
|
- Write `[]` when the post has no images.
|
|
@@ -5,7 +5,7 @@ import { Button } from "./ui/button";
|
|
|
5
5
|
import { Card } from "./ui/card";
|
|
6
6
|
import type { Bookmark } from "../lib/bookmarks";
|
|
7
7
|
import { copyText } from "../lib/copy";
|
|
8
|
-
import {
|
|
8
|
+
import { postParts } from "../lib/media";
|
|
9
9
|
import { TOPIC_LABELS } from "../lib/topics";
|
|
10
10
|
|
|
11
11
|
type Props = {
|
|
@@ -13,14 +13,13 @@ type Props = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const { bookmark } = Astro.props;
|
|
16
|
-
const { id, url, author, postedAt, savedAt, topic, shelfLife, tldr, summary,
|
|
17
|
-
bookmark;
|
|
16
|
+
const { id, url, author, postedAt, savedAt, topic, shelfLife, tldr, summary, keyPoints, action, links } = bookmark;
|
|
18
17
|
|
|
19
18
|
const dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeZone: "UTC" });
|
|
20
19
|
const isoDay = (date: Date) => date.toISOString().slice(0, 10);
|
|
21
20
|
const postedOtherDay = isoDay(postedAt) !== isoDay(savedAt);
|
|
22
21
|
const hostOf = (link: string) => new URL(link).hostname.replace(/^www\./, "");
|
|
23
|
-
const
|
|
22
|
+
const post = postParts(bookmark);
|
|
24
23
|
---
|
|
25
24
|
|
|
26
25
|
<li id={`b-${id}`} data-bookmark-id={id} data-url={url} data-topic={topic} class="group/bookmark scroll-mt-6">
|
|
@@ -90,39 +89,44 @@ const images = imagesWithAlt(bookmark);
|
|
|
90
89
|
))}
|
|
91
90
|
</ul>
|
|
92
91
|
)}
|
|
93
|
-
{
|
|
92
|
+
{post.length > 0 && (
|
|
94
93
|
<figure class="border-t border-border pt-4">
|
|
95
94
|
<figcaption class="mb-2 text-xs font-medium text-muted-foreground">Original post</figcaption>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
95
|
+
<blockquote class="flex flex-col gap-3 text-sm leading-relaxed text-pretty text-foreground/80">
|
|
96
|
+
{post.map((part) =>
|
|
97
|
+
part.kind === "text" ? (
|
|
98
|
+
<p class="whitespace-pre-wrap">{part.text}</p>
|
|
99
|
+
) : (
|
|
100
|
+
<ul
|
|
101
|
+
class:list={["grid max-w-md gap-2", { "grid-cols-2": part.images.length > 1 }]}
|
|
102
|
+
aria-label="Images in the post"
|
|
103
|
+
>
|
|
104
|
+
{part.images.map((image, index) => (
|
|
105
|
+
<li
|
|
106
|
+
class:list={{
|
|
107
|
+
"col-span-2": index === 0 && part.images.length > 1 && part.images.length % 2 === 1,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<a
|
|
111
|
+
href={image.url}
|
|
112
|
+
target="_blank"
|
|
113
|
+
rel="noopener noreferrer"
|
|
114
|
+
class="block overflow-hidden rounded-lg border border-border bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
|
|
115
|
+
>
|
|
116
|
+
<img
|
|
117
|
+
src={image.url}
|
|
118
|
+
alt={image.alt}
|
|
119
|
+
loading="lazy"
|
|
120
|
+
decoding="async"
|
|
121
|
+
class="aspect-video w-full object-cover"
|
|
122
|
+
/>
|
|
123
|
+
</a>
|
|
124
|
+
</li>
|
|
125
|
+
))}
|
|
126
|
+
</ul>
|
|
127
|
+
),
|
|
128
|
+
)}
|
|
129
|
+
</blockquote>
|
|
126
130
|
</figure>
|
|
127
131
|
)}
|
|
128
132
|
</div>
|
package/dist/lib/bookmarks.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
34
34
|
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
35
35
|
url: z.ZodURL;
|
|
36
36
|
alt: z.ZodOptional<z.ZodString>;
|
|
37
|
+
link: z.ZodOptional<z.ZodString>;
|
|
37
38
|
}, z.core.$strict>>>;
|
|
38
39
|
}, z.core.$strict>, z.ZodTransform<{
|
|
39
40
|
shelfLife: "news" | "evergreen";
|
|
@@ -58,6 +59,7 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
58
59
|
media: {
|
|
59
60
|
url: string;
|
|
60
61
|
alt?: string | undefined;
|
|
62
|
+
link?: string | undefined;
|
|
61
63
|
}[];
|
|
62
64
|
}, {
|
|
63
65
|
id: string;
|
|
@@ -82,6 +84,7 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
82
84
|
media: {
|
|
83
85
|
url: string;
|
|
84
86
|
alt?: string | undefined;
|
|
87
|
+
link?: string | undefined;
|
|
85
88
|
}[];
|
|
86
89
|
}>>;
|
|
87
90
|
export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
@@ -118,6 +121,7 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
118
121
|
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
119
122
|
url: z.ZodURL;
|
|
120
123
|
alt: z.ZodOptional<z.ZodString>;
|
|
124
|
+
link: z.ZodOptional<z.ZodString>;
|
|
121
125
|
}, z.core.$strict>>>;
|
|
122
126
|
}, z.core.$strict>, z.ZodTransform<{
|
|
123
127
|
shelfLife: "news" | "evergreen";
|
|
@@ -142,6 +146,7 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
142
146
|
media: {
|
|
143
147
|
url: string;
|
|
144
148
|
alt?: string | undefined;
|
|
149
|
+
link?: string | undefined;
|
|
145
150
|
}[];
|
|
146
151
|
}, {
|
|
147
152
|
id: string;
|
|
@@ -166,6 +171,7 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
166
171
|
media: {
|
|
167
172
|
url: string;
|
|
168
173
|
alt?: string | undefined;
|
|
174
|
+
link?: string | undefined;
|
|
169
175
|
}[];
|
|
170
176
|
}>>>;
|
|
171
177
|
export declare const stateFileSchema: z.ZodArray<z.ZodString>;
|
package/dist/lib/bookmarks.js
CHANGED
|
@@ -30,8 +30,19 @@ export const bookmarkSchema = z
|
|
|
30
30
|
action: z.string().trim(),
|
|
31
31
|
links: z.array(z.strictObject({ title: nonEmpty, url: z.url({ protocol: /^https$/ }) })),
|
|
32
32
|
media: z
|
|
33
|
-
.array(z.strictObject({
|
|
33
|
+
.array(z.strictObject({
|
|
34
|
+
url: z.url({ protocol: /^https$/, hostname: X_MEDIA_HOSTNAME }),
|
|
35
|
+
alt: nonEmpty.optional(),
|
|
36
|
+
link: nonEmpty.optional(),
|
|
37
|
+
}))
|
|
34
38
|
.default([]),
|
|
39
|
+
})
|
|
40
|
+
.superRefine(({ text, media }, ctx) => {
|
|
41
|
+
media.forEach(({ link }, index) => {
|
|
42
|
+
if (link !== undefined && !text.includes(link)) {
|
|
43
|
+
ctx.addIssue({ code: "custom", path: ["media", index, "link"], message: `"${link}" is not in text` });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
35
46
|
})
|
|
36
47
|
.transform(({ posted_at, saved_at, shelf_life, key_points, ...rest }) => ({
|
|
37
48
|
...rest,
|
package/dist/lib/feed.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isTopic } from "./filter.js";
|
|
2
|
-
import {
|
|
2
|
+
import { postParts } from "./media.js";
|
|
3
3
|
import { TOPIC_LABELS } from "./topics.js";
|
|
4
4
|
const LOCAL_ORIGIN = "http://localhost:4321";
|
|
5
5
|
export function feedOrigin(site) {
|
|
@@ -30,9 +30,10 @@ function feedItem(bookmark, page) {
|
|
|
30
30
|
const link = new URL(page.href);
|
|
31
31
|
link.hash = `b-${id}`;
|
|
32
32
|
const points = keyPoints.map((point) => `<li>${escapeHtml(point)}</li>`).join("");
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const quoted = postParts(bookmark)
|
|
34
|
+
.flatMap((part) => part.kind === "text"
|
|
35
|
+
? [`<p>${escapeHtml(part.text)}</p>`]
|
|
36
|
+
: part.images.map((image) => `<p><img src="${escapeHtml(image.url)}" alt="${escapeHtml(image.alt)}"></p>`))
|
|
36
37
|
.join("");
|
|
37
38
|
const linkItems = links
|
|
38
39
|
.map((outbound) => `<li><a href="${escapeHtml(outbound.url)}">${escapeHtml(outbound.title)}</a></li>`)
|
|
@@ -47,8 +48,7 @@ function feedItem(bookmark, page) {
|
|
|
47
48
|
action === "" ? "" : `<p>Try: ${escapeHtml(action)}</p>`,
|
|
48
49
|
points === "" ? "" : `<ul>${points}</ul>`,
|
|
49
50
|
linkItems === "" ? "" : `<ul>${linkItems}</ul>`,
|
|
50
|
-
|
|
51
|
-
images,
|
|
51
|
+
quoted === "" ? "" : `<blockquote>${quoted}</blockquote>`,
|
|
52
52
|
`<p><a href="${escapeHtml(url)}">Open on X</a></p>`,
|
|
53
53
|
]
|
|
54
54
|
.filter((part) => part !== "")
|
package/dist/lib/media.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { Bookmark } from "./bookmarks.ts";
|
|
2
|
-
|
|
2
|
+
type Image = {
|
|
3
3
|
url: string;
|
|
4
4
|
alt: string;
|
|
5
|
-
}
|
|
5
|
+
};
|
|
6
|
+
type PostPart = {
|
|
7
|
+
kind: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "images";
|
|
11
|
+
images: Image[];
|
|
12
|
+
};
|
|
13
|
+
export declare function postParts({ author, text, media }: Bookmark): PostPart[];
|
|
14
|
+
export {};
|
package/dist/lib/media.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function postParts({ author, text, media }) {
|
|
2
2
|
const position = (index) => (media.length === 1 ? "" : ` ${index + 1} of ${media.length}`);
|
|
3
|
-
|
|
3
|
+
const images = media.map(({ url, alt, link }, index) => ({
|
|
4
4
|
url,
|
|
5
5
|
alt: alt ?? `Image${position(index)} from the post by @${author.handle}`,
|
|
6
|
+
link,
|
|
6
7
|
}));
|
|
8
|
+
const imagesFor = (link) => ({
|
|
9
|
+
kind: "images",
|
|
10
|
+
images: images.filter((image) => image.link === link).map(({ url, alt }) => ({ url, alt })),
|
|
11
|
+
});
|
|
12
|
+
const links = [...new Set(images.flatMap(({ link }) => (link === undefined ? [] : [link])))].toSorted((a, b) => text.indexOf(a) - text.indexOf(b));
|
|
13
|
+
const parts = [];
|
|
14
|
+
let rest = text;
|
|
15
|
+
for (const link of links) {
|
|
16
|
+
const at = rest.indexOf(link);
|
|
17
|
+
parts.push({ kind: "text", text: rest.slice(0, at).trim() }, imagesFor(link));
|
|
18
|
+
rest = rest.slice(at + link.length);
|
|
19
|
+
}
|
|
20
|
+
parts.push({ kind: "text", text: rest.trim() }, imagesFor(undefined));
|
|
21
|
+
return parts.filter((part) => (part.kind === "text" ? part.text !== "" : part.images.length > 0));
|
|
7
22
|
}
|
package/package.json
CHANGED