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
34463e4585
commit
52fa87a255
59 changed files with 148 additions and 122 deletions
|
|
@ -29,7 +29,7 @@ describe('generateIcal', () => {
|
|||
const ical = generateIcal([]);
|
||||
assert.match(ical, /^BEGIN:VCALENDAR\r\n/);
|
||||
assert.match(ical, /\r\nEND:VCALENDAR\r\n$/);
|
||||
assert.match(ical, /PRODID:-\/\/linkedin-thought-leadership\/\/EN/);
|
||||
assert.match(ical, /PRODID:-\/\/linkedin-studio\/\/EN/);
|
||||
assert.match(ical, /VERSION:2\.0/);
|
||||
assert.ok(!ical.includes('BEGIN:VEVENT'), 'should not contain VEVENT');
|
||||
});
|
||||
|
|
@ -39,7 +39,7 @@ describe('generateIcal', () => {
|
|||
assert.match(ical, /BEGIN:VEVENT/);
|
||||
assert.match(ical, /DTSTART;TZID=Europe\/Oslo:20260414T083000/);
|
||||
assert.match(ical, /SUMMARY:LinkedIn: AI strategy in public sector/);
|
||||
assert.match(ical, /UID:post-2026-04-14-ai-strategy@linkedin-thought-leadership/);
|
||||
assert.match(ical, /UID:post-2026-04-14-ai-strategy@linkedin-studio/);
|
||||
assert.match(ical, /END:VEVENT/);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// Cross-platform clipboard helper for linkedin-thought-leadership plugin
|
||||
// Cross-platform clipboard helper for linkedin-studio plugin
|
||||
// Copies text to system clipboard using platform-native commands.
|
||||
// Standalone: reads stdin and copies it. Import: export { copyToClipboard, clipboardAvailable }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
// content-gatekeeper.mjs
|
||||
// Unified PreToolUse/PostToolUse gatekeeper for linkedin-thought-leadership plugin
|
||||
// Unified PreToolUse/PostToolUse gatekeeper for linkedin-studio plugin
|
||||
//
|
||||
// Replaces 4 nearly identical bash scripts:
|
||||
// pre-content-quality-gate.sh, pre-voice-guardian.sh,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// RFC 5545 iCal generator for linkedin-thought-leadership plugin
|
||||
// RFC 5545 iCal generator for linkedin-studio plugin
|
||||
// Import: import { generateIcal, generateIcalFromQueue, writeIcalFile } from './ical-generator.mjs';
|
||||
// Standalone: node ical-generator.mjs --from-queue --output path/to/schedule.ics
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ export function generateIcal(events, options = {}) {
|
|||
const lines = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//linkedin-thought-leadership//EN',
|
||||
'PRODID:-//linkedin-studio//EN',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
];
|
||||
|
|
@ -153,7 +153,7 @@ export function generateIcal(events, options = {}) {
|
|||
|
||||
lines.push(
|
||||
'BEGIN:VEVENT',
|
||||
`UID:${event.id}@linkedin-thought-leadership`,
|
||||
`UID:${event.id}@linkedin-studio`,
|
||||
`DTSTAMP:${now}`,
|
||||
`DTSTART;TZID=${tz}:${dtstart}`,
|
||||
`DTEND;TZID=${tz}:${dtend}`,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function isLinkedInContent(filePath) {
|
|||
// POSITIVE: explicit LinkedIn content paths only
|
||||
if (normalized.startsWith('assets/drafts/') || normalized.includes('/assets/drafts/')) return true;
|
||||
if (normalized.includes('/linkedin-posts/')) return true;
|
||||
if (normalized.includes('/linkedin-thought-leadership/assets/')) return true;
|
||||
if (normalized.includes('/linkedin-studio/assets/')) return true;
|
||||
|
||||
// DEFAULT: everything else is NOT LinkedIn content
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// Personalization score calculator for linkedin-thought-leadership plugin
|
||||
// Personalization score calculator for linkedin-studio plugin
|
||||
// Checks 8 asset categories for real user data vs placeholder templates
|
||||
// Standalone: outputs SCORE:N|M/8 assets personalized
|
||||
// Import: export function calculateScore(pluginRoot) => { score, personalized, categories }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// Notification hook for linkedin-thought-leadership plugin
|
||||
// Notification hook for linkedin-studio plugin
|
||||
// Fires on idle_prompt to show posting reminders. Rate-limited: max once per 30 min.
|
||||
|
||||
import { readFileSync, existsSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
|
|
@ -10,7 +10,7 @@ import { queueToday, queueOverdue } from './queue-manager.mjs';
|
|||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
||||
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
||||
const STATE_FILE = join(HOME, '.claude', 'linkedin-thought-leadership.local.md');
|
||||
const STATE_FILE = join(HOME, '.claude', 'linkedin-studio.local.md');
|
||||
const SESSION_DIR = '/tmp/linkedin-hooks';
|
||||
const COOLDOWN_FILE = join(SESSION_DIR, 'last-notification');
|
||||
const COOLDOWN_SECONDS = 1800;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
// pre-compact.mjs
|
||||
// PreCompact hook for linkedin-thought-leadership plugin
|
||||
// PreCompact hook for linkedin-studio plugin
|
||||
// Reminds Claude to preserve critical LinkedIn session context before compaction
|
||||
//
|
||||
// Exit codes:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// Queue management library for linkedin-thought-leadership plugin
|
||||
// Queue management library for linkedin-studio plugin
|
||||
// Import: import { queueRead, queueToday, ... } from './queue-manager.mjs';
|
||||
// Replaces python3 dependency with native Node.js JSON/Date operations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// Quick-import helper for linkedin-thought-leadership plugin
|
||||
// Quick-import helper for linkedin-studio plugin
|
||||
// Opens LinkedIn analytics in browser, watches ~/Downloads for new CSV files
|
||||
|
||||
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'node:fs';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
// SessionStart hook for linkedin-thought-leadership plugin
|
||||
// SessionStart hook for linkedin-studio plugin
|
||||
// Reads persistent state and session context, outputs JSON with additionalContext
|
||||
|
||||
import { readFileSync, existsSync, copyFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
|
|
@ -12,7 +12,7 @@ import { applyWeekRollover } from './week-rollover.mjs';
|
|||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
||||
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
||||
const STATE_FILE = join(HOME, '.claude', 'linkedin-thought-leadership.local.md');
|
||||
const STATE_FILE = join(HOME, '.claude', 'linkedin-studio.local.md');
|
||||
|
||||
function extractYaml(content, key) {
|
||||
const re = new RegExp(`^${key}: *"?([^"\\n]*)"?`, 'm');
|
||||
|
|
@ -234,7 +234,7 @@ if (existsSync(STATE_FILE)) {
|
|||
}
|
||||
|
||||
// Build context output
|
||||
context = 'LinkedIn Thought Leadership session context loaded.\\n\\n';
|
||||
context = 'LinkedIn Studio session context loaded.\\n\\n';
|
||||
context += `## Status\\n\`\`\`\\n${statusLine}\\n\`\`\`\\n\\n`;
|
||||
|
||||
if (weekResetNote) context += `**${weekResetNote}**\\n\\n`;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Deterministic state mutation functions for linkedin-thought-leadership plugin.
|
||||
// Deterministic state mutation functions for linkedin-studio plugin.
|
||||
// Pure functions operate on string content (same pattern as week-rollover.mjs).
|
||||
// I/O wrapper (writeState) handles file reads/writes (same pattern as queue-manager.mjs).
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import { applyWeekRollover } from './week-rollover.mjs';
|
|||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
||||
const STATE_FILE = process.env.STATE_FILE || join(HOME, '.claude', 'linkedin-thought-leadership.local.md');
|
||||
const STATE_FILE = process.env.STATE_FILE || join(HOME, '.claude', 'linkedin-studio.local.md');
|
||||
|
||||
function replaceField(content, field, value) {
|
||||
return content.replace(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
// stop-reminder.mjs
|
||||
// Stop hook for linkedin-thought-leadership plugin
|
||||
// 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).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
// user-prompt-context.mjs
|
||||
// UserPromptSubmit hook for linkedin-thought-leadership plugin
|
||||
// UserPromptSubmit hook for linkedin-studio plugin
|
||||
//
|
||||
// Two-tier keyword matching in user prompts:
|
||||
// Tier 1: Strong signals (slash commands, explicit phrases)
|
||||
|
|
@ -19,7 +19,7 @@ import { fileURLToPath } from 'node:url';
|
|||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pluginRoot = join(__dirname, '..', '..');
|
||||
const home = process.env.HOME || process.env.USERPROFILE || '';
|
||||
const stateFile = join(home, '.claude', 'linkedin-thought-leadership.local.md');
|
||||
const stateFile = join(home, '.claude', 'linkedin-studio.local.md');
|
||||
|
||||
// Read stdin JSON
|
||||
let input;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue