BREAKING CHANGE: the marketplace slug, the agent namespace (linkedin-studio:<agent>), and the runtime state-file path (~/.claude/linkedin-studio.local.md) all change. Reinstall required; existing state migrated in place (post metrics, streak, history preserved). The /linkedin:* commands are unchanged — the command namespace is set per-command in frontmatter and was always independent of the plugin slug. Functionality is byte-identical to v2.4.0; this release is pure identity. - dir + manifests: plugins/linkedin-studio + plugin.json + root marketplace.json - agent namespace updated in commands/newsletter.md (only functional invoker) - state path updated in 4 hook scripts + topic-rotation prompt + state template - catch-all skill dir renamed skills/linkedin-studio (5 functional skills unchanged) - docs + version bump to 3.0.0 across README badge, CHANGELOG, root README/CLAUDE.md - historical records (CHANGELOG past entries, docs/ build artifacts, config-audit v5.0.0 snapshots) intentionally retain the old slug Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.1 KiB
JavaScript
70 lines
2.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// content-gatekeeper.mjs
|
|
// Unified PreToolUse/PostToolUse gatekeeper for linkedin-studio plugin
|
|
//
|
|
// Replaces 4 nearly identical bash scripts:
|
|
// pre-content-quality-gate.sh, pre-voice-guardian.sh,
|
|
// pre-topic-rotation-gate.sh, post-creation-check.sh
|
|
//
|
|
// Usage:
|
|
// node content-gatekeeper.mjs <prompt-filename> [--no-session-marker]
|
|
//
|
|
// Arguments:
|
|
// prompt-filename - Prompt file in hooks/prompts/ (e.g. content-quality-gate.md)
|
|
// --no-session-marker - Skip creating session-active marker (for PostToolUse)
|
|
//
|
|
// Exit codes:
|
|
// 0 - Always allow (injects systemMessage or passes through)
|
|
|
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { isLinkedInContent } from './linkedin-content-filter.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const pluginRoot = join(__dirname, '..', '..');
|
|
|
|
const promptFile = process.argv[2];
|
|
const noSessionMarker = process.argv.includes('--no-session-marker');
|
|
|
|
if (!promptFile) {
|
|
process.stdout.write('{}');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Read and parse stdin JSON
|
|
let input;
|
|
try {
|
|
input = JSON.parse(readFileSync(0, 'utf-8'));
|
|
} catch {
|
|
process.stdout.write('{}');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Extract file_path from tool_input
|
|
const toolInput = input.tool_input ?? {};
|
|
const filePath = toolInput.file_path ?? toolInput.filePath ?? '';
|
|
|
|
// Check if this is LinkedIn content
|
|
if (!isLinkedInContent(filePath)) {
|
|
process.stdout.write('{}');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Mark session as having LinkedIn content activity
|
|
if (!noSessionMarker) {
|
|
const sessionDir = '/tmp/linkedin-hooks';
|
|
mkdirSync(sessionDir, { recursive: true });
|
|
writeFileSync(join(sessionDir, 'session-active'), '');
|
|
}
|
|
|
|
// Load and return prompt
|
|
const promptPath = join(pluginRoot, 'hooks', 'prompts', promptFile);
|
|
if (!existsSync(promptPath)) {
|
|
process.stdout.write('{}');
|
|
process.exit(0);
|
|
}
|
|
|
|
const promptContent = readFileSync(promptPath, 'utf-8');
|
|
process.stdout.write(JSON.stringify({ systemMessage: promptContent }));
|
|
process.exit(0);
|