openalmanac 0.2.17 → 0.2.18

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 CHANGED
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from "./server.js";
3
3
  import { runLogin, runLogout } from "./login.js";
4
+ import { runSetup } from "./setup.js";
4
5
  const command = process.argv[2];
5
- if (command === "login") {
6
+ if (command === "setup") {
7
+ runSetup().catch((e) => {
8
+ console.error(e instanceof Error ? e.message : e);
9
+ process.exit(1);
10
+ });
11
+ }
12
+ else if (command === "login") {
6
13
  runLogin().catch((e) => {
7
14
  console.error(e instanceof Error ? e.message : e);
8
15
  process.exit(1);
@@ -0,0 +1 @@
1
+ export declare function runSetup(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,231 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const TOOL_GROUPS = [
5
+ {
6
+ name: "Search & Read",
7
+ description: "search articles, pull, view status",
8
+ tools: [
9
+ "mcp__almanac__search_articles",
10
+ "mcp__almanac__pull",
11
+ "mcp__almanac__status",
12
+ "mcp__almanac__requested_articles",
13
+ ],
14
+ },
15
+ {
16
+ name: "Research",
17
+ description: "web search, read pages, find images",
18
+ tools: [
19
+ "mcp__almanac__search_web",
20
+ "mcp__almanac__read_webpage",
21
+ "mcp__almanac__search_images",
22
+ "mcp__almanac__view_image",
23
+ ],
24
+ },
25
+ {
26
+ name: "Write & Publish",
27
+ description: "create articles, push edits, stubs",
28
+ tools: [
29
+ "mcp__almanac__new",
30
+ "mcp__almanac__push",
31
+ "mcp__almanac__create_stub",
32
+ ],
33
+ },
34
+ {
35
+ name: "Auth",
36
+ description: "login and logout",
37
+ tools: ["mcp__almanac__login", "mcp__almanac__logout"],
38
+ },
39
+ {
40
+ name: "Community",
41
+ description: "communities, posts, article linking",
42
+ tools: [
43
+ "mcp__almanac__search_communities",
44
+ "mcp__almanac__create_community",
45
+ "mcp__almanac__create_post",
46
+ "mcp__almanac__link_article",
47
+ ],
48
+ },
49
+ {
50
+ name: "People",
51
+ description: "search people profiles",
52
+ tools: ["mcp__almanac__search_people"],
53
+ },
54
+ {
55
+ name: "Local Files",
56
+ description: "read & edit articles in ~/.openalmanac",
57
+ tools: [
58
+ "Read(~/.openalmanac/**)",
59
+ "Write(~/.openalmanac/**)",
60
+ "Edit(~/.openalmanac/**)",
61
+ ],
62
+ },
63
+ ];
64
+ /* ── ANSI helpers ───────────────────────────────────────────────── */
65
+ const RST = "\x1b[0m";
66
+ const BOLD = "\x1b[1m";
67
+ const DIM = "\x1b[2m";
68
+ const GREEN = "\x1b[32m";
69
+ const CYAN = "\x1b[36m";
70
+ const RED = "\x1b[31m";
71
+ const YELLOW = "\x1b[33m";
72
+ const MAGENTA = "\x1b[35m";
73
+ const WHITE_BOLD = "\x1b[1;37m";
74
+ /* ── File helpers ───────────────────────────────────────────────── */
75
+ const CLAUDE_DIR = join(homedir(), ".claude");
76
+ const MCP_JSON = join(CLAUDE_DIR, "mcp.json");
77
+ const SETTINGS_JSON = join(CLAUDE_DIR, "settings.json");
78
+ function ensureDir(dir) {
79
+ if (!existsSync(dir))
80
+ mkdirSync(dir, { recursive: true });
81
+ }
82
+ function readJson(path) {
83
+ try {
84
+ return JSON.parse(readFileSync(path, "utf-8"));
85
+ }
86
+ catch {
87
+ return {};
88
+ }
89
+ }
90
+ function writeJson(path, data) {
91
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
92
+ }
93
+ /* ── Step 1 — MCP server ───────────────────────────────────────── */
94
+ function configureMcp() {
95
+ ensureDir(CLAUDE_DIR);
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ const cfg = readJson(MCP_JSON);
98
+ if (!cfg.mcpServers)
99
+ cfg.mcpServers = {};
100
+ const cur = cfg.mcpServers.almanac;
101
+ if (cur?.command === "npx" &&
102
+ JSON.stringify(cur.args) === JSON.stringify(["-y", "openalmanac"])) {
103
+ return false; // already set
104
+ }
105
+ cfg.mcpServers.almanac = { command: "npx", args: ["-y", "openalmanac"] };
106
+ writeJson(MCP_JSON, cfg);
107
+ return true;
108
+ }
109
+ /* ── Step 2 — Permissions ──────────────────────────────────────── */
110
+ function configurePermissions(tools) {
111
+ ensureDir(CLAUDE_DIR);
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ const settings = readJson(SETTINGS_JSON);
114
+ if (!settings.permissions)
115
+ settings.permissions = {};
116
+ if (!Array.isArray(settings.permissions.allow))
117
+ settings.permissions.allow = [];
118
+ const existing = new Set(settings.permissions.allow);
119
+ let added = 0;
120
+ for (const t of tools) {
121
+ if (!existing.has(t)) {
122
+ settings.permissions.allow.push(t);
123
+ added++;
124
+ }
125
+ }
126
+ if (added > 0)
127
+ writeJson(SETTINGS_JSON, settings);
128
+ return tools.length;
129
+ }
130
+ /* ── TUI ────────────────────────────────────────────────────────── */
131
+ const MAX_NAME = Math.max(...TOOL_GROUPS.map((g) => g.name.length));
132
+ function render(selected, cursor, mcpLine) {
133
+ process.stdout.write("\x1b[2J\x1b[H"); // clear + home
134
+ const w = (s) => process.stdout.write(s + "\n");
135
+ w("");
136
+ w(` ${WHITE_BOLD}OpenAlmanac${RST} — Claude Code Setup`);
137
+ w(` ${DIM}${"─".repeat(36)}${RST}`);
138
+ w("");
139
+ w(` ${mcpLine}`);
140
+ w("");
141
+ w(` Allow all tools for a ${WHITE_BOLD}seamless${RST} writing experience?`);
142
+ w(` ${DIM}Deselect any you'd rather approve manually.${RST}`);
143
+ w("");
144
+ for (let i = 0; i < TOOL_GROUPS.length; i++) {
145
+ const arrow = i === cursor ? `${CYAN}❯${RST}` : " ";
146
+ const check = selected[i] ? `${GREEN}✓${RST}` : " ";
147
+ const pad = TOOL_GROUPS[i].name.padEnd(MAX_NAME + 2);
148
+ const name = i === cursor ? `${BOLD}${pad}${RST}` : pad;
149
+ const desc = `${DIM}${TOOL_GROUPS[i].description}${RST}`;
150
+ w(` ${arrow} [${check}] ${name} ${desc}`);
151
+ }
152
+ w("");
153
+ w(` ${GREEN}${BOLD}[enter]${RST} confirm ${CYAN}${BOLD}[space]${RST} toggle ${CYAN}${BOLD}[↑↓]${RST} move ${CYAN}${BOLD}[a]${RST} all ${CYAN}${BOLD}[q]${RST} quit`);
154
+ w("");
155
+ }
156
+ function runTui(mcpLine) {
157
+ return new Promise((resolve) => {
158
+ const selected = TOOL_GROUPS.map(() => true); // all on by default
159
+ let cursor = 0;
160
+ render(selected, cursor, mcpLine);
161
+ process.stdin.setRawMode(true);
162
+ process.stdin.resume();
163
+ process.stdin.setEncoding("utf-8");
164
+ const cleanup = () => {
165
+ process.stdin.removeListener("data", onData);
166
+ process.stdin.setRawMode(false);
167
+ process.stdin.pause();
168
+ };
169
+ const onData = (key) => {
170
+ // Ctrl-C / q → cancel
171
+ if (key === "\x03" || key === "q") {
172
+ cleanup();
173
+ process.stdout.write("\x1b[2J\x1b[H");
174
+ console.log("\n Setup cancelled.\n");
175
+ process.exit(0);
176
+ }
177
+ if (key === "\x1b[A" || key === "k")
178
+ cursor = (cursor - 1 + TOOL_GROUPS.length) % TOOL_GROUPS.length;
179
+ else if (key === "\x1b[B" || key === "j")
180
+ cursor = (cursor + 1) % TOOL_GROUPS.length;
181
+ else if (key === " ")
182
+ selected[cursor] = !selected[cursor];
183
+ else if (key === "a") {
184
+ const all = selected.every(Boolean);
185
+ selected.fill(!all);
186
+ }
187
+ else if (key === "\r" || key === "\n") {
188
+ cleanup();
189
+ const tools = [];
190
+ for (let i = 0; i < TOOL_GROUPS.length; i++) {
191
+ if (selected[i])
192
+ tools.push(...TOOL_GROUPS[i].tools);
193
+ }
194
+ resolve(tools);
195
+ return;
196
+ }
197
+ render(selected, cursor, mcpLine);
198
+ };
199
+ process.stdin.on("data", onData);
200
+ });
201
+ }
202
+ /* ── Result screen ──────────────────────────────────────────────── */
203
+ function printResult(mcpChanged, toolCount) {
204
+ process.stdout.write("\x1b[2J\x1b[H");
205
+ console.log("");
206
+ console.log(` ${WHITE_BOLD}OpenAlmanac${RST} — Claude Code Setup`);
207
+ console.log(` ${DIM}${"─".repeat(36)}${RST}`);
208
+ console.log("");
209
+ console.log(` ${GREEN}✓${RST} MCP server ${mcpChanged ? "added to" : "already in"} ${DIM}~/.claude/mcp.json${RST}`);
210
+ console.log(` ${GREEN}✓${RST} ${CYAN}${toolCount}${RST} tool${toolCount !== 1 ? "s" : ""} allowed in ${DIM}~/.claude/settings.json${RST}`);
211
+ console.log("");
212
+ console.log(` ${YELLOW}Restart Claude Code${RST} to apply changes.`);
213
+ console.log(` Then try: ${MAGENTA}"Write an article about quantum computing"${RST}`);
214
+ console.log("");
215
+ }
216
+ /* ── Entry point ────────────────────────────────────────────────── */
217
+ export async function runSetup() {
218
+ const skipTui = process.argv.includes("--yes") || process.argv.includes("-y");
219
+ const interactive = process.stdin.isTTY && !skipTui;
220
+ const mcpChanged = configureMcp();
221
+ const mcpLine = `${GREEN}✓${RST} MCP server ${mcpChanged ? "added" : "already configured"}`;
222
+ let tools;
223
+ if (interactive) {
224
+ tools = await runTui(mcpLine);
225
+ }
226
+ else {
227
+ tools = TOOL_GROUPS.flatMap((g) => g.tools);
228
+ }
229
+ const count = configurePermissions(tools);
230
+ printResult(mcpChanged, count);
231
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openalmanac",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "OpenAlmanac — pull, edit, and push articles to the open knowledge base",
5
5
  "type": "module",
6
6
  "bin": {