@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.
- package/.cursor-plugin/plugin.json +1 -1
- package/hooks/massa-ai-hook +56 -1
- package/install.sh +23 -1
- package/package.json +1 -1
package/hooks/massa-ai-hook
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|