@vailent/pulse-mcp 1.8.1 → 1.10.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.
Files changed (2) hide show
  1. package/dist/server.js +180 -13
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -39920,7 +39920,9 @@ async function handlePods(params) {
39920
39920
  if (params.name !== void 0) updates.name = params.name.trim();
39921
39921
  if (params.color !== void 0) updates.color = params.color;
39922
39922
  if (params.isActive !== void 0) updates.is_active = params.isActive;
39923
- if (params.slackChannelId !== void 0) updates.slack_channel_id = params.slackChannelId || null;
39923
+ if (params.slackChannelId !== void 0) {
39924
+ updates.slack_channel_ids = params.slackChannelId ? [params.slackChannelId] : [];
39925
+ }
39924
39926
  const { data, error: error2 } = await supabase.from("pods").update(updates).eq("id", podId).select().single();
39925
39927
  if (error2) return err(error2.message);
39926
39928
  return ok(data, `Updated pod: ${data?.name}`);
@@ -41869,35 +41871,102 @@ async function handleWorkstreams(params) {
41869
41871
  const action = params.action;
41870
41872
  switch (action) {
41871
41873
  case "list": {
41872
- let query = supabase.from("projects").select("*").order("name");
41873
- if (params.podId) query = query.eq("pod_id", params.podId);
41874
+ let query = supabase.from("projects").select("*, project_pods(pod_id, is_primary)").order("name");
41874
41875
  if (params.status) query = query.eq("status", params.status);
41875
41876
  else query = query.eq("status", "active");
41876
41877
  const { data, error: error2 } = await query;
41877
41878
  if (error2) return err(error2.message);
41878
- return ok(data, `${(data || []).length} workstreams`, (data || []).length);
41879
+ let results = data || [];
41880
+ if (params.podId) {
41881
+ results = results.filter((p) => {
41882
+ const pp = p.project_pods || [];
41883
+ return pp.some((r) => r.pod_id === params.podId) || p.pod_id === params.podId;
41884
+ });
41885
+ }
41886
+ return ok(results, `${results.length} workstreams`, results.length);
41879
41887
  }
41880
41888
  case "get": {
41881
41889
  const slug = params.slug;
41882
41890
  if (!slug) return err("slug is required", "MISSING_PARAM");
41883
- const { data, error: error2 } = await supabase.from("projects").select("*").eq("slug", slug).single();
41891
+ const { data, error: error2 } = await supabase.from("projects").select("*, project_pods(pod_id, is_primary)").eq("slug", slug).single();
41884
41892
  if (error2 || !data) return err("Workstream not found", "NOT_FOUND");
41885
41893
  return ok(data, `Workstream: ${data.name}`);
41886
41894
  }
41887
41895
  case "create": {
41888
41896
  const name = params.name;
41897
+ const podId = params.podId;
41898
+ const mode = params.mode;
41889
41899
  if (!name?.trim()) return err("name is required", "MISSING_PARAM");
41890
- const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41900
+ const trimmedName = name.trim();
41901
+ const { data: existing } = await supabase.from("projects").select("id, name, slug, project_pods(pod_id)").eq("name", trimmedName).maybeSingle();
41902
+ if (existing && !mode) {
41903
+ const existingPodIds = existing.project_pods?.map((r) => r.pod_id) || [];
41904
+ if (podId && existingPodIds.includes(podId)) {
41905
+ return ok(existing, `Workstream "${trimmedName}" already linked to this pod`);
41906
+ }
41907
+ return ok(
41908
+ { conflict: true, existingProject: { id: existing.id, name: existing.name, slug: existing.slug, podIds: existingPodIds } },
41909
+ `Workstream "${trimmedName}" already exists in another pod. Use mode: "link" to share or mode: "create_new" to create a separate one.`
41910
+ );
41911
+ }
41912
+ if (existing && mode === "link") {
41913
+ if (!podId) return err("podId is required to link", "MISSING_PARAM");
41914
+ await supabase.from("project_pods").insert({ project_id: existing.id, pod_id: podId, is_primary: false });
41915
+ return ok(existing, `Linked "${trimmedName}" to pod`);
41916
+ }
41917
+ if (existing && mode === "create_new") {
41918
+ const { data: pod } = podId ? await supabase.from("pods").select("name").eq("id", podId).single() : { data: null };
41919
+ const suffix = pod?.name || podId || "";
41920
+ const newName = suffix ? `${trimmedName} (${suffix})` : trimmedName;
41921
+ const newSlug = newName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41922
+ const { data: data2, error: error3 } = await supabase.from("projects").insert({
41923
+ name: newName,
41924
+ slug: newSlug,
41925
+ description: params.description || null,
41926
+ pod_id: podId || null,
41927
+ color: params.color || "#71717a",
41928
+ status: "active"
41929
+ }).select().single();
41930
+ if (error3) return err(error3.message);
41931
+ if (podId) await supabase.from("project_pods").insert({ project_id: data2.id, pod_id: podId, is_primary: true });
41932
+ return ok(data2, `Created separate workstream: ${newName}`);
41933
+ }
41934
+ const slug = trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41891
41935
  const { data, error: error2 } = await supabase.from("projects").insert({
41892
- name: name.trim(),
41936
+ name: trimmedName,
41893
41937
  slug,
41894
41938
  description: params.description || null,
41895
- pod_id: params.podId || null,
41939
+ pod_id: podId || null,
41896
41940
  color: params.color || "#71717a",
41897
41941
  status: "active"
41898
41942
  }).select().single();
41899
41943
  if (error2) return err(error2.message);
41900
- return ok(data, `Created workstream: ${name}`);
41944
+ if (podId) await supabase.from("project_pods").insert({ project_id: data.id, pod_id: podId, is_primary: true });
41945
+ return ok(data, `Created workstream: ${trimmedName}`);
41946
+ }
41947
+ case "link": {
41948
+ const slug = params.slug;
41949
+ const podId = params.podId;
41950
+ if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
41951
+ const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
41952
+ if (!project) return err("Workstream not found", "NOT_FOUND");
41953
+ const { error: error2 } = await supabase.from("project_pods").insert({
41954
+ project_id: project.id,
41955
+ pod_id: podId,
41956
+ is_primary: false
41957
+ });
41958
+ if (error2) return err(error2.message);
41959
+ return ok({ projectId: project.id, podId }, `Linked "${project.name}" to pod`);
41960
+ }
41961
+ case "unlink": {
41962
+ const slug = params.slug;
41963
+ const podId = params.podId;
41964
+ if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
41965
+ const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
41966
+ if (!project) return err("Workstream not found", "NOT_FOUND");
41967
+ const { error: error2 } = await supabase.from("project_pods").delete().eq("project_id", project.id).eq("pod_id", podId);
41968
+ if (error2) return err(error2.message);
41969
+ return ok({ projectId: project.id, podId }, `Unlinked "${project.name}" from pod`);
41901
41970
  }
41902
41971
  case "update": {
41903
41972
  const slug = params.slug;
@@ -47319,6 +47388,87 @@ async function handleSearch(params) {
47319
47388
  );
47320
47389
  }
47321
47390
 
47391
+ // tools/milestones.ts
47392
+ async function handleMilestones(params) {
47393
+ const supabase = getAdminClient();
47394
+ const action = params.action;
47395
+ switch (action) {
47396
+ case "list": {
47397
+ const projectId = params.projectId;
47398
+ const slug = params.slug;
47399
+ let query = supabase.from("milestones").select("*").order("sort_order").order("target_date");
47400
+ if (projectId) {
47401
+ query = query.eq("project_id", projectId);
47402
+ } else if (slug) {
47403
+ const { data: project } = await supabase.from("projects").select("id").eq("slug", slug).single();
47404
+ if (!project) return err("Workstream not found", "NOT_FOUND");
47405
+ query = query.eq("project_id", project.id);
47406
+ } else {
47407
+ return err("projectId or slug is required", "MISSING_PARAM");
47408
+ }
47409
+ const { data, error: error2 } = await query;
47410
+ if (error2) return err(error2.message);
47411
+ return ok(data, `${(data || []).length} milestones`, (data || []).length);
47412
+ }
47413
+ case "create": {
47414
+ const title = params.title;
47415
+ if (!title?.trim()) return err("title is required", "MISSING_PARAM");
47416
+ let projectId = params.projectId;
47417
+ if (!projectId && params.slug) {
47418
+ const { data: project } = await supabase.from("projects").select("id").eq("slug", params.slug).single();
47419
+ if (!project) return err("Workstream not found", "NOT_FOUND");
47420
+ projectId = project.id;
47421
+ }
47422
+ if (!projectId) return err("projectId or slug is required", "MISSING_PARAM");
47423
+ const { data, error: error2 } = await supabase.from("milestones").insert({
47424
+ project_id: projectId,
47425
+ title: title.trim(),
47426
+ description: params.description || null,
47427
+ target_date: params.targetDate || null,
47428
+ status: "pending"
47429
+ }).select().single();
47430
+ if (error2) return err(error2.message);
47431
+ return ok(data, `Created milestone: ${title}`);
47432
+ }
47433
+ case "update": {
47434
+ const milestoneId = params.milestoneId;
47435
+ if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
47436
+ const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
47437
+ if (params.title !== void 0) updates.title = params.title;
47438
+ if (params.description !== void 0) updates.description = params.description;
47439
+ if (params.targetDate !== void 0) updates.target_date = params.targetDate;
47440
+ if (params.status !== void 0) {
47441
+ updates.status = params.status;
47442
+ updates.completed_at = params.status === "completed" ? (/* @__PURE__ */ new Date()).toISOString() : null;
47443
+ }
47444
+ if (params.sortOrder !== void 0) updates.sort_order = params.sortOrder;
47445
+ const { error: error2 } = await supabase.from("milestones").update(updates).eq("id", milestoneId);
47446
+ if (error2) return err(error2.message);
47447
+ return ok({ id: milestoneId }, "Milestone updated");
47448
+ }
47449
+ case "complete": {
47450
+ const milestoneId = params.milestoneId;
47451
+ if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
47452
+ const { error: error2 } = await supabase.from("milestones").update({
47453
+ status: "completed",
47454
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
47455
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
47456
+ }).eq("id", milestoneId);
47457
+ if (error2) return err(error2.message);
47458
+ return ok({ id: milestoneId }, "Milestone completed");
47459
+ }
47460
+ case "delete": {
47461
+ const milestoneId = params.milestoneId;
47462
+ if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
47463
+ const { error: error2 } = await supabase.from("milestones").delete().eq("id", milestoneId);
47464
+ if (error2) return err(error2.message);
47465
+ return ok({ deleted: milestoneId }, "Milestone deleted");
47466
+ }
47467
+ default:
47468
+ return err(`Unknown action: ${action}`, "INVALID_ACTION");
47469
+ }
47470
+ }
47471
+
47322
47472
  // server.ts
47323
47473
  try {
47324
47474
  await import("./config-V5TD57MJ.js");
@@ -47399,18 +47549,35 @@ server.tool(
47399
47549
  );
47400
47550
  server.tool(
47401
47551
  "pulse_workstreams",
47402
- "Manage workstreams (project containers). Actions: list, get (by slug), create, update, delete, get_suggestions (AI-generated improvement ideas).",
47552
+ "Manage workstreams (project containers). Actions: list, get (by slug), create (detects name conflicts \u2014 use mode 'link' to share or 'create_new' for separate), link (add pod to existing workstream), unlink (remove pod from workstream), update, delete, get_suggestions.",
47403
47553
  {
47404
- action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions"]),
47554
+ action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions", "link", "unlink"]),
47405
47555
  slug: external_exports.string().optional().describe("Workstream slug"),
47406
- podId: external_exports.string().optional().describe("Pod ID filter"),
47556
+ podId: external_exports.string().optional().describe("Pod ID filter / target pod for link/unlink"),
47407
47557
  name: external_exports.string().optional().describe("Workstream name"),
47408
47558
  description: external_exports.string().optional().describe("Description"),
47409
47559
  color: external_exports.string().optional().describe("Hex color"),
47410
- status: external_exports.string().optional().describe("Status: active, archived, completed")
47560
+ status: external_exports.string().optional().describe("Status: active, archived, completed"),
47561
+ mode: external_exports.enum(["link", "create_new"]).optional().describe("When creating with a conflicting name: 'link' to share or 'create_new' for separate")
47411
47562
  },
47412
47563
  async (params) => handleWorkstreams(params)
47413
47564
  );
47565
+ server.tool(
47566
+ "pulse_milestones",
47567
+ "Manage workstream milestones (key dates/goals). Actions: list (by projectId or slug), create, update, complete, delete.",
47568
+ {
47569
+ action: external_exports.enum(["list", "create", "update", "complete", "delete"]),
47570
+ projectId: external_exports.string().optional().describe("Project UUID"),
47571
+ slug: external_exports.string().optional().describe("Workstream slug (alternative to projectId)"),
47572
+ milestoneId: external_exports.string().optional().describe("Milestone ID (for update/complete/delete)"),
47573
+ title: external_exports.string().optional().describe("Milestone title"),
47574
+ description: external_exports.string().optional().describe("Description"),
47575
+ targetDate: external_exports.string().optional().describe("Target date (YYYY-MM-DD)"),
47576
+ status: external_exports.string().optional().describe("Status: pending, completed"),
47577
+ sortOrder: external_exports.number().optional().describe("Sort order")
47578
+ },
47579
+ async (params) => handleMilestones(params)
47580
+ );
47414
47581
  server.tool(
47415
47582
  "pulse_requests",
47416
47583
  "Manage feature requests and feedback. Actions: list (by pod), create, triage (accept/decline/defer/convert/acknowledge).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vailent/pulse-mcp",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "Pulse MCP server — manage pods, features, workstreams, bugs, and more from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {