openalmanac 0.2.12 → 0.2.14

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/dist/server.js CHANGED
@@ -43,8 +43,16 @@ export function createServer() {
43
43
  "or create it via create_stub. Links to non-existent slugs are stripped to plain text on push.\n\n" +
44
44
  "Workflow: For each entity mentioned → search_articles (check if exists) → if not, create_stub " +
45
45
  "with title, entity_type, headline, and a 2-4 sentence summary → then use [[slug|Display Text]] in your article.\n\n" +
46
- "For people: call search_people to find their LinkedIn-backed slug, then create_stub. " +
46
+ "For people: call search_people to find their LinkedIn-backed slug and profile_url, then " +
47
+ "read_webpage(url=profile_url) to get their full profile with a high-resolution photo, then " +
48
+ "create_stub with the image_url from the ![Profile Photo](url) line in the scraped markdown. " +
47
49
  "For topics/orgs/events: use descriptive kebab-case slugs (e.g. 'reinforcement-learning', 'openai').\n\n" +
50
+ "## Images\n\n" +
51
+ "Use search_images to find images for articles. Place images inline using `![Descriptive caption](url)` " +
52
+ "on the line before the paragraph they illustrate. Images float right on desktop. " +
53
+ "Every image must have a descriptive caption. Use 1-3 images per major section. " +
54
+ "For the infobox hero image, set infobox.header.image_url in frontmatter. " +
55
+ "External image URLs are auto-persisted on push — no extra steps needed.\n\n" +
48
56
  "After creating an article, always share the exact URL from the push response with the user. " +
49
57
  "This URL includes a celebration page for the newly created article.\n\n" +
50
58
  "After pushing an article, call search_communities to find relevant communities. " +
@@ -82,6 +82,27 @@ Include an infobox for any article about a person, place, organization, event, o
82
82
  - Every source in the sources list must be referenced at least once in the body
83
83
  - Every [N] marker must have a matching source
84
84
 
85
+ ## Images
86
+
87
+ Use search_images to find relevant images. Place them inline using standard markdown:
88
+
89
+ \`\`\`markdown
90
+ ![Alan Turing in 1930, aged 18](https://upload.wikimedia.org/...)
91
+
92
+ The early life of Alan Turing began...
93
+ \`\`\`
94
+
95
+ The image goes on the line BEFORE the paragraph it illustrates. It floats right on desktop.
96
+
97
+ **Caption rules:**
98
+ - Every image MUST have a descriptive caption (the alt text)
99
+ - Describe what the image shows: "Alan Turing in 1930, aged 18" not "Photo"
100
+ - For the infobox hero image, use \`infobox.header.image_url\` in frontmatter instead
101
+
102
+ **Placement:** 1-3 images per major section, spread throughout. First image near the top.
103
+
104
+ External image URLs are auto-persisted on push — no extra steps needed.
105
+
85
106
  ## Writing quality
86
107
 
87
108
  - Every sentence should contain a specific fact the reader didn't know
@@ -230,9 +251,10 @@ export function registerArticleTools(server) {
230
251
  "validates locally (frontmatter, citations, sources), then pushes via the API. Requires login.",
231
252
  parameters: z.object({
232
253
  slug: z.string().describe("Article slug matching the filename (without .md)"),
233
- change_summary: z.string().optional().describe("Brief description of changes"),
254
+ change_title: z.string().optional().describe("Short title for the change (e.g. 'Added early life section')"),
255
+ change_description: z.string().optional().describe("Longer description of what changed and why"),
234
256
  }),
235
- async execute({ slug, change_summary }) {
257
+ async execute({ slug, change_title, change_description }) {
236
258
  const filePath = join(ARTICLES_DIR, `${slug}.md`);
237
259
  let raw;
238
260
  try {
@@ -247,11 +269,14 @@ export function registerArticleTools(server) {
247
269
  const lines = errors.map((e) => ` ${e.field}: ${e.message}`);
248
270
  throw new Error(`Validation failed (${errors.length} error${errors.length > 1 ? "s" : ""}):\n${lines.join("\n")}\n\nFix the file and try again.`);
249
271
  }
250
- // Inject change_summary into frontmatter if provided
272
+ // Inject change_title/change_description into frontmatter if provided
251
273
  let body = raw;
252
- if (change_summary) {
274
+ if (change_title || change_description) {
253
275
  const { frontmatter, content } = parseFrontmatter(raw);
254
- frontmatter.change_summary = change_summary;
276
+ if (change_title)
277
+ frontmatter.change_title = change_title;
278
+ if (change_description)
279
+ frontmatter.change_description = change_description;
255
280
  const newFrontmatter = yamlStringify(frontmatter);
256
281
  body = `---\n${newFrontmatter}---\n${content}`;
257
282
  }
@@ -266,6 +291,21 @@ export function registerArticleTools(server) {
266
291
  return `Pushed successfully.\n\nArticle URL (share this exact link with the user): ${articleUrl}\n\n${JSON.stringify(data, null, 2)}`;
267
292
  },
268
293
  });
294
+ server.addTool({
295
+ name: "requested_articles",
296
+ description: "List requested articles — stubs that are referenced by the most articles but haven't been fully written yet. " +
297
+ "Use this to find high-demand topics to write about. No authentication needed.",
298
+ parameters: z.object({
299
+ limit: z.number().default(20).describe("Max results (1-200, default 20)"),
300
+ offset: z.number().default(0).describe("Pagination offset (default 0)"),
301
+ }),
302
+ async execute({ limit, offset }) {
303
+ const resp = await request("GET", "/api/articles/requested", {
304
+ params: { limit, offset },
305
+ });
306
+ return JSON.stringify(await resp.json(), null, 2);
307
+ },
308
+ });
269
309
  server.addTool({
270
310
  name: "status",
271
311
  description: "Show login status and list all article files in your local working directory (~/.openalmanac/articles/). " +
@@ -40,4 +40,37 @@ export function registerResearchTools(server) {
40
40
  return JSON.stringify(data, null, 2);
41
41
  },
42
42
  });
43
+ server.addTool({
44
+ name: "search_images",
45
+ description: "Search for images to include in articles. Returns image URLs, titles, dimensions, and licensing info. " +
46
+ "Two sources: 'wikimedia' (free, open-licensed images from Wikimedia Commons — preferred) and 'google' (broader web images via Google). " +
47
+ "Use descriptive search terms. Pick images based on their title and description. " +
48
+ "External image URLs are automatically persisted when you push the article — no extra steps needed.\n\n" +
49
+ "## Using images in articles\n\n" +
50
+ "Add images inline using markdown: `![Descriptive caption](image_url)`\n\n" +
51
+ "Place the image on the line BEFORE the paragraph it illustrates. The image floats to the right of the text on desktop.\n\n" +
52
+ "**Caption rules:**\n" +
53
+ "- Every image MUST have a descriptive caption (the alt text in `![caption here](url)`)\n" +
54
+ "- Captions should describe what the image shows, not just name the subject\n" +
55
+ "- Bad: `![Logo](url)` — Good: `![The OpenAI logo, a stylized spiral](url)`\n" +
56
+ "- Bad: `![Photo](url)` — Good: `![Alan Turing in 1930, aged 18](url)`\n\n" +
57
+ "**Placement rules:**\n" +
58
+ "- Place 1-3 images per major section — don't overload\n" +
59
+ "- First image should appear near the top, illustrating the article's subject\n" +
60
+ "- Spread images throughout, not clustered together\n" +
61
+ "- For the infobox hero image, set `infobox.header.image_url` in frontmatter instead\n\n" +
62
+ "Requires login. Rate limit: 10/min.",
63
+ parameters: z.object({
64
+ query: z.string().describe("Descriptive search terms for the image (e.g. 'Apollo 11 moon landing photograph')"),
65
+ source: z.enum(["wikimedia", "google"]).default("wikimedia").describe("Image source: 'wikimedia' (free, open-licensed — preferred) or 'google' (broader coverage)"),
66
+ limit: z.number().default(10).describe("Max results (1-30, default 10)"),
67
+ }),
68
+ async execute({ query, source, limit }) {
69
+ const resp = await request("GET", "/api/research/images", {
70
+ auth: true,
71
+ params: { query, source, limit },
72
+ });
73
+ return JSON.stringify(await resp.json(), null, 2);
74
+ },
75
+ });
43
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "OpenAlmanac — pull, edit, and push articles to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,6 @@
32
32
  "node": ">=18.0.0"
33
33
  },
34
34
  "files": [
35
- "dist",
36
- "skill"
35
+ "dist"
37
36
  ]
38
37
  }