postproxy-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +635 -0
  3. package/dist/api/client.d.ts +71 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +432 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/auth/credentials.d.ts +19 -0
  8. package/dist/auth/credentials.d.ts.map +1 -0
  9. package/dist/auth/credentials.js +40 -0
  10. package/dist/auth/credentials.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +44 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/server.d.ts +162 -0
  16. package/dist/server.d.ts.map +1 -0
  17. package/dist/server.js +220 -0
  18. package/dist/server.js.map +1 -0
  19. package/dist/setup-cli.d.ts +6 -0
  20. package/dist/setup-cli.d.ts.map +1 -0
  21. package/dist/setup-cli.js +10 -0
  22. package/dist/setup-cli.js.map +1 -0
  23. package/dist/setup.d.ts +8 -0
  24. package/dist/setup.d.ts.map +1 -0
  25. package/dist/setup.js +143 -0
  26. package/dist/setup.js.map +1 -0
  27. package/dist/tools/accounts.d.ts +11 -0
  28. package/dist/tools/accounts.d.ts.map +1 -0
  29. package/dist/tools/accounts.js +53 -0
  30. package/dist/tools/accounts.js.map +1 -0
  31. package/dist/tools/auth.d.ts +11 -0
  32. package/dist/tools/auth.d.ts.map +1 -0
  33. package/dist/tools/auth.js +35 -0
  34. package/dist/tools/auth.js.map +1 -0
  35. package/dist/tools/history.d.ts +13 -0
  36. package/dist/tools/history.d.ts.map +1 -0
  37. package/dist/tools/history.js +79 -0
  38. package/dist/tools/history.js.map +1 -0
  39. package/dist/tools/post.d.ts +44 -0
  40. package/dist/tools/post.d.ts.map +1 -0
  41. package/dist/tools/post.js +251 -0
  42. package/dist/tools/post.js.map +1 -0
  43. package/dist/tools/profiles.d.ts +11 -0
  44. package/dist/tools/profiles.d.ts.map +1 -0
  45. package/dist/tools/profiles.js +52 -0
  46. package/dist/tools/profiles.js.map +1 -0
  47. package/dist/types/index.d.ts +147 -0
  48. package/dist/types/index.d.ts.map +1 -0
  49. package/dist/types/index.js +5 -0
  50. package/dist/types/index.js.map +1 -0
  51. package/dist/utils/errors.d.ts +21 -0
  52. package/dist/utils/errors.d.ts.map +1 -0
  53. package/dist/utils/errors.js +33 -0
  54. package/dist/utils/errors.js.map +1 -0
  55. package/dist/utils/idempotency.d.ts +8 -0
  56. package/dist/utils/idempotency.d.ts.map +1 -0
  57. package/dist/utils/idempotency.js +23 -0
  58. package/dist/utils/idempotency.js.map +1 -0
  59. package/dist/utils/logger.d.ts +20 -0
  60. package/dist/utils/logger.d.ts.map +1 -0
  61. package/dist/utils/logger.js +68 -0
  62. package/dist/utils/logger.js.map +1 -0
  63. package/dist/utils/validation.d.ts +555 -0
  64. package/dist/utils/validation.d.ts.map +1 -0
  65. package/dist/utils/validation.js +145 -0
  66. package/dist/utils/validation.js.map +1 -0
  67. package/package.json +39 -0
  68. package/src/api/client.ts +497 -0
  69. package/src/auth/credentials.ts +43 -0
  70. package/src/index.ts +57 -0
  71. package/src/server.ts +235 -0
  72. package/src/setup-cli.ts +11 -0
  73. package/src/setup.ts +187 -0
  74. package/src/tools/auth.ts +45 -0
  75. package/src/tools/history.ts +89 -0
  76. package/src/tools/post.ts +338 -0
  77. package/src/tools/profiles.ts +69 -0
  78. package/src/types/index.ts +161 -0
  79. package/src/utils/errors.ts +38 -0
  80. package/src/utils/idempotency.ts +31 -0
  81. package/src/utils/logger.ts +75 -0
  82. package/src/utils/validation.ts +171 -0
  83. package/tsconfig.json +19 -0
  84. package/worker/index.ts +901 -0
  85. package/wrangler.toml +11 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Credentials management - reading API key from environment variables
3
+ */
4
+
5
+ const DEFAULT_BASE_URL = "https://api.postproxy.dev/api";
6
+
7
+ /**
8
+ * Get API key from environment variables
9
+ * @returns API key or null if not found
10
+ */
11
+ export function getApiKey(): string | null {
12
+ const apiKey = process.env.POSTPROXY_API_KEY;
13
+ return apiKey || null;
14
+ }
15
+
16
+ /**
17
+ * Get base URL from environment variables or use default
18
+ * @returns Base URL for PostProxy API
19
+ */
20
+ export function getBaseUrl(): string {
21
+ return process.env.POSTPROXY_BASE_URL || DEFAULT_BASE_URL;
22
+ }
23
+
24
+ /**
25
+ * Validate API key by making a test request (optional)
26
+ * This can be used to verify the key is valid before using it
27
+ */
28
+ export async function validateApiKey(apiKey: string, baseUrl: string): Promise<boolean> {
29
+ try {
30
+ const response = await fetch(`${baseUrl}/profile_groups/`, {
31
+ method: "GET",
32
+ headers: {
33
+ Authorization: `Bearer ${apiKey}`,
34
+ "Content-Type": "application/json",
35
+ },
36
+ signal: AbortSignal.timeout(5000), // 5 second timeout for validation
37
+ });
38
+
39
+ return response.ok;
40
+ } catch (error) {
41
+ return false;
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Entry point for PostProxy MCP Server
4
+ */
5
+
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { getApiKey, getBaseUrl } from "./auth/credentials.js";
8
+ import { PostProxyClient } from "./api/client.js";
9
+ import { createMCPServer } from "./server.js";
10
+ import { log, logError } from "./utils/logger.js";
11
+ import { createError, ErrorCodes } from "./utils/errors.js";
12
+
13
+ async function main() {
14
+ // Check if setup command was called
15
+ if (process.argv[2] === "setup") {
16
+ const { setup } = await import("./setup.js");
17
+ await setup();
18
+ return;
19
+ }
20
+
21
+ try {
22
+ // Read environment variables
23
+ const apiKey = getApiKey();
24
+ const baseUrl = getBaseUrl();
25
+
26
+ if (!apiKey) {
27
+ logError(
28
+ createError(
29
+ ErrorCodes.AUTH_MISSING,
30
+ "POSTPROXY_API_KEY environment variable is not set"
31
+ ),
32
+ "startup"
33
+ );
34
+ process.exit(1);
35
+ }
36
+
37
+ // Create API client
38
+ const client = new PostProxyClient(apiKey, baseUrl);
39
+
40
+ // Create MCP server
41
+ const server = await createMCPServer(client);
42
+
43
+ // Setup stdio transport
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+
47
+ log("PostProxy MCP Server started");
48
+ } catch (error) {
49
+ logError(error as Error, "startup");
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ main().catch((error) => {
55
+ logError(error as Error, "main");
56
+ process.exit(1);
57
+ });
package/src/server.ts ADDED
@@ -0,0 +1,235 @@
1
+ /**
2
+ * MCP Server setup and tool registration
3
+ */
4
+
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import type { PostProxyClient } from "./api/client.js";
8
+ import { handleAuthStatus } from "./tools/auth.js";
9
+ import { handleProfilesList } from "./tools/profiles.js";
10
+ import {
11
+ handlePostPublish,
12
+ handlePostStatus,
13
+ handlePostDelete,
14
+ handlePostPublishDraft,
15
+ } from "./tools/post.js";
16
+ import { handleHistoryList } from "./tools/history.js";
17
+ import { createError, ErrorCodes } from "./utils/errors.js";
18
+ import { logToolCall } from "./utils/logger.js";
19
+
20
+ /**
21
+ * Tool definitions for the PostProxy MCP server.
22
+ * Exported for potential reuse in other contexts (e.g., Cloudflare Workers).
23
+ */
24
+ export const TOOL_DEFINITIONS = [
25
+ {
26
+ name: "auth.status",
27
+ description: "Check authentication status, API configuration, and workspace information",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {},
31
+ },
32
+ },
33
+ {
34
+ name: "profiles.list",
35
+ description: "List all available social media profiles (targets) for posting",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {},
39
+ },
40
+ },
41
+ {
42
+ name: "post.publish",
43
+ description: "Publish a post to specified social media targets. Supports text content, media attachments, scheduling, drafts, and platform-specific customization via the 'platforms' parameter.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {
47
+ content: {
48
+ type: "string",
49
+ description: "Post content text (caption/description)",
50
+ },
51
+ targets: {
52
+ type: "array",
53
+ items: { type: "string" },
54
+ description: "Array of target profile IDs to publish to",
55
+ },
56
+ schedule: {
57
+ type: "string",
58
+ description: "Optional ISO 8601 scheduled time (e.g., '2024-12-31T23:59:59Z')",
59
+ },
60
+ media: {
61
+ type: "array",
62
+ items: { type: "string" },
63
+ description: "Optional array of media URLs or local file paths (images or videos). File paths can be absolute (/path/to/file.jpg), relative (./image.png), or use ~ for home directory (~/Pictures/photo.jpg)",
64
+ },
65
+ idempotency_key: {
66
+ type: "string",
67
+ description: "Optional idempotency key for request deduplication",
68
+ },
69
+ require_confirmation: {
70
+ type: "boolean",
71
+ description: "If true, return summary without publishing (dry run)",
72
+ },
73
+ draft: {
74
+ type: "boolean",
75
+ description: "If true, creates a draft post that won't publish automatically",
76
+ },
77
+ platforms: {
78
+ type: "object",
79
+ description: "Platform-specific parameters. Keys are platform names, values are parameter objects. Use this to add collaborators, set video titles, privacy settings, etc.",
80
+ properties: {
81
+ instagram: {
82
+ type: "object",
83
+ description: "Instagram: format (post|reel|story), collaborators (array of usernames), first_comment (string), cover_url (string), audio_name (string), trial_strategy (MANUAL|SS_PERFORMANCE), thumb_offset (string in ms)",
84
+ additionalProperties: true,
85
+ },
86
+ youtube: {
87
+ type: "object",
88
+ description: "YouTube: title (string), privacy_status (public|unlisted|private), cover_url (thumbnail URL)",
89
+ additionalProperties: true,
90
+ },
91
+ tiktok: {
92
+ type: "object",
93
+ description: "TikTok: privacy_status (PUBLIC_TO_EVERYONE|MUTUAL_FOLLOW_FRIENDS|FOLLOWER_OF_CREATOR|SELF_ONLY), photo_cover_index (integer), auto_add_music (bool), made_with_ai (bool), disable_comment (bool), disable_duet (bool), disable_stitch (bool), brand_content_toggle (bool), brand_organic_toggle (bool)",
94
+ additionalProperties: true,
95
+ },
96
+ facebook: {
97
+ type: "object",
98
+ description: "Facebook: format (post|story), first_comment (string), page_id (string)",
99
+ additionalProperties: true,
100
+ },
101
+ linkedin: {
102
+ type: "object",
103
+ description: "LinkedIn: organization_id (string for company pages)",
104
+ additionalProperties: true,
105
+ },
106
+ twitter: {
107
+ type: "object",
108
+ description: "Twitter/X: No platform-specific parameters available",
109
+ additionalProperties: true,
110
+ },
111
+ threads: {
112
+ type: "object",
113
+ description: "Threads: No platform-specific parameters available",
114
+ additionalProperties: true,
115
+ },
116
+ },
117
+ additionalProperties: true,
118
+ },
119
+ },
120
+ required: ["content", "targets"],
121
+ },
122
+ },
123
+ {
124
+ name: "post.status",
125
+ description: "Get status of a published post by job ID",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: {
129
+ job_id: {
130
+ type: "string",
131
+ description: "Job ID from post.publish response",
132
+ },
133
+ },
134
+ required: ["job_id"],
135
+ },
136
+ },
137
+ {
138
+ name: "post.publish_draft",
139
+ description: "Publish a draft post. Only posts with draft status can be published using this endpoint",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ job_id: {
144
+ type: "string",
145
+ description: "Job ID of the draft post to publish",
146
+ },
147
+ },
148
+ required: ["job_id"],
149
+ },
150
+ },
151
+ {
152
+ name: "post.delete",
153
+ description: "Delete a post by job ID",
154
+ inputSchema: {
155
+ type: "object",
156
+ properties: {
157
+ job_id: {
158
+ type: "string",
159
+ description: "Job ID to delete",
160
+ },
161
+ },
162
+ required: ["job_id"],
163
+ },
164
+ },
165
+ {
166
+ name: "history.list",
167
+ description: "List recent post jobs",
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ limit: {
172
+ type: "number",
173
+ description: "Maximum number of jobs to return (default: 10)",
174
+ },
175
+ },
176
+ },
177
+ },
178
+ ] as const;
179
+
180
+ export async function createMCPServer(client: PostProxyClient): Promise<Server> {
181
+ const server = new Server(
182
+ {
183
+ name: "postproxy-mcp",
184
+ version: "0.1.0",
185
+ },
186
+ {
187
+ capabilities: {
188
+ tools: {},
189
+ },
190
+ }
191
+ );
192
+
193
+ // List available tools
194
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
195
+ return {
196
+ tools: [...TOOL_DEFINITIONS],
197
+ };
198
+ });
199
+
200
+ // Handle tool calls
201
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
202
+ const { name, arguments: args } = request.params;
203
+
204
+ logToolCall(name, args);
205
+
206
+ try {
207
+ switch (name) {
208
+ case "auth.status":
209
+ return await handleAuthStatus(client);
210
+ case "profiles.list":
211
+ return await handleProfilesList(client);
212
+ case "post.publish":
213
+ return await handlePostPublish(client, args as any);
214
+ case "post.status":
215
+ return await handlePostStatus(client, args as any);
216
+ case "post.publish_draft":
217
+ return await handlePostPublishDraft(client, args as any);
218
+ case "post.delete":
219
+ return await handlePostDelete(client, args as any);
220
+ case "history.list":
221
+ return await handleHistoryList(client, args as any);
222
+ default:
223
+ throw createError(ErrorCodes.API_ERROR, `Unknown tool: ${name}`);
224
+ }
225
+ } catch (error: any) {
226
+ if (error.code && error.message) {
227
+ // Already an MCPError
228
+ throw error;
229
+ }
230
+ throw createError(ErrorCodes.API_ERROR, error.message || "Tool execution failed");
231
+ }
232
+ });
233
+
234
+ return server;
235
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for setup script
4
+ */
5
+
6
+ import { setup } from "./setup.js";
7
+
8
+ setup().catch((error) => {
9
+ console.error(`\n❌ Fatal error: ${error}`);
10
+ process.exit(1);
11
+ });
package/src/setup.ts ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Interactive setup script for PostProxy MCP Server
4
+ * Makes it easy for non-technical users to configure Claude Code integration
5
+ */
6
+
7
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { homedir } from "os";
10
+ import * as readline from "readline";
11
+
12
+ interface ClaudeConfig {
13
+ mcpServers?: {
14
+ [key: string]: {
15
+ command: string;
16
+ env?: {
17
+ POSTPROXY_API_KEY?: string;
18
+ POSTPROXY_BASE_URL?: string;
19
+ };
20
+ };
21
+ };
22
+ }
23
+
24
+ function getConfigPath(): string {
25
+ const platform = process.platform;
26
+
27
+ if (platform === "win32") {
28
+ return join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
29
+ } else {
30
+ return join(homedir(), ".config", "claude", "claude_desktop_config.json");
31
+ }
32
+ }
33
+
34
+ function readConfig(): ClaudeConfig {
35
+ const configPath = getConfigPath();
36
+
37
+ if (!existsSync(configPath)) {
38
+ return {};
39
+ }
40
+
41
+ try {
42
+ const content = readFileSync(configPath, "utf-8");
43
+ return JSON.parse(content);
44
+ } catch (error) {
45
+ console.error(`⚠️ Error reading config file: ${error}`);
46
+ return {};
47
+ }
48
+ }
49
+
50
+ function writeConfig(config: ClaudeConfig): void {
51
+ const configPath = getConfigPath();
52
+ const configDir = dirname(configPath);
53
+
54
+ // Create directory if it doesn't exist
55
+ if (!existsSync(configDir)) {
56
+ mkdirSync(configDir, { recursive: true });
57
+ }
58
+
59
+ try {
60
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
61
+ console.log(`✅ Configuration saved to: ${configPath}`);
62
+ } catch (error) {
63
+ console.error(`❌ Error writing config file: ${error}`);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ function question(rl: readline.Interface, query: string): Promise<string> {
69
+ return new Promise((resolve) => {
70
+ rl.question(query, resolve);
71
+ });
72
+ }
73
+
74
+ function questionWithDefault(rl: readline.Interface, query: string, defaultValue: string): Promise<string> {
75
+ return new Promise((resolve) => {
76
+ rl.question(`${query} [${defaultValue}]: `, (answer) => {
77
+ resolve(answer.trim() || defaultValue);
78
+ });
79
+ });
80
+ }
81
+
82
+ async function main() {
83
+ console.log("\n🚀 PostProxy MCP Server Setup");
84
+ console.log("=" .repeat(50));
85
+ console.log("This script will help you configure Claude Code to use PostProxy MCP Server.\n");
86
+
87
+ const rl = readline.createInterface({
88
+ input: process.stdin,
89
+ output: process.stdout,
90
+ });
91
+
92
+ try {
93
+ // Get API key
94
+ console.log("📝 Step 1: PostProxy API Configuration");
95
+ console.log("-" .repeat(50));
96
+
97
+ const apiKey = await question(
98
+ rl,
99
+ "Enter your PostProxy API key: "
100
+ );
101
+
102
+ if (!apiKey || apiKey.trim().length === 0) {
103
+ console.error("❌ API key is required!");
104
+ process.exit(1);
105
+ }
106
+
107
+ const baseUrl = await questionWithDefault(
108
+ rl,
109
+ "Enter PostProxy API base URL",
110
+ "https://api.postproxy.dev/api"
111
+ );
112
+
113
+ // Determine command path
114
+ console.log("\n📦 Step 2: Installation Path");
115
+ console.log("-" .repeat(50));
116
+
117
+ console.log("\nHow did you install PostProxy MCP Server?");
118
+ console.log("1. Installed globally via npm (npm install -g postproxy-mcp)");
119
+ console.log("2. Using local development build");
120
+ console.log("3. Custom path");
121
+
122
+ const installType = await question(rl, "\nSelect option (1-3): ");
123
+
124
+ let command: string;
125
+
126
+ if (installType === "1") {
127
+ command = "postproxy-mcp";
128
+ } else if (installType === "2") {
129
+ // Try to detect current location
130
+ const currentDir = process.cwd();
131
+ const possiblePath = join(currentDir, "dist", "index.js");
132
+
133
+ if (existsSync(possiblePath)) {
134
+ command = possiblePath;
135
+ console.log(`✅ Detected local build at: ${command}`);
136
+ } else {
137
+ const customPath = await question(
138
+ rl,
139
+ "Enter path to dist/index.js: "
140
+ );
141
+ command = customPath;
142
+ }
143
+ } else {
144
+ command = await question(rl, "Enter full path to postproxy-mcp command: ");
145
+ }
146
+
147
+ // Read existing config
148
+ const config = readConfig();
149
+
150
+ // Update config
151
+ if (!config.mcpServers) {
152
+ config.mcpServers = {};
153
+ }
154
+
155
+ config.mcpServers.postproxy = {
156
+ command: command,
157
+ env: {
158
+ POSTPROXY_API_KEY: apiKey.trim(),
159
+ POSTPROXY_BASE_URL: baseUrl.trim(),
160
+ },
161
+ };
162
+
163
+ // Write config
164
+ console.log("\n💾 Step 3: Saving Configuration");
165
+ console.log("-" .repeat(50));
166
+
167
+ writeConfig(config);
168
+
169
+ // Final instructions
170
+ console.log("\n✨ Setup Complete!");
171
+ console.log("=" .repeat(50));
172
+ console.log("\n📋 Next steps:");
173
+ console.log("1. Restart your Claude Code session");
174
+ console.log("2. Test the connection by asking Claude: 'Check my PostProxy authentication status'");
175
+ console.log("3. If configured correctly, Claude will automatically use PostProxy tools");
176
+ console.log("\n💡 Tip: You can run this setup again anytime with: postproxy-mcp setup");
177
+ console.log("");
178
+
179
+ } catch (error) {
180
+ console.error(`\n❌ Setup failed: ${error}`);
181
+ process.exit(1);
182
+ } finally {
183
+ rl.close();
184
+ }
185
+ }
186
+
187
+ export { main as setup };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Authentication tools: auth.status
3
+ */
4
+
5
+ import type { PostProxyClient } from "../api/client.js";
6
+ import { getApiKey, getBaseUrl } from "../auth/credentials.js";
7
+ import { createError, ErrorCodes } from "../utils/errors.js";
8
+ import { logError, logToolCall } from "../utils/logger.js";
9
+
10
+ export async function handleAuthStatus(client: PostProxyClient) {
11
+ logToolCall("auth.status", {});
12
+
13
+ const apiKey = getApiKey();
14
+ const baseUrl = getBaseUrl();
15
+
16
+ const result: {
17
+ authenticated: boolean;
18
+ base_url: string;
19
+ profile_groups_count?: number;
20
+ } = {
21
+ authenticated: apiKey !== null,
22
+ base_url: baseUrl,
23
+ };
24
+
25
+ // If authenticated, try to get profile groups count
26
+ if (apiKey !== null) {
27
+ try {
28
+ const profileGroups = await client.getProfileGroups();
29
+ result.profile_groups_count = profileGroups.length;
30
+ } catch (error) {
31
+ // If we can't get profile groups, just return without the count
32
+ // Don't fail the whole request
33
+ logError(error as Error, "auth.status");
34
+ }
35
+ }
36
+
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: JSON.stringify(result, null, 2),
42
+ },
43
+ ],
44
+ };
45
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * History tools: history.list
3
+ */
4
+
5
+ import type { PostProxyClient } from "../api/client.js";
6
+ import { createError, ErrorCodes } from "../utils/errors.js";
7
+ import { logError } from "../utils/logger.js";
8
+
9
+ export async function handleHistoryList(
10
+ client: PostProxyClient,
11
+ args: { limit?: number }
12
+ ) {
13
+ const limit = args.limit || 10;
14
+
15
+ try {
16
+ const posts = await client.listPosts(limit);
17
+
18
+ const jobs = posts.map((post) => {
19
+ // Get content from either "body" or "content" field (API uses "body")
20
+ const content = post.body || post.content || "";
21
+
22
+ // Determine overall status from post status
23
+ let overallStatus = "unknown";
24
+
25
+ // Handle draft status first
26
+ if (post.status === "draft" || post.draft === true) {
27
+ overallStatus = "draft";
28
+ } else if (post.status === "scheduled") {
29
+ overallStatus = "pending";
30
+ } else if (post.status === "processing") {
31
+ overallStatus = "processing";
32
+ } else if (post.status === "processed") {
33
+ // Check platform statuses to determine overall status
34
+ if (post.platforms && post.platforms.length > 0) {
35
+ const allPublished = post.platforms.every((p) => p.status === "published");
36
+ const allFailed = post.platforms.every((p) => p.status === "failed");
37
+ const anyPending = post.platforms.some((p) => p.status === "pending" || p.status === "processing");
38
+
39
+ if (anyPending) {
40
+ // Only if there are pending/processing platforms - this is truly processing
41
+ overallStatus = "processing";
42
+ } else if (allPublished) {
43
+ overallStatus = "complete";
44
+ } else if (allFailed) {
45
+ overallStatus = "failed";
46
+ } else {
47
+ // Mixed statuses (some published, some failed) - processing is complete
48
+ // Use "complete" since processing is finished, details are in platforms
49
+ overallStatus = "complete";
50
+ }
51
+ } else {
52
+ overallStatus = "pending";
53
+ }
54
+ } else if (post.status === "pending") {
55
+ overallStatus = "pending";
56
+ }
57
+
58
+ return {
59
+ job_id: post.id,
60
+ content_preview: content.substring(0, 100) + (content.length > 100 ? "..." : ""),
61
+ created_at: post.created_at,
62
+ overall_status: overallStatus,
63
+ draft: post.draft || false,
64
+ platforms_count: post.platforms?.length || 0,
65
+ };
66
+ });
67
+
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: JSON.stringify(
73
+ {
74
+ jobs,
75
+ },
76
+ null,
77
+ 2
78
+ ),
79
+ },
80
+ ],
81
+ };
82
+ } catch (error) {
83
+ logError(error as Error, "history.list");
84
+ throw createError(
85
+ ErrorCodes.API_ERROR,
86
+ `Failed to list history: ${(error as Error).message}`
87
+ );
88
+ }
89
+ }