refactor(linkedin)!: rename plugin linkedin-thought-leadership → linkedin-studio (v3.0.0)
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>
This commit is contained in:
parent
9df3de795c
commit
b6bb61246b
196 changed files with 164 additions and 138 deletions
90
plugins/linkedin-studio/hooks/scripts/stop-reminder.mjs
Normal file
90
plugins/linkedin-studio/hooks/scripts/stop-reminder.mjs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env node
|
||||
// stop-reminder.mjs
|
||||
// Stop hook for linkedin-studio plugin
|
||||
//
|
||||
// Only fires if LinkedIn content was worked on (session marker exists).
|
||||
// First stop: blocks with reason (Claude processes reminders).
|
||||
// Subsequent stops within 60s: allows (prevents infinite loop).
|
||||
//
|
||||
// Exit codes:
|
||||
// 0 - Allow (pass through or second stop)
|
||||
// 2 - Not used; uses {"decision": "block"} JSON instead
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, statSync, unlinkSync, mkdirSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pluginRoot = join(__dirname, '..', '..');
|
||||
const promptFile = join(pluginRoot, 'hooks', 'prompts', 'state-update-reminder.md');
|
||||
|
||||
const sessionDir = '/tmp/linkedin-hooks';
|
||||
const sessionMarker = join(sessionDir, 'session-active');
|
||||
const lockFile = join(sessionDir, 'stop-hook.lock');
|
||||
|
||||
function nowSeconds() {
|
||||
return Date.now() / 1000;
|
||||
}
|
||||
|
||||
function fileAgeSeconds(filePath) {
|
||||
try {
|
||||
return nowSeconds() - statSync(filePath).mtime.getTime() / 1000;
|
||||
} catch {
|
||||
return Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
function safeUnlink(filePath) {
|
||||
try { unlinkSync(filePath); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Read stdin
|
||||
let input;
|
||||
try {
|
||||
input = JSON.parse(readFileSync(0, 'utf-8'));
|
||||
} catch {
|
||||
input = {};
|
||||
}
|
||||
|
||||
// Infinite loop prevention: if Claude is already continuing from a Stop hook
|
||||
if (input.stop_hook_active === true) {
|
||||
process.stdout.write('{}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// No session marker = no LinkedIn work done
|
||||
if (!existsSync(sessionMarker)) {
|
||||
process.stdout.write('{}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Staleness check: ignore markers older than 12 hours (43200 seconds)
|
||||
if (fileAgeSeconds(sessionMarker) > 43200) {
|
||||
safeUnlink(sessionMarker);
|
||||
process.stdout.write('{}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Infinite-loop prevention: lock file within 60 seconds = second stop
|
||||
if (existsSync(lockFile)) {
|
||||
if (fileAgeSeconds(lockFile) < 60) {
|
||||
safeUnlink(lockFile);
|
||||
safeUnlink(sessionMarker);
|
||||
process.stdout.write('{}');
|
||||
process.exit(0);
|
||||
}
|
||||
safeUnlink(lockFile);
|
||||
}
|
||||
|
||||
// First stop: create lock and block with reminder prompt
|
||||
mkdirSync(sessionDir, { recursive: true });
|
||||
writeFileSync(lockFile, '');
|
||||
|
||||
if (!existsSync(promptFile)) {
|
||||
process.stdout.write('{}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const promptContent = readFileSync(promptFile, 'utf-8');
|
||||
process.stdout.write(JSON.stringify({ decision: 'block', reason: promptContent }));
|
||||
process.exit(0);
|
||||
Loading…
Add table
Add a link
Reference in a new issue