@vailent/pulse-mcp 1.1.0 → 1.3.0
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/dist/server.js +131 -19
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -40498,14 +40498,32 @@ async function handlePodMembers(params) {
|
|
|
40498
40498
|
return ok(data, `${(data || []).length} members in pod`, (data || []).length);
|
|
40499
40499
|
}
|
|
40500
40500
|
case "add": {
|
|
40501
|
-
const
|
|
40502
|
-
|
|
40503
|
-
|
|
40501
|
+
const userId = params.userId;
|
|
40502
|
+
let displayName = params.displayName;
|
|
40503
|
+
let initials = params.initials;
|
|
40504
|
+
let role = params.role || "engineer";
|
|
40505
|
+
if (userId) {
|
|
40506
|
+
const { data: user } = await supabase.from("users").select("full_name, job_title").eq("id", userId).single();
|
|
40507
|
+
if (!user) return err("User not found", "NOT_FOUND");
|
|
40508
|
+
if (!displayName) displayName = user.full_name;
|
|
40509
|
+
if (!initials) {
|
|
40510
|
+
initials = displayName.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
|
|
40511
|
+
}
|
|
40512
|
+
if (!params.role) {
|
|
40513
|
+
const jobTitles = user.job_title || ["member"];
|
|
40514
|
+
role = jobTitles.includes("product_lead") ? "product_lead" : jobTitles.includes("designer") ? "designer" : "engineer";
|
|
40515
|
+
}
|
|
40516
|
+
}
|
|
40517
|
+
if (!displayName?.trim()) return err("displayName is required (or provide userId)", "MISSING_PARAM");
|
|
40518
|
+
if (!initials) {
|
|
40519
|
+
initials = displayName.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
|
|
40520
|
+
}
|
|
40504
40521
|
const { data, error: error2 } = await supabase.from("pod_members").insert({
|
|
40505
40522
|
pod_id: podId,
|
|
40523
|
+
user_id: userId || null,
|
|
40506
40524
|
display_name: displayName.trim(),
|
|
40507
40525
|
initials,
|
|
40508
|
-
role
|
|
40526
|
+
role
|
|
40509
40527
|
}).select().single();
|
|
40510
40528
|
if (error2) return err(error2.message);
|
|
40511
40529
|
return ok(data, `Added ${displayName} to pod`);
|
|
@@ -40533,6 +40551,74 @@ async function handlePodMembers(params) {
|
|
|
40533
40551
|
}
|
|
40534
40552
|
}
|
|
40535
40553
|
|
|
40554
|
+
// lib/current-user.ts
|
|
40555
|
+
import { execSync } from "child_process";
|
|
40556
|
+
var cached2;
|
|
40557
|
+
async function getCurrentUser() {
|
|
40558
|
+
if (cached2 !== void 0) return cached2;
|
|
40559
|
+
try {
|
|
40560
|
+
cached2 = await resolveUser();
|
|
40561
|
+
if (cached2) {
|
|
40562
|
+
console.error(`\u2713 Pulse: identified as ${cached2.fullName} (${cached2.email})`);
|
|
40563
|
+
}
|
|
40564
|
+
return cached2;
|
|
40565
|
+
} catch {
|
|
40566
|
+
cached2 = null;
|
|
40567
|
+
return null;
|
|
40568
|
+
}
|
|
40569
|
+
}
|
|
40570
|
+
async function resolveUser() {
|
|
40571
|
+
const supabase = getAdminClient();
|
|
40572
|
+
const envEmail = process.env.PULSE_USER_EMAIL;
|
|
40573
|
+
if (envEmail?.endsWith("@vailent.com")) {
|
|
40574
|
+
const user = await lookupByEmail(supabase, envEmail);
|
|
40575
|
+
if (user) return user;
|
|
40576
|
+
}
|
|
40577
|
+
const localEmail = getGitEmail("local");
|
|
40578
|
+
if (localEmail?.endsWith("@vailent.com")) {
|
|
40579
|
+
const user = await lookupByEmail(supabase, localEmail);
|
|
40580
|
+
if (user) return user;
|
|
40581
|
+
}
|
|
40582
|
+
const globalEmail = getGitEmail("global");
|
|
40583
|
+
if (globalEmail?.endsWith("@vailent.com")) {
|
|
40584
|
+
const user = await lookupByEmail(supabase, globalEmail);
|
|
40585
|
+
if (user) return user;
|
|
40586
|
+
}
|
|
40587
|
+
const anyEmail = localEmail || globalEmail;
|
|
40588
|
+
if (anyEmail) {
|
|
40589
|
+
console.error(
|
|
40590
|
+
`\u26A0 Pulse: Could not identify you.
|
|
40591
|
+
Your git email (${anyEmail}) is not a @vailent.com address.
|
|
40592
|
+
Fix: git config --global user.email yourname@vailent.com
|
|
40593
|
+
Then restart Claude Code.`
|
|
40594
|
+
);
|
|
40595
|
+
} else {
|
|
40596
|
+
console.error(
|
|
40597
|
+
`\u26A0 Pulse: Could not identify you.
|
|
40598
|
+
Git email not configured.
|
|
40599
|
+
Fix: git config --global user.email yourname@vailent.com
|
|
40600
|
+
Then restart Claude Code.`
|
|
40601
|
+
);
|
|
40602
|
+
}
|
|
40603
|
+
return null;
|
|
40604
|
+
}
|
|
40605
|
+
function getGitEmail(scope) {
|
|
40606
|
+
try {
|
|
40607
|
+
const flag = scope === "local" ? "--local" : "--global";
|
|
40608
|
+
return execSync(`git config ${flag} user.email`, {
|
|
40609
|
+
encoding: "utf-8",
|
|
40610
|
+
timeout: 3e3
|
|
40611
|
+
}).trim() || null;
|
|
40612
|
+
} catch {
|
|
40613
|
+
return null;
|
|
40614
|
+
}
|
|
40615
|
+
}
|
|
40616
|
+
async function lookupByEmail(supabase, email3) {
|
|
40617
|
+
const { data } = await supabase.from("users").select("id, email, full_name, job_title").eq("email", email3).limit(1).single();
|
|
40618
|
+
if (!data) return null;
|
|
40619
|
+
return { id: data.id, email: data.email, fullName: data.full_name, jobTitle: data.job_title || ["member"] };
|
|
40620
|
+
}
|
|
40621
|
+
|
|
40536
40622
|
// tools/features.ts
|
|
40537
40623
|
async function handleFeatures(params) {
|
|
40538
40624
|
const supabase = getAdminClient();
|
|
@@ -40540,7 +40626,16 @@ async function handleFeatures(params) {
|
|
|
40540
40626
|
switch (action) {
|
|
40541
40627
|
case "list": {
|
|
40542
40628
|
let query = supabase.from("features").select("*").order("week_start", { ascending: false });
|
|
40543
|
-
if (params.podId)
|
|
40629
|
+
if (params.podId) {
|
|
40630
|
+
query = query.eq("pod_id", params.podId);
|
|
40631
|
+
} else {
|
|
40632
|
+
const currentUser = await getCurrentUser();
|
|
40633
|
+
if (currentUser) {
|
|
40634
|
+
const { data: memberships } = await supabase.from("pod_members").select("pod_id").eq("user_id", currentUser.id);
|
|
40635
|
+
const myPodIds = (memberships || []).map((m) => m.pod_id);
|
|
40636
|
+
if (myPodIds.length > 0) query = query.in("pod_id", myPodIds);
|
|
40637
|
+
}
|
|
40638
|
+
}
|
|
40544
40639
|
if (params.projectName) query = query.eq("project_name", params.projectName);
|
|
40545
40640
|
if (params.weekStart) query = query.eq("week_start", params.weekStart);
|
|
40546
40641
|
if (params.status) query = query.eq("status", params.status);
|
|
@@ -40597,7 +40692,10 @@ async function handleFeatures(params) {
|
|
|
40597
40692
|
weekStart: "week_start",
|
|
40598
40693
|
size: "size",
|
|
40599
40694
|
projectName: "project_name",
|
|
40600
|
-
sortOrder: "sort_order"
|
|
40695
|
+
sortOrder: "sort_order",
|
|
40696
|
+
prdText: "prd_text",
|
|
40697
|
+
prdFileUrl: "prd_file_url",
|
|
40698
|
+
prdFileName: "prd_file_name"
|
|
40601
40699
|
};
|
|
40602
40700
|
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
40603
40701
|
for (const [key, col] of Object.entries(allowedFields)) {
|
|
@@ -42067,9 +42165,9 @@ var multipartFormRequestOptions = async (opts, fetch2, stripFilenames = true) =>
|
|
|
42067
42165
|
var supportsFormDataMap = /* @__PURE__ */ new WeakMap();
|
|
42068
42166
|
function supportsFormData(fetchObject) {
|
|
42069
42167
|
const fetch2 = typeof fetchObject === "function" ? fetchObject : fetchObject.fetch;
|
|
42070
|
-
const
|
|
42071
|
-
if (
|
|
42072
|
-
return
|
|
42168
|
+
const cached3 = supportsFormDataMap.get(fetch2);
|
|
42169
|
+
if (cached3)
|
|
42170
|
+
return cached3;
|
|
42073
42171
|
const promise2 = (async () => {
|
|
42074
42172
|
try {
|
|
42075
42173
|
const FetchResponse = "Response" in fetch2 ? fetch2.Response : (await fetch2("data:,")).constructor;
|
|
@@ -47392,7 +47490,10 @@ function formatWeekStart(date4) {
|
|
|
47392
47490
|
}
|
|
47393
47491
|
async function handleBriefing(params) {
|
|
47394
47492
|
const supabase = getAdminClient();
|
|
47395
|
-
const
|
|
47493
|
+
const currentUser = await getCurrentUser();
|
|
47494
|
+
const paramTitle = params.jobTitle;
|
|
47495
|
+
const jobTitles = paramTitle ? [paramTitle] : currentUser?.jobTitle || ["engineer"];
|
|
47496
|
+
const jobTitle = jobTitles.find((t) => t === "leadership") || jobTitles.find((t) => t === "product_lead") || jobTitles.find((t) => t === "designer") || jobTitles.find((t) => t === "engineer") || jobTitles.find((t) => t === "sales") || jobTitles[0] || "engineer";
|
|
47396
47497
|
const podIds = params.podIds;
|
|
47397
47498
|
const now = /* @__PURE__ */ new Date();
|
|
47398
47499
|
const thisWeekStart = getWeekStart(now);
|
|
@@ -47461,7 +47562,7 @@ async function handleTeam(params) {
|
|
|
47461
47562
|
id: u.id,
|
|
47462
47563
|
email: u.email,
|
|
47463
47564
|
fullName: u.full_name,
|
|
47464
|
-
jobTitle: u.job_title || "member",
|
|
47565
|
+
jobTitle: u.job_title || ["member"],
|
|
47465
47566
|
role: u.role,
|
|
47466
47567
|
isActive: u.is_active,
|
|
47467
47568
|
pods: userPods
|
|
@@ -47471,11 +47572,16 @@ async function handleTeam(params) {
|
|
|
47471
47572
|
}
|
|
47472
47573
|
case "update_role": {
|
|
47473
47574
|
const userId = params.userId;
|
|
47474
|
-
const
|
|
47475
|
-
if (!userId || !
|
|
47476
|
-
const
|
|
47575
|
+
const rawTitle = params.jobTitle;
|
|
47576
|
+
if (!userId || !rawTitle) return err("userId and jobTitle are required", "MISSING_PARAM");
|
|
47577
|
+
const titles = Array.isArray(rawTitle) ? rawTitle : [rawTitle];
|
|
47578
|
+
if (titles.length === 0) return err("At least one role is required", "INVALID_PARAM");
|
|
47579
|
+
const valid = ["product_lead", "engineer", "designer", "leadership", "sales", "member"];
|
|
47580
|
+
const invalid = titles.filter((t) => !valid.includes(t));
|
|
47581
|
+
if (invalid.length > 0) return err(`Invalid role(s): ${invalid.join(", ")}`, "INVALID_PARAM");
|
|
47582
|
+
const { error: error2 } = await supabase.from("users").update({ job_title: titles, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", userId);
|
|
47477
47583
|
if (error2) return err(error2.message);
|
|
47478
|
-
return ok({ userId, jobTitle }, `Updated
|
|
47584
|
+
return ok({ userId, jobTitle: titles }, `Updated roles to ${titles.join(", ")}`);
|
|
47479
47585
|
}
|
|
47480
47586
|
case "assign_pod": {
|
|
47481
47587
|
const userId = params.userId;
|
|
@@ -47486,8 +47592,8 @@ async function handleTeam(params) {
|
|
|
47486
47592
|
const { data: user } = await supabase.from("users").select("full_name, job_title").eq("id", userId).single();
|
|
47487
47593
|
const displayName = user?.full_name || "Unknown";
|
|
47488
47594
|
const initials = displayName.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
|
|
47489
|
-
const
|
|
47490
|
-
const podRole =
|
|
47595
|
+
const jobTitles = user?.job_title || ["member"];
|
|
47596
|
+
const podRole = jobTitles.includes("product_lead") ? "product_lead" : jobTitles.includes("designer") ? "designer" : "engineer";
|
|
47491
47597
|
await supabase.from("pod_members").insert({
|
|
47492
47598
|
pod_id: podId,
|
|
47493
47599
|
user_id: userId,
|
|
@@ -47531,6 +47637,7 @@ server.tool(
|
|
|
47531
47637
|
action: external_exports4.enum(["list", "add", "update", "remove"]),
|
|
47532
47638
|
podId: external_exports4.string().describe("Pod ID"),
|
|
47533
47639
|
memberId: external_exports4.string().optional().describe("Member ID (for update/remove)"),
|
|
47640
|
+
userId: external_exports4.string().optional().describe("User ID \u2014 links pod member to a user (for add). Auto-populates name/initials/role from user."),
|
|
47534
47641
|
displayName: external_exports4.string().optional().describe("Display name"),
|
|
47535
47642
|
initials: external_exports4.string().optional().describe("2-letter initials"),
|
|
47536
47643
|
role: external_exports4.string().optional().describe("Role: engineer, product_lead, designer")
|
|
@@ -47539,7 +47646,7 @@ server.tool(
|
|
|
47539
47646
|
);
|
|
47540
47647
|
server.tool(
|
|
47541
47648
|
"pulse_features",
|
|
47542
|
-
"Manage features and tasks. Actions: list (filter by pod/workstream/week/status/size), get, create (feature or task via size param), update (phase/step/status/blocker/week), delete, add_note, reorder.",
|
|
47649
|
+
"Manage features and tasks. Actions: list (filter by pod/workstream/week/status/size), get, create (feature or task via size param), update (phase/step/status/blocker/week/prdText/prdFileUrl), delete, add_note, reorder.",
|
|
47543
47650
|
{
|
|
47544
47651
|
action: external_exports4.enum(["list", "get", "create", "update", "delete", "add_note", "reorder"]),
|
|
47545
47652
|
featureId: external_exports4.string().optional().describe("Feature ID"),
|
|
@@ -47556,6 +47663,9 @@ server.tool(
|
|
|
47556
47663
|
blockerText: external_exports4.string().optional().describe("Blocker description"),
|
|
47557
47664
|
sortOrder: external_exports4.number().optional().describe("Sort order for drag-to-reorder"),
|
|
47558
47665
|
projectColor: external_exports4.string().optional().describe("Project color hex"),
|
|
47666
|
+
prdText: external_exports4.string().optional().describe("PRD text content (for update)"),
|
|
47667
|
+
prdFileUrl: external_exports4.string().optional().describe("PRD file URL (for update)"),
|
|
47668
|
+
prdFileName: external_exports4.string().optional().describe("PRD file name (for update)"),
|
|
47559
47669
|
note: external_exports4.string().optional().describe("Note text (for add_note)"),
|
|
47560
47670
|
items: external_exports4.array(external_exports4.object({ id: external_exports4.string(), sortOrder: external_exports4.number() })).optional().describe("Items to reorder"),
|
|
47561
47671
|
limit: external_exports4.number().optional().describe("Max results")
|
|
@@ -47671,7 +47781,7 @@ server.tool(
|
|
|
47671
47781
|
{
|
|
47672
47782
|
action: external_exports4.enum(["list", "update_role", "assign_pod"]),
|
|
47673
47783
|
userId: external_exports4.string().optional().describe("User ID"),
|
|
47674
|
-
jobTitle: external_exports4.string().optional().describe("Job title: product_lead, engineer, designer, leadership, sales, member"),
|
|
47784
|
+
jobTitle: external_exports4.union([external_exports4.string(), external_exports4.array(external_exports4.string())]).optional().describe("Job title(s): product_lead, engineer, designer, leadership, sales, member. Accepts single string or array."),
|
|
47675
47785
|
podId: external_exports4.string().optional().nullable().describe("Pod ID (null to remove from pods)")
|
|
47676
47786
|
},
|
|
47677
47787
|
async (params) => handleTeam(params)
|
|
@@ -47679,6 +47789,8 @@ server.tool(
|
|
|
47679
47789
|
async function main() {
|
|
47680
47790
|
const transport = new StdioServerTransport();
|
|
47681
47791
|
await server.connect(transport);
|
|
47792
|
+
getCurrentUser().catch(() => {
|
|
47793
|
+
});
|
|
47682
47794
|
console.error("Pulse MCP server running");
|
|
47683
47795
|
}
|
|
47684
47796
|
main().catch((e) => {
|