social-agent-cli 3.1.1 → 4.0.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/mapper.ts CHANGED
@@ -1,173 +1,8 @@
1
- import { spawn } from "node:child_process";
2
- import { readFileSync, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
3
2
  import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
3
  import { PATHS } from "../core/config.js";
6
4
  import type { SelectorMap } from "../core/types.js";
7
5
 
8
- // ── JSON Schemas ─────────────────────────────────────────────
9
-
10
- const SELECTOR_MAP_SCHEMA = JSON.stringify({
11
- type: "object",
12
- properties: {
13
- platform: { type: "string" },
14
- action: { type: "string" },
15
- description: { type: "string" },
16
- version: { type: "number" },
17
- lastUpdated: { type: "string" },
18
- parameters: { type: "array", items: { type: "string" } },
19
- steps: {
20
- type: "array",
21
- items: {
22
- type: "object",
23
- properties: {
24
- action: { type: "string", enum: ["click", "type", "wait", "keypress", "goto", "upload"] },
25
- description: { type: "string" },
26
- selector: { type: "string" },
27
- value: { type: "string" },
28
- key: { type: "string" },
29
- url: { type: "string" },
30
- waitMs: { type: "number" },
31
- fallbackSelectors: { type: "array", items: { type: "string" } },
32
- },
33
- required: ["action", "description"],
34
- },
35
- },
36
- },
37
- required: ["platform", "version", "steps"],
38
- });
39
-
40
- const HEAL_SCHEMA = JSON.stringify({
41
- type: "object",
42
- properties: {
43
- selector: { type: "string" },
44
- fallbackSelectors: { type: "array", items: { type: "string" } },
45
- explanation: { type: "string" },
46
- },
47
- required: ["selector", "fallbackSelectors", "explanation"],
48
- });
49
-
50
- // ── Claude -p Wrapper ────────────────────────────────────────
51
-
52
- interface ClaudeOptions {
53
- prompt: string;
54
- jsonSchema?: string;
55
- imagePath?: string;
56
- model?: string;
57
- effort?: string;
58
- stream?: boolean;
59
- }
60
-
61
- function claude(opts: ClaudeOptions): Promise<any> {
62
- const { prompt, jsonSchema, imagePath, model = "opus", effort = "low", stream = true } = opts;
63
-
64
- const tmpDir = mkdtempSync(join(tmpdir(), "social-agent-"));
65
- const promptFile = join(tmpDir, "prompt.txt");
66
- writeFileSync(promptFile, prompt);
67
-
68
- return new Promise((resolve, reject) => {
69
- let cmd: string;
70
- const base = `claude -p --model ${model} --effort ${effort} --max-turns 3 --tools ""`;
71
-
72
- // Screenshot varsa prompt'a dosya yolunu ekle, Claude Code dosyayı okuyabilir
73
- if (imagePath && existsSync(imagePath)) {
74
- writeFileSync(promptFile, `Screenshot: ${imagePath}\n\n${prompt}`);
75
- }
76
- cmd = `cat '${promptFile}' | ${base}`;
77
- if (jsonSchema) cmd += ` --output-format json --json-schema '${jsonSchema}'`;
78
-
79
- const proc = spawn("bash", ["-c", cmd], {
80
- stdio: ["pipe", "pipe", "pipe"],
81
- });
82
-
83
- let stdout = "";
84
- let stderr = "";
85
-
86
- proc.stdout.on("data", (chunk: Buffer) => {
87
- const text = chunk.toString();
88
- stdout += text;
89
- });
90
-
91
- proc.stderr.on("data", (chunk: Buffer) => {
92
- stderr += chunk.toString();
93
- });
94
-
95
- proc.on("close", (code) => {
96
- rmSync(tmpDir, { recursive: true, force: true });
97
- if (stream) process.stdout.write("\n");
98
-
99
- const output = stdout.trim();
100
-
101
- if (jsonSchema) {
102
- try {
103
- const parsed = JSON.parse(output);
104
-
105
- // structured_output alanı varsa ve dolu ise
106
- if (parsed.structured_output && parsed.structured_output.steps) {
107
- resolve(parsed.structured_output);
108
- return;
109
- }
110
-
111
- // result alanında JSON string olabilir
112
- if (parsed.result && typeof parsed.result === "string" && parsed.result.length > 2) {
113
- try { resolve(JSON.parse(parsed.result)); return; } catch {}
114
- }
115
-
116
- // error_max_turns veya boş result durumunda - result'tan text çıkar
117
- if (parsed.subtype === "error_max_turns" || !parsed.structured_output) {
118
- // Claude tool kullanıp bitirmeden durmuş, sonucu elle çıkarmayı dene
119
- reject(new Error(`Claude tamamlayamadı (${parsed.subtype || "unknown"}). max-turns artırılabilir.`));
120
- return;
121
- }
122
-
123
- // Mesaj dizisi formatı
124
- if (Array.isArray(parsed)) {
125
- const last = parsed[parsed.length - 1];
126
- const text = typeof last.content === "string"
127
- ? last.content
128
- : last.content?.map((c: any) => c.text || "").join("") || "";
129
- resolve(JSON.parse(text));
130
- return;
131
- }
132
-
133
- // Direkt obje
134
- if (parsed.steps || parsed.selector) {
135
- resolve(parsed);
136
- return;
137
- }
138
-
139
- resolve(parsed);
140
- } catch (parseErr: any) {
141
- // Regex fallback
142
- const match = output.match(/\{[\s\S]*\}/);
143
- if (match) {
144
- try { resolve(JSON.parse(match[0])); return; } catch {}
145
- }
146
- reject(new Error("Claude'dan geçerli JSON alınamadı"));
147
- }
148
- return;
149
- }
150
-
151
- if (code !== 0 && !output) {
152
- reject(new Error(`Claude hata (code ${code}): ${stderr}`));
153
- return;
154
- }
155
-
156
- resolve(output);
157
- });
158
-
159
- proc.on("error", (err) => {
160
- rmSync(tmpDir, { recursive: true, force: true });
161
- reject(err);
162
- });
163
-
164
- setTimeout(() => {
165
- proc.kill();
166
- reject(new Error("Claude timeout (180s)"));
167
- }, 180000);
168
- });
169
- }
170
-
171
6
  // ── Map CRUD (platform başına tek dosya) ─────────────────────
172
7
 
173
8
  interface PlatformMaps {
@@ -207,182 +42,15 @@ export function listMaps(platform?: string): SelectorMap[] {
207
42
  for (const file of readdirSync(PATHS.maps)) {
208
43
  if (!file.endsWith(".json")) continue;
209
44
  if (platform && file !== `${platform}.json`) continue;
210
- const maps = JSON.parse(readFileSync(join(PATHS.maps, file), "utf-8"));
211
- for (const map of Object.values(maps) as SelectorMap[]) {
212
- results.push(map);
45
+ const content = JSON.parse(readFileSync(join(PATHS.maps, file), "utf-8"));
46
+ if (content.steps) {
47
+ results.push(content);
48
+ } else {
49
+ for (const map of Object.values(content) as SelectorMap[]) {
50
+ if (map.steps) results.push(map);
51
+ }
213
52
  }
214
53
  }
215
54
  } catch {}
216
55
  return results;
217
56
  }
218
-
219
- // ── Map Oluştur ──────────────────────────────────────────────
220
-
221
- export async function generateMap(
222
- platform: string,
223
- screenshotPath: string,
224
- domSnapshot: string,
225
- taskDescription = "yeni bir metin postu at",
226
- actionName = "post"
227
- ): Promise<SelectorMap> {
228
- const prompt = `Sen bir browser otomasyon uzmanısın. ${platform} platformunda şu görevi yerine getirmek için adımları belirle:
229
-
230
- GÖREV: "${taskDescription}"
231
-
232
- Sayfanın Accessibility Tree'si (ARIA Snapshot):
233
- ${domSnapshot}
234
-
235
- Kurallar:
236
- - ARIA snapshot'taki role ve name bilgilerini kullanarak selector oluştur
237
- - aria-label, role, data-testid, id tercih et
238
- - Dosya/görsel/video yükleme için "upload" action kullan. Upload action şöyle çalışır:
239
- - selector'da medya/fotoğraf ekleme BUTONUNU ver (ör: button[aria-label*='Fotoğraf'], button[aria-label*='Add media'])
240
- - Sistem butona tıklayıp fileChooser'ı yakalayacak ve dosyayı programatik olarak set edecek, file picker açılmayacak
241
- - Eğer selector verilmezse sayfadaki input[type=file]'a direkt set eder
242
- - value alanına {{IMAGE}} yaz
243
- - Her adım için 2+ fallback selector
244
- - Parametreler için placeholder kullan: {{CONTENT}} = metin, {{USERNAME}} = kullanıcı adı, {{URL}} = link, vs.
245
- - Hangi parametreleri kullandığını "parameters" alanında listele
246
- - action: "${actionName}"
247
- - platform: "${platform}", version: 1, lastUpdated: "${new Date().toISOString()}"
248
- - Gerekiyorsa "goto" action'ı ile belirli bir URL'e git (url alanını kullan)`;
249
-
250
- console.log(`[claude] "${taskDescription}" analiz ediliyor...\n`);
251
-
252
- let map: SelectorMap;
253
- try {
254
- map = await claude({
255
- prompt,
256
- jsonSchema: SELECTOR_MAP_SCHEMA,
257
- imagePath: screenshotPath,
258
- stream: false,
259
- effort: "low",
260
- });
261
- } catch (err: any) {
262
- console.error(`\n[claude] Hata: ${err.message}`);
263
- throw err;
264
- }
265
-
266
- if (!map || !map.steps) {
267
- throw new Error("Claude geçerli bir map döndürmedi");
268
- }
269
-
270
- map.platform = platform;
271
- map.action = actionName;
272
- map.description = taskDescription;
273
- map.lastUpdated = new Date().toISOString();
274
- saveMap(map);
275
- return map;
276
- }
277
-
278
- // ── Self-Heal: Kaldığın yerden devam et ──────────────────────
279
-
280
- const REMAINING_STEPS_SCHEMA = JSON.stringify({
281
- type: "object",
282
- properties: {
283
- analysis: { type: "string" },
284
- remainingSteps: {
285
- type: "array",
286
- items: {
287
- type: "object",
288
- properties: {
289
- action: { type: "string", enum: ["click", "type", "wait", "keypress", "goto", "upload"] },
290
- description: { type: "string" },
291
- selector: { type: "string" },
292
- value: { type: "string" },
293
- key: { type: "string" },
294
- url: { type: "string" },
295
- waitMs: { type: "number" },
296
- fallbackSelectors: { type: "array", items: { type: "string" } },
297
- },
298
- required: ["action", "description"],
299
- },
300
- },
301
- },
302
- required: ["analysis", "remainingSteps"],
303
- });
304
-
305
- /**
306
- * Bir adım başarısız olduğunda AI ekranı görür,
307
- * durumu analiz eder ve kalan adımları yeniden planlar.
308
- * Sadece selector düzeltmez - tamamen yeni akış çizebilir.
309
- */
310
- export async function healAndContinue(
311
- platform: string,
312
- failedStepIndex: number,
313
- originalSteps: SelectorStep[],
314
- screenshotPath: string,
315
- domSnapshot: string,
316
- errorMessage: string,
317
- postContent: string,
318
- effort = "low"
319
- ): Promise<SelectorStep[]> {
320
- const completedSteps = originalSteps.slice(0, failedStepIndex);
321
- const failedStep = originalSteps[failedStepIndex];
322
- const remainingOriginal = originalSteps.slice(failedStepIndex);
323
-
324
- const prompt = `Bir browser otomasyon script'i çalışırken hata oluştu. Ekranın screenshot'ını ve DOM'unu görüyorsun.
325
-
326
- Platform: ${platform}
327
- Amaç: "${postContent}" metnini post olarak paylaşmak
328
-
329
- Tamamlanan adımlar:
330
- ${completedSteps.map((s, i) => ` ${i + 1}. [${s.action}] ${s.description} ✓`).join("\n")}
331
-
332
- Başarısız olan adım (#${failedStepIndex + 1}):
333
- [${failedStep.action}] ${failedStep.description}
334
- Selector: ${failedStep.selector}
335
- Hata: ${errorMessage}
336
-
337
- Kalan orijinal adımlar:
338
- ${remainingOriginal.map((s, i) => ` ${failedStepIndex + i + 1}. [${s.action}] ${s.description} → ${s.selector}`).join("\n")}
339
-
340
- Sayfanın Accessibility Tree'si:
341
- ${domSnapshot}
342
-
343
- GÖREV: Ekranın mevcut durumunu analiz et. Amaca ulaşmak için kalan adımları yeniden planla.
344
- - Belki selector değişmiş → yeni selector bul
345
- - Belki ekstra bir adım gerekiyor (popup kapatma, scroll, vb.)
346
- - Belki farklı bir yol izlenmeli
347
- - {{CONTENT}} = post metni placeholder'ı
348
- - data-testid, aria-label, role tercih et, her adıma 2+ fallback selector ver
349
- - Dosya yükleme için "upload" action kullan. selector'a medya ekleme butonunu ver, value: {{IMAGE}}`;
350
-
351
- console.log(`\n[heal] AI ekranı analiz ediyor (effort: ${effort})...\n`);
352
-
353
- let result: any;
354
- try {
355
- result = await claude({
356
- prompt,
357
- jsonSchema: REMAINING_STEPS_SCHEMA,
358
- imagePath: screenshotPath,
359
- stream: false,
360
- effort,
361
- });
362
- } catch (err: any) {
363
- console.log(`\n[heal] AI hatası: ${err.message}`);
364
- return originalSteps.slice(failedStepIndex + 1); // başarısız step'i atla
365
- }
366
-
367
- // result çeşitli formatlarda gelebilir
368
- if (!result) {
369
- return originalSteps.slice(failedStepIndex + 1);
370
- }
371
-
372
- // structured_output içinde olabilir
373
- if (result.structured_output?.remainingSteps) {
374
- result = result.structured_output;
375
- }
376
-
377
- // remainingSteps yoksa steps dene
378
- const steps = result.remainingSteps || result.steps || result;
379
-
380
- if (!Array.isArray(steps)) {
381
- console.log(`\n[heal] Plan parse edilemedi, başarısız adım atlanıyor.`);
382
- return originalSteps.slice(failedStepIndex + 1);
383
- }
384
-
385
- console.log(`\n[heal] ${steps.length} yeni adım`);
386
-
387
- return steps;
388
- }
package/ai/planner.ts CHANGED
@@ -151,7 +151,7 @@ GÖREV: Bu komutu yerine getirmek için bir çalıştırma planı oluştur.
151
151
  Kurallar:
152
152
  - Mevcut map varsa "use_map" kullan, mapAction'a action adını yaz
153
153
  - Mevcut map yoksa "learn_new" kullan, AI otomatik öğrenecek
154
- - Mevcut map'in parametrelerini kontrol et. Komutta dosya/görsel/fotoğraf varsa ama map'te {{IMAGE}} parametresi yoksa, bu map yetersizdir - "learn_new" ile görsel destekli versiyonunu öğret
154
+ - Komutta fotoğraf/görsel varsa mevcut "post" map'ini kullan, IMAGE parametresini ekle. Sistem upload'ı otomatik handle eder - ayrı map öğretmeye gerek YOK
155
155
  - Parametreleri doldur ({{TWEET_URL}}, {{USERNAME}}, {{CONTENT}}, {{IMAGE}} vb.)
156
156
  - Belirli kişi/URL verilmemişse platformun keşif/öneri sayfalarını kullan
157
157
  - Tekrar eden işlemler için "repeat" kullan
package/ai/runner.ts CHANGED
@@ -1,101 +1,93 @@
1
1
  import { BrowserDriver } from "../platforms/browser/driver.js";
2
- import { loadMap, healAndContinue } from "./mapper.js";
3
- import { createPlan } from "./planner.js";
2
+ import { loadMap } from "./mapper.js";
4
3
  import { saveToHistory } from "../core/history.js";
5
4
  import type { Post } from "../core/types.js";
6
- import { join } from "node:path";
7
- import { PATHS } from "../core/config.js";
8
5
 
9
6
  const BROWSER_PLATFORMS: Record<string, string> = {
10
7
  x: "https://x.com",
11
8
  linkedin: "https://www.linkedin.com",
12
9
  };
13
10
 
11
+ /**
12
+ * Post at - map'i direkt çalıştır, AI çağırmadan
13
+ */
14
14
  export async function runCommand(platform: string, command: string): Promise<void> {
15
15
  const url = BROWSER_PLATFORMS[platform];
16
- if (!url) { console.log(`"${platform}" desteklenmiyor.`); return; }
16
+ if (!url) { console.log(`"${platform}" desteklenmiyor.`); process.exit(1); }
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() || "";
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();
28
+
29
+ // Map seç
30
+ const map = loadMap(platform, "post");
31
+ if (!map) {
32
+ console.log(`[${platform}] post map'i bulunamadı. "social-agent learn ${platform}" çalıştır.`);
33
+ process.exit(1);
34
+ }
17
35
 
18
- const plan = await createPlan(platform, command);
19
- console.log("\n[runner] Çalıştırılıyor...\n");
36
+ console.log(`[${platform}] Post: "${content.substring(0, 50)}${content.length > 50 ? "..." : ""}"`);
37
+ if (image) console.log(`[${platform}] Görsel: ${image}`);
20
38
 
21
- for (const [i, step] of plan.steps.entries()) {
22
- console.log(`━━━ ${i + 1}/${plan.steps.length}: ${step.description} ━━━`);
39
+ const driver = new BrowserDriver(platform, url);
40
+ await driver.launch();
23
41
 
24
- if (step.type === "learn_new") {
25
- const driver = new BrowserDriver(platform, url);
26
- await driver.learn(step.learnDescription || step.description, step.learnActionName || "custom");
27
- console.log(`[runner] Öğrenildi!\n`);
42
+ try {
43
+ // Ana sayfaya git
44
+ if (map.steps[0]?.action !== "goto") {
45
+ driver.navigate(url);
28
46
  }
29
47
 
30
- const mapAction = step.type === "use_map" ? step.mapAction! : step.learnActionName || "custom";
31
- const map = loadMap(platform, mapAction);
32
- if (!map) { console.log(`"${mapAction}" bulunamadı, atlanıyor.`); continue; }
33
-
34
- const repeatCount = step.repeat || 1;
35
-
36
- for (let r = 0; r < repeatCount; r++) {
37
- if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
38
-
39
- const params = { ...step.parameters };
40
- const content = params["{{CONTENT}}"] || params["CONTENT"] || "";
41
- const image = params["{{IMAGE}}"] || params["IMAGE"] || "";
42
-
43
- const driver = new BrowserDriver(platform, url);
44
- await driver.launch();
48
+ let steps = [...map.steps];
49
+
50
+ // Image varsa: gönder butonundan önce upload step ekle
51
+ if (image && !steps.some(s => s.action === "upload")) {
52
+ const sendIdx = steps.findLastIndex(s => s.action === "click");
53
+ if (sendIdx > -1) {
54
+ steps.splice(sendIdx, 0, {
55
+ action: "upload" as any,
56
+ description: "Fotoğraf yükle",
57
+ selector: "input[type='file']",
58
+ value: "{{IMAGE}}",
59
+ waitMs: 3000,
60
+ });
61
+ }
62
+ }
45
63
 
64
+ // Step'leri çalıştır
65
+ const errors: string[] = [];
66
+ for (let idx = 0; idx < steps.length; idx++) {
67
+ const s = steps[idx];
46
68
  try {
47
- // Ana sayfaya git (ilk step goto değilse)
48
- if (map.steps[0]?.action !== "goto") {
49
- driver.navigate(url);
50
- }
51
-
52
- let steps = [...map.steps];
53
- let healCount = 0;
54
-
55
- for (let idx = 0; idx < steps.length; idx++) {
56
- const s = steps[idx];
57
-
58
- try {
59
- console.log(` ${idx + 1}. ${s.description}`);
60
- driver.executeStep(s, content, image);
61
- } catch (err: any) {
62
- if (healCount >= 2) {
63
- console.log(` ✗ ${err.message} (atlanıyor)`);
64
- continue;
65
- }
66
- healCount++;
67
- console.log(` ✗ ${err.message}`);
68
- console.log(` → Düzeltiliyor...`);
69
-
70
- const ss = join(PATHS.screenshots, `${platform}_heal.png`);
71
- driver.takeScreenshot(ss);
72
- const snap = driver.getSnapshot();
73
-
74
- const fixed = await healAndContinue(
75
- platform, idx, steps, ss, snap,
76
- err.message, content || step.description, "medium"
77
- );
78
- steps = [...steps.slice(0, idx), ...fixed];
79
- idx--;
80
- }
81
- }
82
-
83
- console.log(` ✓ Tamamlandı`);
84
- saveToHistory({ platform, content: content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
69
+ console.log(` ${idx + 1}. ${s.description}`);
70
+ driver.executeStep(s, content, image);
85
71
  } catch (err: any) {
72
+ errors.push(`Step ${idx + 1} (${s.description}): ${err.message}`);
86
73
  console.log(` ✗ ${err.message}`);
87
- saveToHistory({ platform, content: content || step.description, action: mapAction, success: false, error: err.message });
88
- } finally {
89
- await driver.close();
90
74
  }
75
+ }
91
76
 
92
- if (r < repeatCount - 1) {
93
- await new Promise((res) => setTimeout(res, 2000 + Math.random() * 3000));
94
- }
77
+ if (errors.length === 0) {
78
+ console.log(`\n✓ ${platform} post gönderildi`);
79
+ saveToHistory({ platform, content, action: "post", success: true, image: image || undefined });
80
+ } else {
81
+ console.log(`\n⚠ ${errors.length} hata oluştu:`);
82
+ errors.forEach(e => console.log(` - ${e}`));
83
+ saveToHistory({ platform, content, action: "post", success: false, error: errors.join("; ") });
95
84
  }
96
- console.log("");
85
+ } catch (err: any) {
86
+ console.log(`\n✗ ${err.message}`);
87
+ saveToHistory({ platform, content, action: "post", success: false, error: err.message });
88
+ } finally {
89
+ await driver.close();
97
90
  }
98
91
 
99
- console.log("[runner] Tamamlandı.");
100
92
  process.exit(0);
101
93
  }
package/cli.ts CHANGED
@@ -189,17 +189,16 @@ switch (command) {
189
189
  }
190
190
 
191
191
  case "run": {
192
- const hasYes = args.includes("--yes") || args.includes("-y");
193
- const filteredArgs = args.filter(a => a !== "--yes" && a !== "-y");
192
+ const filteredArgs = args.filter(a => !a.startsWith("--"));
194
193
  const runPlatform = BROWSER_PLATFORMS[filteredArgs[0]] ? filteredArgs[0] : "x";
195
194
  const runCmd = BROWSER_PLATFORMS[filteredArgs[0]]
196
195
  ? filteredArgs.slice(1).join(" ")
197
196
  : filteredArgs.join(" ");
198
197
  if (!runCmd) {
199
- console.log('Kullanım: social-agent run <platform> "doğal dil komutu" [--yes]');
198
+ console.log('Kullanım: social-agent run <platform> "komut"');
200
199
  break;
201
200
  }
202
- await runCommand(runPlatform, runCmd, hasYes);
201
+ await runCommand(runPlatform, runCmd);
203
202
  break;
204
203
  }
205
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-agent-cli",
3
- "version": "3.1.1",
3
+ "version": "4.0.0",
4
4
  "description": "AI-powered social media agent - free APIs + browser automation with self-healing selectors",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { existsSync, mkdirSync, copyFileSync } from "node:fs";
5
5
  import { PATHS } from "../../core/config.js";
6
- import { loadMap, generateMap } from "../../ai/mapper.js";
6
+ import { loadMap } from "../../ai/mapper.js";
7
7
  import { pickChromeProfile, getSavedProfile } from "../../core/profiles.js";
8
8
  import type { SelectorMap } from "../../core/types.js";
9
9
 
@@ -105,27 +105,26 @@ export class BrowserDriver {
105
105
  }
106
106
 
107
107
  /**
108
- * Learn - ARIA snapshot ile map oluştur
108
+ * Learn - ARIA snapshot al ve dosyaya yaz (Claude Code okuyup map oluşturur)
109
109
  */
110
110
  async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
111
111
  await this.launch();
112
112
  this.cmd(`open "${this.homeUrl}"`, 30000);
113
113
  this.cmd("wait 3000");
114
114
 
115
- const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
115
+ const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn.png`);
116
116
  this.cmd(`screenshot "${screenshotPath}"`);
117
117
 
118
- // Compact interactive snapshot
119
118
  const snapshot = this.cmd("snapshot -i -c", 10000);
119
+ const snapshotPath = join(PATHS.screenshots, `${this.platform}_snapshot.txt`);
120
+ const { writeFileSync: writeFile } = await import("node:fs");
121
+ writeFile(snapshotPath, snapshot);
120
122
 
121
- console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
122
- console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
123
- console.log(`[${this.platform}] Claude analiz ediyor...`);
124
-
125
- const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
126
- console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
123
+ console.log(`Screenshot: ${screenshotPath}`);
124
+ console.log(`Snapshot: ${snapshotPath}`);
125
+ console.log(`\nBu dosyaları Claude Code'a ver, map oluştursun.`);
127
126
  process.exit(0);
128
- return map;
127
+ return {} as SelectorMap;
129
128
  }
130
129
 
131
130
  navigate(url: string): void {
@@ -176,9 +175,16 @@ export class BrowserDriver {
176
175
  case "upload": {
177
176
  const filePath = val || imagePath || "";
178
177
  if (!filePath || filePath.includes("{{")) break;
178
+ // Her zaman input[type=file] ile yükle - en güvenilir yol
179
+ try {
180
+ this.cmd(`upload "input[type='file']" "${filePath}"`, 10000);
181
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
182
+ return;
183
+ } catch {}
184
+ // Fallback: selector'larla dene
179
185
  const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
180
186
  for (const sel of sels) {
181
- try { this.cmd(`upload "${sel}" "${filePath}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
187
+ try { this.cmd(`upload "${sel}" "${filePath}"`, 10000); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
182
188
  }
183
189
  throw new Error(`Upload: ${filePath}`);
184
190
  }