grok-digest 0.3.0 → 0.3.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/BOT.md +18 -3
- package/README.md +1 -1
- package/dist/components/BookmarkCard.astro +33 -4
- package/dist/lib/bookmarks.d.ts +24 -0
- package/dist/lib/bookmarks.js +4 -0
- package/dist/lib/copy.d.ts +1 -1
- package/dist/lib/copy.js +2 -1
- package/dist/lib/feed.js +5 -0
- package/dist/lib/load-bookmarks.d.ts +4 -0
- package/dist/lib/media.d.ts +5 -0
- package/dist/lib/media.js +7 -0
- package/package.json +1 -1
package/BOT.md
CHANGED
|
@@ -60,11 +60,14 @@ bun run validate
|
|
|
60
60
|
"Keep only comments that must exist, such as browser-quirk explanations."
|
|
61
61
|
],
|
|
62
62
|
"action": "Have an agent run /no-comments on one repo and sort each comment into refactor or delete.",
|
|
63
|
-
"links": [{ "title": "pstack: the /no-comments skill", "url": "https://github.com/example/pstack" }]
|
|
63
|
+
"links": [{ "title": "pstack: the /no-comments skill", "url": "https://github.com/example/pstack" }],
|
|
64
|
+
"media": [
|
|
65
|
+
{ "url": "https://pbs.twimg.com/media/G3xAmPLeAAAbCdE.jpg", "alt": "A diff that deletes a workaround comment" }
|
|
66
|
+
]
|
|
64
67
|
}
|
|
65
68
|
```
|
|
66
69
|
|
|
67
|
-
Write every field, in this order. The build rejects an entry with a missing field or a field not listed here. `text` and `action` may be empty strings, and `key_points` and `
|
|
70
|
+
Write every field, in this order. The build rejects an entry with a missing field or a field not listed here. `text` and `action` may be empty strings, and `key_points`, `links`, and `media` may be empty arrays. Entries saved before `media` existed have no `media` field; that is valid, and you don't need to add one.
|
|
68
71
|
|
|
69
72
|
### id
|
|
70
73
|
|
|
@@ -125,7 +128,7 @@ One or two sentences on what the post says and why it matters. For a thread, sum
|
|
|
125
128
|
|
|
126
129
|
### text
|
|
127
130
|
|
|
128
|
-
The post's own words, copied exactly as posted, with its line breaks. For a thread, the author's posts in order, separated by a blank line. Write `""` when the post has no text.
|
|
131
|
+
The post's own words, copied exactly as posted, with its line breaks. For a thread, the author's posts in order, separated by a blank line. Keep a `t.co` line that stands for an image or video as it is; the image itself goes in `media`. Write `""` when the post has no text.
|
|
129
132
|
|
|
130
133
|
Keep it complete. Most AI assistants can't open x.com, so when the owner copies a bookmark into one, this text is all it gets of the post.
|
|
131
134
|
|
|
@@ -151,6 +154,18 @@ Every link in the post and its thread, followed to where it really lands. Expand
|
|
|
151
154
|
- Leave out links to the post itself, to the author's profile, and to the post's images or videos.
|
|
152
155
|
- Write `[]` when there are none.
|
|
153
156
|
|
|
157
|
+
### media
|
|
158
|
+
|
|
159
|
+
The images in the post and its thread, in thread order, so the card can show them. One object per photo:
|
|
160
|
+
|
|
161
|
+
- `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
|
+
- `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 `""`.
|
|
163
|
+
- 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
|
+
- Don't add link-preview cards or the author's profile picture.
|
|
165
|
+
- Write `[]` when the post has no images.
|
|
166
|
+
|
|
167
|
+
Images never go in `links`, and `text` keeps the post's exact words, including any `t.co` lines that stood for the images.
|
|
168
|
+
|
|
154
169
|
## Change or remove a bookmark
|
|
155
170
|
|
|
156
171
|
To change one, edit its fields in place. Keep `id` and `saved_at`. Commit with `Update bookmark (@handle)`.
|
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ The site needs no Astro config. The package depends on the Astro version it was
|
|
|
63
63
|
import { SCHEMA_VERSION, bookmarkSchema, parseBookmarks, TOPICS } from "grok-digest/schema";
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`SCHEMA_VERSION` is `1`. The JSON files have no version field. A `bookmarks.json` that already validates is version 1. `posted_at` with milliseconds and a `Z` offset, as the bot writes it, is version 1.
|
|
66
|
+
`SCHEMA_VERSION` is `1`. The JSON files have no version field. A `bookmarks.json` that already validates is version 1. `posted_at` with milliseconds and a `Z` offset, as the bot writes it, is version 1. The optional `media` field, a post's images, is part of version 1 too: a bookmark without it parses with `media` set to `[]`.
|
|
67
67
|
|
|
68
68
|
| File | Version 1 shape |
|
|
69
69
|
| --------------------- | -------------------------------------------------------------------- |
|
|
@@ -5,6 +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 { imagesWithAlt } from "../lib/media";
|
|
8
9
|
import { TOPIC_LABELS } from "../lib/topics";
|
|
9
10
|
|
|
10
11
|
type Props = {
|
|
@@ -19,6 +20,7 @@ const dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeZone
|
|
|
19
20
|
const isoDay = (date: Date) => date.toISOString().slice(0, 10);
|
|
20
21
|
const postedOtherDay = isoDay(postedAt) !== isoDay(savedAt);
|
|
21
22
|
const hostOf = (link: string) => new URL(link).hostname.replace(/^www\./, "");
|
|
23
|
+
const images = imagesWithAlt(bookmark);
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
<li id={`b-${id}`} data-bookmark-id={id} data-url={url} data-topic={topic} class="group/bookmark scroll-mt-6">
|
|
@@ -88,12 +90,39 @@ const hostOf = (link: string) => new URL(link).hostname.replace(/^www\./, "");
|
|
|
88
90
|
))}
|
|
89
91
|
</ul>
|
|
90
92
|
)}
|
|
91
|
-
{text !== "" && (
|
|
93
|
+
{(text !== "" || images.length > 0) && (
|
|
92
94
|
<figure class="border-t border-border pt-4">
|
|
93
95
|
<figcaption class="mb-2 text-xs font-medium text-muted-foreground">Original post</figcaption>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
{text !== "" && (
|
|
97
|
+
<blockquote class="text-sm leading-relaxed whitespace-pre-wrap text-pretty text-foreground/80">
|
|
98
|
+
{text}
|
|
99
|
+
</blockquote>
|
|
100
|
+
)}
|
|
101
|
+
{images.length > 0 && (
|
|
102
|
+
<ul
|
|
103
|
+
class:list={["grid max-w-md gap-2", { "grid-cols-2": images.length > 1, "mt-3": text !== "" }]}
|
|
104
|
+
aria-label="Images in the post"
|
|
105
|
+
>
|
|
106
|
+
{images.map((image, index) => (
|
|
107
|
+
<li class:list={{ "col-span-2": index === 0 && images.length > 1 && images.length % 2 === 1 }}>
|
|
108
|
+
<a
|
|
109
|
+
href={image.url}
|
|
110
|
+
target="_blank"
|
|
111
|
+
rel="noopener noreferrer"
|
|
112
|
+
class="block overflow-hidden rounded-lg border border-border bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
|
|
113
|
+
>
|
|
114
|
+
<img
|
|
115
|
+
src={image.url}
|
|
116
|
+
alt={image.alt}
|
|
117
|
+
loading="lazy"
|
|
118
|
+
decoding="async"
|
|
119
|
+
class="aspect-video w-full object-cover"
|
|
120
|
+
/>
|
|
121
|
+
</a>
|
|
122
|
+
</li>
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
)}
|
|
97
126
|
</figure>
|
|
98
127
|
)}
|
|
99
128
|
</div>
|
package/dist/lib/bookmarks.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
31
31
|
title: z.ZodString;
|
|
32
32
|
url: z.ZodURL;
|
|
33
33
|
}, z.core.$strict>>;
|
|
34
|
+
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
35
|
+
url: z.ZodURL;
|
|
36
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$strict>>>;
|
|
34
38
|
}, z.core.$strict>, z.ZodTransform<{
|
|
35
39
|
shelfLife: "news" | "evergreen";
|
|
36
40
|
postedAt: Date;
|
|
@@ -51,6 +55,10 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
51
55
|
title: string;
|
|
52
56
|
url: string;
|
|
53
57
|
}[];
|
|
58
|
+
media: {
|
|
59
|
+
url: string;
|
|
60
|
+
alt?: string | undefined;
|
|
61
|
+
}[];
|
|
54
62
|
}, {
|
|
55
63
|
id: string;
|
|
56
64
|
url: string;
|
|
@@ -71,6 +79,10 @@ export declare const bookmarkSchema: z.ZodPipe<z.ZodObject<{
|
|
|
71
79
|
title: string;
|
|
72
80
|
url: string;
|
|
73
81
|
}[];
|
|
82
|
+
media: {
|
|
83
|
+
url: string;
|
|
84
|
+
alt?: string | undefined;
|
|
85
|
+
}[];
|
|
74
86
|
}>>;
|
|
75
87
|
export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
76
88
|
id: z.ZodString;
|
|
@@ -103,6 +115,10 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
103
115
|
title: z.ZodString;
|
|
104
116
|
url: z.ZodURL;
|
|
105
117
|
}, z.core.$strict>>;
|
|
118
|
+
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
119
|
+
url: z.ZodURL;
|
|
120
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strict>>>;
|
|
106
122
|
}, z.core.$strict>, z.ZodTransform<{
|
|
107
123
|
shelfLife: "news" | "evergreen";
|
|
108
124
|
postedAt: Date;
|
|
@@ -123,6 +139,10 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
123
139
|
title: string;
|
|
124
140
|
url: string;
|
|
125
141
|
}[];
|
|
142
|
+
media: {
|
|
143
|
+
url: string;
|
|
144
|
+
alt?: string | undefined;
|
|
145
|
+
}[];
|
|
126
146
|
}, {
|
|
127
147
|
id: string;
|
|
128
148
|
url: string;
|
|
@@ -143,6 +163,10 @@ export declare const bookmarksFileSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
|
143
163
|
title: string;
|
|
144
164
|
url: string;
|
|
145
165
|
}[];
|
|
166
|
+
media: {
|
|
167
|
+
url: string;
|
|
168
|
+
alt?: string | undefined;
|
|
169
|
+
}[];
|
|
146
170
|
}>>>;
|
|
147
171
|
export declare const stateFileSchema: z.ZodArray<z.ZodString>;
|
|
148
172
|
export type BookmarkFileEntry = z.input<typeof bookmarkSchema>;
|
package/dist/lib/bookmarks.js
CHANGED
|
@@ -3,6 +3,7 @@ import { TOPICS } from "./topics.js";
|
|
|
3
3
|
export const SCHEMA_VERSION = 1;
|
|
4
4
|
const X_HOSTNAME = /^(www\.|mobile\.)?(x|twitter)\.com$/;
|
|
5
5
|
const X_HANDLE = /^[A-Za-z0-9_]{1,15}$/;
|
|
6
|
+
const X_MEDIA_HOSTNAME = /^pbs\.twimg\.com$/;
|
|
6
7
|
const nonEmpty = z.string().trim().min(1);
|
|
7
8
|
const instant = z.iso.datetime({ offset: true });
|
|
8
9
|
const SHELF_LIVES = ["news", "evergreen"];
|
|
@@ -28,6 +29,9 @@ export const bookmarkSchema = z
|
|
|
28
29
|
key_points: z.array(nonEmpty),
|
|
29
30
|
action: z.string().trim(),
|
|
30
31
|
links: z.array(z.strictObject({ title: nonEmpty, url: z.url({ protocol: /^https$/ }) })),
|
|
32
|
+
media: z
|
|
33
|
+
.array(z.strictObject({ url: z.url({ protocol: /^https$/, hostname: X_MEDIA_HOSTNAME }), alt: nonEmpty.optional() }))
|
|
34
|
+
.default([]),
|
|
31
35
|
})
|
|
32
36
|
.transform(({ posted_at, saved_at, shelf_life, key_points, ...rest }) => ({
|
|
33
37
|
...rest,
|
package/dist/lib/copy.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Bookmark } from "./bookmarks.ts";
|
|
2
|
-
export declare function copyText({ url, author, postedAt, tldr, summary, text, keyPoints, action, links }: Bookmark): string;
|
|
2
|
+
export declare function copyText({ url, author, postedAt, tldr, summary, text, media, keyPoints, action, links, }: Bookmark): string;
|
package/dist/lib/copy.js
CHANGED
|
@@ -8,11 +8,12 @@ function quote(text) {
|
|
|
8
8
|
function list(heading, items) {
|
|
9
9
|
return items.length === 0 ? "" : [`${heading}:`, ...items.map((item) => `- ${item}`)].join("\n");
|
|
10
10
|
}
|
|
11
|
-
export function copyText({ url, author, postedAt, tldr, summary, text, keyPoints, action, links }) {
|
|
11
|
+
export function copyText({ url, author, postedAt, tldr, summary, text, media, keyPoints, action, links, }) {
|
|
12
12
|
return [
|
|
13
13
|
"A post I saved from X:",
|
|
14
14
|
`**${tldr}**\n${author.name} (@${author.handle}) · posted ${dateFormat.format(postedAt)} · ${url}`,
|
|
15
15
|
text === "" ? "" : quote(text),
|
|
16
|
+
list("Images", media.map((image) => (image.alt === undefined ? image.url : `${image.alt}: ${image.url}`))),
|
|
16
17
|
`Summary: ${summary}`,
|
|
17
18
|
list("Key points", keyPoints),
|
|
18
19
|
action === "" ? "" : `Try: ${action}`,
|
package/dist/lib/feed.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isTopic } from "./filter.js";
|
|
2
|
+
import { imagesWithAlt } from "./media.js";
|
|
2
3
|
import { TOPIC_LABELS } from "./topics.js";
|
|
3
4
|
const LOCAL_ORIGIN = "http://localhost:4321";
|
|
4
5
|
export function feedOrigin(site) {
|
|
@@ -30,6 +31,9 @@ function feedItem(bookmark, page) {
|
|
|
30
31
|
link.hash = `b-${id}`;
|
|
31
32
|
const points = keyPoints.map((point) => `<li>${escapeHtml(point)}</li>`).join("");
|
|
32
33
|
const quote = text === "" ? "" : `<blockquote><p>${escapeHtml(text)}</p></blockquote>`;
|
|
34
|
+
const images = imagesWithAlt(bookmark)
|
|
35
|
+
.map((image) => `<p><img src="${escapeHtml(image.url)}" alt="${escapeHtml(image.alt)}"></p>`)
|
|
36
|
+
.join("");
|
|
33
37
|
const linkItems = links
|
|
34
38
|
.map((outbound) => `<li><a href="${escapeHtml(outbound.url)}">${escapeHtml(outbound.title)}</a></li>`)
|
|
35
39
|
.join("");
|
|
@@ -44,6 +48,7 @@ function feedItem(bookmark, page) {
|
|
|
44
48
|
points === "" ? "" : `<ul>${points}</ul>`,
|
|
45
49
|
linkItems === "" ? "" : `<ul>${linkItems}</ul>`,
|
|
46
50
|
quote,
|
|
51
|
+
images,
|
|
47
52
|
`<p><a href="${escapeHtml(url)}">Open on X</a></p>`,
|
|
48
53
|
]
|
|
49
54
|
.filter((part) => part !== "")
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function imagesWithAlt({ author, media }) {
|
|
2
|
+
const position = (index) => (media.length === 1 ? "" : ` ${index + 1} of ${media.length}`);
|
|
3
|
+
return media.map(({ url, alt }, index) => ({
|
|
4
|
+
url,
|
|
5
|
+
alt: alt ?? `Image${position(index)} from the post by @${author.handle}`,
|
|
6
|
+
}));
|
|
7
|
+
}
|
package/package.json
CHANGED