posterly-mcp-server 0.19.1 → 0.19.2
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 +3 -1
- package/dist/index.js +50 -49
- package/dist/lib/api-client.js +1 -1
- package/dist/lib/format.d.ts +21 -0
- package/dist/lib/format.js +125 -0
- package/dist/tools/audit-google-business-profile.js +15 -12
- package/dist/tools/create-oauth-client.js +7 -1
- package/dist/tools/create-post.js +20 -27
- package/dist/tools/create-posts-batch.js +23 -15
- package/dist/tools/create-signed-upload.js +11 -8
- package/dist/tools/create-webhook.js +7 -2
- package/dist/tools/delete-google-business-review-reply.d.ts +1 -1
- package/dist/tools/delete-google-business-review-reply.js +4 -1
- package/dist/tools/delete-oauth-client.js +4 -1
- package/dist/tools/delete-post-group.js +5 -1
- package/dist/tools/delete-post.js +4 -1
- package/dist/tools/delete-webhook.js +4 -1
- package/dist/tools/disconnect-account.js +7 -5
- package/dist/tools/find-slot.js +10 -3
- package/dist/tools/generate-image.js +17 -19
- package/dist/tools/generate-video.js +11 -9
- package/dist/tools/get-account-analytics.js +64 -45
- package/dist/tools/get-brand-profile.js +34 -40
- package/dist/tools/get-brand.js +14 -16
- package/dist/tools/get-connect-link.js +26 -7
- package/dist/tools/get-google-business-review-link.js +6 -6
- package/dist/tools/get-platform-schema.js +23 -16
- package/dist/tools/get-post-analytics.js +53 -50
- package/dist/tools/get-post-missing.js +5 -1
- package/dist/tools/get-post.js +16 -14
- package/dist/tools/get-video-job.d.ts +2 -2
- package/dist/tools/get-video-job.js +28 -11
- package/dist/tools/get-video-options.js +21 -15
- package/dist/tools/get-x-posting-quota.js +12 -9
- package/dist/tools/list-accounts.js +12 -4
- package/dist/tools/list-activity.js +13 -18
- package/dist/tools/list-brand-accounts.js +11 -3
- package/dist/tools/list-brands.js +12 -11
- package/dist/tools/list-google-business-reviews.js +13 -11
- package/dist/tools/list-oauth-clients.js +10 -4
- package/dist/tools/list-platforms.js +12 -5
- package/dist/tools/list-posts.js +12 -7
- package/dist/tools/list-webhooks.js +13 -11
- package/dist/tools/reply-google-business-review.d.ts +1 -1
- package/dist/tools/reply-google-business-review.js +4 -1
- package/dist/tools/run-video-function.js +4 -4
- package/dist/tools/suggest-google-business-review-reply.js +5 -4
- package/dist/tools/test-webhook.js +7 -1
- package/dist/tools/trigger-platform-helper.js +5 -1
- package/dist/tools/update-oauth-client.js +8 -1
- package/dist/tools/update-post-release-id.js +5 -1
- package/dist/tools/update-post-status.d.ts +2 -2
- package/dist/tools/update-post-status.js +7 -1
- package/dist/tools/update-post.js +10 -4
- package/dist/tools/update-webhook.js +7 -1
- package/dist/tools/upload-media-from-url.js +7 -9
- package/dist/tools/upload-media.js +6 -2
- package/dist/tools/whoami.js +18 -17
- package/package.json +1 -1
- package/src/index.ts +50 -49
- package/src/lib/api-client.ts +1 -1
- package/src/lib/format.ts +132 -0
- package/src/tools/audit-google-business-profile.ts +18 -12
- package/src/tools/create-oauth-client.ts +8 -2
- package/src/tools/create-post.ts +20 -28
- package/src/tools/create-posts-batch.ts +23 -18
- package/src/tools/create-signed-upload.ts +11 -8
- package/src/tools/create-webhook.ts +7 -3
- package/src/tools/delete-google-business-review-reply.ts +4 -1
- package/src/tools/delete-oauth-client.ts +4 -1
- package/src/tools/delete-post-group.ts +5 -1
- package/src/tools/delete-post.ts +4 -1
- package/src/tools/delete-webhook.ts +4 -2
- package/src/tools/disconnect-account.ts +7 -5
- package/src/tools/find-slot.ts +13 -5
- package/src/tools/generate-image.ts +20 -20
- package/src/tools/generate-video.ts +11 -9
- package/src/tools/get-account-analytics.ts +70 -49
- package/src/tools/get-brand-profile.ts +34 -38
- package/src/tools/get-brand.ts +14 -14
- package/src/tools/get-connect-link.ts +29 -7
- package/src/tools/get-google-business-review-link.ts +6 -6
- package/src/tools/get-platform-schema.ts +29 -16
- package/src/tools/get-post-analytics.ts +58 -51
- package/src/tools/get-post-missing.ts +5 -2
- package/src/tools/get-post.ts +16 -13
- package/src/tools/get-video-job.ts +31 -11
- package/src/tools/get-video-options.ts +27 -15
- package/src/tools/get-x-posting-quota.ts +12 -9
- package/src/tools/list-accounts.ts +15 -6
- package/src/tools/list-activity.ts +16 -20
- package/src/tools/list-brand-accounts.ts +14 -6
- package/src/tools/list-brands.ts +16 -13
- package/src/tools/list-google-business-reviews.ts +16 -12
- package/src/tools/list-oauth-clients.ts +13 -4
- package/src/tools/list-platforms.ts +15 -5
- package/src/tools/list-posts.ts +15 -9
- package/src/tools/list-webhooks.ts +16 -12
- package/src/tools/reply-google-business-review.ts +4 -1
- package/src/tools/run-video-function.ts +4 -4
- package/src/tools/suggest-google-business-review-reply.ts +5 -4
- package/src/tools/test-webhook.ts +7 -1
- package/src/tools/trigger-platform-helper.ts +5 -1
- package/src/tools/update-oauth-client.ts +9 -2
- package/src/tools/update-post-release-id.ts +5 -2
- package/src/tools/update-post-status.ts +7 -1
- package/src/tools/update-post.ts +10 -5
- package/src/tools/update-webhook.ts +7 -2
- package/src/tools/upload-media-from-url.ts +7 -9
- package/src/tools/upload-media.ts +6 -2
- package/src/tools/whoami.ts +21 -23
package/src/tools/create-post.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
3
|
import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
4
|
+
import { code, formatDateTime, mdKeyValue, mdSuccess, statusLabel } from '../lib/format.js';
|
|
4
5
|
|
|
5
6
|
export const instagramSettingsSchema = z.object({
|
|
6
7
|
__type: z.enum(['instagram', 'instagram-standalone']).optional(),
|
|
@@ -179,33 +180,24 @@ export const createPostTool = {
|
|
|
179
180
|
const p = result.post as Record<string, any>;
|
|
180
181
|
const ws = result.workspace;
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
lines.push('• Instagram first comment: yes');
|
|
202
|
-
}
|
|
203
|
-
if (platform_settings) {
|
|
204
|
-
lines.push('• Platform settings: yes');
|
|
205
|
-
}
|
|
206
|
-
if (ws) {
|
|
207
|
-
lines.push(`• Workspace: ${ws.name} (${ws.id}) — resolved from ${ws.resolved_from}`);
|
|
208
|
-
}
|
|
209
|
-
return lines.join('\n');
|
|
183
|
+
return [
|
|
184
|
+
mdSuccess('Post created', [
|
|
185
|
+
['Post ID', code(p.id)],
|
|
186
|
+
['Platform', p.platform || input.platform || 'unknown'],
|
|
187
|
+
['Type', p.post_type],
|
|
188
|
+
['Status', statusLabel(p.status)],
|
|
189
|
+
['Scheduled', p.scheduled_at ? formatDateTime(p.scheduled_at) : 'now'],
|
|
190
|
+
['Thread', thread_posts ? `${thread_posts.length} posts` : undefined],
|
|
191
|
+
['Instagram collaborators', instagram_settings?.collaborators?.length],
|
|
192
|
+
['Instagram first comment', Boolean(instagram_settings?.first_comment)],
|
|
193
|
+
['Platform settings', Boolean(platform_settings)],
|
|
194
|
+
], 'Use `get_post` if you want to review the saved post details.'),
|
|
195
|
+
ws
|
|
196
|
+
? mdKeyValue([
|
|
197
|
+
['Workspace', `${ws.name} (${code(ws.id)})`],
|
|
198
|
+
['Workspace resolved from', ws.resolved_from],
|
|
199
|
+
])
|
|
200
|
+
: '',
|
|
201
|
+
].filter(Boolean).join('\n\n');
|
|
210
202
|
},
|
|
211
203
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
3
|
import { buildCreatePostPayload, createPostInputSchema, type CreatePostInput } from './create-post.js';
|
|
4
|
+
import { code, formatDateTime, mdSection, mdTable, mdTitle, statusLabel } from '../lib/format.js';
|
|
4
5
|
|
|
5
6
|
export const createPostsBatchTool = {
|
|
6
7
|
name: 'create_posts_batch',
|
|
@@ -23,26 +24,30 @@ export const createPostsBatchTool = {
|
|
|
23
24
|
const payloads = input.posts.map((post) => buildCreatePostPayload(post));
|
|
24
25
|
const result = await client.createPostsBatch({ posts: payloads });
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
-
'Batch create completed.',
|
|
28
|
-
`• Total: ${result.total}`,
|
|
29
|
-
`• Created: ${result.created}`,
|
|
30
|
-
`• Replayed: ${result.replayed}`,
|
|
31
|
-
`• Failed: ${result.failed}`,
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const item of result.posts.slice(0, 25)) {
|
|
27
|
+
const createdRows = result.posts.slice(0, 25).map((item) => {
|
|
35
28
|
const post = item.post as Record<string, any>;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
return [
|
|
30
|
+
item.index,
|
|
31
|
+
code(post.id),
|
|
32
|
+
post.platform || 'unknown',
|
|
33
|
+
post.post_type || 'post',
|
|
34
|
+
statusLabel(post.status || 'created'),
|
|
35
|
+
post.scheduled_at ? formatDateTime(post.scheduled_at) : 'now',
|
|
36
|
+
item.replayed ? 'yes' : 'no',
|
|
37
|
+
];
|
|
38
|
+
});
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const errorRows = result.errors.slice(0, 25).map((error) => [
|
|
41
|
+
error.index,
|
|
42
|
+
error.status,
|
|
43
|
+
error.error,
|
|
44
|
+
]);
|
|
45
45
|
|
|
46
|
-
return
|
|
46
|
+
return [
|
|
47
|
+
mdTitle(result.failed ? '⚠️ Batch create completed with issues' : '✅ Batch create completed'),
|
|
48
|
+
mdTable(['Total', 'Created', 'Replayed', 'Failed'], [[result.total, result.created, result.replayed, result.failed]]),
|
|
49
|
+
mdSection('Created posts', mdTable(['Index', 'Post ID', 'Platform', 'Type', 'Status', 'Scheduled', 'Replayed'], createdRows)),
|
|
50
|
+
mdSection('Errors', mdTable(['Index', 'Status', 'Error'], errorRows)),
|
|
51
|
+
].filter(Boolean).join('\n\n');
|
|
47
52
|
},
|
|
48
53
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, formatBytes, mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const createSignedUploadTool = {
|
|
5
6
|
name: 'create_signed_upload',
|
|
6
7
|
description:
|
|
7
|
-
'Create a signed
|
|
8
|
+
'Create a signed upload URL for a larger image or video. Upload the binary to upload_url; the API validates actual bytes before storage, then public_url can be used with create_post.',
|
|
8
9
|
inputSchema: z.object({
|
|
9
10
|
filename: z.string().describe('Filename with extension, e.g. launch-video.mp4'),
|
|
10
11
|
content_type: z.string().describe('MIME type, e.g. video/mp4'),
|
|
@@ -16,12 +17,14 @@ export const createSignedUploadTool = {
|
|
|
16
17
|
input: { filename: string; content_type: string; size: number },
|
|
17
18
|
) {
|
|
18
19
|
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
|
|
19
|
-
return [
|
|
20
|
-
'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
return mdSuccess('Signed upload created', [
|
|
21
|
+
['Filename', input.filename],
|
|
22
|
+
['Content type', input.content_type],
|
|
23
|
+
['Size', formatBytes(input.size)],
|
|
24
|
+
['Upload URL', signed.upload_url],
|
|
25
|
+
['Token', code(signed.token)],
|
|
26
|
+
['Storage path', signed.path],
|
|
27
|
+
['Public URL after upload', signed.public_url],
|
|
28
|
+
]);
|
|
26
29
|
},
|
|
27
30
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
3
|
import { webhookEventSchema } from './webhook-events.js';
|
|
4
|
+
import { code, mdSuccess } from '../lib/format.js';
|
|
4
5
|
|
|
5
6
|
export const createWebhookTool = {
|
|
6
7
|
name: 'create_webhook',
|
|
@@ -28,8 +29,11 @@ export const createWebhookTool = {
|
|
|
28
29
|
) {
|
|
29
30
|
const { confirm: _confirm, ...payload } = input;
|
|
30
31
|
const result = await client.createWebhook(payload);
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
return mdSuccess('Webhook created', [
|
|
33
|
+
['Webhook ID', code(result.webhook.id)],
|
|
34
|
+
['URL', result.webhook.url],
|
|
35
|
+
['Events', result.webhook.events.join(', ')],
|
|
36
|
+
['Signing secret', result.secret ? code(result.secret) : undefined],
|
|
37
|
+
], 'Store the signing secret now; Posterly only returns it once.');
|
|
33
38
|
},
|
|
34
39
|
};
|
|
35
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const deleteGoogleBusinessReviewReplyTool = {
|
|
5
6
|
name: 'delete_google_business_review_reply',
|
|
@@ -25,6 +26,8 @@ export const deleteGoogleBusinessReviewReplyTool = {
|
|
|
25
26
|
) {
|
|
26
27
|
const { confirm: _confirm, ...payload } = input;
|
|
27
28
|
const result = await client.deleteGoogleBusinessReviewReply(payload);
|
|
28
|
-
return
|
|
29
|
+
return mdSuccess('Google Business review reply deleted', [
|
|
30
|
+
['Message', result.message || 'Google Business review reply deleted successfully.'],
|
|
31
|
+
]);
|
|
29
32
|
},
|
|
30
33
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const deleteOAuthClientTool = {
|
|
5
6
|
name: 'delete_oauth_client',
|
|
@@ -18,6 +19,8 @@ export const deleteOAuthClientTool = {
|
|
|
18
19
|
},
|
|
19
20
|
) {
|
|
20
21
|
const result = await client.deleteOAuthClient(input.client_id);
|
|
21
|
-
return
|
|
22
|
+
return mdSuccess('OAuth client deleted', [
|
|
23
|
+
['Client ID', code(result.client_id)],
|
|
24
|
+
]);
|
|
22
25
|
},
|
|
23
26
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { mdJson, mdTitle } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const deletePostGroupTool = {
|
|
5
6
|
name: 'delete_post_group',
|
|
@@ -12,6 +13,9 @@ export const deletePostGroupTool = {
|
|
|
12
13
|
|
|
13
14
|
async execute(client: PosterlyClient, input: { group_id: string; confirm: true }) {
|
|
14
15
|
const result = await client.deletePostGroup(input.group_id);
|
|
15
|
-
return
|
|
16
|
+
return [
|
|
17
|
+
mdTitle('✅ Post group delete completed', `Group: ${input.group_id}`),
|
|
18
|
+
mdJson('Result', result),
|
|
19
|
+
].join('\n\n');
|
|
16
20
|
},
|
|
17
21
|
};
|
package/src/tools/delete-post.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const deletePostTool = {
|
|
5
6
|
name: 'delete_post',
|
|
@@ -15,6 +16,8 @@ export const deletePostTool = {
|
|
|
15
16
|
input: { post_id: number },
|
|
16
17
|
) {
|
|
17
18
|
const result = await client.deletePost(input.post_id);
|
|
18
|
-
return
|
|
19
|
+
return mdSuccess('Post deleted', [
|
|
20
|
+
['Post ID', code(result.id)],
|
|
21
|
+
]);
|
|
19
22
|
},
|
|
20
23
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const deleteWebhookTool = {
|
|
5
6
|
name: 'delete_webhook',
|
|
@@ -12,7 +13,8 @@ export const deleteWebhookTool = {
|
|
|
12
13
|
|
|
13
14
|
async execute(client: PosterlyClient, input: { webhook_id: string; confirm: true }) {
|
|
14
15
|
const result = await client.deleteWebhook(input.webhook_id);
|
|
15
|
-
return
|
|
16
|
+
return mdSuccess('Webhook deleted', [
|
|
17
|
+
['Webhook ID', code(result.id)],
|
|
18
|
+
]);
|
|
16
19
|
},
|
|
17
20
|
};
|
|
18
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdSuccess } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const disconnectAccountTool = {
|
|
5
6
|
name: 'disconnect_account',
|
|
@@ -21,10 +22,11 @@ export const disconnectAccountTool = {
|
|
|
21
22
|
|
|
22
23
|
const result = await client.disconnectAccount(input.account_id);
|
|
23
24
|
const account = result.account || {};
|
|
24
|
-
return [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
return mdSuccess('Account disconnected', [
|
|
26
|
+
['Account', `${account.platform || 'account'}${account.username ? ` @${account.username}` : ''}`],
|
|
27
|
+
['Account ID', code(account.id || input.account_id)],
|
|
28
|
+
['Transferred posts', result.transferred_post_count ?? 0],
|
|
29
|
+
['Stamped posts', result.stamped_post_count ?? 0],
|
|
30
|
+
]);
|
|
29
31
|
},
|
|
30
32
|
};
|
package/src/tools/find-slot.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const findSlotTool = {
|
|
5
6
|
name: 'find_available_slot',
|
|
@@ -38,12 +39,19 @@ export const findSlotTool = {
|
|
|
38
39
|
const slots = await client.findAvailableSlots(input);
|
|
39
40
|
|
|
40
41
|
if (slots.length === 0) {
|
|
41
|
-
return 'No available slots found in the next 14 days.';
|
|
42
|
+
return mdEmpty('available slots', 'No available posting slots were found in the next 14 days.');
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
(
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
return [
|
|
46
|
+
mdTitle(`Available posting slots (${slots.length})`),
|
|
47
|
+
mdTable(
|
|
48
|
+
['Option', 'Local time', 'Date'],
|
|
49
|
+
slots.map((slot, index) => [
|
|
50
|
+
index + 1,
|
|
51
|
+
slot.local_time,
|
|
52
|
+
formatDate(slot.time),
|
|
53
|
+
]),
|
|
54
|
+
),
|
|
55
|
+
].join('\n\n');
|
|
48
56
|
},
|
|
49
57
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { mdBullets, mdKeyValue, mdSection, mdTable, mdTitle } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const generateImageTool = {
|
|
5
6
|
name: 'generate_image',
|
|
@@ -62,25 +63,24 @@ export const generateImageTool = {
|
|
|
62
63
|
) {
|
|
63
64
|
const result = await client.generateImage(input);
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return lines.join('\n');
|
|
66
|
+
return [
|
|
67
|
+
mdTitle(`✅ Generated ${result.images.length} image${result.images.length === 1 ? '' : 's'}`, `Model: ${result.model}`),
|
|
68
|
+
mdTable(
|
|
69
|
+
['#', 'Image URL', 'Filename'],
|
|
70
|
+
result.images.map((img, index) => [
|
|
71
|
+
index + 1,
|
|
72
|
+
img.url,
|
|
73
|
+
img.filename,
|
|
74
|
+
]),
|
|
75
|
+
),
|
|
76
|
+
mdKeyValue([
|
|
77
|
+
['Billed from', result.usage.billed_from],
|
|
78
|
+
['Credits used', result.credits_used],
|
|
79
|
+
['Plan usage', result.usage.limit != null ? `${result.usage.used ?? 0}/${result.usage.limit} this ${result.usage.period || 'period'}` : undefined],
|
|
80
|
+
['Plan tier', result.usage.tier || undefined],
|
|
81
|
+
]),
|
|
82
|
+
result.warnings?.length ? mdSection('Warnings', mdBullets(result.warnings)) : '',
|
|
83
|
+
'**Next step:** Pass any URL above to `create_post` as `media_url`, or use multiple as `media_urls` for a carousel.',
|
|
84
|
+
].filter(Boolean).join('\n\n');
|
|
85
85
|
},
|
|
86
86
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdJson, mdKeyValue, mdTitle, statusLabel } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const generateVideoTool = {
|
|
5
6
|
name: 'generate_video',
|
|
@@ -25,14 +26,15 @@ export const generateVideoTool = {
|
|
|
25
26
|
async execute(client: PosterlyClient, input: any) {
|
|
26
27
|
const result = await client.generateVideo(input);
|
|
27
28
|
return [
|
|
28
|
-
'Video generation job queued',
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
mdTitle('🎬 Video generation job queued'),
|
|
30
|
+
mdKeyValue([
|
|
31
|
+
['Job ID', code(result.job_id)],
|
|
32
|
+
['Status', statusLabel(result.status)],
|
|
33
|
+
['Credit cost', `${result.credit_cost} (${result.included_credit_cost || 0} included, ${result.purchased_credit_cost || 0} purchased)`],
|
|
34
|
+
['Message', result.message],
|
|
35
|
+
]),
|
|
36
|
+
'**Next step:** Poll `get_video_job` with the job ID until the video URL is ready.',
|
|
37
|
+
mdJson('Raw response', result),
|
|
38
|
+
].filter(Boolean).join('\n\n');
|
|
37
39
|
},
|
|
38
40
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { AccountAnalyticsSummary, PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { formatDelta, formatNumber, formatPercent, mdBullets, mdKeyValue, mdSection, mdTable, mdTitle } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const getAccountAnalyticsTool = {
|
|
5
6
|
name: 'get_account_analytics',
|
|
@@ -26,62 +27,82 @@ export const getAccountAnalyticsTool = {
|
|
|
26
27
|
const result = await client.getAccountAnalytics(input);
|
|
27
28
|
const { account, range, summary, snapshots } = result;
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'',
|
|
33
|
-
'Summary:',
|
|
34
|
-
];
|
|
30
|
+
const metricRows = account.platform === 'google_business'
|
|
31
|
+
? googleBusinessMetricRows(summary)
|
|
32
|
+
: genericMetricRows(summary, account.platform);
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
pushMetric(lines, 'Call clicks', metrics.call_clicks ?? summary.total_call_clicks);
|
|
46
|
-
pushMetric(lines, 'Direction requests', metrics.direction_requests ?? summary.total_direction_requests);
|
|
47
|
-
pushMetric(lines, 'Conversations', metrics.conversations ?? summary.total_conversations);
|
|
48
|
-
pushMetric(lines, 'Bookings', metrics.bookings ?? summary.total_bookings);
|
|
49
|
-
} else if (account.platform === 'pinterest') {
|
|
50
|
-
pushGenericSummary(lines, summary);
|
|
51
|
-
pushMetric(lines, 'Outbound clicks', summary.total_website_clicks);
|
|
52
|
-
} else if (account.platform === 'youtube') {
|
|
53
|
-
pushGenericSummary(lines, summary);
|
|
54
|
-
pushMetric(lines, 'Watch minutes', summary.total_watch_minutes);
|
|
55
|
-
pushMetric(lines, 'Likes', summary.total_likes);
|
|
56
|
-
} else {
|
|
57
|
-
pushGenericSummary(lines, summary);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return lines.join('\n');
|
|
34
|
+
return [
|
|
35
|
+
mdTitle(`📊 Analytics for @${account.username}`, `${account.platform} · ${range.from} → ${range.to}`),
|
|
36
|
+
mdKeyValue([
|
|
37
|
+
['Account ID', account.id],
|
|
38
|
+
['Daily snapshots', snapshots.length],
|
|
39
|
+
]),
|
|
40
|
+
mdSection('Summary', mdTable(['Metric', 'Value'], metricRows)),
|
|
41
|
+
mdSection('Quick read', mdBullets(buildInsights(summary, account.platform))),
|
|
42
|
+
].filter(Boolean).join('\n\n');
|
|
61
43
|
},
|
|
62
44
|
};
|
|
63
45
|
|
|
64
|
-
function
|
|
65
|
-
|
|
46
|
+
function genericMetricRows(summary: AccountAnalyticsSummary, platform: string) {
|
|
47
|
+
const rows: Array<[string, string]> = [
|
|
48
|
+
['Followers', `${formatNumber(summary.current_followers)} (${formatDelta(summary.followers_change)} in range)`],
|
|
49
|
+
['Follows gained / lost', `+${formatNumber(summary.total_follows_gained)} / -${formatNumber(summary.total_follows_lost)}`],
|
|
50
|
+
['Total reach', formatNumber(summary.total_reach)],
|
|
51
|
+
['Total views', formatNumber(summary.total_views)],
|
|
52
|
+
['Profile views', formatNumber(summary.total_profile_views)],
|
|
53
|
+
['Accounts engaged', formatNumber(summary.total_accounts_engaged)],
|
|
54
|
+
['Engagement rate by reach', formatPercent(summary.engagement_rate)],
|
|
55
|
+
['Engagement rate by followers', formatPercent(summary.engagement_rate_by_followers)],
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
if (platform === 'pinterest') {
|
|
59
|
+
rows.push(['Outbound clicks', formatNumber(summary.total_website_clicks)]);
|
|
60
|
+
}
|
|
61
|
+
if (platform === 'youtube') {
|
|
62
|
+
rows.push(['Watch minutes', formatNumber(summary.total_watch_minutes)]);
|
|
63
|
+
rows.push(['Likes', formatNumber(summary.total_likes)]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return rows;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
function googleBusinessMetricRows(summary: AccountAnalyticsSummary) {
|
|
70
|
+
const metrics = summary.platform_metrics || {};
|
|
71
|
+
return [
|
|
72
|
+
['Profile views', formatNumber(metrics.profile_views ?? summary.total_views)],
|
|
73
|
+
['Search views', formatNumber(metrics.search_views ?? summary.total_reach)],
|
|
74
|
+
['Maps views', formatNumber(metrics.maps_views ?? summary.total_profile_views)],
|
|
75
|
+
['Customer actions', formatNumber(metrics.customer_actions ?? summary.total_accounts_engaged)],
|
|
76
|
+
['Posts', formatNumber(metrics.posts ?? summary.current_media_count)],
|
|
77
|
+
['Engagement rate (actions / profile views)', formatPercent(summary.engagement_rate)],
|
|
78
|
+
['Website clicks', formatNumber(metrics.website_clicks ?? summary.total_website_clicks)],
|
|
79
|
+
['Call clicks', formatNumber(metrics.call_clicks ?? summary.total_call_clicks)],
|
|
80
|
+
['Direction requests', formatNumber(metrics.direction_requests ?? summary.total_direction_requests)],
|
|
81
|
+
['Conversations', formatNumber(metrics.conversations ?? summary.total_conversations)],
|
|
82
|
+
['Bookings', formatNumber(metrics.bookings ?? summary.total_bookings)],
|
|
83
|
+
];
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
function buildInsights(summary: AccountAnalyticsSummary, platform: string): string[] {
|
|
87
|
+
const insights: string[] = [];
|
|
88
|
+
|
|
89
|
+
if (summary.followers_change > 0) {
|
|
90
|
+
insights.push(`Audience grew by ${formatDelta(summary.followers_change)} followers in this range.`);
|
|
91
|
+
} else if (summary.followers_change < 0) {
|
|
92
|
+
insights.push(`Audience dipped by ${formatDelta(summary.followers_change)} followers in this range.`);
|
|
86
93
|
}
|
|
94
|
+
|
|
95
|
+
if (summary.engagement_rate != null) {
|
|
96
|
+
insights.push(`Engagement by reach is ${formatPercent(summary.engagement_rate)}.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (platform === 'google_business' && summary.total_website_clicks != null) {
|
|
100
|
+
insights.push(`${formatNumber(summary.total_website_clicks)} website clicks came from the profile.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (platform === 'youtube' && summary.total_watch_minutes != null) {
|
|
104
|
+
insights.push(`${formatNumber(summary.total_watch_minutes)} total watch minutes were recorded.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return insights.length > 0 ? insights : ['No standout movement detected in the returned period.'];
|
|
87
108
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, compactText, formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
|
|
3
4
|
|
|
4
5
|
export const getBrandProfileTool = {
|
|
5
6
|
name: 'get_brand_profile',
|
|
@@ -16,48 +17,43 @@ export const getBrandProfileTool = {
|
|
|
16
17
|
const result = await client.getBrandProfile(input.brand_id);
|
|
17
18
|
const profile = result.brand_profile as Record<string, any>;
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const profileBody = [
|
|
21
|
+
mdKeyValue([
|
|
22
|
+
['Source', profile.source],
|
|
23
|
+
['Profile ID', profile.id ? code(profile.id) : undefined],
|
|
24
|
+
['Workspace client ID', profile.workspace_client_id ? code(profile.workspace_client_id) : undefined],
|
|
25
|
+
['Legacy brand group ID', profile.brand_group_id ? code(profile.brand_group_id) : undefined],
|
|
26
|
+
['Logo', profile.logo_url],
|
|
27
|
+
['Last refreshed', profile.last_context_refresh_at ? formatDateTime(profile.last_context_refresh_at) : undefined],
|
|
28
|
+
]),
|
|
29
|
+
mdSection('Voice and audience', mdKeyValue([
|
|
30
|
+
['Tone of voice', compactText(profile.tone_of_voice, 180)],
|
|
31
|
+
['Audience', compactText(profile.audience, 180)],
|
|
32
|
+
['Brand values', compactText(profile.brand_values, 180)],
|
|
33
|
+
['Brand story', compactText(formatStructuredValue(profile.brand_story), 240)],
|
|
34
|
+
])),
|
|
35
|
+
mdSection('Guidance', mdKeyValue([
|
|
36
|
+
['Custom instructions', compactText(profile.custom_instructions, 240)],
|
|
37
|
+
['Keywords', formatStructuredValue(profile.keywords)],
|
|
38
|
+
['Competitors', formatStructuredValue(profile.competitors)],
|
|
39
|
+
['Do / Don’ts', formatStructuredValue(profile.do_donts)],
|
|
40
|
+
['Topics to cover', formatStructuredValue(profile.topics_to_cover)],
|
|
41
|
+
['Topics to avoid', formatStructuredValue(profile.topics_to_avoid)],
|
|
42
|
+
])),
|
|
43
|
+
mdSection('Creative references', mdKeyValue([
|
|
44
|
+
['Visual guidelines', formatStructuredValue(profile.visual_guidelines)],
|
|
45
|
+
['Example posts', formatStructuredValue(profile.example_posts)],
|
|
46
|
+
['Voice examples', formatStructuredValue(profile.voice_examples)],
|
|
47
|
+
])),
|
|
48
|
+
].filter(Boolean).join('\n\n');
|
|
23
49
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (profile.brand_values) lines.push(`• Brand values: ${profile.brand_values}`);
|
|
29
|
-
if (profile.custom_instructions) lines.push(`• Custom instructions: ${profile.custom_instructions}`);
|
|
30
|
-
if (profile.logo_url) lines.push(`• Logo URL: ${profile.logo_url}`);
|
|
31
|
-
|
|
32
|
-
addStructuredLine(lines, 'Keywords', profile.keywords);
|
|
33
|
-
addStructuredLine(lines, 'Competitors', profile.competitors);
|
|
34
|
-
addStructuredLine(lines, 'Do / Don’ts', profile.do_donts);
|
|
35
|
-
addStructuredLine(lines, 'Visual guidelines', profile.visual_guidelines);
|
|
36
|
-
addStructuredLine(lines, 'Brand story', profile.brand_story);
|
|
37
|
-
addStructuredLine(lines, 'Example posts', profile.example_posts);
|
|
38
|
-
addStructuredLine(lines, 'Topics to cover', profile.topics_to_cover);
|
|
39
|
-
addStructuredLine(lines, 'Topics to avoid', profile.topics_to_avoid);
|
|
40
|
-
addStructuredLine(lines, 'Voice examples', profile.voice_examples);
|
|
41
|
-
|
|
42
|
-
if (profile.last_context_refresh_at) {
|
|
43
|
-
lines.push(`• Last refreshed: ${profile.last_context_refresh_at}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (lines.length === 2) {
|
|
47
|
-
lines.push('• No extended brand profile fields have been saved yet.');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return lines.join('\n');
|
|
50
|
+
return [
|
|
51
|
+
mdTitle(`Brand profile: ${profile.brand_name}`),
|
|
52
|
+
profileBody || 'No extended brand profile fields have been saved yet.',
|
|
53
|
+
].join('\n\n');
|
|
51
54
|
},
|
|
52
55
|
};
|
|
53
56
|
|
|
54
|
-
function addStructuredLine(lines: string[], label: string, value: unknown) {
|
|
55
|
-
const formatted = formatStructuredValue(value);
|
|
56
|
-
if (formatted) {
|
|
57
|
-
lines.push(`• ${label}: ${formatted}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
57
|
function formatStructuredValue(value: unknown): string | null {
|
|
62
58
|
if (value == null) return null;
|
|
63
59
|
if (typeof value === 'string') {
|