@lifeaitools/clauth 1.5.37 → 1.5.38
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/cli/commands/serve.js +282 -2
- package/package.json +4 -2
package/cli/commands/serve.js
CHANGED
|
@@ -17,6 +17,9 @@ import ora from "ora";
|
|
|
17
17
|
import { execSync as execSyncTop } from "child_process";
|
|
18
18
|
import Conf from "conf";
|
|
19
19
|
import { getConfOptions } from "../conf-path.js";
|
|
20
|
+
import { readdir, readFile, writeFile, rm, mkdir, stat, rename } from "node:fs/promises";
|
|
21
|
+
import fg from "fast-glob";
|
|
22
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
20
23
|
|
|
21
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
25
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
|
|
@@ -2913,7 +2916,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
2913
2916
|
if (reqPath.startsWith("/.well-known/oauth-protected-resource")) {
|
|
2914
2917
|
const base = oauthBase();
|
|
2915
2918
|
const suffix = reqPath.replace("/.well-known/oauth-protected-resource", "").replace(/^\//, "");
|
|
2916
|
-
const resourcePath = suffix && ["/gws", "/clauth", "/mcp", "/sse"].includes("/" + suffix) ? "/" + suffix : "/sse";
|
|
2919
|
+
const resourcePath = suffix && ["/gws", "/clauth", "/mcp", "/fs", "/sse"].includes("/" + suffix) ? "/" + suffix : "/sse";
|
|
2917
2920
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store", ...CORS });
|
|
2918
2921
|
return res.end(JSON.stringify({
|
|
2919
2922
|
resource: `${base}${resourcePath}`,
|
|
@@ -3087,16 +3090,18 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3087
3090
|
}
|
|
3088
3091
|
|
|
3089
3092
|
// ── MCP path helpers ──
|
|
3090
|
-
const MCP_PATHS = ["/mcp", "/gws", "/clauth"];
|
|
3093
|
+
const MCP_PATHS = ["/mcp", "/gws", "/clauth", "/fs"];
|
|
3091
3094
|
const isMcpPath = MCP_PATHS.includes(reqPath);
|
|
3092
3095
|
function toolsForPath(p) {
|
|
3093
3096
|
if (p === "/gws") return MCP_TOOLS.filter(t => t.name.startsWith("gws_"));
|
|
3094
3097
|
if (p === "/clauth") return MCP_TOOLS.filter(t => t.name.startsWith("clauth_"));
|
|
3098
|
+
if (p === "/fs") return MCP_TOOLS.filter(t => t.name.startsWith("fs_"));
|
|
3095
3099
|
return MCP_TOOLS; // /mcp — all tools
|
|
3096
3100
|
}
|
|
3097
3101
|
function serverNameForPath(p) {
|
|
3098
3102
|
if (p === "/gws") return "gws";
|
|
3099
3103
|
if (p === "/clauth") return "clauth";
|
|
3104
|
+
if (p === "/fs") return "fs";
|
|
3100
3105
|
return "clauth";
|
|
3101
3106
|
}
|
|
3102
3107
|
|
|
@@ -4782,6 +4787,26 @@ const ENV_MAP = {
|
|
|
4782
4787
|
"gmail": "GMAIL_CREDENTIALS",
|
|
4783
4788
|
};
|
|
4784
4789
|
|
|
4790
|
+
// ── Filesystem service config ──
|
|
4791
|
+
const FS_MOUNTS = [
|
|
4792
|
+
{ name: "regen-root", path: "C:/Dev/regen-root", access: "rwd" },
|
|
4793
|
+
];
|
|
4794
|
+
|
|
4795
|
+
function resolveInMount(requestedPath, mountName) {
|
|
4796
|
+
const mount = FS_MOUNTS.find(m => m.name === mountName) || FS_MOUNTS[0];
|
|
4797
|
+
if (!mount) return { error: "No filesystem mounts configured" };
|
|
4798
|
+
const resolved = path.resolve(mount.path, requestedPath);
|
|
4799
|
+
const normalized = path.normalize(resolved);
|
|
4800
|
+
if (!normalized.startsWith(path.normalize(mount.path))) {
|
|
4801
|
+
return { error: `Path escapes mount: ${requestedPath}` };
|
|
4802
|
+
}
|
|
4803
|
+
return { resolved: normalized, mount };
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
function checkAccess(mount, flag) {
|
|
4807
|
+
return mount.access.includes(flag);
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4785
4810
|
const MCP_TOOLS = [
|
|
4786
4811
|
{
|
|
4787
4812
|
name: "clauth_ping",
|
|
@@ -4959,6 +4984,105 @@ const MCP_TOOLS = [
|
|
|
4959
4984
|
additionalProperties: false
|
|
4960
4985
|
}
|
|
4961
4986
|
},
|
|
4987
|
+
// ── Filesystem tools ──
|
|
4988
|
+
{
|
|
4989
|
+
name: "fs_read",
|
|
4990
|
+
description: "Read a file. Returns UTF-8 text with line numbers. Supports offset/limit for large files.",
|
|
4991
|
+
inputSchema: {
|
|
4992
|
+
type: "object",
|
|
4993
|
+
properties: {
|
|
4994
|
+
path: { type: "string", description: "Relative path within mount (e.g. 'packages/ui/src/index.ts')" },
|
|
4995
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
4996
|
+
offset: { type: "number", description: "Start line (0-based, default 0)" },
|
|
4997
|
+
limit: { type: "number", description: "Max lines to return (default 500)" },
|
|
4998
|
+
},
|
|
4999
|
+
required: ["path"],
|
|
5000
|
+
additionalProperties: false,
|
|
5001
|
+
},
|
|
5002
|
+
},
|
|
5003
|
+
{
|
|
5004
|
+
name: "fs_write",
|
|
5005
|
+
description: "Write content to a file. Creates parent directories if needed. Overwrites existing file.",
|
|
5006
|
+
inputSchema: {
|
|
5007
|
+
type: "object",
|
|
5008
|
+
properties: {
|
|
5009
|
+
path: { type: "string", description: "Relative path within mount" },
|
|
5010
|
+
content: { type: "string", description: "File content to write" },
|
|
5011
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5012
|
+
},
|
|
5013
|
+
required: ["path", "content"],
|
|
5014
|
+
additionalProperties: false,
|
|
5015
|
+
},
|
|
5016
|
+
},
|
|
5017
|
+
{
|
|
5018
|
+
name: "fs_list",
|
|
5019
|
+
description: "List directory contents with file type, size, and modification time.",
|
|
5020
|
+
inputSchema: {
|
|
5021
|
+
type: "object",
|
|
5022
|
+
properties: {
|
|
5023
|
+
path: { type: "string", description: "Relative directory path (default: mount root)" },
|
|
5024
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5025
|
+
},
|
|
5026
|
+
additionalProperties: false,
|
|
5027
|
+
},
|
|
5028
|
+
},
|
|
5029
|
+
{
|
|
5030
|
+
name: "fs_grep",
|
|
5031
|
+
description: "Search file contents using ripgrep. Returns matching lines with context. Spawns rg process.",
|
|
5032
|
+
inputSchema: {
|
|
5033
|
+
type: "object",
|
|
5034
|
+
properties: {
|
|
5035
|
+
pattern: { type: "string", description: "Regex pattern to search for" },
|
|
5036
|
+
path: { type: "string", description: "Relative path to search in (default: mount root)" },
|
|
5037
|
+
glob: { type: "string", description: "File glob filter (e.g. '*.ts', '*.{js,tsx}')" },
|
|
5038
|
+
context: { type: "number", description: "Lines of context around matches (default 0)" },
|
|
5039
|
+
max_results: { type: "number", description: "Max matches (default 50)" },
|
|
5040
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5041
|
+
},
|
|
5042
|
+
required: ["pattern"],
|
|
5043
|
+
additionalProperties: false,
|
|
5044
|
+
},
|
|
5045
|
+
},
|
|
5046
|
+
{
|
|
5047
|
+
name: "fs_glob",
|
|
5048
|
+
description: "Find files by glob pattern. Returns matching file paths relative to mount.",
|
|
5049
|
+
inputSchema: {
|
|
5050
|
+
type: "object",
|
|
5051
|
+
properties: {
|
|
5052
|
+
pattern: { type: "string", description: "Glob pattern (e.g. '**/*.tsx', 'apps/*/package.json')" },
|
|
5053
|
+
path: { type: "string", description: "Relative base path (default: mount root)" },
|
|
5054
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5055
|
+
},
|
|
5056
|
+
required: ["pattern"],
|
|
5057
|
+
additionalProperties: false,
|
|
5058
|
+
},
|
|
5059
|
+
},
|
|
5060
|
+
{
|
|
5061
|
+
name: "fs_delete",
|
|
5062
|
+
description: "Delete a file or empty directory.",
|
|
5063
|
+
inputSchema: {
|
|
5064
|
+
type: "object",
|
|
5065
|
+
properties: {
|
|
5066
|
+
path: { type: "string", description: "Relative path to delete" },
|
|
5067
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5068
|
+
},
|
|
5069
|
+
required: ["path"],
|
|
5070
|
+
additionalProperties: false,
|
|
5071
|
+
},
|
|
5072
|
+
},
|
|
5073
|
+
{
|
|
5074
|
+
name: "fs_mkdir",
|
|
5075
|
+
description: "Create a directory (recursive — creates parents if needed).",
|
|
5076
|
+
inputSchema: {
|
|
5077
|
+
type: "object",
|
|
5078
|
+
properties: {
|
|
5079
|
+
path: { type: "string", description: "Relative directory path to create" },
|
|
5080
|
+
mount: { type: "string", description: "Mount name (default: first mount)" },
|
|
5081
|
+
},
|
|
5082
|
+
required: ["path"],
|
|
5083
|
+
additionalProperties: false,
|
|
5084
|
+
},
|
|
5085
|
+
},
|
|
4962
5086
|
];
|
|
4963
5087
|
|
|
4964
5088
|
function writeTempSecret(service, value) {
|
|
@@ -5315,6 +5439,162 @@ async function handleMcpTool(vault, name, args) {
|
|
|
5315
5439
|
} catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
|
|
5316
5440
|
}
|
|
5317
5441
|
|
|
5442
|
+
// ── Filesystem tools ──────────────────────────────────────
|
|
5443
|
+
|
|
5444
|
+
case "fs_read": {
|
|
5445
|
+
const r = resolveInMount(args.path, args.mount);
|
|
5446
|
+
if (r.error) return mcpError(r.error);
|
|
5447
|
+
if (!checkAccess(r.mount, "r")) return mcpError("Read access denied on this mount");
|
|
5448
|
+
try {
|
|
5449
|
+
const content = await readFile(r.resolved, "utf8");
|
|
5450
|
+
const lines = content.split("\n");
|
|
5451
|
+
const offset = args.offset || 0;
|
|
5452
|
+
const limit = args.limit || 500;
|
|
5453
|
+
const slice = lines.slice(offset, offset + limit);
|
|
5454
|
+
const numbered = slice.map((line, i) => `${offset + i + 1}\t${line}`).join("\n");
|
|
5455
|
+
const header = `${r.resolved} (${lines.length} lines${offset > 0 ? `, showing ${offset + 1}-${Math.min(offset + limit, lines.length)}` : ""})`;
|
|
5456
|
+
return mcpResult(`${header}\n${numbered}`);
|
|
5457
|
+
} catch (err) {
|
|
5458
|
+
if (err.code === "ENOENT") return mcpError(`File not found: ${args.path}`);
|
|
5459
|
+
return mcpError(`Read failed: ${err.message}`);
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
case "fs_write": {
|
|
5464
|
+
const r = resolveInMount(args.path, args.mount);
|
|
5465
|
+
if (r.error) return mcpError(r.error);
|
|
5466
|
+
if (!checkAccess(r.mount, "w")) return mcpError("Write access denied on this mount");
|
|
5467
|
+
try {
|
|
5468
|
+
await mkdir(path.dirname(r.resolved), { recursive: true });
|
|
5469
|
+
await writeFile(r.resolved, args.content, "utf8");
|
|
5470
|
+
return mcpResult(`Written: ${args.path} (${Buffer.byteLength(args.content)} bytes)`);
|
|
5471
|
+
} catch (err) {
|
|
5472
|
+
return mcpError(`Write failed: ${err.message}`);
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5476
|
+
case "fs_list": {
|
|
5477
|
+
const dirPath = args.path || ".";
|
|
5478
|
+
const r = resolveInMount(dirPath, args.mount);
|
|
5479
|
+
if (r.error) return mcpError(r.error);
|
|
5480
|
+
if (!checkAccess(r.mount, "r")) return mcpError("Read access denied on this mount");
|
|
5481
|
+
try {
|
|
5482
|
+
const entries = await readdir(r.resolved, { withFileTypes: true });
|
|
5483
|
+
const results = [];
|
|
5484
|
+
for (const entry of entries) {
|
|
5485
|
+
try {
|
|
5486
|
+
const s = await stat(path.join(r.resolved, entry.name));
|
|
5487
|
+
results.push({
|
|
5488
|
+
name: entry.name,
|
|
5489
|
+
type: entry.isDirectory() ? "dir" : "file",
|
|
5490
|
+
size: s.size,
|
|
5491
|
+
modified: s.mtime.toISOString(),
|
|
5492
|
+
});
|
|
5493
|
+
} catch {
|
|
5494
|
+
results.push({ name: entry.name, type: entry.isDirectory() ? "dir" : "file" });
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
return mcpResult(JSON.stringify(results, null, 2));
|
|
5498
|
+
} catch (err) {
|
|
5499
|
+
if (err.code === "ENOENT") return mcpError(`Directory not found: ${dirPath}`);
|
|
5500
|
+
return mcpError(`List failed: ${err.message}`);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
case "fs_grep": {
|
|
5505
|
+
const searchPath = args.path || ".";
|
|
5506
|
+
const r = resolveInMount(searchPath, args.mount);
|
|
5507
|
+
if (r.error) return mcpError(r.error);
|
|
5508
|
+
if (!checkAccess(r.mount, "r")) return mcpError("Read access denied on this mount");
|
|
5509
|
+
|
|
5510
|
+
const maxResults = args.max_results || 50;
|
|
5511
|
+
const rgArgs = [
|
|
5512
|
+
"--no-heading", "--line-number", "--color", "never",
|
|
5513
|
+
"--max-count", String(maxResults),
|
|
5514
|
+
];
|
|
5515
|
+
if (args.context) rgArgs.push("-C", String(args.context));
|
|
5516
|
+
if (args.glob) rgArgs.push("--glob", args.glob);
|
|
5517
|
+
rgArgs.push(args.pattern, r.resolved);
|
|
5518
|
+
|
|
5519
|
+
return new Promise((resolve) => {
|
|
5520
|
+
let output = "";
|
|
5521
|
+
let killed = false;
|
|
5522
|
+
const proc = spawnProc(rgPath, rgArgs, { timeout: 15000, windowsHide: true });
|
|
5523
|
+
|
|
5524
|
+
proc.stdout.on("data", (chunk) => {
|
|
5525
|
+
output += chunk.toString();
|
|
5526
|
+
if (output.length > 65536) { // 64KB cap
|
|
5527
|
+
killed = true;
|
|
5528
|
+
proc.kill();
|
|
5529
|
+
}
|
|
5530
|
+
});
|
|
5531
|
+
proc.stderr.on("data", () => {}); // ignore stderr
|
|
5532
|
+
|
|
5533
|
+
proc.on("close", (code) => {
|
|
5534
|
+
if (killed) {
|
|
5535
|
+
resolve(mcpResult(output.slice(0, 65536) + "\n... (output truncated at 64KB)"));
|
|
5536
|
+
} else if (code === 1) {
|
|
5537
|
+
resolve(mcpResult("No matches found"));
|
|
5538
|
+
} else if (output) {
|
|
5539
|
+
// Make paths relative to mount
|
|
5540
|
+
const mountNorm = r.mount.path.replace(/\\/g, "/");
|
|
5541
|
+
const cleaned = output.replace(new RegExp(mountNorm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + "/?", "g"), "");
|
|
5542
|
+
resolve(mcpResult(cleaned));
|
|
5543
|
+
} else {
|
|
5544
|
+
resolve(mcpResult("No matches found"));
|
|
5545
|
+
}
|
|
5546
|
+
});
|
|
5547
|
+
|
|
5548
|
+
proc.on("error", (err) => {
|
|
5549
|
+
resolve(mcpError(`Grep failed: ${err.message}`));
|
|
5550
|
+
});
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
case "fs_glob": {
|
|
5555
|
+
const basePath = args.path || ".";
|
|
5556
|
+
const r = resolveInMount(basePath, args.mount);
|
|
5557
|
+
if (r.error) return mcpError(r.error);
|
|
5558
|
+
if (!checkAccess(r.mount, "r")) return mcpError("Read access denied on this mount");
|
|
5559
|
+
try {
|
|
5560
|
+
const matches = await fg(args.pattern, {
|
|
5561
|
+
cwd: r.resolved,
|
|
5562
|
+
dot: false,
|
|
5563
|
+
onlyFiles: true,
|
|
5564
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
5565
|
+
});
|
|
5566
|
+
if (matches.length === 0) return mcpResult("No files matched");
|
|
5567
|
+
return mcpResult(matches.sort().join("\n"));
|
|
5568
|
+
} catch (err) {
|
|
5569
|
+
return mcpError(`Glob failed: ${err.message}`);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
case "fs_delete": {
|
|
5574
|
+
const r = resolveInMount(args.path, args.mount);
|
|
5575
|
+
if (r.error) return mcpError(r.error);
|
|
5576
|
+
if (!checkAccess(r.mount, "d")) return mcpError("Delete access denied on this mount");
|
|
5577
|
+
try {
|
|
5578
|
+
await rm(r.resolved);
|
|
5579
|
+
return mcpResult(`Deleted: ${args.path}`);
|
|
5580
|
+
} catch (err) {
|
|
5581
|
+
if (err.code === "ENOENT") return mcpError(`Not found: ${args.path}`);
|
|
5582
|
+
return mcpError(`Delete failed: ${err.message}`);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
|
|
5586
|
+
case "fs_mkdir": {
|
|
5587
|
+
const r = resolveInMount(args.path, args.mount);
|
|
5588
|
+
if (r.error) return mcpError(r.error);
|
|
5589
|
+
if (!checkAccess(r.mount, "w")) return mcpError("Write access denied on this mount");
|
|
5590
|
+
try {
|
|
5591
|
+
await mkdir(r.resolved, { recursive: true });
|
|
5592
|
+
return mcpResult(`Created: ${args.path}`);
|
|
5593
|
+
} catch (err) {
|
|
5594
|
+
return mcpError(`Mkdir failed: ${err.message}`);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5318
5598
|
default:
|
|
5319
5599
|
return mcpError(`Unknown tool: ${name}`);
|
|
5320
5600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifeaitools/clauth",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.38",
|
|
4
4
|
"description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"conf": "^13.0.0",
|
|
20
20
|
"inquirer": "^10.1.0",
|
|
21
21
|
"node-fetch": "^3.3.2",
|
|
22
|
-
"ora": "^8.1.0"
|
|
22
|
+
"ora": "^8.1.0",
|
|
23
|
+
"@vscode/ripgrep": "^1.15.9",
|
|
24
|
+
"fast-glob": "^3.3.2"
|
|
23
25
|
},
|
|
24
26
|
"engines": {
|
|
25
27
|
"node": ">=18.0.0"
|