feat(linkedin): add state-updater.mjs — deterministic state mutations with tests
Pure functions for post tracking (streak, week rollover, first_post_date), content history pruning, and follower count updates. 19 tests green. Follows week-rollover.mjs pattern (pure functions) + queue-manager.mjs pattern (I/O wrapper with atomic writes). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b3979d0e5d
commit
aa5cca9cf6
2 changed files with 548 additions and 0 deletions
|
|
@ -0,0 +1,295 @@
|
|||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { updatePostTracking, pruneContentHistory, updateFollowerCount } from '../state-updater.mjs';
|
||||
|
||||
const SAMPLE_STATE = `---
|
||||
last_post_date: "2026-04-05"
|
||||
first_post_date: "2026-01-15"
|
||||
last_post_topic: "AI strategy"
|
||||
posts_this_week: 2
|
||||
weekly_goal: 3
|
||||
current_streak: 5
|
||||
longest_streak: 12
|
||||
current_week: "2026-W14"
|
||||
last_import_date: "2026-04-01"
|
||||
last_import_week: "2026-W14"
|
||||
follower_count: 850
|
||||
follower_target: 10000
|
||||
target_date: "2026-12-31"
|
||||
monthly_growth: []
|
||||
projected_10k_date: ""
|
||||
growth_rate_needed: 0
|
||||
---
|
||||
|
||||
# LinkedIn Session State
|
||||
|
||||
## Recent Posts
|
||||
|
||||
- [2026-04-05] "AI governance is not about..." (1450) - AI strategy
|
||||
- [2026-04-03] "Three things I learned..." (1200) - leadership
|
||||
- [2026-03-28] "Why most teams fail at..." (1350) - team building
|
||||
|
||||
## Session Notes
|
||||
|
||||
## Planned Content
|
||||
|
||||
## Milestone Log
|
||||
`;
|
||||
|
||||
describe('updatePostTracking', () => {
|
||||
test('sets last_post_date to provided date', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-07',
|
||||
postTopic: 'AI governance',
|
||||
hookText: 'The real problem with AI governance...',
|
||||
charCount: 1500,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^last_post_date: "2026-04-07"$/m);
|
||||
});
|
||||
|
||||
test('sets last_post_topic', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-07',
|
||||
postTopic: 'AI governance',
|
||||
hookText: 'The real problem...',
|
||||
charCount: 1500,
|
||||
format: 'post'
|
||||
});
|
||||
assert.match(result.content, /^last_post_topic: "AI governance"$/m);
|
||||
});
|
||||
|
||||
test('increments posts_this_week when same week', () => {
|
||||
// 2026-04-06 is a Monday, ISO W15. current_week is W14.
|
||||
// Use a date that stays in W14: 2026-04-05 is Sunday W14 — but last_post_date is already 04-05.
|
||||
// Let's use a state with current_week matching the post date week.
|
||||
const w15State = SAMPLE_STATE.replace('current_week: "2026-W14"', 'current_week: "2026-W15"');
|
||||
const result = updatePostTracking(w15State, {
|
||||
postDate: '2026-04-07', // Tuesday W15
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^posts_this_week: 3$/m); // was 2, incremented
|
||||
});
|
||||
|
||||
test('increments streak when gap <= 2 days', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-06', // 1 day after last_post_date 2026-04-05
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^current_streak: 6$/m); // was 5, incremented
|
||||
});
|
||||
|
||||
test('resets streak to 1 when gap > 2 days', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-09', // 4 days after 2026-04-05
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^current_streak: 1$/m);
|
||||
});
|
||||
|
||||
test('sets first_post_date when null', () => {
|
||||
const nullFirstPost = SAMPLE_STATE.replace(
|
||||
'first_post_date: "2026-01-15"',
|
||||
'first_post_date: null'
|
||||
);
|
||||
const result = updatePostTracking(nullFirstPost, {
|
||||
postDate: '2026-04-07',
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^first_post_date: "2026-04-07"$/m);
|
||||
});
|
||||
|
||||
test('does NOT overwrite existing first_post_date', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-07',
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^first_post_date: "2026-01-15"$/m);
|
||||
});
|
||||
|
||||
test('triggers week rollover when ISO week changes', () => {
|
||||
// 2026-04-14 is W16, current_week is W14
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-14',
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
// After rollover, posts_this_week resets to 0 then increments to 1
|
||||
assert.match(result.content, /^posts_this_week: 1$/m);
|
||||
assert.match(result.content, /^current_week: "2026-W16"$/m);
|
||||
});
|
||||
|
||||
test('appends to Recent Posts section', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-06',
|
||||
postTopic: 'AI governance',
|
||||
hookText: 'The real problem with AI governance today...',
|
||||
charCount: 1500,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.ok(result.content.includes('- [2026-04-06] "The real problem with AI governance today..." (1500) - AI governance'));
|
||||
// Existing entries should still be there
|
||||
assert.ok(result.content.includes('- [2026-04-05] "AI governance is not about..."'));
|
||||
});
|
||||
|
||||
test('updates longest_streak when current exceeds it', () => {
|
||||
const highStreak = SAMPLE_STATE.replace('current_streak: 5', 'current_streak: 12');
|
||||
const result = updatePostTracking(highStreak, {
|
||||
postDate: '2026-04-06', // 1 day gap, streak increments to 13
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^current_streak: 13$/m);
|
||||
assert.match(result.content, /^longest_streak: 13$/m);
|
||||
});
|
||||
|
||||
test('does not update longest_streak when current is lower', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-06',
|
||||
postTopic: 'test',
|
||||
hookText: 'Hook',
|
||||
charCount: 1000,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^current_streak: 6$/m);
|
||||
assert.match(result.content, /^longest_streak: 12$/m); // unchanged
|
||||
});
|
||||
|
||||
test('returns changes array describing what changed', () => {
|
||||
const result = updatePostTracking(SAMPLE_STATE, {
|
||||
postDate: '2026-04-06',
|
||||
postTopic: 'AI governance',
|
||||
hookText: 'Hook',
|
||||
charCount: 1500,
|
||||
format: 'post'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.ok(Array.isArray(result.changes));
|
||||
assert.ok(result.changes.length > 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pruneContentHistory', () => {
|
||||
test('removes entries older than 90 days', () => {
|
||||
const today = new Date();
|
||||
const old = new Date(today);
|
||||
old.setDate(old.getDate() - 100);
|
||||
const oldDate = old.toISOString().slice(0, 10);
|
||||
|
||||
const recent = new Date(today);
|
||||
recent.setDate(recent.getDate() - 10);
|
||||
const recentDate = recent.toISOString().slice(0, 10);
|
||||
|
||||
const stateWithOld = SAMPLE_STATE.replace(
|
||||
'## Recent Posts\n\n',
|
||||
`## Recent Posts\n\n- [${oldDate}] "Old post..." (1000) - old topic\n- [${recentDate}] "Recent post..." (1200) - recent topic\n`
|
||||
);
|
||||
|
||||
const result = pruneContentHistory(stateWithOld, 90);
|
||||
assert.notEqual(result, null);
|
||||
assert.equal(result.pruned, 1);
|
||||
assert.ok(!result.content.includes(oldDate));
|
||||
assert.ok(result.content.includes(recentDate));
|
||||
});
|
||||
|
||||
test('preserves entries within 90 days', () => {
|
||||
const today = new Date();
|
||||
const recent = new Date(today);
|
||||
recent.setDate(recent.getDate() - 30);
|
||||
const recentDate = recent.toISOString().slice(0, 10);
|
||||
|
||||
const stateWithRecent = SAMPLE_STATE.replace(
|
||||
'## Recent Posts\n\n',
|
||||
`## Recent Posts\n\n- [${recentDate}] "Recent post..." (1200) - topic\n`
|
||||
);
|
||||
|
||||
const result = pruneContentHistory(stateWithRecent, 90);
|
||||
assert.equal(result, null); // nothing to prune
|
||||
});
|
||||
|
||||
test('returns null when no entries exist', () => {
|
||||
const emptyRecent = SAMPLE_STATE.replace(
|
||||
/## Recent Posts\n\n[\s\S]*?(?=## Session Notes)/,
|
||||
'## Recent Posts\n\n'
|
||||
);
|
||||
const result = pruneContentHistory(emptyRecent, 90);
|
||||
assert.equal(result, null);
|
||||
});
|
||||
|
||||
test('handles custom maxAgeDays', () => {
|
||||
const today = new Date();
|
||||
const old = new Date(today);
|
||||
old.setDate(old.getDate() - 40);
|
||||
const oldDate = old.toISOString().slice(0, 10);
|
||||
|
||||
const stateWithOld = SAMPLE_STATE.replace(
|
||||
'## Recent Posts\n\n',
|
||||
`## Recent Posts\n\n- [${oldDate}] "Somewhat old..." (1000) - topic\n`
|
||||
);
|
||||
|
||||
const result = pruneContentHistory(stateWithOld, 30);
|
||||
assert.notEqual(result, null);
|
||||
assert.equal(result.pruned, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateFollowerCount', () => {
|
||||
test('updates follower_count', () => {
|
||||
const result = updateFollowerCount(SAMPLE_STATE, {
|
||||
count: 920,
|
||||
month: '2026-04'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.match(result.content, /^follower_count: 920$/m);
|
||||
});
|
||||
|
||||
test('recalculates growth_rate_needed', () => {
|
||||
const result = updateFollowerCount(SAMPLE_STATE, {
|
||||
count: 920,
|
||||
month: '2026-04'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
const match = result.content.match(/^growth_rate_needed: (\d+)$/m);
|
||||
assert.ok(match, 'growth_rate_needed should be present');
|
||||
const rate = parseInt(match[1], 10);
|
||||
assert.ok(rate > 0, 'growth_rate_needed should be positive');
|
||||
});
|
||||
|
||||
test('appends to Milestone Log section', () => {
|
||||
const result = updateFollowerCount(SAMPLE_STATE, {
|
||||
count: 920,
|
||||
month: '2026-04'
|
||||
});
|
||||
assert.notEqual(result, null);
|
||||
assert.ok(result.content.includes('[2026-04] 920 (+70)'));
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
// Deterministic state mutation functions for linkedin-thought-leadership 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).
|
||||
|
||||
import { readFileSync, writeFileSync, renameSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
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');
|
||||
|
||||
function replaceField(content, field, value) {
|
||||
return content.replace(
|
||||
new RegExp(`^${field}: .*`, 'm'),
|
||||
`${field}: ${value}`
|
||||
);
|
||||
}
|
||||
|
||||
function isoWeekFromDate(dateStr) {
|
||||
const d = new Date(dateStr + 'T12:00:00Z');
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
const weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
|
||||
return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function daysBetween(dateA, dateB) {
|
||||
const a = new Date(dateA + 'T12:00:00Z').getTime();
|
||||
const b = new Date(dateB + 'T12:00:00Z').getTime();
|
||||
if (isNaN(a) || isNaN(b)) return null;
|
||||
return Math.abs(Math.round((b - a) / 86400000));
|
||||
}
|
||||
|
||||
function extractField(content, field) {
|
||||
const re = new RegExp(`^${field}: *"?([^"\\n]*)"?`, 'm');
|
||||
const m = content.match(re);
|
||||
return m ? m[1].trim() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update post tracking fields deterministically.
|
||||
* @param {string} stateContent - Full state file content
|
||||
* @param {{ postDate: string, postTopic: string, hookText: string, charCount: number, format: string }} opts
|
||||
* @returns {{ content: string, changes: string[] } | null}
|
||||
*/
|
||||
export function updatePostTracking(stateContent, { postDate, postTopic, hookText, charCount, format }) {
|
||||
let content = stateContent;
|
||||
const changes = [];
|
||||
|
||||
// 1. Update last_post_date
|
||||
content = replaceField(content, 'last_post_date', `"${postDate}"`);
|
||||
changes.push(`last_post_date → ${postDate}`);
|
||||
|
||||
// 2. Update last_post_topic
|
||||
content = replaceField(content, 'last_post_topic', `"${postTopic}"`);
|
||||
changes.push(`last_post_topic → ${postTopic}`);
|
||||
|
||||
// 3. Set first_post_date if null
|
||||
const existingFirst = extractField(content, 'first_post_date');
|
||||
if (!existingFirst || existingFirst === 'null') {
|
||||
content = replaceField(content, 'first_post_date', `"${postDate}"`);
|
||||
changes.push(`first_post_date → ${postDate} (first post!)`);
|
||||
}
|
||||
|
||||
// 4. Week rollover — check if ISO week changed
|
||||
const currentWeek = extractField(content, 'current_week');
|
||||
const postWeek = isoWeekFromDate(postDate);
|
||||
const rollover = applyWeekRollover(content, currentWeek, postWeek);
|
||||
if (rollover) {
|
||||
content = rollover.content;
|
||||
changes.push(rollover.message);
|
||||
}
|
||||
|
||||
// 5. Increment posts_this_week
|
||||
const currentPosts = parseInt(extractField(content, 'posts_this_week') || '0', 10);
|
||||
content = replaceField(content, 'posts_this_week', String(currentPosts + 1));
|
||||
changes.push(`posts_this_week → ${currentPosts + 1}`);
|
||||
|
||||
// 6. Update streak
|
||||
const lastPostDate = extractField(stateContent, 'last_post_date');
|
||||
let currentStreak = parseInt(extractField(content, 'current_streak') || '0', 10);
|
||||
|
||||
if (lastPostDate && lastPostDate !== 'null') {
|
||||
const gap = daysBetween(lastPostDate, postDate);
|
||||
if (gap !== null && gap <= 2) {
|
||||
currentStreak += 1;
|
||||
changes.push(`current_streak → ${currentStreak} (gap: ${gap}d)`);
|
||||
} else {
|
||||
currentStreak = 1;
|
||||
changes.push(`current_streak → 1 (gap: ${gap}d, reset)`);
|
||||
}
|
||||
} else {
|
||||
currentStreak = 1;
|
||||
changes.push('current_streak → 1 (first post)');
|
||||
}
|
||||
content = replaceField(content, 'current_streak', String(currentStreak));
|
||||
|
||||
// 7. Update longest_streak if exceeded
|
||||
const longestStreak = parseInt(extractField(content, 'longest_streak') || '0', 10);
|
||||
if (currentStreak > longestStreak) {
|
||||
content = replaceField(content, 'longest_streak', String(currentStreak));
|
||||
changes.push(`longest_streak → ${currentStreak}`);
|
||||
}
|
||||
|
||||
// 8. Append to Recent Posts section
|
||||
const hookPreview = hookText.length > 60 ? hookText.slice(0, 57) + '...' : hookText;
|
||||
const entry = `- [${postDate}] "${hookPreview}" (${charCount}) - ${postTopic}`;
|
||||
content = content.replace(
|
||||
/^(## Recent Posts\n\n?)/m,
|
||||
`$1${entry}\n`
|
||||
);
|
||||
changes.push(`Recent Posts += ${postDate} "${hookPreview.slice(0, 30)}..."`);
|
||||
|
||||
if (content === stateContent) return null;
|
||||
return { content, changes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Recent Posts entries older than maxAgeDays.
|
||||
* @param {string} stateContent - Full state file content
|
||||
* @param {number} [maxAgeDays=90]
|
||||
* @returns {{ content: string, pruned: number } | null}
|
||||
*/
|
||||
export function pruneContentHistory(stateContent, maxAgeDays = 90) {
|
||||
const today = new Date();
|
||||
const cutoff = new Date(today);
|
||||
cutoff.setDate(cutoff.getDate() - maxAgeDays);
|
||||
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
||||
|
||||
// Find all Recent Posts entries
|
||||
const entryPattern = /^- \[(\d{4}-\d{2}-\d{2})\] .+$/gm;
|
||||
const recentSection = stateContent.match(/## Recent Posts\n\n?([\s\S]*?)(?=\n## [^R]|\n## $|$)/m);
|
||||
if (!recentSection || !recentSection[1].trim()) return null;
|
||||
|
||||
const sectionContent = recentSection[1];
|
||||
let pruned = 0;
|
||||
const lines = sectionContent.split('\n');
|
||||
const kept = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const dateMatch = line.match(/^- \[(\d{4}-\d{2}-\d{2})\]/);
|
||||
if (dateMatch) {
|
||||
if (dateMatch[1] < cutoffStr) {
|
||||
pruned++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
kept.push(line);
|
||||
}
|
||||
|
||||
if (pruned === 0) return null;
|
||||
|
||||
const newSection = kept.join('\n');
|
||||
const content = stateContent.replace(recentSection[1], newSection);
|
||||
return { content, pruned };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update follower count and recalculate growth metrics.
|
||||
* @param {string} stateContent - Full state file content
|
||||
* @param {{ count: number, month: string }} opts
|
||||
* @returns {{ content: string, changes: string[] } | null}
|
||||
*/
|
||||
export function updateFollowerCount(stateContent, { count, month }) {
|
||||
let content = stateContent;
|
||||
const changes = [];
|
||||
|
||||
const previousCount = parseInt(extractField(content, 'follower_count') || '0', 10);
|
||||
const delta = count - previousCount;
|
||||
|
||||
// Update follower_count
|
||||
content = replaceField(content, 'follower_count', String(count));
|
||||
changes.push(`follower_count → ${count} (${delta >= 0 ? '+' : ''}${delta})`);
|
||||
|
||||
// Recalculate growth_rate_needed
|
||||
const target = parseInt(extractField(content, 'follower_target') || '10000', 10);
|
||||
const targetDate = extractField(content, 'target_date');
|
||||
const remaining = target - count;
|
||||
|
||||
if (targetDate && targetDate !== 'null' && targetDate !== '""') {
|
||||
const [tYear, tMonth] = targetDate.split('-').map(Number);
|
||||
const [mYear, mMonth] = month.split('-').map(Number);
|
||||
const monthsLeft = (tYear - mYear) * 12 + (tMonth - mMonth);
|
||||
const effectiveMonths = Math.max(1, monthsLeft);
|
||||
const rateNeeded = Math.ceil(remaining / effectiveMonths);
|
||||
content = replaceField(content, 'growth_rate_needed', String(rateNeeded));
|
||||
changes.push(`growth_rate_needed → ${rateNeeded}/month`);
|
||||
}
|
||||
|
||||
// Append to Milestone Log section
|
||||
const logEntry = `- [${month}] ${count} (${delta >= 0 ? '+' : ''}${delta})`;
|
||||
content = content.replace(
|
||||
/^(## Milestone Log\n)/m,
|
||||
`$1${logEntry}\n`
|
||||
);
|
||||
changes.push(`Milestone Log += ${month}`);
|
||||
|
||||
if (content === stateContent) return null;
|
||||
return { content, changes };
|
||||
}
|
||||
|
||||
/**
|
||||
* I/O wrapper: read state file, apply update function, write atomically.
|
||||
* @param {function(string): {content: string}|null} updateFn - Pure update function
|
||||
*/
|
||||
export function writeState(updateFn) {
|
||||
const content = readFileSync(STATE_FILE, 'utf-8');
|
||||
const result = updateFn(content);
|
||||
if (!result) {
|
||||
console.log('No changes needed.');
|
||||
return;
|
||||
}
|
||||
const tmpPath = STATE_FILE + '.tmp';
|
||||
writeFileSync(tmpPath, result.content, 'utf-8');
|
||||
renameSync(tmpPath, STATE_FILE);
|
||||
if (result.changes) {
|
||||
console.log('State updated:', result.changes.join(', '));
|
||||
} else if (result.pruned !== undefined) {
|
||||
console.log(`Pruned ${result.pruned} old entries.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone mode
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes('--update-post')) {
|
||||
const getArg = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : ''; };
|
||||
writeState(content => updatePostTracking(content, {
|
||||
postDate: getArg('--date') || new Date().toISOString().slice(0, 10),
|
||||
postTopic: getArg('--topic') || 'unknown',
|
||||
hookText: getArg('--hook') || '',
|
||||
charCount: parseInt(getArg('--chars') || '0', 10),
|
||||
format: getArg('--format') || 'post'
|
||||
}));
|
||||
} else if (args.includes('--prune')) {
|
||||
const days = parseInt(args[args.indexOf('--prune') + 1] || '90', 10);
|
||||
writeState(content => pruneContentHistory(content, days));
|
||||
} else if (args.includes('--update-followers')) {
|
||||
const getArg = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : ''; };
|
||||
writeState(content => updateFollowerCount(content, {
|
||||
count: parseInt(getArg('--count') || '0', 10),
|
||||
month: getArg('--month') || new Date().toISOString().slice(0, 7)
|
||||
}));
|
||||
} else {
|
||||
console.log('Usage:');
|
||||
console.log(' node state-updater.mjs --update-post --date YYYY-MM-DD --topic "topic" --hook "Hook text" --chars 1500 --format post');
|
||||
console.log(' node state-updater.mjs --prune [days]');
|
||||
console.log(' node state-updater.mjs --update-followers --count 920 --month 2026-04');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue