Installs into .claude/skills of the current project.
Are you the author of Gecmis Import?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/respected0-gecmis-import)
---
name: gecmis-import
description: Eski sohbetleri (ChatGPT, Claude) daily/ altına aktarır. "geçmiş import", "chatgpt geçmişi" dendiğinde kullan.
---
# Geçmiş İçe Aktarımı
Eski sohbet arşivini beynin normal hattına sokar. Dışa aktarım dosyası yerelde okunur,
sohbetler aylara bölünür ve `daily/import-YYYY-MM-part-NNN.md` dosyalarına yazılır.
Akşam derleyicisi bu dosyaların içeriğini normal günlük loglar gibi okur ve özetleme için
kullanıcının seçili yerel AI CLI oturumu üzerinden modele gönderir. Dışa aktarım dosyasının
kendisi yüklenmez ve veriler başka hiçbir yere gönderilmez.
Özel veya hassas sohbetler, kullanıcı tarih ya da anahtar kelime filtresiyle hariç tutmadıkça
içe aktarıma ve sonraki derlemeye dahil olur.
## Zorunlu onay kapısı
Agent aşağıdaki adımları sırayla tamamlamak ZORUNDADIR. Kullanıcının izni olmadan dışa aktarım
dosyasını açmak, ayrıştırmak veya `daily/` altına dosya yazmak YASAKTIR.
1. Agent veri akışını açıkça anlatmak ZORUNDADIR: dışa aktarım yerelde okunur, seçilen sohbetler
yerel `daily/` dosyalarına yazılır, akşam derleyicisi bu dosyaların içeriğini özetleme için
kullanıcının seçili yerel AI CLI oturumu üzerinden modele gönderir. Bunun dışında hiçbir yere
yükleme veya gönderim yapılmaz.
2. Agent özel ve hassas sohbetlerin varsayılan olarak dahil olacağını söylemek ZORUNDADIR.
Kullanıcıya başlangıç ve bitiş tarihi sınırı ile bir veya daha fazla anahtar kelimeden oluşan
hariç tutma listesi sunmak ZORUNDADIR. Eşleşme büyük küçük harf duyarsızdır ve eşleşen
konuşmanın tamamı atlanır.
3. Agent dışa aktarım dosyasının yolunu kullanıcıdan istemek ve yolu yalnızca metin olarak almak
ZORUNDADIR. Yolu tahmin edemez veya dosyayı henüz açamaz. Ardından yalnızca yerel, salt okunur
önizleme taraması için açık izin istemek ve yanıtı beklemek ZORUNDADIR. Açık izin yoksa
dosyayı açamaz. İzin verilirse seçilen filtrelerle yerel önizleme planını üretir. Bu aşama
hiçbir çıktı dosyası yazmaz.
4. Agent önizlemedeki içe aktarılacak sohbet sayısını, tarih aralığını ve tarih, anahtar kelime,
boş içerik veya geçersiz kayıt nedeniyle atlanacak sayıları kullanıcıya göstermek ZORUNDADIR.
5. Agent kullanıcıya dürüst vazgeçme seçeneklerini de söylemek ZORUNDADIR: içe aktarılan
dosyaları hiç derlemeyebilir veya akşam derlemesinden önce istemediği aylık parça dosyasını
silebilir. Kullanıcı vazgeçerse işlem durur.
6. Agent seçilen tarih aralığını ve anahtar kelime listesini tekrar yazıp şu açık onayı istemek
ZORUNDADIR: `Evet, bu önizleme ve filtrelerle içe aktar.` Kullanıcı açıkça evet demedikçe
içe aktarım ve yazma işlemi başlatılamaz. Belirsiz yanıt onay sayılmaz.
Bu onay talimatı agent içindir. Betiğe otomatik onay, etkileşimli onay veya onayı atlayan bir
bayrak ekleme.
## Adımlar
1. Onay kapısını tamamla ve kullanıcıdan aldığın dışa aktarım yolunu kullan. Tahmin etme.
2. Dosya türünü doğrula.
- ChatGPT: dışa aktarım zip'i içindeki `conversations.json`. Zip açılmadıysa kullanıcıdan
açmasını iste veya açık izinle yerelde `unzip` kullan.
- Claude: dışa aktarım içindeki `conversations.json`.
- Gemini: Google Takeout içindeki `My Activity/Gemini Apps/MyActivity.json`.
3. Dosya boyutunu mevcut yerel dosya aracıyla ölç. 50 MB üstünde son 12 takvim ayını
otomatik sınır olarak uygula; daha dar kullanıcı sınırı önceliklidir. Önizlemede bunu söyle.
4. Kurulu native CLI'de harici sohbet export importer komutu yoktur. Aşağıdaki kayıpsız
çözümleme algoritmasını referans alarak mevcut yerel JSON/dosya araçlarınla aynı önizleme,
tarih/anahtar kelime filtreleri ve parça planını uygula. Önizleme hiçbir çıktı yazmaz.
Bu araçlar büyük JSON'u eksiksiz işleyemiyorsa dur ve eksik yeteneği bildir; kurulu motorun
bunu yaptığı iddiasında bulunma veya kullanıcıdan Python kurmasını isteme.
5. Tarihler dahil günlerdir; her hariç tutma anahtar kelimesini ayrı filtre olarak uygula.
Örnek önizleme isteği: conversations.json, başlangıç 2025-01-01, bitiş 2025-12-31,
hariç kelimeler “sağlık” ve “özel proje”. İkinci açık onaydan sonra aynı planı VaultRoot/daily
altında uygula. Hedef dosya mevcutsa exclusive-create sözleşmesiyle yazmayı reddet.
6. İşlemin özetini kullanıcıya eksiksiz aktar. Mevcut bir hedef dosya varsa işlem üzerine
yazmayı reddeder. Bu durumda kullanıcıdan açıkça yeni bir çıktı klasörü seçmesini veya mevcut
dosyaları kendisinin ele almasını iste. Dosyayı silme, birleştirme ya da üzerine yazma.
7. Derleme planını aşağıdaki `Sonra ne olur` bölümüne göre anlat.
## ChatGPT çözümleme referansı
Aşağıdaki Python kodu algoritma referansıdır; kurulu ürünün çalıştırma komutu veya
Python kurulum gereksinimi değildir. Aynı veri kurallarını mevcut yerel araçlarında uygula.
ChatGPT dışa aktarımında her sohbet bir `mapping` ağacıdır. Dallanma varsa bütün erişilebilir
düğümler kaynak sırasına göre bir kez yazılır. Sistem ve araç mesajları günlük dosyasına yazılmaz.
Mesaj ve konuşma metinleri kısaltılmaz. Aylık sınır dolduğunda konuşmalar atılmak yerine yeni bir
parça dosyasına geçilir. Tek bir konuşma sınırdan büyükse içerik kaybolmasın diye o parça sınırı
aşabilir.
```python
#!/usr/bin/env python3
"""ChatGPT conversations.json arşivini yerel günlük parça dosyalarına dönüştür."""
import argparse
import datetime as dt
import json
import os
import sys
MAX_EXPORT_BYTES = 50 * 1024 * 1024
RECENT_MONTHS = 12
MAX_FILE_CHARS = 200000
def iso_date(value):
try:
return dt.datetime.strptime(value, "%Y-%m-%d").date()
except ValueError as exc:
raise argparse.ArgumentTypeError("tarih YYYY-MM-DD olmalı: %s" % value) from exc
def arguments():
parser = argparse.ArgumentParser(description="ChatGPT geçmişini yerelde içe aktar")
parser.add_argument("source", help="conversations.json dosyası veya onu içeren klasör")
parser.add_argument("--preview", action="store_true", help="yalnızca önizle, dosya yazma")
parser.add_argument("--start", type=iso_date, help="dahil başlangıç tarihi, YYYY-MM-DD")
parser.add_argument("--end", type=iso_date, help="dahil bitiş tarihi, YYYY-MM-DD")
parser.add_argument(
"--exclude-keyword",
action="append",
default=[],
help="eşleşen sohbeti atla, birden çok kez kullanılabilir",
)
parser.add_argument("--out-dir", default="daily", help="çıktı klasörü, varsayılan daily")
args = parser.parse_args()
if args.start and args.end and args.start > args.end:
parser.error("başlangıç tarihi bitiş tarihinden sonra olamaz")
return args
def load_export(path):
if os.path.isdir(path):
path = os.path.join(path, "conversations.json")
with open(path, "r", encoding="utf-8") as handle:
data = json.load(handle)
if isinstance(data, dict):
data = data.get("conversations", [])
if not isinstance(data, list):
raise ValueError("dışa aktarım bir sohbet listesi değil")
return path, data
def content_parts(message):
if not isinstance(message, dict):
return []
content = message.get("content") or {}
if content.get("content_type") not in ("text", "multimodal_text", None):
return []
texts = []
for part in content.get("parts") or []:
if isinstance(part, str) and part.strip():
texts.append(part)
elif isinstance(part, dict) and isinstance(part.get("text"), str):
if part["text"].strip():
texts.append(part["text"])
return texts
def message_text(message):
if not isinstance(message, dict):
return None, None
role = (message.get("author") or {}).get("role")
if role not in ("user", "assistant"):
return None, None
metadata = message.get("metadata") or {}
if metadata.get("is_visually_hidden_from_conversation"):
return None, None
parts = content_parts(message)
if not parts:
return None, None
return role, "\n".join(parts)
def walk_nodes(mapping):
roots = [
node_id
for node_id, node in mapping.items()
if isinstance(node, dict) and not node.get("parent")
]
seeds = roots + [node_id for node_id in mapping if node_id not in roots]
seen = set()
for seed in seeds:
stack = [seed]
while stack:
node_id = stack.pop()
if node_id in seen:
continue
seen.add(node_id)
node = mapping.get(node_id)
if not isinstance(node, dict):
continue
yield node
children = node.get("children") or []
for child in reversed(children):
stack.append(child)
def conversation_turns(mapping):
turns = []
for node in walk_nodes(mapping):
role, text = message_text(node.get("message"))
if role:
turns.append((role, text))
return turns
def conversation_time(conversation, mapping):
stamp = conversation.get("create_time") or conversation.get("update_time")
if isinstance(stamp, (int, float)) and stamp > 0:
return float(stamp)
for node in walk_nodes(mapping):
message = node.get("message") or {}
stamp = message.get("create_time")
if isinstance(stamp, (int, float)) and stamp > 0:
return float(stamp)
return None
def searchable_text(conversation, mapping):
values = [str(conversation.get("title") or "")]
for node in walk_nodes(mapping):
values.extend(content_parts(node.get("message")))
return "\n".join(values).casefold()
def recent_cutoff(today):
month_number = today.year * 12 + today.month - 1 - (RECENT_MONTHS - 1)
year, month_index = divmod(month_number, 12)
return dt.date(year, month_index + 1, 1)
def select_conversations(conversations, size, start, end, keywords):
stats = {
"source": len(conversations),
"invalid": 0,
"date": 0,
"keyword": 0,
"empty": 0,
}
automatic_start = None
if size > MAX_EXPORT_BYTES:
automatic_start = recent_cutoff(dt.datetime.now(dt.timezone.utc).date())
effective_start = start
if automatic_start and (effective_start is None or automatic_start > effective_start):
effective_start = automatic_start
folded_keywords = [word.casefold() for word in keywords if word.strip()]
selected = []
for conversation in conversations:
if not isinstance(conversation, dict):
stats["invalid"] += 1
continue
mapping = conversation.get("mapping") or {}
if not isinstance(mapping, dict):
stats["invalid"] += 1
continue
stamp = conversation_time(conversation, mapping)
if stamp is None:
stats["invalid"] += 1
continue
moment = dt.datetime.fromtimestamp(stamp, dt.timezone.utc)
day = moment.date()
if (effective_start and day < effective_start) or (end and day > end):
stats["date"] += 1
continue
haystack = searchable_text(conversation, mapping)
if any(word in haystack for word in folded_keywords):
stats["keyword"] += 1
continue
turns = conversation_turns(mapping)
if not turns:
stats["empty"] += 1
continue
selected.append(
{
"title": str(conversation.get("title") or "başlık yok"),
"moment": moment,
"turns": turns,
}
)
selected.sort(key=lambda item: item["moment"])
return selected, stats, automatic_start, effective_start
def conversation_block(record):
moment = record["moment"]
title = record["title"].replace("\r", " ").replace("\n", " ").strip()
heading = "### Oturum (%s UTC) ChatGPT: %s\n\n" % (
moment.strftime("%Y-%m-%d %H:%M"),
title or "başlık yok",
)
lines = []
for role, text in record["turns"]:
label = "**User:**" if role == "user" else "**Assistant:**"
lines.append("%s %s\n" % (label, text))
return heading + "\n".join(lines)
def part_header(month, part_number):
return (
"# Günlük Log: %s (içe aktarım, parça %03d)\n\n"
"Kaynak: ChatGPT dışa aktarımı. Bu dosya makine tarafından yazıldı.\n\n"
"## Oturumlar\n\n" % (month, part_number)
)
def build_parts(selected, out_dir):
months = {}
for record in selected:
month = record["moment"].strftime("%Y-%m")
months.setdefault(month, []).append(conversation_block(record))
plans = []
for month in sorted(months):
part_number = 1
header = part_header(month, part_number)
blocks = []
total = len(header)
for block in months[month]:
separator = "" if not blocks else "\n"
if blocks and total + len(separator) + len(block) > MAX_FILE_CHARS:
text = header + "\n".join(blocks)
name = "import-%s-part-%03d.md" % (month, part_number)
plans.append((os.path.join(out_dir, name), text, len(blocks)))
part_number += 1
header = part_header(month, part_number)
blocks = []
total = len(header)
separator = ""
blocks.append(block)
total += len(separator) + len(block)
if blocks:
text = header + "\n".join(blocks)
name = "import-%s-part-%03d.md" % (month, part_number)
plans.append((os.path.join(out_dir, name), text, len(blocks)))
return plans
def write_exclusive(plans):
claimed = []
try:
for path, text, count in plans:
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
claimed.append([path, descriptor, text, count])
except OSError:
for path, descriptor, _text, _count in claimed:
os.close(descriptor)
os.unlink(path)
raise
try:
for item in claimed:
with os.fdopen(item[1], "w", encoding="utf-8", newline="\n") as handle:
handle.write(item[2])
item[1] = None
except OSError:
for path, descriptor, _text, _count in claimed:
if descriptor is not None:
try:
os.close(descriptor)
except OSError:
pass
try:
os.unlink(path)
except FileNotFoundError:
pass
raise
def print_selection(selected, stats, automatic_start, effective_start, preview):
skipped = stats["invalid"] + stats["date"] + stats["keyword"] + stats["empty"]
print("ÖNİZLEME" if preview else "İÇE AKTARIM")
print("kaynak sohbet: %d" % stats["source"])
print("yazılacak sohbet: %d" % len(selected))
if selected:
print(
"tarih aralığı: %s .. %s"
% (selected[0]["moment"].date(), selected[-1]["moment"].date())
)
else:
print("tarih aralığı: yok")
print(
"atlanan sohbet: %d (tarih: %d, anahtar kelime: %d, boş: %d, geçersiz: %d)"
% (skipped, stats["date"], stats["keyword"], stats["empty"], stats["invalid"])
)
if automatic_start:
print("50 MB sınırı: son 12 takvim ayı, otomatik başlangıç %s" % automatic_start)
if effective_start:
print("etkin başlangıç tarihi: %s" % effective_start)
def main():
args = arguments()
try:
path, conversations = load_export(args.source)
size = os.path.getsize(path)
selected, stats, automatic_start, effective_start = select_conversations(
conversations, size, args.start, args.end, args.exclude_keyword
)
print_selection(selected, stats, automatic_start, effective_start, args.preview)
if args.preview:
print("dosya yazılmadı")
return 0
plans = build_parts(selected, args.out_dir)
if not plans:
print("yazılan dosya: 0")
return 0
os.makedirs(args.out_dir, exist_ok=True)
try:
write_exclusive(plans)
except FileExistsError as exc:
print(
"HATA: mevcut içe aktarım dosyasının üzerine yazılmadı: %s" % exc.filename,
file=sys.stderr,
)
return 3
print("yazılan dosya: %d" % len(plans))
print("yazılan sohbet: %d, atlanan sohbet: %d" % (
sum(count for _path, _text, count in plans),
stats["invalid"] + stats["date"] + stats["keyword"] + stats["empty"],
))
for output, text, count in plans:
print(" %s %d sohbet %d karakter" % (output, count, len(text)))
return 0
except (OSError, ValueError, json.JSONDecodeError) as exc:
print("HATA: %s" % exc, file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())
```
## Claude çözümleme referansı
Claude dışa aktarımının şeması düzdür. Aynı onay kapısını, `--preview`, tarih ve anahtar kelime
filtrelerini, kayıpsız parça üretimini ve `write_exclusive` yazımını kullan. ChatGPT'e özgü
`conversation_turns` yerine aşağıdaki çözümleyiciyi çağır. Sistem kayıtlarını yazma.
```python
def claude_turns(conversation):
turns = []
for message in conversation.get("chat_messages") or []:
role = {"human": "user", "assistant": "assistant"}.get(message.get("sender"))
if not role:
continue
texts = []
for block in message.get("content") or []:
if isinstance(block, dict) and block.get("type") == "text":
text = block.get("text")
if isinstance(text, str) and text.strip():
texts.append(text)
fallback = message.get("text")
if not texts and isinstance(fallback, str) and fallback.strip():
texts.append(fallback)
if texts:
turns.append((role, "\n".join(texts)))
return turns
```
Zaman damgasını yalnızca stdlib ile şöyle çöz:
```python
created = str(conversation.get("created_at") or "")
moment = dt.datetime.fromisoformat(created.replace("Z", "+00:00"))
if moment.tzinfo is None:
moment = moment.replace(tzinfo=dt.timezone.utc)
stamp = moment.timestamp()
```
## Gemini Takeout
Takeout arşivinde `My Activity/Gemini Apps/` klasörü vardır. JSON seçildiyse
`MyActivity.json` bir kayıt listesidir. Bu format sohbet bütünlüğünü korumaz, her kayıt tek bir
istemdir. Gemini içe aktarımını istem günlüğü gibi yaz: aynı günün kayıtlarını tek bir
`### Oturum (YYYY-MM-DD) Gemini` bloğunda topla ve her kaydı `**User:**` satırı yap. Aynı onay,
önizleme, filtre, parça ve çakışma kuralları geçerlidir. Takeout HTML ise kullanıcıdan JSON
formatında yeniden dışa aktarmasını iste.
## Sonra ne olur
İçe aktarım bittiğinde kullanıcıya dürüst özeti ver:
- Yazılan ve atlanan sohbet sayılarını ayrı göster. Her parça dosyasının sohbet ve karakter
sayısını doğrulanmış işlem sonucundan aktar.
- `daily/import-*.md` dosyaları yereldir. Akşam derleyicisi içeriklerini özetleme için
`DataRoot/config.json` preferences ve fallback sırasına göre seçilen yerel AI CLI'ın modeline gönderir.
Başka yere gönderim yapılmaz.
- Derleyici akşamları bir tur çalışır ve her turda değişen logları işler. Büyük bir arşiv birkaç
akşama yayılabilir.
- Kullanıcı beklemek istemezse önce `respectedbrain compile --vault-id UUID --dry-run`, sonra açık
onayıyla `respectedbrain compile --vault-id UUID` çalıştırılabilir. Her tur abonelik limitinden pay
tüketir.
- Kullanıcı içeriği hiçbir harici modele göndermek istemezse derleyiciyi çalıştırmamalı ve ilgili
aylık parça dosyalarını akşam derlemesinden önce silmelidir. Belirli bir sağlayıcıyı istemiyorsa
derlemeden önce `respectedbrain configure --summary-provider codex` ile başka bir ilk tercih seçebilir.
- 50 MB sınırı nedeniyle daha eski arşiv atlandıysa bunu açıkça söyle ve ayrı bir tarih aralığıyla
ikinci tur isteyip istemediğini sor.
`UUID` yer tutucusunu native vault list çıktısındaki kayıtlı kasa kimliğiyle değiştir.
Teknik durum DataRoot/vaults/<UUID>/state altında; cache ayrı dizindedir. Derleme önizlemesi:
```text
respectedbrain vault list
respectedbrain compile --vault-id UUID --dry-run
```
Son adım olarak `beyin-doktor` çalıştırmayı öner. Derleme durumu satırından ilerleme izlenebilir.