social-agent-cli 1.0.0 → 1.0.2

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.
@@ -0,0 +1,47 @@
1
+ import { readFileSync, writeFileSync, existsSync, appendFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "../core/config.js";
4
+
5
+ const KNOWLEDGE_DIR = join(PATHS.root, "knowledge");
6
+
7
+ /**
8
+ * Platform bilgisini oku.
9
+ * knowledge/PLATFORM.md dosyasından gelir (kullanıcı tarafından yazılır).
10
+ * AI sadece okur, yazmaz.
11
+ */
12
+ export function getPlatformKnowledge(platform: string): string {
13
+ const mainPath = join(KNOWLEDGE_DIR, `${platform}.md`);
14
+ const learnedPath = join(KNOWLEDGE_DIR, `${platform}_learned.md`);
15
+
16
+ let knowledge = "";
17
+
18
+ // Ana bilgi dosyası (kullanıcı tarafından yazılır)
19
+ if (existsSync(mainPath)) {
20
+ knowledge += readFileSync(mainPath, "utf-8");
21
+ } else {
22
+ console.log(`[knowledge] ${platform}.md bulunamadı. knowledge/${platform}.md dosyasını oluştur.`);
23
+ }
24
+
25
+ // AI'ın işlem sırasında öğrendikleri
26
+ if (existsSync(learnedPath)) {
27
+ knowledge += "\n\n## AI Tarafından Öğrenilen Bilgiler\n" + readFileSync(learnedPath, "utf-8");
28
+ }
29
+
30
+ return knowledge;
31
+ }
32
+
33
+ /**
34
+ * AI işlem sırasında yeni bir şey öğrendiğinde bunu kaydeder.
35
+ * Ana knowledge dosyasına dokunmaz, ayrı _learned.md dosyasına yazar.
36
+ */
37
+ export function addLearnedKnowledge(platform: string, info: string): void {
38
+ const { mkdirSync } = require("node:fs");
39
+ mkdirSync(KNOWLEDGE_DIR, { recursive: true });
40
+
41
+ const learnedPath = join(KNOWLEDGE_DIR, `${platform}_learned.md`);
42
+ const timestamp = new Date().toISOString().split("T")[0];
43
+ const entry = `\n### ${timestamp}\n${info}\n`;
44
+
45
+ appendFileSync(learnedPath, entry);
46
+ console.log(`[knowledge] ${platform} yeni bilgi eklendi: ${info.substring(0, 80)}...`);
47
+ }
package/ai/mapper.ts ADDED
@@ -0,0 +1,348 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFileSync, writeFileSync, existsSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { PATHS } from "../core/config.js";
6
+ import type { SelectorMap } from "../core/types.js";
7
+
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"] },
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
+ if (stream) process.stdout.write(text);
90
+ });
91
+
92
+ proc.stderr.on("data", (chunk: Buffer) => {
93
+ stderr += chunk.toString();
94
+ });
95
+
96
+ proc.on("close", (code) => {
97
+ rmSync(tmpDir, { recursive: true, force: true });
98
+ if (stream) process.stdout.write("\n");
99
+
100
+ const output = stdout.trim();
101
+
102
+ if (jsonSchema) {
103
+ try {
104
+ const parsed = JSON.parse(output);
105
+
106
+ // structured_output alanı varsa ve dolu ise
107
+ if (parsed.structured_output && parsed.structured_output.steps) {
108
+ resolve(parsed.structured_output);
109
+ return;
110
+ }
111
+
112
+ // result alanında JSON string olabilir
113
+ if (parsed.result && typeof parsed.result === "string" && parsed.result.length > 2) {
114
+ try { resolve(JSON.parse(parsed.result)); return; } catch {}
115
+ }
116
+
117
+ // error_max_turns veya boş result durumunda - result'tan text çıkar
118
+ if (parsed.subtype === "error_max_turns" || !parsed.structured_output) {
119
+ // Claude tool kullanıp bitirmeden durmuş, sonucu elle çıkarmayı dene
120
+ reject(new Error(`Claude tamamlayamadı (${parsed.subtype || "unknown"}). max-turns artırılabilir.`));
121
+ return;
122
+ }
123
+
124
+ // Mesaj dizisi formatı
125
+ if (Array.isArray(parsed)) {
126
+ const last = parsed[parsed.length - 1];
127
+ const text = typeof last.content === "string"
128
+ ? last.content
129
+ : last.content?.map((c: any) => c.text || "").join("") || "";
130
+ resolve(JSON.parse(text));
131
+ return;
132
+ }
133
+
134
+ // Direkt obje
135
+ if (parsed.steps || parsed.selector) {
136
+ resolve(parsed);
137
+ return;
138
+ }
139
+
140
+ resolve(parsed);
141
+ } catch (parseErr: any) {
142
+ // Regex fallback
143
+ const match = output.match(/\{[\s\S]*\}/);
144
+ if (match) {
145
+ try { resolve(JSON.parse(match[0])); return; } catch {}
146
+ }
147
+ reject(new Error("Claude'dan geçerli JSON alınamadı"));
148
+ }
149
+ return;
150
+ }
151
+
152
+ if (code !== 0 && !output) {
153
+ reject(new Error(`Claude hata (code ${code}): ${stderr}`));
154
+ return;
155
+ }
156
+
157
+ resolve(output);
158
+ });
159
+
160
+ proc.on("error", (err) => {
161
+ rmSync(tmpDir, { recursive: true, force: true });
162
+ reject(err);
163
+ });
164
+
165
+ setTimeout(() => {
166
+ proc.kill();
167
+ reject(new Error("Claude timeout (180s)"));
168
+ }, 180000);
169
+ });
170
+ }
171
+
172
+ // ── Map CRUD ─────────────────────────────────────────────────
173
+
174
+ export function loadMap(platform: string, action = "post"): SelectorMap | null {
175
+ const mapPath = join(PATHS.maps, `${platform}_${action}.json`);
176
+ if (!existsSync(mapPath)) return null;
177
+ return JSON.parse(readFileSync(mapPath, "utf-8"));
178
+ }
179
+
180
+ export function saveMap(map: SelectorMap): void {
181
+ const action = map.action || "post";
182
+ const mapPath = join(PATHS.maps, `${map.platform}_${action}.json`);
183
+ writeFileSync(mapPath, JSON.stringify(map, null, 2));
184
+ }
185
+
186
+ export function listMaps(platform?: string): SelectorMap[] {
187
+ const { readdirSync } = require("node:fs");
188
+ const maps: SelectorMap[] = [];
189
+ try {
190
+ for (const file of readdirSync(PATHS.maps)) {
191
+ if (!file.endsWith(".json")) continue;
192
+ if (platform && !file.startsWith(platform + "_")) continue;
193
+ const map = JSON.parse(readFileSync(join(PATHS.maps, file), "utf-8"));
194
+ maps.push(map);
195
+ }
196
+ } catch {}
197
+ return maps;
198
+ }
199
+
200
+ // ── Map Oluştur ──────────────────────────────────────────────
201
+
202
+ export async function generateMap(
203
+ platform: string,
204
+ screenshotPath: string,
205
+ domSnapshot: string,
206
+ taskDescription = "yeni bir metin postu at",
207
+ actionName = "post"
208
+ ): Promise<SelectorMap> {
209
+ const prompt = `Sen bir browser otomasyon uzmanısın. ${platform} platformunda şu görevi yerine getirmek için adımları belirle:
210
+
211
+ GÖREV: "${taskDescription}"
212
+
213
+ DOM:
214
+ ${domSnapshot.substring(0, 50000)}
215
+
216
+ Kurallar:
217
+ - data-testid, aria-label, role, id tercih et
218
+ - Her adım için 2+ fallback selector
219
+ - Parametreler için placeholder kullan: {{CONTENT}} = metin, {{USERNAME}} = kullanıcı adı, {{URL}} = link, vs.
220
+ - Hangi parametreleri kullandığını "parameters" alanında listele
221
+ - action: "${actionName}"
222
+ - platform: "${platform}", version: 1, lastUpdated: "${new Date().toISOString()}"
223
+ - Gerekiyorsa "goto" action'ı ile belirli bir URL'e git (url alanını kullan)`;
224
+
225
+ console.log(`[claude] "${taskDescription}" analiz ediliyor...\n`);
226
+
227
+ let map: SelectorMap;
228
+ try {
229
+ map = await claude({
230
+ prompt,
231
+ jsonSchema: SELECTOR_MAP_SCHEMA,
232
+ imagePath: screenshotPath,
233
+ stream: true,
234
+ effort: "low",
235
+ });
236
+ } catch (err: any) {
237
+ console.error(`\n[claude] Hata: ${err.message}`);
238
+ throw err;
239
+ }
240
+
241
+ if (!map || !map.steps) {
242
+ throw new Error("Claude geçerli bir map döndürmedi");
243
+ }
244
+
245
+ map.platform = platform;
246
+ map.action = actionName;
247
+ map.description = taskDescription;
248
+ map.lastUpdated = new Date().toISOString();
249
+ saveMap(map);
250
+ return map;
251
+ }
252
+
253
+ // ── Self-Heal: Kaldığın yerden devam et ──────────────────────
254
+
255
+ const REMAINING_STEPS_SCHEMA = JSON.stringify({
256
+ type: "object",
257
+ properties: {
258
+ analysis: { type: "string" },
259
+ remainingSteps: {
260
+ type: "array",
261
+ items: {
262
+ type: "object",
263
+ properties: {
264
+ action: { type: "string", enum: ["click", "type", "wait", "keypress", "goto"] },
265
+ description: { type: "string" },
266
+ selector: { type: "string" },
267
+ value: { type: "string" },
268
+ key: { type: "string" },
269
+ url: { type: "string" },
270
+ waitMs: { type: "number" },
271
+ fallbackSelectors: { type: "array", items: { type: "string" } },
272
+ },
273
+ required: ["action", "description"],
274
+ },
275
+ },
276
+ },
277
+ required: ["analysis", "remainingSteps"],
278
+ });
279
+
280
+ /**
281
+ * Bir adım başarısız olduğunda AI ekranı görür,
282
+ * durumu analiz eder ve kalan adımları yeniden planlar.
283
+ * Sadece selector düzeltmez - tamamen yeni akış çizebilir.
284
+ */
285
+ export async function healAndContinue(
286
+ platform: string,
287
+ failedStepIndex: number,
288
+ originalSteps: SelectorStep[],
289
+ screenshotPath: string,
290
+ domSnapshot: string,
291
+ errorMessage: string,
292
+ postContent: string,
293
+ effort = "low"
294
+ ): Promise<SelectorStep[]> {
295
+ const completedSteps = originalSteps.slice(0, failedStepIndex);
296
+ const failedStep = originalSteps[failedStepIndex];
297
+ const remainingOriginal = originalSteps.slice(failedStepIndex);
298
+
299
+ const prompt = `Bir browser otomasyon script'i çalışırken hata oluştu. Ekranın screenshot'ını ve DOM'unu görüyorsun.
300
+
301
+ Platform: ${platform}
302
+ Amaç: "${postContent}" metnini post olarak paylaşmak
303
+
304
+ Tamamlanan adımlar:
305
+ ${completedSteps.map((s, i) => ` ${i + 1}. [${s.action}] ${s.description} ✓`).join("\n")}
306
+
307
+ Başarısız olan adım (#${failedStepIndex + 1}):
308
+ [${failedStep.action}] ${failedStep.description}
309
+ Selector: ${failedStep.selector}
310
+ Hata: ${errorMessage}
311
+
312
+ Kalan orijinal adımlar:
313
+ ${remainingOriginal.map((s, i) => ` ${failedStepIndex + i + 1}. [${s.action}] ${s.description} → ${s.selector}`).join("\n")}
314
+
315
+ Güncel DOM:
316
+ ${domSnapshot.substring(0, 50000)}
317
+
318
+ GÖREV: Ekranın mevcut durumunu analiz et. Amaca ulaşmak için kalan adımları yeniden planla.
319
+ - Belki selector değişmiş → yeni selector bul
320
+ - Belki ekstra bir adım gerekiyor (popup kapatma, scroll, vb.)
321
+ - Belki farklı bir yol izlenmeli
322
+ - {{CONTENT}} = post metni placeholder'ı
323
+ - data-testid, aria-label, role tercih et, her adıma 2+ fallback selector ver`;
324
+
325
+ console.log(`\n[heal] AI ekranı analiz ediyor (effort: ${effort})...\n`);
326
+
327
+ const result = await claude({
328
+ prompt,
329
+ jsonSchema: REMAINING_STEPS_SCHEMA,
330
+ imagePath: screenshotPath,
331
+ stream: true,
332
+ effort,
333
+ });
334
+
335
+ console.log(`\n[heal] Analiz: ${result.analysis}`);
336
+ console.log(`[heal] ${result.remainingSteps.length} yeni adım planlandı`);
337
+
338
+ // Map'i güncelle: tamamlanan adımlar + yeni kalan adımlar
339
+ const currentMap = loadMap(platform);
340
+ if (currentMap) {
341
+ currentMap.steps = [...completedSteps, ...result.remainingSteps];
342
+ currentMap.version++;
343
+ currentMap.lastUpdated = new Date().toISOString();
344
+ saveMap(currentMap);
345
+ }
346
+
347
+ return result.remainingSteps;
348
+ }
package/ai/planner.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "../core/config.js";
4
+ import type { SelectorMap } from "../core/types.js";
5
+ import { getPlatformKnowledge } from "./knowledge.js";
6
+ export { addLearnedKnowledge } from "./knowledge.js";
7
+
8
+ // claude fonksiyonunu mapper'dan import etmek yerine aynı şekilde çağıracağız
9
+ // (mapper'daki claude private, export etmemiz lazım)
10
+ // Bunun yerine planner kendi claude wrapper'ını kullanacak
11
+ import { spawn } from "node:child_process";
12
+ import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+
15
+ // ── Claude wrapper (planner için) ────────────────────────────
16
+
17
+ function claudeJSON(prompt: string, schema: string): Promise<any> {
18
+ const tmpDir = mkdtempSync(join(tmpdir(), "planner-"));
19
+ const promptFile = join(tmpDir, "prompt.txt");
20
+ writeFileSync(promptFile, prompt);
21
+
22
+ const cmd = `cat '${promptFile}' | claude -p --model opus --effort low --max-turns 3 --tools "" --output-format json --json-schema '${schema}'`;
23
+
24
+ return new Promise((resolve, reject) => {
25
+ const proc = spawn("bash", ["-c", cmd], { stdio: ["pipe", "pipe", "pipe"] });
26
+ let stdout = "";
27
+
28
+ proc.stdout.on("data", (chunk: Buffer) => {
29
+ const text = chunk.toString();
30
+ stdout += text;
31
+ process.stdout.write(text);
32
+ });
33
+
34
+ proc.on("close", () => {
35
+ rmSync(tmpDir, { recursive: true, force: true });
36
+ process.stdout.write("\n");
37
+ try {
38
+ const parsed = JSON.parse(stdout.trim());
39
+ if (parsed.structured_output) { resolve(parsed.structured_output); return; }
40
+ if (parsed.result && parsed.result.length > 2) { try { resolve(JSON.parse(parsed.result)); return; } catch {} }
41
+ if (parsed.steps || parsed.plan) { resolve(parsed); return; }
42
+ resolve(parsed);
43
+ } catch {
44
+ const match = stdout.match(/\{[\s\S]*\}/);
45
+ if (match) { try { resolve(JSON.parse(match[0])); return; } catch {} }
46
+ reject(new Error("Planner JSON parse hatası"));
47
+ }
48
+ });
49
+
50
+ proc.on("error", (err) => { rmSync(tmpDir, { recursive: true, force: true }); reject(err); });
51
+ setTimeout(() => { proc.kill(); reject(new Error("Planner timeout")); }, 60000);
52
+ });
53
+ }
54
+
55
+ // ── Mevcut map'leri listele ──────────────────────────────────
56
+
57
+ export function listAllMaps(platform?: string): SelectorMap[] {
58
+ const maps: SelectorMap[] = [];
59
+ try {
60
+ for (const file of readdirSync(PATHS.maps)) {
61
+ if (!file.endsWith(".json")) continue;
62
+ if (platform && !file.startsWith(platform + "_")) continue;
63
+ const map = JSON.parse(readFileSync(join(PATHS.maps, file), "utf-8"));
64
+ maps.push(map);
65
+ }
66
+ } catch {}
67
+ return maps;
68
+ }
69
+
70
+ // ── Plan tipi ────────────────────────────────────────────────
71
+
72
+ export interface ExecutionPlan {
73
+ analysis: string;
74
+ steps: PlanStep[];
75
+ }
76
+
77
+ export interface PlanStep {
78
+ type: "use_map" | "learn_new";
79
+ mapAction?: string; // mevcut map'in action adı (use_map için)
80
+ learnDescription?: string; // öğrenilecek görev açıklaması (learn_new için)
81
+ learnActionName?: string; // oluşturulacak map adı
82
+ parameters: Record<string, string>; // {{TWEET_URL}}: "...", {{USERNAME}}: "..."
83
+ repeat?: number; // kaç kez tekrarla (beğeni gibi loop işlemler)
84
+ description: string; // adımın insan-okunur açıklaması
85
+ }
86
+
87
+ const PLAN_SCHEMA = JSON.stringify({
88
+ type: "object",
89
+ properties: {
90
+ analysis: { type: "string" },
91
+ steps: {
92
+ type: "array",
93
+ items: {
94
+ type: "object",
95
+ properties: {
96
+ type: { type: "string", enum: ["use_map", "learn_new"] },
97
+ mapAction: { type: "string" },
98
+ learnDescription: { type: "string" },
99
+ learnActionName: { type: "string" },
100
+ parameters: { type: "object" },
101
+ repeat: { type: "number" },
102
+ description: { type: "string" },
103
+ },
104
+ required: ["type", "description", "parameters"],
105
+ },
106
+ },
107
+ },
108
+ required: ["analysis", "steps"],
109
+ });
110
+
111
+ // ── Ana planlama fonksiyonu ──────────────────────────────────
112
+
113
+ export async function createPlan(
114
+ platform: string,
115
+ naturalLanguageCommand: string
116
+ ): Promise<ExecutionPlan> {
117
+ const maps = listAllMaps(platform);
118
+
119
+ const mapsDescription = maps.length
120
+ ? maps.map((m) => {
121
+ const params = m.parameters?.join(", ") || "yok";
122
+ return `- "${m.action}" → ${m.description || m.action} [parametreler: ${params}] (${m.steps.length} adım)`;
123
+ }).join("\n")
124
+ : "Henüz öğrenilmiş aksiyon yok.";
125
+
126
+ // Platform hakkında bilgi (kullanıcı tarafından yazılmış + AI öğrenmeleri)
127
+ const knowledge = getPlatformKnowledge(platform);
128
+
129
+ const prompt = `Sen bir sosyal medya otomasyon planlayıcısısın.
130
+
131
+ Platform: ${platform}
132
+ Platform bilgisi:
133
+ ${knowledge.substring(0, 5000)}
134
+
135
+ Kullanıcı komutu: "${naturalLanguageCommand}"
136
+
137
+ Mevcut öğrenilmiş aksiyonlar (map'ler):
138
+ ${mapsDescription}
139
+
140
+ GÖREV: Bu komutu yerine getirmek için bir çalıştırma planı oluştur.
141
+
142
+ Kurallar:
143
+ - Mevcut map varsa "use_map" kullan, mapAction'a action adını yaz
144
+ - Mevcut map yoksa "learn_new" kullan, AI otomatik öğrenecek
145
+ - Parametreleri doldur ({{TWEET_URL}}, {{USERNAME}}, {{CONTENT}} vb.)
146
+ - Belirli kişi/URL verilmemişse platformun keşif/öneri sayfalarını kullan (ör: LinkedIn'de ağım sayfası)
147
+ - Tekrar eden işlemler için "repeat" kullan
148
+ - Adımları doğru sırada planla
149
+ - Kısa ve net analiz yaz`;
150
+
151
+ console.log(`[planner] "${naturalLanguageCommand}" planlanıyor...\n`);
152
+
153
+ let plan: ExecutionPlan;
154
+ try {
155
+ plan = await claudeJSON(prompt, PLAN_SCHEMA);
156
+ } catch (err: any) {
157
+ console.error(`\n[planner] Hata: ${err.message}`);
158
+ throw err;
159
+ }
160
+
161
+ if (!plan || !plan.steps) {
162
+ throw new Error("Planner geçerli bir plan oluşturamadı");
163
+ }
164
+
165
+ console.log(`\n[planner] Analiz: ${plan.analysis}`);
166
+ console.log(`[planner] ${plan.steps.length} adımlı plan oluşturuldu:\n`);
167
+
168
+ for (const [i, step] of plan.steps.entries()) {
169
+ const icon = step.type === "use_map" ? "▶" : "📚";
170
+ const repeat = step.repeat && step.repeat > 1 ? ` (x${step.repeat})` : "";
171
+ console.log(` ${i + 1}. ${icon} ${step.description}${repeat}`);
172
+ if (step.type === "use_map") {
173
+ console.log(` map: ${step.mapAction} | params: ${JSON.stringify(step.parameters)}`);
174
+ } else {
175
+ console.log(` öğrenilecek: "${step.learnDescription}" → ${step.learnActionName}`);
176
+ }
177
+ }
178
+
179
+ return plan;
180
+ }