🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

seekstone

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

seekstone - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+232
-8
dist/index.js

@@ -11,2 +11,42 @@ #!/usr/bin/env node

// src/cli-args.ts
function parseCliIntent(argv) {
if (argv[0] === "init") return "init";
for (const arg of argv) {
if (arg === "--version" || arg === "-v") return "version";
if (arg === "--help" || arg === "-h") return "help";
}
return "run";
}
function helpText(version) {
return `seekstone ${version} \u2014 an Obsidian MCP server (filesystem-direct, low context-tax)
Seekstone runs as a Model Context Protocol stdio server. It is normally
launched by an MCP client (Claude Desktop, Claude Code), not run by hand.
Usage:
seekstone Start the MCP server (reads from stdin/stdout)
seekstone init Validate a vault and print/patch the Claude config
seekstone --version Print the version and exit
seekstone --help Print this help and exit
"seekstone init" options:
--vault <path> Vault to use (default: $SEEKSTONE_VAULT)
--client <name> desktop (default) | code
--write Patch the Claude Desktop config in place (backs it up first)
Required environment:
SEEKSTONE_VAULT Absolute path to your Obsidian vault
Optional environment:
SEEKSTONE_LOG_LEVEL error | warn | info (default) | debug
SEEKSTONE_LOG_FILE Absolute path; append JSON-line logs here
SEEKSTONE_WATCH_POLL Set to 1 to poll for changes (network drives, WSL)
Add to Claude Code:
claude mcp add seekstone --env SEEKSTONE_VAULT=/path/to/vault -- npx -y seekstone
Docs: https://github.com/shaqmughal/seekstone`;
}
// src/tools/append_note.ts

@@ -229,4 +269,4 @@ import { readFile, writeFile } from "fs/promises";

const results = [];
for (const [path, note] of ctx2.notes) {
if (input.folder && !path.startsWith(input.folder)) continue;
for (const [path2, note] of ctx2.notes) {
if (input.folder && !path2.startsWith(input.folder)) continue;
if (tag) {

@@ -237,3 +277,3 @@ const noteTags = note.tags.split(" ").filter(Boolean);

results.push({
path,
path: path2,
title: note.title,

@@ -642,2 +682,164 @@ tags: note.tags.split(" ").filter(Boolean),

// src/init.ts
import { access as access3, copyFile, mkdir as mkdir3, readFile as readFile5, readdir, stat as stat2, writeFile as writeFile4 } from "fs/promises";
import path, { dirname as dirname3, join as join8 } from "path";
function parseInitArgs(argv) {
const opts = { write: false, client: "desktop" };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--write") opts.write = true;
else if (a === "--vault") opts.vault = argv[++i];
else if (a === "--client") {
const v = argv[++i];
if (v === "desktop" || v === "code") opts.client = v;
}
}
return opts;
}
function seekstoneServerConfig(vaultPath) {
return {
command: "npx",
args: ["-y", "seekstone"],
env: { SEEKSTONE_VAULT: vaultPath }
};
}
function mcpAddCommand(vaultPath) {
return `claude mcp add seekstone --env SEEKSTONE_VAULT=${vaultPath} -- npx -y seekstone`;
}
function claudeDesktopConfigPath(platform, env) {
if (platform === "win32") {
const j2 = path.win32.join;
const base = env.appData ?? j2(env.home ?? "", "AppData", "Roaming");
return j2(base, "Claude", "claude_desktop_config.json");
}
const j = path.posix.join;
const home = env.home ?? "";
if (platform === "darwin") {
return j(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
}
return j(home, ".config", "Claude", "claude_desktop_config.json");
}
function mergeSeekstoneConfig(existing, vaultPath) {
const base = existing ? structuredClone(existing) : {};
const servers = { ...base.mcpServers ?? {} };
servers.seekstone = seekstoneServerConfig(vaultPath);
return { ...base, mcpServers: servers };
}
async function validateVault(vaultPath) {
try {
const st = await stat2(vaultPath);
if (!st.isDirectory()) return { ok: false, error: `Not a directory: ${vaultPath}` };
} catch {
return { ok: false, error: `Path does not exist: ${vaultPath}` };
}
try {
await access3(join8(vaultPath, ".obsidian"));
} catch {
return {
ok: false,
error: `No ".obsidian" folder found in ${vaultPath} \u2014 this doesn't look like an Obsidian vault. Open the folder in Obsidian once to initialize it, or pass the correct --vault path.`
};
}
return { ok: true, noteCount: await countMarkdown(vaultPath) };
}
async function countMarkdown(root) {
let count = 0;
async function walk(dir) {
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return;
}
for (const e of entries) {
if (e.name.startsWith(".")) continue;
const full = join8(dir, e.name);
if (e.isDirectory()) await walk(full);
else if (e.name.endsWith(".md")) count++;
}
}
await walk(root);
return count;
}
async function runInit(opts, deps) {
const vaultPath = opts.vault ?? deps.env.SEEKSTONE_VAULT;
if (!vaultPath) {
return {
ok: false,
exitCode: 1,
output: [
"No vault specified. Pass --vault <path> or set SEEKSTONE_VAULT.",
'Example: seekstone init --vault "/Users/you/Obsidian/My Vault"'
]
};
}
const check = await validateVault(vaultPath);
if (!check.ok) {
return { ok: false, exitCode: 1, output: [`\u2717 ${check.error}`] };
}
const out = [
`\u2713 Vault looks good: ${vaultPath}`,
` ${check.noteCount} markdown note${check.noteCount === 1 ? "" : "s"} found.`,
""
];
if (opts.client === "code") {
out.push("Add to Claude Code by running:", "", ` ${mcpAddCommand(vaultPath)}`, "");
return { ok: true, exitCode: 0, output: out };
}
const configPath = claudeDesktopConfigPath(deps.platform, {
home: deps.env.HOME,
appData: deps.env.APPDATA
});
const block = JSON.stringify(
{ mcpServers: { seekstone: seekstoneServerConfig(vaultPath) } },
null,
2
);
if (!opts.write) {
out.push(
"Add this to your Claude Desktop config:",
` ${configPath}`,
"",
block,
"",
"Then restart Claude Desktop. Or re-run with --write to patch it automatically."
);
return { ok: true, exitCode: 0, output: out };
}
let existing = null;
let existingRaw = null;
try {
existingRaw = await readFile5(configPath, "utf8");
existing = JSON.parse(existingRaw);
} catch (err) {
if (existingRaw !== null) {
return {
ok: false,
exitCode: 1,
output: [
`\u2717 ${configPath} exists but is not valid JSON; not modifying it.`,
` ${err instanceof Error ? err.message : String(err)}`
]
};
}
existing = null;
}
const merged = mergeSeekstoneConfig(existing, vaultPath);
let backupPath;
await mkdir3(dirname3(configPath), { recursive: true });
if (existingRaw !== null) {
backupPath = `${configPath}.backup-${deps.timestamp}`;
await copyFile(configPath, backupPath);
}
await writeFile4(configPath, `${JSON.stringify(merged, null, 2)}
`, "utf8");
out.push(
`\u2713 Patched ${configPath}`,
backupPath ? ` Backup saved to ${backupPath}` : " (created a new config file)",
"",
"Restart Claude Desktop to load seekstone."
);
return { ok: true, exitCode: 0, output: out, wrotePath: configPath, backupPath };
}
// src/log.ts

@@ -704,3 +906,3 @@ import { appendFileSync, renameSync, statSync } from "fs";

}
function writeFile4(record) {
function writeFile5(record) {
if (!fileOk || filePath === void 0) return;

@@ -727,3 +929,3 @@ const line = `${JSON.stringify(record)}

`);
writeFile4({ t: ts, level: lvl, msg, ...f });
writeFile5({ t: ts, level: lvl, msg, ...f });
}

@@ -740,4 +942,4 @@ return {

// src/watcher.ts
import { readFile as readFile5 } from "fs/promises";
import { join as join8, relative as relative2, sep as sep2 } from "path";
import { readFile as readFile6 } from "fs/promises";
import { join as join9, relative as relative2, sep as sep2 } from "path";
import chokidar from "chokidar";

@@ -749,3 +951,3 @@ function startWatcher(ctx2, log2, opts) {

try {
const raw = await readFile5(join8(ctx2.vaultRoot, relPath), "utf8");
const raw = await readFile6(join9(ctx2.vaultRoot, relPath), "utf8");
upsertDoc(ctx2, buildDoc(relPath, raw));

@@ -791,2 +993,24 @@ log2?.debug("index updated", { path: relPath, op });

// src/index.ts
var VERSION = true ? "0.2.0" : "0.0.0-dev";
var intent = parseCliIntent(process.argv.slice(2));
if (intent === "version") {
process.stdout.write(`${VERSION}
`);
process.exit(0);
}
if (intent === "help") {
process.stdout.write(`${helpText(VERSION)}
`);
process.exit(0);
}
if (intent === "init") {
const result = await runInit(parseInitArgs(process.argv.slice(3)), {
env: process.env,
platform: process.platform,
timestamp: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")
});
process.stdout.write(`${result.output.join("\n")}
`);
process.exit(result.exitCode);
}
var log = createLogger();

@@ -793,0 +1017,0 @@ var vaultRoot = process.env.SEEKSTONE_VAULT;

+1
-1
{
"name": "seekstone",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",

@@ -5,0 +5,0 @@ "description": "Seekstone MCP server — filesystem-direct Obsidian vault access, low context-tax.",

@@ -32,2 +32,12 @@ # seekstone

### Guided setup
`seekstone init` validates your vault and prints the config to paste, or patches the Claude Desktop config for you (with a backup, leaving other MCP servers untouched):
```bash
npx -y seekstone init --vault "/absolute/path/to/your/vault" # print config
npx -y seekstone init --vault "/absolute/path/to/your/vault" --write # patch Claude Desktop
npx -y seekstone init --vault "/absolute/path/to/your/vault" --client code # print Claude Code command
```
## Configuration

@@ -34,0 +44,0 @@

Sorry, the diff of this file is too big to display