posterly-mcp-server 0.44.0 → 0.45.1

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.
Files changed (236) hide show
  1. package/README.md +310 -111
  2. package/dist/index.js +190 -894
  3. package/dist/lib/alias-transport.d.ts +84 -0
  4. package/dist/lib/alias-transport.js +193 -0
  5. package/dist/lib/api-client.d.ts +18 -0
  6. package/dist/lib/api-client.js +55 -0
  7. package/dist/lib/preview-token-core.d.ts +62 -0
  8. package/dist/lib/preview-token-core.js +131 -0
  9. package/dist/lib/preview-token.d.ts +23 -0
  10. package/dist/lib/preview-token.js +44 -0
  11. package/dist/lib/tool-registry.d.ts +246 -0
  12. package/dist/lib/tool-registry.js +394 -0
  13. package/dist/lib/tool-runtime.d.ts +68 -0
  14. package/dist/lib/tool-runtime.js +85 -0
  15. package/dist/lib/version.d.ts +1 -1
  16. package/dist/lib/version.js +1 -1
  17. package/dist/tools/audit-google-business-profile.js +1 -1
  18. package/dist/tools/connect-account.d.ts +4 -4
  19. package/dist/tools/connect-account.js +2 -2
  20. package/dist/tools/create-connect-session.d.ts +2 -2
  21. package/dist/tools/create-connect-session.js +2 -2
  22. package/dist/tools/create-post.d.ts +30 -7
  23. package/dist/tools/create-post.js +75 -9
  24. package/dist/tools/create-posts-batch.d.ts +4 -4
  25. package/dist/tools/create-posts-batch.js +14 -2
  26. package/dist/tools/create-webhook.d.ts +2 -2
  27. package/dist/tools/delete-post.d.ts +34 -10
  28. package/dist/tools/delete-post.js +52 -16
  29. package/dist/tools/generate-captions.d.ts +2 -2
  30. package/dist/tools/generate-image.d.ts +4 -4
  31. package/dist/tools/generate-image.js +2 -2
  32. package/dist/tools/generate-video.d.ts +6 -6
  33. package/dist/tools/generate-video.js +1 -1
  34. package/dist/tools/get-agent-signup-info.js +2 -2
  35. package/dist/tools/get-post.d.ts +16 -1
  36. package/dist/tools/get-post.js +35 -19
  37. package/dist/tools/list-accounts.d.ts +49 -3
  38. package/dist/tools/list-accounts.js +148 -33
  39. package/dist/tools/list-analytics.d.ts +111 -0
  40. package/dist/tools/list-analytics.js +393 -0
  41. package/dist/tools/list-brands.d.ts +38 -3
  42. package/dist/tools/list-brands.js +138 -20
  43. package/dist/tools/list-comments.d.ts +29 -9
  44. package/dist/tools/list-comments.js +75 -34
  45. package/dist/tools/list-conversations.d.ts +32 -9
  46. package/dist/tools/list-conversations.js +63 -27
  47. package/dist/tools/list-google-business-media.js +1 -1
  48. package/dist/tools/list-google-business-reviews.d.ts +32 -9
  49. package/dist/tools/list-google-business-reviews.js +77 -52
  50. package/dist/tools/list-jobs.d.ts +60 -0
  51. package/dist/tools/list-jobs.js +81 -0
  52. package/dist/tools/list-platforms.d.ts +62 -3
  53. package/dist/tools/list-platforms.js +118 -9
  54. package/dist/tools/list-posts.d.ts +2 -2
  55. package/dist/tools/manage-comment.d.ts +102 -0
  56. package/dist/tools/manage-comment.js +125 -0
  57. package/dist/tools/manage-conversation.d.ts +79 -0
  58. package/dist/tools/manage-conversation.js +75 -0
  59. package/dist/tools/manage-google-business-media.d.ts +103 -0
  60. package/dist/tools/manage-google-business-media.js +126 -0
  61. package/dist/tools/manage-google-business-review.d.ts +146 -0
  62. package/dist/tools/manage-google-business-review.js +115 -0
  63. package/dist/tools/manage-oauth-client.d.ts +121 -0
  64. package/dist/tools/manage-oauth-client.js +94 -0
  65. package/dist/tools/manage-webhook.d.ts +99 -0
  66. package/dist/tools/manage-webhook.js +87 -0
  67. package/dist/tools/start-signup.d.ts +2 -2
  68. package/dist/tools/submit-agent-feedback.d.ts +4 -4
  69. package/dist/tools/trigger-platform-helper.d.ts +2 -2
  70. package/dist/tools/update-post.d.ts +62 -11
  71. package/dist/tools/update-post.js +79 -26
  72. package/dist/tools/upload-media.d.ts +32 -9
  73. package/dist/tools/upload-media.js +54 -18
  74. package/dist/tools/validate-post.d.ts +4 -4
  75. package/dist/tools/validate-post.js +2 -2
  76. package/dist/tools/whoami.d.ts +15 -2
  77. package/dist/tools/whoami.js +221 -25
  78. package/package.json +5 -3
  79. package/scripts/test-alias-transport.mjs +368 -0
  80. package/scripts/test-client-header-sanitization.mjs +78 -0
  81. package/scripts/test-payload-builders.mjs +143 -20
  82. package/scripts/test-tool-annotations.mjs +38 -17
  83. package/server.json +9 -2
  84. package/src/index.ts +227 -1443
  85. package/src/lib/alias-transport.ts +233 -0
  86. package/src/lib/api-client.ts +65 -0
  87. package/src/lib/preview-token-core.ts +157 -0
  88. package/src/lib/preview-token.ts +54 -0
  89. package/src/lib/tool-registry.ts +538 -0
  90. package/src/lib/tool-runtime.ts +117 -0
  91. package/src/lib/version.ts +1 -1
  92. package/src/tools/audit-google-business-profile.ts +1 -1
  93. package/src/tools/connect-account.ts +2 -2
  94. package/src/tools/create-connect-session.ts +2 -2
  95. package/src/tools/create-post.ts +87 -12
  96. package/src/tools/create-posts-batch.ts +13 -2
  97. package/src/tools/delete-post.ts +60 -19
  98. package/src/tools/generate-image.ts +2 -2
  99. package/src/tools/generate-video.ts +1 -1
  100. package/src/tools/get-agent-signup-info.ts +2 -2
  101. package/src/tools/get-post.ts +35 -21
  102. package/src/tools/list-accounts.ts +187 -39
  103. package/src/tools/list-analytics.ts +444 -0
  104. package/src/tools/list-brands.ts +162 -26
  105. package/src/tools/list-comments.ts +86 -43
  106. package/src/tools/list-conversations.ts +81 -39
  107. package/src/tools/list-google-business-media.ts +1 -1
  108. package/src/tools/list-google-business-reviews.ts +93 -64
  109. package/src/tools/list-jobs.ts +91 -0
  110. package/src/tools/list-platforms.ts +135 -11
  111. package/src/tools/manage-comment.ts +142 -0
  112. package/src/tools/manage-conversation.ts +89 -0
  113. package/src/tools/manage-google-business-media.ts +147 -0
  114. package/src/tools/manage-google-business-review.ts +139 -0
  115. package/src/tools/manage-oauth-client.ts +116 -0
  116. package/src/tools/manage-webhook.ts +103 -0
  117. package/src/tools/update-post.ts +106 -39
  118. package/src/tools/upload-media.ts +76 -28
  119. package/src/tools/validate-post.ts +2 -1
  120. package/src/tools/whoami.ts +253 -30
  121. package/tool-annotations.json +76 -149
  122. package/tool-registry.json +2085 -0
  123. package/dist/tools/add-google-business-media.d.ts +0 -42
  124. package/dist/tools/add-google-business-media.js +0 -34
  125. package/dist/tools/create-oauth-client.d.ts +0 -40
  126. package/dist/tools/create-oauth-client.js +0 -20
  127. package/dist/tools/delete-comment.d.ts +0 -24
  128. package/dist/tools/delete-comment.js +0 -22
  129. package/dist/tools/delete-google-business-media.d.ts +0 -32
  130. package/dist/tools/delete-google-business-media.js +0 -17
  131. package/dist/tools/delete-google-business-review-reply.d.ts +0 -32
  132. package/dist/tools/delete-google-business-review-reply.js +0 -17
  133. package/dist/tools/delete-oauth-client.d.ts +0 -20
  134. package/dist/tools/delete-oauth-client.js +0 -13
  135. package/dist/tools/delete-post-group.d.ts +0 -20
  136. package/dist/tools/delete-post-group.js +0 -13
  137. package/dist/tools/delete-webhook.d.ts +0 -20
  138. package/dist/tools/delete-webhook.js +0 -13
  139. package/dist/tools/get-account-analytics.d.ts +0 -31
  140. package/dist/tools/get-account-analytics.js +0 -152
  141. package/dist/tools/get-brand-profile.d.ts +0 -16
  142. package/dist/tools/get-brand-profile.js +0 -73
  143. package/dist/tools/get-brand.d.ts +0 -16
  144. package/dist/tools/get-brand.js +0 -26
  145. package/dist/tools/get-comment.d.ts +0 -20
  146. package/dist/tools/get-comment.js +0 -36
  147. package/dist/tools/get-connect-link.d.ts +0 -28
  148. package/dist/tools/get-connect-link.js +0 -51
  149. package/dist/tools/get-connect-session.d.ts +0 -20
  150. package/dist/tools/get-connect-session.js +0 -54
  151. package/dist/tools/get-conversation.d.ts +0 -24
  152. package/dist/tools/get-conversation.js +0 -31
  153. package/dist/tools/get-google-business-review-link.d.ts +0 -24
  154. package/dist/tools/get-google-business-review-link.js +0 -19
  155. package/dist/tools/get-image-job.d.ts +0 -24
  156. package/dist/tools/get-image-job.js +0 -31
  157. package/dist/tools/get-learned-voice.d.ts +0 -16
  158. package/dist/tools/get-learned-voice.js +0 -22
  159. package/dist/tools/get-mcp-status.d.ts +0 -8
  160. package/dist/tools/get-mcp-status.js +0 -182
  161. package/dist/tools/get-performance-profile.d.ts +0 -16
  162. package/dist/tools/get-performance-profile.js +0 -39
  163. package/dist/tools/get-platform-schema.d.ts +0 -24
  164. package/dist/tools/get-platform-schema.js +0 -48
  165. package/dist/tools/get-post-analytics.d.ts +0 -39
  166. package/dist/tools/get-post-analytics.js +0 -222
  167. package/dist/tools/get-post-insights.d.ts +0 -28
  168. package/dist/tools/get-post-insights.js +0 -39
  169. package/dist/tools/get-post-missing.d.ts +0 -16
  170. package/dist/tools/get-post-missing.js +0 -12
  171. package/dist/tools/get-video-job.d.ts +0 -24
  172. package/dist/tools/get-video-job.js +0 -31
  173. package/dist/tools/list-brand-accounts.d.ts +0 -16
  174. package/dist/tools/list-brand-accounts.js +0 -24
  175. package/dist/tools/reply-google-business-review.d.ts +0 -36
  176. package/dist/tools/reply-google-business-review.js +0 -18
  177. package/dist/tools/reply-to-comment.d.ts +0 -32
  178. package/dist/tools/reply-to-comment.js +0 -17
  179. package/dist/tools/send-message.d.ts +0 -28
  180. package/dist/tools/send-message.js +0 -16
  181. package/dist/tools/suggest-google-business-review-reply.d.ts +0 -48
  182. package/dist/tools/suggest-google-business-review-reply.js +0 -27
  183. package/dist/tools/sync-inbox.d.ts +0 -24
  184. package/dist/tools/sync-inbox.js +0 -18
  185. package/dist/tools/test-webhook.d.ts +0 -20
  186. package/dist/tools/test-webhook.js +0 -14
  187. package/dist/tools/update-comment.d.ts +0 -28
  188. package/dist/tools/update-comment.js +0 -21
  189. package/dist/tools/update-oauth-client.d.ts +0 -44
  190. package/dist/tools/update-oauth-client.js +0 -21
  191. package/dist/tools/update-post-release-id.d.ts +0 -28
  192. package/dist/tools/update-post-release-id.js +0 -18
  193. package/dist/tools/update-post-status.d.ts +0 -28
  194. package/dist/tools/update-post-status.js +0 -23
  195. package/dist/tools/update-webhook.d.ts +0 -40
  196. package/dist/tools/update-webhook.js +0 -21
  197. package/dist/tools/upload-media-from-url.d.ts +0 -24
  198. package/dist/tools/upload-media-from-url.js +0 -26
  199. package/src/tools/add-google-business-media.ts +0 -50
  200. package/src/tools/create-oauth-client.ts +0 -36
  201. package/src/tools/delete-comment.ts +0 -29
  202. package/src/tools/delete-google-business-media.ts +0 -30
  203. package/src/tools/delete-google-business-review-reply.ts +0 -30
  204. package/src/tools/delete-oauth-client.ts +0 -23
  205. package/src/tools/delete-post-group.ts +0 -17
  206. package/src/tools/delete-webhook.ts +0 -17
  207. package/src/tools/get-account-analytics.ts +0 -171
  208. package/src/tools/get-brand-profile.ts +0 -86
  209. package/src/tools/get-brand.ts +0 -34
  210. package/src/tools/get-comment.ts +0 -47
  211. package/src/tools/get-connect-link.ts +0 -61
  212. package/src/tools/get-connect-session.ts +0 -63
  213. package/src/tools/get-conversation.ts +0 -44
  214. package/src/tools/get-google-business-review-link.ts +0 -30
  215. package/src/tools/get-image-job.ts +0 -36
  216. package/src/tools/get-learned-voice.ts +0 -31
  217. package/src/tools/get-mcp-status.ts +0 -212
  218. package/src/tools/get-performance-profile.ts +0 -47
  219. package/src/tools/get-platform-schema.ts +0 -56
  220. package/src/tools/get-post-analytics.ts +0 -243
  221. package/src/tools/get-post-insights.ts +0 -60
  222. package/src/tools/get-post-missing.ts +0 -16
  223. package/src/tools/get-video-job.ts +0 -36
  224. package/src/tools/list-brand-accounts.ts +0 -36
  225. package/src/tools/reply-google-business-review.ts +0 -32
  226. package/src/tools/reply-to-comment.ts +0 -30
  227. package/src/tools/send-message.ts +0 -28
  228. package/src/tools/suggest-google-business-review-reply.ts +0 -44
  229. package/src/tools/sync-inbox.ts +0 -28
  230. package/src/tools/test-webhook.ts +0 -18
  231. package/src/tools/update-comment.ts +0 -31
  232. package/src/tools/update-oauth-client.ts +0 -38
  233. package/src/tools/update-post-release-id.ts +0 -25
  234. package/src/tools/update-post-status.ts +0 -31
  235. package/src/tools/update-webhook.ts +0 -36
  236. package/src/tools/upload-media-from-url.ts +0 -33
@@ -1,243 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PostAnalyticsResponse, PostAnalyticsRow, PosterlyClient } from '../lib/api-client.js';
3
-
4
- const presentationSchema = z.enum(['compact', 'table', 'json']).optional();
5
- type AnalyticsPresentation = z.infer<typeof presentationSchema>;
6
-
7
- export const getPostAnalyticsTool = {
8
- name: 'get_post_analytics',
9
- description:
10
- 'Get per-post engagement metrics (likes, comments/replies, reach, impressions/views, saves, shares, reposts, quotes, plays, clicks, watch time) for a connected social account. Supports Instagram, Facebook, LinkedIn, Google Business Profile, Pinterest, YouTube, and Threads. Returns the most recent posts first. presentation controls output: compact for Telegram/mobile, table for Markdown clients, json for custom chart/card renderers.',
11
- inputSchema: z.object({
12
- account_id: z
13
- .number()
14
- .describe('The social account ID (from list_accounts)'),
15
- from: z
16
- .string()
17
- .optional()
18
- .describe('Start date (ISO date, e.g. 2026-03-19). Defaults to 30 days ago.'),
19
- to: z
20
- .string()
21
- .optional()
22
- .describe('End date (ISO date). Defaults to today.'),
23
- limit: z
24
- .number()
25
- .min(1)
26
- .max(200)
27
- .optional()
28
- .describe('Number of posts to return (default 50, max 200)'),
29
- offset: z.number().min(0).optional().describe('Pagination offset'),
30
- presentation: presentationSchema.describe('Output style: compact bullets, Markdown table, or raw JSON for client-side chart/card rendering.'),
31
- }),
32
-
33
- async execute(
34
- client: PosterlyClient,
35
- input: {
36
- account_id: number;
37
- from?: string;
38
- to?: string;
39
- limit?: number;
40
- offset?: number;
41
- presentation?: AnalyticsPresentation;
42
- }
43
- ) {
44
- const result = await client.getPostAnalytics(input);
45
- return formatPostAnalytics(result, input.presentation || 'compact');
46
- },
47
- };
48
-
49
- function formatPostAnalytics(result: PostAnalyticsResponse, presentation: Exclude<AnalyticsPresentation, undefined>): string {
50
- const { account, range, posts, total } = result;
51
-
52
- if (posts.length === 0) {
53
- return `No analytics found for ${accountLabel(account.username)} (${account.platform}) between ${range.from} and ${range.to}.`;
54
- }
55
-
56
- const totals = posts.reduce((acc, post) => {
57
- acc.likes += post.likes || 0;
58
- acc.comments += post.comments || 0;
59
- acc.reach += post.reach || 0;
60
- acc.impressions += post.impressions || 0;
61
- acc.shares += post.shares || 0;
62
- acc.reposts += post.reposts || 0;
63
- acc.quotes += post.quotes || 0;
64
- acc.saves += post.saved || 0;
65
- return acc;
66
- }, { likes: 0, comments: 0, reach: 0, impressions: 0, shares: 0, reposts: 0, quotes: 0, saves: 0 });
67
- const isThreads = account.platform === 'threads';
68
-
69
- if (presentation === 'json') {
70
- return JSON.stringify({
71
- type: 'post_analytics',
72
- account,
73
- range,
74
- returned: {
75
- count: posts.length,
76
- total,
77
- },
78
- totals,
79
- posts,
80
- }, null, 2);
81
- }
82
-
83
- if (presentation === 'table') {
84
- if (isThreads) {
85
- return [
86
- `**📈 Post analytics for ${accountLabel(account.username)} (${account.id})**`,
87
- `_Threads · ${range.from} → ${range.to} · showing ${posts.length} of ${total}_`,
88
- '',
89
- '**Returned totals**',
90
- markdownTable(['Views', 'Likes', 'Replies', 'Reposts', 'Quotes', 'Shares'], [[
91
- formatNumber(totals.impressions),
92
- formatNumber(totals.likes),
93
- formatNumber(totals.comments),
94
- formatNumber(totals.reposts),
95
- formatNumber(totals.quotes),
96
- formatNumber(totals.shares),
97
- ]]),
98
- '',
99
- '**Posts**',
100
- markdownTable(['Posted', 'Post', 'Caption', 'Views', 'Likes', 'Replies', 'Reposts', 'Quotes', 'Shares'], posts.map((post) => [
101
- dateTime(post.posted_at),
102
- post.id ? String(post.id) : post.platform_media_id,
103
- compact(post.caption_snippet, 70) || '(no caption)',
104
- formatNumber(post.impressions),
105
- formatNumber(post.likes),
106
- formatNumber(post.comments),
107
- formatNumber(post.reposts),
108
- formatNumber(post.quotes),
109
- formatNumber(post.shares),
110
- ])),
111
- ].join('\n');
112
- }
113
-
114
- return [
115
- `**📈 Post analytics for ${accountLabel(account.username)} (${account.id})**`,
116
- `_${account.platform} · ${range.from} → ${range.to} · showing ${posts.length} of ${total}_`,
117
- '',
118
- '**Returned totals**',
119
- markdownTable(['Likes', 'Comments', 'Reach', 'Impressions', 'Shares', 'Saves'], [[
120
- formatNumber(totals.likes),
121
- formatNumber(totals.comments),
122
- formatNumber(totals.reach),
123
- formatNumber(totals.impressions),
124
- formatNumber(totals.shares),
125
- formatNumber(totals.saves),
126
- ]]),
127
- '',
128
- '**Posts**',
129
- markdownTable(['Posted', 'Post', 'Caption', 'Reach', 'Likes', 'Comments', 'Extras'], posts.map((post) => [
130
- dateTime(post.posted_at),
131
- post.id ? String(post.id) : post.platform_media_id,
132
- compact(post.caption_snippet, 70) || '(no caption)',
133
- formatNumber(post.reach),
134
- formatNumber(post.likes),
135
- formatNumber(post.comments),
136
- postExtras(account.platform, post),
137
- ])),
138
- ].join('\n');
139
- }
140
-
141
- const lines: string[] = [
142
- `**📈 Post analytics for ${accountLabel(account.username)} (${account.id})**`,
143
- `_${account.platform} · ${range.from} → ${range.to} · showing ${posts.length} of ${total}_`,
144
- '',
145
- '**Returned totals**',
146
- `- **Likes:** ${formatNumber(totals.likes)}`,
147
- `- **Comments:** ${formatNumber(totals.comments)}`,
148
- `- **Reach:** ${formatNumber(totals.reach)}`,
149
- `- **Impressions:** ${formatNumber(totals.impressions)}`,
150
- `- **Shares:** ${formatNumber(totals.shares)}`,
151
- `- **Saves:** ${formatNumber(totals.saves)}`,
152
- '',
153
- '**Posts**',
154
- ];
155
-
156
- if (isThreads) {
157
- const threadLines: string[] = [
158
- `**📈 Post analytics for ${accountLabel(account.username)} (${account.id})**`,
159
- `_Threads · ${range.from} → ${range.to} · showing ${posts.length} of ${total}_`,
160
- '',
161
- '**Returned totals**',
162
- `- **Views:** ${formatNumber(totals.impressions)}`,
163
- `- **Likes:** ${formatNumber(totals.likes)}`,
164
- `- **Replies:** ${formatNumber(totals.comments)}`,
165
- `- **Reposts:** ${formatNumber(totals.reposts)}`,
166
- `- **Quotes:** ${formatNumber(totals.quotes)}`,
167
- `- **Shares:** ${formatNumber(totals.shares)}`,
168
- '',
169
- '**Posts**',
170
- ];
171
- posts.forEach((post, index) => {
172
- threadLines.push(`${index + 1}. **${compact(post.caption_snippet, 70) || '(no caption)'}**`);
173
- threadLines.push(` Posted: ${dateTime(post.posted_at)}`);
174
- threadLines.push(` Post ID: ${post.id ? String(post.id) : post.platform_media_id}`);
175
- threadLines.push(` Views: ${formatNumber(post.impressions)}`);
176
- threadLines.push(` Likes: ${formatNumber(post.likes)}`);
177
- threadLines.push(` Replies: ${formatNumber(post.comments)}`);
178
- threadLines.push(` Reposts: ${formatNumber(post.reposts)}`);
179
- threadLines.push(` Quotes: ${formatNumber(post.quotes)}`);
180
- threadLines.push(` Shares: ${formatNumber(post.shares)}`);
181
- if (post.permalink) threadLines.push(` Permalink: ${post.permalink}`);
182
- });
183
- return threadLines.join('\n');
184
- }
185
-
186
- posts.forEach((post, index) => {
187
- lines.push(`${index + 1}. **${compact(post.caption_snippet, 70) || '(no caption)'}**`);
188
- lines.push(` Posted: ${dateTime(post.posted_at)}`);
189
- lines.push(` Post ID: ${post.id ? String(post.id) : post.platform_media_id}`);
190
- lines.push(` Reach: ${formatNumber(post.reach)}`);
191
- lines.push(` Likes: ${formatNumber(post.likes)}`);
192
- lines.push(` Comments: ${formatNumber(post.comments)}`);
193
- lines.push(` Extras: ${postExtras(account.platform, post)}`);
194
- });
195
-
196
- return lines.join('\n');
197
- }
198
-
199
- function postExtras(platform: string, post: PostAnalyticsRow): string {
200
- const metrics: string[] = [];
201
- if (post.impressions) metrics.push(`${formatNumber(post.impressions)} impressions`);
202
- if (post.saved) metrics.push(`${formatNumber(post.saved)} saved`);
203
- if (platform !== 'threads' && post.shares) metrics.push(`${formatNumber(post.shares)} shares`);
204
- if (post.plays) metrics.push(`${formatNumber(post.plays)} plays`);
205
- if (platform === 'youtube' && post.total_watch_time_ms) metrics.push(`${formatNumber(Math.round(post.total_watch_time_ms / 60000))} watch minutes`);
206
- if (platform === 'youtube' && post.avg_watch_time_ms) metrics.push(`${formatNumber(Math.round(post.avg_watch_time_ms / 1000))}s avg view`);
207
- if (post.url_link_clicks) metrics.push(platform === 'pinterest' ? `${formatNumber(post.url_link_clicks)} outbound clicks` : `${formatNumber(post.url_link_clicks)} link clicks`);
208
- if (post.user_profile_clicks) metrics.push(platform === 'pinterest' ? `${formatNumber(post.user_profile_clicks)} pin clicks` : `${formatNumber(post.user_profile_clicks)} profile clicks`);
209
- if (post.permalink) metrics.push(post.permalink);
210
- return metrics.join(' · ') || 'n/a';
211
- }
212
-
213
- function accountLabel(username: string): string {
214
- if (!username) return 'account';
215
- return username.startsWith('@') || /\s/.test(username) ? username : `@${username}`;
216
- }
217
-
218
- function dateTime(value: string | null): string {
219
- if (!value) return 'n/a';
220
- const date = new Date(value);
221
- return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
222
- }
223
-
224
- function compact(value: string | null, maxLength: number): string {
225
- const text = String(value || '').replace(/\s+/g, ' ').trim();
226
- return text.length > maxLength ? `${text.slice(0, Math.max(0, maxLength - 1))}…` : text;
227
- }
228
-
229
- function formatNumber(value: number | null | undefined): string {
230
- return value == null ? 'n/a' : value.toLocaleString();
231
- }
232
-
233
- function markdownTable(headers: string[], rows: string[][]): string {
234
- return [
235
- `| ${headers.map(escapeCell).join(' | ')} |`,
236
- `| ${headers.map(() => '---').join(' | ')} |`,
237
- ...rows.map((row) => `| ${row.map(escapeCell).join(' | ')} |`),
238
- ].join('\n');
239
- }
240
-
241
- function escapeCell(value: string): string {
242
- return String(value).replace(/\|/g, '\\|').replace(/\r?\n/g, '<br>');
243
- }
@@ -1,60 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
- import { mdTitle, mdSection, mdKeyValue, mdEmpty, truncateText, formatDateTime } from '../lib/format.js';
4
-
5
- export const getPostInsightsTool = {
6
- name: 'get_post_insights',
7
- description:
8
- 'List per-post feedback insights from the posterly performance feedback loop: for recently published posts, the performance tier (great/good/mixed/poor), a diagnosis, a suggested next action, metrics, and the baseline it was compared against. Filter by account_id, post_id, and checkpoint (1h, 6h, 24h, 72h, 7d). Read-only; requires a Pro plan or higher.',
9
- inputSchema: z.object({
10
- account_id: z
11
- .union([z.string(), z.number()])
12
- .optional()
13
- .describe('Filter to one social account ID (from list_accounts).'),
14
- post_id: z.number().int().positive().optional().describe('Filter to one post ID.'),
15
- checkpoint: z
16
- .enum(['1h', '6h', '24h', '72h', '7d'])
17
- .optional()
18
- .describe('Filter to one checkpoint after publish.'),
19
- limit: z.number().int().min(1).max(100).optional().describe('Max insights to return (default 20).'),
20
- }),
21
-
22
- async execute(
23
- client: PosterlyClient,
24
- input: {
25
- account_id?: string | number;
26
- post_id?: number;
27
- checkpoint?: '1h' | '6h' | '24h' | '72h' | '7d';
28
- limit?: number;
29
- },
30
- ) {
31
- const { insights } = await client.getPostInsights(input);
32
-
33
- if (!insights.length) {
34
- return mdEmpty(
35
- 'feedback insights',
36
- 'No per-post feedback insights are available for these filters yet. Insights appear as posts hit their 1h, 6h, 24h, 72h, and 7d checkpoints.',
37
- );
38
- }
39
-
40
- const blocks = insights.map((insight) => {
41
- const heading = `${insight.title || 'Insight'} (${insight.performance_tier}, ${insight.checkpoint})`;
42
- return mdSection(
43
- heading,
44
- mdKeyValue([
45
- ['Post ID', insight.post_id],
46
- ['Platform', insight.platform],
47
- ['Account', insight.username ? `@${insight.username}` : String(insight.social_account_id)],
48
- ['Diagnosis', truncateText(insight.diagnosis, 220)],
49
- ['Next action', truncateText(insight.next_action, 220)],
50
- ['Impact score', insight.impact_score],
51
- ['Confidence', insight.confidence],
52
- ['Published', formatDateTime(insight.published_at)],
53
- ['Permalink', insight.permalink || undefined],
54
- ]),
55
- );
56
- });
57
-
58
- return [mdTitle(`Post feedback insights (${insights.length})`), ...blocks].join('\n\n');
59
- },
60
- };
@@ -1,16 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const getPostMissingTool = {
5
- name: 'get_post_missing',
6
- description:
7
- 'Inspect whether a post is missing required content, media, account, platform settings, or metadata before it can publish. Use this to repair failed or imported posts.',
8
- inputSchema: z.object({
9
- post_id: z.number().int().positive().describe('The post ID to inspect.'),
10
- }),
11
-
12
- async execute(client: PosterlyClient, input: { post_id: number }) {
13
- const result = await client.getPostMissing(input.post_id);
14
- return JSON.stringify(result, null, 2);
15
- },
16
- };
@@ -1,36 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient, VideoJob } from '../lib/api-client.js';
3
-
4
- export const getVideoJobTool = {
5
- name: 'get_video_job',
6
- description:
7
- 'Get one Veo video generation job by ID, or list recent video jobs when job_id is omitted. Use after generate_video to poll for completed video_url.',
8
- inputSchema: z.object({
9
- job_id: z.string().optional(),
10
- status: z.enum(['pending', 'processing', 'completed', 'failed', 'dismissed']).optional(),
11
- limit: z.number().int().min(1).max(50).default(10).optional(),
12
- }),
13
-
14
- async execute(client: PosterlyClient, input: { job_id?: string; status?: string; limit?: number }) {
15
- const result = await client.getVideoJob(input);
16
- if (result.job) return formatJob(result.job, true);
17
-
18
- const jobs = result.jobs || [];
19
- return [
20
- `Video jobs (${jobs.length})`,
21
- jobs.map((job) => formatJob(job, false)).join('\n\n'),
22
- ].filter(Boolean).join('\n');
23
- },
24
- };
25
-
26
- function formatJob(job: VideoJob, includeRaw: boolean): string {
27
- const lines = [
28
- `${job.id}: ${job.status}`,
29
- `Prompt: ${job.prompt}`,
30
- `Model: ${job.model}; duration: ${job.duration_seconds}s; resolution: ${job.resolution || 'n/a'}; cost: ${job.credit_cost}`,
31
- job.video_url ? `Video URL: ${job.video_url}` : null,
32
- job.error_message ? `Error: ${job.error_message}` : null,
33
- includeRaw ? `\nRaw job:\n${JSON.stringify(job, null, 2)}` : null,
34
- ];
35
- return lines.filter(Boolean).join('\n');
36
- }
@@ -1,36 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
- import { code, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
4
-
5
- export const listBrandAccountsTool = {
6
- name: 'list_brand_accounts',
7
- description:
8
- 'List the connected social accounts assigned to a brand/client. Use this when a user refers to a brand name rather than a raw account handle.',
9
- inputSchema: z.object({
10
- brand_id: z.string().describe('The brand ID to inspect (from list_brands).'),
11
- }),
12
-
13
- async execute(
14
- client: PosterlyClient,
15
- input: { brand_id: string },
16
- ) {
17
- const accounts = await client.listBrandAccounts(input.brand_id);
18
-
19
- if (accounts.length === 0) {
20
- return mdEmpty('brand accounts', 'No social accounts are currently assigned to this brand.');
21
- }
22
-
23
- return [
24
- mdTitle(`Brand accounts (${accounts.length})`),
25
- mdTable(
26
- ['Platform', 'Account', 'Account ID', 'Workspace'],
27
- accounts.map((account) => [
28
- account.platform,
29
- `@${account.username}`,
30
- code(account.id),
31
- account.workspace_id ? code(account.workspace_id) : 'default',
32
- ]),
33
- ),
34
- ].join('\n\n');
35
- },
36
- };
@@ -1,32 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const replyGoogleBusinessReviewTool = {
5
- name: 'reply_google_business_review',
6
- description:
7
- 'Post or update the owner reply on a Google Business Profile review. WRITE: show the review, location, and exact reply text to the user, then pass confirm=true only after explicit confirmation.',
8
- inputSchema: z.object({
9
- workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
10
- account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
11
- location_id: z.string().optional().describe('Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID returned by get_google_business_review_link. Prefer account_id.'),
12
- review_name: z.string().min(1).describe('Full Google review resource name.'),
13
- comment: z.string().min(1).max(4096).describe('Exact owner reply text to post.'),
14
- confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
15
- }),
16
-
17
- async execute(
18
- client: PosterlyClient,
19
- input: {
20
- workspace_id?: string;
21
- account_id?: number;
22
- location_id?: string;
23
- review_name: string;
24
- comment: string;
25
- confirm: true;
26
- },
27
- ) {
28
- const { confirm: _confirm, ...payload } = input;
29
- const result = await client.replyGoogleBusinessReview(payload);
30
- return result.message || 'Google Business review reply posted successfully.';
31
- },
32
- };
@@ -1,30 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const replyToCommentTool = {
5
- name: 'reply_to_comment',
6
- description:
7
- 'Reply to an Instagram, Facebook Page, Threads, or LinkedIn Page inbox comment. WRITE: posts to the social network. Facebook has no private comment replies. Threads has no DMs, so only public replies. Instagram supports public or private. LinkedIn Page comments are public replies only. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
8
- inputSchema: z.object({
9
- comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
10
- content: z.string().min(1).describe('Reply text to post.'),
11
- type: z.enum(['public', 'private']).optional().describe('public (default) or private. Private is Instagram only.'),
12
- workspace_id: z.string().optional().describe('Optional. Omit to search every workspace you belong to; pass a workspace ID from whoami to limit to one.'),
13
- confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
14
- }),
15
-
16
- async execute(
17
- client: PosterlyClient,
18
- input: {
19
- comment_id: string;
20
- content: string;
21
- type?: 'public' | 'private';
22
- workspace_id?: string;
23
- confirm: true;
24
- },
25
- ) {
26
- const { confirm: _confirm, ...payload } = input;
27
- const result = await client.replyToComment(payload);
28
- return `Comment reply sent (${result.reply_type}, reply_id ${result.reply_id}).`;
29
- },
30
- };
@@ -1,28 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const sendMessageTool = {
5
- name: 'send_message',
6
- description:
7
- 'Send a DM reply in an Instagram or Facebook Page inbox conversation. WRITE: posts to the social network. Honor the 24-hour messaging window. Threads has no DMs. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
8
- inputSchema: z.object({
9
- conversation_id: z.string().min(1).describe('Conversation ID from list_conversations.'),
10
- content: z.string().min(1).describe('Message text to send.'),
11
- workspace_id: z.string().optional().describe('Optional. Omit to search every workspace you belong to; pass a workspace ID from whoami to limit to one.'),
12
- confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
13
- }),
14
-
15
- async execute(
16
- client: PosterlyClient,
17
- input: {
18
- conversation_id: string;
19
- content: string;
20
- workspace_id?: string;
21
- confirm: true;
22
- },
23
- ) {
24
- const { confirm: _confirm, ...payload } = input;
25
- const result = await client.sendMessage(payload);
26
- return `Message sent (message_id ${result.message_id}).`;
27
- },
28
- };
@@ -1,44 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const suggestGoogleBusinessReviewReplyTool = {
5
- name: 'suggest_google_business_review_reply',
6
- description:
7
- 'Generate short, brand-aware AI reply suggestions for a Google review. Uses the AI Caption Assist allowance; does not post the reply.',
8
- inputSchema: z.object({
9
- workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
10
- account_id: z.number().int().positive().optional().describe('Optional Google Business account ID for brand voice context.'),
11
- location_id: z.string().optional().describe('Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID returned by get_google_business_review_link. Prefer account_id.'),
12
- review_text: z.string().optional().describe('Review text. Leave empty for rating-only reviews.'),
13
- star_rating: z.number().min(1).max(5).describe('Review rating from 1 to 5.'),
14
- reviewer_name: z.string().optional(),
15
- business_name: z.string().optional(),
16
- tone: z.enum(['professional', 'friendly', 'apologetic']).optional(),
17
- count: z.number().int().min(1).max(5).optional(),
18
- }),
19
-
20
- async execute(
21
- client: PosterlyClient,
22
- input: {
23
- workspace_id?: string;
24
- account_id?: number;
25
- location_id?: string;
26
- review_text?: string;
27
- star_rating: number;
28
- reviewer_name?: string;
29
- business_name?: string;
30
- tone?: 'professional' | 'friendly' | 'apologetic';
31
- count?: number;
32
- },
33
- ) {
34
- const result = await client.suggestGoogleBusinessReviewReply(input);
35
- const suggestions = Array.isArray(result.suggestions) ? result.suggestions : [];
36
- if (suggestions.length === 0) {
37
- return 'No reply suggestions were generated.';
38
- }
39
- return [
40
- `Suggested replies (${result.tone || 'professional'}, ${result.rating || input.star_rating} stars):`,
41
- ...suggestions.map((text: string, index: number) => `${index + 1}. ${text}`),
42
- ].join('\n');
43
- },
44
- };
@@ -1,28 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const syncInboxTool = {
5
- name: 'sync_inbox',
6
- description:
7
- 'Pull the latest Instagram, Facebook Page, Threads, or LinkedIn inbox data for one connected account. Honors a 30-second cooldown (60 seconds for LinkedIn). Threads and LinkedIn sync comments only (no DMs). Requires a Pro plan or higher plus posts:write.',
8
- inputSchema: z.object({
9
- account_id: z.number().int().positive().describe('Social account ID from list_accounts.'),
10
- sync_type: z.enum(['all', 'dm', 'comments']).optional().describe('What to sync. Defaults to all. Threads and LinkedIn are comments-only.'),
11
- workspace_id: z.string().optional().describe('Optional. Omit to use the connected account\'s own workspace; pass a workspace ID from whoami to require that specific workspace.'),
12
- }),
13
-
14
- async execute(
15
- client: PosterlyClient,
16
- input: {
17
- account_id: number;
18
- sync_type?: 'all' | 'dm' | 'comments';
19
- workspace_id?: string;
20
- },
21
- ) {
22
- const result = await client.syncInbox(input);
23
- const parts = ['Inbox sync completed.'];
24
- const keys = Object.keys(result.results || {});
25
- if (keys.length > 0) parts.push(`Synced: ${keys.join(', ')}.`);
26
- return parts.join(' ');
27
- },
28
- };
@@ -1,18 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const testWebhookTool = {
5
- name: 'test_webhook',
6
- description:
7
- 'Send a signed webhook.test delivery to a webhook URL. OUTBOUND SIDE EFFECT: get explicit confirmation before sending a test request.',
8
- inputSchema: z.object({
9
- webhook_id: z.string().describe('Webhook ID from list_webhooks.'),
10
- confirm: z.literal(true).describe('Must be true after the user explicitly confirms the test delivery.'),
11
- }),
12
-
13
- async execute(client: PosterlyClient, input: { webhook_id: string; confirm: true }) {
14
- const result = await client.testWebhook(input.webhook_id);
15
- const delivery = result.delivery || {};
16
- return `Webhook test sent.\nDelivery: ${JSON.stringify(delivery, null, 2)}`;
17
- },
18
- };
@@ -1,31 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- export const updateCommentTool = {
5
- name: 'update_comment',
6
- description:
7
- 'Update an inbox comment: hide/unhide it on the platform, or mark it read/unread. Hide is reversible and does not delete the comment. Requires a Pro plan or higher plus posts:write.',
8
- inputSchema: z.object({
9
- comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
10
- is_hidden: z.boolean().optional().describe('Hide or unhide the comment on Instagram, Facebook, or Threads.'),
11
- is_read: z.boolean().optional().describe('Mark the comment read or unread in posterly.'),
12
- workspace_id: z.string().optional().describe('Optional. Omit to search every workspace you belong to; pass a workspace ID from whoami to limit to one.'),
13
- }),
14
-
15
- async execute(
16
- client: PosterlyClient,
17
- input: {
18
- comment_id: string;
19
- is_hidden?: boolean;
20
- is_read?: boolean;
21
- workspace_id?: string;
22
- },
23
- ) {
24
- const result = await client.updateComment(input);
25
- const comment = result.comment;
26
- const parts = [`Comment ${String(comment.id)} updated.`];
27
- if (typeof comment.is_hidden === 'boolean') parts.push(`Hidden: ${comment.is_hidden ? 'yes' : 'no'}.`);
28
- if (typeof comment.is_read === 'boolean') parts.push(`Read: ${comment.is_read ? 'yes' : 'no'}.`);
29
- return parts.join(' ');
30
- },
31
- };
@@ -1,38 +0,0 @@
1
- import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
3
-
4
- const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
5
-
6
- export const updateOAuthClientTool = {
7
- name: 'update_oauth_client',
8
- description:
9
- 'Update a self-serve OAuth developer client. WRITE: confirm changed redirect URIs, scopes, and active state before calling.',
10
- inputSchema: z.object({
11
- client_id: z.string().min(1),
12
- client_name: z.string().min(1).max(120).optional(),
13
- description: z.string().max(500).optional(),
14
- homepage: z.string().url().optional(),
15
- allowed_redirect_uris: z.array(z.string().url()).min(1).max(10).optional(),
16
- default_scopes: z.array(scopeSchema).min(1).optional(),
17
- is_active: z.boolean().optional(),
18
- confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
19
- }),
20
-
21
- async execute(
22
- client: PosterlyClient,
23
- input: {
24
- client_id: string;
25
- client_name?: string;
26
- description?: string;
27
- homepage?: string;
28
- allowed_redirect_uris?: string[];
29
- default_scopes?: string[];
30
- is_active?: boolean;
31
- confirm: true;
32
- },
33
- ) {
34
- const { client_id, confirm: _confirm, ...payload } = input;
35
- const result = await client.updateOAuthClient(client_id, payload);
36
- return `OAuth client updated: ${result.client.client_name} (${result.client.client_id}) active=${result.client.is_active}`;
37
- },
38
- };