rankcontrol 0.8.3 → 0.9.0

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/README.md CHANGED
@@ -43,7 +43,7 @@ npx rankcontrol query-edit <queryId> "new text"
43
43
  npx rankcontrol query-track <queryId> off # pause weekly checks (frees a slot)
44
44
  npx rankcontrol query-remove <queryId>
45
45
  npx rankcontrol competitors
46
- npx rankcontrol competitor-add "Acme" https://acme.com
46
+ npx rankcontrol competitor-add "Acme" # site resolved from the name; pass a URL to override
47
47
  npx rankcontrol competitor-remove <competitorId>
48
48
 
49
49
  # Content pipeline
@@ -96,8 +96,8 @@ npx rankcontrol network-remove-placement <placementId> --confirm
96
96
  npx rankcontrol repurpose [contentId] # published articles + social drafts (8 platforms)
97
97
  npx rankcontrol repurpose-generate <contentId> --platforms twitter --confirm
98
98
  npx rankcontrol repurpose-edit <draftId> --title "New hook"
99
- npx rankcontrol repurpose-channels # Postiz channel ids
100
- npx rankcontrol repurpose-push <draftId> --channels <id> --confirm
99
+ npx rankcontrol repurpose-channels # Postiz and Buffer channel ids
100
+ npx rankcontrol repurpose-push <draftId> --channels <id> --confirm # --scheduler postiz|buffer when both are connected
101
101
  npx rankcontrol repurpose-mark-posted <draftId>
102
102
  npx rankcontrol social # Reddit/X threads where your articles fit
103
103
  npx rankcontrol social-stats
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "rankcontrol",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "description": "RankControl CLI + MCP server: drive your SEO/AI-visibility workspace from the terminal or any AI agent",
5
5
  "license": "MIT",
6
+ "scripts": {
7
+ "prepublishOnly": "node ../../scripts/check-package-parity.mjs"
8
+ },
6
9
  "homepage": "https://rctrl.com",
7
10
  "type": "module",
8
11
  "bin": {
package/src/cli.mjs CHANGED
@@ -233,9 +233,9 @@ export function runCli(argv) {
233
233
 
234
234
  program
235
235
  .command("competitor-add")
236
- .description("Track a competitor (max 10)")
236
+ .description("Track a competitor (max 10); the site is resolved from the name when omitted")
237
237
  .argument("<name>", "Competitor name")
238
- .argument("<websiteUrl>", "Competitor website URL")
238
+ .argument("[websiteUrl]", "Competitor website URL (optional)")
239
239
  .action((name, websiteUrl) =>
240
240
  api.addCompetitor(name, websiteUrl).then(out).catch(fail)
241
241
  );
@@ -492,11 +492,10 @@ export function runCli(argv) {
492
492
 
493
493
  program
494
494
  .command("reschedule <contentId> <date>")
495
- .description("Move a planned article to a day (YYYY-MM-DD, local time; needs the flexible schedule)")
495
+ .description("Move a planned article to a day (YYYY-MM-DD in the workspace's timezone; needs the flexible schedule)")
496
496
  .action((contentId, date) => {
497
- const day = new Date(`${date}T00:00:00`);
498
- if (Number.isNaN(day.getTime())) return fail(new Error("Date must be YYYY-MM-DD"));
499
- return api.reschedule(contentId, day.getTime()).then(out).catch(fail);
497
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return fail(new Error("Date must be YYYY-MM-DD"));
498
+ return api.reschedule(contentId, date).then(out).catch(fail);
500
499
  });
501
500
 
502
501
  program
@@ -572,20 +571,22 @@ export function runCli(argv) {
572
571
 
573
572
  program
574
573
  .command("repurpose-channels")
575
- .description("Connected Postiz channels (ids needed for repurpose-push)")
574
+ .description("Connected Postiz and Buffer channels (ids needed for repurpose-push)")
576
575
  .action(() => api.repurposeChannels().then(out).catch(fail));
577
576
 
578
577
  program
579
578
  .command("repurpose-push <draftId>")
580
- .description("Send a draft to Postiz (dry run unless --confirm)")
581
- .option("--channels <id1,id2>", "Postiz channel ids (see repurpose-channels)")
582
- .option("--when <now|schedule|draft>", "Postiz mode", "now")
579
+ .description("Send a draft to Postiz or Buffer (dry run unless --confirm)")
580
+ .option("--scheduler <postiz|buffer>", "Which scheduler (required when both are connected)")
581
+ .option("--channels <id1,id2>", "Channel ids (see repurpose-channels)")
582
+ .option("--when <now|schedule|draft|queue>", "Mode; queue is Buffer only", "now")
583
583
  .option("--date <iso>", "ISO time for --when schedule")
584
584
  .option("--confirm", "Actually push (publicly visible)")
585
585
  .action((draftId, opts) =>
586
586
  api
587
587
  .repurposePush({
588
588
  draftId,
589
+ scheduler: opts.scheduler,
589
590
  integrationIds: opts.channels ? list(opts.channels) : undefined,
590
591
  scheduleType: opts.when,
591
592
  date: opts.date,
package/src/client.mjs CHANGED
@@ -124,8 +124,8 @@ export const api = {
124
124
  articleSettings: () => request("GET", "/api/v1/settings/articles"),
125
125
  updateArticleSettings: (opts) =>
126
126
  request("POST", "/api/v1/settings/articles", opts),
127
- reschedule: (contentId, targetDayStartMs) =>
128
- request("POST", "/api/v1/content/reschedule", { contentId, targetDayStartMs }),
127
+ reschedule: (contentId, date) =>
128
+ request("POST", "/api/v1/content/reschedule", { contentId, date }),
129
129
  sitePages: () => request("GET", "/api/v1/links/pages"),
130
130
  detectSiteLinks: (source, url) =>
131
131
  request("POST", "/api/v1/links/detect", { source, url }),
package/src/login.mjs CHANGED
@@ -13,13 +13,10 @@ const DEFAULT_SCOPES = [
13
13
  "read:content",
14
14
  "read:analytics",
15
15
  "write:content",
16
- "write:queries",
17
16
  "write:backlinks",
18
17
  "write:social",
19
18
  "write:publish",
20
- "manage:integrations",
21
19
  "manage:org",
22
- "trigger:agents",
23
20
  ];
24
21
 
25
22
  function openBrowser(url) {
package/src/mcp.mjs CHANGED
@@ -29,7 +29,7 @@ const plannedTitle = z.object({
29
29
  });
30
30
 
31
31
  export async function startMcpServer() {
32
- const server = new McpServer({ name: "rankcontrol", version: "0.8.3" });
32
+ const server = new McpServer({ name: "rankcontrol", version: "0.9.0" });
33
33
 
34
34
  server.tool(
35
35
  "get_overview_funnel",
@@ -236,10 +236,13 @@ export async function startMcpServer() {
236
236
 
237
237
  server.tool(
238
238
  "add_competitor",
239
- "Track a competitor (max 10). They enter weekly share-of-voice comparisons against the org's own citation rate.",
239
+ "Track a competitor (max 10). They enter weekly share-of-voice comparisons against the org's own citation rate. Omit websiteUrl to have the site resolved from the name.",
240
240
  {
241
241
  name: z.string().describe("Competitor name"),
242
- websiteUrl: z.string().describe("Competitor website URL"),
242
+ websiteUrl: z
243
+ .string()
244
+ .optional()
245
+ .describe("Competitor website URL (optional; resolved from the name when omitted)"),
243
246
  },
244
247
  run(({ name, websiteUrl }) => api.addCompetitor(name, websiteUrl))
245
248
  );
@@ -446,7 +449,7 @@ export async function startMcpServer() {
446
449
 
447
450
  const articleSettingsShape = {
448
451
  autoPublish: z.boolean().optional(),
449
- autoGenerate: z.boolean().optional().describe("Off = fully manual mode: articles are written only on demand (generate_article) and publish only from the editor"),
452
+ autoGenerate: z.boolean().optional().describe("Off = fully manual mode: articles are written only on demand (generate_article) and publish only from the editor. Turning it on needs a connected publishing destination."),
450
453
  includeInfographics: z.boolean().optional().describe("AI hero + section images"),
451
454
  titleInHeroImage: z.boolean().optional().describe("Write the post title into the hero image"),
452
455
  includeSectionInfographics: z.boolean().optional().describe("Informational panels in 2,000+ word articles"),
@@ -481,12 +484,12 @@ export async function startMcpServer() {
481
484
 
482
485
  server.tool(
483
486
  "reschedule_article",
484
- "Move a PLANNED article to another day (requires the flexible schedule; max 5/day; the exact hour is placed automatically). Pass the target day as epoch ms of local midnight.",
487
+ "Move a PLANNED article to another day (requires the flexible schedule; max 5/day; the exact hour is placed automatically). Pass the target day as YYYY-MM-DD in the workspace's timezone.",
485
488
  {
486
489
  contentId: z.string(),
487
- targetDayStartMs: z.number().describe("Epoch ms of the target day's local midnight"),
490
+ date: z.string().describe("Target day as YYYY-MM-DD in the workspace's timezone"),
488
491
  },
489
- run(({ contentId, targetDayStartMs }) => api.reschedule(contentId, targetDayStartMs))
492
+ run(({ contentId, date }) => api.reschedule(contentId, date))
490
493
  );
491
494
 
492
495
  server.tool(
@@ -579,26 +582,30 @@ export async function startMcpServer() {
579
582
 
580
583
  server.tool(
581
584
  "list_postiz_channels",
582
- "Connected Postiz social channels (id, name, platform). Channel ids are required by push_repurpose_draft.",
585
+ "Connected social channels across Postiz and Buffer (scheduler, id, name, platform). Channel ids are required by push_repurpose_draft.",
583
586
  {},
584
587
  run(() => api.repurposeChannels())
585
588
  );
586
589
 
587
590
  server.tool(
588
591
  "push_repurpose_draft",
589
- "Send a repurpose draft to connected Postiz channels (post now, schedule, or save as a Postiz draft). PUBLICLY VISIBLE side effect: defaults to a DRY RUN; a human should approve before calling again with confirm=true.",
592
+ "Send a repurpose draft to a connected scheduler's channels: Postiz or Buffer (post now, schedule, save as a draft, or add to Buffer's queue). PUBLICLY VISIBLE side effect: defaults to a DRY RUN; a human should approve before calling again with confirm=true.",
590
593
  {
591
594
  draftId: z.string(),
595
+ scheduler: z
596
+ .enum(["postiz", "buffer"])
597
+ .optional()
598
+ .describe("Which scheduler; required when both are connected"),
592
599
  integrationIds: z
593
600
  .array(z.string())
594
601
  .optional()
595
602
  .describe(
596
- "Postiz channel ids from list_postiz_channels (required with confirm)"
603
+ "Channel ids from list_postiz_channels (required with confirm)"
597
604
  ),
598
605
  scheduleType: z
599
- .enum(["now", "schedule", "draft"])
606
+ .enum(["now", "schedule", "draft", "queue"])
600
607
  .optional()
601
- .describe("Default now"),
608
+ .describe("Default now; queue is Buffer only"),
602
609
  date: z.string().optional().describe("ISO time for scheduleType=schedule"),
603
610
  confirm: z.boolean().optional(),
604
611
  },
@@ -662,13 +669,13 @@ export async function startMcpServer() {
662
669
 
663
670
  server.tool(
664
671
  "list_backlinks",
665
- "The workspace's backlink table, newest first.",
672
+ "The workspace's backlink table, newest first. Each row carries its source (index, outreach, managed, network).",
666
673
  {
667
674
  status: z
668
675
  .string()
669
676
  .optional()
670
677
  .describe(
671
- "Filter: discovered, verified, lost, identified, contacted, replied, link_placed, rejected"
678
+ "Filter: discovered, verified, lost, placed, identified, contacted, replied, link_placed, rejected"
672
679
  ),
673
680
  },
674
681
  run(({ status }) => api.backlinks(status))