@massa-ai/cursor-plugin 1.7.1 → 1.8.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "massa-ai",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "massa-ai — semantic code search, memory, and context compression for Cursor"
5
5
  }
@@ -29,6 +29,7 @@
29
29
 
30
30
  import { spawnSync } from "child_process";
31
31
  import { readFileSync, writeFileSync, mkdirSync, existsSync, fstatSync } from "fs";
32
+ import { homedir } from "os";
32
33
  import path from "path";
33
34
 
34
35
  // ── Event type mapping ──────────────────────────────────────────────────────
@@ -132,6 +133,60 @@ export function readStdin(): string {
132
133
  }
133
134
  }
134
135
 
136
+ // ── API key resolution (SEC-06) ─────────────────────────────────────────────
137
+
138
+ /**
139
+ * Path to the runtime config file, resolved the same way
140
+ * `packages/shared/src/config/xdg.ts` resolves it: `XDG_CONFIG_HOME` when set
141
+ * and non-blank, else `~/.config`.
142
+ *
143
+ * Duplicated rather than imported on purpose. This binary's entire dependency
144
+ * surface is `child_process`/`fs`/`os`/`path`; pulling in `@massa-ai/shared`
145
+ * would drag the config loader, its Prisma-adjacent transitive graph, and its
146
+ * startup cost into a process that runs on every single tool call and is
147
+ * budgeted in milliseconds. The duplication is nine lines and is pinned by
148
+ * tests on both sides.
149
+ */
150
+ export function getHookConfigPath(): string {
151
+ const xdg = process.env.XDG_CONFIG_HOME;
152
+ const base = xdg && xdg.trim() ? xdg : path.join(homedir(), ".config");
153
+ return path.join(base, "massa-ai", "config.json");
154
+ }
155
+
156
+ /**
157
+ * Resolve the Tools API key: `MASSA_AI_API_KEY` → `config.json`
158
+ * `security.apiKey` → none. Returns "" when no key is available.
159
+ *
160
+ * SEC-01 auto-provisions the key into config.json on first API start, and this
161
+ * binary never runs the `env.ts` seeding that puts it in the environment for
162
+ * everything else — so without reading the file it would send unauthenticated
163
+ * POSTs forever. Because the hook silent-degrades by contract, that failure
164
+ * has no symptom: observation capture just stops.
165
+ *
166
+ * Whitespace-only values count as unset, matching `usable()` in
167
+ * `packages/shared/src/config/api-key.ts`. Every failure mode (missing file,
168
+ * unreadable, malformed JSON, wrong shape) degrades to "" rather than throwing:
169
+ * a hook must never block the agent.
170
+ *
171
+ * Deliberately not memoised. A hook process handles exactly one event and
172
+ * exits, so there is no second call to amortise, and a module-level cache would
173
+ * make the resolution untestable without a reset seam.
174
+ */
175
+ export function resolveHookApiKey(): string {
176
+ const fromEnv = process.env.MASSA_AI_API_KEY;
177
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim();
178
+
179
+ try {
180
+ const parsed = JSON.parse(readFileSync(getHookConfigPath(), "utf8"));
181
+ const stored = parsed?.security?.apiKey;
182
+ if (typeof stored === "string" && stored.trim()) return stored.trim();
183
+ } catch {
184
+ // Missing, unreadable, or malformed config → no key (silent-degrade).
185
+ }
186
+
187
+ return "";
188
+ }
189
+
135
190
  // ── POST helper ─────────────────────────────────────────────────────────────
136
191
 
137
192
  export function postObservation(
@@ -149,7 +204,7 @@ export function postObservation(
149
204
  const headers: Record<string, string> = {
150
205
  "Content-Type": "application/json",
151
206
  };
152
- const apiKey = process.env.MASSA_AI_API_KEY;
207
+ const apiKey = resolveHookApiKey();
153
208
  if (apiKey) {
154
209
  headers["x-api-key"] = apiKey;
155
210
  }
package/install.sh CHANGED
@@ -57,7 +57,18 @@ if [[ "$SCOPE" == "project" ]]; then
57
57
  else
58
58
  CURSOR_DIR="$HOME/.cursor"
59
59
  fi
60
- PLUGIN_DIR="$CURSOR_DIR/plugins/massa-ai"
60
+ # Cursor discovers locally-installed plugins under plugins/local/<name>/, not
61
+ # plugins/<name>/. Installing to the latter is why massa-ai never appeared in
62
+ # Cursor's plugin list even though every file was written correctly.
63
+ #
64
+ # UNVERIFIED against a running Cursor.app — this path comes from Cursor's
65
+ # plugin documentation, not from an observed load, because Cursor is not
66
+ # installed on the machine this was developed on. Treat it as lower confidence
67
+ # than the Claude/Codex routes, which were verified end-to-end.
68
+ PLUGIN_DIR="$CURSOR_DIR/plugins/local/massa-ai"
69
+ # Pre-fix installs wrote here; removed on install so the two cannot both be
70
+ # discovered and register duplicate hooks.
71
+ LEGACY_PLUGIN_DIR="$CURSOR_DIR/plugins/massa-ai"
61
72
  HOOKS_JSON="$CURSOR_DIR/hooks.json"
62
73
 
63
74
  # The 7 Cursor events → binary subcommands. The command path uses the
@@ -294,6 +305,10 @@ if [[ "$UNINSTALL" -eq 1 ]]; then
294
305
  echo " - removed massa-ai hook entries from $HOOKS_JSON"
295
306
  fi
296
307
  # Remove plugin directory
308
+ if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
309
+ rm -rf "$LEGACY_PLUGIN_DIR"
310
+ echo " - removed $LEGACY_PLUGIN_DIR (pre-fix location)"
311
+ fi
297
312
  if [[ -d "$PLUGIN_DIR" ]]; then
298
313
  rm -rf "$PLUGIN_DIR"
299
314
  echo " - removed $PLUGIN_DIR"
@@ -305,6 +320,13 @@ fi
305
320
 
306
321
  # ── Install ──────────────────────────────────────────────────────────────────
307
322
  vecho "Installing massa-ai Cursor plugin to: $PLUGIN_DIR"
323
+ # Migration: a pre-fix install left a copy at plugins/massa-ai. Leaving it in
324
+ # place risks Cursor discovering both and firing every hook twice, so it goes
325
+ # before the new location is written.
326
+ if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
327
+ rm -rf "$LEGACY_PLUGIN_DIR"
328
+ echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
329
+ fi
308
330
  mkdir -p "$PLUGIN_DIR/.cursor-plugin" "$PLUGIN_DIR/skills" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/agents"
309
331
 
310
332
  # Copy manifest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/cursor-plugin",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "massa-ai plugin for Cursor — semantic code search, memory, and context compression",
5
5
  "files": [
6
6
  "agents",