social-agent-cli 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ai/runner.ts CHANGED
@@ -15,16 +15,22 @@ export async function runCommand(platform: string, command: string): Promise<voi
15
15
  const url = BROWSER_PLATFORMS[platform];
16
16
  if (!url) { console.log(`"${platform}" desteklenmiyor.`); process.exit(1); }
17
17
 
18
- // Komuttan CONTENT ve IMAGE çıkar
19
- const imageMatch = command.match(/(?:fotoğraf|foto|image|görsel)[:\s]*([^\s,]+)/i);
20
- const image = imageMatch?.[1]?.trim() || "";
18
+ // --content "..." ve --image ... parse et
19
+ let content = "";
20
+ let image = "";
21
21
 
22
- // Image referansını ve prefix'leri temizle
23
- let content = command
24
- .replace(/,?\s*(?:fotoğraf|foto|image|görsel)[:\s]*[^\s,]+/i, "")
25
- .replace(/^şu mesajı (?:fotoğraf ile )?(?:tweetle|paylaş|gönder)[:\s]*/i, "")
26
- .replace(/^(?:tweetle|paylaş|post at|mesaj)[:\s]*/i, "")
27
- .trim();
22
+ const contentMatch = command.match(/--content\s+([\s\S]+?)(?=\s+--|\s*$)/);
23
+ const imageMatch = command.match(/--image\s+(\S+)/);
24
+
25
+ if (contentMatch) {
26
+ content = contentMatch[1].replace(/^"|"$/g, "").trim();
27
+ } else {
28
+ // Flag yoksa tüm komutu content olarak al
29
+ content = command.replace(/--image\s+\S+/g, "").trim();
30
+ }
31
+ if (imageMatch) {
32
+ image = imageMatch[1];
33
+ }
28
34
 
29
35
  // Map seç
30
36
  const map = loadMap(platform, "post");
package/cli.ts CHANGED
@@ -189,13 +189,11 @@ switch (command) {
189
189
  }
190
190
 
191
191
  case "run": {
192
- const filteredArgs = args.filter(a => !a.startsWith("--"));
193
- const runPlatform = BROWSER_PLATFORMS[filteredArgs[0]] ? filteredArgs[0] : "x";
194
- const runCmd = BROWSER_PLATFORMS[filteredArgs[0]]
195
- ? filteredArgs.slice(1).join(" ")
196
- : filteredArgs.join(" ");
192
+ const runPlatform = BROWSER_PLATFORMS[args[0]] ? args[0] : "x";
193
+ const runArgs = BROWSER_PLATFORMS[args[0]] ? args.slice(1) : args;
194
+ const runCmd = runArgs.join(" ");
197
195
  if (!runCmd) {
198
- console.log('Kullanım: social-agent run <platform> "komut"');
196
+ console.log('Kullanım: social-agent run <platform> --content "mesaj" [--image /path/to/img]');
199
197
  break;
200
198
  }
201
199
  await runCommand(runPlatform, runCmd);
@@ -0,0 +1,34 @@
1
+ /**
2
+ * LinkedIn upload helper - Playwright fileChooser ile dosya yükler
3
+ * agent-browser CDP ile çalışmıyor çünkü LinkedIn native file dialog kullanıyor
4
+ *
5
+ * Kullanım: npx tsx core/upload-helper.ts <cdp-port> <button-selector> <file-path>
6
+ */
7
+ import { chromium } from "playwright";
8
+
9
+ const [, , port, selector, filePath] = process.argv;
10
+
11
+ if (!port || !selector || !filePath) {
12
+ console.error("Kullanım: npx tsx core/upload-helper.ts <port> <selector> <file-path>");
13
+ process.exit(1);
14
+ }
15
+
16
+ async function upload() {
17
+ const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
18
+ const context = browser.contexts()[0];
19
+ const pages = context.pages();
20
+ const page = pages[pages.length - 1]; // son aktif sayfa
21
+
22
+ const [fileChooser] = await Promise.all([
23
+ page.waitForEvent("filechooser", { timeout: 10000 }),
24
+ page.click(selector),
25
+ ]);
26
+
27
+ await fileChooser.setFiles(filePath);
28
+ console.log("OK");
29
+ }
30
+
31
+ upload().catch((e) => {
32
+ console.error(e.message);
33
+ process.exit(1);
34
+ });
@@ -2,68 +2,35 @@
2
2
  "post": {
3
3
  "platform": "linkedin",
4
4
  "action": "post",
5
- "description": "LinkedIn üzerinden yeni bir metin postu paylaşma",
6
- "version": 1,
7
- "lastUpdated": "2026-03-14T12:00:00.000Z",
5
+ "description": "LinkedIn post paylaşma",
6
+ "version": 3,
8
7
  "parameters": [
9
8
  "{{CONTENT}}"
10
9
  ],
11
10
  "steps": [
12
11
  {
13
12
  "action": "click",
14
- "description": "Gönderi oluştur kutusuna tıkla",
13
+ "description": "\"Gönderi başlatın\" butonuna tıkla",
15
14
  "selector": "button.share-box-feed-entry__trigger",
16
- "fallbackSelectors": [
17
- "[aria-label*='Start a post']",
18
- "[aria-label*='Gönderi başlat']",
19
- "button[aria-label*='post']"
20
- ]
21
- },
22
- {
23
- "action": "wait",
24
- "description": "Post oluşturma modalının açılmasını bekle",
25
- "waitMs": 1500
26
- },
27
- {
28
- "action": "click",
29
- "description": "Metin alanına tıkla",
30
- "selector": "[role='textbox'][aria-label*='Text editor']",
31
- "fallbackSelectors": [
32
- "[role='textbox'][contenteditable='true']",
33
- ".ql-editor[contenteditable='true']",
34
- "[aria-label*='Metin düzenleyici']"
35
- ]
15
+ "fallbackSelectors": [],
16
+ "waitMs": 2000
36
17
  },
37
18
  {
38
19
  "action": "type",
39
20
  "description": "Post metnini yaz",
40
- "selector": "[role='textbox'][aria-label*='Text editor']",
21
+ "selector": "[role=\"textbox\"]",
41
22
  "value": "{{CONTENT}}",
42
23
  "fallbackSelectors": [
43
- "[role='textbox'][contenteditable='true']",
44
- ".ql-editor[contenteditable='true']"
45
- ]
46
- },
47
- {
48
- "action": "wait",
49
- "description": "Metin girişi sonrası bekleme",
24
+ "[contenteditable=\"true\"]"
25
+ ],
50
26
  "waitMs": 1000
51
27
  },
52
28
  {
53
29
  "action": "click",
54
- "description": "Gönder butonuna tıkla",
30
+ "description": "\"Gönderi\" gönder butonuna tıkla",
55
31
  "selector": "button.share-actions__primary-action",
56
- "fallbackSelectors": [
57
- "[aria-label='Post']",
58
- "[aria-label='Gönder']",
59
- "button[aria-label*='Post']",
60
- "button[aria-label*='Paylaş']"
61
- ]
62
- },
63
- {
64
- "action": "wait",
65
- "description": "Post gönderimini bekle",
66
- "waitMs": 2000
32
+ "fallbackSelectors": [],
33
+ "waitMs": 3000
67
34
  }
68
35
  ]
69
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-agent-cli",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "AI-powered social media agent - free APIs + browser automation with self-healing selectors",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,7 @@
24
24
  "tsconfig.json"
25
25
  ],
26
26
  "dependencies": {
27
+ "playwright": "^1.58.2",
27
28
  "tsx": "^4.19.0"
28
29
  },
29
30
  "devDependencies": {
@@ -146,16 +146,31 @@ export class BrowserDriver {
146
146
 
147
147
  case "click": {
148
148
  const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
149
+ // 1. CSS selector ile dene
149
150
  for (const sel of sels) {
150
151
  if (sel.includes("{{")) continue;
151
152
  try { this.cmd(`click "${sel}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
152
153
  }
153
- throw new Error(`Click: ${sels[0]}`);
154
+ // 2. JS eval fallback - description'dan buton text'i çıkar
155
+ const desc = step.description || "";
156
+ // Tırnak içindeki text'leri bul: "Gönderi", 'Post', vb.
157
+ const textMatches = desc.match(/["'""\u201C\u201D]([^"'""\u201C\u201D]+)["'""\u201C\u201D]/g) || [];
158
+ const texts = textMatches.map(m => m.replace(/["'""\u201C\u201D]/g, "").trim()).filter(Boolean);
159
+
160
+ for (const text of texts) {
161
+ try {
162
+ this.cmd(`eval "document.querySelectorAll('button,a,[role=button]').forEach(b => { if(b.textContent.trim()==='${text}' || b.textContent.trim().startsWith('${text}')) b.click() })"`);
163
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
164
+ return;
165
+ } catch {}
166
+ }
167
+ throw new Error(`Click: ${sels[0] || desc}`);
154
168
  }
155
169
 
156
170
  case "type": {
157
171
  const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
158
- const escaped = val.replace(/"/g, '\\"');
172
+ const escaped = val.replace(/"/g, '\\"').replace(/'/g, "\\'");
173
+ // 1. CSS selector ile dene
159
174
  for (const sel of sels) {
160
175
  if (sel.includes("{{")) continue;
161
176
  try {
@@ -164,6 +179,12 @@ export class BrowserDriver {
164
179
  return;
165
180
  } catch {}
166
181
  }
182
+ // 2. Fallback: sayfadaki ilk textbox/contenteditable'a yaz
183
+ try {
184
+ this.cmd(`eval "var el = document.querySelector('[role=textbox],[contenteditable=true]'); if(el) { el.focus(); document.execCommand('insertText', false, '${escaped}'); }"`);
185
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
186
+ return;
187
+ } catch {}
167
188
  throw new Error(`Type: ${sels[0]}`);
168
189
  }
169
190
 
@@ -175,17 +196,35 @@ export class BrowserDriver {
175
196
  case "upload": {
176
197
  const filePath = val || imagePath || "";
177
198
  if (!filePath || filePath.includes("{{")) break;
178
- // Her zaman input[type=file] ile yükle - en güvenilir yol
199
+
200
+ // 1. agent-browser upload ile dene (X'te çalışır)
179
201
  try {
180
- this.cmd(`upload "input[type='file']" "${filePath}"`, 10000);
202
+ this.cmd(`upload "input[type='file']" "${filePath}"`, 5000);
181
203
  if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
182
204
  return;
183
205
  } catch {}
184
- // Fallback: selector'larla dene
206
+
207
+ // 2. Playwright fileChooser ile dene (LinkedIn gibi native dialog kullananlar için)
185
208
  const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
186
- for (const sel of sels) {
187
- try { this.cmd(`upload "${sel}" "${filePath}"`, 10000); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
188
- }
209
+ const btnSel = sels.find(s => !s.includes("input[type")) || "button";
210
+ try {
211
+ const { dirname: dn } = await import("node:path");
212
+ const { fileURLToPath: fu } = await import("node:url");
213
+ const pkgDir = process.env.SOCIAL_AGENT_PKG || join(dn(fu(import.meta.url)), "../..");
214
+ execSync(
215
+ `npx tsx "${join(pkgDir, 'core/upload-helper.ts')}" ${CDP_PORT} "${btnSel}" "${filePath}"`,
216
+ { stdio: "pipe", timeout: 15000 }
217
+ );
218
+ this.cmd("wait 3000");
219
+ // Editör modal'ı varsa kapat
220
+ try {
221
+ this.cmd(`eval "document.querySelectorAll('button').forEach(b => { var t=b.textContent.trim().toLowerCase(); if(t==='ileri' || t==='bitti' || t==='done' || t==='next' || t==='tamam') b.click() })"`);
222
+ this.cmd("wait 1000");
223
+ } catch {}
224
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
225
+ return;
226
+ } catch {}
227
+
189
228
  throw new Error(`Upload: ${filePath}`);
190
229
  }
191
230
  }