openalmanac 0.2.26 → 0.2.28

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 CHANGED
@@ -88,6 +88,9 @@ export async function request(method, path, options = {}) {
88
88
  }
89
89
  const resp = await fetch(url, init);
90
90
  if (!resp.ok) {
91
+ if (resp.status === 401 || resp.status === 403) {
92
+ throw new Error(`Authentication failed (${resp.status}). Your API key may be invalid or expired. Run 'login' to re-authenticate.`);
93
+ }
91
94
  const text = await resp.text();
92
95
  throw new Error(`${resp.status} ${resp.statusText}: ${text}`);
93
96
  }
package/dist/browser.js CHANGED
@@ -1,13 +1,14 @@
1
- import { execSync } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import { platform } from "node:os";
3
3
  export function openBrowser(url) {
4
4
  const cmd = platform() === "darwin"
5
- ? `open "${url}"`
5
+ ? "open"
6
6
  : platform() === "win32"
7
- ? `start "" "${url}"`
8
- : `xdg-open "${url}"`;
7
+ ? "start"
8
+ : "xdg-open";
9
9
  try {
10
- execSync(cmd, { stdio: "ignore" });
10
+ const args = platform() === "win32" ? ["", url] : [url];
11
+ execFileSync(cmd, args, { stdio: "ignore" });
11
12
  return true;
12
13
  }
13
14
  catch {
package/dist/server.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
1
4
  import { FastMCP } from "fastmcp";
2
5
  import { registerAuthTools } from "./tools/auth.js";
3
6
  import { registerArticleTools } from "./tools/articles.js";
@@ -5,6 +8,8 @@ import { registerResearchTools } from "./tools/research.js";
5
8
  import { registerCommunityTools } from "./tools/communities.js";
6
9
  import { registerPeopleTools } from "./tools/people.js";
7
10
  import { getApiKey } from "./auth.js";
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
8
13
  export function createServer() {
9
14
  if (!getApiKey()) {
10
15
  console.error(`
@@ -24,7 +29,7 @@ export function createServer() {
24
29
  }
25
30
  const server = new FastMCP({
26
31
  name: "OpenAlmanac",
27
- version: "0.1.0",
32
+ version: pkg.version,
28
33
  instructions: [
29
34
  "OpenAlmanac is an open knowledge base — a Wikipedia anyone can read from and write to through an API. Articles are markdown files with YAML frontmatter and [N] citation markers mapped to sources.",
30
35
  "",
package/dist/setup.js CHANGED
@@ -127,7 +127,8 @@ const vis = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
127
127
  const w = (s) => process.stdout.write(s + "\n");
128
128
  /* ── File helpers ───────────────────────────────────────────────── */
129
129
  const CLAUDE_DIR = join(homedir(), ".claude");
130
- const CLAUDE_JSON = join(homedir(), ".claude.json");
130
+ const CLAUDE_JSON = join(homedir(), ".claude.json"); // Claude Desktop
131
+ const CLAUDE_CODE_MCP = join(CLAUDE_DIR, "mcp.json"); // Claude Code
131
132
  const SETTINGS_JSON = join(CLAUDE_DIR, "settings.json");
132
133
  function ensureDir(dir) {
133
134
  if (!existsSync(dir))
@@ -145,19 +146,35 @@ function writeJson(path, data) {
145
146
  writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
146
147
  }
147
148
  /* ── Step 1 — MCP server ───────────────────────────────────────── */
149
+ const ALMANAC_MCP_ENTRY = { command: "npx", args: ["-y", "openalmanac@latest"] };
150
+ function isAlmanacCurrent(server) {
151
+ return (server?.command === "npx" &&
152
+ JSON.stringify(server.args) === JSON.stringify(ALMANAC_MCP_ENTRY.args));
153
+ }
148
154
  function configureMcp() {
155
+ let changed = false;
156
+ // Claude Desktop — ~/.claude.json
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ const desktop = readJson(CLAUDE_JSON);
159
+ if (!desktop.mcpServers)
160
+ desktop.mcpServers = {};
161
+ if (!isAlmanacCurrent(desktop.mcpServers.almanac)) {
162
+ desktop.mcpServers.almanac = ALMANAC_MCP_ENTRY;
163
+ writeJson(CLAUDE_JSON, desktop);
164
+ changed = true;
165
+ }
166
+ // Claude Code — ~/.claude/mcp.json
167
+ ensureDir(CLAUDE_DIR);
149
168
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
- const cfg = readJson(CLAUDE_JSON);
151
- if (!cfg.mcpServers)
152
- cfg.mcpServers = {};
153
- const cur = cfg.mcpServers.almanac;
154
- if (cur?.command === "npx" &&
155
- JSON.stringify(cur.args) === JSON.stringify(["-y", "openalmanac"])) {
156
- return false; // already set
169
+ const code = readJson(CLAUDE_CODE_MCP);
170
+ if (!code.mcpServers)
171
+ code.mcpServers = {};
172
+ if (!isAlmanacCurrent(code.mcpServers.almanac)) {
173
+ code.mcpServers.almanac = ALMANAC_MCP_ENTRY;
174
+ writeJson(CLAUDE_CODE_MCP, code);
175
+ changed = true;
157
176
  }
158
- cfg.mcpServers.almanac = { command: "npx", args: ["-y", "openalmanac"] };
159
- writeJson(CLAUDE_JSON, cfg);
160
- return true;
177
+ return changed;
161
178
  }
162
179
  /* ── Step 2 — Permissions ──────────────────────────────────────── */
163
180
  function configurePermissions(tools) {
@@ -492,7 +509,7 @@ function printResult(agent, loginResult, mcpChanged, toolCount) {
492
509
  stepDone(`${BLUE}Setup complete${RST}`);
493
510
  w("");
494
511
  // Next steps box
495
- const innerW = 52;
512
+ const innerW = 62;
496
513
  const row = (content) => {
497
514
  const padding = Math.max(0, innerW - vis(content));
498
515
  return ` ${BLUE_DIM}\u2502${RST}${content}${" ".repeat(padding)}${BLUE_DIM}\u2502${RST}`;
@@ -218,7 +218,7 @@ export function registerArticleTools(server) {
218
218
  ? "\n\nThis is a STUB article — a placeholder that hasn't been fully written yet. " +
219
219
  "Fill in the content body with a complete article, then push to publish."
220
220
  : "";
221
- return `Pulled "${title}" to ${filePath}\n${wordCount} words, ${frontmatter.sources?.length ?? 0} sources.${stubNote}\n\n${WRITING_GUIDE}`;
221
+ return `Downloaded "${title}" to ${filePath}\n${wordCount} words, ${frontmatter.sources?.length ?? 0} sources.${stubNote}\n\n${WRITING_GUIDE}`;
222
222
  },
223
223
  });
224
224
  server.addTool({
package/dist/validate.js CHANGED
@@ -47,14 +47,18 @@ export function validateArticle(raw) {
47
47
  if (!s.title || typeof s.title !== "string") {
48
48
  errors.push({ field: `sources[${i}].title`, message: "Title is required" });
49
49
  }
50
- if (!s.accessed_date || typeof s.accessed_date !== "string") {
50
+ const accessedDate = s.accessed_date;
51
+ if (!accessedDate) {
51
52
  errors.push({ field: `sources[${i}].accessed_date`, message: "Accessed date is required" });
52
53
  }
53
- else if (!DATE_RE.test(s.accessed_date)) {
54
- errors.push({
55
- field: `sources[${i}].accessed_date`,
56
- message: "Must be YYYY-MM-DD format",
57
- });
54
+ else if (accessedDate instanceof Date) {
55
+ // YAML parsed it as a Date object — valid
56
+ }
57
+ else if (typeof accessedDate === "string" && !DATE_RE.test(accessedDate)) {
58
+ errors.push({ field: `sources[${i}].accessed_date`, message: "Must be YYYY-MM-DD format" });
59
+ }
60
+ else if (typeof accessedDate !== "string" && !(accessedDate instanceof Date)) {
61
+ errors.push({ field: `sources[${i}].accessed_date`, message: "Must be YYYY-MM-DD format" });
58
62
  }
59
63
  }
60
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "OpenAlmanac — pull, edit, and push articles to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {