Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 6 additions & 3 deletions scripts/hooks/post-tool-use-failure.mjs
Original file line number Diff line number Diff line change
@@ -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) =>
Expand All @@ -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);
});
9 changes: 6 additions & 3 deletions scripts/hooks/post-tool-use.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
47 changes: 39 additions & 8 deletions scripts/hooks/record-work-graph-event.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
),
},
};
}
Expand All @@ -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,
Expand Down Expand Up @@ -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);
});
}
9 changes: 6 additions & 3 deletions scripts/hooks/session-start.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
9 changes: 6 additions & 3 deletions scripts/hooks/subagent-start.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
9 changes: 6 additions & 3 deletions scripts/hooks/subagent-stop.mjs
Original file line number Diff line number Diff line change
@@ -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);
});
5 changes: 3 additions & 2 deletions scripts/install-local.mjs
Original file line number Diff line number Diff line change
@@ -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 });
Expand Down
13 changes: 13 additions & 0 deletions scripts/verify-plugin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.');