@pbhamri/quartermaster-mcp 0.6.0 → 0.7.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/bin/server.js +131 -0
  2. package/package.json +8 -2
package/bin/server.js CHANGED
@@ -764,6 +764,134 @@ function qmAutoreplyDrafts({ limit = 10 } = {}) {
764
764
  }
765
765
 
766
766
 
767
+ // ---------------- v0.6.1 adoption tracking ----------------
768
+ // Opt-in: peers call qm_report_adoption to send anonymous usage summary
769
+ // to the Quartermaster repo as a GitHub issue. No PII — only machine hash,
770
+ // event counts, and version. Requires `gh` CLI on PATH.
771
+
772
+ const ADOPTION_PING_FILE = path.join(os.homedir(), ".quartermaster", ".adoption-ping");
773
+
774
+ function hasReportedThisVersion() {
775
+ try {
776
+ if (!exists(ADOPTION_PING_FILE)) return false;
777
+ const data = readJson(ADOPTION_PING_FILE);
778
+ return data.version === PKG.version;
779
+ } catch { return false; }
780
+ }
781
+
782
+ function markReported() {
783
+ writeText(ADOPTION_PING_FILE, JSON.stringify({
784
+ version: PKG.version, machine: machineId(), ts: new Date().toISOString(),
785
+ }));
786
+ }
787
+
788
+ function qmReportAdoption({ dryRun = false } = {}) {
789
+ // Collect anonymous local stats
790
+ const machine = machineId();
791
+ const stats = { machine, version: PKG.version, os: os.platform(), arch: os.arch() };
792
+
793
+ // Read local telemetry
794
+ if (exists(METRICS_FILE)) {
795
+ const lines = fs.readFileSync(METRICS_FILE, "utf8").split("\n").filter(Boolean);
796
+ const mcpEvents = [];
797
+ for (const l of lines) { try { const e = JSON.parse(l); if (e.source === "quartermaster-mcp") mcpEvents.push(e); } catch {} }
798
+ const typeCounts = {};
799
+ for (const e of mcpEvents) typeCounts[e.type] = (typeCounts[e.type] || 0) + 1;
800
+ stats.total_events = mcpEvents.length;
801
+ stats.event_types = typeCounts;
802
+ stats.first_event = mcpEvents[0]?.ts || null;
803
+ stats.last_event = mcpEvents[mcpEvents.length - 1]?.ts || null;
804
+ } else {
805
+ stats.total_events = 0;
806
+ stats.event_types = {};
807
+ }
808
+
809
+ // Check what profile is active
810
+ const profilePath = path.join(os.homedir(), ".copilot", "paved-path", "profile.json");
811
+ if (exists(profilePath)) {
812
+ try {
813
+ const p = readJson(profilePath);
814
+ stats.active_profile = p.profile?.id || "unknown";
815
+ } catch { stats.active_profile = "unreadable"; }
816
+ } else {
817
+ stats.active_profile = "none";
818
+ }
819
+
820
+ // Check connect-db exists
821
+ stats.has_connect_db = exists(QM_CONNECT_DB);
822
+
823
+ // Already reported this version?
824
+ stats.already_reported = hasReportedThisVersion();
825
+
826
+ if (dryRun) {
827
+ return { mode: "dry-run", stats, note: "Call with dryRun=false to submit." };
828
+ }
829
+
830
+ // Submit as a GitHub issue (anonymous — titled by machine hash only)
831
+ let ghAvailable = false;
832
+ try { execFileSync("gh", ["--version"], { stdio: "pipe" }); ghAvailable = true; } catch {}
833
+
834
+ if (!ghAvailable) {
835
+ return {
836
+ mode: "manual",
837
+ stats,
838
+ note: "gh CLI not found. Share this JSON with the Quartermaster maintainer manually or install gh CLI.",
839
+ };
840
+ }
841
+
842
+ const title = `[adoption] ${machine} v${PKG.version}`;
843
+ const body = [
844
+ "## Anonymous adoption ping",
845
+ "",
846
+ "| Field | Value |",
847
+ "|---|---|",
848
+ `| Machine hash | \`${machine}\` |`,
849
+ `| Version | ${PKG.version} |`,
850
+ `| OS | ${stats.os}/${stats.arch} |`,
851
+ `| Active profile | ${stats.active_profile} |`,
852
+ `| Has connect-db | ${stats.has_connect_db} |`,
853
+ `| Total MCP events | ${stats.total_events} |`,
854
+ `| First event | ${stats.first_event || "n/a"} |`,
855
+ `| Last event | ${stats.last_event || "n/a"} |`,
856
+ "",
857
+ "### Event breakdown",
858
+ "",
859
+ "| Type | Count |",
860
+ "|---|---|",
861
+ ...Object.entries(stats.event_types).sort((a,b) => b[1] - a[1]).map(([t,c]) => `| ${t} | ${c} |`),
862
+ "",
863
+ "> Auto-generated by `qm_report_adoption`. No PII — machine hash is SHA-256 of platform:arch:cpus:ram.",
864
+ ].join("\n");
865
+
866
+ try {
867
+ const issueUrl = execFileSync("gh", [
868
+ "issue", "create",
869
+ "--repo", "pbhamri_microsoft/Quartermaster",
870
+ "--title", title,
871
+ "--body", body,
872
+ "--label", "adoption-ping",
873
+ ], { stdio: "pipe" }).toString().trim();
874
+
875
+ markReported();
876
+ emit("qm.report_adoption", { machine });
877
+
878
+ return {
879
+ mode: "submitted",
880
+ issue_url: issueUrl,
881
+ stats,
882
+ note: "Adoption ping submitted as a GitHub issue. Thank you!",
883
+ };
884
+ } catch (e) {
885
+ return {
886
+ mode: "error",
887
+ error: e.message,
888
+ stats,
889
+ note: "Could not create issue. You may not have access to the repo. Share the stats JSON manually.",
890
+ };
891
+ }
892
+ }
893
+
894
+
767
895
  // ---------------- MCP wiring ----------------
768
896
  const server = new Server(
769
897
  { name: "quartermaster-mcp", version: PKG.version },
@@ -833,6 +961,8 @@ const TOOLS = [
833
961
  inputSchema: { type: "object", properties: {} } },
834
962
  { name: "qm_autoreply_drafts", description: "List the most recent auto-reply drafts written under ~/.copilot/autoreply/drafts (with tier, violations, inbound text).",
835
963
  inputSchema: { type: "object", properties: { limit: { type: "integer", default: 10 } } } },
964
+ { name: "qm_report_adoption", description: "Opt-in: send an anonymous adoption ping (machine hash + event counts + version) to the Quartermaster repo as a GitHub issue. No PII. Supports dryRun=true to preview before submitting.",
965
+ inputSchema: { type: "object", properties: { dryRun: { type: "boolean", default: false, description: "Preview stats without submitting" } } } },
836
966
  ];
837
967
 
838
968
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
@@ -862,6 +992,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
862
992
  case "qm_autoreply_set_presence": result = await timed("qm.autoreply_set_presence", { state: args.state }, async () => qmAutoreplySetPresence(args)); break;
863
993
  case "qm_autoreply_failure_modes": result = qmAutoreplyFailureModes(); break;
864
994
  case "qm_autoreply_drafts": result = qmAutoreplyDrafts(args); break;
995
+ case "qm_report_adoption": result = qmReportAdoption(args); break;
865
996
  default: throw new Error(`unknown tool: ${name}`);
866
997
  }
867
998
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pbhamri/quartermaster-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "MCP server that seeds any repo with a customizable PM kit. Any PM, any product — connect YOUR GitHub, ADO, Kusto, IcM. 19 tools: scaffolding, repo-read, onboarding, custom profile creation, self-agent auto-reply. Never ships personal data — profiles are templates you fill in.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,13 @@
26
26
  "engines": {
27
27
  "node": ">=18"
28
28
  },
29
- "keywords": ["mcp", "pm", "purview", "quartermaster", "copilot"],
29
+ "keywords": [
30
+ "mcp",
31
+ "pm",
32
+ "purview",
33
+ "quartermaster",
34
+ "copilot"
35
+ ],
30
36
  "author": "pbhamri",
31
37
  "license": "MIT"
32
38
  }