From c4f471dbf165a7effbc1a2342f5fc3c5dabeb0d6 Mon Sep 17 00:00:00 2001 From: hopeatina Date: Thu, 21 May 2026 22:31:10 -0500 Subject: [PATCH] Fix Cursor plugin hook and install behavior --- README.md | 2 +- scripts/hooks/post-tool-use-failure.mjs | 9 +++-- scripts/hooks/post-tool-use.mjs | 9 +++-- scripts/hooks/record-work-graph-event.mjs | 47 +++++++++++++++++++---- scripts/hooks/session-start.mjs | 9 +++-- scripts/hooks/subagent-start.mjs | 9 +++-- scripts/hooks/subagent-stop.mjs | 9 +++-- scripts/install-local.mjs | 5 ++- scripts/verify-plugin.mjs | 13 +++++++ 9 files changed, 86 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 342cdf3..a218b6b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ This repo contains the plugin artifact needed for Cursor Marketplace submission 1. Run `npm run verify` 2. Run `npm run install:local` 3. Restart Cursor or run `Developer: Reload Window` -4. Confirm the plugin loads from `~/.cursor/plugins/local/cursor-plugin` +4. Confirm the plugin loads from `~/.cursor/plugins/local/orgx` ## Hook behavior diff --git a/scripts/hooks/post-tool-use-failure.mjs b/scripts/hooks/post-tool-use-failure.mjs index d26b980..0ddaeb5 100644 --- a/scripts/hooks/post-tool-use-failure.mjs +++ b/scripts/hooks/post-tool-use-failure.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { main, readStdin } from './record-work-graph-event.mjs'; +import { exitCodeForResult, main, readStdin } from './record-work-graph-event.mjs'; readStdin() .then((stdinText) => @@ -9,5 +9,8 @@ readStdin() stdinText, }) ) - .then(() => process.exit(0)) - .catch(() => process.exit(0)); + .then((result) => process.exit(exitCodeForResult(result))) + .catch((error) => { + process.stderr.write(`OrgX Cursor post-tool-use-failure hook failed: ${error.message}\n`); + process.exit(1); + }); diff --git a/scripts/hooks/post-tool-use.mjs b/scripts/hooks/post-tool-use.mjs index 9e91580..ef1eb78 100644 --- a/scripts/hooks/post-tool-use.mjs +++ b/scripts/hooks/post-tool-use.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env node -import { main, readStdin } from './record-work-graph-event.mjs'; +import { exitCodeForResult, main, readStdin } from './record-work-graph-event.mjs'; readStdin() .then((stdinText) => main({ argv: ['--event=post_tool_use', '--source_client=cursor'], stdinText }) ) - .then(() => process.exit(0)) - .catch(() => process.exit(0)); + .then((result) => process.exit(exitCodeForResult(result))) + .catch((error) => { + process.stderr.write(`OrgX Cursor post-tool-use hook failed: ${error.message}\n`); + process.exit(1); + }); diff --git a/scripts/hooks/record-work-graph-event.mjs b/scripts/hooks/record-work-graph-event.mjs index 3f88da5..30423ea 100644 --- a/scripts/hooks/record-work-graph-event.mjs +++ b/scripts/hooks/record-work-graph-event.mjs @@ -6,6 +6,19 @@ import { dirname, join } from 'node:path'; import process from 'node:process'; import { pathToFileURL } from 'node:url'; +const SENSITIVE_PAYLOAD_KEYS = new Set([ + 'api_key', + 'apiKey', + 'authorization', + 'cookie', + 'password', + 'storage_state', + 'storageState', + 'token', + 'transcript_path', + 'transcriptPath', +]); + export function parseArgs(argv) { const args = {}; for (const arg of argv) { @@ -46,6 +59,12 @@ export function parseJsonRecord(value) { } } +export function visiblePayloadKeys(payload) { + return Object.keys(payload) + .filter((key) => !SENSITIVE_PAYLOAD_KEYS.has(key)) + .slice(0, 40); +} + export function buildWorkGraphHookRecord({ args, payload, @@ -83,12 +102,14 @@ export function buildWorkGraphHookRecord({ args.cwd, cwd ), - transcript_path: pickString(payload.transcript_path, payload.transcriptPath), timestamp, summary: { tool_name: toolName, prompt_chars: prompt ? prompt.length : undefined, - payload_keys: Object.keys(payload).slice(0, 40), + payload_keys: visiblePayloadKeys(payload), + transcript_path_present: Boolean( + pickString(payload.transcript_path, payload.transcriptPath) + ), }, }; } @@ -106,6 +127,10 @@ export function appendWorkGraphHookRecord(record, outbox) { } } +export function exitCodeForResult(result) { + return result?.work_graph_spooled ? 0 : 1; +} + export async function main({ argv = process.argv.slice(2), env = process.env, @@ -138,19 +163,25 @@ export async function main({ timestamp, }); + const workGraphSpooled = appendWorkGraphHookRecord(record, outbox); + return { - ok: true, - work_graph_spooled: appendWorkGraphHookRecord(record, outbox), + ok: workGraphSpooled, + work_graph_spooled: workGraphSpooled, }; } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { readStdin() .then((stdinText) => main({ stdinText })) - .then(() => { - process.exit(0); + .then((result) => { + if (!result.work_graph_spooled) { + process.stderr.write('OrgX Cursor hook failed to spool Work Graph event.\n'); + } + process.exit(exitCodeForResult(result)); }) - .catch(() => { - process.exit(0); + .catch((error) => { + process.stderr.write(`OrgX Cursor hook failed: ${error.message}\n`); + process.exit(1); }); } diff --git a/scripts/hooks/session-start.mjs b/scripts/hooks/session-start.mjs index efca4d2..b6ba64a 100644 --- a/scripts/hooks/session-start.mjs +++ b/scripts/hooks/session-start.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env node -import { main, readStdin } from './record-work-graph-event.mjs'; +import { exitCodeForResult, main, readStdin } from './record-work-graph-event.mjs'; readStdin() .then((stdinText) => main({ argv: ['--event=session_start', '--source_client=cursor'], stdinText }) ) - .then(() => process.exit(0)) - .catch(() => process.exit(0)); + .then((result) => process.exit(exitCodeForResult(result))) + .catch((error) => { + process.stderr.write(`OrgX Cursor session-start hook failed: ${error.message}\n`); + process.exit(1); + }); diff --git a/scripts/hooks/subagent-start.mjs b/scripts/hooks/subagent-start.mjs index 8e0022f..bacd6c0 100644 --- a/scripts/hooks/subagent-start.mjs +++ b/scripts/hooks/subagent-start.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env node -import { main, readStdin } from './record-work-graph-event.mjs'; +import { exitCodeForResult, main, readStdin } from './record-work-graph-event.mjs'; readStdin() .then((stdinText) => main({ argv: ['--event=subagent_start', '--source_client=cursor'], stdinText }) ) - .then(() => process.exit(0)) - .catch(() => process.exit(0)); + .then((result) => process.exit(exitCodeForResult(result))) + .catch((error) => { + process.stderr.write(`OrgX Cursor subagent-start hook failed: ${error.message}\n`); + process.exit(1); + }); diff --git a/scripts/hooks/subagent-stop.mjs b/scripts/hooks/subagent-stop.mjs index 3bcd066..37b2a25 100644 --- a/scripts/hooks/subagent-stop.mjs +++ b/scripts/hooks/subagent-stop.mjs @@ -1,10 +1,13 @@ #!/usr/bin/env node -import { main, readStdin } from './record-work-graph-event.mjs'; +import { exitCodeForResult, main, readStdin } from './record-work-graph-event.mjs'; readStdin() .then((stdinText) => main({ argv: ['--event=subagent_stop', '--source_client=cursor'], stdinText }) ) - .then(() => process.exit(0)) - .catch(() => process.exit(0)); + .then((result) => process.exit(exitCodeForResult(result))) + .catch((error) => { + process.stderr.write(`OrgX Cursor subagent-stop hook failed: ${error.message}\n`); + process.exit(1); + }); diff --git a/scripts/install-local.mjs b/scripts/install-local.mjs index 57591cb..b15dcb7 100644 --- a/scripts/install-local.mjs +++ b/scripts/install-local.mjs @@ -1,10 +1,11 @@ import { mkdirSync, rmSync, symlinkSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { homedir } from 'node:os'; +import { fileURLToPath } from 'node:url'; -const pluginRoot = resolve(new URL('..', import.meta.url).pathname); +const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url))); const localPluginsDir = resolve(homedir(), '.cursor/plugins/local'); -const linkPath = resolve(localPluginsDir, 'cursor-plugin'); +const linkPath = resolve(localPluginsDir, 'orgx'); mkdirSync(localPluginsDir, { recursive: true }); rmSync(linkPath, { force: true, recursive: true }); diff --git a/scripts/verify-plugin.mjs b/scripts/verify-plugin.mjs index 5221127..462d1f3 100644 --- a/scripts/verify-plugin.mjs +++ b/scripts/verify-plugin.mjs @@ -58,11 +58,24 @@ for (const [eventName, scriptName] of [ } const hookScript = readFileSync(resolve('scripts/hooks/record-work-graph-event.mjs'), 'utf8'); +const installScript = readFileSync(resolve('scripts/install-local.mjs'), 'utf8'); if (!hookScript.includes('orgx_cursor_plugin_runtime_hook')) { throw new Error('record-work-graph-event.mjs must emit orgx_cursor_plugin_runtime_hook records'); } if (!hookScript.includes('ORGX_WIZARD_HOOK_OUTBOX')) { throw new Error('record-work-graph-event.mjs must support ORGX_WIZARD_HOOK_OUTBOX'); } +if (hookScript.includes('transcript_path:')) { + throw new Error('record-work-graph-event.mjs must not persist raw transcript paths'); +} +if (!hookScript.includes('exitCodeForResult')) { + throw new Error('record-work-graph-event.mjs must expose hook failure exit handling'); +} +if (!installScript.includes("fileURLToPath(import.meta.url)")) { + throw new Error('install-local.mjs must resolve plugin root with fileURLToPath'); +} +if (!installScript.includes("resolve(localPluginsDir, 'orgx')")) { + throw new Error('install-local.mjs must install under the orgx plugin name'); +} console.log('Plugin manifest, MCP config, and hooks look valid.');