openalmanac 0.4.0 → 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 (37) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/instructions.d.ts +1 -0
  3. package/dist/instructions.js +150 -0
  4. package/dist/onboarding-copy.d.ts +1 -1
  5. package/dist/onboarding-copy.js +6 -6
  6. package/dist/openalmanac_mcp-0.3.1-py3-none-any.whl +0 -0
  7. package/dist/openalmanac_mcp-0.3.1.tar.gz +0 -0
  8. package/dist/openalmanac_mcp-0.3.2-py3-none-any.whl +0 -0
  9. package/dist/openalmanac_mcp-0.3.2.tar.gz +0 -0
  10. package/dist/server.js +3 -149
  11. package/dist/setup/clients.d.ts +10 -0
  12. package/dist/setup/clients.js +291 -0
  13. package/dist/setup/config-files.d.ts +43 -0
  14. package/dist/setup/config-files.js +257 -0
  15. package/dist/setup/index.d.ts +2 -0
  16. package/dist/setup/index.js +55 -0
  17. package/dist/setup/permissions.d.ts +3 -0
  18. package/dist/setup/permissions.js +52 -0
  19. package/dist/{setup.d.ts → setup/reddit.d.ts} +0 -1
  20. package/dist/setup/reddit.js +69 -0
  21. package/dist/setup/tui.d.ts +7 -0
  22. package/dist/setup/tui.js +496 -0
  23. package/dist/setup/types.d.ts +43 -0
  24. package/dist/setup/types.js +1 -0
  25. package/dist/tool-registry.js +1 -1
  26. package/dist/tools/{pages.js → pages/index.js} +8 -164
  27. package/dist/tools/pages/publish-format.d.ts +48 -0
  28. package/dist/tools/pages/publish-format.js +92 -0
  29. package/dist/tools/pages/workspace.d.ts +7 -0
  30. package/dist/tools/pages/workspace.js +14 -0
  31. package/dist/tools/pages/writing-guide.d.ts +1 -0
  32. package/dist/tools/pages/writing-guide.js +56 -0
  33. package/package.json +1 -1
  34. package/dist/setup.js +0 -1216
  35. package/dist/validate.d.ts +0 -971
  36. package/dist/validate.js +0 -154
  37. /package/dist/tools/{pages.d.ts → pages/index.d.ts} +0 -0
@@ -1,168 +1,12 @@
1
1
  import { z } from "zod";
2
- import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, unlinkSync } from "node:fs";
3
- import { join } from "node:path";
2
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from "node:fs";
4
3
  import { stringify as yamlStringify } from "yaml";
5
- import { request, PAGES_DIR } from "../auth.js";
6
- import { openBrowser } from "../browser.js";
7
- import { coerceJson } from "../utils.js";
8
- const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
9
- function resolvePageDir(wikiSlug) {
10
- return join(PAGES_DIR, wikiSlug);
11
- }
12
- function resolvePagePaths(slug, wikiSlug) {
13
- const dir = resolvePageDir(wikiSlug);
14
- return {
15
- dir,
16
- filePath: join(dir, `${slug}.md`),
17
- refPath: join(dir, `.${slug}.ref`),
18
- };
19
- }
20
- const WRITING_GUIDE = `
21
- ## Page structure
22
-
23
- \`\`\`yaml
24
- ---
25
- title: Page Title
26
- wiki: wiki-slug
27
- topics: [topic-one, topic-two]
28
- sources:
29
- - key: example-source
30
- url: https://example.com
31
- title: Source Title
32
- accessed_date: "2025-01-15"
33
- infobox:
34
- header:
35
- image_url: https://...
36
- subtitle: Short tagline
37
- details:
38
- - key: Born
39
- value: January 1, 1990
40
- ---
41
-
42
- Page body with [@key] citation markers and [[wikilinks]]...
43
- \`\`\`
44
-
45
- ## Wikilinks
46
-
47
- - Write natural text in double brackets: [[spool pins]], [[pin tumbler locks]]
48
- - Display text: [[spool-pins|spool pins]]
49
- - Cross-wiki: [[global:reddit|Reddit]], [[lockpicking:spool-pins|spool pins]]
50
-
51
- ## Citations
52
-
53
- - Mark claims with [@key] after punctuation
54
- - Keys must be kebab-case with at least one hyphen
55
- - Every source must be referenced; every reference must have a source
56
-
57
- ## Quoting
58
-
59
- For any string value with punctuation, quotes, or special characters (common in \`sources[].title\`), use YAML block-literal syntax:
60
-
61
- \`\`\`yaml
62
- sources:
63
- - key: farza-yc
64
- title: |-
65
- "I'm joining Y Combinator, again" — Farza Majeed
66
- url: https://...
67
- \`\`\`
68
-
69
- This sidesteps every YAML escaping rule. If you skip this, inner double quotes or em-dashes will break the parser.
70
-
71
- ## Images
72
-
73
- Use search_images to find relevant images. Syntax: \`![Caption](url "position")\`
74
- Positions: "right" (default), "left", "center". Every image needs a descriptive caption.
75
- `.trim();
76
- function formatPublishResults(results, targetSlugs, wiki_slug, dry_run) {
77
- const allAutoStubs = new Set();
78
- const lines = [];
79
- let okCount = 0;
80
- let errorCount = 0;
81
- for (let i = 0; i < results.length; i++) {
82
- const r = results[i];
83
- const slug = targetSlugs[i] ?? r.slug;
84
- if (dry_run && r.plan) {
85
- const plan = r.plan;
86
- const hasError = plan.validation.status === "failed" ||
87
- !plan.authorization.can_write ||
88
- plan.action === "error";
89
- if (hasError) {
90
- errorCount++;
91
- const reasons = [];
92
- for (const e of plan.validation.errors) {
93
- reasons.push(`${e.field}: ${e.message}`);
94
- }
95
- if (!plan.authorization.can_write && plan.authorization.reason) {
96
- reasons.push(`auth: ${plan.authorization.reason}`);
97
- }
98
- lines.push(`- ${slug}: **error** — ${reasons.join("; ")}`);
99
- }
100
- else {
101
- okCount++;
102
- let line = `- ${plan.slug}: **${plan.action}**`;
103
- if (plan.renamed_from)
104
- line += ` (rename: ${plan.renamed_from} → ${plan.slug})`;
105
- const details = [];
106
- if (plan.source_keys.referenced.length > 0) {
107
- details.push(`${plan.source_keys.referenced.length} source(s)`);
108
- }
109
- if (plan.wikilinks.will_auto_stub.length > 0) {
110
- details.push(`${plan.wikilinks.will_auto_stub.length} new stub(s)`);
111
- plan.wikilinks.will_auto_stub.forEach(s => allAutoStubs.add(s));
112
- }
113
- const inBatchLinks = plan.wikilinks.in_batch ?? [];
114
- if (inBatchLinks.length > 0) {
115
- details.push(`${inBatchLinks.length} in-batch link(s)`);
116
- }
117
- if (plan.source_keys.orphaned.length > 0) {
118
- details.push(`missing source key(s): ${plan.source_keys.orphaned.join(", ")}`);
119
- }
120
- if (plan.source_keys.unreferenced.length > 0) {
121
- details.push(`unreferenced source(s): ${plan.source_keys.unreferenced.join(", ")}`);
122
- }
123
- if (details.length > 0)
124
- line += ` (${details.join(", ")})`;
125
- lines.push(line);
126
- }
127
- }
128
- else {
129
- // Real publish result
130
- if (r.status === "error") {
131
- errorCount++;
132
- lines.push(`- ${r.slug}: **error** — ${r.error}`);
133
- }
134
- else {
135
- okCount++;
136
- // Clean up local files — pre-rename slug names the file
137
- const fileSlug = r.renamed_from ?? slug;
138
- const { filePath, refPath } = resolvePagePaths(fileSlug, wiki_slug);
139
- try {
140
- unlinkSync(filePath);
141
- }
142
- catch { /* ok */ }
143
- try {
144
- unlinkSync(refPath);
145
- }
146
- catch { /* ok */ }
147
- let line = `- ${r.slug}: **${r.status}**`;
148
- if (r.renamed_from)
149
- line += ` (renamed from ${r.renamed_from})`;
150
- if (r.stubs_created?.length) {
151
- r.stubs_created.forEach(s => allAutoStubs.add(s));
152
- }
153
- lines.push(line);
154
- }
155
- }
156
- }
157
- const verb = dry_run ? "Dry-run" : "Published";
158
- const summary = `${verb}: ${okCount}/${targetSlugs.length} OK${errorCount > 0 ? `, ${errorCount} error(s)` : ""}.`;
159
- const parts = [summary, "", ...lines];
160
- if (allAutoStubs.size > 0) {
161
- const stubVerb = dry_run ? "Stubs that will be auto-created" : "Stubs auto-created";
162
- parts.push("", `${stubVerb}: ${[...allAutoStubs].join(", ")}`);
163
- }
164
- return parts.join("\n");
165
- }
4
+ import { request } from "../../auth.js";
5
+ import { openBrowser } from "../../browser.js";
6
+ import { coerceJson } from "../../utils.js";
7
+ import { formatPublishResults } from "./publish-format.js";
8
+ import { resolvePageDir, resolvePagePaths, SLUG_RE } from "./workspace.js";
9
+ import { WRITING_GUIDE } from "./writing-guide.js";
166
10
  export function registerPageTools(server) {
167
11
  server.addTool({
168
12
  name: "search_pages",
@@ -312,7 +156,7 @@ export function registerPageTools(server) {
312
156
  .replace(/^-+|-+$/g, "")
313
157
  .replace(/-{2,}/g, "-") || "untitled";
314
158
  }
315
- const filePath = join(dir, `${fileSlug}.md`);
159
+ const { filePath } = resolvePagePaths(fileSlug, wiki_slug);
316
160
  if (existsSync(filePath)) {
317
161
  skipped.push(`${fileSlug}.md already exists`);
318
162
  continue;
@@ -0,0 +1,48 @@
1
+ export type PublishPlanValidation = {
2
+ status: string;
3
+ errors: Array<{
4
+ field: string;
5
+ message: string;
6
+ }>;
7
+ };
8
+ export type PublishPlanAuthorization = {
9
+ can_write: boolean;
10
+ reason?: string;
11
+ };
12
+ export type PublishPlanWikilinks = {
13
+ found: string[];
14
+ in_batch?: string[];
15
+ stubs: string[];
16
+ will_auto_stub: string[];
17
+ };
18
+ export type PublishPlanSourceKeys = {
19
+ referenced: string[];
20
+ unreferenced: string[];
21
+ orphaned: string[];
22
+ };
23
+ export type PublishPlanInfobox = {
24
+ status: string;
25
+ errors: Array<{
26
+ field: string;
27
+ message: string;
28
+ }>;
29
+ };
30
+ export type PublishPlan = {
31
+ action: string;
32
+ slug: string;
33
+ renamed_from?: string;
34
+ validation: PublishPlanValidation;
35
+ authorization: PublishPlanAuthorization;
36
+ wikilinks: PublishPlanWikilinks;
37
+ source_keys: PublishPlanSourceKeys;
38
+ infobox: PublishPlanInfobox;
39
+ };
40
+ export type PublishResult = {
41
+ slug: string;
42
+ status: string;
43
+ renamed_from?: string;
44
+ stubs_created?: string[];
45
+ error?: string;
46
+ plan?: PublishPlan;
47
+ };
48
+ export declare function formatPublishResults(results: PublishResult[], targetSlugs: string[], wiki_slug: string, dry_run: boolean): string;
@@ -0,0 +1,92 @@
1
+ import { unlinkSync } from "node:fs";
2
+ import { resolvePagePaths } from "./workspace.js";
3
+ export function formatPublishResults(results, targetSlugs, wiki_slug, dry_run) {
4
+ const allAutoStubs = new Set();
5
+ const lines = [];
6
+ let okCount = 0;
7
+ let errorCount = 0;
8
+ for (let i = 0; i < results.length; i++) {
9
+ const r = results[i];
10
+ const slug = targetSlugs[i] ?? r.slug;
11
+ if (dry_run && r.plan) {
12
+ const plan = r.plan;
13
+ const hasError = plan.validation.status === "failed" ||
14
+ !plan.authorization.can_write ||
15
+ plan.action === "error";
16
+ if (hasError) {
17
+ errorCount++;
18
+ const reasons = [];
19
+ for (const e of plan.validation.errors) {
20
+ reasons.push(`${e.field}: ${e.message}`);
21
+ }
22
+ if (!plan.authorization.can_write && plan.authorization.reason) {
23
+ reasons.push(`auth: ${plan.authorization.reason}`);
24
+ }
25
+ lines.push(`- ${slug}: **error** — ${reasons.join("; ")}`);
26
+ }
27
+ else {
28
+ okCount++;
29
+ let line = `- ${plan.slug}: **${plan.action}**`;
30
+ if (plan.renamed_from)
31
+ line += ` (rename: ${plan.renamed_from} → ${plan.slug})`;
32
+ const details = [];
33
+ if (plan.source_keys.referenced.length > 0) {
34
+ details.push(`${plan.source_keys.referenced.length} source(s)`);
35
+ }
36
+ if (plan.wikilinks.will_auto_stub.length > 0) {
37
+ details.push(`${plan.wikilinks.will_auto_stub.length} new stub(s)`);
38
+ plan.wikilinks.will_auto_stub.forEach(s => allAutoStubs.add(s));
39
+ }
40
+ const inBatchLinks = plan.wikilinks.in_batch ?? [];
41
+ if (inBatchLinks.length > 0) {
42
+ details.push(`${inBatchLinks.length} in-batch link(s)`);
43
+ }
44
+ if (plan.source_keys.orphaned.length > 0) {
45
+ details.push(`missing source key(s): ${plan.source_keys.orphaned.join(", ")}`);
46
+ }
47
+ if (plan.source_keys.unreferenced.length > 0) {
48
+ details.push(`unreferenced source(s): ${plan.source_keys.unreferenced.join(", ")}`);
49
+ }
50
+ if (details.length > 0)
51
+ line += ` (${details.join(", ")})`;
52
+ lines.push(line);
53
+ }
54
+ }
55
+ else {
56
+ // Real publish result
57
+ if (r.status === "error") {
58
+ errorCount++;
59
+ lines.push(`- ${r.slug}: **error** — ${r.error}`);
60
+ }
61
+ else {
62
+ okCount++;
63
+ // Clean up local files — pre-rename slug names the file
64
+ const fileSlug = r.renamed_from ?? slug;
65
+ const { filePath, refPath } = resolvePagePaths(fileSlug, wiki_slug);
66
+ try {
67
+ unlinkSync(filePath);
68
+ }
69
+ catch { /* ok */ }
70
+ try {
71
+ unlinkSync(refPath);
72
+ }
73
+ catch { /* ok */ }
74
+ let line = `- ${r.slug}: **${r.status}**`;
75
+ if (r.renamed_from)
76
+ line += ` (renamed from ${r.renamed_from})`;
77
+ if (r.stubs_created?.length) {
78
+ r.stubs_created.forEach(s => allAutoStubs.add(s));
79
+ }
80
+ lines.push(line);
81
+ }
82
+ }
83
+ }
84
+ const verb = dry_run ? "Dry-run" : "Published";
85
+ const summary = `${verb}: ${okCount}/${targetSlugs.length} OK${errorCount > 0 ? `, ${errorCount} error(s)` : ""}.`;
86
+ const parts = [summary, "", ...lines];
87
+ if (allAutoStubs.size > 0) {
88
+ const stubVerb = dry_run ? "Stubs that will be auto-created" : "Stubs auto-created";
89
+ parts.push("", `${stubVerb}: ${[...allAutoStubs].join(", ")}`);
90
+ }
91
+ return parts.join("\n");
92
+ }
@@ -0,0 +1,7 @@
1
+ export declare const SLUG_RE: RegExp;
2
+ export declare function resolvePageDir(wikiSlug: string): string;
3
+ export declare function resolvePagePaths(slug: string, wikiSlug: string): {
4
+ dir: string;
5
+ filePath: string;
6
+ refPath: string;
7
+ };
@@ -0,0 +1,14 @@
1
+ import { join } from "node:path";
2
+ import { PAGES_DIR } from "../../auth.js";
3
+ export const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
4
+ export function resolvePageDir(wikiSlug) {
5
+ return join(PAGES_DIR, wikiSlug);
6
+ }
7
+ export function resolvePagePaths(slug, wikiSlug) {
8
+ const dir = resolvePageDir(wikiSlug);
9
+ return {
10
+ dir,
11
+ filePath: join(dir, `${slug}.md`),
12
+ refPath: join(dir, `.${slug}.ref`),
13
+ };
14
+ }
@@ -0,0 +1 @@
1
+ export declare const WRITING_GUIDE: string;
@@ -0,0 +1,56 @@
1
+ export const WRITING_GUIDE = `
2
+ ## Page structure
3
+
4
+ \`\`\`yaml
5
+ ---
6
+ title: Page Title
7
+ wiki: wiki-slug
8
+ topics: [topic-one, topic-two]
9
+ sources:
10
+ - key: example-source
11
+ url: https://example.com
12
+ title: Source Title
13
+ accessed_date: "2025-01-15"
14
+ infobox:
15
+ header:
16
+ image_url: https://...
17
+ subtitle: Short tagline
18
+ details:
19
+ - key: Born
20
+ value: January 1, 1990
21
+ ---
22
+
23
+ Page body with [@key] citation markers and [[wikilinks]]...
24
+ \`\`\`
25
+
26
+ ## Wikilinks
27
+
28
+ - Write natural text in double brackets: [[spool pins]], [[pin tumbler locks]]
29
+ - Display text: [[spool-pins|spool pins]]
30
+ - Cross-wiki: [[global:reddit|Reddit]], [[lockpicking:spool-pins|spool pins]]
31
+
32
+ ## Citations
33
+
34
+ - Mark claims with [@key] after punctuation
35
+ - Keys must be kebab-case with at least one hyphen
36
+ - Every source must be referenced; every reference must have a source
37
+
38
+ ## Quoting
39
+
40
+ For any string value with punctuation, quotes, or special characters (common in \`sources[].title\`), use YAML block-literal syntax:
41
+
42
+ \`\`\`yaml
43
+ sources:
44
+ - key: farza-yc
45
+ title: |-
46
+ "I'm joining Y Combinator, again" — Farza Majeed
47
+ url: https://...
48
+ \`\`\`
49
+
50
+ This sidesteps every YAML escaping rule. If you skip this, inner double quotes or em-dashes will break the parser.
51
+
52
+ ## Images
53
+
54
+ Use search_images to find relevant images. Syntax: \`![Caption](url "position")\`
55
+ Positions: "right" (default), "left", "center". Every image needs a descriptive caption.
56
+ `.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "OpenAlmanac — pull, edit, and push pages to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {