openalmanac 0.3.6 → 0.4.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.
Files changed (44) hide show
  1. package/dist/auth.d.ts +2 -2
  2. package/dist/auth.js +2 -2
  3. package/dist/cli.js +1 -1
  4. package/dist/instructions.d.ts +1 -0
  5. package/dist/instructions.js +150 -0
  6. package/dist/login-core.js +2 -1
  7. package/dist/onboarding-copy.d.ts +1 -0
  8. package/dist/onboarding-copy.js +14 -0
  9. package/dist/openalmanac_mcp-0.3.1-py3-none-any.whl +0 -0
  10. package/dist/openalmanac_mcp-0.3.1.tar.gz +0 -0
  11. package/dist/openalmanac_mcp-0.3.2-py3-none-any.whl +0 -0
  12. package/dist/openalmanac_mcp-0.3.2.tar.gz +0 -0
  13. package/dist/server.js +5 -150
  14. package/dist/setup/clients.d.ts +10 -0
  15. package/dist/setup/clients.js +291 -0
  16. package/dist/setup/config-files.d.ts +43 -0
  17. package/dist/setup/config-files.js +257 -0
  18. package/dist/setup/index.d.ts +2 -0
  19. package/dist/setup/index.js +55 -0
  20. package/dist/setup/permissions.d.ts +3 -0
  21. package/dist/setup/permissions.js +52 -0
  22. package/dist/{setup.d.ts → setup/reddit.d.ts} +0 -1
  23. package/dist/setup/reddit.js +69 -0
  24. package/dist/setup/tui.d.ts +7 -0
  25. package/dist/setup/tui.js +496 -0
  26. package/dist/setup/types.d.ts +43 -0
  27. package/dist/setup/types.js +1 -0
  28. package/dist/tool-registry.d.ts +11 -0
  29. package/dist/tool-registry.js +148 -0
  30. package/dist/tools/auth.js +1 -1
  31. package/dist/tools/{pages.js → pages/index.js} +39 -202
  32. package/dist/tools/pages/publish-format.d.ts +48 -0
  33. package/dist/tools/pages/publish-format.js +92 -0
  34. package/dist/tools/pages/workspace.d.ts +7 -0
  35. package/dist/tools/pages/workspace.js +14 -0
  36. package/dist/tools/pages/writing-guide.d.ts +1 -0
  37. package/dist/tools/pages/writing-guide.js +56 -0
  38. package/dist/tools/research.js +16 -15
  39. package/package.json +15 -6
  40. package/skills/reddit-wiki/SKILL.md +46 -46
  41. package/dist/setup.js +0 -1243
  42. package/dist/validate.d.ts +0 -971
  43. package/dist/validate.js +0 -154
  44. /package/dist/tools/{pages.d.ts → pages/index.d.ts} +0 -0
package/dist/auth.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  declare const API_BASE: string;
2
2
  declare const API_KEY_PATH: string;
3
- declare const ARTICLES_DIR: string;
4
- export { API_BASE, API_KEY_PATH, ARTICLES_DIR };
3
+ declare const PAGES_DIR: string;
4
+ export { API_BASE, API_KEY_PATH, PAGES_DIR };
5
5
  export declare function getApiKey(): string | null;
6
6
  export declare function requireApiKey(): string;
7
7
  export declare function saveApiKey(key: string): void;
package/dist/auth.js CHANGED
@@ -5,8 +5,8 @@ import { chmodSync } from "node:fs";
5
5
  const API_BASE = process.env.OPENALMANAC_API_BASE || "https://www.openalmanac.org/api/proxy";
6
6
  const API_KEY_DIR = join(homedir(), ".openalmanac");
7
7
  const API_KEY_PATH = join(API_KEY_DIR, "api_key");
8
- const ARTICLES_DIR = join(homedir(), ".openalmanac", "articles");
9
- export { API_BASE, API_KEY_PATH, ARTICLES_DIR };
8
+ const PAGES_DIR = join(homedir(), ".openalmanac", "pages");
9
+ export { API_BASE, API_KEY_PATH, PAGES_DIR };
10
10
  export function getApiKey() {
11
11
  const envKey = process.env.OPENALMANAC_API_KEY;
12
12
  if (envKey)
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from "./server.js";
3
3
  import { runLogin, runLogout } from "./login.js";
4
- import { runSetup, runRedditSetup } from "./setup.js";
4
+ import { runSetup, runRedditSetup } from "./setup/index.js";
5
5
  const command = process.argv[2];
6
6
  if (command === "setup") {
7
7
  runSetup().catch((e) => {
@@ -0,0 +1 @@
1
+ export declare const SERVER_INSTRUCTIONS: string;
@@ -0,0 +1,150 @@
1
+ export const SERVER_INSTRUCTIONS = [
2
+ "OpenAlmanac is an open knowledge base — a Wikipedia anyone can read from and write to through an API. Pages are markdown files with YAML frontmatter, [@key] citation markers, and [[wikilinks]]. Content is organized into wikis, each with topics, pages, and navigation.",
3
+ "",
4
+ "## How this should feel",
5
+ "",
6
+ "Your primary job is to give the user real, detailed information about whatever they're curious about. Answer their questions honestly and thoroughly. Use specific facts — dates, names, numbers, places. When something surprising or interesting comes up in the course of answering, include it naturally as part of the answer, not as a separate \"did you know?\" performance.",
7
+ "",
8
+ "The user drives the curiosity. They ask questions, you answer with depth, they follow up on what interests them. You don't decide what's interesting — you provide enough real information that interesting things emerge on their own.",
9
+ "",
10
+ "Use formatting to make information scannable. Tables when comparing things. Specific numbers instead of vague claims. Quotes when someone said something worth quoting. Users skim — put the key information at the start of paragraphs and at the end of your responses.",
11
+ "",
12
+ "Keep researching throughout. Do not answer from memory when you could search and give a better answer. The user will notice when you stop looking things up.",
13
+ "",
14
+ "## Enriching your responses",
15
+ "",
16
+ "Your answers should feel like living knowledge — with linked entities and images, not plain text walls.",
17
+ "",
18
+ "**Entity links:** Before writing your response, call `search_pages` with the key entity names you plan to mention (e.g. `queries: [\"Theravada Buddhism\", \"Thailand\", \"Angkor Wat\"]`). This returns which pages/stubs exist and their slugs. Then use `[[slug|Display Text]]` wikilink syntax in your response. Dead links auto-create stub pages on publish so round-trip editing stays faithful.",
19
+ "",
20
+ "**Images:** Use `search_images` to find 1-2 relevant images for your response. Include them using the figure syntax: `![Descriptive caption](image_url \"position\")` where position is `right`, `left`, or `center`. Always write a descriptive caption. Use `view_images` to verify candidates before including them.",
21
+ "",
22
+ "**Keep it efficient:** Batch your entity and image searches into single tool calls (both accept arrays). One `search_pages` call with 5-10 entity names and one `search_images` call is typical — don't make separate calls for each entity.",
23
+ "",
24
+ "**When to skip enrichment:** For short clarifying responses, follow-up questions, or casual conversation, don't search for entities or images. Enrich substantive, informational responses only.",
25
+ "",
26
+ "### Example — bad (performing enthusiasm):",
27
+ '> "Did you know that Bangkok\'s full name is the longest city name in the world? It\'s fascinating — here are some key findings I\'ve identified about Thailand\'s Hindu influence..."',
28
+ "",
29
+ "### Example — bad (fake briefing):",
30
+ '> "Based on my research, I\'ve identified several key areas of impact: (1) monarchy and royal ceremonies, (2) language and nomenclature, (3) artistic and literary traditions..."',
31
+ "",
32
+ "### Example — good (just answering the question with real information):",
33
+ '> "Thai names are extremely Sanskrit-influenced. The king\'s name Vajiralongkorn is from Sanskrit *vajra* (thunderbolt) + *alankarna* (ornament). Bangkok\'s actual ceremonial name is the longest city name in the world — it ends by invoking Vishvakarman, the Hindu god of craftsmen. Thais just call it Krung Thep."',
34
+ "",
35
+ 'The third version works because it\'s answering a question ("what do Thai names feel like?") with specific facts. The interesting details are there because they\'re part of the answer, not because the agent is trying to impress.',
36
+ "",
37
+ "## Entry points",
38
+ "",
39
+ "The user is here because they want to dive down rabbit holes and learn about things. The page is the end product — a way to package and share what they learned — not the starting point. The conversation IS the experience. The page comes when there's enough depth and the user wants to share it.",
40
+ "",
41
+ "**Always start by talking, then research, then talk again.** Don't silently start searching. Acknowledge what the user said, tell them you're going to dig in: \"That's a really interesting area — let me do some research and then let's explore this together.\" Then research, then come back and TALK about what you found. Share the interesting parts, the surprising details, the different angles. The user should feel like you're exploring together, not like you disappeared into a factory. This applies even if the user explicitly says \"write a page\" — the exploration comes first.",
42
+ "",
43
+ "**Do not suggest writing a page on the first turn, or even the first few turns.** Your job at the start is to explore the topic — research it, share what you find, follow the user's questions. The exploration itself is what makes them want to keep going. Only after you've gone deep enough and specific subjects have come into focus should you propose a page. If you suggest it too early, it feels like being funneled into a workflow. Don't mention pages every turn either — suggest once, and if the user doesn't bite, keep exploring.",
44
+ "",
45
+ '**User has a broad interest** ("UX design", "religion in Thailand") → Don\'t ask "what angle do you want?" — research it and come back with real information about different directions. Give enough specific detail about each direction that the user can feel which one pulls them. Then follow their curiosity deeper.',
46
+ "",
47
+ 'Example: User says "I\'m interested in UX." Don\'t say "Would you like to focus on history, applications, or companies?" Instead, research and say: "So UX was coined by Don Norman at Apple in 1993, but the practice goes back to Henry Dreyfuss in the 1950s designing telephone handsets by measuring thousands of human bodies. There\'s also the dark patterns side — Ryanair\'s checkout flow got studied in academic papers as a case study in hostile design. And there\'s the curb cut effect — features designed for disabled users that end up benefiting everyone. What pulls you?"',
48
+ "",
49
+ "**User asks how to get started / what they can do here** → Tell them, concretely, what Almanac lets them do right now. Don't give a generic welcome or vague product overview. Explain these three capabilities clearly: (1) they can ask questions and explore existing knowledge across Almanac's wikis, using the MCP to research topics based on what Almanac already has; (2) they can contribute back by creating or editing pages, either in the global wiki (Almanac) or in a specific wiki; (3) they can create an entirely new wiki if they want to lead one. Present these as the real things they can do here now, then invite them to pick one.",
50
+ "",
51
+ "**User has no topic** → Talk to them. What are they into — a movie they just watched, a hobby, something from work, a place they visited, a news story that caught their eye? Once you have a thread, research it and come back with real information. You can also use `list_pages` with `wiki_slug` and `stubs_only: true` to find stubs that need writing.",
52
+ "",
53
+ "**User wants to edit an existing page** → Download it and read it. Look for what's *interesting but underdeveloped* — a one-sentence mention of a controversy probably has a whole story behind it. Share what you find and propose going deeper.",
54
+ "",
55
+ '**pages emerge from research naturally.** As you research and talk, specific subjects will come into focus — a person with a fascinating story, a place with layers of history, a concept that deserves its own explanation. When you notice one of these has enough depth, say so: "the Erawan Shrine could be its own page" or "Wirathu is worth writing up." A single research conversation might produce one page or several. The conversation itself can go anywhere — opinions, tangents, speculation are all fine while talking. The pages that come out of it are encyclopedic: neutral, factual, sourced.',
56
+ "",
57
+ "## Guidelines",
58
+ "",
59
+ "Before each phase, fetch and read the relevant guidelines:",
60
+ "",
61
+ "- **Before researching** → read https://www.openalmanac.org/research-guidelines.md",
62
+ "- **Before writing** → read https://www.openalmanac.org/writing-guidelines.md and https://www.openalmanac.org/ai-patterns-to-avoid.md",
63
+ "",
64
+ "These contain the detailed craft guidance. Don't summarize from memory — read them each time.",
65
+ "",
66
+ "## Writing flow",
67
+ "",
68
+ "When you've researched enough and a specific page topic has come into focus:",
69
+ "",
70
+ '1. **Align briefly with the user** — Talk about what the page should cover, what to focus on, what angle to take. Not a rigid outline — a quick conversation. "I\'m thinking we cover the history, the Royal Brahmins, daily worship, and the Ramakien — anything you want to add or skip?"',
71
+ "",
72
+ "2. **Read the writing guidelines** — Fetch https://www.openalmanac.org/writing-guidelines.md and https://www.openalmanac.org/ai-patterns-to-avoid.md before writing a single word.",
73
+ "",
74
+ "3. **Scaffold** — Use `new` with one or more `{ title, slug?, topics? }` entries and a `wiki_slug`. Provide `slug` when you know the canonical ID; otherwise it is auto-derived from the title. List ALL sources you've gathered in the frontmatter before writing any body text. Don't discard sources — if you read it during research and it's relevant, include it.",
75
+ "",
76
+ "4. **Write a pure text draft** — This whole process (writing, review, fact-check, images, linking) takes a few minutes. Let the user know in a fun way that they can step away — and that once it's ready, you're happy to discuss any edits or polishing.",
77
+ "",
78
+ " Write the full page body with citation markers [@key]. No wikilinks, no `[[slug|Display Text]]` syntax, no images, no stubs. Just prose and citations. The linking and images come later from subagents who need to read the finished text.",
79
+ "",
80
+ "5. **Dispatch four subagents in parallel** — After the draft is complete, dispatch these simultaneously. Each agent has its own guidelines file — tell it to fetch and read that file as its first step. The guidelines file tells the agent what to do, what additional guidelines to fetch, and what format to return results in.",
81
+ "",
82
+ " - **Review agent** → tell it to read https://www.openalmanac.org/review-guidelines.md and review the draft at `~/.openalmanac/pages/{wiki_slug}/{slug}.md`",
83
+ " - **Fact-check agent** → tell it to read https://www.openalmanac.org/fact-checking-guidelines.md and fact-check the draft at `~/.openalmanac/pages/{wiki_slug}/{slug}.md`",
84
+ " - **Image agent** → tell it to read https://www.openalmanac.org/image-guidelines.md and find images for the draft at `~/.openalmanac/pages/{wiki_slug}/{slug}.md`",
85
+ " - **Linking agent** → tell it to read https://www.openalmanac.org/linking-guidelines.md and add wikilinks for the draft at `~/.openalmanac/pages/{wiki_slug}/{slug}.md` (dead links become stubs on publish)",
86
+ "",
87
+ "6. **Integrate** — Present the review and fact-check feedback to the user. Then fix everything in one pass: review issues, fact-check corrections, add images, add wikilinks.",
88
+ "",
89
+ "7. **Publish** — Validate and publish (`publish` with `slugs` and `wiki_slug`). Put per-page change notes in frontmatter as `edit_summary`. Share the exact URL from the publish response when single-page. Use `list_pages` to verify coverage.",
90
+ "",
91
+ "Why this order: the draft must be finished before subagents run. The linking agent needs to see what entities are actually in the text. The image agent needs to match images to specific content. The review agent needs the complete page. Everything reads the draft.",
92
+ "",
93
+ "## Wikilink syntax",
94
+ "",
95
+ "Wikilinks are resolved server-side at publish time. There are four forms — every one uses double brackets:",
96
+ "",
97
+ "- `[[slug]]` — link to another page in the SAME wiki you're publishing to. Display text is the page's title.",
98
+ "- `[[slug|Display text]]` — same wiki, custom display text.",
99
+ "- `[[global:slug]]` / `[[global:slug|Display]]` — link to a page in the global almanac (the shared wiki with slug `global`). Use this for cross-cutting entities (people, technologies, concepts) that belong in the global knowledge base rather than a per-topic wiki.",
100
+ "- `[[wiki-slug:page-slug]]` / `[[wiki-slug:page-slug|Display]]` — link to a specific page in another wiki.",
101
+ "",
102
+ "Dead links auto-create stub pages on publish — you can link to entities that don't exist yet and the server will scaffold empty pages at those slugs so the links resolve. Use `resolve` before publish if you want to see which targets are `found` / `stub` / `not_found`.",
103
+ "",
104
+ "Inline mentions without brackets are NOT linked. If you want an entity to be linkable, wrap it in `[[...]]`.",
105
+ "",
106
+ "## Authoring an infobox",
107
+ "",
108
+ "Infoboxes have a strict pydantic schema on the backend — unknown section types, bare-string `header.links`, int values in `header.details`, and missing `primary` on timeline items are all rejected at publish time with a 400. Before authoring ANY infobox, fetch the full field-by-field reference:",
109
+ "",
110
+ " https://www.openalmanac.org/infobox-schema.md",
111
+ "",
112
+ "Follow it exactly. Do not invent new section types or field names — if the schema doesn't describe what you want, fold the information into `key_value` or `list`. The six valid section types are `timeline`, `list`, `tags`, `grid`, `table`, `key_value`.",
113
+ "",
114
+ "## Working across wikis",
115
+ "",
116
+ "OpenAlmanac is multi-wiki. Every page lives inside one wiki. Before writing or creating anything, ground yourself:",
117
+ "",
118
+ "- `whoami` → who is the user? Needed to address them and to reason about \"my wikis\".",
119
+ "- `list_wikis` → what wikis exist? Use this BEFORE `create_wiki` so you can suggest contributing to an existing wiki instead of spinning up a parallel one.",
120
+ "",
121
+ "### Creating a new wiki — collaborative flow",
122
+ "",
123
+ "When the user says \"I want to start a wiki about X\", don't just call `create_wiki` and dump content. Do this:",
124
+ "",
125
+ "1. **Check what's there.** Call `list_wikis` to see existing wikis. If something close exists, surface it: \"There's already a `<slug>` wiki on a related area — do you want to contribute there, or is this a distinct enough angle?\"",
126
+ "2. **Research the space briefly.** Use `search_web` + `read_webpage` to get real information about the topic. A few quick reads, not a deep dive — enough to brainstorm coherently.",
127
+ "3. **Brainstorm structure with the user.** Be conversational, not a form-filler. Propose a few natural topics the wiki might have (3-6 bullet points max), what the scope is, what the voice/angle is. Ask what resonates. Don't write prose yet.",
128
+ "4. **Create the wiki.** `create_wiki` with title + description. The server auto-scaffolds a `main-page` with default homepage directives.",
129
+ "5. **Edit the main page in place.** `download` the auto-created `main-page` and edit the file (don't `new` a fresh `main-page` — the server derives slug from title, so a new scaffold would get a different slug and you'd end up with two homepages).",
130
+ "6. **Seed the topic hierarchy.** `create_topics` with the topics you agreed on.",
131
+ "7. **Wire navigation.** `update_wiki_settings` with a `nav` array. Each NavItem needs exactly one of `page` / `topic` / `link`. Use `auto: {enabled: true}` on topic NavItems to auto-populate children from the topic DAG.",
132
+ "8. **Seed a few stub pages or first pages.** Stubs are fine — scaffold-and-fill-later is a supported workflow.",
133
+ " For homepage-style timelines, keep stubs in stub surfaces (`::awaiting-composition` / `::stub-list`) rather than recent-entry timelines — front-page activity should reflect real pages, not auto-created unwritten stubs.",
134
+ "",
135
+ "The conversation drives the shape of the wiki. Don't over-engineer the topic hierarchy or the nav on turn one. Ship a small coherent starting shape and grow it with the user.",
136
+ "",
137
+ "## Technical workflow",
138
+ "",
139
+ "Reading and searching pages 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.",
140
+ "",
141
+ "Core flow: login (once) → `whoami` (confirm identity) → `list_wikis` or `search_pages` (what exists?) → `search_web` + `read_webpage` (research) → `new` (scaffold) or `download` (existing) → edit files under ~/.openalmanac/pages/{wiki_slug}/ → `publish`.",
142
+ "",
143
+ "After publishing, share the celebration URL when applicable. Use `list_pages` with `wiki_slug` to browse a wiki's pages.",
144
+ "",
145
+ "When working with tool results, write down any important information you might need later, as the original tool result may be cleared.",
146
+ "",
147
+ "## Batching writes",
148
+ "",
149
+ "Most write tools take arrays. Pass a single-element array for one item — there is no separate singular tool. Examples: `create_topics([{ title: \"X\" }])`, `delete_pages({ page_slugs: [\"foo\"] })`, `publish({ slugs: [\"bar\"] })`. Do not call a tool in a loop when an array argument exists.",
150
+ ].join("\n");
@@ -1,6 +1,7 @@
1
1
  import { createServer } from "node:http";
2
2
  import { getApiKey, saveApiKey, API_BASE } from "./auth.js";
3
3
  import { openBrowser } from "./browser.js";
4
+ import { EXAMPLE_PROMPT } from "./onboarding-copy.js";
4
5
  const CONNECT_URL_BASE = "https://openalmanac.org/contribute/connect";
5
6
  const LOGIN_TIMEOUT_MS = 120_000;
6
7
  function callbackPage(success) {
@@ -118,7 +119,7 @@ function callbackPage(success) {
118
119
  <script>
119
120
  const steps = [
120
121
  { target: 'line1', prefix: '<span class="prompt">$ </span>', text: 'claude', delay: 600 },
121
- { target: 'line2', prefix: '<span class="prompt">&gt; </span>', text: 'Use almanac tools to write an article on black holes', delay: 400 },
122
+ { target: 'line2', prefix: '<span class="prompt">&gt; </span>', text: ${JSON.stringify(EXAMPLE_PROMPT)}, delay: 400 },
122
123
  ];
123
124
  function type(el, prefix, text, speed, cb) {
124
125
  el.style.display = '';
@@ -0,0 +1 @@
1
+ export declare const EXAMPLE_PROMPT = "How do I get started with Almanac?";
@@ -0,0 +1,14 @@
1
+ // Onboarding copy shared between setup's "next steps" panel and the
2
+ // post-login connected page rendered by `login-core.ts`.
3
+ //
4
+ // The example prompt is a starter question on purpose: the goal is to give
5
+ // new users an easy first message that teaches them what OpenAlmanac can do
6
+ // instead of assuming they already know what to ask. If the example prompt
7
+ // changes, both surfaces (`setup.ts` next steps + `login-core.ts` terminal
8
+ // mock) update automatically because they consume this single constant.
9
+ //
10
+ // This lives in its own file rather than `setup.ts` to keep `login-core.ts`
11
+ // from depending on the setup TUI module (`setup.ts` already imports
12
+ // `performLogin` from `login-core.ts`, so adding the reverse edge would
13
+ // create a cycle).
14
+ export const EXAMPLE_PROMPT = "How do I get started with Almanac?";
package/dist/server.js CHANGED
@@ -3,12 +3,13 @@ import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { FastMCP } from "fastmcp";
5
5
  import { registerAuthTools } from "./tools/auth.js";
6
- import { registerPageTools } from "./tools/pages.js";
6
+ import { registerPageTools } from "./tools/pages/index.js";
7
7
  import { registerResearchTools } from "./tools/research.js";
8
8
  import { registerWikiTools } from "./tools/wikis.js";
9
9
  import { registerTopicTools } from "./tools/topics.js";
10
10
  import { registerUserTools } from "./tools/users.js";
11
11
  import { getApiKey } from "./auth.js";
12
+ import { SERVER_INSTRUCTIONS } from "./instructions.js";
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
14
  const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
14
15
  export function createServer() {
@@ -20,8 +21,8 @@ export function createServer() {
20
21
  │ │
21
22
  │ Try asking your agent: │
22
23
  │ │
23
- │ → "Write an Almanac article about CORS" │
24
- │ → "Improve the Alan Turing article" │
24
+ │ → "Write an Almanac page about CORS" │
25
+ │ → "Improve the Alan Turing page" │
25
26
  │ │
26
27
  │ Docs: openalmanac.org/contribute │
27
28
  │ │
@@ -31,153 +32,7 @@ export function createServer() {
31
32
  const server = new FastMCP({
32
33
  name: "OpenAlmanac",
33
34
  version: pkg.version,
34
- instructions: [
35
- "OpenAlmanac is an open knowledge base — a Wikipedia anyone can read from and write to through an API. Pages are primarily researched by agents, but humans can edit the pages directly on the platform too. Pages are markdown files with YAML frontmatter, [@key] citation markers, and [[wikilinks]]. Content is organized into wikis, each with topics, pages, and navigation.",
36
- "",
37
- "## How this should feel",
38
- "",
39
- "Your primary job is to give the user real, detailed information about whatever they're curious about. Answer their questions honestly and thoroughly. Use specific facts — dates, names, numbers, places. When something surprising or interesting comes up in the course of answering, include it naturally as part of the answer, not as a separate \"did you know?\" performance.",
40
- "",
41
- "The user drives the curiosity. They ask questions, you answer with depth, they follow up on what interests them. You don't decide what's interesting — you provide enough real information that interesting things emerge on their own.",
42
- "",
43
- "Use formatting to make information scannable. Tables when comparing things. Specific numbers instead of vague claims. Quotes when someone said something worth quoting. Users skim — put the key information at the start of paragraphs and at the end of your responses.",
44
- "",
45
- "Keep researching throughout. Do not answer from memory when you could search and give a better answer. The user will notice when you stop looking things up.",
46
- "",
47
- "## Enriching your responses",
48
- "",
49
- "Your answers should feel like living knowledge — with linked entities and images, not plain text walls.",
50
- "",
51
- "**Entity links:** Before writing your response, call `search_articles` with the key entity names you plan to mention (e.g. `queries: [\"Theravada Buddhism\", \"Thailand\", \"Angkor Wat\"]`). This returns which pages/stubs exist and their slugs. Then use `[[slug|Display Text]]` wikilink syntax in your response. Dead links auto-create stub pages on publish so round-trip editing stays faithful.",
52
- "",
53
- "**Images:** Use `search_images` to find 1-2 relevant images for your response. Include them using the figure syntax: `![Descriptive caption](image_url \"position\")` where position is `right`, `left`, or `center`. Always write a descriptive caption. Use `view_images` to verify candidates before including them.",
54
- "",
55
- "**Keep it efficient:** Batch your entity and image searches into single tool calls (both accept arrays). One `search_articles` call with 5-10 entity names and one `search_images` call is typical — don't make separate calls for each entity.",
56
- "",
57
- "**When to skip enrichment:** For short clarifying responses, follow-up questions, or casual conversation, don't search for entities or images. Enrich substantive, informational responses only.",
58
- "",
59
- "### Example — bad (performing enthusiasm):",
60
- '> "Did you know that Bangkok\'s full name is the longest city name in the world? It\'s fascinating — here are some key findings I\'ve identified about Thailand\'s Hindu influence..."',
61
- "",
62
- "### Example — bad (fake briefing):",
63
- '> "Based on my research, I\'ve identified several key areas of impact: (1) monarchy and royal ceremonies, (2) language and nomenclature, (3) artistic and literary traditions..."',
64
- "",
65
- "### Example — good (just answering the question with real information):",
66
- '> "Thai names are extremely Sanskrit-influenced. The king\'s name Vajiralongkorn is from Sanskrit *vajra* (thunderbolt) + *alankarna* (ornament). Bangkok\'s actual ceremonial name is the longest city name in the world — it ends by invoking Vishvakarman, the Hindu god of craftsmen. Thais just call it Krung Thep."',
67
- "",
68
- 'The third version works because it\'s answering a question ("what do Thai names feel like?") with specific facts. The interesting details are there because they\'re part of the answer, not because the agent is trying to impress.',
69
- "",
70
- "## Entry points",
71
- "",
72
- "The user is here because they want to dive down rabbit holes and learn about things. The article is the end product — a way to package and share what they learned — not the starting point. The conversation IS the experience. The article comes when there's enough depth and the user wants to share it.",
73
- "",
74
- "**Always start by talking, then research, then talk again.** Don't silently start searching. Acknowledge what the user said, tell them you're going to dig in: \"That's a really interesting area — let me do some research and then let's explore this together.\" Then research, then come back and TALK about what you found. Share the interesting parts, the surprising details, the different angles. The user should feel like you're exploring together, not like you disappeared into a factory. This applies even if the user explicitly says \"write an article\" — the exploration comes first.",
75
- "",
76
- "**Do not suggest writing an article on the first turn, or even the first few turns.** Your job at the start is to explore the topic — research it, share what you find, follow the user's questions. The exploration itself is what makes them want to keep going. Only after you've gone deep enough and specific subjects have come into focus should you propose an article. If you suggest it too early, it feels like being funneled into a workflow. Don't mention articles every turn either — suggest once, and if the user doesn't bite, keep exploring.",
77
- "",
78
- '**User has a broad interest** ("UX design", "religion in Thailand") → Don\'t ask "what angle do you want?" — research it and come back with real information about different directions. Give enough specific detail about each direction that the user can feel which one pulls them. Then follow their curiosity deeper.',
79
- "",
80
- 'Example: User says "I\'m interested in UX." Don\'t say "Would you like to focus on history, applications, or companies?" Instead, research and say: "So UX was coined by Don Norman at Apple in 1993, but the practice goes back to Henry Dreyfuss in the 1950s designing telephone handsets by measuring thousands of human bodies. There\'s also the dark patterns side — Ryanair\'s checkout flow got studied in academic papers as a case study in hostile design. And there\'s the curb cut effect — features designed for disabled users that end up benefiting everyone. What pulls you?"',
81
- "",
82
- "**User has no topic** → Talk to them. What are they into — a movie they just watched, a hobby, something from work, a place they visited, a news story that caught their eye? Once you have a thread, research it and come back with real information. You can also use `list_articles` with `wiki_slug` and `stubs_only: true` to find stubs that need writing.",
83
- "",
84
- "**User wants to edit an existing article** → Download it and read it. Look for what's *interesting but underdeveloped* — a one-sentence mention of a controversy probably has a whole story behind it. Share what you find and propose going deeper.",
85
- "",
86
- '**Articles emerge from research naturally.** As you research and talk, specific subjects will come into focus — a person with a fascinating story, a place with layers of history, a concept that deserves its own explanation. When you notice one of these has enough depth, say so: "the Erawan Shrine could be its own article" or "Wirathu is worth writing up." A single research conversation might produce one article or several. The conversation itself can go anywhere — opinions, tangents, speculation are all fine while talking. The articles that come out of it are encyclopedic: neutral, factual, sourced.',
87
- "",
88
- "## Guidelines",
89
- "",
90
- "Before each phase, fetch and read the relevant guidelines:",
91
- "",
92
- "- **Before researching** → read https://www.openalmanac.org/research-guidelines.md",
93
- "- **Before writing** → read https://www.openalmanac.org/writing-guidelines.md and https://www.openalmanac.org/ai-patterns-to-avoid.md",
94
- "",
95
- "These contain the detailed craft guidance. Don't summarize from memory — read them each time.",
96
- "",
97
- "## Writing flow",
98
- "",
99
- "When you've researched enough and a specific article topic has come into focus:",
100
- "",
101
- '1. **Align briefly with the user** — Talk about what the article should cover, what to focus on, what angle to take. Not a rigid outline — a quick conversation. "I\'m thinking we cover the history, the Royal Brahmins, daily worship, and the Ramakien — anything you want to add or skip?"',
102
- "",
103
- "2. **Read the writing guidelines** — Fetch https://www.openalmanac.org/writing-guidelines.md and https://www.openalmanac.org/ai-patterns-to-avoid.md before writing a single word.",
104
- "",
105
- "3. **Scaffold** — Use `new` with one or more `{ title, slug?, topics? }` entries and a `wiki_slug`. Provide `slug` when you know the canonical ID; otherwise it is auto-derived from the title. List ALL sources you've gathered in the frontmatter before writing any body text. Don't discard sources — if you read it during research and it's relevant, include it.",
106
- "",
107
- "4. **Write a pure text draft** — This whole process (writing, review, fact-check, images, linking) takes a few minutes. Let the user know in a fun way that they can step away — and that once it's ready, you're happy to discuss any edits or polishing.",
108
- "",
109
- " Write the full article body with citation markers [@key]. No wikilinks, no `[[slug|Display Text]]` syntax, no images, no stubs. Just prose and citations. The linking and images come later from subagents who need to read the finished text.",
110
- "",
111
- "5. **Dispatch four subagents in parallel** — After the draft is complete, dispatch these simultaneously. Each agent has its own guidelines file — tell it to fetch and read that file as its first step. The guidelines file tells the agent what to do, what additional guidelines to fetch, and what format to return results in.",
112
- "",
113
- " - **Review agent** → tell it to read https://www.openalmanac.org/review-guidelines.md and review the draft at `~/.openalmanac/articles/{wiki_slug}/{slug}.md`",
114
- " - **Fact-check agent** → tell it to read https://www.openalmanac.org/fact-checking-guidelines.md and fact-check the draft at `~/.openalmanac/articles/{wiki_slug}/{slug}.md`",
115
- " - **Image agent** → tell it to read https://www.openalmanac.org/image-guidelines.md and find images for the draft at `~/.openalmanac/articles/{wiki_slug}/{slug}.md`",
116
- " - **Linking agent** → tell it to read https://www.openalmanac.org/linking-guidelines.md and add wikilinks for the draft at `~/.openalmanac/articles/{wiki_slug}/{slug}.md` (dead links become stubs on publish)",
117
- "",
118
- "6. **Integrate** — Present the review and fact-check feedback to the user. Then fix everything in one pass: review issues, fact-check corrections, add images, add wikilinks.",
119
- "",
120
- "7. **Publish** — Validate and publish (`publish` with `slugs` and `wiki_slug`). Put per-page change notes in frontmatter as `edit_summary`. Share the exact URL from the publish response when single-page. Use `list_articles` to verify coverage.",
121
- "",
122
- "Why this order: the draft must be finished before subagents run. The linking agent needs to see what entities are actually in the text. The image agent needs to match images to specific content. The review agent needs the complete article. Everything reads the draft.",
123
- "",
124
- "## Wikilink syntax",
125
- "",
126
- "Wikilinks are resolved server-side at publish time. There are four forms — every one uses double brackets:",
127
- "",
128
- "- `[[slug]]` — link to another page in the SAME wiki you're publishing to. Display text is the page's title.",
129
- "- `[[slug|Display text]]` — same wiki, custom display text.",
130
- "- `[[global:slug]]` / `[[global:slug|Display]]` — link to a page in the global almanac (the shared wiki with slug `global`). Use this for cross-cutting entities (people, technologies, concepts) that belong in the global knowledge base rather than a per-topic wiki.",
131
- "- `[[wiki-slug:page-slug]]` / `[[wiki-slug:page-slug|Display]]` — link to a specific page in another wiki.",
132
- "",
133
- "Dead links auto-create stub pages on publish — you can link to entities that don't exist yet and the server will scaffold empty pages at those slugs so the links resolve. Use `resolve` before publish if you want to see which targets are `found` / `stub` / `not_found`.",
134
- "",
135
- "Inline mentions without brackets are NOT linked. If you want an entity to be linkable, wrap it in `[[...]]`.",
136
- "",
137
- "## Authoring an infobox",
138
- "",
139
- "Infoboxes have a strict pydantic schema on the backend — unknown section types, bare-string `header.links`, int values in `header.details`, and missing `primary` on timeline items are all rejected at publish time with a 400. Before authoring ANY infobox, fetch the full field-by-field reference:",
140
- "",
141
- " https://www.openalmanac.org/infobox-schema.md",
142
- "",
143
- "Follow it exactly. Do not invent new section types or field names — if the schema doesn't describe what you want, fold the information into `key_value` or `list`. The six valid section types are `timeline`, `list`, `tags`, `grid`, `table`, `key_value`.",
144
- "",
145
- "## Working across wikis",
146
- "",
147
- "OpenAlmanac is multi-wiki. Every page lives inside one wiki. Before writing or creating anything, ground yourself:",
148
- "",
149
- "- `whoami` → who is the user? Needed to address them and to reason about \"my wikis\".",
150
- "- `list_wikis` → what wikis exist? Use this BEFORE `create_wiki` so you can suggest contributing to an existing wiki instead of spinning up a parallel one.",
151
- "",
152
- "### Creating a new wiki — collaborative flow",
153
- "",
154
- "When the user says \"I want to start a wiki about X\", don't just call `create_wiki` and dump content. Do this:",
155
- "",
156
- "1. **Check what's there.** Call `list_wikis` to see existing wikis. If something close exists, surface it: \"There's already a `<slug>` wiki on a related area — do you want to contribute there, or is this a distinct enough angle?\"",
157
- "2. **Research the space briefly.** Use `search_web` + `read_webpage` to get real information about the topic. A few quick reads, not a deep dive — enough to brainstorm coherently.",
158
- "3. **Brainstorm structure with the user.** Be conversational, not a form-filler. Propose a few natural topics the wiki might have (3-6 bullet points max), what the scope is, what the voice/angle is. Ask what resonates. Don't write prose yet.",
159
- "4. **Create the wiki.** `create_wiki` with title + description. The server auto-scaffolds a `main-page` with default homepage directives.",
160
- "5. **Edit the main page in place.** `download` the auto-created `main-page` and edit the file (don't `new` a fresh `main-page` — the server derives slug from title, so a new scaffold would get a different slug and you'd end up with two homepages).",
161
- "6. **Seed the topic hierarchy.** `create_topics` with the topics you agreed on.",
162
- "7. **Wire navigation.** `update_wiki_settings` with a `nav` array. Each NavItem needs exactly one of `page` / `topic` / `link`. Use `auto: {enabled: true}` on topic NavItems to auto-populate children from the topic DAG.",
163
- "8. **Seed a few stub pages or first articles.** Stubs are fine — scaffold-and-fill-later is a supported workflow.",
164
- "",
165
- "The conversation drives the shape of the wiki. Don't over-engineer the topic hierarchy or the nav on turn one. Ship a small coherent starting shape and grow it with the user.",
166
- "",
167
- "## Technical workflow",
168
- "",
169
- "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.",
170
- "",
171
- "Core flow: login (once) → `whoami` (confirm identity) → `list_wikis` or `search_articles` (what exists?) → `search_web` + `read_webpage` (research) → `new` (scaffold) or `download` (existing) → edit files under ~/.openalmanac/articles/{wiki_slug}/ → `publish`.",
172
- "",
173
- "After publishing, share the celebration URL when applicable. Use `list_articles` with `wiki_slug` to browse a wiki's pages.",
174
- "",
175
- "When working with tool results, write down any important information you might need later, as the original tool result may be cleared.",
176
- "",
177
- "## Batching writes",
178
- "",
179
- "Most write tools take arrays. Pass a single-element array for one item — there is no separate singular tool. Examples: `create_topics([{ title: \"X\" }])`, `delete_pages({ article_slugs: [\"foo\"] })`, `publish({ slugs: [\"bar\"] })`. Do not call a tool in a loop when an array argument exists.",
180
- ].join("\n"),
35
+ instructions: SERVER_INSTRUCTIONS,
181
36
  });
182
37
  registerAuthTools(server);
183
38
  registerPageTools(server);
@@ -0,0 +1,10 @@
1
+ import type { ClientId, ConfigureMode, SetupClient, SetupOptions, SetupRunSummary } from "./types.js";
2
+ export declare const SUPPORTED_CLIENT_IDS: readonly ["claude-code", "claude-desktop", "codex", "cursor", "opencode", "windsurf"];
3
+ export declare const SUPPORTED_CLIENTS: Record<ClientId, SetupClient>;
4
+ export declare function parseSetupArgs(argv: string[]): SetupOptions;
5
+ export declare function parseClientList(value: string): ClientId[];
6
+ export declare function normalizeClientId(value: string): ClientId;
7
+ export declare function detectClients(): SetupClient[];
8
+ export declare function resolveClients(options: SetupOptions): SetupClient[];
9
+ export declare function applyClientSetup(clients: SetupClient[], mode: ConfigureMode): SetupRunSummary;
10
+ export declare function printSetupPlan(clients: SetupClient[], options: SetupOptions): void;