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.
- package/dist/cli.js +1 -1
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +150 -0
- package/dist/onboarding-copy.d.ts +1 -1
- package/dist/onboarding-copy.js +6 -6
- package/dist/openalmanac_mcp-0.3.1-py3-none-any.whl +0 -0
- package/dist/openalmanac_mcp-0.3.1.tar.gz +0 -0
- package/dist/openalmanac_mcp-0.3.2-py3-none-any.whl +0 -0
- package/dist/openalmanac_mcp-0.3.2.tar.gz +0 -0
- package/dist/server.js +3 -149
- package/dist/setup/clients.d.ts +10 -0
- package/dist/setup/clients.js +291 -0
- package/dist/setup/config-files.d.ts +43 -0
- package/dist/setup/config-files.js +257 -0
- package/dist/setup/index.d.ts +2 -0
- package/dist/setup/index.js +55 -0
- package/dist/setup/permissions.d.ts +3 -0
- package/dist/setup/permissions.js +52 -0
- package/dist/{setup.d.ts → setup/reddit.d.ts} +0 -1
- package/dist/setup/reddit.js +69 -0
- package/dist/setup/tui.d.ts +7 -0
- package/dist/setup/tui.js +496 -0
- package/dist/setup/types.d.ts +43 -0
- package/dist/setup/types.js +1 -0
- package/dist/tool-registry.js +1 -1
- package/dist/tools/{pages.js → pages/index.js} +8 -164
- package/dist/tools/pages/publish-format.d.ts +48 -0
- package/dist/tools/pages/publish-format.js +92 -0
- package/dist/tools/pages/workspace.d.ts +7 -0
- package/dist/tools/pages/workspace.js +14 -0
- package/dist/tools/pages/writing-guide.d.ts +1 -0
- package/dist/tools/pages/writing-guide.js +56 -0
- package/package.json +1 -1
- package/dist/setup.js +0 -1216
- package/dist/validate.d.ts +0 -971
- package/dist/validate.js +0 -154
- /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
|
|
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
|
|
6
|
-
import { openBrowser } from "
|
|
7
|
-
import { coerceJson } from "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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: \`\`
|
|
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 =
|
|
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,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: \`\`
|
|
55
|
+
Positions: "right" (default), "left", "center". Every image needs a descriptive caption.
|
|
56
|
+
`.trim();
|