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,20 +1,44 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
4
+ type DeletePostInput = {
5
+ post_id?: number;
6
+ group_id?: string;
7
+ confirm?: boolean;
8
+ preview_id?: string;
9
+ };
3
10
  export declare const deletePostTool: {
4
11
  name: string;
5
12
  description: string;
6
13
  inputSchema: z.ZodObject<{
7
- post_id: z.ZodNumber;
8
- confirm: z.ZodLiteral<true>;
14
+ post_id: z.ZodOptional<z.ZodNumber>;
15
+ group_id: z.ZodOptional<z.ZodString>;
16
+ confirm: z.ZodOptional<z.ZodBoolean>;
17
+ preview_id: z.ZodOptional<z.ZodString>;
9
18
  }, "strip", z.ZodTypeAny, {
10
- confirm: true;
11
- post_id: number;
19
+ confirm?: boolean | undefined;
20
+ preview_id?: string | undefined;
21
+ post_id?: number | undefined;
22
+ group_id?: string | undefined;
12
23
  }, {
13
- confirm: true;
14
- post_id: number;
24
+ confirm?: boolean | undefined;
25
+ preview_id?: string | undefined;
26
+ post_id?: number | undefined;
27
+ group_id?: string | undefined;
15
28
  }>;
16
- execute(client: PosterlyClient, input: {
17
- post_id: number;
18
- confirm: true;
19
- }): Promise<string>;
29
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
30
+ legacySchemas: {
31
+ delete_post_group: z.ZodObject<{
32
+ group_id: z.ZodString;
33
+ confirm: z.ZodLiteral<true>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ confirm: true;
36
+ group_id: string;
37
+ }, {
38
+ confirm: true;
39
+ group_id: string;
40
+ }>;
41
+ };
42
+ execute(client: PosterlyClient, input: DeletePostInput, ctx: ToolContext): Promise<string>;
20
43
  };
44
+ export {};
@@ -1,24 +1,60 @@
1
1
  import { z } from 'zod';
2
- import { dashboardUrlForPosts } from '../lib/format.js';
2
+ import { code, dashboardUrlForPosts } from '../lib/format.js';
3
+ import { previewThenConfirm, validateArgs } from '../lib/tool-runtime.js';
3
4
  export const deletePostTool = {
4
5
  name: 'delete_post',
5
- description: 'Delete a scheduled or draft post. DESTRUCTIVE and IRREVERSIBLE - the post and its caption cannot be recovered. Cannot delete published or currently publishing posts.\n\n' +
6
- 'REQUIRED BEFORE CALLING: Fetch the post with `get_post` first and show the user what will be deleted (caption, account, scheduled time). Get explicit confirmation ("yes delete it", "remove it") before calling. Never delete multiple posts in a single batch without listing each one and confirming the full list.',
6
+ description: 'Delete posts. DESTRUCTIVE and IRREVERSIBLE: deleted posts and captions cannot be recovered, and published or currently publishing posts are never deleted. post_id: delete one scheduled or draft post; fetch it with `get_post` first, show the user what will be deleted (caption, account, scheduled time), and pass confirm=true only after explicit confirmation ("yes delete it", "remove it"). group_id: delete every draft, scheduled, failed, or paused post matching a caller-defined group_id, post_group_id, api_group_id, or release_id; preview then confirm: call without confirm to get a preview and preview_id, show it, then call again with confirm=true and that preview_id. Never delete several posts one by one without listing each and confirming the full list.',
7
7
  inputSchema: z.object({
8
- post_id: z.number().describe('The post ID to delete'),
9
- confirm: z.literal(true).describe('Must be true after explicit user confirmation of the post deletion.'),
8
+ post_id: z.number().optional().describe('Delete this one post.'),
9
+ group_id: z.string().min(1).max(160).optional().describe('Delete every unpublished post in this group or release instead.'),
10
+ confirm: z
11
+ .boolean()
12
+ .optional()
13
+ .describe('post_id: true after explicit user confirmation of the deletion. group_id: leave out to get a preview; true only with the preview_id after the user approved it.'),
14
+ preview_id: z.string().optional().describe('group_id: the preview_id from the preview call. Required with confirm: true.'),
10
15
  }),
11
- async execute(client, input) {
12
- if (input.confirm !== true) {
13
- throw new Error('Refusing to delete post without confirm=true after explicit user confirmation.');
16
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
17
+ legacySchemas: {
18
+ delete_post_group: z.object({
19
+ group_id: z.string().min(1).max(160),
20
+ confirm: z.literal(true),
21
+ }),
22
+ },
23
+ async execute(client, input, ctx) {
24
+ const { mode, args } = validateArgs(ctx, 'delete_post', input);
25
+ if (mode === 'group') {
26
+ return previewThenConfirm({
27
+ tool: 'delete_post',
28
+ action: 'group',
29
+ args,
30
+ nonce: false,
31
+ legacyRefusal: 'Refusing to delete post group without confirm=true after explicit user confirmation.',
32
+ ctx,
33
+ // GET /api/v1/posts has no group filter, so the preview says in plain
34
+ // words which posts match instead of listing them.
35
+ describe: async () => [
36
+ `Deletes every draft, scheduled, failed, or paused post whose group_id, post_group_id, api_group_id, or release_id is ${code(args.group_id)}. Published posts are never deleted. This cannot be undone.`,
37
+ 'To see the posts first, call list_posts and check each post\'s group or release ID.',
38
+ ].join('\n\n'),
39
+ run: async () => {
40
+ const result = await client.deletePostGroup(args.group_id);
41
+ return JSON.stringify(result, null, 2);
42
+ },
43
+ });
14
44
  }
15
- const result = await client.deletePost(input.post_id);
16
- return [
17
- `Post #${result.id} deleted successfully.`,
18
- 'It has been removed from the posterly queue.',
19
- `View queue in posterly: ${dashboardUrlForPosts()}`,
20
- '',
21
- 'Next step for the AI agent: tell the user the post is gone and offer to create, reschedule, or review the next post.',
22
- ].join('\n');
45
+ return deleteOne(client, args);
23
46
  },
24
47
  };
48
+ async function deleteOne(client, input) {
49
+ if (input.confirm !== true) {
50
+ throw new Error('Refusing to delete post without confirm=true after explicit user confirmation.');
51
+ }
52
+ const result = await client.deletePost(input.post_id);
53
+ return [
54
+ `Post #${result.id} deleted successfully.`,
55
+ 'It has been removed from the posterly queue.',
56
+ `View queue in posterly: ${dashboardUrlForPosts()}`,
57
+ '',
58
+ 'Next step for the AI agent: tell the user the post is gone and offer to create, reschedule, or review the next post.',
59
+ ].join('\n');
60
+ }
@@ -35,12 +35,12 @@ export declare const generateCaptionsTool: {
35
35
  platforms: ("instagram" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business")[];
36
36
  workspace_id?: string | undefined;
37
37
  count?: number | undefined;
38
+ mode?: "generate" | "adapt" | undefined;
38
39
  social_account_id?: string | undefined;
39
40
  brief?: string | undefined;
40
41
  tone?: string | undefined;
41
42
  allow_emojis?: boolean | undefined;
42
43
  hashtag_strategy?: "none" | "smart" | "minimal" | "aggressive" | undefined;
43
- mode?: "generate" | "adapt" | undefined;
44
44
  source_caption?: string | undefined;
45
45
  source_platform?: "instagram" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | undefined;
46
46
  preserve_links?: boolean | undefined;
@@ -55,12 +55,12 @@ export declare const generateCaptionsTool: {
55
55
  platforms: ("instagram" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business")[];
56
56
  workspace_id?: string | undefined;
57
57
  count?: number | undefined;
58
+ mode?: "generate" | "adapt" | undefined;
58
59
  social_account_id?: string | undefined;
59
60
  brief?: string | undefined;
60
61
  tone?: string | undefined;
61
62
  allow_emojis?: boolean | undefined;
62
63
  hashtag_strategy?: "none" | "smart" | "minimal" | "aggressive" | undefined;
63
- mode?: "generate" | "adapt" | undefined;
64
64
  source_caption?: string | undefined;
65
65
  source_platform?: "instagram" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | undefined;
66
66
  preserve_links?: boolean | undefined;
@@ -20,10 +20,10 @@ export declare const generateImageTool: {
20
20
  prompt: string;
21
21
  provider?: "google" | "xai" | undefined;
22
22
  aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "1:4" | "1:8" | "21:9" | "4:1" | "8:1" | "5:4" | undefined;
23
+ resolution?: "512" | "1K" | "2K" | "4K" | undefined;
24
+ model?: "pro" | "lite" | "flash" | undefined;
23
25
  style?: "minimal" | "professional" | "photographic" | "illustration" | "vibrant" | "youtube_thumbnail" | "reel_cover" | "review_background" | undefined;
24
26
  variations?: number | undefined;
25
- model?: "pro" | "lite" | "flash" | undefined;
26
- resolution?: "512" | "1K" | "2K" | "4K" | undefined;
27
27
  quality?: "low" | "medium" | "high" | undefined;
28
28
  thinking_level?: "minimal" | "high" | undefined;
29
29
  reference_image_urls?: string[] | undefined;
@@ -32,10 +32,10 @@ export declare const generateImageTool: {
32
32
  prompt: string;
33
33
  provider?: "google" | "xai" | undefined;
34
34
  aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "1:4" | "1:8" | "21:9" | "4:1" | "8:1" | "5:4" | undefined;
35
+ resolution?: "512" | "1K" | "2K" | "4K" | undefined;
36
+ model?: "pro" | "lite" | "flash" | undefined;
35
37
  style?: "minimal" | "professional" | "photographic" | "illustration" | "vibrant" | "youtube_thumbnail" | "reel_cover" | "review_background" | undefined;
36
38
  variations?: number | undefined;
37
- model?: "pro" | "lite" | "flash" | undefined;
38
- resolution?: "512" | "1K" | "2K" | "4K" | undefined;
39
39
  quality?: "low" | "medium" | "high" | undefined;
40
40
  thinking_level?: "minimal" | "high" | undefined;
41
41
  reference_image_urls?: string[] | undefined;
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { mdKeyValue, mdTitle } from '../lib/format.js';
3
3
  export const generateImageTool = {
4
4
  name: 'generate_image',
5
- description: 'Queue an AI image job via posterly using Nano Banana (Gemini) or Grok Imagine Image 2.0 (xAI). Returns job_id. Poll get_image_job until status is completed, then pass urls to create_post as media_url / media_urls. Gemini thinking high does not change the credit price.\n\n' +
5
+ description: 'Queue an AI image job via posterly using Nano Banana (Gemini) or Grok Imagine Image 2.0 (xAI). Returns job_id. Poll list_jobs with type=image until status is completed, then pass urls to create_post as media_url / media_urls. Gemini thinking high does not change the credit price.\n\n' +
6
6
  'COSTS CREDITS. Every call consumes part of the user\'s monthly AI image quota (or purchased credits once the quota is exhausted). Do NOT call speculatively - always describe the image you\'re about to generate (subject, style, aspect ratio, quality/thinking, resolution) to the user and get confirmation before calling.\n\n' +
7
7
  'If the user is over their plan limit and has no credits, this tool returns a 402 with upgrade info. Surface that message verbatim - do not retry.\n\n' +
8
8
  'Common aspect ratios by platform:\n' +
@@ -80,7 +80,7 @@ export const generateImageTool = {
80
80
  ['Credits used', result.credits_used],
81
81
  ['Billed from', result.usage?.billed_from],
82
82
  ]),
83
- result.message || 'Poll get_image_job with this job_id until status is completed, then pass urls to create_post.',
83
+ result.message || 'Poll list_jobs with type=image and this job_id until status is completed, then pass urls to create_post.',
84
84
  ].filter(Boolean).join('\n\n');
85
85
  },
86
86
  };
@@ -30,11 +30,11 @@ export declare const generateVideoTool: {
30
30
  confirm: true;
31
31
  prompt: string;
32
32
  provider?: "google" | "xai" | undefined;
33
- aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
34
- model?: "lite" | "fast" | "standard" | undefined;
35
- resolution?: "480p" | "720p" | "1080p" | undefined;
36
33
  negative_prompt?: string | undefined;
34
+ aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
37
35
  duration_seconds?: number | undefined;
36
+ resolution?: "480p" | "720p" | "1080p" | undefined;
37
+ model?: "lite" | "fast" | "standard" | undefined;
38
38
  image_url?: string | undefined;
39
39
  end_image_url?: string | undefined;
40
40
  reference_images?: {
@@ -47,11 +47,11 @@ export declare const generateVideoTool: {
47
47
  confirm: true;
48
48
  prompt: string;
49
49
  provider?: "google" | "xai" | undefined;
50
- aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
51
- model?: "lite" | "fast" | "standard" | undefined;
52
- resolution?: "480p" | "720p" | "1080p" | undefined;
53
50
  negative_prompt?: string | undefined;
51
+ aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
54
52
  duration_seconds?: number | undefined;
53
+ resolution?: "480p" | "720p" | "1080p" | undefined;
54
+ model?: "lite" | "fast" | "standard" | undefined;
55
55
  image_url?: string | undefined;
56
56
  end_image_url?: string | undefined;
57
57
  reference_images?: {
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export const generateVideoTool = {
3
3
  name: 'generate_video',
4
- description: 'Queue an AI video generation job with Google Veo 3.1 or xAI Grok Imagine Video 1.5. COSTS CREDITS: confirm provider, prompt, duration, resolution, aspect ratio, audio choice, and credit cost with the user before calling. Grok supports 1–15 seconds and 480p–1080p; reference-to-video is capped at 720p. Poll get_video_job for status and final video_url.',
4
+ description: 'Queue an AI video generation job with Google Veo 3.1 or xAI Grok Imagine Video 1.5. COSTS CREDITS: confirm provider, prompt, duration, resolution, aspect ratio, audio choice, and credit cost with the user before calling. Grok supports 1 to 15 seconds and 480p to 1080p; reference-to-video is capped at 720p. Poll list_jobs with type=video for status and final video_url.',
5
5
  inputSchema: z.object({
6
6
  provider: z.enum(['google', 'xai']).default('google').optional(),
7
7
  prompt: z.string().min(5).max(4000),
@@ -13,14 +13,14 @@ export const getAgentSignupInfoTool = {
13
13
  authState,
14
14
  '',
15
15
  'Pre-auth tools:',
16
- '- get_mcp_status: checks the installed server version, latest npm version, MCP endpoint health, API auth health, and whether POSTERLY_API_KEY is configured.',
16
+ '- whoami with view=status: checks the installed server version, latest npm version, MCP endpoint health, API auth health, and whether POSTERLY_API_KEY is configured.',
17
17
  '- start_signup: starts paid signup and returns a signup poll URL, a browser Checkout fallback, and, when enabled, a prepaid PaymentIntent for a Link virtual card (credential_type: card).',
18
18
  '- get_signup_session: polls payment, password, and agent-access status.',
19
19
  '',
20
20
  'After posterly access is installed, onboard the user IN THIS chat (do not send them to /onboarding-v2):',
21
21
  '- whoami: confirm the paid account and read onboarding.next_step.',
22
22
  '- About you: confirm name and IANA timezone.',
23
- '- create_connect_session / get_connect_session: first social account in the browser.',
23
+ '- create_connect_session, then list_accounts with view=connect_session: first social account in the browser.',
24
24
  '- Brand voice: ask how they want to sound, then use that on captions.',
25
25
  '- create_post: first scheduled post after validate_post and explicit confirm.',
26
26
  '',
@@ -1,16 +1,31 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
3
4
  export declare const getPostTool: {
4
5
  name: string;
5
6
  description: string;
6
7
  inputSchema: z.ZodObject<{
7
8
  post_id: z.ZodNumber;
9
+ view: z.ZodOptional<z.ZodEnum<["post", "missing"]>>;
8
10
  }, "strip", z.ZodTypeAny, {
9
11
  post_id: number;
12
+ view?: "post" | "missing" | undefined;
10
13
  }, {
11
14
  post_id: number;
15
+ view?: "post" | "missing" | undefined;
12
16
  }>;
17
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
18
+ legacySchemas: {
19
+ get_post_missing: z.ZodObject<{
20
+ post_id: z.ZodNumber;
21
+ }, "strip", z.ZodTypeAny, {
22
+ post_id: number;
23
+ }, {
24
+ post_id: number;
25
+ }>;
26
+ };
13
27
  execute(client: PosterlyClient, input: {
14
28
  post_id: number;
15
- }): Promise<string>;
29
+ view?: "post" | "missing";
30
+ }, ctx: ToolContext): Promise<string>;
16
31
  };
@@ -1,28 +1,44 @@
1
1
  import { z } from 'zod';
2
2
  import { dashboardUrlForPost, formatLocalDateTime } from '../lib/format.js';
3
+ import { validateArgs } from '../lib/tool-runtime.js';
3
4
  export const getPostTool = {
4
5
  name: 'get_post',
5
- description: 'Get details of a specific post by ID. Returns caption, status, scheduled time, media, and platform info.',
6
+ description: 'One post by ID. view=post (default): caption, status, scheduled time, media, and platform info. view=missing: whether the post is missing required content, media, account, platform settings, or metadata before it can publish; use it to repair failed or imported posts.',
6
7
  inputSchema: z.object({
7
8
  post_id: z.number().describe('The post ID to look up'),
9
+ view: z.enum(['post', 'missing']).optional().describe('post (default) or missing.'),
8
10
  }),
9
- async execute(client, input) {
10
- const result = await client.getPost(input.post_id);
11
- const p = result.post;
12
- const lines = [
13
- `Post #${p.id}`,
14
- `• Status: ${p.status}`,
15
- `• Caption: ${p.content || '(empty)'}`,
16
- `• Type: ${p.post_type || 'unknown'}`,
17
- `• Scheduled: ${formatLocalDateTime(p.scheduled_at)}`,
18
- `• View in posterly: ${dashboardUrlForPost({ id: p.id, scheduled_at: p.scheduled_at })}`,
19
- ];
20
- if (p.platform_post_url)
21
- lines.push(`• Published URL: ${p.platform_post_url}`);
22
- if (p.media_url)
23
- lines.push(`• Media: ${p.media_url}`);
24
- if (p.media_urls?.length)
25
- lines.push(`• Media (${p.media_urls.length}): ${p.media_urls.join(', ')}`);
26
- return lines.join('\n');
11
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
12
+ legacySchemas: {
13
+ get_post_missing: z.object({ post_id: z.number().int().positive() }),
14
+ },
15
+ async execute(client, input, ctx) {
16
+ const { mode, args } = validateArgs(ctx, 'get_post', input);
17
+ if (mode === 'missing')
18
+ return postMissing(client, args);
19
+ return post(client, args);
27
20
  },
28
21
  };
22
+ async function post(client, input) {
23
+ const result = await client.getPost(input.post_id);
24
+ const p = result.post;
25
+ const lines = [
26
+ `Post #${p.id}`,
27
+ `• Status: ${p.status}`,
28
+ `• Caption: ${p.content || '(empty)'}`,
29
+ `• Type: ${p.post_type || 'unknown'}`,
30
+ `• Scheduled: ${formatLocalDateTime(p.scheduled_at)}`,
31
+ `• View in posterly: ${dashboardUrlForPost({ id: p.id, scheduled_at: p.scheduled_at })}`,
32
+ ];
33
+ if (p.platform_post_url)
34
+ lines.push(`• Published URL: ${p.platform_post_url}`);
35
+ if (p.media_url)
36
+ lines.push(`• Media: ${p.media_url}`);
37
+ if (p.media_urls?.length)
38
+ lines.push(`• Media (${p.media_urls.length}): ${p.media_urls.join(', ')}`);
39
+ return lines.join('\n');
40
+ }
41
+ async function postMissing(client, input) {
42
+ const result = await client.getPostMissing(input.post_id);
43
+ return JSON.stringify(result, null, 2);
44
+ }
@@ -1,16 +1,62 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
4
+ type ListAccountsInput = {
5
+ view?: 'accounts' | 'learned_voice' | 'performance_profile' | 'connect_session';
6
+ workspace_id?: string;
7
+ account_id?: string | number;
8
+ session_id?: string;
9
+ debug?: boolean;
10
+ };
3
11
  export declare const listAccountsTool: {
4
12
  name: string;
5
13
  description: string;
6
14
  inputSchema: z.ZodObject<{
15
+ view: z.ZodOptional<z.ZodEnum<["accounts", "learned_voice", "performance_profile", "connect_session"]>>;
7
16
  workspace_id: z.ZodOptional<z.ZodString>;
17
+ account_id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
18
+ session_id: z.ZodOptional<z.ZodString>;
19
+ debug: z.ZodOptional<z.ZodBoolean>;
8
20
  }, "strip", z.ZodTypeAny, {
9
21
  workspace_id?: string | undefined;
22
+ account_id?: string | number | undefined;
23
+ view?: "accounts" | "learned_voice" | "performance_profile" | "connect_session" | undefined;
24
+ session_id?: string | undefined;
25
+ debug?: boolean | undefined;
10
26
  }, {
11
27
  workspace_id?: string | undefined;
28
+ account_id?: string | number | undefined;
29
+ view?: "accounts" | "learned_voice" | "performance_profile" | "connect_session" | undefined;
30
+ session_id?: string | undefined;
31
+ debug?: boolean | undefined;
12
32
  }>;
13
- execute(client: PosterlyClient, input: {
14
- workspace_id?: string;
15
- }): Promise<string>;
33
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
34
+ legacySchemas: {
35
+ get_learned_voice: z.ZodObject<{
36
+ account_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ account_id: string | number;
39
+ }, {
40
+ account_id: string | number;
41
+ }>;
42
+ get_performance_profile: z.ZodObject<{
43
+ account_id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
44
+ }, "strip", z.ZodTypeAny, {
45
+ account_id: string | number;
46
+ }, {
47
+ account_id: string | number;
48
+ }>;
49
+ get_connect_session: z.ZodObject<{
50
+ session_id: z.ZodString;
51
+ debug: z.ZodOptional<z.ZodBoolean>;
52
+ }, "strip", z.ZodTypeAny, {
53
+ session_id: string;
54
+ debug?: boolean | undefined;
55
+ }, {
56
+ session_id: string;
57
+ debug?: boolean | undefined;
58
+ }>;
59
+ };
60
+ execute(client: PosterlyClient, input: ListAccountsInput, ctx: ToolContext): Promise<string>;
16
61
  };
62
+ export {};
@@ -1,44 +1,159 @@
1
1
  import { z } from 'zod';
2
- import { code, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
2
+ import { DASHBOARD_CONNECT_URL, code, formatConnectedAccounts, formatDateTime, formatLocalDateTime, formatPercent, mdBullets, mdEmpty, mdKeyValue, mdLearnedVoice, mdQuote, mdSection, mdTable, mdTitle, } from '../lib/format.js';
3
+ import { validateArgs } from '../lib/tool-runtime.js';
3
4
  export const listAccountsTool = {
4
5
  name: 'list_accounts',
5
- description: 'List connected social media accounts. Instagram rows identify direct-login versus Meta-linked connections and whether native media tags/collaborators are available. Check this before using Instagram user_tags or collaborators.',
6
+ description: "Connected social accounts and what posterly has learned about them. view=accounts (default): connected accounts, optionally one workspace_id or one account_id; Instagram rows identify direct-login versus Meta-linked connections and whether native media tags/collaborators are available, so check this before Instagram user_tags or collaborators. view=learned_voice (account_id): the voice learned from the account's real published captions (summary, traits, style guidelines, habits); read-only, never overwrites the saved brand profile. view=performance_profile (account_id): 90-day coaching stats, engagement-rate trend, and summary (Pro plan or higher; platforms without per-post analytics never get one). view=connect_session (session_id): poll a session from create_connect_session and keep the user updated in plain language; stop when status is connected, failed, cancelled, or expired.",
6
7
  inputSchema: z.object({
8
+ view: z
9
+ .enum(['accounts', 'learned_voice', 'performance_profile', 'connect_session'])
10
+ .optional()
11
+ .describe('accounts (default), learned_voice, performance_profile, or connect_session.'),
7
12
  workspace_id: z
8
13
  .string()
9
14
  .optional()
10
- .describe('Filter to accounts in a specific workspace (get IDs via whoami).'),
15
+ .describe('view=accounts: filter to accounts in a specific workspace (get IDs via whoami).'),
16
+ account_id: z
17
+ .union([z.string(), z.number()])
18
+ .optional()
19
+ .describe('Social account ID from list_accounts. Required for learned_voice and performance_profile; with view=accounts it returns just that account.'),
20
+ session_id: z.string().optional().describe('view=connect_session: connect session ID returned by create_connect_session.'),
21
+ debug: z
22
+ .boolean()
23
+ .optional()
24
+ .describe('view=connect_session: when true, include the raw connect session JSON for debugging. Keep false for normal user-facing agent flows.'),
11
25
  }),
12
- async execute(client, input) {
13
- const accounts = await client.listAccounts({ workspace_id: input.workspace_id });
14
- if (accounts.length === 0) {
15
- return input.workspace_id
16
- ? mdEmpty('connected accounts', 'This workspace does not have any social accounts connected yet.')
17
- : mdEmpty('connected accounts', 'posterly does not see any connected social accounts yet.', 'Connect accounts in the posterly dashboard, then run this again.');
26
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
27
+ legacySchemas: {
28
+ get_learned_voice: z.object({ account_id: z.union([z.string(), z.number()]) }),
29
+ get_performance_profile: z.object({ account_id: z.union([z.string(), z.number()]) }),
30
+ get_connect_session: z.object({ session_id: z.string(), debug: z.boolean().optional() }),
31
+ },
32
+ async execute(client, input, ctx) {
33
+ const { mode, args } = validateArgs(ctx, 'list_accounts', input);
34
+ if (mode === 'learned_voice')
35
+ return learnedVoice(client, args);
36
+ if (mode === 'performance_profile')
37
+ return performanceProfile(client, args);
38
+ if (mode === 'connect_session') {
39
+ const result = await client.getConnectSession(args.session_id);
40
+ return formatConnectSession(result.connect_session, Boolean(args.debug));
18
41
  }
19
- const hasInstagram = accounts.some((account) => account.platform === 'instagram');
20
- return [
21
- mdTitle(`Connected accounts (${accounts.length})`),
22
- mdTable(hasInstagram
23
- ? ['Platform', 'Account', 'Account ID', 'Workspace', 'Connection', 'Native tags', 'Collaborators']
24
- : ['Platform', 'Account', 'Account ID', 'Workspace'], accounts.map((account) => [
25
- account.platform,
26
- `@${account.username}`,
27
- code(account.id),
28
- account.workspace_id ? code(account.workspace_id) : 'default',
29
- ...(hasInstagram
30
- ? account.platform === 'instagram'
31
- ? [
32
- account.connection_mode === 'meta' ? 'Meta-linked' : account.connection_mode === 'instagram_direct' ? 'Direct login' : 'unknown',
33
- account.instagram_tagging?.media_user_tags?.supported ? 'yes' : 'no',
34
- account.instagram_tagging?.collaborators?.supported ? 'yes' : 'no',
35
- ]
36
- : ['—', '—', '—']
37
- : []),
38
- ])),
39
- hasInstagram
40
- ? '_Instagram caption @mentions use @username in caption text. Native media tags work with eligible direct or Meta-linked accounts; collaborator invitations require an eligible Meta-linked account._'
41
- : '',
42
- ].filter(Boolean).join('\n\n');
42
+ return accountsList(client, args);
43
43
  },
44
44
  };
45
+ async function accountsList(client, input) {
46
+ const listed = await client.listAccounts({ workspace_id: input.workspace_id });
47
+ // There is no GET /accounts/:id, so account_id filters the list here.
48
+ const accounts = input.account_id !== undefined
49
+ ? listed.filter((account) => String(account.id) === String(input.account_id))
50
+ : listed;
51
+ if (input.account_id !== undefined && accounts.length === 0) {
52
+ return mdEmpty('connected account', `No connected account has the ID ${String(input.account_id)}.`);
53
+ }
54
+ if (accounts.length === 0) {
55
+ return input.workspace_id
56
+ ? mdEmpty('connected accounts', 'This workspace does not have any social accounts connected yet.')
57
+ : mdEmpty('connected accounts', 'posterly does not see any connected social accounts yet.', 'Connect accounts in the posterly dashboard, then run this again.');
58
+ }
59
+ const hasInstagram = accounts.some((account) => account.platform === 'instagram');
60
+ return [
61
+ mdTitle(`Connected accounts (${accounts.length})`),
62
+ mdTable(hasInstagram
63
+ ? ['Platform', 'Account', 'Account ID', 'Workspace', 'Connection', 'Native tags', 'Collaborators']
64
+ : ['Platform', 'Account', 'Account ID', 'Workspace'], accounts.map((account) => [
65
+ account.platform,
66
+ `@${account.username}`,
67
+ code(account.id),
68
+ account.workspace_id ? code(account.workspace_id) : 'default',
69
+ ...(hasInstagram
70
+ ? account.platform === 'instagram'
71
+ ? [
72
+ account.connection_mode === 'meta' ? 'Meta-linked' : account.connection_mode === 'instagram_direct' ? 'Direct login' : 'unknown',
73
+ account.instagram_tagging?.media_user_tags?.supported ? 'yes' : 'no',
74
+ account.instagram_tagging?.collaborators?.supported ? 'yes' : 'no',
75
+ ]
76
+ : ['n/a', 'n/a', 'n/a']
77
+ : []),
78
+ ])),
79
+ hasInstagram
80
+ ? '_Instagram caption @mentions use @username in caption text. Native media tags work with eligible direct or Meta-linked accounts; collaborator invitations require an eligible Meta-linked account._'
81
+ : '',
82
+ ].filter(Boolean).join('\n\n');
83
+ }
84
+ async function learnedVoice(client, input) {
85
+ const { learned_voice } = await client.getAccountLearnedVoice(input.account_id);
86
+ const body = mdLearnedVoice(learned_voice);
87
+ if (!body) {
88
+ return [
89
+ mdTitle('No learned voice yet'),
90
+ 'posterly has not learned a voice for this account yet. It learns automatically once the account has enough published posts (the captions are analyzed; the saved brand profile is never overwritten).',
91
+ ].join('\n\n');
92
+ }
93
+ return [mdTitle('Learned voice (from published captions)'), body].join('\n\n');
94
+ }
95
+ async function performanceProfile(client, input) {
96
+ const { performance_profile, reason } = await client.getAccountPerformanceProfile(input.account_id);
97
+ if (!performance_profile) {
98
+ const detail = reason === 'platform_not_measurable'
99
+ ? 'This platform does not produce per-post analytics, so posterly cannot build a performance profile for it.'
100
+ : reason === 'feature_not_available'
101
+ ? 'Performance profiles require a Pro plan or higher.'
102
+ : 'posterly has not built a performance profile for this account yet. It needs about 90 days of tracked posts to learn from.';
103
+ return mdEmpty('performance profile yet', detail);
104
+ }
105
+ const label = performance_profile.username
106
+ ? `@${performance_profile.username}${performance_profile.platform ? ` (${performance_profile.platform})` : ''}`
107
+ : `account ${performance_profile.social_account_id}`;
108
+ const rate = performance_profile.engagement_rate_avg != null
109
+ ? formatPercent(performance_profile.engagement_rate_avg * 100)
110
+ : 'n/a';
111
+ return [
112
+ mdTitle('Account performance profile', `${label} · last ${performance_profile.window_days ?? 90} days`),
113
+ performance_profile.narrative_summary ? mdSection('Summary', mdQuote(performance_profile.narrative_summary)) : '',
114
+ performance_profile.bullets.length ? mdSection('What is working', mdBullets(performance_profile.bullets)) : '',
115
+ mdSection('Stats', mdKeyValue([
116
+ ['Posts sampled', performance_profile.sample_size],
117
+ ['Avg engagement rate', rate],
118
+ ['Engagement trend', performance_profile.engagement_rate_trend],
119
+ ['Derived at', formatDateTime(performance_profile.derived_at)],
120
+ ])),
121
+ ].filter(Boolean).join('\n\n');
122
+ }
123
+ function formatConnectSession(session, includeRaw) {
124
+ const connected = session.connected_count ?? session.connected_accounts?.length ?? 0;
125
+ return [
126
+ `posterly connect session: ${session.id}`,
127
+ `Platform: ${session.platform}`,
128
+ `Status: ${session.status}`,
129
+ `Message: ${session.status_message}`,
130
+ `Connected accounts: ${connected}`,
131
+ `Accounts: ${formatConnectedAccounts(session.connected_accounts)}`,
132
+ session.error_message ? `Error: ${session.error_message}` : '',
133
+ `Expires: ${formatLocalDateTime(session.expires_at)}`,
134
+ `Dashboard connect page: ${DASHBOARD_CONNECT_URL}`,
135
+ '',
136
+ guidanceForStatus(session),
137
+ includeRaw ? `\nRaw connect session:\n${JSON.stringify(session, null, 2)}` : '',
138
+ ].filter(Boolean).join('\n');
139
+ }
140
+ function guidanceForStatus(session) {
141
+ switch (session.status) {
142
+ case 'created':
143
+ case 'opened':
144
+ case 'awaiting_provider':
145
+ return 'Next step for the AI agent: tell the user posterly is waiting for browser approval, then keep polling this session. Do not ask for social passwords or OAuth codes.';
146
+ case 'awaiting_credentials':
147
+ return 'Next step for the AI agent: tell the user posterly needs them to finish the browser credential step, then keep polling. Do not ask the user to paste credentials into chat.';
148
+ case 'connected':
149
+ return `Next step for the AI agent: tell the user ${formatConnectedAccounts(session.connected_accounts)} is connected, then ask what they want to schedule first or offer to create a test post.`;
150
+ case 'failed':
151
+ return 'Next step for the AI agent: tell the user the connection failed, share the error if present, and offer to create a fresh connect session.';
152
+ case 'cancelled':
153
+ return 'Next step for the AI agent: tell the user the connection was cancelled and offer to create a fresh connect session when they are ready.';
154
+ case 'expired':
155
+ return 'Next step for the AI agent: tell the user the connection link expired and create a fresh connect session if they still want to connect this account.';
156
+ default:
157
+ return 'Next step for the AI agent: explain the current connection status in plain language and keep polling unless the session is terminal.';
158
+ }
159
+ }