@lifeaitools/clauth 1.5.5 β†’ 1.5.7

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.
Files changed (2) hide show
  1. package/cli/commands/serve.js +245 -26
  2. package/package.json +1 -1
@@ -739,6 +739,7 @@ function dashboardHtml(port, whitelist, isStaged = false) {
739
739
  <button class="btn-add" onclick="toggleAddService()">+ Add Service</button>
740
740
  <button class="btn-check" id="check-btn" onclick="checkAll()">⬀ Check All</button>
741
741
  <button class="btn-lock" onclick="lockVault()">πŸ”’ Lock</button>
742
+ <button class="btn-stop" onclick="restartDaemon()" style="background:#1a2e1a;border:1px solid #166534;color:#86efac" title="Restart daemon β€” keeps vault unlocked">β†Ί Restart</button>
742
743
  <button class="btn-stop" onclick="stopDaemon()" style="background:#7f1d1d;border:1px solid #991b1b;color:#fca5a5" title="Stop daemon β€” password required on next start">⏹ Stop</button>
743
744
  <button class="btn-cancel" style="margin-left:auto" onclick="toggleChangePw()">Change Password</button>
744
745
  </div>
@@ -747,8 +748,12 @@ function dashboardHtml(port, whitelist, isStaged = false) {
747
748
  <h3>Add New Service</h3>
748
749
  <div class="add-row">
749
750
  <div class="add-field">
750
- <label>Service name</label>
751
- <input class="add-input" id="add-name" type="text" placeholder="e.g. coolify-admin" autocomplete="off" spellcheck="false">
751
+ <label>Slug <span style="color:#475569;font-weight:400">(used in <code style="font-size:.72rem">clauth get &lt;slug&gt;</code>)</span></label>
752
+ <input class="add-input" id="add-name" type="text" placeholder="e.g. coolify-admin" autocomplete="off" spellcheck="false" style="font-family:'Courier New',monospace">
753
+ </div>
754
+ <div class="add-field">
755
+ <label>Label <span style="color:#475569;font-weight:400">(display name)</span></label>
756
+ <input class="add-input" id="add-label" type="text" placeholder="e.g. Coolify Admin Token" autocomplete="off" spellcheck="false">
752
757
  </div>
753
758
  <div class="add-field">
754
759
  <label>Key type</label>
@@ -756,6 +761,12 @@ function dashboardHtml(port, whitelist, isStaged = false) {
756
761
  <option value="">Loading…</option>
757
762
  </select>
758
763
  </div>
764
+ </div>
765
+ <div class="add-row">
766
+ <div class="add-field">
767
+ <label>Description <span style="color:#475569;font-weight:400">(optional)</span></label>
768
+ <input class="add-input" id="add-description" type="text" placeholder="e.g. API token for Coolify deployments" autocomplete="off" spellcheck="false" style="width:420px">
769
+ </div>
759
770
  <div class="add-field">
760
771
  <label>Project <span style="color:#475569;font-weight:400">(optional)</span></label>
761
772
  <input class="add-input" id="add-project" type="text" placeholder="e.g. marketing-engine" autocomplete="off" spellcheck="false">
@@ -1121,6 +1132,17 @@ async function makeLive() {
1121
1132
  }
1122
1133
  }
1123
1134
 
1135
+ // ── Restart daemon (keeps boot.key β€” vault stays unlocked) ──
1136
+ async function restartDaemon() {
1137
+ if (!confirm("Restart the daemon?\\n\\nThe vault will stay unlocked (boot.key kept).")) return;
1138
+ const btn = document.querySelector('[onclick="restartDaemon()"]');
1139
+ if (btn) { btn.disabled = true; btn.textContent = "β†Ί Restarting…"; }
1140
+ try {
1141
+ await fetch(BASE + "/restart", { method: "POST" });
1142
+ } catch {}
1143
+ document.getElementById("main-view").innerHTML = '<div style="text-align:center;padding:80px 20px"><div style="font-size:3rem;margin-bottom:16px">β†Ί</div><div style="font-size:1.1rem;color:#86efac">Restarting…</div><div style="font-size:.85rem;color:#64748b;margin-top:8px">Page will reload automatically.</div></div>';
1144
+ }
1145
+
1124
1146
  // ── Stop daemon (user-initiated β€” clears boot.key, requires password on restart) ──
1125
1147
  async function stopDaemon() {
1126
1148
  if (!confirm("Stop the daemon?\\n\\nCredentials will need to be re-entered on next start.")) return;
@@ -1176,14 +1198,18 @@ function renderServiceGrid(services) {
1176
1198
  grid.innerHTML = filtered.map(s => \`
1177
1199
  <div class="card">
1178
1200
  <div style="display:flex;align-items:flex-start;justify-content:space-between">
1179
- <div>
1180
- <div class="card-name">\${s.name}</div>
1201
+ <div style="flex:1;min-width:0">
1202
+ <div style="display:flex;align-items:baseline;gap:8px;flex-wrap:wrap">
1203
+ <span class="card-name" id="label-display-\${s.name}">\${s.label && s.label !== s.name ? s.label : s.name}</span>
1204
+ <code style="font-family:'Courier New',monospace;font-size:.75rem;color:#64748b;background:rgba(100,116,139,.1);padding:1px 6px;border-radius:3px;letter-spacing:.3px">\${s.name}</code>
1205
+ </div>
1181
1206
  <div style="display:flex;align-items:center;gap:6px;margin-top:2px">
1182
1207
  <div class="card-type">\${s.key_type || "secret"}</div>
1183
1208
  <span class="svc-badge \${s.enabled === false ? "off" : "on"}" id="badge-\${s.name}">\${s.enabled === false ? "disabled" : "enabled"}</span>
1184
1209
  <span class="expiry-badge" id="expiry-\${s.name}" style="font-size:.65rem;border-radius:3px;padding:1px 6px;display:none"></span>
1185
1210
  \${s.project ? \`<span style="font-size:.68rem;color:#3b82f6;background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.2);border-radius:3px;padding:1px 6px">\${s.project}</span>\` : ""}
1186
1211
  </div>
1212
+ \${s.description ? \`<div style="font-size:.78rem;color:#64748b;margin-top:4px;line-height:1.3">\${s.description}</div>\` : ""}
1187
1213
  \${KEY_URLS[s.name] ? \`<a class="card-getkey" href="\${KEY_URLS[s.name]}" target="_blank" rel="noopener">β†— Get / rotate key</a>\` : ""}
1188
1214
  \${(EXTRA_LINKS[s.name] || []).map(l => \`<a class="card-getkey" href="\${l.url}" target="_blank" rel="noopener" style="margin-left:0">\${l.label}</a>\`).join("")}
1189
1215
  </div>
@@ -1197,14 +1223,22 @@ function renderServiceGrid(services) {
1197
1223
  <button class="btn-project" onclick="toggleProjectEdit('\${s.name}')">\${s.project ? "✎ Project" : "+ Project"}</button>
1198
1224
  <button class="btn \${s.enabled === false ? "btn-enable" : "btn-disable"}" id="togbtn-\${s.name}" onclick="toggleService('\${s.name}')">\${s.enabled === false ? "Enable" : "Disable"}</button>
1199
1225
  <button class="btn-rotate" id="rotbtn-\${s.name}" style="display:none;background:#0e7490;border:1px solid #06b6d4;color:#cffafe;font-size:.75rem;padding:3px 8px;border-radius:4px;cursor:pointer" onclick="rotateKey('\${s.name}')">↻ Rotate</button>
1200
- <button class="btn-rename" onclick="toggleRename('\${s.name}')" title="Rename service">✎</button>
1226
+ <button class="btn-rename" onclick="toggleLabelEdit('\${s.name}')" title="Edit display label">✏️</button>
1201
1227
  <button class="btn-delete" onclick="deleteService('\${s.name}')" title="Delete service">βœ•</button>
1202
1228
  </div>
1203
1229
  <div class="rename-panel" id="rn-\${s.name}">
1204
- <input class="rename-input" id="rn-input-\${s.name}" value="\${s.name}" spellcheck="false" autocomplete="off" placeholder="New name…">
1205
- <button class="btn" onclick="saveRename('\${s.name}')" style="padding:4px 10px;font-size:.8rem">Save</button>
1206
- <button class="btn" onclick="toggleRename('\${s.name}')" style="padding:4px 10px;font-size:.8rem;background:#1e293b">Cancel</button>
1207
- <span class="rename-msg" id="rn-msg-\${s.name}"></span>
1230
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
1231
+ <span style="font-size:.72rem;color:#64748b">Edit label for</span>
1232
+ <code style="font-family:'Courier New',monospace;font-size:.75rem;color:#94a3b8;background:rgba(100,116,139,.1);padding:1px 5px;border-radius:3px">\${s.name}</code>
1233
+ <span style="font-size:.68rem;color:#475569">(slug unchanged)</span>
1234
+ </div>
1235
+ <div style="display:flex;gap:6px;align-items:center">
1236
+ <input class="rename-input" id="rn-input-\${s.name}" value="\${s.label || s.name}" spellcheck="false" autocomplete="off" placeholder="Human-readable label…"
1237
+ onkeydown="if(event.key==='Enter')saveLabel('\${s.name}');if(event.key==='Escape')toggleLabelEdit('\${s.name}')">
1238
+ <button class="btn" onclick="saveLabel('\${s.name}')" style="padding:4px 10px;font-size:.8rem">Save</button>
1239
+ <button class="btn" onclick="toggleLabelEdit('\${s.name}')" style="padding:4px 10px;font-size:.8rem;background:#1e293b">Cancel</button>
1240
+ <span class="rename-msg" id="rn-msg-\${s.name}"></span>
1241
+ </div>
1208
1242
  </div>
1209
1243
  <div class="project-edit" id="pe-\${s.name}">
1210
1244
  <input type="text" id="pe-input-\${s.name}" value="\${s.project || ""}" placeholder="Project name…" spellcheck="false" autocomplete="off">
@@ -1428,39 +1462,50 @@ async function clearProject(name) {
1428
1462
  }
1429
1463
 
1430
1464
  // ── Rename service ───────────────────────────
1431
- function toggleRename(name) {
1465
+ function toggleLabelEdit(name) {
1432
1466
  const panel = document.getElementById("rn-" + name);
1433
1467
  const open = panel.style.display === "block";
1434
1468
  panel.style.display = open ? "none" : "block";
1435
1469
  if (!open) {
1436
1470
  const inp = document.getElementById("rn-input-" + name);
1437
- inp.value = name;
1471
+ const svc = allServices.find(s => s.name === name);
1472
+ inp.value = (svc && svc.label) ? svc.label : name;
1438
1473
  inp.focus(); inp.select();
1439
1474
  document.getElementById("rn-msg-" + name).textContent = "";
1440
1475
  }
1441
1476
  }
1442
1477
 
1443
- async function saveRename(oldName) {
1444
- const inp = document.getElementById("rn-input-" + oldName);
1445
- const msg = document.getElementById("rn-msg-" + oldName);
1446
- const newName = inp.value.trim();
1447
- if (!newName || newName === oldName) { toggleRename(oldName); return; }
1478
+ async function saveLabel(name) {
1479
+ const inp = document.getElementById("rn-input-" + name);
1480
+ const msg = document.getElementById("rn-msg-" + name);
1481
+ const newLabel = inp.value.trim();
1482
+ if (!newLabel) { toggleLabelEdit(name); return; }
1483
+ const svc = allServices.find(s => s.name === name);
1484
+ if (svc && newLabel === svc.label) { toggleLabelEdit(name); return; }
1448
1485
  msg.style.color = "#94a3b8"; msg.textContent = "Saving…";
1449
1486
  try {
1450
- const r = await fetch(BASE + "/rename/" + oldName, {
1487
+ const r = await fetch(BASE + "/update-service", {
1451
1488
  method: "POST",
1452
1489
  headers: { "Content-Type": "application/json" },
1453
- body: JSON.stringify({ new_name: newName })
1490
+ body: JSON.stringify({ service: name, label: newLabel })
1454
1491
  }).then(r => r.json());
1455
1492
  if (r.locked) { showLockScreen(false); return; }
1456
1493
  if (r.error) throw new Error(r.error);
1457
- msg.style.color = "#4ade80"; msg.textContent = "βœ“ Renamed";
1458
- setTimeout(() => loadServices(), 800);
1494
+ msg.style.color = "#4ade80"; msg.textContent = "βœ“ Label updated";
1495
+ // Update in-memory and DOM immediately
1496
+ if (svc) svc.label = newLabel;
1497
+ const labelEl = document.getElementById("label-display-" + name);
1498
+ if (labelEl) labelEl.textContent = newLabel;
1499
+ setTimeout(() => { toggleLabelEdit(name); }, 800);
1459
1500
  } catch (e) {
1460
1501
  msg.style.color = "#f87171"; msg.textContent = "βœ— " + e.message;
1461
1502
  }
1462
1503
  }
1463
1504
 
1505
+ // Legacy rename (slug) β€” kept for API compatibility but hidden from UI
1506
+ function toggleRename(name) { toggleLabelEdit(name); }
1507
+ async function saveRename(oldName) { await saveLabel(oldName); }
1508
+
1464
1509
  // ── Delete service ───────────────────────────
1465
1510
  async function deleteService(name) {
1466
1511
  if (!confirm(\`Delete service "\${name}"? This cannot be undone.\`)) return;
@@ -1706,6 +1751,8 @@ async function toggleAddService() {
1706
1751
  panel.style.display = open ? "none" : "block";
1707
1752
  if (!open) {
1708
1753
  document.getElementById("add-name").value = "";
1754
+ document.getElementById("add-label").value = "";
1755
+ document.getElementById("add-description").value = "";
1709
1756
  document.getElementById("add-project").value = "";
1710
1757
  document.getElementById("add-msg").textContent = "";
1711
1758
  // Fetch available key types from server
@@ -1728,16 +1775,19 @@ async function toggleAddService() {
1728
1775
 
1729
1776
  async function addService() {
1730
1777
  const name = document.getElementById("add-name").value.trim().toLowerCase();
1778
+ const label = document.getElementById("add-label").value.trim();
1779
+ const description = document.getElementById("add-description").value.trim();
1731
1780
  const type = document.getElementById("add-type").value;
1732
1781
  const project = document.getElementById("add-project").value.trim();
1733
1782
  const msg = document.getElementById("add-msg");
1734
1783
 
1735
- if (!name) { msg.className = "add-msg fail"; msg.textContent = "Service name is required."; return; }
1736
- if (!/^[a-z0-9][a-z0-9_-]*$/.test(name)) { msg.className = "add-msg fail"; msg.textContent = "Lowercase letters, numbers, hyphens, underscores only."; return; }
1784
+ if (!name) { msg.className = "add-msg fail"; msg.textContent = "Slug is required."; return; }
1785
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(name)) { msg.className = "add-msg fail"; msg.textContent = "Slug: lowercase letters, numbers, hyphens, underscores only."; return; }
1737
1786
 
1738
1787
  msg.className = "add-msg"; msg.textContent = "Creating…";
1739
1788
  try {
1740
- const payload = { name, key_type: type, label: name };
1789
+ const payload = { name, key_type: type, label: label || name };
1790
+ if (description) payload.description = description;
1741
1791
  if (project) payload.project = project;
1742
1792
  const r = await fetch(BASE + "/add-service", {
1743
1793
  method: "POST",
@@ -1764,8 +1814,9 @@ document.addEventListener("DOMContentLoaded", () => {
1764
1814
  document.getElementById("lock-input").addEventListener("keydown", e => {
1765
1815
  if (e.key === "Enter") unlock();
1766
1816
  });
1767
- document.getElementById("add-name").addEventListener("keydown", e => {
1768
- if (e.key === "Enter") addService();
1817
+ ["add-name", "add-label", "add-description", "add-project"].forEach(id => {
1818
+ const el = document.getElementById(id);
1819
+ if (el) el.addEventListener("keydown", e => { if (e.key === "Enter") addService(); });
1769
1820
  });
1770
1821
  });
1771
1822
 
@@ -3233,6 +3284,28 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3233
3284
  return ok(res, { status: tunnelStatus });
3234
3285
  }
3235
3286
 
3287
+ // POST /restart β€” spawn fresh process then exit (keeps boot.key, vault stays unlocked)
3288
+ if (method === "POST" && reqPath === "/restart") {
3289
+ ok(res, { ok: true, message: "restarting" });
3290
+ const { spawn } = await import("child_process");
3291
+ const cliEntry = path.resolve(__dirname, "../index.js");
3292
+ const childArgs = [cliEntry, "serve", "start", "--port", String(port)];
3293
+ if (password) childArgs.push("--pw", password);
3294
+ if (whitelist) childArgs.push("--services", whitelist.join(","));
3295
+ if (tunnelHostname) childArgs.push("--tunnel", tunnelHostname);
3296
+ const out = fs.openSync(LOG_FILE, "a");
3297
+ const child = spawn(process.execPath, childArgs, {
3298
+ detached: true,
3299
+ stdio: ["ignore", out, out],
3300
+ env: { ...process.env, __CLAUTH_DAEMON: "1" },
3301
+ });
3302
+ child.unref();
3303
+ stopTunnel();
3304
+ removePid();
3305
+ setTimeout(() => process.exit(0), 300);
3306
+ return;
3307
+ }
3308
+
3236
3309
  // GET|POST /shutdown (for daemon stop β€” programmatic, keeps boot.key)
3237
3310
  // Accept POST as well β€” older scripts and curl default to POST
3238
3311
  if ((method === "GET" || method === "POST") && reqPath === "/shutdown") {
@@ -4248,7 +4321,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
4248
4321
  // Don't count browser noise, MCP discovery probes, or OAuth probes as auth failures
4249
4322
  const isBenign = reqPath.startsWith("/.well-known/") || [
4250
4323
  "/favicon.ico", "/robots.txt", "/apple-touch-icon.png", "/apple-touch-icon-precomposed.png",
4251
- "/sse", "/mcp", "/message", "/register", "/authorize", "/token", "/shutdown",
4324
+ "/sse", "/mcp", "/message", "/register", "/authorize", "/token", "/shutdown", "/restart",
4252
4325
  ].includes(reqPath);
4253
4326
  if (isBenign) {
4254
4327
  res.writeHead(404, { "Content-Type": "application/json", ...CORS });
@@ -4671,6 +4744,88 @@ const MCP_TOOLS = [
4671
4744
  description: "Test whether the clauth MCP connector is reachable via the Cloudflare tunnel. Returns connectivity status and tunnel URL.",
4672
4745
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
4673
4746
  },
4747
+
4748
+ // ── Google Workspace (gws CLI) ──────────────────────────────────────────
4749
+ {
4750
+ name: "gws_run",
4751
+ description: "Run any gws CLI command for Google Workspace (Drive, Gmail, Calendar, Docs, Sheets, Slides, Tasks, People, Chat, etc.).\nCLI pattern: gws <service> <resource> [sub_resource] <method> [--params JSON] [--body JSON]",
4752
+ inputSchema: {
4753
+ type: "object",
4754
+ properties: {
4755
+ service: { type: "string", description: "Google Workspace service: drive, sheets, gmail, calendar, docs, slides, tasks, people, chat, classroom, forms, keep, meet, script" },
4756
+ resource: { type: "string", description: "Resource noun, e.g. 'files', 'users', 'events'" },
4757
+ sub_resource: { type: "string", description: "Optional sub-resource, e.g. 'messages' in 'users messages'" },
4758
+ method: { type: "string", description: "Method verb: list, get, insert, update, delete, send, create" },
4759
+ params: { type: "object", description: "Query/path params passed as --params JSON" },
4760
+ body: { type: "object", description: "Request body passed as --json JSON" }
4761
+ },
4762
+ required: ["service", "resource", "method"],
4763
+ additionalProperties: false
4764
+ }
4765
+ },
4766
+ {
4767
+ name: "gws_gmail_list",
4768
+ description: "List Gmail messages. Supports Gmail search syntax (e.g. 'from:alice subject:meeting is:unread').",
4769
+ inputSchema: {
4770
+ type: "object",
4771
+ properties: {
4772
+ query: { type: "string", description: "Gmail search query" },
4773
+ max_results: { type: "number", description: "Max messages (1-500, default 10)" }
4774
+ },
4775
+ additionalProperties: false
4776
+ }
4777
+ },
4778
+ {
4779
+ name: "gws_gmail_read",
4780
+ description: "Read a single Gmail message by ID. Returns full message with headers and body.",
4781
+ inputSchema: {
4782
+ type: "object",
4783
+ properties: { message_id: { type: "string", description: "Gmail message ID" } },
4784
+ required: ["message_id"],
4785
+ additionalProperties: false
4786
+ }
4787
+ },
4788
+ {
4789
+ name: "gws_gmail_send",
4790
+ description: "Send an email via Gmail.",
4791
+ inputSchema: {
4792
+ type: "object",
4793
+ properties: {
4794
+ to: { type: "string", description: "Recipient email address" },
4795
+ subject: { type: "string", description: "Email subject" },
4796
+ body: { type: "string", description: "Plain-text email body" },
4797
+ from: { type: "string", description: "Sender name/address (optional)" }
4798
+ },
4799
+ required: ["to", "subject", "body"],
4800
+ additionalProperties: false
4801
+ }
4802
+ },
4803
+ {
4804
+ name: "gws_calendar_list",
4805
+ description: "List upcoming Google Calendar events.",
4806
+ inputSchema: {
4807
+ type: "object",
4808
+ properties: {
4809
+ calendar_id: { type: "string", description: "Calendar ID (default: primary)" },
4810
+ max_results: { type: "number", description: "Max events (default 10)" },
4811
+ time_min: { type: "string", description: "Start bound ISO 8601, e.g. '2026-04-10T00:00:00Z'" },
4812
+ time_max: { type: "string", description: "End bound ISO 8601" }
4813
+ },
4814
+ additionalProperties: false
4815
+ }
4816
+ },
4817
+ {
4818
+ name: "gws_drive_list",
4819
+ description: "List files in Google Drive. Supports Drive search query syntax.",
4820
+ inputSchema: {
4821
+ type: "object",
4822
+ properties: {
4823
+ query: { type: "string", description: "Drive search query, e.g. \"name contains 'report'\"" },
4824
+ max_results: { type: "number", description: "Max files (default 10)" }
4825
+ },
4826
+ additionalProperties: false
4827
+ }
4828
+ },
4674
4829
  ];
4675
4830
 
4676
4831
  function writeTempSecret(service, value) {
@@ -4963,6 +5118,70 @@ async function handleMcpTool(vault, name, args) {
4963
5118
  return mcpResult(results.join("\n"));
4964
5119
  }
4965
5120
 
5121
+ // ── Google Workspace (gws CLI) ───────────────────────────────────────
5122
+ case "gws_run": {
5123
+ const { service, resource, sub_resource, method, params, body } = args;
5124
+ const cmdArgs = [service, resource];
5125
+ if (sub_resource) cmdArgs.push(sub_resource);
5126
+ cmdArgs.push(method);
5127
+ if (params) cmdArgs.push("--params", `'${JSON.stringify(params)}'`);
5128
+ if (body) cmdArgs.push("--json", `'${JSON.stringify(body)}'`);
5129
+ try {
5130
+ const raw = execSyncTop(["gws", ...cmdArgs].join(" "), { encoding: "utf8", timeout: 30000, windowsHide: true });
5131
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5132
+ } catch (err) {
5133
+ return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`);
5134
+ }
5135
+ }
5136
+
5137
+ case "gws_gmail_list": {
5138
+ const p = { userId: "me", maxResults: args.max_results ?? 10 };
5139
+ if (args.query) p.q = args.query;
5140
+ try {
5141
+ const raw = execSyncTop(`gws gmail users messages list --params '${JSON.stringify(p)}'`, { encoding: "utf8", timeout: 30000, windowsHide: true });
5142
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5143
+ } catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
5144
+ }
5145
+
5146
+ case "gws_gmail_read": {
5147
+ const p = { userId: "me", id: args.message_id, format: "full" };
5148
+ try {
5149
+ const raw = execSyncTop(`gws gmail users messages get --params '${JSON.stringify(p)}'`, { encoding: "utf8", timeout: 30000, windowsHide: true });
5150
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5151
+ } catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
5152
+ }
5153
+
5154
+ case "gws_gmail_send": {
5155
+ const lines = [];
5156
+ if (args.from) lines.push(`From: ${args.from}`);
5157
+ lines.push(`To: ${args.to}`, `Subject: ${args.subject}`, "Content-Type: text/plain; charset=utf-8", "MIME-Version: 1.0", "", args.body);
5158
+ const encoded = Buffer.from(lines.join("\r\n")).toString("base64url");
5159
+ const bodyObj = { userId: "me", resource: { raw: encoded } };
5160
+ try {
5161
+ const raw = execSyncTop(`gws gmail users messages send --json '${JSON.stringify(bodyObj)}'`, { encoding: "utf8", timeout: 30000, windowsHide: true });
5162
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5163
+ } catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
5164
+ }
5165
+
5166
+ case "gws_calendar_list": {
5167
+ const p = { calendarId: args.calendar_id ?? "primary", maxResults: args.max_results ?? 10, singleEvents: true, orderBy: "startTime" };
5168
+ if (args.time_min) p.timeMin = args.time_min;
5169
+ if (args.time_max) p.timeMax = args.time_max;
5170
+ try {
5171
+ const raw = execSyncTop(`gws calendar events list --params '${JSON.stringify(p)}'`, { encoding: "utf8", timeout: 30000, windowsHide: true });
5172
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5173
+ } catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
5174
+ }
5175
+
5176
+ case "gws_drive_list": {
5177
+ const p = { pageSize: args.max_results ?? 10, fields: "files(id,name,mimeType,modifiedTime,size,webViewLink)" };
5178
+ if (args.query) p.q = args.query;
5179
+ try {
5180
+ const raw = execSyncTop(`gws drive files list --params '${JSON.stringify(p)}'`, { encoding: "utf8", timeout: 30000, windowsHide: true });
5181
+ try { return mcpResult(JSON.stringify(JSON.parse(raw.trim()), null, 2)); } catch { return mcpResult(raw); }
5182
+ } catch (err) { return mcpError(`gws failed: ${err.stderr || err.stdout || err.message}`); }
5183
+ }
5184
+
4966
5185
  default:
4967
5186
  return mcpError(`Unknown tool: ${name}`);
4968
5187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {