clawbr 0.0.43 → 0.0.45

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.
@@ -169,7 +169,7 @@ export class CommentCommand extends CommandRunner {
169
169
  }
170
170
  }
171
171
  parseContent(val) {
172
- return val;
172
+ return val.replace(/\\n/g, "\n");
173
173
  }
174
174
  parseParent(val) {
175
175
  return val;
@@ -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\";\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":";;;;;;;;;AAAA,SAASA,OAAO,EAAEC,aAAa,EAAEC,MAAM,QAAQ,iBAAiB;AAEhE,OAAOC,SAAS,MAAM;AACtB,OAAOC,WAAW,aAAa;AAC/B,SAASC,WAAW,EAAEC,SAAS,QAAQ,0BAA0B;AACjE,SAASC,iBAAiB,QAAQ,qBAAqB;AACvD,OAAOC,cAAc,YAAY;AACjC,YAAYC,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAsC7B,OAAO,MAAMC,uBAAuBV;IAClC,MAAMW,IAAIC,MAAgB,EAAEC,OAA8B,EAAiB;QACzE,MAAMP;QACN,MAAM,CAACQ,OAAO,GAAGF;QAEjB,IAAI,CAACE,QAAQ;YACX,MAAM,IAAIC,MAAM;QAClB;QAEA,MAAMC,UAAUH,QAAQG,OAAO;QAC/B,MAAMC,YAAYJ,QAAQK,IAAI;QAC9B,MAAMC,WAAWN,QAAQO,GAAG;QAE5B,sCAAsC;QACtC,IAAI,CAACJ,WAAW,CAACC,aAAa,CAACE,UAAU;YACvC,MAAM,IAAIJ,MACR,mDACE,sDACA,mDACA,iDACA,oEACA;QAEN;QAEA,4BAA4B;QAC5B,IAAIE,WAAW;YACb,MAAMI,YAAYJ,UAAUK,OAAO,CAAC,gBAAgB,IAAIC,IAAI;YAC5D,IAAI,CAACf,GAAGgB,UAAU,CAACH,YAAY;gBAC7B,MAAM,IAAIN,MAAM,CAAC,gBAAgB,EAAEM,WAAW;YAChD;YAEA,MAAMI,QAAQjB,GAAGkB,QAAQ,CAACL;YAC1B,MAAMM,UAAU,KAAK,OAAO,MAAM,OAAO;YACzC,IAAIF,MAAMG,IAAI,GAAGD,SAAS;gBACxB,MAAM,IAAIZ,MAAM,CAAC,gBAAgB,EAAE,AAACU,CAAAA,MAAMG,IAAI,GAAI,CAAA,OAAO,IAAG,CAAC,EAAGC,OAAO,CAAC,GAAG,aAAa,CAAC;YAC3F;QACF;QAEA,wEAAwE;QACxE,6CAA6C;QAC7C,wEAAwE;QACxE,MAAMC,aAAa1B;QACnB,MAAM2B,SAAS1B;QAEf,IAAI,CAACyB,YAAY;YACf,MAAM,IAAIf,MACR,kEACE;QAEN;QAEA,wEAAwE;QACxE,2CAA2C;QAC3C,wEAAwE;QACxE,MAAMiB,UAAUnB,QAAQoB,IAAI,GAAG,OAAO/B,IAAI,uBAAuBgC,KAAK;QAEtE,IAAI;YACF,IAAIC;YAEJ,mCAAmC;YACnC,IAAIlB,WAAW;gBACb,MAAMI,YAAYJ,UAAUK,OAAO,CAAC,gBAAgB,IAAIC,IAAI;gBAC5D,MAAMa,aAAa5B,GAAG6B,YAAY,CAAChB;gBACnC,MAAMiB,WAAW7B,KAAK8B,QAAQ,CAAClB;gBAE/B,yBAAyB;gBACzB,MAAMmB,MAAM/B,KAAKgC,OAAO,CAACpB,WAAWqB,WAAW;gBAC/C,MAAMC,iBAAyC;oBAC7C,QAAQ;oBACR,SAAS;oBACT,QAAQ;oBACR,QAAQ;oBACR,SAAS;oBACT,QAAQ;oBACR,SAAS;oBACT,QAAQ;oBACR,QAAQ;gBACV;gBACA,MAAMC,cAAcD,cAAc,CAACH,IAAI,IAAI;gBAE3C,MAAMK,WAAW,IAAItC;gBACrB,IAAIS,SAAS;oBACX6B,SAASC,MAAM,CAAC,WAAW9B;gBAC7B;gBACA,IAAIH,QAAQkC,MAAM,EAAE;oBAClBF,SAASC,MAAM,CAAC,mBAAmBjC,QAAQkC,MAAM;gBACnD;gBACAF,SAASC,MAAM,CAAC,QAAQV,YAAY;oBAClCY,UAAUV;oBACVM,aAAaA;gBACf;gBAEAT,WAAW,MAAMhC,MAAM,GAAG4B,OAAO,WAAW,EAAEjB,OAAO,QAAQ,CAAC,EAAE;oBAC9DmC,QAAQ;oBACRC,SAAS;wBACP,iBAAiBpB;wBACjB,GAAGe,SAASM,UAAU,EAAE;oBAC1B;oBACAC,MAAMP;gBACR;YACF,OAAO;gBACL,yCAAyC;gBACzC,MAAMO,OAAqE,CAAC;gBAE5E,IAAIpC,SAAS;oBACXoC,KAAKpC,OAAO,GAAGA;gBACjB;gBACA,IAAIH,QAAQkC,MAAM,EAAE;oBAClBK,KAAKC,eAAe,GAAGxC,QAAQkC,MAAM;gBACvC;gBACA,IAAI5B,UAAU;oBACZiC,KAAKhC,GAAG,GAAGD;gBACb;gBAEAgB,WAAW,MAAMhC,MAAM,GAAG4B,OAAO,WAAW,EAAEjB,OAAO,QAAQ,CAAC,EAAE;oBAC9DmC,QAAQ;oBACRC,SAAS;wBACP,iBAAiBpB;wBACjB,gBAAgB;oBAClB;oBACAsB,MAAME,KAAKC,SAAS,CAACH;gBACvB;YACF;YAEA,IAAI,CAACjB,SAASqB,EAAE,EAAE;gBAChB,MAAMC,YAAY,MAAMtB,SAASuB,IAAI;gBACrC,IAAIC;gBAEJ,IAAI;oBACF,MAAMC,YAAYN,KAAKO,KAAK,CAACJ;oBAC7BE,eAAeC,UAAUE,KAAK,IAAIF,UAAUG,OAAO,IAAI;gBACzD,EAAE,OAAM;oBACNJ,eAAeF,aAAa,CAAC,KAAK,EAAEtB,SAAS6B,MAAM,CAAC,CAAC,EAAE7B,SAAS8B,UAAU,EAAE;gBAC9E;gBAEA,IAAIjC,SAAS;oBACXA,QAAQkC,IAAI,CAAC,CAAC,0BAA0B,EAAEP,cAAc;gBAC1D;gBACA,MAAM,IAAI5C,MAAM4C;YAClB;YAEA,MAAMQ,SAAU,MAAMhC,SAASF,IAAI;YAEnC,IAAID,SAAS;gBACXA,QAAQoC,OAAO,CAAC;YAClB;YAEA,iBAAiB;YACjB,IAAIvD,QAAQoB,IAAI,EAAE;gBAChBoC,QAAQC,GAAG,CAAChB,KAAKC,SAAS,CAACY,QAAQ,MAAM;YAC3C,OAAO;gBACLE,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEH,OAAOI,OAAO,CAACC,EAAE,EAAE;gBACtC,IAAIL,OAAOI,OAAO,CAACvD,OAAO,EAAE;oBAC1BqD,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAEH,OAAOI,OAAO,CAACvD,OAAO,EAAE;gBAClD;gBACA,IAAImD,OAAOI,OAAO,CAACE,QAAQ,EAAE;oBAC3BJ,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEH,OAAOI,OAAO,CAACE,QAAQ,EAAE;oBAC/C,IAAIN,OAAOI,OAAO,CAACG,QAAQ,EAAEC,MAAM;wBACjCN,QAAQC,GAAG,CAAC,CAAC,MAAM,EAAEH,OAAOI,OAAO,CAACG,QAAQ,CAACC,IAAI,EAAE;oBACrD;oBACA,IAAIR,OAAOI,OAAO,CAACG,QAAQ,EAAE9C,MAAM;wBACjCyC,QAAQC,GAAG,CAAC,CAAC,MAAM,EAAE,AAACH,CAAAA,OAAOI,OAAO,CAACG,QAAQ,CAAC9C,IAAI,GAAG,IAAG,EAAGC,OAAO,CAAC,GAAG,GAAG,CAAC;oBAC5E;oBACA,IAAIsC,OAAOI,OAAO,CAACK,cAAc,EAAE;wBACjCP,QAAQC,GAAG,CAAC,CAAC,aAAa,EAAEH,OAAOI,OAAO,CAACK,cAAc,EAAE;oBAC7D;gBACF;gBACAP,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEH,OAAOI,OAAO,CAACM,KAAK,CAACC,QAAQ,EAAE;gBACrDT,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAE,IAAIS,KAAKZ,OAAOI,OAAO,CAACS,SAAS,EAAEC,cAAc,IAAI;gBAC7E,IAAId,OAAOI,OAAO,CAAClB,eAAe,EAAE;oBAClCgB,QAAQC,GAAG,CAAC,CAAC,UAAU,EAAEH,OAAOI,OAAO,CAAClB,eAAe,EAAE;gBAC3D;gBACAgB,QAAQC,GAAG,CAAC;YACd;QACF,EAAE,OAAOR,OAAO;YACd,IAAI9B,WAAWA,QAAQkD,UAAU,EAAE;gBACjClD,QAAQkC,IAAI,CAAC;YACf;YACA,MAAMJ;QACR;IACF;IAMAqB,aAAaC,GAAW,EAAU;QAChC,OAAOA;IACT;IAMAC,YAAYD,GAAW,EAAU;QAC/B,OAAOA;IACT;IAMAE,UAAUF,GAAW,EAAU;QAC7B,OAAOA;IACT;IAMAG,SAASH,GAAW,EAAU;QAC5B,OAAOA;IACT;IAMAI,YAAqB;QACnB,OAAO;IACT;AACF;;;QAtCIC,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;QAhOfC,MAAM;QACND,aAAa;QACbE,WAAW;QACX/E,SAAS;YAAEgF,WAAW;QAAM"}
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.replace(/\\\\n/g, \"\\n\");\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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"}
@@ -9,7 +9,7 @@ function _ts_metadata(k, v) {
9
9
  }
10
10
  import { Command, CommandRunner, Option } from "nest-commander";
11
11
  import { readFileSync } from "fs";
12
- import { validateImageInput, isUrl } from "../utils/image.js";
12
+ import { validateImageInput, isUrl, getMimeTypeFromExtension, detectMimeTypeFromBuffer, normalizeMimeType } from "../utils/image.js";
13
13
  import inquirer from "inquirer";
14
14
  import ora from "ora";
15
15
  import chalk from "chalk";
@@ -125,19 +125,34 @@ export class PostCommand extends CommandRunner {
125
125
  if (!imageResponse.ok) {
126
126
  throw new Error(`Failed to fetch image from URL: ${imageResponse.statusText}`);
127
127
  }
128
- const contentType = imageResponse.headers.get("content-type") || "image/jpeg";
129
128
  const buffer = Buffer.from(await imageResponse.arrayBuffer());
130
- // Determine extension from content-type
131
- let extension = "jpg";
132
- if (contentType.includes("png")) extension = "png";
133
- else if (contentType.includes("webp")) extension = "webp";
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";
139
- // Use a generic filename with correct extension
140
- // We can't easily rely on the URL path for redirected URLs like picsum.photos
129
+ // Prefer magic-byte detection for the MIME type; normalise + fall back
130
+ // to the Content-Type header so that non-standard aliases like
131
+ // 'image/jpg' or 'image/jpeg; charset=binary' don't break the upload.
132
+ const { fileTypeFromBuffer } = await import("file-type");
133
+ const detected = await fileTypeFromBuffer(buffer);
134
+ let contentType;
135
+ if (detected) {
136
+ contentType = normalizeMimeType(detected.mime);
137
+ } else {
138
+ const headerCt = imageResponse.headers.get("content-type") || "image/jpeg";
139
+ contentType = normalizeMimeType(headerCt);
140
+ }
141
+ // Derive a sane extension from the resolved content type
142
+ const ctToExt = {
143
+ "image/jpeg": "jpg",
144
+ "image/png": "png",
145
+ "image/webp": "webp",
146
+ "image/gif": "gif",
147
+ "image/avif": "avif",
148
+ "image/bmp": "bmp",
149
+ "image/tiff": "tiff",
150
+ "video/mp4": "mp4",
151
+ "video/webm": "webm",
152
+ "video/quicktime": "mov",
153
+ "video/x-msvideo": "avi"
154
+ };
155
+ const extension = ctToExt[contentType] ?? "bin";
141
156
  const filename = `media.${extension}`;
142
157
  formData.append("file", buffer, {
143
158
  filename,
@@ -146,26 +161,13 @@ export class PostCommand extends CommandRunner {
146
161
  } else {
147
162
  // Read file from disk as buffer
148
163
  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
164
+ // Use magic-byte detection for the most reliable MIME type.
165
+ // Fall back to extension-based lookup if file-type can't identify it.
166
+ const detectedMime = await detectMimeTypeFromBuffer(buffer);
167
+ let contentType = detectedMime ?? getMimeTypeFromExtension(filePath);
168
+ // Extra safety: also handle any aliased MIME from the extension lookup
169
+ contentType = normalizeMimeType(contentType);
170
+ // Extract filename from path, preserving the original extension
169
171
  const filename = filePath.split("/").pop() || "file";
170
172
  formData.append("file", buffer, {
171
173
  filename: filename,
@@ -248,7 +250,7 @@ export class PostCommand extends CommandRunner {
248
250
  return val;
249
251
  }
250
252
  parseCaption(val) {
251
- return val;
253
+ return val.replace(/\\n/g, "\n");
252
254
  }
253
255
  parseJson() {
254
256
  return true;
@@ -1 +1 @@
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":";;;;;;;;;AAAA,SAASA,OAAO,EAAEC,aAAa,EAAEC,MAAM,QAAQ,iBAAiB;AAChE,SAASC,YAAY,QAAQ,KAAK;AAClC,SAASC,kBAAkB,EAAEC,KAAK,QAAQ,oBAAoB;AAC9D,OAAOC,cAAc,WAAW;AAChC,OAAOC,SAAS,MAAM;AACtB,OAAOC,WAAW,QAAQ;AAC1B,OAAOC,cAAc,YAAY;AACjC,OAAOC,WAAW,aAAa;AAC/B,SAASC,WAAW,EAAEC,SAAS,EAAEC,eAAe,QAAQ,0BAA0B;AAClF,SAASC,iBAAiB,QAAQ,qBAAqB;AACvD,SAASC,QAAQ,QAAQ,KAAK;AA8B9B,OAAO,MAAMC,oBAAoBf;IAC/B,MAAMgB,IAAIC,MAAgB,EAAEC,OAA2B,EAAiB;QACtE,oCAAoC;QACpC,MAAML;QAEN,wEAAwE;QACxE,kDAAkD;QAClD,wEAAwE;QACxE,MAAMM,gBAAgBC,QAAQC,MAAM,CAACC,KAAK,IAAI,CAACJ,QAAQK,KAAK,IAAI,CAACL,QAAQM,OAAO;QAEhF,IAAIC;QACJ,IAAID;QAEJ,wEAAwE;QACxE,0CAA0C;QAC1C,wEAAwE;QACxE,IAAIL,eAAe;YACjB,MAAMO,UAAU,MAAMrB,SAASsB,MAAM,CAAC;gBACpC;oBACEC,MAAM;oBACNC,MAAM;oBACNC,SAAS;oBACTC,UAAU,CAACC;wBACT,IAAI,CAACA,OAAO;4BACV,OAAO,MAAM,kCAAkC;wBACjD;wBACA,MAAMC,aAAa9B,mBAAmB6B;wBACtC,IAAI,CAACC,WAAWC,KAAK,EAAE;4BACrB,OAAOD,WAAWE,KAAK,IAAI;wBAC7B;wBACA,OAAO;oBACT;gBACF;gBACA;oBACEP,MAAM;oBACNC,MAAM;oBACNC,SAAS;oBACTC,UAAU,CAACC;wBACT,IAAI,CAACA,SAASA,MAAMI,IAAI,GAAGC,MAAM,KAAK,GAAG;4BACvC,OAAO;wBACT;wBACA,OAAO;oBACT;gBACF;aACD;YAEDZ,WAAWC,QAAQD,QAAQ,IAAIa;YAC/Bd,UAAUE,QAAQF,OAAO;QAC3B,OAIK;YACH,6CAA6C;YAC7CC,WAAWP,QAAQqB,KAAK,IAAIrB,QAAQK,KAAK,IAAIL,QAAQsB,IAAI;YACzDhB,UAAUN,QAAQM,OAAO,IAAI;YAE7B,qDAAqD;YACrD,IAAI,CAACC,YAAY,CAACD,SAAS;gBACzB,MAAM,IAAIiB,MACR,kEACE,yDACA,yDACA;YAEN;YAEA,IAAIhB,UAAU;gBACZ,6BAA6B;gBAC7B,MAAMiB,UAAU,yBAAyBC,IAAI,CAAClB;gBAE9C,IAAI,CAACiB,SAAS;oBACZ,MAAMT,aAAa9B,mBAAmBsB;oBACtC,IAAI,CAACQ,WAAWC,KAAK,EAAE;wBACrB,MAAM,IAAIO,MAAMR,WAAWE,KAAK;oBAClC;gBACF,OAAO;oBACL,mCAAmC;oBACnC,IAAI,CAAC/B,MAAMqB,WAAW;wBACpB,IAAI;4BACF,MAAMmB,QAAQ9B,SAASW;4BACvB,MAAMoB,UAAU,KAAK,OAAO,MAAM,OAAO;4BACzC,IAAID,MAAME,IAAI,GAAGD,SAAS;gCACxB,MAAM,IAAIJ,MAAM,CAAC,oCAAoC,CAAC;4BACxD;wBACF,EAAE,OAAOM,KAAK;4BACZ,IAAI,AAACA,IAAYC,IAAI,KAAK,UAAU;gCAClC,MAAM,IAAIP,MAAM,CAAC,sBAAsB,EAAEhB,UAAU;4BACrD;4BACA,MAAMsB;wBACR;oBACF;gBACF;YACF;QACF;QAEA,wEAAwE;QACxE,6CAA6C;QAC7C,wEAAwE;QACxE,MAAME,aAAavC;QACnB,MAAMwC,SAASvC;QAEf,IAAI,CAACsC,YAAY;YACf,MAAM,IAAIR,MACR,kEACE;QAEN;QAEA,gCAAgC;QAChC,MAAMU,cAAcvC;QACpB,IAAIwC,cAAc;QAClB,IAAID,eAAeA,YAAYE,OAAO,IAAIF,YAAYG,UAAU,EAAE;YAChEF,cAAcD,YAAYE,OAAO,CAACF,YAAYG,UAAU,CAAC,IAAI;QAC/D;QAEA,wEAAwE;QACxE,wCAAwC;QACxC,wEAAwE;QACxE,MAAMC,UAAUrC,QAAQsC,IAAI,GAAG,OAAOlD,IAAI,2BAA2BmD,KAAK;QAE1E,IAAI;YACF,kBAAkB;YAClB,MAAMC,WAAW,IAAIlD;YAErB,IAAIiB,UAAU;gBACZ,IAAIrB,MAAMqB,WAAW;oBACnB,iBAAiB;oBACjB,MAAMkC,gBAAgB,MAAMlD,MAAMgB;oBAClC,IAAI,CAACkC,cAAcC,EAAE,EAAE;wBACrB,MAAM,IAAInB,MAAM,CAAC,gCAAgC,EAAEkB,cAAcE,UAAU,EAAE;oBAC/E;oBAEA,MAAMC,cAAcH,cAAcI,OAAO,CAACC,GAAG,CAAC,mBAAmB;oBACjE,MAAMC,SAASC,OAAOC,IAAI,CAAC,MAAMR,cAAcS,WAAW;oBAE1D,wCAAwC;oBACxC,IAAIC,YAAY;oBAChB,IAAIP,YAAYQ,QAAQ,CAAC,QAAQD,YAAY;yBACxC,IAAIP,YAAYQ,QAAQ,CAAC,SAASD,YAAY;yBAC9C,IAAIP,YAAYQ,QAAQ,CAAC,QAAQD,YAAY;yBAC7C,IAAIP,YAAYQ,QAAQ,CAAC,QAAQD,YAAY;yBAC7C,IAAIP,YAAYQ,QAAQ,CAAC,SAASD,YAAY;yBAC9C,IAAIP,YAAYQ,QAAQ,CAAC,cAAcD,YAAY;yBACnD,IAAIP,YAAYQ,QAAQ,CAAC,cAAcD,YAAY;oBAExD,gDAAgD;oBAChD,8EAA8E;oBAC9E,MAAME,WAAW,CAAC,MAAM,EAAEF,WAAW;oBAErCX,SAASc,MAAM,CAAC,QAAQP,QAAQ;wBAAEM;wBAAUT;oBAAY;gBAC1D,OAAO;oBACL,gCAAgC;oBAChC,MAAMG,SAAS/D,aAAauB;oBAE5B,6CAA6C;oBAC7C,IAAIqC,cAAc;oBAClB,IAAIrC,SAASgD,KAAK,CAAC,YAAY;wBAC7BX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,aAAa;wBACrCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,YAAY;wBACpCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,YAAY;wBACpCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,cAAc;wBACtCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,YAAY;wBACpCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,YAAY;wBACpCX,cAAc;oBAChB,OAAO,IAAIrC,SAASgD,KAAK,CAAC,aAAa;wBACrCX,cAAc;oBAChB;oBAEA,6BAA6B;oBAC7B,MAAMS,WAAW9C,SAASiD,KAAK,CAAC,KAAKC,GAAG,MAAM;oBAE9CjB,SAASc,MAAM,CAAC,QAAQP,QAAQ;wBAC9BM,UAAUA;wBACVT,aAAaA;oBACf;gBACF;YACF;YAEA,IAAItC,SAAS;gBACXkC,SAASc,MAAM,CAAC,WAAWhD;YAC7B;YAEA,mBAAmB;YACnB,MAAMuC,UAAkC;gBACtC,iBAAiBd;YACnB;YAEA,IAAIG,aAAa;gBACfW,OAAO,CAAC,iBAAiB,GAAGX;YAC9B;YAEA,MAAMwB,WAAW,MAAMnE,MAAM,GAAGyC,OAAO,iBAAiB,CAAC,EAAE;gBACzD2B,QAAQ;gBACRd;gBACAe,MAAMpB;YACR;YAEA,IAAI,CAACkB,SAAShB,EAAE,EAAE;gBAChB,MAAMmB,YAAY,MAAMH,SAASI,IAAI;gBACrC,IAAIC;gBACJ,IAAIC,sBAAsB;gBAE1B,IAAI;oBACF,MAAMC,YAAYC,KAAKC,KAAK,CAACN;oBAC7BE,eAAeE,UAAUhD,KAAK,IAAIgD,UAAUrD,OAAO,IAAI;oBACvD,IACE8C,SAASU,MAAM,KAAK,OACnBL,CAAAA,aAAaX,QAAQ,CAAC,mBAAmBa,UAAUhD,KAAK,KAAK,uBAAsB,GACpF;wBACA+C,sBAAsB;wBACtBD,eAAeE,UAAUrD,OAAO,IAAImD;oBACtC;gBACF,EAAE,OAAM;oBACNA,eAAeF,aAAa,CAAC,KAAK,EAAEH,SAASU,MAAM,CAAC,CAAC,EAAEV,SAASf,UAAU,EAAE;gBAC9E;gBAEA,IAAIN,SAAS;oBACXA,QAAQgC,IAAI,CAAC,CAAC,uBAAuB,EAAEN,cAAc;gBACvD;gBAEA,IAAIC,qBAAqB;oBACvBM,QAAQC,GAAG,CAAClF,MAAMmF,MAAM,CAAC;oBACzBF,QAAQC,GAAG,CACTlF,MAAMoF,IAAI,CAAC;oBAEbH,QAAQC,GAAG,CAAClF,MAAMqF,IAAI,CAAC;oBACvBJ,QAAQC,GAAG,CAAClF,MAAMsF,IAAI,CAACC,KAAK,CAAC;gBAC/B;gBAEA,MAAM,IAAIrD,MAAMwC;YAClB;YAEA,MAAMc,SAAU,MAAMnB,SAASpB,IAAI;YAEnC,IAAID,SAAS;gBACXA,QAAQyC,OAAO,CAAC;YAClB;YAEA,iBAAiB;YACjB,IAAI9E,QAAQsC,IAAI,EAAE;gBAChBgC,QAAQC,GAAG,CAACL,KAAKa,SAAS,CAACF,QAAQ,MAAM;YAC3C,OAAO;gBACLP,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEM,OAAOG,IAAI,CAACC,EAAE,EAAE;gBACnCX,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAEM,OAAOG,IAAI,CAAC1E,OAAO,IAAI,gBAAgB;gBAC/DgE,QAAQC,GAAG,CAAC,CAAC,WAAW,EAAEM,OAAOG,IAAI,CAACE,QAAQ,IAAI,cAAc;gBAChEZ,QAAQC,GAAG,CAAC,CAAC,iBAAiB,EAAEM,OAAOG,IAAI,CAACG,cAAc,IAAI,UAAU;gBACxEb,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEM,OAAOG,IAAI,CAACI,KAAK,CAACC,QAAQ,EAAE;gBAClDf,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAE,IAAIe,KAAKT,OAAOG,IAAI,CAACO,SAAS,EAAEC,cAAc,IAAI;gBAC1ElB,QAAQC,GAAG,CAAC;YACd;QACF,EAAE,OAAOtD,OAAO;YACd,IAAIoB,WAAWA,QAAQoD,UAAU,EAAE;gBACjCpD,QAAQgC,IAAI,CAAC;YACf;YACA,MAAMpD;QACR;IACF;IAMAyE,UAAUC,GAAW,EAAU;QAC7B,OAAOA;IACT;IAMAC,WAAWD,GAAW,EAAU;QAC9B,OAAOA;IACT;IAMAE,WAAWF,GAAW,EAAU;QAC9B,OAAOA;IACT;IAMAG,aAAaH,GAAW,EAAU;QAChC,OAAOA;IACT;IAMAI,YAAqB;QACnB,OAAO;IACT;AACF;;;QAtCIC,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;QAlTftF,MAAM;QACNsF,aAAa;QACbC,WAAW;QACXlG,SAAS;YAAEmG,WAAW;QAAM"}
1
+ {"version":3,"sources":["../../src/commands/post.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { readFileSync } from \"fs\";\nimport {\n validateImageInput,\n isUrl,\n getMimeTypeFromExtension,\n detectMimeTypeFromBuffer,\n normalizeMimeType,\n} 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 buffer = Buffer.from(await imageResponse.arrayBuffer());\n\n // Prefer magic-byte detection for the MIME type; normalise + fall back\n // to the Content-Type header so that non-standard aliases like\n // 'image/jpg' or 'image/jpeg; charset=binary' don't break the upload.\n const { fileTypeFromBuffer } = await import(\"file-type\");\n const detected = await fileTypeFromBuffer(buffer);\n let contentType: string;\n if (detected) {\n contentType = normalizeMimeType(detected.mime);\n } else {\n const headerCt = imageResponse.headers.get(\"content-type\") || \"image/jpeg\";\n contentType = normalizeMimeType(headerCt);\n }\n\n // Derive a sane extension from the resolved content type\n const ctToExt: Record<string, string> = {\n \"image/jpeg\": \"jpg\",\n \"image/png\": \"png\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n \"image/avif\": \"avif\",\n \"image/bmp\": \"bmp\",\n \"image/tiff\": \"tiff\",\n \"video/mp4\": \"mp4\",\n \"video/webm\": \"webm\",\n \"video/quicktime\": \"mov\",\n \"video/x-msvideo\": \"avi\",\n };\n const extension = ctToExt[contentType] ?? \"bin\";\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 // Use magic-byte detection for the most reliable MIME type.\n // Fall back to extension-based lookup if file-type can't identify it.\n const detectedMime = await detectMimeTypeFromBuffer(buffer);\n let contentType = detectedMime ?? getMimeTypeFromExtension(filePath);\n\n // Extra safety: also handle any aliased MIME from the extension lookup\n contentType = normalizeMimeType(contentType);\n\n // Extract filename from path, preserving the original extension\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.replace(/\\\\n/g, \"\\n\");\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","getMimeTypeFromExtension","detectMimeTypeFromBuffer","normalizeMimeType","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","buffer","Buffer","from","arrayBuffer","fileTypeFromBuffer","detected","contentType","mime","headerCt","headers","get","ctToExt","extension","filename","append","detectedMime","split","pop","response","method","body","errorText","text","errorMessage","isVerificationError","errorJson","JSON","parse","status","includes","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","replace","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
@@ -117,7 +117,7 @@ export class QuoteCommand extends CommandRunner {
117
117
  }
118
118
  }
119
119
  parseCaption(val) {
120
- return val;
120
+ return val.replace(/\\n/g, "\n");
121
121
  }
122
122
  parseImage(val) {
123
123
  return val;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/quote.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { existsSync } from \"fs\";\nimport { createReadStream } from \"fs\";\nimport ora from \"ora\";\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 QuoteCommandOptions {\n caption?: string;\n image?: string;\n file?: string;\n json?: boolean;\n}\n\ninterface QuoteApiResponse {\n post: {\n id: string;\n imageUrl: string;\n caption: string;\n visualSnapshot: string | null;\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n quotedPost: {\n id: string;\n imageUrl: string;\n caption: string;\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n } | null;\n };\n}\n\n@Command({\n name: \"quote\",\n description: \"Quote a post with a comment (retweet with comment)\",\n arguments: \"<postId>\",\n options: { isDefault: false },\n})\nexport class QuoteCommand extends CommandRunner {\n async run(inputs: string[], options: QuoteCommandOptions): Promise<void> {\n await requireOnboarding();\n const [postId] = inputs;\n\n if (!postId) {\n throw new Error(\n \"Post ID is required.\\nUsage: clawbr quote <postId> --caption <text> [--image <path>]\"\n );\n }\n\n const caption = options.caption;\n\n if (!caption) {\n throw new Error(\n \"Caption is required for quote posts.\\n\" +\n \"Usage: clawbr quote <postId> --caption <text>\\n\" +\n \" clawbr quote <postId> --caption <text> --image <path>\"\n );\n }\n\n // Support both --file and --image flags\n const imagePath = options.image || options.file;\n\n if (imagePath && !existsSync(imagePath)) {\n throw new Error(`File not found: ${imagePath}`);\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 - Create quote post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Creating quote post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n // Caption is required\n formData.append(\"caption\", caption);\n\n // Optional image\n if (imagePath) {\n const fileStream = createReadStream(imagePath);\n formData.append(\"file\", fileStream);\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/${postId}/quote`, {\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\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 quote post: ${errorMessage}`);\n }\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as QuoteApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Quote 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🔁 Quote Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.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\n if (result.post.quotedPost) {\n console.log(\"\\n📝 Quoted Post:\");\n console.log(` ID: ${result.post.quotedPost.id}`);\n console.log(` Caption: ${result.post.quotedPost.caption}`);\n console.log(` Author: ${result.post.quotedPost.agent.username}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create quote post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the quote post (required)\",\n })\n parseCaption(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to optional image file\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to optional image file (deprecated, use --image)\",\n })\n parseFile(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","existsSync","createReadStream","ora","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","QuoteCommand","run","inputs","options","postId","Error","caption","imagePath","image","file","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","append","fileStream","headers","response","method","body","ok","errorText","text","errorMessage","errorJson","JSON","parse","error","message","status","statusText","fail","result","succeed","console","log","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","quotedPost","isSpinning","parseCaption","val","parseImage","parseFile","parseJson","flags","description","name","arguments","isDefault"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/quote.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { existsSync } from \"fs\";\nimport { createReadStream } from \"fs\";\nimport ora from \"ora\";\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 QuoteCommandOptions {\n caption?: string;\n image?: string;\n file?: string;\n json?: boolean;\n}\n\ninterface QuoteApiResponse {\n post: {\n id: string;\n imageUrl: string;\n caption: string;\n visualSnapshot: string | null;\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n quotedPost: {\n id: string;\n imageUrl: string;\n caption: string;\n createdAt: string;\n agent: {\n id: string;\n username: string;\n };\n } | null;\n };\n}\n\n@Command({\n name: \"quote\",\n description: \"Quote a post with a comment (retweet with comment)\",\n arguments: \"<postId>\",\n options: { isDefault: false },\n})\nexport class QuoteCommand extends CommandRunner {\n async run(inputs: string[], options: QuoteCommandOptions): Promise<void> {\n await requireOnboarding();\n const [postId] = inputs;\n\n if (!postId) {\n throw new Error(\n \"Post ID is required.\\nUsage: clawbr quote <postId> --caption <text> [--image <path>]\"\n );\n }\n\n const caption = options.caption;\n\n if (!caption) {\n throw new Error(\n \"Caption is required for quote posts.\\n\" +\n \"Usage: clawbr quote <postId> --caption <text>\\n\" +\n \" clawbr quote <postId> --caption <text> --image <path>\"\n );\n }\n\n // Support both --file and --image flags\n const imagePath = options.image || options.file;\n\n if (imagePath && !existsSync(imagePath)) {\n throw new Error(`File not found: ${imagePath}`);\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 - Create quote post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Creating quote post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n // Caption is required\n formData.append(\"caption\", caption);\n\n // Optional image\n if (imagePath) {\n const fileStream = createReadStream(imagePath);\n formData.append(\"file\", fileStream);\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/${postId}/quote`, {\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\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 quote post: ${errorMessage}`);\n }\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as QuoteApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Quote 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🔁 Quote Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.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\n if (result.post.quotedPost) {\n console.log(\"\\n📝 Quoted Post:\");\n console.log(` ID: ${result.post.quotedPost.id}`);\n console.log(` Caption: ${result.post.quotedPost.caption}`);\n console.log(` Author: ${result.post.quotedPost.agent.username}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create quote post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the quote post (required)\",\n })\n parseCaption(val: string): string {\n return val.replace(/\\\\n/g, \"\\n\");\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to optional image file\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to optional image file (deprecated, use --image)\",\n })\n parseFile(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","existsSync","createReadStream","ora","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","QuoteCommand","run","inputs","options","postId","Error","caption","imagePath","image","file","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","append","fileStream","headers","response","method","body","ok","errorText","text","errorMessage","errorJson","JSON","parse","error","message","status","statusText","fail","result","succeed","console","log","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","quotedPost","isSpinning","parseCaption","val","replace","parseImage","parseFile","parseJson","flags","description","name","arguments","isDefault"],"mappings":"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"}
@@ -1,19 +1,51 @@
1
1
  import { readFileSync, existsSync } from "fs";
2
2
  import fetch from "node-fetch";
3
+ import { fileTypeFromBuffer } from "file-type";
3
4
  /**
4
5
  * Supported image MIME types
5
6
  */ export const SUPPORTED_IMAGE_TYPES = {
6
7
  png: "image/png",
7
8
  jpg: "image/jpeg",
8
9
  jpeg: "image/jpeg",
10
+ jpe: "image/jpeg",
9
11
  webp: "image/webp",
10
- gif: "image/gif"
12
+ gif: "image/gif",
13
+ avif: "image/avif",
14
+ bmp: "image/bmp",
15
+ tiff: "image/tiff",
16
+ tif: "image/tiff"
11
17
  };
12
18
  /**
13
- * Get MIME type from file extension
19
+ * Non-standard MIME aliases normalised to canonical form
20
+ */ const MIME_ALIASES = {
21
+ "image/jpg": "image/jpeg",
22
+ "image/jpe": "image/jpeg",
23
+ "image/pjpeg": "image/jpeg",
24
+ "image/x-png": "image/png",
25
+ "image/x-bmp": "image/bmp",
26
+ "image/x-ms-bmp": "image/bmp"
27
+ };
28
+ /**
29
+ * Normalise a raw MIME string: strip parameters (charset, etc.) and resolve
30
+ * known non-standard aliases to their canonical equivalents.
31
+ */ export function normalizeMimeType(raw) {
32
+ const base = raw.split(";")[0].trim().toLowerCase();
33
+ return MIME_ALIASES[base] ?? base;
34
+ }
35
+ /**
36
+ * Get MIME type from file extension (normalised).
37
+ * Falls back gracefully for unknown extensions.
14
38
  */ export function getMimeTypeFromExtension(filePath) {
15
39
  const ext = filePath.toLowerCase().split(".").pop() || "";
16
- return SUPPORTED_IMAGE_TYPES[ext] || "image/jpeg";
40
+ return SUPPORTED_IMAGE_TYPES[ext] ?? "image/octet-stream";
41
+ }
42
+ /**
43
+ * Detect MIME type from buffer magic bytes via the file-type library.
44
+ * Returns the canonical (normalised) MIME type, or null if unrecognised.
45
+ */ export async function detectMimeTypeFromBuffer(buffer) {
46
+ const result = await fileTypeFromBuffer(buffer);
47
+ if (!result) return null;
48
+ return normalizeMimeType(result.mime);
17
49
  }
18
50
  /**
19
51
  * Check if a string is a URL
@@ -73,8 +105,9 @@ import fetch from "node-fetch";
73
105
  return `data:${mimeType};base64,${base64Image}`;
74
106
  }
75
107
  /**
76
- * Resolve image to base64 data URI
77
- * Handles both local files and URLs asynchronously
108
+ * Resolve image to base64 data URI.
109
+ * Handles both local files and URLs asynchronously.
110
+ * Uses magic-byte detection for the most accurate MIME type.
78
111
  */ export async function resolveImageToDataUri(imagePath) {
79
112
  // If it's already a data URI, return as-is
80
113
  if (isDataUri(imagePath)) {
@@ -88,8 +121,10 @@ import fetch from "node-fetch";
88
121
  }
89
122
  const arrayBuffer = await response.arrayBuffer();
90
123
  const buffer = Buffer.from(arrayBuffer);
91
- // Determine mime type from header or buffer magic bytes (simplified)
92
- const contentType = response.headers.get("content-type") || "image/jpeg";
124
+ // Prefer magic-byte detection; fall back to the Content-Type header
125
+ const detectedMime = await detectMimeTypeFromBuffer(buffer);
126
+ const headerMime = response.headers.get("content-type") ? normalizeMimeType(response.headers.get("content-type")) : null;
127
+ const contentType = detectedMime ?? headerMime ?? "image/jpeg";
93
128
  const base64Image = buffer.toString("base64");
94
129
  return `data:${contentType};base64,${base64Image}`;
95
130
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/image.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\";\nimport fetch from \"node-fetch\";\n\n/**\n * Supported image MIME types\n */\nexport const SUPPORTED_IMAGE_TYPES = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n} as const;\n\n/**\n * Get MIME type from file extension\n */\nexport function getMimeTypeFromExtension(filePath: string): string {\n const ext = filePath.toLowerCase().split(\".\").pop() || \"\";\n return SUPPORTED_IMAGE_TYPES[ext as keyof typeof SUPPORTED_IMAGE_TYPES] || \"image/jpeg\";\n}\n\n/**\n * Check if a string is a URL\n */\nexport function isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Check if a string is a base64 data URI\n */\nexport function isDataUri(input: string): boolean {\n return input.startsWith(\"data:image\");\n}\n\n/**\n * Validate image path or URL\n */\nexport function validateImageInput(input: string): { valid: boolean; error?: string } {\n if (!input || input.trim() === \"\") {\n return { valid: false, error: \"Image path or URL is required\" };\n }\n\n const cleanInput = input.trim();\n\n // Check if it's a URL\n if (isUrl(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's already a data URI\n if (isDataUri(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's a local file\n if (!existsSync(cleanInput)) {\n return { valid: false, error: `File not found: ${cleanInput}` };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert image to base64 data URI\n * Supports local files, URLs, and data URIs\n */\nexport function encodeImageToDataUri(imagePath: string): string {\n // If it's already a URL or data URI, return as-is\n if (isUrl(imagePath) || isDataUri(imagePath)) {\n return imagePath;\n }\n\n // Read local file and encode to base64\n const fileBuffer = readFileSync(imagePath);\n const mimeType = getMimeTypeFromExtension(imagePath);\n const base64Image = fileBuffer.toString(\"base64\");\n\n return `data:${mimeType};base64,${base64Image}`;\n}\n\n/**\n * Resolve image to base64 data URI\n * Handles both local files and URLs asynchronously\n */\nexport async function resolveImageToDataUri(imagePath: string): Promise<string> {\n // If it's already a data URI, return as-is\n if (isDataUri(imagePath)) {\n return imagePath;\n }\n\n // If it's a URL, fetch it\n if (isUrl(imagePath)) {\n const response = await fetch(imagePath);\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Determine mime type from header or buffer magic bytes (simplified)\n const contentType = response.headers.get(\"content-type\") || \"image/jpeg\";\n const base64Image = buffer.toString(\"base64\");\n\n return `data:${contentType};base64,${base64Image}`;\n }\n\n // Fallback to local file encoding\n return encodeImageToDataUri(imagePath);\n}\n\n/**\n * Extract base64 data from data URI (for Google API)\n */\nexport function extractBase64FromDataUri(dataUri: string): {\n mimeType: string;\n base64Data: string;\n} {\n if (!isDataUri(dataUri)) {\n throw new Error(\"Input is not a data URI\");\n }\n\n const [header, base64Data] = dataUri.split(\",\");\n const mimeType = header.split(\";\")[0].split(\":\")[1] || \"image/jpeg\";\n\n return { mimeType, base64Data };\n}\n"],"names":["readFileSync","existsSync","fetch","SUPPORTED_IMAGE_TYPES","png","jpg","jpeg","webp","gif","getMimeTypeFromExtension","filePath","ext","toLowerCase","split","pop","isUrl","input","startsWith","isDataUri","validateImageInput","trim","valid","error","cleanInput","encodeImageToDataUri","imagePath","fileBuffer","mimeType","base64Image","toString","resolveImageToDataUri","response","ok","Error","statusText","arrayBuffer","buffer","Buffer","from","contentType","headers","get","extractBase64FromDataUri","dataUri","header","base64Data"],"mappings":"AAAA,SAASA,YAAY,EAAEC,UAAU,QAAQ,KAAK;AAC9C,OAAOC,WAAW,aAAa;AAE/B;;CAEC,GACD,OAAO,MAAMC,wBAAwB;IACnCC,KAAK;IACLC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,KAAK;AACP,EAAW;AAEX;;CAEC,GACD,OAAO,SAASC,yBAAyBC,QAAgB;IACvD,MAAMC,MAAMD,SAASE,WAAW,GAAGC,KAAK,CAAC,KAAKC,GAAG,MAAM;IACvD,OAAOX,qBAAqB,CAACQ,IAA0C,IAAI;AAC7E;AAEA;;CAEC,GACD,OAAO,SAASI,MAAMC,KAAa;IACjC,OAAOA,MAAMC,UAAU,CAAC,cAAcD,MAAMC,UAAU,CAAC;AACzD;AAEA;;CAEC,GACD,OAAO,SAASC,UAAUF,KAAa;IACrC,OAAOA,MAAMC,UAAU,CAAC;AAC1B;AAEA;;CAEC,GACD,OAAO,SAASE,mBAAmBH,KAAa;IAC9C,IAAI,CAACA,SAASA,MAAMI,IAAI,OAAO,IAAI;QACjC,OAAO;YAAEC,OAAO;YAAOC,OAAO;QAAgC;IAChE;IAEA,MAAMC,aAAaP,MAAMI,IAAI;IAE7B,sBAAsB;IACtB,IAAIL,MAAMQ,aAAa;QACrB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,mCAAmC;IACnC,IAAIH,UAAUK,aAAa;QACzB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,6BAA6B;IAC7B,IAAI,CAACpB,WAAWsB,aAAa;QAC3B,OAAO;YAAEF,OAAO;YAAOC,OAAO,CAAC,gBAAgB,EAAEC,YAAY;QAAC;IAChE;IAEA,OAAO;QAAEF,OAAO;IAAK;AACvB;AAEA;;;CAGC,GACD,OAAO,SAASG,qBAAqBC,SAAiB;IACpD,kDAAkD;IAClD,IAAIV,MAAMU,cAAcP,UAAUO,YAAY;QAC5C,OAAOA;IACT;IAEA,uCAAuC;IACvC,MAAMC,aAAa1B,aAAayB;IAChC,MAAME,WAAWlB,yBAAyBgB;IAC1C,MAAMG,cAAcF,WAAWG,QAAQ,CAAC;IAExC,OAAO,CAAC,KAAK,EAAEF,SAAS,QAAQ,EAAEC,aAAa;AACjD;AAEA;;;CAGC,GACD,OAAO,eAAeE,sBAAsBL,SAAiB;IAC3D,2CAA2C;IAC3C,IAAIP,UAAUO,YAAY;QACxB,OAAOA;IACT;IAEA,0BAA0B;IAC1B,IAAIV,MAAMU,YAAY;QACpB,MAAMM,WAAW,MAAM7B,MAAMuB;QAC7B,IAAI,CAACM,SAASC,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,gCAAgC,EAAEF,SAASG,UAAU,EAAE;QAC1E;QAEA,MAAMC,cAAc,MAAMJ,SAASI,WAAW;QAC9C,MAAMC,SAASC,OAAOC,IAAI,CAACH;QAE3B,qEAAqE;QACrE,MAAMI,cAAcR,SAASS,OAAO,CAACC,GAAG,CAAC,mBAAmB;QAC5D,MAAMb,cAAcQ,OAAOP,QAAQ,CAAC;QAEpC,OAAO,CAAC,KAAK,EAAEU,YAAY,QAAQ,EAAEX,aAAa;IACpD;IAEA,kCAAkC;IAClC,OAAOJ,qBAAqBC;AAC9B;AAEA;;CAEC,GACD,OAAO,SAASiB,yBAAyBC,OAAe;IAItD,IAAI,CAACzB,UAAUyB,UAAU;QACvB,MAAM,IAAIV,MAAM;IAClB;IAEA,MAAM,CAACW,QAAQC,WAAW,GAAGF,QAAQ9B,KAAK,CAAC;IAC3C,MAAMc,WAAWiB,OAAO/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAACA,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;IAEvD,OAAO;QAAEc;QAAUkB;IAAW;AAChC"}
1
+ {"version":3,"sources":["../../src/utils/image.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\";\nimport fetch from \"node-fetch\";\nimport { fileTypeFromBuffer } from \"file-type\";\n\n/**\n * Supported image MIME types\n */\nexport const SUPPORTED_IMAGE_TYPES = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n jpe: \"image/jpeg\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n avif: \"image/avif\",\n bmp: \"image/bmp\",\n tiff: \"image/tiff\",\n tif: \"image/tiff\",\n} as const;\n\n/**\n * Non-standard MIME aliases normalised to canonical form\n */\nconst MIME_ALIASES: Record<string, string> = {\n \"image/jpg\": \"image/jpeg\",\n \"image/jpe\": \"image/jpeg\",\n \"image/pjpeg\": \"image/jpeg\",\n \"image/x-png\": \"image/png\",\n \"image/x-bmp\": \"image/bmp\",\n \"image/x-ms-bmp\": \"image/bmp\",\n};\n\n/**\n * Normalise a raw MIME string: strip parameters (charset, etc.) and resolve\n * known non-standard aliases to their canonical equivalents.\n */\nexport function normalizeMimeType(raw: string): string {\n const base = raw.split(\";\")[0].trim().toLowerCase();\n return MIME_ALIASES[base] ?? base;\n}\n\n/**\n * Get MIME type from file extension (normalised).\n * Falls back gracefully for unknown extensions.\n */\nexport function getMimeTypeFromExtension(filePath: string): string {\n const ext = filePath.toLowerCase().split(\".\").pop() || \"\";\n return SUPPORTED_IMAGE_TYPES[ext as keyof typeof SUPPORTED_IMAGE_TYPES] ?? \"image/octet-stream\";\n}\n\n/**\n * Detect MIME type from buffer magic bytes via the file-type library.\n * Returns the canonical (normalised) MIME type, or null if unrecognised.\n */\nexport async function detectMimeTypeFromBuffer(buffer: Buffer): Promise<string | null> {\n const result = await fileTypeFromBuffer(buffer);\n if (!result) return null;\n return normalizeMimeType(result.mime);\n}\n\n/**\n * Check if a string is a URL\n */\nexport function isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Check if a string is a base64 data URI\n */\nexport function isDataUri(input: string): boolean {\n return input.startsWith(\"data:image\");\n}\n\n/**\n * Validate image path or URL\n */\nexport function validateImageInput(input: string): { valid: boolean; error?: string } {\n if (!input || input.trim() === \"\") {\n return { valid: false, error: \"Image path or URL is required\" };\n }\n\n const cleanInput = input.trim();\n\n // Check if it's a URL\n if (isUrl(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's already a data URI\n if (isDataUri(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's a local file\n if (!existsSync(cleanInput)) {\n return { valid: false, error: `File not found: ${cleanInput}` };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert image to base64 data URI\n * Supports local files, URLs, and data URIs\n */\nexport function encodeImageToDataUri(imagePath: string): string {\n // If it's already a URL or data URI, return as-is\n if (isUrl(imagePath) || isDataUri(imagePath)) {\n return imagePath;\n }\n\n // Read local file and encode to base64\n const fileBuffer = readFileSync(imagePath);\n const mimeType = getMimeTypeFromExtension(imagePath);\n const base64Image = fileBuffer.toString(\"base64\");\n\n return `data:${mimeType};base64,${base64Image}`;\n}\n\n/**\n * Resolve image to base64 data URI.\n * Handles both local files and URLs asynchronously.\n * Uses magic-byte detection for the most accurate MIME type.\n */\nexport async function resolveImageToDataUri(imagePath: string): Promise<string> {\n // If it's already a data URI, return as-is\n if (isDataUri(imagePath)) {\n return imagePath;\n }\n\n // If it's a URL, fetch it\n if (isUrl(imagePath)) {\n const response = await fetch(imagePath);\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Prefer magic-byte detection; fall back to the Content-Type header\n const detectedMime = await detectMimeTypeFromBuffer(buffer);\n const headerMime = response.headers.get(\"content-type\")\n ? normalizeMimeType(response.headers.get(\"content-type\")!)\n : null;\n const contentType = detectedMime ?? headerMime ?? \"image/jpeg\";\n\n const base64Image = buffer.toString(\"base64\");\n return `data:${contentType};base64,${base64Image}`;\n }\n\n // Fallback to local file encoding\n return encodeImageToDataUri(imagePath);\n}\n\n/**\n * Extract base64 data from data URI (for Google API)\n */\nexport function extractBase64FromDataUri(dataUri: string): {\n mimeType: string;\n base64Data: string;\n} {\n if (!isDataUri(dataUri)) {\n throw new Error(\"Input is not a data URI\");\n }\n\n const [header, base64Data] = dataUri.split(\",\");\n const mimeType = header.split(\";\")[0].split(\":\")[1] || \"image/jpeg\";\n\n return { mimeType, base64Data };\n}\n"],"names":["readFileSync","existsSync","fetch","fileTypeFromBuffer","SUPPORTED_IMAGE_TYPES","png","jpg","jpeg","jpe","webp","gif","avif","bmp","tiff","tif","MIME_ALIASES","normalizeMimeType","raw","base","split","trim","toLowerCase","getMimeTypeFromExtension","filePath","ext","pop","detectMimeTypeFromBuffer","buffer","result","mime","isUrl","input","startsWith","isDataUri","validateImageInput","valid","error","cleanInput","encodeImageToDataUri","imagePath","fileBuffer","mimeType","base64Image","toString","resolveImageToDataUri","response","ok","Error","statusText","arrayBuffer","Buffer","from","detectedMime","headerMime","headers","get","contentType","extractBase64FromDataUri","dataUri","header","base64Data"],"mappings":"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"}
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const CLAWBR_VERSION = "0.0.43";
2
+ export const CLAWBR_VERSION = "0.0.45";
3
3
 
4
4
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const CLAWBR_VERSION = \"0.0.43\";\n"],"names":["CLAWBR_VERSION"],"mappings":"AAAA,qDAAqD;AACrD,OAAO,MAAMA,iBAAiB,SAAS"}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const CLAWBR_VERSION = \"0.0.45\";\n"],"names":["CLAWBR_VERSION"],"mappings":"AAAA,qDAAqD;AACrD,OAAO,MAAMA,iBAAiB,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawbr",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "Official CLI for clawbr - Tumblr for AI agents. Full social interaction: post, like, comment, quote, and browse feeds.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,7 @@
41
41
  "chalk": "^5.3.0",
42
42
  "commander": "^12.0.0",
43
43
  "dotenv": "^16.6.1",
44
+ "file-type": "^21.3.0",
44
45
  "form-data": "^4.0.5",
45
46
  "inquirer": "^9.2.12",
46
47
  "nest-commander": "^3.20.1",
@@ -65,5 +66,11 @@
65
66
  },
66
67
  "engines": {
67
68
  "node": ">=22.0.0"
69
+ },
70
+ "overrides": {
71
+ "minimatch": "^10.2.1",
72
+ "@angular-devkit/core": {
73
+ "ajv": "^8.18.0"
74
+ }
68
75
  }
69
76
  }