🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@geml/geml

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@geml/geml - npm Package Compare versions

Comparing version
1.4.3
to
1.4.4
+1
-0
dist/geml.d.ts

@@ -74,1 +74,2 @@ #!/usr/bin/env node

export declare function blockSpans(source: string): Map<string, Span>;
export declare const PARSER_VERSION: string;
+3
-3
#!/usr/bin/env node
export interface McpOptions {
workspace: string;
root: string;
history: boolean;

@@ -8,3 +8,3 @@ }

export declare function configure(o: Partial<McpOptions>): McpOptions;
export declare function resolveInWorkspace(file: string): string;
export declare function resolveInRoot(file: string): string;
export interface Tool {

@@ -18,3 +18,3 @@ name: string;

export declare function handleLine(line: string, write?: (s: string) => void): void;
export declare const MCP_USAGE = "usage: geml mcp --workspace <dir> [--no-history]\n\n Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0).\n\n --workspace <dir> REQUIRED. Root directory holding the .geml documents.\n Every path a client names is confined to this directory;\n a client cannot widen or override it.\n --no-history Do not auto-commit a .gemlhistory revision before each\n write. Default is to commit, so geml_revert_block always\n has a revision to undo to.\n\n Register with a client:\n claude mcp add geml-docs -- geml mcp --workspace /abs/path/to/docs";
export declare const MCP_USAGE = "usage: geml mcp --root <dir> [--no-history]\n\n Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0).\n\n --root <dir> REQUIRED. Root directory holding the .geml documents.\n Relative paths resolve against the server process's CWD,\n which the CLIENT chooses \u2014 pass an absolute path.\n Every path a client names is confined to this directory;\n a client cannot widen or override it.\n --no-history Do not auto-commit a .gemlhistory revision before each\n write. Default is to commit, so geml_revert_block always\n has a revision to undo to.\n\n Register with a client:\n claude mcp add geml -- geml mcp --root /abs/path/to/docs";
export declare function parseArgs(args: string[]): McpOptions;
#!/usr/bin/env node
// `geml mcp` — MCP server for GEML document CRUD.
//
// Nine tools over a confined workspace of `.geml` documents: four read-only,
// Nine tools over a confined root directory of `.geml` documents: four read-only,
// five that write. It is the document-editing counterpart to the read-only

@@ -10,3 +10,3 @@ // code-graph server in `codemap/mcp-server.mjs`, and deliberately mirrors its

//
// claude mcp add geml-docs -- geml mcp --workspace /abs/path/to/docs
// claude mcp add geml -- geml mcp --root /abs/path/to/docs
//

@@ -24,3 +24,3 @@ // Three invariants make this worth more than letting a model `str_replace` the

// tool in the set would have nothing to revert to.
// 3. EVERY PATH IS CONFINED to a server-side `--workspace` root the client
// 3. EVERY PATH IS CONFINED to a server-side `--root` directory the client
// cannot override or widen.

@@ -37,6 +37,10 @@ //

import { createInterface } from "node:readline";
import { parse } from "./geml.js";
import { parse, PARSER_VERSION } from "./geml.js";
import { commit, listRevisions, isCurrent } from "./history.js";
const SERVER_VERSION = "0.1.0";
let OPTS = { workspace: process.cwd(), history: true };
// One version for the whole package: `geml --version` and the MCP handshake
// must not disagree. This used to be its own literal and had drifted to 0.1.0
// against a 1.4.x package — invisible to everyone except the user reading their
// client's server list.
const SERVER_VERSION = PARSER_VERSION;
let OPTS = { root: process.cwd(), history: true };
/** Configure the server. Exported so the suite can point it at a temp dir. */

@@ -51,3 +55,3 @@ export function configure(o) {

// `file` is client-supplied, so `../../../etc/passwd` — or a symlink planted
// inside the workspace that points out of it — must not resolve. Canonicalize
// inside the root that points out of it — must not resolve. Canonicalize
// BOTH sides with realpathSync (which follows every link component) and require

@@ -58,6 +62,6 @@ // the real target to sit at or under the real root. Unlike the code-graph

// name its own root could write anywhere.
export function resolveInWorkspace(file) {
export function resolveInRoot(file) {
if (typeof file !== "string" || file === "")
throw new Error("`file` is required");
const root = realpathSync(OPTS.workspace);
const root = realpathSync(OPTS.root);
const target = resolve(root, file);

@@ -69,6 +73,6 @@ let real;

catch {
throw new Error(`no such file in the workspace: ${file}`);
throw new Error(`no such file under the server root: ${file}`);
}
if (real !== root && !real.startsWith(root + sep)) {
throw new Error(`path escapes the workspace: ${file}`);
throw new Error(`path escapes the server root: ${file}`);
}

@@ -79,9 +83,9 @@ if (!statSync(real).isFile())

}
// Cross-document references resolve against the workspace root, never against
// Cross-document references resolve against the SERVER root, never against
// a client-named directory: `root` may only NARROW to a directory inside it.
function resolveRoot(root) {
const ws = realpathSync(OPTS.workspace);
const serverRoot = realpathSync(OPTS.root);
if (root === undefined || root === "")
return ws;
const target = resolve(ws, root);
return serverRoot;
const target = resolve(serverRoot, root);
let real;

@@ -92,6 +96,6 @@ try {

catch {
throw new Error(`no such directory in the workspace: ${root}`);
throw new Error(`no such directory under the server root: ${root}`);
}
if (real !== ws && !real.startsWith(ws + sep))
throw new Error(`root escapes the workspace: ${root}`);
if (real !== serverRoot && !real.startsWith(serverRoot + sep))
throw new Error(`root escapes the server root: ${root}`);
return real;

@@ -141,5 +145,5 @@ }

function applyWrite(spec) {
const real = resolveInWorkspace(spec.file);
const real = resolveInRoot(spec.file);
const before = readFileSync(real, "utf8");
const root = realpathSync(OPTS.workspace);
const root = realpathSync(OPTS.root);
const errorKey = (d) => `${d.code}:${d.message}`;

@@ -218,3 +222,3 @@ const preexisting = new Set(parse(before, { resolveDoc: docResolver(root) }).diagnostics

const hashId = (id) => (id.startsWith("#") ? id : `#${id}`);
const FILE_ARG = { type: "string", description: "Document path relative to the server's --workspace root, e.g. notes/spec.geml" };
const FILE_ARG = { type: "string", description: "Document path relative to the server's --root directory, e.g. notes/spec.geml" };
export const TOOLS = [

@@ -227,3 +231,3 @@ // ----- read -----

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const run = runCli(["get", real, "--json"]);

@@ -247,3 +251,3 @@ if (!run.ok)

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const run = runCli(["get", real, hashId(args.id)]);

@@ -262,3 +266,3 @@ if (!run.ok)

file: FILE_ARG,
root: { type: "string", description: "Directory (inside the workspace) against which cross-document references resolve. Defaults to the workspace root." },
root: { type: "string", description: "Directory (inside the server root) against which cross-document references resolve. Defaults to the server root itself. This is a REFERENCE root and is distinct from the server's own --root sandbox, which it can only narrow." },
},

@@ -268,3 +272,3 @@ required: ["file"],

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const root = resolveRoot(args.root);

@@ -287,3 +291,3 @@ const doc = parse(readFileSync(real, "utf8"), { resolveDoc: docResolver(root) });

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const historyPath = real.replace(/\.geml$/, "") + ".gemlhistory";

@@ -310,3 +314,3 @@ if (!existsSync(historyPath))

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const part = args.part ?? "whole";

@@ -338,3 +342,3 @@ if (!["whole", "head", "body"].includes(part))

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
let where;

@@ -370,3 +374,3 @@ if (args.position === "append")

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
const ids = Array.isArray(args.ids) ? args.ids : [args.ids];

@@ -396,3 +400,3 @@ if (!ids.length)

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
return applyWrite({

@@ -418,3 +422,3 @@ file: args.file,

run: (args) => {
const real = resolveInWorkspace(args.file);
const real = resolveInRoot(args.file);
// Default to `--rev changed`, NOT the tip (`0`) or the CLI's own `-1`. Each

@@ -457,3 +461,3 @@ // write commits the PRE-write state, so the tip undoes the block only when

capabilities: { tools: {} },
serverInfo: { name: "geml-docs", version: SERVER_VERSION },
serverInfo: { name: "geml", version: SERVER_VERSION },
});

@@ -499,7 +503,9 @@ }

// ---------------------------------------------------------------------------
export const MCP_USAGE = `usage: geml mcp --workspace <dir> [--no-history]
export const MCP_USAGE = `usage: geml mcp --root <dir> [--no-history]
Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0).
--workspace <dir> REQUIRED. Root directory holding the .geml documents.
--root <dir> REQUIRED. Root directory holding the .geml documents.
Relative paths resolve against the server process's CWD,
which the CLIENT chooses — pass an absolute path.
Every path a client names is confined to this directory;

@@ -512,23 +518,32 @@ a client cannot widen or override it.

Register with a client:
claude mcp add geml-docs -- geml mcp --workspace /abs/path/to/docs`;
claude mcp add geml -- geml mcp --root /abs/path/to/docs`;
export function parseArgs(args) {
let workspace;
let root;
let history = true;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === "--workspace" || a === "-w")
workspace = args[++i];
else if (a.startsWith("--workspace="))
workspace = a.slice("--workspace=".length);
if (a === "--root" || a === "-r")
root = args[++i];
else if (a.startsWith("--root="))
root = a.slice("--root=".length);
else if (a === "--no-history")
history = false;
// The flag used to be --workspace/-w. Name the replacement instead of
// failing with a bare `unknown option`: this runs inside a client's server
// config, where the only thing the user sees is that the server did not
// start, and guessing from `unknown option '--workspace'` is a bad evening.
else if (a === "--workspace" || a === "-w" || a.startsWith("--workspace=")) {
throw new Error("--workspace is now --root (same meaning: the one directory the server may read and write)");
}
else
throw new Error(`unknown option '${a}'`);
}
if (!workspace)
throw new Error("--workspace <dir> is required (the root the server may read and write)");
const abs = resolve(workspace);
if (!root)
throw new Error("--root <dir> is required (the one directory the server may read and write)");
// Relative paths resolve against THIS process's cwd, which an MCP client
// picks — so they work from a shell and are a coin flip from a client config.
const abs = resolve(root);
if (!existsSync(abs) || !statSync(abs).isDirectory())
throw new Error(`--workspace is not a directory: ${workspace}`);
return { workspace: realpathSync(abs), history };
throw new Error(`--root is not a directory: ${root}`);
return { root: realpathSync(abs), history };
}

@@ -535,0 +550,0 @@ // Auto-run only as a MAIN module: the CLI dispatcher spawns this file as a

{
"name": "@geml/geml",
"version": "1.4.3",
"version": "1.4.4",
"mcpName": "io.github.geml-spec/geml",
"publishConfig": {

@@ -5,0 +6,0 @@ "access": "public"

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