@pbhamri/quartermaster-mcp 0.8.0 → 0.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 (3) hide show
  1. package/README.md +14 -1
  2. package/bin/server.js +55 -0
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -211,9 +211,22 @@ resources/
211
211
  | `QM_REPO_ROOT` | `process.cwd()` | Override repo root for read-only access |
212
212
  | `QM_CONNECT_DB` | `~/.quartermaster/connect-db.json` | Connect tracker path for `qm_personalize` |
213
213
  | `QM_METRICS_FILE` | `~/.copilot/metrics/paved-path-events.jsonl` | Telemetry output path |
214
- | `QM_METRICS_OPT_OUT` | (unset) | Set to `1` to disable telemetry |
214
+ | `QM_METRICS_OPT_OUT` | (unset) | Set to `1` to disable **all** telemetry (local file + beacon) |
215
+ | `QM_BEACON_URL` | (from package) | Override the usage-beacon endpoint |
216
+ | `QM_BEACON_OPT_OUT` | (unset) | Set to `1` to disable only the usage beacon |
215
217
  | `QM_SELF_AGENT_DIR` | `~/.copilot/self-agent` | Self-agent skeleton directory |
216
218
 
219
+ ### Telemetry & privacy
220
+
221
+ This package emits **one anonymized usage ping per machine per UTC day** so the
222
+ maintainer can count distinct PMs using it. The ping contains only a SHA-256
223
+ **machine hash** (`platform:arch:cpus:ram`) plus version, OS, and date —
224
+ **never** usernames, hostnames, IPs, file paths, repo names, or content.
225
+
226
+ Opt out any time with `QM_BEACON_OPT_OUT=1` (beacon only) or
227
+ `QM_METRICS_OPT_OUT=1` (everything). The beacon server is open-source in
228
+ [`beacon/`](beacon/README.md).
229
+
217
230
  ---
218
231
 
219
232
  ## Why MCP, not a script
package/bin/server.js CHANGED
@@ -62,6 +62,60 @@ async function timed(type, baseData, fn) {
62
62
  }
63
63
  }
64
64
 
65
+ // ---- First-run-of-day usage beacon (privacy-preserving, opt-out) ----
66
+ // Sends ONE anonymized ping per machine per UTC day so the maintainer can
67
+ // count DISTINCT PMs who ran `npx @pbhamri/quartermaster-mcp`. Payload is
68
+ // machine hash (already non-PII: SHA-256 of platform:arch:cpus:ram) + version
69
+ // + OS + date. NO usernames, hostnames, paths, repo names, or content.
70
+ // Disable entirely with QM_BEACON_OPT_OUT=1 or QM_METRICS_OPT_OUT=1.
71
+ const BEACON_URL = process.env.QM_BEACON_URL || PKG.beaconUrl || "";
72
+ const BEACON_STATE_FILE = process.env.QM_BEACON_STATE
73
+ || path.join(os.homedir(), ".quartermaster", "beacon-state.json");
74
+
75
+ function beaconOptedOut() {
76
+ return process.env.QM_BEACON_OPT_OUT === "1" || process.env.QM_METRICS_OPT_OUT === "1";
77
+ }
78
+
79
+ function alreadyBeaconedToday(today) {
80
+ try {
81
+ const s = JSON.parse(fs.readFileSync(BEACON_STATE_FILE, "utf8"));
82
+ return s.last_beacon_date === today;
83
+ } catch { return false; }
84
+ }
85
+
86
+ function markBeaconed(today) {
87
+ try {
88
+ fs.mkdirSync(path.dirname(BEACON_STATE_FILE), { recursive: true });
89
+ fs.writeFileSync(BEACON_STATE_FILE, JSON.stringify({ last_beacon_date: today, version: PKG.version }), "utf8");
90
+ } catch { /* non-fatal */ }
91
+ }
92
+
93
+ // Fire-and-forget. Never throws, never blocks server startup.
94
+ function sendBeacon() {
95
+ try {
96
+ if (beaconOptedOut() || !BEACON_URL) return;
97
+ if (typeof fetch !== "function") return; // Node < 18: skip silently
98
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD (UTC)
99
+ if (alreadyBeaconedToday(today)) return;
100
+ markBeaconed(today); // mark first so a slow/failed POST never double-sends
101
+ const payload = {
102
+ machine: machineId(),
103
+ version: PKG.version,
104
+ os: os.platform(),
105
+ arch: os.arch(),
106
+ date: today,
107
+ };
108
+ const ctrl = new AbortController();
109
+ const timer = setTimeout(() => ctrl.abort(), 1500);
110
+ fetch(BEACON_URL, {
111
+ method: "POST",
112
+ headers: { "content-type": "application/json" },
113
+ body: JSON.stringify(payload),
114
+ signal: ctrl.signal,
115
+ }).catch(() => {}).finally(() => clearTimeout(timer));
116
+ } catch { /* beacon must never break the server */ }
117
+ }
118
+
65
119
  const readJson = (p) => JSON.parse(fs.readFileSync(p, "utf8"));
66
120
  const writeText = (p, t) => { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, t, "utf8"); };
67
121
  const exists = (p) => fs.existsSync(p);
@@ -1089,4 +1143,5 @@ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
1089
1143
 
1090
1144
  await server.connect(new StdioServerTransport());
1091
1145
  process.stderr.write(`quartermaster-mcp v${PKG.version} ready (stdio)\n`);
1146
+ sendBeacon(); // once/day anonymized usage ping; opt out via QM_BEACON_OPT_OUT=1
1092
1147
 
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@pbhamri/quartermaster-mcp",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
+ "beaconUrl": "",
4
5
  "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
6
  "type": "module",
6
7
  "bin": {