prosicht 1.7.0 → 1.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # prosicht
2
2
 
3
- Pro Sicht AI standartlarını, sunum ve blog altyapısını projeye indiren CLI. Şablonlar [prosicht/ai-standards](https://github.com/prosicht/ai-standards) deposundan canlı olarak çekilir.
3
+ Pro Sicht AI standartlarını, sunum ve blog altyapısını projeye indiren, ikinci beyni (Beyin) kuran CLI. Şablonlar [prosicht/ai-standards](https://github.com/prosicht/ai-standards) deposundan canlı olarak çekilir.
4
4
 
5
5
  ## Kullanım
6
6
 
@@ -11,12 +11,14 @@ npx prosicht init
11
11
  - **Uygulama:** Proje şablonunu seçersiniz (web, landing, mobile, backend); kurallar `AGENTS.md` olarak iner.
12
12
  - **Sunum:** Etkileşimli, animasyonlu sunum altyapısı (çekirdek dosyalar, `AGENTS.md` ve çalışan bir örnek deste) bulunduğunuz klasöre iner.
13
13
  - **Blog:** Etkileşimli, animasyonlu blog yazısı altyapısı (çekirdek dosyalar, `AGENTS.md` ve çalışan bir örnek yazı) bulunduğunuz klasöre iner.
14
+ - **Beyin:** Obsidian ve Claude Code / Codex / Antigravity için ikinci beyin (Avenox Beyin V3) `~/Beyin` klasörüne kurulur. Ayrıntı: [Beyin](#beyin-ikinci-beyin).
14
15
 
15
16
  Şablonu doğrudan vermek için:
16
17
  ```bash
17
18
  npx prosicht init -t web
18
19
  npx prosicht init -t presentation
19
20
  npx prosicht init -t blog
21
+ npx prosicht init -t beyin
20
22
  ```
21
23
 
22
24
  Güncelleme (şablon, `AGENTS.md` başındaki etiketten otomatik tespit edilir):
@@ -37,6 +39,25 @@ Sunum klasörlerinde `update` yalnızca çekirdek dosyaları (`index.html`, `css
37
39
  ```
38
40
  İşaretler eksik, ters sırada ya da birden fazlaysa CLI hata verir ve `AGENTS.md`'ye dokunmaz. Uygulama şablonlarında ve sunum / blog kitlerinde aynı şekilde çalışır (CLI 1.7+).
39
41
 
42
+ ## Beyin (ikinci beyin)
43
+
44
+ ```bash
45
+ npx prosicht init -t beyin
46
+ ```
47
+ [Avenox Beyin V3](https://github.com/avenoxai/avenoxbeyin)'ü ajan kurulum rehberindeki ([avenox.lol/beyin.md](https://avenox.lol/beyin.md)) adımlarla kurar; kullanıcının Obsidian indirmesi, kasa açması ve kurulum mesajı yapıştırması gerekmez:
48
+
49
+ 1. **Obsidian:** Kurulu değilse sorup kurar (macOS: Homebrew, yoksa resmi DMG; imza Obsidian'ın Apple geliştirici kimliğine ve notarizasyona karşı doğrulanır. Windows: winget. Linux: flatpak).
50
+ 2. **Kasa:** `~/Beyin` klasörünü açar (mevcutsa notlar korunur).
51
+ 3. **Beyin:** Python 3.11+ yoksa sorup kurar (macOS: Homebrew, Windows: winget). En son kararlı GitHub release'ini indirir, SHA-256'yı doğrular, arşivi güvenli açar; resmi installer'ı (`install_v3.py`) çalıştırır. Kurulu V3 varsa kendi updater'ı (`beyin.py update --package`) kullanılır; V1/V2'den geçişte önce plan gösterilip onay alınır. Sonunda `beyin.py doctor`.
52
+ 4. **Tanışma:** En fazla üç soru (ad/hitap, çalışma alanı, beklenti); cevaplar `🔮 850-Companion/Core.md`'ye yazılır ve beynin kendi aramasıyla (`beyin.py context`) geri okunur. Kişiselleştirilmiş bir Core.md'ye dokunulmaz.
53
+ 5. **Açılış:** Kasa Obsidian'ın listesine eklenip açılır (Obsidian açıksa yeniden başlatmak için onay istenir). Ardından kurulu AI aracı (Claude Code, Codex, Antigravity) seçilir: terminal araçları Beyin klasöründe, ilk mesaj olarak yeni oturum doğrulamasıyla başlar; Antigravity'de mesaj panoya kopyalanır.
54
+
55
+ Otomatik kurulum herhangi bir adımda tamamlanamazsa (Python yok, ağ hatası, elle değiştirilmiş dosya) hiçbir kullanıcı dosyası ezilmez; AI aracı resmi ajan kurulum mesajıyla açılır ve kurulumu o tamamlar. Klasör / hook güven onayları (Claude Code ve Antigravity'de klasör güveni, Codex'te `/hooks`) her zaman kullanıcıya aittir.
56
+
57
+ Seçenekler: `--vault <yol>` (varsayılan `~/Beyin`), `-y` (sormadan kur: eksik Obsidian / Python kurulur, tanışma ilk AI oturumuna kalır, AI aracı açılmaz), `--no-open` (Obsidian'ı ve AI aracını açma).
58
+
59
+ Güncelleme: Beyin klasöründe `npx prosicht update` (ya da her yerden `npx prosicht update -t beyin --vault ~/Beyin`); Beyin'in kendi updater'ını kullanır, `beyin.py rollback` ile geri alınabilir.
60
+
40
61
  ## Yayınlama
41
62
 
42
63
  ```bash
@@ -88,4 +109,6 @@ PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js
88
109
  PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js init -t blog
89
110
  ```
90
111
 
112
+ Beyin kurulumunu GitHub'a gitmeden yerel bir paketle denemek için (yanında `<zip>.sha256` olmalı): `PROSICHT_BEYIN_PACKAGE=/path/beyin-v3-X.Y.Z.zip node index.js init -t beyin --vault /tmp/deneme-beyin --no-open`
113
+
91
114
  Node.js 18 veya üstü gerekir.
package/index.js CHANGED
@@ -9,6 +9,8 @@ const { detectLayout } = require('./lib/layout');
9
9
  const { migrateV1 } = require('./lib/migrate');
10
10
  const { publish } = require('./lib/publish');
11
11
  const { insertOverrides, readExistingOverrides } = require('./lib/overrides');
12
+ const { initBeyin, updateBeyin, installedVersion } = require('./lib/beyin');
13
+ const { confirm } = require('./lib/system');
12
14
 
13
15
  // PROSICHT_TEMPLATES_URL: sablonlari push etmeden yerel bir sunucudan denemek icin
14
16
  const BASE_URL = (process.env.PROSICHT_TEMPLATES_URL || 'https://raw.githubusercontent.com/ProSicht/ai-standards/main/templates').replace(/\/+$/, '');
@@ -16,7 +18,8 @@ const BASE_URL = (process.env.PROSICHT_TEMPLATES_URL || 'https://raw.githubuserc
16
18
  const KINDS = {
17
19
  app: { title: 'Uygulama', description: 'Proje turune gore AGENTS.md (web, landing, mobile, backend)' },
18
20
  presentation: { title: 'Sunum', description: 'Etkilesimli, animasyonlu sunum altyapisi + AGENTS.md' },
19
- blog: { title: 'Blog', description: 'Etkilesimli, animasyonlu blog yazisi altyapisi + AGENTS.md' }
21
+ blog: { title: 'Blog', description: 'Etkilesimli, animasyonlu blog yazisi altyapisi + AGENTS.md' },
22
+ beyin: { title: 'Beyin', description: 'Obsidian + Claude Code / Codex / Antigravity icin ikinci beyin (~/Beyin)' }
20
23
  };
21
24
 
22
25
  const APP_TEMPLATES = {
@@ -54,7 +57,7 @@ const KITS = {
54
57
  }
55
58
  };
56
59
 
57
- const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), ...Object.keys(KITS)];
60
+ const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), ...Object.keys(KITS), 'beyin'];
58
61
  const TAG_PATTERN = /<!--\s*template:\s*(\w+)\s*-->\n?/;
59
62
 
60
63
  // Projedeki mevcut AGENTS.md dosyasından şablon türünü okur
@@ -127,11 +130,11 @@ async function askAppTemplate(message) {
127
130
  return response.template;
128
131
  }
129
132
 
130
- // Önce tür (Uygulama / Sunum / Blog), uygulamaysa ardından şablon sorulur
133
+ // Önce tür (Uygulama / Sunum / Blog / Beyin), uygulamaysa ardından şablon sorulur
131
134
  async function askTemplate(message) {
132
135
  const kind = await askKind();
133
136
  if (!kind) return null;
134
- if (KITS[kind]) return kind;
137
+ if (KITS[kind] || kind === 'beyin') return kind;
135
138
  return askAppTemplate(message);
136
139
  }
137
140
 
@@ -166,11 +169,6 @@ function resolveInsideCwd(relPath) {
166
169
  return target;
167
170
  }
168
171
 
169
- async function confirm(message, initial = true) {
170
- const response = await prompts({ type: 'confirm', name: 'ok', initial, message });
171
- return Boolean(response.ok);
172
- }
173
-
174
172
  // init: çekirdek + başlangıç dosyaları (mevcut başlangıç dosyaları korunur)
175
173
  // update: yalnızca çekirdek dosyalar; sunum / yazı içeriğine dokunulmaz
176
174
  async function installKit(key, mode, options = {}) {
@@ -256,6 +254,7 @@ async function install(templateKey, mode, options = {}) {
256
254
  console.error(`Hata: Gecersiz sablon "${templateKey}". Gecerli: ${TEMPLATE_KEYS.join(', ')}`);
257
255
  process.exit(1);
258
256
  }
257
+ if (templateKey === 'beyin') return mode === 'init' ? initBeyin(options) : updateBeyin(options);
259
258
  if (KITS[templateKey]) return installKit(templateKey, mode, options);
260
259
  return fetchAndSaveTemplate(templateKey);
261
260
  }
@@ -267,8 +266,11 @@ program
267
266
 
268
267
  program
269
268
  .command('init')
270
- .description('Projeye AI standartlarini (AGENTS.md), sunum veya blog altyapisini indirir')
269
+ .description('Projeye AI standartlarini (AGENTS.md), sunum veya blog altyapisini indirir; ya da ikinci beyni kurar')
271
270
  .option('-t, --template <type>', `Sablon turu (${TEMPLATE_KEYS.join(', ')})`)
271
+ .option('--vault <yol>', 'Beyin: kasa klasoru (varsayilan: ~/Beyin)')
272
+ .option('-y, --yes', 'Beyin: sormadan kur (eksik Obsidian / Python kurulur, tanisma ilk AI oturumuna kalir)')
273
+ .option('--no-open', 'Beyin: Obsidian\'i ve AI aracini acma')
272
274
  .action(async (options) => {
273
275
  let templateKey = options.template;
274
276
 
@@ -277,7 +279,9 @@ program
277
279
  if (!templateKey) process.exit(0);
278
280
  }
279
281
 
280
- await install(templateKey, 'init');
282
+ // -y / --vault / --no-open yalnızca Beyin için: kitlerin init davranışı değişmesin
283
+ const beyinOptions = { vault: options.vault, yes: options.yes, open: options.open };
284
+ await install(templateKey, 'init', templateKey === 'beyin' ? beyinOptions : {});
281
285
  });
282
286
 
283
287
  program
@@ -285,10 +289,15 @@ program
285
289
  .description('Projedeki AGENTS.md (ve sunum / blog altyapisinin cekirdek dosyalarini) tespit edip gunceller')
286
290
  .option('-t, --template <type>', 'Sablon turunu zorla belirle')
287
291
  .option('-y, --yes', 'Sormadan devam et: eski sunum yapisini yeni yapiya tasir')
292
+ .option('--vault <yol>', 'Beyin: guncellenecek kasa klasoru')
288
293
  .action(async (options) => {
289
294
  let templateKey = options.template;
290
295
 
291
296
  // -t verilmediyse mevcut dosyadan otomatik tespit et
297
+ if (!templateKey && installedVersion(process.cwd())) {
298
+ templateKey = 'beyin';
299
+ console.log('🔍 Tespit edilen mevcut kurulum: Beyin');
300
+ }
292
301
  if (!templateKey) {
293
302
  templateKey = detectExistingTemplate();
294
303
  if (templateKey) {
@@ -300,7 +309,7 @@ program
300
309
  }
301
310
  }
302
311
 
303
- await install(templateKey, 'update', { yes: options.yes });
312
+ await install(templateKey, 'update', { yes: options.yes, vault: options.vault });
304
313
  });
305
314
 
306
315
  program
package/lib/beyin.js ADDED
@@ -0,0 +1,515 @@
1
+ const crypto = require('crypto');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const { spawn } = require('child_process');
6
+ const prompts = require('prompts');
7
+ const obsidian = require('./obsidian');
8
+ const { IS_MAC, IS_WIN, run, runVisible, which, expandHome, openExternal, copyToClipboard, confirm } = require('./system');
9
+
10
+ // Avenox İkinci Beyin V3: https://github.com/avenoxai/avenoxbeyin
11
+ // Kurulum, ajan rehberindeki (https://avenox.lol/beyin.md) adımları izler: yalnızca en son kararlı
12
+ // resmi release, SHA-256 doğrulaması, güvenli arşiv açma, resmi installer / kurulu updater ve doctor.
13
+ const REPO = 'avenoxai/avenoxbeyin';
14
+ const MIN_PYTHON = [3, 11];
15
+ const DEFAULT_VAULT = path.join(os.homedir(), 'Beyin');
16
+ const COMPANION_DIR = '🔮 850-Companion';
17
+ const PLACEHOLDER = 'Henüz kişiselleştirilmedi.';
18
+ const STABLE_TAG = /^v(\d+\.\d+\.\d+)$/;
19
+ const UA = 'prosicht-cli';
20
+
21
+ // Otomatik kurulum tamamlanamazsa ajana verilen resmi kurulum isteği
22
+ const AGENT_SETUP_PROMPT = 'https://avenox.lol/beyin.md adresini oku. Bu klasöre ikinci beynimi kur. Mevcut kurulum varsa notlarımı koruyarak güncelle. Gerekli indirme ve kurulum adımlarını sen yap. Sonunda örnek bir bilgiyi kaydedip yeni oturumda geri okuyarak birlikte doğrulayalım.';
23
+ // Kurulum tamamsa ilk oturumun mesajı: CLI'nin kaydettiği bilgiyi yeni bir oturumda geri okur
24
+ const FIRST_SESSION_PROMPT = 'Beyin kurulumu prosicht CLI ile bu klasöre yapıldı. Yeni oturumda birlikte doğrulayalım: .agents/skills/beyin/SKILL.md dosyasını oku, beyin.py doctor --human ile sağlığı kontrol et ve Core.md\'ye göre beni nasıl tanıdığını kısaca söyle. Core.md\'de adım, işim veya beklentim eksikse en fazla üç kısa soruyla tanış ve cevaplarımı Core.md\'ye kaydet.';
25
+
26
+ // Arşivi Python'un zipfile'ı ile açar: mutlak yol, '..' kaçışı ve sembolik bağ içeren paket reddedilir
27
+ const EXTRACT_SCRIPT = `
28
+ import stat, sys, zipfile
29
+ from pathlib import Path, PurePosixPath
30
+ archive, dest = sys.argv[1], Path(sys.argv[2]).resolve()
31
+ with zipfile.ZipFile(archive) as z:
32
+ for info in z.infolist():
33
+ name = info.filename
34
+ parts = PurePosixPath(name).parts
35
+ if name.startswith('/') or '\\\\' in name or ':' in name or '..' in parts or stat.S_ISLNK(info.external_attr >> 16):
36
+ sys.exit('unsafe archive member: ' + name)
37
+ target = (dest / name).resolve()
38
+ if target != dest and dest not in target.parents:
39
+ sys.exit('unsafe archive member: ' + name)
40
+ z.extractall(dest)
41
+ `;
42
+
43
+ function parseVersion(text) {
44
+ const match = /^(\d+)\.(\d+)(?:\.(\d+))?/.exec(String(text || '').trim());
45
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3] || 0)] : null;
46
+ }
47
+
48
+ function atLeast(version, min) {
49
+ for (let i = 0; i < min.length; i++) {
50
+ if ((version[i] || 0) !== min[i]) return (version[i] || 0) > min[i];
51
+ }
52
+ return true;
53
+ }
54
+
55
+ function parseJson(text) {
56
+ try {
57
+ return JSON.parse(text);
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ // --- Python -----------------------------------------------------------------
64
+
65
+ function pythonCandidates() {
66
+ if (IS_WIN) {
67
+ const local = process.env.LOCALAPPDATA || '';
68
+ return [['py', ['-3']], ['python', []], [path.join(local, 'Programs', 'Python', 'Launcher', 'py.exe'), ['-3']]];
69
+ }
70
+ const names = ['python3', 'python3.14', 'python3.13', 'python3.12', 'python3.11', 'python'];
71
+ const brew = IS_MAC ? ['/opt/homebrew/bin/python3', '/usr/local/bin/python3'] : [];
72
+ return [...names, ...brew].map((cmd) => [cmd, []]);
73
+ }
74
+
75
+ // Dönüş: { python: { cmd, pre, version } | null, old: bulunan eski sürüm }
76
+ function findPython() {
77
+ let old = null;
78
+ for (const [cmd, pre] of pythonCandidates()) {
79
+ // macOS'taki /usr/bin/python3 3.9; Xcode araçları yoksa çağırmak kurulum penceresi açar
80
+ if (IS_MAC && which(cmd) === '/usr/bin/python3') continue;
81
+ const result = run(cmd, [...pre, '-c', 'import sys; print("%d.%d.%d" % sys.version_info[:3])']);
82
+ const version = result.status === 0 && parseVersion(result.stdout);
83
+ if (!version) continue;
84
+ if (atLeast(version, MIN_PYTHON)) return { python: { cmd, pre, version: version.join('.') }, old };
85
+ old = old || version.join('.');
86
+ }
87
+ return { python: null, old };
88
+ }
89
+
90
+ function pythonInstaller() {
91
+ if (IS_MAC) {
92
+ const brew = which('brew') || ['/opt/homebrew/bin/brew', '/usr/local/bin/brew'].find((p) => fs.existsSync(p));
93
+ return brew ? { label: 'Homebrew', cmd: brew, args: ['install', 'python'] } : null;
94
+ }
95
+ if (IS_WIN && which('winget')) return { label: 'winget', cmd: 'winget', args: ['install', '-e', '--id', 'Python.Python.3.13'] };
96
+ return null;
97
+ }
98
+
99
+ async function ensurePython({ yes, open }) {
100
+ const found = findPython();
101
+ if (found.python) {
102
+ console.log(` Python ${found.python.version} bulundu.`);
103
+ return found.python;
104
+ }
105
+ console.log(found.old
106
+ ? ` Python ${found.old} bulundu; Beyin icin 3.11 veya ustu gerekli.`
107
+ : ' Python 3.11 veya ustu bulunamadi.');
108
+ const installer = pythonInstaller();
109
+ if (installer && (yes || (await confirm(`Python ${installer.label} ile kurulsun mu?`)))) {
110
+ if (runVisible(installer.cmd, installer.args)) {
111
+ const again = findPython();
112
+ if (again.python) {
113
+ console.log(` Python ${again.python.version} kuruldu.`);
114
+ return again.python;
115
+ }
116
+ }
117
+ console.error(' Python kurulamadi.');
118
+ }
119
+ console.log(' Python 3\'u https://www.python.org/downloads/ adresinden kurup komutu tekrar calistirabilirsiniz.');
120
+ if (!installer && open) openExternal('https://www.python.org/downloads/');
121
+ return null;
122
+ }
123
+
124
+ function py(python, args, options = {}) {
125
+ return run(python.cmd, [...python.pre, ...args], options);
126
+ }
127
+
128
+ // --- Resmi paket ------------------------------------------------------------
129
+
130
+ async function download(url) {
131
+ const response = await fetch(url, { headers: { 'User-Agent': UA } });
132
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
133
+ return Buffer.from(await response.arrayBuffer());
134
+ }
135
+
136
+ function tagFromUrl(url) {
137
+ const match = /\/releases\/tag\/(v\d+\.\d+\.\d+)\/?$/.exec(url || '');
138
+ return match ? match[1] : null;
139
+ }
140
+
141
+ function packageNames(tag) {
142
+ const zip = `beyin-v3-${STABLE_TAG.exec(tag)[1]}.zip`;
143
+ return { zip, sha: `${zip}.sha256` };
144
+ }
145
+
146
+ // En son kararlı sürüm: önce GitHub API; saatlik sınıra takılırsa releases/latest yönlendirmesi.
147
+ // Sürüm hiçbir durumda tahmin edilmez.
148
+ async function latestTag() {
149
+ const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
150
+ headers: { 'User-Agent': UA, Accept: 'application/vnd.github+json' }
151
+ });
152
+ if (response.ok) {
153
+ const release = await response.json();
154
+ if (release.draft || release.prerelease || !STABLE_TAG.test(release.tag_name || '')) {
155
+ throw new Error(`Son Beyin surumu kararli degil (${release.tag_name}); kurulum yapilmadi.`);
156
+ }
157
+ return release.tag_name;
158
+ }
159
+ if (![403, 429].includes(response.status)) throw new Error(`GitHub API HTTP ${response.status}`);
160
+ const redirect = await fetch(`https://github.com/${REPO}/releases/latest`, { redirect: 'manual', headers: { 'User-Agent': UA } });
161
+ const tag = tagFromUrl(redirect.headers.get('location'));
162
+ if (!tag) throw new Error('Son Beyin surumu belirlenemedi.');
163
+ return tag;
164
+ }
165
+
166
+ function sha256(buffer) {
167
+ return crypto.createHash('sha256').update(buffer).digest('hex');
168
+ }
169
+
170
+ function verifyChecksum(zip, shaText) {
171
+ const expected = String(shaText).trim().split(/\s+/)[0].toLowerCase();
172
+ if (!/^[0-9a-f]{64}$/.test(expected) || expected !== sha256(zip)) {
173
+ throw new Error('Paketin SHA-256 degeri eslesmedi; arsiv acilmadi.');
174
+ }
175
+ }
176
+
177
+ // PROSICHT_BEYIN_PACKAGE: geliştirme için yerel paket (yanında <zip>.sha256 olmalı)
178
+ async function fetchPackage(python, tmp) {
179
+ let zipPath;
180
+ let zip;
181
+ let shaText;
182
+ const local = process.env.PROSICHT_BEYIN_PACKAGE;
183
+ if (local) {
184
+ zipPath = path.resolve(local);
185
+ zip = fs.readFileSync(zipPath);
186
+ shaText = fs.readFileSync(`${zipPath}.sha256`, 'utf8');
187
+ } else {
188
+ const tag = await latestTag();
189
+ const names = packageNames(tag);
190
+ const base = `https://github.com/${REPO}/releases/download/${tag}`;
191
+ console.log(` Beyin ${tag} indiriliyor...`);
192
+ [zip, shaText] = await Promise.all([download(`${base}/${names.zip}`), download(`${base}/${names.sha}`).then(String)]);
193
+ zipPath = path.join(tmp, names.zip);
194
+ fs.writeFileSync(zipPath, zip);
195
+ }
196
+ verifyChecksum(zip, shaText);
197
+
198
+ const dir = path.join(tmp, 'paket');
199
+ fs.mkdirSync(dir);
200
+ const extract = py(python, ['-I', '-c', EXTRACT_SCRIPT, zipPath, dir]);
201
+ if (extract.status !== 0) throw new Error(`Paket acilamadi: ${(extract.stderr || '').trim()}`);
202
+ const manifest = parseJson(fs.existsSync(path.join(dir, 'manifest.json')) && fs.readFileSync(path.join(dir, 'manifest.json'), 'utf8'));
203
+ if (!manifest || !fs.existsSync(path.join(dir, 'scripts', 'install_v3.py'))) {
204
+ throw new Error('Paket beklenen yapida degil (manifest.json, scripts/install_v3.py).');
205
+ }
206
+ const minPython = parseVersion(manifest.min_python);
207
+ if (minPython && !atLeast(parseVersion(python.version), minPython)) {
208
+ throw new Error(`Bu Beyin surumu Python ${manifest.min_python} veya ustunu istiyor (bulunan: ${python.version}).`);
209
+ }
210
+ return { zip: zipPath, dir, version: manifest.version };
211
+ }
212
+
213
+ // --- Kurulum / güncelleme ---------------------------------------------------
214
+
215
+ function installedVersion(vault) {
216
+ const file = path.join(vault, '.beyin-version');
217
+ return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null;
218
+ }
219
+
220
+ function hasLegacyInstall(vault) {
221
+ return ['.claude/hooks/session-start.sh', '.claude/hooks/session-start.ps1', '.claude/scripts/flush.py', '.claude/scripts/compile.py']
222
+ .some((rel) => fs.existsSync(path.join(vault, rel)));
223
+ }
224
+
225
+ // Resmi installer / updater hatalarını kullanıcının anlayacağı dile çevirir. Kullanıcı dosyalarına dokunulmaz.
226
+ function describeFailure(result) {
227
+ const error = parseJson((result.stderr || '').trim().split('\n').pop()) || parseJson(result.stdout) || {};
228
+ const message = error.message || (result.stderr || result.stdout || '').trim() || `cikis kodu ${result.status}`;
229
+ let match = /Customized legacy runner requires review (.+)/.exec(message);
230
+ if (match) return `Eski kurulumda elle degistirilmis bir dosya var (${match[1]}). Onayiniz olmadan degistirilmez; AI araciniz incelemeli.`;
231
+ match = /Reinstall conflict: managed file changed (.+)/.exec(message);
232
+ if (match) return `Beyin'in yonettigi bir dosya elle degistirilmis (${match[1]}); dosyaniz ezilmedi.`;
233
+ if (/Pending installation/.test(message)) return 'Yarim kalmis bir kurulum var: Beyin klasorunde "python3 beyin.py recover" calistirin.';
234
+ return message;
235
+ }
236
+
237
+ // Dönüş: { status: 'installed' | 'updated' | 'noop', result }
238
+ async function applyPackage(python, pkg, vault, { yes }) {
239
+ const current = installedVersion(vault);
240
+ if (current && current.startsWith('3.')) {
241
+ // Kurulu V3: kendi updater'ı (yedek ve rollback onda)
242
+ const check = py(python, ['beyin.py', 'update', '--check', '--package', pkg.zip], { cwd: vault });
243
+ const checked = parseJson(check.stdout);
244
+ if (check.status === 0 && checked && checked.status === 'noop') return { status: 'noop', result: checked };
245
+ const update = py(python, ['beyin.py', 'update', '--package', pkg.zip], { cwd: vault });
246
+ const result = parseJson(update.stdout);
247
+ if (update.status === 0 && result && ['updated', 'noop'].includes(result.status)) return { status: result.status, result };
248
+ // Çok eski V3'te update komutu yok: resmi installer'a düş
249
+ if (!/invalid choice/.test(update.stderr || '')) throw new Error(describeFailure(update));
250
+ }
251
+
252
+ const installer = path.join(pkg.dir, 'scripts', 'install_v3.py');
253
+ if (hasLegacyInstall(vault)) {
254
+ const plan = py(python, [installer, '--vault', vault, '--plan'], { cwd: pkg.dir });
255
+ const report = parseJson(plan.stdout);
256
+ if (plan.status !== 0 || !report) throw new Error(describeFailure(plan));
257
+ console.log(` Eski (V1/V2) kurulum bulundu: ${report.write.length} dosya yazilacak, ${report.retire.length} eski dosya emekliye ayrilacak. Notlariniz korunur.`);
258
+ if (!yes && !(await confirm('V3\'e gecilsin mi?'))) throw new Error('Gecis iptal edildi; hicbir dosya degistirilmedi.');
259
+ }
260
+ const install = py(python, [installer, '--vault', vault], { cwd: pkg.dir });
261
+ const result = parseJson(install.stdout);
262
+ if (install.status !== 0 || !result) throw new Error(describeFailure(install));
263
+ return { status: result.status === 'installed' ? 'installed' : String(result.status), result };
264
+ }
265
+
266
+ function doctor(python, vault) {
267
+ const result = py(python, ['beyin.py', 'doctor', '--human'], { cwd: vault });
268
+ return { ok: result.status === 0, output: (result.status === 0 ? result.stdout : result.stderr || result.stdout || '').trim() };
269
+ }
270
+
271
+ // --- Tanışma ----------------------------------------------------------------
272
+
273
+ function corePath(vault) {
274
+ const file = path.join(vault, COMPANION_DIR, 'Core.md');
275
+ return fs.existsSync(file) ? file : null;
276
+ }
277
+
278
+ function cleanAnswer(value) {
279
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 300);
280
+ }
281
+
282
+ // Core.md'deki "Henüz kişiselleştirilmedi" paragrafını cevaplarla değiştirir; dosyanın geri kalanı aynen kalır.
283
+ // Kişiselleştirilmiş Core.md'ye ya da boş cevaplara null döner (hiçbir şey yazılmaz).
284
+ function personalizeCore(text, answers, date) {
285
+ const start = text.indexOf(PLACEHOLDER);
286
+ if (start === -1) return null;
287
+ const lines = [];
288
+ if (answers.name) lines.push(`- **Ad ve hitap:** ${answers.name}`);
289
+ if (answers.work) lines.push(`- **Çalışma alanı:** ${answers.work}`);
290
+ if (answers.expectation) lines.push(`- **Beyinden beklentisi:** ${answers.expectation}`);
291
+ if (!lines.length) return null;
292
+ lines.push(`- **Kaynak:** Kullanıcının \`prosicht init\` kurulumunda verdiği cevaplar (${date}). Eksik kalanı konuşarak öğren; bilinmeyen geçmişi uydurma.`);
293
+ const blank = text.indexOf('\n\n', start);
294
+ return text.slice(0, start) + lines.join('\n') + (blank === -1 ? '\n' : text.slice(blank));
295
+ }
296
+
297
+ async function askIntroduction() {
298
+ let cancelled = false;
299
+ const answers = await prompts([
300
+ { type: 'text', name: 'name', message: 'Adiniz ne, size nasil hitap edilsin?' },
301
+ { type: 'text', name: 'work', message: 'Ne is yapiyorsunuz, beyni hangi alanda kullanacaksiniz?' },
302
+ { type: 'text', name: 'expectation', message: 'Beyinden en cok ne bekliyorsunuz?' }
303
+ ], { onCancel: () => { cancelled = true; return false; } });
304
+ if (cancelled) return null;
305
+ return { name: cleanAnswer(answers.name), work: cleanAnswer(answers.work), expectation: cleanAnswer(answers.expectation) };
306
+ }
307
+
308
+ // Kaydedilen bilgiyi beynin kendi aramasıyla geri okur; dönen kaynak yolu doğrulamadır
309
+ function readBack(python, vault, query) {
310
+ if (py(python, ['beyin.py', 'sync'], { cwd: vault }).status !== 0) return null;
311
+ const context = parseJson(py(python, ['beyin.py', 'context', query], { cwd: vault }).stdout);
312
+ const records = context && Array.isArray(context.records) ? context.records : [];
313
+ const hit = records.find((r) => typeof r.source === 'string' && r.source.endsWith('Core.md') && String(r.text || '').includes(query));
314
+ return hit ? hit.source : null;
315
+ }
316
+
317
+ async function introduce(python, vault, options) {
318
+ const file = corePath(vault);
319
+ if (!file) return { status: 'skipped' };
320
+ const text = fs.readFileSync(file, 'utf8');
321
+ if (!text.includes(PLACEHOLDER)) return { status: 'existing' };
322
+ if (options.yes) return { status: 'skipped' };
323
+
324
+ console.log('\n[Pro Sicht] Tanisalim (bos birakabilirsiniz; ilk oturumda AI araciniz sorar)');
325
+ const answers = await askIntroduction();
326
+ const updated = answers && personalizeCore(text, answers, new Date().toISOString().slice(0, 10));
327
+ if (!updated) return { status: 'skipped' };
328
+ fs.writeFileSync(file, updated, 'utf8');
329
+ const source = answers.name ? readBack(python, vault, answers.name) : null;
330
+ return { status: 'saved', source, name: answers.name };
331
+ }
332
+
333
+ // --- AI aracı ---------------------------------------------------------------
334
+
335
+ function findAntigravity() {
336
+ if (IS_MAC) return ['/Applications/Antigravity.app', path.join(os.homedir(), 'Applications', 'Antigravity.app')].find((p) => fs.existsSync(p)) || null;
337
+ if (IS_WIN) {
338
+ const exe = path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Antigravity', 'Antigravity.exe');
339
+ return fs.existsSync(exe) ? exe : null;
340
+ }
341
+ return which('antigravity');
342
+ }
343
+
344
+ function detectTools() {
345
+ const tools = [];
346
+ if (which('claude')) tools.push({ key: 'claude', title: 'Claude Code', cmd: 'claude' });
347
+ if (which('codex')) tools.push({ key: 'codex', title: 'Codex', cmd: 'codex' });
348
+ const antigravity = findAntigravity();
349
+ if (antigravity) tools.push({ key: 'antigravity', title: 'Antigravity', app: antigravity });
350
+ return tools;
351
+ }
352
+
353
+ function runInTerminal(cmd, args, cwd) {
354
+ return new Promise((resolve) => {
355
+ const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: IS_WIN });
356
+ child.on('error', () => resolve(false));
357
+ child.on('exit', () => resolve(true));
358
+ });
359
+ }
360
+
361
+ async function launchTool(tool, vault, message) {
362
+ if (tool.key === 'antigravity') {
363
+ const copied = copyToClipboard(message);
364
+ if (IS_MAC) openExternal(vault, tool.app);
365
+ else spawn(tool.app, [vault], { detached: true, stdio: 'ignore' }).unref();
366
+ console.log(`\nAntigravity ${vault} klasoruyle aciliyor. Klasore guvenin, sonra Agent paneline ${copied ? 'panodaki mesaji yapistirin' : 'yukaridaki mesaji yazin'}.`);
367
+ return;
368
+ }
369
+ console.log(`\n${tool.title} ${vault} icinde baslatiliyor. Klasor guven sorusunu onaylayin.`);
370
+ if (tool.key === 'codex') console.log('Codex\'te Beyin hook\'larini /hooks ekranindan inceleyip guvenin.');
371
+ // Windows'ta kabuk tırnaklaması Türkçe mesajı bozabilir: mesaj panoda, kullanıcı yapıştırır
372
+ if (IS_WIN && copyToClipboard(message)) console.log('Ilk mesaj panoda: oturum acilinca yapistirin.');
373
+ await runInTerminal(tool.cmd, IS_WIN ? [] : [message], vault);
374
+ }
375
+
376
+ async function openTool(vault, message, { yes, open }) {
377
+ const tools = open && !yes ? detectTools() : [];
378
+ if (tools.length) {
379
+ const response = await prompts({
380
+ type: 'select',
381
+ name: 'tool',
382
+ message: 'Beyin hangi AI aracinda acilsin?',
383
+ choices: [...tools.map((t) => ({ title: t.title, value: t.key })), { title: 'Simdi degil', value: null }]
384
+ });
385
+ const tool = tools.find((t) => t.key === response.tool);
386
+ if (tool) return launchTool(tool, vault, message);
387
+ }
388
+ const copied = copyToClipboard(message);
389
+ console.log(`Beyin klasorunu (${vault}) Claude Code, Codex veya Antigravity ile acin ve ilk mesaj olarak ${copied ? 'panodaki su metni yapistirin' : 'su metni gonderin'}:\n`);
390
+ console.log(` ${message}\n`);
391
+ }
392
+
393
+ // --- Komutlar ---------------------------------------------------------------
394
+
395
+ function resolveVault(value) {
396
+ return path.resolve(expandHome(value || DEFAULT_VAULT));
397
+ }
398
+
399
+ function makeTmp() {
400
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'prosicht-beyin-'));
401
+ }
402
+
403
+ // prosicht init -t beyin: Obsidian, ~/Beyin kasası, resmi Beyin V3 kurulumu, tanışma ve ilk AI oturumu
404
+ async function initBeyin(options = {}) {
405
+ const vault = resolveVault(options.vault);
406
+ const open = options.open !== false;
407
+ const tmp = makeTmp();
408
+ let ready = false;
409
+
410
+ console.log(`\n[Pro Sicht] Ikinci beyin (Avenox Beyin V3) kuruluyor: ${vault}`);
411
+ try {
412
+ console.log('\n1/4 Obsidian');
413
+ const hasObsidian = await obsidian.ensure({ tmp, download, yes: options.yes });
414
+
415
+ console.log('\n2/4 Beyin kasasi');
416
+ const existed = fs.existsSync(vault);
417
+ fs.mkdirSync(path.join(vault, '.obsidian'), { recursive: true });
418
+ console.log(` ${vault} ${existed ? '(mevcut klasor; notlariniz korunur)' : 'olusturuldu'}`);
419
+
420
+ console.log('\n3/4 Beyin kurulumu');
421
+ const python = await ensurePython({ yes: options.yes, open });
422
+ if (python) {
423
+ try {
424
+ const pkg = await fetchPackage(python, tmp);
425
+ const applied = await applyPackage(python, pkg, vault, options);
426
+ const label = { installed: 'kuruldu', updated: 'guncellendi', noop: 'zaten guncel' }[applied.status] || applied.status;
427
+ console.log(` Beyin ${pkg.version} ${label}.`);
428
+ const companion = applied.result && applied.result.companion;
429
+ if (companion && companion.status === 'needs_attention') {
430
+ console.log(' Birden fazla kimlik klasoru bulundu; hangisinin kullanilacagini ilk oturumda AI araciniz soracak.');
431
+ }
432
+ const conflicts = applied.result && applied.result.skills && applied.result.skills.conflicts;
433
+ if (conflicts && conflicts.length) console.log(` ${conflicts.length} skill cakismasi var; ilk oturumda AI araciniz inceleyecek.`);
434
+ const health = doctor(python, vault);
435
+ if (!health.ok) throw new Error(`Saglik kontrolu basarisiz:\n${health.output}`);
436
+ console.log(` Saglik kontrolu tamam (${health.output.split('\n')[0]}).`);
437
+ ready = true;
438
+ } catch (error) {
439
+ // Ağ, paket ya da installer hatası: kurulumu resmi ajan rehberiyle AI aracı tamamlar
440
+ console.error(` Otomatik kurulum tamamlanamadi: ${error.message}`);
441
+ }
442
+ }
443
+
444
+ if (ready) {
445
+ const intro = await introduce(python, vault, options);
446
+ if (intro.status === 'saved') {
447
+ console.log(` Kaydedildi: ${COMPANION_DIR}/Core.md`);
448
+ if (intro.source) console.log(` Geri okuma: "${intro.name}" -> ${intro.source}`);
449
+ } else if (intro.status === 'existing') {
450
+ console.log('\n Beyin sizi zaten taniyor (Core.md kisisellestirilmis); dokunulmadi.');
451
+ }
452
+ }
453
+
454
+ console.log('\n4/4 Acilis');
455
+ if (hasObsidian) await obsidian.addAndOpen(vault, { yes: options.yes, open });
456
+
457
+ if (ready) {
458
+ console.log(`\nBeyin hazir: ${vault}`);
459
+ } else {
460
+ console.log('\nKurulumu AI araciniz tamamlayacak (resmi ajan kurulumu).');
461
+ }
462
+ await openTool(vault, ready ? FIRST_SESSION_PROMPT : AGENT_SETUP_PROMPT, { yes: options.yes, open });
463
+ if (!ready) process.exitCode = 1;
464
+ } finally {
465
+ fs.rmSync(tmp, { recursive: true, force: true });
466
+ }
467
+ }
468
+
469
+ // prosicht update (Beyin klasöründe): kurulu updater ile en son kararlı sürüm
470
+ async function updateBeyin(options = {}) {
471
+ const vault = options.vault
472
+ ? resolveVault(options.vault)
473
+ : [process.cwd(), DEFAULT_VAULT].find((dir) => installedVersion(dir)) || null;
474
+ if (!vault || !installedVersion(vault)) {
475
+ console.error('Hata: Beyin kurulumu bulunamadi. Kurmak icin: npx prosicht init -t beyin');
476
+ process.exitCode = 1;
477
+ return;
478
+ }
479
+ const { python } = findPython();
480
+ if (!python) {
481
+ console.error('Hata: Python 3.11 veya ustu bulunamadi.');
482
+ process.exitCode = 1;
483
+ return;
484
+ }
485
+ const tmp = makeTmp();
486
+ console.log(`\n[Pro Sicht] Beyin guncelleniyor: ${vault} (kurulu: ${installedVersion(vault)})`);
487
+ try {
488
+ const pkg = await fetchPackage(python, tmp);
489
+ const applied = await applyPackage(python, pkg, vault, options);
490
+ console.log(applied.status === 'noop' ? `Beyin zaten guncel (${pkg.version}).` : `Basarili: Beyin ${pkg.version} (${applied.status}).`);
491
+ const health = doctor(python, vault);
492
+ console.log(health.ok ? 'Saglik kontrolu tamam.' : `Saglik kontrolu basarisiz:\n${health.output}`);
493
+ if (!health.ok) process.exitCode = 1;
494
+ } catch (error) {
495
+ console.error(`Guncelleme hatasi: ${error.message}`);
496
+ process.exitCode = 1;
497
+ } finally {
498
+ fs.rmSync(tmp, { recursive: true, force: true });
499
+ }
500
+ }
501
+
502
+ module.exports = {
503
+ initBeyin,
504
+ updateBeyin,
505
+ installedVersion,
506
+ // testler için
507
+ parseVersion,
508
+ atLeast,
509
+ tagFromUrl,
510
+ packageNames,
511
+ verifyChecksum,
512
+ personalizeCore,
513
+ describeFailure,
514
+ DEFAULT_VAULT
515
+ };
@@ -0,0 +1,193 @@
1
+ const crypto = require('crypto');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const { IS_MAC, IS_WIN, run, runVisible, which, isRunning, openExternal, confirm } = require('./system');
6
+
7
+ const RELEASES_API = 'https://api.github.com/repos/obsidianmd/obsidian-releases/releases/latest';
8
+ // Obsidian'ın (Dynalist Inc.) Apple geliştirici kimliği: indirilen uygulamanın imzası buna bağlı olmalı
9
+ const TEAM_ID = '6JSW4SJWN9';
10
+ const PROCESS = IS_MAC || IS_WIN ? 'Obsidian' : 'obsidian';
11
+
12
+ function findBrew() {
13
+ return which('brew') || ['/opt/homebrew/bin/brew', '/usr/local/bin/brew'].find((p) => fs.existsSync(p)) || null;
14
+ }
15
+
16
+ function findApp() {
17
+ const home = os.homedir();
18
+ if (IS_MAC) {
19
+ return ['/Applications/Obsidian.app', path.join(home, 'Applications', 'Obsidian.app')].find((p) => fs.existsSync(p)) || null;
20
+ }
21
+ if (IS_WIN) {
22
+ const local = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
23
+ const candidates = [path.join(local, 'Programs', 'Obsidian', 'Obsidian.exe')];
24
+ if (process.env.ProgramFiles) candidates.push(path.join(process.env.ProgramFiles, 'Obsidian', 'Obsidian.exe'));
25
+ return candidates.find((p) => fs.existsSync(p)) || null;
26
+ }
27
+ if (which('obsidian')) return which('obsidian');
28
+ if (which('flatpak') && run('flatpak', ['info', 'md.obsidian.Obsidian']).status === 0) return 'flatpak:md.obsidian.Obsidian';
29
+ return null;
30
+ }
31
+
32
+ // Obsidian'ın kasa listesini tuttuğu dosya (açık kasalar ve son açılma zamanı)
33
+ function configPath() {
34
+ const home = os.homedir();
35
+ if (IS_MAC) return path.join(home, 'Library', 'Application Support', 'obsidian', 'obsidian.json');
36
+ if (IS_WIN) return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'obsidian', 'obsidian.json');
37
+ const flatpak = path.join(home, '.var', 'app', 'md.obsidian.Obsidian', 'config', 'obsidian');
38
+ if (fs.existsSync(flatpak)) return path.join(flatpak, 'obsidian.json');
39
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'obsidian', 'obsidian.json');
40
+ }
41
+
42
+ function readConfig(file) {
43
+ if (!fs.existsSync(file)) return {};
44
+ try {
45
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'));
46
+ return data && typeof data === 'object' && !Array.isArray(data) ? data : null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ // 'exists' | 'missing' | 'unreadable' (bozuk dosyaya dokunulmaz)
53
+ function vaultStatus(file, vault) {
54
+ const data = readConfig(file);
55
+ if (!data) return 'unreadable';
56
+ const target = path.resolve(vault);
57
+ const vaults = data.vaults && typeof data.vaults === 'object' ? Object.values(data.vaults) : [];
58
+ return vaults.some((v) => v && typeof v.path === 'string' && path.resolve(v.path) === target) ? 'exists' : 'missing';
59
+ }
60
+
61
+ // Kasayı Obsidian'ın listesine ekler; diğer alanlar ve kasalar korunur.
62
+ // Obsidian listeyi açılışta okuyup bellekte tuttuğu için yalnızca Obsidian kapalıyken çağrılmalı.
63
+ function registerVault(file, vault, { now = Date.now() } = {}) {
64
+ const status = vaultStatus(file, vault);
65
+ if (status !== 'missing') return status;
66
+ const data = readConfig(file);
67
+ const vaults = data.vaults && typeof data.vaults === 'object' ? data.vaults : {};
68
+ let id = crypto.randomBytes(8).toString('hex');
69
+ while (vaults[id]) id = crypto.randomBytes(8).toString('hex');
70
+ vaults[id] = { path: path.resolve(vault), ts: now };
71
+ data.vaults = vaults;
72
+ fs.mkdirSync(path.dirname(file), { recursive: true });
73
+ const tmp = `${file}.prosicht-${process.pid}`;
74
+ fs.writeFileSync(tmp, JSON.stringify(data));
75
+ fs.renameSync(tmp, file);
76
+ return 'added';
77
+ }
78
+
79
+ function openVaultUri(vault) {
80
+ return `obsidian://open?path=${encodeURIComponent(path.resolve(vault))}`;
81
+ }
82
+
83
+ async function waitUntilClosed(ms = 10000) {
84
+ const until = Date.now() + ms;
85
+ while (isRunning(PROCESS)) {
86
+ if (Date.now() > until) return false;
87
+ await new Promise((resolve) => setTimeout(resolve, 300));
88
+ }
89
+ return true;
90
+ }
91
+
92
+ // Homebrew yoksa resmi GitHub sürümünden DMG: imza Obsidian'ın geliştirici kimliğine ve notarizasyona bağlı olmalı
93
+ async function installMacDmg(tmp, download) {
94
+ const response = await fetch(RELEASES_API, { headers: { 'User-Agent': 'prosicht-cli' } });
95
+ if (!response.ok) throw new Error(`Obsidian surumu alinamadi (HTTP ${response.status})`);
96
+ const release = await response.json();
97
+ const asset = (release.assets || []).find((a) => /^Obsidian-\d+\.\d+\.\d+\.dmg$/.test(a.name));
98
+ if (!asset) throw new Error('Obsidian DMG dosyasi bulunamadi');
99
+
100
+ console.log(` ${asset.name} indiriliyor (${Math.round(asset.size / 1024 / 1024)} MB)...`);
101
+ const dmg = path.join(tmp, asset.name);
102
+ fs.writeFileSync(dmg, await download(asset.browser_download_url));
103
+ const mount = path.join(tmp, 'obsidian-dmg');
104
+ fs.mkdirSync(mount);
105
+ if (run('hdiutil', ['attach', '-nobrowse', '-readonly', '-noautoopen', '-mountpoint', mount, dmg]).status !== 0) {
106
+ throw new Error('DMG acilamadi');
107
+ }
108
+ try {
109
+ const app = path.join(mount, 'Obsidian.app');
110
+ const requirement = `=anchor apple generic and certificate leaf[subject.OU] = "${TEAM_ID}"`;
111
+ if (run('codesign', ['--verify', '--deep', '--strict', `-R${requirement}`, app]).status !== 0 ||
112
+ run('spctl', ['--assess', '--type', 'execute', app]).status !== 0) {
113
+ throw new Error('Obsidian imzasi dogrulanamadi; kurulmadi');
114
+ }
115
+ const applications = canWrite('/Applications') ? '/Applications' : path.join(os.homedir(), 'Applications');
116
+ fs.mkdirSync(applications, { recursive: true });
117
+ if (run('ditto', [app, path.join(applications, 'Obsidian.app')]).status !== 0) throw new Error('Obsidian kopyalanamadi');
118
+ } finally {
119
+ run('hdiutil', ['detach', mount, '-quiet']);
120
+ }
121
+ }
122
+
123
+ function canWrite(dir) {
124
+ try {
125
+ fs.accessSync(dir, fs.constants.W_OK);
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ async function install(tmp, download) {
133
+ if (IS_MAC) {
134
+ const brew = findBrew();
135
+ if (brew) return runVisible(brew, ['install', '--cask', 'obsidian']);
136
+ await installMacDmg(tmp, download);
137
+ return true;
138
+ }
139
+ if (IS_WIN && which('winget')) return runVisible('winget', ['install', '-e', '--id', 'Obsidian.Obsidian']);
140
+ if (!IS_WIN && which('flatpak')) return runVisible('flatpak', ['install', 'flathub', 'md.obsidian.Obsidian']);
141
+ return false;
142
+ }
143
+
144
+ // Obsidian kurulu değilse kurar. Dönüş: kurulu mu
145
+ async function ensure({ tmp, download, yes }) {
146
+ if (findApp()) {
147
+ console.log(' Obsidian kurulu.');
148
+ return true;
149
+ }
150
+ if (!yes && !(await confirm('Obsidian kurulu degil. Simdi kurulsun mu?'))) return false;
151
+ try {
152
+ if ((await install(tmp, download)) && findApp()) {
153
+ console.log(' Obsidian kuruldu.');
154
+ return true;
155
+ }
156
+ } catch (error) {
157
+ console.error(` Obsidian kurulamadi: ${error.message}`);
158
+ }
159
+ console.log(' Obsidian\'i https://obsidian.md/download adresinden kurabilirsiniz.');
160
+ return false;
161
+ }
162
+
163
+ // Kasayı Obsidian'a ekler ve açar. Obsidian açıksa listeyi bellekten geri yazacağı için önce kapanması gerekir.
164
+ async function addAndOpen(vault, { yes, open }) {
165
+ const file = configPath();
166
+ let status = vaultStatus(file, vault);
167
+ if (status === 'missing' && isRunning(PROCESS)) {
168
+ let closed = false;
169
+ if (!yes) {
170
+ if (!IS_MAC) {
171
+ await confirm('Obsidian acik. Beyin kasasini eklemek icin Obsidian\'i kapatin, sonra devam edin.');
172
+ closed = await waitUntilClosed(1000);
173
+ } else if (await confirm('Obsidian acik. Beyin kasasini eklemek icin Obsidian kapatilip yeniden acilsin mi? (Notlariniz zaten kayitli.)')) {
174
+ run('pkill', ['-TERM', '-x', PROCESS]);
175
+ closed = await waitUntilClosed();
176
+ }
177
+ }
178
+ if (!closed) status = 'running';
179
+ }
180
+ if (status === 'missing') status = registerVault(file, vault);
181
+
182
+ if (status === 'added' || status === 'exists') {
183
+ console.log(` Obsidian kasasi: ${vault}${status === 'added' ? ' (eklendi)' : ''}`);
184
+ if (open && openExternal(openVaultUri(vault))) console.log(' Obsidian aciliyor.');
185
+ return true;
186
+ }
187
+ if (status === 'unreadable') console.log(' Obsidian ayar dosyasi okunamadi; kasa listesine dokunulmadi.');
188
+ console.log(` Obsidian'da "Klasoru kasa olarak ac" ile su klasoru secin: ${vault}`);
189
+ if (open) openExternal('obsidian://choose-vault');
190
+ return false;
191
+ }
192
+
193
+ module.exports = { ensure, addAndOpen, findApp, configPath, vaultStatus, registerVault, openVaultUri };
package/lib/system.js ADDED
@@ -0,0 +1,58 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const { spawnSync } = require('child_process');
4
+ const prompts = require('prompts');
5
+
6
+ const IS_MAC = process.platform === 'darwin';
7
+ const IS_WIN = process.platform === 'win32';
8
+
9
+ function run(cmd, args, options = {}) {
10
+ return spawnSync(cmd, args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, ...options });
11
+ }
12
+
13
+ // Kurulum komutları (brew, winget): çıktı ve soruları doğrudan kullanıcının terminaline akar
14
+ function runVisible(cmd, args, options = {}) {
15
+ return spawnSync(cmd, args, { stdio: 'inherit', ...options }).status === 0;
16
+ }
17
+
18
+ // PATH'te komutu arar; yoksa null
19
+ function which(cmd) {
20
+ const result = run(IS_WIN ? 'where' : 'which', [cmd]);
21
+ if (result.status !== 0 || !result.stdout) return null;
22
+ return result.stdout.split(/\r?\n/)[0].trim() || null;
23
+ }
24
+
25
+ function expandHome(p) {
26
+ if (p === '~') return os.homedir();
27
+ if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(os.homedir(), p.slice(2));
28
+ return p;
29
+ }
30
+
31
+ function isRunning(name) {
32
+ if (IS_WIN) return run('tasklist', ['/FI', `IMAGENAME eq ${name}.exe`, '/NH']).stdout.includes(`${name}.exe`);
33
+ return run('pgrep', ['-x', name]).status === 0;
34
+ }
35
+
36
+ // URL'yi, klasörü ya da uygulamayı işletim sisteminin varsayılan yoluyla açar
37
+ function openExternal(target, app) {
38
+ if (IS_MAC) return run('open', app ? ['-a', app, target] : [target]).status === 0;
39
+ if (IS_WIN) return run('rundll32', ['url.dll,FileProtocolHandler', target]).status === 0;
40
+ return run('xdg-open', [target]).status === 0;
41
+ }
42
+
43
+ function copyToClipboard(text) {
44
+ const attempts = IS_MAC
45
+ ? [['pbcopy', []]]
46
+ : IS_WIN
47
+ ? [['powershell', ['-NoProfile', '-Command', '[Console]::InputEncoding=[Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())']]]
48
+ : [['wl-copy', []], ['xclip', ['-selection', 'clipboard']], ['xsel', ['--clipboard', '--input']]];
49
+ const env = { ...process.env, LANG: process.env.LANG || 'en_US.UTF-8' };
50
+ return attempts.some(([cmd, args]) => run(cmd, args, { input: text, env }).status === 0);
51
+ }
52
+
53
+ async function confirm(message, initial = true) {
54
+ const response = await prompts({ type: 'confirm', name: 'ok', initial, message });
55
+ return Boolean(response.ok);
56
+ }
57
+
58
+ module.exports = { IS_MAC, IS_WIN, run, runVisible, which, expandHome, isRunning, openExternal, copyToClipboard, confirm };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prosicht",
3
- "version": "1.7.0",
4
- "description": "Pro Sicht AI Standards CLI: AGENTS.md sablonlari, sunum ve blog altyapisi",
3
+ "version": "1.8.0",
4
+ "description": "Pro Sicht AI Standards CLI: AGENTS.md sablonlari, sunum ve blog altyapisi, ikinci beyin kurulumu",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "prosicht": "./index.js"
@@ -22,7 +22,9 @@
22
22
  "cli",
23
23
  "prosicht",
24
24
  "presentation",
25
- "blog"
25
+ "blog",
26
+ "obsidian",
27
+ "second-brain"
26
28
  ],
27
29
  "author": "Pro Sicht",
28
30
  "license": "MIT",