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:
Kjell Tore Guttormsen 2026-05-29 11:32:02 +02:00
commit b6bb61246b
196 changed files with 164 additions and 138 deletions

View file

@ -0,0 +1,125 @@
#!/usr/bin/env node
// 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
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = process.env.PLUGIN_ROOT || join(__dirname, '..', '..');
const QUEUE_FILE = join(PLUGIN_ROOT, 'assets', 'drafts', 'queue.json');
function ensureQueue() {
if (!existsSync(QUEUE_FILE)) {
mkdirSync(dirname(QUEUE_FILE), { recursive: true });
writeFileSync(QUEUE_FILE, JSON.stringify({ version: 1, queue: [] }, null, 2));
}
}
function readQueue() {
ensureQueue();
try {
const data = JSON.parse(readFileSync(QUEUE_FILE, 'utf-8'));
return data.queue || [];
} catch {
return [];
}
}
function writeQueue(queue) {
ensureQueue();
const data = JSON.parse(readFileSync(QUEUE_FILE, 'utf-8'));
data.queue = queue;
writeFileSync(QUEUE_FILE, JSON.stringify(data, null, 2));
}
function todayISO() {
return new Date().toISOString().slice(0, 10);
}
// Read all queue entries
export function queueRead() {
return readQueue();
}
// Get entries scheduled for today (status=scheduled only)
export function queueToday() {
const today = todayISO();
return readQueue().filter(e => e.scheduled_date === today && e.status === 'scheduled');
}
// Get entries for next N days (status=scheduled only)
export function queueUpcoming(days = 7) {
const today = todayISO();
const end = new Date();
end.setDate(end.getDate() + days);
const endStr = end.toISOString().slice(0, 10);
return readQueue()
.filter(e => e.status === 'scheduled' && e.scheduled_date >= today && e.scheduled_date <= endStr)
.sort((a, b) => (a.scheduled_date + (a.scheduled_time || '')).localeCompare(b.scheduled_date + (b.scheduled_time || '')));
}
// Add entry to queue
export function queueAdd(id, draftPath, schedDate, schedTime, pillar, format, hookPreview, charCount) {
const queue = readQueue().filter(e => e.id !== id);
queue.push({
id,
draft_path: draftPath,
scheduled_date: schedDate,
scheduled_time: schedTime,
pillar,
format,
hook_preview: hookPreview,
character_count: charCount,
status: 'scheduled',
created_at: todayISO()
});
writeQueue(queue);
return `Added: ${id}`;
}
// Update status of a queue entry
export function queueUpdateStatus(id, newStatus) {
const queue = readQueue();
const entry = queue.find(e => e.id === id);
if (entry) {
entry.status = newStatus;
writeQueue(queue);
return `Updated: ${id} -> ${newStatus}`;
}
return `Not found: ${id}`;
}
// Get overdue entries (past scheduled_date, still "scheduled")
export function queueOverdue() {
const today = todayISO();
return readQueue()
.filter(e => e.status === 'scheduled' && (e.scheduled_date || '9999') < today)
.sort((a, b) => (a.scheduled_date || '').localeCompare(b.scheduled_date || ''));
}
// Count entries by status
export function queueCount() {
const counts = {};
for (const e of readQueue()) {
const s = e.status || 'unknown';
counts[s] = (counts[s] || 0) + 1;
}
return counts;
}
// Format queue entries as readable summary
export function queueFormatSummary(entries) {
if (!entries || entries.length === 0) return '(none)';
return entries.map(e => {
const d = e.scheduled_date || '?';
const t = e.scheduled_time || '?';
const hook = (e.hook_preview || '').slice(0, 50);
const pillar = e.pillar || '?';
const fmt = e.format || '?';
const status = e.status || '?';
return ` ${d} ${t} | ${hook}... | ${pillar} (${fmt}) [${status}]`;
}).join('\n');
}