suparank 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/suparank.js
CHANGED
|
@@ -15,9 +15,74 @@ import * as fs from 'fs'
|
|
|
15
15
|
import * as path from 'path'
|
|
16
16
|
import * as os from 'os'
|
|
17
17
|
import * as readline from 'readline'
|
|
18
|
-
import { spawn } from 'child_process'
|
|
18
|
+
import { spawn, execSync } from 'child_process'
|
|
19
|
+
import { fileURLToPath } from 'url'
|
|
19
20
|
|
|
20
21
|
const SUPARANK_DIR = path.join(os.homedir(), '.suparank')
|
|
22
|
+
const VERSION_CACHE_FILE = path.join(SUPARANK_DIR, '.version-check')
|
|
23
|
+
|
|
24
|
+
// Get current package version
|
|
25
|
+
function getCurrentVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const packagePath = path.join(import.meta.dirname, '..', 'package.json')
|
|
28
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
|
|
29
|
+
return pkg.version
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check for updates (non-blocking, cached for 1 hour)
|
|
36
|
+
async function checkForUpdates() {
|
|
37
|
+
const currentVersion = getCurrentVersion()
|
|
38
|
+
if (!currentVersion) return
|
|
39
|
+
|
|
40
|
+
// Check cache to avoid spamming npm registry
|
|
41
|
+
try {
|
|
42
|
+
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
|
43
|
+
const cache = JSON.parse(fs.readFileSync(VERSION_CACHE_FILE, 'utf-8'))
|
|
44
|
+
const cacheAge = Date.now() - cache.timestamp
|
|
45
|
+
if (cacheAge < 3600000) { // 1 hour cache
|
|
46
|
+
if (cache.latest !== currentVersion && cache.latest > currentVersion) {
|
|
47
|
+
console.error(`[suparank] Update available: ${currentVersion} → ${cache.latest}`)
|
|
48
|
+
}
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
|
|
54
|
+
// Fetch latest version from npm (with timeout)
|
|
55
|
+
try {
|
|
56
|
+
const controller = new AbortController()
|
|
57
|
+
const timeout = setTimeout(() => controller.abort(), 3000) // 3 second timeout
|
|
58
|
+
|
|
59
|
+
const response = await fetch('https://registry.npmjs.org/suparank/latest', {
|
|
60
|
+
signal: controller.signal
|
|
61
|
+
})
|
|
62
|
+
clearTimeout(timeout)
|
|
63
|
+
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
const data = await response.json()
|
|
66
|
+
const latestVersion = data.version
|
|
67
|
+
|
|
68
|
+
// Cache the result
|
|
69
|
+
fs.mkdirSync(SUPARANK_DIR, { recursive: true })
|
|
70
|
+
fs.writeFileSync(VERSION_CACHE_FILE, JSON.stringify({
|
|
71
|
+
latest: latestVersion,
|
|
72
|
+
current: currentVersion,
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
}))
|
|
75
|
+
|
|
76
|
+
if (latestVersion !== currentVersion && latestVersion > currentVersion) {
|
|
77
|
+
console.error(`[suparank] Update available: ${currentVersion} → ${latestVersion}`)
|
|
78
|
+
console.error('[suparank] Run: npx suparank@latest OR npx clear-npx-cache')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Silently fail - don't block MCP startup
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
21
86
|
const CONFIG_FILE = path.join(SUPARANK_DIR, 'config.json')
|
|
22
87
|
const CREDENTIALS_FILE = path.join(SUPARANK_DIR, 'credentials.json')
|
|
23
88
|
const SESSION_FILE = path.join(SUPARANK_DIR, 'session.json')
|
|
@@ -35,7 +100,7 @@ const colors = {
|
|
|
35
100
|
}
|
|
36
101
|
|
|
37
102
|
// Check if running in MCP mode (no command argument = MCP server)
|
|
38
|
-
const isMCPMode = !process.argv[2] || !['setup', 'test', 'session', 'clear', 'help', '--help', '-h'].includes(process.argv[2])
|
|
103
|
+
const isMCPMode = !process.argv[2] || !['setup', 'test', 'session', 'clear', 'update', 'version', '-v', '--version', 'help', '--help', '-h'].includes(process.argv[2])
|
|
39
104
|
|
|
40
105
|
function log(message, color = 'reset') {
|
|
41
106
|
// In MCP mode, use stderr to avoid breaking JSON protocol
|
|
@@ -344,7 +409,10 @@ function clearSession() {
|
|
|
344
409
|
}
|
|
345
410
|
}
|
|
346
411
|
|
|
347
|
-
function runMCP() {
|
|
412
|
+
async function runMCP() {
|
|
413
|
+
// Check for updates in background (non-blocking)
|
|
414
|
+
checkForUpdates()
|
|
415
|
+
|
|
348
416
|
const config = loadConfig()
|
|
349
417
|
|
|
350
418
|
if (!config) {
|
|
@@ -412,6 +480,27 @@ switch (command) {
|
|
|
412
480
|
case 'clear':
|
|
413
481
|
clearSession()
|
|
414
482
|
break
|
|
483
|
+
case 'update':
|
|
484
|
+
logHeader('Updating Suparank')
|
|
485
|
+
log('Clearing npx cache and fetching latest version...', 'yellow')
|
|
486
|
+
try {
|
|
487
|
+
execSync('rm -rf ~/.npm/_npx', { stdio: 'inherit' })
|
|
488
|
+
log('Cache cleared!', 'green')
|
|
489
|
+
log('Next run will use the latest version.', 'dim')
|
|
490
|
+
// Also clear version cache
|
|
491
|
+
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
|
492
|
+
fs.unlinkSync(VERSION_CACHE_FILE)
|
|
493
|
+
}
|
|
494
|
+
} catch (e) {
|
|
495
|
+
log(`Update failed: ${e.message}`, 'red')
|
|
496
|
+
}
|
|
497
|
+
break
|
|
498
|
+
case 'version':
|
|
499
|
+
case '-v':
|
|
500
|
+
case '--version':
|
|
501
|
+
const ver = getCurrentVersion()
|
|
502
|
+
console.log(ver || 'unknown')
|
|
503
|
+
break
|
|
415
504
|
case 'help':
|
|
416
505
|
case '--help':
|
|
417
506
|
case '-h':
|
|
@@ -424,6 +513,8 @@ switch (command) {
|
|
|
424
513
|
log(' test Test API connection', 'dim')
|
|
425
514
|
log(' session View current session state', 'dim')
|
|
426
515
|
log(' clear Clear session state', 'dim')
|
|
516
|
+
log(' update Clear cache and update to latest', 'dim')
|
|
517
|
+
log(' version Show current version', 'dim')
|
|
427
518
|
log(' help Show this help message', 'dim')
|
|
428
519
|
break
|
|
429
520
|
default:
|
|
@@ -205,7 +205,16 @@ async function handleSaveContent(args) {
|
|
|
205
205
|
const wordCountOk = targetWordCount ? wordCount >= targetWordCount * 0.95 : true
|
|
206
206
|
const shortfall = targetWordCount ? targetWordCount - wordCount : 0
|
|
207
207
|
|
|
208
|
+
// Multi-article progress tracking
|
|
209
|
+
const totalExpected = workflow?.settings?.article_count || 1
|
|
210
|
+
const savedCount = sessionState.articles.length
|
|
211
|
+
const remaining = totalExpected - savedCount
|
|
212
|
+
const isMultiArticle = totalExpected > 1
|
|
213
|
+
|
|
208
214
|
log(`Word count check: ${wordCount} words (target: ${targetWordCount}, ok: ${wordCountOk})`)
|
|
215
|
+
if (isMultiArticle) {
|
|
216
|
+
log(`Multi-article progress: ${savedCount}/${totalExpected} saved, ${remaining} remaining`)
|
|
217
|
+
}
|
|
209
218
|
|
|
210
219
|
// Find next step info
|
|
211
220
|
const imageStep = workflow?.steps?.find(s => s.action === 'generate_images')
|
|
@@ -261,13 +270,28 @@ Please EXPAND the content before publishing.
|
|
|
261
270
|
` : ''}
|
|
262
271
|
${!meta_description ? '**Warning:** Meta description is missing. Add it for better SEO.\n' : ''}
|
|
263
272
|
${articlesListSection}${categoriesSection}
|
|
264
|
-
|
|
273
|
+
${isMultiArticle && remaining > 0 ? `## ⚠️ MULTI-ARTICLE WORKFLOW: ${remaining} Article(s) Remaining
|
|
274
|
+
|
|
275
|
+
**Progress:** ${savedCount} of ${totalExpected} articles saved.
|
|
276
|
+
|
|
277
|
+
**NEXT ACTION REQUIRED:**
|
|
278
|
+
1. Create article ${savedCount + 1} of ${totalExpected} using topic from content calendar
|
|
279
|
+
2. Call \`content_write\` or write the article directly
|
|
280
|
+
3. Call \`save_content\` to save it
|
|
281
|
+
|
|
282
|
+
⛔ Do NOT publish until all ${totalExpected} articles are saved!
|
|
283
|
+
` : ''}${isMultiArticle && remaining <= 0 ? `## ✅ ALL ${totalExpected} ARTICLES SAVED!
|
|
284
|
+
|
|
285
|
+
All articles in your batch are complete. Ready to publish.
|
|
286
|
+
|
|
287
|
+
**Next:** Call \`publish_content\` to publish all ${savedCount} articles.
|
|
288
|
+
` : ''}${!isMultiArticle ? `## Next Step${includeImages && imageStep ? ': Generate Images' : ': Ready to Publish or Continue'}
|
|
265
289
|
${includeImages && imageStep ? `Generate **${totalImages} images** (1 cover + ${totalImages - 1} inline images).
|
|
266
290
|
|
|
267
291
|
Call \`generate_image\` ${totalImages} times with prompts based on your article sections.` : `You can:
|
|
268
292
|
- **Add more articles**: Continue creating content (each save_content adds to the batch)
|
|
269
293
|
- **Publish all**: Call \`publish_content\` to publish all ${sessionState.articles.length} article(s)
|
|
270
|
-
- **View session**: Call \`get_session\` to see all saved articles`}`
|
|
294
|
+
- **View session**: Call \`get_session\` to see all saved articles`}` : ''}`
|
|
271
295
|
}]
|
|
272
296
|
}
|
|
273
297
|
}
|
|
@@ -465,6 +489,11 @@ function handleGetSession() {
|
|
|
465
489
|
return sum + (a.imageUrl ? 1 : 0) + (a.inlineImages?.length || 0)
|
|
466
490
|
}, 0)
|
|
467
491
|
|
|
492
|
+
// Multi-article workflow progress
|
|
493
|
+
const expectedArticles = workflow?.settings?.article_count || 1
|
|
494
|
+
const isMultiArticle = expectedArticles > 1
|
|
495
|
+
const remainingArticles = expectedArticles - totalArticles
|
|
496
|
+
|
|
468
497
|
const articlesSection = sessionState.articles.length > 0 ? `
|
|
469
498
|
## Saved Articles (${totalArticles} total)
|
|
470
499
|
|
|
@@ -494,16 +523,27 @@ No articles saved yet. Use \`save_content\` after writing an article.
|
|
|
494
523
|
*This article is being edited. Call \`save_content\` to add it to the session.*
|
|
495
524
|
` : ''
|
|
496
525
|
|
|
526
|
+
// Multi-article progress section
|
|
527
|
+
const multiArticleProgress = isMultiArticle ? `
|
|
528
|
+
## 📊 Multi-Article Workflow Progress
|
|
529
|
+
**Status:** ${totalArticles}/${expectedArticles} articles saved${remainingArticles > 0 ? ` (${remainingArticles} remaining)` : ' ✅ Complete!'}
|
|
530
|
+
${remainingArticles > 0 ? `
|
|
531
|
+
⚠️ **NEXT:** Create article ${totalArticles + 1} of ${expectedArticles} and save it.
|
|
532
|
+
⛔ Do NOT publish until all ${expectedArticles} articles are saved.
|
|
533
|
+
` : `
|
|
534
|
+
✅ All articles saved! Ready to call \`publish_content\`.
|
|
535
|
+
`}` : ''
|
|
536
|
+
|
|
497
537
|
return {
|
|
498
538
|
content: [{
|
|
499
539
|
type: 'text',
|
|
500
540
|
text: `# Session State
|
|
501
541
|
|
|
502
542
|
**Workflow:** ${workflow?.workflow_id || 'None active'}
|
|
503
|
-
**Total Articles:** ${totalArticles}
|
|
543
|
+
**Total Articles:** ${totalArticles}${isMultiArticle ? ` / ${expectedArticles} expected` : ''}
|
|
504
544
|
**Ready to Publish:** ${unpublishedArticles.length}
|
|
505
545
|
**Already Published:** ${publishedArticles.length}
|
|
506
|
-
${articlesSection}${currentWorkingSection}
|
|
546
|
+
${multiArticleProgress}${articlesSection}${currentWorkingSection}
|
|
507
547
|
## Current Working Images (${imagesGenerated}/${totalImagesNeeded})
|
|
508
548
|
**Cover Image:** ${sessionState.imageUrl || 'Not generated'}
|
|
509
549
|
**Inline Images:** ${sessionState.inlineImages.length > 0 ? sessionState.inlineImages.map((url, i) => `\n ${i+1}. ${url.substring(0, 60)}...`).join('') : 'None'}
|
|
@@ -428,13 +428,22 @@ TRIGGERS - Use when user says:
|
|
|
428
428
|
- "make content about..."
|
|
429
429
|
- any request involving writing/creating/generating articles or blog posts
|
|
430
430
|
|
|
431
|
+
SINGLE ARTICLE (count=1): Creates, saves, and publishes 1 article automatically.
|
|
432
|
+
|
|
433
|
+
MULTIPLE ARTICLES (count>1): Creates a workflow with N separate article steps.
|
|
434
|
+
- Each article MUST be written and saved with save_content
|
|
435
|
+
- Progress tracked: "Article 1 of 5", "Article 2 of 5", etc.
|
|
436
|
+
- get_session shows "3/5 articles saved"
|
|
437
|
+
- Do NOT publish until ALL articles are saved
|
|
438
|
+
- After all saved, call publish_content to publish batch
|
|
439
|
+
|
|
431
440
|
WORKFLOW (automatic 4-phase):
|
|
432
|
-
1. RESEARCH: Keywords, SEO strategy, content
|
|
433
|
-
2. CREATION:
|
|
434
|
-
3. OPTIMIZATION: Quality check, GEO optimization
|
|
435
|
-
4. PUBLISHING: Generate images, publish to
|
|
441
|
+
1. RESEARCH: Keywords, SEO strategy, content calendar
|
|
442
|
+
2. CREATION: (write + save) × N articles
|
|
443
|
+
3. OPTIMIZATION: Quality check, GEO optimization
|
|
444
|
+
4. PUBLISHING: Generate images, publish all to CMS
|
|
436
445
|
|
|
437
|
-
OUTCOME: Complete article written, optimized, and published to CMS.`,
|
|
446
|
+
OUTCOME: Complete article(s) written, optimized, and published to CMS.`,
|
|
438
447
|
inputSchema: {
|
|
439
448
|
type: 'object',
|
|
440
449
|
properties: {
|
|
@@ -291,14 +291,20 @@ Use format: [IMAGE: description of what image should show]` : '**Note:** Images
|
|
|
291
291
|
store: 'outline'
|
|
292
292
|
})
|
|
293
293
|
|
|
294
|
-
// Step: Write Content
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
action: 'content_write',
|
|
300
|
-
instruction: `Write the COMPLETE article following your outline.
|
|
294
|
+
// Step: Write Content (generates N steps for N articles)
|
|
295
|
+
for (let articleIndex = 0; articleIndex < count; articleIndex++) {
|
|
296
|
+
const articleNum = articleIndex + 1
|
|
297
|
+
const isFirstArticle = articleIndex === 0
|
|
298
|
+
const isLastArticle = articleIndex === count - 1
|
|
301
299
|
|
|
300
|
+
stepNum++
|
|
301
|
+
steps.push({
|
|
302
|
+
step: stepNum,
|
|
303
|
+
type: 'llm_execute',
|
|
304
|
+
action: 'content_write',
|
|
305
|
+
instruction: `${count > 1 ? `## 📝 ARTICLE ${articleNum} OF ${count}\n\n` : ''}Write the COMPLETE article following your outline.
|
|
306
|
+
${count > 1 && !isFirstArticle ? `\n**Progress:** ${articleIndex} article(s) already saved. Now creating article ${articleNum}.` : ''}
|
|
307
|
+
${count > 1 ? `**Topic:** Use topic #${articleNum} from your content calendar.\n` : ''}
|
|
302
308
|
**MANDATORY WORD COUNT: ${targetWordCount} WORDS MINIMUM**
|
|
303
309
|
This is a strict requirement from the project settings.
|
|
304
310
|
The article will be REJECTED if under ${targetWordCount} words.
|
|
@@ -323,16 +329,19 @@ ${shouldGenerateImages ? '- Image placeholders: [IMAGE: description] where image
|
|
|
323
329
|
- FAQ section with 5-8 Q&As (detailed answers, not one-liners)
|
|
324
330
|
- Strong conclusion with clear CTA
|
|
325
331
|
|
|
326
|
-
**After writing ${targetWordCount}+ words, call 'save_content' with:**
|
|
332
|
+
**MANDATORY: After writing ${targetWordCount}+ words, call 'save_content' with:**
|
|
327
333
|
- title: Your SEO-optimized title
|
|
328
334
|
- content: The full article (markdown)
|
|
329
335
|
- keywords: Array of target keywords
|
|
330
336
|
- meta_description: Your 150-160 char meta description
|
|
331
337
|
|
|
332
338
|
STOP! Before calling save_content, verify you have ${targetWordCount}+ words.
|
|
333
|
-
Count the words. If under ${targetWordCount}, ADD MORE CONTENT
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
Count the words. If under ${targetWordCount}, ADD MORE CONTENT.
|
|
340
|
+
${count > 1 && !isLastArticle ? `\n⚠️ After saving this article, you MUST continue with article ${articleNum + 1} of ${count}. Do NOT publish until all ${count} articles are saved.` : ''}
|
|
341
|
+
${count > 1 && isLastArticle ? `\n✅ This is the LAST article (${articleNum} of ${count}). After saving, all articles will be ready for publishing.` : ''}`,
|
|
342
|
+
store: `article${count > 1 ? `_${articleNum}` : ''}`
|
|
343
|
+
})
|
|
344
|
+
}
|
|
336
345
|
|
|
337
346
|
// ═══════════════════════════════════════════════════════════
|
|
338
347
|
// OPTIMIZATION PHASE
|
|
@@ -490,6 +499,7 @@ Call 'publish_content' tool - it will automatically use:
|
|
|
490
499
|
niche: niche
|
|
491
500
|
},
|
|
492
501
|
settings: {
|
|
502
|
+
article_count: count, // Track expected article count for progress
|
|
493
503
|
target_word_count: targetWordCount,
|
|
494
504
|
reading_level: readingLevel,
|
|
495
505
|
reading_level_display: readingLevelDisplay,
|