← back to Tiktok Architect
scripts/tt-list.js
81 lines
#!/usr/bin/env node
/**
* tt-list.js — list a TikTok creator's most-recent videos and flag the ones
* that are actually about Claude Code / AI-coding (the "what's new" scan).
*
* @tiktok_architect posts a MIXED feed — motorcycles, hobbies, privacy, AND
* Claude Code / vibecoding tips. This enumerates recent uploads via yt-dlp's
* flat playlist (id + caption, no download, $0) and marks each `claude_relevant`
* by hashtag/keyword so the scout only spends effort on the on-topic ones.
*
* Usage:
* node tt-list.js [handle] [count]
* handle TikTok handle without @ (default: tiktok_architect)
* count how many newest videos to list (default: 12)
*
* Requires yt-dlp on PATH. TikTok may throttle; on failure this exits non-zero
* with a hint to fall back to the mirrors in references/retrieval-playbook.md.
* yt-dlp prints an "attempting impersonation" WARNING even on success — that is
* NOT an error. If downloads later fail, install a curl_cffi impersonate target.
*
* Output: JSON array on stdout, newest first:
* [{ id, url, caption, hashtags[], claude_relevant }]
*/
'use strict';
const { spawnSync } = require('child_process');
const RELEVANCE_RE =
/\b(claude|claudecode|claude-code|anthropic|vibecod|cursor|codex|copilot|chatgpt|openai|\bllm\b|\bai\b|aitools|aiagent|agentic|mcp|prompt|coding|developer|softwareengineer|programming)\b/i;
function hashtagsFrom(caption) {
const tags = new Set();
const re = /#([\p{L}\p{N}_]+)/gu;
let m;
while ((m = re.exec(caption || ''))) tags.add(m[1].toLowerCase());
return [...tags];
}
function main() {
const handle = (process.argv[2] || 'tiktok_architect').replace(/^@/, '');
const count = Math.max(1, parseInt(process.argv[3] || '12', 10) || 12);
const url = `https://www.tiktok.com/@${handle}`;
const r = spawnSync(
'yt-dlp',
['--flat-playlist', '--playlist-end', String(count), '--dump-json', url],
{ encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, timeout: 90_000 }
);
if (r.error && r.error.code === 'ENOENT') {
console.error('yt-dlp not found on PATH. brew install yt-dlp — or use the mirror path in references/retrieval-playbook.md.');
process.exit(2);
}
const rows = [];
for (const line of (r.stdout || '').split('\n')) {
if (!line.trim()) continue;
let d;
try { d = JSON.parse(line); } catch { continue; }
const caption = d.title || d.description || '';
rows.push({
id: d.id || null,
url: d.url || (d.id ? `https://www.tiktok.com/@${handle}/video/${d.id}` : null),
caption,
hashtags: hashtagsFrom(caption),
claude_relevant: RELEVANCE_RE.test(caption),
});
}
if (!rows.length) {
console.error('yt-dlp returned 0 videos (throttle/block or empty feed). Fall back to mirrors — do NOT conclude "no new tips".');
if (r.stderr) console.error(r.stderr.split('\n').slice(-3).join('\n'));
process.exit(3);
}
console.log(JSON.stringify(rows, null, 2));
}
main();