openalmanac 0.2.31 → 0.2.33
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/auth.js +2 -2
- package/dist/login-core.js +3 -3
- package/dist/login.js +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/articles.js +31 -6
- package/dist/tools/auth.js +2 -2
- package/dist/tools/communities.js +20 -2
- package/dist/tools/research.js +36 -2
- package/package.json +1 -1
package/dist/auth.js
CHANGED
|
@@ -42,13 +42,13 @@ export async function getAuthStatus() {
|
|
|
42
42
|
if (!key)
|
|
43
43
|
return { loggedIn: false };
|
|
44
44
|
try {
|
|
45
|
-
const resp = await fetch(`${API_BASE}/api/
|
|
45
|
+
const resp = await fetch(`${API_BASE}/api/users/me`, {
|
|
46
46
|
headers: { Authorization: `Bearer ${key}` },
|
|
47
47
|
signal: AbortSignal.timeout(10_000),
|
|
48
48
|
});
|
|
49
49
|
if (resp.ok) {
|
|
50
50
|
const data = (await resp.json());
|
|
51
|
-
return { loggedIn: true, name: data.
|
|
51
|
+
return { loggedIn: true, name: data.display_name ?? data.username ?? "unknown" };
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
catch {
|
package/dist/login-core.js
CHANGED
|
@@ -7,7 +7,7 @@ function callbackPage(success) {
|
|
|
7
7
|
// These values are hardcoded — never interpolate user input here.
|
|
8
8
|
const title = success ? "You\u2019re connected" : "Something went wrong";
|
|
9
9
|
const messageLine1 = success
|
|
10
|
-
? "Your
|
|
10
|
+
? "Your account is connected and a personal API key is ready to go."
|
|
11
11
|
: "Invalid token. Please return to your terminal and try again.";
|
|
12
12
|
const messageLine2 = success
|
|
13
13
|
? "You can close this tab and return to your terminal."
|
|
@@ -160,13 +160,13 @@ export async function performLogin(options) {
|
|
|
160
160
|
const existingKey = getApiKey();
|
|
161
161
|
if (existingKey) {
|
|
162
162
|
try {
|
|
163
|
-
const resp = await fetch(`${API_BASE}/api/
|
|
163
|
+
const resp = await fetch(`${API_BASE}/api/users/me`, {
|
|
164
164
|
headers: { Authorization: `Bearer ${existingKey}` },
|
|
165
165
|
signal: AbortSignal.timeout(10_000),
|
|
166
166
|
});
|
|
167
167
|
if (resp.ok) {
|
|
168
168
|
const data = (await resp.json());
|
|
169
|
-
return { status: "already_logged_in", name: data.
|
|
169
|
+
return { status: "already_logged_in", name: data.display_name ?? data.username ?? "unknown" };
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
catch {
|
package/dist/login.js
CHANGED
|
@@ -6,7 +6,7 @@ export async function runLogin() {
|
|
|
6
6
|
console.log(`Already logged in as ${result.name}.`);
|
|
7
7
|
}
|
|
8
8
|
else {
|
|
9
|
-
console.log("Logged in.
|
|
9
|
+
console.log("Logged in. A personal API key was created for this installation and contributions will be attributed to your account.");
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
export async function runLogout() {
|
package/dist/server.js
CHANGED
|
@@ -124,7 +124,7 @@ export function createServer() {
|
|
|
124
124
|
"",
|
|
125
125
|
"## Technical workflow",
|
|
126
126
|
"",
|
|
127
|
-
"Reading and searching articles is open. Writing requires an API key (from login). Login
|
|
127
|
+
"Reading and searching articles is open. Writing requires an API key (from login). Login creates a personal API key linked to your user account, so contributions are attributed to you.",
|
|
128
128
|
"",
|
|
129
129
|
"Core flow: login (once) → search_articles (check if exists) → search_web + read_webpage (research) → new (scaffold) or download (existing) → edit ~/.openalmanac/articles/{slug}.md → publish (validate & publish).",
|
|
130
130
|
"",
|
package/dist/tools/articles.js
CHANGED
|
@@ -6,6 +6,24 @@ import { request, ARTICLES_DIR, getAuthStatus } from "../auth.js";
|
|
|
6
6
|
import { validateArticle, parseFrontmatter } from "../validate.js";
|
|
7
7
|
import { openBrowser } from "../browser.js";
|
|
8
8
|
const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
9
|
+
/**
|
|
10
|
+
* Workaround for Claude Agent SDK MCP transport bug (#18260):
|
|
11
|
+
* Array/object parameters are sometimes serialized as JSON strings
|
|
12
|
+
* instead of native values. This preprocessor coerces them back.
|
|
13
|
+
*/
|
|
14
|
+
function coerceJson(schema) {
|
|
15
|
+
return z.preprocess((val) => {
|
|
16
|
+
if (typeof val === "string") {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(val);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return val;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return val;
|
|
25
|
+
}, schema);
|
|
26
|
+
}
|
|
9
27
|
const WRITING_GUIDE = `
|
|
10
28
|
## Article structure
|
|
11
29
|
|
|
@@ -131,7 +149,7 @@ export function registerArticleTools(server) {
|
|
|
131
149
|
"Use this to check if articles or stubs exist before creating them, or to find entity slugs for wikilinks. " +
|
|
132
150
|
"Results include 'stub' field (true/false) and 'entity_type' field. No authentication needed.",
|
|
133
151
|
parameters: z.object({
|
|
134
|
-
queries: z.array(z.string()).min(1).max(20).describe("Search queries (1-20)"),
|
|
152
|
+
queries: coerceJson(z.array(z.string()).min(1).max(20)).describe("Search queries (1-20)"),
|
|
135
153
|
limit: z.number().default(5).describe("Max results per query (1-50, default 5)"),
|
|
136
154
|
include_stubs: z.boolean().default(true).describe("Include stub articles in results (default true)"),
|
|
137
155
|
}),
|
|
@@ -148,7 +166,7 @@ export function registerArticleTools(server) {
|
|
|
148
166
|
"Use this to reference or summarize existing articles in conversation. " +
|
|
149
167
|
"For editing articles locally, use 'download' instead. No authentication needed.",
|
|
150
168
|
parameters: z.object({
|
|
151
|
-
slugs: z.array(z.string()).min(1).max(20).describe("Article slugs to read (1-20)"),
|
|
169
|
+
slugs: coerceJson(z.array(z.string()).min(1).max(20)).describe("Article slugs to read (1-20)"),
|
|
152
170
|
}),
|
|
153
171
|
async execute({ slugs }) {
|
|
154
172
|
const resp = await request("POST", "/api/articles/batch", {
|
|
@@ -163,7 +181,7 @@ export function registerArticleTools(server) {
|
|
|
163
181
|
"Use this for every entity (person, organization, topic, etc.) mentioned in an article. " +
|
|
164
182
|
"Idempotent: existing slugs return their current status. Requires login.",
|
|
165
183
|
parameters: z.object({
|
|
166
|
-
stubs: z.array(z.object({
|
|
184
|
+
stubs: coerceJson(z.array(z.object({
|
|
167
185
|
slug: z
|
|
168
186
|
.string()
|
|
169
187
|
.min(1)
|
|
@@ -187,7 +205,7 @@ export function registerArticleTools(server) {
|
|
|
187
205
|
.optional()
|
|
188
206
|
.describe("2-4 sentence summary of the entity. This becomes the stub page content. " +
|
|
189
207
|
"Be informative — include key facts, dates, and context."),
|
|
190
|
-
})).min(1).max(50).describe("Stubs to create (1-50)"),
|
|
208
|
+
})).min(1).max(50)).describe("Stubs to create (1-50)"),
|
|
191
209
|
}),
|
|
192
210
|
async execute({ stubs }) {
|
|
193
211
|
const resp = await request("POST", "/api/articles/stubs", {
|
|
@@ -300,7 +318,11 @@ export function registerArticleTools(server) {
|
|
|
300
318
|
});
|
|
301
319
|
const data = (await resp.json());
|
|
302
320
|
const articleUrl = `https://www.openalmanac.org/article/${slug}?celebrate=true`;
|
|
303
|
-
|
|
321
|
+
const inGui = process.env.OPENALMANAC_GUI === "1";
|
|
322
|
+
// Skip browser open when running inside the GUI — it handles navigation itself
|
|
323
|
+
if (!inGui) {
|
|
324
|
+
openBrowser(articleUrl);
|
|
325
|
+
}
|
|
304
326
|
// Clean up local files after successful publish
|
|
305
327
|
let cleanupWarning = "";
|
|
306
328
|
try {
|
|
@@ -319,7 +341,10 @@ export function registerArticleTools(server) {
|
|
|
319
341
|
cleanupWarning += `\nNote: could not remove original copy: ${e.message}`;
|
|
320
342
|
}
|
|
321
343
|
}
|
|
322
|
-
|
|
344
|
+
const urlLine = inGui
|
|
345
|
+
? "The article has been published! Let the user know it's live. Do not send them to a web URL."
|
|
346
|
+
: `Article URL (share this exact link with the user): ${articleUrl}`;
|
|
347
|
+
return `Pushed successfully.\n\n${urlLine}${cleanupWarning}\n\n${JSON.stringify(data, null, 2)}`;
|
|
323
348
|
},
|
|
324
349
|
});
|
|
325
350
|
server.addTool({
|
package/dist/tools/auth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { performLogin } from "../login-core.js";
|
|
|
3
3
|
export function registerAuthTools(server) {
|
|
4
4
|
server.addTool({
|
|
5
5
|
name: "login",
|
|
6
|
-
description: "Log in via browser to
|
|
6
|
+
description: "Log in via browser to connect your account and get a personal API key. This is the required " +
|
|
7
7
|
"first step before creating or updating articles. Only needs to be called once.\n\n" +
|
|
8
8
|
"If you already have a valid API key, this returns immediately without opening a browser.",
|
|
9
9
|
async execute() {
|
|
@@ -11,7 +11,7 @@ export function registerAuthTools(server) {
|
|
|
11
11
|
if (result.status === "already_logged_in") {
|
|
12
12
|
return `Already logged in as ${result.name}.`;
|
|
13
13
|
}
|
|
14
|
-
return "Logged in.
|
|
14
|
+
return "Logged in. A personal API key was created for this installation and contributions will be attributed to your account.";
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
17
|
server.addTool({
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { request } from "../auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Workaround for Claude Agent SDK MCP transport bug (#18260):
|
|
5
|
+
* Array/object parameters are sometimes serialized as JSON strings
|
|
6
|
+
* instead of native values. This preprocessor coerces them back.
|
|
7
|
+
*/
|
|
8
|
+
function coerceJson(schema) {
|
|
9
|
+
return z.preprocess((val) => {
|
|
10
|
+
if (typeof val === "string") {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(val);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return val;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return val;
|
|
19
|
+
}, schema);
|
|
20
|
+
}
|
|
3
21
|
export function registerCommunityTools(server) {
|
|
4
22
|
server.addTool({
|
|
5
23
|
name: "search_communities",
|
|
@@ -98,10 +116,10 @@ export function registerCommunityTools(server) {
|
|
|
98
116
|
"Idempotent — already-linked articles are reported but don't cause errors. Requires login.",
|
|
99
117
|
parameters: z.object({
|
|
100
118
|
article_id: z.string().describe("Article slug/ID to link (e.g. 'machine-learning')"),
|
|
101
|
-
community_slugs: z
|
|
119
|
+
community_slugs: coerceJson(z
|
|
102
120
|
.array(z.string())
|
|
103
121
|
.min(1)
|
|
104
|
-
.max(50)
|
|
122
|
+
.max(50))
|
|
105
123
|
.describe("List of community slugs to link the article to (max 50)"),
|
|
106
124
|
}),
|
|
107
125
|
async execute({ article_id, community_slugs }) {
|
package/dist/tools/research.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { imageContent } from "fastmcp";
|
|
3
3
|
import { request } from "../auth.js";
|
|
4
|
+
/**
|
|
5
|
+
* Workaround for Claude Agent SDK MCP transport bug (#18260):
|
|
6
|
+
* Array/object parameters are sometimes serialized as JSON strings
|
|
7
|
+
* instead of native values. This preprocessor coerces them back.
|
|
8
|
+
*/
|
|
9
|
+
function coerceJson(schema) {
|
|
10
|
+
return z.preprocess((val) => {
|
|
11
|
+
if (typeof val === "string") {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(val);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return val;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return val;
|
|
20
|
+
}, schema);
|
|
21
|
+
}
|
|
4
22
|
export function registerResearchTools(server) {
|
|
5
23
|
server.addTool({
|
|
6
24
|
name: "search_web",
|
|
@@ -70,7 +88,7 @@ export function registerResearchTools(server) {
|
|
|
70
88
|
"- For the infobox hero image, set `infobox.header.image_url` in frontmatter instead\n\n" +
|
|
71
89
|
"Requires login. Rate limit: 10/min.",
|
|
72
90
|
parameters: z.object({
|
|
73
|
-
queries: z.array(z.string()).min(1).max(10).describe("Image search queries (1-10)"),
|
|
91
|
+
queries: coerceJson(z.array(z.string()).min(1).max(10)).describe("Image search queries (1-10)"),
|
|
74
92
|
source: z.enum(["wikimedia", "google"]).default("wikimedia").describe("Image source: 'wikimedia' (free, open-licensed — preferred) or 'google' (broader coverage)"),
|
|
75
93
|
limit: z.number().default(5).describe("Max results per query (1-10, default 5)"),
|
|
76
94
|
}),
|
|
@@ -87,7 +105,7 @@ export function registerResearchTools(server) {
|
|
|
87
105
|
description: "View images to verify they're suitable. Use after search_images to inspect candidates " +
|
|
88
106
|
"before including them. Returns each image so you can see what it shows and write accurate captions.",
|
|
89
107
|
parameters: z.object({
|
|
90
|
-
urls: z.array(z.string().url()).min(1).max(10).describe("Image URLs to view (1-10)"),
|
|
108
|
+
urls: coerceJson(z.array(z.string().url()).min(1).max(10)).describe("Image URLs to view (1-10)"),
|
|
91
109
|
}),
|
|
92
110
|
async execute({ urls }) {
|
|
93
111
|
const results = await Promise.allSettled(urls.map(async (url) => {
|
|
@@ -112,4 +130,20 @@ export function registerResearchTools(server) {
|
|
|
112
130
|
return { content };
|
|
113
131
|
},
|
|
114
132
|
});
|
|
133
|
+
server.addTool({
|
|
134
|
+
name: "register_sources",
|
|
135
|
+
description: "Register sources you plan to cite in your response. Call this BEFORE writing your response text. " +
|
|
136
|
+
"Each source becomes a clickable citation bubble when you use [@key] markers in your text. " +
|
|
137
|
+
"Collect sources from your read_webpage calls and any subagent results, then register them all in one call.",
|
|
138
|
+
parameters: z.object({
|
|
139
|
+
sources: coerceJson(z.array(z.object({
|
|
140
|
+
key: z.string().describe("Citation key — kebab-case, BibTeX-style: {domain}-{title-words} (e.g. 'nytimes-climate-report')"),
|
|
141
|
+
url: z.string().describe("Source URL"),
|
|
142
|
+
title: z.string().describe("Source title — include publication name after em dash (e.g. 'Climate Report — The New York Times')"),
|
|
143
|
+
})).min(1)).describe("Sources to register for citation"),
|
|
144
|
+
}),
|
|
145
|
+
async execute({ sources }) {
|
|
146
|
+
return `Registered ${sources.length} source${sources.length === 1 ? "" : "s"}. Use [@key] markers in your response to cite them.`;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
115
149
|
}
|