@vailent/pulse-mcp 1.8.0 → 1.9.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 +82 -12
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -41869,35 +41869,102 @@ async function handleWorkstreams(params) {
41869
41869
  const action = params.action;
41870
41870
  switch (action) {
41871
41871
  case "list": {
41872
- let query = supabase.from("projects").select("*").order("name");
41873
- if (params.podId) query = query.eq("pod_id", params.podId);
41872
+ let query = supabase.from("projects").select("*, project_pods(pod_id, is_primary)").order("name");
41874
41873
  if (params.status) query = query.eq("status", params.status);
41875
41874
  else query = query.eq("status", "active");
41876
41875
  const { data, error: error2 } = await query;
41877
41876
  if (error2) return err(error2.message);
41878
- return ok(data, `${(data || []).length} workstreams`, (data || []).length);
41877
+ let results = data || [];
41878
+ if (params.podId) {
41879
+ results = results.filter((p) => {
41880
+ const pp = p.project_pods || [];
41881
+ return pp.some((r) => r.pod_id === params.podId) || p.pod_id === params.podId;
41882
+ });
41883
+ }
41884
+ return ok(results, `${results.length} workstreams`, results.length);
41879
41885
  }
41880
41886
  case "get": {
41881
41887
  const slug = params.slug;
41882
41888
  if (!slug) return err("slug is required", "MISSING_PARAM");
41883
- const { data, error: error2 } = await supabase.from("projects").select("*").eq("slug", slug).single();
41889
+ const { data, error: error2 } = await supabase.from("projects").select("*, project_pods(pod_id, is_primary)").eq("slug", slug).single();
41884
41890
  if (error2 || !data) return err("Workstream not found", "NOT_FOUND");
41885
41891
  return ok(data, `Workstream: ${data.name}`);
41886
41892
  }
41887
41893
  case "create": {
41888
41894
  const name = params.name;
41895
+ const podId = params.podId;
41896
+ const mode = params.mode;
41889
41897
  if (!name?.trim()) return err("name is required", "MISSING_PARAM");
41890
- const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41898
+ const trimmedName = name.trim();
41899
+ const { data: existing } = await supabase.from("projects").select("id, name, slug, project_pods(pod_id)").eq("name", trimmedName).maybeSingle();
41900
+ if (existing && !mode) {
41901
+ const existingPodIds = existing.project_pods?.map((r) => r.pod_id) || [];
41902
+ if (podId && existingPodIds.includes(podId)) {
41903
+ return ok(existing, `Workstream "${trimmedName}" already linked to this pod`);
41904
+ }
41905
+ return ok(
41906
+ { conflict: true, existingProject: { id: existing.id, name: existing.name, slug: existing.slug, podIds: existingPodIds } },
41907
+ `Workstream "${trimmedName}" already exists in another pod. Use mode: "link" to share or mode: "create_new" to create a separate one.`
41908
+ );
41909
+ }
41910
+ if (existing && mode === "link") {
41911
+ if (!podId) return err("podId is required to link", "MISSING_PARAM");
41912
+ await supabase.from("project_pods").insert({ project_id: existing.id, pod_id: podId, is_primary: false });
41913
+ return ok(existing, `Linked "${trimmedName}" to pod`);
41914
+ }
41915
+ if (existing && mode === "create_new") {
41916
+ const { data: pod } = podId ? await supabase.from("pods").select("name").eq("id", podId).single() : { data: null };
41917
+ const suffix = pod?.name || podId || "";
41918
+ const newName = suffix ? `${trimmedName} (${suffix})` : trimmedName;
41919
+ const newSlug = newName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41920
+ const { data: data2, error: error3 } = await supabase.from("projects").insert({
41921
+ name: newName,
41922
+ slug: newSlug,
41923
+ description: params.description || null,
41924
+ pod_id: podId || null,
41925
+ color: params.color || "#71717a",
41926
+ status: "active"
41927
+ }).select().single();
41928
+ if (error3) return err(error3.message);
41929
+ if (podId) await supabase.from("project_pods").insert({ project_id: data2.id, pod_id: podId, is_primary: true });
41930
+ return ok(data2, `Created separate workstream: ${newName}`);
41931
+ }
41932
+ const slug = trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
41891
41933
  const { data, error: error2 } = await supabase.from("projects").insert({
41892
- name: name.trim(),
41934
+ name: trimmedName,
41893
41935
  slug,
41894
41936
  description: params.description || null,
41895
- pod_id: params.podId || null,
41937
+ pod_id: podId || null,
41896
41938
  color: params.color || "#71717a",
41897
41939
  status: "active"
41898
41940
  }).select().single();
41899
41941
  if (error2) return err(error2.message);
41900
- return ok(data, `Created workstream: ${name}`);
41942
+ if (podId) await supabase.from("project_pods").insert({ project_id: data.id, pod_id: podId, is_primary: true });
41943
+ return ok(data, `Created workstream: ${trimmedName}`);
41944
+ }
41945
+ case "link": {
41946
+ const slug = params.slug;
41947
+ const podId = params.podId;
41948
+ if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
41949
+ const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
41950
+ if (!project) return err("Workstream not found", "NOT_FOUND");
41951
+ const { error: error2 } = await supabase.from("project_pods").insert({
41952
+ project_id: project.id,
41953
+ pod_id: podId,
41954
+ is_primary: false
41955
+ });
41956
+ if (error2) return err(error2.message);
41957
+ return ok({ projectId: project.id, podId }, `Linked "${project.name}" to pod`);
41958
+ }
41959
+ case "unlink": {
41960
+ const slug = params.slug;
41961
+ const podId = params.podId;
41962
+ if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
41963
+ const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
41964
+ if (!project) return err("Workstream not found", "NOT_FOUND");
41965
+ const { error: error2 } = await supabase.from("project_pods").delete().eq("project_id", project.id).eq("pod_id", podId);
41966
+ if (error2) return err(error2.message);
41967
+ return ok({ projectId: project.id, podId }, `Unlinked "${project.name}" from pod`);
41901
41968
  }
41902
41969
  case "update": {
41903
41970
  const slug = params.slug;
@@ -41954,6 +42021,7 @@ async function handleRequests(params) {
41954
42021
  const title = params.title;
41955
42022
  if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
41956
42023
  const { data, error: error2 } = await supabase.from("work_items").insert({
42024
+ id: `f-${Date.now().toString(36)}`,
41957
42025
  title: title.trim(),
41958
42026
  description: params.description || null,
41959
42027
  pod_id: podId,
@@ -42033,6 +42101,7 @@ async function handleBugs(params) {
42033
42101
  const title = params.title;
42034
42102
  if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
42035
42103
  const { data, error: error2 } = await supabase.from("work_items").insert({
42104
+ id: `f-${Date.now().toString(36)}`,
42036
42105
  title: title.trim(),
42037
42106
  description: params.description || null,
42038
42107
  pod_id: podId,
@@ -47397,15 +47466,16 @@ server.tool(
47397
47466
  );
47398
47467
  server.tool(
47399
47468
  "pulse_workstreams",
47400
- "Manage workstreams (project containers). Actions: list, get (by slug), create, update, delete, get_suggestions (AI-generated improvement ideas).",
47469
+ "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.",
47401
47470
  {
47402
- action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions"]),
47471
+ action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions", "link", "unlink"]),
47403
47472
  slug: external_exports.string().optional().describe("Workstream slug"),
47404
- podId: external_exports.string().optional().describe("Pod ID filter"),
47473
+ podId: external_exports.string().optional().describe("Pod ID filter / target pod for link/unlink"),
47405
47474
  name: external_exports.string().optional().describe("Workstream name"),
47406
47475
  description: external_exports.string().optional().describe("Description"),
47407
47476
  color: external_exports.string().optional().describe("Hex color"),
47408
- status: external_exports.string().optional().describe("Status: active, archived, completed")
47477
+ status: external_exports.string().optional().describe("Status: active, archived, completed"),
47478
+ mode: external_exports.enum(["link", "create_new"]).optional().describe("When creating with a conflicting name: 'link' to share or 'create_new' for separate")
47409
47479
  },
47410
47480
  async (params) => handleWorkstreams(params)
47411
47481
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vailent/pulse-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Pulse MCP server — manage pods, features, workstreams, bugs, and more from Claude Code",
5
5
  "type": "module",
6
6
  "bin": {