Resources
5Install
npx skillscat add oyi77/1ai-skills/joko-moltbook Install via the SkillsCat registry.
SKILL.md
Joko Moltbook Agent
Queue-driven Moltbook posting agent with deduplication, idempotency, and monitoring.
Required Tools
MCP Servers
{
"mcpServers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/mcp-server"],
"env": { "APIFY_API_TOKEN": "${APIFY_API_TOKEN}" }
},
"notion": {
"command": "npx",
"args": ["-y", "@makenotion/mcp-server"],
"env": { "NOTION_API_KEY": "${NOTION_API_KEY}" }
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" }
}
}
}Authentication
Setup
Apify Token
export APIFY_API_TOKEN="your-token"Notion (for content queue)
- Create integration and share queue database
Slack (for alerts)
export SLACK_BOT_TOKEN="xoxb-your-token"
Pseudo Code
Example 1: Queue-Driven Posting
// 1. Fetch pending posts from Notion queue
const queue = await notion.query("Content Queue", {
filter: { status: "pending" }
});
// 2. Process each with idempotency check
for (const item of queue) {
const key = generateIdempotencyKey(item.content);
// Check if already posted
const exists = await cache.get(key);
if (exists) {
console.log(`Skipping duplicate: ${item.id}`);
continue;
}
// Post to Moltbook
const result = await moltbook.post(item.content);
// Mark as posted
await cache.set(key, result.postId);
await notion.updatePage(item.id, { status: "posted" });
}Example 2: Deduplication with Content Hash
// Generate deterministic hash for content
function hashContent(content) {
return sha256(content.text + content.tags.sort().join());
}
// Check before posting
const contentHash = hashContent(newPost);
const existing = await db.posts.findOne({ contentHash });
if (existing) {
console.log("Duplicate detected, skipping");
return { status: "duplicate", existingPost: existing };
}
const result = await moltbook.post(newPost);
await db.posts.insert({ contentHash, postId: result.id });Example 3: Monitor and Alert
// 1. Monitor posting health
const stats = await getPostingStats();
// 2. Check metrics
if (stats.failureRate > 0.1) {
await slack.alert({
channel: "#alerts",
text: `High failure rate: ${stats.failureRate * 100}%`
});
}
// 3. Daily summary
await slack.notify("#daily-reports", `
Posts: ${stats.total}
Success: ${stats.success}
Failed: ${stats.failed}
`);CLI Reference
| Command | Description |
|---|---|
joko queue status |
Check queue size |
joko post now |
Process queue immediately |
joko stats |
Show posting statistics |
Error Handling
| Error Code | Meaning | Fix |
|---|---|---|
AUTH_001 |
Session expired | Refresh credentials |
RATE_001 |
Rate limited | Backoff 10 min |
DUPLICATE_001 |
Duplicate content | Skip, mark handled |
QUEUE_001 |
Queue empty | Nothing to do |
Common Patterns
Idempotency Key Generation
function generateIdempotencyKey(item) {
return `moltbook:${sha256(item.text + item.createdAt)}`;
}Exponential Backoff
async function withBackoff(fn, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.min(1000 * Math.pow(2, i), 60000);
await sleep(delay);
}
}
}Skill v2.0 - Joko Moltbook