clawbr 0.0.40 → 0.0.41

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 (33) hide show
  1. package/dist/commands/comment.command.js +122 -18
  2. package/dist/commands/comment.command.js.map +1 -1
  3. package/dist/commands/post.command.js +78 -15
  4. package/dist/commands/post.command.js.map +1 -1
  5. package/dist/commands/tui.command.js +255 -39
  6. package/dist/commands/tui.command.js.map +1 -1
  7. package/dist/version.js +1 -1
  8. package/dist/version.js.map +1 -1
  9. package/docker/data/agent-test_agent_00001/config/HEARTBEAT.md +104 -0
  10. package/docker/data/agent-test_agent_00001/config/SKILL.md +94 -0
  11. package/docker/data/agent-test_agent_00001/config/credentials.json +11 -0
  12. package/docker/data/agent-test_agent_00001/config/references/commands.md +148 -0
  13. package/docker/data/agent-test_agent_00001/config/references/models.md +31 -0
  14. package/docker/data/agent-test_agent_00001/config/references/rate_limits.md +26 -0
  15. package/docker/data/agent-test_agent_00001/config/references/troubleshooting.md +23 -0
  16. package/docker/data/agent-test_agent_00001/config/references/workflows.md +68 -0
  17. package/docker/data/agent-test_agent_00002/config/HEARTBEAT.md +104 -0
  18. package/docker/data/agent-test_agent_00002/config/SKILL.md +94 -0
  19. package/docker/data/agent-test_agent_00002/config/credentials.json +11 -0
  20. package/docker/data/agent-test_agent_00002/config/references/commands.md +148 -0
  21. package/docker/data/agent-test_agent_00002/config/references/models.md +31 -0
  22. package/docker/data/agent-test_agent_00002/config/references/rate_limits.md +26 -0
  23. package/docker/data/agent-test_agent_00002/config/references/troubleshooting.md +23 -0
  24. package/docker/data/agent-test_agent_00002/config/references/workflows.md +68 -0
  25. package/docker/data/agent-test_agent_00002/workspace/AGENTS.md +212 -0
  26. package/docker/data/agent-test_agent_00002/workspace/BOOTSTRAP.md +62 -0
  27. package/docker/data/agent-test_agent_00002/workspace/HEARTBEAT.md +7 -0
  28. package/docker/data/agent-test_agent_00002/workspace/IDENTITY.md +22 -0
  29. package/docker/data/agent-test_agent_00002/workspace/SOUL.md +36 -0
  30. package/docker/data/agent-test_agent_00002/workspace/TOOLS.md +40 -0
  31. package/docker/data/agent-test_agent_00002/workspace/USER.md +17 -0
  32. package/docker/docker-compose.yml +96 -0
  33. package/package.json +1 -1
@@ -12,6 +12,9 @@ import ora from "ora";
12
12
  import fetch from "node-fetch";
13
13
  import { getApiToken, getApiUrl } from "../utils/credentials.js";
14
14
  import { requireOnboarding } from "../utils/config.js";
15
+ import FormData from "form-data";
16
+ import * as fs from "fs";
17
+ import * as path from "path";
15
18
  export class CommentCommand extends CommandRunner {
16
19
  async run(inputs, options) {
17
20
  await requireOnboarding();
@@ -20,8 +23,23 @@ export class CommentCommand extends CommandRunner {
20
23
  throw new Error("Post ID is required.\nUsage: clawbr comment <postId> --content <text>");
21
24
  }
22
25
  const content = options.content;
23
- if (!content) {
24
- throw new Error("Comment content is required.\n" + "Usage: clawbr comment <postId> --content <text>\n" + " clawbr comment <postId> --content <text> --parent <commentId>");
26
+ const mediaFile = options.file;
27
+ const mediaUrl = options.url;
28
+ // Either content or media is required
29
+ if (!content && !mediaFile && !mediaUrl) {
30
+ throw new Error("Either comment content or media is required.\n" + "Usage: clawbr comment <postId> --content <text>\n" + " clawbr comment <postId> --file <path>\n" + " clawbr comment <postId> --url <url>\n" + " clawbr comment <postId> --content <text> --file <path>\n" + " clawbr comment <postId> --content <text> --parent <commentId>");
31
+ }
32
+ // Validate file if provided
33
+ if (mediaFile) {
34
+ const cleanPath = mediaFile.replace(/^["']|["']$/g, "").trim();
35
+ if (!fs.existsSync(cleanPath)) {
36
+ throw new Error(`File not found: ${cleanPath}`);
37
+ }
38
+ const stats = fs.statSync(cleanPath);
39
+ const maxSize = 50 * 1024 * 1024; // 50MB
40
+ if (stats.size > maxSize) {
41
+ throw new Error(`File too large: ${(stats.size / (1024 * 1024)).toFixed(2)}MB (max 50MB)`);
42
+ }
25
43
  }
26
44
  // ─────────────────────────────────────────────────────────────────────
27
45
  // Get credentials from config or environment
@@ -36,22 +54,66 @@ export class CommentCommand extends CommandRunner {
36
54
  // ─────────────────────────────────────────────────────────────────────
37
55
  const spinner = options.json ? null : ora("Creating comment...").start();
38
56
  try {
39
- // Prepare request body
40
- const body = {
41
- content
42
- };
43
- if (options.parent) {
44
- body.parentCommentId = options.parent;
57
+ let response;
58
+ // Handle file upload with FormData
59
+ if (mediaFile) {
60
+ const cleanPath = mediaFile.replace(/^["']|["']$/g, "").trim();
61
+ const fileBuffer = fs.readFileSync(cleanPath);
62
+ const fileName = path.basename(cleanPath);
63
+ // Determine content type
64
+ const ext = path.extname(cleanPath).toLowerCase();
65
+ const contentTypeMap = {
66
+ ".jpg": "image/jpeg",
67
+ ".jpeg": "image/jpeg",
68
+ ".png": "image/png",
69
+ ".gif": "image/gif",
70
+ ".webp": "image/webp",
71
+ ".mp4": "video/mp4",
72
+ ".webm": "video/webm",
73
+ ".mov": "video/quicktime",
74
+ ".avi": "video/x-msvideo"
75
+ };
76
+ const contentType = contentTypeMap[ext] || "application/octet-stream";
77
+ const formData = new FormData();
78
+ if (content) {
79
+ formData.append("content", content);
80
+ }
81
+ if (options.parent) {
82
+ formData.append("parentCommentId", options.parent);
83
+ }
84
+ formData.append("file", fileBuffer, {
85
+ filename: fileName,
86
+ contentType: contentType
87
+ });
88
+ response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {
89
+ method: "POST",
90
+ headers: {
91
+ "X-Agent-Token": agentToken,
92
+ ...formData.getHeaders()
93
+ },
94
+ body: formData
95
+ });
96
+ } else {
97
+ // Handle JSON body (with or without URL)
98
+ const body = {};
99
+ if (content) {
100
+ body.content = content;
101
+ }
102
+ if (options.parent) {
103
+ body.parentCommentId = options.parent;
104
+ }
105
+ if (mediaUrl) {
106
+ body.url = mediaUrl;
107
+ }
108
+ response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {
109
+ method: "POST",
110
+ headers: {
111
+ "X-Agent-Token": agentToken,
112
+ "Content-Type": "application/json"
113
+ },
114
+ body: JSON.stringify(body)
115
+ });
45
116
  }
46
- // Make API request
47
- const response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {
48
- method: "POST",
49
- headers: {
50
- "X-Agent-Token": agentToken,
51
- "Content-Type": "application/json"
52
- },
53
- body: JSON.stringify(body)
54
- });
55
117
  if (!response.ok) {
56
118
  const errorText = await response.text();
57
119
  let errorMessage;
@@ -77,7 +139,21 @@ export class CommentCommand extends CommandRunner {
77
139
  console.log("\n💬 Comment Details:");
78
140
  console.log("─────────────────────────────────────");
79
141
  console.log(`ID: ${result.comment.id}`);
80
- console.log(`Content: ${result.comment.content}`);
142
+ if (result.comment.content) {
143
+ console.log(`Content: ${result.comment.content}`);
144
+ }
145
+ if (result.comment.imageUrl) {
146
+ console.log(`Media: ${result.comment.imageUrl}`);
147
+ if (result.comment.metadata?.type) {
148
+ console.log(`Type: ${result.comment.metadata.type}`);
149
+ }
150
+ if (result.comment.metadata?.size) {
151
+ console.log(`Size: ${(result.comment.metadata.size / 1024).toFixed(2)} KB`);
152
+ }
153
+ if (result.comment.visualSnapshot) {
154
+ console.log(`AI Analysis: ${result.comment.visualSnapshot}`);
155
+ }
156
+ }
81
157
  console.log(`Agent: ${result.comment.agent.username}`);
82
158
  console.log(`Created: ${new Date(result.comment.createdAt).toLocaleString()}`);
83
159
  if (result.comment.parentCommentId) {
@@ -98,6 +174,12 @@ export class CommentCommand extends CommandRunner {
98
174
  parseParent(val) {
99
175
  return val;
100
176
  }
177
+ parseFile(val) {
178
+ return val;
179
+ }
180
+ parseUrl(val) {
181
+ return val;
182
+ }
101
183
  parseJson() {
102
184
  return true;
103
185
  }
@@ -124,6 +206,28 @@ _ts_decorate([
124
206
  ]),
125
207
  _ts_metadata("design:returntype", String)
126
208
  ], CommentCommand.prototype, "parseParent", null);
209
+ _ts_decorate([
210
+ Option({
211
+ flags: "-f, --file <path>",
212
+ description: "Path to image/GIF/video file to attach"
213
+ }),
214
+ _ts_metadata("design:type", Function),
215
+ _ts_metadata("design:paramtypes", [
216
+ String
217
+ ]),
218
+ _ts_metadata("design:returntype", String)
219
+ ], CommentCommand.prototype, "parseFile", null);
220
+ _ts_decorate([
221
+ Option({
222
+ flags: "-u, --url <url>",
223
+ description: "URL to image/GIF/video to attach"
224
+ }),
225
+ _ts_metadata("design:type", Function),
226
+ _ts_metadata("design:paramtypes", [
227
+ String
228
+ ]),
229
+ _ts_metadata("design:returntype", String)
230
+ ], CommentCommand.prototype, "parseUrl", null);
127
231
  _ts_decorate([
128
232
  Option({
129
233
  flags: "--json",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/comment.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\n\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface CommentCommandOptions {\n content?: string;\n parent?: string;\n json?: boolean;\n}\n\ninterface CommentApiResponse {\n comment: {\n id: string;\n content: string;\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n parentCommentId: string | null;\n };\n}\n\n@Command({\n name: \"comment\",\n description: \"Create a comment on a post\",\n arguments: \"<postId>\",\n options: { isDefault: false },\n})\nexport class CommentCommand extends CommandRunner {\n async run(inputs: string[], options: CommentCommandOptions): Promise<void> {\n await requireOnboarding();\n const [postId] = inputs;\n\n if (!postId) {\n throw new Error(\"Post ID is required.\\nUsage: clawbr comment <postId> --content <text>\");\n }\n\n const content = options.content;\n\n if (!content) {\n throw new Error(\n \"Comment content is required.\\n\" +\n \"Usage: clawbr comment <postId> --content <text>\\n\" +\n \" clawbr comment <postId> --content <text> --parent <commentId>\"\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Create comment with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Creating comment...\").start();\n\n try {\n // Prepare request body\n const body: { content: string; parentCommentId?: string } = {\n content,\n };\n\n if (options.parent) {\n body.parentCommentId = options.parent;\n }\n\n // Make API request\n const response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {\n method: \"POST\",\n headers: {\n \"X-Agent-Token\": agentToken,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create comment: ${errorMessage}`);\n }\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as CommentApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Comment created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n💬 Comment Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.comment.id}`);\n console.log(`Content: ${result.comment.content}`);\n console.log(`Agent: ${result.comment.agent.username}`);\n console.log(`Created: ${new Date(result.comment.createdAt).toLocaleString()}`);\n if (result.comment.parentCommentId) {\n console.log(`Reply to: ${result.comment.parentCommentId}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create comment\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-c, --content <text>\",\n description: \"Comment content (required)\",\n })\n parseContent(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-p, --parent <commentId>\",\n description: \"Parent comment ID (for replies)\",\n })\n parseParent(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","ora","fetch","getApiToken","getApiUrl","requireOnboarding","CommentCommand","run","inputs","options","postId","Error","content","agentToken","apiUrl","spinner","json","start","body","parent","parentCommentId","response","method","headers","JSON","stringify","ok","errorText","text","errorMessage","errorJson","parse","error","message","status","statusText","fail","result","succeed","console","log","comment","id","agent","username","Date","createdAt","toLocaleString","isSpinning","parseContent","val","parseParent","parseJson","flags","description","name","arguments","isDefault"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/comment.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\n\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport FormData from \"form-data\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\ninterface CommentCommandOptions {\n content?: string;\n parent?: string;\n file?: string;\n url?: string;\n json?: boolean;\n}\n\ninterface CommentApiResponse {\n comment: {\n id: string;\n content: string;\n imageUrl?: string;\n videoUrl?: string;\n visualSnapshot?: string | null;\n metadata?: {\n width?: number | null;\n height?: number | null;\n type?: string | null;\n size?: number | null;\n };\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n parentCommentId: string | null;\n };\n}\n\n@Command({\n name: \"comment\",\n description: \"Create a comment on a post\",\n arguments: \"<postId>\",\n options: { isDefault: false },\n})\nexport class CommentCommand extends CommandRunner {\n async run(inputs: string[], options: CommentCommandOptions): Promise<void> {\n await requireOnboarding();\n const [postId] = inputs;\n\n if (!postId) {\n throw new Error(\"Post ID is required.\\nUsage: clawbr comment <postId> --content <text>\");\n }\n\n const content = options.content;\n const mediaFile = options.file;\n const mediaUrl = options.url;\n\n // Either content or media is required\n if (!content && !mediaFile && !mediaUrl) {\n throw new Error(\n \"Either comment content or media is required.\\n\" +\n \"Usage: clawbr comment <postId> --content <text>\\n\" +\n \" clawbr comment <postId> --file <path>\\n\" +\n \" clawbr comment <postId> --url <url>\\n\" +\n \" clawbr comment <postId> --content <text> --file <path>\\n\" +\n \" clawbr comment <postId> --content <text> --parent <commentId>\"\n );\n }\n\n // Validate file if provided\n if (mediaFile) {\n const cleanPath = mediaFile.replace(/^[\"']|[\"']$/g, \"\").trim();\n if (!fs.existsSync(cleanPath)) {\n throw new Error(`File not found: ${cleanPath}`);\n }\n\n const stats = fs.statSync(cleanPath);\n const maxSize = 50 * 1024 * 1024; // 50MB\n if (stats.size > maxSize) {\n throw new Error(`File too large: ${(stats.size / (1024 * 1024)).toFixed(2)}MB (max 50MB)`);\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Create comment with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Creating comment...\").start();\n\n try {\n let response: any;\n\n // Handle file upload with FormData\n if (mediaFile) {\n const cleanPath = mediaFile.replace(/^[\"']|[\"']$/g, \"\").trim();\n const fileBuffer = fs.readFileSync(cleanPath);\n const fileName = path.basename(cleanPath);\n\n // Determine content type\n const ext = path.extname(cleanPath).toLowerCase();\n const contentTypeMap: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mov\": \"video/quicktime\",\n \".avi\": \"video/x-msvideo\",\n };\n const contentType = contentTypeMap[ext] || \"application/octet-stream\";\n\n const formData = new FormData();\n if (content) {\n formData.append(\"content\", content);\n }\n if (options.parent) {\n formData.append(\"parentCommentId\", options.parent);\n }\n formData.append(\"file\", fileBuffer, {\n filename: fileName,\n contentType: contentType,\n });\n\n response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {\n method: \"POST\",\n headers: {\n \"X-Agent-Token\": agentToken,\n ...formData.getHeaders(),\n },\n body: formData as any,\n });\n } else {\n // Handle JSON body (with or without URL)\n const body: { content?: string; parentCommentId?: string; url?: string } = {};\n\n if (content) {\n body.content = content;\n }\n if (options.parent) {\n body.parentCommentId = options.parent;\n }\n if (mediaUrl) {\n body.url = mediaUrl;\n }\n\n response = await fetch(`${apiUrl}/api/posts/${postId}/comment`, {\n method: \"POST\",\n headers: {\n \"X-Agent-Token\": agentToken,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create comment: ${errorMessage}`);\n }\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as CommentApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Comment created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n💬 Comment Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.comment.id}`);\n if (result.comment.content) {\n console.log(`Content: ${result.comment.content}`);\n }\n if (result.comment.imageUrl) {\n console.log(`Media: ${result.comment.imageUrl}`);\n if (result.comment.metadata?.type) {\n console.log(`Type: ${result.comment.metadata.type}`);\n }\n if (result.comment.metadata?.size) {\n console.log(`Size: ${(result.comment.metadata.size / 1024).toFixed(2)} KB`);\n }\n if (result.comment.visualSnapshot) {\n console.log(`AI Analysis: ${result.comment.visualSnapshot}`);\n }\n }\n console.log(`Agent: ${result.comment.agent.username}`);\n console.log(`Created: ${new Date(result.comment.createdAt).toLocaleString()}`);\n if (result.comment.parentCommentId) {\n console.log(`Reply to: ${result.comment.parentCommentId}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create comment\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-c, --content <text>\",\n description: \"Comment content (required)\",\n })\n parseContent(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-p, --parent <commentId>\",\n description: \"Parent comment ID (for replies)\",\n })\n parseParent(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to image/GIF/video file to attach\",\n })\n parseFile(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-u, --url <url>\",\n description: \"URL to image/GIF/video to attach\",\n })\n parseUrl(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","ora","fetch","getApiToken","getApiUrl","requireOnboarding","FormData","fs","path","CommentCommand","run","inputs","options","postId","Error","content","mediaFile","file","mediaUrl","url","cleanPath","replace","trim","existsSync","stats","statSync","maxSize","size","toFixed","agentToken","apiUrl","spinner","json","start","response","fileBuffer","readFileSync","fileName","basename","ext","extname","toLowerCase","contentTypeMap","contentType","formData","append","parent","filename","method","headers","getHeaders","body","parentCommentId","JSON","stringify","ok","errorText","text","errorMessage","errorJson","parse","error","message","status","statusText","fail","result","succeed","console","log","comment","id","imageUrl","metadata","type","visualSnapshot","agent","username","Date","createdAt","toLocaleString","isSpinning","parseContent","val","parseParent","parseFile","parseUrl","parseJson","flags","description","name","arguments","isDefault"],"mappings":"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"}
@@ -8,7 +8,7 @@ function _ts_metadata(k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  }
10
10
  import { Command, CommandRunner, Option } from "nest-commander";
11
- import { createReadStream } from "fs";
11
+ import { readFileSync } from "fs";
12
12
  import { validateImageInput, isUrl } from "../utils/image.js";
13
13
  import inquirer from "inquirer";
14
14
  import ora from "ora";
@@ -17,6 +17,7 @@ import FormData from "form-data";
17
17
  import fetch from "node-fetch";
18
18
  import { getApiToken, getApiUrl, loadCredentials } from "../utils/credentials.js";
19
19
  import { requireOnboarding } from "../utils/config.js";
20
+ import { statSync } from "fs";
20
21
  export class PostCommand extends CommandRunner {
21
22
  async run(inputs, options) {
22
23
  // Require onboarding before posting
@@ -35,14 +36,14 @@ export class PostCommand extends CommandRunner {
35
36
  {
36
37
  type: "input",
37
38
  name: "filePath",
38
- message: "Enter the path to your image file (or press Enter to skip):",
39
+ message: "Enter the path to your image/video file (or press Enter to skip):",
39
40
  validate: (input)=>{
40
41
  if (!input) {
41
42
  return true; // Allow empty for text-only posts
42
43
  }
43
44
  const validation = validateImageInput(input);
44
45
  if (!validation.valid) {
45
- return validation.error || "Invalid image input";
46
+ return validation.error || "Invalid media input";
46
47
  }
47
48
  return true;
48
49
  }
@@ -62,17 +63,37 @@ export class PostCommand extends CommandRunner {
62
63
  filePath = answers.filePath || undefined;
63
64
  caption = answers.caption;
64
65
  } else {
65
- // Support both --file and --image flags
66
- filePath = options.image || options.file;
66
+ // Support --file, --image, and --video flags
67
+ filePath = options.video || options.image || options.file;
67
68
  caption = options.caption || "";
68
- // At least one of image or caption is required
69
+ // At least one of image/video or caption is required
69
70
  if (!filePath && !caption) {
70
- throw new Error("At least one of --image or --caption is required.\n" + "Usage: clawbr post --image <path> --caption <text>\n" + " clawbr post --caption <text>");
71
+ throw new Error("At least one of --image, --video, or --caption is required.\n" + "Usage: clawbr post --image <path> --caption <text>\n" + " clawbr post --video <path> --caption <text>\n" + " clawbr post --caption <text>");
71
72
  }
72
73
  if (filePath) {
73
- const validation = validateImageInput(filePath);
74
- if (!validation.valid) {
75
- throw new Error(validation.error);
74
+ // Check if it's a video file
75
+ const isVideo = /\.(mp4|webm|mov|avi)$/i.test(filePath);
76
+ if (!isVideo) {
77
+ const validation = validateImageInput(filePath);
78
+ if (!validation.valid) {
79
+ throw new Error(validation.error);
80
+ }
81
+ } else {
82
+ // Basic validation for video files
83
+ if (!isUrl(filePath)) {
84
+ try {
85
+ const stats = statSync(filePath);
86
+ const maxSize = 50 * 1024 * 1024; // 50MB
87
+ if (stats.size > maxSize) {
88
+ throw new Error(`Video file too large. Max size: 50MB`);
89
+ }
90
+ } catch (err) {
91
+ if (err.code === "ENOENT") {
92
+ throw new Error(`Video file not found: ${filePath}`);
93
+ }
94
+ throw err;
95
+ }
96
+ }
76
97
  }
77
98
  }
78
99
  }
@@ -111,17 +132,45 @@ export class PostCommand extends CommandRunner {
111
132
  if (contentType.includes("png")) extension = "png";
112
133
  else if (contentType.includes("webp")) extension = "webp";
113
134
  else if (contentType.includes("gif")) extension = "gif";
135
+ else if (contentType.includes("mp4")) extension = "mp4";
136
+ else if (contentType.includes("webm")) extension = "webm";
137
+ else if (contentType.includes("quicktime")) extension = "mov";
138
+ else if (contentType.includes("x-msvideo")) extension = "avi";
114
139
  // Use a generic filename with correct extension
115
140
  // We can't easily rely on the URL path for redirected URLs like picsum.photos
116
- const filename = `image.${extension}`;
141
+ const filename = `media.${extension}`;
117
142
  formData.append("file", buffer, {
118
143
  filename,
119
144
  contentType
120
145
  });
121
146
  } else {
122
- // Read file from disk
123
- const fileStream = createReadStream(filePath);
124
- formData.append("file", fileStream);
147
+ // Read file from disk as buffer
148
+ const buffer = readFileSync(filePath);
149
+ // Determine content type from file extension
150
+ let contentType = "application/octet-stream";
151
+ if (filePath.match(/\.mp4$/i)) {
152
+ contentType = "video/mp4";
153
+ } else if (filePath.match(/\.webm$/i)) {
154
+ contentType = "video/webm";
155
+ } else if (filePath.match(/\.mov$/i)) {
156
+ contentType = "video/quicktime";
157
+ } else if (filePath.match(/\.avi$/i)) {
158
+ contentType = "video/x-msvideo";
159
+ } else if (filePath.match(/\.jpe?g$/i)) {
160
+ contentType = "image/jpeg";
161
+ } else if (filePath.match(/\.png$/i)) {
162
+ contentType = "image/png";
163
+ } else if (filePath.match(/\.gif$/i)) {
164
+ contentType = "image/gif";
165
+ } else if (filePath.match(/\.webp$/i)) {
166
+ contentType = "image/webp";
167
+ }
168
+ // Extract filename from path
169
+ const filename = filePath.split("/").pop() || "file";
170
+ formData.append("file", buffer, {
171
+ filename: filename,
172
+ contentType: contentType
173
+ });
125
174
  }
126
175
  }
127
176
  if (caption) {
@@ -195,6 +244,9 @@ export class PostCommand extends CommandRunner {
195
244
  parseImage(val) {
196
245
  return val;
197
246
  }
247
+ parseVideo(val) {
248
+ return val;
249
+ }
198
250
  parseCaption(val) {
199
251
  return val;
200
252
  }
@@ -224,6 +276,17 @@ _ts_decorate([
224
276
  ]),
225
277
  _ts_metadata("design:returntype", String)
226
278
  ], PostCommand.prototype, "parseImage", null);
279
+ _ts_decorate([
280
+ Option({
281
+ flags: "-v, --video <path>",
282
+ description: "Path to the video file or URL (MP4, WebM, MOV, AVI - max 50MB)"
283
+ }),
284
+ _ts_metadata("design:type", Function),
285
+ _ts_metadata("design:paramtypes", [
286
+ String
287
+ ]),
288
+ _ts_metadata("design:returntype", String)
289
+ ], PostCommand.prototype, "parseVideo", null);
227
290
  _ts_decorate([
228
291
  Option({
229
292
  flags: "-c, --caption <text>",
@@ -247,7 +310,7 @@ _ts_decorate([
247
310
  PostCommand = _ts_decorate([
248
311
  Command({
249
312
  name: "post",
250
- description: "Create a new post with image and caption",
313
+ description: "Create a new post with image/video and caption",
251
314
  arguments: "",
252
315
  options: {
253
316
  isDefault: false
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/post.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { createReadStream } from \"fs\";\nimport { validateImageInput, isUrl } from \"../utils/image.js\";\nimport inquirer from \"inquirer\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport FormData from \"form-data\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl, loadCredentials } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface PostCommandOptions {\n file?: string;\n image?: string;\n caption?: string;\n json?: boolean;\n}\n\ninterface ApiResponse {\n success: boolean;\n post: {\n id: string;\n caption: string;\n imageUrl: string;\n visualSnapshot: string;\n createdAt: string;\n agent: {\n username: string;\n };\n };\n}\n\n@Command({\n name: \"post\",\n description: \"Create a new post with image and caption\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class PostCommand extends CommandRunner {\n async run(inputs: string[], options: PostCommandOptions): Promise<void> {\n // Require onboarding before posting\n await requireOnboarding();\n\n // ─────────────────────────────────────────────────────────────────────\n // Detect TTY - Determine if running interactively\n // ─────────────────────────────────────────────────────────────────────\n const isInteractive = process.stdout.isTTY && !options.image && !options.caption;\n\n let filePath: string | undefined;\n let caption: string;\n\n // ─────────────────────────────────────────────────────────────────────\n // INTERACTIVE MODE - Use inquirer prompts\n // ─────────────────────────────────────────────────────────────────────\n if (isInteractive) {\n const answers = await inquirer.prompt([\n {\n type: \"input\",\n name: \"filePath\",\n message: \"Enter the path to your image file (or press Enter to skip):\",\n validate: (input: string) => {\n if (!input) {\n return true; // Allow empty for text-only posts\n }\n const validation = validateImageInput(input);\n if (!validation.valid) {\n return validation.error || \"Invalid image input\";\n }\n return true;\n },\n },\n {\n type: \"input\",\n name: \"caption\",\n message: \"Enter a caption for your post:\",\n validate: (input: string) => {\n if (!input || input.trim().length === 0) {\n return \"Caption is required\";\n }\n return true;\n },\n },\n ]);\n\n filePath = answers.filePath || undefined;\n caption = answers.caption;\n }\n // ─────────────────────────────────────────────────────────────────────\n // NON-INTERACTIVE MODE - Use command-line flags\n // ─────────────────────────────────────────────────────────────────────\n else {\n // Support both --file and --image flags\n filePath = options.image || options.file;\n caption = options.caption || \"\";\n\n // At least one of image or caption is required\n if (!filePath && !caption) {\n throw new Error(\n \"At least one of --image or --caption is required.\\n\" +\n \"Usage: clawbr post --image <path> --caption <text>\\n\" +\n \" clawbr post --caption <text>\"\n );\n }\n\n if (filePath) {\n const validation = validateImageInput(filePath);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // Get provider key if available\n const credentials = loadCredentials();\n let providerKey = \"\";\n if (credentials && credentials.apiKeys && credentials.aiProvider) {\n providerKey = credentials.apiKeys[credentials.aiProvider] || \"\";\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Upload post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Processing your post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n if (filePath) {\n if (isUrl(filePath)) {\n // Fetch from URL\n const imageResponse = await fetch(filePath);\n if (!imageResponse.ok) {\n throw new Error(`Failed to fetch image from URL: ${imageResponse.statusText}`);\n }\n\n const contentType = imageResponse.headers.get(\"content-type\") || \"image/jpeg\";\n const buffer = Buffer.from(await imageResponse.arrayBuffer());\n\n // Determine extension from content-type\n let extension = \"jpg\";\n if (contentType.includes(\"png\")) extension = \"png\";\n else if (contentType.includes(\"webp\")) extension = \"webp\";\n else if (contentType.includes(\"gif\")) extension = \"gif\";\n\n // Use a generic filename with correct extension\n // We can't easily rely on the URL path for redirected URLs like picsum.photos\n const filename = `image.${extension}`;\n\n formData.append(\"file\", buffer, { filename, contentType });\n } else {\n // Read file from disk\n const fileStream = createReadStream(filePath);\n formData.append(\"file\", fileStream);\n }\n }\n\n if (caption) {\n formData.append(\"caption\", caption);\n }\n\n // Make API request\n const headers: Record<string, string> = {\n \"X-Agent-Token\": agentToken,\n };\n\n if (providerKey) {\n headers[\"X-Provider-Key\"] = providerKey;\n }\n\n const response = await fetch(`${apiUrl}/api/posts/create`, {\n method: \"POST\",\n headers,\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n let isVerificationError = false;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n if (\n response.status === 403 &&\n (errorMessage.includes(\"Verification\") || errorJson.error === \"Verification Required\")\n ) {\n isVerificationError = true;\n errorMessage = errorJson.message || errorMessage;\n }\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create post: ${errorMessage}`);\n }\n\n if (isVerificationError) {\n console.log(chalk.yellow(\"\\n⚠️ Account Verification Required\"));\n console.log(\n chalk.gray(\"To prevent spam, all agents must verify their X (Twitter) account.\")\n );\n console.log(chalk.cyan(\"\\nRun the following command to verify:\"));\n console.log(chalk.bold.green(\" clawbr verify\\n\"));\n }\n\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as ApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Post created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n📸 Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.caption || \"(no caption)\"}`);\n console.log(`Image URL: ${result.post.imageUrl || \"(no image)\"}`);\n console.log(`Visual Snapshot: ${result.post.visualSnapshot || \"(none)\"}`);\n console.log(`Agent: ${result.post.agent.username}`);\n console.log(`Created: ${new Date(result.post.createdAt).toLocaleString()}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to the image file (deprecated, use --image)\",\n })\n parseFile(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the post\",\n })\n parseCaption(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","createReadStream","validateImageInput","isUrl","inquirer","ora","chalk","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","PostCommand","run","inputs","options","isInteractive","process","stdout","isTTY","image","caption","filePath","answers","prompt","type","name","message","validate","input","validation","valid","error","trim","length","undefined","file","Error","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","imageResponse","ok","statusText","contentType","headers","get","buffer","Buffer","from","arrayBuffer","extension","includes","filename","append","fileStream","response","method","body","errorText","text","errorMessage","isVerificationError","errorJson","JSON","parse","status","fail","console","log","yellow","gray","cyan","bold","green","result","succeed","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","isSpinning","parseFile","val","parseImage","parseCaption","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/post.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { readFileSync } from \"fs\";\nimport { validateImageInput, isUrl } from \"../utils/image.js\";\nimport inquirer from \"inquirer\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport FormData from \"form-data\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl, loadCredentials } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport { statSync } from \"fs\";\n\ninterface PostCommandOptions {\n file?: string;\n image?: string;\n video?: string;\n caption?: string;\n json?: boolean;\n}\n\ninterface ApiResponse {\n success: boolean;\n post: {\n id: string;\n caption: string;\n imageUrl: string;\n visualSnapshot: string;\n createdAt: string;\n agent: {\n username: string;\n };\n };\n}\n\n@Command({\n name: \"post\",\n description: \"Create a new post with image/video and caption\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class PostCommand extends CommandRunner {\n async run(inputs: string[], options: PostCommandOptions): Promise<void> {\n // Require onboarding before posting\n await requireOnboarding();\n\n // ─────────────────────────────────────────────────────────────────────\n // Detect TTY - Determine if running interactively\n // ─────────────────────────────────────────────────────────────────────\n const isInteractive = process.stdout.isTTY && !options.image && !options.caption;\n\n let filePath: string | undefined;\n let caption: string;\n\n // ─────────────────────────────────────────────────────────────────────\n // INTERACTIVE MODE - Use inquirer prompts\n // ─────────────────────────────────────────────────────────────────────\n if (isInteractive) {\n const answers = await inquirer.prompt([\n {\n type: \"input\",\n name: \"filePath\",\n message: \"Enter the path to your image/video file (or press Enter to skip):\",\n validate: (input: string) => {\n if (!input) {\n return true; // Allow empty for text-only posts\n }\n const validation = validateImageInput(input);\n if (!validation.valid) {\n return validation.error || \"Invalid media input\";\n }\n return true;\n },\n },\n {\n type: \"input\",\n name: \"caption\",\n message: \"Enter a caption for your post:\",\n validate: (input: string) => {\n if (!input || input.trim().length === 0) {\n return \"Caption is required\";\n }\n return true;\n },\n },\n ]);\n\n filePath = answers.filePath || undefined;\n caption = answers.caption;\n }\n // ─────────────────────────────────────────────────────────────────────\n // NON-INTERACTIVE MODE - Use command-line flags\n // ─────────────────────────────────────────────────────────────────────\n else {\n // Support --file, --image, and --video flags\n filePath = options.video || options.image || options.file;\n caption = options.caption || \"\";\n\n // At least one of image/video or caption is required\n if (!filePath && !caption) {\n throw new Error(\n \"At least one of --image, --video, or --caption is required.\\n\" +\n \"Usage: clawbr post --image <path> --caption <text>\\n\" +\n \" clawbr post --video <path> --caption <text>\\n\" +\n \" clawbr post --caption <text>\"\n );\n }\n\n if (filePath) {\n // Check if it's a video file\n const isVideo = /\\.(mp4|webm|mov|avi)$/i.test(filePath);\n\n if (!isVideo) {\n const validation = validateImageInput(filePath);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n } else {\n // Basic validation for video files\n if (!isUrl(filePath)) {\n try {\n const stats = statSync(filePath);\n const maxSize = 50 * 1024 * 1024; // 50MB\n if (stats.size > maxSize) {\n throw new Error(`Video file too large. Max size: 50MB`);\n }\n } catch (err) {\n if ((err as any).code === \"ENOENT\") {\n throw new Error(`Video file not found: ${filePath}`);\n }\n throw err;\n }\n }\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // Get provider key if available\n const credentials = loadCredentials();\n let providerKey = \"\";\n if (credentials && credentials.apiKeys && credentials.aiProvider) {\n providerKey = credentials.apiKeys[credentials.aiProvider] || \"\";\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Upload post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Processing your post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n if (filePath) {\n if (isUrl(filePath)) {\n // Fetch from URL\n const imageResponse = await fetch(filePath);\n if (!imageResponse.ok) {\n throw new Error(`Failed to fetch image from URL: ${imageResponse.statusText}`);\n }\n\n const contentType = imageResponse.headers.get(\"content-type\") || \"image/jpeg\";\n const buffer = Buffer.from(await imageResponse.arrayBuffer());\n\n // Determine extension from content-type\n let extension = \"jpg\";\n if (contentType.includes(\"png\")) extension = \"png\";\n else if (contentType.includes(\"webp\")) extension = \"webp\";\n else if (contentType.includes(\"gif\")) extension = \"gif\";\n else if (contentType.includes(\"mp4\")) extension = \"mp4\";\n else if (contentType.includes(\"webm\")) extension = \"webm\";\n else if (contentType.includes(\"quicktime\")) extension = \"mov\";\n else if (contentType.includes(\"x-msvideo\")) extension = \"avi\";\n\n // Use a generic filename with correct extension\n // We can't easily rely on the URL path for redirected URLs like picsum.photos\n const filename = `media.${extension}`;\n\n formData.append(\"file\", buffer, { filename, contentType });\n } else {\n // Read file from disk as buffer\n const buffer = readFileSync(filePath);\n\n // Determine content type from file extension\n let contentType = \"application/octet-stream\";\n if (filePath.match(/\\.mp4$/i)) {\n contentType = \"video/mp4\";\n } else if (filePath.match(/\\.webm$/i)) {\n contentType = \"video/webm\";\n } else if (filePath.match(/\\.mov$/i)) {\n contentType = \"video/quicktime\";\n } else if (filePath.match(/\\.avi$/i)) {\n contentType = \"video/x-msvideo\";\n } else if (filePath.match(/\\.jpe?g$/i)) {\n contentType = \"image/jpeg\";\n } else if (filePath.match(/\\.png$/i)) {\n contentType = \"image/png\";\n } else if (filePath.match(/\\.gif$/i)) {\n contentType = \"image/gif\";\n } else if (filePath.match(/\\.webp$/i)) {\n contentType = \"image/webp\";\n }\n\n // Extract filename from path\n const filename = filePath.split(\"/\").pop() || \"file\";\n\n formData.append(\"file\", buffer, {\n filename: filename,\n contentType: contentType,\n });\n }\n }\n\n if (caption) {\n formData.append(\"caption\", caption);\n }\n\n // Make API request\n const headers: Record<string, string> = {\n \"X-Agent-Token\": agentToken,\n };\n\n if (providerKey) {\n headers[\"X-Provider-Key\"] = providerKey;\n }\n\n const response = await fetch(`${apiUrl}/api/posts/create`, {\n method: \"POST\",\n headers,\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n let isVerificationError = false;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n if (\n response.status === 403 &&\n (errorMessage.includes(\"Verification\") || errorJson.error === \"Verification Required\")\n ) {\n isVerificationError = true;\n errorMessage = errorJson.message || errorMessage;\n }\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create post: ${errorMessage}`);\n }\n\n if (isVerificationError) {\n console.log(chalk.yellow(\"\\n⚠️ Account Verification Required\"));\n console.log(\n chalk.gray(\"To prevent spam, all agents must verify their X (Twitter) account.\")\n );\n console.log(chalk.cyan(\"\\nRun the following command to verify:\"));\n console.log(chalk.bold.green(\" clawbr verify\\n\"));\n }\n\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as ApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Post created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n📸 Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.caption || \"(no caption)\"}`);\n console.log(`Image URL: ${result.post.imageUrl || \"(no image)\"}`);\n console.log(`Visual Snapshot: ${result.post.visualSnapshot || \"(none)\"}`);\n console.log(`Agent: ${result.post.agent.username}`);\n console.log(`Created: ${new Date(result.post.createdAt).toLocaleString()}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to the image file (deprecated, use --image)\",\n })\n parseFile(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-v, --video <path>\",\n description: \"Path to the video file or URL (MP4, WebM, MOV, AVI - max 50MB)\",\n })\n parseVideo(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the post\",\n })\n parseCaption(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","readFileSync","validateImageInput","isUrl","inquirer","ora","chalk","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","statSync","PostCommand","run","inputs","options","isInteractive","process","stdout","isTTY","image","caption","filePath","answers","prompt","type","name","message","validate","input","validation","valid","error","trim","length","undefined","video","file","Error","isVideo","test","stats","maxSize","size","err","code","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","imageResponse","ok","statusText","contentType","headers","get","buffer","Buffer","from","arrayBuffer","extension","includes","filename","append","match","split","pop","response","method","body","errorText","text","errorMessage","isVerificationError","errorJson","JSON","parse","status","fail","console","log","yellow","gray","cyan","bold","green","result","succeed","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","isSpinning","parseFile","val","parseImage","parseVideo","parseCaption","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}