@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.
- package/cli/commands/serve.js +245 -26
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -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>
|
|
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 <slug></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
|
|
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="
|
|
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
|
-
<
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1444
|
-
const inp = document.getElementById("rn-input-" +
|
|
1445
|
-
const msg = document.getElementById("rn-msg-" +
|
|
1446
|
-
const
|
|
1447
|
-
if (!
|
|
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 + "/
|
|
1487
|
+
const r = await fetch(BASE + "/update-service", {
|
|
1451
1488
|
method: "POST",
|
|
1452
1489
|
headers: { "Content-Type": "application/json" },
|
|
1453
|
-
body: JSON.stringify({
|
|
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 = "β
|
|
1458
|
-
|
|
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 = "
|
|
1736
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/.test(name)) { msg.className = "add-msg fail"; msg.textContent = "
|
|
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
|
-
|
|
1768
|
-
|
|
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
|
}
|