fullcourtdefense-cli 1.1.0 → 1.1.1

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.
@@ -206,11 +206,6 @@ function sessionId(p) {
206
206
  return fromPayload;
207
207
  return `cursor-${os.hostname()}`;
208
208
  }
209
- function emit(decision, blocked) {
210
- process.stdout.write(JSON.stringify(decision));
211
- // Exit 2 is the universal "block" signal across all hook events.
212
- process.exit(blocked ? 2 : 0);
213
- }
214
209
  async function hookCommand(args, config) {
215
210
  const shadow = args.shadow === 'true';
216
211
  const failClosed = args.failClosed === 'true';
@@ -236,15 +231,26 @@ async function hookCommand(args, config) {
236
231
  }
237
232
  const event = inferEvent(args.event, payload);
238
233
  const text = extractText(event, payload).trim();
234
+ // Emit the verdict in the shape Cursor expects for THIS event, then exit.
235
+ // beforeSubmitPrompt blocks via { continue: false }; the execution hooks
236
+ // (shell/mcp/read) block via { permission: "deny" }. Exit 2 reinforces it.
237
+ const respond = (blocked, userMsg, agentMsg) => {
238
+ const decision = event === 'prompt'
239
+ ? (blocked ? { continue: false } : { continue: true })
240
+ : { permission: blocked ? 'deny' : 'allow' };
241
+ if (userMsg)
242
+ decision.user_message = userMsg;
243
+ if (agentMsg)
244
+ decision.agent_message = agentMsg;
245
+ process.stdout.write(JSON.stringify(decision));
246
+ process.exit(blocked ? 2 : 0);
247
+ };
239
248
  // Nothing to scan, or no Shield configured → allow (fail-open by design here:
240
249
  // a misconfigured machine must not brick the developer's IDE).
241
250
  if (!text)
242
- emit({ permission: 'allow' }, false);
251
+ respond(false);
243
252
  if (!shieldId) {
244
- emit({
245
- permission: 'allow',
246
- agent_message: 'FullCourtDefense hook installed but no Shield ID configured (run `fullcourtdefense configure`).',
247
- }, false);
253
+ respond(false, undefined, 'FullCourtDefense hook installed but no Shield ID configured (run `fullcourtdefense configure`).');
248
254
  }
249
255
  try {
250
256
  const controller = new AbortController();
@@ -269,12 +275,7 @@ async function hookCommand(args, config) {
269
275
  clearTimeout(timer);
270
276
  if (!resp.ok) {
271
277
  // Backend rejected (quota, bad shield, etc.) — fail open unless told otherwise.
272
- const blocked = failClosed;
273
- emit({
274
- permission: blocked ? 'deny' : 'allow',
275
- user_message: blocked ? `FullCourtDefense gate unavailable (HTTP ${resp.status}) — blocked by fail-closed policy.` : undefined,
276
- agent_message: `FullCourtDefense gate returned HTTP ${resp.status}.`,
277
- }, blocked);
278
+ respond(failClosed, failClosed ? `FullCourtDefense gate unavailable (HTTP ${resp.status}) — blocked by fail-closed policy.` : undefined, `FullCourtDefense gate returned HTTP ${resp.status}.`);
278
279
  }
279
280
  const data = await resp.json().catch(() => ({}));
280
281
  const sh = (data._shield || {});
@@ -285,28 +286,16 @@ async function hookCommand(args, config) {
285
286
  const reason = String(sh.reason || 'policy_violation');
286
287
  const explanation = String(sh.reason || 'Flagged by FullCourtDefense.');
287
288
  if (!isBlocked) {
288
- emit({ permission: 'allow' }, false);
289
+ respond(false);
289
290
  }
290
291
  // Blocked verdict. In shadow mode we annotate but let it through.
291
292
  if (shadow) {
292
- emit({
293
- permission: 'allow',
294
- agent_message: `[FullCourtDefense shadow] would block ${event} (${reason}): ${explanation}`,
295
- }, false);
293
+ respond(false, undefined, `[FullCourtDefense shadow] would block ${event} (${reason}): ${explanation}`);
296
294
  }
297
- emit({
298
- permission: 'deny',
299
- user_message: `Blocked by FullCourtDefense — ${reason}. ${explanation}`,
300
- agent_message: `FullCourtDefense blocked this ${event} as "${reason}". Do not retry; revise to remove the flagged content.`,
301
- }, true);
295
+ respond(true, `Blocked by FullCourtDefense — ${reason}.`, `FullCourtDefense blocked this ${event} as "${reason}". Do not retry; revise to remove the flagged content.`);
302
296
  }
303
297
  catch (err) {
304
298
  // Network error / timeout.
305
- const blocked = failClosed;
306
- emit({
307
- permission: blocked ? 'deny' : 'allow',
308
- user_message: blocked ? 'FullCourtDefense gate unreachable — blocked by fail-closed policy.' : undefined,
309
- agent_message: `FullCourtDefense hook error: ${err instanceof Error ? err.message : String(err)}.`,
310
- }, blocked);
299
+ respond(failClosed, failClosed ? 'FullCourtDefense gate unreachable — blocked by fail-closed policy.' : undefined, `FullCourtDefense hook error: ${err instanceof Error ? err.message : String(err)}.`);
311
300
  }
312
301
  }
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ const configure_1 = require("./commands/configure");
10
10
  const discover_1 = require("./commands/discover");
11
11
  const hook_1 = require("./commands/hook");
12
12
  const installCursorHook_1 = require("./commands/installCursorHook");
13
- const VERSION = '1.1.0';
13
+ const VERSION = '1.1.1';
14
14
  function parseArgs(argv) {
15
15
  const flags = {};
16
16
  let command = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {