@lernstoff/cli 0.0.0-stage ā 0.1.0-beta.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/LICENSE +21 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +901 -0
- package/dist/bin.mjs.map +1 -0
- package/package.json +34 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JsonLee12138
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { execSync, spawn } from "node:child_process";
|
|
5
|
+
import { DaemonServer, LERNSTOFF_DIR, QmdBridge, VaultOps, createLernstoffMcpServer, formatScratchpad, getConfigPath, getConfigValue, initVault, loadConfig, parseScratchpad, resetConfigToDefaults, setConfigValue } from "@lernstoff/core";
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
// src/daemon-pid.ts
|
|
10
|
+
/**
|
|
11
|
+
* Check if a process ID is currently running in the OS.
|
|
12
|
+
*/
|
|
13
|
+
function isPidAlive(pid) {
|
|
14
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read the full command line of a process by PID across macOS, Linux, and Windows.
|
|
24
|
+
*/
|
|
25
|
+
function readProcessCmdline(pid) {
|
|
26
|
+
try {
|
|
27
|
+
if (process.platform === "win32") {
|
|
28
|
+
const match = execSync(`wmic process where ProcessId=${pid} get CommandLine /value`, {
|
|
29
|
+
stdio: [
|
|
30
|
+
"ignore",
|
|
31
|
+
"pipe",
|
|
32
|
+
"ignore"
|
|
33
|
+
],
|
|
34
|
+
timeout: 1e3
|
|
35
|
+
}).toString("utf-8").match(/CommandLine=(.*)/i);
|
|
36
|
+
return match ? match[1].trim() : null;
|
|
37
|
+
} else return execSync(`ps -p ${pid} -o command=`, {
|
|
38
|
+
stdio: [
|
|
39
|
+
"ignore",
|
|
40
|
+
"pipe",
|
|
41
|
+
"ignore"
|
|
42
|
+
],
|
|
43
|
+
timeout: 1e3
|
|
44
|
+
}).toString("utf-8").trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Verifies process identity to prevent false positives from recycled PIDs.
|
|
51
|
+
* Modeled on qmd/src/cli/mcp-pid.ts#L74-L86.
|
|
52
|
+
*/
|
|
53
|
+
function looksLikeLernstoffDaemon(cmdline) {
|
|
54
|
+
const lower = cmdline.toLowerCase();
|
|
55
|
+
return lower.includes("lernstoff") || lower.includes("daemon-server") || lower.includes("@lernstoff/cli") || lower.includes("lernstoff-wiki");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Inspects <vault>/.lernstoff/daemon.pid and daemon.port to check if the daemon is alive.
|
|
59
|
+
* If the process is dead or stale, cleans up the files and returns null.
|
|
60
|
+
*/
|
|
61
|
+
function getRunningDaemonInfo(vaultPath) {
|
|
62
|
+
const resolved = resolve(vaultPath);
|
|
63
|
+
const lernstoffDir = join(resolved, LERNSTOFF_DIR);
|
|
64
|
+
const pidFile = join(lernstoffDir, "daemon.pid");
|
|
65
|
+
const portFile = join(lernstoffDir, "daemon.port");
|
|
66
|
+
if (!existsSync(pidFile)) return null;
|
|
67
|
+
let pid;
|
|
68
|
+
try {
|
|
69
|
+
pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!isPidAlive(pid)) {
|
|
74
|
+
try {
|
|
75
|
+
unlinkSync(pidFile);
|
|
76
|
+
} catch {}
|
|
77
|
+
try {
|
|
78
|
+
if (existsSync(portFile)) unlinkSync(portFile);
|
|
79
|
+
} catch {}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const cmdline = readProcessCmdline(pid);
|
|
83
|
+
if (cmdline && !looksLikeLernstoffDaemon(cmdline)) {
|
|
84
|
+
try {
|
|
85
|
+
unlinkSync(pidFile);
|
|
86
|
+
} catch {}
|
|
87
|
+
try {
|
|
88
|
+
if (existsSync(portFile)) unlinkSync(portFile);
|
|
89
|
+
} catch {}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
let port = 8188;
|
|
93
|
+
if (existsSync(portFile)) try {
|
|
94
|
+
port = parseInt(readFileSync(portFile, "utf-8").trim(), 10) || 8188;
|
|
95
|
+
} catch {}
|
|
96
|
+
return {
|
|
97
|
+
pid,
|
|
98
|
+
port
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Gracefully kill a running daemon process.
|
|
103
|
+
*/
|
|
104
|
+
function killDaemonProcess(pid, timeoutMs = 3e3) {
|
|
105
|
+
if (!isPidAlive(pid)) return true;
|
|
106
|
+
try {
|
|
107
|
+
process.kill(pid, "SIGTERM");
|
|
108
|
+
} catch {
|
|
109
|
+
return !isPidAlive(pid);
|
|
110
|
+
}
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
while (Date.now() - start < timeoutMs) {
|
|
113
|
+
if (!isPidAlive(pid)) return true;
|
|
114
|
+
try {
|
|
115
|
+
execSync("sleep 0.05");
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, "SIGKILL");
|
|
120
|
+
} catch {}
|
|
121
|
+
return !isPidAlive(pid);
|
|
122
|
+
}
|
|
123
|
+
// src/stdio-proxy.ts
|
|
124
|
+
/**
|
|
125
|
+
* Runs MCP over stdio.
|
|
126
|
+
* If the Vault's background daemon is already running, proxies JSON-RPC over HTTP
|
|
127
|
+
* with zero memory footprint; otherwise boots an in-process MCP server instance.
|
|
128
|
+
*/
|
|
129
|
+
async function runMcpStdio(vaultPath) {
|
|
130
|
+
const daemonInfo = getRunningDaemonInfo(vaultPath);
|
|
131
|
+
if (daemonInfo) {
|
|
132
|
+
const daemonUrl = `http://127.0.0.1:${daemonInfo.port}/mcp`;
|
|
133
|
+
createInterface({
|
|
134
|
+
input: process.stdin,
|
|
135
|
+
terminal: false
|
|
136
|
+
}).on("line", async (line) => {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
if (!trimmed) return;
|
|
139
|
+
try {
|
|
140
|
+
const res = await fetch(daemonUrl, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
Accept: "application/json, text/event-stream"
|
|
145
|
+
},
|
|
146
|
+
body: trimmed
|
|
147
|
+
});
|
|
148
|
+
if ((res.headers.get("content-type") || "").includes("json")) {
|
|
149
|
+
const bodyText = await res.text();
|
|
150
|
+
if (bodyText) process.stdout.write(`${bodyText}\n`);
|
|
151
|
+
} else {
|
|
152
|
+
const reader = res.body?.getReader();
|
|
153
|
+
if (reader) while (true) {
|
|
154
|
+
const { done, value } = await reader.read();
|
|
155
|
+
if (done) break;
|
|
156
|
+
process.stdout.write(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const errorRpc = {
|
|
161
|
+
jsonrpc: "2.0",
|
|
162
|
+
id: null,
|
|
163
|
+
error: {
|
|
164
|
+
code: -32603,
|
|
165
|
+
message: `Daemon proxy error: ${err.message}`
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
process.stdout.write(`${JSON.stringify(errorRpc)}\n`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const server = createLernstoffMcpServer({ vaultPath });
|
|
174
|
+
const transport = new StdioServerTransport();
|
|
175
|
+
await server.connect(transport);
|
|
176
|
+
}
|
|
177
|
+
// src/service-manager.ts
|
|
178
|
+
var ServiceManager = class ServiceManager {
|
|
179
|
+
static getServiceSlug(vaultPath) {
|
|
180
|
+
const name = basename(resolve(vaultPath));
|
|
181
|
+
return VaultOps.slugify(name || "default");
|
|
182
|
+
}
|
|
183
|
+
static getLaunchdPlistPath(vaultPath) {
|
|
184
|
+
const slug = ServiceManager.getServiceSlug(vaultPath);
|
|
185
|
+
return join(homedir(), "Library/LaunchAgents", `ai.lernstoff.wiki.${slug}.plist`);
|
|
186
|
+
}
|
|
187
|
+
static getSystemdServicePath(vaultPath) {
|
|
188
|
+
const slug = ServiceManager.getServiceSlug(vaultPath);
|
|
189
|
+
return join(homedir(), ".config/systemd/user", `lernstoff-wiki@${slug}.service`);
|
|
190
|
+
}
|
|
191
|
+
static generateLaunchdPlist(vaultPath, cliPath) {
|
|
192
|
+
const slug = ServiceManager.getServiceSlug(vaultPath);
|
|
193
|
+
const absVault = resolve(vaultPath);
|
|
194
|
+
const logPath = join(absVault, ".lernstoff", "daemon.log");
|
|
195
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
196
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
197
|
+
<plist version="1.0">
|
|
198
|
+
<dict>
|
|
199
|
+
<key>Label</key>
|
|
200
|
+
<string>ai.lernstoff.wiki.${slug}</string>
|
|
201
|
+
<key>ProgramArguments</key>
|
|
202
|
+
<array>
|
|
203
|
+
<string>${process.execPath}</string>
|
|
204
|
+
<string>${cliPath}</string>
|
|
205
|
+
<string>server</string>
|
|
206
|
+
<string>start</string>
|
|
207
|
+
<string>--foreground</string>
|
|
208
|
+
<string>--vault</string>
|
|
209
|
+
<string>${absVault}</string>
|
|
210
|
+
</array>
|
|
211
|
+
<key>RunAtLoad</key>
|
|
212
|
+
<true/>
|
|
213
|
+
<key>KeepAlive</key>
|
|
214
|
+
<true/>
|
|
215
|
+
<key>StandardOutPath</key>
|
|
216
|
+
<string>${logPath}</string>
|
|
217
|
+
<key>StandardErrorPath</key>
|
|
218
|
+
<string>${logPath}</string>
|
|
219
|
+
<key>WorkingDirectory</key>
|
|
220
|
+
<string>${absVault}</string>
|
|
221
|
+
</dict>
|
|
222
|
+
</plist>
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
static generateSystemdService(vaultPath, cliPath) {
|
|
226
|
+
const slug = ServiceManager.getServiceSlug(vaultPath);
|
|
227
|
+
const absVault = resolve(vaultPath);
|
|
228
|
+
const logPath = join(absVault, ".lernstoff", "daemon.log");
|
|
229
|
+
return `[Unit]
|
|
230
|
+
Description=Lernstoff Wiki Headless Cognitive Engine (${slug})
|
|
231
|
+
After=network.target
|
|
232
|
+
|
|
233
|
+
[Service]
|
|
234
|
+
Type=simple
|
|
235
|
+
WorkingDirectory=${absVault}
|
|
236
|
+
ExecStart=${process.execPath} ${cliPath} server start --foreground --vault ${absVault}
|
|
237
|
+
Restart=always
|
|
238
|
+
RestartSec=5
|
|
239
|
+
StandardOutput=append:${logPath}
|
|
240
|
+
StandardError=append:${logPath}
|
|
241
|
+
|
|
242
|
+
[Install]
|
|
243
|
+
WantedBy=default.target
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Install OS boot auto-start service for the target Vault.
|
|
248
|
+
*/
|
|
249
|
+
static async installService(vaultPath, cliPath = process.argv[1] || "lernstoff") {
|
|
250
|
+
const absVault = resolve(vaultPath);
|
|
251
|
+
const platform = process.platform;
|
|
252
|
+
if (platform === "darwin") {
|
|
253
|
+
const plistPath = ServiceManager.getLaunchdPlistPath(absVault);
|
|
254
|
+
mkdirSync(join(homedir(), "Library/LaunchAgents"), { recursive: true });
|
|
255
|
+
const plistXml = ServiceManager.generateLaunchdPlist(absVault, cliPath);
|
|
256
|
+
writeFileSync(plistPath, plistXml, "utf-8");
|
|
257
|
+
try {
|
|
258
|
+
execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
259
|
+
} catch {}
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
servicePath: plistPath,
|
|
263
|
+
platform: "darwin"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (platform === "linux") {
|
|
267
|
+
const servicePath = ServiceManager.getSystemdServicePath(absVault);
|
|
268
|
+
mkdirSync(join(homedir(), ".config/systemd/user"), { recursive: true });
|
|
269
|
+
const serviceUnit = ServiceManager.generateSystemdService(absVault, cliPath);
|
|
270
|
+
writeFileSync(servicePath, serviceUnit, "utf-8");
|
|
271
|
+
try {
|
|
272
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
273
|
+
execSync(`systemctl --user enable --now "${basename(servicePath)}"`, { stdio: "ignore" });
|
|
274
|
+
} catch {}
|
|
275
|
+
return {
|
|
276
|
+
success: true,
|
|
277
|
+
servicePath,
|
|
278
|
+
platform: "linux"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const fallbackPath = join(absVault, ".lernstoff", "autostart.cmd");
|
|
282
|
+
writeFileSync(fallbackPath, `@echo off\n"${process.execPath}" "${cliPath}" server start --foreground --vault "${absVault}"\n`, "utf-8");
|
|
283
|
+
return {
|
|
284
|
+
success: true,
|
|
285
|
+
servicePath: fallbackPath,
|
|
286
|
+
platform: "win32"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Uninstall and deregister the OS boot auto-start service.
|
|
291
|
+
*/
|
|
292
|
+
static async uninstallService(vaultPath) {
|
|
293
|
+
const absVault = resolve(vaultPath);
|
|
294
|
+
const platform = process.platform;
|
|
295
|
+
if (platform === "darwin") {
|
|
296
|
+
const plistPath = ServiceManager.getLaunchdPlistPath(absVault);
|
|
297
|
+
if (existsSync(plistPath)) {
|
|
298
|
+
try {
|
|
299
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
300
|
+
} catch {}
|
|
301
|
+
try {
|
|
302
|
+
unlinkSync(plistPath);
|
|
303
|
+
} catch {}
|
|
304
|
+
return {
|
|
305
|
+
success: true,
|
|
306
|
+
servicePath: plistPath
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
} else if (platform === "linux") {
|
|
310
|
+
const servicePath = ServiceManager.getSystemdServicePath(absVault);
|
|
311
|
+
if (existsSync(servicePath)) {
|
|
312
|
+
try {
|
|
313
|
+
execSync(`systemctl --user stop "${basename(servicePath)}"`, { stdio: "ignore" });
|
|
314
|
+
execSync(`systemctl --user disable "${basename(servicePath)}"`, { stdio: "ignore" });
|
|
315
|
+
} catch {}
|
|
316
|
+
try {
|
|
317
|
+
unlinkSync(servicePath);
|
|
318
|
+
} catch {}
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
321
|
+
servicePath
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return { success: true };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get service registration status and current running daemon info.
|
|
329
|
+
*/
|
|
330
|
+
static async getServiceStatus(vaultPath) {
|
|
331
|
+
const absVault = resolve(vaultPath);
|
|
332
|
+
const platform = process.platform;
|
|
333
|
+
let installed = false;
|
|
334
|
+
let servicePath;
|
|
335
|
+
if (platform === "darwin") {
|
|
336
|
+
servicePath = ServiceManager.getLaunchdPlistPath(absVault);
|
|
337
|
+
installed = existsSync(servicePath);
|
|
338
|
+
} else if (platform === "linux") {
|
|
339
|
+
servicePath = ServiceManager.getSystemdServicePath(absVault);
|
|
340
|
+
installed = existsSync(servicePath);
|
|
341
|
+
}
|
|
342
|
+
const daemonInfo = getRunningDaemonInfo(absVault);
|
|
343
|
+
return {
|
|
344
|
+
installed,
|
|
345
|
+
running: !!daemonInfo,
|
|
346
|
+
servicePath: installed ? servicePath : void 0,
|
|
347
|
+
platform,
|
|
348
|
+
pid: daemonInfo?.pid,
|
|
349
|
+
port: daemonInfo?.port
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
// src/agent-installer.ts
|
|
354
|
+
const DEFAULT_SKILL_MD = `---
|
|
355
|
+
name: lernstoff-wiki
|
|
356
|
+
description: Headless Cognitive Selective Note-Taking Protocol for AI Agents and Obsidian. Use when reading documentation, analyzing papers, researching concepts, managing curiosity inquiry trees, verifying cognitive deltas, and creating evergreen proposition notes.
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
# lernstoff-wiki Cognitive Selective Note-Taking Protocol
|
|
360
|
+
|
|
361
|
+
When pair-programming, exploring codebases, reading papers, or synthesizing architecture decisions, follow this 4-step selective note-taking protocol to avoid information hoarding ("Collector's Fallacy") and keep the Obsidian Vault high-signal.
|
|
362
|
+
|
|
363
|
+
## Protocol Workflow
|
|
364
|
+
1. Step 1: Probe Cognitive Delta (lernstoff_probe_delta) - NEVER write notes directly to disk.
|
|
365
|
+
2. Step 2: Socratic 4-Question Defense (Q1: Delta, Q2: Belief update, Q3: >= 2 outbound links, Q4: Proposition-as-Title).
|
|
366
|
+
3. Step 3: Crystallize Proposition Note (lernstoff_crystallize_note).
|
|
367
|
+
4. Step 4: Fast Scratchpad Consolidation (lernstoff_consolidate_scratchpad).
|
|
368
|
+
`;
|
|
369
|
+
var AgentInstaller = class AgentInstaller {
|
|
370
|
+
static getMcpConfig(vaultPath) {
|
|
371
|
+
return {
|
|
372
|
+
command: "lernstoff",
|
|
373
|
+
args: [
|
|
374
|
+
"mcp",
|
|
375
|
+
"--vault",
|
|
376
|
+
resolve(vaultPath)
|
|
377
|
+
]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
static installSkill(target, vaultPath) {
|
|
381
|
+
const absVault = resolve(vaultPath);
|
|
382
|
+
const installedPaths = [];
|
|
383
|
+
let skillContent = DEFAULT_SKILL_MD;
|
|
384
|
+
const localSkill = join(absVault, "skills/lernstoff-wiki/SKILL.md");
|
|
385
|
+
if (existsSync(localSkill)) try {
|
|
386
|
+
skillContent = readFileSync(localSkill, "utf-8");
|
|
387
|
+
} catch {}
|
|
388
|
+
const targets = {
|
|
389
|
+
claude: [join(homedir(), ".claude/skills/lernstoff-wiki/SKILL.md")],
|
|
390
|
+
antigravity: [join(homedir(), ".gemini/config/skills/lernstoff-wiki/SKILL.md")],
|
|
391
|
+
cursor: [join(absVault, ".cursor/skills/lernstoff-wiki/SKILL.md"), join(homedir(), ".cursor/skills/lernstoff-wiki/SKILL.md")]
|
|
392
|
+
};
|
|
393
|
+
const keys = target === "all" ? Object.keys(targets) : [target];
|
|
394
|
+
for (const key of keys) {
|
|
395
|
+
const paths = targets[key] || [];
|
|
396
|
+
for (const p of paths) try {
|
|
397
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
398
|
+
writeFileSync(p, skillContent, "utf-8");
|
|
399
|
+
installedPaths.push(p);
|
|
400
|
+
} catch {}
|
|
401
|
+
}
|
|
402
|
+
return installedPaths;
|
|
403
|
+
}
|
|
404
|
+
static installMcp(target, vaultPath) {
|
|
405
|
+
const absVault = resolve(vaultPath);
|
|
406
|
+
const mcpEntry = AgentInstaller.getMcpConfig(absVault);
|
|
407
|
+
const configuredFiles = [];
|
|
408
|
+
const configFiles = {
|
|
409
|
+
claude: [join(homedir(), ".claude.json"), join(absVault, ".mcp.json")],
|
|
410
|
+
cursor: [join(absVault, ".cursor/mcp.json"), join(homedir(), ".cursor/mcp.json")],
|
|
411
|
+
antigravity: [join(homedir(), ".gemini/antigravity/mcp_config.json")]
|
|
412
|
+
};
|
|
413
|
+
const keys = target === "all" ? Object.keys(configFiles) : [target];
|
|
414
|
+
for (const key of keys) {
|
|
415
|
+
const files = configFiles[key] || [];
|
|
416
|
+
for (const file of files) try {
|
|
417
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
418
|
+
let json = {};
|
|
419
|
+
if (existsSync(file)) try {
|
|
420
|
+
json = JSON.parse(readFileSync(file, "utf-8"));
|
|
421
|
+
} catch {
|
|
422
|
+
json = {};
|
|
423
|
+
}
|
|
424
|
+
if (!json.mcpServers) json.mcpServers = {};
|
|
425
|
+
json.mcpServers["lernstoff"] = mcpEntry;
|
|
426
|
+
writeFileSync(file, JSON.stringify(json, null, 2), "utf-8");
|
|
427
|
+
configuredFiles.push(file);
|
|
428
|
+
} catch {}
|
|
429
|
+
}
|
|
430
|
+
return configuredFiles;
|
|
431
|
+
}
|
|
432
|
+
static async runDoctor(options) {
|
|
433
|
+
const absVault = resolve(options.vaultPath);
|
|
434
|
+
const lernstoffDir = join(absVault, LERNSTOFF_DIR);
|
|
435
|
+
const configFile = join(lernstoffDir, "config.json");
|
|
436
|
+
const sqliteFile = join(lernstoffDir, "index.sqlite");
|
|
437
|
+
const vaultInitialized = existsSync(configFile);
|
|
438
|
+
const sqlitePresent = existsSync(sqliteFile);
|
|
439
|
+
const daemonInfo = getRunningDaemonInfo(absVault);
|
|
440
|
+
let models = {};
|
|
441
|
+
if (vaultInitialized) try {
|
|
442
|
+
models = JSON.parse(readFileSync(configFile, "utf-8")).models || {};
|
|
443
|
+
} catch {}
|
|
444
|
+
if (options.cleanup && sqlitePresent) try {
|
|
445
|
+
const bridge = new QmdBridge({ vaultPath: absVault });
|
|
446
|
+
const store = await bridge.getStore();
|
|
447
|
+
if (typeof store.cleanup === "function") await store.cleanup();
|
|
448
|
+
await bridge.close();
|
|
449
|
+
} catch {}
|
|
450
|
+
if (options.pullModels) {}
|
|
451
|
+
return {
|
|
452
|
+
nodeVersion: process.version,
|
|
453
|
+
platform: process.platform,
|
|
454
|
+
arch: process.arch,
|
|
455
|
+
vaultPath: absVault,
|
|
456
|
+
vaultInitialized,
|
|
457
|
+
daemonRunning: !!daemonInfo,
|
|
458
|
+
daemonPid: daemonInfo?.pid,
|
|
459
|
+
daemonPort: daemonInfo?.port,
|
|
460
|
+
sqlitePresent,
|
|
461
|
+
configValid: vaultInitialized,
|
|
462
|
+
models
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
// src/cognitive-cmds.ts
|
|
467
|
+
/**
|
|
468
|
+
* Builds editor URI (e.g. obsidian://open?path={path}) for terminal hyperlinks.
|
|
469
|
+
* Modeled on qmd/src/cli/qmd.ts#L2660-L2675.
|
|
470
|
+
*/
|
|
471
|
+
function buildEditorUri(template, absPath, line = 1, col = 1) {
|
|
472
|
+
return template.replace("{path}", encodeURIComponent(absPath)).replace("{line}", String(line)).replace("{col}", String(col));
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* OSC-8 terminal hyperlink format.
|
|
476
|
+
*/
|
|
477
|
+
function termLink(text, url, isTTY = !!process.stdout.isTTY) {
|
|
478
|
+
if (!isTTY) return text;
|
|
479
|
+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
|
|
480
|
+
}
|
|
481
|
+
async function searchCmd(vaultPath, query, options = {}) {
|
|
482
|
+
const absVault = resolve(vaultPath);
|
|
483
|
+
const config = loadConfig(absVault);
|
|
484
|
+
const limit = options.limit || 5;
|
|
485
|
+
let daemonInfo = getRunningDaemonInfo(absVault);
|
|
486
|
+
let searchResult;
|
|
487
|
+
if (daemonInfo) try {
|
|
488
|
+
searchResult = await (await fetch(`http://127.0.0.1:${daemonInfo.port}/api/search`, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: { "Content-Type": "application/json" },
|
|
491
|
+
body: JSON.stringify({
|
|
492
|
+
queries: [{
|
|
493
|
+
type: options.lex ? "lex" : "vec",
|
|
494
|
+
query
|
|
495
|
+
}],
|
|
496
|
+
limit
|
|
497
|
+
})
|
|
498
|
+
})).json();
|
|
499
|
+
} catch {
|
|
500
|
+
daemonInfo = null;
|
|
501
|
+
}
|
|
502
|
+
if (!daemonInfo) {
|
|
503
|
+
const bridge = new QmdBridge({
|
|
504
|
+
vaultPath: absVault,
|
|
505
|
+
config
|
|
506
|
+
});
|
|
507
|
+
try {
|
|
508
|
+
searchResult = await bridge.search({
|
|
509
|
+
queries: [{
|
|
510
|
+
type: options.lex ? "lex" : "vec",
|
|
511
|
+
query
|
|
512
|
+
}],
|
|
513
|
+
limit
|
|
514
|
+
});
|
|
515
|
+
} finally {
|
|
516
|
+
await bridge.close();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (options.json) {
|
|
520
|
+
console.log(JSON.stringify(searchResult, null, 2));
|
|
521
|
+
return searchResult;
|
|
522
|
+
}
|
|
523
|
+
const hits = Array.isArray(searchResult) ? searchResult : searchResult.directHits || [];
|
|
524
|
+
if (hits.length === 0) {
|
|
525
|
+
console.log(`No results found for "${query}".`);
|
|
526
|
+
return hits;
|
|
527
|
+
}
|
|
528
|
+
console.log(`\nš Found ${hits.length} note(s) for "${query}":\n`);
|
|
529
|
+
for (const hit of hits) {
|
|
530
|
+
const filePath = hit.displayPath || hit.file || "";
|
|
531
|
+
const fullPath = join(absVault, filePath);
|
|
532
|
+
const link = termLink(filePath, buildEditorUri(config.paths.editorUriTemplate, fullPath));
|
|
533
|
+
const title = hit.title || filePath;
|
|
534
|
+
const score = hit.score !== void 0 ? ` (score: ${(hit.score * 100).toFixed(1)}%)` : "";
|
|
535
|
+
console.log(` ⢠${title}${score}`);
|
|
536
|
+
console.log(` ${link}\n`);
|
|
537
|
+
}
|
|
538
|
+
return searchResult;
|
|
539
|
+
}
|
|
540
|
+
async function inquiryCmd(vaultPath, action, textOrId, options = {}) {
|
|
541
|
+
const absVault = resolve(vaultPath);
|
|
542
|
+
let daemonInfo = getRunningDaemonInfo(absVault);
|
|
543
|
+
let result;
|
|
544
|
+
const payload = { action };
|
|
545
|
+
if (action === "add") payload.question = textOrId;
|
|
546
|
+
if (action === "close" || action === "reopen") payload.id = textOrId;
|
|
547
|
+
if (daemonInfo) try {
|
|
548
|
+
result = await (await fetch(`http://127.0.0.1:${daemonInfo.port}/api/inquiries`, {
|
|
549
|
+
method: "POST",
|
|
550
|
+
headers: { "Content-Type": "application/json" },
|
|
551
|
+
body: JSON.stringify(payload)
|
|
552
|
+
})).json();
|
|
553
|
+
} catch {
|
|
554
|
+
daemonInfo = null;
|
|
555
|
+
}
|
|
556
|
+
if (!daemonInfo) result = await new VaultOps(absVault).manageInquiries(payload);
|
|
557
|
+
if (options.json) {
|
|
558
|
+
console.log(JSON.stringify(result, null, 2));
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
if (action === "list" && Array.isArray(result)) {
|
|
562
|
+
console.log(`\nš§ Active Inquiry Tree (${result.length} questions):\n`);
|
|
563
|
+
for (const inq of result) {
|
|
564
|
+
const statusIcon = inq.status === "OPEN" ? "š¢" : "āŖ";
|
|
565
|
+
console.log(` ${statusIcon} [${inq.id || "#"}] ${inq.question}`);
|
|
566
|
+
if (inq.supportingNoteCount > 0) console.log(` ā³ ${inq.supportingNoteCount} supporting proposition note(s)`);
|
|
567
|
+
}
|
|
568
|
+
console.log("");
|
|
569
|
+
} else console.log(`Inquiry updated:`, result);
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
async function scratchpadCmd(vaultPath, action, text, options = {}) {
|
|
573
|
+
const absVault = resolve(vaultPath);
|
|
574
|
+
const config = loadConfig(absVault);
|
|
575
|
+
const scratchFile = join(absVault, config.paths.scratchpadFile);
|
|
576
|
+
if (action === "add") {
|
|
577
|
+
if (!text) throw new Error("Text is required for scratchpad add.");
|
|
578
|
+
let entries = [];
|
|
579
|
+
if (existsSync(scratchFile)) entries = parseScratchpad(readFileSync(scratchFile, "utf-8"));
|
|
580
|
+
entries.push({
|
|
581
|
+
firstSeen: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
582
|
+
claim: text.trim(),
|
|
583
|
+
hitCount: 1,
|
|
584
|
+
relatedEntities: [],
|
|
585
|
+
sources: ["cli"],
|
|
586
|
+
status: "DEFERRED"
|
|
587
|
+
});
|
|
588
|
+
writeFileSync(scratchFile, formatScratchpad(entries), "utf-8");
|
|
589
|
+
console.log(`Recorded note to ${config.paths.scratchpadFile}`);
|
|
590
|
+
return { added: text };
|
|
591
|
+
}
|
|
592
|
+
let daemonInfo = getRunningDaemonInfo(absVault);
|
|
593
|
+
let result;
|
|
594
|
+
const opAction = action === "gc" ? "gc" : "inspect";
|
|
595
|
+
if (daemonInfo) try {
|
|
596
|
+
result = await (await fetch(`http://127.0.0.1:${daemonInfo.port}/api/scratchpad`, {
|
|
597
|
+
method: "POST",
|
|
598
|
+
headers: { "Content-Type": "application/json" },
|
|
599
|
+
body: JSON.stringify({ action: opAction })
|
|
600
|
+
})).json();
|
|
601
|
+
} catch {
|
|
602
|
+
daemonInfo = null;
|
|
603
|
+
}
|
|
604
|
+
if (!daemonInfo) result = await new VaultOps(absVault).consolidateScratchpad({ action: opAction });
|
|
605
|
+
if (options.json) {
|
|
606
|
+
console.log(JSON.stringify(result, null, 2));
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
console.log(`\nā” Scratchpad Buffer (Total: ${result.activeCount}, Ready for Promotion: ${result.promotions.length}):`);
|
|
610
|
+
for (const p of result.promotions) console.log(` ā [PROMOTION READY] ${p.claim} (hits: ${p.hitCount}, sources: ${p.sources.join(", ")})`);
|
|
611
|
+
if (result.decayedArchivedCount) console.log(` šļø Pruned & archived ${result.decayedArchivedCount} expired entries to .lernstoff/archive/decayed.jsonl`);
|
|
612
|
+
console.log("");
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
async function lintCmd(vaultPath, options = {}) {
|
|
616
|
+
const absVault = resolve(vaultPath);
|
|
617
|
+
let daemonInfo = getRunningDaemonInfo(absVault);
|
|
618
|
+
let result;
|
|
619
|
+
if (daemonInfo) try {
|
|
620
|
+
result = await (await fetch(`http://127.0.0.1:${daemonInfo.port}/api/lint`, {
|
|
621
|
+
method: "POST",
|
|
622
|
+
headers: { "Content-Type": "application/json" },
|
|
623
|
+
body: JSON.stringify({
|
|
624
|
+
checkOrphans: true,
|
|
625
|
+
checkContradictions: true,
|
|
626
|
+
checkSynthesisReady: true
|
|
627
|
+
})
|
|
628
|
+
})).json();
|
|
629
|
+
} catch {
|
|
630
|
+
daemonInfo = null;
|
|
631
|
+
}
|
|
632
|
+
if (!daemonInfo) result = await new VaultOps(absVault).lintWiki();
|
|
633
|
+
if (options.json) {
|
|
634
|
+
console.log(JSON.stringify(result, null, 2));
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
console.log(`\nšļø Socratic Health Audit:`);
|
|
638
|
+
console.log(` Total Notes: ${result.summary.totalNotes}`);
|
|
639
|
+
console.log(` Orphan Notes (in-degree 0): ${result.summary.orphanCount}`);
|
|
640
|
+
for (const o of result.orphans) console.log(` ā ļø Orphan: [[${o.title}]] (in: ${o.inDegree}, out: ${o.outDegree})`);
|
|
641
|
+
console.log(` Contradictions: ${result.summary.contradictionCount}`);
|
|
642
|
+
for (const c of result.contradictions) console.log(` ā” Latent Contradiction: [[${c.noteA}]] vs [[${c.noteB}]]`);
|
|
643
|
+
console.log(` Synthesis Ready Inquiries: ${result.summary.synthesisReadyCount}`);
|
|
644
|
+
for (const s of result.synthesisReady) console.log(` š Ready: ${s.inquiryId} has ${s.supportingNoteCount} supporting notes -> ready for survey note!`);
|
|
645
|
+
console.log("");
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
// src/bin.ts
|
|
649
|
+
function parseArgs(args) {
|
|
650
|
+
const flags = {};
|
|
651
|
+
const positional = [];
|
|
652
|
+
for (let i = 0; i < args.length; i++) {
|
|
653
|
+
const arg = args[i];
|
|
654
|
+
if (arg.startsWith("--")) {
|
|
655
|
+
const key = arg.slice(2);
|
|
656
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
657
|
+
flags[key] = args[i + 1];
|
|
658
|
+
i++;
|
|
659
|
+
} else flags[key] = true;
|
|
660
|
+
} else if (arg.startsWith("-")) flags[arg.slice(1)] = true;
|
|
661
|
+
else positional.push(arg);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
flags,
|
|
665
|
+
positional
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
async function main() {
|
|
669
|
+
const { flags, positional } = parseArgs(process.argv.slice(2));
|
|
670
|
+
const vaultPath = resolve(typeof flags.vault === "string" ? flags.vault : process.cwd());
|
|
671
|
+
const command = positional[0] || "help";
|
|
672
|
+
const subCommand = positional[1];
|
|
673
|
+
switch (command) {
|
|
674
|
+
case "init":
|
|
675
|
+
await initVault(vaultPath);
|
|
676
|
+
console.log(`⨠Initialized lernstoff-wiki in: ${vaultPath}`);
|
|
677
|
+
console.log(` Created .lernstoff/config.json and starter templates.`);
|
|
678
|
+
break;
|
|
679
|
+
case "config": {
|
|
680
|
+
const action = subCommand || "list";
|
|
681
|
+
const key = positional[2];
|
|
682
|
+
const val = positional[3];
|
|
683
|
+
if (action === "path") console.log(getConfigPath(vaultPath));
|
|
684
|
+
else if (action === "get" && key) {
|
|
685
|
+
const value = getConfigValue(vaultPath, key);
|
|
686
|
+
console.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
|
|
687
|
+
} else if (action === "set" && key && val !== void 0) {
|
|
688
|
+
let parsedVal = val;
|
|
689
|
+
if (val === "true") parsedVal = true;
|
|
690
|
+
else if (val === "false") parsedVal = false;
|
|
691
|
+
else if (!isNaN(Number(val))) parsedVal = Number(val);
|
|
692
|
+
setConfigValue(vaultPath, key, parsedVal);
|
|
693
|
+
console.log(`Updated ${key} = ${JSON.stringify(parsedVal)}`);
|
|
694
|
+
const daemonInfo = getRunningDaemonInfo(vaultPath);
|
|
695
|
+
if (daemonInfo) try {
|
|
696
|
+
await fetch(`http://127.0.0.1:${daemonInfo.port}/api/reload-config`, { method: "POST" });
|
|
697
|
+
} catch {}
|
|
698
|
+
} else if (action === "reset") {
|
|
699
|
+
const conf = resetConfigToDefaults(vaultPath, key);
|
|
700
|
+
console.log(`Reset configuration to defaults:`, conf);
|
|
701
|
+
} else {
|
|
702
|
+
const conf = loadConfig(vaultPath);
|
|
703
|
+
console.log(JSON.stringify(conf, null, 2));
|
|
704
|
+
}
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case "server": {
|
|
708
|
+
const action = subCommand || "status";
|
|
709
|
+
if (action === "start") {
|
|
710
|
+
if (flags.daemon !== void 0 || flags.d !== void 0) {
|
|
711
|
+
const existing = getRunningDaemonInfo(vaultPath);
|
|
712
|
+
if (existing) {
|
|
713
|
+
console.log(`Daemon already running on http://127.0.0.1:${existing.port} (PID ${existing.pid})`);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const logPath = join(vaultPath, LERNSTOFF_DIR, "daemon.log");
|
|
717
|
+
const logFd = openSync(logPath, "a");
|
|
718
|
+
const child = spawn(process.execPath, [
|
|
719
|
+
process.argv[1] || "",
|
|
720
|
+
"server",
|
|
721
|
+
"start",
|
|
722
|
+
"--foreground",
|
|
723
|
+
"--vault",
|
|
724
|
+
vaultPath
|
|
725
|
+
], {
|
|
726
|
+
stdio: [
|
|
727
|
+
"ignore",
|
|
728
|
+
logFd,
|
|
729
|
+
logFd
|
|
730
|
+
],
|
|
731
|
+
detached: true,
|
|
732
|
+
cwd: vaultPath,
|
|
733
|
+
env: process.env
|
|
734
|
+
});
|
|
735
|
+
child.unref();
|
|
736
|
+
closeSync(logFd);
|
|
737
|
+
console.log(`š Started lernstoff daemon in background (PID ${child.pid})`);
|
|
738
|
+
console.log(` Logs: ${logPath}`);
|
|
739
|
+
} else {
|
|
740
|
+
const daemon = new DaemonServer({ vaultPath });
|
|
741
|
+
if (flags.port) daemon.config.server.port = Number(flags.port);
|
|
742
|
+
const port = await daemon.start();
|
|
743
|
+
console.log(`š§ Lernstoff daemon running on http://127.0.0.1:${port} (PID ${process.pid})`);
|
|
744
|
+
const shutdown = async () => {
|
|
745
|
+
console.log("\nShutting down lernstoff daemon...");
|
|
746
|
+
await daemon.stop();
|
|
747
|
+
process.exit(0);
|
|
748
|
+
};
|
|
749
|
+
process.on("SIGINT", shutdown);
|
|
750
|
+
process.on("SIGTERM", shutdown);
|
|
751
|
+
}
|
|
752
|
+
} else if (action === "stop") {
|
|
753
|
+
const existing = getRunningDaemonInfo(vaultPath);
|
|
754
|
+
if (!existing) {
|
|
755
|
+
console.log(`No daemon running for vault: ${vaultPath}`);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
killDaemonProcess(existing.pid);
|
|
759
|
+
console.log(`š Stopped daemon (PID ${existing.pid})`);
|
|
760
|
+
} else if (action === "restart") {
|
|
761
|
+
const existing = getRunningDaemonInfo(vaultPath);
|
|
762
|
+
if (existing) killDaemonProcess(existing.pid);
|
|
763
|
+
const port = await new DaemonServer({ vaultPath }).start();
|
|
764
|
+
console.log(`š Restarted daemon on port ${port} (PID ${process.pid})`);
|
|
765
|
+
} else if (action === "status") {
|
|
766
|
+
const existing = getRunningDaemonInfo(vaultPath);
|
|
767
|
+
if (existing) console.log(`š¢ Daemon Running (PID ${existing.pid}, Port ${existing.port})`);
|
|
768
|
+
else console.log(`āŖ Daemon Stopped`);
|
|
769
|
+
} else if (action === "logs") {
|
|
770
|
+
const logPath = join(vaultPath, LERNSTOFF_DIR, "daemon.log");
|
|
771
|
+
if (existsSync(logPath)) {
|
|
772
|
+
const lines = readFileSync(logPath, "utf-8").split("\n").slice(-50).join("\n");
|
|
773
|
+
console.log(lines);
|
|
774
|
+
} else console.log(`No logs found at ${logPath}`);
|
|
775
|
+
}
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
case "service": {
|
|
779
|
+
const action = subCommand || "status";
|
|
780
|
+
if (action === "install") {
|
|
781
|
+
const res = await ServiceManager.installService(vaultPath, process.argv[1]);
|
|
782
|
+
console.log(`ā
Installed OS boot auto-start service (${res.platform}): ${res.servicePath}`);
|
|
783
|
+
} else if (action === "uninstall") {
|
|
784
|
+
await ServiceManager.uninstallService(vaultPath);
|
|
785
|
+
console.log(`šļø Uninstalled OS boot auto-start service for: ${vaultPath}`);
|
|
786
|
+
} else {
|
|
787
|
+
const status = await ServiceManager.getServiceStatus(vaultPath);
|
|
788
|
+
console.log(`Service Status:`, status);
|
|
789
|
+
}
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case "mcp":
|
|
793
|
+
await runMcpStdio(vaultPath);
|
|
794
|
+
break;
|
|
795
|
+
case "search": {
|
|
796
|
+
const query = positional.slice(1).join(" ");
|
|
797
|
+
if (!query) {
|
|
798
|
+
console.error("Usage: lernstoff search <query>");
|
|
799
|
+
process.exit(1);
|
|
800
|
+
}
|
|
801
|
+
await searchCmd(vaultPath, query, {
|
|
802
|
+
lex: !!flags.lex,
|
|
803
|
+
vec: !!flags.vec,
|
|
804
|
+
limit: flags.limit ? Number(flags.limit) : 5,
|
|
805
|
+
json: !!flags.json
|
|
806
|
+
});
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
case "inquiry": {
|
|
810
|
+
const action = subCommand || "list";
|
|
811
|
+
const textOrId = positional[2];
|
|
812
|
+
await inquiryCmd(vaultPath, action, textOrId, { json: !!flags.json });
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
case "scratchpad":
|
|
816
|
+
await scratchpadCmd(vaultPath, subCommand || "list", positional.slice(2).join(" "), { json: !!flags.json });
|
|
817
|
+
break;
|
|
818
|
+
case "lint":
|
|
819
|
+
await lintCmd(vaultPath, { json: !!flags.json });
|
|
820
|
+
break;
|
|
821
|
+
case "skill":
|
|
822
|
+
if ((subCommand || "show") === "install") {
|
|
823
|
+
const target = flags.target || "all";
|
|
824
|
+
const skills = AgentInstaller.installSkill(target, vaultPath);
|
|
825
|
+
const mcps = AgentInstaller.installMcp(target, vaultPath);
|
|
826
|
+
console.log(`ā
Installed SKILL.md to:\n ${skills.join("\n ")}`);
|
|
827
|
+
console.log(`ā
Injected MCP configuration to:\n ${mcps.join("\n ")}`);
|
|
828
|
+
} else console.log(DEFAULT_SKILL_MD);
|
|
829
|
+
break;
|
|
830
|
+
case "doctor": {
|
|
831
|
+
const report = await AgentInstaller.runDoctor({
|
|
832
|
+
vaultPath,
|
|
833
|
+
pullModels: !!flags["pull-models"],
|
|
834
|
+
cleanup: !!flags.cleanup
|
|
835
|
+
});
|
|
836
|
+
console.log(`\n𩺠Lernstoff Doctor Report:`);
|
|
837
|
+
console.log(JSON.stringify(report, null, 2));
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
case "plugin":
|
|
841
|
+
if ((subCommand || "install") === "install") {
|
|
842
|
+
const obsidianDir = join(vaultPath, ".obsidian");
|
|
843
|
+
const targetPluginDir = join(obsidianDir, "plugins", "lernstoff-wiki");
|
|
844
|
+
mkdirSync(targetPluginDir, { recursive: true });
|
|
845
|
+
const candidateDirs = [
|
|
846
|
+
join(import.meta.dirname, "../../obsidian-plugin/dist"),
|
|
847
|
+
join(import.meta.dirname, "../node_modules/@lernstoff/obsidian-plugin/dist"),
|
|
848
|
+
join(process.cwd(), "packages/obsidian-plugin/dist")
|
|
849
|
+
];
|
|
850
|
+
let foundDir = null;
|
|
851
|
+
for (const dir of candidateDirs) if (existsSync(join(dir, "main.js")) && existsSync(join(dir, "manifest.json"))) {
|
|
852
|
+
foundDir = dir;
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
if (!foundDir) {
|
|
856
|
+
console.error("Could not find compiled plugin assets (main.js, manifest.json). Please run 'pnpm build' first.");
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
copyFileSync(join(foundDir, "main.js"), join(targetPluginDir, "main.js"));
|
|
860
|
+
copyFileSync(join(foundDir, "manifest.json"), join(targetPluginDir, "manifest.json"));
|
|
861
|
+
if (existsSync(join(foundDir, "styles.css"))) copyFileSync(join(foundDir, "styles.css"), join(targetPluginDir, "styles.css"));
|
|
862
|
+
const communityPluginsPath = join(obsidianDir, "community-plugins.json");
|
|
863
|
+
try {
|
|
864
|
+
let plugins = [];
|
|
865
|
+
if (existsSync(communityPluginsPath)) plugins = JSON.parse(readFileSync(communityPluginsPath, "utf-8"));
|
|
866
|
+
if (!plugins.includes("lernstoff-wiki")) {
|
|
867
|
+
plugins.push("lernstoff-wiki");
|
|
868
|
+
writeFileSync(communityPluginsPath, JSON.stringify(plugins, null, 2), "utf-8");
|
|
869
|
+
}
|
|
870
|
+
} catch {}
|
|
871
|
+
console.log(`ā
Successfully installed Obsidian plugin into:\n ${targetPluginDir}`);
|
|
872
|
+
console.log(` Copied: main.js, manifest.json, styles.css`);
|
|
873
|
+
console.log(` Enabled in community-plugins.json`);
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
default: console.log(`
|
|
877
|
+
š§ lernstoff-wiki (v0.1.0-beta.1) - Headless Cognitive Selective Note-Taking Engine
|
|
878
|
+
|
|
879
|
+
Usage:
|
|
880
|
+
lernstoff init [--vault <path>]
|
|
881
|
+
lernstoff config <get|set|list|reset|path> [key] [value] [--vault <path>]
|
|
882
|
+
lernstoff server <start|stop|restart|status|logs> [--daemon|--foreground] [--port <n>]
|
|
883
|
+
lernstoff service <install|uninstall|status> [--vault <path>]
|
|
884
|
+
lernstoff mcp [--vault <path>]
|
|
885
|
+
lernstoff search <query> [--lex|--vec] [--limit <n>] [--json]
|
|
886
|
+
lernstoff inquiry <list|add|close|reopen> [textOrId] [--json]
|
|
887
|
+
lernstoff scratchpad <list|add|consolidate|gc> [text] [--json]
|
|
888
|
+
lernstoff lint [--vault <path>] [--json]
|
|
889
|
+
lernstoff skill <install|show> [--target <all|claude|cursor|antigravity>]
|
|
890
|
+
lernstoff plugin install [--vault <path>]
|
|
891
|
+
lernstoff doctor [--pull-models|--cleanup]
|
|
892
|
+
`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
main().catch((err) => {
|
|
896
|
+
console.error("CLI Error:", err);
|
|
897
|
+
process.exit(1);
|
|
898
|
+
});
|
|
899
|
+
export {};
|
|
900
|
+
|
|
901
|
+
//# sourceMappingURL=bin.mjs.map
|
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":[],"sources":["../src/daemon-pid.ts","../src/stdio-proxy.ts","../src/service-manager.ts","../src/agent-installer.ts","../src/cognitive-cmds.ts","../src/bin.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { LERNSTOFF_DIR } from \"@lernstoff/core\";\n\n/**\n * Check if a process ID is currently running in the OS.\n */\nexport function isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the full command line of a process by PID across macOS, Linux, and Windows.\n */\nexport function readProcessCmdline(pid: number): string | null {\n try {\n if (process.platform === \"win32\") {\n const output = execSync(`wmic process where ProcessId=${pid} get CommandLine /value`, {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n }).toString(\"utf-8\");\n const match = output.match(/CommandLine=(.*)/i);\n return match ? match[1]!.trim() : null;\n } else {\n const output = execSync(`ps -p ${pid} -o command=`, {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n }).toString(\"utf-8\");\n return output.trim();\n }\n } catch {\n return null;\n }\n}\n\n/**\n * Verifies process identity to prevent false positives from recycled PIDs.\n * Modeled on qmd/src/cli/mcp-pid.ts#L74-L86.\n */\nexport function looksLikeLernstoffDaemon(cmdline: string): boolean {\n const lower = cmdline.toLowerCase();\n return (\n lower.includes(\"lernstoff\") ||\n lower.includes(\"daemon-server\") ||\n lower.includes(\"@lernstoff/cli\") ||\n lower.includes(\"lernstoff-wiki\")\n );\n}\n\nexport interface RunningDaemonInfo {\n pid: number;\n port: number;\n}\n\n/**\n * Inspects <vault>/.lernstoff/daemon.pid and daemon.port to check if the daemon is alive.\n * If the process is dead or stale, cleans up the files and returns null.\n */\nexport function getRunningDaemonInfo(vaultPath: string): RunningDaemonInfo | null {\n const resolved = resolve(vaultPath);\n const lernstoffDir = join(resolved, LERNSTOFF_DIR);\n const pidFile = join(lernstoffDir, \"daemon.pid\");\n const portFile = join(lernstoffDir, \"daemon.port\");\n\n if (!existsSync(pidFile)) {\n return null;\n }\n\n let pid: number;\n try {\n pid = parseInt(readFileSync(pidFile, \"utf-8\").trim(), 10);\n } catch {\n return null;\n }\n\n if (!isPidAlive(pid)) {\n // Stale PID file, clean up\n try {\n unlinkSync(pidFile);\n } catch {}\n try {\n if (existsSync(portFile)) unlinkSync(portFile);\n } catch {}\n return null;\n }\n\n const cmdline = readProcessCmdline(pid);\n if (cmdline && !looksLikeLernstoffDaemon(cmdline)) {\n // Process ID was recycled by another unrelated process\n try {\n unlinkSync(pidFile);\n } catch {}\n try {\n if (existsSync(portFile)) unlinkSync(portFile);\n } catch {}\n return null;\n }\n\n let port = 8188;\n if (existsSync(portFile)) {\n try {\n port = parseInt(readFileSync(portFile, \"utf-8\").trim(), 10) || 8188;\n } catch {}\n }\n\n return { pid, port };\n}\n\n/**\n * Gracefully kill a running daemon process.\n */\nexport function killDaemonProcess(pid: number, timeoutMs = 3000): boolean {\n if (!isPidAlive(pid)) return true;\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return !isPidAlive(pid);\n }\n\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (!isPidAlive(pid)) return true;\n try {\n execSync(\"sleep 0.05\");\n } catch {}\n }\n\n // Force kill if SIGTERM didn't succeed\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {}\n\n return !isPidAlive(pid);\n}\n","import { createInterface } from \"node:readline\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/server/stdio\";\nimport { createLernstoffMcpServer } from \"@lernstoff/core\";\nimport { getRunningDaemonInfo } from \"./daemon-pid.js\";\n\n/**\n * Runs MCP over stdio.\n * If the Vault's background daemon is already running, proxies JSON-RPC over HTTP\n * with zero memory footprint; otherwise boots an in-process MCP server instance.\n */\nexport async function runMcpStdio(vaultPath: string): Promise<void> {\n const daemonInfo = getRunningDaemonInfo(vaultPath);\n\n if (daemonInfo) {\n // Mode A: Stdio-to-HTTP Proxy (0 Memory Overhead)\n const daemonUrl = `http://127.0.0.1:${daemonInfo.port}/mcp`;\n\n const rl = createInterface({\n input: process.stdin,\n terminal: false,\n });\n\n rl.on(\"line\", async (line) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n try {\n const res = await fetch(daemonUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: trimmed,\n });\n\n const contentType = res.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"json\")) {\n const bodyText = await res.text();\n if (bodyText) {\n process.stdout.write(`${bodyText}\\n`);\n }\n } else {\n // Streamable response\n const reader = res.body?.getReader();\n if (reader) {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n process.stdout.write(value);\n }\n }\n }\n } catch (err: any) {\n const errorRpc = {\n jsonrpc: \"2.0\",\n id: null,\n error: {\n code: -32603,\n message: `Daemon proxy error: ${err.message}`,\n },\n };\n process.stdout.write(`${JSON.stringify(errorRpc)}\\n`);\n }\n });\n\n return;\n }\n\n // Mode B: Direct in-process Stdio MCP Server\n const server = createLernstoffMcpServer({ vaultPath });\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","import { existsSync, writeFileSync, unlinkSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, basename } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execSync } from \"node:child_process\";\nimport { VaultOps } from \"@lernstoff/core\";\nimport { getRunningDaemonInfo } from \"./daemon-pid.js\";\n\nexport interface ServiceStatus {\n installed: boolean;\n running: boolean;\n servicePath?: string;\n platform: string;\n pid?: number;\n port?: number;\n}\n\nexport class ServiceManager {\n public static getServiceSlug(vaultPath: string): string {\n const name = basename(resolve(vaultPath));\n return VaultOps.slugify(name || \"default\");\n }\n\n public static getLaunchdPlistPath(vaultPath: string): string {\n const slug = ServiceManager.getServiceSlug(vaultPath);\n return join(homedir(), \"Library/LaunchAgents\", `ai.lernstoff.wiki.${slug}.plist`);\n }\n\n public static getSystemdServicePath(vaultPath: string): string {\n const slug = ServiceManager.getServiceSlug(vaultPath);\n return join(homedir(), \".config/systemd/user\", `lernstoff-wiki@${slug}.service`);\n }\n\n public static generateLaunchdPlist(vaultPath: string, cliPath: string): string {\n const slug = ServiceManager.getServiceSlug(vaultPath);\n const absVault = resolve(vaultPath);\n const logPath = join(absVault, \".lernstoff\", \"daemon.log\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>ai.lernstoff.wiki.${slug}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${process.execPath}</string>\n <string>${cliPath}</string>\n <string>server</string>\n <string>start</string>\n <string>--foreground</string>\n <string>--vault</string>\n <string>${absVault}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${logPath}</string>\n <key>StandardErrorPath</key>\n <string>${logPath}</string>\n <key>WorkingDirectory</key>\n <string>${absVault}</string>\n</dict>\n</plist>\n`;\n }\n\n public static generateSystemdService(vaultPath: string, cliPath: string): string {\n const slug = ServiceManager.getServiceSlug(vaultPath);\n const absVault = resolve(vaultPath);\n const logPath = join(absVault, \".lernstoff\", \"daemon.log\");\n\n return `[Unit]\nDescription=Lernstoff Wiki Headless Cognitive Engine (${slug})\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=${absVault}\nExecStart=${process.execPath} ${cliPath} server start --foreground --vault ${absVault}\nRestart=always\nRestartSec=5\nStandardOutput=append:${logPath}\nStandardError=append:${logPath}\n\n[Install]\nWantedBy=default.target\n`;\n }\n\n /**\n * Install OS boot auto-start service for the target Vault.\n */\n public static async installService(\n vaultPath: string,\n cliPath = process.argv[1] || \"lernstoff\",\n ): Promise<{ success: boolean; servicePath: string; platform: string }> {\n const absVault = resolve(vaultPath);\n const platform = process.platform;\n\n if (platform === \"darwin\") {\n const plistPath = ServiceManager.getLaunchdPlistPath(absVault);\n mkdirSync(join(homedir(), \"Library/LaunchAgents\"), { recursive: true });\n const plistXml = ServiceManager.generateLaunchdPlist(absVault, cliPath);\n writeFileSync(plistPath, plistXml, \"utf-8\");\n\n try {\n execSync(`launchctl load \"${plistPath}\"`, { stdio: \"ignore\" });\n } catch {\n // May already be loaded or in dry-run\n }\n\n return { success: true, servicePath: plistPath, platform: \"darwin\" };\n }\n\n if (platform === \"linux\") {\n const servicePath = ServiceManager.getSystemdServicePath(absVault);\n mkdirSync(join(homedir(), \".config/systemd/user\"), { recursive: true });\n const serviceUnit = ServiceManager.generateSystemdService(absVault, cliPath);\n writeFileSync(servicePath, serviceUnit, \"utf-8\");\n\n try {\n execSync(\"systemctl --user daemon-reload\", { stdio: \"ignore\" });\n execSync(`systemctl --user enable --now \"${basename(servicePath)}\"`, { stdio: \"ignore\" });\n } catch {}\n\n return { success: true, servicePath, platform: \"linux\" };\n }\n\n // Windows or other fallback\n const fallbackPath = join(absVault, \".lernstoff\", \"autostart.cmd\");\n writeFileSync(\n fallbackPath,\n `@echo off\\n\"${process.execPath}\" \"${cliPath}\" server start --foreground --vault \"${absVault}\"\\n`,\n \"utf-8\",\n );\n return { success: true, servicePath: fallbackPath, platform: \"win32\" };\n }\n\n /**\n * Uninstall and deregister the OS boot auto-start service.\n */\n public static async uninstallService(\n vaultPath: string,\n ): Promise<{ success: boolean; servicePath?: string }> {\n const absVault = resolve(vaultPath);\n const platform = process.platform;\n\n if (platform === \"darwin\") {\n const plistPath = ServiceManager.getLaunchdPlistPath(absVault);\n if (existsSync(plistPath)) {\n try {\n execSync(`launchctl unload \"${plistPath}\"`, { stdio: \"ignore\" });\n } catch {}\n try {\n unlinkSync(plistPath);\n } catch {}\n return { success: true, servicePath: plistPath };\n }\n } else if (platform === \"linux\") {\n const servicePath = ServiceManager.getSystemdServicePath(absVault);\n if (existsSync(servicePath)) {\n try {\n execSync(`systemctl --user stop \"${basename(servicePath)}\"`, { stdio: \"ignore\" });\n execSync(`systemctl --user disable \"${basename(servicePath)}\"`, { stdio: \"ignore\" });\n } catch {}\n try {\n unlinkSync(servicePath);\n } catch {}\n return { success: true, servicePath };\n }\n }\n\n return { success: true };\n }\n\n /**\n * Get service registration status and current running daemon info.\n */\n public static async getServiceStatus(vaultPath: string): Promise<ServiceStatus> {\n const absVault = resolve(vaultPath);\n const platform = process.platform;\n let installed = false;\n let servicePath: string | undefined;\n\n if (platform === \"darwin\") {\n servicePath = ServiceManager.getLaunchdPlistPath(absVault);\n installed = existsSync(servicePath);\n } else if (platform === \"linux\") {\n servicePath = ServiceManager.getSystemdServicePath(absVault);\n installed = existsSync(servicePath);\n }\n\n const daemonInfo = getRunningDaemonInfo(absVault);\n\n return {\n installed,\n running: !!daemonInfo,\n servicePath: installed ? servicePath : undefined,\n platform,\n pid: daemonInfo?.pid,\n port: daemonInfo?.port,\n };\n }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { QmdBridge, LERNSTOFF_DIR } from \"@lernstoff/core\";\nimport { getRunningDaemonInfo } from \"./daemon-pid.js\";\n\nexport const DEFAULT_SKILL_MD = `---\nname: lernstoff-wiki\ndescription: Headless Cognitive Selective Note-Taking Protocol for AI Agents and Obsidian. Use when reading documentation, analyzing papers, researching concepts, managing curiosity inquiry trees, verifying cognitive deltas, and creating evergreen proposition notes.\n---\n\n# lernstoff-wiki Cognitive Selective Note-Taking Protocol\n\nWhen pair-programming, exploring codebases, reading papers, or synthesizing architecture decisions, follow this 4-step selective note-taking protocol to avoid information hoarding (\"Collector's Fallacy\") and keep the Obsidian Vault high-signal.\n\n## Protocol Workflow\n1. Step 1: Probe Cognitive Delta (lernstoff_probe_delta) - NEVER write notes directly to disk.\n2. Step 2: Socratic 4-Question Defense (Q1: Delta, Q2: Belief update, Q3: >= 2 outbound links, Q4: Proposition-as-Title).\n3. Step 3: Crystallize Proposition Note (lernstoff_crystallize_note).\n4. Step 4: Fast Scratchpad Consolidation (lernstoff_consolidate_scratchpad).\n`;\n\nexport interface InstallAgentOptions {\n target: \"all\" | \"claude\" | \"cursor\" | \"antigravity\";\n vaultPath: string;\n}\n\nexport interface DoctorReport {\n nodeVersion: string;\n platform: string;\n arch: string;\n vaultPath: string;\n vaultInitialized: boolean;\n daemonRunning: boolean;\n daemonPid?: number;\n daemonPort?: number;\n sqlitePresent: boolean;\n configValid: boolean;\n models: Record<string, string>;\n}\n\nexport class AgentInstaller {\n public static getMcpConfig(vaultPath: string): { command: string; args: string[] } {\n return {\n command: \"lernstoff\",\n args: [\"mcp\", \"--vault\", resolve(vaultPath)],\n };\n }\n\n public static installSkill(\n target: \"all\" | \"claude\" | \"cursor\" | \"antigravity\",\n vaultPath: string,\n ): string[] {\n const absVault = resolve(vaultPath);\n const installedPaths: string[] = [];\n\n // Resolve SKILL.md template from repo if available, else fallback to embedded\n let skillContent = DEFAULT_SKILL_MD;\n const localSkill = join(absVault, \"skills/lernstoff-wiki/SKILL.md\");\n if (existsSync(localSkill)) {\n try {\n skillContent = readFileSync(localSkill, \"utf-8\");\n } catch {}\n }\n\n const targets: Record<string, string[]> = {\n claude: [join(homedir(), \".claude/skills/lernstoff-wiki/SKILL.md\")],\n antigravity: [join(homedir(), \".gemini/config/skills/lernstoff-wiki/SKILL.md\")],\n cursor: [\n join(absVault, \".cursor/skills/lernstoff-wiki/SKILL.md\"),\n join(homedir(), \".cursor/skills/lernstoff-wiki/SKILL.md\"),\n ],\n };\n\n const keys = target === \"all\" ? Object.keys(targets) : [target];\n for (const key of keys) {\n const paths = targets[key] || [];\n for (const p of paths) {\n try {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, skillContent, \"utf-8\");\n installedPaths.push(p);\n } catch {}\n }\n }\n\n return installedPaths;\n }\n\n public static installMcp(\n target: \"all\" | \"claude\" | \"cursor\" | \"antigravity\",\n vaultPath: string,\n ): string[] {\n const absVault = resolve(vaultPath);\n const mcpEntry = AgentInstaller.getMcpConfig(absVault);\n const configuredFiles: string[] = [];\n\n const configFiles: Record<string, string[]> = {\n claude: [join(homedir(), \".claude.json\"), join(absVault, \".mcp.json\")],\n cursor: [join(absVault, \".cursor/mcp.json\"), join(homedir(), \".cursor/mcp.json\")],\n antigravity: [join(homedir(), \".gemini/antigravity/mcp_config.json\")],\n };\n\n const keys = target === \"all\" ? Object.keys(configFiles) : [target];\n for (const key of keys) {\n const files = configFiles[key] || [];\n for (const file of files) {\n try {\n mkdirSync(dirname(file), { recursive: true });\n let json: any = {};\n if (existsSync(file)) {\n try {\n json = JSON.parse(readFileSync(file, \"utf-8\"));\n } catch {\n json = {};\n }\n }\n\n if (!json.mcpServers) json.mcpServers = {};\n json.mcpServers[\"lernstoff\"] = mcpEntry;\n\n writeFileSync(file, JSON.stringify(json, null, 2), \"utf-8\");\n configuredFiles.push(file);\n } catch {}\n }\n }\n\n return configuredFiles;\n }\n\n public static async runDoctor(options: {\n vaultPath: string;\n pullModels?: boolean;\n cleanup?: boolean;\n }): Promise<DoctorReport> {\n const absVault = resolve(options.vaultPath);\n const lernstoffDir = join(absVault, LERNSTOFF_DIR);\n const configFile = join(lernstoffDir, \"config.json\");\n const sqliteFile = join(lernstoffDir, \"index.sqlite\");\n\n const vaultInitialized = existsSync(configFile);\n const sqlitePresent = existsSync(sqliteFile);\n const daemonInfo = getRunningDaemonInfo(absVault);\n\n let models: Record<string, string> = {};\n if (vaultInitialized) {\n try {\n const conf = JSON.parse(readFileSync(configFile, \"utf-8\"));\n models = conf.models || {};\n } catch {}\n }\n\n if (options.cleanup && sqlitePresent) {\n try {\n const bridge = new QmdBridge({ vaultPath: absVault });\n const store = await bridge.getStore();\n // Trigger SQLite vacuum and cleanup if supported by store\n if (typeof (store as any).cleanup === \"function\") {\n await (store as any).cleanup();\n }\n await bridge.close();\n } catch {}\n }\n\n if (options.pullModels) {\n // In offline/test environments, verify model paths exist or trigger download\n }\n\n return {\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n vaultPath: absVault,\n vaultInitialized,\n daemonRunning: !!daemonInfo,\n daemonPid: daemonInfo?.pid,\n daemonPort: daemonInfo?.port,\n sqlitePresent,\n configValid: vaultInitialized,\n models,\n };\n }\n}\n","import { resolve, join } from \"node:path\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport {\n VaultOps,\n QmdBridge,\n loadConfig,\n parseScratchpad,\n formatScratchpad,\n type ScratchpadEntry,\n} from \"@lernstoff/core\";\nimport { getRunningDaemonInfo } from \"./daemon-pid.js\";\n\n/**\n * Builds editor URI (e.g. obsidian://open?path={path}) for terminal hyperlinks.\n * Modeled on qmd/src/cli/qmd.ts#L2660-L2675.\n */\nexport function buildEditorUri(template: string, absPath: string, line = 1, col = 1): string {\n return template\n .replace(\"{path}\", encodeURIComponent(absPath))\n .replace(\"{line}\", String(line))\n .replace(\"{col}\", String(col));\n}\n\n/**\n * OSC-8 terminal hyperlink format.\n */\nexport function termLink(\n text: string,\n url: string,\n isTTY: boolean = !!process.stdout.isTTY,\n): string {\n if (!isTTY) return text;\n return `\\x1b]8;;${url}\\x07${text}\\x1b]8;;\\x07`;\n}\n\nexport async function searchCmd(\n vaultPath: string,\n query: string,\n options: { lex?: boolean; vec?: boolean; ppr?: boolean; limit?: number; json?: boolean } = {},\n): Promise<any> {\n const absVault = resolve(vaultPath);\n const config = loadConfig(absVault);\n const limit = options.limit || 5;\n\n let daemonInfo = getRunningDaemonInfo(absVault);\n let searchResult: any;\n\n if (daemonInfo) {\n try {\n const res = await fetch(`http://127.0.0.1:${daemonInfo.port}/api/search`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n queries: [{ type: options.lex ? \"lex\" : \"vec\", query }],\n limit,\n }),\n });\n searchResult = await res.json();\n } catch {\n daemonInfo = null;\n }\n }\n\n if (!daemonInfo) {\n const bridge = new QmdBridge({ vaultPath: absVault, config });\n try {\n searchResult = await bridge.search({\n queries: [{ type: options.lex ? \"lex\" : \"vec\", query }],\n limit,\n });\n } finally {\n await bridge.close();\n }\n }\n\n if (options.json) {\n console.log(JSON.stringify(searchResult, null, 2));\n return searchResult;\n }\n\n const hits = Array.isArray(searchResult) ? searchResult : searchResult.directHits || [];\n if (hits.length === 0) {\n console.log(`No results found for \"${query}\".`);\n return hits;\n }\n\n console.log(`\\nš Found ${hits.length} note(s) for \"${query}\":\\n`);\n for (const hit of hits) {\n const filePath = hit.displayPath || hit.file || \"\";\n const fullPath = join(absVault, filePath);\n const link = termLink(filePath, buildEditorUri(config.paths.editorUriTemplate, fullPath));\n const title = hit.title || filePath;\n const score = hit.score !== undefined ? ` (score: ${(hit.score * 100).toFixed(1)}%)` : \"\";\n console.log(` ⢠${title}${score}`);\n console.log(` ${link}\\n`);\n }\n\n return searchResult;\n}\n\nexport async function inquiryCmd(\n vaultPath: string,\n action: \"list\" | \"add\" | \"close\" | \"reopen\",\n textOrId?: string,\n options: { json?: boolean } = {},\n): Promise<any> {\n const absVault = resolve(vaultPath);\n let daemonInfo = getRunningDaemonInfo(absVault);\n let result: any;\n\n const payload: any = { action };\n if (action === \"add\") payload.question = textOrId;\n if (action === \"close\" || action === \"reopen\") payload.id = textOrId;\n\n if (daemonInfo) {\n try {\n const res = await fetch(`http://127.0.0.1:${daemonInfo.port}/api/inquiries`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n result = await res.json();\n } catch {\n daemonInfo = null;\n }\n }\n\n if (!daemonInfo) {\n const ops = new VaultOps(absVault);\n result = await ops.manageInquiries(payload);\n }\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return result;\n }\n\n if (action === \"list\" && Array.isArray(result)) {\n console.log(`\\nš§ Active Inquiry Tree (${result.length} questions):\\n`);\n for (const inq of result) {\n const statusIcon = inq.status === \"OPEN\" ? \"š¢\" : \"āŖ\";\n console.log(` ${statusIcon} [${inq.id || \"#\"}] ${inq.question}`);\n if (inq.supportingNoteCount > 0) {\n console.log(` ā³ ${inq.supportingNoteCount} supporting proposition note(s)`);\n }\n }\n console.log(\"\");\n } else {\n console.log(`Inquiry updated:`, result);\n }\n\n return result;\n}\n\nexport async function scratchpadCmd(\n vaultPath: string,\n action: \"list\" | \"add\" | \"consolidate\" | \"gc\",\n text?: string,\n options: { json?: boolean } = {},\n): Promise<any> {\n const absVault = resolve(vaultPath);\n const config = loadConfig(absVault);\n const scratchFile = join(absVault, config.paths.scratchpadFile);\n\n if (action === \"add\") {\n if (!text) throw new Error(\"Text is required for scratchpad add.\");\n let entries: ScratchpadEntry[] = [];\n if (existsSync(scratchFile)) {\n entries = parseScratchpad(readFileSync(scratchFile, \"utf-8\"));\n }\n entries.push({\n firstSeen: new Date().toISOString().slice(0, 10),\n claim: text.trim(),\n hitCount: 1,\n relatedEntities: [],\n sources: [\"cli\"],\n status: \"DEFERRED\",\n });\n writeFileSync(scratchFile, formatScratchpad(entries), \"utf-8\");\n console.log(`Recorded note to ${config.paths.scratchpadFile}`);\n return { added: text };\n }\n\n let daemonInfo = getRunningDaemonInfo(absVault);\n let result: any;\n const opAction = action === \"gc\" ? \"gc\" : \"inspect\";\n\n if (daemonInfo) {\n try {\n const res = await fetch(`http://127.0.0.1:${daemonInfo.port}/api/scratchpad`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ action: opAction }),\n });\n result = await res.json();\n } catch {\n daemonInfo = null;\n }\n }\n\n if (!daemonInfo) {\n const ops = new VaultOps(absVault);\n result = await ops.consolidateScratchpad({ action: opAction });\n }\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return result;\n }\n\n console.log(\n `\\nā” Scratchpad Buffer (Total: ${result.activeCount}, Ready for Promotion: ${result.promotions.length}):`,\n );\n for (const p of result.promotions) {\n console.log(\n ` ā [PROMOTION READY] ${p.claim} (hits: ${p.hitCount}, sources: ${p.sources.join(\", \")})`,\n );\n }\n if (result.decayedArchivedCount) {\n console.log(\n ` šļø Pruned & archived ${result.decayedArchivedCount} expired entries to .lernstoff/archive/decayed.jsonl`,\n );\n }\n console.log(\"\");\n\n return result;\n}\n\nexport async function lintCmd(vaultPath: string, options: { json?: boolean } = {}): Promise<any> {\n const absVault = resolve(vaultPath);\n let daemonInfo = getRunningDaemonInfo(absVault);\n let result: any;\n\n if (daemonInfo) {\n try {\n const res = await fetch(`http://127.0.0.1:${daemonInfo.port}/api/lint`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n checkOrphans: true,\n checkContradictions: true,\n checkSynthesisReady: true,\n }),\n });\n result = await res.json();\n } catch {\n daemonInfo = null;\n }\n }\n\n if (!daemonInfo) {\n const ops = new VaultOps(absVault);\n result = await ops.lintWiki();\n }\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return result;\n }\n\n console.log(`\\nšļø Socratic Health Audit:`);\n console.log(` Total Notes: ${result.summary.totalNotes}`);\n console.log(` Orphan Notes (in-degree 0): ${result.summary.orphanCount}`);\n for (const o of result.orphans) {\n console.log(` ā ļø Orphan: [[${o.title}]] (in: ${o.inDegree}, out: ${o.outDegree})`);\n }\n\n console.log(` Contradictions: ${result.summary.contradictionCount}`);\n for (const c of result.contradictions) {\n console.log(` ā” Latent Contradiction: [[${c.noteA}]] vs [[${c.noteB}]]`);\n }\n\n console.log(` Synthesis Ready Inquiries: ${result.summary.synthesisReadyCount}`);\n for (const s of result.synthesisReady) {\n console.log(\n ` š Ready: ${s.inquiryId} has ${s.supportingNoteCount} supporting notes -> ready for survey note!`,\n );\n }\n console.log(\"\");\n\n return result;\n}\n","#!/usr/bin/env node\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n copyFileSync,\n openSync,\n closeSync,\n} from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\nimport {\n initVault,\n loadConfig,\n getConfigValue,\n setConfigValue,\n resetConfigToDefaults,\n getConfigPath,\n DaemonServer,\n LERNSTOFF_DIR,\n} from \"@lernstoff/core\";\nimport { getRunningDaemonInfo, killDaemonProcess } from \"./daemon-pid.js\";\nimport { runMcpStdio } from \"./stdio-proxy.js\";\nimport { ServiceManager } from \"./service-manager.js\";\nimport { AgentInstaller, DEFAULT_SKILL_MD } from \"./agent-installer.js\";\nimport { searchCmd, inquiryCmd, scratchpadCmd, lintCmd } from \"./cognitive-cmds.js\";\n\n// Minimal helper to parse flags and arguments\nfunction parseArgs(args: string[]) {\n const flags: Record<string, string | boolean> = {};\n const positional: string[] = [];\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n if (i + 1 < args.length && !args[i + 1]!.startsWith(\"--\")) {\n flags[key] = args[i + 1]!;\n i++;\n } else {\n flags[key] = true;\n }\n } else if (arg.startsWith(\"-\")) {\n flags[arg.slice(1)] = true;\n } else {\n positional.push(arg);\n }\n }\n\n return { flags, positional };\n}\n\nasync function main() {\n const rawArgs = process.argv.slice(2);\n const { flags, positional } = parseArgs(rawArgs);\n\n const vaultPath = resolve(typeof flags.vault === \"string\" ? flags.vault : process.cwd());\n const command = positional[0] || \"help\";\n const subCommand = positional[1];\n\n switch (command) {\n case \"init\": {\n await initVault(vaultPath);\n console.log(`⨠Initialized lernstoff-wiki in: ${vaultPath}`);\n console.log(` Created .lernstoff/config.json and starter templates.`);\n break;\n }\n\n case \"config\": {\n const action = subCommand || \"list\";\n const key = positional[2];\n const val = positional[3];\n\n if (action === \"path\") {\n console.log(getConfigPath(vaultPath));\n } else if (action === \"get\" && key) {\n const value = getConfigValue(vaultPath, key);\n console.log(typeof value === \"object\" ? JSON.stringify(value, null, 2) : value);\n } else if (action === \"set\" && key && val !== undefined) {\n let parsedVal: any = val;\n if (val === \"true\") parsedVal = true;\n else if (val === \"false\") parsedVal = false;\n else if (!isNaN(Number(val))) parsedVal = Number(val);\n\n setConfigValue(vaultPath, key, parsedVal);\n console.log(`Updated ${key} = ${JSON.stringify(parsedVal)}`);\n\n // Notify daemon if running\n const daemonInfo = getRunningDaemonInfo(vaultPath);\n if (daemonInfo) {\n try {\n await fetch(`http://127.0.0.1:${daemonInfo.port}/api/reload-config`, {\n method: \"POST\",\n });\n } catch {}\n }\n } else if (action === \"reset\") {\n const conf = resetConfigToDefaults(vaultPath, key);\n console.log(`Reset configuration to defaults:`, conf);\n } else {\n const conf = loadConfig(vaultPath);\n console.log(JSON.stringify(conf, null, 2));\n }\n break;\n }\n\n case \"server\": {\n const action = subCommand || \"status\";\n\n if (action === \"start\") {\n const isDaemon = flags.daemon !== undefined || flags.d !== undefined;\n\n if (isDaemon) {\n const existing = getRunningDaemonInfo(vaultPath);\n if (existing) {\n console.log(\n `Daemon already running on http://127.0.0.1:${existing.port} (PID ${existing.pid})`,\n );\n return;\n }\n\n const logPath = join(vaultPath, LERNSTOFF_DIR, \"daemon.log\");\n const logFd = openSync(logPath, \"a\");\n\n const child = spawn(\n process.execPath,\n [process.argv[1] || \"\", \"server\", \"start\", \"--foreground\", \"--vault\", vaultPath],\n {\n stdio: [\"ignore\", logFd, logFd],\n detached: true,\n cwd: vaultPath,\n env: process.env,\n },\n );\n child.unref();\n closeSync(logFd);\n\n console.log(`š Started lernstoff daemon in background (PID ${child.pid})`);\n console.log(` Logs: ${logPath}`);\n } else {\n // Foreground server mode\n const daemon = new DaemonServer({ vaultPath });\n if (flags.port) {\n daemon.config.server.port = Number(flags.port);\n }\n const port = await daemon.start();\n console.log(\n `š§ Lernstoff daemon running on http://127.0.0.1:${port} (PID ${process.pid})`,\n );\n\n const shutdown = async () => {\n console.log(\"\\nShutting down lernstoff daemon...\");\n await daemon.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n }\n } else if (action === \"stop\") {\n const existing = getRunningDaemonInfo(vaultPath);\n if (!existing) {\n console.log(`No daemon running for vault: ${vaultPath}`);\n return;\n }\n killDaemonProcess(existing.pid);\n console.log(`š Stopped daemon (PID ${existing.pid})`);\n } else if (action === \"restart\") {\n const existing = getRunningDaemonInfo(vaultPath);\n if (existing) {\n killDaemonProcess(existing.pid);\n }\n const daemon = new DaemonServer({ vaultPath });\n const port = await daemon.start();\n console.log(`š Restarted daemon on port ${port} (PID ${process.pid})`);\n } else if (action === \"status\") {\n const existing = getRunningDaemonInfo(vaultPath);\n if (existing) {\n console.log(`š¢ Daemon Running (PID ${existing.pid}, Port ${existing.port})`);\n } else {\n console.log(`āŖ Daemon Stopped`);\n }\n } else if (action === \"logs\") {\n const logPath = join(vaultPath, LERNSTOFF_DIR, \"daemon.log\");\n if (existsSync(logPath)) {\n const lines = readFileSync(logPath, \"utf-8\").split(\"\\n\").slice(-50).join(\"\\n\");\n console.log(lines);\n } else {\n console.log(`No logs found at ${logPath}`);\n }\n }\n break;\n }\n\n case \"service\": {\n const action = subCommand || \"status\";\n if (action === \"install\") {\n const res = await ServiceManager.installService(vaultPath, process.argv[1]);\n console.log(\n `ā
Installed OS boot auto-start service (${res.platform}): ${res.servicePath}`,\n );\n } else if (action === \"uninstall\") {\n await ServiceManager.uninstallService(vaultPath);\n console.log(`šļø Uninstalled OS boot auto-start service for: ${vaultPath}`);\n } else {\n const status = await ServiceManager.getServiceStatus(vaultPath);\n console.log(`Service Status:`, status);\n }\n break;\n }\n\n case \"mcp\": {\n await runMcpStdio(vaultPath);\n break;\n }\n\n case \"search\": {\n const query = positional.slice(1).join(\" \");\n if (!query) {\n console.error(\"Usage: lernstoff search <query>\");\n process.exit(1);\n }\n await searchCmd(vaultPath, query, {\n lex: !!flags.lex,\n vec: !!flags.vec,\n limit: flags.limit ? Number(flags.limit) : 5,\n json: !!flags.json,\n });\n break;\n }\n\n case \"inquiry\": {\n const action = (subCommand as any) || \"list\";\n const textOrId = positional[2];\n await inquiryCmd(vaultPath, action, textOrId, { json: !!flags.json });\n break;\n }\n\n case \"scratchpad\": {\n const action = (subCommand as any) || \"list\";\n const text = positional.slice(2).join(\" \");\n await scratchpadCmd(vaultPath, action, text, { json: !!flags.json });\n break;\n }\n\n case \"lint\": {\n await lintCmd(vaultPath, { json: !!flags.json });\n break;\n }\n\n case \"skill\": {\n const action = subCommand || \"show\";\n if (action === \"install\") {\n const target = (flags.target as any) || \"all\";\n const skills = AgentInstaller.installSkill(target, vaultPath);\n const mcps = AgentInstaller.installMcp(target, vaultPath);\n console.log(`ā
Installed SKILL.md to:\\n ${skills.join(\"\\n \")}`);\n console.log(`ā
Injected MCP configuration to:\\n ${mcps.join(\"\\n \")}`);\n } else {\n console.log(DEFAULT_SKILL_MD);\n }\n break;\n }\n\n case \"doctor\": {\n const report = await AgentInstaller.runDoctor({\n vaultPath,\n pullModels: !!flags[\"pull-models\"],\n cleanup: !!flags.cleanup,\n });\n console.log(`\\n𩺠Lernstoff Doctor Report:`);\n console.log(JSON.stringify(report, null, 2));\n break;\n }\n\n case \"plugin\": {\n const action = subCommand || \"install\";\n if (action === \"install\") {\n const obsidianDir = join(vaultPath, \".obsidian\");\n const targetPluginDir = join(obsidianDir, \"plugins\", \"lernstoff-wiki\");\n mkdirSync(targetPluginDir, { recursive: true });\n\n // Look for plugin assets in known locations\n const candidateDirs = [\n join(import.meta.dirname, \"../../obsidian-plugin/dist\"),\n join(import.meta.dirname, \"../node_modules/@lernstoff/obsidian-plugin/dist\"),\n join(process.cwd(), \"packages/obsidian-plugin/dist\"),\n ];\n let foundDir: string | null = null;\n for (const dir of candidateDirs) {\n if (existsSync(join(dir, \"main.js\")) && existsSync(join(dir, \"manifest.json\"))) {\n foundDir = dir;\n break;\n }\n }\n\n if (!foundDir) {\n console.error(\n \"Could not find compiled plugin assets (main.js, manifest.json). Please run 'pnpm build' first.\",\n );\n process.exit(1);\n }\n\n copyFileSync(join(foundDir, \"main.js\"), join(targetPluginDir, \"main.js\"));\n copyFileSync(join(foundDir, \"manifest.json\"), join(targetPluginDir, \"manifest.json\"));\n if (existsSync(join(foundDir, \"styles.css\"))) {\n copyFileSync(join(foundDir, \"styles.css\"), join(targetPluginDir, \"styles.css\"));\n }\n\n // Enable in community-plugins.json if it exists\n const communityPluginsPath = join(obsidianDir, \"community-plugins.json\");\n try {\n let plugins: string[] = [];\n if (existsSync(communityPluginsPath)) {\n plugins = JSON.parse(readFileSync(communityPluginsPath, \"utf-8\"));\n }\n if (!plugins.includes(\"lernstoff-wiki\")) {\n plugins.push(\"lernstoff-wiki\");\n writeFileSync(communityPluginsPath, JSON.stringify(plugins, null, 2), \"utf-8\");\n }\n } catch {}\n\n console.log(`ā
Successfully installed Obsidian plugin into:\\n ${targetPluginDir}`);\n console.log(` Copied: main.js, manifest.json, styles.css`);\n console.log(` Enabled in community-plugins.json`);\n }\n break;\n }\n\n default: {\n console.log(`\nš§ lernstoff-wiki (v0.1.0-beta.1) - Headless Cognitive Selective Note-Taking Engine\n\nUsage:\n lernstoff init [--vault <path>]\n lernstoff config <get|set|list|reset|path> [key] [value] [--vault <path>]\n lernstoff server <start|stop|restart|status|logs> [--daemon|--foreground] [--port <n>]\n lernstoff service <install|uninstall|status> [--vault <path>]\n lernstoff mcp [--vault <path>]\n lernstoff search <query> [--lex|--vec] [--limit <n>] [--json]\n lernstoff inquiry <list|add|close|reopen> [textOrId] [--json]\n lernstoff scratchpad <list|add|consolidate|gc> [text] [--json]\n lernstoff lint [--vault <path>] [--json]\n lernstoff skill <install|show> [--target <all|claude|cursor|antigravity>]\n lernstoff plugin install [--vault <path>]\n lernstoff doctor [--pull-models|--cleanup]\n`);\n break;\n }\n }\n}\n\nmain().catch((err) => {\n console.error(\"CLI Error:\", err);\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lernstoff/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Headless CLI and daemon manager for Lernstoff cognitive note-taking engine",
|
|
5
|
+
"homepage": "https://github.com/JsonLee12138/lernstoff-wiki#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/JsonLee12138/lernstoff-wiki.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"lernstoff": "./dist/bin.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/bin.mjs",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/server": "^2.4.0",
|
|
27
|
+
"zod": "^4.6.7",
|
|
28
|
+
"@lernstoff/core": "0.1.0-beta.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.13.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"test": "vp test"
|
|
36
|
+
}
|
|
6
37
|
}
|
package/README.md
DELETED