@lifeaitools/clauth 1.5.39 → 1.5.41

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.
@@ -3119,7 +3119,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3119
3119
  const isMcpPath = MCP_PATHS.includes(reqPath);
3120
3120
  function toolsForPath(p) {
3121
3121
  if (p === "/gws") return MCP_TOOLS.filter(t => t.name.startsWith("gws_"));
3122
- if (p === "/clauth") return MCP_TOOLS.filter(t => t.name.startsWith("clauth_") || t.name === "monkey_dispatch");
3122
+ if (p === "/clauth") return MCP_TOOLS.filter(t => t.name.startsWith("clauth_") || t.name === "monkey_dispatch" || t.name.startsWith("terminal_"));
3123
3123
  if (p === "/fs") return MCP_TOOLS.filter(t => t.name.startsWith("fs_"));
3124
3124
  return MCP_TOOLS; // /mcp — all tools
3125
3125
  }
@@ -3494,6 +3494,83 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3494
3494
  return;
3495
3495
  }
3496
3496
 
3497
+ // POST /terminal/start — start a named terminal session
3498
+ if (method === "POST" && reqPath === "/terminal/start") {
3499
+ let body = "";
3500
+ req.on("data", d => body += d);
3501
+ req.on("end", () => {
3502
+ try {
3503
+ const { name, knowledge_tier, context_md } = JSON.parse(body);
3504
+ if (!name) {
3505
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3506
+ return res.end(JSON.stringify({ error: "name required" }));
3507
+ }
3508
+ const tier = knowledge_tier || 'db_only';
3509
+ const result = startTerminalSession(name, tier, context_md || null);
3510
+ const status = result.error ? 503 : 200;
3511
+ res.writeHead(status, { "Content-Type": "application/json", ...CORS });
3512
+ res.end(JSON.stringify(result));
3513
+ } catch {
3514
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3515
+ res.end(JSON.stringify({ error: "invalid JSON" }));
3516
+ }
3517
+ });
3518
+ return;
3519
+ }
3520
+
3521
+ // POST /terminal/send — send a message to a running terminal session
3522
+ if (method === "POST" && reqPath === "/terminal/send") {
3523
+ let body = "";
3524
+ req.on("data", d => body += d);
3525
+ req.on("end", () => {
3526
+ try {
3527
+ const { session_id, message } = JSON.parse(body);
3528
+ if (!session_id || !message) {
3529
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3530
+ return res.end(JSON.stringify({ error: "session_id and message required" }));
3531
+ }
3532
+ const result = sendTerminalMessage(session_id, message);
3533
+ const status = result.error === 'session_busy' ? 409 : result.error ? 404 : 200;
3534
+ res.writeHead(status, { "Content-Type": "application/json", ...CORS });
3535
+ res.end(JSON.stringify(result));
3536
+ } catch {
3537
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3538
+ res.end(JSON.stringify({ error: "invalid JSON" }));
3539
+ }
3540
+ });
3541
+ return;
3542
+ }
3543
+
3544
+ // GET /terminal/list — list all active terminal sessions
3545
+ if (method === "GET" && reqPath === "/terminal/list") {
3546
+ res.writeHead(200, { "Content-Type": "application/json", ...CORS });
3547
+ res.end(JSON.stringify(listTerminalSessions()));
3548
+ return;
3549
+ }
3550
+
3551
+ // POST /terminal/stop — stop a terminal session
3552
+ if (method === "POST" && reqPath === "/terminal/stop") {
3553
+ let body = "";
3554
+ req.on("data", d => body += d);
3555
+ req.on("end", () => {
3556
+ try {
3557
+ const { session_id } = JSON.parse(body);
3558
+ if (!session_id) {
3559
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3560
+ return res.end(JSON.stringify({ error: "session_id required" }));
3561
+ }
3562
+ const result = stopTerminalSession(session_id);
3563
+ const status = result.error ? 404 : 200;
3564
+ res.writeHead(status, { "Content-Type": "application/json", ...CORS });
3565
+ res.end(JSON.stringify(result));
3566
+ } catch {
3567
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3568
+ res.end(JSON.stringify({ error: "invalid JSON" }));
3569
+ }
3570
+ });
3571
+ return;
3572
+ }
3573
+
3497
3574
  // GET|POST /shutdown (for daemon stop — programmatic, keeps boot.key)
3498
3575
  // Accept POST as well — older scripts and curl default to POST
3499
3576
  if ((method === "GET" || method === "POST") && reqPath === "/shutdown") {
@@ -4866,6 +4943,134 @@ function spawnClaudeTask(prompt, jobId) {
4866
4943
  return { status: 'spawned', pid: proc.pid, jobId, activeWorkers: activeCliWorkers };
4867
4944
  }
4868
4945
 
4946
+ // ── Terminal session manager ─────────────────────────────────────
4947
+ // Rolling-context approach: each session stores a context string.
4948
+ // /terminal/send spawns a fresh claude -p with [context + message],
4949
+ // captures stdout, stores result back in session context.
4950
+ const terminalSessions = new Map(); // session_id → SessionState
4951
+
4952
+ function generateSessionId() {
4953
+ return crypto.randomUUID();
4954
+ }
4955
+
4956
+ function startTerminalSession(name, knowledge_tier, context_md) {
4957
+ const binary = findClaudeBinary();
4958
+ if (!binary) {
4959
+ return { error: 'binary_not_found', message: 'claude CLI not found in PATH or AppData/npm' };
4960
+ }
4961
+ const session_id = generateSessionId();
4962
+ const preamble = context_md
4963
+ ? `${context_md}\n\n---\n`
4964
+ : '';
4965
+ const initialContext = `${preamble}Session: ${name} | Tier: ${knowledge_tier}\nReady. Await instructions.`;
4966
+ const session = {
4967
+ session_id,
4968
+ name,
4969
+ knowledge_tier,
4970
+ status: 'ready',
4971
+ started_at: new Date().toISOString(),
4972
+ context: initialContext,
4973
+ activeProc: null,
4974
+ };
4975
+ terminalSessions.set(session_id, session);
4976
+ console.log(`[terminal] started session ${session_id} name=${name}`);
4977
+ return { session_id, status: 'ready' };
4978
+ }
4979
+
4980
+ function sendTerminalMessage(session_id, message) {
4981
+ const session = terminalSessions.get(session_id);
4982
+ if (!session) return { error: 'not_found', message: `Session ${session_id} not found` };
4983
+ if (session.status === 'stopped') return { error: 'stopped', message: 'Session is stopped' };
4984
+ if (session.status === 'busy') return { error: 'session_busy', message: 'Session is busy — try again shortly' };
4985
+
4986
+ const binary = findClaudeBinary();
4987
+ if (!binary) return { error: 'binary_not_found', message: 'claude CLI not found in PATH or AppData/npm' };
4988
+
4989
+ session.status = 'busy';
4990
+ const fullPrompt = `${session.context}\n\n---\nUser: ${message}`;
4991
+
4992
+ const proc = spawnProc(binary, ['-p', fullPrompt, '--dangerously-skip-permissions'], {
4993
+ cwd: 'C:/Dev/regen-root',
4994
+ env: process.env,
4995
+ stdio: ['ignore', 'pipe', 'pipe'],
4996
+ shell: true,
4997
+ });
4998
+ session.activeProc = proc;
4999
+
5000
+ let stdout = '';
5001
+ let stderr = '';
5002
+ proc.stdout.on('data', d => { stdout += d; });
5003
+ proc.stderr.on('data', d => { stderr += d; });
5004
+ proc.on('close', (code) => {
5005
+ if (terminalSessions.has(session_id)) {
5006
+ const s = terminalSessions.get(session_id);
5007
+ const response = stdout.trim() || stderr.trim() || '(no output)';
5008
+ // Append turn to rolling context (cap at ~8000 chars to avoid overflow)
5009
+ const turn = `\n\nUser: ${message}\nAssistant: ${response}`;
5010
+ const combined = s.context + turn;
5011
+ s.context = combined.length > 8000 ? combined.slice(combined.length - 8000) : combined;
5012
+ s.lastResponse = response;
5013
+ s.lastResponseAt = new Date().toISOString();
5014
+ s.turn = (s.turn || 0) + 1;
5015
+ s.status = 'ready';
5016
+ s.activeProc = null;
5017
+ console.log(`[terminal] session ${session_id} turn=${s.turn} complete code=${code}`);
5018
+ }
5019
+ });
5020
+
5021
+ return { queued: true, session_id };
5022
+ }
5023
+
5024
+ function recvTerminalResponse(session_id, timeout_ms = 30000) {
5025
+ const session = terminalSessions.get(session_id);
5026
+ if (!session) return { error: 'not_found', message: `Session ${session_id} not found` };
5027
+
5028
+ // If already ready with a response, return it immediately
5029
+ if (session.status === 'ready' && session.lastResponse !== undefined) {
5030
+ return {
5031
+ session_id,
5032
+ status: 'ready',
5033
+ turn: session.turn || 0,
5034
+ response: session.lastResponse,
5035
+ responded_at: session.lastResponseAt,
5036
+ };
5037
+ }
5038
+
5039
+ // Session is still busy — caller should poll again
5040
+ return {
5041
+ session_id,
5042
+ status: session.status,
5043
+ turn: session.turn || 0,
5044
+ response: null,
5045
+ message: session.status === 'busy' ? 'Still processing — poll again in a few seconds' : 'No response yet',
5046
+ };
5047
+ }
5048
+
5049
+ function listTerminalSessions() {
5050
+ const sessions = [];
5051
+ for (const [, s] of terminalSessions) {
5052
+ sessions.push({
5053
+ session_id: s.session_id,
5054
+ name: s.name,
5055
+ status: s.status,
5056
+ knowledge_tier: s.knowledge_tier,
5057
+ started_at: s.started_at,
5058
+ });
5059
+ }
5060
+ return sessions;
5061
+ }
5062
+
5063
+ function stopTerminalSession(session_id) {
5064
+ const session = terminalSessions.get(session_id);
5065
+ if (!session) return { error: 'not_found', message: `Session ${session_id} not found` };
5066
+ if (session.activeProc) {
5067
+ try { session.activeProc.kill(); } catch {}
5068
+ }
5069
+ terminalSessions.delete(session_id);
5070
+ console.log(`[terminal] stopped session ${session_id}`);
5071
+ return { stopped: true, session_id };
5072
+ }
5073
+
4869
5074
  const ENV_MAP = {
4870
5075
  "github": "GITHUB_TOKEN",
4871
5076
  "supabase-anon": "NEXT_PUBLIC_SUPABASE_ANON_KEY",
@@ -5046,6 +5251,64 @@ const MCP_TOOLS = [
5046
5251
  },
5047
5252
  },
5048
5253
 
5254
+ // ── Terminal session manager ───────────────────────────────────────────
5255
+ {
5256
+ name: "terminal_start",
5257
+ description: "Start a named persistent Claude session. Sessions maintain rolling context across sends. Returns session_id for subsequent sends.",
5258
+ inputSchema: {
5259
+ type: "object",
5260
+ properties: {
5261
+ name: { type: "string", description: "Human-readable session name (e.g. 'knowledgedude-prt')" },
5262
+ knowledge_tier: { type: "string", enum: ["db_only", "corpus", "project"], description: "Knowledge tier for the session. 'corpus' injects context_md as preamble." },
5263
+ context_md: { type: "string", description: "Optional markdown preamble injected as session context (used for 'corpus' tier)" },
5264
+ },
5265
+ required: ["name"],
5266
+ additionalProperties: false,
5267
+ },
5268
+ },
5269
+ {
5270
+ name: "terminal_send",
5271
+ description: "Send a message to a running terminal session. Spawns a Claude worker with rolling context + message. Returns immediately — session processes async.",
5272
+ inputSchema: {
5273
+ type: "object",
5274
+ properties: {
5275
+ session_id: { type: "string", description: "Session ID returned by terminal_start" },
5276
+ message: { type: "string", description: "Message to send to the session" },
5277
+ },
5278
+ required: ["session_id", "message"],
5279
+ additionalProperties: false,
5280
+ },
5281
+ },
5282
+ {
5283
+ name: "terminal_list",
5284
+ description: "List all active terminal sessions with their status (ready/busy/stopped), knowledge tier, and start time.",
5285
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
5286
+ },
5287
+ {
5288
+ name: "terminal_recv",
5289
+ description: "Read the last response from a terminal session. Returns response if ready, or status='busy' if still processing. Poll every 3-5 seconds until status='ready'.",
5290
+ inputSchema: {
5291
+ type: "object",
5292
+ properties: {
5293
+ session_id: { type: "string", description: "Session ID returned by terminal_start" },
5294
+ },
5295
+ required: ["session_id"],
5296
+ additionalProperties: false,
5297
+ },
5298
+ },
5299
+ {
5300
+ name: "terminal_stop",
5301
+ description: "Stop a terminal session and remove it from memory. Kills any active process.",
5302
+ inputSchema: {
5303
+ type: "object",
5304
+ properties: {
5305
+ session_id: { type: "string", description: "Session ID to stop" },
5306
+ },
5307
+ required: ["session_id"],
5308
+ additionalProperties: false,
5309
+ },
5310
+ },
5311
+
5049
5312
  // ── Google Workspace (gws CLI) ──────────────────────────────────────────
5050
5313
  {
5051
5314
  name: "gws_run",
@@ -5762,6 +6025,42 @@ async function handleMcpTool(vault, name, args) {
5762
6025
  return mcpResult(JSON.stringify(result));
5763
6026
  }
5764
6027
 
6028
+ case "terminal_start": {
6029
+ const { name, knowledge_tier, context_md } = args;
6030
+ if (!name) return mcpError("name required");
6031
+ const result = startTerminalSession(name, knowledge_tier || 'db_only', context_md || null);
6032
+ if (result.error) return mcpError(`${result.error}: ${result.message}`);
6033
+ return mcpResult(JSON.stringify(result));
6034
+ }
6035
+
6036
+ case "terminal_send": {
6037
+ const { session_id, message } = args;
6038
+ if (!session_id || !message) return mcpError("session_id and message required");
6039
+ const result = sendTerminalMessage(session_id, message);
6040
+ if (result.error) return mcpError(`${result.error}: ${result.message}`);
6041
+ return mcpResult(JSON.stringify(result));
6042
+ }
6043
+
6044
+ case "terminal_list": {
6045
+ return mcpResult(JSON.stringify(listTerminalSessions()));
6046
+ }
6047
+
6048
+ case "terminal_recv": {
6049
+ const { session_id } = args;
6050
+ if (!session_id) return mcpError("session_id required");
6051
+ const result = recvTerminalResponse(session_id);
6052
+ if (result.error) return mcpError(`${result.error}: ${result.message}`);
6053
+ return mcpResult(JSON.stringify(result));
6054
+ }
6055
+
6056
+ case "terminal_stop": {
6057
+ const { session_id } = args;
6058
+ if (!session_id) return mcpError("session_id required");
6059
+ const result = stopTerminalSession(session_id);
6060
+ if (result.error) return mcpError(`${result.error}: ${result.message}`);
6061
+ return mcpResult(JSON.stringify(result));
6062
+ }
6063
+
5765
6064
  default:
5766
6065
  return mcpError(`Unknown tool: ${name}`);
5767
6066
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.5.39",
3
+ "version": "1.5.41",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {